1282 lines
44 KiB
JavaScript
1282 lines
44 KiB
JavaScript
import Check from "../../Core/Check.js";
|
|
import defined from "../../Core/defined.js";
|
|
import Cartesian2 from "../../Core/Cartesian2.js";
|
|
import Cartesian3 from "../../Core/Cartesian3.js";
|
|
import Cartesian4 from "../../Core/Cartesian4.js";
|
|
import DeveloperError from "../../Core/DeveloperError.js";
|
|
import ComponentDatatype from "../../Core/ComponentDatatype.js";
|
|
import AttributeCompression from "../../Core/AttributeCompression.js";
|
|
import IndexDatatype from "../../Core/IndexDatatype.js";
|
|
import PrimitiveType from "../../Core/PrimitiveType.js";
|
|
import Matrix4 from "../../Core/Matrix4.js";
|
|
import Quaternion from "../../Core/Quaternion.js";
|
|
import Transforms from "../../Core/Transforms.js";
|
|
|
|
import AttributeType from "../AttributeType.js";
|
|
import InstanceAttributeSemantic from "../InstanceAttributeSemantic.js";
|
|
import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
|
|
import ModelUtility from "./ModelUtility.js";
|
|
|
|
/**
|
|
* A class for reading the data from a <code>ModelComponents.Attribute</code>.
|
|
*
|
|
* NOTE: Much of the functionality here already exists, scattered in many places.
|
|
* In most cases, the functionality is tailored for "one case" (like only handling
|
|
* positions, or only normals, or not considering quantization, or not handling
|
|
* interleaved buffers, ...). In many cases, the functionality is tailored for an
|
|
* 'accessor' (and often, the functions also expect the 'gltf' to be given).
|
|
* Most of what is done here (and in the existing functions) is pretty low-level
|
|
* and generic, though: The functions could often be fed with some (count, type,
|
|
* componentType), and there could be convenience functions that EITHER take these
|
|
* values from an 'accessor' OR from an 'attribute'. The tl;dr: Large parts of
|
|
* this could be "nicer", or "more generic", and "better" along all dimensions
|
|
* of this term. Just give me time...
|
|
*
|
|
* NOTE: The fact that all this has to operate on TypedArray is unfortunate.
|
|
* Most of the subsequent processing could operate on some abstraction of
|
|
* that. The fact that that TypedArrays can be read/written as "bulk", and
|
|
* then offer access that is "as efficient as it can be" could be a
|
|
* justification, as part of the performance-genericity trade-off
|
|
*
|
|
* NOTE: All this does not properly handle MATn types. There should be SOME
|
|
* abstraction for element- and component-wise access of the data. See
|
|
* https://github.com/javagl/JglTF/blob/84ce6d019fec3b75b6af1649bbe834005b2c620f/jgltf-model/src/main/java/de/javagl/jgltf/model/AbstractAccessorData.java#L149
|
|
*
|
|
* @private
|
|
*/
|
|
class ModelReader {
|
|
/**
|
|
* Reads the data of the given atttribute into a typed array.
|
|
*
|
|
* This will read the data into a compact, flat array with the data
|
|
* type corresponding to the data type of the attribute.
|
|
*
|
|
* If the attribute is contained in an interleaved buffer, marked as
|
|
* 'normalized', quantized, or oct-encoded, then it will be deinterleaved,
|
|
* normalization will be applied, it will be dequantized and oct-decoded
|
|
* as necessary.
|
|
*
|
|
* The result will be THE actual attribute data.
|
|
*
|
|
* @param {Attribute} attribute The attribute
|
|
* @returns {TypedArray} The attribute data
|
|
*/
|
|
static readAttributeAsTypedArray(attribute) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("attribute", attribute);
|
|
//>>includeEnd('debug');
|
|
|
|
// Obtain a compact (non-interleaved) typed array that contains
|
|
// the components.
|
|
const compactTypedArray =
|
|
ModelReader.readAttributeAsRawCompactTypedArray(attribute);
|
|
|
|
// If the attribute is not normalized and the data is not quantized
|
|
// and not normalized, then this can be returned directly
|
|
const normalized = attribute.normalized;
|
|
const quantization = attribute.quantization;
|
|
if (!defined(quantization) && !normalized) {
|
|
return compactTypedArray;
|
|
}
|
|
|
|
const elementType = attribute.type;
|
|
const elementCount = attribute.count;
|
|
|
|
// If the attribute is normalized, normalize the data from
|
|
// the typed array
|
|
let normalizedTypedArray = compactTypedArray;
|
|
if (normalized) {
|
|
// Note that although this is called "dequantize", it does
|
|
// not really "dequantize" based on the quantization. It only
|
|
// performs the conversion from the (normalized) integer
|
|
// component types into floating point.
|
|
normalizedTypedArray = AttributeCompression.dequantize(
|
|
compactTypedArray,
|
|
attribute.componentDatatype,
|
|
elementType,
|
|
elementCount,
|
|
);
|
|
}
|
|
|
|
if (!defined(quantization)) {
|
|
return normalizedTypedArray;
|
|
}
|
|
// Now, this one actually DOES dequantize...
|
|
const dequantizedTypedArray = ModelReader.dequantize(
|
|
normalizedTypedArray,
|
|
elementCount,
|
|
elementType,
|
|
quantization,
|
|
);
|
|
return dequantizedTypedArray;
|
|
}
|
|
|
|
/**
|
|
* Read the data of the given attribute into a compact typed array.
|
|
*
|
|
* If the attribute is stored as interleaved data, then the result
|
|
* will be the deinterleaved data. If the data is quantized or
|
|
* normalized, then the resulting data will be the "raw" data,
|
|
* without applying normalization or dequantization.
|
|
*
|
|
* @param {ModelComponents.Attribute} attribute The attribute
|
|
* @returns {TypedArray} The raw attribute data
|
|
*/
|
|
static readAttributeAsRawCompactTypedArray(attribute) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("attribute", attribute);
|
|
//>>includeEnd('debug');
|
|
|
|
const elementType = attribute.type;
|
|
const elementCount = attribute.count;
|
|
|
|
const componentsPerElement =
|
|
AttributeType.getNumberOfComponents(elementType);
|
|
const totalComponentCount = elementCount * componentsPerElement;
|
|
|
|
// If the data is quantized, use the quantized component type
|
|
let componentType = attribute.componentDatatype;
|
|
const quantization = attribute.quantization;
|
|
if (defined(quantization)) {
|
|
componentType = quantization.componentDatatype;
|
|
}
|
|
const buffer = attribute.buffer;
|
|
|
|
// If the byte stride is the default (i.e. the total element size),
|
|
// then just fetch the whole buffer data into a typed array of the
|
|
// desired target type, and return it
|
|
const byteOffset = attribute.byteOffset;
|
|
const byteStride = attribute.byteStride;
|
|
const bytesPerComponent = ComponentDatatype.getSizeInBytes(componentType);
|
|
const defaultByteStride = componentsPerElement * bytesPerComponent;
|
|
|
|
const isDefaultStride =
|
|
!defined(byteStride) || byteStride === defaultByteStride;
|
|
const hasTypedArray = defined(attribute.typedArray);
|
|
|
|
// If in-memory — return as-is
|
|
// Important: typedArray is already tightly-packed on creation (See: ModelComponents.typedArray and GltfLoader.getPackedTypedArray)
|
|
// byteOffset and byteStride should thus be ignored
|
|
if (hasTypedArray) {
|
|
return attribute.typedArray;
|
|
}
|
|
|
|
// Non-interleaved — copy from typedArray or read from GPU
|
|
if (isDefaultStride) {
|
|
const typedArray = ComponentDatatype.createTypedArray(
|
|
componentType,
|
|
totalComponentCount,
|
|
);
|
|
buffer.getBufferData(typedArray, byteOffset);
|
|
return typedArray;
|
|
}
|
|
|
|
// Fetch the whole buffer in its raw form, to pick out the
|
|
// interleaved values.
|
|
// Note: When ALL attributes have to be fetched from an
|
|
// interleaved buffer, then this getBufferData call will
|
|
// be performed multiple times. It would be preferable to
|
|
// have ONE "TypedArray[] getThemFrom(buffer)" call that
|
|
// returns all of the (interleaved) attributes at once,
|
|
// but this requires abstractions that we don't have.
|
|
|
|
// Read back from GPU if not available in memory
|
|
const fullTypedArray = new Uint8Array(buffer.sizeInBytes);
|
|
buffer.getBufferData(fullTypedArray);
|
|
|
|
// Read the components of each element, and write them into
|
|
// a typed array in a compact form
|
|
const compactTypedArray = ComponentDatatype.createTypedArray(
|
|
componentType,
|
|
totalComponentCount,
|
|
);
|
|
const elementByteStride = byteStride ?? defaultByteStride;
|
|
const dataView = new DataView(
|
|
fullTypedArray.buffer,
|
|
fullTypedArray.byteOffset,
|
|
fullTypedArray.byteLength,
|
|
);
|
|
const components = new Array(componentsPerElement);
|
|
const componentsReader = ModelReader.createComponentsReader(componentType);
|
|
for (let i = 0; i < elementCount; ++i) {
|
|
const elementByteOffset = byteOffset + i * elementByteStride;
|
|
componentsReader(
|
|
dataView,
|
|
elementByteOffset,
|
|
componentsPerElement,
|
|
components,
|
|
);
|
|
for (let j = 0; j < componentsPerElement; ++j) {
|
|
compactTypedArray[i * componentsPerElement + j] = components[j];
|
|
}
|
|
}
|
|
return compactTypedArray;
|
|
}
|
|
|
|
/**
|
|
* Dequantize the data from the given input array, based on the given
|
|
* quantization information, and return the result.
|
|
*
|
|
* This assumes that normalization has already been applied. This means that
|
|
* when the <code>quantization.normalized</code> flag is <code>true</code>,
|
|
* then the input is assumed to contain floating point values in the range
|
|
* [-1, 1].
|
|
*
|
|
* @param {TypedArray} quantizedTypedArray The quantized typed array
|
|
* @param {number} elementCount The number of elements
|
|
* @param {AttributeType} elementType The element type
|
|
* @param {ModelComponents.Quantization} quantization The quantization
|
|
* @returns {TypedArray} The result
|
|
* @throws DeveloperError When the element type is not SCALAR, VEC2,
|
|
* VEC3, or VEC4
|
|
*/
|
|
static dequantize(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
elementType,
|
|
quantization,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("quantizedTypedArray", quantizedTypedArray);
|
|
Check.typeOf.number.greaterThanOrEquals("elementCount", elementCount, 0);
|
|
Check.defined("elementType", elementType);
|
|
Check.defined("quantization", quantization);
|
|
//>>includeEnd('debug');
|
|
|
|
if (quantization.octEncoded) {
|
|
const dequantizedTypedArray = ModelReader.octDecode(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
quantization.normalizationRange,
|
|
undefined,
|
|
);
|
|
if (quantization.octEncodedZXY) {
|
|
ModelReader.convertZxyToXyz(
|
|
dequantizedTypedArray,
|
|
dequantizedTypedArray,
|
|
);
|
|
}
|
|
return dequantizedTypedArray;
|
|
}
|
|
|
|
// These could be generalized, if the offset/stepSize were not
|
|
// CartesianX objects, but arrays...
|
|
const stepSize = quantization.quantizedVolumeStepSize;
|
|
const offset = quantization.quantizedVolumeOffset;
|
|
if (elementType === AttributeType.SCALAR) {
|
|
return ModelReader.dequantize1D(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
stepSize,
|
|
offset,
|
|
undefined,
|
|
);
|
|
}
|
|
if (elementType === AttributeType.VEC2) {
|
|
return ModelReader.dequantize2D(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
stepSize,
|
|
offset,
|
|
undefined,
|
|
);
|
|
}
|
|
if (elementType === AttributeType.VEC3) {
|
|
return ModelReader.dequantize3D(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
stepSize,
|
|
offset,
|
|
undefined,
|
|
);
|
|
}
|
|
if (elementType === AttributeType.VEC4) {
|
|
return ModelReader.dequantize4D(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
stepSize,
|
|
offset,
|
|
undefined,
|
|
);
|
|
}
|
|
throw new DeveloperError(
|
|
`Element type for dequantization must be SCALAR, VEC2, VEC3, or VEC4, but is ${elementType}`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Decode oct-encoded normals from the given input, and write the
|
|
* result into the given output, allocating and returning a new
|
|
* array if the result was undefined.
|
|
*
|
|
* This will apply the <code>AttributeCompression.octDecodeInRange</code>
|
|
* function to each three components of the input.
|
|
*
|
|
* @param {TypedArray} quantizedTypedArray The input
|
|
* @param {number} elementCount The number of elements
|
|
* @param {number} normalizationRange The normalization range
|
|
* @param {TypedArray} [dequantizedTypedArray] The result
|
|
* @returns {TypedArray} The result
|
|
*/
|
|
static octDecode(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
normalizationRange,
|
|
dequantizedTypedArray,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("quantizedTypedArray", quantizedTypedArray);
|
|
Check.typeOf.number.greaterThanOrEquals("elementCount", elementCount, 0);
|
|
Check.typeOf.number.greaterThan(
|
|
"normalizationRange",
|
|
normalizationRange,
|
|
0,
|
|
);
|
|
//>>includeEnd('debug');
|
|
|
|
if (!defined(dequantizedTypedArray)) {
|
|
dequantizedTypedArray = new Float32Array(quantizedTypedArray.length);
|
|
}
|
|
const c = new Cartesian3();
|
|
for (let i = 0; i < elementCount; i++) {
|
|
Cartesian3.unpack(quantizedTypedArray, i * 3, c);
|
|
AttributeCompression.octDecodeInRange(c.x, c.y, normalizationRange, c);
|
|
Cartesian3.pack(c, dequantizedTypedArray, i * 3);
|
|
}
|
|
return dequantizedTypedArray;
|
|
}
|
|
|
|
/**
|
|
* Swizzle all three consecutive elements in the given input array
|
|
* from (z, x, y) to (x, y, z), and write the result into the
|
|
* given output array, creating a new array if the given output
|
|
* array was undefined.
|
|
*
|
|
* @param {TypedArray} input The input
|
|
* @param {number} elementCount The number of elements
|
|
* @param {TypedArray} [output] The result
|
|
* @returns {TypedArray} The result
|
|
*/
|
|
static convertZxyToXyz(input, elementCount, output) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("input", input);
|
|
Check.typeOf.number.greaterThanOrEquals("elementCount", elementCount, 0);
|
|
//>>includeEnd('debug');
|
|
|
|
if (!defined(output)) {
|
|
output = new Float32Array(input.length);
|
|
}
|
|
let offset = 0;
|
|
for (let i = 0; i < elementCount; i++, offset += 3) {
|
|
const z = input[offset + 0];
|
|
const x = input[offset + 1];
|
|
const y = input[offset + 2];
|
|
output[offset + 0] = x;
|
|
output[offset + 1] = y;
|
|
output[offset + 2] = z;
|
|
}
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Dequantize the given quantized array, based on the given quantization
|
|
* information, and write the result into the given output array, creating
|
|
* the output array if it was undefined.
|
|
*
|
|
* This will simply fill the output array with
|
|
* <code>output[i] = input[i] * stepSize + offset</code>
|
|
*
|
|
* @param {TypedArray} quantizedTypedArray The quantized array
|
|
* @param {number} elementCount The number of elements
|
|
* @param {number} stepSize The quantization step size
|
|
* @param {number} offset The quantization offset
|
|
* @param {TypedArray} [dequantizedTypedArray] The result
|
|
* @returns {TypedArray} The result
|
|
*/
|
|
static dequantize1D(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
stepSize,
|
|
offset,
|
|
dequantizedTypedArray,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("quantizedTypedArray", quantizedTypedArray);
|
|
Check.typeOf.number.greaterThanOrEquals("elementCount", elementCount, 0);
|
|
Check.defined("stepSize", stepSize);
|
|
Check.defined("offset", offset);
|
|
//>>includeEnd('debug');
|
|
|
|
if (!defined(dequantizedTypedArray)) {
|
|
dequantizedTypedArray = new Float32Array(quantizedTypedArray.length);
|
|
}
|
|
for (let i = 0; i < elementCount; i++) {
|
|
const q = quantizedTypedArray[i];
|
|
const d = q * stepSize + offset;
|
|
dequantizedTypedArray[i] = d;
|
|
}
|
|
return dequantizedTypedArray;
|
|
}
|
|
|
|
/**
|
|
* Dequantize the given quantized array, based on the given quantization
|
|
* information, and write the result into the given output array, creating
|
|
* the output array if it was undefined.
|
|
*
|
|
* This will simply fill the output array with
|
|
* <code>output[i] = input[i] * stepSize + offset</code>
|
|
* when interpreting the input and output as arrays of Cartesian2.
|
|
*
|
|
* @param {TypedArray} quantizedTypedArray The quantized array
|
|
* @param {number} elementCount The number of elements
|
|
* @param {Cartesian2} stepSize The quantization step size
|
|
* @param {Cartesian2} offset The quantization offset
|
|
* @param {TypedArray} [dequantizedTypedArray] The result
|
|
* @returns {TypedArray} The result
|
|
*/
|
|
static dequantize2D(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
stepSize,
|
|
offset,
|
|
dequantizedTypedArray,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("quantizedTypedArray", quantizedTypedArray);
|
|
Check.typeOf.number.greaterThanOrEquals("elementCount", elementCount, 0);
|
|
Check.defined("stepSize", stepSize);
|
|
Check.defined("offset", offset);
|
|
//>>includeEnd('debug');
|
|
|
|
if (!defined(dequantizedTypedArray)) {
|
|
dequantizedTypedArray = new Float32Array(quantizedTypedArray.length);
|
|
}
|
|
const c = new Cartesian2();
|
|
for (let i = 0; i < elementCount; i++) {
|
|
Cartesian2.unpack(quantizedTypedArray, i * 2, c);
|
|
Cartesian2.multiplyComponents(c, stepSize, c);
|
|
Cartesian2.add(c, offset, c);
|
|
Cartesian2.pack(c, dequantizedTypedArray, i * 2);
|
|
}
|
|
return dequantizedTypedArray;
|
|
}
|
|
|
|
/**
|
|
* Dequantize the given quantized array, based on the given quantization
|
|
* information, and write the result into the given output array, creating
|
|
* the output array if it was undefined.
|
|
*
|
|
* This will simply fill the output array with
|
|
* <code>output[i] = input[i] * stepSize + offset</code>
|
|
* when interpreting the input and output as arrays of Cartesian3.
|
|
*
|
|
* @param {TypedArray} quantizedTypedArray The quantized array
|
|
* @param {number} elementCount The number of elements
|
|
* @param {Cartesian3} stepSize The quantization step size
|
|
* @param {Cartesian3} offset The quantization offset
|
|
* @param {TypedArray} [dequantizedTypedArray] The result
|
|
* @returns {TypedArray} The result
|
|
*/
|
|
static dequantize3D(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
stepSize,
|
|
offset,
|
|
dequantizedTypedArray,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("quantizedTypedArray", quantizedTypedArray);
|
|
Check.typeOf.number.greaterThanOrEquals("elementCount", elementCount, 0);
|
|
Check.defined("stepSize", stepSize);
|
|
Check.defined("offset", offset);
|
|
//>>includeEnd('debug');
|
|
|
|
if (!defined(dequantizedTypedArray)) {
|
|
dequantizedTypedArray = new Float32Array(quantizedTypedArray.length);
|
|
}
|
|
const c = new Cartesian3();
|
|
for (let i = 0; i < elementCount; i++) {
|
|
Cartesian3.unpack(quantizedTypedArray, i * 3, c);
|
|
Cartesian3.multiplyComponents(c, stepSize, c);
|
|
Cartesian3.add(c, offset, c);
|
|
Cartesian3.pack(c, dequantizedTypedArray, i * 3);
|
|
}
|
|
return dequantizedTypedArray;
|
|
}
|
|
|
|
/**
|
|
* Dequantize the given quantized array, based on the given quantization
|
|
* information, and write the result into the given output array, creating
|
|
* the output array if it was undefined.
|
|
*
|
|
* This will simply fill the output array with
|
|
* <code>output[i] = input[i] * stepSize + offset</code>
|
|
* when interpreting the input and output as arrays of Cartesian4.
|
|
*
|
|
* @param {TypedArray} quantizedTypedArray The quantized array
|
|
* @param {number} elementCount The number of elements
|
|
* @param {Cartesian4} stepSize The quantization step size
|
|
* @param {Cartesian4} offset The quantization offset
|
|
* @param {TypedArray} [dequantizedTypedArray] The result
|
|
* @returns {TypedArray} The result
|
|
*/
|
|
static dequantize4D(
|
|
quantizedTypedArray,
|
|
elementCount,
|
|
stepSize,
|
|
offset,
|
|
dequantizedTypedArray,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("quantizedTypedArray", quantizedTypedArray);
|
|
Check.typeOf.number.greaterThanOrEquals("elementCount", elementCount, 0);
|
|
Check.defined("stepSize", stepSize);
|
|
Check.defined("offset", offset);
|
|
//>>includeEnd('debug');
|
|
|
|
if (!defined(dequantizedTypedArray)) {
|
|
dequantizedTypedArray = new Float32Array(quantizedTypedArray.length);
|
|
}
|
|
const c = new Cartesian4();
|
|
for (let i = 0; i < elementCount; i++) {
|
|
Cartesian4.unpack(quantizedTypedArray, i * 4, c);
|
|
Cartesian4.multiplyComponents(c, stepSize, c);
|
|
Cartesian4.add(c, offset, c);
|
|
Cartesian4.pack(c, dequantizedTypedArray, i * 4);
|
|
}
|
|
return dequantizedTypedArray;
|
|
}
|
|
|
|
/**
|
|
* Reads and returns a value with the given type
|
|
* at the given byte offset from the data view, in little-endian
|
|
* order
|
|
* @callback ComponentsReaderCallback
|
|
* @param {DataView} dataView Typed data view into a binary buffer
|
|
* @param {number} byteOffset The offset, in bytes, from the start of the view to read the data from
|
|
* @param {number} numberOfComponents The number of components to read
|
|
* @param {number[]} result The array in which to read the result
|
|
*/
|
|
|
|
/**
|
|
* Creates a function that reads the specified number of components with
|
|
* the given type from the given data view, in little-endian
|
|
* order, and writes them into a given result array.
|
|
*
|
|
* @param {ComponentDatatype} componentType The component type
|
|
* @returns {ComponentsReaderCallback} The reader
|
|
*/
|
|
static createComponentsReader(componentType) {
|
|
const componentReader = ModelReader.createComponentReader(componentType);
|
|
const sizeInBytes = ComponentDatatype.getSizeInBytes(componentType);
|
|
return function (dataView, byteOffset, numberOfComponents, result) {
|
|
let offset = byteOffset;
|
|
for (let i = 0; i < numberOfComponents; ++i) {
|
|
result[i] = componentReader(dataView, offset);
|
|
offset += sizeInBytes;
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reads and returns a value with the given type
|
|
* at the given byte offset from the data view, in little-endian
|
|
* order
|
|
* @callback ComponentReaderCallback
|
|
* @param {DataView} dataView Typed data view into a binary buffer
|
|
* @param {number} byteOffset The offset, in bytes, from the start of the view to read the data from
|
|
* @returns {number|BigInt} The value read from the dataView
|
|
*/
|
|
|
|
/**
|
|
* Creates a function that reads and returns a value with the given type
|
|
* at the given byte offset from the data view, in little-endian
|
|
* order
|
|
* @param {ComponentDatatype} componentType The component type
|
|
* @returns {ComponentReaderCallback} The reader
|
|
*/
|
|
static createComponentReader(componentType) {
|
|
switch (componentType) {
|
|
case ComponentDatatype.BYTE:
|
|
return function (dataView, byteOffset) {
|
|
return dataView.getInt8(byteOffset);
|
|
};
|
|
case ComponentDatatype.UNSIGNED_BYTE:
|
|
return function (dataView, byteOffset) {
|
|
return dataView.getUint8(byteOffset);
|
|
};
|
|
case ComponentDatatype.SHORT:
|
|
return function (dataView, byteOffset) {
|
|
return dataView.getInt16(byteOffset, true);
|
|
};
|
|
case ComponentDatatype.UNSIGNED_SHORT:
|
|
return function (dataView, byteOffset) {
|
|
return dataView.getUint16(byteOffset, true);
|
|
};
|
|
case ComponentDatatype.INT:
|
|
return function (dataView, byteOffset) {
|
|
return dataView.getInt32(byteOffset, true);
|
|
};
|
|
case ComponentDatatype.UNSIGNED_INT:
|
|
return function (dataView, byteOffset) {
|
|
return dataView.getUint32(byteOffset, true);
|
|
};
|
|
case ComponentDatatype.FLOAT:
|
|
return function (dataView, byteOffset) {
|
|
return dataView.getFloat32(byteOffset, true);
|
|
};
|
|
case ComponentDatatype.DOUBLE:
|
|
return function (dataView, byteOffset) {
|
|
return dataView.getFloat64(byteOffset, true);
|
|
};
|
|
}
|
|
throw new DeveloperError(
|
|
`The componentType must be a valid ComponentDatatype, but is ${componentType}`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Transform the elements of the given array with the given 4x4 matrix,
|
|
* interpreting each 3 consecutive elements as a 3D point, and write
|
|
* the result into the given result array, creating the result array
|
|
* if it was undefined.
|
|
*
|
|
* @param {TypedArray} input The input array
|
|
* @param {Matrix4} matrix The matrix
|
|
* @param {TypedArray} [result] The result
|
|
* @returns {TypedArray} The result
|
|
*/
|
|
static transform3D(input, matrix, result) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("input", input);
|
|
Check.defined("matrix", matrix);
|
|
//>>includeEnd('debug');
|
|
|
|
if (!defined(result)) {
|
|
result = new Float32Array(input.length);
|
|
}
|
|
const c = new Cartesian3();
|
|
const elementCount = input.length / 3;
|
|
for (let i = 0; i < elementCount; i++) {
|
|
Cartesian3.unpack(input, i * 3, c);
|
|
Matrix4.multiplyByPoint(matrix, c, c);
|
|
Cartesian3.pack(c, result, i * 3);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Reads feature IDs from an implicit range feature ID set into a typed array.
|
|
*
|
|
* Generates values using <code>offset + Math.floor(i / repeat)</code> for
|
|
* each vertex. If <code>repeat</code> is undefined, all values are set to
|
|
* <code>offset</code>.
|
|
*
|
|
* @param {FeatureIdImplicitRange} featureIdSet The implicit range feature ID set.
|
|
* @param {object} attributeOwner An object with an <code>attributes</code> array
|
|
* (a primitive or an instances object)
|
|
* @returns {Float32Array} The generated feature ID values.
|
|
*/
|
|
static readImplicitRangeAsTypedArray(featureIdSet, attributeOwner) {
|
|
const count = attributeOwner.attributes[0]?.count ?? 0;
|
|
const offset = featureIdSet.offset;
|
|
const repeat = featureIdSet.repeat;
|
|
const typedArray = new Float32Array(count);
|
|
if (defined(repeat)) {
|
|
for (let i = 0; i < count; i++) {
|
|
typedArray[i] = offset + Math.floor(i / repeat);
|
|
}
|
|
} else {
|
|
typedArray.fill(offset);
|
|
}
|
|
return typedArray;
|
|
}
|
|
|
|
/**
|
|
* Read the indices values from the given primitive indices, and
|
|
* return them as a typed array.
|
|
*
|
|
* If the given object already has a <code>typedArray/code> property, then it
|
|
* is assumed that this contains the proper indices, and they are returned.
|
|
*
|
|
* Otherwise, this reads the data from the <code>buffer</code> of the given
|
|
* primitive indices object, into a typed array with a type that matches the
|
|
* <code>indexDataType</code>, and returns it.
|
|
*
|
|
* Clients may not modify the returned typed array.
|
|
*
|
|
* @param {ModelComponents.Indices} primitiveIndices The primitive indices
|
|
* @returns {TypedArray} The indices values
|
|
* @throws {DeveloperError} If the <code>indexDataType</code> of the given
|
|
* object is neither <code>UNSIGNED_BYTE</code>, nor <code>UNSIGNED_SHORT</code>,
|
|
* nor <code>UNSIGNED_INT</code>
|
|
*/
|
|
static readIndicesAsTypedArray(primitiveIndices) {
|
|
const existingIndices = primitiveIndices.typedArray;
|
|
if (defined(existingIndices)) {
|
|
return existingIndices;
|
|
}
|
|
const indicesBuffer = primitiveIndices.buffer;
|
|
const indicesCount = primitiveIndices.count;
|
|
const indexDatatype = primitiveIndices.indexDatatype;
|
|
const indices = ModelReader.createIndexTypedArray(
|
|
indexDatatype,
|
|
indicesCount,
|
|
);
|
|
indicesBuffer.getBufferData(indices);
|
|
return indices;
|
|
}
|
|
|
|
/**
|
|
* Read the indices values from the given primitive indices object, and return
|
|
* them as a typed array of triangle vertex indices.
|
|
*
|
|
* If the given primitive type is <code>TRIANGLES</code>, then the indices
|
|
* values will be read from the given object, and returned.
|
|
*
|
|
* If the primitive type is <code>TRIANGLE_STRIP</code> or <code>TRIANGLE_FAN</code>,
|
|
* then the original indices values, will be read, converted into triangle indices
|
|
* (i.e. their equivalent <code>TRIANGLES</code> representation), and the result
|
|
* will be returned.
|
|
*
|
|
* The type of the returned array will match the <code>indexDataType</code>
|
|
* of the given object.
|
|
*
|
|
* Clients may not modify the returned typed array.
|
|
*
|
|
* @param {ModelComponents.Indices} primitiveIndices The primitive indices
|
|
* @returns {TypedArray} The indices, converted to triangle indices if necessary
|
|
* @throws {DeveloperError} If the <code>indexDataType</code> of the given
|
|
* object is neither <code>UNSIGNED_BYTE</code>, nor <code>UNSIGNED_SHORT</code>,
|
|
* nor <code>UNSIGNED_INT</code>, or the given <code>primitiveType</code>
|
|
* is neither <code>TRIANGLES</code>, nor <code>TRIANGLE_STRIP</code>,
|
|
* nor <code>TRIANGLE_FAN</code>
|
|
*/
|
|
static readIndicesAsTriangleIndicesTypedArray(
|
|
primitiveIndices,
|
|
primitiveType,
|
|
) {
|
|
const originalIndices =
|
|
ModelReader.readIndicesAsTypedArray(primitiveIndices);
|
|
if (primitiveType === PrimitiveType.TRIANGLES) {
|
|
return originalIndices;
|
|
}
|
|
if (primitiveType === PrimitiveType.TRIANGLE_STRIP) {
|
|
const triangleIndices =
|
|
ModelReader.convertTriangleStripToTriangleIndices(originalIndices);
|
|
return triangleIndices;
|
|
}
|
|
if (primitiveType === PrimitiveType.TRIANGLE_FAN) {
|
|
const triangleIndices =
|
|
ModelReader.convertTriangleFanToTriangleIndices(originalIndices);
|
|
return triangleIndices;
|
|
}
|
|
throw new DeveloperError(
|
|
`The primitiveType must be TRIANGLES (${PrimitiveType.TRIANGLES}, ` +
|
|
`TRIANGLE_STRIP (${PrimitiveType.TRIANGLE_STRIP}, or ` +
|
|
`TRIANGLE_FAN (${PrimitiveType.TRIANGLE_FAN}, but is ${primitiveType}`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Converts the given indices from a <code>TRIANGLE_STRIP</code> representation
|
|
* into a <code>TRIANGLES</code> representation, and returns the result.
|
|
*
|
|
* The type of the result will be the same as the type of the input array.
|
|
*
|
|
* @param {TypedArray} indices The input indices
|
|
* @returns {TypedArray} The resulting triangle indices
|
|
*/
|
|
static convertTriangleStripToTriangleIndices(indices) {
|
|
const triangleIndices = indices.constructor((indices.length - 2) * 3);
|
|
for (let i = 0; i < indices.length - 2; i++) {
|
|
if (i % 2 === 1) {
|
|
triangleIndices[i * 3 + 0] = indices[i + 0];
|
|
triangleIndices[i * 3 + 1] = indices[i + 2];
|
|
triangleIndices[i * 3 + 2] = indices[i + 1];
|
|
} else {
|
|
triangleIndices[i * 3 + 0] = indices[i + 0];
|
|
triangleIndices[i * 3 + 1] = indices[i + 1];
|
|
triangleIndices[i * 3 + 2] = indices[i + 2];
|
|
}
|
|
}
|
|
return triangleIndices;
|
|
}
|
|
|
|
/**
|
|
* Converts the given indices from a <code>TRIANGLE_FAN</code> representation
|
|
* into a <code>TRIANGLES</code> representation, and returns the result.
|
|
*
|
|
* The type of the result will be the same as the type of the input array.
|
|
*
|
|
* @param {TypedArray} indices The input indices
|
|
* @returns {TypedArray} The resulting triangle indices
|
|
*/
|
|
static convertTriangleFanToTriangleIndices(indices) {
|
|
const triangleIndices = indices.constructor((indices.length - 2) * 3);
|
|
for (let i = 0; i < indices.length - 2; i++) {
|
|
triangleIndices[i * 3 + 0] = indices[i + 0];
|
|
triangleIndices[i * 3 + 1] = indices[i + 1];
|
|
triangleIndices[i * 3 + 2] = indices[i + 2];
|
|
}
|
|
return triangleIndices;
|
|
}
|
|
|
|
/**
|
|
* Create a typed array with a type that matches the given index data type,
|
|
* and the given size.
|
|
*
|
|
* @param {number} indexDatatype The <code>IndexDataType</code>
|
|
* @param {number} size The size of the array that will be created
|
|
* @returns {TypedArray} The typed array
|
|
* @throws {DeveloperError} If the <code>indexDataType</code> is neither
|
|
* <code>UNSIGNED_BYTE</code>, nor <code>UNSIGNED_SHORT</code>,
|
|
* nor <code>UNSIGNED_INT</code>, or the size is negative.
|
|
*/
|
|
static createIndexTypedArray(indexDatatype, size) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("size", size, 0);
|
|
//>>includeEnd('debug');
|
|
|
|
switch (indexDatatype) {
|
|
case IndexDatatype.UNSIGNED_BYTE:
|
|
return new Uint8Array(size);
|
|
case IndexDatatype.UNSIGNED_SHORT:
|
|
return new Uint16Array(size);
|
|
case IndexDatatype.UNSIGNED_INT:
|
|
return new Uint32Array(size);
|
|
}
|
|
throw new DeveloperError(
|
|
`The indexDatatype must be UNSIGNED_BYTE (${IndexDatatype.UNSIGNED_BYTE}, ` +
|
|
`UNSIGNED_SHORT (${IndexDatatype.UNSIGNED_SHORT}, or ` +
|
|
`UNSIGNED_INT (${IndexDatatype.UNSIGNED_INT}, but is ${indexDatatype}`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Per-instance data combining a transform matrix with an optional feature ID.
|
|
*
|
|
* @typedef {object} ModelReader.Instance
|
|
* @property {Matrix4} transform The instance transform matrix.
|
|
* @property {number} [featureId] The per-instance feature ID, or undefined.
|
|
*
|
|
* @private
|
|
*/
|
|
|
|
/**
|
|
* A callback invoked by {@link ModelReader.forEachPrimitive} for each
|
|
* runtime primitive in the model.
|
|
*
|
|
* @callback ModelReader.ForEachPrimitiveCallback
|
|
* @param {object} runtimePrimitive The runtime primitive wrapper.
|
|
* @param {object} primitive The underlying model primitive (runtimePrimitive.primitive).
|
|
* @param {ModelReader.Instance[]} instances Per-instance data (transforms and optional feature IDs).
|
|
* @param {Matrix4} computedModelMatrix The computed model matrix for the node.
|
|
*
|
|
* @private
|
|
*/
|
|
|
|
/**
|
|
* Iterates over every primitive in a model's scene graph, computing
|
|
* node transforms and instance transforms once per node and invoking
|
|
* a callback for each runtime primitive.
|
|
* <p>
|
|
* When a map projection is provided, the computed model matrix is
|
|
* projected to 2D via {@link Transforms.basisTo2D}.
|
|
* </p>
|
|
*
|
|
* @param {Model} model The model whose scene graph to traverse.
|
|
* @param {object} [options] Object with the following properties:
|
|
* @param {MapProjection} [options.mapProjection] The map projection for 2D mode. When defined, the computed model matrix is projected to 2D.
|
|
* @param {string} [options.instanceFeatureIdLabel] The label used to select which instance feature ID set to read. When defined, per-instance feature IDs are fetched. When undefined, feature IDs are not fetched.
|
|
* @param {ModelReader.ForEachPrimitiveCallback} callback The function invoked for each primitive.
|
|
*/
|
|
static forEachPrimitive(model, options, callback) {
|
|
const mapProjection = options?.mapProjection;
|
|
const instanceFeatureIdLabel = options?.instanceFeatureIdLabel;
|
|
const sceneGraph = model.sceneGraph;
|
|
if (!defined(sceneGraph)) {
|
|
return;
|
|
}
|
|
|
|
const scratchNodeTransforms = {
|
|
nodeComputedTransform: new Matrix4(),
|
|
modelMatrix: new Matrix4(),
|
|
computedModelMatrix: new Matrix4(),
|
|
};
|
|
|
|
const nodes = sceneGraph._runtimeNodes;
|
|
const nodesLength = nodes.length;
|
|
|
|
for (let n = 0; n < nodesLength; n++) {
|
|
const runtimeNode = nodes[n];
|
|
|
|
const nodeTransforms = ModelReader.computeNodeTransforms(
|
|
runtimeNode,
|
|
sceneGraph,
|
|
model,
|
|
scratchNodeTransforms,
|
|
);
|
|
|
|
let computedModelMatrix = nodeTransforms.computedModelMatrix;
|
|
|
|
if (defined(mapProjection)) {
|
|
computedModelMatrix = Transforms.basisTo2D(
|
|
mapProjection,
|
|
computedModelMatrix,
|
|
computedModelMatrix,
|
|
);
|
|
}
|
|
|
|
const instanceTransforms = ModelReader.computeInstanceTransforms(
|
|
runtimeNode,
|
|
computedModelMatrix,
|
|
nodeTransforms.nodeComputedTransform,
|
|
nodeTransforms.modelMatrix,
|
|
);
|
|
|
|
const instanceFeatureIds = defined(instanceFeatureIdLabel)
|
|
? ModelReader.computeInstanceFeatureIds(
|
|
runtimeNode,
|
|
instanceFeatureIdLabel,
|
|
)
|
|
: undefined;
|
|
|
|
const instances = [];
|
|
for (let i = 0; i < instanceTransforms.length; i++) {
|
|
instances.push({
|
|
transform: instanceTransforms[i],
|
|
featureId: defined(instanceFeatureIds)
|
|
? instanceFeatureIds[i]
|
|
: undefined,
|
|
});
|
|
}
|
|
|
|
const primitivesLength = runtimeNode.runtimePrimitives.length;
|
|
for (let p = 0; p < primitivesLength; p++) {
|
|
const runtimePrimitive = runtimeNode.runtimePrimitives[p];
|
|
callback(
|
|
runtimePrimitive,
|
|
runtimePrimitive.primitive,
|
|
instances,
|
|
computedModelMatrix,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Computes the model matrix for a runtime node, accounting for instancing
|
|
* and world-space transforms.
|
|
*
|
|
* @param {object} runtimeNode The runtime node.
|
|
* @param {ModelSceneGraph} sceneGraph The model scene graph.
|
|
* @param {Model} model The model.
|
|
* @param {object} result An object with scratch matrices: { nodeComputedTransform: Matrix4, modelMatrix: Matrix4, computedModelMatrix: Matrix4 }.
|
|
* @returns {object} The result parameter, populated with the computed transforms.
|
|
*
|
|
*/
|
|
static computeNodeTransforms(runtimeNode, sceneGraph, model, result) {
|
|
const node = runtimeNode.node;
|
|
|
|
let nodeComputedTransform = Matrix4.clone(
|
|
runtimeNode.computedTransform,
|
|
result.nodeComputedTransform,
|
|
);
|
|
let modelMatrix = Matrix4.clone(
|
|
sceneGraph.computedModelMatrix,
|
|
result.modelMatrix,
|
|
);
|
|
|
|
const instances = node.instances;
|
|
if (defined(instances)) {
|
|
if (instances.transformInWorldSpace) {
|
|
// Replicate the multiplication order in LegacyInstancingStageVS.
|
|
modelMatrix = Matrix4.multiplyTransformation(
|
|
model.modelMatrix,
|
|
sceneGraph.components.transform,
|
|
modelMatrix,
|
|
);
|
|
nodeComputedTransform = Matrix4.multiplyTransformation(
|
|
sceneGraph.axisCorrectionMatrix,
|
|
runtimeNode.computedTransform,
|
|
nodeComputedTransform,
|
|
);
|
|
}
|
|
}
|
|
|
|
const computedModelMatrix = Matrix4.multiplyTransformation(
|
|
modelMatrix,
|
|
nodeComputedTransform,
|
|
result.computedModelMatrix,
|
|
);
|
|
|
|
result.computedModelMatrix = computedModelMatrix;
|
|
result.nodeComputedTransform = nodeComputedTransform;
|
|
result.modelMatrix = modelMatrix;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Builds an array of instance transforms for a node.
|
|
* If the node is not instanced, returns an array containing only the
|
|
* computedModelMatrix.
|
|
*
|
|
* @param {object} runtimeNode The runtime node.
|
|
* @param {Matrix4} computedModelMatrix The computed model matrix.
|
|
* @param {Matrix4} nodeComputedTransform The node computed transform.
|
|
* @param {Matrix4} modelMatrix The model matrix.
|
|
* @returns {Matrix4[]}
|
|
*/
|
|
static computeInstanceTransforms(
|
|
runtimeNode,
|
|
computedModelMatrix,
|
|
nodeComputedTransform,
|
|
modelMatrix,
|
|
) {
|
|
const transforms = [];
|
|
const node = runtimeNode.node;
|
|
const instances = node.instances;
|
|
|
|
if (defined(instances)) {
|
|
const transformsCount = instances.attributes[0].count;
|
|
const instanceComponentDatatype =
|
|
instances.attributes[0].componentDatatype;
|
|
|
|
const transformElements = 12;
|
|
let transformsTypedArray = runtimeNode.transformsTypedArray;
|
|
|
|
if (!defined(transformsTypedArray)) {
|
|
const instanceTransformsBuffer = runtimeNode.instancingTransformsBuffer;
|
|
if (defined(instanceTransformsBuffer)) {
|
|
transformsTypedArray = ComponentDatatype.createTypedArray(
|
|
instanceComponentDatatype,
|
|
transformsCount * transformElements,
|
|
);
|
|
instanceTransformsBuffer.getBufferData(transformsTypedArray);
|
|
}
|
|
}
|
|
|
|
if (defined(transformsTypedArray)) {
|
|
ModelReader.computeInstanceTransformsFromTypedArray(
|
|
transformsTypedArray,
|
|
transformsCount,
|
|
transforms,
|
|
);
|
|
} else {
|
|
ModelReader.computeInstanceTransformsFromAttributes(
|
|
instances,
|
|
transformsCount,
|
|
transforms,
|
|
);
|
|
}
|
|
|
|
for (let i = 0; i < transforms.length; i++) {
|
|
const transform = transforms[i];
|
|
if (instances.transformInWorldSpace) {
|
|
Matrix4.multiplyTransformation(
|
|
transform,
|
|
nodeComputedTransform,
|
|
transform,
|
|
);
|
|
Matrix4.multiplyTransformation(modelMatrix, transform, transform);
|
|
} else {
|
|
Matrix4.multiplyTransformation(
|
|
computedModelMatrix,
|
|
transform,
|
|
transform,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (transforms.length === 0) {
|
|
transforms.push(computedModelMatrix);
|
|
}
|
|
|
|
return transforms;
|
|
}
|
|
|
|
/**
|
|
* Builds an array of per-instance feature IDs for a node.
|
|
* If the node is not instanced or has no matching feature ID set,
|
|
* returns <code>undefined</code>.
|
|
*
|
|
* @param {object} runtimeNode The runtime node.
|
|
* @param {string} instanceFeatureIdLabel The label used to select the feature ID set.
|
|
* @returns {number[]|undefined} The per-instance feature IDs, or undefined.
|
|
*/
|
|
static computeInstanceFeatureIds(runtimeNode, instanceFeatureIdLabel) {
|
|
const node = runtimeNode.node;
|
|
const instances = node.instances;
|
|
|
|
if (!defined(instances)) {
|
|
return undefined;
|
|
}
|
|
|
|
const featureIdSet = ModelUtility.getFeatureIdsByLabel(
|
|
instances.featureIds,
|
|
instanceFeatureIdLabel,
|
|
);
|
|
|
|
if (!defined(featureIdSet)) {
|
|
return undefined;
|
|
}
|
|
|
|
let typedArray;
|
|
|
|
// Case: FeatureIdAttribute
|
|
if ("setIndex" in featureIdSet) {
|
|
const attribute = ModelUtility.getAttributeBySemantic(
|
|
instances,
|
|
VertexAttributeSemantic.FEATURE_ID,
|
|
featureIdSet.setIndex,
|
|
);
|
|
if (defined(attribute)) {
|
|
typedArray = ModelReader.readAttributeAsTypedArray(attribute);
|
|
}
|
|
}
|
|
|
|
// Case: FeatureIdImplicitRange
|
|
if ("offset" in featureIdSet) {
|
|
typedArray = ModelReader.readImplicitRangeAsTypedArray(
|
|
featureIdSet,
|
|
instances,
|
|
);
|
|
}
|
|
|
|
if (!defined(typedArray)) {
|
|
return undefined;
|
|
}
|
|
|
|
const featureIds = new Array(typedArray.length);
|
|
for (let i = 0; i < typedArray.length; i++) {
|
|
featureIds[i] = typedArray[i];
|
|
}
|
|
return featureIds;
|
|
}
|
|
|
|
/**
|
|
* Builds instance transforms from a packed typed array where each instance
|
|
* is stored as 12 floats (3 rows of 4 columns, row-major).
|
|
*
|
|
* @param {TypedArray} transformsTypedArray The packed transforms array.
|
|
* @param {number} count The number of instances.
|
|
* @param {Matrix4[]} transforms The output array to push transforms into.
|
|
*/
|
|
static computeInstanceTransformsFromTypedArray(
|
|
transformsTypedArray,
|
|
count,
|
|
transforms,
|
|
) {
|
|
const transformElements = 12;
|
|
for (let i = 0; i < count; i++) {
|
|
const index = i * transformElements;
|
|
|
|
const transform = new Matrix4(
|
|
transformsTypedArray[index],
|
|
transformsTypedArray[index + 1],
|
|
transformsTypedArray[index + 2],
|
|
transformsTypedArray[index + 3],
|
|
transformsTypedArray[index + 4],
|
|
transformsTypedArray[index + 5],
|
|
transformsTypedArray[index + 6],
|
|
transformsTypedArray[index + 7],
|
|
transformsTypedArray[index + 8],
|
|
transformsTypedArray[index + 9],
|
|
transformsTypedArray[index + 10],
|
|
transformsTypedArray[index + 11],
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
);
|
|
|
|
transforms.push(transform);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds instance transforms from individual TRANSLATION, ROTATION, and SCALE
|
|
* attributes when no packed transformsTypedArray is available.
|
|
*
|
|
* @param {object} instances The instances object.
|
|
* @param {number} count The number of instances.
|
|
* @param {Matrix4[]} transforms The output array to push transforms into.
|
|
*/
|
|
static computeInstanceTransformsFromAttributes(instances, count, transforms) {
|
|
const translationAttribute = ModelUtility.getAttributeBySemantic(
|
|
instances,
|
|
InstanceAttributeSemantic.TRANSLATION,
|
|
);
|
|
const rotationAttribute = ModelUtility.getAttributeBySemantic(
|
|
instances,
|
|
InstanceAttributeSemantic.ROTATION,
|
|
);
|
|
const scaleAttribute = ModelUtility.getAttributeBySemantic(
|
|
instances,
|
|
InstanceAttributeSemantic.SCALE,
|
|
);
|
|
|
|
const hasTranslation = defined(translationAttribute);
|
|
const hasRotation = defined(rotationAttribute);
|
|
const hasScale = defined(scaleAttribute);
|
|
|
|
if (!hasTranslation && !hasRotation && !hasScale) {
|
|
return;
|
|
}
|
|
|
|
let translationTypedArray, rotationTypedArray, scaleTypedArray;
|
|
if (hasTranslation) {
|
|
translationTypedArray =
|
|
ModelReader.readAttributeAsRawCompactTypedArray(translationAttribute);
|
|
}
|
|
if (hasRotation) {
|
|
rotationTypedArray =
|
|
ModelReader.readAttributeAsRawCompactTypedArray(rotationAttribute);
|
|
}
|
|
if (hasScale) {
|
|
scaleTypedArray =
|
|
ModelReader.readAttributeAsRawCompactTypedArray(scaleAttribute);
|
|
}
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const translation = hasTranslation
|
|
? new Cartesian3(
|
|
translationTypedArray[i * 3],
|
|
translationTypedArray[i * 3 + 1],
|
|
translationTypedArray[i * 3 + 2],
|
|
)
|
|
: Cartesian3.ZERO;
|
|
|
|
const rotation = hasRotation
|
|
? new Quaternion(
|
|
rotationTypedArray[i * 4],
|
|
rotationTypedArray[i * 4 + 1],
|
|
rotationTypedArray[i * 4 + 2],
|
|
rotationTypedArray[i * 4 + 3],
|
|
)
|
|
: Quaternion.IDENTITY;
|
|
|
|
const scale = hasScale
|
|
? new Cartesian3(
|
|
scaleTypedArray[i * 3],
|
|
scaleTypedArray[i * 3 + 1],
|
|
scaleTypedArray[i * 3 + 2],
|
|
)
|
|
: Cartesian3.ONE;
|
|
|
|
const transform = Matrix4.fromTranslationQuaternionRotationScale(
|
|
translation,
|
|
rotation,
|
|
scale,
|
|
new Matrix4(),
|
|
);
|
|
|
|
transforms.push(transform);
|
|
}
|
|
}
|
|
}
|
|
|
|
export default ModelReader;
|