BatchTableHierarchy.js 17 KB

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