holosplat 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +890 -0
- package/bin/holosplat.cjs +374 -0
- package/dist/holosplat.esm.js +766 -0
- package/dist/holosplat.esm.js.map +7 -0
- package/dist/holosplat.iife.js +766 -0
- package/dist/holosplat.iife.js.map +7 -0
- package/holosplat/editor.js +2947 -0
- package/holosplat/index.html +614 -0
- package/holosplat/stats.js +101 -0
- package/package.json +30 -0
- package/server.py +560 -0
- package/src/server.js +198 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/shaders.js", "../src/sort-shaders.js", "../src/renderer.js", "../src/camera.js", "../src/sorter.js", "../src/loaders/fetch-utils.js", "../src/loaders/splat-loader.js", "../src/loaders/ply-loader.js", "../src/loaders/spz-loader.js", "../src/compress.js", "../src/variant-pack.js", "../src/loaders/spzv-loader.js", "../src/animation.js", "../src/device-tier.js", "../src/viewer.js", "../src/player.js", "../src/scroll-scene.js", "../src/optimize.js", "../src/index.js"],
|
|
4
|
+
"sourcesContent": ["// WGSL shader for Gaussian splatting.\n//\n// GPU layout (must match CPU Float32Array layout in loaders):\n//\n// struct Gaussian { byte offset\n// pos : vec3<f32>, // 0 (12 bytes)\n// part : f32, // 12 (part/object index, stored as float 0.0/1.0/2.0\u2026)\n// color : vec4<f32>, // 16 (rgba \u2014 DC-only base color: 0.5 + SH_C0 * f_dc)\n// scale : vec3<f32>, // 32 (12 bytes + 4 implicit padding = 16)\n// quat : vec4<f32>, // 48 (xyzw)\n// }; // stride = 64 bytes = 16 floats/gaussian\n//\n// Binding 3: per-part model-space \u2192 world-space transforms (col-major mat4, one per part).\n// For single-part scenes part=0 and transforms[0] is the identity matrix.\n//\n// Binding 6: SH rest coefficients \u2014 packed f32 array (no vec3 padding).\n// Layout: shCoeffs[(gi * numSHBases + basis) * 3 + ch] ch=0\u2192r, 1\u2192g, 2\u2192b.\n// Bound to a tiny dummy buffer (12 bytes) when shDegree == 0.\n//\n// Uniform layout (176 bytes = 44 floats):\n// [0-15] view matrix (col-major)\n// [16-31] proj matrix (col-major)\n// [32-33] viewport (width, height) in pixels\n// [34-35] focal (fx, fy) in pixels\n// [36] splatScale multiplier\n// [37] near (view-space units)\n// [38] gamma (1.0 = linear, 2.2 = sRGB)\n// [39] radiusCap (screen-space radius cap, fraction of viewport.y)\n// [40] shDegree (0/1/2/3, stored as f32, read as u32 in shader)\n// [41] numSHBases (0/3/8/15)\n// [42] aaDilation (covariance low-pass filter, default 0.3)\n// [43] (padding)\n\nexport const SHADER = /* wgsl */`\n\nstruct Uniforms {\n view : mat4x4<f32>,\n proj : mat4x4<f32>,\n viewport : vec2<f32>,\n focal : vec2<f32>,\n params : vec4<f32>, // .x = splatScale .y = near .z = gamma .w = radiusCap\n shParams : vec4<f32>, // .x = shDegree .y = numSHBases .z = aaDilation\n};\n\nstruct Gaussian {\n pos : vec3<f32>,\n part : f32, // part index (0.0, 1.0, 2.0\u2026) \u2014 cast to u32 in shader\n color : vec4<f32>, // DC-only base color (0.5 + SH_C0 * f_dc)\n scale : vec3<f32>,\n quat : vec4<f32>,\n};\n\n@group(0) @binding(0) var<uniform> uniforms : Uniforms;\n@group(0) @binding(1) var<storage, read> gaussians : array<Gaussian>;\n@group(0) @binding(2) var<storage, read> order : array<u32>;\n@group(0) @binding(3) var<storage, read> transforms : array<mat4x4<f32>>;\n\n// Mask volume: a world-space box that clips splats \u2014 splats inside render at\n// full size, splats outside fade to zero over a softEdge band.\nstruct MaskVolume {\n invMatrix : mat4x4<f32>, // world \u2192 local transform (precomputed inverse)\n params : vec4<f32>, // .x = softEdge (world-space fade width)\n};\nstruct MaskUniforms {\n header : vec4<u32>, // .x = active volume count (0..8)\n volumes : array<MaskVolume, 8>,\n};\n@group(0) @binding(4) var<uniform> maskUnif : MaskUniforms;\n@group(0) @binding(5) var<storage, read> partVolMask : array<u32>;\n\n// SH rest coefficients \u2014 packed f32 array, no padding.\n// Layout: for Gaussian gi, basis b: shCoeffs[(gi*numSHBases + b)*3 + channel].\n// Bound to a tiny dummy buffer when shDegree == 0.\n@group(0) @binding(6) var<storage, read> shCoeffs : array<f32>;\n\n// TEMP debug instrumentation \u2014 writes intermediate per-splat values for the\n// one gaussian index named in uniforms.shParams.w (\u22121 = disabled), so the\n// GPU-computed math for a specific splat can be cross-checked on the CPU\n// against hand-computed expected values from the raw source file.\n@group(0) @binding(7) var<storage, read_write> debugOut : array<f32, 16>;\n\nstruct VOut {\n @builtin(position) clipPos : vec4<f32>,\n @location(0) color : vec4<f32>,\n // Raw \u00B11 quad-corner UV. The quad geometry itself is pre-warped to align\n // with the 2-D covariance's eigenvectors (see cs_preprocess), so this UV\n // directly parameterizes the ellipse \u2014 no per-pixel conic/Mahalanobis math\n // needed.\n @location(1) uv : vec2<f32>,\n};\n\n// Per-splat results of the heavy per-gaussian math (covariance projection,\n// eigendecomposition, SH), computed once per splat by cs_preprocess instead\n// of once per *vertex* (6x redundant \u2014 every vertex of a splat's quad used\n// to redo the same work). vs_main just reads this and places the quad.\n// ndc.xy = screen-space center (NDC, pre-offset)\n// ndc.z = NDC depth (clip.z/clip.w) \u2014 ndc.z > 1.5 marks a degenerate/\n// discarded splat (near-plane, edge-on, etc.), matching the old\n// degen() sentinel.\n// v1/v2 = quad corner basis vectors (pixels), already scaled by sizeFade.\n// color = final straight-alpha rgba (SH-evaluated rgb, alpha folded with\n// nearFade/sizeFade/maskFade/aaFactor).\nstruct SplatGeom {\n ndc : vec4<f32>,\n v1 : vec2<f32>,\n v2 : vec2<f32>,\n color : vec4<f32>,\n};\n// read_write (compute-only) and read (vertex-only) views of the same buffer\n// get distinct binding numbers \u2014 WGSL doesn't allow one module-scope\n// resource to change access mode per entry point, and read_write storage\n// isn't permitted outside the compute stage anyway.\n@group(0) @binding(8) var<storage, read_write> splatGeomOut : array<SplatGeom>;\n@group(0) @binding(9) var<storage, read> splatGeomIn : array<SplatGeom>;\n\nstruct PreprocessParams { count : vec4<u32> };\n@group(0) @binding(10) var<uniform> ppParams : PreprocessParams;\n\n// \u2500\u2500 SH constants (match ply-loader.js) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst SH_C1 = 0.4886025119029199;\nconst SH_C2_0 = 1.0925484305920792;\nconst SH_C2_1 = -1.0925484305920792;\nconst SH_C2_2 = 0.31539156525252005;\nconst SH_C2_3 = -1.0925484305920792;\nconst SH_C2_4 = 0.5462742152960396;\nconst SH_C3_0 = -0.5900435899266435;\nconst SH_C3_1 = 2.890611442640554;\nconst SH_C3_2 = -0.4570457994644658;\nconst SH_C3_3 = 0.3731763325901154;\nconst SH_C3_4 = -0.4570457994644658;\nconst SH_C3_5 = 1.445305721320277;\nconst SH_C3_6 = -0.5900435899266435;\n\n// Read one SH basis vector (3 packed floats) from the flat shCoeffs array.\n// Each basis occupies 3 consecutive floats: [r, g, b].\nfn shBasis(gi: u32, numBases: u32, b: u32) -> vec3<f32> {\n let o = (gi * numBases + b) * 3u;\n return vec3<f32>(shCoeffs[o], shCoeffs[o + 1u], shCoeffs[o + 2u]);\n}\n\n// Evaluate higher-order SH (degree 1\u20133) for Gaussian gi at direction dir.\n// Returns the additive contribution to the DC color \u2014 caller clamps final sum.\nfn evalSH(gi: u32, dir: vec3<f32>, shDeg: u32, numBases: u32) -> vec3<f32> {\n var result = vec3<f32>(0.0);\n if (shDeg == 0u) { return result; }\n\n let x = dir.x; let y = dir.y; let z = dir.z;\n\n let sh0 = shBasis(gi, numBases, 0u);\n let sh1 = shBasis(gi, numBases, 1u);\n let sh2 = shBasis(gi, numBases, 2u);\n result += SH_C1 * (-sh0 * y + sh1 * z - sh2 * x);\n if (shDeg == 1u) { return result; }\n\n let sh3 = shBasis(gi, numBases, 3u);\n let sh4 = shBasis(gi, numBases, 4u);\n let sh5 = shBasis(gi, numBases, 5u);\n let sh6 = shBasis(gi, numBases, 6u);\n let sh7 = shBasis(gi, numBases, 7u);\n let xx = x*x; let yy = y*y; let zz = z*z;\n let xy = x*y; let xz = x*z; let yz = y*z;\n result += SH_C2_0 * xy * sh3\n + SH_C2_1 * yz * sh4\n + SH_C2_2 * (2.0*zz - xx - yy) * sh5\n + SH_C2_3 * xz * sh6\n + SH_C2_4 * (xx - yy) * sh7;\n if (shDeg == 2u) { return result; }\n\n let sh8 = shBasis(gi, numBases, 8u);\n let sh9 = shBasis(gi, numBases, 9u);\n let sh10 = shBasis(gi, numBases, 10u);\n let sh11 = shBasis(gi, numBases, 11u);\n let sh12 = shBasis(gi, numBases, 12u);\n let sh13 = shBasis(gi, numBases, 13u);\n let sh14 = shBasis(gi, numBases, 14u);\n result += SH_C3_0 * y * (3.0*xx - yy) * sh8\n + SH_C3_1 * xy * z * sh9\n + SH_C3_2 * y * (4.0*zz - xx - yy) * sh10\n + SH_C3_3 * z * (2.0*zz - 3.0*xx - 3.0*yy) * sh11\n + SH_C3_4 * x * (4.0*zz - xx - yy) * sh12\n + SH_C3_5 * (xx - yy) * z * sh13\n + SH_C3_6 * x * (xx - 3.0*yy) * sh14;\n return result;\n}\n\n// Quaternion (xyzw) \u2192 column-major rotation matrix\nfn quatToMat3(q: vec4<f32>) -> mat3x3<f32> {\n let x = q.x; let y = q.y; let z = q.z; let w = q.w;\n return mat3x3<f32>(\n vec3<f32>(1.0 - 2.0*(y*y+z*z), 2.0*(x*y+w*z), 2.0*(x*z-w*y)),\n vec3<f32>(2.0*(x*y-w*z), 1.0 - 2.0*(x*x+z*z), 2.0*(y*z+w*x)),\n vec3<f32>(2.0*(x*z+w*y), 2.0*(y*z-w*x), 1.0 - 2.0*(x*x+y*y))\n );\n}\n\n// Writes the degenerate-splat sentinel (moved outside clip space \u2014 its quad\n// gets clipped away entirely, matching the old per-vertex degen() early-out).\nfn writeDegenerate(gi: u32) {\n var o: SplatGeom;\n o.ndc = vec4<f32>(0.0, 0.0, 2.0, 0.0);\n o.v1 = vec2<f32>(0.0);\n o.v2 = vec2<f32>(0.0);\n o.color = vec4<f32>(0.0);\n splatGeomOut[gi] = o;\n}\n\n// \u2500\u2500 Per-splat preprocess (compute) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Runs the heavy per-gaussian math \u2014 covariance projection, eigendecomposition,\n// SH evaluation \u2014 exactly once per splat (not once per vertex). vs_main used\n// to redo all of this 6x per splat (once per quad vertex); on weaker mobile\n// GPUs that redundant vertex-shader work, not fragment/overdraw cost, turned\n// out to be the dominant per-frame cost (lowering render resolution didn't\n// help fps at all, which only makes sense if the bottleneck was vertex-bound).\n@compute @workgroup_size(64)\nfn cs_preprocess(@builtin(global_invocation_id) gid: vec3<u32>) {\n let gi = gid.x;\n if gi >= ppParams.count.x { return; }\n\n let g = gaussians[gi];\n\n // \u2500\u2500 Part transform: local \u2192 world \u2192 view \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let partId = u32(g.part);\n let partMat = transforms[partId];\n let worldPos4 = partMat * vec4<f32>(g.pos, 1.0);\n let viewPos4 = uniforms.view * worldPos4;\n let t = viewPos4.xyz;\n\n // Discard if at or behind near plane\n let near = uniforms.params.y;\n if t.z > -near { writeDegenerate(gi); return; }\n\n // Near-depth fade: fade out splats within 3\u00D7 the near distance so that\n // close-up splats don't cover the entire screen (matches Blender's behaviour).\n let depth = -t.z;\n let nearFade = clamp((depth - near) / (near * 2.0), 0.0, 1.0);\n\n // \u2500\u2500 Mask volumes: fade splats outside assigned volume boxes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let volBits = partVolMask[partId];\n var maskFade = 1.0;\n for (var vi = 0u; vi < maskUnif.header.x; vi++) {\n if (((volBits >> vi) & 1u) != 0u) {\n let vol = maskUnif.volumes[vi];\n let lp = vol.invMatrix * worldPos4;\n let q = abs(lp.xyz) - vec3<f32>(0.5);\n let sdf = length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0);\n let e = max(vol.params.x, 0.001);\n maskFade *= 1.0 - smoothstep(-e, e, sdf);\n }\n }\n\n // \u2500\u2500 3-D covariance \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let splatScale = uniforms.params.x;\n let s = g.scale * (splatScale * maskFade);\n let R = quatToMat3(g.quat);\n // M = R * diag(s); Cov3D = M * M\u1D40\n let M = mat3x3<f32>(R[0]*s.x, R[1]*s.y, R[2]*s.z);\n let cov3 = M * transpose(M);\n\n // \u2500\u2500 Project covariance to 2-D \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Perspective Jacobian at view-space point t (camera looks down \u2013Z)\n let tz2 = t.z * t.z;\n let fx = uniforms.focal.x;\n let fy = uniforms.focal.y;\n let J = mat3x3<f32>(\n vec3<f32>(fx / (-t.z), 0.0, 0.0),\n vec3<f32>(0.0, fy / (-t.z), 0.0),\n vec3<f32>(fx*t.x / tz2, fy*t.y / tz2, 0.0)\n );\n // Combined rotation: view \u00D7 part \u2014 maps splat-local covariance to view space.\n // For single-part (identity partMat) this reduces to just the view rotation.\n let R_part = mat3x3<f32>(partMat[0].xyz, partMat[1].xyz, partMat[2].xyz);\n let W_view = mat3x3<f32>(uniforms.view[0].xyz, uniforms.view[1].xyz, uniforms.view[2].xyz);\n let W = W_view * R_part;\n let T = J * W;\n let cov2 = T * cov3 * transpose(T);\n\n // Extract 2\u00D72 + low-pass filter (anti-aliasing dilation)\n let aaDil = uniforms.shParams.z;\n let detRaw = cov2[0][0] * cov2[1][1] - cov2[0][1] * cov2[0][1]; // pre-dilation det(\u03A3)\n let a = cov2[0][0] + aaDil; // \u03A3_xx (cov2[col][row])\n let b = cov2[0][1]; // \u03A3_xy\n let c = cov2[1][1] + aaDil; // \u03A3_yy\n\n let det = a*c - b*b;\n if det < 1e-4 { writeDegenerate(gi); return; }\n\n // Mip-Splatting opacity compensation: dilating the covariance for AA\n // lowers the Gaussian's peak density (det grows), which silently dims\n // every splat \u2014 small/thin splats (fine surface detail) the most, since\n // aaDil is a larger fraction of their det. Rescale alpha by sqrt(det(\u03A3)/det(\u03A3+aaDil\u00B7I))\n // to preserve the splat's original peak opacity under the dilated kernel.\n let aaFactor = sqrt(max(detRaw / det, 0.0));\n\n // \u2500\u2500 Eigen-decomposition of the 2\u00D72 covariance \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Quad geometry is aligned to the covariance's eigenvectors (matches\n // PlayCanvas/SuperSplat's actual gsplat shader) so each splat rasterizes\n // only the minimal area its ellipse actually covers \u2014 an axis-aligned\n // isotropic bound (sized to the larger eigenvalue in both screen axes) was\n // tried instead to dodge a suspected eigenvector-orientation instability,\n // but that didn't fix anything and meaningfully increased overdraw on\n // anisotropic splats (every elongated splat rasterizing a much bigger\n // square than it needs), which is a likely contributor to the overall\n // softness compared to SuperSplat.\n //\n // Degenerate (near edge-on, disk-like) splats \u2014 where the minor-axis\n // variance goes non-positive \u2014 are discarded outright, matching\n // GaussianSplats3D's eigenValue2 <= 0.0 early return, rather than forced\n // to a minimum width. A previous version floored lambda2 to 0.1, which\n // renders these as a faint but visible ~1px sliver instead of nothing \u2014\n // on a curved/reflective surface, many splats sit near this grazing-angle\n // case, and the accumulated phantom slivers are a plausible source of the\n // \"cloudy\" look on specular surfaces specifically.\n let mid = 0.5 * (a + c);\n let evDist = length(vec2<f32>((a - c) * 0.5, b));\n let lambda1 = mid + evDist;\n let lambda2 = mid - evDist;\n if lambda2 <= 0.0 { writeDegenerate(gi); return; }\n\n let vmin = min(1024.0, min(uniforms.viewport.x, uniforms.viewport.y));\n let l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);\n let l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);\n\n let eigenDir = normalize(vec2<f32>(b, lambda1 - a) + vec2<f32>(1e-6, 0.0));\n let v1 = l1 * eigenDir;\n let v2 = l2 * vec2<f32>(eigenDir.y, -eigenDir.x);\n\n // Clamp the major-axis extent: splats larger than radiusCap \u00D7 viewport\n // height are faded out and capped, preventing a handful of close-up splats\n // from overdrawing the whole screen. radiusCap shrinks adaptively under\n // load \u2014 see Viewer#_updateAdaptiveQuality. l1/l2 are corner-to-center\n // offset magnitudes (already radii, not diameters \u2014 see v1/v2 below).\n let maxRadius = uniforms.viewport.y * uniforms.params.w;\n let extent = max(l1, l2);\n // Same factor both shrinks the quad geometry and fades its alpha \u2014 capped\n // splats fade out as they're clamped, rather than abruptly losing their\n // true aspect ratio.\n let sizeFade = clamp(maxRadius / max(extent, 1.0), 0.0, 1.0);\n\n // \u2500\u2500 View-dependent SH colour \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Camera world-space position from the view matrix:\n // view = [R | -R*cam], so cam = -R\u1D40 * t where t = view[3].xyz\n // In column-major WGSL: cam.x = -dot(view[0].xyz, view[3].xyz), etc.\n let shDeg = u32(uniforms.shParams.x);\n let shBases = u32(uniforms.shParams.y);\n var rgb = g.color.rgb;\n if (shDeg > 0u) {\n let camWorld = vec3<f32>(\n -dot(uniforms.view[0].xyz, uniforms.view[3].xyz),\n -dot(uniforms.view[1].xyz, uniforms.view[3].xyz),\n -dot(uniforms.view[2].xyz, uniforms.view[3].xyz)\n );\n // Reference convention (3DGS eval_sh): dir = point - camera (outward\n // along the view ray), not camera - point. Degree-1/3 terms are odd in\n // dir, so this sign was previously inverting half the SH contribution.\n let shDir = normalize(worldPos4.xyz - camWorld);\n rgb = rgb + evalSH(gi, shDir, shDeg, shBases);\n }\n // Single clamp after the full DC + SH-rest sum \u2014 g.color.rgb (the DC term)\n // is intentionally left unclamped by the loader, since clamping it before\n // adding the higher-order terms would bake in the wrong value.\n rgb = clamp(rgb, vec3<f32>(0.0), vec3<f32>(1.0));\n\n let clip = uniforms.proj * viewPos4;\n let ndcXY = clip.xy / clip.w;\n\n var o: SplatGeom;\n o.ndc = vec4<f32>(ndcXY, clip.z / clip.w, 0.0);\n o.v1 = v1 * sizeFade;\n o.v2 = v2 * sizeFade;\n o.color = vec4<f32>(rgb, g.color.a * nearFade * sizeFade * maskFade * aaFactor);\n splatGeomOut[gi] = o;\n\n // TEMP debug instrumentation \u2014 see binding(7) declaration above. Writable\n // directly here (compute allows read_write storage; the old vertex-shader\n // version had to smuggle these out via varyings into fs_main instead).\n if gi == u32(uniforms.shParams.w) {\n debugOut[0] = t.z; debugOut[1] = a; debugOut[2] = b; debugOut[3] = c;\n debugOut[4] = det; debugOut[5] = lambda1; debugOut[6] = lambda2; debugOut[7] = extent;\n debugOut[8] = sizeFade; debugOut[9] = nearFade; debugOut[10] = aaFactor; debugOut[11] = o.color.a;\n debugOut[12] = rgb.x; debugOut[13] = rgb.y; debugOut[14] = rgb.z; debugOut[15] = worldPos4.x;\n }\n}\n\n@vertex\nfn vs_main(\n @builtin(vertex_index) vi : u32,\n @builtin(instance_index) ii : u32,\n) -> VOut {\n // Quad corners (two triangles, CCW)\n const corners = array<vec2<f32>, 6>(\n vec2<f32>(-1.0, -1.0), vec2<f32>( 1.0, -1.0), vec2<f32>(-1.0, 1.0),\n vec2<f32>( 1.0, -1.0), vec2<f32>( 1.0, 1.0), vec2<f32>(-1.0, 1.0)\n );\n\n let gi = order[ii];\n let geo = splatGeomIn[gi];\n let corner = corners[vi];\n\n var o: VOut;\n if geo.ndc.z > 1.5 {\n o.clipPos = vec4<f32>(0.0, 0.0, 2.0, 1.0);\n o.color = vec4<f32>(0.0);\n o.uv = vec2<f32>(0.0);\n return o;\n }\n\n // corner (\u00B11,\u00B11) is split across the two eigenvector axes (v1, v2) instead\n // of a single axis-aligned radius \u2014 this is what lets fs_main use a plain\n // circular falloff over the unwarped corner UV.\n let pixOff = corner.x * geo.v1 + corner.y * geo.v2;\n let ndcOff = pixOff / (uniforms.viewport * 0.5);\n\n o.clipPos = vec4<f32>(geo.ndc.xy + ndcOff, geo.ndc.z, 1.0);\n o.color = geo.color;\n o.uv = corner;\n return o;\n}\n\n@fragment\nfn fs_main(in: VOut) -> @location(0) vec4<f32> {\n // Plain Gaussian falloff \u2014 matches the original 3DGS reference renderer and\n // mkkellogg/GaussianSplats3D (an independent, widely-used implementation),\n // not PlayCanvas's hard-edged normExp, which turned out to be a\n // PlayCanvas-specific stylistic choice rather than a more \"correct\" shape.\n // in.uv (raw \u00B11 quad corner) is built so dot(uv,uv)=1 at the edge\n // corresponds to a true Mahalanobis\u00B2 of 8 (since the quad extent is\n // sqrt(8\u00B7\u03BB) \u2014 see cs_preprocess), so the equivalent exp(-0.5\u00B78\u00B7A) = exp(-4\u00B7A).\n let A = dot(in.uv, in.uv);\n if A > 1.0 { discard; }\n let alpha = in.color.a * exp(-4.0 * A);\n if alpha < 1.0/255.0 { discard; }\n let gamma = max(uniforms.params.z, 0.01);\n let rgb = pow(max(in.color.rgb, vec3<f32>(0.0)), vec3<f32>(1.0 / gamma));\n return vec4<f32>(rgb * alpha, alpha);\n}\n`;\n", "/**\n * GPU compute-shader radix sort kernels for back-to-front depth ordering.\n *\n * Mirrors the CPU algorithm in src/sorter.js: 4 passes of 8-bit-bucket radix\n * sort over depth keys, where view-space z (always negative) is converted to\n * a sortable u32 by flipping all bits of its IEEE-754 representation.\n *\n * Pipeline (see Renderer#runGpuSort):\n * cs_depth_key - one thread per splat: computes view-space depth, writes\n * the initial key/index arrays (keysA/idxA).\n * cs_histogram - per 256-element workgroup: counts the current pass's\n * byte into a per-workgroup 256-bin histogram.\n * cs_scan_reduce - per 256-workgroup chunk: turns per-workgroup histograms\n * into chunk-local per-bin exclusive prefixes (blockPfx)\n * and per-chunk per-bin totals (chunkTotal).\n * cs_scan_global - single workgroup: scans chunkTotal into per-chunk\n * per-bin offsets (chunkPfx) and global per-bin exclusive\n * prefixes (globalPfx).\n * cs_scan_combine- per 256-element workgroup: adds chunkPfx into blockPfx\n * so it becomes globally-relative.\n * cs_scatter - per 256-element workgroup: scatters each element to its\n * sorted position using globalPfx + blockPfx + a stable\n * intra-workgroup rank.\n *\n * cs_scan is split into reduce/global/combine so no single dispatch does\n * O(numWorkgroups) sequential work - each stage is bounded to ~256\n * iterations regardless of scene size, which matters on mobile GPU drivers\n * with aggressive watchdog (TDR) timeouts.\n *\n * Buffers ping-pong between the \"A\" and \"B\" key/index arrays - each kernel\n * picks source/destination by sortParams.passIndex parity via the readKey/writeKey\n * helpers below, so no bind-group rebuilds are needed between passes. After 4\n * passes (even), the result lands back in idxA, which is _orderBuf.\n */\n\nexport const SORT_SHADER = /* wgsl */`\n\nstruct Uniforms {\n view : mat4x4<f32>,\n proj : mat4x4<f32>,\n viewport : vec2<f32>,\n focal : vec2<f32>,\n params : vec4<f32>,\n};\n\nstruct Gaussian {\n pos : vec3<f32>,\n part : f32,\n color : vec4<f32>,\n scale : vec3<f32>,\n quat : vec4<f32>,\n};\n\nstruct SortParams {\n numElements : u32,\n passIndex : u32,\n numWorkgroups : u32,\n numChunks : u32,\n};\n\n@group(0) @binding(0) var<uniform> uniforms : Uniforms;\n@group(0) @binding(1) var<storage, read> gaussians : array<Gaussian>;\n@group(0) @binding(2) var<storage, read> transforms : array<mat4x4<f32>>;\n@group(0) @binding(3) var<uniform> sortParams : SortParams;\n@group(0) @binding(4) var<storage, read_write> keysA : array<u32>;\n@group(0) @binding(5) var<storage, read_write> keysB : array<u32>;\n@group(0) @binding(6) var<storage, read_write> idxA : array<u32>;\n@group(0) @binding(7) var<storage, read_write> idxB : array<u32>;\n@group(0) @binding(8) var<storage, read_write> histo : array<u32>;\n@group(0) @binding(9) var<storage, read_write> blockPfx : array<u32>;\n@group(0) @binding(10) var<storage, read_write> globalPfx : array<u32>;\n@group(0) @binding(11) var<storage, read_write> chunkTotal : array<u32>;\n@group(0) @binding(12) var<storage, read_write> chunkPfx : array<u32>;\n\n// \u2500\u2500 Ping-pong helpers: pass even -> A is source, B is dest; pass odd -> reversed \u2500\u2500\n\nfn readKey(i: u32, p: u32) -> u32 {\n if (p % 2u == 0u) { return keysA[i]; } else { return keysB[i]; }\n}\nfn writeKey(i: u32, p: u32, v: u32) {\n if (p % 2u == 0u) { keysB[i] = v; } else { keysA[i] = v; }\n}\nfn readIdx(i: u32, p: u32) -> u32 {\n if (p % 2u == 0u) { return idxA[i]; } else { return idxB[i]; }\n}\nfn writeIdx(i: u32, p: u32, v: u32) {\n if (p % 2u == 0u) { idxB[i] = v; } else { idxA[i] = v; }\n}\n\n// \u2500\u2500 cs_depth_key: seed keys/indices from view-space depth \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n@compute @workgroup_size(256)\nfn cs_depth_key(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x;\n if (i >= sortParams.numElements) { return; }\n\n let g = gaussians[i];\n let partId = u32(g.part);\n let world = transforms[partId] * vec4<f32>(g.pos, 1.0);\n let view = uniforms.view * world;\n\n // Flip all bits of the (always-negative) IEEE-754 depth so ascending u32\n // order matches back-to-front draw order - same trick as src/sorter.js.\n let bits = bitcast<u32>(view.z);\n keysA[i] = bits ^ 0xffffffffu;\n idxA[i] = i;\n}\n\n// \u2500\u2500 cs_histogram: per-workgroup 256-bin histogram of the current byte \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nvar<workgroup> localHist: array<atomic<u32>, 256>;\n\n@compute @workgroup_size(256)\nfn cs_histogram(\n @builtin(global_invocation_id) gid: vec3<u32>,\n @builtin(local_invocation_id) lid: vec3<u32>,\n @builtin(workgroup_id) wgid: vec3<u32>,\n) {\n atomicStore(&localHist[lid.x], 0u);\n workgroupBarrier();\n\n let i = gid.x;\n if (i < sortParams.numElements) {\n let key = readKey(i, sortParams.passIndex);\n let bin = (key >> (sortParams.passIndex * 8u)) & 0xffu;\n atomicAdd(&localHist[bin], 1u);\n }\n workgroupBarrier();\n\n histo[wgid.x * 256u + lid.x] = atomicLoad(&localHist[lid.x]);\n}\n\n// \u2500\u2500 cs_scan_reduce: per-chunk per-bin exclusive prefixes + chunk totals \u2500\u2500\u2500\u2500\u2500\n//\n// Workgroups of histogram blocks are grouped into chunks of 256. Each\n// dispatched workgroup (one per chunk) computes, for its chunk, the\n// chunk-local exclusive prefix sum of histo[] into blockPfx[] (relative to\n// the chunk's first block) plus the chunk's total per bin (chunkTotal[]).\n// Bounded to 256 iterations per thread regardless of scene size.\n\n@compute @workgroup_size(256)\nfn cs_scan_reduce(\n @builtin(local_invocation_id) lid: vec3<u32>,\n @builtin(workgroup_id) wgid: vec3<u32>,\n) {\n let bin = lid.x;\n let c = wgid.x;\n let nwg = sortParams.numWorkgroups;\n var running: u32 = 0u;\n for (var j = 0u; j < 256u; j = j + 1u) {\n let wg = c * 256u + j;\n if (wg < nwg) {\n blockPfx[wg * 256u + bin] = running;\n running = running + histo[wg * 256u + bin];\n }\n }\n chunkTotal[c * 256u + bin] = running;\n}\n\n// \u2500\u2500 cs_scan_global: scan chunk totals + compute global per-bin offsets \u2500\u2500\u2500\u2500\u2500\n//\n// Single workgroup. Each thread (one per bin) scans chunkTotal across all\n// chunks into chunkPfx (bounded to numChunks <= 256 iterations even for\n// huge scenes), then thread 0 scans the 256 per-bin totals into globalPfx.\n\nvar<workgroup> binTotal: array<u32, 256>;\n\n@compute @workgroup_size(256)\nfn cs_scan_global(@builtin(local_invocation_id) lid: vec3<u32>) {\n let bin = lid.x;\n let numChunks = sortParams.numChunks;\n var running: u32 = 0u;\n for (var c = 0u; c < numChunks; c = c + 1u) {\n chunkPfx[c * 256u + bin] = running;\n running = running + chunkTotal[c * 256u + bin];\n }\n binTotal[bin] = running;\n workgroupBarrier();\n\n if (bin == 0u) {\n var acc: u32 = 0u;\n for (var b = 0u; b < 256u; b = b + 1u) {\n globalPfx[b] = acc;\n acc = acc + binTotal[b];\n }\n }\n}\n\n// \u2500\u2500 cs_scan_combine: fold chunk offsets into blockPfx \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// blockPfx[] currently holds chunk-local exclusive prefixes (from\n// cs_scan_reduce); add each block's chunk offset (chunkPfx) to make it\n// globally-relative. One thread per (workgroup, bin) - O(1) each.\n\n@compute @workgroup_size(256)\nfn cs_scan_combine(\n @builtin(local_invocation_id) lid: vec3<u32>,\n @builtin(workgroup_id) wgid: vec3<u32>,\n) {\n let bin = lid.x;\n let wg = wgid.x;\n let chunk = wg / 256u;\n blockPfx[wg * 256u + bin] = blockPfx[wg * 256u + bin] + chunkPfx[chunk * 256u + bin];\n}\n\n// \u2500\u2500 cs_scatter: stable scatter to sorted positions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nvar<workgroup> wgBin: array<u32, 256>;\nvar<workgroup> wgRank: array<u32, 256>;\nvar<workgroup> counts: array<u32, 256>;\n\n@compute @workgroup_size(256)\nfn cs_scatter(\n @builtin(global_invocation_id) gid: vec3<u32>,\n @builtin(local_invocation_id) lid: vec3<u32>,\n @builtin(workgroup_id) wgid: vec3<u32>,\n) {\n let i = gid.x;\n let p = sortParams.passIndex;\n let inBounds = i < sortParams.numElements;\n\n var key: u32 = 0u;\n var idx: u32 = 0u;\n var bin: u32 = 256u; // sentinel for out-of-bounds lanes\n if (inBounds) {\n key = readKey(i, p);\n idx = readIdx(i, p);\n bin = (key >> (p * 8u)) & 0xffu;\n }\n wgBin[lid.x] = bin;\n counts[lid.x] = 0u;\n workgroupBarrier();\n\n // Single-threaded, in-order pass over this workgroup's 256 lanes: assigns\n // each element a stable rank among same-bin elements (required so each\n // 8-bit pass preserves the ordering established by previous passes).\n // counts lives in workgroup memory (zeroed above by all 256 lanes in\n // parallel) rather than a per-invocation private array, which would be\n // allocated 256x over on some mobile GPU drivers.\n if (lid.x == 0u) {\n for (var k = 0u; k < 256u; k = k + 1u) {\n let b = wgBin[k];\n if (b < 256u) {\n wgRank[k] = counts[b];\n counts[b] = counts[b] + 1u;\n }\n }\n }\n workgroupBarrier();\n\n if (inBounds) {\n let dst = globalPfx[bin] + blockPfx[wgid.x * 256u + bin] + wgRank[lid.x];\n writeKey(dst, p, key);\n writeIdx(dst, p, idx);\n }\n}\n`;\n\n// Elements processed by each 256-thread workgroup (1 element/thread) for\n// cs_depth_key, cs_histogram and cs_scatter dispatches.\nexport const ELEMS_PER_WG = 256;\n\n/** Number of 256-element workgroups needed to cover `count` elements. */\nexport function numWorkgroups(count) {\n return Math.ceil(count / ELEMS_PER_WG);\n}\n\n/** Number of 256-workgroup chunks cs_scan_reduce/cs_scan_global operate over. */\nexport function numChunks(count) {\n return Math.ceil(numWorkgroups(count) / 256);\n}\n", "/**\n * WebGPU renderer for Gaussian splatting.\n *\n * Manages device, pipeline, bind groups, and GPU buffers.\n * Upload happens once (gaussians); order buffer is updated every frame.\n *\n * Buffer layout (must match src/shaders.js):\n * Uniforms : 160 bytes (40 \u00D7 f32)\n * Gaussians : N \u00D7 64 bytes (16 \u00D7 f32 each)\n * Order : N \u00D7 4 bytes (u32 indices, sorted back-to-front)\n */\nimport { SHADER } from './shaders.js';\nimport { SORT_SHADER, numWorkgroups, numChunks } from './sort-shaders.js';\n\n// Fullscreen blit: copies the HDR (rgba16float) composited image to the\n// canvas's swapchain format. Splats are blended directly into an 8-bit\n// canvas in most simple renderers \u2014 but a single pixel can be covered by\n// dozens of overlapping translucent splats, and 8-bit blending accumulates\n// rounding error on every single blend. Compositing in float and quantizing\n// only once, at the very end, avoids that.\nconst BLIT_SHADER = /* wgsl */`\n@group(0) @binding(0) var hdrTex : texture_2d<f32>;\n@group(0) @binding(1) var hdrSampler : sampler;\n\nstruct VOut {\n @builtin(position) pos : vec4<f32>,\n @location(0) uv : vec2<f32>,\n};\n\n@vertex\nfn vs_main(@builtin(vertex_index) vi : u32) -> VOut {\n const pos = array<vec2<f32>, 3>(\n vec2<f32>(-1.0, -1.0), vec2<f32>(3.0, -1.0), vec2<f32>(-1.0, 3.0)\n );\n var o: VOut;\n o.pos = vec4<f32>(pos[vi], 0.0, 1.0);\n o.uv = (pos[vi] * vec2<f32>(0.5, -0.5)) + vec2<f32>(0.5, 0.5);\n return o;\n}\n\n@fragment\nfn fs_main(in: VOut) -> @location(0) vec4<f32> {\n return textureSample(hdrTex, hdrSampler, in.uv);\n}\n`;\n\n// Uniform buffer offsets (in float32 index units)\nconst U_VIEW = 0; // mat4 (16 floats)\nconst U_PROJ = 16; // mat4 (16 floats)\nconst U_VIEWPORT = 32; // vec2 (2 floats)\nconst U_FOCAL = 34; // vec2 (2 floats)\nconst U_PARAMS = 36; // vec4 (4 floats), .x = splatScale .w = radiusCap\nconst U_SH_PARAMS = 40; // vec4 (4 floats), .x = shDegree .y = numSHBases .z = aaDilation\nconst U_AA_DILATION = 42; // within shParams.z\nconst U_SIZE = 44; // total floats \u2192 176 bytes\n\nexport class Renderer {\n constructor(canvas, background) {\n this.canvas = canvas;\n this.background = parseBackground(background);\n this.device = null;\n this.context = null;\n this.pipeline = null;\n this.bindGroup = null;\n this._mainModule = null; // shared SHADER module \u2014 see _createPipeline/_createPreprocessPipeline\n\n // HDR (rgba16float) intermediate target + blit pipeline \u2014 see BLIT_SHADER.\n this._hdrFormat = 'rgba16float';\n this._hdrTexture = null;\n this._hdrView = null;\n this._blitPipeline = null;\n this._blitSampler = null;\n this._blitBindGroup = null;\n\n // GPU buffers\n this._uniformBuf = null;\n this._gaussianBuf = null;\n this._orderBuf = null; // also bound as idxA for the GPU sort\n this._transformBuf = null;\n this._maskVolBuf = null; // MaskUniforms (uniform, 656 bytes)\n this._partVolMaskBuf = null; // u32 per part (storage)\n\n // Per-splat preprocess (see shaders.js cs_preprocess) \u2014 covariance/eigen/\n // SH math computed once per splat instead of once per vertex. _splatGeomBuf\n // holds the SplatGeom struct (48 bytes/splat) the compute pass writes and\n // vs_main reads; _preprocessParamsBuf carries the live splat count.\n this._splatGeomBuf = null;\n this._preprocessParamsBuf = null;\n this._preprocessPipeline = null;\n this._preprocessBindGroup = null;\n this._preprocessCountData = new Uint32Array(4); // scratch for preprocess()'s writeBuffer\n\n // SH coefficients buffer \u2014 null when shDegree=0; rebuilt by uploadSH/allocateSH.\n // _shDummyBuf is a single vec3<f32> bound in place of _shBuf when no SH data exists.\n this._shBuf = null;\n this._shDummyBuf = null;\n this._shNumBases = 0;\n\n // GPU radix sort buffers/pipelines (see src/sort-shaders.js) \u2014 sized for\n // the current splat count in uploadGaussians; _globalPfxBuf is fixed-size.\n this._keysBufA = null;\n this._keysBufB = null;\n this._idxBufB = null;\n this._histoBuf = null;\n this._blockPfxBuf = null;\n this._globalPfxBuf = null;\n this._chunkTotalBuf = null;\n this._chunkPfxBuf = null;\n this._sortParamsBuf = null;\n this._sortParamsStaging = null;\n // 5 slots x {numElements, passIndex, numWorkgroups, numChunks}: slot 0 for\n // cs_depth_key (passIndex unused), slots 1-4 for the 4 radix passes \u2014 see\n // runGpuSort.\n this._sortParamsData = new Uint32Array(20);\n\n this._depthKeyPipeline = null;\n this._histogramPipeline = null;\n this._scanReducePipeline = null;\n this._scanGlobalPipeline = null;\n this._scanCombinePipeline = null;\n this._scatterPipeline = null;\n this._depthKeyBindGroup = null;\n this._histogramBindGroup = null;\n this._scanReduceBindGroup = null;\n this._scanGlobalBindGroup = null;\n this._scanCombineBindGroup = null;\n this._scatterBindGroup = null;\n\n // Set true by the uncapturederror handler (see init) if a GPU validation\n // error occurs \u2014 Viewer#_tick stops calling runGpuSort once this is set,\n // falling back to the CPU sorter without crashing.\n this._gpuSortFailed = false;\n\n // CPU-side uniform data \u2014 shared ArrayBuffer so float and u32 views alias.\n this._uniforms = new Float32Array(U_SIZE);\n this._uniformsU32 = new Uint32Array(this._uniforms.buffer);\n this._uniforms[U_PARAMS] = 1.08; // splatScale default \u2014 slightly >1 to close coverage gaps on sparse/specular regions (overwritten by Viewer#setSplatScale on init)\n this._uniforms[U_PARAMS + 2] = 1.0; // gamma = 1.0 (linear)\n this._uniforms[U_PARAMS + 3] = 1.0; // radiusCap = 1.0 \u00D7 viewport.y (safety net, not a routine constraint \u2014 see Viewer)\n // shParams: degree=0, numBases=0 (no SH), aaDilation=0.3 (matches PlayCanvas/SuperSplat's gsplat shader)\n this._uniforms[U_AA_DILATION] = 0.3;\n\n this._numSplats = 0;\n this._maskWarned = new Set();\n }\n\n // \u2500\u2500 Initialise WebGPU \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async init() {\n if (!navigator.gpu) throw new Error('WebGPU is not supported in this browser.');\n const adapter = await navigator.gpu.requestAdapter();\n if (!adapter) throw new Error('No WebGPU adapter found.');\n this.device = await adapter.requestDevice({\n requiredLimits: {\n maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,\n maxBufferSize: adapter.limits.maxBufferSize,\n },\n });\n\n // Defensive diagnostics: a GPU validation error (e.g. from a future\n // sort-shader bug) disables GPU sort for subsequent frames instead of\n // spamming errors or relying on the page to crash. device.lost can't be\n // recovered from here (would need to recreate the device + all GPU\n // resources) \u2014 just log it clearly for diagnosis.\n this.device.addEventListener('uncapturederror', (event) => {\n console.error('[HoloSplat] GPU error:', event.error);\n this._gpuSortFailed = true;\n });\n this.device.lost.then((info) => {\n console.error('[HoloSplat] WebGPU device lost:', info.reason, info.message);\n });\n\n this.context = this.canvas.getContext('webgpu');\n this._format = navigator.gpu.getPreferredCanvasFormat();\n this.context.configure({\n device: this.device,\n format: this._format,\n alphaMode: 'premultiplied',\n });\n\n this._createPipeline();\n this._createBlitPipeline();\n this._createSortPipelines();\n this._createPreprocessPipeline();\n this._uniformBuf = this._createBuffer(U_SIZE * 4,\n GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);\n this._preprocessParamsBuf = this._createBuffer(16,\n GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);\n\n // Dummy SH buffer \u2014 one vec3<f32> \u2014 bound when shDegree=0 so the bind\n // group is always valid even before SH data is uploaded.\n this._shDummyBuf = this._createBuffer(12, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);\n\n // TEMP debug instrumentation \u2014 see shaders.js binding(7) / setDebugIndex.\n this._debugBuf = this._createBuffer(16 * 4, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC);\n this._debugReadBuf = this._createBuffer(16 * 4, GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST);\n this._uniforms[U_SH_PARAMS + 3] = -1; // disabled by default\n\n // GPU sort: fixed-size buffers (256 bins) \u2014 created once.\n this._globalPfxBuf = this._createBuffer(256 * 4, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);\n this._sortParamsBuf = this._createBuffer(16, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);\n // Staging buffer for all 5 SortParams slots \u2014 copied into _sortParamsBuf\n // slot-by-slot inside the command encoder (see runGpuSort). Avoids the\n // need for separate queue.submit() calls or per-pass bind groups, since\n // queue.writeBuffer() calls all land before any encoded dispatch runs.\n this._sortParamsStaging = this._createBuffer(16 * 5, GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST);\n\n // Default transforms buffer: one identity mat4 (single-part scenes)\n const identity = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]);\n this._transformBuf = this._createBuffer(64, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);\n this.device.queue.writeBuffer(this._transformBuf, 0, identity);\n\n // Mask volume buffers \u2014 zeroed by default (count=0 \u2192 no masking)\n // Layout: 16-byte header (vec4<u32>, .x = count) + 8 \u00D7 MaskVolume (80 bytes each) = 656 bytes\n this._maskVolBuf = this._createBuffer(656, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);\n // One u32 per part; zero means no volumes affect this part\n this._partVolMaskBuf = this._createBuffer(4, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);\n }\n\n // \u2500\u2500 Upload scene data (call once after load) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n uploadGaussians(data, count) {\n this._numSplats = count;\n\n // Gaussian storage buffer (read-once upload). Deliberately NOT calling\n // .destroy() on the outgoing buffer here: uploadGaussians can run\n // mid-session (setFlipY, variant swaps that change splat count) while\n // the render loop's runGpuSort still has commands for the *previous*\n // buffers in flight on the queue. Destroying a buffer still referenced\n // by unfinished submitted work raises a GPU validation error, which\n // permanently disables GPU sort for the rest of the session (see\n // _gpuSortFailed in init()/uncapturederror). Dropping the reference and\n // letting it get GC'd once the GPU is done with it is the safe pattern.\n this._gaussianBuf = this.device.createBuffer({\n size: data.byteLength,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n });\n this.device.queue.writeBuffer(this._gaussianBuf, 0, data);\n\n // Order buffer (rewritten every frame). Also bound as idxA for the GPU\n // sort (COPY_SRC so debugReadOrder can read it back for verification).\n this._orderBuf = this.device.createBuffer({\n size: count * 4,\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,\n });\n\n // Per-splat preprocess output (see shaders.js SplatGeom) \u2014 48 bytes/splat\n // (vec4 ndc + vec2 v1 + vec2 v2 + vec4 color). Written by cs_preprocess,\n // read by vs_main. Same in-flight-GPU-work hazard as above \u2014 no .destroy().\n this._splatGeomBuf = this.device.createBuffer({\n size: Math.max(count * 48, 48),\n usage: GPUBufferUsage.STORAGE,\n });\n\n // GPU sort scratch buffers, sized for this splat count. See the\n // .destroy() note above \u2014 same in-flight-GPU-work hazard applies here.\n const u32Buf = (n) => this.device.createBuffer({\n size: Math.max(n * 4, 4),\n usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,\n });\n this._keysBufA = u32Buf(count);\n this._keysBufB = u32Buf(count);\n this._idxBufB = u32Buf(count);\n const nwg = numWorkgroups(count);\n const nch = numChunks(count);\n this._histoBuf = u32Buf(nwg * 256);\n this._blockPfxBuf = u32Buf(nwg * 256);\n this._chunkTotalBuf = u32Buf(nch * 256);\n this._chunkPfxBuf = u32Buf(nch * 256);\n\n this._rebuildBindGroup();\n this._rebuildPreprocessBindGroup();\n this._rebuildSortBindGroups();\n }\n\n // \u2500\u2500 Per-frame updates \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n updateUniforms({ view, proj, width, height, focal, near = 0.01, radiusCap }) {\n const u = this._uniforms;\n u.set(view, U_VIEW);\n u.set(proj, U_PROJ);\n u[U_VIEWPORT] = width;\n u[U_VIEWPORT + 1] = height;\n u[U_FOCAL] = focal;\n u[U_FOCAL + 1] = focal;\n u[U_PARAMS + 1] = near;\n if (radiusCap != null) u[U_PARAMS + 3] = radiusCap;\n this.device.queue.writeBuffer(this._uniformBuf, 0, u);\n }\n\n updateOrder(sortedIndices, count) {\n // Defensive clamp against two independent size mismatches, either of\n // which makes the requested write exceed a buffer's actual bytes:\n // - _orderBuf (destination) is sized as of the last uploadGaussians().\n // - sortedIndices (source) comes from the async CPU sorter\n // (sorter.js's createSorter), which can return a *stale* result from\n // a previous, smaller scene's sort while the worker is still busy\n // computing the first sort for a newly-loaded, larger scene.\n // Either way, writeBuffer throws (crashing the render loop on every\n // subsequent frame, since _tick doesn't catch) rather than just\n // producing a wrong-for-one-frame sort order. Skipping a frame's sort\n // update is harmless; crashing the whole render loop is not.\n const n = Math.min(count, this._orderBuf.size / 4, sortedIndices.length);\n if (n < count) {\n console.warn(`[HoloSplat] updateOrder: requested count (${count}) exceeds available buffer/source size (${n}) \u2014 clamping this frame's sort update`);\n }\n if (n <= 0) return;\n this.device.queue.writeBuffer(\n this._orderBuf, 0,\n sortedIndices.buffer, 0, n * 4\n );\n }\n\n /** Upload a new transforms buffer (array of Float32Array(16), one mat4 per part).\n * Rebuilds the bind group \u2014 call once after uploadGaussians. */\n uploadTransforms(transforms) {\n const flat = new Float32Array(transforms.length * 16);\n for (let i = 0; i < transforms.length; i++) flat.set(transforms[i], i * 16);\n // No .destroy() on the outgoing buffer \u2014 see uploadGaussians for why.\n this._transformBuf = this._createBuffer(\n flat.byteLength,\n GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST\n );\n this.device.queue.writeBuffer(this._transformBuf, 0, flat);\n if (this._gaussianBuf) {\n this._rebuildPreprocessBindGroup();\n // _depthKeyBindGroup also references _transformBuf \u2014 rebuild GPU sort\n // bind groups too, or they'd point at the just-destroyed buffer.\n this._rebuildSortBindGroups();\n }\n }\n\n /** Write updated transform data each frame without reallocating (transforms array must\n * be the same length as the last uploadTransforms call). */\n updateTransforms(flat) {\n if (this._transformBuf) this.device.queue.writeBuffer(this._transformBuf, 0, flat);\n }\n\n /** Upload per-part volume bitmask (one u32 per part, bit i = affected by volume i).\n * Call after loading parts or attaching a new animation with volumes. */\n uploadPartVolumeMask(masks) {\n // No .destroy() on the outgoing buffer \u2014 see uploadGaussians for why.\n this._partVolMaskBuf = this._createBuffer(\n Math.max(masks.byteLength, 4),\n GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST\n );\n this.device.queue.writeBuffer(this._partVolMaskBuf, 0, masks);\n if (this._gaussianBuf) this._rebuildPreprocessBindGroup();\n }\n\n /** Write current-frame inverse matrices for all active volumes.\n * @param {Array<{matrix: Float32Array(16), softEdge: number}>} volumes */\n updateMaskVolumes(volumes) {\n const count = Math.min(volumes.length, 8);\n const arrBuf = new ArrayBuffer(656);\n const u32v = new Uint32Array(arrBuf);\n const f32v = new Float32Array(arrBuf);\n\n u32v[0] = count; // header.x = count (bytes 0-3); bytes 4-15 = 0 (pad)\n\n for (let i = 0; i < count; i++) {\n const byteBase = 16 + i * 80;\n const f32Base = byteBase >> 2; // / 4\n const vol = volumes[i];\n const inv = invertMat4(vol.matrix);\n if (inv) {\n f32v.set(inv, f32Base); // invMatrix (16 floats)\n } else if (!this._maskWarned.has(vol.name)) {\n this._maskWarned.add(vol.name);\n console.warn(`[HoloSplat] mask volume \"${vol.name}\" has a non-invertible (degenerate) transform \u2014 it will have no effect. Check that its Blender object has non-zero scale on all 3 axes.`);\n }\n f32v[f32Base + 16] = vol.softEdge ?? 0.05; // params.x\n // params.yzw = 0 (padding, already zero)\n }\n\n this.device.queue.writeBuffer(this._maskVolBuf, 0, arrBuf);\n }\n\n /** Patch a region of the Gaussian buffer in-place (no reallocation).\n * Used for progressive streaming: call after uploadGaussians to fill in chunks.\n * @param {Float32Array} data chunk of decoded splats (n \u00D7 16 floats)\n * @param {number} vertexOffset first vertex index to overwrite */\n patchGaussians(data, vertexOffset) {\n if (!this._gaussianBuf) return;\n this.device.queue.writeBuffer(this._gaussianBuf, vertexOffset * 64, data);\n }\n\n setSplatScale(s) {\n this._uniforms[U_PARAMS] = s;\n }\n\n setGamma(g) {\n this._uniforms[U_PARAMS + 2] = g;\n }\n\n /** Upload a full SH coefficients buffer (non-streaming scenes).\n * shData: Float32Array(count \u00D7 numSHBases \u00D7 3), or null to clear SH.\n * Rebuilds the bind group. */\n uploadSH(shData, numSHBases) {\n // No .destroy() on the outgoing buffer \u2014 see uploadGaussians for why.\n this._shBuf = null;\n this._shNumBases = numSHBases || 0;\n this._uniforms[U_SH_PARAMS + 1] = this._shNumBases;\n if (shData && this._shNumBases > 0) {\n this._shBuf = this._createBuffer(shData.byteLength, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);\n this.device.queue.writeBuffer(this._shBuf, 0, shData);\n }\n if (this._gaussianBuf) this._rebuildPreprocessBindGroup();\n }\n\n /** Pre-allocate the SH buffer for a streaming scene (zeros, no data yet).\n * Follow up with patchSH() as chunks arrive. */\n allocateSH(count, numSHBases) {\n // No .destroy() on the outgoing buffer \u2014 see uploadGaussians for why.\n this._shBuf = null;\n this._shNumBases = numSHBases || 0;\n this._uniforms[U_SH_PARAMS + 1] = this._shNumBases;\n if (this._shNumBases > 0 && count > 0) {\n this._shBuf = this._createBuffer(count * this._shNumBases * 12, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);\n }\n if (this._gaussianBuf) this._rebuildPreprocessBindGroup();\n }\n\n /** Write a chunk of decoded SH coefficients into the SH buffer at gaussianOffset.\n * shChunk: Float32Array(nVerts \u00D7 numSHBases \u00D7 3). */\n patchSH(shChunk, gaussianOffset) {\n if (this._shBuf && shChunk) {\n this.device.queue.writeBuffer(this._shBuf, gaussianOffset * this._shNumBases * 12, shChunk);\n }\n }\n\n /** Update the active SH degree written into the per-frame uniform. */\n setShDegree(deg) {\n this._uniforms[U_SH_PARAMS] = deg;\n }\n\n setAaDilation(v) {\n this._uniforms[U_AA_DILATION] = v;\n }\n\n /** TEMP debug instrumentation \u2014 see shaders.js binding(7). Set the\n * gaussian index to dump intermediate per-splat values for (-1 disables),\n * then call readDebug() after a frame has rendered. */\n setDebugIndex(i) {\n this._uniforms[U_SH_PARAMS + 3] = i;\n }\n\n /** Reads back the debug buffer written by the shader for the splat index\n * set via setDebugIndex(). Returns a plain object of named fields. */\n async readDebug() {\n const encoder = this.device.createCommandEncoder();\n encoder.copyBufferToBuffer(this._debugBuf, 0, this._debugReadBuf, 0, 16 * 4);\n this.device.queue.submit([encoder.finish()]);\n await this._debugReadBuf.mapAsync(GPUMapMode.READ);\n const data = new Float32Array(this._debugReadBuf.getMappedRange().slice(0));\n this._debugReadBuf.unmap();\n const [tz, a, b, c, det, lambda1, lambda2, extent, sizeFade, nearFade, aaFactor, finalAlpha, r, g, bl, worldX] = data;\n return { tz, a, b, c, det, lambda1, lambda2, extent, sizeFade, nearFade, aaFactor, finalAlpha, rgb: [r, g, bl], worldX };\n }\n\n setBackground(bg) {\n this.background = parseBackground(bg);\n }\n\n /** Runs cs_preprocess over the first `count` gaussians \u2014 must be called\n * every frame before runGpuSort/the CPU sort path and before draw(), since\n * it depends on this frame's view/proj/transforms. See shaders.js. */\n preprocess(count) {\n if (!count || !this._preprocessBindGroup) return;\n this._preprocessCountData[0] = count;\n this.device.queue.writeBuffer(this._preprocessParamsBuf, 0, this._preprocessCountData);\n\n const encoder = this.device.createCommandEncoder();\n const pass = encoder.beginComputePass();\n pass.setPipeline(this._preprocessPipeline);\n pass.setBindGroup(0, this._preprocessBindGroup);\n pass.dispatchWorkgroups(Math.ceil(count / 64));\n pass.end();\n this.device.queue.submit([encoder.finish()]);\n }\n\n // \u2500\u2500 Draw \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n draw(count = this._numSplats) {\n if (!count || !this.bindGroup) return;\n this._ensureHdrTexture();\n\n const encoder = this.device.createCommandEncoder();\n const splatPass = encoder.beginRenderPass({\n colorAttachments: [{\n view: this._hdrView,\n clearValue: this.background,\n loadOp: 'clear',\n storeOp: 'store',\n }],\n });\n splatPass.setPipeline(this.pipeline);\n splatPass.setBindGroup(0, this.bindGroup);\n splatPass.draw(6, count, 0, 0); // 6 verts/instance, count instances\n splatPass.end();\n\n const blitPass = encoder.beginRenderPass({\n colorAttachments: [{\n view: this.context.getCurrentTexture().createView(),\n loadOp: 'load', // fullscreen triangle overwrites every pixel \u2014 no clear needed\n storeOp: 'store',\n }],\n });\n blitPass.setPipeline(this._blitPipeline);\n blitPass.setBindGroup(0, this._blitBindGroup);\n blitPass.draw(3, 1, 0, 0); // fullscreen triangle\n blitPass.end();\n\n this.device.queue.submit([encoder.finish()]);\n }\n\n /** (Re)creates the HDR intermediate texture + its blit bind group to match\n * the canvas's current backing-buffer size. Cheap to call every frame \u2014\n * no-ops once sized. */\n _ensureHdrTexture() {\n const w = this.canvas.width, h = this.canvas.height;\n if (this._hdrTexture && this._hdrTexture.width === w && this._hdrTexture.height === h) return;\n this._hdrTexture?.destroy();\n this._hdrTexture = this.device.createTexture({\n size: [w, h],\n format: this._hdrFormat,\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,\n });\n this._hdrView = this._hdrTexture.createView();\n this._blitBindGroup = this.device.createBindGroup({\n layout: this._blitPipeline.getBindGroupLayout(0),\n entries: [\n { binding: 0, resource: this._hdrView },\n { binding: 1, resource: this._blitSampler },\n ],\n });\n }\n\n // \u2500\u2500 Cleanup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n destroy() {\n this._hdrTexture?.destroy();\n this._debugBuf?.destroy();\n this._debugReadBuf?.destroy();\n this._uniformBuf?.destroy();\n this._gaussianBuf?.destroy();\n this._orderBuf?.destroy();\n this._transformBuf?.destroy();\n this._maskVolBuf?.destroy();\n this._partVolMaskBuf?.destroy();\n this._keysBufA?.destroy();\n this._keysBufB?.destroy();\n this._idxBufB?.destroy();\n this._histoBuf?.destroy();\n this._blockPfxBuf?.destroy();\n this._globalPfxBuf?.destroy();\n this._chunkTotalBuf?.destroy();\n this._chunkPfxBuf?.destroy();\n this._sortParamsBuf?.destroy();\n this._sortParamsStaging?.destroy();\n this._shBuf?.destroy();\n this._shDummyBuf?.destroy();\n this._splatGeomBuf?.destroy();\n this._preprocessParamsBuf?.destroy();\n this.context?.unconfigure();\n }\n\n // \u2500\u2500 Private helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n _createPipeline() {\n // Shared with _createPreprocessPipeline() \u2014 one compiled module for the\n // whole SHADER source (vs_main/fs_main/cs_preprocess).\n this._mainModule = this._mainModule ?? this.device.createShaderModule({ code: SHADER });\n const module = this._mainModule;\n\n this.pipeline = this.device.createRenderPipeline({\n layout: 'auto',\n vertex: { module, entryPoint: 'vs_main' },\n fragment: {\n module,\n entryPoint: 'fs_main',\n targets: [{\n // Splats blend into the HDR intermediate target, not the canvas\n // directly \u2014 see BLIT_SHADER.\n format: this._hdrFormat,\n blend: {\n color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },\n alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },\n },\n }],\n },\n primitive: { topology: 'triangle-list', cullMode: 'none' },\n // No depth/stencil \u2014 Gaussians are manually sorted back-to-front\n });\n }\n\n _createBlitPipeline() {\n const module = this.device.createShaderModule({ code: BLIT_SHADER });\n this._blitPipeline = this.device.createRenderPipeline({\n layout: 'auto',\n vertex: { module, entryPoint: 'vs_main' },\n fragment: { module, entryPoint: 'fs_main', targets: [{ format: this._format }] },\n primitive: { topology: 'triangle-list' },\n });\n this._blitSampler = this.device.createSampler({ magFilter: 'linear', minFilter: 'linear' });\n }\n\n _createBuffer(size, usage) {\n return this.device.createBuffer({ size, usage });\n }\n\n /** Creates the GPU radix sort compute pipelines (see src/sort-shaders.js).\n * Each entry point only references the bindings it needs, so layout:'auto'\n * derives a distinct bind group layout per pipeline \u2014 see _rebuildSortBindGroups. */\n /** Creates the cs_preprocess compute pipeline (see shaders.js) \u2014 shares the\n * SHADER module with vs_main/fs_main so it can reuse the Gaussian/Uniforms\n * structs and evalSH/quatToMat3 helpers without duplicating them. */\n _createPreprocessPipeline() {\n this._mainModule = this._mainModule ?? this.device.createShaderModule({ code: SHADER });\n const module = this._mainModule;\n this._preprocessPipeline = this.device.createComputePipeline({\n layout: 'auto',\n compute: { module, entryPoint: 'cs_preprocess' },\n });\n }\n\n _createSortPipelines() {\n const module = this.device.createShaderModule({ code: SORT_SHADER });\n const make = (entryPoint) => this.device.createComputePipeline({\n layout: 'auto',\n compute: { module, entryPoint },\n });\n this._depthKeyPipeline = make('cs_depth_key');\n this._histogramPipeline = make('cs_histogram');\n this._scanReducePipeline = make('cs_scan_reduce');\n this._scanGlobalPipeline = make('cs_scan_global');\n this._scanCombinePipeline = make('cs_scan_combine');\n this._scatterPipeline = make('cs_scatter');\n }\n\n /** vs_main/fs_main now only touch uniforms, order, and the precomputed\n * splatGeom buffer \u2014 all the per-gaussian source data (gaussians,\n * transforms, masks, SH, debug) moved into cs_preprocess. See\n * _rebuildPreprocessBindGroup for that pipeline's (much larger) bind group. */\n _rebuildBindGroup() {\n this.bindGroup = this.device.createBindGroup({\n layout: this.pipeline.getBindGroupLayout(0),\n entries: [\n { binding: 0, resource: { buffer: this._uniformBuf } },\n { binding: 2, resource: { buffer: this._orderBuf } },\n { binding: 9, resource: { buffer: this._splatGeomBuf } },\n ],\n });\n }\n\n /** Rebuilds cs_preprocess's bind group. Called after uploadGaussians, and\n * whenever transforms/masks/SH change (those don't affect vs_main/fs_main\n * anymore \u2014 see _rebuildBindGroup \u2014 only the preprocess compute pass). */\n _rebuildPreprocessBindGroup() {\n this._preprocessBindGroup = this.device.createBindGroup({\n layout: this._preprocessPipeline.getBindGroupLayout(0),\n entries: [\n { binding: 0, resource: { buffer: this._uniformBuf } },\n { binding: 1, resource: { buffer: this._gaussianBuf } },\n { binding: 3, resource: { buffer: this._transformBuf } },\n { binding: 4, resource: { buffer: this._maskVolBuf } },\n { binding: 5, resource: { buffer: this._partVolMaskBuf } },\n { binding: 6, resource: { buffer: this._shBuf ?? this._shDummyBuf } },\n { binding: 7, resource: { buffer: this._debugBuf } },\n { binding: 8, resource: { buffer: this._splatGeomBuf } },\n { binding: 10, resource: { buffer: this._preprocessParamsBuf } },\n ],\n });\n }\n\n /** Rebuilds the GPU sort bind groups against the current per-frame\n * buffers (gaussians, transforms, order/idxA, keys/idx/histo/prefix scratch).\n * Called after uploadGaussians, since buffer sizes depend on splat count.\n * Each bind group is built from its own pipeline's derived layout \u2014 only\n * the bindings that pipeline's entry point actually references. */\n _rebuildSortBindGroups() {\n const entries = {\n 0: { buffer: this._uniformBuf },\n 1: { buffer: this._gaussianBuf },\n 2: { buffer: this._transformBuf },\n 3: { buffer: this._sortParamsBuf },\n 4: { buffer: this._keysBufA },\n 5: { buffer: this._keysBufB },\n 6: { buffer: this._orderBuf }, // idxA\n 7: { buffer: this._idxBufB },\n 8: { buffer: this._histoBuf },\n 9: { buffer: this._blockPfxBuf },\n 10: { buffer: this._globalPfxBuf },\n 11: { buffer: this._chunkTotalBuf },\n 12: { buffer: this._chunkPfxBuf },\n };\n const buildGroup = (pipeline, bindings) => this.device.createBindGroup({\n layout: pipeline.getBindGroupLayout(0),\n entries: bindings.map(binding => ({ binding, resource: entries[binding] })),\n });\n this._depthKeyBindGroup = buildGroup(this._depthKeyPipeline, [0, 1, 2, 3, 4, 6]);\n this._histogramBindGroup = buildGroup(this._histogramPipeline, [3, 4, 5, 8]);\n this._scanReduceBindGroup = buildGroup(this._scanReducePipeline, [3, 8, 9, 11]);\n this._scanGlobalBindGroup = buildGroup(this._scanGlobalPipeline, [3, 10, 11, 12]);\n this._scanCombineBindGroup = buildGroup(this._scanCombinePipeline, [9, 12]);\n this._scatterBindGroup = buildGroup(this._scatterPipeline, [3, 4, 5, 6, 7, 9, 10]);\n }\n\n /** Runs the GPU radix sort over the first `count` gaussians (4 passes of\n * depth_key -> histogram -> scan_reduce -> scan_global -> scan_combine ->\n * scatter). Result permutation lands back in _orderBuf (idxA) \u2014 ready for\n * vs_main's next draw, no CPU readback needed. See src/sort-shaders.js.\n *\n * Submitted as 5 separate queue.submit() calls (depth_key, then one per\n * radix pass) rather than one big command buffer \u2014 mobile driver TDR\n * watchdogs key off a single submission's GPU time, and bundling all ~13\n * array-sized dispatches into one submission risks tripping it on large\n * scenes. Each per-pass submission now carries at most ~3 array-sized\n * dispatches (histogram, scan_combine, scatter). */\n runGpuSort(count) {\n if (!count) return;\n const nwg = numWorkgroups(count);\n const nch = numChunks(count);\n\n // Fill all 5 SortParams slots up front and upload in one go. Per-pass\n // values are applied inside each submission's encoder via\n // copyBufferToBuffer \u2014 a queue.writeBuffer() per pass would all land\n // before any dispatch in these command buffers runs, since encoded\n // commands only execute at submit().\n const p = this._sortParamsData;\n for (let slot = 0; slot < 5; slot++) {\n const o = slot * 4;\n p[o] = count;\n p[o + 1] = slot === 0 ? 0 : slot - 1; // slot 0 = cs_depth_key (passIndex unused)\n p[o + 2] = nwg;\n p[o + 3] = nch;\n }\n this.device.queue.writeBuffer(this._sortParamsStaging, 0, p);\n\n {\n const encoder = this.device.createCommandEncoder();\n encoder.copyBufferToBuffer(this._sortParamsStaging, 0, this._sortParamsBuf, 0, 16);\n const pass = encoder.beginComputePass();\n pass.setPipeline(this._depthKeyPipeline);\n pass.setBindGroup(0, this._depthKeyBindGroup);\n pass.dispatchWorkgroups(nwg);\n pass.end();\n this.device.queue.submit([encoder.finish()]);\n }\n\n for (let i = 0; i < 4; i++) {\n const encoder = this.device.createCommandEncoder();\n encoder.copyBufferToBuffer(this._sortParamsStaging, (i + 1) * 16, this._sortParamsBuf, 0, 16);\n\n const histPass = encoder.beginComputePass();\n histPass.setPipeline(this._histogramPipeline);\n histPass.setBindGroup(0, this._histogramBindGroup);\n histPass.dispatchWorkgroups(nwg);\n histPass.end();\n\n const scanReducePass = encoder.beginComputePass();\n scanReducePass.setPipeline(this._scanReducePipeline);\n scanReducePass.setBindGroup(0, this._scanReduceBindGroup);\n scanReducePass.dispatchWorkgroups(nch);\n scanReducePass.end();\n\n const scanGlobalPass = encoder.beginComputePass();\n scanGlobalPass.setPipeline(this._scanGlobalPipeline);\n scanGlobalPass.setBindGroup(0, this._scanGlobalBindGroup);\n scanGlobalPass.dispatchWorkgroups(1);\n scanGlobalPass.end();\n\n const scanCombinePass = encoder.beginComputePass();\n scanCombinePass.setPipeline(this._scanCombinePipeline);\n scanCombinePass.setBindGroup(0, this._scanCombineBindGroup);\n scanCombinePass.dispatchWorkgroups(nwg);\n scanCombinePass.end();\n\n const scatterPass = encoder.beginComputePass();\n scatterPass.setPipeline(this._scatterPipeline);\n scatterPass.setBindGroup(0, this._scatterBindGroup);\n scatterPass.dispatchWorkgroups(nwg);\n scatterPass.end();\n\n this.device.queue.submit([encoder.finish()]);\n }\n }\n\n /** Debug/test helper: reads back _orderBuf (idxA) as a Uint32Array(count).\n * Not used in the render hot path \u2014 see examples/gpu-sort-test.html. */\n async debugReadOrder(count) {\n const byteSize = count * 4;\n const staging = this.device.createBuffer({\n size: byteSize,\n usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\n });\n const encoder = this.device.createCommandEncoder();\n encoder.copyBufferToBuffer(this._orderBuf, 0, staging, 0, byteSize);\n this.device.queue.submit([encoder.finish()]);\n\n await staging.mapAsync(GPUMapMode.READ);\n const result = new Uint32Array(staging.getMappedRange()).slice();\n staging.unmap();\n staging.destroy();\n return result;\n }\n}\n\n// \u2500\u2500 4\u00D74 matrix inverse (column-major Float32Array, gl-matrix convention) \u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function invertMat4(a) {\n const a00=a[0], a01=a[1], a02=a[2], a03=a[3];\n const a10=a[4], a11=a[5], a12=a[6], a13=a[7];\n const a20=a[8], a21=a[9], a22=a[10],a23=a[11];\n const a30=a[12],a31=a[13],a32=a[14],a33=a[15];\n\n const b00=a00*a11-a01*a10, b01=a00*a12-a02*a10;\n const b02=a00*a13-a03*a10, b03=a01*a12-a02*a11;\n const b04=a01*a13-a03*a11, b05=a02*a13-a03*a12;\n const b06=a20*a31-a21*a30, b07=a20*a32-a22*a30;\n const b08=a20*a33-a23*a30, b09=a21*a32-a22*a31;\n const b10=a21*a33-a23*a31, b11=a22*a33-a23*a32;\n\n const det = b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06;\n if (!det) return null;\n const d = 1.0 / det;\n const out = new Float32Array(16);\n out[0] = (a11*b11 - a12*b10 + a13*b09) * d;\n out[1] = (a02*b10 - a01*b11 - a03*b09) * d;\n out[2] = (a31*b05 - a32*b04 + a33*b03) * d;\n out[3] = (a22*b04 - a21*b05 - a23*b03) * d;\n out[4] = (a12*b08 - a10*b11 - a13*b07) * d;\n out[5] = (a00*b11 - a02*b08 + a03*b07) * d;\n out[6] = (a32*b02 - a30*b05 - a33*b01) * d;\n out[7] = (a20*b05 - a22*b02 + a23*b01) * d;\n out[8] = (a10*b10 - a11*b08 + a13*b06) * d;\n out[9] = (a01*b08 - a00*b10 - a03*b06) * d;\n out[10] = (a30*b04 - a31*b02 + a33*b00) * d;\n out[11] = (a21*b02 - a20*b04 - a23*b00) * d;\n out[12] = (a11*b07 - a10*b09 - a12*b06) * d;\n out[13] = (a00*b09 - a01*b07 + a02*b06) * d;\n out[14] = (a31*b01 - a30*b03 - a32*b00) * d;\n out[15] = (a20*b03 - a21*b01 + a22*b00) * d;\n return out;\n}\n\n// \u2500\u2500 Background colour parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction parseBackground(bg) {\n if (!bg || bg === 'transparent') return { r: 0, g: 0, b: 0, a: 0 };\n if (Array.isArray(bg)) return { r: bg[0], g: bg[1], b: bg[2], a: bg[3] ?? 1 };\n if (typeof bg === 'string') {\n const hex = bg.replace('#', '');\n if (hex.length === 6) {\n return {\n r: parseInt(hex.slice(0, 2), 16) / 255,\n g: parseInt(hex.slice(2, 4), 16) / 255,\n b: parseInt(hex.slice(4, 6), 16) / 255,\n a: 1,\n };\n }\n if (hex.length === 8) {\n return {\n r: parseInt(hex.slice(0, 2), 16) / 255,\n g: parseInt(hex.slice(2, 4), 16) / 255,\n b: parseInt(hex.slice(4, 6), 16) / 255,\n a: parseInt(hex.slice(6, 8), 16) / 255,\n };\n }\n }\n return { r: 0, g: 0, b: 0, a: 1 };\n}\n", "/**\n * Orbit camera with mouse and touch controls.\n * Spherical coordinates (theta = azimuth, phi = elevation) around a target point.\n *\n * View matrix is column-major Float32Array(16) compatible with WebGPU.\n */\nexport class OrbitCamera {\n constructor({ fov = 60, near = 0.01, far = 2000 } = {}) {\n this.fov = fov * Math.PI / 180; // radians\n this.near = near;\n this.far = far;\n\n // Spherical state\n this.theta = 0; // azimuth\n this.phi = 0.2; // elevation (clamped away from poles)\n this.radius = 5;\n this.target = [0, 0, 0];\n\n // Set to false to disable all input handling (e.g. when scroll-scene owns playback).\n this.enabled = true;\n // Set to false to disable drag-orbit specifically. When that happens,\n // left-drag pans instead of orbiting (if panEnabled), so pan is\n // reachable without requiring a right-click drag.\n this.orbitEnabled = true;\n this.panEnabled = true;\n this.panSpeed = 1; // sensitivity multiplier (0..1, derived from damping)\n this.panButton = 2; // mouse button that starts a pan-drag: 0 = left, 2 = right\n this.panRadius = null; // max distance from panOrigin; null = unlimited\n this.panOrigin = null; // world-space [x,y,z] centre for limited pan\n\n // Angular constraints (radians). null = unconstrained.\n // Set by constrainAngles(); cleared by clearConstraints().\n this.thetaMin = null;\n this.thetaMax = null;\n this.phiMin = null;\n this.phiMax = null;\n\n // Zoom (radius) control. zoomEnabled=false blocks wheel/pinch entirely.\n this.zoomEnabled = true;\n this.radiusMin = null;\n this.radiusMax = null;\n\n // Overlay callbacks \u2014 set by viewer.js when hs-* markers are active.\n // orbitDeltaCallback(dTheta, dPhi) \u2014 drag input routed as deltas instead of direct state mutation\n // dragStartCallback() \u2014 fired on left-button mousedown / touch\n // dragEndCallback() \u2014 fired on mouseup / mouseleave / touch end\n // panDeltaCallback(dx, dy, dz) \u2014 right-drag/two-finger pan routed as a world-space\n // delta instead of mutating target directly (the\n // animation overwrites target every frame)\n // zoomDeltaCallback(factor) \u2014 wheel/pinch routed as a radius multiplier instead\n // of mutating radius directly (animation overwrites\n // radius every frame)\n this.orbitDeltaCallback = null;\n this.dragStartCallback = null;\n this.dragEndCallback = null;\n this.panDeltaCallback = null;\n this.zoomDeltaCallback = null;\n\n this._drag = null; // { x, y, button }\n this._touches = [];\n\n // When true, single-finger touch is left alone (no preventDefault, no\n // orbit/pan) so the page can scroll natively \u2014 set by player.js\n // for scroll-driven scenes, where swipe-to-scroll is the primary mobile\n // interaction. Two-finger pinch/pan still works regardless. Cleared\n // during freecamera \"explore\" acts so single-finger touch can orbit.\n // Using the setter so touch-action is updated on the document root.\n this._allowTouchScroll = false;\n\n this.viewMatrix = new Float32Array(16);\n this.projMatrix = new Float32Array(16);\n }\n\n get allowTouchScroll() { return this._allowTouchScroll; }\n set allowTouchScroll(v) {\n this._allowTouchScroll = !!v;\n // pan-y: browser handles vertical scroll natively; horizontal touches reach JS\n // uninterrupted (no touchcancel), so horizontal-swipe orbit still works.\n // Resetting to '' restores default `auto` when scroll mode is off.\n if (typeof document !== 'undefined') {\n document.documentElement.style.touchAction = v ? 'pan-y' : '';\n }\n }\n\n // \u2500\u2500 Attach / detach input listeners \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n attach(canvas) {\n this._canvas = canvas;\n\n // Check whether a client point is actually over the canvas \u2014 not just\n // within its bounding rect, since overlays (e.g. the holosplat editor\n // panel) are positioned on top of the canvas via z-index while the\n // canvas itself still spans the full viewport underneath them. A rect-\n // only check would treat clicks on such overlays as canvas clicks,\n // calling preventDefault() on their mousedown and silently blocking the\n // browser's default click-to-focus behavior on any input inside them.\n const overCanvas = (cx, cy) => canvas.contains(document.elementFromPoint(cx, cy));\n\n // Mouse events: listen on document so overlapping elements don't block them.\n this._onMouseDown = e => { if (overCanvas(e.clientX, e.clientY)) this._mouseDown(e); };\n this._onMouseMove = e => this._mouseMove(e);\n this._onMouseUp = () => { this._drag = null; this.dragEndCallback?.(); };\n // Wheel must stay on canvas so we can preventDefault without the passive restriction.\n this._onWheel = e => this._wheel(e);\n // Touch: listen on document for same reason.\n this._onTouchStart = e => { if (overCanvas(e.touches[0]?.clientX, e.touches[0]?.clientY)) this._touchStart(e); };\n this._onTouchMove = e => this._touchMove(e);\n this._onTouchEnd = e => this._touchEnd(e);\n this._onCtxMenu = e => { if (overCanvas(e.clientX, e.clientY)) e.preventDefault(); };\n // Mouse leaves the browser window entirely: relatedTarget is null only when\n // the pointer exits the document (not when moving to another element).\n // Ends any active drag even when the cursor is dragged or jumps\n // off-window without a mouseup.\n this._onMouseOutDoc = e => {\n if (e.relatedTarget !== null) return;\n if (this._drag) { this._drag = null; this.dragEndCallback?.(); }\n };\n\n document.addEventListener('mousedown', this._onMouseDown);\n document.addEventListener('mousemove', this._onMouseMove);\n document.addEventListener('mouseup', this._onMouseUp);\n document.addEventListener('mouseout', this._onMouseOutDoc);\n canvas.addEventListener('wheel', this._onWheel, { passive: false });\n document.addEventListener('touchstart', this._onTouchStart, { passive: false });\n document.addEventListener('touchmove', this._onTouchMove, { passive: false });\n document.addEventListener('touchend', this._onTouchEnd);\n document.addEventListener('contextmenu', this._onCtxMenu);\n }\n\n detach() {\n const c = this._canvas;\n if (!c) return;\n document.removeEventListener('mousedown', this._onMouseDown);\n document.removeEventListener('mousemove', this._onMouseMove);\n document.removeEventListener('mouseup', this._onMouseUp);\n document.removeEventListener('mouseout', this._onMouseOutDoc);\n c.removeEventListener('wheel', this._onWheel);\n document.removeEventListener('touchstart', this._onTouchStart);\n document.removeEventListener('touchmove', this._onTouchMove);\n document.removeEventListener('touchend', this._onTouchEnd);\n document.removeEventListener('contextmenu', this._onCtxMenu);\n this._canvas = null;\n }\n\n // \u2500\u2500 Mouse handlers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n _mouseDown(e) {\n if (!this.enabled) return;\n if (e.button === 2 && !this.panEnabled) return;\n this._drag = { x: e.clientX, y: e.clientY, button: e.button };\n if (e.button === 0) this.dragStartCallback?.();\n e.preventDefault();\n }\n\n _mouseMove(e) {\n if (this._drag) {\n const dx = e.clientX - this._drag.x;\n const dy = e.clientY - this._drag.y;\n this._drag.x = e.clientX;\n this._drag.y = e.clientY;\n\n const isPanButton = this._drag.button === this.panButton;\n if (this.panEnabled && (isPanButton || (!this.orbitEnabled && this._drag.button === 0))) {\n // pan.button drag, or left-drag when orbit is off but pan is on: pan.\n this._pan(dx, dy);\n return;\n }\n if (this._drag.button === 0 && !isPanButton) {\n // Left-drag (when not bound to pan): orbit\n this._orbit(dx, dy);\n }\n }\n }\n\n _wheel(e) {\n if (!this.enabled || !this.zoomEnabled) return;\n e.preventDefault();\n const factor = e.deltaY > 0 ? 1.1 : 0.9;\n if (this.zoomDeltaCallback) { this.zoomDeltaCallback(factor); return; }\n this.radius = Math.max(0.01, this.radius * factor);\n if (this.radiusMin !== null) this.radius = Math.max(this.radiusMin, this.radius);\n if (this.radiusMax !== null) this.radius = Math.min(this.radiusMax, this.radius);\n }\n\n // \u2500\u2500 Touch handlers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n _touchStart(e) {\n if (!this.enabled) return;\n if (this.allowTouchScroll && e.touches.length < 2) {\n return; // no preventDefault \u2014 page scrolls freely\n }\n e.preventDefault();\n this._touches = Array.from(e.touches).map(t => ({ id: t.identifier, x: t.clientX, y: t.clientY }));\n }\n\n _touchMove(e) {\n if (!this.enabled) return;\n if (this.allowTouchScroll && e.touches.length < 2) {\n this._touches = [];\n return;\n }\n e.preventDefault();\n const prev = this._touches;\n const curr = Array.from(e.touches).map(t => ({ id: t.identifier, x: t.clientX, y: t.clientY }));\n\n if (curr.length === 1 && prev.length === 1) {\n const dx = curr[0].x - prev[0].x;\n const dy = curr[0].y - prev[0].y;\n // Single-finger touch acts as a left-click drag.\n if (this.panEnabled && (this.panButton === 0 || !this.orbitEnabled)) {\n this._pan(dx, dy);\n } else {\n this._orbit(dx, dy);\n }\n } else if (curr.length === 2 && prev.length === 2) {\n // Pinch to zoom\n const prevDist = Math.hypot(prev[1].x - prev[0].x, prev[1].y - prev[0].y);\n const currDist = Math.hypot(curr[1].x - curr[0].x, curr[1].y - curr[0].y);\n if (prevDist > 0 && currDist > 0 && this.zoomEnabled) {\n const factor = prevDist / currDist;\n if (this.zoomDeltaCallback) {\n this.zoomDeltaCallback(factor);\n } else {\n this.radius = Math.max(0.01, this.radius * factor);\n if (this.radiusMin !== null) this.radius = Math.max(this.radiusMin, this.radius);\n if (this.radiusMax !== null) this.radius = Math.min(this.radiusMax, this.radius);\n }\n }\n // Two-finger pan (centroid delta)\n if (this.panEnabled) {\n const prevCx = (prev[0].x + prev[1].x) * 0.5;\n const prevCy = (prev[0].y + prev[1].y) * 0.5;\n const currCx = (curr[0].x + curr[1].x) * 0.5;\n const currCy = (curr[0].y + curr[1].y) * 0.5;\n this._pan(currCx - prevCx, currCy - prevCy);\n }\n }\n\n this._touches = curr;\n }\n\n _touchEnd(e) {\n this._touches = Array.from(e.touches).map(t => ({ id: t.identifier, x: t.clientX, y: t.clientY }));\n if (this._touches.length === 0) this.dragEndCallback?.();\n }\n\n // \u2500\u2500 Orbit & pan helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n _orbit(dx, dy) {\n const speed = 0.005;\n const dTheta = -dx * speed;\n const dPhi = dy * speed;\n if (this.orbitDeltaCallback) {\n this.orbitDeltaCallback(dTheta, dPhi);\n return;\n }\n this.theta += dTheta;\n this.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.phi + dPhi));\n if (this.thetaMin !== null) this.theta = Math.max(this.thetaMin, this.theta);\n if (this.thetaMax !== null) this.theta = Math.min(this.thetaMax, this.theta);\n if (this.phiMin !== null) this.phi = Math.max(this.phiMin, this.phi);\n if (this.phiMax !== null) this.phi = Math.min(this.phiMax, this.phi);\n }\n\n /**\n * Restrict orbit to \u00B1hDeg horizontal and \u00B1vDeg vertical from the current\n * theta/phi. Call after setFromLookAt() so the current angles are the center.\n * Pass null for either axis to leave it unconstrained.\n */\n constrainAngles(hDeg, vDeg) {\n if (hDeg !== null) {\n const r = hDeg * Math.PI / 180;\n this.thetaMin = this.theta - r;\n this.thetaMax = this.theta + r;\n } else {\n this.thetaMin = null;\n this.thetaMax = null;\n }\n if (vDeg !== null) {\n const r = vDeg * Math.PI / 180;\n this.phiMin = Math.max(-Math.PI / 2 + 0.01, this.phi - r);\n this.phiMax = Math.min( Math.PI / 2 - 0.01, this.phi + r);\n } else {\n this.phiMin = null;\n this.phiMax = null;\n }\n }\n\n clearConstraints() {\n this.thetaMin = null;\n this.thetaMax = null;\n this.phiMin = null;\n this.phiMax = null;\n }\n\n /** Disable scroll/pinch zoom entirely (used when entering free-camera mode without hs-zoom). */\n disableZoom() {\n this.zoomEnabled = false;\n this.radiusMin = null;\n this.radiusMax = null;\n }\n\n /** Enable zoom constrained to \u00B1rangePct% of baseRadius. */\n constrainZoom(baseRadius, rangePct) {\n const r = rangePct / 100;\n this.zoomEnabled = true;\n this.radiusMin = Math.max(0.01, baseRadius * (1 - r));\n this.radiusMax = baseRadius * (1 + r);\n }\n\n /** Re-enable unrestricted zoom (used when leaving free-camera mode). */\n enableZoom() {\n this.zoomEnabled = true;\n this.radiusMin = null;\n this.radiusMax = null;\n }\n\n _pan(dx, dy) {\n const speed = this.radius * 0.001 * this.panSpeed;\n const right = this._cameraRight();\n const up = this._cameraUp();\n const ddx = -(right[0] * dx - up[0] * dy) * speed;\n const ddy = -(right[1] * dx - up[1] * dy) * speed;\n const ddz = -(right[2] * dx - up[2] * dy) * speed;\n if (this.panDeltaCallback) { this.panDeltaCallback(ddx, ddy, ddz); return; }\n this.target[0] += ddx;\n this.target[1] += ddy;\n this.target[2] += ddz;\n if (this.panRadius !== null && this.panOrigin) {\n const [ox, oy, oz] = this.panOrigin;\n const dist = Math.hypot(this.target[0]-ox, this.target[1]-oy, this.target[2]-oz);\n if (dist > this.panRadius) {\n const f = this.panRadius / dist;\n this.target[0] = ox + (this.target[0]-ox) * f;\n this.target[1] = oy + (this.target[1]-oy) * f;\n this.target[2] = oz + (this.target[2]-oz) * f;\n }\n }\n }\n\n _cameraRight() {\n // X axis of the camera in world space = first row of view matrix\n return [this.viewMatrix[0], this.viewMatrix[4], this.viewMatrix[8]];\n }\n\n _cameraUp() {\n // Y axis of the camera in world space = second row of view matrix\n return [this.viewMatrix[1], this.viewMatrix[5], this.viewMatrix[9]];\n }\n\n // \u2500\u2500 Matrix computation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Update viewMatrix and projMatrix. Must be called before getViewMatrix(). */\n update(width, height) {\n const eye = this._eye();\n lookAt(eye, this.target, [0, 1, 0], this.viewMatrix);\n perspective(this.fov, width / height, this.near, this.far, this.projMatrix);\n }\n\n get eye() { return this._eye(); }\n\n _eye() {\n const cp = Math.cos(this.phi), sp = Math.sin(this.phi);\n const ct = Math.cos(this.theta), st = Math.sin(this.theta);\n return [\n this.target[0] + this.radius * cp * st,\n this.target[1] + this.radius * sp,\n this.target[2] + this.radius * cp * ct,\n ];\n }\n\n /** Focal length in pixels for a given viewport dimension and fov. */\n focalLength(height) {\n return (height * 0.5) / Math.tan(this.fov * 0.5);\n }\n\n /**\n * Sync orbit state from an explicit eye + target.\n * After this call, update() reproduces the same view.\n * Used by the animation system so orbit controls resume from the animated position.\n */\n setFromLookAt(eye, target) {\n this.target = [target[0], target[1], target[2]];\n const dx = eye[0] - target[0];\n const dy = eye[1] - target[1];\n const dz = eye[2] - target[2];\n this.radius = Math.hypot(dx, dy, dz) || 0.001;\n this.phi = Math.asin(Math.max(-1, Math.min(1, dy / this.radius)));\n this.theta = Math.atan2(dx, dz);\n }\n\n /** Fit camera to a scene bounding box, resetting angle to default. */\n fitScene(positions, numSplats) {\n this._sceneBounds(positions, numSplats);\n this.theta = 0;\n this.phi = 0.2;\n }\n\n /** Fit camera to a scene bounding box, preserving current angle. */\n focusScene(positions, numSplats) {\n this._sceneBounds(positions, numSplats);\n }\n\n _sceneBounds(positions, numSplats) {\n let minX = Infinity, minY = Infinity, minZ = Infinity;\n let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;\n for (let i = 0; i < numSplats; i++) {\n const j = i * 16;\n const x = positions[j], y = positions[j + 1], z = positions[j + 2];\n if (x < minX) minX = x; if (x > maxX) maxX = x;\n if (y < minY) minY = y; if (y > maxY) maxY = y;\n if (z < minZ) minZ = z; if (z > maxZ) maxZ = z;\n }\n this.target = [\n (minX + maxX) * 0.5,\n (minY + maxY) * 0.5,\n (minZ + maxZ) * 0.5,\n ];\n const extent = Math.max(maxX - minX, maxY - minY, maxZ - minZ) * 0.5;\n this.radius = extent / Math.tan(this.fov * 0.5) * 1.2;\n }\n}\n\n// \u2500\u2500 Math helpers (column-major, WebGPU convention) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction lookAt(eye, center, up, out) {\n const [ex, ey, ez] = eye;\n const [cx, cy, cz] = center;\n const [ux, uy, uz] = up;\n\n let zx = ex - cx, zy = ey - cy, zz = ez - cz;\n let zl = Math.hypot(zx, zy, zz);\n zx /= zl; zy /= zl; zz /= zl;\n\n let xx = uy * zz - uz * zy;\n let xy = uz * zx - ux * zz;\n let xz = ux * zy - uy * zx;\n const xl = Math.hypot(xx, xy, xz);\n xx /= xl; xy /= xl; xz /= xl;\n\n const yx = zy * xz - zz * xy;\n const yy = zz * xx - zx * xz;\n const yz = zx * xy - zy * xx;\n\n out[ 0] = xx; out[ 1] = yx; out[ 2] = zx; out[ 3] = 0;\n out[ 4] = xy; out[ 5] = yy; out[ 6] = zy; out[ 7] = 0;\n out[ 8] = xz; out[ 9] = yz; out[10] = zz; out[11] = 0;\n out[12] = -(xx*ex + xy*ey + xz*ez);\n out[13] = -(yx*ex + yy*ey + yz*ez);\n out[14] = -(zx*ex + zy*ey + zz*ez);\n out[15] = 1;\n}\n\nfunction perspective(fovY, aspect, near, far, out) {\n const f = 1.0 / Math.tan(fovY * 0.5);\n const nf = near - far;\n out[ 0] = f / aspect; out[ 1] = 0; out[ 2] = 0; out[ 3] = 0;\n out[ 4] = 0; out[ 5] = f; out[ 6] = 0; out[ 7] = 0;\n out[ 8] = 0; out[ 9] = 0; out[10] = far / nf; out[11] = -1;\n out[12] = 0; out[13] = 0; out[14] = near * far / nf; out[15] = 0;\n}\n", "/**\n * Async 32-bit radix sort for depth ordering.\n *\n * Converts IEEE-754 view-space depths (always negative) to sortable uint32\n * keys by flipping all bits \u2014 gives full float precision with zero per-frame\n * rescaling, eliminating the shimmer caused by the previous 16-bit approach.\n *\n * 4 passes \u00D7 8-bit buckets. Runs in a Web Worker so the main thread never\n * blocks on sorting. Falls back to synchronous if Workers are unavailable.\n *\n * `gIndex` (optional) restricts the sort to a subset of gaussians: it's a\n * Uint32Array of gaussian indices, one per entry in `depths`/`copy`. When\n * omitted, the full [0, N) range is sorted (gaussian index === array index).\n */\n\n// \u2500\u2500 Shared sort logic (used by both worker and sync fallback) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst SORT_FN = `\nfunction radixSort32(keys, idx, tmp_k, tmp_i, counts, pfx, N) {\n for (let pass = 0; pass < 4; pass++) {\n const shift = pass * 8;\n counts.fill(0);\n for (let i = 0; i < N; i++) counts[(keys[i] >>> shift) & 0xff]++;\n pfx[0] = 0;\n for (let i = 1; i < 256; i++) pfx[i] = pfx[i-1] + counts[i-1];\n for (let i = 0; i < N; i++) {\n const b = (keys[i] >>> shift) & 0xff;\n tmp_k[pfx[b]] = keys[i];\n tmp_i[pfx[b]++] = idx[i];\n }\n // swap\n const k = keys; keys = tmp_k; tmp_k = k;\n const x = idx; idx = tmp_i; tmp_i = x;\n }\n // after 4 passes (even) idx holds the result\n return idx;\n}\n`;\n\n// \u2500\u2500 Worker source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst WORKER_SRC = SORT_FN + `\nconst u32 = new Uint32Array(1);\nconst f32 = new Float32Array(u32.buffer);\n\nlet keys0, keys1, idx0, idx1, counts, pfx, allocN = -1;\nfunction alloc(N) {\n if (N <= allocN) return;\n allocN = N;\n keys0 = new Uint32Array(N);\n keys1 = new Uint32Array(N);\n idx0 = new Uint32Array(N);\n idx1 = new Uint32Array(N);\n counts = new Uint32Array(256);\n pfx = new Uint32Array(256);\n}\n\nself.onmessage = function(e) {\n const depths = e.data.depths;\n const gIndex = e.data.gIndex;\n const N = e.data.N;\n alloc(N);\n\n // Convert negative floats \u2192 sortable uint32 by flipping all bits\n const dv = new DataView(depths.buffer);\n for (let i = 0; i < N; i++) {\n keys0[i] = dv.getUint32(i * 4, true) ^ 0xffffffff;\n idx0[i] = gIndex ? gIndex[i] : i;\n }\n\n const sorted = radixSort32(keys0, idx0, keys1, idx1, counts, pfx, N);\n const result = sorted.slice(0, N);\n self.postMessage({ order: result }, [result.buffer]);\n};\n`;\n\n// \u2500\u2500 Synchronous fallback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction createSyncSorter(maxN) {\n let keys0 = new Uint32Array(maxN);\n let keys1 = new Uint32Array(maxN);\n let idx0 = new Uint32Array(maxN);\n let idx1 = new Uint32Array(maxN);\n const counts = new Uint32Array(256);\n const pfx = new Uint32Array(256);\n\n // inline radixSort32\n function sort(depths, N, gIndex = null) {\n const dv = new DataView(depths.buffer);\n for (let i = 0; i < N; i++) {\n const di = gIndex ? gIndex[i] : i;\n keys0[i] = dv.getUint32(di * 4, true) ^ 0xffffffff;\n idx0[i] = di;\n }\n for (let pass = 0; pass < 4; pass++) {\n const shift = pass * 8;\n counts.fill(0);\n for (let i = 0; i < N; i++) counts[(keys0[i] >>> shift) & 0xff]++;\n pfx[0] = 0;\n for (let i = 1; i < 256; i++) pfx[i] = pfx[i-1] + counts[i-1];\n for (let i = 0; i < N; i++) {\n const b = (keys0[i] >>> shift) & 0xff;\n keys1[pfx[b]] = keys0[i];\n idx1[pfx[b]++] = idx0[i];\n }\n [keys0, keys1] = [keys1, keys0];\n [idx0, idx1 ] = [idx1, idx0 ];\n }\n return idx0;\n }\n\n return sort;\n}\n\n// \u2500\u2500 Async worker sorter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function createSorter(maxN) {\n if (typeof Worker === 'undefined') return createSyncSorter(maxN);\n\n let worker;\n try {\n const blob = new Blob([WORKER_SRC], { type: 'application/javascript' });\n const url = URL.createObjectURL(blob);\n worker = new Worker(url);\n URL.revokeObjectURL(url);\n } catch (_) {\n return createSyncSorter(maxN);\n }\n\n // Identity order until first worker result arrives\n let lastOrder = new Uint32Array(maxN);\n for (let i = 0; i < maxN; i++) lastOrder[i] = i;\n\n let busy = false;\n let queued = null; // latest pending request while worker is busy\n\n worker.onmessage = (e) => {\n lastOrder = e.data.order;\n busy = false;\n if (queued) {\n const { depths, N, gIndex } = queued;\n queued = null;\n busy = true;\n const transfer = gIndex ? [depths.buffer, gIndex.buffer] : [depths.buffer];\n worker.postMessage({ depths, N, gIndex }, transfer);\n }\n };\n\n return function sort(depths, N, gIndex = null) {\n const copy = new Float32Array(N);\n if (gIndex) {\n for (let i = 0; i < N; i++) copy[i] = depths[gIndex[i]];\n } else {\n copy.set(depths.subarray(0, N));\n }\n const gCopy = gIndex ? gIndex.slice(0, N) : null;\n\n if (!busy) {\n busy = true;\n const transfer = gCopy ? [copy.buffer, gCopy.buffer] : [copy.buffer];\n worker.postMessage({ depths: copy, N, gIndex: gCopy }, transfer);\n } else {\n queued = { depths: copy, N, gIndex: gCopy }; // drop stale request, keep latest\n }\n\n // lastOrder can be sized for a *previous* request's N \u2014 e.g. mask/\n // frustum culling (Viewer#_computeActiveRanges) changes the active\n // subset's size between ticks, and the worker hasn't caught up to the\n // new N yet. Returning a mismatched-size array makes the caller\n // (Renderer#updateOrder) write a partly-stale/uninitialized buffer,\n // which renders as every instance collapsing onto gaussian 0 \u2014 i.e. the\n // whole scene appears to vanish until the worker's next response\n // happens to land on a matching N. Fall back to gCopy (already a valid,\n // if depth-unsorted, set of indices to render) or plain identity so the\n // returned array is always exactly N long.\n if (lastOrder.length !== N) {\n return gCopy && gCopy.length === N ? gCopy : Uint32Array.from({ length: N }, (_, i) => i);\n }\n return lastOrder;\n };\n}\n", "/**\n * Shared fetch helper used by all loaders.\n * Streams the response body so onProgress(0..1) can be reported.\n */\nexport async function fetchWithProgress(url, onProgress) {\n const res = await fetch(url);\n if (!res.ok) throw new Error(`HTTP ${res.status} loading ${url}`);\n\n const total = parseInt(res.headers.get('content-length') || '0', 10);\n const reader = res.body.getReader();\n const chunks = [];\n let loaded = 0;\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n loaded += value.byteLength;\n if (onProgress && total > 0) onProgress(loaded / total);\n }\n\n const combined = new Uint8Array(loaded);\n let offset = 0;\n for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.byteLength; }\n return combined.buffer;\n}\n", "/**\n * Loader for the .splat binary format.\n *\n * Each Gaussian is 32 bytes:\n * bytes 0\u201311 : position (3 \u00D7 float32, little-endian)\n * bytes 12\u201323 : scale (3 \u00D7 float32, already linear \u2013 no exp needed)\n * bytes 24\u201327 : color RGBA (4 \u00D7 uint8, 0\u2013255)\n * bytes 28\u201331 : quaternion (4 \u00D7 uint8, w/x/y/z order, decoded via (b\u2212128)/128)\n *\n * Output: Float32Array with 16 floats per Gaussian in the canonical layout:\n * [0-2] pos.xyz\n * [3] 0 (padding)\n * [4-7] color.rgba (0\u20131)\n * [8-10] scale.xyz\n * [11] 0 (padding)\n * [12-15] quat.xyzw\n */\nimport { fetchWithProgress } from './fetch-utils.js';\n\nexport async function loadSplat(url, onProgress) {\n const buffer = await fetchWithProgress(url, onProgress);\n return parseSplat(buffer);\n}\n\nexport function parseSplat(buffer) {\n const STRIDE = 32;\n const N = Math.floor(buffer.byteLength / STRIDE);\n if (N === 0) throw new Error('Empty or invalid .splat file');\n\n const src = new DataView(buffer);\n const data = new Float32Array(N * 16);\n\n for (let i = 0; i < N; i++) {\n const s = i * STRIDE;\n const d = i * 16;\n\n // Position\n data[d + 0] = src.getFloat32(s + 0, true);\n data[d + 1] = src.getFloat32(s + 4, true);\n data[d + 2] = src.getFloat32(s + 8, true);\n // d[3] = 0 (padding)\n\n // Color (uint8 \u2192 0..1)\n data[d + 4] = src.getUint8(s + 24) / 255;\n data[d + 5] = src.getUint8(s + 25) / 255;\n data[d + 6] = src.getUint8(s + 26) / 255;\n data[d + 7] = src.getUint8(s + 27) / 255;\n\n // Scale (already linear in .splat format)\n data[d + 8] = src.getFloat32(s + 12, true);\n data[d + 9] = src.getFloat32(s + 16, true);\n data[d + 10] = src.getFloat32(s + 20, true);\n // d[11] = 0 (padding)\n\n // Quaternion: bytes 28\u201331 = [w, x, y, z], decoded (b\u2212128)/128, then normalize\n const qw = (src.getUint8(s + 28) - 128) / 128;\n const qx = (src.getUint8(s + 29) - 128) / 128;\n const qy = (src.getUint8(s + 30) - 128) / 128;\n const qz = (src.getUint8(s + 31) - 128) / 128;\n const ql = Math.hypot(qx, qy, qz, qw) || 1;\n data[d + 12] = qx / ql;\n data[d + 13] = qy / ql;\n data[d + 14] = qz / ql;\n data[d + 15] = qw / ql;\n }\n\n return { data, count: N, shData: null, numSHBases: 0 };\n}\n\n// fetchWithProgress is defined in fetch-utils.js (shared)\n", "/**\n * Loader for 3D Gaussian Splatting .ply files (standard 3DGS training output).\n *\n * Supports two loading modes:\n * loadPly(url, onProgress) \u2014 full download, then decode (all formats)\n * openPlyStream(url, onProgress) \u2014 streams header immediately, returns a reader\n * for iterative chunk decoding via decodePlyVertices\n *\n * Transforms applied:\n * f_dc_0/1/2 \u2192 RGB: 0.5 + SH_C0 * f_dc (SH_C0 = 0.28209479177387814)\n * opacity \u2192 sigmoid: 1 / (1 + exp(\u2212x))\n * scale_i \u2192 exp(scale_i)\n * rot_0..3 \u2192 quaternion (w,x,y,z) \u2192 (x,y,z,w), normalised\n *\n * Output: canonical Float32Array layout, 16 floats per Gaussian.\n */\n\nimport { fetchWithProgress } from './fetch-utils.js';\n\nconst SH_C0 = 0.28209479177387814;\nconst SH_C1 = 0.4886025119029199;\nconst SH_C2 = [1.0925484305920792, -1.0925484305920792, 0.31539156525252005, -1.0925484305920792, 0.5462742152960396];\nconst SH_C3 = [-0.5900435899266435, 2.890611442640554, -0.4570457994644658, 0.3731763325901154, -0.4570457994644658, 1.445305721320277, -0.5900435899266435];\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function loadPly(url, onProgress, shDegree = 0) {\n const buffer = await fetchWithProgress(url, onProgress);\n return parsePly(buffer, shDegree);\n}\n\nexport function parsePly(buffer, shDegree = 0) {\n const bytes = new Uint8Array(buffer);\n const eoh = findEndHeader(bytes);\n if (eoh < 0) throw new Error('Invalid PLY: end_header not found');\n const { numVertices, propMap, stride, hasColor } = parseHeaderText(\n new TextDecoder().decode(bytes.subarray(0, eoh))\n );\n if (numVertices === 0) throw new Error('PLY file contains no vertices');\n const src = new DataView(buffer, eoh);\n const { data, shData, numSHBases } = decodePlyVertices(src, propMap, stride, hasColor, numVertices, shDegree);\n return { data, count: numVertices, shData, numSHBases };\n}\n\n/**\n * Open a PLY stream: reads the header bytes (typically < 1 KB), then\n * returns `{ numVertices, consume }`.\n *\n * `consume(onChunk, onProgress?)` streams and decodes the vertex data,\n * calling `onChunk(Float32Array, vertexCount)` for each decoded batch.\n * It always processes bytes already buffered during header reading before\n * pulling more from the network, so the caller never misses data even if\n * the entire file arrived in the first network read.\n *\n * @returns {{ numVertices: number, numSHBases: number,\n * consume: (onChunk, onProgress?) => Promise<void> }}\n */\nexport async function openPlyStream(url, onHeaderProgress, shDegree = 0) {\n const res = await fetch(url);\n if (!res.ok) throw new Error(`HTTP ${res.status} loading ${url}`);\n\n const contentLength = parseInt(res.headers.get('content-length') || '0', 10);\n const reader = res.body.getReader();\n let buf = new Uint8Array(0);\n let loaded = 0;\n\n // Read until end_header is found.\n for (;;) {\n const { done, value } = await reader.read();\n if (done) throw new Error('PLY stream ended before end_header');\n\n loaded += value.byteLength;\n if (onHeaderProgress && contentLength > 0) onHeaderProgress(loaded / contentLength);\n\n const next = new Uint8Array(buf.length + value.length);\n next.set(buf); next.set(value, buf.length);\n buf = next;\n\n const eoh = findEndHeader(buf);\n if (eoh >= 0) {\n const info = parseHeaderText(new TextDecoder().decode(buf.subarray(0, eoh)));\n const pending = buf.slice(eoh); // vertex bytes already in memory\n const { numVertices, propMap, stride, hasColor } = info;\n\n // Compute numSHBases from the header so the caller can pre-allocate.\n const numSHBasesHdr = propMap['f_rest_44'] ? 15 : propMap['f_rest_23'] ? 8 : propMap['f_rest_8'] ? 3 : 0;\n const maxFileDegHdr = numSHBasesHdr >= 15 ? 3 : numSHBasesHdr >= 8 ? 2 : numSHBasesHdr >= 3 ? 1 : 0;\n const effectiveDegHdr = Math.min(shDegree, maxFileDegHdr);\n const streamSHBases = effectiveDegHdr >= 3 ? 15 : effectiveDegHdr >= 2 ? 8 : effectiveDegHdr >= 1 ? 3 : 0;\n\n const consume = async (onChunk, onProgress) => {\n let p = pending;\n\n for (;;) {\n const { done: d, value: v } = await reader.read();\n\n if (!d) {\n loaded += v.byteLength;\n if (onProgress && contentLength > 0) onProgress(loaded / contentLength);\n const next2 = new Uint8Array(p.length + v.length);\n next2.set(p); next2.set(v, p.length);\n p = next2;\n }\n\n // Decode all complete vertices now in p (including any leftover pending)\n const nVerts = Math.floor(p.length / stride);\n if (nVerts > 0) {\n const usedBytes = nVerts * stride;\n const { data, shData } = decodePlyVertices(\n new DataView(p.buffer, p.byteOffset, usedBytes),\n propMap, stride, hasColor, nVerts, shDegree\n );\n onChunk(data, nVerts, shData);\n p = p.slice(usedBytes);\n }\n\n if (d) break;\n }\n };\n\n return { numVertices, numSHBases: streamSHBases, consume };\n }\n if (buf.length > 65536) throw new Error('PLY header exceeds 64 KB');\n }\n}\n\n/**\n * Decode `count` Gaussian splats from a DataView positioned at the start\n * of vertex data. Used by both parsePly (full load) and streaming.\n *\n * @param {DataView} src\n * @param {object} propMap { name: { offset, type } }\n * @param {number} stride bytes per vertex\n * @param {boolean} hasColor\n * @param {number} count\n * @returns {{ data: Float32Array, shData: Float32Array|null, numSHBases: number }}\n */\nexport function decodePlyVertices(src, propMap, stride, hasColor, count, shDegree = 0) {\n const data = new Float32Array(count * 16);\n\n // f_rest layout: [R bases 0..M-1], [G bases 0..M-1], [B bases 0..M-1]\n // where M = numSHBases (3 for deg1-only, 8 for deg1+2, 15 for deg1+2+3).\n const numSHBases = propMap['f_rest_44'] ? 15 : propMap['f_rest_23'] ? 8 : propMap['f_rest_8'] ? 3 : 0;\n const maxFileDeg = numSHBases >= 15 ? 3 : numSHBases >= 8 ? 2 : numSHBases >= 3 ? 1 : 0;\n const effectiveDeg = Math.min(shDegree, maxFileDeg);\n const numBases = effectiveDeg >= 3 ? 15 : effectiveDeg >= 2 ? 8 : effectiveDeg >= 1 ? 3 : 0;\n const hasSH = numBases > 0 && hasColor;\n\n // Pre-build per-channel prop lookups indexed by SH basis number.\n const rSH = [], gSH = [], bSH = [];\n if (hasSH) {\n const G = numSHBases, B = 2 * numSHBases;\n for (let b = 0; b < numBases; b++) {\n rSH[b] = propMap[`f_rest_${b}`];\n gSH[b] = propMap[`f_rest_${G + b}`];\n bSH[b] = propMap[`f_rest_${B + b}`];\n }\n }\n\n // shData layout: for Gaussian i, basis b \u2192 shData[(i*numBases+b)*3 + channel].\n // Matches the GPU binding: shCoeffs[gi*numBases + b] = vec3(r,g,b).\n const shData = hasSH ? new Float32Array(count * numBases * 3) : null;\n\n for (let i = 0; i < count; i++) {\n const base = i * stride;\n const d = i * 16;\n\n data[d + 0] = readProp(src, base, propMap['x']);\n data[d + 1] = readProp(src, base, propMap['y']);\n data[d + 2] = readProp(src, base, propMap['z']);\n\n if (hasColor) {\n // DC-only base color \u2014 SH higher-order terms are stored separately in shData\n // and evaluated per-frame in the shader using the actual view direction.\n // Left unclamped \u2014 higher-order SH terms (added in the shader) can push\n // this back into [0,1]; clamping here first would bake in the wrong value.\n data[d + 4] = 0.5 + SH_C0 * readProp(src, base, propMap['f_dc_0']);\n data[d + 5] = 0.5 + SH_C0 * readProp(src, base, propMap['f_dc_1']);\n data[d + 6] = 0.5 + SH_C0 * readProp(src, base, propMap['f_dc_2']);\n\n if (hasSH) {\n const sBase = i * numBases * 3;\n for (let b = 0; b < numBases; b++) {\n const o = sBase + b * 3;\n shData[o + 0] = readProp(src, base, rSH[b]);\n shData[o + 1] = readProp(src, base, gSH[b]);\n shData[o + 2] = readProp(src, base, bSH[b]);\n }\n }\n } else {\n data[d + 4] = 1; data[d + 5] = 1; data[d + 6] = 1;\n }\n\n data[d + 7] = sigmoid(readProp(src, base, propMap['opacity']));\n data[d + 8] = Math.exp(readProp(src, base, propMap['scale_0']));\n data[d + 9] = Math.exp(readProp(src, base, propMap['scale_1']));\n data[d + 10] = Math.exp(readProp(src, base, propMap['scale_2']));\n\n const rw = readProp(src, base, propMap['rot_0']);\n const rx = readProp(src, base, propMap['rot_1']);\n const ry = readProp(src, base, propMap['rot_2']);\n const rz = readProp(src, base, propMap['rot_3']);\n const rl = Math.hypot(rx, ry, rz, rw) || 1;\n data[d + 12] = rx / rl;\n data[d + 13] = ry / rl;\n data[d + 14] = rz / rl;\n data[d + 15] = rw / rl;\n }\n return { data, shData, numSHBases: numBases };\n}\n\n// \u2500\u2500 Header parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Return byte offset of first byte after \"end_header\\n\", or -1 if not found. */\nfunction findEndHeader(bytes) {\n // \"end_header\" as bytes [101,110,100,95,104,101,97,100,101,114]\n const tag = [101, 110, 100, 95, 104, 101, 97, 100, 101, 114];\n outer: for (let i = 0; i <= bytes.length - tag.length; i++) {\n for (let j = 0; j < tag.length; j++) {\n if (bytes[i + j] !== tag[j]) continue outer;\n }\n let end = i + tag.length;\n if (bytes[end] === 13) end++; // \\r\n if (bytes[end] === 10) end++; // \\n\n return end;\n }\n return -1;\n}\n\nfunction parseHeaderText(text) {\n const lines = text.split('\\n');\n let numVertices = 0;\n let inVertex = false;\n const properties = [];\n\n for (const raw of lines) {\n const parts = raw.trim().split(/\\s+/);\n if (parts[0] === 'element') {\n inVertex = parts[1] === 'vertex';\n if (inVertex) numVertices = parseInt(parts[2], 10);\n } else if (parts[0] === 'property' && inVertex) {\n properties.push({ type: parts[1], name: parts[2] });\n }\n }\n\n const propMap = {};\n let stride = 0;\n for (const p of properties) {\n propMap[p.name] = { offset: stride, type: p.type };\n stride += sizeOf(p.type);\n }\n\n const required = ['x','y','z','scale_0','scale_1','scale_2',\n 'rot_0','rot_1','rot_2','rot_3','opacity'];\n for (const r of required) {\n if (!propMap[r]) throw new Error(`PLY missing required property: ${r}`);\n }\n\n return {\n numVertices,\n propMap,\n stride,\n hasColor: !!(propMap['f_dc_0'] && propMap['f_dc_1'] && propMap['f_dc_2']),\n };\n}\n\n// \u2500\u2500 Binary read helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction sizeOf(type) {\n switch (type) {\n case 'float': case 'float32': case 'int': case 'uint': return 4;\n case 'double': case 'int64': case 'uint64': return 8;\n case 'short': case 'ushort': case 'int16': case 'uint16': return 2;\n case 'char': case 'uchar': case 'int8': case 'uint8': return 1;\n default: return 4;\n }\n}\n\nfunction readProp(view, base, prop) {\n const off = base + prop.offset;\n switch (prop.type) {\n case 'float': case 'float32': return view.getFloat32(off, true);\n case 'double': return view.getFloat64(off, true);\n case 'int': case 'int32': return view.getInt32(off, true);\n case 'uint': case 'uint32': return view.getUint32(off, true);\n case 'short': case 'int16': return view.getInt16(off, true);\n case 'ushort': case 'uint16': return view.getUint16(off, true);\n case 'char': case 'int8': return view.getInt8(off);\n case 'uchar': case 'uint8': return view.getUint8(off);\n default: return view.getFloat32(off, true);\n }\n}\n\nfunction sigmoid(x) { return 1 / (1 + Math.exp(-x)); }\n", "/**\n * Loader for the .spz Gaussian splat format (Niantic Labs).\n *\n * The file is gzip-compressed. After decompression:\n *\n * Header (16 bytes, little-endian):\n * [0-3] magic uint32 = 0x5053474E ('N','G','S','P' \u2192 LE 0x5053474E)\n * [4-7] version uint32 (2 = xyz-int8 rot, 3-4 = smallest-3 rot)\n * [8-11] numPoints uint32\n * [12] shDegree uint8\n * [13] fractionalBits uint8 (position precision; typically 12)\n * [14] flags uint8\n * [15] reserved uint8\n *\n * Data sections (column-organized, all for numPoints Gaussians):\n * Positions : numPoints \u00D7 9 bytes (3 \u00D7 int24 LE signed, \u00F7 2^fractionalBits)\n * Alphas : numPoints \u00D7 1 byte (uint8 \u2192 opacity 0\u20131)\n * Colors : numPoints \u00D7 3 bytes (uint8 RGB, 0\u2013255 \u2192 0\u20131)\n * Scales : numPoints \u00D7 3 bytes (uint8, log-space, exp((b\u2013128)/16))\n * Rotations : v2: numPoints \u00D7 3 bytes (int8 x,y,z; w = sqrt(1-x\u00B2-y\u00B2-z\u00B2))\n * v3-4: numPoints \u00D7 4 bytes (2-bit largestIdx + three 10-bit signed)\n *\n * Output: canonical Float32Array layout (16 floats/Gaussian) compatible with\n * the HoloSplat renderer \u2014 see shaders.js for the GPU-side layout.\n */\n\nimport { fetchWithProgress } from './fetch-utils.js';\n\nconst MAGIC = 0x5053474E;\nconst SQRT2INV = 1 / Math.SQRT2;\n\nexport async function loadSpz(url, onProgress, shDegree = 0) {\n const compressed = await fetchWithProgress(url, onProgress);\n const buffer = await decompressGzip(compressed);\n return parseSpz(buffer, shDegree);\n}\n\n/** Decompress and parse an in-memory gzip-compressed .spz buffer (e.g. from a File). */\nexport async function parseSpzGzip(compressed, shDegree = 0) {\n const buffer = await decompressGzip(compressed);\n return parseSpz(buffer, shDegree);\n}\n\n// Number of Gaussians decoded per main-thread slice. Large .spz files (1M+\n// points) can take seconds to decode; yielding periodically keeps the page\n// (scroll, paint, loading-bar animation) responsive during that time.\nconst PARSE_CHUNK = 50000;\n\nfunction yieldToMain() {\n return new Promise(resolve => setTimeout(resolve, 0));\n}\n\nexport async function parseSpz(buffer, shDegree = 0) {\n const view = new DataView(buffer);\n\n // \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const magic = view.getUint32(0, true);\n const version = view.getUint32(4, true);\n const numPoints = view.getUint32(8, true);\n const fileShDegree = view.getUint8(12);\n const fractionalBits = view.getUint8(13);\n // flags / reserved at 14-15 (ignored for now)\n\n if (magic !== MAGIC) {\n throw new Error(\n `Invalid .spz magic: 0x${magic.toString(16).toUpperCase()} ` +\n `(expected 0x${MAGIC.toString(16).toUpperCase()})`\n );\n }\n if (version < 2 || version > 4) {\n console.warn(`HoloSplat: .spz version ${version} is untested; attempting load anyway`);\n }\n\n const posDiv = 1 << fractionalBits; // position divisor\n const rotSize = version >= 3 ? 4 : 3; // bytes per rotation\n\n // SH-rest: the file's own per-point byte stride depends on fileShDegree\n // (we must skip it correctly even if we only want a lower degree); what we\n // actually decode is capped to min(requested, file's degree, 3) \u2014 degree 4\n // (24 bases) isn't supported by our renderer's evalSH.\n const degToBases = deg => deg >= 4 ? 24 : deg >= 3 ? 15 : deg >= 2 ? 8 : deg >= 1 ? 3 : 0;\n const fileNumSHBases = degToBases(fileShDegree);\n const effectiveDeg = Math.min(shDegree, fileShDegree, 3);\n const numSHBases = degToBases(effectiveDeg);\n const hasSH = numSHBases > 0;\n\n // \u2500\u2500 Section offsets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const offPos = 16;\n const offAlpha = offPos + numPoints * 9;\n const offColor = offAlpha + numPoints * 1;\n const offScale = offColor + numPoints * 3;\n const offRot = offScale + numPoints * 3;\n const offSH = offRot + numPoints * rotSize;\n\n const data = new Float32Array(numPoints * 16);\n const shData = hasSH ? new Float32Array(numPoints * numSHBases * 3) : null;\n\n for (let i = 0; i < numPoints; i++) {\n const d = i * 16;\n\n // \u2500\u2500 Position (int24 signed LE, \u00F7 2^fractionalBits) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const pb = offPos + i * 9;\n data[d + 0] = readInt24(view, pb + 0) / posDiv;\n data[d + 1] = readInt24(view, pb + 3) / posDiv;\n data[d + 2] = readInt24(view, pb + 6) / posDiv;\n // d[3] = 0 (padding)\n\n // \u2500\u2500 Color (uint8 RGB \u2192 0-1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const cb = offColor + i * 3;\n data[d + 4] = view.getUint8(cb + 0) / 255;\n data[d + 5] = view.getUint8(cb + 1) / 255;\n data[d + 6] = view.getUint8(cb + 2) / 255;\n\n // \u2500\u2500 Alpha (uint8 \u2192 0-1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n data[d + 7] = view.getUint8(offAlpha + i) / 255;\n\n // \u2500\u2500 Scale (uint8 log-space \u2192 linear) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const sb = offScale + i * 3;\n data[d + 8] = Math.exp((view.getUint8(sb + 0) - 128) / 16);\n data[d + 9] = Math.exp((view.getUint8(sb + 1) - 128) / 16);\n data[d + 10] = Math.exp((view.getUint8(sb + 2) - 128) / 16);\n // d[11] = 0 (padding)\n\n // \u2500\u2500 Rotation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const rb = offRot + i * rotSize;\n let qx, qy, qz, qw;\n\n if (version >= 3) {\n // Smallest-3 encoding: 4 bytes\n // bits [ 1: 0] largestIdx (0=x, 1=y, 2=z, 3=w)\n // bits [11: 2] component a (int10 signed)\n // bits [21:12] component b (int10 signed)\n // bits [31:22] component c (int10 signed)\n const u32 = view.getUint32(rb, true);\n const idx = u32 & 3;\n const s = SQRT2INV / 512; // int10 \u2192 [-1/\u221A2, 1/\u221A2]\n const a = signExtend10((u32 >> 2) & 0x3FF) * s;\n const b_ = signExtend10((u32 >> 12) & 0x3FF) * s;\n const c = signExtend10((u32 >> 22) & 0x3FF) * s;\n const d_ = Math.sqrt(Math.max(0, 1 - a*a - b_*b_ - c*c));\n\n // Re-insert the reconstructed largest component\n switch (idx) {\n case 0: qx = d_; qy = a; qz = b_; qw = c; break;\n case 1: qx = a; qy = d_; qz = b_; qw = c; break;\n case 2: qx = a; qy = b_; qz = d_; qw = c; break;\n default: qx = a; qy = b_; qz = c; qw = d_;\n }\n } else {\n // v2: int8 x,y,z; reconstruct w = sqrt(1 - x\u00B2-y\u00B2-z\u00B2)\n qx = view.getInt8(rb + 0) / 128;\n qy = view.getInt8(rb + 1) / 128;\n qz = view.getInt8(rb + 2) / 128;\n qw = Math.sqrt(Math.max(0, 1 - qx*qx - qy*qy - qz*qz));\n }\n\n const ql = Math.hypot(qx, qy, qz, qw) || 1;\n data[d + 12] = qx / ql;\n data[d + 13] = qy / ql;\n data[d + 14] = qz / ql;\n data[d + 15] = qw / ql;\n\n // \u2500\u2500 SH-rest (uint8 \u2192 [-1,1], basis-major RGB-interleaved \u2014 matches the\n // reference niantic-labs/spz layout exactly, so no reordering needed) \u2500\u2500\n if (hasSH) {\n const fb = offSH + i * fileNumSHBases * 3; // file's per-point block start\n const db = i * numSHBases * 3;\n for (let k = 0; k < numSHBases * 3; k++) {\n shData[db + k] = decodeSH(view.getUint8(fb + k));\n }\n }\n\n if ((i + 1) % PARSE_CHUNK === 0) await yieldToMain();\n }\n\n return { data, count: numPoints, shData, numSHBases };\n}\n\n// \u2500\u2500 Binary helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function readInt24(view, offset) {\n const lo = view.getUint8(offset);\n const mi = view.getUint8(offset + 1);\n const hi = view.getInt8(offset + 2); // signed \u2192 sign extension\n return lo | (mi << 8) | (hi << 16);\n}\n\nexport function signExtend10(v) {\n // Sign-extend 10-bit integer to JS number\n return v & 0x200 ? v | 0xFFFFFC00 : v;\n}\n\n/** Inverse of encodeScale (compress.js): log-space uint8 \u2192 linear scale. */\nexport function decodeScale(byte) {\n return Math.exp((byte - 128) / 16);\n}\n\n/** Inverse of encodeSH (compress.js): matches the reference\n * niantic-labs/spz unquantizeSH formula exactly. */\nexport function decodeSH(byte) {\n return (byte - 128) / 128;\n}\n\n/** Inverse of encodeQuat (compress.js): smallest-3-encoded uint32 \u2192 normalized [qx,qy,qz,qw]. */\nexport function decodeQuat(u32) {\n const idx = u32 & 3;\n const s = SQRT2INV / 512;\n const a = signExtend10((u32 >> 2) & 0x3FF) * s;\n const b = signExtend10((u32 >> 12) & 0x3FF) * s;\n const c = signExtend10((u32 >> 22) & 0x3FF) * s;\n const d = Math.sqrt(Math.max(0, 1 - a*a - b*b - c*c));\n\n let qx, qy, qz, qw;\n switch (idx) {\n case 0: qx = d; qy = a; qz = b; qw = c; break;\n case 1: qx = a; qy = d; qz = b; qw = c; break;\n case 2: qx = a; qy = b; qz = d; qw = c; break;\n default: qx = a; qy = b; qz = c; qw = d;\n }\n const len = Math.hypot(qx, qy, qz, qw) || 1;\n return [qx / len, qy / len, qz / len, qw / len];\n}\n\n// \u2500\u2500 Gzip decompression (uses browser DecompressionStream API) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function decompressGzip(buffer) {\n if (typeof DecompressionStream === 'undefined') {\n throw new Error('DecompressionStream is not available in this environment');\n }\n const stream = new DecompressionStream('gzip');\n const writer = stream.writable.getWriter();\n writer.write(buffer);\n writer.close();\n return new Response(stream.readable).arrayBuffer();\n}\n\n// fetchWithProgress is defined in fetch-utils.js (shared)\n", "/**\n * HoloSplat compressor \u2014 encodes canonical Gaussian data to the .spz format\n * and gzip-compresses it for storage or transfer.\n *\n * The .spz format (Niantic Labs, v3) stores each Gaussian as:\n * Position 9 bytes (int24 \u00D7 3, fixed-point with auto-selected precision)\n * Alpha 1 byte (uint8)\n * Color 3 bytes (uint8 RGB)\n * Scale 3 bytes (uint8 log-space quantized)\n * Rotation 4 bytes (smallest-3 quaternion encoding)\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * Total 20 bytes/Gaussian (before gzip) vs 32 bytes for .splat\n *\n * Input: canonical Float32Array (16 floats/Gaussian):\n * [0-2] pos.xyz\n * [3] pad\n * [4-7] color.rgba (0-1)\n * [8-10] scale.xyz (linear)\n * [11] pad\n * [12-15] quat.xyzw\n *\n * Usage:\n * import { compressToSpz } from 'holosplat';\n * const buffer = await compressToSpz(data, count);\n * const blob = new Blob([buffer], { type: 'application/octet-stream' });\n */\n\nconst SPZ_MAGIC = 0x5053474E;\nconst SPZ_VERSION = 3;\n\n/**\n * Encode canonical Gaussian data and gzip-compress to a .spz ArrayBuffer.\n *\n * @param {Float32Array} data \u2013 16 floats/Gaussian, canonical layout\n * @param {number} count \u2013 number of Gaussians\n * @param {object} [opts]\n * @param {number} [opts.fractionalBits] \u2013 position fixed-point precision\n * (auto-detected from data range if omitted)\n * @returns {Promise<ArrayBuffer>}\n */\nexport async function compressToSpz(data, count, opts = {}) {\n const raw = encodeSpz(data, count, opts);\n return compressGzip(raw);\n}\n\n/**\n * Encode canonical Gaussian data to a raw (uncompressed) .spz Uint8Array.\n * Useful if you want to compress with a different algorithm or inspect the bytes.\n *\n * @param {Float32Array} data\n * @param {number} count\n * @param {object} [opts]\n * @param {number} [opts.fractionalBits]\n * @param {Float32Array} [opts.shData] \u2013 SH-rest coefficients, canonical\n * layout (gi*numBases+basis)*3+channel \u2014 see ply-loader.js. Omit for DC-only.\n * @param {number} [opts.numSHBases] \u2013 3/8/15 for SH degree 1/2/3\n * @returns {Uint8Array}\n */\nexport function encodeSpz(data, count, opts = {}) {\n // \u2500\u2500 Choose position fixed-point precision \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // int24 range: -8,388,608 \u2026 8,388,607 (2^23 - 1)\n // max representable position = INT24_MAX / 2^fractionalBits\n let { fractionalBits } = opts;\n if (fractionalBits == null) {\n let maxAbsPos = 0;\n for (let i = 0; i < count; i++) {\n const b = i * 16;\n if (Math.abs(data[b]) > maxAbsPos) maxAbsPos = Math.abs(data[b]);\n if (Math.abs(data[b + 1]) > maxAbsPos) maxAbsPos = Math.abs(data[b + 1]);\n if (Math.abs(data[b + 2]) > maxAbsPos) maxAbsPos = Math.abs(data[b + 2]);\n }\n const INT24_MAX = (1 << 23) - 1;\n fractionalBits = maxAbsPos > 0\n ? Math.min(20, Math.max(0, Math.floor(Math.log2(INT24_MAX / maxAbsPos))))\n : 12;\n }\n\n // \u2500\u2500 SH-rest (optional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // numBases 3/8/15 \u2192 degree 1/2/3 (matches dimForDegree in the reference\n // niantic-labs/spz encoder \u2014 see also degreeForDim).\n const { shData, numSHBases = 0 } = opts;\n const shDegree = numSHBases >= 15 ? 3 : numSHBases >= 8 ? 2 : numSHBases >= 3 ? 1 : 0;\n const shBytesPerPoint = numSHBases * 3;\n\n // \u2500\u2500 Allocate buffer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const HEADER = 16;\n const total = HEADER + count * (9 + 1 + 3 + 3 + 4 + shBytesPerPoint);\n const buf = new ArrayBuffer(total);\n const view = new DataView(buf);\n const u8 = new Uint8Array(buf);\n\n // \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n view.setUint32( 0, SPZ_MAGIC, true);\n view.setUint32( 4, SPZ_VERSION, true);\n view.setUint32( 8, count, true);\n view.setUint8 (12, shDegree);\n view.setUint8 (13, fractionalBits);\n view.setUint8 (14, 0); // flags\n view.setUint8 (15, 0); // reserved\n\n // \u2500\u2500 Section offsets (column-organised, matching the SPZ spec) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const offPos = HEADER;\n const offAlpha = offPos + count * 9;\n const offColor = offAlpha + count * 1;\n const offScale = offColor + count * 3;\n const offRot = offScale + count * 3;\n const offSH = offRot + count * 4;\n\n const posScale = 1 << fractionalBits;\n\n for (let i = 0; i < count; i++) {\n const d = i * 16;\n\n // Position (float \u2192 fixed-point int24 LE)\n writeInt24(view, offPos + i * 9 + 0, data[d + 0] * posScale);\n writeInt24(view, offPos + i * 9 + 3, data[d + 1] * posScale);\n writeInt24(view, offPos + i * 9 + 6, data[d + 2] * posScale);\n\n // Alpha\n u8[offAlpha + i] = clampU8(data[d + 7] * 255);\n\n // Color RGB\n u8[offColor + i * 3 + 0] = clampU8(data[d + 4] * 255);\n u8[offColor + i * 3 + 1] = clampU8(data[d + 5] * 255);\n u8[offColor + i * 3 + 2] = clampU8(data[d + 6] * 255);\n\n // Scale: linear \u2192 log-space uint8\n // Inverse of: Math.exp((b - 128) / 16) \u2192 b = log(scale) * 16 + 128\n u8[offScale + i * 3 + 0] = encodeScale(data[d + 8]);\n u8[offScale + i * 3 + 1] = encodeScale(data[d + 9]);\n u8[offScale + i * 3 + 2] = encodeScale(data[d + 10]);\n\n // Rotation: smallest-3 encoding (4 bytes, uint32 LE)\n view.setUint32(offRot + i * 4,\n encodeQuat(data[d + 12], data[d + 13], data[d + 14], data[d + 15]),\n true);\n\n // SH-rest: basis-major, RGB-interleaved (matches the reference\n // niantic-labs/spz layout exactly, so no reordering is needed \u2014 see\n // encodeSH). shData uses the same layout already (ply-loader.js).\n if (shData && numSHBases > 0) {\n const sBase = i * shBytesPerPoint;\n const dBase = i * numSHBases * 3;\n for (let k = 0; k < shBytesPerPoint; k++) {\n u8[offSH + sBase + k] = encodeSH(shData[dBase + k]);\n }\n }\n }\n\n return u8;\n}\n\n// \u2500\u2500 Encoding helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function writeInt24(view, offset, value) {\n const v = Math.max(-8388608, Math.min(8388607, Math.round(value)));\n view.setUint8(offset, v & 0xFF);\n view.setUint8(offset + 1, (v >> 8) & 0xFF);\n view.setUint8(offset + 2, (v >> 16) & 0xFF);\n}\n\nexport function clampU8(v) {\n return Math.max(0, Math.min(255, Math.round(v)));\n}\n\nexport function encodeScale(linear) {\n return clampU8(Math.log(Math.max(1e-9, linear)) * 16 + 128);\n}\n\n/** Inverse of decodeSH (spz-loader.js): matches the reference\n * niantic-labs/spz quantizeSH/unquantizeSH formula exactly. */\nexport function encodeSH(x) {\n return clampU8(Math.round(x * 128) + 128);\n}\n\n/**\n * Encode a unit quaternion (x,y,z,w) using smallest-3 / \"omit largest\" encoding.\n *\n * The largest component is stored implicitly as sqrt(1 - a\u00B2 - b\u00B2 - c\u00B2).\n * Canonically it must be positive, so we flip the sign of all components when\n * the largest is negative (the rotation is identical either way).\n *\n * Packed uint32:\n * bits [ 1: 0] index of largest component (0=x,1=y,2=z,3=w)\n * bits [11: 2] a (int10, scaled by 512*\u221A2 to fill [-1/\u221A2, 1/\u221A2])\n * bits [21:12] b\n * bits [31:22] c\n */\nexport function encodeQuat(qx, qy, qz, qw) {\n // Normalize\n const len = Math.hypot(qx, qy, qz, qw) || 1;\n const q = [qx / len, qy / len, qz / len, qw / len];\n\n // Find largest-magnitude component\n let maxIdx = 0;\n for (let j = 1; j < 4; j++) {\n if (Math.abs(q[j]) > Math.abs(q[maxIdx])) maxIdx = j;\n }\n\n // Ensure largest is positive (flip sign of whole quaternion if needed)\n const sign = q[maxIdx] < 0 ? -1 : 1;\n\n // The three \"other\" components, in the order the decoder expects:\n // maxIdx=0 \u2192 [qy,qz,qw] idx [1,2,3]\n // maxIdx=1 \u2192 [qx,qz,qw] idx [0,2,3]\n // maxIdx=2 \u2192 [qx,qy,qw] idx [0,1,3]\n // maxIdx=3 \u2192 [qx,qy,qz] idx [0,1,2]\n // [0,1,2,3].filter(j => j !== maxIdx) produces exactly these in ascending order.\n const others = [0, 1, 2, 3].filter(j => j !== maxIdx);\n\n // Scale factor: decoder uses s = (1/\u221A2) / 512, encoder uses 1/s = 512*\u221A2\n const S = 512 * Math.SQRT2;\n const encode10 = j => Math.max(-512, Math.min(511, Math.round(q[j] * sign * S)));\n\n const a = encode10(others[0]);\n const b = encode10(others[1]);\n const c = encode10(others[2]);\n\n // Pack and return as unsigned 32-bit (>>> 0 ensures correct bit pattern)\n return ((maxIdx & 3) | ((a & 0x3FF) << 2) | ((b & 0x3FF) << 12) | ((c & 0x3FF) << 22)) >>> 0;\n}\n\n// \u2500\u2500 Gzip compression \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function compressGzip(data) {\n if (typeof CompressionStream === 'undefined') {\n throw new Error('CompressionStream API is not available in this environment');\n }\n const stream = new CompressionStream('gzip');\n const writer = stream.writable.getWriter();\n writer.write(data);\n writer.close();\n return new Response(stream.readable).arrayBuffer();\n}\n", "/**\n * HoloSplat variant packer \u2014 merges N color/material variants of the same\n * geometry into a single .spzv file: geometry (position/scale/rotation) is\n * stored once, plus one small color+alpha palette per variant. The viewer\n * can swap the active palette at runtime (Viewer#setVariant) with no reload\n * and without rendering every variant simultaneously.\n *\n * .spzv layout (gzip-compressed), reusing the .spz per-Gaussian encodings\n * (see compress.js):\n *\n * Header (16 bytes, little-endian):\n * [0-3] magic uint32 = 0x56505348 (\"HSPV\")\n * [4-7] version uint32 = 1\n * [8-11] numPoints uint32\n * [12] numVariants uint8\n * [13] fractionalBits uint8 (position precision, as in .spz)\n * [14] flags uint8 (reserved)\n * [15] reserved uint8\n *\n * Variant names: numVariants \u00D7 16 bytes (UTF-8, NUL-padded)\n *\n * Geometry (shared, numPoints entries):\n * Position numPoints \u00D7 9 bytes (int24 \u00D7 3, \u00F7 2^fractionalBits)\n * Scale numPoints \u00D7 3 bytes (log-space uint8)\n * Rotation numPoints \u00D7 4 bytes (smallest-3 encoding)\n *\n * Palettes (one block per variant, in declaration order, numPoints entries):\n * Alpha numPoints \u00D7 1 byte\n * Color numPoints \u00D7 3 bytes (uint8 RGB)\n *\n * Usage:\n * import { compressVariantsToSpzv } from 'holosplat';\n * const buffer = await compressVariantsToSpzv([\n * { name: 'blue', data: blueData },\n * { name: 'yellow', data: yellowData },\n * ], count);\n */\n\nimport { writeInt24, clampU8, encodeScale, encodeQuat, compressGzip } from './compress.js';\n\nexport const SPZV_MAGIC = 0x56505348; // \"HSPV\"\nexport const SPZV_VERSION = 1;\nexport const SPZV_NAME_BYTES = 16;\n\n/**\n * Encode N color/material variants of the same geometry into a raw\n * (uncompressed) .spzv Uint8Array. Geometry (position/scale/rotation) is\n * taken from `variants[0].data`; every variant contributes its own\n * color+alpha palette.\n *\n * @param {{name: string, data: Float32Array}[]} variants \u2013 each `data` is\n * 16 floats/Gaussian canonical layout, `count` Gaussians, same geometry\n * @param {number} count\n * @param {object} [opts]\n * @param {number} [opts.fractionalBits] \u2013 position fixed-point precision\n * @returns {Uint8Array}\n */\nexport function encodeSpzv(variants, count, opts = {}) {\n if (!variants.length) throw new Error('HoloSplat: encodeSpzv requires at least one variant');\n if (variants.length > 255) throw new Error('HoloSplat: encodeSpzv supports at most 255 variants');\n\n const geom = variants[0].data;\n\n let { fractionalBits } = opts;\n if (fractionalBits == null) {\n let maxAbsPos = 0;\n for (let i = 0; i < count; i++) {\n const b = i * 16;\n maxAbsPos = Math.max(maxAbsPos, Math.abs(geom[b]), Math.abs(geom[b + 1]), Math.abs(geom[b + 2]));\n }\n const INT24_MAX = (1 << 23) - 1;\n fractionalBits = maxAbsPos > 0\n ? Math.min(20, Math.max(0, Math.floor(Math.log2(INT24_MAX / maxAbsPos))))\n : 12;\n }\n\n const numVariants = variants.length;\n const HEADER = 16;\n const NAMES = numVariants * SPZV_NAME_BYTES;\n const GEOM = count * (9 + 3 + 4);\n const PALETTE = count * (1 + 3);\n const total = HEADER + NAMES + GEOM + numVariants * PALETTE;\n\n const buf = new ArrayBuffer(total);\n const view = new DataView(buf);\n const u8 = new Uint8Array(buf);\n\n view.setUint32( 0, SPZV_MAGIC, true);\n view.setUint32( 4, SPZV_VERSION, true);\n view.setUint32( 8, count, true);\n view.setUint8 (12, numVariants);\n view.setUint8 (13, fractionalBits);\n view.setUint8 (14, 0); // flags\n view.setUint8 (15, 0); // reserved\n\n let off = HEADER;\n const encoder = new TextEncoder();\n for (const v of variants) {\n const bytes = encoder.encode(v.name).slice(0, SPZV_NAME_BYTES - 1);\n u8.set(bytes, off);\n off += SPZV_NAME_BYTES;\n }\n\n const offPos = off;\n const offScale = offPos + count * 9;\n const offRot = offScale + count * 3;\n off = offRot + count * 4;\n\n const posScale = 1 << fractionalBits;\n for (let i = 0; i < count; i++) {\n const d = i * 16;\n\n writeInt24(view, offPos + i * 9 + 0, geom[d + 0] * posScale);\n writeInt24(view, offPos + i * 9 + 3, geom[d + 1] * posScale);\n writeInt24(view, offPos + i * 9 + 6, geom[d + 2] * posScale);\n\n u8[offScale + i * 3 + 0] = encodeScale(geom[d + 8]);\n u8[offScale + i * 3 + 1] = encodeScale(geom[d + 9]);\n u8[offScale + i * 3 + 2] = encodeScale(geom[d + 10]);\n\n view.setUint32(offRot + i * 4,\n encodeQuat(geom[d + 12], geom[d + 13], geom[d + 14], geom[d + 15]),\n true);\n }\n\n for (const v of variants) {\n const offAlpha = off;\n const offColor = offAlpha + count;\n for (let i = 0; i < count; i++) {\n const d = i * 16;\n u8[offAlpha + i] = clampU8(v.data[d + 7] * 255);\n u8[offColor + i * 3 + 0] = clampU8(v.data[d + 4] * 255);\n u8[offColor + i * 3 + 1] = clampU8(v.data[d + 5] * 255);\n u8[offColor + i * 3 + 2] = clampU8(v.data[d + 6] * 255);\n }\n off = offColor + count * 3;\n }\n\n return u8;\n}\n\n/**\n * Encode and gzip-compress N color/material variants to a .spzv ArrayBuffer.\n * @see encodeSpzv\n * @returns {Promise<ArrayBuffer>}\n */\nexport async function compressVariantsToSpzv(variants, count, opts = {}) {\n const raw = encodeSpzv(variants, count, opts);\n return compressGzip(raw);\n}\n", "/**\n * Loader for the .spzv packed-variant format \u2014 see variant-pack.js for the\n * on-disk layout. Decodes to canonical Float32Array geometry (16 floats/\n * Gaussian, color/alpha taken from the first variant) plus the full set of\n * per-variant color+alpha palettes for runtime swapping.\n */\n\nimport { fetchWithProgress } from './fetch-utils.js';\nimport { readInt24, decodeScale, decodeQuat, decompressGzip } from './spz-loader.js';\nimport { SPZV_MAGIC, SPZV_NAME_BYTES } from '../variant-pack.js';\n\n// Yield to main thread every PARSE_CHUNK Gaussians while decoding \u2014 keeps the\n// page responsive for large files (see spz-loader.js).\nconst PARSE_CHUNK = 50000;\n\nfunction yieldToMain() {\n return new Promise(resolve => setTimeout(resolve, 0));\n}\n\nexport async function loadSpzv(url, onProgress) {\n const compressed = await fetchWithProgress(url, onProgress);\n const buffer = await decompressGzip(compressed);\n return parseSpzv(buffer);\n}\n\n/** Decompress and parse an in-memory gzip-compressed .spzv buffer (e.g. from a File). */\nexport async function parseSpzvGzip(compressed) {\n const buffer = await decompressGzip(compressed);\n return parseSpzv(buffer);\n}\n\n/**\n * @param {ArrayBuffer} buffer\n * @returns {Promise<{\n * data: Float32Array,\n * count: number,\n * variants: {name: string, palette: Float32Array}[],\n * }>}\n * `data` is canonical 16-floats/Gaussian layout using the first variant's\n * palette. Each `variants[i].palette` is `count * 4` floats: r,g,b,a.\n */\nexport async function parseSpzv(buffer) {\n const view = new DataView(buffer);\n const u8 = new Uint8Array(buffer);\n\n const magic = view.getUint32(0, true);\n const version = view.getUint32(4, true);\n const count = view.getUint32(8, true);\n const numVariants = view.getUint8(12);\n const fractionalBits = view.getUint8(13);\n\n if (magic !== SPZV_MAGIC) {\n throw new Error(\n `Invalid .spzv magic: 0x${magic.toString(16).toUpperCase()} ` +\n `(expected 0x${SPZV_MAGIC.toString(16).toUpperCase()})`\n );\n }\n if (version !== 1) {\n console.warn(`HoloSplat: .spzv version ${version} is untested; attempting load anyway`);\n }\n\n // \u2500\u2500 Variant names \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let off = 16;\n const decoder = new TextDecoder();\n const names = [];\n for (let v = 0; v < numVariants; v++) {\n const bytes = u8.subarray(off, off + SPZV_NAME_BYTES);\n const nul = bytes.indexOf(0);\n names.push(decoder.decode(bytes.subarray(0, nul < 0 ? SPZV_NAME_BYTES : nul)));\n off += SPZV_NAME_BYTES;\n }\n\n // \u2500\u2500 Geometry (shared) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const offPos = off;\n const offScale = offPos + count * 9;\n const offRot = offScale + count * 3;\n off = offRot + count * 4;\n\n const posDiv = 1 << fractionalBits;\n const data = new Float32Array(count * 16);\n\n for (let i = 0; i < count; i++) {\n const d = i * 16;\n const pb = offPos + i * 9;\n data[d + 0] = readInt24(view, pb + 0) / posDiv;\n data[d + 1] = readInt24(view, pb + 3) / posDiv;\n data[d + 2] = readInt24(view, pb + 6) / posDiv;\n\n const sb = offScale + i * 3;\n data[d + 8] = decodeScale(u8[sb + 0]);\n data[d + 9] = decodeScale(u8[sb + 1]);\n data[d + 10] = decodeScale(u8[sb + 2]);\n\n const [qx, qy, qz, qw] = decodeQuat(view.getUint32(offRot + i * 4, true));\n data[d + 12] = qx;\n data[d + 13] = qy;\n data[d + 14] = qz;\n data[d + 15] = qw;\n\n if ((i + 1) % PARSE_CHUNK === 0) await yieldToMain();\n }\n\n // \u2500\u2500 Palettes (one per variant) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const variants = [];\n for (let v = 0; v < numVariants; v++) {\n const offAlpha = off;\n const offColor = offAlpha + count;\n const palette = new Float32Array(count * 4);\n\n for (let i = 0; i < count; i++) {\n const p = i * 4;\n palette[p + 0] = u8[offColor + i * 3 + 0] / 255;\n palette[p + 1] = u8[offColor + i * 3 + 1] / 255;\n palette[p + 2] = u8[offColor + i * 3 + 2] / 255;\n palette[p + 3] = u8[offAlpha + i] / 255;\n\n if (v === 0) {\n const d = i * 16;\n data[d + 4] = palette[p + 0];\n data[d + 5] = palette[p + 1];\n data[d + 6] = palette[p + 2];\n data[d + 7] = palette[p + 3];\n }\n }\n\n off = offColor + count * 3;\n variants.push({ name: names[v], palette });\n if ((v + 1) % 1 === 0) await yieldToMain();\n }\n\n return { data, count, variants, shData: null, numSHBases: 0 };\n}\n", "/**\n * HoloSplat Animation \u2014 drives the camera from Blender-exported keyframe data.\n *\n * JSON format (produced by blender/export_holosplat.py):\n * {\n * \"version\" : 1,\n * \"fps\" : 24,\n * \"frameCount\" : 120,\n * \"fov\" : 60, // optional \u2014 overrides player fov when present\n * \"frames\" : [ // flat array, 6 values per frame:\n * ex, ey, ez, // eye position (viewer Y-up space)\n * fx, fy, fz, // normalized forward vector\n * ...\n * ],\n * \"callouts\" : [ // world-space points to project onto the screen\n * { \"id\": \"label\", \"pos\": [x, y, z] }\n * ],\n * \"markers\" : { // Blender timeline markers \u2192 frame numbers (0-based)\n * \"intro\" : 0, // used by scroll-scene data-from / data-to attributes\n * \"desk_reveal\" : 72,\n * \"end\" : 119\n * },\n * \"stateCalls\" : [ // \"state: <asset>.<axis>=<value>\" markers\n * { \"frame\": 80, \"asset\": \"headphones\", \"axis\": \"fold\", \"value\": \"folded\" }\n * ]\n * }\n *\n * Coordinates are in viewer Y-up space. The Blender export script converts\n * from Blender's Z-up space automatically.\n */\n\n// Quaternion slerp \u2014 takes the shortest arc and normalizes at the boundary.\nexport function slerpQuat(ax, ay, az, aw, bx, by, bz, bw, t) {\n let dot = ax * bx + ay * by + az * bz + aw * bw;\n if (dot < 0) { bx = -bx; by = -by; bz = -bz; bw = -bw; dot = -dot; }\n if (dot > 0.9995) {\n // Quaternions nearly identical \u2014 safe to lerp + normalize.\n const x = ax + (bx - ax) * t, y = ay + (by - ay) * t;\n const z = az + (bz - az) * t, w = aw + (bw - aw) * t;\n const n = Math.sqrt(x * x + y * y + z * z + w * w);\n return [x / n, y / n, z / n, w / n];\n }\n const theta0 = Math.acos(dot), theta = theta0 * t;\n const st0 = Math.sin(theta0), st = Math.sin(theta);\n const sa = Math.cos(theta) - dot * st / st0, sb = st / st0;\n return [sa * ax + sb * bx, sa * ay + sb * by, sa * az + sb * bz, sa * aw + sb * bw];\n}\n\n/**\n * Extract the splat file stem from a Blender empty name.\n *\n * Old convention \u2014 control prefixes followed by a single name:\n * \"fork-left\" \u2192 \"fork-left\"\n * \"ctrl.fork-left\" \u2192 \"fork-left\"\n * \"ctrl.fork-left.001\" \u2192 \"fork-left\"\n * \"hs-part.ctrl.fork-left\" \u2192 \"fork-left\"\n *\n * New hierarchical convention \u2014 dotted name IS the file stem:\n * \"headphones.headband\" \u2192 \"headphones.headband\"\n * \"headphones.cup.l\" \u2192 \"headphones.cup.l\"\n * \"headphones.cup.l.001\" \u2192 \"headphones.cup.l\"\n *\n * Control prefixes \"hs-part.\" and \"ctrl.\" are always stripped first,\n * then trailing all-digit Blender duplicate suffixes (e.g. \".001\") are removed.\n */\nexport function splatNameFromId(id) {\n let s = id.replace(/^hs-part\\./, '').replace(/^ctrl\\./, '');\n s = s.replace(/(\\.\\d+)+$/, '');\n return s;\n}\n\nexport class Animation {\n /**\n * @param {object} data Parsed JSON from export_holosplat.py\n */\n constructor(data) {\n if (!data.frames || data.frames.length === 0) {\n throw new Error('HoloSplat Animation: no frame data');\n }\n\n this.fps = data.fps ?? 24;\n this.frameCount = data.frameCount ?? Math.floor(data.frames.length / 6);\n this.fov = data.fov ?? null; // null = keep player fov\n this.near = data.near ?? null; // null = keep player near\n this.far = data.far ?? null; // null = keep player far\n this.callouts = data.callouts ?? [];\n this.focalPoint = data.focalPoint ?? null;\n this._focalFrames = data.focalFrames ? new Float32Array(data.focalFrames) : null;\n this.loop = true;\n\n // Named timeline markers: { markerName: frameNumber }\n // Accepts either object { name: frame } or array [{ name, frame }]\n if (Array.isArray(data.markers)) {\n this.markers = Object.fromEntries(data.markers.map(m => [m.name, m.frame]));\n } else {\n this.markers = data.markers ?? {};\n }\n\n // \"state: <asset>.<axis>=<value>\" markers \u2014 calls into an asset's own\n // state axis (see export_holosplat_asset.py). Resolved every tick by\n // Viewer#_syncAssetStates, same \"most recently passed\" rule as markers\n // above, so seeking/scrubbing the main timeline lands on the right value\n // without needing edge-triggered crossing detection.\n this.stateCalls = data.stateCalls ?? [];\n\n // Typed array: 6 floats per frame [ex ey ez fx fy fz]\n this._frames = new Float32Array(data.frames);\n\n // Per-object animated transforms: 7 floats per frame [px py pz qx qy qz qw]\n this._objects = (data.objects ?? []).map(obj => ({\n id: obj.id,\n frames: new Float32Array(obj.frames),\n variants: obj.variants,\n }));\n\n // Mask volumes: each has a name prefix (for CPU-side part matching) and\n // 16 floats per frame (world-space col-major mat4).\n this._volumes = (data.volumes ?? []).map(v => ({\n name: v.name,\n softEdge: v.softEdge ?? 0.05,\n matrices: new Float32Array(v.matrices),\n }));\n\n // Asset anchors: a pure parent transform (7 floats per frame, same\n // layout as _objects) for an externally-loaded asset's parts \u2014 see\n // Viewer#_tick, which composes this onto any loaded part whose id is\n // namespaced under the anchor's asset id (\"hs-anchor.<assetId>\" in\n // Blender). Not bind-pose relative like parts \u2014 there's no rest pose to\n // subtract, the asset's own parts already carry their own.\n this._anchors = (data.anchors ?? []).map(a => ({\n asset: a.asset,\n frames: new Float32Array(a.frames),\n }));\n\n // Clips (independent, button-triggered per-object animations \u2014 product\n // customization) live in their own separate asset JSON files now, loaded\n // via Viewer#loadClips \u2014 not part of the main Animation/timeline at all.\n // See export_holosplat_asset.py and Viewer#playClip.\n\n this._time = 0;\n this._playing = true;\n this.direction = 1; // 1 = forward, -1 = reverse\n this.pingPong = false; // bounce direction at each end instead of looping\n }\n\n // \u2500\u2500 Read-only state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n get duration() { return this.frameCount / this.fps; }\n get time() { return this._time; }\n get playing() { return this._playing; }\n /** Array of tracked objects: [{ id, frames, variants? }]. Empty for v1 animations. */\n get objects() { return this._objects; }\n /** Array of mask volumes: [{ name, softEdge, matrices }]. Empty if none exported. */\n get volumes() { return this._volumes; }\n /** Array of asset anchors: [{ asset, frames }]. Empty if none exported. */\n get anchors() { return this._anchors; }\n\n // \u2500\u2500 Playback control \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n play() { this._playing = true; }\n pause() { this._playing = false; }\n\n seek(seconds) {\n this._time = Math.max(0, Math.min(this.duration, seconds));\n }\n\n /** Seek to an exact frame number (0-based). */\n seekFrame(frame) {\n this._time = Math.max(0, Math.min(this.frameCount - 1, frame)) / this.fps;\n }\n\n /** Current playback position as a frame number (may be fractional). */\n get frame() {\n return this._time * this.fps;\n }\n\n /**\n * Advance playback by dt seconds. Call once per render tick.\n */\n tick(dt) {\n if (!this._playing) return;\n this._time += dt * this.direction;\n if (this.direction >= 0) {\n if (this._time >= this.duration) {\n if (this.pingPong) {\n this.direction = -1;\n this._time = 2 * this.duration - this._time; // reflect\n } else {\n this._time = this.loop ? this._time % this.duration : this.duration;\n if (!this.loop) this._playing = false;\n }\n }\n } else {\n if (this._time <= 0) {\n if (this.pingPong) {\n this.direction = 1;\n this._time = -this._time; // reflect\n } else if (this.loop) {\n this._time = this.duration + this._time; // wrap (time was negative)\n } else {\n this._time = 0;\n this._playing = false;\n }\n }\n }\n }\n\n // \u2500\u2500 Camera frame \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Returns [{ id, pos:[x,y,z], quat:[x,y,z,w] }] for every tracked object at\n * the current playback time, or at `frameOverride` if given \u2014 lets callers\n * sample a pose at an arbitrary frame without touching actual playback\n * state (see player.js's scene-blend, which needs to peek at an adjacent\n * scene's frame while still playing its own).\n * Empty array for v1 animations with no objects.\n */\n getObjectFrames(frameOverride) {\n const rawFrame = frameOverride != null\n ? Math.max(0, Math.min(frameOverride, this.frameCount - 1))\n : Math.min(this._time * this.fps, this.frameCount - 1);\n const frameA = Math.min(Math.floor(rawFrame), this.frameCount - 1);\n const frameB = Math.min(frameA + 1, this.frameCount - 1);\n const t = rawFrame - frameA;\n return this._objects.map(obj => {\n const f = obj.frames;\n const ia = frameA * 7, ib = frameB * 7;\n return {\n id: obj.id,\n pos: [\n f[ia] + (f[ib] - f[ia]) * t,\n f[ia + 1] + (f[ib + 1] - f[ia + 1]) * t,\n f[ia + 2] + (f[ib + 2] - f[ia + 2]) * t,\n ],\n quat: slerpQuat(f[ia+3], f[ia+4], f[ia+5], f[ia+6], f[ib+3], f[ib+4], f[ib+5], f[ib+6], t),\n };\n });\n }\n\n /**\n * Returns [{ asset, pos:[x,y,z], quat:[x,y,z,w] }] for every asset anchor\n * at the current playback time. Same interpolation as getObjectFrames \u2014\n * see there for frameOverride. Empty array if none exported.\n */\n getAnchorFrames(frameOverride) {\n const rawFrame = frameOverride != null\n ? Math.max(0, Math.min(frameOverride, this.frameCount - 1))\n : Math.min(this._time * this.fps, this.frameCount - 1);\n const frameA = Math.min(Math.floor(rawFrame), this.frameCount - 1);\n const frameB = Math.min(frameA + 1, this.frameCount - 1);\n const t = rawFrame - frameA;\n return this._anchors.map(a => {\n const f = a.frames;\n const ia = frameA * 7, ib = frameB * 7;\n return {\n asset: a.asset,\n pos: [\n f[ia] + (f[ib] - f[ia]) * t,\n f[ia + 1] + (f[ib + 1] - f[ia + 1]) * t,\n f[ia + 2] + (f[ib + 2] - f[ia + 2]) * t,\n ],\n quat: slerpQuat(f[ia+3], f[ia+4], f[ia+5], f[ia+6], f[ib+3], f[ib+4], f[ib+5], f[ib+6], t),\n };\n });\n }\n\n /**\n * Returns [{ name, softEdge, matrix: Float32Array(16) }] for every mask volume\n * at the current playback time, or at `frameOverride` if given \u2014 see\n * getObjectFrames for why (player.js's scene-blend needs to peek at an\n * adjacent scene's mask state too, not just camera/object pose, otherwise\n * masks snap instantly at a scene handoff while everything else eases in).\n * Sub-frame interpolated, same as the other getters, so a mask doesn't\n * step abruptly between integer frames during normal playback either.\n */\n getVolumeFrames(frameOverride) {\n const rawFrame = frameOverride != null\n ? Math.max(0, Math.min(frameOverride, this.frameCount - 1))\n : Math.min(this._time * this.fps, this.frameCount - 1);\n const frameA = Math.min(Math.floor(rawFrame), this.frameCount - 1);\n const frameB = Math.min(frameA + 1, this.frameCount - 1);\n const t = rawFrame - frameA;\n return this._volumes.map(v => {\n const m = v.matrices;\n const ia = frameA * 16, ib = frameB * 16;\n const matrix = new Float32Array(16);\n for (let i = 0; i < 16; i++) matrix[i] = m[ia + i] + (m[ib + i] - m[ia + i]) * t;\n return { name: v.name, softEdge: v.softEdge, matrix };\n });\n }\n\n /**\n * Returns the focal point world position at the current frame,\n * or null if no focal point is defined.\n * Uses per-frame data (focalFrames) when available \u2014 handles the case where\n * the focal-point Empty is a child of an animated parent.\n */\n getFocalPoint() {\n if (!this.focalPoint) return null;\n if (!this._focalFrames) return this.focalPoint;\n const rawFrame = Math.min(this._time * this.fps, this.frameCount - 1);\n const frameA = Math.min(Math.floor(rawFrame), this.frameCount - 1);\n const frameB = Math.min(frameA + 1, this.frameCount - 1);\n const t = rawFrame - frameA;\n const f = this._focalFrames;\n const ia = frameA * 3, ib = frameB * 3;\n return [\n f[ia] + (f[ib] - f[ia]) * t,\n f[ia + 1] + (f[ib + 1] - f[ia + 1]) * t,\n f[ia + 2] + (f[ib + 2] - f[ia + 2]) * t,\n ];\n }\n\n /**\n * Returns { eye, target } arrays for the current playback time, or at\n * `frameOverride` if given \u2014 see getObjectFrames for why.\n * `target` is eye + forward (1 unit ahead), suitable for lookAt.\n */\n getCameraFrame(frameOverride) {\n const rawFrame = frameOverride != null\n ? Math.max(0, Math.min(frameOverride, this.frameCount - 1))\n : Math.min(this._time * this.fps, this.frameCount - 1);\n const frameA = Math.min(Math.floor(rawFrame), this.frameCount - 1);\n const frameB = Math.min(frameA + 1, this.frameCount - 1);\n const t = rawFrame - frameA;\n const f = this._frames;\n const ia = frameA * 6, ib = frameB * 6;\n const ex = f[ia] + (f[ib] - f[ia]) * t;\n const ey = f[ia + 1] + (f[ib + 1] - f[ia + 1]) * t;\n const ez = f[ia + 2] + (f[ib + 2] - f[ia + 2]) * t;\n const fx = f[ia + 3] + (f[ib + 3] - f[ia + 3]) * t;\n const fy = f[ia + 4] + (f[ib + 4] - f[ia + 4]) * t;\n const fz = f[ia + 5] + (f[ib + 5] - f[ia + 5]) * t;\n return {\n eye: [ex, ey, ez],\n target: [ex + fx, ey + fy, ez + fz],\n };\n }\n}\n\n/**\n * Returns [{ id, pos:[x,y,z], quat:[x,y,z,w] }] for every object animated by\n * `clip`, at `frame` (clamped to the clip's own [0, frameCount-1] range and\n * linearly/slerp-interpolated, same scheme as Animation#getObjectFrames).\n * Clips aren't tied to an Animation instance's own playback clock \u2014 Viewer\n * manages each clip's local frame position itself (see Viewer#playClip) \u2014\n * so this is a standalone function rather than an Animation method.\n */\nexport function getClipObjectFrames(clip, frame) {\n const frameA = Math.max(0, Math.min(Math.floor(frame), clip.frameCount - 1));\n const frameB = Math.min(frameA + 1, clip.frameCount - 1);\n const t = Math.max(0, Math.min(1, frame - frameA));\n return clip.objects.map(obj => {\n const f = obj.frames;\n const ia = frameA * 7, ib = frameB * 7;\n return {\n id: obj.id,\n pos: [\n f[ia] + (f[ib] - f[ia]) * t,\n f[ia + 1] + (f[ib + 1] - f[ia + 1]) * t,\n f[ia + 2] + (f[ib + 2] - f[ia + 2]) * t,\n ],\n quat: slerpQuat(f[ia+3], f[ia+4], f[ia+5], f[ia+6], f[ib+3], f[ib+4], f[ib+5], f[ib+6], t),\n };\n });\n}\n\n/**\n * Returns [{ name, softEdge, matrix:Float32Array(16) }] for every mask\n * volume animated by `clip`, at `frame` (clamped + linearly interpolated,\n * same scheme as getClipObjectFrames). A clip's masks are independent of\n * its objects \u2014 a clip may animate masks only (e.g. fading between color\n * variants while the parts themselves stay put).\n */\nexport function getClipMaskFrames(clip, frame) {\n const masks = clip.masks;\n if (!masks || !masks.length) return [];\n const frameA = Math.max(0, Math.min(Math.floor(frame), clip.frameCount - 1));\n const frameB = Math.min(frameA + 1, clip.frameCount - 1);\n const t = Math.max(0, Math.min(1, frame - frameA));\n return masks.map(({ name, softEdge, matrices: m }) => {\n const ia = frameA * 16, ib = frameB * 16;\n const matrix = new Float32Array(16);\n for (let k = 0; k < 16; k++) matrix[k] = m[ia + k] + (m[ib + k] - m[ia + k]) * t;\n return { name, softEdge, matrix };\n });\n}\n\n/**\n * Returns { pos:[x,y,z], quat:[x,y,z,w] } interpolated from a flat 7-floats-\n * per-frame array at `frame` (clamped to [0, frameCount-1]). Used by\n * Viewer's axis-transition playback (see Viewer#playVariant), where each\n * value's part data is its own standalone array, not grouped into a clip.\n */\nexport function interpPosQuat(frames, frameCount, frame) {\n const frameA = Math.max(0, Math.min(Math.floor(frame), frameCount - 1));\n const frameB = Math.min(frameA + 1, frameCount - 1);\n const t = Math.max(0, Math.min(1, frame - frameA));\n const ia = frameA * 7, ib = frameB * 7;\n return {\n pos: [\n frames[ia] + (frames[ib] - frames[ia]) * t,\n frames[ia + 1] + (frames[ib + 1] - frames[ia + 1]) * t,\n frames[ia + 2] + (frames[ib + 2] - frames[ia + 2]) * t,\n ],\n quat: slerpQuat(frames[ia+3], frames[ia+4], frames[ia+5], frames[ia+6], frames[ib+3], frames[ib+4], frames[ib+5], frames[ib+6], t),\n };\n}\n\n/**\n * Returns a Float32Array(16) interpolated from a flat 16-floats-per-frame\n * matrix array at `frame` (clamped to [0, frameCount-1]). See interpPosQuat.\n */\nexport function interpMat4Frames(matrices, frameCount, frame) {\n const frameA = Math.max(0, Math.min(Math.floor(frame), frameCount - 1));\n const frameB = Math.min(frameA + 1, frameCount - 1);\n const t = Math.max(0, Math.min(1, frame - frameA));\n const ia = frameA * 16, ib = frameB * 16;\n const out = new Float32Array(16);\n for (let k = 0; k < 16; k++) out[k] = matrices[ia + k] + (matrices[ib + k] - matrices[ia + k]) * t;\n return out;\n}\n\n/**\n * Fetch and parse an animation JSON file.\n * @param {string} url\n * @returns {Promise<Animation>}\n */\nexport async function loadAnimation(url) {\n const res = await fetch(url);\n if (!res.ok) throw new Error(`HoloSplat: failed to load animation \"${url}\" (HTTP ${res.status})`);\n return new Animation(await res.json());\n}\n", "/**\n * Lightweight device-capability heuristics, used by `create()`/`player()` to\n * pick sane defaults with zero configuration (\"quality: 'auto'\", the default).\n *\n * Tiers and what they cap:\n * 'low' \u2013 maxPixelRatio 1, SH capped at degree 1, prefers a\n * `<name>.lods/<name>.lod3.spz` sibling of the scene file if\n * one exists (40% of splats kept \u2014 the most aggressive tier\n * `generateLods` produces), no background variant prefetch.\n * 'medium' \u2013 maxPixelRatio 1.5, SH capped at degree 1, prefers `lod2`\n * (60% of splats kept), no background variant prefetch.\n * 'high' \u2013 no caps, loads the file as given (`lod0`, 100% of splats),\n * background variant prefetch enabled.\n *\n * 'low' previously disabled SH entirely (cap 0) \u2014 going from full SH to zero\n * is such a visually drastic, \"looks broken\" regression (flat shading reads\n * as fundamentally lower quality than competing viewers, not just slightly\n * softer) that it's a bad trade even on weak devices, and it silently made\n * any SH-degree control (e.g. the /holosplat editor's) look like it was\n * doing nothing on auto-detected 'low'-tier machines. Capping at 1 still\n * saves most of the cost without that.\n *\n * LOD tiers are produced by examples/prune.html (\"Generate LOD tiers\"), in a\n * `<name>.lods/` folder next to the source file.\n */\n\nconst TIER_SETTINGS = {\n low: { maxPixelRatio: 1, shDegreeCap: 1, lod: 3, prefetchVariants: false },\n medium: { maxPixelRatio: 1.5, shDegreeCap: 1, lod: 2, prefetchVariants: false },\n high: { maxPixelRatio: 2, shDegreeCap: Infinity, lod: 0, prefetchVariants: true },\n};\n\n/**\n * Classifies the current device into a quality tier using `navigator`\n * heuristics (memory, core count, save-data, mobile UA). Returns 'high' in\n * non-browser environments or when nothing suggests a weaker device.\n *\n * Mobile GPUs (even on phones with 8GB RAM / 8 cores, e.g. Pixel 7) can't\n * sustain even the 'medium' tier \u2014 measured ~10fps on a Pixel 7 at\n * maxPixelRatio 1.5 / lod 1. Memory/core-count alone overestimates their\n * fill-rate budget, so any mobile UA is capped at 'low' regardless of specs.\n *\n * @returns {'low'|'medium'|'high'}\n */\nexport function detectDeviceTier() {\n if (typeof navigator === 'undefined') return 'high';\n if (navigator.connection?.saveData) return 'low';\n\n const mem = navigator.deviceMemory; // GiB \u2014 Chrome/Edge/Android only\n const cores = navigator.hardwareConcurrency || 4;\n const mobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent || '');\n\n if ((mem != null && mem <= 2) || cores <= 2 || mobile) return 'low';\n if ((mem != null && mem <= 4) || cores <= 4) return 'medium';\n return 'high';\n}\n\n/** @param {'low'|'medium'|'high'} tier */\nexport function qualityForTier(tier) {\n return TIER_SETTINGS[tier] || TIER_SETTINGS.high;\n}\n\n/**\n * Probes for a `<name>.lods/<name>.lod{N}.spz` sibling folder of `url` and\n * returns it if found; otherwise returns `url` unchanged. `lod` of 0 always\n * returns `url` without a network request. LOD tiers are always `.spz`\n * regardless of the source file's own format (see examples/prune.html,\n * which generates them via compressToSpz).\n *\n * @param {string} url\n * @param {number} lod\n * @returns {Promise<string>}\n */\nexport async function resolveLodUrl(url, lod) {\n if (!lod) return url;\n const m = url.match(/\\.(spz|ply|splat)$/i);\n if (!m) return url;\n const base = url.slice(0, -m[0].length);\n const slash = base.lastIndexOf('/');\n const dir = base.slice(0, slash + 1);\n const name = base.slice(slash + 1);\n const candidate = `${dir}${name}.lods/${name}.lod${lod}.spz`;\n try {\n const res = await fetch(candidate, { method: 'HEAD', cache: 'no-store' });\n if (res.ok) return candidate;\n } catch { /* fall through to original url */ }\n return url;\n}\n\n/**\n * Applies {@link resolveLodUrl} across a `parts` map. Values may be a single\n * URL, an array of URLs, or `{ url, variants }` (lazy per-variant parts \u2014\n * only `url` is LOD-resolved; `variants` passes through unchanged).\n */\nexport async function resolvePartsLod(parts, lod) {\n if (!lod) return parts;\n const out = {};\n for (const [id, value] of Object.entries(parts)) {\n if (Array.isArray(value)) {\n out[id] = await Promise.all(value.map(u => resolveLodUrl(u, lod)));\n } else if (value && typeof value === 'object') {\n out[id] = { ...value, url: await resolveLodUrl(value.url, lod) };\n } else {\n out[id] = await resolveLodUrl(value, lod);\n }\n }\n return out;\n}\n", "/**\n * HoloSplat Viewer \u2014 orchestrates load \u2192 sort \u2192 render.\n *\n * Usage:\n * const viewer = new Viewer(options);\n * await viewer.init();\n * await viewer.load(url);\n * viewer.start();\n */\nimport { Renderer, invertMat4 } from './renderer.js';\nimport { OrbitCamera } from './camera.js';\nimport { createSorter } from './sorter.js';\nimport { loadSplat } from './loaders/splat-loader.js';\nimport { loadPly, openPlyStream } from './loaders/ply-loader.js';\nimport { loadSpz } from './loaders/spz-loader.js';\nimport { loadSpzv } from './loaders/spzv-loader.js';\nimport { loadAnimation, getClipObjectFrames, getClipMaskFrames, interpPosQuat, interpMat4Frames, slerpQuat } from './animation.js';\nimport { resolvePartsLod, resolveLodUrl } from './device-tier.js';\n\n\nexport class Viewer {\n constructor(options = {}) {\n const {\n canvas,\n background = '#000000',\n fov = 60,\n near = 0.01,\n far = 2000,\n splatScale = 1.08,\n autoRotate = false,\n flipY = false,\n shDegree = 0,\n aaDilation = 0.3,\n maxPixelRatio = 2,\n adaptiveQuality = true,\n prefetchVariants = true,\n gpuSort = false,\n tier = null, // 'low'|'medium'|'high'|null \u2014 see device-tier.js; surfaced via getStats() for the ?hs mobile overlay\n onProgress,\n onError,\n } = options;\n\n this._canvas = resolveCanvas(canvas);\n this._onProgress = onProgress;\n this._onError = onError;\n this._autoRotate = autoRotate;\n this._splatScale = splatScale;\n this._maxPixelRatio = maxPixelRatio;\n this._flipY = flipY;\n this._shDegree = shDegree;\n this._aaDilation = aaDilation;\n this._tier = tier;\n\n // Adaptive quality: dynamically scales render resolution between\n // _minPixelRatio and _maxPixelRatio based on a running average of frame\n // time \u2014 see _updateAdaptiveQuality. _effectivePixelRatio is the value\n // _updateSize() actually uses.\n this._adaptiveQuality = adaptiveQuality;\n this._prefetchVariantsEnabled = prefetchVariants;\n // GPU compute-shader radix sort (see src/sort-shaders.js), opt-in. Only\n // used when activeCount === null (no per-part cull active this frame) \u2014\n // see _tick. Default false: zero behavior change unless explicitly enabled.\n this._gpuSort = gpuSort;\n this._effectivePixelRatio = maxPixelRatio;\n this._minPixelRatio = 0.5;\n this._frameTimeEMA = null;\n this._lastQualityCheck = 0;\n this._wasSceneReady = false;\n this._qualityWarmupUntil = 0; // see the _sceneReady rising-edge check in _tick\n\n // Screen-space splat radius cap (fraction of viewport.y \u2014 see shaders.js\n // `maxRadius`), eased between _minRadiusCap and _maxRadiusCap by\n // _updateAdaptiveQuality alongside _effectivePixelRatio. Tightening this\n // under load reduces the overdraw cost of large close-up splats.\n // _maxRadiusCap is a safety net against runaway/degenerate splat scales,\n // not a routine constraint \u2014 it must stay well above what normal close-up\n // framing produces (0.25 was too tight: legitimate close-up shots of a\n // surface routinely have visible splats with screen radii > 25% of the\n // viewport height, which made them visibly fade away while zooming in).\n this._effectiveRadiusCap = 1.0;\n this._minRadiusCap = 0.4;\n this._maxRadiusCap = 1.0;\n\n this._renderer = new Renderer(this._canvas, background);\n this._camera = new OrbitCamera({ fov, near, far });\n\n this._gaussians = null; // Float32Array after load\n this._numSplats = 0;\n this._depths = null; // pre-allocated Float32Array\n this._sort = null; // sorter function\n this._rafId = null;\n this._running = false;\n this._resizeObs = null;\n this._resizeTimer = null;\n\n // Multi-part support\n this._partIndex = {}; // id \u2192 transform slot index(es)\n this._fileNames = []; // slot \u2192 splat name, for mask matching\n this._partVariants = {}; // slot \u2192 'spzv' { variants:[{name,palette}], active } | 'file' { names, active, baseUrl, ext, cache }\n this._partTransforms = [IDENTITY_MAT4.slice()]; // Float32Array[] one per slot\n this._partTransFlat = IDENTITY_MAT4.slice(); // flattened, written to GPU each frame\n this._partLocalPose = {}; // id \u2192 local mat4 set this tick (clip/transition/state/anim object), for anchors to compose against\n\n // Mask-aware active-subset rendering (skip fully-hidden files when masks are present)\n this._fileRanges = null; // slot \u2192 [start, end) gaussian index range\n this._fileAABB = null; // slot \u2192 { min: [x,y,z], max: [x,y,z] } local-space bounds\n this._fileMasks = null; // slot \u2192 u32 bitmask of mask volumes affecting this file\n this._activeIdx = null; // Uint32Array(_numSplats) \u2014 packed gaussian indices, active subset\n this._lastActiveCount = 0; // splats actually drawn last frame \u2014 see getStats()\n this._currentSceneName = null; // set by player.js's scroll-playback override \u2014 see getStats()\n\n this._animation = null; // Animation instance, or null\n this._animPaused = false; // true \u2192 animation frozen, camera responds to user input\n this._cameraFree = false; // true \u2192 scroll-scene freecamera act owns the camera\n this._blendBack = null; // { fromEye, fromTarget, t, duration } \u2014 blend from freecamera back to animation\n // { otherEye, otherTarget, otherObjects: {id:{pos,quat}}, bf } | null \u2014 set\n // externally by player.js's scroll-scene blend. Crossfades the live\n // animation pose (this scene's own frame) with `other` at weight bf (bf=1\n // \u2192 fully live, bf=0 \u2192 fully other). See _tick.\n this._sceneBlend = null;\n this._sceneReady = false; // true \u2192 all splat data is on GPU, animation may tick\n this._lastUrl = null; // last URL passed to load() \u2014 used by setShDegree to reload\n this._lastParts = null; // last parts map passed to loadParts() \u2014 used by setShDegree to reload\n this._lastTick = null; // performance.now() of previous tick (for dt)\n this.onFrame = null; // callback(view, proj, width, height) \u2014 called each tick\n this._lastSortView = null; // Float32Array(16) \u2014 view matrix used for the last sort\n this._sortDirty = true; // force sort+render on first frame and after data/uniform changes\n this._lastRenderMs = 0; // performance.now() of last sort+render \u2014 for 60fps cap\n\n // Pan/zoom overlays: applied on top of the animation-driven look-at each\n // tick since the animation overwrites both every frame.\n this._panOffset = [0, 0, 0];\n this._panLimit = null; // max |panOffset| (world units), or null = unlimited\n this._zoomFactor = 1; // radius multiplier\n this._zoomLimit = null; // max deviation from 1 (e.g. 0.5 = 50%..150%), or null = unlimited\n this._camMode = null;\n this._camModeType = null;\n\n // Per-volume softEdge (\"feather\") overrides set by the editor/player config.\n // Keyed by mask volume name; applied on top of the Blender-exported default.\n this._maskFeather = {};\n\n // Clips (product customization) \u2014 independent of the main Animation/\n // timeline entirely, loaded from separate asset files via loadClips().\n // See export_holosplat_asset.py and playClip().\n this._clips = {}; // clipId -> { id, fps, frameCount, holdFrame, objects }\n // Keyed by \"product\" (a clip id's prefix before its last hyphen, e.g.\n // \"headphones\" for \"headphones-blue\"), so each product's customization\n // plays independently of every other product's.\n this._clipPlaybacks = {}; // product -> { clip, dir:'in'|'out', frame, nextClipId }\n this._clipHeld = {}; // product -> currently-held clip id, or undefined\n // Resting/current world matrix + softEdge for every mask volume declared\n // by any loaded clip \u2014 independent of this._animation's own volumes\n // (which only exist for the main scroll timeline). Updated by\n // _applyClipFrame while a clip plays; otherwise holds whatever its\n // clip last left it at (its \"out\" pose by default \u2014 see loadClips()).\n this._clipMaskState = {}; // mask name -> Float32Array(16)\n this._clipMaskSoftEdge = {}; // mask name -> number\n\n // loadParts() always replaces the WHOLE merged scene (see its docstring) \u2014\n // fine for a single call, but loadClips() now makes two per asset (default\n // variant first, then the full set once the background fetch finishes \u2014\n // see loadClips()) and multiple assets can each have their own splatsDir\n // parts. Without accumulating into one registry and always loading the\n // combined map, every one of those calls would wipe out whatever any\n // other asset/phase had already loaded \u2014 which is what was causing the\n // flicker/stutter (parts repeatedly vanishing and reappearing) and the\n // resulting adaptive-quality resolution drop.\n this._clipPartsRegistry = {}; // part id -> url|url[]|{url,variants}, accumulated across loadClips() calls\n this._clipPartsLoadQueue = Promise.resolve(); // serializes the loadParts() calls below\n this._clipPartsFlush = null; // pending debounced flush \u2014 see _mergeClipParts\n\n // Per-URL cache of loadUrl() results. Every queued loadParts() call below\n // re-loads the FULL accumulated registry from scratch (it's the only way\n // loadParts() knows how to load anything \u2014 see its docstring), so without\n // this, each new asset/phase landing would re-fetch every splat file\n // every earlier call had already loaded, snowballing into the \"lots of\n // 404s\" / repeated downloads seen with multiple clip assets.\n this._urlCache = new Map(); // url#shDegree -> Promise<loadUrl() result>\n\n // Axis transitions (button-triggered color switches) \u2014 one shared\n // in/hold/out timeline per axis, not one per value (contrast with\n // clips above). See loadClips()/playVariant().\n this._transitions = {}; // axis -> { fps, frameCount, holdFrame, parts, masks }\n this._transitionPlaybacks = {}; // axis -> { value, prevValue, elapsed }\n this._axisActive = {}; // axis -> currently active value, or undefined\n\n // Asset states (\"state: <axis>=<value>\" markers) \u2014 one continuous\n // timeline per axis spanning every named value, as opposed to the\n // two-value crossfade transitions above. Switching values seeks along\n // the shared timeline, forward or backward, through whatever frames lie\n // between. See loadClips()/playState()/_syncAssetStates().\n this._states = {}; // axis -> { fps, frameCount, markers, default, parts, masks }\n this._stateFrame = {}; // axis -> current resting/in-flight frame\n this._stateActive = {}; // axis -> value currently at rest, or undefined\n this._stateTarget = {}; // axis -> value currently targeted (resting or in-flight)\n this._statePlaybacks = {}; // axis -> { dir, frame, toFrame, value }\n }\n\n /** Override a mask volume's soft-edge falloff distance (scene units). */\n setMaskFeather(name, value) {\n this._maskFeather[name] = value;\n }\n\n // \u2500\u2500 Clips (product customization) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Merges `partsMap` into every part contributed so far by loadClips() (see\n * _clipPartsRegistry above), then loads the combined map \u2014 never the\n * partial one alone, so this call can't wipe out another asset's (or this\n * same asset's earlier-phase) parts. The actual loadParts() call is\n * debounced a few ms: every loadParts() call resets each part's transform\n * to identity for a frame (until the next animation tick re-applies it),\n * so several calls landing close together (e.g. two assets' default-phase\n * loads at boot, or a background variant fetch finishing mid-scroll) was\n * what caused the visible flicker \u2014 coalescing them into one call using\n * whatever's accumulated by the time it fires fixes that. Queued onto\n * _clipPartsLoadQueue so non-coalesced calls still run strictly in order. */\n _mergeClipParts(partsMap) {\n Object.assign(this._clipPartsRegistry, partsMap);\n if (!this._clipPartsFlush) {\n this._clipPartsFlush = new Promise((resolve, reject) => {\n setTimeout(() => {\n this._clipPartsFlush = null;\n const snapshot = { ...this._clipPartsRegistry };\n // Run after whatever's ahead in the queue regardless of whether\n // that succeeded or failed \u2014 a single bad file 404ing must not\n // permanently stall every later merge for the rest of the session.\n const run = this._clipPartsLoadQueue.then(() => this.loadParts(snapshot), () => this.loadParts(snapshot));\n // The shared queue itself must never reject, or the *next* flush's\n // `.then` above would skip straight past its loadParts() call too.\n this._clipPartsLoadQueue = run.catch(() => {});\n run.then(resolve, reject);\n }, 30);\n });\n }\n return this._clipPartsFlush;\n }\n\n /**\n * Load an asset's clip file (see export_holosplat_asset.py) and merge its\n * clips into the live registry. Independent of the main Animation/timeline\n * entirely \u2014 can be called before or after the main animation loads, and\n * multiple asset files can be loaded (clip ids are expected to be unique\n * across all of them). Returns the list of clip ids this call added, so a\n * caller (e.g. the editor's asset list) can show what a given file\n * contains without needing the registry to track per-file origin itself.\n *\n * @param {string} url\n * @param {{splatsDir?: string, defaults?: Object<string,string>, lod?: number}} [opts]\n * If splatsDir is given, this also resolves and loads the asset's own\n * geometry (the JSON's \"parts\" field) via loadParts \u2014 defaults picks\n * which variant value to use per axis (e.g. {color: \"blue\"}); a part\n * with no variants of its own just loads its bare splat name. `lod`\n * (see device-tier.js) probes for a `<name>.lods/<name>.lod{N}.spz`\n * sibling of each part/variant and uses it instead when found.\n * NOTE: loadParts() replaces the whole merged scene rather than\n * appending to it, so this currently assumes the main scene has no\n * parts of its own loaded separately \u2014 combining both isn't supported\n * yet.\n */\n async loadClips(url, opts = {}) {\n const res = await fetch(url);\n if (!res.ok) throw new Error(`HoloSplat: failed to load clips \"${url}\" (HTTP ${res.status})`);\n const data = await res.json();\n const ids = [];\n for (const c of data.clips ?? []) {\n const masks = (c.masks ?? []).map(m => ({\n name: m.name, softEdge: m.softEdge ?? 0.05, matrices: new Float32Array(m.matrices),\n }));\n this._clips[c.id] = {\n id: c.id,\n fps: c.fps ?? data.fps ?? 24,\n frameCount: c.frameCount,\n holdFrame: c.holdFrame,\n objects: (c.objects ?? []).map(o => ({ id: o.id, frames: new Float32Array(o.frames) })),\n masks,\n };\n ids.push(c.id);\n // Rest each of this clip's masks at their \"out\" (hidden) pose by\n // default \u2014 resolving the asset's default variant below (or a later\n // playClip()) promotes whichever one should actually be visible.\n for (const m of masks) {\n if (this._clipMaskState[m.name]) continue;\n const lastFrame = m.matrices.length / 16 - 1;\n this._clipMaskState[m.name] = m.matrices.slice(lastFrame * 16, lastFrame * 16 + 16);\n this._clipMaskSoftEdge[m.name] = m.softEdge;\n }\n }\n\n // Axis transitions \u2014 one shared in/hold/out timeline per axis (see\n // playVariant()), as opposed to clips above (one timeline per clip id).\n for (const [axis, t] of Object.entries(data.transitions ?? {})) {\n const masks = (t.masks ?? []).map(m => ({\n name: m.name, value: m.value, softEdge: m.softEdge ?? 0.05, matrices: new Float32Array(m.matrices),\n }));\n this._transitions[axis] = {\n fps: t.fps ?? data.fps ?? 24,\n frameCount: t.frameCount,\n holdFrame: t.holdFrame,\n parts: (t.parts ?? []).map(o => ({ id: o.id, value: o.value, frames: new Float32Array(o.frames) })),\n masks,\n };\n // Rest every value's masks at their \"out\" (hidden) pose by default \u2014\n // resolving the asset's default variant below (or a later\n // playVariant()) promotes whichever value should actually be visible.\n for (const m of masks) {\n if (this._clipMaskState[m.name]) continue;\n const lastFrame = m.matrices.length / 16 - 1;\n this._clipMaskState[m.name] = m.matrices.slice(lastFrame * 16, lastFrame * 16 + 16);\n this._clipMaskSoftEdge[m.name] = m.softEdge;\n }\n }\n\n // Asset states \u2014 one continuous timeline per axis (see playState()),\n // as opposed to transitions above (two values crossfading on a shared\n // in/hold/out range).\n for (const [axis, s] of Object.entries(data.states ?? {})) {\n const masks = (s.masks ?? []).map(m => ({\n name: m.name, softEdge: m.softEdge ?? 0.05, matrices: new Float32Array(m.matrices),\n }));\n this._states[axis] = {\n fps: s.fps ?? data.fps ?? 24,\n frameCount: s.frameCount,\n markers: s.markers ?? {},\n default: s.default,\n parts: (s.parts ?? []).map(o => ({ id: o.id, frames: new Float32Array(o.frames) })),\n masks,\n };\n }\n\n if (opts.splatsDir && data.parts && Object.keys(data.parts).length) {\n const dir = opts.splatsDir.replace(/\\/?$/, '/');\n\n // Phase 1 \u2014 load only each part's default variant (per opts.defaults),\n // so the scene becomes visible without paying for every color up front.\n // LOD tiers are always .spz (see device-tier.js), so the explicit\n // .spz here is what resolveLodUrl needs to match against \u2014 it falls\n // back through .ply/.splat downstream (loadUrl) if no .spz exists.\n let defaultPartsMap = {};\n for (const [id, part] of Object.entries(data.parts)) {\n defaultPartsMap[id] = `${dir}${part.splatName}${defaultVariantSuffix(part, opts.defaults)}.spz`;\n }\n if (opts.lod) defaultPartsMap = await resolvePartsLod(defaultPartsMap, opts.lod);\n await this._mergeClipParts(defaultPartsMap);\n\n // Phase 2 \u2014 fetch every other variant in the background, then swap in\n // the full multi-slot scene so axis-transition masks (see playVariant)\n // have every color's geometry to fade between. Not awaited: this must\n // not block the caller (or the boot sequence) on color variants nobody\n // may ever select. _clipMaskState (set below/by playVariant) survives\n // the swap untouched, so whichever color is already showing stays put.\n // Delayed a few seconds rather than firing immediately: starting a\n // dozen more downloads + decompressions the instant the default scene\n // becomes visible competes with it for bandwidth and main-thread time\n // right when the page most needs to settle, which read as startup\n // stutter even though the default variant alone had already loaded.\n if (Object.values(data.parts).some(p => p.variants.length > 1)) {\n setTimeout(() => { (async () => {\n // Per-file mask culling is disabled (see _computeActiveRanges) \u2014\n // every variant a part loads gets sorted and drawn every frame\n // forever, whether or not it's the currently visible color. The\n // active variant already loaded at full quality in Phase 1, so\n // the other (mostly-hidden, only ever partially revealed mid\n // color-swap transition) variants are capped at a low LOD tier\n // regardless of device tier \u2014 pure dead weight otherwise.\n const fullPartsMap = {};\n for (const [id, part] of Object.entries(data.parts)) {\n if (!part.variants.length) { fullPartsMap[id] = `${dir}${part.splatName}.spz`; continue; }\n const activeVariant = defaultVariantSuffix(part, opts.defaults).slice(1);\n fullPartsMap[id] = await Promise.all(part.variants.map(v => {\n const lod = v === activeVariant ? (opts.lod || 0) : Math.max(opts.lod || 0, BACKGROUND_VARIANT_LOD);\n return resolveLodUrl(`${dir}${part.splatName}.${v}.spz`, lod);\n }));\n }\n try { await this._mergeClipParts(fullPartsMap); }\n catch (e) { console.error(`[HoloSplat] background variant load failed for \"${url}\":`, e); }\n })(); }, CLIP_VARIANT_PREFETCH_DELAY_MS);\n }\n }\n\n // Resolve the initial active value per axis from opts.defaults, so the\n // default-selected variant's masks/pose start fully revealed instead of\n // at their resting \"out\" state \u2014 applied instantly (no animation).\n for (const [axis, value] of Object.entries(opts.defaults ?? {})) {\n if (this._transitions[axis]) this._setVariantInstant(axis, value);\n if (this._states[axis]) this._setStateInstant(axis, value);\n }\n // Any state axis with no explicit default in opts.defaults still needs\n // resolving to its own exported default value, so its parts/masks start\n // at a defined pose rather than wherever loadParts() last left them.\n for (const [axis, s] of Object.entries(this._states)) {\n if (this._stateActive[axis] === undefined && s.default != null) this._setStateInstant(axis, s.default);\n }\n\n return ids;\n }\n\n /** Remove specific clips from the registry by id (e.g. when an asset's row is removed in the editor). */\n unloadClips(ids) {\n for (const id of ids) delete this._clips[id];\n }\n\n /**\n * Trigger a clip by id (\"[productName]-[variant]\" \u2014 same string as the\n * triggering button's element id). Radio-group behavior is implicit: the\n * \"product\" (everything before the last hyphen) groups variants, so\n * selecting a new variant for a product currently holding a different one\n * first plays that old variant's \"out\" segment, then this one's \"in\" \u2014\n * matching the in/hold/out marker convention (see export_holosplat_asset.py).\n */\n playClip(clipId) {\n const clip = this._clips[clipId];\n if (!clip) {\n console.warn(`[HoloSplat] playClip: unknown clip \"${clipId}\"`);\n return;\n }\n const cut = clipId.lastIndexOf('-');\n if (cut <= 0) {\n console.warn(`[HoloSplat] playClip: \"${clipId}\" doesn't match \"productName-variant\"`);\n return;\n }\n const product = clipId.slice(0, cut);\n const held = this._clipHeld[product];\n if (held === clipId) return; // already showing this variant\n\n const heldClip = held ? this._clips[held] : null;\n this._clipPlaybacks[product] = heldClip\n ? { clip: heldClip, dir: 'out', frame: heldClip.holdFrame, nextClipId: clipId }\n : { clip, dir: 'in', frame: 0, nextClipId: null };\n this._clipHeld[product] = clipId;\n }\n\n /** Advance every in-progress clip playback by dt seconds. */\n _tickClips(dt) {\n for (const product in this._clipPlaybacks) {\n const pb = this._clipPlaybacks[product];\n const { clip, dir } = pb;\n const endFrame = dir === 'in' ? clip.holdFrame : clip.frameCount - 1;\n pb.frame += dt * clip.fps;\n const done = pb.frame >= endFrame;\n if (done) pb.frame = endFrame;\n this._applyClipFrame(clip, pb.frame);\n if (!done) continue;\n if (dir === 'out' && pb.nextClipId) {\n this._clipPlaybacks[product] = { clip: this._clips[pb.nextClipId], dir: 'in', frame: 0, nextClipId: null };\n } else {\n delete this._clipPlaybacks[product];\n }\n }\n }\n\n /** Write a clip's interpolated object transforms + mask volumes at `frame` into the live state. */\n _applyClipFrame(clip, frame) {\n let dirty = false;\n for (const { id, pos, quat } of getClipObjectFrames(clip, frame)) {\n const slots = this._partIndex[id];\n if (slots && slots.length) {\n const m = quatPosToMat4(pos, quat);\n this._partLocalPose[id] = m;\n for (const slot of slots) this._partTransFlat.set(m, slot * 16);\n dirty = true;\n }\n }\n for (const { name, softEdge, matrix } of getClipMaskFrames(clip, frame)) {\n this._clipMaskState[name] = matrix;\n this._clipMaskSoftEdge[name] = softEdge;\n this._sortDirty = true; // mask change affects active-subset culling\n }\n if (dirty) {\n this._renderer.updateTransforms(this._partTransFlat);\n this._sortDirty = true;\n }\n }\n\n /**\n * Switch an axis to `value` (e.g. playVariant('color', 'blue')) \u2014 every\n * part/mask tagged \"<axis>=<value>\" plays its .in\u2192.hold segment while\n * whatever value was previously active for this axis plays .hold\u2192.out,\n * simultaneously (see export_holosplat_asset.py's axis-transition export).\n * No-op if `value` is already active for `axis`.\n */\n playVariant(axis, value) {\n const t = this._transitions[axis];\n if (!t) {\n console.warn(`[HoloSplat] playVariant: unknown axis \"${axis}\"`);\n return;\n }\n const prevValue = this._axisActive[axis];\n if (prevValue === value) return;\n this._axisActive[axis] = value;\n this._transitionPlaybacks[axis] = { value, prevValue, elapsed: 0 };\n }\n\n /** Apply a value instantly at its .hold frame, with no previous value to\n * play out \u2014 used to silently resolve an asset's default variant on load. */\n _setVariantInstant(axis, value) {\n const t = this._transitions[axis];\n if (!t) return;\n this._axisActive[axis] = value;\n this._applyTransitionValue(t, value, t.holdFrame);\n }\n\n /** Advance every in-progress axis transition by dt seconds. */\n _tickTransitions(dt) {\n for (const axis in this._transitionPlaybacks) {\n const pb = this._transitionPlaybacks[axis];\n const t = this._transitions[axis];\n pb.elapsed += dt * t.fps;\n const enterFrame = Math.min(pb.elapsed, t.holdFrame);\n const exitFrame = Math.min(t.holdFrame + pb.elapsed, t.frameCount - 1);\n this._applyTransitionValue(t, pb.value, enterFrame);\n if (pb.prevValue) this._applyTransitionValue(t, pb.prevValue, exitFrame);\n const done = enterFrame >= t.holdFrame && exitFrame >= t.frameCount - 1;\n if (done) delete this._transitionPlaybacks[axis];\n }\n }\n\n /** Write one value's interpolated part transforms + mask volumes at `frame` into the live state. */\n _applyTransitionValue(t, value, frame) {\n let dirty = false;\n for (const obj of t.parts) {\n if (obj.value !== value) continue;\n const slots = this._partIndex[obj.id];\n if (slots && slots.length) {\n const { pos, quat } = interpPosQuat(obj.frames, t.frameCount, frame);\n const m = quatPosToMat4(pos, quat);\n this._partLocalPose[obj.id] = m;\n for (const slot of slots) this._partTransFlat.set(m, slot * 16);\n dirty = true;\n }\n }\n for (const m of t.masks) {\n if (m.value !== value) continue;\n this._clipMaskState[m.name] = interpMat4Frames(m.matrices, t.frameCount, frame);\n this._clipMaskSoftEdge[m.name] = m.softEdge;\n this._sortDirty = true;\n }\n if (dirty) {\n this._renderer.updateTransforms(this._partTransFlat);\n this._sortDirty = true;\n }\n }\n\n /**\n * Switch a state axis to `value` (e.g. playState('fold', 'folded')) \u2014\n * seeks the axis's own timeline from wherever it currently sits to the\n * named marker's frame, forward or backward as needed (see\n * export_holosplat_asset.py's \"state: <axis>=<value>\" markers). No-op if\n * `value` is already the resting/targeted value for `axis`.\n */\n playState(axis, value) {\n const t = this._states[axis];\n if (!t) {\n console.warn(`[HoloSplat] playState: unknown state axis \"${axis}\"`);\n return;\n }\n if (!(value in t.markers)) {\n console.warn(`[HoloSplat] playState: axis \"${axis}\" has no value \"${value}\"`);\n return;\n }\n if (this._stateTarget[axis] === value) return;\n const toFrame = t.markers[value];\n const fromFrame = this._stateFrame[axis] ?? toFrame;\n this._stateTarget[axis] = value;\n if (fromFrame === toFrame) {\n this._stateActive[axis] = value;\n delete this._statePlaybacks[axis];\n return;\n }\n this._statePlaybacks[axis] = { dir: toFrame > fromFrame ? 1 : -1, frame: fromFrame, toFrame, value };\n }\n\n /** Apply a value instantly at its marker frame, with no seek \u2014 used to\n * silently resolve an asset's default state on load. */\n _setStateInstant(axis, value) {\n const t = this._states[axis];\n if (!t || !(value in t.markers)) return;\n const frame = t.markers[value];\n this._stateFrame[axis] = frame;\n this._stateActive[axis] = value;\n this._stateTarget[axis] = value;\n delete this._statePlaybacks[axis];\n this._applyStateFrame(t, frame);\n }\n\n /** Advance every in-progress state seek by dt seconds. */\n _tickStates(dt) {\n for (const axis in this._statePlaybacks) {\n const pb = this._statePlaybacks[axis];\n const t = this._states[axis];\n pb.frame += dt * t.fps * pb.dir;\n const done = pb.dir > 0 ? pb.frame >= pb.toFrame : pb.frame <= pb.toFrame;\n if (done) pb.frame = pb.toFrame;\n this._applyStateFrame(t, pb.frame);\n this._stateFrame[axis] = pb.frame;\n if (done) {\n this._stateActive[axis] = pb.value;\n delete this._statePlaybacks[axis];\n }\n }\n }\n\n /** Write a state axis's interpolated part transforms + mask volumes at `frame` into the live state. */\n _applyStateFrame(t, frame) {\n let dirty = false;\n for (const obj of t.parts) {\n const slots = this._partIndex[obj.id];\n if (slots && slots.length) {\n const { pos, quat } = interpPosQuat(obj.frames, t.frameCount, frame);\n const m = quatPosToMat4(pos, quat);\n this._partLocalPose[obj.id] = m;\n for (const slot of slots) this._partTransFlat.set(m, slot * 16);\n dirty = true;\n }\n }\n for (const m of t.masks) {\n this._clipMaskState[m.name] = interpMat4Frames(m.matrices, t.frameCount, frame);\n this._clipMaskSoftEdge[m.name] = m.softEdge;\n this._sortDirty = true;\n }\n if (dirty) {\n this._renderer.updateTransforms(this._partTransFlat);\n this._sortDirty = true;\n }\n }\n\n /**\n * Resolve every state axis's target value from the main timeline's\n * \"state: <asset>.<axis>=<value>\" calls \u2014 same \"most recently passed\"\n * rule as _syncCameraMode, so seeking/scrubbing lands on the right value\n * without needing edge-triggered crossing detection. Axes with no call\n * yet passed fall back to their own exported default.\n */\n _syncAssetStates() {\n const calls = this._animation.stateCalls;\n if (!calls || !calls.length) return;\n const frame = this._animation.frame;\n const byAxis = {};\n for (const c of calls) {\n if (!this._states[c.axis]) continue;\n if (c.frame <= frame && (!byAxis[c.axis] || c.frame > byAxis[c.axis].frame)) byAxis[c.axis] = c;\n }\n for (const axis in this._states) {\n const call = byAxis[axis];\n const target = call ? call.value : this._states[axis].default;\n if (target != null) this.playState(axis, target);\n }\n }\n\n // \u2500\u2500 Lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async init() {\n await this._renderer.init();\n this._renderer.setSplatScale(this._splatScale);\n this._renderer.setAaDilation(this._aaDilation);\n this._camera.attach(this._canvas);\n this._observeResize();\n this._updateSize();\n }\n\n async load(url) {\n this._lastUrl = url;\n this._lastParts = null;\n this._sceneReady = false;\n\n const ext = url.split('?')[0].split('.').pop().toLowerCase();\n if (ext === 'ply') {\n try {\n await this._loadPlyStreamSingle(url);\n return;\n } catch (e) {\n if (!/^HTTP 4/.test(e.message)) throw e;\n // 404 \u2192 fall through to format-agnostic loadUrl\n }\n }\n\n const { data, count, variants, shData, numSHBases } = await loadUrl(url, p => this._onProgress?.(p), this._shDegree);\n if (this._flipY) { flipYInPlace(data, count); flipYInPlaceSH(shData, numSHBases, count); }\n for (let i = 0; i < count; i++) data[i * 16 + 3] = 0;\n // Cached so setFlipY() (live-toggled, e.g. the examples/viewer.html\n // checkbox) can correctly re-flip SH data too, not just position/quat.\n this._lastShData = shData;\n this._lastNumSHBases = numSHBases;\n this._gaussians = data;\n this._numSplats = count;\n this._depths = new Float32Array(count);\n this._sort = createSorter(count);\n this._partIndex = {};\n this._partLocalPose = {};\n this._partVariants = variants ? { 0: { kind: 'spzv', variants, active: variants[0]?.name } } : {};\n this._fileNames = [basenameNoExt(url)];\n this._partTransforms = [IDENTITY_MAT4.slice()];\n this._partTransFlat = IDENTITY_MAT4.slice();\n // Single-file scenes never benefit from active-subset masking (there's\n // nothing else to render if this file is hidden) \u2014 leave _activeIdx\n // null so the render loop always takes the fast (full-scene) path.\n this._fileRanges = [[0, count]];\n this._fileAABB = [null];\n this._activeIdx = null;\n this._renderer.uploadGaussians(data, count);\n this._renderer.uploadTransforms(this._partTransforms);\n this._renderer.uploadSH(shData || null, numSHBases || 0);\n this._renderer.setShDegree(numSHBases > 0 ? this._shDegree : 0);\n this._sortDirty = true;\n this._camera.fitScene(data, count);\n if (this._animation) {\n const { eye, target } = this._animation.getCameraFrame();\n this._camera.setFromLookAt(eye, target);\n }\n this._buildPartVolumeMask();\n this._sceneReady = true;\n }\n\n async _loadPlyStreamSingle(url) {\n const { numVertices, numSHBases, consume } = await openPlyStream(url, p => this._onProgress?.(p * 0.02), this._shDegree);\n\n const zeros = new Float32Array(numVertices * 16);\n this._gaussians = zeros;\n this._numSplats = numVertices;\n this._depths = new Float32Array(numVertices);\n this._sort = createSorter(numVertices);\n this._partIndex = {};\n this._partLocalPose = {};\n this._partVariants = {};\n this._fileNames = [basenameNoExt(url)];\n this._partTransforms = [IDENTITY_MAT4.slice()];\n this._partTransFlat = IDENTITY_MAT4.slice();\n this._fileRanges = [[0, numVertices]];\n this._fileAABB = [null];\n this._activeIdx = null;\n this._renderer.uploadGaussians(zeros, numVertices);\n this._renderer.uploadTransforms(this._partTransforms);\n this._renderer.allocateSH(numVertices, numSHBases);\n this._renderer.setShDegree(numSHBases > 0 ? this._shDegree : 0);\n this._buildPartVolumeMask();\n if (this._animation) {\n const { eye, target } = this._animation.getCameraFrame();\n this._camera.setFromLookAt(eye, target);\n }\n\n // Accumulated alongside this._gaussians so setFlipY() (live-toggled \u2014\n // see examples/viewer.html's checkbox) can re-flip SH data too, the same\n // way it already re-flips this._gaussians.\n this._lastShData = numSHBases > 0 ? new Float32Array(numVertices * numSHBases * 3) : null;\n this._lastNumSHBases = numSHBases;\n\n let vOff = 0;\n await consume((chunk, nVerts, shChunk) => {\n for (let j = 0; j < nVerts; j++) chunk[j * 16 + 3] = 0;\n if (this._flipY) { flipYInPlace(chunk, nVerts); flipYInPlaceSH(shChunk, numSHBases, nVerts); }\n this._renderer.patchGaussians(chunk, vOff);\n this._renderer.patchSH(shChunk, vOff);\n this._gaussians.set(chunk, vOff * 16);\n if (this._lastShData && shChunk) this._lastShData.set(shChunk, vOff * numSHBases * 3);\n vOff += nVerts;\n }, p => this._onProgress?.(p));\n\n if (!this._animation) this._camera.fitScene(this._gaussians, this._numSplats);\n this._sceneReady = true;\n }\n\n /** loadUrl(), but cached per URL+shDegree on this viewer instance \u2014 see\n * _urlCache above. Callers (loadParts()'s task loop) mutate the returned\n * data in place (flipY, slot tagging), so a cache hit must hand back a\n * fresh copy, never the same typed-array instance twice. */\n async _loadUrlCached(url, onProgress, shDegree) {\n const key = `${url}#${shDegree}`;\n let entry = this._urlCache.get(key);\n if (!entry) {\n entry = loadUrl(url, onProgress, shDegree);\n this._urlCache.set(key, entry);\n entry.catch(() => this._urlCache.delete(key)); // don't cache failures\n } else {\n onProgress?.(1);\n }\n const result = await entry;\n return {\n ...result,\n data: result.data.slice(),\n shData: result.shData ? result.shData.slice() : result.shData,\n };\n }\n\n /**\n * Load multiple splat parts and merge them into a single GPU scene.\n *\n * @param {Object<string, string|string[]|{url: string, variants: string[]}>} partsMap\n * Map of object id \u2192 splat file URL, an array of URLs, or\n * `{ url, variants }`. All files for a part share that part's animated\n * transform.\n * - A plain URL array loads every file as its own slot, rendered\n * together \u2014 useful for unmasked color variants until per-color masks\n * select between them.\n * - `{ url, variants }` loads only `url` (one of `<base>.<v>.<ext>` for\n * `v` in `variants`); the others are fetched lazily by setVariant() \u2014\n * use this for independently-trained per-color variants whose geometry\n * differs, where loading every variant up front would be wasteful.\n *\n * PLY parts are streamed: the header is parsed first (getting the vertex count),\n * the GPU buffer is allocated with zeros immediately so rendering starts as soon\n * as possible, then vertex data is patched in as it downloads.\n * Non-PLY parts (spz, splat) are loaded fully before the GPU buffer is allocated.\n *\n * _sceneReady becomes true only after all streams are exhausted, at which\n * point the animation begins ticking from frame 0.\n */\n async loadParts(partsMap) {\n this._lastParts = partsMap;\n this._lastUrl = null;\n const partIds = Object.keys(partsMap);\n if (partIds.length === 0) throw new Error('HoloSplat: loadParts called with empty map');\n\n this._sceneReady = false;\n\n // Flatten to one task per splat file, each getting its own transform\n // slot. A part may reference multiple files (e.g. unmasked color\n // variants, all rendered together until per-color masks are set up) \u2014\n // slots belonging to the same part are kept in sync every animation\n // tick, so they animate together.\n const tasks = [];\n partIds.forEach((id) => {\n const entry = partsMap[id];\n // { url, variants } \u2014 single file loaded now, with sibling\n // \"<base>.<variant>.<ext>\" files fetched lazily by setVariant().\n if (entry && typeof entry === 'object' && !Array.isArray(entry)) {\n tasks.push({ id, slot: tasks.length, url: entry.url, variantNames: entry.variants });\n return;\n }\n const urls = Array.isArray(entry) ? entry : [entry];\n for (const url of urls) tasks.push({ id, slot: tasks.length, url });\n });\n\n const progresses = new Array(tasks.length).fill(0);\n const reportProg = () => {\n if (this._onProgress)\n this._onProgress(progresses.reduce((a, b) => a + b, 0) / tasks.length);\n };\n\n // Phase 1 \u2014 open PLY streams (header only) and fully load non-PLY parts.\n // All happen in parallel; every task resolves with its vertex count.\n const parts = await Promise.all(tasks.map(async ({ id, slot, url }, i) => {\n const ext = url.split('?')[0].split('.').pop().toLowerCase();\n if (ext === 'ply') {\n try {\n const { numVertices, numSHBases, consume } = await openPlyStream(url, p => {\n progresses[i] = p * 0.03; reportProg();\n }, this._shDegree);\n return { id, slot, kind: 'stream', numVertices, numSHBases, consume };\n } catch (e) {\n if (!/^HTTP 4/.test(e.message)) throw e;\n // 404 \u2014 let loadUrl try other extensions\n }\n }\n const { data, count, variants, shData, numSHBases } = await this._loadUrlCached(url, p => {\n progresses[i] = p * 0.9; reportProg();\n }, this._shDegree);\n progresses[i] = 0.9;\n return { id, slot, kind: 'loaded', data, count, variants, shData, numSHBases };\n }));\n\n // Compute total vertex count and per-task offsets in the merged buffer.\n const counts = parts.map(p => p.kind === 'stream' ? p.numVertices : p.count);\n const total = counts.reduce((a, b) => a + b, 0);\n const vtxOffsets = [];\n { let off = 0; for (const c of counts) { vtxOffsets.push(off); off += c; } }\n\n // Build the merged CPU buffer.\n // Non-PLY parts: fill with real data. PLY stream parts: stay as zeros (transparent).\n const merged = new Float32Array(total * 16);\n const fileAABB = new Array(tasks.length).fill(null);\n parts.forEach((part, i) => {\n if (part.kind !== 'loaded') return;\n const { data, count, slot, shData, numSHBases } = part;\n for (let j = 0; j < count; j++) data[j * 16 + 3] = slot;\n if (this._flipY) { flipYInPlace(data, count); flipYInPlaceSH(shData, numSHBases, count); }\n merged.set(data, vtxOffsets[i] * 16);\n fileAABB[slot] = computeAABB(data, count);\n progresses[i] = 1;\n });\n\n // Initialise viewer state and upload the buffer (zeros for stream parts).\n // Each loaded file gets its own transform slot; _partIndex maps a part id\n // to every slot that should follow that part's animated transform (more\n // than one when a part has multiple color variants loaded). _fileNames\n // gives each slot's splat name, used by _buildPartVolumeMask to match\n // mask-volume naming conventions.\n this._partIndex = {};\n partIds.forEach(id => { this._partIndex[id] = []; });\n tasks.forEach(({ id, slot }) => { this._partIndex[id].push(slot); });\n this._fileNames = tasks.map(t => basenameNoExt(t.url));\n\n // Two ways a slot gets runtime-swappable variants \u2014 see setVariant():\n // - 'spzv': a packed file carries every variant's color/alpha palette\n // alongside one shared geometry \u2014 swap is instant, no fetch.\n // - 'file': the partsMap entry was { url, variants }, where `url` is\n // \"<base>.<active>.<ext>\" and the other names are sibling files with\n // their own geometry, fetched (and cached) on first use.\n this._partVariants = {};\n parts.forEach(part => {\n if (part.variants) {\n this._partVariants[part.slot] = { kind: 'spzv', variants: part.variants, active: part.variants[0]?.name };\n }\n });\n tasks.forEach(({ slot, url, variantNames }) => {\n if (!variantNames?.length || this._partVariants[slot]) return;\n const clean = url.split('?')[0];\n const m = clean.match(/^(.*)\\.([^./]+)\\.(spz|ply|splat)$/i);\n if (!m || !variantNames.includes(m[2])) return;\n this._partVariants[slot] = {\n kind: 'file', names: variantNames, active: m[2],\n baseUrl: m[1], ext: m[3], cache: {},\n };\n });\n\n this._partTransforms = tasks.map(() => IDENTITY_MAT4.slice());\n this._partTransFlat = new Float32Array(tasks.length * 16);\n for (let i = 0; i < tasks.length; i++) this._partTransFlat.set(IDENTITY_MAT4, i * 16);\n\n // Per-file gaussian ranges and bounding boxes, for the active-subset\n // AABB-vs-mask-volume test in _computeActiveRanges (skips fully-masked\n // files entirely so they cost nothing in sort/draw).\n this._fileRanges = vtxOffsets.map((off, i) => [off, off + counts[i]]);\n this._fileAABB = fileAABB;\n this._activeIdx = new Uint32Array(total);\n\n this._gaussians = merged;\n this._numSplats = total;\n this._depths = new Float32Array(total);\n this._sort = createSorter(total);\n\n this._renderer.uploadGaussians(merged, total);\n this._renderer.uploadTransforms(this._partTransforms);\n\n // SH: find the max numSHBases across all parts; pre-allocate the GPU SH\n // buffer (zeroed), then fill in the non-streaming parts' data immediately.\n const maxSHBases = parts.reduce((m, p) => Math.max(m, p.numSHBases || 0), 0);\n this._renderer.allocateSH(total, maxSHBases);\n this._renderer.setShDegree(maxSHBases > 0 ? this._shDegree : 0);\n parts.forEach((part, i) => {\n if (part.kind === 'loaded' && part.shData && part.numSHBases === maxSHBases) {\n this._renderer.patchSH(part.shData, vtxOffsets[i]);\n }\n });\n\n this._buildPartVolumeMask();\n this._camera.fitScene(merged, total);\n if (this._animation) {\n const { eye, target } = this._animation.getCameraFrame();\n this._camera.setFromLookAt(eye, target);\n }\n reportProg();\n\n // Phase 2 \u2014 stream PLY vertex data, patching the GPU buffer as chunks arrive.\n const streamParts = parts\n .map((part, i) => ({ part, i }))\n .filter(({ part }) => part.kind === 'stream');\n if (streamParts.length === 0) {\n this._sceneReady = true;\n this._prefetchVariants();\n return;\n }\n\n await Promise.all(streamParts.map(async ({ part, i }) => {\n const { slot, consume } = part;\n let vOff = vtxOffsets[i];\n\n await consume((chunk, nVerts, shChunk) => {\n for (let j = 0; j < nVerts; j++) chunk[j * 16 + 3] = slot;\n if (this._flipY) { flipYInPlace(chunk, nVerts); flipYInPlaceSH(shChunk, part.numSHBases, nVerts); }\n this._renderer.patchGaussians(chunk, vOff);\n if (shChunk && part.numSHBases === maxSHBases) this._renderer.patchSH(shChunk, vOff);\n this._gaussians.set(chunk, vOff * 16);\n this._fileAABB[slot] = extendAABB(this._fileAABB[slot], chunk, nVerts);\n vOff += nVerts;\n }, p => {\n progresses[i] = 0.03 + 0.97 * p;\n reportProg();\n });\n\n progresses[i] = 1;\n reportProg();\n }));\n\n if (!this._animation) this._camera.fitScene(this._gaussians, this._numSplats);\n this._sceneReady = true;\n this._prefetchVariants();\n }\n\n start() {\n if (this._running) return;\n this._running = true;\n this._tick();\n }\n\n stop() {\n this._running = false;\n if (this._rafId) cancelAnimationFrame(this._rafId);\n this._rafId = null;\n }\n\n destroy() {\n this.stop();\n this._camera.detach();\n this._renderer.destroy();\n this._resizeObs?.disconnect();\n clearTimeout(this._resizeTimer);\n }\n\n // \u2500\u2500 Configuration setters (can be called at runtime) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n setBackground(bg) { this._renderer.setBackground(bg); }\n\n setSplatScale(s) {\n this._splatScale = s;\n this._renderer.setSplatScale(s);\n }\n\n setGamma(g) {\n this._renderer.setGamma(g);\n }\n\n async setShDegree(n) {\n if (n === this._shDegree) return;\n this._shDegree = n;\n if (this._lastParts) await this.loadParts(this._lastParts);\n else if (this._lastUrl) await this.load(this._lastUrl);\n }\n\n setAaDilation(v) {\n this._aaDilation = v;\n this._renderer.setAaDilation(v);\n this._sortDirty = true;\n }\n\n /** TEMP debug instrumentation \u2014 see src/shaders.js binding(7). */\n setDebugIndex(i) { this._renderer.setDebugIndex(i); }\n async readDebug() { return this._renderer.readDebug(); }\n\n setAutoRotate(v) { this._autoRotate = v; }\n\n setFlipY(enabled) {\n if (!!enabled === this._flipY) return;\n this._flipY = !!enabled;\n if (this._gaussians) {\n flipYInPlace(this._gaussians, this._numSplats);\n this._renderer.uploadGaussians(this._gaussians, this._numSplats);\n this._camera.fitScene(this._gaussians, this._numSplats);\n this._sortDirty = true;\n // Only covers the single-file load path (loadParts doesn't cache SH\n // data per-part) \u2014 see _lastShData.\n if (this._lastShData) {\n flipYInPlaceSH(this._lastShData, this._lastNumSHBases, this._numSplats);\n this._renderer.uploadSH(this._lastShData, this._lastNumSHBases);\n }\n }\n }\n\n /**\n * Snapshot of live performance/quality numbers, for the `?hs` mobile stats\n * overlay (see holosplat/stats.js) and any other diagnostics UI.\n * @returns {{fps: number, numSplats: number, activeSplats: number,\n * shDegree: number, pixelRatio: number, sceneName: string|null,\n * gpuSort: boolean, gpuSortFailed: boolean, tier: string|null}}\n */\n getStats() {\n return {\n fps: this._frameTimeEMA ? 1 / this._frameTimeEMA : 0,\n numSplats: this._numSplats,\n activeSplats: this._lastActiveCount || this._numSplats,\n shDegree: this._shDegree,\n pixelRatio: this._effectivePixelRatio,\n sceneName: this._currentSceneName,\n // _gpuSort is the requested config; _gpuSortFailed flips true if a GPU\n // validation error forced a silent fallback to CPU sort (see\n // renderer.js's uncapturederror handler) \u2014 surface both so \"is it\n // actually using the GPU\" is answerable from the stats overlay alone.\n gpuSort: this._gpuSort && !this._renderer._gpuSortFailed,\n gpuSortFailed: this._gpuSort && this._renderer._gpuSortFailed,\n tier: this._tier,\n };\n }\n\n /** TEMP debug helper \u2014 returns the CPU-decoded canonical layout for one\n * gaussian, for cross-checking against hand-computed values from the raw\n * source file. See src/viewer.js's 16-floats/gaussian layout comment. */\n getSplatDebug(index) {\n if (!this._gaussians || index < 0 || index >= this._numSplats) return null;\n const d = index * 16;\n const g = this._gaussians;\n return {\n pos: [g[d], g[d+1], g[d+2]],\n part: g[d+3],\n color: [g[d+4], g[d+5], g[d+6], g[d+7]],\n scale: [g[d+8], g[d+9], g[d+10]],\n quat: [g[d+12], g[d+13], g[d+14], g[d+15]],\n };\n }\n\n /**\n * List the color/material variant names available for a part (or the\n * whole scene, for a single-file load \u2014 pass no id). Empty if the part\n * has no variants.\n * @param {string} [id] part id, as passed to loadParts()\n * @returns {string[]}\n */\n getVariants(id) {\n const slots = id == null ? [0] : this._partIndex[id];\n if (!slots) return [];\n const pv = this._partVariants[slots[0]];\n if (!pv) return [];\n return pv.kind === 'spzv' ? pv.variants.map(v => v.name) : pv.names;\n }\n\n /**\n * Switch the active color/material variant for a part.\n *\n * - For parts loaded from a packed `.spzv` file (shared geometry, one\n * palette per variant \u2014 see examples/pack.html), the swap is instant:\n * no fetch, no reload, geometry is stored once.\n * - For parts loaded as `{ url, variants }` (each variant is its own file\n * with its own geometry \u2014 the common case for independently-trained\n * color variants), the target variant's file is fetched on first use\n * (cached after that) and that part's slice of the scene is rebuilt and\n * re-uploaded. Other variants are prefetched in the background after the\n * scene loads, so repeat swaps are instant.\n *\n * @param {string} id part id, as passed to loadParts() (omit for a single-file load)\n * @param {string} name one of the names returned by getVariants()\n * @returns {Promise<boolean>} true if the variant was found and applied\n */\n async setVariant(id, name) {\n const slots = id == null ? [0] : this._partIndex[id];\n if (!slots) return false;\n\n let applied = false;\n for (const slot of slots) {\n const pv = this._partVariants[slot];\n if (!pv) continue;\n\n if (pv.kind === 'spzv') {\n const variant = pv.variants.find(v => v.name === name);\n if (!variant) continue;\n\n const [start, end] = this._fileRanges[slot];\n for (let i = start; i < end; i++) {\n const d = i * 16, p = (i - start) * 4;\n this._gaussians[d + 4] = variant.palette[p + 0];\n this._gaussians[d + 5] = variant.palette[p + 1];\n this._gaussians[d + 6] = variant.palette[p + 2];\n this._gaussians[d + 7] = variant.palette[p + 3];\n }\n this._renderer.patchGaussians(this._gaussians.subarray(start * 16, end * 16), start);\n pv.active = name;\n applied = true;\n continue;\n }\n\n // kind === 'file' \u2014 each variant is its own file with its own geometry.\n if (pv.active === name) { applied = true; continue; }\n if (!pv.names.includes(name)) continue;\n await this._swapPartVariant(slot, pv, name);\n applied = true;\n }\n return applied;\n }\n\n /** Fetch (or reuse a cached) \"<base>.<name>.<ext>\" file for a 'file'-kind\n * variant slot, replace that slot's geometry+color in the merged scene,\n * and re-upload. Handles a different splat count than the slot currently\n * has by resizing the merged buffer and shifting later slots' ranges. */\n async _swapPartVariant(slot, pv, name) {\n let entry = pv.cache[name];\n if (!entry) {\n entry = await loadUrl(`${pv.baseUrl}.${name}.${pv.ext}`);\n pv.cache[name] = entry;\n }\n\n const [start, end] = this._fileRanges[slot];\n const oldCount = end - start;\n const newCount = entry.count;\n const delta = newCount - oldCount;\n\n const slotData = entry.data.slice();\n for (let j = 0; j < newCount; j++) slotData[j * 16 + 3] = slot;\n if (this._flipY) flipYInPlace(slotData, newCount);\n\n if (delta === 0) {\n this._gaussians.set(slotData, start * 16);\n this._renderer.patchGaussians(slotData, start);\n } else {\n const newTotal = this._numSplats + delta;\n const merged = new Float32Array(newTotal * 16);\n merged.set(this._gaussians.subarray(0, start * 16), 0);\n merged.set(slotData, start * 16);\n merged.set(this._gaussians.subarray(end * 16), (start + newCount) * 16);\n\n for (let s = 0; s < this._fileRanges.length; s++) {\n const [a, b] = this._fileRanges[s];\n if (s === slot) this._fileRanges[s] = [start, start + newCount];\n else if (a >= end) this._fileRanges[s] = [a + delta, b + delta];\n }\n\n this._gaussians = merged;\n this._numSplats = newTotal;\n this._depths = new Float32Array(newTotal);\n this._sort = createSorter(newTotal);\n this._activeIdx = new Uint32Array(newTotal);\n this._renderer.uploadGaussians(this._gaussians, this._numSplats);\n }\n\n this._fileAABB[slot] = computeAABB(slotData, newCount);\n this._fileNames[slot] = basenameNoExt(`${pv.baseUrl}.${name}.${pv.ext}`);\n pv.active = name;\n this._buildPartVolumeMask();\n }\n\n /** Background-fetch every non-active 'file'-kind variant after the scene is\n * ready, so later setVariant() calls don't pay a network round trip.\n * Fetched sequentially (low priority); failures are logged, not thrown. */\n async _prefetchVariants() {\n if (!this._prefetchVariantsEnabled) return;\n for (const pv of Object.values(this._partVariants)) {\n if (pv.kind !== 'file') continue;\n for (const name of pv.names) {\n if (name === pv.active || pv.cache[name]) continue;\n try {\n pv.cache[name] = await loadUrl(`${pv.baseUrl}.${name}.${pv.ext}`);\n } catch (e) {\n console.warn(`[HoloSplat] variant prefetch failed for \"${pv.baseUrl}.${name}.${pv.ext}\": ${e.message}`);\n }\n }\n }\n }\n\n\n /** Freeze / unfreeze animation playback. When paused, camera responds to user input. */\n setAnimationPaused(paused) { this._animPaused = paused; }\n\n /** Used by scroll-scene freecamera acts to hand the camera fully to the user.\n * On exit, smoothly blends back to the animation path.\n * (hs-* markers use the overlay system instead \u2014 see _syncCameraMode.) */\n setCameraFree(v) {\n const wasActive = this._cameraFree;\n this._cameraFree = !!v;\n if (v) {\n this._blendBack = null;\n this._camera.disableZoom();\n // Explore acts own single-finger touch for orbiting \u2014 stop passing it\n // through to page scroll while active.\n if (this.animTickOverride) this._camera.allowTouchScroll = false;\n } else {\n this._camera.enableZoom();\n if (this.animTickOverride) this._camera.allowTouchScroll = true;\n if (wasActive && this._animation) {\n this._blendBack = {\n fromEye: this._camera.eye.slice(),\n fromTarget: this._camera.target.slice(),\n t: 0,\n duration: 0.5,\n };\n }\n }\n }\n\n /**\n * Detect the active hs-* marker and apply its scene config's pan/zoom\n * overlay settings on top of the animation base.\n *\n * NOTE: orbit/follow-mouse support was removed here (cfg.orbit) as part of\n * a deliberate cleanup \u2014 it will be re-added slowly later.\n */\n _syncCameraMode() {\n const markers = this._animation.markers;\n const frame = this._animation.frame;\n\n // Find the most recently passed marker.\n let activeMarker = null;\n let maxFrame = -1;\n for (const [name, mf] of Object.entries(markers)) {\n if (mf <= frame && mf > maxFrame) { maxFrame = mf; activeMarker = name; }\n }\n\n // Read scene config \u2014 editor overlay takes priority, then linked DOM element.\n // Fall back to parsing the marker name for backwards compatibility.\n let cfg = null;\n if (activeMarker) {\n // Live editor config (set by editor overlay; available without a linked element)\n cfg = window.__hsSceneConfigs?.[activeMarker] ?? null;\n\n // Legacy fallback: parse hs-* tokens from marker name\n if (!cfg) {\n const hsMode = extractHsMode(activeMarker);\n if (hsMode && hsMode !== 'hs-locked') {\n const zm = hsMode.match(/zoom-(\\d+)/);\n cfg = {\n zoom: { enabled: !!zm, mode: 'limited', range: zm ? +zm[1] : 25 },\n };\n }\n }\n }\n\n // Build a stable mode key for change detection\n const newMode = cfg ? JSON.stringify(cfg) : null;\n if (newMode === this._camMode) return;\n this._camMode = newMode;\n\n // Structural type key \u2014 pan/zoom on-off only.\n const newModeType = cfg\n ? `${+!!(cfg.pan?.enabled)}:${+!!(cfg.zoom?.enabled)}`\n : null;\n const typeChanged = newModeType !== this._camModeType;\n this._camModeType = newModeType;\n\n if (cfg) {\n const pan = cfg.pan || {};\n const zoom = cfg.zoom || {};\n\n if (typeChanged) {\n this._panOffset = [0, 0, 0];\n this._zoomFactor = 1;\n }\n\n // Zoom \u2014 overlay-based (see _zoomFactor): the animation drives radius every\n // frame, so wheel/pinch input is routed through zoomDeltaCallback as a\n // multiplier applied on top of the animation radius (see _tick).\n if (zoom.enabled) {\n this._camera.zoomEnabled = true;\n this._zoomLimit = zoom.limited ? Math.max(0, (zoom.range ?? 500) / 100) : null;\n // Bring the current zoom back inside the new bounds immediately\n // (e.g. when \"limited\" is turned on while already zoomed out further).\n if (this._zoomLimit !== null) {\n this._zoomFactor = Math.max(1 - this._zoomLimit, Math.min(1 + this._zoomLimit, this._zoomFactor));\n }\n this._camera.zoomDeltaCallback = (factor) => {\n let f = this._zoomFactor * factor;\n if (this._zoomLimit !== null) f = Math.max(1 - this._zoomLimit, Math.min(1 + this._zoomLimit, f));\n this._zoomFactor = Math.max(0.01, f);\n };\n } else {\n this._camera.zoomEnabled = false;\n this._camera.zoomDeltaCallback = null;\n this._zoomFactor = 1;\n }\n\n // Pan \u2014 overlay-based (see _panOffset): the animation drives target every\n // frame, so drag input is routed through panDeltaCallback as a world-space\n // delta accumulated on top of the animation target (see _tick).\n if (pan.enabled) {\n this._camera.panEnabled = true;\n this._camera.panSpeed = 1 - Math.min(100, Math.max(0, pan.damping ?? 0)) / 100;\n this._camera.panButton = pan.button === 'left' ? 0 : 2;\n // pan.radius is a percentage of the camera's orbit radius (like zoom.range),\n // so the limit scales with the scene instead of being a fixed world-unit value.\n this._panLimit = pan.limited\n ? Math.max(0, (pan.radius ?? 500) / 100) * this._camera.radius\n : null;\n // Bring the current pan offset back inside the new bounds immediately\n // (e.g. when \"limited\" is turned on while already panned further out).\n if (this._panLimit !== null) {\n const off = this._panOffset;\n const mag = Math.hypot(off[0], off[1], off[2]);\n if (mag > this._panLimit) {\n const f = this._panLimit / mag;\n off[0] *= f; off[1] *= f; off[2] *= f;\n }\n }\n this._camera.panDeltaCallback = (dx, dy, dz) => {\n const off = this._panOffset;\n off[0] += dx; off[1] += dy; off[2] += dz;\n if (this._panLimit !== null) {\n const mag = Math.hypot(off[0], off[1], off[2]);\n if (mag > this._panLimit) {\n const f = this._panLimit / mag;\n off[0] *= f; off[1] *= f; off[2] *= f;\n }\n }\n };\n } else {\n this._camera.panEnabled = false;\n this._camera.panDeltaCallback = null;\n this._panOffset = [0, 0, 0];\n }\n } else {\n this._panOffset = [0, 0, 0];\n this._zoomFactor = 1;\n this._camera.panDeltaCallback = null;\n this._camera.zoomDeltaCallback = null;\n this._camera.panEnabled = !this._animation.focalPoint;\n this._camera.panSpeed = 1;\n this._camera.panButton = 2;\n this._camera.panRadius = null;\n this._camera.panOrigin = null;\n this._camera.enableZoom();\n }\n }\n\n resetCamera() { this._camera.fitScene(this._gaussians, this._numSplats); }\n focusCamera() { this._camera.focusScene(this._gaussians, this._numSplats); }\n\n /** Replace scene data (rebuilds sorter + GPU buffers). fitCamera=true re-fits the orbit camera. */\n setGaussians(data, count, fitCamera = false) {\n this._gaussians = data;\n this._numSplats = count;\n this._depths = new Float32Array(count);\n this._sort = createSorter(count);\n this._sceneReady = true;\n this._renderer.uploadGaussians(data, count);\n if (fitCamera) this._camera.fitScene(data, count);\n }\n\n /** Re-upload display data for the current frame without rebuilding the sorter (used for highlights). */\n uploadDisplay(data) {\n if (this._numSplats) this._renderer.uploadGaussians(data, this._numSplats);\n }\n\n /** Build per-file volume bitmasks and upload to GPU.\n * Call after loading parts or after attaching an animation with volumes.\n *\n * Mask volume names follow one of two conventions (matched against each\n * loaded splat file's name, e.g. \"headphones.headband.yellow\"):\n * - \"<prefix>.mask\" \u2192 affects any file equal to <prefix> or\n * starting with \"<prefix>.\". E.g.\n * \"headphones.mask\" affects every\n * \"headphones.*\" file (all parts, all\n * variants).\n * - \"<prefix>..<suffix>.mask\" \u2192 affects any file starting with\n * \"<prefix>.\" AND ending with\n * \".<suffix>\". E.g. \"headphones..yellow.mask\"\n * affects \"headphones.headband.yellow\",\n * \"headphones.cup.left.yellow\", etc. \u2014\n * every \"yellow\" variant across all parts.\n */\n _buildPartVolumeMask() {\n // Clip masks and axis-transition masks (see loadClips()) are independent\n // of this._animation's own volumes \u2014 they exist for an asset's\n // button-triggered transitions, not the main scroll timeline \u2014 but use\n // the same file-name matching, so they're merged into the same bitmask.\n const clipMaskNames = new Set();\n for (const clip of Object.values(this._clips)) {\n for (const m of clip.masks ?? []) clipMaskNames.add(m.name);\n }\n for (const t of Object.values(this._transitions)) {\n for (const m of t.masks ?? []) clipMaskNames.add(m.name);\n }\n for (const t of Object.values(this._states)) {\n for (const m of t.masks ?? []) clipMaskNames.add(m.name);\n }\n const vols = [...(this._animation?.volumes ?? []), ...[...clipMaskNames].map(name => ({ name }))];\n const fileNames = this._fileNames ?? [];\n const count = Math.max(fileNames.length, 1);\n const masks = new Uint32Array(count);\n vols.forEach((vol, vi) => {\n const prefix = vol.name;\n let matched = 0;\n for (let fi = 0; fi < fileNames.length; fi++) {\n if (matchesMaskPrefix(fileNames[fi], prefix)) {\n masks[fi] |= (1 << vi);\n matched++;\n }\n }\n if (matched === 0 && fileNames.length > 0) {\n console.warn(`[HoloSplat] mask volume \"${prefix}\" matched 0 of ${fileNames.length} file(s) \u2014 check naming convention`);\n }\n });\n this._fileMasks = masks;\n this._renderer.uploadPartVolumeMask(masks);\n }\n\n // \u2500\u2500 Animation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Attach a pre-loaded Animation instance. Pass null to detach. */\n setAnimation(anim) {\n this._animation = anim;\n if (!anim) return;\n if (anim.fov != null) this._camera.fov = anim.fov * Math.PI / 180;\n if (anim.near != null) this._camera.near = anim.near;\n if (anim.far != null) this._camera.far = anim.far;\n // Apply frame 0 camera position immediately so the scene never flashes\n // the auto-fit angle while the user waits for assets to finish loading.\n const { eye, target } = anim.getCameraFrame();\n this._camera.setFromLookAt(eye, target);\n this._buildPartVolumeMask();\n }\n\n /** Fetch, parse, and attach an animation from a URL. */\n async loadAnimationUrl(url) {\n const anim = await loadAnimation(url);\n this.setAnimation(anim);\n return anim;\n }\n\n // \u2500\u2500 Callout projection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Project an array of { id, pos:[x,y,z] } callouts to screen coordinates.\n * Returns array of { id, visible, x, y } using the current view/proj matrices.\n * Call this after update() (i.e. inside onFrame or after a tick).\n */\n projectCallouts(callouts) {\n const view = this._camera.viewMatrix;\n const proj = this._camera.projMatrix;\n // Use CSS pixels (clientWidth/Height) so positions map directly to\n // element left/top without DPR scaling issues.\n const w = this._canvas.clientWidth;\n const h = this._canvas.clientHeight;\n const out = [];\n\n for (const c of callouts) {\n const [px, py, pz] = c.pos;\n\n // Transform to view space (column-major matrix multiply)\n const vx = view[0]*px + view[4]*py + view[8]*pz + view[12];\n const vy = view[1]*px + view[5]*py + view[9]*pz + view[13];\n const vz = view[2]*px + view[6]*py + view[10]*pz + view[14];\n\n if (vz >= 0) { out.push({ id: c.id, visible: false, x: 0, y: 0 }); continue; }\n\n // Perspective divide using the projection matrix shortcut:\n // clip_x = proj[0]*vx, clip_y = proj[5]*vy, clip_w = -vz (since proj[11] = -1)\n const cw = -vz;\n const sx = (proj[0] * vx / cw * 0.5 + 0.5) * w;\n const sy = (1 - (proj[5] * vy / cw * 0.5 + 0.5)) * h;\n\n out.push({ id: c.id, visible: true, x: sx, y: sy });\n }\n return out;\n }\n\n get camera() { return this._camera; }\n\n // \u2500\u2500 Render loop \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n _tick() {\n if (!this._running) return;\n this._rafId = requestAnimationFrame(() => this._tick());\n\n // Delta time (seconds since last tick)\n const now = performance.now();\n const dt = this._lastTick ? Math.min((now - this._lastTick) / 1000, 0.1) : 0;\n this._lastTick = now;\n\n // Rising edge of _sceneReady: the page can stay genuinely busy for\n // several seconds *after* the scene lands too \u2014 decoding/prefetching\n // additional assets (clips, color variants), first-time draw/sort of\n // the full splat count, etc. None of that reflects steady-state device\n // performance, so it shouldn't get baked into \"this device is slow\" and\n // trigger a pixelRatio drop \u2014 ramp-down is 0.85x/s but ramp-up is only\n // 1.05x/s (see _updateAdaptiveQuality), so a drop earned in 2-3s of\n // startup churn takes 15-20s to claw back, which reads as sustained\n // blur. Reset the EMA and hold quality at max for a grace window once\n // ready; a longer preload-feeling startup beats a visibly degrading\n // render.\n if (this._sceneReady && !this._wasSceneReady) {\n this._frameTimeEMA = null;\n this._qualityWarmupUntil = now + QUALITY_WARMUP_MS;\n }\n this._wasSceneReady = this._sceneReady;\n\n // PLY parts stream in progressively: the merged buffer (real data for\n // non-PLY parts, zeros for streaming ones) is uploaded and drawn every\n // frame from the start, well before _sceneReady \u2014 which only flips once\n // every stream has fully finished decoding (see loadParts). That decode\n // (chunked across many files, each yielding to the main thread) runs\n // concurrently with this same render loop for the entire streaming\n // window, which is the real source of sustained slow frames the warm-up\n // above never sees because it only starts counting once _sceneReady\n // goes true. Suspend degradation for the whole !_sceneReady window too.\n const warmingUp = !this._sceneReady || now < this._qualityWarmupUntil;\n this._updateAdaptiveQuality(dt, warmingUp);\n\n const w = this._canvas.width;\n const h = this._canvas.height;\n\n // Mask-volume frames for this tick, used by _computeActiveRanges below\n // to restrict sort/draw to the active subset (mask hiding + frustum cull).\n let volFrames = [];\n let transformsDirty = false;\n\n // Clip playback (product customization) is user-triggered and fully\n // independent of the main Animation/timeline \u2014 it doesn't require one\n // to exist at all, and ticks regardless of _animPaused/_sceneReady.\n if (Object.keys(this._clipPlaybacks).length > 0) this._tickClips(dt);\n if (Object.keys(this._transitionPlaybacks).length > 0) this._tickTransitions(dt);\n if (Object.keys(this._statePlaybacks).length > 0) this._tickStates(dt);\n\n if (this._animation) {\n if (!this._animPaused && this._sceneReady) {\n if (this.animTickOverride) this.animTickOverride(dt);\n else this._animation.tick(dt);\n }\n\n // Update camera from animation BEFORE _syncCameraMode so that when a\n // mode transition fires (e.g. scrubbing into an hs zone), the camera is\n // already at the correct frame position \u2014 no stale-frame snap.\n if (!this._cameraFree) {\n const { eye, target } = this._animation.getCameraFrame();\n if (this._blendBack) {\n // Smooth blend from scroll-scene freecamera position back to animation\n this._blendBack.t += dt;\n const alpha = smoothstep(Math.min(this._blendBack.t / this._blendBack.duration, 1));\n this._camera.setFromLookAt(\n lerp3(this._blendBack.fromEye, eye, alpha),\n lerp3(this._blendBack.fromTarget, target, alpha),\n );\n if (this._blendBack.t >= this._blendBack.duration) this._blendBack = null;\n } else {\n // NOTE: orbit/follow-mouse + focal-point look-target blending was\n // removed here as part of a deliberate cleanup \u2014 it will be\n // re-added slowly later. This currently just follows the\n // animation's own eye/target (optionally crossfaded with an\n // adjacent scene via _sceneBlend), then applies pan/zoom overlays.\n let finalEye = eye, finalTarget = target;\n if (this._sceneBlend) {\n const { otherEye, otherTarget, bf } = this._sceneBlend;\n finalEye = lerp3(otherEye, eye, bf);\n finalTarget = lerp3(otherTarget, target, bf);\n }\n this._camera.setFromLookAt(finalEye, finalTarget);\n // Pan/zoom overlays: user-dragged target offset and scroll-wheel radius\n // multiplier on top of the animation base (see panDeltaCallback/zoomDeltaCallback).\n if (this._panOffset[0] !== 0 || this._panOffset[1] !== 0 || this._panOffset[2] !== 0) {\n this._camera.target[0] += this._panOffset[0];\n this._camera.target[1] += this._panOffset[1];\n this._camera.target[2] += this._panOffset[2];\n }\n if (this._zoomFactor !== 1) {\n this._camera.radius *= this._zoomFactor;\n }\n }\n }\n\n // Runs every tick (cheap \u2014 early-returns when mode is unchanged).\n // Running during scrub lets the camera respond immediately when\n // the playhead crosses an hs-* marker.\n this._syncCameraMode();\n // Same reasoning \u2014 resolve asset state calls every tick so scrubbing\n // (not just forward playback) lands assets on the right state.\n this._syncAssetStates();\n\n const objFrames = this._animation.getObjectFrames();\n if (objFrames.length > 0) {\n let dirty = false;\n for (const { id, pos, quat } of objFrames) {\n const slots = this._partIndex[id];\n if (slots && slots.length) {\n let fpos = pos, fquat = quat;\n const other = this._sceneBlend?.otherObjects?.[id];\n if (other) {\n const bf = this._sceneBlend.bf;\n fpos = lerp3(other.pos, pos, bf);\n fquat = slerpQuat(\n other.quat[0], other.quat[1], other.quat[2], other.quat[3],\n quat[0], quat[1], quat[2], quat[3], bf,\n );\n }\n const m = quatPosToMat4(fpos, fquat);\n this._partLocalPose[id] = m;\n for (const slot of slots) this._partTransFlat.set(m, slot * 16);\n dirty = true;\n }\n }\n if (dirty) { this._renderer.updateTransforms(this._partTransFlat); transformsDirty = true; }\n }\n\n // \u2500\u2500 Asset anchors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // A pure parent transform for an externally-loaded asset's parts (see\n // Animation#getAnchorFrames) \u2014 any loaded part whose id is namespaced\n // under \"ctrl.<assetId>\" gets the anchor's world transform composed\n // with whatever local pose this tick already wrote for that part\n // (e.g. a state/transition/clip pose, or an anim object frame above \u2014\n // see _partLocalPose) \u2014 anchorMat alone for parts with no local pose\n // of their own. Composing rather than overwriting is required for\n // assets like headphones that are both anchored on the main timeline\n // AND have their own state axis (e.g. \"fold\") animating sub-parts;\n // overwriting wiped out the state's pose every single tick.\n const anchorFrames = this._animation.getAnchorFrames();\n if (anchorFrames.length) {\n let anchored = false;\n for (const { asset, pos, quat } of anchorFrames) {\n const anchorMat = quatPosToMat4(pos, quat);\n const prefix = `ctrl.${asset}`;\n for (const id in this._partIndex) {\n if (!id.startsWith(prefix)) continue;\n const local = this._partLocalPose[id];\n const m = local ? mat4Mul(anchorMat, local) : anchorMat;\n for (const slot of this._partIndex[id]) this._partTransFlat.set(m, slot * 16);\n anchored = true;\n }\n }\n if (anchored) { this._renderer.updateTransforms(this._partTransFlat); transformsDirty = true; }\n }\n\n volFrames = this._animation.getVolumeFrames();\n // Cross-fade mask-volume matrices the same way object/camera poses are\n // blended across a scene handoff (see player.js's updateSceneBlend) \u2014\n // otherwise a mask snaps instantly to its new-scene state on the tick\n // the handoff fires while everything else eases in, a visible flicker.\n if (this._sceneBlend?.otherVolumes) {\n const { otherVolumes, bf } = this._sceneBlend;\n for (const v of volFrames) {\n const other = otherVolumes[v.name];\n if (other) v.matrix = lerpMat4(other, v.matrix, bf);\n }\n }\n // A mask volume can be animated (e.g. a reveal wipe during an intro)\n // independently of the camera \u2014 _computeActiveRanges below only runs\n // when something is flagged dirty, so without this, a part hidden by\n // a moving mask stays stuck hidden/shown (using a stale active-range\n // snapshot) until the next camera-driven render forces a recompute \u2014\n // visible as a part vanishing, then popping back on the next scroll.\n const animFrame = this._animation.frame;\n if (volFrames.length > 0 && animFrame !== this._lastVolAnimFrame) this._sortDirty = true;\n this._lastVolAnimFrame = animFrame;\n } else if (this._autoRotate) {\n this._camera.theta += 0.005;\n }\n\n // Clip masks (asset color-transition fades \u2014 see loadClips()/playClip())\n // are independent of this._animation and apply regardless of whether a\n // main animation is attached at all.\n for (const name in this._clipMaskState) {\n volFrames.push({ name, matrix: this._clipMaskState[name], softEdge: this._clipMaskSoftEdge[name] });\n }\n if (volFrames.length > 0) {\n for (const v of volFrames) {\n const feather = this._maskFeather[v.name];\n if (feather != null) v.softEdge = feather;\n }\n this._renderer.updateMaskVolumes(volFrames);\n }\n\n if (!this._numSplats) return;\n\n this._camera.update(w, h);\n\n const view = this._camera.viewMatrix;\n const proj = this._camera.projMatrix;\n\n // Skip sort+render when nothing has changed \u2014 keeps GPU idle on static scenes.\n let viewChanged = !this._lastSortView;\n if (!viewChanged) {\n for (let i = 0; i < 16; i++) {\n if (view[i] !== this._lastSortView[i]) { viewChanged = true; break; }\n }\n }\n if (!viewChanged && !transformsDirty && !this._sortDirty) return;\n\n // Frame rate cap: limit GPU work to ~60fps on high-refresh displays.\n // The animation tick (above) still runs every RAF for smooth timing.\n if (now - this._lastRenderMs < 15) return;\n this._lastRenderMs = now;\n\n if (!this._lastSortView) this._lastSortView = new Float32Array(16);\n this._lastSortView.set(view);\n this._sortDirty = false;\n\n // Notify listeners (e.g. player updating callout positions)\n if (this.onFrame) {\n this.onFrame(view, proj, w, h);\n }\n\n const focal = this._camera.focalLength(h);\n\n // Upload uniforms before sorting \u2014 the GPU sort's cs_depth_key kernel\n // reads uniforms.view for this frame's depth computation.\n this._renderer.updateUniforms({\n view, proj, width: w, height: h, focal,\n near: this._camera.near, radiusCap: this._effectiveRadiusCap,\n });\n\n // null = render the full scene (fast path). When mask volumes hide one or\n // more files entirely, or a part's bounds fall entirely outside the view\n // frustum, this is the number of active gaussians and this._activeIdx\n // holds their packed indices \u2014 see _computeActiveRanges.\n const activeCount = this._computeActiveRanges(volFrames, view, proj);\n\n // Sort back-to-front. When some parts are hidden/culled (activeCount !==\n // null), restrict sort/draw to the active subset \u2014 sort cost and\n // instance count both scale with N, not _numSplats.\n const N = activeCount === null ? this._numSplats : activeCount;\n this._lastActiveCount = N;\n\n // Per-splat covariance/eigen/SH preprocess (see shaders.js cs_preprocess) \u2014\n // must run before both the sort (GPU sort's depth_key reads gaussian data\n // independently, but vs_main needs this frame's geometry either way) and\n // draw(). Runs every frame: view/transforms/masks can all change frame to\n // frame.\n this._renderer.preprocess(N);\n\n if (this._gpuSort && activeCount === null && !this._renderer._gpuSortFailed) {\n // GPU radix sort writes the permutation directly into _orderBuf (idxA) \u2014\n // no CPU readback. Not used with per-part culling yet (v1 keeps GPU\n // sort and _activeIdx compaction mutually exclusive \u2014 see plan).\n // Falls back to the CPU path below if _gpuSortFailed gets set (a GPU\n // validation error was reported via uncapturederror \u2014 see renderer init).\n this._renderer.runGpuSort(N);\n } else {\n this._computeDepths(view);\n const order = this._sort(this._depths, N, activeCount === null ? null : this._activeIdx);\n this._renderer.updateOrder(order, N);\n }\n\n this._renderer.draw(N);\n }\n\n _computeDepths(view) {\n // Precompute per-part depth row: vz = a*lx + b*ly + c*lz + d\n // where (lx,ly,lz) is the splat position in part-local space.\n const v2 = view[2], v6 = view[6], v10 = view[10], v14 = view[14];\n const rows = [];\n const flat = this._partTransFlat;\n const nParts = this._partTransforms.length;\n for (let p = 0; p < nParts; p++) {\n const o = p * 16;\n rows.push([\n v2*flat[o] + v6*flat[o+1] + v10*flat[o+2],\n v2*flat[o+4] + v6*flat[o+5] + v10*flat[o+6],\n v2*flat[o+8] + v6*flat[o+9] + v10*flat[o+10],\n v2*flat[o+12]+ v6*flat[o+13] + v10*flat[o+14] + v14,\n ]);\n }\n\n const gs = this._gaussians;\n const dep = this._depths;\n const N = this._numSplats;\n for (let i = 0; i < N; i++) {\n const j = i * 16;\n const r = rows[gs[j + 3]]; // part index stored as float 0.0, 1.0, \u2026\n dep[i] = r[0] * gs[j] + r[1] * gs[j + 1] + r[2] * gs[j + 2] + r[3];\n }\n }\n\n /** Build the set of gaussian indices to sort/draw this frame, writing them\n * into this._activeIdx. A part's range is excluded when it's fully hidden\n * by mask volumes (_isFileHidden) or its bounds lie entirely outside the\n * view frustum (_isFileOutsideView). Returns null (fast path \u2014 render\n * everything) for single-part scenes or when nothing is excluded;\n * otherwise returns the active count. */\n _computeActiveRanges(volFrames, view, proj) {\n // Per-part culling (mask/frustum) forces the renderer onto the slower\n // CPU sort fallback (see _tick \u2014 GPU sort only runs when activeCount is\n // null), which has turned out to cause visible flicker/instability and\n // softer image quality versus the GPU radix sort's fast path. Until that\n // CPU-sort-path issue is root-caused, skip per-file culling entirely \u2014\n // costs some performance (everything gets sorted/drawn every frame,\n // masked-out splats included) but the shader's own per-splat maskFade\n // still hides them correctly; this only forgoes the CPU-side shortcut.\n return null;\n const ranges = this._fileRanges;\n if (!this._activeIdx || !ranges || ranges.length < 2) return null;\n\n const masks = this._fileMasks;\n let anyMasked = false;\n if (masks) {\n for (let i = 0; i < masks.length; i++) {\n if (masks[i]) { anyMasked = true; break; }\n }\n }\n const invMats = anyMasked ? volFrames.map(v => invertMat4(v.matrix)) : null;\n\n const tanHalfFovX = 1 / proj[0];\n const tanHalfFovY = 1 / proj[5];\n\n const idx = this._activeIdx;\n let count = 0;\n let anyExcluded = false;\n for (let slot = 0; slot < ranges.length; slot++) {\n const range = ranges[slot];\n if (!range) continue;\n if (anyMasked && this._isFileHidden(slot, volFrames, invMats)) { anyExcluded = true; continue; }\n if (this._isFileOutsideView(slot, view, proj, tanHalfFovX, tanHalfFovY)) { anyExcluded = true; continue; }\n for (let g = range[0]; g < range[1]; g++) idx[count++] = g;\n }\n return anyExcluded ? count : null;\n }\n\n /** True if `slot`'s world-space bounding box lies entirely outside the\n * camera's view frustum (behind the near plane, or off to one side), so\n * its splat range can be skipped from sort/draw. Conservative: a corner\n * that can't be unambiguously placed on one side prevents the cull. */\n _isFileOutsideView(slot, view, proj, tanHalfFovX, tanHalfFovY) {\n const aabb = this._fileAABB[slot];\n if (!aabb) return false;\n\n const { min, max } = aabb;\n const m = this._partTransFlat.subarray(slot * 16, slot * 16 + 16);\n const near = this._camera.near;\n\n let allBehindNear = true;\n let allLeft = true, allRight = true, allAbove = true, allBelow = true;\n\n for (let c = 0; c < 8; c++) {\n const lx = (c & 1) ? max[0] : min[0];\n const ly = (c & 2) ? max[1] : min[1];\n const lz = (c & 4) ? max[2] : min[2];\n\n const wx = m[0]*lx + m[4]*ly + m[8]*lz + m[12];\n const wy = m[1]*lx + m[5]*ly + m[9]*lz + m[13];\n const wz = m[2]*lx + m[6]*ly + m[10]*lz + m[14];\n\n const vx = view[0]*wx + view[4]*wy + view[8]*wz + view[12];\n const vy = view[1]*wx + view[5]*wy + view[9]*wz + view[13];\n const vz = view[2]*wx + view[6]*wy + view[10]*wz + view[14];\n\n if (vz < -near) {\n allBehindNear = false;\n const limX = -vz * tanHalfFovX;\n const limY = -vz * tanHalfFovY;\n if (vx > -limX) allLeft = false;\n if (vx < limX) allRight = false;\n if (vy > -limY) allAbove = false;\n if (vy < limY) allBelow = false;\n } else {\n allLeft = allRight = allAbove = allBelow = false;\n }\n }\n\n return allBehindNear || allLeft || allRight || allAbove || allBelow;\n }\n\n /** True if `slot`'s bounding box lies entirely outside any mask volume\n * that affects it \u2014 i.e. that volume's maskFade is 0 across the whole\n * file, making the file invisible regardless of other volumes (maskFade\n * is a product across affecting volumes). */\n _isFileHidden(slot, volFrames, invMats) {\n const maskBits = this._fileMasks[slot];\n if (!maskBits) return false;\n const aabb = this._fileAABB[slot];\n if (!aabb) return false;\n\n const partMat = this._partTransFlat.subarray(slot * 16, slot * 16 + 16);\n for (let vi = 0; vi < volFrames.length; vi++) {\n if (!((maskBits >> vi) & 1)) continue;\n const inv = invMats[vi];\n if (!inv) continue; // degenerate volume \u2014 shader treats it as a no-op too\n\n const m = mat4Mul(inv, partMat);\n let minX = Infinity, minY = Infinity, minZ = Infinity;\n let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;\n for (let c = 0; c < 8; c++) {\n const x = (c & 1) ? aabb.max[0] : aabb.min[0];\n const y = (c & 2) ? aabb.max[1] : aabb.min[1];\n const z = (c & 4) ? aabb.max[2] : aabb.min[2];\n const px = m[0]*x + m[4]*y + m[8]*z + m[12];\n const py = m[1]*x + m[5]*y + m[9]*z + m[13];\n const pz = m[2]*x + m[6]*y + m[10]*z + m[14];\n if (px < minX) minX = px; if (px > maxX) maxX = px;\n if (py < minY) minY = py; if (py > maxY) maxY = py;\n if (pz < minZ) minZ = pz; if (pz > maxZ) maxZ = pz;\n }\n\n const lim = 0.5 + (volFrames[vi].softEdge ?? 0.05);\n if (maxX < -lim || minX > lim || maxY < -lim || minY > lim || maxZ < -lim || minZ > lim) {\n return true;\n }\n }\n return false;\n }\n\n // \u2500\u2500 Resize handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n _observeResize() {\n if (typeof ResizeObserver === 'undefined') return;\n this._resizeObs = new ResizeObserver(() => {\n // Debounce so mobile browser bar show/hide (which fires a continuous stream\n // of resize events as the bar animates) only triggers one canvas resize\n // after the animation settles, not one per pixel of bar movement.\n clearTimeout(this._resizeTimer);\n this._resizeTimer = setTimeout(() => this._updateSize(), 150);\n });\n this._resizeObs.observe(this._canvas);\n }\n\n _updateSize() {\n const dpr = Math.min(window.devicePixelRatio || 1, this._effectivePixelRatio);\n const w = Math.round(this._canvas.clientWidth * dpr);\n const h = Math.round(this._canvas.clientHeight * dpr);\n if (w && h && (this._canvas.width !== w || this._canvas.height !== h)) {\n this._canvas.width = w;\n this._canvas.height = h;\n }\n }\n\n // \u2500\u2500 Adaptive quality \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Tracks a smoothed frame time and, once per second, nudges\n * _effectivePixelRatio down when frames are running slow (<27fps) or back\n * up toward _maxPixelRatio when comfortably fast (>50fps), resizing the\n * canvas to match. No-op if `dt` is 0 (first frame) or adaptiveQuality is off.\n *\n * @param {boolean} warmingUp while true (see the _sceneReady rising-edge\n * check in _tick), the EMA still gets tracked but the scale-down branch\n * is skipped \u2014 startup churn raises frame times without representing\n * steady-state device performance, and scale-down recovers far slower\n * than it triggers (0.85x/s down vs 1.05x/s up).\n */\n _updateAdaptiveQuality(dt, warmingUp) {\n if (!this._adaptiveQuality || !dt) return;\n\n const alpha = 1 - Math.exp(-dt / 0.5); // ~0.5s smoothing window\n this._frameTimeEMA = this._frameTimeEMA == null\n ? dt\n : this._frameTimeEMA + (dt - this._frameTimeEMA) * alpha;\n\n const now = performance.now();\n if (now - this._lastQualityCheck < 1000) return;\n this._lastQualityCheck = now;\n\n const SLOW = 1 / 27; // scale down below ~27fps\n const FAST = 1 / 50; // scale back up above ~50fps\n\n if (!warmingUp && this._frameTimeEMA > SLOW && this._effectivePixelRatio > this._minPixelRatio) {\n this._effectivePixelRatio = Math.max(this._minPixelRatio, this._effectivePixelRatio * 0.85);\n this._updateSize();\n } else if (this._frameTimeEMA < FAST && this._effectivePixelRatio < this._maxPixelRatio) {\n this._effectivePixelRatio = Math.min(this._maxPixelRatio, this._effectivePixelRatio * 1.05);\n this._updateSize();\n }\n\n if (!warmingUp && this._frameTimeEMA > SLOW && this._effectiveRadiusCap > this._minRadiusCap) {\n this._effectiveRadiusCap = Math.max(this._minRadiusCap, this._effectiveRadiusCap * 0.85);\n } else if (this._frameTimeEMA < FAST && this._effectiveRadiusCap < this._maxRadiusCap) {\n this._effectiveRadiusCap = Math.min(this._maxRadiusCap, this._effectiveRadiusCap * 1.05);\n }\n }\n}\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst IDENTITY_MAT4 = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]);\n\n// How long after _sceneReady flips true to hold adaptive quality at max\n// before letting it scale down \u2014 see the _sceneReady rising-edge check in\n// _tick. Covers startup churn (clip/variant decode, first full-scene\n// draw+sort) that isn't representative of steady-state frame time.\nconst QUALITY_WARMUP_MS = 8000;\n\n// How long loadClips() waits after the default color variant is showing\n// before fetching the other color variants in the background \u2014 see the\n// Phase 2 comment in loadClips(). Long enough for the page to settle past\n// its own startup churn before adding more network/decompression load.\nconst CLIP_VARIANT_PREFETCH_DELAY_MS = 4000;\n\n// LOD tier forced on a clip part's non-active color variants (see\n// loadClips() Phase 2) regardless of device tier \u2014 per-file mask culling is\n// disabled (_computeActiveRanges always returns null), so every variant a\n// part loads costs sort/draw time every frame forever, not just while\n// visible. 2 is the most aggressive tier prune.html generates.\nconst BACKGROUND_VARIANT_LOD = 2;\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Strip query string, directory, known splat extension, and a trailing\n * `.lod{N}` LOD-tier suffix (see device-tier.js's resolveLodUrl \u2014 mask\n * matching below must key off the original splat name regardless of which\n * LOD tier actually got fetched, or every \"<prefix>..<suffix>.mask\" lookup\n * silently matches 0 files once LOD substitution is in play) from a URL,\n * leaving the bare splat name (e.g. \"headphones.headband.yellow\"). */\nfunction basenameNoExt(url) {\n const base = url.split('?')[0].split('/').pop();\n return base.replace(/\\.(spz|ply|splat)$/i, '').replace(/\\.lod\\d+$/i, '');\n}\n\n/** Picks which of a part's \"<axis>=<value>\" variant suffixes (see\n * export_holosplat_asset.py) matches `defaults`, returning it as a leading\n * \".\" segment ready to append to the part's base splat name \u2014 or '' if the\n * part has no variants. Falls back to the first variant if none of them\n * match (e.g. defaults omits that part's axis). */\nfunction defaultVariantSuffix(part, defaults) {\n if (!part.variants.length) return '';\n const match = part.variants.find(v => {\n const eq = v.indexOf('=');\n if (eq < 0) return false;\n return defaults?.[v.slice(0, eq)] === v.slice(eq + 1);\n });\n return '.' + (match ?? part.variants[0]);\n}\n\n/** Multiplies two column-major mat4s: returns a * b. */\nfunction mat4Mul(a, b) {\n const out = new Float32Array(16);\n for (let col = 0; col < 4; col++) {\n for (let row = 0; row < 4; row++) {\n out[col * 4 + row] =\n a[row] * b[col * 4] +\n a[row + 4] * b[col * 4 + 1] +\n a[row + 8] * b[col * 4 + 2] +\n a[row + 12] * b[col * 4 + 3];\n }\n }\n return out;\n}\n\n// Half-extent multiplier applied to each axis's largest splat scale when\n// computing a file's local-space AABB \u2014 a coarse but cheap bound on how far\n// a Gaussian's visible footprint extends past its center.\nconst MASK_AABB_MARGIN = 3;\n\n/** Local-space AABB (position \u00B1 scale margin) over `count` Gaussians,\n * or null if count is 0. */\nfunction computeAABB(data, count) {\n if (count === 0) return null;\n let minX = Infinity, minY = Infinity, minZ = Infinity;\n let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;\n for (let i = 0; i < count; i++) {\n const j = i * 16;\n const x = data[j], y = data[j + 1], z = data[j + 2];\n const m = MASK_AABB_MARGIN * Math.max(data[j + 8], data[j + 9], data[j + 10]);\n if (x - m < minX) minX = x - m; if (x + m > maxX) maxX = x + m;\n if (y - m < minY) minY = y - m; if (y + m > maxY) maxY = y + m;\n if (z - m < minZ) minZ = z - m; if (z + m > maxZ) maxZ = z + m;\n }\n return { min: [minX, minY, minZ], max: [maxX, maxY, maxZ] };\n}\n\n/** Merges `aabb` (or null) with the AABB of another chunk of Gaussians. */\nfunction extendAABB(aabb, data, count) {\n const next = computeAABB(data, count);\n if (!next) return aabb;\n if (!aabb) return next;\n return {\n min: [Math.min(aabb.min[0], next.min[0]), Math.min(aabb.min[1], next.min[1]), Math.min(aabb.min[2], next.min[2])],\n max: [Math.max(aabb.max[0], next.max[0]), Math.max(aabb.max[1], next.max[1]), Math.max(aabb.max[2], next.max[2])],\n };\n}\n\n/**\n * Tests whether a loaded splat file's name matches a mask-volume prefix.\n *\n * \"<a>\" \u2192 fileName === a || fileName.startsWith(a + '.')\n * \"<a>..<b>\" \u2192 both of the above for `a`, AND\n * fileName === b || fileName.endsWith('.' + b)\n * (an empty `a` or `b` skips that half of the check, so\n * \"..yellow\" matches any file ending in \".yellow\")\n */\nfunction matchesMaskPrefix(fileName, prefix) {\n const sep = prefix.indexOf('..');\n const name1 = sep === -1 ? prefix : prefix.slice(0, sep);\n const name2 = sep === -1 ? '' : prefix.slice(sep + 2);\n const pre = name1 === '' || fileName === name1 || fileName.startsWith(name1 + '.');\n const suf = name2 === '' || fileName === name2 || fileName.endsWith('.' + name2);\n return pre && suf;\n}\n\n/**\n * Extract the camera-mode token from a marker name.\n * Handles both pure hs-* markers (\"hs-h30\") and scene markers with embedded\n * hs-* tokens (\"feature-01 hs-h30\", \"intro hs-h30 hs-v20\").\n * Returns the hs-* portion as a single string, or null for non-camera markers.\n *\n * \"hs-h30\" \u2192 \"hs-h30\"\n * \"feature-01 hs-h30\" \u2192 \"hs-h30\"\n * \"intro hs-h30 hs-v20\" \u2192 \"hs-h30-v20\" (merged for regex matching)\n * \"hs-locked\" \u2192 \"hs-locked\"\n * \"feature-01 hs-locked\"\u2192 \"hs-locked\"\n * \"intro\" \u2192 null\n */\nfunction extractHsMode(name) {\n if (name.startsWith('hs-')) return name;\n const hs = name.trim().split(/\\s+/).filter(t => t.startsWith('hs-'));\n if (!hs.length) return null;\n return hs.length === 1 ? hs[0] : 'hs-' + hs.map(t => t.slice(3)).join('-');\n}\n\nfunction flipYInPlace(data, count) {\n // Applies a 180\u00B0 rotation around the X axis to every Gaussian in canonical layout.\n // Converts Y-down scenes (OpenCV/COLMAP convention) to Y-up (OpenGL/HoloSplat convention).\n // Position: (x, y, z) \u2192 (x, -y, -z)\n // Quaternion pre-multiply by (1,0,0,0): (qx,qy,qz,qw) \u2192 (qw,-qz,qy,-qx)\n for (let i = 0; i < count; i++) {\n const d = i * 16;\n data[d + 1] = -data[d + 1];\n data[d + 2] = -data[d + 2];\n const qx = data[d + 12], qy = data[d + 13], qz = data[d + 14], qw = data[d + 15];\n data[d + 12] = qw;\n data[d + 13] = -qz;\n data[d + 14] = qy;\n data[d + 15] = -qx;\n }\n}\n\n// Sign flip per SH-rest basis (indices 0-14) under the same 180\u00B0-about-X\n// rotation flipYInPlace applies to position/quaternion: y\u2192-y, z\u2192-z, x\n// unchanged. Each real-SH basis function is a polynomial in x,y,z, so its\n// sign under this transform is just its parity in y and z combined \u2014 e.g.\n// basis 0 \u221D y flips, basis 2 \u221D x doesn't, basis 4 \u221D yz doesn't (both factors\n// flip, signs cancel). Derived directly from the basis list in shaders.js.\n// SH-rest coefficients are NOT rotation-invariant \u2014 flipYInPlace rotates\n// every splat's position/orientation but never touched this data, so SH\n// evaluation was silently using coefficients meant for the original,\n// unrotated frame against directions computed in the flipped frame.\nconst SH_FLIP_Y_SIGN = [\n -1, -1, 1, // degree 1: y, z, x\n -1, 1, 1, -1, 1, // degree 2: xy, yz, (2zz-xx-yy), xz, (xx-yy)\n -1, 1, -1, -1, 1, -1, 1, // degree 3\n];\n\nfunction flipYInPlaceSH(shData, numBases, count) {\n if (!shData || !numBases) return;\n const n = Math.min(numBases, SH_FLIP_Y_SIGN.length);\n for (let i = 0; i < count; i++) {\n const base = i * numBases * 3;\n for (let b = 0; b < n; b++) {\n if (SH_FLIP_Y_SIGN[b] !== -1) continue;\n const o = base + b * 3;\n shData[o] = -shData[o];\n shData[o + 1] = -shData[o + 1];\n shData[o + 2] = -shData[o + 2];\n }\n }\n}\n\nconst SPLAT_EXTS = ['spz', 'ply', 'splat'];\n// .ply and .spz are handled explicitly below (both need shDegree); this map\n// only covers the remaining formats (currently just .splat, via loadSplat).\nconst SPLAT_LOADERS = {};\n\n/**\n * Load a splat file, auto-detecting format from the extension.\n * If the file returns HTTP 4xx, tries the other known extensions in order\n * (.spz \u2192 .ply \u2192 .splat) so renaming or re-encoding a file doesn't break callers.\n * Non-4xx errors (network failures, 5xx) are rethrown immediately.\n */\nasync function loadUrl(url, onProgress, shDegree = 0) {\n const clean = url.split('?')[0];\n const lastDot = clean.lastIndexOf('.');\n const rawExt = lastDot >= 0 ? clean.slice(lastDot + 1).toLowerCase() : '';\n\n // .spzv (packed variants) is a deliberate choice, not a fallback target \u2014\n // load it directly with no .spz/.ply/.splat fallback chain.\n if (rawExt === 'spzv') return loadSpzv(url, onProgress);\n\n const hasExt = SPLAT_EXTS.includes(rawExt);\n\n // Try the given extension first, then the remaining formats as fallbacks.\n const exts = hasExt ? [rawExt, ...SPLAT_EXTS.filter(e => e !== rawExt)] : SPLAT_EXTS;\n const base = hasExt ? url.slice(0, url.lastIndexOf('.')) : url;\n\n let lastErr;\n for (const ext of exts) {\n const candidate = `${base}.${ext}`;\n const loader = ext === 'ply' ? (u, p) => loadPly(u, p, shDegree)\n : ext === 'spz' ? (u, p) => loadSpz(u, p, shDegree)\n : (SPLAT_LOADERS[ext] ?? loadSplat);\n try {\n return await loader(candidate, onProgress);\n } catch (err) {\n if (!/^HTTP 4/.test(err.message)) throw err; // non-4xx \u2192 give up immediately\n lastErr = err;\n }\n }\n throw new Error(`HoloSplat: splat file not found as .spz / .ply / .splat \u2014 \"${base}\"`);\n}\n\nfunction smoothstep(t) { return t * t * (3 - 2 * t); }\nfunction lerp3(a, b, t) {\n return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];\n}\nfunction lerpMat4(a, b, t) {\n const out = new Float32Array(16);\n for (let i = 0; i < 16; i++) out[i] = a[i] + (b[i] - a[i]) * t;\n return out;\n}\n\n/** Build a column-major mat4 from pos=[x,y,z] and quat=[x,y,z,w]. */\nfunction quatPosToMat4(pos, quat) {\n const [qx, qy, qz, qw] = quat;\n const [px, py, pz] = pos;\n const x2 = qx*2, y2 = qy*2, z2 = qz*2;\n const xx = qx*x2, xy = qx*y2, xz = qx*z2;\n const yy = qy*y2, yz = qy*z2, zz = qz*z2;\n const wx = qw*x2, wy = qw*y2, wz = qw*z2;\n return new Float32Array([\n 1-yy-zz, xy+wz, xz-wy, 0,\n xy-wz, 1-xx-zz, yz+wx, 0,\n xz+wy, yz-wx, 1-xx-yy, 0,\n px, py, pz, 1,\n ]);\n}\n\nfunction resolveCanvas(canvas) {\n if (!canvas) throw new Error('HoloSplat: canvas option is required');\n if (typeof canvas === 'string') {\n const el = document.querySelector(canvas);\n if (!el) throw new Error(`HoloSplat: canvas selector \"${canvas}\" not found`);\n return el;\n }\n return canvas;\n}\n", "/**\n * HoloSplat Player \u2014 embeddable Gaussian splat player for any website.\n *\n * Accepts any container element, creates its own canvas inside it, and handles\n * the full lifecycle: WebGPU init, loading spinner, progress bar, error display,\n * and responsive resize.\n *\n * \u2500\u2500\u2500 Script-tag usage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * <script src=\"holosplat.iife.js\"></script>\n *\n * <div id=\"scene\" style=\"width:100%; height:500px\"></div>\n * <script>\n * HoloSplat.player('#scene', {\n * src: 'https://cdn.example.com/scene.spz',\n * });\n * </script>\n *\n * \u2500\u2500\u2500 Data-attribute auto-init (no JS needed) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * <div data-holosplat=\"https://cdn.example.com/scene.spz\"\n * style=\"width:100%; height:500px\"></div>\n *\n * All [data-holosplat] elements are initialised automatically when the\n * script loads (or on DOMContentLoaded if the script is in <head>).\n *\n * \u2500\u2500\u2500 Returned API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * load(url) \u2013 load a new scene into the same player\n * loadAnim(url) \u2013 load a Blender animation JSON (camera + callouts)\n * destroy() \u2013 stop rendering and remove all created DOM\n * setBackground(bg) \u2013 '#rrggbb' | '#rrggbbaa' | 'transparent' | [r,g,b,a]\n * setSplatScale(n) \u2013 multiplier applied to all splat sizes\n * setAutoRotate(bool) \u2013 toggle slow orbit rotation (disabled while animation plays)\n * resetCamera() \u2013 fit camera back to the loaded scene\n * camera \u2013 OrbitCamera instance for direct manipulation\n * animation \u2013 Animation instance (after loadAnim), or null\n * callout(id) \u2013 returns the HTMLElement for a named callout div\n *\n * \u2500\u2500\u2500 Callout styling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Each callout exported from Blender creates a <div class=\"hs-callout\"\n * data-id=\"name\"> absolutely positioned over the canvas. The library only\n * moves it \u2014 you provide the content and styles:\n *\n * .hs-callout[data-id=\"screen\"] { width: 120px; background: white; ... }\n *\n * When the callout point goes behind the camera, the class hs-callout--hidden\n * is added (display: none by default) and removed when it comes back into view.\n *\n * \u2500\u2500\u2500 Data attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * data-holosplat=\"url\" \u2013 scene file (auto-init)\n * data-holosplat-anim=\"url\" \u2013 animation JSON (auto-init alongside scene)\n */\n\nimport { Viewer } from './viewer.js';\nimport { splatNameFromId } from './animation.js';\nimport { detectDeviceTier, qualityForTier, resolveLodUrl, resolvePartsLod } from './device-tier.js';\n\n// \u2500\u2500 CSS (injected once per page) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst PLAYER_CSS = `\n.hs-player{position:relative;overflow:hidden;}\n.hs-player canvas{position:absolute;inset:0;width:100%;height:100%;display:block;}\n.hs-player .hs-overlay{\n position:absolute;inset:0;display:flex;flex-direction:column;\n align-items:center;justify-content:center;gap:10px;\n pointer-events:none;font-family:system-ui,sans-serif;\n}\n.hs-player .hs-pct{font-size:1.1rem;font-weight:600;color:rgba(255,255,255,.85);letter-spacing:.02em;}\n.hs-player .hs-bar-wrap{width:140px;height:3px;background:rgba(255,255,255,.1);border-radius:2px;overflow:hidden;}\n.hs-player .hs-bar{height:100%;background:#3a7aff;width:0%;transition:width .1s;}\n.hs-player .hs-msg{font-size:.78rem;color:rgba(255,255,255,.4);text-align:center;max-width:260px;line-height:1.5;padding:0 16px;}\n.hs-player .hs-msg.hs-err{color:#f87;}\n.hs-player .hs-callouts{position:absolute;inset:0;pointer-events:none;}\n.hs-lines{position:absolute;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none;}\n.hs-dot{fill:#3a7aff;stroke:#fff;stroke-width:2;}\n.hs-line{stroke:rgba(255,255,255,.55);stroke-width:1.5;}\n.hs-callout{position:absolute;pointer-events:auto;}\n.hs-callout--hidden{display:none;}\n`;\n\nlet cssInjected = false;\nfunction injectCss() {\n if (cssInjected || typeof document === 'undefined') return;\n cssInjected = true;\n const s = document.createElement('style');\n s.textContent = PLAYER_CSS;\n document.head.appendChild(s);\n}\n\n// \u2500\u2500 player() \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * @param {string|HTMLElement} container CSS selector or DOM element\n * @param {object} [opts]\n * @param {string} [opts.src] URL to load immediately (.splat / .ply / .spz)\n * @param {string|number[]} [opts.background='transparent']\n * @param {number} [opts.fov=60]\n * @param {number} [opts.near=0.01]\n * @param {number} [opts.far=2000]\n * @param {number} [opts.splatScale=1.08]\n * @param {boolean} [opts.autoRotate=false]\n * @param {'auto'|'low'|'medium'|'high'} [opts.quality='auto'] device-tier presets\n * for maxPixelRatio/shDegree caps and LOD file selection \u2014 see device-tier.js\n * @param {boolean} [opts.adaptiveQuality=true] scale render resolution down when\n * frame time exceeds ~37ms (27fps) and back up toward maxPixelRatio when\n * comfortably under ~20ms (50fps)\n * @param {function} [opts.onLoad]\n * @param {function} [opts.onProgress]\n * @param {function} [opts.onError]\n */\nexport function player(container, opts = {}) {\n injectCss();\n\n // \u2500\u2500 Resolve container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const root = typeof container === 'string'\n ? document.querySelector(container)\n : container;\n if (!root) throw new Error(`HoloSplat: container not found \u2014 \"${container}\"`);\n\n const dataSrc = root.getAttribute('data-holosplat') || undefined;\n const dataAnim = root.getAttribute('data-holosplat-anim') || undefined;\n\n const {\n scene, // preferred name\n src: srcAlias, // kept for backward compat\n parts,\n animation: animSrc = dataAnim,\n clips, // string URL, or array of URLs \u2014 asset clip file(s); see export_holosplat_asset.py\n partsDir,\n partsExt = '',\n scenes, // markerName \u2192 scene config object\n masks, // mask volume name \u2192 { feather } config object\n sh, // global SH degree (0\u20133); overrides per-scene sh if set\n background = 'transparent',\n fov = 60,\n near = 0.01,\n far = 2000,\n splatScale = 1.08,\n autoRotate = false,\n flipY = false,\n maxPixelRatio,\n quality = 'auto', // 'auto'|'low'|'medium'|'high' \u2014 see device-tier.js\n adaptiveQuality = true, // dynamically scale render resolution based on frame time\n prefetchVariants, // background-fetch inactive color variants; default depends on device tier\n aaDilation = 0.3, // anti-aliasing covariance dilation (default 0.3, matches PlayCanvas/SuperSplat)\n gpuSort = false, // opt-in GPU compute-shader radix sort \u2014 see src/sort-shaders.js\n zIndex = 5,\n onLoad, onProgress, onError,\n } = opts;\n\n const src = scene ?? srcAlias ?? dataSrc;\n\n const tier = quality === 'auto' ? detectDeviceTier() : quality;\n const caps = qualityForTier(tier);\n const effectiveMaxPixelRatio = maxPixelRatio ?? caps.maxPixelRatio;\n\n root.style.zIndex = String(zIndex);\n\n // \u2500\u2500 Build DOM \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n root.classList.add('hs-player');\n\n const canvas = document.createElement('canvas');\n const calloutEl = document.createElement('div');\n calloutEl.className = 'hs-callouts';\n const overlay = document.createElement('div');\n overlay.className = 'hs-overlay';\n overlay.innerHTML =\n '<div class=\"hs-pct\">0%</div>' +\n '<div class=\"hs-bar-wrap\"><div class=\"hs-bar\"></div></div>' +\n '<div class=\"hs-msg\"></div>';\n root.appendChild(canvas);\n root.appendChild(calloutEl);\n root.appendChild(overlay);\n\n const pctEl = overlay.querySelector('.hs-pct');\n const barWrap = overlay.querySelector('.hs-bar-wrap');\n const bar = overlay.querySelector('.hs-bar');\n const msgEl = overlay.querySelector('.hs-msg');\n let _loadStart = null;\n // Ref-counted: the boot sequence kicks off several loads concurrently\n // (main scene/parts, animation, clip assets), each wrapped in its own\n // showLoading()/showReady() pair. Without counting, whichever one finishes\n // first hides the overlay while the others are still fetching \u2014 exactly\n // what made the loading screen vanish early while big asset variants kept\n // downloading in the background.\n let _pendingLoads = 0;\n\n function showLoading() {\n _pendingLoads++;\n pctEl.textContent = '0%';\n pctEl.style.display = '';\n barWrap.style.display = '';\n msgEl.textContent = '';\n msgEl.className = 'hs-msg';\n overlay.style.display = 'flex';\n _loadStart = performance.now();\n }\n function showReady() {\n _pendingLoads = Math.max(0, _pendingLoads - 1);\n if (_pendingLoads === 0) overlay.style.display = 'none';\n }\n function showError(text) {\n pctEl.style.display = 'none';\n barWrap.style.display = 'none';\n msgEl.textContent = text;\n msgEl.className = 'hs-msg hs-err';\n overlay.style.display = 'flex';\n }\n\n overlay.style.display = 'none'; // hidden until first load\n\n // \u2500\u2500 Viewer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const viewer = new Viewer({\n canvas,\n background,\n fov, near, far,\n splatScale,\n autoRotate,\n flipY,\n aaDilation,\n maxPixelRatio: effectiveMaxPixelRatio,\n adaptiveQuality,\n prefetchVariants: prefetchVariants ?? caps.prefetchVariants,\n gpuSort,\n tier,\n onProgress: p => {\n const pct = Math.round(p * 100);\n pctEl.textContent = pct + '%';\n bar.style.width = pct + '%';\n if (_loadStart && p > 0.05) {\n const elapsed = (performance.now() - _loadStart) / 1000;\n const eta = Math.round(elapsed * (1 - p) / p);\n msgEl.textContent = eta > 0 ? '~' + eta + 's' : '';\n }\n if (onProgress) onProgress(p);\n },\n });\n\n // \u2500\u2500 Callout DOM \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Map of id \u2192 { card, dot, line }\n const calloutDivs = {};\n const NS = 'http://www.w3.org/2000/svg';\n\n function buildCallouts(callouts) {\n calloutEl.innerHTML = '';\n for (const key of Object.keys(calloutDivs)) delete calloutDivs[key];\n if (!callouts.length) return;\n\n // SVG overlay for dots and connector lines\n const svg = document.createElementNS(NS, 'svg');\n svg.setAttribute('class', 'hs-lines');\n calloutEl.appendChild(svg);\n\n for (const c of callouts) {\n // Line drawn first (behind dot)\n const line = document.createElementNS(NS, 'line');\n line.setAttribute('class', 'hs-line');\n svg.appendChild(line);\n\n const dot = document.createElementNS(NS, 'circle');\n dot.setAttribute('class', 'hs-dot');\n dot.setAttribute('r', '5');\n svg.appendChild(dot);\n\n // Find user-authored card: look in root container first, then document-wide\n let card = root.querySelector(`.hs-callout[data-id=\"${c.id}\"]`)\n ?? document.querySelector(`.hs-callout[data-id=\"${c.id}\"]`);\n if (card) {\n calloutEl.appendChild(card); // move into overlay for absolute positioning\n } else {\n card = document.createElement('div');\n card.className = 'hs-callout';\n card.dataset.id = c.id;\n calloutEl.appendChild(card);\n }\n\n calloutDivs[c.id] = { card, dot, line };\n }\n }\n\n // Update positions each frame\n viewer.onFrame = () => {\n if (!viewer._animation?.callouts.length) return;\n const projected = viewer.projectCallouts(viewer._animation.callouts);\n for (const { id, visible, x, y } of projected) {\n const entry = calloutDivs[id];\n if (!entry) continue;\n const { card, dot, line } = entry;\n if (visible) {\n const ox = parseFloat(card.dataset.offsetX ?? card.dataset.ox ?? 80);\n const oy = parseFloat(card.dataset.offsetY ?? card.dataset.oy ?? -40);\n const cx = x + ox, cy = y + oy;\n\n dot.setAttribute('cx', x); dot.setAttribute('cy', y);\n line.setAttribute('x1', x); line.setAttribute('y1', y);\n line.setAttribute('x2', cx); line.setAttribute('y2', cy);\n dot.style.display = '';\n line.style.display = '';\n\n card.style.left = cx + 'px';\n card.style.top = cy + 'px';\n card.classList.remove('hs-callout--hidden');\n } else {\n dot.style.display = 'none';\n line.style.display = 'none';\n card.classList.add('hs-callout--hidden');\n }\n }\n };\n\n // \u2500\u2500 Load scene \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // These throw on failure so the boot sequence can catch and show the error.\n // showReady / onLoad are called by boot after ALL parallel loads finish.\n async function load(url) {\n showLoading();\n bar.style.width = '0%';\n try {\n await viewer.load(url);\n showReady();\n } catch (err) {\n const msg = navigator.gpu\n ? err.message\n : 'WebGPU not supported. Use Chrome 113+ or Edge 113+.';\n showError(msg);\n if (onError) onError(err);\n throw err;\n }\n }\n\n async function loadPartsMap(partsMap) {\n showLoading();\n bar.style.width = '0%';\n try {\n await viewer.loadParts(partsMap);\n showReady();\n } catch (err) {\n const msg = navigator.gpu\n ? err.message\n : 'WebGPU not supported. Use Chrome 113+ or Edge 113+.';\n showError(msg);\n if (onError) onError(err);\n throw err;\n }\n }\n\n // \u2500\u2500 Clips (product customization) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Wires a 'click' listener to any page element whose id matches an exported\n // clip's id (\"[productName]-[variant]\" \u2014 see export_holosplat_asset.py /\n // Viewer#playClip). Buttons live in the surrounding page, not inside the\n // player root, so this searches the whole document (same pattern\n // buildCallouts uses below). Clips are fully independent of the main\n // animation, so this just binds whatever's currently in viewer._clips \u2014\n // call again after loadClips() if buttons exist before that resolves.\n function bindClipButtons() {\n const ids = Object.keys(viewer._clips);\n let bound = 0;\n for (const clipId of ids) {\n const btn = document.getElementById(clipId);\n if (!btn || btn.dataset.hsClipBound) continue;\n btn.dataset.hsClipBound = '1';\n btn.addEventListener('click', () => viewer.playClip(clipId));\n bound++;\n }\n if (bound) console.log(`[HoloSplat] ${bound} clip button(s) bound:`, ids);\n }\n\n // Internal: used by the boot sequence's `clips` option \u2014 swallows errors\n // so one bad URL doesn't break Promise.all for the others (same pattern\n // as loadAnim below). Each entry is either a bare url string, or\n // {url, splatsDir, defaults} once a splats path/default variant has been\n // set in the editor \u2014 see holosplat/editor.js's saveAssetsAttr.\n async function loadClips(entry) {\n const { url, splatsDir, defaults } = typeof entry === 'string' ? { url: entry } : entry;\n showLoading();\n try {\n // Resolves once each part's default variant is loaded \u2014 non-default\n // color variants keep fetching in the background after this returns\n // (see Viewer#loadClips), so they don't hold up the boot sequence.\n await viewer.loadClips(url, { splatsDir, defaults, lod: caps.lod });\n bindClipButtons();\n } catch (err) {\n console.error('[HoloSplat] clips failed to load:', err);\n if (onError) onError(err);\n } finally {\n showReady();\n }\n }\n\n // \u2500\u2500 Load animation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n async function loadAnim(url) {\n try {\n const anim = await viewer.loadAnimationUrl(url);\n buildCallouts(anim.callouts);\n console.log(\n `[HoloSplat] animation loaded: ${anim.frameCount} frames @ ${anim.fps}fps, ` +\n `${anim.callouts.length} callout(s):`, anim.callouts.map(c => c.id),\n '| markers:', anim.markers\n );\n return anim;\n } catch (err) {\n console.error('[HoloSplat] animation failed to load:', err);\n if (onError) onError(err);\n // Don't rethrow \u2014 leave the scene visible, just no animation\n }\n }\n\n // \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const api = {\n load,\n loadParts: loadPartsMap,\n loadAnim,\n destroy() {\n viewer.destroy();\n root.innerHTML = '';\n root.classList.remove('hs-player');\n },\n setBackground(bg) { viewer.setBackground(bg); },\n setSplatScale(s) { viewer.setSplatScale(s); },\n setAutoRotate(v) { viewer.setAutoRotate(v); },\n setFlipY(v) { viewer.setFlipY(v); },\n setShDegree(n) { return viewer.setShDegree(n); },\n setAaDilation(v) { viewer.setAaDilation(v); },\n setAnimationPaused(v) { viewer.setAnimationPaused(v); },\n setCameraFree(v) { viewer.setCameraFree(v); },\n setMaskFeather(name, v) { viewer.setMaskFeather(name, v); },\n playClip(clipId) { viewer.playClip(clipId); },\n playVariant(axis, value) { viewer.playVariant(axis, value); },\n playState(axis, value) { viewer.playState(axis, value); },\n // Unlike the internal boot-time loader above, this propagates errors\n // (rejects) so a caller like the editor's asset list can show a\n // per-row error state instead of having failures silently swallowed.\n async loadClips(url, opts) { const ids = await viewer.loadClips(url, opts); bindClipButtons(); return ids; },\n unloadClips(ids) { viewer.unloadClips(ids); },\n getVariants(id) { return viewer.getVariants(id); },\n setVariant(id, name) { return viewer.setVariant(id, name); },\n resetCamera() { viewer.resetCamera(); },\n callout(id) { return calloutDivs[id]?.card ?? null; },\n get camera() { return viewer.camera; },\n get animation() { return viewer._animation; },\n get animationPaused() { return viewer._animPaused; },\n get clips() { return viewer._clips; },\n };\n\n // Register early so the editor script can find the entry even before boot.\n if (!window.__hsPlayers) window.__hsPlayers = [];\n window.__hsPlayers.push({ root, api, viewer });\n\n // \u2500\u2500 Scroll-driven / per-scene playback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Called after animation loads. Installs animTickOverride + scroll listener.\n function setupScrollPlayback(anim) {\n if (!scenes || !anim) return null;\n\n // Prevent browser from restoring scroll position on refresh; always start at top.\n if (history.scrollRestoration) history.scrollRestoration = 'manual';\n window.scrollTo(0, 0);\n\n // Disable scroll anchoring: when content heights use dynamic viewport units\n // (dvh), the browser adjusts scrollY as the mobile address bar animates in/out.\n // That fires spurious scroll events which jitter the animation.\n document.documentElement.style.overflowAnchor = 'none';\n\n // See the pingpong turnaround easing below (animTickOverride) for why\n // these exist \u2014 softens the velocity discontinuity at a pingpong bounce.\n const PINGPONG_EASE_FRAMES = 15; // ease window, in frames, on each side of the bounce\n const PINGPONG_MIN_SPEED = 0.12; // speed floor as a fraction of full speed (0..1) \u2014 keeps the bounce from fully stalling\n\n // Sort markers by frame to derive per-scene frame ranges.\n const markerEntries = Object.entries(anim.markers).sort((a, b) => a[1] - b[1]);\n const sceneList = markerEntries.map(([name, fromFrame], i) => {\n const cfg = scenes[name] ?? {};\n return {\n name,\n fromFrame,\n toFrame: (markerEntries[i + 1]?.[1] ?? anim.frameCount) - 1,\n playback: cfg.playback ?? 'scroll',\n pingpong: cfg.pingpong ?? false,\n playOnce: cfg.playOnce ?? false,\n blendIn: (cfg.blendIn ?? 0) / 100, // fraction of this scene's own container height\n blendOut: (cfg.blendOut ?? 0) / 100,\n done: false, // play-once scenes: true once their first pass completes\n el: cfg.linkedId ? document.getElementById(cfg.linkedId) : null,\n };\n });\n // next: the scene blendOut eases toward \u2014 set after the array exists so\n // each entry can reference its successor.\n sceneList.forEach((s, i) => { s.next = sceneList[i + 1] ?? null; });\n\n // Only install override if there are any non-auto scenes (would change default behaviour).\n const needsOverride = sceneList.some(s => s.playback !== 'auto');\n if (!needsOverride) return null;\n\n // Swipe-to-scroll is the primary mobile interaction in scroll-driven scenes \u2014\n // let single-finger touch pass through to the page instead of orbiting/panning\n // the camera. Two-finger pinch/pan still works. Cleared during freecamera\n // \"explore\" acts (see Viewer#setCameraFree) so touch-orbit works there.\n viewer.camera.allowTouchScroll = true;\n\n // \u2500\u2500 Scroll state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // A scene's zone \"drives\" its frame range over exactly one zone-height of\n // scroll: progress 0 when the zone's top edge reaches the viewport top,\n // progress 1 once it has scrolled fully past (top edge has moved up by\n // its own height). Returns null if the zone hasn't been reached yet.\n //\n // For the last zone on the page (nothing below it to keep scrolling\n // into), \"scrolled fully past\" would require scrollY to exceed the\n // document's max scroll by a full viewport height \u2014 physically\n // unreachable, so progress would cap below 1 and the animation could\n // never finish by scroll alone. Clamp the zone's effective bottom to\n // the max reachable scrollY so progress can still reach 1 there.\n function scrollFrameFor(s) {\n if (!s.el) return null;\n const rect = s.el.getBoundingClientRect();\n if (rect.top > 0) return null;\n const scrollY = window.scrollY;\n const zoneTop = scrollY + rect.top;\n const zoneBottom = zoneTop + Math.max(s.el.offsetHeight, 1);\n const maxScrollY = document.documentElement.scrollHeight - window.innerHeight;\n const effBottom = Math.min(zoneBottom, Math.max(maxScrollY, zoneTop + 1));\n const t = Math.max(0, Math.min(1, (scrollY - zoneTop) / (effBottom - zoneTop)));\n return { t, frame: s.fromFrame + t * (s.toFrame - s.fromFrame) };\n }\n\n // \u2500\u2500 Blend in/out \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Eases the seam where one scene hands off to the next by crossfading\n // actual camera/object POSES, not frame numbers \u2014 interpolating frame\n // indices would scrub through whatever animation lies between the two\n // frames, which isn't a blend, it's a fast preview of the timeline.\n // blendIn/blendOut are 0 by default (no blend, exact old behaviour).\n //\n // blendIn eases away from a pose captured once at scene-entry, since the\n // scene being left may not be sitting at a fixed, re-queryable frame (an\n // auto/pingpong scene's position depends on how far it got before\n // handoff) \u2014 so it must be snapshotted live, in the moment.\n // blendOut, by contrast, blends toward the next scene's start frame,\n // which IS fixed/known in advance, so it's just sampled directly via\n // Animation#getCameraFrame/getObjectFrames' frameOverride \u2014 no capture.\n //\n // Both write into viewer._sceneBlend = { otherEye, otherTarget,\n // otherObjects, bf }, which Viewer#_tick crossfades against its own\n // live-computed pose at weight bf (bf=1 \u2192 fully live, bf=0 \u2192 fully\n // \"other\"). scrollTarget.frame itself stays a plain, unblended frame\n // number throughout \u2014 only the rendered pose is ever blended.\n let blendSceneName = null;\n let blendFromEye = null;\n let blendFromTarget = null;\n let blendFromObjects = null; // { id: { pos, quat } }\n let blendFromVolumes = null; // { name: Float32Array(16) }\n\n function smoothstep01(t) {\n const x = Math.max(0, Math.min(1, t));\n return x * x * (3 - 2 * x);\n }\n function captureCurrentPose() {\n const cam = anim.getCameraFrame();\n blendFromEye = cam.eye;\n blendFromTarget = cam.target;\n blendFromObjects = {};\n for (const o of anim.getObjectFrames()) blendFromObjects[o.id] = { pos: o.pos, quat: o.quat };\n blendFromVolumes = {};\n for (const v of anim.getVolumeFrames()) blendFromVolumes[v.name] = v.matrix;\n }\n\n function updateSceneBlend(s, t) {\n if (blendSceneName !== s.name) {\n blendSceneName = s.name;\n captureCurrentPose();\n }\n if (s.blendIn > 0 && t < s.blendIn) {\n const bf = smoothstep01(t / s.blendIn);\n viewer._sceneBlend = {\n otherEye: blendFromEye, otherTarget: blendFromTarget,\n otherObjects: blendFromObjects, otherVolumes: blendFromVolumes, bf,\n };\n return;\n }\n if (s.blendOut > 0 && s.next && t > 1 - s.blendOut) {\n const rawBf = smoothstep01((t - (1 - s.blendOut)) / s.blendOut);\n const nextCam = anim.getCameraFrame(s.next.fromFrame);\n const nextObjs = {};\n for (const o of anim.getObjectFrames(s.next.fromFrame)) nextObjs[o.id] = { pos: o.pos, quat: o.quat };\n const nextVols = {};\n for (const v of anim.getVolumeFrames(s.next.fromFrame)) nextVols[v.name] = v.matrix;\n viewer._sceneBlend = {\n otherEye: nextCam.eye, otherTarget: nextCam.target,\n otherObjects: nextObjs, otherVolumes: nextVols, bf: 1 - rawBf,\n };\n return;\n }\n viewer._sceneBlend = null;\n }\n\n // scrollTarget holds the scroll scene currently in view and its target frame.\n // Updated by scroll events; kept at last known value when nothing is in view\n // so the frame doesn't snap back on minor oscillations.\n let scrollTarget = null; // { scene, frame } | null\n // Prevent auto\u2192scroll handoff from triggering at page load before any user scroll.\n let hasScrolled = false;\n\n // Frame the auto/pingpong scene was paused at when a scroll scene took over \u2014\n // restored when scrolling back up so it resumes from there.\n let pausedAutoFrame = null;\n\n // Fallback hand-off frame if scroll is reversed before any auto\u2192scroll\n // handoff has occurred (e.g. page loaded already mid-scroll).\n const firstScrollIdx = sceneList.findIndex(s => s.playback === 'scroll');\n const entryScene = firstScrollIdx > 0 ? sceneList[firstScrollIdx - 1] : null;\n const entryFrame = entryScene ? entryScene.toFrame : (sceneList[0]?.fromFrame ?? 0);\n\n function updateScrollTarget() {\n // Zones are reached in document order as the user scrolls down, so the\n // LAST zone that has been reached is the current one \u2014 earlier zones\n // remain non-null (clamped to their toFrame) once scrolled past.\n let found = null;\n for (const s of sceneList) {\n if (s.playback !== 'scroll' || !s.el) continue;\n const r = scrollFrameFor(s);\n if (r !== null) found = { scene: s, t: r.t, frame: r.frame };\n }\n if (found) {\n updateSceneBlend(found.scene, found.t);\n scrollTarget = { scene: found.scene, frame: found.frame };\n return;\n }\n // No scroll zone reached yet. If we were previously tracking one, the user\n // has scrolled back above all of them \u2014 hand control straight back to\n // wherever the auto/pingpong scene was paused (or the entry frame if it\n // never ran) so it can resume from there.\n if (scrollTarget) {\n scrollTarget = null;\n blendSceneName = null;\n viewer._sceneBlend = null;\n anim.seekFrame(pausedAutoFrame ?? entryFrame);\n }\n }\n\n function onScroll() { hasScrolled = true; updateScrollTarget(); }\n window.addEventListener('scroll', onScroll, { passive: true });\n updateScrollTarget();\n\n // \u2500\u2500 Per-frame tick override \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n viewer.animTickOverride = (dt) => {\n const frame = anim.frame;\n let s = null;\n for (const sc of sceneList) {\n // A finished play-once scene sitting at its end frame hands off to the\n // next scene instead of freezing playback forever at the boundary.\n if (sc.playOnce && sc.done && frame >= sc.toFrame - 0.5) continue;\n if (frame <= sc.toFrame + 0.5) { s = sc; break; }\n }\n if (!s) s = sceneList[sceneList.length - 1];\n if (!s) { anim.tick(dt); return; }\n viewer._currentSceneName = s.name;\n\n if (s.playback === 'auto') {\n // A scroll scene is demanding control past this auto scene \u2014 jump\n // anim straight to scrollTarget.frame; viewer._sceneBlend (set by\n // updateSceneBlend above) crossfades the rendered pose from this\n // scene's last frame if the incoming scroll scene has blendIn set \u2014\n // with blendIn at 0 (default) there's no crossfade, same as before\n // blending existed.\n // hasScrolled guard prevents this from firing on page load when scroll=0 but\n // a scroll div is already partially visible in the viewport.\n // playOnce auto scenes (e.g. an intro) are excluded: their playability must\n // not depend on the scrollbar \u2014 they always run to completion uninterrupted.\n if (!s.playOnce && hasScrolled && scrollTarget && scrollTarget.frame > s.toFrame + 0.5) {\n pausedAutoFrame = frame;\n anim.seekFrame(scrollTarget.frame);\n return;\n }\n // Play-once scenes hold their final frame after their first pass \u2014\n // no further looping/pingponging until the page reloads.\n if (s.playOnce && s.done) return;\n // Ease speed down near a pingpong turnaround instead of advancing at\n // full speed right up to the instant direction flips. The camera is\n // typically still moving at a real, non-zero speed when it hits the\n // boundary (verified ~0.12 units/sec in this project's scenes), so an\n // instant reversal is a sharp velocity discontinuity \u2014 visible as a\n // snap/wobble right at the bounce \u2014 even though the underlying\n // keyframed path itself is perfectly smooth. PINGPONG_MIN_SPEED keeps\n // a speed floor so this never fully stalls at the boundary.\n let tickDt = dt;\n if (s.pingpong) {\n const distToBoundary = Math.min(frame - s.fromFrame, s.toFrame - frame);\n const ease = Math.max(PINGPONG_MIN_SPEED, Math.min(1, distToBoundary / PINGPONG_EASE_FRAMES));\n tickDt = dt * ease;\n }\n anim.tick(tickDt);\n const f = anim.frame;\n if (s.pingpong) {\n if (f >= s.toFrame) {\n anim.seekFrame(s.toFrame);\n if (s.playOnce) s.done = true;\n else anim.direction = -1;\n } else if (f <= s.fromFrame) {\n anim.seekFrame(s.fromFrame);\n anim.direction = 1;\n }\n } else {\n if (f < s.fromFrame) anim.seekFrame(s.fromFrame);\n else if (s.playOnce && f >= s.toFrame) {\n anim.seekFrame(s.toFrame);\n s.done = true;\n }\n }\n return;\n }\n\n // Scroll scene: the scroll position is the source of truth, regardless of\n // which scene-slot `s` currently sits in (scrollTarget can race ahead of\n // anim.frame across degenerate/zero-length scenes).\n if (scrollTarget) {\n anim.seekFrame(scrollTarget.frame);\n } else if (!s.el) {\n anim.seekFrame(s.fromFrame);\n }\n };\n\n return () => {\n window.removeEventListener('scroll', onScroll);\n viewer.animTickOverride = null;\n viewer._currentSceneName = null;\n viewer._sceneBlend = null;\n };\n }\n\n // Pre-populate scene configs from opts.scenes\n if (scenes && typeof scenes === 'object') {\n window.__hsSceneConfigs = window.__hsSceneConfigs || {};\n Object.assign(window.__hsSceneConfigs, scenes);\n }\n\n // Pre-populate mask configs from opts.masks and apply feather overrides.\n if (masks && typeof masks === 'object') {\n window.__hsMaskConfigs = window.__hsMaskConfigs || {};\n Object.assign(window.__hsMaskConfigs, masks);\n for (const [name, cfg] of Object.entries(masks)) {\n if (cfg && typeof cfg.feather === 'number') viewer.setMaskFeather(name, cfg.feather);\n }\n }\n\n // Apply SH degree: explicit global sh option takes priority, then max of per-scene values,\n // capped by the device-tier quality preset (e.g. 'low' disables SH entirely).\n const effectiveSh = sh != null ? sh\n : scenes\n ? Object.values(scenes).reduce((m, c) => Math.max(m, c.sh ?? 0), viewer._shDegree)\n : viewer._shDegree;\n const cappedSh = Math.min(effectiveSh, caps.shDegreeCap);\n if (cappedSh !== viewer._shDegree) viewer._shDegree = cappedSh;\n\n // \u2500\u2500 Boot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n viewer.init()\n .then(async () => {\n viewer.start();\n let anim;\n // Clip asset file(s) are fully independent of the main animation/parts\n // load sequence below, so kick them off in parallel regardless of\n // which branch (sequential vs parallel) the rest takes.\n const clipUrls = clips ? (Array.isArray(clips) ? clips : [clips]) : [];\n const clipLoads = Promise.all(clipUrls.map(loadClips));\n if (partsDir && animSrc) {\n // Sequential: load animation first, then derive + load parts from object IDs\n anim = await loadAnim(animSrc);\n if (anim?.objects.length) {\n const dir = partsDir.replace(/\\/?$/, '/');\n let autoPartsMap = Object.fromEntries(\n anim.objects.map(obj => [obj.id, `${dir}${splatNameFromId(obj.id)}${partsExt}`])\n );\n autoPartsMap = await resolvePartsLod(autoPartsMap, caps.lod);\n await loadPartsMap(autoPartsMap);\n }\n } else {\n const loads = [];\n if (parts) loads.push(resolvePartsLod(parts, caps.lod).then(loadPartsMap));\n else if (src) loads.push(resolveLodUrl(src, caps.lod).then(load));\n if (animSrc) loads.push(loadAnim(animSrc).then(a => { anim = a; }));\n await Promise.all(loads);\n }\n await clipLoads;\n // All loads done \u2014 show ready and call onLoad with animation guaranteed available\n showReady();\n onLoad?.();\n setupScrollPlayback(anim ?? viewer._animation);\n })\n .catch(err => {\n // scene/parts errors already showed their own error UI; only handle\n // unexpected failures here (e.g. viewer.init() failing)\n if (!navigator.gpu) showError('WebGPU not supported. Use Chrome 113+ or Edge 113+.');\n });\n\n // Inject overlay when ?hs is in the URL (dev only). The full art-direction\n // editor is desktop-only; on touch/narrow viewports load the lightweight\n // stats overlay instead (see holosplat/stats.js).\n if (typeof location !== 'undefined' &&\n new URLSearchParams(location.search).has('hs') &&\n !document.getElementById('__hs-script')) {\n const isMobile = window.matchMedia('(pointer: coarse)').matches\n || window.matchMedia('(max-width: 768px)').matches\n || /Android|iPhone|iPad|iPod/i.test(navigator.userAgent || '');\n const s = document.createElement('script');\n s.id = '__hs-script';\n s.src = isMobile ? '/holosplat/stats.js' : '/holosplat/editor.js';\n document.head.appendChild(s);\n }\n\n return api;\n}\n\n// \u2500\u2500 Data-attribute auto-init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction autoInit() {\n document.querySelectorAll('[data-holosplat]').forEach(el => {\n if (el._hsPlayer) return;\n const src = el.getAttribute('data-holosplat') || undefined;\n const anim = el.getAttribute('data-holosplat-anim') || undefined;\n const partsDir = el.getAttribute('data-holosplat-parts') || undefined;\n el._hsPlayer = player(el, { src, animation: anim, partsDir });\n });\n}\n\nif (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoInit);\n } else {\n autoInit();\n }\n}\n", "/**\n * HoloSplat Scroll Scene \u2014 scroll-driven animation controller.\n *\n * Binds a HoloSplat player's animation playback to the scroll position of the\n * page, using a declarative HTML structure of acts and holds.\n *\n * \u2500\u2500 HTML structure \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * <div class=\"hs-scene\">\n *\n * <!-- Sticky canvas: stays in view while scrolling through the track -->\n * <div class=\"hs-stage\">\n * <!-- player() targets this element -->\n * </div>\n *\n * <!-- Scroll track: normal-flow divs that create scroll height.\n * Uses margin-top:-100vh to visually overlap the sticky stage. -->\n * <div class=\"hs-track\">\n *\n * <!-- Act: maps a Blender marker range to scroll distance.\n * The larger the height, the slower / more scroll needed to pass\n * through the frame range.\n *\n * data-from \u2014 start marker name OR frame number (default: 0)\n * data-to \u2014 end marker name OR frame number (default: last frame)\n * data-loop \u2014 repeat the frame range N times as progress goes 0\u21921.\n * \"true\" / \"\" = 1\u00D7, \"3\" = 3\u00D7.\n * Reverse loops (data-from > data-to) are supported.\n * -->\n * <div class=\"hs-act\"\n * data-from=\"intro\"\n * data-to=\"desk_reveal\"\n * style=\"height:300vh\">\n *\n * <!-- Caption: fades in at data-at progress (0..1) through this act.\n * data-at defaults to 0 (immediately visible). -->\n * <div class=\"hs-caption\" data-at=\"0.3\">Introducing the scene</div>\n *\n * </div>\n *\n * <!-- Hold: freeze at one frame while user scrolls past (reading time). -->\n * <div class=\"hs-hold\"\n * data-frame=\"desk_reveal\"\n * style=\"height:120vh\">\n * <div class=\"hs-caption\">Notice the details</div>\n * </div>\n *\n * <!-- Reverse: data-from > data-to plays the range backwards. -->\n * <div class=\"hs-act\"\n * data-from=\"desk_reveal\"\n * data-to=\"intro\"\n * style=\"height:200vh\">\n * </div>\n *\n * <!-- Loop 3\u00D7: plays intro\u2192zoom three times as user scrolls through. -->\n * <div class=\"hs-act\"\n * data-from=\"intro\"\n * data-to=\"zoom\"\n * data-loop=\"3\"\n * style=\"height:300vh\">\n * </div>\n *\n * </div>\n * </div>\n *\n * \u2500\u2500 JS usage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * // Programmatic:\n * const p = HoloSplat.player('.hs-stage', { src: '...', animation: '...' });\n * HoloSplat.scrollScene(document.querySelector('.hs-scene'), p);\n *\n * // Data-attribute auto-init (no JS needed):\n * // Place data-holosplat / data-holosplat-anim on the .hs-stage; the scroll\n * // scene is wired up automatically after the player initialises.\n *\n * \u2500\u2500 Marker names \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * data-from / data-to / data-frame accept either:\n * \u2022 A Blender timeline marker name exported in the animation JSON\n * (e.g. \"intro\", \"desk_reveal\"). Marker names are prefixed with\n * \"data-marker-\" automatically in Blender by adding them as\n * timeline markers named exactly as you want to reference them here.\n * \u2022 A bare frame number (e.g. \"0\", \"72\").\n *\n * If a name is not found in the markers dict a warning is logged and 0 is used.\n */\n\n// \u2500\u2500 CSS (injected once per page) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst SCROLL_CSS = `\n.hs-scene {\n position: relative;\n width: 100%;\n}\n/* .hs-stage also carries .hs-player (position:relative) from player.js.\n !important ensures sticky wins regardless of injection order.\n 100vw fills the full viewport regardless of parent padding/margin. */\n.hs-stage {\n position: sticky !important;\n top: 0;\n left: 0;\n width: 100vw !important;\n height: 100vh !important;\n z-index: 1;\n overflow: hidden;\n}\n.hs-stage canvas {\n position: absolute !important;\n inset: 0 !important;\n width: 100% !important;\n height: 100% !important;\n display: block;\n}\n.hs-track {\n /* Overlap the sticky stage so acts start scrolling at the same time the\n canvas appears \u2014 not after it. */\n margin-top: -100vh;\n position: relative;\n /* Clicks pass through to the canvas; restore per-element as needed. */\n pointer-events: none;\n z-index: 2;\n}\n.hs-act,\n.hs-hold {\n position: relative;\n}\n.hs-caption {\n pointer-events: auto;\n}\n.hs-caption--hidden {\n opacity: 0;\n pointer-events: none;\n}\n`;\n\nlet sceneCssInjected = false;\nfunction injectScrollCss() {\n if (sceneCssInjected || typeof document === 'undefined') return;\n sceneCssInjected = true;\n const s = document.createElement('style');\n s.textContent = SCROLL_CSS;\n document.head.appendChild(s);\n}\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Resolve a marker name or raw frame string to a frame number.\n * Falls back to 0 with a warning if the name is unknown.\n */\nfunction resolveFrame(value, markers, fallback = 0) {\n if (value === undefined || value === null || value === '') return fallback;\n const str = String(value).trim();\n if (Object.prototype.hasOwnProperty.call(markers, str)) return markers[str];\n const n = parseFloat(str);\n if (!isNaN(n)) return n;\n console.warn(`[HoloSplat] scrollScene: unknown marker \"${str}\" \u2014 using ${fallback}`);\n return fallback;\n}\n\n/**\n * Parse a single act/hold element into a descriptor object.\n * @param {HTMLElement} el\n * @param {object} markers { name: frame }\n * @param {number} lastFrame fallback for data-to when omitted\n */\nfunction parseAct(el, markers, lastFrame) {\n const isHold = el.classList.contains('hs-hold');\n\n if (isHold) {\n return {\n el,\n type: 'hold',\n frame: resolveFrame(el.dataset.frame, markers, 0),\n captions: parseCaptions(el),\n };\n }\n\n const fromAttr = el.dataset.from ?? '';\n const isPingpong = fromAttr === 'pingpong-start';\n const isFreecamera = fromAttr === 'freecamera-start';\n\n const from = resolveFrame(fromAttr, markers, 0);\n const to = resolveFrame(el.dataset.to, markers, lastFrame);\n\n // data-loop: number of times to cycle the frame range as progress 0\u21921.\n // \"true\" / \"\" = 1 (seamless, one full cycle); \"3\" = 3 repetitions.\n let loop = 0;\n if (el.dataset.loop !== undefined) {\n const v = el.dataset.loop.trim();\n loop = (v === '' || v === 'true') ? 1 : (Math.max(1, parseFloat(v) || 1));\n }\n\n return {\n el,\n type: isPingpong ? 'pingpong' : isFreecamera ? 'freecamera' : 'act',\n from,\n to,\n loop,\n captions: parseCaptions(el),\n };\n}\n\nfunction parseCaptions(el) {\n return [...el.querySelectorAll('.hs-caption')].map(c => ({\n el: c,\n at: parseFloat(c.dataset.at ?? '0'),\n }));\n}\n\n/**\n * Compute the target frame for a given 0..1 progress through an act.\n */\nfunction actFrame(act, progress) {\n if (act.type === 'hold') return act.frame;\n if (act.type === 'pingpong' ) return progress >= 1 ? act.to : act.from;\n if (act.type === 'freecamera' ) return progress >= 1 ? act.to : act.from;\n\n const { from, to, loop } = act;\n const range = to - from;\n if (range === 0) return from;\n\n let p = progress;\n if (loop > 0) {\n // Tile the range `loop` times; modulo keeps it within one cycle.\n p = (progress * loop) % 1;\n }\n\n return from + p * range;\n}\n\n/**\n * Returns the scroll progress (0..1) of an element relative to the top of\n * the viewport. 0 = element top is at viewport top;\n * 1 = element bottom is at viewport top (fully scrolled past).\n *\n * For the last act on the page, \"fully scrolled past\" would need scrollY to\n * exceed the document's max scroll by a full viewport height \u2014 unreachable \u2014\n * so progress would cap below 1. Clamp the act's effective bottom to the max\n * reachable scrollY so the last act can still reach progress 1.\n */\nfunction getProgress(el) {\n const rect = el.getBoundingClientRect();\n if (rect.top > 0) return 0;\n const scrollY = window.scrollY;\n const top = scrollY + rect.top;\n const h = el.offsetHeight || 1;\n const bottom = top + h;\n const maxScrollY = document.documentElement.scrollHeight - window.innerHeight;\n const effBottom = Math.min(bottom, Math.max(maxScrollY, top + 1));\n return Math.max(0, Math.min(1, (scrollY - top) / (effBottom - top)));\n}\n\n// \u2500\u2500 Pingpong transition helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction lerp(a, b, t) { return a + (b - a) * t; }\nfunction smoothstep(a, b, x) {\n const t = Math.max(0, Math.min(1, (x - a) / (b - a)));\n return t * t * (3 - 2 * t);\n}\n\n// \u2500\u2500 scrollScene \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Attach scroll-driven animation control to an .hs-scene element.\n *\n * @param {HTMLElement} sceneEl The .hs-scene container element.\n * @param {object} playerInstance A HoloSplat player() return value.\n * @returns {{ rebuild: function, destroy: function }}\n */\nexport function scrollScene(sceneEl, playerInstance, opts = {}) {\n injectScrollCss();\n\n const track = sceneEl.querySelector('.hs-track');\n if (!track) {\n console.warn('[HoloSplat] scrollScene: .hs-track not found inside scene');\n return { rebuild() {}, destroy() {} };\n }\n\n let acts = [];\n\n // \u2500\u2500 Pingpong playback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Autonomous time-driven pingpong between two frame bounds.\n // Active while the user is scrolled into a pingpong act; stops automatically\n // when they scroll out, handing control back to the scroll-seek path.\n\n let ppActive = false;\n let ppDir = 1;\n let ppFrame = 0;\n let ppStart = 0;\n let ppEnd = 0;\n let ppLastTime = null;\n let ppRafId = null;\n\n // Exit transition: after leaving a pingpong zone, ease from the captured\n // pingpong frame to the scroll-driven target over the first portion of the\n // next act. PP_TRANSITION_ZONE is that fraction of the next act's scroll range.\n const PP_TRANSITION_ZONE = opts.pingpongTransition ?? 0.25;\n let ppTransitioning = false;\n let ppCapturedFrame = 0;\n\n let fcActive = false; // freecamera section currently active\n\n // fromEnd=true when entering from below (scrolling up): start at ppEnd, go backward.\n function startPingpong(startFrame, endFrame, fromEnd = false) {\n if (ppActive) return;\n ppTransitioning = false;\n ppActive = true;\n ppStart = startFrame;\n ppEnd = endFrame;\n ppDir = fromEnd ? -1 : 1;\n ppFrame = fromEnd ? endFrame : startFrame;\n ppLastTime = null;\n ppRafId = requestAnimationFrame(tickPingpong);\n }\n\n function stopPingpong() {\n ppActive = false;\n if (ppRafId !== null) { cancelAnimationFrame(ppRafId); ppRafId = null; }\n }\n\n function tickPingpong(now) {\n if (!ppActive) return;\n ppRafId = requestAnimationFrame(tickPingpong);\n if (ppLastTime === null) { ppLastTime = now; return; }\n\n const dt = (now - ppLastTime) / 1000;\n ppLastTime = now;\n\n const fps = playerInstance.animation?.fps ?? 24;\n ppFrame += ppDir * fps * dt;\n if (ppFrame >= ppEnd) { ppFrame = ppEnd; ppDir = -1; }\n if (ppFrame <= ppStart) { ppFrame = ppStart; ppDir = 1; }\n\n playerInstance.animation.seekFrame(ppFrame);\n }\n\n // \u2500\u2500 Act building \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n function buildActs() {\n const anim = playerInstance.animation;\n const markers = anim?.markers ?? {};\n const lastFrame = anim ? anim.frameCount - 1 : 0;\n acts = [...track.children].map(el => parseAct(el, markers, lastFrame));\n // Hand playback control to scroll \u2014 stop the auto-play loop.\n if (anim) playerInstance.setAnimationPaused(true);\n }\n\n // \u2500\u2500 Scroll update \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n function update() {\n if (!playerInstance.animation || !acts.length) return;\n\n // Walk acts in order; find the one currently in progress.\n // After a fully-scrolled act, keep its frame as the candidate so the last\n // state holds until the next act starts.\n let frame = actFrame(acts[0], 0);\n let activeAct = acts[0];\n let activeProgress = 0;\n\n for (const act of acts) {\n const p = getProgress(act.el);\n if (p <= 0) break; // haven't reached this act yet\n\n frame = actFrame(act, p);\n activeAct = act;\n activeProgress = p;\n\n if (p < 1) break; // this is the currently-active act\n // p === 1: fully scrolled past \u2014 continue to check next act\n }\n\n const inPingpong = activeAct.type === 'pingpong' && activeProgress > 0 && activeProgress < 1;\n const inFreecamera = activeAct.type === 'freecamera' && activeProgress > 0 && activeProgress < 1;\n\n // \u2500\u2500 Pingpong \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (inPingpong) {\n ppTransitioning = false;\n if (!ppActive) startPingpong(activeAct.from, activeAct.to, activeProgress > 0.5);\n } else {\n if (ppActive) { ppCapturedFrame = ppFrame; ppTransitioning = true; }\n stopPingpong();\n }\n\n // \u2500\u2500 Freecamera \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (inFreecamera) {\n if (!fcActive) {\n fcActive = true;\n playerInstance.setCameraFree(true);\n playerInstance.camera.enabled = true;\n playerInstance.camera.panEnabled = false;\n }\n } else {\n if (fcActive) {\n fcActive = false;\n playerInstance.setCameraFree(false);\n playerInstance.camera.enabled = false;\n playerInstance.camera.panEnabled = true;\n }\n }\n\n // \u2500\u2500 Seek (skip when pingpong or freecamera own the camera) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (!inPingpong && !inFreecamera) {\n if (ppTransitioning) {\n const t = smoothstep(0, PP_TRANSITION_ZONE, activeProgress);\n if (t >= 1) ppTransitioning = false;\n playerInstance.animation.seekFrame(lerp(ppCapturedFrame, frame, t));\n } else {\n playerInstance.animation.seekFrame(frame);\n }\n }\n\n // \u2500\u2500 Debug: log on act change \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const activeId = activeAct?.el?.id ?? '\u2014';\n if (update._activeId !== activeId) {\n update._activeId = activeId;\n const act = activeAct;\n const range = act.type === 'hold'\n ? `hold @ frame ${act.frame}`\n : act.type === 'pingpong'\n ? `pingpong [${act.from} \u2194 ${act.to}]`\n : `act [${act.from} \u2192 ${act.to}]`;\n console.log(`[HoloSplat] active: ${activeId} (${range}) | current frame: ${frame.toFixed(1)}`);\n }\n\n // \u2500\u2500 Caption visibility \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // All acts: hide captions of acts we haven't reached or have left.\n for (const act of acts) {\n if (!act.captions.length) continue;\n const isActive = act === activeAct;\n for (const cap of act.captions) {\n const visible = isActive && activeProgress >= cap.at;\n cap.el.classList.toggle('hs-caption--hidden', !visible);\n }\n }\n }\n\n // \u2500\u2500 Throttle with rAF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n let rafPending = false;\n function onScroll() {\n if (rafPending) return;\n rafPending = true;\n requestAnimationFrame(() => { rafPending = false; update(); });\n }\n\n // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n window.addEventListener('scroll', onScroll, { passive: true });\n\n // Wait for the animation to load (it's fetched async after player init),\n // then immediately pause auto-play and seek to the correct scroll position.\n // Using rAF so we don't block and the check is cheap.\n (function waitForAnim() {\n if (!playerInstance.animation) { requestAnimationFrame(waitForAnim); return; }\n // Disable orbit/wheel/touch controls so they don't swallow scroll events.\n playerInstance.camera.enabled = false;\n // Let the caller set data-from/to/frame attributes BEFORE we parse them.\n if (opts.onReady) opts.onReady(playerInstance.animation);\n buildActs();\n update();\n })();\n\n // \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n return {\n /**\n * Re-read act elements from the DOM and re-resolve marker names.\n * Call after programmatic DOM changes to .hs-track.\n */\n rebuild() {\n stopPingpong(); ppTransitioning = false;\n if (fcActive) { fcActive = false; playerInstance.setCameraFree(false); playerInstance.camera.panEnabled = true; }\n buildActs(); update();\n },\n\n destroy() {\n stopPingpong(); ppTransitioning = false;\n if (fcActive) { fcActive = false; playerInstance.setCameraFree(false); playerInstance.camera.panEnabled = true; }\n window.removeEventListener('scroll', onScroll);\n playerInstance.camera.enabled = true;\n playerInstance.setAnimationPaused(false);\n },\n };\n}\n\n// \u2500\u2500 Data-attribute auto-init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Looks for .hs-scene elements that contain a .hs-stage with data-holosplat.\n// The player() auto-init in player.js handles creating the player on .hs-stage;\n// we wait for DOMContentLoaded (same event) and wire up scroll scenes after.\n\nfunction autoInitScrollScenes() {\n document.querySelectorAll('.hs-scene').forEach(sceneEl => {\n if (sceneEl._hsScroll) return;\n\n const stage = sceneEl.querySelector('.hs-stage');\n if (!stage) return;\n\n // Ensure the stage has a player (created by player.js autoInit).\n if (!stage._hsPlayer) return;\n\n sceneEl._hsScroll = scrollScene(sceneEl, stage._hsPlayer);\n });\n}\n\nif (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoInitScrollScenes);\n } else {\n autoInitScrollScenes();\n }\n}\n", "/**\n * Splat pruning \u2014 removes low-impact Gaussians to cut file size and render\n * cost (sort + rasterization both scale with splat count).\n *\n * Input/output: canonical Float32Array (16 floats/Gaussian), see compress.js\n * for the layout.\n *\n * Two independent stages:\n * 1. Hard filter \u2014 drop splats that are nearly invisible regardless of\n * viewing distance: `alpha < minAlpha` or every scale axis < `minScale`.\n * 2. Importance subsample \u2014 of what's left, keep the top `keepFraction`\n * ranked by `alpha * cbrt(volume)` (small/transparent splats go first).\n *\n * Usage:\n * import { pruneGaussians, generateLods, PRUNE_PRESETS } from 'holosplat';\n * const { data, count } = pruneGaussians(srcData, srcCount, PRUNE_PRESETS.balanced);\n */\n\n/** Relative visual contribution of a splat \u2014 bigger & more opaque = more important. */\nfunction importance(data, i) {\n const j = i * 16;\n const alpha = data[j + 7];\n const sx = data[j + 8], sy = data[j + 9], sz = data[j + 10];\n return alpha * Math.cbrt(Math.max(0, sx * sy * sz));\n}\n\n/**\n * @param {Float32Array} data\n * @param {number} count\n * @param {object} [opts]\n * @param {number} [opts.minAlpha=0] drop splats with alpha below this (0-1)\n * @param {number} [opts.minScale=0] drop splats whose largest scale axis is below this (world units)\n * @param {number} [opts.keepFraction=1] keep this fraction of the remaining splats, ranked by importance\n * @returns {{data: Float32Array, count: number}}\n */\nexport function pruneGaussians(data, count, opts = {}) {\n const { minAlpha = 0, minScale = 0, keepFraction = 1 } = opts;\n\n let kept = [];\n for (let i = 0; i < count; i++) {\n const j = i * 16;\n if (data[j + 7] < minAlpha) continue;\n if (Math.max(data[j + 8], data[j + 9], data[j + 10]) < minScale) continue;\n kept.push(i);\n }\n\n if (keepFraction < 1 && kept.length > 0) {\n kept.sort((a, b) => importance(data, b) - importance(data, a));\n kept.length = Math.max(1, Math.round(kept.length * keepFraction));\n }\n\n const out = new Float32Array(kept.length * 16);\n for (let k = 0; k < kept.length; k++) out.set(data.subarray(kept[k] * 16, kept[k] * 16 + 16), k * 16);\n return { data: out, count: kept.length };\n}\n\n/**\n * Generates a series of LOD tiers by progressively keeping fewer, more\n * important splats \u2014 each tier's kept set is a prefix of the previous\n * tier's (after the hard filter), so tiers are strictly nested.\n *\n * @param {Float32Array} data\n * @param {number} count\n * @param {object} [opts]\n * @param {number} [opts.minAlpha=0] hard filter, applied once before ranking\n * @param {number} [opts.minScale=0]\n * @param {number[]} [opts.fractions=[1, 0.8, 0.6, 0.4]] keepFraction per tier\n * @returns {{data: Float32Array, count: number, fraction: number}[]}\n */\nexport function generateLods(data, count, opts = {}) {\n const { minAlpha = 0, minScale = 0, fractions = [1, 0.8, 0.6, 0.4] } = opts;\n\n let kept = [];\n for (let i = 0; i < count; i++) {\n const j = i * 16;\n if (data[j + 7] < minAlpha) continue;\n if (Math.max(data[j + 8], data[j + 9], data[j + 10]) < minScale) continue;\n kept.push(i);\n }\n kept.sort((a, b) => importance(data, b) - importance(data, a));\n\n return fractions.map(fraction => {\n const n = Math.max(1, Math.round(kept.length * fraction));\n const out = new Float32Array(n * 16);\n for (let k = 0; k < n; k++) out.set(data.subarray(kept[k] * 16, kept[k] * 16 + 16), k * 16);\n return { data: out, count: n, fraction };\n });\n}\n\n/** Named pruning presets for the prune/LOD UI tools. */\nexport const PRUNE_PRESETS = {\n light: { minAlpha: 0.004, minScale: 0.0001, keepFraction: 1 },\n balanced: { minAlpha: 0.02, minScale: 0.0005, keepFraction: 0.7 },\n aggressive: { minAlpha: 0.05, minScale: 0.001, keepFraction: 0.4 },\n mobile: { minAlpha: 0.05, minScale: 0.001, keepFraction: 0.25 },\n};\n", "/**\n * HoloSplat \u2013 WebGPU Gaussian Splat viewer library\n *\n * \u2500\u2500 Quick start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * ESM (bundler / native module):\n * import { create } from 'holosplat';\n *\n * IIFE (Webflow / plain script tag):\n * <script src=\"holosplat.iife.js\"></script>\n * HoloSplat.create({ ... });\n *\n * \u2500\u2500 create(options) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * canvas {string|HTMLCanvasElement} \u2013 required; CSS selector or element\n * src {string} \u2013 URL of .splat or .ply file\n * background {string|number[]} \u2013 '#rrggbb', '#rrggbbaa', 'transparent',\n * or [r,g,b,a] (0\u20131). Default '#000000'\n * fov {number} \u2013 vertical field of view, degrees. Default 60\n * near {number} \u2013 near clip plane. Default 0.1\n * far {number} \u2013 far clip plane. Default 2000\n * splatScale {number} \u2013 global scale multiplier. Default 1\n * autoRotate {boolean} \u2013 slow continuous orbit. Default false\n * quality {'auto'|'low'|'medium'|'high'} \u2013 device-tier presets for\n * maxPixelRatio/shDegree caps and LOD\n * file selection (see device-tier.js).\n * Default 'auto' (detects the device).\n * onLoad {function} \u2013 called after scene is fully loaded\n * onProgress {function(0..1)} \u2013 called during fetch with progress 0\u20131\n * onError {function(Error)} \u2013 called on any error\n *\n * \u2500\u2500 Returns \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * A controller object with:\n * destroy() \u2013 stop rendering, release GPU resources\n * setBackground(bg) \u2013 change background colour\n * setSplatScale(s) \u2013 change global splat scale\n * setAutoRotate(bool) \u2013 toggle auto-rotate\n * resetCamera() \u2013 fit camera back to scene\n * getVariants(id?) \u2013 list color/material variant names for a part (or the\n * whole scene if `id` is omitted); [] if it has none\n * setVariant(id?, name) \u2013 switch the active color/material variant (async \u2014\n * instant for packed .spzv parts, fetches on first use\n * for per-file variants; see Viewer#setVariant)\n * camera \u2013 OrbitCamera instance (for direct manipulation)\n */\nimport { Viewer } from './viewer.js';\nimport { player } from './player.js';\nimport { Animation, loadAnimation, splatNameFromId } from './animation.js';\nimport { scrollScene } from './scroll-scene.js';\nimport { compressToSpz, encodeSpz } from './compress.js';\nimport { parseSplat } from './loaders/splat-loader.js';\nimport { parsePly } from './loaders/ply-loader.js';\nimport { parseSpz, parseSpzGzip } from './loaders/spz-loader.js';\nimport { parseSpzv, parseSpzvGzip } from './loaders/spzv-loader.js';\nimport { encodeSpzv, compressVariantsToSpzv } from './variant-pack.js';\nimport { pruneGaussians, generateLods, PRUNE_PRESETS } from './optimize.js';\nimport { detectDeviceTier, qualityForTier, resolveLodUrl, resolvePartsLod } from './device-tier.js';\n\nexport async function create(options = {}) {\n const { onLoad, onError, src, parts, quality = 'auto', ...viewerOpts } = options;\n\n const tier = quality === 'auto' ? detectDeviceTier() : quality;\n const caps = qualityForTier(tier);\n\n const maxPixelRatio = viewerOpts.maxPixelRatio ?? caps.maxPixelRatio;\n const shDegree = Math.min(viewerOpts.shDegree ?? 0, caps.shDegreeCap);\n const prefetchVariants = viewerOpts.prefetchVariants ?? caps.prefetchVariants;\n\n const viewer = new Viewer({ ...viewerOpts, maxPixelRatio, shDegree, prefetchVariants, tier });\n\n const noop = {\n destroy() {}, setBackground() {}, setSplatScale() {}, setGamma() {}, setAaDilation() {},\n setAutoRotate() {}, setFlipY() {}, resetCamera() {}, focusCamera() {},\n getVariants() { return []; }, async setVariant() { return false; },\n getStats() { return null; }, getSplatDebug() { return null; }, camera: null,\n setDebugIndex() {}, async readDebug() { return null; },\n };\n\n try {\n await viewer.init();\n if (parts) await viewer.loadParts(await resolvePartsLod(parts, caps.lod));\n else if (src) await viewer.load(await resolveLodUrl(src, caps.lod));\n } catch (err) {\n viewer.destroy();\n if (onError) { onError(err); return noop; }\n throw err;\n }\n\n viewer.start();\n onLoad?.();\n\n return {\n destroy() { viewer.destroy(); },\n setBackground(bg) { viewer.setBackground(bg); },\n setSplatScale(s) { viewer.setSplatScale(s); },\n setGamma(g) { viewer.setGamma(g); },\n setAaDilation(v) { viewer.setAaDilation(v); },\n setAutoRotate(v) { viewer.setAutoRotate(v); },\n setFlipY(v) { viewer.setFlipY(v); },\n resetCamera() { viewer.resetCamera(); },\n focusCamera() { viewer.focusCamera(); },\n getVariants(id) { return viewer.getVariants(id); },\n getStats() { return viewer.getStats(); },\n getSplatDebug(i) { return viewer.getSplatDebug(i); },\n setDebugIndex(i) { viewer.setDebugIndex(i); },\n readDebug() { return viewer.readDebug(); },\n setVariant(id, name) { return viewer.setVariant(id, name); }, // async\n get camera() { return viewer.camera; },\n };\n}\n\n// Also expose Viewer class, animation, scroll scene, parsers, compression/pruning,\n// and device-tier utilities\nexport {\n Viewer, player, scrollScene, Animation, loadAnimation, splatNameFromId,\n compressToSpz, encodeSpz, parseSplat, parsePly, parseSpz, parseSpzGzip,\n parseSpzv, parseSpzvGzip, encodeSpzv, compressVariantsToSpzv,\n pruneGaussians, generateLods, PRUNE_PRESETS,\n detectDeviceTier, qualityForTier, resolveLodUrl, resolvePartsLod,\n};\n"],
|
|
5
|
+
"mappings": "AAiCO,IAAMA,GAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECEzB,IAAMC,GAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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",
|
|
6
|
+
"names": ["SHADER", "SORT_SHADER", "numWorkgroups", "count", "numChunks", "BLIT_SHADER", "U_VIEW", "U_PROJ", "U_VIEWPORT", "U_FOCAL", "U_PARAMS", "U_SH_PARAMS", "U_AA_DILATION", "U_SIZE", "Renderer", "canvas", "background", "parseBackground", "adapter", "event", "info", "identity", "data", "count", "u32Buf", "nwg", "numWorkgroups", "nch", "numChunks", "view", "proj", "width", "height", "focal", "near", "radiusCap", "u", "sortedIndices", "n", "transforms", "flat", "i", "masks", "volumes", "arrBuf", "u32v", "f32v", "f32Base", "vol", "inv", "invertMat4", "vertexOffset", "s", "g", "shData", "numSHBases", "shChunk", "gaussianOffset", "deg", "v", "encoder", "tz", "a", "b", "c", "det", "lambda1", "lambda2", "extent", "sizeFade", "nearFade", "aaFactor", "finalAlpha", "r", "bl", "worldX", "bg", "pass", "splatPass", "blitPass", "w", "h", "SHADER", "module", "size", "usage", "SORT_SHADER", "make", "entryPoint", "entries", "buildGroup", "pipeline", "bindings", "binding", "p", "slot", "o", "histPass", "scanReducePass", "scanGlobalPass", "scanCombinePass", "scatterPass", "byteSize", "staging", "result", "a00", "a01", "a02", "a03", "a10", "a11", "a12", "a13", "a20", "a21", "a22", "a23", "a30", "a31", "a32", "a33", "b00", "b01", "b02", "b03", "b04", "b05", "b06", "b07", "b08", "b09", "b10", "b11", "d", "out", "hex", "OrbitCamera", "fov", "near", "far", "v", "canvas", "overCanvas", "cx", "cy", "e", "c", "dx", "dy", "isPanButton", "factor", "t", "prev", "curr", "prevDist", "currDist", "prevCx", "prevCy", "currCx", "currCy", "dTheta", "dPhi", "hDeg", "vDeg", "r", "baseRadius", "rangePct", "speed", "right", "up", "ddx", "ddy", "ddz", "ox", "oy", "oz", "dist", "f", "width", "height", "eye", "lookAt", "perspective", "cp", "sp", "ct", "st", "target", "dz", "positions", "numSplats", "minX", "minY", "minZ", "maxX", "maxY", "maxZ", "i", "j", "x", "y", "z", "extent", "center", "out", "ex", "ey", "ez", "cz", "ux", "uy", "uz", "zx", "zy", "zz", "zl", "xx", "xy", "xz", "xl", "yx", "yy", "yz", "fovY", "aspect", "nf", "SORT_FN", "WORKER_SRC", "createSyncSorter", "maxN", "keys0", "keys1", "idx0", "idx1", "counts", "pfx", "sort", "depths", "N", "gIndex", "dv", "i", "di", "pass", "shift", "b", "createSorter", "worker", "blob", "url", "lastOrder", "busy", "queued", "e", "transfer", "copy", "gCopy", "_", "fetchWithProgress", "url", "onProgress", "res", "total", "reader", "chunks", "loaded", "done", "value", "combined", "offset", "chunk", "loadSplat", "url", "onProgress", "buffer", "fetchWithProgress", "parseSplat", "N", "src", "data", "i", "s", "d", "qw", "qx", "qy", "qz", "ql", "SH_C0", "loadPly", "url", "onProgress", "shDegree", "buffer", "fetchWithProgress", "parsePly", "bytes", "eoh", "findEndHeader", "numVertices", "propMap", "stride", "hasColor", "parseHeaderText", "src", "data", "shData", "numSHBases", "decodePlyVertices", "openPlyStream", "onHeaderProgress", "res", "contentLength", "reader", "buf", "loaded", "done", "value", "next", "info", "pending", "numSHBasesHdr", "maxFileDegHdr", "effectiveDegHdr", "streamSHBases", "onChunk", "p", "d", "v", "next2", "nVerts", "usedBytes", "count", "maxFileDeg", "effectiveDeg", "numBases", "hasSH", "rSH", "gSH", "bSH", "G", "B", "b", "i", "base", "readProp", "SH_C0", "sBase", "o", "sigmoid", "rw", "rx", "ry", "rz", "rl", "tag", "outer", "j", "end", "text", "lines", "inVertex", "properties", "raw", "parts", "sizeOf", "required", "r", "type", "view", "prop", "off", "x", "MAGIC", "SQRT2INV", "loadSpz", "url", "onProgress", "shDegree", "compressed", "fetchWithProgress", "buffer", "decompressGzip", "parseSpz", "parseSpzGzip", "PARSE_CHUNK", "yieldToMain", "resolve", "view", "magic", "version", "numPoints", "fileShDegree", "fractionalBits", "posDiv", "rotSize", "degToBases", "deg", "fileNumSHBases", "effectiveDeg", "numSHBases", "hasSH", "offPos", "offAlpha", "offColor", "offScale", "offRot", "offSH", "data", "shData", "i", "d", "pb", "readInt24", "cb", "sb", "rb", "qx", "qy", "qz", "qw", "u32", "idx", "s", "a", "signExtend10", "b_", "c", "d_", "ql", "fb", "db", "k", "decodeSH", "offset", "lo", "mi", "hi", "v", "decodeScale", "byte", "decodeQuat", "b", "len", "stream", "writer", "compressToSpz", "data", "count", "opts", "raw", "encodeSpz", "compressGzip", "fractionalBits", "maxAbsPos", "i", "INT24_MAX", "shData", "numSHBases", "shDegree", "shBytesPerPoint", "HEADER", "total", "buf", "view", "u8", "offPos", "offAlpha", "offColor", "offScale", "offRot", "offSH", "posScale", "d", "writeInt24", "clampU8", "encodeScale", "encodeQuat", "sBase", "dBase", "k", "encodeSH", "offset", "value", "v", "linear", "x", "qx", "qy", "qz", "qw", "len", "q", "maxIdx", "j", "sign", "others", "S", "encode10", "a", "b", "c", "stream", "writer", "SPZV_MAGIC", "SPZV_VERSION", "SPZV_NAME_BYTES", "encodeSpzv", "variants", "count", "opts", "geom", "fractionalBits", "maxAbsPos", "i", "INT24_MAX", "numVariants", "HEADER", "NAMES", "GEOM", "PALETTE", "total", "buf", "view", "u8", "off", "encoder", "v", "bytes", "offPos", "offScale", "offRot", "posScale", "d", "writeInt24", "encodeScale", "encodeQuat", "offAlpha", "offColor", "clampU8", "compressVariantsToSpzv", "raw", "compressGzip", "PARSE_CHUNK", "yieldToMain", "resolve", "loadSpzv", "url", "onProgress", "compressed", "fetchWithProgress", "buffer", "decompressGzip", "parseSpzv", "parseSpzvGzip", "view", "u8", "magic", "version", "count", "numVariants", "fractionalBits", "SPZV_MAGIC", "off", "decoder", "names", "v", "bytes", "SPZV_NAME_BYTES", "nul", "offPos", "offScale", "offRot", "posDiv", "data", "i", "d", "pb", "readInt24", "sb", "decodeScale", "qx", "qy", "qz", "qw", "decodeQuat", "variants", "offAlpha", "offColor", "palette", "p", "slerpQuat", "ax", "ay", "az", "aw", "bx", "by", "bz", "bw", "t", "dot", "x", "z", "w", "n", "theta0", "theta", "st0", "st", "sa", "sb", "splatNameFromId", "id", "s", "Animation", "data", "m", "obj", "v", "a", "seconds", "frame", "dt", "frameOverride", "rawFrame", "frameA", "frameB", "f", "ia", "ib", "matrix", "i", "ex", "ey", "ez", "fx", "fy", "fz", "getClipObjectFrames", "clip", "getClipMaskFrames", "masks", "name", "softEdge", "k", "interpPosQuat", "frames", "frameCount", "interpMat4Frames", "matrices", "out", "loadAnimation", "url", "res", "TIER_SETTINGS", "detectDeviceTier", "mem", "cores", "mobile", "qualityForTier", "tier", "resolveLodUrl", "url", "lod", "m", "base", "slash", "dir", "name", "candidate", "resolvePartsLod", "parts", "out", "id", "value", "u", "Viewer", "options", "canvas", "background", "fov", "near", "far", "splatScale", "autoRotate", "flipY", "shDegree", "aaDilation", "maxPixelRatio", "adaptiveQuality", "prefetchVariants", "gpuSort", "tier", "onProgress", "onError", "resolveCanvas", "Renderer", "OrbitCamera", "IDENTITY_MAT4", "name", "value", "partsMap", "resolve", "reject", "snapshot", "run", "url", "opts", "res", "data", "ids", "c", "masks", "m", "o", "lastFrame", "axis", "t", "s", "dir", "defaultPartsMap", "id", "part", "defaultVariantSuffix", "resolvePartsLod", "p", "fullPartsMap", "activeVariant", "v", "lod", "BACKGROUND_VARIANT_LOD", "resolveLodUrl", "e", "CLIP_VARIANT_PREFETCH_DELAY_MS", "clipId", "clip", "cut", "product", "held", "heldClip", "dt", "pb", "endFrame", "done", "frame", "dirty", "pos", "quat", "getClipObjectFrames", "slots", "quatPosToMat4", "slot", "softEdge", "matrix", "getClipMaskFrames", "prevValue", "enterFrame", "exitFrame", "obj", "interpPosQuat", "interpMat4Frames", "toFrame", "fromFrame", "calls", "byAxis", "call", "target", "count", "variants", "shData", "numSHBases", "loadUrl", "flipYInPlace", "flipYInPlaceSH", "i", "createSorter", "basenameNoExt", "eye", "numVertices", "consume", "openPlyStream", "zeros", "vOff", "chunk", "nVerts", "shChunk", "j", "key", "entry", "result", "partIds", "tasks", "urls", "progresses", "reportProg", "a", "b", "parts", "counts", "total", "vtxOffsets", "off", "merged", "fileAABB", "computeAABB", "variantNames", "maxSHBases", "streamParts", "extendAABB", "bg", "g", "n", "enabled", "index", "d", "pv", "applied", "variant", "start", "end", "oldCount", "newCount", "delta", "slotData", "newTotal", "paused", "wasActive", "markers", "activeMarker", "maxFrame", "mf", "cfg", "hsMode", "extractHsMode", "zm", "newMode", "newModeType", "typeChanged", "pan", "zoom", "factor", "mag", "f", "dx", "dy", "dz", "fitCamera", "clipMaskNames", "vols", "fileNames", "vol", "vi", "prefix", "matched", "fi", "matchesMaskPrefix", "anim", "loadAnimation", "callouts", "view", "proj", "w", "h", "out", "px", "py", "pz", "vx", "vy", "vz", "cw", "sx", "sy", "now", "QUALITY_WARMUP_MS", "warmingUp", "volFrames", "transformsDirty", "alpha", "smoothstep", "lerp3", "finalEye", "finalTarget", "otherEye", "otherTarget", "bf", "objFrames", "fpos", "fquat", "other", "slerpQuat", "anchorFrames", "anchored", "asset", "anchorMat", "local", "mat4Mul", "otherVolumes", "lerpMat4", "animFrame", "feather", "viewChanged", "focal", "activeCount", "N", "order", "v2", "v6", "v10", "v14", "rows", "flat", "nParts", "gs", "dep", "r", "tanHalfFovX", "tanHalfFovY", "aabb", "min", "max", "allBehindNear", "allLeft", "allRight", "allAbove", "allBelow", "lx", "ly", "lz", "wx", "wy", "wz", "limX", "limY", "invMats", "maskBits", "partMat", "inv", "minX", "minY", "minZ", "maxX", "maxY", "maxZ", "x", "y", "z", "lim", "dpr", "SLOW", "FAST", "defaults", "eq", "col", "row", "MASK_AABB_MARGIN", "next", "fileName", "sep", "name1", "name2", "pre", "suf", "hs", "qx", "qy", "qz", "qw", "SH_FLIP_Y_SIGN", "numBases", "base", "SPLAT_EXTS", "SPLAT_LOADERS", "clean", "lastDot", "rawExt", "loadSpzv", "hasExt", "exts", "lastErr", "ext", "candidate", "loader", "u", "loadPly", "loadSpz", "loadSplat", "err", "x2", "y2", "z2", "xx", "xy", "xz", "yy", "yz", "zz", "el", "PLAYER_CSS", "cssInjected", "injectCss", "s", "player", "container", "opts", "root", "dataSrc", "dataAnim", "scene", "srcAlias", "parts", "animSrc", "clips", "partsDir", "partsExt", "scenes", "masks", "sh", "background", "fov", "near", "far", "splatScale", "autoRotate", "flipY", "maxPixelRatio", "quality", "adaptiveQuality", "prefetchVariants", "aaDilation", "gpuSort", "zIndex", "onLoad", "onProgress", "onError", "src", "tier", "detectDeviceTier", "caps", "qualityForTier", "effectiveMaxPixelRatio", "canvas", "calloutEl", "overlay", "pctEl", "barWrap", "bar", "msgEl", "_loadStart", "_pendingLoads", "showLoading", "showReady", "showError", "text", "viewer", "Viewer", "p", "pct", "elapsed", "eta", "calloutDivs", "NS", "buildCallouts", "callouts", "key", "svg", "c", "line", "dot", "card", "projected", "id", "visible", "x", "y", "entry", "ox", "oy", "cx", "cy", "load", "url", "err", "msg", "loadPartsMap", "partsMap", "bindClipButtons", "ids", "bound", "clipId", "btn", "loadClips", "splatsDir", "defaults", "loadAnim", "anim", "api", "bg", "v", "n", "name", "axis", "value", "setupScrollPlayback", "PINGPONG_EASE_FRAMES", "PINGPONG_MIN_SPEED", "markerEntries", "a", "b", "sceneList", "fromFrame", "i", "cfg", "scrollFrameFor", "rect", "scrollY", "zoneTop", "zoneBottom", "maxScrollY", "effBottom", "t", "blendSceneName", "blendFromEye", "blendFromTarget", "blendFromObjects", "blendFromVolumes", "smoothstep01", "captureCurrentPose", "cam", "o", "updateSceneBlend", "bf", "rawBf", "nextCam", "nextObjs", "nextVols", "scrollTarget", "hasScrolled", "pausedAutoFrame", "firstScrollIdx", "entryScene", "entryFrame", "updateScrollTarget", "found", "r", "onScroll", "dt", "frame", "sc", "tickDt", "distToBoundary", "ease", "f", "effectiveSh", "m", "cappedSh", "clipUrls", "clipLoads", "dir", "autoPartsMap", "obj", "splatNameFromId", "resolvePartsLod", "loads", "resolveLodUrl", "isMobile", "autoInit", "el", "SCROLL_CSS", "sceneCssInjected", "injectScrollCss", "s", "resolveFrame", "value", "markers", "fallback", "str", "n", "parseAct", "el", "lastFrame", "parseCaptions", "fromAttr", "isPingpong", "isFreecamera", "from", "to", "loop", "v", "c", "actFrame", "act", "progress", "range", "p", "getProgress", "rect", "scrollY", "top", "h", "bottom", "maxScrollY", "effBottom", "lerp", "b", "t", "smoothstep", "x", "scrollScene", "sceneEl", "playerInstance", "opts", "track", "acts", "ppActive", "ppDir", "ppFrame", "ppStart", "ppEnd", "ppLastTime", "ppRafId", "PP_TRANSITION_ZONE", "ppTransitioning", "ppCapturedFrame", "fcActive", "startPingpong", "startFrame", "endFrame", "fromEnd", "tickPingpong", "stopPingpong", "now", "dt", "fps", "buildActs", "anim", "update", "frame", "activeAct", "activeProgress", "inPingpong", "inFreecamera", "activeId", "isActive", "cap", "visible", "rafPending", "onScroll", "waitForAnim", "autoInitScrollScenes", "stage", "importance", "data", "i", "j", "alpha", "sx", "sy", "sz", "pruneGaussians", "count", "opts", "minAlpha", "minScale", "keepFraction", "kept", "a", "b", "out", "k", "generateLods", "fractions", "fraction", "n", "PRUNE_PRESETS", "create", "options", "onLoad", "onError", "src", "parts", "quality", "viewerOpts", "tier", "detectDeviceTier", "caps", "qualityForTier", "maxPixelRatio", "shDegree", "prefetchVariants", "viewer", "Viewer", "noop", "resolvePartsLod", "resolveLodUrl", "err", "bg", "s", "g", "v", "id", "i", "name"]
|
|
7
|
+
}
|