@animech-public/playcanvas
Version:
PlayCanvas WebGL game engine
251 lines (248 loc) • 7.01 kB
JavaScript
import { Quat } from '../../core/math/quat.js';
import { Vec3 } from '../../core/math/vec3.js';
import { Vec4 } from '../../core/math/vec4.js';
import { GSplatData } from './gsplat-data.js';
const SH_C0 = 0.28209479177387814;
class SplatCompressedIterator {
constructor(gsplatData, p, r, s, c, sh) {
const unpackUnorm = (value, bits) => {
const t = (1 << bits) - 1;
return (value & t) / t;
};
const unpack111011 = (result, value) => {
result.x = unpackUnorm(value >>> 21, 11);
result.y = unpackUnorm(value >>> 11, 10);
result.z = unpackUnorm(value, 11);
};
const unpack8888 = (result, value) => {
result.x = unpackUnorm(value >>> 24, 8);
result.y = unpackUnorm(value >>> 16, 8);
result.z = unpackUnorm(value >>> 8, 8);
result.w = unpackUnorm(value, 8);
};
const unpackRot = (result, value) => {
const norm = 1.0 / (Math.sqrt(2) * 0.5);
const a = (unpackUnorm(value >>> 20, 10) - 0.5) * norm;
const b = (unpackUnorm(value >>> 10, 10) - 0.5) * norm;
const c = (unpackUnorm(value, 10) - 0.5) * norm;
const m = Math.sqrt(1.0 - (a * a + b * b + c * c));
switch (value >>> 30) {
case 0:
result.set(a, b, c, m);
break;
case 1:
result.set(m, b, c, a);
break;
case 2:
result.set(b, m, c, a);
break;
case 3:
result.set(b, c, m, a);
break;
}
};
const lerp = (a, b, t) => {
return a === b ? a : a * (1 - t) + b * t;
};
const {
chunkData,
chunkSize,
vertexData,
shData,
shBands
} = gsplatData;
const shCoeffs = [3, 8, 15][shBands - 1];
this.read = i => {
const ci = Math.floor(i / 256) * chunkSize;
if (p) {
unpack111011(p, vertexData[i * 4 + 0]);
p.x = lerp(chunkData[ci + 0], chunkData[ci + 3], p.x);
p.y = lerp(chunkData[ci + 1], chunkData[ci + 4], p.y);
p.z = lerp(chunkData[ci + 2], chunkData[ci + 5], p.z);
}
if (r) {
unpackRot(r, vertexData[i * 4 + 1]);
}
if (s) {
unpack111011(s, vertexData[i * 4 + 2]);
s.x = lerp(chunkData[ci + 6], chunkData[ci + 9], s.x);
s.y = lerp(chunkData[ci + 7], chunkData[ci + 10], s.y);
s.z = lerp(chunkData[ci + 8], chunkData[ci + 11], s.z);
}
if (c) {
unpack8888(c, vertexData[i * 4 + 3]);
if (chunkSize > 12) {
c.x = lerp(chunkData[ci + 12], chunkData[ci + 15], c.x);
c.y = lerp(chunkData[ci + 13], chunkData[ci + 16], c.y);
c.z = lerp(chunkData[ci + 14], chunkData[ci + 17], c.z);
}
}
if (sh && shBands > 0) {
for (let j = 0; j < 3; ++j) {
for (let k = 0; k < 15; ++k) {
sh[j * 15 + k] = k < shCoeffs ? shData[(i * 3 + j) * shCoeffs + k] * (8 / 255) - 4 : 0;
}
}
}
};
}
}
class GSplatCompressedData {
constructor() {
this.numSplats = void 0;
this.chunkData = void 0;
this.vertexData = void 0;
this.shData = void 0;
}
createIter(p, r, s, c, sh) {
return new SplatCompressedIterator(this, p, r, s, c, sh);
}
calcAabb(result) {
const {
chunkData,
numChunks,
chunkSize
} = this;
let s = Math.exp(Math.max(chunkData[9], chunkData[10], chunkData[11]));
let mx = chunkData[0] - s;
let my = chunkData[1] - s;
let mz = chunkData[2] - s;
let Mx = chunkData[3] + s;
let My = chunkData[4] + s;
let Mz = chunkData[5] + s;
for (let i = 1; i < numChunks; ++i) {
const off = i * chunkSize;
s = Math.exp(Math.max(chunkData[off + 9], chunkData[off + 10], chunkData[off + 11]));
mx = Math.min(mx, chunkData[off + 0] - s);
my = Math.min(my, chunkData[off + 1] - s);
mz = Math.min(mz, chunkData[off + 2] - s);
Mx = Math.max(Mx, chunkData[off + 3] + s);
My = Math.max(My, chunkData[off + 4] + s);
Mz = Math.max(Mz, chunkData[off + 5] + s);
}
result.center.set((mx + Mx) * 0.5, (my + My) * 0.5, (mz + Mz) * 0.5);
result.halfExtents.set((Mx - mx) * 0.5, (My - my) * 0.5, (Mz - mz) * 0.5);
return true;
}
getCenters(result) {
const {
vertexData,
chunkData,
numChunks,
chunkSize
} = this;
let mx, my, mz, Mx, My, Mz;
for (let c = 0; c < numChunks; ++c) {
const off = c * chunkSize;
mx = chunkData[off + 0];
my = chunkData[off + 1];
mz = chunkData[off + 2];
Mx = chunkData[off + 3];
My = chunkData[off + 4];
Mz = chunkData[off + 5];
const end = Math.min(this.numSplats, (c + 1) * 256);
for (let i = c * 256; i < end; ++i) {
const p = vertexData[i * 4];
const px = (p >>> 21) / 2047;
const py = (p >>> 11 & 0x3ff) / 1023;
const pz = (p & 0x7ff) / 2047;
result[i * 3 + 0] = (1 - px) * mx + px * Mx;
result[i * 3 + 1] = (1 - py) * my + py * My;
result[i * 3 + 2] = (1 - pz) * mz + pz * Mz;
}
}
}
calcFocalPoint(result) {
const {
chunkData,
numChunks,
chunkSize
} = this;
result.x = 0;
result.y = 0;
result.z = 0;
for (let i = 0; i < numChunks; ++i) {
const off = i * chunkSize;
result.x += chunkData[off + 0] + chunkData[off + 3];
result.y += chunkData[off + 1] + chunkData[off + 4];
result.z += chunkData[off + 2] + chunkData[off + 5];
}
result.mulScalar(0.5 / numChunks);
}
get isCompressed() {
return true;
}
get numChunks() {
return Math.ceil(this.numSplats / 256);
}
get chunkSize() {
return this.chunkData.length / this.numChunks;
}
get shBands() {
var _sizes, _this$shData;
const sizes = {
3: 1,
8: 2,
15: 3
};
return (_sizes = sizes[((_this$shData = this.shData) == null ? void 0 : _this$shData.length) / this.numSplats / 3]) != null ? _sizes : 0;
}
decompress() {
const members = ['x', 'y', 'z', 'f_dc_0', 'f_dc_1', 'f_dc_2', 'opacity', 'rot_0', 'rot_1', 'rot_2', 'rot_3', 'scale_0', 'scale_1', 'scale_2'];
const {
shBands
} = this;
if (shBands > 0) {
const shMembers = [];
for (let i = 0; i < 45; ++i) {
shMembers.push(`f_rest_${i}`);
}
members.splice(members.indexOf('f_dc_0') + 1, 0, ...shMembers);
}
const data = {};
members.forEach(name => {
data[name] = new Float32Array(this.numSplats);
});
const p = new Vec3();
const r = new Quat();
const s = new Vec3();
const c = new Vec4();
const sh = shBands > 0 ? new Float32Array(45) : null;
const iter = this.createIter(p, r, s, c, sh);
for (let i = 0; i < this.numSplats; ++i) {
iter.read(i);
data.x[i] = p.x;
data.y[i] = p.y;
data.z[i] = p.z;
data.rot_1[i] = r.x;
data.rot_2[i] = r.y;
data.rot_3[i] = r.z;
data.rot_0[i] = r.w;
data.scale_0[i] = s.x;
data.scale_1[i] = s.y;
data.scale_2[i] = s.z;
data.f_dc_0[i] = (c.x - 0.5) / SH_C0;
data.f_dc_1[i] = (c.y - 0.5) / SH_C0;
data.f_dc_2[i] = (c.z - 0.5) / SH_C0;
data.opacity[i] = c.w <= 0 ? -40 : c.w >= 1 ? 40 : -Math.log(1 / c.w - 1);
if (sh) {
for (let _c = 0; _c < 45; ++_c) {
data[`f_rest_${_c}`][i] = sh[_c];
}
}
}
return new GSplatData([{
name: 'vertex',
count: this.numSplats,
properties: members.map(name => {
return {
name: name,
type: 'float',
byteSize: 4,
storage: data[name]
};
})
}]);
}
}
export { GSplatCompressedData };