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 Offset #668

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions src/manifold/include/manifold.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ class Manifold {
Manifold Transform(const glm::mat4x3&) const;
Manifold Mirror(glm::vec3) const;
Manifold Warp(std::function<void(glm::vec3&)>) const;
Manifold Offset(float delta, int circularSegments = 0) const;
Manifold SetProperties(
int, std::function<void(float*, glm::vec3, const float*)>) const;
Manifold CalculateCurvature(int gaussianIdx, int meanIdx) const;
Expand Down
112 changes: 112 additions & 0 deletions src/manifold/src/manifold.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,32 @@ struct MakeTri {
}
};

struct ConvexEdge {
VecView<bool> vertConvex;
VecView<const Halfedge> halfedge;
VecView<const glm::vec3> vertPos;
VecView<const glm::vec3> faceNormal;
const bool inset;

bool operator()(int idx) {
const Halfedge edge = halfedge[idx];
if (!edge.IsForward()) return false;

const glm::vec3 normal0 = faceNormal[edge.face];
const glm::vec3 normal1 = faceNormal[halfedge[edge.pairedHalfedge].face];
if (glm::all(glm::equal(normal0, normal1))) return false;

const glm::vec3 edgeVec = vertPos[edge.endVert] - vertPos[edge.startVert];
const bool convex = ((inset ? -1 : 1) *
glm::dot(edgeVec, glm::cross(normal0, normal1))) > 0;
if (convex) {
vertConvex[edge.startVert] = true;
vertConvex[edge.endVert] = true;
}
return convex;
}
};

struct UpdateProperties {
float* properties;
const int numProp;
Expand Down Expand Up @@ -569,6 +595,92 @@ Manifold Manifold::Warp(std::function<void(glm::vec3&)> warpFunc) const {
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}

/**
* Inflate the Manifold by the specified delta, rounding convex vertices.
*
* @param delta Positive deltas will add volume to all surfaces of the Manifold,
* dilating it. Negative deltas will have the opposite effect, eroding it.
* @param circularSegments Denotes the resolution of the sphere used at convex
* vertices. Default is calculated by the static Quality defaults according to
* the radius, which is delta.
*/
Manifold Manifold::Offset(float delta, int circularSegments) const {
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());

if (delta == 0) {
return Manifold(std::make_shared<CsgLeafNode>(pImpl));
}

const bool inset = delta < 0;
const float radius = glm::abs(delta);
const int n = circularSegments > 0 ? (circularSegments + 3) / 4
: Quality::GetCircularSegments(delta) / 4;
const Manifold sphere = Manifold::Sphere(radius, circularSegments);
const Manifold cylinder = Manifold::Cylinder(1, radius, radius, 4 * n);
const SimplePolygon triangle = {{-1, -1}, {1, 0}, {0, 1}};
const Manifold block = Manifold::Extrude(triangle, 1);

Vec<int> convexEdges(NumEdge());
Vec<bool> vertConvex(NumVert(), false);
convexEdges.resize(
copy_if(countAt(0), countAt(pImpl->halfedge_.size()), convexEdges.begin(),
ConvexEdge({vertConvex, pImpl->halfedge_, pImpl->vertPos_,
pImpl->faceNormal_, inset})) -
convexEdges.begin());

Vec<int> convexVerts(NumVert());
convexVerts.resize(copy_if(countAt(0), countAt(NumVert()), vertConvex.begin(),
convexVerts.begin(), thrust::identity()) -
convexVerts.begin());

const int edgeOffset = 1 + NumTri();
const int vertOffset = edgeOffset + convexEdges.size();
std::vector<Manifold> batch(vertOffset + convexVerts.size());
batch[0] = *this;

for_each_n(countAt(0), NumTri(), [&batch, &block, &pImpl, radius](int tri) {
glm::mat3 triPos;
for (const int i : {0, 1, 2}) {
triPos[i] = pImpl->vertPos_[pImpl->halfedge_[3 * tri + i].startVert];
}
const glm::vec3 normal = radius * pImpl->faceNormal_[tri];
batch[1 + tri] = block.Warp([triPos, normal](glm::vec3& pos) {
const float dir = pos.z > 0 ? 1.0f : -1.0f;
if (pos.x < 0) {
pos = triPos[0];
} else if (pos.x > 0) {
pos = triPos[1];
} else {
pos = triPos[2];
}
pos += dir * normal;
});
});

for_each_n(countAt(0), convexEdges.size(),
[&batch, &cylinder, &pImpl, &convexEdges, edgeOffset](int idx) {
const Halfedge halfedge = pImpl->halfedge_[convexEdges[idx]];
glm::vec3 edge = pImpl->vertPos_[halfedge.endVert] -
pImpl->vertPos_[halfedge.startVert];
const float length = glm::length(edge);
// Reverse RotateUp
edge.x *= -1;
edge.y *= -1;
batch[edgeOffset + idx] =
cylinder.Scale({1, 1, length})
.Transform(RotateUp(edge))
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@starseeker This might help you.

.Translate(pImpl->vertPos_[halfedge.startVert]);
});

for_each_n(countAt(0), convexVerts.size(),
[&batch, &sphere, &pImpl, &convexVerts, vertOffset](int idx) {
batch[vertOffset + idx] =
sphere.Translate(pImpl->vertPos_[convexVerts[idx]]);
});

return BatchBoolean(batch, inset ? OpType::Subtract : OpType::Add);
}

/**
* Create a new copy of this manifold with updated vertex properties by
* supplying a function that takes the existing position and properties as
Expand Down
5 changes: 4 additions & 1 deletion src/utilities/include/public.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ inline float cosd(float x) { return sind(x + 90.0f); }
inline glm::mat4x3 RotateUp(glm::vec3 up) {
up = glm::normalize(up);
glm::vec3 axis = glm::cross(up, {0, 0, 1});
float angle = glm::asin(glm::length(axis));
float length = glm::length(axis);
if (!isfinite(length)) length = 0;
float angle = glm::asin(glm::min(length, 1.0f));
if (length == 0) axis = {1, 0, 0};
if (glm::dot(up, {0, 0, 1}) < 0) angle = glm::pi<float>() - angle;
return glm::mat4x3(glm::rotate(glm::mat4(1), angle, axis));
}
Expand Down
19 changes: 19 additions & 0 deletions test/boolean_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,25 @@ TEST(Boolean, Subtract) {
first.GetMesh();
}

TEST(Boolean, Offset) {
PolygonParams().processOverlaps = true;

Manifold cutout = Manifold::Cube(glm::vec3(100), true) - Manifold::Sphere(60);
Manifold fat = cutout.Offset(5);
Manifold thin = cutout.Offset(-5);
EXPECT_EQ(fat.Genus(), cutout.Genus());
EXPECT_EQ(thin.Genus(), -7);

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

PolygonParams().processOverlaps = false;
}

TEST(Boolean, Close) {
PolygonParams().processOverlaps = true;

Expand Down