@voluma/vlam 0.2.3 → 0.3.0

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 (91) hide show
  1. package/README.md +3 -2
  2. package/dist/budget-governor.d.ts +4 -4
  3. package/dist/camera-budget-governor.d.ts +11 -11
  4. package/dist/chunk-cache-budget.d.ts +5 -5
  5. package/dist/chunk-fetch-scheduler.d.ts +4 -4
  6. package/dist/chunk-loader-DlNwggfG.js +1859 -0
  7. package/dist/chunk-loader-DlNwggfG.js.map +1 -0
  8. package/dist/chunk-loader.d.ts +2 -2
  9. package/dist/compute-sorter.d.ts +1 -1
  10. package/dist/effects.d.ts +26 -16
  11. package/dist/effects.js +199 -165
  12. package/dist/effects.js.map +1 -1
  13. package/dist/formats/ksplat/index.d.ts +1 -1
  14. package/dist/formats/lcc/collision-mesh.d.ts +2 -2
  15. package/dist/formats/lcc.js +17 -16
  16. package/dist/formats/lcc.js.map +1 -1
  17. package/dist/formats/ply/index.d.ts +1 -1
  18. package/dist/formats/ply/parse-splat-ply.d.ts +2 -2
  19. package/dist/formats/ply.js +1 -1
  20. package/dist/formats/ply.js.map +1 -1
  21. package/dist/formats/rad/index.d.ts +1 -1
  22. package/dist/formats/rad/parse-rad.d.ts +1 -1
  23. package/dist/formats/rad/rad.d.ts +2 -2
  24. package/dist/formats/rad.js +3 -3
  25. package/dist/formats/rad.js.map +1 -1
  26. package/dist/formats/sog/index.d.ts +1 -1
  27. package/dist/formats/sog/parse-sog.d.ts +4 -4
  28. package/dist/formats/sog.js.map +1 -1
  29. package/dist/formats/splat/index.d.ts +1 -1
  30. package/dist/formats/spz/index.d.ts +1 -1
  31. package/dist/index.d.ts +16 -25
  32. package/dist/index.js +253 -9047
  33. package/dist/index.js.map +1 -1
  34. package/dist/{load-scene.d.ts → load-splat-data.d.ts} +15 -13
  35. package/dist/loaders.d.ts +15 -0
  36. package/dist/loaders.js +26 -0
  37. package/dist/loaders.js.map +1 -0
  38. package/dist/loading-wLo8vRbA.js.map +1 -1
  39. package/dist/loading.d.ts +8 -8
  40. package/dist/{splat-mesh-types-CTN_duw2.js → lod-scheduler-B0a_uBlv.js} +133 -140
  41. package/dist/lod-scheduler-B0a_uBlv.js.map +1 -0
  42. package/dist/lod-scheduler.d.ts +1 -1
  43. package/dist/lod-source.d.ts +5 -5
  44. package/dist/{splat-scene.d.ts → merged-splat-mesh.d.ts} +14 -15
  45. package/dist/orientation.d.ts +7 -7
  46. package/dist/{ply-header-BwgExCn-.js → ply-header-B9WllbPg.js} +3 -3
  47. package/dist/{ply-header-BwgExCn-.js.map → ply-header-B9WllbPg.js.map} +1 -1
  48. package/dist/radix-sorter-R0FCSqKu.js +178 -0
  49. package/dist/radix-sorter-R0FCSqKu.js.map +1 -0
  50. package/dist/relighting-CSQF3UcL.js +969 -0
  51. package/dist/relighting-CSQF3UcL.js.map +1 -0
  52. package/dist/relighting.d.ts +1 -1
  53. package/dist/selection.d.ts +13 -0
  54. package/dist/selection.js +137 -0
  55. package/dist/selection.js.map +1 -0
  56. package/dist/source-transform.d.ts +4 -4
  57. package/dist/{splat-budget-DXew_86c.js → splat-budget-DuVZspPQ.js} +7 -7
  58. package/dist/{splat-budget-DXew_86c.js.map → splat-budget-DuVZspPQ.js.map} +1 -1
  59. package/dist/splat-budget.d.ts +1 -1
  60. package/dist/splat-data-BZM5dhhx.js.map +1 -1
  61. package/dist/splat-data.d.ts +4 -4
  62. package/dist/splat-mesh-aQdcWA6v.js +1942 -0
  63. package/dist/splat-mesh-aQdcWA6v.js.map +1 -0
  64. package/dist/splat-mesh-material.d.ts +2 -2
  65. package/dist/splat-mesh-types-BZIko-_9.js +18 -0
  66. package/dist/splat-mesh-types-BZIko-_9.js.map +1 -0
  67. package/dist/splat-mesh-types.d.ts +14 -8
  68. package/dist/splat-mesh.d.ts +4 -4
  69. package/dist/splat-modifier.d.ts +5 -5
  70. package/dist/splat-partition.d.ts +1 -1
  71. package/dist/static-lod-entry.d.ts +12 -0
  72. package/dist/static-lod-splat-mesh.d.ts +2 -2
  73. package/dist/{static-lod-worker-D2fyqQZO.js → static-lod-worker-DktKnJkR.js} +46 -46
  74. package/dist/{static-lod-worker-D2fyqQZO.js.map → static-lod-worker-DktKnJkR.js.map} +1 -1
  75. package/dist/static-lod.js +148 -0
  76. package/dist/static-lod.js.map +1 -0
  77. package/dist/streamed-splat-mesh.d.ts +21 -27
  78. package/dist/streaming.d.ts +23 -0
  79. package/dist/streaming.js +2877 -0
  80. package/dist/streaming.js.map +1 -0
  81. package/dist/{unified-splat-renderer.d.ts → unified-splat-mesh.d.ts} +10 -10
  82. package/dist/unified-work-buffer.d.ts +66 -0
  83. package/dist/unified.d.ts +11 -0
  84. package/dist/unified.js +738 -0
  85. package/dist/unified.js.map +1 -0
  86. package/dist/webgpu-limits-Y3CI_6_6.js +43 -0
  87. package/dist/webgpu-limits-Y3CI_6_6.js.map +1 -0
  88. package/dist/webgpu-limits.d.ts +0 -59
  89. package/dist/work-buffer-gather.d.ts +1 -1
  90. package/package.json +24 -4
  91. package/dist/splat-mesh-types-CTN_duw2.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unified.js","sources":["../src/lib/work-buffer-gather.ts","../src/lib/work-buffer-material.ts","../src/lib/unified-work-buffer.ts","../src/lib/unified-splat-mesh.ts"],"sourcesContent":["import * as THREE from 'three/webgpu';\nimport {\n Fn,\n If,\n bool,\n float,\n int,\n instanceIndex,\n ivec2,\n mat3,\n storage,\n textureLoad,\n uniform,\n vec3,\n vec4,\n} from 'three/tsl';\nimport {\n evaluateSplatSh,\n foldSplatModifierStack,\n asNode,\n applyIsotropicCovarianceOverride,\n DEFAULT_ISOTROPIC_VARIANCE_SCALE,\n type SplatShInputs,\n type Vec3Uniform,\n} from './splat-mesh-material';\nimport { colorSpaceToWorking } from 'three/tsl';\nimport type { SplatModifier } from './splat-modifier';\nimport { StorageMirrorReleaser } from './storage-attribute-mirror';\n\n/**\n * GPU gather pass for Phase 0 of the unified renderer. It resolves a source\n * pool's active local centers into a shared world-space storage buffer. The\n * work-buffer sorter consumes this buffer directly, so source-local pools can\n * later be heterogeneous (including streamed LOD pools).\n *\n * Internal while M15.4's draw path and public source API are completed.\n */\n/** Shared GPU storage for every gathered source in one unified frame. */\nexport class WorkBuffer {\n /** World-space center xyz; w is the drawable flag (1 draw, 0 modifier-hidden). */\n readonly centers: THREE.StorageBufferAttribute;\n /** Resolved color and opacity per work slot. */\n readonly colors: THREE.StorageBufferAttribute;\n /** First packed covariance row per work slot. */\n readonly covarianceA: THREE.StorageBufferAttribute;\n /** Second packed covariance row per work slot. */\n readonly covarianceB: THREE.StorageBufferAttribute;\n /** Per-slot isotropic covariance mix (0 = keep Σ, 1 = round dot). */\n readonly isotropicMix: THREE.StorageBufferAttribute;\n /** Per-slot isotropic target sigma radius in screen px (0 = disabled). */\n readonly isotropicScreenRadius: THREE.StorageBufferAttribute;\n\n /**\n * Slots past {@link capacity}, reserved so a pipeline warm-up has somewhere\n * harmless to write. See {@link WorkBufferGather.warmUp}: compiling a compute\n * pipeline requires actually dispatching it, and dispatching zero workgroups\n * - while it does compile - makes WebGPU log an \"unusual workgroup count of 0\"\n * warning for every source. One workgroup aimed at this tail compiles just the\n * same, silently, and cannot disturb a drawn slot: admission never allocates\n * past `capacity` and the draw's `instanceCount` never covers it.\n */\n static readonly SCRATCH_SLOTS = 256;\n\n constructor(readonly capacity: number) {\n const slots = capacity + WorkBuffer.SCRATCH_SLOTS;\n this.centers = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.colors = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.covarianceA = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.covarianceB = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.isotropicMix = new THREE.StorageBufferAttribute(new Float32Array(slots), 1);\n this.isotropicScreenRadius = new THREE.StorageBufferAttribute(new Float32Array(slots), 1);\n this.mirrors = new StorageMirrorReleaser([\n this.centers,\n this.colors,\n this.covarianceA,\n this.covarianceB,\n this.isotropicMix,\n this.isotropicScreenRadius,\n ]);\n }\n\n /**\n * Drops the JS-heap copies three keeps behind these storage buffers.\n *\n * Every one of them is written only by the gather compute pass and read only\n * by the draw material - the CPU side is zeroes that no code ever reads, and\n * three's WebGPU backend never frees it on its own. At 72 B/slot that mirror\n * is ~720 MB of JS heap on a 10M-slot scene. See `storage-attribute-mirror`.\n *\n * Safe to call after any dispatch that bound this buffer; a no-op once\n * settled, and on WebGL2 or a renderer double that has no WebGPU backend.\n */\n releaseCpuMirrors(renderer: THREE.WebGPURenderer): void {\n if (this.mirrors.settled) return;\n this.mirrors.release(renderer);\n }\n\n private readonly mirrors: StorageMirrorReleaser;\n}\n\nexport class WorkBufferGather {\n /** World-space `xyzw` center buffer, one element per unified work slot. */\n readonly centers: THREE.StorageBufferAttribute;\n /** Resolved source RGBA values; production draw consumes this next. */\n readonly colors: THREE.StorageBufferAttribute;\n /** Packed symmetric world-space covariance rows. */\n readonly covarianceA: THREE.StorageBufferAttribute;\n readonly covarianceB: THREE.StorageBufferAttribute;\n readonly isotropicMix: THREE.StorageBufferAttribute;\n readonly isotropicScreenRadius: THREE.StorageBufferAttribute;\n /** Shared target for this source's gathered range. */\n readonly workBuffer: WorkBuffer;\n private readonly capacity: number;\n private readonly activeCount = uniform(0);\n private readonly targetOffset = uniform(0);\n private readonly sourceMatrix = uniform(new THREE.Matrix4());\n private readonly cameraViewMatrix = uniform(new THREE.Matrix4());\n private readonly opacity = uniform(1);\n private readonly pass: THREE.ComputeNode;\n\n constructor(options: {\n capacity: number;\n sourceCapacity: number;\n sourceIndex: THREE.StorageBufferAttribute;\n centersTexture: THREE.DataTexture;\n colorsTexture: THREE.DataTexture;\n covarianceATexture: THREE.DataTexture;\n covarianceBTexture: THREE.DataTexture;\n dataTextureWidth: number;\n sh?: SplatShInputs | null;\n modifiers?: readonly SplatModifier[];\n channels?: ReadonlyMap<string, { texture: THREE.DataTexture }>;\n localCameraPosition?: Vec3Uniform;\n srgbOutput?: boolean;\n /**\n * Source stores Spark LOD alpha (`alpha ÷ 2`, `.rad`). Recover the full\n * `alpha ∈ [0,2]` here so the shared draw material sees one convention;\n * `alpha > 1` then marks a merged node (see `createWorkBufferMaterial`).\n */\n lodAlpha?: boolean;\n /** Reuse one target across all source gather passes. */\n workBuffer?: WorkBuffer;\n }) {\n const {\n capacity,\n sourceCapacity,\n sourceIndex,\n centersTexture,\n colorsTexture,\n covarianceATexture,\n covarianceBTexture,\n dataTextureWidth,\n } = options;\n const sh = options.sh ?? null;\n const modifiers = options.modifiers ?? [];\n const channels = options.channels ?? new Map<string, { texture: THREE.DataTexture }>();\n const localCameraPosition = options.localCameraPosition ?? uniform(new THREE.Vector3());\n this.workBuffer = options.workBuffer ?? new WorkBuffer(capacity);\n this.capacity = this.workBuffer.capacity;\n this.centers = this.workBuffer.centers;\n this.colors = this.workBuffer.colors;\n this.covarianceA = this.workBuffer.covarianceA;\n this.covarianceB = this.workBuffer.covarianceB;\n this.isotropicMix = this.workBuffer.isotropicMix;\n this.isotropicScreenRadius = this.workBuffer.isotropicScreenRadius;\n const source = storage(sourceIndex, 'uint', sourceCapacity);\n const output = storage(this.centers, 'vec4', capacity);\n const outputColor = storage(this.colors, 'vec4', capacity);\n const outputCovA = storage(this.covarianceA, 'vec4', capacity);\n const outputCovB = storage(this.covarianceB, 'vec4', capacity);\n const outputIsotropicMix = storage(this.isotropicMix, 'float', capacity);\n const outputIsotropicScreenRadius = storage(this.isotropicScreenRadius, 'float', capacity);\n this.pass = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n const poolIndex = int(source.element(instanceIndex));\n const texel = ivec2(\n poolIndex.mod(int(dataTextureWidth)),\n poolIndex.div(int(dataTextureWidth)),\n );\n const localCenter = textureLoad(centersTexture, texel).xyz;\n const target = instanceIndex.add(this.targetOffset.toUint());\n const inputA = textureLoad(covarianceATexture, texel);\n const inputB = textureLoad(covarianceBTexture, texel);\n const covarianceBase = mat3(\n vec3(inputA.x, inputA.y, inputA.z),\n vec3(inputA.y, inputA.w, inputB.x),\n vec3(inputA.z, inputB.x, inputB.y),\n );\n const baseColor = textureLoad(colorsTexture, texel);\n const colorAfterSh =\n sh === null\n ? baseColor\n : vec4(\n baseColor.rgb\n .add(\n evaluateSplatSh(\n sh,\n { covarianceBTexture },\n texel,\n localCenter.sub(localCameraPosition).normalize(),\n ),\n )\n .clamp(0.0, 1.0),\n baseColor.a,\n );\n const worldCenter = this.sourceMatrix.mul(vec4(localCenter, 1.0)).xyz;\n const stack = foldSplatModifierStack(modifiers, localCameraPosition, {\n index: poolIndex,\n localCenter,\n color: colorAfterSh,\n makeWorldCenter: () => worldCenter,\n makeViewCenter: () => this.cameraViewMatrix.mul(vec4(worldCenter, 1.0)).xyz,\n makeNormal: () => {\n const inverse = covarianceBase.inverse();\n const toCamera = localCameraPosition.sub(localCenter);\n const axis = inverse.mul(inverse.mul(toCamera)).normalize();\n return axis.mul(axis.dot(toCamera).sign());\n },\n makeChannel: (name) => {\n const channel = channels.get(name);\n if (!channel) {\n throw new Error(\n `UnifiedSplatMesh: source modifier reads undefined channel \"${name}\".`,\n );\n }\n return textureLoad(channel.texture, texel).r;\n },\n });\n const center = stack.offset === null ? localCenter : localCenter.add(stack.offset);\n const visible = stack.visible === null ? bool(true) : stack.visible;\n // Stable work slots: keep hidden splats in the gather/sort range, but\n // stamp drawable=0 into center.w so the draw material clips them.\n // Alpha-zero remains a secondary guard in the color channel.\n // Compacting hidden splats out instead was evaluated and rejected\n // (ROADMAP L3): the hidden set exists only on the GPU, so compaction\n // needs a count readback before the CPU-sized sort dispatch - see the\n const gatheredCenter = this.sourceMatrix.mul(vec4(center, 1.0)).xyz;\n output.element(target).assign(vec4(gatheredCenter, visible.select(float(1), float(0))));\n const resolvedColor = options.srgbOutput\n ? stack.color\n : asNode<'vec4'>(colorSpaceToWorking(stack.color, THREE.SRGBColorSpace));\n // `.rad` stores `alpha ÷ 2`; recover it here rather than in the draw\n // material, which is shared by every source and must see one alpha\n // convention. The work buffer is float storage, so `alpha > 1` (a\n // merged node) survives to the draw unclamped.\n const sourceAlpha = options.lodAlpha\n ? asNode<'float'>(resolvedColor.a.mul(2.0))\n : asNode<'float'>(resolvedColor.a);\n outputColor\n .element(target)\n .assign(vec4(resolvedColor.rgb, visible.select(sourceAlpha.mul(this.opacity), float(0))));\n const covariance =\n stack.rotation === null\n ? covarianceBase\n : stack.rotation.mul(covarianceBase).mul(stack.rotation.transpose());\n // Collapse in mesh-local space (Spark's min(scale) is local), then lift\n // to world. Applying isotropic on world Σ with a local normal inflated\n // λ and produced large soft blobs under mesh transforms.\n let localCovariance: THREE.Node<'mat3'> = asNode<'mat3'>(covariance);\n if (stack.isotropicCovarianceMix !== null) {\n const varianceScale =\n stack.isotropicVarianceScale ?? float(DEFAULT_ISOTROPIC_VARIANCE_SCALE);\n localCovariance = applyIsotropicCovarianceOverride(\n localCovariance,\n vec3(0, 0, 1),\n stack.isotropicCovarianceMix,\n varianceScale,\n );\n }\n const linear = this.sourceMatrix.toMat3();\n const worldCovarianceBase = linear.mul(localCovariance).mul(linear.transpose());\n const worldCovariance =\n stack.scaleSquared === null\n ? worldCovarianceBase\n : worldCovarianceBase.mul(stack.scaleSquared);\n const c0 = worldCovariance.mul(vec3(1, 0, 0));\n const c1 = worldCovariance.mul(vec3(0, 1, 0));\n const c2 = worldCovariance.mul(vec3(0, 0, 1));\n outputCovA.element(target).assign(vec4(c0.x, c0.y, c0.z, c1.y));\n outputCovB.element(target).assign(vec4(c1.z, c2.z, 0, 0));\n outputIsotropicMix\n .element(target)\n .assign(stack.isotropicCovarianceMix === null ? float(0) : stack.isotropicCovarianceMix);\n outputIsotropicScreenRadius\n .element(target)\n .assign(\n stack.isotropicCovarianceMix === null\n ? float(0)\n : (stack.isotropicScreenRadiusPx ?? float(0)),\n );\n });\n })().compute(sourceCapacity, [256]);\n }\n\n /** Gathers a source's active pool slots into a contiguous work-buffer range. */\n gather(\n renderer: THREE.WebGPURenderer,\n activeCount: number,\n targetOffset: number,\n sourceMatrix: THREE.Matrix4,\n cameraViewMatrixOrOpacity: THREE.Matrix4 | number = new THREE.Matrix4(),\n opacity = 1,\n ): void {\n if (activeCount < 0 || targetOffset < 0 || targetOffset + activeCount > this.capacity) {\n throw new Error('WorkBufferGather: requested range exceeds work-buffer capacity.');\n }\n this.activeCount.value = activeCount;\n this.targetOffset.value = targetOffset;\n this.sourceMatrix.value.copy(sourceMatrix);\n const cameraViewMatrix =\n typeof cameraViewMatrixOrOpacity === 'number'\n ? new THREE.Matrix4()\n : cameraViewMatrixOrOpacity;\n this.cameraViewMatrix.value.copy(cameraViewMatrix);\n this.opacity.value = Math.max(\n 0,\n typeof cameraViewMatrixOrOpacity === 'number' ? cameraViewMatrixOrOpacity : opacity,\n );\n this.pass.count = activeCount;\n renderer.compute(this.pass);\n // The dispatch just built this pass's bind group, which is what makes three\n // create the storage buffers - synchronously. Nothing reads their CPU side\n // back, so drop it. A `WorkBuffer` shared by several gathers releases on\n // whichever dispatches first; the rest find it settled.\n this.workBuffer.releaseCpuMirrors(renderer);\n }\n\n /**\n * Compiles this gather's compute pipeline without gathering anything.\n *\n * WebGPU compiles a compute pipeline lazily, at its first dispatch - not when\n * the node graph is built. Measured on a 13-mesh scene, a source's *first*\n * `gather` took up to 2.0s while every later dispatch of the same pipeline\n * took under 0.4ms, and because `gather` runs inside `update` that compile\n * landed whole in one render frame. Running it through the async path here\n * compiles off the critical frame, so the first real gather finds it ready.\n *\n * The dispatch targets {@link WorkBuffer.SCRATCH_SLOTS} past the drawable\n * capacity rather than dispatching zero workgroups: zero also compiles, but\n * WebGPU warns about it once per source, and writing into a drawn slot could\n * leave junk behind when a cached gather is reused instead of re-run.\n *\n * Best-effort: a failure here costs only the stall it was meant to avoid, so\n * callers may ignore it.\n */\n async warmUp(renderer: THREE.WebGPURenderer): Promise<void> {\n this.activeCount.value = 1;\n this.targetOffset.value = this.capacity;\n this.pass.count = 1;\n await renderer.computeAsync(this.pass);\n // Leave nothing that a later reuse check could mistake for real state.\n this.activeCount.value = 0;\n this.targetOffset.value = 0;\n // Warm-up is often the first dispatch of all, so this is usually where the\n // mirrors actually go.\n this.workBuffer.releaseCpuMirrors(renderer);\n }\n\n /** Releases the gather pipeline. The owner releases the storage attribute. */\n dispose(): void {\n this.pass.dispose();\n }\n}\n","import * as THREE from 'three/webgpu';\nimport type { FloatUniform, Vec2Uniform } from './splat-mesh-material';\nimport {\n capProjectedEigenvaluesToScreenRadius,\n equalizeProjectedEigenvalues,\n} from './splat-mesh-material';\nimport { MAX_DOF_VARIANCE } from './depth-of-field';\nimport {\n Discard,\n Fn,\n cameraProjectionMatrix,\n float,\n instanceIndex,\n mat3,\n mix,\n modelViewMatrix,\n positionGeometry,\n screenUV,\n storage,\n texture as tslTexture,\n varying,\n vec2,\n vec3,\n vec4,\n} from 'three/tsl';\n\n/**\n * Creates the unified EWA draw material for gathered world-space splats.\n *\n * Its vertex stage reads both the sorter-written order and the gather-written\n * centers, covariance, and resolved color from storage buffers. It keeps the\n * verified ±3σ / exp(-4.5·|q|²) convention used by {@link SplatMesh}.\n */\nexport function createWorkBufferMaterial(options: {\n capacity: number;\n centers: THREE.StorageBufferAttribute;\n colors: THREE.StorageBufferAttribute;\n covarianceA: THREE.StorageBufferAttribute;\n covarianceB: THREE.StorageBufferAttribute;\n isotropicMix: THREE.StorageBufferAttribute;\n isotropicScreenRadius: THREE.StorageBufferAttribute;\n order: THREE.StorageInstancedBufferAttribute;\n focal: Vec2Uniform;\n viewport: Vec2Uniform;\n maxStdDev: FloatUniform;\n /** Screen-space minimum splat radius in px; `0` disables. */\n minSplatSizePx: FloatUniform;\n antialias: FloatUniform;\n /** Format-selected projected low-pass variance, in px². */\n projectedLowPassVariance: FloatUniform;\n /** LCC always preserves the low-pass integral; standard follows antialias. */\n compensateProjectedLowPass: FloatUniform;\n /** Core projected-2D DoF focus plane. Live; `0` aperture disables. */\n dofFocusDistance: FloatUniform;\n dofAperture: FloatUniform;\n /**\n * Proxy-mesh relight map (RGB = lit, A = coverage). Display only.\n * Host swaps the texture object and rebuilds when enabling/changing map.\n */\n relightMap: THREE.Texture;\n relightBlend: FloatUniform;\n relightBrightness: FloatUniform;\n relightBackground: FloatUniform;\n /** Coverage soft edge in screen pixels; `0` = hard mask. */\n relightSoftness: FloatUniform;\n}): THREE.NodeMaterial {\n const material = new THREE.NodeMaterial();\n const centers = storage(options.centers, 'vec4', options.capacity);\n const order = storage(options.order, 'float', options.capacity);\n const colors = storage(options.colors, 'vec4', options.capacity);\n const covarianceA = storage(options.covarianceA, 'vec4', options.capacity);\n const covarianceB = storage(options.covarianceB, 'vec4', options.capacity);\n const isotropicMix = storage(options.isotropicMix, 'float', options.capacity);\n const isotropicScreenRadius = storage(options.isotropicScreenRadius, 'float', options.capacity);\n const workColor = varying(vec4(1, 1, 1, 1), 'vWorkColor');\n const quadPosition = varying(positionGeometry.xy, 'vWorkQuadPosition');\n const opacityCompensation = varying(float(1), 'vWorkOpacityCompensation');\n // Spark LOD alpha, matching `SplatMesh`'s display graph. The gather has\n // already recovered `alpha ∈ [0,2]` for `.rad` sources, so `alpha > 1`\n // identifies a merged node here without a per-source shader variant: a\n // non-`.rad` source can only reach `alpha ≤ 1`, for which the branches below\n // collapse to the plain Gaussian this material has always drawn.\n const adjustedStdDev = varying(float(0), 'vWorkAdjustedStdDev');\n material.vertexNode = Fn(() => {\n const workIndex = order.element(instanceIndex).toInt();\n const centerSample = centers.element(workIndex);\n const center = centerSample.xyz;\n // Gather stamps drawable into center.w (1 = draw, 0 = modifier-hidden).\n const drawable = centerSample.w.greaterThan(0.5);\n workColor.assign(colors.element(workIndex));\n const viewCenter = modelViewMatrix.mul(vec4(center, 1.0)).toVar();\n const clipCenter = cameraProjectionMatrix.mul(viewCenter).toVar();\n const covA = covarianceA.element(workIndex);\n const covB = covarianceB.element(workIndex);\n const covariance = mat3(\n vec3(covA.x, covA.y, covA.z),\n vec3(covA.y, covA.w, covB.x),\n vec3(covA.z, covB.x, covB.y),\n );\n const invZ = float(1).div(viewCenter.z);\n const invZ2 = invZ.mul(invZ);\n const j1 = vec3(\n options.focal.x.mul(invZ),\n 0,\n options.focal.x.negate().mul(viewCenter.x).mul(invZ2),\n );\n const j2 = vec3(\n 0,\n options.focal.y.mul(invZ),\n options.focal.y.negate().mul(viewCenter.y).mul(invZ2),\n );\n const viewRotationT = modelViewMatrix.toMat3().transpose();\n const u1 = viewRotationT.mul(j1);\n const u2 = viewRotationT.mul(j2);\n const aRaw = u1.dot(covariance.mul(u1));\n const dRaw = u2.dot(covariance.mul(u2));\n const bRaw = u1.dot(covariance.mul(u2));\n\n const depth = viewCenter.z.negate().max(1e-4);\n const focus = options.dofFocusDistance.max(1e-4);\n // Aperture maps through the live focus plane (Spark host-helper parity), so\n // a near focus plane widens the angle and softens the whole scene.\n const halfApertureAngle = options.dofAperture.max(0).mul(0.5).div(focus).atan();\n // Spark's falloff (`/depth`). Unbounded near the camera; MAX_DOF_VARIANCE\n // below is the fill-rate guard, exactly as Spark's maxPixelRadius is.\n const focusBlur = depth.sub(focus).abs().div(depth);\n const apertureRadius = options.focal.x.mul(halfApertureAngle.tan());\n const cocRadiusPx = focusBlur.mul(apertureRadius);\n const cocVar = cocRadiusPx.mul(cocRadiusPx).min(float(MAX_DOF_VARIANCE));\n\n const a = aRaw.add(options.projectedLowPassVariance).add(cocVar);\n const d = dRaw.add(options.projectedLowPassVariance).add(cocVar);\n const b = bRaw;\n const isoMix = isotropicMix.element(workIndex);\n const detBlur = a.mul(d).sub(b.mul(b)).max(1e-9);\n // Classic LCC always compensates its 0.1 px² low-pass. Standard sources\n // retain their antialias-controlled 0.3 px² compatibility behavior.\n const detRaw = aRaw.mul(dRaw).sub(bRaw.mul(bRaw)).max(0);\n const detBase = aRaw\n .add(options.projectedLowPassVariance)\n .mul(dRaw.add(options.projectedLowPassVariance))\n .sub(bRaw.mul(bRaw))\n .max(1e-9);\n const mipFade = detRaw.div(detBlur).sqrt();\n const dofOnlyFade = detBase.div(detBlur).sqrt();\n const compensate = options.antialias.max(options.compensateProjectedLowPass);\n const fade = mix(dofOnlyFade, mipFade, compensate);\n // Screen-capped isotropic points must not inherit the mip-filter opacity\n // fade - it was derived from the pre-cap footprint and washes out dots.\n opacityCompensation.assign(mix(fade, float(1), isoMix));\n const mid = a.add(d).mul(0.5);\n const radius = vec2(a.sub(d).mul(0.5), b).length();\n let lambda1 = mid.add(radius);\n let lambda2 = mid.sub(radius).max(0);\n const equalized = equalizeProjectedEigenvalues(lambda1, lambda2, isoMix);\n lambda1 = equalized.lambda1;\n lambda2 = equalized.lambda2;\n const capped = capProjectedEigenvaluesToScreenRadius(\n lambda1,\n lambda2,\n isoMix,\n isotropicScreenRadius.element(workIndex),\n options.maxStdDev,\n );\n lambda1 = capped.lambda1;\n lambda2 = capped.lambda2;\n // A merged node (`alpha > 1`) grows the σ-cutoff `maxStdDev + 0.7·(remap−1)`\n // so one coarse splat covers the subtree it stands in for; the covariance is\n // untouched. A leaf keeps the base cutoff. `SplatMesh` does the same at\n // `applySplatMaterialGraph`'s `lodAlpha` branch.\n const remap = workColor.a.mul(4).sub(3).min(5);\n const stdDev = workColor.a\n .greaterThan(1)\n .select(options.maxStdDev.add(remap.sub(1).mul(0.7)), options.maxStdDev);\n adjustedStdDev.assign(stdDev);\n const eigenvector = vec2(b, lambda1.sub(a)).add(vec2(1e-6, 0)).normalize();\n // Screen-space minimum on each axis: a splat below the floor grows to it so\n // its Gaussian tiles with neighbours instead of leaving dark gaps between\n // sparse zoomed-out splats; already-large splats are untouched. Mirrors\n // `applySplatMaterialGraph`. `.max` after `.min(1024)` keeps the order valid.\n const minSplat = options.minSplatSizePx;\n const major = eigenvector.mul(lambda1.sqrt().mul(stdDev).min(1024).max(minSplat));\n const minor = vec2(eigenvector.y, eigenvector.x.negate()).mul(\n lambda2.sqrt().mul(stdDev).min(1024).max(minSplat),\n );\n const pixelOffset = major.mul(positionGeometry.x).add(minor.mul(positionGeometry.y));\n const ndcCenter = clipCenter.xy.div(clipCenter.w);\n const clipPosition = vec4(\n ndcCenter.add(pixelOffset.mul(2).div(options.viewport)),\n clipCenter.z.div(clipCenter.w),\n 1,\n );\n // Match SplatMesh's conservative center-frustum rejection. Without this,\n // a center behind the camera can produce a mirrored, screen-filling quad.\n // Modifier-hidden entries (drawable=0) share the clipped destination so\n // they generate no fragments while keeping a stable sort slot.\n const margin = clipCenter.w.mul(1.2);\n const inFrustum = clipCenter.z\n .greaterThan(margin.negate())\n .and(clipCenter.x.abs().lessThan(margin))\n .and(clipCenter.y.abs().lessThan(margin));\n return inFrustum.and(drawable).select(clipPosition, vec4(0, 0, 2, 1));\n })();\n material.fragmentNode = Fn(() => {\n const squaredDistance = quadPosition.dot(quadPosition);\n Discard(squaredDistance.greaterThan(1));\n // Spark's LOD falloff, mirroring `SplatMesh`'s display fragment.\n // `g = exp(-½·adjustedStdDev²·|q|²)` is the Gaussian at this fragment. A leaf\n // (`alpha ≤ 1`, and every non-`.rad` source) composites `g · alpha`. A merged\n // node uses the super-Gaussian plateau `1 − (1 − g)^a`, `a = exp((remap²−1)/e)`,\n // so a coarse splat fills its subtree's footprint without inflating Σ.\n const g = squaredDistance.mul(adjustedStdDev.mul(adjustedStdDev).mul(-0.5)).exp();\n const remap = workColor.a.mul(4).sub(3).min(5);\n const aExp = remap\n .mul(remap)\n .sub(1)\n .mul(1 / Math.E)\n .exp();\n const merged = g.oneMinus().pow(aExp).oneMinus();\n const opacity = workColor.a.greaterThan(1).select(merged, g.mul(workColor.a));\n const alpha = opacity.mul(opacityCompensation);\n const litCenter = tslTexture(options.relightMap, screenUV);\n const ox = options.relightSoftness.div(options.viewport.x.max(1));\n const oy = options.relightSoftness.div(options.viewport.y.max(1));\n const s0 = litCenter;\n const s1 = tslTexture(options.relightMap, screenUV.add(vec2(ox, 0)));\n const s2 = tslTexture(options.relightMap, screenUV.add(vec2(ox.negate(), 0)));\n const s3 = tslTexture(options.relightMap, screenUV.add(vec2(0, oy)));\n const s4 = tslTexture(options.relightMap, screenUV.add(vec2(0, oy.negate())));\n const wSum = s0.a.add(s1.a).add(s2.a).add(s3.a).add(s4.a).max(1e-4);\n const rgbSoft = s0.rgb\n .mul(s0.a)\n .add(s1.rgb.mul(s1.a))\n .add(s2.rgb.mul(s2.a))\n .add(s3.rgb.mul(s3.a))\n .add(s4.rgb.mul(s4.a))\n .div(wSum);\n const aSoft = wSum.mul(0.2);\n const litSoft = vec4(rgbSoft, aSoft);\n const lit = options.relightSoftness.greaterThan(0.5).select(litSoft, litCenter);\n const factor = mix(\n vec3(options.relightBackground),\n lit.rgb.mul(options.relightBrightness),\n lit.a,\n );\n const rgb = mix(workColor.rgb, workColor.rgb.mul(factor), options.relightBlend);\n return vec4(rgb.mul(alpha), alpha);\n })();\n material.transparent = true;\n material.depthTest = true;\n material.depthWrite = false;\n material.side = THREE.DoubleSide;\n material.blending = THREE.CustomBlending;\n material.blendSrc = THREE.OneFactor;\n material.blendDst = THREE.OneMinusSrcAlphaFactor;\n material.blendSrcAlpha = THREE.OneFactor;\n material.blendDstAlpha = THREE.OneMinusSrcAlphaFactor;\n material.toneMapped = false;\n return material;\n}\n","/**\n * Byte costs of {@link UnifiedSplatMesh}'s GPU work buffer.\n *\n * Kept off the core entry so hosts that never composite sources do not pay\n * for the accounting, and so the numbers cannot drift from the renderer\n * that allocates those buffers.\n */\n\n/** Bytes per splat in the largest unified work-buffer attribute (RGBA32F centers). */\nexport const WORK_BUFFER_CENTERS_BYTES_PER_SPLAT = 16;\n\n/**\n * Bytes per work-buffer slot across *all* of its attributes: centers, colors,\n * covarianceA and covarianceB at RGBA32F (16 each), plus the isotropic mix and\n * screen-radius scalars (4 each).\n */\nexport const WORK_BUFFER_BYTES_PER_SLOT = 16 * 4 + 4 * 2;\n\nfunction assertNonNegativeCapacity(capacity: number): void {\n if (!Number.isFinite(capacity) || capacity < 0) {\n throw new RangeError('Splat capacity must be a non-negative finite number.');\n }\n}\n\n/**\n * Byte size of the largest single storage-buffer bind VLAM allocates for a\n * given splat capacity (unified work-buffer centers: `capacity × 16`).\n *\n * Use with adapter/device limits to decide whether to raise `requiredLimits`\n * or lower the budget before constructing {@link UnifiedSplatMesh}.\n */\nexport function estimateLargestStorageBufferBytes(capacity: number): number {\n assertNonNegativeCapacity(capacity);\n return Math.floor(capacity) * WORK_BUFFER_CENTERS_BYTES_PER_SPLAT;\n}\n\n/**\n * Estimates what a {@link UnifiedSplatMesh}'s work buffer costs, in bytes,\n * once the scene has rendered a frame.\n *\n * This used to be double the figure below, and the doubling was not a detail.\n * Every `THREE.StorageBufferAttribute` is constructed around a JS typed array,\n * and three's WebGPU backend never releases it: the\n * `attribute.onUploadCallback()` call that would is missing from the backend's\n * buffer upload path. So a work buffer of N slots cost\n * N × {@link WORK_BUFFER_BYTES_PER_SLOT} on the GPU *and* the same again on the\n * JS heap, permanently, even though nothing ever reads the CPU copy back - the\n * gather pass writes these buffers entirely on the GPU.\n *\n * `WorkBuffer.releaseCpuMirrors` now drops those mirrors after the first\n * dispatch that uploads them (see `storage-attribute-mirror`), so steady state\n * is the GPU side alone. The *peak* is still double - the array has to exist\n * for the upload copy - which is what\n * {@link estimateUnifiedWorkBufferPeakBytes} reports.\n *\n * Either figure is invisible to `estimateSplatPoolBytes`, which prices the\n * splat *pool* and knows nothing about the unified renderer's scratch space. A\n * host sizing a scene for a memory-constrained device wants both.\n *\n * @param capacity - Work-buffer capacity in splats.\n * @returns Estimated steady-state bytes.\n * @throws {RangeError} if `capacity` is not a non-negative finite number.\n */\nexport function estimateUnifiedWorkBufferBytes(capacity: number): number {\n assertNonNegativeCapacity(capacity);\n return Math.floor(capacity) * WORK_BUFFER_BYTES_PER_SLOT;\n}\n\n/**\n * Peak bytes a {@link UnifiedSplatMesh}'s work buffer occupies: the window\n * between construction and the first dispatch, while the GPU buffer and the JS\n * array three copied it from are both alive.\n *\n * Ask this one when the question is \"will allocating this scene succeed\" - an\n * out-of-memory failure happens against the peak. Ask\n * {@link estimateUnifiedWorkBufferBytes} when the question is \"what will this\n * scene hold once it is up\".\n *\n * @param capacity - Work-buffer capacity in splats.\n * @returns Estimated bytes, GPU plus the not-yet-released CPU mirror.\n * @throws {RangeError} if `capacity` is not a non-negative finite number.\n */\nexport function estimateUnifiedWorkBufferPeakBytes(capacity: number): number {\n return estimateUnifiedWorkBufferBytes(capacity) * 2;\n}\n","import * as THREE from 'three/webgpu';\nimport { uniform } from 'three/tsl';\nimport { ComputeSorter, releaseRendererAttributes } from './compute-sorter';\nimport { RadixSorter } from './radix-sorter';\nimport { clampDepthOfFieldSettings, type DepthOfFieldSettings } from './depth-of-field';\nimport {\n clampRelightingSettings,\n createPlaceholderRelightTexture,\n DEFAULT_RELIGHT_BACKGROUND,\n DEFAULT_RELIGHT_BRIGHTNESS,\n DEFAULT_RELIGHT_SOFTNESS,\n type RelightingSettings,\n type RelightingUniforms,\n} from './relighting';\nimport { SplatMesh } from './splat-mesh';\nimport type { SplatPickOptions, SplatPickResult, UnifiedSourceView } from './splat-mesh';\nimport { isFillConstrainedSplatDevice } from './splat-budget';\nimport { WebGpuSortScheduler } from './sort-scheduler';\nimport { WorkBuffer, WorkBufferGather } from './work-buffer-gather';\nimport { createWorkBufferMaterial } from './work-buffer-material';\nimport type { FloatUniform, Vec2Uniform } from './splat-mesh-material';\nimport { assertStorageBufferFitsDevice } from './webgpu-limits';\nimport { estimateLargestStorageBufferBytes } from './unified-work-buffer';\nimport { resolveXrView } from './xr-view';\nimport { StorageMirrorReleaser } from './storage-attribute-mirror';\nimport type { SplatSorter } from './sorter';\n\ninterface SourceRecord {\n source: SplatMesh;\n gather: WorkBufferGather;\n graphRevision: number;\n priority: number;\n opacity: number;\n visible: boolean;\n /** Whether modifier uniforms are host-invalidated instead of assumed live. */\n cacheModifiers: boolean;\n originalVisible: boolean;\n lastGather: {\n activeCount: number;\n contentRevision: number;\n offset: number;\n opacity: number;\n matrixWorld: THREE.Matrix4;\n /** Local camera position used by view-dependent SH evaluation. */\n localCameraPosition: THREE.Vector3;\n } | null;\n /** Per-prepare scratch: the source view snapshot for the current frame. */\n view: UnifiedSourceView | null;\n /** Per-prepare scratch: registration index for stable priority ordering. */\n registrationOrder: number;\n /** Per-prepare scratch: this frame's admitted work-buffer offset. */\n offset: number;\n}\n\n/** One admitted source's work-buffer ownership from a prepared frame. */\ninterface LayoutEntry {\n source: SplatMesh;\n offset: number;\n activeCount: number;\n}\n\n/** Registration settings for one source in a {@link UnifiedSplatMesh}. */\nexport interface UnifiedSplatSourceOptions {\n /** Whole-source opacity applied after its modifier stack. Defaults to `1`. */\n opacity?: number;\n /** Whether this source contributes to the unified draw. Defaults to `true`. */\n visible?: boolean;\n /** Higher values survive a fixed work-buffer overflow first. Defaults to `0`. */\n priority?: number;\n /**\n * Cache a modifier-bearing source until {@link UnifiedSplatMesh.invalidateSource}\n * is called. Defaults to `false`, which safely re-gathers live modifier\n * uniforms every frame.\n *\n * Use this for static modifiers, or when the host has a natural dirty signal\n * for every uniform/channel mutation. Forgetting to invalidate leaves the\n * unified work-buffer output stale.\n */\n cacheModifiers?: boolean;\n}\n\n/**\n * Result of a successful {@link UnifiedSplatMesh.pick}: the frontmost hit\n * across every visible registered source, tagged with the source it landed on.\n */\nexport interface UnifiedSplatPickResult extends SplatPickResult {\n /** The registered source mesh whose splat produced the winning hit. */\n readonly source: SplatMesh;\n}\n\n/**\n * Construction settings shared by every source in a unified renderer.\n *\n * @experimental May change in a minor release.\n */\nexport interface UnifiedSplatMeshOptions {\n /** Composite source colors in display (sRGB) space. Defaults to `false`. */\n srgbOutput?: boolean;\n /**\n * Global depth-sort strategy. `'exact'` preserves every Float32 depth bit\n * through a stable radix sort of gathered world-space centers. `'counting'`\n * remains the lower-cost default for existing callers.\n *\n * @experimental Exact sorting trades two additional radix passes for better\n * ordering in large scenes with dense foliage or overlapping surfaces.\n */\n sortStrategy?: 'counting' | 'exact';\n}\n\n/**\n * Returns true when `renderer` can drive {@link UnifiedSplatMesh}.\n * Heterogeneous gather/sort/draw is WebGPU-only; WebGL2 hosts keep standalone\n * `SplatMesh` draws or static {@link MergedSplatMesh}.\n */\nexport function supportsUnifiedSplatMesh(renderer: object): boolean {\n return (\n (renderer as { backend?: { isWebGPUBackend?: boolean } }).backend?.isWebGPUBackend === true\n );\n}\n\n/**\n * One gather → global-sort → draw path for static and streamed splat sources.\n * Sources retain their own residency and update lifecycle; this mesh gathers\n * their current active pool ranges into one shared work buffer per frame.\n *\n * The unified mesh stays at identity (`matrixAutoUpdate` is off). Pose each\n * registered source mesh instead - sorter bounds and the draw material both\n * consume world-space centers written by gather.\n *\n * WebGPU only. Prefer {@link supportsUnifiedSplatMesh} before construction.\n *\n * @experimental May change in a minor release.\n */\nexport class UnifiedSplatMesh extends THREE.Mesh {\n private readonly workBuffer: WorkBuffer;\n private readonly workSourceIndex: THREE.StorageBufferAttribute;\n /** The sorted draw-order buffer; lives outside the geometry, freed on dispose. */\n private readonly orderAttribute: THREE.StorageInstancedBufferAttribute;\n /**\n * Frees the JS mirrors three keeps behind `workSourceIndex` and `order`.\n *\n * `workSourceIndex` is an identity ramp written once at construction; `order`\n * is written only by the sorter, on the GPU. Neither is ever read back, so\n * both mirrors are 4 B/slot of dead JS heap once uploaded.\n */\n private readonly mirrors: StorageMirrorReleaser;\n private readonly sorter: SplatSorter;\n private readonly renderer: THREE.WebGPURenderer;\n private readonly sources: SourceRecord[] = [];\n private previousLayout: LayoutEntry[] = [];\n /** Previous layout keyed by source; kept in step with {@link previousLayout}. */\n private readonly previousLayoutBySource = new Map<SplatMesh, LayoutEntry>();\n /** Per-prepare scratch lists, reused so a steady frame allocates nothing. */\n private readonly candidateScratch: SourceRecord[] = [];\n private readonly admittedScratch: SourceRecord[] = [];\n private readonly drawingBufferSize = new THREE.Vector2();\n /** Reused target dimensions for secondary views; avoids per-view garbage. */\n private readonly secondaryViewSize = new THREE.Vector2();\n /**\n * Camera gating for the global sorter, mirroring the standalone\n * `SplatMesh.requestSortIfNeeded` path: an unchanged work buffer under a\n * stationary camera skips the whole-buffer re-sort. Content changes\n * (regather, layout change) force the next sort through the scheduler.\n */\n private readonly sortScheduler: WebGpuSortScheduler;\n /** Model-view of the last accepted sort; starts unmatchable (zero scale). */\n private readonly lastSortedModelView = new THREE.Matrix4().makeScale(0, 0, 0);\n /**\n * Total admitted splats in the last prepared frame. `removeSource` filters\n * {@link previousLayout} directly, so a tail source's removal can leave the\n * remaining entries identical - only this total betrays the shrink, and a\n * shrunk draw range must re-sort (the old permutation indexes past it).\n */\n private previousAdmittedTotal = 0;\n private readonly bounds = new THREE.Sphere();\n private readonly focal: Vec2Uniform;\n private readonly viewport: Vec2Uniform;\n private readonly maxStdDev: FloatUniform;\n private readonly minSplatSizePx: FloatUniform;\n private readonly antialias: FloatUniform;\n private readonly projectedLowPassVariance: FloatUniform;\n private readonly compensateProjectedLowPass: FloatUniform;\n private readonly dofFocusDistance: FloatUniform;\n private readonly dofAperture: FloatUniform;\n private readonly relightPlaceholder: THREE.DataTexture;\n private relightMap: THREE.Texture;\n private readonly relightBlend: FloatUniform;\n private readonly relightBrightness: FloatUniform;\n private readonly relightBackground: FloatUniform;\n private readonly relightSoftness: FloatUniform;\n private readonly srgbOutput: boolean;\n private sourceMaxStdDev: number | null = null;\n private sourceAntialias: boolean | null = null;\n private sourceProjectedFilterProfile: 'default' | 'lcc' | null = null;\n private overflowedSourceCount = 0;\n private overflowedSplatCount = 0;\n private disposed = false;\n\n constructor(\n renderer: THREE.WebGPURenderer,\n capacity: number,\n options: UnifiedSplatMeshOptions = {},\n ) {\n if (!supportsUnifiedSplatMesh(renderer)) {\n throw new Error(\n 'UnifiedSplatMesh requires a WebGPU backend (renderer.backend.isWebGPUBackend). ' +\n 'On WebGL2 use standalone SplatMesh draws or static MergedSplatMesh.',\n );\n }\n // Fail before allocating StorageBufferAttributes that would trip a cryptic\n // CreateBindGroup validation error when the host left the 128 MiB default.\n assertStorageBufferFitsDevice(renderer, estimateLargestStorageBufferBytes(capacity), capacity);\n const workBuffer = new WorkBuffer(capacity);\n const focal = uniform(new THREE.Vector2());\n const viewport = uniform(new THREE.Vector2());\n const maxStdDev = uniform(3);\n const minSplatSizePx = uniform(0);\n const antialias = uniform(0);\n const projectedLowPassVariance = uniform(0.3);\n const compensateProjectedLowPass = uniform(0);\n const dofFocusDistance = uniform(10);\n const dofAperture = uniform(0);\n const relightPlaceholder = createPlaceholderRelightTexture();\n const relightBlend = uniform(0);\n const relightBrightness = uniform(DEFAULT_RELIGHT_BRIGHTNESS);\n const relightBackground = uniform(DEFAULT_RELIGHT_BACKGROUND);\n const relightSoftness = uniform(DEFAULT_RELIGHT_SOFTNESS);\n const order = new THREE.StorageInstancedBufferAttribute(new Float32Array(capacity), 1);\n const geometry = new THREE.InstancedBufferGeometry();\n geometry.setIndex([0, 1, 2, 0, 2, 3]);\n geometry.setAttribute(\n 'position',\n new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]), 3),\n );\n geometry.instanceCount = 0;\n super(\n geometry,\n createWorkBufferMaterial({\n capacity,\n centers: workBuffer.centers,\n colors: workBuffer.colors,\n covarianceA: workBuffer.covarianceA,\n covarianceB: workBuffer.covarianceB,\n isotropicMix: workBuffer.isotropicMix,\n isotropicScreenRadius: workBuffer.isotropicScreenRadius,\n order,\n focal,\n viewport,\n maxStdDev,\n minSplatSizePx,\n antialias,\n projectedLowPassVariance,\n compensateProjectedLowPass,\n dofFocusDistance,\n dofAperture,\n relightMap: relightPlaceholder,\n relightBlend,\n relightBrightness,\n relightBackground,\n relightSoftness,\n }),\n );\n const indices = new Uint32Array(capacity);\n for (let i = 0; i < capacity; i++) indices[i] = i;\n this.workBuffer = workBuffer;\n this.focal = focal;\n this.viewport = viewport;\n this.maxStdDev = maxStdDev;\n this.minSplatSizePx = minSplatSizePx;\n this.antialias = antialias;\n this.projectedLowPassVariance = projectedLowPassVariance;\n this.compensateProjectedLowPass = compensateProjectedLowPass;\n this.dofFocusDistance = dofFocusDistance;\n this.dofAperture = dofAperture;\n this.relightPlaceholder = relightPlaceholder;\n this.relightMap = relightPlaceholder;\n this.relightBlend = relightBlend;\n this.relightBrightness = relightBrightness;\n this.relightBackground = relightBackground;\n this.relightSoftness = relightSoftness;\n this.srgbOutput = options.srgbOutput ?? false;\n this.sortScheduler = new WebGpuSortScheduler(undefined, isFillConstrainedSplatDevice());\n this.orderAttribute = order;\n this.workSourceIndex = new THREE.StorageBufferAttribute(indices, 1);\n this.mirrors = new StorageMirrorReleaser([this.workSourceIndex, this.orderAttribute]);\n this.renderer = renderer;\n const sortInputs = {\n renderer,\n capacity,\n centersBuffer: workBuffer.centers,\n splatIndexAttribute: order,\n sourceIndexAttribute: this.workSourceIndex,\n };\n this.sorter =\n options.sortStrategy === 'exact'\n ? new RadixSorter({ ...sortInputs, exactDepth: true })\n : new ComputeSorter(sortInputs);\n this.frustumCulled = false;\n this.matrixAutoUpdate = false;\n this.matrix.identity();\n this.matrixWorld.identity();\n }\n\n /** Fixed work-buffer splat capacity chosen at construction. */\n get capacity(): number {\n return this.workBuffer.capacity;\n }\n\n /** Registers a source. The caller keeps ownership and may still query it. */\n addSource(source: SplatMesh, options: UnifiedSplatSourceOptions = {}): void {\n this.assertNotDisposed('addSource');\n if (this.sources.some((record) => record.source === source)) return;\n const view = source.getUnifiedSourceView();\n const resolvedVisible = options.visible ?? source.visible;\n if (view.hasSourcePlacement) {\n // The gather pass resolves one matrix per source (the mesh's\n // `matrixWorld`); it knows nothing about a pool's *inner* per-source\n // placement, so a nested scene would draw every one of its sources at\n // its pool-local position. Reject rather than draw it wrong.\n throw new Error(\n 'UnifiedSplatMesh: a MergedSplatMesh is already a unified pool with its own global ' +\n 'sort; add its sources individually, or draw the mesh directly.',\n );\n }\n if (view.srgbOutput !== this.srgbOutput) {\n throw new Error(\"UnifiedSplatMesh: every source must use the renderer's srgbOutput setting.\");\n }\n if (this.sourceMaxStdDev === null) {\n this.sourceMaxStdDev = view.maxStdDev;\n this.maxStdDev.value = view.maxStdDev;\n this.minSplatSizePx.value = view.minSplatSizePx;\n } else if (this.sourceMaxStdDev !== view.maxStdDev) {\n throw new Error('UnifiedSplatMesh: every source must use the same maxStdDev setting.');\n } else if (this.minSplatSizePx.value !== view.minSplatSizePx) {\n throw new Error('UnifiedSplatMesh: every source must use the same minSplatSizePx setting.');\n }\n if (this.sourceAntialias === null) {\n this.sourceAntialias = view.antialias;\n this.antialias.value = view.antialias ? 1 : 0;\n } else if (this.sourceAntialias !== view.antialias) {\n throw new Error('UnifiedSplatMesh: every source must use the same antialias setting.');\n }\n if (this.sourceProjectedFilterProfile === null) {\n this.sourceProjectedFilterProfile = view.projectedFilterProfile;\n const lccProfile = view.projectedFilterProfile === 'lcc';\n this.projectedLowPassVariance.value = lccProfile ? 0.1 : 0.3;\n this.compensateProjectedLowPass.value = lccProfile ? 1 : 0;\n } else if (this.sourceProjectedFilterProfile !== view.projectedFilterProfile) {\n throw new Error('UnifiedSplatMesh: every source must use the same projected filter profile.');\n }\n this.sources.push({\n source,\n gather: this.createGather(view),\n graphRevision: view.graphRevision,\n priority: options.priority ?? 0,\n opacity: options.opacity ?? 1,\n visible: resolvedVisible,\n cacheModifiers: options.cacheModifiers ?? false,\n originalVisible: source.visible,\n lastGather: null,\n view: null,\n registrationOrder: 0,\n offset: 0,\n });\n source.visible = false;\n source.setUnifiedPickVisibility(resolvedVisible);\n }\n\n /**\n * Invalidates one registered source's cached gather output.\n *\n * Required after changing modifier uniforms or channels when that source was\n * registered with `cacheModifiers: true`. The following {@link update}\n * re-gathers the source and re-sorts because modifiers may move splats.\n * Returns `false` when the source is not registered.\n */\n invalidateSource(source: SplatMesh): boolean {\n this.assertNotDisposed('invalidateSource');\n const record = this.sources.find((candidate) => candidate.source === source);\n if (!record) return false;\n record.lastGather = null;\n return true;\n }\n\n /** Number of whole sources omitted in the most recent update due to capacity. */\n get droppedSourceCount(): number {\n return this.overflowedSourceCount;\n }\n\n /** Number of active splats omitted in the most recent update due to capacity. */\n get droppedSplatCount(): number {\n return this.overflowedSplatCount;\n }\n\n /** Updates a source's resolved opacity without rebuilding its gather pipeline. */\n setSourceOpacity(source: SplatMesh, opacity: number): void {\n this.assertNotDisposed('setSourceOpacity');\n const record = this.sources.find((entry) => entry.source === source);\n if (!record) throw new Error('UnifiedSplatMesh: source is not registered.');\n record.opacity = opacity;\n }\n\n /** Shows or hides a source without changing its residency. */\n setSourceVisible(source: SplatMesh, visible: boolean): void {\n this.assertNotDisposed('setSourceVisible');\n const record = this.sources.find((entry) => entry.source === source);\n if (!record) throw new Error('UnifiedSplatMesh: source is not registered.');\n record.visible = visible;\n record.source.setUnifiedPickVisibility(visible);\n }\n\n /**\n * Core projected-2D depth of field on the unified draw pass (not gather).\n * Live uniforms - no gather-cache invalidation. Pass `aperture: 0` to disable.\n */\n setDepthOfField(settings: Partial<DepthOfFieldSettings>): void {\n this.assertNotDisposed('setDepthOfField');\n const next = clampDepthOfFieldSettings(settings, this.getDepthOfField());\n this.dofFocusDistance.value = next.focusDistance;\n this.dofAperture.value = next.aperture;\n }\n\n /** Current core DoF uniforms (`aperture === 0` means off). */\n getDepthOfField(): DepthOfFieldSettings {\n return {\n focusDistance: this.dofFocusDistance.value,\n aperture: this.dofAperture.value,\n };\n }\n\n /**\n * PlayCanvas-style proxy-mesh relighting on the unified draw pass (not gather).\n * Live blend/brightness/background; map identity change rebuilds the draw\n * material. Does not invalidate gather caches. Pass `null` to disable.\n */\n setRelighting(options: RelightingSettings | null): void {\n this.assertNotDisposed('setRelighting');\n if (options === null) {\n this.relightBlend.value = 0;\n if (this.relightMap !== this.relightPlaceholder) {\n this.relightMap = this.relightPlaceholder;\n this.rebuildDrawMaterial();\n }\n return;\n }\n const next = clampRelightingSettings(options, this.getRelighting());\n this.relightBlend.value = next.blend;\n this.relightBrightness.value = next.brightness;\n this.relightBackground.value = next.background;\n this.relightSoftness.value = next.softness;\n if (options.map !== this.relightMap) {\n this.relightMap = options.map;\n this.rebuildDrawMaterial();\n }\n }\n\n /** Current relight numeric uniforms (`blend === 0` means off). */\n getRelighting(): RelightingUniforms {\n return {\n blend: this.relightBlend.value,\n brightness: this.relightBrightness.value,\n background: this.relightBackground.value,\n softness: this.relightSoftness.value,\n };\n }\n\n /**\n * Asynchronously picks the frontmost visible splat under an NDC coordinate\n * across every registered source.\n *\n * Semantics: each source that is\n * currently visible in this unified renderer runs its own depth-tested GPU\n * pick pass; the hit nearest the camera wins and the result names its source\n * mesh. Hidden sources (`setSourceVisible(source, false)`) never hit. A hit\n * whose source was removed, hidden, or disposed while its readback was in\n * flight is dropped rather than misattributed. Resolves `null` for misses,\n * an empty source list, and picks pending across {@link dispose}.\n *\n * Uses the construction renderer. Concurrent calls are safe: each source\n * serializes its own picks and shared renderer state is restored\n * synchronously before any await.\n */\n async pick(\n ndc: THREE.Vector2,\n camera: THREE.Camera,\n options?: SplatPickOptions,\n ): Promise<UnifiedSplatPickResult | null> {\n if (this.disposed) return null;\n const candidates = this.sources\n .filter((record) => record.visible)\n .map((record) => record.source);\n if (candidates.length === 0) return null;\n const hits = await Promise.all(\n candidates.map(async (source) => {\n const hit = await source.pick(ndc, camera, this.renderer, options);\n return hit === null ? null : { source, point: hit.point, distance: hit.distance };\n }),\n );\n if (this.disposed) return null;\n let best: UnifiedSplatPickResult | null = null;\n for (const hit of hits) {\n if (hit === null) continue;\n // Registration may have changed while the readbacks were in flight;\n // never attribute a hit to a source no longer picked by this renderer.\n const record = this.sources.find((entry) => entry.source === hit.source);\n if (record === undefined || !record.visible) continue;\n if (best === null || hit.distance < best.distance) best = hit;\n }\n return best;\n }\n\n /** Unregisters a source and restores its standalone draw visibility. */\n removeSource(source: SplatMesh): boolean {\n this.assertNotDisposed('removeSource');\n const index = this.sources.findIndex((entry) => entry.source === source);\n if (index < 0) return false;\n const [record] = this.sources.splice(index, 1);\n if (record) {\n record.gather.dispose();\n record.source.visible = record.originalVisible;\n record.source.setUnifiedPickVisibility(null);\n record.lastGather = null;\n }\n this.previousLayout = this.previousLayout.filter((entry) => entry.source !== source);\n this.previousLayoutBySource.delete(source);\n if (this.sources.length === 0) {\n this.sourceMaxStdDev = null;\n this.sourceAntialias = null;\n this.sourceProjectedFilterProfile = null;\n this.previousLayout = [];\n this.previousLayoutBySource.clear();\n }\n return true;\n }\n\n /** Updates source LODs, gathers their active splats, then globally sorts them. */\n update(camera: THREE.PerspectiveCamera): void {\n if (this.disposed) return;\n this.prepare(camera);\n }\n\n /**\n * Re-sorts and draws this unified source list for a secondary camera.\n * Mirrors and portals use this instead of rendering the hidden source meshes\n * individually, so their reflection keeps one global transparent order.\n */\n renderView(\n camera: THREE.PerspectiveCamera,\n renderer: THREE.WebGPURenderer,\n target: THREE.RenderTarget | null = null,\n ): void {\n if (this.disposed) return;\n if (renderer !== this.renderer) {\n throw new Error('UnifiedSplatMesh: renderView must use its construction renderer.');\n }\n if (!supportsUnifiedSplatMesh(renderer)) {\n throw new Error(\n 'UnifiedSplatMesh requires a WebGPU backend (renderer.backend.isWebGPUBackend).',\n );\n }\n // A secondary view must sort for its own camera regardless of cadence, and\n // it leaves a foreign order in the shared buffer - invalidate afterwards so\n // the next primary update re-sorts even if its camera has not moved.\n const targetSize = target ? this.secondaryViewSize.set(target.width, target.height) : undefined;\n this.prepare(camera, targetSize, true);\n const previousTarget = renderer.getRenderTarget();\n try {\n renderer.setRenderTarget(target);\n renderer.render(this, camera);\n } finally {\n renderer.setRenderTarget(previousTarget);\n // `prepare` left the shared order buffer sorted for the secondary view.\n // Invalidate even when its draw throws, or a stationary primary view can\n // render through that foreign order after the host recovers.\n this.sortScheduler.invalidate();\n }\n }\n\n private prepare(\n camera: THREE.PerspectiveCamera,\n targetSize?: THREE.Vector2,\n forceSort = false,\n ): void {\n if (!supportsUnifiedSplatMesh(this.renderer)) {\n throw new Error(\n 'UnifiedSplatMesh requires a WebGPU backend (renderer.backend.isWebGPUBackend).',\n );\n }\n // Hosts must not pose this mesh; world centers already include source transforms.\n this.matrix.identity();\n this.matrixWorld.identity();\n // A secondary view (`renderView`) states its own size and camera; the\n // primary one follows the renderer, which in XR means a *per-eye* viewport\n // and a cyclopean head pose. Taking the drawing buffer there would be twice\n // the true width and halve every splat's horizontal size, and sorting from\n // the application camera would order the scene from wherever that camera\n // was left standing.\n const xrView = targetSize ? null : resolveXrView(camera, this.renderer);\n const projectionCamera: THREE.Camera = xrView?.eye ?? camera;\n // One global sort from the head serves both eyes (see `xr-view.ts`).\n const viewCamera: THREE.Camera = xrView?.head ?? camera;\n const viewport = this.drawingBufferSize;\n if (targetSize) viewport.copy(targetSize);\n else if (xrView) viewport.set(xrView.width, xrView.height);\n else this.renderer.getDrawingBufferSize(viewport);\n const projection = projectionCamera.projectionMatrix.elements;\n this.viewport.value.copy(viewport);\n const focalX = projection[0] ?? 0;\n const focalY = projection[5] ?? 0;\n this.focal.value.set((focalX * viewport.x) / 2, (focalY * viewport.y) / 2);\n const updated = this.candidateScratch;\n updated.length = 0;\n for (let i = 0; i < this.sources.length; i++) {\n const record = this.sources[i] as SourceRecord;\n // Children take the application camera: they resolve the XR view for\n // themselves, and a streamed source needs it for LOD scheduling.\n record.source.update(camera, this.renderer, { sort: false });\n record.view = record.source.getUnifiedSourceView();\n record.registrationOrder = i;\n updated.push(record);\n }\n // Overflow is deterministic and region-safe: preserve whole sources rather\n // than gathering a prefix of a streamed cut. Higher priority wins; matching\n // priorities retain registration order.\n updated.sort((a, b) => b.priority - a.priority || a.registrationOrder - b.registrationOrder);\n\n this.overflowedSourceCount = 0;\n this.overflowedSplatCount = 0;\n this.bounds.makeEmpty();\n\n const admitted = this.admittedScratch;\n admitted.length = 0;\n let offset = 0;\n for (const record of updated) {\n const view = record.view as UnifiedSourceView;\n if (record.graphRevision !== view.graphRevision) {\n // Build the replacement before disposing the old pipeline: a modifier\n // stack that throws at graph-build time must leave the record holding\n // a live gather (and an unchanged revision) so the next update can\n // retry after the caller fixes the stack.\n const rebuilt = this.createGather(view);\n record.gather.dispose();\n record.gather = rebuilt;\n record.graphRevision = view.graphRevision;\n record.lastGather = null;\n }\n if (!record.visible) {\n record.lastGather = null;\n continue;\n }\n if (view.activeCount === 0) {\n // An empty cut (e.g. a streamed source before its first chunk lands)\n // owns no work slice: skip the zero-splat gather dispatch and keep the\n // sorter bounds limited to sources that contribute splats.\n record.lastGather = null;\n continue;\n }\n if (offset + view.activeCount > this.workBuffer.capacity) {\n this.overflowedSourceCount++;\n this.overflowedSplatCount += view.activeCount;\n record.lastGather = null;\n continue;\n }\n record.offset = offset;\n admitted.push(record);\n this.bounds.union(view.worldBounds);\n offset += view.activeCount;\n }\n\n // Geometry under the draw order changed (layout, active cut, transform,\n // opacity, content revision). Camera-only SH / modifier color refreshes\n // re-gather the *same* world centers and must not force a sort.\n let geometryInvalidated = false;\n for (const record of admitted) {\n const view = record.view as UnifiedSourceView;\n const sliceOffset = record.offset;\n const previous = this.previousLayoutBySource.get(record.source);\n const ownedSameSlice =\n previous !== undefined &&\n previous.offset === sliceOffset &&\n previous.activeCount === view.activeCount;\n const last = record.lastGather;\n const geometryMatches =\n last !== null &&\n ownedSameSlice &&\n last.activeCount === view.activeCount &&\n last.contentRevision === view.contentRevision &&\n last.offset === sliceOffset &&\n last.opacity === record.opacity &&\n last.matrixWorld.equals(view.matrixWorld);\n const reusable =\n geometryMatches &&\n // SH depends on camera position, not orientation. A stationary camera\n // can reuse the resolved colors instead of re-running a full-source\n // gather every frame; camera motion invalidates only SH-bearing slices.\n (view.sh === null || last.localCameraPosition.equals(view.localCameraPosition.value)) &&\n (view.modifiers.length === 0 || record.cacheModifiers) &&\n // Belt-and-braces: a placed pool's splats move without its own\n // `matrixWorld` changing, so a cached gather would go stale silently.\n // `addSource` already rejects these.\n !view.hasSourcePlacement;\n if (!reusable) {\n record.gather.gather(\n this.renderer,\n view.activeCount,\n sliceOffset,\n view.matrixWorld,\n viewCamera.matrixWorldInverse,\n record.opacity,\n );\n if (!geometryMatches) geometryInvalidated = true;\n if (record.lastGather === null) {\n record.lastGather = {\n activeCount: view.activeCount,\n contentRevision: view.contentRevision,\n offset: sliceOffset,\n opacity: record.opacity,\n matrixWorld: view.matrixWorld.clone(),\n localCameraPosition: view.localCameraPosition.value.clone(),\n };\n } else {\n record.lastGather.activeCount = view.activeCount;\n record.lastGather.contentRevision = view.contentRevision;\n record.lastGather.offset = sliceOffset;\n record.lastGather.opacity = record.opacity;\n record.lastGather.matrixWorld.copy(view.matrixWorld);\n record.lastGather.localCameraPosition.copy(view.localCameraPosition.value);\n }\n }\n }\n\n // Commit ownership only after every gather for this preparation has run.\n let layoutChanged =\n admitted.length !== this.previousLayout.length || offset !== this.previousAdmittedTotal;\n this.previousAdmittedTotal = offset;\n if (!layoutChanged) {\n for (let i = 0; i < admitted.length; i++) {\n const record = admitted[i] as SourceRecord;\n const entry = this.previousLayout[i] as LayoutEntry;\n if (\n entry.source !== record.source ||\n entry.offset !== record.offset ||\n entry.activeCount !== (record.view as UnifiedSourceView).activeCount\n ) {\n layoutChanged = true;\n break;\n }\n }\n }\n if (layoutChanged) {\n this.previousLayout = admitted.map((record) => ({\n source: record.source,\n offset: record.offset,\n activeCount: (record.view as UnifiedSourceView).activeCount,\n }));\n this.previousLayoutBySource.clear();\n for (const entry of this.previousLayout) this.previousLayoutBySource.set(entry.source, entry);\n }\n\n // Work-buffer *geometry* moved underneath the current draw order: the next\n // sort must run whatever the camera did. Camera-driven SH/modifier color\n // refreshes re-gather without geometryInvalidated and deliberately skip\n // this. DoF is a live draw uniform and reaches neither branch.\n if (geometryInvalidated || layoutChanged) this.sortScheduler.invalidateContent();\n\n if (offset > 0) {\n const now = performance.now();\n const viewInverse = viewCamera.matrixWorldInverse;\n if (\n forceSort ||\n this.sortScheduler.shouldSubmit(viewInverse, this.lastSortedModelView, offset, now)\n ) {\n if (this.sorter.sort(viewInverse, offset, this.bounds)) {\n this.lastSortedModelView.copy(viewInverse);\n this.sortScheduler.markAccepted(now);\n }\n }\n }\n (this.geometry as THREE.InstancedBufferGeometry).instanceCount = offset;\n // Both are uploaded by the first frame's gather/sort dispatches, and neither\n // is ever read back - see the field comment. The work buffer's own mirrors\n // are released by `WorkBufferGather.gather`.\n if (!this.mirrors.settled) this.mirrors.release(this.renderer);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n for (const record of this.sources) {\n record.gather.dispose();\n record.source.visible = record.originalVisible;\n record.source.setUnifiedPickVisibility(null);\n record.lastGather = null;\n }\n this.sources.length = 0;\n this.previousLayout = [];\n this.previousLayoutBySource.clear();\n this.candidateScratch.length = 0;\n this.admittedScratch.length = 0;\n this.sourceMaxStdDev = null;\n this.sourceAntialias = null;\n this.sourceProjectedFilterProfile = null;\n this.sorter.dispose();\n this.geometry.dispose();\n (this.material as THREE.Material).dispose();\n this.relightPlaceholder.dispose();\n // The work buffer, its source-index list and the draw-order buffer are\n // storage attributes outside the geometry, so nothing above frees their\n // GPU buffers - ~72 B per work slot leaked per scene swap without this.\n releaseRendererAttributes(this.renderer, [\n this.workBuffer.centers,\n this.workBuffer.colors,\n this.workBuffer.covarianceA,\n this.workBuffer.covarianceB,\n this.workBuffer.isotropicMix,\n this.workBuffer.isotropicScreenRadius,\n this.workSourceIndex,\n this.orderAttribute,\n ]);\n }\n\n private assertNotDisposed(operation: string): void {\n if (this.disposed) {\n throw new Error(`UnifiedSplatMesh: ${operation} called after dispose.`);\n }\n }\n\n /** Rebuilds the EWA draw material when the relight map identity changes. */\n private rebuildDrawMaterial(): void {\n const previous = this.material as THREE.Material;\n this.material = createWorkBufferMaterial({\n capacity: this.workBuffer.capacity,\n centers: this.workBuffer.centers,\n colors: this.workBuffer.colors,\n covarianceA: this.workBuffer.covarianceA,\n covarianceB: this.workBuffer.covarianceB,\n isotropicMix: this.workBuffer.isotropicMix,\n isotropicScreenRadius: this.workBuffer.isotropicScreenRadius,\n order: this.orderAttribute,\n focal: this.focal,\n viewport: this.viewport,\n maxStdDev: this.maxStdDev,\n minSplatSizePx: this.minSplatSizePx,\n antialias: this.antialias,\n projectedLowPassVariance: this.projectedLowPassVariance,\n compensateProjectedLowPass: this.compensateProjectedLowPass,\n dofFocusDistance: this.dofFocusDistance,\n dofAperture: this.dofAperture,\n relightMap: this.relightMap,\n relightBlend: this.relightBlend,\n relightBrightness: this.relightBrightness,\n relightBackground: this.relightBackground,\n relightSoftness: this.relightSoftness,\n });\n previous.dispose();\n }\n\n private createGather(view: ReturnType<SplatMesh['getUnifiedSourceView']>): WorkBufferGather {\n const gather = this.buildGather(view);\n // Compile off the critical frame. WebGPU defers compute-pipeline compilation\n // to the first dispatch, and that dispatch happens inside `update` - a\n // measured 2.0s frame on a multi-mesh scene. Warming here is best-effort:\n // if it fails or has not finished by the first real gather, the only cost is\n // the stall this avoids, so nothing waits on it.\n void gather.warmUp(this.renderer).catch(() => undefined);\n return gather;\n }\n\n private buildGather(view: ReturnType<SplatMesh['getUnifiedSourceView']>): WorkBufferGather {\n return new WorkBufferGather({\n capacity: this.workBuffer.capacity,\n sourceCapacity: view.capacity,\n sourceIndex: view.sourceIndex,\n centersTexture: view.centersTexture,\n colorsTexture: view.colorsTexture,\n covarianceATexture: view.covarianceATexture,\n covarianceBTexture: view.covarianceBTexture,\n dataTextureWidth: view.dataTextureWidth,\n sh: view.sh,\n modifiers: view.modifiers,\n channels: view.channels,\n localCameraPosition: view.localCameraPosition,\n srgbOutput: this.srgbOutput,\n // Per source, not a compatibility field: the gather normalizes `.rad`\n // half-alpha here so one shared draw material serves mixed scenes.\n lodAlpha: view.lodAlpha,\n workBuffer: this.workBuffer,\n });\n }\n}\n"],"names":["_WorkBuffer","capacity","__publicField","slots","THREE","StorageMirrorReleaser","renderer","WorkBuffer","WorkBufferGather","options","uniform","sourceCapacity","sourceIndex","centersTexture","colorsTexture","covarianceATexture","covarianceBTexture","dataTextureWidth","sh","modifiers","channels","localCameraPosition","source","storage","output","outputColor","outputCovA","outputCovB","outputIsotropicMix","outputIsotropicScreenRadius","Fn","If","float","instanceIndex","poolIndex","int","texel","ivec2","localCenter","textureLoad","target","inputA","inputB","covarianceBase","mat3","vec3","baseColor","colorAfterSh","vec4","evaluateSplatSh","worldCenter","stack","foldSplatModifierStack","inverse","toCamera","axis","name","channel","center","visible","bool","gatheredCenter","resolvedColor","asNode","colorSpaceToWorking","sourceAlpha","covariance","localCovariance","varianceScale","DEFAULT_ISOTROPIC_VARIANCE_SCALE","applyIsotropicCovarianceOverride","linear","worldCovarianceBase","worldCovariance","c0","c1","c2","activeCount","targetOffset","sourceMatrix","cameraViewMatrixOrOpacity","opacity","cameraViewMatrix","createWorkBufferMaterial","material","centers","order","colors","covarianceA","covarianceB","isotropicMix","isotropicScreenRadius","workColor","varying","quadPosition","positionGeometry","opacityCompensation","adjustedStdDev","workIndex","centerSample","drawable","viewCenter","modelViewMatrix","clipCenter","cameraProjectionMatrix","covA","covB","invZ","invZ2","j1","j2","viewRotationT","u1","u2","aRaw","dRaw","bRaw","depth","focus","halfApertureAngle","focusBlur","apertureRadius","cocRadiusPx","cocVar","MAX_DOF_VARIANCE","a","d","b","isoMix","detBlur","detRaw","detBase","mipFade","dofOnlyFade","compensate","fade","mix","mid","radius","vec2","lambda1","lambda2","equalized","equalizeProjectedEigenvalues","capped","capProjectedEigenvaluesToScreenRadius","remap","stdDev","eigenvector","minSplat","major","minor","pixelOffset","ndcCenter","clipPosition","margin","squaredDistance","Discard","g","aExp","merged","alpha","litCenter","tslTexture","screenUV","ox","oy","s0","s1","s2","s3","s4","wSum","rgbSoft","aSoft","litSoft","lit","factor","rgb","WORK_BUFFER_CENTERS_BYTES_PER_SPLAT","WORK_BUFFER_BYTES_PER_SLOT","assertNonNegativeCapacity","estimateLargestStorageBufferBytes","estimateUnifiedWorkBufferBytes","estimateUnifiedWorkBufferPeakBytes","supportsUnifiedSplatMesh","_a","UnifiedSplatMesh","assertStorageBufferFitsDevice","workBuffer","focal","viewport","maxStdDev","minSplatSizePx","antialias","projectedLowPassVariance","compensateProjectedLowPass","dofFocusDistance","dofAperture","relightPlaceholder","createPlaceholderRelightTexture","relightBlend","relightBrightness","DEFAULT_RELIGHT_BRIGHTNESS","relightBackground","DEFAULT_RELIGHT_BACKGROUND","relightSoftness","DEFAULT_RELIGHT_SOFTNESS","geometry","indices","i","WebGpuSortScheduler","isFillConstrainedSplatDevice","sortInputs","RadixSorter","ComputeSorter","record","view","resolvedVisible","lccProfile","candidate","entry","settings","next","clampDepthOfFieldSettings","clampRelightingSettings","ndc","camera","candidates","hits","hit","best","index","targetSize","previousTarget","forceSort","xrView","resolveXrView","projectionCamera","viewCamera","projection","focalX","focalY","updated","admitted","offset","rebuilt","geometryInvalidated","sliceOffset","previous","ownedSameSlice","last","geometryMatches","layoutChanged","now","viewInverse","releaseRendererAttributes","operation","gather"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ const a = 134217728;
2
+ function d(e) {
3
+ return {
4
+ maxStorageBufferBindingSize: e.limits.maxStorageBufferBindingSize,
5
+ maxBufferSize: e.limits.maxBufferSize,
6
+ // Spec minimum, so an adapter that somehow reports nothing still requests a
7
+ // legal value rather than `undefined` (which requestDevice rejects).
8
+ maxTextureDimension2D: e.limits.maxTextureDimension2D ?? 8192
9
+ };
10
+ }
11
+ function u(e = typeof navigator < "u" ? navigator : void 0) {
12
+ var t;
13
+ if (!e) return !0;
14
+ const i = (t = e.userAgentData) == null ? void 0 : t.platform;
15
+ return typeof i == "string" && i.length > 0 ? i !== "Windows" : /^Win/i.test(e.platform ?? "") ? !1 : !/Windows/i.test(e.userAgent ?? "");
16
+ }
17
+ function m(e = "high-performance", i) {
18
+ return u(i) ? { powerPreference: e } : {};
19
+ }
20
+ function f(e) {
21
+ var r, n;
22
+ const i = e.backend;
23
+ if ((i == null ? void 0 : i.isWebGPUBackend) !== !0) return 0;
24
+ const t = (n = (r = i.device) == null ? void 0 : r.limits) == null ? void 0 : n.maxStorageBufferBindingSize;
25
+ return typeof t == "number" && t > 0 ? t : 0;
26
+ }
27
+ function B(e, i, t) {
28
+ const r = f(e);
29
+ if (r <= 0 || i <= r) return;
30
+ const n = (i / (1024 * 1024)).toFixed(1), o = (r / (1024 * 1024)).toFixed(1), s = (134217728 / (1024 * 1024)).toFixed(0);
31
+ throw new Error(
32
+ `vlam: storage buffer needs ${n} MiB for ${t.toLocaleString("en-US")} splats, but device maxStorageBufferBindingSize is ${o} MiB (WebGPU default is ${s}). Pass recommendedWebGpuRequiredLimits(adapter) to WebGPURenderer, or lower the splat budget.`
33
+ );
34
+ }
35
+ export {
36
+ a as W,
37
+ B as a,
38
+ f as d,
39
+ d as r,
40
+ u as s,
41
+ m as w
42
+ };
43
+ //# sourceMappingURL=webgpu-limits-Y3CI_6_6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgpu-limits-Y3CI_6_6.js","sources":["../src/lib/webgpu-limits.ts"],"sourcesContent":["import type { WebGPURenderer } from 'three/webgpu';\n\n/**\n * WebGPU device limits hosts should request for VLAM beyond the spec defaults.\n *\n * The WebGPU default `maxStorageBufferBindingSize` is 128 MiB. Unified work\n * buffers and large streamed pools allocate storage attributes past that\n * (16 bytes per splat for an RGBA32F centers buffer). Desktop adapters often\n * advertise ~2 GiB, but only when those values are passed as `requiredLimits`\n * to `adapter.requestDevice()` / Three.js `WebGPURenderer`.\n *\n * `maxTextureDimension2D` is here for the same reason: the default is 8192,\n * which caps a pool at 8192 rows of {@link SPLAT_DATA_TEXTURE_WIDTH} (≈16.7 M\n * splats of *capacity*, so ≈11.2 M resident at the usual 1.5 slack). Desktop\n * adapters commonly advertise 16384 and double that ceiling for free.\n */\nexport interface WebGpuRequiredLimits {\n maxStorageBufferBindingSize: number;\n maxBufferSize: number;\n maxTextureDimension2D: number;\n // Assignable to the `Record<string, number>` Three.js WebGPURenderer takes\n // as `requiredLimits`, and open to further WebGPU limit names.\n [limit: string]: number;\n}\n\n/**\n * WebGPU's default `maxStorageBufferBindingSize` when the host does not raise\n * it via `requiredLimits` (128 MiB).\n */\nexport const WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE = 134_217_728;\n\n/**\n * Copies the adapter's advertised buffer maxima for use as\n * `WebGPURenderer({ requiredLimits })`.\n *\n * Does not call `requestDevice` - the host owns device creation.\n */\nexport function recommendedWebGpuRequiredLimits(adapter: {\n limits: {\n maxStorageBufferBindingSize: number;\n maxBufferSize: number;\n maxTextureDimension2D?: number;\n };\n}): WebGpuRequiredLimits {\n return {\n maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,\n maxBufferSize: adapter.limits.maxBufferSize,\n // Spec minimum, so an adapter that somehow reports nothing still requests a\n // legal value rather than `undefined` (which requestDevice rejects).\n maxTextureDimension2D: adapter.limits.maxTextureDimension2D ?? 8192,\n };\n}\n\n/** Mirrors WebGPU `GPUPowerPreference` without requiring `@webgpu/types`. */\nexport type WebGpuPowerPreference = 'high-performance' | 'low-power';\n\ntype NavigatorPlatformProbe = Pick<Navigator, 'platform' | 'userAgent'> & {\n userAgentData?: { platform?: string };\n};\n\n/**\n * Whether the host should pass `powerPreference` to `navigator.gpu.requestAdapter`\n * / Three.js `WebGPURenderer`.\n *\n * Chrome on Windows ignores the option and logs a console warning when it is\n * present ([crbug.com/369219127](https://crbug.com/369219127)). Prefer\n * {@link webGpuPowerPreferenceOptions} so Windows hosts omit it automatically.\n */\nexport function supportsWebGpuPowerPreference(\n nav: NavigatorPlatformProbe | undefined = typeof navigator !== 'undefined'\n ? navigator\n : undefined,\n): boolean {\n if (!nav) return true;\n\n const uaDataPlatform = nav.userAgentData?.platform;\n if (typeof uaDataPlatform === 'string' && uaDataPlatform.length > 0) {\n return uaDataPlatform !== 'Windows';\n }\n\n // `navigator.platform` is \"Win32\" on both 32- and 64-bit Windows browsers.\n if (/^Win/i.test(nav.platform ?? '')) return false;\n return !/Windows/i.test(nav.userAgent ?? '');\n}\n\n/**\n * Options to spread into `requestAdapter(...)` and `WebGPURenderer({...})`.\n * Empty on Windows so Chrome does not warn about an ignored `powerPreference`.\n */\nexport function webGpuPowerPreferenceOptions(\n powerPreference: WebGpuPowerPreference = 'high-performance',\n nav?: NavigatorPlatformProbe,\n): { powerPreference: WebGpuPowerPreference } | Record<string, never> {\n return supportsWebGpuPowerPreference(nav) ? { powerPreference } : {};\n}\n\n/**\n * The renderer's WebGPU `maxStorageBufferBindingSize`, or 0 when it cannot be\n * read (WebGL2 fallback, or device not yet available). Callers skip the check\n * when 0 rather than falsely reject.\n */\nexport function deviceMaxStorageBufferBindingSize(renderer: WebGPURenderer): number {\n const backend = renderer.backend as\n | {\n isWebGPUBackend?: boolean;\n device?: { limits?: { maxStorageBufferBindingSize?: number } };\n }\n | undefined;\n if (backend?.isWebGPUBackend !== true) return 0;\n const limit = backend.device?.limits?.maxStorageBufferBindingSize;\n return typeof limit === 'number' && limit > 0 ? limit : 0;\n}\n\n/**\n * Throws when a planned storage buffer would exceed the device bind limit.\n * No-ops when the limit cannot be read (WebGL2 / uninitialized backend).\n */\nexport function assertStorageBufferFitsDevice(\n renderer: WebGPURenderer,\n byteSize: number,\n capacity: number,\n): void {\n const limit = deviceMaxStorageBufferBindingSize(renderer);\n if (limit <= 0 || byteSize <= limit) return;\n\n const needMiB = (byteSize / (1024 * 1024)).toFixed(1);\n const limitMiB = (limit / (1024 * 1024)).toFixed(1);\n const defaultMiB = (WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE / (1024 * 1024)).toFixed(0);\n throw new Error(\n `vlam: storage buffer needs ${needMiB} MiB for ${capacity.toLocaleString('en-US')} splats, ` +\n `but device maxStorageBufferBindingSize is ${limitMiB} MiB ` +\n `(WebGPU default is ${defaultMiB}). Pass recommendedWebGpuRequiredLimits(adapter) to ` +\n `WebGPURenderer, or lower the splat budget.`,\n );\n}\n"],"names":["WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE","recommendedWebGpuRequiredLimits","adapter","supportsWebGpuPowerPreference","nav","_a","uaDataPlatform","webGpuPowerPreferenceOptions","powerPreference","deviceMaxStorageBufferBindingSize","renderer","_b","backend","limit","assertStorageBufferFitsDevice","byteSize","capacity","needMiB","limitMiB","defaultMiB"],"mappings":"AA6BO,MAAMA,IAAiD;AAQvD,SAASC,EAAgCC,GAMvB;AACvB,SAAO;AAAA,IACL,6BAA6BA,EAAQ,OAAO;AAAA,IAC5C,eAAeA,EAAQ,OAAO;AAAA;AAAA;AAAA,IAG9B,uBAAuBA,EAAQ,OAAO,yBAAyB;AAAA,EAAA;AAEnE;AAiBO,SAASC,EACdC,IAA0C,OAAO,YAAc,MAC3D,YACA,QACK;AA3CJ,MAAAC;AA4CL,MAAI,CAACD,EAAK,QAAO;AAEjB,QAAME,KAAiBD,IAAAD,EAAI,kBAAJ,gBAAAC,EAAmB;AAC1C,SAAI,OAAOC,KAAmB,YAAYA,EAAe,SAAS,IACzDA,MAAmB,YAIxB,QAAQ,KAAKF,EAAI,YAAY,EAAE,IAAU,KACtC,CAAC,WAAW,KAAKA,EAAI,aAAa,EAAE;AAC7C;AAMO,SAASG,EACdC,IAAyC,oBACzCJ,GACoE;AACpE,SAAOD,EAA8BC,CAAG,IAAI,EAAE,iBAAAI,EAAA,IAAoB,CAAA;AACpE;AAOO,SAASC,EAAkCC,GAAkC;AAxE7E,MAAAL,GAAAM;AAyEL,QAAMC,IAAUF,EAAS;AAMzB,OAAIE,KAAA,gBAAAA,EAAS,qBAAoB,GAAM,QAAO;AAC9C,QAAMC,KAAQF,KAAAN,IAAAO,EAAQ,WAAR,gBAAAP,EAAgB,WAAhB,gBAAAM,EAAwB;AACtC,SAAO,OAAOE,KAAU,YAAYA,IAAQ,IAAIA,IAAQ;AAC1D;AAMO,SAASC,EACdJ,GACAK,GACAC,GACM;AACN,QAAMH,IAAQJ,EAAkCC,CAAQ;AACxD,MAAIG,KAAS,KAAKE,KAAYF,EAAO;AAErC,QAAMI,KAAWF,KAAY,OAAO,OAAO,QAAQ,CAAC,GAC9CG,KAAYL,KAAS,OAAO,OAAO,QAAQ,CAAC,GAC5CM,KAAc,aAAkD,OAAO,OAAO,QAAQ,CAAC;AAC7F,QAAM,IAAI;AAAA,IACR,8BAA8BF,CAAO,YAAYD,EAAS,eAAe,OAAO,CAAC,sDAClCE,CAAQ,2BAC/BC,CAAU;AAAA,EAAA;AAGtC;"}
@@ -19,14 +19,6 @@ export interface WebGpuRequiredLimits {
19
19
  maxTextureDimension2D: number;
20
20
  [limit: string]: number;
21
21
  }
22
- /** Bytes per splat in the largest unified work-buffer attribute (RGBA32F centers). */
23
- export declare const WORK_BUFFER_CENTERS_BYTES_PER_SPLAT = 16;
24
- /**
25
- * Bytes per work-buffer slot across *all* of its attributes: centers, colors,
26
- * covarianceA and covarianceB at RGBA32F (16 each), plus the isotropic mix and
27
- * screen-radius scalars (4 each).
28
- */
29
- export declare const WORK_BUFFER_BYTES_PER_SLOT: number;
30
22
  /**
31
23
  * WebGPU's default `maxStorageBufferBindingSize` when the host does not raise
32
24
  * it via `requiredLimits` (128 MiB).
@@ -68,57 +60,6 @@ export declare function supportsWebGpuPowerPreference(nav?: NavigatorPlatformPro
68
60
  export declare function webGpuPowerPreferenceOptions(powerPreference?: WebGpuPowerPreference, nav?: NavigatorPlatformProbe): {
69
61
  powerPreference: WebGpuPowerPreference;
70
62
  } | Record<string, never>;
71
- /**
72
- * Byte size of the largest single storage-buffer bind VLAM allocates for a
73
- * given splat capacity (unified work-buffer centers: `capacity × 16`).
74
- *
75
- * Use with adapter/device limits to decide whether to raise `requiredLimits`
76
- * or lower the budget before constructing {@link UnifiedSplatRenderer}.
77
- */
78
- export declare function estimateLargestStorageBufferBytes(capacity: number): number;
79
- /**
80
- * Estimates what a {@link UnifiedSplatRenderer}'s work buffer costs, in bytes,
81
- * once the scene has rendered a frame.
82
- *
83
- * This used to be double the figure below, and the doubling was not a detail.
84
- * Every `THREE.StorageBufferAttribute` is constructed around a JS typed array,
85
- * and three's WebGPU backend never releases it: the
86
- * `attribute.onUploadCallback()` call that would is missing from the backend's
87
- * buffer upload path. So a work buffer of N slots cost
88
- * N × {@link WORK_BUFFER_BYTES_PER_SLOT} on the GPU *and* the same again on the
89
- * JS heap, permanently, even though nothing ever reads the CPU copy back - the
90
- * gather pass writes these buffers entirely on the GPU.
91
- *
92
- * `WorkBuffer.releaseCpuMirrors` now drops those mirrors after the first
93
- * dispatch that uploads them (see `storage-attribute-mirror`), so steady state
94
- * is the GPU side alone. The *peak* is still double - the array has to exist
95
- * for the upload copy - which is what
96
- * {@link estimateUnifiedWorkBufferPeakBytes} reports.
97
- *
98
- * Either figure is invisible to `estimateSplatPoolBytes`, which prices the
99
- * splat *pool* and knows nothing about the unified renderer's scratch space. A
100
- * host sizing a scene for a memory-constrained device wants both.
101
- *
102
- * @param capacity - Work-buffer capacity in splats.
103
- * @returns Estimated steady-state bytes.
104
- * @throws {RangeError} if `capacity` is not a non-negative finite number.
105
- */
106
- export declare function estimateUnifiedWorkBufferBytes(capacity: number): number;
107
- /**
108
- * Peak bytes a {@link UnifiedSplatRenderer}'s work buffer occupies: the window
109
- * between construction and the first dispatch, while the GPU buffer and the JS
110
- * array three copied it from are both alive.
111
- *
112
- * Ask this one when the question is "will allocating this scene succeed" - an
113
- * out-of-memory failure happens against the peak. Ask
114
- * {@link estimateUnifiedWorkBufferBytes} when the question is "what will this
115
- * scene hold once it is up".
116
- *
117
- * @param capacity - Work-buffer capacity in splats.
118
- * @returns Estimated bytes, GPU plus the not-yet-released CPU mirror.
119
- * @throws {RangeError} if `capacity` is not a non-negative finite number.
120
- */
121
- export declare function estimateUnifiedWorkBufferPeakBytes(capacity: number): number;
122
63
  /**
123
64
  * The renderer's WebGPU `maxStorageBufferBindingSize`, or 0 when it cannot be
124
65
  * read (WebGL2 fallback, or device not yet available). Callers skip the check
@@ -99,7 +99,7 @@ export declare class WorkBufferGather {
99
99
  * Compiles this gather's compute pipeline without gathering anything.
100
100
  *
101
101
  * WebGPU compiles a compute pipeline lazily, at its first dispatch - not when
102
- * the node graph is built. Measured on a 13-marker scene, a source's *first*
102
+ * the node graph is built. Measured on a 13-mesh scene, a source's *first*
103
103
  * `gather` took up to 2.0s while every later dispatch of the same pipeline
104
104
  * took under 0.4ms, and because `gather` runs inside `update` that compile
105
105
  * landed whole in one render frame. Running it through the async path here
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voluma/vlam",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "VLAM! - A lightweight WebGPU Gaussian splat viewer for three.js, built for high performance, streaming LOD, and fully customizable rendering through an open shader pipeline.",
5
5
  "license": "MIT",
6
6
  "author": "Voluma",
@@ -43,6 +43,26 @@
43
43
  "types": "./dist/index.d.ts",
44
44
  "import": "./dist/index.js"
45
45
  },
46
+ "./loaders": {
47
+ "types": "./dist/loaders.d.ts",
48
+ "import": "./dist/loaders.js"
49
+ },
50
+ "./static-lod": {
51
+ "types": "./dist/static-lod-entry.d.ts",
52
+ "import": "./dist/static-lod.js"
53
+ },
54
+ "./streaming": {
55
+ "types": "./dist/streaming.d.ts",
56
+ "import": "./dist/streaming.js"
57
+ },
58
+ "./unified": {
59
+ "types": "./dist/unified.d.ts",
60
+ "import": "./dist/unified.js"
61
+ },
62
+ "./selection": {
63
+ "types": "./dist/selection.d.ts",
64
+ "import": "./dist/selection.js"
65
+ },
46
66
  "./effects": {
47
67
  "types": "./dist/effects.d.ts",
48
68
  "import": "./dist/effects.js"
@@ -79,9 +99,9 @@
79
99
  },
80
100
  "scripts": {
81
101
  "dev": "npm run docs:api:site && vitepress dev site",
82
- "docs:api:site": "typedoc --options typedoc.site.json",
102
+ "docs:api:site": "typedoc --options site/typedoc.site.json",
83
103
  "build:site": "npm run typecheck && node scripts/build-site.mjs",
84
- "build:lib": "vite build --config vite.config.lib.ts && tsc -p tsconfig.lib.json",
104
+ "build:lib": "vite build --config scripts/vite.config.lib.ts && tsc -p tsconfig.lib.json",
85
105
  "build": "npm run build:site && npm run build:lib",
86
106
  "prepack": "npm run build:lib",
87
107
  "typecheck": "tsc --noEmit",
@@ -92,7 +112,7 @@
92
112
  "test:coverage": "vitest run --coverage",
93
113
  "docs:check": "node scripts/docs-check.mjs",
94
114
  "docs:samples": "tsc --noEmit -p docs/guide/samples/tsconfig.json && tsc --noEmit -p docs/examples/samples/tsconfig.json",
95
- "docs:api": "typedoc",
115
+ "docs:api": "typedoc --options site/typedoc.json",
96
116
  "size:check": "node scripts/check-bundle-size.mjs",
97
117
  "deploy": "npm run build:site && wrangler deploy"
98
118
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"splat-mesh-types-CTN_duw2.js","sources":["../src/lib/lod-scheduler.ts","../src/lib/splat-mesh-types.ts"],"sourcesContent":["import * as THREE from 'three/webgpu';\nimport type { LodLeaf, LodManifest } from './lod-manifest';\nimport type { LodSource } from './lod-source';\n\n/**\n * A contiguous splat range within one chunk file, at one LOD level - the\n * unit the pool activates. Adjacent manifest leaves that resolve to the same\n * `(file, level)` and whose ranges abut are coalesced into one run, so the\n * pool sees a handful of large ranges instead of thousands of tiny leaves.\n */\nexport interface LodRun {\n readonly file: number;\n readonly level: number;\n readonly offset: number;\n readonly count: number;\n /** First manifest-leaf index this run covers. */\n readonly leafStart: number;\n /** One past the last covered leaf index. */\n readonly leafEnd: number;\n /**\n * Physical-coverage identity when several manifest leaves form one region.\n * A classic LCC cell may be split into fetch-sized leaves, but its slices\n * must still swap as one visible unit. Undefined retains interval-based\n * grouping for hierarchical sources.\n */\n readonly coverageGroup?: number;\n /**\n * Camera distance (mesh-local) of the nearest leaf in this run, from the\n * last {@link LodScheduler.computeDesiredRuns} pass. Streamed meshes use it\n * to fetch near detail before far coarse coverage. Absent on sources that\n * do not track per-leaf distance.\n */\n readonly distance?: number;\n /**\n * True when any leaf in this run intersects the view frustum (with the\n * scheduler's edge margin). Used to rank classic-path fetches so in-view\n * detail beats behind-camera work; absent when the source does not track it.\n */\n readonly inView?: boolean;\n /**\n * Fetch-only angular distance from the camera's forward axis. Smaller values\n * are nearer the screen centre. It never affects LOD selection or budget.\n */\n readonly screenImportance?: number;\n}\n\n/** Stable identity of a run, for diffing desired vs. resident sets. */\nexport function runKey(run: LodRun): string {\n return `${run.file}:${run.level}:${run.offset}:${run.count}`;\n}\n\n/** Sentinel level meaning \"this leaf is currently dropped (renders nothing)\". */\nconst DROPPED = -1;\n\n/**\n * Maximum splats in one run. Large contiguous regions are split into several\n * runs so no single pool append (and thus no single frame) does an unbounded\n * amount of copy + upload work. Split runs stay contiguous, so they still\n * upload as one coalesced rectangle.\n */\nconst MAX_RUN_SPLATS = 128_000;\n\n/** Hysteresis dead-band around each LOD distance threshold. */\nconst THRESHOLD_MARGIN = 0.1;\n/** Minimum time a leaf must hold a level before changing again, ms. */\nconst DWELL_MS = 500;\n/** Frustum test margin, as a fraction of each leaf's own size. */\nconst FRUSTUM_MARGIN = 0.1;\n/**\n * Out-of-frustum leaves act this many times farther away instead of being\n * hard-coarsened. Rotating the camera then re-ranks leaves smoothly (and\n * dwell applies), rather than mass-flipping edge leaves every frame -\n * PlayCanvas uses the same behind-camera-penalty idea.\n */\nconst FRUSTUM_PENALTY = 3;\n\n/**\n * Chooses a level of detail per spatial leaf from the camera, keeps the\n * total within a splat budget, and coalesces the result into runs.\n *\n * The scheduler is pure with respect to rendering: it takes a camera\n * position and frustum (both in the mesh's local space) and returns the set\n * of runs that should be resident. It owns only the small amount of state\n * needed for temporal stability (each leaf's current level and when it last\n * changed), so it is straightforward to drive from a test harness.\n *\n * LOD distance model (PlayCanvas-compatible): level 0 is used within\n * `lodBaseDistance`; each successive level covers a band `lodMultiplier`×\n * farther out. By default out-of-frustum leaves act {@link FRUSTUM_PENALTY}×\n * farther away. Formats with broad spatial cells can disable that bias so an\n * orbit does not churn already-refined cells at the frustum edges.\n *\n * The distance model only sets the *floor* and the *priority*: when the\n * distance-chosen set leaves budget unused, {@link fillBudget} promotes\n * in-frustum leaves nearest-first until its configured headroom target is\n * spent (90% by default). Formats can cap that optional refinement and avoid\n * a full-finest cut even when an explicitly large budget could hold it. The\n * budget\n * therefore decides how far refinement reaches - a desktop budget yields\n * full detail even with the whole scene in view, a mobile budget refines\n * only near the camera.\n */\nexport class LodScheduler implements LodSource {\n /** World-unit distance inside which the finest level (0) is used. */\n lodBaseDistance: number;\n /** Distance ratio between successive LOD levels. */\n lodMultiplier: number;\n /** Maximum active splats; the desired set is demoted to fit. */\n budget: number;\n\n private readonly leaves: readonly LodLeaf[];\n private readonly coarsest: number;\n /** Per leaf: lowest/highest LOD level that has data. */\n private readonly minLevel: Int32Array;\n private readonly maxLevel: Int32Array;\n /**\n * Per leaf: the distance *ambition* - the finest level the camera distance\n * asks for, with hysteresis/dwell applied. This is the hysteresis *state*:\n * only {@link selectLevels} writes it, so budget demotion can never feed\n * back into the dead-band/dwell logic and oscillate. With `fillPastDistance`\n * false, {@link fillBudget} restores {@link resolved} only up to here.\n */\n private readonly level: Int32Array;\n /**\n * Per leaf: the resolved target - the ambition capped to fit the budget by\n * {@link enforceBudget}, then refilled from leftover headroom by\n * {@link fillBudget} (or DROPPED). This is what {@link coalesceRuns} emits.\n */\n private readonly resolved: Int32Array;\n /** Per leaf: timestamp of the last distance-level change, for dwell. */\n private readonly changedAt: Float64Array;\n\n private readonly distance: Float64Array;\n private readonly inFrustum: Uint8Array;\n /** Leaves that must move through the budget cut together. */\n private readonly budgetGroups: readonly Uint32Array[];\n /** Per-leaf physical coverage group, when the manifest defines one. */\n private readonly coverageGroups: Int32Array;\n /** Whether view-frustum membership affects distance and fill priority. */\n private readonly frustumAware: boolean;\n /** Fraction of the usable budget that optional refinement may consume. */\n private readonly budgetFillFraction: number;\n /** Absolute cap on optional refinement; distance-selected detail may exceed it. */\n private readonly budgetFillCap: number;\n /** Whether a budget that holds every finest leaf forces a full-finest cut. */\n private readonly forceFinestWhenFits: boolean;\n /**\n * Leaves at or inside this camera distance always resolve to their finest\n * available level (no hysteresis climb). Classic LCC uses a multi-band\n * radius so adjacent broad cells do not paint coarse discs at startup.\n */\n private readonly forceFinestWithin: number | undefined;\n /**\n * When false, {@link fillBudget} only restores toward each leaf's\n * distance-selected {@link level} - never finer. Classic LCC sets this so\n * a mid-range cell does not fetch L0 only to demote to L1 once the cut\n * settles.\n */\n private readonly fillPastDistance: boolean;\n\n /**\n * What the last cut actually decided, for the HUD.\n *\n * The resident splat count alone cannot distinguish \"the scheduler asked for\n * this\" from \"the scheduler asked for more and the mesh could not apply it\",\n * and those have opposite fixes. Measured rather than inferred after a\n * zoomed-out `sandwijck` sat at exactly its coarsest total (190,730) with a\n * 600k budget - 68% of the budget unspent, with no way to see which stage\n * declined to spend it.\n */\n readonly stats = {\n /** Leaves the frustum test accepted - `fillBudget`'s candidate set. */\n inFrustum: 0,\n /** Total leaves in the tree. */\n leaves: 0,\n /** Splats implied by the resolved cut, before the mesh applies anything. */\n desired: 0,\n /** Splats `fillBudget` added on top of the distance-chosen levels. */\n filled: 0,\n };\n\n private readonly scratchBox = new THREE.Box3();\n private readonly scratchSize = new THREE.Vector3();\n private readonly scratchCenter = new THREE.Vector3();\n private readonly screenImportance: Float32Array;\n /** Whether the last selection pass received a camera-forward vector. */\n private hasScreenImportance = false;\n /**\n * Persistent leaf-index ordering scratch for {@link enforceBudget} and\n * {@link fillBudget}, sorted in place per call instead of allocating a\n * fresh `[...keys]` array. Comparators break ties on the index itself so\n * the order is identical to the stable `Array#sort` this replaces\n * (`TypedArray#sort` stability is not guaranteed).\n */\n private readonly orderScratch: Uint32Array;\n\n constructor(\n manifest: LodManifest,\n options: {\n budget: number;\n lodBaseDistance: number;\n lodMultiplier: number;\n frustumAware?: boolean;\n budgetFillFraction?: number;\n budgetFillCap?: number;\n forceFinestWhenFits?: boolean;\n forceFinestWithin?: number;\n /**\n * When false, leftover-budget fill never refines past the distance-\n * selected level (see {@link fillPastDistance}). Default true.\n */\n fillPastDistance?: boolean;\n },\n ) {\n this.leaves = manifest.leaves;\n this.coarsest = manifest.lodLevels - 1;\n this.budget = options.budget;\n this.lodBaseDistance = options.lodBaseDistance;\n this.lodMultiplier = options.lodMultiplier;\n this.frustumAware = options.frustumAware !== false;\n this.budgetFillFraction = options.budgetFillFraction ?? 0.9;\n this.budgetFillCap = options.budgetFillCap ?? Number.POSITIVE_INFINITY;\n this.forceFinestWhenFits = options.forceFinestWhenFits !== false;\n this.forceFinestWithin = options.forceFinestWithin;\n this.fillPastDistance = options.fillPastDistance !== false;\n if (this.budgetFillFraction <= 0 || this.budgetFillFraction > 1) {\n throw new RangeError('LodScheduler budgetFillFraction must be in (0, 1].');\n }\n if (!(this.budgetFillCap > 0)) {\n throw new RangeError('LodScheduler budgetFillCap must be positive.');\n }\n if (this.forceFinestWithin !== undefined && !(this.forceFinestWithin >= 0)) {\n throw new RangeError('LodScheduler forceFinestWithin must be >= 0.');\n }\n\n const n = this.leaves.length;\n this.minLevel = new Int32Array(n);\n this.maxLevel = new Int32Array(n);\n this.level = new Int32Array(n).fill(this.coarsest);\n this.resolved = new Int32Array(n).fill(this.coarsest);\n this.changedAt = new Float64Array(n);\n this.distance = new Float64Array(n);\n this.inFrustum = new Uint8Array(n);\n this.screenImportance = new Float32Array(n);\n this.orderScratch = new Uint32Array(n);\n this.coverageGroups = new Int32Array(n).fill(-1);\n\n const groups: number[][] = [];\n const explicitGroups = new Map<number, number>();\n for (let i = 0; i < n; i++) {\n const key = (this.leaves[i] as LodLeaf).budgetGroup;\n if (key === undefined) {\n groups.push([i]);\n continue;\n }\n let groupIndex = explicitGroups.get(key);\n if (groupIndex === undefined) {\n groupIndex = groups.length;\n explicitGroups.set(key, groupIndex);\n groups.push([]);\n }\n (groups[groupIndex] as number[]).push(i);\n this.coverageGroups[i] = key;\n }\n this.budgetGroups = groups.map((members) => Uint32Array.from(members));\n\n for (let i = 0; i < n; i++) {\n const lods = (this.leaves[i] as LodLeaf).lods;\n let min = this.coarsest;\n let max = 0;\n for (let l = 0; l < lods.length; l++) {\n if (lods[l] === undefined) continue;\n min = Math.min(min, l);\n max = Math.max(max, l);\n }\n this.minLevel[i] = min;\n this.maxLevel[i] = max;\n this.level[i] = max; // start coarse\n }\n }\n\n /**\n * Recomputes the desired resident run set for the current camera.\n *\n * @param cameraLocal - Camera position in the mesh's local space.\n * @param frustum - View frustum in the mesh's local space.\n * @param now - Monotonic timestamp (ms) for dwell hysteresis.\n */\n computeDesiredRuns(\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n cameraForward?: THREE.Vector3,\n ): LodRun[] {\n this.selectLevels(cameraLocal, frustum, now, cameraForward);\n // When the budget already holds every leaf's finest level, skip distance /\n // frustum demotion entirely. Otherwise a small rotate marks leaves\n // out-of-frustum (×{@link FRUSTUM_PENALTY}), coarsens them, and the mesh\n // aborts fine fetches / swaps to coarse LCC discs - then has to climb\n // back when they re-enter the view.\n if (this.forceFinestWhenFits && this.budget >= this.finestTotal()) {\n for (let i = 0; i < this.leaves.length; i++) {\n this.resolved[i] = this.minLevel[i] as number;\n }\n this.stats.desired = this.activeTotal();\n this.stats.filled = 0;\n return this.coalesceRuns();\n }\n // Copy the ambition into the resolved target, cap it to the budget, then\n // refill leftover headroom. Budget stages touch only `resolved`, never the\n // ambition/hysteresis state - a stationary camera therefore resolves the\n // same cut every pass instead of oscillating red↔orange.\n this.resolved.set(this.level);\n this.enforceBudget();\n const beforeFill = this.activeTotal();\n this.fillBudget();\n this.stats.desired = this.activeTotal();\n this.stats.filled = this.stats.desired - beforeFill;\n return this.coalesceRuns();\n }\n\n /** Total active splats implied by the current resolved levels. */\n private activeTotal(): number {\n let total = 0;\n for (let i = 0; i < this.leaves.length; i++) {\n const level = this.resolved[i] as number;\n if (level === DROPPED) continue;\n total += (this.leaves[i] as LodLeaf).lods[level]?.count ?? 0;\n }\n return total;\n }\n\n /** Splats if every leaf sat at its finest available level (none dropped). */\n private finestTotal(): number {\n let total = 0;\n for (let i = 0; i < this.leaves.length; i++) {\n const level = this.minLevel[i] as number;\n total += (this.leaves[i] as LodLeaf).lods[level]?.count ?? 0;\n }\n return total;\n }\n\n private selectLevels(\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n cameraForward?: THREE.Vector3,\n ): void {\n const { lodBaseDistance: base, lodMultiplier: m } = this;\n this.hasScreenImportance = cameraForward !== undefined;\n let visibleCount = 0;\n this.stats.leaves = this.leaves.length;\n for (let i = 0; i < this.leaves.length; i++) {\n const leaf = this.leaves[i] as LodLeaf;\n const d = leaf.bounds.distanceToPoint(cameraLocal);\n this.distance[i] = d;\n\n // Widen the box by a fraction of its own size for edge stability.\n this.scratchBox.copy(leaf.bounds);\n leaf.bounds.getSize(this.scratchSize);\n this.scratchBox.expandByScalar(this.scratchSize.length() * FRUSTUM_MARGIN);\n const visible = frustum.intersectsBox(this.scratchBox);\n this.inFrustum[i] = visible ? 1 : 0;\n if (visible) visibleCount++;\n\n // Broad classic-LCC tiles frequently all intersect the frustum, so the\n // boolean above cannot tell the centre of the screen from its edges.\n // This is deliberately fetch metadata only: distance remains the source\n // of truth for the resolved LOD and the budget cut.\n if (cameraForward) {\n leaf.bounds.getCenter(this.scratchCenter).sub(cameraLocal);\n const depth = this.scratchCenter.dot(cameraForward);\n const lateralSquared = Math.max(0, this.scratchCenter.lengthSq() - depth * depth);\n this.screenImportance[i] =\n depth > 0 ? Math.sqrt(lateralSquared) / Math.max(depth, 0.25) : Number.POSITIVE_INFINITY;\n } else {\n this.screenImportance[i] = 0;\n }\n\n const current = this.level[i] as number;\n // Blanket force-finest only when fill may still climb past the distance\n // band (octree / pagetable). Classic LCC never uses this path.\n if (\n this.fillPastDistance &&\n this.forceFinestWithin !== undefined &&\n d <= this.forceFinestWithin\n ) {\n const finest = this.minLevel[i] as number;\n if (current !== finest) {\n this.level[i] = finest;\n this.changedAt[i] = now;\n }\n continue;\n }\n\n const effectiveDistance = !this.frustumAware || visible ? d : d * FRUSTUM_PENALTY;\n\n // Classic LCC: the ambition tracks the pure distance band only. Budget\n // demotions live in `resolved`, never here, so they cannot feed back\n // into hysteresis. A cold cell snaps straight to its band (no dwell\n // climb through intermediate LODs); afterward only a band *crossing*\n // moves the ambition - a stationary camera in the same band holds,\n // even if the budget just demoted its resolved level.\n if (!this.fillPastDistance) {\n const band = this.distanceBandLevel(i, effectiveDistance, base, m);\n const coldStart =\n (this.changedAt[i] as number) === 0 && current === (this.maxLevel[i] as number);\n if (coldStart) {\n if (band !== current) {\n this.level[i] = band;\n this.changedAt[i] = now;\n }\n continue;\n }\n if (band === current) continue;\n const proposed = this.hysteresisLevel(i, effectiveDistance, base, m, current);\n if (proposed === current) continue;\n // Coarsen immediately (moving away should not linger over budget);\n // only refinement waits out the dwell window.\n const coarsening = proposed > current;\n if (coarsening || now - (this.changedAt[i] as number) >= DWELL_MS) {\n this.level[i] = proposed;\n this.changedAt[i] = now;\n }\n continue;\n }\n\n const proposed = this.hysteresisLevel(i, effectiveDistance, base, m, current);\n if (proposed === current) continue;\n const coldSnap =\n (this.changedAt[i] as number) === 0 && current === (this.maxLevel[i] as number);\n if (coldSnap || now - (this.changedAt[i] as number) >= DWELL_MS) {\n this.level[i] = proposed;\n this.changedAt[i] = now;\n }\n }\n this.stats.inFrustum = visibleCount;\n }\n\n /**\n * Pure distance→level band (independent of the leaf's current level), so a\n * classic LCC cut never has to dwell through intermediate rungs.\n */\n private distanceBandLevel(index: number, d: number, base: number, m: number): number {\n const min = this.minLevel[index] as number;\n const max = this.maxLevel[index] as number;\n let level = min;\n while (level < max && d > base * m ** level * (1 + THRESHOLD_MARGIN)) level++;\n return this.resolveAvailable(index, level);\n }\n\n /**\n * Stable level from distance: coarsen only past `threshold·(1 + margin)`,\n * refine only within `threshold·(1 − margin)`. The available-level clamp\n * keeps leaves that lack a level on their nearest coarser one.\n */\n private hysteresisLevel(\n index: number,\n d: number,\n base: number,\n m: number,\n current: number,\n ): number {\n const min = this.minLevel[index] as number;\n const max = this.maxLevel[index] as number;\n let level = Math.min(max, Math.max(min, current));\n // threshold(L) = base·m^L is the boundary between level L and L+1.\n while (level < max && d > base * m ** level * (1 + THRESHOLD_MARGIN)) level++;\n while (level > min && d <= base * m ** (level - 1) * (1 - THRESHOLD_MARGIN)) level--;\n // Resolve to the nearest available level (prefer coarser) if this exact\n // one has no data (leaves need not carry every level).\n return this.resolveAvailable(index, level);\n }\n\n private resolveAvailable(index: number, target: number): number {\n const lods = (this.leaves[index] as LodLeaf).lods;\n for (let l = target; l <= (this.maxLevel[index] as number); l++) if (lods[l]) return l;\n for (let l = target - 1; l >= (this.minLevel[index] as number); l--) if (lods[l]) return l;\n return this.maxLevel[index] as number;\n }\n\n /** Demotes/drops lowest-priority leaves until the total fits the budget. */\n private enforceBudget(): void {\n let total = this.activeTotal();\n if (total <= this.budget) return;\n\n // Demote lowest priority first. When frustum-aware: out-of-frustum before\n // in-frustum, then farthest. Classic LCC sets frustumAware false - broad\n // XY cells often sit mostly behind the camera while the user stands in\n // them (d≈0); frustum demotion would coarsen that cell first and let\n // thinner cells ahead keep fine detail (backwards load). Distance only.\n const order = this.orderScratch.subarray(0, this.budgetGroups.length);\n for (let i = 0; i < order.length; i++) order[i] = i;\n order.sort((a, b) => {\n const ia = (this.budgetGroups[a] as Uint32Array)[0] as number;\n const ib = (this.budgetGroups[b] as Uint32Array)[0] as number;\n if (this.frustumAware) {\n const fa = this.inFrustum[ia] as number;\n const fb = this.inFrustum[ib] as number;\n if (fa !== fb) return fa - fb; // out-of-frustum (0) first\n }\n return (this.distance[ib] as number) - (this.distance[ia] as number) || ia - ib;\n });\n\n // Pass 1: coarsen toward the coarsest available level.\n for (const groupIndex of order) {\n const group = this.budgetGroups[groupIndex] as Uint32Array;\n while (total > this.budget) {\n let reduction = 0;\n let changed = false;\n for (const i of group) {\n const current = this.resolved[i] as number;\n if (current >= (this.maxLevel[i] as number)) continue;\n const lods = (this.leaves[i] as LodLeaf).lods;\n const nextLevel = this.resolveAvailable(i, current + 1);\n if (nextLevel === current) continue;\n reduction += (lods[current]?.count ?? 0) - (lods[nextLevel]?.count ?? 0);\n this.resolved[i] = nextLevel;\n changed = true;\n }\n if (!changed) break;\n total -= reduction;\n }\n if (total <= this.budget) return;\n }\n\n // Pass 2 (last resort): drop groups entirely, lowest priority first.\n for (const groupIndex of order) {\n if (total <= this.budget) return;\n for (const i of this.budgetGroups[groupIndex] as Uint32Array) {\n if ((this.resolved[i] as number) === DROPPED) continue;\n total -= (this.leaves[i] as LodLeaf).lods[this.resolved[i] as number]?.count ?? 0;\n this.resolved[i] = DROPPED;\n }\n }\n }\n\n /**\n * Uses leftover budget to restore detail after {@link enforceBudget}\n * demoted leaves, nearest first, while the total stays under the fill\n * target. By default this may also refine *past* the distance-chosen level\n * when headroom remains (octree / pagetable streams). With\n * {@link fillPastDistance} false (classic LCC), fill never goes finer than\n * each leaf's distance-selected level - so a cell at the L1 band does not\n * fetch L0 only to demote moments later with the camera still.\n */\n private fillBudget(): void {\n const target =\n this.forceFinestWhenFits && this.budget >= this.finestTotal()\n ? this.budget\n : Math.min(this.budget, this.budgetFillCap) * this.budgetFillFraction;\n let total = this.activeTotal();\n if (total >= target) return;\n\n // Candidate in-frustum groups, nearest first (ties keep manifest order),\n // packed into the front of the shared index scratch.\n let candidateCount = 0;\n for (let groupIndex = 0; groupIndex < this.budgetGroups.length; groupIndex++) {\n const first = (this.budgetGroups[groupIndex] as Uint32Array)[0] as number;\n if (\n (!this.frustumAware || this.inFrustum[first] === 1) &&\n (this.resolved[first] as number) !== DROPPED\n ) {\n this.orderScratch[candidateCount++] = groupIndex;\n }\n }\n const order = this.orderScratch.subarray(0, candidateCount);\n order.sort((a, b) => {\n const ia = (this.budgetGroups[a] as Uint32Array)[0] as number;\n const ib = (this.budgetGroups[b] as Uint32Array)[0] as number;\n return (this.distance[ia] as number) - (this.distance[ib] as number) || ia - ib;\n });\n\n // Nearest groups refine first (up to the distance floor when capped).\n for (const groupIndex of order) {\n const group = this.budgetGroups[groupIndex] as Uint32Array;\n while (true) {\n let growth = 0;\n let canPromote = false;\n for (const i of group) {\n const current = this.resolved[i] as number;\n const floor = this.fillPastDistance\n ? (this.minLevel[i] as number)\n : (this.level[i] as number);\n if (current <= floor) continue;\n const leaf = this.leaves[i] as LodLeaf;\n let finer = current - 1;\n while (finer > floor && !leaf.lods[finer]) finer--;\n if (finer < floor || !leaf.lods[finer]) continue;\n growth += (leaf.lods[finer]?.count ?? 0) - (leaf.lods[current]?.count ?? 0);\n canPromote = true;\n }\n if (!canPromote || total + growth > target) break;\n for (const i of group) {\n const current = this.resolved[i] as number;\n const floor = this.fillPastDistance\n ? (this.minLevel[i] as number)\n : (this.level[i] as number);\n if (current <= floor) continue;\n const leaf = this.leaves[i] as LodLeaf;\n let finer = current - 1;\n while (finer > floor && !leaf.lods[finer]) finer--;\n if (finer >= floor && leaf.lods[finer]) this.resolved[i] = finer;\n }\n total += growth;\n }\n }\n }\n\n private coalesceRuns(): LodRun[] {\n return this.buildRuns(0, this.leaves.length, (i) => this.resolved[i] as number);\n }\n\n /**\n * Runs covering [from, to) at each leaf's coarsest available level - used\n * to substitute always-cached coverage while a finer level is fetching.\n */\n coarsestRunsFor(from: number, to: number): LodRun[] {\n return this.buildRuns(from, to, (i) => this.maxLevel[i] as number);\n }\n\n /**\n * Runs covering [from, to) at `level`, clamped per leaf to an available rung\n * (prefer the requested level, else the next coarser, else the next finer).\n */\n runsAtLevelFor(from: number, to: number, level: number): LodRun[] {\n const wanted = Math.floor(level);\n return this.buildRuns(from, to, (i) => this.clampLeafLevel(i, wanted));\n }\n\n /** Prefer `wanted`, else next coarser, else next finer; {@link DROPPED} if none. */\n private clampLeafLevel(leafIndex: number, wanted: number): number {\n const leaf = this.leaves[leafIndex] as LodLeaf;\n if (leaf.lods[wanted]) return wanted;\n for (let l = wanted + 1; l < leaf.lods.length; l++) {\n if (leaf.lods[l]) return l;\n }\n for (let l = wanted - 1; l >= 0; l--) {\n if (leaf.lods[l]) return l;\n }\n return DROPPED;\n }\n\n /** Coalesces leaves [from, to) at `levelOf(leaf)` into contiguous runs. */\n private buildRuns(from: number, to: number, levelOf: (index: number) => number): LodRun[] {\n const runs: LodRun[] = [];\n let file = -1;\n let level = -1;\n let offset = 0;\n let count = 0;\n let leafStart = -1;\n let leafEnd = -1;\n let coverageGroup = -1;\n let distance = Number.POSITIVE_INFINITY;\n let inView = false;\n let screenImportance = Number.POSITIVE_INFINITY;\n const flush = (): void => {\n if (count > 0) {\n runs.push({\n file,\n level,\n offset,\n count,\n leafStart,\n leafEnd,\n distance,\n inView,\n ...(this.hasScreenImportance ? { screenImportance } : {}),\n ...(coverageGroup >= 0 ? { coverageGroup } : {}),\n });\n }\n count = 0;\n };\n\n for (let i = from; i < to; i++) {\n const l = levelOf(i);\n if (l === DROPPED) {\n flush();\n continue;\n }\n const range = (this.leaves[i] as LodLeaf).lods[l];\n if (!range) {\n flush();\n continue;\n }\n const leafCoverageGroup = this.coverageGroups[i] as number;\n const leafDistance = this.distance[i] as number;\n const leafInView = (this.inFrustum[i] as number) === 1;\n if (\n count > 0 &&\n range.file === file &&\n l === level &&\n leafCoverageGroup === coverageGroup &&\n range.offset === offset + count &&\n count + range.count <= MAX_RUN_SPLATS\n ) {\n count += range.count; // extend the current run\n leafEnd = i + 1;\n if (leafDistance < distance) distance = leafDistance;\n if (leafInView) inView = true;\n screenImportance = Math.min(screenImportance, this.screenImportance[i] as number);\n } else {\n flush();\n file = range.file;\n level = l;\n offset = range.offset;\n count = range.count;\n leafStart = i;\n leafEnd = i + 1;\n coverageGroup = leafCoverageGroup;\n distance = leafDistance;\n inView = leafInView;\n screenImportance = this.screenImportance[i] as number;\n }\n }\n flush();\n return runs;\n }\n}\n","/** Public configuration and result types for SplatMesh. */\nimport type * as THREE from 'three/webgpu';\nimport type { SplatData } from './splat-data';\nimport type { SplatOrientation } from './orientation';\nimport type { SplatModifier } from './splat-modifier';\nimport {\n detectSplatDeviceProfile,\n isFillConstrainedSplatDevice,\n type SplatDeviceProfile,\n} from './splat-budget';\nimport type { SplatPool } from './splat-mesh-pool';\nimport type { SplatShInputs, Vec3Uniform } from './splat-mesh-material';\n\n/** Construction-time projected-footprint policy selected by a streamed format. */\nexport type ProjectedFilterProfile = 'default' | 'lcc';\n\n/**\n * Opaque handle for a range of splats appended to a {@link SplatMesh},\n * used to remove the range again.\n */\nexport interface SplatRange {\n /** Number of splats in this range. */\n readonly count: number;\n}\n\n/**\n * Storage format of a per-splat channel (see {@link SplatMesh.defineChannel}).\n *\n * - `'byte'`: one `Uint8` per splat (`r8unorm`). Compact - a good fit for\n * masks and labels. `ctx.channel(name)` reads it back **normalized** to\n * `[0, 1]`, so a painted `255` reads as `1.0`.\n * - `'float'`: one `Float32` per splat (`r32float`), read back verbatim.\n */\nexport type SplatChannelType = 'byte' | 'float';\n\n/** Options for {@link SplatMesh.defineChannel}. */\nexport interface SplatChannelOptions {\n /** Storage format; default `'float'`. */\n type?: SplatChannelType;\n /**\n * Value every splat starts at before any {@link SplatMesh.writeChannel}.\n * Default `0`. For `'byte'` channels this is a raw `0..255` value.\n */\n fill?: number;\n}\n\n/** The highest SH order this renderer evaluates (3rd → 15 coefficients). */\nexport const MAX_SH_BANDS = 3;\n\n/**\n * The contribution-culling profile a mesh will use, given an optional\n * explicit override. Exported so callers that must decide something *before*\n * constructing the mesh - such as whether a streamed scene should fetch its\n * SH at all - agree with what the mesh itself will pick.\n */\nexport function resolveSplatPerformanceProfile(\n explicit?: SplatPerformanceProfile,\n profile: SplatDeviceProfile | undefined = detectSplatDeviceProfile(),\n): SplatPerformanceProfile {\n return explicit ?? (isFillConstrainedSplatDevice(profile) ? 'smooth' : 'quality');\n}\n\n/** Construction options for {@link SplatMesh}. */\nexport interface SplatMeshOptions {\n /**\n * Storage for per-splat higher-order SH in a dynamic-capacity pool, in\n * bands (1, 2 or 3 → 3, 8 or 15 coefficients per channel); 0 (default)\n * allocates nothing.\n *\n * Only formats that store SH per splat can fill this - LCC `Quality`, `.rad`, etc.\n * It costs 16 bytes per splat per band-group of four\n * coefficients (64 B/splat at 3 bands), so it is opt-in. On a static mesh\n * this is ignored: packed SH is taken from `source.shPacked` when present,\n * otherwise palette `source.sh` (SOG).\n */\n shBands?: 0 | 1 | 2 | 3;\n /**\n * Minimum interval between WebGPU sorts while the camera moves. When\n * omitted, the interval adapts to the active splat count. Use `0` to sort\n * every changed frame. WebGL worker sorting is unaffected.\n */\n sortIntervalMs?: number;\n /**\n * WebGPU sorter used for A/B validation. Defaults to the proven counting\n * sorter. `'radix'` keeps the fast 24-bit key path; `'exact'` lazy-loads a\n * stable 32-bit Float32-depth radix path that avoids scene-range\n * quantization. The first frames may skip sorting until the module resolves.\n *\n * @experimental Radix strategies may change in a minor release.\n */\n sortStrategy?: SplatSortStrategy;\n /**\n * Render-quality policy. `smooth` rejects negligible projected contributions.\n *\n * The default is device-aware: `smooth` on mobile (where rejecting splats too\n * small or too faint to see is worth far more than it costs), `quality`\n * everywhere else. Passing a value opts out of the detection.\n */\n performanceProfile?: SplatPerformanceProfile;\n /**\n * How far out, in standard deviations, each Gaussian is drawn before it is\n * cut off. Every splat is an alpha-blended quad sized to this radius, so it\n * sets how much each one costs to blend - the dominant cost in a busy view.\n * Lowering it shrinks every quad and clips the faint outer tail of each\n * Gaussian; the falloff within the remaining radius is unchanged.\n *\n * Defaults to `3`, the reference 3DGS rasterizer's radius. Below ~2 the\n * truncation shows as visible splat edges; much above ~5 the extra fill is not\n * worth it. Mobile coverage gaps are handled by `minSplatSizePx` instead of\n * growing every splat.\n */\n maxStdDev?: number;\n /**\n * Floor, in viewport pixels, on each rendered splat's projected quad radius.\n *\n * A screen-space *minimum* size, the counterpart to `maxScreenRadiusPx`'s\n * maximum. When a splat projects smaller than this - because it is distant, or\n * because the whole scene is zoomed out - its quad is grown to this radius and\n * the Gaussian is stretched to fill it (the falloff normalizes to the quad, so\n * no hard edge appears). Splats already larger are untouched, so it costs no\n * extra fill on the near-camera splats that dominate overdraw.\n *\n * This is the fix for the \"dark gaps when zoomed out\" failure mode: a capture\n * whose finest splats are spaced farther apart than their footprint leaves the\n * background showing between them, and the effect is worst at low resolution -\n * i.e. on a phone. Raising `maxStdDev` also closes the gaps but inflates\n * *every* splat's fragment count by its square, paying the coverage cost on the\n * large splats too; this floor spends it only where a gap can actually open.\n *\n * Defaults to `1.5` px on mobile and `0` (disabled) elsewhere, including\n * fill-constrained desktops. Values around 1–3 px close typical gaps; too\n * large a floor blurs distinct small features into discs, so tune it up from\n * small on the target device. An explicit `0` always disables the floor.\n */\n minSplatSizePx?: number;\n /**\n * Apply the Mip-Splatting 2D antialiasing filter - the screen-space low-pass\n * dilation plus the opacity compensation that conserves each Gaussian's\n * integral, so small/distant splats stop over-brightening. Match the\n * exporter: enable it for scenes trained/exported with antialiasing (the SOG\n * `antialias` meta flag sets this automatically). Defaults to `false` (the\n * classic 3DGS dilation without compensation).\n */\n antialias?: boolean;\n /**\n * Internal format-selected reconstruction profile. Classic LCC uses the\n * XGRIDS-compatible 0.1 px² compensated low-pass; callers should leave this\n * unset and select a format through {@link StreamedSplatMesh.load} instead.\n *\n * @internal\n */\n projectedFilterProfile?: ProjectedFilterProfile;\n /**\n * Emit splat colors in sRGB (display) space instead of decoding them to the\n * renderer's linear working space. Pair with a renderer that skips output\n * conversion (`outputColorSpace = LinearSRGBColorSpace`, `NoToneMapping`,\n * inline sRGB encode for other materials via `renderer.contextNode`): splats\n * then alpha-composite on gamma-encoded values - the math 3DGS training\n * optimizes against, and what WebGL splat viewers render. Defaults to\n * `false` (linear working-space compositing).\n */\n srgbOutput?: boolean;\n /**\n * Cull any splat whose projected on-screen radius exceeds this many pixels,\n * rendering a hole instead. A physically large splat close to the camera -\n * a coarse merged LOD node (a Spark `.rad` \"blob\"), or a giant background\n * Gaussian - projects huge while a fine surface splat stays small, so this\n * removes the near-camera blobs without touching detailed geometry. `0` or\n * unset disables it (the default). Baked into the material graph.\n */\n maxSplatScreenRadius?: number;\n /**\n * Foveation band lower bound (px): cull any splat whose projected on-screen\n * radius is *below* this. Paired with {@link maxSplatScreenRadius}, only\n * splats sized `(min, max]` on screen draw. Because a `.rad` LOD tree's node\n * sizes shrink geometrically, exactly one level per view ray lands in the\n * band - near rays on fine leaves, far rays on coarse nodes - giving a\n * camera-distance foveated cut. `0` or unset disables it (the default).\n * Baked into the material graph. See `docs/formats/rad-notes.md` M14.6.\n */\n minSplatScreenRadius?: number;\n /**\n * How a `.rad` foveated mesh picks its per-splat LOD cut:\n * - `'band'` (default): the screen-radius band above\n * ({@link minSplatScreenRadius}, {@link maxSplatScreenRadius}].\n * - `'frontier'`: Spark's exact tree cut - draw splat `i` iff its parent is\n * too big and it is small enough (`parentPixelScale > limit ≥ ownPixelScale`),\n * using per-splat `own_size`/`parent_size`. Full coverage by construction, no\n * band leapfrogging. Baked into the material graph. See `docs/formats/rad-notes.md`.\n * - `'pagetable'`: the {@link StreamedSplatMesh} default for `.rad` - a worker\n * owns the tree traversal and pages only the *selected* frontier into the\n * pool (Spark's selected-index model), so the whole splat budget buys\n * on-screen detail. Requires the streamed `.rad` machinery; on a plain\n * `SplatMesh` it has no worker to drive it.\n *\n * @experimental `.rad` foveation option; may change in a minor release.\n */\n foveationMode?: 'band' | 'frontier' | 'pagetable';\n /**\n * Target on-screen size (px) for the frontier / page-table cut: it keeps one\n * LOD level per view ray whose projected node size is about this. Larger =\n * coarser/fewer splats, smaller = finer/denser. Default\n * {@link DEFAULT_FOVEATION_TARGET_PX} (1, matching Spark's `lodRenderScale`),\n * so the draw budget rather than the cut size is what bounds detail. Raise it\n * to trade sharpness for fill rate on weak GPUs.\n * Acts as the *finest* bound: the adaptive limit coarsens above it to hold the\n * draw budget but never dips below it.\n *\n * @experimental `.rad` foveation option; may change in a minor release.\n */\n foveationTargetPx?: number;\n /**\n * Target upper bound on the number of splats the frontier cut *draws*\n * (Spark's `maxSplats`). Each reschedule the cut's `pixelScaleLimit`\n * self-adjusts - coarsening when the estimated drawn count exceeds this - so\n * frame cost stays bounded as detail streams in. Default\n * {@link DEFAULT_FOVEATION_DRAW_BUDGET}. Only used in `'frontier'` mode.\n *\n * @experimental `.rad` foveation option; may change in a minor release.\n */\n foveationDrawBudget?: number;\n /**\n * Cap on a rendered splat's major/minor axis ratio (`0`/unset = off). A very\n * anisotropic Gaussian (a flat 3DGS disk edge-on, or an expansion-enlarged\n * coarse LOD node) otherwise projects to a long needle; this bounds its drawn\n * length to `maxSplatAspect`× its width. Baked into the material graph.\n *\n * @experimental `.rad` foveation option; may change in a minor release.\n */\n maxSplatAspect?: number;\n /**\n * Spark's LOD alpha encoding (`.rad`): the stored opacity is `alpha/2` so the\n * shader recovers `alpha ∈ [0,2]`, and `alpha > 1` marks a merged node rendered\n * with a grown σ-cutoff + super-Gaussian falloff. Set for foveated `.rad`.\n *\n * @experimental `.rad` foveation option; may change in a minor release.\n */\n lodAlpha?: boolean;\n /**\n * How the scene is oriented into the three.js Y-up world. `'y-up'` (default)\n * normalizes every known format to Y-up - 3DGS Y-down formats\n * (PLY/`.splat`/`.ksplat`/SOG) are flipped 180° about X; SPZ/`.rad` are\n * already Y-up; LCC keeps its own Z-up→Y-up matrix. `'source'` applies no\n * cosmetic flip and renders in the data frame (raw Spark / mkkellogg parity);\n * LCC still self-orients (that is format semantics, not part of the switch).\n *\n * For a static mesh the flip is chosen from {@link SplatData.sourceFormat}\n * (stamped by the loaders); a dynamic-capacity mesh carries no format, so its\n * host applies {@link yUpTransformForFormat} itself. See {@link SplatOrientation}.\n */\n orientation?: SplatOrientation;\n /**\n * GPU storage type for the pool's continuous float textures (`centers` and\n * `covarianceA`). `'float16'` uploads them as `rgba16float` (~16 B/splat\n * saved vs the default). CPU backing stays float32 (sorter, query, writes).\n *\n * `covarianceB` is always float32: it packs integer IDs (SOG palette labels,\n * RAD frontier parents) that half floats cannot represent exactly above\n * 2048. Colors and packed SH are unchanged. Construction-time only.\n */\n poolFloatTextures?: 'float32' | 'float16';\n /**\n * An existing pool to draw from instead of allocating one.\n *\n * Several meshes sharing a pool share its memory envelope: rows go to\n * whichever mesh needs them, so a mesh the camera is near can hold far more\n * than an even split would give it, and one that is far away holds almost\n * nothing - without every mesh having reserved a private ceiling up front.\n * This is the multi-mesh analogue of a single streamed mesh's LOD budget.\n *\n * The pool is *not* owned by the mesh: {@link SplatMesh.dispose} releases the\n * mesh's rows and leaves the textures alone, so the pool's creator disposes\n * it once every tenant is gone. `capacity` on the source is then only used\n * for the mesh's own draw list, not to size storage.\n *\n * Sharing costs a whole-pool stall when the pool fragments - see\n * {@link SplatMesh.compact}.\n */\n pool?: SplatPool;\n /**\n * The device's `maxTextureDimension2D`, forwarded to a pool this mesh\n * allocates for itself so an over-tall pool fails at construction with a\n * readable error instead of at first draw. Pass `deviceMaxTextureSize(renderer)`.\n *\n * Ignored when {@link SplatMeshOptions.pool} supplies the pool - that pool\n * was already checked when its creator built it.\n */\n maxTextureSize?: number;\n}\n\n/** Available WebGPU depth-sort implementations. */\nexport type SplatSortStrategy = 'counting' | 'radix' | 'exact';\n\n/** Controls optional work during a per-frame source update. */\nexport interface SplatUpdateOptions {\n /** Leave sorting to {@link UnifiedSplatRenderer}; uploads and LOD state still update. */\n sort?: boolean;\n}\n\n/**\n * Read-only GPU-facing view of a mesh's current active pool. It is consumed by\n * the M15.4 unified gather path; streamed meshes expose their current LOD cut\n * through the same view because they inherit {@link SplatMesh}.\n *\n * @experimental May change in a minor release.\n */\nexport interface UnifiedSourceView {\n /** Total addressable pool slots. */\n readonly capacity: number;\n /** Pool indices of active splats, packed from zero. */\n readonly sourceIndex: THREE.StorageBufferAttribute;\n /** Active entries at the front of {@link sourceIndex}. */\n readonly activeCount: number;\n /** Local-space centers, RGBA32F and pool-indexed. */\n readonly centersTexture: THREE.DataTexture;\n /** Source display color and opacity, RGBA8 and pool-indexed. */\n readonly colorsTexture: THREE.DataTexture;\n /** Upper covariance rows, RGBA32F and pool-indexed. */\n readonly covarianceATexture: THREE.DataTexture;\n /** Final covariance row, RGBA32F and pool-indexed. */\n readonly covarianceBTexture: THREE.DataTexture;\n /** Centers texture row width. */\n readonly dataTextureWidth: number;\n /** Current source-local → world transform. */\n readonly matrixWorld: THREE.Matrix4;\n /** Conservative world-space bound for depth quantization. */\n readonly worldBounds: THREE.Sphere;\n /** Higher-order color data resolved by the gather pass when present. */\n readonly sh: SplatShInputs | null;\n /** Effect hooks and their source-local data channels. */\n readonly modifiers: readonly SplatModifier[];\n /**\n * True when this source is itself a unified pool with per-source placement\n * (`SplatScene`). The gather path cannot resolve nested placement, so\n * `UnifiedSplatRenderer` rejects these sources.\n */\n readonly hasSourcePlacement: boolean;\n readonly channels: ReadonlyMap<string, { texture: THREE.DataTexture }>;\n /** Same uniform node the source graph updates each frame. */\n readonly localCameraPosition: Vec3Uniform;\n /** Changes whenever a modifier graph must be rebuilt. */\n readonly graphRevision: number;\n /** Whether this source intentionally composites in display (sRGB) space. */\n readonly srgbOutput: boolean;\n /** Shared draw-path settings that must agree across unified sources. */\n readonly maxStdDev: number;\n /** Screen-space minimum splat radius, px (0 = off). */\n readonly minSplatSizePx: number;\n readonly antialias: boolean;\n /** Construction-time projected-footprint policy shared by one unified pass. */\n readonly projectedFilterProfile: ProjectedFilterProfile;\n /**\n * Whether this source stores Spark LOD alpha (`alpha ÷ 2`, `.rad`). The\n * gather recovers the full `alpha ∈ [0,2]`; the draw material then treats\n * `alpha > 1` as a merged node. Per source, not a compatibility field - a\n * scene may mix `.rad` and non-`.rad` sources.\n */\n readonly lodAlpha: boolean;\n /** Increments whenever pool-backed data or active residency changes. */\n readonly contentRevision: number;\n}\n\n/** Quality-compatible rendering or smoother contribution-culling rendering. */\nexport type SplatPerformanceProfile = 'quality' | 'smooth';\n\n/**\n * Options for {@link SplatMesh.pick}.\n *\n * Picking returns the selected splat's rendered center plane (depth-tested\n * Gaussian coverage), not a persistent splat identifier or a collision mesh.\n */\nexport interface SplatPickOptions {\n /**\n * Minimum Gaussian opacity (after falloff × splat alpha) for a fragment to\n * count as a hit. Default `0.1`.\n */\n alphaThreshold?: number;\n}\n\n/**\n * Result of a successful {@link SplatMesh.pick}.\n *\n * The point lies on the frontmost splat's billboard plane at the picked\n * pixel - suitable for click-to-focus and placement anchors, not physics.\n */\nexport interface SplatPickResult {\n /** Hit position in world space. */\n readonly point: THREE.Vector3;\n /** Distance from the camera position to {@link point}. */\n readonly distance: number;\n}\n\n/**\n * Result of {@link SplatMesh.queryNearest}: the resident splat center closest\n * to the query point, in world space.\n */\nexport interface SplatNearestResult {\n /** The splat's center, in world space. */\n readonly point: THREE.Vector3;\n /** World-space distance from the query point to {@link point}. */\n readonly distance: number;\n}\n\n/** Result of a successful synchronous {@link SplatMesh.queryRay}. */\nexport interface SplatRayResult {\n /** Resident splat center in world space. */\n readonly point: THREE.Vector3;\n /** Distance along the ray from its origin to the center's closest plane. */\n readonly distance: number;\n}\n\n/**\n * Result of {@link SplatMesh.queryHeight}: the supporting surface found beneath\n * the query point (the highest resident splat within the drop and horizontal\n * radius), in world space.\n */\nexport interface SplatHeightResult {\n /** The supporting splat's center, in world space. */\n readonly point: THREE.Vector3;\n /** How far below the query point the surface sits (world units, ≥ 0). */\n readonly drop: number;\n}\n"],"names":["runKey","run","DROPPED","MAX_RUN_SPLATS","THRESHOLD_MARGIN","DWELL_MS","FRUSTUM_MARGIN","FRUSTUM_PENALTY","LodScheduler","manifest","options","__publicField","THREE","n","groups","explicitGroups","i","key","groupIndex","members","lods","min","max","l","cameraLocal","frustum","now","cameraForward","beforeFill","total","level","_a","base","m","visibleCount","leaf","d","visible","depth","lateralSquared","current","finest","effectiveDistance","band","proposed","index","target","order","a","b","ia","ib","fa","fb","group","reduction","changed","nextLevel","_b","_c","candidateCount","first","growth","canPromote","floor","finer","from","to","wanted","leafIndex","levelOf","runs","file","offset","count","leafStart","leafEnd","coverageGroup","distance","inView","screenImportance","flush","range","leafCoverageGroup","leafDistance","leafInView","MAX_SH_BANDS","resolveSplatPerformanceProfile","explicit","profile","detectSplatDeviceProfile","isFillConstrainedSplatDevice"],"mappings":"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"}