Skip to content

Commit

Permalink
3d Convex Hull via Akuukka QuickHull (#489)
Browse files Browse the repository at this point in the history
Add convex hull methods to Manifold using newly vendored https://github.com/akuukka/quickhull
  • Loading branch information
geoffder authored Jul 18, 2023
1 parent a3d7991 commit 2f22b92
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/manifold/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
5 changes: 4 additions & 1 deletion src/manifold/include/manifold.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ class Manifold {
Manifold TrimByPlane(glm::vec3 normal, float originOffset) const;
///@}

Manifold Hull() const;
static Manifold Hull(const std::vector<Manifold>& manifolds);
static Manifold Hull(const std::vector<glm::vec3>& pts);

/** @name Testing hooks
* These are just for internal testing.
*/
Expand All @@ -249,7 +253,6 @@ class Manifold {
Manifold(std::shared_ptr<CsgNode> pNode_);
Manifold(std::shared_ptr<Impl> pImpl_);
static Manifold Invalid();

mutable std::shared_ptr<CsgNode> pNode_;

CsgLeafNode& GetCsgLeafNode() const;
Expand Down
49 changes: 48 additions & 1 deletion src/manifold/src/manifold.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <QuickHull.hpp>
#include <algorithm>
#include <map>
#include <numeric>
Expand Down Expand Up @@ -775,4 +776,50 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const {
}

ExecutionParams& ManifoldParams() { return params; }
} // namespace manifold

/**
* 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<glm::vec3>& pts) {
const int numVert = pts.size();
if (numVert < 4) return Manifold();

std::vector<quickhull::Vector3<double>> vertices(numVert);
for (int i = 0; i < numVert; i++) {
vertices[i] = {pts[i].x, pts[i].y, pts[i].z};
}

quickhull::QuickHull<double> 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;
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);
}

/**
* 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<Manifold>& manifolds) {
return Compose(manifolds).Hull();
}
} // namespace manifold
5 changes: 4 additions & 1 deletion src/third_party/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

add_library(quickhull quickhull/QuickHull.cpp)
target_include_directories(quickhull PUBLIC quickhull)
target_compile_features(quickhull PUBLIC cxx_std_17)
1 change: 1 addition & 0 deletions src/third_party/quickhull
Submodule quickhull added at 1ffbc6
47 changes: 46 additions & 1 deletion test/manifold_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -711,4 +711,49 @@ TEST(Manifold, PinchedVert) {
EXPECT_FALSE(touch.IsEmpty());
EXPECT_EQ(touch.Status(), Manifold::Error::NoError);
EXPECT_EQ(touch.Genus(), 0);
}
}

TEST(Manifold, TictacHull) {
const float tictacRad = 100;
const float tictacHeight = 500;
const int tictacSeg = 1000;
const float tictacMid = tictacHeight - 2 * tictacRad;
const auto sphere = Manifold::Sphere(tictacRad, tictacSeg);
const std::vector<Manifold> spheres{sphere,
sphere.Translate({0, 0, tictacMid})};
const auto tictac = Manifold::Hull(spheres);

#ifdef MANIFOLD_EXPORT
if (options.exportModels) {
ExportMesh("tictac_hull.glb", tictac.GetMesh(), {});
}
#endif

EXPECT_EQ(sphere.NumVert() + 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<glm::vec3> 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);
}

TEST(Manifold, EmptyHull) {
const std::vector<glm::vec3> tooFew{{0, 0, 0}, {1, 0, 0}, {0, 1, 0}};
EXPECT_TRUE(Manifold::Hull(tooFew).IsEmpty());

const std::vector<glm::vec3> coplanar{
{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}};
EXPECT_TRUE(Manifold::Hull(coplanar).IsEmpty());
}

0 comments on commit 2f22b92

Please sign in to comment.