UNPKG

@loaders.gl/gltf

Version:

Framework-independent loader for the glTF format

300 lines 14 kB
import { GLTFScenegraph } from "../../api/gltf-scenegraph.js"; import { convertRawBufferToMetadataArray, getPrimitiveTextureData, primitivePropertyDataToAttributes, getArrayElementByteSize, getOffsetsForProperty, parseVariableLengthArrayNumeric, parseFixedLengthArrayNumeric, getPropertyDataString } from "../utils/3d-tiles-utils.js"; /** Extension name */ const EXT_FEATURE_METADATA_NAME = 'EXT_feature_metadata'; export const name = EXT_FEATURE_METADATA_NAME; export async function decode(gltfData, options) { const scenegraph = new GLTFScenegraph(gltfData); decodeExtFeatureMetadata(scenegraph, options); } /** * Decodes feature metadata from extension. * @param scenegraph - Instance of the class for structured access to GLTF data. * @param options - GLTFLoader options. */ function decodeExtFeatureMetadata(scenegraph, options) { // Decoding metadata involves buffers processing. // So, if buffers have not been loaded, there is no reason to process metadata. if (!options.gltf?.loadBuffers) { return; } const extension = scenegraph.getExtension(EXT_FEATURE_METADATA_NAME); if (!extension) { return; } if (options.gltf?.loadImages) { decodePropertyTextures(scenegraph, extension); } decodePropertyTables(scenegraph, extension); } /** * Processes the data stored in the textures * @param scenegraph - Instance of the class for structured access to GLTF data. * @param extension - Top-level extension. */ function decodePropertyTextures(scenegraph, extension) { const schema = extension.schema; if (!schema) { return; } const schemaClasses = schema.classes; const { featureTextures } = extension; if (schemaClasses && featureTextures) { for (const schemaName in schemaClasses) { const schemaClass = schemaClasses[schemaName]; const featureTexture = findFeatureTextureByClass(featureTextures, schemaName); if (featureTexture) { handleFeatureTextureProperties(scenegraph, featureTexture, schemaClass); } } } } /** * Processes the data stored in the property tables. * @param scenegraph - Instance of the class for structured access to GLTF data. * @param extension - Top-level extension. */ function decodePropertyTables(scenegraph, extension) { const schema = extension.schema; if (!schema) { return; } const schemaClasses = schema.classes; const propertyTables = extension.featureTables; if (schemaClasses && propertyTables) { for (const schemaName in schemaClasses) { const propertyTable = findPropertyTableByClass(propertyTables, schemaName); if (propertyTable) { processPropertyTable(scenegraph, schema, propertyTable); } } } } /** * Finds the property table by class name. * @param propertyTables - propertyTable definition taken from the top-level extension. * @param schemaClassName - class name in the extension schema. */ function findPropertyTableByClass(propertyTables, schemaClassName) { for (const propertyTableName in propertyTables) { const propertyTable = propertyTables[propertyTableName]; if (propertyTable.class === schemaClassName) { return propertyTable; } } return null; } function findFeatureTextureByClass(featureTextures, schemaClassName) { for (const featureTexturesName in featureTextures) { const featureTable = featureTextures[featureTexturesName]; if (featureTable.class === schemaClassName) { return featureTable; } } return null; } /** * Navigates through all properies in the property table, gets properties data, * and put the data to `propertyTable.data` as an array. * @param scenegraph - Instance of the class for structured access to GLTF data. * @param schema - schema object. * @param propertyTable - propertyTable definition taken from the top-level extension. */ function processPropertyTable(scenegraph, schema, propertyTable) { // Though 'class' is not required by spec, it doesn't make any scence when it's not provided. // So, bale out here. if (!propertyTable.class) { return; } const schemaClass = schema.classes?.[propertyTable.class]; if (!schemaClass) { throw new Error(`Incorrect data in the EXT_structural_metadata extension: no schema class with name ${propertyTable.class}`); } const numberOfElements = propertyTable.count; // `propertyTable.count` is a number of elements in each property array. for (const propertyName in schemaClass.properties) { const classProperty = schemaClass.properties[propertyName]; const propertyTableProperty = propertyTable.properties?.[propertyName]; if (propertyTableProperty) { // Getting all elements (`numberOfElements`) of the array in the `propertyTableProperty` const data = getPropertyDataFromBinarySource(scenegraph, schema, classProperty, numberOfElements, propertyTableProperty); propertyTableProperty.data = data; } } } /** * Navigates through all properies in feature texture and gets properties data. * Data will be stored in featureTexture.properties[propertyName].data. * @param scenegraph - Instance of the class for structured access to GLTF data. * @param featureTexture * @param schemaClass */ function handleFeatureTextureProperties(scenegraph, featureTexture, schemaClass) { const attributeName = featureTexture.class; for (const propertyName in schemaClass.properties) { const featureTextureProperty = featureTexture?.properties?.[propertyName]; if (featureTextureProperty) { const data = getPropertyDataFromTexture(scenegraph, featureTextureProperty, attributeName); featureTextureProperty.data = data; } } } /** * Decodes properties from binary sourse based on property type. * @param scenegraph - Instance of the class for structured access to GLTF data. * @param schemaProperty * @param numberOfFeatures * @param featureTableProperty */ function getPropertyDataFromBinarySource(scenegraph, schema, classProperty, numberOfFeatures, featureTableProperty) { let data = []; const bufferView = featureTableProperty.bufferView; const dataArray = scenegraph.getTypedArrayForBufferView(bufferView); const arrayOffsets = getArrayOffsetsForProperty(scenegraph, classProperty, featureTableProperty, numberOfFeatures); const stringOffsets = getStringOffsetsForProperty(scenegraph, classProperty, featureTableProperty, numberOfFeatures); if (classProperty.type === 'STRING' || classProperty.componentType === 'STRING') { data = getPropertyDataString(numberOfFeatures, dataArray, arrayOffsets, stringOffsets); } else if (isNumericProperty(classProperty)) { data = getPropertyDataNumeric(classProperty, numberOfFeatures, dataArray, arrayOffsets); } return data; } /** * Parses propertyTable.property.arrayOffsets that are offsets of sub-arrays in a flatten array of values. * @param scenegraph - Instance of the class for structured access to GLTF data. * @param classProperty - class property object. * @param propertyTableProperty - propertyTable's property metadata. * @param numberOfElements - The number of elements in each property array that propertyTableProperty contains. It's a number of rows in the table. * @returns Typed array with offset values. * @see https://github.com/CesiumGS/glTF/blob/2976f1183343a47a29e4059a70961371cd2fcee8/extensions/2.0/Vendor/EXT_structural_metadata/schema/propertyTable.property.schema.json#L21 */ function getArrayOffsetsForProperty(scenegraph, classProperty, propertyTableProperty, numberOfElements) { /* If ARRAY is used, then componentType must also be specified. ARRAY is a fixed-length array when componentCount is defined, and variable-length otherwise. */ if (classProperty.type === 'ARRAY' && // `componentCount` is a number of fixed-length array elements. // If `componentCount` is NOT defined, it's a VARIABLE-length array typeof classProperty.componentCount === 'undefined' && // `arrayOffsetBufferView` is an index of the buffer view containing offsets for variable-length arrays. typeof propertyTableProperty.arrayOffsetBufferView !== 'undefined') { // Data are in a VARIABLE-length array return getOffsetsForProperty(scenegraph, propertyTableProperty.arrayOffsetBufferView, propertyTableProperty.offsetType || 'UINT32', // offsetType is used both for stringOffsetBufferView and arrayOffsetBufferView numberOfElements); } return null; } /** * Parses featureTable.property.stringOffsetBufferView. * String offsets is an array of offsets of strings in the united array of characters. * @param scenegraph - Instance of the class for structured access to GLTF data. * @param propertyTableProperty - propertyTable's property metadata. * @param numberOfElements - The number of elements in each property array that propertyTableProperty contains. It's a number of rows in the table. * @returns Typed array of offset values. The number of offsets in the array is equal to `numberOfElements` plus one. * @see https://github.com/CesiumGS/glTF/blob/c38f7f37e894004353c15cd0481bc5b7381ce841/extensions/2.0/Vendor/EXT_feature_metadata/schema/featureTable.property.schema.json#L50C10-L50C32 */ function getStringOffsetsForProperty(scenegraph, classProperty, propertyTableProperty, numberOfElements) { if (typeof propertyTableProperty.stringOffsetBufferView !== 'undefined' // `stringOffsetBufferView` is an index of the buffer view containing offsets for strings. ) { // Data are in a FIXED-length array return getOffsetsForProperty(scenegraph, propertyTableProperty.stringOffsetBufferView, propertyTableProperty.offsetType || 'UINT32', // offsetType is used both for stringOffsetBufferView and arrayOffsetBufferView numberOfElements); } return null; } /** * Checks if the feature table property is of numeric type. * @param schemaPropertyType - feature table property * @returns true if property is numeric, else - false */ function isNumericProperty(schemaProperty) { const types = [ 'UINT8', 'INT16', 'UINT16', 'INT32', 'UINT32', 'INT64', 'UINT64', 'FLOAT32', 'FLOAT64' ]; return (types.includes(schemaProperty.type) || (typeof schemaProperty.componentType !== 'undefined' && types.includes(schemaProperty.componentType))); } /** * Decodes properties of numeric types from binary sourse. * @param classProperty - class property object. * @param numberOfElements - The number of elements in each property array that propertyTableProperty contains. It's a number of rows in the table. * @param valuesDataBytes - Data taken from values property of the property table property. * @param arrayOffsets - Offsets for variable-length arrays. It's null for fixed-length arrays or scalar types. * @returns Property values in a typed array or in an array of typed arrays. */ function getPropertyDataNumeric(classProperty, numberOfElements, valuesDataBytes, arrayOffsets) { const isArray = classProperty.type === 'ARRAY'; const arrayCount = classProperty.componentCount; /* We are getting Numeric data. So, the component type can be one of NumericComponentType, the attribute type should be 'SCALAR' */ const attributeType = 'SCALAR'; const componentType = classProperty.componentType || classProperty.type; const elementSize = getArrayElementByteSize(attributeType, componentType); const elementCount = valuesDataBytes.byteLength / elementSize; const valuesData = convertRawBufferToMetadataArray(valuesDataBytes, attributeType, componentType, elementCount); if (isArray) { if (arrayOffsets) { // VARIABLE-length array return parseVariableLengthArrayNumeric(valuesData, numberOfElements, arrayOffsets, valuesDataBytes.length, elementSize); } if (arrayCount) { // FIXED-length array return parseFixedLengthArrayNumeric(valuesData, numberOfElements, arrayCount); } return []; } return valuesData; } /** * Gets properties from texture associated with all mesh primitives. * @param scenegraph - Instance of the class for structured access to GLTF data. * @param featureTextureProperty * @param attributeName * @returns Feature texture data */ function getPropertyDataFromTexture(scenegraph, featureTextureProperty, attributeName) { const json = scenegraph.gltf.json; if (!json.meshes) { return []; } const featureTextureTable = []; for (const mesh of json.meshes) { for (const primitive of mesh.primitives) { processPrimitiveTextures(scenegraph, attributeName, featureTextureProperty, featureTextureTable, primitive); } } return featureTextureTable; } /** * Processes data encoded in the texture associated with the primitive. This data will be accessible through the attributes. * @param scenegraph - Instance of the class for structured access to GLTF data. * @param attributeName * @param featureTextureProperty * @param featureTextureTable * @param primitive */ function processPrimitiveTextures(scenegraph, attributeName, featureTextureProperty, featureTextureTable, primitive) { const textureInfoTopLevel = { channels: featureTextureProperty.channels, ...featureTextureProperty.texture }; const propertyData = getPrimitiveTextureData(scenegraph, textureInfoTopLevel, primitive); if (!propertyData) { return; } primitivePropertyDataToAttributes(scenegraph, attributeName, propertyData, featureTextureTable, primitive); } //# sourceMappingURL=EXT_feature_metadata.js.map