@driftengine/assets 4.9.0 → 4.11.0

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.
@@ -0,0 +1,566 @@
1
+ /**
2
+ * ETC2 and EAC blocks made from pixels: what a phone samples, at half a byte a texel for colour and
3
+ * a byte with alpha, where RGBA costs four. The loader encodes a texture this way where the device
4
+ * takes ETC2 and not the BC its file holds (`etc2Plan`), and a caller holding pixels of its own can
5
+ * call `encodeEtc2` directly — off the main thread, since a 2048² image is 262,144 blocks.
6
+ *
7
+ * **ETC2's colour, through the modes it shares with ETC1**: each 4x4 block split in two halves,
8
+ * side by side or one above the other, each half a base colour and one of eight tables of
9
+ * brightness steps, each texel the step nearest its own colour. Both splits and both ways of storing
10
+ * the two bases — each on its own at four bits, or the second as a small difference from the first
11
+ * at five — are tried, and the least error kept. **What it gives up** is ETC2's three further modes,
12
+ * T, H and planar, which every ETC2 device decodes and this encoder never chooses: a smooth gradient
13
+ * across a block and a sharp edge between two colours come out a little coarser than a full search
14
+ * would make them. What would make that worth changing is a texture whose banding shows at the size
15
+ * it is seen.
16
+ *
17
+ * **EAC for everything that is not colour**: an alpha channel beside ETC2's colour, and one or two
18
+ * channels of data on their own (`eac-r11`, `eac-rg11`) — a roughness map, a normal map's x and y.
19
+ * Each block a base, a step size and one of sixteen tables of eight steps, searched per table.
20
+ *
21
+ * Texels past the edge of a level smaller than a block repeat the last row and column, so a 2x2 or
22
+ * 1x1 level is one block whose other texels match. Allocates the blocks it returns, once a level.
23
+ */
24
+
25
+ /** The formats this encodes: colour, colour with alpha, and one or two channels of data. */
26
+ export type Etc2Format = 'etc2-rgb8' | 'etc2-rgba8' | 'eac-r11' | 'eac-rg11';
27
+
28
+ /** ETC1's eight tables of brightness steps: the small and the large, each added and subtracted. */
29
+ const STEPS: readonly (readonly [number, number])[] = [
30
+ [2, 8],
31
+ [5, 17],
32
+ [9, 29],
33
+ [13, 42],
34
+ [18, 60],
35
+ [24, 80],
36
+ [33, 106],
37
+ [47, 183],
38
+ ];
39
+
40
+ /** A texel's step by its two-bit index: the small added, the large added, then both subtracted. */
41
+ function step(table: number, index: number): number {
42
+ const [small, large] = STEPS[table] as readonly [number, number];
43
+ return index === 0 ? small : index === 1 ? large : index === 2 ? -small : -large;
44
+ }
45
+
46
+ /** EAC's sixteen tables of eight steps. */
47
+ const EAC: readonly (readonly number[])[] = [
48
+ [-3, -6, -9, -15, 2, 5, 8, 14],
49
+ [-3, -7, -10, -13, 2, 6, 9, 12],
50
+ [-2, -5, -8, -13, 1, 4, 7, 12],
51
+ [-2, -4, -6, -13, 1, 3, 5, 12],
52
+ [-3, -6, -8, -12, 2, 5, 7, 11],
53
+ [-3, -7, -9, -11, 2, 6, 8, 10],
54
+ [-4, -7, -8, -11, 3, 6, 7, 10],
55
+ [-3, -5, -8, -11, 2, 4, 7, 10],
56
+ [-2, -6, -8, -10, 1, 5, 7, 9],
57
+ [-2, -5, -8, -10, 1, 4, 7, 9],
58
+ [-2, -4, -8, -10, 1, 3, 7, 9],
59
+ [-2, -5, -7, -10, 1, 4, 6, 9],
60
+ [-3, -4, -7, -10, 2, 3, 6, 9],
61
+ [-1, -2, -3, -10, 0, 1, 2, 9],
62
+ [-4, -6, -8, -9, 3, 5, 7, 8],
63
+ [-3, -5, -7, -9, 2, 4, 6, 8],
64
+ ];
65
+
66
+ /** Per EAC table, the index of the step nearest each whole offset from -16 to 15. */
67
+ const NEAREST: readonly Int8Array[] = EAC.map((steps) => {
68
+ const lookup = new Int8Array(32);
69
+ for (let u = -16; u < 16; u++) {
70
+ let chosen = 0;
71
+ for (let index = 1; index < 8; index++) {
72
+ if (Math.abs((steps[index] as number) - u) < Math.abs((steps[chosen] as number) - u)) {
73
+ chosen = index;
74
+ }
75
+ }
76
+ lookup[u + 16] = chosen;
77
+ }
78
+ return lookup;
79
+ });
80
+
81
+ /** Bytes a block of each format takes. */
82
+ const BYTES: Readonly<Record<Etc2Format, number>> = {
83
+ 'etc2-rgb8': 8,
84
+ 'etc2-rgba8': 16,
85
+ 'eac-r11': 8,
86
+ 'eac-rg11': 16,
87
+ };
88
+
89
+ const clamp255 = (v: number): number => (v < 0 ? 0 : v > 255 ? 255 : v);
90
+
91
+ /** One block's texels, RGBA, in ETC's own order: down each column, then across. */
92
+ const texels = new Int32Array(64);
93
+
94
+ /** `rgba` at `width` by `height` as `format` blocks, row of blocks after row of blocks. */
95
+ export function encodeEtc2(
96
+ format: Etc2Format,
97
+ rgba: Uint8Array,
98
+ width: number,
99
+ height: number,
100
+ ): Uint8Array {
101
+ const across = Math.max(1, Math.ceil(width / 4));
102
+ const down = Math.max(1, Math.ceil(height / 4));
103
+ const bytes = BYTES[format];
104
+ const out = new Uint8Array(across * down * bytes);
105
+ for (let by = 0; by < down; by++) {
106
+ for (let bx = 0; bx < across; bx++) {
107
+ gather(rgba, width, height, bx * 4, by * 4);
108
+ const at = (by * across + bx) * bytes;
109
+ if (format === 'etc2-rgb8') encodeColour(out, at);
110
+ else if (format === 'etc2-rgba8') {
111
+ encodeEac(out, at, 3, false);
112
+ encodeColour(out, at + 8);
113
+ } else if (format === 'eac-r11') encodeEac(out, at, 0, true);
114
+ else {
115
+ encodeEac(out, at, 0, true);
116
+ encodeEac(out, at + 8, 1, true);
117
+ }
118
+ }
119
+ }
120
+ return out;
121
+ }
122
+
123
+ /** The block at `(x0, y0)` into `texels`, the edge repeated past a level smaller than a block. */
124
+ function gather(rgba: Uint8Array, width: number, height: number, x0: number, y0: number): void {
125
+ for (let x = 0; x < 4; x++) {
126
+ for (let y = 0; y < 4; y++) {
127
+ const sx = Math.min(x0 + x, width - 1);
128
+ const sy = Math.min(y0 + y, height - 1);
129
+ const from = (sy * width + sx) * 4;
130
+ const to = (x * 4 + y) * 4;
131
+ texels[to] = rgba[from] as number;
132
+ texels[to + 1] = rgba[from + 1] as number;
133
+ texels[to + 2] = rgba[from + 2] as number;
134
+ texels[to + 3] = rgba[from + 3] as number;
135
+ }
136
+ }
137
+ }
138
+
139
+ /** Whether texel `i` (ETC order) is in the first half, for a split `flip` names. */
140
+ const firstHalf = (i: number, flip: number): boolean => (flip === 0 ? i < 8 : i % 4 < 2);
141
+
142
+ /** The eight texels of each half: `HALF_TEXELS[flip * 2 + half]`. */
143
+ const HALF_TEXELS: readonly Int32Array[] = [0, 1, 2, 3].map((k) => {
144
+ const list = new Int32Array(8);
145
+ let count = 0;
146
+ for (let i = 0; i < 16; i++) {
147
+ if (firstHalf(i, k >> 1) === ((k & 1) === 0)) list[count++] = i;
148
+ }
149
+ return list;
150
+ });
151
+
152
+ /** Each table's small and large step, as numbers the search reads without unpacking a pair. */
153
+ const SMALL = new Int32Array(STEPS.map(([small]) => small));
154
+ const LARGE = new Int32Array(STEPS.map(([, large]) => large));
155
+
156
+ /** A half's search result, reused. */
157
+ interface Half {
158
+ error: number;
159
+ table: number;
160
+ /** The two-bit index of each of the half's eight texels, in `HALF_TEXELS` order. */
161
+ readonly indices: Int32Array;
162
+ }
163
+ const halves: [Half, Half] = [
164
+ { error: 0, table: 0, indices: new Int32Array(8) },
165
+ { error: 0, table: 0, indices: new Int32Array(8) },
166
+ ];
167
+ const best = { error: Infinity, high: 0, low: 0 };
168
+ const base = new Int32Array(6);
169
+ const quantized = new Int32Array(6);
170
+ const offsets = new Float64Array(8);
171
+
172
+ /** The least-error ETC1-mode block for `texels`, written at `at`. */
173
+ function encodeColour(out: Uint8Array, at: number): void {
174
+ best.error = Infinity;
175
+ const [first, second] = halves;
176
+ for (let flip = 0; flip < 2; flip++) {
177
+ let fitted = false;
178
+ for (let differential = 1; differential >= 0; differential--) {
179
+ /* Five bits where the pair's difference fits, which is nearly always the better; four bits
180
+ each only where it does not, so a split pays for one search and not two. */
181
+ if (differential === 0 && fitted) continue;
182
+ fitted = bases(flip, differential === 1);
183
+ if (!fitted) continue;
184
+ /* Each half searched only as far as it could still beat the best block so far. */
185
+ searchHalf(flip, 0, first, best.error);
186
+ if (first.error >= best.error) continue;
187
+ searchHalf(flip, 1, second, best.error - first.error);
188
+ const error = first.error + second.error;
189
+ if (error >= best.error) continue;
190
+ best.error = error;
191
+ let high = 0;
192
+ if (differential === 1) {
193
+ for (let c = 0; c < 3; c++) {
194
+ const q = quantized[c] as number;
195
+ const delta = (quantized[c + 3] as number) - q;
196
+ high |= ((q << 3) | (delta & 7)) << (24 - c * 8);
197
+ }
198
+ } else {
199
+ for (let c = 0; c < 3; c++) {
200
+ high |= (((quantized[c] as number) << 4) | (quantized[c + 3] as number)) << (24 - c * 8);
201
+ }
202
+ }
203
+ high |= (first.table << 5) | (second.table << 2) | (differential << 1) | flip;
204
+ let low = 0;
205
+ for (let half = 0; half < 2; half++) {
206
+ const list = HALF_TEXELS[flip * 2 + half] as Int32Array;
207
+ const indices = (halves[half] as Half).indices;
208
+ for (let k = 0; k < 8; k++) {
209
+ const i = list[k] as number;
210
+ const index = indices[k] as number;
211
+ low |= ((index >> 1) << (i + 16)) | ((index & 1) << i);
212
+ }
213
+ }
214
+ best.high = high >>> 0;
215
+ best.low = low >>> 0;
216
+ }
217
+ }
218
+ write32(out, at, best.high);
219
+ write32(out, at + 4, best.low);
220
+ }
221
+
222
+ /**
223
+ * Each half's average, quantised as the mode stores it and expanded back, into `base` — and false
224
+ * where the differential mode cannot hold the second as a step of -4 to 3 from the first.
225
+ */
226
+ function bases(flip: number, differential: boolean): boolean {
227
+ for (let half = 0; half < 2; half++) {
228
+ const list = HALF_TEXELS[flip * 2 + half] as Int32Array;
229
+ let r = 0;
230
+ let g = 0;
231
+ let b = 0;
232
+ for (let k = 0; k < 8; k++) {
233
+ const i = list[k] as number;
234
+ r += texels[i * 4] as number;
235
+ g += texels[i * 4 + 1] as number;
236
+ b += texels[i * 4 + 2] as number;
237
+ }
238
+ for (let c = 0; c < 3; c++) {
239
+ const average = (c === 0 ? r : c === 1 ? g : b) / 8;
240
+ if (differential) {
241
+ const q = Math.round((average * 31) / 255);
242
+ quantized[half * 3 + c] = q;
243
+ base[half * 3 + c] = (q << 3) | (q >> 2);
244
+ } else {
245
+ const q = Math.round((average * 15) / 255);
246
+ quantized[half * 3 + c] = q;
247
+ base[half * 3 + c] = (q << 4) | q;
248
+ }
249
+ }
250
+ }
251
+ if (!differential) return true;
252
+ for (let c = 0; c < 3; c++) {
253
+ const delta = (quantized[c + 3] as number) - (quantized[c] as number);
254
+ if (delta < -4 || delta > 3) return false;
255
+ }
256
+ return true;
257
+ }
258
+
259
+ /**
260
+ * The table with the least error for one half against its base, and each texel's step for it —
261
+ * or `into.error` left at infinity where no table comes under `bound`.
262
+ *
263
+ * **A texel's step is found, not searched.** Every step moves all three channels by one amount, so
264
+ * the amount that brings a texel nearest its colour is its average offset from the base, and the
265
+ * step to take is the one nearest that. Unclamped, a texel's error at step `s` is its spread about
266
+ * that average offset `o` plus `3(s − o)²`, so a table's error is the half's spread, summed once,
267
+ * plus that term — exact, with no channel read again. **Only a table whose large step could carry a
268
+ * channel past 0 or 255 is checked texel by texel**, with the clamp a device applies and the
269
+ * nearest step's neighbour as a second candidate, since a clamped channel can make the nearest step
270
+ * no longer the closest. On a photograph that is a dark or a bright block's few tables.
271
+ *
272
+ * What it gives up is the error a weighting by channel would trade; the steps are one brightness for
273
+ * all three, so a weight changes which of two near steps wins and little else.
274
+ */
275
+ function searchHalf(flip: number, half: number, into: Half, bound: number): void {
276
+ const list = HALF_TEXELS[flip * 2 + half] as Int32Array;
277
+ const r0 = base[half * 3] as number;
278
+ const g0 = base[half * 3 + 1] as number;
279
+ const b0 = base[half * 3 + 2] as number;
280
+ const lowest = Math.min(r0, g0, b0);
281
+ const highest = Math.max(r0, g0, b0);
282
+ let spread = 0;
283
+ for (let k = 0; k < 8; k++) {
284
+ const i = list[k] as number;
285
+ const dr = (texels[i * 4] as number) - r0;
286
+ const dg = (texels[i * 4 + 1] as number) - g0;
287
+ const db = (texels[i * 4 + 2] as number) - b0;
288
+ const offset = (dr + dg + db) / 3;
289
+ offsets[k] = offset;
290
+ spread += dr * dr + dg * dg + db * db - 3 * offset * offset;
291
+ }
292
+ into.error = Infinity;
293
+ let chosen = -1;
294
+ let least = bound;
295
+ for (let table = 0; table < 8; table++) {
296
+ const small = SMALL[table] as number;
297
+ const large = LARGE[table] as number;
298
+ const error =
299
+ lowest - large >= 0 && highest + large <= 255
300
+ ? spread + 3 * nearestSum(small, large, null)
301
+ : clampedError(list, r0, g0, b0, small, large, least, null);
302
+ if (error < least) {
303
+ least = error;
304
+ chosen = table;
305
+ }
306
+ }
307
+ if (chosen < 0) return;
308
+ into.error = least;
309
+ into.table = chosen;
310
+ const small = SMALL[chosen] as number;
311
+ const large = LARGE[chosen] as number;
312
+ if (lowest - large >= 0 && highest + large <= 255) nearestSum(small, large, into.indices);
313
+ else clampedError(list, r0, g0, b0, small, large, Infinity, into.indices);
314
+ }
315
+
316
+ /** `Σ (s − o)²` over the half's offsets at each one's nearest step, the steps into `write`. */
317
+ function nearestSum(small: number, large: number, write: Int32Array | null): number {
318
+ const middle = (small + large) / 2;
319
+ let sum = 0;
320
+ for (let k = 0; k < 8; k++) {
321
+ const offset = offsets[k] as number;
322
+ let index: number;
323
+ let d: number;
324
+ if (offset >= 0) {
325
+ index = offset > middle ? 1 : 0;
326
+ d = (index === 1 ? large : small) - offset;
327
+ } else {
328
+ index = offset < -middle ? 3 : 2;
329
+ d = (index === 3 ? -large : -small) - offset;
330
+ }
331
+ if (write !== null) write[k] = index;
332
+ sum += d * d;
333
+ }
334
+ return sum;
335
+ }
336
+
337
+ /** A table's error with every channel clamped as a device does, stopping once past `bound`. */
338
+ function clampedError(
339
+ list: Int32Array,
340
+ r0: number,
341
+ g0: number,
342
+ b0: number,
343
+ small: number,
344
+ large: number,
345
+ bound: number,
346
+ write: Int32Array | null,
347
+ ): number {
348
+ let error = 0;
349
+ for (let k = 0; k < 8; k++) {
350
+ const i = list[k] as number;
351
+ const offset = offsets[k] as number;
352
+ /* The nearest step by the offset, and its neighbour toward zero. */
353
+ let near: number;
354
+ let other: number;
355
+ if (offset >= 0) {
356
+ near = offset > (small + large) / 2 ? 1 : 0;
357
+ other = near === 1 ? 0 : 2;
358
+ } else {
359
+ near = offset < -(small + large) / 2 ? 3 : 2;
360
+ other = near === 3 ? 2 : 0;
361
+ }
362
+ const e1 = texelError(i, r0, g0, b0, stepOf(near, small, large));
363
+ const e2 = texelError(i, r0, g0, b0, stepOf(other, small, large));
364
+ if (write !== null) write[k] = e1 <= e2 ? near : other;
365
+ error += e1 <= e2 ? e1 : e2;
366
+ if (error >= bound) return error;
367
+ }
368
+ return error;
369
+ }
370
+
371
+ /** A step by its two-bit index, from a table's two amounts. */
372
+ function stepOf(index: number, small: number, large: number): number {
373
+ return index === 0 ? small : index === 1 ? large : index === 2 ? -small : -large;
374
+ }
375
+
376
+ /** One texel's squared error against a base moved by `s`, every channel clamped as a device does. */
377
+ function texelError(i: number, r0: number, g0: number, b0: number, s: number): number {
378
+ const dr = (texels[i * 4] as number) - clamp255(r0 + s);
379
+ const dg = (texels[i * 4 + 1] as number) - clamp255(g0 + s);
380
+ const db = (texels[i * 4 + 2] as number) - clamp255(b0 + s);
381
+ return dr * dr + dg * dg + db * db;
382
+ }
383
+ const eacIndices = new Int32Array(16);
384
+ const eacBest = new Int32Array(16);
385
+
386
+ /**
387
+ * One EAC block of channel `channel` at `at`: eight-bit, as alpha beside colour is, or eleven-bit,
388
+ * as `eac-r11` stores data. A block of one value is stored exactly, through the one table with a
389
+ * zero step.
390
+ */
391
+ function encodeEac(out: Uint8Array, at: number, channel: number, eleven: boolean): void {
392
+ let min = 255;
393
+ let max = 0;
394
+ for (let i = 0; i < 16; i++) {
395
+ const v = texels[i * 4 + channel] as number;
396
+ if (v < min) min = v;
397
+ if (v > max) max = v;
398
+ }
399
+ /* Targets in the stored range: eight bits, or eleven with 255 at 2047. */
400
+ const scale = eleven ? 2047 / 255 : 1;
401
+ let bestError = Infinity;
402
+ let bestBase = 0;
403
+ let bestMultiplier = 1;
404
+ let bestTable = 13;
405
+ for (let table = 0; table < 16; table++) {
406
+ const steps = EAC[table] as readonly number[];
407
+ let lo = Infinity;
408
+ let hi = -Infinity;
409
+ for (const s of steps) {
410
+ if (s < lo) lo = s;
411
+ if (s > hi) hi = s;
412
+ }
413
+ const span = (max - min) * scale;
414
+ const grain = eleven ? 8 : 1;
415
+ const ideal = span / ((hi - lo) * grain);
416
+ for (let round = 0; round < 2; round++) {
417
+ const m = Math.min(15, Math.max(1, round === 0 ? Math.floor(ideal) : Math.ceil(ideal)));
418
+ /* The base that puts the table's middle on the block's. */
419
+ const middle = ((min + max) / 2) * scale - ((hi + lo) / 2) * m * grain;
420
+ const b = Math.min(255, Math.max(0, Math.round(eleven ? (middle - 4) / 8 : middle)));
421
+ let error = 0;
422
+ const nearest = NEAREST[table] as Int8Array;
423
+ const origin = eleven ? b * 8 + 4 : b;
424
+ const unit = m * grain;
425
+ for (let i = 0; i < 16; i++) {
426
+ const target = (texels[i * 4 + channel] as number) * scale;
427
+ /* The step nearest the texel's offset, looked up, and checked exactly with the clamp. */
428
+ const u = Math.round((target - origin) / unit);
429
+ const chosen = nearest[(u < -16 ? -16 : u > 15 ? 15 : u) + 16] as number;
430
+ const d = eacValue(b, m, steps[chosen] as number, eleven) - target;
431
+ eacIndices[i] = chosen;
432
+ error += d * d;
433
+ if (error >= bestError) break;
434
+ }
435
+ if (error < bestError) {
436
+ bestError = error;
437
+ bestBase = b;
438
+ bestMultiplier = m;
439
+ bestTable = table;
440
+ eacBest.set(eacIndices);
441
+ }
442
+ }
443
+ }
444
+ out[at] = bestBase;
445
+ out[at + 1] = (bestMultiplier << 4) | bestTable;
446
+ /* Forty-eight bits of three-bit indices, the first texel in the highest: two words of 24. */
447
+ let first = 0;
448
+ let second = 0;
449
+ for (let i = 0; i < 8; i++) first = (first << 3) | (eacBest[i] as number);
450
+ for (let i = 8; i < 16; i++) second = (second << 3) | (eacBest[i] as number);
451
+ out[at + 2] = (first >>> 16) & 255;
452
+ out[at + 3] = (first >>> 8) & 255;
453
+ out[at + 4] = first & 255;
454
+ out[at + 5] = (second >>> 16) & 255;
455
+ out[at + 6] = (second >>> 8) & 255;
456
+ out[at + 7] = second & 255;
457
+ }
458
+
459
+ /** What an EAC block decodes a step to: eight-bit, or eleven-bit as `eac-r11` stores. */
460
+ function eacValue(base: number, multiplier: number, step: number, eleven: boolean): number {
461
+ if (!eleven) return clamp255(base + step * multiplier);
462
+ const v = base * 8 + 4 + step * multiplier * 8;
463
+ return v < 0 ? 0 : v > 2047 ? 2047 : v;
464
+ }
465
+
466
+ function write32(out: Uint8Array, at: number, value: number): void {
467
+ out[at] = (value >>> 24) & 255;
468
+ out[at + 1] = (value >>> 16) & 255;
469
+ out[at + 2] = (value >>> 8) & 255;
470
+ out[at + 3] = value & 255;
471
+ }
472
+
473
+ /**
474
+ * One level of a block back to RGBA, for the modes this encoder writes and EAC: what a device
475
+ * decodes, stated here so the encoder can be held to a number. A block in T, H or planar mode is
476
+ * refused by name, since nothing here writes one.
477
+ */
478
+ export function decodeEtc2(
479
+ format: Etc2Format,
480
+ blocks: Uint8Array,
481
+ width: number,
482
+ height: number,
483
+ ): Uint8Array {
484
+ const across = Math.max(1, Math.ceil(width / 4));
485
+ const bytes = BYTES[format];
486
+ const out = new Uint8Array(width * height * 4);
487
+ for (let y = 0; y < height; y++) {
488
+ for (let x = 0; x < width; x++) {
489
+ const at = (Math.floor(y / 4) * across + Math.floor(x / 4)) * bytes;
490
+ const i = (x % 4) * 4 + (y % 4);
491
+ const to = (y * width + x) * 4;
492
+ out[to + 3] = 255;
493
+ if (format === 'etc2-rgb8' || format === 'etc2-rgba8') {
494
+ decodeColourTexel(blocks, format === 'etc2-rgba8' ? at + 8 : at, i, out, to);
495
+ if (format === 'etc2-rgba8') out[to + 3] = decodeEacTexel(blocks, at, i, false);
496
+ } else {
497
+ out[to] = Math.round((decodeEacTexel(blocks, at, i, true) * 255) / 2047);
498
+ out[to + 1] =
499
+ format === 'eac-rg11'
500
+ ? Math.round((decodeEacTexel(blocks, at + 8, i, true) * 255) / 2047)
501
+ : 0;
502
+ out[to + 2] = 0;
503
+ }
504
+ }
505
+ }
506
+ return out;
507
+ }
508
+
509
+ function read32(blocks: Uint8Array, at: number): number {
510
+ return (
511
+ (((blocks[at] as number) << 24) |
512
+ ((blocks[at + 1] as number) << 16) |
513
+ ((blocks[at + 2] as number) << 8) |
514
+ (blocks[at + 3] as number)) >>>
515
+ 0
516
+ );
517
+ }
518
+
519
+ function decodeColourTexel(
520
+ blocks: Uint8Array,
521
+ at: number,
522
+ i: number,
523
+ out: Uint8Array,
524
+ to: number,
525
+ ): void {
526
+ const high = read32(blocks, at);
527
+ const low = read32(blocks, at + 4);
528
+ const flip = high & 1;
529
+ const differential = (high >> 1) & 1;
530
+ const half = firstHalf(i, flip) ? 0 : 1;
531
+ const table = half === 0 ? (high >> 5) & 7 : (high >> 2) & 7;
532
+ const index = (((low >>> (i + 16)) & 1) << 1) | ((low >>> i) & 1);
533
+ for (let c = 0; c < 3; c++) {
534
+ let value: number;
535
+ if (differential === 1) {
536
+ const first = (high >>> (27 - c * 8)) & 31;
537
+ let delta = (high >>> (24 - c * 8)) & 7;
538
+ if (delta >= 4) delta -= 8;
539
+ const q = half === 0 ? first : first + delta;
540
+ if (q < 0 || q > 31) {
541
+ throw new Error('decodeEtc2: a block in T, H or planar mode, which this does not decode');
542
+ }
543
+ value = (q << 3) | (q >> 2);
544
+ } else {
545
+ const q = (high >>> (half === 0 ? 28 - c * 8 : 24 - c * 8)) & 15;
546
+ value = (q << 4) | q;
547
+ }
548
+ out[to + c] = clamp255(value + step(table, index));
549
+ }
550
+ }
551
+
552
+ function decodeEacTexel(blocks: Uint8Array, at: number, i: number, eleven: boolean): number {
553
+ const b = blocks[at] as number;
554
+ const multiplier = (blocks[at + 1] as number) >> 4;
555
+ const table = (blocks[at + 1] as number) & 15;
556
+ const first =
557
+ ((blocks[at + 2] as number) << 16) |
558
+ ((blocks[at + 3] as number) << 8) |
559
+ (blocks[at + 4] as number);
560
+ const second =
561
+ ((blocks[at + 5] as number) << 16) |
562
+ ((blocks[at + 6] as number) << 8) |
563
+ (blocks[at + 7] as number);
564
+ const index = i < 8 ? (first >>> (21 - i * 3)) & 7 : (second >>> (21 - (i - 8) * 3)) & 7;
565
+ return eacValue(b, multiplier, (EAC[table] as readonly number[])[index] as number, eleven);
566
+ }