BatchTableHierarchy.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. import AttributeType from "./AttributeType.js";
  2. import Check from "../Core/Check.js";
  3. import clone from "../Core/clone.js";
  4. import combine from "../Core/combine.js";
  5. import ComponentDatatype from "../Core/ComponentDatatype.js";
  6. import defined from "../Core/defined.js";
  7. import defaultValue from "../Core/defaultValue.js";
  8. import DeveloperError from "../Core/DeveloperError.js";
  9. import getBinaryAccessor from "./getBinaryAccessor.js";
  10. import RuntimeError from "../Core/RuntimeError.js";
  11. /**
  12. * Object for handling the <code>3DTILES_batch_table_hierarchy</code> extension
  13. *
  14. * @param {object} options Object with the following properties:
  15. * @param {object} options.extension The <code>3DTILES_batch_table_hierarchy</code> extension object.
  16. * @param {Uint8Array} [options.binaryBody] The binary body of the batch table
  17. *
  18. * @alias BatchTableHierarchy
  19. * @constructor
  20. *
  21. * @private
  22. */
  23. function BatchTableHierarchy(options) {
  24. this._classes = undefined;
  25. this._classIds = undefined;
  26. this._classIndexes = undefined;
  27. this._parentCounts = undefined;
  28. this._parentIndexes = undefined;
  29. this._parentIds = undefined;
  30. // Total memory used by the typed arrays
  31. this._byteLength = 0;
  32. //>>includeStart('debug', pragmas.debug);
  33. Check.typeOf.object("options.extension", options.extension);
  34. //>>includeEnd('debug');
  35. initialize(this, options.extension, options.binaryBody);
  36. //>>includeStart('debug', pragmas.debug);
  37. validateHierarchy(this);
  38. //>>includeEnd('debug');
  39. }
  40. Object.defineProperties(BatchTableHierarchy.prototype, {
  41. byteLength: {
  42. get: function () {
  43. return this._byteLength;
  44. },
  45. },
  46. });
  47. /**
  48. * Parse the batch table hierarchy from the
  49. * <code>3DTILES_batch_table_hierarchy</code> extension.
  50. *
  51. * @param {BatchTableHierarchy} hierarchy The hierarchy instance
  52. * @param {object} hierarchyJson The JSON of the extension
  53. * @param {Uint8Array} [binaryBody] The binary body of the batch table for accessing binary properties
  54. * @private
  55. */
  56. function initialize(hierarchy, hierarchyJson, binaryBody) {
  57. let i;
  58. let classId;
  59. let binaryAccessor;
  60. const instancesLength = hierarchyJson.instancesLength;
  61. const classes = hierarchyJson.classes;
  62. let classIds = hierarchyJson.classIds;
  63. let parentCounts = hierarchyJson.parentCounts;
  64. let parentIds = hierarchyJson.parentIds;
  65. let parentIdsLength = instancesLength;
  66. let byteLength = 0;
  67. if (defined(classIds.byteOffset)) {
  68. classIds.componentType = defaultValue(
  69. classIds.componentType,
  70. ComponentDatatype.UNSIGNED_SHORT
  71. );
  72. classIds.type = AttributeType.SCALAR;
  73. binaryAccessor = getBinaryAccessor(classIds);
  74. classIds = binaryAccessor.createArrayBufferView(
  75. binaryBody.buffer,
  76. binaryBody.byteOffset + classIds.byteOffset,
  77. instancesLength
  78. );
  79. byteLength += classIds.byteLength;
  80. }
  81. let parentIndexes;
  82. if (defined(parentCounts)) {
  83. if (defined(parentCounts.byteOffset)) {
  84. parentCounts.componentType = defaultValue(
  85. parentCounts.componentType,
  86. ComponentDatatype.UNSIGNED_SHORT
  87. );
  88. parentCounts.type = AttributeType.SCALAR;
  89. binaryAccessor = getBinaryAccessor(parentCounts);
  90. parentCounts = binaryAccessor.createArrayBufferView(
  91. binaryBody.buffer,
  92. binaryBody.byteOffset + parentCounts.byteOffset,
  93. instancesLength
  94. );
  95. byteLength += parentCounts.byteLength;
  96. }
  97. parentIndexes = new Uint16Array(instancesLength);
  98. parentIdsLength = 0;
  99. for (i = 0; i < instancesLength; ++i) {
  100. parentIndexes[i] = parentIdsLength;
  101. parentIdsLength += parentCounts[i];
  102. }
  103. byteLength += parentIndexes.byteLength;
  104. }
  105. if (defined(parentIds) && defined(parentIds.byteOffset)) {
  106. parentIds.componentType = defaultValue(
  107. parentIds.componentType,
  108. ComponentDatatype.UNSIGNED_SHORT
  109. );
  110. parentIds.type = AttributeType.SCALAR;
  111. binaryAccessor = getBinaryAccessor(parentIds);
  112. parentIds = binaryAccessor.createArrayBufferView(
  113. binaryBody.buffer,
  114. binaryBody.byteOffset + parentIds.byteOffset,
  115. parentIdsLength
  116. );
  117. byteLength += parentIds.byteLength;
  118. }
  119. const classesLength = classes.length;
  120. for (i = 0; i < classesLength; ++i) {
  121. const classInstancesLength = classes[i].length;
  122. const properties = classes[i].instances;
  123. const binaryProperties = getBinaryProperties(
  124. classInstancesLength,
  125. properties,
  126. binaryBody
  127. );
  128. byteLength += countBinaryPropertyMemory(binaryProperties);
  129. classes[i].instances = combine(binaryProperties, properties);
  130. }
  131. const classCounts = new Array(classesLength).fill(0);
  132. const classIndexes = new Uint16Array(instancesLength);
  133. for (i = 0; i < instancesLength; ++i) {
  134. classId = classIds[i];
  135. classIndexes[i] = classCounts[classId];
  136. ++classCounts[classId];
  137. }
  138. byteLength += classIndexes.byteLength;
  139. hierarchy._classes = classes;
  140. hierarchy._classIds = classIds;
  141. hierarchy._classIndexes = classIndexes;
  142. hierarchy._parentCounts = parentCounts;
  143. hierarchy._parentIndexes = parentIndexes;
  144. hierarchy._parentIds = parentIds;
  145. hierarchy._byteLength = byteLength;
  146. }
  147. function getBinaryProperties(featuresLength, properties, binaryBody) {
  148. let binaryProperties;
  149. for (const name in properties) {
  150. if (properties.hasOwnProperty(name)) {
  151. const property = properties[name];
  152. const byteOffset = property.byteOffset;
  153. if (defined(byteOffset)) {
  154. // This is a binary property
  155. const componentType = property.componentType;
  156. const type = property.type;
  157. if (!defined(componentType)) {
  158. throw new RuntimeError("componentType is required.");
  159. }
  160. if (!defined(type)) {
  161. throw new RuntimeError("type is required.");
  162. }
  163. if (!defined(binaryBody)) {
  164. throw new RuntimeError(
  165. `Property ${name} requires a batch table binary.`
  166. );
  167. }
  168. const binaryAccessor = getBinaryAccessor(property);
  169. const componentCount = binaryAccessor.componentsPerAttribute;
  170. const classType = binaryAccessor.classType;
  171. const typedArray = binaryAccessor.createArrayBufferView(
  172. binaryBody.buffer,
  173. binaryBody.byteOffset + byteOffset,
  174. featuresLength
  175. );
  176. if (!defined(binaryProperties)) {
  177. binaryProperties = {};
  178. }
  179. // Store any information needed to access the binary data, including the typed array,
  180. // componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4).
  181. binaryProperties[name] = {
  182. typedArray: typedArray,
  183. componentCount: componentCount,
  184. type: classType,
  185. };
  186. }
  187. }
  188. }
  189. return binaryProperties;
  190. }
  191. function countBinaryPropertyMemory(binaryProperties) {
  192. let byteLength = 0;
  193. for (const name in binaryProperties) {
  194. if (binaryProperties.hasOwnProperty(name)) {
  195. byteLength += binaryProperties[name].typedArray.byteLength;
  196. }
  197. }
  198. return byteLength;
  199. }
  200. //>>includeStart('debug', pragmas.debug);
  201. const scratchValidateStack = [];
  202. function validateHierarchy(hierarchy) {
  203. const stack = scratchValidateStack;
  204. stack.length = 0;
  205. const classIds = hierarchy._classIds;
  206. const instancesLength = classIds.length;
  207. for (let i = 0; i < instancesLength; ++i) {
  208. validateInstance(hierarchy, i, stack);
  209. }
  210. }
  211. function validateInstance(hierarchy, instanceIndex, stack) {
  212. const parentCounts = hierarchy._parentCounts;
  213. const parentIds = hierarchy._parentIds;
  214. const parentIndexes = hierarchy._parentIndexes;
  215. const classIds = hierarchy._classIds;
  216. const instancesLength = classIds.length;
  217. if (!defined(parentIds)) {
  218. // No need to validate if there are no parents
  219. return;
  220. }
  221. if (instanceIndex >= instancesLength) {
  222. throw new DeveloperError(
  223. `Parent index ${instanceIndex} exceeds the total number of instances: ${instancesLength}`
  224. );
  225. }
  226. if (stack.indexOf(instanceIndex) > -1) {
  227. throw new DeveloperError(
  228. "Circular dependency detected in the batch table hierarchy."
  229. );
  230. }
  231. stack.push(instanceIndex);
  232. const parentCount = defined(parentCounts) ? parentCounts[instanceIndex] : 1;
  233. const parentIndex = defined(parentCounts)
  234. ? parentIndexes[instanceIndex]
  235. : instanceIndex;
  236. for (let i = 0; i < parentCount; ++i) {
  237. const parentId = parentIds[parentIndex + i];
  238. // Stop the traversal when the instance has no parent (its parentId equals itself), else continue the traversal.
  239. if (parentId !== instanceIndex) {
  240. validateInstance(hierarchy, parentId, stack);
  241. }
  242. }
  243. stack.pop(instanceIndex);
  244. }
  245. //>>includeEnd('debug');
  246. // The size of this array equals the maximum instance count among all loaded tiles, which has the potential to be large.
  247. const scratchVisited = [];
  248. const scratchStack = [];
  249. let marker = 0;
  250. function traverseHierarchyMultipleParents(
  251. hierarchy,
  252. instanceIndex,
  253. endConditionCallback
  254. ) {
  255. const classIds = hierarchy._classIds;
  256. const parentCounts = hierarchy._parentCounts;
  257. const parentIds = hierarchy._parentIds;
  258. const parentIndexes = hierarchy._parentIndexes;
  259. const instancesLength = classIds.length;
  260. // Ignore instances that have already been visited. This occurs in diamond inheritance situations.
  261. // Use a marker value to indicate that an instance has been visited, which increments with each run.
  262. // This is more efficient than clearing the visited array every time.
  263. const visited = scratchVisited;
  264. visited.length = Math.max(visited.length, instancesLength);
  265. const visitedMarker = ++marker;
  266. const stack = scratchStack;
  267. stack.length = 0;
  268. stack.push(instanceIndex);
  269. while (stack.length > 0) {
  270. instanceIndex = stack.pop();
  271. if (visited[instanceIndex] === visitedMarker) {
  272. // This instance has already been visited, stop traversal
  273. continue;
  274. }
  275. visited[instanceIndex] = visitedMarker;
  276. const result = endConditionCallback(hierarchy, instanceIndex);
  277. if (defined(result)) {
  278. // The end condition was met, stop the traversal and return the result
  279. return result;
  280. }
  281. const parentCount = parentCounts[instanceIndex];
  282. const parentIndex = parentIndexes[instanceIndex];
  283. for (let i = 0; i < parentCount; ++i) {
  284. const parentId = parentIds[parentIndex + i];
  285. // Stop the traversal when the instance has no parent (its parentId equals itself)
  286. // else add the parent to the stack to continue the traversal.
  287. if (parentId !== instanceIndex) {
  288. stack.push(parentId);
  289. }
  290. }
  291. }
  292. }
  293. function traverseHierarchySingleParent(
  294. hierarchy,
  295. instanceIndex,
  296. endConditionCallback
  297. ) {
  298. let hasParent = true;
  299. while (hasParent) {
  300. const result = endConditionCallback(hierarchy, instanceIndex);
  301. if (defined(result)) {
  302. // The end condition was met, stop the traversal and return the result
  303. return result;
  304. }
  305. const parentId = hierarchy._parentIds[instanceIndex];
  306. hasParent = parentId !== instanceIndex;
  307. instanceIndex = parentId;
  308. }
  309. }
  310. function traverseHierarchy(hierarchy, instanceIndex, endConditionCallback) {
  311. // Traverse over the hierarchy and process each instance with the endConditionCallback.
  312. // When the endConditionCallback returns a value, the traversal stops and that value is returned.
  313. const parentCounts = hierarchy._parentCounts;
  314. const parentIds = hierarchy._parentIds;
  315. if (!defined(parentIds)) {
  316. return endConditionCallback(hierarchy, instanceIndex);
  317. } else if (defined(parentCounts)) {
  318. return traverseHierarchyMultipleParents(
  319. hierarchy,
  320. instanceIndex,
  321. endConditionCallback
  322. );
  323. }
  324. return traverseHierarchySingleParent(
  325. hierarchy,
  326. instanceIndex,
  327. endConditionCallback
  328. );
  329. }
  330. /**
  331. * Returns whether the feature has this property.
  332. *
  333. * @param {number} batchId the batch ID of the feature
  334. * @param {string} propertyId The case-sensitive ID of the property.
  335. * @returns {boolean} Whether the feature has this property.
  336. * @private
  337. */
  338. BatchTableHierarchy.prototype.hasProperty = function (batchId, propertyId) {
  339. const result = traverseHierarchy(this, batchId, function (
  340. hierarchy,
  341. instanceIndex
  342. ) {
  343. const classId = hierarchy._classIds[instanceIndex];
  344. const instances = hierarchy._classes[classId].instances;
  345. if (defined(instances[propertyId])) {
  346. return true;
  347. }
  348. });
  349. return defined(result);
  350. };
  351. /**
  352. * Returns whether any feature has this property.
  353. *
  354. * @param {string} propertyId The case-sensitive ID of the property.
  355. * @returns {boolean} Whether any feature has this property.
  356. * @private
  357. */
  358. BatchTableHierarchy.prototype.propertyExists = function (propertyId) {
  359. const classes = this._classes;
  360. const classesLength = classes.length;
  361. for (let i = 0; i < classesLength; ++i) {
  362. const instances = classes[i].instances;
  363. if (defined(instances[propertyId])) {
  364. return true;
  365. }
  366. }
  367. return false;
  368. };
  369. /**
  370. * Returns an array of property IDs.
  371. *
  372. * @param {number} batchId the batch ID of the feature
  373. * @param {number} index The index of the entity.
  374. * @param {string[]} [results] An array into which to store the results.
  375. * @returns {string[]} The property IDs.
  376. * @private
  377. */
  378. BatchTableHierarchy.prototype.getPropertyIds = function (batchId, results) {
  379. results = defined(results) ? results : [];
  380. results.length = 0;
  381. traverseHierarchy(this, batchId, function (hierarchy, instanceIndex) {
  382. const classId = hierarchy._classIds[instanceIndex];
  383. const instances = hierarchy._classes[classId].instances;
  384. for (const name in instances) {
  385. if (instances.hasOwnProperty(name)) {
  386. if (results.indexOf(name) === -1) {
  387. results.push(name);
  388. }
  389. }
  390. }
  391. });
  392. return results;
  393. };
  394. /**
  395. * Returns a copy of the value of the property with the given ID.
  396. *
  397. * @param {number} batchId the batch ID of the feature
  398. * @param {string} propertyId The case-sensitive ID of the property.
  399. * @returns {*} The value of the property or <code>undefined</code> if the feature does not have this property.
  400. * @private
  401. */
  402. BatchTableHierarchy.prototype.getProperty = function (batchId, propertyId) {
  403. return traverseHierarchy(this, batchId, function (hierarchy, instanceIndex) {
  404. const classId = hierarchy._classIds[instanceIndex];
  405. const instanceClass = hierarchy._classes[classId];
  406. const indexInClass = hierarchy._classIndexes[instanceIndex];
  407. const propertyValues = instanceClass.instances[propertyId];
  408. if (defined(propertyValues)) {
  409. if (defined(propertyValues.typedArray)) {
  410. return getBinaryProperty(propertyValues, indexInClass);
  411. }
  412. return clone(propertyValues[indexInClass], true);
  413. }
  414. });
  415. };
  416. function getBinaryProperty(binaryProperty, index) {
  417. const typedArray = binaryProperty.typedArray;
  418. const componentCount = binaryProperty.componentCount;
  419. if (componentCount === 1) {
  420. return typedArray[index];
  421. }
  422. return binaryProperty.type.unpack(typedArray, index * componentCount);
  423. }
  424. /**
  425. * Sets the value of the property with the given ID. Only properties of the
  426. * instance may be set; parent properties may not be set.
  427. *
  428. * @param {number} batchId The batchId of the feature
  429. * @param {string} propertyId The case-sensitive ID of the property.
  430. * @param {*} value The value of the property that will be copied.
  431. * @returns {boolean} <code>true</code> if the property was set, <code>false</code> otherwise.
  432. *
  433. * @exception {DeveloperError} when setting an inherited property
  434. * @private
  435. */
  436. BatchTableHierarchy.prototype.setProperty = function (
  437. batchId,
  438. propertyId,
  439. value
  440. ) {
  441. const result = traverseHierarchy(this, batchId, function (
  442. hierarchy,
  443. instanceIndex
  444. ) {
  445. const classId = hierarchy._classIds[instanceIndex];
  446. const instanceClass = hierarchy._classes[classId];
  447. const indexInClass = hierarchy._classIndexes[instanceIndex];
  448. const propertyValues = instanceClass.instances[propertyId];
  449. if (defined(propertyValues)) {
  450. //>>includeStart('debug', pragmas.debug);
  451. if (instanceIndex !== batchId) {
  452. throw new DeveloperError(
  453. `Inherited property "${propertyId}" is read-only.`
  454. );
  455. }
  456. //>>includeEnd('debug');
  457. if (defined(propertyValues.typedArray)) {
  458. setBinaryProperty(propertyValues, indexInClass, value);
  459. } else {
  460. propertyValues[indexInClass] = clone(value, true);
  461. }
  462. return true;
  463. }
  464. });
  465. return defined(result);
  466. };
  467. function setBinaryProperty(binaryProperty, index, value) {
  468. const typedArray = binaryProperty.typedArray;
  469. const componentCount = binaryProperty.componentCount;
  470. if (componentCount === 1) {
  471. typedArray[index] = value;
  472. } else {
  473. binaryProperty.type.pack(value, typedArray, index * componentCount);
  474. }
  475. }
  476. /**
  477. * Check if a feature belongs to a class with the given name
  478. *
  479. * @param {number} batchId The batch ID of the feature
  480. * @param {string} className The name of the class
  481. * @return {boolean} <code>true</code> if the feature belongs to the class given by className, or <code>false</code> otherwise
  482. * @private
  483. */
  484. BatchTableHierarchy.prototype.isClass = function (batchId, className) {
  485. // PERFORMANCE_IDEA : cache results in the ancestor classes to speed up this check if this area becomes a hotspot
  486. // PERFORMANCE_IDEA : treat class names as integers for faster comparisons
  487. const result = traverseHierarchy(this, batchId, function (
  488. hierarchy,
  489. instanceIndex
  490. ) {
  491. const classId = hierarchy._classIds[instanceIndex];
  492. const instanceClass = hierarchy._classes[classId];
  493. if (instanceClass.name === className) {
  494. return true;
  495. }
  496. });
  497. return defined(result);
  498. };
  499. /**
  500. * Get the name of the class a given feature belongs to
  501. *
  502. * @param {number} batchId The batch ID of the feature
  503. * @return {string} The name of the class this feature belongs to
  504. */
  505. BatchTableHierarchy.prototype.getClassName = function (batchId) {
  506. const classId = this._classIds[batchId];
  507. const instanceClass = this._classes[classId];
  508. return instanceClass.name;
  509. };
  510. export default BatchTableHierarchy;