import { defined, defaultValue, FeatureDetection, PropertyTable, MetadataClass, MetadataComponentType, MetadataEnum, MetadataTable, MetadataType, } from "../Source/Cesium.js"; function MetadataTester() {} MetadataTester.isSupported = function () { return ( FeatureDetection.supportsBigInt64Array() && FeatureDetection.supportsBigUint64Array() && FeatureDetection.supportsBigInt() && typeof TextEncoder !== "undefined" ); }; MetadataTester.createProperty = function (options) { var properties = { propertyId: options.property, }; var propertyValues = { propertyId: options.values, }; var table = MetadataTester.createMetadataTable({ properties: properties, propertyValues: propertyValues, // offsetType is for legacy EXT_feature_metadata, arrayOffsetType and // stringOffsetType are for EXT_mesh_features offsetType: options.offsetType, arrayOffsetType: options.arrayOffsetType, stringOffsetType: options.stringOffsetType, enums: options.enums, disableBigIntSupport: options.disableBigIntSupport, disableBigInt64ArraySupport: options.disableBigInt64ArraySupport, disableBigUint64ArraySupport: options.disableBigUint64ArraySupport, }); return table._properties.propertyId; }; function createProperties(options) { var schema = options.schema; var classId = options.classId; var propertyValues = options.propertyValues; var offsetType = options.offsetType; var stringOffsetType = options.stringOffsetType; var arrayOffsetType = options.arrayOffsetType; var bufferViews = defined(options.bufferViews) ? options.bufferViews : {}; var enums = defined(schema.enums) ? schema.enums : {}; var enumDefinitions = {}; for (var enumId in enums) { if (enums.hasOwnProperty(enumId)) { enumDefinitions[enumId] = new MetadataEnum({ id: enumId, enum: enums[enumId], }); } } var classDefinition = new MetadataClass({ id: classId, class: schema.classes[classId], enums: enumDefinitions, }); var properties = {}; var bufferViewIndex = Object.keys(bufferViews).length; var count = 0; for (var propertyId in propertyValues) { if (propertyValues.hasOwnProperty(propertyId)) { var classProperty = classDefinition.properties[propertyId]; var values = propertyValues[propertyId]; count = values.length; var valuesBuffer = addPadding(createValuesBuffer(values, classProperty)); var valuesBufferView = bufferViewIndex++; bufferViews[valuesBufferView] = valuesBuffer; var property = { bufferView: valuesBufferView, }; properties[propertyId] = property; // for legacy EXT_feature_metadata if (defined(offsetType)) { property.offsetType = offsetType; } if (defined(stringOffsetType)) { property.stringOffsetType = offsetType; } if (defined(arrayOffsetType)) { property.arrayOffsetType = arrayOffsetType; } if ( classProperty.type === MetadataType.ARRAY && !defined(classProperty.componentCount) ) { var arrayOffsetBufferType = defaultValue(arrayOffsetType, offsetType); var arrayOffsetBuffer = addPadding( createArrayOffsetBuffer(values, arrayOffsetBufferType) ); var arrayOffsetBufferView = bufferViewIndex++; bufferViews[arrayOffsetBufferView] = arrayOffsetBuffer; property.arrayOffsetBufferView = arrayOffsetBufferView; } if (classProperty.componentType === MetadataComponentType.STRING) { var stringOffsetBufferType = defaultValue(stringOffsetType, offsetType); var stringOffsetBuffer = addPadding( createStringOffsetBuffer(values, stringOffsetBufferType) ); var stringOffsetBufferView = bufferViewIndex++; bufferViews[stringOffsetBufferView] = stringOffsetBuffer; property.stringOffsetBufferView = stringOffsetBufferView; } } } return { count: count, properties: properties, class: classDefinition, bufferViews: bufferViews, }; } MetadataTester.createMetadataTable = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var disableBigIntSupport = options.disableBigIntSupport; var disableBigInt64ArraySupport = options.disableBigInt64ArraySupport; var disableBigUint64ArraySupport = options.disableBigUint64ArraySupport; var schema = { enums: options.enums, classes: { classId: { properties: options.properties, }, }, }; var propertyResults = createProperties({ schema: schema, classId: "classId", propertyValues: options.propertyValues, offsetType: options.offsetType, }); var count = propertyResults.count; var properties = propertyResults.properties; var classDefinition = propertyResults.class; var bufferViews = propertyResults.bufferViews; if (disableBigIntSupport) { spyOn(FeatureDetection, "supportsBigInt").and.returnValue(false); } if (disableBigInt64ArraySupport) { spyOn(FeatureDetection, "supportsBigInt64Array").and.returnValue(false); } if (disableBigUint64ArraySupport) { spyOn(FeatureDetection, "supportsBigUint64Array").and.returnValue(false); } return new MetadataTable({ count: count, properties: properties, class: classDefinition, bufferViews: bufferViews, }); }; MetadataTester.createPropertyTable = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var disableBigIntSupport = options.disableBigIntSupport; var disableBigInt64ArraySupport = options.disableBigInt64ArraySupport; var disableBigUint64ArraySupport = options.disableBigUint64ArraySupport; var schema = { enums: options.enums, classes: { classId: { properties: options.properties, }, }, }; var propertyResults = createProperties({ schema: schema, classId: "classId", propertyValues: options.propertyValues, offsetType: options.offsetType, }); var count = propertyResults.count; var properties = propertyResults.properties; var classDefinition = propertyResults.class; var bufferViews = propertyResults.bufferViews; if (disableBigIntSupport) { spyOn(FeatureDetection, "supportsBigInt").and.returnValue(false); } if (disableBigInt64ArraySupport) { spyOn(FeatureDetection, "supportsBigInt64Array").and.returnValue(false); } if (disableBigUint64ArraySupport) { spyOn(FeatureDetection, "supportsBigUint64Array").and.returnValue(false); } var metadataTable = new MetadataTable({ count: count, class: classDefinition, bufferViews: bufferViews, properties: properties, }); return new PropertyTable({ metadataTable: metadataTable, count: count, extras: options.extras, extensions: options.extensions, }); }; // for EXT_mesh_features MetadataTester.createPropertyTables = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var propertyTables = []; var bufferViews = {}; for (var i = 0; i < options.propertyTables.length; i++) { var propertyTable = options.propertyTables[i]; var tablePropertyResults = createProperties({ schema: options.schema, classId: propertyTable.class, propertyValues: propertyTable.properties, bufferViews: bufferViews, }); var count = tablePropertyResults.count; var properties = tablePropertyResults.properties; propertyTables.push({ name: propertyTable.name, class: propertyTable.class, count: count, properties: properties, }); } return { propertyTables: propertyTables, bufferViews: bufferViews, }; }; // For EXT_feature_metadata MetadataTester.createFeatureTables = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var featureTables = {}; var bufferViews = {}; for (var featureTableId in options.featureTables) { if (options.featureTables.hasOwnProperty(featureTableId)) { var featureTable = options.featureTables[featureTableId]; var propertyResults = createProperties({ schema: options.schema, classId: featureTable.class, propertyValues: featureTable.properties, bufferViews: bufferViews, }); var count = propertyResults.count; var properties = propertyResults.properties; featureTables[featureTableId] = { class: featureTable.class, count: count, properties: properties, }; } } return { featureTables: featureTables, bufferViews: bufferViews, }; }; MetadataTester.createGltf = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var propertyTableResults = MetadataTester.createPropertyTables(options); var bufferByteLength = 0; var bufferViewsMap = propertyTableResults.bufferViews; var bufferViewsLength = Object.keys(bufferViewsMap).length; var byteLengths = new Array(bufferViewsLength); var bufferViewId; var uint8Array; for (bufferViewId in bufferViewsMap) { if (bufferViewsMap.hasOwnProperty(bufferViewId)) { uint8Array = bufferViewsMap[bufferViewId]; var remainder = uint8Array.byteLength % 8; var padding = remainder === 0 ? 0 : 8 - remainder; var byteLength = uint8Array.byteLength + padding; bufferByteLength += byteLength; byteLengths[bufferViewId] = byteLength; } } var buffer = new Uint8Array(bufferByteLength); var bufferViews = new Array(bufferViewsLength); var byteOffset = 0; for (bufferViewId in bufferViewsMap) { if (bufferViewsMap.hasOwnProperty(bufferViewId)) { uint8Array = bufferViewsMap[bufferViewId]; bufferViews[bufferViewId] = { buffer: 0, byteOffset: byteOffset, byteLength: uint8Array.byteLength, }; buffer.set(uint8Array, byteOffset); byteOffset += byteLengths[bufferViewId]; } } var gltf = { buffers: [ { uri: "external.bin", byteLength: buffer.byteLength, }, ], images: options.images, textures: options.textures, bufferViews: bufferViews, extensionsUsed: ["EXT_mesh_features"], extensions: { EXT_mesh_features: { schema: options.schema, propertyTables: propertyTableResults.propertyTables, propertyTextures: options.propertyTextures, }, }, }; return { gltf: gltf, buffer: buffer, }; }; function createBuffer(values, componentType) { var typedArray; switch (componentType) { case MetadataComponentType.INT8: typedArray = new Int8Array(values); break; case MetadataComponentType.UINT8: typedArray = new Uint8Array(values); break; case MetadataComponentType.INT16: typedArray = new Int16Array(values); break; case MetadataComponentType.UINT16: typedArray = new Uint16Array(values); break; case MetadataComponentType.INT32: typedArray = new Int32Array(values); break; case MetadataComponentType.UINT32: typedArray = new Uint32Array(values); break; case MetadataComponentType.INT64: typedArray = new BigInt64Array(values); // eslint-disable-line break; case MetadataComponentType.UINT64: typedArray = new BigUint64Array(values); // eslint-disable-line break; case MetadataComponentType.FLOAT32: typedArray = new Float32Array(values); break; case MetadataComponentType.FLOAT64: typedArray = new Float64Array(values); break; case MetadataComponentType.STRING: var encoder = new TextEncoder(); typedArray = encoder.encode(values.join("")); break; case MetadataComponentType.BOOLEAN: var length = Math.ceil(values.length / 8); typedArray = new Uint8Array(length); // Initialized as 0's for (var i = 0; i < values.length; ++i) { var byteIndex = i >> 3; var bitIndex = i % 8; if (values[i]) { typedArray[byteIndex] |= 1 << bitIndex; } } break; } return new Uint8Array(typedArray.buffer); } function flatten(values) { return [].concat.apply([], values); } function createValuesBuffer(values, classProperty) { var valueType = classProperty.valueType; var enumType = classProperty.enumType; var flattenedValues = flatten(values); if (defined(enumType)) { var length = flattenedValues.length; for (var i = 0; i < length; ++i) { flattenedValues[i] = enumType.valuesByName[flattenedValues[i]]; } } return createBuffer(flattenedValues, valueType); } function createStringOffsetBuffer(values, offsetType) { var encoder = new TextEncoder(); var strings = flatten(values); var length = strings.length; var offsets = new Array(length + 1); var offset = 0; for (var i = 0; i < length; ++i) { offsets[i] = offset; offset += encoder.encode(strings[i]).length; } offsets[length] = offset; offsetType = defaultValue(offsetType, MetadataComponentType.UINT32); return createBuffer(offsets, offsetType); } function createArrayOffsetBuffer(values, offsetType) { var length = values.length; var offsets = new Array(length + 1); var offset = 0; for (var i = 0; i < length; ++i) { offsets[i] = offset; offset += values[i].length; } offsets[length] = offset; offsetType = defaultValue(offsetType, MetadataComponentType.UINT32); return createBuffer(offsets, offsetType); } function addPadding(uint8Array) { // This tests that MetadataTable uses the Uint8Array's byteOffset properly var paddingBytes = 8; var padded = new Uint8Array(paddingBytes + uint8Array.length); padded.set(uint8Array, paddingBytes); return new Uint8Array(padded.buffer, paddingBytes, uint8Array.length); } export default MetadataTester;