@playcanvas/splat-transform 2.0.3 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +4 -1
  2. package/dist/cli.mjs +6701 -3666
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/index.cjs +6683 -3664
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.mjs +6683 -3664
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/lib/gpu/gpu-dilation.d.ts +87 -0
  9. package/dist/lib/gpu/gpu-voxelization.d.ts +2 -1
  10. package/dist/lib/gpu/index.d.ts +1 -0
  11. package/dist/lib/index.d.cts +1 -1
  12. package/dist/lib/index.d.ts +1 -1
  13. package/dist/lib/io/write/memory-file-system.d.ts +1 -1
  14. package/dist/lib/mesh/index.d.ts +2 -1
  15. package/dist/lib/mesh/marching-cubes.d.ts +19 -6
  16. package/dist/lib/mesh/voxel-faces.d.ts +18 -0
  17. package/dist/lib/spatial/gaussian-bvh.d.ts +34 -0
  18. package/dist/lib/types.d.ts +10 -3
  19. package/dist/lib/utils/math.d.ts +2 -1
  20. package/dist/lib/voxel/block-cleanup.d.ts +6 -3
  21. package/dist/lib/voxel/block-mask-buffer.d.ts +18 -19
  22. package/dist/lib/voxel/block-mask-map.d.ts +5 -0
  23. package/dist/lib/voxel/carve.d.ts +3 -2
  24. package/dist/lib/voxel/dilation.d.ts +12 -15
  25. package/dist/lib/voxel/fill-exterior.d.ts +4 -3
  26. package/dist/lib/voxel/fill-floor.d.ts +10 -6
  27. package/dist/lib/voxel/flood-fill.d.ts +4 -1
  28. package/dist/lib/voxel/grid-ops.d.ts +2 -1
  29. package/dist/lib/voxel/morton.d.ts +1 -17
  30. package/dist/lib/voxel/sparse-voxel-grid.d.ts +52 -5
  31. package/dist/lib/voxel/voxel-query.d.ts +14 -9
  32. package/dist/lib/writers/collision-glb.d.ts +7 -8
  33. package/dist/lib/writers/sparse-octree.d.ts +15 -14
  34. package/dist/lib/writers/write-voxel.d.ts +14 -5
  35. package/package.json +1 -1
@@ -0,0 +1,87 @@
1
+ import { GraphicsDevice } from 'playcanvas';
2
+ import type { SparseVoxelGrid } from '../voxel/sparse-voxel-grid';
3
+ /**
4
+ * Separable 3D dilation on the GPU using a row-aligned dense bit grid
5
+ * (1 bit per voxel, packed into u32 words; each row of bits along X starts
6
+ * on a word boundary so per-word access is trivial). Each pass owns its
7
+ * own `Compute` instance because their uniform buffers must not collide
8
+ * within a single submit.
9
+ */
10
+ declare class GpuDilation {
11
+ private device;
12
+ private dilateXShader;
13
+ private dilateYZShader;
14
+ private clearShader;
15
+ private extractShader;
16
+ private compactShader;
17
+ private dilateXBindGroupFormat;
18
+ private dilateYZBindGroupFormat;
19
+ private clearBindGroupFormat;
20
+ private extractBindGroupFormat;
21
+ private compactBindGroupFormat;
22
+ private slots;
23
+ private srcTypesBuffer;
24
+ private srcKeysBuffer;
25
+ private srcLoBuffer;
26
+ private srcHiBuffer;
27
+ private srcTypesCapacity;
28
+ private srcMasksCapacity;
29
+ private srcMeta;
30
+ /** Number of double-buffered dispatch slots. */
31
+ static readonly NUM_SLOTS = 2;
32
+ constructor(device: GraphicsDevice);
33
+ private ensureSlotBuffers;
34
+ /**
35
+ * Dispatch a compute clear of `dst` to zero for the first `numWords` words.
36
+ * Uses the command encoder so it's correctly ordered with subsequent
37
+ * dilation passes (unlike `queue.writeBuffer`, which is queued separately
38
+ * and would race against the dispatches).
39
+ * @param slot - Per-chunk slot whose `clearCompute` pipeline is dispatched.
40
+ * @param dst - Destination buffer to zero.
41
+ * @param numWords - Number of leading u32 words to clear.
42
+ */
43
+ private dispatchClear;
44
+ /**
45
+ * Upload a `SparseVoxelGrid` to GPU storage buffers used by the extract
46
+ * shader. Reuses the existing buffers if they're large enough; otherwise
47
+ * destroys and reallocates. Designed to be called once per
48
+ * `gpuDilate3` call (the same `src` is read across all chunks).
49
+ * @param src - Source sparse grid to upload.
50
+ */
51
+ uploadSrc(src: SparseVoxelGrid): void;
52
+ /** Free uploaded `src` buffers. Caller can call after `gpuDilate3` finishes. */
53
+ releaseSrc(): void;
54
+ private ensureSlotOutputBuffers;
55
+ /**
56
+ * Sparse-path submit. Reads from the previously-uploaded `src` (via
57
+ * `uploadSrc`), runs extract → dilate → compact as GPU passes, and returns
58
+ * Promises for the per-block `typesOut` (packed 2-bit) and `masksOut`
59
+ * (lo/hi pairs). Caller integrates these into `dst` directly.
60
+ * @param slotIdx - Round-robin slot index (`0..NUM_SLOTS-1`).
61
+ * @param minBx - Outer chunk origin block X (in `src`'s block coords).
62
+ * @param minBy - Outer chunk origin block Y.
63
+ * @param minBz - Outer chunk origin block Z.
64
+ * @param outerBx - Outer chunk size in blocks along X.
65
+ * @param outerBy - Outer chunk size in blocks along Y.
66
+ * @param outerBz - Outer chunk size in blocks along Z.
67
+ * @param haloBx - Halo size in blocks along X (one side).
68
+ * @param haloBy - Halo size in blocks along Y (one side).
69
+ * @param haloBz - Halo size in blocks along Z (one side).
70
+ * @param innerBx - Inner (output) region size in blocks along X.
71
+ * @param innerBy - Inner region size in blocks along Y.
72
+ * @param innerBz - Inner region size in blocks along Z.
73
+ * @param halfExtentXZ - Dilation half-extent in voxels along X and Z.
74
+ * @param halfExtentY - Dilation half-extent in voxels along Y.
75
+ * @returns Promises for the inner region's packed types and `[lo, hi]` masks.
76
+ */
77
+ submitChunkSparse(slotIdx: number, minBx: number, minBy: number, minBz: number, outerBx: number, outerBy: number, outerBz: number, haloBx: number, haloBy: number, haloBz: number, innerBx: number, innerBy: number, innerBz: number, halfExtentXZ: number, halfExtentY: number): {
78
+ types: Promise<Uint32Array>;
79
+ masks: Promise<Uint32Array>;
80
+ };
81
+ private dispatchExtract;
82
+ private dispatchCompact;
83
+ private dispatchX;
84
+ private dispatchYZ;
85
+ destroy(): void;
86
+ }
87
+ export { GpuDilation };
@@ -49,9 +49,10 @@ declare class GpuVoxelization {
49
49
  private gaussianBuffer;
50
50
  private slots;
51
51
  private totalGaussians;
52
- private zeroBuffer;
53
52
  /** Floats per Gaussian in the interleaved buffer (16 for alignment) */
54
53
  private static readonly FLOATS_PER_GAUSSIAN;
54
+ /** Gaussian rows per CPU staging chunk when uploading to the shared GPU buffer. */
55
+ private static readonly UPLOAD_CHUNK_GAUSSIANS;
55
56
  /** Maximum blocks per batch (16^3 = 4096 for 4x4x4 voxel blocks in a 16-block batch) */
56
57
  static readonly MAX_BLOCKS_PER_BATCH = 4096;
57
58
  /** Number of u32 fields per BatchInfo struct */
@@ -1,3 +1,4 @@
1
1
  export { GpuClustering } from './gpu-clustering';
2
+ export { GpuDilation } from './gpu-dilation';
2
3
  export { GpuVoxelization } from './gpu-voxelization';
3
4
  export type { BatchSpec, MultiBatchResult } from './gpu-voxelization';
@@ -17,5 +17,5 @@ export { writeSog, writePly, writeCompressedPly, writeCsv, writeHtml, writeLod,
17
17
  export type { WriteVoxelOptions, VoxelMetadata } from './writers';
18
18
  export { carve, fillExterior, fillFloor, filterCluster, filterFloaters, findClusterVoxelFlood, voxelizeToBuffer } from './voxel';
19
19
  export type { NavSeed, NavSimplifyResult } from './voxel';
20
- export type { Options, Param, DeviceCreator } from './types';
20
+ export type { CollisionMeshShape, Options, Param, DeviceCreator } from './types';
21
21
  export { version, revision } from './version';
@@ -17,5 +17,5 @@ export { writeSog, writePly, writeCompressedPly, writeCsv, writeHtml, writeLod,
17
17
  export type { WriteVoxelOptions, VoxelMetadata } from './writers';
18
18
  export { carve, fillExterior, fillFloor, filterCluster, filterFloaters, findClusterVoxelFlood, voxelizeToBuffer } from './voxel';
19
19
  export type { NavSeed, NavSimplifyResult } from './voxel';
20
- export type { Options, Param, DeviceCreator } from './types';
20
+ export type { CollisionMeshShape, Options, Param, DeviceCreator } from './types';
21
21
  export { version, revision } from './version';
@@ -17,6 +17,6 @@ import { type FileSystem, type Writer } from './file-system';
17
17
  declare class MemoryFileSystem implements FileSystem {
18
18
  results: Map<string, Uint8Array>;
19
19
  createWriter(filename: string): Writer;
20
- mkdir(path: string): Promise<void>;
20
+ mkdir(_path: string): Promise<void>;
21
21
  }
22
22
  export { MemoryFileSystem };
@@ -1,3 +1,4 @@
1
1
  export { marchingCubes } from './marching-cubes';
2
- export type { Mesh, MarchingCubesMesh } from './marching-cubes';
2
+ export type { Mesh, MarchingCubesMesh, MarchingCubesOptions } from './marching-cubes';
3
3
  export { coplanarMerge } from './coplanar-merge';
4
+ export { voxelFaces } from './voxel-faces';
@@ -1,5 +1,5 @@
1
1
  import type { Bounds } from '../data-table';
2
- import { BlockMaskBuffer } from '../voxel/block-mask-buffer';
2
+ import { SparseVoxelGrid } from '../voxel/sparse-voxel-grid';
3
3
  /**
4
4
  * A simple triangle mesh with positions and indices.
5
5
  */
@@ -14,17 +14,30 @@ interface Mesh {
14
14
  */
15
15
  type MarchingCubesMesh = Mesh;
16
16
  /**
17
- * Extract a triangle mesh from a BlockMaskBuffer using marching cubes.
17
+ * Options for marching cubes extraction.
18
+ */
19
+ interface MarchingCubesOptions {
20
+ /**
21
+ * Pre-merge exact full-face cells on flat axis-aligned regions before
22
+ * creating the mesh. Ambiguous and bevel cases still use normal marching
23
+ * cubes, so coplanarMerge can apply the final lossless optimization.
24
+ */
25
+ mergeFlatFaces?: boolean;
26
+ }
27
+ /**
28
+ * Extract a triangle mesh from a SparseVoxelGrid using marching cubes.
18
29
  *
19
30
  * Each voxel is treated as a cell in the marching cubes grid. Corner values
20
31
  * are binary (0 = empty, 1 = occupied) with a 0.5 threshold. Vertices are
21
- * placed at edge midpoints, producing a mesh that follows voxel boundaries.
32
+ * placed at edge midpoints, producing the binary-field isosurface between
33
+ * occupied and empty samples.
22
34
  *
23
- * @param buffer - Voxel block data after filtering
35
+ * @param grid - Voxel grid (after filtering / nav phases)
24
36
  * @param gridBounds - Grid bounds aligned to block boundaries
25
37
  * @param voxelResolution - Size of each voxel in world units
38
+ * @param options - Optional extraction settings
26
39
  * @returns Mesh with positions and indices
27
40
  */
28
- declare function marchingCubes(buffer: BlockMaskBuffer, gridBounds: Bounds, voxelResolution: number): MarchingCubesMesh;
41
+ declare function marchingCubes(grid: SparseVoxelGrid, gridBounds: Bounds, voxelResolution: number, options?: MarchingCubesOptions): MarchingCubesMesh;
29
42
  export { marchingCubes };
30
- export type { Mesh, MarchingCubesMesh };
43
+ export type { Mesh, MarchingCubesMesh, MarchingCubesOptions };
@@ -0,0 +1,18 @@
1
+ import type { Bounds } from '../data-table';
2
+ import type { Mesh } from './marching-cubes';
3
+ import { SparseVoxelGrid } from '../voxel/sparse-voxel-grid';
4
+ /**
5
+ * Extract a watertight voxel-boundary mesh from a SparseVoxelGrid.
6
+ *
7
+ * Exposed voxel faces are first greedily merged into axis-aligned rectangles.
8
+ * Rectangle boundaries are then split at every collinear rectangle corner
9
+ * before triangulation, so adjacent rectangles share matching edges instead
10
+ * of producing T-junctions.
11
+ *
12
+ * @param grid - Voxel grid after filtering / nav phases.
13
+ * @param gridBounds - Grid bounds aligned to block boundaries.
14
+ * @param voxelResolution - Size of each voxel in world units.
15
+ * @returns Mesh with positions and indices.
16
+ */
17
+ declare const voxelFaces: (grid: SparseVoxelGrid, gridBounds: Bounds, voxelResolution: number) => Mesh;
18
+ export { voxelFaces };
@@ -86,6 +86,24 @@ declare class GaussianBVH {
86
86
  * @returns Array of Gaussian indices that overlap the box
87
87
  */
88
88
  queryOverlappingRaw(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): number[];
89
+ /**
90
+ * Query overlapping Gaussian indices directly into a caller-provided buffer.
91
+ *
92
+ * Returns the total number of matches even if the target buffer is too
93
+ * small; in that case only the prefix that fits is written and the caller
94
+ * can grow the buffer then retry.
95
+ *
96
+ * @param minX - Minimum X of query box
97
+ * @param minY - Minimum Y of query box
98
+ * @param minZ - Minimum Z of query box
99
+ * @param maxX - Maximum X of query box
100
+ * @param maxY - Maximum Y of query box
101
+ * @param maxZ - Maximum Z of query box
102
+ * @param result - Buffer to append matching Gaussian indices into.
103
+ * @param offset - Starting element offset in `result`.
104
+ * @returns Number of matching Gaussian indices.
105
+ */
106
+ queryOverlappingRawInto(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: Uint32Array, offset: number): number;
89
107
  /**
90
108
  * Recursive query helper.
91
109
  *
@@ -99,6 +117,22 @@ declare class GaussianBVH {
99
117
  * @param result - Array to append matching indices to
100
118
  */
101
119
  private queryNode;
120
+ /**
121
+ * Recursive typed-buffer query helper.
122
+ *
123
+ * @param node - Current BVH node to query
124
+ * @param minX - Query box minimum X
125
+ * @param minY - Query box minimum Y
126
+ * @param minZ - Query box minimum Z
127
+ * @param maxX - Query box maximum X
128
+ * @param maxY - Query box maximum Y
129
+ * @param maxZ - Query box maximum Z
130
+ * @param result - Buffer to append matching indices into
131
+ * @param offset - Starting element offset in `result`
132
+ * @param count - Number of matches already found for this query
133
+ * @returns Updated match count
134
+ */
135
+ private queryNodeInto;
102
136
  /**
103
137
  * Get the total number of Gaussians in the BVH.
104
138
  *
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Collision mesh shape generated alongside voxel output.
3
+ *
4
+ * - `smooth` - marching cubes with lossless coplanar merge.
5
+ * - `faces` - direct watertight voxel-boundary faces.
6
+ */
7
+ type CollisionMeshShape = 'smooth' | 'faces';
1
8
  /**
2
9
  * Options for read/write operations.
3
10
  */
@@ -35,8 +42,8 @@ type Options = {
35
42
  y: number;
36
43
  z: number;
37
44
  };
38
- /** When `true`, a collision mesh (.collision.glb) is generated alongside the voxel output, using marching cubes followed by lossless coplanar merge. */
39
- collisionMesh?: boolean;
45
+ /** When set, a collision mesh (.collision.glb) is generated alongside the voxel output. `true` is equivalent to `smooth`. */
46
+ collisionMesh?: boolean | CollisionMeshShape;
40
47
  };
41
48
  /**
42
49
  * Parameter passed to MJS generator scripts.
@@ -55,4 +62,4 @@ type Param = {
55
62
  * @returns Promise resolving to a GraphicsDevice instance.
56
63
  */
57
64
  type DeviceCreator = () => Promise<import('playcanvas').GraphicsDevice>;
58
- export type { Options, Param, DeviceCreator };
65
+ export type { CollisionMeshShape, Options, Param, DeviceCreator };
@@ -92,7 +92,8 @@ declare class Transform {
92
92
  static IDENTITY: Readonly<Transform>;
93
93
  /**
94
94
  * PLY coordinate convention: 180-degree rotation around Z.
95
- * Used by PLY, splat, KSplat, SPZ, and legacy SOG formats.
95
+ * Used by formats that store Gaussian data in PLY-style coordinates:
96
+ * PLY, splat, KSplat, SPZ, and SOG.
96
97
  */
97
98
  static PLY: Readonly<Transform>;
98
99
  }
@@ -9,8 +9,11 @@ import { BlockMaskBuffer } from './block-mask-buffer';
9
9
  *
10
10
  * Blocks that become empty or solid as a consequence are handled automatically.
11
11
  *
12
- * @param buffer - BlockMaskBuffer with voxelization results
13
- * @returns New BlockMaskBuffer with filtered/filled data
12
+ * @param buffer - BlockMaskBuffer with voxelization results (linear-keyed).
13
+ * @param nbx - Grid block dimension X (used to decode block indices).
14
+ * @param nby - Grid block dimension Y (used to decode block indices).
15
+ * @param nbz - Grid block dimension Z (used for neighbor bounds checks).
16
+ * @returns New BlockMaskBuffer with filtered/filled data.
14
17
  */
15
- declare function filterAndFillBlocks(buffer: BlockMaskBuffer): BlockMaskBuffer;
18
+ declare function filterAndFillBlocks(buffer: BlockMaskBuffer, nbx: number, nby: number, nbz: number): BlockMaskBuffer;
16
19
  export { filterAndFillBlocks };
@@ -1,24 +1,23 @@
1
1
  /**
2
2
  * Append-only buffer for streaming voxelization results.
3
- * Stores block masks using Morton codes for efficient octree construction.
3
+ * Stores (linear blockIdx, voxel mask) pairs for non-empty 4x4x4 blocks.
4
4
  *
5
- * Backed by typed arrays that grow geometrically. Morton codes use
6
- * Float64Array (51 bits of precision needed; exceeds Smi range), and
7
- * voxel masks use Uint32Array. This raises the per-buffer capacity well
8
- * above V8's regular-array backing-store limit so very large grids can
9
- * round-trip without throwing `RangeError: Invalid array length`.
5
+ * Block keys are linear block indices `bx + by*nbx + bz*nbx*nby` in the
6
+ * producer's grid coordinate system. Producers and consumers must agree
7
+ * on the grid dimensions; the buffer itself is dimension-agnostic.
10
8
  *
11
- * Buffers start at length 0 and the first `addBlock` allocates
12
- * `INITIAL_CAPACITY` entries; subsequent grows double. A freshly cleared
13
- * instance therefore retains no allocations until it is reused.
9
+ * Backed by typed arrays that grow geometrically. Keys use Float64Array so
10
+ * the per-buffer capacity exceeds V8's regular-array backing-store limit
11
+ * (large grids exceed Smi range and would throw `RangeError: Invalid array
12
+ * length` with a regular array).
14
13
  */
15
14
  declare class BlockMaskBuffer {
16
- /** Morton codes for solid blocks (mask is implicitly all 1s) */
17
- private _solidMorton;
15
+ /** Linear block indices for solid blocks (mask is implicitly all 1s) */
16
+ private _solidIdx;
18
17
  private _solidCount;
19
18
  private _solidCap;
20
- /** Morton codes for mixed blocks */
21
- private _mixedMorton;
19
+ /** Linear block indices for mixed blocks */
20
+ private _mixedIdx;
22
21
  private _mixedCount;
23
22
  private _mixedCap;
24
23
  /** Interleaved voxel masks for mixed blocks: [lo0, hi0, lo1, hi1, ...] */
@@ -27,25 +26,25 @@ declare class BlockMaskBuffer {
27
26
  * Add a non-empty block to the buffer.
28
27
  * Automatically classifies as solid or mixed based on mask values.
29
28
  *
30
- * @param morton - Morton code encoding block position
29
+ * @param blockIdx - Linear block index (`bx + by*nbx + bz*nbx*nby`)
31
30
  * @param lo - Lower 32 bits of voxel mask
32
31
  * @param hi - Upper 32 bits of voxel mask
33
32
  */
34
- addBlock(morton: number, lo: number, hi: number): void;
33
+ addBlock(blockIdx: number, lo: number, hi: number): void;
35
34
  /**
36
35
  * Get all mixed blocks as views into the underlying buffers.
37
- * Index `i` of `morton` corresponds to mask pair `(masks[i*2], masks[i*2+1])`.
36
+ * Index `i` of `blockIdx` corresponds to mask pair `(masks[i*2], masks[i*2+1])`.
38
37
  *
39
- * @returns Object with morton codes and interleaved masks
38
+ * @returns Object with linear block indices and interleaved masks
40
39
  */
41
40
  getMixedBlocks(): {
42
- morton: Float64Array;
41
+ blockIdx: Float64Array;
43
42
  masks: Uint32Array;
44
43
  };
45
44
  /**
46
45
  * Get all solid blocks as a view into the underlying buffer.
47
46
  *
48
- * @returns Array of Morton codes
47
+ * @returns Array of linear block indices
49
48
  */
50
49
  getSolidBlocks(): Float64Array;
51
50
  /**
@@ -51,6 +51,11 @@ declare class BlockMaskMap {
51
51
  removeAt(slot: number): void;
52
52
  delete(key: number): void;
53
53
  clear(): void;
54
+ /**
55
+ * Release all backing storage. The map should not be used again except by
56
+ * replacing it with a new instance.
57
+ */
58
+ releaseStorage(): void;
54
59
  forEach(fn: (key: number, lo: number, hi: number) => void): void;
55
60
  clone(): BlockMaskMap;
56
61
  private _grow;
@@ -1,5 +1,6 @@
1
- import { BlockMaskBuffer } from './block-mask-buffer';
2
1
  import type { NavSeed, NavSimplifyResult } from './fill-exterior';
3
2
  import type { Bounds } from '../data-table';
4
- declare const carve: (buffer: BlockMaskBuffer, gridBounds: Bounds, voxelResolution: number, capsuleHeight: number, capsuleRadius: number, seed: NavSeed) => NavSimplifyResult;
3
+ import type { GpuDilation } from '../gpu';
4
+ import { SparseVoxelGrid } from './sparse-voxel-grid';
5
+ declare const carve: (gridA: SparseVoxelGrid, gridBounds: Bounds, voxelResolution: number, capsuleHeight: number, capsuleRadius: number, seed: NavSeed, gpu: GpuDilation) => Promise<NavSimplifyResult>;
5
6
  export { carve };
@@ -1,22 +1,19 @@
1
1
  import { SparseVoxelGrid } from './sparse-voxel-grid';
2
+ import { GpuDilation } from '../gpu';
2
3
  /**
3
- * 3D dilation by separable 1D passes (X then Z then Y).
4
+ * GPU separable 3D dilation. Chunks the grid into block-aligned inner regions plus
5
+ * a halo on each side, runs three GPU passes per chunk, and OR's the
6
+ * dilated inner region into a fresh destination `SparseVoxelGrid`.
4
7
  *
5
- * Allocates one fresh working grid (`a`) and uses one more (`b`) for the
6
- * intermediate. When `consumeSrc` is true the caller relinquishes `src`
7
- * it gets cleared and reused as the second working grid, saving one
8
- * full SparseVoxelGrid allocation per call. After the function returns
9
- * with `consumeSrc=true`, `src` references an empty grid and must not
10
- * be read by the caller.
8
+ * The exact requested half-extents are passed to the dilation shaders. The
9
+ * sparse extraction halo is rounded up to the next 4-voxel block boundary so
10
+ * chunk extract/compact math remains block-aligned without over-dilating.
11
11
  *
12
- * @param src - Input grid. Read-only when `consumeSrc=false`; consumed
13
- * (cleared and used as scratch) when `consumeSrc=true`.
12
+ * @param gpu - Reusable GPU dilation context (compiled shader + buffers).
13
+ * @param src - Input sparse grid (read-only across the call).
14
14
  * @param halfExtentXZ - Dilation half-extent in voxels along X and Z.
15
15
  * @param halfExtentY - Dilation half-extent in voxels along Y.
16
- * @param consumeSrc - If true, the function may reuse `src`'s memory as
17
- * a working buffer. Saves ~one full grid's worth of allocation. The
18
- * caller must not read `src` after the call.
19
- * @returns Newly allocated dilated grid.
16
+ * @returns Newly allocated dilated sparse grid.
20
17
  */
21
- declare function sparseDilate3(src: SparseVoxelGrid, halfExtentXZ: number, halfExtentY: number, consumeSrc?: boolean): SparseVoxelGrid;
22
- export { sparseDilate3 };
18
+ declare function gpuDilate3(gpu: GpuDilation, src: SparseVoxelGrid, halfExtentXZ: number, halfExtentY: number): Promise<SparseVoxelGrid>;
19
+ export { gpuDilate3 };
@@ -1,14 +1,15 @@
1
- import { BlockMaskBuffer } from './block-mask-buffer';
2
1
  import type { Bounds } from '../data-table';
2
+ import type { GpuDilation } from '../gpu';
3
+ import { SparseVoxelGrid } from './sparse-voxel-grid';
3
4
  type NavSeed = {
4
5
  x: number;
5
6
  y: number;
6
7
  z: number;
7
8
  };
8
9
  type NavSimplifyResult = {
9
- buffer: BlockMaskBuffer;
10
+ grid: SparseVoxelGrid;
10
11
  gridBounds: Bounds;
11
12
  };
12
- declare const fillExterior: (buffer: BlockMaskBuffer, gridBounds: Bounds, voxelResolution: number, dilation: number, seed: NavSeed) => NavSimplifyResult;
13
+ declare const fillExterior: (gridOriginal: SparseVoxelGrid, gridBounds: Bounds, voxelResolution: number, dilation: number, seed: NavSeed, gpu: GpuDilation) => Promise<NavSimplifyResult>;
13
14
  export { fillExterior };
14
15
  export type { NavSeed, NavSimplifyResult };
@@ -1,6 +1,7 @@
1
- import { BlockMaskBuffer } from './block-mask-buffer';
2
1
  import type { NavSimplifyResult } from './fill-exterior';
3
2
  import type { Bounds } from '../data-table';
3
+ import type { GpuDilation } from '../gpu';
4
+ import { SparseVoxelGrid } from './sparse-voxel-grid';
4
5
  /**
5
6
  * Floor-fill via XZ dilate -> per-column upward walk -> XZ dilate -> OR.
6
7
  *
@@ -9,12 +10,12 @@ import type { Bounds } from '../data-table';
9
10
  * 3D boundary BFS, and the dilations operate only in X and Z.
10
11
  *
11
12
  * Steps with `r = ceil(dilation / voxelResolution)`:
12
- * 1. `S_xz = sparseDilate3(S, r, 0)` closes any XZ holes in horizontal
13
+ * 1. `S_xz = gpuDilate3(S, r, 0)` closes any XZ holes in horizontal
13
14
  * surfaces smaller than `2 * r`.
14
15
  * 2. For every (lx, lz), walk `y = 0` upward through `S_xz`. Mark each
15
16
  * visited empty voxel into `foundEmpty`. Stop on the first solid voxel
16
17
  * of `S_xz` or at the grid top.
17
- * 3. `dilatedFound = sparseDilate3(foundEmpty, r, 0)` spreads the found
18
+ * 3. `dilatedFound = gpuDilate3(foundEmpty, r, 0)` spreads the found
18
19
  * under-surface volume back out in XZ to cover the kernel halo.
19
20
  * 4. `output = S | dilatedFound` adds the dilated under-surface region as
20
21
  * solid on top of the original solids.
@@ -27,11 +28,14 @@ import type { Bounds } from '../data-table';
27
28
  * "fill the under-side of every column up to the first solid", matching the
28
29
  * original (pre-dilation) `fillFloor` behavior.
29
30
  *
30
- * @param buffer - Voxelized scene data.
31
+ * @param grid - Voxel grid (linear-keyed) — mutated as the under-surface
32
+ * region is OR'd into it. The same grid instance is returned in
33
+ * `NavSimplifyResult.grid`.
31
34
  * @param gridBounds - Axis-aligned bounds of the voxel grid.
32
35
  * @param voxelResolution - Size of each voxel in world units.
33
36
  * @param dilation - XZ dilation radius in world units. 0 disables dilation.
34
- * @returns Modified buffer with under-surface regions filled.
37
+ * @param gpu - Reusable GPU dilation context. Required when dilation > 0.
38
+ * @returns Modified grid with under-surface regions filled.
35
39
  */
36
- declare const fillFloor: (buffer: BlockMaskBuffer, gridBounds: Bounds, voxelResolution: number, dilation?: number) => NavSimplifyResult;
40
+ declare const fillFloor: (grid: SparseVoxelGrid, gridBounds: Bounds, voxelResolution: number, dilation?: number, gpu?: GpuDilation | null) => Promise<NavSimplifyResult>;
37
41
  export { fillFloor };
@@ -20,11 +20,14 @@ import { SparseVoxelGrid } from './sparse-voxel-grid';
20
20
  * @param nx - Grid dimension X in voxels.
21
21
  * @param ny - Grid dimension Y in voxels.
22
22
  * @param nz - Grid dimension Z in voxels.
23
+ * @param onBlockFilled - Optional progress callback receiving the running
24
+ * count of whole-block fills. Throttled internally so callers can wire it
25
+ * directly to a progress bar without worrying about per-block overhead.
23
26
  * @returns Sparse voxel grid marking all reachable voxels.
24
27
  */
25
28
  declare function twoLevelBFS(blocked: SparseVoxelGrid, blockSeeds: number[], voxelSeeds: {
26
29
  ix: number;
27
30
  iy: number;
28
31
  iz: number;
29
- }[], nx: number, ny: number, nz: number): SparseVoxelGrid;
32
+ }[], nx: number, ny: number, nz: number, onBlockFilled?: (count: number) => void): SparseVoxelGrid;
30
33
  export { twoLevelBFS };
@@ -18,8 +18,9 @@ declare function computeEmptyGrid(visited: SparseVoxelGrid, blocked: SparseVoxel
18
18
  * @param consumeA - If true, `a` is mutated in place and returned. The
19
19
  * caller must not subsequently read `a` as an independent value
20
20
  * (the returned grid IS `a`).
21
+ * @param onProgress - Optional progress callback over `b.types` words.
21
22
  * @returns Grid containing the union of both inputs. Equal to `a` when
22
23
  * `consumeA=true`, otherwise a freshly cloned grid.
23
24
  */
24
- declare function sparseOrGrids(a: SparseVoxelGrid, b: SparseVoxelGrid, consumeA?: boolean): SparseVoxelGrid;
25
+ declare function sparseOrGrids(a: SparseVoxelGrid, b: SparseVoxelGrid, consumeA?: boolean, onProgress?: (done: number, total: number) => void): SparseVoxelGrid;
25
26
  export { computeEmptyGrid, sparseOrGrids };
@@ -8,13 +8,6 @@
8
8
  * @returns Morton code with interleaved bits: ...z2y2x2 z1y1x1 z0y0x0
9
9
  */
10
10
  declare function xyzToMorton(x: number, y: number, z: number): number;
11
- /**
12
- * Decode Morton code to block coordinates.
13
- *
14
- * @param m - Morton code
15
- * @returns Tuple of [x, y, z] block coordinates
16
- */
17
- declare function mortonToXYZ(m: number): [number, number, number];
18
11
  /**
19
12
  * Count the number of set bits in a 32-bit integer.
20
13
  *
@@ -38,13 +31,4 @@ declare function isSolid(lo: number, hi: number): boolean;
38
31
  * @returns True if all 64 voxels are empty
39
32
  */
40
33
  declare function isEmpty(lo: number, hi: number): boolean;
41
- /**
42
- * Get the offset to a child node given a parent's child mask and octant.
43
- * Uses popcount to count how many children come before this octant.
44
- *
45
- * @param mask - 8-bit child mask from parent node
46
- * @param octant - Octant index (0-7)
47
- * @returns Offset from base child pointer
48
- */
49
- declare function getChildOffset(mask: number, octant: number): number;
50
- export { xyzToMorton, mortonToXYZ, popcount, isSolid, isEmpty, getChildOffset };
34
+ export { xyzToMorton, popcount, isSolid, isEmpty };
@@ -68,16 +68,62 @@ declare class SparseVoxelGrid {
68
68
  setVoxel(ix: number, iy: number, iz: number): void;
69
69
  orBlock(blockIdx: number, lo: number, hi: number): void;
70
70
  clear(): void;
71
+ /**
72
+ * Release the large backing arrays once a grid has been consumed.
73
+ */
74
+ releaseStorage(): void;
71
75
  clone(): SparseVoxelGrid;
72
- static fromBuffer(acc: BlockMaskBuffer, nx: number, ny: number, nz: number): SparseVoxelGrid;
73
- toBuffer(cropMinBx: number, cropMinBy: number, cropMinBz: number, cropMaxBx: number, cropMaxBy: number, cropMaxBz: number, defaultSolid?: boolean): BlockMaskBuffer;
74
- toBufferInverted(cropMinBx: number, cropMinBy: number, cropMinBz: number, cropMaxBx: number, cropMaxBy: number, cropMaxBz: number): BlockMaskBuffer;
76
+ static fromBuffer(acc: BlockMaskBuffer, nx: number, ny: number, nz: number, onProgress?: (done: number, total: number) => void): SparseVoxelGrid;
77
+ toBuffer(cropMinBx: number, cropMinBy: number, cropMinBz: number, cropMaxBx: number, cropMaxBy: number, cropMaxBz: number, onProgress?: (done: number, total: number) => void): BlockMaskBuffer;
78
+ /**
79
+ * Crop this grid to a block-aligned sub-region. Returns a fresh grid of
80
+ * size `(cropMaxBx-cropMinBx)*4 x (cropMaxBy-cropMinBy)*4 x (cropMaxBz-cropMinBz)*4`
81
+ * containing the same data with origin shifted to (cropMinBx, cropMinBy, cropMinBz).
82
+ *
83
+ * Iterates only non-empty blocks of the source via word-level skipping.
84
+ *
85
+ * @param cropMinBx - Min block X (inclusive).
86
+ * @param cropMinBy - Min block Y (inclusive).
87
+ * @param cropMinBz - Min block Z (inclusive).
88
+ * @param cropMaxBx - Max block X (exclusive).
89
+ * @param cropMaxBy - Max block Y (exclusive).
90
+ * @param cropMaxBz - Max block Z (exclusive).
91
+ * @param onProgress - Optional progress callback over source `types` words.
92
+ * @returns Newly allocated cropped grid.
93
+ */
94
+ cropTo(cropMinBx: number, cropMinBy: number, cropMinBz: number, cropMaxBx: number, cropMaxBy: number, cropMaxBz: number, onProgress?: (done: number, total: number) => void): SparseVoxelGrid;
95
+ /**
96
+ * Crop this grid to a sub-region with inverted occupancy: source EMPTY
97
+ * becomes destination SOLID, source SOLID becomes destination EMPTY,
98
+ * source MIXED becomes destination MIXED with bitwise-inverted mask.
99
+ *
100
+ * The destination grid has dimensions matching the crop window. Used by
101
+ * carve to flip the navigable region (1=navigable) into the runtime's
102
+ * occupancy convention (1=blocked).
103
+ *
104
+ * Note: cropping the EMPTY-becomes-SOLID region is exact within the crop
105
+ * window only. Since the source grid's EMPTY blocks become SOLID in the
106
+ * output, the output's `types` for empty blocks must be set rather than
107
+ * left at their default. We therefore initialize the entire output to
108
+ * SOLID (word fill) and then patch any non-EMPTY source blocks.
109
+ *
110
+ * @param cropMinBx - Min block X (inclusive).
111
+ * @param cropMinBy - Min block Y (inclusive).
112
+ * @param cropMinBz - Min block Z (inclusive).
113
+ * @param cropMaxBx - Max block X (exclusive).
114
+ * @param cropMaxBy - Max block Y (exclusive).
115
+ * @param cropMaxBz - Max block Z (exclusive).
116
+ * @param onProgress - Optional progress callback over source `types` words.
117
+ * @returns Newly allocated cropped + inverted grid.
118
+ */
119
+ cropToInverted(cropMinBx: number, cropMinBy: number, cropMinBz: number, cropMaxBx: number, cropMaxBy: number, cropMaxBz: number, onProgress?: (done: number, total: number) => void): SparseVoxelGrid;
75
120
  /**
76
121
  * Get the bounding box of occupied blocks.
77
122
  *
123
+ * @param onProgress - Optional progress callback over `types` words.
78
124
  * @returns Block coordinate bounds, or null if no blocks are occupied.
79
125
  */
80
- getOccupiedBlockBounds(): {
126
+ getOccupiedBlockBounds(onProgress?: (done: number, total: number) => void): {
81
127
  minBx: number;
82
128
  minBy: number;
83
129
  minBz: number;
@@ -91,9 +137,10 @@ declare class SparseVoxelGrid {
91
137
  * voxel). Useful when the runtime treats out-of-grid as solid, so fully
92
138
  * solid blocks beyond the navigable region can be cropped away.
93
139
  *
140
+ * @param onProgress - Optional progress callback over `types` words.
94
141
  * @returns Block coordinate bounds, or null if every block is solid.
95
142
  */
96
- getNavigableBlockBounds(): {
143
+ getNavigableBlockBounds(onProgress?: (done: number, total: number) => void): {
97
144
  minBx: number;
98
145
  minBy: number;
99
146
  minBz: number;