UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

201 lines (200 loc) 5.46 kB
import { Asset } from "../../framework/asset/asset.js"; import { http, Http } from "../../platform/net/http.js"; import { GSplatResource } from "../../scene/gsplat/gsplat-resource.js"; import { GSplatSogData } from "../../scene/gsplat/gsplat-sog-data.js"; import { GSplatSogResource } from "../../scene/gsplat/gsplat-sog-resource.js"; const combineProgress = (target, assets) => { const map = /* @__PURE__ */ new Map(); const count = assets.length; const fire = () => { let loaded = 0; let total = 0; map.forEach((value) => { loaded += value.loaded; total += value.total; }); const reporting = map.size; if (reporting > 0 && reporting < count) { total = Math.ceil(total * count / reporting); } target.fire("progress", loaded, total); }; assets.forEach((asset) => { const progress = (loaded, total) => { map.set(asset, { loaded, total }); fire(); }; const done = () => { asset.off("progress", progress); asset.off("load", done); asset.off("error", done); }; asset.on("progress", progress); asset.on("load", done); asset.on("error", done); }); }; const upgradeMeta = (meta) => { const result = { version: 1, count: meta.means.shape[0], means: { mins: meta.means.mins, maxs: meta.means.maxs, files: meta.means.files }, scales: { mins: meta.scales.mins, maxs: meta.scales.maxs, files: meta.scales.files }, quats: { files: meta.quats.files }, sh0: { mins: meta.sh0.mins, maxs: meta.sh0.maxs, files: meta.sh0.files } }; if (meta.shN) { result.shN = { mins: meta.shN.mins, maxs: meta.shN.maxs, files: meta.shN.files }; } return result; }; class SogParser { app; maxRetries; constructor(app, maxRetries) { this.app = app; this.maxRetries = maxRetries; } _shouldAbort(asset, unloaded) { if (unloaded || !this.app.assets.get(asset.id)) return true; if (!this.app?.graphicsDevice || this.app.graphicsDevice._destroyed) return true; return false; } async loadTextures(url, callback, asset, meta) { const gsplatCentersEnabledAtLoad = this.app.scene?.gsplatCentersEnabled !== false; if (meta.version !== 2) { meta = upgradeMeta(meta); } const { assets } = this.app; const subs = ["means", "quats", "scales", "sh0", "shN"]; const textures = {}; const promises = []; const base = window.document?.baseURI ?? window.location.href; subs.forEach((sub) => { const files = meta[sub]?.files ?? []; textures[sub] = files.map((filename) => { const texture = new Asset(filename, "texture", { url: asset.options?.mapUrl?.(filename) ?? new URL(filename, new URL(url.load, base).toString()).toString(), filename }, { mipmaps: false }, { crossOrigin: "anonymous" }); const promise = new Promise((resolve, reject) => { texture.on("load", () => resolve(null)); texture.on("error", (err) => reject(err)); }); assets.add(texture); promises.push(promise); return texture; }); }); const textureAssets = subs.map((sub) => textures[sub]).flat(); let unloaded = false; asset.once("unload", () => { unloaded = true; textureAssets.forEach((t) => { assets.remove(t); t.unload(); }); }); combineProgress(asset, textureAssets); textureAssets.forEach((t) => assets.load(t)); await Promise.allSettled(promises); if (this._shouldAbort(asset, unloaded)) { textureAssets.forEach((t) => { assets.remove(t); t.unload(); }); callback(null, null); return; } const data = new GSplatSogData(); data.url = url.original; data.meta = meta; data.numSplats = meta.count; data.means_l = textures.means[0].resource; data.means_u = textures.means[1].resource; data.quats = textures.quats[0].resource; data.scales = textures.scales[0].resource; data.sh0 = textures.sh0[0].resource; data.sh_centroids = textures.shN?.[0]?.resource; data.sh_labels = textures.shN?.[1]?.resource; data.shBands = GSplatSogData.calcBands(data.sh_centroids?.width); const decompress = asset.data?.decompress; if (!decompress) { if (this._shouldAbort(asset, unloaded)) { data.destroy(); callback(null, null); return; } data.prepareCodebook(); if (gsplatCentersEnabledAtLoad) { await data.prepareGpuData(); } } if (this._shouldAbort(asset, unloaded)) { data.destroy(); callback(null, null); return; } const prepareCenters = gsplatCentersEnabledAtLoad; const resource = decompress ? new GSplatResource(this.app.graphicsDevice, await data.decompress(), { prepareCenters }) : new GSplatSogResource(this.app.graphicsDevice, data, { prepareCenters }); if (this._shouldAbort(asset, unloaded)) { resource.destroy(); callback(null, null); return; } callback(null, resource); } load(url, callback, asset) { if (asset.data?.means) { this.loadTextures(url, callback, asset, asset.data); } else { if (typeof url === "string") { url = { load: url, original: url }; } const options = { retry: this.maxRetries > 0, maxRetries: this.maxRetries, responseType: Http.ResponseType.JSON }; http.get(url.load, options, (err, meta) => { if (this._shouldAbort(asset, false)) { callback(null, null); return; } if (!err) { this.loadTextures(url, callback, asset, meta); } else { callback(`Error loading gsplat meta: ${url.original} [${err}]`); } }); } } } export { SogParser };