UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in

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