UNPKG

@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
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 };