UNPKG

3d-tiles-renderer

Version:

https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification

999 lines (998 loc) 40.1 kB
var he = Object.defineProperty; var ue = (t, e, n) => e in t ? he(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n; var M = (t, e, n) => ue(t, typeof e != "symbol" ? e + "" : e, n); import { t as fe } from "./LoaderBase-CU5shB7w.js"; function W(t) { if (!t) return null; let e = t.length; const n = t.indexOf("?"), s = t.indexOf("#"); n !== -1 && (e = Math.min(e, n)), s !== -1 && (e = Math.min(e, s)); const a = t.lastIndexOf(".", e), r = t.lastIndexOf("/", e), i = t.indexOf("://"); return i !== -1 && i + 2 === r || a === -1 || a < r ? null : t.substring(a + 1, e) || null; } class F { /** * Sets the active "XRSession" value to use to scheduling rAF callbacks. * @param {XRSession} session */ static setXRSession(e) { e !== this.session && (this.flushPending(), this.session = e); } /** * Request animation frame (defer to XR session if set) * @param {Function} cb * @returns {number} */ static requestAnimationFrame(e) { const { session: n, pending: s } = this; let a; const r = () => { s.delete(a), e(); }; return n ? a = n.requestAnimationFrame(r) : a = requestAnimationFrame(r), s.set(a, e), a; } /** * Cancel animation frame via handle (defer to XR session if set) * @param {number} handle */ static cancelAnimationFrame(e) { const { pending: n, session: s } = this; n.delete(e), s ? s.cancelAnimationFrame(e) : cancelAnimationFrame(e); } /** * Flush and complete pending AFs (defer to XR session if set) */ static flushPending() { this.pending.forEach((e, n) => { e(), this.cancelAnimationFrame(n); }); } } M(F, "pending", /* @__PURE__ */ new Map()), M(F, "session", null); const $ = 2 ** 30; class ve { /** * Comparator used to determine eviction order. Items that sort last are evicted first. * When `null`, eviction order is by last-used time. * @type {UnloadPriorityCallback|null} * @default null */ get unloadPriorityCallback() { return this._unloadPriorityCallback; } set unloadPriorityCallback(e) { e.length === 1 ? (console.warn('LRUCache: "unloadPriorityCallback" function has been changed to take two arguments.'), this._unloadPriorityCallback = (n, s) => { const a = e(n), r = e(s); return a < r ? -1 : a > r ? 1 : 0; }) : this._unloadPriorityCallback = e; } constructor() { this.minSize = 6e3, this.maxSize = 8e3, this.minBytesSize = 0.3 * $, this.maxBytesSize = 0.4 * $, this.unloadPercent = 0.05, this.autoMarkUnused = !0, this.itemSet = /* @__PURE__ */ new Map(), this.itemList = [], this.usedSet = /* @__PURE__ */ new Set(), this.callbacks = /* @__PURE__ */ new Map(), this.unloadingHandle = -1, this.cachedBytes = 0, this.bytesMap = /* @__PURE__ */ new Map(), this.loadedSet = /* @__PURE__ */ new Set(), this._unloadPriorityCallback = null; const e = this.itemSet; this.defaultPriorityCallback = (n) => e.get(n); } /** * Returns whether the cache has reached its maximum item count or byte size. * @returns {boolean} */ isFull() { return this.itemSet.size >= this.maxSize || this.cachedBytes >= this.maxBytesSize; } /** * Returns the byte size registered for the given item, or 0 if not tracked. * @param {any} item * @returns {number} */ getMemoryUsage(e) { return this.bytesMap.get(e) || 0; } /** * Sets the byte size for the given item, updating the total `cachedBytes` count. * @param {any} item * @param {number} bytes */ setMemoryUsage(e, n) { const { bytesMap: s, itemSet: a } = this; a.has(e) && (this.cachedBytes -= s.get(e) || 0, s.set(e, n), this.cachedBytes += n); } /** * Adds an item to the cache. Returns false if the item already exists or the cache is full. * @param {any} item * @param {RemoveCallback} removeCb - Called with the item when it is evicted * @returns {boolean} */ add(e, n) { const s = this.itemSet; if (s.has(e) || this.isFull()) return !1; const a = this.usedSet, r = this.itemList, i = this.callbacks; return r.push(e), a.add(e), s.set(e, Date.now()), i.set(e, n), !0; } /** * Returns whether the given item is in the cache. * @param {any} item * @returns {boolean} */ has(e) { return this.itemSet.has(e); } /** * Removes an item from the cache immediately, invoking its removal callback. * Returns false if the item was not in the cache. * @param {any} item * @returns {boolean} */ remove(e) { const n = this.usedSet, s = this.itemSet, a = this.itemList, r = this.bytesMap, i = this.callbacks, l = this.loadedSet; if (s.has(e)) { this.cachedBytes -= r.get(e) || 0, r.delete(e), i.get(e)(e); const o = a.indexOf(e); return a.splice(o, 1), n.delete(e), s.delete(e), i.delete(e), l.delete(e), !0; } return !1; } /** * Marks whether an item has finished loading. Unloaded items may be evicted early * when the cache is over its max size limits, even if they are marked as used. * @param {any} item * @param {boolean} value */ setLoaded(e, n) { const { itemSet: s, loadedSet: a } = this; s.has(e) && (n === !0 ? a.add(e) : a.delete(e)); } /** * Marks an item as used in the current frame, preventing it from being evicted. * @param {any} item */ markUsed(e) { const n = this.itemSet, s = this.usedSet; n.has(e) && !s.has(e) && (n.set(e, Date.now()), s.add(e)); } /** * Marks an item as unused, making it eligible for eviction. * @param {any} item */ markUnused(e) { this.usedSet.delete(e); } /** * Marks all items in the cache as unused. */ markAllUnused() { this.usedSet.clear(); } /** * Returns whether the given item is currently marked as used. * @param {any} item * @returns {boolean} */ isUsed(e) { return this.usedSet.has(e); } /** * Evicts unused items until the cache is within its min size and byte limits. * Items are sorted by `unloadPriorityCallback` before eviction. */ // TODO: this should be renamed because it's not necessarily unloading all unused content // Maybe call it "cleanup" or "unloadToMinSize" unloadUnusedContent() { const { unloadPercent: e, minSize: n, maxSize: s, itemList: a, itemSet: r, usedSet: i, loadedSet: l, callbacks: o, bytesMap: u, minBytesSize: v, maxBytesSize: c } = this, h = a.length - i.size, k = a.length - l.size, d = Math.max(Math.min(a.length - n, h), 0), p = this.cachedBytes - v, P = this.unloadPriorityCallback || this.defaultPriorityCallback; let L = !1; const re = d > 0 && h > 0 || k && a.length > s; if (h && this.cachedBytes > v || k && this.cachedBytes > c || re) { a.sort((f, g) => { const G = i.has(f), de = i.has(g); if (G === de) { const H = l.has(f), ce = l.has(g); return H === ce ? -P(f, g) : H ? 1 : -1; } else return G ? 1 : -1; }); const ie = Math.max(n * e, d * e), j = Math.ceil(Math.min(ie, h, d)), le = Math.max(e * p, e * v), oe = Math.min(le, p); let m = 0, U = 0; for (; this.cachedBytes - U > c || a.length - m > s; ) { const f = a[m], g = u.get(f) || 0; if (i.has(f) && l.has(f) || this.cachedBytes - U - g < c && a.length - m <= s) break; U += g, m++; } for (; U < oe || m < j; ) { const f = a[m], g = u.get(f) || 0; if (i.has(f) || this.cachedBytes - U - g < v && m >= j) break; U += g, m++; } a.splice(0, m).forEach((f) => { this.cachedBytes -= u.get(f) || 0, o.get(f)(f), u.delete(f), r.delete(f), o.delete(f), l.delete(f), i.delete(f); }), L = m < d || U < p && m < h, L = L && m > 0; } L && (this.unloadingHandle = F.requestAnimationFrame(() => this.scheduleUnload())); } /** * Schedules `unloadUnusedContent` to run asynchronously via microtask. */ scheduleUnload() { F.cancelAnimationFrame(this.unloadingHandle), this.scheduled || (this.scheduled = !0, queueMicrotask(() => { this.scheduled = !1, this.unloadUnusedContent(); })); } } class me extends DOMException { constructor() { super("PriorityQueue: Item removed", "AbortError"); } } class q { /** * returns whether tasks are queued or actively running * @readonly * @type {boolean} */ get running() { return this.items.length !== 0 || this.currJobs !== 0; } constructor() { this.maxJobs = 6, this.items = [], this.callbacks = /* @__PURE__ */ new Map(), this.currJobs = 0, this.scheduled = !1, this.autoUpdate = !0, this.priorityCallback = null, this._schedulingCallback = (e) => { F.requestAnimationFrame(e); }, this._runjobs = () => { this.scheduled = !1, this.tryRunJobs(); }; } /** * Sorts the pending item list using `priorityCallback`, if set. */ sort() { const e = this.priorityCallback, n = this.items; e !== null && n.sort(e); } /** * Returns whether the given item is currently queued. * @param {any} item * @returns {boolean} */ has(e) { return this.callbacks.has(e); } /** * Adds an item to the queue and returns a Promise that resolves when the item's * callback completes, or rejects if the item is removed before running. * @param {any} item * @param {ItemCallback} callback - Invoked with `item` when it is dequeued; may return a Promise * @returns {Promise<any>} */ add(e, n) { const s = { callback: n, reject: null, resolve: null, promise: null }; return s.promise = new Promise((a, r) => { const i = this.items, l = this.callbacks; s.resolve = a, s.reject = r, i.unshift(e), l.set(e, s), this.autoUpdate && this.scheduleJobRun(); }), s.promise; } /** * Removes an item from the queue, rejecting its promise with an `AbortError` DOMException. * @param {any} item */ remove(e) { const n = this.items, s = this.callbacks, a = n.indexOf(e); if (a !== -1) { const r = s.get(e); r.promise.catch((i) => { if (i.name !== "AbortError") throw i; }), r.reject(new me()), n.splice(a, 1), s.delete(e); } } /** * Removes all queued items for which `filter` returns true. * @param {FilterCallback} filter - Called with each item; return true to remove */ removeByFilter(e) { const { items: n } = this; for (let s = 0; s < n.length; s++) { const a = n[s]; e(a) && (this.remove(a), s--); } } /** * Immediately attempts to dequeue and run pending jobs up to `maxJobs` concurrency. */ tryRunJobs() { this.sort(); const e = this.items, n = this.callbacks, s = this.maxJobs; let a = 0; const r = () => { this.currJobs--, this.autoUpdate && this.scheduleJobRun(); }; for (; s > this.currJobs && e.length > 0 && a < s; ) { this.currJobs++, a++; const i = e.pop(), { callback: l, resolve: o, reject: u } = n.get(i); n.delete(i); let v; try { v = l(i); } catch (c) { u(c), r(); } v instanceof Promise ? v.then(o).catch(u).finally(r) : (o(v), r()); } } /** * Immediately runs the callback for the given item, removing it from the queue. * Does nothing if the item is not queued. * @param {any} item * @returns {Promise<any>|any} */ flush(e) { const { items: n, callbacks: s } = this, a = n.indexOf(e); if (!s.has(e)) return; const { callback: r, resolve: i, reject: l } = s.get(e); s.delete(e), n.splice(a, 1); let o; try { o = r(e); } catch (u) { l(u); return; } return o instanceof Promise ? o.then(i).catch(l) : i(o), o; } /** * Schedules a deferred call to `tryRunJobs` via `schedulingCallback`. */ scheduleJobRun() { this.scheduled || (this._schedulingCallback(this._runjobs), this.scheduled = !0); } } const R = -1, C = 0, A = 1, x = 2, O = 3, S = 4, ke = 6378137, Fe = 1 / 298.257223563, Re = 6356752314245179e-9, B = { inView: !1, error: 1 / 0, distanceFromCamera: 1 / 0 }; function V(t) { return t === S || t === R; } function y(t, e) { return I(t) && t.traversal.lastFrameVisited === e && t.traversal.used; } function I(t) { return !!t.traversal; } function E(t) { const { children: e } = t, n = e.length === 0 || I(e[e.length - 1]), s = !t.internal.hasUnrenderableContent || V(t.internal.loadingState); return n && s; } function T(t) { return t.traversal.unconditionallyRefine; } function w(t, e) { if (I(t) && (e.ensureChildrenArePreprocessed(t), t.traversal.lastFrameVisited !== e.frameCount && (t.traversal.wasInFrustum = t.traversal.inFrustum, t.traversal.wasSetActive = t.traversal.active, t.traversal.wasSetVisible = t.traversal.visible, t.traversal.usedLastFrame = t.traversal.used, t.traversal.lastFrameVisited = e.frameCount, t.traversal.used = !1, t.traversal.inFrustum = !1, t.traversal.isLeaf = !1, t.traversal.visible = !1, t.traversal.active = !1, t.traversal.error = 1 / 0, t.traversal.distanceFromCamera = 1 / 0, t.traversal.allChildrenReady = !1, t.traversal.allChildrenLoaded = !1, t.traversal.kicked = !1, t.traversal.allUsedChildrenProcessed = !1, e.calculateTileViewErrorWithPlugin(t, B), t.traversal.inFrustum = B.inView, t.traversal.error = B.error, t.traversal.distanceFromCamera = B.distanceFromCamera, t.traversal.unconditionallyRefine = t.internal.hasUnrenderableContent, !t.traversal.unconditionallyRefine))) { let n = t.parent; for (; n && n.traversal.unconditionallyRefine; ) n = n.parent; n && n.geometricError <= t.geometricError && (t.traversal.unconditionallyRefine = !0); } } function N(t, e, n = !1) { if (w(t, e), n ? e.markTileUsed(t) : D(t), T(t) && E(t)) { const s = t.children; for (let a = 0, r = s.length; a < r; a++) N(s[a], e, n); } } function K(t, e) { if (w(t, e), t.traversal.usedLastFrame && (D(t), t.traversal.wasSetActive && (t.traversal.active = !0), (!t.traversal.active || T(t)) && E(t))) { const n = t.children; for (let s = 0, a = n.length; s < a; s++) K(n[s], e); } } function D(t) { t.traversal.used = !0; } function pe(t, e) { return !(t.traversal.error <= e.errorTarget && !T(t) || e.maxDepth > 0 && t.internal.depth + 1 >= e.maxDepth || !E(t)); } function Z(t, e) { const { frameCount: n } = e, { children: s } = t; for (let a = 0, r = s.length; a < r; a++) { const i = s[a]; y(i, n) && (i.traversal.active && (i.traversal.kicked = !0, i.traversal.active = !1), Z(i, e)); } } function Y(t) { return !T(t) && (!t.internal.hasContent || V(t.internal.loadingState)); } function ee(t, e) { if (w(t, e), !t.traversal.inFrustum) return; if (!pe(t, e)) { D(t); return; } let n = !1, s = !1; const a = t.children; for (let r = 0, i = a.length; r < i; r++) { const l = a[r]; ee(l, e), n = n || y(l, e.frameCount), s = s || l.traversal.inFrustum; } if (t.refine === "REPLACE" && !s && a.length !== 0) { t.traversal.inFrustum = !1, e.markTileUsed(t); for (let r = 0, i = a.length; r < i; r++) N(a[r], e, !0); return; } if (D(t), t.refine === "REPLACE" && n && (e.loadSiblings || e.loadAncestors)) for (let r = 0, i = a.length; r < i; r++) N(a[r], e); } function te(t, e) { const n = e.frameCount; if (!y(t, n)) return; const s = t.children; let a = !1; for (let i = 0, l = s.length; i < l; i++) { const o = s[i]; a = a || y(o, n); } if (!a) t.traversal.isLeaf = !0; else { for (let l = 0, o = s.length; l < o; l++) te(s[l], e); let i = !0; for (let l = 0, o = s.length; l < o; l++) { const u = s[l]; if (y(u, n)) { const v = !T(u), c = !u.internal.hasContent || V(u.internal.loadingState); v && c || u.traversal.allChildrenLoaded || (i = !1); } } t.traversal.allChildrenLoaded = i; } let r = !0; for (let i = 0, l = s.length; i < l; i++) { const o = s[i]; y(o, e.frameCount) && !o.traversal.allUsedChildrenProcessed && (r = !1); } t.traversal.allUsedChildrenProcessed = r && E(t); } function se(t, e) { if (!y(t, e.frameCount)) return; const n = t.children; if (e.loadAncestors && !t.traversal.allChildrenLoaded && !T(t) && (t.traversal.isLeaf = !0), t.traversal.isLeaf) { if (!T(t) && (t.traversal.active = !0, E(t) && t.internal.hasContent && !V(t.internal.loadingState))) for (let a = 0, r = n.length; a < r; a++) K(n[a], e); return; } let s = n.length > 0; for (let a = 0, r = n.length; a < r; a++) { const i = n[a]; se(i, e), y(i, e.frameCount) && !(i.traversal.active && Y(i)) && !i.traversal.allChildrenReady && (s = !1); } t.traversal.allChildrenReady = s, !s && t.traversal.wasSetActive && Y(t) && (t.traversal.active = !0, Z(t, e)); } function ne(t, e) { w(t, e); const n = y(t, e.frameCount); if (n && (t.internal.hasUnrenderableContent && (e.markTileUsed(t), e.queueTileForDownload(t)), t.internal.hasRenderableContent && t.refine === "ADD" && (t.traversal.active = !0), (t.traversal.active || t.traversal.kicked) && t.internal.hasContent && (e.markTileUsed(t), t.traversal.allUsedChildrenProcessed && e.queueTileForDownload(t), t.internal.loadingState !== S && (t.traversal.active = !1)), e.loadAncestors && t.internal.hasContent && (e.markTileUsed(t), e.queueTileForDownload(t)), t.internal.virtualChildCount > 0 && t.internal.hasContent && e.markTileUsed(t), t.traversal.visible = t.internal.hasRenderableContent && t.traversal.active && t.traversal.inFrustum && t.internal.loadingState === S, e.stats.used++, t.traversal.inFrustum && e.stats.inFrustum++), n || I(t) && t.traversal.usedLastFrame) { let s = !1, a = !1; n ? (s = t.traversal.active, e.displayActiveTiles ? a = t.traversal.active || t.traversal.visible : a = t.traversal.visible) : w(t, e), t.internal.hasRenderableContent && t.internal.loadingState === S ? (s && e.stats.active++, a && e.stats.visible++, t.traversal.wasSetActive !== s && e.invokeOnePlugin((i) => i.setTileActive && i.setTileActive(t, s)), t.traversal.wasSetVisible !== a && e.invokeOnePlugin((i) => i.setTileVisible && i.setTileVisible(t, a))) : t.internal.hasRenderableContent || (a = t.traversal.isLeaf, t.traversal.wasSetVisible !== a && e.invokeOnePlugin((i) => i.setEmptyTileVisible && i.setEmptyTileVisible(t, a))), t.traversal.visible = a, t.traversal.active = s; const r = t.children; for (let i = 0, l = r.length; i < l; i++) { const o = r[i]; ne(o, e); } } } function ge(t, e) { ee(t, e), te(t, e), se(t, e), ne(t, e); } function ye(t) { let e = null; return () => { e === null && (e = F.requestAnimationFrame(() => { e = null, t(); })); }; } const X = Symbol("PLUGIN_REGISTERED"), b = { inView: !0, error: 0, distance: 1 / 0 }, Ce = (t, e) => { const n = t.priority || 0, s = e.priority || 0; return n !== s ? n > s ? 1 : -1 : !t.traversal || !e.traversal ? 0 : t.traversal.used !== e.traversal.used ? t.traversal.used ? 1 : -1 : t.traversal.error !== e.traversal.error ? t.traversal.error > e.traversal.error ? 1 : -1 : t.traversal.distanceFromCamera !== e.traversal.distanceFromCamera ? t.traversal.distanceFromCamera > e.traversal.distanceFromCamera ? -1 : 1 : t.internal.depthFromRenderedParent !== e.internal.depthFromRenderedParent ? t.internal.depthFromRenderedParent > e.internal.depthFromRenderedParent ? -1 : 1 : 0; }, be = (t, e) => t.traversal.used !== e.traversal.used ? t.traversal.used ? 1 : -1 : t.traversal.inFrustum !== e.traversal.inFrustum ? t.traversal.inFrustum ? 1 : -1 : t.internal.hasUnrenderableContent !== e.internal.hasUnrenderableContent ? t.internal.hasUnrenderableContent ? 1 : -1 : t.traversal.distanceFromCamera !== e.traversal.distanceFromCamera ? t.traversal.distanceFromCamera > e.traversal.distanceFromCamera ? -1 : 1 : t.internal.depthFromRenderedParent !== e.internal.depthFromRenderedParent ? t.internal.depthFromRenderedParent > e.internal.depthFromRenderedParent ? -1 : 1 : 0, Se = (t, e) => t.traversal.lastFrameVisited !== e.traversal.lastFrameVisited ? t.traversal.lastFrameVisited > e.traversal.lastFrameVisited ? -1 : 1 : t.internal.depthFromRenderedParent !== e.internal.depthFromRenderedParent ? t.internal.depthFromRenderedParent > e.internal.depthFromRenderedParent ? 1 : -1 : t.internal.loadingState !== e.internal.loadingState ? t.internal.loadingState > e.internal.loadingState ? -1 : 1 : t.internal.hasUnrenderableContent !== e.internal.hasUnrenderableContent ? t.internal.hasUnrenderableContent ? -1 : 1 : t.traversal.error !== e.traversal.error ? t.traversal.error > e.traversal.error ? -1 : 1 : 0, _ = (t, e) => { const n = t.priority ?? 1 / 0, s = e.priority ?? 1 / 0; if (n !== s) return n > s ? 1 : -1; if (!t.internal || !e.internal) return 0; const a = t.internal.renderer, r = e.internal.renderer, i = !a.loadAncestors, l = !r.loadAncestors; return i && l ? be(t, e) : Ce(t, e); }, ae = new ve(); ae.unloadPriorityCallback = Se; const Q = new q(); Q.maxJobs = 25; Q.priorityCallback = _; const J = new q(); J.maxJobs = 5; J.priorityCallback = _; const z = new q(); z.maxJobs = 25; z.priorityCallback = (t, e) => { const n = t.parent, s = e.parent; return n === s ? 0 : n ? s ? _(n, s) : -1 : 1; }; class we { /** * Root tile of the loaded root tileset, or null if not yet loaded. * @type {Tile|null} * @readonly */ get root() { const e = this.rootTileset; return e ? e.root : null; } /** * Fraction of tiles loaded since the last idle state, from 0 (nothing loaded) to 1 (all loaded). * @type {number} * @readonly */ get loadProgress() { const { stats: e, isLoading: n } = this, s = e.queued + e.downloading + e.parsing, a = e.inCacheSinceLoad + (n ? 1 : 0); return a === 0 ? 1 : 1 - s / a; } /** * @param {string} [url] - URL of the root tileset JSON to load. */ constructor(e = null) { this.rootLoadingState = C, this.rootTileset = null, this.rootURL = e, this.fetchOptions = {}, this.plugins = [], this.queuedTiles = [], this.cachedSinceLoadComplete = /* @__PURE__ */ new Set(), this.isLoading = !1, this.processedTiles = /* @__PURE__ */ new WeakSet(), this.visibleTiles = /* @__PURE__ */ new Set(), this.activeTiles = /* @__PURE__ */ new Set(), this.usedSet = /* @__PURE__ */ new Set(), this.loadingTiles = /* @__PURE__ */ new Set(), this.lruCache = ae, this.downloadQueue = Q, this.parseQueue = J, this.processNodeQueue = z, this.stats = { inCacheSinceLoad: 0, inCache: 0, queued: 0, downloading: 0, parsing: 0, loaded: 0, failed: 0, inFrustum: 0, used: 0, active: 0, visible: 0, tilesProcessed: 0 }, this.frameCount = 0, this._dispatchNeedsUpdateEvent = ye(() => { this.dispatchEvent({ type: "needs-update" }); }), this.errorTarget = 16, this.displayActiveTiles = !1, this.maxDepth = 1 / 0, this.loadSiblings = !0, this.loadAncestors = !0, this.maxTilesProcessed = 250; } // Plugins /** * Registers a plugin with this renderer. Plugins are inserted in priority order and * receive lifecycle callbacks throughout the tile loading and rendering process. * A plugin instance may only be registered to one renderer at a time. * @param {Object} plugin */ registerPlugin(e) { if (e[X] === !0) throw new Error("TilesRendererBase: A plugin can only be registered to a single tileset"); const n = this.plugins, s = e.priority || 0; let a = n.length; for (let r = 0; r < n.length; r++) if ((n[r].priority || 0) > s) { a = r; break; } n.splice(a, 0, e), e[X] = !0, e.init && e.init(this); } /** * Removes a registered plugin. Calls `plugin.dispose()` if defined. * Accepts either the plugin instance or its string name. * Returns true if the plugin was found and removed. * @param {Object|string} plugin * @returns {boolean} */ unregisterPlugin(e) { const n = this.plugins; if (typeof e == "string" && (e = this.getPluginByName(e)), n.includes(e)) { const s = n.indexOf(e); return n.splice(s, 1), e.dispose && e.dispose(), !0; } return !1; } /** * Returns the first registered plugin whose `name` property matches, or null. * @param {string} name * @returns {Object|null} */ getPluginByName(e) { return this.plugins.find((n) => n.name === e) || null; } invokeOnePlugin(e) { const n = [...this.plugins, this]; for (let s = 0; s < n.length; s++) { const a = e(n[s]); if (a) return a; } return null; } invokeAllPlugins(e) { const n = [...this.plugins, this], s = []; for (let a = 0; a < n.length; a++) { const r = e(n[a]); r && s.push(r); } return s.length === 0 ? null : Promise.all(s); } // Public API /** * Iterates over all tiles in the loaded hierarchy. `beforecb` is called before * descending into a tile's children; returning true from it skips the subtree. * `aftercb` is called after all children have been visited. * @param {TileBeforeCallback|null} [beforecb] * @param {TileAfterCallback|null} [aftercb] */ traverse(e, n, s = !0) { this.root && fe(this.root, (a, ...r) => (s && this.ensureChildrenArePreprocessed(a, !0), e ? e(a, ...r) : !1), n); } /** * Collects attribution data from all registered plugins into `target` and returns it. * @param {Array<{type: string, value: any}>} [target] * @returns {Array<{type: string, value: any}>} */ getAttributions(e = []) { return this.invokeAllPlugins((n) => n !== this && n.getAttributions && n.getAttributions(e)), e; } /** * Runs the tile traversal and update loop. Should be called once per frame after * camera matrices have been updated. Triggers tile loading, visibility updates, * and LRU cache eviction. */ update() { const { lruCache: e, usedSet: n, stats: s, root: a, downloadQueue: r, parseQueue: i, processNodeQueue: l } = this; if (this.rootLoadingState === C && (this.rootLoadingState = x, this.invokeOnePlugin((c) => c.loadRootTileset && c.loadRootTileset()).then((c) => { let h = this.rootURL; h !== null && this.invokeAllPlugins((k) => h = k.preprocessURL ? k.preprocessURL(h, null) : h), this.rootLoadingState = S, this.rootTileset = c, this.dispatchEvent({ type: "needs-update" }), this.dispatchEvent({ type: "load-tileset", tileset: c, url: h }), this.dispatchEvent({ type: "load-root-tileset", tileset: c, url: h }); }).catch((c) => { this.rootLoadingState = R, console.error(c), this.rootTileset = null, this.dispatchEvent({ type: "load-error", tile: null, error: c, url: this.rootURL }); })), !a) return; let o = null; if (this.invokeAllPlugins((c) => { if (c.doTilesNeedUpdate) { const h = c.doTilesNeedUpdate(); o === null ? o = h : o = !!(o || h); } }), o === !1) { this.dispatchEvent({ type: "update-before" }), this.dispatchEvent({ type: "update-after" }); return; } this.dispatchEvent({ type: "update-before" }), s.inFrustum = 0, s.used = 0, s.active = 0, s.visible = 0, s.tilesProcessed = 0, this.frameCount++, n.forEach((c) => e.markUnused(c)), n.clear(), this.prepareForTraversal(), ge(a, this), this.removeUnusedPendingTiles(); const u = this.queuedTiles; u.sort(e.unloadPriorityCallback); for (let c = 0, h = u.length; c < h && !e.isFull(); c++) this.requestTileContents(u[c]); u.length = 0, e.scheduleUnload(), (r.running || i.running || l.running) === !1 && this.isLoading === !0 && (this.cachedSinceLoadComplete.clear(), s.inCacheSinceLoad = 0, this.dispatchEvent({ type: "tiles-load-end" }), this.isLoading = !1), this.dispatchEvent({ type: "update-after" }); } /** * Resets any tiles that previously failed to load so they will be retried on the next `update`. */ resetFailedTiles() { this.rootLoadingState === R && (this.rootLoadingState = C); const e = this.stats; e.failed !== 0 && (this.traverse((n) => { n.internal.loadingState === R && (n.internal.loadingState = C); }, null, !1), e.failed = 0); } calculateTileViewErrorWithPlugin(e, n) { this.calculateTileViewError(e, n); let s = null, a = 0, r = 1 / 0; this.invokeAllPlugins((i) => { i !== this && i.calculateTileViewError && (b.inView = !0, b.error = 0, b.distance = 1 / 0, i.calculateTileViewError(e, b) && (s === null && (s = !0), s = s && b.inView, b.inView && (r = Math.min(r, b.distance), a = Math.max(a, b.error)))); }), n.inView && s !== !1 ? (n.error = Math.max(n.error, a), n.distanceFromCamera = Math.min(n.distanceFromCamera, r)) : s ? (n.inView = !0, n.error = a, n.distanceFromCamera = r) : n.inView = !1; } /** * Disposes all loaded tiles and unregisters all plugins. The renderer should not * be used after calling this. */ dispose() { [...this.plugins].forEach((a) => { this.unregisterPlugin(a); }); const n = this.lruCache, s = []; this.traverse((a) => (s.push(a), !1), null, !1); for (let a = 0, r = s.length; a < r; a++) n.remove(s[a]); this.stats = { queued: 0, parsing: 0, downloading: 0, failed: 0, inFrustum: 0, traversed: 0, used: 0, active: 0, visible: 0 }, this.frameCount = 0, this.loadingTiles.clear(); } // Overrideable calculateBytesUsed(e, n) { return 0; } /** * Dispatches an event to all registered listeners for the given event type. * @param {{ type: string }} e */ dispatchEvent(e) { } /** * Registers a listener for the given event type. * @param {string} name * @param {EventCallback} callback */ addEventListener(e, n) { } /** * Removes a previously registered event listener. * @param {string} name * @param {EventCallback} callback */ removeEventListener(e, n) { } parseTile(e, n, s) { return null; } prepareForTraversal() { } disposeTile(e) { e.traversal.visible && (e.internal.hasRenderableContent ? this.invokeOnePlugin((s) => s.setTileVisible && s.setTileVisible(e, !1)) : this.invokeOnePlugin((s) => s.setEmptyTileVisible && s.setEmptyTileVisible(e, !1)), e.traversal.visible = !1), e.traversal.active && e.internal.hasRenderableContent && this.invokeOnePlugin((s) => s.setTileActive && s.setTileActive(e, !1)), e.traversal.active = !1; const { scene: n } = e.engineData; n && this.dispatchEvent({ type: "dispose-model", scene: n, tile: e }); } preprocessNode(e, n, s = null) { var a; if (this.processedTiles.add(e), this.stats.tilesProcessed++, e.content && (!("uri" in e.content) && "url" in e.content && (e.content.uri = e.content.url, delete e.content.url), e.content.boundingVolume && !("box" in e.content.boundingVolume || "sphere" in e.content.boundingVolume || "region" in e.content.boundingVolume) && delete e.content.boundingVolume), e.parent = s, e.children = e.children || [], e.internal = { hasContent: !1, hasRenderableContent: !1, hasUnrenderableContent: !1, loadingState: C, basePath: n, depth: -1, depthFromRenderedParent: -1, isVirtual: !1, virtualChildCount: 0, renderer: this, // preserve any pre-seeded fields ...e.internal }, (a = e.content) != null && a.uri) { const r = W(e.content.uri), i = !!(r && /json$/.test(r)); e.internal.hasContent = !0, e.internal.hasUnrenderableContent = i, e.internal.hasRenderableContent = !i; } else e.internal.hasContent = !1, e.internal.hasUnrenderableContent = !1, e.internal.hasRenderableContent = !1; s ? (e.internal.depth = s.internal.depth + 1, e.internal.depthFromRenderedParent = s.internal.depthFromRenderedParent + (e.internal.hasRenderableContent ? 1 : 0)) : (e.internal.depth = 0, e.internal.depthFromRenderedParent = e.internal.hasRenderableContent ? 1 : 0), e.traversal = { distanceFromCamera: 1 / 0, error: 1 / 0, inFrustum: !1, wasInFrustum: !1, isLeaf: !1, used: !1, usedLastFrame: !1, visible: !1, wasSetVisible: !1, active: !1, wasSetActive: !1, allChildrenReady: !1, allChildrenLoaded: !1, kicked: !1, allUsedChildrenProcessed: !1, lastFrameVisited: -1 }, s === null ? e.refine = e.refine || "REPLACE" : e.refine = e.refine || s.refine, e.engineData = { scene: null, metadata: null, boundingVolume: null }, Object.defineProperty(e, "cached", { get() { return console.warn('TilesRenderer: "tile.cached" field has been renamed to "tile.engineData".'), this.engineData; }, enumerable: !1, configurable: !0 }), this.invokeAllPlugins((r) => { r !== this && r.preprocessNode && r.preprocessNode(e, n, s); }); } setTileActive(e, n) { n ? this.activeTiles.add(e) : this.activeTiles.delete(e); } setTileVisible(e, n) { n ? this.visibleTiles.add(e) : this.visibleTiles.delete(e), this.dispatchEvent({ type: "tile-visibility-change", scene: e.engineData.scene, tile: e, visible: n }); } calculateTileViewError(e, n) { } removeUnusedPendingTiles() { const { lruCache: e, loadingTiles: n } = this, s = []; for (const a of n) !e.isUsed(a) && a.internal.loadingState === A && s.push(a); for (let a = 0; a < s.length; a++) e.remove(s[a]); } // Private Functions queueTileForDownload(e) { e.internal.loadingState !== C || this.lruCache.isFull() || this.queuedTiles.push(e); } markTileUsed(e) { this.usedSet.add(e), this.lruCache.markUsed(e); } fetchData(e, n) { return fetch(e, n); } ensureChildrenArePreprocessed(e, n = this.stats.tilesProcessed < this.maxTilesProcessed) { const s = e.children; if (s.length === 0 || s[s.length - 1].traversal) return; const a = (r) => { for (let i = 0, l = r.length; i < l; i++) { const o = r[i]; o && !o.traversal && this.preprocessNode(o, e.internal.basePath, e); } }; n ? (this.processNodeQueue.remove(e), a(s)) : this.processNodeQueue.has(e) || this.processNodeQueue.add(e, (r) => { a(r.children), this._dispatchNeedsUpdateEvent(); }); } // returns the total bytes used for by the given tile as reported by all plugins getBytesUsed(e) { let n = 0; return this.invokeAllPlugins((s) => { s.calculateBytesUsed && (n += s.calculateBytesUsed(e, e.engineData.scene) || 0); }), n; } // force a recalculation of the tile or all tiles if no tile is provided recalculateBytesUsed(e = null) { const { lruCache: n, processedTiles: s } = this; e === null ? n.itemSet.forEach((a) => { s.has(a) && n.setMemoryUsage(a, this.getBytesUsed(a)); }) : n.setMemoryUsage(e, this.getBytesUsed(e)); } preprocessTileset(e, n, s = null) { const a = e.asset.version, [r, i] = a.split(".").map((o) => parseInt(o)); console.assert( r <= 1, "TilesRenderer: asset.version is expected to be a 1.x or a compatible version." ), r === 1 && i > 0 && console.warn("TilesRenderer: tiles versions at 1.1 or higher have limited support. Some new extensions and features may not be supported."); let l = n.replace(/\/[^/]*$/, ""); l = new URL(l, window.location.href).toString(), this.preprocessNode(e.root, l, s); } loadRootTileset() { let e = this.rootURL; return this.invokeAllPlugins((s) => e = s.preprocessURL ? s.preprocessURL(e, null) : e), this.invokeOnePlugin((s) => s.fetchData && s.fetchData(e, this.fetchOptions)).then((s) => { if (s instanceof Response) { if (s.ok) return s.json(); throw new Error(`TilesRenderer: Failed to load tileset "${e}" with status ${s.status} : ${s.statusText}`); } else return s; }).then((s) => (this.preprocessTileset(s, e), s)); } requestTileContents(e) { if (e.internal.loadingState !== C) return; let n = !1, s = null, a = new URL(e.content.uri, e.internal.basePath + "/").toString(); this.invokeAllPlugins((d) => a = d.preprocessURL ? d.preprocessURL(a, e) : a); const r = this.stats, i = this.lruCache, l = this.downloadQueue, o = this.parseQueue, u = this.loadingTiles, v = W(a), c = new AbortController(), h = c.signal; if (i.add(e, (d) => { c.abort(), n ? d.children.length = 0 : this.invokeAllPlugins((p) => { p.disposeTile && p.disposeTile(d); }), r.inCache--, this.cachedSinceLoadComplete.has(e) && (this.cachedSinceLoadComplete.delete(e), r.inCacheSinceLoad--), d.internal.loadingState === A ? r.queued-- : d.internal.loadingState === x ? r.downloading-- : d.internal.loadingState === O ? r.parsing-- : d.internal.loadingState === S && r.loaded--, d.internal.loadingState = C, o.remove(d), l.remove(d), u.delete(d); })) return this.isLoading || (this.isLoading = !0, this.dispatchEvent({ type: "tiles-load-start" })), i.setMemoryUsage(e, this.getBytesUsed(e)), this.cachedSinceLoadComplete.add(e), r.inCacheSinceLoad++, r.inCache++, r.queued++, e.internal.loadingState = A, u.add(e), l.add(e, (d) => { if (h.aborted) return Promise.resolve(); e.internal.loadingState = x, r.downloading++, r.queued--; const p = this.invokeOnePlugin((P) => P.fetchData && P.fetchData(a, { ...this.fetchOptions, signal: h })); return this.dispatchEvent({ type: "tile-download-start", tile: e, url: a, get uri() { return console.warn('tile-download-start event: "uri" has been renamed to "url".'), this.url; } }), p; }).then((d) => { if (!h.aborted) if (d instanceof Response) { if (d.ok) return v === "json" ? d.json() : d.arrayBuffer(); throw new Error(`Failed to load model with error code ${d.status}`); } else return d; }).then((d) => { if (!h.aborted) return r.downloading--, r.parsing++, e.internal.loadingState = O, o.add(e, (p) => h.aborted ? Promise.resolve() : v === "json" && d.root ? (this.preprocessTileset(d, a, e), e.children.push(d.root), s = d, n = !0, Promise.resolve()) : this.invokeOnePlugin((P) => P.parseTile && P.parseTile(d, p, v, a, h))); }).then(() => { if (h.aborted) return; r.parsing--, r.loaded++, e.internal.loadingState = S, u.delete(e), i.setLoaded(e, !0); const d = this.getBytesUsed(e); if (i.getMemoryUsage(e) === 0 && d > 0 && i.isFull()) { i.remove(e); return; } i.setMemoryUsage(e, d), this.dispatchEvent({ type: "needs-update" }), n && this.dispatchEvent({ type: "load-tileset", tileset: s, url: a }), e.engineData.scene && this.dispatchEvent({ type: "load-model", scene: e.engineData.scene, tile: e, url: a }); }).catch((d) => { h.aborted || (d.name !== "AbortError" ? (o.remove(e), l.remove(e), e.internal.loadingState === A ? r.queued-- : e.internal.loadingState === x ? r.downloading-- : e.internal.loadingState === O ? r.parsing-- : e.internal.loadingState === S && r.loaded--, r.failed++, console.error(`TilesRenderer : Failed to load tile at url "${e.content.uri}".`), console.error(d), e.internal.loadingState = R, u.delete(e), i.setLoaded(e, !0), this.dispatchEvent({ type: "load-error", tile: e, error: d, url: a })) : i.remove(e)); }); } } export { Q as D, R as F, S as L, O as P, A as Q, F as S, we as T, C as U, Fe as W, x as a, ve as b, q as c, Re as d, ke as e, _ as u }; //# sourceMappingURL=TilesRendererBase-BGxy2Uih.js.map