8000 Fix face removal (#1191) by pca006132 · Pull Request #1192 · elalish/manifold · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fix face removal (#1191) #1192

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

Merged
merged 10 commits into from
Mar 15, 2025
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
2 changes: 2 additions & 0 deletions src/edge_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,7 @@ void Manifold::Impl::SplitPinchedVerts() {
if (local[i]) continue;
local[i] = true;
const int vert = halfedge_[i].startVert;
if (vert == -1) continue;
size_t largest = i;
ForVert(i, [&local, &largest](int current) {
local[current] = true;
Expand Down Expand Up @@ -791,6 +792,7 @@ void Manifold::Impl::SplitPinchedVerts() {
for (size_t i = 0; i < nbEdges; ++i) {
if (halfedgeProcessed[i]) continue;
int vert = halfedge_[i].startVert;
if (vert == -1) continue;
if (vertProcessed[vert]) {
vertPos_.push_back(vertPos_[vert]);
vert = NumVert() - 1;
Expand Down
11 changes: 4 additions & 7 deletions src/face_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,6 @@
const vec3 normal = faceNormal_[face];

if (numEdge == 3) { // Single triangle
int mapping[3] = {halfedge_[firstEdge].startVert,
halfedge_[firstEdge + 1].startVert,
halfedge_[firstEdge + 2].startVert};
ivec3 tri(halfedge_[firstEdge].startVert,
halfedge_[firstEdge + 1].startVert,
halfedge_[firstEdge + 2].startVert);
Expand All @@ -132,10 +129,6 @@

addTri(face, tri, normal, halfedgeRef[firstEdge]);
} else if (numEdge == 4) { // Pair of triangles
int mapping[4] = {halfedge_[firstEdge].startVert,
halfedge_[firstEdge + 1].startVert,
halfedge_[firstEdge + 2].startVert,
halfedge_[firstEdge + 3].startVert};
const mat2x3 projection = GetAxisAlignedProjection(normal);
auto triCCW = [&projection, this](const ivec3 tri) {
return CCW(projection * this->vertPos_[tri[0]],
Expand Down Expand Up @@ -269,6 +262,10 @@
for (const int i : {0, 1, 2}) {
const int edge = 3 * tri + i;
const int pair = halfedge_[edge].pairedHalfedge;
if (pair < 0) {
edgeFace[edge] = remove;
return;

Check warning on line 267 in src/face_op.cpp

View check run for this annotation

Codecov / codecov/patch

src/face_op.cpp#L266-L267

Added lines #L266 - L267 were not covered by tests
}
const auto& ref = meshRelation_.triRef[tri];
if (ref.SameFace(meshRelation_.triRef[pair / 3])) {
edgeFace[edge] = remove;
Expand Down
56 changes: 40 additions & 16 deletions src/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@
for_each_n(autoPolicy(numTri), countAt(0), numTri,
[&triPriority, this](int tri) {
meshRelation_.triRef[tri].faceID = -1;
if (halfedge_[3 * tri].startVert < 0) {
triPriority[tri] = {0, tri};
return;
}
const vec3 v = vertPos_[halfedge_[3 * tri].startVert];
triPriority[tri] = {
length2(cross(vertPos_[halfedge_[3 * tri].endVert] - v,
Expand All @@ -189,6 +193,7 @@
if (meshRelation_.triRef[tp.tri].faceID >= 0) continue;

meshRelation_.triRef[tp.tri].faceID = tp.tri;
if (halfedge_[3 * tp.tri].startVert < 0) continue;

Check warning on line 196 in src/impl.cpp

View check run for this annotation

Codecov / codecov/patch

src/impl.cpp#L196

Added line #L196 was not covered by tests
const vec3 base = vertPos_[halfedge_[3 * tp.tri].startVert];
const vec3 normal = faceNormal_[tp.tri];
interiorHalfedges.resize(3);
Expand Down Expand Up @@ -306,25 +311,32 @@
// Mark opposed triangles for removal - this may strand unreferenced verts
// which are removed later by RemoveUnreferencedVerts() and Finish().
const int numEdge = numHalfedge / 2;
const auto body = [&](int i, int segmentEnd) {

constexpr int removedHalfedge = -2;
const auto body = [&, removedHalfedge](int i, int consecutiveStart,
int segmentEnd) {
const int pair0 = ids[i];
Halfedge h0 = halfedge_[pair0];
int k = i + numEdge;
Halfedge& h0 = halfedge_[pair0];
int k = consecutiveStart + numEdge;
while (1) {
const int pair1 = ids[k];
Halfedge h1 = halfedge_[pair1];
Halfedge& h1 = halfedge_[pair1];
if (h0.startVert != h1.endVert || h0.endVert != h1.startVert) break;
if (halfedge_[NextHalfedge(pair0)].endVert ==
halfedge_[NextHalfedge(pair1)].endVert) {
h0 = {-1, -1, -1};
h1 = {-1, -1, -1};
h0.pairedHalfedge = h1.pairedHalfedge = removedHalfedge;
// Reorder so that remaining edges pair up
if (k != i + numEdge) std::swap(ids[i + numEdge], ids[k]);
break;
}
++k;
if (k >= segmentEnd + numEdge) break;
}
if (i + 1 == segmentEnd) return consecutiveStart;
Halfedge& h1 = halfedge_[ids[i + 1]];
if (h0.startVert == h1.startVert && h0.endVert == h1.endVert)
return consecutiveStart;
return i + 1;
};

#if MANIFOLD_PAR == 1
Expand All @@ -348,23 +360,30 @@
for_each(ExecutionPolicy::Par, ranges.begin(), ranges.end(),
[&](const std::pair<int, int>& range) {
const auto [start, end] = range;
for (int i = start; i < end; ++i) body(i, end);
int consecutiveStart = start;
for (int i = start; i < end; ++i)
consecutiveStart = body(i, consecutiveStart, end);
});
#else
for (int i = 0; i < numEdge; ++i) body(i, numEdge);
int consecutiveStart = 0;
for (int i = 0; i < numEdge; ++i)
consecutiveStart = body(i, consecutiveStart, numEdge);
#endif

// Once sorted, the first half of the range is the forward halfedges, which
// correspond to their backward pair at the same offset in the second half
// of the range.
for_each_n(policy, countAt(0), numEdge, [this, &ids, numEdge](int i) {
const int pair0 = ids[i];
const int pair1 = ids[i + numEdge];
if (halfedge_[pair0].startVert >= 0) {
halfedge_[pair0].pairedHalfedge = pair1;
halfedge_[pair1].pairedHalfedge = pair0;
}
});
for_each_n(policy, countAt(0), numEdge,
[this, &ids, numEdge, removedHalfedge](int i) {
const int pair0 = ids[i];
const int pair1 = ids[i + numEdge];
if (halfedge_[pair0].pairedHalfedge != removedHalfedge) {
halfedge_[pair0].pairedHalfedge = pair1;
halfedge_[pair1].pairedHalfedge = pair0;
} else {
halfedge_[pair0] = halfedge_[pair1] = {-1, -1, -1};
}
});
}

/**
Expand Down Expand Up @@ -511,6 +530,7 @@
});

auto atomicMin = [&vertHalfedgeMap](int value, int vert) {
if (vert < 0) return;
int old = std::numeric_limits<int>::max();
while (!vertHalfedgeMap[vert].compare_exchange_strong(old, value))
if (old < value) break;
Expand All @@ -520,6 +540,10 @@
calculateTriNormal = true;
for_each_n(policy, countAt(0), NumTri(), [&](const int face) {
vec3& triNormal = faceNormal_[face];
if (halfedge_[3 * face].startVert < 0) {
triNormal = vec3(0, 0, 1);
return;
}

ivec3 triVerts;
for (int i : {0, 1, 2}) {
Expand Down
51 changes: 50 additions & 1 deletion test/manifold_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,55 @@ TEST(Manifold, InvalidInput6) {
EXPECT_EQ(tet.Status(), Manifold::Error::VertexOutOfBounds);
}

TEST(Manifold, OppositeFace) {
MeshGL gl;
gl.vertProperties = {
0, 0, 0, //
1, 0, 0, //
0, 1, 0, //
1, 1, 0, //
0, 0, 1, //
1, 0, 1, //
0, 1, 1, //
1, 1, 1, //
//
2, 0, 0, //
2, 1, 0, //
2, 0, 1, //
2, 1, 1, //
};
gl.triVerts = {
0, 1, 4, //
0, 2, 3, //
0, 3, 1, //
0, 4, 2, //
1, 3, 5, //
1, 3, 9, //
1, 5, 3, //
1, 5, 4, //
1, 8, 5, //
1, 9, 8, //
2, 4, 6, //
2, 6, 7, //
2, 7, 3, //
3, 5, 7, //
3, 7, 5, //
3, 7, 11, //
3, 11, 9, //
4, 5, 6, //
5, 7, 6, //
5, 8, 10, //
5, 10, 7, //
7, 10, 11, //
8, 9, 10, //
9, 11, 10, //
};
Manifold man(gl);
EXPECT_EQ(man.Status(), Manifold::Error::NoError);
EXPECT_EQ(man.NumVert(), 8);
EXPECT_FLOAT_EQ(man.Volume(), 2);
}

/**
* ExpectMeshes performs a decomposition, so this test ensures that compose and
* decompose are inverse operations.
Expand Down Expand Up @@ -990,4 +1039,4 @@ TEST(Manifold, MergeRefine) {
manifold = manifold.RefineToLength(1.0);
EXPECT_NEAR(manifold.Volume(), 31.21, 0.01);
}
#endif
#endif
2 changes: 1 addition & 1 deletion test/sdf_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ TEST(SDF, SphereShell) {
},
{vec3(-1.1), vec3(1.1)}, 0.01, 0, 0.0001);

EXPECT_NEAR(sphere.Genus(), 11500, 1000);
EXPECT_NEAR(sphere.Genus(), 14235, 1000);
Copy link
Owner

Choose a reason for hiding this comment

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

Does this still look good, or look better? I recall working to remove little bits of infinitely thin geometry on this one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't see infinitely thin stuff, but I see something that looks like self-intersection:

image

This is probably related to the other thing I mentioned, as there are many non-2-manifold edges here.

Copy link
Owner

Choose a reason for hiding this comment

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

Ah, right - I think my slick pre-edge collapse thing in the SDF can create even manifold edges. We should probably look at that again and see if there's a better way. I believe without that it's all guaranteed 2-manifold.


#ifdef MANIFOLD_EXPORT
if (options.exportModels)
Expand Down
0