@playcanvas/splat-transform 2.0.5 → 2.0.6
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.
- package/dist/cli.mjs +78 -24
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +67 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +67 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -17494,11 +17494,11 @@ class CompressedChunk {
|
|
|
17494
17494
|
/**
|
|
17495
17495
|
* The splat-transform version (semver MAJOR.MINOR.PATCH).
|
|
17496
17496
|
*/
|
|
17497
|
-
const version = '2.0.
|
|
17497
|
+
const version = '2.0.6';
|
|
17498
17498
|
/**
|
|
17499
17499
|
* The splat-transform revision (short Git hash of HEAD at build time).
|
|
17500
17500
|
*/
|
|
17501
|
-
const revision = '
|
|
17501
|
+
const revision = 'aa3dbae';
|
|
17502
17502
|
|
|
17503
17503
|
const generatedByString = `Generated by splat-transform ${version}`;
|
|
17504
17504
|
const chunkProps = [
|
|
@@ -19486,13 +19486,17 @@ class GpuDilation {
|
|
|
19486
19486
|
// Extract: clear bufferA, dispatch extract from sparse src into bufferA.
|
|
19487
19487
|
this.dispatchClear(slot, slot.bufferA, numWords);
|
|
19488
19488
|
this.dispatchExtract(slot, minBx, minBy, minBz, outerBx, outerBy, outerBz, numXWords);
|
|
19489
|
-
//
|
|
19490
|
-
//
|
|
19491
|
-
//
|
|
19492
|
-
//
|
|
19493
|
-
// writes
|
|
19494
|
-
//
|
|
19495
|
-
|
|
19489
|
+
// Force a queue submission boundary between the extract pass (which
|
|
19490
|
+
// writes bufferA via atomicOr — bound as `storage, read_write` with
|
|
19491
|
+
// `array<atomic<u32>>`) and the dilate-X pass (which reads bufferA
|
|
19492
|
+
// bound as `storage, read` with `array<u32>`). Without this, the
|
|
19493
|
+
// atomic writes are not reliably visible to the next pass — dilation
|
|
19494
|
+
// produces empty output. Other inter-pass transitions in this
|
|
19495
|
+
// pipeline (clear→extract, dilateX→Z→Y, Y→compact) rely on automatic
|
|
19496
|
+
// intra-encoder synchronization without issue. Verified raw Dawn
|
|
19497
|
+
// synchronizes this case correctly, so the bug is somewhere in
|
|
19498
|
+
// PlayCanvas's compute dispatch / bind-group path.
|
|
19499
|
+
this.device.submit();
|
|
19496
19500
|
// X-pass: A → B
|
|
19497
19501
|
this.dispatchX(slot, slot.bufferA, slot.bufferB, numXWords, outerNy, outerNz, halfExtentXZ);
|
|
19498
19502
|
// Z-pass: B → A
|
|
@@ -20907,8 +20911,16 @@ const binIndices = (parent, lod) => {
|
|
|
20907
20911
|
*/
|
|
20908
20912
|
const writeLod = async (options, fs) => {
|
|
20909
20913
|
const { filename, iterations, createDevice, chunkCount, chunkExtent } = options;
|
|
20910
|
-
|
|
20911
|
-
|
|
20914
|
+
// Operate in PLY space so per-leaf bounds in tree.bound are in the same
|
|
20915
|
+
// coordinate frame as the SOG chunk data emitted by writeSog (which also
|
|
20916
|
+
// converts to Transform.PLY). Without this, view-dependent streaming
|
|
20917
|
+
// built on tree.bound picks the wrong chunks because the bounds are
|
|
20918
|
+
// 180°-Z-rotated relative to the splat positions inside them.
|
|
20919
|
+
// This intentionally mutates the input tables to avoid doubling peak
|
|
20920
|
+
// memory during LOD export. Callers should treat writeLod as consuming its
|
|
20921
|
+
// DataTable inputs.
|
|
20922
|
+
const dataTable = convertToSpace(options.dataTable, Transform.PLY, true);
|
|
20923
|
+
const envDataTable = options.envDataTable ? convertToSpace(options.envDataTable, Transform.PLY, true) : null;
|
|
20912
20924
|
const outputDir = dirname(filename);
|
|
20913
20925
|
// ensure top-level output folder exists
|
|
20914
20926
|
await fs.mkdir(outputDir);
|
|
@@ -26420,8 +26432,14 @@ const voxelizeToBuffer = async (bvh, gpuVoxelization, gridBounds, voxelResolutio
|
|
|
26420
26432
|
const buffer = new BlockMaskBuffer();
|
|
26421
26433
|
const bStride = numBlocksX * numBlocksY;
|
|
26422
26434
|
const batchSize = 16;
|
|
26423
|
-
|
|
26424
|
-
|
|
26435
|
+
// Caps for one mega-flush. Sized to keep a single compute dispatch's
|
|
26436
|
+
// wall-clock comfortably under Windows D3D12 TDR (~2s watchdog) on slower
|
|
26437
|
+
// GPUs without materially affecting throughput on fast GPUs (the
|
|
26438
|
+
// double-buffered slots keep the GPU fed across the extra flushes).
|
|
26439
|
+
// Output is bit-identical regardless: each batch's full Gaussian list rides
|
|
26440
|
+
// with it, and slot buffers grow on demand to accommodate any oversize batch.
|
|
26441
|
+
const MEGA_MAX_BATCHES = 256;
|
|
26442
|
+
const MEGA_MAX_INDICES = 2 * 1024 * 1024;
|
|
26425
26443
|
const maxBlocks = GpuVoxelization.MAX_BLOCKS_PER_BATCH;
|
|
26426
26444
|
const numSlots = GpuVoxelization.NUM_SLOTS;
|
|
26427
26445
|
const slotIndexArrays = [];
|
|
@@ -28227,21 +28245,32 @@ const cropToNavigable = (grid, gridBounds, voxelResolution) => {
|
|
|
28227
28245
|
return { grid, gridBounds };
|
|
28228
28246
|
}
|
|
28229
28247
|
const { minBx, minBy, minBz, maxBx, maxBy, maxBz } = navBounds;
|
|
28230
|
-
|
|
28231
|
-
|
|
28232
|
-
|
|
28233
|
-
|
|
28248
|
+
// Pad by 1 block on each side so the cropped grid retains the solid wall
|
|
28249
|
+
// blocks immediately surrounding the navigable cavity. Matches the
|
|
28250
|
+
// MARGIN = 1 pattern used by carve() before this re-crop strips it. The
|
|
28251
|
+
// collision-mesh extractors treat out-of-grid as empty, so without this
|
|
28252
|
+
// padding the mesh has holes wherever the cavity reaches the cropped
|
|
28253
|
+
// boundary; with it, the mesh extractor sees a real SOLID→EMPTY
|
|
28254
|
+
// transition at the cavity edge and emits a sealed wall there.
|
|
28255
|
+
const MARGIN = 1;
|
|
28256
|
+
const cropMinBx = Math.max(0, minBx - MARGIN);
|
|
28257
|
+
const cropMinBy = Math.max(0, minBy - MARGIN);
|
|
28258
|
+
const cropMinBz = Math.max(0, minBz - MARGIN);
|
|
28259
|
+
const cropMaxBx = Math.min(nbx, maxBx + 1 + MARGIN);
|
|
28260
|
+
const cropMaxBy = Math.min(nby, maxBy + 1 + MARGIN);
|
|
28261
|
+
const cropMaxBz = Math.min(nbz, maxBz + 1 + MARGIN);
|
|
28262
|
+
if (cropMinBx === 0 && cropMinBy === 0 && cropMinBz === 0 &&
|
|
28234
28263
|
cropMaxBx === nbx && cropMaxBy === nby && cropMaxBz === nbz) {
|
|
28235
28264
|
return { grid, gridBounds };
|
|
28236
28265
|
}
|
|
28237
28266
|
const cropBar = logger.bar('Cropping grid', grid.types.length);
|
|
28238
|
-
const croppedGrid = grid.cropTo(
|
|
28267
|
+
const croppedGrid = grid.cropTo(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz, done => cropBar.update(done));
|
|
28239
28268
|
cropBar.end();
|
|
28240
28269
|
const blockSize = 4 * voxelResolution;
|
|
28241
|
-
const croppedMin = new Vec3(gridBounds.min.x +
|
|
28270
|
+
const croppedMin = new Vec3(gridBounds.min.x + cropMinBx * blockSize, gridBounds.min.y + cropMinBy * blockSize, gridBounds.min.z + cropMinBz * blockSize);
|
|
28242
28271
|
const croppedBounds = {
|
|
28243
28272
|
min: croppedMin,
|
|
28244
|
-
max: new Vec3(croppedMin.x + (cropMaxBx -
|
|
28273
|
+
max: new Vec3(croppedMin.x + (cropMaxBx - cropMinBx) * blockSize, croppedMin.y + (cropMaxBy - cropMinBy) * blockSize, croppedMin.z + (cropMaxBz - cropMinBz) * blockSize)
|
|
28245
28274
|
};
|
|
28246
28275
|
return { grid: croppedGrid, gridBounds: croppedBounds };
|
|
28247
28276
|
};
|
|
@@ -28368,13 +28397,27 @@ const writeVoxel = async (options, fs) => {
|
|
|
28368
28397
|
gpuVoxelization = new GpuVoxelization(device);
|
|
28369
28398
|
gpuVoxelization.uploadAllGaussians(pcDataTable, extentsResult.extents);
|
|
28370
28399
|
// Align grid bounds to block boundaries BEFORE voxelization so the
|
|
28371
|
-
// block coordinates used during voxelization match what the reader
|
|
28372
|
-
//
|
|
28373
|
-
//
|
|
28400
|
+
// block coordinates used during voxelization match what the reader
|
|
28401
|
+
// expects. fillExterior and fillFloor both need a margin of empty
|
|
28402
|
+
// voxels outside the splat's tight 3-sigma extents to do their job:
|
|
28403
|
+
// fillExterior so the boundary-face flood seeds survive its dilation
|
|
28404
|
+
// (notably below the floor), fillFloor so its column walk has empty
|
|
28405
|
+
// XZ columns to convert into wall pillars and the dilation halo to
|
|
28406
|
+
// extend the floor footprint outward.
|
|
28407
|
+
//
|
|
28408
|
+
// Lateral pad combines both as `dilation_radius + 1` voxels per side.
|
|
28409
|
+
// Vertical pad is only contributed by exteriorPad — fillFloor's
|
|
28410
|
+
// dilation is XZ-only, and Y padding would extend the wall pillars
|
|
28411
|
+
// above the splat's natural ceiling and below its floor.
|
|
28374
28412
|
const exteriorPad = hasFillExterior ?
|
|
28375
28413
|
(Math.ceil(navExteriorRadius / voxelResolution) + 1) * voxelResolution :
|
|
28376
28414
|
0;
|
|
28377
|
-
|
|
28415
|
+
const floorPad = hasFloorFill ?
|
|
28416
|
+
(Math.ceil(floorFillDilation / voxelResolution) + 1) * voxelResolution :
|
|
28417
|
+
0;
|
|
28418
|
+
const padXZ = Math.max(exteriorPad, floorPad);
|
|
28419
|
+
const padY = exteriorPad;
|
|
28420
|
+
let gridBounds = alignGridBounds(bounds.min.x - padXZ, bounds.min.y - padY, bounds.min.z - padXZ, bounds.max.x + padXZ, bounds.max.y + padY, bounds.max.z + padXZ, voxelResolution);
|
|
28378
28421
|
const buffer = await voxelizeToBuffer(bvh, gpuVoxelization, gridBounds, voxelResolution, opacityCutoff);
|
|
28379
28422
|
bvh = null;
|
|
28380
28423
|
pcDataTable = null;
|
|
@@ -29051,6 +29094,17 @@ const createDevice = async (adapterName) => {
|
|
|
29051
29094
|
stencil: false
|
|
29052
29095
|
});
|
|
29053
29096
|
await graphicsDevice.createDevice();
|
|
29097
|
+
// Surface Dawn's device-lost reason directly. PlayCanvas logs this only via
|
|
29098
|
+
// Debug.warn (debug builds) and then attempts to recreate the device, which
|
|
29099
|
+
// can fail noisily (e.g. DXGI_ERROR_DEVICE_REMOVED on D3D12 TDR) and bury
|
|
29100
|
+
// the original cause. Skip the `destroyed` reason — that fires on intentional
|
|
29101
|
+
// device.destroy() during normal shutdown.
|
|
29102
|
+
// @ts-ignore - wgpu is private on WebgpuGraphicsDevice but exposed in practice
|
|
29103
|
+
graphicsDevice.wgpu?.lost?.then((info) => {
|
|
29104
|
+
if (info?.reason === 'destroyed')
|
|
29105
|
+
return;
|
|
29106
|
+
logger.error(`WebGPU device was lost: reason=${info.reason || 'unknown'}, message=${info.message || '(none)'}`);
|
|
29107
|
+
});
|
|
29054
29108
|
return graphicsDevice;
|
|
29055
29109
|
};
|
|
29056
29110
|
|