123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- import AttributeType from "./AttributeType.js";
- import Check from "../Core/Check.js";
- import clone from "../Core/clone.js";
- import combine from "../Core/combine.js";
- import ComponentDatatype from "../Core/ComponentDatatype.js";
- import defined from "../Core/defined.js";
- import defaultValue from "../Core/defaultValue.js";
- import DeveloperError from "../Core/DeveloperError.js";
- import getBinaryAccessor from "./getBinaryAccessor.js";
- import RuntimeError from "../Core/RuntimeError.js";
- /**
- * Object for handling the <code>3DTILES_batch_table_hierarchy</code> extension
- *
- * @param {object} options Object with the following properties:
- * @param {object} options.extension The <code>3DTILES_batch_table_hierarchy</code> extension object.
- * @param {Uint8Array} [options.binaryBody] The binary body of the batch table
- *
- * @alias BatchTableHierarchy
- * @constructor
- *
- * @private
- */
- function BatchTableHierarchy(options) {
- this._classes = undefined;
- this._classIds = undefined;
- this._classIndexes = undefined;
- this._parentCounts = undefined;
- this._parentIndexes = undefined;
- this._parentIds = undefined;
- // Total memory used by the typed arrays
- this._byteLength = 0;
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.object("options.extension", options.extension);
- //>>includeEnd('debug');
- initialize(this, options.extension, options.binaryBody);
- //>>includeStart('debug', pragmas.debug);
- validateHierarchy(this);
- //>>includeEnd('debug');
- }
- Object.defineProperties(BatchTableHierarchy.prototype, {
- byteLength: {
- get: function () {
- return this._byteLength;
- },
- },
- });
- /**
- * Parse the batch table hierarchy from the
- * <code>3DTILES_batch_table_hierarchy</code> extension.
- *
- * @param {BatchTableHierarchy} hierarchy The hierarchy instance
- * @param {object} hierarchyJson The JSON of the extension
- * @param {Uint8Array} [binaryBody] The binary body of the batch table for accessing binary properties
- * @private
- */
- function initialize(hierarchy, hierarchyJson, binaryBody) {
- let i;
- let classId;
- let binaryAccessor;
- const instancesLength = hierarchyJson.instancesLength;
- const classes = hierarchyJson.classes;
- let classIds = hierarchyJson.classIds;
- let parentCounts = hierarchyJson.parentCounts;
- let parentIds = hierarchyJson.parentIds;
- let parentIdsLength = instancesLength;
- let byteLength = 0;
- if (defined(classIds.byteOffset)) {
- classIds.componentType = defaultValue(
- classIds.componentType,
- ComponentDatatype.UNSIGNED_SHORT
- );
- classIds.type = AttributeType.SCALAR;
- binaryAccessor = getBinaryAccessor(classIds);
- classIds = binaryAccessor.createArrayBufferView(
- binaryBody.buffer,
- binaryBody.byteOffset + classIds.byteOffset,
- instancesLength
- );
- byteLength += classIds.byteLength;
- }
- let parentIndexes;
- if (defined(parentCounts)) {
- if (defined(parentCounts.byteOffset)) {
- parentCounts.componentType = defaultValue(
- parentCounts.componentType,
- ComponentDatatype.UNSIGNED_SHORT
- );
- parentCounts.type = AttributeType.SCALAR;
- binaryAccessor = getBinaryAccessor(parentCounts);
- parentCounts = binaryAccessor.createArrayBufferView(
- binaryBody.buffer,
- binaryBody.byteOffset + parentCounts.byteOffset,
- instancesLength
- );
- byteLength += parentCounts.byteLength;
- }
- parentIndexes = new Uint16Array(instancesLength);
- parentIdsLength = 0;
- for (i = 0; i < instancesLength; ++i) {
- parentIndexes[i] = parentIdsLength;
- parentIdsLength += parentCounts[i];
- }
- byteLength += parentIndexes.byteLength;
- }
- if (defined(parentIds) && defined(parentIds.byteOffset)) {
- parentIds.componentType = defaultValue(
- parentIds.componentType,
- ComponentDatatype.UNSIGNED_SHORT
- );
- parentIds.type = AttributeType.SCALAR;
- binaryAccessor = getBinaryAccessor(parentIds);
- parentIds = binaryAccessor.createArrayBufferView(
- binaryBody.buffer,
- binaryBody.byteOffset + parentIds.byteOffset,
- parentIdsLength
- );
- byteLength += parentIds.byteLength;
- }
- const classesLength = classes.length;
- for (i = 0; i < classesLength; ++i) {
- const classInstancesLength = classes[i].length;
- const properties = classes[i].instances;
- const binaryProperties = getBinaryProperties(
- classInstancesLength,
- properties,
- binaryBody
- );
- byteLength += countBinaryPropertyMemory(binaryProperties);
- classes[i].instances = combine(binaryProperties, properties);
- }
- const classCounts = new Array(classesLength).fill(0);
- const classIndexes = new Uint16Array(instancesLength);
- for (i = 0; i < instancesLength; ++i) {
- classId = classIds[i];
- classIndexes[i] = classCounts[classId];
- ++classCounts[classId];
- }
- byteLength += classIndexes.byteLength;
- hierarchy._classes = classes;
- hierarchy._classIds = classIds;
- hierarchy._classIndexes = classIndexes;
- hierarchy._parentCounts = parentCounts;
- hierarchy._parentIndexes = parentIndexes;
- hierarchy._parentIds = parentIds;
- hierarchy._byteLength = byteLength;
- }
- function getBinaryProperties(featuresLength, properties, binaryBody) {
- let binaryProperties;
- for (const name in properties) {
- if (properties.hasOwnProperty(name)) {
- const property = properties[name];
- const byteOffset = property.byteOffset;
- if (defined(byteOffset)) {
- // This is a binary property
- const componentType = property.componentType;
- const type = property.type;
- if (!defined(componentType)) {
- throw new RuntimeError("componentType is required.");
- }
- if (!defined(type)) {
- throw new RuntimeError("type is required.");
- }
- if (!defined(binaryBody)) {
- throw new RuntimeError(
- `Property ${name} requires a batch table binary.`
- );
- }
- const binaryAccessor = getBinaryAccessor(property);
- const componentCount = binaryAccessor.componentsPerAttribute;
- const classType = binaryAccessor.classType;
- const typedArray = binaryAccessor.createArrayBufferView(
- binaryBody.buffer,
- binaryBody.byteOffset + byteOffset,
- featuresLength
- );
- if (!defined(binaryProperties)) {
- binaryProperties = {};
- }
- // Store any information needed to access the binary data, including the typed array,
- // componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4).
- binaryProperties[name] = {
- typedArray: typedArray,
- componentCount: componentCount,
- type: classType,
- };
- }
- }
- }
- return binaryProperties;
- }
- function countBinaryPropertyMemory(binaryProperties) {
- let byteLength = 0;
- for (const name in binaryProperties) {
- if (binaryProperties.hasOwnProperty(name)) {
- byteLength += binaryProperties[name].typedArray.byteLength;
- }
- }
- return byteLength;
- }
- //>>includeStart('debug', pragmas.debug);
- const scratchValidateStack = [];
- function validateHierarchy(hierarchy) {
- const stack = scratchValidateStack;
- stack.length = 0;
- const classIds = hierarchy._classIds;
- const instancesLength = classIds.length;
- for (let i = 0; i < instancesLength; ++i) {
- validateInstance(hierarchy, i, stack);
- }
- }
- function validateInstance(hierarchy, instanceIndex, stack) {
- const parentCounts = hierarchy._parentCounts;
- const parentIds = hierarchy._parentIds;
- const parentIndexes = hierarchy._parentIndexes;
- const classIds = hierarchy._classIds;
- const instancesLength = classIds.length;
- if (!defined(parentIds)) {
- // No need to validate if there are no parents
- return;
- }
- if (instanceIndex >= instancesLength) {
- throw new DeveloperError(
- `Parent index ${instanceIndex} exceeds the total number of instances: ${instancesLength}`
- );
- }
- if (stack.indexOf(instanceIndex) > -1) {
- throw new DeveloperError(
- "Circular dependency detected in the batch table hierarchy."
- );
- }
- stack.push(instanceIndex);
- const parentCount = defined(parentCounts) ? parentCounts[instanceIndex] : 1;
- const parentIndex = defined(parentCounts)
- ? parentIndexes[instanceIndex]
- : instanceIndex;
- for (let i = 0; i < parentCount; ++i) {
- const parentId = parentIds[parentIndex + i];
- // Stop the traversal when the instance has no parent (its parentId equals itself), else continue the traversal.
- if (parentId !== instanceIndex) {
- validateInstance(hierarchy, parentId, stack);
- }
- }
- stack.pop(instanceIndex);
- }
- //>>includeEnd('debug');
- // The size of this array equals the maximum instance count among all loaded tiles, which has the potential to be large.
- const scratchVisited = [];
- const scratchStack = [];
- let marker = 0;
- function traverseHierarchyMultipleParents(
- hierarchy,
- instanceIndex,
- endConditionCallback
- ) {
- const classIds = hierarchy._classIds;
- const parentCounts = hierarchy._parentCounts;
- const parentIds = hierarchy._parentIds;
- const parentIndexes = hierarchy._parentIndexes;
- const instancesLength = classIds.length;
- // Ignore instances that have already been visited. This occurs in diamond inheritance situations.
- // Use a marker value to indicate that an instance has been visited, which increments with each run.
- // This is more efficient than clearing the visited array every time.
- const visited = scratchVisited;
- visited.length = Math.max(visited.length, instancesLength);
- const visitedMarker = ++marker;
- const stack = scratchStack;
- stack.length = 0;
- stack.push(instanceIndex);
- while (stack.length > 0) {
- instanceIndex = stack.pop();
- if (visited[instanceIndex] === visitedMarker) {
- // This instance has already been visited, stop traversal
- continue;
- }
- visited[instanceIndex] = visitedMarker;
- const result = endConditionCallback(hierarchy, instanceIndex);
- if (defined(result)) {
- // The end condition was met, stop the traversal and return the result
- return result;
- }
- const parentCount = parentCounts[instanceIndex];
- const parentIndex = parentIndexes[instanceIndex];
- for (let i = 0; i < parentCount; ++i) {
- const parentId = parentIds[parentIndex + i];
- // Stop the traversal when the instance has no parent (its parentId equals itself)
- // else add the parent to the stack to continue the traversal.
- if (parentId !== instanceIndex) {
- stack.push(parentId);
- }
- }
- }
- }
- function traverseHierarchySingleParent(
- hierarchy,
- instanceIndex,
- endConditionCallback
- ) {
- let hasParent = true;
- while (hasParent) {
- const result = endConditionCallback(hierarchy, instanceIndex);
- if (defined(result)) {
- // The end condition was met, stop the traversal and return the result
- return result;
- }
- const parentId = hierarchy._parentIds[instanceIndex];
- hasParent = parentId !== instanceIndex;
- instanceIndex = parentId;
- }
- }
- function traverseHierarchy(hierarchy, instanceIndex, endConditionCallback) {
- // Traverse over the hierarchy and process each instance with the endConditionCallback.
- // When the endConditionCallback returns a value, the traversal stops and that value is returned.
- const parentCounts = hierarchy._parentCounts;
- const parentIds = hierarchy._parentIds;
- if (!defined(parentIds)) {
- return endConditionCallback(hierarchy, instanceIndex);
- } else if (defined(parentCounts)) {
- return traverseHierarchyMultipleParents(
- hierarchy,
- instanceIndex,
- endConditionCallback
- );
- }
- return traverseHierarchySingleParent(
- hierarchy,
- instanceIndex,
- endConditionCallback
- );
- }
- /**
- * Returns whether the feature has this property.
- *
- * @param {number} batchId the batch ID of the feature
- * @param {string} propertyId The case-sensitive ID of the property.
- * @returns {boolean} Whether the feature has this property.
- * @private
- */
- BatchTableHierarchy.prototype.hasProperty = function (batchId, propertyId) {
- const result = traverseHierarchy(this, batchId, function (
- hierarchy,
- instanceIndex
- ) {
- const classId = hierarchy._classIds[instanceIndex];
- const instances = hierarchy._classes[classId].instances;
- if (defined(instances[propertyId])) {
- return true;
- }
- });
- return defined(result);
- };
- /**
- * Returns whether any feature has this property.
- *
- * @param {string} propertyId The case-sensitive ID of the property.
- * @returns {boolean} Whether any feature has this property.
- * @private
- */
- BatchTableHierarchy.prototype.propertyExists = function (propertyId) {
- const classes = this._classes;
- const classesLength = classes.length;
- for (let i = 0; i < classesLength; ++i) {
- const instances = classes[i].instances;
- if (defined(instances[propertyId])) {
- return true;
- }
- }
- return false;
- };
- /**
- * Returns an array of property IDs.
- *
- * @param {number} batchId the batch ID of the feature
- * @param {number} index The index of the entity.
- * @param {string[]} [results] An array into which to store the results.
- * @returns {string[]} The property IDs.
- * @private
- */
- BatchTableHierarchy.prototype.getPropertyIds = function (batchId, results) {
- results = defined(results) ? results : [];
- results.length = 0;
- traverseHierarchy(this, batchId, function (hierarchy, instanceIndex) {
- const classId = hierarchy._classIds[instanceIndex];
- const instances = hierarchy._classes[classId].instances;
- for (const name in instances) {
- if (instances.hasOwnProperty(name)) {
- if (results.indexOf(name) === -1) {
- results.push(name);
- }
- }
- }
- });
- return results;
- };
- /**
- * Returns a copy of the value of the property with the given ID.
- *
- * @param {number} batchId the batch ID of the feature
- * @param {string} propertyId The case-sensitive ID of the property.
- * @returns {*} The value of the property or <code>undefined</code> if the feature does not have this property.
- * @private
- */
- BatchTableHierarchy.prototype.getProperty = function (batchId, propertyId) {
- return traverseHierarchy(this, batchId, function (hierarchy, instanceIndex) {
- const classId = hierarchy._classIds[instanceIndex];
- const instanceClass = hierarchy._classes[classId];
- const indexInClass = hierarchy._classIndexes[instanceIndex];
- const propertyValues = instanceClass.instances[propertyId];
- if (defined(propertyValues)) {
- if (defined(propertyValues.typedArray)) {
- return getBinaryProperty(propertyValues, indexInClass);
- }
- return clone(propertyValues[indexInClass], true);
- }
- });
- };
- function getBinaryProperty(binaryProperty, index) {
- const typedArray = binaryProperty.typedArray;
- const componentCount = binaryProperty.componentCount;
- if (componentCount === 1) {
- return typedArray[index];
- }
- return binaryProperty.type.unpack(typedArray, index * componentCount);
- }
- /**
- * Sets the value of the property with the given ID. Only properties of the
- * instance may be set; parent properties may not be set.
- *
- * @param {number} batchId The batchId of the feature
- * @param {string} propertyId The case-sensitive ID of the property.
- * @param {*} value The value of the property that will be copied.
- * @returns {boolean} <code>true</code> if the property was set, <code>false</code> otherwise.
- *
- * @exception {DeveloperError} when setting an inherited property
- * @private
- */
- BatchTableHierarchy.prototype.setProperty = function (
- batchId,
- propertyId,
- value
- ) {
- const result = traverseHierarchy(this, batchId, function (
- hierarchy,
- instanceIndex
- ) {
- const classId = hierarchy._classIds[instanceIndex];
- const instanceClass = hierarchy._classes[classId];
- const indexInClass = hierarchy._classIndexes[instanceIndex];
- const propertyValues = instanceClass.instances[propertyId];
- if (defined(propertyValues)) {
- //>>includeStart('debug', pragmas.debug);
- if (instanceIndex !== batchId) {
- throw new DeveloperError(
- `Inherited property "${propertyId}" is read-only.`
- );
- }
- //>>includeEnd('debug');
- if (defined(propertyValues.typedArray)) {
- setBinaryProperty(propertyValues, indexInClass, value);
- } else {
- propertyValues[indexInClass] = clone(value, true);
- }
- return true;
- }
- });
- return defined(result);
- };
- function setBinaryProperty(binaryProperty, index, value) {
- const typedArray = binaryProperty.typedArray;
- const componentCount = binaryProperty.componentCount;
- if (componentCount === 1) {
- typedArray[index] = value;
- } else {
- binaryProperty.type.pack(value, typedArray, index * componentCount);
- }
- }
- /**
- * Check if a feature belongs to a class with the given name
- *
- * @param {number} batchId The batch ID of the feature
- * @param {string} className The name of the class
- * @return {boolean} <code>true</code> if the feature belongs to the class given by className, or <code>false</code> otherwise
- * @private
- */
- BatchTableHierarchy.prototype.isClass = function (batchId, className) {
- // PERFORMANCE_IDEA : cache results in the ancestor classes to speed up this check if this area becomes a hotspot
- // PERFORMANCE_IDEA : treat class names as integers for faster comparisons
- const result = traverseHierarchy(this, batchId, function (
- hierarchy,
- instanceIndex
- ) {
- const classId = hierarchy._classIds[instanceIndex];
- const instanceClass = hierarchy._classes[classId];
- if (instanceClass.name === className) {
- return true;
- }
- });
- return defined(result);
- };
- /**
- * Get the name of the class a given feature belongs to
- *
- * @param {number} batchId The batch ID of the feature
- * @return {string} The name of the class this feature belongs to
- */
- BatchTableHierarchy.prototype.getClassName = function (batchId) {
- const classId = this._classIds[batchId];
- const instanceClass = this._classes[classId];
- return instanceClass.name;
- };
- export default BatchTableHierarchy;
|