// https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/specification/TileFormats/Batched3DModel/README.md // convert an array of numbers to a string function arrayToString( array ) { let str = ''; for ( let i = 0, l = array.length; i < l; i ++ ) { str += String.fromCharCode( array[ i ] ); } return str; } export class B3DMLoaderBase { constructor() { this.fetchOptions = {}; } load( url ) { return fetch( url, this.fetchOptions ) .then( res => res.arrayBuffer() ) .then( buffer => this.parse( buffer ) ); } parse( buffer ) { const dataView = new DataView( buffer ); // 28-byte header // 4 bytes const magic = String.fromCharCode( dataView.getUint8( 0 ) ) + String.fromCharCode( dataView.getUint8( 1 ) ) + String.fromCharCode( dataView.getUint8( 2 ) ) + String.fromCharCode( dataView.getUint8( 3 ) ); console.assert( magic === 'b3dm' ); // 4 bytes const version = dataView.getUint32( 4, true ); console.assert( version === 1 ); // 4 bytes const byteLength = dataView.getUint32( 8, true ); console.assert( byteLength === buffer.byteLength ); // 4 bytes const featureTableJSONByteLength = dataView.getUint32( 12, true ); // 4 bytes const featureTableBinaryByteLength = dataView.getUint32( 16, true ); // 4 bytes const batchTableJSONByteLength = dataView.getUint32( 20, true ); // 4 bytes const batchTableBinaryByteLength = dataView.getUint32( 24, true ); // Feature Table const featureTableStart = 28; const jsonFeatureTableData = new Uint8Array( buffer, featureTableStart, featureTableJSONByteLength ); const jsonFeatureTable = featureTableJSONByteLength === 0 ? {} : JSON.parse( arrayToString( jsonFeatureTableData ) ); const featureTable = { ...jsonFeatureTable }; // const binFeatureTableData = new Uint8Array( buffer, featureTableStart + featureTableJSONByteLength, featureTableBinaryByteLength ); // TODO: dereference the json feature table data in to the binary array. // https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/specification/TileFormats/FeatureTable/README.md#json-header // TODO: The feature table contains data with implicit stride and data types, which means we can't parse it into arrays // unless they are specified ahead of time?s // Batch Table const batchTableStart = featureTableStart + featureTableJSONByteLength + featureTableBinaryByteLength; const jsonBatchTableData = new Uint8Array( buffer, batchTableStart, batchTableJSONByteLength ); const jsonBatchTable = batchTableJSONByteLength === 0 ? {} : JSON.parse( arrayToString( jsonBatchTableData ) ); const batchTable = { ...jsonBatchTable }; // dereference the json batch table data in to the binary array. // https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/specification/TileFormats/FeatureTable/README.md#json-header // const binBatchTableData = new Uint8Array( buffer, batchTableStart + batchTableJSONByteLength, batchTableBinaryByteLength ); const batchLength = jsonFeatureTable.BATCH_LENGTH; for ( const key in jsonBatchTable ) { const feature = jsonBatchTable[ key ]; if ( Array.isArray( feature ) ) { batchTable[ key ] = { type: 'SCALAR', stride: 1, data: feature, }; } else { let stride; let data; const arrayStart = batchTableStart + batchTableJSONByteLength; const arrayLength = batchLength * stride + feature.byteOffset; switch ( feature.type ) { case 'SCALAR': stride = 1; break; case 'VEC2': stride = 2; break; case 'VEC3': stride = 3; break; case 'VEC4': stride = 4; break; } switch( feature.componentType ) { case 'BYTE': data = new Int8Array( buffer, arrayStart, arrayLength ); break; case 'UNSIGNED_BYTE': data = new Uint8Array( buffer, arrayStart, arrayLength ); break; case 'SHORT': data = new Int16Array( buffer, arrayStart, arrayLength ); break; case 'UNSIGNED_SHORT': data = new Uint16Array( buffer, arrayStart, arrayLength ); break; case 'INT': data = new Int32Array( buffer, arrayStart, arrayLength ); break; case 'UNSIGNED_INT': data = new Uint32Array( buffer, arrayStart, arrayLength ); break; case 'FLOAT': data = new Float32Array( buffer, arrayStart, arrayLength ); break; case 'DOUBLE': data = new Float64Array( buffer, arrayStart, arrayLength ); break; } batchTable[ key ] = { type: feature.type, stride, data, }; } } const glbStart = batchTableStart + batchTableJSONByteLength + batchTableBinaryByteLength; const glbBytes = new Uint8Array( buffer, glbStart, byteLength - glbStart ); // TODO: Understand how to apply the batchId semantics // https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/specification/TileFormats/Batched3DModel/README.md#binary-gltf return { version, featureTable, batchTable, glbBytes, }; } }