@voluma/vlam 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/THIRD_PARTY_NOTICES.md +86 -0
- package/dist/budget-governor.d.ts +154 -0
- package/dist/camera-budget-governor.d.ts +234 -0
- package/dist/chunk-cache-budget.d.ts +156 -0
- package/dist/chunk-fetch-scheduler.d.ts +168 -0
- package/dist/chunk-loader.d.ts +85 -0
- package/dist/compute-sorter.d.ts +131 -0
- package/dist/create-splat-renderer.d.ts +99 -0
- package/dist/dataset-source.d.ts +55 -0
- package/dist/depth-of-field.d.ts +73 -0
- package/dist/effects.d.ts +337 -0
- package/dist/effects.js +180 -0
- package/dist/effects.js.map +1 -0
- package/dist/formats/ksplat/index.d.ts +8 -0
- package/dist/formats/ksplat/parse-ksplat.d.ts +9 -0
- package/dist/formats/ksplat.js +167 -0
- package/dist/formats/ksplat.js.map +1 -0
- package/dist/formats/lcc/collision-mesh.d.ts +37 -0
- package/dist/formats/lcc/collision-partition.d.ts +35 -0
- package/dist/formats/lcc/index.d.ts +16 -0
- package/dist/formats/lcc/lcc.d.ts +28 -0
- package/dist/formats/lcc/lcc2-transform.d.ts +18 -0
- package/dist/formats/lcc/lcc2.d.ts +4 -0
- package/dist/formats/lcc/parse-collision-lci.d.ts +28 -0
- package/dist/formats/lcc/parse-lcc.d.ts +177 -0
- package/dist/formats/lcc/parse-mesh-ply.d.ts +21 -0
- package/dist/formats/lcc.js +969 -0
- package/dist/formats/lcc.js.map +1 -0
- package/dist/formats/ply/index.d.ts +12 -0
- package/dist/formats/ply/parse-compressed-ply.d.ts +16 -0
- package/dist/formats/ply/parse-splat-ply.d.ts +46 -0
- package/dist/formats/ply.js +230 -0
- package/dist/formats/ply.js.map +1 -0
- package/dist/formats/rad/frontier-worker-protocol.d.ts +177 -0
- package/dist/formats/rad/index.d.ts +10 -0
- package/dist/formats/rad/parse-rad.d.ts +88 -0
- package/dist/formats/rad/rad-column-decoders.d.ts +15 -0
- package/dist/formats/rad/rad-foveated-source.d.ts +71 -0
- package/dist/formats/rad/rad-parent-sizes.d.ts +45 -0
- package/dist/formats/rad/rad.d.ts +132 -0
- package/dist/formats/rad.js +1098 -0
- package/dist/formats/rad.js.map +1 -0
- package/dist/formats/sog/index.d.ts +9 -0
- package/dist/formats/sog/parse-sog.d.ts +45 -0
- package/dist/formats/sog.js +257 -0
- package/dist/formats/sog.js.map +1 -0
- package/dist/formats/splat/index.d.ts +8 -0
- package/dist/formats/splat/parse-splat.d.ts +10 -0
- package/dist/formats/splat.js +39 -0
- package/dist/formats/splat.js.map +1 -0
- package/dist/formats/spz/index.d.ts +8 -0
- package/dist/formats/spz/parse-spz.d.ts +10 -0
- package/dist/formats/spz.js +218 -0
- package/dist/formats/spz.js.map +1 -0
- package/dist/frontier-worker-CAnK_Cmu.js +566 -0
- package/dist/frontier-worker-CAnK_Cmu.js.map +1 -0
- package/dist/half-float-Cs4FDq8Q.js +30 -0
- package/dist/half-float-Cs4FDq8Q.js.map +1 -0
- package/dist/half-float.d.ts +15 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +9054 -0
- package/dist/index.js.map +1 -0
- package/dist/load-scene.d.ts +50 -0
- package/dist/load-worker-protocol.d.ts +82 -0
- package/dist/loading-wLo8vRbA.js +102 -0
- package/dist/loading-wLo8vRbA.js.map +1 -0
- package/dist/loading.d.ts +150 -0
- package/dist/lod-manifest.d.ts +61 -0
- package/dist/lod-scheduler.d.ts +229 -0
- package/dist/lod-source.d.ts +207 -0
- package/dist/logging-BfPdd7NJ.js +19 -0
- package/dist/logging-BfPdd7NJ.js.map +1 -0
- package/dist/logging.d.ts +37 -0
- package/dist/one-shot-worker-G80btnth.js +664 -0
- package/dist/one-shot-worker-G80btnth.js.map +1 -0
- package/dist/orientation.d.ts +35 -0
- package/dist/ply-header-BwgExCn-.js +125 -0
- package/dist/ply-header-BwgExCn-.js.map +1 -0
- package/dist/ply-header.d.ts +87 -0
- package/dist/radix-sort.d.ts +28 -0
- package/dist/radix-sorter.d.ts +46 -0
- package/dist/relighting.d.ts +50 -0
- package/dist/selection-volume.d.ts +87 -0
- package/dist/sh-pack-D5wAe5gg.js +54 -0
- package/dist/sh-pack-D5wAe5gg.js.map +1 -0
- package/dist/sh-pack.d.ts +49 -0
- package/dist/sort-scheduler.d.ts +62 -0
- package/dist/sort-worker-protocol.d.ts +37 -0
- package/dist/sorter.d.ts +31 -0
- package/dist/source-transform.d.ts +57 -0
- package/dist/splat-budget-PSojLJPO.js +157 -0
- package/dist/splat-budget-PSojLJPO.js.map +1 -0
- package/dist/splat-budget.d.ts +404 -0
- package/dist/splat-data-BZM5dhhx.js +10 -0
- package/dist/splat-data-BZM5dhhx.js.map +1 -0
- package/dist/splat-data.d.ts +150 -0
- package/dist/splat-depth-pack.d.ts +39 -0
- package/dist/splat-mesh-material.d.ts +255 -0
- package/dist/splat-mesh-picking.d.ts +87 -0
- package/dist/splat-mesh-pool.d.ts +241 -0
- package/dist/splat-mesh-types-8QDjTbZO.js +390 -0
- package/dist/splat-mesh-types-8QDjTbZO.js.map +1 -0
- package/dist/splat-mesh-types.d.ts +390 -0
- package/dist/splat-mesh.d.ts +737 -0
- package/dist/splat-modifier-stack.d.ts +38 -0
- package/dist/splat-modifier.d.ts +156 -0
- package/dist/splat-partition.d.ts +37 -0
- package/dist/splat-query.d.ts +51 -0
- package/dist/splat-scene.d.ts +127 -0
- package/dist/splat-sort-bounds.d.ts +11 -0
- package/dist/static-lod-splat-mesh.d.ts +53 -0
- package/dist/static-lod-worker-D2fyqQZO.js +474 -0
- package/dist/static-lod-worker-D2fyqQZO.js.map +1 -0
- package/dist/static-lod-worker-protocol.d.ts +37 -0
- package/dist/static-lod.d.ts +24 -0
- package/dist/storage-attribute-mirror.d.ts +103 -0
- package/dist/streamed-splat-mesh-utils.d.ts +146 -0
- package/dist/streamed-splat-mesh.d.ts +985 -0
- package/dist/unified-splat-renderer.d.ts +205 -0
- package/dist/webgpu-limits.d.ts +133 -0
- package/dist/work-buffer-gather.d.ts +119 -0
- package/dist/work-buffer-material.d.ts +42 -0
- package/dist/worker-sorter.d.ts +57 -0
- package/dist/xr-view.d.ts +92 -0
- package/package.json +135 -21
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.js","sources":["../src/lib/effects.ts"],"sourcesContent":["/**\n * `@voluma/vlam/effects` - optional, tree-shakeable effects built on the M7\n * {@link SplatModifier} contract. Import only what you use; nothing here is\n * pulled into the core renderer bundle.\n *\n * - {@link sdfEffects}: a declarative sphere/box/cylinder shape list evaluated\n * in-shader from a bounded uniform array (a fixed unroll over `maxShapes`\n * slots, gated by the live count) - add/move/remove shapes is data-only\n * (no pipeline recompile), on WebGPU and the WebGL2 fallback alike (M7.4).\n * The kinds mirror the CPU selection volumes (`createSelectionVolume`), so\n * a host can highlight exactly what a selection would take.\n * - {@link lightingPreset} / {@link revealPreset}: small reference presets,\n * starting points rather than a framework (M7.5).\n * - {@link createRelightingProxy}: builds a gray lit mesh group from collision\n * tiles (or raw geometries) for PlayCanvas-style {@link SplatMesh.setRelighting}.\n * - {@link createRelightingShadowFactorMaterial}: writes a shadow **multiplier**\n * (1 = lit, <1 = umbra) so coverage does not statically tint splats.\n * - {@link worldWarpPreset}: camera-centered sphere wrap (planet / bowl).\n * - {@link depthOfFieldPreset}: stylized modifier-based depth-of-field (M13).\n * For physically-modeled camera DoF prefer the core\n * {@link SplatMesh.setDepthOfField} / `UnifiedSplatRenderer.setDepthOfField`\n * path - this preset is the soft/approximate alternative built purely on\n * the modifier hook.\n *\n * Every preset here reads positions from `ctx.localCenter` - mesh-local space,\n * which inside a `SplatScene` is the splat's **placed** position. So a shape or\n * a reveal spans every source as one scene instead of travelling with a moved\n * one; see `docs/guide/effects-and-modifiers.md` for what that looks like, and\n * `ctx.sourceCenter` for the opposite behaviour.\n *\n * See `docs/guide/effects-and-modifiers.md`.\n */\nimport * as THREE from 'three/webgpu';\nimport {\n float,\n int,\n mat3,\n mix,\n modelViewMatrix,\n shadow,\n smoothstep,\n uniform,\n uniformArray,\n vec2,\n vec3,\n vec4,\n wgslFn,\n} from 'three/tsl';\nimport type { SplatContext, SplatModifier } from './splat-modifier';\nimport { warn } from './logging';\nimport type { CollisionMeshTile } from './formats/lcc/collision-mesh';\nimport type { TriangleMeshData } from './formats/lcc/parse-mesh-ply';\n\ntype Node<T extends string> = THREE.Node<T>;\nfunction asNode<T extends string>(node: unknown): Node<T> {\n return node as Node<T>;\n}\n\n// --- SDF effects (M7.4) -----------------------------------------------------\n\n/**\n * A signed-distance primitive kind. `cylinder` is capped and runs along the\n * shape-local Y axis (rotate it with {@link SdfShape.rotation}).\n */\nexport type SdfShapeKind = 'sphere' | 'box' | 'cylinder';\n\n/**\n * What a shape does to the splats it covers:\n * - `tint` - blend the splat color toward the shape color.\n * - `desaturate` - blend the splat color toward its luminance.\n * - `hide` - fade the splat out (soft cutaway).\n * - `rim` - tint only a band around the shape's surface.\n */\nexport type SdfMode = 'tint' | 'desaturate' | 'hide' | 'rim';\n\n/** One SDF shape in a {@link sdfEffects} list. All spaces are mesh-local. */\nexport interface SdfShape {\n kind: SdfShapeKind;\n /** Shape center, mesh-local. Default `[0, 0, 0]`. */\n center?: readonly [number, number, number];\n /**\n * Sphere/cylinder radius. Required for `kind: 'sphere'` and\n * `kind: 'cylinder'`; must be positive - {@link SdfEffect.setShapes} throws\n * otherwise.\n */\n radius?: number;\n /**\n * Cylinder full height along the shape-local Y axis. Required (and only\n * used) for `kind: 'cylinder'`; must be positive - {@link SdfEffect.setShapes}\n * throws otherwise.\n */\n height?: number;\n /**\n * Box half-extents. Required (and only used) for `kind: 'box'`; every\n * component must be positive - {@link SdfEffect.setShapes} throws otherwise.\n */\n halfExtents?: readonly [number, number, number];\n /** Orientation quaternion `[x, y, z, w]` (boxes and cylinders). Default identity. */\n rotation?: readonly [number, number, number, number];\n /** Effect color (tint/rim). Default white. */\n color?: readonly [number, number, number];\n /** Soft-edge width in world units; 0 is a hard edge. Default `0`. */\n falloff?: number;\n /** Invert the test - affect the outside of the shape instead. */\n invert?: boolean;\n /** Effect strength in `[0, 1]`. Default `1`. */\n strength?: number;\n mode: SdfMode;\n}\n\n/** A live SDF effect: one modifier plus a data-only way to update its shapes. */\nexport interface SdfEffect {\n /** Attach to `mesh.modifiers`. Built once; updating shapes never recompiles. */\n readonly modifier: SplatModifier;\n /**\n * Replaces the shape list. Purely a uniform-buffer write, so animating\n * shape poses/colors/thresholds every frame costs no pipeline recompile.\n * Shapes beyond {@link maxShapes} are dropped with a one-time warning.\n *\n * Throws (before writing anything) if a sphere or cylinder lacks a positive\n * `radius`, a cylinder lacks a positive `height`, or a box lacks positive\n * `halfExtents` - a zero-size shape is always a caller bug, never a useful\n * no-op.\n */\n setShapes(shapes: readonly SdfShape[]): void;\n /** The compiled-in shape capacity (see {@link sdfEffects} `maxShapes`). */\n readonly maxShapes: number;\n /**\n * The packed uniform state behind the modifier - `maxShapes × SHAPE_STRIDE`\n * vec4 slots plus the live shape count.\n * @internal Exposed for unit tests of the packing; not public API.\n */\n readonly _uniforms: {\n readonly slots: readonly THREE.Vector4[];\n readonly count: { value: number };\n };\n}\n\n/** vec4 slots per shape in the uniform array (see the packing below). */\nconst SHAPE_STRIDE = 5;\nconst MODE_INDEX: Record<SdfMode, number> = { tint: 0, desaturate: 1, hide: 2, rim: 3 };\n\n// Shared defaults so a per-frame `setShapes` on sparse shape literals does not\n// allocate a fresh array per omitted field.\nconst DEFAULT_CENTER: readonly [number, number, number] = [0, 0, 0];\nconst DEFAULT_COLOR: readonly [number, number, number] = [1, 1, 1];\nconst DEFAULT_ROTATION: readonly [number, number, number, number] = [0, 0, 0, 1];\n\n/**\n * Builds a {@link SdfEffect} from a declarative shape list. Shapes live in a\n * bounded uniform array and are tested in-shader against each splat's local\n * center as a **fixed unroll over all `maxShapes` slots**, each slot gated by\n * `slot < count` so unused slots contribute nothing. (An in-shader loop is\n * not viable here - see the TSL varying constraint documented inside the\n * modifier.) Adding, moving or removing a shape - or changing the count - is\n * still a pure uniform write with no recompile. Works on WebGPU and the\n * WebGL2 fallback.\n *\n * `maxShapes` is the compiled capacity (default 32). Because every slot is\n * unrolled into the shader, raising it grows the shader's size and compile\n * time (and the uniform array; very large values can exceed WebGL2's uniform\n * limits), so keep it to what a scene needs.\n *\n * Shape positions are mesh-local. In a `SplatScene` that means scene\n * coordinates - a shape overlapping two sources covers both as one continuous\n * shape - so place them with `computeSplatBounds()`, which reports the sources\n * at their current placement.\n */\nexport function sdfEffects(\n initialShapes: readonly SdfShape[] = [],\n options: { maxShapes?: number } = {},\n): SdfEffect {\n const maxShapes = Math.max(1, Math.floor(options.maxShapes ?? 32));\n\n // One flat uniform array of vec4; SHAPE_STRIDE slots per shape. Mutating\n // these Vector4s (and `count`) re-uploads as plain uniforms - never a\n // recompile. `count` bounds the in-shader loop.\n const slots: THREE.Vector4[] = [];\n for (let i = 0; i < maxShapes * SHAPE_STRIDE; i++) slots.push(new THREE.Vector4());\n const shapesUniform = uniformArray(slots, 'vec4');\n const count = uniform(0, 'int');\n\n let warnedOverflow = false;\n const setShapes = (shapes: readonly SdfShape[]): void => {\n const n = Math.min(shapes.length, maxShapes);\n if (shapes.length > maxShapes && !warnedOverflow) {\n warnedOverflow = true;\n warn(\n `sdfEffects: ${shapes.length} shapes exceeds maxShapes=${maxShapes}; ` +\n `extra shapes are ignored. Raise maxShapes if you need more.`,\n );\n }\n // Validate before touching any slot, so a bad list never leaves the\n // uniform array half-updated.\n for (let i = 0; i < n; i++) {\n const s = shapes[i] as SdfShape;\n if (s.kind === 'box') {\n const he = s.halfExtents;\n if (!he || !(he[0] > 0) || !(he[1] > 0) || !(he[2] > 0)) {\n throw new Error(\n `sdfEffects.setShapes: shape ${i} is a box and requires halfExtents ` +\n `with three positive components (got ${he ? `[${he.join(', ')}]` : 'undefined'}).`,\n );\n }\n } else if (!(typeof s.radius === 'number' && s.radius > 0)) {\n throw new Error(\n `sdfEffects.setShapes: shape ${i} is a ${s.kind} and requires a positive ` +\n `radius (got ${String(s.radius)}).`,\n );\n } else if (s.kind === 'cylinder' && !(typeof s.height === 'number' && s.height > 0)) {\n throw new Error(\n `sdfEffects.setShapes: shape ${i} is a cylinder and requires a positive ` +\n `height (got ${String(s.height)}).`,\n );\n }\n }\n for (let i = 0; i < n; i++) {\n const s = shapes[i] as SdfShape;\n const b = i * SHAPE_STRIDE;\n const c = s.center ?? DEFAULT_CENTER;\n const col = s.color ?? DEFAULT_COLOR;\n const rot = s.rotation ?? DEFAULT_ROTATION;\n const kindIndex = s.kind === 'box' ? 1 : s.kind === 'cylinder' ? 2 : 0;\n (slots[b + 0] as THREE.Vector4).set(c[0], c[1], c[2], kindIndex);\n if (s.kind === 'box') {\n const he = s.halfExtents as readonly [number, number, number];\n (slots[b + 1] as THREE.Vector4).set(he[0], he[1], he[2], s.falloff ?? 0);\n } else if (s.kind === 'cylinder') {\n (slots[b + 1] as THREE.Vector4).set(\n s.radius as number,\n (s.height as number) / 2,\n 0,\n s.falloff ?? 0,\n );\n } else {\n (slots[b + 1] as THREE.Vector4).set(s.radius as number, 0, 0, s.falloff ?? 0);\n }\n (slots[b + 2] as THREE.Vector4).set(col[0], col[1], col[2], MODE_INDEX[s.mode]);\n (slots[b + 3] as THREE.Vector4).set(rot[0], rot[1], rot[2], rot[3]);\n (slots[b + 4] as THREE.Vector4).set(s.invert ? 1 : 0, s.strength ?? 1, 0, 0);\n }\n count.value = n;\n };\n setShapes(initialShapes);\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n // Unrolled over the fixed `maxShapes` slots as *pure expressions* (no\n // control flow): a splat's color is a chain of `select`/`mix` folds, one\n // per slot, each gated by `slot < count` so unused slots are transparent.\n // Control flow inside a modifier color is not viable - three.js evaluates\n // a color that reaches the fragment stage through a varying, and a varying\n // does not emit `Loop`/`If` statements - so the shape count drives a\n // uniform-gated unroll instead. Result: changing shapes or their count is\n // still a pure uniform write (no recompile); the cost is a shader whose\n // size grows with `maxShapes`.\n const p = ctx.localCenter;\n let rgb: Node<'vec3'> = asNode<'vec3'>(ctx.color.rgb);\n let alphaMul: Node<'float'> = float(1);\n\n for (let k = 0; k < maxShapes; k++) {\n const b = k * SHAPE_STRIDE;\n const el = (j: number): Node<'vec4'> => asNode<'vec4'>(shapesUniform.element(b + j));\n const s0 = el(0);\n const s1 = el(1);\n const s2 = el(2);\n const s3 = el(3);\n const s4 = el(4);\n\n const center = s0.xyz;\n const kind = s0.w; // 0 sphere · 1 box · 2 cylinder (stored as a float index)\n const size = s1.xyz;\n const falloff = s1.w.max(1e-4);\n const shapeColor = s2.xyz;\n const mode = s2.w;\n const invert = s4.x; // 0 · 1 flag\n const strength = s4.y;\n // Slots past the live count contribute nothing.\n const gate = int(k).lessThan(count).select(float(1), float(0));\n\n // Sample point in shape-local space: R⁻¹·(p − center). The sphere/box\n // and invert choices blend with `mix` on their 0/1 flags rather than a\n // `select`: three.js miscompiles a ConditionalNode whose branches carry\n // large sub-expressions (the box SDF) here, silently zeroing the result.\n const rel = p.sub(center);\n const pl = rotateByQuatConj(s3, rel);\n const dSphere = pl.length().sub(size.x);\n const dBox = sdBox(pl, size);\n const dCylinder = sdCappedCylinder(pl, size.x, size.y);\n // Kind index → 0/1 masks, folded with `mix` like the mode masks below.\n const boxMask = kind.greaterThan(0.5).and(kind.lessThan(1.5)).select(float(1), float(0));\n const cylinderMask = kind.greaterThan(1.5).select(float(1), float(0));\n const dRaw = mix(mix(dSphere, dBox, boxMask), dCylinder, cylinderMask);\n const d = mix(dRaw, dRaw.negate(), invert);\n\n // Inside coverage (1 within, fading out over `falloff`); rim coverage\n // peaks on the surface. Both scaled by strength and the live-slot gate.\n const insideCov = float(1)\n .sub(smoothstep(0, falloff, d))\n .mul(strength)\n .mul(gate);\n const rimCov = float(1)\n .sub(smoothstep(0, falloff, d.abs()))\n .mul(strength)\n .mul(gate);\n\n // Per-mode scalar masks (1 for the active mode, else 0). Applied by\n // folding the coverage with `mix`/multiply - NOT by a `select` that\n // reassigns `rgb`: three.js miscompiles a conditional whose branches\n // reuse the running value across an unrolled reassignment chain, so the\n // accumulation must stay pure arithmetic.\n const tintMask = mode.lessThan(0.5).select(float(1), float(0));\n const desatMask = mode.greaterThan(0.5).and(mode.lessThan(1.5)).select(float(1), float(0));\n const hideMask = mode.greaterThan(1.5).and(mode.lessThan(2.5)).select(float(1), float(0));\n const rimMask = mode.greaterThan(2.5).select(float(1), float(0));\n const luma = asNode<'float'>(rgb.dot(vec3(0.2126, 0.7152, 0.0722)));\n\n rgb = asNode<'vec3'>(mix(rgb, shapeColor, insideCov.mul(tintMask)));\n rgb = asNode<'vec3'>(mix(rgb, vec3(luma), insideCov.mul(desatMask)));\n rgb = asNode<'vec3'>(mix(rgb, shapeColor, rimCov.mul(rimMask)));\n alphaMul = asNode<'float'>(alphaMul.mul(float(1).sub(insideCov.mul(hideMask))));\n }\n\n return { color: vec4(rgb, ctx.color.a.mul(alphaMul)) };\n };\n\n return { modifier, setShapes, maxShapes, _uniforms: { slots, count } };\n}\n\n/** SDF of an axis-aligned box (shape-local) with half-extents `b`. */\nfunction sdBox(p: Node<'vec3'>, b: Node<'vec3'>): Node<'float'> {\n const q = p.abs().sub(b);\n const outside = q.max(vec3(0, 0, 0)).length();\n const inside = q.x.max(q.y.max(q.z)).min(0);\n return asNode<'float'>(outside.add(inside));\n}\n\n/** SDF of a Y-axis capped cylinder (shape-local): radius `r`, half-height `h`. */\nfunction sdCappedCylinder(p: Node<'vec3'>, r: Node<'float'>, h: Node<'float'>): Node<'float'> {\n const dr = p.xz.length().sub(r);\n const dy = p.y.abs().sub(h);\n const outside = vec2(dr.max(0), dy.max(0)).length();\n const inside = dr.max(dy).min(0);\n return asNode<'float'>(outside.add(inside));\n}\n\n/** Rotates `v` by the conjugate of quaternion `q` (xyzw): R⁻¹·v. */\nfunction rotateByQuatConj(q: Node<'vec4'>, v: Node<'vec3'>): Node<'vec3'> {\n const u = q.xyz;\n const s = q.w;\n const t = u.cross(v).mul(2);\n return asNode<'vec3'>(v.sub(t.mul(s)).add(u.cross(t)));\n}\n\n// --- Reference presets (M7.5) ----------------------------------------------\n\n/** A preset that exposes a live light direction uniform. */\nexport interface LightingPreset {\n readonly modifier: SplatModifier;\n /**\n * Mesh-local light direction; mutate `.value` to animate (no recompile).\n * Inside a `SplatScene` that frame is the scene's, so one direction lights\n * every source consistently however they are posed.\n */\n readonly direction: { value: THREE.Vector3 };\n}\n\n/**\n * Simple per-splat Lambertian shading using the splat's approximate normal\n * (`ctx.normal`, the least-variance covariance axis). A starting point for\n * relit product shots - not a lighting rig. Works on both backends.\n *\n * In a `SplatScene` the normal comes from the *placed* covariance, so a source\n * that rotates relights as it turns.\n */\nexport function lightingPreset(\n options: {\n direction?: readonly [number, number, number];\n ambient?: number;\n diffuse?: number;\n } = {},\n): LightingPreset {\n const dir = new THREE.Vector3(...(options.direction ?? [0.3, 1, 0.6])).normalize();\n const direction = uniform(dir);\n const ambient = float(options.ambient ?? 0.35);\n const diffuse = float(options.diffuse ?? 0.75);\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n const ndl = ctx.normal.dot(direction.normalize()).max(0);\n const shade = ambient.add(ndl.mul(diffuse));\n return { color: vec4(ctx.color.rgb.mul(shade), ctx.color.a) };\n };\n return { modifier, direction: direction };\n}\n\n/** A live depth-of-field preset: focus plane and aperture are mutable uniforms. */\nexport interface DepthOfFieldPreset {\n readonly modifier: SplatModifier;\n /** World-unit distance to the focal plane; splats there stay sharp. Mutate\n * `.value` to rack focus (no recompile). Must stay positive. */\n readonly focusDistance: { value: number };\n /** Blur strength (aperture-like); 0 is no blur. Mutate `.value`. */\n readonly aperture: { value: number };\n}\n\n/**\n * Depth-of-field on the {@link SplatModifier} hook - **stylized / soft** path\n * (M13). Prefer {@link SplatMesh.setDepthOfField} / the core projected-2D\n * aperture model for camera DoF (isotropic screen-space CoC with √det opacity).\n *\n * A splat's circle of confusion grows with how far its view-space depth\n * sits from `focusDistance`; the modifier blurs it by enlarging it\n * (`scale = 1 + coc`) and, because the material scales the projected covariance\n * by `scale²`, divides opacity by `scale²` so the widened splat conserves its\n * integrated mass instead of over-brightening. Focus and aperture are live\n * uniforms, so racking focus every frame costs no recompile. Pure TSL - works\n * on WebGPU and the WebGL2 fallback.\n *\n * This is an *approximate* DoF: `scale` is a uniform 3D-covariance multiplier,\n * so the blur enlarges the splat's own (possibly anisotropic) footprint rather\n * than adding a truly isotropic screen-space disc. The physically exact aperture\n * model adds `coc²·I` to the *projected 2D* covariance with the same √(det)\n * opacity conservation the antialias filter uses - that path is\n * `SplatMesh.setDepthOfField` / `UnifiedSplatRenderer.setDepthOfField`\n * (see `docs/guide/effects-and-modifiers.md`, M13).\n *\n * Opacity conservation is exact only with `antialias` on: without it, the\n * fixed 0.3-px dilation dominates sub-pixel splats, so strong blur *dims*\n * distant splats (their footprint barely grows while alpha still falls by\n * 1/scale²) rather than blurring them.\n */\nexport function depthOfFieldPreset(\n options: { focusDistance?: number; aperture?: number; maxBlur?: number } = {},\n): DepthOfFieldPreset {\n const focusDistance = uniform(Math.max(1e-4, options.focusDistance ?? 10));\n const aperture = uniform(Math.max(0, options.aperture ?? 0.5));\n // Cap the enlargement so a splat far behind the focus plane cannot balloon\n // to cover the screen (and its 1/scale² opacity cannot underflow to nothing).\n const maxBlur = float(Math.max(0, options.maxBlur ?? 6));\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n // View-space depth along the optical axis (camera looks down −z).\n const depth = ctx.viewCenter.z.negate().max(1e-4);\n // Clamped in-shader too: the constructor clamp cannot protect against a\n // later `focusDistance.value = 0`, which would divide by zero below and\n // NaN every splat's scale.\n const focus = focusDistance.max(1e-4);\n // Circle of confusion, normalized by the focus distance so it is scale-\n // invariant: 0 at the focal plane, growing with |depth − focus|.\n const coc = depth.sub(focus).abs().div(focus).mul(aperture).min(maxBlur);\n const blur = float(1).add(coc);\n // Conserve integrated opacity: the material scales area by blur², so peak\n // opacity must fall by 1/blur² or the blurred splat over-brightens. The\n // blur multiplies the running scale (and compensates only for its own\n // factor), so a preceding modifier's scale survives composition.\n const alpha = asNode<'float'>(ctx.color.a.div(blur.mul(blur)));\n return { scale: asNode<'float'>(ctx.scale.mul(blur)), color: vec4(ctx.color.rgb, alpha) };\n };\n return { modifier, focusDistance, aperture };\n}\n\n/** A preset that exposes a reveal `progress` uniform in `[0, 1]`. */\nexport interface RevealPreset {\n readonly modifier: SplatModifier;\n /** Reveal progress; 0 hides everything, 1 shows everything. */\n readonly progress: { value: number };\n}\n\n/**\n * Time-driven dissolve/reveal. A value-noise field over the splat's local\n * position is thresholded against `progress`: splats whose noise is below the\n * threshold are shown, with a soft alpha edge. The noise is a `wgslFn` chunk -\n * the M7 escape hatch - so this preset is **WebGPU-only** (see the fallback\n * notes); force WebGL2 and it will not compile.\n *\n * The field is anchored to the mesh, so in a `SplatScene` a moved source\n * reveals in step with whatever it now sits beside (and the pattern slides\n * across it as it travels) rather than carrying its own schedule around.\n */\nexport function revealPreset(options: { frequency?: number; edge?: number } = {}): RevealPreset {\n const progress = uniform(0);\n const frequency = options.frequency ?? 3;\n // The edge doubles as the threshold-range margin below, so it must stay in\n // [0, 0.5] for the remap to be well-formed.\n const edge = Math.min(Math.max(options.edge ?? 0.08, 0), 0.5);\n\n // Value noise as a single self-contained WGSL function: hash the 8 corners\n // of the lattice cell, then trilinearly interpolate with smoothstep weights\n // (g²(3 − 2g)). Every corner hash is a `fract` in [0, 1) and `mix` is a\n // convex combination, so the result is provably in [0, 1) - the reveal\n // threshold math below depends on that bound. The hashes are `let`-bound\n // locals so the chunk stays one function - three.js `wgslFn` binds call\n // inputs to the *first* function it finds, so a multi-function chunk would\n // need the entry first anyway; keeping it to one avoids that footgun.\n const vnoise = wgslFn(/* wgsl */ `\n fn vnoise( x: vec3<f32> ) -> f32 {\n let i = floor( x );\n let g = fract( x );\n let u = g * g * ( 3.0 - 2.0 * g );\n let k = vec3<f32>( 12.9898, 78.233, 37.719 );\n let s = 43758.5453;\n let h000 = fract( sin( dot( i + vec3<f32>( 0.0, 0.0, 0.0 ), k ) ) * s );\n let h100 = fract( sin( dot( i + vec3<f32>( 1.0, 0.0, 0.0 ), k ) ) * s );\n let h010 = fract( sin( dot( i + vec3<f32>( 0.0, 1.0, 0.0 ), k ) ) * s );\n let h110 = fract( sin( dot( i + vec3<f32>( 1.0, 1.0, 0.0 ), k ) ) * s );\n let h001 = fract( sin( dot( i + vec3<f32>( 0.0, 0.0, 1.0 ), k ) ) * s );\n let h101 = fract( sin( dot( i + vec3<f32>( 1.0, 0.0, 1.0 ), k ) ) * s );\n let h011 = fract( sin( dot( i + vec3<f32>( 0.0, 1.0, 1.0 ), k ) ) * s );\n let h111 = fract( sin( dot( i + vec3<f32>( 1.0, 1.0, 1.0 ), k ) ) * s );\n let x00 = mix( h000, h100, u.x );\n let x10 = mix( h010, h110, u.x );\n let x01 = mix( h001, h101, u.x );\n let x11 = mix( h011, h111, u.x );\n let y0 = mix( x00, x10, u.y );\n let y1 = mix( x01, x11, u.y );\n return mix( y0, y1, u.z );\n }\n `);\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n // Each splat gets a per-position threshold from the noise; it appears as\n // `progress` sweeps past that threshold, with a soft `edge`-wide band.\n // The noise is in [0, 1); remapping it to [edge, 1 − edge] keeps the whole\n // smoothstep band inside [0, 1], so progress 0 hides every splat fully and\n // progress 1 shows every splat fully - the documented contract.\n const noise = asNode<'float'>(vnoise({ x: ctx.localCenter.mul(frequency) }));\n const threshold = asNode<'float'>(noise.mul(1 - 2 * edge).add(edge));\n const alpha = smoothstep(threshold.sub(edge), threshold.add(edge), progress);\n const visible = progress.greaterThan(threshold.sub(edge));\n return { color: vec4(ctx.color.rgb, ctx.color.a.mul(alpha)), visible: asNode<'bool'>(visible) };\n };\n return { modifier, progress: progress };\n}\n\n/** A live world-warp preset: signed intensity and radius are mutable uniforms. */\nexport interface WorldWarpPreset {\n readonly modifier: SplatModifier;\n /**\n * Signed warp amount in `[-1, 1]`. Positive wraps the street *down* so you\n * walk on top of the sphere (tiny planet); negative wraps it *up* so you\n * stand in the bowl (Inception fold). `0` is identity. Mutate `.value` to\n * animate (no recompile).\n */\n readonly intensity: { value: number };\n /**\n * Distance at which the wrap becomes strong. Nearby splats (`depth ≪\n * radius`) stay put so walk/fly controls still read as moving through the\n * unwarped near field. Must stay positive. Mutate `.value`.\n */\n readonly radius: { value: number };\n}\n\n/**\n * Camera-centered splat warp on the {@link SplatModifier} hook: one signed\n * `intensity` uniform, two opposite hinges of the same view-space wrap. The\n * camera is not moved - only splat centers (and their rigid orientation)\n * change, so WASD / orbit stay as they are.\n *\n * Both sides hinge view-space `(y, z)` around camera +X by\n * `(π/2) · atan(depth / radius)`:\n *\n * - `intensity > 0` - planet: the far street wraps *down* under your feet so\n * you walk on top of the sphere.\n * - `intensity < 0` - fold: the far street wraps *up* over your head so you\n * stand in the bowl.\n * - `intensity = 0` is identity (`offset = 0`, rotation unchanged).\n *\n * The shader mixes each full-strength hinge toward the original position by\n * `|intensity|`. Orientation follows the orthonormalized Jacobian so\n * ellipsoids wrap with the surface rather than sliding. Pure TSL - WebGPU\n * and the WebGL2 fallback.\n *\n * Displaced splats keep their **pre-displacement** depth-sort order (the\n * modifier-stack contract). Extreme warps can composite in the wrong order;\n * this preset does not re-sort on warped centers.\n */\nexport function worldWarpPreset(\n options: { intensity?: number; radius?: number } = {},\n): WorldWarpPreset {\n const intensity = uniform(Math.min(1, Math.max(-1, options.intensity ?? 0)));\n const radius = uniform(Math.max(1e-4, options.radius ?? 1));\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n const k = asNode<'float'>(intensity.clamp(-1, 1));\n const R = asNode<'float'>(radius.max(1e-4));\n const v = ctx.viewCenter;\n const planetW = k.max(0);\n const foldW = k.negate().max(0);\n\n const depth = v.z.negate().max(0);\n const alpha = depth\n .div(R)\n .atan()\n .mul(float(Math.PI * 0.5));\n const cA = alpha.cos();\n const sFold = alpha.sin();\n const sPlanet = sFold.negate();\n const foldV = vec3(v.x, v.y.mul(cA).sub(v.z.mul(sFold)), v.y.mul(sFold).add(v.z.mul(cA)));\n const planetV = vec3(v.x, v.y.mul(cA).sub(v.z.mul(sPlanet)), v.y.mul(sPlanet).add(v.z.mul(cA)));\n\n const warpedV = mix(mix(v, foldV, foldW), planetV, planetW);\n\n const halfPiR = float(Math.PI * 0.5).mul(R);\n const dFoldDz = v.z\n .lessThan(0)\n .select(halfPiR.negate().div(depth.mul(depth).add(R.mul(R))), float(0));\n const dPlanetDz = dFoldDz.negate();\n const foldC0 = vec3(1, 0, 0);\n const foldC1 = vec3(0, cA, sFold);\n const foldC2 = vec3(0, sFold.negate().sub(foldV.z.mul(dFoldDz)), cA.add(foldV.y.mul(dFoldDz)));\n const planetC0 = vec3(1, 0, 0);\n const planetC1 = vec3(0, cA, sPlanet);\n const planetC2 = vec3(\n 0,\n sPlanet.negate().sub(planetV.z.mul(dPlanetDz)),\n cA.add(planetV.y.mul(dPlanetDz)),\n );\n\n const i0 = vec3(1, 0, 0);\n const i1 = vec3(0, 1, 0);\n const i2 = vec3(0, 0, 1);\n const j0 = mix(mix(i0, foldC0, foldW), planetC0, planetW);\n const j1 = mix(mix(i1, foldC1, foldW), planetC1, planetW);\n const j2 = mix(mix(i2, foldC2, foldW), planetC2, planetW);\n\n const r0 = j0.div(j0.length().max(1e-6));\n const r1u = j1.sub(r0.mul(r0.dot(j1)));\n const r1 = r1u.div(r1u.length().max(1e-6));\n const r2gs = j2.sub(r0.mul(r0.dot(j2))).sub(r1.mul(r1.dot(j2)));\n const r2 = r2gs.div(r2gs.length().max(1e-6));\n const qView = asNode<'mat3'>(mat3(r0, r1, r2));\n const viewR = asNode<'mat3'>(modelViewMatrix.toMat3());\n const viewRT = viewR.transpose();\n const warpR = asNode<'mat3'>(viewRT.mul(qView).mul(viewR));\n\n return {\n offset: asNode<'vec3'>(ctx.offset.add(viewRT.mul(warpedV.sub(v)))),\n rotation: asNode<'mat3'>(warpR.mul(ctx.rotation)),\n };\n };\n return { modifier, intensity, radius };\n}\n\n// --- Proxy-mesh relighting helper -------------------------------------------\n\n/** Handle returned by {@link createRelightingProxy}. */\nexport interface RelightingProxy {\n /** World-space group of lit proxy meshes (hide from the main splat scene). */\n readonly group: THREE.Group;\n /**\n * Configures a material for the relight coverage pass: gray albedo and\n * opaque alpha so uncovered RT pixels stay at clear-alpha 0.\n */\n configureMaterial(material: THREE.Material): void;\n /** Disposes geometries and the default material owned by this helper. */\n dispose(): void;\n}\n\n/**\n * Node material for a **shadow-factor** lighting RT used with\n * {@link SplatMesh.setRelighting}.\n *\n * Fragment output is `vec4(vec3(mix(umbra, 1, shadow(light))), 1)`:\n * - covered + lit → RGB ≈ 1 (identity modulate when brightness/background are 1)\n * - covered + umbra → RGB ≈ `umbra` (soft darken; never pure black)\n * - uncovered stays clear-alpha 0 from the RT clear\n *\n * Clear the lighting RT to **RGB 1, A 0** (not black). Softness / bilinear\n * samples at coverage edges otherwise pull in black and draw a dark outline\n * of every collision triangle.\n *\n * Prefer this over a lit MeshStandard / ShadowMaterial pass when the host wants\n * cast umbras without a static dark stamp on the whole collision footprint.\n * Assign to proxy meshes with `castShadow` and `receiveShadow` both true; the\n * umbra shape follows those triangles (splat foliage cannot cast).\n *\n * @param light - Shadow-casting light (typically a {@link THREE.DirectionalLight}).\n * @param options.umbra - Multiplier in full shadow, in `[0, 1]`. Default `0.45`.\n */\nexport function createRelightingShadowFactorMaterial(\n light: THREE.Light,\n options: { umbra?: number } = {},\n): THREE.MeshStandardNodeMaterial {\n const umbra = Number.isFinite(options.umbra)\n ? Math.min(1, Math.max(0, options.umbra as number))\n : 0.45;\n const material = new THREE.MeshStandardNodeMaterial();\n material.side = THREE.DoubleSide;\n material.color = new THREE.Color(1, 1, 1);\n material.roughness = 1;\n material.metalness = 0;\n material.transparent = false;\n material.depthWrite = true;\n // polygonOffset reduces self-shadow acne that reads as mesh wireframe.\n material.polygonOffset = true;\n material.polygonOffsetFactor = 1;\n material.polygonOffsetUnits = 1;\n // Bypass Lambert/PBR — only the light's shadow attenuation, floored so\n // umbra multiplies splats by `umbra` instead of crushing them to black.\n const attenuation = asNode<'float'>(shadow(light));\n const factor = mix(float(umbra), float(1), attenuation);\n material.outputNode = vec4(vec3(factor), float(1));\n return material;\n}\n\nfunction triangleMeshToGeometry(data: TriangleMeshData): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(data.positions.slice(), 3));\n geometry.setIndex(new THREE.BufferAttribute(data.indices.slice(), 1));\n // Normals are computed after any world bake in {@link createRelightingProxy}.\n return geometry;\n}\n\n/**\n * Builds a PlayCanvas-style relighting **proxy** from collision tiles or raw\n * geometries. The host places {@link RelightingProxy.group} on a dedicated\n * layer / scene with lights (never the main splat scene), renders that into\n * an RGBA RT matching the main camera, then calls\n * {@link SplatMesh.setRelighting} with the RT texture.\n *\n * Collision meshes are a convenient stand-in when their silhouette is good\n * enough; a denser reconstructed mesh is often better. This helper does not\n * own lights or the render target.\n *\n * When `matrixWorld` is set (e.g. streamed mesh world matrix with the LCC\n * Z-up→Y-up correction), it is **baked into the geometry** the same way the\n * demo collision BVH path does - so the proxy aligns with splats without\n * depending on a live Object3D transform.\n *\n * Coarse collision proxies leave hard coverage silhouettes; hosts should pass\n * `RelightingSettings.softness` (and often a half-res lighting RT) so those\n * edges do not look like a static shadow.\n *\n * For cast umbras **without** a static dark footprint, render the proxy with\n * {@link createRelightingShadowFactorMaterial} so RT RGB is a multiplier\n * (unshadowed ≈ 1), not a lit-gray appearance.\n */\nexport function createRelightingProxy(\n options: {\n /** LCC collision tiles (source-local). Prefer with `matrixWorld`. */\n tiles?: readonly CollisionMeshTile[];\n /** Extra or alternative geometries already in the desired frame. */\n geometries?: readonly THREE.BufferGeometry[];\n /**\n * Source-local → world (e.g. streamed mesh `matrixWorld`). Baked into\n * tile / cloned geometries; leave unset when geometries are already world.\n */\n matrixWorld?: THREE.Matrix4;\n /** Gray albedo in `[0, 1]`. Default `0.5` (PlayCanvas brightness 2). */\n albedo?: number;\n } = {},\n): RelightingProxy {\n const albedo = options.albedo ?? 0.5;\n const group = new THREE.Group();\n const worldMatrix = options.matrixWorld ?? null;\n\n const ownedGeometries: THREE.BufferGeometry[] = [];\n const material = new THREE.MeshStandardMaterial({\n color: new THREE.Color(albedo, albedo, albedo),\n roughness: 1,\n metalness: 0,\n side: THREE.DoubleSide,\n });\n\n const bakeWorld = (geometry: THREE.BufferGeometry): void => {\n if (!worldMatrix) return;\n geometry.applyMatrix4(worldMatrix);\n };\n\n const addGeometry = (geometry: THREE.BufferGeometry, owns: boolean): void => {\n let geo = geometry;\n let owned = owns;\n if (worldMatrix && !owns) {\n // Caller still owns the original; bake a private copy.\n geo = geometry.clone();\n owned = true;\n }\n bakeWorld(geo);\n geo.computeVertexNormals();\n if (owned) ownedGeometries.push(geo);\n const mesh = new THREE.Mesh(geo, material);\n mesh.castShadow = true;\n mesh.receiveShadow = true;\n // Proxy is lighting-only; never participate in picking / raycasts.\n mesh.raycast = () => undefined;\n group.add(mesh);\n };\n\n for (const tile of options.tiles ?? []) {\n addGeometry(triangleMeshToGeometry(tile.data), true);\n }\n for (const geometry of options.geometries ?? []) {\n addGeometry(geometry, false);\n }\n\n const configureMaterial = (mat: THREE.Material): void => {\n mat.side = THREE.DoubleSide;\n mat.transparent = false;\n mat.opacity = 1;\n if ('color' in mat && mat.color instanceof THREE.Color) {\n mat.color.setRGB(albedo, albedo, albedo);\n }\n if ('roughness' in mat && typeof mat.roughness === 'number') {\n (mat as THREE.MeshStandardMaterial).roughness = 1;\n }\n if ('metalness' in mat && typeof mat.metalness === 'number') {\n (mat as THREE.MeshStandardMaterial).metalness = 0;\n }\n };\n\n return {\n group,\n configureMaterial,\n dispose: () => {\n material.dispose();\n for (const geometry of ownedGeometries) geometry.dispose();\n group.clear();\n },\n };\n}\n"],"names":["SHAPE_STRIDE","MODE_INDEX","DEFAULT_CENTER","DEFAULT_COLOR","DEFAULT_ROTATION","sdfEffects","initialShapes","options","maxShapes","slots","i","THREE","shapesUniform","uniformArray","count","uniform","warnedOverflow","setShapes","shapes","n","warn","he","b","c","col","rot","kindIndex","ctx","p","rgb","alphaMul","float","k","el","j","s0","s1","s2","s3","s4","center","kind","size","falloff","shapeColor","mode","invert","strength","gate","int","rel","pl","rotateByQuatConj","dSphere","dBox","sdBox","dCylinder","sdCappedCylinder","boxMask","cylinderMask","dRaw","mix","d","insideCov","smoothstep","rimCov","tintMask","desatMask","hideMask","rimMask","luma","vec3","vec4","q","outside","inside","r","h","dr","dy","vec2","v","u","s","t","lightingPreset","dir","direction","ambient","diffuse","ndl","shade","depthOfFieldPreset","focusDistance","aperture","maxBlur","depth","focus","coc","blur","alpha","revealPreset","progress","frequency","edge","vnoise","wgslFn","noise","threshold","visible","worldWarpPreset","intensity","radius","R","planetW","foldW","cA","sFold","sPlanet","foldV","planetV","warpedV","halfPiR","dFoldDz","dPlanetDz","foldC0","foldC1","foldC2","planetC0","planetC1","planetC2","i0","i1","i2","j0","j1","j2","r0","r1u","r1","r2gs","r2","qView","mat3","viewR","modelViewMatrix","viewRT","warpR","createRelightingShadowFactorMaterial","light","umbra","material","attenuation","shadow","factor","triangleMeshToGeometry","data","geometry","createRelightingProxy","albedo","group","worldMatrix","ownedGeometries","bakeWorld","addGeometry","owns","geo","owned","mesh","tile","mat"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@voluma/vlam/formats/ksplat` - mkkellogg `.ksplat` parser.
|
|
3
|
+
*
|
|
4
|
+
* Loading through {@link loadScene} / {@link loadSceneFile} does not require
|
|
5
|
+
* this import; the loading worker already knows this format. Use this subpath
|
|
6
|
+
* for direct decode outside the default loaders.
|
|
7
|
+
*/
|
|
8
|
+
export { parseKsplat } from './parse-ksplat';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SplatData } from '../../splat-data';
|
|
2
|
+
/**
|
|
3
|
+
* Parses a GaussianSplats3D `.ksplat` file, including compression levels 0–2.
|
|
4
|
+
* Base color, opacity, position, scale, rotation and optional higher-order
|
|
5
|
+
* spherical harmonics (degrees 1–2) are retained.
|
|
6
|
+
*
|
|
7
|
+
* @throws {Error} if the header, section layout or record data is invalid.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseKsplat(buffer: ArrayBuffer): SplatData;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { h as x } from "../half-float-Cs4FDq8Q.js";
|
|
2
|
+
import { p as R } from "../sh-pack-D5wAe5gg.js";
|
|
3
|
+
import { w as M } from "../splat-data-BZM5dhhx.js";
|
|
4
|
+
const P = 4096, y = 1024, _ = 12, K = 1.5;
|
|
5
|
+
function V(t) {
|
|
6
|
+
const e = new DataView(t), n = N(e), r = O(e, n), u = r.reduce((c, g) => c + g.count, 0);
|
|
7
|
+
if (u !== n.splatCount)
|
|
8
|
+
throw new Error(
|
|
9
|
+
`Invalid KSPLAT counts: header declares ${n.splatCount}, sections contain ${u}.`
|
|
10
|
+
);
|
|
11
|
+
const s = new Float32Array(n.splatCount * 3), o = new Uint8Array(n.splatCount * 4), i = new Float32Array(n.splatCount * 6), f = r.reduce((c, g) => Math.max(c, g.shDegree), 0), h = F(f), l = h > 0 ? new Float32Array(n.splatCount * h) : null;
|
|
12
|
+
let p = 0;
|
|
13
|
+
for (const c of r) {
|
|
14
|
+
const g = D(e, c), m = F(c.shDegree), A = n.compressionLevel === 0 ? 44 : 24;
|
|
15
|
+
for (let U = 0; U < c.count; U++, p++) {
|
|
16
|
+
const a = c.dataBase + U * c.bytesPerSplat, S = p * 3, C = p * 4, w = n.compressionLevel === 0 ? 12 : 6, B = n.compressionLevel === 0 ? 24 : 12, $ = n.compressionLevel === 0 ? 40 : 20;
|
|
17
|
+
if (n.compressionLevel === 0)
|
|
18
|
+
s[S + 0] = e.getFloat32(a + 0, !0), s[S + 1] = e.getFloat32(a + 4, !0), s[S + 2] = e.getFloat32(a + 8, !0);
|
|
19
|
+
else {
|
|
20
|
+
const k = Y(U, c, g), d = c.bucketsBase + k * c.bucketStorageBytes, T = c.bucketBlockSize * 0.5 / c.compressionRange;
|
|
21
|
+
s[S + 0] = (e.getUint16(a + 0, !0) - c.compressionRange) * T + e.getFloat32(d + 0, !0), s[S + 1] = (e.getUint16(a + 2, !0) - c.compressionRange) * T + e.getFloat32(d + 4, !0), s[S + 2] = (e.getUint16(a + 4, !0) - c.compressionRange) * T + e.getFloat32(d + 8, !0);
|
|
22
|
+
}
|
|
23
|
+
o[C + 0] = e.getUint8(a + $ + 0), o[C + 1] = e.getUint8(a + $ + 1), o[C + 2] = e.getUint8(a + $ + 2), o[C + 3] = e.getUint8(a + $ + 3);
|
|
24
|
+
const b = (k) => n.compressionLevel === 0 ? e.getFloat32(a + k, !0) : x(e.getUint16(a + k, !0)), L = n.compressionLevel === 0 ? 4 : 2;
|
|
25
|
+
if (M(
|
|
26
|
+
i,
|
|
27
|
+
p,
|
|
28
|
+
b(w + 0 * L),
|
|
29
|
+
b(w + 1 * L),
|
|
30
|
+
b(w + 2 * L),
|
|
31
|
+
b(B + 0 * L),
|
|
32
|
+
b(B + 1 * L),
|
|
33
|
+
b(B + 2 * L),
|
|
34
|
+
b(B + 3 * L)
|
|
35
|
+
), l && m > 0) {
|
|
36
|
+
const k = p * h;
|
|
37
|
+
for (let d = 0; d < m; d++) {
|
|
38
|
+
const T = A + d * I(n.compressionLevel);
|
|
39
|
+
l[k + d] = H(
|
|
40
|
+
e,
|
|
41
|
+
a + T,
|
|
42
|
+
n.compressionLevel,
|
|
43
|
+
n.shCoeffMin,
|
|
44
|
+
n.shCoeffMax
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const E = l && f > 0 ? R(l, n.splatCount, f) : void 0;
|
|
51
|
+
return {
|
|
52
|
+
count: n.splatCount,
|
|
53
|
+
positions: s,
|
|
54
|
+
colors: o,
|
|
55
|
+
covariances: i,
|
|
56
|
+
...E ? { shPacked: E } : {}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function I(t) {
|
|
60
|
+
return t === 0 ? 4 : t === 1 ? 2 : 1;
|
|
61
|
+
}
|
|
62
|
+
function H(t, e, n, r, u) {
|
|
63
|
+
return n === 0 ? t.getFloat32(e, !0) : n === 1 ? x(t.getUint16(e, !0)) : t.getUint8(e) / 255 * (u - r) + r;
|
|
64
|
+
}
|
|
65
|
+
function F(t) {
|
|
66
|
+
if (t === 0) return 0;
|
|
67
|
+
if (t === 1) return 9;
|
|
68
|
+
if (t === 2) return 24;
|
|
69
|
+
throw new Error(`Unsupported KSPLAT SH degree ${t}.`);
|
|
70
|
+
}
|
|
71
|
+
function N(t) {
|
|
72
|
+
if (t.byteLength < P) throw new Error("Truncated KSPLAT header.");
|
|
73
|
+
const e = t.getUint8(0), n = t.getUint8(1), r = t.getUint32(4, !0), u = t.getUint32(8, !0), s = t.getUint32(12, !0), o = t.getUint32(16, !0), i = t.getUint16(20, !0), f = t.getFloat32(36, !0) || -K, h = t.getFloat32(40, !0) || K;
|
|
74
|
+
if (e !== 0 || n < 1)
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Unsupported KSPLAT version ${e}.${n}; expected 0.1 or newer.`
|
|
77
|
+
);
|
|
78
|
+
if (i < 0 || i > 2)
|
|
79
|
+
throw new Error(`Unsupported KSPLAT compression level ${i}.`);
|
|
80
|
+
if (!Number.isSafeInteger(r) || u > r || u === 0)
|
|
81
|
+
throw new Error(`Invalid KSPLAT section count ${u}/${r}.`);
|
|
82
|
+
if (o > s)
|
|
83
|
+
throw new Error(`Invalid KSPLAT splat count ${o}/${s}.`);
|
|
84
|
+
const l = P + r * y;
|
|
85
|
+
if (!Number.isSafeInteger(l) || l > t.byteLength)
|
|
86
|
+
throw new Error("Truncated KSPLAT section headers.");
|
|
87
|
+
return {
|
|
88
|
+
versionMajor: e,
|
|
89
|
+
versionMinor: n,
|
|
90
|
+
sectionCount: u,
|
|
91
|
+
splatCount: o,
|
|
92
|
+
compressionLevel: i,
|
|
93
|
+
shCoeffMin: f,
|
|
94
|
+
shCoeffMax: h
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function O(t, e) {
|
|
98
|
+
const n = t.getUint32(4, !0);
|
|
99
|
+
let r = P + n * y;
|
|
100
|
+
const u = [];
|
|
101
|
+
for (let s = 0; s < n; s++) {
|
|
102
|
+
const o = P + s * y, i = t.getUint32(o + 0, !0), f = t.getUint32(o + 4, !0), h = t.getUint32(o + 8, !0), l = t.getUint32(o + 12, !0), p = t.getFloat32(o + 16, !0), E = t.getUint16(o + 20, !0), c = t.getUint32(o + 24, !0) || 32767, g = t.getUint32(o + 32, !0), m = t.getUint32(o + 36, !0), A = t.getUint16(o + 40, !0), U = z(e.compressionLevel, A), a = m * 4, S = l * E, C = a + S + f * U;
|
|
103
|
+
if (s < e.sectionCount) {
|
|
104
|
+
if (i > f)
|
|
105
|
+
throw new Error(`Invalid KSPLAT section ${s} count ${i}/${f}.`);
|
|
106
|
+
if (e.compressionLevel > 0) {
|
|
107
|
+
if (h === 0 || l === 0 || E < _)
|
|
108
|
+
throw new Error(`Invalid KSPLAT bucket layout in section ${s}.`);
|
|
109
|
+
if (!Number.isFinite(p) || p <= 0 || c === 0)
|
|
110
|
+
throw new Error(`Invalid KSPLAT compression parameters in section ${s}.`);
|
|
111
|
+
if (g + m > l)
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Invalid KSPLAT section ${s}: ${g} full + ${m} partial buckets exceed the ${l} declared.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (r + C > t.byteLength)
|
|
117
|
+
throw new Error(`Truncated KSPLAT data in section ${s}.`);
|
|
118
|
+
u.push({
|
|
119
|
+
count: i,
|
|
120
|
+
maxCount: f,
|
|
121
|
+
bucketSize: h,
|
|
122
|
+
bucketCount: l,
|
|
123
|
+
bucketBlockSize: p,
|
|
124
|
+
bucketStorageBytes: E,
|
|
125
|
+
compressionRange: c,
|
|
126
|
+
fullBucketCount: g,
|
|
127
|
+
partialBucketCount: m,
|
|
128
|
+
shDegree: A,
|
|
129
|
+
bytesPerSplat: U,
|
|
130
|
+
base: r,
|
|
131
|
+
bucketsBase: r + a,
|
|
132
|
+
dataBase: r + a + S
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
r += C;
|
|
136
|
+
}
|
|
137
|
+
return u;
|
|
138
|
+
}
|
|
139
|
+
function z(t, e) {
|
|
140
|
+
if (e < 0 || e > 2) throw new Error(`Unsupported KSPLAT SH degree ${e}.`);
|
|
141
|
+
const n = F(e);
|
|
142
|
+
return t === 0 ? 44 + n * 4 : 24 + n * I(t);
|
|
143
|
+
}
|
|
144
|
+
function D(t, e) {
|
|
145
|
+
const n = new Uint32Array(e.partialBucketCount);
|
|
146
|
+
for (let r = 0; r < n.length; r++) n[r] = t.getUint32(e.base + r * 4, !0);
|
|
147
|
+
return n;
|
|
148
|
+
}
|
|
149
|
+
function Y(t, e, n) {
|
|
150
|
+
const r = e.fullBucketCount * e.bucketSize, u = (o) => {
|
|
151
|
+
if (o >= e.bucketCount)
|
|
152
|
+
throw new Error(`KSPLAT splat ${t} maps to out-of-range bucket ${o}.`);
|
|
153
|
+
return o;
|
|
154
|
+
};
|
|
155
|
+
if (t < r) return u(Math.floor(t / e.bucketSize));
|
|
156
|
+
let s = r;
|
|
157
|
+
for (let o = 0; o < n.length; o++) {
|
|
158
|
+
const i = n[o];
|
|
159
|
+
if (t < s + i) return u(e.fullBucketCount + o);
|
|
160
|
+
s += i;
|
|
161
|
+
}
|
|
162
|
+
throw new Error(`KSPLAT splat ${t} does not belong to a bucket.`);
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
V as parseKsplat
|
|
166
|
+
};
|
|
167
|
+
//# sourceMappingURL=ksplat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ksplat.js","sources":["../../src/lib/formats/ksplat/parse-ksplat.ts"],"sourcesContent":["import { halfToFloat } from '../../half-float';\nimport { packShCoefficients } from '../../sh-pack';\nimport { writeCovariance, type SplatData } from '../../splat-data';\n\nconst HEADER_BYTES = 4096;\nconst SECTION_HEADER_BYTES = 1024;\nconst BUCKET_CENTER_BYTES = 12;\n/** Default 8-bit SH dequant range half-width (GaussianSplats3D `Constants`). */\nconst DEFAULT_SH_RANGE_HALF = 1.5;\n\ninterface KsplatHeader {\n versionMajor: number;\n versionMinor: number;\n sectionCount: number;\n splatCount: number;\n compressionLevel: number;\n shCoeffMin: number;\n shCoeffMax: number;\n}\n\ninterface KsplatSection {\n count: number;\n maxCount: number;\n bucketSize: number;\n bucketCount: number;\n bucketBlockSize: number;\n bucketStorageBytes: number;\n compressionRange: number;\n fullBucketCount: number;\n partialBucketCount: number;\n shDegree: number;\n bytesPerSplat: number;\n base: number;\n bucketsBase: number;\n dataBase: number;\n}\n\n/**\n * Parses a GaussianSplats3D `.ksplat` file, including compression levels 0–2.\n * Base color, opacity, position, scale, rotation and optional higher-order\n * spherical harmonics (degrees 1–2) are retained.\n *\n * @throws {Error} if the header, section layout or record data is invalid.\n */\nexport function parseKsplat(buffer: ArrayBuffer): SplatData {\n const view = new DataView(buffer);\n const header = parseHeader(view);\n const sections = parseSections(view, header);\n const totalSectionSplats = sections.reduce((sum, section) => sum + section.count, 0);\n if (totalSectionSplats !== header.splatCount) {\n throw new Error(\n `Invalid KSPLAT counts: header declares ${header.splatCount}, sections contain ${totalSectionSplats}.`,\n );\n }\n\n const positions = new Float32Array(header.splatCount * 3);\n const colors = new Uint8Array(header.splatCount * 4);\n const covariances = new Float32Array(header.splatCount * 6);\n const maxShDegree = sections.reduce((max, section) => Math.max(max, section.shDegree), 0);\n const shComponents = shComponentsForDegree(maxShDegree);\n const coefficients = shComponents > 0 ? new Float32Array(header.splatCount * shComponents) : null;\n let outputIndex = 0;\n\n for (const section of sections) {\n const partialLengths = readPartialBucketLengths(view, section);\n const sectionShComponents = shComponentsForDegree(section.shDegree);\n const shOffset = header.compressionLevel === 0 ? 44 : 24;\n for (let localIndex = 0; localIndex < section.count; localIndex++, outputIndex++) {\n const source = section.dataBase + localIndex * section.bytesPerSplat;\n const position = outputIndex * 3;\n const color = outputIndex * 4;\n const scaleOffset = header.compressionLevel === 0 ? 12 : 6;\n const rotationOffset = header.compressionLevel === 0 ? 24 : 12;\n const colorOffset = header.compressionLevel === 0 ? 40 : 20;\n\n if (header.compressionLevel === 0) {\n positions[position + 0] = view.getFloat32(source + 0, true);\n positions[position + 1] = view.getFloat32(source + 4, true);\n positions[position + 2] = view.getFloat32(source + 8, true);\n } else {\n const bucket = bucketIndexForSplat(localIndex, section, partialLengths);\n const bucketBase = section.bucketsBase + bucket * section.bucketStorageBytes;\n const factor = (section.bucketBlockSize * 0.5) / section.compressionRange;\n positions[position + 0] =\n (view.getUint16(source + 0, true) - section.compressionRange) * factor +\n view.getFloat32(bucketBase + 0, true);\n positions[position + 1] =\n (view.getUint16(source + 2, true) - section.compressionRange) * factor +\n view.getFloat32(bucketBase + 4, true);\n positions[position + 2] =\n (view.getUint16(source + 4, true) - section.compressionRange) * factor +\n view.getFloat32(bucketBase + 8, true);\n }\n\n colors[color + 0] = view.getUint8(source + colorOffset + 0);\n colors[color + 1] = view.getUint8(source + colorOffset + 1);\n colors[color + 2] = view.getUint8(source + colorOffset + 2);\n colors[color + 3] = view.getUint8(source + colorOffset + 3);\n\n const readScalar = (offset: number): number =>\n header.compressionLevel === 0\n ? view.getFloat32(source + offset, true)\n : halfToFloat(view.getUint16(source + offset, true));\n const scalarBytes = header.compressionLevel === 0 ? 4 : 2;\n writeCovariance(\n covariances,\n outputIndex,\n readScalar(scaleOffset + 0 * scalarBytes),\n readScalar(scaleOffset + 1 * scalarBytes),\n readScalar(scaleOffset + 2 * scalarBytes),\n readScalar(rotationOffset + 0 * scalarBytes),\n readScalar(rotationOffset + 1 * scalarBytes),\n readScalar(rotationOffset + 2 * scalarBytes),\n readScalar(rotationOffset + 3 * scalarBytes),\n );\n\n if (coefficients && sectionShComponents > 0) {\n const dest = outputIndex * shComponents;\n for (let c = 0; c < sectionShComponents; c++) {\n const byteOffset = shOffset + c * shScalarBytes(header.compressionLevel);\n coefficients[dest + c] = readShScalar(\n view,\n source + byteOffset,\n header.compressionLevel,\n header.shCoeffMin,\n header.shCoeffMax,\n );\n }\n }\n }\n }\n\n const shPacked =\n coefficients && maxShDegree > 0\n ? packShCoefficients(coefficients, header.splatCount, maxShDegree as 1 | 2)\n : undefined;\n\n return {\n count: header.splatCount,\n positions,\n colors,\n covariances,\n ...(shPacked ? { shPacked } : {}),\n };\n}\n\nfunction shScalarBytes(compressionLevel: number): number {\n return compressionLevel === 0 ? 4 : compressionLevel === 1 ? 2 : 1;\n}\n\nfunction readShScalar(\n view: DataView,\n offset: number,\n compressionLevel: number,\n rangeMin: number,\n rangeMax: number,\n): number {\n if (compressionLevel === 0) return view.getFloat32(offset, true);\n if (compressionLevel === 1) return halfToFloat(view.getUint16(offset, true));\n const encoded = view.getUint8(offset);\n return (encoded / 255) * (rangeMax - rangeMin) + rangeMin;\n}\n\nfunction shComponentsForDegree(shDegree: number): number {\n if (shDegree === 0) return 0;\n if (shDegree === 1) return 9;\n if (shDegree === 2) return 24;\n throw new Error(`Unsupported KSPLAT SH degree ${shDegree}.`);\n}\n\nfunction parseHeader(view: DataView): KsplatHeader {\n if (view.byteLength < HEADER_BYTES) throw new Error('Truncated KSPLAT header.');\n const versionMajor = view.getUint8(0);\n const versionMinor = view.getUint8(1);\n const maxSectionCount = view.getUint32(4, true);\n const sectionCount = view.getUint32(8, true);\n const maxSplatCount = view.getUint32(12, true);\n const splatCount = view.getUint32(16, true);\n const compressionLevel = view.getUint16(20, true);\n const shCoeffMin = view.getFloat32(36, true) || -DEFAULT_SH_RANGE_HALF;\n const shCoeffMax = view.getFloat32(40, true) || DEFAULT_SH_RANGE_HALF;\n\n if (versionMajor !== 0 || versionMinor < 1) {\n throw new Error(\n `Unsupported KSPLAT version ${versionMajor}.${versionMinor}; expected 0.1 or newer.`,\n );\n }\n if (compressionLevel < 0 || compressionLevel > 2) {\n throw new Error(`Unsupported KSPLAT compression level ${compressionLevel}.`);\n }\n if (\n !Number.isSafeInteger(maxSectionCount) ||\n sectionCount > maxSectionCount ||\n sectionCount === 0\n ) {\n throw new Error(`Invalid KSPLAT section count ${sectionCount}/${maxSectionCount}.`);\n }\n if (splatCount > maxSplatCount) {\n throw new Error(`Invalid KSPLAT splat count ${splatCount}/${maxSplatCount}.`);\n }\n const headersEnd = HEADER_BYTES + maxSectionCount * SECTION_HEADER_BYTES;\n if (!Number.isSafeInteger(headersEnd) || headersEnd > view.byteLength) {\n throw new Error('Truncated KSPLAT section headers.');\n }\n\n return {\n versionMajor,\n versionMinor,\n sectionCount,\n splatCount,\n compressionLevel,\n shCoeffMin,\n shCoeffMax,\n };\n}\n\nfunction parseSections(view: DataView, header: KsplatHeader): KsplatSection[] {\n const maxSectionCount = view.getUint32(4, true);\n let sectionBase = HEADER_BYTES + maxSectionCount * SECTION_HEADER_BYTES;\n const sections: KsplatSection[] = [];\n\n for (let index = 0; index < maxSectionCount; index++) {\n const offset = HEADER_BYTES + index * SECTION_HEADER_BYTES;\n const count = view.getUint32(offset + 0, true);\n const maxCount = view.getUint32(offset + 4, true);\n const bucketSize = view.getUint32(offset + 8, true);\n const bucketCount = view.getUint32(offset + 12, true);\n const bucketBlockSize = view.getFloat32(offset + 16, true);\n const bucketStorageBytes = view.getUint16(offset + 20, true);\n const compressionRange = view.getUint32(offset + 24, true) || 32767;\n const fullBucketCount = view.getUint32(offset + 32, true);\n const partialBucketCount = view.getUint32(offset + 36, true);\n const shDegree = view.getUint16(offset + 40, true);\n const bytesPerSplat = bytesPerKsplat(header.compressionLevel, shDegree);\n const bucketMetadataBytes = partialBucketCount * 4;\n const bucketBytes = bucketCount * bucketStorageBytes;\n const storageBytes = bucketMetadataBytes + bucketBytes + maxCount * bytesPerSplat;\n\n if (index < header.sectionCount) {\n if (count > maxCount)\n throw new Error(`Invalid KSPLAT section ${index} count ${count}/${maxCount}.`);\n if (header.compressionLevel > 0) {\n if (bucketSize === 0 || bucketCount === 0 || bucketStorageBytes < BUCKET_CENTER_BYTES) {\n throw new Error(`Invalid KSPLAT bucket layout in section ${index}.`);\n }\n if (!Number.isFinite(bucketBlockSize) || bucketBlockSize <= 0 || compressionRange === 0) {\n throw new Error(`Invalid KSPLAT compression parameters in section ${index}.`);\n }\n if (fullBucketCount + partialBucketCount > bucketCount) {\n throw new Error(\n `Invalid KSPLAT section ${index}: ${fullBucketCount} full + ` +\n `${partialBucketCount} partial buckets exceed the ${bucketCount} declared.`,\n );\n }\n }\n if (sectionBase + storageBytes > view.byteLength) {\n throw new Error(`Truncated KSPLAT data in section ${index}.`);\n }\n sections.push({\n count,\n maxCount,\n bucketSize,\n bucketCount,\n bucketBlockSize,\n bucketStorageBytes,\n compressionRange,\n fullBucketCount,\n partialBucketCount,\n shDegree,\n bytesPerSplat,\n base: sectionBase,\n bucketsBase: sectionBase + bucketMetadataBytes,\n dataBase: sectionBase + bucketMetadataBytes + bucketBytes,\n });\n }\n sectionBase += storageBytes;\n }\n return sections;\n}\n\nfunction bytesPerKsplat(compressionLevel: number, shDegree: number): number {\n if (shDegree < 0 || shDegree > 2) throw new Error(`Unsupported KSPLAT SH degree ${shDegree}.`);\n const shComponents = shComponentsForDegree(shDegree);\n if (compressionLevel === 0) return 44 + shComponents * 4;\n return 24 + shComponents * shScalarBytes(compressionLevel);\n}\n\nfunction readPartialBucketLengths(view: DataView, section: KsplatSection): Uint32Array {\n const lengths = new Uint32Array(section.partialBucketCount);\n for (let i = 0; i < lengths.length; i++) lengths[i] = view.getUint32(section.base + i * 4, true);\n return lengths;\n}\n\nfunction bucketIndexForSplat(\n localIndex: number,\n section: KsplatSection,\n partialLengths: Uint32Array,\n): number {\n const fullSplats = section.fullBucketCount * section.bucketSize;\n const checked = (bucket: number): number => {\n // Belt-and-braces: a bucket index at or past bucketCount would read\n // bucket centers out of the section's storage (OOB DataView reads or\n // garbage positions from a hostile file).\n if (bucket >= section.bucketCount) {\n throw new Error(`KSPLAT splat ${localIndex} maps to out-of-range bucket ${bucket}.`);\n }\n return bucket;\n };\n if (localIndex < fullSplats) return checked(Math.floor(localIndex / section.bucketSize));\n let first = fullSplats;\n for (let i = 0; i < partialLengths.length; i++) {\n const length = partialLengths[i] as number;\n if (localIndex < first + length) return checked(section.fullBucketCount + i);\n first += length;\n }\n throw new Error(`KSPLAT splat ${localIndex} does not belong to a bucket.`);\n}\n"],"names":["HEADER_BYTES","SECTION_HEADER_BYTES","BUCKET_CENTER_BYTES","DEFAULT_SH_RANGE_HALF","parseKsplat","buffer","view","header","parseHeader","sections","parseSections","totalSectionSplats","sum","section","positions","colors","covariances","maxShDegree","max","shComponents","shComponentsForDegree","coefficients","outputIndex","partialLengths","readPartialBucketLengths","sectionShComponents","shOffset","localIndex","source","position","color","scaleOffset","rotationOffset","colorOffset","bucket","bucketIndexForSplat","bucketBase","factor","readScalar","offset","halfToFloat","scalarBytes","writeCovariance","dest","c","byteOffset","shScalarBytes","readShScalar","shPacked","packShCoefficients","compressionLevel","rangeMin","rangeMax","shDegree","versionMajor","versionMinor","maxSectionCount","sectionCount","maxSplatCount","splatCount","shCoeffMin","shCoeffMax","headersEnd","sectionBase","index","count","maxCount","bucketSize","bucketCount","bucketBlockSize","bucketStorageBytes","compressionRange","fullBucketCount","partialBucketCount","bytesPerSplat","bytesPerKsplat","bucketMetadataBytes","bucketBytes","storageBytes","lengths","i","fullSplats","checked","first","length"],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type * as THREE from 'three/webgpu';
|
|
2
|
+
import { type SplatRequestOptions } from '../../loading';
|
|
3
|
+
import { type TriangleMeshData } from './parse-mesh-ply';
|
|
4
|
+
import type { SceneCollision } from '../../lod-source';
|
|
5
|
+
/**
|
|
6
|
+
* Fetches the collision-mesh tiles a streamed scene declares.
|
|
7
|
+
*
|
|
8
|
+
* VLAM! is not a physics engine: this hands back plain triangle geometry and
|
|
9
|
+
* stops there. Building an acceleration structure, resolving a character
|
|
10
|
+
* against it, and deciding what "collision" means for the host's camera are
|
|
11
|
+
* all the host's business (see the demo's `collision.ts` for one answer).
|
|
12
|
+
*
|
|
13
|
+
* Descriptors may point at `.lcc2` mesh PLYs or a classic-LCC `collision.lci`
|
|
14
|
+
* (one file → many per-cell tiles).
|
|
15
|
+
*/
|
|
16
|
+
/** One loaded collision tile, in the scene's source-local frame. */
|
|
17
|
+
export interface CollisionMeshTile {
|
|
18
|
+
/** The URL it came from - a stable identity for caching or debugging. */
|
|
19
|
+
readonly url: string;
|
|
20
|
+
readonly data: TriangleMeshData;
|
|
21
|
+
/** Source-local bounds from the manifest, when it declared them. */
|
|
22
|
+
readonly bounds?: THREE.Box3;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fetches and parses every tile in `collision`, concurrently.
|
|
26
|
+
*
|
|
27
|
+
* A tile that fails to fetch or parse is warned about and skipped rather than
|
|
28
|
+
* failing the batch: collision degrades to partial coverage, which is what the
|
|
29
|
+
* rest of the streaming pipeline does with a bad chunk too. A classic-LCC
|
|
30
|
+
* `.lci` descriptor expands into one tile per cell mesh inside the file.
|
|
31
|
+
*
|
|
32
|
+
* @throws a `DOMException` named `AbortError` when cancelled.
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadCollisionMeshTiles(collision: SceneCollision, options?: {
|
|
35
|
+
request?: SplatRequestOptions;
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}): Promise<CollisionMeshTile[]>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { TriangleMeshData } from './parse-mesh-ply';
|
|
2
|
+
import type { SelectionVolume } from '../../selection-volume';
|
|
3
|
+
/**
|
|
4
|
+
* Splits a collision triangle mesh by the same {@link SelectionVolume} used to
|
|
5
|
+
* separate a splat region, so the separated object keeps its collision
|
|
6
|
+
* geometry and both halves stay usable by a host's collision system.
|
|
7
|
+
*
|
|
8
|
+
* Collision tiles arrive in the dataset's source-local frame (see
|
|
9
|
+
* `parse-mesh-ply.ts`), which for `.lcc2` is the frame the mesh matrix maps to
|
|
10
|
+
* world - the same frame `SplatData.positions` use. So a volume built for the
|
|
11
|
+
* splats partitions the collision mesh with no extra transform, as long as the
|
|
12
|
+
* host holds both in that one frame.
|
|
13
|
+
*/
|
|
14
|
+
export interface TriangleMeshPartition {
|
|
15
|
+
/** Triangles assigned to the selection. */
|
|
16
|
+
readonly inside: TriangleMeshData;
|
|
17
|
+
/** Everything else. */
|
|
18
|
+
readonly outside: TriangleMeshData;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Partitions a triangle mesh by a selection volume.
|
|
22
|
+
*
|
|
23
|
+
* A triangle goes to `inside` when **any** of its vertices is inside the
|
|
24
|
+
* volume. Triangles are never clipped, so the two halves tile the original
|
|
25
|
+
* exactly (no cracks, no new geometry); the trade-off is that boundary
|
|
26
|
+
* triangles overhang the volume by up to one triangle. Vertex winding is
|
|
27
|
+
* preserved. Vertex buffers are compacted per half - shared vertices are
|
|
28
|
+
* duplicated into whichever halves reference them.
|
|
29
|
+
*
|
|
30
|
+
* @throws {Error} if `mesh` is internally inconsistent (array lengths that
|
|
31
|
+
* disagree with the declared counts, or an out-of-range index). {@link parseMeshPly}
|
|
32
|
+
* already guarantees this; a host-built mesh is checked here rather than
|
|
33
|
+
* silently producing `NaN` positions.
|
|
34
|
+
*/
|
|
35
|
+
export declare function partitionTriangleMesh(mesh: TriangleMeshData, volume: SelectionVolume): TriangleMeshPartition;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@voluma/vlam/formats/lcc` - XGRIDS LCC (`.lcc`, manifest v3–v5) / `.lcc2` builders, sidecars, and
|
|
3
|
+
* collision-mesh helpers.
|
|
4
|
+
*
|
|
5
|
+
* Loading through {@link StreamedSplatMesh.load} does not require this import;
|
|
6
|
+
* the library pulls the format in on demand. Use this subpath for direct
|
|
7
|
+
* manifest inspection or collision-tile decoding.
|
|
8
|
+
*/
|
|
9
|
+
export { parseLccManifest, parseLccIndex, parseLccChunk, type LccManifest, type LccIndexCell, type LccChunkParams, } from './parse-lcc';
|
|
10
|
+
export { buildLccScene, type LccSceneOptions } from './lcc';
|
|
11
|
+
export { buildLcc2Scene } from './lcc2';
|
|
12
|
+
export { createLcc2ToThreeMatrix, applyLcc2ToThreeTransform } from './lcc2-transform';
|
|
13
|
+
export { loadCollisionMeshTiles, type CollisionMeshTile } from './collision-mesh';
|
|
14
|
+
export { parseMeshPly, type TriangleMeshData } from './parse-mesh-ply';
|
|
15
|
+
export { parseCollisionLci, type CollisionLciFile, type CollisionLciTile, } from './parse-collision-lci';
|
|
16
|
+
export { partitionTriangleMesh, type TriangleMeshPartition } from './collision-partition';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { LodSourceOptions, StreamedScene } from '../../lod-source';
|
|
2
|
+
import { type LccManifest } from './parse-lcc';
|
|
3
|
+
import { type SplatRequestOptions } from '../../loading';
|
|
4
|
+
import type { SplatDatasetSource } from '../../dataset-source';
|
|
5
|
+
/** Options for {@link buildLccScene} beyond the shared LOD knobs. */
|
|
6
|
+
export interface LccSceneOptions extends LodSourceOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Higher-order SH bands to stream for a `Quality` capture: 0 disables SH
|
|
9
|
+
* (nothing is fetched from `shcoef.bin`), 3 is the full 3rd order.
|
|
10
|
+
* Unset means every band the capture carries. Always clamped to that -
|
|
11
|
+
* a `Portable` capture has none, whatever is asked for.
|
|
12
|
+
*/
|
|
13
|
+
shBands?: 0 | 1 | 2 | 3;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
request?: SplatRequestOptions;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Builds a streamed scene from a parsed `.lcc` manifest, fetching the small
|
|
19
|
+
* sidecars (`index.bin`, and `environment.bin`'s size) it needs to lay out
|
|
20
|
+
* chunks. `data.bin` itself is never fetched here.
|
|
21
|
+
*
|
|
22
|
+
* @param json - The parsed `.lcc` manifest.
|
|
23
|
+
* @param source - Dataset used to resolve and fetch the manifest's sidecars.
|
|
24
|
+
* @param options - LOD, request, cancellation, and spherical-harmonic options.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildLccScene(json: unknown, source: SplatDatasetSource, options: LccSceneOptions): Promise<StreamedScene>;
|
|
27
|
+
/** Bytes per `environment.bin` record: Quality interleaves a 64-byte SH block. */
|
|
28
|
+
export declare function environmentStride(manifest: LccManifest): number;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
/**
|
|
3
|
+
* LCC2 / XGRIDS captures are Z-up. Three.js (and this package's SOG/PLY path)
|
|
4
|
+
* expects Y-up. The established LCC/Spark convention maps raw LCC coordinates
|
|
5
|
+
* `(x, y, z)` → `(-x, z, y)` - equivalent to −90° about X followed by 180°
|
|
6
|
+
* about Y. Kept as a single matrix so consumers cannot accidentally apply only
|
|
7
|
+
* half of the correction.
|
|
8
|
+
*
|
|
9
|
+
* Basis check: +X → −X, +Y → +Z, +Z → +Y.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createLcc2ToThreeMatrix(): THREE.Matrix4;
|
|
12
|
+
/**
|
|
13
|
+
* Applies the LCC2→Three.js format correction to a mesh. Bounds and LOD data
|
|
14
|
+
* stay in source-local coordinates; `matrixWorld` / `worldToLocal` pick this up.
|
|
15
|
+
* `StreamedSplatMesh.load()` applies this correction automatically for LCC2;
|
|
16
|
+
* use this helper only with meshes created through another loading path.
|
|
17
|
+
*/
|
|
18
|
+
export declare function applyLcc2ToThreeTransform(target: THREE.Object3D): void;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LodSourceOptions, StreamedScene } from '../../lod-source';
|
|
2
|
+
import type { SplatDatasetSource } from '../../dataset-source';
|
|
3
|
+
/** Builds a scene from a parsed `.lcc2` manifest (JSON already fetched). */
|
|
4
|
+
export declare function buildLcc2Scene(json: unknown, dataset: SplatDatasetSource, options: LodSourceOptions, shBands?: 0 | 1 | 2 | 3): StreamedScene;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import type { TriangleMeshData } from './parse-mesh-ply';
|
|
3
|
+
/** One cell's collision mesh decoded from a `collision.lci`. */
|
|
4
|
+
export interface CollisionLciTile {
|
|
5
|
+
readonly cellX: number;
|
|
6
|
+
readonly cellY: number;
|
|
7
|
+
readonly data: TriangleMeshData;
|
|
8
|
+
/**
|
|
9
|
+
* Cell-grid AABB in the file's source-local frame, clamped to the file's
|
|
10
|
+
* scene bounds. Useful for culling before consulting the triangles.
|
|
11
|
+
*/
|
|
12
|
+
readonly bounds: THREE.Box3;
|
|
13
|
+
}
|
|
14
|
+
/** Everything {@link parseCollisionLci} extracts from one file. */
|
|
15
|
+
export interface CollisionLciFile {
|
|
16
|
+
readonly version: number;
|
|
17
|
+
readonly bounds: THREE.Box3;
|
|
18
|
+
readonly cellLengthX: number;
|
|
19
|
+
readonly cellLengthY: number;
|
|
20
|
+
readonly tiles: readonly CollisionLciTile[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parses a classic-LCC `collision.lci` buffer into per-cell triangle meshes.
|
|
24
|
+
*
|
|
25
|
+
* @throws {Error} on bad magic, unsupported version, truncated payloads,
|
|
26
|
+
* inconsistent size fields, or out-of-range face indices.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseCollisionLci(buffer: ArrayBuffer): CollisionLciFile;
|