@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,186 lines • 75.6 kB
JavaScript
import { BufferGeometry as j, Mesh as K, Box3 as ce, Vector3 as k, Sphere as Pe, CompressedTexture as Ee, Texture as G, Matrix3 as Ne, InterleavedBuffer as Ve, InterleavedBufferAttribute as Xe, BufferAttribute as je, TextureLoader as Ke, Matrix4 as ke, Clock as He, Color as Re } from "./three.js";
import { DRACOLoader as Ye, KTX2Loader as Qe, MeshoptDecoder as Je, GLTFLoader as ve } from "./three-examples.js";
const Ze = "";
globalThis.GLTF_PROGRESSIVE_VERSION = Ze;
console.debug("[gltf-progressive] version -");
let I = "https://www.gstatic.com/draco/versioned/decoders/1.5.7/", H = "https://cdn.needle.tools/static/three/0.179.1/basis2/";
const et = I, tt = H, Ae = new URL(I + "draco_decoder.js");
Ae.searchParams.append("range", "true");
fetch(Ae, {
method: "GET",
headers: {
Range: "bytes=0-1"
}
}).catch((i) => {
console.debug(`Failed to fetch remote Draco decoder from ${I} (offline: ${typeof navigator < "u" ? navigator.onLine : "unknown"})`), I === et && rt("./include/draco/"), H === tt && nt("./include/ktx2/");
}).finally(() => {
$e();
});
const st = () => ({
dracoDecoderPath: I,
ktx2TranscoderPath: H
});
function rt(i) {
I = i, P && P[xe] != I ? (console.debug("Updating Draco decoder path to " + i), P[xe] = I, P.setDecoderPath(I), P.preload()) : console.debug("Setting Draco decoder path to " + i);
}
function nt(i) {
H = i, $ && $.transcoderPath != H ? (console.debug("Updating KTX2 transcoder path to " + i), $.setTranscoderPath(H), $.init()) : console.debug("Setting KTX2 transcoder path to " + i);
}
function De(i) {
return $e(), i ? $.detectSupport(i) : i !== null && console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"), { dracoLoader: P, ktx2Loader: $, meshoptDecoder: le };
}
function Ie(i) {
i.dracoLoader || i.setDRACOLoader(P), i.ktx2Loader || i.setKTX2Loader($), i.meshoptDecoder || i.setMeshoptDecoder(le);
}
const xe = /* @__PURE__ */ Symbol("dracoDecoderPath");
let P, le, $;
function $e() {
P || (P = new Ye(), P[xe] = I, P.setDecoderPath(I), P.setDecoderConfig({ type: "js" }), P.preload()), $ || ($ = new Qe(), $.setTranscoderPath(H), $.init()), le || (le = Je);
}
const we = /* @__PURE__ */ new WeakMap();
function Be(i, t) {
let e = we.get(i);
e ? e = Object.assign(e, t) : e = t, we.set(i, e);
}
const it = ve.prototype.load;
function ot(...i) {
const t = we.get(this);
let e = i[0];
const s = new URL(e, window.location.href);
if (s.hostname.endsWith("needle.tools")) {
const n = t?.progressive !== void 0 ? t.progressive : !0, o = t?.usecase ? t.usecase : "default";
n ? this.requestHeader.Accept = `*/*;progressive=allowed;usecase=${o}` : this.requestHeader.Accept = `*/*;usecase=${o}`, e = s.toString();
}
return i[0] = e, it?.call(this, ...i);
}
ve.prototype.load = ot;
E("debugprogressive");
function E(i) {
if (typeof window > "u") return !1;
const e = new URL(window.location.href).searchParams.get(i);
return e == null || e === "0" || e === "false" ? !1 : e === "" ? !0 : e;
}
function at(i, t) {
if (t === void 0 || i === void 0 || t.startsWith("./") || t.startsWith("http") || t.startsWith("data:") || t.startsWith("blob:"))
return t;
const e = i.lastIndexOf("/");
if (e >= 0) {
const s = i.substring(0, e + 1);
for (; s.endsWith("/") && t.startsWith("/"); ) t = t.substring(1);
return s + t;
}
return t;
}
function Me() {
return Z !== void 0 || (Z = /iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent), E("debugprogressive") && console.log("[glTF Progressive]: isMobileDevice", Z)), Z;
}
let Z;
function Ge() {
if (typeof window > "u") return !1;
const i = new URL(window.location.href), t = i.hostname === "localhost" || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(i.hostname);
return i.hostname === "127.0.0.1" || t;
}
class lt {
constructor(t, e = {}) {
this.maxConcurrent = t, this.debug = e.debug ?? !1, typeof window < "u" && window.requestAnimationFrame(this.tick);
}
_running = /* @__PURE__ */ new Map();
_queue = [];
debug = !1;
tick = () => {
this.internalUpdate(), setTimeout(this.tick, 10);
};
/**
* Request a slot for a promise with a specific key. This function returns a promise with a `use` method that can be called to add the promise to the queue.
*/
slot(t) {
return this.debug && console.debug(`[PromiseQueue]: Requesting slot for key ${t}, running: ${this._running.size}, waiting: ${this._queue.length}`), new Promise((e) => {
this._queue.push({ key: t, resolve: e });
});
}
add(t, e) {
this._running.has(t) || (this._running.set(t, e), e.finally(() => {
this._running.delete(t), this.debug && console.debug(`[PromiseQueue]: Promise finished now running: ${this._running.size}, waiting: ${this._queue.length}. (finished ${t})`);
}), this.debug && console.debug(`[PromiseQueue]: Added new promise, now running: ${this._running.size}, waiting: ${this._queue.length}. (added ${t})`));
}
internalUpdate() {
const t = this.maxConcurrent - this._running.size;
for (let e = 0; e < t && this._queue.length > 0; e++) {
this.debug && console.debug(`[PromiseQueue]: Running ${this._running.size} promises, waiting for ${this._queue.length} more.`);
const { key: s, resolve: r } = this._queue.shift();
r({
use: (n) => this.add(s, n)
});
}
}
}
function ut(i) {
const t = i.image?.width ?? 0, e = i.image?.height ?? 0, s = i.image?.depth ?? 1, r = Math.floor(Math.log2(Math.max(t, e, s))) + 1, n = ct(i);
return t * e * s * n * (1 - Math.pow(0.25, r)) / (1 - 0.25);
}
function ct(i) {
let t = 4;
const e = i.format;
e === 1024 || e === 1025 ? t = 1 : e === 1026 || e === 1027 ? t = 2 : e === 1022 || e === 1029 ? t = 3 : (e === 1023 || e === 1033) && (t = 4);
let s = 1;
const r = i.type;
return r === 1009 || r === 1010 ? s = 1 : r === 1011 || r === 1012 ? s = 2 : r === 1013 || r === 1014 || r === 1015 ? s = 4 : r === 1016 && (s = 2), t * s;
}
const dt = typeof window > "u" && typeof document > "u", Le = /* @__PURE__ */ Symbol("needle:raycast-mesh");
function ne(i) {
return i?.[Le] instanceof j ? i[Le] : null;
}
function ft(i, t) {
if ((i.type === "Mesh" || i.type === "SkinnedMesh") && !ne(i)) {
const s = gt(t);
s.userData = { isRaycastMesh: !0 }, i[Le] = s;
}
}
function ht(i = !0) {
if (i) {
if (ee) return;
const t = ee = K.prototype.raycast;
K.prototype.raycast = function(e, s) {
const r = this, n = ne(r);
let o;
n && r.isMesh && (o = r.geometry, r.geometry = n), t.call(this, e, s), o && (r.geometry = o);
};
} else {
if (!ee) return;
K.prototype.raycast = ee, ee = null;
}
}
let ee = null;
function gt(i) {
const t = new j();
for (const e in i.attributes)
t.setAttribute(e, i.getAttribute(e));
return t.setIndex(i.getIndex()), t;
}
const W = new Array(), h = E("debugprogressive");
let re, V = -1;
if (h) {
let i = function() {
V += 1, V >= t && (V = -1), console.log(`Toggle LOD level [${V}]`);
};
const t = 6;
window.addEventListener("keyup", (e) => {
e.key === "p" && i(), e.key === "w" && (re = !re, console.log(`Toggle wireframe [${re}]`));
const s = parseInt(e.key);
!isNaN(s) && s >= 0 && (V = s, console.log(`Set LOD level to [${V}]`));
});
}
function qe(i) {
if (h && re !== void 0)
if (Array.isArray(i))
for (const t of i)
qe(t);
else i && "wireframe" in i && (i.wireframe = re === !0);
}
const te = new Array();
let pt = 0;
const mt = Me() ? 2 : 10;
function yt(i) {
if (te.length < mt) {
const s = te.length;
h && console.warn(`[Worker] Creating new worker #${s}`);
const r = _e.createWorker(i || {});
return te.push(r), r;
}
const t = pt++ % te.length;
return te[t];
}
class _e {
constructor(t, e) {
this.worker = t, this._debug = e.debug ?? !1, t.onmessage = (s) => {
const r = s.data;
switch (this._debug && console.log("[Worker] EVENT", r), r.type) {
case "loaded-gltf":
for (const n of this._running)
if (n.url === r.result.url) {
xt(r.result), n.resolve(r.result);
const o = n.url;
o.startsWith("blob:") && URL.revokeObjectURL(o);
}
}
}, t.onerror = (s) => {
console.error("[Worker] Error in gltf-progressive worker:", s);
}, t.postMessage({
type: "init"
});
}
static async createWorker(t) {
const e = /* new-worker */ new Worker(URL.createObjectURL(new Blob(["import '" + `${new URL('./gltf-progressive.worker-BqODMeeW.js', import.meta.url).toString()}` + "';"], { type: 'text/javascript' })), {
type: "module"
});
return new _e(e, t);
}
_running = [];
_webglRenderer = null;
async load(t, e) {
const s = st();
let r = e?.renderer;
r || (this._webglRenderer ??= (async () => {
const { WebGLRenderer: u } = await import("./three.js").then((c) => c.THREE);
return new u();
})(), r = await this._webglRenderer);
const a = De(r).ktx2Loader.workerConfig;
t instanceof URL ? t = t.toString() : t.startsWith("file:") ? t = URL.createObjectURL(new Blob([t])) : !t.startsWith("blob:") && !t.startsWith("http:") && !t.startsWith("https:") && (t = new URL(t, window.location.href).toString());
const l = {
type: "load",
url: t,
dracoDecoderPath: s.dracoDecoderPath,
ktx2TranscoderPath: s.ktx2TranscoderPath,
ktx2LoaderConfig: a
};
return this._debug && console.debug("[Worker] Sending load request", l), this.worker.postMessage(l), new Promise((u) => {
this._running.push({
url: t.toString(),
resolve: u
});
});
}
_debug = !1;
}
function xt(i) {
for (const t of i.geometries) {
const e = t.geometry, s = new j();
if (s.name = e.name || "", e.index) {
const r = e.index;
s.setIndex(fe(r));
}
for (const r in e.attributes) {
const n = e.attributes[r], o = fe(n);
s.setAttribute(r, o);
}
if (e.morphAttributes)
for (const r in e.morphAttributes) {
const o = e.morphAttributes[r].map((a) => fe(a));
s.morphAttributes[r] = o;
}
if (s.morphTargetsRelative = e.morphTargetsRelative ?? !1, s.boundingBox = new ce(), s.boundingBox.min = new k(
e.boundingBox?.min.x,
e.boundingBox?.min.y,
e.boundingBox?.min.z
), s.boundingBox.max = new k(
e.boundingBox?.max.x,
e.boundingBox?.max.y,
e.boundingBox?.max.z
), s.boundingSphere = new Pe(
new k(
e.boundingSphere?.center.x,
e.boundingSphere?.center.y,
e.boundingSphere?.center.z
),
e.boundingSphere?.radius
), e.groups)
for (const r of e.groups)
s.addGroup(r.start, r.count, r.materialIndex);
e.userData && (s.userData = e.userData), t.geometry = s;
}
for (const t of i.textures) {
const e = t.texture;
let s = null;
if (e.isCompressedTexture) {
const r = e.mipmaps, n = e.image?.width || e.source?.data?.width || -1, o = e.image?.height || e.source?.data?.height || -1;
s = new Ee(
r,
n,
o,
e.format,
e.type,
e.mapping,
e.wrapS,
e.wrapT,
e.magFilter,
e.minFilter,
e.anisotropy,
e.colorSpace
);
} else
s = new G(
e.image,
e.mapping,
e.wrapS,
e.wrapT,
e.magFilter,
e.minFilter,
e.format,
e.type,
e.anisotropy,
e.colorSpace
), s.mipmaps = e.mipmaps, s.channel = e.channel, s.source.data = e.source.data, s.flipY = e.flipY, s.premultiplyAlpha = e.premultiplyAlpha, s.unpackAlignment = e.unpackAlignment, s.matrix = new Ne(...e.matrix.elements);
if (!s) {
console.error("[Worker] Failed to create new texture from received data. Texture is not a CompressedTexture or Texture.");
continue;
}
t.texture = s;
}
return i;
}
function fe(i) {
let t = i;
if ("isInterleavedBufferAttribute" in i && i.isInterleavedBufferAttribute) {
const e = i.data, s = e.array, r = new Ve(s, e.stride);
t = new Xe(r, i.itemSize, s.byteOffset, i.normalized), t.offset = i.offset;
} else "isBufferAttribute" in i && i.isBufferAttribute && (t = new je(i.array, i.itemSize, i.normalized), t.usage = i.usage, t.gpuType = i.gpuType, t.updateRanges = i.updateRanges);
return t;
}
const wt = E("gltf-progressive-worker");
E("gltf-progressive-reduce-mipmaps");
const se = E("gltf-progressive-gc"), he = /* @__PURE__ */ Symbol("needle-progressive-texture"), U = "NEEDLE_progressive";
class p {
/** The name of the extension */
get name() {
return U;
}
// #region PUBLIC API
/**
* Get the progressive mesh LOD extension data associated with a geometry.
* Returns the extension metadata (available LOD levels, vertex/index counts, densities) if the geometry was registered with progressive LODs, or `null` otherwise.
*
* @param geo - The buffer geometry to look up.
* @returns The mesh LOD extension data, or `null` if no progressive LODs are registered for this geometry.
*/
static getMeshLODExtension(t) {
const e = this.getAssignedLODInformation(t);
return e?.key ? this.lodInfos.get(e.key) : null;
}
/**
* Get the glTF primitive index for a geometry within its parent mesh.
* A single glTF mesh node can contain multiple primitives (sub-geometries). This returns which primitive the geometry corresponds to.
*
* @param geo - The buffer geometry to look up.
* @returns The zero-based primitive index, or `-1` if no LOD information is assigned to this geometry.
*/
static getPrimitiveIndex(t) {
const e = this.getAssignedLODInformation(t)?.index;
return e ?? -1;
}
/**
* Compute the minimum and maximum number of texture LOD levels across all textures of a material (or array of materials).
* Iterates over all texture slots on the material and collects LOD count ranges and per-level resolution bounds.
* Results are cached on the material so subsequent calls are free.
*
* @param material - A single material or an array of materials to inspect.
* @param minmax - Optional accumulator to merge results into (used internally for recursive calls with material arrays).
* @returns An object with `min_count` / `max_count` (the range of LOD levels across all textures) and a per-level `lods` array with `min_height` / `max_height`.
*/
static getMaterialMinMaxLODsCount(t, e) {
const s = this, r = "LODS:minmax", n = t[r];
if (n != null) return n;
if (e || (e = {
min_count: 1 / 0,
max_count: 0,
lods: []
}), Array.isArray(t)) {
for (const a of t)
this.getMaterialMinMaxLODsCount(a, e);
return t[r] = e, e;
}
if (h === "verbose" && console.log("getMaterialMinMaxLODsCount", t), t.type === "ShaderMaterial" || t.type === "RawShaderMaterial") {
const a = t;
for (const l of Object.keys(a.uniforms)) {
const u = a.uniforms[l].value;
u?.isTexture === !0 && o(u, e);
}
} else if (t.isMaterial)
for (const a of Object.keys(t)) {
const l = t[a];
l?.isTexture === !0 && o(l, e);
}
else
h && console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);
return t[r] = e, e;
function o(a, l) {
const u = s.getAssignedLODInformation(a);
if (u) {
const c = s.lodInfos.get(u.key);
if (c && c.lods) {
l.min_count = Math.min(l.min_count, c.lods.length), l.max_count = Math.max(l.max_count, c.lods.length);
for (let d = 0; d < c.lods.length; d++) {
const f = c.lods[d];
f.width && (l.lods[d] = l.lods[d] || { min_height: 1 / 0, max_height: 0 }, l.lods[d].min_height = Math.min(l.lods[d].min_height, f.height), l.lods[d].max_height = Math.max(l.lods[d].max_height, f.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) {
if (Array.isArray(t)) {
for (const n of t)
if (this.hasLODLevelAvailable(n, e)) return !0;
return !1;
}
if (t.isMaterial === !0) {
for (const n of Object.keys(t)) {
const o = t[n];
if (o && o.isTexture && this.hasLODLevelAvailable(o, e))
return !0;
}
return !1;
} else if (t.isGroup === !0) {
for (const n of t.children)
if (n.isMesh === !0 && this.hasLODLevelAvailable(n, e))
return !0;
}
let s, r;
if (t.isMesh ? s = t.geometry : (t.isBufferGeometry || t.isTexture) && (s = t), s && s?.userData?.LODS) {
const n = s.userData.LODS;
if (r = this.lodInfos.get(n.key), e === void 0) return r != null;
if (r)
return Array.isArray(r.lods) ? e < r.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, s) {
if (!t) return Promise.resolve(null);
if (t instanceof K || t.isMesh === !0) {
const r = t.geometry, n = this.getAssignedLODInformation(r);
if (!n)
return Promise.resolve(null);
for (const a of W)
a.onBeforeGetLODMesh?.(t, e);
t["LOD:requested level"] = e;
const o = () => t["LOD:requested level"] === e || this.shouldApplyStaleMeshLOD(t, e);
return p.getOrLoadLOD(r, e, {
isCurrent: o
}).then((a) => {
if (Array.isArray(a)) {
const u = n.index || 0;
a = a[u];
}
const l = t["LOD:requested level"] === e;
return (l || this.shouldApplyStaleMeshLOD(t, e)) && (l && delete t["LOD:requested level"], a && r != a && (a?.isBufferGeometry ? typeof s?.apply == "function" ? s.apply(a, e, t) : s?.apply !== !1 && (t.geometry = a) : h && console.error("Invalid LOD geometry", a))), a;
}).catch((a) => (console.error("Error loading mesh LOD", t, a), null));
} else h && 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, s) {
if (!t) return Promise.resolve(null);
const r = s?.force === !0;
if (t.isMesh === !0) {
const n = t;
if (Array.isArray(n.material)) {
const o = new Array();
for (const a of n.material) {
const l = this.assignTextureLOD(a, e, s);
o.push(l);
}
return Promise.all(o).then((a) => {
const l = new Array();
for (const u of a)
Array.isArray(u) && l.push(...u);
return l;
});
} else
return this.assignTextureLOD(n.material, e, s);
}
if (t.isMaterial === !0) {
const n = t, o = [], a = new Array();
if (this.trackCurrentMaterialTextureSlots(n), n.uniforms && (n.isRawShaderMaterial || n.isShaderMaterial === !0)) {
const l = n;
for (const u of Object.keys(l.uniforms)) {
const c = l.uniforms[u].value;
if (c?.isTexture === !0) {
const d = this.assignTextureLODForSlot(c, e, n, u, r).then((f) => (f && l.uniforms[u].value != f && (l.uniforms[u].value = f, l.uniformsNeedUpdate = !0), f));
o.push(d), a.push(u);
}
}
} else
for (const l of Object.keys(n)) {
const u = n[l];
if (u?.isTexture === !0) {
const c = this.assignTextureLODForSlot(u, e, n, l, r);
o.push(c), a.push(l);
}
}
return Promise.all(o).then((l) => {
const u = new Array();
for (let c = 0; c < l.length; c++) {
const d = l[c], f = a[c];
d && d.isTexture === !0 ? u.push({ material: n, slot: f, texture: d, level: e }) : u.push({ material: n, slot: f, texture: null, level: e });
}
return u;
});
}
if (t instanceof G || t.isTexture === !0) {
const n = t;
return this.assignTextureLODForSlot(n, e, null, null, r);
}
return Promise.resolve(null);
}
/**
* Set the maximum number of concurrent loading tasks for LOD resources. This limits how many LOD resources (meshes or textures) can be loaded at the same time to prevent overloading the network or GPU. If the limit is reached, additional loading requests will be queued and processed as previous ones finish.
* @default 50 on desktop, 20 on mobile devices
*/
static set maxConcurrentLoadingTasks(t) {
p.queue.maxConcurrent = t;
}
static get maxConcurrentLoadingTasks() {
return p.queue.maxConcurrent;
}
// #region INTERNAL
static assignTextureLODForSlot(t, e, s, r, n) {
if (t?.isTexture !== !0)
return Promise.resolve(null);
if (r === "glyphMap")
return Promise.resolve(t);
const o = this.getAssignedLODInformation(t);
if (o && (o.level === e || !n && o.level < e))
return Promise.resolve(t);
if (s && r) {
const d = this.getPendingTextureSlotRequest(s, r);
if (d && d.level === e && d.force === n)
return d.promise;
}
const a = s && r ? this.nextTextureSlotRequestId(s, r, e, n) : 0, l = () => !s || !r || this.getLatestTextureSlotRequest(s, r)?.id === a, u = () => l() || this.shouldApplyStaleTextureSlotLOD(s, r, e, n), c = p.getOrLoadLOD(t, e, {
isCurrent: u
}).then((d) => {
if (!l() && !this.shouldApplyStaleTextureSlotLOD(s, r, e, n)) return null;
if (Array.isArray(d))
return console.warn("Progressive: Got an array of textures for a texture slot, this should not happen..."), null;
if (d?.isTexture === !0) {
if (d != t && s && r) {
const f = this.getMaterialTextureSlot(s, r) ?? t;
if (f && !n) {
const g = this.getAssignedLODInformation(f);
if (g && g?.level < e)
return h === "verbose" && console.warn("Assigned texture level is already higher: ", g.level, e, s, f, d), null;
}
this.assignTrackedTextureSlot(s, r, d);
}
return d;
} else h == "verbose" && console.warn("No LOD found for", t, e);
return null;
}).catch((d) => (console.error("Error loading LOD", t, d), null));
return s && r && this.setPendingTextureSlotRequest(s, r, e, n, a, c), c;
}
// Track material slots, not just texture objects. A shared fallback texture can be
// referenced by many slots and should only be disposed after every slot moved away.
static trackedTextureSlots = /* @__PURE__ */ new WeakMap();
static pendingTextureSlotRequests = /* @__PURE__ */ new WeakMap();
static latestTextureSlotRequests = /* @__PURE__ */ new WeakMap();
static textureSlotRequestId = 0;
static trackCurrentMaterialTextureSlots(t) {
if (t.uniforms && (t.isRawShaderMaterial || t.isShaderMaterial === !0)) {
const e = t;
for (const s of Object.keys(e.uniforms)) {
const r = e.uniforms[s].value;
r?.isTexture === !0 && this.ensureTrackedTextureSlot(t, s, r);
}
return;
}
for (const e of Object.keys(t)) {
const s = t[e];
s?.isTexture === !0 && this.ensureTrackedTextureSlot(t, e, s);
}
}
static getPendingTextureSlotRequest(t, e) {
return this.pendingTextureSlotRequests.get(t)?.get(e);
}
static nextTextureSlotRequestId(t, e, s, r) {
let n = this.latestTextureSlotRequests.get(t);
n || (n = /* @__PURE__ */ new Map(), this.latestTextureSlotRequests.set(t, n));
const o = ++this.textureSlotRequestId;
return n.set(e, { id: o, level: s, force: r }), o;
}
static getLatestTextureSlotRequest(t, e) {
return this.latestTextureSlotRequests.get(t)?.get(e);
}
static shouldApplyStaleTextureSlotLOD(t, e, s, r) {
if (!t || !e) return !1;
const n = this.getLatestTextureSlotRequest(t, e), o = this.getMaterialTextureSlot(t, e), a = this.getAssignedLODInformation(o)?.level ?? 1 / 0;
return s >= a ? !1 : r ? n ? s >= n.level : !1 : !0;
}
static shouldApplyStaleMeshLOD(t, e) {
const s = t["LOD:requested level"];
if (typeof s != "number") return !1;
const r = this.getAssignedLODInformation(t.geometry)?.level ?? 1 / 0;
return e < r && e >= s;
}
static setPendingTextureSlotRequest(t, e, s, r, n, o) {
let a = this.pendingTextureSlotRequests.get(t);
a || (a = /* @__PURE__ */ new Map(), this.pendingTextureSlotRequests.set(t, a));
const l = { level: s, force: r, id: n, promise: o };
a.set(e, l), o.finally(() => {
a.get(e)?.id === n && a.delete(e);
});
}
static getMaterialTextureSlot(t, e) {
const r = t.uniforms?.[e];
if (r?.value?.isTexture === !0)
return r.value;
const n = t[e];
return n?.isTexture === !0 ? n : null;
}
static setMaterialTextureSlot(t, e, s) {
const n = t.uniforms?.[e];
if (n?.value?.isTexture === !0) {
n.value = s, t.uniformsNeedUpdate = !0;
return;
}
t[e] = s;
}
static assignTrackedTextureSlot(t, e, s) {
let r = this.trackedTextureSlots.get(t);
r || (r = /* @__PURE__ */ new Map(), this.trackedTextureSlots.set(t, r));
const n = this.getMaterialTextureSlot(t, e);
let o = r.get(e);
!o && n ? o = this.ensureTrackedTextureSlot(t, e, n) : o && n && o !== n && (this.releaseTrackedTextureSlot(t, e, o), o = this.ensureTrackedTextureSlot(t, e, n)), !(o === s && n === s) && (o && o !== s && this.releaseTrackedTextureSlot(t, e, o), o !== s && (this.trackTextureUsage(s), r.set(e, s)), n !== s && this.setMaterialTextureSlot(t, e, s));
}
static ensureTrackedTextureSlot(t, e, s) {
let r = this.trackedTextureSlots.get(t);
r || (r = /* @__PURE__ */ new Map(), this.trackedTextureSlots.set(t, r));
const n = r.get(e);
return n === s ? n : (n && this.releaseTrackedTextureSlot(t, e, n), this.trackTextureUsage(s), r.set(e, s), s);
}
static releaseTrackedTextureSlot(t, e, s) {
const r = this.trackedTextureSlots.get(t);
if (r?.get(e) === s && r.delete(e), this.untrackTextureUsage(s) && (h || se)) {
const o = this.getAssignedLODInformation(s);
console.log(`[gltf-progressive] Disposed old texture LOD ${o?.level ?? "?"} for ${t.name || t.type}.${e}`, s.uuid);
}
}
parser;
url;
constructor(t) {
const e = t.options.path;
h && console.log("Progressive extension registered for", e), this.parser = t, this.url = e;
}
_isLoadingMesh;
loadMesh = (t) => {
if (this._isLoadingMesh) return null;
const e = this.parser.json.meshes[t]?.extensions?.[U];
return e ? (this._isLoadingMesh = !0, this.parser.getDependency("mesh", t).then((s) => (this._isLoadingMesh = !1, s && p.registerMesh(this.url, e.guid, s, e.lods?.length, 0, e), s))) : null;
};
// private _isLoadingTexture;
// loadTexture = (textureIndex: number) => {
// if (this._isLoadingTexture) return null;
// const ext = this.parser.json.textures[textureIndex]?.extensions?.[EXTENSION_NAME] as NEEDLE_ext_progressive_texture;
// if (!ext) return null;
// this._isLoadingTexture = true;
// return this.parser.getDependency("texture", textureIndex).then(tex => {
// this._isLoadingTexture = false;
// if (tex) {
// NEEDLE_progressive.registerTexture(this.url, tex as Texture, ext.lods?.length, textureIndex, ext);
// }
// return tex;
// });
// }
afterRoot(t) {
return h && console.log("AFTER", this.url, t), this.parser.json.textures?.forEach((e, s) => {
if (e?.extensions) {
const r = e?.extensions[U];
if (r) {
if (!r.lods) {
h && console.warn("Texture has no LODs", r);
return;
}
let n = !1;
for (const o of this.parser.associations.keys())
o.isTexture === !0 && this.parser.associations.get(o)?.textures === s && (n = !0, p.registerTexture(this.url, o, r.lods?.length, s, r));
n || this.parser.getDependency("texture", s).then((o) => {
o && p.registerTexture(this.url, o, r.lods?.length, s, r);
});
}
}
}), this.parser.json.meshes?.forEach((e, s) => {
if (e?.extensions) {
const r = e?.extensions[U];
if (r && r.lods) {
for (const n of this.parser.associations.keys())
if (n.isMesh) {
const o = this.parser.associations.get(n);
o?.meshes === s && p.registerMesh(this.url, r.guid, n, r.lods.length, o.primitives, r);
}
}
}
}), null;
}
/**
* Register a texture with progressive LOD information. This associates the texture with its LOD extension data
* so the LODs manager can later swap it for higher or lower resolution versions based on screen coverage.
* Typically called during glTF loading when the progressive extension is parsed.
*
* @param url - The source URL of the glTF file this texture was loaded from.
* @param tex - The three.js Texture instance to register.
* @param level - The LOD level this texture represents (0 = highest resolution).
* @param index - The texture index within the glTF file.
* @param ext - The parsed progressive texture extension data containing all available LOD levels and their dimensions.
*/
static registerTexture = (t, e, s, r, n) => {
if (!e) {
h && console.error("!! gltf-progressive: Called register texture without texture");
return;
}
if (h) {
const a = e.image?.width || e.source?.data?.width || 0, l = e.image?.height || e.source?.data?.height || 0;
console.log(`> gltf-progressive: register texture[${r}] "${e.name || e.uuid}", Current: ${a}x${l}, Max: ${n.lods[0]?.width}x${n.lods[0]?.height}, uuid: ${e.uuid}`, n, e);
}
e.source && (e.source[he] = n);
const o = n.guid;
p.assignLODInformation(t, e, o, s, r), p.lodInfos.set(o, n), p.lowresCache.set(o, new WeakRef(e));
};
/**
* Register a mesh with progressive LOD information. This associates the mesh geometry with its LOD extension data
* so the LODs manager can later swap it for higher or lower density versions based on screen coverage.
* Typically called during glTF loading when the progressive extension is parsed.
* If the mesh is registered at a level > 0 (i.e. not full resolution), a raycast mesh is automatically preserved for accurate picking.
*
* @param url - The source URL of the glTF file this mesh was loaded from.
* @param key - A unique key identifying this mesh's LOD group (typically derived from the extension GUID).
* @param mesh - The three.js Mesh instance to register.
* @param level - The LOD level this mesh represents (0 = highest resolution / full density).
* @param index - The primitive index within the glTF mesh node.
* @param ext - The parsed progressive mesh extension data containing all available LOD levels with vertex/index counts and densities.
*/
static registerMesh = (t, e, s, r, n, o) => {
const a = s.geometry;
if (!a) {
h && console.warn("gltf-progressive: Register mesh without geometry");
return;
}
a.userData || (a.userData = {}), h && console.log("> Progressive: register mesh " + s.name, { index: n, uuid: s.uuid }, o, s), p.assignLODInformation(t, a, e, r, n), p.lodInfos.set(e, o);
let u = p.lowresCache.get(e)?.deref();
u ? u.push(s.geometry) : u = [s.geometry], p.lowresCache.set(e, new WeakRef(u)), r > 0 && !ne(s) && ft(s, a);
for (const c of W)
c.onRegisteredNewMesh?.(s, o);
};
/**
* Dispose cached resources to free memory.
* Call this when a model is removed from the scene to allow garbage collection of its LOD resources.
* Calls three.js `.dispose()` on cached Textures and BufferGeometries to free GPU memory.
* Also clears reference counts for disposed textures.
* @param guid Optional GUID to dispose resources for a specific model. If omitted, all cached resources are cleared.
*/
static dispose(t) {
if (t) {
this.lodInfos.delete(t);
const e = this.lowresCache.get(t);
if (e) {
const s = e.deref();
if (s) {
if (s.isTexture) {
const r = s;
this.textureRefCounts.delete(r.uuid), r.dispose();
} else if (Array.isArray(s))
for (const r of s) r.dispose();
}
this.lowresCache.delete(t);
}
for (const [s, r] of this.cache)
s.includes(t) && (this._disposeCacheEntry(r), this.cache.delete(s));
} else {
this.lodInfos.clear();
for (const [, e] of this.lowresCache) {
const s = e.deref();
if (s) {
if (s.isTexture) {
const r = s;
this.textureRefCounts.delete(r.uuid), r.dispose();
} else if (Array.isArray(s))
for (const r of s) r.dispose();
}
}
this.lowresCache.clear();
for (const [, e] of this.cache)
this._disposeCacheEntry(e);
this.cache.clear(), this.textureRefCounts.clear(), this.trackedTextureSlots = /* @__PURE__ */ new WeakMap(), this.pendingTextureSlotRequests = /* @__PURE__ */ new WeakMap(), this.latestTextureSlotRequests = /* @__PURE__ */ new WeakMap(), this.textureSlotRequestId = 0;
}
}
/** Dispose a single cache entry's three.js resource(s) to free GPU memory. */
static _disposeCacheEntry(t) {
if (t instanceof WeakRef) {
const e = t.deref();
e && (e.isTexture && this.textureRefCounts.delete(e.uuid), e.dispose());
} else
t.then((e) => {
if (e)
if (Array.isArray(e))
for (const s of e) s.dispose();
else
e.isTexture && this.textureRefCounts.delete(e.uuid), e.dispose();
}).catch(() => {
});
}
/** A map of key = asset uuid and value = LOD information */
static lodInfos = /* @__PURE__ */ new Map();
/** cache of already loaded mesh lods. Uses WeakRef for single resources to allow garbage collection when unused. */
static cache = /* @__PURE__ */ new Map();
/** this contains the geometry/textures that were originally loaded. Uses WeakRef to allow garbage collection when unused. */
static lowresCache = /* @__PURE__ */ new Map();
/** Reference counting for textures to track usage across multiple materials/objects */
static textureRefCounts = /* @__PURE__ */ new Map();
/**
* FinalizationRegistry to automatically clean up `previouslyLoaded` cache entries
* when their associated three.js resources are garbage collected by the browser.
* The held value is the cache key string used in `previouslyLoaded`.
*/
static _resourceRegistry = new FinalizationRegistry((t) => {
const e = p.cache.get(t);
(h || se) && console.debug(`[gltf-progressive] Memory: Resource GC'd
${t}`), e instanceof WeakRef && (e.deref() || (p.cache.delete(t), (h || se) && console.log("[gltf-progressive] ↪ Cache entry deleted (GC)")));
});
/**
* Track texture usage by incrementing reference count
*/
static trackTextureUsage(t) {
const e = t.uuid, s = this.textureRefCounts.get(e) || 0;
this.textureRefCounts.set(e, s + 1), h === "verbose" && console.log(`[gltf-progressive] Track texture ${e}, refCount: ${s} → ${s + 1}`);
}
/**
* Untrack texture usage by decrementing reference count.
* Automatically disposes the texture when reference count reaches zero.
* @returns true if the texture was disposed, false otherwise
*/
static untrackTextureUsage(t) {
const e = t.uuid, s = this.textureRefCounts.get(e);
if (!s)
return (h === "verbose" || se) && n("[gltf-progressive] Memory: Untrack untracked texture (dispose immediately)", 0), t.dispose(), !0;
const r = s - 1;
if (r <= 0)
return this.textureRefCounts.delete(e), (h || se) && n("[gltf-progressive] Memory: Dispose texture", r), t.dispose(), !0;
return this.textureRefCounts.set(e, r), h === "verbose" && n("[gltf-progressive] Memory: Untrack texture", r), !1;
function n(o, a) {
const l = t.image?.width || t.source?.data?.width || 0, u = t.image?.height || t.source?.data?.height || 0, c = l && u ? `${l}x${u}` : "N/A";
let d = "N/A";
l && u && (d = `~${(ut(t) / (1024 * 1024)).toFixed(2)} MB`), console.log(`${o} — ${t.name} ${c} (${d}), refCount: ${s} → ${a}
${e}`);
}
}
static workers = [];
static _workersIndex = 0;
static async getOrLoadLOD(t, e, s) {
const r = h == "verbose", n = this.getAssignedLODInformation(t);
if (!n)
return h && console.warn(`[gltf-progressive] No LOD information found: ${t.name}, uuid: ${t.uuid}, type: ${t.type}`, t), null;
const o = n?.key;
let a;
if (t.isTexture === !0) {
const u = t;
u.source && u.source[he] && (a = u.source[he]);
}
if (a || (a = p.lodInfos.get(o)), !a)
h && console.warn(`Can not load LOD ${e}: no LOD info found for "${o}" ${t.name}`, t.type, p.lodInfos);
else {
if (e > 0) {
let d = !1;
const f = Array.isArray(a.lods);
if (f && e >= a.lods.length ? d = !0 : f || (d = !0), d) {
const g = this.lowresCache.get(o);
if (g) {
const O = g.deref();
if (O) return O;
this.lowresCache.delete(o), h && console.log(`[gltf-progressive] Lowres cache entry was GC'd: ${o}`);
}
return null;
}
}
const u = Array.isArray(a.lods) ? a.lods[e]?.path : a.lods;
if (!u)
return h && !a["missing:uri"] && (a["missing:uri"] = !0, console.warn("Missing uri for progressive asset for LOD " + e, a)), null;
const c = at(n.url, u);
if (c.endsWith(".glb") || c.endsWith(".gltf")) {
if (!a.guid)
return console.warn("missing pointer for glb/gltf texture", a), null;
const d = c + "_" + a.guid, f = await this.tryResolveLODCacheEntry(this.cache.get(d), d, c, t, e, r);
if (f.found) return f.value;
if (s?.isCurrent?.() === !1)
return r && console.log(`Skipping stale LOD ${e} request before queue: ${c}`), null;
const g = await this.queue.slot(c);
if (s?.isCurrent?.() === !1)
return r && console.log(`Skipping stale LOD ${e} request after queue: ${c}`), null;
const O = await this.tryResolveLODCacheEntry(this.cache.get(d), d, c, t, e, r);
if (O.found) return O.value;
if (!g.use)
return h && console.log(`LOD ${e} was aborted: ${c}`), null;
const y = a, x = new Promise(async (L, N) => {
if (wt) {
const m = await (await yt({})).load(c);
if (m.textures.length > 0)
for (const M of m.textures) {
let _ = M.texture;
return p.assignLODInformation(n.url, _, o, e, void 0), t instanceof G && (_ = this.copySettings(t, _)), _ && (_.guid = y.guid), L(_);
}
if (m.geometries.length > 0) {
const M = new Array();
for (const _ of m.geometries) {
const B = _.geometry;
p.assignLODInformation(n.url, B, o, e, _.primitiveIndex), M.push(B);
}
return L(M);
}
return L(null);
}
const w = new ve();
Ie(w), h && (await new Promise((b) => setTimeout(b, 1e3)), r && console.warn("Start loading (delayed) " + c, y.guid));
let S = c;
if (y && Array.isArray(y.lods)) {
const b = y.lods[e];
b.hash && (S += "?v=" + b.hash);
}
const D = await w.loadAsync(S).catch((b) => (console.error(`Error loading LOD ${e} from ${c}
`, b), L(null)));
if (!D)
return L(null);
const R = D.parser;
r && console.log("Loading finished " + c, y.guid);
let C = 0;
if (D.parser.json.textures) {
let b = !1;
for (const m of D.parser.json.textures) {
if (m?.extensions) {
const M = m?.extensions[U];
if (M?.guid && M.guid === y.guid) {
b = !0;
break;
}
}
C++;
}
if (b) {
let m = await R.getDependency("texture", C);
return m && p.assignLODInformation(n.url, m, o, e, void 0), r && console.log('change "' + t.name + '" → "' + m.name + '"', c, C, m, d), t instanceof G && (m = this.copySettings(t, m)), m && (m.guid = y.guid), L(m);
} else h && console.warn("Could not find texture with guid", y.guid, D.parser.json);
}
if (C = 0, D.parser.json.meshes) {
let b = !1;
for (const m of D.parser.json.meshes) {
if (m?.extensions) {
const M = m?.extensions[U];
if (M?.guid && M.guid === y.guid) {
b = !0;
break;
}
}
C++;
}
if (b) {
const m = await R.getDependency("mesh", C);
if (r && console.log(`Loaded Mesh "${m.name}"`, c, C, m, d), m.isMesh === !0) {
const M = m.geometry;
return p.assignLODInformation(n.url, M, o, e, 0), L(M);
} else {
const M = new Array();
for (let _ = 0; _ < m.children.length; _++) {
const B = m.children[_];
if (B.isMesh === !0) {
const ie = B.geometry;
p.assignLODInformation(n.url, ie, o, e, _), M.push(ie);
}
}
return L(M);
}
} else h && console.warn("Could not find mesh with guid", y.guid, D.parser.json);
}
return L(null);
});
this.cache.set(d, x), g.use(x);
const v = await x;
return v != null ? v instanceof G ? (this.cache.set(d, new WeakRef(v)), p._resourceRegistry.register(v, d)) : Array.isArray(v) ? this.cache.set(d, Promise.resolve(v)) : this.cache.set(d, Promise.resolve(v)) : this.cache.set(d, Promise.resolve(null)), v;
} else if (t instanceof G) {
if (s?.isCurrent?.() === !1)
return r && console.log(`Skipping stale texture LOD ${e} request: ${c}`), null;
r && console.log("Load texture from uri: " + c);
const f = await new Ke().loadAsync(c);
return s?.isCurrent?.() === !1 ? (f?.dispose(), null) : (f ? (f.guid = a.guid, f.flipY = !1, f.needsUpdate = !0, f.colorSpace = t.colorSpace, r && console.log(a, f)) : h && console.warn("failed loading", c), f);
}
}
return null;
}
static async tryResolveLODCacheEntry(t, e, s, r, n, o) {
if (t === void 0)
return { found: !1 };
if (o && console.log(`LOD ${n} was already loading/loaded: ${e}`), t instanceof WeakRef) {
const u = t.deref();
if (u) {
let c = u, d = !1;
if (c instanceof G && r instanceof G ? c.image?.data || c.source?.data ? c = this.copySettings(r, c) : d = !0 : c instanceof j && r instanceof j && (c.attributes.position?.array || (d = !0)), !d)
return { found: !0, value: c };
}
return this.cache.delete(e), h && console.log(`[gltf-progressive] Re-loading GC'd/disposed resource: ${e}`), { found: !1 };
}
let a = await t.catch((u) => (console.error(`Error loading LOD ${n} from ${s}
`, u), null)), l = !1;
return a == null || (a instanceof G && r instanceof G ? a.image?.data || a.source?.data ? a = this.copySettings(r, a) : (l = !0, this.cache.delete(e)) : a instanceof j && r instanceof j && (a.attributes.position?.array || (l = !0, this.cache.delete(e)))), l ? { found: !1 } : { found: !0, value: a };
}
static _queue;
static get queue() {
return this._queue ??= new lt(Me() ? 20 : 50, { debug: h != !1 });
}
static assignLODInformation(t, e, s, r, n) {
if (!e) return;
e.userData || (e.userData = {});
const o = new Lt(t, s, r, n);
e.userData.LODS = o, "source" in e && typeof e.source == "object" && (e.source.LODS = o);
}
static getAssignedLODInformation(t) {
return t ? t.userData?.LODS ? t.userData.LODS : "source" in t && t.source?.LODS ? t.source.LODS : null : null;
}
// private static readonly _copiedTextures: WeakMap<Texture, Texture> = new Map();
static copySettings(t, e) {
return e ? (h === "verbose" && console.debug(`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;
}
}
class Lt {
url;
/** the key to lookup the LOD information */
key;
level;
/** For multi objects (e.g. a group of meshes) this is the index of the object */
index;
constructor(t, e, s, r) {
this.url = t, this.key = e, this.level = s, r != null && (this.index = r);
}
}
class oe {
static addPromise = (t, e, s, r) => {
r.forEach((n) => {
n.add(t, e, s);
});
};
ready;
/** The number of promises that have been added to this group so far */
get awaitedCount() {
return this._addedCount;
}
/** The number of promises that have been resolved */
get resolvedCount() {
return this._resolvedCount;
}
/** The number of promises that are in-flight */
get currentlyAwaiting() {
return this._awaiting.length;
}
_resolve;
_signal;
/** start frame can be undefined if the user configured this group to wait for the first promise.
* Then the start frame will be set when the first promise has been added to the group */
_frame_start;
/** How many frames to capture since the start frame */
_frames_to_capture;
_resolved = !1;
_addedCount = 0;
_resolvedCount = 0;
/** These promises are currently being awaited */
_awaiting = [];
_maxPromisesPerObject = 1;
constructor(t, e) {
const r = Math.max(e.frames ?? 2, 2);
this._frame_start = e.waitForFirstCapture ? void 0 : t, this._frames_to_capture = r, this.ready = new Promise((n) => {
this._resolve = n;
}), this.ready.finally(() => {
this._resolved = !0, this._awaiting.length = 0;
}), this._signal = e.signal, this._signal?.addEventListener("abort", () => {
this.resolveNow();
}), this._maxPromisesPerObject = Math.max(1, e.maxPromisesPerObject ?? 1);
}
_currentFrame = 0;
update(t) {
this._currentFrame = t, this._frame_start === void 0 && this._addedCount > 0 && (this._frame_start = t), (this._signal?.aborted || this._awaiting.length === 0 && this._frame_start !== void 0 && t > this._frame_start + this._frames_to_capture) && this.resolveNow();
}
_seen = /* @__PURE__ */ new WeakMap();
add(t, e, s) {
if (this._resolved) {
h && console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");
return;
}
if (!(this._frame_start !== void 0 && this._currentFrame > this._frame_start + this._frames_to_capture)) {
if (this._maxPromisesPerObject >= 1)
if (this._seen.has(e)) {
const r = this._seen.get(e);
if (r >= this._maxPromisesPerObject) {
h && console.warn("PromiseGroup: Already awaiting object ignoring new promise for it.");
return;
}
this._seen.set(e, r + 1);
} else
this._seen.set(e, 1);
this._awaiting.push(s), this._addedCount++, s.finally(() => {
this._resolvedCount++, this._awaiting.splice(this._awaiting.indexOf(s), 1);
});
}
}
resolveNow() {
this._resolved || this._resolve?.({
awaited_count: this._addedCount,
resolved_count: this._resolvedCount,
cancelled: this._signal?.aborted ?? !1
});
}
}
const A = E("debugprogressive"), vt = A === "colors", Dt = E("noprogressive"), ge = /* @__PURE__ */ Symbol("Needle:LODSManager"), pe = /* @__PURE__ */ Symbol("Needle:LODState"), F = /* @__PURE__ */ Symbol("Needle:CurrentLOD"), T = { mesh_lod: -1, texture_lod: -1 }, Mt = new Re(), Oe = [
3526751,
11065402,
15978811,
15897394,
15749691,
11032304,
4827122,
3332036,
16739229,
7306743,
14053330,
3516499,
12035359,
14703919,
3963096,
42662,
14100029,
8344319,
4633680,
16229681,
3120096,
12076434,
9083434,
2060171,
15751837,
10182117,
48121,
62932,
16704576,
15817653,
5083278,
5592405
], me = new ce(), z = new ce(), Se = new ce(), _t = new k(), Ot = new k(), St = new ke(), q = new k(), Y = new k(), Q = new k(), J = new k();
function bt(i, t) {
const e = i.min, s = i.max, r = (e.x + s.x) * 0.5, n = (e.y + s.y) * 0.5;
return q.set(r, n, e.z).applyMatrix4(t).z < 0;
}
function Tt(i) {
const {
geometry: t,
matrixWorld: e,
camera: s,
projectionScreenMatrix: r,
desiredDensity: n,
canvasHeight: o = 0,
currentLevel: a = -1,
xrEnabled: l = !1,
debugDrawLine: u,
warnMissingPrimitiveDensities: c = !1
} = i, d = p.getMeshLODExtension(t)?.lods, f = p.getPrimitiveIndex(t), g = i.target ?? {
level: a,
pr