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