USDCParser.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878
  1. const textDecoder = new TextDecoder();
  2. // Pre-computed half-float exponent lookup table for fast conversion
  3. // Math.pow(2, exp - 15) for exp = 0..31
  4. const HALF_EXPONENT_TABLE = new Float32Array( 32 );
  5. for ( let i = 0; i < 32; i ++ ) {
  6. HALF_EXPONENT_TABLE[ i ] = Math.pow( 2, i - 15 );
  7. }
  8. // Pre-computed constant for denormalized half-floats: 2^-14
  9. const HALF_DENORM_SCALE = Math.pow( 2, - 14 );
  10. // Type enum values from crateDataTypes.h
  11. const TypeEnum = {
  12. Invalid: 0,
  13. Bool: 1,
  14. UChar: 2,
  15. Int: 3,
  16. UInt: 4,
  17. Int64: 5,
  18. UInt64: 6,
  19. Half: 7,
  20. Float: 8,
  21. Double: 9,
  22. String: 10,
  23. Token: 11,
  24. AssetPath: 12,
  25. Matrix2d: 13,
  26. Matrix3d: 14,
  27. Matrix4d: 15,
  28. Quatd: 16,
  29. Quatf: 17,
  30. Quath: 18,
  31. Vec2d: 19,
  32. Vec2f: 20,
  33. Vec2h: 21,
  34. Vec2i: 22,
  35. Vec3d: 23,
  36. Vec3f: 24,
  37. Vec3h: 25,
  38. Vec3i: 26,
  39. Vec4d: 27,
  40. Vec4f: 28,
  41. Vec4h: 29,
  42. Vec4i: 30,
  43. Dictionary: 31,
  44. TokenListOp: 32,
  45. StringListOp: 33,
  46. PathListOp: 34,
  47. ReferenceListOp: 35,
  48. IntListOp: 36,
  49. Int64ListOp: 37,
  50. UIntListOp: 38,
  51. UInt64ListOp: 39,
  52. PathVector: 40,
  53. TokenVector: 41,
  54. Specifier: 42,
  55. Permission: 43,
  56. Variability: 44,
  57. VariantSelectionMap: 45,
  58. TimeSamples: 46,
  59. Payload: 47,
  60. DoubleVector: 48,
  61. LayerOffsetVector: 49,
  62. StringVector: 50,
  63. ValueBlock: 51,
  64. Value: 52,
  65. UnregisteredValue: 53,
  66. UnregisteredValueListOp: 54,
  67. PayloadListOp: 55,
  68. TimeCode: 56,
  69. PathExpression: 57,
  70. Relocates: 58,
  71. Spline: 59,
  72. AnimationBlock: 60
  73. };
  74. // Field set terminator marker
  75. const FIELD_SET_TERMINATOR = 0xFFFFFFFF;
  76. // Float compression type codes
  77. const FLOAT_COMPRESSION_INT = 0x69; // 'i' - compressed as integers
  78. const FLOAT_COMPRESSION_LUT = 0x74; // 't' - lookup table
  79. // ============================================================================
  80. // LZ4 Decompression (minimal implementation for USD)
  81. // Based on LZ4 block format specification
  82. // ============================================================================
  83. function lz4DecompressBlock( input, inputOffset, inputEnd, output, outputOffset, outputEnd ) {
  84. while ( inputOffset < inputEnd ) {
  85. // Read token
  86. const token = input[ inputOffset ++ ];
  87. if ( inputOffset > inputEnd ) break;
  88. // Literal length
  89. let literalLength = token >> 4;
  90. if ( literalLength === 15 ) {
  91. let b;
  92. do {
  93. if ( inputOffset >= inputEnd ) break;
  94. b = input[ inputOffset ++ ];
  95. literalLength += b;
  96. } while ( b === 255 && inputOffset < inputEnd );
  97. }
  98. // Copy literals
  99. if ( literalLength > 0 ) {
  100. if ( inputOffset + literalLength > inputEnd ) {
  101. literalLength = inputEnd - inputOffset;
  102. }
  103. for ( let i = 0; i < literalLength; i ++ ) {
  104. if ( outputOffset >= outputEnd ) break;
  105. output[ outputOffset ++ ] = input[ inputOffset ++ ];
  106. }
  107. }
  108. // Check if we're at the end (last sequence has no match)
  109. if ( inputOffset >= inputEnd ) break;
  110. // Read match offset (little-endian 16-bit)
  111. if ( inputOffset + 2 > inputEnd ) break;
  112. const matchOffset = input[ inputOffset ++ ] | ( input[ inputOffset ++ ] << 8 );
  113. if ( matchOffset === 0 ) {
  114. // Invalid offset
  115. break;
  116. }
  117. // Match length
  118. let matchLength = ( token & 0x0F ) + 4;
  119. if ( matchLength === 19 ) {
  120. let b;
  121. do {
  122. if ( inputOffset >= inputEnd ) break;
  123. b = input[ inputOffset ++ ];
  124. matchLength += b;
  125. } while ( b === 255 && inputOffset < inputEnd );
  126. }
  127. // Copy match (byte-by-byte to handle overlapping)
  128. const matchPos = outputOffset - matchOffset;
  129. if ( matchPos < 0 ) {
  130. // Invalid match position
  131. break;
  132. }
  133. for ( let i = 0; i < matchLength; i ++ ) {
  134. if ( outputOffset >= outputEnd ) break;
  135. output[ outputOffset ++ ] = output[ matchPos + i ];
  136. }
  137. }
  138. return outputOffset;
  139. }
  140. // USD uses TfFastCompression which wraps LZ4 with chunk headers
  141. function decompressLZ4( input, uncompressedSize ) {
  142. // TfFastCompression format (used by OpenUSD):
  143. // Single chunk (byte 0 == 0): [0] + LZ4 data
  144. // Multi chunk (byte 0 > 0): [numChunks] + [compressedSizes...] + [chunkData...]
  145. const output = new Uint8Array( uncompressedSize );
  146. const numChunks = input[ 0 ];
  147. if ( numChunks === 0 ) {
  148. // Single chunk - all remaining bytes are LZ4 compressed
  149. lz4DecompressBlock( input, 1, input.length, output, 0, uncompressedSize );
  150. return output;
  151. } else {
  152. // Multiple chunks - each chunk decompresses to max 65536 bytes
  153. const CHUNK_SIZE = 65536;
  154. // First, read all chunk sizes
  155. let headerOffset = 1;
  156. const compressedSizes = [];
  157. for ( let i = 0; i < numChunks; i ++ ) {
  158. const size = ( input[ headerOffset ] |
  159. ( input[ headerOffset + 1 ] << 8 ) |
  160. ( input[ headerOffset + 2 ] << 16 ) |
  161. ( input[ headerOffset + 3 ] << 24 ) ) >>> 0;
  162. compressedSizes.push( size );
  163. headerOffset += 4;
  164. }
  165. // Decompress each chunk
  166. let inputOffset = headerOffset;
  167. let outputOffset = 0;
  168. for ( let i = 0; i < numChunks; i ++ ) {
  169. const chunkCompressedSize = compressedSizes[ i ];
  170. const chunkOutputSize = Math.min( CHUNK_SIZE, uncompressedSize - outputOffset );
  171. lz4DecompressBlock(
  172. input, inputOffset, inputOffset + chunkCompressedSize,
  173. output, outputOffset, outputOffset + chunkOutputSize
  174. );
  175. inputOffset += chunkCompressedSize;
  176. outputOffset += chunkOutputSize;
  177. }
  178. return output;
  179. }
  180. }
  181. // ============================================================================
  182. // Integer Decompression (USD-specific delta + variable-width encoding)
  183. // ============================================================================
  184. function decompressIntegers32( compressedData, numInts ) {
  185. // First decompress with LZ4
  186. const encodedSize = numInts * 4 + ( ( numInts * 2 + 7 ) >> 3 ) + 4;
  187. const encoded = decompressLZ4( new Uint8Array( compressedData ), encodedSize );
  188. // Then decode
  189. return decodeIntegers32( encoded, numInts );
  190. }
  191. function decodeIntegers32( data, numInts ) {
  192. const view = new DataView( data.buffer, data.byteOffset, data.byteLength );
  193. let offset = 0;
  194. // Read common value (signed 32-bit)
  195. const commonValue = view.getInt32( offset, true );
  196. offset += 4;
  197. const numCodesBytes = ( numInts * 2 + 7 ) >> 3;
  198. const codesStart = offset;
  199. const vintsStart = offset + numCodesBytes;
  200. const result = new Int32Array( numInts );
  201. let prevVal = 0;
  202. let codesOffset = codesStart;
  203. let vintsOffset = vintsStart;
  204. for ( let i = 0; i < numInts; ) {
  205. const codeByte = data[ codesOffset ++ ];
  206. for ( let j = 0; j < 4 && i < numInts; j ++, i ++ ) {
  207. const code = ( codeByte >> ( j * 2 ) ) & 3;
  208. let delta = 0;
  209. switch ( code ) {
  210. case 0: // Common value
  211. delta = commonValue;
  212. break;
  213. case 1: // 8-bit signed
  214. delta = view.getInt8( vintsOffset );
  215. vintsOffset += 1;
  216. break;
  217. case 2: // 16-bit signed
  218. delta = view.getInt16( vintsOffset, true );
  219. vintsOffset += 2;
  220. break;
  221. case 3: // 32-bit signed
  222. delta = view.getInt32( vintsOffset, true );
  223. vintsOffset += 4;
  224. break;
  225. }
  226. prevVal += delta;
  227. result[ i ] = prevVal;
  228. }
  229. }
  230. return result;
  231. }
  232. // ============================================================================
  233. // Binary Reader Helper
  234. // ============================================================================
  235. class BinaryReader {
  236. constructor( buffer ) {
  237. this.buffer = buffer;
  238. this.view = new DataView( buffer );
  239. this.offset = 0;
  240. }
  241. seek( offset ) {
  242. this.offset = offset;
  243. }
  244. tell() {
  245. return this.offset;
  246. }
  247. readUint8() {
  248. const value = this.view.getUint8( this.offset );
  249. this.offset += 1;
  250. return value;
  251. }
  252. readInt8() {
  253. const value = this.view.getInt8( this.offset );
  254. this.offset += 1;
  255. return value;
  256. }
  257. readUint16() {
  258. const value = this.view.getUint16( this.offset, true );
  259. this.offset += 2;
  260. return value;
  261. }
  262. readInt16() {
  263. const value = this.view.getInt16( this.offset, true );
  264. this.offset += 2;
  265. return value;
  266. }
  267. readUint32() {
  268. const value = this.view.getUint32( this.offset, true );
  269. this.offset += 4;
  270. return value;
  271. }
  272. readInt32() {
  273. const value = this.view.getInt32( this.offset, true );
  274. this.offset += 4;
  275. return value;
  276. }
  277. readUint64() {
  278. const lo = this.view.getUint32( this.offset, true );
  279. const hi = this.view.getUint32( this.offset + 4, true );
  280. this.offset += 8;
  281. // For values that fit in Number, this is safe
  282. return hi * 0x100000000 + lo;
  283. }
  284. readInt64() {
  285. const lo = this.view.getUint32( this.offset, true );
  286. const hi = this.view.getInt32( this.offset + 4, true );
  287. this.offset += 8;
  288. return hi * 0x100000000 + lo;
  289. }
  290. readFloat32() {
  291. const value = this.view.getFloat32( this.offset, true );
  292. this.offset += 4;
  293. return value;
  294. }
  295. readFloat64() {
  296. const value = this.view.getFloat64( this.offset, true );
  297. this.offset += 8;
  298. return value;
  299. }
  300. readBytes( length ) {
  301. const bytes = new Uint8Array( this.buffer, this.offset, length );
  302. this.offset += length;
  303. return bytes;
  304. }
  305. readString( length ) {
  306. const bytes = this.readBytes( length );
  307. let end = 0;
  308. while ( end < length && bytes[ end ] !== 0 ) end ++;
  309. return textDecoder.decode( bytes.subarray( 0, end ) );
  310. }
  311. }
  312. // ============================================================================
  313. // ValueRep - 64-bit packed value representation
  314. // ============================================================================
  315. class ValueRep {
  316. constructor( lo, hi ) {
  317. this.lo = lo; // Lower 32 bits
  318. this.hi = hi; // Upper 32 bits
  319. }
  320. get isArray() {
  321. return ( this.hi & 0x80000000 ) !== 0;
  322. }
  323. get isInlined() {
  324. return ( this.hi & 0x40000000 ) !== 0;
  325. }
  326. get isCompressed() {
  327. return ( this.hi & 0x20000000 ) !== 0;
  328. }
  329. get typeEnum() {
  330. return ( this.hi >> 16 ) & 0xFF;
  331. }
  332. get payload() {
  333. // 48-bit payload: lo (32 bits) + hi lower 16 bits
  334. // Note: JavaScript numbers are IEEE 754 doubles with 53 bits of integer precision,
  335. // so 48-bit values are represented exactly without loss of precision.
  336. return this.lo + ( ( this.hi & 0xFFFF ) * 0x100000000 );
  337. }
  338. getInlinedValue() {
  339. // For inlined scalars, the value is in the lower 32 bits
  340. return this.lo;
  341. }
  342. }
  343. // ============================================================================
  344. // USDC Parser
  345. // ============================================================================
  346. class USDCParser {
  347. /**
  348. * Parse USDC file and return raw spec data without building Three.js scene.
  349. * Used by USDComposer for unified scene composition.
  350. */
  351. parseData( buffer ) {
  352. this.buffer = buffer instanceof ArrayBuffer ? buffer : buffer.buffer;
  353. this.reader = new BinaryReader( this.buffer );
  354. this.version = { major: 0, minor: 0, patch: 0 };
  355. this._conversionBuffer = new ArrayBuffer( 4 );
  356. this._conversionView = new DataView( this._conversionBuffer );
  357. this._readBootstrap();
  358. this._readTOC();
  359. this._readTokens();
  360. this._readStrings();
  361. this._readFields();
  362. this._readFieldSets();
  363. this._readPaths();
  364. this._readSpecs();
  365. // Build specsByPath without building scene
  366. this.specsByPath = {};
  367. for ( const spec of this.specs ) {
  368. const path = this.paths[ spec.pathIndex ];
  369. if ( ! path ) continue;
  370. const fields = this._getFieldsForSpec( spec );
  371. this.specsByPath[ path ] = { specType: spec.specType, fields };
  372. }
  373. return { specsByPath: this.specsByPath };
  374. }
  375. _readBootstrap() {
  376. const reader = this.reader;
  377. reader.seek( 0 );
  378. // Read magic "PXR-USDC"
  379. const magic = reader.readString( 8 );
  380. if ( magic !== 'PXR-USDC' ) {
  381. throw new Error( 'THREE.USDCParser: Not a valid USDC file.' );
  382. }
  383. // Read version
  384. this.version.major = reader.readUint8();
  385. this.version.minor = reader.readUint8();
  386. this.version.patch = reader.readUint8();
  387. reader.readBytes( 5 ); // Skip remaining version bytes
  388. // Read TOC offset
  389. this.tocOffset = reader.readUint64();
  390. // Skip reserved bytes (rest of 128-byte header)
  391. // Already at offset 24, skip to end of bootstrap (88 bytes total for bootstrap struct)
  392. }
  393. _readTOC() {
  394. const reader = this.reader;
  395. reader.seek( this.tocOffset );
  396. // Read number of sections
  397. const numSections = reader.readUint64();
  398. this.sections = {};
  399. for ( let i = 0; i < numSections; i ++ ) {
  400. const name = reader.readString( 16 );
  401. const start = reader.readUint64();
  402. const size = reader.readUint64();
  403. this.sections[ name ] = { start, size };
  404. }
  405. }
  406. _readTokens() {
  407. const section = this.sections[ 'TOKENS' ];
  408. if ( ! section ) return;
  409. const reader = this.reader;
  410. reader.seek( section.start );
  411. const numTokens = reader.readUint64();
  412. this.tokens = [];
  413. if ( this.version.major === 0 && this.version.minor < 4 ) {
  414. // Uncompressed tokens (version < 0.4.0)
  415. const tokensNumBytes = reader.readUint64();
  416. const tokensData = reader.readBytes( tokensNumBytes );
  417. let strStart = 0;
  418. for ( let i = 0; i < numTokens; i ++ ) {
  419. let strEnd = strStart;
  420. while ( strEnd < tokensData.length && tokensData[ strEnd ] !== 0 ) strEnd ++;
  421. this.tokens.push( textDecoder.decode( tokensData.subarray( strStart, strEnd ) ) );
  422. strStart = strEnd + 1;
  423. }
  424. } else {
  425. // Compressed tokens (version >= 0.4.0)
  426. const uncompressedSize = reader.readUint64();
  427. const compressedSize = reader.readUint64();
  428. const compressedData = reader.readBytes( compressedSize );
  429. const tokensData = decompressLZ4( compressedData, uncompressedSize );
  430. let strStart = 0;
  431. for ( let i = 0; i < numTokens; i ++ ) {
  432. let strEnd = strStart;
  433. while ( strEnd < tokensData.length && tokensData[ strEnd ] !== 0 ) strEnd ++;
  434. this.tokens.push( textDecoder.decode( tokensData.subarray( strStart, strEnd ) ) );
  435. strStart = strEnd + 1;
  436. }
  437. }
  438. }
  439. _readStrings() {
  440. const section = this.sections[ 'STRINGS' ];
  441. if ( ! section ) {
  442. this.strings = [];
  443. return;
  444. }
  445. const reader = this.reader;
  446. reader.seek( section.start );
  447. // Strings section has an 8-byte count prefix, but string indices stored
  448. // elsewhere in the file are relative to the section start (not the data).
  449. // So we read the entire section as uint32 values to maintain correct indexing.
  450. const numStrings = Math.floor( section.size / 4 );
  451. this.strings = [];
  452. for ( let i = 0; i < numStrings; i ++ ) {
  453. this.strings.push( reader.readUint32() );
  454. }
  455. }
  456. _readFields() {
  457. const section = this.sections[ 'FIELDS' ];
  458. if ( ! section ) return;
  459. const reader = this.reader;
  460. reader.seek( section.start );
  461. this.fields = [];
  462. if ( this.version.major === 0 && this.version.minor < 4 ) {
  463. // Uncompressed fields
  464. const numFields = Math.floor( section.size / 12 ); // 4 bytes token index + 8 bytes value rep
  465. for ( let i = 0; i < numFields; i ++ ) {
  466. const tokenIndex = reader.readUint32();
  467. const repLo = reader.readUint32();
  468. const repHi = reader.readUint32();
  469. this.fields.push( {
  470. tokenIndex,
  471. valueRep: new ValueRep( repLo, repHi )
  472. } );
  473. }
  474. } else {
  475. // Compressed fields (version >= 0.4.0)
  476. const numFields = reader.readUint64();
  477. // Read compressed token indices
  478. const tokenIndicesCompressedSize = reader.readUint64();
  479. const tokenIndicesCompressed = reader.readBytes( tokenIndicesCompressedSize );
  480. const tokenIndices = decompressIntegers32(
  481. tokenIndicesCompressed.buffer.slice(
  482. tokenIndicesCompressed.byteOffset,
  483. tokenIndicesCompressed.byteOffset + tokenIndicesCompressedSize
  484. ),
  485. numFields
  486. );
  487. // Read compressed value reps (LZ4 only, no integer encoding)
  488. const repsCompressedSize = reader.readUint64();
  489. const repsCompressed = reader.readBytes( repsCompressedSize );
  490. const repsData = decompressLZ4( repsCompressed, numFields * 8 );
  491. const repsView = new DataView( repsData.buffer, repsData.byteOffset, repsData.byteLength );
  492. for ( let i = 0; i < numFields; i ++ ) {
  493. const repLo = repsView.getUint32( i * 8, true );
  494. const repHi = repsView.getUint32( i * 8 + 4, true );
  495. this.fields.push( {
  496. tokenIndex: tokenIndices[ i ],
  497. valueRep: new ValueRep( repLo, repHi )
  498. } );
  499. }
  500. }
  501. }
  502. _readFieldSets() {
  503. const section = this.sections[ 'FIELDSETS' ];
  504. if ( ! section ) return;
  505. const reader = this.reader;
  506. reader.seek( section.start );
  507. this.fieldSets = [];
  508. if ( this.version.major === 0 && this.version.minor < 4 ) {
  509. // Uncompressed field sets
  510. const numFieldSets = Math.floor( section.size / 4 );
  511. for ( let i = 0; i < numFieldSets; i ++ ) {
  512. this.fieldSets.push( reader.readUint32() );
  513. }
  514. } else {
  515. // Compressed field sets
  516. const numFieldSets = reader.readUint64();
  517. const compressedSize = reader.readUint64();
  518. const compressed = reader.readBytes( compressedSize );
  519. const indices = decompressIntegers32(
  520. compressed.buffer.slice(
  521. compressed.byteOffset,
  522. compressed.byteOffset + compressedSize
  523. ),
  524. numFieldSets
  525. );
  526. for ( let i = 0; i < numFieldSets; i ++ ) {
  527. this.fieldSets.push( indices[ i ] );
  528. }
  529. }
  530. }
  531. _readPaths() {
  532. const section = this.sections[ 'PATHS' ];
  533. if ( ! section ) return;
  534. const reader = this.reader;
  535. reader.seek( section.start );
  536. const numPaths = reader.readUint64();
  537. this.paths = new Array( numPaths ).fill( '' );
  538. if ( this.version.major === 0 && this.version.minor < 4 ) {
  539. // Uncompressed paths - recursive tree structure
  540. this._readPathsRecursive( '' );
  541. } else {
  542. // Compressed paths (version >= 0.4.0)
  543. // Note: numPaths is stored twice - once for array sizing, once in compressed paths section
  544. reader.readUint64(); // Read duplicate numPaths value (matches numPaths above)
  545. const compressedSize1 = reader.readUint64();
  546. const pathIndicesCompressed = reader.readBytes( compressedSize1 );
  547. const pathIndices = decompressIntegers32(
  548. pathIndicesCompressed.buffer.slice(
  549. pathIndicesCompressed.byteOffset,
  550. pathIndicesCompressed.byteOffset + compressedSize1
  551. ),
  552. numPaths
  553. );
  554. const compressedSize2 = reader.readUint64();
  555. const elementTokenIndicesCompressed = reader.readBytes( compressedSize2 );
  556. const elementTokenIndices = decompressIntegers32(
  557. elementTokenIndicesCompressed.buffer.slice(
  558. elementTokenIndicesCompressed.byteOffset,
  559. elementTokenIndicesCompressed.byteOffset + compressedSize2
  560. ),
  561. numPaths
  562. );
  563. const compressedSize3 = reader.readUint64();
  564. const jumpsCompressed = reader.readBytes( compressedSize3 );
  565. const jumps = decompressIntegers32(
  566. jumpsCompressed.buffer.slice(
  567. jumpsCompressed.byteOffset,
  568. jumpsCompressed.byteOffset + compressedSize3
  569. ),
  570. numPaths
  571. );
  572. // Build paths from compressed data
  573. this._buildPathsFromCompressed( pathIndices, elementTokenIndices, jumps );
  574. }
  575. }
  576. _readPathsRecursive( parentPath, depth = 0 ) {
  577. const reader = this.reader;
  578. // Prevent infinite recursion
  579. if ( depth > 1000 ) return;
  580. // Read path item header
  581. const index = reader.readUint32();
  582. const elementTokenIndex = reader.readUint32();
  583. const bits = reader.readUint8();
  584. const hasChild = ( bits & 1 ) !== 0;
  585. const hasSibling = ( bits & 2 ) !== 0;
  586. const isPrimProperty = ( bits & 4 ) !== 0;
  587. // Build path
  588. let path;
  589. if ( parentPath === '' ) {
  590. path = '/';
  591. } else {
  592. const elemToken = this.tokens[ elementTokenIndex ] || '';
  593. if ( isPrimProperty ) {
  594. path = parentPath + '.' + elemToken;
  595. } else {
  596. path = parentPath === '/' ? '/' + elemToken : parentPath + '/' + elemToken;
  597. }
  598. }
  599. this.paths[ index ] = path;
  600. // Process children and siblings
  601. if ( hasChild && hasSibling ) {
  602. // Read sibling offset
  603. const siblingOffset = reader.readUint64();
  604. // Read child
  605. this._readPathsRecursive( path, depth + 1 );
  606. // Read sibling
  607. reader.seek( siblingOffset );
  608. this._readPathsRecursive( parentPath, depth + 1 );
  609. } else if ( hasChild ) {
  610. this._readPathsRecursive( path, depth + 1 );
  611. } else if ( hasSibling ) {
  612. this._readPathsRecursive( parentPath, depth + 1 );
  613. }
  614. }
  615. _buildPathsFromCompressed( pathIndices, elementTokenIndices, jumps ) {
  616. // Jump encoding from USD:
  617. // 0 = only sibling (no child), next entry is sibling
  618. // -1 = only child (no sibling), next entry is child
  619. // -2 = leaf (no child, no sibling)
  620. // >0 = has both child and sibling, value is offset to sibling
  621. const buildPaths = ( startIndex, parentPath ) => {
  622. let curIndex = startIndex;
  623. while ( curIndex < pathIndices.length ) {
  624. const thisIndex = curIndex ++;
  625. const pathIndex = pathIndices[ thisIndex ];
  626. const elementTokenIndex = elementTokenIndices[ thisIndex ];
  627. const jump = jumps[ thisIndex ];
  628. // Build path
  629. let path;
  630. if ( parentPath === '' ) {
  631. path = '/';
  632. parentPath = path;
  633. } else {
  634. const elemToken = this.tokens[ Math.abs( elementTokenIndex ) ] || '';
  635. const isPrimProperty = elementTokenIndex < 0;
  636. if ( isPrimProperty ) {
  637. path = parentPath + '.' + elemToken;
  638. } else {
  639. path = parentPath === '/' ? '/' + elemToken : parentPath + '/' + elemToken;
  640. }
  641. }
  642. this.paths[ pathIndex ] = path;
  643. // Determine children and siblings
  644. const hasChild = jump > 0 || jump === - 1;
  645. const hasSibling = jump >= 0;
  646. if ( hasChild ) {
  647. if ( hasSibling ) {
  648. // Has both child and sibling
  649. // Recursively process sibling subtree
  650. const siblingIndex = thisIndex + jump;
  651. buildPaths( siblingIndex, parentPath );
  652. }
  653. // Child is next entry, continue with new parent path
  654. parentPath = path;
  655. } else if ( hasSibling ) {
  656. // Only sibling, next entry is sibling with same parent
  657. // Just continue loop with curIndex and same parentPath
  658. } else {
  659. // Leaf node, exit loop
  660. break;
  661. }
  662. }
  663. };
  664. buildPaths( 0, '' );
  665. }
  666. _readSpecs() {
  667. const section = this.sections[ 'SPECS' ];
  668. if ( ! section ) return;
  669. const reader = this.reader;
  670. reader.seek( section.start );
  671. this.specs = [];
  672. if ( this.version.major === 0 && this.version.minor < 4 ) {
  673. // Uncompressed specs
  674. // Each spec: pathIndex (4), fieldSetIndex (4), specType (4) = 12 bytes
  675. // For version 0.0.1 there may be different padding
  676. const specSize = ( this.version.minor === 0 && this.version.patch === 1 ) ? 16 : 12;
  677. const numSpecs = Math.floor( section.size / specSize );
  678. for ( let i = 0; i < numSpecs; i ++ ) {
  679. const pathIndex = reader.readUint32();
  680. const fieldSetIndex = reader.readUint32();
  681. const specType = reader.readUint32();
  682. if ( specSize === 16 ) reader.readUint32(); // padding
  683. this.specs.push( { pathIndex, fieldSetIndex, specType } );
  684. }
  685. } else {
  686. // Compressed specs
  687. const numSpecs = reader.readUint64();
  688. const compressedSize1 = reader.readUint64();
  689. const pathIndicesCompressed = reader.readBytes( compressedSize1 );
  690. const pathIndices = decompressIntegers32(
  691. pathIndicesCompressed.buffer.slice(
  692. pathIndicesCompressed.byteOffset,
  693. pathIndicesCompressed.byteOffset + compressedSize1
  694. ),
  695. numSpecs
  696. );
  697. const compressedSize2 = reader.readUint64();
  698. const fieldSetIndicesCompressed = reader.readBytes( compressedSize2 );
  699. const fieldSetIndices = decompressIntegers32(
  700. fieldSetIndicesCompressed.buffer.slice(
  701. fieldSetIndicesCompressed.byteOffset,
  702. fieldSetIndicesCompressed.byteOffset + compressedSize2
  703. ),
  704. numSpecs
  705. );
  706. const compressedSize3 = reader.readUint64();
  707. const specTypesCompressed = reader.readBytes( compressedSize3 );
  708. const specTypes = decompressIntegers32(
  709. specTypesCompressed.buffer.slice(
  710. specTypesCompressed.byteOffset,
  711. specTypesCompressed.byteOffset + compressedSize3
  712. ),
  713. numSpecs
  714. );
  715. for ( let i = 0; i < numSpecs; i ++ ) {
  716. this.specs.push( {
  717. pathIndex: pathIndices[ i ],
  718. fieldSetIndex: fieldSetIndices[ i ],
  719. specType: specTypes[ i ]
  720. } );
  721. }
  722. }
  723. }
  724. // ========================================================================
  725. // Value Reading
  726. // ========================================================================
  727. _readValue( valueRep ) {
  728. const type = valueRep.typeEnum;
  729. const isArray = valueRep.isArray;
  730. const isInlined = valueRep.isInlined;
  731. // Handle TimeSamples specially - they have their own format
  732. if ( type === TypeEnum.TimeSamples ) {
  733. return this._readTimeSamples( valueRep );
  734. }
  735. if ( isInlined ) {
  736. return this._readInlinedValue( valueRep );
  737. }
  738. // Seek to payload offset and read value
  739. const offset = valueRep.payload;
  740. if ( offset === 0 && isArray ) {
  741. // Spec 16.3.9.3: Array payload 0 is an explicit empty-array sentinel.
  742. return [];
  743. }
  744. if ( offset < 0 || offset >= this.buffer.byteLength ) {
  745. throw new RangeError( 'USDCParser: Invalid payload offset ' + offset + ' for type ' + type + '.' );
  746. }
  747. const savedOffset = this.reader.tell();
  748. this.reader.seek( offset );
  749. let value;
  750. if ( isArray ) {
  751. value = this._readArrayValue( valueRep );
  752. } else {
  753. value = this._readScalarValue( type );
  754. }
  755. this.reader.seek( savedOffset );
  756. return value;
  757. }
  758. _readInlinedValue( valueRep ) {
  759. const type = valueRep.typeEnum;
  760. const payload = valueRep.getInlinedValue();
  761. const view = this._conversionView;
  762. switch ( type ) {
  763. case TypeEnum.Bool:
  764. return payload !== 0;
  765. case TypeEnum.UChar:
  766. return payload & 0xFF;
  767. case TypeEnum.Int:
  768. case TypeEnum.UInt:
  769. return payload;
  770. case TypeEnum.Float: {
  771. view.setUint32( 0, payload, true );
  772. return view.getFloat32( 0, true );
  773. }
  774. case TypeEnum.Double: {
  775. // When a double is inlined, it's stored as float32 bits in the payload
  776. view.setUint32( 0, payload, true );
  777. return view.getFloat32( 0, true );
  778. }
  779. case TypeEnum.Token:
  780. return this.tokens[ payload ] || '';
  781. case TypeEnum.String:
  782. return this.tokens[ this.strings[ payload ] ] || '';
  783. case TypeEnum.AssetPath:
  784. return this.tokens[ payload ] || '';
  785. case TypeEnum.Specifier:
  786. return payload; // 0=def, 1=over, 2=class
  787. case TypeEnum.Permission:
  788. case TypeEnum.Variability:
  789. return payload;
  790. // Vec2h: Two half-floats fit in 4 bytes, stored directly
  791. case TypeEnum.Vec2h: {
  792. view.setUint32( 0, payload, true );
  793. return [ this._halfToFloat( view.getUint16( 0, true ) ), this._halfToFloat( view.getUint16( 2, true ) ) ];
  794. }
  795. // Inlined vectors that don't fit in 4 bytes are encoded as signed 8-bit integers
  796. // Vec2f = 8 bytes (2x float32), Vec3f = 12 bytes, Vec4f = 16 bytes, etc.
  797. case TypeEnum.Vec2f:
  798. case TypeEnum.Vec2i: {
  799. view.setUint32( 0, payload, true );
  800. return [ view.getInt8( 0 ), view.getInt8( 1 ) ];
  801. }
  802. case TypeEnum.Vec3f:
  803. case TypeEnum.Vec3i: {
  804. view.setUint32( 0, payload, true );
  805. return [ view.getInt8( 0 ), view.getInt8( 1 ), view.getInt8( 2 ) ];
  806. }
  807. case TypeEnum.Vec4f:
  808. case TypeEnum.Vec4i: {
  809. view.setUint32( 0, payload, true );
  810. return [ view.getInt8( 0 ), view.getInt8( 1 ), view.getInt8( 2 ), view.getInt8( 3 ) ];
  811. }
  812. case TypeEnum.Matrix2d: {
  813. // Inlined Matrix2d stores diagonal values as 2 signed int8 values
  814. view.setUint32( 0, payload, true );
  815. const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 );
  816. return [ d0, 0, 0, d1 ];
  817. }
  818. case TypeEnum.Matrix3d: {
  819. // Inlined Matrix3d stores diagonal values as 3 signed int8 values
  820. view.setUint32( 0, payload, true );
  821. const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 );
  822. return [ d0, 0, 0, 0, d1, 0, 0, 0, d2 ];
  823. }
  824. case TypeEnum.Matrix4d: {
  825. // Inlined Matrix4d stores diagonal values as 4 signed int8 values
  826. view.setUint32( 0, payload, true );
  827. const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 ), d3 = view.getInt8( 3 );
  828. return [ d0, 0, 0, 0, 0, d1, 0, 0, 0, 0, d2, 0, 0, 0, 0, d3 ];
  829. }
  830. default:
  831. return payload;
  832. }
  833. }
  834. _readTimeSamples( valueRep ) {
  835. const reader = this.reader;
  836. const offset = valueRep.payload;
  837. const savedOffset = reader.tell();
  838. reader.seek( offset );
  839. // TimeSamples format uses RELATIVE offsets (from OpenUSD _RecursiveRead):
  840. // _RecursiveRead: read int64 relativeOffset at current position, then seek to start + relativeOffset
  841. // After reading timesRep, continue reading from current position (after timesRep)
  842. // Layout at TimeSamples location:
  843. // - int64 timesOffset (relative from start of this int64)
  844. // At (start + timesOffset): timesRep ValueRep, then int64 valuesOffset, then numValues + ValueReps
  845. // Read times relative offset and resolve
  846. const timesStart = reader.tell();
  847. const timesRelOffset = reader.readInt64();
  848. reader.seek( timesStart + timesRelOffset );
  849. const timesRepLo = reader.readUint32();
  850. const timesRepHi = reader.readUint32();
  851. const timesRep = new ValueRep( timesRepLo, timesRepHi );
  852. // Resolve times array
  853. const times = this._readValue( timesRep );
  854. // Continue reading from current position (after timesRep)
  855. // The second _RecursiveRead reads from CURRENT position, not from the beginning
  856. const afterTimesRep = timesStart + timesRelOffset + 8;
  857. reader.seek( afterTimesRep );
  858. // Read values relative offset
  859. const valuesStart = reader.tell();
  860. const valuesRelOffset = reader.readInt64();
  861. reader.seek( valuesStart + valuesRelOffset );
  862. // Read number of values
  863. const numValues = reader.readUint64();
  864. // Read all ValueReps
  865. const valueReps = [];
  866. for ( let i = 0; i < numValues; i ++ ) {
  867. const repLo = reader.readUint32();
  868. const repHi = reader.readUint32();
  869. valueReps.push( new ValueRep( repLo, repHi ) );
  870. }
  871. // Resolve each value
  872. const values = [];
  873. for ( let i = 0; i < numValues; i ++ ) {
  874. values.push( this._readValue( valueReps[ i ] ) );
  875. }
  876. reader.seek( savedOffset );
  877. // Convert times to array if needed
  878. const timesArray = times instanceof Float64Array ? Array.from( times ) : ( Array.isArray( times ) ? times : [ times ] );
  879. return { times: timesArray, values };
  880. }
  881. _readScalarValue( type ) {
  882. const reader = this.reader;
  883. switch ( type ) {
  884. case TypeEnum.Invalid:
  885. return null;
  886. case TypeEnum.Bool:
  887. return reader.readUint8() !== 0;
  888. case TypeEnum.UChar:
  889. return reader.readUint8();
  890. case TypeEnum.Int:
  891. return reader.readInt32();
  892. case TypeEnum.UInt:
  893. return reader.readUint32();
  894. case TypeEnum.Int64:
  895. return reader.readInt64();
  896. case TypeEnum.UInt64:
  897. return reader.readUint64();
  898. case TypeEnum.Half:
  899. return this._readHalf();
  900. case TypeEnum.Float:
  901. return reader.readFloat32();
  902. case TypeEnum.Double:
  903. return reader.readFloat64();
  904. case TypeEnum.String:
  905. case TypeEnum.Token: {
  906. const index = reader.readUint32();
  907. return this.tokens[ index ] || '';
  908. }
  909. case TypeEnum.AssetPath: {
  910. const index = reader.readUint32();
  911. return this.tokens[ index ] || '';
  912. }
  913. case TypeEnum.Vec2f:
  914. return [ reader.readFloat32(), reader.readFloat32() ];
  915. case TypeEnum.Vec2d:
  916. return [ reader.readFloat64(), reader.readFloat64() ];
  917. case TypeEnum.Vec2i:
  918. return [ reader.readInt32(), reader.readInt32() ];
  919. case TypeEnum.Vec3f:
  920. return [ reader.readFloat32(), reader.readFloat32(), reader.readFloat32() ];
  921. case TypeEnum.Vec3d:
  922. return [ reader.readFloat64(), reader.readFloat64(), reader.readFloat64() ];
  923. case TypeEnum.Vec3i:
  924. return [ reader.readInt32(), reader.readInt32(), reader.readInt32() ];
  925. case TypeEnum.Vec4f:
  926. return [ reader.readFloat32(), reader.readFloat32(), reader.readFloat32(), reader.readFloat32() ];
  927. case TypeEnum.Vec4d:
  928. return [ reader.readFloat64(), reader.readFloat64(), reader.readFloat64(), reader.readFloat64() ];
  929. case TypeEnum.Quatf:
  930. return [ reader.readFloat32(), reader.readFloat32(), reader.readFloat32(), reader.readFloat32() ];
  931. case TypeEnum.Quatd:
  932. return [ reader.readFloat64(), reader.readFloat64(), reader.readFloat64(), reader.readFloat64() ];
  933. case TypeEnum.Matrix4d: {
  934. const m = [];
  935. for ( let i = 0; i < 16; i ++ ) m.push( reader.readFloat64() );
  936. return m;
  937. }
  938. case TypeEnum.TokenVector: {
  939. const count = reader.readUint64();
  940. const tokens = [];
  941. for ( let i = 0; i < count; i ++ ) {
  942. const index = reader.readUint32();
  943. tokens.push( this.tokens[ index ] || '' );
  944. }
  945. return tokens;
  946. }
  947. case TypeEnum.PathVector: {
  948. const count = reader.readUint64();
  949. const paths = [];
  950. for ( let i = 0; i < count; i ++ ) {
  951. const index = reader.readUint32();
  952. paths.push( this.paths[ index ] || '' );
  953. }
  954. return paths;
  955. }
  956. case TypeEnum.DoubleVector: {
  957. // DoubleVector is a count-prefixed array of doubles
  958. const count = reader.readUint64();
  959. const arr = new Float64Array( count );
  960. for ( let i = 0; i < count; i ++ ) arr[ i ] = reader.readFloat64();
  961. return arr;
  962. }
  963. case TypeEnum.Dictionary: {
  964. // Dictionary format:
  965. // u64 elementCount
  966. // For each element: u32 keyIndex + i64 valueOffset (relative)
  967. const elementCount = reader.readUint64();
  968. const dict = {};
  969. for ( let i = 0; i < elementCount; i ++ ) {
  970. const keyIdx = reader.readUint32();
  971. const key = this.tokens[ keyIdx ];
  972. // Value offset is relative to current position
  973. const currentPos = reader.position;
  974. const valueOffset = reader.readInt64();
  975. const valuePos = currentPos + valueOffset;
  976. // Save position, read value, restore position
  977. const savedPos = reader.position;
  978. reader.position = valuePos;
  979. // Read the value representation at the offset
  980. const valueRepData = reader.readUint64();
  981. const valueRep = new ValueRep( valueRepData );
  982. // Read the value based on the representation
  983. let value = null;
  984. if ( valueRep.isInlined ) {
  985. value = this._readInlinedValue( valueRep );
  986. } else if ( valueRep.isArray ) {
  987. reader.position = valueRep.payload;
  988. value = this._readArrayValue( valueRep );
  989. } else {
  990. reader.position = valueRep.payload;
  991. value = this._readScalarValue( valueRep.typeEnum );
  992. }
  993. reader.position = savedPos;
  994. if ( key !== undefined && value !== null ) {
  995. dict[ key ] = value;
  996. }
  997. }
  998. return dict;
  999. }
  1000. case TypeEnum.TokenListOp:
  1001. case TypeEnum.StringListOp:
  1002. case TypeEnum.IntListOp:
  1003. case TypeEnum.Int64ListOp:
  1004. case TypeEnum.UIntListOp:
  1005. case TypeEnum.UInt64ListOp:
  1006. // These complex types are not needed for geometry loading
  1007. // Skip them silently
  1008. return null;
  1009. case TypeEnum.PathListOp: {
  1010. // PathListOp format (from AOUSD Core Spec 16.3.10.25):
  1011. // Header byte bitmask:
  1012. // - bit 0 (0x01): Make Explicit (clears list)
  1013. // - bit 1 (0x02): Add Explicit Items
  1014. // - bit 2 (0x04): Add Items
  1015. // - bit 3 (0x08): Delete Items
  1016. // - bit 4 (0x10): Reorder Items
  1017. // - bit 5 (0x20): Prepend Items
  1018. // - bit 6 (0x40): Append Items
  1019. // Arrays follow in order: Explicit, Add, Prepend, Append, Delete, Reorder
  1020. // Each array: uint64 count + count * uint32 path indices
  1021. const flags = reader.readUint8();
  1022. const hasExplicitItems = ( flags & 0x02 ) !== 0;
  1023. const hasAddItems = ( flags & 0x04 ) !== 0;
  1024. const hasDeleteItems = ( flags & 0x08 ) !== 0;
  1025. const hasReorderItems = ( flags & 0x10 ) !== 0;
  1026. const hasPrependItems = ( flags & 0x20 ) !== 0;
  1027. const hasAppendItems = ( flags & 0x40 ) !== 0;
  1028. const readPathList = () => {
  1029. const itemCount = reader.readUint64();
  1030. const paths = [];
  1031. for ( let i = 0; i < itemCount; i ++ ) {
  1032. const pathIdx = reader.readUint32();
  1033. paths.push( this.paths[ pathIdx ] );
  1034. }
  1035. return paths;
  1036. };
  1037. // Read arrays in spec order: Explicit, Add, Prepend, Append, Delete, Reorder
  1038. let explicitPaths = null;
  1039. let addPaths = null;
  1040. let prependPaths = null;
  1041. let appendPaths = null;
  1042. if ( hasExplicitItems ) explicitPaths = readPathList();
  1043. if ( hasAddItems ) addPaths = readPathList();
  1044. if ( hasPrependItems ) prependPaths = readPathList();
  1045. if ( hasAppendItems ) appendPaths = readPathList();
  1046. if ( hasDeleteItems ) readPathList(); // Skip delete items
  1047. if ( hasReorderItems ) readPathList(); // Skip reorder items
  1048. // Return the first non-empty list (connections are typically prepended)
  1049. if ( prependPaths && prependPaths.length > 0 ) return prependPaths;
  1050. if ( explicitPaths && explicitPaths.length > 0 ) return explicitPaths;
  1051. if ( appendPaths && appendPaths.length > 0 ) return appendPaths;
  1052. if ( addPaths && addPaths.length > 0 ) return addPaths;
  1053. return null;
  1054. }
  1055. case TypeEnum.VariantSelectionMap: {
  1056. const elementCount = reader.readUint64();
  1057. const map = {};
  1058. for ( let i = 0; i < elementCount; i ++ ) {
  1059. const keyIdx = reader.readUint32();
  1060. const valueIdx = reader.readUint32();
  1061. const key = this.tokens[ this.strings[ keyIdx ] ];
  1062. const value = this.tokens[ this.strings[ valueIdx ] ];
  1063. if ( key && value ) map[ key ] = value;
  1064. }
  1065. return map;
  1066. }
  1067. default:
  1068. console.warn( 'USDCParser: Unsupported scalar type', type );
  1069. return null;
  1070. }
  1071. }
  1072. _readArrayValue( valueRep ) {
  1073. const reader = this.reader;
  1074. const type = valueRep.typeEnum;
  1075. const isCompressed = valueRep.isCompressed;
  1076. // Read array size
  1077. let size;
  1078. if ( this.version.major === 0 && this.version.minor < 7 ) {
  1079. size = reader.readUint32();
  1080. } else {
  1081. size = reader.readUint64();
  1082. }
  1083. if ( ! Number.isSafeInteger( size ) || size < 0 ) {
  1084. throw new RangeError( 'USDCParser: Invalid array size ' + size + ' for type ' + type + '.' );
  1085. }
  1086. if ( size > 0x7FFFFFFF ) {
  1087. // Crate stores counts as uint64, but JS typed arrays cannot represent all such sizes.
  1088. throw new RangeError( 'USDCParser: Array size ' + size + ' exceeds implementation limits.' );
  1089. }
  1090. if ( size === 0 ) return [];
  1091. // Handle compressed arrays
  1092. if ( isCompressed ) {
  1093. return this._readCompressedArray( type, size );
  1094. }
  1095. // Read uncompressed array
  1096. switch ( type ) {
  1097. case TypeEnum.Int: {
  1098. const arr = new Int32Array( size );
  1099. for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readInt32();
  1100. return arr;
  1101. }
  1102. case TypeEnum.UInt: {
  1103. const arr = new Uint32Array( size );
  1104. for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readUint32();
  1105. return arr;
  1106. }
  1107. case TypeEnum.Float: {
  1108. const arr = new Float32Array( size );
  1109. for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readFloat32();
  1110. return arr;
  1111. }
  1112. case TypeEnum.Double: {
  1113. const arr = new Float64Array( size );
  1114. for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readFloat64();
  1115. return arr;
  1116. }
  1117. case TypeEnum.Vec2f: {
  1118. const arr = new Float32Array( size * 2 );
  1119. for ( let i = 0; i < size * 2; i ++ ) arr[ i ] = reader.readFloat32();
  1120. return arr;
  1121. }
  1122. case TypeEnum.Vec3f: {
  1123. const arr = new Float32Array( size * 3 );
  1124. for ( let i = 0; i < size * 3; i ++ ) arr[ i ] = reader.readFloat32();
  1125. return arr;
  1126. }
  1127. case TypeEnum.Vec4f: {
  1128. const arr = new Float32Array( size * 4 );
  1129. for ( let i = 0; i < size * 4; i ++ ) arr[ i ] = reader.readFloat32();
  1130. return arr;
  1131. }
  1132. case TypeEnum.Vec3h: {
  1133. // Half-precision vec3 array (used for scales in skeletal animation)
  1134. const arr = new Float32Array( size * 3 );
  1135. for ( let i = 0; i < size * 3; i ++ ) arr[ i ] = this._readHalf();
  1136. return arr;
  1137. }
  1138. case TypeEnum.Quatf: {
  1139. const arr = new Float32Array( size * 4 );
  1140. for ( let i = 0; i < size * 4; i ++ ) arr[ i ] = reader.readFloat32();
  1141. return arr;
  1142. }
  1143. case TypeEnum.Quath: {
  1144. // Half-precision quaternion array
  1145. const arr = new Float32Array( size * 4 );
  1146. for ( let i = 0; i < size * 4; i ++ ) arr[ i ] = this._readHalf();
  1147. return arr;
  1148. }
  1149. case TypeEnum.Matrix4d: {
  1150. // 4x4 matrix array (16 doubles per matrix, row-major)
  1151. const arr = new Float64Array( size * 16 );
  1152. for ( let i = 0; i < size * 16; i ++ ) arr[ i ] = reader.readFloat64();
  1153. return arr;
  1154. }
  1155. case TypeEnum.Token: {
  1156. const arr = [];
  1157. for ( let i = 0; i < size; i ++ ) {
  1158. const index = reader.readUint32();
  1159. arr.push( this.tokens[ index ] || '' );
  1160. }
  1161. return arr;
  1162. }
  1163. case TypeEnum.Half: {
  1164. const arr = new Float32Array( size );
  1165. for ( let i = 0; i < size; i ++ ) arr[ i ] = this._readHalf();
  1166. return arr;
  1167. }
  1168. default:
  1169. console.warn( 'USDCParser: Unsupported array type', type );
  1170. return [];
  1171. }
  1172. }
  1173. _readCompressedArray( type, size ) {
  1174. const reader = this.reader;
  1175. switch ( type ) {
  1176. case TypeEnum.Int:
  1177. case TypeEnum.UInt: {
  1178. const compressedSize = reader.readUint64();
  1179. const compressed = reader.readBytes( compressedSize );
  1180. return decompressIntegers32(
  1181. compressed.buffer.slice(
  1182. compressed.byteOffset,
  1183. compressed.byteOffset + compressedSize
  1184. ),
  1185. size
  1186. );
  1187. }
  1188. case TypeEnum.Float: {
  1189. // Float compression: 'i' = compressed as ints, 't' = lookup table
  1190. const code = reader.readInt8();
  1191. if ( code === FLOAT_COMPRESSION_INT ) {
  1192. const compressedSize = reader.readUint64();
  1193. const compressed = reader.readBytes( compressedSize );
  1194. const ints = decompressIntegers32(
  1195. compressed.buffer.slice(
  1196. compressed.byteOffset,
  1197. compressed.byteOffset + compressedSize
  1198. ),
  1199. size
  1200. );
  1201. const floats = new Float32Array( size );
  1202. for ( let i = 0; i < size; i ++ ) floats[ i ] = ints[ i ];
  1203. return floats;
  1204. } else if ( code === FLOAT_COMPRESSION_LUT ) {
  1205. const lutSize = reader.readUint32();
  1206. const lut = new Float32Array( lutSize );
  1207. for ( let i = 0; i < lutSize; i ++ ) lut[ i ] = reader.readFloat32();
  1208. const compressedSize = reader.readUint64();
  1209. const compressed = reader.readBytes( compressedSize );
  1210. const indices = decompressIntegers32(
  1211. compressed.buffer.slice(
  1212. compressed.byteOffset,
  1213. compressed.byteOffset + compressedSize
  1214. ),
  1215. size
  1216. );
  1217. const floats = new Float32Array( size );
  1218. for ( let i = 0; i < size; i ++ ) floats[ i ] = lut[ indices[ i ] ];
  1219. return floats;
  1220. }
  1221. console.warn( 'USDCParser: Unknown float compression code', code );
  1222. return new Float32Array( size );
  1223. }
  1224. default:
  1225. console.warn( 'USDCParser: Unsupported compressed array type', type );
  1226. return [];
  1227. }
  1228. }
  1229. _readHalf() {
  1230. return this._halfToFloat( this.reader.readUint16() );
  1231. }
  1232. _halfToFloat( h ) {
  1233. const sign = ( h & 0x8000 ) >> 15;
  1234. const exp = ( h & 0x7C00 ) >> 10;
  1235. const frac = h & 0x03FF;
  1236. if ( exp === 0 ) {
  1237. // Zero or denormalized number
  1238. if ( frac === 0 ) {
  1239. return sign ? - 0 : 0;
  1240. }
  1241. // Denormalized: value = ±2^-14 × (frac/1024)
  1242. return ( sign ? - 1 : 1 ) * HALF_DENORM_SCALE * ( frac / 1024 );
  1243. } else if ( exp === 31 ) {
  1244. return frac ? NaN : ( sign ? - Infinity : Infinity );
  1245. }
  1246. return ( sign ? - 1 : 1 ) * HALF_EXPONENT_TABLE[ exp ] * ( 1 + frac / 1024 );
  1247. }
  1248. _getFieldsForSpec( spec ) {
  1249. const fields = {};
  1250. let fieldSetIndex = spec.fieldSetIndex;
  1251. // Field sets are terminated by FIELD_SET_TERMINATOR
  1252. // Limit iterations to prevent infinite loops from malformed data
  1253. const maxIterations = 10000;
  1254. let iterations = 0;
  1255. while ( fieldSetIndex < this.fieldSets.length && iterations < maxIterations ) {
  1256. const fieldIndex = this.fieldSets[ fieldSetIndex ];
  1257. // Terminator
  1258. if ( fieldIndex === FIELD_SET_TERMINATOR || fieldIndex === - 1 ) break;
  1259. const field = this.fields[ fieldIndex ];
  1260. if ( field ) {
  1261. const name = this.tokens[ field.tokenIndex ];
  1262. const value = this._readValue( field.valueRep );
  1263. fields[ name ] = value;
  1264. }
  1265. fieldSetIndex ++;
  1266. iterations ++;
  1267. }
  1268. return fields;
  1269. }
  1270. }
  1271. export { USDCParser };
粤ICP备19079148号