@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
397 lines (384 loc) • 13.8 kB
JavaScript
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import defined from "../Core/defined.js";
import Matrix2 from "../Core/Matrix2.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import RuntimeError from "../Core/RuntimeError.js";
import MetadataClassProperty from "./MetadataClassProperty.js";
import MetadataComponentType from "./MetadataComponentType.js";
import MetadataType from "./MetadataType.js";
/**
* Utility functions for metadata picking.
*
* These are used by the `Picking.pickMetadata` function to decode
* the metadata values that have been read from the frame buffer
* into the actual metadata values, according to the structure
* defined by the `MetadataClassProperty`.
*
* @private
*/
const MetadataPicking = {};
/**
* Returns the value at the specified index of the given data view,
* interpreting the data to have the given component type.
*
* @param {MetadataComponentType} componentType The `MetadataComponentType`
* @param {DataView} dataView The data view
* @param {number} index The index (byte offset)
* @returns {number|bigint|undefined} The value
* @throws RuntimeError If the given component type is not a valid
* `MetadataComponentType`
* @throws RangeError If reading the data from the given data view would
* cause an out-of-bounds access
*
* @private
*/
MetadataPicking.decodeRawMetadataValue = function (
componentType,
dataView,
index,
) {
switch (componentType) {
case MetadataComponentType.INT8:
return dataView.getInt8(index);
case MetadataComponentType.UINT8:
return dataView.getUint8(index);
case MetadataComponentType.INT16:
return dataView.getInt16(index, true);
case MetadataComponentType.UINT16:
return dataView.getUint16(index, true);
case MetadataComponentType.INT32:
return dataView.getInt32(index, true);
case MetadataComponentType.UINT32:
return dataView.getUint32(index, true);
case MetadataComponentType.INT64:
return dataView.getBigInt64(index, true);
case MetadataComponentType.UINT64:
return dataView.getBigUint64(index, true);
case MetadataComponentType.FLOAT32:
return dataView.getFloat32(index, true);
case MetadataComponentType.FLOAT64:
return dataView.getFloat64(index, true);
}
throw new RuntimeError(`Invalid component type: ${componentType}`);
};
/**
* Decodes one component of a metadata value with the given property type
* from the given data view.
*
* This will decode one component (e.g. one entry of a SCALAR array,
* or one component of a VEC2 element).
*
* This will apply normalization to the raw component value if the given
* class property is 'normalized'.
*
* @param {MetadataClassProperty} classProperty The class property
* @param {DataView} dataView The data view containing the raw metadata values
* @param {number} dataViewOffset The byte offset within the data view from
* which the component should be read
* @returns {number|bigint|undefined} The metadata value component
* @throws RuntimeError If the component of the given property is not
* a valid `MetadataComponentType`
* @throws RangeError If reading the data from the given data view would
* cause an out-of-bounds access
*/
MetadataPicking.decodeRawMetadataValueComponent = function (
classProperty,
dataView,
dataViewOffset,
) {
const componentType = classProperty.componentType;
const component = MetadataPicking.decodeRawMetadataValue(
componentType,
dataView,
dataViewOffset,
);
if (classProperty.normalized) {
return MetadataComponentType.normalize(component, componentType);
}
return component;
};
/**
* Decodes one element of a metadata value with the given property type
* from the given data view.
*
* When the given class property is vector- or matrix typed, then the
* result will be an array, with a length that corresponds to the
* number of vector- or matrix components.
*
* Otherwise, it will be a single value.
*
* In any case, the return value will be the "raw" value, which does
* take into account normalization, but does NOT take into account
* default/noData value handling.
*
* @param {MetadataClassProperty} classProperty The metadata class property
* @param {DataView} dataView The data view containing the raw metadata values
* @param {number} elementIndex The index of the element. This is the index
* inside the array for array-typed properties, and 0 for non-array types.
* @returns {number|number[]|bigint|bigint[]|undefined} The decoded metadata value element
* @throws RuntimeError If the component of the given property is not
* a valid `MetadataComponentType`
* @throws RangeError If reading the data from the given data view would
* cause an out-of-bounds access
*
*/
MetadataPicking.decodeRawMetadataValueElement = function (
classProperty,
dataView,
elementIndex,
) {
const componentType = classProperty.componentType;
const componentSizeInBytes =
MetadataComponentType.getSizeInBytes(componentType);
const type = classProperty.type;
const componentCount = MetadataType.getComponentCount(type);
const elementSizeInBytes = componentSizeInBytes * componentCount;
if (componentCount > 1) {
const result = Array(componentCount);
for (let i = 0; i < componentCount; i++) {
const offset =
elementIndex * elementSizeInBytes + i * componentSizeInBytes;
const component = MetadataPicking.decodeRawMetadataValueComponent(
classProperty,
dataView,
offset,
);
result[i] = component;
}
return result;
}
const offset = elementIndex * elementSizeInBytes;
const result = MetadataPicking.decodeRawMetadataValueComponent(
classProperty,
dataView,
offset,
);
return result;
};
/**
* Decode the given raw values into the raw (array-based) form of
* a metadata property value.
*
* (For decoding to types like `CartesianN`, the `decodeMetadataValues`
* function can be used)
*
* The given values are a `Uint8Array` containing the RGBA
* values that have been read from the metadata picking
* frame buffer. They are assumed to contain the value for
* the given class property, as encoded by the
* `MetadataPickingPipelineStage` for metadata picking.
*
* When the given class property is an array, then (it has to be
* a fixed-length array, and) the result will be an array with
* the respective length.
*
* When the given class property is vector- or matrix typed,
* then the result will be an array, with a length that corresponds
* to the number of vector- or matrix components.
*
* (The case that the property is an array of vector- or matrix
* elements is not supported on the side of the general metadata
* shader infrastructure, but handled here nevertheless. For such
* an input, the result would be an array of arrays, with each
* element representing one of the vectors or matrices).
*
* In any case, the return value will be the "raw" value, which does
* take into account normalization, but does NOT take into account
* any offset/scale, or default/noData value handling.
*
* @param {MetadataClassProperty} classProperty The `MetadataClassProperty`
* @param {Uint8Array} rawPixelValues The raw values
* @returns {number|bigint|number[]|bigint[]|undefined} The value
* @throws RuntimeError If the class property has an invalid component type
*
* @private
*/
MetadataPicking.decodeRawMetadataValues = function (
classProperty,
rawPixelValues,
) {
const dataView = new DataView(
rawPixelValues.buffer,
rawPixelValues.byteOffset,
rawPixelValues.byteLength,
);
if (classProperty.isArray) {
const arrayLength = classProperty.arrayLength;
const result = Array(arrayLength);
for (let i = 0; i < arrayLength; i++) {
const element = MetadataPicking.decodeRawMetadataValueElement(
classProperty,
dataView,
i,
);
result[i] = element;
}
return result;
}
const result = MetadataPicking.decodeRawMetadataValueElement(
classProperty,
dataView,
0,
);
return result;
};
/**
* Converts the given type into an object representation where appropriate.
*
* When the given type is `SCALAR`, `STRING`, `BOOLEAN`, or `ENUM`, or
* when the given value is `undefined`, then the given value will be
* returned.
*
* Otherwise, for the `VECn/MATn` types, the given value is assumed to be
* a numeric array, and is converted into the matching `CartesianN/MatrixN`
* value.
*
* @param {string} type The `ClassProperty` type
* @param {number|bigint|number[]|bigint[]|undefined} value The input value
* @returns {undefined|number|bigint|string|boolean|Cartesian2|Cartesian3|Cartesian4|Matrix2|Matrix3|Matrix4} The object representation
* @throws RuntimeError If the type is not a valid `MetadataType`
*/
MetadataPicking.convertToObjectType = function (type, value) {
if (!defined(value)) {
return value;
}
if (
type === MetadataType.SCALAR ||
type === MetadataType.STRING ||
type === MetadataType.BOOLEAN ||
type === MetadataType.ENUM
) {
return value;
}
const numbers = value.map((n) => Number(n));
switch (type) {
case MetadataType.VEC2:
return Cartesian2.unpack(numbers, 0, new Cartesian2());
case MetadataType.VEC3:
return Cartesian3.unpack(numbers, 0, new Cartesian3());
case MetadataType.VEC4:
return Cartesian4.unpack(numbers, 0, new Cartesian4());
case MetadataType.MAT2:
return Matrix2.unpack(numbers, 0, new Matrix2());
case MetadataType.MAT3:
return Matrix3.unpack(numbers, 0, new Matrix3());
case MetadataType.MAT4:
return Matrix4.unpack(numbers, 0, new Matrix4());
}
// Should never happen:
throw new RuntimeError(`Invalid metadata object type: ${type}`);
};
/**
* Converts the given type into a raw value or array representation.
*
* For `VECn/MATn` types, the given value is converted into an array.
* For other types, the value is returned directly
*
* @param {string} type The `ClassProperty` type
* @param {undefined|number|bigint|string|boolean|Cartesian2|Cartesian3|Cartesian4|Matrix2|Matrix3|Matrix4} value The input value
* @returns {undefined|number|bigint|string|boolean|number[]} The array representation
* @throws RuntimeError If the type is not a valid `MetadataType`
*/
MetadataPicking.convertFromObjectType = function (type, value) {
if (!defined(value)) {
return value;
}
if (
type === MetadataType.SCALAR ||
type === MetadataType.STRING ||
type === MetadataType.BOOLEAN ||
type === MetadataType.ENUM
) {
return value;
}
switch (type) {
case MetadataType.VEC2:
return Cartesian2.pack(value, Array(2));
case MetadataType.VEC3:
return Cartesian3.pack(value, Array(3));
case MetadataType.VEC4:
return Cartesian4.pack(value, Array(4));
case MetadataType.MAT2:
return Matrix2.pack(value, Array(4));
case MetadataType.MAT3:
return Matrix3.pack(value, Array(9));
case MetadataType.MAT4:
return Matrix4.pack(value, Array(16));
}
// Should never happen:
throw new RuntimeError(`Invalid metadata object type: ${type}`);
};
/**
* Decode the given raw values into a metadata property value.
*
* This applies the value transform (offset/scale) to the result
* of `decodeRawMetadataValues`, and converts this from array-based
* types into object types like `CartesianN`.
*
* @param {MetadataClassProperty} classProperty The `MetadataClassProperty`
* @param {object} metadataProperty The
* `PropertyTextureProperty` or `PropertyAttributeProperty`
* @param {Uint8Array} rawPixelValues The raw values
* @returns {MetadataValue} The value
* @throws RuntimeError If the class property has an invalid type
* or component type
* @throws RangeError If the given pixel values do not have sufficient
* size to contain the expected value type
*
* @private
*/
MetadataPicking.decodeMetadataValues = function (
classProperty,
metadataProperty,
rawPixelValues,
) {
let arrayBasedResult = MetadataPicking.decodeRawMetadataValues(
classProperty,
rawPixelValues,
);
if (metadataProperty.hasValueTransform) {
// In the MetadataClassProperty, these offset/scale are always in
// their array-based form (e.g. a number[3] for `VEC3`). But for
// the PropertyTextureProperty and PropertyAttributeProperty,
// the type of the offset/scale is defined to be
// number|Cartesian2|Cartesian3|Cartesian4|Matrix2|Matrix3|Matrix4
// So these types are converted into their array-based form here, before
// applying them with `MetadataClassProperty.valueTransformInPlace`
const offset = MetadataPicking.convertFromObjectType(
classProperty.type,
metadataProperty.offset,
);
const scale = MetadataPicking.convertFromObjectType(
classProperty.type,
metadataProperty.scale,
);
arrayBasedResult = MetadataClassProperty.valueTransformInPlace(
arrayBasedResult,
offset,
scale,
MetadataComponentType.applyValueTransform,
);
}
if (classProperty.isArray) {
const arrayLength = classProperty.arrayLength;
const result = Array(arrayLength);
for (let i = 0; i < arrayLength; i++) {
const arrayBasedValue = arrayBasedResult[i];
const objectBasedValue = MetadataPicking.convertToObjectType(
classProperty.type,
arrayBasedValue,
);
result[i] = objectBasedValue;
}
return result;
}
const objectResult = MetadataPicking.convertToObjectType(
classProperty.type,
arrayBasedResult,
);
return objectResult;
};
export default Object.freeze(MetadataPicking);