luma-splatting-for-babylonjs
Version:
LumaAI Web Library for Babylon.js
163 lines (162 loc) • 4.84 kB
JavaScript
var R = Object.defineProperty;
var W = (r, e, t) => e in r ? R(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
var G = (r, e, t) => W(r, typeof e != "symbol" ? e + "" : e, t);
import { Quaternion as H, GaussianSplattingMesh as I } from "@babylonjs/core";
const N = async (r) => {
var h;
const t = `https://webapp.engineeringlumalabs.com/api/v3/captures/${r}/public`, s = await fetch(t, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then((n) => n.json());
if (!((h = s.response) != null && h.artifacts))
return null;
const i = s.response.artifacts, l = i.find((n) => n.type === "gs_point_cloud"), f = i.find((n) => n.type === "gs_point_cloud_meta");
return !l || !f ? null : {
metaFileUrl: f.url,
plyFilesUrl: l.url
};
}, V = (r) => (e, t) => (s, i) => (r - e) / (t - e) * (i - s) + s, Q = (r, e) => {
var L;
const t = new Uint8Array(r), s = new TextDecoder().decode(t.slice(0, 1024 * 10)), i = `end_header
`, l = s.indexOf(i);
if (l < 0 || !s)
return r;
const f = (L = /element vertex (\d+)\n/.exec(s)) == null ? void 0 : L[1];
if (!f)
throw new Error("cannot parse vertex count from header!");
const h = Number.parseInt(f);
let n = 0;
const S = {
double: 8,
int: 4,
uint: 4,
float: 4,
short: 2,
ushort: 2,
uchar: 1
}, U = [], q = s.slice(0, l).split(`
`).filter((o) => o.startsWith("property "));
for (const o of q) {
const [b, p, c] = o.split(" ");
if (U.push({ name: c, type: p, offset: n }), !S[p]) throw new Error(`Unsupported property type: ${p}`);
n += S[p];
}
const _ = 3 * 4 + 3 * 4 + 4 + 4, w = 0.28209479177387814, z = new DataView(r, l + i.length), y = new ArrayBuffer(_ * h), d = new H();
for (let o = 0; o < h; o++) {
const b = new Float32Array(y, o * _, 3), p = new Float32Array(y, o * _ + 12, 3), c = new Uint8ClampedArray(y, o * _ + 24, 4), x = new Uint8ClampedArray(y, o * _ + 28, 4);
let M = 255, T = 0, $ = 0, j = 0;
for (let A = 0; A < U.length; A++) {
const m = U[A];
let u, a;
switch (m.type) {
case "ushort":
u = z.getUint16(m.offset + o * n, !0), a = V(u)(0, 65535);
break;
case "uchar":
u = z.getUint8(m.offset + o * n), a = V(u)(0, 255);
break;
default:
throw new Error(`Unsupported property type: ${m.type}`);
}
const {
f_dc_max: D,
f_dc_min: E,
opacity_max: O,
opacity_min: P,
rotation_max: k,
rotation_min: g,
scale_max: v,
scale_min: B,
xyz_max: F,
xyz_min: C
} = e.gaussians.bounds;
switch (m.name) {
case "x":
b[0] = a(C[0], F[0]);
break;
case "y":
b[1] = a(C[1], F[1]);
break;
case "z":
b[2] = a(C[2], F[2]);
break;
case "scale_0":
p[0] = Math.exp(a(B[0], v[0]));
break;
case "scale_1":
p[1] = Math.exp(a(B[1], v[1]));
break;
case "scale_2":
p[2] = Math.exp(a(B[2], v[2]));
break;
case "red":
c[0] = u;
break;
case "green":
c[1] = u;
break;
case "blue":
c[2] = u;
break;
case "f_dc_0":
c[0] = (0.5 + w * a(E[0], D[0])) * 255;
break;
case "f_dc_1":
c[1] = (0.5 + w * a(E[1], D[1])) * 255;
break;
case "f_dc_2":
c[2] = (0.5 + w * a(E[2], D[2])) * 255;
break;
case "f_dc_3":
c[3] = (0.5 + w * u) * 255;
break;
case "opacity":
c[3] = 1 / (1 + Math.exp(-a(P[0], O[0]))) * 255;
break;
case "rot_0":
M = a(g[0], k[0]);
break;
case "rot_1":
T = a(g[1], k[1]);
break;
case "rot_2":
$ = a(g[2], k[2]);
break;
case "rot_3":
j = a(g[3], k[3]);
break;
}
}
d.set(T, $, j, M), d.normalize(), x[0] = d.w * 128 + 128, x[1] = d.x * 128 + 128, x[2] = d.y * 128 + 128, x[3] = d.z * 128 + 128;
}
return y;
};
class X {
constructor(e, t) {
G(this, "_splat");
this.name = e, this.scene = t, this._splat = new I(e, null, t);
}
get splat() {
return this._splat;
}
async loadDataWithUuidAsync(e) {
const t = await N(e);
if (!t)
return;
const { metaFileUrl: s, plyFilesUrl: i } = t, [l, f] = await Promise.all([
fetch(i).then((n) => n.arrayBuffer()),
fetch(s).then(
(n) => n.json()
)
]), h = await Q(
l,
f
);
this._splat = new I(this.name, null, this.scene), await this._splat.loadDataAsync(h);
}
}
export {
X as LumaGaussianSplatting
};