Spaces:
Running on Zero
Running on Zero
kwabs22 Claude Opus 5.5 commited on
Commit ·
65f605e
1
Parent(s): e0f9b43
Fall back to vertex clustering when quadric decimation stalls
Browse filesLive check after e0f9b43: the cat now reaches 400 faces, but the SimReady
desk lamp stayed at 22,228 (floor: true, a 522k-character inline page).
Quadric decimation stalls on its sliver triangles at ~21k whatever the
aggression or welding. When that happens, decimate snaps vertices to the
finest grid that reaches the target. Tested on the lamp's real geometry:
it reaches 20000/5000/1000/400/250/60, keeping the arms, shade and base,
which quadric-after-clustering dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- SPEC.md +4 -1
- vli/simplify.py +31 -2
SPEC.md
CHANGED
|
@@ -106,7 +106,10 @@ that exists but maps to the wrong thing shows up before submitting.
|
|
| 106 |
|
| 107 |
Ladder rungs: original → 20k / 5k / 1k / 250 / 60 faces → convex hull → oriented
|
| 108 |
box. Where the object stops reading as its word is judged in the discussion.
|
| 109 |
-
Decimation welds split vertices (UV and normal seams) between passes.
|
|
|
|
|
|
|
|
|
|
| 110 |
labelled with the faces they actually have, and the ladder stops descending when
|
| 111 |
a rung doesn't reduce, so a stalled mesh never shows as several identical rungs.
|
| 112 |
|
|
|
|
| 106 |
|
| 107 |
Ladder rungs: original → 20k / 5k / 1k / 250 / 60 faces → convex hull → oriented
|
| 108 |
box. Where the object stops reading as its word is judged in the discussion.
|
| 109 |
+
Decimation welds split vertices (UV and normal seams) between passes. When quadric
|
| 110 |
+
decimation still stalls (CAD meshes with sliver triangles, e.g. a SimReady lamp stuck
|
| 111 |
+
at ~21k faces), vertex clustering takes over: cruder, but it always reduces and keeps
|
| 112 |
+
thin parts. Rungs are
|
| 113 |
labelled with the faces they actually have, and the ladder stops descending when
|
| 114 |
a rung doesn't reduce, so a stalled mesh never shows as several identical rungs.
|
| 115 |
|
vli/simplify.py
CHANGED
|
@@ -34,11 +34,38 @@ def _welded(mesh):
|
|
| 34 |
process=False)
|
| 35 |
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
def decimate(mesh, faces):
|
| 38 |
"""Reduce toward `faces`, re-welding between passes while that still helps.
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
|
|
|
| 42 |
"""
|
| 43 |
color = base_color(mesh)
|
| 44 |
out = _welded(mesh)
|
|
@@ -47,6 +74,8 @@ def decimate(mesh, faces):
|
|
| 47 |
if len(nxt.faces) > 0.95 * len(out.faces):
|
| 48 |
break
|
| 49 |
out = nxt
|
|
|
|
|
|
|
| 50 |
out.visual = trimesh.visual.ColorVisuals(out, face_colors=np.tile(color, (len(out.faces), 1)))
|
| 51 |
return out
|
| 52 |
|
|
|
|
| 34 |
process=False)
|
| 35 |
|
| 36 |
|
| 37 |
+
def _clustered(mesh, cell):
|
| 38 |
+
"""Vertex clustering: snap vertices to a grid of `cell` metres and merge each cell."""
|
| 39 |
+
v, f = np.asarray(mesh.vertices, dtype=np.float64), np.asarray(mesh.faces, dtype=np.int64)
|
| 40 |
+
inv = np.unique(np.floor((v - v.min(axis=0)) / cell).astype(np.int64), axis=0, return_inverse=True)[1].ravel()
|
| 41 |
+
nv = np.zeros((inv.max() + 1, 3))
|
| 42 |
+
np.add.at(nv, inv, v)
|
| 43 |
+
nv /= np.bincount(inv)[:, None]
|
| 44 |
+
nf = inv[f]
|
| 45 |
+
nf = nf[(nf[:, 0] != nf[:, 1]) & (nf[:, 1] != nf[:, 2]) & (nf[:, 0] != nf[:, 2])]
|
| 46 |
+
return trimesh.Trimesh(nv, nf, process=False)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _cluster_to(mesh, faces):
|
| 50 |
+
"""The finest grid that gets to `faces` or fewer (log-scale bisection on the cell size)."""
|
| 51 |
+
ext = float(mesh.extents.max()) or 1.0
|
| 52 |
+
lo, hi, best = ext / 2000, ext / 2, None
|
| 53 |
+
for _ in range(20):
|
| 54 |
+
cell = (lo * hi) ** 0.5
|
| 55 |
+
m = _clustered(mesh, cell)
|
| 56 |
+
if len(m.faces) > faces:
|
| 57 |
+
lo = cell
|
| 58 |
+
else:
|
| 59 |
+
hi, best = cell, m
|
| 60 |
+
return best
|
| 61 |
+
|
| 62 |
+
|
| 63 |
def decimate(mesh, faces):
|
| 64 |
"""Reduce toward `faces`, re-welding between passes while that still helps.
|
| 65 |
|
| 66 |
+
Quadric decimation stalls on some CAD meshes (long sliver triangles: a SimReady
|
| 67 |
+
lamp stopped at ~21k faces whatever the settings). Then vertex clustering takes
|
| 68 |
+
over: cruder, but it always reduces and keeps thin parts that quadric drops.
|
| 69 |
"""
|
| 70 |
color = base_color(mesh)
|
| 71 |
out = _welded(mesh)
|
|
|
|
| 74 |
if len(nxt.faces) > 0.95 * len(out.faces):
|
| 75 |
break
|
| 76 |
out = nxt
|
| 77 |
+
if len(out.faces) > faces:
|
| 78 |
+
out = _cluster_to(out, faces) or out
|
| 79 |
out.visual = trimesh.visual.ColorVisuals(out, face_colors=np.tile(color, (len(out.faces), 1)))
|
| 80 |
return out
|
| 81 |
|