UNPKG

@playcanvas/splat-transform

Version:

Library and CLI tool for 3D Gaussian splat format conversion and transformation

64 lines (63 loc) 3.32 kB
import type { Mesh } from './marching-cubes'; /** * Losslessly reduce coplanar regions of a marching-cubes mesh by * topology-preserving vertex removal. * * For a closed manifold MC mesh, a vertex `v` is "lossless-removable" iff * its incident-tri fan, walked in cyclic order, falls into one of: * * 1. K=1 coplanar fan. Every triangle in v's fan lies on the same plane * (same unit normal and same plane offset, within tolerance). Removing * v is the inverse of vertex split: re-triangulate the boundary * polygon in the same plane. * * 2. K=2 collinear seam. The fan splits into exactly two contiguous * coplanar arcs (different planes). The two crease vertices `a` and * `b` (the boundary points where the plane changes around v) are * collinear with v in 3D, with v between them. Removing v collapses * the two crease edges (v-a, v-b) into a single straight edge (a-b) * that lies in both planes; each arc's polygon re-triangulates * without v. * * Vertices with K >= 3 (multi-way corners) are kept. * * Removing a removable v is exact-lossless: the surface footprint is * identical, no vertex moves and none are created. The transformation * is the inverse of vertex split, so it is topology-preserving by * construction: * * - No T-junctions. Every old vertex on the polygon boundary remains a * vertex of every triangle that previously touched it. Adjacent fused * regions and verbatim regions stay coupled at every shared vertex. * - Watertight. The closed manifold structure is preserved across * removal. Both the K=1 and K=2 cases preserve the K=2 seam edge as * a single shared edge between the two plane groups. * - Bit-exact. Every output position is a verbatim copy of an input * position; no vertex is fabricated. * * Algorithm: * * 1. Build per-vertex incident-tri lists and per-tri normalized normals * and plane offsets. * 2. Process vertices via a dirty-flag worklist. Initially queue every * vertex; after a successful removal, re-queue the ring neighbours * so chains of K=1 / K=2 vertices collapse in one run. * 3. For each dequeued vertex `v`: * a. Walk the fan to extract the cyclic ring vertices and the * cyclic ordered tris (each tri (v, ring[i], ring[(i+1)%k]) is * the i-th tri in fan order). * b. Decide K. If all tris share a plane: K=1. Otherwise count * transitions in cyclic order; K=2 if exactly two arcs. * c. K>=3: skip. K=2: verify ring[i1], v, ring[i2] are collinear. * d. For each arc, project its polygon to 2D using the arc's * plane basis, ear-clip, and append the new tris. * e. Mark v's old tris dead, register the new tris in each polygon * vertex's incident list, and re-queue the ring. * 4. Compact: drop dead tris and unused vertices, remap indices. * * @param mesh - Input triangle mesh from {@link marchingCubes}. * @param voxelResolution - Size of one voxel in world units. Used to scale the plane-offset tolerance. (The K=2 collinearity check is purely angular and has no voxel-scaled term.) * @returns A new mesh with the same surface geometry, no T-junctions, and far fewer triangles. */ declare const coplanarMerge: (mesh: Mesh, voxelResolution: number) => Mesh; export { coplanarMerge };