ImplicitSubtree.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167
  1. import Check from "../Core/Check.js";
  2. import defaultValue from "../Core/defaultValue.js";
  3. import DeveloperError from "../Core/DeveloperError.js";
  4. import defined from "../Core/defined.js";
  5. import destroyObject from "../Core/destroyObject.js";
  6. import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
  7. import RuntimeError from "../Core/RuntimeError.js";
  8. import hasExtension from "./hasExtension.js";
  9. import ImplicitAvailabilityBitstream from "./ImplicitAvailabilityBitstream.js";
  10. import ImplicitMetadataView from "./ImplicitMetadataView.js";
  11. import ImplicitSubdivisionScheme from "./ImplicitSubdivisionScheme.js";
  12. import ImplicitSubtreeMetadata from "./ImplicitSubtreeMetadata.js";
  13. import MetadataTable from "./MetadataTable.js";
  14. import ResourceCache from "./ResourceCache.js";
  15. /**
  16. * An object representing a single subtree in an implicit tileset
  17. * including availability.
  18. * <p>
  19. * Subtrees handle tile metadata, defined in the subtree JSON in either
  20. * tileMetadata (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension.
  21. * Subtrees also handle content metadata and metadata about the subtree itself.
  22. * </p>
  23. *
  24. * This object is normally not instantiated directly, use {@link ImplicitSubtree.fromSubtreeJson}.
  25. *
  26. * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_metadata#implicit-tile-properties|Implicit Tile Properties in the 3DTILES_metadata specification}
  27. * @see ImplicitSubtree.fromSubtreeJson
  28. *
  29. * @alias ImplicitSubtree
  30. * @constructor
  31. *
  32. * @param {Resource} resource The resource for this subtree. This is used for fetching external buffers as needed.
  33. * @param {ImplicitTileset} implicitTileset The implicit tileset. This includes information about the size of subtrees
  34. * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the subtree's root tile.
  35. *
  36. * @private
  37. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  38. */
  39. function ImplicitSubtree(resource, implicitTileset, implicitCoordinates) {
  40. //>>includeStart('debug', pragmas.debug);
  41. Check.typeOf.object("resource", resource);
  42. Check.typeOf.object("implicitTileset", implicitTileset);
  43. Check.typeOf.object("implicitCoordinates", implicitCoordinates);
  44. //>>includeEnd('debug');
  45. this._resource = resource;
  46. this._subtreeJson = undefined;
  47. this._bufferLoader = undefined;
  48. this._tileAvailability = undefined;
  49. this._contentAvailabilityBitstreams = [];
  50. this._childSubtreeAvailability = undefined;
  51. this._implicitCoordinates = implicitCoordinates;
  52. this._subtreeLevels = implicitTileset.subtreeLevels;
  53. this._subdivisionScheme = implicitTileset.subdivisionScheme;
  54. this._branchingFactor = implicitTileset.branchingFactor;
  55. // properties for metadata
  56. this._metadata = undefined;
  57. this._tileMetadataTable = undefined;
  58. this._tilePropertyTableJson = undefined;
  59. this._contentMetadataTables = [];
  60. this._contentPropertyTableJsons = [];
  61. // Jump buffers are maps of availability bit index to entity ID
  62. this._tileJumpBuffer = undefined;
  63. this._contentJumpBuffers = [];
  64. this._ready = false;
  65. }
  66. Object.defineProperties(ImplicitSubtree.prototype, {
  67. /**
  68. * Returns true once all necessary availability buffers
  69. * are loaded.
  70. *
  71. * @type {boolean}
  72. * @readonly
  73. * @private
  74. */
  75. ready: {
  76. get: function () {
  77. return this._ready;
  78. },
  79. },
  80. /**
  81. * When subtree metadata is present (3D Tiles 1.1), this property stores an {@link ImplicitSubtreeMetadata} instance
  82. *
  83. * @type {ImplicitSubtreeMetadata}
  84. * @readonly
  85. * @private
  86. */
  87. metadata: {
  88. get: function () {
  89. return this._metadata;
  90. },
  91. },
  92. /**
  93. * When tile metadata is present (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension is used,
  94. * this property stores a {@link MetadataTable} instance for the tiles in the subtree.
  95. *
  96. * @type {MetadataTable}
  97. * @readonly
  98. * @private
  99. */
  100. tileMetadataTable: {
  101. get: function () {
  102. return this._tileMetadataTable;
  103. },
  104. },
  105. /**
  106. * When tile metadata is present (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension is used,
  107. * this property stores the JSON from the extension. This is used by {@link TileMetadata}
  108. * to get the extras and extensions for the tiles in the subtree.
  109. *
  110. * @type {object}
  111. * @readonly
  112. * @private
  113. */
  114. tilePropertyTableJson: {
  115. get: function () {
  116. return this._tilePropertyTableJson;
  117. },
  118. },
  119. /**
  120. * When content metadata is present (3D Tiles 1.1), this property stores
  121. * an array of {@link MetadataTable} instances for the contents in the subtree.
  122. *
  123. * @type {Array}
  124. * @readonly
  125. * @private
  126. */
  127. contentMetadataTables: {
  128. get: function () {
  129. return this._contentMetadataTables;
  130. },
  131. },
  132. /**
  133. * When content metadata is present (3D Tiles 1.1), this property
  134. * an array of the JSONs from the extension. This is used to get the extras
  135. * and extensions for the contents in the subtree.
  136. *
  137. * @type {Array}
  138. * @readonly
  139. * @private
  140. */
  141. contentPropertyTableJsons: {
  142. get: function () {
  143. return this._contentPropertyTableJsons;
  144. },
  145. },
  146. /**
  147. * Gets the implicit tile coordinates for the root of the subtree.
  148. *
  149. * @type {ImplicitTileCoordinates}
  150. * @readonly
  151. * @private
  152. */
  153. implicitCoordinates: {
  154. get: function () {
  155. return this._implicitCoordinates;
  156. },
  157. },
  158. });
  159. /**
  160. * Check if a specific tile is available at an index of the tile availability bitstream
  161. *
  162. * @param {number} index The index of the desired tile
  163. * @returns {boolean} The value of the i-th bit
  164. * @private
  165. */
  166. ImplicitSubtree.prototype.tileIsAvailableAtIndex = function (index) {
  167. return this._tileAvailability.getBit(index);
  168. };
  169. /**
  170. * Check if a specific tile is available at an implicit tile coordinate
  171. *
  172. * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
  173. * @returns {boolean} The value of the i-th bit
  174. * @private
  175. */
  176. ImplicitSubtree.prototype.tileIsAvailableAtCoordinates = function (
  177. implicitCoordinates
  178. ) {
  179. const index = this.getTileIndex(implicitCoordinates);
  180. return this.tileIsAvailableAtIndex(index);
  181. };
  182. /**
  183. * Check if a specific tile's content is available at an index of the content availability bitstream
  184. *
  185. * @param {number} index The index of the desired tile
  186. * @param {number} [contentIndex=0] The index of the desired content when multiple contents are used.
  187. * @returns {boolean} The value of the i-th bit
  188. * @private
  189. */
  190. ImplicitSubtree.prototype.contentIsAvailableAtIndex = function (
  191. index,
  192. contentIndex
  193. ) {
  194. contentIndex = defaultValue(contentIndex, 0);
  195. //>>includeStart('debug', pragmas.debug);
  196. if (
  197. contentIndex < 0 ||
  198. contentIndex >= this._contentAvailabilityBitstreams.length
  199. ) {
  200. throw new DeveloperError("contentIndex out of bounds.");
  201. }
  202. //>>includeEnd('debug');
  203. return this._contentAvailabilityBitstreams[contentIndex].getBit(index);
  204. };
  205. /**
  206. * Check if a specific tile's content is available at an implicit tile coordinate
  207. *
  208. * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
  209. * @param {number} [contentIndex=0] The index of the desired content when the <code>3DTILES_multiple_contents</code> extension is used.
  210. * @returns {boolean} The value of the i-th bit
  211. * @private
  212. */
  213. ImplicitSubtree.prototype.contentIsAvailableAtCoordinates = function (
  214. implicitCoordinates,
  215. contentIndex
  216. ) {
  217. const index = this.getTileIndex(implicitCoordinates);
  218. return this.contentIsAvailableAtIndex(index, contentIndex);
  219. };
  220. /**
  221. * Check if a child subtree is available at an index of the child subtree availability bitstream
  222. *
  223. * @param {number} index The index of the desired child subtree
  224. * @returns {boolean} The value of the i-th bit
  225. * @private
  226. */
  227. ImplicitSubtree.prototype.childSubtreeIsAvailableAtIndex = function (index) {
  228. return this._childSubtreeAvailability.getBit(index);
  229. };
  230. /**
  231. * Check if a specific child subtree is available at an implicit tile coordinate
  232. *
  233. * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a child subtree
  234. * @returns {boolean} The value of the i-th bit
  235. * @private
  236. */
  237. ImplicitSubtree.prototype.childSubtreeIsAvailableAtCoordinates = function (
  238. implicitCoordinates
  239. ) {
  240. const index = this.getChildSubtreeIndex(implicitCoordinates);
  241. return this.childSubtreeIsAvailableAtIndex(index);
  242. };
  243. /**
  244. * Get the index of the first node at the given level within this subtree.
  245. * e.g. for a quadtree:
  246. * <ul>
  247. * <li>Level 0 starts at index 0</li>
  248. * <li>Level 1 starts at index 1</li>
  249. * <li>Level 2 starts at index 5</li>
  250. * </ul>
  251. *
  252. * @param {number} level The 0-indexed level number relative to the root of the subtree
  253. * @returns {number} The first index at the desired level
  254. * @private
  255. */
  256. ImplicitSubtree.prototype.getLevelOffset = function (level) {
  257. const branchingFactor = this._branchingFactor;
  258. return (Math.pow(branchingFactor, level) - 1) / (branchingFactor - 1);
  259. };
  260. /**
  261. * Get the morton index of a tile's parent. This is equivalent to
  262. * chopping off the last 2 (quadtree) or 3 (octree) bits of the morton
  263. * index.
  264. *
  265. * @param {number} childIndex The morton index of the child tile relative to its parent
  266. * @returns {number} The index of the child's parent node
  267. * @private
  268. */
  269. ImplicitSubtree.prototype.getParentMortonIndex = function (mortonIndex) {
  270. let bitsPerLevel = 2;
  271. if (this._subdivisionScheme === ImplicitSubdivisionScheme.OCTREE) {
  272. bitsPerLevel = 3;
  273. }
  274. return mortonIndex >> bitsPerLevel;
  275. };
  276. /**
  277. * Parse all relevant information out of the subtree. This fetches any
  278. * external buffers that are used by the implicit tileset.
  279. *
  280. * @param {Resource} resource The resource for this subtree. This is used for fetching external buffers as needed.
  281. * @param {object} [json] The JSON object for this subtree. If parsing from a binary subtree file, this will be undefined.
  282. * @param {Uint8Array} [subtreeView] The contents of the subtree binary
  283. * @param {ImplicitTileset} implicitTileset The implicit tileset this subtree belongs to.
  284. * @param {ImplicitTileCoordinates} implicitCoordinates The coordinates of the subtree's root tile.
  285. * @return {Promise<ImplicitSubtree>} The created subtree
  286. * @private
  287. *
  288. * @exception {DeveloperError} One of json and subtreeView must be defined.
  289. */
  290. ImplicitSubtree.fromSubtreeJson = async function (
  291. resource,
  292. json,
  293. subtreeView,
  294. implicitTileset,
  295. implicitCoordinates
  296. ) {
  297. //>>includeStart('debug', pragmas.debug);
  298. Check.typeOf.object("resource", resource);
  299. if (defined(json) === defined(subtreeView)) {
  300. throw new DeveloperError("One of json and subtreeView must be defined.");
  301. }
  302. Check.typeOf.object("implicitTileset", implicitTileset);
  303. Check.typeOf.object("implicitCoordinates", implicitCoordinates);
  304. //>>includeEnd('debug');
  305. const subtree = new ImplicitSubtree(
  306. resource,
  307. implicitTileset,
  308. implicitCoordinates
  309. );
  310. let chunks;
  311. if (defined(json)) {
  312. chunks = {
  313. json: json,
  314. binary: undefined,
  315. };
  316. } else {
  317. chunks = parseSubtreeChunks(subtreeView);
  318. }
  319. const subtreeJson = chunks.json;
  320. subtree._subtreeJson = subtreeJson;
  321. let tilePropertyTableJson;
  322. if (hasExtension(subtreeJson, "3DTILES_metadata")) {
  323. tilePropertyTableJson = subtreeJson.extensions["3DTILES_metadata"];
  324. } else if (defined(subtreeJson.tileMetadata)) {
  325. const propertyTableIndex = subtreeJson.tileMetadata;
  326. tilePropertyTableJson = subtreeJson.propertyTables[propertyTableIndex];
  327. }
  328. const contentPropertyTableJsons = [];
  329. if (defined(subtreeJson.contentMetadata)) {
  330. const length = subtreeJson.contentMetadata.length;
  331. for (let i = 0; i < length; i++) {
  332. const propertyTableIndex = subtreeJson.contentMetadata[i];
  333. contentPropertyTableJsons.push(
  334. subtreeJson.propertyTables[propertyTableIndex]
  335. );
  336. }
  337. }
  338. let metadata;
  339. const schema = implicitTileset.metadataSchema;
  340. const subtreeMetadata = subtreeJson.subtreeMetadata;
  341. if (defined(subtreeMetadata)) {
  342. const metadataClass = subtreeMetadata.class;
  343. const subtreeMetadataClass = schema.classes[metadataClass];
  344. metadata = new ImplicitSubtreeMetadata({
  345. subtreeMetadata: subtreeMetadata,
  346. class: subtreeMetadataClass,
  347. });
  348. }
  349. subtree._metadata = metadata;
  350. subtree._tilePropertyTableJson = tilePropertyTableJson;
  351. subtree._contentPropertyTableJsons = contentPropertyTableJsons;
  352. // if no contentAvailability is specified, no tile in the subtree has
  353. // content
  354. const defaultContentAvailability = {
  355. constant: 0,
  356. };
  357. // In 3D Tiles 1.1, content availability is provided in an array in the subtree JSON
  358. // regardless of whether or not it contains multiple contents. This differs from previous
  359. // schemas, where content availability is either a single object in the subtree JSON or
  360. // as an array in the 3DTILES_multiple_contents extension.
  361. //
  362. // After identifying how availability is stored, put the results in this new array for consistent processing later
  363. subtreeJson.contentAvailabilityHeaders = [];
  364. if (hasExtension(subtreeJson, "3DTILES_multiple_contents")) {
  365. subtreeJson.contentAvailabilityHeaders =
  366. subtreeJson.extensions["3DTILES_multiple_contents"].contentAvailability;
  367. } else if (Array.isArray(subtreeJson.contentAvailability)) {
  368. subtreeJson.contentAvailabilityHeaders = subtreeJson.contentAvailability;
  369. } else {
  370. subtreeJson.contentAvailabilityHeaders.push(
  371. defaultValue(subtreeJson.contentAvailability, defaultContentAvailability)
  372. );
  373. }
  374. const bufferHeaders = preprocessBuffers(subtreeJson.buffers);
  375. const bufferViewHeaders = preprocessBufferViews(
  376. subtreeJson.bufferViews,
  377. bufferHeaders
  378. );
  379. // Buffers and buffer views are inactive until explicitly marked active.
  380. // This way we can avoid fetching buffers that will not be used.
  381. markActiveBufferViews(subtreeJson, bufferViewHeaders);
  382. if (defined(tilePropertyTableJson)) {
  383. markActiveMetadataBufferViews(tilePropertyTableJson, bufferViewHeaders);
  384. }
  385. for (let i = 0; i < contentPropertyTableJsons.length; i++) {
  386. const contentPropertyTableJson = contentPropertyTableJsons[i];
  387. markActiveMetadataBufferViews(contentPropertyTableJson, bufferViewHeaders);
  388. }
  389. const buffersU8 = await requestActiveBuffers(
  390. subtree,
  391. bufferHeaders,
  392. chunks.binary
  393. );
  394. const bufferViewsU8 = parseActiveBufferViews(bufferViewHeaders, buffersU8);
  395. parseAvailability(subtree, subtreeJson, implicitTileset, bufferViewsU8);
  396. if (defined(tilePropertyTableJson)) {
  397. parseTileMetadataTable(subtree, implicitTileset, bufferViewsU8);
  398. makeTileJumpBuffer(subtree);
  399. }
  400. parseContentMetadataTables(subtree, implicitTileset, bufferViewsU8);
  401. makeContentJumpBuffers(subtree);
  402. subtree._ready = true;
  403. return subtree;
  404. };
  405. /**
  406. * A helper object for storing the two parts of the subtree binary
  407. *
  408. * @typedef {object} SubtreeChunks
  409. * @property {object} json The json chunk of the subtree
  410. * @property {Uint8Array} binary The binary chunk of the subtree. This represents the internal buffer.
  411. * @private
  412. */
  413. /**
  414. * Given the binary contents of a subtree, split into JSON and binary chunks
  415. *
  416. * @param {Uint8Array} subtreeView The subtree binary
  417. * @returns {SubtreeChunks} An object containing the JSON and binary chunks.
  418. * @private
  419. */
  420. function parseSubtreeChunks(subtreeView) {
  421. // Parse the header
  422. const littleEndian = true;
  423. const subtreeReader = new DataView(
  424. subtreeView.buffer,
  425. subtreeView.byteOffset
  426. );
  427. // Skip to the chunk lengths
  428. let byteOffset = 8;
  429. // Read the bottom 32 bits of the 64-bit byte length. This is ok for now because:
  430. // 1) not all browsers have native 64-bit operations
  431. // 2) the data is well under 4GB
  432. const jsonByteLength = subtreeReader.getUint32(byteOffset, littleEndian);
  433. byteOffset += 8;
  434. const binaryByteLength = subtreeReader.getUint32(byteOffset, littleEndian);
  435. byteOffset += 8;
  436. const subtreeJson = getJsonFromTypedArray(
  437. subtreeView,
  438. byteOffset,
  439. jsonByteLength
  440. );
  441. byteOffset += jsonByteLength;
  442. const subtreeBinary = subtreeView.subarray(
  443. byteOffset,
  444. byteOffset + binaryByteLength
  445. );
  446. return {
  447. json: subtreeJson,
  448. binary: subtreeBinary,
  449. };
  450. }
  451. /**
  452. * A buffer header is the JSON header from the subtree JSON chunk plus
  453. * a couple extra boolean flags for easy reference.
  454. *
  455. * Buffers are assumed inactive until explicitly marked active. This is used
  456. * to avoid fetching unneeded buffers.
  457. *
  458. * @typedef {object} BufferHeader
  459. * @property {boolean} isExternal True if this is an external buffer
  460. * @property {boolean} isActive Whether this buffer is currently used.
  461. * @property {string} [uri] The URI of the buffer (external buffers only)
  462. * @property {number} byteLength The byte length of the buffer, including any padding contained within.
  463. * @private
  464. */
  465. /**
  466. * Iterate over the list of buffers from the subtree JSON and add the
  467. * isExternal and isActive fields for easier parsing later. This modifies
  468. * the objects in place.
  469. *
  470. * @param {Object[]} [bufferHeaders=[]] The JSON from subtreeJson.buffers.
  471. * @returns {BufferHeader[]} The same array of headers with additional fields.
  472. * @private
  473. */
  474. function preprocessBuffers(bufferHeaders) {
  475. bufferHeaders = defined(bufferHeaders) ? bufferHeaders : [];
  476. for (let i = 0; i < bufferHeaders.length; i++) {
  477. const bufferHeader = bufferHeaders[i];
  478. bufferHeader.isExternal = defined(bufferHeader.uri);
  479. bufferHeader.isActive = false;
  480. }
  481. return bufferHeaders;
  482. }
  483. /**
  484. * A buffer header is the JSON header from the subtree JSON chunk plus
  485. * the isActive flag and a reference to the header for the underlying buffer
  486. *
  487. * @typedef {object} BufferViewHeader
  488. * @property {BufferHeader} bufferHeader A reference to the header for the underlying buffer
  489. * @property {boolean} isActive Whether this bufferView is currently used.
  490. * @property {number} buffer The index of the underlying buffer.
  491. * @property {number} byteOffset The start byte of the bufferView within the buffer.
  492. * @property {number} byteLength The length of the bufferView. No padding is included in this length.
  493. * @private
  494. */
  495. /**
  496. * Iterate the list of buffer views from the subtree JSON and add the
  497. * isActive flag. Also save a reference to the bufferHeader
  498. *
  499. * @param {Object[]} [bufferViewHeaders=[]] The JSON from subtree.bufferViews
  500. * @param {BufferHeader[]} bufferHeaders The preprocessed buffer headers
  501. * @returns {BufferViewHeader[]} The same array of bufferView headers with additional fields
  502. * @private
  503. */
  504. function preprocessBufferViews(bufferViewHeaders, bufferHeaders) {
  505. bufferViewHeaders = defined(bufferViewHeaders) ? bufferViewHeaders : [];
  506. for (let i = 0; i < bufferViewHeaders.length; i++) {
  507. const bufferViewHeader = bufferViewHeaders[i];
  508. const bufferHeader = bufferHeaders[bufferViewHeader.buffer];
  509. bufferViewHeader.bufferHeader = bufferHeader;
  510. bufferViewHeader.isActive = false;
  511. }
  512. return bufferViewHeaders;
  513. }
  514. /**
  515. * Determine which buffer views need to be loaded into memory. This includes:
  516. *
  517. * <ul>
  518. * <li>The tile availability bitstream (if a bitstream is defined)</li>
  519. * <li>The content availability bitstream(s) (if a bitstream is defined)</li>
  520. * <li>The child subtree availability bitstream (if a bitstream is defined)</li>
  521. * </ul>
  522. *
  523. * <p>
  524. * This function modifies the buffer view headers' isActive flags in place.
  525. * </p>
  526. *
  527. * @param {Object[]} subtreeJson The JSON chunk from the subtree
  528. * @param {BufferViewHeader[]} bufferViewHeaders The preprocessed buffer view headers
  529. * @private
  530. */
  531. function markActiveBufferViews(subtreeJson, bufferViewHeaders) {
  532. let header;
  533. const tileAvailabilityHeader = subtreeJson.tileAvailability;
  534. // Check for bitstream first, which is part of the current schema.
  535. // bufferView is the name of the bitstream from an older schema.
  536. if (defined(tileAvailabilityHeader.bitstream)) {
  537. header = bufferViewHeaders[tileAvailabilityHeader.bitstream];
  538. } else if (defined(tileAvailabilityHeader.bufferView)) {
  539. header = bufferViewHeaders[tileAvailabilityHeader.bufferView];
  540. }
  541. if (defined(header)) {
  542. header.isActive = true;
  543. header.bufferHeader.isActive = true;
  544. }
  545. const contentAvailabilityHeaders = subtreeJson.contentAvailabilityHeaders;
  546. for (let i = 0; i < contentAvailabilityHeaders.length; i++) {
  547. header = undefined;
  548. if (defined(contentAvailabilityHeaders[i].bitstream)) {
  549. header = bufferViewHeaders[contentAvailabilityHeaders[i].bitstream];
  550. } else if (defined(contentAvailabilityHeaders[i].bufferView)) {
  551. header = bufferViewHeaders[contentAvailabilityHeaders[i].bufferView];
  552. }
  553. if (defined(header)) {
  554. header.isActive = true;
  555. header.bufferHeader.isActive = true;
  556. }
  557. }
  558. header = undefined;
  559. const childSubtreeAvailabilityHeader = subtreeJson.childSubtreeAvailability;
  560. if (defined(childSubtreeAvailabilityHeader.bitstream)) {
  561. header = bufferViewHeaders[childSubtreeAvailabilityHeader.bitstream];
  562. } else if (defined(childSubtreeAvailabilityHeader.bufferView)) {
  563. header = bufferViewHeaders[childSubtreeAvailabilityHeader.bufferView];
  564. }
  565. if (defined(header)) {
  566. header.isActive = true;
  567. header.bufferHeader.isActive = true;
  568. }
  569. }
  570. /**
  571. * For handling metadata, look over the tile and content metadata buffers
  572. * <p>
  573. * This always loads all of the metadata immediately. Future iterations may
  574. * allow filtering this to avoid downloading unneeded buffers.
  575. * </p>
  576. *
  577. * @param {object} propertyTableJson The property table JSON for either a tile or some content
  578. * @param {BufferViewHeader[]} bufferViewHeaders The preprocessed buffer view headers
  579. * @private
  580. */
  581. function markActiveMetadataBufferViews(propertyTableJson, bufferViewHeaders) {
  582. const properties = propertyTableJson.properties;
  583. let header;
  584. for (const key in properties) {
  585. if (properties.hasOwnProperty(key)) {
  586. const metadataHeader = properties[key];
  587. // An older spec used bufferView
  588. const valuesBufferView = defaultValue(
  589. metadataHeader.values,
  590. metadataHeader.bufferView
  591. );
  592. header = bufferViewHeaders[valuesBufferView];
  593. header.isActive = true;
  594. header.bufferHeader.isActive = true;
  595. // An older spec used stringOffsetBufferView
  596. const stringOffsetBufferView = defaultValue(
  597. metadataHeader.stringOffsets,
  598. metadataHeader.stringOffsetBufferView
  599. );
  600. if (defined(stringOffsetBufferView)) {
  601. header = bufferViewHeaders[stringOffsetBufferView];
  602. header.isActive = true;
  603. header.bufferHeader.isActive = true;
  604. }
  605. // an older spec used arrayOffsetBufferView
  606. const arrayOffsetBufferView = defaultValue(
  607. metadataHeader.arrayOffsets,
  608. metadataHeader.arrayOffsetBufferView
  609. );
  610. if (defined(arrayOffsetBufferView)) {
  611. header = bufferViewHeaders[arrayOffsetBufferView];
  612. header.isActive = true;
  613. header.bufferHeader.isActive = true;
  614. }
  615. }
  616. }
  617. }
  618. /**
  619. * Go through the list of buffers and gather all the active ones into a
  620. * a dictionary. Since external buffers are allowed, this sometimes involves
  621. * fetching separate binary files. Consequently, this method returns a promise.
  622. * <p>
  623. * The results are put into a dictionary object. The keys are indices of
  624. * buffers, and the values are Uint8Arrays of the contents. Only buffers
  625. * marked with the isActive flag are fetched.
  626. * </p>
  627. * <p>
  628. * The internal buffer (the subtree's binary chunk) is also stored in this
  629. * dictionary if it is marked active.
  630. * </p>
  631. * @param {ImplicitSubtree} subtree The subtree
  632. * @param {BufferHeader[]} bufferHeaders The preprocessed buffer headers
  633. * @param {Uint8Array} internalBuffer The binary chunk of the subtree file
  634. * @returns {Promise<object>} A promise resolving to the dictionary of active buffers
  635. * @private
  636. */
  637. function requestActiveBuffers(subtree, bufferHeaders, internalBuffer) {
  638. const promises = [];
  639. for (let i = 0; i < bufferHeaders.length; i++) {
  640. const bufferHeader = bufferHeaders[i];
  641. if (!bufferHeader.isActive) {
  642. promises.push(Promise.resolve(undefined));
  643. } else if (bufferHeader.isExternal) {
  644. const promise = requestExternalBuffer(subtree, bufferHeader);
  645. promises.push(promise);
  646. } else {
  647. promises.push(Promise.resolve(internalBuffer));
  648. }
  649. }
  650. return Promise.all(promises).then(function (bufferResults) {
  651. const buffersU8 = {};
  652. for (let i = 0; i < bufferResults.length; i++) {
  653. const result = bufferResults[i];
  654. if (defined(result)) {
  655. buffersU8[i] = result;
  656. }
  657. }
  658. return buffersU8;
  659. });
  660. }
  661. async function requestExternalBuffer(subtree, bufferHeader) {
  662. const baseResource = subtree._resource;
  663. const bufferResource = baseResource.getDerivedResource({
  664. url: bufferHeader.uri,
  665. });
  666. const bufferLoader = ResourceCache.getExternalBufferLoader({
  667. resource: bufferResource,
  668. });
  669. subtree._bufferLoader = bufferLoader;
  670. try {
  671. await bufferLoader.load();
  672. } catch (error) {
  673. if (bufferLoader.isDestroyed()) {
  674. return;
  675. }
  676. throw error;
  677. }
  678. return bufferLoader.typedArray;
  679. }
  680. /**
  681. * Go through the list of buffer views, and if they are marked as active,
  682. * extract a subarray from one of the active buffers.
  683. *
  684. * @param {BufferViewHeader[]} bufferViewHeaders
  685. * @param {object} buffersU8 A dictionary of buffer index to a Uint8Array of its contents.
  686. * @returns {object} A dictionary of buffer view index to a Uint8Array of its contents.
  687. * @private
  688. */
  689. function parseActiveBufferViews(bufferViewHeaders, buffersU8) {
  690. const bufferViewsU8 = {};
  691. for (let i = 0; i < bufferViewHeaders.length; i++) {
  692. const bufferViewHeader = bufferViewHeaders[i];
  693. if (!bufferViewHeader.isActive) {
  694. continue;
  695. }
  696. const start = bufferViewHeader.byteOffset;
  697. const end = start + bufferViewHeader.byteLength;
  698. const buffer = buffersU8[bufferViewHeader.buffer];
  699. const bufferView = buffer.subarray(start, end);
  700. bufferViewsU8[i] = bufferView;
  701. }
  702. return bufferViewsU8;
  703. }
  704. /**
  705. * Parse the three availability bitstreams and store them in the subtree
  706. *
  707. * @param {ImplicitSubtree} subtree The subtree to modify
  708. * @param {object} subtreeJson The subtree JSON
  709. * @param {ImplicitTileset} implicitTileset The implicit tileset this subtree belongs to
  710. * @param {object} bufferViewsU8 A dictionary of buffer view index to a Uint8Array of its contents.
  711. * @private
  712. */
  713. function parseAvailability(
  714. subtree,
  715. subtreeJson,
  716. implicitTileset,
  717. bufferViewsU8
  718. ) {
  719. const branchingFactor = implicitTileset.branchingFactor;
  720. const subtreeLevels = implicitTileset.subtreeLevels;
  721. const tileAvailabilityBits =
  722. (Math.pow(branchingFactor, subtreeLevels) - 1) / (branchingFactor - 1);
  723. const childSubtreeBits = Math.pow(branchingFactor, subtreeLevels);
  724. // availableCount is only needed for the metadata jump buffer, which
  725. // corresponds to the tile availability bitstream.
  726. const hasMetadataExtension = hasExtension(subtreeJson, "3DTILES_metadata");
  727. const hasTileMetadata = defined(subtree._tilePropertyTableJson);
  728. let computeAvailableCountEnabled = hasMetadataExtension || hasTileMetadata;
  729. subtree._tileAvailability = parseAvailabilityBitstream(
  730. subtreeJson.tileAvailability,
  731. bufferViewsU8,
  732. tileAvailabilityBits,
  733. computeAvailableCountEnabled
  734. );
  735. const hasContentMetadata = subtree._contentPropertyTableJsons.length > 0;
  736. computeAvailableCountEnabled =
  737. computeAvailableCountEnabled || hasContentMetadata;
  738. for (let i = 0; i < subtreeJson.contentAvailabilityHeaders.length; i++) {
  739. const bitstream = parseAvailabilityBitstream(
  740. subtreeJson.contentAvailabilityHeaders[i],
  741. bufferViewsU8,
  742. // content availability has the same length as tile availability.
  743. tileAvailabilityBits,
  744. computeAvailableCountEnabled
  745. );
  746. subtree._contentAvailabilityBitstreams.push(bitstream);
  747. }
  748. subtree._childSubtreeAvailability = parseAvailabilityBitstream(
  749. subtreeJson.childSubtreeAvailability,
  750. bufferViewsU8,
  751. childSubtreeBits
  752. );
  753. }
  754. /**
  755. * Given the JSON describing an availability bitstream, turn it into an
  756. * in-memory representation using an {@link ImplicitAvailabilityBitstream}
  757. * object. This handles both constants and bitstreams from a bufferView.
  758. *
  759. * @param {object} availabilityJson A JSON object representing the availability
  760. * @param {object} bufferViewsU8 A dictionary of bufferView index to its Uint8Array contents.
  761. * @param {number} lengthBits The length of the availability bitstream in bits
  762. * @param {boolean} [computeAvailableCountEnabled] If true and availabilityJson.availableCount is undefined, the availableCount will be computed.
  763. * @returns {ImplicitAvailabilityBitstream} The parsed bitstream object
  764. * @private
  765. */
  766. function parseAvailabilityBitstream(
  767. availabilityJson,
  768. bufferViewsU8,
  769. lengthBits,
  770. computeAvailableCountEnabled
  771. ) {
  772. if (defined(availabilityJson.constant)) {
  773. return new ImplicitAvailabilityBitstream({
  774. constant: Boolean(availabilityJson.constant),
  775. lengthBits: lengthBits,
  776. availableCount: availabilityJson.availableCount,
  777. });
  778. }
  779. let bufferView;
  780. // Check for bitstream first, which is part of the current schema.
  781. // bufferView is the name of the bitstream from an older schema.
  782. if (defined(availabilityJson.bitstream)) {
  783. bufferView = bufferViewsU8[availabilityJson.bitstream];
  784. } else if (defined(availabilityJson.bufferView)) {
  785. bufferView = bufferViewsU8[availabilityJson.bufferView];
  786. }
  787. return new ImplicitAvailabilityBitstream({
  788. bitstream: bufferView,
  789. lengthBits: lengthBits,
  790. availableCount: availabilityJson.availableCount,
  791. computeAvailableCountEnabled: computeAvailableCountEnabled,
  792. });
  793. }
  794. /**
  795. * Parse the metadata table for the tile metadata, storing a {@link MetadataTable}
  796. * in the subtree.
  797. *
  798. * @param {ImplicitSubtree} subtree The subtree
  799. * @param {ImplicitTileset} implicitTileset The implicit tileset this subtree belongs to.
  800. * @param {object} bufferViewsU8 A dictionary of bufferView index to its Uint8Array contents.
  801. * @private
  802. */
  803. function parseTileMetadataTable(subtree, implicitTileset, bufferViewsU8) {
  804. const tilePropertyTableJson = subtree._tilePropertyTableJson;
  805. const tileCount = subtree._tileAvailability.availableCount;
  806. const metadataSchema = implicitTileset.metadataSchema;
  807. const tileMetadataClassName = tilePropertyTableJson.class;
  808. const tileMetadataClass = metadataSchema.classes[tileMetadataClassName];
  809. subtree._tileMetadataTable = new MetadataTable({
  810. class: tileMetadataClass,
  811. count: tileCount,
  812. properties: tilePropertyTableJson.properties,
  813. bufferViews: bufferViewsU8,
  814. });
  815. }
  816. /**
  817. * Parse the metadata tables for the content metadata, storing an array of
  818. * {@link MetadataTable}s in the subtree.
  819. *
  820. * @param {ImplicitSubtree} subtree The subtree
  821. * @param {ImplicitTileset} implicitTileset The implicit tileset this subtree belongs to.
  822. * @param {object} bufferViewsU8 A dictionary of bufferView index to its Uint8Array contents.
  823. * @private
  824. */
  825. function parseContentMetadataTables(subtree, implicitTileset, bufferViewsU8) {
  826. const contentPropertyTableJsons = subtree._contentPropertyTableJsons;
  827. const contentAvailabilityBitstreams = subtree._contentAvailabilityBitstreams;
  828. const metadataSchema = implicitTileset.metadataSchema;
  829. const contentMetadataTables = subtree._contentMetadataTables;
  830. for (let i = 0; i < contentPropertyTableJsons.length; i++) {
  831. const contentPropertyTableJson = contentPropertyTableJsons[i];
  832. const contentAvailabilityBitsteam = contentAvailabilityBitstreams[i];
  833. const contentCount = contentAvailabilityBitsteam.availableCount;
  834. const contentMetadataClassName = contentPropertyTableJson.class;
  835. const contentMetadataClass =
  836. metadataSchema.classes[contentMetadataClassName];
  837. const metadataTable = new MetadataTable({
  838. class: contentMetadataClass,
  839. count: contentCount,
  840. properties: contentPropertyTableJson.properties,
  841. bufferViews: bufferViewsU8,
  842. });
  843. contentMetadataTables.push(metadataTable);
  844. }
  845. }
  846. /**
  847. * Make a jump buffer, i.e. a map of a bit index to the metadata entity ID.
  848. * <p>
  849. * For unavailable tiles and content, the jump buffer entries will be uninitialized.
  850. * Use the tile and content availability to determine whether a jump buffer value is valid.
  851. * </p>
  852. *
  853. * @param {ImplicitAvailabilityBitstream} availability The availability bitstream to create the jump buffer from.
  854. * @returns {Array} The resulting jump buffer.
  855. * @private
  856. */
  857. function makeJumpBuffer(availability) {
  858. let entityId = 0;
  859. const bufferLength = availability.lengthBits;
  860. const availableCount = availability.availableCount;
  861. let jumpBuffer;
  862. if (availableCount < 256) {
  863. jumpBuffer = new Uint8Array(bufferLength);
  864. } else if (availableCount < 65536) {
  865. jumpBuffer = new Uint16Array(bufferLength);
  866. } else {
  867. jumpBuffer = new Uint32Array(bufferLength);
  868. }
  869. for (let i = 0; i < availability.lengthBits; i++) {
  870. if (availability.getBit(i)) {
  871. jumpBuffer[i] = entityId;
  872. entityId++;
  873. }
  874. }
  875. return jumpBuffer;
  876. }
  877. /**
  878. * Make the jump buffer, i.e. a map of a bit index to the metadata entity ID,
  879. * for the content metadata. This is stored in the subtree.
  880. *
  881. * @param {ImplicitSubtree} subtree The subtree
  882. * @private
  883. */
  884. function makeTileJumpBuffer(subtree) {
  885. const tileJumpBuffer = makeJumpBuffer(subtree._tileAvailability);
  886. subtree._tileJumpBuffer = tileJumpBuffer;
  887. }
  888. /**
  889. * Make the jump buffers, i.e. maps of bit indices to the metadata entity IDs,
  890. * for the content metadata. This is stored in the subtree.
  891. *
  892. * @param {ImplicitSubtree} subtree The subtree
  893. * @private
  894. */
  895. function makeContentJumpBuffers(subtree) {
  896. const contentJumpBuffers = subtree._contentJumpBuffers;
  897. const contentAvailabilityBitstreams = subtree._contentAvailabilityBitstreams;
  898. for (let i = 0; i < contentAvailabilityBitstreams.length; i++) {
  899. const contentAvailability = contentAvailabilityBitstreams[i];
  900. const contentJumpBuffer = makeJumpBuffer(contentAvailability);
  901. contentJumpBuffers.push(contentJumpBuffer);
  902. }
  903. }
  904. /**
  905. * Given the implicit tiling coordinates for a tile, get the index within the
  906. * subtree's tile availability bitstream.
  907. * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
  908. * @return {number} The tile's index within the subtree.
  909. * @private
  910. */
  911. ImplicitSubtree.prototype.getTileIndex = function (implicitCoordinates) {
  912. const localLevel =
  913. implicitCoordinates.level - this._implicitCoordinates.level;
  914. if (localLevel < 0 || this._subtreeLevels <= localLevel) {
  915. throw new RuntimeError("level is out of bounds for this subtree");
  916. }
  917. const subtreeCoordinates = implicitCoordinates.getSubtreeCoordinates();
  918. const offsetCoordinates = subtreeCoordinates.getOffsetCoordinates(
  919. implicitCoordinates
  920. );
  921. const index = offsetCoordinates.tileIndex;
  922. return index;
  923. };
  924. /**
  925. * Given the implicit tiling coordinates for a child subtree, get the index within the
  926. * subtree's child subtree availability bitstream.
  927. * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a child subtree
  928. * @return {number} The child subtree's index within the subtree's child subtree availability bitstream.
  929. * @private
  930. */
  931. ImplicitSubtree.prototype.getChildSubtreeIndex = function (
  932. implicitCoordinates
  933. ) {
  934. const localLevel =
  935. implicitCoordinates.level - this._implicitCoordinates.level;
  936. if (localLevel !== this._implicitCoordinates.subtreeLevels) {
  937. throw new RuntimeError("level is out of bounds for this subtree");
  938. }
  939. // Call getParentSubtreeCoordinates instead of getSubtreeCoordinates because the
  940. // child subtree is by definition the root of its own subtree, so we need to find
  941. // the parent subtree.
  942. const parentSubtreeCoordinates = implicitCoordinates.getParentSubtreeCoordinates();
  943. const offsetCoordinates = parentSubtreeCoordinates.getOffsetCoordinates(
  944. implicitCoordinates
  945. );
  946. const index = offsetCoordinates.mortonIndex;
  947. return index;
  948. };
  949. /**
  950. * Get the entity ID for a tile within this subtree.
  951. * @param {ImplicitSubtree} subtree The subtree
  952. * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
  953. * @return {number} The entity ID for this tile for accessing tile metadata, or <code>undefined</code> if not applicable.
  954. *
  955. * @private
  956. */
  957. function getTileEntityId(subtree, implicitCoordinates) {
  958. if (!defined(subtree._tileMetadataTable)) {
  959. return undefined;
  960. }
  961. const tileIndex = subtree.getTileIndex(implicitCoordinates);
  962. if (subtree._tileAvailability.getBit(tileIndex)) {
  963. return subtree._tileJumpBuffer[tileIndex];
  964. }
  965. return undefined;
  966. }
  967. /**
  968. * Get the entity ID for a content within this subtree.
  969. * @param {ImplicitSubtree} subtree The subtree
  970. * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a content
  971. * @param {number} contentIndex The content index, for distinguishing between multiple contents.
  972. * @return {number} The entity ID for this content for accessing content metadata, or <code>undefined</code> if not applicable.
  973. *
  974. * @private
  975. */
  976. function getContentEntityId(subtree, implicitCoordinates, contentIndex) {
  977. const metadataTables = subtree._contentMetadataTables;
  978. if (!defined(metadataTables)) {
  979. return undefined;
  980. }
  981. const metadataTable = metadataTables[contentIndex];
  982. if (!defined(metadataTable)) {
  983. return undefined;
  984. }
  985. const availability = subtree._contentAvailabilityBitstreams[contentIndex];
  986. const tileIndex = subtree.getTileIndex(implicitCoordinates);
  987. if (availability.getBit(tileIndex)) {
  988. const contentJumpBuffer = subtree._contentJumpBuffers[contentIndex];
  989. return contentJumpBuffer[tileIndex];
  990. }
  991. return undefined;
  992. }
  993. /**
  994. * Create and return a metadata table view for a tile within this subtree.
  995. * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a tile
  996. * @return {ImplicitMetadataView} The metadata view for this tile, or <code>undefined</code> if not applicable.
  997. *
  998. * @private
  999. */
  1000. ImplicitSubtree.prototype.getTileMetadataView = function (implicitCoordinates) {
  1001. const entityId = getTileEntityId(this, implicitCoordinates);
  1002. if (!defined(entityId)) {
  1003. return undefined;
  1004. }
  1005. const metadataTable = this._tileMetadataTable;
  1006. return new ImplicitMetadataView({
  1007. class: metadataTable.class,
  1008. metadataTable: metadataTable,
  1009. entityId: entityId,
  1010. propertyTableJson: this._tilePropertyTableJson,
  1011. });
  1012. };
  1013. /**
  1014. * Create and return a metadata table view for a content within this subtree.
  1015. * @param {ImplicitTileCoordinates} implicitCoordinates The global coordinates of a content
  1016. * @param {number} contentIndex The index of the content used to distinguish between multiple contents
  1017. * @return {ImplicitMetadataView} The metadata view for this content, or <code>undefined</code> if not applicable.
  1018. *
  1019. * @private
  1020. */
  1021. ImplicitSubtree.prototype.getContentMetadataView = function (
  1022. implicitCoordinates,
  1023. contentIndex
  1024. ) {
  1025. const entityId = getContentEntityId(this, implicitCoordinates, contentIndex);
  1026. if (!defined(entityId)) {
  1027. return undefined;
  1028. }
  1029. const metadataTable = this._contentMetadataTables[contentIndex];
  1030. const propertyTableJson = this._contentPropertyTableJsons[contentIndex];
  1031. return new ImplicitMetadataView({
  1032. class: metadataTable.class,
  1033. metadataTable: metadataTable,
  1034. entityId: entityId,
  1035. contentIndex: contentIndex,
  1036. propertyTableJson: propertyTableJson,
  1037. });
  1038. };
  1039. /**
  1040. * @private
  1041. */
  1042. ImplicitSubtree.prototype.isDestroyed = function () {
  1043. return false;
  1044. };
  1045. /**
  1046. * @private
  1047. */
  1048. ImplicitSubtree.prototype.destroy = function () {
  1049. if (defined(this._bufferLoader)) {
  1050. ResourceCache.unload(this._bufferLoader);
  1051. }
  1052. return destroyObject(this);
  1053. };
  1054. export default ImplicitSubtree;