@loaders.gl/scene 5.0.0-beta.4 → 5.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/DRACO-README.md +1 -1
  2. package/GLTF-README.md +1 -1
  3. package/OBJ-README.md +1 -1
  4. package/README.md +6 -1
  5. package/THIRD-PARTY-NOTICES.md +6 -0
  6. package/dist/dist.dev.js +2958 -750
  7. package/dist/dist.min.js +18 -18
  8. package/dist/draco/draco-javascript-loader-with-parser.d.ts +2 -2
  9. package/dist/draco/draco-loader-types.cjs +3 -3
  10. package/dist/draco/draco-loader-types.cjs.map +2 -2
  11. package/dist/draco/draco-loader-types.d.ts +1 -1
  12. package/dist/draco/draco-loader-types.d.ts.map +1 -1
  13. package/dist/draco/draco-loader-types.js +1 -1
  14. package/dist/draco/draco-loader-types.js.map +1 -1
  15. package/dist/draco/draco-loader.d.ts +2 -2
  16. package/dist/draco/draco-wasm-loader-with-parser.d.ts +2 -2
  17. package/dist/draco/draco3d-loader-with-parser.d.ts +2 -2
  18. package/dist/draco/lib/utils/version.cjs +1 -1
  19. package/dist/draco/lib/utils/version.js +1 -1
  20. package/dist/draco-worker-node.js +400 -113
  21. package/dist/draco-worker-node.js.map +4 -4
  22. package/dist/draco-worker.js +394 -113
  23. package/dist/draco-worker.js.map +4 -4
  24. package/dist/draco-writer-worker-node.cjs +654 -85
  25. package/dist/draco-writer-worker-node.cjs.map +4 -4
  26. package/dist/draco-writer-worker.js +624 -69
  27. package/dist/draco-writer-worker.js.map +4 -4
  28. package/dist/gltf/lib/extensions/KHR_gaussian_splatting.cjs +11 -4
  29. package/dist/gltf/lib/extensions/KHR_gaussian_splatting.cjs.map +2 -2
  30. package/dist/gltf/lib/extensions/KHR_gaussian_splatting.d.ts.map +1 -1
  31. package/dist/gltf/lib/extensions/KHR_gaussian_splatting.js +12 -2
  32. package/dist/gltf/lib/extensions/KHR_gaussian_splatting.js.map +1 -1
  33. package/dist/gltf/lib/parsers/parse-gltf.cjs.map +2 -2
  34. package/dist/gltf/lib/parsers/parse-gltf.d.ts +2 -0
  35. package/dist/gltf/lib/parsers/parse-gltf.d.ts.map +1 -1
  36. package/dist/gltf/lib/parsers/parse-gltf.js.map +1 -1
  37. package/dist/gltf/lib/types/gltf-additional-extension-schemas.cjs +22 -0
  38. package/dist/gltf/lib/types/gltf-additional-extension-schemas.cjs.map +2 -2
  39. package/dist/gltf/lib/types/gltf-additional-extension-schemas.d.ts +23 -0
  40. package/dist/gltf/lib/types/gltf-additional-extension-schemas.d.ts.map +1 -1
  41. package/dist/gltf/lib/types/gltf-additional-extension-schemas.js +22 -0
  42. package/dist/gltf/lib/types/gltf-additional-extension-schemas.js.map +1 -1
  43. package/dist/gltf/lib/types/gltf-extension-schemas.cjs +8 -0
  44. package/dist/gltf/lib/types/gltf-extension-schemas.cjs.map +2 -2
  45. package/dist/gltf/lib/types/gltf-extension-schemas.d.ts +27 -0
  46. package/dist/gltf/lib/types/gltf-extension-schemas.d.ts.map +1 -1
  47. package/dist/gltf/lib/types/gltf-extension-schemas.js +8 -1
  48. package/dist/gltf/lib/types/gltf-extension-schemas.js.map +1 -1
  49. package/dist/gltf/lib/types/gltf-json-schema.cjs.map +1 -1
  50. package/dist/gltf/lib/types/gltf-json-schema.d.ts +2 -2
  51. package/dist/gltf/lib/types/gltf-json-schema.d.ts.map +1 -1
  52. package/dist/gltf/lib/types/gltf-version-zod-schemas.d.ts +34 -0
  53. package/dist/gltf/lib/types/gltf-version-zod-schemas.d.ts.map +1 -1
  54. package/dist/gltf/lib/types/gltf-zod-schema.cjs +1 -1
  55. package/dist/gltf/lib/types/gltf-zod-schema.cjs.map +2 -2
  56. package/dist/gltf/lib/types/gltf-zod-schema.d.ts +51 -0
  57. package/dist/gltf/lib/types/gltf-zod-schema.d.ts.map +1 -1
  58. package/dist/gltf/lib/types/gltf-zod-schema.js +2 -2
  59. package/dist/gltf/lib/types/gltf-zod-schema.js.map +1 -1
  60. package/dist/gltf/lib/utils/version.cjs +1 -1
  61. package/dist/gltf/lib/utils/version.js +1 -1
  62. package/dist/index.cjs +4 -1
  63. package/dist/index.cjs.map +2 -2
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +2 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/mtl-loader-types.cjs +1 -1
  69. package/dist/obj/mtl-loader-types.cjs +1 -1
  70. package/dist/obj/mtl-loader-types.js +1 -1
  71. package/dist/obj/obj-loader-types.cjs +3 -3
  72. package/dist/obj/obj-loader-types.cjs.map +2 -2
  73. package/dist/obj/obj-loader-types.d.ts +1 -1
  74. package/dist/obj/obj-loader-types.d.ts.map +1 -1
  75. package/dist/obj/obj-loader-types.js +2 -2
  76. package/dist/obj/obj-loader-types.js.map +1 -1
  77. package/dist/obj/obj-loader.cjs +5 -5
  78. package/dist/obj/obj-loader.cjs.map +2 -2
  79. package/dist/obj/obj-loader.d.ts +1 -1
  80. package/dist/obj/obj-loader.d.ts.map +1 -1
  81. package/dist/obj/obj-loader.js +1 -1
  82. package/dist/obj/obj-loader.js.map +1 -1
  83. package/dist/obj/obj-writer.cjs +1 -1
  84. package/dist/obj/obj-writer.js +1 -1
  85. package/dist/obj-loader-types.cjs +3 -3
  86. package/dist/obj-loader-types.cjs.map +2 -2
  87. package/dist/obj-loader.cjs +5 -5
  88. package/dist/obj-loader.cjs.map +2 -2
  89. package/dist/obj-worker.js +568 -287
  90. package/dist/obj-writer.cjs +1 -1
  91. package/dist/stl/parse-stl.cjs +100 -0
  92. package/dist/stl/parse-stl.cjs.map +7 -0
  93. package/dist/stl/parse-stl.d.ts +4 -0
  94. package/dist/stl/parse-stl.d.ts.map +1 -0
  95. package/dist/stl/parse-stl.js +84 -0
  96. package/dist/stl/parse-stl.js.map +1 -0
  97. package/dist/stl-loader-types.cjs +58 -0
  98. package/dist/stl-loader-types.cjs.map +7 -0
  99. package/dist/stl-loader-types.d.ts +32 -0
  100. package/dist/stl-loader-types.d.ts.map +1 -0
  101. package/dist/stl-loader-types.js +25 -0
  102. package/dist/stl-loader-types.js.map +1 -0
  103. package/dist/stl-loader.cjs +38 -0
  104. package/dist/stl-loader.cjs.map +7 -0
  105. package/dist/stl-loader.d.ts +17 -0
  106. package/dist/stl-loader.d.ts.map +1 -0
  107. package/dist/stl-loader.js +13 -0
  108. package/dist/stl-loader.js.map +1 -0
  109. package/dist/usd/index.cjs.map +1 -1
  110. package/dist/usd/index.d.ts +1 -1
  111. package/dist/usd/index.d.ts.map +1 -1
  112. package/dist/usd/lib/parse-usd.cjs +42 -5
  113. package/dist/usd/lib/parse-usd.cjs.map +2 -2
  114. package/dist/usd/lib/parse-usd.d.ts.map +1 -1
  115. package/dist/usd/lib/parse-usd.js +53 -6
  116. package/dist/usd/lib/parse-usd.js.map +1 -1
  117. package/dist/usd/lib/parse-usdc.cjs +187 -0
  118. package/dist/usd/lib/parse-usdc.cjs.map +7 -0
  119. package/dist/usd/lib/parse-usdc.d.ts +4 -0
  120. package/dist/usd/lib/parse-usdc.d.ts.map +1 -0
  121. package/dist/usd/lib/parse-usdc.js +176 -0
  122. package/dist/usd/lib/parse-usdc.js.map +1 -0
  123. package/dist/usd/lib/parse-usdz.cjs +22 -0
  124. package/dist/usd/lib/parse-usdz.cjs.map +2 -2
  125. package/dist/usd/lib/parse-usdz.d.ts.map +1 -1
  126. package/dist/usd/lib/parse-usdz.js +26 -0
  127. package/dist/usd/lib/parse-usdz.js.map +1 -1
  128. package/dist/usd/lib/usd-types.cjs.map +1 -1
  129. package/dist/usd/lib/usd-types.d.ts +14 -1
  130. package/dist/usd/lib/usd-types.d.ts.map +1 -1
  131. package/dist/usd/lib/usd-types.js +1 -1
  132. package/dist/usd/lib/usd-types.js.map +1 -1
  133. package/dist/usd/libs/README.md +27 -0
  134. package/dist/usd/libs/THREE-LICENSE +21 -0
  135. package/dist/usd/libs/usdc-parser.d.ts +78 -0
  136. package/dist/usd/libs/usdc-parser.d.ts.map +1 -0
  137. package/dist/usd/libs/usdc-parser.js +1669 -0
  138. package/dist/usd/libs/usdc-parser.js.map +1 -0
  139. package/dist/usd/usd-loader-types.cjs +4 -4
  140. package/dist/usd/usd-loader-types.cjs.map +2 -2
  141. package/dist/usd/usd-loader-types.d.ts +7 -7
  142. package/dist/usd/usd-loader-types.d.ts.map +1 -1
  143. package/dist/usd/usd-loader-types.js +6 -6
  144. package/dist/usd/usd-loader-types.js.map +1 -1
  145. package/dist/usd/usd-loader.cjs.map +1 -1
  146. package/dist/usd/usd-loader.d.ts +4 -4
  147. package/dist/usd/usd-loader.d.ts.map +1 -1
  148. package/dist/usd/usd-loader.js +2 -2
  149. package/dist/usd/usd-loader.js.map +1 -1
  150. package/dist/usd-loader-types.cjs +4 -4
  151. package/dist/usd-loader-types.cjs.map +2 -2
  152. package/dist/usd-loader.cjs.map +1 -1
  153. package/package.json +14 -8
  154. package/src/draco/draco-loader-types.ts +1 -1
  155. package/src/gltf/lib/extensions/KHR_gaussian_splatting.ts +14 -4
  156. package/src/gltf/lib/parsers/parse-gltf.ts +2 -0
  157. package/src/gltf/lib/types/gltf-additional-extension-schemas.ts +32 -0
  158. package/src/gltf/lib/types/gltf-extension-schemas.ts +12 -0
  159. package/src/gltf/lib/types/gltf-json-schema.ts +2 -1
  160. package/src/gltf/lib/types/gltf-zod-schema.ts +2 -1
  161. package/src/index.ts +3 -1
  162. package/src/obj/obj-loader-types.ts +1 -1
  163. package/src/obj/obj-loader.ts +1 -1
  164. package/src/stl/parse-stl.ts +90 -0
  165. package/src/stl-loader-types.ts +28 -0
  166. package/src/stl-loader.ts +12 -0
  167. package/src/usd/index.ts +1 -0
  168. package/src/usd/lib/parse-usd.ts +68 -7
  169. package/src/usd/lib/parse-usdc.ts +188 -0
  170. package/src/usd/lib/parse-usdz.ts +28 -0
  171. package/src/usd/lib/usd-types.ts +16 -2
  172. package/src/usd/libs/README.md +27 -0
  173. package/src/usd/libs/THREE-LICENSE +21 -0
  174. package/src/usd/libs/usdc-parser.d.ts +18 -0
  175. package/src/usd/libs/usdc-parser.js +1669 -0
  176. package/src/usd/usd-loader-types.ts +5 -5
  177. package/src/usd/usd-loader.ts +2 -2
@@ -0,0 +1,1669 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) vis.gl contributors
3
+ // @ts-nocheck -- Vendored r187 decoder; the handwritten adapter provides the checked public boundary.
4
+ // Copyright © 2010-2026 three.js authors. See THREE-LICENSE and README.md.
5
+ // Adapted to fail on unsupported encodings and enforce bounded allocations/offsets.
6
+ let allocationBytes = 0;
7
+ /** Budgets decoded storage without confusing byte/component lengths with logical counts. */
8
+ function checkedLength(length, bytesPerElement = 8) {
9
+ const byteLength = length * bytesPerElement;
10
+ if (
11
+ !Number.isSafeInteger(length) ||
12
+ length < 0 ||
13
+ !Number.isSafeInteger(byteLength) ||
14
+ byteLength > 256 * 1024 * 1024 - allocationBytes
15
+ )
16
+ throw new Error('USDC: decoded allocation limit exceeded');
17
+ allocationBytes += byteLength;
18
+ return length;
19
+ }
20
+ /** Bounds logical collection entries independently of their storage representation. */
21
+ function checkedCount(count) {
22
+ if (!Number.isSafeInteger(count) || count < 0 || count > 4_000_000)
23
+ throw new Error('USDC: decoded allocation limit exceeded');
24
+ return checkedLength(count);
25
+ }
26
+ const textDecoder = new TextDecoder();
27
+
28
+ // Pre-computed half-float exponent lookup table for fast conversion
29
+ // Math.pow(2, exp - 15) for exp = 0..31
30
+ const HALF_EXPONENT_TABLE = new Float32Array(32);
31
+ for (let i = 0; i < 32; i++) {
32
+ HALF_EXPONENT_TABLE[i] = Math.pow(2, i - 15);
33
+ }
34
+
35
+ // Pre-computed constant for denormalized half-floats: 2^-14
36
+ const HALF_DENORM_SCALE = Math.pow(2, -14);
37
+
38
+ // Type enum values from crateDataTypes.h
39
+ const TypeEnum = {
40
+ Invalid: 0,
41
+ Bool: 1,
42
+ UChar: 2,
43
+ Int: 3,
44
+ UInt: 4,
45
+ Int64: 5,
46
+ UInt64: 6,
47
+ Half: 7,
48
+ Float: 8,
49
+ Double: 9,
50
+ String: 10,
51
+ Token: 11,
52
+ AssetPath: 12,
53
+ Matrix2d: 13,
54
+ Matrix3d: 14,
55
+ Matrix4d: 15,
56
+ Quatd: 16,
57
+ Quatf: 17,
58
+ Quath: 18,
59
+ Vec2d: 19,
60
+ Vec2f: 20,
61
+ Vec2h: 21,
62
+ Vec2i: 22,
63
+ Vec3d: 23,
64
+ Vec3f: 24,
65
+ Vec3h: 25,
66
+ Vec3i: 26,
67
+ Vec4d: 27,
68
+ Vec4f: 28,
69
+ Vec4h: 29,
70
+ Vec4i: 30,
71
+ Dictionary: 31,
72
+ TokenListOp: 32,
73
+ StringListOp: 33,
74
+ PathListOp: 34,
75
+ ReferenceListOp: 35,
76
+ IntListOp: 36,
77
+ Int64ListOp: 37,
78
+ UIntListOp: 38,
79
+ UInt64ListOp: 39,
80
+ PathVector: 40,
81
+ TokenVector: 41,
82
+ Specifier: 42,
83
+ Permission: 43,
84
+ Variability: 44,
85
+ VariantSelectionMap: 45,
86
+ TimeSamples: 46,
87
+ Payload: 47,
88
+ DoubleVector: 48,
89
+ LayerOffsetVector: 49,
90
+ StringVector: 50,
91
+ ValueBlock: 51,
92
+ Value: 52,
93
+ UnregisteredValue: 53,
94
+ UnregisteredValueListOp: 54,
95
+ PayloadListOp: 55,
96
+ TimeCode: 56,
97
+ PathExpression: 57,
98
+ Relocates: 58,
99
+ Spline: 59,
100
+ AnimationBlock: 60
101
+ };
102
+
103
+ /** Scalar arrays with an implemented dictionary value representation. */
104
+ const DICTIONARY_ARRAY_TYPES = new Set([
105
+ TypeEnum.Int,
106
+ TypeEnum.UInt,
107
+ TypeEnum.Float,
108
+ TypeEnum.Double,
109
+ TypeEnum.Half,
110
+ TypeEnum.String,
111
+ TypeEnum.Token
112
+ ]);
113
+
114
+ // Field set terminator marker
115
+ const FIELD_SET_TERMINATOR = 0xffffffff;
116
+
117
+ // Float compression type codes
118
+ const FLOAT_COMPRESSION_INT = 0x69; // 'i' - compressed as integers
119
+ const FLOAT_COMPRESSION_LUT = 0x74; // 't' - lookup table
120
+
121
+ // ============================================================================
122
+ // LZ4 Decompression (minimal implementation for USD)
123
+ // Based on LZ4 block format specification
124
+ // ============================================================================
125
+
126
+ function lz4DecompressBlock(input, inputOffset, inputEnd, output, outputOffset, outputEnd) {
127
+ while (inputOffset < inputEnd) {
128
+ // Read token
129
+ const token = input[inputOffset++];
130
+ if (inputOffset > inputEnd) throw new Error('USDC: truncated LZ4 token');
131
+
132
+ // Literal length
133
+ let literalLength = token >> 4;
134
+ if (literalLength === 15) {
135
+ let b;
136
+ do {
137
+ if (inputOffset >= inputEnd) throw new Error('USDC: truncated LZ4 length');
138
+ b = input[inputOffset++];
139
+ literalLength += b;
140
+ } while (b === 255);
141
+ }
142
+
143
+ // Copy literals
144
+ if (literalLength > 0) {
145
+ if (inputOffset + literalLength > inputEnd) {
146
+ throw new Error('USDC: truncated LZ4 literal');
147
+ }
148
+
149
+ for (let i = 0; i < literalLength; i++) {
150
+ if (outputOffset >= outputEnd) throw new Error('USDC: LZ4 output overflow');
151
+ output[outputOffset++] = input[inputOffset++];
152
+ }
153
+ }
154
+
155
+ // Check if we're at the end (last sequence has no match)
156
+ if (inputOffset >= inputEnd) break;
157
+
158
+ // Read match offset (little-endian 16-bit)
159
+ if (inputOffset + 2 > inputEnd) throw new Error('USDC: truncated LZ4 offset');
160
+ const matchOffset = input[inputOffset++] | (input[inputOffset++] << 8);
161
+
162
+ if (matchOffset === 0) {
163
+ throw new Error('USDC: invalid LZ4 match offset');
164
+ }
165
+
166
+ // Match length
167
+ let matchLength = (token & 0x0f) + 4;
168
+ if (matchLength === 19) {
169
+ let b;
170
+ do {
171
+ if (inputOffset >= inputEnd) throw new Error('USDC: truncated LZ4 length');
172
+ b = input[inputOffset++];
173
+ matchLength += b;
174
+ } while (b === 255);
175
+ }
176
+
177
+ // Copy match (byte-by-byte to handle overlapping)
178
+ const matchPos = outputOffset - matchOffset;
179
+ if (matchPos < 0) {
180
+ throw new Error('USDC: invalid LZ4 match position');
181
+ }
182
+
183
+ for (let i = 0; i < matchLength; i++) {
184
+ if (outputOffset >= outputEnd) throw new Error('USDC: LZ4 output overflow');
185
+ output[outputOffset++] = output[matchPos + i];
186
+ }
187
+ }
188
+
189
+ return outputOffset;
190
+ }
191
+
192
+ // USD uses TfFastCompression which wraps LZ4 with chunk headers
193
+ function decompressLZ4(input, uncompressedSize, allowShortOutput = false) {
194
+ // TfFastCompression format (used by OpenUSD):
195
+ // Single chunk (byte 0 == 0): [0] + LZ4 data
196
+ // Multi chunk (byte 0 > 0): [numChunks] + [compressedSizes...] + [chunkData...]
197
+
198
+ const output = new Uint8Array(checkedLength(uncompressedSize, 1));
199
+ if (!input.length) throw new Error('USDC: missing LZ4 chunk header');
200
+ const numChunks = input[0];
201
+
202
+ if (numChunks === 0) {
203
+ // Single chunk - all remaining bytes are LZ4 compressed
204
+ const written = lz4DecompressBlock(input, 1, input.length, output, 0, uncompressedSize);
205
+ if (!allowShortOutput && written !== uncompressedSize)
206
+ throw new Error('USDC: incomplete LZ4 output');
207
+ return output.subarray(0, written);
208
+ } else {
209
+ // Multiple chunks - each chunk decompresses to max 65536 bytes
210
+ const CHUNK_SIZE = 65536;
211
+
212
+ // First, read all chunk sizes
213
+ if (1 + numChunks * 4 > input.length) throw new Error('USDC: truncated LZ4 chunk sizes');
214
+ let headerOffset = 1;
215
+ const compressedSizes = [];
216
+
217
+ for (let i = 0; i < numChunks; i++) {
218
+ const size =
219
+ (input[headerOffset] |
220
+ (input[headerOffset + 1] << 8) |
221
+ (input[headerOffset + 2] << 16) |
222
+ (input[headerOffset + 3] << 24)) >>>
223
+ 0;
224
+ compressedSizes.push(size);
225
+ headerOffset += 4;
226
+ }
227
+
228
+ // Decompress each chunk
229
+ let inputOffset = headerOffset;
230
+ let outputOffset = 0;
231
+
232
+ for (let i = 0; i < numChunks; i++) {
233
+ const chunkCompressedSize = compressedSizes[i];
234
+ const chunkOutputSize = Math.min(CHUNK_SIZE, uncompressedSize - outputOffset);
235
+
236
+ if (chunkCompressedSize > input.length - inputOffset)
237
+ throw new Error('USDC: truncated LZ4 chunk');
238
+ const written = lz4DecompressBlock(
239
+ input,
240
+ inputOffset,
241
+ inputOffset + chunkCompressedSize,
242
+ output,
243
+ outputOffset,
244
+ outputOffset + chunkOutputSize
245
+ );
246
+
247
+ inputOffset += chunkCompressedSize;
248
+ if (i < numChunks - 1 && written !== outputOffset + chunkOutputSize)
249
+ throw new Error('USDC: incomplete LZ4 chunk');
250
+ outputOffset = written;
251
+ }
252
+
253
+ if (inputOffset !== input.length || (!allowShortOutput && outputOffset !== uncompressedSize))
254
+ throw new Error('USDC: incomplete LZ4 output');
255
+ return output.subarray(0, outputOffset);
256
+ }
257
+ }
258
+
259
+ // ============================================================================
260
+ // Integer Decompression (USD-specific delta + variable-width encoding)
261
+ // ============================================================================
262
+
263
+ function decompressIntegers32(compressedData, numInts) {
264
+ // First decompress with LZ4
265
+ const encodedSize = numInts * 4 + ((numInts * 2 + 7) >> 3) + 4;
266
+ const encoded = decompressLZ4(new Uint8Array(compressedData), encodedSize, true);
267
+
268
+ // Then decode
269
+ return decodeIntegers32(encoded, numInts);
270
+ }
271
+
272
+ function decodeIntegers32(data, numInts) {
273
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
274
+ let offset = 0;
275
+
276
+ // Read common value (signed 32-bit)
277
+ const commonValue = view.getInt32(offset, true);
278
+ offset += 4;
279
+
280
+ const numCodesBytes = (numInts * 2 + 7) >> 3;
281
+ if (data.length < offset + numCodesBytes) throw new Error('USDC: truncated integer codes');
282
+ const codesStart = offset;
283
+ const vintsStart = offset + numCodesBytes;
284
+
285
+ const result = new Int32Array(checkedLength(numInts));
286
+ let prevVal = 0;
287
+ let codesOffset = codesStart;
288
+ let vintsOffset = vintsStart;
289
+
290
+ for (let i = 0; i < numInts; ) {
291
+ const codeByte = data[codesOffset++];
292
+
293
+ for (let j = 0; j < 4 && i < numInts; j++, i++) {
294
+ const code = (codeByte >> (j * 2)) & 3;
295
+ let delta = 0;
296
+
297
+ switch (code) {
298
+ case 0: // Common value
299
+ delta = commonValue;
300
+ break;
301
+ case 1: // 8-bit signed
302
+ delta = view.getInt8(vintsOffset);
303
+ vintsOffset += 1;
304
+ break;
305
+ case 2: // 16-bit signed
306
+ delta = view.getInt16(vintsOffset, true);
307
+ vintsOffset += 2;
308
+ break;
309
+ case 3: // 32-bit signed
310
+ delta = view.getInt32(vintsOffset, true);
311
+ vintsOffset += 4;
312
+ break;
313
+ }
314
+
315
+ prevVal += delta;
316
+ result[i] = prevVal;
317
+ }
318
+ }
319
+
320
+ return result;
321
+ }
322
+
323
+ // ============================================================================
324
+ // Binary Reader Helper
325
+ // ============================================================================
326
+
327
+ class BinaryReader {
328
+ constructor(buffer) {
329
+ this.buffer = buffer;
330
+ this.view = new DataView(buffer);
331
+ this.offset = 0;
332
+ }
333
+
334
+ seek(offset) {
335
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset > this.buffer.byteLength)
336
+ throw new Error('USDC: invalid seek');
337
+ this.offset = offset;
338
+ }
339
+
340
+ tell() {
341
+ return this.offset;
342
+ }
343
+
344
+ readUint8() {
345
+ const value = this.view.getUint8(this.offset);
346
+ this.offset += 1;
347
+ return value;
348
+ }
349
+
350
+ readInt8() {
351
+ const value = this.view.getInt8(this.offset);
352
+ this.offset += 1;
353
+ return value;
354
+ }
355
+
356
+ readUint16() {
357
+ const value = this.view.getUint16(this.offset, true);
358
+ this.offset += 2;
359
+ return value;
360
+ }
361
+
362
+ readInt16() {
363
+ const value = this.view.getInt16(this.offset, true);
364
+ this.offset += 2;
365
+ return value;
366
+ }
367
+
368
+ readUint32() {
369
+ const value = this.view.getUint32(this.offset, true);
370
+ this.offset += 4;
371
+ return value;
372
+ }
373
+
374
+ readInt32() {
375
+ const value = this.view.getInt32(this.offset, true);
376
+ this.offset += 4;
377
+ return value;
378
+ }
379
+
380
+ readUint64() {
381
+ const lo = this.view.getUint32(this.offset, true);
382
+ const hi = this.view.getUint32(this.offset + 4, true);
383
+ this.offset += 8;
384
+ // For values that fit in Number, this is safe
385
+ const result = hi * 0x100000000 + lo;
386
+ if (!Number.isSafeInteger(result)) throw new Error('USDC: integer exceeds safe precision');
387
+ return result;
388
+ }
389
+
390
+ readInt64() {
391
+ const lo = this.view.getUint32(this.offset, true);
392
+ const hi = this.view.getInt32(this.offset + 4, true);
393
+ this.offset += 8;
394
+ const result = hi * 0x100000000 + lo;
395
+ if (!Number.isSafeInteger(result)) throw new Error('USDC: integer exceeds safe precision');
396
+ return result;
397
+ }
398
+
399
+ readFloat32() {
400
+ const value = this.view.getFloat32(this.offset, true);
401
+ this.offset += 4;
402
+ return value;
403
+ }
404
+
405
+ readFloat64() {
406
+ const value = this.view.getFloat64(this.offset, true);
407
+ this.offset += 8;
408
+ return value;
409
+ }
410
+
411
+ readBytes(length) {
412
+ const bytes = new Uint8Array(this.buffer, this.offset, length);
413
+ this.offset += length;
414
+ return bytes;
415
+ }
416
+
417
+ readString(length) {
418
+ const bytes = this.readBytes(length);
419
+ let end = 0;
420
+ while (end < length && bytes[end] !== 0) end++;
421
+ return textDecoder.decode(bytes.subarray(0, end));
422
+ }
423
+ }
424
+
425
+ // ============================================================================
426
+ // ValueRep - 64-bit packed value representation
427
+ // ============================================================================
428
+
429
+ class ValueRep {
430
+ constructor(lo, hi) {
431
+ this.lo = lo; // Lower 32 bits
432
+ this.hi = hi; // Upper 32 bits
433
+ }
434
+
435
+ get isArray() {
436
+ return (this.hi & 0x80000000) !== 0;
437
+ }
438
+
439
+ get isInlined() {
440
+ return (this.hi & 0x40000000) !== 0;
441
+ }
442
+
443
+ get isCompressed() {
444
+ return (this.hi & 0x20000000) !== 0;
445
+ }
446
+
447
+ get typeEnum() {
448
+ return (this.hi >> 16) & 0xff;
449
+ }
450
+
451
+ get payload() {
452
+ // 48-bit payload: lo (32 bits) + hi lower 16 bits
453
+ // Note: JavaScript numbers are IEEE 754 doubles with 53 bits of integer precision,
454
+ // so 48-bit values are represented exactly without loss of precision.
455
+ return this.lo + (this.hi & 0xffff) * 0x100000000;
456
+ }
457
+
458
+ getInlinedValue() {
459
+ // For inlined scalars, the value is in the lower 32 bits
460
+ return this.lo;
461
+ }
462
+ }
463
+
464
+ // ============================================================================
465
+ // USDC Parser
466
+ // ============================================================================
467
+
468
+ class USDCParser {
469
+ /**
470
+ * Parse USDC file and return raw spec data without building Three.js scene.
471
+ * Used by USDComposer for unified scene composition.
472
+ */
473
+ parseData(buffer) {
474
+ allocationBytes = 0;
475
+ this._valueReadCount = 0;
476
+ this._activeDictionaries = new Set();
477
+
478
+ this.buffer = buffer instanceof ArrayBuffer ? buffer : buffer.buffer;
479
+ this.reader = new BinaryReader(this.buffer);
480
+ this.version = {major: 0, minor: 0, patch: 0};
481
+
482
+ this._conversionBuffer = new ArrayBuffer(4);
483
+ this._conversionView = new DataView(this._conversionBuffer);
484
+
485
+ this._readBootstrap();
486
+ this._readTOC();
487
+ this._readTokens();
488
+ this._readStrings();
489
+ this._readFields();
490
+ this._readFieldSets();
491
+ this._readPaths();
492
+ this._readSpecs();
493
+
494
+ // Build specsByPath without building scene
495
+ this.specsByPath = Object.create(null);
496
+
497
+ for (const spec of this.specs) {
498
+ const path = this.paths[spec.pathIndex];
499
+ if (!path) continue;
500
+
501
+ const fields = this._getFieldsForSpec(spec);
502
+ this.specsByPath[path] = {specType: spec.specType, fields};
503
+ }
504
+
505
+ return {specsByPath: this.specsByPath};
506
+ }
507
+
508
+ _readBootstrap() {
509
+ const reader = this.reader;
510
+ reader.seek(0);
511
+
512
+ // Read magic "PXR-USDC"
513
+ const magic = reader.readString(8);
514
+ if (magic !== 'PXR-USDC') {
515
+ throw new Error('THREE.USDCParser: Not a valid USDC file.');
516
+ }
517
+
518
+ // Read version
519
+ this.version.major = reader.readUint8();
520
+ this.version.minor = reader.readUint8();
521
+ this.version.patch = reader.readUint8();
522
+ reader.readBytes(5); // Skip remaining version bytes
523
+
524
+ // Read TOC offset
525
+ this.tocOffset = reader.readUint64();
526
+
527
+ // Skip reserved bytes (rest of 128-byte header)
528
+ // Already at offset 24, skip to end of bootstrap (88 bytes total for bootstrap struct)
529
+ }
530
+
531
+ _readTOC() {
532
+ const reader = this.reader;
533
+ reader.seek(this.tocOffset);
534
+
535
+ // Read number of sections
536
+ const numSections = reader.readUint64();
537
+ if (numSections > 16) throw new Error('USDC: invalid section count');
538
+ this.sections = Object.create(null);
539
+
540
+ for (let i = 0; i < numSections; i++) {
541
+ const name = reader.readString(16);
542
+ const start = reader.readUint64();
543
+ const size = reader.readUint64();
544
+
545
+ this.sections[name] = {start, size};
546
+ }
547
+ }
548
+
549
+ _readTokens() {
550
+ const section = this.sections['TOKENS'];
551
+ if (!section) return;
552
+
553
+ const reader = this.reader;
554
+ reader.seek(section.start);
555
+
556
+ const numTokens = checkedCount(reader.readUint64());
557
+ this.tokens = [];
558
+
559
+ if (this.version.major === 0 && this.version.minor < 4) {
560
+ // Uncompressed tokens (version < 0.4.0)
561
+ const tokensNumBytes = reader.readUint64();
562
+ const tokensData = reader.readBytes(tokensNumBytes);
563
+
564
+ let strStart = 0;
565
+ for (let i = 0; i < numTokens; i++) {
566
+ let strEnd = strStart;
567
+ while (strEnd < tokensData.length && tokensData[strEnd] !== 0) strEnd++;
568
+
569
+ if (strEnd >= tokensData.length) throw new Error('USDC: unterminated token data');
570
+ this.tokens.push(textDecoder.decode(tokensData.subarray(strStart, strEnd)));
571
+ strStart = strEnd + 1;
572
+ }
573
+ } else {
574
+ // Compressed tokens (version >= 0.4.0)
575
+ const uncompressedSize = reader.readUint64();
576
+ const compressedSize = reader.readUint64();
577
+ const compressedData = reader.readBytes(compressedSize);
578
+
579
+ const tokensData = decompressLZ4(compressedData, uncompressedSize);
580
+ checkedLength(tokensData.length, 2);
581
+
582
+ let strStart = 0;
583
+ for (let i = 0; i < numTokens; i++) {
584
+ let strEnd = strStart;
585
+ while (strEnd < tokensData.length && tokensData[strEnd] !== 0) strEnd++;
586
+
587
+ if (strEnd >= tokensData.length) throw new Error('USDC: unterminated token data');
588
+ this.tokens.push(textDecoder.decode(tokensData.subarray(strStart, strEnd)));
589
+ strStart = strEnd + 1;
590
+ }
591
+ }
592
+ }
593
+
594
+ _readStrings() {
595
+ const section = this.sections['STRINGS'];
596
+ if (!section) {
597
+ this.strings = [];
598
+ return;
599
+ }
600
+
601
+ const reader = this.reader;
602
+ reader.seek(section.start);
603
+
604
+ const numStrings = checkedCount(reader.readUint64());
605
+ this.strings = [];
606
+
607
+ for (let i = 0; i < numStrings; i++) {
608
+ this.strings.push(reader.readUint32());
609
+ }
610
+ }
611
+
612
+ _readFields() {
613
+ const section = this.sections['FIELDS'];
614
+ if (!section) return;
615
+
616
+ const reader = this.reader;
617
+ reader.seek(section.start);
618
+
619
+ this.fields = [];
620
+
621
+ if (this.version.major === 0 && this.version.minor < 4) {
622
+ // Uncompressed fields
623
+ const numFields = Math.floor(section.size / 12); // 4 bytes token index + 8 bytes value rep
624
+
625
+ for (let i = 0; i < numFields; i++) {
626
+ const tokenIndex = reader.readUint32();
627
+ const repLo = reader.readUint32();
628
+ const repHi = reader.readUint32();
629
+
630
+ this.fields.push({
631
+ tokenIndex,
632
+ valueRep: new ValueRep(repLo, repHi)
633
+ });
634
+ }
635
+ } else {
636
+ // Compressed fields (version >= 0.4.0)
637
+ const numFields = checkedCount(reader.readUint64());
638
+
639
+ // Read compressed token indices
640
+ const tokenIndicesCompressedSize = reader.readUint64();
641
+ const tokenIndicesCompressed = reader.readBytes(tokenIndicesCompressedSize);
642
+ const tokenIndices = decompressIntegers32(
643
+ tokenIndicesCompressed.buffer.slice(
644
+ tokenIndicesCompressed.byteOffset,
645
+ tokenIndicesCompressed.byteOffset + tokenIndicesCompressedSize
646
+ ),
647
+ numFields
648
+ );
649
+
650
+ // Read compressed value reps (LZ4 only, no integer encoding)
651
+ const repsCompressedSize = reader.readUint64();
652
+ const repsCompressed = reader.readBytes(repsCompressedSize);
653
+ const repsData = decompressLZ4(repsCompressed, numFields * 8);
654
+ const repsView = new DataView(repsData.buffer, repsData.byteOffset, repsData.byteLength);
655
+
656
+ for (let i = 0; i < numFields; i++) {
657
+ const repLo = repsView.getUint32(i * 8, true);
658
+ const repHi = repsView.getUint32(i * 8 + 4, true);
659
+
660
+ this.fields.push({
661
+ tokenIndex: tokenIndices[i],
662
+ valueRep: new ValueRep(repLo, repHi)
663
+ });
664
+ }
665
+ }
666
+ }
667
+
668
+ _readFieldSets() {
669
+ const section = this.sections['FIELDSETS'];
670
+ if (!section) return;
671
+
672
+ const reader = this.reader;
673
+ reader.seek(section.start);
674
+
675
+ this.fieldSets = [];
676
+
677
+ if (this.version.major === 0 && this.version.minor < 4) {
678
+ // Uncompressed field sets
679
+ const numFieldSets = Math.floor(section.size / 4);
680
+
681
+ for (let i = 0; i < numFieldSets; i++) {
682
+ this.fieldSets.push(reader.readUint32());
683
+ }
684
+ } else {
685
+ // Compressed field sets
686
+ const numFieldSets = checkedCount(reader.readUint64());
687
+ const compressedSize = reader.readUint64();
688
+ const compressed = reader.readBytes(compressedSize);
689
+
690
+ const indices = decompressIntegers32(
691
+ compressed.buffer.slice(compressed.byteOffset, compressed.byteOffset + compressedSize),
692
+ numFieldSets
693
+ );
694
+
695
+ for (let i = 0; i < numFieldSets; i++) {
696
+ this.fieldSets.push(indices[i]);
697
+ }
698
+ }
699
+ }
700
+
701
+ _readPaths() {
702
+ const section = this.sections['PATHS'];
703
+ if (!section) return;
704
+
705
+ const reader = this.reader;
706
+ reader.seek(section.start);
707
+
708
+ const numPaths = checkedCount(reader.readUint64());
709
+ this.paths = new Array(checkedLength(numPaths)).fill('');
710
+
711
+ if (this.version.major === 0 && this.version.minor < 4) {
712
+ // Uncompressed paths - recursive tree structure
713
+ this._readPathsRecursive('');
714
+ } else {
715
+ // Compressed paths (version >= 0.4.0)
716
+ // Note: numPaths is stored twice - once for array sizing, once in compressed paths section
717
+ reader.readUint64(); // Read duplicate numPaths value (matches numPaths above)
718
+
719
+ const compressedSize1 = reader.readUint64();
720
+ const pathIndicesCompressed = reader.readBytes(compressedSize1);
721
+ const pathIndices = decompressIntegers32(
722
+ pathIndicesCompressed.buffer.slice(
723
+ pathIndicesCompressed.byteOffset,
724
+ pathIndicesCompressed.byteOffset + compressedSize1
725
+ ),
726
+ numPaths
727
+ );
728
+
729
+ const compressedSize2 = reader.readUint64();
730
+ const elementTokenIndicesCompressed = reader.readBytes(compressedSize2);
731
+ const elementTokenIndices = decompressIntegers32(
732
+ elementTokenIndicesCompressed.buffer.slice(
733
+ elementTokenIndicesCompressed.byteOffset,
734
+ elementTokenIndicesCompressed.byteOffset + compressedSize2
735
+ ),
736
+ numPaths
737
+ );
738
+
739
+ const compressedSize3 = reader.readUint64();
740
+ const jumpsCompressed = reader.readBytes(compressedSize3);
741
+ const jumps = decompressIntegers32(
742
+ jumpsCompressed.buffer.slice(
743
+ jumpsCompressed.byteOffset,
744
+ jumpsCompressed.byteOffset + compressedSize3
745
+ ),
746
+ numPaths
747
+ );
748
+
749
+ // Build paths from compressed data
750
+ this._buildPathsFromCompressed(pathIndices, elementTokenIndices, jumps);
751
+ }
752
+ }
753
+
754
+ _readPathsRecursive(parentPath, depth = 0) {
755
+ const reader = this.reader;
756
+
757
+ // Prevent infinite recursion
758
+ if (depth > 1000) return;
759
+
760
+ // Read path item header
761
+ const index = reader.readUint32();
762
+ const elementTokenIndex = reader.readUint32();
763
+ const bits = reader.readUint8();
764
+
765
+ const hasChild = (bits & 1) !== 0;
766
+ const hasSibling = (bits & 2) !== 0;
767
+ const isPrimProperty = (bits & 4) !== 0;
768
+
769
+ // Build path
770
+ let path;
771
+ if (parentPath === '') {
772
+ path = '/';
773
+ } else {
774
+ const elemToken = this.tokens[elementTokenIndex] || '';
775
+ if (isPrimProperty) {
776
+ path = parentPath + '.' + elemToken;
777
+ } else {
778
+ path = parentPath === '/' ? '/' + elemToken : parentPath + '/' + elemToken;
779
+ }
780
+ }
781
+
782
+ checkedLength(path.length, 2);
783
+ this.paths[index] = path;
784
+
785
+ // Process children and siblings
786
+ if (hasChild && hasSibling) {
787
+ // Read sibling offset
788
+ const siblingOffset = reader.readUint64();
789
+
790
+ // Read child
791
+ this._readPathsRecursive(path, depth + 1);
792
+
793
+ // Read sibling
794
+ reader.seek(siblingOffset);
795
+ this._readPathsRecursive(parentPath, depth + 1);
796
+ } else if (hasChild) {
797
+ this._readPathsRecursive(path, depth + 1);
798
+ } else if (hasSibling) {
799
+ this._readPathsRecursive(parentPath, depth + 1);
800
+ }
801
+ }
802
+
803
+ /** Rebuilds compressed paths with an explicit stack instead of sibling recursion. */
804
+ _buildPathsFromCompressed(pathIndices, elementTokenIndices, jumps) {
805
+ let traversalCount = 0;
806
+ const pendingPaths = [{startIndex: 0, parentPath: ''}];
807
+ while (pendingPaths.length) {
808
+ const pendingPath = pendingPaths.pop();
809
+ let currentIndex = pendingPath.startIndex;
810
+ let parentPath = pendingPath.parentPath;
811
+ while (currentIndex < pathIndices.length) {
812
+ if (++traversalCount > pathIndices.length * 2)
813
+ throw new Error('USDC: path traversal limit exceeded');
814
+ const nodeIndex = currentIndex++;
815
+ const pathIndex = pathIndices[nodeIndex];
816
+ const elementTokenIndex = elementTokenIndices[nodeIndex];
817
+ const jump = jumps[nodeIndex];
818
+ if (
819
+ pathIndex < 0 ||
820
+ pathIndex >= this.paths.length ||
821
+ Math.abs(elementTokenIndex) >= this.tokens.length ||
822
+ jump < -2
823
+ )
824
+ throw new Error('USDC: invalid compressed path index');
825
+ let path;
826
+ if (parentPath === '') {
827
+ path = '/';
828
+ parentPath = path;
829
+ } else {
830
+ const elementToken = this.tokens[Math.abs(elementTokenIndex)];
831
+ path =
832
+ elementTokenIndex < 0
833
+ ? parentPath + '.' + elementToken
834
+ : parentPath === '/'
835
+ ? '/' + elementToken
836
+ : parentPath + '/' + elementToken;
837
+ }
838
+ checkedLength(path.length, 2);
839
+ this.paths[pathIndex] = path;
840
+ const hasChild = jump > 0 || jump === -1;
841
+ const hasSibling = jump >= 0;
842
+ if ((hasChild || hasSibling) && currentIndex >= pathIndices.length)
843
+ throw new Error('USDC: missing compressed path entry');
844
+ if (hasChild && hasSibling) {
845
+ const siblingIndex = nodeIndex + jump;
846
+ if (siblingIndex <= currentIndex || siblingIndex >= pathIndices.length)
847
+ throw new Error('USDC: invalid compressed path jump');
848
+ pendingPaths.push({startIndex: siblingIndex, parentPath});
849
+ }
850
+ if (hasChild) parentPath = path;
851
+ else if (!hasSibling) break;
852
+ }
853
+ }
854
+ }
855
+
856
+ _readSpecs() {
857
+ const section = this.sections['SPECS'];
858
+ if (!section) return;
859
+
860
+ const reader = this.reader;
861
+ reader.seek(section.start);
862
+
863
+ this.specs = [];
864
+
865
+ if (this.version.major === 0 && this.version.minor < 4) {
866
+ // Uncompressed specs
867
+ // Each spec: pathIndex (4), fieldSetIndex (4), specType (4) = 12 bytes
868
+ // For version 0.0.1 there may be different padding
869
+ const specSize = this.version.minor === 0 && this.version.patch === 1 ? 16 : 12;
870
+ const numSpecs = Math.floor(section.size / specSize);
871
+
872
+ for (let i = 0; i < numSpecs; i++) {
873
+ const pathIndex = reader.readUint32();
874
+ const fieldSetIndex = reader.readUint32();
875
+ const specType = reader.readUint32();
876
+
877
+ if (specSize === 16) reader.readUint32(); // padding
878
+
879
+ this.specs.push({pathIndex, fieldSetIndex, specType});
880
+ }
881
+ } else {
882
+ // Compressed specs
883
+ const numSpecs = checkedCount(reader.readUint64());
884
+
885
+ const compressedSize1 = reader.readUint64();
886
+ const pathIndicesCompressed = reader.readBytes(compressedSize1);
887
+ const pathIndices = decompressIntegers32(
888
+ pathIndicesCompressed.buffer.slice(
889
+ pathIndicesCompressed.byteOffset,
890
+ pathIndicesCompressed.byteOffset + compressedSize1
891
+ ),
892
+ numSpecs
893
+ );
894
+
895
+ const compressedSize2 = reader.readUint64();
896
+ const fieldSetIndicesCompressed = reader.readBytes(compressedSize2);
897
+ const fieldSetIndices = decompressIntegers32(
898
+ fieldSetIndicesCompressed.buffer.slice(
899
+ fieldSetIndicesCompressed.byteOffset,
900
+ fieldSetIndicesCompressed.byteOffset + compressedSize2
901
+ ),
902
+ numSpecs
903
+ );
904
+
905
+ const compressedSize3 = reader.readUint64();
906
+ const specTypesCompressed = reader.readBytes(compressedSize3);
907
+ const specTypes = decompressIntegers32(
908
+ specTypesCompressed.buffer.slice(
909
+ specTypesCompressed.byteOffset,
910
+ specTypesCompressed.byteOffset + compressedSize3
911
+ ),
912
+ numSpecs
913
+ );
914
+
915
+ for (let i = 0; i < numSpecs; i++) {
916
+ this.specs.push({
917
+ pathIndex: pathIndices[i],
918
+ fieldSetIndex: fieldSetIndices[i],
919
+ specType: specTypes[i]
920
+ });
921
+ }
922
+ }
923
+ }
924
+
925
+ // ========================================================================
926
+ // Value Reading
927
+ // ========================================================================
928
+
929
+ _readValue(valueRep) {
930
+ this._valueReadCount = (this._valueReadCount || 0) + 1;
931
+ if (this._valueReadCount > 1_000_000) throw new Error('USDC: value traversal limit exceeded');
932
+
933
+ const type = valueRep.typeEnum;
934
+ const isArray = valueRep.isArray;
935
+ const isInlined = valueRep.isInlined;
936
+
937
+ // Handle TimeSamples specially - they have their own format
938
+ if (type === TypeEnum.TimeSamples) {
939
+ return this._readTimeSamples(valueRep);
940
+ }
941
+
942
+ if (isInlined) {
943
+ return this._readInlinedValue(valueRep);
944
+ }
945
+
946
+ // Seek to payload offset and read value
947
+ const offset = valueRep.payload;
948
+ if (offset === 0 && isArray) {
949
+ // Spec 16.3.9.3: Array payload 0 is an explicit empty-array sentinel.
950
+ return [];
951
+ }
952
+
953
+ if (offset < 0 || offset >= this.buffer.byteLength) {
954
+ throw new RangeError(
955
+ 'USDCParser: Invalid payload offset ' + offset + ' for type ' + type + '.'
956
+ );
957
+ }
958
+
959
+ const savedOffset = this.reader.tell();
960
+ this.reader.seek(offset);
961
+
962
+ let value;
963
+
964
+ if (isArray) {
965
+ value = this._readArrayValue(valueRep);
966
+ } else {
967
+ value = this._readScalarValue(type);
968
+ }
969
+
970
+ this.reader.seek(savedOffset);
971
+ return value;
972
+ }
973
+
974
+ _readInlinedValue(valueRep) {
975
+ const type = valueRep.typeEnum;
976
+ const payload = valueRep.getInlinedValue();
977
+ const view = this._conversionView;
978
+
979
+ switch (type) {
980
+ case TypeEnum.Dictionary:
981
+ if (payload !== 0) throw new Error('USDC: invalid inline dictionary');
982
+ return Object.create(null);
983
+ case TypeEnum.Bool:
984
+ return payload !== 0;
985
+ case TypeEnum.UChar:
986
+ return payload & 0xff;
987
+ case TypeEnum.Int:
988
+ case TypeEnum.Int64:
989
+ return payload | 0;
990
+ case TypeEnum.UInt:
991
+ case TypeEnum.UInt64:
992
+ return payload >>> 0;
993
+ case TypeEnum.Half:
994
+ return this._halfToFloat(payload & 0xffff);
995
+ case TypeEnum.Float: {
996
+ view.setUint32(0, payload, true);
997
+ return view.getFloat32(0, true);
998
+ }
999
+
1000
+ case TypeEnum.Double: {
1001
+ // When a double is inlined, it's stored as float32 bits in the payload
1002
+ view.setUint32(0, payload, true);
1003
+ return view.getFloat32(0, true);
1004
+ }
1005
+
1006
+ case TypeEnum.Token:
1007
+ return this.tokens[payload] || '';
1008
+ case TypeEnum.String:
1009
+ return this.tokens[this.strings[payload]] || '';
1010
+ case TypeEnum.AssetPath:
1011
+ return this.tokens[payload] || '';
1012
+ case TypeEnum.Specifier:
1013
+ return payload; // 0=def, 1=over, 2=class
1014
+ case TypeEnum.Permission:
1015
+ case TypeEnum.Variability:
1016
+ return payload;
1017
+
1018
+ // Vec2h: Two half-floats fit in 4 bytes, stored directly
1019
+ case TypeEnum.Vec2h: {
1020
+ view.setUint32(0, payload, true);
1021
+ return [
1022
+ this._halfToFloat(view.getUint16(0, true)),
1023
+ this._halfToFloat(view.getUint16(2, true))
1024
+ ];
1025
+ }
1026
+
1027
+ // Inlined vectors that don't fit in 4 bytes are encoded as signed 8-bit integers
1028
+ // Vec2f = 8 bytes (2x float32), Vec3f = 12 bytes, Vec4f = 16 bytes, etc.
1029
+ case TypeEnum.Vec2f:
1030
+ case TypeEnum.Vec2i: {
1031
+ view.setUint32(0, payload, true);
1032
+ return [view.getInt8(0), view.getInt8(1)];
1033
+ }
1034
+
1035
+ case TypeEnum.Vec3f:
1036
+ case TypeEnum.Vec3i: {
1037
+ view.setUint32(0, payload, true);
1038
+ return [view.getInt8(0), view.getInt8(1), view.getInt8(2)];
1039
+ }
1040
+
1041
+ case TypeEnum.Vec4f:
1042
+ case TypeEnum.Vec4i: {
1043
+ view.setUint32(0, payload, true);
1044
+ return [view.getInt8(0), view.getInt8(1), view.getInt8(2), view.getInt8(3)];
1045
+ }
1046
+
1047
+ case TypeEnum.Matrix2d: {
1048
+ // Inlined Matrix2d stores diagonal values as 2 signed int8 values
1049
+ view.setUint32(0, payload, true);
1050
+ const d0 = view.getInt8(0),
1051
+ d1 = view.getInt8(1);
1052
+ return [d0, 0, 0, d1];
1053
+ }
1054
+
1055
+ case TypeEnum.Matrix3d: {
1056
+ // Inlined Matrix3d stores diagonal values as 3 signed int8 values
1057
+ view.setUint32(0, payload, true);
1058
+ const d0 = view.getInt8(0),
1059
+ d1 = view.getInt8(1),
1060
+ d2 = view.getInt8(2);
1061
+ return [d0, 0, 0, 0, d1, 0, 0, 0, d2];
1062
+ }
1063
+
1064
+ case TypeEnum.Matrix4d: {
1065
+ // Inlined Matrix4d stores diagonal values as 4 signed int8 values
1066
+ view.setUint32(0, payload, true);
1067
+ const d0 = view.getInt8(0),
1068
+ d1 = view.getInt8(1),
1069
+ d2 = view.getInt8(2),
1070
+ d3 = view.getInt8(3);
1071
+ return [d0, 0, 0, 0, 0, d1, 0, 0, 0, 0, d2, 0, 0, 0, 0, d3];
1072
+ }
1073
+
1074
+ default:
1075
+ throw new Error('USDC: unsupported inline value type ' + type);
1076
+ }
1077
+ }
1078
+
1079
+ _readTimeSamples(valueRep) {
1080
+ const reader = this.reader;
1081
+ const offset = valueRep.payload;
1082
+ const savedOffset = reader.tell();
1083
+ reader.seek(offset);
1084
+
1085
+ // TimeSamples format uses RELATIVE offsets (from OpenUSD _RecursiveRead):
1086
+ // _RecursiveRead: read int64 relativeOffset at current position, then seek to start + relativeOffset
1087
+ // After reading timesRep, continue reading from current position (after timesRep)
1088
+ // Layout at TimeSamples location:
1089
+ // - int64 timesOffset (relative from start of this int64)
1090
+ // At (start + timesOffset): timesRep ValueRep, then int64 valuesOffset, then numValues + ValueReps
1091
+
1092
+ // Read times relative offset and resolve
1093
+ const timesStart = reader.tell();
1094
+ const timesRelOffset = reader.readInt64();
1095
+ reader.seek(timesStart + timesRelOffset);
1096
+
1097
+ const timesRepLo = reader.readUint32();
1098
+ const timesRepHi = reader.readUint32();
1099
+ const timesRep = new ValueRep(timesRepLo, timesRepHi);
1100
+
1101
+ // Resolve times array
1102
+ const times = this._readValue(timesRep);
1103
+
1104
+ // Continue reading from current position (after timesRep)
1105
+ // The second _RecursiveRead reads from CURRENT position, not from the beginning
1106
+ const afterTimesRep = timesStart + timesRelOffset + 8;
1107
+ reader.seek(afterTimesRep);
1108
+
1109
+ // Read values relative offset
1110
+ const valuesStart = reader.tell();
1111
+ const valuesRelOffset = reader.readInt64();
1112
+ reader.seek(valuesStart + valuesRelOffset);
1113
+
1114
+ // Read number of values
1115
+ const numValues = checkedCount(reader.readUint64());
1116
+
1117
+ // Read all ValueReps
1118
+ const valueReps = [];
1119
+ for (let i = 0; i < numValues; i++) {
1120
+ const repLo = reader.readUint32();
1121
+ const repHi = reader.readUint32();
1122
+ valueReps.push(new ValueRep(repLo, repHi));
1123
+ }
1124
+
1125
+ // Resolve each value
1126
+ const values = [];
1127
+ for (let i = 0; i < numValues; i++) {
1128
+ values.push(this._readValue(valueReps[i]));
1129
+ }
1130
+
1131
+ reader.seek(savedOffset);
1132
+
1133
+ // Convert times to array if needed
1134
+ const timesArray =
1135
+ times instanceof Float64Array ? Array.from(times) : Array.isArray(times) ? times : [times];
1136
+
1137
+ return {times: timesArray, values};
1138
+ }
1139
+
1140
+ _readScalarValue(type) {
1141
+ const reader = this.reader;
1142
+
1143
+ switch (type) {
1144
+ case TypeEnum.Invalid:
1145
+ return null;
1146
+ case TypeEnum.Bool:
1147
+ return reader.readUint8() !== 0;
1148
+ case TypeEnum.UChar:
1149
+ return reader.readUint8();
1150
+ case TypeEnum.Int:
1151
+ return reader.readInt32();
1152
+ case TypeEnum.UInt:
1153
+ return reader.readUint32();
1154
+ case TypeEnum.Int64:
1155
+ return reader.readInt64();
1156
+ case TypeEnum.UInt64:
1157
+ return reader.readUint64();
1158
+ case TypeEnum.Half:
1159
+ return this._readHalf();
1160
+ case TypeEnum.Float:
1161
+ return reader.readFloat32();
1162
+ case TypeEnum.Double:
1163
+ return reader.readFloat64();
1164
+ case TypeEnum.String:
1165
+ case TypeEnum.Token: {
1166
+ const index = reader.readUint32();
1167
+ return this.tokens[index] || '';
1168
+ }
1169
+
1170
+ case TypeEnum.AssetPath: {
1171
+ const index = reader.readUint32();
1172
+ return this.tokens[index] || '';
1173
+ }
1174
+
1175
+ case TypeEnum.Vec2f:
1176
+ return [reader.readFloat32(), reader.readFloat32()];
1177
+ case TypeEnum.Vec2d:
1178
+ return [reader.readFloat64(), reader.readFloat64()];
1179
+ case TypeEnum.Vec2i:
1180
+ return [reader.readInt32(), reader.readInt32()];
1181
+ case TypeEnum.Vec3f:
1182
+ return [reader.readFloat32(), reader.readFloat32(), reader.readFloat32()];
1183
+ case TypeEnum.Vec3d:
1184
+ return [reader.readFloat64(), reader.readFloat64(), reader.readFloat64()];
1185
+ case TypeEnum.Vec3i:
1186
+ return [reader.readInt32(), reader.readInt32(), reader.readInt32()];
1187
+ case TypeEnum.Vec4f:
1188
+ return [
1189
+ reader.readFloat32(),
1190
+ reader.readFloat32(),
1191
+ reader.readFloat32(),
1192
+ reader.readFloat32()
1193
+ ];
1194
+ case TypeEnum.Vec4d:
1195
+ return [
1196
+ reader.readFloat64(),
1197
+ reader.readFloat64(),
1198
+ reader.readFloat64(),
1199
+ reader.readFloat64()
1200
+ ];
1201
+ case TypeEnum.Quatf:
1202
+ return [
1203
+ reader.readFloat32(),
1204
+ reader.readFloat32(),
1205
+ reader.readFloat32(),
1206
+ reader.readFloat32()
1207
+ ];
1208
+ case TypeEnum.Quatd:
1209
+ return [
1210
+ reader.readFloat64(),
1211
+ reader.readFloat64(),
1212
+ reader.readFloat64(),
1213
+ reader.readFloat64()
1214
+ ];
1215
+ case TypeEnum.Matrix4d: {
1216
+ const m = [];
1217
+ for (let i = 0; i < 16; i++) m.push(reader.readFloat64());
1218
+ return m;
1219
+ }
1220
+
1221
+ case TypeEnum.TokenVector: {
1222
+ const count = checkedCount(reader.readUint64());
1223
+ const tokens = [];
1224
+ for (let i = 0; i < count; i++) {
1225
+ const index = reader.readUint32();
1226
+ tokens.push(this.tokens[index] || '');
1227
+ }
1228
+
1229
+ return tokens;
1230
+ }
1231
+
1232
+ case TypeEnum.PathVector: {
1233
+ const count = checkedCount(reader.readUint64());
1234
+ const paths = [];
1235
+ for (let i = 0; i < count; i++) {
1236
+ const index = reader.readUint32();
1237
+ paths.push(this.paths[index] || '');
1238
+ }
1239
+
1240
+ return paths;
1241
+ }
1242
+
1243
+ case TypeEnum.DoubleVector: {
1244
+ // DoubleVector is a count-prefixed array of doubles
1245
+ const count = checkedCount(reader.readUint64());
1246
+ const arr = new Float64Array(checkedLength(count));
1247
+ for (let i = 0; i < count; i++) arr[i] = reader.readFloat64();
1248
+ return arr;
1249
+ }
1250
+
1251
+ case TypeEnum.Dictionary:
1252
+ return this._readDictionary();
1253
+ case TypeEnum.TokenListOp:
1254
+ case TypeEnum.StringListOp:
1255
+ case TypeEnum.IntListOp:
1256
+ case TypeEnum.Int64ListOp:
1257
+ case TypeEnum.UIntListOp:
1258
+ case TypeEnum.UInt64ListOp:
1259
+ // These complex types are not needed for geometry loading
1260
+ // Composition semantics must not be silently discarded.
1261
+ throw new Error('USDC: value list operations are unsupported');
1262
+
1263
+ case TypeEnum.PathListOp: {
1264
+ // PathListOp format (from AOUSD Core Spec 16.3.10.25):
1265
+ // Header byte bitmask:
1266
+ // - bit 0 (0x01): Make Explicit (clears list)
1267
+ // - bit 1 (0x02): Add Explicit Items
1268
+ // - bit 2 (0x04): Add Items
1269
+ // - bit 3 (0x08): Delete Items
1270
+ // - bit 4 (0x10): Reorder Items
1271
+ // - bit 5 (0x20): Prepend Items
1272
+ // - bit 6 (0x40): Append Items
1273
+ // Arrays follow in order: Explicit, Add, Prepend, Append, Delete, Reorder
1274
+ // Each array: uint64 count + count * uint32 path indices
1275
+ const flags = reader.readUint8();
1276
+ if (flags & 0x18 || (flags & 0x66).toString(2).replaceAll('0', '').length > 1)
1277
+ throw new Error('USDC: compound path list operations are unsupported');
1278
+ const hasExplicitItems = (flags & 0x02) !== 0;
1279
+ const hasAddItems = (flags & 0x04) !== 0;
1280
+ const hasDeleteItems = (flags & 0x08) !== 0;
1281
+ const hasReorderItems = (flags & 0x10) !== 0;
1282
+ const hasPrependItems = (flags & 0x20) !== 0;
1283
+ const hasAppendItems = (flags & 0x40) !== 0;
1284
+
1285
+ const readPathList = () => {
1286
+ const itemCount = checkedCount(reader.readUint64());
1287
+ const paths = [];
1288
+ for (let i = 0; i < itemCount; i++) {
1289
+ const pathIdx = reader.readUint32();
1290
+ paths.push(this.paths[pathIdx]);
1291
+ }
1292
+
1293
+ return paths;
1294
+ };
1295
+
1296
+ // Read arrays in spec order: Explicit, Add, Prepend, Append, Delete, Reorder
1297
+ let explicitPaths = null;
1298
+ let addPaths = null;
1299
+ let prependPaths = null;
1300
+ let appendPaths = null;
1301
+
1302
+ if (hasExplicitItems) explicitPaths = readPathList();
1303
+ if (hasAddItems) addPaths = readPathList();
1304
+ if (hasPrependItems) prependPaths = readPathList();
1305
+ if (hasAppendItems) appendPaths = readPathList();
1306
+ if (hasDeleteItems) readPathList(); // Skip delete items
1307
+ if (hasReorderItems) readPathList(); // Skip reorder items
1308
+
1309
+ // Return the first non-empty list (connections are typically prepended)
1310
+ if (prependPaths && prependPaths.length > 0) return prependPaths;
1311
+ if (explicitPaths && explicitPaths.length > 0) return explicitPaths;
1312
+ if (appendPaths && appendPaths.length > 0) return appendPaths;
1313
+ if (addPaths && addPaths.length > 0) return addPaths;
1314
+
1315
+ return null;
1316
+ }
1317
+
1318
+ case TypeEnum.VariantSelectionMap: {
1319
+ const elementCount = checkedCount(reader.readUint64());
1320
+ const map = Object.create(null);
1321
+
1322
+ for (let i = 0; i < elementCount; i++) {
1323
+ const keyIdx = reader.readUint32();
1324
+ const valueIdx = reader.readUint32();
1325
+ const key = this.tokens[this.strings[keyIdx]];
1326
+ const value = this.tokens[this.strings[valueIdx]];
1327
+ if (key && value) map[key] = value;
1328
+ }
1329
+
1330
+ return map;
1331
+ }
1332
+
1333
+ default:
1334
+ throw new Error(
1335
+ 'USDC: unsupported encoding: ' + ['USDCParser: Unsupported scalar type', type].join(' ')
1336
+ );
1337
+ }
1338
+ }
1339
+
1340
+ /** Reads bounded string-keyed dictionary entries and their recursive value representations. */
1341
+ _readDictionary() {
1342
+ const reader = this.reader;
1343
+ const dictionaryOffset = reader.tell();
1344
+ this._activeDictionaries ||= new Set();
1345
+ if (this._activeDictionaries.has(dictionaryOffset))
1346
+ throw new Error('USDC: cyclic dictionary value');
1347
+ if (this._activeDictionaries.size >= 64)
1348
+ throw new Error('USDC: dictionary depth limit exceeded');
1349
+ this._activeDictionaries.add(dictionaryOffset);
1350
+ try {
1351
+ const count = checkedCount(reader.readUint64());
1352
+ if (count > Math.floor((this.buffer.byteLength - reader.tell()) / 20))
1353
+ throw new Error('USDC: truncated dictionary entries');
1354
+ const dictionary = Object.create(null);
1355
+ for (let index = 0; index < count; index++) {
1356
+ const stringIndex = reader.readUint32();
1357
+ const key = this.tokens[this.strings[stringIndex]];
1358
+ if (typeof key !== 'string' || Object.hasOwn(dictionary, key))
1359
+ throw new Error('USDC: invalid dictionary key');
1360
+ const offsetLocation = reader.tell();
1361
+ const relativeOffset = reader.readInt64();
1362
+ if (relativeOffset < 8) throw new Error('USDC: invalid dictionary value offset');
1363
+ reader.seek(offsetLocation + relativeOffset);
1364
+ const representation = new ValueRep(reader.readUint32(), reader.readUint32());
1365
+ if (
1366
+ representation.typeEnum < TypeEnum.Bool ||
1367
+ (representation.typeEnum > TypeEnum.AssetPath &&
1368
+ representation.typeEnum !== TypeEnum.Dictionary) ||
1369
+ (representation.isArray && !DICTIONARY_ARRAY_TYPES.has(representation.typeEnum))
1370
+ )
1371
+ throw new Error('USDC: unsupported dictionary value type');
1372
+ const nextEntry = reader.tell();
1373
+ const value = this._readValue(representation);
1374
+ dictionary[key] =
1375
+ representation.typeEnum === TypeEnum.AssetPath ? {assetPath: value} : value;
1376
+ reader.seek(nextEntry);
1377
+ }
1378
+ return dictionary;
1379
+ } finally {
1380
+ this._activeDictionaries.delete(dictionaryOffset);
1381
+ }
1382
+ }
1383
+
1384
+ _readArrayValue(valueRep) {
1385
+ const reader = this.reader;
1386
+ const type = valueRep.typeEnum;
1387
+ const isCompressed = valueRep.isCompressed;
1388
+
1389
+ // Read array size
1390
+ let size;
1391
+ if (this.version.major === 0 && this.version.minor < 7) {
1392
+ size = reader.readUint32();
1393
+ } else {
1394
+ size = reader.readUint64();
1395
+ }
1396
+
1397
+ if (!Number.isSafeInteger(size) || size < 0) {
1398
+ throw new RangeError('USDCParser: Invalid array size ' + size + ' for type ' + type + '.');
1399
+ }
1400
+
1401
+ if (size > 0x7fffffff) {
1402
+ // Crate stores counts as uint64, but JS typed arrays cannot represent all such sizes.
1403
+ throw new RangeError('USDCParser: Array size ' + size + ' exceeds implementation limits.');
1404
+ }
1405
+
1406
+ size = checkedCount(size);
1407
+ if (size === 0) return [];
1408
+
1409
+ // Handle compressed arrays
1410
+ if (isCompressed) {
1411
+ return this._readCompressedArray(type, size);
1412
+ }
1413
+
1414
+ // Read uncompressed array
1415
+ switch (type) {
1416
+ case TypeEnum.Int: {
1417
+ const arr = new Int32Array(checkedLength(size));
1418
+ for (let i = 0; i < size; i++) arr[i] = reader.readInt32();
1419
+ return arr;
1420
+ }
1421
+
1422
+ case TypeEnum.UInt: {
1423
+ const arr = new Uint32Array(checkedLength(size));
1424
+ for (let i = 0; i < size; i++) arr[i] = reader.readUint32();
1425
+ return arr;
1426
+ }
1427
+
1428
+ case TypeEnum.Float: {
1429
+ const arr = new Float32Array(checkedLength(size));
1430
+ for (let i = 0; i < size; i++) arr[i] = reader.readFloat32();
1431
+ return arr;
1432
+ }
1433
+
1434
+ case TypeEnum.Double: {
1435
+ const arr = new Float64Array(checkedLength(size));
1436
+ for (let i = 0; i < size; i++) arr[i] = reader.readFloat64();
1437
+ return arr;
1438
+ }
1439
+
1440
+ case TypeEnum.Vec2f: {
1441
+ const arr = new Float32Array(checkedLength(size * 2));
1442
+ for (let i = 0; i < size * 2; i++) arr[i] = reader.readFloat32();
1443
+ return arr;
1444
+ }
1445
+
1446
+ case TypeEnum.Vec3f: {
1447
+ const arr = new Float32Array(checkedLength(size * 3));
1448
+ for (let i = 0; i < size * 3; i++) arr[i] = reader.readFloat32();
1449
+ return arr;
1450
+ }
1451
+
1452
+ case TypeEnum.Vec4f: {
1453
+ const arr = new Float32Array(checkedLength(size * 4));
1454
+ for (let i = 0; i < size * 4; i++) arr[i] = reader.readFloat32();
1455
+ return arr;
1456
+ }
1457
+
1458
+ case TypeEnum.Vec3h: {
1459
+ // Half-precision vec3 array (used for scales in skeletal animation)
1460
+ const arr = new Float32Array(checkedLength(size * 3));
1461
+ for (let i = 0; i < size * 3; i++) arr[i] = this._readHalf();
1462
+ return arr;
1463
+ }
1464
+
1465
+ case TypeEnum.Quatf: {
1466
+ const arr = new Float32Array(checkedLength(size * 4));
1467
+ for (let i = 0; i < size * 4; i++) arr[i] = reader.readFloat32();
1468
+ return arr;
1469
+ }
1470
+
1471
+ case TypeEnum.Quath: {
1472
+ // Half-precision quaternion array
1473
+ const arr = new Float32Array(checkedLength(size * 4));
1474
+ for (let i = 0; i < size * 4; i++) arr[i] = this._readHalf();
1475
+ return arr;
1476
+ }
1477
+
1478
+ case TypeEnum.Matrix4d: {
1479
+ // 4x4 matrix array (16 doubles per matrix, row-major)
1480
+ const arr = new Float64Array(checkedLength(size * 16));
1481
+ for (let i = 0; i < size * 16; i++) arr[i] = reader.readFloat64();
1482
+ return arr;
1483
+ }
1484
+
1485
+ case TypeEnum.String:
1486
+ case TypeEnum.Token: {
1487
+ const arr = [];
1488
+ for (let i = 0; i < size; i++) {
1489
+ const index = reader.readUint32();
1490
+ const tokenIndex = type === TypeEnum.String ? this.strings[index] : index;
1491
+ const value = this.tokens[tokenIndex];
1492
+ if (typeof value !== 'string') throw new Error('USDC: invalid string array index');
1493
+ arr.push(value);
1494
+ }
1495
+
1496
+ return arr;
1497
+ }
1498
+
1499
+ case TypeEnum.Half: {
1500
+ const arr = new Float32Array(checkedLength(size));
1501
+ for (let i = 0; i < size; i++) arr[i] = this._readHalf();
1502
+ return arr;
1503
+ }
1504
+
1505
+ default:
1506
+ throw new Error(
1507
+ 'USDC: unsupported encoding: ' + ['USDCParser: Unsupported array type', type].join(' ')
1508
+ );
1509
+ }
1510
+ }
1511
+
1512
+ _readCompressedArray(type, size) {
1513
+ const reader = this.reader;
1514
+
1515
+ switch (type) {
1516
+ case TypeEnum.Int:
1517
+ case TypeEnum.UInt: {
1518
+ const compressedSize = reader.readUint64();
1519
+ const compressed = reader.readBytes(compressedSize);
1520
+ const values = decompressIntegers32(
1521
+ compressed.buffer.slice(compressed.byteOffset, compressed.byteOffset + compressedSize),
1522
+ size
1523
+ );
1524
+ return type === TypeEnum.UInt
1525
+ ? new Uint32Array(values.buffer, values.byteOffset, values.length)
1526
+ : values;
1527
+ }
1528
+
1529
+ case TypeEnum.Half:
1530
+ case TypeEnum.Double:
1531
+ case TypeEnum.Float: {
1532
+ // Float compression: 'i' = compressed as ints, 't' = lookup table
1533
+ const code = reader.readInt8();
1534
+
1535
+ if (code === FLOAT_COMPRESSION_INT) {
1536
+ const compressedSize = reader.readUint64();
1537
+ const compressed = reader.readBytes(compressedSize);
1538
+ const ints = decompressIntegers32(
1539
+ compressed.buffer.slice(compressed.byteOffset, compressed.byteOffset + compressedSize),
1540
+ size
1541
+ );
1542
+ const floats =
1543
+ type === TypeEnum.Double
1544
+ ? new Float64Array(checkedLength(size))
1545
+ : new Float32Array(checkedLength(size));
1546
+ for (let i = 0; i < size; i++)
1547
+ floats[i] = type === TypeEnum.Half ? this._convertHalfInteger(ints[i]) : ints[i];
1548
+ return floats;
1549
+ } else if (code === FLOAT_COMPRESSION_LUT) {
1550
+ const lutSize = checkedCount(reader.readUint32());
1551
+ if (lutSize === 0) throw new Error('USDC: invalid float lookup table');
1552
+ const lut =
1553
+ type === TypeEnum.Double
1554
+ ? new Float64Array(checkedLength(lutSize))
1555
+ : new Float32Array(checkedLength(lutSize));
1556
+ for (let i = 0; i < lutSize; i++)
1557
+ lut[i] =
1558
+ type === TypeEnum.Half
1559
+ ? this._readHalf()
1560
+ : type === TypeEnum.Double
1561
+ ? reader.readFloat64()
1562
+ : reader.readFloat32();
1563
+
1564
+ const compressedSize = reader.readUint64();
1565
+ const compressed = reader.readBytes(compressedSize);
1566
+ const indices = decompressIntegers32(
1567
+ compressed.buffer.slice(compressed.byteOffset, compressed.byteOffset + compressedSize),
1568
+ size
1569
+ );
1570
+
1571
+ const floats =
1572
+ type === TypeEnum.Double
1573
+ ? new Float64Array(checkedLength(size))
1574
+ : new Float32Array(checkedLength(size));
1575
+ for (let i = 0; i < size; i++) {
1576
+ if (indices[i] < 0 || indices[i] >= lutSize)
1577
+ throw new Error('USDC: invalid float lookup index');
1578
+ floats[i] = lut[indices[i]];
1579
+ }
1580
+ return floats;
1581
+ }
1582
+
1583
+ throw new Error(
1584
+ 'USDC: unsupported encoding: ' +
1585
+ ['USDCParser: Unknown float compression code', code].join(' ')
1586
+ );
1587
+ }
1588
+
1589
+ default:
1590
+ throw new Error(
1591
+ 'USDC: unsupported encoding: ' +
1592
+ ['USDCParser: Unsupported compressed array type', type].join(' ')
1593
+ );
1594
+ }
1595
+ }
1596
+
1597
+ /** Rounds integer-compressed half samples using IEEE ties-to-even semantics. */
1598
+ _convertHalfInteger(value) {
1599
+ const absoluteValue = Math.abs(value);
1600
+ if (absoluteValue <= 2048) return value;
1601
+ if (absoluteValue >= 65520) return Math.sign(value) * Infinity;
1602
+ const spacing = 2 ** (Math.floor(Math.log2(absoluteValue)) - 10);
1603
+ const scaledValue = absoluteValue / spacing;
1604
+ const lowerValue = Math.floor(scaledValue);
1605
+ const fractionValue = scaledValue - lowerValue;
1606
+ const roundedValue =
1607
+ fractionValue > 0.5 || (fractionValue === 0.5 && lowerValue % 2 !== 0)
1608
+ ? lowerValue + 1
1609
+ : lowerValue;
1610
+ return Math.sign(value) * roundedValue * spacing;
1611
+ }
1612
+
1613
+ _readHalf() {
1614
+ return this._halfToFloat(this.reader.readUint16());
1615
+ }
1616
+
1617
+ _halfToFloat(h) {
1618
+ const sign = (h & 0x8000) >> 15;
1619
+ const exp = (h & 0x7c00) >> 10;
1620
+ const frac = h & 0x03ff;
1621
+
1622
+ if (exp === 0) {
1623
+ // Zero or denormalized number
1624
+ if (frac === 0) {
1625
+ return sign ? -0 : 0;
1626
+ }
1627
+
1628
+ // Denormalized: value = ±2^-14 × (frac/1024)
1629
+ return (sign ? -1 : 1) * HALF_DENORM_SCALE * (frac / 1024);
1630
+ } else if (exp === 31) {
1631
+ return frac ? NaN : sign ? -Infinity : Infinity;
1632
+ }
1633
+
1634
+ return (sign ? -1 : 1) * HALF_EXPONENT_TABLE[exp] * (1 + frac / 1024);
1635
+ }
1636
+
1637
+ _getFieldsForSpec(spec) {
1638
+ const fields = Object.create(null);
1639
+ let fieldSetIndex = spec.fieldSetIndex;
1640
+
1641
+ // Field sets are terminated by FIELD_SET_TERMINATOR
1642
+ // Limit iterations to prevent infinite loops from malformed data
1643
+ const maxIterations = 10000;
1644
+ let iterations = 0;
1645
+
1646
+ while (fieldSetIndex < this.fieldSets.length && iterations < maxIterations) {
1647
+ const fieldIndex = this.fieldSets[fieldSetIndex];
1648
+
1649
+ // Terminator
1650
+ if (fieldIndex === FIELD_SET_TERMINATOR || fieldIndex === -1) break;
1651
+
1652
+ const field = this.fields[fieldIndex];
1653
+ if (!field) throw new Error('USDC: invalid field index');
1654
+ if (field) {
1655
+ const name = this.tokens[field.tokenIndex];
1656
+ const value = this._readValue(field.valueRep);
1657
+ fields[name] = value;
1658
+ }
1659
+
1660
+ fieldSetIndex++;
1661
+ iterations++;
1662
+ }
1663
+
1664
+ if (iterations >= maxIterations) throw new Error('USDC: field traversal limit exceeded');
1665
+ return fields;
1666
+ }
1667
+ }
1668
+
1669
+ export {USDCParser};