| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852 |
- 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( '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;
- 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 ( 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 };
|