| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 | import arrayFill from "../Core/arrayFill.js";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 */export default function BatchTableHierarchy(options) {  this._classes = undefined;  this._classIds = undefined;  this._classIndexes = undefined;  this._parentCounts = undefined;  this._parentIndexes = undefined;  this._parentIds = undefined;  //>>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');}/** * 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;  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    );  }  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      );    }    parentIndexes = new Uint16Array(instancesLength);    parentIdsLength = 0;    for (i = 0; i < instancesLength; ++i) {      parentIndexes[i] = parentIdsLength;      parentIdsLength += parentCounts[i];    }  }  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    );  }  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    );    classes[i].instances = combine(binaryProperties, properties);  }  const classCounts = arrayFill(new Array(classesLength), 0);  const classIndexes = new Uint16Array(instancesLength);  for (i = 0; i < instancesLength; ++i) {    classId = classIds[i];    classIndexes[i] = classCounts[classId];    ++classCounts[classId];  }  hierarchy._classes = classes;  hierarchy._classIds = classIds;  hierarchy._classIndexes = classIndexes;  hierarchy._parentCounts = parentCounts;  hierarchy._parentIndexes = parentIndexes;  hierarchy._parentIds = parentIds;}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;}//>>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;};
 |