mirror of https://github.com/CesiumGS/cesium.git
510 lines
14 KiB
JavaScript
510 lines
14 KiB
JavaScript
import {
|
|
defined,
|
|
Frozen,
|
|
DeveloperError,
|
|
FeatureDetection,
|
|
PropertyTable,
|
|
MetadataClass,
|
|
MetadataComponentType,
|
|
MetadataEnum,
|
|
MetadataTable,
|
|
MetadataType,
|
|
} from "@cesium/engine";
|
|
|
|
function MetadataTester() {}
|
|
|
|
MetadataTester.isSupported = function () {
|
|
return (
|
|
FeatureDetection.supportsBigInt64Array() &&
|
|
FeatureDetection.supportsBigUint64Array() &&
|
|
FeatureDetection.supportsBigInt() &&
|
|
typeof TextEncoder !== "undefined"
|
|
);
|
|
};
|
|
|
|
MetadataTester.createProperty = function (options) {
|
|
const properties = {
|
|
propertyId: options.property,
|
|
};
|
|
const propertyValues = {
|
|
propertyId: options.values,
|
|
};
|
|
|
|
const table = MetadataTester.createMetadataTable({
|
|
properties: properties,
|
|
propertyValues: propertyValues,
|
|
// offsetType is for legacy EXT_feature_metadata, arrayOffsetType and
|
|
// stringOffsetType are for EXT_structural_metadata
|
|
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) {
|
|
const schema = options.schema;
|
|
const classId = options.classId;
|
|
const propertyValues = options.propertyValues;
|
|
const offsetType = options.offsetType;
|
|
const stringOffsetType = options.stringOffsetType;
|
|
const arrayOffsetType = options.arrayOffsetType;
|
|
const bufferViews = defined(options.bufferViews) ? options.bufferViews : {};
|
|
|
|
const enums = defined(schema.enums) ? schema.enums : {};
|
|
const enumDefinitions = {};
|
|
for (const enumId in enums) {
|
|
if (enums.hasOwnProperty(enumId)) {
|
|
enumDefinitions[enumId] = MetadataEnum.fromJson({
|
|
id: enumId,
|
|
enum: enums[enumId],
|
|
});
|
|
}
|
|
}
|
|
|
|
const classDefinition = MetadataClass.fromJson({
|
|
id: classId,
|
|
class: schema.classes[classId],
|
|
enums: enumDefinitions,
|
|
});
|
|
|
|
const properties = {};
|
|
let bufferViewIndex = Object.keys(bufferViews).length;
|
|
let count = 0;
|
|
|
|
for (const propertyId in propertyValues) {
|
|
if (propertyValues.hasOwnProperty(propertyId)) {
|
|
const classProperty = classDefinition.properties[propertyId];
|
|
const values = propertyValues[propertyId];
|
|
count = values.length;
|
|
|
|
const valuesBuffer = addPadding(
|
|
createValuesBuffer(values, classProperty),
|
|
);
|
|
const valuesBufferView = bufferViewIndex++;
|
|
bufferViews[valuesBufferView] = valuesBuffer;
|
|
|
|
const property = {
|
|
values: valuesBufferView,
|
|
};
|
|
|
|
properties[propertyId] = property;
|
|
|
|
// for legacy EXT_feature_metadata
|
|
if (defined(offsetType)) {
|
|
property.offsetType = offsetType;
|
|
}
|
|
|
|
if (defined(stringOffsetType)) {
|
|
property.stringOffsetType = stringOffsetType;
|
|
}
|
|
|
|
if (defined(arrayOffsetType)) {
|
|
property.arrayOffsetType = arrayOffsetType;
|
|
}
|
|
|
|
if (classProperty.isVariableLengthArray) {
|
|
const arrayOffsetBufferType = arrayOffsetType ?? offsetType;
|
|
const arrayOffsetBuffer = addPadding(
|
|
createArrayOffsetBuffer(
|
|
values,
|
|
classProperty.type,
|
|
arrayOffsetBufferType,
|
|
),
|
|
);
|
|
const arrayOffsetBufferView = bufferViewIndex++;
|
|
bufferViews[arrayOffsetBufferView] = arrayOffsetBuffer;
|
|
property.arrayOffsets = arrayOffsetBufferView;
|
|
}
|
|
|
|
if (classProperty.type === MetadataType.STRING) {
|
|
const stringOffsetBufferType = stringOffsetType ?? offsetType;
|
|
const stringOffsetBuffer = addPadding(
|
|
createStringOffsetBuffer(values, stringOffsetBufferType),
|
|
);
|
|
const stringOffsetBufferView = bufferViewIndex++;
|
|
bufferViews[stringOffsetBufferView] = stringOffsetBuffer;
|
|
property.stringOffsets = stringOffsetBufferView;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
count: count,
|
|
properties: properties,
|
|
class: classDefinition,
|
|
bufferViews: bufferViews,
|
|
};
|
|
}
|
|
|
|
MetadataTester.createMetadataTable = function (options) {
|
|
options = options ?? Frozen.EMPTY_OBJECT;
|
|
const disableBigIntSupport = options.disableBigIntSupport;
|
|
const disableBigInt64ArraySupport = options.disableBigInt64ArraySupport;
|
|
const disableBigUint64ArraySupport = options.disableBigUint64ArraySupport;
|
|
|
|
const schema = {
|
|
enums: options.enums,
|
|
classes: {
|
|
classId: {
|
|
properties: options.properties,
|
|
},
|
|
},
|
|
};
|
|
|
|
const propertyResults = createProperties({
|
|
schema: schema,
|
|
classId: "classId",
|
|
propertyValues: options.propertyValues,
|
|
offsetType: options.offsetType,
|
|
});
|
|
|
|
const count = propertyResults.count;
|
|
const properties = propertyResults.properties;
|
|
const classDefinition = propertyResults.class;
|
|
const 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 = options ?? Frozen.EMPTY_OBJECT;
|
|
const disableBigIntSupport = options.disableBigIntSupport;
|
|
const disableBigInt64ArraySupport = options.disableBigInt64ArraySupport;
|
|
const disableBigUint64ArraySupport = options.disableBigUint64ArraySupport;
|
|
|
|
const schema = {
|
|
enums: options.enums,
|
|
classes: {
|
|
classId: {
|
|
properties: options.properties,
|
|
},
|
|
},
|
|
};
|
|
|
|
const propertyResults = createProperties({
|
|
schema: schema,
|
|
classId: "classId",
|
|
propertyValues: options.propertyValues,
|
|
offsetType: options.offsetType,
|
|
});
|
|
|
|
const count = propertyResults.count;
|
|
const properties = propertyResults.properties;
|
|
const classDefinition = propertyResults.class;
|
|
const 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);
|
|
}
|
|
|
|
const 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_structural_metadata
|
|
MetadataTester.createPropertyTables = function (options) {
|
|
options = options ?? Frozen.EMPTY_OBJECT;
|
|
|
|
const propertyTables = [];
|
|
const bufferViews = {};
|
|
|
|
for (let i = 0; i < options.propertyTables.length; i++) {
|
|
const propertyTable = options.propertyTables[i];
|
|
const tablePropertyResults = createProperties({
|
|
schema: options.schema,
|
|
classId: propertyTable.class,
|
|
propertyValues: propertyTable.properties,
|
|
bufferViews: bufferViews,
|
|
});
|
|
|
|
const count = tablePropertyResults.count;
|
|
const 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 = options ?? Frozen.EMPTY_OBJECT;
|
|
|
|
const featureTables = {};
|
|
const bufferViews = {};
|
|
|
|
for (const featureTableId in options.featureTables) {
|
|
if (options.featureTables.hasOwnProperty(featureTableId)) {
|
|
const featureTable = options.featureTables[featureTableId];
|
|
const propertyResults = createProperties({
|
|
schema: options.schema,
|
|
classId: featureTable.class,
|
|
propertyValues: featureTable.properties,
|
|
bufferViews: bufferViews,
|
|
});
|
|
|
|
const count = propertyResults.count;
|
|
const properties = propertyResults.properties;
|
|
featureTables[featureTableId] = {
|
|
class: featureTable.class,
|
|
count: count,
|
|
properties: properties,
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
featureTables: featureTables,
|
|
bufferViews: bufferViews,
|
|
};
|
|
};
|
|
|
|
MetadataTester.createGltf = function (options) {
|
|
options = options ?? Frozen.EMPTY_OBJECT;
|
|
|
|
const propertyTableResults = MetadataTester.createPropertyTables(options);
|
|
|
|
let bufferByteLength = 0;
|
|
const bufferViewsMap = propertyTableResults.bufferViews;
|
|
const bufferViewsLength = Object.keys(bufferViewsMap).length;
|
|
|
|
const byteLengths = new Array(bufferViewsLength);
|
|
|
|
let bufferViewId;
|
|
let uint8Array;
|
|
|
|
for (bufferViewId in bufferViewsMap) {
|
|
if (bufferViewsMap.hasOwnProperty(bufferViewId)) {
|
|
uint8Array = bufferViewsMap[bufferViewId];
|
|
|
|
const remainder = uint8Array.byteLength % 8;
|
|
const padding = remainder === 0 ? 0 : 8 - remainder;
|
|
const byteLength = uint8Array.byteLength + padding;
|
|
bufferByteLength += byteLength;
|
|
byteLengths[bufferViewId] = byteLength;
|
|
}
|
|
}
|
|
|
|
const buffer = new Uint8Array(bufferByteLength);
|
|
const bufferViews = new Array(bufferViewsLength);
|
|
let 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];
|
|
}
|
|
}
|
|
|
|
const gltf = {
|
|
buffers: [
|
|
{
|
|
uri: "external.bin",
|
|
byteLength: buffer.byteLength,
|
|
},
|
|
],
|
|
images: options.images,
|
|
textures: options.textures,
|
|
bufferViews: bufferViews,
|
|
extensionsUsed: ["EXT_structural_metadata"],
|
|
extensions: {
|
|
EXT_structural_metadata: {
|
|
schema: options.schema,
|
|
propertyTables: propertyTableResults.propertyTables,
|
|
propertyTextures: options.propertyTextures,
|
|
},
|
|
},
|
|
};
|
|
|
|
return {
|
|
gltf: gltf,
|
|
buffer: buffer,
|
|
};
|
|
};
|
|
|
|
function createBuffer(values, componentType) {
|
|
let 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);
|
|
break;
|
|
case MetadataComponentType.UINT64:
|
|
typedArray = new BigUint64Array(values);
|
|
break;
|
|
case MetadataComponentType.FLOAT32:
|
|
typedArray = new Float32Array(values);
|
|
break;
|
|
case MetadataComponentType.FLOAT64:
|
|
typedArray = new Float64Array(values);
|
|
break;
|
|
//>>includeStart('debug', pragmas.debug);
|
|
default:
|
|
throw new DeveloperError(
|
|
`${componentType} is not a valid component type`,
|
|
);
|
|
//>>includeEnd('debug');
|
|
}
|
|
|
|
return new Uint8Array(typedArray.buffer);
|
|
}
|
|
|
|
function createStringBuffer(values) {
|
|
const encoder = new TextEncoder();
|
|
return encoder.encode(values.join(""));
|
|
}
|
|
|
|
function createBooleanBuffer(values) {
|
|
const length = Math.ceil(values.length / 8);
|
|
const typedArray = new Uint8Array(length); // Initialized as 0's
|
|
for (let i = 0; i < values.length; ++i) {
|
|
const byteIndex = i >> 3;
|
|
const bitIndex = i % 8;
|
|
if (values[i]) {
|
|
typedArray[byteIndex] |= 1 << bitIndex;
|
|
}
|
|
}
|
|
return typedArray;
|
|
}
|
|
|
|
function flatten(values) {
|
|
return [].concat.apply([], values);
|
|
}
|
|
|
|
function createValuesBuffer(values, classProperty) {
|
|
const type = classProperty.type;
|
|
const valueType = classProperty.valueType;
|
|
const enumType = classProperty.enumType;
|
|
const flattenedValues = flatten(values);
|
|
|
|
if (type === MetadataType.STRING) {
|
|
return createStringBuffer(flattenedValues);
|
|
}
|
|
|
|
if (type === MetadataType.BOOLEAN) {
|
|
return createBooleanBuffer(flattenedValues);
|
|
}
|
|
|
|
if (defined(enumType)) {
|
|
const length = flattenedValues.length;
|
|
for (let i = 0; i < length; ++i) {
|
|
flattenedValues[i] = enumType.valuesByName[flattenedValues[i]];
|
|
}
|
|
}
|
|
|
|
return createBuffer(flattenedValues, valueType);
|
|
}
|
|
|
|
function createStringOffsetBuffer(values, offsetType) {
|
|
const encoder = new TextEncoder();
|
|
const strings = flatten(values);
|
|
const length = strings.length;
|
|
const offsets = new Array(length + 1);
|
|
let offset = 0;
|
|
for (let i = 0; i < length; ++i) {
|
|
offsets[i] = offset;
|
|
offset += encoder.encode(strings[i]).length;
|
|
}
|
|
offsets[length] = offset;
|
|
offsetType = offsetType ?? MetadataComponentType.UINT32;
|
|
return createBuffer(offsets, offsetType);
|
|
}
|
|
|
|
function createArrayOffsetBuffer(values, type, offsetType) {
|
|
const componentCount = MetadataType.getComponentCount(type);
|
|
const length = values.length;
|
|
const offsets = new Array(length + 1);
|
|
let offset = 0;
|
|
for (let i = 0; i < length; ++i) {
|
|
offsets[i] = offset;
|
|
offset += values[i].length / componentCount;
|
|
}
|
|
offsets[length] = offset;
|
|
offsetType = offsetType ?? MetadataComponentType.UINT32;
|
|
return createBuffer(offsets, offsetType);
|
|
}
|
|
|
|
function addPadding(uint8Array) {
|
|
// This tests that MetadataTable uses the Uint8Array's byteOffset properly
|
|
const paddingBytes = 8;
|
|
const padded = new Uint8Array(paddingBytes + uint8Array.length);
|
|
padded.set(uint8Array, paddingBytes);
|
|
return new Uint8Array(padded.buffer, paddingBytes, uint8Array.length);
|
|
}
|
|
|
|
export default MetadataTester;
|