Skip to content

Index non-manifold edge search by sink vertex - #1240

Open
Filyus wants to merge 1 commit into
google:mainfrom
Filyus:index-non-manifold-edge-search
Open

Filyus wants to merge 1 commit into
google:mainfrom
Filyus:index-non-manifold-edge-search

Conversation

@Filyus

@Filyus Filyus commented Oct 4, 2026

Copy link
Copy Markdown

BreakNonManifoldEdges() walks the corners around each pivot vertex and, for every edge, scans the list of edges already seen around that pivot for one with the same sink vertex. The scan makes the walk quadratic in the valence of the pivot, and a single hub vertex with a high valence is enough to make it the slowest stage of CornerTable::Init().

This replaces the list with a table indexed by sink vertex. An entry keeps the first two corners stored for its sink vertex in the current walk. The scan takes the first matching corner that is not the opposite of the current edge corner, and the stored corners are all different, so the first two are the only ones it can take. The table is sized once by the vertex count and a walk counter marks which entries belong to the current pivot, so nothing is cleared between pivots.

The TODO above the restart loop is left as it is. That loop runs at most a few sweeps on the meshes I measured. The cost is in the scan.

Measurements, Release build with MSVC, encoding a closed fan with two hubs:

hub valence before after
30000 0.36 s 0.15 s
100000 2.5 s 0.22 s

Verification:

  • The encoder output is byte-identical to main on the fans and on the test meshes, including test_nm.obj and non_manifold_wrap.obj, at compression levels 0, 2, 5, 7 and 10.
  • The corner tables (opposite corners, corner vertices and left-most corners) are identical to main on 6000 random triangle soups, 3858 of which have edges broken. That probe is not part of the change.
  • The second slot keeps the result exact when two edges with the same sink vertex were stored before a match. The random soups never reached that case, so it is kept for equivalence rather than covered by a test.
  • draco_tests passes under AddressSanitizer. Behaviour is unchanged, and CornerTableTest.TestNonManifoldEdges covers it, so there is no new test.

BreakNonManifoldEdges() walks the corners around each pivot vertex and,
for every edge, scans the list of edges already seen around that pivot
for one with the same sink vertex. The scan makes the walk quadratic in
the valence of the pivot. A hub vertex with a high valence is enough to
make it the slowest stage of CornerTable::Init().

The list is replaced by a table indexed by sink vertex. An entry keeps
the first two corners stored for its sink vertex in the current walk.
The scan takes the first matching corner that is not the opposite of
the current edge corner, and the stored corners are all different, so
the first two are the only ones it can take. The table is sized once by
the vertex count and a walk counter marks which entries belong to the
current pivot, so nothing is cleared between pivots.

The TODO above the restart loop is left as it is. That loop runs at most
a few sweeps on the meshes we measured. The cost is in the scan.

Encoding a closed fan with two hubs of valence 100000 takes 2.5 s
before and 0.22 s after (Release, MSVC). The output is byte-identical
on the fan, on the test meshes including test_nm.obj and
non_manifold_wrap.obj at several compression levels, and the corner
tables are identical on 6000 random triangle soups, 3858 of which have
edges broken. The second slot keeps the result exact when two edges
with the same sink vertex were stored before a match, but the random
soups never reached it.

This branch has not been deployed

No deployments
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