You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-08 12:40:44 +00:00
Merge pull request #94321 from fire/vsk-csg-manifold-update-4.3
Fix mesh corruption of CSG by using elalish/manifold
This commit is contained in:
@@ -334,6 +334,11 @@ Comment: WebP codec
|
||||
Copyright: 2010, Google Inc.
|
||||
License: BSD-3-clause
|
||||
|
||||
Files: ./thirdparty/manifold/
|
||||
Comment: Manifold
|
||||
Copyright: 2020-2024, The Manifold Authors
|
||||
License: Apache-2.0
|
||||
|
||||
Files: ./thirdparty/mbedtls/
|
||||
Comment: Mbed TLS
|
||||
Copyright: The Mbed TLS Contributors
|
||||
|
||||
@@ -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")
|
||||
|
||||
1428
modules/csg/csg.cpp
1428
modules/csg/csg.cpp
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
12
thirdparty/README.md
vendored
12
thirdparty/README.md
vendored
@@ -546,6 +546,18 @@ Patch `godot-node-debug-fix.patch` workarounds shadowing of Godot's Node class
|
||||
in the MSVC debugger.
|
||||
|
||||
|
||||
## manifold
|
||||
|
||||
- Upstream: https://github.com/elalish/manifold
|
||||
- Version: 3.0.0 (5d127e57fbfb89225a8e905d0d914ccc86c139c8, 2024)
|
||||
- License: Apache 2.0
|
||||
|
||||
File extracted from upstream source:
|
||||
|
||||
- `src/`
|
||||
- `AUTHORS`, `LICENSE`
|
||||
|
||||
|
||||
## mbedtls
|
||||
|
||||
- Upstream: https://github.com/Mbed-TLS/mbedtls
|
||||
|
||||
10
thirdparty/manifold/AUTHORS
vendored
Normal file
10
thirdparty/manifold/AUTHORS
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# This is the list of Manifold's significant contributors.
|
||||
#
|
||||
# This does not necessarily list everyone who has contributed code,
|
||||
# especially since many employees of one corporation may be contributing.
|
||||
# To see the full list of contributors, see the revision history in
|
||||
# source control.
|
||||
Emmett Lalish <elalish>
|
||||
Chun Kit LAM <pca006132>
|
||||
Geoff deRosenroll <geoffder>
|
||||
Google LLC
|
||||
201
thirdparty/manifold/LICENSE
vendored
Normal file
201
thirdparty/manifold/LICENSE
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
650
thirdparty/manifold/include/manifold/common.h
vendored
Normal file
650
thirdparty/manifold/include/manifold/common.h
vendored
Normal file
@@ -0,0 +1,650 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#include <chrono>
|
||||
#endif
|
||||
|
||||
#include "manifold/linalg.h"
|
||||
|
||||
namespace manifold {
|
||||
/** @addtogroup Math
|
||||
* @ingroup Core
|
||||
* @brief Simple math operations.
|
||||
* */
|
||||
|
||||
/** @addtogroup LinAlg
|
||||
* @{
|
||||
*/
|
||||
namespace la = linalg;
|
||||
using vec2 = la::vec<double, 2>;
|
||||
using vec3 = la::vec<double, 3>;
|
||||
using vec4 = la::vec<double, 4>;
|
||||
using bvec4 = la::vec<bool, 4>;
|
||||
using mat2 = la::mat<double, 2, 2>;
|
||||
using mat3x2 = la::mat<double, 3, 2>;
|
||||
using mat4x2 = la::mat<double, 4, 2>;
|
||||
using mat2x3 = la::mat<double, 2, 3>;
|
||||
using mat3 = la::mat<double, 3, 3>;
|
||||
using mat4x3 = la::mat<double, 4, 3>;
|
||||
using mat3x4 = la::mat<double, 3, 4>;
|
||||
using mat4 = la::mat<double, 4, 4>;
|
||||
using ivec2 = la::vec<int, 2>;
|
||||
using ivec3 = la::vec<int, 3>;
|
||||
using ivec4 = la::vec<int, 4>;
|
||||
using quat = la::vec<double, 4>;
|
||||
/** @} */
|
||||
|
||||
/** @addtogroup Scalar
|
||||
* @ingroup Math
|
||||
* @brief Simple scalar operations.
|
||||
* @{
|
||||
*/
|
||||
|
||||
constexpr double kPi = 3.14159265358979323846264338327950288;
|
||||
constexpr double kTwoPi = 6.28318530717958647692528676655900576;
|
||||
constexpr double kHalfPi = 1.57079632679489661923132169163975144;
|
||||
|
||||
/**
|
||||
* Convert degrees to radians.
|
||||
*
|
||||
* @param a Angle in degrees.
|
||||
*/
|
||||
constexpr double radians(double a) { return a * kPi / 180; }
|
||||
|
||||
/**
|
||||
* Convert radians to degrees.
|
||||
*
|
||||
* @param a Angle in radians.
|
||||
*/
|
||||
constexpr double degrees(double a) { return a * 180 / kPi; }
|
||||
|
||||
/**
|
||||
* Performs smooth Hermite interpolation between 0 and 1 when edge0 < x < edge1.
|
||||
*
|
||||
* @param edge0 Specifies the value of the lower edge of the Hermite function.
|
||||
* @param edge1 Specifies the value of the upper edge of the Hermite function.
|
||||
* @param a Specifies the source value for interpolation.
|
||||
*/
|
||||
constexpr double smoothstep(double edge0, double edge1, double a) {
|
||||
const double x = la::clamp((a - edge0) / (edge1 - edge0), 0, 1);
|
||||
return x * x * (3 - 2 * x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sine function where multiples of 90 degrees come out exact.
|
||||
*
|
||||
* @param x Angle in degrees.
|
||||
*/
|
||||
inline double sind(double x) {
|
||||
if (!la::isfinite(x)) return sin(x);
|
||||
if (x < 0.0) return -sind(-x);
|
||||
int quo;
|
||||
x = remquo(fabs(x), 90.0, &quo);
|
||||
switch (quo % 4) {
|
||||
case 0:
|
||||
return sin(radians(x));
|
||||
case 1:
|
||||
return cos(radians(x));
|
||||
case 2:
|
||||
return -sin(radians(x));
|
||||
case 3:
|
||||
return -cos(radians(x));
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cosine function where multiples of 90 degrees come out exact.
|
||||
*
|
||||
* @param x Angle in degrees.
|
||||
*/
|
||||
inline double cosd(double x) { return sind(x + 90.0); }
|
||||
/** @} */
|
||||
|
||||
/** @addtogroup Structs
|
||||
* @ingroup Core
|
||||
* @brief Miscellaneous data structures for interfacing with this library.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Single polygon contour, wound CCW. First and last point are implicitly
|
||||
* connected. Should ensure all input is
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
using SimplePolygon = std::vector<vec2>;
|
||||
|
||||
/**
|
||||
* @brief Set of polygons with holes. Order of contours is arbitrary. Can
|
||||
* contain any depth of nested holes and any number of separate polygons. Should
|
||||
* ensure all input is
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
using Polygons = std::vector<SimplePolygon>;
|
||||
|
||||
/**
|
||||
* @brief Defines which edges to sharpen and how much for the Manifold.Smooth()
|
||||
* constructor.
|
||||
*/
|
||||
struct Smoothness {
|
||||
/// The halfedge index = 3 * tri + i, referring to Mesh.triVerts[tri][i].
|
||||
size_t halfedge;
|
||||
/// A value between 0 and 1, where 0 is sharp and 1 is the default and the
|
||||
/// curvature is interpolated between these values. The two paired halfedges
|
||||
/// can have different values while maintaining C-1 continuity (except for 0).
|
||||
double smoothness;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Axis-aligned 3D box, primarily for bounding.
|
||||
*/
|
||||
struct Box {
|
||||
vec3 min = vec3(std::numeric_limits<double>::infinity());
|
||||
vec3 max = vec3(-std::numeric_limits<double>::infinity());
|
||||
|
||||
/**
|
||||
* Default constructor is an infinite box that contains all space.
|
||||
*/
|
||||
constexpr Box() {}
|
||||
|
||||
/**
|
||||
* Creates a box that contains the two given points.
|
||||
*/
|
||||
constexpr Box(const vec3 p1, const vec3 p2) {
|
||||
min = la::min(p1, p2);
|
||||
max = la::max(p1, p2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dimensions of the Box.
|
||||
*/
|
||||
constexpr vec3 Size() const { return max - min; }
|
||||
|
||||
/**
|
||||
* Returns the center point of the Box.
|
||||
*/
|
||||
constexpr vec3 Center() const { return 0.5 * (max + min); }
|
||||
|
||||
/**
|
||||
* Returns the absolute-largest coordinate value of any contained
|
||||
* point.
|
||||
*/
|
||||
constexpr double Scale() const {
|
||||
vec3 absMax = la::max(la::abs(min), la::abs(max));
|
||||
return la::max(absMax.x, la::max(absMax.y, absMax.z));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this box contain (includes equal) the given point?
|
||||
*/
|
||||
constexpr bool Contains(const vec3& p) const {
|
||||
return la::all(la::gequal(p, min)) && la::all(la::gequal(max, p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this box contain (includes equal) the given box?
|
||||
*/
|
||||
constexpr bool Contains(const Box& box) const {
|
||||
return la::all(la::gequal(box.min, min)) &&
|
||||
la::all(la::gequal(max, box.max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this box to include the given point.
|
||||
*/
|
||||
void Union(const vec3 p) {
|
||||
min = la::min(min, p);
|
||||
max = la::max(max, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this box to include the given box.
|
||||
*/
|
||||
constexpr Box Union(const Box& box) const {
|
||||
Box out;
|
||||
out.min = la::min(min, box.min);
|
||||
out.max = la::max(max, box.max);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the given box by the given axis-aligned affine transform.
|
||||
*
|
||||
* Ensure the transform passed in is axis-aligned (rotations are all
|
||||
* multiples of 90 degrees), or else the resulting bounding box will no longer
|
||||
* bound properly.
|
||||
*/
|
||||
constexpr Box Transform(const mat3x4& transform) const {
|
||||
Box out;
|
||||
vec3 minT = transform * vec4(min, 1.0);
|
||||
vec3 maxT = transform * vec4(max, 1.0);
|
||||
out.min = la::min(minT, maxT);
|
||||
out.max = la::max(minT, maxT);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this box by the given vector.
|
||||
*/
|
||||
constexpr Box operator+(vec3 shift) const {
|
||||
Box out;
|
||||
out.min = min + shift;
|
||||
out.max = max + shift;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this box in-place by the given vector.
|
||||
*/
|
||||
Box& operator+=(vec3 shift) {
|
||||
min += shift;
|
||||
max += shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this box by the given vector.
|
||||
*/
|
||||
constexpr Box operator*(vec3 scale) const {
|
||||
Box out;
|
||||
out.min = min * scale;
|
||||
out.max = max * scale;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this box in-place by the given vector.
|
||||
*/
|
||||
Box& operator*=(vec3 scale) {
|
||||
min *= scale;
|
||||
max *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this box overlap the one given (including equality)?
|
||||
*/
|
||||
constexpr bool DoesOverlap(const Box& box) const {
|
||||
return min.x <= box.max.x && min.y <= box.max.y && min.z <= box.max.z &&
|
||||
max.x >= box.min.x && max.y >= box.min.y && max.z >= box.min.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the given point project within the XY extent of this box
|
||||
* (including equality)?
|
||||
*/
|
||||
constexpr bool DoesOverlap(vec3 p) const { // projected in z
|
||||
return p.x <= max.x && p.x >= min.x && p.y <= max.y && p.y >= min.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this box have finite bounds?
|
||||
*/
|
||||
constexpr bool IsFinite() const {
|
||||
return la::all(la::isfinite(min)) && la::all(la::isfinite(max));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Axis-aligned 2D box, primarily for bounding.
|
||||
*/
|
||||
struct Rect {
|
||||
vec2 min = vec2(std::numeric_limits<double>::infinity());
|
||||
vec2 max = vec2(-std::numeric_limits<double>::infinity());
|
||||
|
||||
/**
|
||||
* Default constructor is an empty rectangle..
|
||||
*/
|
||||
constexpr Rect() {}
|
||||
|
||||
/**
|
||||
* Create a rectangle that contains the two given points.
|
||||
*/
|
||||
constexpr Rect(const vec2 a, const vec2 b) {
|
||||
min = la::min(a, b);
|
||||
max = la::max(a, b);
|
||||
}
|
||||
|
||||
/** @name Information
|
||||
* Details of the rectangle
|
||||
*/
|
||||
///@{
|
||||
|
||||
/**
|
||||
* Return the dimensions of the rectangle.
|
||||
*/
|
||||
constexpr vec2 Size() const { return max - min; }
|
||||
|
||||
/**
|
||||
* Return the area of the rectangle.
|
||||
*/
|
||||
constexpr double Area() const {
|
||||
auto sz = Size();
|
||||
return sz.x * sz.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute-largest coordinate value of any contained
|
||||
* point.
|
||||
*/
|
||||
constexpr double Scale() const {
|
||||
vec2 absMax = la::max(la::abs(min), la::abs(max));
|
||||
return la::max(absMax.x, absMax.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the center point of the rectangle.
|
||||
*/
|
||||
constexpr vec2 Center() const { return 0.5 * (max + min); }
|
||||
|
||||
/**
|
||||
* Does this rectangle contain (includes on border) the given point?
|
||||
*/
|
||||
constexpr bool Contains(const vec2& p) const {
|
||||
return la::all(la::gequal(p, min)) && la::all(la::gequal(max, p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this rectangle contain (includes equal) the given rectangle?
|
||||
*/
|
||||
constexpr bool Contains(const Rect& rect) const {
|
||||
return la::all(la::gequal(rect.min, min)) &&
|
||||
la::all(la::gequal(max, rect.max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this rectangle overlap the one given (including equality)?
|
||||
*/
|
||||
constexpr bool DoesOverlap(const Rect& rect) const {
|
||||
return min.x <= rect.max.x && min.y <= rect.max.y && max.x >= rect.min.x &&
|
||||
max.y >= rect.min.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the rectangle empty (containing no space)?
|
||||
*/
|
||||
constexpr bool IsEmpty() const { return max.y <= min.y || max.x <= min.x; };
|
||||
|
||||
/**
|
||||
* Does this recangle have finite bounds?
|
||||
*/
|
||||
constexpr bool IsFinite() const {
|
||||
return la::all(la::isfinite(min)) && la::all(la::isfinite(max));
|
||||
}
|
||||
|
||||
///@}
|
||||
|
||||
/** @name Modification
|
||||
*/
|
||||
///@{
|
||||
|
||||
/**
|
||||
* Expand this rectangle (in place) to include the given point.
|
||||
*/
|
||||
void Union(const vec2 p) {
|
||||
min = la::min(min, p);
|
||||
max = la::max(max, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this rectangle to include the given Rect.
|
||||
*/
|
||||
constexpr Rect Union(const Rect& rect) const {
|
||||
Rect out;
|
||||
out.min = la::min(min, rect.min);
|
||||
out.max = la::max(max, rect.max);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this rectangle by the given vector.
|
||||
*/
|
||||
constexpr Rect operator+(const vec2 shift) const {
|
||||
Rect out;
|
||||
out.min = min + shift;
|
||||
out.max = max + shift;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& operator+=(const vec2 shift) {
|
||||
min += shift;
|
||||
max += shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this rectangle by the given vector.
|
||||
*/
|
||||
constexpr Rect operator*(const vec2 scale) const {
|
||||
Rect out;
|
||||
out.min = min * scale;
|
||||
out.max = max * scale;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& operator*=(const vec2 scale) {
|
||||
min *= scale;
|
||||
max *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the rectangle by the given axis-aligned affine transform.
|
||||
*
|
||||
* Ensure the transform passed in is axis-aligned (rotations are all
|
||||
* multiples of 90 degrees), or else the resulting rectangle will no longer
|
||||
* bound properly.
|
||||
*/
|
||||
constexpr Rect Transform(const mat2x3& m) const {
|
||||
Rect rect;
|
||||
rect.min = m * vec3(min, 1);
|
||||
rect.max = m * vec3(max, 1);
|
||||
return rect;
|
||||
}
|
||||
///@}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Boolean operation type: Add (Union), Subtract (Difference), and
|
||||
* Intersect.
|
||||
*/
|
||||
enum class OpType { Add, Subtract, Intersect };
|
||||
|
||||
constexpr int DEFAULT_SEGMENTS = 0;
|
||||
constexpr double DEFAULT_ANGLE = 10.0;
|
||||
constexpr double DEFAULT_LENGTH = 1.0;
|
||||
/**
|
||||
* @brief These static properties control how circular shapes are quantized by
|
||||
* default on construction.
|
||||
*
|
||||
* If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
class Quality {
|
||||
private:
|
||||
inline static int circularSegments_ = DEFAULT_SEGMENTS;
|
||||
inline static double circularAngle_ = DEFAULT_ANGLE;
|
||||
inline static double circularEdgeLength_ = DEFAULT_LENGTH;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets an angle constraint the default number of circular segments for the
|
||||
* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and
|
||||
* Manifold::Revolve() constructors. The number of segments will be rounded up
|
||||
* to the nearest factor of four.
|
||||
*
|
||||
* @param angle The minimum angle in degrees between consecutive segments. The
|
||||
* angle will increase if the the segments hit the minimum edge length.
|
||||
* Default is 10 degrees.
|
||||
*/
|
||||
static void SetMinCircularAngle(double angle) {
|
||||
if (angle <= 0) return;
|
||||
circularAngle_ = angle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a length constraint the default number of circular segments for the
|
||||
* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and
|
||||
* Manifold::Revolve() constructors. The number of segments will be rounded up
|
||||
* to the nearest factor of four.
|
||||
*
|
||||
* @param length The minimum length of segments. The length will
|
||||
* increase if the the segments hit the minimum angle. Default is 1.0.
|
||||
*/
|
||||
static void SetMinCircularEdgeLength(double length) {
|
||||
if (length <= 0) return;
|
||||
circularEdgeLength_ = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default number of circular segments for the
|
||||
* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and
|
||||
* Manifold::Revolve() constructors. Overrides the edge length and angle
|
||||
* constraints and sets the number of segments to exactly this value.
|
||||
*
|
||||
* @param number Number of circular segments. Default is 0, meaning no
|
||||
* constraint is applied.
|
||||
*/
|
||||
static void SetCircularSegments(int number) {
|
||||
if (number < 3 && number != 0) return;
|
||||
circularSegments_ = number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the result of the SetMinCircularAngle(),
|
||||
* SetMinCircularEdgeLength(), and SetCircularSegments() defaults.
|
||||
*
|
||||
* @param radius For a given radius of circle, determine how many default
|
||||
* segments there will be.
|
||||
*/
|
||||
static int GetCircularSegments(double radius) {
|
||||
if (circularSegments_ > 0) return circularSegments_;
|
||||
int nSegA = 360.0 / circularAngle_;
|
||||
int nSegL = 2.0 * radius * kPi / circularEdgeLength_;
|
||||
int nSeg = fmin(nSegA, nSegL) + 3;
|
||||
nSeg -= nSeg % 4;
|
||||
return std::max(nSeg, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the circular construction parameters to their defaults if
|
||||
* SetMinCircularAngle, SetMinCircularEdgeLength, or SetCircularSegments have
|
||||
* been called.
|
||||
*/
|
||||
static void ResetToDefaults() {
|
||||
circularSegments_ = DEFAULT_SEGMENTS;
|
||||
circularAngle_ = DEFAULT_ANGLE;
|
||||
circularEdgeLength_ = DEFAULT_LENGTH;
|
||||
}
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/** @addtogroup Debug
|
||||
* @ingroup Optional
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Global parameters that control debugging output. Only has an
|
||||
* effect when compiled with the MANIFOLD_DEBUG flag.
|
||||
*/
|
||||
struct ExecutionParams {
|
||||
/// Perform extra sanity checks and assertions on the intermediate data
|
||||
/// structures.
|
||||
bool intermediateChecks = false;
|
||||
/// Verbose output primarily of the Boolean, including timing info and vector
|
||||
/// sizes.
|
||||
bool verbose = false;
|
||||
/// If processOverlaps is false, a geometric check will be performed to assert
|
||||
/// all triangles are CCW.
|
||||
bool processOverlaps = true;
|
||||
/// Suppresses printed errors regarding CW triangles. Has no effect if
|
||||
/// processOverlaps is true.
|
||||
bool suppressErrors = false;
|
||||
/// Perform optional but recommended triangle cleanups in SimplifyTopology()
|
||||
bool cleanupTriangles = true;
|
||||
};
|
||||
/** @} */
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Box& box) {
|
||||
return stream << "min: " << box.min << ", "
|
||||
<< "max: " << box.max;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Rect& box) {
|
||||
return stream << "min: " << box.min << ", "
|
||||
<< "max: " << box.max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the contents of this vector to standard output. Only exists if compiled
|
||||
* with MANIFOLD_DEBUG flag.
|
||||
*/
|
||||
template <typename T>
|
||||
void Dump(const std::vector<T>& vec) {
|
||||
std::cout << "Vec = " << std::endl;
|
||||
for (size_t i = 0; i < vec.size(); ++i) {
|
||||
std::cout << i << ", " << vec[i] << ", " << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Diff(const std::vector<T>& a, const std::vector<T>& b) {
|
||||
std::cout << "Diff = " << std::endl;
|
||||
if (a.size() != b.size()) {
|
||||
std::cout << "a and b must have the same length, aborting Diff"
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < a.size(); ++i) {
|
||||
if (a[i] != b[i])
|
||||
std::cout << i << ": " << a[i] << ", " << b[i] << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
struct Timer {
|
||||
std::chrono::high_resolution_clock::time_point start, end;
|
||||
|
||||
void Start() { start = std::chrono::high_resolution_clock::now(); }
|
||||
|
||||
void Stop() { end = std::chrono::high_resolution_clock::now(); }
|
||||
|
||||
float Elapsed() {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
|
||||
.count();
|
||||
}
|
||||
void Print(std::string message) {
|
||||
std::cout << "----------- " << std::round(Elapsed()) << " ms for "
|
||||
<< message << std::endl;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
} // namespace manifold
|
||||
314
thirdparty/manifold/include/manifold/iters.h
vendored
Normal file
314
thirdparty/manifold/include/manifold/iters.h
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
|
||||
namespace manifold {
|
||||
|
||||
template <typename F, typename Iter>
|
||||
struct TransformIterator {
|
||||
private:
|
||||
Iter iter;
|
||||
F f;
|
||||
|
||||
public:
|
||||
using pointer = void;
|
||||
using reference = std::invoke_result_t<
|
||||
F, typename std::iterator_traits<std::remove_const_t<Iter>>::value_type>;
|
||||
using difference_type =
|
||||
typename std::iterator_traits<std::remove_const_t<Iter>>::difference_type;
|
||||
using value_type = reference;
|
||||
using iterator_category = typename std::iterator_traits<
|
||||
std::remove_const_t<Iter>>::iterator_category;
|
||||
|
||||
constexpr TransformIterator(Iter iter, F f) : iter(iter), f(f) {}
|
||||
|
||||
TransformIterator& operator=(const TransformIterator& other) {
|
||||
if (this == &other) return *this;
|
||||
// don't copy function, should be the same
|
||||
iter = other.iter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr reference operator*() const { return f(*iter); }
|
||||
|
||||
constexpr reference operator[](size_t i) const { return f(iter[i]); }
|
||||
|
||||
// prefix increment
|
||||
TransformIterator& operator++() {
|
||||
iter += 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// postfix
|
||||
TransformIterator operator++(int) {
|
||||
auto old = *this;
|
||||
operator++();
|
||||
return old;
|
||||
}
|
||||
|
||||
// prefix increment
|
||||
TransformIterator& operator--() {
|
||||
iter -= 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// postfix
|
||||
TransformIterator operator--(int) {
|
||||
auto old = *this;
|
||||
operator--();
|
||||
return old;
|
||||
}
|
||||
|
||||
constexpr TransformIterator operator+(size_t n) const {
|
||||
return TransformIterator(iter + n, f);
|
||||
}
|
||||
|
||||
TransformIterator& operator+=(size_t n) {
|
||||
iter += n;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr TransformIterator operator-(size_t n) const {
|
||||
return TransformIterator(iter - n, f);
|
||||
}
|
||||
|
||||
TransformIterator& operator-=(size_t n) {
|
||||
iter -= n;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool operator==(TransformIterator other) const {
|
||||
return iter == other.iter;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(TransformIterator other) const {
|
||||
return !(iter == other.iter);
|
||||
}
|
||||
|
||||
constexpr bool operator<(TransformIterator other) const {
|
||||
return iter < other.iter;
|
||||
}
|
||||
|
||||
constexpr difference_type operator-(TransformIterator other) const {
|
||||
return iter - other.iter;
|
||||
}
|
||||
|
||||
constexpr operator TransformIterator<F, const Iter>() const {
|
||||
return TransformIterator(f, iter);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct CountingIterator {
|
||||
private:
|
||||
T counter;
|
||||
|
||||
public:
|
||||
using pointer = void;
|
||||
using reference = T;
|
||||
using difference_type = std::make_signed_t<T>;
|
||||
using value_type = T;
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
constexpr CountingIterator(T counter) : counter(counter) {}
|
||||
|
||||
constexpr value_type operator*() const { return counter; }
|
||||
constexpr value_type operator[](T i) const { return counter + i; }
|
||||
|
||||
// prefix increment
|
||||
CountingIterator& operator++() {
|
||||
counter += 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// postfix
|
||||
CountingIterator operator++(int) {
|
||||
auto old = *this;
|
||||
operator++();
|
||||
return old;
|
||||
}
|
||||
|
||||
// prefix increment
|
||||
CountingIterator& operator--() {
|
||||
counter -= 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// postfix
|
||||
CountingIterator operator--(int) {
|
||||
auto old = *this;
|
||||
operator--();
|
||||
return old;
|
||||
}
|
||||
|
||||
constexpr CountingIterator operator+(T n) const {
|
||||
return CountingIterator(counter + n);
|
||||
}
|
||||
|
||||
CountingIterator& operator+=(T n) {
|
||||
counter += n;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr CountingIterator operator-(T n) const {
|
||||
return CountingIterator(counter - n);
|
||||
}
|
||||
|
||||
CountingIterator& operator-=(T n) {
|
||||
counter -= n;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr friend bool operator==(CountingIterator a, CountingIterator b) {
|
||||
return a.counter == b.counter;
|
||||
}
|
||||
|
||||
constexpr friend bool operator!=(CountingIterator a, CountingIterator b) {
|
||||
return a.counter != b.counter;
|
||||
}
|
||||
|
||||
constexpr friend bool operator<(CountingIterator a, CountingIterator b) {
|
||||
return a.counter < b.counter;
|
||||
}
|
||||
|
||||
constexpr friend difference_type operator-(CountingIterator a,
|
||||
CountingIterator b) {
|
||||
return a.counter - b.counter;
|
||||
}
|
||||
|
||||
constexpr operator CountingIterator<const T>() const {
|
||||
return CountingIterator(counter);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr CountingIterator<size_t> countAt(size_t i) {
|
||||
return CountingIterator(i);
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
struct StridedRange {
|
||||
private:
|
||||
struct StridedRangeIter {
|
||||
private:
|
||||
Iter iter;
|
||||
size_t stride;
|
||||
|
||||
public:
|
||||
using pointer =
|
||||
typename std::iterator_traits<std::remove_const_t<Iter>>::pointer;
|
||||
using reference =
|
||||
typename std::iterator_traits<std::remove_const_t<Iter>>::reference;
|
||||
using difference_type = typename std::iterator_traits<
|
||||
std::remove_const_t<Iter>>::difference_type;
|
||||
using value_type =
|
||||
typename std::iterator_traits<std::remove_const_t<Iter>>::value_type;
|
||||
using iterator_category = typename std::iterator_traits<
|
||||
std::remove_const_t<Iter>>::iterator_category;
|
||||
|
||||
constexpr StridedRangeIter(Iter iter, int stride)
|
||||
: iter(iter), stride(stride) {}
|
||||
|
||||
constexpr reference operator*() { return *iter; }
|
||||
|
||||
constexpr std::add_const_t<reference> operator*() const { return *iter; }
|
||||
|
||||
constexpr reference operator[](size_t i) { return iter[i * stride]; }
|
||||
|
||||
constexpr std::add_const_t<reference> operator[](size_t i) const {
|
||||
return iter[i * stride];
|
||||
}
|
||||
|
||||
// prefix increment
|
||||
StridedRangeIter& operator++() {
|
||||
iter += stride;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// postfix
|
||||
StridedRangeIter operator++(int) {
|
||||
auto old = *this;
|
||||
operator++();
|
||||
return old;
|
||||
}
|
||||
|
||||
// prefix increment
|
||||
StridedRangeIter& operator--() {
|
||||
iter -= stride;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// postfix
|
||||
StridedRangeIter operator--(int) {
|
||||
auto old = *this;
|
||||
operator--();
|
||||
return old;
|
||||
}
|
||||
|
||||
constexpr StridedRangeIter operator+(size_t n) const {
|
||||
return StridedRangeIter(iter + n * stride, stride);
|
||||
}
|
||||
|
||||
StridedRangeIter& operator+=(size_t n) {
|
||||
iter += n * stride;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr StridedRangeIter operator-(size_t n) const {
|
||||
return StridedRangeIter(iter - n * stride, stride);
|
||||
}
|
||||
|
||||
StridedRangeIter& operator-=(size_t n) {
|
||||
iter -= n * stride;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr friend bool operator==(StridedRangeIter a, StridedRangeIter b) {
|
||||
return a.iter == b.iter;
|
||||
}
|
||||
|
||||
constexpr friend bool operator!=(StridedRangeIter a, StridedRangeIter b) {
|
||||
return !(a.iter == b.iter);
|
||||
}
|
||||
|
||||
constexpr friend bool operator<(StridedRangeIter a, StridedRangeIter b) {
|
||||
return a.iter < b.iter;
|
||||
}
|
||||
|
||||
constexpr friend difference_type operator-(StridedRangeIter a,
|
||||
StridedRangeIter b) {
|
||||
// note that this is not well-defined if a.stride != b.stride...
|
||||
return (a.iter - b.iter) / a.stride;
|
||||
}
|
||||
};
|
||||
Iter _start, _end;
|
||||
const size_t stride;
|
||||
|
||||
public:
|
||||
constexpr StridedRange(Iter start, Iter end, size_t stride)
|
||||
: _start(start), _end(end), stride(stride) {}
|
||||
|
||||
constexpr StridedRangeIter begin() const {
|
||||
return StridedRangeIter(_start, stride);
|
||||
}
|
||||
|
||||
constexpr StridedRangeIter end() const {
|
||||
return StridedRangeIter(_start, stride) +
|
||||
((std::distance(_start, _end) + (stride - 1)) / stride);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
2601
thirdparty/manifold/include/manifold/linalg.h
vendored
Normal file
2601
thirdparty/manifold/include/manifold/linalg.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
435
thirdparty/manifold/include/manifold/manifold.h
vendored
Normal file
435
thirdparty/manifold/include/manifold/manifold.h
vendored
Normal file
@@ -0,0 +1,435 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/vec_view.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* @ingroup Debug
|
||||
*
|
||||
* Allows modification of the assertions checked in MANIFOLD_DEBUG mode.
|
||||
*
|
||||
* @return ExecutionParams&
|
||||
*/
|
||||
ExecutionParams& ManifoldParams();
|
||||
|
||||
class CsgNode;
|
||||
class CsgLeafNode;
|
||||
|
||||
/** @addtogroup Core
|
||||
* @brief The central classes of the library
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Mesh input/output suitable for pushing directly into graphics
|
||||
* libraries.
|
||||
*
|
||||
* This may not be manifold since the verts are duplicated along property
|
||||
* boundaries that do not match. The additional merge vectors store this missing
|
||||
* information, allowing the manifold to be reconstructed. MeshGL is an alias
|
||||
* for the standard single-precision version. Use MeshGL64 to output the full
|
||||
* double precision that Manifold uses internally.
|
||||
*/
|
||||
template <typename Precision, typename I = uint32_t>
|
||||
struct MeshGLP {
|
||||
/// Number of property vertices
|
||||
I NumVert() const { return vertProperties.size() / numProp; };
|
||||
/// Number of triangles
|
||||
I NumTri() const { return triVerts.size() / 3; };
|
||||
/// Number of properties per vertex, always >= 3.
|
||||
I numProp = 3;
|
||||
/// Flat, GL-style interleaved list of all vertex properties: propVal =
|
||||
/// vertProperties[vert * numProp + propIdx]. The first three properties are
|
||||
/// always the position x, y, z.
|
||||
std::vector<Precision> vertProperties;
|
||||
/// The vertex indices of the three triangle corners in CCW (from the outside)
|
||||
/// order, for each triangle.
|
||||
std::vector<I> triVerts;
|
||||
/// Optional: A list of only the vertex indicies that need to be merged to
|
||||
/// reconstruct the manifold.
|
||||
std::vector<I> mergeFromVert;
|
||||
/// Optional: The same length as mergeFromVert, and the corresponding value
|
||||
/// contains the vertex to merge with. It will have an identical position, but
|
||||
/// the other properties may differ.
|
||||
std::vector<I> mergeToVert;
|
||||
/// Optional: Indicates runs of triangles that correspond to a particular
|
||||
/// input mesh instance. The runs encompass all of triVerts and are sorted
|
||||
/// by runOriginalID. Run i begins at triVerts[runIndex[i]] and ends at
|
||||
/// triVerts[runIndex[i+1]]. All runIndex values are divisible by 3. Returned
|
||||
/// runIndex will always be 1 longer than runOriginalID, but same length is
|
||||
/// also allowed as input: triVerts.size() will be automatically appended in
|
||||
/// this case.
|
||||
std::vector<I> runIndex;
|
||||
/// Optional: The OriginalID of the mesh this triangle run came from. This ID
|
||||
/// is ideal for reapplying materials to the output mesh. Multiple runs may
|
||||
/// have the same ID, e.g. representing different copies of the same input
|
||||
/// mesh. If you create an input MeshGL that you want to be able to reference
|
||||
/// as one or more originals, be sure to set unique values from ReserveIDs().
|
||||
std::vector<uint32_t> runOriginalID;
|
||||
/// Optional: For each run, a 3x4 transform is stored representing how the
|
||||
/// corresponding original mesh was transformed to create this triangle run.
|
||||
/// This matrix is stored in column-major order and the length of the overall
|
||||
/// vector is 12 * runOriginalID.size().
|
||||
std::vector<Precision> runTransform;
|
||||
/// Optional: Length NumTri, contains the source face ID this
|
||||
/// triangle comes from. When auto-generated, this ID will be a triangle index
|
||||
/// into the original mesh. This index/ID is purely for external use (e.g.
|
||||
/// recreating polygonal faces) and will not affect Manifold's algorithms.
|
||||
std::vector<I> faceID;
|
||||
/// Optional: The X-Y-Z-W weighted tangent vectors for smooth Refine(). If
|
||||
/// non-empty, must be exactly four times as long as Mesh.triVerts. Indexed
|
||||
/// as 4 * (3 * tri + i) + j, i < 3, j < 4, representing the tangent value
|
||||
/// Mesh.triVerts[tri][i] along the CCW edge. If empty, mesh is faceted.
|
||||
std::vector<Precision> halfedgeTangent;
|
||||
/// Tolerance for mesh simplification. When creating a Manifold, the tolerance
|
||||
/// used will be the maximum of this and a baseline tolerance from the size of
|
||||
/// the bounding box. Any edge shorter than tolerance may be collapsed.
|
||||
/// Tolerance may be enlarged when floating point error accumulates.
|
||||
Precision tolerance = 0;
|
||||
|
||||
MeshGLP() = default;
|
||||
|
||||
/**
|
||||
* Updates the mergeFromVert and mergeToVert vectors in order to create a
|
||||
* manifold solid. If the MeshGL is already manifold, no change will occur and
|
||||
* the function will return false. Otherwise, this will merge verts along open
|
||||
* edges within tolerance (the maximum of the MeshGL tolerance and the
|
||||
* baseline bounding-box tolerance), keeping any from the existing merge
|
||||
* vectors, and return true.
|
||||
*
|
||||
* There is no guarantee the result will be manifold - this is a best-effort
|
||||
* helper function designed primarily to aid in the case where a manifold
|
||||
* multi-material MeshGL was produced, but its merge vectors were lost due to
|
||||
* a round-trip through a file format. Constructing a Manifold from the result
|
||||
* will report an error status if it is not manifold.
|
||||
*/
|
||||
bool Merge();
|
||||
|
||||
/**
|
||||
* Returns the x, y, z position of the ith vertex.
|
||||
*
|
||||
* @param v vertex index.
|
||||
*/
|
||||
la::vec<Precision, 3> GetVertPos(size_t v) const {
|
||||
size_t offset = v * numProp;
|
||||
return la::vec<Precision, 3>(vertProperties[offset],
|
||||
vertProperties[offset + 1],
|
||||
vertProperties[offset + 2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the three vertex indices of the ith triangle.
|
||||
*
|
||||
* @param t triangle index.
|
||||
*/
|
||||
la::vec<I, 3> GetTriVerts(size_t t) const {
|
||||
size_t offset = 3 * t;
|
||||
return la::vec<I, 3>(triVerts[offset], triVerts[offset + 1],
|
||||
triVerts[offset + 2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x, y, z, w tangent of the ith halfedge.
|
||||
*
|
||||
* @param h halfedge index (3 * triangle_index + [0|1|2]).
|
||||
*/
|
||||
la::vec<Precision, 4> GetTangent(size_t h) const {
|
||||
size_t offset = 4 * h;
|
||||
return la::vec<Precision, 4>(
|
||||
halfedgeTangent[offset], halfedgeTangent[offset + 1],
|
||||
halfedgeTangent[offset + 2], halfedgeTangent[offset + 3]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Single-precision - ideal for most uses, especially graphics.
|
||||
*/
|
||||
using MeshGL = MeshGLP<float>;
|
||||
/**
|
||||
* @brief Double-precision, 64-bit indices - best for huge meshes.
|
||||
*/
|
||||
using MeshGL64 = MeshGLP<double, uint64_t>;
|
||||
|
||||
/**
|
||||
* @brief This library's internal representation of an oriented, 2-manifold,
|
||||
* triangle mesh - a simple boundary-representation of a solid object. Use this
|
||||
* class to store and operate on solids, and use MeshGL for input and output.
|
||||
*
|
||||
* In addition to storing geometric data, a Manifold can also store an arbitrary
|
||||
* number of vertex properties. These could be anything, e.g. normals, UV
|
||||
* coordinates, colors, etc, but this library is completely agnostic. All
|
||||
* properties are merely float values indexed by channel number. It is up to the
|
||||
* user to associate channel numbers with meaning.
|
||||
*
|
||||
* Manifold allows vertex properties to be shared for efficient storage, or to
|
||||
* have multiple property verts associated with a single geometric vertex,
|
||||
* allowing sudden property changes, e.g. at Boolean intersections, without
|
||||
* sacrificing manifoldness.
|
||||
*
|
||||
* Manifolds also keep track of their relationships to their inputs, via
|
||||
* OriginalIDs and the faceIDs and transforms accessible through MeshGL. This
|
||||
* allows object-level properties to be re-associated with the output after many
|
||||
* operations, particularly useful for materials. Since separate object's
|
||||
* properties are not mixed, there is no requirement that channels have
|
||||
* consistent meaning between different inputs.
|
||||
*/
|
||||
class Manifold {
|
||||
public:
|
||||
/** @name Basics
|
||||
* Copy / move / assignment
|
||||
*/
|
||||
///@{
|
||||
Manifold();
|
||||
~Manifold();
|
||||
Manifold(const Manifold& other);
|
||||
Manifold& operator=(const Manifold& other);
|
||||
Manifold(Manifold&&) noexcept;
|
||||
Manifold& operator=(Manifold&&) noexcept;
|
||||
///@}
|
||||
|
||||
/** @name Input & Output
|
||||
* Create and retrieve arbitrary manifolds
|
||||
*/
|
||||
///@{
|
||||
Manifold(const MeshGL&);
|
||||
Manifold(const MeshGL64&);
|
||||
MeshGL GetMeshGL(int normalIdx = -1) const;
|
||||
MeshGL64 GetMeshGL64(int normalIdx = -1) const;
|
||||
///@}
|
||||
|
||||
/** @name Constructors
|
||||
* Topological ops, primitives, and SDF
|
||||
*/
|
||||
///@{
|
||||
std::vector<Manifold> Decompose() const;
|
||||
static Manifold Compose(const std::vector<Manifold>&);
|
||||
static Manifold Tetrahedron();
|
||||
static Manifold Cube(vec3 size = vec3(1.0), bool center = false);
|
||||
static Manifold Cylinder(double height, double radiusLow,
|
||||
double radiusHigh = -1.0, int circularSegments = 0,
|
||||
bool center = false);
|
||||
static Manifold Sphere(double radius, int circularSegments = 0);
|
||||
static Manifold LevelSet(std::function<double(vec3)> sdf, Box bounds,
|
||||
double edgeLength, double level = 0,
|
||||
double tolerance = -1, bool canParallel = true);
|
||||
///@}
|
||||
|
||||
/** @name Polygons
|
||||
* 3D to 2D and 2D to 3D
|
||||
*/
|
||||
///@{
|
||||
Polygons Slice(double height = 0) const;
|
||||
Polygons Project() const;
|
||||
static Manifold Extrude(const Polygons& crossSection, double height,
|
||||
int nDivisions = 0, double twistDegrees = 0.0,
|
||||
vec2 scaleTop = vec2(1.0));
|
||||
static Manifold Revolve(const Polygons& crossSection,
|
||||
int circularSegments = 0,
|
||||
double revolveDegrees = 360.0f);
|
||||
///@}
|
||||
|
||||
enum class Error {
|
||||
NoError,
|
||||
NonFiniteVertex,
|
||||
NotManifold,
|
||||
VertexOutOfBounds,
|
||||
PropertiesWrongLength,
|
||||
MissingPositionProperties,
|
||||
MergeVectorsDifferentLengths,
|
||||
MergeIndexOutOfBounds,
|
||||
TransformWrongLength,
|
||||
RunIndexWrongLength,
|
||||
FaceIDWrongLength,
|
||||
InvalidConstruction,
|
||||
};
|
||||
|
||||
/** @name Information
|
||||
* Details of the manifold
|
||||
*/
|
||||
///@{
|
||||
Error Status() const;
|
||||
bool IsEmpty() const;
|
||||
size_t NumVert() const;
|
||||
size_t NumEdge() const;
|
||||
size_t NumTri() const;
|
||||
size_t NumProp() const;
|
||||
size_t NumPropVert() const;
|
||||
Box BoundingBox() const;
|
||||
int Genus() const;
|
||||
double GetTolerance() const;
|
||||
///@}
|
||||
|
||||
/** @name Measurement
|
||||
*/
|
||||
///@{
|
||||
double SurfaceArea() const;
|
||||
double Volume() const;
|
||||
double MinGap(const Manifold& other, double searchLength) const;
|
||||
///@}
|
||||
|
||||
/** @name Mesh ID
|
||||
* Details of the manifold's relation to its input meshes, for the purposes
|
||||
* of reapplying mesh properties.
|
||||
*/
|
||||
///@{
|
||||
int OriginalID() const;
|
||||
Manifold AsOriginal() const;
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
///@}
|
||||
|
||||
/** @name Transformations
|
||||
*/
|
||||
///@{
|
||||
Manifold Translate(vec3) const;
|
||||
Manifold Scale(vec3) const;
|
||||
Manifold Rotate(double xDegrees, double yDegrees = 0.0,
|
||||
double zDegrees = 0.0) const;
|
||||
Manifold Mirror(vec3) const;
|
||||
Manifold Transform(const mat3x4&) const;
|
||||
Manifold Warp(std::function<void(vec3&)>) const;
|
||||
Manifold WarpBatch(std::function<void(VecView<vec3>)>) const;
|
||||
Manifold SetTolerance(double) const;
|
||||
///@}
|
||||
|
||||
/** @name Boolean
|
||||
* Combine two manifolds
|
||||
*/
|
||||
///@{
|
||||
Manifold Boolean(const Manifold& second, OpType op) const;
|
||||
static Manifold BatchBoolean(const std::vector<Manifold>& manifolds,
|
||||
OpType op);
|
||||
// Boolean operation shorthand
|
||||
Manifold operator+(const Manifold&) const; // Add (Union)
|
||||
Manifold& operator+=(const Manifold&);
|
||||
Manifold operator-(const Manifold&) const; // Subtract (Difference)
|
||||
Manifold& operator-=(const Manifold&);
|
||||
Manifold operator^(const Manifold&) const; // Intersect
|
||||
Manifold& operator^=(const Manifold&);
|
||||
std::pair<Manifold, Manifold> Split(const Manifold&) const;
|
||||
std::pair<Manifold, Manifold> SplitByPlane(vec3 normal,
|
||||
double originOffset) const;
|
||||
Manifold TrimByPlane(vec3 normal, double originOffset) const;
|
||||
///@}
|
||||
|
||||
/** @name Properties
|
||||
* Create and modify vertex properties.
|
||||
*/
|
||||
///@{
|
||||
Manifold SetProperties(
|
||||
int numProp,
|
||||
std::function<void(double*, vec3, const double*)> propFunc) const;
|
||||
Manifold CalculateCurvature(int gaussianIdx, int meanIdx) const;
|
||||
Manifold CalculateNormals(int normalIdx, double minSharpAngle = 60) const;
|
||||
///@}
|
||||
|
||||
/** @name Smoothing
|
||||
* Smooth meshes by calculating tangent vectors and refining to a higher
|
||||
* triangle count.
|
||||
*/
|
||||
///@{
|
||||
Manifold Refine(int) const;
|
||||
Manifold RefineToLength(double) const;
|
||||
Manifold RefineToTolerance(double) const;
|
||||
Manifold SmoothByNormals(int normalIdx) const;
|
||||
Manifold SmoothOut(double minSharpAngle = 60, double minSmoothness = 0) const;
|
||||
static Manifold Smooth(const MeshGL&,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {});
|
||||
static Manifold Smooth(const MeshGL64&,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {});
|
||||
///@}
|
||||
|
||||
/** @name Convex Hull
|
||||
*/
|
||||
///@{
|
||||
Manifold Hull() const;
|
||||
static Manifold Hull(const std::vector<Manifold>& manifolds);
|
||||
static Manifold Hull(const std::vector<vec3>& pts);
|
||||
///@}
|
||||
|
||||
/** @name Testing Hooks
|
||||
* These are just for internal testing.
|
||||
*/
|
||||
///@{
|
||||
bool MatchesTriNormals() const;
|
||||
size_t NumDegenerateTris() const;
|
||||
size_t NumOverlaps(const Manifold& second) const;
|
||||
double GetEpsilon() const;
|
||||
///@}
|
||||
|
||||
struct Impl;
|
||||
|
||||
private:
|
||||
Manifold(std::shared_ptr<CsgNode> pNode_);
|
||||
Manifold(std::shared_ptr<Impl> pImpl_);
|
||||
static Manifold Invalid();
|
||||
mutable std::shared_ptr<CsgNode> pNode_;
|
||||
|
||||
CsgLeafNode& GetCsgLeafNode() const;
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/** @addtogroup Debug
|
||||
* @ingroup Optional
|
||||
* @brief Debugging features
|
||||
*
|
||||
* The features require compiler flags to be enabled. Assertions are enabled
|
||||
* with the MANIFOLD_DEBUG flag and then controlled with ExecutionParams.
|
||||
* @{
|
||||
*/
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
inline std::string ToString(const Manifold::Error& error) {
|
||||
switch (error) {
|
||||
case Manifold::Error::NoError:
|
||||
return "No Error";
|
||||
case Manifold::Error::NonFiniteVertex:
|
||||
return "Non Finite Vertex";
|
||||
case Manifold::Error::NotManifold:
|
||||
return "Not Manifold";
|
||||
case Manifold::Error::VertexOutOfBounds:
|
||||
return "Vertex Out Of Bounds";
|
||||
case Manifold::Error::PropertiesWrongLength:
|
||||
return "Properties Wrong Length";
|
||||
case Manifold::Error::MissingPositionProperties:
|
||||
return "Missing Position Properties";
|
||||
case Manifold::Error::MergeVectorsDifferentLengths:
|
||||
return "Merge Vectors Different Lengths";
|
||||
case Manifold::Error::MergeIndexOutOfBounds:
|
||||
return "Merge Index Out Of Bounds";
|
||||
case Manifold::Error::TransformWrongLength:
|
||||
return "Transform Wrong Length";
|
||||
case Manifold::Error::RunIndexWrongLength:
|
||||
return "Run Index Wrong Length";
|
||||
case Manifold::Error::FaceIDWrongLength:
|
||||
return "Face ID Wrong Length";
|
||||
case Manifold::Error::InvalidConstruction:
|
||||
return "Invalid Construction";
|
||||
default:
|
||||
return "Unknown Error";
|
||||
};
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream,
|
||||
const Manifold::Error& error) {
|
||||
return stream << ToString(error);
|
||||
}
|
||||
#endif
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
66
thirdparty/manifold/include/manifold/optional_assert.h
vendored
Normal file
66
thirdparty/manifold/include/manifold/optional_assert.h
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
/** @addtogroup Debug
|
||||
* @{
|
||||
*/
|
||||
struct userErr : public virtual std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
struct topologyErr : public virtual std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
struct geometryErr : public virtual std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
using logicErr = std::logic_error;
|
||||
|
||||
template <typename Ex>
|
||||
void AssertFail(const char* file, int line, const char* cond, const char* msg) {
|
||||
std::ostringstream output;
|
||||
output << "Error in file: " << file << " (" << line << "): \'" << cond
|
||||
<< "\' is false: " << msg;
|
||||
throw Ex(output.str());
|
||||
}
|
||||
|
||||
template <typename Ex>
|
||||
void AssertFail(const char* file, int line, const std::string& cond,
|
||||
const std::string& msg) {
|
||||
std::ostringstream output;
|
||||
output << "Error in file: " << file << " (" << line << "): \'" << cond
|
||||
<< "\' is false: " << msg;
|
||||
throw Ex(output.str());
|
||||
}
|
||||
|
||||
// DEBUG_ASSERT is slightly slower due to the function call, but gives more
|
||||
// detailed info.
|
||||
#define DEBUG_ASSERT(condition, EX, msg) \
|
||||
if (!(condition)) AssertFail<EX>(__FILE__, __LINE__, #condition, msg);
|
||||
// ASSERT has almost no overhead, so better to use for frequent calls like
|
||||
// vector bounds checking.
|
||||
#define ASSERT(condition, EX) \
|
||||
if (!(condition)) throw(EX);
|
||||
#else
|
||||
#define DEBUG_ASSERT(condition, EX, msg)
|
||||
#define ASSERT(condition, EX)
|
||||
#endif
|
||||
/** @} */
|
||||
62
thirdparty/manifold/include/manifold/polygon.h
vendored
Normal file
62
thirdparty/manifold/include/manifold/polygon.h
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "manifold/common.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @addtogroup Structs
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Polygon vertex.
|
||||
*/
|
||||
struct PolyVert {
|
||||
/// X-Y position
|
||||
vec2 pos;
|
||||
/// ID or index into another vertex vector
|
||||
int idx;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Single polygon contour, wound CCW, with indices. First and last point
|
||||
* are implicitly connected. Should ensure all input is
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
using SimplePolygonIdx = std::vector<PolyVert>;
|
||||
|
||||
/**
|
||||
* @brief Set of indexed polygons with holes. Order of contours is arbitrary.
|
||||
* Can contain any depth of nested holes and any number of separate polygons.
|
||||
* Should ensure all input is
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
using PolygonsIdx = std::vector<SimplePolygonIdx>;
|
||||
/** @} */
|
||||
|
||||
/** @addtogroup Triangulation
|
||||
* @ingroup Core
|
||||
* @brief Polygon triangulation
|
||||
* @{
|
||||
*/
|
||||
std::vector<ivec3> TriangulateIdx(const PolygonsIdx &polys,
|
||||
double epsilon = -1);
|
||||
|
||||
std::vector<ivec3> Triangulate(const Polygons &polygons, double epsilon = -1);
|
||||
|
||||
ExecutionParams &PolygonParams();
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
151
thirdparty/manifold/include/manifold/vec_view.h
vendored
Normal file
151
thirdparty/manifold/include/manifold/vec_view.h
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "manifold/optional_assert.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* View for Vec, can perform offset operation.
|
||||
* This will be invalidated when the original vector is dropped or changes
|
||||
* length. Roughly equivalent to std::span<T> from c++20
|
||||
*/
|
||||
template <typename T>
|
||||
class VecView {
|
||||
public:
|
||||
using Iter = T *;
|
||||
using IterC = const T *;
|
||||
|
||||
VecView() : ptr_(nullptr), size_(0) {}
|
||||
|
||||
VecView(T *ptr, size_t size) : ptr_(ptr), size_(size) {}
|
||||
|
||||
VecView(const std::vector<std::remove_cv_t<T>> &v)
|
||||
: ptr_(v.data()), size_(v.size()) {}
|
||||
|
||||
VecView(const VecView &other) {
|
||||
ptr_ = other.ptr_;
|
||||
size_ = other.size_;
|
||||
}
|
||||
|
||||
VecView &operator=(const VecView &other) {
|
||||
ptr_ = other.ptr_;
|
||||
size_ = other.size_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// allows conversion to a const VecView
|
||||
operator VecView<const T>() const { return {ptr_, size_}; }
|
||||
|
||||
inline const T &operator[](size_t i) const {
|
||||
ASSERT(i < size_, std::out_of_range("Vec out of range"));
|
||||
return ptr_[i];
|
||||
}
|
||||
|
||||
inline T &operator[](size_t i) {
|
||||
ASSERT(i < size_, std::out_of_range("Vec out of range"));
|
||||
return ptr_[i];
|
||||
}
|
||||
|
||||
IterC cbegin() const { return ptr_; }
|
||||
IterC cend() const { return ptr_ + size_; }
|
||||
|
||||
IterC begin() const { return cbegin(); }
|
||||
IterC end() const { return cend(); }
|
||||
|
||||
Iter begin() { return ptr_; }
|
||||
Iter end() { return ptr_ + size_; }
|
||||
|
||||
const T &front() const {
|
||||
ASSERT(size_ != 0,
|
||||
std::out_of_range("Attempt to take the front of an empty vector"));
|
||||
return ptr_[0];
|
||||
}
|
||||
|
||||
const T &back() const {
|
||||
ASSERT(size_ != 0,
|
||||
std::out_of_range("Attempt to take the back of an empty vector"));
|
||||
return ptr_[size_ - 1];
|
||||
}
|
||||
|
||||
T &front() {
|
||||
ASSERT(size_ != 0,
|
||||
std::out_of_range("Attempt to take the front of an empty vector"));
|
||||
return ptr_[0];
|
||||
}
|
||||
|
||||
T &back() {
|
||||
ASSERT(size_ != 0,
|
||||
std::out_of_range("Attempt to take the back of an empty vector"));
|
||||
return ptr_[size_ - 1];
|
||||
}
|
||||
|
||||
size_t size() const { return size_; }
|
||||
|
||||
bool empty() const { return size_ == 0; }
|
||||
|
||||
VecView<T> view(size_t offset = 0,
|
||||
size_t length = std::numeric_limits<size_t>::max()) {
|
||||
if (length == std::numeric_limits<size_t>::max())
|
||||
length = this->size_ - offset;
|
||||
ASSERT(length >= 0, std::out_of_range("Vec::view out of range"));
|
||||
ASSERT(offset + length <= this->size_ && offset >= 0,
|
||||
std::out_of_range("Vec::view out of range"));
|
||||
return VecView<T>(this->ptr_ + offset, length);
|
||||
}
|
||||
|
||||
VecView<const T> cview(
|
||||
size_t offset = 0,
|
||||
size_t length = std::numeric_limits<size_t>::max()) const {
|
||||
if (length == std::numeric_limits<size_t>::max())
|
||||
length = this->size_ - offset;
|
||||
ASSERT(length >= 0, std::out_of_range("Vec::cview out of range"));
|
||||
ASSERT(offset + length <= this->size_ && offset >= 0,
|
||||
std::out_of_range("Vec::cview out of range"));
|
||||
return VecView<const T>(this->ptr_ + offset, length);
|
||||
}
|
||||
|
||||
VecView<const T> view(
|
||||
size_t offset = 0,
|
||||
size_t length = std::numeric_limits<size_t>::max()) const {
|
||||
return cview(offset, length);
|
||||
}
|
||||
|
||||
T *data() { return this->ptr_; }
|
||||
|
||||
const T *data() const { return this->ptr_; }
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
void Dump() const {
|
||||
std::cout << "Vec = " << std::endl;
|
||||
for (size_t i = 0; i < size(); ++i) {
|
||||
std::cout << i << ", " << ptr_[i] << ", " << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
T *ptr_ = nullptr;
|
||||
size_t size_ = 0;
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
599
thirdparty/manifold/src/boolean3.cpp
vendored
Normal file
599
thirdparty/manifold/src/boolean3.cpp
vendored
Normal file
@@ -0,0 +1,599 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "./boolean3.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "./parallel.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
|
||||
// These two functions (Interpolate and Intersect) are the only places where
|
||||
// floating-point operations take place in the whole Boolean function. These
|
||||
// are carefully designed to minimize rounding error and to eliminate it at edge
|
||||
// cases to ensure consistency.
|
||||
|
||||
vec2 Interpolate(vec3 pL, vec3 pR, double x) {
|
||||
const double dxL = x - pL.x;
|
||||
const double dxR = x - pR.x;
|
||||
DEBUG_ASSERT(dxL * dxR <= 0, logicErr,
|
||||
"Boolean manifold error: not in domain");
|
||||
const bool useL = fabs(dxL) < fabs(dxR);
|
||||
const vec3 dLR = pR - pL;
|
||||
const double lambda = (useL ? dxL : dxR) / dLR.x;
|
||||
if (!std::isfinite(lambda) || !std::isfinite(dLR.y) || !std::isfinite(dLR.z))
|
||||
return vec2(pL.y, pL.z);
|
||||
vec2 yz;
|
||||
yz[0] = fma(lambda, dLR.y, useL ? pL.y : pR.y);
|
||||
yz[1] = fma(lambda, dLR.z, useL ? pL.z : pR.z);
|
||||
return yz;
|
||||
}
|
||||
|
||||
vec4 Intersect(const vec3 &pL, const vec3 &pR, const vec3 &qL, const vec3 &qR) {
|
||||
const double dyL = qL.y - pL.y;
|
||||
const double dyR = qR.y - pR.y;
|
||||
DEBUG_ASSERT(dyL * dyR <= 0, logicErr,
|
||||
"Boolean manifold error: no intersection");
|
||||
const bool useL = fabs(dyL) < fabs(dyR);
|
||||
const double dx = pR.x - pL.x;
|
||||
double lambda = (useL ? dyL : dyR) / (dyL - dyR);
|
||||
if (!std::isfinite(lambda)) lambda = 0.0;
|
||||
vec4 xyzz;
|
||||
xyzz.x = fma(lambda, dx, useL ? pL.x : pR.x);
|
||||
const double pDy = pR.y - pL.y;
|
||||
const double qDy = qR.y - qL.y;
|
||||
const bool useP = fabs(pDy) < fabs(qDy);
|
||||
xyzz.y = fma(lambda, useP ? pDy : qDy,
|
||||
useL ? (useP ? pL.y : qL.y) : (useP ? pR.y : qR.y));
|
||||
xyzz.z = fma(lambda, pR.z - pL.z, useL ? pL.z : pR.z);
|
||||
xyzz.w = fma(lambda, qR.z - qL.z, useL ? qL.z : qR.z);
|
||||
return xyzz;
|
||||
}
|
||||
|
||||
template <const bool inverted>
|
||||
struct CopyFaceEdges {
|
||||
const SparseIndices &p1q1;
|
||||
// x can be either vert or edge (0 or 1).
|
||||
SparseIndices &pXq1;
|
||||
VecView<const Halfedge> halfedgesQ;
|
||||
const size_t offset;
|
||||
|
||||
void operator()(const size_t i) {
|
||||
int idx = 3 * (i + offset);
|
||||
int pX = p1q1.Get(i, inverted);
|
||||
int q2 = p1q1.Get(i, !inverted);
|
||||
|
||||
for (const int j : {0, 1, 2}) {
|
||||
const int q1 = 3 * q2 + j;
|
||||
const Halfedge edge = halfedgesQ[q1];
|
||||
int a = pX;
|
||||
int b = edge.IsForward() ? q1 : edge.pairedHalfedge;
|
||||
if (inverted) std::swap(a, b);
|
||||
pXq1.Set(idx + static_cast<size_t>(j), a, b);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SparseIndices Filter11(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
const SparseIndices &p1q2, const SparseIndices &p2q1) {
|
||||
ZoneScoped;
|
||||
SparseIndices p1q1(3 * p1q2.size() + 3 * p2q1.size());
|
||||
for_each_n(autoPolicy(p1q2.size(), 1e5), countAt(0_uz), p1q2.size(),
|
||||
CopyFaceEdges<false>({p1q2, p1q1, inQ.halfedge_, 0_uz}));
|
||||
for_each_n(autoPolicy(p2q1.size(), 1e5), countAt(0_uz), p2q1.size(),
|
||||
CopyFaceEdges<true>({p2q1, p1q1, inP.halfedge_, p1q2.size()}));
|
||||
p1q1.Unique();
|
||||
return p1q1;
|
||||
}
|
||||
|
||||
inline bool Shadows(double p, double q, double dir) {
|
||||
return p == q ? dir < 0 : p < q;
|
||||
}
|
||||
|
||||
inline std::pair<int, vec2> Shadow01(
|
||||
const int p0, const int q1, VecView<const vec3> vertPosP,
|
||||
VecView<const vec3> vertPosQ, VecView<const Halfedge> halfedgeQ,
|
||||
const double expandP, VecView<const vec3> normalP, const bool reverse) {
|
||||
const int q1s = halfedgeQ[q1].startVert;
|
||||
const int q1e = halfedgeQ[q1].endVert;
|
||||
const double p0x = vertPosP[p0].x;
|
||||
const double q1sx = vertPosQ[q1s].x;
|
||||
const double q1ex = vertPosQ[q1e].x;
|
||||
int s01 = reverse ? Shadows(q1sx, p0x, expandP * normalP[q1s].x) -
|
||||
Shadows(q1ex, p0x, expandP * normalP[q1e].x)
|
||||
: Shadows(p0x, q1ex, expandP * normalP[p0].x) -
|
||||
Shadows(p0x, q1sx, expandP * normalP[p0].x);
|
||||
vec2 yz01(NAN);
|
||||
|
||||
if (s01 != 0) {
|
||||
yz01 = Interpolate(vertPosQ[q1s], vertPosQ[q1e], vertPosP[p0].x);
|
||||
if (reverse) {
|
||||
vec3 diff = vertPosQ[q1s] - vertPosP[p0];
|
||||
const double start2 = la::dot(diff, diff);
|
||||
diff = vertPosQ[q1e] - vertPosP[p0];
|
||||
const double end2 = la::dot(diff, diff);
|
||||
const double dir = start2 < end2 ? normalP[q1s].y : normalP[q1e].y;
|
||||
if (!Shadows(yz01[0], vertPosP[p0].y, expandP * dir)) s01 = 0;
|
||||
} else {
|
||||
if (!Shadows(vertPosP[p0].y, yz01[0], expandP * normalP[p0].y)) s01 = 0;
|
||||
}
|
||||
}
|
||||
return std::make_pair(s01, yz01);
|
||||
}
|
||||
|
||||
// https://github.com/scandum/binary_search/blob/master/README.md
|
||||
// much faster than standard binary search on large arrays
|
||||
size_t monobound_quaternary_search(VecView<const int64_t> array, int64_t key) {
|
||||
if (array.size() == 0) {
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
size_t bot = 0;
|
||||
size_t top = array.size();
|
||||
while (top >= 65536) {
|
||||
size_t mid = top / 4;
|
||||
top -= mid * 3;
|
||||
if (key < array[bot + mid * 2]) {
|
||||
if (key >= array[bot + mid]) {
|
||||
bot += mid;
|
||||
}
|
||||
} else {
|
||||
bot += mid * 2;
|
||||
if (key >= array[bot + mid]) {
|
||||
bot += mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (top > 3) {
|
||||
size_t mid = top / 2;
|
||||
if (key >= array[bot + mid]) {
|
||||
bot += mid;
|
||||
}
|
||||
top -= mid;
|
||||
}
|
||||
|
||||
while (top--) {
|
||||
if (key == array[bot + top]) {
|
||||
return bot + top;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct Kernel11 {
|
||||
VecView<vec4> xyzz;
|
||||
VecView<int> s;
|
||||
VecView<const vec3> vertPosP;
|
||||
VecView<const vec3> vertPosQ;
|
||||
VecView<const Halfedge> halfedgeP;
|
||||
VecView<const Halfedge> halfedgeQ;
|
||||
const double expandP;
|
||||
VecView<const vec3> normalP;
|
||||
const SparseIndices &p1q1;
|
||||
|
||||
void operator()(const size_t idx) {
|
||||
const int p1 = p1q1.Get(idx, false);
|
||||
const int q1 = p1q1.Get(idx, true);
|
||||
vec4 &xyzz11 = xyzz[idx];
|
||||
int &s11 = s[idx];
|
||||
|
||||
// For pRL[k], qRL[k], k==0 is the left and k==1 is the right.
|
||||
int k = 0;
|
||||
vec3 pRL[2], qRL[2];
|
||||
// Either the left or right must shadow, but not both. This ensures the
|
||||
// intersection is between the left and right.
|
||||
bool shadows = false;
|
||||
s11 = 0;
|
||||
|
||||
const int p0[2] = {halfedgeP[p1].startVert, halfedgeP[p1].endVert};
|
||||
for (int i : {0, 1}) {
|
||||
const auto syz01 = Shadow01(p0[i], q1, vertPosP, vertPosQ, halfedgeQ,
|
||||
expandP, normalP, false);
|
||||
const int s01 = syz01.first;
|
||||
const vec2 yz01 = syz01.second;
|
||||
// If the value is NaN, then these do not overlap.
|
||||
if (std::isfinite(yz01[0])) {
|
||||
s11 += s01 * (i == 0 ? -1 : 1);
|
||||
if (k < 2 && (k == 0 || (s01 != 0) != shadows)) {
|
||||
shadows = s01 != 0;
|
||||
pRL[k] = vertPosP[p0[i]];
|
||||
qRL[k] = vec3(pRL[k].x, yz01.x, yz01.y);
|
||||
++k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int q0[2] = {halfedgeQ[q1].startVert, halfedgeQ[q1].endVert};
|
||||
for (int i : {0, 1}) {
|
||||
const auto syz10 = Shadow01(q0[i], p1, vertPosQ, vertPosP, halfedgeP,
|
||||
expandP, normalP, true);
|
||||
const int s10 = syz10.first;
|
||||
const vec2 yz10 = syz10.second;
|
||||
// If the value is NaN, then these do not overlap.
|
||||
if (std::isfinite(yz10[0])) {
|
||||
s11 += s10 * (i == 0 ? -1 : 1);
|
||||
if (k < 2 && (k == 0 || (s10 != 0) != shadows)) {
|
||||
shadows = s10 != 0;
|
||||
qRL[k] = vertPosQ[q0[i]];
|
||||
pRL[k] = vec3(qRL[k].x, yz10.x, yz10.y);
|
||||
++k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s11 == 0) { // No intersection
|
||||
xyzz11 = vec4(NAN);
|
||||
} else {
|
||||
DEBUG_ASSERT(k == 2, logicErr, "Boolean manifold error: s11");
|
||||
xyzz11 = Intersect(pRL[0], pRL[1], qRL[0], qRL[1]);
|
||||
|
||||
const int p1s = halfedgeP[p1].startVert;
|
||||
const int p1e = halfedgeP[p1].endVert;
|
||||
vec3 diff = vertPosP[p1s] - vec3(xyzz11);
|
||||
const double start2 = la::dot(diff, diff);
|
||||
diff = vertPosP[p1e] - vec3(xyzz11);
|
||||
const double end2 = la::dot(diff, diff);
|
||||
const double dir = start2 < end2 ? normalP[p1s].z : normalP[p1e].z;
|
||||
|
||||
if (!Shadows(xyzz11.z, xyzz11.w, expandP * dir)) s11 = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple<Vec<int>, Vec<vec4>> Shadow11(SparseIndices &p1q1,
|
||||
const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ,
|
||||
double expandP) {
|
||||
ZoneScoped;
|
||||
Vec<int> s11(p1q1.size());
|
||||
Vec<vec4> xyzz11(p1q1.size());
|
||||
|
||||
for_each_n(autoPolicy(p1q1.size(), 1e4), countAt(0_uz), p1q1.size(),
|
||||
Kernel11({xyzz11, s11, inP.vertPos_, inQ.vertPos_, inP.halfedge_,
|
||||
inQ.halfedge_, expandP, inP.vertNormal_, p1q1}));
|
||||
|
||||
p1q1.KeepFinite(xyzz11, s11);
|
||||
|
||||
return std::make_tuple(s11, xyzz11);
|
||||
};
|
||||
|
||||
struct Kernel02 {
|
||||
VecView<int> s;
|
||||
VecView<double> z;
|
||||
VecView<const vec3> vertPosP;
|
||||
VecView<const Halfedge> halfedgeQ;
|
||||
VecView<const vec3> vertPosQ;
|
||||
const double expandP;
|
||||
VecView<const vec3> vertNormalP;
|
||||
const SparseIndices &p0q2;
|
||||
const bool forward;
|
||||
|
||||
void operator()(const size_t idx) {
|
||||
const int p0 = p0q2.Get(idx, !forward);
|
||||
const int q2 = p0q2.Get(idx, forward);
|
||||
int &s02 = s[idx];
|
||||
double &z02 = z[idx];
|
||||
|
||||
// For yzzLR[k], k==0 is the left and k==1 is the right.
|
||||
int k = 0;
|
||||
vec3 yzzRL[2];
|
||||
// Either the left or right must shadow, but not both. This ensures the
|
||||
// intersection is between the left and right.
|
||||
bool shadows = false;
|
||||
int closestVert = -1;
|
||||
double minMetric = std::numeric_limits<double>::infinity();
|
||||
s02 = 0;
|
||||
|
||||
const vec3 posP = vertPosP[p0];
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int q1 = 3 * q2 + i;
|
||||
const Halfedge edge = halfedgeQ[q1];
|
||||
const int q1F = edge.IsForward() ? q1 : edge.pairedHalfedge;
|
||||
|
||||
if (!forward) {
|
||||
const int qVert = halfedgeQ[q1F].startVert;
|
||||
const vec3 diff = posP - vertPosQ[qVert];
|
||||
const double metric = la::dot(diff, diff);
|
||||
if (metric < minMetric) {
|
||||
minMetric = metric;
|
||||
closestVert = qVert;
|
||||
}
|
||||
}
|
||||
|
||||
const auto syz01 = Shadow01(p0, q1F, vertPosP, vertPosQ, halfedgeQ,
|
||||
expandP, vertNormalP, !forward);
|
||||
const int s01 = syz01.first;
|
||||
const vec2 yz01 = syz01.second;
|
||||
// If the value is NaN, then these do not overlap.
|
||||
if (std::isfinite(yz01[0])) {
|
||||
s02 += s01 * (forward == edge.IsForward() ? -1 : 1);
|
||||
if (k < 2 && (k == 0 || (s01 != 0) != shadows)) {
|
||||
shadows = s01 != 0;
|
||||
yzzRL[k++] = vec3(yz01[0], yz01[1], yz01[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s02 == 0) { // No intersection
|
||||
z02 = NAN;
|
||||
} else {
|
||||
DEBUG_ASSERT(k == 2, logicErr, "Boolean manifold error: s02");
|
||||
vec3 vertPos = vertPosP[p0];
|
||||
z02 = Interpolate(yzzRL[0], yzzRL[1], vertPos.y)[1];
|
||||
if (forward) {
|
||||
if (!Shadows(vertPos.z, z02, expandP * vertNormalP[p0].z)) s02 = 0;
|
||||
} else {
|
||||
// DEBUG_ASSERT(closestVert != -1, topologyErr, "No closest vert");
|
||||
if (!Shadows(z02, vertPos.z, expandP * vertNormalP[closestVert].z))
|
||||
s02 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple<Vec<int>, Vec<double>> Shadow02(const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ,
|
||||
SparseIndices &p0q2, bool forward,
|
||||
double expandP) {
|
||||
ZoneScoped;
|
||||
Vec<int> s02(p0q2.size());
|
||||
Vec<double> z02(p0q2.size());
|
||||
|
||||
auto vertNormalP = forward ? inP.vertNormal_ : inQ.vertNormal_;
|
||||
for_each_n(autoPolicy(p0q2.size(), 1e4), countAt(0_uz), p0q2.size(),
|
||||
Kernel02({s02, z02, inP.vertPos_, inQ.halfedge_, inQ.vertPos_,
|
||||
expandP, vertNormalP, p0q2, forward}));
|
||||
|
||||
p0q2.KeepFinite(z02, s02);
|
||||
|
||||
return std::make_tuple(s02, z02);
|
||||
};
|
||||
|
||||
struct Kernel12 {
|
||||
VecView<int> x;
|
||||
VecView<vec3> v;
|
||||
VecView<const int64_t> p0q2;
|
||||
VecView<const int> s02;
|
||||
VecView<const double> z02;
|
||||
VecView<const int64_t> p1q1;
|
||||
VecView<const int> s11;
|
||||
VecView<const vec4> xyzz11;
|
||||
VecView<const Halfedge> halfedgesP;
|
||||
VecView<const Halfedge> halfedgesQ;
|
||||
VecView<const vec3> vertPosP;
|
||||
const bool forward;
|
||||
const SparseIndices &p1q2;
|
||||
|
||||
void operator()(const size_t idx) {
|
||||
int p1 = p1q2.Get(idx, !forward);
|
||||
int q2 = p1q2.Get(idx, forward);
|
||||
int &x12 = x[idx];
|
||||
vec3 &v12 = v[idx];
|
||||
|
||||
// For xzyLR-[k], k==0 is the left and k==1 is the right.
|
||||
int k = 0;
|
||||
vec3 xzyLR0[2];
|
||||
vec3 xzyLR1[2];
|
||||
// Either the left or right must shadow, but not both. This ensures the
|
||||
// intersection is between the left and right.
|
||||
bool shadows = false;
|
||||
x12 = 0;
|
||||
|
||||
const Halfedge edge = halfedgesP[p1];
|
||||
|
||||
for (int vert : {edge.startVert, edge.endVert}) {
|
||||
const int64_t key = forward ? SparseIndices::EncodePQ(vert, q2)
|
||||
: SparseIndices::EncodePQ(q2, vert);
|
||||
const size_t idx = monobound_quaternary_search(p0q2, key);
|
||||
if (idx != std::numeric_limits<size_t>::max()) {
|
||||
const int s = s02[idx];
|
||||
x12 += s * ((vert == edge.startVert) == forward ? 1 : -1);
|
||||
if (k < 2 && (k == 0 || (s != 0) != shadows)) {
|
||||
shadows = s != 0;
|
||||
xzyLR0[k] = vertPosP[vert];
|
||||
std::swap(xzyLR0[k].y, xzyLR0[k].z);
|
||||
xzyLR1[k] = xzyLR0[k];
|
||||
xzyLR1[k][1] = z02[idx];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int q1 = 3 * q2 + i;
|
||||
const Halfedge edge = halfedgesQ[q1];
|
||||
const int q1F = edge.IsForward() ? q1 : edge.pairedHalfedge;
|
||||
const int64_t key = forward ? SparseIndices::EncodePQ(p1, q1F)
|
||||
: SparseIndices::EncodePQ(q1F, p1);
|
||||
const size_t idx = monobound_quaternary_search(p1q1, key);
|
||||
if (idx !=
|
||||
std::numeric_limits<size_t>::max()) { // s is implicitly zero for
|
||||
// anything not found
|
||||
const int s = s11[idx];
|
||||
x12 -= s * (edge.IsForward() ? 1 : -1);
|
||||
if (k < 2 && (k == 0 || (s != 0) != shadows)) {
|
||||
shadows = s != 0;
|
||||
const vec4 xyzz = xyzz11[idx];
|
||||
xzyLR0[k][0] = xyzz.x;
|
||||
xzyLR0[k][1] = xyzz.z;
|
||||
xzyLR0[k][2] = xyzz.y;
|
||||
xzyLR1[k] = xzyLR0[k];
|
||||
xzyLR1[k][1] = xyzz.w;
|
||||
if (!forward) std::swap(xzyLR0[k][1], xzyLR1[k][1]);
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (x12 == 0) { // No intersection
|
||||
v12 = vec3(NAN);
|
||||
} else {
|
||||
DEBUG_ASSERT(k == 2, logicErr, "Boolean manifold error: v12");
|
||||
const vec4 xzyy = Intersect(xzyLR0[0], xzyLR0[1], xzyLR1[0], xzyLR1[1]);
|
||||
v12.x = xzyy[0];
|
||||
v12.y = xzyy[2];
|
||||
v12.z = xzyy[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple<Vec<int>, Vec<vec3>> Intersect12(
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ, const Vec<int> &s02,
|
||||
const SparseIndices &p0q2, const Vec<int> &s11, const SparseIndices &p1q1,
|
||||
const Vec<double> &z02, const Vec<vec4> &xyzz11, SparseIndices &p1q2,
|
||||
bool forward) {
|
||||
ZoneScoped;
|
||||
Vec<int> x12(p1q2.size());
|
||||
Vec<vec3> v12(p1q2.size());
|
||||
|
||||
for_each_n(
|
||||
autoPolicy(p1q2.size(), 1e4), countAt(0_uz), p1q2.size(),
|
||||
Kernel12({x12, v12, p0q2.AsVec64(), s02, z02, p1q1.AsVec64(), s11, xyzz11,
|
||||
inP.halfedge_, inQ.halfedge_, inP.vertPos_, forward, p1q2}));
|
||||
|
||||
p1q2.KeepFinite(v12, x12);
|
||||
|
||||
return std::make_tuple(x12, v12);
|
||||
};
|
||||
|
||||
Vec<int> Winding03(const Manifold::Impl &inP, Vec<int> &vertices, Vec<int> &s02,
|
||||
bool reverse) {
|
||||
ZoneScoped;
|
||||
// verts that are not shadowed (not in p0q2) have winding number zero.
|
||||
Vec<int> w03(inP.NumVert(), 0);
|
||||
if (vertices.size() <= 1e5) {
|
||||
for_each_n(ExecutionPolicy::Seq, countAt(0), s02.size(),
|
||||
[&w03, &vertices, &s02, reverse](const int i) {
|
||||
w03[vertices[i]] += s02[i] * (reverse ? -1 : 1);
|
||||
});
|
||||
} else {
|
||||
for_each_n(ExecutionPolicy::Par, countAt(0), s02.size(),
|
||||
[&w03, &vertices, &s02, reverse](const int i) {
|
||||
AtomicAdd(w03[vertices[i]], s02[i] * (reverse ? -1 : 1));
|
||||
});
|
||||
}
|
||||
return w03;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
OpType op)
|
||||
: inP_(inP), inQ_(inQ), expandP_(op == OpType::Add ? 1.0 : -1.0) {
|
||||
// Symbolic perturbation:
|
||||
// Union -> expand inP
|
||||
// Difference, Intersection -> contract inP
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
Timer broad;
|
||||
broad.Start();
|
||||
#endif
|
||||
|
||||
if (inP.IsEmpty() || inQ.IsEmpty() || !inP.bBox_.DoesOverlap(inQ.bBox_)) {
|
||||
PRINT("No overlap, early out");
|
||||
w03_.resize(inP.NumVert(), 0);
|
||||
w30_.resize(inQ.NumVert(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Level 3
|
||||
// Find edge-triangle overlaps (broad phase)
|
||||
p1q2_ = inQ_.EdgeCollisions(inP_);
|
||||
p2q1_ = inP_.EdgeCollisions(inQ_, true); // inverted
|
||||
|
||||
p1q2_.Sort();
|
||||
PRINT("p1q2 size = " << p1q2_.size());
|
||||
|
||||
p2q1_.Sort();
|
||||
PRINT("p2q1 size = " << p2q1_.size());
|
||||
|
||||
// Level 2
|
||||
// Find vertices that overlap faces in XY-projection
|
||||
SparseIndices p0q2 = inQ.VertexCollisionsZ(inP.vertPos_);
|
||||
p0q2.Sort();
|
||||
PRINT("p0q2 size = " << p0q2.size());
|
||||
|
||||
SparseIndices p2q0 = inP.VertexCollisionsZ(inQ.vertPos_, true); // inverted
|
||||
p2q0.Sort();
|
||||
PRINT("p2q0 size = " << p2q0.size());
|
||||
|
||||
// Find involved edge pairs from Level 3
|
||||
SparseIndices p1q1 = Filter11(inP_, inQ_, p1q2_, p2q1_);
|
||||
PRINT("p1q1 size = " << p1q1.size());
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
broad.Stop();
|
||||
Timer intersections;
|
||||
intersections.Start();
|
||||
#endif
|
||||
|
||||
// Level 2
|
||||
// Build up XY-projection intersection of two edges, including the z-value for
|
||||
// each edge, keeping only those whose intersection exists.
|
||||
Vec<int> s11;
|
||||
Vec<vec4> xyzz11;
|
||||
std::tie(s11, xyzz11) = Shadow11(p1q1, inP, inQ, expandP_);
|
||||
PRINT("s11 size = " << s11.size());
|
||||
|
||||
// Build up Z-projection of vertices onto triangles, keeping only those that
|
||||
// fall inside the triangle.
|
||||
Vec<int> s02;
|
||||
Vec<double> z02;
|
||||
std::tie(s02, z02) = Shadow02(inP, inQ, p0q2, true, expandP_);
|
||||
PRINT("s02 size = " << s02.size());
|
||||
|
||||
Vec<int> s20;
|
||||
Vec<double> z20;
|
||||
std::tie(s20, z20) = Shadow02(inQ, inP, p2q0, false, expandP_);
|
||||
PRINT("s20 size = " << s20.size());
|
||||
|
||||
// Level 3
|
||||
// Build up the intersection of the edges and triangles, keeping only those
|
||||
// that intersect, and record the direction the edge is passing through the
|
||||
// triangle.
|
||||
std::tie(x12_, v12_) =
|
||||
Intersect12(inP, inQ, s02, p0q2, s11, p1q1, z02, xyzz11, p1q2_, true);
|
||||
PRINT("x12 size = " << x12_.size());
|
||||
|
||||
std::tie(x21_, v21_) =
|
||||
Intersect12(inQ, inP, s20, p2q0, s11, p1q1, z20, xyzz11, p2q1_, false);
|
||||
PRINT("x21 size = " << x21_.size());
|
||||
|
||||
s11.clear();
|
||||
xyzz11.clear();
|
||||
z02.clear();
|
||||
z20.clear();
|
||||
|
||||
Vec<int> p0 = p0q2.Copy(false);
|
||||
p0q2.Resize(0);
|
||||
Vec<int> q0 = p2q0.Copy(true);
|
||||
p2q0.Resize(0);
|
||||
// Sum up the winding numbers of all vertices.
|
||||
w03_ = Winding03(inP, p0, s02, false);
|
||||
|
||||
w30_ = Winding03(inQ, q0, s20, true);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
intersections.Stop();
|
||||
|
||||
if (ManifoldParams().verbose) {
|
||||
broad.Print("Broad phase");
|
||||
intersections.Print("Intersections");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} // namespace manifold
|
||||
60
thirdparty/manifold/src/boolean3.h
vendored
Normal file
60
thirdparty/manifold/src/boolean3.h
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2020 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "./impl.h"
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#define PRINT(msg) \
|
||||
if (ManifoldParams().verbose) std::cout << msg << std::endl;
|
||||
#else
|
||||
#define PRINT(msg)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The notation in these files is abbreviated due to the complexity of the
|
||||
* functions involved. The key is that the input manifolds are P and Q, while
|
||||
* the output is R, and these letters in both upper and lower case refer to
|
||||
* these objects. Operations are based on dimensionality: vert: 0, edge: 1,
|
||||
* face: 2, solid: 3. X denotes a winding-number type quantity from the source
|
||||
* paper of this algorithm, while S is closely related but includes only the
|
||||
* subset of X values which "shadow" (are on the correct side of).
|
||||
*
|
||||
* Nearly everything here are sparse arrays, where for instance each pair in
|
||||
* p2q1 refers to a face index of P interacting with a halfedge index of Q.
|
||||
* Adjacent arrays like x21 refer to the values of X corresponding to each
|
||||
* sparse index pair.
|
||||
*
|
||||
* Note many functions are designed to work symmetrically, for instance for both
|
||||
* p2q1 and p1q2. Inside of these functions P and Q are marked as though the
|
||||
* function is forwards, but it may include a Boolean "reverse" that indicates P
|
||||
* and Q have been swapped.
|
||||
*/
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
class Boolean3 {
|
||||
public:
|
||||
Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ, OpType op);
|
||||
Manifold::Impl Result(OpType op) const;
|
||||
|
||||
private:
|
||||
const Manifold::Impl &inP_, &inQ_;
|
||||
const double expandP_;
|
||||
SparseIndices p1q2_, p2q1_;
|
||||
Vec<int> x12_, x21_, w03_, w30_;
|
||||
Vec<vec3> v12_, v21_;
|
||||
};
|
||||
} // namespace manifold
|
||||
889
thirdparty/manifold/src/boolean_result.cpp
vendored
Normal file
889
thirdparty/manifold/src/boolean_result.cpp
vendored
Normal file
@@ -0,0 +1,889 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <map>
|
||||
|
||||
#include "./boolean3.h"
|
||||
#include "./parallel.h"
|
||||
#include "./utils.h"
|
||||
|
||||
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_map.h>)
|
||||
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
|
||||
#include <tbb/concurrent_map.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
template <typename K, typename V>
|
||||
using concurrent_map = tbb::concurrent_map<K, V>;
|
||||
#else
|
||||
template <typename K, typename V>
|
||||
// not really concurrent when tbb is disabled
|
||||
using concurrent_map = std::map<K, V>;
|
||||
#endif
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
template <>
|
||||
struct std::hash<std::pair<int, int>> {
|
||||
size_t operator()(const std::pair<int, int> &p) const {
|
||||
return std::hash<int>()(p.first) ^ std::hash<int>()(p.second);
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kParallelThreshold = 128;
|
||||
|
||||
struct AbsSum {
|
||||
int operator()(int a, int b) const { return abs(a) + abs(b); }
|
||||
};
|
||||
|
||||
struct DuplicateVerts {
|
||||
VecView<vec3> vertPosR;
|
||||
VecView<const int> inclusion;
|
||||
VecView<const int> vertR;
|
||||
VecView<const vec3> vertPosP;
|
||||
|
||||
void operator()(const int vert) {
|
||||
const int n = std::abs(inclusion[vert]);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
vertPosR[vertR[vert] + i] = vertPosP[vert];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <bool atomic>
|
||||
struct CountVerts {
|
||||
VecView<Halfedge> halfedges;
|
||||
VecView<int> count;
|
||||
VecView<const int> inclusion;
|
||||
|
||||
void operator()(size_t i) {
|
||||
if (atomic)
|
||||
AtomicAdd(count[i / 3], std::abs(inclusion[halfedges[i].startVert]));
|
||||
else
|
||||
count[i / 3] += std::abs(inclusion[halfedges[i].startVert]);
|
||||
}
|
||||
};
|
||||
|
||||
template <const bool inverted, const bool atomic>
|
||||
struct CountNewVerts {
|
||||
VecView<int> countP;
|
||||
VecView<int> countQ;
|
||||
VecView<const int> i12;
|
||||
const SparseIndices &pq;
|
||||
VecView<const Halfedge> halfedges;
|
||||
|
||||
void operator()(const int idx) {
|
||||
int edgeP = pq.Get(idx, inverted);
|
||||
int faceQ = pq.Get(idx, !inverted);
|
||||
int inclusion = std::abs(i12[idx]);
|
||||
|
||||
if (atomic) {
|
||||
AtomicAdd(countQ[faceQ], inclusion);
|
||||
const Halfedge half = halfedges[edgeP];
|
||||
AtomicAdd(countP[edgeP / 3], inclusion);
|
||||
AtomicAdd(countP[half.pairedHalfedge / 3], inclusion);
|
||||
} else {
|
||||
countQ[faceQ] += inclusion;
|
||||
const Halfedge half = halfedges[edgeP];
|
||||
countP[edgeP / 3] += inclusion;
|
||||
countP[half.pairedHalfedge / 3] += inclusion;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple<Vec<int>, Vec<int>> SizeOutput(
|
||||
Manifold::Impl &outR, const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
const Vec<int> &i03, const Vec<int> &i30, const Vec<int> &i12,
|
||||
const Vec<int> &i21, const SparseIndices &p1q2, const SparseIndices &p2q1,
|
||||
bool invertQ) {
|
||||
ZoneScoped;
|
||||
Vec<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri(), 0);
|
||||
// note: numFaceR <= facePQ2R.size() = sidesPerFacePQ.size() + 1
|
||||
|
||||
auto sidesPerFaceP = sidesPerFacePQ.view(0, inP.NumTri());
|
||||
auto sidesPerFaceQ = sidesPerFacePQ.view(inP.NumTri(), inQ.NumTri());
|
||||
|
||||
if (inP.halfedge_.size() >= 1e5) {
|
||||
for_each(ExecutionPolicy::Par, countAt(0_uz), countAt(inP.halfedge_.size()),
|
||||
CountVerts<true>({inP.halfedge_, sidesPerFaceP, i03}));
|
||||
for_each(ExecutionPolicy::Par, countAt(0_uz), countAt(inQ.halfedge_.size()),
|
||||
CountVerts<true>({inQ.halfedge_, sidesPerFaceQ, i30}));
|
||||
} else {
|
||||
for_each(ExecutionPolicy::Seq, countAt(0_uz), countAt(inP.halfedge_.size()),
|
||||
CountVerts<false>({inP.halfedge_, sidesPerFaceP, i03}));
|
||||
for_each(ExecutionPolicy::Seq, countAt(0_uz), countAt(inQ.halfedge_.size()),
|
||||
CountVerts<false>({inQ.halfedge_, sidesPerFaceQ, i30}));
|
||||
}
|
||||
|
||||
if (i12.size() >= 1e5) {
|
||||
for_each_n(ExecutionPolicy::Par, countAt(0), i12.size(),
|
||||
CountNewVerts<false, true>(
|
||||
{sidesPerFaceP, sidesPerFaceQ, i12, p1q2, inP.halfedge_}));
|
||||
for_each_n(ExecutionPolicy::Par, countAt(0), i21.size(),
|
||||
CountNewVerts<true, true>(
|
||||
{sidesPerFaceQ, sidesPerFaceP, i21, p2q1, inQ.halfedge_}));
|
||||
} else {
|
||||
for_each_n(ExecutionPolicy::Seq, countAt(0), i12.size(),
|
||||
CountNewVerts<false, false>(
|
||||
{sidesPerFaceP, sidesPerFaceQ, i12, p1q2, inP.halfedge_}));
|
||||
for_each_n(ExecutionPolicy::Seq, countAt(0), i21.size(),
|
||||
CountNewVerts<true, false>(
|
||||
{sidesPerFaceQ, sidesPerFaceP, i21, p2q1, inQ.halfedge_}));
|
||||
}
|
||||
|
||||
Vec<int> facePQ2R(inP.NumTri() + inQ.NumTri() + 1, 0);
|
||||
auto keepFace = TransformIterator(sidesPerFacePQ.begin(),
|
||||
[](int x) { return x > 0 ? 1 : 0; });
|
||||
|
||||
inclusive_scan(keepFace, keepFace + sidesPerFacePQ.size(),
|
||||
facePQ2R.begin() + 1);
|
||||
int numFaceR = facePQ2R.back();
|
||||
facePQ2R.resize(inP.NumTri() + inQ.NumTri());
|
||||
|
||||
outR.faceNormal_.resize(numFaceR);
|
||||
|
||||
Vec<size_t> tmpBuffer(outR.faceNormal_.size());
|
||||
auto faceIds = TransformIterator(countAt(0_uz), [&sidesPerFacePQ](size_t i) {
|
||||
if (sidesPerFacePQ[i] > 0) return i;
|
||||
return std::numeric_limits<size_t>::max();
|
||||
});
|
||||
|
||||
auto next =
|
||||
copy_if(faceIds, faceIds + inP.faceNormal_.size(), tmpBuffer.begin(),
|
||||
[](size_t v) { return v != std::numeric_limits<size_t>::max(); });
|
||||
|
||||
gather(tmpBuffer.begin(), next, inP.faceNormal_.begin(),
|
||||
outR.faceNormal_.begin());
|
||||
|
||||
auto faceIdsQ =
|
||||
TransformIterator(countAt(0_uz), [&sidesPerFacePQ, &inP](size_t i) {
|
||||
if (sidesPerFacePQ[i + inP.faceNormal_.size()] > 0) return i;
|
||||
return std::numeric_limits<size_t>::max();
|
||||
});
|
||||
auto end =
|
||||
copy_if(faceIdsQ, faceIdsQ + inQ.faceNormal_.size(), next,
|
||||
[](size_t v) { return v != std::numeric_limits<size_t>::max(); });
|
||||
|
||||
if (invertQ) {
|
||||
gather(next, end,
|
||||
TransformIterator(inQ.faceNormal_.begin(), Negate<vec3>()),
|
||||
outR.faceNormal_.begin() + std::distance(tmpBuffer.begin(), next));
|
||||
} else {
|
||||
gather(next, end, inQ.faceNormal_.begin(),
|
||||
outR.faceNormal_.begin() + std::distance(tmpBuffer.begin(), next));
|
||||
}
|
||||
|
||||
auto newEnd = remove(sidesPerFacePQ.begin(), sidesPerFacePQ.end(), 0);
|
||||
Vec<int> faceEdge(newEnd - sidesPerFacePQ.begin() + 1, 0);
|
||||
inclusive_scan(sidesPerFacePQ.begin(), newEnd, faceEdge.begin() + 1);
|
||||
outR.halfedge_.resize(faceEdge.back());
|
||||
|
||||
return std::make_tuple(faceEdge, facePQ2R);
|
||||
}
|
||||
|
||||
struct EdgePos {
|
||||
int vert;
|
||||
double edgePos;
|
||||
bool isStart;
|
||||
};
|
||||
|
||||
// thread sanitizer doesn't really know how to check when there are too many
|
||||
// mutex
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(thread_sanitizer)
|
||||
__attribute__((no_sanitize("thread")))
|
||||
#endif
|
||||
#endif
|
||||
void AddNewEdgeVerts(
|
||||
// we need concurrent_map because we will be adding things concurrently
|
||||
concurrent_map<int, std::vector<EdgePos>> &edgesP,
|
||||
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
|
||||
const SparseIndices &p1q2, const Vec<int> &i12, const Vec<int> &v12R,
|
||||
const Vec<Halfedge> &halfedgeP, bool forward) {
|
||||
ZoneScoped;
|
||||
// For each edge of P that intersects a face of Q (p1q2), add this vertex to
|
||||
// P's corresponding edge vector and to the two new edges, which are
|
||||
// intersections between the face of Q and the two faces of P attached to the
|
||||
// edge. The direction and duplicity are given by i12, while v12R remaps to
|
||||
// the output vert index. When forward is false, all is reversed.
|
||||
auto process = [&](std::function<void(size_t)> lock,
|
||||
std::function<void(size_t)> unlock, size_t i) {
|
||||
const int edgeP = p1q2.Get(i, !forward);
|
||||
const int faceQ = p1q2.Get(i, forward);
|
||||
const int vert = v12R[i];
|
||||
const int inclusion = i12[i];
|
||||
|
||||
Halfedge halfedge = halfedgeP[edgeP];
|
||||
std::pair<int, int> keyRight = {halfedge.pairedHalfedge / 3, faceQ};
|
||||
if (!forward) std::swap(keyRight.first, keyRight.second);
|
||||
|
||||
std::pair<int, int> keyLeft = {edgeP / 3, faceQ};
|
||||
if (!forward) std::swap(keyLeft.first, keyLeft.second);
|
||||
|
||||
bool direction = inclusion < 0;
|
||||
std::hash<std::pair<int, int>> pairHasher;
|
||||
std::array<std::tuple<bool, size_t, std::vector<EdgePos> *>, 3> edges = {
|
||||
std::make_tuple(direction, std::hash<int>{}(edgeP), &edgesP[edgeP]),
|
||||
std::make_tuple(direction ^ !forward, // revert if not forward
|
||||
pairHasher(keyRight), &edgesNew[keyRight]),
|
||||
std::make_tuple(direction ^ forward, // revert if forward
|
||||
pairHasher(keyLeft), &edgesNew[keyLeft])};
|
||||
for (const auto &tuple : edges) {
|
||||
lock(std::get<1>(tuple));
|
||||
for (int j = 0; j < std::abs(inclusion); ++j)
|
||||
std::get<2>(tuple)->push_back({vert + j, 0.0, std::get<0>(tuple)});
|
||||
unlock(std::get<1>(tuple));
|
||||
direction = !direction;
|
||||
}
|
||||
};
|
||||
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/tbb.h>)
|
||||
// parallelize operations, requires concurrent_map so we can only enable this
|
||||
// with tbb
|
||||
if (p1q2.size() > kParallelThreshold) {
|
||||
// ideally we should have 1 mutex per key, but kParallelThreshold is enough
|
||||
// to avoid contention for most of the cases
|
||||
std::array<std::mutex, kParallelThreshold> mutexes;
|
||||
static tbb::affinity_partitioner ap;
|
||||
auto processFun = std::bind(
|
||||
process, [&](size_t hash) { mutexes[hash % mutexes.size()].lock(); },
|
||||
[&](size_t hash) { mutexes[hash % mutexes.size()].unlock(); },
|
||||
std::placeholders::_1);
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0_uz, p1q2.size(), 32),
|
||||
[&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i != range.end(); i++) processFun(i);
|
||||
},
|
||||
ap);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
auto processFun = std::bind(
|
||||
process, [](size_t _) {}, [](size_t _) {}, std::placeholders::_1);
|
||||
for (size_t i = 0; i < p1q2.size(); ++i) processFun(i);
|
||||
}
|
||||
|
||||
std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
|
||||
// Pair start vertices with end vertices to form edges. The choice of pairing
|
||||
// is arbitrary for the manifoldness guarantee, but must be ordered to be
|
||||
// geometrically valid. If the order does not go start-end-start-end... then
|
||||
// the input and output are not geometrically valid and this algorithm becomes
|
||||
// a heuristic.
|
||||
DEBUG_ASSERT(edgePos.size() % 2 == 0, topologyErr,
|
||||
"Non-manifold edge! Not an even number of points.");
|
||||
size_t nEdges = edgePos.size() / 2;
|
||||
auto middle = std::partition(edgePos.begin(), edgePos.end(),
|
||||
[](EdgePos x) { return x.isStart; });
|
||||
DEBUG_ASSERT(static_cast<size_t>(middle - edgePos.begin()) == nEdges,
|
||||
topologyErr, "Non-manifold edge!");
|
||||
auto cmp = [](EdgePos a, EdgePos b) { return a.edgePos < b.edgePos; };
|
||||
std::stable_sort(edgePos.begin(), middle, cmp);
|
||||
std::stable_sort(middle, edgePos.end(), cmp);
|
||||
std::vector<Halfedge> edges;
|
||||
for (size_t i = 0; i < nEdges; ++i)
|
||||
edges.push_back({edgePos[i].vert, edgePos[i + nEdges].vert, -1});
|
||||
return edges;
|
||||
}
|
||||
|
||||
void AppendPartialEdges(Manifold::Impl &outR, Vec<char> &wholeHalfedgeP,
|
||||
Vec<int> &facePtrR,
|
||||
concurrent_map<int, std::vector<EdgePos>> &edgesP,
|
||||
Vec<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const Vec<int> &i03, const Vec<int> &vP2R,
|
||||
const Vec<int>::IterC faceP2R, bool forward) {
|
||||
ZoneScoped;
|
||||
// Each edge in the map is partially retained; for each of these, look up
|
||||
// their original verts and include them based on their winding number (i03),
|
||||
// while remapping them to the output using vP2R. Use the verts position
|
||||
// projected along the edge vector to pair them up, then distribute these
|
||||
// edges to their faces.
|
||||
Vec<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
const Vec<vec3> &vertPosP = inP.vertPos_;
|
||||
const Vec<Halfedge> &halfedgeP = inP.halfedge_;
|
||||
|
||||
for (auto &value : edgesP) {
|
||||
const int edgeP = value.first;
|
||||
std::vector<EdgePos> &edgePosP = value.second;
|
||||
|
||||
const Halfedge &halfedge = halfedgeP[edgeP];
|
||||
wholeHalfedgeP[edgeP] = false;
|
||||
wholeHalfedgeP[halfedge.pairedHalfedge] = false;
|
||||
|
||||
const int vStart = halfedge.startVert;
|
||||
const int vEnd = halfedge.endVert;
|
||||
const vec3 edgeVec = vertPosP[vEnd] - vertPosP[vStart];
|
||||
// Fill in the edge positions of the old points.
|
||||
for (EdgePos &edge : edgePosP) {
|
||||
edge.edgePos = la::dot(outR.vertPos_[edge.vert], edgeVec);
|
||||
}
|
||||
|
||||
int inclusion = i03[vStart];
|
||||
EdgePos edgePos = {vP2R[vStart],
|
||||
la::dot(outR.vertPos_[vP2R[vStart]], edgeVec),
|
||||
inclusion > 0};
|
||||
for (int j = 0; j < std::abs(inclusion); ++j) {
|
||||
edgePosP.push_back(edgePos);
|
||||
++edgePos.vert;
|
||||
}
|
||||
|
||||
inclusion = i03[vEnd];
|
||||
edgePos = {vP2R[vEnd], la::dot(outR.vertPos_[vP2R[vEnd]], edgeVec),
|
||||
inclusion < 0};
|
||||
for (int j = 0; j < std::abs(inclusion); ++j) {
|
||||
edgePosP.push_back(edgePos);
|
||||
++edgePos.vert;
|
||||
}
|
||||
|
||||
// sort edges into start/end pairs along length
|
||||
std::vector<Halfedge> edges = PairUp(edgePosP);
|
||||
|
||||
// add halfedges to result
|
||||
const int faceLeftP = edgeP / 3;
|
||||
const int faceLeft = faceP2R[faceLeftP];
|
||||
const int faceRightP = halfedge.pairedHalfedge / 3;
|
||||
const int faceRight = faceP2R[faceRightP];
|
||||
// Negative inclusion means the halfedges are reversed, which means our
|
||||
// reference is now to the endVert instead of the startVert, which is one
|
||||
// position advanced CCW. This is only valid if this is a retained vert; it
|
||||
// will be ignored later if the vert is new.
|
||||
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
|
||||
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
|
||||
|
||||
for (Halfedge e : edges) {
|
||||
const int forwardEdge = facePtrR[faceLeft]++;
|
||||
const int backwardEdge = facePtrR[faceRight]++;
|
||||
|
||||
e.pairedHalfedge = backwardEdge;
|
||||
halfedgeR[forwardEdge] = e;
|
||||
halfedgeRef[forwardEdge] = forwardRef;
|
||||
|
||||
std::swap(e.startVert, e.endVert);
|
||||
e.pairedHalfedge = forwardEdge;
|
||||
halfedgeR[backwardEdge] = e;
|
||||
halfedgeRef[backwardEdge] = backwardRef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppendNewEdges(
|
||||
Manifold::Impl &outR, Vec<int> &facePtrR,
|
||||
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
|
||||
Vec<TriRef> &halfedgeRef, const Vec<int> &facePQ2R, const int numFaceP) {
|
||||
ZoneScoped;
|
||||
// Pair up each edge's verts and distribute to faces based on indices in key.
|
||||
Vec<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
Vec<vec3> &vertPosR = outR.vertPos_;
|
||||
|
||||
for (auto &value : edgesNew) {
|
||||
const int faceP = value.first.first;
|
||||
const int faceQ = value.first.second;
|
||||
std::vector<EdgePos> &edgePos = value.second;
|
||||
|
||||
Box bbox;
|
||||
for (auto edge : edgePos) {
|
||||
bbox.Union(vertPosR[edge.vert]);
|
||||
}
|
||||
const vec3 size = bbox.Size();
|
||||
// Order the points along their longest dimension.
|
||||
const int i = (size.x > size.y && size.x > size.z) ? 0
|
||||
: size.y > size.z ? 1
|
||||
: 2;
|
||||
for (auto &edge : edgePos) {
|
||||
edge.edgePos = vertPosR[edge.vert][i];
|
||||
}
|
||||
|
||||
// sort edges into start/end pairs along length.
|
||||
std::vector<Halfedge> edges = PairUp(edgePos);
|
||||
|
||||
// add halfedges to result
|
||||
const int faceLeft = facePQ2R[faceP];
|
||||
const int faceRight = facePQ2R[numFaceP + faceQ];
|
||||
const TriRef forwardRef = {0, -1, faceP};
|
||||
const TriRef backwardRef = {1, -1, faceQ};
|
||||
for (Halfedge e : edges) {
|
||||
const int forwardEdge = facePtrR[faceLeft]++;
|
||||
const int backwardEdge = facePtrR[faceRight]++;
|
||||
|
||||
e.pairedHalfedge = backwardEdge;
|
||||
halfedgeR[forwardEdge] = e;
|
||||
halfedgeRef[forwardEdge] = forwardRef;
|
||||
|
||||
std::swap(e.startVert, e.endVert);
|
||||
e.pairedHalfedge = forwardEdge;
|
||||
halfedgeR[backwardEdge] = e;
|
||||
halfedgeRef[backwardEdge] = backwardRef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DuplicateHalfedges {
|
||||
VecView<Halfedge> halfedgesR;
|
||||
VecView<TriRef> halfedgeRef;
|
||||
VecView<int> facePtr;
|
||||
VecView<const char> wholeHalfedgeP;
|
||||
VecView<const Halfedge> halfedgesP;
|
||||
VecView<const int> i03;
|
||||
VecView<const int> vP2R;
|
||||
VecView<const int> faceP2R;
|
||||
const bool forward;
|
||||
|
||||
void operator()(const int idx) {
|
||||
if (!wholeHalfedgeP[idx]) return;
|
||||
Halfedge halfedge = halfedgesP[idx];
|
||||
if (!halfedge.IsForward()) return;
|
||||
|
||||
const int inclusion = i03[halfedge.startVert];
|
||||
if (inclusion == 0) return;
|
||||
if (inclusion < 0) { // reverse
|
||||
std::swap(halfedge.startVert, halfedge.endVert);
|
||||
}
|
||||
halfedge.startVert = vP2R[halfedge.startVert];
|
||||
halfedge.endVert = vP2R[halfedge.endVert];
|
||||
const int faceLeftP = idx / 3;
|
||||
const int newFace = faceP2R[faceLeftP];
|
||||
const int faceRightP = halfedge.pairedHalfedge / 3;
|
||||
const int faceRight = faceP2R[faceRightP];
|
||||
// Negative inclusion means the halfedges are reversed, which means our
|
||||
// reference is now to the endVert instead of the startVert, which is one
|
||||
// position advanced CCW.
|
||||
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
|
||||
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
|
||||
|
||||
for (int i = 0; i < std::abs(inclusion); ++i) {
|
||||
int forwardEdge = AtomicAdd(facePtr[newFace], 1);
|
||||
int backwardEdge = AtomicAdd(facePtr[faceRight], 1);
|
||||
halfedge.pairedHalfedge = backwardEdge;
|
||||
|
||||
halfedgesR[forwardEdge] = halfedge;
|
||||
halfedgesR[backwardEdge] = {halfedge.endVert, halfedge.startVert,
|
||||
forwardEdge};
|
||||
halfedgeRef[forwardEdge] = forwardRef;
|
||||
halfedgeRef[backwardEdge] = backwardRef;
|
||||
|
||||
++halfedge.startVert;
|
||||
++halfedge.endVert;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void AppendWholeEdges(Manifold::Impl &outR, Vec<int> &facePtrR,
|
||||
Vec<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const Vec<char> wholeHalfedgeP, const Vec<int> &i03,
|
||||
const Vec<int> &vP2R, VecView<const int> faceP2R,
|
||||
bool forward) {
|
||||
ZoneScoped;
|
||||
for_each_n(
|
||||
autoPolicy(inP.halfedge_.size()), countAt(0), inP.halfedge_.size(),
|
||||
DuplicateHalfedges({outR.halfedge_, halfedgeRef, facePtrR, wholeHalfedgeP,
|
||||
inP.halfedge_, i03, vP2R, faceP2R, forward}));
|
||||
}
|
||||
|
||||
struct MapTriRef {
|
||||
VecView<const TriRef> triRefP;
|
||||
VecView<const TriRef> triRefQ;
|
||||
const int offsetQ;
|
||||
|
||||
void operator()(TriRef &triRef) {
|
||||
const int tri = triRef.tri;
|
||||
const bool PQ = triRef.meshID == 0;
|
||||
triRef = PQ ? triRefP[tri] : triRefQ[tri];
|
||||
if (!PQ) triRef.meshID += offsetQ;
|
||||
}
|
||||
};
|
||||
|
||||
void UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ, bool invertQ) {
|
||||
const int offsetQ = Manifold::Impl::meshIDCounter_;
|
||||
for_each_n(
|
||||
autoPolicy(outR.NumTri(), 1e5), outR.meshRelation_.triRef.begin(),
|
||||
outR.NumTri(),
|
||||
MapTriRef({inP.meshRelation_.triRef, inQ.meshRelation_.triRef, offsetQ}));
|
||||
|
||||
for (const auto &pair : inP.meshRelation_.meshIDtransform) {
|
||||
outR.meshRelation_.meshIDtransform[pair.first] = pair.second;
|
||||
}
|
||||
for (const auto &pair : inQ.meshRelation_.meshIDtransform) {
|
||||
outR.meshRelation_.meshIDtransform[pair.first + offsetQ] = pair.second;
|
||||
outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^=
|
||||
invertQ;
|
||||
}
|
||||
}
|
||||
|
||||
struct Barycentric {
|
||||
VecView<vec3> uvw;
|
||||
VecView<const TriRef> ref;
|
||||
VecView<const vec3> vertPosP;
|
||||
VecView<const vec3> vertPosQ;
|
||||
VecView<const vec3> vertPosR;
|
||||
VecView<const Halfedge> halfedgeP;
|
||||
VecView<const Halfedge> halfedgeQ;
|
||||
VecView<const Halfedge> halfedgeR;
|
||||
const double epsilon;
|
||||
|
||||
void operator()(const int tri) {
|
||||
const TriRef refPQ = ref[tri];
|
||||
if (halfedgeR[3 * tri].startVert < 0) return;
|
||||
|
||||
const int triPQ = refPQ.tri;
|
||||
const bool PQ = refPQ.meshID == 0;
|
||||
const auto &vertPos = PQ ? vertPosP : vertPosQ;
|
||||
const auto &halfedge = PQ ? halfedgeP : halfedgeQ;
|
||||
|
||||
mat3 triPos;
|
||||
for (const int j : {0, 1, 2})
|
||||
triPos[j] = vertPos[halfedge[3 * triPQ + j].startVert];
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = halfedgeR[3 * tri + i].startVert;
|
||||
uvw[3 * tri + i] = GetBarycentric(vertPosR[vert], triPos, epsilon);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ) {
|
||||
ZoneScoped;
|
||||
const int numPropP = inP.NumProp();
|
||||
const int numPropQ = inQ.NumProp();
|
||||
const int numProp = std::max(numPropP, numPropQ);
|
||||
outR.meshRelation_.numProp = numProp;
|
||||
if (numProp == 0) return;
|
||||
|
||||
const int numTri = outR.NumTri();
|
||||
outR.meshRelation_.triProperties.resize(numTri);
|
||||
|
||||
Vec<vec3> bary(outR.halfedge_.size());
|
||||
for_each_n(autoPolicy(numTri, 1e4), countAt(0), numTri,
|
||||
Barycentric({bary, outR.meshRelation_.triRef, inP.vertPos_,
|
||||
inQ.vertPos_, outR.vertPos_, inP.halfedge_,
|
||||
inQ.halfedge_, outR.halfedge_, outR.epsilon_}));
|
||||
|
||||
using Entry = std::pair<ivec3, int>;
|
||||
int idMissProp = outR.NumVert();
|
||||
std::vector<std::vector<Entry>> propIdx(outR.NumVert() + 1);
|
||||
std::vector<int> propMissIdx[2];
|
||||
propMissIdx[0].resize(inQ.NumPropVert(), -1);
|
||||
propMissIdx[1].resize(inP.NumPropVert(), -1);
|
||||
|
||||
outR.meshRelation_.properties.reserve(outR.NumVert() * numProp);
|
||||
int idx = 0;
|
||||
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
// Skip collapsed triangles
|
||||
if (outR.halfedge_[3 * tri].startVert < 0) continue;
|
||||
|
||||
const TriRef ref = outR.meshRelation_.triRef[tri];
|
||||
const bool PQ = ref.meshID == 0;
|
||||
const int oldNumProp = PQ ? numPropP : numPropQ;
|
||||
const auto &properties =
|
||||
PQ ? inP.meshRelation_.properties : inQ.meshRelation_.properties;
|
||||
const ivec3 &triProp = oldNumProp == 0 ? ivec3(-1)
|
||||
: PQ ? inP.meshRelation_.triProperties[ref.tri]
|
||||
: inQ.meshRelation_.triProperties[ref.tri];
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = outR.halfedge_[3 * tri + i].startVert;
|
||||
const vec3 &uvw = bary[3 * tri + i];
|
||||
|
||||
ivec4 key(PQ, idMissProp, -1, -1);
|
||||
if (oldNumProp > 0) {
|
||||
int edge = -2;
|
||||
for (const int j : {0, 1, 2}) {
|
||||
if (uvw[j] == 1) {
|
||||
// On a retained vert, the propVert must also match
|
||||
key[2] = triProp[j];
|
||||
edge = -1;
|
||||
break;
|
||||
}
|
||||
if (uvw[j] == 0) edge = j;
|
||||
}
|
||||
if (edge >= 0) {
|
||||
// On an edge, both propVerts must match
|
||||
const int p0 = triProp[Next3(edge)];
|
||||
const int p1 = triProp[Prev3(edge)];
|
||||
key[1] = vert;
|
||||
key[2] = std::min(p0, p1);
|
||||
key[3] = std::max(p0, p1);
|
||||
} else if (edge == -2) {
|
||||
key[1] = vert;
|
||||
}
|
||||
}
|
||||
|
||||
if (key.y == idMissProp && key.z >= 0) {
|
||||
// only key.x/key.z matters
|
||||
auto &entry = propMissIdx[key.x][key.z];
|
||||
if (entry >= 0) {
|
||||
outR.meshRelation_.triProperties[tri][i] = entry;
|
||||
continue;
|
||||
}
|
||||
entry = idx;
|
||||
} else {
|
||||
auto &bin = propIdx[key.y];
|
||||
bool bFound = false;
|
||||
for (const auto &b : bin) {
|
||||
if (b.first == ivec3(key.x, key.z, key.w)) {
|
||||
bFound = true;
|
||||
outR.meshRelation_.triProperties[tri][i] = b.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bFound) continue;
|
||||
bin.push_back(std::make_pair(ivec3(key.x, key.z, key.w), idx));
|
||||
}
|
||||
|
||||
outR.meshRelation_.triProperties[tri][i] = idx++;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
if (p < oldNumProp) {
|
||||
vec3 oldProps;
|
||||
for (const int j : {0, 1, 2})
|
||||
oldProps[j] = properties[oldNumProp * triProp[j] + p];
|
||||
outR.meshRelation_.properties.push_back(la::dot(uvw, oldProps));
|
||||
} else {
|
||||
outR.meshRelation_.properties.push_back(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
Timer assemble;
|
||||
assemble.Start();
|
||||
#endif
|
||||
|
||||
DEBUG_ASSERT((expandP_ > 0) == (op == OpType::Add), logicErr,
|
||||
"Result op type not compatible with constructor op type.");
|
||||
const int c1 = op == OpType::Intersect ? 0 : 1;
|
||||
const int c2 = op == OpType::Add ? 1 : 0;
|
||||
const int c3 = op == OpType::Intersect ? 1 : -1;
|
||||
|
||||
if (inP_.status_ != Manifold::Error::NoError) {
|
||||
auto impl = Manifold::Impl();
|
||||
impl.status_ = inP_.status_;
|
||||
return impl;
|
||||
}
|
||||
if (inQ_.status_ != Manifold::Error::NoError) {
|
||||
auto impl = Manifold::Impl();
|
||||
impl.status_ = inQ_.status_;
|
||||
return impl;
|
||||
}
|
||||
|
||||
if (inP_.IsEmpty()) {
|
||||
if (!inQ_.IsEmpty() && op == OpType::Add) {
|
||||
return inQ_;
|
||||
}
|
||||
return Manifold::Impl();
|
||||
} else if (inQ_.IsEmpty()) {
|
||||
if (op == OpType::Intersect) {
|
||||
return Manifold::Impl();
|
||||
}
|
||||
return inP_;
|
||||
}
|
||||
|
||||
const bool invertQ = op == OpType::Subtract;
|
||||
|
||||
// Convert winding numbers to inclusion values based on operation type.
|
||||
Vec<int> i12(x12_.size());
|
||||
Vec<int> i21(x21_.size());
|
||||
Vec<int> i03(w03_.size());
|
||||
Vec<int> i30(w30_.size());
|
||||
|
||||
transform(x12_.begin(), x12_.end(), i12.begin(),
|
||||
[c3](int v) { return c3 * v; });
|
||||
transform(x21_.begin(), x21_.end(), i21.begin(),
|
||||
[c3](int v) { return c3 * v; });
|
||||
transform(w03_.begin(), w03_.end(), i03.begin(),
|
||||
[c1, c3](int v) { return c1 + c3 * v; });
|
||||
transform(w30_.begin(), w30_.end(), i30.begin(),
|
||||
[c2, c3](int v) { return c2 + c3 * v; });
|
||||
|
||||
Vec<int> vP2R(inP_.NumVert());
|
||||
exclusive_scan(i03.begin(), i03.end(), vP2R.begin(), 0, AbsSum());
|
||||
int numVertR = AbsSum()(vP2R.back(), i03.back());
|
||||
const int nPv = numVertR;
|
||||
|
||||
Vec<int> vQ2R(inQ_.NumVert());
|
||||
exclusive_scan(i30.begin(), i30.end(), vQ2R.begin(), numVertR, AbsSum());
|
||||
numVertR = AbsSum()(vQ2R.back(), i30.back());
|
||||
const int nQv = numVertR - nPv;
|
||||
|
||||
Vec<int> v12R(v12_.size());
|
||||
if (v12_.size() > 0) {
|
||||
exclusive_scan(i12.begin(), i12.end(), v12R.begin(), numVertR, AbsSum());
|
||||
numVertR = AbsSum()(v12R.back(), i12.back());
|
||||
}
|
||||
const int n12 = numVertR - nPv - nQv;
|
||||
|
||||
Vec<int> v21R(v21_.size());
|
||||
if (v21_.size() > 0) {
|
||||
exclusive_scan(i21.begin(), i21.end(), v21R.begin(), numVertR, AbsSum());
|
||||
numVertR = AbsSum()(v21R.back(), i21.back());
|
||||
}
|
||||
const int n21 = numVertR - nPv - nQv - n12;
|
||||
|
||||
// Create the output Manifold
|
||||
Manifold::Impl outR;
|
||||
|
||||
if (numVertR == 0) return outR;
|
||||
|
||||
outR.epsilon_ = std::max(inP_.epsilon_, inQ_.epsilon_);
|
||||
outR.tolerance_ = std::max(inP_.tolerance_, inQ_.tolerance_);
|
||||
|
||||
outR.vertPos_.resize(numVertR);
|
||||
// Add vertices, duplicating for inclusion numbers not in [-1, 1].
|
||||
// Retained vertices from P and Q:
|
||||
for_each_n(autoPolicy(inP_.NumVert(), 1e4), countAt(0), inP_.NumVert(),
|
||||
DuplicateVerts({outR.vertPos_, i03, vP2R, inP_.vertPos_}));
|
||||
for_each_n(autoPolicy(inQ_.NumVert(), 1e4), countAt(0), inQ_.NumVert(),
|
||||
DuplicateVerts({outR.vertPos_, i30, vQ2R, inQ_.vertPos_}));
|
||||
// New vertices created from intersections:
|
||||
for_each_n(autoPolicy(i12.size(), 1e4), countAt(0), i12.size(),
|
||||
DuplicateVerts({outR.vertPos_, i12, v12R, v12_}));
|
||||
for_each_n(autoPolicy(i21.size(), 1e4), countAt(0), i21.size(),
|
||||
DuplicateVerts({outR.vertPos_, i21, v21R, v21_}));
|
||||
|
||||
PRINT(nPv << " verts from inP");
|
||||
PRINT(nQv << " verts from inQ");
|
||||
PRINT(n12 << " new verts from edgesP -> facesQ");
|
||||
PRINT(n21 << " new verts from facesP -> edgesQ");
|
||||
|
||||
// Build up new polygonal faces from triangle intersections. At this point the
|
||||
// calculation switches from parallel to serial.
|
||||
|
||||
// Level 3
|
||||
|
||||
// This key is the forward halfedge index of P or Q. Only includes intersected
|
||||
// edges.
|
||||
concurrent_map<int, std::vector<EdgePos>> edgesP, edgesQ;
|
||||
// This key is the face index of <P, Q>
|
||||
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> edgesNew;
|
||||
|
||||
AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12, v12R, inP_.halfedge_, true);
|
||||
AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21, v21R, inQ_.halfedge_, false);
|
||||
|
||||
v12R.clear();
|
||||
v21R.clear();
|
||||
|
||||
// Level 4
|
||||
Vec<int> faceEdge;
|
||||
Vec<int> facePQ2R;
|
||||
std::tie(faceEdge, facePQ2R) =
|
||||
SizeOutput(outR, inP_, inQ_, i03, i30, i12, i21, p1q2_, p2q1_, invertQ);
|
||||
|
||||
i12.clear();
|
||||
i21.clear();
|
||||
|
||||
// This gets incremented for each halfedge that's added to a face so that the
|
||||
// next one knows where to slot in.
|
||||
Vec<int> facePtrR = faceEdge;
|
||||
// Intersected halfedges are marked false.
|
||||
Vec<char> wholeHalfedgeP(inP_.halfedge_.size(), true);
|
||||
Vec<char> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
|
||||
// The halfedgeRef contains the data that will become triRef once the faces
|
||||
// are triangulated.
|
||||
Vec<TriRef> halfedgeRef(2 * outR.NumEdge());
|
||||
|
||||
AppendPartialEdges(outR, wholeHalfedgeP, facePtrR, edgesP, halfedgeRef, inP_,
|
||||
i03, vP2R, facePQ2R.begin(), true);
|
||||
AppendPartialEdges(outR, wholeHalfedgeQ, facePtrR, edgesQ, halfedgeRef, inQ_,
|
||||
i30, vQ2R, facePQ2R.begin() + inP_.NumTri(), false);
|
||||
|
||||
edgesP.clear();
|
||||
edgesQ.clear();
|
||||
|
||||
AppendNewEdges(outR, facePtrR, edgesNew, halfedgeRef, facePQ2R,
|
||||
inP_.NumTri());
|
||||
|
||||
edgesNew.clear();
|
||||
|
||||
AppendWholeEdges(outR, facePtrR, halfedgeRef, inP_, wholeHalfedgeP, i03, vP2R,
|
||||
facePQ2R.cview(0, inP_.NumTri()), true);
|
||||
AppendWholeEdges(outR, facePtrR, halfedgeRef, inQ_, wholeHalfedgeQ, i30, vQ2R,
|
||||
facePQ2R.cview(inP_.NumTri(), inQ_.NumTri()), false);
|
||||
|
||||
wholeHalfedgeP.clear();
|
||||
wholeHalfedgeQ.clear();
|
||||
facePtrR.clear();
|
||||
facePQ2R.clear();
|
||||
i03.clear();
|
||||
i30.clear();
|
||||
vP2R.clear();
|
||||
vQ2R.clear();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
assemble.Stop();
|
||||
Timer triangulate;
|
||||
triangulate.Start();
|
||||
#endif
|
||||
|
||||
// Level 6
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
DEBUG_ASSERT(outR.IsManifold(), logicErr, "polygon mesh is not manifold!");
|
||||
|
||||
outR.Face2Tri(faceEdge, halfedgeRef);
|
||||
halfedgeRef.clear();
|
||||
faceEdge.clear();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
triangulate.Stop();
|
||||
Timer simplify;
|
||||
simplify.Start();
|
||||
#endif
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
DEBUG_ASSERT(outR.IsManifold(), logicErr,
|
||||
"triangulated mesh is not manifold!");
|
||||
|
||||
CreateProperties(outR, inP_, inQ_);
|
||||
|
||||
UpdateReference(outR, inP_, inQ_, invertQ);
|
||||
|
||||
outR.SimplifyTopology();
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
DEBUG_ASSERT(outR.Is2Manifold(), logicErr,
|
||||
"simplified mesh is not 2-manifold!");
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
simplify.Stop();
|
||||
Timer sort;
|
||||
sort.Start();
|
||||
#endif
|
||||
|
||||
outR.Finish();
|
||||
outR.IncrementMeshIDs();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
sort.Stop();
|
||||
if (ManifoldParams().verbose) {
|
||||
assemble.Print("Assembly");
|
||||
triangulate.Print("Triangulation");
|
||||
simplify.Print("Simplification");
|
||||
sort.Print("Sorting");
|
||||
std::cout << outR.NumVert() << " verts and " << outR.NumTri() << " tris"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
return outR;
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
382
thirdparty/manifold/src/collider.h
vendored
Normal file
382
thirdparty/manifold/src/collider.h
vendored
Normal file
@@ -0,0 +1,382 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "./parallel.h"
|
||||
#include "./sparse.h"
|
||||
#include "./utils.h"
|
||||
#include "./vec.h"
|
||||
#include "manifold/common.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#if (MANIFOLD_PAR == 1)
|
||||
#include <tbb/combinable.h>
|
||||
#endif
|
||||
|
||||
namespace manifold {
|
||||
|
||||
namespace collider_internal {
|
||||
// Adjustable parameters
|
||||
constexpr int kInitialLength = 128;
|
||||
constexpr int kLengthMultiple = 4;
|
||||
constexpr int kSequentialThreshold = 512;
|
||||
// Fundamental constants
|
||||
constexpr int kRoot = 1;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#ifndef _WINDEF_
|
||||
typedef unsigned long DWORD;
|
||||
#endif
|
||||
|
||||
uint32_t inline ctz(uint32_t value) {
|
||||
DWORD trailing_zero = 0;
|
||||
|
||||
if (_BitScanForward(&trailing_zero, value)) {
|
||||
return trailing_zero;
|
||||
} else {
|
||||
// This is undefined, I better choose 32 than 0
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t inline clz(uint32_t value) {
|
||||
DWORD leading_zero = 0;
|
||||
|
||||
if (_BitScanReverse(&leading_zero, value)) {
|
||||
return 31 - leading_zero;
|
||||
} else {
|
||||
// Same remarks as above
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr inline bool IsLeaf(int node) { return node % 2 == 0; }
|
||||
constexpr inline bool IsInternal(int node) { return node % 2 == 1; }
|
||||
constexpr inline int Node2Internal(int node) { return (node - 1) / 2; }
|
||||
constexpr inline int Internal2Node(int internal) { return internal * 2 + 1; }
|
||||
constexpr inline int Node2Leaf(int node) { return node / 2; }
|
||||
constexpr inline int Leaf2Node(int leaf) { return leaf * 2; }
|
||||
|
||||
struct CreateRadixTree {
|
||||
VecView<int> nodeParent_;
|
||||
VecView<std::pair<int, int>> internalChildren_;
|
||||
const VecView<const uint32_t> leafMorton_;
|
||||
|
||||
int PrefixLength(uint32_t a, uint32_t b) const {
|
||||
// count-leading-zeros is used to find the number of identical highest-order
|
||||
// bits
|
||||
#ifdef _MSC_VER
|
||||
// return __lzcnt(a ^ b);
|
||||
return clz(a ^ b);
|
||||
#else
|
||||
return __builtin_clz(a ^ b);
|
||||
#endif
|
||||
}
|
||||
|
||||
int PrefixLength(int i, int j) const {
|
||||
if (j < 0 || j >= static_cast<int>(leafMorton_.size())) {
|
||||
return -1;
|
||||
} else {
|
||||
int out;
|
||||
if (leafMorton_[i] == leafMorton_[j])
|
||||
// use index to disambiguate
|
||||
out = 32 +
|
||||
PrefixLength(static_cast<uint32_t>(i), static_cast<uint32_t>(j));
|
||||
else
|
||||
out = PrefixLength(leafMorton_[i], leafMorton_[j]);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
int RangeEnd(int i) const {
|
||||
// Determine direction of range (+1 or -1)
|
||||
int dir = PrefixLength(i, i + 1) - PrefixLength(i, i - 1);
|
||||
dir = (dir > 0) - (dir < 0);
|
||||
// Compute conservative range length with exponential increase
|
||||
int commonPrefix = PrefixLength(i, i - dir);
|
||||
int max_length = kInitialLength;
|
||||
while (PrefixLength(i, i + dir * max_length) > commonPrefix)
|
||||
max_length *= kLengthMultiple;
|
||||
// Compute precise range length with binary search
|
||||
int length = 0;
|
||||
for (int step = max_length / 2; step > 0; step /= 2) {
|
||||
if (PrefixLength(i, i + dir * (length + step)) > commonPrefix)
|
||||
length += step;
|
||||
}
|
||||
return i + dir * length;
|
||||
}
|
||||
|
||||
int FindSplit(int first, int last) const {
|
||||
int commonPrefix = PrefixLength(first, last);
|
||||
// Find the furthest object that shares more than commonPrefix bits with the
|
||||
// first one, using binary search.
|
||||
int split = first;
|
||||
int step = last - first;
|
||||
do {
|
||||
step = (step + 1) >> 1; // divide by 2, rounding up
|
||||
int newSplit = split + step;
|
||||
if (newSplit < last) {
|
||||
int splitPrefix = PrefixLength(first, newSplit);
|
||||
if (splitPrefix > commonPrefix) split = newSplit;
|
||||
}
|
||||
} while (step > 1);
|
||||
return split;
|
||||
}
|
||||
|
||||
void operator()(int internal) {
|
||||
int first = internal;
|
||||
// Find the range of objects with a common prefix
|
||||
int last = RangeEnd(first);
|
||||
if (first > last) std::swap(first, last);
|
||||
// Determine where the next-highest difference occurs
|
||||
int split = FindSplit(first, last);
|
||||
int child1 = split == first ? Leaf2Node(split) : Internal2Node(split);
|
||||
++split;
|
||||
int child2 = split == last ? Leaf2Node(split) : Internal2Node(split);
|
||||
// Record parent_child relationships.
|
||||
internalChildren_[internal].first = child1;
|
||||
internalChildren_[internal].second = child2;
|
||||
int node = Internal2Node(internal);
|
||||
nodeParent_[child1] = node;
|
||||
nodeParent_[child2] = node;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, const bool selfCollision, typename Recorder>
|
||||
struct FindCollision {
|
||||
VecView<const T> queries;
|
||||
VecView<const Box> nodeBBox_;
|
||||
VecView<const std::pair<int, int>> internalChildren_;
|
||||
Recorder recorder;
|
||||
|
||||
inline int RecordCollision(int node, const int queryIdx, SparseIndices& ind) {
|
||||
bool overlaps = nodeBBox_[node].DoesOverlap(queries[queryIdx]);
|
||||
if (overlaps && IsLeaf(node)) {
|
||||
int leafIdx = Node2Leaf(node);
|
||||
if (!selfCollision || leafIdx != queryIdx) {
|
||||
recorder.record(queryIdx, leafIdx, ind);
|
||||
}
|
||||
}
|
||||
return overlaps && IsInternal(node); // Should traverse into node
|
||||
}
|
||||
|
||||
void operator()(const int queryIdx) {
|
||||
// stack cannot overflow because radix tree has max depth 30 (Morton code) +
|
||||
// 32 (index).
|
||||
int stack[64];
|
||||
int top = -1;
|
||||
// Depth-first search
|
||||
int node = kRoot;
|
||||
SparseIndices& ind = recorder.local();
|
||||
while (1) {
|
||||
int internal = Node2Internal(node);
|
||||
int child1 = internalChildren_[internal].first;
|
||||
int child2 = internalChildren_[internal].second;
|
||||
|
||||
int traverse1 = RecordCollision(child1, queryIdx, ind);
|
||||
int traverse2 = RecordCollision(child2, queryIdx, ind);
|
||||
|
||||
if (!traverse1 && !traverse2) {
|
||||
if (top < 0) break; // done
|
||||
node = stack[top--]; // get a saved node
|
||||
} else {
|
||||
node = traverse1 ? child1 : child2; // go here next
|
||||
if (traverse1 && traverse2) {
|
||||
stack[++top] = child2; // save the other for later
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <const bool inverted>
|
||||
struct SeqCollisionRecorder {
|
||||
SparseIndices& queryTri_;
|
||||
inline void record(int queryIdx, int leafIdx, SparseIndices& ind) const {
|
||||
if (inverted)
|
||||
ind.Add(leafIdx, queryIdx);
|
||||
else
|
||||
ind.Add(queryIdx, leafIdx);
|
||||
}
|
||||
SparseIndices& local() { return queryTri_; }
|
||||
};
|
||||
|
||||
#if (MANIFOLD_PAR == 1)
|
||||
template <const bool inverted>
|
||||
struct ParCollisionRecorder {
|
||||
tbb::combinable<SparseIndices>& store;
|
||||
inline void record(int queryIdx, int leafIdx, SparseIndices& ind) const {
|
||||
// Add may invoke something in parallel, and it may return in
|
||||
// another thread, making thread local unsafe
|
||||
// we need to explicitly forbid parallelization by passing a flag
|
||||
if (inverted)
|
||||
ind.Add(leafIdx, queryIdx, true);
|
||||
else
|
||||
ind.Add(queryIdx, leafIdx, true);
|
||||
}
|
||||
SparseIndices& local() { return store.local(); }
|
||||
};
|
||||
#endif
|
||||
|
||||
struct BuildInternalBoxes {
|
||||
VecView<Box> nodeBBox_;
|
||||
VecView<int> counter_;
|
||||
const VecView<int> nodeParent_;
|
||||
const VecView<std::pair<int, int>> internalChildren_;
|
||||
|
||||
void operator()(int leaf) {
|
||||
int node = Leaf2Node(leaf);
|
||||
do {
|
||||
node = nodeParent_[node];
|
||||
int internal = Node2Internal(node);
|
||||
if (AtomicAdd(counter_[internal], 1) == 0) return;
|
||||
nodeBBox_[node] = nodeBBox_[internalChildren_[internal].first].Union(
|
||||
nodeBBox_[internalChildren_[internal].second]);
|
||||
} while (node != kRoot);
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformBox {
|
||||
const mat3x4 transform;
|
||||
void operator()(Box& box) { box = box.Transform(transform); }
|
||||
};
|
||||
|
||||
constexpr inline uint32_t SpreadBits3(uint32_t v) {
|
||||
v = 0xFF0000FFu & (v * 0x00010001u);
|
||||
v = 0x0F00F00Fu & (v * 0x00000101u);
|
||||
v = 0xC30C30C3u & (v * 0x00000011u);
|
||||
v = 0x49249249u & (v * 0x00000005u);
|
||||
return v;
|
||||
}
|
||||
} // namespace collider_internal
|
||||
|
||||
/** @ingroup Private */
|
||||
class Collider {
|
||||
public:
|
||||
Collider() {};
|
||||
|
||||
Collider(const VecView<const Box>& leafBB,
|
||||
const VecView<const uint32_t>& leafMorton) {
|
||||
ZoneScoped;
|
||||
DEBUG_ASSERT(leafBB.size() == leafMorton.size(), userErr,
|
||||
"vectors must be the same length");
|
||||
int num_nodes = 2 * leafBB.size() - 1;
|
||||
// assign and allocate members
|
||||
nodeBBox_.resize(num_nodes);
|
||||
nodeParent_.resize(num_nodes, -1);
|
||||
internalChildren_.resize(leafBB.size() - 1, std::make_pair(-1, -1));
|
||||
// organize tree
|
||||
for_each_n(autoPolicy(NumInternal(), 1e4), countAt(0), NumInternal(),
|
||||
collider_internal::CreateRadixTree(
|
||||
{nodeParent_, internalChildren_, leafMorton}));
|
||||
UpdateBoxes(leafBB);
|
||||
}
|
||||
|
||||
bool Transform(mat3x4 transform) {
|
||||
ZoneScoped;
|
||||
bool axisAligned = true;
|
||||
for (int row : {0, 1, 2}) {
|
||||
int count = 0;
|
||||
for (int col : {0, 1, 2}) {
|
||||
if (transform[col][row] == 0.0) ++count;
|
||||
}
|
||||
if (count != 2) axisAligned = false;
|
||||
}
|
||||
if (axisAligned) {
|
||||
for_each(autoPolicy(nodeBBox_.size(), 1e5), nodeBBox_.begin(),
|
||||
nodeBBox_.end(),
|
||||
[transform](Box& box) { box = box.Transform(transform); });
|
||||
}
|
||||
return axisAligned;
|
||||
}
|
||||
|
||||
void UpdateBoxes(const VecView<const Box>& leafBB) {
|
||||
ZoneScoped;
|
||||
DEBUG_ASSERT(leafBB.size() == NumLeaves(), userErr,
|
||||
"must have the same number of updated boxes as original");
|
||||
// copy in leaf node Boxes
|
||||
auto leaves = StridedRange(nodeBBox_.begin(), nodeBBox_.end(), 2);
|
||||
copy(leafBB.cbegin(), leafBB.cend(), leaves.begin());
|
||||
// create global counters
|
||||
Vec<int> counter(NumInternal(), 0);
|
||||
// kernel over leaves to save internal Boxes
|
||||
for_each_n(autoPolicy(NumInternal(), 1e3), countAt(0), NumLeaves(),
|
||||
collider_internal::BuildInternalBoxes(
|
||||
{nodeBBox_, counter, nodeParent_, internalChildren_}));
|
||||
}
|
||||
|
||||
template <const bool selfCollision = false, const bool inverted = false,
|
||||
typename T>
|
||||
void Collisions(const VecView<const T>& queriesIn,
|
||||
SparseIndices& queryTri) const {
|
||||
ZoneScoped;
|
||||
using collider_internal::FindCollision;
|
||||
#if (MANIFOLD_PAR == 1)
|
||||
if (queriesIn.size() > collider_internal::kSequentialThreshold) {
|
||||
tbb::combinable<SparseIndices> store;
|
||||
for_each_n(
|
||||
ExecutionPolicy::Par, countAt(0), queriesIn.size(),
|
||||
FindCollision<T, selfCollision,
|
||||
collider_internal::ParCollisionRecorder<inverted>>{
|
||||
queriesIn, nodeBBox_, internalChildren_, {store}});
|
||||
|
||||
std::vector<SparseIndices> tmp;
|
||||
store.combine_each(
|
||||
[&](SparseIndices& ind) { tmp.emplace_back(std::move(ind)); });
|
||||
queryTri.FromIndices(tmp);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
for_each_n(ExecutionPolicy::Seq, countAt(0), queriesIn.size(),
|
||||
FindCollision<T, selfCollision,
|
||||
collider_internal::SeqCollisionRecorder<inverted>>{
|
||||
queriesIn, nodeBBox_, internalChildren_, {queryTri}});
|
||||
}
|
||||
|
||||
template <const bool selfCollision = false, const bool inverted = false,
|
||||
typename T>
|
||||
SparseIndices Collisions(const VecView<const T>& queriesIn) const {
|
||||
SparseIndices result;
|
||||
Collisions<selfCollision, inverted, T>(queriesIn, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint32_t MortonCode(vec3 position, Box bBox) {
|
||||
using collider_internal::SpreadBits3;
|
||||
vec3 xyz = (position - bBox.min) / (bBox.max - bBox.min);
|
||||
xyz = la::min(vec3(1023.0), la::max(vec3(0.0), 1024.0 * xyz));
|
||||
uint32_t x = SpreadBits3(static_cast<uint32_t>(xyz.x));
|
||||
uint32_t y = SpreadBits3(static_cast<uint32_t>(xyz.y));
|
||||
uint32_t z = SpreadBits3(static_cast<uint32_t>(xyz.z));
|
||||
return x * 4 + y * 2 + z;
|
||||
}
|
||||
|
||||
private:
|
||||
Vec<Box> nodeBBox_;
|
||||
Vec<int> nodeParent_;
|
||||
// even nodes are leaves, odd nodes are internal, root is 1
|
||||
Vec<std::pair<int, int>> internalChildren_;
|
||||
|
||||
size_t NumInternal() const { return internalChildren_.size(); };
|
||||
size_t NumLeaves() const {
|
||||
return internalChildren_.empty() ? 0 : (NumInternal() + 1);
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
503
thirdparty/manifold/src/constructors.cpp
vendored
Normal file
503
thirdparty/manifold/src/constructors.cpp
vendored
Normal file
@@ -0,0 +1,503 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "./csg_tree.h"
|
||||
#include "./impl.h"
|
||||
#include "./parallel.h"
|
||||
#include "manifold/polygon.h"
|
||||
|
||||
namespace manifold {
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param meshGL input MeshGL.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
Manifold Manifold::Smooth(const MeshGL& meshGL,
|
||||
const std::vector<Smoothness>& sharpenedEdges) {
|
||||
DEBUG_ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error,
|
||||
"when supplying tangents, the normal constructor should be used "
|
||||
"rather than Smooth().");
|
||||
|
||||
std::shared_ptr<Impl> impl = std::make_shared<Impl>(meshGL);
|
||||
impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges));
|
||||
return Manifold(impl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param meshGL64 input MeshGL64.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
Manifold Manifold::Smooth(const MeshGL64& meshGL64,
|
||||
const std::vector<Smoothness>& sharpenedEdges) {
|
||||
DEBUG_ASSERT(meshGL64.halfedgeTangent.empty(), std::runtime_error,
|
||||
"when supplying tangents, the normal constructor should be used "
|
||||
"rather than Smooth().");
|
||||
|
||||
std::shared_ptr<Impl> impl = std::make_shared<Impl>(meshGL64);
|
||||
impl->CreateTangents(impl->UpdateSharpenedEdges(sharpenedEdges));
|
||||
return Manifold(impl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
Manifold Manifold::Tetrahedron() {
|
||||
return Manifold(std::make_shared<Impl>(Impl::Shape::Tetrahedron));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin. If any dimensions in size are negative, or if
|
||||
* all are zero, an empty Manifold will be returned.
|
||||
*
|
||||
* @param size The X, Y, and Z dimensions of the box.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
Manifold Manifold::Cube(vec3 size, bool center) {
|
||||
if (size.x < 0.0 || size.y < 0.0 || size.z < 0.0 || la::length(size) == 0.) {
|
||||
return Invalid();
|
||||
}
|
||||
mat3x4 m({{size.x, 0.0, 0.0}, {0.0, size.y, 0.0}, {0.0, 0.0, size.z}},
|
||||
center ? (-size / 2.0) : vec3(0.0));
|
||||
return Manifold(std::make_shared<Impl>(Manifold::Impl::Shape::Cube, m));
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience constructor for the common case of extruding a circle. Can also
|
||||
* form cones if both radii are specified.
|
||||
*
|
||||
* @param height Z-extent
|
||||
* @param radiusLow Radius of bottom circle. Must be positive.
|
||||
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
|
||||
* radiusLow.
|
||||
* @param circularSegments How many line segments to use around the circle.
|
||||
* Default is calculated by the static Defaults.
|
||||
* @param center Set to true to shift the center to the origin. Default is
|
||||
* origin at the bottom.
|
||||
*/
|
||||
Manifold Manifold::Cylinder(double height, double radiusLow, double radiusHigh,
|
||||
int circularSegments, bool center) {
|
||||
if (height <= 0.0 || radiusLow <= 0.0) {
|
||||
return Invalid();
|
||||
}
|
||||
const double scale = radiusHigh >= 0.0 ? radiusHigh / radiusLow : 1.0;
|
||||
const double radius = fmax(radiusLow, radiusHigh);
|
||||
const int n = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(radius);
|
||||
|
||||
SimplePolygon circle(n);
|
||||
const double dPhi = 360.0 / n;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[i] = {radiusLow * cosd(dPhi * i), radiusLow * sind(dPhi * i)};
|
||||
}
|
||||
|
||||
Manifold cylinder = Manifold::Extrude({circle}, height, 0, 0.0, vec2(scale));
|
||||
if (center)
|
||||
cylinder = cylinder.Translate(vec3(0.0, 0.0, -height / 2.0)).AsOriginal();
|
||||
return cylinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a geodesic sphere of a given radius.
|
||||
*
|
||||
* @param radius Radius of the sphere. Must be positive.
|
||||
* @param circularSegments Number of segments along its
|
||||
* diameter. This number will always be rounded up to the nearest factor of
|
||||
* four, as this sphere is constructed by refining an octahedron. This means
|
||||
* there are a circle of vertices on all three of the axis planes. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
Manifold Manifold::Sphere(double radius, int circularSegments) {
|
||||
if (radius <= 0.0) {
|
||||
return Invalid();
|
||||
}
|
||||
int n = circularSegments > 0 ? (circularSegments + 3) / 4
|
||||
: Quality::GetCircularSegments(radius) / 4;
|
||||
auto pImpl_ = std::make_shared<Impl>(Impl::Shape::Octahedron);
|
||||
pImpl_->Subdivide(
|
||||
[n](vec3 edge, vec4 tangentStart, vec4 tangentEnd) { return n - 1; });
|
||||
for_each_n(autoPolicy(pImpl_->NumVert(), 1e5), pImpl_->vertPos_.begin(),
|
||||
pImpl_->NumVert(), [radius](vec3& v) {
|
||||
v = la::cos(kHalfPi * (1.0 - v));
|
||||
v = radius * la::normalize(v);
|
||||
if (std::isnan(v.x)) v = vec3(0.0);
|
||||
});
|
||||
pImpl_->Finish();
|
||||
// Ignore preceding octahedron.
|
||||
pImpl_->InitializeOriginal();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by extruding them along the
|
||||
* Z-axis.
|
||||
* Note that high twistDegrees with small nDivisions may cause
|
||||
* self-intersection. This is not checked here and it is up to the user to
|
||||
* choose the correct parameters.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to extrude.
|
||||
* @param height Z-extent of extrusion.
|
||||
* @param nDivisions Number of extra copies of the crossSection to insert into
|
||||
* the shape vertically; especially useful in combination with twistDegrees to
|
||||
* avoid interpolation artifacts. Default is none.
|
||||
* @param twistDegrees Amount to twist the top crossSection relative to the
|
||||
* bottom, interpolated linearly for the divisions in between.
|
||||
* @param scaleTop Amount to scale the top (independently in X and Y). If the
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
|
||||
* Note that scale is applied after twist.
|
||||
* Default {1, 1}.
|
||||
*/
|
||||
Manifold Manifold::Extrude(const Polygons& crossSection, double height,
|
||||
int nDivisions, double twistDegrees, vec2 scaleTop) {
|
||||
ZoneScoped;
|
||||
if (crossSection.size() == 0 || height <= 0.0) {
|
||||
return Invalid();
|
||||
}
|
||||
|
||||
scaleTop.x = std::max(scaleTop.x, 0.0);
|
||||
scaleTop.y = std::max(scaleTop.y, 0.0);
|
||||
|
||||
auto pImpl_ = std::make_shared<Impl>();
|
||||
++nDivisions;
|
||||
auto& vertPos = pImpl_->vertPos_;
|
||||
Vec<ivec3> triVertsDH;
|
||||
auto& triVerts = triVertsDH;
|
||||
int nCrossSection = 0;
|
||||
bool isCone = scaleTop.x == 0.0 && scaleTop.y == 0.0;
|
||||
size_t idx = 0;
|
||||
PolygonsIdx polygonsIndexed;
|
||||
for (auto& poly : crossSection) {
|
||||
nCrossSection += poly.size();
|
||||
SimplePolygonIdx simpleIndexed;
|
||||
for (const vec2& polyVert : poly) {
|
||||
vertPos.push_back({polyVert.x, polyVert.y, 0.0});
|
||||
simpleIndexed.push_back({polyVert, static_cast<int>(idx++)});
|
||||
}
|
||||
polygonsIndexed.push_back(simpleIndexed);
|
||||
}
|
||||
for (int i = 1; i < nDivisions + 1; ++i) {
|
||||
double alpha = i / double(nDivisions);
|
||||
double phi = alpha * twistDegrees;
|
||||
vec2 scale = la::lerp(vec2(1.0), scaleTop, alpha);
|
||||
mat2 rotation({cosd(phi), sind(phi)}, {-sind(phi), cosd(phi)});
|
||||
mat2 transform = mat2({scale.x, 0.0}, {0.0, scale.y}) * rotation;
|
||||
size_t j = 0;
|
||||
size_t idx = 0;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (size_t vert = 0; vert < poly.size(); ++vert) {
|
||||
size_t offset = idx + nCrossSection * i;
|
||||
size_t thisVert = vert + offset;
|
||||
size_t lastVert = (vert == 0 ? poly.size() : vert) - 1 + offset;
|
||||
if (i == nDivisions && isCone) {
|
||||
triVerts.push_back(ivec3(nCrossSection * i + j,
|
||||
lastVert - nCrossSection,
|
||||
thisVert - nCrossSection));
|
||||
} else {
|
||||
vec2 pos = transform * poly[vert];
|
||||
vertPos.push_back({pos.x, pos.y, height * alpha});
|
||||
triVerts.push_back(
|
||||
ivec3(thisVert, lastVert, thisVert - nCrossSection));
|
||||
triVerts.push_back(ivec3(lastVert, lastVert - nCrossSection,
|
||||
thisVert - nCrossSection));
|
||||
}
|
||||
}
|
||||
++j;
|
||||
idx += poly.size();
|
||||
}
|
||||
}
|
||||
if (isCone)
|
||||
for (size_t j = 0; j < crossSection.size();
|
||||
++j) // Duplicate vertex for Genus
|
||||
vertPos.push_back({0.0, 0.0, height});
|
||||
std::vector<ivec3> top = TriangulateIdx(polygonsIndexed);
|
||||
for (const ivec3& tri : top) {
|
||||
triVerts.push_back({tri[0], tri[2], tri[1]});
|
||||
if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions);
|
||||
}
|
||||
|
||||
pImpl_->CreateHalfedges(triVertsDH);
|
||||
pImpl_->Finish();
|
||||
pImpl_->InitializeOriginal();
|
||||
pImpl_->CreateFaces();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by revolving this cross-section
|
||||
* around its Y-axis and then setting this as the Z-axis of the resulting
|
||||
* manifold. If the polygons cross the Y-axis, only the part on the positive X
|
||||
* side is used. Geometrically valid input will result in geometrically valid
|
||||
* output.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to revolve.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
* @param revolveDegrees Number of degrees to revolve. Default is 360 degrees.
|
||||
*/
|
||||
Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments,
|
||||
double revolveDegrees) {
|
||||
ZoneScoped;
|
||||
|
||||
Polygons polygons;
|
||||
double radius = 0;
|
||||
for (const SimplePolygon& poly : crossSection) {
|
||||
size_t i = 0;
|
||||
while (i < poly.size() && poly[i].x < 0) {
|
||||
++i;
|
||||
}
|
||||
if (i == poly.size()) {
|
||||
continue;
|
||||
}
|
||||
polygons.push_back({});
|
||||
const size_t start = i;
|
||||
do {
|
||||
if (poly[i].x >= 0) {
|
||||
polygons.back().push_back(poly[i]);
|
||||
radius = std::max(radius, poly[i].x);
|
||||
}
|
||||
const size_t next = i + 1 == poly.size() ? 0 : i + 1;
|
||||
if ((poly[next].x < 0) != (poly[i].x < 0)) {
|
||||
const double y = poly[next].y + poly[next].x *
|
||||
(poly[i].y - poly[next].y) /
|
||||
(poly[i].x - poly[next].x);
|
||||
polygons.back().push_back({0, y});
|
||||
}
|
||||
i = next;
|
||||
} while (i != start);
|
||||
}
|
||||
|
||||
if (polygons.empty()) {
|
||||
return Invalid();
|
||||
}
|
||||
|
||||
if (revolveDegrees > 360.0) {
|
||||
revolveDegrees = 360.0;
|
||||
}
|
||||
const bool isFullRevolution = revolveDegrees == 360.0;
|
||||
|
||||
const int nDivisions =
|
||||
circularSegments > 2
|
||||
? circularSegments
|
||||
: Quality::GetCircularSegments(radius) * revolveDegrees / 360;
|
||||
|
||||
auto pImpl_ = std::make_shared<Impl>();
|
||||
auto& vertPos = pImpl_->vertPos_;
|
||||
Vec<ivec3> triVertsDH;
|
||||
auto& triVerts = triVertsDH;
|
||||
|
||||
std::vector<int> startPoses;
|
||||
std::vector<int> endPoses;
|
||||
|
||||
const double dPhi = revolveDegrees / nDivisions;
|
||||
// first and last slice are distinguished if not a full revolution.
|
||||
const int nSlices = isFullRevolution ? nDivisions : nDivisions + 1;
|
||||
|
||||
for (const auto& poly : polygons) {
|
||||
std::size_t nPosVerts = 0;
|
||||
std::size_t nRevolveAxisVerts = 0;
|
||||
for (auto& pt : poly) {
|
||||
if (pt.x > 0) {
|
||||
nPosVerts++;
|
||||
} else {
|
||||
nRevolveAxisVerts++;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t polyVert = 0; polyVert < poly.size(); ++polyVert) {
|
||||
const size_t startPosIndex = vertPos.size();
|
||||
|
||||
if (!isFullRevolution) startPoses.push_back(startPosIndex);
|
||||
|
||||
const vec2 currPolyVertex = poly[polyVert];
|
||||
const vec2 prevPolyVertex =
|
||||
poly[polyVert == 0 ? poly.size() - 1 : polyVert - 1];
|
||||
|
||||
const int prevStartPosIndex =
|
||||
startPosIndex +
|
||||
(polyVert == 0 ? nRevolveAxisVerts + (nSlices * nPosVerts) : 0) +
|
||||
(prevPolyVertex.x == 0.0 ? -1 : -nSlices);
|
||||
|
||||
for (int slice = 0; slice < nSlices; ++slice) {
|
||||
const double phi = slice * dPhi;
|
||||
if (slice == 0 || currPolyVertex.x > 0) {
|
||||
vertPos.push_back({currPolyVertex.x * cosd(phi),
|
||||
currPolyVertex.x * sind(phi), currPolyVertex.y});
|
||||
}
|
||||
|
||||
if (isFullRevolution || slice > 0) {
|
||||
const int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
if (currPolyVertex.x > 0.0) {
|
||||
triVerts.push_back(ivec3(
|
||||
startPosIndex + slice, startPosIndex + lastSlice,
|
||||
// "Reuse" vertex of first slice if it lies on the revolve axis
|
||||
(prevPolyVertex.x == 0.0 ? prevStartPosIndex
|
||||
: prevStartPosIndex + lastSlice)));
|
||||
}
|
||||
|
||||
if (prevPolyVertex.x > 0.0) {
|
||||
triVerts.push_back(
|
||||
ivec3(prevStartPosIndex + lastSlice, prevStartPosIndex + slice,
|
||||
(currPolyVertex.x == 0.0 ? startPosIndex
|
||||
: startPosIndex + slice)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isFullRevolution) endPoses.push_back(vertPos.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Add front and back triangles if not a full revolution.
|
||||
if (!isFullRevolution) {
|
||||
std::vector<ivec3> frontTriangles = Triangulate(polygons, pImpl_->epsilon_);
|
||||
for (auto& t : frontTriangles) {
|
||||
triVerts.push_back({startPoses[t.x], startPoses[t.y], startPoses[t.z]});
|
||||
}
|
||||
|
||||
for (auto& t : frontTriangles) {
|
||||
triVerts.push_back({endPoses[t.z], endPoses[t.y], endPoses[t.x]});
|
||||
}
|
||||
}
|
||||
|
||||
pImpl_->CreateHalfedges(triVertsDH);
|
||||
pImpl_->Finish();
|
||||
pImpl_->InitializeOriginal();
|
||||
pImpl_->CreateFaces();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new manifold from a vector of other manifolds. This is a purely
|
||||
* topological operation, so care should be taken to avoid creating
|
||||
* overlapping results. It is the inverse operation of Decompose().
|
||||
*
|
||||
* @param manifolds A vector of Manifolds to lazy-union together.
|
||||
*/
|
||||
Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
|
||||
std::vector<std::shared_ptr<CsgLeafNode>> children;
|
||||
for (const auto& manifold : manifolds) {
|
||||
children.push_back(manifold.pNode_->ToLeafNode());
|
||||
}
|
||||
return Manifold(std::make_shared<Impl>(CsgLeafNode::Compose(children)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of Compose().
|
||||
*/
|
||||
std::vector<Manifold> Manifold::Decompose() const {
|
||||
ZoneScoped;
|
||||
UnionFind<> uf(NumVert());
|
||||
// Graph graph;
|
||||
auto pImpl_ = GetCsgLeafNode().GetImpl();
|
||||
for (const Halfedge& halfedge : pImpl_->halfedge_) {
|
||||
if (halfedge.IsForward()) uf.unionXY(halfedge.startVert, halfedge.endVert);
|
||||
}
|
||||
std::vector<int> componentIndices;
|
||||
const int numComponents = uf.connectedComponents(componentIndices);
|
||||
|
||||
if (numComponents == 1) {
|
||||
std::vector<Manifold> meshes(1);
|
||||
meshes[0] = *this;
|
||||
return meshes;
|
||||
}
|
||||
Vec<int> vertLabel(componentIndices);
|
||||
|
||||
const int numVert = NumVert();
|
||||
std::vector<Manifold> meshes;
|
||||
for (int i = 0; i < numComponents; ++i) {
|
||||
auto impl = std::make_shared<Impl>();
|
||||
// inherit original object's precision
|
||||
impl->epsilon_ = pImpl_->epsilon_;
|
||||
impl->tolerance_ = pImpl_->tolerance_;
|
||||
|
||||
Vec<int> vertNew2Old(numVert);
|
||||
const int nVert =
|
||||
copy_if(countAt(0), countAt(numVert), vertNew2Old.begin(),
|
||||
[i, &vertLabel](int v) { return vertLabel[v] == i; }) -
|
||||
vertNew2Old.begin();
|
||||
impl->vertPos_.resize(nVert);
|
||||
vertNew2Old.resize(nVert);
|
||||
gather(vertNew2Old.begin(), vertNew2Old.end(), pImpl_->vertPos_.begin(),
|
||||
impl->vertPos_.begin());
|
||||
|
||||
Vec<int> faceNew2Old(NumTri());
|
||||
const auto& halfedge = pImpl_->halfedge_;
|
||||
const int nFace =
|
||||
copy_if(countAt(0_uz), countAt(NumTri()), faceNew2Old.begin(),
|
||||
[i, &vertLabel, &halfedge](int face) {
|
||||
return vertLabel[halfedge[3 * face].startVert] == i;
|
||||
}) -
|
||||
faceNew2Old.begin();
|
||||
|
||||
if (nFace == 0) continue;
|
||||
faceNew2Old.resize(nFace);
|
||||
|
||||
impl->GatherFaces(*pImpl_, faceNew2Old);
|
||||
impl->ReindexVerts(vertNew2Old, pImpl_->NumVert());
|
||||
impl->Finish();
|
||||
|
||||
meshes.push_back(Manifold(impl));
|
||||
}
|
||||
return meshes;
|
||||
}
|
||||
} // namespace manifold
|
||||
789
thirdparty/manifold/src/cross_section/cross_section.cpp
vendored
Normal file
789
thirdparty/manifold/src/cross_section/cross_section.cpp
vendored
Normal file
@@ -0,0 +1,789 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "manifold/cross_section.h"
|
||||
|
||||
#include "../utils.h"
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
|
||||
namespace C2 = Clipper2Lib;
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace manifold {
|
||||
struct PathImpl {
|
||||
PathImpl(const C2::PathsD paths_) : paths_(paths_) {}
|
||||
operator const C2::PathsD&() const { return paths_; }
|
||||
const C2::PathsD paths_;
|
||||
};
|
||||
} // namespace manifold
|
||||
|
||||
namespace {
|
||||
const int precision_ = 8;
|
||||
|
||||
C2::ClipType cliptype_of_op(OpType op) {
|
||||
C2::ClipType ct = C2::ClipType::Union;
|
||||
switch (op) {
|
||||
case OpType::Add:
|
||||
break;
|
||||
case OpType::Subtract:
|
||||
ct = C2::ClipType::Difference;
|
||||
break;
|
||||
case OpType::Intersect:
|
||||
ct = C2::ClipType::Intersection;
|
||||
break;
|
||||
};
|
||||
return ct;
|
||||
}
|
||||
|
||||
C2::FillRule fr(CrossSection::FillRule fillrule) {
|
||||
C2::FillRule fr = C2::FillRule::EvenOdd;
|
||||
switch (fillrule) {
|
||||
case CrossSection::FillRule::EvenOdd:
|
||||
break;
|
||||
case CrossSection::FillRule::NonZero:
|
||||
fr = C2::FillRule::NonZero;
|
||||
break;
|
||||
case CrossSection::FillRule::Positive:
|
||||
fr = C2::FillRule::Positive;
|
||||
break;
|
||||
case CrossSection::FillRule::Negative:
|
||||
fr = C2::FillRule::Negative;
|
||||
break;
|
||||
};
|
||||
return fr;
|
||||
}
|
||||
|
||||
C2::JoinType jt(CrossSection::JoinType jointype) {
|
||||
C2::JoinType jt = C2::JoinType::Square;
|
||||
switch (jointype) {
|
||||
case CrossSection::JoinType::Square:
|
||||
break;
|
||||
case CrossSection::JoinType::Round:
|
||||
jt = C2::JoinType::Round;
|
||||
break;
|
||||
case CrossSection::JoinType::Miter:
|
||||
jt = C2::JoinType::Miter;
|
||||
break;
|
||||
};
|
||||
return jt;
|
||||
}
|
||||
|
||||
vec2 v2_of_pd(const C2::PointD p) { return {p.x, p.y}; }
|
||||
|
||||
C2::PointD v2_to_pd(const vec2 v) { return C2::PointD(v.x, v.y); }
|
||||
|
||||
C2::PathD pathd_of_contour(const SimplePolygon& ctr) {
|
||||
auto p = C2::PathD();
|
||||
p.reserve(ctr.size());
|
||||
for (auto v : ctr) {
|
||||
p.push_back(v2_to_pd(v));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
C2::PathsD transform(const C2::PathsD ps, const mat2x3 m) {
|
||||
const bool invert = la::determinant(mat2(m)) < 0;
|
||||
auto transformed = C2::PathsD();
|
||||
transformed.reserve(ps.size());
|
||||
for (auto path : ps) {
|
||||
auto sz = path.size();
|
||||
auto s = C2::PathD(sz);
|
||||
for (size_t i = 0; i < sz; ++i) {
|
||||
auto idx = invert ? sz - 1 - i : i;
|
||||
s[idx] = v2_to_pd(m * vec3(path[i].x, path[i].y, 1));
|
||||
}
|
||||
transformed.push_back(s);
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
|
||||
std::shared_ptr<const PathImpl> shared_paths(const C2::PathsD& ps) {
|
||||
return std::make_shared<const PathImpl>(ps);
|
||||
}
|
||||
|
||||
// forward declaration for mutual recursion
|
||||
void decompose_hole(const C2::PolyTreeD* outline,
|
||||
std::vector<C2::PathsD>& polys, C2::PathsD& poly,
|
||||
size_t n_holes, size_t j);
|
||||
|
||||
void decompose_outline(const C2::PolyTreeD* tree,
|
||||
std::vector<C2::PathsD>& polys, size_t i) {
|
||||
auto n_outlines = tree->Count();
|
||||
if (i < n_outlines) {
|
||||
auto outline = tree->Child(i);
|
||||
auto n_holes = outline->Count();
|
||||
auto poly = C2::PathsD(n_holes + 1);
|
||||
poly[0] = outline->Polygon();
|
||||
decompose_hole(outline, polys, poly, n_holes, 0);
|
||||
polys.push_back(poly);
|
||||
if (i < n_outlines - 1) {
|
||||
decompose_outline(tree, polys, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decompose_hole(const C2::PolyTreeD* outline,
|
||||
std::vector<C2::PathsD>& polys, C2::PathsD& poly,
|
||||
size_t n_holes, size_t j) {
|
||||
if (j < n_holes) {
|
||||
auto child = outline->Child(j);
|
||||
decompose_outline(child, polys, 0);
|
||||
poly[j + 1] = child->Polygon();
|
||||
decompose_hole(outline, polys, poly, n_holes, j + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void flatten(const C2::PolyTreeD* tree, C2::PathsD& polys, size_t i) {
|
||||
auto n_outlines = tree->Count();
|
||||
if (i < n_outlines) {
|
||||
auto outline = tree->Child(i);
|
||||
flatten(outline, polys, 0);
|
||||
polys.push_back(outline->Polygon());
|
||||
if (i < n_outlines - 1) {
|
||||
flatten(tree, polys, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool V2Lesser(vec2 a, vec2 b) {
|
||||
if (a.x == b.x) return a.y < b.y;
|
||||
return a.x < b.x;
|
||||
}
|
||||
|
||||
void HullBacktrack(const vec2& pt, std::vector<vec2>& stack) {
|
||||
auto sz = stack.size();
|
||||
while (sz >= 2 && CCW(stack[sz - 2], stack[sz - 1], pt, 0.0) <= 0.0) {
|
||||
stack.pop_back();
|
||||
sz = stack.size();
|
||||
}
|
||||
}
|
||||
|
||||
// Based on method described here:
|
||||
// https://www.hackerearth.com/practice/math/geometry/line-sweep-technique/tutorial/
|
||||
// Changed to follow:
|
||||
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
|
||||
// This is the same algorithm (Andrew, also called Montone Chain).
|
||||
C2::PathD HullImpl(SimplePolygon& pts) {
|
||||
size_t len = pts.size();
|
||||
if (len < 3) return C2::PathD(); // not enough points to create a polygon
|
||||
std::sort(pts.begin(), pts.end(), V2Lesser);
|
||||
|
||||
auto lower = std::vector<vec2>{};
|
||||
for (auto& pt : pts) {
|
||||
HullBacktrack(pt, lower);
|
||||
lower.push_back(pt);
|
||||
}
|
||||
auto upper = std::vector<vec2>{};
|
||||
for (auto pt_iter = pts.rbegin(); pt_iter != pts.rend(); pt_iter++) {
|
||||
HullBacktrack(*pt_iter, upper);
|
||||
upper.push_back(*pt_iter);
|
||||
}
|
||||
|
||||
upper.pop_back();
|
||||
lower.pop_back();
|
||||
|
||||
auto path = C2::PathD();
|
||||
path.reserve(lower.size() + upper.size());
|
||||
for (const auto& l : lower) path.push_back(v2_to_pd(l));
|
||||
for (const auto& u : upper) path.push_back(v2_to_pd(u));
|
||||
return path;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* The default constructor is an empty cross-section (containing no contours).
|
||||
*/
|
||||
CrossSection::CrossSection() {
|
||||
paths_ = std::make_shared<const PathImpl>(C2::PathsD());
|
||||
}
|
||||
|
||||
CrossSection::~CrossSection() = default;
|
||||
CrossSection::CrossSection(CrossSection&&) noexcept = default;
|
||||
CrossSection& CrossSection::operator=(CrossSection&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* The copy constructor avoids copying the underlying paths vector (sharing
|
||||
* with its parent via shared_ptr), however subsequent transformations, and
|
||||
* their application will not be shared. It is generally recommended to avoid
|
||||
* this, opting instead to simply create CrossSections with the available
|
||||
* const methods.
|
||||
*/
|
||||
CrossSection::CrossSection(const CrossSection& other) {
|
||||
paths_ = other.paths_;
|
||||
transform_ = other.transform_;
|
||||
}
|
||||
|
||||
CrossSection& CrossSection::operator=(const CrossSection& other) {
|
||||
if (this != &other) {
|
||||
paths_ = other.paths_;
|
||||
transform_ = other.transform_;
|
||||
}
|
||||
return *this;
|
||||
};
|
||||
|
||||
// Private, skips unioning.
|
||||
CrossSection::CrossSection(std::shared_ptr<const PathImpl> ps) { paths_ = ps; }
|
||||
|
||||
/**
|
||||
* Create a 2d cross-section from a single contour. A boolean union operation
|
||||
* (with Positive filling rule by default) is performed to ensure the
|
||||
* resulting CrossSection is free of self-intersections.
|
||||
*
|
||||
* @param contour A closed path outlining the desired cross-section.
|
||||
* @param fillrule The filling rule used to interpret polygon sub-regions
|
||||
* created by self-intersections in contour.
|
||||
*/
|
||||
CrossSection::CrossSection(const SimplePolygon& contour, FillRule fillrule) {
|
||||
auto ps = C2::PathsD{(pathd_of_contour(contour))};
|
||||
paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 2d cross-section from a set of contours (complex polygons). A
|
||||
* boolean union operation (with Positive filling rule by default) is
|
||||
* performed to combine overlapping polygons and ensure the resulting
|
||||
* CrossSection is free of intersections.
|
||||
*
|
||||
* @param contours A set of closed paths describing zero or more complex
|
||||
* polygons.
|
||||
* @param fillrule The filling rule used to interpret polygon sub-regions in
|
||||
* contours.
|
||||
*/
|
||||
CrossSection::CrossSection(const Polygons& contours, FillRule fillrule) {
|
||||
auto ps = C2::PathsD();
|
||||
ps.reserve(contours.size());
|
||||
for (auto ctr : contours) {
|
||||
ps.push_back(pathd_of_contour(ctr));
|
||||
}
|
||||
paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 2d cross-section from an axis-aligned rectangle (bounding box).
|
||||
*
|
||||
* @param rect An axis-aligned rectangular bounding box.
|
||||
*/
|
||||
CrossSection::CrossSection(const Rect& rect) {
|
||||
C2::PathD p(4);
|
||||
p[0] = C2::PointD(rect.min.x, rect.min.y);
|
||||
p[1] = C2::PointD(rect.max.x, rect.min.y);
|
||||
p[2] = C2::PointD(rect.max.x, rect.max.y);
|
||||
p[3] = C2::PointD(rect.min.x, rect.max.y);
|
||||
paths_ = shared_paths(C2::PathsD{p});
|
||||
}
|
||||
|
||||
// Private
|
||||
// All access to paths_ should be done through the GetPaths() method, which
|
||||
// applies the accumulated transform_
|
||||
std::shared_ptr<const PathImpl> CrossSection::GetPaths() const {
|
||||
if (transform_ == mat2x3(la::identity)) {
|
||||
return paths_;
|
||||
}
|
||||
paths_ = shared_paths(::transform(paths_->paths_, transform_));
|
||||
transform_ = mat2x3(la::identity);
|
||||
return paths_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a square with the given XY dimensions. By default it is
|
||||
* positioned in the first quadrant, touching the origin. If any dimensions in
|
||||
* size are negative, or if all are zero, an empty Manifold will be returned.
|
||||
*
|
||||
* @param size The X, and Y dimensions of the square.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
CrossSection CrossSection::Square(const vec2 size, bool center) {
|
||||
if (size.x < 0.0 || size.y < 0.0 || la::length(size) == 0.0) {
|
||||
return CrossSection();
|
||||
}
|
||||
|
||||
auto p = C2::PathD(4);
|
||||
if (center) {
|
||||
const auto w = size.x / 2;
|
||||
const auto h = size.y / 2;
|
||||
p[0] = C2::PointD(w, h);
|
||||
p[1] = C2::PointD(-w, h);
|
||||
p[2] = C2::PointD(-w, -h);
|
||||
p[3] = C2::PointD(w, -h);
|
||||
} else {
|
||||
const double x = size.x;
|
||||
const double y = size.y;
|
||||
p[0] = C2::PointD(0.0, 0.0);
|
||||
p[1] = C2::PointD(x, 0.0);
|
||||
p[2] = C2::PointD(x, y);
|
||||
p[3] = C2::PointD(0.0, y);
|
||||
}
|
||||
return CrossSection(shared_paths(C2::PathsD{p}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a circle of a given radius.
|
||||
*
|
||||
* @param radius Radius of the circle. Must be positive.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Quality defaults according to the radius.
|
||||
*/
|
||||
CrossSection CrossSection::Circle(double radius, int circularSegments) {
|
||||
if (radius <= 0.0) {
|
||||
return CrossSection();
|
||||
}
|
||||
int n = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(radius);
|
||||
double dPhi = 360.0 / n;
|
||||
auto circle = C2::PathD(n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[i] = C2::PointD(radius * cosd(dPhi * i), radius * sind(dPhi * i));
|
||||
}
|
||||
return CrossSection(shared_paths(C2::PathsD{circle}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given boolean operation between this and another CrossSection.
|
||||
*/
|
||||
CrossSection CrossSection::Boolean(const CrossSection& second,
|
||||
OpType op) const {
|
||||
auto ct = cliptype_of_op(op);
|
||||
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, GetPaths()->paths_,
|
||||
second.GetPaths()->paths_, precision_);
|
||||
return CrossSection(shared_paths(res));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given boolean operation on a list of CrossSections. In case of
|
||||
* Subtract, all CrossSections in the tail are differenced from the head.
|
||||
*/
|
||||
CrossSection CrossSection::BatchBoolean(
|
||||
const std::vector<CrossSection>& crossSections, OpType op) {
|
||||
if (crossSections.size() == 0)
|
||||
return CrossSection();
|
||||
else if (crossSections.size() == 1)
|
||||
return crossSections[0];
|
||||
|
||||
auto subjs = crossSections[0].GetPaths();
|
||||
int n_clips = 0;
|
||||
for (size_t i = 1; i < crossSections.size(); ++i) {
|
||||
n_clips += crossSections[i].GetPaths()->paths_.size();
|
||||
}
|
||||
auto clips = C2::PathsD();
|
||||
clips.reserve(n_clips);
|
||||
for (size_t i = 1; i < crossSections.size(); ++i) {
|
||||
auto ps = crossSections[i].GetPaths();
|
||||
clips.insert(clips.end(), ps->paths_.begin(), ps->paths_.end());
|
||||
}
|
||||
|
||||
auto ct = cliptype_of_op(op);
|
||||
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, subjs->paths_, clips,
|
||||
precision_);
|
||||
return CrossSection(shared_paths(res));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean union between two cross-sections.
|
||||
*/
|
||||
CrossSection CrossSection::operator+(const CrossSection& Q) const {
|
||||
return Boolean(Q, OpType::Add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean union between two cross-sections, assigning the result
|
||||
* to the first.
|
||||
*/
|
||||
CrossSection& CrossSection::operator+=(const CrossSection& Q) {
|
||||
*this = *this + Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean difference of a (clip) cross-section from another
|
||||
* (subject).
|
||||
*/
|
||||
CrossSection CrossSection::operator-(const CrossSection& Q) const {
|
||||
return Boolean(Q, OpType::Subtract);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean difference of a (clip) cross-section from a another
|
||||
* (subject), assigning the result to the subject.
|
||||
*/
|
||||
CrossSection& CrossSection::operator-=(const CrossSection& Q) {
|
||||
*this = *this - Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean intersection between two cross-sections.
|
||||
*/
|
||||
CrossSection CrossSection::operator^(const CrossSection& Q) const {
|
||||
return Boolean(Q, OpType::Intersect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean intersection between two cross-sections, assigning the
|
||||
* result to the first.
|
||||
*/
|
||||
CrossSection& CrossSection::operator^=(const CrossSection& Q) {
|
||||
*this = *this ^ Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a CrossSection from a vector of other CrossSections (batch
|
||||
* boolean union).
|
||||
*/
|
||||
CrossSection CrossSection::Compose(std::vector<CrossSection>& crossSections) {
|
||||
return BatchBoolean(crossSections, OpType::Add);
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation returns a vector of CrossSections that are topologically
|
||||
* disconnected, each containing one outline contour with zero or more
|
||||
* holes.
|
||||
*/
|
||||
std::vector<CrossSection> CrossSection::Decompose() const {
|
||||
if (NumContour() < 2) {
|
||||
return std::vector<CrossSection>{CrossSection(*this)};
|
||||
}
|
||||
|
||||
C2::PolyTreeD tree;
|
||||
C2::BooleanOp(C2::ClipType::Union, C2::FillRule::Positive, GetPaths()->paths_,
|
||||
C2::PathsD(), tree, precision_);
|
||||
|
||||
auto polys = std::vector<C2::PathsD>();
|
||||
decompose_outline(&tree, polys, 0);
|
||||
|
||||
auto comps = std::vector<CrossSection>();
|
||||
comps.reserve(polys.size());
|
||||
// reverse the stack while wrapping
|
||||
for (auto poly = polys.rbegin(); poly != polys.rend(); ++poly)
|
||||
comps.emplace_back(CrossSection(shared_paths(*poly)));
|
||||
|
||||
return comps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this CrossSection in space. This operation can be chained. Transforms
|
||||
* are combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
CrossSection CrossSection::Translate(const vec2 v) const {
|
||||
mat2x3 m({1.0, 0.0}, //
|
||||
{0.0, 1.0}, //
|
||||
{v.x, v.y});
|
||||
return Transform(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a (Z-axis) rotation to the CrossSection, in degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param degrees degrees about the Z-axis to rotate.
|
||||
*/
|
||||
CrossSection CrossSection::Rotate(double degrees) const {
|
||||
auto s = sind(degrees);
|
||||
auto c = cosd(degrees);
|
||||
mat2x3 m({c, s}, //
|
||||
{-s, c}, //
|
||||
{0.0, 0.0});
|
||||
return Transform(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this CrossSection in space. This operation can be chained. Transforms
|
||||
* are combined and applied lazily.
|
||||
*
|
||||
* @param scale The vector to multiply every vertex by per component.
|
||||
*/
|
||||
CrossSection CrossSection::Scale(const vec2 scale) const {
|
||||
mat2x3 m({scale.x, 0.0}, //
|
||||
{0.0, scale.y}, //
|
||||
{0.0, 0.0});
|
||||
return Transform(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror this CrossSection over the arbitrary axis described by the unit form
|
||||
* of the given vector. If the length of the vector is zero, an empty
|
||||
* CrossSection is returned. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param ax the axis to be mirrored over
|
||||
*/
|
||||
CrossSection CrossSection::Mirror(const vec2 ax) const {
|
||||
if (la::length(ax) == 0.) {
|
||||
return CrossSection();
|
||||
}
|
||||
auto n = la::normalize(la::abs(ax));
|
||||
auto m = mat2x3(mat2(la::identity) - 2.0 * la::outerprod(n, n), vec2(0.0));
|
||||
return Transform(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform this CrossSection in space. The first two columns form a 2x2
|
||||
* matrix transform and the last is a translation vector. This operation can
|
||||
* be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param m The affine transform matrix to apply to all the vertices.
|
||||
*/
|
||||
CrossSection CrossSection::Transform(const mat2x3& m) const {
|
||||
auto transformed = CrossSection();
|
||||
transformed.transform_ = m * Mat3(transform_);
|
||||
transformed.paths_ = paths_;
|
||||
return transformed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the vertices of this CrossSection (creating a new one) according to
|
||||
* any arbitrary input function, followed by a union operation (with a
|
||||
* Positive fill rule) that ensures any introduced intersections are not
|
||||
* included in the result.
|
||||
*
|
||||
* @param warpFunc A function that modifies a given vertex position.
|
||||
*/
|
||||
CrossSection CrossSection::Warp(std::function<void(vec2&)> warpFunc) const {
|
||||
return WarpBatch([&warpFunc](VecView<vec2> vecs) {
|
||||
for (vec2& p : vecs) {
|
||||
warpFunc(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as CrossSection::Warp but calls warpFunc with
|
||||
* a VecView which is roughly equivalent to std::span
|
||||
* pointing to all vec2 elements to be modified in-place
|
||||
*
|
||||
* @param warpFunc A function that modifies multiple vertex positions.
|
||||
*/
|
||||
CrossSection CrossSection::WarpBatch(
|
||||
std::function<void(VecView<vec2>)> warpFunc) const {
|
||||
std::vector<vec2> tmp_verts;
|
||||
C2::PathsD paths = GetPaths()->paths_; // deep copy
|
||||
for (C2::PathD const& path : paths) {
|
||||
for (C2::PointD const& p : path) {
|
||||
tmp_verts.push_back(v2_of_pd(p));
|
||||
}
|
||||
}
|
||||
|
||||
warpFunc(VecView<vec2>(tmp_verts.data(), tmp_verts.size()));
|
||||
|
||||
auto cursor = tmp_verts.begin();
|
||||
for (C2::PathD& path : paths) {
|
||||
for (C2::PointD& p : path) {
|
||||
p = v2_to_pd(*cursor);
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
|
||||
return CrossSection(
|
||||
shared_paths(C2::Union(paths, C2::FillRule::Positive, precision_)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove vertices from the contours in this CrossSection that are less than
|
||||
* the specified distance epsilon from an imaginary line that passes through
|
||||
* its two adjacent vertices. Near duplicate vertices and collinear points
|
||||
* will be removed at lower epsilons, with elimination of line segments
|
||||
* becoming increasingly aggressive with larger epsilons.
|
||||
*
|
||||
* It is recommended to apply this function following Offset, in order to
|
||||
* clean up any spurious tiny line segments introduced that do not improve
|
||||
* quality in any meaningful way. This is particularly important if further
|
||||
* offseting operations are to be performed, which would compound the issue.
|
||||
*/
|
||||
CrossSection CrossSection::Simplify(double epsilon) const {
|
||||
C2::PolyTreeD tree;
|
||||
C2::BooleanOp(C2::ClipType::Union, C2::FillRule::Positive, GetPaths()->paths_,
|
||||
C2::PathsD(), tree, precision_);
|
||||
|
||||
C2::PathsD polys;
|
||||
flatten(&tree, polys, 0);
|
||||
|
||||
// Filter out contours less than epsilon wide.
|
||||
C2::PathsD filtered;
|
||||
for (C2::PathD poly : polys) {
|
||||
auto area = C2::Area(poly);
|
||||
Rect box;
|
||||
for (auto vert : poly) {
|
||||
box.Union(vec2(vert.x, vert.y));
|
||||
}
|
||||
vec2 size = box.Size();
|
||||
if (std::abs(area) > std::max(size.x, size.y) * epsilon) {
|
||||
filtered.push_back(poly);
|
||||
}
|
||||
}
|
||||
|
||||
auto ps = SimplifyPaths(filtered, epsilon, true);
|
||||
return CrossSection(shared_paths(ps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the contours in CrossSection by the specified delta, handling
|
||||
* corners according to the given JoinType.
|
||||
*
|
||||
* @param delta Positive deltas will cause the expansion of outlining contours
|
||||
* to expand, and retraction of inner (hole) contours. Negative deltas will
|
||||
* have the opposite effect.
|
||||
* @param jointype The join type specifying the treatment of contour joins
|
||||
* (corners).
|
||||
* @param miter_limit The maximum distance in multiples of delta that vertices
|
||||
* can be offset from their original positions with before squaring is
|
||||
* applied, <B>when the join type is Miter</B> (default is 2, which is the
|
||||
* minimum allowed). See the [Clipper2
|
||||
* MiterLimit](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm)
|
||||
* page for a visual example.
|
||||
* @param circularSegments Number of segments per 360 degrees of
|
||||
* <B>JoinType::Round</B> corners (roughly, the number of vertices that
|
||||
* will be added to each contour). Default is calculated by the static Quality
|
||||
* defaults according to the radius.
|
||||
*/
|
||||
CrossSection CrossSection::Offset(double delta, JoinType jointype,
|
||||
double miter_limit,
|
||||
int circularSegments) const {
|
||||
double arc_tol = 0.;
|
||||
if (jointype == JoinType::Round) {
|
||||
int n = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(delta);
|
||||
// This calculates tolerance as a function of circular segments and delta
|
||||
// (radius) in order to get back the same number of segments in Clipper2:
|
||||
// steps_per_360 = PI / acos(1 - arc_tol / abs_delta)
|
||||
const double abs_delta = std::fabs(delta);
|
||||
const double scaled_delta = abs_delta * std::pow(10, precision_);
|
||||
arc_tol = (std::cos(Clipper2Lib::PI / n) - 1) * -scaled_delta;
|
||||
}
|
||||
auto ps =
|
||||
C2::InflatePaths(GetPaths()->paths_, delta, jt(jointype),
|
||||
C2::EndType::Polygon, miter_limit, precision_, arc_tol);
|
||||
return CrossSection(shared_paths(ps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the convex hull enveloping a set of cross-sections.
|
||||
*
|
||||
* @param crossSections A vector of cross-sections over which to compute a
|
||||
* convex hull.
|
||||
*/
|
||||
CrossSection CrossSection::Hull(
|
||||
const std::vector<CrossSection>& crossSections) {
|
||||
int n = 0;
|
||||
for (auto cs : crossSections) n += cs.NumVert();
|
||||
SimplePolygon pts;
|
||||
pts.reserve(n);
|
||||
for (auto cs : crossSections) {
|
||||
auto paths = cs.GetPaths()->paths_;
|
||||
for (auto path : paths) {
|
||||
for (auto p : path) {
|
||||
pts.push_back(v2_of_pd(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
return CrossSection(shared_paths(C2::PathsD{HullImpl(pts)}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the convex hull of this cross-section.
|
||||
*/
|
||||
CrossSection CrossSection::Hull() const {
|
||||
return Hull(std::vector<CrossSection>{*this});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the convex hull of a set of points. If the given points are fewer
|
||||
* than 3, an empty CrossSection will be returned.
|
||||
*
|
||||
* @param pts A vector of 2-dimensional points over which to compute a convex
|
||||
* hull.
|
||||
*/
|
||||
CrossSection CrossSection::Hull(SimplePolygon pts) {
|
||||
return CrossSection(shared_paths(C2::PathsD{HullImpl(pts)}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the convex hull of a set of points/polygons. If the given points are
|
||||
* fewer than 3, an empty CrossSection will be returned.
|
||||
*
|
||||
* @param polys A vector of vectors of 2-dimensional points over which to
|
||||
* compute a convex hull.
|
||||
*/
|
||||
CrossSection CrossSection::Hull(const Polygons polys) {
|
||||
SimplePolygon pts;
|
||||
for (auto poly : polys) {
|
||||
for (auto p : poly) {
|
||||
pts.push_back(p);
|
||||
}
|
||||
}
|
||||
return Hull(pts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total area covered by complex polygons making up the
|
||||
* CrossSection.
|
||||
*/
|
||||
double CrossSection::Area() const { return C2::Area(GetPaths()->paths_); }
|
||||
|
||||
/**
|
||||
* Return the number of vertices in the CrossSection.
|
||||
*/
|
||||
int CrossSection::NumVert() const {
|
||||
int n = 0;
|
||||
auto paths = GetPaths()->paths_;
|
||||
for (auto p : paths) {
|
||||
n += p.size();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of contours (both outer and inner paths) in the
|
||||
* CrossSection.
|
||||
*/
|
||||
int CrossSection::NumContour() const { return GetPaths()->paths_.size(); }
|
||||
|
||||
/**
|
||||
* Does the CrossSection contain any contours?
|
||||
*/
|
||||
bool CrossSection::IsEmpty() const { return GetPaths()->paths_.empty(); }
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding rectangle of all the CrossSections'
|
||||
* vertices.
|
||||
*/
|
||||
Rect CrossSection::Bounds() const {
|
||||
auto r = C2::GetBounds(GetPaths()->paths_);
|
||||
return Rect({r.left, r.bottom}, {r.right, r.top});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contours of this CrossSection as a Polygons.
|
||||
*/
|
||||
Polygons CrossSection::ToPolygons() const {
|
||||
auto polys = Polygons();
|
||||
auto paths = GetPaths()->paths_;
|
||||
polys.reserve(paths.size());
|
||||
for (auto p : paths) {
|
||||
auto sp = SimplePolygon();
|
||||
sp.reserve(p.size());
|
||||
for (auto v : p) {
|
||||
sp.push_back({v.x, v.y});
|
||||
}
|
||||
polys.push_back(sp);
|
||||
}
|
||||
return polys;
|
||||
}
|
||||
} // namespace manifold
|
||||
643
thirdparty/manifold/src/csg_tree.cpp
vendored
Normal file
643
thirdparty/manifold/src/csg_tree.cpp
vendored
Normal file
@@ -0,0 +1,643 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_priority_queue.h>)
|
||||
#include <tbb/tbb.h>
|
||||
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
|
||||
#include <tbb/concurrent_priority_queue.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <variant>
|
||||
|
||||
#include "./boolean3.h"
|
||||
#include "./csg_tree.h"
|
||||
#include "./impl.h"
|
||||
#include "./mesh_fixes.h"
|
||||
#include "./parallel.h"
|
||||
|
||||
constexpr int kParallelThreshold = 4096;
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
struct Transform4x3 {
|
||||
mat3x4 transform;
|
||||
|
||||
vec3 operator()(vec3 position) const {
|
||||
return transform * vec4(position, 1.0);
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateHalfedge {
|
||||
const int nextVert;
|
||||
const int nextEdge;
|
||||
const int nextFace;
|
||||
|
||||
Halfedge operator()(Halfedge edge) {
|
||||
edge.startVert += nextVert;
|
||||
edge.endVert += nextVert;
|
||||
edge.pairedHalfedge += nextEdge;
|
||||
return edge;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateTriProp {
|
||||
const int nextProp;
|
||||
|
||||
ivec3 operator()(ivec3 tri) {
|
||||
tri += nextProp;
|
||||
return tri;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshIDs {
|
||||
const int offset;
|
||||
|
||||
TriRef operator()(TriRef ref) {
|
||||
ref.meshID += offset;
|
||||
return ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckOverlap {
|
||||
VecView<const Box> boxes;
|
||||
const size_t i;
|
||||
bool operator()(size_t j) { return boxes[i].DoesOverlap(boxes[j]); }
|
||||
};
|
||||
|
||||
using SharedImpl = std::variant<std::shared_ptr<const Manifold::Impl>,
|
||||
std::shared_ptr<Manifold::Impl>>;
|
||||
struct GetImplPtr {
|
||||
const Manifold::Impl *operator()(const SharedImpl &p) {
|
||||
if (std::holds_alternative<std::shared_ptr<const Manifold::Impl>>(p)) {
|
||||
return std::get_if<std::shared_ptr<const Manifold::Impl>>(&p)->get();
|
||||
} else {
|
||||
return std::get_if<std::shared_ptr<Manifold::Impl>>(&p)->get();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct MeshCompare {
|
||||
bool operator()(const SharedImpl &a, const SharedImpl &b) {
|
||||
return GetImplPtr()(a)->NumVert() < GetImplPtr()(b)->NumVert();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
namespace manifold {
|
||||
|
||||
std::shared_ptr<CsgNode> CsgNode::Boolean(
|
||||
const std::shared_ptr<CsgNode> &second, OpType op) {
|
||||
if (auto opNode = std::dynamic_pointer_cast<CsgOpNode>(second)) {
|
||||
// "this" is not a CsgOpNode (which overrides Boolean), but if "second" is
|
||||
// and the operation is commutative, we let it built the tree.
|
||||
if ((op == OpType::Add || op == OpType::Intersect)) {
|
||||
return opNode->Boolean(shared_from_this(), op);
|
||||
}
|
||||
}
|
||||
std::vector<std::shared_ptr<CsgNode>> children({shared_from_this(), second});
|
||||
return std::make_shared<CsgOpNode>(children, op);
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgNode> CsgNode::Translate(const vec3 &t) const {
|
||||
mat3x4 transform = la::identity;
|
||||
transform[3] += t;
|
||||
return Transform(transform);
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgNode> CsgNode::Scale(const vec3 &v) const {
|
||||
mat3x4 transform;
|
||||
for (int i : {0, 1, 2}) transform[i][i] = v[i];
|
||||
return Transform(transform);
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgNode> CsgNode::Rotate(double xDegrees, double yDegrees,
|
||||
double zDegrees) const {
|
||||
mat3 rX({1.0, 0.0, 0.0}, //
|
||||
{0.0, cosd(xDegrees), sind(xDegrees)}, //
|
||||
{0.0, -sind(xDegrees), cosd(xDegrees)});
|
||||
mat3 rY({cosd(yDegrees), 0.0, -sind(yDegrees)}, //
|
||||
{0.0, 1.0, 0.0}, //
|
||||
{sind(yDegrees), 0.0, cosd(yDegrees)});
|
||||
mat3 rZ({cosd(zDegrees), sind(zDegrees), 0.0}, //
|
||||
{-sind(zDegrees), cosd(zDegrees), 0.0}, //
|
||||
{0.0, 0.0, 1.0});
|
||||
mat3x4 transform(rZ * rY * rX, vec3());
|
||||
return Transform(transform);
|
||||
}
|
||||
|
||||
CsgLeafNode::CsgLeafNode() : pImpl_(std::make_shared<Manifold::Impl>()) {}
|
||||
|
||||
CsgLeafNode::CsgLeafNode(std::shared_ptr<const Manifold::Impl> pImpl_)
|
||||
: pImpl_(pImpl_) {}
|
||||
|
||||
CsgLeafNode::CsgLeafNode(std::shared_ptr<const Manifold::Impl> pImpl_,
|
||||
mat3x4 transform_)
|
||||
: pImpl_(pImpl_), transform_(transform_) {}
|
||||
|
||||
std::shared_ptr<const Manifold::Impl> CsgLeafNode::GetImpl() const {
|
||||
if (transform_ == mat3x4(la::identity)) return pImpl_;
|
||||
pImpl_ =
|
||||
std::make_shared<const Manifold::Impl>(pImpl_->Transform(transform_));
|
||||
transform_ = la::identity;
|
||||
return pImpl_;
|
||||
}
|
||||
|
||||
mat3x4 CsgLeafNode::GetTransform() const { return transform_; }
|
||||
|
||||
std::shared_ptr<CsgLeafNode> CsgLeafNode::ToLeafNode() const {
|
||||
return std::make_shared<CsgLeafNode>(*this);
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgNode> CsgLeafNode::Transform(const mat3x4 &m) const {
|
||||
return std::make_shared<CsgLeafNode>(pImpl_, m * Mat4(transform_));
|
||||
}
|
||||
|
||||
CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::Leaf; }
|
||||
|
||||
/**
|
||||
* Efficient union of a set of pairwise disjoint meshes.
|
||||
*/
|
||||
Manifold::Impl CsgLeafNode::Compose(
|
||||
const std::vector<std::shared_ptr<CsgLeafNode>> &nodes) {
|
||||
ZoneScoped;
|
||||
double epsilon = -1;
|
||||
double tolerance = -1;
|
||||
int numVert = 0;
|
||||
int numEdge = 0;
|
||||
int numTri = 0;
|
||||
int numPropVert = 0;
|
||||
std::vector<int> vertIndices;
|
||||
std::vector<int> edgeIndices;
|
||||
std::vector<int> triIndices;
|
||||
std::vector<int> propVertIndices;
|
||||
int numPropOut = 0;
|
||||
for (auto &node : nodes) {
|
||||
if (node->pImpl_->status_ != Manifold::Error::NoError) {
|
||||
Manifold::Impl impl;
|
||||
impl.status_ = node->pImpl_->status_;
|
||||
return impl;
|
||||
}
|
||||
double nodeOldScale = node->pImpl_->bBox_.Scale();
|
||||
double nodeNewScale =
|
||||
node->pImpl_->bBox_.Transform(node->transform_).Scale();
|
||||
double nodeEpsilon = node->pImpl_->epsilon_;
|
||||
nodeEpsilon *= std::max(1.0, nodeNewScale / nodeOldScale);
|
||||
nodeEpsilon = std::max(nodeEpsilon, kPrecision * nodeNewScale);
|
||||
if (!std::isfinite(nodeEpsilon)) nodeEpsilon = -1;
|
||||
epsilon = std::max(epsilon, nodeEpsilon);
|
||||
tolerance = std::max(tolerance, node->pImpl_->tolerance_);
|
||||
|
||||
vertIndices.push_back(numVert);
|
||||
edgeIndices.push_back(numEdge * 2);
|
||||
triIndices.push_back(numTri);
|
||||
propVertIndices.push_back(numPropVert);
|
||||
numVert += node->pImpl_->NumVert();
|
||||
numEdge += node->pImpl_->NumEdge();
|
||||
numTri += node->pImpl_->NumTri();
|
||||
const int numProp = node->pImpl_->NumProp();
|
||||
numPropOut = std::max(numPropOut, numProp);
|
||||
numPropVert +=
|
||||
numProp == 0 ? 1
|
||||
: node->pImpl_->meshRelation_.properties.size() / numProp;
|
||||
}
|
||||
|
||||
Manifold::Impl combined;
|
||||
combined.epsilon_ = epsilon;
|
||||
combined.tolerance_ = tolerance;
|
||||
combined.vertPos_.resize(numVert);
|
||||
combined.halfedge_.resize(2 * numEdge);
|
||||
combined.faceNormal_.resize(numTri);
|
||||
combined.halfedgeTangent_.resize(2 * numEdge);
|
||||
combined.meshRelation_.triRef.resize(numTri);
|
||||
if (numPropOut > 0) {
|
||||
combined.meshRelation_.numProp = numPropOut;
|
||||
combined.meshRelation_.properties.resize(numPropOut * numPropVert, 0);
|
||||
combined.meshRelation_.triProperties.resize(numTri);
|
||||
}
|
||||
auto policy = autoPolicy(numTri);
|
||||
|
||||
// if we are already parallelizing for each node, do not perform multithreaded
|
||||
// copying as it will slightly hurt performance
|
||||
if (nodes.size() > 1 && policy == ExecutionPolicy::Par)
|
||||
policy = ExecutionPolicy::Seq;
|
||||
|
||||
for_each_n(
|
||||
nodes.size() > 1 ? ExecutionPolicy::Par : ExecutionPolicy::Seq,
|
||||
countAt(0), nodes.size(),
|
||||
[&nodes, &vertIndices, &edgeIndices, &triIndices, &propVertIndices,
|
||||
numPropOut, &combined, policy](int i) {
|
||||
auto &node = nodes[i];
|
||||
copy(node->pImpl_->halfedgeTangent_.begin(),
|
||||
node->pImpl_->halfedgeTangent_.end(),
|
||||
combined.halfedgeTangent_.begin() + edgeIndices[i]);
|
||||
transform(
|
||||
node->pImpl_->halfedge_.begin(), node->pImpl_->halfedge_.end(),
|
||||
combined.halfedge_.begin() + edgeIndices[i],
|
||||
UpdateHalfedge({vertIndices[i], edgeIndices[i], triIndices[i]}));
|
||||
|
||||
if (numPropOut > 0) {
|
||||
auto start =
|
||||
combined.meshRelation_.triProperties.begin() + triIndices[i];
|
||||
if (node->pImpl_->NumProp() > 0) {
|
||||
auto &triProp = node->pImpl_->meshRelation_.triProperties;
|
||||
transform(triProp.begin(), triProp.end(), start,
|
||||
UpdateTriProp({propVertIndices[i]}));
|
||||
|
||||
const int numProp = node->pImpl_->NumProp();
|
||||
auto &oldProp = node->pImpl_->meshRelation_.properties;
|
||||
auto &newProp = combined.meshRelation_.properties;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
auto oldRange =
|
||||
StridedRange(oldProp.cbegin() + p, oldProp.cend(), numProp);
|
||||
auto newRange = StridedRange(
|
||||
newProp.begin() + numPropOut * propVertIndices[i] + p,
|
||||
newProp.end(), numPropOut);
|
||||
copy(oldRange.begin(), oldRange.end(), newRange.begin());
|
||||
}
|
||||
} else {
|
||||
// point all triangles at single new property of zeros.
|
||||
fill(start, start + node->pImpl_->NumTri(),
|
||||
ivec3(propVertIndices[i]));
|
||||
}
|
||||
}
|
||||
|
||||
if (node->transform_ == mat3x4(la::identity)) {
|
||||
copy(node->pImpl_->vertPos_.begin(), node->pImpl_->vertPos_.end(),
|
||||
combined.vertPos_.begin() + vertIndices[i]);
|
||||
copy(node->pImpl_->faceNormal_.begin(),
|
||||
node->pImpl_->faceNormal_.end(),
|
||||
combined.faceNormal_.begin() + triIndices[i]);
|
||||
} else {
|
||||
// no need to apply the transform to the node, just copy the vertices
|
||||
// and face normals and apply transform on the fly
|
||||
auto vertPosBegin = TransformIterator(
|
||||
node->pImpl_->vertPos_.begin(), Transform4x3({node->transform_}));
|
||||
mat3 normalTransform =
|
||||
la::inverse(la::transpose(mat3(node->transform_)));
|
||||
auto faceNormalBegin =
|
||||
TransformIterator(node->pImpl_->faceNormal_.begin(),
|
||||
TransformNormals({normalTransform}));
|
||||
copy_n(vertPosBegin, node->pImpl_->vertPos_.size(),
|
||||
combined.vertPos_.begin() + vertIndices[i]);
|
||||
copy_n(faceNormalBegin, node->pImpl_->faceNormal_.size(),
|
||||
combined.faceNormal_.begin() + triIndices[i]);
|
||||
|
||||
const bool invert = la::determinant(mat3(node->transform_)) < 0;
|
||||
for_each_n(policy, countAt(0), node->pImpl_->halfedgeTangent_.size(),
|
||||
TransformTangents{combined.halfedgeTangent_,
|
||||
edgeIndices[i], mat3(node->transform_),
|
||||
invert, node->pImpl_->halfedgeTangent_,
|
||||
node->pImpl_->halfedge_});
|
||||
if (invert)
|
||||
for_each_n(policy, countAt(triIndices[i]), node->pImpl_->NumTri(),
|
||||
FlipTris({combined.halfedge_}));
|
||||
}
|
||||
// Since the nodes may be copies containing the same meshIDs, it is
|
||||
// important to add an offset so that each node instance gets
|
||||
// unique meshIDs.
|
||||
const int offset = i * Manifold::Impl::meshIDCounter_;
|
||||
transform(node->pImpl_->meshRelation_.triRef.begin(),
|
||||
node->pImpl_->meshRelation_.triRef.end(),
|
||||
combined.meshRelation_.triRef.begin() + triIndices[i],
|
||||
UpdateMeshIDs({offset}));
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < nodes.size(); i++) {
|
||||
auto &node = nodes[i];
|
||||
const int offset = i * Manifold::Impl::meshIDCounter_;
|
||||
|
||||
for (const auto &pair : node->pImpl_->meshRelation_.meshIDtransform) {
|
||||
combined.meshRelation_.meshIDtransform[pair.first + offset] = pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
// required to remove parts that are smaller than the tolerance
|
||||
combined.SimplifyTopology();
|
||||
combined.Finish();
|
||||
combined.IncrementMeshIDs();
|
||||
return combined;
|
||||
}
|
||||
|
||||
CsgOpNode::CsgOpNode() {}
|
||||
|
||||
CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>> &children,
|
||||
OpType op)
|
||||
: impl_(Impl{}) {
|
||||
auto impl = impl_.GetGuard();
|
||||
impl->children_ = children;
|
||||
SetOp(op);
|
||||
}
|
||||
|
||||
CsgOpNode::CsgOpNode(std::vector<std::shared_ptr<CsgNode>> &&children,
|
||||
OpType op)
|
||||
: impl_(Impl{}) {
|
||||
auto impl = impl_.GetGuard();
|
||||
impl->children_ = children;
|
||||
SetOp(op);
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgNode> CsgOpNode::Boolean(
|
||||
const std::shared_ptr<CsgNode> &second, OpType op) {
|
||||
std::vector<std::shared_ptr<CsgNode>> children;
|
||||
|
||||
auto isReused = [](const auto &node) { return node->impl_.UseCount() > 1; };
|
||||
|
||||
auto copyChildren = [&](const auto &list, const mat3x4 &transform) {
|
||||
for (const auto &child : list) {
|
||||
children.push_back(child->Transform(transform));
|
||||
}
|
||||
};
|
||||
|
||||
auto self = std::dynamic_pointer_cast<CsgOpNode>(shared_from_this());
|
||||
if (IsOp(op) && !isReused(self)) {
|
||||
auto impl = impl_.GetGuard();
|
||||
copyChildren(impl->children_, transform_);
|
||||
} else {
|
||||
children.push_back(self);
|
||||
}
|
||||
|
||||
auto secondOp = std::dynamic_pointer_cast<CsgOpNode>(second);
|
||||
auto canInlineSecondOp = [&]() {
|
||||
switch (op) {
|
||||
case OpType::Add:
|
||||
case OpType::Intersect:
|
||||
return secondOp->IsOp(op);
|
||||
case OpType::Subtract:
|
||||
return secondOp->IsOp(OpType::Add);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (secondOp && canInlineSecondOp() && !isReused(secondOp)) {
|
||||
auto secondImpl = secondOp->impl_.GetGuard();
|
||||
copyChildren(secondImpl->children_, secondOp->transform_);
|
||||
} else {
|
||||
children.push_back(second);
|
||||
}
|
||||
|
||||
return std::make_shared<CsgOpNode>(children, op);
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgNode> CsgOpNode::Transform(const mat3x4 &m) const {
|
||||
auto node = std::make_shared<CsgOpNode>();
|
||||
node->impl_ = impl_;
|
||||
node->transform_ = m * Mat4(transform_);
|
||||
node->op_ = op_;
|
||||
return node;
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
|
||||
if (cache_ != nullptr) return cache_;
|
||||
// turn the children into leaf nodes
|
||||
GetChildren();
|
||||
auto impl = impl_.GetGuard();
|
||||
auto &children_ = impl->children_;
|
||||
if (children_.size() > 1) {
|
||||
switch (op_) {
|
||||
case CsgNodeType::Union:
|
||||
BatchUnion();
|
||||
break;
|
||||
case CsgNodeType::Intersection: {
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> impls;
|
||||
for (auto &child : children_) {
|
||||
impls.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(child)->GetImpl());
|
||||
}
|
||||
children_.clear();
|
||||
children_.push_back(std::make_shared<CsgLeafNode>(
|
||||
BatchBoolean(OpType::Intersect, impls)));
|
||||
break;
|
||||
};
|
||||
case CsgNodeType::Difference: {
|
||||
// take the lhs out and treat the remaining nodes as the rhs, perform
|
||||
// union optimization for them
|
||||
auto lhs = std::dynamic_pointer_cast<CsgLeafNode>(children_.front());
|
||||
children_.erase(children_.begin());
|
||||
BatchUnion();
|
||||
auto rhs = std::dynamic_pointer_cast<CsgLeafNode>(children_.front());
|
||||
children_.clear();
|
||||
Boolean3 boolean(*lhs->GetImpl(), *rhs->GetImpl(), OpType::Subtract);
|
||||
children_.push_back(
|
||||
std::make_shared<CsgLeafNode>(std::make_shared<Manifold::Impl>(
|
||||
boolean.Result(OpType::Subtract))));
|
||||
};
|
||||
case CsgNodeType::Leaf:
|
||||
// unreachable
|
||||
break;
|
||||
}
|
||||
} else if (children_.size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
// children_ must contain only one CsgLeafNode now, and its Transform will
|
||||
// give CsgLeafNode as well
|
||||
cache_ = std::dynamic_pointer_cast<CsgLeafNode>(
|
||||
children_.front()->Transform(transform_));
|
||||
return cache_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Efficient boolean operation on a set of nodes utilizing commutativity of the
|
||||
* operation. Only supports union and intersection.
|
||||
*/
|
||||
std::shared_ptr<Manifold::Impl> CsgOpNode::BatchBoolean(
|
||||
OpType operation,
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> &results) {
|
||||
ZoneScoped;
|
||||
auto getImplPtr = GetImplPtr();
|
||||
DEBUG_ASSERT(operation != OpType::Subtract, logicErr,
|
||||
"BatchBoolean doesn't support Difference.");
|
||||
// common cases
|
||||
if (results.size() == 0) return std::make_shared<Manifold::Impl>();
|
||||
if (results.size() == 1)
|
||||
return std::make_shared<Manifold::Impl>(*results.front());
|
||||
if (results.size() == 2) {
|
||||
Boolean3 boolean(*results[0], *results[1], operation);
|
||||
return std::make_shared<Manifold::Impl>(boolean.Result(operation));
|
||||
}
|
||||
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/tbb.h>)
|
||||
tbb::task_group group;
|
||||
tbb::concurrent_priority_queue<SharedImpl, MeshCompare> queue(results.size());
|
||||
for (auto result : results) {
|
||||
queue.emplace(result);
|
||||
}
|
||||
results.clear();
|
||||
std::function<void()> process = [&]() {
|
||||
while (queue.size() > 1) {
|
||||
SharedImpl a, b;
|
||||
if (!queue.try_pop(a)) continue;
|
||||
if (!queue.try_pop(b)) {
|
||||
queue.push(a);
|
||||
continue;
|
||||
}
|
||||
group.run([&, a, b]() {
|
||||
Boolean3 boolean(*getImplPtr(a), *getImplPtr(b), operation);
|
||||
queue.emplace(
|
||||
std::make_shared<Manifold::Impl>(boolean.Result(operation)));
|
||||
return group.run(process);
|
||||
});
|
||||
}
|
||||
};
|
||||
group.run_and_wait(process);
|
||||
SharedImpl r;
|
||||
queue.try_pop(r);
|
||||
return *std::get_if<std::shared_ptr<Manifold::Impl>>(&r);
|
||||
#endif
|
||||
// apply boolean operations starting from smaller meshes
|
||||
// the assumption is that boolean operations on smaller meshes is faster,
|
||||
// due to less data being copied and processed
|
||||
auto cmpFn = MeshCompare();
|
||||
std::make_heap(results.begin(), results.end(), cmpFn);
|
||||
while (results.size() > 1) {
|
||||
std::pop_heap(results.begin(), results.end(), cmpFn);
|
||||
auto a = std::move(results.back());
|
||||
results.pop_back();
|
||||
std::pop_heap(results.begin(), results.end(), cmpFn);
|
||||
auto b = std::move(results.back());
|
||||
results.pop_back();
|
||||
// boolean operation
|
||||
Boolean3 boolean(*a, *b, operation);
|
||||
auto result = std::make_shared<Manifold::Impl>(boolean.Result(operation));
|
||||
if (results.size() == 0) {
|
||||
return result;
|
||||
}
|
||||
results.push_back(result);
|
||||
std::push_heap(results.begin(), results.end(), cmpFn);
|
||||
}
|
||||
return std::make_shared<Manifold::Impl>(*results.front());
|
||||
}
|
||||
|
||||
/**
|
||||
* Efficient union operation on a set of nodes by doing Compose as much as
|
||||
* possible.
|
||||
* Note: Due to some unknown issues with `Compose`, we are now doing
|
||||
* `BatchBoolean` instead of using `Compose` for non-intersecting manifolds.
|
||||
*/
|
||||
void CsgOpNode::BatchUnion() const {
|
||||
ZoneScoped;
|
||||
// INVARIANT: children_ is a vector of leaf nodes
|
||||
// this kMaxUnionSize is a heuristic to avoid the pairwise disjoint check
|
||||
// with O(n^2) complexity to take too long.
|
||||
// If the number of children exceeded this limit, we will operate on chunks
|
||||
// with size kMaxUnionSize.
|
||||
constexpr size_t kMaxUnionSize = 1000;
|
||||
auto impl = impl_.GetGuard();
|
||||
auto &children_ = impl->children_;
|
||||
while (children_.size() > 1) {
|
||||
const size_t start = (children_.size() > kMaxUnionSize)
|
||||
? (children_.size() - kMaxUnionSize)
|
||||
: 0;
|
||||
Vec<Box> boxes;
|
||||
boxes.reserve(children_.size() - start);
|
||||
for (size_t i = start; i < children_.size(); i++) {
|
||||
boxes.push_back(std::dynamic_pointer_cast<CsgLeafNode>(children_[i])
|
||||
->GetImpl()
|
||||
->bBox_);
|
||||
}
|
||||
// partition the children into a set of disjoint sets
|
||||
// each set contains a set of children that are pairwise disjoint
|
||||
std::vector<Vec<size_t>> disjointSets;
|
||||
for (size_t i = 0; i < boxes.size(); i++) {
|
||||
auto lambda = [&boxes, i](const Vec<size_t> &set) {
|
||||
return std::find_if(set.begin(), set.end(), CheckOverlap({boxes, i})) ==
|
||||
set.end();
|
||||
};
|
||||
auto it = std::find_if(disjointSets.begin(), disjointSets.end(), lambda);
|
||||
if (it == disjointSets.end()) {
|
||||
disjointSets.push_back(std::vector<size_t>{i});
|
||||
} else {
|
||||
it->push_back(i);
|
||||
}
|
||||
}
|
||||
// compose each set of disjoint children
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> impls;
|
||||
for (auto &set : disjointSets) {
|
||||
if (set.size() == 1) {
|
||||
impls.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(children_[start + set[0]])
|
||||
->GetImpl());
|
||||
} else {
|
||||
std::vector<std::shared_ptr<CsgLeafNode>> tmp;
|
||||
for (size_t j : set) {
|
||||
tmp.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(children_[start + j]));
|
||||
}
|
||||
impls.push_back(
|
||||
std::make_shared<const Manifold::Impl>(CsgLeafNode::Compose(tmp)));
|
||||
}
|
||||
}
|
||||
|
||||
children_.erase(children_.begin() + start, children_.end());
|
||||
children_.push_back(
|
||||
std::make_shared<CsgLeafNode>(BatchBoolean(OpType::Add, impls)));
|
||||
// move it to the front as we process from the back, and the newly added
|
||||
// child should be quite complicated
|
||||
std::swap(children_.front(), children_.back());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten the children to a list of leaf nodes and return them.
|
||||
* If forceToLeafNodes is true, the list will be guaranteed to be a list of leaf
|
||||
* nodes (i.e. no ops). Otherwise, the list may contain ops. Note that this
|
||||
* function will not apply the transform to children, as they may be shared with
|
||||
* other nodes.
|
||||
*/
|
||||
std::vector<std::shared_ptr<CsgNode>> &CsgOpNode::GetChildren(
|
||||
bool forceToLeafNodes) const {
|
||||
auto impl = impl_.GetGuard();
|
||||
|
||||
if (forceToLeafNodes && !impl->forcedToLeafNodes_) {
|
||||
impl->forcedToLeafNodes_ = true;
|
||||
for_each(ExecutionPolicy::Par, impl->children_.begin(),
|
||||
impl->children_.end(), [](auto &child) {
|
||||
if (child->GetNodeType() != CsgNodeType::Leaf) {
|
||||
child = child->ToLeafNode();
|
||||
}
|
||||
});
|
||||
}
|
||||
return impl->children_;
|
||||
}
|
||||
|
||||
void CsgOpNode::SetOp(OpType op) {
|
||||
switch (op) {
|
||||
case OpType::Add:
|
||||
op_ = CsgNodeType::Union;
|
||||
break;
|
||||
case OpType::Subtract:
|
||||
op_ = CsgNodeType::Difference;
|
||||
break;
|
||||
case OpType::Intersect:
|
||||
op_ = CsgNodeType::Intersection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CsgOpNode::IsOp(OpType op) {
|
||||
switch (op) {
|
||||
case OpType::Add:
|
||||
return op_ == CsgNodeType::Union;
|
||||
case OpType::Subtract:
|
||||
return op_ == CsgNodeType::Difference;
|
||||
case OpType::Intersect:
|
||||
return op_ == CsgNodeType::Intersection;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mat3x4 CsgOpNode::GetTransform() const { return transform_; }
|
||||
|
||||
} // namespace manifold
|
||||
108
thirdparty/manifold/src/csg_tree.h
vendored
Normal file
108
thirdparty/manifold/src/csg_tree.h
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "./utils.h"
|
||||
#include "manifold/manifold.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
enum class CsgNodeType { Union, Intersection, Difference, Leaf };
|
||||
|
||||
class CsgLeafNode;
|
||||
|
||||
class CsgNode : public std::enable_shared_from_this<CsgNode> {
|
||||
public:
|
||||
virtual std::shared_ptr<CsgLeafNode> ToLeafNode() const = 0;
|
||||
virtual std::shared_ptr<CsgNode> Transform(const mat3x4 &m) const = 0;
|
||||
virtual CsgNodeType GetNodeType() const = 0;
|
||||
virtual mat3x4 GetTransform() const = 0;
|
||||
|
||||
virtual std::shared_ptr<CsgNode> Boolean(
|
||||
const std::shared_ptr<CsgNode> &second, OpType op);
|
||||
|
||||
std::shared_ptr<CsgNode> Translate(const vec3 &t) const;
|
||||
std::shared_ptr<CsgNode> Scale(const vec3 &s) const;
|
||||
std::shared_ptr<CsgNode> Rotate(double xDegrees = 0, double yDegrees = 0,
|
||||
double zDegrees = 0) const;
|
||||
};
|
||||
|
||||
class CsgLeafNode final : public CsgNode {
|
||||
public:
|
||||
CsgLeafNode();
|
||||
CsgLeafNode(std::shared_ptr<const Manifold::Impl> pImpl_);
|
||||
CsgLeafNode(std::shared_ptr<const Manifold::Impl> pImpl_, mat3x4 transform_);
|
||||
|
||||
std::shared_ptr<const Manifold::Impl> GetImpl() const;
|
||||
|
||||
std::shared_ptr<CsgLeafNode> ToLeafNode() const override;
|
||||
|
||||
std::shared_ptr<CsgNode> Transform(const mat3x4 &m) const override;
|
||||
|
||||
CsgNodeType GetNodeType() const override;
|
||||
|
||||
mat3x4 GetTransform() const override;
|
||||
|
||||
static Manifold::Impl Compose(
|
||||
const std::vector<std::shared_ptr<CsgLeafNode>> &nodes);
|
||||
|
||||
private:
|
||||
mutable std::shared_ptr<const Manifold::Impl> pImpl_;
|
||||
mutable mat3x4 transform_ = la::identity;
|
||||
};
|
||||
|
||||
class CsgOpNode final : public CsgNode {
|
||||
public:
|
||||
CsgOpNode();
|
||||
|
||||
CsgOpNode(const std::vector<std::shared_ptr<CsgNode>> &children, OpType op);
|
||||
|
||||
CsgOpNode(std::vector<std::shared_ptr<CsgNode>> &&children, OpType op);
|
||||
|
||||
std::shared_ptr<CsgNode> Boolean(const std::shared_ptr<CsgNode> &second,
|
||||
OpType op) override;
|
||||
|
||||
std::shared_ptr<CsgNode> Transform(const mat3x4 &m) const override;
|
||||
|
||||
std::shared_ptr<CsgLeafNode> ToLeafNode() const override;
|
||||
|
||||
CsgNodeType GetNodeType() const override { return op_; }
|
||||
|
||||
mat3x4 GetTransform() const override;
|
||||
|
||||
private:
|
||||
struct Impl {
|
||||
std::vector<std::shared_ptr<CsgNode>> children_;
|
||||
bool forcedToLeafNodes_ = false;
|
||||
};
|
||||
mutable ConcurrentSharedPtr<Impl> impl_ = ConcurrentSharedPtr<Impl>(Impl{});
|
||||
CsgNodeType op_;
|
||||
mat3x4 transform_ = la::identity;
|
||||
// the following fields are for lazy evaluation, so they are mutable
|
||||
mutable std::shared_ptr<CsgLeafNode> cache_ = nullptr;
|
||||
|
||||
void SetOp(OpType);
|
||||
bool IsOp(OpType op);
|
||||
|
||||
static std::shared_ptr<Manifold::Impl> BatchBoolean(
|
||||
OpType operation,
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> &results);
|
||||
|
||||
void BatchUnion() const;
|
||||
|
||||
std::vector<std::shared_ptr<CsgNode>> &GetChildren(
|
||||
bool forceToLeafNodes = true) const;
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
696
thirdparty/manifold/src/edge_op.cpp
vendored
Normal file
696
thirdparty/manifold/src/edge_op.cpp
vendored
Normal file
@@ -0,0 +1,696 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "./impl.h"
|
||||
#include "./parallel.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
ivec3 TriOf(int edge) {
|
||||
ivec3 triEdge;
|
||||
triEdge[0] = edge;
|
||||
triEdge[1] = NextHalfedge(triEdge[0]);
|
||||
triEdge[2] = NextHalfedge(triEdge[1]);
|
||||
return triEdge;
|
||||
}
|
||||
|
||||
bool Is01Longest(vec2 v0, vec2 v1, vec2 v2) {
|
||||
const vec2 e[3] = {v1 - v0, v2 - v1, v0 - v2};
|
||||
double l[3];
|
||||
for (int i : {0, 1, 2}) l[i] = la::dot(e[i], e[i]);
|
||||
return l[0] > l[1] && l[0] > l[2];
|
||||
}
|
||||
|
||||
struct DuplicateEdge {
|
||||
const Halfedge* sortedHalfedge;
|
||||
|
||||
bool operator()(int edge) {
|
||||
const Halfedge& halfedge = sortedHalfedge[edge];
|
||||
const Halfedge& nextHalfedge = sortedHalfedge[edge + 1];
|
||||
return halfedge.startVert == nextHalfedge.startVert &&
|
||||
halfedge.endVert == nextHalfedge.endVert;
|
||||
}
|
||||
};
|
||||
|
||||
struct ShortEdge {
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const vec3> vertPos;
|
||||
const double tolerance;
|
||||
|
||||
bool operator()(int edge) const {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag short edges
|
||||
const vec3 delta =
|
||||
vertPos[halfedge[edge].endVert] - vertPos[halfedge[edge].startVert];
|
||||
return la::dot(delta, delta) < tolerance * tolerance;
|
||||
}
|
||||
};
|
||||
|
||||
struct FlagEdge {
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const TriRef> triRef;
|
||||
|
||||
bool operator()(int edge) const {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag redundant edges - those where the startVert is surrounded by only
|
||||
// two original triangles.
|
||||
const TriRef ref0 = triRef[edge / 3];
|
||||
int current = NextHalfedge(halfedge[edge].pairedHalfedge);
|
||||
const TriRef ref1 = triRef[current / 3];
|
||||
while (current != edge) {
|
||||
current = NextHalfedge(halfedge[current].pairedHalfedge);
|
||||
int tri = current / 3;
|
||||
const TriRef ref = triRef[tri];
|
||||
if (!ref.SameFace(ref0) && !ref.SameFace(ref1)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct SwappableEdge {
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const vec3> vertPos;
|
||||
VecView<const vec3> triNormal;
|
||||
const double tolerance;
|
||||
|
||||
bool operator()(int edge) const {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
|
||||
int tri = edge / 3;
|
||||
ivec3 triEdge = TriOf(edge);
|
||||
mat2x3 projection = GetAxisAlignedProjection(triNormal[tri]);
|
||||
vec2 v[3];
|
||||
for (int i : {0, 1, 2})
|
||||
v[i] = projection * vertPos[halfedge[triEdge[i]].startVert];
|
||||
if (CCW(v[0], v[1], v[2], tolerance) > 0 || !Is01Longest(v[0], v[1], v[2]))
|
||||
return false;
|
||||
|
||||
// Switch to neighbor's projection.
|
||||
edge = halfedge[edge].pairedHalfedge;
|
||||
tri = edge / 3;
|
||||
triEdge = TriOf(edge);
|
||||
projection = GetAxisAlignedProjection(triNormal[tri]);
|
||||
for (int i : {0, 1, 2})
|
||||
v[i] = projection * vertPos[halfedge[triEdge[i]].startVert];
|
||||
return CCW(v[0], v[1], v[2], tolerance) > 0 ||
|
||||
Is01Longest(v[0], v[1], v[2]);
|
||||
}
|
||||
};
|
||||
|
||||
struct SortEntry {
|
||||
int start;
|
||||
int end;
|
||||
size_t index;
|
||||
inline bool operator<(const SortEntry& other) const {
|
||||
return start == other.start ? end < other.end : start < other.start;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Duplicates just enough verts to covert an even-manifold to a proper
|
||||
* 2-manifold, splitting non-manifold verts and edges with too many triangles.
|
||||
*/
|
||||
void Manifold::Impl::CleanupTopology() {
|
||||
if (!halfedge_.size()) return;
|
||||
|
||||
// In the case of a very bad triangulation, it is possible to create pinched
|
||||
// verts. They must be removed before edge collapse.
|
||||
SplitPinchedVerts();
|
||||
|
||||
while (1) {
|
||||
ZoneScopedN("DedupeEdge");
|
||||
|
||||
const size_t nbEdges = halfedge_.size();
|
||||
size_t numFlagged = 0;
|
||||
|
||||
Vec<SortEntry> entries;
|
||||
entries.reserve(nbEdges / 2);
|
||||
for (size_t i = 0; i < nbEdges; ++i) {
|
||||
if (halfedge_[i].IsForward()) {
|
||||
entries.push_back({halfedge_[i].startVert, halfedge_[i].endVert, i});
|
||||
}
|
||||
}
|
||||
|
||||
stable_sort(entries.begin(), entries.end());
|
||||
for (size_t i = 0; i < entries.size() - 1; ++i) {
|
||||
const int h0 = entries[i].index;
|
||||
const int h1 = entries[i + 1].index;
|
||||
if (halfedge_[h0].startVert == halfedge_[h1].startVert &&
|
||||
halfedge_[h0].endVert == halfedge_[h1].endVert) {
|
||||
DedupeEdge(entries[i].index);
|
||||
numFlagged++;
|
||||
}
|
||||
}
|
||||
|
||||
if (numFlagged == 0) break;
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (ManifoldParams().verbose) {
|
||||
std::cout << "found " << numFlagged << " duplicate edges to split"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses degenerate triangles by removing edges shorter than tolerance_ and
|
||||
* any edge that is preceeded by an edge that joins the same two face relations.
|
||||
* It also performs edge swaps on the long edges of degenerate triangles, though
|
||||
* there are some configurations of degenerates that cannot be removed this way.
|
||||
*
|
||||
* Before collapsing edges, the mesh is checked for duplicate edges (more than
|
||||
* one pair of triangles sharing the same edge), which are removed by
|
||||
* duplicating one vert and adding two triangles. These degenerate triangles are
|
||||
* likely to be collapsed again in the subsequent simplification.
|
||||
*
|
||||
* Note when an edge collapse would result in something non-manifold, the
|
||||
* vertices are duplicated in such a way as to remove handles or separate
|
||||
* meshes, thus decreasing the Genus(). It only increases when meshes that have
|
||||
* collapsed to just a pair of triangles are removed entirely.
|
||||
*
|
||||
* Rather than actually removing the edges, this step merely marks them for
|
||||
* removal, by setting vertPos to NaN and halfedge to {-1, -1, -1, -1}.
|
||||
*/
|
||||
void Manifold::Impl::SimplifyTopology() {
|
||||
if (!halfedge_.size()) return;
|
||||
|
||||
CleanupTopology();
|
||||
|
||||
if (!ManifoldParams().cleanupTriangles) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t nbEdges = halfedge_.size();
|
||||
auto policy = autoPolicy(nbEdges, 1e5);
|
||||
size_t numFlagged = 0;
|
||||
Vec<uint8_t> bFlags(nbEdges);
|
||||
|
||||
std::vector<int> scratchBuffer;
|
||||
scratchBuffer.reserve(10);
|
||||
{
|
||||
ZoneScopedN("CollapseShortEdge");
|
||||
numFlagged = 0;
|
||||
ShortEdge se{halfedge_, vertPos_, epsilon_};
|
||||
for_each_n(policy, countAt(0_uz), nbEdges,
|
||||
[&](size_t i) { bFlags[i] = se(i); });
|
||||
for (size_t i = 0; i < nbEdges; ++i) {
|
||||
if (bFlags[i]) {
|
||||
CollapseEdge(i, scratchBuffer);
|
||||
scratchBuffer.resize(0);
|
||||
numFlagged++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (ManifoldParams().verbose && numFlagged > 0) {
|
||||
std::cout << "found " << numFlagged << " short edges to collapse"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
ZoneScopedN("CollapseFlaggedEdge");
|
||||
numFlagged = 0;
|
||||
FlagEdge se{halfedge_, meshRelation_.triRef};
|
||||
for_each_n(policy, countAt(0_uz), nbEdges,
|
||||
[&](size_t i) { bFlags[i] = se(i); });
|
||||
for (size_t i = 0; i < nbEdges; ++i) {
|
||||
if (bFlags[i]) {
|
||||
CollapseEdge(i, scratchBuffer);
|
||||
scratchBuffer.resize(0);
|
||||
numFlagged++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (ManifoldParams().verbose && numFlagged > 0) {
|
||||
std::cout << "found " << numFlagged << " colinear edges to collapse"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
ZoneScopedN("RecursiveEdgeSwap");
|
||||
numFlagged = 0;
|
||||
SwappableEdge se{halfedge_, vertPos_, faceNormal_, tolerance_};
|
||||
for_each_n(policy, countAt(0_uz), nbEdges,
|
||||
[&](size_t i) { bFlags[i] = se(i); });
|
||||
std::vector<int> edgeSwapStack;
|
||||
std::vector<int> visited(halfedge_.size(), -1);
|
||||
int tag = 0;
|
||||
for (size_t i = 0; i < nbEdges; ++i) {
|
||||
if (bFlags[i]) {
|
||||
numFlagged++;
|
||||
tag++;
|
||||
RecursiveEdgeSwap(i, tag, visited, edgeSwapStack, scratchBuffer);
|
||||
while (!edgeSwapStack.empty()) {
|
||||
int last = edgeSwapStack.back();
|
||||
edgeSwapStack.pop_back();
|
||||
RecursiveEdgeSwap(last, tag, visited, edgeSwapStack, scratchBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (ManifoldParams().verbose && numFlagged > 0) {
|
||||
std::cout << "found " << numFlagged << " edges to swap" << std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Deduplicate the given 4-manifold edge by duplicating endVert, thus making the
|
||||
// edges distinct. Also duplicates startVert if it becomes pinched.
|
||||
void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
// Orbit endVert
|
||||
const int startVert = halfedge_[edge].startVert;
|
||||
const int endVert = halfedge_[edge].endVert;
|
||||
int current = halfedge_[NextHalfedge(edge)].pairedHalfedge;
|
||||
while (current != edge) {
|
||||
const int vert = halfedge_[current].startVert;
|
||||
if (vert == startVert) {
|
||||
// Single topological unit needs 2 faces added to be split
|
||||
const int newVert = vertPos_.size();
|
||||
vertPos_.push_back(vertPos_[endVert]);
|
||||
if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);
|
||||
current = halfedge_[NextHalfedge(current)].pairedHalfedge;
|
||||
const int opposite = halfedge_[NextHalfedge(edge)].pairedHalfedge;
|
||||
|
||||
UpdateVert(newVert, current, opposite);
|
||||
|
||||
int newHalfedge = halfedge_.size();
|
||||
int newFace = newHalfedge / 3;
|
||||
int oldFace = current / 3;
|
||||
int outsideVert = halfedge_[current].startVert;
|
||||
halfedge_.push_back({endVert, newVert, -1});
|
||||
halfedge_.push_back({newVert, outsideVert, -1});
|
||||
halfedge_.push_back({outsideVert, endVert, -1});
|
||||
PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge);
|
||||
PairUp(newHalfedge + 1, current);
|
||||
if (meshRelation_.triRef.size() > 0)
|
||||
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
|
||||
if (meshRelation_.triProperties.size() > 0)
|
||||
meshRelation_.triProperties.push_back(
|
||||
meshRelation_.triProperties[oldFace]);
|
||||
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
|
||||
|
||||
newHalfedge += 3;
|
||||
++newFace;
|
||||
oldFace = opposite / 3;
|
||||
outsideVert = halfedge_[opposite].startVert;
|
||||
halfedge_.push_back({newVert, endVert, -1});
|
||||
halfedge_.push_back({endVert, outsideVert, -1});
|
||||
halfedge_.push_back({outsideVert, newVert, -1});
|
||||
PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);
|
||||
PairUp(newHalfedge + 1, opposite);
|
||||
PairUp(newHalfedge, newHalfedge - 3);
|
||||
if (meshRelation_.triRef.size() > 0)
|
||||
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
|
||||
if (meshRelation_.triProperties.size() > 0)
|
||||
meshRelation_.triProperties.push_back(
|
||||
meshRelation_.triProperties[oldFace]);
|
||||
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
current = halfedge_[NextHalfedge(current)].pairedHalfedge;
|
||||
}
|
||||
|
||||
if (current == edge) {
|
||||
// Separate topological unit needs no new faces to be split
|
||||
const int newVert = vertPos_.size();
|
||||
vertPos_.push_back(vertPos_[endVert]);
|
||||
if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);
|
||||
|
||||
ForVert(NextHalfedge(current), [this, newVert](int e) {
|
||||
halfedge_[e].startVert = newVert;
|
||||
halfedge_[halfedge_[e].pairedHalfedge].endVert = newVert;
|
||||
});
|
||||
}
|
||||
|
||||
// Orbit startVert
|
||||
const int pair = halfedge_[edge].pairedHalfedge;
|
||||
current = halfedge_[NextHalfedge(pair)].pairedHalfedge;
|
||||
while (current != pair) {
|
||||
const int vert = halfedge_[current].startVert;
|
||||
if (vert == endVert) {
|
||||
break; // Connected: not a pinched vert
|
||||
}
|
||||
current = halfedge_[NextHalfedge(current)].pairedHalfedge;
|
||||
}
|
||||
|
||||
if (current == pair) {
|
||||
// Split the pinched vert the previous split created.
|
||||
const int newVert = vertPos_.size();
|
||||
vertPos_.push_back(vertPos_[endVert]);
|
||||
if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);
|
||||
|
||||
ForVert(NextHalfedge(current), [this, newVert](int e) {
|
||||
halfedge_[e].startVert = newVert;
|
||||
halfedge_[halfedge_[e].pairedHalfedge].endVert = newVert;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Manifold::Impl::PairUp(int edge0, int edge1) {
|
||||
halfedge_[edge0].pairedHalfedge = edge1;
|
||||
halfedge_[edge1].pairedHalfedge = edge0;
|
||||
}
|
||||
|
||||
// Traverses CW around startEdge.endVert from startEdge to endEdge
|
||||
// (edgeEdge.endVert must == startEdge.endVert), updating each edge to point
|
||||
// to vert instead.
|
||||
void Manifold::Impl::UpdateVert(int vert, int startEdge, int endEdge) {
|
||||
int current = startEdge;
|
||||
while (current != endEdge) {
|
||||
halfedge_[current].endVert = vert;
|
||||
current = NextHalfedge(current);
|
||||
halfedge_[current].startVert = vert;
|
||||
current = halfedge_[current].pairedHalfedge;
|
||||
DEBUG_ASSERT(current != startEdge, logicErr, "infinite loop in decimator!");
|
||||
}
|
||||
}
|
||||
|
||||
// In the event that the edge collapse would create a non-manifold edge,
|
||||
// instead we duplicate the two verts and attach the manifolds the other way
|
||||
// across this edge.
|
||||
void Manifold::Impl::FormLoop(int current, int end) {
|
||||
int startVert = vertPos_.size();
|
||||
vertPos_.push_back(vertPos_[halfedge_[current].startVert]);
|
||||
int endVert = vertPos_.size();
|
||||
vertPos_.push_back(vertPos_[halfedge_[current].endVert]);
|
||||
|
||||
int oldMatch = halfedge_[current].pairedHalfedge;
|
||||
int newMatch = halfedge_[end].pairedHalfedge;
|
||||
|
||||
UpdateVert(startVert, oldMatch, newMatch);
|
||||
UpdateVert(endVert, end, current);
|
||||
|
||||
halfedge_[current].pairedHalfedge = newMatch;
|
||||
halfedge_[newMatch].pairedHalfedge = current;
|
||||
halfedge_[end].pairedHalfedge = oldMatch;
|
||||
halfedge_[oldMatch].pairedHalfedge = end;
|
||||
|
||||
RemoveIfFolded(end);
|
||||
}
|
||||
|
||||
void Manifold::Impl::CollapseTri(const ivec3& triEdge) {
|
||||
if (halfedge_[triEdge[1]].pairedHalfedge == -1) return;
|
||||
int pair1 = halfedge_[triEdge[1]].pairedHalfedge;
|
||||
int pair2 = halfedge_[triEdge[2]].pairedHalfedge;
|
||||
halfedge_[pair1].pairedHalfedge = pair2;
|
||||
halfedge_[pair2].pairedHalfedge = pair1;
|
||||
for (int i : {0, 1, 2}) {
|
||||
halfedge_[triEdge[i]] = {-1, -1, -1};
|
||||
}
|
||||
}
|
||||
|
||||
void Manifold::Impl::RemoveIfFolded(int edge) {
|
||||
const ivec3 tri0edge = TriOf(edge);
|
||||
const ivec3 tri1edge = TriOf(halfedge_[edge].pairedHalfedge);
|
||||
if (halfedge_[tri0edge[1]].pairedHalfedge == -1) return;
|
||||
if (halfedge_[tri0edge[1]].endVert == halfedge_[tri1edge[1]].endVert) {
|
||||
if (halfedge_[tri0edge[1]].pairedHalfedge == tri1edge[2]) {
|
||||
if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {
|
||||
for (int i : {0, 1, 2})
|
||||
vertPos_[halfedge_[tri0edge[i]].startVert] = vec3(NAN);
|
||||
} else {
|
||||
vertPos_[halfedge_[tri0edge[1]].startVert] = vec3(NAN);
|
||||
}
|
||||
} else {
|
||||
if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {
|
||||
vertPos_[halfedge_[tri1edge[1]].startVert] = vec3(NAN);
|
||||
}
|
||||
}
|
||||
PairUp(halfedge_[tri0edge[1]].pairedHalfedge,
|
||||
halfedge_[tri1edge[2]].pairedHalfedge);
|
||||
PairUp(halfedge_[tri0edge[2]].pairedHalfedge,
|
||||
halfedge_[tri1edge[1]].pairedHalfedge);
|
||||
for (int i : {0, 1, 2}) {
|
||||
halfedge_[tri0edge[i]] = {-1, -1, -1};
|
||||
halfedge_[tri1edge[i]] = {-1, -1, -1};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collapses the given edge by removing startVert. May split the mesh
|
||||
// topologically if the collapse would have resulted in a 4-manifold edge. Do
|
||||
// not collapse an edge if startVert is pinched - the vert will be marked NaN,
|
||||
// but other edges may still be pointing to it.
|
||||
void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
|
||||
Vec<TriRef>& triRef = meshRelation_.triRef;
|
||||
Vec<ivec3>& triProp = meshRelation_.triProperties;
|
||||
|
||||
const Halfedge toRemove = halfedge_[edge];
|
||||
if (toRemove.pairedHalfedge < 0) return;
|
||||
|
||||
const int endVert = toRemove.endVert;
|
||||
const ivec3 tri0edge = TriOf(edge);
|
||||
const ivec3 tri1edge = TriOf(toRemove.pairedHalfedge);
|
||||
|
||||
const vec3 pNew = vertPos_[endVert];
|
||||
const vec3 pOld = vertPos_[toRemove.startVert];
|
||||
const vec3 delta = pNew - pOld;
|
||||
const bool shortEdge = la::dot(delta, delta) < tolerance_ * tolerance_;
|
||||
|
||||
// Orbit endVert
|
||||
int current = halfedge_[tri0edge[1]].pairedHalfedge;
|
||||
while (current != tri1edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
edges.push_back(current);
|
||||
current = halfedge_[current].pairedHalfedge;
|
||||
}
|
||||
|
||||
// Orbit startVert
|
||||
int start = halfedge_[tri1edge[1]].pairedHalfedge;
|
||||
if (!shortEdge) {
|
||||
current = start;
|
||||
TriRef refCheck = triRef[toRemove.pairedHalfedge / 3];
|
||||
vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert];
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
vec3 pNext = vertPos_[halfedge_[current].endVert];
|
||||
const int tri = current / 3;
|
||||
const TriRef ref = triRef[tri];
|
||||
const mat2x3 projection = GetAxisAlignedProjection(faceNormal_[tri]);
|
||||
// Don't collapse if the edge is not redundant (this may have changed due
|
||||
// to the collapse of neighbors).
|
||||
if (!ref.SameFace(refCheck)) {
|
||||
refCheck = triRef[edge / 3];
|
||||
if (!ref.SameFace(refCheck)) {
|
||||
return;
|
||||
} else {
|
||||
// Don't collapse if the edges separating the faces are not colinear
|
||||
// (can happen when the two faces are coplanar).
|
||||
if (CCW(projection * pOld, projection * pLast, projection * pNew,
|
||||
epsilon_) != 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't collapse edge if it would cause a triangle to invert.
|
||||
if (CCW(projection * pNext, projection * pLast, projection * pNew,
|
||||
epsilon_) < 0)
|
||||
return;
|
||||
|
||||
pLast = pNext;
|
||||
current = halfedge_[current].pairedHalfedge;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove toRemove.startVert and replace with endVert.
|
||||
vertPos_[toRemove.startVert] = vec3(NAN);
|
||||
CollapseTri(tri1edge);
|
||||
|
||||
// Orbit startVert
|
||||
const int tri0 = edge / 3;
|
||||
const int tri1 = toRemove.pairedHalfedge / 3;
|
||||
const int triVert0 = (edge + 1) % 3;
|
||||
const int triVert1 = toRemove.pairedHalfedge % 3;
|
||||
current = start;
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
|
||||
if (triProp.size() > 0) {
|
||||
// Update the shifted triangles to the vertBary of endVert
|
||||
const int tri = current / 3;
|
||||
const int vIdx = current - 3 * tri;
|
||||
if (triRef[tri].SameFace(triRef[tri0])) {
|
||||
triProp[tri][vIdx] = triProp[tri0][triVert0];
|
||||
} else if (triRef[tri].SameFace(triRef[tri1])) {
|
||||
triProp[tri][vIdx] = triProp[tri1][triVert1];
|
||||
}
|
||||
}
|
||||
|
||||
const int vert = halfedge_[current].endVert;
|
||||
const int next = halfedge_[current].pairedHalfedge;
|
||||
for (size_t i = 0; i < edges.size(); ++i) {
|
||||
if (vert == halfedge_[edges[i]].endVert) {
|
||||
FormLoop(edges[i], current);
|
||||
start = next;
|
||||
edges.resize(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
|
||||
UpdateVert(endVert, start, tri0edge[2]);
|
||||
CollapseTri(tri0edge);
|
||||
RemoveIfFolded(start);
|
||||
}
|
||||
|
||||
void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag,
|
||||
std::vector<int>& visited,
|
||||
std::vector<int>& edgeSwapStack,
|
||||
std::vector<int>& edges) {
|
||||
Vec<TriRef>& triRef = meshRelation_.triRef;
|
||||
|
||||
if (edge < 0) return;
|
||||
const int pair = halfedge_[edge].pairedHalfedge;
|
||||
if (pair < 0) return;
|
||||
|
||||
// avoid infinite recursion
|
||||
if (visited[edge] == tag && visited[pair] == tag) return;
|
||||
|
||||
const ivec3 tri0edge = TriOf(edge);
|
||||
const ivec3 tri1edge = TriOf(pair);
|
||||
const ivec3 perm0 = TriOf(edge % 3);
|
||||
const ivec3 perm1 = TriOf(pair % 3);
|
||||
|
||||
mat2x3 projection = GetAxisAlignedProjection(faceNormal_[edge / 3]);
|
||||
vec2 v[4];
|
||||
for (int i : {0, 1, 2})
|
||||
v[i] = projection * vertPos_[halfedge_[tri0edge[i]].startVert];
|
||||
// Only operate on the long edge of a degenerate triangle.
|
||||
if (CCW(v[0], v[1], v[2], tolerance_) > 0 || !Is01Longest(v[0], v[1], v[2]))
|
||||
return;
|
||||
|
||||
// Switch to neighbor's projection.
|
||||
projection = GetAxisAlignedProjection(faceNormal_[pair / 3]);
|
||||
for (int i : {0, 1, 2})
|
||||
v[i] = projection * vertPos_[halfedge_[tri0edge[i]].startVert];
|
||||
v[3] = projection * vertPos_[halfedge_[tri1edge[2]].startVert];
|
||||
|
||||
auto SwapEdge = [&]() {
|
||||
// The 0-verts are swapped to the opposite 2-verts.
|
||||
const int v0 = halfedge_[tri0edge[2]].startVert;
|
||||
const int v1 = halfedge_[tri1edge[2]].startVert;
|
||||
halfedge_[tri0edge[0]].startVert = v1;
|
||||
halfedge_[tri0edge[2]].endVert = v1;
|
||||
halfedge_[tri1edge[0]].startVert = v0;
|
||||
halfedge_[tri1edge[2]].endVert = v0;
|
||||
PairUp(tri0edge[0], halfedge_[tri1edge[2]].pairedHalfedge);
|
||||
PairUp(tri1edge[0], halfedge_[tri0edge[2]].pairedHalfedge);
|
||||
PairUp(tri0edge[2], tri1edge[2]);
|
||||
// Both triangles are now subsets of the neighboring triangle.
|
||||
const int tri0 = tri0edge[0] / 3;
|
||||
const int tri1 = tri1edge[0] / 3;
|
||||
faceNormal_[tri0] = faceNormal_[tri1];
|
||||
triRef[tri0] = triRef[tri1];
|
||||
const double l01 = la::length(v[1] - v[0]);
|
||||
const double l02 = la::length(v[2] - v[0]);
|
||||
const double a = std::max(0.0, std::min(1.0, l02 / l01));
|
||||
// Update properties if applicable
|
||||
if (meshRelation_.properties.size() > 0) {
|
||||
Vec<ivec3>& triProp = meshRelation_.triProperties;
|
||||
Vec<double>& prop = meshRelation_.properties;
|
||||
triProp[tri0] = triProp[tri1];
|
||||
triProp[tri0][perm0[1]] = triProp[tri1][perm1[0]];
|
||||
triProp[tri0][perm0[0]] = triProp[tri1][perm1[2]];
|
||||
const int numProp = NumProp();
|
||||
const int newProp = prop.size() / numProp;
|
||||
const int propIdx0 = triProp[tri1][perm1[0]];
|
||||
const int propIdx1 = triProp[tri1][perm1[1]];
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
prop.push_back(a * prop[numProp * propIdx0 + p] +
|
||||
(1 - a) * prop[numProp * propIdx1 + p]);
|
||||
}
|
||||
triProp[tri1][perm1[0]] = newProp;
|
||||
triProp[tri0][perm0[2]] = newProp;
|
||||
}
|
||||
|
||||
// if the new edge already exists, duplicate the verts and split the mesh.
|
||||
int current = halfedge_[tri1edge[0]].pairedHalfedge;
|
||||
const int endVert = halfedge_[tri1edge[1]].endVert;
|
||||
while (current != tri0edge[1]) {
|
||||
current = NextHalfedge(current);
|
||||
if (halfedge_[current].endVert == endVert) {
|
||||
FormLoop(tri0edge[2], current);
|
||||
RemoveIfFolded(tri0edge[2]);
|
||||
return;
|
||||
}
|
||||
current = halfedge_[current].pairedHalfedge;
|
||||
}
|
||||
};
|
||||
|
||||
// Only operate if the other triangles are not degenerate.
|
||||
if (CCW(v[1], v[0], v[3], tolerance_) <= 0) {
|
||||
if (!Is01Longest(v[1], v[0], v[3])) return;
|
||||
// Two facing, long-edge degenerates can swap.
|
||||
SwapEdge();
|
||||
const vec2 e23 = v[3] - v[2];
|
||||
if (la::dot(e23, e23) < tolerance_ * tolerance_) {
|
||||
tag++;
|
||||
CollapseEdge(tri0edge[2], edges);
|
||||
edges.resize(0);
|
||||
} else {
|
||||
visited[edge] = tag;
|
||||
visited[pair] = tag;
|
||||
edgeSwapStack.insert(edgeSwapStack.end(), {tri1edge[1], tri1edge[0],
|
||||
tri0edge[1], tri0edge[0]});
|
||||
}
|
||||
return;
|
||||
} else if (CCW(v[0], v[3], v[2], tolerance_) <= 0 ||
|
||||
CCW(v[1], v[2], v[3], tolerance_) <= 0) {
|
||||
return;
|
||||
}
|
||||
// Normal path
|
||||
SwapEdge();
|
||||
visited[edge] = tag;
|
||||
visited[pair] = tag;
|
||||
edgeSwapStack.insert(edgeSwapStack.end(),
|
||||
{halfedge_[tri1edge[0]].pairedHalfedge,
|
||||
halfedge_[tri0edge[1]].pairedHalfedge});
|
||||
}
|
||||
|
||||
void Manifold::Impl::SplitPinchedVerts() {
|
||||
ZoneScoped;
|
||||
std::vector<bool> vertProcessed(NumVert(), false);
|
||||
std::vector<bool> halfedgeProcessed(halfedge_.size(), false);
|
||||
for (size_t i = 0; i < halfedge_.size(); ++i) {
|
||||
if (halfedgeProcessed[i]) continue;
|
||||
int vert = halfedge_[i].startVert;
|
||||
if (vertProcessed[vert]) {
|
||||
vertPos_.push_back(vertPos_[vert]);
|
||||
vert = NumVert() - 1;
|
||||
} else {
|
||||
vertProcessed[vert] = true;
|
||||
}
|
||||
ForVert(i, [this, &halfedgeProcessed, vert](int current) {
|
||||
halfedgeProcessed[current] = true;
|
||||
halfedge_[current].startVert = vert;
|
||||
halfedge_[halfedge_[current].pairedHalfedge].endVert = vert;
|
||||
});
|
||||
}
|
||||
}
|
||||
} // namespace manifold
|
||||
319
thirdparty/manifold/src/face_op.cpp
vendored
Normal file
319
thirdparty/manifold/src/face_op.cpp
vendored
Normal file
@@ -0,0 +1,319 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/concurrent_map.h>)
|
||||
#include <tbb/tbb.h>
|
||||
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
|
||||
#include <tbb/concurrent_map.h>
|
||||
#endif
|
||||
#include <unordered_set>
|
||||
|
||||
#include "./impl.h"
|
||||
#include "./parallel.h"
|
||||
#include "manifold/polygon.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
using GeneralTriangulation = std::function<std::vector<ivec3>(int)>;
|
||||
using AddTriangle = std::function<void(int, ivec3, vec3, TriRef)>;
|
||||
|
||||
/**
|
||||
* Triangulates the faces. In this case, the halfedge_ vector is not yet a set
|
||||
* of triangles as required by this data structure, but is instead a set of
|
||||
* general faces with the input faceEdge vector having length of the number of
|
||||
* faces + 1. The values are indicies into the halfedge_ vector for the first
|
||||
* edge of each face, with the final value being the length of the halfedge_
|
||||
* vector itself. Upon return, halfedge_ has been lengthened and properly
|
||||
* represents the mesh as a set of triangles as usual. In this process the
|
||||
* faceNormal_ values are retained, repeated as necessary.
|
||||
*/
|
||||
void Manifold::Impl::Face2Tri(const Vec<int>& faceEdge,
|
||||
const Vec<TriRef>& halfedgeRef) {
|
||||
ZoneScoped;
|
||||
Vec<ivec3> triVerts;
|
||||
Vec<vec3> triNormal;
|
||||
Vec<TriRef>& triRef = meshRelation_.triRef;
|
||||
triRef.resize(0);
|
||||
auto processFace = [&](GeneralTriangulation general, AddTriangle addTri,
|
||||
int face) {
|
||||
const int firstEdge = faceEdge[face];
|
||||
const int lastEdge = faceEdge[face + 1];
|
||||
const int numEdge = lastEdge - firstEdge;
|
||||
DEBUG_ASSERT(numEdge >= 3, topologyErr, "face has less than three edges.");
|
||||
const vec3 normal = faceNormal_[face];
|
||||
|
||||
if (numEdge == 3) { // Single triangle
|
||||
int mapping[3] = {halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
halfedge_[firstEdge + 2].startVert};
|
||||
ivec3 tri(halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
halfedge_[firstEdge + 2].startVert);
|
||||
ivec3 ends(halfedge_[firstEdge].endVert, halfedge_[firstEdge + 1].endVert,
|
||||
halfedge_[firstEdge + 2].endVert);
|
||||
if (ends[0] == tri[2]) {
|
||||
std::swap(tri[1], tri[2]);
|
||||
std::swap(ends[1], ends[2]);
|
||||
}
|
||||
DEBUG_ASSERT(ends[0] == tri[1] && ends[1] == tri[2] && ends[2] == tri[0],
|
||||
topologyErr, "These 3 edges do not form a triangle!");
|
||||
|
||||
addTri(face, tri, normal, halfedgeRef[firstEdge]);
|
||||
} else if (numEdge == 4) { // Pair of triangles
|
||||
int mapping[4] = {halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
halfedge_[firstEdge + 2].startVert,
|
||||
halfedge_[firstEdge + 3].startVert};
|
||||
const mat2x3 projection = GetAxisAlignedProjection(normal);
|
||||
auto triCCW = [&projection, this](const ivec3 tri) {
|
||||
return CCW(projection * this->vertPos_[tri[0]],
|
||||
projection * this->vertPos_[tri[1]],
|
||||
projection * this->vertPos_[tri[2]], epsilon_) >= 0;
|
||||
};
|
||||
|
||||
ivec3 tri0(halfedge_[firstEdge].startVert, halfedge_[firstEdge].endVert,
|
||||
-1);
|
||||
ivec3 tri1(-1, -1, tri0[0]);
|
||||
for (const int i : {1, 2, 3}) {
|
||||
if (halfedge_[firstEdge + i].startVert == tri0[1]) {
|
||||
tri0[2] = halfedge_[firstEdge + i].endVert;
|
||||
tri1[0] = tri0[2];
|
||||
}
|
||||
if (halfedge_[firstEdge + i].endVert == tri0[0]) {
|
||||
tri1[1] = halfedge_[firstEdge + i].startVert;
|
||||
}
|
||||
}
|
||||
DEBUG_ASSERT(la::all(la::gequal(tri0, ivec3(0))) &&
|
||||
la::all(la::gequal(tri1, ivec3(0))),
|
||||
topologyErr, "non-manifold quad!");
|
||||
bool firstValid = triCCW(tri0) && triCCW(tri1);
|
||||
tri0[2] = tri1[1];
|
||||
tri1[2] = tri0[1];
|
||||
bool secondValid = triCCW(tri0) && triCCW(tri1);
|
||||
|
||||
if (!secondValid) {
|
||||
tri0[2] = tri1[0];
|
||||
tri1[2] = tri0[0];
|
||||
} else if (firstValid) {
|
||||
vec3 firstCross = vertPos_[tri0[0]] - vertPos_[tri1[0]];
|
||||
vec3 secondCross = vertPos_[tri0[1]] - vertPos_[tri1[1]];
|
||||
if (la::dot(firstCross, firstCross) <
|
||||
la::dot(secondCross, secondCross)) {
|
||||
tri0[2] = tri1[0];
|
||||
tri1[2] = tri0[0];
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& tri : {tri0, tri1}) {
|
||||
addTri(face, tri, normal, halfedgeRef[firstEdge]);
|
||||
}
|
||||
} else { // General triangulation
|
||||
for (const auto& tri : general(face)) {
|
||||
addTri(face, tri, normal, halfedgeRef[firstEdge]);
|
||||
}
|
||||
}
|
||||
};
|
||||
auto generalTriangulation = [&](int face) {
|
||||
const vec3 normal = faceNormal_[face];
|
||||
const mat2x3 projection = GetAxisAlignedProjection(normal);
|
||||
const PolygonsIdx polys =
|
||||
Face2Polygons(halfedge_.cbegin() + faceEdge[face],
|
||||
halfedge_.cbegin() + faceEdge[face + 1], projection);
|
||||
return TriangulateIdx(polys, epsilon_);
|
||||
};
|
||||
#if (MANIFOLD_PAR == 1) && __has_include(<tbb/tbb.h>)
|
||||
tbb::task_group group;
|
||||
// map from face to triangle
|
||||
tbb::concurrent_unordered_map<int, std::vector<ivec3>> results;
|
||||
Vec<size_t> triCount(faceEdge.size());
|
||||
triCount.back() = 0;
|
||||
// precompute number of triangles per face, and launch async tasks to
|
||||
// triangulate complex faces
|
||||
for_each(autoPolicy(faceEdge.size(), 1e5), countAt(0_uz),
|
||||
countAt(faceEdge.size() - 1), [&](size_t face) {
|
||||
triCount[face] = faceEdge[face + 1] - faceEdge[face] - 2;
|
||||
DEBUG_ASSERT(triCount[face] >= 1, topologyErr,
|
||||
"face has less than three edges.");
|
||||
if (triCount[face] > 2)
|
||||
group.run([&, face] {
|
||||
std::vector<ivec3> newTris = generalTriangulation(face);
|
||||
triCount[face] = newTris.size();
|
||||
results[face] = std::move(newTris);
|
||||
});
|
||||
});
|
||||
group.wait();
|
||||
// prefix sum computation (assign unique index to each face) and preallocation
|
||||
exclusive_scan(triCount.begin(), triCount.end(), triCount.begin(), 0_uz);
|
||||
triVerts.resize(triCount.back());
|
||||
triNormal.resize(triCount.back());
|
||||
triRef.resize(triCount.back());
|
||||
|
||||
auto processFace2 = std::bind(
|
||||
processFace, [&](size_t face) { return std::move(results[face]); },
|
||||
[&](size_t face, ivec3 tri, vec3 normal, TriRef r) {
|
||||
triVerts[triCount[face]] = tri;
|
||||
triNormal[triCount[face]] = normal;
|
||||
triRef[triCount[face]] = r;
|
||||
triCount[face]++;
|
||||
},
|
||||
std::placeholders::_1);
|
||||
// set triangles in parallel
|
||||
for_each(autoPolicy(faceEdge.size(), 1e4), countAt(0_uz),
|
||||
countAt(faceEdge.size() - 1), processFace2);
|
||||
#else
|
||||
triVerts.reserve(faceEdge.size());
|
||||
triNormal.reserve(faceEdge.size());
|
||||
triRef.reserve(faceEdge.size());
|
||||
auto processFace2 = std::bind(
|
||||
processFace, generalTriangulation,
|
||||
[&](size_t _face, ivec3 tri, vec3 normal, TriRef r) {
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triRef.push_back(r);
|
||||
},
|
||||
std::placeholders::_1);
|
||||
for (size_t face = 0; face < faceEdge.size() - 1; ++face) {
|
||||
processFace2(face);
|
||||
}
|
||||
#endif
|
||||
|
||||
faceNormal_ = std::move(triNormal);
|
||||
CreateHalfedges(triVerts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of 2D polygons formed by the input projection of the vertices
|
||||
* of the list of Halfedges, which must be an even-manifold, meaning each vert
|
||||
* must be referenced the same number of times as a startVert and endVert.
|
||||
*/
|
||||
PolygonsIdx Manifold::Impl::Face2Polygons(VecView<Halfedge>::IterC start,
|
||||
VecView<Halfedge>::IterC end,
|
||||
mat2x3 projection) const {
|
||||
std::multimap<int, int> vert_edge;
|
||||
for (auto edge = start; edge != end; ++edge) {
|
||||
vert_edge.emplace(
|
||||
std::make_pair(edge->startVert, static_cast<int>(edge - start)));
|
||||
}
|
||||
|
||||
PolygonsIdx polys;
|
||||
int startEdge = 0;
|
||||
int thisEdge = startEdge;
|
||||
while (1) {
|
||||
if (thisEdge == startEdge) {
|
||||
if (vert_edge.empty()) break;
|
||||
startEdge = vert_edge.begin()->second;
|
||||
thisEdge = startEdge;
|
||||
polys.push_back({});
|
||||
}
|
||||
int vert = (start + thisEdge)->startVert;
|
||||
polys.back().push_back({projection * vertPos_[vert], vert});
|
||||
const auto result = vert_edge.find((start + thisEdge)->endVert);
|
||||
DEBUG_ASSERT(result != vert_edge.end(), topologyErr, "non-manifold edge");
|
||||
thisEdge = result->second;
|
||||
vert_edge.erase(result);
|
||||
}
|
||||
return polys;
|
||||
}
|
||||
|
||||
Polygons Manifold::Impl::Slice(double height) const {
|
||||
Box plane = bBox_;
|
||||
plane.min.z = plane.max.z = height;
|
||||
Vec<Box> query;
|
||||
query.push_back(plane);
|
||||
const SparseIndices collisions =
|
||||
collider_.Collisions<false, false>(query.cview());
|
||||
|
||||
std::unordered_set<int> tris;
|
||||
for (size_t i = 0; i < collisions.size(); ++i) {
|
||||
const int tri = collisions.Get(i, 1);
|
||||
double min = std::numeric_limits<double>::infinity();
|
||||
double max = -std::numeric_limits<double>::infinity();
|
||||
for (const int j : {0, 1, 2}) {
|
||||
const double z = vertPos_[halfedge_[3 * tri + j].startVert].z;
|
||||
min = std::min(min, z);
|
||||
max = std::max(max, z);
|
||||
}
|
||||
|
||||
if (min <= height && max > height) {
|
||||
tris.insert(tri);
|
||||
}
|
||||
}
|
||||
|
||||
Polygons polys;
|
||||
while (!tris.empty()) {
|
||||
const int startTri = *tris.begin();
|
||||
SimplePolygon poly;
|
||||
|
||||
int k = 0;
|
||||
for (const int j : {0, 1, 2}) {
|
||||
if (vertPos_[halfedge_[3 * startTri + j].startVert].z > height &&
|
||||
vertPos_[halfedge_[3 * startTri + Next3(j)].startVert].z <= height) {
|
||||
k = Next3(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int tri = startTri;
|
||||
do {
|
||||
tris.erase(tris.find(tri));
|
||||
if (vertPos_[halfedge_[3 * tri + k].endVert].z <= height) {
|
||||
k = Next3(k);
|
||||
}
|
||||
|
||||
Halfedge up = halfedge_[3 * tri + k];
|
||||
const vec3 below = vertPos_[up.startVert];
|
||||
const vec3 above = vertPos_[up.endVert];
|
||||
const double a = (height - below.z) / (above.z - below.z);
|
||||
poly.push_back(vec2(la::lerp(below, above, a)));
|
||||
|
||||
const int pair = up.pairedHalfedge;
|
||||
tri = pair / 3;
|
||||
k = Next3(pair % 3);
|
||||
} while (tri != startTri);
|
||||
|
||||
polys.push_back(poly);
|
||||
}
|
||||
|
||||
return polys;
|
||||
}
|
||||
|
||||
Polygons Manifold::Impl::Project() const {
|
||||
const mat2x3 projection = GetAxisAlignedProjection({0, 0, 1});
|
||||
Vec<Halfedge> cusps(NumEdge());
|
||||
cusps.resize(
|
||||
copy_if(
|
||||
halfedge_.cbegin(), halfedge_.cend(), cusps.begin(),
|
||||
[&](Halfedge edge) {
|
||||
return faceNormal_[halfedge_[edge.pairedHalfedge].pairedHalfedge /
|
||||
3]
|
||||
.z >= 0 &&
|
||||
faceNormal_[edge.pairedHalfedge / 3].z < 0;
|
||||
}) -
|
||||
cusps.begin());
|
||||
|
||||
PolygonsIdx polysIndexed =
|
||||
Face2Polygons(cusps.cbegin(), cusps.cend(), projection);
|
||||
|
||||
Polygons polys;
|
||||
for (const auto& poly : polysIndexed) {
|
||||
SimplePolygon simple;
|
||||
for (const PolyVert& polyVert : poly) {
|
||||
simple.push_back(polyVert.pos);
|
||||
}
|
||||
polys.push_back(simple);
|
||||
}
|
||||
|
||||
return polys;
|
||||
}
|
||||
} // namespace manifold
|
||||
168
thirdparty/manifold/src/hashtable.h
vendored
Normal file
168
thirdparty/manifold/src/hashtable.h
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "./utils.h"
|
||||
#include "./vec.h"
|
||||
|
||||
namespace {
|
||||
typedef unsigned long long int Uint64;
|
||||
typedef Uint64 (*hash_fun_t)(Uint64);
|
||||
inline constexpr Uint64 kOpen = std::numeric_limits<Uint64>::max();
|
||||
|
||||
template <typename T>
|
||||
T AtomicCAS(T& target, T compare, T val) {
|
||||
std::atomic<T>& tar = reinterpret_cast<std::atomic<T>&>(target);
|
||||
tar.compare_exchange_strong(compare, val, std::memory_order_acq_rel);
|
||||
return compare;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AtomicStore(T& target, T val) {
|
||||
std::atomic<T>& tar = reinterpret_cast<std::atomic<T>&>(target);
|
||||
// release is good enough, although not really something general
|
||||
tar.store(val, std::memory_order_release);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T AtomicLoad(const T& target) {
|
||||
const std::atomic<T>& tar = reinterpret_cast<const std::atomic<T>&>(target);
|
||||
// acquire is good enough, although not general
|
||||
return tar.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
|
||||
inline Uint64 hash64bit(Uint64 x) {
|
||||
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull;
|
||||
x = (x ^ (x >> 27)) * 0x94d049bb133111ebull;
|
||||
x = x ^ (x >> 31);
|
||||
return x;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
template <typename V, hash_fun_t H = hash64bit>
|
||||
class HashTableD {
|
||||
public:
|
||||
HashTableD(Vec<Uint64>& keys, Vec<V>& values, std::atomic<size_t>& used,
|
||||
uint32_t step = 1)
|
||||
: step_{step}, keys_{keys}, values_{values}, used_{used} {}
|
||||
|
||||
int Size() const { return keys_.size(); }
|
||||
|
||||
bool Full() const {
|
||||
return used_.load(std::memory_order_relaxed) * 2 >
|
||||
static_cast<size_t>(Size());
|
||||
}
|
||||
|
||||
void Insert(Uint64 key, const V& val) {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
if (Full()) return;
|
||||
Uint64& k = keys_[idx];
|
||||
const Uint64 found = AtomicCAS(k, kOpen, key);
|
||||
if (found == kOpen) {
|
||||
used_.fetch_add(1, std::memory_order_relaxed);
|
||||
values_[idx] = val;
|
||||
return;
|
||||
}
|
||||
if (found == key) return;
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
V& operator[](Uint64 key) {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
const Uint64 k = AtomicLoad(keys_[idx]);
|
||||
if (k == key || k == kOpen) {
|
||||
return values_[idx];
|
||||
}
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
const V& operator[](Uint64 key) const {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
const Uint64 k = AtomicLoad(keys_[idx]);
|
||||
if (k == key || k == kOpen) {
|
||||
return values_[idx];
|
||||
}
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
Uint64 KeyAt(int idx) const { return AtomicLoad(keys_[idx]); }
|
||||
V& At(int idx) { return values_[idx]; }
|
||||
const V& At(int idx) const { return values_[idx]; }
|
||||
|
||||
private:
|
||||
uint32_t step_;
|
||||
VecView<Uint64> keys_;
|
||||
VecView<V> values_;
|
||||
std::atomic<size_t>& used_;
|
||||
};
|
||||
|
||||
template <typename V, hash_fun_t H = hash64bit>
|
||||
class HashTable {
|
||||
public:
|
||||
HashTable(size_t size, uint32_t step = 1)
|
||||
: keys_{size == 0 ? 0 : 1_uz << (int)ceil(log2(size)), kOpen},
|
||||
values_{size == 0 ? 0 : 1_uz << (int)ceil(log2(size)), {}},
|
||||
step_(step) {}
|
||||
|
||||
HashTable(const HashTable& other)
|
||||
: keys_(other.keys_), values_(other.values_), step_(other.step_) {
|
||||
used_.store(other.used_.load());
|
||||
}
|
||||
|
||||
HashTable& operator=(const HashTable& other) {
|
||||
if (this == &other) return *this;
|
||||
keys_ = other.keys_;
|
||||
values_ = other.values_;
|
||||
used_.store(other.used_.load());
|
||||
step_ = other.step_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HashTableD<V, H> D() { return {keys_, values_, used_, step_}; }
|
||||
|
||||
int Entries() const { return used_.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t Size() const { return keys_.size(); }
|
||||
|
||||
bool Full() const {
|
||||
return used_.load(std::memory_order_relaxed) * 2 > Size();
|
||||
}
|
||||
|
||||
double FilledFraction() const {
|
||||
return static_cast<double>(used_.load(std::memory_order_relaxed)) / Size();
|
||||
}
|
||||
|
||||
Vec<V>& GetValueStore() { return values_; }
|
||||
|
||||
static Uint64 Open() { return kOpen; }
|
||||
|
||||
private:
|
||||
Vec<Uint64> keys_;
|
||||
Vec<V> values_;
|
||||
std::atomic<size_t> used_ = 0;
|
||||
uint32_t step_;
|
||||
};
|
||||
} // namespace manifold
|
||||
686
thirdparty/manifold/src/impl.cpp
vendored
Normal file
686
thirdparty/manifold/src/impl.cpp
vendored
Normal file
@@ -0,0 +1,686 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "./impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
|
||||
#include "./hashtable.h"
|
||||
#include "./mesh_fixes.h"
|
||||
#include "./parallel.h"
|
||||
#include "./svd.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
constexpr uint64_t kRemove = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
void AtomicAddVec3(vec3& target, const vec3& add) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
std::atomic<double>& tar =
|
||||
reinterpret_cast<std::atomic<double>&>(target[i]);
|
||||
double old_val = tar.load(std::memory_order_relaxed);
|
||||
while (!tar.compare_exchange_weak(old_val, old_val + add[i],
|
||||
std::memory_order_relaxed)) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Transform4x3 {
|
||||
const mat3x4 transform;
|
||||
|
||||
vec3 operator()(vec3 position) { return transform * vec4(position, 1.0); }
|
||||
};
|
||||
|
||||
template <bool calculateTriNormal>
|
||||
struct AssignNormals {
|
||||
VecView<vec3> faceNormal;
|
||||
VecView<vec3> vertNormal;
|
||||
VecView<const vec3> vertPos;
|
||||
VecView<const Halfedge> halfedges;
|
||||
|
||||
void operator()(const int face) {
|
||||
vec3& triNormal = faceNormal[face];
|
||||
|
||||
ivec3 triVerts;
|
||||
for (int i : {0, 1, 2}) triVerts[i] = halfedges[3 * face + i].startVert;
|
||||
|
||||
vec3 edge[3];
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int j = (i + 1) % 3;
|
||||
edge[i] = la::normalize(vertPos[triVerts[j]] - vertPos[triVerts[i]]);
|
||||
}
|
||||
|
||||
if (calculateTriNormal) {
|
||||
triNormal = la::normalize(la::cross(edge[0], edge[1]));
|
||||
if (std::isnan(triNormal.x)) triNormal = vec3(0, 0, 1);
|
||||
}
|
||||
|
||||
// corner angles
|
||||
vec3 phi;
|
||||
double dot = -la::dot(edge[2], edge[0]);
|
||||
phi[0] = dot >= 1 ? 0 : (dot <= -1 ? kPi : std::acos(dot));
|
||||
dot = -la::dot(edge[0], edge[1]);
|
||||
phi[1] = dot >= 1 ? 0 : (dot <= -1 ? kPi : std::acos(dot));
|
||||
phi[2] = kPi - phi[0] - phi[1];
|
||||
|
||||
// assign weighted sum
|
||||
for (int i : {0, 1, 2}) {
|
||||
AtomicAddVec3(vertNormal[triVerts[i]], phi[i] * triNormal);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshID {
|
||||
const HashTableD<uint32_t> meshIDold2new;
|
||||
|
||||
void operator()(TriRef& ref) { ref.meshID = meshIDold2new[ref.meshID]; }
|
||||
};
|
||||
|
||||
struct CoplanarEdge {
|
||||
VecView<std::pair<int, int>> face2face;
|
||||
VecView<double> triArea;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const vec3> vertPos;
|
||||
VecView<const TriRef> triRef;
|
||||
VecView<const ivec3> triProp;
|
||||
const int numProp;
|
||||
const double epsilon;
|
||||
const double tolerance;
|
||||
|
||||
void operator()(const int edgeIdx) {
|
||||
const Halfedge edge = halfedge[edgeIdx];
|
||||
const Halfedge pair = halfedge[edge.pairedHalfedge];
|
||||
const int edgeFace = edgeIdx / 3;
|
||||
const int pairFace = edge.pairedHalfedge / 3;
|
||||
|
||||
if (triRef[edgeFace].meshID != triRef[pairFace].meshID) return;
|
||||
|
||||
const vec3 base = vertPos[edge.startVert];
|
||||
const int baseNum = edgeIdx - 3 * edgeFace;
|
||||
const int jointNum = edge.pairedHalfedge - 3 * pairFace;
|
||||
|
||||
if (numProp > 0) {
|
||||
if (triProp[edgeFace][baseNum] != triProp[pairFace][Next3(jointNum)] ||
|
||||
triProp[edgeFace][Next3(baseNum)] != triProp[pairFace][jointNum])
|
||||
return;
|
||||
}
|
||||
|
||||
if (!edge.IsForward()) return;
|
||||
|
||||
const int edgeNum = baseNum == 0 ? 2 : baseNum - 1;
|
||||
const int pairNum = jointNum == 0 ? 2 : jointNum - 1;
|
||||
const vec3 jointVec = vertPos[pair.startVert] - base;
|
||||
const vec3 edgeVec =
|
||||
vertPos[halfedge[3 * edgeFace + edgeNum].startVert] - base;
|
||||
const vec3 pairVec =
|
||||
vertPos[halfedge[3 * pairFace + pairNum].startVert] - base;
|
||||
|
||||
const double length = std::max(la::length(jointVec), la::length(edgeVec));
|
||||
const double lengthPair =
|
||||
std::max(la::length(jointVec), la::length(pairVec));
|
||||
vec3 normal = la::cross(jointVec, edgeVec);
|
||||
const double area = la::length(normal);
|
||||
const double areaPair = la::length(la::cross(pairVec, jointVec));
|
||||
|
||||
// make sure we only write this once
|
||||
if (edgeIdx % 3 == 0) triArea[edgeFace] = area;
|
||||
// Don't link degenerate triangles
|
||||
if (area < length * epsilon || areaPair < lengthPair * epsilon) return;
|
||||
|
||||
const double volume = std::abs(la::dot(normal, pairVec));
|
||||
// Only operate on coplanar triangles
|
||||
if (volume > std::max(area, areaPair) * tolerance) return;
|
||||
|
||||
face2face[edgeIdx] = std::make_pair(edgeFace, pairFace);
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckCoplanarity {
|
||||
VecView<int> comp2tri;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const vec3> vertPos;
|
||||
std::vector<int>* components;
|
||||
const double tolerance;
|
||||
|
||||
void operator()(int tri) {
|
||||
const int component = (*components)[tri];
|
||||
const int referenceTri =
|
||||
reinterpret_cast<std::atomic<int>*>(&comp2tri[component])
|
||||
->load(std::memory_order_relaxed);
|
||||
if (referenceTri < 0 || referenceTri == tri) return;
|
||||
|
||||
const vec3 origin = vertPos[halfedge[3 * referenceTri].startVert];
|
||||
const vec3 normal = la::normalize(
|
||||
la::cross(vertPos[halfedge[3 * referenceTri + 1].startVert] - origin,
|
||||
vertPos[halfedge[3 * referenceTri + 2].startVert] - origin));
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const vec3 vert = vertPos[halfedge[3 * tri + i].startVert];
|
||||
// If any component vertex is not coplanar with the component's reference
|
||||
// triangle, unmark the entire component so that none of its triangles are
|
||||
// marked coplanar.
|
||||
if (std::abs(la::dot(normal, vert - origin)) > tolerance) {
|
||||
reinterpret_cast<std::atomic<int>*>(&comp2tri[component])
|
||||
->store(-1, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int GetLabels(std::vector<int>& components,
|
||||
const Vec<std::pair<int, int>>& edges, int numNodes) {
|
||||
UnionFind<> uf(numNodes);
|
||||
for (auto edge : edges) {
|
||||
if (edge.first == -1 || edge.second == -1) continue;
|
||||
uf.unionXY(edge.first, edge.second);
|
||||
}
|
||||
|
||||
return uf.connectedComponents(components);
|
||||
}
|
||||
|
||||
void DedupePropVerts(manifold::Vec<ivec3>& triProp,
|
||||
const Vec<std::pair<int, int>>& vert2vert) {
|
||||
ZoneScoped;
|
||||
std::vector<int> vertLabels;
|
||||
const int numLabels = GetLabels(vertLabels, vert2vert, vert2vert.size());
|
||||
|
||||
std::vector<int> label2vert(numLabels);
|
||||
for (size_t v = 0; v < vert2vert.size(); ++v) label2vert[vertLabels[v]] = v;
|
||||
for (auto& prop : triProp)
|
||||
for (int i : {0, 1, 2}) prop[i] = label2vert[vertLabels[prop[i]]];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
std::atomic<uint32_t> Manifold::Impl::meshIDCounter_(1);
|
||||
|
||||
uint32_t Manifold::Impl::ReserveIDs(uint32_t n) {
|
||||
return Manifold::Impl::meshIDCounter_.fetch_add(n, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create either a unit tetrahedron, cube or octahedron. The cube is in the
|
||||
* first octant, while the others are symmetric about the origin.
|
||||
*/
|
||||
Manifold::Impl::Impl(Shape shape, const mat3x4 m) {
|
||||
std::vector<vec3> vertPos;
|
||||
std::vector<ivec3> triVerts;
|
||||
switch (shape) {
|
||||
case Shape::Tetrahedron:
|
||||
vertPos = {{-1.0, -1.0, 1.0},
|
||||
{-1.0, 1.0, -1.0},
|
||||
{1.0, -1.0, -1.0},
|
||||
{1.0, 1.0, 1.0}};
|
||||
triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
|
||||
break;
|
||||
case Shape::Cube:
|
||||
vertPos = {{0.0, 0.0, 0.0}, //
|
||||
{0.0, 0.0, 1.0}, //
|
||||
{0.0, 1.0, 0.0}, //
|
||||
{0.0, 1.0, 1.0}, //
|
||||
{1.0, 0.0, 0.0}, //
|
||||
{1.0, 0.0, 1.0}, //
|
||||
{1.0, 1.0, 0.0}, //
|
||||
{1.0, 1.0, 1.0}};
|
||||
triVerts = {{1, 0, 4}, {2, 4, 0}, //
|
||||
{1, 3, 0}, {3, 1, 5}, //
|
||||
{3, 2, 0}, {3, 7, 2}, //
|
||||
{5, 4, 6}, {5, 1, 4}, //
|
||||
{6, 4, 2}, {7, 6, 2}, //
|
||||
{7, 3, 5}, {7, 5, 6}};
|
||||
break;
|
||||
case Shape::Octahedron:
|
||||
vertPos = {{1.0, 0.0, 0.0}, //
|
||||
{-1.0, 0.0, 0.0}, //
|
||||
{0.0, 1.0, 0.0}, //
|
||||
{0.0, -1.0, 0.0}, //
|
||||
{0.0, 0.0, 1.0}, //
|
||||
{0.0, 0.0, -1.0}};
|
||||
triVerts = {{0, 2, 4}, {1, 5, 3}, //
|
||||
{2, 1, 4}, {3, 5, 0}, //
|
||||
{1, 3, 4}, {0, 5, 2}, //
|
||||
{3, 0, 4}, {2, 5, 1}};
|
||||
break;
|
||||
}
|
||||
vertPos_ = vertPos;
|
||||
for (auto& v : vertPos_) v = m * vec4(v, 1.0);
|
||||
CreateHalfedges(triVerts);
|
||||
Finish();
|
||||
InitializeOriginal();
|
||||
CreateFaces();
|
||||
}
|
||||
|
||||
void Manifold::Impl::RemoveUnreferencedVerts() {
|
||||
ZoneScoped;
|
||||
Vec<int> vertOld2New(NumVert(), 0);
|
||||
auto policy = autoPolicy(NumVert(), 1e5);
|
||||
for_each(policy, halfedge_.cbegin(), halfedge_.cend(),
|
||||
[&vertOld2New](Halfedge h) {
|
||||
reinterpret_cast<std::atomic<int>*>(&vertOld2New[h.startVert])
|
||||
->store(1, std::memory_order_relaxed);
|
||||
});
|
||||
|
||||
const Vec<vec3> oldVertPos = vertPos_;
|
||||
|
||||
Vec<size_t> tmpBuffer(oldVertPos.size());
|
||||
auto vertIdIter = TransformIterator(countAt(0_uz), [&vertOld2New](size_t i) {
|
||||
if (vertOld2New[i] > 0) return i;
|
||||
return std::numeric_limits<size_t>::max();
|
||||
});
|
||||
|
||||
auto next =
|
||||
copy_if(vertIdIter, vertIdIter + tmpBuffer.size(), tmpBuffer.begin(),
|
||||
[](size_t v) { return v != std::numeric_limits<size_t>::max(); });
|
||||
if (next == tmpBuffer.end()) return;
|
||||
|
||||
gather(tmpBuffer.begin(), next, oldVertPos.begin(), vertPos_.begin());
|
||||
|
||||
vertPos_.resize(std::distance(tmpBuffer.begin(), next));
|
||||
|
||||
exclusive_scan(vertOld2New.begin(), vertOld2New.end(), vertOld2New.begin());
|
||||
|
||||
for_each(policy, halfedge_.begin(), halfedge_.end(),
|
||||
[&vertOld2New](Halfedge& h) {
|
||||
h.startVert = vertOld2New[h.startVert];
|
||||
h.endVert = vertOld2New[h.endVert];
|
||||
});
|
||||
}
|
||||
|
||||
void Manifold::Impl::InitializeOriginal(bool keepFaceID) {
|
||||
const int meshID = ReserveIDs(1);
|
||||
meshRelation_.originalID = meshID;
|
||||
auto& triRef = meshRelation_.triRef;
|
||||
triRef.resize(NumTri());
|
||||
for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(),
|
||||
[meshID, keepFaceID, &triRef](const int tri) {
|
||||
triRef[tri] = {meshID, meshID, tri,
|
||||
keepFaceID ? triRef[tri].faceID : tri};
|
||||
});
|
||||
meshRelation_.meshIDtransform.clear();
|
||||
meshRelation_.meshIDtransform[meshID] = {meshID};
|
||||
}
|
||||
|
||||
void Manifold::Impl::CreateFaces() {
|
||||
ZoneScoped;
|
||||
Vec<std::pair<int, int>> face2face(halfedge_.size(), {-1, -1});
|
||||
Vec<std::pair<int, int>> vert2vert(halfedge_.size(), {-1, -1});
|
||||
Vec<double> triArea(NumTri());
|
||||
|
||||
const size_t numProp = NumProp();
|
||||
if (numProp > 0) {
|
||||
for_each_n(
|
||||
autoPolicy(halfedge_.size(), 1e4), countAt(0), halfedge_.size(),
|
||||
[&vert2vert, numProp, this](const int edgeIdx) {
|
||||
const Halfedge edge = halfedge_[edgeIdx];
|
||||
const Halfedge pair = halfedge_[edge.pairedHalfedge];
|
||||
const int edgeFace = edgeIdx / 3;
|
||||
const int pairFace = edge.pairedHalfedge / 3;
|
||||
|
||||
if (meshRelation_.triRef[edgeFace].meshID !=
|
||||
meshRelation_.triRef[pairFace].meshID)
|
||||
return;
|
||||
|
||||
const int baseNum = edgeIdx - 3 * edgeFace;
|
||||
const int jointNum = edge.pairedHalfedge - 3 * pairFace;
|
||||
|
||||
const int prop0 = meshRelation_.triProperties[edgeFace][baseNum];
|
||||
const int prop1 =
|
||||
meshRelation_
|
||||
.triProperties[pairFace][jointNum == 2 ? 0 : jointNum + 1];
|
||||
bool propEqual = true;
|
||||
for (size_t p = 0; p < numProp; ++p) {
|
||||
if (meshRelation_.properties[numProp * prop0 + p] !=
|
||||
meshRelation_.properties[numProp * prop1 + p]) {
|
||||
propEqual = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (propEqual) {
|
||||
vert2vert[edgeIdx] = std::make_pair(prop0, prop1);
|
||||
}
|
||||
});
|
||||
DedupePropVerts(meshRelation_.triProperties, vert2vert);
|
||||
}
|
||||
|
||||
for_each_n(autoPolicy(halfedge_.size(), 1e4), countAt(0), halfedge_.size(),
|
||||
CoplanarEdge({face2face, triArea, halfedge_, vertPos_,
|
||||
meshRelation_.triRef, meshRelation_.triProperties,
|
||||
meshRelation_.numProp, epsilon_, tolerance_}));
|
||||
|
||||
std::vector<int> components;
|
||||
const int numComponent = GetLabels(components, face2face, NumTri());
|
||||
|
||||
Vec<int> comp2tri(numComponent, -1);
|
||||
for (size_t tri = 0; tri < NumTri(); ++tri) {
|
||||
const int comp = components[tri];
|
||||
const int current = comp2tri[comp];
|
||||
if (current < 0 || triArea[tri] > triArea[current]) {
|
||||
comp2tri[comp] = tri;
|
||||
triArea[comp] = triArea[tri];
|
||||
}
|
||||
}
|
||||
|
||||
for_each_n(autoPolicy(halfedge_.size(), 1e4), countAt(0), NumTri(),
|
||||
CheckCoplanarity(
|
||||
{comp2tri, halfedge_, vertPos_, &components, tolerance_}));
|
||||
|
||||
Vec<TriRef>& triRef = meshRelation_.triRef;
|
||||
for (size_t tri = 0; tri < NumTri(); ++tri) {
|
||||
const int referenceTri = comp2tri[components[tri]];
|
||||
if (referenceTri >= 0) {
|
||||
triRef[tri].faceID = referenceTri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the halfedge_ data structure from an input triVerts array like Mesh.
|
||||
*/
|
||||
void Manifold::Impl::CreateHalfedges(const Vec<ivec3>& triVerts) {
|
||||
ZoneScoped;
|
||||
const size_t numTri = triVerts.size();
|
||||
const int numHalfedge = 3 * numTri;
|
||||
// drop the old value first to avoid copy
|
||||
halfedge_.resize(0);
|
||||
halfedge_.resize(numHalfedge);
|
||||
Vec<uint64_t> edge(numHalfedge);
|
||||
Vec<int> ids(numHalfedge);
|
||||
auto policy = autoPolicy(numTri, 1e5);
|
||||
sequence(ids.begin(), ids.end());
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
[this, &edge, &triVerts](const int tri) {
|
||||
const ivec3& verts = triVerts[tri];
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int j = (i + 1) % 3;
|
||||
const int e = 3 * tri + i;
|
||||
halfedge_[e] = {verts[i], verts[j], -1};
|
||||
// Sort the forward halfedges in front of the backward ones
|
||||
// by setting the highest-order bit.
|
||||
edge[e] = uint64_t(verts[i] < verts[j] ? 1 : 0) << 63 |
|
||||
((uint64_t)std::min(verts[i], verts[j])) << 32 |
|
||||
std::max(verts[i], verts[j]);
|
||||
}
|
||||
});
|
||||
// Stable sort is required here so that halfedges from the same face are
|
||||
// paired together (the triangles were created in face order). In some
|
||||
// degenerate situations the triangulator can add the same internal edge in
|
||||
// two different faces, causing this edge to not be 2-manifold. These are
|
||||
// fixed by duplicating verts in SimplifyTopology.
|
||||
stable_sort(ids.begin(), ids.end(), [&edge](const int& a, const int& b) {
|
||||
return edge[a] < edge[b];
|
||||
});
|
||||
|
||||
// Mark opposed triangles for removal
|
||||
const int numEdge = numHalfedge / 2;
|
||||
for (int i = 0; i < numEdge; ++i) {
|
||||
const int pair0 = ids[i];
|
||||
Halfedge h0 = halfedge_[pair0];
|
||||
int k = i + numEdge;
|
||||
while (1) {
|
||||
const int pair1 = ids[k];
|
||||
Halfedge h1 = halfedge_[pair1];
|
||||
if (h0.startVert != h1.endVert || h0.endVert != h1.startVert) break;
|
||||
if (halfedge_[NextHalfedge(pair0)].endVert ==
|
||||
halfedge_[NextHalfedge(pair1)].endVert) {
|
||||
// Reorder so that remaining edges pair up
|
||||
if (k != i + numEdge) std::swap(ids[i + numEdge], ids[k]);
|
||||
break;
|
||||
}
|
||||
++k;
|
||||
if (k >= numHalfedge) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Once sorted, the first half of the range is the forward halfedges, which
|
||||
// correspond to their backward pair at the same offset in the second half
|
||||
// of the range.
|
||||
for_each_n(policy, countAt(0), numEdge, [this, &ids, numEdge](int i) {
|
||||
const int pair0 = ids[i];
|
||||
const int pair1 = ids[i + numEdge];
|
||||
halfedge_[pair0].pairedHalfedge = pair1;
|
||||
halfedge_[pair1].pairedHalfedge = pair0;
|
||||
});
|
||||
|
||||
// When opposed triangles are removed, they may strand unreferenced verts.
|
||||
RemoveUnreferencedVerts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a full recalculation of the face bounding boxes, including updating
|
||||
* the collider, but does not resort the faces.
|
||||
*/
|
||||
void Manifold::Impl::Update() {
|
||||
CalculateBBox();
|
||||
Vec<Box> faceBox;
|
||||
Vec<uint32_t> faceMorton;
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
collider_.UpdateBoxes(faceBox);
|
||||
}
|
||||
|
||||
void Manifold::Impl::MarkFailure(Error status) {
|
||||
bBox_ = Box();
|
||||
vertPos_.resize(0);
|
||||
halfedge_.resize(0);
|
||||
vertNormal_.resize(0);
|
||||
faceNormal_.resize(0);
|
||||
halfedgeTangent_.resize(0);
|
||||
meshRelation_ = MeshRelationD();
|
||||
status_ = status;
|
||||
}
|
||||
|
||||
void Manifold::Impl::Warp(std::function<void(vec3&)> warpFunc) {
|
||||
WarpBatch([&warpFunc](VecView<vec3> vecs) {
|
||||
for_each(ExecutionPolicy::Seq, vecs.begin(), vecs.end(), warpFunc);
|
||||
});
|
||||
}
|
||||
|
||||
void Manifold::Impl::WarpBatch(std::function<void(VecView<vec3>)> warpFunc) {
|
||||
warpFunc(vertPos_.view());
|
||||
CalculateBBox();
|
||||
if (!IsFinite()) {
|
||||
MarkFailure(Error::NonFiniteVertex);
|
||||
return;
|
||||
}
|
||||
Update();
|
||||
faceNormal_.resize(0); // force recalculation of triNormal
|
||||
CalculateNormals();
|
||||
SetEpsilon();
|
||||
Finish();
|
||||
CreateFaces();
|
||||
meshRelation_.originalID = -1;
|
||||
}
|
||||
|
||||
Manifold::Impl Manifold::Impl::Transform(const mat3x4& transform_) const {
|
||||
ZoneScoped;
|
||||
if (transform_ == mat3x4(la::identity)) return *this;
|
||||
auto policy = autoPolicy(NumVert());
|
||||
Impl result;
|
||||
if (status_ != Manifold::Error::NoError) {
|
||||
result.status_ = status_;
|
||||
return result;
|
||||
}
|
||||
if (!all(la::isfinite(transform_))) {
|
||||
result.MarkFailure(Error::NonFiniteVertex);
|
||||
return result;
|
||||
}
|
||||
result.collider_ = collider_;
|
||||
result.meshRelation_ = meshRelation_;
|
||||
result.epsilon_ = epsilon_;
|
||||
result.tolerance_ = tolerance_;
|
||||
result.bBox_ = bBox_;
|
||||
result.halfedge_ = halfedge_;
|
||||
result.halfedgeTangent_.resize(halfedgeTangent_.size());
|
||||
|
||||
result.meshRelation_.originalID = -1;
|
||||
for (auto& m : result.meshRelation_.meshIDtransform) {
|
||||
m.second.transform = transform_ * Mat4(m.second.transform);
|
||||
}
|
||||
|
||||
result.vertPos_.resize(NumVert());
|
||||
result.faceNormal_.resize(faceNormal_.size());
|
||||
result.vertNormal_.resize(vertNormal_.size());
|
||||
transform(vertPos_.begin(), vertPos_.end(), result.vertPos_.begin(),
|
||||
Transform4x3({transform_}));
|
||||
|
||||
mat3 normalTransform = NormalTransform(transform_);
|
||||
transform(faceNormal_.begin(), faceNormal_.end(), result.faceNormal_.begin(),
|
||||
TransformNormals({normalTransform}));
|
||||
transform(vertNormal_.begin(), vertNormal_.end(), result.vertNormal_.begin(),
|
||||
TransformNormals({normalTransform}));
|
||||
|
||||
const bool invert = la::determinant(mat3(transform_)) < 0;
|
||||
|
||||
if (halfedgeTangent_.size() > 0) {
|
||||
for_each_n(policy, countAt(0), halfedgeTangent_.size(),
|
||||
TransformTangents({result.halfedgeTangent_, 0, mat3(transform_),
|
||||
invert, halfedgeTangent_, halfedge_}));
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
for_each_n(policy, countAt(0), result.NumTri(),
|
||||
FlipTris({result.halfedge_}));
|
||||
}
|
||||
|
||||
// This optimization does a cheap collider update if the transform is
|
||||
// axis-aligned.
|
||||
if (!result.collider_.Transform(transform_)) result.Update();
|
||||
|
||||
result.CalculateBBox();
|
||||
// Scale epsilon by the norm of the 3x3 portion of the transform.
|
||||
result.epsilon_ *= SpectralNorm(mat3(transform_));
|
||||
// Maximum of inherited epsilon loss and translational epsilon loss.
|
||||
result.SetEpsilon(result.epsilon_);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets epsilon based on the bounding box, and limits its minimum value
|
||||
* by the optional input.
|
||||
*/
|
||||
void Manifold::Impl::SetEpsilon(double minEpsilon, bool useSingle) {
|
||||
epsilon_ = MaxEpsilon(minEpsilon, bBox_);
|
||||
double minTol = epsilon_;
|
||||
if (useSingle)
|
||||
minTol =
|
||||
std::max(minTol, std::numeric_limits<float>::epsilon() * bBox_.Scale());
|
||||
tolerance_ = std::max(tolerance_, minTol);
|
||||
}
|
||||
|
||||
/**
|
||||
* If face normals are already present, this function uses them to compute
|
||||
* vertex normals (angle-weighted pseudo-normals); otherwise it also computes
|
||||
* the face normals. Face normals are only calculated when needed because
|
||||
* nearly degenerate faces will accrue rounding error, while the Boolean can
|
||||
* retain their original normal, which is more accurate and can help with
|
||||
* merging coplanar faces.
|
||||
*
|
||||
* If the face normals have been invalidated by an operation like Warp(),
|
||||
* ensure you do faceNormal_.resize(0) before calling this function to force
|
||||
* recalculation.
|
||||
*/
|
||||
void Manifold::Impl::CalculateNormals() {
|
||||
ZoneScoped;
|
||||
vertNormal_.resize(NumVert());
|
||||
auto policy = autoPolicy(NumTri(), 1e4);
|
||||
fill(vertNormal_.begin(), vertNormal_.end(), vec3(0.0));
|
||||
bool calculateTriNormal = false;
|
||||
if (faceNormal_.size() != NumTri()) {
|
||||
faceNormal_.resize(NumTri());
|
||||
calculateTriNormal = true;
|
||||
}
|
||||
if (calculateTriNormal)
|
||||
for_each_n(
|
||||
policy, countAt(0), NumTri(),
|
||||
AssignNormals<true>({faceNormal_, vertNormal_, vertPos_, halfedge_}));
|
||||
else
|
||||
for_each_n(
|
||||
policy, countAt(0), NumTri(),
|
||||
AssignNormals<false>({faceNormal_, vertNormal_, vertPos_, halfedge_}));
|
||||
for_each(policy, vertNormal_.begin(), vertNormal_.end(),
|
||||
[](vec3& v) { v = SafeNormalize(v); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Remaps all the contained meshIDs to new unique values to represent new
|
||||
* instances of these meshes.
|
||||
*/
|
||||
void Manifold::Impl::IncrementMeshIDs() {
|
||||
HashTable<uint32_t> meshIDold2new(meshRelation_.meshIDtransform.size() * 2);
|
||||
// Update keys of the transform map
|
||||
std::map<int, Relation> oldTransforms;
|
||||
std::swap(meshRelation_.meshIDtransform, oldTransforms);
|
||||
const int numMeshIDs = oldTransforms.size();
|
||||
int nextMeshID = ReserveIDs(numMeshIDs);
|
||||
for (const auto& pair : oldTransforms) {
|
||||
meshIDold2new.D().Insert(pair.first, nextMeshID);
|
||||
meshRelation_.meshIDtransform[nextMeshID++] = pair.second;
|
||||
}
|
||||
|
||||
const size_t numTri = NumTri();
|
||||
for_each_n(autoPolicy(numTri, 1e5), meshRelation_.triRef.begin(), numTri,
|
||||
UpdateMeshID({meshIDold2new.D()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sparse array of the bounding box overlaps between the edges of
|
||||
* the input manifold, Q and the faces of this manifold. Returned indices only
|
||||
* point to forward halfedges.
|
||||
*/
|
||||
SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q,
|
||||
bool inverted) const {
|
||||
ZoneScoped;
|
||||
Vec<TmpEdge> edges = CreateTmpEdges(Q.halfedge_);
|
||||
const size_t numEdge = edges.size();
|
||||
Vec<Box> QedgeBB(numEdge);
|
||||
const auto& vertPos = Q.vertPos_;
|
||||
auto policy = autoPolicy(numEdge, 1e5);
|
||||
for_each_n(
|
||||
policy, countAt(0), numEdge, [&QedgeBB, &edges, &vertPos](const int e) {
|
||||
QedgeBB[e] = Box(vertPos[edges[e].first], vertPos[edges[e].second]);
|
||||
});
|
||||
|
||||
SparseIndices q1p2(0);
|
||||
if (inverted)
|
||||
q1p2 = collider_.Collisions<false, true>(QedgeBB.cview());
|
||||
else
|
||||
q1p2 = collider_.Collisions<false, false>(QedgeBB.cview());
|
||||
|
||||
if (inverted)
|
||||
for_each(policy, countAt(0_uz), countAt(q1p2.size()),
|
||||
ReindexEdge<true>({edges, q1p2}));
|
||||
else
|
||||
for_each(policy, countAt(0_uz), countAt(q1p2.size()),
|
||||
ReindexEdge<false>({edges, q1p2}));
|
||||
return q1p2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sparse array of the input vertices that project inside the XY
|
||||
* bounding boxes of the faces of this manifold.
|
||||
*/
|
||||
SparseIndices Manifold::Impl::VertexCollisionsZ(VecView<const vec3> vertsIn,
|
||||
bool inverted) const {
|
||||
ZoneScoped;
|
||||
if (inverted)
|
||||
return collider_.Collisions<false, true>(vertsIn);
|
||||
else
|
||||
return collider_.Collisions<false, false>(vertsIn);
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
352
thirdparty/manifold/src/impl.h
vendored
Normal file
352
thirdparty/manifold/src/impl.h
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <map>
|
||||
|
||||
#include "./collider.h"
|
||||
#include "./shared.h"
|
||||
#include "./sparse.h"
|
||||
#include "./vec.h"
|
||||
#include "manifold/manifold.h"
|
||||
#include "manifold/polygon.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
struct Manifold::Impl {
|
||||
struct Relation {
|
||||
int originalID = -1;
|
||||
mat3x4 transform = la::identity;
|
||||
bool backSide = false;
|
||||
};
|
||||
struct MeshRelationD {
|
||||
/// The originalID of this Manifold if it is an original; -1 otherwise.
|
||||
int originalID = -1;
|
||||
int numProp = 0;
|
||||
Vec<double> properties;
|
||||
std::map<int, Relation> meshIDtransform;
|
||||
Vec<TriRef> triRef;
|
||||
Vec<ivec3> triProperties;
|
||||
};
|
||||
struct BaryIndices {
|
||||
int tri, start4, end4;
|
||||
};
|
||||
|
||||
Box bBox_;
|
||||
double epsilon_ = -1;
|
||||
double tolerance_ = -1;
|
||||
Error status_ = Error::NoError;
|
||||
Vec<vec3> vertPos_;
|
||||
Vec<Halfedge> halfedge_;
|
||||
Vec<vec3> vertNormal_;
|
||||
Vec<vec3> faceNormal_;
|
||||
Vec<vec4> halfedgeTangent_;
|
||||
MeshRelationD meshRelation_;
|
||||
Collider collider_;
|
||||
|
||||
static std::atomic<uint32_t> meshIDCounter_;
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
|
||||
Impl() {}
|
||||
enum class Shape { Tetrahedron, Cube, Octahedron };
|
||||
Impl(Shape, const mat3x4 = la::identity);
|
||||
|
||||
template <typename Precision, typename I>
|
||||
Impl(const MeshGLP<Precision, I>& meshGL) {
|
||||
const uint32_t numVert = meshGL.NumVert();
|
||||
const uint32_t numTri = meshGL.NumTri();
|
||||
|
||||
if (meshGL.numProp < 3) {
|
||||
MarkFailure(Error::MissingPositionProperties);
|
||||
return;
|
||||
}
|
||||
|
||||
if (meshGL.mergeFromVert.size() != meshGL.mergeToVert.size()) {
|
||||
MarkFailure(Error::MergeVectorsDifferentLengths);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.runTransform.empty() &&
|
||||
12 * meshGL.runOriginalID.size() != meshGL.runTransform.size()) {
|
||||
MarkFailure(Error::TransformWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.runOriginalID.empty() && !meshGL.runIndex.empty() &&
|
||||
meshGL.runOriginalID.size() + 1 != meshGL.runIndex.size() &&
|
||||
meshGL.runOriginalID.size() != meshGL.runIndex.size()) {
|
||||
MarkFailure(Error::RunIndexWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.faceID.empty() && meshGL.faceID.size() != meshGL.NumTri()) {
|
||||
MarkFailure(Error::FaceIDWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<int> prop2vert(numVert);
|
||||
std::iota(prop2vert.begin(), prop2vert.end(), 0);
|
||||
for (size_t i = 0; i < meshGL.mergeFromVert.size(); ++i) {
|
||||
const uint32_t from = meshGL.mergeFromVert[i];
|
||||
const uint32_t to = meshGL.mergeToVert[i];
|
||||
if (from >= numVert || to >= numVert) {
|
||||
MarkFailure(Error::MergeIndexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
prop2vert[from] = to;
|
||||
}
|
||||
|
||||
const auto numProp = meshGL.numProp - 3;
|
||||
meshRelation_.numProp = numProp;
|
||||
meshRelation_.properties.resize(meshGL.NumVert() * numProp);
|
||||
tolerance_ = meshGL.tolerance;
|
||||
// This will have unreferenced duplicate positions that will be removed by
|
||||
// Impl::RemoveUnreferencedVerts().
|
||||
vertPos_.resize(meshGL.NumVert());
|
||||
|
||||
for (size_t i = 0; i < meshGL.NumVert(); ++i) {
|
||||
for (const int j : {0, 1, 2})
|
||||
vertPos_[i][j] = meshGL.vertProperties[meshGL.numProp * i + j];
|
||||
for (size_t j = 0; j < numProp; ++j)
|
||||
meshRelation_.properties[i * numProp + j] =
|
||||
meshGL.vertProperties[meshGL.numProp * i + 3 + j];
|
||||
}
|
||||
|
||||
halfedgeTangent_.resize(meshGL.halfedgeTangent.size() / 4);
|
||||
for (size_t i = 0; i < halfedgeTangent_.size(); ++i) {
|
||||
for (const int j : {0, 1, 2, 3})
|
||||
halfedgeTangent_[i][j] = meshGL.halfedgeTangent[4 * i + j];
|
||||
}
|
||||
|
||||
Vec<TriRef> triRef;
|
||||
if (!meshGL.runOriginalID.empty()) {
|
||||
auto runIndex = meshGL.runIndex;
|
||||
const auto runEnd = meshGL.triVerts.size();
|
||||
if (runIndex.empty()) {
|
||||
runIndex = {0, static_cast<I>(runEnd)};
|
||||
} else if (runIndex.size() == meshGL.runOriginalID.size()) {
|
||||
runIndex.push_back(runEnd);
|
||||
}
|
||||
triRef.resize(meshGL.NumTri());
|
||||
const auto startID = Impl::ReserveIDs(meshGL.runOriginalID.size());
|
||||
for (size_t i = 0; i < meshGL.runOriginalID.size(); ++i) {
|
||||
const int meshID = startID + i;
|
||||
const int originalID = meshGL.runOriginalID[i];
|
||||
for (size_t tri = runIndex[i] / 3; tri < runIndex[i + 1] / 3; ++tri) {
|
||||
TriRef& ref = triRef[tri];
|
||||
ref.meshID = meshID;
|
||||
ref.originalID = originalID;
|
||||
ref.tri = meshGL.faceID.empty() ? tri : meshGL.faceID[tri];
|
||||
ref.faceID = tri;
|
||||
}
|
||||
|
||||
if (meshGL.runTransform.empty()) {
|
||||
meshRelation_.meshIDtransform[meshID] = {originalID};
|
||||
} else {
|
||||
const Precision* m = meshGL.runTransform.data() + 12 * i;
|
||||
meshRelation_.meshIDtransform[meshID] = {originalID,
|
||||
{{m[0], m[1], m[2]},
|
||||
{m[3], m[4], m[5]},
|
||||
{m[6], m[7], m[8]},
|
||||
{m[9], m[10], m[11]}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vec<ivec3> triVerts;
|
||||
triVerts.reserve(numTri);
|
||||
for (size_t i = 0; i < numTri; ++i) {
|
||||
ivec3 tri;
|
||||
for (const size_t j : {0, 1, 2}) {
|
||||
uint32_t vert = (uint32_t)meshGL.triVerts[3 * i + j];
|
||||
if (vert >= numVert) {
|
||||
MarkFailure(Error::VertexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
tri[j] = prop2vert[vert];
|
||||
}
|
||||
if (tri[0] != tri[1] && tri[1] != tri[2] && tri[2] != tri[0]) {
|
||||
triVerts.push_back(tri);
|
||||
if (triRef.size() > 0) {
|
||||
meshRelation_.triRef.push_back(triRef[i]);
|
||||
}
|
||||
if (numProp > 0) {
|
||||
meshRelation_.triProperties.push_back(
|
||||
ivec3(static_cast<uint32_t>(meshGL.triVerts[3 * i]),
|
||||
static_cast<uint32_t>(meshGL.triVerts[3 * i + 1]),
|
||||
static_cast<uint32_t>(meshGL.triVerts[3 * i + 2])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CreateHalfedges(triVerts);
|
||||
if (!IsManifold()) {
|
||||
MarkFailure(Error::NotManifold);
|
||||
return;
|
||||
}
|
||||
|
||||
CalculateBBox();
|
||||
if (!IsFinite()) {
|
||||
MarkFailure(Error::NonFiniteVertex);
|
||||
return;
|
||||
}
|
||||
SetEpsilon(-1, std::is_same<Precision, float>::value);
|
||||
|
||||
SplitPinchedVerts();
|
||||
|
||||
CalculateNormals();
|
||||
|
||||
if (meshGL.runOriginalID.empty()) {
|
||||
InitializeOriginal();
|
||||
}
|
||||
|
||||
CreateFaces();
|
||||
|
||||
SimplifyTopology();
|
||||
Finish();
|
||||
|
||||
// A Manifold created from an input mesh is never an original - the input is
|
||||
// the original.
|
||||
meshRelation_.originalID = -1;
|
||||
}
|
||||
|
||||
inline void ForVert(int halfedge, std::function<void(int halfedge)> func) {
|
||||
int current = halfedge;
|
||||
do {
|
||||
current = NextHalfedge(halfedge_[current].pairedHalfedge);
|
||||
func(current);
|
||||
} while (current != halfedge);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ForVert(
|
||||
int halfedge, std::function<T(int halfedge)> transform,
|
||||
std::function<void(int halfedge, const T& here, T& next)> binaryOp) {
|
||||
T here = transform(halfedge);
|
||||
int current = halfedge;
|
||||
do {
|
||||
const int nextHalfedge = NextHalfedge(halfedge_[current].pairedHalfedge);
|
||||
T next = transform(nextHalfedge);
|
||||
binaryOp(current, here, next);
|
||||
here = next;
|
||||
current = nextHalfedge;
|
||||
} while (current != halfedge);
|
||||
}
|
||||
|
||||
void CreateFaces();
|
||||
void RemoveUnreferencedVerts();
|
||||
void InitializeOriginal(bool keepFaceID = false);
|
||||
void CreateHalfedges(const Vec<ivec3>& triVerts);
|
||||
void CalculateNormals();
|
||||
void IncrementMeshIDs();
|
||||
|
||||
void Update();
|
||||
void MarkFailure(Error status);
|
||||
void Warp(std::function<void(vec3&)> warpFunc);
|
||||
void WarpBatch(std::function<void(VecView<vec3>)> warpFunc);
|
||||
Impl Transform(const mat3x4& transform) const;
|
||||
SparseIndices EdgeCollisions(const Impl& B, bool inverted = false) const;
|
||||
SparseIndices VertexCollisionsZ(VecView<const vec3> vertsIn,
|
||||
bool inverted = false) const;
|
||||
|
||||
bool IsEmpty() const { return NumTri() == 0; }
|
||||
size_t NumVert() const { return vertPos_.size(); }
|
||||
size_t NumEdge() const { return halfedge_.size() / 2; }
|
||||
size_t NumTri() const { return halfedge_.size() / 3; }
|
||||
size_t NumProp() const { return meshRelation_.numProp; }
|
||||
size_t NumPropVert() const {
|
||||
return NumProp() == 0 ? NumVert()
|
||||
: meshRelation_.properties.size() / NumProp();
|
||||
}
|
||||
|
||||
// properties.cu
|
||||
enum class Property { Volume, SurfaceArea };
|
||||
double GetProperty(Property prop) const;
|
||||
void CalculateCurvature(int gaussianIdx, int meanIdx);
|
||||
void CalculateBBox();
|
||||
bool IsFinite() const;
|
||||
bool IsIndexInBounds(VecView<const ivec3> triVerts) const;
|
||||
void SetEpsilon(double minEpsilon = -1, bool useSingle = false);
|
||||
bool IsManifold() const;
|
||||
bool Is2Manifold() const;
|
||||
bool MatchesTriNormals() const;
|
||||
int NumDegenerateTris() const;
|
||||
double MinGap(const Impl& other, double searchLength) const;
|
||||
|
||||
// sort.cu
|
||||
void Finish();
|
||||
void SortVerts();
|
||||
void ReindexVerts(const Vec<int>& vertNew2Old, size_t numOldVert);
|
||||
void CompactProps();
|
||||
void GetFaceBoxMorton(Vec<Box>& faceBox, Vec<uint32_t>& faceMorton) const;
|
||||
void SortFaces(Vec<Box>& faceBox, Vec<uint32_t>& faceMorton);
|
||||
void GatherFaces(const Vec<int>& faceNew2Old);
|
||||
void GatherFaces(const Impl& old, const Vec<int>& faceNew2Old);
|
||||
|
||||
// face_op.cu
|
||||
void Face2Tri(const Vec<int>& faceEdge, const Vec<TriRef>& halfedgeRef);
|
||||
PolygonsIdx Face2Polygons(VecView<Halfedge>::IterC start,
|
||||
VecView<Halfedge>::IterC end,
|
||||
mat2x3 projection) const;
|
||||
Polygons Slice(double height) const;
|
||||
Polygons Project() const;
|
||||
|
||||
// edge_op.cu
|
||||
void CleanupTopology();
|
||||
void SimplifyTopology();
|
||||
void DedupeEdge(int edge);
|
||||
void CollapseEdge(int edge, std::vector<int>& edges);
|
||||
void RecursiveEdgeSwap(int edge, int& tag, std::vector<int>& visited,
|
||||
std::vector<int>& edgeSwapStack,
|
||||
std::vector<int>& edges);
|
||||
void RemoveIfFolded(int edge);
|
||||
void PairUp(int edge0, int edge1);
|
||||
void UpdateVert(int vert, int startEdge, int endEdge);
|
||||
void FormLoop(int current, int end);
|
||||
void CollapseTri(const ivec3& triEdge);
|
||||
void SplitPinchedVerts();
|
||||
|
||||
// subdivision.cpp
|
||||
int GetNeighbor(int tri) const;
|
||||
ivec4 GetHalfedges(int tri) const;
|
||||
BaryIndices GetIndices(int halfedge) const;
|
||||
void FillRetainedVerts(Vec<Barycentric>& vertBary) const;
|
||||
Vec<Barycentric> Subdivide(std::function<int(vec3, vec4, vec4)>,
|
||||
bool = false);
|
||||
|
||||
// smoothing.cpp
|
||||
bool IsInsideQuad(int halfedge) const;
|
||||
bool IsMarkedInsideQuad(int halfedge) const;
|
||||
vec3 GetNormal(int halfedge, int normalIdx) const;
|
||||
vec4 TangentFromNormal(const vec3& normal, int halfedge) const;
|
||||
std::vector<Smoothness> UpdateSharpenedEdges(
|
||||
const std::vector<Smoothness>&) const;
|
||||
Vec<bool> FlatFaces() const;
|
||||
Vec<int> VertFlatFace(const Vec<bool>&) const;
|
||||
Vec<int> VertHalfedge() const;
|
||||
std::vector<Smoothness> SharpenEdges(double minSharpAngle,
|
||||
double minSmoothness) const;
|
||||
void SharpenTangent(int halfedge, double smoothness);
|
||||
void SetNormals(int normalIdx, double minSharpAngle);
|
||||
void LinearizeFlatTangents();
|
||||
void DistributeTangents(const Vec<bool>& fixedHalfedges);
|
||||
void CreateTangents(int normalIdx);
|
||||
void CreateTangents(std::vector<Smoothness>);
|
||||
void Refine(std::function<int(vec3, vec4, vec4)>, bool = false);
|
||||
|
||||
// quickhull.cpp
|
||||
void Hull(VecView<vec3> vertPos);
|
||||
};
|
||||
} // namespace manifold
|
||||
1038
thirdparty/manifold/src/manifold.cpp
vendored
Normal file
1038
thirdparty/manifold/src/manifold.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
65
thirdparty/manifold/src/mesh_fixes.h
vendored
Normal file
65
thirdparty/manifold/src/mesh_fixes.h
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#pragma once
|
||||
#include "./shared.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
inline int FlipHalfedge(int halfedge) {
|
||||
const int tri = halfedge / 3;
|
||||
const int vert = 2 - (halfedge - 3 * tri);
|
||||
return 3 * tri + vert;
|
||||
}
|
||||
|
||||
struct TransformNormals {
|
||||
mat3 transform;
|
||||
|
||||
vec3 operator()(vec3 normal) const {
|
||||
normal = la::normalize(transform * normal);
|
||||
if (std::isnan(normal.x)) normal = vec3(0.0);
|
||||
return normal;
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformTangents {
|
||||
VecView<vec4> tangent;
|
||||
const int edgeOffset;
|
||||
const mat3 transform;
|
||||
const bool invert;
|
||||
VecView<const vec4> oldTangents;
|
||||
VecView<const Halfedge> halfedge;
|
||||
|
||||
void operator()(const int edgeOut) {
|
||||
const int edgeIn =
|
||||
invert ? halfedge[FlipHalfedge(edgeOut)].pairedHalfedge : edgeOut;
|
||||
tangent[edgeOut + edgeOffset] =
|
||||
vec4(transform * vec3(oldTangents[edgeIn]), oldTangents[edgeIn].w);
|
||||
}
|
||||
};
|
||||
|
||||
struct FlipTris {
|
||||
VecView<Halfedge> halfedge;
|
||||
|
||||
void operator()(const int tri) {
|
||||
std::swap(halfedge[3 * tri], halfedge[3 * tri + 2]);
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
std::swap(halfedge[3 * tri + i].startVert, halfedge[3 * tri + i].endVert);
|
||||
halfedge[3 * tri + i].pairedHalfedge =
|
||||
FlipHalfedge(halfedge[3 * tri + i].pairedHalfedge);
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
1125
thirdparty/manifold/src/parallel.h
vendored
Normal file
1125
thirdparty/manifold/src/parallel.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1010
thirdparty/manifold/src/polygon.cpp
vendored
Normal file
1010
thirdparty/manifold/src/polygon.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
387
thirdparty/manifold/src/properties.cpp
vendored
Normal file
387
thirdparty/manifold/src/properties.cpp
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "./impl.h"
|
||||
#include "./parallel.h"
|
||||
#include "./tri_dist.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
struct CurvatureAngles {
|
||||
VecView<double> meanCurvature;
|
||||
VecView<double> gaussianCurvature;
|
||||
VecView<double> area;
|
||||
VecView<double> degree;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const vec3> vertPos;
|
||||
VecView<const vec3> triNormal;
|
||||
|
||||
void operator()(size_t tri) {
|
||||
vec3 edge[3];
|
||||
vec3 edgeLength(0.0);
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int startVert = halfedge[3 * tri + i].startVert;
|
||||
const int endVert = halfedge[3 * tri + i].endVert;
|
||||
edge[i] = vertPos[endVert] - vertPos[startVert];
|
||||
edgeLength[i] = la::length(edge[i]);
|
||||
edge[i] /= edgeLength[i];
|
||||
const int neighborTri = halfedge[3 * tri + i].pairedHalfedge / 3;
|
||||
const double dihedral =
|
||||
0.25 * edgeLength[i] *
|
||||
std::asin(la::dot(la::cross(triNormal[tri], triNormal[neighborTri]),
|
||||
edge[i]));
|
||||
AtomicAdd(meanCurvature[startVert], dihedral);
|
||||
AtomicAdd(meanCurvature[endVert], dihedral);
|
||||
AtomicAdd(degree[startVert], 1.0);
|
||||
}
|
||||
|
||||
vec3 phi;
|
||||
phi[0] = std::acos(-la::dot(edge[2], edge[0]));
|
||||
phi[1] = std::acos(-la::dot(edge[0], edge[1]));
|
||||
phi[2] = kPi - phi[0] - phi[1];
|
||||
const double area3 = edgeLength[0] * edgeLength[1] *
|
||||
la::length(la::cross(edge[0], edge[1])) / 6;
|
||||
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedge[3 * tri + i].startVert;
|
||||
AtomicAdd(gaussianCurvature[vert], -phi[i]);
|
||||
AtomicAdd(area[vert], area3);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateProperties {
|
||||
VecView<ivec3> triProp;
|
||||
VecView<double> properties;
|
||||
VecView<uint8_t> counters;
|
||||
|
||||
VecView<const double> oldProperties;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const double> meanCurvature;
|
||||
VecView<const double> gaussianCurvature;
|
||||
const int oldNumProp;
|
||||
const int numProp;
|
||||
const int gaussianIdx;
|
||||
const int meanIdx;
|
||||
|
||||
void operator()(const size_t tri) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = halfedge[3 * tri + i].startVert;
|
||||
if (oldNumProp == 0) {
|
||||
triProp[tri][i] = vert;
|
||||
}
|
||||
const int propVert = triProp[tri][i];
|
||||
|
||||
auto old = std::atomic_exchange(
|
||||
reinterpret_cast<std::atomic<uint8_t>*>(&counters[propVert]),
|
||||
static_cast<uint8_t>(1));
|
||||
if (old == 1) continue;
|
||||
|
||||
for (int p = 0; p < oldNumProp; ++p) {
|
||||
properties[numProp * propVert + p] =
|
||||
oldProperties[oldNumProp * propVert + p];
|
||||
}
|
||||
|
||||
if (gaussianIdx >= 0) {
|
||||
properties[numProp * propVert + gaussianIdx] = gaussianCurvature[vert];
|
||||
}
|
||||
if (meanIdx >= 0) {
|
||||
properties[numProp * propVert + meanIdx] = meanCurvature[vert];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckHalfedges {
|
||||
VecView<const Halfedge> halfedges;
|
||||
|
||||
bool operator()(size_t edge) const {
|
||||
const Halfedge halfedge = halfedges[edge];
|
||||
if (halfedge.startVert == -1 || halfedge.endVert == -1) return true;
|
||||
if (halfedge.pairedHalfedge == -1) return false;
|
||||
|
||||
const Halfedge paired = halfedges[halfedge.pairedHalfedge];
|
||||
bool good = true;
|
||||
good &= paired.pairedHalfedge == static_cast<int>(edge);
|
||||
good &= halfedge.startVert != halfedge.endVert;
|
||||
good &= halfedge.startVert == paired.endVert;
|
||||
good &= halfedge.endVert == paired.startVert;
|
||||
return good;
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckCCW {
|
||||
VecView<const Halfedge> halfedges;
|
||||
VecView<const vec3> vertPos;
|
||||
VecView<const vec3> triNormal;
|
||||
const double tol;
|
||||
|
||||
bool operator()(size_t face) const {
|
||||
if (halfedges[3 * face].pairedHalfedge < 0) return true;
|
||||
|
||||
const mat2x3 projection = GetAxisAlignedProjection(triNormal[face]);
|
||||
vec2 v[3];
|
||||
for (int i : {0, 1, 2})
|
||||
v[i] = projection * vertPos[halfedges[3 * face + i].startVert];
|
||||
|
||||
int ccw = CCW(v[0], v[1], v[2], std::abs(tol));
|
||||
bool check = tol > 0 ? ccw >= 0 : ccw == 0;
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (tol > 0 && !check) {
|
||||
vec2 v1 = v[1] - v[0];
|
||||
vec2 v2 = v[2] - v[0];
|
||||
double area = v1.x * v2.y - v1.y * v2.x;
|
||||
double base2 = std::max(la::dot(v1, v1), la::dot(v2, v2));
|
||||
double base = std::sqrt(base2);
|
||||
vec3 V0 = vertPos[halfedges[3 * face].startVert];
|
||||
vec3 V1 = vertPos[halfedges[3 * face + 1].startVert];
|
||||
vec3 V2 = vertPos[halfedges[3 * face + 2].startVert];
|
||||
vec3 norm = la::cross(V1 - V0, V2 - V0);
|
||||
printf(
|
||||
"Tri %ld does not match normal, approx height = %g, base = %g\n"
|
||||
"tol = %g, area2 = %g, base2*tol2 = %g\n"
|
||||
"normal = %g, %g, %g\n"
|
||||
"norm = %g, %g, %g\nverts: %d, %d, %d\n",
|
||||
face, area / base, base, tol, area * area, base2 * tol * tol,
|
||||
triNormal[face].x, triNormal[face].y, triNormal[face].z, norm.x,
|
||||
norm.y, norm.z, halfedges[3 * face].startVert,
|
||||
halfedges[3 * face + 1].startVert, halfedges[3 * face + 2].startVert);
|
||||
}
|
||||
#endif
|
||||
return check;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Returns true if this manifold is in fact an oriented even manifold and all of
|
||||
* the data structures are consistent.
|
||||
*/
|
||||
bool Manifold::Impl::IsManifold() const {
|
||||
if (halfedge_.size() == 0) return true;
|
||||
return all_of(countAt(0_uz), countAt(halfedge_.size()),
|
||||
CheckHalfedges({halfedge_}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this manifold is in fact an oriented 2-manifold and all of
|
||||
* the data structures are consistent.
|
||||
*/
|
||||
bool Manifold::Impl::Is2Manifold() const {
|
||||
if (halfedge_.size() == 0) return true;
|
||||
if (!IsManifold()) return false;
|
||||
|
||||
Vec<Halfedge> halfedge(halfedge_);
|
||||
stable_sort(halfedge.begin(), halfedge.end());
|
||||
|
||||
return all_of(
|
||||
countAt(0_uz), countAt(2 * NumEdge() - 1), [&halfedge](size_t edge) {
|
||||
const Halfedge h = halfedge[edge];
|
||||
if (h.startVert == -1 && h.endVert == -1 && h.pairedHalfedge == -1)
|
||||
return true;
|
||||
return h.startVert != halfedge[edge + 1].startVert ||
|
||||
h.endVert != halfedge[edge + 1].endVert;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all triangles are CCW relative to their triNormals_.
|
||||
*/
|
||||
bool Manifold::Impl::MatchesTriNormals() const {
|
||||
if (halfedge_.size() == 0 || faceNormal_.size() != NumTri()) return true;
|
||||
return all_of(countAt(0_uz), countAt(NumTri()),
|
||||
CheckCCW({halfedge_, vertPos_, faceNormal_, 2 * epsilon_}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of triangles that are colinear within epsilon_.
|
||||
*/
|
||||
int Manifold::Impl::NumDegenerateTris() const {
|
||||
if (halfedge_.size() == 0 || faceNormal_.size() != NumTri()) return true;
|
||||
return count_if(
|
||||
countAt(0_uz), countAt(NumTri()),
|
||||
CheckCCW({halfedge_, vertPos_, faceNormal_, -1 * epsilon_ / 2}));
|
||||
}
|
||||
|
||||
double Manifold::Impl::GetProperty(Property prop) const {
|
||||
ZoneScoped;
|
||||
if (IsEmpty()) return 0;
|
||||
|
||||
auto Volume = [this](size_t tri) {
|
||||
const vec3 v = vertPos_[halfedge_[3 * tri].startVert];
|
||||
vec3 crossP = la::cross(vertPos_[halfedge_[3 * tri + 1].startVert] - v,
|
||||
vertPos_[halfedge_[3 * tri + 2].startVert] - v);
|
||||
return la::dot(crossP, v) / 6.0;
|
||||
};
|
||||
|
||||
auto Area = [this](size_t tri) {
|
||||
const vec3 v = vertPos_[halfedge_[3 * tri].startVert];
|
||||
return la::length(
|
||||
la::cross(vertPos_[halfedge_[3 * tri + 1].startVert] - v,
|
||||
vertPos_[halfedge_[3 * tri + 2].startVert] - v)) /
|
||||
2.0;
|
||||
};
|
||||
|
||||
// Kahan summation
|
||||
double value = 0;
|
||||
double valueCompensation = 0;
|
||||
for (size_t i = 0; i < NumTri(); ++i) {
|
||||
const double value1 = prop == Property::SurfaceArea ? Area(i) : Volume(i);
|
||||
const double t = value + value1;
|
||||
valueCompensation += (value - t) + value1;
|
||||
value = t;
|
||||
}
|
||||
value += valueCompensation;
|
||||
return value;
|
||||
}
|
||||
|
||||
void Manifold::Impl::CalculateCurvature(int gaussianIdx, int meanIdx) {
|
||||
ZoneScoped;
|
||||
if (IsEmpty()) return;
|
||||
if (gaussianIdx < 0 && meanIdx < 0) return;
|
||||
Vec<double> vertMeanCurvature(NumVert(), 0);
|
||||
Vec<double> vertGaussianCurvature(NumVert(), kTwoPi);
|
||||
Vec<double> vertArea(NumVert(), 0);
|
||||
Vec<double> degree(NumVert(), 0);
|
||||
auto policy = autoPolicy(NumTri(), 1e4);
|
||||
for_each(policy, countAt(0_uz), countAt(NumTri()),
|
||||
CurvatureAngles({vertMeanCurvature, vertGaussianCurvature, vertArea,
|
||||
degree, halfedge_, vertPos_, faceNormal_}));
|
||||
for_each_n(policy, countAt(0), NumVert(),
|
||||
[&vertMeanCurvature, &vertGaussianCurvature, &vertArea,
|
||||
°ree](const int vert) {
|
||||
const double factor = degree[vert] / (6 * vertArea[vert]);
|
||||
vertMeanCurvature[vert] *= factor;
|
||||
vertGaussianCurvature[vert] *= factor;
|
||||
});
|
||||
|
||||
const int oldNumProp = NumProp();
|
||||
const int numProp = std::max(oldNumProp, std::max(gaussianIdx, meanIdx) + 1);
|
||||
const Vec<double> oldProperties = meshRelation_.properties;
|
||||
meshRelation_.properties = Vec<double>(numProp * NumPropVert(), 0);
|
||||
meshRelation_.numProp = numProp;
|
||||
if (meshRelation_.triProperties.size() == 0) {
|
||||
meshRelation_.triProperties.resize(NumTri());
|
||||
}
|
||||
|
||||
const Vec<uint8_t> counters(NumPropVert(), 0);
|
||||
for_each_n(
|
||||
policy, countAt(0_uz), NumTri(),
|
||||
UpdateProperties({meshRelation_.triProperties, meshRelation_.properties,
|
||||
counters, oldProperties, halfedge_, vertMeanCurvature,
|
||||
vertGaussianCurvature, oldNumProp, numProp, gaussianIdx,
|
||||
meanIdx}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the bounding box of the entire manifold, which is stored
|
||||
* internally to short-cut Boolean operations. Ignores NaNs.
|
||||
*/
|
||||
void Manifold::Impl::CalculateBBox() {
|
||||
bBox_.min =
|
||||
reduce(vertPos_.begin(), vertPos_.end(),
|
||||
vec3(std::numeric_limits<double>::infinity()), [](auto a, auto b) {
|
||||
if (std::isnan(a.x)) return b;
|
||||
if (std::isnan(b.x)) return a;
|
||||
return la::min(a, b);
|
||||
});
|
||||
bBox_.max = reduce(vertPos_.begin(), vertPos_.end(),
|
||||
vec3(-std::numeric_limits<double>::infinity()),
|
||||
[](auto a, auto b) {
|
||||
if (std::isnan(a.x)) return b;
|
||||
if (std::isnan(b.x)) return a;
|
||||
return la::max(a, b);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if all verts are finite. Checking just the bounding box dimensions
|
||||
* is insufficient as it ignores NaNs.
|
||||
*/
|
||||
bool Manifold::Impl::IsFinite() const {
|
||||
return transform_reduce(
|
||||
vertPos_.begin(), vertPos_.end(), true,
|
||||
[](bool a, bool b) { return a && b; },
|
||||
[](auto v) { return la::all(la::isfinite(v)); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the input triVerts array has all indices inside bounds of the
|
||||
* vertPos_ array.
|
||||
*/
|
||||
bool Manifold::Impl::IsIndexInBounds(VecView<const ivec3> triVerts) const {
|
||||
ivec2 minmax = transform_reduce(
|
||||
triVerts.begin(), triVerts.end(),
|
||||
ivec2(std::numeric_limits<int>::max(), std::numeric_limits<int>::min()),
|
||||
[](auto a, auto b) {
|
||||
a[0] = std::min(a[0], b[0]);
|
||||
a[1] = std::max(a[1], b[1]);
|
||||
return a;
|
||||
},
|
||||
[](auto tri) {
|
||||
return ivec2(std::min(tri[0], std::min(tri[1], tri[2])),
|
||||
std::max(tri[0], std::max(tri[1], tri[2])));
|
||||
});
|
||||
|
||||
return minmax[0] >= 0 && minmax[1] < static_cast<int>(NumVert());
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the minimum gap between two manifolds. Returns a double between
|
||||
* 0 and searchLength.
|
||||
*/
|
||||
double Manifold::Impl::MinGap(const Manifold::Impl& other,
|
||||
double searchLength) const {
|
||||
ZoneScoped;
|
||||
Vec<Box> faceBoxOther;
|
||||
Vec<uint32_t> faceMortonOther;
|
||||
|
||||
other.GetFaceBoxMorton(faceBoxOther, faceMortonOther);
|
||||
|
||||
transform(faceBoxOther.begin(), faceBoxOther.end(), faceBoxOther.begin(),
|
||||
[searchLength](const Box& box) {
|
||||
return Box(box.min - vec3(searchLength),
|
||||
box.max + vec3(searchLength));
|
||||
});
|
||||
|
||||
SparseIndices collisions = collider_.Collisions(faceBoxOther.cview());
|
||||
|
||||
double minDistanceSquared = transform_reduce(
|
||||
countAt(0_uz), countAt(collisions.size()), searchLength * searchLength,
|
||||
[](double a, double b) { return std::min(a, b); },
|
||||
[&collisions, this, &other](int i) {
|
||||
const int tri = collisions.Get(i, 1);
|
||||
const int triOther = collisions.Get(i, 0);
|
||||
|
||||
std::array<vec3, 3> p;
|
||||
std::array<vec3, 3> q;
|
||||
|
||||
for (const int j : {0, 1, 2}) {
|
||||
p[j] = vertPos_[halfedge_[3 * tri + j].startVert];
|
||||
q[j] = other.vertPos_[other.halfedge_[3 * triOther + j].startVert];
|
||||
}
|
||||
|
||||
return DistanceTriangleTriangleSquared(p, q);
|
||||
});
|
||||
|
||||
return sqrt(minDistanceSquared);
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
860
thirdparty/manifold/src/quickhull.cpp
vendored
Normal file
860
thirdparty/manifold/src/quickhull.cpp
vendored
Normal file
@@ -0,0 +1,860 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Derived from the public domain work of Antti Kuukka at
|
||||
// https://github.com/akuukka/quickhull
|
||||
|
||||
#include "quickhull.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include "./impl.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
double defaultEps() { return 0.0000001; }
|
||||
|
||||
inline double getSquaredDistanceBetweenPointAndRay(const vec3& p,
|
||||
const Ray& r) {
|
||||
const vec3 s = p - r.S;
|
||||
double t = la::dot(s, r.V);
|
||||
return la::dot(s, s) - t * t * r.VInvLengthSquared;
|
||||
}
|
||||
|
||||
inline double getSquaredDistance(const vec3& p1, const vec3& p2) {
|
||||
return la::dot(p1 - p2, p1 - p2);
|
||||
}
|
||||
// Note that the unit of distance returned is relative to plane's normal's
|
||||
// length (divide by N.getNormalized() if needed to get the "real" distance).
|
||||
inline double getSignedDistanceToPlane(const vec3& v, const Plane& p) {
|
||||
return la::dot(p.N, v) + p.D;
|
||||
}
|
||||
|
||||
inline vec3 getTriangleNormal(const vec3& a, const vec3& b, const vec3& c) {
|
||||
// We want to get (a-c).crossProduct(b-c) without constructing temp vectors
|
||||
double x = a.x - c.x;
|
||||
double y = a.y - c.y;
|
||||
double z = a.z - c.z;
|
||||
double rhsx = b.x - c.x;
|
||||
double rhsy = b.y - c.y;
|
||||
double rhsz = b.z - c.z;
|
||||
double px = y * rhsz - z * rhsy;
|
||||
double py = z * rhsx - x * rhsz;
|
||||
double pz = x * rhsy - y * rhsx;
|
||||
return la::normalize(vec3(px, py, pz));
|
||||
}
|
||||
|
||||
size_t MeshBuilder::addFace() {
|
||||
if (disabledFaces.size()) {
|
||||
size_t index = disabledFaces.back();
|
||||
auto& f = faces[index];
|
||||
DEBUG_ASSERT(f.isDisabled(), logicErr, "f should be disabled");
|
||||
DEBUG_ASSERT(!f.pointsOnPositiveSide, logicErr,
|
||||
"f should not be on the positive side");
|
||||
f.mostDistantPointDist = 0;
|
||||
disabledFaces.pop_back();
|
||||
return index;
|
||||
}
|
||||
faces.emplace_back();
|
||||
return faces.size() - 1;
|
||||
}
|
||||
|
||||
size_t MeshBuilder::addHalfedge() {
|
||||
if (disabledHalfedges.size()) {
|
||||
const size_t index = disabledHalfedges.back();
|
||||
disabledHalfedges.pop_back();
|
||||
return index;
|
||||
}
|
||||
halfedges.push_back({});
|
||||
halfedgeToFace.push_back(0);
|
||||
halfedgeNext.push_back(0);
|
||||
return halfedges.size() - 1;
|
||||
}
|
||||
|
||||
void MeshBuilder::setup(int a, int b, int c, int d) {
|
||||
faces.clear();
|
||||
halfedges.clear();
|
||||
halfedgeToFace.clear();
|
||||
halfedgeNext.clear();
|
||||
disabledFaces.clear();
|
||||
disabledHalfedges.clear();
|
||||
|
||||
faces.reserve(4);
|
||||
halfedges.reserve(12);
|
||||
|
||||
// Create halfedges
|
||||
// AB
|
||||
halfedges.push_back({0, b, 6});
|
||||
halfedgeToFace.push_back(0);
|
||||
halfedgeNext.push_back(1);
|
||||
// BC
|
||||
halfedges.push_back({0, c, 9});
|
||||
halfedgeToFace.push_back(0);
|
||||
halfedgeNext.push_back(2);
|
||||
// CA
|
||||
halfedges.push_back({0, a, 3});
|
||||
halfedgeToFace.push_back(0);
|
||||
halfedgeNext.push_back(0);
|
||||
// AC
|
||||
halfedges.push_back({0, c, 2});
|
||||
halfedgeToFace.push_back(1);
|
||||
halfedgeNext.push_back(4);
|
||||
// CD
|
||||
halfedges.push_back({0, d, 11});
|
||||
halfedgeToFace.push_back(1);
|
||||
halfedgeNext.push_back(5);
|
||||
// DA
|
||||
halfedges.push_back({0, a, 7});
|
||||
halfedgeToFace.push_back(1);
|
||||
halfedgeNext.push_back(3);
|
||||
// BA
|
||||
halfedges.push_back({0, a, 0});
|
||||
halfedgeToFace.push_back(2);
|
||||
halfedgeNext.push_back(7);
|
||||
// AD
|
||||
halfedges.push_back({0, d, 5});
|
||||
halfedgeToFace.push_back(2);
|
||||
halfedgeNext.push_back(8);
|
||||
// DB
|
||||
halfedges.push_back({0, b, 10});
|
||||
halfedgeToFace.push_back(2);
|
||||
halfedgeNext.push_back(6);
|
||||
// CB
|
||||
halfedges.push_back({0, b, 1});
|
||||
halfedgeToFace.push_back(3);
|
||||
halfedgeNext.push_back(10);
|
||||
// BD
|
||||
halfedges.push_back({0, d, 8});
|
||||
halfedgeToFace.push_back(3);
|
||||
halfedgeNext.push_back(11);
|
||||
// DC
|
||||
halfedges.push_back({0, c, 4});
|
||||
halfedgeToFace.push_back(3);
|
||||
halfedgeNext.push_back(9);
|
||||
|
||||
// Create faces
|
||||
faces.emplace_back(0);
|
||||
faces.emplace_back(3);
|
||||
faces.emplace_back(6);
|
||||
faces.emplace_back(9);
|
||||
}
|
||||
|
||||
std::array<int, 3> MeshBuilder::getVertexIndicesOfFace(const Face& f) const {
|
||||
std::array<int, 3> v;
|
||||
size_t index = f.he;
|
||||
auto* he = &halfedges[index];
|
||||
v[0] = he->endVert;
|
||||
|
||||
index = halfedgeNext[index];
|
||||
he = &halfedges[index];
|
||||
v[1] = he->endVert;
|
||||
|
||||
index = halfedgeNext[index];
|
||||
he = &halfedges[index];
|
||||
v[2] = he->endVert;
|
||||
return v;
|
||||
}
|
||||
|
||||
HalfEdgeMesh::HalfEdgeMesh(const MeshBuilder& builderObject,
|
||||
const VecView<vec3>& vertexData) {
|
||||
std::unordered_map<size_t, size_t> faceMapping;
|
||||
std::unordered_map<size_t, size_t> halfEdgeMapping;
|
||||
std::unordered_map<size_t, size_t> vertexMapping;
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto& face : builderObject.faces) {
|
||||
if (!face.isDisabled()) {
|
||||
halfEdgeIndexFaces.emplace_back(static_cast<size_t>(face.he));
|
||||
faceMapping[i] = halfEdgeIndexFaces.size() - 1;
|
||||
|
||||
const auto heIndices = builderObject.getHalfEdgeIndicesOfFace(face);
|
||||
for (const auto heIndex : heIndices) {
|
||||
const auto vertexIndex = builderObject.halfedges[heIndex].endVert;
|
||||
if (vertexMapping.count(vertexIndex) == 0) {
|
||||
vertices.push_back(vertexData[vertexIndex]);
|
||||
vertexMapping[vertexIndex] = vertices.size() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (const auto& halfEdge : builderObject.halfedges) {
|
||||
if (halfEdge.pairedHalfedge != -1) {
|
||||
halfedges.push_back({halfEdge.endVert, halfEdge.pairedHalfedge,
|
||||
builderObject.halfedgeToFace[i]});
|
||||
halfedgeToFace.push_back(builderObject.halfedgeToFace[i]);
|
||||
halfedgeNext.push_back(builderObject.halfedgeNext[i]);
|
||||
halfEdgeMapping[i] = halfedges.size() - 1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
for (auto& halfEdgeIndexFace : halfEdgeIndexFaces) {
|
||||
DEBUG_ASSERT(halfEdgeMapping.count(halfEdgeIndexFace) == 1, logicErr,
|
||||
"invalid halfedge mapping");
|
||||
halfEdgeIndexFace = halfEdgeMapping[halfEdgeIndexFace];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < halfedges.size(); i++) {
|
||||
auto& he = halfedges[i];
|
||||
halfedgeToFace[i] = faceMapping[halfedgeToFace[i]];
|
||||
he.pairedHalfedge = halfEdgeMapping[he.pairedHalfedge];
|
||||
halfedgeNext[i] = halfEdgeMapping[halfedgeNext[i]];
|
||||
he.endVert = vertexMapping[he.endVert];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of the algorithm
|
||||
*/
|
||||
std::pair<Vec<Halfedge>, Vec<vec3>> QuickHull::buildMesh(double epsilon) {
|
||||
if (originalVertexData.size() == 0) {
|
||||
return {Vec<Halfedge>(), Vec<vec3>()};
|
||||
}
|
||||
|
||||
// Very first: find extreme values and use them to compute the scale of the
|
||||
// point cloud.
|
||||
extremeValues = getExtremeValues();
|
||||
scale = getScale(extremeValues);
|
||||
|
||||
// Epsilon we use depends on the scale
|
||||
m_epsilon = epsilon * scale;
|
||||
epsilonSquared = m_epsilon * m_epsilon;
|
||||
|
||||
// The planar case happens when all the points appear to lie on a two
|
||||
// dimensional subspace of R^3.
|
||||
planar = false;
|
||||
createConvexHalfedgeMesh();
|
||||
if (planar) {
|
||||
const int extraPointIndex = planarPointCloudTemp.size() - 1;
|
||||
for (auto& he : mesh.halfedges) {
|
||||
if (he.endVert == extraPointIndex) {
|
||||
he.endVert = 0;
|
||||
}
|
||||
}
|
||||
planarPointCloudTemp.clear();
|
||||
}
|
||||
|
||||
// reorder halfedges
|
||||
Vec<Halfedge> halfedges(mesh.halfedges.size());
|
||||
Vec<int> halfedgeToFace(mesh.halfedges.size());
|
||||
Vec<int> counts(mesh.halfedges.size(), 0);
|
||||
Vec<int> mapping(mesh.halfedges.size());
|
||||
Vec<int> faceMap(mesh.faces.size());
|
||||
|
||||
// Some faces are disabled and should not go into the halfedge vector, we can
|
||||
// update the face indices of the halfedges at the end using index/3
|
||||
int j = 0;
|
||||
for_each(
|
||||
autoPolicy(mesh.halfedges.size()), countAt(0_uz),
|
||||
countAt(mesh.halfedges.size()), [&](size_t i) {
|
||||
if (mesh.halfedges[i].pairedHalfedge < 0) return;
|
||||
if (mesh.faces[mesh.halfedgeToFace[i]].isDisabled()) return;
|
||||
if (AtomicAdd(counts[mesh.halfedgeToFace[i]], 1) > 0) return;
|
||||
int currIndex = AtomicAdd(j, 3);
|
||||
mapping[i] = currIndex;
|
||||
halfedges[currIndex + 0] = mesh.halfedges[i];
|
||||
halfedgeToFace[currIndex + 0] = mesh.halfedgeToFace[i];
|
||||
|
||||
size_t k = mesh.halfedgeNext[i];
|
||||
mapping[k] = currIndex + 1;
|
||||
halfedges[currIndex + 1] = mesh.halfedges[k];
|
||||
halfedgeToFace[currIndex + 1] = mesh.halfedgeToFace[k];
|
||||
|
||||
k = mesh.halfedgeNext[k];
|
||||
mapping[k] = currIndex + 2;
|
||||
halfedges[currIndex + 2] = mesh.halfedges[k];
|
||||
halfedgeToFace[currIndex + 2] = mesh.halfedgeToFace[k];
|
||||
halfedges[currIndex + 0].startVert = halfedges[currIndex + 2].endVert;
|
||||
halfedges[currIndex + 1].startVert = halfedges[currIndex + 0].endVert;
|
||||
halfedges[currIndex + 2].startVert = halfedges[currIndex + 1].endVert;
|
||||
});
|
||||
halfedges.resize(j);
|
||||
halfedgeToFace.resize(j);
|
||||
// fix pairedHalfedge id
|
||||
for_each(
|
||||
autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(),
|
||||
[&](Halfedge& he) { he.pairedHalfedge = mapping[he.pairedHalfedge]; });
|
||||
counts.resize(originalVertexData.size() + 1);
|
||||
fill(counts.begin(), counts.end(), 0);
|
||||
|
||||
// remove unused vertices
|
||||
for_each(autoPolicy(halfedges.size() / 3), countAt(0_uz),
|
||||
countAt(halfedges.size() / 3), [&](size_t i) {
|
||||
AtomicAdd(counts[halfedges[3 * i].startVert], 1);
|
||||
AtomicAdd(counts[halfedges[3 * i + 1].startVert], 1);
|
||||
AtomicAdd(counts[halfedges[3 * i + 2].startVert], 1);
|
||||
});
|
||||
auto saturate = [](int c) { return c > 0 ? 1 : 0; };
|
||||
exclusive_scan(TransformIterator(counts.begin(), saturate),
|
||||
TransformIterator(counts.end(), saturate), counts.begin(), 0);
|
||||
Vec<vec3> vertices(counts.back());
|
||||
for_each(autoPolicy(originalVertexData.size()), countAt(0_uz),
|
||||
countAt(originalVertexData.size()), [&](size_t i) {
|
||||
if (counts[i + 1] - counts[i] > 0) {
|
||||
vertices[counts[i]] = originalVertexData[i];
|
||||
}
|
||||
});
|
||||
for_each(autoPolicy(halfedges.size()), halfedges.begin(), halfedges.end(),
|
||||
[&](Halfedge& he) {
|
||||
he.startVert = counts[he.startVert];
|
||||
he.endVert = counts[he.endVert];
|
||||
});
|
||||
return {std::move(halfedges), std::move(vertices)};
|
||||
}
|
||||
|
||||
void QuickHull::createConvexHalfedgeMesh() {
|
||||
visibleFaces.clear();
|
||||
horizonEdgesData.clear();
|
||||
possiblyVisibleFaces.clear();
|
||||
|
||||
// Compute base tetrahedron
|
||||
setupInitialTetrahedron();
|
||||
DEBUG_ASSERT(mesh.faces.size() == 4, logicErr, "not a tetrahedron");
|
||||
|
||||
// Init face stack with those faces that have points assigned to them
|
||||
faceList.clear();
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
auto& f = mesh.faces[i];
|
||||
if (f.pointsOnPositiveSide && f.pointsOnPositiveSide->size() > 0) {
|
||||
faceList.push_back(i);
|
||||
f.inFaceStack = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Process faces until the face list is empty.
|
||||
size_t iter = 0;
|
||||
while (!faceList.empty()) {
|
||||
iter++;
|
||||
if (iter == std::numeric_limits<size_t>::max()) {
|
||||
// Visible face traversal marks visited faces with iteration counter (to
|
||||
// mark that the face has been visited on this iteration) and the max
|
||||
// value represents unvisited faces. At this point we have to reset
|
||||
// iteration counter. This shouldn't be an issue on 64 bit machines.
|
||||
iter = 0;
|
||||
}
|
||||
|
||||
const auto topFaceIndex = faceList.front();
|
||||
faceList.pop_front();
|
||||
|
||||
auto& tf = mesh.faces[topFaceIndex];
|
||||
tf.inFaceStack = 0;
|
||||
|
||||
DEBUG_ASSERT(
|
||||
!tf.pointsOnPositiveSide || tf.pointsOnPositiveSide->size() > 0,
|
||||
logicErr, "there should be points on the positive side");
|
||||
if (!tf.pointsOnPositiveSide || tf.isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pick the most distant point to this triangle plane as the point to which
|
||||
// we extrude
|
||||
const vec3& activePoint = originalVertexData[tf.mostDistantPoint];
|
||||
const size_t activePointIndex = tf.mostDistantPoint;
|
||||
|
||||
// Find out the faces that have our active point on their positive side
|
||||
// (these are the "visible faces"). The face on top of the stack of course
|
||||
// is one of them. At the same time, we create a list of horizon edges.
|
||||
horizonEdgesData.clear();
|
||||
possiblyVisibleFaces.clear();
|
||||
visibleFaces.clear();
|
||||
possiblyVisibleFaces.push_back({topFaceIndex, -1});
|
||||
while (possiblyVisibleFaces.size()) {
|
||||
const auto faceData = possiblyVisibleFaces.back();
|
||||
possiblyVisibleFaces.pop_back();
|
||||
auto& pvf = mesh.faces[faceData.faceIndex];
|
||||
DEBUG_ASSERT(!pvf.isDisabled(), logicErr, "pvf should not be disabled");
|
||||
|
||||
if (pvf.visibilityCheckedOnIteration == iter) {
|
||||
if (pvf.isVisibleFaceOnCurrentIteration) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
const Plane& P = pvf.P;
|
||||
pvf.visibilityCheckedOnIteration = iter;
|
||||
const double d = la::dot(P.N, activePoint) + P.D;
|
||||
if (d > 0) {
|
||||
pvf.isVisibleFaceOnCurrentIteration = 1;
|
||||
pvf.horizonEdgesOnCurrentIteration = 0;
|
||||
visibleFaces.push_back(faceData.faceIndex);
|
||||
for (auto heIndex : mesh.getHalfEdgeIndicesOfFace(pvf)) {
|
||||
if (mesh.halfedges[heIndex].pairedHalfedge !=
|
||||
faceData.enteredFromHalfedge) {
|
||||
possiblyVisibleFaces.push_back(
|
||||
{mesh.halfedgeToFace[mesh.halfedges[heIndex].pairedHalfedge],
|
||||
heIndex});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
DEBUG_ASSERT(faceData.faceIndex != topFaceIndex, logicErr,
|
||||
"face index invalid");
|
||||
}
|
||||
|
||||
// The face is not visible. Therefore, the halfedge we came from is part
|
||||
// of the horizon edge.
|
||||
pvf.isVisibleFaceOnCurrentIteration = 0;
|
||||
horizonEdgesData.push_back(faceData.enteredFromHalfedge);
|
||||
// Store which half edge is the horizon edge. The other half edges of the
|
||||
// face will not be part of the final mesh so their data slots can by
|
||||
// recycled.
|
||||
const auto halfEdgesMesh = mesh.getHalfEdgeIndicesOfFace(
|
||||
mesh.faces[mesh.halfedgeToFace[faceData.enteredFromHalfedge]]);
|
||||
const std::int8_t ind =
|
||||
(halfEdgesMesh[0] == faceData.enteredFromHalfedge)
|
||||
? 0
|
||||
: (halfEdgesMesh[1] == faceData.enteredFromHalfedge ? 1 : 2);
|
||||
mesh.faces[mesh.halfedgeToFace[faceData.enteredFromHalfedge]]
|
||||
.horizonEdgesOnCurrentIteration |= (1 << ind);
|
||||
}
|
||||
const size_t horizonEdgeCount = horizonEdgesData.size();
|
||||
|
||||
// Order horizon edges so that they form a loop. This may fail due to
|
||||
// numerical instability in which case we give up trying to solve horizon
|
||||
// edge for this point and accept a minor degeneration in the convex hull.
|
||||
if (!reorderHorizonEdges(horizonEdgesData)) {
|
||||
failedHorizonEdges++;
|
||||
int change_flag = 0;
|
||||
for (size_t index = 0; index < tf.pointsOnPositiveSide->size(); index++) {
|
||||
if ((*tf.pointsOnPositiveSide)[index] == activePointIndex) {
|
||||
change_flag = 1;
|
||||
} else if (change_flag == 1) {
|
||||
change_flag = 2;
|
||||
(*tf.pointsOnPositiveSide)[index - 1] =
|
||||
(*tf.pointsOnPositiveSide)[index];
|
||||
}
|
||||
}
|
||||
if (change_flag == 1)
|
||||
tf.pointsOnPositiveSide->resize(tf.pointsOnPositiveSide->size() - 1);
|
||||
|
||||
if (tf.pointsOnPositiveSide->size() == 0) {
|
||||
reclaimToIndexVectorPool(tf.pointsOnPositiveSide);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Except for the horizon edges, all half edges of the visible faces can be
|
||||
// marked as disabled. Their data slots will be reused. The faces will be
|
||||
// disabled as well, but we need to remember the points that were on the
|
||||
// positive side of them - therefore we save pointers to them.
|
||||
newFaceIndices.clear();
|
||||
newHalfedgeIndices.clear();
|
||||
disabledFacePointVectors.clear();
|
||||
size_t disableCounter = 0;
|
||||
for (auto faceIndex : visibleFaces) {
|
||||
auto& disabledFace = mesh.faces[faceIndex];
|
||||
auto halfEdgesMesh = mesh.getHalfEdgeIndicesOfFace(disabledFace);
|
||||
for (size_t j = 0; j < 3; j++) {
|
||||
if ((disabledFace.horizonEdgesOnCurrentIteration & (1 << j)) == 0) {
|
||||
if (disableCounter < horizonEdgeCount * 2) {
|
||||
// Use on this iteration
|
||||
newHalfedgeIndices.push_back(halfEdgesMesh[j]);
|
||||
disableCounter++;
|
||||
} else {
|
||||
// Mark for reusal on later iteration step
|
||||
mesh.disableHalfedge(halfEdgesMesh[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Disable the face, but retain pointer to the points that were on the
|
||||
// positive side of it. We need to assign those points to the new faces we
|
||||
// create shortly.
|
||||
auto t = mesh.disableFace(faceIndex);
|
||||
if (t) {
|
||||
// Because we should not assign point vectors to faces unless needed...
|
||||
DEBUG_ASSERT(t->size(), logicErr, "t should not be empty");
|
||||
disabledFacePointVectors.push_back(std::move(t));
|
||||
}
|
||||
}
|
||||
if (disableCounter < horizonEdgeCount * 2) {
|
||||
const size_t newHalfEdgesNeeded = horizonEdgeCount * 2 - disableCounter;
|
||||
for (size_t i = 0; i < newHalfEdgesNeeded; i++) {
|
||||
newHalfedgeIndices.push_back(mesh.addHalfedge());
|
||||
}
|
||||
}
|
||||
|
||||
// Create new faces using the edgeloop
|
||||
for (size_t i = 0; i < horizonEdgeCount; i++) {
|
||||
const size_t AB = horizonEdgesData[i];
|
||||
|
||||
auto horizonEdgeVertexIndices =
|
||||
mesh.getVertexIndicesOfHalfEdge(mesh.halfedges[AB]);
|
||||
size_t A, B, C;
|
||||
A = horizonEdgeVertexIndices[0];
|
||||
B = horizonEdgeVertexIndices[1];
|
||||
C = activePointIndex;
|
||||
|
||||
const size_t newFaceIndex = mesh.addFace();
|
||||
newFaceIndices.push_back(newFaceIndex);
|
||||
|
||||
const size_t CA = newHalfedgeIndices[2 * i + 0];
|
||||
const size_t BC = newHalfedgeIndices[2 * i + 1];
|
||||
|
||||
mesh.halfedgeNext[AB] = BC;
|
||||
mesh.halfedgeNext[BC] = CA;
|
||||
mesh.halfedgeNext[CA] = AB;
|
||||
|
||||
mesh.halfedgeToFace[BC] = newFaceIndex;
|
||||
mesh.halfedgeToFace[CA] = newFaceIndex;
|
||||
mesh.halfedgeToFace[AB] = newFaceIndex;
|
||||
|
||||
mesh.halfedges[CA].endVert = A;
|
||||
mesh.halfedges[BC].endVert = C;
|
||||
|
||||
auto& newFace = mesh.faces[newFaceIndex];
|
||||
|
||||
const vec3 planeNormal = getTriangleNormal(
|
||||
originalVertexData[A], originalVertexData[B], activePoint);
|
||||
newFace.P = Plane(planeNormal, activePoint);
|
||||
newFace.he = AB;
|
||||
|
||||
mesh.halfedges[CA].pairedHalfedge =
|
||||
newHalfedgeIndices[i > 0 ? i * 2 - 1 : 2 * horizonEdgeCount - 1];
|
||||
mesh.halfedges[BC].pairedHalfedge =
|
||||
newHalfedgeIndices[((i + 1) * 2) % (horizonEdgeCount * 2)];
|
||||
}
|
||||
|
||||
// Assign points that were on the positive side of the disabled faces to the
|
||||
// new faces.
|
||||
for (auto& disabledPoints : disabledFacePointVectors) {
|
||||
DEBUG_ASSERT(disabledPoints != nullptr, logicErr,
|
||||
"disabledPoints should not be null");
|
||||
for (const auto& point : *(disabledPoints)) {
|
||||
if (point == activePointIndex) {
|
||||
continue;
|
||||
}
|
||||
for (size_t j = 0; j < horizonEdgeCount; j++) {
|
||||
if (addPointToFace(mesh.faces[newFaceIndices[j]], point)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The points are no longer needed: we can move them to the vector pool
|
||||
// for reuse.
|
||||
reclaimToIndexVectorPool(disabledPoints);
|
||||
}
|
||||
|
||||
// Increase face stack size if needed
|
||||
for (const auto newFaceIndex : newFaceIndices) {
|
||||
auto& newFace = mesh.faces[newFaceIndex];
|
||||
if (newFace.pointsOnPositiveSide) {
|
||||
DEBUG_ASSERT(newFace.pointsOnPositiveSide->size() > 0, logicErr,
|
||||
"there should be points on the positive side");
|
||||
if (!newFace.inFaceStack) {
|
||||
faceList.push_back(newFaceIndex);
|
||||
newFace.inFaceStack = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
indexVectorPool.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
* Private helper functions
|
||||
*/
|
||||
|
||||
std::array<size_t, 6> QuickHull::getExtremeValues() {
|
||||
std::array<size_t, 6> outIndices{0, 0, 0, 0, 0, 0};
|
||||
double extremeVals[6] = {originalVertexData[0].x, originalVertexData[0].x,
|
||||
originalVertexData[0].y, originalVertexData[0].y,
|
||||
originalVertexData[0].z, originalVertexData[0].z};
|
||||
const size_t vCount = originalVertexData.size();
|
||||
for (size_t i = 1; i < vCount; i++) {
|
||||
const vec3& pos = originalVertexData[i];
|
||||
if (pos.x > extremeVals[0]) {
|
||||
extremeVals[0] = pos.x;
|
||||
outIndices[0] = i;
|
||||
} else if (pos.x < extremeVals[1]) {
|
||||
extremeVals[1] = pos.x;
|
||||
outIndices[1] = i;
|
||||
}
|
||||
if (pos.y > extremeVals[2]) {
|
||||
extremeVals[2] = pos.y;
|
||||
outIndices[2] = i;
|
||||
} else if (pos.y < extremeVals[3]) {
|
||||
extremeVals[3] = pos.y;
|
||||
outIndices[3] = i;
|
||||
}
|
||||
if (pos.z > extremeVals[4]) {
|
||||
extremeVals[4] = pos.z;
|
||||
outIndices[4] = i;
|
||||
} else if (pos.z < extremeVals[5]) {
|
||||
extremeVals[5] = pos.z;
|
||||
outIndices[5] = i;
|
||||
}
|
||||
}
|
||||
return outIndices;
|
||||
}
|
||||
|
||||
bool QuickHull::reorderHorizonEdges(VecView<size_t>& horizonEdges) {
|
||||
const size_t horizonEdgeCount = horizonEdges.size();
|
||||
for (size_t i = 0; i + 1 < horizonEdgeCount; i++) {
|
||||
const size_t endVertexCheck = mesh.halfedges[horizonEdges[i]].endVert;
|
||||
bool foundNext = false;
|
||||
for (size_t j = i + 1; j < horizonEdgeCount; j++) {
|
||||
const size_t beginVertex =
|
||||
mesh.halfedges[mesh.halfedges[horizonEdges[j]].pairedHalfedge]
|
||||
.endVert;
|
||||
if (beginVertex == endVertexCheck) {
|
||||
std::swap(horizonEdges[i + 1], horizonEdges[j]);
|
||||
foundNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundNext) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
DEBUG_ASSERT(
|
||||
mesh.halfedges[horizonEdges[horizonEdges.size() - 1]].endVert ==
|
||||
mesh.halfedges[mesh.halfedges[horizonEdges[0]].pairedHalfedge]
|
||||
.endVert,
|
||||
logicErr, "invalid halfedge");
|
||||
return true;
|
||||
}
|
||||
|
||||
double QuickHull::getScale(const std::array<size_t, 6>& extremeValuesInput) {
|
||||
double s = 0;
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
const double* v =
|
||||
(const double*)(&originalVertexData[extremeValuesInput[i]]);
|
||||
v += i / 2;
|
||||
auto a = std::abs(*v);
|
||||
if (a > s) {
|
||||
s = a;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void QuickHull::setupInitialTetrahedron() {
|
||||
const size_t vertexCount = originalVertexData.size();
|
||||
|
||||
// If we have at most 4 points, just return a degenerate tetrahedron:
|
||||
if (vertexCount <= 4) {
|
||||
size_t v[4] = {0, std::min((size_t)1, vertexCount - 1),
|
||||
std::min((size_t)2, vertexCount - 1),
|
||||
std::min((size_t)3, vertexCount - 1)};
|
||||
const vec3 N =
|
||||
getTriangleNormal(originalVertexData[v[0]], originalVertexData[v[1]],
|
||||
originalVertexData[v[2]]);
|
||||
const Plane trianglePlane(N, originalVertexData[v[0]]);
|
||||
if (trianglePlane.isPointOnPositiveSide(originalVertexData[v[3]])) {
|
||||
std::swap(v[0], v[1]);
|
||||
}
|
||||
return mesh.setup(v[0], v[1], v[2], v[3]);
|
||||
}
|
||||
|
||||
// Find two most distant extreme points.
|
||||
double maxD = epsilonSquared;
|
||||
std::pair<size_t, size_t> selectedPoints;
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
for (size_t j = i + 1; j < 6; j++) {
|
||||
// I found a function for squaredDistance but i can't seem to include it
|
||||
// like this for some reason
|
||||
const double d = getSquaredDistance(originalVertexData[extremeValues[i]],
|
||||
originalVertexData[extremeValues[j]]);
|
||||
if (d > maxD) {
|
||||
maxD = d;
|
||||
selectedPoints = {extremeValues[i], extremeValues[j]};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxD == epsilonSquared) {
|
||||
// A degenerate case: the point cloud seems to consists of a single point
|
||||
return mesh.setup(0, std::min((size_t)1, vertexCount - 1),
|
||||
std::min((size_t)2, vertexCount - 1),
|
||||
std::min((size_t)3, vertexCount - 1));
|
||||
}
|
||||
DEBUG_ASSERT(selectedPoints.first != selectedPoints.second, logicErr,
|
||||
"degenerate selectedPoints");
|
||||
|
||||
// Find the most distant point to the line between the two chosen extreme
|
||||
// points.
|
||||
const Ray r(originalVertexData[selectedPoints.first],
|
||||
(originalVertexData[selectedPoints.second] -
|
||||
originalVertexData[selectedPoints.first]));
|
||||
maxD = epsilonSquared;
|
||||
size_t maxI = std::numeric_limits<size_t>::max();
|
||||
const size_t vCount = originalVertexData.size();
|
||||
for (size_t i = 0; i < vCount; i++) {
|
||||
const double distToRay =
|
||||
getSquaredDistanceBetweenPointAndRay(originalVertexData[i], r);
|
||||
if (distToRay > maxD) {
|
||||
maxD = distToRay;
|
||||
maxI = i;
|
||||
}
|
||||
}
|
||||
if (maxD == epsilonSquared) {
|
||||
// It appears that the point cloud belongs to a 1 dimensional subspace of
|
||||
// R^3: convex hull has no volume => return a thin triangle Pick any point
|
||||
// other than selectedPoints.first and selectedPoints.second as the third
|
||||
// point of the triangle
|
||||
auto it =
|
||||
std::find_if(originalVertexData.begin(), originalVertexData.end(),
|
||||
[&](const vec3& ve) {
|
||||
return ve != originalVertexData[selectedPoints.first] &&
|
||||
ve != originalVertexData[selectedPoints.second];
|
||||
});
|
||||
const size_t thirdPoint =
|
||||
(it == originalVertexData.end())
|
||||
? selectedPoints.first
|
||||
: std::distance(originalVertexData.begin(), it);
|
||||
it =
|
||||
std::find_if(originalVertexData.begin(), originalVertexData.end(),
|
||||
[&](const vec3& ve) {
|
||||
return ve != originalVertexData[selectedPoints.first] &&
|
||||
ve != originalVertexData[selectedPoints.second] &&
|
||||
ve != originalVertexData[thirdPoint];
|
||||
});
|
||||
const size_t fourthPoint =
|
||||
(it == originalVertexData.end())
|
||||
? selectedPoints.first
|
||||
: std::distance(originalVertexData.begin(), it);
|
||||
return mesh.setup(selectedPoints.first, selectedPoints.second, thirdPoint,
|
||||
fourthPoint);
|
||||
}
|
||||
|
||||
// These three points form the base triangle for our tetrahedron.
|
||||
DEBUG_ASSERT(selectedPoints.first != maxI && selectedPoints.second != maxI,
|
||||
logicErr, "degenerate selectedPoints");
|
||||
std::array<size_t, 3> baseTriangle{selectedPoints.first,
|
||||
selectedPoints.second, maxI};
|
||||
const vec3 baseTriangleVertices[] = {originalVertexData[baseTriangle[0]],
|
||||
originalVertexData[baseTriangle[1]],
|
||||
originalVertexData[baseTriangle[2]]};
|
||||
|
||||
// Next step is to find the 4th vertex of the tetrahedron. We naturally choose
|
||||
// the point farthest away from the triangle plane.
|
||||
maxD = m_epsilon;
|
||||
maxI = 0;
|
||||
const vec3 N =
|
||||
getTriangleNormal(baseTriangleVertices[0], baseTriangleVertices[1],
|
||||
baseTriangleVertices[2]);
|
||||
Plane trianglePlane(N, baseTriangleVertices[0]);
|
||||
for (size_t i = 0; i < vCount; i++) {
|
||||
const double d = std::abs(
|
||||
getSignedDistanceToPlane(originalVertexData[i], trianglePlane));
|
||||
if (d > maxD) {
|
||||
maxD = d;
|
||||
maxI = i;
|
||||
}
|
||||
}
|
||||
if (maxD == m_epsilon) {
|
||||
// All the points seem to lie on a 2D subspace of R^3. How to handle this?
|
||||
// Well, let's add one extra point to the point cloud so that the convex
|
||||
// hull will have volume.
|
||||
planar = true;
|
||||
const vec3 N1 =
|
||||
getTriangleNormal(baseTriangleVertices[1], baseTriangleVertices[2],
|
||||
baseTriangleVertices[0]);
|
||||
planarPointCloudTemp = Vec<vec3>(originalVertexData);
|
||||
const vec3 extraPoint = N1 + originalVertexData[0];
|
||||
planarPointCloudTemp.push_back(extraPoint);
|
||||
maxI = planarPointCloudTemp.size() - 1;
|
||||
originalVertexData = planarPointCloudTemp;
|
||||
}
|
||||
|
||||
// Enforce CCW orientation (if user prefers clockwise orientation, swap two
|
||||
// vertices in each triangle when final mesh is created)
|
||||
const Plane triPlane(N, baseTriangleVertices[0]);
|
||||
if (triPlane.isPointOnPositiveSide(originalVertexData[maxI])) {
|
||||
std::swap(baseTriangle[0], baseTriangle[1]);
|
||||
}
|
||||
|
||||
// Create a tetrahedron half edge mesh and compute planes defined by each
|
||||
// triangle
|
||||
mesh.setup(baseTriangle[0], baseTriangle[1], baseTriangle[2], maxI);
|
||||
for (auto& f : mesh.faces) {
|
||||
auto v = mesh.getVertexIndicesOfFace(f);
|
||||
const vec3 N1 =
|
||||
getTriangleNormal(originalVertexData[v[0]], originalVertexData[v[1]],
|
||||
originalVertexData[v[2]]);
|
||||
const Plane plane(N1, originalVertexData[v[0]]);
|
||||
f.P = plane;
|
||||
}
|
||||
|
||||
// Finally we assign a face for each vertex outside the tetrahedron (vertices
|
||||
// inside the tetrahedron have no role anymore)
|
||||
for (size_t i = 0; i < vCount; i++) {
|
||||
for (auto& face : mesh.faces) {
|
||||
if (addPointToFace(face, i)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Vec<size_t>> QuickHull::getIndexVectorFromPool() {
|
||||
auto r = indexVectorPool.get();
|
||||
r->resize(0);
|
||||
return r;
|
||||
}
|
||||
|
||||
void QuickHull::reclaimToIndexVectorPool(std::unique_ptr<Vec<size_t>>& ptr) {
|
||||
const size_t oldSize = ptr->size();
|
||||
if ((oldSize + 1) * 128 < ptr->capacity()) {
|
||||
// Reduce memory usage! Huge vectors are needed at the beginning of
|
||||
// iteration when faces have many points on their positive side. Later on,
|
||||
// smaller vectors will suffice.
|
||||
ptr.reset(nullptr);
|
||||
return;
|
||||
}
|
||||
indexVectorPool.reclaim(ptr);
|
||||
}
|
||||
|
||||
bool QuickHull::addPointToFace(typename MeshBuilder::Face& f,
|
||||
size_t pointIndex) {
|
||||
const double D =
|
||||
getSignedDistanceToPlane(originalVertexData[pointIndex], f.P);
|
||||
if (D > 0 && D * D > epsilonSquared * f.P.sqrNLength) {
|
||||
if (!f.pointsOnPositiveSide) {
|
||||
f.pointsOnPositiveSide = getIndexVectorFromPool();
|
||||
}
|
||||
f.pointsOnPositiveSide->push_back(pointIndex);
|
||||
if (D > f.mostDistantPointDist) {
|
||||
f.mostDistantPointDist = D;
|
||||
f.mostDistantPoint = pointIndex;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wrapper to call the QuickHull algorithm with the given vertex data to build
|
||||
// the Impl
|
||||
void Manifold::Impl::Hull(VecView<vec3> vertPos) {
|
||||
size_t numVert = vertPos.size();
|
||||
if (numVert < 4) {
|
||||
status_ = Error::InvalidConstruction;
|
||||
return;
|
||||
}
|
||||
|
||||
QuickHull qh(vertPos);
|
||||
std::tie(halfedge_, vertPos_) = qh.buildMesh();
|
||||
CalculateBBox();
|
||||
SetEpsilon();
|
||||
CalculateNormals();
|
||||
InitializeOriginal();
|
||||
Finish();
|
||||
CreateFaces();
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
288
thirdparty/manifold/src/quickhull.h
vendored
Normal file
288
thirdparty/manifold/src/quickhull.h
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Derived from the public domain work of Antti Kuukka at
|
||||
// https://github.com/akuukka/quickhull
|
||||
|
||||
/*
|
||||
* INPUT: a list of points in 3D space (for example, vertices of a 3D mesh)
|
||||
*
|
||||
* OUTPUT: a ConvexHull object which provides vertex and index buffers of the
|
||||
*generated convex hull as a triangle mesh.
|
||||
*
|
||||
*
|
||||
*
|
||||
* The implementation is thread-safe if each thread is using its own QuickHull
|
||||
*object.
|
||||
*
|
||||
*
|
||||
* SUMMARY OF THE ALGORITHM:
|
||||
* - Create initial simplex (tetrahedron) using extreme points. We have
|
||||
*four faces now and they form a convex mesh M.
|
||||
* - For each point, assign them to the first face for which they are on
|
||||
*the positive side of (so each point is assigned to at most one face). Points
|
||||
*inside the initial tetrahedron are left behind now and no longer affect the
|
||||
*calculations.
|
||||
* - Add all faces that have points assigned to them to Face Stack.
|
||||
* - Iterate until Face Stack is empty:
|
||||
* - Pop topmost face F from the stack
|
||||
* - From the points assigned to F, pick the point P that is
|
||||
*farthest away from the plane defined by F.
|
||||
* - Find all faces of M that have P on their positive side. Let us
|
||||
*call these the "visible faces".
|
||||
* - Because of the way M is constructed, these faces are
|
||||
*connected. Solve their horizon edge loop.
|
||||
* - "Extrude to P": Create new faces by connecting
|
||||
*P with the points belonging to the horizon edge. Add the new faces to M and
|
||||
*remove the visible faces from M.
|
||||
* - Each point that was assigned to visible faces is now assigned
|
||||
*to at most one of the newly created faces.
|
||||
* - Those new faces that have points assigned to them are added to
|
||||
*the top of Face Stack.
|
||||
* - M is now the convex hull.
|
||||
*
|
||||
* */
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#include "./shared.h"
|
||||
#include "./vec.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
class Pool {
|
||||
std::vector<std::unique_ptr<Vec<size_t>>> data;
|
||||
|
||||
public:
|
||||
void clear() { data.clear(); }
|
||||
|
||||
void reclaim(std::unique_ptr<Vec<size_t>>& ptr) {
|
||||
data.push_back(std::move(ptr));
|
||||
}
|
||||
|
||||
std::unique_ptr<Vec<size_t>> get() {
|
||||
if (data.size() == 0) {
|
||||
return std::make_unique<Vec<size_t>>();
|
||||
}
|
||||
auto it = data.end() - 1;
|
||||
std::unique_ptr<Vec<size_t>> r = std::move(*it);
|
||||
data.erase(it);
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
class Plane {
|
||||
public:
|
||||
vec3 N;
|
||||
|
||||
// Signed distance (if normal is of length 1) to the plane from origin
|
||||
double D;
|
||||
|
||||
// Normal length squared
|
||||
double sqrNLength;
|
||||
|
||||
bool isPointOnPositiveSide(const vec3& Q) const {
|
||||
double d = la::dot(N, Q) + D;
|
||||
if (d >= 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
Plane() = default;
|
||||
|
||||
// Construct a plane using normal N and any point P on the plane
|
||||
Plane(const vec3& N, const vec3& P)
|
||||
: N(N), D(la::dot(-N, P)), sqrNLength(la::dot(N, N)) {}
|
||||
};
|
||||
|
||||
struct Ray {
|
||||
const vec3 S;
|
||||
const vec3 V;
|
||||
const double VInvLengthSquared;
|
||||
|
||||
Ray(const vec3& S, const vec3& V)
|
||||
: S(S), V(V), VInvLengthSquared(1 / (la::dot(V, V))) {}
|
||||
};
|
||||
|
||||
class MeshBuilder {
|
||||
public:
|
||||
struct Face {
|
||||
int he;
|
||||
Plane P{};
|
||||
double mostDistantPointDist = 0.0;
|
||||
size_t mostDistantPoint = 0;
|
||||
size_t visibilityCheckedOnIteration = 0;
|
||||
std::uint8_t isVisibleFaceOnCurrentIteration : 1;
|
||||
std::uint8_t inFaceStack : 1;
|
||||
// Bit for each half edge assigned to this face, each being 0 or 1 depending
|
||||
// on whether the edge belongs to horizon edge
|
||||
std::uint8_t horizonEdgesOnCurrentIteration : 3;
|
||||
std::unique_ptr<Vec<size_t>> pointsOnPositiveSide;
|
||||
|
||||
Face(size_t he)
|
||||
: he(he),
|
||||
isVisibleFaceOnCurrentIteration(0),
|
||||
inFaceStack(0),
|
||||
horizonEdgesOnCurrentIteration(0) {}
|
||||
|
||||
Face()
|
||||
: he(-1),
|
||||
isVisibleFaceOnCurrentIteration(0),
|
||||
inFaceStack(0),
|
||||
horizonEdgesOnCurrentIteration(0) {}
|
||||
|
||||
void disable() { he = -1; }
|
||||
|
||||
bool isDisabled() const { return he == -1; }
|
||||
};
|
||||
|
||||
// Mesh data
|
||||
std::vector<Face> faces;
|
||||
Vec<Halfedge> halfedges;
|
||||
Vec<int> halfedgeToFace;
|
||||
Vec<int> halfedgeNext;
|
||||
|
||||
// When the mesh is modified and faces and half edges are removed from it, we
|
||||
// do not actually remove them from the container vectors. Insted, they are
|
||||
// marked as disabled which means that the indices can be reused when we need
|
||||
// to add new faces and half edges to the mesh. We store the free indices in
|
||||
// the following vectors.
|
||||
Vec<size_t> disabledFaces, disabledHalfedges;
|
||||
|
||||
size_t addFace();
|
||||
|
||||
size_t addHalfedge();
|
||||
|
||||
// Mark a face as disabled and return a pointer to the points that were on the
|
||||
// positive of it.
|
||||
std::unique_ptr<Vec<size_t>> disableFace(size_t faceIndex) {
|
||||
auto& f = faces[faceIndex];
|
||||
f.disable();
|
||||
disabledFaces.push_back(faceIndex);
|
||||
return std::move(f.pointsOnPositiveSide);
|
||||
}
|
||||
|
||||
void disableHalfedge(size_t heIndex) {
|
||||
auto& he = halfedges[heIndex];
|
||||
he.pairedHalfedge = -1;
|
||||
disabledHalfedges.push_back(heIndex);
|
||||
}
|
||||
|
||||
MeshBuilder() = default;
|
||||
|
||||
// Create a mesh with initial tetrahedron ABCD. Dot product of AB with the
|
||||
// normal of triangle ABC should be negative.
|
||||
void setup(int a, int b, int c, int d);
|
||||
|
||||
std::array<int, 3> getVertexIndicesOfFace(const Face& f) const;
|
||||
|
||||
std::array<int, 2> getVertexIndicesOfHalfEdge(const Halfedge& he) const {
|
||||
return {halfedges[he.pairedHalfedge].endVert, he.endVert};
|
||||
}
|
||||
|
||||
std::array<int, 3> getHalfEdgeIndicesOfFace(const Face& f) const {
|
||||
return {f.he, halfedgeNext[f.he], halfedgeNext[halfedgeNext[f.he]]};
|
||||
}
|
||||
};
|
||||
|
||||
class HalfEdgeMesh {
|
||||
public:
|
||||
Vec<vec3> vertices;
|
||||
// Index of one of the half edges of the faces
|
||||
std::vector<size_t> halfEdgeIndexFaces;
|
||||
Vec<Halfedge> halfedges;
|
||||
Vec<int> halfedgeToFace;
|
||||
Vec<int> halfedgeNext;
|
||||
|
||||
HalfEdgeMesh(const MeshBuilder& builderObject,
|
||||
const VecView<vec3>& vertexData);
|
||||
};
|
||||
|
||||
double defaultEps();
|
||||
|
||||
class QuickHull {
|
||||
struct FaceData {
|
||||
int faceIndex;
|
||||
// If the face turns out not to be visible, this half edge will be marked as
|
||||
// horizon edge
|
||||
int enteredFromHalfedge;
|
||||
};
|
||||
|
||||
double m_epsilon, epsilonSquared, scale;
|
||||
bool planar;
|
||||
Vec<vec3> planarPointCloudTemp;
|
||||
VecView<vec3> originalVertexData;
|
||||
MeshBuilder mesh;
|
||||
std::array<size_t, 6> extremeValues;
|
||||
size_t failedHorizonEdges = 0;
|
||||
|
||||
// Temporary variables used during iteration process
|
||||
Vec<size_t> newFaceIndices;
|
||||
Vec<size_t> newHalfedgeIndices;
|
||||
Vec<size_t> visibleFaces;
|
||||
Vec<size_t> horizonEdgesData;
|
||||
Vec<FaceData> possiblyVisibleFaces;
|
||||
std::vector<std::unique_ptr<Vec<size_t>>> disabledFacePointVectors;
|
||||
std::deque<int> faceList;
|
||||
|
||||
// Create a half edge mesh representing the base tetrahedron from which the
|
||||
// QuickHull iteration proceeds. extremeValues must be properly set up when
|
||||
// this is called.
|
||||
void setupInitialTetrahedron();
|
||||
|
||||
// Given a list of half edges, try to rearrange them so that they form a loop.
|
||||
// Return true on success.
|
||||
bool reorderHorizonEdges(VecView<size_t>& horizonEdges);
|
||||
|
||||
// Find indices of extreme values (max x, min x, max y, min y, max z, min z)
|
||||
// for the given point cloud
|
||||
std::array<size_t, 6> getExtremeValues();
|
||||
|
||||
// Compute scale of the vertex data.
|
||||
double getScale(const std::array<size_t, 6>& extremeValuesInput);
|
||||
|
||||
// Each face contains a unique pointer to a vector of indices. However, many -
|
||||
// often most - faces do not have any points on the positive side of them
|
||||
// especially at the the end of the iteration. When a face is removed from the
|
||||
// mesh, its associated point vector, if such exists, is moved to the index
|
||||
// vector pool, and when we need to add new faces with points on the positive
|
||||
// side to the mesh, we reuse these vectors. This reduces the amount of
|
||||
// std::vectors we have to deal with, and impact on performance is remarkable.
|
||||
Pool indexVectorPool;
|
||||
inline std::unique_ptr<Vec<size_t>> getIndexVectorFromPool();
|
||||
inline void reclaimToIndexVectorPool(std::unique_ptr<Vec<size_t>>& ptr);
|
||||
|
||||
// Associates a point with a face if the point resides on the positive side of
|
||||
// the plane. Returns true if the points was on the positive side.
|
||||
inline bool addPointToFace(typename MeshBuilder::Face& f, size_t pointIndex);
|
||||
|
||||
// This will create HalfedgeMesh from which we create the ConvexHull object
|
||||
// that buildMesh function returns
|
||||
void createConvexHalfedgeMesh();
|
||||
|
||||
public:
|
||||
// This function assumes that the pointCloudVec data resides in memory in the
|
||||
// following format: x_0,y_0,z_0,x_1,y_1,z_1,...
|
||||
QuickHull(VecView<vec3> pointCloudVec)
|
||||
: originalVertexData(VecView(pointCloudVec)) {}
|
||||
|
||||
// Computes convex hull for a given point cloud. Params: eps: minimum distance
|
||||
// to a plane to consider a point being on positive side of it (for a point
|
||||
// cloud with scale 1) Returns: Convex hull of the point cloud as halfEdge
|
||||
// vector and vertex vector
|
||||
std::pair<Vec<Halfedge>, Vec<vec3>> buildMesh(double eps = defaultEps());
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
533
thirdparty/manifold/src/sdf.cpp
vendored
Normal file
533
thirdparty/manifold/src/sdf.cpp
vendored
Normal file
@@ -0,0 +1,533 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "./hashtable.h"
|
||||
#include "./impl.h"
|
||||
#include "./parallel.h"
|
||||
#include "./utils.h"
|
||||
#include "./vec.h"
|
||||
#include "manifold/manifold.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
constexpr int kCrossing = -2;
|
||||
constexpr int kNone = -1;
|
||||
constexpr ivec4 kVoxelOffset(1, 1, 1, 0);
|
||||
// Maximum fraction of spacing that a vert can move.
|
||||
constexpr double kS = 0.25;
|
||||
// Corresponding approximate distance ratio bound.
|
||||
constexpr double kD = 1 / kS - 1;
|
||||
// Maximum number of opposed verts (of 7) to allow collapse.
|
||||
constexpr int kMaxOpposed = 3;
|
||||
|
||||
ivec3 TetTri0(int i) {
|
||||
constexpr ivec3 tetTri0[16] = {{-1, -1, -1}, //
|
||||
{0, 3, 4}, //
|
||||
{0, 1, 5}, //
|
||||
{1, 5, 3}, //
|
||||
{1, 4, 2}, //
|
||||
{1, 0, 3}, //
|
||||
{2, 5, 0}, //
|
||||
{5, 3, 2}, //
|
||||
{2, 3, 5}, //
|
||||
{0, 5, 2}, //
|
||||
{3, 0, 1}, //
|
||||
{2, 4, 1}, //
|
||||
{3, 5, 1}, //
|
||||
{5, 1, 0}, //
|
||||
{4, 3, 0}, //
|
||||
{-1, -1, -1}};
|
||||
return tetTri0[i];
|
||||
}
|
||||
|
||||
ivec3 TetTri1(int i) {
|
||||
constexpr ivec3 tetTri1[16] = {{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{3, 4, 1}, //
|
||||
{-1, -1, -1}, //
|
||||
{3, 2, 1}, //
|
||||
{0, 4, 2}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{2, 4, 0}, //
|
||||
{1, 2, 3}, //
|
||||
{-1, -1, -1}, //
|
||||
{1, 4, 3}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}};
|
||||
return tetTri1[i];
|
||||
}
|
||||
|
||||
ivec4 Neighbor(ivec4 base, int i) {
|
||||
constexpr ivec4 neighbors[14] = {{0, 0, 0, 1}, //
|
||||
{1, 0, 0, 0}, //
|
||||
{0, 1, 0, 0}, //
|
||||
{0, 0, 1, 0}, //
|
||||
{-1, 0, 0, 1}, //
|
||||
{0, -1, 0, 1}, //
|
||||
{0, 0, -1, 1}, //
|
||||
{-1, -1, -1, 1}, //
|
||||
{-1, 0, 0, 0}, //
|
||||
{0, -1, 0, 0}, //
|
||||
{0, 0, -1, 0}, //
|
||||
{0, -1, -1, 1}, //
|
||||
{-1, 0, -1, 1}, //
|
||||
{-1, -1, 0, 1}};
|
||||
ivec4 neighborIndex = base + neighbors[i];
|
||||
if (neighborIndex.w == 2) {
|
||||
neighborIndex += 1;
|
||||
neighborIndex.w = 0;
|
||||
}
|
||||
return neighborIndex;
|
||||
}
|
||||
|
||||
Uint64 EncodeIndex(ivec4 gridPos, ivec3 gridPow) {
|
||||
return static_cast<Uint64>(gridPos.w) | static_cast<Uint64>(gridPos.z) << 1 |
|
||||
static_cast<Uint64>(gridPos.y) << (1 + gridPow.z) |
|
||||
static_cast<Uint64>(gridPos.x) << (1 + gridPow.z + gridPow.y);
|
||||
}
|
||||
|
||||
ivec4 DecodeIndex(Uint64 idx, ivec3 gridPow) {
|
||||
ivec4 gridPos;
|
||||
gridPos.w = idx & 1;
|
||||
idx = idx >> 1;
|
||||
gridPos.z = idx & ((1 << gridPow.z) - 1);
|
||||
idx = idx >> gridPow.z;
|
||||
gridPos.y = idx & ((1 << gridPow.y) - 1);
|
||||
idx = idx >> gridPow.y;
|
||||
gridPos.x = idx & ((1 << gridPow.x) - 1);
|
||||
return gridPos;
|
||||
}
|
||||
|
||||
vec3 Position(ivec4 gridIndex, vec3 origin, vec3 spacing) {
|
||||
return origin + spacing * (vec3(gridIndex) + (gridIndex.w == 1 ? 0.0 : -0.5));
|
||||
}
|
||||
|
||||
vec3 Bound(vec3 pos, vec3 origin, vec3 spacing, ivec3 gridSize) {
|
||||
return min(max(pos, origin), origin + spacing * (vec3(gridSize) - 1));
|
||||
}
|
||||
|
||||
double BoundedSDF(ivec4 gridIndex, vec3 origin, vec3 spacing, ivec3 gridSize,
|
||||
double level, std::function<double(vec3)> sdf) {
|
||||
const ivec3 xyz(gridIndex);
|
||||
const int lowerBoundDist = minelem(xyz);
|
||||
const int upperBoundDist = minelem(gridSize - xyz);
|
||||
const int boundDist = std::min(lowerBoundDist, upperBoundDist - gridIndex.w);
|
||||
|
||||
if (boundDist < 0) {
|
||||
return 0.0;
|
||||
}
|
||||
const double d = sdf(Position(gridIndex, origin, spacing)) - level;
|
||||
return boundDist == 0 ? std::min(d, 0.0) : d;
|
||||
}
|
||||
|
||||
// Simplified ITP root finding algorithm - same worst-case performance as
|
||||
// bisection, better average performance.
|
||||
inline vec3 FindSurface(vec3 pos0, double d0, vec3 pos1, double d1, double tol,
|
||||
double level, std::function<double(vec3)> sdf) {
|
||||
if (d0 == 0) {
|
||||
return pos0;
|
||||
} else if (d1 == 0) {
|
||||
return pos1;
|
||||
}
|
||||
|
||||
// Sole tuning parameter, k: (0, 1) - smaller value gets better median
|
||||
// performance, but also hits the worst case more often.
|
||||
const double k = 0.1;
|
||||
const double check = 2 * tol / la::length(pos0 - pos1);
|
||||
double frac = 1;
|
||||
double biFrac = 1;
|
||||
while (frac > check) {
|
||||
const double t = la::lerp(d0 / (d0 - d1), 0.5, k);
|
||||
const double r = biFrac / frac - 0.5;
|
||||
const double x = la::abs(t - 0.5) < r ? t : 0.5 - r * (t < 0.5 ? 1 : -1);
|
||||
|
||||
const vec3 mid = la::lerp(pos0, pos1, x);
|
||||
const double d = sdf(mid) - level;
|
||||
|
||||
if ((d > 0) == (d0 > 0)) {
|
||||
d0 = d;
|
||||
pos0 = mid;
|
||||
frac *= 1 - x;
|
||||
} else {
|
||||
d1 = d;
|
||||
pos1 = mid;
|
||||
frac *= x;
|
||||
}
|
||||
biFrac /= 2;
|
||||
}
|
||||
|
||||
return la::lerp(pos0, pos1, d0 / (d0 - d1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Each GridVert is connected to 14 others, and in charge of 7 of these edges
|
||||
* (see Neighbor() above). Each edge that changes sign contributes one vert,
|
||||
* unless the GridVert is close enough to the surface, in which case it
|
||||
* contributes only a single movedVert and all crossing edgeVerts refer to that.
|
||||
*/
|
||||
struct GridVert {
|
||||
double distance = NAN;
|
||||
int movedVert = kNone;
|
||||
int edgeVerts[7] = {kNone, kNone, kNone, kNone, kNone, kNone, kNone};
|
||||
|
||||
inline bool HasMoved() const { return movedVert >= 0; }
|
||||
|
||||
inline bool SameSide(double dist) const {
|
||||
return (dist > 0) == (distance > 0);
|
||||
}
|
||||
|
||||
inline int Inside() const { return distance > 0 ? 1 : -1; }
|
||||
|
||||
inline int NeighborInside(int i) const {
|
||||
return Inside() * (edgeVerts[i] == kNone ? 1 : -1);
|
||||
}
|
||||
};
|
||||
|
||||
struct NearSurface {
|
||||
VecView<vec3> vertPos;
|
||||
VecView<int> vertIndex;
|
||||
HashTableD<GridVert> gridVerts;
|
||||
VecView<const double> voxels;
|
||||
const std::function<double(vec3)> sdf;
|
||||
const vec3 origin;
|
||||
const ivec3 gridSize;
|
||||
const ivec3 gridPow;
|
||||
const vec3 spacing;
|
||||
const double level;
|
||||
const double tol;
|
||||
|
||||
inline void operator()(Uint64 index) {
|
||||
ZoneScoped;
|
||||
if (gridVerts.Full()) return;
|
||||
|
||||
const ivec4 gridIndex = DecodeIndex(index, gridPow);
|
||||
|
||||
if (la::any(la::greater(ivec3(gridIndex), gridSize))) return;
|
||||
|
||||
GridVert gridVert;
|
||||
gridVert.distance = voxels[EncodeIndex(gridIndex + kVoxelOffset, gridPow)];
|
||||
|
||||
bool keep = false;
|
||||
double vMax = 0;
|
||||
int closestNeighbor = -1;
|
||||
int opposedVerts = 0;
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
const double val =
|
||||
voxels[EncodeIndex(Neighbor(gridIndex, i) + kVoxelOffset, gridPow)];
|
||||
const double valOp = voxels[EncodeIndex(
|
||||
Neighbor(gridIndex, i + 7) + kVoxelOffset, gridPow)];
|
||||
|
||||
if (!gridVert.SameSide(val)) {
|
||||
gridVert.edgeVerts[i] = kCrossing;
|
||||
keep = true;
|
||||
if (!gridVert.SameSide(valOp)) {
|
||||
++opposedVerts;
|
||||
}
|
||||
// Approximate bound on vert movement.
|
||||
if (la::abs(val) > kD * la::abs(gridVert.distance) &&
|
||||
la::abs(val) > la::abs(vMax)) {
|
||||
vMax = val;
|
||||
closestNeighbor = i;
|
||||
}
|
||||
} else if (!gridVert.SameSide(valOp) &&
|
||||
la::abs(valOp) > kD * la::abs(gridVert.distance) &&
|
||||
la::abs(valOp) > la::abs(vMax)) {
|
||||
vMax = valOp;
|
||||
closestNeighbor = i + 7;
|
||||
}
|
||||
}
|
||||
|
||||
// This is where we collapse all the crossing edge verts into this GridVert,
|
||||
// speeding up the algorithm and avoiding poor quality triangles. Without
|
||||
// this step the result is guaranteed 2-manifold, but with this step it can
|
||||
// become an even-manifold with kissing verts. These must be removed in a
|
||||
// post-process: CleanupTopology().
|
||||
if (closestNeighbor >= 0 && opposedVerts <= kMaxOpposed) {
|
||||
const vec3 gridPos = Position(gridIndex, origin, spacing);
|
||||
const ivec4 neighborIndex = Neighbor(gridIndex, closestNeighbor);
|
||||
const vec3 pos = FindSurface(gridPos, gridVert.distance,
|
||||
Position(neighborIndex, origin, spacing),
|
||||
vMax, tol, level, sdf);
|
||||
// Bound the delta of each vert to ensure the tetrahedron cannot invert.
|
||||
if (la::all(la::less(la::abs(pos - gridPos), kS * spacing))) {
|
||||
const int idx = AtomicAdd(vertIndex[0], 1);
|
||||
vertPos[idx] = Bound(pos, origin, spacing, gridSize);
|
||||
gridVert.movedVert = idx;
|
||||
for (int j = 0; j < 7; ++j) {
|
||||
if (gridVert.edgeVerts[j] == kCrossing) gridVert.edgeVerts[j] = idx;
|
||||
}
|
||||
keep = true;
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < 7; ++j) gridVert.edgeVerts[j] = kNone;
|
||||
}
|
||||
|
||||
if (keep) gridVerts.Insert(index, gridVert);
|
||||
}
|
||||
};
|
||||
|
||||
struct ComputeVerts {
|
||||
VecView<vec3> vertPos;
|
||||
VecView<int> vertIndex;
|
||||
HashTableD<GridVert> gridVerts;
|
||||
VecView<const double> voxels;
|
||||
const std::function<double(vec3)> sdf;
|
||||
const vec3 origin;
|
||||
const ivec3 gridSize;
|
||||
const ivec3 gridPow;
|
||||
const vec3 spacing;
|
||||
const double level;
|
||||
const double tol;
|
||||
|
||||
void operator()(int idx) {
|
||||
ZoneScoped;
|
||||
Uint64 baseKey = gridVerts.KeyAt(idx);
|
||||
if (baseKey == kOpen) return;
|
||||
|
||||
GridVert& gridVert = gridVerts.At(idx);
|
||||
|
||||
if (gridVert.HasMoved()) return;
|
||||
|
||||
const ivec4 gridIndex = DecodeIndex(baseKey, gridPow);
|
||||
|
||||
const vec3 position = Position(gridIndex, origin, spacing);
|
||||
|
||||
// These seven edges are uniquely owned by this gridVert; any of them
|
||||
// which intersect the surface create a vert.
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
const ivec4 neighborIndex = Neighbor(gridIndex, i);
|
||||
const GridVert& neighbor = gridVerts[EncodeIndex(neighborIndex, gridPow)];
|
||||
|
||||
const double val =
|
||||
std::isfinite(neighbor.distance)
|
||||
? neighbor.distance
|
||||
: voxels[EncodeIndex(neighborIndex + kVoxelOffset, gridPow)];
|
||||
if (gridVert.SameSide(val)) continue;
|
||||
|
||||
if (neighbor.HasMoved()) {
|
||||
gridVert.edgeVerts[i] = neighbor.movedVert;
|
||||
continue;
|
||||
}
|
||||
|
||||
const int idx = AtomicAdd(vertIndex[0], 1);
|
||||
const vec3 pos = FindSurface(position, gridVert.distance,
|
||||
Position(neighborIndex, origin, spacing),
|
||||
val, tol, level, sdf);
|
||||
vertPos[idx] = Bound(pos, origin, spacing, gridSize);
|
||||
gridVert.edgeVerts[i] = idx;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildTris {
|
||||
VecView<ivec3> triVerts;
|
||||
VecView<int> triIndex;
|
||||
const HashTableD<GridVert> gridVerts;
|
||||
const ivec3 gridPow;
|
||||
|
||||
void CreateTri(const ivec3& tri, const int edges[6]) {
|
||||
if (tri[0] < 0) return;
|
||||
const ivec3 verts(edges[tri[0]], edges[tri[1]], edges[tri[2]]);
|
||||
if (verts[0] == verts[1] || verts[1] == verts[2] || verts[2] == verts[0])
|
||||
return;
|
||||
int idx = AtomicAdd(triIndex[0], 1);
|
||||
triVerts[idx] = verts;
|
||||
}
|
||||
|
||||
void CreateTris(const ivec4& tet, const int edges[6]) {
|
||||
const int i = (tet[0] > 0 ? 1 : 0) + (tet[1] > 0 ? 2 : 0) +
|
||||
(tet[2] > 0 ? 4 : 0) + (tet[3] > 0 ? 8 : 0);
|
||||
CreateTri(TetTri0(i), edges);
|
||||
CreateTri(TetTri1(i), edges);
|
||||
}
|
||||
|
||||
void operator()(int idx) {
|
||||
ZoneScoped;
|
||||
Uint64 baseKey = gridVerts.KeyAt(idx);
|
||||
if (baseKey == kOpen) return;
|
||||
|
||||
const GridVert& base = gridVerts.At(idx);
|
||||
const ivec4 baseIndex = DecodeIndex(baseKey, gridPow);
|
||||
|
||||
ivec4 leadIndex = baseIndex;
|
||||
if (leadIndex.w == 0)
|
||||
leadIndex.w = 1;
|
||||
else {
|
||||
leadIndex += 1;
|
||||
leadIndex.w = 0;
|
||||
}
|
||||
|
||||
// This GridVert is in charge of the 6 tetrahedra surrounding its edge in
|
||||
// the (1,1,1) direction (edge 0).
|
||||
ivec4 tet(base.NeighborInside(0), base.Inside(), -2, -2);
|
||||
ivec4 thisIndex = baseIndex;
|
||||
thisIndex.x += 1;
|
||||
|
||||
GridVert thisVert = gridVerts[EncodeIndex(thisIndex, gridPow)];
|
||||
|
||||
tet[2] = base.NeighborInside(1);
|
||||
for (const int i : {0, 1, 2}) {
|
||||
thisIndex = leadIndex;
|
||||
--thisIndex[Prev3(i)];
|
||||
// Indices take unsigned input, so check for negatives, given the
|
||||
// decrement. If negative, the vert is outside and only connected to other
|
||||
// outside verts - no edgeVerts.
|
||||
GridVert nextVert = thisIndex[Prev3(i)] < 0
|
||||
? GridVert()
|
||||
: gridVerts[EncodeIndex(thisIndex, gridPow)];
|
||||
tet[3] = base.NeighborInside(Prev3(i) + 4);
|
||||
|
||||
const int edges1[6] = {base.edgeVerts[0],
|
||||
base.edgeVerts[i + 1],
|
||||
nextVert.edgeVerts[Next3(i) + 4],
|
||||
nextVert.edgeVerts[Prev3(i) + 1],
|
||||
thisVert.edgeVerts[i + 4],
|
||||
base.edgeVerts[Prev3(i) + 4]};
|
||||
thisVert = nextVert;
|
||||
CreateTris(tet, edges1);
|
||||
|
||||
thisIndex = baseIndex;
|
||||
++thisIndex[Next3(i)];
|
||||
nextVert = gridVerts[EncodeIndex(thisIndex, gridPow)];
|
||||
tet[2] = tet[3];
|
||||
tet[3] = base.NeighborInside(Next3(i) + 1);
|
||||
|
||||
const int edges2[6] = {base.edgeVerts[0],
|
||||
edges1[5],
|
||||
thisVert.edgeVerts[i + 4],
|
||||
nextVert.edgeVerts[Next3(i) + 4],
|
||||
edges1[3],
|
||||
base.edgeVerts[Next3(i) + 1]};
|
||||
thisVert = nextVert;
|
||||
CreateTris(tet, edges2);
|
||||
|
||||
tet[2] = tet[3];
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Constructs a level-set manifold from the input Signed-Distance Function
|
||||
* (SDF). This uses a form of Marching Tetrahedra (akin to Marching
|
||||
* Cubes, but better for manifoldness). Instead of using a cubic grid, it uses a
|
||||
* body-centered cubic grid (two shifted cubic grids). These grid points are
|
||||
* snapped to the surface where possible to keep short edges from forming.
|
||||
*
|
||||
* @param sdf The signed-distance functor, containing this function signature:
|
||||
* `double operator()(vec3 point)`, which returns the
|
||||
* signed distance of a given point in R^3. Positive values are inside,
|
||||
* negative outside. There is no requirement that the function be a true
|
||||
* distance, or even continuous.
|
||||
* @param bounds An axis-aligned box that defines the extent of the grid.
|
||||
* @param edgeLength Approximate maximum edge length of the triangles in the
|
||||
* final result. This affects grid spacing, and hence has a strong effect on
|
||||
* performance.
|
||||
* @param level Extract the surface at this value of your sdf; defaults to
|
||||
* zero. You can inset your mesh by using a positive value, or outset it with a
|
||||
* negative value.
|
||||
* @param tolerance Ensure each vertex is within this distance of the true
|
||||
* surface. Defaults to -1, which will return the interpolated
|
||||
* crossing-point based on the two nearest grid points. Small positive values
|
||||
* will require more sdf evaluations per output vertex.
|
||||
* @param canParallel Parallel policies violate will crash language runtimes
|
||||
* with runtime locks that expect to not be called back by unregistered threads.
|
||||
* This allows bindings use LevelSet despite being compiled with MANIFOLD_PAR
|
||||
* active.
|
||||
*/
|
||||
Manifold Manifold::LevelSet(std::function<double(vec3)> sdf, Box bounds,
|
||||
double edgeLength, double level, double tolerance,
|
||||
bool canParallel) {
|
||||
if (tolerance <= 0) {
|
||||
tolerance = std::numeric_limits<double>::infinity();
|
||||
}
|
||||
|
||||
auto pImpl_ = std::make_shared<Impl>();
|
||||
auto& vertPos = pImpl_->vertPos_;
|
||||
|
||||
const vec3 dim = bounds.Size();
|
||||
const ivec3 gridSize(dim / edgeLength + 1.0);
|
||||
const vec3 spacing = dim / (vec3(gridSize - 1));
|
||||
|
||||
const ivec3 gridPow(la::log2(gridSize + 2) + 1);
|
||||
const Uint64 maxIndex = EncodeIndex(ivec4(gridSize + 2, 1), gridPow);
|
||||
|
||||
// Parallel policies violate will crash language runtimes with runtime locks
|
||||
// that expect to not be called back by unregistered threads. This allows
|
||||
// bindings use LevelSet despite being compiled with MANIFOLD_PAR
|
||||
// active.
|
||||
const auto pol = canParallel ? autoPolicy(maxIndex) : ExecutionPolicy::Seq;
|
||||
|
||||
const vec3 origin = bounds.min;
|
||||
Vec<double> voxels(maxIndex);
|
||||
for_each_n(
|
||||
pol, countAt(0_uz), maxIndex,
|
||||
[&voxels, sdf, level, origin, spacing, gridSize, gridPow](Uint64 idx) {
|
||||
voxels[idx] = BoundedSDF(DecodeIndex(idx, gridPow) - kVoxelOffset,
|
||||
origin, spacing, gridSize, level, sdf);
|
||||
});
|
||||
|
||||
size_t tableSize = std::min(
|
||||
2 * maxIndex, static_cast<Uint64>(10 * la::pow(maxIndex, 0.667)));
|
||||
HashTable<GridVert> gridVerts(tableSize);
|
||||
vertPos.resize(gridVerts.Size() * 7);
|
||||
|
||||
while (1) {
|
||||
Vec<int> index(1, 0);
|
||||
for_each_n(pol, countAt(0_uz), EncodeIndex(ivec4(gridSize, 1), gridPow),
|
||||
NearSurface({vertPos, index, gridVerts.D(), voxels, sdf, origin,
|
||||
gridSize, gridPow, spacing, level, tolerance}));
|
||||
|
||||
if (gridVerts.Full()) { // Resize HashTable
|
||||
const vec3 lastVert = vertPos[index[0] - 1];
|
||||
const Uint64 lastIndex =
|
||||
EncodeIndex(ivec4(ivec3((lastVert - origin) / spacing), 1), gridPow);
|
||||
const double ratio = static_cast<double>(maxIndex) / lastIndex;
|
||||
|
||||
if (ratio > 1000) // do not trust the ratio if it is too large
|
||||
tableSize *= 2;
|
||||
else
|
||||
tableSize *= ratio;
|
||||
gridVerts = HashTable<GridVert>(tableSize);
|
||||
vertPos = Vec<vec3>(gridVerts.Size() * 7);
|
||||
} else { // Success
|
||||
for_each_n(
|
||||
pol, countAt(0), gridVerts.Size(),
|
||||
ComputeVerts({vertPos, index, gridVerts.D(), voxels, sdf, origin,
|
||||
gridSize, gridPow, spacing, level, tolerance}));
|
||||
vertPos.resize(index[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Vec<ivec3> triVerts(gridVerts.Entries() * 12); // worst case
|
||||
|
||||
Vec<int> index(1, 0);
|
||||
for_each_n(pol, countAt(0), gridVerts.Size(),
|
||||
BuildTris({triVerts, index, gridVerts.D(), gridPow}));
|
||||
triVerts.resize(index[0]);
|
||||
|
||||
pImpl_->CreateHalfedges(triVerts);
|
||||
pImpl_->CleanupTopology();
|
||||
pImpl_->Finish();
|
||||
pImpl_->InitializeOriginal();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
} // namespace manifold
|
||||
219
thirdparty/manifold/src/shared.h
vendored
Normal file
219
thirdparty/manifold/src/shared.h
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "./parallel.h"
|
||||
#include "./sparse.h"
|
||||
#include "./utils.h"
|
||||
#include "./vec.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
inline vec3 SafeNormalize(vec3 v) {
|
||||
v = la::normalize(v);
|
||||
return std::isfinite(v.x) ? v : vec3(0.0);
|
||||
}
|
||||
|
||||
inline double MaxEpsilon(double minEpsilon, const Box& bBox) {
|
||||
double epsilon = std::max(minEpsilon, kPrecision * bBox.Scale());
|
||||
return std::isfinite(epsilon) ? epsilon : -1;
|
||||
}
|
||||
|
||||
inline int NextHalfedge(int current) {
|
||||
++current;
|
||||
if (current % 3 == 0) current -= 3;
|
||||
return current;
|
||||
}
|
||||
|
||||
inline mat3 NormalTransform(const mat3x4& transform) {
|
||||
return la::inverse(la::transpose(mat3(transform)));
|
||||
}
|
||||
|
||||
/**
|
||||
* By using the closest axis-aligned projection to the normal instead of a
|
||||
* projection along the normal, we avoid introducing any rounding error.
|
||||
*/
|
||||
inline mat2x3 GetAxisAlignedProjection(vec3 normal) {
|
||||
vec3 absNormal = la::abs(normal);
|
||||
double xyzMax;
|
||||
mat3x2 projection;
|
||||
if (absNormal.z > absNormal.x && absNormal.z > absNormal.y) {
|
||||
projection = mat3x2({1.0, 0.0, 0.0}, //
|
||||
{0.0, 1.0, 0.0});
|
||||
xyzMax = normal.z;
|
||||
} else if (absNormal.y > absNormal.x) {
|
||||
projection = mat3x2({0.0, 0.0, 1.0}, //
|
||||
{1.0, 0.0, 0.0});
|
||||
xyzMax = normal.y;
|
||||
} else {
|
||||
projection = mat3x2({0.0, 1.0, 0.0}, //
|
||||
{0.0, 0.0, 1.0});
|
||||
xyzMax = normal.x;
|
||||
}
|
||||
if (xyzMax < 0) projection[0] *= -1.0;
|
||||
return la::transpose(projection);
|
||||
}
|
||||
|
||||
inline vec3 GetBarycentric(const vec3& v, const mat3& triPos,
|
||||
double tolerance) {
|
||||
const mat3 edges(triPos[2] - triPos[1], triPos[0] - triPos[2],
|
||||
triPos[1] - triPos[0]);
|
||||
const vec3 d2(la::dot(edges[0], edges[0]), la::dot(edges[1], edges[1]),
|
||||
la::dot(edges[2], edges[2]));
|
||||
const int longSide = d2[0] > d2[1] && d2[0] > d2[2] ? 0
|
||||
: d2[1] > d2[2] ? 1
|
||||
: 2;
|
||||
const vec3 crossP = la::cross(edges[0], edges[1]);
|
||||
const double area2 = la::dot(crossP, crossP);
|
||||
const double tol2 = tolerance * tolerance;
|
||||
|
||||
vec3 uvw(0.0);
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const vec3 dv = v - triPos[i];
|
||||
if (la::dot(dv, dv) < tol2) {
|
||||
// Return exactly equal if within tolerance of vert.
|
||||
uvw[i] = 1;
|
||||
return uvw;
|
||||
}
|
||||
}
|
||||
|
||||
if (d2[longSide] < tol2) { // point
|
||||
return vec3(1, 0, 0);
|
||||
} else if (area2 > d2[longSide] * tol2) { // triangle
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int j = Next3(i);
|
||||
const vec3 crossPv = la::cross(edges[i], v - triPos[j]);
|
||||
const double area2v = la::dot(crossPv, crossPv);
|
||||
// Return exactly equal if within tolerance of edge.
|
||||
uvw[i] = area2v < d2[i] * tol2 ? 0 : la::dot(crossPv, crossP);
|
||||
}
|
||||
uvw /= (uvw[0] + uvw[1] + uvw[2]);
|
||||
return uvw;
|
||||
} else { // line
|
||||
const int nextV = Next3(longSide);
|
||||
const double alpha =
|
||||
la::dot(v - triPos[nextV], edges[longSide]) / d2[longSide];
|
||||
uvw[longSide] = 0;
|
||||
uvw[nextV] = 1 - alpha;
|
||||
const int lastV = Next3(nextV);
|
||||
uvw[lastV] = alpha;
|
||||
return uvw;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The fundamental component of the halfedge data structure used for storing and
|
||||
* operating on the Manifold.
|
||||
*/
|
||||
struct Halfedge {
|
||||
int startVert, endVert;
|
||||
int pairedHalfedge;
|
||||
bool IsForward() const { return startVert < endVert; }
|
||||
bool operator<(const Halfedge& other) const {
|
||||
return startVert == other.startVert ? endVert < other.endVert
|
||||
: startVert < other.startVert;
|
||||
}
|
||||
};
|
||||
|
||||
struct Barycentric {
|
||||
int tri;
|
||||
vec4 uvw;
|
||||
};
|
||||
|
||||
struct TriRef {
|
||||
/// The unique ID of the mesh instance of this triangle. If .meshID and .tri
|
||||
/// match for two triangles, then they are coplanar and came from the same
|
||||
/// face.
|
||||
int meshID;
|
||||
/// The OriginalID of the mesh this triangle came from. This ID is ideal for
|
||||
/// reapplying properties like UV coordinates to the output mesh.
|
||||
int originalID;
|
||||
/// Probably the triangle index of the original triangle this was part of:
|
||||
/// Mesh.triVerts[tri], but it's an input, so just pass it along unchanged.
|
||||
int tri;
|
||||
/// Triangles with the same face ID are coplanar.
|
||||
int faceID;
|
||||
|
||||
bool SameFace(const TriRef& other) const {
|
||||
return meshID == other.meshID && faceID == other.faceID;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a temporary edge structure which only stores edges forward and
|
||||
* references the halfedge it was created from.
|
||||
*/
|
||||
struct TmpEdge {
|
||||
int first, second, halfedgeIdx;
|
||||
|
||||
TmpEdge() {}
|
||||
TmpEdge(int start, int end, int idx) {
|
||||
first = std::min(start, end);
|
||||
second = std::max(start, end);
|
||||
halfedgeIdx = idx;
|
||||
}
|
||||
|
||||
bool operator<(const TmpEdge& other) const {
|
||||
return first == other.first ? second < other.second : first < other.first;
|
||||
}
|
||||
};
|
||||
|
||||
Vec<TmpEdge> inline CreateTmpEdges(const Vec<Halfedge>& halfedge) {
|
||||
Vec<TmpEdge> edges(halfedge.size());
|
||||
for_each_n(autoPolicy(edges.size()), countAt(0), edges.size(),
|
||||
[&edges, &halfedge](const int idx) {
|
||||
const Halfedge& half = halfedge[idx];
|
||||
edges[idx] = TmpEdge(half.startVert, half.endVert,
|
||||
half.IsForward() ? idx : -1);
|
||||
});
|
||||
|
||||
size_t numEdge =
|
||||
remove_if(edges.begin(), edges.end(),
|
||||
[](const TmpEdge& edge) { return edge.halfedgeIdx < 0; }) -
|
||||
edges.begin();
|
||||
DEBUG_ASSERT(numEdge == halfedge.size() / 2, topologyErr, "Not oriented!");
|
||||
edges.resize(numEdge);
|
||||
return edges;
|
||||
}
|
||||
|
||||
template <const bool inverted>
|
||||
struct ReindexEdge {
|
||||
VecView<const TmpEdge> edges;
|
||||
SparseIndices& indices;
|
||||
|
||||
void operator()(size_t i) {
|
||||
int& edge = indices.Get(i, inverted);
|
||||
edge = edges[edge].halfedgeIdx;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Halfedge& edge) {
|
||||
return stream << "startVert = " << edge.startVert
|
||||
<< ", endVert = " << edge.endVert
|
||||
<< ", pairedHalfedge = " << edge.pairedHalfedge;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Barycentric& bary) {
|
||||
return stream << "tri = " << bary.tri << ", uvw = " << bary.uvw;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const TriRef& ref) {
|
||||
return stream << "meshID: " << ref.meshID
|
||||
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri
|
||||
<< ", faceID: " << ref.faceID;
|
||||
}
|
||||
#endif
|
||||
} // namespace manifold
|
||||
1003
thirdparty/manifold/src/smoothing.cpp
vendored
Normal file
1003
thirdparty/manifold/src/smoothing.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
517
thirdparty/manifold/src/sort.cpp
vendored
Normal file
517
thirdparty/manifold/src/sort.cpp
vendored
Normal file
@@ -0,0 +1,517 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <atomic>
|
||||
#include <set>
|
||||
|
||||
#include "./impl.h"
|
||||
#include "./parallel.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
constexpr uint32_t kNoCode = 0xFFFFFFFFu;
|
||||
|
||||
uint32_t MortonCode(vec3 position, Box bBox) {
|
||||
// Unreferenced vertices are marked NaN, and this will sort them to the end
|
||||
// (the Morton code only uses the first 30 of 32 bits).
|
||||
if (std::isnan(position.x)) return kNoCode;
|
||||
|
||||
return Collider::MortonCode(position, bBox);
|
||||
}
|
||||
|
||||
struct Reindex {
|
||||
VecView<const int> indexInv;
|
||||
|
||||
void operator()(Halfedge& edge) {
|
||||
if (edge.startVert < 0) return;
|
||||
edge.startVert = indexInv[edge.startVert];
|
||||
edge.endVert = indexInv[edge.endVert];
|
||||
}
|
||||
};
|
||||
|
||||
struct MarkProp {
|
||||
VecView<int> keep;
|
||||
|
||||
void operator()(ivec3 triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
reinterpret_cast<std::atomic<int>*>(&keep[triProp[i]])
|
||||
->store(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ReindexProps {
|
||||
VecView<const int> old2new;
|
||||
|
||||
void operator()(ivec3& triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
triProp[i] = old2new[triProp[i]];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ReindexFace {
|
||||
VecView<Halfedge> halfedge;
|
||||
VecView<vec4> halfedgeTangent;
|
||||
VecView<const Halfedge> oldHalfedge;
|
||||
VecView<const vec4> oldHalfedgeTangent;
|
||||
VecView<const int> faceNew2Old;
|
||||
VecView<const int> faceOld2New;
|
||||
|
||||
void operator()(int newFace) {
|
||||
const int oldFace = faceNew2Old[newFace];
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int oldEdge = 3 * oldFace + i;
|
||||
Halfedge edge = oldHalfedge[oldEdge];
|
||||
const int pairedFace = edge.pairedHalfedge / 3;
|
||||
const int offset = edge.pairedHalfedge - 3 * pairedFace;
|
||||
edge.pairedHalfedge = 3 * faceOld2New[pairedFace] + offset;
|
||||
const int newEdge = 3 * newFace + i;
|
||||
halfedge[newEdge] = edge;
|
||||
if (!oldHalfedgeTangent.empty()) {
|
||||
halfedgeTangent[newEdge] = oldHalfedgeTangent[oldEdge];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Precision, typename I>
|
||||
bool MergeMeshGLP(MeshGLP<Precision, I>& mesh) {
|
||||
ZoneScoped;
|
||||
std::multiset<std::pair<int, int>> openEdges;
|
||||
|
||||
std::vector<int> merge(mesh.NumVert());
|
||||
std::iota(merge.begin(), merge.end(), 0);
|
||||
for (size_t i = 0; i < mesh.mergeFromVert.size(); ++i) {
|
||||
merge[mesh.mergeFromVert[i]] = mesh.mergeToVert[i];
|
||||
}
|
||||
|
||||
const auto numVert = mesh.NumVert();
|
||||
const auto numTri = mesh.NumTri();
|
||||
const int next[3] = {1, 2, 0};
|
||||
for (size_t tri = 0; tri < numTri; ++tri) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
auto edge = std::make_pair(merge[mesh.triVerts[3 * tri + next[i]]],
|
||||
merge[mesh.triVerts[3 * tri + i]]);
|
||||
auto it = openEdges.find(edge);
|
||||
if (it == openEdges.end()) {
|
||||
std::swap(edge.first, edge.second);
|
||||
openEdges.insert(edge);
|
||||
} else {
|
||||
openEdges.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (openEdges.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto numOpenVert = openEdges.size();
|
||||
Vec<int> openVerts(numOpenVert);
|
||||
int i = 0;
|
||||
for (const auto& edge : openEdges) {
|
||||
const int vert = edge.first;
|
||||
openVerts[i++] = vert;
|
||||
}
|
||||
|
||||
Vec<Precision> vertPropD(mesh.vertProperties);
|
||||
Box bBox;
|
||||
for (const int i : {0, 1, 2}) {
|
||||
auto iPos =
|
||||
StridedRange(vertPropD.begin() + i, vertPropD.end(), mesh.numProp);
|
||||
auto minMax = manifold::transform_reduce(
|
||||
iPos.begin(), iPos.end(),
|
||||
std::make_pair(std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::infinity()),
|
||||
[](auto a, auto b) {
|
||||
return std::make_pair(std::min(a.first, b.first),
|
||||
std::max(a.second, b.second));
|
||||
},
|
||||
[](double f) { return std::make_pair(f, f); });
|
||||
bBox.min[i] = minMax.first;
|
||||
bBox.max[i] = minMax.second;
|
||||
}
|
||||
|
||||
const double tolerance = std::max(static_cast<double>(mesh.tolerance),
|
||||
(std::is_same<Precision, float>::value
|
||||
? std::numeric_limits<float>::epsilon()
|
||||
: kPrecision) *
|
||||
bBox.Scale());
|
||||
|
||||
auto policy = autoPolicy(numOpenVert, 1e5);
|
||||
Vec<Box> vertBox(numOpenVert);
|
||||
Vec<uint32_t> vertMorton(numOpenVert);
|
||||
|
||||
for_each_n(policy, countAt(0), numOpenVert,
|
||||
[&vertMorton, &vertBox, &openVerts, &bBox, &mesh,
|
||||
tolerance](const int i) {
|
||||
int vert = openVerts[i];
|
||||
|
||||
const vec3 center(mesh.vertProperties[mesh.numProp * vert],
|
||||
mesh.vertProperties[mesh.numProp * vert + 1],
|
||||
mesh.vertProperties[mesh.numProp * vert + 2]);
|
||||
|
||||
vertBox[i].min = center - tolerance / 2.0;
|
||||
vertBox[i].max = center + tolerance / 2.0;
|
||||
|
||||
vertMorton[i] = MortonCode(center, bBox);
|
||||
});
|
||||
|
||||
Vec<int> vertNew2Old(numOpenVert);
|
||||
sequence(vertNew2Old.begin(), vertNew2Old.end());
|
||||
|
||||
stable_sort(vertNew2Old.begin(), vertNew2Old.end(),
|
||||
[&vertMorton](const int& a, const int& b) {
|
||||
return vertMorton[a] < vertMorton[b];
|
||||
});
|
||||
|
||||
Permute(vertMorton, vertNew2Old);
|
||||
Permute(vertBox, vertNew2Old);
|
||||
Permute(openVerts, vertNew2Old);
|
||||
Collider collider(vertBox, vertMorton);
|
||||
SparseIndices toMerge = collider.Collisions<true>(vertBox.cview());
|
||||
|
||||
UnionFind<> uf(numVert);
|
||||
for (size_t i = 0; i < mesh.mergeFromVert.size(); ++i) {
|
||||
uf.unionXY(static_cast<int>(mesh.mergeFromVert[i]),
|
||||
static_cast<int>(mesh.mergeToVert[i]));
|
||||
}
|
||||
for (size_t i = 0; i < toMerge.size(); ++i) {
|
||||
uf.unionXY(openVerts[toMerge.Get(i, false)],
|
||||
openVerts[toMerge.Get(i, true)]);
|
||||
}
|
||||
|
||||
mesh.mergeToVert.clear();
|
||||
mesh.mergeFromVert.clear();
|
||||
for (size_t v = 0; v < numVert; ++v) {
|
||||
const size_t mergeTo = uf.find(v);
|
||||
if (mergeTo != v) {
|
||||
mesh.mergeFromVert.push_back(v);
|
||||
mesh.mergeToVert.push_back(mergeTo);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Once halfedge_ has been filled in, this function can be called to create the
|
||||
* rest of the internal data structures. This function also removes the verts
|
||||
* and halfedges flagged for removal (NaN verts and -1 halfedges).
|
||||
*/
|
||||
void Manifold::Impl::Finish() {
|
||||
if (halfedge_.size() == 0) return;
|
||||
|
||||
CalculateBBox();
|
||||
SetEpsilon(epsilon_);
|
||||
if (!bBox_.IsFinite()) {
|
||||
// Decimated out of existence - early out.
|
||||
MarkFailure(Error::NoError);
|
||||
return;
|
||||
}
|
||||
|
||||
SortVerts();
|
||||
Vec<Box> faceBox;
|
||||
Vec<uint32_t> faceMorton;
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
SortFaces(faceBox, faceMorton);
|
||||
if (halfedge_.size() == 0) return;
|
||||
CompactProps();
|
||||
|
||||
DEBUG_ASSERT(halfedge_.size() % 6 == 0, topologyErr,
|
||||
"Not an even number of faces after sorting faces!");
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
auto MaxOrMinus = [](int a, int b) {
|
||||
return std::min(a, b) < 0 ? -1 : std::max(a, b);
|
||||
};
|
||||
int face = 0;
|
||||
Halfedge extrema = {0, 0, 0};
|
||||
for (size_t i = 0; i < halfedge_.size(); i++) {
|
||||
Halfedge e = halfedge_[i];
|
||||
if (!e.IsForward()) std::swap(e.startVert, e.endVert);
|
||||
extrema.startVert = std::min(extrema.startVert, e.startVert);
|
||||
extrema.endVert = std::min(extrema.endVert, e.endVert);
|
||||
extrema.pairedHalfedge =
|
||||
MaxOrMinus(extrema.pairedHalfedge, e.pairedHalfedge);
|
||||
face = MaxOrMinus(face, i / 3);
|
||||
}
|
||||
DEBUG_ASSERT(extrema.startVert >= 0, topologyErr,
|
||||
"Vertex index is negative!");
|
||||
DEBUG_ASSERT(extrema.endVert < static_cast<int>(NumVert()), topologyErr,
|
||||
"Vertex index exceeds number of verts!");
|
||||
DEBUG_ASSERT(extrema.pairedHalfedge >= 0, topologyErr,
|
||||
"Halfedge index is negative!");
|
||||
DEBUG_ASSERT(extrema.pairedHalfedge < 2 * static_cast<int>(NumEdge()),
|
||||
topologyErr, "Halfedge index exceeds number of halfedges!");
|
||||
DEBUG_ASSERT(face >= 0, topologyErr, "Face index is negative!");
|
||||
DEBUG_ASSERT(face < static_cast<int>(NumTri()), topologyErr,
|
||||
"Face index exceeds number of faces!");
|
||||
#endif
|
||||
|
||||
DEBUG_ASSERT(meshRelation_.triRef.size() == NumTri() ||
|
||||
meshRelation_.triRef.size() == 0,
|
||||
logicErr, "Mesh Relation doesn't fit!");
|
||||
DEBUG_ASSERT(faceNormal_.size() == NumTri() || faceNormal_.size() == 0,
|
||||
logicErr,
|
||||
"faceNormal size = " + std::to_string(faceNormal_.size()) +
|
||||
", NumTri = " + std::to_string(NumTri()));
|
||||
CalculateNormals();
|
||||
collider_ = Collider(faceBox, faceMorton);
|
||||
|
||||
DEBUG_ASSERT(Is2Manifold(), logicErr, "mesh is not 2-manifold!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the vertices according to their Morton code.
|
||||
*/
|
||||
void Manifold::Impl::SortVerts() {
|
||||
ZoneScoped;
|
||||
const auto numVert = NumVert();
|
||||
Vec<uint32_t> vertMorton(numVert);
|
||||
auto policy = autoPolicy(numVert, 1e5);
|
||||
for_each_n(policy, countAt(0), numVert, [this, &vertMorton](const int vert) {
|
||||
vertMorton[vert] = MortonCode(vertPos_[vert], bBox_);
|
||||
});
|
||||
|
||||
Vec<int> vertNew2Old(numVert);
|
||||
sequence(vertNew2Old.begin(), vertNew2Old.end());
|
||||
|
||||
stable_sort(vertNew2Old.begin(), vertNew2Old.end(),
|
||||
[&vertMorton](const int& a, const int& b) {
|
||||
return vertMorton[a] < vertMorton[b];
|
||||
});
|
||||
|
||||
ReindexVerts(vertNew2Old, numVert);
|
||||
|
||||
// Verts were flagged for removal with NaNs and assigned kNoCode to sort
|
||||
// them to the end, which allows them to be removed.
|
||||
const auto newNumVert = std::find_if(vertNew2Old.begin(), vertNew2Old.end(),
|
||||
[&vertMorton](const int vert) {
|
||||
return vertMorton[vert] == kNoCode;
|
||||
}) -
|
||||
vertNew2Old.begin();
|
||||
|
||||
vertNew2Old.resize(newNumVert);
|
||||
Permute(vertPos_, vertNew2Old);
|
||||
|
||||
if (vertNormal_.size() == numVert) {
|
||||
Permute(vertNormal_, vertNew2Old);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the halfedges to point to new vert indices based on a mapping,
|
||||
* vertNew2Old. This may be a subset, so the total number of original verts is
|
||||
* also given.
|
||||
*/
|
||||
void Manifold::Impl::ReindexVerts(const Vec<int>& vertNew2Old,
|
||||
size_t oldNumVert) {
|
||||
ZoneScoped;
|
||||
Vec<int> vertOld2New(oldNumVert);
|
||||
scatter(countAt(0), countAt(static_cast<int>(NumVert())), vertNew2Old.begin(),
|
||||
vertOld2New.begin());
|
||||
for_each(autoPolicy(oldNumVert, 1e5), halfedge_.begin(), halfedge_.end(),
|
||||
Reindex({vertOld2New}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unreferenced property verts and reindexes triProperties.
|
||||
*/
|
||||
void Manifold::Impl::CompactProps() {
|
||||
ZoneScoped;
|
||||
if (meshRelation_.numProp == 0) return;
|
||||
|
||||
const auto numVerts = meshRelation_.properties.size() / meshRelation_.numProp;
|
||||
Vec<int> keep(numVerts, 0);
|
||||
auto policy = autoPolicy(numVerts, 1e5);
|
||||
|
||||
for_each(policy, meshRelation_.triProperties.cbegin(),
|
||||
meshRelation_.triProperties.cend(), MarkProp({keep}));
|
||||
Vec<int> propOld2New(numVerts + 1, 0);
|
||||
inclusive_scan(keep.begin(), keep.end(), propOld2New.begin() + 1);
|
||||
|
||||
Vec<double> oldProp = meshRelation_.properties;
|
||||
const int numVertsNew = propOld2New[numVerts];
|
||||
const int numProp = meshRelation_.numProp;
|
||||
auto& properties = meshRelation_.properties;
|
||||
properties.resize(numProp * numVertsNew);
|
||||
for_each_n(
|
||||
policy, countAt(0), numVerts,
|
||||
[&properties, &oldProp, &propOld2New, &keep, &numProp](const int oldIdx) {
|
||||
if (keep[oldIdx] == 0) return;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
properties[propOld2New[oldIdx] * numProp + p] =
|
||||
oldProp[oldIdx * numProp + p];
|
||||
}
|
||||
});
|
||||
for_each_n(policy, meshRelation_.triProperties.begin(), NumTri(),
|
||||
ReindexProps({propOld2New}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the faceBox and faceMorton input with the bounding boxes and Morton
|
||||
* codes of the faces, respectively. The Morton code is based on the center of
|
||||
* the bounding box.
|
||||
*/
|
||||
void Manifold::Impl::GetFaceBoxMorton(Vec<Box>& faceBox,
|
||||
Vec<uint32_t>& faceMorton) const {
|
||||
ZoneScoped;
|
||||
faceBox.resize(NumTri());
|
||||
faceMorton.resize(NumTri());
|
||||
for_each_n(autoPolicy(NumTri(), 1e5), countAt(0), NumTri(),
|
||||
[this, &faceBox, &faceMorton](const int face) {
|
||||
// Removed tris are marked by all halfedges having pairedHalfedge
|
||||
// = -1, and this will sort them to the end (the Morton code only
|
||||
// uses the first 30 of 32 bits).
|
||||
if (halfedge_[3 * face].pairedHalfedge < 0) {
|
||||
faceMorton[face] = kNoCode;
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 center(0.0);
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const vec3 pos = vertPos_[halfedge_[3 * face + i].startVert];
|
||||
center += pos;
|
||||
faceBox[face].Union(pos);
|
||||
}
|
||||
center /= 3;
|
||||
|
||||
faceMorton[face] = MortonCode(center, bBox_);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the faces of this manifold according to their input Morton code. The
|
||||
* bounding box and Morton code arrays are also sorted accordingly.
|
||||
*/
|
||||
void Manifold::Impl::SortFaces(Vec<Box>& faceBox, Vec<uint32_t>& faceMorton) {
|
||||
ZoneScoped;
|
||||
Vec<int> faceNew2Old(NumTri());
|
||||
sequence(faceNew2Old.begin(), faceNew2Old.end());
|
||||
|
||||
stable_sort(faceNew2Old.begin(), faceNew2Old.end(),
|
||||
[&faceMorton](const int& a, const int& b) {
|
||||
return faceMorton[a] < faceMorton[b];
|
||||
});
|
||||
|
||||
// Tris were flagged for removal with pairedHalfedge = -1 and assigned kNoCode
|
||||
// to sort them to the end, which allows them to be removed.
|
||||
const int newNumTri = std::find_if(faceNew2Old.begin(), faceNew2Old.end(),
|
||||
[&faceMorton](const int face) {
|
||||
return faceMorton[face] == kNoCode;
|
||||
}) -
|
||||
faceNew2Old.begin();
|
||||
faceNew2Old.resize(newNumTri);
|
||||
|
||||
Permute(faceMorton, faceNew2Old);
|
||||
Permute(faceBox, faceNew2Old);
|
||||
GatherFaces(faceNew2Old);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the halfedge_ vector for this manifold by copying a set of faces from
|
||||
* another manifold, given by oldHalfedge. Input faceNew2Old defines the old
|
||||
* faces to gather into this.
|
||||
*/
|
||||
void Manifold::Impl::GatherFaces(const Vec<int>& faceNew2Old) {
|
||||
ZoneScoped;
|
||||
const auto numTri = faceNew2Old.size();
|
||||
if (meshRelation_.triRef.size() == NumTri())
|
||||
Permute(meshRelation_.triRef, faceNew2Old);
|
||||
if (meshRelation_.triProperties.size() == NumTri())
|
||||
Permute(meshRelation_.triProperties, faceNew2Old);
|
||||
if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old);
|
||||
|
||||
Vec<Halfedge> oldHalfedge(std::move(halfedge_));
|
||||
Vec<vec4> oldHalfedgeTangent(std::move(halfedgeTangent_));
|
||||
Vec<int> faceOld2New(oldHalfedge.size() / 3);
|
||||
auto policy = autoPolicy(numTri, 1e5);
|
||||
scatter(countAt(0_uz), countAt(numTri), faceNew2Old.begin(),
|
||||
faceOld2New.begin());
|
||||
|
||||
halfedge_.resize(3 * numTri);
|
||||
if (oldHalfedgeTangent.size() != 0) halfedgeTangent_.resize(3 * numTri);
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
ReindexFace({halfedge_, halfedgeTangent_, oldHalfedge,
|
||||
oldHalfedgeTangent, faceNew2Old, faceOld2New}));
|
||||
}
|
||||
|
||||
void Manifold::Impl::GatherFaces(const Impl& old, const Vec<int>& faceNew2Old) {
|
||||
ZoneScoped;
|
||||
const auto numTri = faceNew2Old.size();
|
||||
|
||||
meshRelation_.triRef.resize(numTri);
|
||||
gather(faceNew2Old.begin(), faceNew2Old.end(),
|
||||
old.meshRelation_.triRef.begin(), meshRelation_.triRef.begin());
|
||||
|
||||
for (const auto& pair : old.meshRelation_.meshIDtransform) {
|
||||
meshRelation_.meshIDtransform[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
if (old.meshRelation_.triProperties.size() > 0) {
|
||||
meshRelation_.triProperties.resize(numTri);
|
||||
gather(faceNew2Old.begin(), faceNew2Old.end(),
|
||||
old.meshRelation_.triProperties.begin(),
|
||||
meshRelation_.triProperties.begin());
|
||||
meshRelation_.numProp = old.meshRelation_.numProp;
|
||||
meshRelation_.properties = old.meshRelation_.properties;
|
||||
}
|
||||
|
||||
if (old.faceNormal_.size() == old.NumTri()) {
|
||||
faceNormal_.resize(numTri);
|
||||
gather(faceNew2Old.begin(), faceNew2Old.end(), old.faceNormal_.begin(),
|
||||
faceNormal_.begin());
|
||||
}
|
||||
|
||||
Vec<int> faceOld2New(old.NumTri());
|
||||
scatter(countAt(0_uz), countAt(numTri), faceNew2Old.begin(),
|
||||
faceOld2New.begin());
|
||||
|
||||
halfedge_.resize(3 * numTri);
|
||||
if (old.halfedgeTangent_.size() != 0) halfedgeTangent_.resize(3 * numTri);
|
||||
for_each_n(autoPolicy(numTri, 1e5), countAt(0), numTri,
|
||||
ReindexFace({halfedge_, halfedgeTangent_, old.halfedge_,
|
||||
old.halfedgeTangent_, faceNew2Old, faceOld2New}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the mergeFromVert and mergeToVert vectors in order to create a
|
||||
* manifold solid. If the MeshGL is already manifold, no change will occur and
|
||||
* the function will return false. Otherwise, this will merge verts along open
|
||||
* edges within tolerance (the maximum of the MeshGL tolerance and the
|
||||
* baseline bounding-box tolerance), keeping any from the existing merge
|
||||
* vectors, and return true.
|
||||
*
|
||||
* There is no guarantee the result will be manifold - this is a best-effort
|
||||
* helper function designed primarily to aid in the case where a manifold
|
||||
* multi-material MeshGL was produced, but its merge vectors were lost due to
|
||||
* a round-trip through a file format. Constructing a Manifold from the result
|
||||
* will report an error status if it is not manifold.
|
||||
*/
|
||||
template <>
|
||||
bool MeshGL::Merge() {
|
||||
return MergeMeshGLP(*this);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool MeshGL64::Merge() {
|
||||
return MergeMeshGLP(*this);
|
||||
}
|
||||
} // namespace manifold
|
||||
225
thirdparty/manifold/src/sparse.h
vendored
Normal file
225
thirdparty/manifold/src/sparse.h
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "./parallel.h"
|
||||
#include "./utils.h"
|
||||
#include "./vec.h"
|
||||
#include "manifold/common.h"
|
||||
#include "manifold/optional_assert.h"
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
inline bool FirstFinite(T v) {
|
||||
return std::isfinite(v[0]);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool FirstFinite<double>(double v) {
|
||||
return std::isfinite(v);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
class SparseIndices {
|
||||
// sparse indices where {p1: q1, p2: q2, ...} are laid out as
|
||||
// p1 q1 p2 q2 or q1 p1 q2 p2, depending on endianness
|
||||
// such that the indices are sorted by (p << 32) | q
|
||||
public:
|
||||
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \
|
||||
defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || \
|
||||
defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || \
|
||||
defined(__MIBSEB__)
|
||||
static constexpr size_t pOffset = 0;
|
||||
#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \
|
||||
defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \
|
||||
defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \
|
||||
defined(__MIPSEL) || defined(__MIPSEL__) || defined(__EMSCRIPTEN__) || \
|
||||
defined(_WIN32)
|
||||
static constexpr size_t pOffset = 1;
|
||||
#else
|
||||
#error "unknown architecture"
|
||||
#endif
|
||||
static constexpr int64_t EncodePQ(int p, int q) {
|
||||
return (int64_t(p) << 32) | q;
|
||||
}
|
||||
|
||||
SparseIndices() = default;
|
||||
SparseIndices(size_t size) { data_ = Vec<char>(size * sizeof(int64_t)); }
|
||||
|
||||
void Clear() { data_.clear(false); }
|
||||
|
||||
void FromIndices(const std::vector<SparseIndices>& indices) {
|
||||
std::vector<size_t> sizes;
|
||||
size_t total_size = 0;
|
||||
for (const auto& ind : indices) {
|
||||
sizes.push_back(total_size);
|
||||
total_size += ind.data_.size();
|
||||
}
|
||||
data_ = Vec<char>(total_size);
|
||||
for_each_n(ExecutionPolicy::Par, countAt(0), indices.size(), [&](size_t i) {
|
||||
std::copy(indices[i].data_.begin(), indices[i].data_.end(),
|
||||
data_.begin() + sizes[i]);
|
||||
});
|
||||
}
|
||||
|
||||
size_t size() const { return data_.size() / sizeof(int64_t); }
|
||||
|
||||
Vec<int> Copy(bool use_q) const {
|
||||
Vec<int> out(size());
|
||||
size_t offset = pOffset;
|
||||
if (use_q) offset = 1 - offset;
|
||||
const int* p = ptr();
|
||||
for_each(autoPolicy(out.size()), countAt(0_uz), countAt(out.size()),
|
||||
[&](size_t i) { out[i] = p[i * 2 + offset]; });
|
||||
return out;
|
||||
}
|
||||
|
||||
void Sort() {
|
||||
VecView<int64_t> view = AsVec64();
|
||||
stable_sort(view.begin(), view.end());
|
||||
}
|
||||
|
||||
void Resize(size_t size) { data_.resize(size * sizeof(int64_t), -1); }
|
||||
|
||||
inline int& Get(size_t i, bool use_q) {
|
||||
if (use_q)
|
||||
return ptr()[2 * i + 1 - pOffset];
|
||||
else
|
||||
return ptr()[2 * i + pOffset];
|
||||
}
|
||||
|
||||
inline int Get(size_t i, bool use_q) const {
|
||||
if (use_q)
|
||||
return ptr()[2 * i + 1 - pOffset];
|
||||
else
|
||||
return ptr()[2 * i + pOffset];
|
||||
}
|
||||
|
||||
inline int64_t GetPQ(size_t i) const {
|
||||
VecView<const int64_t> view = AsVec64();
|
||||
return view[i];
|
||||
}
|
||||
|
||||
inline void Set(size_t i, int p, int q) {
|
||||
VecView<int64_t> view = AsVec64();
|
||||
view[i] = EncodePQ(p, q);
|
||||
}
|
||||
|
||||
inline void SetPQ(size_t i, int64_t pq) {
|
||||
VecView<int64_t> view = AsVec64();
|
||||
view[i] = pq;
|
||||
}
|
||||
|
||||
VecView<int64_t> AsVec64() {
|
||||
return VecView<int64_t>(reinterpret_cast<int64_t*>(data_.data()),
|
||||
data_.size() / sizeof(int64_t));
|
||||
}
|
||||
|
||||
VecView<const int64_t> AsVec64() const {
|
||||
return VecView<const int64_t>(
|
||||
reinterpret_cast<const int64_t*>(data_.data()),
|
||||
data_.size() / sizeof(int64_t));
|
||||
}
|
||||
|
||||
VecView<int32_t> AsVec32() {
|
||||
return VecView<int32_t>(reinterpret_cast<int32_t*>(data_.data()),
|
||||
data_.size() / sizeof(int32_t));
|
||||
}
|
||||
|
||||
VecView<const int32_t> AsVec32() const {
|
||||
return VecView<const int32_t>(
|
||||
reinterpret_cast<const int32_t*>(data_.data()),
|
||||
data_.size() / sizeof(int32_t));
|
||||
}
|
||||
|
||||
inline void Add(int p, int q, bool seq = false) {
|
||||
data_.extend(sizeof(int64_t), seq);
|
||||
Set(size() - 1, p, q);
|
||||
}
|
||||
|
||||
void Unique() {
|
||||
Sort();
|
||||
VecView<int64_t> view = AsVec64();
|
||||
size_t newSize = unique(view.begin(), view.end()) - view.begin();
|
||||
Resize(newSize);
|
||||
}
|
||||
|
||||
size_t RemoveZeros(Vec<int>& S) {
|
||||
DEBUG_ASSERT(S.size() == size(), userErr,
|
||||
"Different number of values than indicies!");
|
||||
|
||||
Vec<size_t> new2Old(S.size());
|
||||
sequence(new2Old.begin(), new2Old.end());
|
||||
|
||||
size_t size = copy_if(countAt(0_uz), countAt(S.size()), new2Old.begin(),
|
||||
[&S](const size_t i) { return S[i] != 0; }) -
|
||||
new2Old.begin();
|
||||
new2Old.resize(size);
|
||||
|
||||
Permute(S, new2Old);
|
||||
Vec<char> tmp(std::move(data_));
|
||||
Resize(size);
|
||||
gather(new2Old.begin(), new2Old.end(),
|
||||
reinterpret_cast<int64_t*>(tmp.data()),
|
||||
reinterpret_cast<int64_t*>(data_.data()));
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t KeepFinite(Vec<T>& v, Vec<int>& x) {
|
||||
DEBUG_ASSERT(x.size() == size(), userErr,
|
||||
"Different number of values than indicies!");
|
||||
|
||||
Vec<int> new2Old(v.size());
|
||||
size_t size = copy_if(countAt(0_uz), countAt(v.size()), new2Old.begin(),
|
||||
[&v](size_t i) { return FirstFinite(v[i]); }) -
|
||||
new2Old.begin();
|
||||
new2Old.resize(size);
|
||||
|
||||
Permute(v, new2Old);
|
||||
Permute(x, new2Old);
|
||||
Vec<char> tmp(std::move(data_));
|
||||
Resize(size);
|
||||
gather(new2Old.begin(), new2Old.end(),
|
||||
reinterpret_cast<int64_t*>(tmp.data()),
|
||||
reinterpret_cast<int64_t*>(data_.data()));
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
void Dump() const {
|
||||
std::cout << "SparseIndices = " << std::endl;
|
||||
const int* p = ptr();
|
||||
for (size_t i = 0; i < size(); ++i) {
|
||||
std::cout << i << ", p = " << Get(i, false) << ", q = " << Get(i, true)
|
||||
<< std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
Vec<char> data_;
|
||||
inline int* ptr() { return reinterpret_cast<int32_t*>(data_.data()); }
|
||||
inline const int* ptr() const {
|
||||
return reinterpret_cast<const int32_t*>(data_.data());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
809
thirdparty/manifold/src/subdivision.cpp
vendored
Normal file
809
thirdparty/manifold/src/subdivision.cpp
vendored
Normal file
@@ -0,0 +1,809 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "./impl.h"
|
||||
#include "./parallel.h"
|
||||
|
||||
template <>
|
||||
struct std::hash<manifold::ivec4> {
|
||||
size_t operator()(const manifold::ivec4& p) const {
|
||||
return std::hash<int>()(p.x) ^ std::hash<int>()(p.y) ^
|
||||
std::hash<int>()(p.z) ^ std::hash<int>()(p.w);
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
class Partition {
|
||||
public:
|
||||
// The cached partitions don't have idx - it's added to the copy returned
|
||||
// from GetPartition that contains the mapping of the input divisions into the
|
||||
// sorted divisions that are uniquely cached.
|
||||
ivec4 idx;
|
||||
ivec4 sortedDivisions;
|
||||
Vec<vec4> vertBary;
|
||||
Vec<ivec3> triVert;
|
||||
|
||||
int InteriorOffset() const {
|
||||
return sortedDivisions[0] + sortedDivisions[1] + sortedDivisions[2] +
|
||||
sortedDivisions[3];
|
||||
}
|
||||
|
||||
int NumInterior() const { return vertBary.size() - InteriorOffset(); }
|
||||
|
||||
static Partition GetPartition(ivec4 divisions) {
|
||||
if (divisions[0] == 0) return Partition(); // skip wrong side of quad
|
||||
|
||||
ivec4 sortedDiv = divisions;
|
||||
ivec4 triIdx = {0, 1, 2, 3};
|
||||
if (divisions[3] == 0) { // triangle
|
||||
if (sortedDiv[2] > sortedDiv[1]) {
|
||||
std::swap(sortedDiv[2], sortedDiv[1]);
|
||||
std::swap(triIdx[2], triIdx[1]);
|
||||
}
|
||||
if (sortedDiv[1] > sortedDiv[0]) {
|
||||
std::swap(sortedDiv[1], sortedDiv[0]);
|
||||
std::swap(triIdx[1], triIdx[0]);
|
||||
if (sortedDiv[2] > sortedDiv[1]) {
|
||||
std::swap(sortedDiv[2], sortedDiv[1]);
|
||||
std::swap(triIdx[2], triIdx[1]);
|
||||
}
|
||||
}
|
||||
} else { // quad
|
||||
int minIdx = 0;
|
||||
int min = divisions[minIdx];
|
||||
int next = divisions[1];
|
||||
for (const int i : {1, 2, 3}) {
|
||||
const int n = divisions[(i + 1) % 4];
|
||||
if (divisions[i] < min || (divisions[i] == min && n < next)) {
|
||||
minIdx = i;
|
||||
min = divisions[i];
|
||||
next = n;
|
||||
}
|
||||
}
|
||||
// Backwards (mirrored) quads get a separate cache key for now for
|
||||
// simplicity, so there is no reversal necessary for quads when
|
||||
// re-indexing.
|
||||
ivec4 tmp = sortedDiv;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
triIdx[i] = (i + minIdx) % 4;
|
||||
sortedDiv[i] = tmp[triIdx[i]];
|
||||
}
|
||||
}
|
||||
|
||||
Partition partition = GetCachedPartition(sortedDiv);
|
||||
partition.idx = triIdx;
|
||||
|
||||
return partition;
|
||||
}
|
||||
|
||||
Vec<ivec3> Reindex(ivec4 triVerts, ivec4 edgeOffsets, bvec4 edgeFwd,
|
||||
int interiorOffset) const {
|
||||
Vec<int> newVerts;
|
||||
newVerts.reserve(vertBary.size());
|
||||
ivec4 triIdx = idx;
|
||||
ivec4 outTri = {0, 1, 2, 3};
|
||||
if (triVerts[3] < 0 && idx[1] != Next3(idx[0])) {
|
||||
triIdx = {idx[2], idx[0], idx[1], idx[3]};
|
||||
edgeFwd = !edgeFwd;
|
||||
std::swap(outTri[0], outTri[1]);
|
||||
}
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
if (triVerts[triIdx[i]] >= 0) newVerts.push_back(triVerts[triIdx[i]]);
|
||||
}
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
const int n = sortedDivisions[i] - 1;
|
||||
int offset = edgeOffsets[idx[i]] + (edgeFwd[idx[i]] ? 0 : n - 1);
|
||||
for (int j = 0; j < n; ++j) {
|
||||
newVerts.push_back(offset);
|
||||
offset += edgeFwd[idx[i]] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
const int offset = interiorOffset - newVerts.size();
|
||||
size_t old = newVerts.size();
|
||||
newVerts.resize(vertBary.size());
|
||||
std::iota(newVerts.begin() + old, newVerts.end(), old + offset);
|
||||
|
||||
const int numTri = triVert.size();
|
||||
Vec<ivec3> newTriVert(numTri);
|
||||
for_each_n(autoPolicy(numTri), countAt(0), numTri,
|
||||
[&newTriVert, &outTri, &newVerts, this](const int tri) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
newTriVert[tri][outTri[j]] = newVerts[triVert[tri][j]];
|
||||
}
|
||||
});
|
||||
return newTriVert;
|
||||
}
|
||||
|
||||
private:
|
||||
static inline auto cacheLock = std::mutex();
|
||||
static inline auto cache =
|
||||
std::unordered_map<ivec4, std::unique_ptr<Partition>>();
|
||||
|
||||
// This triangulation is purely topological - it depends only on the number of
|
||||
// divisions of the three sides of the triangle. This allows them to be cached
|
||||
// and reused for similar triangles. The shape of the final surface is defined
|
||||
// by the tangents and the barycentric coordinates of the new verts. For
|
||||
// triangles, the input must be sorted: n[0] >= n[1] >= n[2] > 0.
|
||||
static Partition GetCachedPartition(ivec4 n) {
|
||||
{
|
||||
auto lockGuard = std::lock_guard<std::mutex>(cacheLock);
|
||||
auto cached = cache.find(n);
|
||||
if (cached != cache.end()) {
|
||||
return *cached->second;
|
||||
}
|
||||
}
|
||||
Partition partition;
|
||||
partition.sortedDivisions = n;
|
||||
if (n[3] > 0) { // quad
|
||||
partition.vertBary.push_back({1, 0, 0, 0});
|
||||
partition.vertBary.push_back({0, 1, 0, 0});
|
||||
partition.vertBary.push_back({0, 0, 1, 0});
|
||||
partition.vertBary.push_back({0, 0, 0, 1});
|
||||
ivec4 edgeOffsets;
|
||||
edgeOffsets[0] = 4;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
if (i > 0) {
|
||||
edgeOffsets[i] = edgeOffsets[i - 1] + n[i - 1] - 1;
|
||||
}
|
||||
const vec4 nextBary = partition.vertBary[(i + 1) % 4];
|
||||
for (int j = 1; j < n[i]; ++j) {
|
||||
partition.vertBary.push_back(
|
||||
la::lerp(partition.vertBary[i], nextBary, (double)j / n[i]));
|
||||
}
|
||||
}
|
||||
PartitionQuad(partition.triVert, partition.vertBary, {0, 1, 2, 3},
|
||||
edgeOffsets, n - 1, {true, true, true, true});
|
||||
} else { // tri
|
||||
partition.vertBary.push_back({1, 0, 0, 0});
|
||||
partition.vertBary.push_back({0, 1, 0, 0});
|
||||
partition.vertBary.push_back({0, 0, 1, 0});
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const vec4 nextBary = partition.vertBary[(i + 1) % 3];
|
||||
for (int j = 1; j < n[i]; ++j) {
|
||||
partition.vertBary.push_back(
|
||||
la::lerp(partition.vertBary[i], nextBary, (double)j / n[i]));
|
||||
}
|
||||
}
|
||||
const ivec3 edgeOffsets = {3, 3 + n[0] - 1, 3 + n[0] - 1 + n[1] - 1};
|
||||
|
||||
const double f = n[2] * n[2] + n[0] * n[0];
|
||||
if (n[1] == 1) {
|
||||
if (n[0] == 1) {
|
||||
partition.triVert.push_back({0, 1, 2});
|
||||
} else {
|
||||
PartitionFan(partition.triVert, {0, 1, 2}, n[0] - 1, edgeOffsets[0]);
|
||||
}
|
||||
} else if (n[1] * n[1] > f - std::sqrt(2.0) * n[0] * n[2]) { // acute-ish
|
||||
partition.triVert.push_back({edgeOffsets[1] - 1, 1, edgeOffsets[1]});
|
||||
PartitionQuad(partition.triVert, partition.vertBary,
|
||||
{edgeOffsets[1] - 1, edgeOffsets[1], 2, 0},
|
||||
{-1, edgeOffsets[1] + 1, edgeOffsets[2], edgeOffsets[0]},
|
||||
{0, n[1] - 2, n[2] - 1, n[0] - 2},
|
||||
{true, true, true, true});
|
||||
} else { // obtuse -> spit into two acute
|
||||
// portion of n[0] under n[2]
|
||||
const int ns =
|
||||
std::min(n[0] - 2, (int)std::round((f - n[1] * n[1]) / (2 * n[0])));
|
||||
// height from n[0]: nh <= n[2]
|
||||
const int nh =
|
||||
std::max(1., std::round(std::sqrt(n[2] * n[2] - ns * ns)));
|
||||
|
||||
const int hOffset = partition.vertBary.size();
|
||||
const vec4 middleBary = partition.vertBary[edgeOffsets[0] + ns - 1];
|
||||
for (int j = 1; j < nh; ++j) {
|
||||
partition.vertBary.push_back(
|
||||
la::lerp(partition.vertBary[2], middleBary, (double)j / nh));
|
||||
}
|
||||
|
||||
partition.triVert.push_back({edgeOffsets[1] - 1, 1, edgeOffsets[1]});
|
||||
PartitionQuad(
|
||||
partition.triVert, partition.vertBary,
|
||||
{edgeOffsets[1] - 1, edgeOffsets[1], 2, edgeOffsets[0] + ns - 1},
|
||||
{-1, edgeOffsets[1] + 1, hOffset, edgeOffsets[0] + ns},
|
||||
{0, n[1] - 2, nh - 1, n[0] - ns - 2}, {true, true, true, true});
|
||||
|
||||
if (n[2] == 1) {
|
||||
PartitionFan(partition.triVert, {0, edgeOffsets[0] + ns - 1, 2},
|
||||
ns - 1, edgeOffsets[0]);
|
||||
} else {
|
||||
if (ns == 1) {
|
||||
partition.triVert.push_back({hOffset, 2, edgeOffsets[2]});
|
||||
PartitionQuad(partition.triVert, partition.vertBary,
|
||||
{hOffset, edgeOffsets[2], 0, edgeOffsets[0]},
|
||||
{-1, edgeOffsets[2] + 1, -1, hOffset + nh - 2},
|
||||
{0, n[2] - 2, ns - 1, nh - 2},
|
||||
{true, true, true, false});
|
||||
} else {
|
||||
partition.triVert.push_back({hOffset - 1, 0, edgeOffsets[0]});
|
||||
PartitionQuad(
|
||||
partition.triVert, partition.vertBary,
|
||||
{hOffset - 1, edgeOffsets[0], edgeOffsets[0] + ns - 1, 2},
|
||||
{-1, edgeOffsets[0] + 1, hOffset + nh - 2, edgeOffsets[2]},
|
||||
{0, ns - 2, nh - 1, n[2] - 2}, {true, true, false, true});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto lockGuard = std::lock_guard<std::mutex>(cacheLock);
|
||||
cache.insert({n, std::make_unique<Partition>(partition)});
|
||||
return partition;
|
||||
}
|
||||
|
||||
// Side 0 has added edges while sides 1 and 2 do not. Fan spreads from vert 2.
|
||||
static void PartitionFan(Vec<ivec3>& triVert, ivec3 cornerVerts, int added,
|
||||
int edgeOffset) {
|
||||
int last = cornerVerts[0];
|
||||
for (int i = 0; i < added; ++i) {
|
||||
const int next = edgeOffset + i;
|
||||
triVert.push_back({last, next, cornerVerts[2]});
|
||||
last = next;
|
||||
}
|
||||
triVert.push_back({last, cornerVerts[1], cornerVerts[2]});
|
||||
}
|
||||
|
||||
// Partitions are parallel to the first edge unless two consecutive edgeAdded
|
||||
// are zero, in which case a terminal triangulation is performed.
|
||||
static void PartitionQuad(Vec<ivec3>& triVert, Vec<vec4>& vertBary,
|
||||
ivec4 cornerVerts, ivec4 edgeOffsets,
|
||||
ivec4 edgeAdded, bvec4 edgeFwd) {
|
||||
auto GetEdgeVert = [&](int edge, int idx) {
|
||||
return edgeOffsets[edge] + (edgeFwd[edge] ? 1 : -1) * idx;
|
||||
};
|
||||
|
||||
DEBUG_ASSERT(la::all(la::gequal(edgeAdded, ivec4(0))), logicErr,
|
||||
"negative divisions!");
|
||||
|
||||
int corner = -1;
|
||||
int last = 3;
|
||||
int maxEdge = -1;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
if (corner == -1 && edgeAdded[i] == 0 && edgeAdded[last] == 0) {
|
||||
corner = i;
|
||||
}
|
||||
if (edgeAdded[i] > 0) {
|
||||
maxEdge = maxEdge == -1 ? i : -2;
|
||||
}
|
||||
last = i;
|
||||
}
|
||||
if (corner >= 0) { // terminate
|
||||
if (maxEdge >= 0) {
|
||||
ivec4 edge = (ivec4(0, 1, 2, 3) + maxEdge) % 4;
|
||||
const int middle = edgeAdded[maxEdge] / 2;
|
||||
triVert.push_back({cornerVerts[edge[2]], cornerVerts[edge[3]],
|
||||
GetEdgeVert(maxEdge, middle)});
|
||||
int last = cornerVerts[edge[0]];
|
||||
for (int i = 0; i <= middle; ++i) {
|
||||
const int next = GetEdgeVert(maxEdge, i);
|
||||
triVert.push_back({cornerVerts[edge[3]], last, next});
|
||||
last = next;
|
||||
}
|
||||
last = cornerVerts[edge[1]];
|
||||
for (int i = edgeAdded[maxEdge] - 1; i >= middle; --i) {
|
||||
const int next = GetEdgeVert(maxEdge, i);
|
||||
triVert.push_back({cornerVerts[edge[2]], next, last});
|
||||
last = next;
|
||||
}
|
||||
} else {
|
||||
int sideVert = cornerVerts[0]; // initial value is unused
|
||||
for (const int j : {1, 2}) {
|
||||
const int side = (corner + j) % 4;
|
||||
if (j == 2 && edgeAdded[side] > 0) {
|
||||
triVert.push_back(
|
||||
{cornerVerts[side], GetEdgeVert(side, 0), sideVert});
|
||||
} else {
|
||||
sideVert = cornerVerts[side];
|
||||
}
|
||||
for (int i = 0; i < edgeAdded[side]; ++i) {
|
||||
const int nextVert = GetEdgeVert(side, i);
|
||||
triVert.push_back({cornerVerts[corner], sideVert, nextVert});
|
||||
sideVert = nextVert;
|
||||
}
|
||||
if (j == 2 || edgeAdded[side] == 0) {
|
||||
triVert.push_back({cornerVerts[corner], sideVert,
|
||||
cornerVerts[(corner + j + 1) % 4]});
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// recursively partition
|
||||
const int partitions = 1 + std::min(edgeAdded[1], edgeAdded[3]);
|
||||
ivec4 newCornerVerts = {cornerVerts[1], -1, -1, cornerVerts[0]};
|
||||
ivec4 newEdgeOffsets = {edgeOffsets[1], -1,
|
||||
GetEdgeVert(3, edgeAdded[3] + 1), edgeOffsets[0]};
|
||||
ivec4 newEdgeAdded = {0, -1, 0, edgeAdded[0]};
|
||||
bvec4 newEdgeFwd = {edgeFwd[1], true, edgeFwd[3], edgeFwd[0]};
|
||||
|
||||
for (int i = 1; i < partitions; ++i) {
|
||||
const int cornerOffset1 = (edgeAdded[1] * i) / partitions;
|
||||
const int cornerOffset3 =
|
||||
edgeAdded[3] - 1 - (edgeAdded[3] * i) / partitions;
|
||||
const int nextOffset1 = GetEdgeVert(1, cornerOffset1 + 1);
|
||||
const int nextOffset3 = GetEdgeVert(3, cornerOffset3 + 1);
|
||||
const int added = std::round(la::lerp(
|
||||
(double)edgeAdded[0], (double)edgeAdded[2], (double)i / partitions));
|
||||
|
||||
newCornerVerts[1] = GetEdgeVert(1, cornerOffset1);
|
||||
newCornerVerts[2] = GetEdgeVert(3, cornerOffset3);
|
||||
newEdgeAdded[0] = std::abs(nextOffset1 - newEdgeOffsets[0]) - 1;
|
||||
newEdgeAdded[1] = added;
|
||||
newEdgeAdded[2] = std::abs(nextOffset3 - newEdgeOffsets[2]) - 1;
|
||||
newEdgeOffsets[1] = vertBary.size();
|
||||
newEdgeOffsets[2] = nextOffset3;
|
||||
|
||||
for (int j = 0; j < added; ++j) {
|
||||
vertBary.push_back(la::lerp(vertBary[newCornerVerts[1]],
|
||||
vertBary[newCornerVerts[2]],
|
||||
(j + 1.0) / (added + 1.0)));
|
||||
}
|
||||
|
||||
PartitionQuad(triVert, vertBary, newCornerVerts, newEdgeOffsets,
|
||||
newEdgeAdded, newEdgeFwd);
|
||||
|
||||
newCornerVerts[0] = newCornerVerts[1];
|
||||
newCornerVerts[3] = newCornerVerts[2];
|
||||
newEdgeAdded[3] = newEdgeAdded[1];
|
||||
newEdgeOffsets[0] = nextOffset1;
|
||||
newEdgeOffsets[3] = newEdgeOffsets[1] + newEdgeAdded[1] - 1;
|
||||
newEdgeFwd[3] = false;
|
||||
}
|
||||
|
||||
newCornerVerts[1] = cornerVerts[2];
|
||||
newCornerVerts[2] = cornerVerts[3];
|
||||
newEdgeOffsets[1] = edgeOffsets[2];
|
||||
newEdgeAdded[0] =
|
||||
edgeAdded[1] - std::abs(newEdgeOffsets[0] - edgeOffsets[1]);
|
||||
newEdgeAdded[1] = edgeAdded[2];
|
||||
newEdgeAdded[2] = std::abs(newEdgeOffsets[2] - edgeOffsets[3]) - 1;
|
||||
newEdgeOffsets[2] = edgeOffsets[3];
|
||||
newEdgeFwd[1] = edgeFwd[2];
|
||||
|
||||
PartitionQuad(triVert, vertBary, newCornerVerts, newEdgeOffsets,
|
||||
newEdgeAdded, newEdgeFwd);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Returns the tri side index (0-2) connected to the other side of this quad if
|
||||
* this tri is part of a quad, or -1 otherwise.
|
||||
*/
|
||||
int Manifold::Impl::GetNeighbor(int tri) const {
|
||||
int neighbor = -1;
|
||||
for (const int i : {0, 1, 2}) {
|
||||
if (IsMarkedInsideQuad(3 * tri + i)) {
|
||||
neighbor = neighbor == -1 ? i : -2;
|
||||
}
|
||||
}
|
||||
return neighbor;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the given triangle index, returns either the three halfedge indices of
|
||||
* that triangle and halfedges[3] = -1, or if the triangle is part of a quad, it
|
||||
* returns those four indices. If the triangle is part of a quad and is not the
|
||||
* lower of the two triangle indices, it returns all -1s.
|
||||
*/
|
||||
ivec4 Manifold::Impl::GetHalfedges(int tri) const {
|
||||
ivec4 halfedges(-1);
|
||||
for (const int i : {0, 1, 2}) {
|
||||
halfedges[i] = 3 * tri + i;
|
||||
}
|
||||
const int neighbor = GetNeighbor(tri);
|
||||
if (neighbor >= 0) { // quad
|
||||
const int pair = halfedge_[3 * tri + neighbor].pairedHalfedge;
|
||||
if (pair / 3 < tri) {
|
||||
return ivec4(-1); // only process lower tri index
|
||||
}
|
||||
// The order here matters to keep small quads split the way they started, or
|
||||
// else it can create a 4-manifold edge.
|
||||
halfedges[2] = NextHalfedge(halfedges[neighbor]);
|
||||
halfedges[3] = NextHalfedge(halfedges[2]);
|
||||
halfedges[0] = NextHalfedge(pair);
|
||||
halfedges[1] = NextHalfedge(halfedges[0]);
|
||||
}
|
||||
return halfedges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BaryIndices, which gives the tri and indices (0-3), such that
|
||||
* GetHalfedges(val.tri)[val.start4] points back to this halfedge, and val.end4
|
||||
* will point to the next one. This function handles this for both triangles and
|
||||
* quads. Returns {-1, -1, -1} if the edge is the interior of a quad.
|
||||
*/
|
||||
Manifold::Impl::BaryIndices Manifold::Impl::GetIndices(int halfedge) const {
|
||||
int tri = halfedge / 3;
|
||||
int idx = halfedge % 3;
|
||||
const int neighbor = GetNeighbor(tri);
|
||||
if (idx == neighbor) {
|
||||
return {-1, -1, -1};
|
||||
}
|
||||
|
||||
if (neighbor < 0) { // tri
|
||||
return {tri, idx, Next3(idx)};
|
||||
} else { // quad
|
||||
const int pair = halfedge_[3 * tri + neighbor].pairedHalfedge;
|
||||
if (pair / 3 < tri) {
|
||||
tri = pair / 3;
|
||||
idx = Next3(neighbor) == idx ? 0 : 1;
|
||||
} else {
|
||||
idx = Next3(neighbor) == idx ? 2 : 3;
|
||||
}
|
||||
return {tri, idx, (idx + 1) % 4};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retained verts are part of several triangles, and it doesn't matter which one
|
||||
* the vertBary refers to. Here, whichever is last will win and it's done on the
|
||||
* CPU for simplicity for now. Using AtomicCAS on .tri should work for a GPU
|
||||
* version if desired.
|
||||
*/
|
||||
void Manifold::Impl::FillRetainedVerts(Vec<Barycentric>& vertBary) const {
|
||||
const int numTri = halfedge_.size() / 3;
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const BaryIndices indices = GetIndices(3 * tri + i);
|
||||
if (indices.start4 < 0) continue; // skip quad interiors
|
||||
vec4 uvw(0.0);
|
||||
uvw[indices.start4] = 1;
|
||||
vertBary[halfedge_[3 * tri + i].startVert] = {indices.tri, uvw};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split each edge into n pieces as defined by calling the edgeDivisions
|
||||
* function, and sub-triangulate each triangle accordingly. This function
|
||||
* doesn't run Finish(), as that is expensive and it'll need to be run after
|
||||
* the new vertices have moved, which is a likely scenario after refinement
|
||||
* (smoothing).
|
||||
*/
|
||||
Vec<Barycentric> Manifold::Impl::Subdivide(
|
||||
std::function<int(vec3, vec4, vec4)> edgeDivisions, bool keepInterior) {
|
||||
Vec<TmpEdge> edges = CreateTmpEdges(halfedge_);
|
||||
const int numVert = NumVert();
|
||||
const int numEdge = edges.size();
|
||||
const int numTri = NumTri();
|
||||
Vec<int> half2Edge(2 * numEdge);
|
||||
auto policy = autoPolicy(numEdge, 1e4);
|
||||
for_each_n(policy, countAt(0), numEdge,
|
||||
[&half2Edge, &edges, this](const int edge) {
|
||||
const int idx = edges[edge].halfedgeIdx;
|
||||
half2Edge[idx] = edge;
|
||||
half2Edge[halfedge_[idx].pairedHalfedge] = edge;
|
||||
});
|
||||
|
||||
Vec<ivec4> faceHalfedges(numTri);
|
||||
for_each_n(policy, countAt(0), numTri, [&faceHalfedges, this](const int tri) {
|
||||
faceHalfedges[tri] = GetHalfedges(tri);
|
||||
});
|
||||
|
||||
Vec<int> edgeAdded(numEdge);
|
||||
for_each_n(policy, countAt(0), numEdge,
|
||||
[&edgeAdded, &edges, edgeDivisions, this](const int i) {
|
||||
const TmpEdge edge = edges[i];
|
||||
const int hIdx = edge.halfedgeIdx;
|
||||
if (IsMarkedInsideQuad(hIdx)) {
|
||||
edgeAdded[i] = 0;
|
||||
return;
|
||||
}
|
||||
const vec3 vec = vertPos_[edge.first] - vertPos_[edge.second];
|
||||
const vec4 tangent0 = halfedgeTangent_.empty()
|
||||
? vec4(0.0)
|
||||
: halfedgeTangent_[hIdx];
|
||||
const vec4 tangent1 =
|
||||
halfedgeTangent_.empty()
|
||||
? vec4(0.0)
|
||||
: halfedgeTangent_[halfedge_[hIdx].pairedHalfedge];
|
||||
edgeAdded[i] = edgeDivisions(vec, tangent0, tangent1);
|
||||
});
|
||||
|
||||
if (keepInterior) {
|
||||
// Triangles where the greatest number of divisions exceeds the sum of the
|
||||
// other two sides will be triangulated as a strip, since if the sub-edges
|
||||
// were all equal length it would be degenerate. This leads to poor results
|
||||
// with RefineToTolerance, so we avoid this case by adding some extra
|
||||
// divisions to the short sides so that the triangulation has some thickness
|
||||
// and creates more interior facets.
|
||||
Vec<int> tmp(numEdge);
|
||||
for_each_n(
|
||||
policy, countAt(0), numEdge,
|
||||
[&tmp, &edgeAdded, &edges, &half2Edge, this](const int i) {
|
||||
tmp[i] = edgeAdded[i];
|
||||
const TmpEdge edge = edges[i];
|
||||
int hIdx = edge.halfedgeIdx;
|
||||
if (IsMarkedInsideQuad(hIdx)) return;
|
||||
|
||||
const int thisAdded = tmp[i];
|
||||
auto Added = [&edgeAdded, &half2Edge, thisAdded, this](int hIdx) {
|
||||
int longest = 0;
|
||||
int total = 0;
|
||||
for (int j : {0, 1, 2}) {
|
||||
const int added = edgeAdded[half2Edge[hIdx]];
|
||||
longest = la::max(longest, added);
|
||||
total += added;
|
||||
hIdx = NextHalfedge(hIdx);
|
||||
if (IsMarkedInsideQuad(hIdx)) {
|
||||
// No extra on quads
|
||||
longest = 0;
|
||||
total = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const int minExtra = longest * 0.2 + 1;
|
||||
const int extra = 2 * longest + minExtra - total;
|
||||
return extra > 0 ? (extra * (longest - thisAdded)) / longest : 0;
|
||||
};
|
||||
|
||||
tmp[i] += la::max(Added(hIdx), Added(halfedge_[hIdx].pairedHalfedge));
|
||||
});
|
||||
edgeAdded.swap(tmp);
|
||||
}
|
||||
|
||||
Vec<int> edgeOffset(numEdge);
|
||||
exclusive_scan(edgeAdded.begin(), edgeAdded.end(), edgeOffset.begin(),
|
||||
numVert);
|
||||
|
||||
Vec<Barycentric> vertBary(edgeOffset.back() + edgeAdded.back());
|
||||
const int totalEdgeAdded = vertBary.size() - numVert;
|
||||
FillRetainedVerts(vertBary);
|
||||
for_each_n(policy, countAt(0), numEdge,
|
||||
[&vertBary, &edges, &edgeAdded, &edgeOffset, this](const int i) {
|
||||
const int n = edgeAdded[i];
|
||||
const int offset = edgeOffset[i];
|
||||
|
||||
const BaryIndices indices = GetIndices(edges[i].halfedgeIdx);
|
||||
if (indices.tri < 0) {
|
||||
return; // inside quad
|
||||
}
|
||||
const double frac = 1.0 / (n + 1);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
vec4 uvw(0.0);
|
||||
uvw[indices.end4] = (i + 1) * frac;
|
||||
uvw[indices.start4] = 1 - uvw[indices.end4];
|
||||
vertBary[offset + i].uvw = uvw;
|
||||
vertBary[offset + i].tri = indices.tri;
|
||||
}
|
||||
});
|
||||
|
||||
std::vector<Partition> subTris(numTri);
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
[this, &subTris, &half2Edge, &edgeAdded, &faceHalfedges](int tri) {
|
||||
const ivec4 halfedges = faceHalfedges[tri];
|
||||
ivec4 divisions(0);
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
if (halfedges[i] >= 0) {
|
||||
divisions[i] = edgeAdded[half2Edge[halfedges[i]]] + 1;
|
||||
}
|
||||
}
|
||||
subTris[tri] = Partition::GetPartition(divisions);
|
||||
});
|
||||
|
||||
Vec<int> triOffset(numTri);
|
||||
auto numSubTris =
|
||||
TransformIterator(subTris.begin(), [](const Partition& part) {
|
||||
return static_cast<int>(part.triVert.size());
|
||||
});
|
||||
manifold::exclusive_scan(numSubTris, numSubTris + numTri, triOffset.begin(),
|
||||
0);
|
||||
|
||||
Vec<int> interiorOffset(numTri);
|
||||
auto numInterior =
|
||||
TransformIterator(subTris.begin(), [](const Partition& part) {
|
||||
return static_cast<int>(part.NumInterior());
|
||||
});
|
||||
manifold::exclusive_scan(numInterior, numInterior + numTri,
|
||||
interiorOffset.begin(),
|
||||
static_cast<int>(vertBary.size()));
|
||||
|
||||
Vec<ivec3> triVerts(triOffset.back() + subTris.back().triVert.size());
|
||||
vertBary.resize(interiorOffset.back() + subTris.back().NumInterior());
|
||||
Vec<TriRef> triRef(triVerts.size());
|
||||
for_each_n(
|
||||
policy, countAt(0), numTri,
|
||||
[this, &triVerts, &triRef, &vertBary, &subTris, &edgeOffset, &half2Edge,
|
||||
&triOffset, &interiorOffset, &faceHalfedges](int tri) {
|
||||
const ivec4 halfedges = faceHalfedges[tri];
|
||||
if (halfedges[0] < 0) return;
|
||||
ivec4 tri3;
|
||||
ivec4 edgeOffsets;
|
||||
bvec4 edgeFwd(false);
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
if (halfedges[i] < 0) {
|
||||
tri3[i] = -1;
|
||||
continue;
|
||||
}
|
||||
const Halfedge& halfedge = halfedge_[halfedges[i]];
|
||||
tri3[i] = halfedge.startVert;
|
||||
edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]];
|
||||
edgeFwd[i] = halfedge.IsForward();
|
||||
}
|
||||
|
||||
Vec<ivec3> newTris = subTris[tri].Reindex(tri3, edgeOffsets, edgeFwd,
|
||||
interiorOffset[tri]);
|
||||
copy(newTris.begin(), newTris.end(), triVerts.begin() + triOffset[tri]);
|
||||
auto start = triRef.begin() + triOffset[tri];
|
||||
fill(start, start + newTris.size(), meshRelation_.triRef[tri]);
|
||||
|
||||
const ivec4 idx = subTris[tri].idx;
|
||||
const ivec4 vIdx = halfedges[3] >= 0 || idx[1] == Next3(idx[0])
|
||||
? idx
|
||||
: ivec4(idx[2], idx[0], idx[1], idx[3]);
|
||||
ivec4 rIdx;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
rIdx[vIdx[i]] = i;
|
||||
}
|
||||
|
||||
const auto& subBary = subTris[tri].vertBary;
|
||||
transform(subBary.begin() + subTris[tri].InteriorOffset(),
|
||||
subBary.end(), vertBary.begin() + interiorOffset[tri],
|
||||
[tri, rIdx](vec4 bary) {
|
||||
return Barycentric({tri,
|
||||
{bary[rIdx[0]], bary[rIdx[1]],
|
||||
bary[rIdx[2]], bary[rIdx[3]]}});
|
||||
});
|
||||
});
|
||||
meshRelation_.triRef = triRef;
|
||||
|
||||
Vec<vec3> newVertPos(vertBary.size());
|
||||
for_each_n(policy, countAt(0), vertBary.size(),
|
||||
[&newVertPos, &vertBary, &faceHalfedges, this](const int vert) {
|
||||
const Barycentric bary = vertBary[vert];
|
||||
const ivec4 halfedges = faceHalfedges[bary.tri];
|
||||
if (halfedges[3] < 0) {
|
||||
mat3 triPos;
|
||||
for (const int i : {0, 1, 2}) {
|
||||
triPos[i] = vertPos_[halfedge_[halfedges[i]].startVert];
|
||||
}
|
||||
newVertPos[vert] = triPos * vec3(bary.uvw);
|
||||
} else {
|
||||
mat3x4 quadPos;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
quadPos[i] = vertPos_[halfedge_[halfedges[i]].startVert];
|
||||
}
|
||||
newVertPos[vert] = quadPos * bary.uvw;
|
||||
}
|
||||
});
|
||||
vertPos_ = newVertPos;
|
||||
|
||||
faceNormal_.resize(0);
|
||||
|
||||
if (meshRelation_.numProp > 0) {
|
||||
const int numPropVert = NumPropVert();
|
||||
const int addedVerts = NumVert() - numVert;
|
||||
const int propOffset = numPropVert - numVert;
|
||||
Vec<double> prop(meshRelation_.numProp *
|
||||
(numPropVert + addedVerts + totalEdgeAdded));
|
||||
|
||||
// copy retained prop verts
|
||||
copy(meshRelation_.properties.begin(), meshRelation_.properties.end(),
|
||||
prop.begin());
|
||||
|
||||
// copy interior prop verts and forward edge prop verts
|
||||
for_each_n(
|
||||
policy, countAt(0), addedVerts,
|
||||
[&prop, &vertBary, &faceHalfedges, numVert, numPropVert,
|
||||
this](const int i) {
|
||||
const int vert = numPropVert + i;
|
||||
const Barycentric bary = vertBary[numVert + i];
|
||||
const ivec4 halfedges = faceHalfedges[bary.tri];
|
||||
auto& rel = meshRelation_;
|
||||
|
||||
for (int p = 0; p < rel.numProp; ++p) {
|
||||
if (halfedges[3] < 0) {
|
||||
vec3 triProp;
|
||||
for (const int i : {0, 1, 2}) {
|
||||
triProp[i] = rel.properties[rel.triProperties[bary.tri][i] *
|
||||
rel.numProp +
|
||||
p];
|
||||
}
|
||||
prop[vert * rel.numProp + p] = la::dot(triProp, vec3(bary.uvw));
|
||||
} else {
|
||||
vec4 quadProp;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
const int tri = halfedges[i] / 3;
|
||||
const int j = halfedges[i] % 3;
|
||||
quadProp[i] =
|
||||
rel.properties[rel.triProperties[tri][j] * rel.numProp + p];
|
||||
}
|
||||
prop[vert * rel.numProp + p] = la::dot(quadProp, bary.uvw);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// copy backward edge prop verts
|
||||
for_each_n(policy, countAt(0), numEdge,
|
||||
[this, &prop, &edges, &edgeAdded, &edgeOffset, propOffset,
|
||||
addedVerts](const int i) {
|
||||
const int n = edgeAdded[i];
|
||||
const int offset = edgeOffset[i] + propOffset + addedVerts;
|
||||
auto& rel = meshRelation_;
|
||||
|
||||
const double frac = 1.0 / (n + 1);
|
||||
const int halfedgeIdx =
|
||||
halfedge_[edges[i].halfedgeIdx].pairedHalfedge;
|
||||
const int v0 = halfedgeIdx % 3;
|
||||
const int tri = halfedgeIdx / 3;
|
||||
const int prop0 = rel.triProperties[tri][v0];
|
||||
const int prop1 = rel.triProperties[tri][Next3(v0)];
|
||||
for (int i = 0; i < n; ++i) {
|
||||
for (int p = 0; p < rel.numProp; ++p) {
|
||||
prop[(offset + i) * rel.numProp + p] =
|
||||
la::lerp(rel.properties[prop0 * rel.numProp + p],
|
||||
rel.properties[prop1 * rel.numProp + p],
|
||||
(i + 1) * frac);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vec<ivec3> triProp(triVerts.size());
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
[this, &triProp, &subTris, &edgeOffset, &half2Edge, &triOffset,
|
||||
&interiorOffset, &faceHalfedges, propOffset,
|
||||
addedVerts](const int tri) {
|
||||
const ivec4 halfedges = faceHalfedges[tri];
|
||||
if (halfedges[0] < 0) return;
|
||||
|
||||
auto& rel = meshRelation_;
|
||||
ivec4 tri3;
|
||||
ivec4 edgeOffsets;
|
||||
bvec4 edgeFwd(true);
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
if (halfedges[i] < 0) {
|
||||
tri3[i] = -1;
|
||||
continue;
|
||||
}
|
||||
const int thisTri = halfedges[i] / 3;
|
||||
const int j = halfedges[i] % 3;
|
||||
const Halfedge& halfedge = halfedge_[halfedges[i]];
|
||||
tri3[i] = rel.triProperties[thisTri][j];
|
||||
edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]];
|
||||
if (!halfedge.IsForward()) {
|
||||
const int pairTri = halfedge.pairedHalfedge / 3;
|
||||
const int k = halfedge.pairedHalfedge % 3;
|
||||
if (rel.triProperties[pairTri][k] !=
|
||||
rel.triProperties[thisTri][Next3(j)] ||
|
||||
rel.triProperties[pairTri][Next3(k)] !=
|
||||
rel.triProperties[thisTri][j]) {
|
||||
edgeOffsets[i] += addedVerts;
|
||||
} else {
|
||||
edgeFwd[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vec<ivec3> newTris = subTris[tri].Reindex(
|
||||
tri3, edgeOffsets + propOffset, edgeFwd,
|
||||
interiorOffset[tri] + propOffset);
|
||||
copy(newTris.begin(), newTris.end(),
|
||||
triProp.begin() + triOffset[tri]);
|
||||
});
|
||||
|
||||
meshRelation_.properties = prop;
|
||||
meshRelation_.triProperties = triProp;
|
||||
}
|
||||
|
||||
CreateHalfedges(triVerts);
|
||||
|
||||
return vertBary;
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
308
thirdparty/manifold/src/svd.h
vendored
Normal file
308
thirdparty/manifold/src/svd.h
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 wi-re
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
// Modified from https://github.com/wi-re/tbtSVD, removing CUDA dependence and
|
||||
// approximate inverse square roots.
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "manifold/common.h"
|
||||
|
||||
namespace {
|
||||
using manifold::mat3;
|
||||
using manifold::vec3;
|
||||
using manifold::vec4;
|
||||
|
||||
// Constants used for calculation of Givens quaternions
|
||||
inline constexpr double _gamma = 5.82842712474619; // sqrt(8)+3;
|
||||
inline constexpr double _cStar = 0.9238795325112867; // cos(pi/8)
|
||||
inline constexpr double _sStar = 0.3826834323650898; // sin(pi/8)
|
||||
// Threshold value
|
||||
inline constexpr double _SVD_EPSILON = 1e-6;
|
||||
// Iteration counts for Jacobi Eigen Analysis, influences precision
|
||||
inline constexpr int JACOBI_STEPS = 12;
|
||||
|
||||
// Helper function used to swap X with Y and Y with X if c == true
|
||||
inline void CondSwap(bool c, double& X, double& Y) {
|
||||
double Z = X;
|
||||
X = c ? Y : X;
|
||||
Y = c ? Z : Y;
|
||||
}
|
||||
// Helper function used to swap X with Y and Y with -X if c == true
|
||||
inline void CondNegSwap(bool c, double& X, double& Y) {
|
||||
double Z = -X;
|
||||
X = c ? Y : X;
|
||||
Y = c ? Z : Y;
|
||||
}
|
||||
// A simple symmetric 3x3 Matrix class (contains no storage for (0, 1) (0, 2)
|
||||
// and (1, 2)
|
||||
struct Symmetric3x3 {
|
||||
double m_00 = 1.0;
|
||||
double m_10 = 0.0, m_11 = 1.0;
|
||||
double m_20 = 0.0, m_21 = 0.0, m_22 = 1.0;
|
||||
|
||||
Symmetric3x3(double a11 = 1.0, double a21 = 0.0, double a22 = 1.0,
|
||||
double a31 = 0.0, double a32 = 0.0, double a33 = 1.0)
|
||||
: m_00(a11), m_10(a21), m_11(a22), m_20(a31), m_21(a32), m_22(a33) {}
|
||||
Symmetric3x3(mat3 o)
|
||||
: m_00(o[0][0]),
|
||||
m_10(o[0][1]),
|
||||
m_11(o[1][1]),
|
||||
m_20(o[0][2]),
|
||||
m_21(o[1][2]),
|
||||
m_22(o[2][2]) {}
|
||||
};
|
||||
// Helper struct to store 2 doubles to avoid OUT parameters on functions
|
||||
struct Givens {
|
||||
double ch = _cStar;
|
||||
double sh = _sStar;
|
||||
};
|
||||
// Helper struct to store 2 Matrices to avoid OUT parameters on functions
|
||||
struct QR {
|
||||
mat3 Q, R;
|
||||
};
|
||||
// Calculates the squared norm of the vector.
|
||||
inline double Dist2(vec3 v) { return la::dot(v, v); }
|
||||
// For an explanation of the math see
|
||||
// http://pages.cs.wisc.edu/~sifakis/papers/SVD_TR1690.pdf Computing the
|
||||
// Singular Value Decomposition of 3 x 3 matrices with minimal branching and
|
||||
// elementary floating point operations See Algorithm 2 in reference. Given a
|
||||
// matrix A this function returns the Givens quaternion (x and w component, y
|
||||
// and z are 0)
|
||||
inline Givens ApproximateGivensQuaternion(Symmetric3x3& A) {
|
||||
Givens g{2.0 * (A.m_00 - A.m_11), A.m_10};
|
||||
bool b = _gamma * g.sh * g.sh < g.ch * g.ch;
|
||||
double w = 1.0 / hypot(g.ch, g.sh);
|
||||
if (!std::isfinite(w)) b = 0;
|
||||
return Givens{b ? w * g.ch : _cStar, b ? w * g.sh : _sStar};
|
||||
}
|
||||
// Function used to apply a Givens rotation S. Calculates the weights and
|
||||
// updates the quaternion to contain the cumulative rotation
|
||||
inline void JacobiConjugation(const int32_t x, const int32_t y, const int32_t z,
|
||||
Symmetric3x3& S, vec4& q) {
|
||||
auto g = ApproximateGivensQuaternion(S);
|
||||
double scale = 1.0 / fma(g.ch, g.ch, g.sh * g.sh);
|
||||
double a = fma(g.ch, g.ch, -g.sh * g.sh) * scale;
|
||||
double b = 2.0 * g.sh * g.ch * scale;
|
||||
Symmetric3x3 _S = S;
|
||||
// perform conjugation S = Q'*S*Q
|
||||
S.m_00 =
|
||||
fma(a, fma(a, _S.m_00, b * _S.m_10), b * (fma(a, _S.m_10, b * _S.m_11)));
|
||||
S.m_10 = fma(a, fma(-b, _S.m_00, a * _S.m_10),
|
||||
b * (fma(-b, _S.m_10, a * _S.m_11)));
|
||||
S.m_11 = fma(-b, fma(-b, _S.m_00, a * _S.m_10),
|
||||
a * (fma(-b, _S.m_10, a * _S.m_11)));
|
||||
S.m_20 = fma(a, _S.m_20, b * _S.m_21);
|
||||
S.m_21 = fma(-b, _S.m_20, a * _S.m_21);
|
||||
S.m_22 = _S.m_22;
|
||||
// update cumulative rotation qV
|
||||
vec3 tmp = g.sh * vec3(q);
|
||||
g.sh *= q[3];
|
||||
// (x,y,z) corresponds to ((0,1,2),(1,2,0),(2,0,1)) for (p,q) =
|
||||
// ((0,1),(1,2),(0,2))
|
||||
q[z] = fma(q[z], g.ch, g.sh);
|
||||
q[3] = fma(q[3], g.ch, -tmp[z]); // w
|
||||
q[x] = fma(q[x], g.ch, tmp[y]);
|
||||
q[y] = fma(q[y], g.ch, -tmp[x]);
|
||||
// re-arrange matrix for next iteration
|
||||
_S.m_00 = S.m_11;
|
||||
_S.m_10 = S.m_21;
|
||||
_S.m_11 = S.m_22;
|
||||
_S.m_20 = S.m_10;
|
||||
_S.m_21 = S.m_20;
|
||||
_S.m_22 = S.m_00;
|
||||
S.m_00 = _S.m_00;
|
||||
S.m_10 = _S.m_10;
|
||||
S.m_11 = _S.m_11;
|
||||
S.m_20 = _S.m_20;
|
||||
S.m_21 = _S.m_21;
|
||||
S.m_22 = _S.m_22;
|
||||
}
|
||||
// Function used to contain the Givens permutations and the loop of the jacobi
|
||||
// steps controlled by JACOBI_STEPS Returns the quaternion q containing the
|
||||
// cumulative result used to reconstruct S
|
||||
inline mat3 JacobiEigenAnalysis(Symmetric3x3 S) {
|
||||
vec4 q(0, 0, 0, 1);
|
||||
for (int32_t i = 0; i < JACOBI_STEPS; i++) {
|
||||
JacobiConjugation(0, 1, 2, S, q);
|
||||
JacobiConjugation(1, 2, 0, S, q);
|
||||
JacobiConjugation(2, 0, 1, S, q);
|
||||
}
|
||||
return mat3({1.0 - 2.0 * (fma(q.y, q.y, q.z * q.z)), //
|
||||
2.0 * fma(q.x, q.y, +q.w * q.z), //
|
||||
2.0 * fma(q.x, q.z, -q.w * q.y)}, //
|
||||
{2 * fma(q.x, q.y, -q.w * q.z), //
|
||||
1 - 2 * fma(q.x, q.x, q.z * q.z), //
|
||||
2 * fma(q.y, q.z, q.w * q.x)}, //
|
||||
{2 * fma(q.x, q.z, q.w * q.y), //
|
||||
2 * fma(q.y, q.z, -q.w * q.x), //
|
||||
1 - 2 * fma(q.x, q.x, q.y * q.y)});
|
||||
}
|
||||
// Implementation of Algorithm 3
|
||||
inline void SortSingularValues(mat3& B, mat3& V) {
|
||||
double rho1 = Dist2(B[0]);
|
||||
double rho2 = Dist2(B[1]);
|
||||
double rho3 = Dist2(B[2]);
|
||||
bool c;
|
||||
c = rho1 < rho2;
|
||||
CondNegSwap(c, B[0][0], B[1][0]);
|
||||
CondNegSwap(c, V[0][0], V[1][0]);
|
||||
CondNegSwap(c, B[0][1], B[1][1]);
|
||||
CondNegSwap(c, V[0][1], V[1][1]);
|
||||
CondNegSwap(c, B[0][2], B[1][2]);
|
||||
CondNegSwap(c, V[0][2], V[1][2]);
|
||||
CondSwap(c, rho1, rho2);
|
||||
c = rho1 < rho3;
|
||||
CondNegSwap(c, B[0][0], B[2][0]);
|
||||
CondNegSwap(c, V[0][0], V[2][0]);
|
||||
CondNegSwap(c, B[0][1], B[2][1]);
|
||||
CondNegSwap(c, V[0][1], V[2][1]);
|
||||
CondNegSwap(c, B[0][2], B[2][2]);
|
||||
CondNegSwap(c, V[0][2], V[2][2]);
|
||||
CondSwap(c, rho1, rho3);
|
||||
c = rho2 < rho3;
|
||||
CondNegSwap(c, B[1][0], B[2][0]);
|
||||
CondNegSwap(c, V[1][0], V[2][0]);
|
||||
CondNegSwap(c, B[1][1], B[2][1]);
|
||||
CondNegSwap(c, V[1][1], V[2][1]);
|
||||
CondNegSwap(c, B[1][2], B[2][2]);
|
||||
CondNegSwap(c, V[1][2], V[2][2]);
|
||||
}
|
||||
// Implementation of Algorithm 4
|
||||
inline Givens QRGivensQuaternion(double a1, double a2) {
|
||||
// a1 = pivot point on diagonal
|
||||
// a2 = lower triangular entry we want to annihilate
|
||||
double epsilon = _SVD_EPSILON;
|
||||
double rho = hypot(a1, a2);
|
||||
Givens g{fabs(a1) + fmax(rho, epsilon), rho > epsilon ? a2 : 0};
|
||||
bool b = a1 < 0.0;
|
||||
CondSwap(b, g.sh, g.ch);
|
||||
double w = 1.0 / hypot(g.ch, g.sh);
|
||||
g.ch *= w;
|
||||
g.sh *= w;
|
||||
return g;
|
||||
}
|
||||
// Implements a QR decomposition of a Matrix, see Sec 4.2
|
||||
inline QR QRDecomposition(mat3& B) {
|
||||
mat3 Q, R;
|
||||
// first Givens rotation (ch,0,0,sh)
|
||||
auto g1 = QRGivensQuaternion(B[0][0], B[0][1]);
|
||||
auto a = fma(-2.0, g1.sh * g1.sh, 1.0);
|
||||
auto b = 2.0 * g1.ch * g1.sh;
|
||||
// apply B = Q' * B
|
||||
R[0][0] = fma(a, B[0][0], b * B[0][1]);
|
||||
R[1][0] = fma(a, B[1][0], b * B[1][1]);
|
||||
R[2][0] = fma(a, B[2][0], b * B[2][1]);
|
||||
R[0][1] = fma(-b, B[0][0], a * B[0][1]);
|
||||
R[1][1] = fma(-b, B[1][0], a * B[1][1]);
|
||||
R[2][1] = fma(-b, B[2][0], a * B[2][1]);
|
||||
R[0][2] = B[0][2];
|
||||
R[1][2] = B[1][2];
|
||||
R[2][2] = B[2][2];
|
||||
// second Givens rotation (ch,0,-sh,0)
|
||||
auto g2 = QRGivensQuaternion(R[0][0], R[0][2]);
|
||||
a = fma(-2.0, g2.sh * g2.sh, 1.0);
|
||||
b = 2.0 * g2.ch * g2.sh;
|
||||
// apply B = Q' * B;
|
||||
B[0][0] = fma(a, R[0][0], b * R[0][2]);
|
||||
B[1][0] = fma(a, R[1][0], b * R[1][2]);
|
||||
B[2][0] = fma(a, R[2][0], b * R[2][2]);
|
||||
B[0][1] = R[0][1];
|
||||
B[1][1] = R[1][1];
|
||||
B[2][1] = R[2][1];
|
||||
B[0][2] = fma(-b, R[0][0], a * R[0][2]);
|
||||
B[1][2] = fma(-b, R[1][0], a * R[1][2]);
|
||||
B[2][2] = fma(-b, R[2][0], a * R[2][2]);
|
||||
// third Givens rotation (ch,sh,0,0)
|
||||
auto g3 = QRGivensQuaternion(B[1][1], B[1][2]);
|
||||
a = fma(-2.0, g3.sh * g3.sh, 1.0);
|
||||
b = 2.0 * g3.ch * g3.sh;
|
||||
// R is now set to desired value
|
||||
R[0][0] = B[0][0];
|
||||
R[1][0] = B[1][0];
|
||||
R[2][0] = B[2][0];
|
||||
R[0][1] = fma(a, B[0][1], b * B[0][2]);
|
||||
R[1][1] = fma(a, B[1][1], b * B[1][2]);
|
||||
R[2][1] = fma(a, B[2][1], b * B[2][2]);
|
||||
R[0][2] = fma(-b, B[0][1], a * B[0][2]);
|
||||
R[1][2] = fma(-b, B[1][1], a * B[1][2]);
|
||||
R[2][2] = fma(-b, B[2][1], a * B[2][2]);
|
||||
// construct the cumulative rotation Q=Q1 * Q2 * Q3
|
||||
// the number of floating point operations for three quaternion
|
||||
// multiplications is more or less comparable to the explicit form of the
|
||||
// joined matrix. certainly more memory-efficient!
|
||||
auto sh12 = 2.0 * fma(g1.sh, g1.sh, -0.5);
|
||||
auto sh22 = 2.0 * fma(g2.sh, g2.sh, -0.5);
|
||||
auto sh32 = 2.0 * fma(g3.sh, g3.sh, -0.5);
|
||||
Q[0][0] = sh12 * sh22;
|
||||
Q[1][0] = fma(4.0 * g2.ch * g3.ch, sh12 * g2.sh * g3.sh,
|
||||
2.0 * g1.ch * g1.sh * sh32);
|
||||
Q[2][0] = fma(4.0 * g1.ch * g3.ch, g1.sh * g3.sh,
|
||||
-2.0 * g2.ch * sh12 * g2.sh * sh32);
|
||||
|
||||
Q[0][1] = -2.0 * g1.ch * g1.sh * sh22;
|
||||
Q[1][1] =
|
||||
fma(-8.0 * g1.ch * g2.ch * g3.ch, g1.sh * g2.sh * g3.sh, sh12 * sh32);
|
||||
Q[2][1] = fma(
|
||||
-2.0 * g3.ch, g3.sh,
|
||||
4.0 * g1.sh * fma(g3.ch * g1.sh, g3.sh, g1.ch * g2.ch * g2.sh * sh32));
|
||||
|
||||
Q[0][2] = 2.0 * g2.ch * g2.sh;
|
||||
Q[1][2] = -2.0 * g3.ch * sh22 * g3.sh;
|
||||
Q[2][2] = sh22 * sh32;
|
||||
return QR{Q, R};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* The three matrices of a Singular Value Decomposition.
|
||||
*/
|
||||
struct SVDSet {
|
||||
mat3 U, S, V;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the Singular Value Decomposition of A: A = U * S * la::transpose(V).
|
||||
*
|
||||
* @param A The matrix to decompose.
|
||||
*/
|
||||
inline SVDSet SVD(mat3 A) {
|
||||
mat3 V = JacobiEigenAnalysis(la::transpose(A) * A);
|
||||
auto B = A * V;
|
||||
SortSingularValues(B, V);
|
||||
QR qr = QRDecomposition(B);
|
||||
return SVDSet{qr.Q, qr.R, V};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the largest singular value of A.
|
||||
*
|
||||
* @param A The matrix to measure.
|
||||
*/
|
||||
inline double SpectralNorm(mat3 A) {
|
||||
SVDSet usv = SVD(A);
|
||||
return usv.S[0][0];
|
||||
}
|
||||
} // namespace manifold
|
||||
225
thirdparty/manifold/src/tri_dist.h
vendored
Normal file
225
thirdparty/manifold/src/tri_dist.h
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
// Copyright 2024 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "manifold/common.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
// From NVIDIA-Omniverse PhysX - BSD 3-Clause "New" or "Revised" License
|
||||
// https://github.com/NVIDIA-Omniverse/PhysX/blob/main/LICENSE.md
|
||||
// https://github.com/NVIDIA-Omniverse/PhysX/blob/main/physx/source/geomutils/src/sweep/GuSweepCapsuleCapsule.cpp
|
||||
// With minor modifications
|
||||
|
||||
/**
|
||||
* Returns the distance between two line segments.
|
||||
*
|
||||
* @param[out] x Closest point on line segment pa.
|
||||
* @param[out] y Closest point on line segment qb.
|
||||
* @param[in] p One endpoint of the first line segment.
|
||||
* @param[in] a Other endpoint of the first line segment.
|
||||
* @param[in] p One endpoint of the second line segment.
|
||||
* @param[in] b Other endpoint of the second line segment.
|
||||
*/
|
||||
inline void EdgeEdgeDist(vec3& x, vec3& y, // closest points
|
||||
const vec3& p,
|
||||
const vec3& a, // seg 1 origin, vector
|
||||
const vec3& q,
|
||||
const vec3& b) // seg 2 origin, vector
|
||||
{
|
||||
const vec3 T = q - p;
|
||||
const auto ADotA = la::dot(a, a);
|
||||
const auto BDotB = la::dot(b, b);
|
||||
const auto ADotB = la::dot(a, b);
|
||||
const auto ADotT = la::dot(a, T);
|
||||
const auto BDotT = la::dot(b, T);
|
||||
|
||||
// t parameterizes ray (p, a)
|
||||
// u parameterizes ray (q, b)
|
||||
|
||||
// Compute t for the closest point on ray (p, a) to ray (q, b)
|
||||
const auto Denom = ADotA * BDotB - ADotB * ADotB;
|
||||
|
||||
double t; // We will clamp result so t is on the segment (p, a)
|
||||
t = Denom != 0.0
|
||||
? la::clamp((ADotT * BDotB - BDotT * ADotB) / Denom, 0.0, 1.0)
|
||||
: 0.0;
|
||||
|
||||
// find u for point on ray (q, b) closest to point at t
|
||||
double u;
|
||||
if (BDotB != 0.0) {
|
||||
u = (t * ADotB - BDotT) / BDotB;
|
||||
|
||||
// if u is on segment (q, b), t and u correspond to closest points,
|
||||
// otherwise, clamp u, recompute and clamp t
|
||||
if (u < 0.0) {
|
||||
u = 0.0;
|
||||
t = ADotA != 0.0 ? la::clamp(ADotT / ADotA, 0.0, 1.0) : 0.0;
|
||||
} else if (u > 1.0) {
|
||||
u = 1.0;
|
||||
t = ADotA != 0.0 ? la::clamp((ADotB + ADotT) / ADotA, 0.0, 1.0) : 0.0;
|
||||
}
|
||||
} else {
|
||||
u = 0.0;
|
||||
t = ADotA != 0.0 ? la::clamp(ADotT / ADotA, 0.0, 1.0) : 0.0;
|
||||
}
|
||||
x = p + a * t;
|
||||
y = q + b * u;
|
||||
}
|
||||
|
||||
// From NVIDIA-Omniverse PhysX - BSD 3-Clause "New" or "Revised" License
|
||||
// https://github.com/NVIDIA-Omniverse/PhysX/blob/main/LICENSE.md
|
||||
// https://github.com/NVIDIA-Omniverse/PhysX/blob/main/physx/source/geomutils/src/distance/GuDistanceTriangleTriangle.cpp
|
||||
// With minor modifications
|
||||
|
||||
/**
|
||||
* Returns the minimum squared distance between two triangles.
|
||||
*
|
||||
* @param p First triangle.
|
||||
* @param q Second triangle.
|
||||
*/
|
||||
inline auto DistanceTriangleTriangleSquared(const std::array<vec3, 3>& p,
|
||||
const std::array<vec3, 3>& q) {
|
||||
std::array<vec3, 3> Sv;
|
||||
Sv[0] = p[1] - p[0];
|
||||
Sv[1] = p[2] - p[1];
|
||||
Sv[2] = p[0] - p[2];
|
||||
|
||||
std::array<vec3, 3> Tv;
|
||||
Tv[0] = q[1] - q[0];
|
||||
Tv[1] = q[2] - q[1];
|
||||
Tv[2] = q[0] - q[2];
|
||||
|
||||
bool shown_disjoint = false;
|
||||
|
||||
auto mindd = std::numeric_limits<double>::max();
|
||||
|
||||
for (uint32_t i = 0; i < 3; i++) {
|
||||
for (uint32_t j = 0; j < 3; j++) {
|
||||
vec3 cp;
|
||||
vec3 cq;
|
||||
EdgeEdgeDist(cp, cq, p[i], Sv[i], q[j], Tv[j]);
|
||||
const vec3 V = cq - cp;
|
||||
const auto dd = la::dot(V, V);
|
||||
|
||||
if (dd <= mindd) {
|
||||
mindd = dd;
|
||||
|
||||
uint32_t id = i + 2;
|
||||
if (id >= 3) id -= 3;
|
||||
vec3 Z = p[id] - cp;
|
||||
auto a = la::dot(Z, V);
|
||||
id = j + 2;
|
||||
if (id >= 3) id -= 3;
|
||||
Z = q[id] - cq;
|
||||
auto b = la::dot(Z, V);
|
||||
|
||||
if ((a <= 0.0) && (b >= 0.0)) {
|
||||
return la::dot(V, V);
|
||||
};
|
||||
|
||||
if (a <= 0.0)
|
||||
a = 0.0;
|
||||
else if (b > 0.0)
|
||||
b = 0.0;
|
||||
|
||||
if ((mindd - a + b) > 0.0) shown_disjoint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec3 Sn = la::cross(Sv[0], Sv[1]);
|
||||
auto Snl = la::dot(Sn, Sn);
|
||||
|
||||
if (Snl > 1e-15) {
|
||||
const vec3 Tp(la::dot(p[0] - q[0], Sn), la::dot(p[0] - q[1], Sn),
|
||||
la::dot(p[0] - q[2], Sn));
|
||||
|
||||
int index = -1;
|
||||
if ((Tp[0] > 0.0) && (Tp[1] > 0.0) && (Tp[2] > 0.0)) {
|
||||
index = Tp[0] < Tp[1] ? 0 : 1;
|
||||
if (Tp[2] < Tp[index]) index = 2;
|
||||
} else if ((Tp[0] < 0.0) && (Tp[1] < 0.0) && (Tp[2] < 0.0)) {
|
||||
index = Tp[0] > Tp[1] ? 0 : 1;
|
||||
if (Tp[2] > Tp[index]) index = 2;
|
||||
}
|
||||
|
||||
if (index >= 0) {
|
||||
shown_disjoint = true;
|
||||
|
||||
const vec3& qIndex = q[index];
|
||||
|
||||
vec3 V = qIndex - p[0];
|
||||
vec3 Z = la::cross(Sn, Sv[0]);
|
||||
if (la::dot(V, Z) > 0.0) {
|
||||
V = qIndex - p[1];
|
||||
Z = la::cross(Sn, Sv[1]);
|
||||
if (la::dot(V, Z) > 0.0) {
|
||||
V = qIndex - p[2];
|
||||
Z = la::cross(Sn, Sv[2]);
|
||||
if (la::dot(V, Z) > 0.0) {
|
||||
vec3 cp = qIndex + Sn * Tp[index] / Snl;
|
||||
vec3 cq = qIndex;
|
||||
return la::dot(cp - cq, cp - cq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec3 Tn = la::cross(Tv[0], Tv[1]);
|
||||
auto Tnl = la::dot(Tn, Tn);
|
||||
|
||||
if (Tnl > 1e-15) {
|
||||
const vec3 Sp(la::dot(q[0] - p[0], Tn), la::dot(q[0] - p[1], Tn),
|
||||
la::dot(q[0] - p[2], Tn));
|
||||
|
||||
int index = -1;
|
||||
if ((Sp[0] > 0.0) && (Sp[1] > 0.0) && (Sp[2] > 0.0)) {
|
||||
index = Sp[0] < Sp[1] ? 0 : 1;
|
||||
if (Sp[2] < Sp[index]) index = 2;
|
||||
} else if ((Sp[0] < 0.0) && (Sp[1] < 0.0) && (Sp[2] < 0.0)) {
|
||||
index = Sp[0] > Sp[1] ? 0 : 1;
|
||||
if (Sp[2] > Sp[index]) index = 2;
|
||||
}
|
||||
|
||||
if (index >= 0) {
|
||||
shown_disjoint = true;
|
||||
|
||||
const vec3& pIndex = p[index];
|
||||
|
||||
vec3 V = pIndex - q[0];
|
||||
vec3 Z = la::cross(Tn, Tv[0]);
|
||||
if (la::dot(V, Z) > 0.0) {
|
||||
V = pIndex - q[1];
|
||||
Z = la::cross(Tn, Tv[1]);
|
||||
if (la::dot(V, Z) > 0.0) {
|
||||
V = pIndex - q[2];
|
||||
Z = la::cross(Tn, Tv[2]);
|
||||
if (la::dot(V, Z) > 0.0) {
|
||||
vec3 cp = pIndex;
|
||||
vec3 cq = pIndex + Tn * Sp[index] / Tnl;
|
||||
return la::dot(cp - cq, cp - cq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shown_disjoint ? mindd : 0.0;
|
||||
};
|
||||
} // namespace manifold
|
||||
227
thirdparty/manifold/src/utils.h
vendored
Normal file
227
thirdparty/manifold/src/utils.h
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2020 The Manifold Authors, Jared Hoberock and Nathan Bell of
|
||||
// NVIDIA Research
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "./vec.h"
|
||||
#include "manifold/common.h"
|
||||
|
||||
#ifndef MANIFOLD_PAR
|
||||
#error "MANIFOLD_PAR must be defined to either 1 (parallel) or -1 (series)"
|
||||
#else
|
||||
#if (MANIFOLD_PAR != 1) && (MANIFOLD_PAR != -1)
|
||||
#define XSTR(x) STR(x)
|
||||
#define STR(x) #x
|
||||
#pragma message "Current value of MANIFOLD_PAR is: " XSTR(MANIFOLD_PAR)
|
||||
#error "MANIFOLD_PAR must be defined to either 1 (parallel) or -1 (series)"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "./parallel.h"
|
||||
|
||||
#if __has_include(<tracy/Tracy.hpp>)
|
||||
#include <tracy/Tracy.hpp>
|
||||
#else
|
||||
#define FrameMarkStart(x)
|
||||
#define FrameMarkEnd(x)
|
||||
// putting ZoneScoped in a function will instrument the function execution when
|
||||
// TRACY_ENABLE is set, which allows the profiler to record more accurate
|
||||
// timing.
|
||||
#define ZoneScoped
|
||||
#define ZoneScopedN(name)
|
||||
#endif
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Stand-in for C++23's operator""uz (P0330R8)[https://wg21.link/P0330R8].
|
||||
*/
|
||||
[[nodiscard]] constexpr std::size_t operator""_uz(
|
||||
unsigned long long n) noexcept {
|
||||
return n;
|
||||
}
|
||||
|
||||
constexpr double kPrecision = 1e-12;
|
||||
|
||||
inline int Next3(int i) {
|
||||
constexpr ivec3 next3(1, 2, 0);
|
||||
return next3[i];
|
||||
}
|
||||
|
||||
inline int Prev3(int i) {
|
||||
constexpr ivec3 prev3(2, 0, 1);
|
||||
return prev3[i];
|
||||
}
|
||||
|
||||
template <typename T, typename T1>
|
||||
void Permute(Vec<T>& inOut, const Vec<T1>& new2Old) {
|
||||
Vec<T> tmp(std::move(inOut));
|
||||
inOut.resize(new2Old.size());
|
||||
gather(new2Old.begin(), new2Old.end(), tmp.begin(), inOut.begin());
|
||||
}
|
||||
|
||||
template <typename T, typename T1>
|
||||
void Permute(std::vector<T>& inOut, const Vec<T1>& new2Old) {
|
||||
std::vector<T> tmp(std::move(inOut));
|
||||
inOut.resize(new2Old.size());
|
||||
gather(new2Old.begin(), new2Old.end(), tmp.begin(), inOut.begin());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T AtomicAdd(T& target, T add) {
|
||||
std::atomic<T>& tar = reinterpret_cast<std::atomic<T>&>(target);
|
||||
T old_val = tar.load();
|
||||
while (!tar.compare_exchange_weak(old_val, old_val + add,
|
||||
std::memory_order_seq_cst)) {
|
||||
}
|
||||
return old_val;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int AtomicAdd(int& target, int add) {
|
||||
std::atomic<int>& tar = reinterpret_cast<std::atomic<int>&>(target);
|
||||
int old_val = tar.fetch_add(add, std::memory_order_seq_cst);
|
||||
return old_val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class ConcurrentSharedPtr {
|
||||
public:
|
||||
ConcurrentSharedPtr(T value) : impl(std::make_shared<T>(value)) {}
|
||||
ConcurrentSharedPtr(const ConcurrentSharedPtr<T>& other)
|
||||
: impl(other.impl), mutex(other.mutex) {}
|
||||
class SharedPtrGuard {
|
||||
public:
|
||||
SharedPtrGuard(std::recursive_mutex* mutex, T* content)
|
||||
: mutex(mutex), content(content) {
|
||||
mutex->lock();
|
||||
}
|
||||
~SharedPtrGuard() { mutex->unlock(); }
|
||||
|
||||
T& operator*() { return *content; }
|
||||
T* operator->() { return content; }
|
||||
|
||||
private:
|
||||
std::recursive_mutex* mutex;
|
||||
T* content;
|
||||
};
|
||||
SharedPtrGuard GetGuard() { return SharedPtrGuard(mutex.get(), impl.get()); };
|
||||
unsigned int UseCount() { return impl.use_count(); };
|
||||
|
||||
private:
|
||||
std::shared_ptr<T> impl;
|
||||
std::shared_ptr<std::recursive_mutex> mutex =
|
||||
std::make_shared<std::recursive_mutex>();
|
||||
};
|
||||
|
||||
template <typename I = int, typename R = unsigned char>
|
||||
struct UnionFind {
|
||||
Vec<I> parents;
|
||||
// we do union by rank
|
||||
// note that we shift rank by 1, rank 0 means it is not connected to anything
|
||||
// else
|
||||
Vec<R> ranks;
|
||||
|
||||
UnionFind(I numNodes) : parents(numNodes), ranks(numNodes, 0) {
|
||||
sequence(parents.begin(), parents.end());
|
||||
}
|
||||
|
||||
I find(I x) {
|
||||
while (parents[x] != x) {
|
||||
parents[x] = parents[parents[x]];
|
||||
x = parents[x];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
void unionXY(I x, I y) {
|
||||
if (x == y) return;
|
||||
if (ranks[x] == 0) ranks[x] = 1;
|
||||
if (ranks[y] == 0) ranks[y] = 1;
|
||||
x = find(x);
|
||||
y = find(y);
|
||||
if (x == y) return;
|
||||
if (ranks[x] < ranks[y]) std::swap(x, y);
|
||||
if (ranks[x] == ranks[y]) ranks[x]++;
|
||||
parents[y] = x;
|
||||
}
|
||||
|
||||
I connectedComponents(std::vector<I>& components) {
|
||||
components.resize(parents.size());
|
||||
I lonelyNodes = 0;
|
||||
std::unordered_map<I, I> toLabel;
|
||||
for (size_t i = 0; i < parents.size(); ++i) {
|
||||
// we optimize for connected component of size 1
|
||||
// no need to put them into the hashmap
|
||||
if (ranks[i] == 0) {
|
||||
components[i] = static_cast<I>(toLabel.size()) + lonelyNodes++;
|
||||
continue;
|
||||
}
|
||||
parents[i] = find(i);
|
||||
auto iter = toLabel.find(parents[i]);
|
||||
if (iter == toLabel.end()) {
|
||||
I s = static_cast<I>(toLabel.size()) + lonelyNodes;
|
||||
toLabel.insert(std::make_pair(parents[i], s));
|
||||
components[i] = s;
|
||||
} else {
|
||||
components[i] = iter->second;
|
||||
}
|
||||
}
|
||||
return toLabel.size() + lonelyNodes;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Identity {
|
||||
T operator()(T v) const { return v; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Negate {
|
||||
T operator()(T v) const { return -v; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the three points are wound counter-clockwise, clockwise, or
|
||||
* colinear within the specified tolerance.
|
||||
*
|
||||
* @param p0 First point
|
||||
* @param p1 Second point
|
||||
* @param p2 Third point
|
||||
* @param tol Tolerance value for colinearity
|
||||
* @return int, like Signum, this returns 1 for CCW, -1 for CW, and 0 if within
|
||||
* tol of colinear.
|
||||
*/
|
||||
inline int CCW(vec2 p0, vec2 p1, vec2 p2, double tol) {
|
||||
vec2 v1 = p1 - p0;
|
||||
vec2 v2 = p2 - p0;
|
||||
double area = fma(v1.x, v2.y, -v1.y * v2.x);
|
||||
double base2 = la::max(la::dot(v1, v1), la::dot(v2, v2));
|
||||
if (area * area * 4 <= base2 * tol * tol)
|
||||
return 0;
|
||||
else
|
||||
return area > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
inline mat4 Mat4(mat3x4 a) {
|
||||
return mat4({a[0], 0}, {a[1], 0}, {a[2], 0}, {a[3], 1});
|
||||
}
|
||||
inline mat3 Mat3(mat2x3 a) { return mat3({a[0], 0}, {a[1], 0}, {a[2], 1}); }
|
||||
|
||||
} // namespace manifold
|
||||
219
thirdparty/manifold/src/vec.h
vendored
Normal file
219
thirdparty/manifold/src/vec.h
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#if TRACY_ENABLE && TRACY_MEMORY_USAGE
|
||||
#include "tracy/Tracy.hpp"
|
||||
#else
|
||||
#define TracyAllocS(ptr, size, n) (void)0
|
||||
#define TracyFreeS(ptr, n) (void)0
|
||||
#endif
|
||||
#include <vector>
|
||||
|
||||
#include "./parallel.h"
|
||||
#include "manifold/vec_view.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
template <typename T>
|
||||
class Vec;
|
||||
|
||||
/*
|
||||
* Specialized vector implementation with multithreaded fill and uninitialized
|
||||
* memory optimizations.
|
||||
* Note that the constructor and resize function will not perform initialization
|
||||
* if the parameter val is not set. Also, this implementation is a toy
|
||||
* implementation that did not consider things like non-trivial
|
||||
* constructor/destructor, please keep T trivial.
|
||||
*/
|
||||
template <typename T>
|
||||
class Vec : public VecView<T> {
|
||||
public:
|
||||
Vec() {}
|
||||
|
||||
// Note that the vector constructed with this constructor will contain
|
||||
// uninitialized memory. Please specify `val` if you need to make sure that
|
||||
// the data is initialized.
|
||||
Vec(size_t size) {
|
||||
reserve(size);
|
||||
this->size_ = size;
|
||||
}
|
||||
|
||||
Vec(size_t size, T val) { resize(size, val); }
|
||||
|
||||
Vec(const Vec<T> &vec) { *this = Vec(vec.view()); }
|
||||
|
||||
Vec(const VecView<const T> &vec) {
|
||||
this->size_ = vec.size();
|
||||
this->capacity_ = this->size_;
|
||||
auto policy = autoPolicy(this->size_);
|
||||
if (this->size_ != 0) {
|
||||
this->ptr_ = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T)));
|
||||
ASSERT(this->ptr_ != nullptr, std::bad_alloc());
|
||||
TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3);
|
||||
copy(policy, vec.begin(), vec.end(), this->ptr_);
|
||||
}
|
||||
}
|
||||
|
||||
Vec(const std::vector<T> &vec) {
|
||||
this->size_ = vec.size();
|
||||
this->capacity_ = this->size_;
|
||||
auto policy = autoPolicy(this->size_);
|
||||
if (this->size_ != 0) {
|
||||
this->ptr_ = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T)));
|
||||
ASSERT(this->ptr_ != nullptr, std::bad_alloc());
|
||||
TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3);
|
||||
copy(policy, vec.begin(), vec.end(), this->ptr_);
|
||||
}
|
||||
}
|
||||
|
||||
Vec(Vec<T> &&vec) {
|
||||
this->ptr_ = vec.ptr_;
|
||||
this->size_ = vec.size_;
|
||||
capacity_ = vec.capacity_;
|
||||
vec.ptr_ = nullptr;
|
||||
vec.size_ = 0;
|
||||
vec.capacity_ = 0;
|
||||
}
|
||||
|
||||
operator VecView<T>() { return {this->ptr_, this->size_}; }
|
||||
operator VecView<const T>() const { return {this->ptr_, this->size_}; }
|
||||
|
||||
~Vec() {
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(this->ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->ptr_ = nullptr;
|
||||
this->size_ = 0;
|
||||
capacity_ = 0;
|
||||
}
|
||||
|
||||
Vec<T> &operator=(const Vec<T> &other) {
|
||||
if (&other == this) return *this;
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(this->ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->size_ = other.size_;
|
||||
capacity_ = other.size_;
|
||||
if (this->size_ != 0) {
|
||||
this->ptr_ = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T)));
|
||||
ASSERT(this->ptr_ != nullptr, std::bad_alloc());
|
||||
TracyAllocS(this->ptr_, this->size_ * sizeof(T), 3);
|
||||
manifold::copy(other.begin(), other.end(), this->ptr_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vec<T> &operator=(Vec<T> &&other) {
|
||||
if (&other == this) return *this;
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(this->ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->size_ = other.size_;
|
||||
capacity_ = other.capacity_;
|
||||
this->ptr_ = other.ptr_;
|
||||
other.ptr_ = nullptr;
|
||||
other.size_ = 0;
|
||||
other.capacity_ = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator VecView<T>() const { return {this->ptr_, this->size_}; }
|
||||
|
||||
void swap(Vec<T> &other) {
|
||||
std::swap(this->ptr_, other.ptr_);
|
||||
std::swap(this->size_, other.size_);
|
||||
std::swap(capacity_, other.capacity_);
|
||||
}
|
||||
|
||||
inline void push_back(const T &val, bool seq = false) {
|
||||
if (this->size_ >= capacity_) {
|
||||
// avoid dangling pointer in case val is a reference of our array
|
||||
T val_copy = val;
|
||||
reserve(capacity_ == 0 ? 128 : capacity_ * 2, seq);
|
||||
this->ptr_[this->size_++] = val_copy;
|
||||
return;
|
||||
}
|
||||
this->ptr_[this->size_++] = val;
|
||||
}
|
||||
|
||||
inline void extend(size_t n, bool seq = false) {
|
||||
if (this->size_ + n >= capacity_)
|
||||
reserve(capacity_ == 0 ? 128 : std::max(capacity_ * 2, this->size_ + n),
|
||||
seq);
|
||||
this->size_ += n;
|
||||
}
|
||||
|
||||
void reserve(size_t n, bool seq = false) {
|
||||
if (n > capacity_) {
|
||||
T *newBuffer = reinterpret_cast<T *>(malloc(n * sizeof(T)));
|
||||
ASSERT(newBuffer != nullptr, std::bad_alloc());
|
||||
TracyAllocS(newBuffer, n * sizeof(T), 3);
|
||||
if (this->size_ > 0)
|
||||
manifold::copy(seq ? ExecutionPolicy::Seq : autoPolicy(this->size_),
|
||||
this->ptr_, this->ptr_ + this->size_, newBuffer);
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(this->ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->ptr_ = newBuffer;
|
||||
capacity_ = n;
|
||||
}
|
||||
}
|
||||
|
||||
void resize(size_t newSize, T val = T()) {
|
||||
bool shrink = this->size_ > 2 * newSize;
|
||||
reserve(newSize);
|
||||
if (this->size_ < newSize) {
|
||||
fill(autoPolicy(newSize - this->size_), this->ptr_ + this->size_,
|
||||
this->ptr_ + newSize, val);
|
||||
}
|
||||
this->size_ = newSize;
|
||||
if (shrink) shrink_to_fit();
|
||||
}
|
||||
|
||||
void pop_back() { resize(this->size_ - 1); }
|
||||
|
||||
void clear(bool shrink = true) {
|
||||
this->size_ = 0;
|
||||
if (shrink) shrink_to_fit();
|
||||
}
|
||||
|
||||
void shrink_to_fit() {
|
||||
T *newBuffer = nullptr;
|
||||
if (this->size_ > 0) {
|
||||
newBuffer = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T)));
|
||||
ASSERT(newBuffer != nullptr, std::bad_alloc());
|
||||
TracyAllocS(newBuffer, this->size_ * sizeof(T), 3);
|
||||
manifold::copy(this->ptr_, this->ptr_ + this->size_, newBuffer);
|
||||
}
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(this->ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->ptr_ = newBuffer;
|
||||
capacity_ = this->size_;
|
||||
}
|
||||
|
||||
size_t capacity() const { return capacity_; }
|
||||
|
||||
private:
|
||||
size_t capacity_ = 0;
|
||||
|
||||
static_assert(std::is_trivially_destructible<T>::value);
|
||||
};
|
||||
} // namespace manifold
|
||||
Reference in New Issue
Block a user