Add existing to tracked
This commit is contained in:
+504
@@ -0,0 +1,504 @@
|
||||
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 DeveloperError from "../Core/DeveloperError.js";
|
||||
import getBinaryAccessor from "./getBinaryAccessor.js";
|
||||
import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.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 =
|
||||
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 =
|
||||
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 =
|
||||
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 = Cesium3DTileBatchTable.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 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;
|
||||
Reference in New Issue
Block a user