@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
JavaScript
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