@voluma/vlam 0.3.4 → 0.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"splat-mesh-types-BZIko-_9.js","sources":["../src/lib/core/splat-mesh-types.ts"],"sourcesContent":["/** 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/** Canonical `.rad` foveation modes. */\nexport type SplatFoveationMode = 'band' | 'frontier' | 'page-table';\n\n/** Resolve a caller-supplied foveation mode, defaulting when unset. */\nexport function resolveSplatFoveationMode(\n mode: SplatFoveationMode | undefined,\n fallback: SplatFoveationMode = 'band',\n): SplatFoveationMode {\n return mode ?? fallback;\n}\n\n/** True when the mode is the `.rad` page-table pager. */\nexport function isPageTableFoveation(mode: string | undefined): boolean {\n return mode === 'page-table';\n}\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 * - `'page-table'`: 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?: SplatFoveationMode;\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]` from the original texture channel, and\n * `alpha > 1` marks a merged node rendered with a grown σ-cutoff +\n * super-Gaussian falloff. Visual fades and alpha modifiers scale the completed\n * fragment after that classification; they must not change node type or shape.\n * 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 fully loaded mesh the flip is chosen from {@link SplatData.format}\n * (stamped by the loaders); a dynamic-capacity mesh carries no format, so the\n * caller 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 UnifiedSplatMesh}; 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 * (`MergedSplatMesh`). The gather path cannot resolve nested placement, so\n * `UnifiedSplatMesh` 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":["resolveSplatFoveationMode","mode","fallback","isPageTableFoveation","MAX_SH_BANDS","resolveSplatPerformanceProfile","explicit","profile","detectSplatDeviceProfile","isFillConstrainedSplatDevice"],"mappings":";AAoBO,SAASA,EACdC,GACAC,IAA+B,QACX;AACpB,SAAOD,KAAQC;AACjB;AAGO,SAASC,EAAqBF,GAAmC;AACtE,SAAOA,MAAS;AAClB;AAiCO,MAAMG,IAAe;AAQrB,SAASC,EACdC,GACAC,IAA0CC,KACjB;AACzB,SAAOF,MAAaG,EAA6BF,CAAO,IAAI,WAAW;AACzE;"}
1
+ {"version":3,"file":"splat-mesh-types-BZIko-_9.js","sources":["../src/lib/core/splat-mesh-types.ts"],"sourcesContent":["/** 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/** Canonical `.rad` foveation modes. */\nexport type SplatFoveationMode = 'band' | 'frontier' | 'page-table';\n\n/** Resolve a caller-supplied foveation mode, defaulting when unset. */\nexport function resolveSplatFoveationMode(\n mode: SplatFoveationMode | undefined,\n fallback: SplatFoveationMode = 'band',\n): SplatFoveationMode {\n return mode ?? fallback;\n}\n\n/** True when the mode is the `.rad` page-table pager. */\nexport function isPageTableFoveation(mode: string | undefined): boolean {\n return mode === 'page-table';\n}\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; a worker selected\n * explicitly on WebGPU still observes this submission cadence.\n */\n sortIntervalMs?: number;\n /**\n * WebGPU sorter used for A/B validation. Defaults to the proven counting\n * sorter. `'worker'` keeps rendering on WebGPU but sorts asynchronously in\n * the stable CPU worker also used by the WebGL fallback; this is useful for\n * Spark-like temporal stability on captures with extreme position outliers.\n * `'radix'` keeps the fast 24-bit GPU key path; `'exact'` lazy-loads a 32-bit\n * Float32-depth GPU radix path. The first frames may skip GPU radix sorting\n * until the module resolves.\n *\n * @experimental Radix strategies may change in a minor release.\n */\n sortStrategy?: SplatSortStrategy;\n /**\n * Geometric key used to order transparent splats back-to-front.\n *\n * `'depth'` (default) sorts along the camera's view axis and gives the most\n * accurate alpha order for a fixed view. `'radial'` sorts by distance from\n * the camera, matching Spark's stable default: camera rotation alone leaves\n * the order unchanged, which greatly reduces whole-scene shimmer while\n * orbiting dense captures. Pair it with `sortStrategy: 'exact'` when extreme\n * position outliers would make quantized buckets too coarse.\n */\n sortMetric?: SplatSortMetric;\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 * - `'page-table'`: 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?: SplatFoveationMode;\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]` from the original texture channel, and\n * `alpha > 1` marks a merged node rendered with a grown σ-cutoff +\n * super-Gaussian falloff. Visual fades and alpha modifiers scale the completed\n * fragment after that classification; they must not change node type or shape.\n * 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 fully loaded mesh the flip is chosen from {@link SplatData.format}\n * (stamped by the loaders); a dynamic-capacity mesh carries no format, so the\n * caller 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' | 'worker' | 'radix' | 'exact';\n\n/** Camera-space key used for back-to-front splat ordering. */\nexport type SplatSortMetric = 'depth' | 'radial';\n\n/** Controls optional work during a per-frame source update. */\nexport interface SplatUpdateOptions {\n /** Leave sorting to {@link UnifiedSplatMesh}; 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 * (`MergedSplatMesh`). The gather path cannot resolve nested placement, so\n * `UnifiedSplatMesh` 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":["resolveSplatFoveationMode","mode","fallback","isPageTableFoveation","MAX_SH_BANDS","resolveSplatPerformanceProfile","explicit","profile","detectSplatDeviceProfile","isFillConstrainedSplatDevice"],"mappings":";AAoBO,SAASA,EACdC,GACAC,IAA+B,QACX;AACpB,SAAOD,KAAQC;AACjB;AAGO,SAASC,EAAqBF,GAAmC;AACtE,SAAOA,MAAS;AAClB;AAiCO,MAAMG,IAAe;AAQrB,SAASC,EACdC,GACAC,IAA0CC,KACjB;AACzB,SAAOF,MAAaG,EAA6BF,CAAO,IAAI,WAAW;AACzE;"}
@@ -3,7 +3,7 @@ var V = (s, r, e) => r in s ? S(s, r, { enumerable: !0, configurable: !0, writab
3
3
  var i = (s, r, e) => V(s, typeof r != "symbol" ? r + "" : r, e);
4
4
  import * as g from "three/webgpu";
5
5
  import { loadSplatData as L } from "./loaders.js";
6
- import { S as P } from "./splat-mesh-CsLOQb08.js";
6
+ import { S as P } from "./splat-mesh-B1KlWHvx.js";
7
7
  const b = 4096, A = 250, x = (s) => {
8
8
  const r = [
9
9
  s.positions.buffer,
package/dist/streaming.js CHANGED
@@ -3,10 +3,10 @@ var Me = (l, i, e) => i in l ? Fe(l, i, { enumerable: !0, configurable: !0, writ
3
3
  var d = (l, i, e) => Me(l, typeof i != "symbol" ? i + "" : i, e);
4
4
  import { h as Be, f as W, a as Pe, l as ke } from "./splat-budget-DuVZspPQ.js";
5
5
  import * as w from "three/webgpu";
6
- import { S as Ee, a as ie, y as xe } from "./splat-mesh-CsLOQb08.js";
6
+ import { S as Ee, a as ie, y as xe } from "./splat-mesh-B1KlWHvx.js";
7
7
  import { L as We, r as C } from "./lod-scheduler-ChoZyEeq.js";
8
8
  import { S as q, t as ee, r as Ae, i as D, a as _ } from "./loading-wLo8vRbA.js";
9
- import { y as Le } from "./relighting-Tiwep8yd.js";
9
+ import { z as Le } from "./relighting-qaANKC4Z.js";
10
10
  import { C as Ve } from "./chunk-loader-DlNwggfG.js";
11
11
  import { s as Te } from "./sh-pack-D5wAe5gg.js";
12
12
  import { w as E } from "./logging-BfPdd7NJ.js";
@@ -21,7 +21,7 @@ function _e(l) {
21
21
  addCount: i.count
22
22
  })).sort((i, e) => i.leafStart - e.leafStart);
23
23
  }
24
- function Oe(l, i) {
24
+ function ze(l, i) {
25
25
  const e = [...l, ...i.map(([, n]) => n.run)];
26
26
  if (e.length > 0 && e.every((n) => n.coverageGroup !== void 0)) {
27
27
  const n = /* @__PURE__ */ new Map(), a = (r) => {
@@ -84,7 +84,7 @@ function ae(l) {
84
84
  const i = -Math.max(...l.adds.map((e) => e.level));
85
85
  return l.removes.length === 0 ? -1e3 + i : i;
86
86
  }
87
- function ze(l) {
87
+ function Oe(l) {
88
88
  return l.length > 0 && l.every(
89
89
  (i) => [...i.adds, ...i.removes.map(([, e]) => e.run)].every(
90
90
  (e) => e.coverageGroup !== void 0
@@ -1526,10 +1526,10 @@ class Y extends Ee {
1526
1526
  for (const [p, S] of this.staged) n.staged.set(p, S.uploadedCount);
1527
1527
  }
1528
1528
  const a = this.compactionCount;
1529
- if (this.pendingWork = !1, this.lastScheduleTime = t, e.getWorldPosition(this.lastCameraPos), e.getWorldQuaternion(this.lastCameraQuat), x.copy(this.lastCameraPos), this.worldToLocal(x), ve.multiplyMatrices(e.projectionMatrix, e.matrixWorldInverse).multiply(this.matrixWorld), O.setFromProjectionMatrix(ve), this.frontierWorker) {
1529
+ if (this.pendingWork = !1, this.lastScheduleTime = t, e.getWorldPosition(this.lastCameraPos), e.getWorldQuaternion(this.lastCameraQuat), x.copy(this.lastCameraPos), this.worldToLocal(x), ve.multiplyMatrices(e.projectionMatrix, e.matrixWorldInverse).multiply(this.matrixWorld), z.setFromProjectionMatrix(ve), this.frontierWorker) {
1530
1530
  e.getWorldDirection(M).add(this.lastCameraPos), this.worldToLocal(M), M.sub(x).normalize();
1531
1531
  const p = e.projectionMatrix.elements[5] * this.pageTableViewportY / 2;
1532
- if (p > 0 && (this.pageTableLimit = this.pageTableTargetPx / p), this.reschedulePageTable(x, M, O, t), this.onPerformanceEvent === void 0) return null;
1532
+ if (p > 0 && (this.pageTableLimit = this.pageTableTargetPx / p), this.reschedulePageTable(x, M, z, t), this.onPerformanceEvent === void 0) return null;
1533
1533
  const S = performance.now();
1534
1534
  return {
1535
1535
  timestamp: S,
@@ -1551,14 +1551,14 @@ class Y extends Ee {
1551
1551
  e.getWorldDirection(M).add(this.lastCameraPos), this.worldToLocal(M), M.sub(x).normalize();
1552
1552
  const o = this.scene.source.computeDesiredRuns(
1553
1553
  x,
1554
- O,
1554
+ z,
1555
1555
  t,
1556
1556
  M
1557
1557
  ), r = this.captureOrContinueInitialReveal(
1558
1558
  o,
1559
1559
  t,
1560
1560
  x,
1561
- O,
1561
+ z,
1562
1562
  M
1563
1563
  ), c = r !== null, u = r ?? o, f = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Set();
1564
1564
  for (const p of u)
@@ -1582,7 +1582,7 @@ class Y extends Ee {
1582
1582
  // irrelevant - commit each frozen slice as it lands so a partial home
1583
1583
  // cell cannot block reveal behind sibling subchunks still fetching.
1584
1584
  _e(g)
1585
- ) : Oe(g, v), b = !c && ze(m);
1585
+ ) : ze(g, v), b = !c && Oe(m);
1586
1586
  m.sort(
1587
1587
  (p, S) => b ? Ue(p, S) : ae(p) - ae(S)
1588
1588
  );
@@ -2353,7 +2353,7 @@ class Y extends Ee {
2353
2353
  }
2354
2354
  }
2355
2355
  }
2356
- const V = new w.Vector3(), pe = new w.Vector3(), me = new w.Quaternion(), x = new w.Vector3(), ve = new w.Matrix4(), O = new w.Frustum(), wt = new w.Sphere(), M = new w.Vector3(), Se = new w.Vector2();
2356
+ const V = new w.Vector3(), pe = new w.Vector3(), me = new w.Quaternion(), x = new w.Vector3(), ve = new w.Matrix4(), z = new w.Frustum(), wt = new w.Sphere(), M = new w.Vector3(), Se = new w.Vector2();
2357
2357
  function be(l) {
2358
2358
  return Math.ceil(3 * Te(l) / 4);
2359
2359
  }
@@ -2522,8 +2522,8 @@ function J(l) {
2522
2522
  );
2523
2523
  return l;
2524
2524
  }
2525
- const we = new w.Vector3(), ye = new w.Matrix4(), Ce = new w.Frustum(), Z = new w.Box3(), z = new w.Sphere();
2526
- class zt {
2525
+ const we = new w.Vector3(), ye = new w.Matrix4(), Ce = new w.Frustum(), Z = new w.Box3(), O = new w.Sphere();
2526
+ class Ot {
2527
2527
  constructor(i = {}) {
2528
2528
  d(this, "entries", /* @__PURE__ */ new Map());
2529
2529
  d(this, "budgetGovernor");
@@ -2658,12 +2658,12 @@ class zt {
2658
2658
  const { member: e, priority: t } = i;
2659
2659
  if (t === 0 || !(e.effectiveVisibility ?? e.visible)) return 0;
2660
2660
  if (e.updateWorldMatrix(!0, !1), Z.copy(e.computeSplatBounds()), Z.isEmpty()) return t * this.minWeight;
2661
- Z.applyMatrix4(e.matrixWorld).getBoundingSphere(z);
2662
- const s = z.radius;
2661
+ Z.applyMatrix4(e.matrixWorld).getBoundingSphere(O);
2662
+ const s = O.radius;
2663
2663
  if (!(s > 0)) return t * this.minWeight;
2664
- const n = z.center.distanceTo(we) - s, a = Math.max(n, s * 1e-3, 1e-6), o = Tt((s / a) ** this.falloff, this.minWeight, this.maxWeight);
2664
+ const n = O.center.distanceTo(we) - s, a = Math.max(n, s * 1e-3, 1e-6), o = Tt((s / a) ** this.falloff, this.minWeight, this.maxWeight);
2665
2665
  if (!Number.isFinite(o)) return t * this.minWeight;
2666
- const r = Ce.intersectsSphere(z) ? 1 : this.offScreenWeight;
2666
+ const r = Ce.intersectsSphere(O) ? 1 : this.offScreenWeight;
2667
2667
  return t * o * r;
2668
2668
  }
2669
2669
  /** Whether a weight moved enough to be worth a reallocation. */
@@ -2930,7 +2930,7 @@ function kt(l) {
2930
2930
  }
2931
2931
  export {
2932
2932
  It as BudgetGovernor,
2933
- zt as CameraBudgetGovernor,
2933
+ Ot as CameraBudgetGovernor,
2934
2934
  Ht as ChunkCacheBudget,
2935
2935
  Ut as ChunkFetchScheduler,
2936
2936
  q as SplatLoadError,
package/dist/unified.js CHANGED
@@ -1,10 +1,10 @@
1
1
  var je = Object.defineProperty;
2
- var We = (t, n, e) => n in t ? je(t, n, { enumerable: !0, configurable: !0, writable: !0, value: e }) : t[n] = e;
3
- var s = (t, n, e) => We(t, typeof n != "symbol" ? n + "" : n, e);
2
+ var ze = (t, n, e) => n in t ? je(t, n, { enumerable: !0, configurable: !0, writable: !0, value: e }) : t[n] = e;
3
+ var s = (t, n, e) => ze(t, typeof n != "symbol" ? n + "" : n, e);
4
4
  import * as l from "three/webgpu";
5
- import { uniform as x, storage as k, Fn as ye, If as ze, float as A, instanceIndex as ue, int as Se, ivec2 as Ie, textureLoad as ee, mat3 as Re, vec3 as R, vec4 as C, bool as Ne, colorSpaceToWorking as _e, varying as te, positionGeometry as we, modelViewMatrix as Ae, cameraProjectionMatrix as qe, mix as ce, vec2 as I, Discard as He, texture as re, screenUV as se } from "three/tsl";
6
- import { S as De, o as Ke, p as Xe, q as le, s as Ye, t as Ze, d as $e, u as Qe, w as Je, m as et, b as tt, D as rt, c as st, W as it, C as ot, i as at, j as nt, y as ct, r as lt } from "./relighting-Tiwep8yd.js";
7
- import { RadixSorter as ut } from "./radix-sorter-BrbUg_CV.js";
5
+ import { uniform as x, storage as k, Fn as ye, If as We, float as A, instanceIndex as ue, int as Se, ivec2 as Ie, textureLoad as ee, mat3 as Re, vec3 as R, vec4 as C, bool as Ne, colorSpaceToWorking as _e, varying as te, positionGeometry as we, modelViewMatrix as Ae, cameraProjectionMatrix as qe, mix as ce, vec2 as I, Discard as He, texture as re, screenUV as se } from "three/tsl";
6
+ import { S as De, p as Ke, q as Xe, s as le, t as Ye, u as Ze, e as $e, w as Qe, y as Je, n as et, c as tt, D as rt, d as st, W as it, C as ot, j as at, k as nt, z as ct, r as lt } from "./relighting-qaANKC4Z.js";
7
+ import { RadixSorter as ut } from "./radix-sorter-tSffmerk.js";
8
8
  import { i as dt } from "./splat-budget-DuVZspPQ.js";
9
9
  import { a as ht } from "./webgpu-limits-Y3CI_6_6.js";
10
10
  const he = class he {
@@ -92,7 +92,7 @@ class ft {
92
92
  this.workBuffer = n.workBuffer ?? new de(e), this.capacity = this.workBuffer.capacity, this.centers = this.workBuffer.centers, this.colors = this.workBuffer.colors, this.covarianceA = this.workBuffer.covarianceA, this.covarianceB = this.workBuffer.covarianceB, this.isotropicMix = this.workBuffer.isotropicMix, this.isotropicScreenRadius = this.workBuffer.isotropicScreenRadius;
93
93
  const p = k(a, "uint", i), M = k(this.centers, "vec4", e), P = k(this.colors, "vec4", e), r = k(this.covarianceA, "vec4", e), o = k(this.covarianceB, "vec4", e), d = k(this.isotropicMix, "float", e), w = k(this.isotropicScreenRadius, "float", e);
94
94
  this.pass = ye(() => {
95
- ze(A(ue).lessThan(this.activeCount), () => {
95
+ We(A(ue).lessThan(this.activeCount), () => {
96
96
  const b = Se(p.element(ue)), m = Ie(
97
97
  b.mod(Se(f)),
98
98
  b.div(Se(f))
@@ -110,12 +110,12 @@ class ft {
110
110
  )
111
111
  ).clamp(0, 1),
112
112
  L.a
113
- ), W = this.sourceMatrix.mul(C(g, 1)).xyz, h = Xe(V, y, {
113
+ ), z = this.sourceMatrix.mul(C(g, 1)).xyz, h = Xe(V, y, {
114
114
  index: b,
115
115
  localCenter: g,
116
116
  color: O,
117
- makeWorldCenter: () => W,
118
- makeViewCenter: () => this.cameraViewMatrix.mul(C(W, 1)).xyz,
117
+ makeWorldCenter: () => z,
118
+ makeViewCenter: () => this.cameraViewMatrix.mul(C(z, 1)).xyz,
119
119
  makeNormal: () => {
120
120
  const U = E.inverse(), X = y.sub(g), ne = U.mul(U.mul(X)).normalize();
121
121
  return ne.mul(ne.dot(X).sign());
@@ -135,17 +135,17 @@ class ft {
135
135
  } else
136
136
  M.element(v).assign(C(_, ie.mul(this.opacity))), P.element(v).assign(C(Y.rgb, Y.a));
137
137
  const oe = h.rotation === null ? E : h.rotation.mul(E).mul(h.rotation.transpose());
138
- let z = le(oe);
138
+ let W = le(oe);
139
139
  if (h.isotropicCovarianceMix !== null) {
140
140
  const U = h.isotropicVarianceScale ?? A(Ye);
141
- z = Ze(
142
- z,
141
+ W = Ze(
142
+ W,
143
143
  R(0, 0, 1),
144
144
  h.isotropicCovarianceMix,
145
145
  U
146
146
  );
147
147
  }
148
- const Z = this.sourceMatrix.toMat3(), H = Z.mul(z).mul(Z.transpose()), K = h.scaleSquared === null ? H : H.mul(h.scaleSquared), $ = K.mul(R(1, 0, 0)), ae = K.mul(R(0, 1, 0)), pe = K.mul(R(0, 0, 1));
148
+ const Z = this.sourceMatrix.toMat3(), H = Z.mul(W).mul(Z.transpose()), K = h.scaleSquared === null ? H : H.mul(h.scaleSquared), $ = K.mul(R(1, 0, 0)), ae = K.mul(R(0, 1, 0)), pe = K.mul(R(0, 0, 1));
149
149
  r.element(v).assign(C($.x, $.y, $.z, ae.y)), o.element(v).assign(C(ae.z, pe.z, 0, 0)), d.element(v).assign(h.isotropicCovarianceMix === null ? A(0) : h.isotropicCovarianceMix), w.element(v).assign(
150
150
  h.isotropicCovarianceMix === null ? A(0) : h.isotropicScreenRadiusPx ?? A(0)
151
151
  );
@@ -206,9 +206,9 @@ function ke(t) {
206
206
  0,
207
207
  t.focal.y.mul(g),
208
208
  t.focal.y.negate().mul(o.y).mul(v)
209
- ), E = Ae.toMat3().transpose(), L = E.mul(F), O = E.mul(G), W = L.dot(m.mul(L)), h = O.dot(m.mul(O)), j = L.dot(m.mul(O)), N = o.z.negate().max(1e-4), _ = t.dofFocusDistance.max(1e-4), q = t.dofAperture.max(0).mul(0.5).div(_).atan(), fe = N.sub(_).abs().div(N), ie = t.focal.x.mul(q.tan()), Y = fe.mul(ie), oe = Y.mul(Y).min(A($e)), z = W.add(t.projectedLowPassVariance).add(oe), Z = h.add(t.projectedLowPassVariance).add(oe), H = j, K = B.element(p), $ = z.mul(Z).sub(H.mul(H)).max(1e-9), ae = W.mul(h).sub(j.mul(j)).max(0), pe = W.add(t.projectedLowPassVariance).mul(h.add(t.projectedLowPassVariance)).sub(j.mul(j)).max(1e-9), U = ae.div($).sqrt(), X = pe.div($).sqrt(), ne = t.antialias.max(t.compensateProjectedLowPass), Fe = ce(X, U, ne);
209
+ ), E = Ae.toMat3().transpose(), L = E.mul(F), O = E.mul(G), z = L.dot(m.mul(L)), h = O.dot(m.mul(O)), j = L.dot(m.mul(O)), N = o.z.negate().max(1e-4), _ = t.dofFocusDistance.max(1e-4), q = t.dofAperture.max(0).mul(0.5).div(_).atan(), fe = N.sub(_).abs().div(N), ie = t.focal.x.mul(q.tan()), Y = fe.mul(ie), oe = Y.mul(Y).min(A($e)), W = z.add(t.projectedLowPassVariance).add(oe), Z = h.add(t.projectedLowPassVariance).add(oe), H = j, K = B.element(p), $ = W.mul(Z).sub(H.mul(H)).max(1e-9), ae = z.mul(h).sub(j.mul(j)).max(0), pe = z.add(t.projectedLowPassVariance).mul(h.add(t.projectedLowPassVariance)).sub(j.mul(j)).max(1e-9), U = ae.div($).sqrt(), X = pe.div($).sqrt(), ne = t.antialias.max(t.compensateProjectedLowPass), Fe = ce(X, U, ne);
210
210
  V.assign(ce(Fe, A(1), K));
211
- const be = z.add(Z).mul(0.5), Be = I(z.sub(Z).mul(0.5), H).length();
211
+ const be = W.add(Z).mul(0.5), Be = I(W.sub(Z).mul(0.5), H).length();
212
212
  let Q = be.add(Be), J = be.sub(Be).max(0);
213
213
  const Me = Qe(Q, J, K);
214
214
  Q = Me.lambda1, J = Me.lambda2;
@@ -222,7 +222,7 @@ function ke(t) {
222
222
  Q = Ce.lambda1, J = Ce.lambda2;
223
223
  const Le = f.a.mul(4).sub(3).min(5), me = f.a.greaterThan(1).select(t.maxStdDev.add(Le.sub(1).mul(0.7)), t.maxStdDev);
224
224
  y.assign(me);
225
- const ve = I(H, Q.sub(z)).add(I(1e-6, 0)).normalize(), Pe = t.minSplatSizePx, Ue = ve.mul(Q.sqrt().mul(me).min(1024).max(Pe)), Ee = I(ve.y, ve.x.negate()).mul(
225
+ const ve = I(H, Q.sub(W)).add(I(1e-6, 0)).normalize(), Pe = t.minSplatSizePx, Ue = ve.mul(Q.sqrt().mul(me).min(1024).max(Pe)), Ee = I(ve.y, ve.x.negate()).mul(
226
226
  J.sqrt().mul(me).min(1024).max(Pe)
227
227
  ), Ve = Ue.mul(we.x).add(Ee.mul(we.y)), Ge = d.xy.div(d.w), Oe = C(
228
228
  Ge.add(Ve.mul(2).div(t.viewport)),
@@ -233,7 +233,7 @@ function ke(t) {
233
233
  })(), n.fragmentNode = ye(() => {
234
234
  const p = D.dot(D);
235
235
  He(p.greaterThan(1));
236
- const M = p.mul(y.mul(y).mul(-0.5)).exp(), P = f.a.mul(4).sub(3).min(5), r = P.mul(P).sub(1).mul(1 / Math.E).exp(), o = M.oneMinus().pow(r).oneMinus(), w = f.a.greaterThan(1).select(o, M.mul(f.a)).mul(V).mul(T), b = re(t.relightMap, se), m = t.relightSoftness.div(t.viewport.x.max(1)), g = t.relightSoftness.div(t.viewport.y.max(1)), v = b, F = re(t.relightMap, se.add(I(m, 0))), G = re(t.relightMap, se.add(I(m.negate(), 0))), E = re(t.relightMap, se.add(I(0, g))), L = re(t.relightMap, se.add(I(0, g.negate()))), O = v.a.add(F.a).add(G.a).add(E.a).add(L.a).max(1e-4), W = v.rgb.mul(v.a).add(F.rgb.mul(F.a)).add(G.rgb.mul(G.a)).add(E.rgb.mul(E.a)).add(L.rgb.mul(L.a)).div(O), h = O.mul(0.2), j = C(W, h), N = t.relightSoftness.greaterThan(0.5).select(j, b), _ = ce(
236
+ const M = p.mul(y.mul(y).mul(-0.5)).exp(), P = f.a.mul(4).sub(3).min(5), r = P.mul(P).sub(1).mul(1 / Math.E).exp(), o = M.oneMinus().pow(r).oneMinus(), w = f.a.greaterThan(1).select(o, M.mul(f.a)).mul(V).mul(T), b = re(t.relightMap, se), m = t.relightSoftness.div(t.viewport.x.max(1)), g = t.relightSoftness.div(t.viewport.y.max(1)), v = b, F = re(t.relightMap, se.add(I(m, 0))), G = re(t.relightMap, se.add(I(m.negate(), 0))), E = re(t.relightMap, se.add(I(0, g))), L = re(t.relightMap, se.add(I(0, g.negate()))), O = v.a.add(F.a).add(G.a).add(E.a).add(L.a).max(1e-4), z = v.rgb.mul(v.a).add(F.rgb.mul(F.a)).add(G.rgb.mul(G.a)).add(E.rgb.mul(E.a)).add(L.rgb.mul(L.a)).div(O), h = O.mul(0.2), j = C(z, h), N = t.relightSoftness.greaterThan(0.5).select(j, b), _ = ce(
237
237
  R(t.relightBackground),
238
238
  N.rgb.mul(t.relightBrightness),
239
239
  N.a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voluma/vlam",
3
- "version": "0.3.4",
3
+ "version": "0.4.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",
@@ -1,178 +0,0 @@
1
- var Dt = Object.defineProperty;
2
- var Ot = (u, n, e) => n in u ? Dt(u, n, { enumerable: !0, configurable: !0, writable: !0, value: e }) : u[n] = e;
3
- var r = (u, n, e) => Ot(u, typeof n != "symbol" ? n + "" : n, e);
4
- import * as h from "three/webgpu";
5
- import { uniform as z, storage as m, Fn as A, If as l, float as C, instanceIndex as i, ivec2 as Mt, int as G, textureLoad as Kt, floatBitsToUint as Vt, uint as t, atomicStore as D, workgroupId as tt, Loop as B, invocationLocalIndex as bt, atomicAdd as Lt, atomicLoad as I, workgroupArray as Pt, storageBarrier as H, workgroupBarrier as q, atomicOr as Wt, countOneBits as et } from "three/tsl";
6
- import { S as Xt, v as Nt, r as zt } from "./relighting-Tiwep8yd.js";
7
- const Rt = 24, Gt = 32, nt = 4, xt = (1 << Rt) - 1;
8
- function Ht(u) {
9
- if (!Number.isInteger(u) || u <= 0 || u % nt !== 0)
10
- throw new RangeError("Radix key width must be a positive multiple of four bits.");
11
- return u / nt;
12
- }
13
- const c = 256, it = 8, st = c * it, O = 16, _ = c / 32, Y = O * _, Z = 256;
14
- class jt {
15
- constructor(n) {
16
- r(this, "kind", "radix");
17
- r(this, "renderer");
18
- r(this, "stages", []);
19
- r(this, "workingAttributes");
20
- /** Frees the JS mirrors three keeps behind the GPU-only ping-pong buffers. */
21
- r(this, "mirrors");
22
- /** Set by {@link dispose}; makes a second dispose a no-op. */
23
- r(this, "disposed", !1);
24
- r(this, "viewRow2", z(new h.Vector4()));
25
- r(this, "depthMin", z(0));
26
- r(this, "depthScale", z(0));
27
- r(this, "activeCount", z(0));
28
- r(this, "viewCenter", new h.Vector3());
29
- /** Exact mode avoids scene-bounds quantization entirely. */
30
- r(this, "exactDepth");
31
- const { capacity: e, centersTexture: f, dataTextureWidth: g, centersBuffer: v } = n;
32
- if (!f && !v)
33
- throw new Error("RadixSorter: provide centersTexture or centersBuffer.");
34
- if (f && g === void 0)
35
- throw new Error("RadixSorter: dataTextureWidth is required with centersTexture.");
36
- this.renderer = n.renderer;
37
- const S = n.exactDepth === !0;
38
- this.exactDepth = S;
39
- const F = Ht(S ? Gt : Rt), p = Math.ceil(e / st), k = O * p, M = Math.ceil(k / Z), rt = new h.StorageBufferAttribute(new Uint32Array(e), 1), ot = new h.StorageBufferAttribute(new Uint32Array(e), 1), at = new h.StorageBufferAttribute(new Uint32Array(e), 1), ct = new h.StorageBufferAttribute(new Uint32Array(e), 1), ut = new h.StorageBufferAttribute(
40
- new Uint32Array(k),
41
- 1
42
- ), dt = new h.StorageBufferAttribute(new Uint32Array(M), 1), lt = new h.StorageBufferAttribute(
43
- new Uint32Array(p * Y),
44
- 1
45
- );
46
- this.workingAttributes = [
47
- rt,
48
- ot,
49
- at,
50
- ct,
51
- ut,
52
- dt,
53
- lt
54
- ], this.mirrors = new Xt(this.workingAttributes);
55
- const K = m(rt, "uint", e), mt = m(ot, "uint", e), V = m(at, "uint", e), ht = m(ct, "uint", e), vt = m(n.sourceIndexAttribute, "uint", e), St = m(n.splatIndexAttribute, "float", e), gt = v ? m(v, "vec4", e) : null, w = m(ut, "uint", k).toAtomic(), L = m(dt, "uint", M), T = m(lt, "uint", p * Y).toAtomic(), kt = A(() => {
56
- l(C(i).lessThan(this.activeCount), () => {
57
- const o = vt.element(i), P = f ? Mt(
58
- G(o).mod(G(g)),
59
- G(o).div(G(g))
60
- ) : null, j = gt ? gt.element(o).xyz : Kt(f, P).xyz, W = this.viewRow2.xyz.dot(j).add(this.viewRow2.w);
61
- if (S) {
62
- const X = Vt(W), N = X.shiftRight(t(31)).equal(t(0)).select(t(2147483648), t(4294967295));
63
- K.element(i).assign(X.bitXor(N));
64
- } else
65
- K.element(i).assign(W.sub(this.depthMin).mul(this.depthScale).clamp(0, xt).toUint());
66
- V.element(i).assign(o);
67
- });
68
- })().compute(e, [c]);
69
- this.stages.push(kt);
70
- for (let o = 0; o < F; o++) {
71
- const P = o % 2 === 0 ? K : mt, j = o % 2 === 0 ? mt : K, W = o % 2 === 0 ? V : ht, X = o % 2 === 0 ? ht : V, N = t(o * nt), Bt = A(() => {
72
- D(w.element(i), t(0));
73
- })().compute(k, [c]), It = A(() => {
74
- const s = tt.x.mul(t(st));
75
- B(it, ({ i: a }) => {
76
- const d = s.add(t(a).mul(t(c))).add(bt);
77
- l(C(d).lessThan(this.activeCount), () => {
78
- const b = P.element(d).shiftRight(N).bitAnd(t(15));
79
- Lt(w.element(b.mul(t(p)).add(tt.x)), t(1));
80
- });
81
- });
82
- })().compute(p * c, [c]), _t = A(() => {
83
- const s = i.mul(t(Z)), a = t(0).toVar();
84
- B(Z, ({ i: d }) => {
85
- const b = s.add(t(d));
86
- l(b.lessThan(t(k)), () => {
87
- const E = I(w.element(b)).toVar();
88
- D(w.element(b), a), a.addAssign(E);
89
- });
90
- }), L.element(i).assign(a);
91
- })().compute(M, [64]), Et = A(() => {
92
- l(i.equal(t(0)), () => {
93
- const s = t(0).toVar();
94
- B(M, ({ i: a }) => {
95
- const d = L.element(a).toVar();
96
- L.element(a).assign(s), s.addAssign(d);
97
- });
98
- });
99
- })().compute(1, [64]), yt = A(() => {
100
- const s = i.div(t(Z));
101
- D(
102
- w.element(i),
103
- I(w.element(i)).add(L.element(s))
104
- );
105
- })().compute(k, [c]), J = Pt("uint", O), Ut = A(() => {
106
- const s = bt, a = tt.x, d = a.mul(t(Y));
107
- l(s.lessThan(t(O)), () => {
108
- J.element(s).assign(t(0));
109
- }), l(s.lessThan(t(Y)), () => {
110
- D(T.element(d.add(s)), t(0));
111
- }), H(), q(), B(it, ({ i: b }) => {
112
- const E = a.mul(t(st)).add(b.toUint().mul(t(c))).add(s), At = C(E).lessThan(this.activeCount), Q = t(0).toVar(), ft = t(0).toVar(), y = t(0).toVar();
113
- l(At, () => {
114
- Q.assign(P.element(E)), ft.assign(W.element(E)), y.assign(Q.shiftRight(N).bitAnd(t(15)));
115
- const x = s.shiftRight(t(5)), U = s.bitAnd(t(31)), R = d.add(y.mul(t(_))).add(x);
116
- Wt(T.element(R), t(1).shiftLeft(U));
117
- }), H(), q(), l(At, () => {
118
- const x = s.shiftRight(t(5)), U = s.bitAnd(t(31)), R = d.add(y.mul(t(_))), $ = J.element(y).toVar();
119
- B(_, ({ i: wt }) => {
120
- l(t(wt).lessThan(x), () => {
121
- $.addAssign(
122
- et(I(T.element(R.add(wt.toUint()))))
123
- );
124
- });
125
- });
126
- const Ct = t(1).shiftLeft(U).sub(t(1));
127
- $.addAssign(
128
- et(I(T.element(R.add(x))).bitAnd(Ct))
129
- );
130
- const pt = I(w.element(y.mul(t(p)).add(a))).add($);
131
- j.element(pt).assign(Q), X.element(pt).assign(ft);
132
- }), H(), q(), l(s.lessThan(t(O)), () => {
133
- const x = t(0).toVar();
134
- B(_, ({ i: U }) => {
135
- const R = d.add(s.mul(t(_))).add(U.toUint());
136
- x.addAssign(et(I(T.element(R)))), D(T.element(R), t(0));
137
- }), J.element(s).addAssign(x);
138
- }), H(), q();
139
- });
140
- })().compute(p * c, [c]);
141
- this.stages.push(
142
- Bt,
143
- It,
144
- _t,
145
- Et,
146
- yt,
147
- Ut
148
- );
149
- }
150
- const Tt = A(() => {
151
- l(C(i).lessThan(this.activeCount), () => {
152
- St.element(i).assign(C(V.element(i)));
153
- });
154
- })().compute(e, [c]);
155
- this.stages.push(Tt);
156
- }
157
- sort(n, e, f) {
158
- if (e === 0) return !0;
159
- const g = n.elements;
160
- if (this.viewRow2.value.set(g[2], g[6], g[10], g[14]), this.activeCount.value = e, !this.exactDepth) {
161
- this.viewCenter.copy(f.center).applyMatrix4(n);
162
- const v = Nt(n, f.radius), S = this.viewCenter.z - v, F = this.viewCenter.z + v;
163
- this.depthMin.value = S, this.depthScale.value = xt / (F - S || 1);
164
- }
165
- return this.stages[0].count = e, this.stages[this.stages.length - 1].count = e, this.renderer.compute(this.stages), this.mirrors.settled || this.mirrors.release(this.renderer), !0;
166
- }
167
- dispose() {
168
- if (!this.disposed) {
169
- this.disposed = !0;
170
- for (const n of this.stages) n.dispose();
171
- zt(this.renderer, this.workingAttributes);
172
- }
173
- }
174
- }
175
- export {
176
- jt as RadixSorter
177
- };
178
- //# sourceMappingURL=radix-sorter-BrbUg_CV.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"radix-sorter-BrbUg_CV.js","sources":["../src/lib/core/radix-sort.ts","../src/lib/core/radix-sorter.ts"],"sourcesContent":["/**\n * @role Utility\n * Shared constants and deterministic reference logic for GPU radix sorting.\n */\n\n/** Depth precision retained by both the GPU radix path and WebGL worker path. */\nexport const RADIX_KEY_BITS = 24;\n/** Exact Float32 depth keys use every sortable IEEE-754 bit. */\nexport const RADIX_EXACT_KEY_BITS = 32;\n/** Four-bit digits keep the portable shader's workgroup memory requirements small. */\nexport const RADIX_BITS_PER_PASS = 4;\nexport const RADIX_BUCKETS = 1 << RADIX_BITS_PER_PASS;\nexport const RADIX_PASS_COUNT = RADIX_KEY_BITS / RADIX_BITS_PER_PASS;\nexport const RADIX_KEY_MAX = (1 << RADIX_KEY_BITS) - 1;\n\n/** Number of four-bit passes required for one unsigned radix key width. */\nexport function radixPassCount(keyBits: number): number {\n if (!Number.isInteger(keyBits) || keyBits <= 0 || keyBits % RADIX_BITS_PER_PASS !== 0) {\n throw new RangeError('Radix key width must be a positive multiple of four bits.');\n }\n return keyBits / RADIX_BITS_PER_PASS;\n}\n\n/**\n * Maps a Float32 bit pattern onto unsigned integer order without losing depth\n * precision. Ascending mapped keys are ascending numeric floats, including\n * negative view-space Z values (farther splats first in VLAM's convention).\n */\nexport function sortableFloat32Bits(bits: number): number {\n const value = bits >>> 0;\n return (value ^ ((value & 0x80000000) === 0 ? 0x80000000 : 0xffffffff)) >>> 0;\n}\n\n/** Quantizes a depth into the unsigned 24-bit key used by the GPU sorter. */\nexport function quantizeDepthKey(depth: number, minimum: number, maximum: number): number {\n const span = maximum - minimum;\n if (!Number.isFinite(depth) || !Number.isFinite(span) || span <= 0) return 0;\n return Math.min(\n RADIX_KEY_MAX,\n Math.max(0, Math.round(((depth - minimum) / span) * RADIX_KEY_MAX)),\n );\n}\n\n/**\n * Stable CPU reference for validating the GPU's six four-bit radix passes.\n * This is deliberately test/debug-only logic, not the WebGL production sorter.\n */\nexport function stableRadixSortReference(\n keys: Uint32Array,\n values?: Uint32Array,\n keyBits = RADIX_KEY_BITS,\n): Uint32Array {\n const count = keys.length;\n let inputKeys = Uint32Array.from(keys);\n let outputKeys = new Uint32Array(count);\n let inputValues = values ? Uint32Array.from(values) : Uint32Array.from(keys.keys());\n let outputValues = new Uint32Array(count);\n const counts = new Uint32Array(RADIX_BUCKETS);\n const offsets = new Uint32Array(RADIX_BUCKETS);\n\n for (let pass = 0; pass < radixPassCount(keyBits); pass++) {\n counts.fill(0);\n const shift = pass * RADIX_BITS_PER_PASS;\n for (let i = 0; i < count; i++) {\n const digit = ((inputKeys[i] as number) >>> shift) & 0xf;\n counts[digit] = (counts[digit] as number) + 1;\n }\n let total = 0;\n for (let digit = 0; digit < RADIX_BUCKETS; digit++) {\n offsets[digit] = total;\n total += counts[digit] as number;\n }\n for (let i = 0; i < count; i++) {\n const key = inputKeys[i] as number;\n const digit = (key >>> shift) & 0xf;\n const target = offsets[digit] as number;\n offsets[digit] = target + 1;\n outputKeys[target] = key;\n outputValues[target] = inputValues[i] as number;\n }\n [inputKeys, outputKeys] = [outputKeys, inputKeys];\n [inputValues, outputValues] = [outputValues, inputValues];\n }\n return inputValues;\n}\n","/**\n * @role Bridge\n * Portable stable WebGPU radix sorting adapted to Three.js TSL compute nodes.\n */\nimport * as THREE from 'three/webgpu';\nimport {\n Fn,\n If,\n Loop,\n atomicAdd,\n atomicLoad,\n atomicOr,\n atomicStore,\n countOneBits,\n float,\n floatBitsToUint,\n instanceIndex,\n int,\n invocationLocalIndex,\n ivec2,\n storage,\n storageBarrier,\n textureLoad,\n uint,\n uniform,\n workgroupArray,\n workgroupBarrier,\n workgroupId,\n} from 'three/tsl';\nimport type { SplatSorter } from './sorter';\nimport { releaseRendererAttributes } from './compute-sorter';\nimport {\n RADIX_BITS_PER_PASS,\n RADIX_EXACT_KEY_BITS,\n RADIX_KEY_BITS,\n RADIX_KEY_MAX,\n radixPassCount,\n} from './radix-sort';\nimport { viewDepthRadius } from './splat-sort-bounds';\nimport { StorageMirrorReleaser } from './storage-attribute-mirror';\n\nconst WORKGROUP_SIZE = 256;\nconst ELEMENTS_PER_THREAD = 8;\nconst ELEMENTS_PER_WORKGROUP = WORKGROUP_SIZE * ELEMENTS_PER_THREAD;\nconst DIGIT_COUNT = 16;\nconst MASK_WORDS_PER_DIGIT = WORKGROUP_SIZE / 32;\nconst MASK_WORDS_PER_GROUP = DIGIT_COUNT * MASK_WORDS_PER_DIGIT;\nconst SCAN_BLOCK_SIZE = 256;\n\ntype UintWorkgroupArray = {\n element(index: THREE.Node<'uint'>): THREE.Node<'uint'>;\n};\n\n/** Isolates gaps in Three's current WorkgroupInfo/Bitcount TypeScript declarations. */\nfunction asUintNode(node: unknown): THREE.Node<'uint'> {\n return node as THREE.Node<'uint'>;\n}\n\n/**\n * Stable four-bit-per-pass WebGPU radix sorter.\n *\n * The ranked scatter follows PlayCanvas/WebGPU-Radix-Sort's per-digit bitmask\n * design. Three.js does not currently expose atomic workgroup arrays in TSL,\n * so masks use disjoint global-storage slices per workgroup. Workgroup and\n * storage barriers retain the same stable ranking semantics without relying\n * on cross-workgroup execution order.\n */\nexport class RadixSorter implements SplatSorter {\n readonly kind = 'radix' as const;\n private readonly renderer: THREE.WebGPURenderer;\n private readonly stages: THREE.ComputeNode[] = [];\n private readonly workingAttributes: THREE.StorageBufferAttribute[];\n /** Frees the JS mirrors three keeps behind the GPU-only ping-pong buffers. */\n private readonly mirrors: StorageMirrorReleaser;\n /** Set by {@link dispose}; makes a second dispose a no-op. */\n private disposed = false;\n private readonly viewRow2 = uniform(new THREE.Vector4());\n private readonly depthMin = uniform(0);\n private readonly depthScale = uniform(0);\n private readonly activeCount = uniform(0);\n private readonly viewCenter = new THREE.Vector3();\n /** Exact mode avoids scene-bounds quantization entirely. */\n private readonly exactDepth: boolean;\n\n constructor(options: {\n renderer: THREE.WebGPURenderer;\n capacity: number;\n centersTexture?: THREE.DataTexture;\n dataTextureWidth?: number;\n /** Gathered world-space centers for a unified renderer. */\n centersBuffer?: THREE.StorageBufferAttribute;\n splatIndexAttribute: THREE.StorageInstancedBufferAttribute;\n sourceIndexAttribute: THREE.StorageBufferAttribute;\n /** Keep every Float32 depth bit instead of quantizing to 24 bits. */\n exactDepth?: boolean;\n }) {\n const { capacity, centersTexture, dataTextureWidth, centersBuffer } = options;\n if (!centersTexture && !centersBuffer) {\n throw new Error('RadixSorter: provide centersTexture or centersBuffer.');\n }\n if (centersTexture && dataTextureWidth === undefined) {\n throw new Error('RadixSorter: dataTextureWidth is required with centersTexture.');\n }\n this.renderer = options.renderer;\n const exactDepth = options.exactDepth === true;\n this.exactDepth = exactDepth;\n const passCount = radixPassCount(exactDepth ? RADIX_EXACT_KEY_BITS : RADIX_KEY_BITS);\n const groupCount = Math.ceil(capacity / ELEMENTS_PER_WORKGROUP);\n const histogramLength = DIGIT_COUNT * groupCount;\n const scanBlockCount = Math.ceil(histogramLength / SCAN_BLOCK_SIZE);\n\n const keysAAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const keysBAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const valuesAAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const valuesBAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const histogramAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(histogramLength),\n 1,\n );\n const scanSumsAttribute = new THREE.StorageBufferAttribute(new Uint32Array(scanBlockCount), 1);\n const masksAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(groupCount * MASK_WORDS_PER_GROUP),\n 1,\n );\n this.workingAttributes = [\n keysAAttribute,\n keysBAttribute,\n valuesAAttribute,\n valuesBAttribute,\n histogramAttribute,\n scanSumsAttribute,\n masksAttribute,\n ];\n this.mirrors = new StorageMirrorReleaser(this.workingAttributes);\n\n const keysA = storage(keysAAttribute, 'uint', capacity);\n const keysB = storage(keysBAttribute, 'uint', capacity);\n const valuesA = storage(valuesAAttribute, 'uint', capacity);\n const valuesB = storage(valuesBAttribute, 'uint', capacity);\n const source = storage(options.sourceIndexAttribute, 'uint', capacity);\n const order = storage(options.splatIndexAttribute, 'float', capacity);\n const workCenters = centersBuffer ? storage(centersBuffer, 'vec4', capacity) : null;\n const histogram = storage(histogramAttribute, 'uint', histogramLength).toAtomic();\n const scanSums = storage(scanSumsAttribute, 'uint', scanBlockCount);\n const masks = storage(masksAttribute, 'uint', groupCount * MASK_WORDS_PER_GROUP).toAtomic();\n\n const buildKeys = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n const poolIndex = source.element(instanceIndex);\n const texel = centersTexture\n ? ivec2(\n int(poolIndex).mod(int(dataTextureWidth as number)),\n int(poolIndex).div(int(dataTextureWidth as number)),\n )\n : null;\n const center = workCenters\n ? workCenters.element(poolIndex).xyz\n : textureLoad(centersTexture, texel as THREE.Node<'ivec2'>).xyz;\n const depth = this.viewRow2.xyz.dot(center).add(this.viewRow2.w);\n if (exactDepth) {\n // IEEE-754 bits are monotonic only for positive values. Flip the\n // sign partition so ascending unsigned keys remain ascending numeric\n // depth, including VLAM's negative view-space Z convention.\n // Three's BitcastNode typings omit shift/xor; runtime nodes support them.\n const bits = asUintNode(floatBitsToUint(depth));\n const mask = bits\n .shiftRight(uint(31))\n .equal(uint(0))\n .select(uint(0x80000000), uint(0xffffffff));\n keysA.element(instanceIndex).assign(bits.bitXor(mask));\n } else {\n keysA\n .element(instanceIndex)\n .assign(depth.sub(this.depthMin).mul(this.depthScale).clamp(0, RADIX_KEY_MAX).toUint());\n }\n valuesA.element(instanceIndex).assign(poolIndex);\n });\n })().compute(capacity, [WORKGROUP_SIZE]);\n this.stages.push(buildKeys);\n\n for (let pass = 0; pass < passCount; pass++) {\n const inputKeys = pass % 2 === 0 ? keysA : keysB;\n const outputKeys = pass % 2 === 0 ? keysB : keysA;\n const inputValues = pass % 2 === 0 ? valuesA : valuesB;\n const outputValues = pass % 2 === 0 ? valuesB : valuesA;\n const bit = uint(pass * RADIX_BITS_PER_PASS);\n\n const clearHistogram = Fn(() => {\n atomicStore(histogram.element(instanceIndex), uint(0));\n })().compute(histogramLength, [WORKGROUP_SIZE]);\n\n const buildHistogram = Fn(() => {\n const base = workgroupId.x.mul(uint(ELEMENTS_PER_WORKGROUP));\n Loop(ELEMENTS_PER_THREAD, ({ i }) => {\n const index = base.add(uint(i).mul(uint(WORKGROUP_SIZE))).add(invocationLocalIndex);\n If(float(index).lessThan(this.activeCount), () => {\n const digit = inputKeys.element(index).shiftRight(bit).bitAnd(uint(0xf));\n atomicAdd(histogram.element(digit.mul(uint(groupCount)).add(workgroupId.x)), uint(1));\n });\n });\n })().compute(groupCount * WORKGROUP_SIZE, [WORKGROUP_SIZE]);\n\n const scanBlocks = Fn(() => {\n const start = instanceIndex.mul(uint(SCAN_BLOCK_SIZE));\n const running = uint(0).toVar();\n Loop(SCAN_BLOCK_SIZE, ({ i }) => {\n const index = start.add(uint(i));\n If(index.lessThan(uint(histogramLength)), () => {\n const value = atomicLoad(histogram.element(index)).toVar();\n atomicStore(histogram.element(index), running);\n running.addAssign(value);\n });\n });\n scanSums.element(instanceIndex).assign(running);\n })().compute(scanBlockCount, [64]);\n\n const scanBlockSums = Fn(() => {\n If(instanceIndex.equal(uint(0)), () => {\n const running = uint(0).toVar();\n Loop(scanBlockCount, ({ i }) => {\n const value = scanSums.element(i).toVar();\n scanSums.element(i).assign(running);\n running.addAssign(value);\n });\n });\n })().compute(1, [64]);\n\n const addBlockOffsets = Fn(() => {\n const block = instanceIndex.div(uint(SCAN_BLOCK_SIZE));\n atomicStore(\n histogram.element(instanceIndex),\n atomicLoad(histogram.element(instanceIndex)).add(scanSums.element(block)),\n );\n })().compute(histogramLength, [WORKGROUP_SIZE]);\n\n const digitOffsets = workgroupArray('uint', DIGIT_COUNT) as unknown as UintWorkgroupArray;\n const rankedScatter = Fn(() => {\n const thread = invocationLocalIndex;\n const group = workgroupId.x;\n const maskBase = group.mul(uint(MASK_WORDS_PER_GROUP));\n If(thread.lessThan(uint(DIGIT_COUNT)), () => {\n digitOffsets.element(thread).assign(uint(0));\n });\n If(thread.lessThan(uint(MASK_WORDS_PER_GROUP)), () => {\n atomicStore(masks.element(maskBase.add(thread)), uint(0));\n });\n storageBarrier();\n workgroupBarrier();\n\n Loop(ELEMENTS_PER_THREAD, ({ i }) => {\n const index = group\n .mul(uint(ELEMENTS_PER_WORKGROUP))\n .add(i.toUint().mul(uint(WORKGROUP_SIZE)))\n .add(thread);\n const valid = float(index).lessThan(this.activeCount);\n const key = uint(0).toVar();\n const value = uint(0).toVar();\n const digit = uint(0).toVar();\n If(valid, () => {\n key.assign(inputKeys.element(index));\n value.assign(inputValues.element(index));\n digit.assign(key.shiftRight(bit).bitAnd(uint(0xf)));\n const word = thread.shiftRight(uint(5));\n const bitInWord = thread.bitAnd(uint(31));\n const maskIndex = maskBase.add(digit.mul(uint(MASK_WORDS_PER_DIGIT))).add(word);\n atomicOr(masks.element(maskIndex), uint(1).shiftLeft(bitInWord));\n });\n storageBarrier();\n workgroupBarrier();\n\n If(valid, () => {\n const word = thread.shiftRight(uint(5));\n const bitInWord = thread.bitAnd(uint(31));\n const digitBase = maskBase.add(digit.mul(uint(MASK_WORDS_PER_DIGIT)));\n const localRank = digitOffsets.element(digit).toVar();\n Loop(MASK_WORDS_PER_DIGIT, ({ i: wordIndex }) => {\n If(uint(wordIndex).lessThan(word), () => {\n localRank.addAssign(\n asUintNode(\n countOneBits(atomicLoad(masks.element(digitBase.add(wordIndex.toUint())))),\n ),\n );\n });\n });\n const precedingMask = uint(1).shiftLeft(bitInWord).sub(uint(1));\n localRank.addAssign(\n asUintNode(\n countOneBits(atomicLoad(masks.element(digitBase.add(word))).bitAnd(precedingMask)),\n ),\n );\n const prefix = atomicLoad(histogram.element(digit.mul(uint(groupCount)).add(group)));\n const target = prefix.add(localRank);\n outputKeys.element(target).assign(key);\n outputValues.element(target).assign(value);\n });\n\n storageBarrier();\n workgroupBarrier();\n If(thread.lessThan(uint(DIGIT_COUNT)), () => {\n const total = uint(0).toVar();\n Loop(MASK_WORDS_PER_DIGIT, ({ i: wordIndex }) => {\n const maskIndex = maskBase\n .add(thread.mul(uint(MASK_WORDS_PER_DIGIT)))\n .add(wordIndex.toUint());\n total.addAssign(asUintNode(countOneBits(atomicLoad(masks.element(maskIndex)))));\n atomicStore(masks.element(maskIndex), uint(0));\n });\n digitOffsets.element(thread).addAssign(total);\n });\n storageBarrier();\n workgroupBarrier();\n });\n })().compute(groupCount * WORKGROUP_SIZE, [WORKGROUP_SIZE]);\n\n this.stages.push(\n clearHistogram,\n buildHistogram,\n scanBlocks,\n scanBlockSums,\n addBlockOffsets,\n rankedScatter,\n );\n }\n\n const writeOrder = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n order.element(instanceIndex).assign(float(valuesA.element(instanceIndex)));\n });\n })().compute(capacity, [WORKGROUP_SIZE]);\n this.stages.push(writeOrder);\n }\n\n sort(modelView: THREE.Matrix4, activeCount: number, bounds: THREE.Sphere): boolean {\n if (activeCount === 0) return true;\n const m = modelView.elements;\n this.viewRow2.value.set(m[2], m[6], m[10], m[14]);\n this.activeCount.value = activeCount;\n if (!this.exactDepth) {\n this.viewCenter.copy(bounds.center).applyMatrix4(modelView);\n const viewRadius = viewDepthRadius(modelView, bounds.radius);\n const minimum = this.viewCenter.z - viewRadius;\n const maximum = this.viewCenter.z + viewRadius;\n this.depthMin.value = minimum;\n this.depthScale.value = RADIX_KEY_MAX / (maximum - minimum || 1);\n }\n this.stages[0]!.count = activeCount;\n this.stages[this.stages.length - 1]!.count = activeCount;\n this.renderer.compute(this.stages);\n // Key/value ping-pong, histogram, scan sums and masks are all written and\n // read entirely on the GPU - at 4 B per splat of capacity each, their JS\n // mirrors are ~16 B/splat of pure waste. Deliberately *not*\n // `sourceIndex`/`splatIndex`: those belong to the mesh and are rewritten\n // from the CPU every frame. Same ownership line as `releaseRendererAttributes`.\n if (!this.mirrors.settled) this.mirrors.release(this.renderer);\n return true;\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n for (const stage of this.stages) stage.dispose();\n releaseRendererAttributes(this.renderer, this.workingAttributes);\n }\n}\n"],"names":["RADIX_KEY_BITS","RADIX_EXACT_KEY_BITS","RADIX_BITS_PER_PASS","RADIX_KEY_MAX","radixPassCount","keyBits","WORKGROUP_SIZE","ELEMENTS_PER_THREAD","ELEMENTS_PER_WORKGROUP","DIGIT_COUNT","MASK_WORDS_PER_DIGIT","MASK_WORDS_PER_GROUP","SCAN_BLOCK_SIZE","RadixSorter","options","__publicField","uniform","THREE","capacity","centersTexture","dataTextureWidth","centersBuffer","exactDepth","passCount","groupCount","histogramLength","scanBlockCount","keysAAttribute","keysBAttribute","valuesAAttribute","valuesBAttribute","histogramAttribute","scanSumsAttribute","masksAttribute","StorageMirrorReleaser","keysA","storage","keysB","valuesA","valuesB","source","order","workCenters","histogram","scanSums","masks","buildKeys","Fn","If","float","instanceIndex","poolIndex","texel","ivec2","int","center","textureLoad","depth","bits","floatBitsToUint","mask","uint","pass","inputKeys","outputKeys","inputValues","outputValues","bit","clearHistogram","atomicStore","buildHistogram","base","workgroupId","Loop","i","index","invocationLocalIndex","digit","atomicAdd","scanBlocks","start","running","value","atomicLoad","scanBlockSums","addBlockOffsets","block","digitOffsets","workgroupArray","rankedScatter","thread","group","maskBase","storageBarrier","workgroupBarrier","valid","key","word","bitInWord","maskIndex","atomicOr","digitBase","localRank","wordIndex","countOneBits","precedingMask","target","total","writeOrder","modelView","activeCount","bounds","m","viewRadius","viewDepthRadius","minimum","maximum","stage","releaseRendererAttributes"],"mappings":"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"}