| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878 |
- const textDecoder = new TextDecoder();
- // Pre-computed half-float exponent lookup table for fast conversion
- // Math.pow(2, exp - 15) for exp = 0..31
- const HALF_EXPONENT_TABLE = new Float32Array( 32 );
- for ( let i = 0; i < 32; i ++ ) {
- HALF_EXPONENT_TABLE[ i ] = Math.pow( 2, i - 15 );
- }
- // Pre-computed constant for denormalized half-floats: 2^-14
- const HALF_DENORM_SCALE = Math.pow( 2, - 14 );
- // Type enum values from crateDataTypes.h
- const TypeEnum = {
- Invalid: 0,
- Bool: 1,
- UChar: 2,
- Int: 3,
- UInt: 4,
- Int64: 5,
- UInt64: 6,
- Half: 7,
- Float: 8,
- Double: 9,
- String: 10,
- Token: 11,
- AssetPath: 12,
- Matrix2d: 13,
- Matrix3d: 14,
- Matrix4d: 15,
- Quatd: 16,
- Quatf: 17,
- Quath: 18,
- Vec2d: 19,
- Vec2f: 20,
- Vec2h: 21,
- Vec2i: 22,
- Vec3d: 23,
- Vec3f: 24,
- Vec3h: 25,
- Vec3i: 26,
- Vec4d: 27,
- Vec4f: 28,
- Vec4h: 29,
- Vec4i: 30,
- Dictionary: 31,
- TokenListOp: 32,
- StringListOp: 33,
- PathListOp: 34,
- ReferenceListOp: 35,
- IntListOp: 36,
- Int64ListOp: 37,
- UIntListOp: 38,
- UInt64ListOp: 39,
- PathVector: 40,
- TokenVector: 41,
- Specifier: 42,
- Permission: 43,
- Variability: 44,
- VariantSelectionMap: 45,
- TimeSamples: 46,
- Payload: 47,
- DoubleVector: 48,
- LayerOffsetVector: 49,
- StringVector: 50,
- ValueBlock: 51,
- Value: 52,
- UnregisteredValue: 53,
- UnregisteredValueListOp: 54,
- PayloadListOp: 55,
- TimeCode: 56,
- PathExpression: 57,
- Relocates: 58,
- Spline: 59,
- AnimationBlock: 60
- };
- // Field set terminator marker
- const FIELD_SET_TERMINATOR = 0xFFFFFFFF;
- // Float compression type codes
- const FLOAT_COMPRESSION_INT = 0x69; // 'i' - compressed as integers
- const FLOAT_COMPRESSION_LUT = 0x74; // 't' - lookup table
- // ============================================================================
- // LZ4 Decompression (minimal implementation for USD)
- // Based on LZ4 block format specification
- // ============================================================================
- function lz4DecompressBlock( input, inputOffset, inputEnd, output, outputOffset, outputEnd ) {
- while ( inputOffset < inputEnd ) {
- // Read token
- const token = input[ inputOffset ++ ];
- if ( inputOffset > inputEnd ) break;
- // Literal length
- let literalLength = token >> 4;
- if ( literalLength === 15 ) {
- let b;
- do {
- if ( inputOffset >= inputEnd ) break;
- b = input[ inputOffset ++ ];
- literalLength += b;
- } while ( b === 255 && inputOffset < inputEnd );
- }
- // Copy literals
- if ( literalLength > 0 ) {
- if ( inputOffset + literalLength > inputEnd ) {
- literalLength = inputEnd - inputOffset;
- }
- for ( let i = 0; i < literalLength; i ++ ) {
- if ( outputOffset >= outputEnd ) break;
- output[ outputOffset ++ ] = input[ inputOffset ++ ];
- }
- }
- // Check if we're at the end (last sequence has no match)
- if ( inputOffset >= inputEnd ) break;
- // Read match offset (little-endian 16-bit)
- if ( inputOffset + 2 > inputEnd ) break;
- const matchOffset = input[ inputOffset ++ ] | ( input[ inputOffset ++ ] << 8 );
- if ( matchOffset === 0 ) {
- // Invalid offset
- break;
- }
- // Match length
- let matchLength = ( token & 0x0F ) + 4;
- if ( matchLength === 19 ) {
- let b;
- do {
- if ( inputOffset >= inputEnd ) break;
- b = input[ inputOffset ++ ];
- matchLength += b;
- } while ( b === 255 && inputOffset < inputEnd );
- }
- // Copy match (byte-by-byte to handle overlapping)
- const matchPos = outputOffset - matchOffset;
- if ( matchPos < 0 ) {
- // Invalid match position
- break;
- }
- for ( let i = 0; i < matchLength; i ++ ) {
- if ( outputOffset >= outputEnd ) break;
- output[ outputOffset ++ ] = output[ matchPos + i ];
- }
- }
- return outputOffset;
- }
- // USD uses TfFastCompression which wraps LZ4 with chunk headers
- function decompressLZ4( input, uncompressedSize ) {
- // TfFastCompression format (used by OpenUSD):
- // Single chunk (byte 0 == 0): [0] + LZ4 data
- // Multi chunk (byte 0 > 0): [numChunks] + [compressedSizes...] + [chunkData...]
- const output = new Uint8Array( uncompressedSize );
- const numChunks = input[ 0 ];
- if ( numChunks === 0 ) {
- // Single chunk - all remaining bytes are LZ4 compressed
- lz4DecompressBlock( input, 1, input.length, output, 0, uncompressedSize );
- return output;
- } else {
- // Multiple chunks - each chunk decompresses to max 65536 bytes
- const CHUNK_SIZE = 65536;
- // First, read all chunk sizes
- let headerOffset = 1;
- const compressedSizes = [];
- for ( let i = 0; i < numChunks; i ++ ) {
- const size = ( input[ headerOffset ] |
- ( input[ headerOffset + 1 ] << 8 ) |
- ( input[ headerOffset + 2 ] << 16 ) |
- ( input[ headerOffset + 3 ] << 24 ) ) >>> 0;
- compressedSizes.push( size );
- headerOffset += 4;
- }
- // Decompress each chunk
- let inputOffset = headerOffset;
- let outputOffset = 0;
- for ( let i = 0; i < numChunks; i ++ ) {
- const chunkCompressedSize = compressedSizes[ i ];
- const chunkOutputSize = Math.min( CHUNK_SIZE, uncompressedSize - outputOffset );
- lz4DecompressBlock(
- input, inputOffset, inputOffset + chunkCompressedSize,
- output, outputOffset, outputOffset + chunkOutputSize
- );
- inputOffset += chunkCompressedSize;
- outputOffset += chunkOutputSize;
- }
- return output;
- }
- }
- // ============================================================================
- // Integer Decompression (USD-specific delta + variable-width encoding)
- // ============================================================================
- function decompressIntegers32( compressedData, numInts ) {
- // First decompress with LZ4
- const encodedSize = numInts * 4 + ( ( numInts * 2 + 7 ) >> 3 ) + 4;
- const encoded = decompressLZ4( new Uint8Array( compressedData ), encodedSize );
- // Then decode
- return decodeIntegers32( encoded, numInts );
- }
- function decodeIntegers32( data, numInts ) {
- const view = new DataView( data.buffer, data.byteOffset, data.byteLength );
- let offset = 0;
- // Read common value (signed 32-bit)
- const commonValue = view.getInt32( offset, true );
- offset += 4;
- const numCodesBytes = ( numInts * 2 + 7 ) >> 3;
- const codesStart = offset;
- const vintsStart = offset + numCodesBytes;
- const result = new Int32Array( numInts );
- let prevVal = 0;
- let codesOffset = codesStart;
- let vintsOffset = vintsStart;
- for ( let i = 0; i < numInts; ) {
- const codeByte = data[ codesOffset ++ ];
- for ( let j = 0; j < 4 && i < numInts; j ++, i ++ ) {
- const code = ( codeByte >> ( j * 2 ) ) & 3;
- let delta = 0;
- switch ( code ) {
- case 0: // Common value
- delta = commonValue;
- break;
- case 1: // 8-bit signed
- delta = view.getInt8( vintsOffset );
- vintsOffset += 1;
- break;
- case 2: // 16-bit signed
- delta = view.getInt16( vintsOffset, true );
- vintsOffset += 2;
- break;
- case 3: // 32-bit signed
- delta = view.getInt32( vintsOffset, true );
- vintsOffset += 4;
- break;
- }
- prevVal += delta;
- result[ i ] = prevVal;
- }
- }
- return result;
- }
- // ============================================================================
- // Binary Reader Helper
- // ============================================================================
- class BinaryReader {
- constructor( buffer ) {
- this.buffer = buffer;
- this.view = new DataView( buffer );
- this.offset = 0;
- }
- seek( offset ) {
- this.offset = offset;
- }
- tell() {
- return this.offset;
- }
- readUint8() {
- const value = this.view.getUint8( this.offset );
- this.offset += 1;
- return value;
- }
- readInt8() {
- const value = this.view.getInt8( this.offset );
- this.offset += 1;
- return value;
- }
- readUint16() {
- const value = this.view.getUint16( this.offset, true );
- this.offset += 2;
- return value;
- }
- readInt16() {
- const value = this.view.getInt16( this.offset, true );
- this.offset += 2;
- return value;
- }
- readUint32() {
- const value = this.view.getUint32( this.offset, true );
- this.offset += 4;
- return value;
- }
- readInt32() {
- const value = this.view.getInt32( this.offset, true );
- this.offset += 4;
- return value;
- }
- readUint64() {
- const lo = this.view.getUint32( this.offset, true );
- const hi = this.view.getUint32( this.offset + 4, true );
- this.offset += 8;
- // For values that fit in Number, this is safe
- return hi * 0x100000000 + lo;
- }
- readInt64() {
- const lo = this.view.getUint32( this.offset, true );
- const hi = this.view.getInt32( this.offset + 4, true );
- this.offset += 8;
- return hi * 0x100000000 + lo;
- }
- readFloat32() {
- const value = this.view.getFloat32( this.offset, true );
- this.offset += 4;
- return value;
- }
- readFloat64() {
- const value = this.view.getFloat64( this.offset, true );
- this.offset += 8;
- return value;
- }
- readBytes( length ) {
- const bytes = new Uint8Array( this.buffer, this.offset, length );
- this.offset += length;
- return bytes;
- }
- readString( length ) {
- const bytes = this.readBytes( length );
- let end = 0;
- while ( end < length && bytes[ end ] !== 0 ) end ++;
- return textDecoder.decode( bytes.subarray( 0, end ) );
- }
- }
- // ============================================================================
- // ValueRep - 64-bit packed value representation
- // ============================================================================
- class ValueRep {
- constructor( lo, hi ) {
- this.lo = lo; // Lower 32 bits
- this.hi = hi; // Upper 32 bits
- }
- get isArray() {
- return ( this.hi & 0x80000000 ) !== 0;
- }
- get isInlined() {
- return ( this.hi & 0x40000000 ) !== 0;
- }
- get isCompressed() {
- return ( this.hi & 0x20000000 ) !== 0;
- }
- get typeEnum() {
- return ( this.hi >> 16 ) & 0xFF;
- }
- get payload() {
- // 48-bit payload: lo (32 bits) + hi lower 16 bits
- // Note: JavaScript numbers are IEEE 754 doubles with 53 bits of integer precision,
- // so 48-bit values are represented exactly without loss of precision.
- return this.lo + ( ( this.hi & 0xFFFF ) * 0x100000000 );
- }
- getInlinedValue() {
- // For inlined scalars, the value is in the lower 32 bits
- return this.lo;
- }
- }
- // ============================================================================
- // USDC Parser
- // ============================================================================
- class USDCParser {
- /**
- * Parse USDC file and return raw spec data without building Three.js scene.
- * Used by USDComposer for unified scene composition.
- */
- parseData( buffer ) {
- this.buffer = buffer instanceof ArrayBuffer ? buffer : buffer.buffer;
- this.reader = new BinaryReader( this.buffer );
- this.version = { major: 0, minor: 0, patch: 0 };
- this._conversionBuffer = new ArrayBuffer( 4 );
- this._conversionView = new DataView( this._conversionBuffer );
- this._readBootstrap();
- this._readTOC();
- this._readTokens();
- this._readStrings();
- this._readFields();
- this._readFieldSets();
- this._readPaths();
- this._readSpecs();
- // Build specsByPath without building scene
- this.specsByPath = {};
- for ( const spec of this.specs ) {
- const path = this.paths[ spec.pathIndex ];
- if ( ! path ) continue;
- const fields = this._getFieldsForSpec( spec );
- this.specsByPath[ path ] = { specType: spec.specType, fields };
- }
- return { specsByPath: this.specsByPath };
- }
- _readBootstrap() {
- const reader = this.reader;
- reader.seek( 0 );
- // Read magic "PXR-USDC"
- const magic = reader.readString( 8 );
- if ( magic !== 'PXR-USDC' ) {
- throw new Error( 'THREE.USDCParser: Not a valid USDC file.' );
- }
- // Read version
- this.version.major = reader.readUint8();
- this.version.minor = reader.readUint8();
- this.version.patch = reader.readUint8();
- reader.readBytes( 5 ); // Skip remaining version bytes
- // Read TOC offset
- this.tocOffset = reader.readUint64();
- // Skip reserved bytes (rest of 128-byte header)
- // Already at offset 24, skip to end of bootstrap (88 bytes total for bootstrap struct)
- }
- _readTOC() {
- const reader = this.reader;
- reader.seek( this.tocOffset );
- // Read number of sections
- const numSections = reader.readUint64();
- this.sections = {};
- for ( let i = 0; i < numSections; i ++ ) {
- const name = reader.readString( 16 );
- const start = reader.readUint64();
- const size = reader.readUint64();
- this.sections[ name ] = { start, size };
- }
- }
- _readTokens() {
- const section = this.sections[ 'TOKENS' ];
- if ( ! section ) return;
- const reader = this.reader;
- reader.seek( section.start );
- const numTokens = reader.readUint64();
- this.tokens = [];
- if ( this.version.major === 0 && this.version.minor < 4 ) {
- // Uncompressed tokens (version < 0.4.0)
- const tokensNumBytes = reader.readUint64();
- const tokensData = reader.readBytes( tokensNumBytes );
- let strStart = 0;
- for ( let i = 0; i < numTokens; i ++ ) {
- let strEnd = strStart;
- while ( strEnd < tokensData.length && tokensData[ strEnd ] !== 0 ) strEnd ++;
- this.tokens.push( textDecoder.decode( tokensData.subarray( strStart, strEnd ) ) );
- strStart = strEnd + 1;
- }
- } else {
- // Compressed tokens (version >= 0.4.0)
- const uncompressedSize = reader.readUint64();
- const compressedSize = reader.readUint64();
- const compressedData = reader.readBytes( compressedSize );
- const tokensData = decompressLZ4( compressedData, uncompressedSize );
- let strStart = 0;
- for ( let i = 0; i < numTokens; i ++ ) {
- let strEnd = strStart;
- while ( strEnd < tokensData.length && tokensData[ strEnd ] !== 0 ) strEnd ++;
- this.tokens.push( textDecoder.decode( tokensData.subarray( strStart, strEnd ) ) );
- strStart = strEnd + 1;
- }
- }
- }
- _readStrings() {
- const section = this.sections[ 'STRINGS' ];
- if ( ! section ) {
- this.strings = [];
- return;
- }
- const reader = this.reader;
- reader.seek( section.start );
- // Strings section has an 8-byte count prefix, but string indices stored
- // elsewhere in the file are relative to the section start (not the data).
- // So we read the entire section as uint32 values to maintain correct indexing.
- const numStrings = Math.floor( section.size / 4 );
- this.strings = [];
- for ( let i = 0; i < numStrings; i ++ ) {
- this.strings.push( reader.readUint32() );
- }
- }
- _readFields() {
- const section = this.sections[ 'FIELDS' ];
- if ( ! section ) return;
- const reader = this.reader;
- reader.seek( section.start );
- this.fields = [];
- if ( this.version.major === 0 && this.version.minor < 4 ) {
- // Uncompressed fields
- const numFields = Math.floor( section.size / 12 ); // 4 bytes token index + 8 bytes value rep
- for ( let i = 0; i < numFields; i ++ ) {
- const tokenIndex = reader.readUint32();
- const repLo = reader.readUint32();
- const repHi = reader.readUint32();
- this.fields.push( {
- tokenIndex,
- valueRep: new ValueRep( repLo, repHi )
- } );
- }
- } else {
- // Compressed fields (version >= 0.4.0)
- const numFields = reader.readUint64();
- // Read compressed token indices
- const tokenIndicesCompressedSize = reader.readUint64();
- const tokenIndicesCompressed = reader.readBytes( tokenIndicesCompressedSize );
- const tokenIndices = decompressIntegers32(
- tokenIndicesCompressed.buffer.slice(
- tokenIndicesCompressed.byteOffset,
- tokenIndicesCompressed.byteOffset + tokenIndicesCompressedSize
- ),
- numFields
- );
- // Read compressed value reps (LZ4 only, no integer encoding)
- const repsCompressedSize = reader.readUint64();
- const repsCompressed = reader.readBytes( repsCompressedSize );
- const repsData = decompressLZ4( repsCompressed, numFields * 8 );
- const repsView = new DataView( repsData.buffer, repsData.byteOffset, repsData.byteLength );
- for ( let i = 0; i < numFields; i ++ ) {
- const repLo = repsView.getUint32( i * 8, true );
- const repHi = repsView.getUint32( i * 8 + 4, true );
- this.fields.push( {
- tokenIndex: tokenIndices[ i ],
- valueRep: new ValueRep( repLo, repHi )
- } );
- }
- }
- }
- _readFieldSets() {
- const section = this.sections[ 'FIELDSETS' ];
- if ( ! section ) return;
- const reader = this.reader;
- reader.seek( section.start );
- this.fieldSets = [];
- if ( this.version.major === 0 && this.version.minor < 4 ) {
- // Uncompressed field sets
- const numFieldSets = Math.floor( section.size / 4 );
- for ( let i = 0; i < numFieldSets; i ++ ) {
- this.fieldSets.push( reader.readUint32() );
- }
- } else {
- // Compressed field sets
- const numFieldSets = reader.readUint64();
- const compressedSize = reader.readUint64();
- const compressed = reader.readBytes( compressedSize );
- const indices = decompressIntegers32(
- compressed.buffer.slice(
- compressed.byteOffset,
- compressed.byteOffset + compressedSize
- ),
- numFieldSets
- );
- for ( let i = 0; i < numFieldSets; i ++ ) {
- this.fieldSets.push( indices[ i ] );
- }
- }
- }
- _readPaths() {
- const section = this.sections[ 'PATHS' ];
- if ( ! section ) return;
- const reader = this.reader;
- reader.seek( section.start );
- const numPaths = reader.readUint64();
- this.paths = new Array( numPaths ).fill( '' );
- if ( this.version.major === 0 && this.version.minor < 4 ) {
- // Uncompressed paths - recursive tree structure
- this._readPathsRecursive( '' );
- } else {
- // Compressed paths (version >= 0.4.0)
- // Note: numPaths is stored twice - once for array sizing, once in compressed paths section
- reader.readUint64(); // Read duplicate numPaths value (matches numPaths above)
- const compressedSize1 = reader.readUint64();
- const pathIndicesCompressed = reader.readBytes( compressedSize1 );
- const pathIndices = decompressIntegers32(
- pathIndicesCompressed.buffer.slice(
- pathIndicesCompressed.byteOffset,
- pathIndicesCompressed.byteOffset + compressedSize1
- ),
- numPaths
- );
- const compressedSize2 = reader.readUint64();
- const elementTokenIndicesCompressed = reader.readBytes( compressedSize2 );
- const elementTokenIndices = decompressIntegers32(
- elementTokenIndicesCompressed.buffer.slice(
- elementTokenIndicesCompressed.byteOffset,
- elementTokenIndicesCompressed.byteOffset + compressedSize2
- ),
- numPaths
- );
- const compressedSize3 = reader.readUint64();
- const jumpsCompressed = reader.readBytes( compressedSize3 );
- const jumps = decompressIntegers32(
- jumpsCompressed.buffer.slice(
- jumpsCompressed.byteOffset,
- jumpsCompressed.byteOffset + compressedSize3
- ),
- numPaths
- );
- // Build paths from compressed data
- this._buildPathsFromCompressed( pathIndices, elementTokenIndices, jumps );
- }
- }
- _readPathsRecursive( parentPath, depth = 0 ) {
- const reader = this.reader;
- // Prevent infinite recursion
- if ( depth > 1000 ) return;
- // Read path item header
- const index = reader.readUint32();
- const elementTokenIndex = reader.readUint32();
- const bits = reader.readUint8();
- const hasChild = ( bits & 1 ) !== 0;
- const hasSibling = ( bits & 2 ) !== 0;
- const isPrimProperty = ( bits & 4 ) !== 0;
- // Build path
- let path;
- if ( parentPath === '' ) {
- path = '/';
- } else {
- const elemToken = this.tokens[ elementTokenIndex ] || '';
- if ( isPrimProperty ) {
- path = parentPath + '.' + elemToken;
- } else {
- path = parentPath === '/' ? '/' + elemToken : parentPath + '/' + elemToken;
- }
- }
- this.paths[ index ] = path;
- // Process children and siblings
- if ( hasChild && hasSibling ) {
- // Read sibling offset
- const siblingOffset = reader.readUint64();
- // Read child
- this._readPathsRecursive( path, depth + 1 );
- // Read sibling
- reader.seek( siblingOffset );
- this._readPathsRecursive( parentPath, depth + 1 );
- } else if ( hasChild ) {
- this._readPathsRecursive( path, depth + 1 );
- } else if ( hasSibling ) {
- this._readPathsRecursive( parentPath, depth + 1 );
- }
- }
- _buildPathsFromCompressed( pathIndices, elementTokenIndices, jumps ) {
- // Jump encoding from USD:
- // 0 = only sibling (no child), next entry is sibling
- // -1 = only child (no sibling), next entry is child
- // -2 = leaf (no child, no sibling)
- // >0 = has both child and sibling, value is offset to sibling
- const buildPaths = ( startIndex, parentPath ) => {
- let curIndex = startIndex;
- while ( curIndex < pathIndices.length ) {
- const thisIndex = curIndex ++;
- const pathIndex = pathIndices[ thisIndex ];
- const elementTokenIndex = elementTokenIndices[ thisIndex ];
- const jump = jumps[ thisIndex ];
- // Build path
- let path;
- if ( parentPath === '' ) {
- path = '/';
- parentPath = path;
- } else {
- const elemToken = this.tokens[ Math.abs( elementTokenIndex ) ] || '';
- const isPrimProperty = elementTokenIndex < 0;
- if ( isPrimProperty ) {
- path = parentPath + '.' + elemToken;
- } else {
- path = parentPath === '/' ? '/' + elemToken : parentPath + '/' + elemToken;
- }
- }
- this.paths[ pathIndex ] = path;
- // Determine children and siblings
- const hasChild = jump > 0 || jump === - 1;
- const hasSibling = jump >= 0;
- if ( hasChild ) {
- if ( hasSibling ) {
- // Has both child and sibling
- // Recursively process sibling subtree
- const siblingIndex = thisIndex + jump;
- buildPaths( siblingIndex, parentPath );
- }
- // Child is next entry, continue with new parent path
- parentPath = path;
- } else if ( hasSibling ) {
- // Only sibling, next entry is sibling with same parent
- // Just continue loop with curIndex and same parentPath
- } else {
- // Leaf node, exit loop
- break;
- }
- }
- };
- buildPaths( 0, '' );
- }
- _readSpecs() {
- const section = this.sections[ 'SPECS' ];
- if ( ! section ) return;
- const reader = this.reader;
- reader.seek( section.start );
- this.specs = [];
- if ( this.version.major === 0 && this.version.minor < 4 ) {
- // Uncompressed specs
- // Each spec: pathIndex (4), fieldSetIndex (4), specType (4) = 12 bytes
- // For version 0.0.1 there may be different padding
- const specSize = ( this.version.minor === 0 && this.version.patch === 1 ) ? 16 : 12;
- const numSpecs = Math.floor( section.size / specSize );
- for ( let i = 0; i < numSpecs; i ++ ) {
- const pathIndex = reader.readUint32();
- const fieldSetIndex = reader.readUint32();
- const specType = reader.readUint32();
- if ( specSize === 16 ) reader.readUint32(); // padding
- this.specs.push( { pathIndex, fieldSetIndex, specType } );
- }
- } else {
- // Compressed specs
- const numSpecs = reader.readUint64();
- const compressedSize1 = reader.readUint64();
- const pathIndicesCompressed = reader.readBytes( compressedSize1 );
- const pathIndices = decompressIntegers32(
- pathIndicesCompressed.buffer.slice(
- pathIndicesCompressed.byteOffset,
- pathIndicesCompressed.byteOffset + compressedSize1
- ),
- numSpecs
- );
- const compressedSize2 = reader.readUint64();
- const fieldSetIndicesCompressed = reader.readBytes( compressedSize2 );
- const fieldSetIndices = decompressIntegers32(
- fieldSetIndicesCompressed.buffer.slice(
- fieldSetIndicesCompressed.byteOffset,
- fieldSetIndicesCompressed.byteOffset + compressedSize2
- ),
- numSpecs
- );
- const compressedSize3 = reader.readUint64();
- const specTypesCompressed = reader.readBytes( compressedSize3 );
- const specTypes = decompressIntegers32(
- specTypesCompressed.buffer.slice(
- specTypesCompressed.byteOffset,
- specTypesCompressed.byteOffset + compressedSize3
- ),
- numSpecs
- );
- for ( let i = 0; i < numSpecs; i ++ ) {
- this.specs.push( {
- pathIndex: pathIndices[ i ],
- fieldSetIndex: fieldSetIndices[ i ],
- specType: specTypes[ i ]
- } );
- }
- }
- }
- // ========================================================================
- // Value Reading
- // ========================================================================
- _readValue( valueRep ) {
- const type = valueRep.typeEnum;
- const isArray = valueRep.isArray;
- const isInlined = valueRep.isInlined;
- // Handle TimeSamples specially - they have their own format
- if ( type === TypeEnum.TimeSamples ) {
- return this._readTimeSamples( valueRep );
- }
- if ( isInlined ) {
- return this._readInlinedValue( valueRep );
- }
- // Seek to payload offset and read value
- const offset = valueRep.payload;
- if ( offset === 0 && isArray ) {
- // Spec 16.3.9.3: Array payload 0 is an explicit empty-array sentinel.
- return [];
- }
- if ( offset < 0 || offset >= this.buffer.byteLength ) {
- throw new RangeError( 'USDCParser: Invalid payload offset ' + offset + ' for type ' + type + '.' );
- }
- const savedOffset = this.reader.tell();
- this.reader.seek( offset );
- let value;
- if ( isArray ) {
- value = this._readArrayValue( valueRep );
- } else {
- value = this._readScalarValue( type );
- }
- this.reader.seek( savedOffset );
- return value;
- }
- _readInlinedValue( valueRep ) {
- const type = valueRep.typeEnum;
- const payload = valueRep.getInlinedValue();
- const view = this._conversionView;
- switch ( type ) {
- case TypeEnum.Bool:
- return payload !== 0;
- case TypeEnum.UChar:
- return payload & 0xFF;
- case TypeEnum.Int:
- case TypeEnum.UInt:
- return payload;
- case TypeEnum.Float: {
- view.setUint32( 0, payload, true );
- return view.getFloat32( 0, true );
- }
- case TypeEnum.Double: {
- // When a double is inlined, it's stored as float32 bits in the payload
- view.setUint32( 0, payload, true );
- return view.getFloat32( 0, true );
- }
- case TypeEnum.Token:
- return this.tokens[ payload ] || '';
- case TypeEnum.String:
- return this.tokens[ this.strings[ payload ] ] || '';
- case TypeEnum.AssetPath:
- return this.tokens[ payload ] || '';
- case TypeEnum.Specifier:
- return payload; // 0=def, 1=over, 2=class
- case TypeEnum.Permission:
- case TypeEnum.Variability:
- return payload;
- // Vec2h: Two half-floats fit in 4 bytes, stored directly
- case TypeEnum.Vec2h: {
- view.setUint32( 0, payload, true );
- return [ this._halfToFloat( view.getUint16( 0, true ) ), this._halfToFloat( view.getUint16( 2, true ) ) ];
- }
- // Inlined vectors that don't fit in 4 bytes are encoded as signed 8-bit integers
- // Vec2f = 8 bytes (2x float32), Vec3f = 12 bytes, Vec4f = 16 bytes, etc.
- case TypeEnum.Vec2f:
- case TypeEnum.Vec2i: {
- view.setUint32( 0, payload, true );
- return [ view.getInt8( 0 ), view.getInt8( 1 ) ];
- }
- case TypeEnum.Vec3f:
- case TypeEnum.Vec3i: {
- view.setUint32( 0, payload, true );
- return [ view.getInt8( 0 ), view.getInt8( 1 ), view.getInt8( 2 ) ];
- }
- case TypeEnum.Vec4f:
- case TypeEnum.Vec4i: {
- view.setUint32( 0, payload, true );
- return [ view.getInt8( 0 ), view.getInt8( 1 ), view.getInt8( 2 ), view.getInt8( 3 ) ];
- }
- case TypeEnum.Matrix2d: {
- // Inlined Matrix2d stores diagonal values as 2 signed int8 values
- view.setUint32( 0, payload, true );
- const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 );
- return [ d0, 0, 0, d1 ];
- }
- case TypeEnum.Matrix3d: {
- // Inlined Matrix3d stores diagonal values as 3 signed int8 values
- view.setUint32( 0, payload, true );
- const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 );
- return [ d0, 0, 0, 0, d1, 0, 0, 0, d2 ];
- }
- case TypeEnum.Matrix4d: {
- // Inlined Matrix4d stores diagonal values as 4 signed int8 values
- view.setUint32( 0, payload, true );
- const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 ), d3 = view.getInt8( 3 );
- return [ d0, 0, 0, 0, 0, d1, 0, 0, 0, 0, d2, 0, 0, 0, 0, d3 ];
- }
- default:
- return payload;
- }
- }
- _readTimeSamples( valueRep ) {
- const reader = this.reader;
- const offset = valueRep.payload;
- const savedOffset = reader.tell();
- reader.seek( offset );
- // TimeSamples format uses RELATIVE offsets (from OpenUSD _RecursiveRead):
- // _RecursiveRead: read int64 relativeOffset at current position, then seek to start + relativeOffset
- // After reading timesRep, continue reading from current position (after timesRep)
- // Layout at TimeSamples location:
- // - int64 timesOffset (relative from start of this int64)
- // At (start + timesOffset): timesRep ValueRep, then int64 valuesOffset, then numValues + ValueReps
- // Read times relative offset and resolve
- const timesStart = reader.tell();
- const timesRelOffset = reader.readInt64();
- reader.seek( timesStart + timesRelOffset );
- const timesRepLo = reader.readUint32();
- const timesRepHi = reader.readUint32();
- const timesRep = new ValueRep( timesRepLo, timesRepHi );
- // Resolve times array
- const times = this._readValue( timesRep );
- // Continue reading from current position (after timesRep)
- // The second _RecursiveRead reads from CURRENT position, not from the beginning
- const afterTimesRep = timesStart + timesRelOffset + 8;
- reader.seek( afterTimesRep );
- // Read values relative offset
- const valuesStart = reader.tell();
- const valuesRelOffset = reader.readInt64();
- reader.seek( valuesStart + valuesRelOffset );
- // Read number of values
- const numValues = reader.readUint64();
- // Read all ValueReps
- const valueReps = [];
- for ( let i = 0; i < numValues; i ++ ) {
- const repLo = reader.readUint32();
- const repHi = reader.readUint32();
- valueReps.push( new ValueRep( repLo, repHi ) );
- }
- // Resolve each value
- const values = [];
- for ( let i = 0; i < numValues; i ++ ) {
- values.push( this._readValue( valueReps[ i ] ) );
- }
- reader.seek( savedOffset );
- // Convert times to array if needed
- const timesArray = times instanceof Float64Array ? Array.from( times ) : ( Array.isArray( times ) ? times : [ times ] );
- return { times: timesArray, values };
- }
- _readScalarValue( type ) {
- const reader = this.reader;
- switch ( type ) {
- case TypeEnum.Invalid:
- return null;
- case TypeEnum.Bool:
- return reader.readUint8() !== 0;
- case TypeEnum.UChar:
- return reader.readUint8();
- case TypeEnum.Int:
- return reader.readInt32();
- case TypeEnum.UInt:
- return reader.readUint32();
- case TypeEnum.Int64:
- return reader.readInt64();
- case TypeEnum.UInt64:
- return reader.readUint64();
- case TypeEnum.Half:
- return this._readHalf();
- case TypeEnum.Float:
- return reader.readFloat32();
- case TypeEnum.Double:
- return reader.readFloat64();
- case TypeEnum.String:
- case TypeEnum.Token: {
- const index = reader.readUint32();
- return this.tokens[ index ] || '';
- }
- case TypeEnum.AssetPath: {
- const index = reader.readUint32();
- return this.tokens[ index ] || '';
- }
- case TypeEnum.Vec2f:
- return [ reader.readFloat32(), reader.readFloat32() ];
- case TypeEnum.Vec2d:
- return [ reader.readFloat64(), reader.readFloat64() ];
- case TypeEnum.Vec2i:
- return [ reader.readInt32(), reader.readInt32() ];
- case TypeEnum.Vec3f:
- return [ reader.readFloat32(), reader.readFloat32(), reader.readFloat32() ];
- case TypeEnum.Vec3d:
- return [ reader.readFloat64(), reader.readFloat64(), reader.readFloat64() ];
- case TypeEnum.Vec3i:
- return [ reader.readInt32(), reader.readInt32(), reader.readInt32() ];
- case TypeEnum.Vec4f:
- return [ reader.readFloat32(), reader.readFloat32(), reader.readFloat32(), reader.readFloat32() ];
- case TypeEnum.Vec4d:
- return [ reader.readFloat64(), reader.readFloat64(), reader.readFloat64(), reader.readFloat64() ];
- case TypeEnum.Quatf:
- return [ reader.readFloat32(), reader.readFloat32(), reader.readFloat32(), reader.readFloat32() ];
- case TypeEnum.Quatd:
- return [ reader.readFloat64(), reader.readFloat64(), reader.readFloat64(), reader.readFloat64() ];
- case TypeEnum.Matrix4d: {
- const m = [];
- for ( let i = 0; i < 16; i ++ ) m.push( reader.readFloat64() );
- return m;
- }
- case TypeEnum.TokenVector: {
- const count = reader.readUint64();
- const tokens = [];
- for ( let i = 0; i < count; i ++ ) {
- const index = reader.readUint32();
- tokens.push( this.tokens[ index ] || '' );
- }
- return tokens;
- }
- case TypeEnum.PathVector: {
- const count = reader.readUint64();
- const paths = [];
- for ( let i = 0; i < count; i ++ ) {
- const index = reader.readUint32();
- paths.push( this.paths[ index ] || '' );
- }
- return paths;
- }
- case TypeEnum.DoubleVector: {
- // DoubleVector is a count-prefixed array of doubles
- const count = reader.readUint64();
- const arr = new Float64Array( count );
- for ( let i = 0; i < count; i ++ ) arr[ i ] = reader.readFloat64();
- return arr;
- }
- case TypeEnum.Dictionary: {
- // Dictionary format:
- // u64 elementCount
- // For each element: u32 keyIndex + i64 valueOffset (relative)
- const elementCount = reader.readUint64();
- const dict = {};
- for ( let i = 0; i < elementCount; i ++ ) {
- const keyIdx = reader.readUint32();
- const key = this.tokens[ keyIdx ];
- // Value offset is relative to current position
- const currentPos = reader.position;
- const valueOffset = reader.readInt64();
- const valuePos = currentPos + valueOffset;
- // Save position, read value, restore position
- const savedPos = reader.position;
- reader.position = valuePos;
- // Read the value representation at the offset
- const valueRepData = reader.readUint64();
- const valueRep = new ValueRep( valueRepData );
- // Read the value based on the representation
- let value = null;
- if ( valueRep.isInlined ) {
- value = this._readInlinedValue( valueRep );
- } else if ( valueRep.isArray ) {
- reader.position = valueRep.payload;
- value = this._readArrayValue( valueRep );
- } else {
- reader.position = valueRep.payload;
- value = this._readScalarValue( valueRep.typeEnum );
- }
- reader.position = savedPos;
- if ( key !== undefined && value !== null ) {
- dict[ key ] = value;
- }
- }
- return dict;
- }
- case TypeEnum.TokenListOp:
- case TypeEnum.StringListOp:
- case TypeEnum.IntListOp:
- case TypeEnum.Int64ListOp:
- case TypeEnum.UIntListOp:
- case TypeEnum.UInt64ListOp:
- // These complex types are not needed for geometry loading
- // Skip them silently
- return null;
- case TypeEnum.PathListOp: {
- // PathListOp format (from AOUSD Core Spec 16.3.10.25):
- // Header byte bitmask:
- // - bit 0 (0x01): Make Explicit (clears list)
- // - bit 1 (0x02): Add Explicit Items
- // - bit 2 (0x04): Add Items
- // - bit 3 (0x08): Delete Items
- // - bit 4 (0x10): Reorder Items
- // - bit 5 (0x20): Prepend Items
- // - bit 6 (0x40): Append Items
- // Arrays follow in order: Explicit, Add, Prepend, Append, Delete, Reorder
- // Each array: uint64 count + count * uint32 path indices
- const flags = reader.readUint8();
- const hasExplicitItems = ( flags & 0x02 ) !== 0;
- const hasAddItems = ( flags & 0x04 ) !== 0;
- const hasDeleteItems = ( flags & 0x08 ) !== 0;
- const hasReorderItems = ( flags & 0x10 ) !== 0;
- const hasPrependItems = ( flags & 0x20 ) !== 0;
- const hasAppendItems = ( flags & 0x40 ) !== 0;
- const readPathList = () => {
- const itemCount = reader.readUint64();
- const paths = [];
- for ( let i = 0; i < itemCount; i ++ ) {
- const pathIdx = reader.readUint32();
- paths.push( this.paths[ pathIdx ] );
- }
- return paths;
- };
- // Read arrays in spec order: Explicit, Add, Prepend, Append, Delete, Reorder
- let explicitPaths = null;
- let addPaths = null;
- let prependPaths = null;
- let appendPaths = null;
- if ( hasExplicitItems ) explicitPaths = readPathList();
- if ( hasAddItems ) addPaths = readPathList();
- if ( hasPrependItems ) prependPaths = readPathList();
- if ( hasAppendItems ) appendPaths = readPathList();
- if ( hasDeleteItems ) readPathList(); // Skip delete items
- if ( hasReorderItems ) readPathList(); // Skip reorder items
- // Return the first non-empty list (connections are typically prepended)
- if ( prependPaths && prependPaths.length > 0 ) return prependPaths;
- if ( explicitPaths && explicitPaths.length > 0 ) return explicitPaths;
- if ( appendPaths && appendPaths.length > 0 ) return appendPaths;
- if ( addPaths && addPaths.length > 0 ) return addPaths;
- return null;
- }
- case TypeEnum.VariantSelectionMap: {
- const elementCount = reader.readUint64();
- const map = {};
- for ( let i = 0; i < elementCount; i ++ ) {
- const keyIdx = reader.readUint32();
- const valueIdx = reader.readUint32();
- const key = this.tokens[ this.strings[ keyIdx ] ];
- const value = this.tokens[ this.strings[ valueIdx ] ];
- if ( key && value ) map[ key ] = value;
- }
- return map;
- }
- default:
- console.warn( 'USDCParser: Unsupported scalar type', type );
- return null;
- }
- }
- _readArrayValue( valueRep ) {
- const reader = this.reader;
- const type = valueRep.typeEnum;
- const isCompressed = valueRep.isCompressed;
- // Read array size
- let size;
- if ( this.version.major === 0 && this.version.minor < 7 ) {
- size = reader.readUint32();
- } else {
- size = reader.readUint64();
- }
- if ( ! Number.isSafeInteger( size ) || size < 0 ) {
- throw new RangeError( 'USDCParser: Invalid array size ' + size + ' for type ' + type + '.' );
- }
- if ( size > 0x7FFFFFFF ) {
- // Crate stores counts as uint64, but JS typed arrays cannot represent all such sizes.
- throw new RangeError( 'USDCParser: Array size ' + size + ' exceeds implementation limits.' );
- }
- if ( size === 0 ) return [];
- // Handle compressed arrays
- if ( isCompressed ) {
- return this._readCompressedArray( type, size );
- }
- // Read uncompressed array
- switch ( type ) {
- case TypeEnum.Int: {
- const arr = new Int32Array( size );
- for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readInt32();
- return arr;
- }
- case TypeEnum.UInt: {
- const arr = new Uint32Array( size );
- for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readUint32();
- return arr;
- }
- case TypeEnum.Float: {
- const arr = new Float32Array( size );
- for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readFloat32();
- return arr;
- }
- case TypeEnum.Double: {
- const arr = new Float64Array( size );
- for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readFloat64();
- return arr;
- }
- case TypeEnum.Vec2f: {
- const arr = new Float32Array( size * 2 );
- for ( let i = 0; i < size * 2; i ++ ) arr[ i ] = reader.readFloat32();
- return arr;
- }
- case TypeEnum.Vec3f: {
- const arr = new Float32Array( size * 3 );
- for ( let i = 0; i < size * 3; i ++ ) arr[ i ] = reader.readFloat32();
- return arr;
- }
- case TypeEnum.Vec4f: {
- const arr = new Float32Array( size * 4 );
- for ( let i = 0; i < size * 4; i ++ ) arr[ i ] = reader.readFloat32();
- return arr;
- }
- case TypeEnum.Vec3h: {
- // Half-precision vec3 array (used for scales in skeletal animation)
- const arr = new Float32Array( size * 3 );
- for ( let i = 0; i < size * 3; i ++ ) arr[ i ] = this._readHalf();
- return arr;
- }
- case TypeEnum.Quatf: {
- const arr = new Float32Array( size * 4 );
- for ( let i = 0; i < size * 4; i ++ ) arr[ i ] = reader.readFloat32();
- return arr;
- }
- case TypeEnum.Quath: {
- // Half-precision quaternion array
- const arr = new Float32Array( size * 4 );
- for ( let i = 0; i < size * 4; i ++ ) arr[ i ] = this._readHalf();
- return arr;
- }
- case TypeEnum.Matrix4d: {
- // 4x4 matrix array (16 doubles per matrix, row-major)
- const arr = new Float64Array( size * 16 );
- for ( let i = 0; i < size * 16; i ++ ) arr[ i ] = reader.readFloat64();
- return arr;
- }
- case TypeEnum.Token: {
- const arr = [];
- for ( let i = 0; i < size; i ++ ) {
- const index = reader.readUint32();
- arr.push( this.tokens[ index ] || '' );
- }
- return arr;
- }
- case TypeEnum.Half: {
- const arr = new Float32Array( size );
- for ( let i = 0; i < size; i ++ ) arr[ i ] = this._readHalf();
- return arr;
- }
- default:
- console.warn( 'USDCParser: Unsupported array type', type );
- return [];
- }
- }
- _readCompressedArray( type, size ) {
- const reader = this.reader;
- switch ( type ) {
- case TypeEnum.Int:
- case TypeEnum.UInt: {
- const compressedSize = reader.readUint64();
- const compressed = reader.readBytes( compressedSize );
- return decompressIntegers32(
- compressed.buffer.slice(
- compressed.byteOffset,
- compressed.byteOffset + compressedSize
- ),
- size
- );
- }
- case TypeEnum.Float: {
- // Float compression: 'i' = compressed as ints, 't' = lookup table
- const code = reader.readInt8();
- if ( code === FLOAT_COMPRESSION_INT ) {
- const compressedSize = reader.readUint64();
- const compressed = reader.readBytes( compressedSize );
- const ints = decompressIntegers32(
- compressed.buffer.slice(
- compressed.byteOffset,
- compressed.byteOffset + compressedSize
- ),
- size
- );
- const floats = new Float32Array( size );
- for ( let i = 0; i < size; i ++ ) floats[ i ] = ints[ i ];
- return floats;
- } else if ( code === FLOAT_COMPRESSION_LUT ) {
- const lutSize = reader.readUint32();
- const lut = new Float32Array( lutSize );
- for ( let i = 0; i < lutSize; i ++ ) lut[ i ] = reader.readFloat32();
- const compressedSize = reader.readUint64();
- const compressed = reader.readBytes( compressedSize );
- const indices = decompressIntegers32(
- compressed.buffer.slice(
- compressed.byteOffset,
- compressed.byteOffset + compressedSize
- ),
- size
- );
- const floats = new Float32Array( size );
- for ( let i = 0; i < size; i ++ ) floats[ i ] = lut[ indices[ i ] ];
- return floats;
- }
- console.warn( 'USDCParser: Unknown float compression code', code );
- return new Float32Array( size );
- }
- default:
- console.warn( 'USDCParser: Unsupported compressed array type', type );
- return [];
- }
- }
- _readHalf() {
- return this._halfToFloat( this.reader.readUint16() );
- }
- _halfToFloat( h ) {
- const sign = ( h & 0x8000 ) >> 15;
- const exp = ( h & 0x7C00 ) >> 10;
- const frac = h & 0x03FF;
- if ( exp === 0 ) {
- // Zero or denormalized number
- if ( frac === 0 ) {
- return sign ? - 0 : 0;
- }
- // Denormalized: value = ±2^-14 × (frac/1024)
- return ( sign ? - 1 : 1 ) * HALF_DENORM_SCALE * ( frac / 1024 );
- } else if ( exp === 31 ) {
- return frac ? NaN : ( sign ? - Infinity : Infinity );
- }
- return ( sign ? - 1 : 1 ) * HALF_EXPONENT_TABLE[ exp ] * ( 1 + frac / 1024 );
- }
- _getFieldsForSpec( spec ) {
- const fields = {};
- let fieldSetIndex = spec.fieldSetIndex;
- // Field sets are terminated by FIELD_SET_TERMINATOR
- // Limit iterations to prevent infinite loops from malformed data
- const maxIterations = 10000;
- let iterations = 0;
- while ( fieldSetIndex < this.fieldSets.length && iterations < maxIterations ) {
- const fieldIndex = this.fieldSets[ fieldSetIndex ];
- // Terminator
- if ( fieldIndex === FIELD_SET_TERMINATOR || fieldIndex === - 1 ) break;
- const field = this.fields[ fieldIndex ];
- if ( field ) {
- const name = this.tokens[ field.tokenIndex ];
- const value = this._readValue( field.valueRep );
- fields[ name ] = value;
- }
- fieldSetIndex ++;
- iterations ++;
- }
- return fields;
- }
- }
- export { USDCParser };
|