UNPKG

@babylonjs/viewer

Version:

The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.

778 lines (767 loc) 29.7 kB
import { p as U8, F as F32, B as BU, U as U16, ap as U32, o as TU, b0 as createWorldMatrixState, b1 as composeTrsLocalMatrix, b2 as ObservableQuat, b3 as createEulerProxy, b4 as ObservableVec3, b5 as attachWorldMatrixState, b6 as eulerToQuat, t as targetSignatureKey, n as SS, b7 as CW, g as getSceneBindGroupLayout, b8 as getRenderTargetSize, b9 as getViewMatrix, ba as getProjectionMatrix, an as TYPE_SIZES } from './index-DMbDahsc.esm.js'; const ROW_LENGTH = 32; function chooseTextureSize(length) { const width = 4096; const height = Math.max(1, Math.ceil(length / width)); return { width, height }; } function buildSplatGeometry(splatBuffer) { const u = new U8(splatBuffer); const f = new F32(splatBuffer); const vertexCount = u.byteLength / ROW_LENGTH | 0; if (vertexCount === 0) { throw new Error("splat buffer is empty"); } const { width, height } = chooseTextureSize(vertexCount); const texelCount = width * height; const positions = new F32(vertexCount * 3); const centersRGBA = new F32(texelCount * 4); const covARGBA = new F32(texelCount * 4); const covBRGBA = new F32(texelCount * 4); const colorsRGBA = new F32(texelCount * 4); let minX = Infinity, minY = Infinity, minZ = Infinity; let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity; const M = new F32(9); for (let i = 0; i < vertexCount; i++) { const fi = i * 8; const ui = i * ROW_LENGTH; const x = f[fi]; const y = -f[fi + 1]; const z = f[fi + 2]; positions[i * 3] = x; positions[i * 3 + 1] = y; positions[i * 3 + 2] = z; if (x < minX) { minX = x; } if (y < minY) { minY = y; } if (z < minZ) { minZ = z; } if (x > maxX) { maxX = x; } if (y > maxY) { maxY = y; } if (z > maxZ) { maxZ = z; } centersRGBA[i * 4] = x; centersRGBA[i * 4 + 1] = y; centersRGBA[i * 4 + 2] = z; centersRGBA[i * 4 + 3] = 1; colorsRGBA[i * 4] = u[ui + 24] / 255; colorsRGBA[i * 4 + 1] = u[ui + 25] / 255; colorsRGBA[i * 4 + 2] = u[ui + 26] / 255; colorsRGBA[i * 4 + 3] = u[ui + 27] / 255; let qw = -(u[ui + 28] - 127.5) / 127.5; let qx = (u[ui + 29] - 127.5) / 127.5; let qy = -(u[ui + 30] - 127.5) / 127.5; let qz = (u[ui + 31] - 127.5) / 127.5; const qLen = Math.hypot(qw, qx, qy, qz) || 1; const qInv = 1 / qLen; qw *= qInv; qx *= qInv; qy *= qInv; qz *= qInv; const sx = f[fi + 3] * 2; const sy = f[fi + 4] * 2; const sz = f[fi + 5] * 2; const xx = qx * qx, yy = qy * qy, zz = qz * qz; const xy = qx * qy, xz = qx * qz, yz = qy * qz; const wx = qw * qx, wy = qw * qy, wz = qw * qz; const r00 = 1 - 2 * (yy + zz); const r01 = 2 * (xy + wz); const r02 = 2 * (xz - wy); const r10 = 2 * (xy - wz); const r11 = 1 - 2 * (xx + zz); const r12 = 2 * (yz + wx); const r20 = 2 * (xz + wy); const r21 = 2 * (yz - wx); const r22 = 1 - 2 * (xx + yy); M[0] = r00 * sx; M[1] = r01 * sx; M[2] = r02 * sx; M[3] = r10 * sy; M[4] = r11 * sy; M[5] = r12 * sy; M[6] = r20 * sz; M[7] = r21 * sz; M[8] = r22 * sz; const a0 = M[0] * M[0] + M[3] * M[3] + M[6] * M[6]; const a1 = M[0] * M[1] + M[3] * M[4] + M[6] * M[7]; const a2 = M[0] * M[2] + M[3] * M[5] + M[6] * M[8]; const b0 = M[1] * M[1] + M[4] * M[4] + M[7] * M[7]; const b1 = M[1] * M[2] + M[4] * M[5] + M[7] * M[8]; const b2 = M[2] * M[2] + M[5] * M[5] + M[8] * M[8]; covARGBA[i * 4] = a0; covARGBA[i * 4 + 1] = a1; covARGBA[i * 4 + 2] = a2; covARGBA[i * 4 + 3] = 1; covBRGBA[i * 4] = b0; covBRGBA[i * 4 + 1] = b1; covBRGBA[i * 4 + 2] = b2; covBRGBA[i * 4 + 3] = 1; } return { vertexCount, boundMin: [minX, minY, minZ], boundMax: [maxX, maxY, maxZ], textureWidth: width, textureHeight: height, positions, centersRGBA, covARGBA, covBRGBA, colorsRGBA }; } function createGaussianSplattingMesh(engine, name, geom, worker, parsed) { const device = engine._device; const queue = device.queue; const { textureWidth, textureHeight, vertexCount } = geom; const makeRgba32f = (data) => { const tex = device.createTexture({ size: [textureWidth, textureHeight], format: "rgba32float", usage: TU.TEXTURE_BINDING | TU.COPY_DST }); queue.writeTexture({ texture: tex }, data.buffer, { bytesPerRow: textureWidth * 16 }, { width: textureWidth, height: textureHeight }); return { tex, view: tex.createView() }; }; const centers = makeRgba32f(geom.centersRGBA); const covA = makeRgba32f(geom.covARGBA); const covB = makeRgba32f(geom.covBRGBA); const colors = makeRgba32f(geom.colorsRGBA); const sampler = device.createSampler({ magFilter: "nearest", minFilter: "nearest", addressModeU: "clamp-to-edge", addressModeV: "clamp-to-edge" }); const quadBuffer = device.createBuffer({ size: 32, usage: BU.VERTEX, mappedAtCreation: true }); new F32(quadBuffer.getMappedRange()).set([-2, -2, 2, -2, 2, 2, -2, 2]); quadBuffer.unmap(); const indexBuffer = device.createBuffer({ size: 12, usage: BU.INDEX, mappedAtCreation: true }); new U16(indexBuffer.getMappedRange()).set([0, 1, 2, 0, 2, 3]); indexBuffer.unmap(); const splatIndexCpu = new F32(vertexCount); for (let i = 0; i < vertexCount; i++) { splatIndexCpu[i] = i; } const splatIndexBuffer = device.createBuffer({ size: splatIndexCpu.byteLength, usage: BU.VERTEX | BU.COPY_DST }); queue.writeBuffer(splatIndexBuffer, 0, splatIndexCpu.buffer, 0, splatIndexCpu.byteLength); let firstResolve = null; const firstSortReady = new Promise((res) => { firstResolve = res; }); let retainedSplatsData = parsed.data; const mesh = { _kind: "gs-mesh", name, vertexCount, textureWidth, textureHeight, boundMin: geom.boundMin.slice(), boundMax: geom.boundMax.slice(), shDegree: parsed.shDegree ?? 0, _worker: worker, _orderPool: [new U32(vertexCount), new U32(vertexCount)], _pendingOrder: null, _sortDepthTransform: new F32(4), _nextSortDepthTransform: new F32(4), firstSortReady, _firstSortResolve: firstResolve, _gs: { _centersTex: centers.tex, _centersView: centers.view, _covATex: covA.tex, _covAView: covA.view, _covBTex: covB.tex, _covBView: covB.view, _colorsTex: colors.tex, _colorsView: colors.view, _sampler: sampler, _quadBuffer: quadBuffer, _indexBuffer: indexBuffer, _splatIndexBuffer: splatIndexBuffer, _splatIndexCpu: splatIndexCpu, _shTextures: null, _shViews: null } }; Object.defineProperty(mesh, "splatsData", { get: () => retainedSplatsData }); mesh.updateData = (newBuffer) => { const newGeom = buildSplatGeometry(newBuffer); if (newGeom.vertexCount !== mesh.vertexCount) { throw Error("GS vertex count mismatch"); } const gs = mesh._gs; const writeTex = (tex, data) => { queue.writeTexture({ texture: tex }, data.buffer, { bytesPerRow: newGeom.textureWidth * 16 }, { width: newGeom.textureWidth, height: newGeom.textureHeight }); }; writeTex(gs._centersTex, newGeom.centersRGBA); writeTex(gs._covATex, newGeom.covARGBA); writeTex(gs._covBTex, newGeom.covBRGBA); writeTex(gs._colorsTex, newGeom.colorsRGBA); mesh.boundMin = newGeom.boundMin.slice(); mesh.boundMax = newGeom.boundMax.slice(); mesh._worker.postMessage({ p: newGeom.positions }, [newGeom.positions.buffer]); mesh._sortDepthTransform.fill(0); retainedSplatsData = newBuffer; }; initSplatTransform(mesh); worker.postMessage({ p: geom.positions }, [geom.positions.buffer]); worker.onmessage = (e) => { const data = e.data; if (mesh._pendingOrder) { mesh._orderPool.push(mesh._pendingOrder); } mesh._pendingOrder = data.o; if (mesh._firstSortResolve) { mesh._firstSortResolve(); mesh._firstSortResolve = null; } }; return mesh; } const SORT_EPS = 1e-4; function uploadPendingSplatOrder(queue, mesh) { const order = mesh._pendingOrder; if (!order) { return; } mesh._pendingOrder = null; const cpu = mesh._gs._splatIndexCpu; cpu.set(order); queue.writeBuffer(mesh._gs._splatIndexBuffer, 0, cpu.buffer, 0, cpu.byteLength); mesh._orderPool.push(order); } function postSplatSortIfDirty(mesh, world, view) { if (mesh._orderPool.length === 0) { return; } const v0 = view[2]; const v1 = view[6]; const v2 = view[10]; const last = mesh._sortDepthTransform; const next = mesh._nextSortDepthTransform; let dirty = false; for (let i = 0; i < 4; i++) { next[i] = v0 * world[4 * i] + v1 * world[4 * i + 1] + v2 * world[4 * i + 2] + (i === 3 ? view[14] : 0); if (Math.abs(last[i] - next[i]) > SORT_EPS) { dirty = true; } } if (!dirty) { return; } last.set(next); const order = mesh._orderPool.pop(); mesh._worker.postMessage({ t: last, o: order }, [order.buffer]); } function disposeGaussianSplattingMesh(mesh) { const gs = mesh._gs; [gs._centersTex, gs._covATex, gs._covBTex, gs._colorsTex, gs._quadBuffer, gs._indexBuffer, gs._splatIndexBuffer, ...gs._shTextures ?? []].forEach( (resource) => resource.destroy() ); mesh._worker.terminate(); } function initSplatTransform(node) { const wm = createWorldMatrixState(() => composeTrsLocalMatrix(node.position, node.rotationQuaternion, node.scaling)); const onDirty = () => wm.markLocalDirty(); const [iqx, iqy, iqz, iqw] = eulerToQuat(0, 0, 0); const rq = new ObservableQuat(iqx, iqy, iqz, iqw, onDirty); node.rotationQuaternion = rq; node.rotation = createEulerProxy(rq); node.position = new ObservableVec3(0, 0, 0, onDirty); node.scaling = new ObservableVec3(1, 1, 1, onDirty); node.children = []; Object.defineProperty(node, "parent", { get() { return wm.parent; }, set(v) { wm.parent = v; }, configurable: true, enumerable: true }); Object.defineProperty(node, "worldMatrix", { get() { return wm.getWorldMatrix(); }, configurable: true, enumerable: false }); Object.defineProperty(node, "worldMatrixVersion", { get() { return wm.getWorldMatrixVersion(); }, configurable: true, enumerable: false }); attachWorldMatrixState(node, wm); } function registerPickSource(scene, entity, load) { const source = { entity, load }; scene._pickSources.push(source); return () => { const i = scene._pickSources.indexOf(source); if (i >= 0) { scene._pickSources.splice(i, 1); } }; } const WGSL = "struct S{w:mat4x4<f32>,v:mat4x4<f32>,p:mat4x4<f32>,vp:vec2<f32>,f:vec2<f32>,ds:vec2<f32>,a:f32,_p:f32}@group(1) @binding(0) var<uniform> u:S;@group(1) @binding(1) var e:sampler;@group(1) @binding(2) var F:texture_2d<f32>;@group(1) @binding(3) var G:texture_2d<f32>;@group(1) @binding(4) var J:texture_2d<f32>;@group(1) @binding(5) var K:texture_2d<f32>;struct A{@builtin(position) pos:vec4<f32>,@location(0) vc:vec4<f32>,@location(1) vq:vec2<f32>}fn B(r:f32)->vec2<f32>{let v=floor(r/u.ds.x);let N=r-v*u.ds.x;return vec2<f32>((N+0.5)/u.ds.x,(v+0.5)/u.ds.y);}@vertex fn vs(@location(0) k:vec2<f32>,@location(1) R:f32)->A{var a:A;let j=B(R);let M=textureSampleLevel(F,e,j,0.0).xyz;let q=textureSampleLevel(K,e,j,0.0);let d=textureSampleLevel(G,e,j,0.0).xyz;let i=textureSampleLevel(J,e,j,0.0).xyz;let C=u.w*vec4<f32>(M,1.0);let m=u.v*u.w;let h=u.v*C;let b=u.p*h;let g=1.2*b.w;if (b.z<0.0||b.x<-g||b.x>g||b.y<-g||b.y>g){a.pos=vec4<f32>(0.0,0.0,2.0,1.0);a.vc=vec4<f32>(0.0);a.vq=vec2<f32>(0.0);return a;}let H=mat3x3<f32>(vec3<f32>(d.x,d.y,d.z),vec3<f32>(d.y,i.x,i.y),vec3<f32>(d.z,i.y,i.z));let f=1.0/h.z;let s=f*f;let E=mat3x3<f32>(vec3<f32>(u.f.x*f,0.0,-u.f.x*h.x*s),vec3<f32>(0.0,u.f.y*f,-u.f.y*h.y*s),vec3<f32>(0.0,0.0,0.0));let P=mat3x3<f32>(m[0].xyz,m[1].xyz,m[2].xyz);let t=transpose(P)*E;var c=transpose(t)*H*t;let w:f32=0.3;c[0][0]+=w;c[1][1]+=w;let z=(c[0][0]+c[1][1])*0.5;let I=(c[0][0]-c[1][1])*0.5;let y=length(vec2<f32>(I,c[0][1]));let x:f32=0.0001;let n=z+y+x;let p=z-y+x;if (p<0.0){a.pos=vec4<f32>(0.0,0.0,2.0,1.0);a.vc=vec4<f32>(0.0);a.vq=vec2<f32>(0.0);return a;}let l=normalize(vec2<f32>(c[0][1],n-c[0][0]));let O=min(sqrt(2.0*n),1024.0)*l;let D=min(sqrt(2.0*p),1024.0)*vec2<f32>(l.y,-l.x);let Q=b.xy;a.pos=vec4<f32>(Q+(k.x*O+k.y*D)*b.w/u.vp,b.z,b.w);a.vc=vec4<f32>(q.rgb,q.a*u.a);a.vq=k;return a;}/*GS_FRAGMENT_DEFINITIONS*/@fragment fn fs(in:A)->@location(0) vec4<f32>{/*GS_FRAGMENT_MAIN_BEGIN*/let o=-dot(in.vq,in.vq);var finalColor:vec4<f32>;if (o>-4.0){let L=exp(o)*in.vc.a;finalColor=vec4<f32>(in.vc.rgb,L);} else{finalColor=vec4<f32>(0.0);}/*GS_FRAGMENT_BEFORE_FRAGCOLOR*//*GS_FRAGMENT_MAIN_END*/return finalColor;}"; let _cache = null; function applyGsFragments(wgsl, fragments) { const slotCode = {}; for (const frag of fragments) { if (frag.helperFunctions) { slotCode["GS_FRAGMENT_DEFINITIONS"] = (slotCode["GS_FRAGMENT_DEFINITIONS"] ?? "") + frag.helperFunctions + "\n"; } for (const [slot, code] of Object.entries(frag.fragmentSlots ?? {})) { slotCode[slot] = (slotCode[slot] ?? "") + code + "\n"; } } const spliced = wgsl.replace(/\/\*(GS_FRAGMENT_\w+)\*\//g, (_, slot) => slotCode[slot] ?? ""); const mangles = [ ["world", "w"], ["view", "v"], ["projection", "p"], ["viewport", "vp"], ["focal", "f"], ["dataSize", "ds"], ["alpha", "a"], ["_pad", "_p"], ["vColor", "vc"], ["vPos", "vq"], ["dataUv", "du"], ["splatIndex", "si"], ["corner", "co"], ["center", "ce"], ["color", "cl"], ["covA", "ca"], ["covB", "cb"], ["worldPos", "wp"], ["modelView", "mv"], ["camspace", "cs"], ["pos2d", "p2"], ["bounds", "bd"], ["Vrk", "vr"], ["invZ2", "iz2"], ["invZ", "iz"], ["cov2d", "c2"], ["kernelSize", "ks"], ["radius", "ra"], ["epsilon", "ep"], ["lambda1", "l1"], ["lambda2", "l2"], ["diag", "dg"], ["majorAxis", "ma"], ["minorAxis", "mi"], ["vCenter", "vc2"] ]; let mangled = spliced; for (const [from, to] of mangles) { mangled = mangled.replace(new RegExp(`\\b${from}\\b`, "g"), to); } return mangled; } function getOrCreatePipeline(engine, sig, fragments) { const device = engine._device; if (!_cache || _cache.device !== device) { _cache = { device, modules: /* @__PURE__ */ new Map(), entries: /* @__PURE__ */ new Map() }; } const fragKey = ""; const key = targetSignatureKey(sig) + fragKey; let entry = _cache.entries.get(key); if (entry) { return entry; } let module = _cache.modules.get(fragKey); if (!module) { module = device.createShaderModule({ code: WGSL }); _cache.modules.set(fragKey, module); } const meshBindGroupLayout = device.createBindGroupLayout({ entries: [ { binding: 0, visibility: SS.VERTEX | SS.FRAGMENT, buffer: { type: "uniform" } }, { binding: 1, visibility: SS.VERTEX, sampler: { type: "non-filtering" } }, { binding: 2, visibility: SS.VERTEX, texture: { sampleType: "unfilterable-float" } }, { binding: 3, visibility: SS.VERTEX, texture: { sampleType: "unfilterable-float" } }, { binding: 4, visibility: SS.VERTEX, texture: { sampleType: "unfilterable-float" } }, { binding: 5, visibility: SS.VERTEX, texture: { sampleType: "unfilterable-float" } } ] }); const pipeline = device.createRenderPipeline({ layout: device.createPipelineLayout({ bindGroupLayouts: [getSceneBindGroupLayout(engine), meshBindGroupLayout] }), vertex: { module, entryPoint: "vs", buffers: [ { arrayStride: 8, stepMode: "vertex", attributes: [{ shaderLocation: 0, offset: 0, format: "float32x2" }] }, { arrayStride: 4, stepMode: "instance", attributes: [{ shaderLocation: 1, offset: 0, format: "float32" }] } ] }, fragment: { module, entryPoint: "fs", targets: [ { format: sig._colorFormat, blend: { // BJS GS material uses ALPHA_COMBINE: src*srcAlpha + dst*(1-srcAlpha) color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" }, alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" } }, writeMask: CW.ALL } ] }, primitive: { topology: "triangle-list", cullMode: "none" }, depthStencil: { format: sig._depthStencilFormat ?? "depth24plus-stencil8", depthCompare: sig._depthCompare ?? "greater-equal", depthWriteEnabled: false }, multisample: { count: sig._sampleCount } }); entry = { pipeline, meshBindGroupLayout }; _cache.entries.set(key, entry); return entry; } function buildGaussianSplattingRenderable(scene, mesh, fragments) { const engine = scene.surface.engine; const device = engine._device; const UBO_BYTES = 16 * 4 * 3 + 8 * 4; const ubo = device.createBuffer({ size: UBO_BYTES, usage: BU.UNIFORM | BU.COPY_DST }); const cpu = new F32(UBO_BYTES / 4); cpu[48 + 4] = mesh.textureWidth; cpu[48 + 5] = mesh.textureHeight; cpu[48 + 6] = 1; cpu[48 + 7] = 0; const bindGroups = /* @__PURE__ */ new Map(); const getBindGroup = (entry) => { let bg = bindGroups.get(entry.pipeline); if (bg) { return bg; } bg = device.createBindGroup({ layout: entry.meshBindGroupLayout, entries: [ { binding: 0, resource: { buffer: ubo } }, { binding: 1, resource: mesh._gs._sampler }, { binding: 2, resource: mesh._gs._centersView }, { binding: 3, resource: mesh._gs._covAView }, { binding: 4, resource: mesh._gs._covBView }, { binding: 5, resource: mesh._gs._colorsView } ] }); bindGroups.set(entry.pipeline, bg); return bg; }; const update = () => { const cam = scene.camera; if (!cam) { return; } uploadPendingSplatOrder(device.queue, mesh); const size = getRenderTargetSize(engine); const aspect = size.width / size.height; const view = getViewMatrix(cam); const proj = getProjectionMatrix(cam, aspect); const world = mesh.worldMatrix; cpu.set(world, 0); cpu.set(view, 16); cpu.set(proj, 32); cpu[48] = size.width; cpu[48 + 1] = size.height; cpu[48 + 2] = size.width * 0.5 * proj[0]; cpu[48 + 3] = size.height * 0.5 * proj[5]; device.queue.writeBuffer(ubo, 0, cpu.buffer, 0, UBO_BYTES); postSplatSortIfDirty(mesh, world, view); }; const r = { order: 200, isTransparent: true, bind(eng, sig) { const entry = getOrCreatePipeline(eng, sig); const bindGroup = getBindGroup(entry); return { renderable: r, pipeline: entry.pipeline, update, draw(pass) { pass.setBindGroup(1, bindGroup); pass.setVertexBuffer(0, mesh._gs._quadBuffer); pass.setVertexBuffer(1, mesh._gs._splatIndexBuffer); pass.setIndexBuffer(mesh._gs._indexBuffer, "uint16"); pass.drawIndexed(6, mesh.vertexCount); return 1; } }; } }; return r; } function attachGaussianSplattingMesh(scene, mesh, fragments) { const ctx = scene; ctx._renderables.push(buildGaussianSplattingRenderable(scene, mesh)); const unregisterPick = registerPickSource(scene, mesh, () => import('./gs-picking-pipeline-CyS6hN5Z.esm.js')); ctx._disposables.push(() => { unregisterPick(); disposeGaussianSplattingMesh(mesh); }); } const jsContent = "(()=>{function w(c,s,o,a,j){const m=o[0],u=o[1],b=o[2],d=o[3],f=j[0];let r=1/0,g=-1/0;for(let t=0;t<s;t++){f[t]=m*c[3*t]+u*c[3*t+1]+b*c[3*t+2]+d;const n=f[t];n-n===0&&(n<r&&(r=n),n>g&&(g=n))}const h=g-r;if(!(h>1e-12)){for(let t=0;t<s;t++)a[t]=t;return}const e=j[1];e.fill(0);const i=e.length-1,p=i/h;for(let t=0;t<s;t++){const n=f[t];let l;n-n===0?(l=(n-r)*p|0,l>i&&(l=i)):l=i,f[t]=l,e[l]++}let M=0;for(let t=i;t>=0;t--){const n=e[t];e[t]=M,M+=n}for(let t=0;t<s;t++){const n=f[t];a[e[n]++]=t}}let y,k;self.onmessage=c=>{const s=c.data;if(s.p){y=s.p;const a=y.length/3;k=[new Float32Array(a),new Uint32Array(1<<Math.max(10,Math.min(20,Math.round(Math.log2(a/4)))))];return}const o=s.o;w(y,o.length,s.t,o,k),self.postMessage({o},[o.buffer])};})();"; const blob = typeof self !== "undefined" && self.Blob && new Blob(['URL.revokeObjectURL(import.meta.url);',jsContent], { type: "text/javascript;charset=utf-8" }); function WorkerWrapper(options) { let objURL; try { objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob); if (!objURL) throw '' const worker = new Worker(objURL, { type: "module", name: options?.name }); worker.addEventListener("error", () => { (self.URL || self.webkitURL).revokeObjectURL(objURL); }); return worker; } catch(e) { return new Worker( 'data:text/javascript;charset=utf-8,' + encodeURIComponent(jsContent), { type: "module", name: options?.name } ); } } async function attachParsedSplat(scene, name, parsed, fragments) { const geom = buildSplatGeometry(parsed.data); const worker = new WorkerWrapper({ name: "babylon-lite-splat-sort" }); const eng = scene.surface.engine; const mesh = createGaussianSplattingMesh(eng, name, geom, worker, parsed); if (parsed.sh && parsed.shDegree && parsed.shDegree > 0) { const { attachGaussianSplattingMeshSH } = await import('./gaussian-splatting-pipeline-sh-B-GBMjvd.esm.js'); attachGaussianSplattingMeshSH(scene, mesh, parsed.sh, fragments); } else { attachGaussianSplattingMesh(scene, mesh); } return mesh; } const NAME = "KHR_gaussian_splatting"; const RotationAttribute = "KHR_gaussian_splatting:ROTATION"; const ScaleAttribute = "KHR_gaussian_splatting:SCALE"; const OpacityAttribute = "KHR_gaussian_splatting:OPACITY"; const ShDegree0Attribute = "KHR_gaussian_splatting:SH_DEGREE_0_COEF_0"; const CT_BYTE = 5120; const CT_UNSIGNED_BYTE = 5121; const CT_SHORT = 5122; const CT_UNSIGNED_SHORT = 5123; const CT_UNSIGNED_INT = 5125; const CT_FLOAT = 5126; const COMPONENT_BYTES = { [CT_BYTE]: 1, [CT_UNSIGNED_BYTE]: 1, [CT_SHORT]: 2, [CT_UNSIGNED_SHORT]: 2, [CT_UNSIGNED_INT]: 4, [CT_FLOAT]: 4 }; const ShC0 = 0.28209479177387814; const RowLength = 32; function clamp255(value) { return value <= 0 ? 0 : value >= 255 ? 255 : value + 0.5 | 0; } function isGsPrimitive(primitive) { if (primitive?.extensions?.[NAME]) { return true; } const attributes = primitive?.attributes; if (!attributes) { return false; } for (const key in attributes) { if (key.startsWith(NAME + ":")) { return true; } } return false; } function readFloats(json, binChunk, accessorIdx) { const accessor = json.accessors[accessorIdx]; const componentCount = TYPE_SIZES[accessor.type] ?? 1; const count = accessor.count; const out = new Float32Array(count * componentCount); if (accessor.bufferView === void 0) { return out; } const bufferView = json.bufferViews[accessor.bufferView]; const ct = accessor.componentType; const compBytes = COMPONENT_BYTES[ct] ?? 4; const elemBytes = componentCount * compBytes; const stride = bufferView.byteStride ?? elemBytes; const normalized = !!accessor.normalized; const base = binChunk.byteOffset + (bufferView.byteOffset ?? 0) + (accessor.byteOffset ?? 0); const dv = new DataView(binChunk.buffer); for (let v = 0; v < count; v++) { const rowBase = base + v * stride; for (let c = 0; c < componentCount; c++) { const off = rowBase + c * compBytes; let value; switch (ct) { case CT_FLOAT: value = dv.getFloat32(off, true); break; case CT_UNSIGNED_BYTE: value = normalized ? dv.getUint8(off) / 255 : dv.getUint8(off); break; case CT_BYTE: value = normalized ? Math.max(dv.getInt8(off) / 127, -1) : dv.getInt8(off); break; case CT_UNSIGNED_SHORT: value = normalized ? dv.getUint16(off, true) / 65535 : dv.getUint16(off, true); break; case CT_SHORT: value = normalized ? Math.max(dv.getInt16(off, true) / 32767, -1) : dv.getInt16(off, true); break; case CT_UNSIGNED_INT: value = dv.getUint32(off, true); break; default: value = dv.getFloat32(off, true); break; } out[v * componentCount + c] = value; } } return out; } function buildSplatBuffer(json, binChunk, rec) { const attrs = rec.attributes; const positions = readFloats(json, binChunk, attrs["POSITION"]); const splatCount = positions.length / 3 | 0; const scales = attrs[ScaleAttribute] !== void 0 ? readFloats(json, binChunk, attrs[ScaleAttribute]) : null; const rotations = attrs[RotationAttribute] !== void 0 ? readFloats(json, binChunk, attrs[RotationAttribute]) : null; const opacities = attrs[OpacityAttribute] !== void 0 ? readFloats(json, binChunk, attrs[OpacityAttribute]) : null; const shDegree0 = attrs[ShDegree0Attribute] !== void 0 ? readFloats(json, binChunk, attrs[ShDegree0Attribute]) : null; const colors = attrs["COLOR_0"] !== void 0 ? readFloats(json, binChunk, attrs["COLOR_0"]) : null; const colorStride = colors ? colors.length / splatCount | 0 : 0; const buffer = new ArrayBuffer(RowLength * splatCount); const floatView = new Float32Array(buffer); const byteView = new Uint8Array(buffer); for (let i = 0; i < splatCount; i++) { const floatBase = i * 8; const byteBase = i * RowLength; const p = i * 3; floatView[floatBase + 0] = positions[p + 0]; floatView[floatBase + 1] = positions[p + 1]; floatView[floatBase + 2] = positions[p + 2]; floatView[floatBase + 3] = scales ? scales[p + 0] : 1; floatView[floatBase + 4] = scales ? scales[p + 1] : 1; floatView[floatBase + 5] = scales ? scales[p + 2] : 1; if (shDegree0) { byteView[byteBase + 24] = clamp255((0.5 + ShC0 * shDegree0[p + 0]) * 255); byteView[byteBase + 25] = clamp255((0.5 + ShC0 * shDegree0[p + 1]) * 255); byteView[byteBase + 26] = clamp255((0.5 + ShC0 * shDegree0[p + 2]) * 255); } else if (colors) { const c = i * colorStride; byteView[byteBase + 24] = clamp255(colors[c + 0] * 255); byteView[byteBase + 25] = clamp255(colors[c + 1] * 255); byteView[byteBase + 26] = clamp255(colors[c + 2] * 255); } else { byteView[byteBase + 24] = 255; byteView[byteBase + 25] = 255; byteView[byteBase + 26] = 255; } if (opacities) { byteView[byteBase + 27] = clamp255(opacities[i] * 255); } else if (colors && colorStride >= 4) { byteView[byteBase + 27] = clamp255(colors[i * colorStride + 3] * 255); } else { byteView[byteBase + 27] = 255; } const r = i * 4; const qx = rotations ? rotations[r + 0] : 0; const qy = rotations ? rotations[r + 1] : 0; const qz = rotations ? rotations[r + 2] : 0; const qw = rotations ? rotations[r + 3] : 1; byteView[byteBase + 28] = clamp255(qw * 127.5 + 127.5); byteView[byteBase + 29] = clamp255(qx * 127.5 + 127.5); byteView[byteBase + 30] = clamp255(qy * 127.5 + 127.5); byteView[byteBase + 31] = clamp255(qz * 127.5 + 127.5); } return buffer; } const feature = { id: NAME, // Strip GS primitives before mesh extraction so the core loader builds no // triangle/point geometry for them, and stash the accessor indices for applyAsset. async preParse(json) { const records = []; const meshes = json.meshes ?? []; for (let mi = 0; mi < meshes.length; mi++) { const mesh = meshes[mi]; const primitives = mesh?.primitives; if (!primitives?.length) { continue; } const kept = []; for (let pi = 0; pi < primitives.length; pi++) { const primitive = primitives[pi]; if (isGsPrimitive(primitive)) { records.push({ name: `${mesh.name ?? "splat"}_${mi}_${pi}`, attributes: primitive.attributes }); } else { kept.push(primitive); } } mesh.primitives = kept; } if (records.length) { json.__gsSplats = records; } }, // Convert the captured GS primitives to splat row buffers and wire the // resulting renderables into the scene once addToScene supplies the context. async applyAsset(_meshes, _root, ctx) { const records = ctx._json.__gsSplats; if (!records?.length) { return {}; } const prepared = records.map((rec) => ({ name: rec.name, buffer: buildSplatBuffer(ctx._json, ctx._binChunk, rec) })); const ready = []; const sceneSetup = (scene) => { for (const item of prepared) { ready.push( attachParsedSplat(scene, item.name, { data: item.buffer }).then((mesh) => { mesh.rotation.z = Math.PI; return mesh; }) ); } }; return { _sceneSetup: sceneSetup, _gaussianSplats: ready }; } }; var gltfFeatureGaussianSplatting = /*#__PURE__*/Object.freeze({ __proto__: null, default: feature }); export { applyGsFragments as a, disposeGaussianSplattingMesh as d, gltfFeatureGaussianSplatting as g, postSplatSortIfDirty as p, registerPickSource as r, uploadPendingSplatOrder as u }; //# sourceMappingURL=gltf-feature-gaussian-splatting-BiH_nkp6.esm.js.map