import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import deprecationWarning from "../Core/deprecationWarning.js"; import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js"; import RuntimeError from "../Core/RuntimeError.js"; /** * Handles parsing of a Batched 3D Model. * * @namespace B3dmParser * @private */ const B3dmParser = {}; B3dmParser._deprecationWarning = deprecationWarning; const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; /** * Parses the contents of a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel|Batched 3D Model}. * * @private * * @param {ArrayBuffer} arrayBuffer The array buffer containing the b3dm. * @param {Number} [byteOffset=0] The byte offset of the beginning of the b3dm in the array buffer. * @returns {Object} Returns an object with the batch length, feature table (binary and json), batch table (binary and json) and glTF parts of the b3dm. */ B3dmParser.parse = function (arrayBuffer, byteOffset) { const byteStart = defaultValue(byteOffset, 0); //>>includeStart('debug', pragmas.debug); Check.defined("arrayBuffer", arrayBuffer); //>>includeEnd('debug'); byteOffset = byteStart; const uint8Array = new Uint8Array(arrayBuffer); const view = new DataView(arrayBuffer); byteOffset += sizeOfUint32; // Skip magic const version = view.getUint32(byteOffset, true); if (version !== 1) { throw new RuntimeError( `Only Batched 3D Model version 1 is supported. Version ${version} is not.` ); } byteOffset += sizeOfUint32; const byteLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; let featureTableJsonByteLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; let featureTableBinaryByteLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; let batchTableJsonByteLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; let batchTableBinaryByteLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; let batchLength; // Legacy header #1: [batchLength] [batchTableByteLength] // Legacy header #2: [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength] // Current header: [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] // If the header is in the first legacy format 'batchTableJsonByteLength' will be the start of the JSON string (a quotation mark) or the glTF magic. // Accordingly its first byte will be either 0x22 or 0x67, and so the minimum uint32 expected is 0x22000000 = 570425344 = 570MB. It is unlikely that the feature table JSON will exceed this length. // The check for the second legacy format is similar, except it checks 'batchTableBinaryByteLength' instead if (batchTableJsonByteLength >= 570425344) { // First legacy check byteOffset -= sizeOfUint32 * 2; batchLength = featureTableJsonByteLength; batchTableJsonByteLength = featureTableBinaryByteLength; batchTableBinaryByteLength = 0; featureTableJsonByteLength = 0; featureTableBinaryByteLength = 0; B3dmParser._deprecationWarning( "b3dm-legacy-header", "This b3dm header is using the legacy format [batchLength] [batchTableByteLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel." ); } else if (batchTableBinaryByteLength >= 570425344) { // Second legacy check byteOffset -= sizeOfUint32; batchLength = batchTableJsonByteLength; batchTableJsonByteLength = featureTableJsonByteLength; batchTableBinaryByteLength = featureTableBinaryByteLength; featureTableJsonByteLength = 0; featureTableBinaryByteLength = 0; B3dmParser._deprecationWarning( "b3dm-legacy-header", "This b3dm header is using the legacy format [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel." ); } let featureTableJson; if (featureTableJsonByteLength === 0) { featureTableJson = { BATCH_LENGTH: defaultValue(batchLength, 0), }; } else { featureTableJson = getJsonFromTypedArray( uint8Array, byteOffset, featureTableJsonByteLength ); byteOffset += featureTableJsonByteLength; } const featureTableBinary = new Uint8Array( arrayBuffer, byteOffset, featureTableBinaryByteLength ); byteOffset += featureTableBinaryByteLength; let batchTableJson; let batchTableBinary; if (batchTableJsonByteLength > 0) { // PERFORMANCE_IDEA: is it possible to allocate this on-demand? Perhaps keep the // arraybuffer/string compressed in memory and then decompress it when it is first accessed. // // We could also make another request for it, but that would make the property set/get // API async, and would double the number of numbers in some cases. batchTableJson = getJsonFromTypedArray( uint8Array, byteOffset, batchTableJsonByteLength ); byteOffset += batchTableJsonByteLength; if (batchTableBinaryByteLength > 0) { // Has a batch table binary batchTableBinary = new Uint8Array( arrayBuffer, byteOffset, batchTableBinaryByteLength ); // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed batchTableBinary = new Uint8Array(batchTableBinary); byteOffset += batchTableBinaryByteLength; } } const gltfByteLength = byteStart + byteLength - byteOffset; if (gltfByteLength === 0) { throw new RuntimeError("glTF byte length must be greater than 0."); } let gltfView; if (byteOffset % 4 === 0) { gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength); } else { // Create a copy of the glb so that it is 4-byte aligned B3dmParser._deprecationWarning( "b3dm-glb-unaligned", "The embedded glb is not aligned to a 4-byte boundary." ); gltfView = new Uint8Array( uint8Array.subarray(byteOffset, byteOffset + gltfByteLength) ); } return { batchLength: batchLength, featureTableJson: featureTableJson, featureTableBinary: featureTableBinary, batchTableJson: batchTableJson, batchTableBinary: batchTableBinary, gltf: gltfView, }; }; export default B3dmParser;