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