ImplicitSubtree.js 39 KB

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