playcanvas
Version:
PlayCanvas WebGL game engine
322 lines (319 loc) • 9.32 kB
JavaScript
import { Color } from '../../core/math/color.js';
import { Mat4 } from '../../core/math/mat4.js';
import { Quat } from '../../core/math/quat.js';
import { Vec3 } from '../../core/math/vec3.js';
import { BoundingBox } from '../../core/shape/bounding-box.js';
const mat4 = new Mat4();
const quat = new Quat();
const aabb = new BoundingBox();
const aabb2 = new BoundingBox();
const debugColor = new Color(1, 1, 0, 0.4);
const SH_C0 = 0.28209479177387814;
class SplatIterator {
constructor(gsplatData, p, r, s, c){
const x = gsplatData.getProp('x');
const y = gsplatData.getProp('y');
const z = gsplatData.getProp('z');
const rx = gsplatData.getProp('rot_1');
const ry = gsplatData.getProp('rot_2');
const rz = gsplatData.getProp('rot_3');
const rw = gsplatData.getProp('rot_0');
const sx = gsplatData.getProp('scale_0');
const sy = gsplatData.getProp('scale_1');
const sz = gsplatData.getProp('scale_2');
const cr = gsplatData.getProp('f_dc_0');
const cg = gsplatData.getProp('f_dc_1');
const cb = gsplatData.getProp('f_dc_2');
const ca = gsplatData.getProp('opacity');
const sigmoid = (v)=>{
if (v > 0) {
return 1 / (1 + Math.exp(-v));
}
const t = Math.exp(v);
return t / (1 + t);
};
this.read = (i)=>{
if (p) {
p.x = x[i];
p.y = y[i];
p.z = z[i];
}
if (r) {
r.set(rx[i], ry[i], rz[i], rw[i]);
}
if (s) {
s.set(Math.exp(sx[i]), Math.exp(sy[i]), Math.exp(sz[i]));
}
if (c) {
c.set(0.5 + cr[i] * SH_C0, 0.5 + cg[i] * SH_C0, 0.5 + cb[i] * SH_C0, sigmoid(ca[i]));
}
};
}
}
const calcSplatMat = (result, p, r)=>{
quat.set(r.x, r.y, r.z, r.w).normalize();
result.setTRS(p, quat, Vec3.ONE);
};
class GSplatData {
constructor(elements, comments = []){
this.elements = elements;
this.numSplats = this.getElement('vertex').count;
this.comments = comments;
}
static calcSplatAabb(result, p, r, s) {
calcSplatMat(mat4, p, r);
aabb.center.set(0, 0, 0);
aabb.halfExtents.set(s.x * 2, s.y * 2, s.z * 2);
result.setFromTransformedAabb(aabb, mat4);
}
getProp(name, elementName = 'vertex') {
return this.getElement(elementName)?.properties.find((p)=>p.name === name)?.storage;
}
getElement(name) {
return this.elements.find((e)=>e.name === name);
}
addProp(name, storage) {
this.getElement('vertex').properties.push({
type: 'float',
name,
storage,
byteSize: 4
});
}
createIter(p, r, s, c) {
return new SplatIterator(this, p, r, s, c);
}
calcAabb(result, pred) {
let mx, my, mz, Mx, My, Mz;
let first = true;
const x = this.getProp('x');
const y = this.getProp('y');
const z = this.getProp('z');
const sx = this.getProp('scale_0');
const sy = this.getProp('scale_1');
const sz = this.getProp('scale_2');
for(let i = 0; i < this.numSplats; ++i){
if (pred && !pred(i)) {
continue;
}
const px = x[i];
const py = y[i];
const pz = z[i];
const scale = Math.max(sx[i], sy[i], sz[i]);
if (!isFinite(px) || !isFinite(py) || !isFinite(pz) || !isFinite(scale)) {
continue;
}
const scaleVal = 2.0 * Math.exp(scale);
if (first) {
first = false;
mx = px - scaleVal;
my = py - scaleVal;
mz = pz - scaleVal;
Mx = px + scaleVal;
My = py + scaleVal;
Mz = pz + scaleVal;
} else {
mx = Math.min(mx, px - scaleVal);
my = Math.min(my, py - scaleVal);
mz = Math.min(mz, pz - scaleVal);
Mx = Math.max(Mx, px + scaleVal);
My = Math.max(My, py + scaleVal);
Mz = Math.max(Mz, pz + scaleVal);
}
}
if (!first) {
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 !first;
}
calcAabbExact(result, pred) {
const p = new Vec3();
const r = new Quat();
const s = new Vec3();
const iter = this.createIter(p, r, s);
let first = true;
for(let i = 0; i < this.numSplats; ++i){
if (pred && !pred(i)) {
continue;
}
iter.read(i);
if (first) {
first = false;
GSplatData.calcSplatAabb(result, p, r, s);
} else {
GSplatData.calcSplatAabb(aabb2, p, r, s);
result.add(aabb2);
}
}
return !first;
}
getCenters() {
const x = this.getProp('x');
const y = this.getProp('y');
const z = this.getProp('z');
const result = new Float32Array(this.numSplats * 3);
for(let i = 0; i < this.numSplats; ++i){
result[i * 3 + 0] = x[i];
result[i * 3 + 1] = y[i];
result[i * 3 + 2] = z[i];
}
return result;
}
calcFocalPoint(result, pred) {
const x = this.getProp('x');
const y = this.getProp('y');
const z = this.getProp('z');
const sx = this.getProp('scale_0');
const sy = this.getProp('scale_1');
const sz = this.getProp('scale_2');
result.x = 0;
result.y = 0;
result.z = 0;
let sum = 0;
for(let i = 0; i < this.numSplats; ++i){
if (pred && !pred(i)) {
continue;
}
const px = x[i];
const py = y[i];
const pz = z[i];
if (!isFinite(px) || !isFinite(py) || !isFinite(pz)) {
continue;
}
const weight = 1.0 / (1.0 + Math.exp(Math.max(sx[i], sy[i], sz[i])));
result.x += px * weight;
result.y += py * weight;
result.z += pz * weight;
sum += weight;
}
result.mulScalar(1 / sum);
}
renderWireframeBounds(scene, worldMat) {
const p = new Vec3();
const r = new Quat();
const s = new Vec3();
const min = new Vec3();
const max = new Vec3();
const iter = this.createIter(p, r, s);
for(let i = 0; i < this.numSplats; ++i){
iter.read(i);
calcSplatMat(mat4, p, r);
mat4.mul2(worldMat, mat4);
min.set(s.x * -2, s.y * -2, s.z * -2);
max.set(s.x * 2.0, s.y * 2.0, s.z * 2.0);
scene.immediate.drawWireAlignedBox(min, max, debugColor, true, scene.defaultDrawLayer, mat4);
}
}
get isCompressed() {
return false;
}
get shBands() {
const numProps = ()=>{
for(let i = 0; i < 45; ++i){
if (!this.getProp(`f_rest_${i}`)) {
return i;
}
}
return 45;
};
const sizes = {
9: 1,
24: 2,
45: 3
};
return sizes[numProps()] ?? 0;
}
calcMortonOrder() {
const calcMinMax = (arr)=>{
let min = arr[0];
let max = arr[0];
for(let i = 1; i < arr.length; i++){
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
return {
min,
max
};
};
const encodeMorton3 = (x, y, z)=>{
const Part1By2 = (x)=>{
x &= 0x000003ff;
x = (x ^ x << 16) & 0xff0000ff;
x = (x ^ x << 8) & 0x0300f00f;
x = (x ^ x << 4) & 0x030c30c3;
x = (x ^ x << 2) & 0x09249249;
return x;
};
return (Part1By2(z) << 2) + (Part1By2(y) << 1) + Part1By2(x);
};
const x = this.getProp('x');
const y = this.getProp('y');
const z = this.getProp('z');
const { min: minX, max: maxX } = calcMinMax(x);
const { min: minY, max: maxY } = calcMinMax(y);
const { min: minZ, max: maxZ } = calcMinMax(z);
const sizeX = minX === maxX ? 0 : 1024 / (maxX - minX);
const sizeY = minY === maxY ? 0 : 1024 / (maxY - minY);
const sizeZ = minZ === maxZ ? 0 : 1024 / (maxZ - minZ);
const codes = new Map();
for(let i = 0; i < this.numSplats; i++){
const ix = Math.min(1023, Math.floor((x[i] - minX) * sizeX));
const iy = Math.min(1023, Math.floor((y[i] - minY) * sizeY));
const iz = Math.min(1023, Math.floor((z[i] - minZ) * sizeZ));
const code = encodeMorton3(ix, iy, iz);
const val = codes.get(code);
if (val) {
val.push(i);
} else {
codes.set(code, [
i
]);
}
}
const keys = Array.from(codes.keys()).sort((a, b)=>a - b);
const indices = new Uint32Array(this.numSplats);
let idx = 0;
for(let i = 0; i < keys.length; ++i){
const val = codes.get(keys[i]);
for(let j = 0; j < val.length; ++j){
indices[idx++] = val[j];
}
}
return indices;
}
reorder(order) {
const cache = new Map();
const getStorage = (size)=>{
if (cache.has(size)) {
const buffer = cache.get(size);
cache.delete(size);
return buffer;
}
return new ArrayBuffer(size);
};
const returnStorage = (buffer)=>{
cache.set(buffer.byteLength, buffer);
};
const reorder = (data)=>{
const result = new data.constructor(getStorage(data.byteLength));
for(let i = 0; i < order.length; i++){
result[i] = data[order[i]];
}
returnStorage(data.buffer);
return result;
};
this.elements.forEach((element)=>{
element.properties.forEach((property)=>{
if (property.storage) {
property.storage = reorder(property.storage);
}
});
});
}
reorderData() {
this.reorder(this.calcMortonOrder());
}
}
export { GSplatData };