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