Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3d Convex Hull #489

Merged
merged 18 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
elalish marked this conversation as resolved.
Show resolved Hide resolved
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]});
}
pca006132 marked this conversation as resolved.
Show resolved Hide resolved
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);

geoffder marked this conversation as resolved.
Show resolved Hide resolved
#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());
}
Loading