UNPKG

3d-tiles-renderer

Version:

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

563 lines (562 loc) 21.2 kB
import { C as I, b as N, G as U, a as S, Q as R } from "./QuantizedMeshLoaderBase-DIIZywLI.js"; import { L as d, r as T, b as v } from "./LoaderBase-CU5shB7w.js"; function p(u) { return u.implicitTilingData.root.implicitTiling.subdivisionScheme === "OCTREE"; } function m(u) { return p(u) ? 8 : 4; } function y(u, i) { if (!u) return [0, 0, 0]; const e = u.implicitTilingData.x, t = u.implicitTilingData.y, r = u.implicitTilingData.z, n = 2 * e + i % 2, s = 2 * t + Math.floor(i / 2) % 2, l = p(u) ? 2 * r + Math.floor(i / 4) % 2 : 0; return [n, s, l]; } class b { constructor(i, e) { this.parent = i, this.children = [], this.geometricError = 0, this.boundingVolume = null; const [t, r, n] = y(i, e); this.implicitTilingData = { level: i.implicitTilingData.level + 1, root: i.implicitTilingData.root, subtreeIdx: e, x: t, y: r, z: n }; } static clone(i) { return { parent: i.parent, children: [], geometricError: i.geometricError, boundingVolume: i.boundingVolume, implicitTilingData: { ...i.implicitTilingData } }; } } class A extends d { constructor(i) { super(), this.tile = i, this.rootTile = i.implicitTilingData.root, this.workingPath = null; } /** * A helper object for storing the two parts of the subtree binary * * @typedef {Object} Subtree * @property {number} version * @property {JSON} subtreeJson * @property {ArrayBuffer} subtreeByte * @private */ /** * * @param {ArrayBuffer} buffer * @returns {Subtree} */ parseBuffer(i) { const e = new DataView(i); let t = 0; const r = T(e); console.assert(r === "subt", 'SUBTREELoader: The magic bytes equal "subt".'), t += 4; const n = e.getUint32(t, !0); console.assert(n === 1, 'SUBTREELoader: The version listed in the header is "1".'), t += 4; const s = e.getUint32(t, !0); t += 8; const l = e.getUint32(t, !0); t += 8; const o = JSON.parse(v(new Uint8Array(i, t, s))); t += s; const a = i.slice(t, t + l); return { version: n, subtreeJson: o, subtreeByte: a }; } async parse(i) { const e = this.parseBuffer(i), t = e.subtreeJson; t.contentAvailabilityHeaders = [].concat(t.contentAvailability); const r = this.preprocessBuffers(t.buffers), n = this.preprocessBufferViews( t.bufferViews, r ); this.markActiveBufferViews(t, n); const s = await this.requestActiveBuffers( r, e.subtreeByte ), l = this.parseActiveBufferViews(n, s); this.parseAvailability(e, t, l), this.expandSubtree(this.tile, e); } /** * Determine which buffer views need to be loaded into memory. This includes: * * <ul> * <li>The tile availability bitstream (if a bitstream is defined)</li> * <li>The content availability bitstream(s) (if a bitstream is defined)</li> * <li>The child subtree availability bitstream (if a bitstream is defined)</li> * </ul> * * <p> * This function modifies the buffer view headers' isActive flags in place. * </p> * * @param {JSON} subtreeJson The JSON chunk from the subtree * @param {BufferViewHeader[]} bufferViewHeaders The preprocessed buffer view headers * @private */ markActiveBufferViews(i, e) { let t; const r = i.tileAvailability; isNaN(r.bitstream) ? isNaN(r.bufferView) || (t = e[r.bufferView]) : t = e[r.bitstream], t && (t.isActive = !0, t.bufferHeader.isActive = !0); const n = i.contentAvailabilityHeaders; for (let l = 0; l < n.length; l++) t = void 0, isNaN(n[l].bitstream) ? isNaN(n[l].bufferView) || (t = e[n[l].bufferView]) : t = e[n[l].bitstream], t && (t.isActive = !0, t.bufferHeader.isActive = !0); t = void 0; const s = i.childSubtreeAvailability; isNaN(s.bitstream) ? isNaN(s.bufferView) || (t = e[s.bufferView]) : t = e[s.bitstream], t && (t.isActive = !0, t.bufferHeader.isActive = !0); } /** * Go through the list of buffers and gather all the active ones into * a dictionary. * <p> * The results are put into a dictionary object. The keys are indices of * buffers, and the values are Uint8Arrays of the contents. Only buffers * marked with the isActive flag are fetched. * </p> * <p> * The internal buffer (the subtree's binary chunk) is also stored in this * dictionary if it is marked active. * </p> * @param {BufferHeader[]} bufferHeaders The preprocessed buffer headers * @param {ArrayBuffer} internalBuffer The binary chunk of the subtree file * @returns {Object} buffersU8 A dictionary of buffer index to a Uint8Array of its contents. * @private */ async requestActiveBuffers(i, e) { const t = []; for (let s = 0; s < i.length; s++) { const l = i[s]; if (!l.isActive) t.push(Promise.resolve()); else if (l.isExternal) { const o = this.parseImplicitURIBuffer( this.tile, this.rootTile.implicitTiling.subtrees.uri, l.uri ), a = fetch(o, this.fetchOptions).then((c) => { if (!c.ok) throw new Error(`SUBTREELoader: Failed to load external buffer from ${l.uri} with error code ${c.status}.`); return c.arrayBuffer(); }).then((c) => new Uint8Array(c)); t.push(a); } else t.push(Promise.resolve(new Uint8Array(e))); } const r = await Promise.all(t), n = {}; for (let s = 0; s < r.length; s++) { const l = r[s]; l && (n[s] = l); } return n; } /** * Go through the list of buffer views, and if they are marked as active, * extract a subarray from one of the active buffers. * * @param {BufferViewHeader[]} bufferViewHeaders * @param {Object} buffersU8 A dictionary of buffer index to a Uint8Array of its contents. * @returns {Object} A dictionary of buffer view index to a Uint8Array of its contents. * @private */ parseActiveBufferViews(i, e) { const t = {}; for (let r = 0; r < i.length; r++) { const n = i[r]; if (!n.isActive) continue; const s = n.byteOffset, l = s + n.byteLength, o = e[n.buffer]; t[r] = o.slice(s, l); } return t; } /** * A buffer header is the JSON header from the subtree JSON chunk plus * a couple extra boolean flags for easy reference. * * Buffers are assumed inactive until explicitly marked active. This is used * to avoid fetching unneeded buffers. * * @typedef {Object} BufferHeader * @property {boolean} isActive Whether this buffer is currently used. * @property {string} [uri] The URI of the buffer (external buffers only) * @property {number} byteLength The byte length of the buffer, including any padding contained within. * @private */ /** * Iterate over the list of buffers from the subtree JSON and add the isActive field for easier parsing later. * This modifies the objects in place. * @param {Object[]} [bufferHeaders=[]] The JSON from subtreeJson.buffers. * @returns {BufferHeader[]} The same array of headers with additional fields. * @private */ preprocessBuffers(i = []) { for (let e = 0; e < i.length; e++) { const t = i[e]; t.isActive = !1, t.isExternal = !!t.uri; } return i; } /** * A buffer view header is the JSON header from the subtree JSON chunk plus * the isActive flag and a reference to the header for the underlying buffer. * * @typedef {Object} BufferViewHeader * @property {BufferHeader} bufferHeader A reference to the header for the underlying buffer * @property {boolean} isActive Whether this bufferView is currently used. * @property {number} buffer The index of the underlying buffer. * @property {number} byteOffset The start byte of the bufferView within the buffer. * @property {number} byteLength The length of the bufferView. No padding is included in this length. * @private */ /** * Iterate the list of buffer views from the subtree JSON and add the * isActive flag. Also save a reference to the bufferHeader. * * @param {Object[]} [bufferViewHeaders=[]] The JSON from subtree.bufferViews. * @param {BufferHeader[]} bufferHeaders The preprocessed buffer headers. * @returns {BufferViewHeader[]} The same array of bufferView headers with additional fields. * @private */ preprocessBufferViews(i = [], e) { for (let t = 0; t < i.length; t++) { const r = i[t]; r.bufferHeader = e[r.buffer], r.isActive = !1, r.isExternal = r.bufferHeader.isExternal; } return i; } /** * Parse the three availability bitstreams and store them in the subtree. * * @param {Subtree} subtree The subtree to modify. * @param {Object} subtreeJson The subtree JSON. * @param {Object} bufferViewsU8 A dictionary of buffer view index to a Uint8Array of its contents. * @private */ parseAvailability(i, e, t) { const r = m(this.rootTile), n = this.rootTile.implicitTiling.subtreeLevels, s = (Math.pow(r, n) - 1) / (r - 1), l = Math.pow(r, n); i._tileAvailability = this.parseAvailabilityBitstream( e.tileAvailability, t, s ), i._contentAvailabilityBitstreams = []; for (let o = 0; o < e.contentAvailabilityHeaders.length; o++) { const a = this.parseAvailabilityBitstream( e.contentAvailabilityHeaders[o], t, // content availability has the same length as tile availability. s ); i._contentAvailabilityBitstreams.push(a); } i._childSubtreeAvailability = this.parseAvailabilityBitstream( e.childSubtreeAvailability, t, l ); } /** * Given the JSON describing an availability bitstream, turn it into an * in-memory representation using an object. This handles bitstreams from a bufferView. * * @param {Object} availabilityJson A JSON object representing the availability. * @param {Object} bufferViewsU8 A dictionary of buffer view index to its Uint8Array contents. * @param {number} lengthBits The length of the availability bitstream in bits. * @returns {Object} * @private */ parseAvailabilityBitstream(i, e, t) { if (!isNaN(i.constant)) return { constant: !!i.constant, lengthBits: t }; let r; return isNaN(i.bitstream) ? isNaN(i.bufferView) || (r = e[i.bufferView]) : r = e[i.bitstream], { bitstream: r, lengthBits: t }; } /** * Expand a single subtree tile. This transcodes the subtree into * a tree of {@link SubtreeTile}. The root of this tree is stored in * the placeholder tile's children array. This method also creates * tiles for the child subtrees to be lazily expanded as needed. * * @param {Object | SubtreeTile} subtreeRoot The first node of the subtree. * @param {Subtree} subtree The parsed subtree. * @private */ expandSubtree(i, e) { const t = b.clone(i); for (let s = 0; e && s < e._contentAvailabilityBitstreams.length; s++) if (e && this.getBit(e._contentAvailabilityBitstreams[s], 0)) { t.content = { uri: this.parseImplicitURI(i, this.rootTile.content.uri) }; break; } i.children.push(t); const r = this.transcodeSubtreeTiles( t, e ), n = this.listChildSubtrees(e, r); for (let s = 0; s < n.length; s++) { const l = n[s], o = l.tile, a = this.deriveChildTile( null, o, null, l.childMortonIndex ); a.content = { uri: this.parseImplicitURI(a, this.rootTile.implicitTiling.subtrees.uri) }, o.children.push(a); } } /** * Transcode the implicitly defined tiles within this subtree and generate * explicit {@link SubtreeTile} objects. This function only transcodes tiles, * child subtrees are handled separately. * * @param {Object | SubtreeTile} subtreeRoot The root of the current subtree. * @param {Subtree} subtree The subtree to get availability information. * @returns {Array} The bottom row of transcoded tiles. This is helpful for processing child subtrees. * @private */ transcodeSubtreeTiles(i, e) { let t = [i], r = []; for (let n = 1; n < this.rootTile.implicitTiling.subtreeLevels; n++) { const s = m(this.rootTile), l = (Math.pow(s, n) - 1) / (s - 1), o = s * t.length; for (let a = 0; a < o; a++) { const c = l + a, h = a >> Math.log2(s), f = t[h]; if (!this.getBit(e._tileAvailability, c)) { r.push(void 0); continue; } const g = this.deriveChildTile( e, f, c, a ); f.children.push(g), r.push(g); } t = r, r = []; } return t; } /** * Given a parent tile and information about which child to create, derive * the properties of the child tile implicitly. * <p> * This creates a real tile for rendering. * </p> * * @param {Subtree} subtree The subtree the child tile belongs to. * @param {Object | SubtreeTile} parentTile The parent of the new child tile. * @param {number} childBitIndex The index of the child tile within the tile's availability information. * @param {number} childMortonIndex The morton index of the child tile relative to its parent. * @returns {SubtreeTile} The new child tile. * @private */ deriveChildTile(i, e, t, r) { const n = new b(e, r); n.boundingVolume = this.getTileBoundingVolume(n), n.geometricError = this.getGeometricError(n); for (let s = 0; i && s < i._contentAvailabilityBitstreams.length; s++) if (i && this.getBit(i._contentAvailabilityBitstreams[s], t)) { n.content = { uri: this.parseImplicitURI(n, this.rootTile.content.uri) }; break; } return n; } /** * Get a bit from the bitstream as a Boolean. If the bitstream * is a constant, the constant value is returned instead. * * @param {ParsedBitstream} object * @param {number} index The integer index of the bit. * @returns {boolean} The value of the bit. * @private */ getBit(i, e) { if (e < 0 || e >= i.lengthBits) throw new Error("Bit index out of bounds."); if (i.constant !== void 0) return i.constant; const t = e >> 3, r = e % 8; return (new Uint8Array(i.bitstream)[t] >> r & 1) === 1; } /** * //TODO Adapt for Sphere * To maintain numerical stability during this subdivision process, * the actual bounding volumes should not be computed progressively by subdividing a non-root tile volume. * Instead, the exact bounding volumes are computed directly for a given level. * @param {Object | SubtreeTile} tile * @returns {Object} object containing the bounding volume. */ getTileBoundingVolume(i) { const e = {}; if (this.rootTile.boundingVolume.region) { const t = [...this.rootTile.boundingVolume.region], r = t[0], n = t[2], s = t[1], l = t[3], o = (n - r) / Math.pow(2, i.implicitTilingData.level), a = (l - s) / Math.pow(2, i.implicitTilingData.level); t[0] = r + o * i.implicitTilingData.x, t[2] = r + o * (i.implicitTilingData.x + 1), t[1] = s + a * i.implicitTilingData.y, t[3] = s + a * (i.implicitTilingData.y + 1); for (let c = 0; c < 4; c++) { const h = t[c]; h < -Math.PI ? t[c] += 2 * Math.PI : h > Math.PI && (t[c] -= 2 * Math.PI); } if (p(i)) { const c = t[4], f = (t[5] - c) / Math.pow(2, i.implicitTilingData.level); t[4] = c + f * i.implicitTilingData.z, t[5] = c + f * (i.implicitTilingData.z + 1); } e.region = t; } if (this.rootTile.boundingVolume.box) { const t = [...this.rootTile.boundingVolume.box], r = 2 ** i.implicitTilingData.level - 1, n = Math.pow(2, -i.implicitTilingData.level), s = p(i) ? 3 : 2; for (let l = 0; l < s; l++) { t[3 + l * 3 + 0] *= n, t[3 + l * 3 + 1] *= n, t[3 + l * 3 + 2] *= n; const o = t[3 + l * 3 + 0], a = t[3 + l * 3 + 1], c = t[3 + l * 3 + 2], h = l === 0 ? i.implicitTilingData.x : l === 1 ? i.implicitTilingData.y : i.implicitTilingData.z; t[0] += 2 * o * (-0.5 * r + h), t[1] += 2 * a * (-0.5 * r + h), t[2] += 2 * c * (-0.5 * r + h); } e.box = t; } return e; } /** * Each child’s geometricError is half of its parent’s geometricError. * @param {Object | SubtreeTile} tile * @returns {number} */ getGeometricError(i) { return this.rootTile.geometricError / Math.pow(2, i.implicitTilingData.level); } /** * Determine what child subtrees exist and return a list of information. * * @param {Object} subtree The subtree for looking up availability. * @param {Array} bottomRow The bottom row of tiles in a transcoded subtree. * @returns {Array} A list of identifiers for the child subtrees. * @private */ listChildSubtrees(i, e) { const t = [], r = m(this.rootTile); for (let n = 0; n < e.length; n++) { const s = e[n]; if (s !== void 0) for (let l = 0; l < r; l++) { const o = n * r + l; this.getBit(i._childSubtreeAvailability, o) && t.push({ tile: s, childMortonIndex: o }); } } return t; } /** * Replaces placeholder tokens in a URI template with the corresponding tile properties. * * The URI template should contain the tokens: * - `{level}` for the tile's subdivision level. * - `{x}` for the tile's x-coordinate. * - `{y}` for the tile's y-coordinate. * - `{z}` for the tile's z-coordinate. * * @param {Object} tile - The tile object containing properties __level, __x, __y, and __z. * @param {string} uri - The URI template string with placeholders. * @returns {string} The URI with placeholders replaced by the tile's properties. */ parseImplicitURI(i, e) { return e = e.replace("{level}", i.implicitTilingData.level), e = e.replace("{x}", i.implicitTilingData.x), e = e.replace("{y}", i.implicitTilingData.y), e = e.replace("{z}", i.implicitTilingData.z), e; } /** * Generates the full external buffer URI for a tile by combining an implicit URI with a buffer URI. * * First, it parses the implicit URI using the tile properties and the provided template. Then, it creates a new URL * relative to the tile's base path, removes the last path segment, and appends the buffer URI. * * @param {Object} tile - The tile object that contains properties: * - __level: the subdivision level, * - __x, __y, __z: the tile coordinates, * @param {string} uri - The URI template string with placeholders for the tile (e.g., `{level}`, `{x}`, `{y}`, `{z}`). * @param {string} bufUri - The buffer file name to append (e.g., "0_1.bin"). * @returns {string} The full external buffer URI. */ parseImplicitURIBuffer(i, e, t) { const r = this.parseImplicitURI(i, e), n = new URL(r, this.workingPath + "/"); return n.pathname = n.pathname.substring(0, n.pathname.lastIndexOf("/")), new URL(n.pathname + "/" + t, this.workingPath + "/").toString(); } } class w { constructor() { this.name = "IMPLICIT_TILING_PLUGIN"; } init(i) { this.tiles = i; } preprocessNode(i, e, t) { var r; i.implicitTiling ? (i.internal.hasUnrenderableContent = !0, i.internal.hasRenderableContent = !1, i.implicitTilingData = { // Keep this tile as an Implicit Root Tile root: i, // Idx of the tile in its subtree subtreeIdx: 0, // Coords of the tile x: 0, y: 0, z: 0, level: 0 }) : /.subtree$/i.test((r = i.content) == null ? void 0 : r.uri) && (i.internal.hasUnrenderableContent = !0, i.internal.hasRenderableContent = !1); } parseTile(i, e, t) { if (/^subtree$/i.test(t)) { const r = new A(e); return r.workingPath = e.internal.basePath, r.fetchOptions = this.tiles.fetchOptions, r.parse(i); } } preprocessURL(i, e) { if (e && e.implicitTiling) { const t = e.implicitTiling.subtrees.uri.replace("{level}", e.implicitTilingData.level).replace("{x}", e.implicitTilingData.x).replace("{y}", e.implicitTilingData.y).replace("{z}", e.implicitTilingData.z); return new URL(t, e.internal.basePath + "/").toString(); } return i; } disposeTile(i) { var e; /.subtree$/i.test((e = i.content) == null ? void 0 : e.uri) && (i.children.forEach((t) => { this.tiles.processNodeQueue.remove(t); }), i.children.length = 0); } } class x { constructor() { this.name = "ENFORCE_NONZERO_ERROR", this.priority = -1 / 0, this.originalError = /* @__PURE__ */ new Map(); } preprocessNode(i) { if (i.geometricError === 0) { let e = i.parent, t = 1; for (; e !== null; ) { if (e.geometricError !== 0) { i.geometricError = e.geometricError * 2 ** -t; break; } e = e.parent, t++; } } } } export { I as CesiumIonAuth, N as CesiumIonAuthPlugin, x as EnforceNonZeroErrorPlugin, U as GoogleCloudAuth, S as GoogleCloudAuthPlugin, w as ImplicitTilingPlugin, R as QuantizedMeshLoaderBase }; //# sourceMappingURL=index.core-plugins.js.map