USDCParser.js 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852
  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( '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. const savedOffset = this.reader.tell();
  741. this.reader.seek( offset );
  742. let value;
  743. if ( isArray ) {
  744. value = this._readArrayValue( valueRep );
  745. } else {
  746. value = this._readScalarValue( type );
  747. }
  748. this.reader.seek( savedOffset );
  749. return value;
  750. }
  751. _readInlinedValue( valueRep ) {
  752. const type = valueRep.typeEnum;
  753. const payload = valueRep.getInlinedValue();
  754. const view = this._conversionView;
  755. switch ( type ) {
  756. case TypeEnum.Bool:
  757. return payload !== 0;
  758. case TypeEnum.UChar:
  759. return payload & 0xFF;
  760. case TypeEnum.Int:
  761. case TypeEnum.UInt:
  762. return payload;
  763. case TypeEnum.Float: {
  764. view.setUint32( 0, payload, true );
  765. return view.getFloat32( 0, true );
  766. }
  767. case TypeEnum.Double: {
  768. // When a double is inlined, it's stored as float32 bits in the payload
  769. view.setUint32( 0, payload, true );
  770. return view.getFloat32( 0, true );
  771. }
  772. case TypeEnum.Token:
  773. return this.tokens[ payload ] || '';
  774. case TypeEnum.String:
  775. return this.tokens[ this.strings[ payload ] ] || '';
  776. case TypeEnum.AssetPath:
  777. return this.tokens[ payload ] || '';
  778. case TypeEnum.Specifier:
  779. return payload; // 0=def, 1=over, 2=class
  780. case TypeEnum.Permission:
  781. case TypeEnum.Variability:
  782. return payload;
  783. // Vec2h: Two half-floats fit in 4 bytes, stored directly
  784. case TypeEnum.Vec2h: {
  785. view.setUint32( 0, payload, true );
  786. return [ this._halfToFloat( view.getUint16( 0, true ) ), this._halfToFloat( view.getUint16( 2, true ) ) ];
  787. }
  788. // Inlined vectors that don't fit in 4 bytes are encoded as signed 8-bit integers
  789. // Vec2f = 8 bytes (2x float32), Vec3f = 12 bytes, Vec4f = 16 bytes, etc.
  790. case TypeEnum.Vec2f:
  791. case TypeEnum.Vec2i: {
  792. view.setUint32( 0, payload, true );
  793. return [ view.getInt8( 0 ), view.getInt8( 1 ) ];
  794. }
  795. case TypeEnum.Vec3f:
  796. case TypeEnum.Vec3i: {
  797. view.setUint32( 0, payload, true );
  798. return [ view.getInt8( 0 ), view.getInt8( 1 ), view.getInt8( 2 ) ];
  799. }
  800. case TypeEnum.Vec4f:
  801. case TypeEnum.Vec4i: {
  802. view.setUint32( 0, payload, true );
  803. return [ view.getInt8( 0 ), view.getInt8( 1 ), view.getInt8( 2 ), view.getInt8( 3 ) ];
  804. }
  805. case TypeEnum.Matrix2d: {
  806. // Inlined Matrix2d stores diagonal values as 2 signed int8 values
  807. view.setUint32( 0, payload, true );
  808. const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 );
  809. return [ d0, 0, 0, d1 ];
  810. }
  811. case TypeEnum.Matrix3d: {
  812. // Inlined Matrix3d stores diagonal values as 3 signed int8 values
  813. view.setUint32( 0, payload, true );
  814. const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 );
  815. return [ d0, 0, 0, 0, d1, 0, 0, 0, d2 ];
  816. }
  817. case TypeEnum.Matrix4d: {
  818. // Inlined Matrix4d stores diagonal values as 4 signed int8 values
  819. view.setUint32( 0, payload, true );
  820. const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 ), d3 = view.getInt8( 3 );
  821. return [ d0, 0, 0, 0, 0, d1, 0, 0, 0, 0, d2, 0, 0, 0, 0, d3 ];
  822. }
  823. default:
  824. return payload;
  825. }
  826. }
  827. _readTimeSamples( valueRep ) {
  828. const reader = this.reader;
  829. const offset = valueRep.payload;
  830. const savedOffset = reader.tell();
  831. reader.seek( offset );
  832. // TimeSamples format uses RELATIVE offsets (from OpenUSD _RecursiveRead):
  833. // _RecursiveRead: read int64 relativeOffset at current position, then seek to start + relativeOffset
  834. // After reading timesRep, continue reading from current position (after timesRep)
  835. // Layout at TimeSamples location:
  836. // - int64 timesOffset (relative from start of this int64)
  837. // At (start + timesOffset): timesRep ValueRep, then int64 valuesOffset, then numValues + ValueReps
  838. // Read times relative offset and resolve
  839. const timesStart = reader.tell();
  840. const timesRelOffset = reader.readInt64();
  841. reader.seek( timesStart + timesRelOffset );
  842. const timesRepLo = reader.readUint32();
  843. const timesRepHi = reader.readUint32();
  844. const timesRep = new ValueRep( timesRepLo, timesRepHi );
  845. // Resolve times array
  846. const times = this._readValue( timesRep );
  847. // Continue reading from current position (after timesRep)
  848. // The second _RecursiveRead reads from CURRENT position, not from the beginning
  849. const afterTimesRep = timesStart + timesRelOffset + 8;
  850. reader.seek( afterTimesRep );
  851. // Read values relative offset
  852. const valuesStart = reader.tell();
  853. const valuesRelOffset = reader.readInt64();
  854. reader.seek( valuesStart + valuesRelOffset );
  855. // Read number of values
  856. const numValues = reader.readUint64();
  857. // Read all ValueReps
  858. const valueReps = [];
  859. for ( let i = 0; i < numValues; i ++ ) {
  860. const repLo = reader.readUint32();
  861. const repHi = reader.readUint32();
  862. valueReps.push( new ValueRep( repLo, repHi ) );
  863. }
  864. // Resolve each value
  865. const values = [];
  866. for ( let i = 0; i < numValues; i ++ ) {
  867. values.push( this._readValue( valueReps[ i ] ) );
  868. }
  869. reader.seek( savedOffset );
  870. // Convert times to array if needed
  871. const timesArray = times instanceof Float64Array ? Array.from( times ) : ( Array.isArray( times ) ? times : [ times ] );
  872. return { times: timesArray, values };
  873. }
  874. _readScalarValue( type ) {
  875. const reader = this.reader;
  876. switch ( type ) {
  877. case TypeEnum.Invalid:
  878. return null;
  879. case TypeEnum.Bool:
  880. return reader.readUint8() !== 0;
  881. case TypeEnum.UChar:
  882. return reader.readUint8();
  883. case TypeEnum.Int:
  884. return reader.readInt32();
  885. case TypeEnum.UInt:
  886. return reader.readUint32();
  887. case TypeEnum.Int64:
  888. return reader.readInt64();
  889. case TypeEnum.UInt64:
  890. return reader.readUint64();
  891. case TypeEnum.Half:
  892. return this._readHalf();
  893. case TypeEnum.Float:
  894. return reader.readFloat32();
  895. case TypeEnum.Double:
  896. return reader.readFloat64();
  897. case TypeEnum.String:
  898. case TypeEnum.Token: {
  899. const index = reader.readUint32();
  900. return this.tokens[ index ] || '';
  901. }
  902. case TypeEnum.AssetPath: {
  903. const index = reader.readUint32();
  904. return this.tokens[ index ] || '';
  905. }
  906. case TypeEnum.Vec2f:
  907. return [ reader.readFloat32(), reader.readFloat32() ];
  908. case TypeEnum.Vec2d:
  909. return [ reader.readFloat64(), reader.readFloat64() ];
  910. case TypeEnum.Vec2i:
  911. return [ reader.readInt32(), reader.readInt32() ];
  912. case TypeEnum.Vec3f:
  913. return [ reader.readFloat32(), reader.readFloat32(), reader.readFloat32() ];
  914. case TypeEnum.Vec3d:
  915. return [ reader.readFloat64(), reader.readFloat64(), reader.readFloat64() ];
  916. case TypeEnum.Vec3i:
  917. return [ reader.readInt32(), reader.readInt32(), reader.readInt32() ];
  918. case TypeEnum.Vec4f:
  919. return [ reader.readFloat32(), reader.readFloat32(), reader.readFloat32(), reader.readFloat32() ];
  920. case TypeEnum.Vec4d:
  921. return [ reader.readFloat64(), reader.readFloat64(), reader.readFloat64(), reader.readFloat64() ];
  922. case TypeEnum.Quatf:
  923. return [ reader.readFloat32(), reader.readFloat32(), reader.readFloat32(), reader.readFloat32() ];
  924. case TypeEnum.Quatd:
  925. return [ reader.readFloat64(), reader.readFloat64(), reader.readFloat64(), reader.readFloat64() ];
  926. case TypeEnum.Matrix4d: {
  927. const m = [];
  928. for ( let i = 0; i < 16; i ++ ) m.push( reader.readFloat64() );
  929. return m;
  930. }
  931. case TypeEnum.TokenVector: {
  932. const count = reader.readUint64();
  933. const tokens = [];
  934. for ( let i = 0; i < count; i ++ ) {
  935. const index = reader.readUint32();
  936. tokens.push( this.tokens[ index ] || '' );
  937. }
  938. return tokens;
  939. }
  940. case TypeEnum.PathVector: {
  941. const count = reader.readUint64();
  942. const paths = [];
  943. for ( let i = 0; i < count; i ++ ) {
  944. const index = reader.readUint32();
  945. paths.push( this.paths[ index ] || '' );
  946. }
  947. return paths;
  948. }
  949. case TypeEnum.DoubleVector: {
  950. // DoubleVector is a count-prefixed array of doubles
  951. const count = reader.readUint64();
  952. const arr = new Float64Array( count );
  953. for ( let i = 0; i < count; i ++ ) arr[ i ] = reader.readFloat64();
  954. return arr;
  955. }
  956. case TypeEnum.Dictionary: {
  957. // Dictionary format:
  958. // u64 elementCount
  959. // For each element: u32 keyIndex + i64 valueOffset (relative)
  960. const elementCount = reader.readUint64();
  961. const dict = {};
  962. for ( let i = 0; i < elementCount; i ++ ) {
  963. const keyIdx = reader.readUint32();
  964. const key = this.tokens[ keyIdx ];
  965. // Value offset is relative to current position
  966. const currentPos = reader.position;
  967. const valueOffset = reader.readInt64();
  968. const valuePos = currentPos + valueOffset;
  969. // Save position, read value, restore position
  970. const savedPos = reader.position;
  971. reader.position = valuePos;
  972. // Read the value representation at the offset
  973. const valueRepData = reader.readUint64();
  974. const valueRep = new ValueRep( valueRepData );
  975. // Read the value based on the representation
  976. let value = null;
  977. if ( valueRep.isInlined ) {
  978. value = this._readInlinedValue( valueRep );
  979. } else if ( valueRep.isArray ) {
  980. reader.position = valueRep.payload;
  981. value = this._readArrayValue( valueRep );
  982. } else {
  983. reader.position = valueRep.payload;
  984. value = this._readScalarValue( valueRep.typeEnum );
  985. }
  986. reader.position = savedPos;
  987. if ( key !== undefined && value !== null ) {
  988. dict[ key ] = value;
  989. }
  990. }
  991. return dict;
  992. }
  993. case TypeEnum.TokenListOp:
  994. case TypeEnum.StringListOp:
  995. case TypeEnum.IntListOp:
  996. case TypeEnum.Int64ListOp:
  997. case TypeEnum.UIntListOp:
  998. case TypeEnum.UInt64ListOp:
  999. // These complex types are not needed for geometry loading
  1000. // Skip them silently
  1001. return null;
  1002. case TypeEnum.PathListOp: {
  1003. // PathListOp format (from AOUSD Core Spec 16.3.10.25):
  1004. // Header byte bitmask:
  1005. // - bit 0 (0x01): Make Explicit (clears list)
  1006. // - bit 1 (0x02): Add Explicit Items
  1007. // - bit 2 (0x04): Add Items
  1008. // - bit 3 (0x08): Delete Items
  1009. // - bit 4 (0x10): Reorder Items
  1010. // - bit 5 (0x20): Prepend Items
  1011. // - bit 6 (0x40): Append Items
  1012. // Arrays follow in order: Explicit, Add, Prepend, Append, Delete, Reorder
  1013. // Each array: uint64 count + count * uint32 path indices
  1014. const flags = reader.readUint8();
  1015. const hasExplicitItems = ( flags & 0x02 ) !== 0;
  1016. const hasAddItems = ( flags & 0x04 ) !== 0;
  1017. const hasDeleteItems = ( flags & 0x08 ) !== 0;
  1018. const hasReorderItems = ( flags & 0x10 ) !== 0;
  1019. const hasPrependItems = ( flags & 0x20 ) !== 0;
  1020. const hasAppendItems = ( flags & 0x40 ) !== 0;
  1021. const readPathList = () => {
  1022. const itemCount = reader.readUint64();
  1023. const paths = [];
  1024. for ( let i = 0; i < itemCount; i ++ ) {
  1025. const pathIdx = reader.readUint32();
  1026. paths.push( this.paths[ pathIdx ] );
  1027. }
  1028. return paths;
  1029. };
  1030. // Read arrays in spec order: Explicit, Add, Prepend, Append, Delete, Reorder
  1031. let explicitPaths = null;
  1032. let addPaths = null;
  1033. let prependPaths = null;
  1034. let appendPaths = null;
  1035. if ( hasExplicitItems ) explicitPaths = readPathList();
  1036. if ( hasAddItems ) addPaths = readPathList();
  1037. if ( hasPrependItems ) prependPaths = readPathList();
  1038. if ( hasAppendItems ) appendPaths = readPathList();
  1039. if ( hasDeleteItems ) readPathList(); // Skip delete items
  1040. if ( hasReorderItems ) readPathList(); // Skip reorder items
  1041. // Return the first non-empty list (connections are typically prepended)
  1042. if ( prependPaths && prependPaths.length > 0 ) return prependPaths;
  1043. if ( explicitPaths && explicitPaths.length > 0 ) return explicitPaths;
  1044. if ( appendPaths && appendPaths.length > 0 ) return appendPaths;
  1045. if ( addPaths && addPaths.length > 0 ) return addPaths;
  1046. return null;
  1047. }
  1048. case TypeEnum.VariantSelectionMap: {
  1049. const elementCount = reader.readUint64();
  1050. const map = {};
  1051. for ( let i = 0; i < elementCount; i ++ ) {
  1052. const keyIdx = reader.readUint32();
  1053. const valueIdx = reader.readUint32();
  1054. const key = this.tokens[ this.strings[ keyIdx ] ];
  1055. const value = this.tokens[ this.strings[ valueIdx ] ];
  1056. if ( key && value ) map[ key ] = value;
  1057. }
  1058. return map;
  1059. }
  1060. default:
  1061. console.warn( 'USDCParser: Unsupported scalar type', type );
  1062. return null;
  1063. }
  1064. }
  1065. _readArrayValue( valueRep ) {
  1066. const reader = this.reader;
  1067. const type = valueRep.typeEnum;
  1068. const isCompressed = valueRep.isCompressed;
  1069. // Read array size
  1070. let size;
  1071. if ( this.version.major === 0 && this.version.minor < 7 ) {
  1072. size = reader.readUint32();
  1073. } else {
  1074. size = reader.readUint64();
  1075. }
  1076. if ( size === 0 ) return [];
  1077. // Handle compressed arrays
  1078. if ( isCompressed ) {
  1079. return this._readCompressedArray( type, size );
  1080. }
  1081. // Read uncompressed array
  1082. switch ( type ) {
  1083. case TypeEnum.Int: {
  1084. const arr = new Int32Array( size );
  1085. for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readInt32();
  1086. return arr;
  1087. }
  1088. case TypeEnum.UInt: {
  1089. const arr = new Uint32Array( size );
  1090. for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readUint32();
  1091. return arr;
  1092. }
  1093. case TypeEnum.Float: {
  1094. const arr = new Float32Array( size );
  1095. for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readFloat32();
  1096. return arr;
  1097. }
  1098. case TypeEnum.Double: {
  1099. const arr = new Float64Array( size );
  1100. for ( let i = 0; i < size; i ++ ) arr[ i ] = reader.readFloat64();
  1101. return arr;
  1102. }
  1103. case TypeEnum.Vec2f: {
  1104. const arr = new Float32Array( size * 2 );
  1105. for ( let i = 0; i < size * 2; i ++ ) arr[ i ] = reader.readFloat32();
  1106. return arr;
  1107. }
  1108. case TypeEnum.Vec3f: {
  1109. const arr = new Float32Array( size * 3 );
  1110. for ( let i = 0; i < size * 3; i ++ ) arr[ i ] = reader.readFloat32();
  1111. return arr;
  1112. }
  1113. case TypeEnum.Vec4f: {
  1114. const arr = new Float32Array( size * 4 );
  1115. for ( let i = 0; i < size * 4; i ++ ) arr[ i ] = reader.readFloat32();
  1116. return arr;
  1117. }
  1118. case TypeEnum.Vec3h: {
  1119. // Half-precision vec3 array (used for scales in skeletal animation)
  1120. const arr = new Float32Array( size * 3 );
  1121. for ( let i = 0; i < size * 3; i ++ ) arr[ i ] = this._readHalf();
  1122. return arr;
  1123. }
  1124. case TypeEnum.Quatf: {
  1125. const arr = new Float32Array( size * 4 );
  1126. for ( let i = 0; i < size * 4; i ++ ) arr[ i ] = reader.readFloat32();
  1127. return arr;
  1128. }
  1129. case TypeEnum.Quath: {
  1130. // Half-precision quaternion array
  1131. const arr = new Float32Array( size * 4 );
  1132. for ( let i = 0; i < size * 4; i ++ ) arr[ i ] = this._readHalf();
  1133. return arr;
  1134. }
  1135. case TypeEnum.Matrix4d: {
  1136. // 4x4 matrix array (16 doubles per matrix, row-major)
  1137. const arr = new Float64Array( size * 16 );
  1138. for ( let i = 0; i < size * 16; i ++ ) arr[ i ] = reader.readFloat64();
  1139. return arr;
  1140. }
  1141. case TypeEnum.Token: {
  1142. const arr = [];
  1143. for ( let i = 0; i < size; i ++ ) {
  1144. const index = reader.readUint32();
  1145. arr.push( this.tokens[ index ] || '' );
  1146. }
  1147. return arr;
  1148. }
  1149. case TypeEnum.Half: {
  1150. const arr = new Float32Array( size );
  1151. for ( let i = 0; i < size; i ++ ) arr[ i ] = this._readHalf();
  1152. return arr;
  1153. }
  1154. default:
  1155. console.warn( 'USDCParser: Unsupported array type', type );
  1156. return [];
  1157. }
  1158. }
  1159. _readCompressedArray( type, size ) {
  1160. const reader = this.reader;
  1161. switch ( type ) {
  1162. case TypeEnum.Int:
  1163. case TypeEnum.UInt: {
  1164. const compressedSize = reader.readUint64();
  1165. const compressed = reader.readBytes( compressedSize );
  1166. return decompressIntegers32(
  1167. compressed.buffer.slice(
  1168. compressed.byteOffset,
  1169. compressed.byteOffset + compressedSize
  1170. ),
  1171. size
  1172. );
  1173. }
  1174. case TypeEnum.Float: {
  1175. // Float compression: 'i' = compressed as ints, 't' = lookup table
  1176. const code = reader.readInt8();
  1177. if ( code === FLOAT_COMPRESSION_INT ) {
  1178. const compressedSize = reader.readUint64();
  1179. const compressed = reader.readBytes( compressedSize );
  1180. const ints = decompressIntegers32(
  1181. compressed.buffer.slice(
  1182. compressed.byteOffset,
  1183. compressed.byteOffset + compressedSize
  1184. ),
  1185. size
  1186. );
  1187. const floats = new Float32Array( size );
  1188. for ( let i = 0; i < size; i ++ ) floats[ i ] = ints[ i ];
  1189. return floats;
  1190. } else if ( code === FLOAT_COMPRESSION_LUT ) {
  1191. const lutSize = reader.readUint32();
  1192. const lut = new Float32Array( lutSize );
  1193. for ( let i = 0; i < lutSize; i ++ ) lut[ i ] = reader.readFloat32();
  1194. const compressedSize = reader.readUint64();
  1195. const compressed = reader.readBytes( compressedSize );
  1196. const indices = decompressIntegers32(
  1197. compressed.buffer.slice(
  1198. compressed.byteOffset,
  1199. compressed.byteOffset + compressedSize
  1200. ),
  1201. size
  1202. );
  1203. const floats = new Float32Array( size );
  1204. for ( let i = 0; i < size; i ++ ) floats[ i ] = lut[ indices[ i ] ];
  1205. return floats;
  1206. }
  1207. console.warn( 'USDCParser: Unknown float compression code', code );
  1208. return new Float32Array( size );
  1209. }
  1210. default:
  1211. console.warn( 'USDCParser: Unsupported compressed array type', type );
  1212. return [];
  1213. }
  1214. }
  1215. _readHalf() {
  1216. return this._halfToFloat( this.reader.readUint16() );
  1217. }
  1218. _halfToFloat( h ) {
  1219. const sign = ( h & 0x8000 ) >> 15;
  1220. const exp = ( h & 0x7C00 ) >> 10;
  1221. const frac = h & 0x03FF;
  1222. if ( exp === 0 ) {
  1223. // Zero or denormalized number
  1224. if ( frac === 0 ) {
  1225. return sign ? - 0 : 0;
  1226. }
  1227. // Denormalized: value = ±2^-14 × (frac/1024)
  1228. return ( sign ? - 1 : 1 ) * HALF_DENORM_SCALE * ( frac / 1024 );
  1229. } else if ( exp === 31 ) {
  1230. return frac ? NaN : ( sign ? - Infinity : Infinity );
  1231. }
  1232. return ( sign ? - 1 : 1 ) * HALF_EXPONENT_TABLE[ exp ] * ( 1 + frac / 1024 );
  1233. }
  1234. _getFieldsForSpec( spec ) {
  1235. const fields = {};
  1236. let fieldSetIndex = spec.fieldSetIndex;
  1237. // Field sets are terminated by FIELD_SET_TERMINATOR
  1238. // Limit iterations to prevent infinite loops from malformed data
  1239. const maxIterations = 10000;
  1240. let iterations = 0;
  1241. while ( fieldSetIndex < this.fieldSets.length && iterations < maxIterations ) {
  1242. const fieldIndex = this.fieldSets[ fieldSetIndex ];
  1243. // Terminator
  1244. if ( fieldIndex === FIELD_SET_TERMINATOR || fieldIndex === - 1 ) break;
  1245. const field = this.fields[ fieldIndex ];
  1246. if ( field ) {
  1247. const name = this.tokens[ field.tokenIndex ];
  1248. const value = this._readValue( field.valueRep );
  1249. fields[ name ] = value;
  1250. }
  1251. fieldSetIndex ++;
  1252. iterations ++;
  1253. }
  1254. return fields;
  1255. }
  1256. }
  1257. export { USDCParser };
粤ICP备19079148号