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

Add MinkowskiSum and MinkowskiDifference #666

Open
wants to merge 54 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
bcd06a4
Add Naive Minkowski Sum Implementation
zalo Dec 19, 2023
89a9dea
Fix CI Bellyaching
zalo Dec 20, 2023
30be9a0
Add Python Minkowski Test
zalo Dec 20, 2023
f1e245f
Enable Determinism for the Failing CI Tests
zalo Dec 20, 2023
d51976a
Fix Python Test
zalo Dec 20, 2023
12990f8
Fix Python Test Again
zalo Dec 20, 2023
5f52540
Make Python Test Cheaper so it doesn't segfault
zalo Dec 20, 2023
755a55c
Add Insetting Option
zalo Dec 20, 2023
42b8cf6
Update Python Binding Too
zalo Dec 20, 2023
00d92de
Switch to a Member Function
zalo Dec 21, 2023
93b6f30
Remove the useThreading option
zalo Dec 21, 2023
0b7e34d
Merge remote-tracking branch 'upstream/master' into feat-naive-minkowski
zalo Dec 21, 2023
953eaf3
Fix the Python Binding
zalo Dec 21, 2023
2d93b10
And the example
zalo Dec 21, 2023
b668659
Fix Formatting
zalo Dec 21, 2023
847ae97
Split into Add/Subtract Functions, Add C Binding
zalo Dec 21, 2023
1587164
Make Methods const
zalo Dec 21, 2023
ebe2baa
Update the tests; ensure that the inset operation works with convex s…
zalo Dec 21, 2023
5f20c54
Fix Python Example Again
zalo Dec 21, 2023
622556e
Add JS Bindings
zalo Dec 21, 2023
ca0a35d
Fix Formatting
zalo Dec 21, 2023
807910e
Fix Formatting Properly
zalo Dec 21, 2023
3ba8302
Change Function Names to Sum/Difference
zalo Dec 21, 2023
49a50c2
Fix formatting
zalo Dec 21, 2023
0f840b4
Move Tests to boolean_test
zalo Dec 22, 2023
98d9ca9
Fix Formatting
zalo Dec 22, 2023
952a54f
Fix NonConvexConvex Test
zalo Dec 22, 2023
3928719
Fix Formatting
zalo Dec 22, 2023
f5ceb05
Add Surface Area
zalo Dec 22, 2023
84e08f2
passing the last test
elalish Dec 22, 2023
8fdf164
forgot these
elalish Dec 22, 2023
48f174a
Merge pull request #1 from elalish/fixMinkowski
zalo Dec 22, 2023
377d58b
Fix Test?
zalo Dec 22, 2023
cac0610
Fix for Disconnected Manifolds
zalo Dec 23, 2023
1a5611e
Codify IsConvex, Make Python Cheaper
zalo Dec 27, 2023
038e896
Refactor to use Impl
zalo Dec 27, 2023
560b3cb
Merge remote-tracking branch 'upstream/master' into feat-naive-minkow…
zalo Dec 27, 2023
cc6cbd5
Merge remote-tracking branch 'upstream/master' into feat-naive-minkowski
zalo Dec 27, 2023
6ca9645
Remove Dangling Line Return
zalo Dec 27, 2023
418cf1c
Merge branch 'feat-naive-minkowski-refactor' into feat-naive-minkowski
zalo Dec 29, 2023
8599a82
Add another early exit
zalo Jan 4, 2024
d87180b
Merge remote-tracking branch 'upstream/master' into feat-naive-minkowski
zalo Sep 16, 2024
c530b7a
Fix Glaring Bugs...
zalo Sep 16, 2024
114d2c1
Fix Formatting
zalo Sep 16, 2024
adc1163
Again
zalo Sep 16, 2024
922e6b4
Fix Mesh Export
zalo Sep 16, 2024
3ee19b8
Run SimplifyTopology on the output of Minkowski
zalo Sep 16, 2024
31700fc
Update Tests
zalo Sep 17, 2024
baefc51
Update Formatting
zalo Sep 17, 2024
098d2b1
Make more work multithreaded
zalo Sep 17, 2024
2dd5b8b
Fix formatting
zalo Sep 17, 2024
d1f35e2
Disable test cheating?
zalo Sep 17, 2024
be0f63b
Revert "Disable test cheating?"
zalo Sep 17, 2024
7dd55dd
Merge remote-tracking branch 'upstream/master' into feat-naive-minkowski
zalo Oct 8, 2024
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
22 changes: 22 additions & 0 deletions bindings/python/examples/minkowski.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import numpy as np
from manifold3d import Manifold


def run():
small_cube = Manifold.cube([0.1, 0.1, 0.1], True)
cube_vertices = small_cube.to_mesh().vert_properties[:, :3]
star = Manifold.as_original(small_cube)
for offset in [
[[0.2, 0.0, 0.0]],
[[-0.2, 0.0, 0.0]],
[[0.0, 0.2, 0.0]],
[[0.0, -0.2, 0.0]],
[[0.0, 0.0, 0.2]],
[[0.0, 0.0, -0.2]],
]:
star += Manifold.hull_points(np.concatenate((cube_vertices, offset), axis=0))

sphere = Manifold.sphere(0.6, 20)
cube = Manifold.cube([1.0, 1.0, 1.0], True)
sphereless_cube = cube - sphere
return Manifold.minkowski(sphereless_cube, star, False)
3 changes: 3 additions & 0 deletions bindings/python/manifold3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ NB_MODULE(manifold3d, m) {
return Manifold::Hull(vec);
},
"Compute the convex hull enveloping a set of 3d points.")
.def_static("minkowski", Manifold::Minkowski, nb::arg("a"), nb::arg("b"),
nb::arg("inset"), nb::arg("useThreading"),
"Compute the minkowski sum of two manifolds.")
.def(
"transform",
[](Manifold &self, nb::ndarray<float, nb::shape<3, 4>> &mat) {
Expand Down
7 changes: 7 additions & 0 deletions src/manifold/include/manifold.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,13 @@ class Manifold {
static Manifold Hull(const std::vector<glm::vec3>& pts);
///@}

/** @name Minkowski Functions
*/
///@{
static Manifold Minkowski(const Manifold& a, const Manifold& b,
bool inset = false, bool useThreading = false);
zalo marked this conversation as resolved.
Show resolved Hide resolved
///@}

/** @name Testing hooks
* These are just for internal testing.
*/
Expand Down
24 changes: 24 additions & 0 deletions src/manifold/src/face_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,28 @@ CrossSection Manifold::Impl::Project() const {

return CrossSection(polys).Simplify(precision_);
}

std::vector<int> Manifold::Impl::ReflexFaces(double tolerance) const {
std::unordered_set<int> uniqueReflexFaceSet;
zalo marked this conversation as resolved.
Show resolved Hide resolved
for (size_t i = 0; i < halfedge_.size(); i++) {
Halfedge halfedge = halfedge_[i];
int faceA = halfedge.face;
int faceB = halfedge_[halfedge.pairedHalfedge].face;
glm::dvec3 tangent =
glm::cross((glm::dvec3)faceNormal_[faceA],
(glm::dvec3)vertPos_[halfedge_[i].endVert] -
(glm::dvec3)vertPos_[halfedge_[i].startVert]);
double tangentProjection =
glm::dot((glm::dvec3)faceNormal_[faceB], tangent);
// If we've found a pair of reflex triangles, add them to the set
if (tangentProjection > tolerance) {
uniqueReflexFaceSet.insert(faceA);
uniqueReflexFaceSet.insert(faceB);
}
}
std::vector<int> uniqueFaces; // Copy to a vector for indexed access
uniqueFaces.insert(uniqueFaces.end(), uniqueReflexFaceSet.begin(),
uniqueReflexFaceSet.end());
return uniqueFaces;
}
} // namespace manifold
1 change: 1 addition & 0 deletions src/manifold/src/impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ struct Manifold::Impl {
glm::mat3x2 projection) const;
CrossSection Slice(float height) const;
CrossSection Project() const;
std::vector<int> ReflexFaces(double tolerance = 1e-8) const;

// edge_op.cu
void SimplifyTopology();
Expand Down
128 changes: 128 additions & 0 deletions src/manifold/src/manifold.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,40 @@ struct UpdateProperties {
}
};

struct ComputeTriangleHull {
const Manifold* manifold;
std::vector<glm::vec3>* vertPos;
std::vector<Manifold>* output;
void operator()(thrust::tuple<int, glm::ivec3&> inOut) {
const int idx = thrust::get<0>(inOut);
glm::ivec3& tri = thrust::get<1>(inOut);
(*output)[idx] = Manifold::Hull({(*manifold).Translate((*vertPos)[tri.x]),
(*manifold).Translate((*vertPos)[tri.y]),
(*manifold).Translate((*vertPos)[tri.z])});
}
};

struct ComputeTriangleTriangleHull {
std::vector<glm::vec3>* vertPosAPtr;
std::vector<glm::vec3>* vertPosBPtr;
std::vector<Manifold>* output;
void operator()(thrust::tuple<int, std::pair<glm::ivec3, glm::ivec3>> inOut) {
const int idx = thrust::get<0>(inOut);
std::pair<glm::ivec3, glm::ivec3> tris = thrust::get<1>(inOut);
std::vector<glm::vec3> vertPosA = *vertPosAPtr, vertPosB = *vertPosBPtr;
(*output)[idx] =
Manifold::Hull({vertPosA[tris.first.x] + vertPosB[tris.second.x],
vertPosA[tris.first.x] + vertPosB[tris.second.y],
vertPosA[tris.first.x] + vertPosB[tris.second.z],
vertPosA[tris.first.y] + vertPosB[tris.second.x],
vertPosA[tris.first.y] + vertPosB[tris.second.y],
vertPosA[tris.first.y] + vertPosB[tris.second.z],
vertPosA[tris.first.z] + vertPosB[tris.second.x],
vertPosA[tris.first.z] + vertPosB[tris.second.y],
vertPosA[tris.first.z] + vertPosB[tris.second.z]});
}
};

Manifold Halfspace(Box bBox, glm::vec3 normal, float originOffset) {
normal = glm::normalize(normal);
Manifold cutter =
Expand Down Expand Up @@ -859,4 +893,98 @@ Manifold Manifold::Hull() const { return Hull(GetMesh().vertPos); }
Manifold Manifold::Hull(const std::vector<Manifold>& manifolds) {
return Compose(manifolds).Hull();
}

/**
* Compute the minkowski sum of two manifolds.
*
* @param a The first manifold in the sum.
* @param b The second manifold in the sum.
*/
Manifold Manifold::Minkowski(const Manifold& a, const Manifold& b, bool inset,
bool useThreading) {
std::vector<Manifold> composedHulls({a});
bool aConvex = a.GetCsgLeafNode().GetImpl()->ReflexFaces().size() == 0;
bool bConvex = b.GetCsgLeafNode().GetImpl()->ReflexFaces().size() == 0;

// If the convex manifold was supplied first, swap them!
Manifold aM = a, bM = b;
if (aConvex && !bConvex) {
aM = b;
bM = a;
aConvex = !aConvex;
bConvex = !bConvex;
}

manifold::Mesh aMesh = aM.GetMesh();

// Convex-Convex Minkowski: Very Fast
if (aConvex && bConvex) {
std::vector<Manifold> simpleHull;
for (glm::vec3 vertex : aMesh.vertPos) {
simpleHull.push_back(bM.Translate(vertex));
}
composedHulls.push_back(Manifold::Hull(simpleHull));
// Convex - Non-Convex Minkowski: Slower
} else if (!aConvex && bConvex) {
if (useThreading) {
composedHulls.resize(aMesh.triVerts.size() + 1);
thrust::for_each_n(
thrust::host, zip(countAt(1), aMesh.triVerts.begin()),
aMesh.triVerts.size(),
ComputeTriangleHull({&bM, &aMesh.vertPos, &composedHulls}));
} else {
std::vector<std::vector<Manifold>> composedParts;
for (glm::ivec3 vertexIndices : aMesh.triVerts) {
composedParts.push_back({bM.Translate(aMesh.vertPos[vertexIndices.x]),
bM.Translate(aMesh.vertPos[vertexIndices.y]),
bM.Translate(aMesh.vertPos[vertexIndices.z])});
}
std::vector<Manifold> newHulls;
newHulls.reserve(composedParts.size());
newHulls.resize(composedParts.size());
thrust::for_each_n(
thrust::host, zip(composedParts.begin(), newHulls.begin()),
composedParts.size(),
[](thrust::tuple<std::vector<Manifold>, Manifold&> inOut) {
thrust::get<1>(inOut) = Manifold::Hull(thrust::get<0>(inOut));
});
composedHulls.insert(composedHulls.end(), newHulls.begin(),
newHulls.end());
}
// Non-Convex - Non-Convex Minkowski: Very Slow
} else if (!aConvex && !bConvex) {
manifold::Mesh bMesh = bM.GetMesh();
if (useThreading) {
std::vector<std::pair<glm::ivec3, glm::ivec3>> trianglePairs;
for (glm::ivec3 aVertexIndices : aMesh.triVerts) {
for (glm::ivec3 bVertexIndices : bMesh.triVerts) {
trianglePairs.push_back({aVertexIndices, bVertexIndices});
}
}
composedHulls.resize(trianglePairs.size() + 1);
thrust::for_each_n(thrust::host, zip(countAt(1), trianglePairs.begin()),
trianglePairs.size(),
ComputeTriangleTriangleHull(
{&aMesh.vertPos, &bMesh.vertPos, &composedHulls}));
} else {
for (glm::ivec3 aIndices : aMesh.triVerts) {
for (glm::ivec3 bIndices : bMesh.triVerts) {
composedHulls.push_back(Manifold::Hull(
{aMesh.vertPos[aIndices.x] + bMesh.vertPos[bIndices.x],
aMesh.vertPos[aIndices.x] + bMesh.vertPos[bIndices.y],
aMesh.vertPos[aIndices.x] + bMesh.vertPos[bIndices.z],
aMesh.vertPos[aIndices.y] + bMesh.vertPos[bIndices.x],
aMesh.vertPos[aIndices.y] + bMesh.vertPos[bIndices.y],
aMesh.vertPos[aIndices.y] + bMesh.vertPos[bIndices.z],
aMesh.vertPos[aIndices.z] + bMesh.vertPos[bIndices.x],
aMesh.vertPos[aIndices.z] + bMesh.vertPos[bIndices.y],
aMesh.vertPos[aIndices.z] + bMesh.vertPos[bIndices.z]}));
}
}
}
}
return Manifold::BatchBoolean(composedHulls, inset
? manifold::OpType::Subtract
: manifold::OpType::Add);
}
} // namespace manifold
71 changes: 71 additions & 0 deletions test/manifold_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -784,3 +784,74 @@ TEST(Manifold, EmptyHull) {
{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}};
EXPECT_TRUE(Manifold::Hull(coplanar).IsEmpty());
}

TEST(Manifold, ConvexConvexMinkowski) {
zalo marked this conversation as resolved.
Show resolved Hide resolved
Manifold sphere = Manifold::Sphere(0.1, 20);
Manifold cube = Manifold::Cube();
Manifold sum = Manifold::Minkowski(cube, sphere);
EXPECT_FLOAT_EQ(sum.GetProperties().volume, 1.6966598f);
}

TEST(Manifold, ConvexConvexMinkowskiThreaded) {
Manifold sphere = Manifold::Sphere(0.1, 20);
Manifold cube = Manifold::Cube();
Manifold sum = Manifold::Minkowski(cube, sphere, true);
EXPECT_FLOAT_EQ(sum.GetProperties().volume, 1.6966598f);
}

TEST(Manifold, NonConvexConvexMinkowski) {
Manifold sphere = Manifold::Sphere(0.6, 20);
Manifold cube = Manifold::Cube({1.0, 1.0, 1.0}, true);
Manifold nonConvex = cube - sphere;
Manifold sum = Manifold::Minkowski(nonConvex, Manifold::Sphere(0.1, 20));
EXPECT_FLOAT_EQ(sum.GetProperties().volume, 1.0686193f);
}

TEST(Manifold, NonConvexConvexMinkowskiThreaded) {
Manifold sphere = Manifold::Sphere(0.6, 20);
Manifold cube = Manifold::Cube({1.0, 1.0, 1.0}, true);
Manifold nonConvex = cube - sphere;
Manifold sum =
Manifold::Minkowski(nonConvex, Manifold::Sphere(0.1, 20), true);
EXPECT_FLOAT_EQ(sum.GetProperties().volume, 1.0686193f);
}

TEST(Manifold, NonConvexNonConvexMinkowski) {
zalo marked this conversation as resolved.
Show resolved Hide resolved
ManifoldParams().deterministic = true;
Manifold star = Manifold::Cube({0.1, 0.1, 0.1}, true);
std::vector<glm::vec3> verts = star.GetMesh().vertPos;
for (glm::vec3 point :
{glm::vec3(0.2, 0.0, 0.0), glm::vec3(-0.2, 0.0, 0.0),
glm::vec3(0.0, 0.2, 0.0), glm::vec3(0.0, -0.2, 0.0),
glm::vec3(0.0, 0.0, 0.2), glm::vec3(0.0, 0.0, -0.2)}) {
verts.push_back(point);
star += Manifold::Hull(verts);
verts.pop_back();
}

Manifold sphere = Manifold::Sphere(0.6, 20);
Manifold cube = Manifold::Cube({1.0, 1.0, 1.0}, true);
Manifold nonConvex = cube - sphere;
Manifold sum = Manifold::Minkowski(nonConvex, star);
EXPECT_FLOAT_EQ(sum.GetProperties().volume, 1.643248);
}

TEST(Manifold, NonConvexNonConvexMinkowskiThreaded) {
ManifoldParams().deterministic = true;
Manifold star = Manifold::Cube({0.1, 0.1, 0.1}, true);
std::vector<glm::vec3> verts = star.GetMesh().vertPos;
for (glm::vec3 point :
{glm::vec3(0.2, 0.0, 0.0), glm::vec3(-0.2, 0.0, 0.0),
glm::vec3(0.0, 0.2, 0.0), glm::vec3(0.0, -0.2, 0.0),
glm::vec3(0.0, 0.0, 0.2), glm::vec3(0.0, 0.0, -0.2)}) {
verts.push_back(point);
star += Manifold::Hull(verts);
verts.pop_back();
}

Manifold sphere = Manifold::Sphere(0.6, 20);
Manifold cube = Manifold::Cube({1.0, 1.0, 1.0}, true);
Manifold nonConvex = cube - sphere;
Manifold sum = Manifold::Minkowski(nonConvex, star, true);
EXPECT_FLOAT_EQ(sum.GetProperties().volume, 1.643248);
}
Loading