From 4818f99cd82270b95de3eb43c27089d188f40dcc Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Mon, 10 Jul 2023 23:45:13 -0700 Subject: [PATCH 01/18] WIP 3d hull implementation --- src/manifold/src/manifold.cpp | 41 ++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index 0e1f3bf4b..cd36e0c9d 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -71,6 +71,43 @@ Manifold Halfspace(Box bBox, glm::vec3 normal, float originOffset) { float zDeg = glm::degrees(glm::atan(normal.y, normal.x)); return cutter.Rotate(0.0f, yDeg, zDeg); } + +float DistanceToVector(glm::vec3 pt, glm::vec3 v) { + return glm::length(pt - v * glm::dot(pt, v)); +} +// Return a trio of non-collinear indices into pts. If none are found, indices +// will be -1 (and hulling cannot be performed). +glm::ivec3 NonCollinearTriple(const std::vector& pts, + const float precision) { + const int len = pts.size(); + int furthest = 1; + float dist = glm::distance(pts[0], pts[1]); + for (int i = 2; i < len; i++) { + float d = glm::distance(pts[0], pts[i]); + if (d > dist) { + furthest = i; + dist = d; + } + } + if (dist <= precision) return glm::ivec3(-1); + const auto n = (pts[0] - pts[furthest]) / dist; + int third = -1; + float offset = dist * precision; + for (int i = 1; i < len; i++) { + auto off = DistanceToVector(pts[i] - pts[0], n); + if (off > offset) { + third = i; + offset = off; + } + } + if (third < 0) return glm::ivec3(-1); + return glm::ivec3(0, furthest, third); +} + +Manifold HullImpl(std::vector pts, float precision) { + auto len = pts.size(); + if (len < 4) return Manifold(); +} } // namespace namespace manifold { @@ -775,4 +812,6 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const { } ExecutionParams& ManifoldParams() { return params; } -} // namespace manifold \ No newline at end of file + +// Manifold Manifold::Hull() const {} +} // namespace manifold From 4fd9301f5ebe479b20fcb2208d293e7eaecc5b6c Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Wed, 12 Jul 2023 12:15:57 -0700 Subject: [PATCH 02/18] First pass port, unran, untested --- src/manifold/include/manifold.h | 4 +- src/manifold/src/manifold.cpp | 125 +++++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 5 deletions(-) diff --git a/src/manifold/include/manifold.h b/src/manifold/include/manifold.h index 374f9f16c..692b73578 100644 --- a/src/manifold/include/manifold.h +++ b/src/manifold/include/manifold.h @@ -234,6 +234,8 @@ class Manifold { Manifold TrimByPlane(glm::vec3 normal, float originOffset) const; ///@} + Manifold Hull() const; + /** @name Testing hooks * These are just for internal testing. */ @@ -249,7 +251,7 @@ class Manifold { Manifold(std::shared_ptr pNode_); Manifold(std::shared_ptr pImpl_); static Manifold Invalid(); - + static Manifold HullImpl(std::vector pts, float precision); mutable std::shared_ptr pNode_; CsgLeafNode& GetCsgLeafNode() const; diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index cd36e0c9d..57bfc6571 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -75,6 +75,18 @@ Manifold Halfspace(Box bBox, glm::vec3 normal, float originOffset) { float DistanceToVector(glm::vec3 pt, glm::vec3 v) { return glm::length(pt - v * glm::dot(pt, v)); } + +float DistanceToPlane(glm::vec3 pt, glm::vec4 plane) { + return glm::dot(glm::vec3(plane), pt) - plane.w; +} + +glm::vec4 PlaneOfPts(glm::vec3 a, glm::vec3 b, glm::vec3 c) { + auto crx = glm::cross(c - a, b - a); + auto n = glm::length(crx); + if (n == 0.) return glm::vec4(); + return glm::vec4(crx, glm::dot(crx, a)) / n; +} + // Return a trio of non-collinear indices into pts. If none are found, indices // will be -1 (and hulling cannot be performed). glm::ivec3 NonCollinearTriple(const std::vector& pts, @@ -104,10 +116,15 @@ glm::ivec3 NonCollinearTriple(const std::vector& pts, return glm::ivec3(0, furthest, third); } -Manifold HullImpl(std::vector pts, float precision) { - auto len = pts.size(); - if (len < 4) return Manifold(); +std::pair NonCoplanar(const std::vector& pts, + glm::vec4 plane, const float precision) { + for (int i = 0; i < pts.size(); i++) { + float dist = DistanceToPlane(pts[i], plane); + if (std::abs(dist) > precision) return {i, dist > precision}; + } + return {-1, false}; // All points are coplanar, hull is not a 3d shape } + } // namespace namespace manifold { @@ -813,5 +830,105 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const { ExecutionParams& ManifoldParams() { return params; } -// Manifold Manifold::Hull() const {} +Manifold Manifold::HullImpl(std::vector pts, float precision) { + auto len = pts.size(); + if (len < 4) return Invalid(); // FIXME: as with below, is this possible? + const auto trip = NonCollinearTriple(pts, precision); + const auto plane = PlaneOfPts(pts[trip.x], pts[trip.y], pts[trip.z]); + const auto [d, dIsAboveTrip] = NonCoplanar(pts, plane, precision); + // FIXME: Should there be a hull specific error? If the inputs are all + // manifolds, is this error (all points coplanar) even possible? + if (d < 0) return Invalid(); + const int a = trip.x; + const int b = dIsAboveTrip ? trip.z : trip.y; + const int c = dIsAboveTrip ? trip.y : trip.z; + std::vector triangles; + std::vector planes; + std::vector dropped; + // FIXME: actually, I don't want to capture triangles and planes since I + // actually throw them out during pruning on each pass. starting fresh with + // std::vector is still probably faster than a data structure that would allow + // deletion I guess. + // const auto AddTri = [&pts](std::vector& triangles, + // std::vector& planes, int a, int b, + // int c) { + // triangles.push_back({a, b, c}); + // planes.push_back(PlaneOfPts(pts[a], pts[b], pts[c])); + // }; + // AddTri(triangles, planes, a, b, c); + // AddTri(triangles, planes, d, b, a); + // AddTri(triangles, planes, c, d, a); + // AddTri(triangles, planes, b, d, c); + const auto AddTri = [&pts, &triangles, &planes, &dropped](int a, int b, + int c) { + auto tri = glm::ivec3(a, b, c); + auto plane = PlaneOfPts(pts[a], pts[b], pts[c]); + if (dropped.size() > 0) { + const int idx = dropped[dropped.size() - 1]; + triangles[idx] = tri; + planes[idx] = plane; + dropped.pop_back(); + } else { + triangles.push_back(tri); + planes.push_back(plane); + } + }; + AddTri(a, b, c); + AddTri(d, b, a); + AddTri(c, d, a); + AddTri(b, d, c); + for (int i = 0; i < len; i++) { + if (i == a || i == b || i == c || i == d) continue; // skip starting points + // collect half edges of triangles that are in conflict with the points at + // idx, pruning the conflicting triangles and their planes in the process + std::vector halfEdges; // flat set of pairs of indices + for (int j = 0; j < triangles.size(); j++) { + if (DistanceToPlane(pts[i], planes[j]) > precision) { + // edge a -> b + halfEdges.push_back(triangles[j].x); + halfEdges.push_back(triangles[j].y); + // edge b -> c + halfEdges.push_back(triangles[j].y); + halfEdges.push_back(triangles[j].z); + // edge c -> a + halfEdges.push_back(triangles[j].z); + halfEdges.push_back(triangles[j].x); + // mark triangle/plane as dropped + dropped.push_back(j); + } + } + // form new triangles with the outer perimeter (horizon) of the set of + // conflicting triangles and the point at idx + std::vector internal; + for (int j = 0; j < halfEdges.size() / 2 - 1; j++) { + // skip if this edge has already been marked as internal + bool doSkip = false; + for (int s : internal) { + if (j == s) { + doSkip = true; + break; + } + } + if (doSkip) continue; + bool nonInternal = true; + for (int k = j + 1; k < halfEdges.size() / 2; k++) { + if (j == k) continue; + if (halfEdges[j] == halfEdges[k + 1] && + halfEdges[j + 1] == halfEdges[k]) { + internal.push_back(k); + nonInternal = false; + break; + } + } + if (nonInternal) AddTri(halfEdges[j], halfEdges[j + 1], i); + } + } + Mesh mesh; + mesh.vertPos = pts; + mesh.triVerts = triangles; + return Manifold(mesh); +} +Manifold Manifold::Hull() const { + return HullImpl(GetMesh().vertPos, Precision()); +} } // namespace manifold From 6a230496b245d3d53232b25b3e81cc091c5cfa2f Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Wed, 12 Jul 2023 12:46:10 -0700 Subject: [PATCH 03/18] Handle remaining dropped triangles --- src/manifold/src/manifold.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index 57bfc6571..471d89891 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -902,32 +902,38 @@ Manifold Manifold::HullImpl(std::vector pts, float precision) { std::vector internal; for (int j = 0; j < halfEdges.size() / 2 - 1; j++) { // skip if this edge has already been marked as internal - bool doSkip = false; - for (int s : internal) { - if (j == s) { - doSkip = true; - break; - } - } - if (doSkip) continue; + if (std::find(internal.begin(), internal.end(), j) != internal.end()) + continue; bool nonInternal = true; for (int k = j + 1; k < halfEdges.size() / 2; k++) { if (j == k) continue; - if (halfEdges[j] == halfEdges[k + 1] && - halfEdges[j + 1] == halfEdges[k]) { + if (halfEdges[j * 2] == halfEdges[k * 2 + 1] && + halfEdges[j * 2 + 1] == halfEdges[k * 2]) { internal.push_back(k); nonInternal = false; break; } } - if (nonInternal) AddTri(halfEdges[j], halfEdges[j + 1], i); + if (nonInternal) AddTri(halfEdges[j * 2], halfEdges[j * 2 + 1], i); } } Mesh mesh; mesh.vertPos = pts; - mesh.triVerts = triangles; + if (dropped.size() > 0) { + std::vector tris; + tris.reserve(triangles.size() - dropped.size()); + for (int i = 0; i < triangles.size(); i++) { + if (std::find(dropped.begin(), dropped.end(), i) != dropped.end()) + continue; + tris.push_back(triangles[i]); + } + mesh.triVerts = tris; + } else { + mesh.triVerts = triangles; + } return Manifold(mesh); } + Manifold Manifold::Hull() const { return HullImpl(GetMesh().vertPos, Precision()); } From faad6066d79d1c50f74a36908a1f7ebe412ee452 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Wed, 12 Jul 2023 13:22:07 -0700 Subject: [PATCH 04/18] WIP debugging --- src/manifold/src/manifold.cpp | 39 +++++++++++++++++++++++------------ test/manifold_test.cpp | 13 +++++++++++- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index 471d89891..aa8e5a2f8 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include @@ -877,12 +878,17 @@ Manifold Manifold::HullImpl(std::vector pts, float precision) { AddTri(d, b, a); AddTri(c, d, a); AddTri(b, d, c); + std::printf("pts loop start\n"); for (int i = 0; i < len; i++) { if (i == a || i == b || i == c || i == d) continue; // skip starting points // collect half edges of triangles that are in conflict with the points at // idx, pruning the conflicting triangles and their planes in the process std::vector halfEdges; // flat set of pairs of indices + std::printf("triangles loop start, size = %i with %i dropped\n", + (int)triangles.size(), (int)dropped.size()); for (int j = 0; j < triangles.size(); j++) { + if (std::find(dropped.begin(), dropped.end(), j) != dropped.end()) + continue; if (DistanceToPlane(pts[i], planes[j]) > precision) { // edge a -> b halfEdges.push_back(triangles[j].x); @@ -899,22 +905,29 @@ Manifold Manifold::HullImpl(std::vector pts, float precision) { } // form new triangles with the outer perimeter (horizon) of the set of // conflicting triangles and the point at idx + // FIXME: definitely need to use set for halfedges it seems like std::vector internal; - for (int j = 0; j < halfEdges.size() / 2 - 1; j++) { - // skip if this edge has already been marked as internal - if (std::find(internal.begin(), internal.end(), j) != internal.end()) - continue; - bool nonInternal = true; - for (int k = j + 1; k < halfEdges.size() / 2; k++) { - if (j == k) continue; - if (halfEdges[j * 2] == halfEdges[k * 2 + 1] && - halfEdges[j * 2 + 1] == halfEdges[k * 2]) { - internal.push_back(k); - nonInternal = false; - break; + std::printf("halfedges (%i) loop start\n", (int)halfEdges.size()); + if (halfEdges.size() == 0) { + continue; + } else if (halfEdges.size() == 2) { + AddTri(halfEdges[0], halfEdges[1], i); + } else { + for (int j = 0; j < halfEdges.size() / 2 - 1; j++) { + // skip if this edge has already been marked as internal + if (std::find(internal.begin(), internal.end(), j) != internal.end()) + continue; + bool nonInternal = true; + for (int k = j + 1; k < halfEdges.size() / 2; k++) { + if (halfEdges[j * 2] == halfEdges[k * 2 + 1] && + halfEdges[j * 2 + 1] == halfEdges[k * 2]) { + internal.push_back(k); + nonInternal = false; + break; + } } + if (nonInternal) AddTri(halfEdges[j * 2], halfEdges[j * 2 + 1], i); } - if (nonInternal) AddTri(halfEdges[j * 2], halfEdges[j * 2 + 1], i); } } Mesh mesh; diff --git a/test/manifold_test.cpp b/test/manifold_test.cpp index f04edd49b..fa1b7d749 100644 --- a/test/manifold_test.cpp +++ b/test/manifold_test.cpp @@ -711,4 +711,15 @@ TEST(Manifold, PinchedVert) { EXPECT_FALSE(touch.IsEmpty()); EXPECT_EQ(touch.Status(), Manifold::Error::NoError); EXPECT_EQ(touch.Genus(), 0); -} \ No newline at end of file +} + +TEST(Manifold, Hull) { +#ifdef MANIFOLD_EXPORT + if (options.exportModels) { + auto spheres = + Manifold::Sphere(10) + Manifold::Sphere(10).Translate({0, 0, 30}); + auto tictac = spheres.Hull(); + ExportMesh("tictac_hull.glb", tictac.GetMesh(), {}); + } +#endif +} From a4883133093ab813d2b52f7e04732a745641b2fe Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Wed, 12 Jul 2023 17:12:27 -0700 Subject: [PATCH 05/18] Basic tictac example working --- src/manifold/include/manifold.h | 4 +- src/manifold/src/manifold.cpp | 125 ++++++++++++++------------------ 2 files changed, 58 insertions(+), 71 deletions(-) diff --git a/src/manifold/include/manifold.h b/src/manifold/include/manifold.h index 692b73578..7eb358340 100644 --- a/src/manifold/include/manifold.h +++ b/src/manifold/include/manifold.h @@ -235,6 +235,7 @@ class Manifold { ///@} Manifold Hull() const; + static Manifold Hull(const std::vector& pts); /** @name Testing hooks * These are just for internal testing. @@ -251,7 +252,8 @@ class Manifold { Manifold(std::shared_ptr pNode_); Manifold(std::shared_ptr pImpl_); static Manifold Invalid(); - static Manifold HullImpl(std::vector pts, float precision); + static Manifold HullImpl(const std::vector& vertProps, + const int numProp, float precision); mutable std::shared_ptr pNode_; CsgLeafNode& GetCsgLeafNode() const; diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index aa8e5a2f8..d3b92c23b 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "boolean3.h" #include "csg_tree.h" @@ -126,6 +127,12 @@ std::pair NonCoplanar(const std::vector& pts, return {-1, false}; // All points are coplanar, hull is not a 3d shape } +struct IVec2Hash { + std::size_t operator()(const glm::ivec2& v) const { + return (v.y << 16) ^ v.x; + // return (v.x + v.y) * ((v.x + v.y + 1) / 2) + v.y; + } +}; } // namespace namespace manifold { @@ -831,9 +838,16 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const { ExecutionParams& ManifoldParams() { return params; } -Manifold Manifold::HullImpl(std::vector pts, float precision) { - auto len = pts.size(); +// Manifold Manifold::HullImpl(const MeshGL& mesh, float precision) { +Manifold Manifold::HullImpl(const std::vector& vertProps, + const int numProp, float precision) { + const int len = vertProps.size() / numProp; if (len < 4) return Invalid(); // FIXME: as with below, is this possible? + std::vector pts(len); + for (int i = 0; i < len; i++) { + const int j = i * numProp; + pts[i] = {vertProps[j], vertProps[j + 1], vertProps[j + 2]}; + } const auto trip = NonCollinearTriple(pts, precision); const auto plane = PlaneOfPts(pts[trip.x], pts[trip.y], pts[trip.z]); const auto [d, dIsAboveTrip] = NonCoplanar(pts, plane, precision); @@ -846,23 +860,9 @@ Manifold Manifold::HullImpl(std::vector pts, float precision) { std::vector triangles; std::vector planes; std::vector dropped; - // FIXME: actually, I don't want to capture triangles and planes since I - // actually throw them out during pruning on each pass. starting fresh with - // std::vector is still probably faster than a data structure that would allow - // deletion I guess. - // const auto AddTri = [&pts](std::vector& triangles, - // std::vector& planes, int a, int b, - // int c) { - // triangles.push_back({a, b, c}); - // planes.push_back(PlaneOfPts(pts[a], pts[b], pts[c])); - // }; - // AddTri(triangles, planes, a, b, c); - // AddTri(triangles, planes, d, b, a); - // AddTri(triangles, planes, c, d, a); - // AddTri(triangles, planes, b, d, c); const auto AddTri = [&pts, &triangles, &planes, &dropped](int a, int b, int c) { - auto tri = glm::ivec3(a, b, c); + auto tri = glm::ivec3(c, b, a); auto plane = PlaneOfPts(pts[a], pts[b], pts[c]); if (dropped.size() > 0) { const int idx = dropped[dropped.size() - 1]; @@ -878,76 +878,61 @@ Manifold Manifold::HullImpl(std::vector pts, float precision) { AddTri(d, b, a); AddTri(c, d, a); AddTri(b, d, c); - std::printf("pts loop start\n"); for (int i = 0; i < len; i++) { if (i == a || i == b || i == c || i == d) continue; // skip starting points // collect half edges of triangles that are in conflict with the points at // idx, pruning the conflicting triangles and their planes in the process - std::vector halfEdges; // flat set of pairs of indices - std::printf("triangles loop start, size = %i with %i dropped\n", - (int)triangles.size(), (int)dropped.size()); + std::unordered_set halfEdges{}; for (int j = 0; j < triangles.size(); j++) { - if (std::find(dropped.begin(), dropped.end(), j) != dropped.end()) + if (dropped.size() > 0 && + std::find(dropped.begin(), dropped.end(), j) != dropped.end()) continue; if (DistanceToPlane(pts[i], planes[j]) > precision) { - // edge a -> b - halfEdges.push_back(triangles[j].x); - halfEdges.push_back(triangles[j].y); - // edge b -> c - halfEdges.push_back(triangles[j].y); - halfEdges.push_back(triangles[j].z); - // edge c -> a - halfEdges.push_back(triangles[j].z); - halfEdges.push_back(triangles[j].x); - // mark triangle/plane as dropped - dropped.push_back(j); + halfEdges.insert({triangles[j].x, triangles[j].z}); + halfEdges.insert({triangles[j].z, triangles[j].y}); + halfEdges.insert({triangles[j].y, triangles[j].x}); + dropped.push_back(j); // mark triangle/plane as dropped } } // form new triangles with the outer perimeter (horizon) of the set of // conflicting triangles and the point at idx - // FIXME: definitely need to use set for halfedges it seems like - std::vector internal; - std::printf("halfedges (%i) loop start\n", (int)halfEdges.size()); - if (halfEdges.size() == 0) { - continue; - } else if (halfEdges.size() == 2) { - AddTri(halfEdges[0], halfEdges[1], i); - } else { - for (int j = 0; j < halfEdges.size() / 2 - 1; j++) { - // skip if this edge has already been marked as internal - if (std::find(internal.begin(), internal.end(), j) != internal.end()) - continue; - bool nonInternal = true; - for (int k = j + 1; k < halfEdges.size() / 2; k++) { - if (halfEdges[j * 2] == halfEdges[k * 2 + 1] && - halfEdges[j * 2 + 1] == halfEdges[k * 2]) { - internal.push_back(k); - nonInternal = false; - break; - } - } - if (nonInternal) AddTri(halfEdges[j * 2], halfEdges[j * 2 + 1], i); - } + for (auto e : halfEdges) { + if (halfEdges.erase({e.y, e.x}) == 0) AddTri(e.x, e.y, i); } } - Mesh mesh; - mesh.vertPos = pts; - if (dropped.size() > 0) { - std::vector tris; - tris.reserve(triangles.size() - dropped.size()); - for (int i = 0; i < triangles.size(); i++) { - if (std::find(dropped.begin(), dropped.end(), i) != dropped.end()) - continue; - tris.push_back(triangles[i]); - } - mesh.triVerts = tris; - } else { - mesh.triVerts = triangles; + MeshGL mesh; + mesh.numProp = numProp; + mesh.vertProperties = vertProps; + mesh.triVerts.reserve((triangles.size() - dropped.size()) * 3); + for (int i = 0; i < triangles.size(); i++) { + if (dropped.size() > 0 && + std::find(dropped.begin(), dropped.end(), i) != dropped.end()) + continue; + mesh.triVerts.push_back(triangles[i].x); + mesh.triVerts.push_back(triangles[i].y); + mesh.triVerts.push_back(triangles[i].z); } return Manifold(mesh); } Manifold Manifold::Hull() const { - return HullImpl(GetMesh().vertPos, Precision()); + auto mesh = GetMeshGL(); + return HullImpl(mesh.vertProperties, mesh.numProp, Precision()); +} + +Manifold Manifold::Hull(const std::vector& pts) { + std::vector props(pts.size() * 3); + float scale = 0; + for (int i = 0; i < pts.size(); i++) { + props[i * 3] = pts[i].x; + props[i * 3 + 1] = pts[i].y; + props[i * 3 + 2] = pts[i].z; + auto abs = glm::abs(pts[i]); + if (abs.x > scale) scale = abs.x; + if (abs.y > scale) scale = abs.y; + if (abs.z > scale) scale = abs.z; + } + + return HullImpl(props, 3, kTolerance * scale); } } // namespace manifold From d6cb1f27d45f6cc0ae2ed56eccfd7606ee41057e Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Thu, 13 Jul 2023 00:42:40 -0700 Subject: [PATCH 06/18] Optimize triangle skipping with kept vector --- src/manifold/include/manifold.h | 4 +-- src/manifold/src/manifold.cpp | 61 ++++++++++++++++----------------- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/manifold/include/manifold.h b/src/manifold/include/manifold.h index 7eb358340..3530bc970 100644 --- a/src/manifold/include/manifold.h +++ b/src/manifold/include/manifold.h @@ -252,8 +252,8 @@ class Manifold { Manifold(std::shared_ptr pNode_); Manifold(std::shared_ptr pImpl_); static Manifold Invalid(); - static Manifold HullImpl(const std::vector& vertProps, - const int numProp, float precision); + static Manifold Hull(const std::vector& vertProps, const int numProp, + double precision); mutable std::shared_ptr pNode_; CsgLeafNode& GetCsgLeafNode() const; diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index d3b92c23b..6c2835a43 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -74,15 +74,16 @@ Manifold Halfspace(Box bBox, glm::vec3 normal, float originOffset) { return cutter.Rotate(0.0f, yDeg, zDeg); } -float DistanceToVector(glm::vec3 pt, glm::vec3 v) { +double DistanceToVector(const glm::vec3& pt, const glm::vec3& v) { return glm::length(pt - v * glm::dot(pt, v)); } -float DistanceToPlane(glm::vec3 pt, glm::vec4 plane) { - return glm::dot(glm::vec3(plane), pt) - plane.w; +inline double DistanceToPlane(const glm::vec3& pt, const glm::vec4& plane) { + return glm::dot({plane.x, plane.y, plane.z}, pt) - plane.w; } -glm::vec4 PlaneOfPts(glm::vec3 a, glm::vec3 b, glm::vec3 c) { +glm::vec4 PlaneOfPts(const glm::vec3& a, const glm::vec3& b, + const glm::vec3& c) { auto crx = glm::cross(c - a, b - a); auto n = glm::length(crx); if (n == 0.) return glm::vec4(); @@ -92,12 +93,12 @@ glm::vec4 PlaneOfPts(glm::vec3 a, glm::vec3 b, glm::vec3 c) { // Return a trio of non-collinear indices into pts. If none are found, indices // will be -1 (and hulling cannot be performed). glm::ivec3 NonCollinearTriple(const std::vector& pts, - const float precision) { + const double precision) { const int len = pts.size(); int furthest = 1; float dist = glm::distance(pts[0], pts[1]); for (int i = 2; i < len; i++) { - float d = glm::distance(pts[0], pts[i]); + double d = glm::distance(pts[0], pts[i]); if (d > dist) { furthest = i; dist = d; @@ -106,7 +107,7 @@ glm::ivec3 NonCollinearTriple(const std::vector& pts, if (dist <= precision) return glm::ivec3(-1); const auto n = (pts[0] - pts[furthest]) / dist; int third = -1; - float offset = dist * precision; + double offset = dist * precision; for (int i = 1; i < len; i++) { auto off = DistanceToVector(pts[i] - pts[0], n); if (off > offset) { @@ -119,9 +120,10 @@ glm::ivec3 NonCollinearTriple(const std::vector& pts, } std::pair NonCoplanar(const std::vector& pts, - glm::vec4 plane, const float precision) { + const glm::vec4& plane, + const double precision) { for (int i = 0; i < pts.size(); i++) { - float dist = DistanceToPlane(pts[i], plane); + auto dist = DistanceToPlane(pts[i], plane); if (std::abs(dist) > precision) return {i, dist > precision}; } return {-1, false}; // All points are coplanar, hull is not a 3d shape @@ -130,7 +132,6 @@ std::pair NonCoplanar(const std::vector& pts, struct IVec2Hash { std::size_t operator()(const glm::ivec2& v) const { return (v.y << 16) ^ v.x; - // return (v.x + v.y) * ((v.x + v.y + 1) / 2) + v.y; } }; } // namespace @@ -838,11 +839,11 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const { ExecutionParams& ManifoldParams() { return params; } -// Manifold Manifold::HullImpl(const MeshGL& mesh, float precision) { -Manifold Manifold::HullImpl(const std::vector& vertProps, - const int numProp, float precision) { +Manifold Manifold::Hull(const std::vector& vertProps, const int numProp, + const double precision) { const int len = vertProps.size() / numProp; - if (len < 4) return Invalid(); // FIXME: as with below, is this possible? + // FIXME: Should there be a hull specific error? + if (len < 4) return Invalid(); std::vector pts(len); for (int i = 0; i < len; i++) { const int j = i * numProp; @@ -851,27 +852,29 @@ Manifold Manifold::HullImpl(const std::vector& vertProps, const auto trip = NonCollinearTriple(pts, precision); const auto plane = PlaneOfPts(pts[trip.x], pts[trip.y], pts[trip.z]); const auto [d, dIsAboveTrip] = NonCoplanar(pts, plane, precision); - // FIXME: Should there be a hull specific error? If the inputs are all - // manifolds, is this error (all points coplanar) even possible? - if (d < 0) return Invalid(); + if (d < 0) return Invalid(); // All points coplanar. const int a = trip.x; const int b = dIsAboveTrip ? trip.z : trip.y; const int c = dIsAboveTrip ? trip.y : trip.z; std::vector triangles; std::vector planes; + std::unordered_set halfEdges{}; std::vector dropped; - const auto AddTri = [&pts, &triangles, &planes, &dropped](int a, int b, - int c) { + std::vector kept; + const auto AddTri = [&pts, &triangles, &planes, &dropped, &kept](int a, int b, + int c) { auto tri = glm::ivec3(c, b, a); auto plane = PlaneOfPts(pts[a], pts[b], pts[c]); if (dropped.size() > 0) { const int idx = dropped[dropped.size() - 1]; triangles[idx] = tri; planes[idx] = plane; + kept[idx] = true; dropped.pop_back(); } else { triangles.push_back(tri); planes.push_back(plane); + kept.push_back(true); } }; AddTri(a, b, c); @@ -882,16 +885,14 @@ Manifold Manifold::HullImpl(const std::vector& vertProps, if (i == a || i == b || i == c || i == d) continue; // skip starting points // collect half edges of triangles that are in conflict with the points at // idx, pruning the conflicting triangles and their planes in the process - std::unordered_set halfEdges{}; for (int j = 0; j < triangles.size(); j++) { - if (dropped.size() > 0 && - std::find(dropped.begin(), dropped.end(), j) != dropped.end()) - continue; - if (DistanceToPlane(pts[i], planes[j]) > precision) { + if (kept[j] && DistanceToPlane(pts[i], planes[j]) > precision) { halfEdges.insert({triangles[j].x, triangles[j].z}); halfEdges.insert({triangles[j].z, triangles[j].y}); halfEdges.insert({triangles[j].y, triangles[j].x}); - dropped.push_back(j); // mark triangle/plane as dropped + // mark triangle/plane as dropped (slot to be reused) + dropped.push_back(j); + kept[j] = false; } } // form new triangles with the outer perimeter (horizon) of the set of @@ -899,15 +900,14 @@ Manifold Manifold::HullImpl(const std::vector& vertProps, for (auto e : halfEdges) { if (halfEdges.erase({e.y, e.x}) == 0) AddTri(e.x, e.y, i); } + halfEdges.clear(); } MeshGL mesh; mesh.numProp = numProp; mesh.vertProperties = vertProps; mesh.triVerts.reserve((triangles.size() - dropped.size()) * 3); for (int i = 0; i < triangles.size(); i++) { - if (dropped.size() > 0 && - std::find(dropped.begin(), dropped.end(), i) != dropped.end()) - continue; + if (!kept[i]) continue; mesh.triVerts.push_back(triangles[i].x); mesh.triVerts.push_back(triangles[i].y); mesh.triVerts.push_back(triangles[i].z); @@ -917,7 +917,7 @@ Manifold Manifold::HullImpl(const std::vector& vertProps, Manifold Manifold::Hull() const { auto mesh = GetMeshGL(); - return HullImpl(mesh.vertProperties, mesh.numProp, Precision()); + return Hull(mesh.vertProperties, mesh.numProp, 1e-4 * Precision()); } Manifold Manifold::Hull(const std::vector& pts) { @@ -932,7 +932,6 @@ Manifold Manifold::Hull(const std::vector& pts) { if (abs.y > scale) scale = abs.y; if (abs.z > scale) scale = abs.z; } - - return HullImpl(props, 3, kTolerance * scale); + return Hull(props, 3, 1e-9 * scale); } } // namespace manifold From 88fa2f701bd9fc55a7d71bc65ce6f7fcf6b4e1d0 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Thu, 13 Jul 2023 01:11:38 -0700 Subject: [PATCH 07/18] Add some basic tests --- test/manifold_test.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test/manifold_test.cpp b/test/manifold_test.cpp index fa1b7d749..7923792ca 100644 --- a/test/manifold_test.cpp +++ b/test/manifold_test.cpp @@ -715,11 +715,24 @@ TEST(Manifold, PinchedVert) { TEST(Manifold, Hull) { #ifdef MANIFOLD_EXPORT + auto sphere = Manifold::Sphere(100, 36); + auto spheres = sphere + sphere.Translate({0, 0, 300}); + auto tictac = spheres.Hull(); + auto hollow = sphere - sphere.Scale({0.8, 0.8, 0.8}); + std::vector cubePts = { + {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, // corners + {1, 1, 0}, {0, 1, 1}, {1, 0, 1}, {1, 1, 1}, // corners + {0.5, 0.5, 0.5}, {0.5, 0, 0}, {0.5, 0.7, 0.2} // internal points + }; + auto cube = Manifold::Hull(cubePts); + if (options.exportModels) { - auto spheres = - Manifold::Sphere(10) + Manifold::Sphere(10).Translate({0, 0, 30}); - auto tictac = spheres.Hull(); ExportMesh("tictac_hull.glb", tictac.GetMesh(), {}); } + + EXPECT_FLOAT_EQ(hollow.Hull().GetProperties().volume, + sphere.GetProperties().volume); + EXPECT_EQ(spheres.NumVert() / 2 + 36, tictac.NumVert()); + EXPECT_FLOAT_EQ(cube.GetProperties().volume, 1); #endif } From 720b8dc8972c6f7023aadebdcc5ee500ce7e91a8 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Thu, 13 Jul 2023 01:23:34 -0700 Subject: [PATCH 08/18] Add manifold vector overload --- src/manifold/include/manifold.h | 1 + src/manifold/src/manifold.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/manifold/include/manifold.h b/src/manifold/include/manifold.h index 3530bc970..b44fe75e8 100644 --- a/src/manifold/include/manifold.h +++ b/src/manifold/include/manifold.h @@ -235,6 +235,7 @@ class Manifold { ///@} Manifold Hull() const; + static Manifold Hull(const std::vector& manifolds); static Manifold Hull(const std::vector& pts); /** @name Testing hooks diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index 6c2835a43..d4c59f2fc 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -920,6 +920,10 @@ Manifold Manifold::Hull() const { return Hull(mesh.vertProperties, mesh.numProp, 1e-4 * Precision()); } +Manifold Manifold::Hull(const std::vector& manifolds) { + return BatchBoolean(manifolds, OpType::Add).Hull(); +} + Manifold Manifold::Hull(const std::vector& pts) { std::vector props(pts.size() * 3); float scale = 0; From 579b054dd0605a03b17326cac47917aa402a1835 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Thu, 13 Jul 2023 12:24:03 -0700 Subject: [PATCH 09/18] Move forgotten export ifdefs --- test/manifold_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/manifold_test.cpp b/test/manifold_test.cpp index 7923792ca..d6c3f7453 100644 --- a/test/manifold_test.cpp +++ b/test/manifold_test.cpp @@ -714,7 +714,6 @@ TEST(Manifold, PinchedVert) { } TEST(Manifold, Hull) { -#ifdef MANIFOLD_EXPORT auto sphere = Manifold::Sphere(100, 36); auto spheres = sphere + sphere.Translate({0, 0, 300}); auto tictac = spheres.Hull(); @@ -726,13 +725,14 @@ TEST(Manifold, Hull) { }; auto cube = Manifold::Hull(cubePts); +#ifdef MANIFOLD_EXPORT if (options.exportModels) { ExportMesh("tictac_hull.glb", tictac.GetMesh(), {}); } +#endif EXPECT_FLOAT_EQ(hollow.Hull().GetProperties().volume, sphere.GetProperties().volume); EXPECT_EQ(spheres.NumVert() / 2 + 36, tictac.NumVert()); EXPECT_FLOAT_EQ(cube.GetProperties().volume, 1); -#endif } From 300b44471e5743e12684d5a3fad06bc4507107ff Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Sun, 16 Jul 2023 17:17:56 -0700 Subject: [PATCH 10/18] Replace slow impl with Akuukka QuickHull --- .gitmodules | 3 + src/manifold/CMakeLists.txt | 2 +- src/manifold/include/manifold.h | 4 +- src/manifold/src/manifold.cpp | 185 +++++++------------------------- src/third_party/CMakeLists.txt | 5 +- src/third_party/quickhull | 1 + test/manifold_test.cpp | 37 ++++--- 7 files changed, 76 insertions(+), 161 deletions(-) create mode 160000 src/third_party/quickhull diff --git a/.gitmodules b/.gitmodules index 5ba093bab..235a0a1a9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "src/third_party/clipper2"] path = src/third_party/clipper2 url = https://github.com/AngusJohnson/clipper2 +[submodule "src/third_party/quickhull"] + path = src/third_party/quickhull + url = https://github.com/akuukka/quickhull diff --git a/src/manifold/CMakeLists.txt b/src/manifold/CMakeLists.txt index d88b06853..b46f6d56d 100644 --- a/src/manifold/CMakeLists.txt +++ b/src/manifold/CMakeLists.txt @@ -25,7 +25,7 @@ endif() target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include) target_link_libraries(${PROJECT_NAME} PUBLIC utilities cross_section - PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite Clipper2 + PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite Clipper2 quickhull ) target_compile_features(${PROJECT_NAME} diff --git a/src/manifold/include/manifold.h b/src/manifold/include/manifold.h index b44fe75e8..0598b4093 100644 --- a/src/manifold/include/manifold.h +++ b/src/manifold/include/manifold.h @@ -253,8 +253,8 @@ class Manifold { Manifold(std::shared_ptr pNode_); Manifold(std::shared_ptr pImpl_); static Manifold Invalid(); - static Manifold Hull(const std::vector& vertProps, const int numProp, - double precision); + static Manifold Hull(const std::vector& pts, + const std::vector& props, const int numProp); mutable std::shared_ptr pNode_; CsgLeafNode& GetCsgLeafNode() const; diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index d4c59f2fc..955d4e7f2 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -73,67 +74,6 @@ Manifold Halfspace(Box bBox, glm::vec3 normal, float originOffset) { float zDeg = glm::degrees(glm::atan(normal.y, normal.x)); return cutter.Rotate(0.0f, yDeg, zDeg); } - -double DistanceToVector(const glm::vec3& pt, const glm::vec3& v) { - return glm::length(pt - v * glm::dot(pt, v)); -} - -inline double DistanceToPlane(const glm::vec3& pt, const glm::vec4& plane) { - return glm::dot({plane.x, plane.y, plane.z}, pt) - plane.w; -} - -glm::vec4 PlaneOfPts(const glm::vec3& a, const glm::vec3& b, - const glm::vec3& c) { - auto crx = glm::cross(c - a, b - a); - auto n = glm::length(crx); - if (n == 0.) return glm::vec4(); - return glm::vec4(crx, glm::dot(crx, a)) / n; -} - -// Return a trio of non-collinear indices into pts. If none are found, indices -// will be -1 (and hulling cannot be performed). -glm::ivec3 NonCollinearTriple(const std::vector& pts, - const double precision) { - const int len = pts.size(); - int furthest = 1; - float dist = glm::distance(pts[0], pts[1]); - for (int i = 2; i < len; i++) { - double d = glm::distance(pts[0], pts[i]); - if (d > dist) { - furthest = i; - dist = d; - } - } - if (dist <= precision) return glm::ivec3(-1); - const auto n = (pts[0] - pts[furthest]) / dist; - int third = -1; - double offset = dist * precision; - for (int i = 1; i < len; i++) { - auto off = DistanceToVector(pts[i] - pts[0], n); - if (off > offset) { - third = i; - offset = off; - } - } - if (third < 0) return glm::ivec3(-1); - return glm::ivec3(0, furthest, third); -} - -std::pair NonCoplanar(const std::vector& pts, - const glm::vec4& plane, - const double precision) { - for (int i = 0; i < pts.size(); i++) { - auto dist = DistanceToPlane(pts[i], plane); - if (std::abs(dist) > precision) return {i, dist > precision}; - } - return {-1, false}; // All points are coplanar, hull is not a 3d shape -} - -struct IVec2Hash { - std::size_t operator()(const glm::ivec2& v) const { - return (v.y << 16) ^ v.x; - } -}; } // namespace namespace manifold { @@ -839,85 +779,53 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const { ExecutionParams& ManifoldParams() { return params; } -Manifold Manifold::Hull(const std::vector& vertProps, const int numProp, - const double precision) { - const int len = vertProps.size() / numProp; +Manifold Manifold::Hull(const std::vector& pts, + const std::vector& props, const int numProp) { + const int numVert = pts.size(); // FIXME: Should there be a hull specific error? - if (len < 4) return Invalid(); - std::vector pts(len); - for (int i = 0; i < len; i++) { - const int j = i * numProp; - pts[i] = {vertProps[j], vertProps[j + 1], vertProps[j + 2]}; - } - const auto trip = NonCollinearTriple(pts, precision); - const auto plane = PlaneOfPts(pts[trip.x], pts[trip.y], pts[trip.z]); - const auto [d, dIsAboveTrip] = NonCoplanar(pts, plane, precision); - if (d < 0) return Invalid(); // All points coplanar. - const int a = trip.x; - const int b = dIsAboveTrip ? trip.z : trip.y; - const int c = dIsAboveTrip ? trip.y : trip.z; - std::vector triangles; - std::vector planes; - std::unordered_set halfEdges{}; - std::vector dropped; - std::vector kept; - const auto AddTri = [&pts, &triangles, &planes, &dropped, &kept](int a, int b, - int c) { - auto tri = glm::ivec3(c, b, a); - auto plane = PlaneOfPts(pts[a], pts[b], pts[c]); - if (dropped.size() > 0) { - const int idx = dropped[dropped.size() - 1]; - triangles[idx] = tri; - planes[idx] = plane; - kept[idx] = true; - dropped.pop_back(); - } else { - triangles.push_back(tri); - planes.push_back(plane); - kept.push_back(true); - } - }; - AddTri(a, b, c); - AddTri(d, b, a); - AddTri(c, d, a); - AddTri(b, d, c); - for (int i = 0; i < len; i++) { - if (i == a || i == b || i == c || i == d) continue; // skip starting points - // collect half edges of triangles that are in conflict with the points at - // idx, pruning the conflicting triangles and their planes in the process - for (int j = 0; j < triangles.size(); j++) { - if (kept[j] && DistanceToPlane(pts[i], planes[j]) > precision) { - halfEdges.insert({triangles[j].x, triangles[j].z}); - halfEdges.insert({triangles[j].z, triangles[j].y}); - halfEdges.insert({triangles[j].y, triangles[j].x}); - // mark triangle/plane as dropped (slot to be reused) - dropped.push_back(j); - kept[j] = false; - } - } - // form new triangles with the outer perimeter (horizon) of the set of - // conflicting triangles and the point at idx - for (auto e : halfEdges) { - if (halfEdges.erase({e.y, e.x}) == 0) AddTri(e.x, e.y, i); - } - halfEdges.clear(); + if (numVert < 4) return Invalid(); + + std::vector> vertices(numVert); + for (int i = 0; i < numVert; i++) { + vertices[i] = {pts[i].x, pts[i].y, pts[i].z}; } - MeshGL mesh; - mesh.numProp = numProp; - mesh.vertProperties = vertProps; - mesh.triVerts.reserve((triangles.size() - dropped.size()) * 3); - for (int i = 0; i < triangles.size(); i++) { - if (!kept[i]) continue; - mesh.triVerts.push_back(triangles[i].x); - mesh.triVerts.push_back(triangles[i].y); - mesh.triVerts.push_back(triangles[i].z); + + quickhull::QuickHull qh; + // bools: correct triangle winding, and use original indices + auto hull = qh.getConvexHull(vertices, false, true); + const auto& triangles = hull.getIndexBuffer(); + const int numTris = triangles.size() / 3; + + Mesh mesh; + Impl::MeshRelationD relation; + relation.numProp = numProp; + relation.properties = props; + mesh.vertPos = pts; + + mesh.triVerts.reserve(numTris); + for (int i = 0; i < numTris; i++) { + const int j = i * 3; + mesh.triVerts.push_back({triangles[j], triangles[j + 1], triangles[j + 2]}); } return Manifold(mesh); } Manifold Manifold::Hull() const { auto mesh = GetMeshGL(); - return Hull(mesh.vertProperties, mesh.numProp, 1e-4 * Precision()); + const int extraProps = mesh.numProp - 3; + const int numVert = mesh.NumVert(); + std::vector pts(numVert); + std::vector props; + if (extraProps > 0) props.reserve(extraProps * numVert); + for (int i = 0; i < mesh.NumVert(); i++) { + const int j = i * 3; + pts[i] = {mesh.vertProperties[j], mesh.vertProperties[j + 1], + mesh.vertProperties[j + 2]}; + for (int k = 0; k < extraProps; k++) { + props.push_back(mesh.vertProperties[j + 3 + k]); + } + } + return Hull(pts, props, extraProps); } Manifold Manifold::Hull(const std::vector& manifolds) { @@ -925,17 +833,6 @@ Manifold Manifold::Hull(const std::vector& manifolds) { } Manifold Manifold::Hull(const std::vector& pts) { - std::vector props(pts.size() * 3); - float scale = 0; - for (int i = 0; i < pts.size(); i++) { - props[i * 3] = pts[i].x; - props[i * 3 + 1] = pts[i].y; - props[i * 3 + 2] = pts[i].z; - auto abs = glm::abs(pts[i]); - if (abs.x > scale) scale = abs.x; - if (abs.y > scale) scale = abs.y; - if (abs.z > scale) scale = abs.z; - } - return Hull(props, 3, 1e-9 * scale); + return Hull(pts, std::vector{}, 0); } } // namespace manifold diff --git a/src/third_party/CMakeLists.txt b/src/third_party/CMakeLists.txt index e5645d687..08be32bc9 100644 --- a/src/third_party/CMakeLists.txt +++ b/src/third_party/CMakeLists.txt @@ -6,5 +6,8 @@ set(CLIPPER2_EXAMPLES OFF) set(CLIPPER2_TESTS OFF) set(CLIPPER2_USINGZ "OFF" CACHE STRING "Preempt cache default of USINGZ (we only use 2d)") add_definitions(-D_HAS_EXCEPTIONS=0) # disable exceptions for STL - add_subdirectory(clipper2/CPP) + +file(GLOB_RECURSE QH_SOURCE_FILES CONFIGURE_DEPENDS quickhull/*.cpp) +add_library(quickhull ${QH_SOURCE_FILES}) +target_include_directories(quickhull PUBLIC quickhull) diff --git a/src/third_party/quickhull b/src/third_party/quickhull new file mode 160000 index 000000000..1ffbc6f88 --- /dev/null +++ b/src/third_party/quickhull @@ -0,0 +1 @@ +Subproject commit 1ffbc6f884ea1da89e104a5996cf8a726db673d5 diff --git a/test/manifold_test.cpp b/test/manifold_test.cpp index d6c3f7453..5d38f0bd0 100644 --- a/test/manifold_test.cpp +++ b/test/manifold_test.cpp @@ -713,17 +713,14 @@ TEST(Manifold, PinchedVert) { EXPECT_EQ(touch.Genus(), 0); } -TEST(Manifold, Hull) { - auto sphere = Manifold::Sphere(100, 36); - auto spheres = sphere + sphere.Translate({0, 0, 300}); +TEST(Manifold, TictacHull) { + const float tictacRad = 100; + const float tictacHeight = 500; + const int tictacSeg = 1000; + const float tictacMid = tictacHeight - 2 * tictacRad; + auto sphere = Manifold::Sphere(tictacRad, tictacSeg); + auto spheres = sphere + sphere.Translate({0, 0, tictacMid}); auto tictac = spheres.Hull(); - auto hollow = sphere - sphere.Scale({0.8, 0.8, 0.8}); - std::vector cubePts = { - {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, // corners - {1, 1, 0}, {0, 1, 1}, {1, 0, 1}, {1, 1, 1}, // corners - {0.5, 0.5, 0.5}, {0.5, 0, 0}, {0.5, 0.7, 0.2} // internal points - }; - auto cube = Manifold::Hull(cubePts); #ifdef MANIFOLD_EXPORT if (options.exportModels) { @@ -731,8 +728,22 @@ TEST(Manifold, Hull) { } #endif - EXPECT_FLOAT_EQ(hollow.Hull().GetProperties().volume, - sphere.GetProperties().volume); - EXPECT_EQ(spheres.NumVert() / 2 + 36, tictac.NumVert()); + EXPECT_EQ(spheres.NumVert() / 2 + tictacSeg, tictac.NumVert()); +} + +TEST(Manifold, HollowHull) { + auto sphere = Manifold::Sphere(100, 360); + auto hollow = sphere - sphere.Scale({0.8, 0.8, 0.8}); + const float sphere_vol = sphere.GetProperties().volume; + EXPECT_FLOAT_EQ(hollow.Hull().GetProperties().volume, sphere_vol); +} + +TEST(Manifold, CubeHull) { + std::vector cubePts = { + {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, // corners + {1, 1, 0}, {0, 1, 1}, {1, 0, 1}, {1, 1, 1}, // corners + {0.5, 0.5, 0.5}, {0.5, 0, 0}, {0.5, 0.7, 0.2} // internal points + }; + auto cube = Manifold::Hull(cubePts); EXPECT_FLOAT_EQ(cube.GetProperties().volume, 1); } From 5d355ef127aa7b0240b00069e3de63e6d8337be7 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Sun, 16 Jul 2023 17:32:24 -0700 Subject: [PATCH 11/18] quickhull + cxx17 features (try to fix mac build) --- src/third_party/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/third_party/CMakeLists.txt b/src/third_party/CMakeLists.txt index 08be32bc9..28300109a 100644 --- a/src/third_party/CMakeLists.txt +++ b/src/third_party/CMakeLists.txt @@ -11,3 +11,4 @@ add_subdirectory(clipper2/CPP) file(GLOB_RECURSE QH_SOURCE_FILES CONFIGURE_DEPENDS quickhull/*.cpp) add_library(quickhull ${QH_SOURCE_FILES}) target_include_directories(quickhull PUBLIC quickhull) +target_compile_features(quickhull PUBLIC cxx_std_17) From 19166d50c6ba2c21d0754dae8095fefc4d40d4ea Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Sun, 16 Jul 2023 23:39:47 -0700 Subject: [PATCH 12/18] WIP test cases for invalid hull construction --- test/manifold_test.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/manifold_test.cpp b/test/manifold_test.cpp index 5d38f0bd0..4af7c8645 100644 --- a/test/manifold_test.cpp +++ b/test/manifold_test.cpp @@ -747,3 +747,15 @@ TEST(Manifold, CubeHull) { auto cube = Manifold::Hull(cubePts); EXPECT_FLOAT_EQ(cube.GetProperties().volume, 1); } + +TEST(Manifold, InvalidHull) { + const std::vector tooFew{{0, 0, 0}, {1, 0, 0}, {0, 1, 0}}; + const auto tooFewHull = Manifold::Hull(tooFew); + + const std::vector coplanar{ + {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}}; + const auto coplanarHull = Manifold::Hull(coplanar); + + EXPECT_EQ(tooFewHull.Status(), Manifold::Error::InvalidConstruction); + EXPECT_TRUE(coplanarHull.IsEmpty()); +} From af60a5545d549ca4331c568f8c176c36b9527c66 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Sun, 16 Jul 2023 23:40:21 -0700 Subject: [PATCH 13/18] Compose rather than boolean for batch Hull --- src/manifold/src/manifold.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index 955d4e7f2..2d698fb5e 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -829,7 +829,7 @@ Manifold Manifold::Hull() const { } Manifold Manifold::Hull(const std::vector& manifolds) { - return BatchBoolean(manifolds, OpType::Add).Hull(); + return Compose(manifolds).Hull(); } Manifold Manifold::Hull(const std::vector& pts) { From 8930393249e4683e083264a0bd9aca25eabe9c87 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Sun, 16 Jul 2023 23:46:07 -0700 Subject: [PATCH 14/18] Explicit source files for quickhull --- src/third_party/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/third_party/CMakeLists.txt b/src/third_party/CMakeLists.txt index 28300109a..783d16f0f 100644 --- a/src/third_party/CMakeLists.txt +++ b/src/third_party/CMakeLists.txt @@ -8,7 +8,6 @@ set(CLIPPER2_USINGZ "OFF" CACHE STRING "Preempt cache default of USINGZ (we only add_definitions(-D_HAS_EXCEPTIONS=0) # disable exceptions for STL add_subdirectory(clipper2/CPP) -file(GLOB_RECURSE QH_SOURCE_FILES CONFIGURE_DEPENDS quickhull/*.cpp) -add_library(quickhull ${QH_SOURCE_FILES}) +add_library(quickhull quickhull/QuickHull.cpp) target_include_directories(quickhull PUBLIC quickhull) target_compile_features(quickhull PUBLIC cxx_std_17) From f91da6dd6c4bc110a3df9e1a3a3b8fb78d837f64 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Mon, 17 Jul 2023 11:02:56 -0700 Subject: [PATCH 15/18] Drop props and return empty on too few pts --- src/manifold/include/manifold.h | 2 -- src/manifold/src/manifold.cpp | 32 +++----------------------------- test/manifold_test.cpp | 9 +++------ 3 files changed, 6 insertions(+), 37 deletions(-) diff --git a/src/manifold/include/manifold.h b/src/manifold/include/manifold.h index 0598b4093..a4430e63f 100644 --- a/src/manifold/include/manifold.h +++ b/src/manifold/include/manifold.h @@ -253,8 +253,6 @@ class Manifold { Manifold(std::shared_ptr pNode_); Manifold(std::shared_ptr pImpl_); static Manifold Invalid(); - static Manifold Hull(const std::vector& pts, - const std::vector& props, const int numProp); mutable std::shared_ptr pNode_; CsgLeafNode& GetCsgLeafNode() const; diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index 2d698fb5e..596e9dfa9 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -779,11 +779,9 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const { ExecutionParams& ManifoldParams() { return params; } -Manifold Manifold::Hull(const std::vector& pts, - const std::vector& props, const int numProp) { +Manifold Manifold::Hull(const std::vector& pts) { const int numVert = pts.size(); - // FIXME: Should there be a hull specific error? - if (numVert < 4) return Invalid(); + if (numVert < 4) return Manifold(); std::vector> vertices(numVert); for (int i = 0; i < numVert; i++) { @@ -797,11 +795,7 @@ Manifold Manifold::Hull(const std::vector& pts, const int numTris = triangles.size() / 3; Mesh mesh; - Impl::MeshRelationD relation; - relation.numProp = numProp; - relation.properties = props; mesh.vertPos = pts; - mesh.triVerts.reserve(numTris); for (int i = 0; i < numTris; i++) { const int j = i * 3; @@ -810,29 +804,9 @@ Manifold Manifold::Hull(const std::vector& pts, return Manifold(mesh); } -Manifold Manifold::Hull() const { - auto mesh = GetMeshGL(); - const int extraProps = mesh.numProp - 3; - const int numVert = mesh.NumVert(); - std::vector pts(numVert); - std::vector props; - if (extraProps > 0) props.reserve(extraProps * numVert); - for (int i = 0; i < mesh.NumVert(); i++) { - const int j = i * 3; - pts[i] = {mesh.vertProperties[j], mesh.vertProperties[j + 1], - mesh.vertProperties[j + 2]}; - for (int k = 0; k < extraProps; k++) { - props.push_back(mesh.vertProperties[j + 3 + k]); - } - } - return Hull(pts, props, extraProps); -} +Manifold Manifold::Hull() const { return Hull(GetMesh().vertPos); } Manifold Manifold::Hull(const std::vector& manifolds) { return Compose(manifolds).Hull(); } - -Manifold Manifold::Hull(const std::vector& pts) { - return Hull(pts, std::vector{}, 0); -} } // namespace manifold diff --git a/test/manifold_test.cpp b/test/manifold_test.cpp index 4af7c8645..37d767bf1 100644 --- a/test/manifold_test.cpp +++ b/test/manifold_test.cpp @@ -748,14 +748,11 @@ TEST(Manifold, CubeHull) { EXPECT_FLOAT_EQ(cube.GetProperties().volume, 1); } -TEST(Manifold, InvalidHull) { +TEST(Manifold, EmptyHull) { const std::vector tooFew{{0, 0, 0}, {1, 0, 0}, {0, 1, 0}}; - const auto tooFewHull = Manifold::Hull(tooFew); + EXPECT_TRUE(Manifold::Hull(tooFew).IsEmpty()); const std::vector coplanar{ {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}}; - const auto coplanarHull = Manifold::Hull(coplanar); - - EXPECT_EQ(tooFewHull.Status(), Manifold::Error::InvalidConstruction); - EXPECT_TRUE(coplanarHull.IsEmpty()); + EXPECT_TRUE(Manifold::Hull(coplanar).IsEmpty()); } From 935d78f667b797899dc9279d14e13c0109f4aeb3 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Mon, 17 Jul 2023 11:17:43 -0700 Subject: [PATCH 16/18] Use manifold vec overload for tictac test --- test/manifold_test.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/manifold_test.cpp b/test/manifold_test.cpp index 37d767bf1..f4514be5d 100644 --- a/test/manifold_test.cpp +++ b/test/manifold_test.cpp @@ -718,9 +718,10 @@ TEST(Manifold, TictacHull) { const float tictacHeight = 500; const int tictacSeg = 1000; const float tictacMid = tictacHeight - 2 * tictacRad; - auto sphere = Manifold::Sphere(tictacRad, tictacSeg); - auto spheres = sphere + sphere.Translate({0, 0, tictacMid}); - auto tictac = spheres.Hull(); + const auto sphere = Manifold::Sphere(tictacRad, tictacSeg); + const std::vector spheres{sphere, + sphere.Translate({0, 0, tictacMid})}; + const auto tictac = Manifold::Hull(spheres); #ifdef MANIFOLD_EXPORT if (options.exportModels) { @@ -728,7 +729,7 @@ TEST(Manifold, TictacHull) { } #endif - EXPECT_EQ(spheres.NumVert() / 2 + tictacSeg, tictac.NumVert()); + EXPECT_EQ(sphere.NumVert() + tictacSeg, tictac.NumVert()); } TEST(Manifold, HollowHull) { From 37f224d7160431abcf0fce09597e24d6487be8ab Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Mon, 17 Jul 2023 11:29:52 -0700 Subject: [PATCH 17/18] Add doc comments --- src/manifold/src/manifold.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index 596e9dfa9..432fd4bb9 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -779,6 +779,13 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const { ExecutionParams& ManifoldParams() { return params; } +/** + * Compute the convex hull of a set of points. If the given points are fewer + * than 4, or they are all coplanar, an empty Manifold will be returned. + * + * @param pts A vector of 3-dimensional points over which to compute a convex + * hull. + */ Manifold Manifold::Hull(const std::vector& pts) { const int numVert = pts.size(); if (numVert < 4) return Manifold(); @@ -804,8 +811,16 @@ Manifold Manifold::Hull(const std::vector& pts) { return Manifold(mesh); } +/** + * Compute the convex hull of this manifold. + */ Manifold Manifold::Hull() const { return Hull(GetMesh().vertPos); } +/** + * Compute the convex hull enveloping a set of manifolds. + * + * @param manifolds A vector of manifolds over which to compute a convex hull. + */ Manifold Manifold::Hull(const std::vector& manifolds) { return Compose(manifolds).Hull(); } From aaca0c82e9e4deb8b501fcd6e371ab0b58ede8d1 Mon Sep 17 00:00:00 2001 From: Geoff deRosenroll Date: Mon, 17 Jul 2023 15:36:59 -0700 Subject: [PATCH 18/18] Remove uneeded includes --- src/manifold/src/manifold.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/manifold/src/manifold.cpp b/src/manifold/src/manifold.cpp index 432fd4bb9..5019d992f 100644 --- a/src/manifold/src/manifold.cpp +++ b/src/manifold/src/manifold.cpp @@ -14,10 +14,8 @@ #include #include -#include #include #include -#include #include "boolean3.h" #include "csg_tree.h"