@voluma/vlam 0.2.2 → 0.2.3
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/README.md +1 -3
- package/dist/effects.d.ts +90 -5
- package/dist/effects.js +183 -114
- package/dist/effects.js.map +1 -1
- package/dist/index.js +281 -219
- package/dist/index.js.map +1 -1
- package/dist/relighting-pass.d.ts +49 -0
- package/dist/streamed-splat-mesh-utils.d.ts +1 -1
- package/dist/streamed-splat-mesh.d.ts +14 -3
- package/dist/worker-sorter.d.ts +13 -0
- package/package.json +1 -1
package/dist/effects.js.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"effects.js","sources":["../src/lib/relighting-pass.ts","../src/lib/effects.ts"],"sourcesContent":["/**\n * Host-safe lighting-map pass for {@link SplatMesh.setRelighting}.\n *\n * The relight example is a sidecar `render(relightScene, camera)` into an RT.\n * That only fills TSL `shadow()` maps when the renderer looks like a fresh\n * `createSplatRenderer` (`autoClear` true, no tone-map wrap). Host apps often\n * already own a `WebGPURenderer` with `autoClear: false` and an inline ACES /\n * sRGB `contextNode`. This helper isolates those for one pass, then restores\n * them, so the example works in a custom scene.\n *\n * Important: do **not** assign `contextNode = undefined`. WebGPURenderer reads\n * `contextNode.id` while building render objects; clearing it throws and the\n * lighting RT never fills.\n */\nimport * as THREE from 'three/webgpu';\nimport { context } from 'three/tsl';\n\nconst size = new THREE.Vector2();\nconst clearColor = new THREE.Color();\n\n/**\n * Passthrough context: keeps a stable `.id` for the render-object cache, but\n * does not apply host tone-mapping / sRGB encode to the linear factor map.\n */\nconst RELIGHT_PASS_CONTEXT = context({\n getOutput: (node: THREE.Node) => node,\n});\n\n/**\n * Minimal renderer surface {@link renderRelightingFactorMap} needs. A real\n * `THREE.WebGPURenderer` satisfies this; tests can pass a stub.\n */\nexport type RelightingFactorRenderer = {\n autoClear: boolean;\n shadowMap: { enabled: boolean; autoUpdate?: boolean };\n getDrawingBufferSize(target: THREE.Vector2): THREE.Vector2;\n getRenderTarget(): THREE.RenderTarget | null;\n getActiveCubeFace(): number;\n getActiveMipmapLevel(): number;\n getMRT(): THREE.MRTNode | null;\n setMRT(mrt: THREE.MRTNode | null): void;\n setRenderTarget(\n target: THREE.RenderTarget | null,\n activeCubeFace?: number,\n activeMipmapLevel?: number,\n ): void;\n getClearColor(target: THREE.Color): THREE.Color;\n getClearAlpha(): number;\n setClearColor(color: THREE.ColorRepresentation, alpha?: number): void;\n clear(): void;\n render(scene: THREE.Object3D, camera: THREE.Camera): void;\n contextNode: unknown;\n};\n\n/**\n * Renders `scene` from `camera` into `target` as a shadow-factor map:\n * resizes to the drawing buffer, clears **white + A0**, forces `autoClear`\n * and shadow maps on, and swaps in a passthrough `contextNode` so a host\n * ACES/sRGB wrap cannot turn the linear multiplier into speckle.\n *\n * Call each frame **before** `splats.update` / the main splat draw. Restore\n * is guaranteed even if `render` throws.\n */\nexport function renderRelightingFactorMap(\n renderer: RelightingFactorRenderer,\n scene: THREE.Scene,\n camera: THREE.Camera,\n target: THREE.RenderTarget,\n): void {\n renderer.getDrawingBufferSize(size);\n target.setSize(Math.max(1, size.x), Math.max(1, size.y));\n\n const previousTarget = renderer.getRenderTarget();\n const previousCubeFace = renderer.getActiveCubeFace();\n const previousMipmapLevel = renderer.getActiveMipmapLevel();\n const previousMrt = renderer.getMRT();\n const previousAlpha = renderer.getClearAlpha();\n const previousAutoClear = renderer.autoClear;\n const previousShadowEnabled = renderer.shadowMap.enabled;\n const previousShadowAutoUpdate = renderer.shadowMap.autoUpdate;\n const previousContext = renderer.contextNode;\n renderer.getClearColor(clearColor);\n\n try {\n renderer.contextNode = RELIGHT_PASS_CONTEXT;\n renderer.autoClear = true;\n renderer.shadowMap.enabled = true;\n if (previousShadowAutoUpdate === false) renderer.shadowMap.autoUpdate = true;\n renderer.setMRT(null);\n renderer.setRenderTarget(target);\n renderer.setClearColor(0xffffff, 0);\n renderer.clear();\n renderer.render(scene, camera);\n } finally {\n renderer.setRenderTarget(previousTarget, previousCubeFace, previousMipmapLevel);\n renderer.setMRT(previousMrt);\n renderer.setClearColor(clearColor, previousAlpha);\n renderer.autoClear = previousAutoClear;\n renderer.shadowMap.enabled = previousShadowEnabled;\n if (previousShadowAutoUpdate !== undefined) {\n renderer.shadowMap.autoUpdate = previousShadowAutoUpdate;\n }\n renderer.contextNode = previousContext;\n }\n}\n","/**\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 * Accepts one light or {@link RelightingLightContribution}[] with intensity\n * weights. Optional `color` / `diffuse` / `direction` add a Lambert boost on\n * top of that identity (RGB may exceed 1 on a HalfFloat lighting RT).\n * - {@link renderRelightingFactorMap}: host-safe lighting pass. Isolates\n * `autoClear`, shadow maps, and swaps a passthrough `contextNode` so the\n * example works on an existing `WebGPURenderer`, not only `createSplatRenderer`.\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 cameraPosition,\n float,\n int,\n mat3,\n min,\n mix,\n modelViewMatrix,\n normalWorld,\n positionWorld,\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\nexport { renderRelightingFactorMap, type RelightingFactorRenderer } from './relighting-pass';\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/** One independent shadow-casting light for {@link createRelightingShadowFactorMaterial}. */\nexport type RelightingLightContribution = {\n light: THREE.Light;\n /** Relative umbra weight. Default `1`. Values `<= 0` are skipped. */\n intensity?: number;\n};\n\n/** Options for {@link createRelightingShadowFactorMaterial}. */\nexport type RelightingShadowFactorOptions = {\n umbra?: number;\n receiveUpMin?: number;\n color?: THREE.Color;\n diffuse?: number;\n direction?: THREE.Vector3;\n /**\n * How independent contributions combine into one multiplier.\n * - `average` (default): intensity-weighted mean of shadows (fill lights).\n * - `min`: union of umbras — each caster keeps its own silhouette. Contribution\n * `intensity` scales umbra depth: `0` → none, `1` → default `umbra`,\n * `>1` → darker than default (`umbra_i = clamp(1 - I*(1-umbra), 0, 1)`).\n */\n combine?: 'average' | 'min';\n midLight?: THREE.Light;\n midRadius?: number;\n outerLight?: THREE.Light;\n outerRadius?: number;\n farLight?: THREE.Light;\n nearRadius?: number;\n};\n\n/**\n * Cap on independent shadow lights (cascades of the first directional do not\n * count). Extra contributions are ignored. The compiled graph unrolls only\n * the live contribution count, not a padded 32-wide loop.\n */\nexport const MAX_RELIGHTING_SHADOW_LIGHTS = 32;\n\nconst contributionIntensity = (contribution: RelightingLightContribution): number => {\n const value = contribution.intensity;\n if (!Number.isFinite(value)) return 1;\n return Math.max(0, value as number);\n};\n\n/**\n * Fade punctual (spot / point) umbras to lit as fragments approach `light.distance`.\n * Directional lights return 1. Uses the Frostbite cutoff window so weight is 0 at\n * cutoff (no hard shadow-map cliff). `light.decay` steepens the falloff (>1).\n */\nconst punctualUmbraWeight = (light: THREE.Light): Node<'float'> => {\n const punctual = light as THREE.SpotLight & THREE.PointLight;\n const isPunctual = punctual.isSpotLight === true || punctual.isPointLight === true;\n if (!isPunctual) return float(1);\n const cutoff = Number(punctual.distance);\n if (!(cutoff > 0)) return float(1);\n const decay = Math.max(Number(punctual.decay) || 1, 0.01);\n const lightDistance = asNode<'float'>(positionWorld.distance(uniform(punctual.position)));\n // pow2(saturate(1 - pow4(d/cutoff))) → 1 near light, 0 at cutoff\n const window = asNode<'float'>(lightDistance.div(float(cutoff)).pow4().oneMinus().clamp().pow2());\n return asNode<'float'>(window.pow(float(decay)));\n};\n\nconst normalizeRelightingLights = (\n lights: THREE.Light | RelightingLightContribution[],\n): RelightingLightContribution[] => {\n const list = Array.isArray(lights) ? lights : [{ light: lights, intensity: 1 }];\n const weighted: RelightingLightContribution[] = [];\n for (const contribution of list) {\n if (weighted.length >= MAX_RELIGHTING_SHADOW_LIGHTS) break;\n if (contributionIntensity(contribution) <= 0) continue;\n weighted.push(contribution);\n }\n return weighted;\n};\n\nconst cascadedShadow = (\n light: THREE.Light,\n options: RelightingShadowFactorOptions,\n dist: Node<'float'>,\n): Node<'float'> => {\n const nearRadius = Number.isFinite(options.nearRadius)\n ? Math.max(1, options.nearRadius as number)\n : options.midLight\n ? 20\n : 100;\n const midRadius = Number.isFinite(options.midRadius)\n ? Math.max(nearRadius, options.midRadius as number)\n : 50;\n const outerRadius = Number.isFinite(options.outerRadius)\n ? Math.max(midRadius, options.outerRadius as number)\n : 160;\n const blendAt = (inner: Node<'float'>, outer: Node<'float'>, radius: number): Node<'float'> =>\n asNode<'float'>(mix(inner, outer, smoothstep(float(radius * 0.7), float(radius * 0.95), dist)));\n let shadowed = asNode<'float'>(shadow(light));\n if (options.midLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.midLight)), nearRadius);\n if (options.outerLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.outerLight)), midRadius);\n if (options.farLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.farLight)), outerRadius);\n }\n } else if (options.farLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.farLight)), midRadius);\n }\n } else if (options.farLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.farLight)), nearRadius);\n }\n return shadowed;\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)) + boost, 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 * - with `diffuse` > 0, facing surfaces add `color * NdotL * diffuse`\n * (RGB may exceed 1; use a HalfFloat lighting RT)\n *\n * Pass one {@link THREE.Light} (demo / single-sun) or an array of\n * {@link RelightingLightContribution} so independent lights share one\n * multiplier. Default combine is intensity-weighted average\n * (`illum = sum(I_i * shadow_i) / sum(I_i)`); pass `combine: 'min'` for a\n * union of umbras so each caster keeps its silhouette. Spot / point lights\n * with `distance > 0` fade their umbra to lit via a Frostbite cutoff window\n * (steepened by `decay`) so the shadow map far plane is not a hard cliff.\n * Cascades (`midLight` / `outerLight` / `farLight`) still attach to the\n * **first** directional only. At most {@link MAX_RELIGHTING_SHADOW_LIGHTS}\n * independent lights are used; extras are ignored. Graph size follows the\n * live contribution count (not a padded loop of that width).\n *\n * By default, only **upward** proxy faces receive (`receiveUpMin`). Vertical\n * and noisy foliage triangles still **cast**, but they do not self-shadow —\n * that PCF acne reads as per-frame sparkle in trees. Pass `receiveUpMin: 0`\n * to receive on every face. The Lambert term uses the same slope gate, so\n * steep foliage does not pick up a noisy tint.\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 lights - Shadow-casting light, or intensity-weighted contributions.\n * The first entry is the innermost cascade when `midLight` / `outerLight` /\n * `farLight` are set.\n * @param options.umbra - Multiplier in full shadow, in `[0, 1]`. Default `0.45`.\n * @param options.receiveUpMin - World-up `normal.y` below which the face does\n * not receive. Default `0.25`. `0` disables the slope gate.\n * @param options.color - Light tint for the optional Lambert boost. Mutate\n * in place; the material holds this object. Default white.\n * @param options.diffuse - Lambert boost on top of identity. Default `0`\n * (shadow multiplier only). Facing, unshadowed coverage becomes\n * `1 + color * NdotL * diffuse`.\n * @param options.direction - World-space direction **toward** the light.\n * Mutate in place each frame as the sun moves. Default `(0, 1, 0)`.\n * @param options.midLight - Optional mid cascade (demo: ~50 m). Blend from\n * the first light over `nearRadius`.\n * @param options.midRadius - World metres where the mid cascade yields to\n * `outerLight` (or `farLight`). Default `50`.\n * @param options.outerLight - Optional outer cascade (demo: ~160 m) between\n * mid and scene-sized far. Blend from mid over `midRadius`, then to\n * `farLight` over `outerRadius`.\n * @param options.outerRadius - World metres where the outer cascade yields to\n * `farLight`. Default `160`. Ignored without `outerLight` and `farLight`.\n * @param options.farLight - Optional scene-sized cascade. Out-of-frustum\n * `shadow()` is 1 (lit), so inner maps can stay tight without clipping\n * distant umbras. Blend by camera distance.\n * @param options.nearRadius - World metres where the inner cascade yields to\n * `midLight` (or to `farLight` if there is no mid). Default `20` with\n * `midLight`, else `100`.\n */\nexport function createRelightingShadowFactorMaterial(\n lights: THREE.Light | RelightingLightContribution[],\n options: RelightingShadowFactorOptions = {},\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 receiveUpMin = Number.isFinite(options.receiveUpMin)\n ? Math.min(0.95, Math.max(0, options.receiveUpMin as number))\n : 0.25;\n const diffuse = Number.isFinite(options.diffuse) ? Math.max(0, options.diffuse as number) : 0;\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 = 2;\n material.polygonOffsetUnits = 2;\n // Bypass MeshStandard lighting. Shadow attenuation is the base multiplier;\n // optional Lambert is added on top of identity so unshadowed coverage stays\n // ≈ 1 when `diffuse` is 0.\n const dist = positionWorld.distance(cameraPosition);\n const contributions = normalizeRelightingLights(lights);\n const receive =\n receiveUpMin > 0\n ? asNode<'float'>(smoothstep(float(receiveUpMin), float(receiveUpMin + 0.3), normalWorld.y))\n : float(1);\n\n const shadowTermAt = (index: number, light: THREE.Light): Node<'float'> => {\n const raw = index === 0 ? cascadedShadow(light, options, dist) : asNode<'float'>(shadow(light));\n // Mix toward lit (1) as punctual range fades so umbras soften instead of\n // clipping at the shadow-map far plane.\n return asNode<'float'>(mix(float(1), raw, punctualUmbraWeight(light)));\n };\n\n let factor: Node<'float'>;\n let shadowed: Node<'float'> = float(1);\n\n if (options.combine === 'min' && contributions.length > 0) {\n // Union of umbras with per-light depth from contribution intensity:\n // umbra_i = clamp(1 - I * (1 - umbra), 0, 1)\n // so I=0 → no shadow, I=1 → default umbra, I>1 → darker than default.\n let factorCombined: Node<'float'> = float(1);\n for (let i = 0; i < contributions.length; i++) {\n const contribution = contributions[i]!;\n const intensity = contributionIntensity(contribution);\n const umbra_i = Math.min(1, Math.max(0, 1 - intensity * (1 - umbra)));\n const term = shadowTermAt(i, contribution.light);\n const factor_i = asNode<'float'>(mix(float(umbra_i), float(1), term));\n factorCombined = asNode<'float'>(min(factorCombined, factor_i));\n }\n shadowed = factorCombined;\n factor = asNode<'float'>(mix(float(1), factorCombined, receive));\n } else {\n if (contributions.length === 1) {\n shadowed = shadowTermAt(0, contributions[0]!.light);\n } else if (contributions.length > 1) {\n let illum: Node<'float'> = float(0);\n let denom = 0;\n for (let i = 0; i < contributions.length; i++) {\n const contribution = contributions[i]!;\n const intensity = contributionIntensity(contribution);\n denom += intensity;\n const term = shadowTermAt(i, contribution.light);\n illum = asNode<'float'>(illum.add(float(intensity).mul(term)));\n }\n shadowed = denom > 0 ? asNode<'float'>(illum.div(float(denom))) : float(1);\n }\n const attenuation = mix(float(1), shadowed, receive);\n factor = mix(float(umbra), float(1), attenuation);\n }\n\n if (diffuse > 0) {\n const lightColor = uniform(options.color ?? new THREE.Color(1, 1, 1));\n const lightDir = uniform(options.direction ?? new THREE.Vector3(0, 1, 0));\n const ndl = asNode<'float'>(normalWorld.dot(lightDir.normalize()).max(0));\n const boost = asNode<'vec3'>(\n lightColor.mul(ndl).mul(float(diffuse)).mul(receive).mul(shadowed),\n );\n material.outputNode = vec4(vec3(factor).add(boost), float(1));\n } else {\n material.outputNode = vec4(vec3(factor), float(1));\n }\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":["size","THREE","clearColor","RELIGHT_PASS_CONTEXT","context","node","renderRelightingFactorMap","renderer","scene","camera","target","previousTarget","previousCubeFace","previousMipmapLevel","previousMrt","previousAlpha","previousAutoClear","previousShadowEnabled","previousShadowAutoUpdate","previousContext","SHAPE_STRIDE","MODE_INDEX","DEFAULT_CENTER","DEFAULT_COLOR","DEFAULT_ROTATION","sdfEffects","initialShapes","options","maxShapes","slots","i","shapesUniform","uniformArray","count","uniform","warnedOverflow","setShapes","shapes","n","warn","s","he","b","c","col","rot","kindIndex","ctx","p","rgb","alphaMul","float","k","el","j","s0","s1","s2","s3","s4","center","kind","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","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","MAX_RELIGHTING_SHADOW_LIGHTS","contributionIntensity","contribution","value","punctualUmbraWeight","light","punctual","cutoff","decay","lightDistance","positionWorld","window","normalizeRelightingLights","lights","list","weighted","cascadedShadow","dist","nearRadius","midRadius","outerRadius","blendAt","inner","outer","shadowed","shadow","createRelightingShadowFactorMaterial","umbra","receiveUpMin","material","cameraPosition","contributions","receive","normalWorld","shadowTermAt","index","raw","factor","factorCombined","umbra_i","term","factor_i","min","illum","denom","attenuation","lightColor","lightDir","boost","triangleMeshToGeometry","data","geometry","createRelightingProxy","albedo","group","worldMatrix","ownedGeometries","bakeWorld","addGeometry","owns","geo","owned","mesh","tile","mat"],"mappings":"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"}
|