Strip zero-area faces and restitch the T-joints they leave - #81
Merged
Conversation
Deleting a sliver is not enough on its own. Its three vertices are collinear, so one lies between the other two; dropping the face leaves that middle vertex sitting on the interior of the neighbour's edge -- a T-joint -- and the two sides no longer share an edge, which reads as a hole. That is why the checker used to report 722 boundary edges on a mesh that had none. stripSlivers removes the face and splits the neighbour at the middle vertex, so the edges match again. No vertex moves and no geometry changes: the split point already lay exactly on the edge it is inserted into. Volume is unchanged, and the test asserts it rather than trusting the argument. Repeated, because removing one sliver can leave its neighbour's halves degenerate in turn. On a level-4 Menger sponge that is 8 passes, converging on its own rather than at the cap. Not complete: that sponge goes from 330 zero-area faces to 2. The two that remain are ones whose neighbour was consumed by another sliver in the same pass and could not be found again. Reported through the return value rather than passed over in silence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two corrections that between them clear the last of the zero-area faces on a level-4 Menger sponge: 330 -> 0, where it previously stalled at 2. A zero-area face with two corners at the same position is a needle, not a T-joint. Its two long edges run between the same pair of points, so once it is gone the faces on either side already meet and there is nothing to split -- and no middle vertex to split at, so trying was wrong. The coincident pair is merged as well, which makes them share the edge by index and not only by position. The stall was a different thing: two slivers sharing their long edge are each other's only candidate neighbour, so each kept splitting into the other. A non-sliver neighbour is now preferred. Only preferred, not required -- refusing to split into a sliver at all made it far worse (330 -> 160), because slivers are commonly adjacent and that left every such pair untouched. Splitting into one still makes progress, since the halves are smaller and the next pass reconsiders them. Volume drifts by 1.6e-07 relative at level 4, which is float32 accumulation over 396,686 triangles, not geometry moving. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
repairMesh dropped faces by repeated index, which disposes of needles -- welding collapses the coincident pair and leaves exactly that -- but a sliver whose three corners are distinct and collinear survived it untouched. An imported STL is where those turn up, so repair=true is precisely where it mattered. The gap was demonstrated before it was closed: the test failed with 'sliver survived repair' while the needle test alongside it passed, which is what shows welding was already covering one case and only the T-joint one was missing. Stripping runs last. Welding has to come first for needles, and a sliver does not affect welding, orientation or hole-finding -- it is a proper face with two neighbours, so it opens no boundary for those steps to trip over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Deleting a zero-area face is not enough on its own, which is what made the
last round's diagnosis confusing.
A sliver's three vertices are collinear, so one lies between the other
two. Dropping the face leaves that middle vertex sitting on the interior of
the neighbour's edge — a T-joint — and the two sides no longer share an
edge. That reads as a hole, and is exactly why the checker used to report
722 boundary edges on a mesh that had none.
stripSliversremoves the face and splits the neighbour at the middlevertex, so the edges match again. No vertex moves: the split point already
lay exactly on the edge it is inserted into. Volume is unchanged, and the
test asserts that rather than trusting the argument.
Needles are a separate case
A zero-area face with two corners at the same position is a needle, not
a T-joint. Its two long edges run between the same pair of points, so once
it is gone the faces on either side already meet — there is nothing to
split, and no middle vertex to split at. It is removed outright and the
coincident pair merged, which makes them share the edge by index as well as
by position.
Two slivers can deadlock
Sharing their long edge, each is the other's only candidate neighbour, so
each kept splitting into the other. A non-sliver neighbour is preferred
now — preferred, not required: refusing sliver neighbours outright made
it far worse (330 → 160), because slivers are commonly adjacent and every
such pair went untouched.
Result
A level-4 Menger sponge built from three intersected extrusions:
import(repair=true) too
repairMeshdropped faces by repeated index, which disposes of needles —welding collapses the coincident pair and leaves exactly that — but a
collinear sliver survived it untouched. An imported STL is where those turn
up, so that is where it mattered. The gap was demonstrated before it was
closed: the test failed with "sliver survived repair" while the needle
test beside it passed.
1432 tests pass.
🤖 Generated with Claude Code