import Check from "../Core/Check.js"; import clone from "../Core/clone.js"; import ComponentDatatype from "../Core/ComponentDatatype.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import FeatureDetection from "../Core/FeatureDetection.js"; import getStringFromTypedArray from "../Core/getStringFromTypedArray.js"; import oneTimeWarning from "../Core/oneTimeWarning.js"; import MetadataComponentType from "./MetadataComponentType.js"; import MetadataClassProperty from "./MetadataClassProperty.js"; import MetadataType from "./MetadataType.js"; /** * A binary property in a {@MetadataTable} *
* For 3D Tiles Next details, see the {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_metadata|3DTILES_metadata Extension} * for 3D Tiles, as well as the {@link https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata|EXT_structural_metadata Extension} * for glTF. For the legacy glTF extension, see {@link https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_feature_metadata|EXT_feature_metadata Extension} *
* * @param {object} options Object with the following properties: * @param {number} options.count The number of elements in each property array. * @param {object} options.property The property JSON object. * @param {MetadataClassProperty} options.classProperty The class property. * @param {Objectundefined
if the property values are not stored in a typed array.
*
* @private
*/
MetadataTableProperty.prototype.getTypedArray = function () {
// Note: depending on the class definition some properties are unpacked to
// JS arrays when first accessed and values will be undefined. Generally not
// a concern for fixed-length arrays of numbers.
if (defined(this._values)) {
return this._values.typedArray;
}
return undefined;
};
function flatten(values) {
if (!Array.isArray(values)) {
return values;
}
const result = [];
for (let i = 0; i < values.length; i++) {
const value = values[i];
if (Array.isArray(value)) {
result.push.apply(result, value);
} else {
result.push(value);
}
}
return result;
}
function checkIndex(table, index) {
const count = table._count;
if (!defined(index) || index < 0 || index >= count) {
const maximumIndex = count - 1;
throw new DeveloperError(
`index is required and between zero and count - 1. Actual value: ${maximumIndex}`
);
}
}
function get(property, index) {
if (requiresUnpackForGet(property)) {
unpackProperty(property);
}
const classProperty = property._classProperty;
const isArray = classProperty.isArray;
const type = classProperty.type;
const componentCount = MetadataType.getComponentCount(type);
if (defined(property._unpackedValues)) {
const value = property._unpackedValues[index];
if (isArray) {
return clone(value, true);
}
return value;
}
// handle single values
if (!isArray && componentCount === 1) {
return property._getValue(index);
}
return getArrayValues(property, classProperty, index);
}
function getArrayValues(property, classProperty, index) {
let offset;
let length;
if (classProperty.isVariableLengthArray) {
offset = property._arrayOffsets.get(index);
length = property._arrayOffsets.get(index + 1) - offset;
// for vectors and matrices, the offset and length need to be multiplied
// by the component count
const componentCount = MetadataType.getComponentCount(classProperty.type);
offset *= componentCount;
length *= componentCount;
} else {
const arrayLength = defaultValue(classProperty.arrayLength, 1);
const componentCount = arrayLength * property._vectorComponentCount;
offset = index * componentCount;
length = componentCount;
}
const values = new Array(length);
for (let i = 0; i < length; i++) {
values[i] = property._getValue(offset + i);
}
return values;
}
function set(property, index, value) {
if (requiresUnpackForSet(property, index, value)) {
unpackProperty(property);
}
const classProperty = property._classProperty;
const isArray = classProperty.isArray;
const type = classProperty.type;
const componentCount = MetadataType.getComponentCount(type);
if (defined(property._unpackedValues)) {
if (classProperty.isArray) {
value = clone(value, true);
}
property._unpackedValues[index] = value;
return;
}
// Values are unpacked if the length of a variable-size array changes or the
// property has strings. No need to handle these cases below.
// Handle single values
if (!isArray && componentCount === 1) {
property._setValue(index, value);
return;
}
let offset;
let length;
if (classProperty.isVariableLengthArray) {
offset = property._arrayOffsets.get(index);
length = property._arrayOffsets.get(index + 1) - offset;
} else {
const arrayLength = defaultValue(classProperty.arrayLength, 1);
const componentCount = arrayLength * property._vectorComponentCount;
offset = index * componentCount;
length = componentCount;
}
for (let i = 0; i < length; ++i) {
property._setValue(offset + i, value[i]);
}
}
function getString(index, values, stringOffsets) {
const stringByteOffset = stringOffsets.get(index);
const stringByteLength = stringOffsets.get(index + 1) - stringByteOffset;
return getStringFromTypedArray(
values.typedArray,
stringByteOffset,
stringByteLength
);
}
function getBoolean(index, values) {
// byteIndex is floor(index / 8)
const byteIndex = index >> 3;
const bitIndex = index % 8;
return ((values.typedArray[byteIndex] >> bitIndex) & 1) === 1;
}
function setBoolean(index, values, value) {
// byteIndex is floor(index / 8)
const byteIndex = index >> 3;
const bitIndex = index % 8;
if (value) {
values.typedArray[byteIndex] |= 1 << bitIndex;
} else {
values.typedArray[byteIndex] &= ~(1 << bitIndex);
}
}
function getInt64NumberFallback(index, values) {
const dataView = values.dataView;
const byteOffset = index * 8;
let value = 0;
const isNegative = (dataView.getUint8(byteOffset + 7) & 0x80) > 0;
let carrying = true;
for (let i = 0; i < 8; ++i) {
let byte = dataView.getUint8(byteOffset + i);
if (isNegative) {
if (carrying) {
if (byte !== 0x00) {
byte = ~(byte - 1) & 0xff;
carrying = false;
}
} else {
byte = ~byte & 0xff;
}
}
value += byte * Math.pow(256, i);
}
if (isNegative) {
value = -value;
}
return value;
}
function getInt64BigIntFallback(index, values) {
const dataView = values.dataView;
const byteOffset = index * 8;
// eslint-disable-next-line no-undef
let value = BigInt(0);
const isNegative = (dataView.getUint8(byteOffset + 7) & 0x80) > 0;
let carrying = true;
for (let i = 0; i < 8; ++i) {
let byte = dataView.getUint8(byteOffset + i);
if (isNegative) {
if (carrying) {
if (byte !== 0x00) {
byte = ~(byte - 1) & 0xff;
carrying = false;
}
} else {
byte = ~byte & 0xff;
}
}
value += BigInt(byte) * (BigInt(1) << BigInt(i * 8)); // eslint-disable-line
}
if (isNegative) {
value = -value;
}
return value;
}
function getUint64NumberFallback(index, values) {
const dataView = values.dataView;
const byteOffset = index * 8;
// Split 64-bit number into two 32-bit (4-byte) parts
const left = dataView.getUint32(byteOffset, true);
const right = dataView.getUint32(byteOffset + 4, true);
// Combine the two 32-bit values
const value = left + 4294967296 * right;
return value;
}
function getUint64BigIntFallback(index, values) {
const dataView = values.dataView;
const byteOffset = index * 8;
// Split 64-bit number into two 32-bit (4-byte) parts
// eslint-disable-next-line no-undef
const left = BigInt(dataView.getUint32(byteOffset, true));
// eslint-disable-next-line no-undef
const right = BigInt(dataView.getUint32(byteOffset + 4, true));
// Combine the two 32-bit values
// eslint-disable-next-line no-undef
const value = left + BigInt(4294967296) * right;
return value;
}
function getComponentDatatype(componentType) {
switch (componentType) {
case MetadataComponentType.INT8:
return ComponentDatatype.BYTE;
case MetadataComponentType.UINT8:
return ComponentDatatype.UNSIGNED_BYTE;
case MetadataComponentType.INT16:
return ComponentDatatype.SHORT;
case MetadataComponentType.UINT16:
return ComponentDatatype.UNSIGNED_SHORT;
case MetadataComponentType.INT32:
return ComponentDatatype.INT;
case MetadataComponentType.UINT32:
return ComponentDatatype.UNSIGNED_INT;
case MetadataComponentType.FLOAT32:
return ComponentDatatype.FLOAT;
case MetadataComponentType.FLOAT64:
return ComponentDatatype.DOUBLE;
}
}
function requiresUnpackForGet(property) {
if (defined(property._unpackedValues)) {
return false;
}
const classProperty = property._classProperty;
const type = classProperty.type;
const valueType = classProperty.valueType;
if (type === MetadataType.STRING) {
// Unpack since UTF-8 decoding is expensive
return true;
}
if (
valueType === MetadataComponentType.INT64 &&
!FeatureDetection.supportsBigInt64Array()
) {
// Unpack since the fallback INT64 getters are expensive
return true;
}
if (
valueType === MetadataComponentType.UINT64 &&
!FeatureDetection.supportsBigUint64Array()
) {
// Unpack since the fallback UINT64 getters are expensive
return true;
}
return false;
}
function requiresUnpackForSet(property, index, value) {
if (requiresUnpackForGet(property)) {
return true;
}
const arrayOffsets = property._arrayOffsets;
if (defined(arrayOffsets)) {
// Unpacking is required if a variable-size array changes length since it
// would be expensive to repack the binary data
const oldLength = arrayOffsets.get(index + 1) - arrayOffsets.get(index);
const newLength = value.length;
if (oldLength !== newLength) {
return true;
}
}
return false;
}
function unpackProperty(property) {
property._unpackedValues = unpackValues(property);
// Free memory
property._arrayOffsets = undefined;
property._stringOffsets = undefined;
property._values = undefined;
}
function unpackValues(property) {
const count = property._count;
const unpackedValues = new Array(count);
const classProperty = property._classProperty;
const isArray = classProperty.isArray;
const type = classProperty.type;
const componentCount = MetadataType.getComponentCount(type);
// Handle single values
if (!isArray && componentCount === 1) {
for (let i = 0; i < count; ++i) {
unpackedValues[i] = property._getValue(i);
}
return unpackedValues;
}
for (let i = 0; i < count; i++) {
unpackedValues[i] = getArrayValues(property, classProperty, i);
}
return unpackedValues;
}
function applyValueTransform(property, value) {
const classProperty = property._classProperty;
const isVariableLengthArray = classProperty.isVariableLengthArray;
if (!property._hasValueTransform || isVariableLengthArray) {
return value;
}
return MetadataClassProperty.valueTransformInPlace(
value,
property._offset,
property._scale,
MetadataComponentType.applyValueTransform
);
}
function unapplyValueTransform(property, value) {
const classProperty = property._classProperty;
const isVariableLengthArray = classProperty.isVariableLengthArray;
if (!property._hasValueTransform || isVariableLengthArray) {
return value;
}
return MetadataClassProperty.valueTransformInPlace(
value,
property._offset,
property._scale,
MetadataComponentType.unapplyValueTransform
);
}
function BufferView(bufferView, componentType, length) {
const that = this;
let typedArray;
let getFunction;
let setFunction;
if (componentType === MetadataComponentType.INT64) {
if (!FeatureDetection.supportsBigInt()) {
oneTimeWarning(
"INT64 type is not fully supported on this platform. Values greater than 2^53 - 1 or less than -(2^53 - 1) may lose precision when read."
);
typedArray = new Uint8Array(
bufferView.buffer,
bufferView.byteOffset,
length * 8
);
getFunction = function (index) {
return getInt64NumberFallback(index, that);
};
} else if (!FeatureDetection.supportsBigInt64Array()) {
typedArray = new Uint8Array(
bufferView.buffer,
bufferView.byteOffset,
length * 8
);
getFunction = function (index) {
return getInt64BigIntFallback(index, that);
};
} else {
// eslint-disable-next-line
typedArray = new BigInt64Array(
bufferView.buffer,
bufferView.byteOffset,
length
);
setFunction = function (index, value) {
// Convert the number to a BigInt before setting the value in the typed array
that.typedArray[index] = BigInt(value); // eslint-disable-line
};
}
} else if (componentType === MetadataComponentType.UINT64) {
if (!FeatureDetection.supportsBigInt()) {
oneTimeWarning(
"UINT64 type is not fully supported on this platform. Values greater than 2^53 - 1 may lose precision when read."
);
typedArray = new Uint8Array(
bufferView.buffer,
bufferView.byteOffset,
length * 8
);
getFunction = function (index) {
return getUint64NumberFallback(index, that);
};
} else if (!FeatureDetection.supportsBigUint64Array()) {
typedArray = new Uint8Array(
bufferView.buffer,
bufferView.byteOffset,
length * 8
);
getFunction = function (index) {
return getUint64BigIntFallback(index, that);
};
} else {
// eslint-disable-next-line
typedArray = new BigUint64Array(
bufferView.buffer,
bufferView.byteOffset,
length
);
setFunction = function (index, value) {
// Convert the number to a BigInt before setting the value in the typed array
that.typedArray[index] = BigInt(value); // eslint-disable-line
};
}
} else {
const componentDatatype = getComponentDatatype(componentType);
typedArray = ComponentDatatype.createArrayBufferView(
componentDatatype,
bufferView.buffer,
bufferView.byteOffset,
length
);
setFunction = function (index, value) {
that.typedArray[index] = value;
};
}
if (!defined(getFunction)) {
getFunction = function (index) {
return that.typedArray[index];
};
}
this.typedArray = typedArray;
this.dataView = new DataView(typedArray.buffer, typedArray.byteOffset);
this.get = getFunction;
this.set = setFunction;
// for unit testing
this._componentType = componentType;
}
export default MetadataTableProperty;