@needle-tools/gltf-progressive
Version:
three.js support for loading glTF or GLB files that contain progressive loading data
1,102 lines (1,101 loc) • 44.7 kB
JavaScript
var Xe = Object.defineProperty;
var qe = (n, e, t) => e in n ? Xe(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t;
var d = (n, e, t) => (qe(n, typeof e != "symbol" ? e + "" : e, t), t), Ae = (n, e, t) => {
if (!e.has(n))
throw TypeError("Cannot " + t);
};
var m = (n, e, t) => (Ae(n, e, "read from private field"), t ? t.call(n) : e.get(n)), J = (n, e, t) => {
if (e.has(n))
throw TypeError("Cannot add the same private member more than once");
e instanceof WeakSet ? e.add(n) : e.set(n, t);
}, z = (n, e, t, s) => (Ae(n, e, "write to private field"), s ? s.call(n, t) : e.set(n, t), t);
import { BufferGeometry as ge, Mesh as j, Texture as re, TextureLoader as Ke, Matrix4 as Pe, Clock as Ye, MeshStandardMaterial as He, Sphere as Je, Box3 as Ee, Vector3 as X } from "three";
import { GLTFLoader as Te } from "three/examples/jsm/loaders/GLTFLoader.js";
import { MeshoptDecoder as Qe } from "three/examples/jsm/libs/meshopt_decoder.module.js";
import { DRACOLoader as Ze } from "three/examples/jsm/loaders/DRACOLoader.js";
import { KTX2Loader as je } from "three/examples/jsm/loaders/KTX2Loader.js";
const et = "";
globalThis.GLTF_PROGRESSIVE_VERSION = et;
console.debug("[gltf-progressive] version -");
let V = "https://www.gstatic.com/draco/versioned/decoders/1.5.7/", ee = "https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/";
const tt = V, st = ee, Ie = new URL(V + "draco_decoder.js");
Ie.searchParams.append("range", "true");
fetch(Ie, {
method: "GET",
headers: {
Range: "bytes=0-1"
}
}).catch((n) => {
console.debug(`Failed to fetch remote Draco decoder from ${V} (offline: ${typeof navigator < "u" ? navigator.onLine : "unknown"})`), V === tt && rt("./include/draco/"), ee === st && ot("./include/ktx2/");
}).finally(() => {
Ge();
});
function rt(n) {
V = n, B && B[Oe] != V ? (console.debug("Updating Draco decoder path to " + n), B[Oe] = V, B.setDecoderPath(V), B.preload()) : console.debug("Setting Draco decoder path to " + n);
}
function ot(n) {
ee = n, U && U.transcoderPath != ee ? (console.debug("Updating KTX2 transcoder path to " + n), U.setTranscoderPath(ee), U.init()) : console.debug("Setting KTX2 transcoder path to " + n);
}
const Oe = Symbol("dracoDecoderPath");
let B, pe, U;
function Ge() {
B || (B = new Ze(), B[Oe] = V, B.setDecoderPath(V), B.setDecoderConfig({ type: "js" }), B.preload()), U || (U = new je(), U.setTranscoderPath(ee), U.init()), pe || (pe = Qe);
}
function $e(n) {
return Ge(), n ? U.detectSupport(n) : n !== null && console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"), { dracoLoader: B, ktx2Loader: U, meshoptDecoder: pe };
}
function Ue(n) {
n.dracoLoader || n.setDRACOLoader(B), n.ktx2Loader || n.setKTX2Loader(U), n.meshoptDecoder || n.setMeshoptDecoder(pe);
}
const _e = /* @__PURE__ */ new WeakMap();
function Fe(n, e) {
let t = _e.get(n);
t ? t = Object.assign(t, e) : t = e, _e.set(n, t);
}
const De = Te.prototype.load;
function it(...n) {
const e = _e.get(this);
let t = n[0];
const s = new URL(t, window.location.href);
if (s.hostname.endsWith("needle.tools")) {
const r = (e == null ? void 0 : e.progressive) !== void 0 ? e.progressive : !0, o = e != null && e.usecase ? e.usecase : "default";
r ? this.requestHeader.Accept = `*/*;progressive=allowed;usecase=${o}` : this.requestHeader.Accept = `*/*;usecase=${o}`, t = s.toString();
}
return n[0] = t, De == null ? void 0 : De.call(this, ...n);
}
Te.prototype.load = it;
le("debugprogressive");
function le(n) {
if (typeof window > "u")
return !1;
const t = new URL(window.location.href).searchParams.get(n);
return t == null || t === "0" || t === "false" ? !1 : t === "" ? !0 : t;
}
function nt(n, e) {
if (e === void 0 || e.startsWith("./") || e.startsWith("http") || n === void 0)
return e;
const t = n.lastIndexOf("/");
if (t >= 0) {
const s = n.substring(0, t + 1);
for (; s.endsWith("/") && e.startsWith("/"); )
e = e.substring(1);
return s + e;
}
return e;
}
let oe;
function at() {
return oe !== void 0 || (oe = /iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent), le("debugprogressive") && console.log("[glTF Progressive]: isMobileDevice", oe)), oe;
}
const lt = typeof window > "u" && typeof document > "u", be = Symbol("needle:raycast-mesh");
function ce(n) {
return (n == null ? void 0 : n[be]) instanceof ge ? n[be] : null;
}
function ct(n, e) {
if ((n.type === "Mesh" || n.type === "SkinnedMesh") && !ce(n)) {
const s = ft(e);
s.userData = { isRaycastMesh: !0 }, n[be] = s;
}
}
function ut(n = !0) {
if (n) {
if (ie)
return;
const e = ie = j.prototype.raycast;
j.prototype.raycast = function(t, s) {
const i = this, r = ce(i);
let o;
r && i.isMesh && (o = i.geometry, i.geometry = r), e.call(this, t, s), o && (i.geometry = o);
};
} else {
if (!ie)
return;
j.prototype.raycast = ie, ie = null;
}
}
let ie = null;
function ft(n) {
const e = new ge();
for (const t in n.attributes)
e.setAttribute(t, n.getAttribute(t));
return e.setIndex(n.getIndex()), e;
}
const Z = new Array(), q = "NEEDLE_progressive", L = le("debugprogressive"), Me = Symbol("needle-progressive-texture"), ae = /* @__PURE__ */ new Map(), Se = /* @__PURE__ */ new Set();
if (L) {
let n = function() {
e += 1, console.log("Toggle LOD level", e, ae), ae.forEach((i, r) => {
for (const o of i.keys) {
const a = r[o];
if (a != null)
if (a.isBufferGeometry === !0) {
const l = T.getMeshLODInformation(a), u = l ? Math.min(e, l.lods.length) : 0;
r["DEBUG:LOD"] = u, l && (t = Math.max(t, l.lods.length - 1));
} else
r.isMaterial === !0 && (r["DEBUG:LOD"] = e);
}
}), e >= t && (e = -1);
}, e = -1, t = 2, s = !1;
window.addEventListener("keyup", (i) => {
i.key === "p" && n(), i.key === "w" && (s = !s, Se && Se.forEach((r) => {
r.name != "BackgroundCubeMaterial" && r.glyphMap == null && "wireframe" in r && (r.wireframe = s);
}));
});
}
function Ce(n, e, t) {
var i;
if (!L)
return;
ae.has(n) || ae.set(n, { keys: [], sourceId: t });
const s = ae.get(n);
((i = s == null ? void 0 : s.keys) == null ? void 0 : i.includes(e)) == !1 && s.keys.push(e);
}
const v = class {
constructor(e, t) {
d(this, "parser");
d(this, "url");
d(this, "_isLoadingMesh");
d(this, "loadMesh", (e) => {
var s, i;
if (this._isLoadingMesh)
return null;
const t = (i = (s = this.parser.json.meshes[e]) == null ? void 0 : s.extensions) == null ? void 0 : i[q];
return t ? (this._isLoadingMesh = !0, this.parser.getDependency("mesh", e).then((r) => {
var o;
return this._isLoadingMesh = !1, r && v.registerMesh(this.url, t.guid, r, (o = t.lods) == null ? void 0 : o.length, void 0, t), r;
})) : null;
});
L && console.log("Progressive extension registered for", t), this.parser = e, this.url = t;
}
/** The name of the extension */
get name() {
return q;
}
static getMeshLODInformation(e) {
const t = this.getAssignedLODInformation(e);
return t != null && t.key ? this.lodInfos.get(t.key) : null;
}
static getMaterialMinMaxLODsCount(e, t) {
const s = this, i = "LODS:minmax", r = e[i];
if (r != null)
return r;
if (t || (t = {
min_count: 1 / 0,
max_count: 0,
lods: []
}), Array.isArray(e)) {
for (const a of e)
this.getMaterialMinMaxLODsCount(a, t);
return e[i] = t, t;
}
if (L === "verbose" && console.log("getMaterialMinMaxLODsCount", e), e.type === "ShaderMaterial" || e.type === "RawShaderMaterial") {
const a = e;
for (const l of Object.keys(a.uniforms)) {
const u = a.uniforms[l].value;
(u == null ? void 0 : u.isTexture) === !0 && o(u, t);
}
} else if (e.isMaterial)
for (const a of Object.keys(e)) {
const l = e[a];
(l == null ? void 0 : l.isTexture) === !0 && o(l, t);
}
return e[i] = t, t;
function o(a, l) {
const u = s.getAssignedLODInformation(a);
if (u) {
const c = s.lodInfos.get(u.key);
if (c && c.lods) {
l.min_count = Math.min(l.min_count, c.lods.length), l.max_count = Math.max(l.max_count, c.lods.length);
for (let g = 0; g < c.lods.length; g++) {
const p = c.lods[g];
p.width && (l.lods[g] = l.lods[g] || { min_height: 1 / 0, max_height: 0 }, l.lods[g].min_height = Math.min(l.lods[g].min_height, p.height), l.lods[g].max_height = Math.max(l.lods[g].max_height, p.height));
}
}
}
}
}
/** Check if a LOD level is available for a mesh or a texture
* @param obj the mesh or texture to check
* @param level the level of detail to check for (0 is the highest resolution). If undefined, the function checks if any LOD level is available
* @returns true if the LOD level is available (or if any LOD level is available if level is undefined)
*/
static hasLODLevelAvailable(e, t) {
var r;
if (Array.isArray(e)) {
for (const o of e)
if (this.hasLODLevelAvailable(o, t))
return !0;
return !1;
}
if (e.isMaterial === !0) {
for (const o of Object.keys(e)) {
const a = e[o];
if (a && a.isTexture && this.hasLODLevelAvailable(a, t))
return !0;
}
return !1;
} else if (e.isGroup === !0) {
for (const o of e.children)
if (o.isMesh === !0 && this.hasLODLevelAvailable(o, t))
return !0;
}
let s, i;
if (e.isMesh ? s = e.geometry : (e.isBufferGeometry || e.isTexture) && (s = e), s && (r = s == null ? void 0 : s.userData) != null && r.LODS) {
const o = s.userData.LODS;
if (i = this.lodInfos.get(o.key), t === void 0)
return i != null;
if (i)
return Array.isArray(i.lods) ? t < i.lods.length : t === 0;
}
return !1;
}
/** Load a different resolution of a mesh (if available)
* @param context the context
* @param source the sourceid of the file from which the mesh is loaded (this is usually the component's sourceId)
* @param mesh the mesh to load the LOD for
* @param level the level of detail to load (0 is the highest resolution)
* @returns a promise that resolves to the mesh with the requested LOD level
* @example
* ```javascript
* const mesh = this.gameObject as Mesh;
* NEEDLE_progressive.assignMeshLOD(context, sourceId, mesh, 1).then(mesh => {
* console.log("Mesh with LOD level 1 loaded", mesh);
* });
* ```
*/
static assignMeshLOD(e, t) {
var s;
if (!e)
return Promise.resolve(null);
if (e instanceof j || e.isMesh === !0) {
const i = e.geometry, r = this.getAssignedLODInformation(i);
if (!r)
return Promise.resolve(null);
for (const o of Z)
(s = o.onBeforeGetLODMesh) == null || s.call(o, e, t);
return e["LOD:requested level"] = t, v.getOrLoadLOD(i, t).then((o) => {
if (Array.isArray(o)) {
const a = r.index || 0;
o = o[a];
}
return e["LOD:requested level"] === t && (delete e["LOD:requested level"], o && i != o && ((o == null ? void 0 : o.isBufferGeometry) ? (e.geometry = o, L && Ce(e, "geometry", r.url)) : L && console.error("Invalid LOD geometry", o))), o;
}).catch((o) => (console.error("Error loading mesh LOD", e, o), null));
} else
L && console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh", e);
return Promise.resolve(null);
}
static assignTextureLOD(e, t = 0) {
if (!e)
return Promise.resolve(null);
if (e.isMesh === !0) {
const s = e;
if (Array.isArray(s.material)) {
const i = new Array();
for (const r of s.material) {
const o = this.assignTextureLOD(r, t);
i.push(o);
}
return Promise.all(i).then((r) => {
const o = new Array();
for (const a of r)
Array.isArray(a) && o.push(...a);
return o;
});
} else
return this.assignTextureLOD(s.material, t);
}
if (e.isMaterial === !0) {
const s = e, i = [], r = new Array();
if (L && Se.add(s), s.uniforms && (s.isRawShaderMaterial || s.isShaderMaterial === !0)) {
const o = s;
for (const a of Object.keys(o.uniforms)) {
const l = o.uniforms[a].value;
if ((l == null ? void 0 : l.isTexture) === !0) {
const u = this.assignTextureLODForSlot(l, t, s, a).then((c) => (c && o.uniforms[a].value != c && (o.uniforms[a].value = c, o.uniformsNeedUpdate = !0), c));
i.push(u), r.push(a);
}
}
} else
for (const o of Object.keys(s)) {
const a = s[o];
if ((a == null ? void 0 : a.isTexture) === !0) {
const l = this.assignTextureLODForSlot(a, t, s, o);
i.push(l), r.push(o);
}
}
return Promise.all(i).then((o) => {
const a = new Array();
for (let l = 0; l < o.length; l++) {
const u = o[l], c = r[l];
u && u.isTexture === !0 ? a.push({ material: s, slot: c, texture: u, level: t }) : a.push({ material: s, slot: c, texture: null, level: t });
}
return a;
});
}
if (e instanceof re || e.isTexture === !0) {
const s = e;
return this.assignTextureLODForSlot(s, t, null, null);
}
return Promise.resolve(null);
}
static assignTextureLODForSlot(e, t, s, i) {
return (e == null ? void 0 : e.isTexture) !== !0 ? Promise.resolve(null) : i === "glyphMap" ? Promise.resolve(e) : v.getOrLoadLOD(e, t).then((r) => {
if (Array.isArray(r))
return null;
if ((r == null ? void 0 : r.isTexture) === !0) {
if (r != e) {
if (s && i) {
const o = s[i];
if (o && !L) {
const a = this.getAssignedLODInformation(o);
if (a && (a == null ? void 0 : a.level) < t)
return L === "verbose" && console.warn("Assigned texture level is already higher: ", a.level, t, s, o, r), null;
}
s[i] = r;
}
if (L && i && s) {
const o = this.getAssignedLODInformation(e);
o ? Ce(s, i, o.url) : console.warn("No LOD info for texture", e);
}
}
return r;
} else
L == "verbose" && console.warn("No LOD found for", e, t);
return null;
}).catch((r) => (console.error("Error loading LOD", e, r), null));
}
afterRoot(e) {
var t, s;
return L && console.log("AFTER", this.url, e), (t = this.parser.json.textures) == null || t.forEach((i, r) => {
var o;
if (i != null && i.extensions) {
const a = i == null ? void 0 : i.extensions[q];
if (a) {
if (!a.lods) {
L && console.warn("Texture has no LODs", a);
return;
}
let l = !1;
for (const u of this.parser.associations.keys())
if (u.isTexture === !0) {
const c = this.parser.associations.get(u);
(c == null ? void 0 : c.textures) === r && (l = !0, v.registerTexture(this.url, u, (o = a.lods) == null ? void 0 : o.length, r, a));
}
l || this.parser.getDependency("texture", r).then((u) => {
var c;
u && v.registerTexture(this.url, u, (c = a.lods) == null ? void 0 : c.length, r, a);
});
}
}
}), (s = this.parser.json.meshes) == null || s.forEach((i, r) => {
if (i != null && i.extensions) {
const o = i == null ? void 0 : i.extensions[q];
if (o && o.lods) {
for (const a of this.parser.associations.keys())
if (a.isMesh) {
const l = this.parser.associations.get(a);
(l == null ? void 0 : l.meshes) === r && v.registerMesh(this.url, o.guid, a, o.lods.length, l.primitives, o);
}
}
}
}), null;
}
static async getOrLoadLOD(e, t) {
var a, l, u, c;
const s = L == "verbose", i = e.userData.LODS;
if (!i)
return null;
const r = i == null ? void 0 : i.key;
let o;
if (e.isTexture === !0) {
const g = e;
g.source && g.source[Me] && (o = g.source[Me]);
}
if (o || (o = v.lodInfos.get(r)), o) {
if (t > 0) {
let M = !1;
const w = Array.isArray(o.lods);
if (w && t >= o.lods.length ? M = !0 : w || (M = !0), M)
return this.lowresCache.get(r);
}
const g = Array.isArray(o.lods) ? (a = o.lods[t]) == null ? void 0 : a.path : o.lods;
if (!g)
return L && !o["missing:uri"] && (o["missing:uri"] = !0, console.warn("Missing uri for progressive asset for LOD " + t, o)), null;
const p = nt(i.url, g);
if (p.endsWith(".glb") || p.endsWith(".gltf")) {
if (!o.guid)
return console.warn("missing pointer for glb/gltf texture", o), null;
const M = p + "_" + o.guid, w = this.previouslyLoaded.get(M);
if (w !== void 0) {
s && console.log(`LOD ${t} was already loading/loaded: ${M}`);
let h = await w.catch((N) => (console.error(`Error loading LOD ${t} from ${p}
`, N), null)), k = !1;
if (h == null || (h instanceof re && e instanceof re ? (l = h.image) != null && l.data || (u = h.source) != null && u.data ? h = this.copySettings(e, h) : (k = !0, this.previouslyLoaded.delete(M)) : h instanceof ge && e instanceof ge && ((c = h.attributes.position) != null && c.array || (k = !0, this.previouslyLoaded.delete(M)))), !k)
return h;
}
const x = o, F = new Promise(async (h, k) => {
const N = new Te();
Ue(N), L && (await new Promise((b) => setTimeout(b, 1e3)), s && console.warn("Start loading (delayed) " + p, x.guid));
let I = p;
if (x && Array.isArray(x.lods)) {
const b = x.lods[t];
b.hash && (I += "?v=" + b.hash);
}
const A = await N.loadAsync(I).catch((b) => (console.error(`Error loading LOD ${t} from ${p}
`, b), null));
if (!A)
return null;
const W = A.parser;
s && console.log("Loading finished " + p, x.guid);
let _ = 0;
if (A.parser.json.textures) {
let b = !1;
for (const f of A.parser.json.textures) {
if (f != null && f.extensions) {
const y = f == null ? void 0 : f.extensions[q];
if (y != null && y.guid && y.guid === x.guid) {
b = !0;
break;
}
}
_++;
}
if (b) {
let f = await W.getDependency("texture", _);
return f && v.assignLODInformation(i.url, f, r, t, void 0, void 0), s && console.log('change "' + e.name + '" → "' + f.name + '"', p, _, f, M), e instanceof re && (f = this.copySettings(e, f)), f && (f.guid = x.guid), h(f);
} else
L && console.warn("Could not find texture with guid", x.guid, A.parser.json);
}
if (_ = 0, A.parser.json.meshes) {
let b = !1;
for (const f of A.parser.json.meshes) {
if (f != null && f.extensions) {
const y = f == null ? void 0 : f.extensions[q];
if (y != null && y.guid && y.guid === x.guid) {
b = !0;
break;
}
}
_++;
}
if (b) {
const f = await W.getDependency("mesh", _), y = x;
if (s && console.log(`Loaded Mesh "${f.name}"`, p, _, f, M), f.isMesh === !0) {
const O = f.geometry;
return v.assignLODInformation(i.url, O, r, t, void 0, y.density), h(O);
} else {
const O = new Array();
for (let S = 0; S < f.children.length; S++) {
const P = f.children[S];
if (P.isMesh === !0) {
const H = P.geometry;
v.assignLODInformation(i.url, H, r, t, S, y.density), O.push(H);
}
}
return h(O);
}
} else
L && console.warn("Could not find mesh with guid", x.guid, A.parser.json);
}
return h(null);
});
return this.previouslyLoaded.set(M, F), await F;
} else if (e instanceof re) {
s && console.log("Load texture from uri: " + p);
const w = await new Ke().loadAsync(p);
return w ? (w.guid = o.guid, w.flipY = !1, w.needsUpdate = !0, w.colorSpace = e.colorSpace, s && console.log(o, w)) : L && console.warn("failed loading", p), w;
}
} else
L && console.warn(`Can not load LOD ${t}: no LOD info found for "${r}" ${e.name}`, e.type);
return null;
}
static assignLODInformation(e, t, s, i, r, o) {
if (!t)
return;
t.userData || (t.userData = {});
const a = new dt(e, s, i, r, o);
t.userData.LODS = a;
}
static getAssignedLODInformation(e) {
var t;
return ((t = e == null ? void 0 : e.userData) == null ? void 0 : t.LODS) || null;
}
// private static readonly _copiedTextures: WeakMap<Texture, Texture> = new Map();
static copySettings(e, t) {
return t ? (L && console.warn(`Copy texture settings
`, e.uuid, `
`, t.uuid), t = t.clone(), t.offset = e.offset, t.repeat = e.repeat, t.colorSpace = e.colorSpace, t.magFilter = e.magFilter, t.minFilter = e.minFilter, t.wrapS = e.wrapS, t.wrapT = e.wrapT, t.flipY = e.flipY, t.anisotropy = e.anisotropy, t.mipmaps || (t.generateMipmaps = e.generateMipmaps), t) : e;
}
};
let T = v;
/**
* Register a texture with LOD information
*/
d(T, "registerTexture", (e, t, s, i, r) => {
if (L && console.log("> Progressive: register texture", i, t.name, t.uuid, t, r), !t) {
L && console.error("gltf-progressive: Register texture without texture");
return;
}
t.source && (t.source[Me] = r);
const o = r.guid;
v.assignLODInformation(e, t, o, s, i, void 0), v.lodInfos.set(o, r), v.lowresCache.set(o, t);
}), /**
* Register a mesh with LOD information
*/
d(T, "registerMesh", (e, t, s, i, r, o) => {
var u;
L && console.log("> Progressive: register mesh", r, s.name, o, s.uuid, s);
const a = s.geometry;
if (!a) {
L && console.warn("gltf-progressive: Register mesh without geometry");
return;
}
a.userData || (a.userData = {}), v.assignLODInformation(e, a, t, i, r, o.density), v.lodInfos.set(t, o);
let l = v.lowresCache.get(t);
l ? l.push(s.geometry) : l = [s.geometry], v.lowresCache.set(t, l), i > 0 && !ce(s) && ct(s, a);
for (const c of Z)
(u = c.onRegisteredNewMesh) == null || u.call(c, s, o);
}), /** A map of key = asset uuid and value = LOD information */
d(T, "lodInfos", /* @__PURE__ */ new Map()), /** cache of already loaded mesh lods */
d(T, "previouslyLoaded", /* @__PURE__ */ new Map()), /** this contains the geometry/textures that were originally loaded */
d(T, "lowresCache", /* @__PURE__ */ new Map());
class dt {
constructor(e, t, s, i, r) {
d(this, "url");
/** the key to lookup the LOD information */
d(this, "key");
d(this, "level");
/** For multi objects (e.g. a group of meshes) this is the index of the object */
d(this, "index");
/** the mesh density */
d(this, "density");
this.url = e, this.key = t, this.level = s, i != null && (this.index = i), r != null && (this.density = r);
}
}
const G = le("debugprogressive"), ht = le("noprogressive"), we = Symbol("Needle:LODSManager"), ve = Symbol("Needle:LODState"), Q = Symbol("Needle:CurrentLOD"), $ = { mesh_lod: -1, texture_lod: -1 };
var C, K, ye, te, se, me, Y;
const E = class {
// readonly plugins: NEEDLE_progressive_plugin[] = [];
constructor(e, t) {
d(this, "context");
d(this, "renderer");
d(this, "projectionScreenMatrix", new Pe());
/**
* The target triangle density is the desired max amount of triangles on screen when the mesh is filling the screen.
* @default 200_000
*/
d(this, "targetTriangleDensity", 2e5);
/**
* The update interval in frames. If set to 0, the LODs will be updated every frame. If set to 2, the LODs will be updated every second frame, etc.
* @default "auto"
*/
d(this, "updateInterval", "auto");
J(this, C, 1);
/**
* If set to true, the LODsManager will not update the LODs.
* @default false
*/
d(this, "pause", !1);
/**
* When set to true the LODsManager will not update the LODs. This can be used to manually update the LODs using the `update` method.
* Otherwise the LODs will be updated automatically when the renderer renders the scene.
* @default false
*/
d(this, "manual", !1);
d(this, "_lodchangedlisteners", []);
J(this, K, void 0);
J(this, ye, new Ye());
J(this, te, 0);
J(this, se, 0);
J(this, me, 0);
J(this, Y, 0);
d(this, "_fpsBuffer", [60, 60, 60, 60, 60]);
// private testIfLODLevelsAreAvailable() {
d(this, "_sphere", new Je());
d(this, "_tempBox", new Ee());
d(this, "_tempBox2", new Ee());
d(this, "tempMatrix", new Pe());
d(this, "_tempWorldPosition", new X());
d(this, "_tempBoxSize", new X());
d(this, "_tempBox2Size", new X());
this.renderer = e, this.context = { ...t };
}
/** @internal */
static getObjectLODState(e) {
return e[ve];
}
static addPlugin(e) {
Z.push(e);
}
static removePlugin(e) {
const t = Z.indexOf(e);
t >= 0 && Z.splice(t, 1);
}
/**
* Gets the LODsManager for the given renderer. If the LODsManager does not exist yet, it will be created.
* @param renderer The renderer to get the LODsManager for.
* @returns The LODsManager instance.
*/
static get(e, t) {
if (e[we])
return console.debug("[gltf-progressive] LODsManager already exists for this renderer"), e[we];
const s = new E(e, {
engine: "unknown",
...t
});
return e[we] = s, s;
}
/** @deprecated use static `LODsManager.addPlugin()` method. This getter will be removed in later versions */
get plugins() {
return Z;
}
addEventListener(e, t) {
e === "changed" && this._lodchangedlisteners.push(t);
}
removeEventListener(e, t) {
if (e === "changed") {
const s = this._lodchangedlisteners.indexOf(t);
s >= 0 && this._lodchangedlisteners.splice(s, 1);
}
}
/**
* Enable the LODsManager. This will replace the render method of the renderer with a method that updates the LODs.
*/
enable() {
if (m(this, K))
return;
console.debug("[gltf-progressive] Enabling LODsManager for renderer");
let e = 0;
z(this, K, this.renderer.render);
const t = this;
$e(this.renderer), this.renderer.render = function(s, i) {
const r = t.renderer.getRenderTarget();
(r == null || "isXRRenderTarget" in r && r.isXRRenderTarget) && (e = 0, z(t, te, m(t, te) + 1), z(t, se, m(t, ye).getDelta()), z(t, me, m(t, me) + m(t, se)), t._fpsBuffer.shift(), t._fpsBuffer.push(1 / m(t, se)), z(t, Y, t._fpsBuffer.reduce((a, l) => a + l) / t._fpsBuffer.length), G && m(t, te) % 200 === 0 && console.log("FPS", Math.round(m(t, Y)), "Interval:", m(t, C)));
const o = e++;
m(t, K).call(this, s, i), t.onAfterRender(s, i, o);
};
}
disable() {
m(this, K) && (console.debug("[gltf-progressive] Disabling LODsManager for renderer"), this.renderer.render = m(this, K), z(this, K, void 0));
}
update(e, t) {
this.internalUpdate(e, t);
}
onAfterRender(e, t, s) {
if (this.pause)
return;
const r = this.renderer.renderLists.get(e, 0).opaque;
let o = !0;
if (r.length === 1) {
const a = r[0].material;
(a.name === "EffectMaterial" || a.name === "CopyShader") && (o = !1);
}
if ((t.parent && t.parent.type === "CubeCamera" || s >= 1 && t.type === "OrthographicCamera") && (o = !1), o) {
if (ht || (this.updateInterval === "auto" ? m(this, Y) < 40 && m(this, C) < 10 ? (z(this, C, m(this, C) + 1), G && console.warn("↓ Reducing LOD updates", m(this, C), m(this, Y).toFixed(0))) : m(this, Y) >= 60 && m(this, C) > 1 && (z(this, C, m(this, C) - 1), G && console.warn("↑ Increasing LOD updates", m(this, C), m(this, Y).toFixed(0))) : z(this, C, this.updateInterval), m(this, C) > 0 && m(this, te) % m(this, C) != 0))
return;
this.internalUpdate(e, t);
}
}
/**
* Update LODs in a scene
*/
internalUpdate(e, t) {
var l, u;
const s = this.renderer.renderLists.get(e, 0), i = s.opaque;
this.projectionScreenMatrix.multiplyMatrices(t.projectionMatrix, t.matrixWorldInverse);
const r = this.targetTriangleDensity;
for (const c of i) {
if (c.material && (((l = c.geometry) == null ? void 0 : l.type) === "BoxGeometry" || ((u = c.geometry) == null ? void 0 : u.type) === "BufferGeometry") && (c.material.name === "SphericalGaussianBlur" || c.material.name == "BackgroundCubeMaterial" || c.material.name === "CubemapFromEquirect" || c.material.name === "EquirectangularToCubeUV")) {
G && (c.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"] || (c.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"] = !0, console.warn("Ignoring skybox or BLIT object", c, c.material.name, c.material.type)));
continue;
}
switch (c.material.type) {
case "LineBasicMaterial":
case "LineDashedMaterial":
case "PointsMaterial":
case "ShadowMaterial":
case "MeshDistanceMaterial":
case "MeshDepthMaterial":
continue;
}
if (G === "color" && c.material && !c.object.progressive_debug_color) {
c.object.progressive_debug_color = !0;
const p = Math.random() * 16777215, M = new He({ color: p });
c.object.material = M;
}
const g = c.object;
(g instanceof j || g.isMesh) && this.updateLODs(e, t, g, r);
}
const o = s.transparent;
for (const c of o) {
const g = c.object;
(g instanceof j || g.isMesh) && this.updateLODs(e, t, g, r);
}
const a = s.transmissive;
for (const c of a) {
const g = c.object;
(g instanceof j || g.isMesh) && this.updateLODs(e, t, g, r);
}
}
/** Update the LOD levels for the renderer. */
updateLODs(e, t, s, i) {
var a, l;
s.userData || (s.userData = {});
let r = s[ve];
if (r || (r = new gt(), s[ve] = r), r.frames++ < 2)
return;
for (const u of Z)
(a = u.onBeforeUpdateLOD) == null || a.call(u, this.renderer, e, t, s);
this.calculateLodLevel(t, s, r, i, $), $.mesh_lod = Math.round($.mesh_lod), $.texture_lod = Math.round($.texture_lod), $.mesh_lod >= 0 && this.loadProgressiveMeshes(s, $.mesh_lod);
let o = $.texture_lod;
s.material && o >= 0 && this.loadProgressiveTextures(s.material, o);
for (const u of Z)
(l = u.onAfterUpdatedLOD) == null || l.call(u, this.renderer, e, t, s, $);
r.lastLodLevel_Mesh = $.mesh_lod, r.lastLodLevel_Texture = $.texture_lod;
}
/** Load progressive textures for the given material
* @param material the material to load the textures for
* @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
* @returns Promise with true if the LOD was loaded, false if not
*/
loadProgressiveTextures(e, t) {
if (!e)
return;
if (Array.isArray(e)) {
for (const r of e)
this.loadProgressiveTextures(r, t);
return;
}
let s = !1;
(e[Q] === void 0 || t < e[Q]) && (s = !0);
const i = e["DEBUG:LOD"];
i != null && (s = e[Q] != i, t = i), s && (e[Q] = t, T.assignTextureLOD(e, t).then((r) => {
this._lodchangedlisteners.forEach((o) => o({ type: "texture", level: t, object: e }));
}));
}
/** Load progressive meshes for the given mesh
* @param mesh the mesh to load the LOD for
* @param index the index of the mesh if it's part of a group
* @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
* @returns Promise with true if the LOD was loaded, false if not
*/
loadProgressiveMeshes(e, t) {
if (!e)
return Promise.resolve(null);
let s = e[Q] !== t;
const i = e["DEBUG:LOD"];
if (i != null && (s = e[Q] != i, t = i), s) {
e[Q] = t;
const r = e.geometry;
return T.assignMeshLOD(e, t).then((o) => (o && e[Q] == t && r != e.geometry && this._lodchangedlisteners.forEach((a) => a({ type: "mesh", level: t, object: e })), o));
}
return Promise.resolve(null);
}
static isInside(e, t) {
const s = e.min, i = e.max, r = (s.x + i.x) * 0.5, o = (s.y + i.y) * 0.5;
return this._tempPtInside.set(r, o, s.z).applyMatrix4(t).z < 0;
}
calculateLodLevel(e, t, s, i, r) {
var F;
if (!t) {
r.mesh_lod = -1, r.texture_lod = -1;
return;
}
if (!e) {
r.mesh_lod = -1, r.texture_lod = -1;
return;
}
let a = 10 + 1, l = !1;
if (G && t["DEBUG:LOD"] != null)
return t["DEBUG:LOD"];
const u = T.getMeshLODInformation(t.geometry), c = u == null ? void 0 : u.lods, g = c && c.length > 0, p = T.getMaterialMinMaxLODsCount(t.material), M = (p == null ? void 0 : p.min_count) != 1 / 0 && p.min_count > 0 && p.max_count > 0;
if (!g && !M) {
r.mesh_lod = 0, r.texture_lod = 0;
return;
}
g || (l = !0, a = 0);
const w = this.renderer.domElement.clientHeight || this.renderer.domElement.height;
let x = t.geometry.boundingBox;
if (t.type === "SkinnedMesh") {
const D = t;
if (!D.boundingBox)
D.computeBoundingBox();
else if (s.frames % 30 === 0) {
const h = ce(D), k = D.geometry;
h && (D.geometry = h), D.computeBoundingBox(), D.geometry = k;
}
x = D.boundingBox;
}
if (x) {
const D = e;
if (t.geometry.attributes.color && t.geometry.attributes.color.count < 100 && t.geometry.boundingSphere) {
this._sphere.copy(t.geometry.boundingSphere), this._sphere.applyMatrix4(t.matrixWorld);
const f = e.getWorldPosition(this._tempWorldPosition);
if (this._sphere.containsPoint(f)) {
r.mesh_lod = 0, r.texture_lod = 0;
return;
}
}
if (this._tempBox.copy(x), this._tempBox.applyMatrix4(t.matrixWorld), D.isPerspectiveCamera && E.isInside(this._tempBox, this.projectionScreenMatrix)) {
r.mesh_lod = 0, r.texture_lod = 0;
return;
}
if (this._tempBox.applyMatrix4(this.projectionScreenMatrix), this.renderer.xr.enabled && D.isPerspectiveCamera && D.fov > 70) {
const f = this._tempBox.min, y = this._tempBox.max;
let O = f.x, S = f.y, P = y.x, H = y.y;
const ue = 2, Le = 1.5, fe = (f.x + y.x) * 0.5, de = (f.y + y.y) * 0.5;
O = (O - fe) * ue + fe, S = (S - de) * ue + de, P = (P - fe) * ue + fe, H = (H - de) * ue + de;
const Ve = O < 0 && P > 0 ? 0 : Math.min(Math.abs(f.x), Math.abs(y.x)), We = S < 0 && H > 0 ? 0 : Math.min(Math.abs(f.y), Math.abs(y.y)), xe = Math.max(Ve, We);
s.lastCentrality = (Le - xe) * (Le - xe) * (Le - xe);
} else
s.lastCentrality = 1;
const h = this._tempBox.getSize(this._tempBoxSize);
h.multiplyScalar(0.5), screen.availHeight > 0 && w > 0 && h.multiplyScalar(w / screen.availHeight), e.isPerspectiveCamera ? h.x *= e.aspect : e.isOrthographicCamera;
const k = e.matrixWorldInverse, N = this._tempBox2;
N.copy(x), N.applyMatrix4(t.matrixWorld), N.applyMatrix4(k);
const I = N.getSize(this._tempBox2Size), A = Math.max(I.x, I.y);
if (Math.max(h.x, h.y) != 0 && A != 0 && (h.z = I.z / Math.max(I.x, I.y) * Math.max(h.x, h.y)), s.lastScreenCoverage = Math.max(h.x, h.y, h.z), s.lastScreenspaceVolume.copy(h), s.lastScreenCoverage *= s.lastCentrality, G && E.debugDrawLine) {
const f = this.tempMatrix.copy(this.projectionScreenMatrix);
f.invert();
const y = E.corner0, O = E.corner1, S = E.corner2, P = E.corner3;
y.copy(this._tempBox.min), O.copy(this._tempBox.max), O.x = y.x, S.copy(this._tempBox.max), S.y = y.y, P.copy(this._tempBox.max);
const H = (y.z + P.z) * 0.5;
y.z = O.z = S.z = P.z = H, y.applyMatrix4(f), O.applyMatrix4(f), S.applyMatrix4(f), P.applyMatrix4(f), E.debugDrawLine(y, O, 255), E.debugDrawLine(y, S, 255), E.debugDrawLine(O, P, 255), E.debugDrawLine(S, P, 255);
}
let _ = 999;
if (c && s.lastScreenCoverage > 0) {
for (let f = 0; f < c.length; f++)
if (c[f].density / s.lastScreenCoverage < i) {
_ = f;
break;
}
}
_ < a && (a = _, l = !0);
}
if (l ? r.mesh_lod = a : r.mesh_lod = s.lastLodLevel_Mesh, G && r.mesh_lod != s.lastLodLevel_Mesh) {
const h = c == null ? void 0 : c[r.mesh_lod];
h && console.log(`Mesh LOD changed: ${s.lastLodLevel_Mesh} → ${r.mesh_lod} (${h.density.toFixed(0)}) - ${t.name}`);
}
if (M) {
const D = "saveData" in globalThis.navigator && globalThis.navigator.saveData === !0;
if (s.lastLodLevel_Texture < 0) {
if (r.texture_lod = p.max_count - 1, G) {
const h = p.lods[p.max_count - 1];
G && console.log(`First Texture LOD ${r.texture_lod} (${h.max_height}px) - ${t.name}`);
}
} else {
const h = s.lastScreenspaceVolume.x + s.lastScreenspaceVolume.y + s.lastScreenspaceVolume.z;
let k = s.lastScreenCoverage * 4;
((F = this.context) == null ? void 0 : F.engine) === "model-viewer" && (k *= 1.5);
const I = w / window.devicePixelRatio * k;
let A = !1;
for (let W = p.lods.length - 1; W >= 0; W--) {
let _ = p.lods[W];
if (!(D && _.max_height >= 2048) && !(at() && _.max_height > 4096) && (_.max_height > I || !A && W === 0)) {
if (A = !0, r.texture_lod = W, r.texture_lod < s.lastLodLevel_Texture) {
const b = _.max_height;
G && console.log(`Texture LOD changed: ${s.lastLodLevel_Texture} → ${r.texture_lod} = ${b}px
Screensize: ${I.toFixed(0)}px, Coverage: ${(100 * s.lastScreenCoverage).toFixed(2)}%, Volume ${h.toFixed(1)}
${t.name}`);
}
break;
}
}
}
} else
r.texture_lod = 0;
}
};
let R = E;
C = new WeakMap(), K = new WeakMap(), ye = new WeakMap(), te = new WeakMap(), se = new WeakMap(), me = new WeakMap(), Y = new WeakMap(), /** Assign a function to draw debug lines for the LODs. This function will be called with the start and end position of the line and the color of the line when the `debugprogressive` query parameter is set.
*/
d(R, "debugDrawLine"), d(R, "corner0", new X()), d(R, "corner1", new X()), d(R, "corner2", new X()), d(R, "corner3", new X()), d(R, "_tempPtInside", new X());
class gt {
constructor() {
d(this, "frames", 0);
d(this, "lastLodLevel_Mesh", -1);
d(this, "lastLodLevel_Texture", -1);
d(this, "lastScreenCoverage", 0);
d(this, "lastScreenspaceVolume", new X());
d(this, "lastCentrality", 0);
}
}
const Be = Symbol("NEEDLE_mesh_lod"), he = Symbol("NEEDLE_texture_lod");
let ne = null;
function Ne() {
const n = pt();
n && (n.mapURLs(function(e) {
return Re(), e;
}), Re(), ne == null || ne.disconnect(), ne = new MutationObserver((e) => {
e.forEach((t) => {
t.addedNodes.forEach((s) => {
s instanceof HTMLElement && s.tagName.toLowerCase() === "model-viewer" && ze(s);
});
});
}), ne.observe(document, { childList: !0, subtree: !0 }));
}
function pt() {
if (typeof customElements > "u")
return null;
const n = customElements.get("model-viewer");
return n || (customElements.whenDefined("model-viewer").then(() => {
console.debug("[gltf-progressive] model-viewer defined"), Ne();
}), null);
}
function Re() {
if (typeof document > "u")
return;
document.querySelectorAll("model-viewer").forEach((e) => {
ze(e);
});
}
const ke = /* @__PURE__ */ new WeakSet();
let yt = 0;
function ze(n) {
if (!n || ke.has(n))
return null;
ke.add(n), console.debug("[gltf-progressive] found new model-viewer..." + ++yt + `
`, n.getAttribute("src"));
let e = null, t = null, s = null;
for (let i = n; i != null; i = Object.getPrototypeOf(i)) {
const r = Object.getOwnPropertySymbols(i), o = r.find((u) => u.toString() == "Symbol(renderer)"), a = r.find((u) => u.toString() == "Symbol(scene)"), l = r.find((u) => u.toString() == "Symbol(needsRender)");
!e && o != null && (e = n[o].threeRenderer), !t && a != null && (t = n[a]), !s && l != null && (s = n[l]);
}
if (e && t) {
let i = function() {
if (s) {
let o = 0, a = setInterval(() => {
if (o++ > 5) {
clearInterval(a);
return;
}
s == null || s.call(n);
}, 300);
}
};
console.debug("[gltf-progressive] setup model-viewer");
const r = R.get(e, { engine: "model-viewer" });
return R.addPlugin(new mt()), r.enable(), r.addEventListener("changed", () => {
s == null || s.call(n);
}), n.addEventListener("model-visibility", (o) => {
o.detail.visible && (s == null || s.call(n));
}), n.addEventListener("load", () => {
i();
}), () => {
r.disable();
};
}
return null;
}
class mt {
constructor() {
d(this, "_didWarnAboutMissingUrl", !1);
}
onBeforeUpdateLOD(e, t, s, i) {
this.tryParseMeshLOD(t, i), this.tryParseTextureLOD(t, i);
}
getUrl(e) {
if (!e)
return null;
let t = e.getAttribute("src");
return t || (t = e.src), t || (this._didWarnAboutMissingUrl || console.warn("No url found in modelviewer", e), this._didWarnAboutMissingUrl = !0), t;
}
tryGetCurrentGLTF(e) {
return e._currentGLTF;
}
tryGetCurrentModelViewer(e) {
return e.element;
}
tryParseTextureLOD(e, t) {
if (t[he] == !0)
return;
t[he] = !0;
const s = this.tryGetCurrentGLTF(e), i = this.tryGetCurrentModelViewer(e), r = this.getUrl(i);
if (r && s && t.material) {
let o = function(l) {
var c, g, p;
if (l[he] == !0)
return;
l[he] = !0, l.userData && (l.userData.LOD = -1);
const u = Object.keys(l);
for (let M = 0; M < u.length; M++) {
const w = u[M], x = l[w];
if ((x == null ? void 0 : x.isTexture) === !0) {
const F = (g = (c = x.userData) == null ? void 0 : c.associations) == null ? void 0 : g.textures;
if (F == null)
continue;
const D = s.parser.json.textures[F];
if (!D) {
console.warn("Texture data not found for texture index " + F);
continue;
}
if ((p = D == null ? void 0 : D.extensions) != null && p[q]) {
const h = D.extensions[q];
h && r && T.registerTexture(r, x, h.lods.length, F, h);
}
}
}
};
const a = t.material;
if (Array.isArray(a))
for (const l of a)
o(l);
else
o(a);
}
}
tryParseMeshLOD(e, t) {
var o, a;
if (t[Be] == !0)
return;
t[Be] = !0;
const s = this.tryGetCurrentModelViewer(e), i = this.getUrl(s);
if (!i)
return;
const r = (a = (o = t.userData) == null ? void 0 : o.gltfExtensions) == null ? void 0 : a[q];
if (r && i) {
const l = t.uuid;
T.registerMesh(i, l, t, 0, r.lods.length, r);
}
}
}
function Lt(n, e, t, s) {
$e(e), Ue(t), Fe(t, {
progressive: !0,
...s == null ? void 0 : s.hints
}), t.register((r) => new T(r, n));
const i = R.get(e);
return (s == null ? void 0 : s.enableLODsManager) !== !1 && i.enable(), i;
}
Ne();
if (!lt) {
const n = {
gltfProgressive: {
useNeedleProgressive: Lt,
LODsManager: R,
configureLoader: Fe,
getRaycastMesh: ce,
useRaycastMeshes: ut
}
};
if (!globalThis.Needle)
globalThis.Needle = n;
else
for (const e in n)
globalThis.Needle[e] = n[e];
}
export {
q as EXTENSION_NAME,
R as LODsManager,
T as NEEDLE_progressive,
et as VERSION,
Ue as addDracoAndKTX2Loaders,
Fe as configureLoader,
$e as createLoaders,
ce as getRaycastMesh,
Ne as patchModelViewer,
ct as registerRaycastMesh,
rt as setDracoDecoderLocation,
ot as setKTX2TranscoderLocation,
Lt as useNeedleProgressive,
ut as useRaycastMeshes
};