Skip to content

Strip zero-area faces and restitch the T-joints they leave - #81

Merged
revarbat merged 3 commits into
mainfrom
strip-slivers
Aug 8, 2026
Merged

Strip zero-area faces and restitch the T-joints they leave#81
revarbat merged 3 commits into
mainfrom
strip-slivers

Conversation

@revarbat

@revarbat revarbat commented Aug 8, 2026

Copy link
Copy Markdown
Member

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.

stripSlivers removes the face and splits the neighbour at the middle
vertex
, 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:

Level Zero-area faces Manifold Volume
2 0 → 0 unchanged
3 4 → 0 unchanged
4 330 → 0 drifts 1.6e-07 relative, float32 accumulation over 396,686 triangles

import(repair=true) too

repairMesh dropped 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

Revar Desmera and others added 3 commits August 7, 2026 20:43
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>
@revarbat
revarbat merged commit fc9c44a into main Aug 8, 2026
3 checks passed
@revarbat
revarbat deleted the strip-slivers branch August 8, 2026 04:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant