@driftengine/texture 4.0.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.
Files changed (99) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +29 -0
  3. package/README.md +106 -0
  4. package/dist/decodeCpu.d.ts +59 -0
  5. package/dist/decodeCpu.js +234 -0
  6. package/dist/decodeGraph.d.ts +105 -0
  7. package/dist/decodeGraph.js +180 -0
  8. package/dist/half.d.ts +24 -0
  9. package/dist/half.js +86 -0
  10. package/dist/index.d.ts +66 -0
  11. package/dist/index.js +55 -0
  12. package/dist/inference.d.ts +53 -0
  13. package/dist/inference.js +243 -0
  14. package/dist/materialArray.d.ts +38 -0
  15. package/dist/materialArray.js +40 -0
  16. package/dist/mipNdf.d.ts +29 -0
  17. package/dist/mipNdf.js +53 -0
  18. package/dist/overlay/journal.d.ts +78 -0
  19. package/dist/overlay/journal.js +171 -0
  20. package/dist/overlay/sparse.d.ts +68 -0
  21. package/dist/overlay/sparse.js +212 -0
  22. package/dist/progressive.d.ts +30 -0
  23. package/dist/progressive.js +56 -0
  24. package/dist/residency/pageCache.d.ts +103 -0
  25. package/dist/residency/pageCache.js +184 -0
  26. package/dist/residency/predict.d.ts +55 -0
  27. package/dist/residency/predict.js +51 -0
  28. package/dist/residency/predictor.d.ts +16 -0
  29. package/dist/residency/predictor.js +44 -0
  30. package/dist/residency/queue.d.ts +26 -0
  31. package/dist/residency/queue.js +52 -0
  32. package/dist/residency/stream.d.ts +66 -0
  33. package/dist/residency/stream.js +142 -0
  34. package/dist/residency/table.d.ts +36 -0
  35. package/dist/residency/table.js +72 -0
  36. package/dist/residency/viewTiles.d.ts +108 -0
  37. package/dist/residency/viewTiles.js +419 -0
  38. package/dist/semantics.d.ts +52 -0
  39. package/dist/semantics.js +76 -0
  40. package/dist/tensor/architecture.d.ts +53 -0
  41. package/dist/tensor/architecture.js +96 -0
  42. package/dist/tensor/attention.d.ts +5 -0
  43. package/dist/tensor/attention.js +62 -0
  44. package/dist/tensor/denseOperators.d.ts +2 -0
  45. package/dist/tensor/denseOperators.js +136 -0
  46. package/dist/tensor/graph.d.ts +83 -0
  47. package/dist/tensor/graph.js +175 -0
  48. package/dist/tensor/linear.d.ts +49 -0
  49. package/dist/tensor/linear.js +136 -0
  50. package/dist/tensor/operatorKit.d.ts +27 -0
  51. package/dist/tensor/operatorKit.js +45 -0
  52. package/dist/tensor/operators.d.ts +3 -0
  53. package/dist/tensor/operators.js +24 -0
  54. package/dist/tensor/resize.d.ts +6 -0
  55. package/dist/tensor/resize.js +107 -0
  56. package/dist/tensor/reuse.d.ts +33 -0
  57. package/dist/tensor/reuse.js +59 -0
  58. package/dist/tensor/shapeOperators.d.ts +3 -0
  59. package/dist/tensor/shapeOperators.js +173 -0
  60. package/dist/tensor/spatial.d.ts +34 -0
  61. package/dist/tensor/spatial.js +131 -0
  62. package/dist/tensor/spatialOperators.d.ts +2 -0
  63. package/dist/tensor/spatialOperators.js +138 -0
  64. package/dist/tileHash.d.ts +29 -0
  65. package/dist/tileHash.js +50 -0
  66. package/dist/timeNodes.d.ts +26 -0
  67. package/dist/timeNodes.js +48 -0
  68. package/package.json +59 -0
  69. package/src/decodeCpu.ts +308 -0
  70. package/src/decodeGraph.ts +214 -0
  71. package/src/half.ts +86 -0
  72. package/src/index.ts +175 -0
  73. package/src/inference.ts +278 -0
  74. package/src/materialArray.ts +67 -0
  75. package/src/mipNdf.ts +63 -0
  76. package/src/overlay/journal.ts +218 -0
  77. package/src/overlay/sparse.ts +275 -0
  78. package/src/progressive.ts +60 -0
  79. package/src/residency/pageCache.ts +233 -0
  80. package/src/residency/predict.ts +74 -0
  81. package/src/residency/predictor.ts +62 -0
  82. package/src/residency/queue.ts +78 -0
  83. package/src/residency/stream.ts +194 -0
  84. package/src/residency/table.ts +89 -0
  85. package/src/residency/viewTiles.ts +553 -0
  86. package/src/semantics.ts +114 -0
  87. package/src/tensor/architecture.ts +140 -0
  88. package/src/tensor/attention.ts +75 -0
  89. package/src/tensor/denseOperators.ts +153 -0
  90. package/src/tensor/graph.ts +244 -0
  91. package/src/tensor/linear.ts +153 -0
  92. package/src/tensor/operatorKit.ts +76 -0
  93. package/src/tensor/operators.ts +28 -0
  94. package/src/tensor/resize.ts +140 -0
  95. package/src/tensor/shapeOperators.ts +173 -0
  96. package/src/tensor/spatial.ts +178 -0
  97. package/src/tensor/spatialOperators.ts +182 -0
  98. package/src/tileHash.ts +60 -0
  99. package/src/timeNodes.ts +60 -0
@@ -0,0 +1,131 @@
1
+ /**
2
+ * The spatial operators: convolution, its transpose, a patch embedding and max pooling. Resizing is
3
+ * `resize.ts`'s.
4
+ *
5
+ * **Channel-major images, one at a time** — `[channels][height][width]`, the layout the upstream
6
+ * frameworks call NCHW with a batch of one — and **weights in the upstream layout**: a convolution's
7
+ * `[out][in][kh][kw]`, a transposed convolution's `[in][out][kh][kw]`. A converted checkpoint is
8
+ * then read as it was stored, and a rearrangement is never a place for a transposed weight to hide.
9
+ */
10
+ /**
11
+ * `out[cout][oh][ow]` from `input[cin][h][w]`, with `oh = ⌊(h + 2·padding − kh)/stride⌋ + 1` and
12
+ * likewise `ow`. `bias` may be null.
13
+ *
14
+ * **Grouped, the channels split into `groups` runs** and output `o` reads only the run `o` falls in:
15
+ * the weight is `[cout][cin / groups][kh][kw]`, as the upstream stores it, and a depthwise
16
+ * convolution is `groups = cin = cout`.
17
+ */
18
+ export function conv2d(out, input, cin, h, w, weight, bias, cout, kh, kw, stride, padding, groups = 1) {
19
+ const oh = Math.floor((h + 2 * padding - kh) / stride) + 1;
20
+ const ow = Math.floor((w + 2 * padding - kw) / stride) + 1;
21
+ const inPerGroup = cin / groups;
22
+ const outPerGroup = cout / groups;
23
+ for (let o = 0; o < cout; o += 1) {
24
+ const first = Math.floor(o / outPerGroup) * inPerGroup;
25
+ for (let y = 0; y < oh; y += 1) {
26
+ for (let x = 0; x < ow; x += 1) {
27
+ let sum = bias === null ? 0 : bias[o];
28
+ for (let c = 0; c < inPerGroup; c += 1) {
29
+ for (let ky = 0; ky < kh; ky += 1) {
30
+ const sy = y * stride - padding + ky;
31
+ if (sy < 0 || sy >= h)
32
+ continue;
33
+ for (let kx = 0; kx < kw; kx += 1) {
34
+ const sx = x * stride - padding + kx;
35
+ if (sx < 0 || sx >= w)
36
+ continue;
37
+ sum +=
38
+ input[((first + c) * h + sy) * w + sx] *
39
+ weight[((o * inPerGroup + c) * kh + ky) * kw + kx];
40
+ }
41
+ }
42
+ }
43
+ out[(o * oh + y) * ow + x] = sum;
44
+ }
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * `out[cout][oh][ow]` from `input[cin][h][w]`, with `oh = (h − 1)·stride − 2·padding + kh`: each input
50
+ * pixel spread over the kernel at its strided place, which is how a decoder head upsamples.
51
+ */
52
+ export function convTranspose2d(out, input, cin, h, w, weight, bias, cout, kh, kw, stride, padding) {
53
+ const oh = (h - 1) * stride - 2 * padding + kh;
54
+ const ow = (w - 1) * stride - 2 * padding + kw;
55
+ for (let o = 0; o < cout; o += 1) {
56
+ const value = bias === null ? 0 : bias[o];
57
+ for (let i = 0; i < oh * ow; i += 1)
58
+ out[o * oh * ow + i] = value;
59
+ }
60
+ for (let c = 0; c < cin; c += 1) {
61
+ for (let y = 0; y < h; y += 1) {
62
+ for (let x = 0; x < w; x += 1) {
63
+ const pixel = input[(c * h + y) * w + x];
64
+ for (let o = 0; o < cout; o += 1) {
65
+ for (let ky = 0; ky < kh; ky += 1) {
66
+ const ty = y * stride - padding + ky;
67
+ if (ty < 0 || ty >= oh)
68
+ continue;
69
+ for (let kx = 0; kx < kw; kx += 1) {
70
+ const tx = x * stride - padding + kx;
71
+ if (tx < 0 || tx >= ow)
72
+ continue;
73
+ const at = (o * oh + ty) * ow + tx;
74
+ out[at] =
75
+ out[at] +
76
+ pixel * weight[((c * cout + o) * kh + ky) * kw + kx];
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ /**
85
+ * A patch embedding: a convolution with stride and kernel both `patch`, written token-major —
86
+ * `out[tokens][dim]`, tokens row by row — which is the order a transformer reads them in.
87
+ */
88
+ export function patchEmbed(out, image, cin, h, w, weight, bias, dim, patch) {
89
+ const rows = Math.floor(h / patch);
90
+ const cols = Math.floor(w / patch);
91
+ for (let ty = 0; ty < rows; ty += 1) {
92
+ for (let tx = 0; tx < cols; tx += 1) {
93
+ const token = ty * cols + tx;
94
+ for (let o = 0; o < dim; o += 1) {
95
+ let sum = bias === null ? 0 : bias[o];
96
+ for (let c = 0; c < cin; c += 1) {
97
+ for (let ky = 0; ky < patch; ky += 1) {
98
+ for (let kx = 0; kx < patch; kx += 1) {
99
+ sum +=
100
+ image[(c * h + ty * patch + ky) * w + tx * patch + kx] *
101
+ weight[((o * cin + c) * patch + ky) * patch + kx];
102
+ }
103
+ }
104
+ }
105
+ out[token * dim + o] = sum;
106
+ }
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * `out[channels][oh][ow]`, each the largest of its `kernel`-square window at `stride`, with
112
+ * `oh = ⌊(h − kernel)/stride⌋ + 1`: no padding, and a ragged last row or column dropped, as
113
+ * PyTorch's `max_pool2d` does without `ceil_mode`.
114
+ */
115
+ export function maxPool2d(out, input, channels, h, w, kernel, stride) {
116
+ const oh = Math.floor((h - kernel) / stride) + 1;
117
+ const ow = Math.floor((w - kernel) / stride) + 1;
118
+ for (let c = 0; c < channels; c += 1) {
119
+ for (let y = 0; y < oh; y += 1) {
120
+ for (let x = 0; x < ow; x += 1) {
121
+ let largest = Number.NEGATIVE_INFINITY;
122
+ for (let ky = 0; ky < kernel; ky += 1) {
123
+ for (let kx = 0; kx < kernel; kx += 1) {
124
+ largest = Math.max(largest, input[(c * h + y * stride + ky) * w + x * stride + kx]);
125
+ }
126
+ }
127
+ out[(c * oh + y) * ow + x] = largest;
128
+ }
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,2 @@
1
+ import { type Operator } from './operatorKit.ts';
2
+ export declare const SPATIAL_OPERATORS: readonly (readonly [string, Operator])[];
@@ -0,0 +1,138 @@
1
+ /** The spatial operators' rows: convolution, its transpose, patch embedding, resizing, pooling. */
2
+ import { resize } from './resize.js';
3
+ import { conv2d, convTranspose2d, maxPool2d, patchEmbed } from './spatial.js';
4
+ import { num } from './operatorKit.js';
5
+ /*
6
+ * A resize's `stepHeight` and `stepWidth`: the source pixels one destination pixel covers, where
7
+ * that is not the ratio of the sizes — PyTorch's interpolate handed a scale factor samples by the
8
+ * factor's inverse, and DINOv2's positional embeddings are resized that way. Both or neither.
9
+ */
10
+ function stepOf(attributes) {
11
+ const height = attributes['stepHeight'];
12
+ const width = attributes['stepWidth'];
13
+ return typeof height === 'number' && typeof width === 'number' ? [height, width] : undefined;
14
+ }
15
+ /* A resize's mode, bilinear when none is named, and undefined for one the runtime lacks. */
16
+ function modeOf(attributes) {
17
+ const mode = attributes['mode'] ?? 'bilinear';
18
+ return mode === 'bilinear' || mode === 'bicubic' || mode === 'nearest' ? mode : undefined;
19
+ }
20
+ export const SPATIAL_OPERATORS = [
21
+ [
22
+ 'conv2d',
23
+ {
24
+ ranks: [3, 4, 1],
25
+ arity: [2, 3],
26
+ shape: ([x, w], attributes) => {
27
+ const [c, h, width] = x;
28
+ const [o, ci, kh, kw] = w;
29
+ const groups = num(attributes, 'groups', 1);
30
+ if (c % groups !== 0)
31
+ return `x's ${c} channels do not split into ${groups} groups`;
32
+ if (o % groups !== 0)
33
+ return `the weight's ${o} outputs do not split into ${groups} groups`;
34
+ if (c !== ci * groups)
35
+ return `x has ${c} channels and the weight takes ${ci * groups}`;
36
+ const stride = num(attributes, 'stride', 1);
37
+ const padding = num(attributes, 'padding', 0);
38
+ return [
39
+ o,
40
+ Math.floor((h + 2 * padding - kh) / stride) + 1,
41
+ Math.floor((width + 2 * padding - kw) / stride) + 1,
42
+ ];
43
+ },
44
+ evaluate: ([x, w, b], [xs, ws], attributes, out) => {
45
+ const [c, h, width] = xs;
46
+ const [o, , kh, kw] = ws;
47
+ conv2d(out, x, c, h, width, w, b ?? null, o, kh, kw, num(attributes, 'stride', 1), num(attributes, 'padding', 0), num(attributes, 'groups', 1));
48
+ },
49
+ },
50
+ ],
51
+ [
52
+ 'convTranspose2d',
53
+ {
54
+ ranks: [3, 4, 1],
55
+ arity: [2, 3],
56
+ shape: ([x, w], attributes) => {
57
+ const [c, h, width] = x;
58
+ const [ci, o, kh, kw] = w;
59
+ if (c !== ci)
60
+ return `x has ${c} channels and the weight takes ${ci}`;
61
+ const stride = num(attributes, 'stride', 1);
62
+ const padding = num(attributes, 'padding', 0);
63
+ return [o, (h - 1) * stride - 2 * padding + kh, (width - 1) * stride - 2 * padding + kw];
64
+ },
65
+ evaluate: ([x, w, b], [xs, ws], attributes, out) => {
66
+ const [c, h, width] = xs;
67
+ const [, o, kh, kw] = ws;
68
+ convTranspose2d(out, x, c, h, width, w, b ?? null, o, kh, kw, num(attributes, 'stride', 1), num(attributes, 'padding', 0));
69
+ },
70
+ },
71
+ ],
72
+ [
73
+ 'patchEmbed',
74
+ {
75
+ ranks: [3, 4, 1],
76
+ arity: [2, 3],
77
+ shape: ([x, w], attributes) => {
78
+ const [c, h, width] = x;
79
+ const [dim, ci] = w;
80
+ if (c !== ci)
81
+ return `x has ${c} channels and the weight takes ${ci}`;
82
+ const patch = num(attributes, 'patch');
83
+ return [Math.floor(h / patch) * Math.floor(width / patch), dim];
84
+ },
85
+ evaluate: ([x, w, b], [xs, ws], attributes, out) => {
86
+ const [c, h, width] = xs;
87
+ patchEmbed(out, x, c, h, width, w, b ?? null, ws[0], num(attributes, 'patch'));
88
+ },
89
+ },
90
+ ],
91
+ [
92
+ 'resize',
93
+ {
94
+ ranks: [3],
95
+ arity: [1, 1],
96
+ shape: ([x], attributes) => {
97
+ const stepped = ['stepHeight', 'stepWidth'].filter((name) => name in attributes).length;
98
+ if (stepped === 1)
99
+ return 'a step is given for one axis and not the other';
100
+ if (stepped === 2 && attributes['alignCorners'] === true) {
101
+ return 'a step has no meaning with aligned corners, which fix the corners instead';
102
+ }
103
+ const mode = modeOf(attributes);
104
+ if (mode === undefined)
105
+ return `the runtime has no resize mode "${String(attributes['mode'])}"`;
106
+ if (mode === 'nearest' && attributes['alignCorners'] === true) {
107
+ return 'nearest has no aligned corners, as PyTorch has none';
108
+ }
109
+ return [x[0], num(attributes, 'height'), num(attributes, 'width')];
110
+ },
111
+ evaluate: ([x], [xs], attributes, out) => {
112
+ const [c, h, width] = xs;
113
+ const mode = modeOf(attributes) ?? 'bilinear';
114
+ resize(out, x, c, h, width, num(attributes, 'height'), num(attributes, 'width'), mode, attributes['alignCorners'] === true, stepOf(attributes));
115
+ },
116
+ },
117
+ ],
118
+ [
119
+ 'maxPool2d',
120
+ {
121
+ ranks: [3],
122
+ arity: [1, 1],
123
+ shape: ([x], attributes) => {
124
+ const [c, h, w] = x;
125
+ const kernel = num(attributes, 'kernel');
126
+ const stride = num(attributes, 'stride', kernel);
127
+ if (kernel > h || kernel > w)
128
+ return `a window of ${kernel} does not fit ${h} × ${w}`;
129
+ return [c, Math.floor((h - kernel) / stride) + 1, Math.floor((w - kernel) / stride) + 1];
130
+ },
131
+ evaluate: ([x], [xs], attributes, out) => {
132
+ const [c, h, w] = xs;
133
+ const kernel = num(attributes, 'kernel');
134
+ maxPool2d(out, x, c, h, w, kernel, num(attributes, 'stride', kernel));
135
+ },
136
+ },
137
+ ],
138
+ ];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Content addressing for latent tiles: the same surface stored once however many assets use it.
3
+ *
4
+ * **One mechanism doing three jobs**, which is why it earns its own module rather than being a
5
+ * detail of the encoder. The hash is the deduplication key, the streaming unit and the cache key —
6
+ * so a tile that arrives for one material is already resident for the other thirty-nine that share
7
+ * it.
8
+ *
9
+ * **No seed, no platform dependency, and nothing that varies per process.** A hash that changes
10
+ * between runs makes the baker non-reproducible, which breaks every content comparison downstream
11
+ * and does it silently, because the output is still valid — just different.
12
+ */
13
+ /** FNV-1a over 64 bits, carried as two 32-bit halves because JavaScript has no 64-bit integer. */
14
+ export declare function hashTile(bytes: Uint8Array, offset?: number, length?: number): string;
15
+ export interface TileIndex {
16
+ /** Slot per hash. */
17
+ slots: Map<string, number>;
18
+ /** The bytes each slot holds. */
19
+ bytes: Uint8Array[];
20
+ }
21
+ export declare function createTileIndex(): TileIndex;
22
+ /**
23
+ * Give this tile a slot, reusing the one its content already has.
24
+ *
25
+ * Returns the slot. Two calls with identical content return the same slot and store one copy,
26
+ * which is the whole of the deduplication.
27
+ */
28
+ export declare function internTile(index: TileIndex, hash: string, bytes: Uint8Array): number;
29
+ export declare function tileSlotCount(index: TileIndex): number;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Content addressing for latent tiles: the same surface stored once however many assets use it.
3
+ *
4
+ * **One mechanism doing three jobs**, which is why it earns its own module rather than being a
5
+ * detail of the encoder. The hash is the deduplication key, the streaming unit and the cache key —
6
+ * so a tile that arrives for one material is already resident for the other thirty-nine that share
7
+ * it.
8
+ *
9
+ * **No seed, no platform dependency, and nothing that varies per process.** A hash that changes
10
+ * between runs makes the baker non-reproducible, which breaks every content comparison downstream
11
+ * and does it silently, because the output is still valid — just different.
12
+ */
13
+ /** FNV-1a over 64 bits, carried as two 32-bit halves because JavaScript has no 64-bit integer. */
14
+ export function hashTile(bytes, offset = 0, length = bytes.length - offset) {
15
+ let lo = 0x84222325;
16
+ let hi = 0xcbf29ce4;
17
+ for (let i = 0; i < length; i += 1) {
18
+ lo ^= bytes[offset + i];
19
+ /* 64-bit multiply by the FNV prime 0x100000001b3, split across the two halves. */
20
+ const loLow = lo & 0xffff;
21
+ const loHigh = lo >>> 16;
22
+ const l0 = loLow * 0x01b3;
23
+ const l1 = loHigh * 0x01b3 + (l0 >>> 16);
24
+ const newLo = ((l1 << 16) | (l0 & 0xffff)) >>> 0;
25
+ hi = (Math.imul(hi, 0x01b3) + Math.imul(lo, 0x0100) + (l1 >>> 16)) >>> 0;
26
+ lo = newLo;
27
+ }
28
+ return (hi >>> 0).toString(16).padStart(8, '0') + (lo >>> 0).toString(16).padStart(8, '0');
29
+ }
30
+ export function createTileIndex() {
31
+ return { slots: new Map(), bytes: [] };
32
+ }
33
+ /**
34
+ * Give this tile a slot, reusing the one its content already has.
35
+ *
36
+ * Returns the slot. Two calls with identical content return the same slot and store one copy,
37
+ * which is the whole of the deduplication.
38
+ */
39
+ export function internTile(index, hash, bytes) {
40
+ const existing = index.slots.get(hash);
41
+ if (existing !== undefined)
42
+ return existing;
43
+ const slot = index.bytes.length;
44
+ index.slots.set(hash, slot);
45
+ index.bytes.push(bytes);
46
+ return slot;
47
+ }
48
+ export function tileSlotCount(index) {
49
+ return index.bytes.length;
50
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Time as a sampling argument, which is what makes an animated texture replay-exact.
3
+ *
4
+ * **`t` is the caller's, from the simulation's clock, and is never read from a platform.** Every
5
+ * other engine's animated texture reads a wall clock, so replaying the same simulation shows
6
+ * different texels — which matters for a rollback session, for a deterministic capture, and for
7
+ * every automated visual comparison this repository runs.
8
+ *
9
+ * **Latent interpolation is why a long animation is nearly free.** A sixty-frame animation shares
10
+ * one latent and moves only the time slice, so it costs a little more than one frame rather than
11
+ * sixty times one.
12
+ */
13
+ /** Which frame of a flipbook this time lands on. Never negative, never past the end. */
14
+ export declare function flipbookFrame(t: number, frames: number, fps: number, loop: boolean): number;
15
+ export interface LerpWeights {
16
+ a: number;
17
+ b: number;
18
+ mix: number;
19
+ }
20
+ /**
21
+ * The two keys surrounding this time, and how far between them it sits.
22
+ *
23
+ * At or past the end it lands on the last key with no mix, rather than wrapping — a caller that
24
+ * wants looping says so by wrapping `t` before calling.
25
+ */
26
+ export declare function latentLerpWeights(t: number, keyCount: number, duration: number, out: LerpWeights): void;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Time as a sampling argument, which is what makes an animated texture replay-exact.
3
+ *
4
+ * **`t` is the caller's, from the simulation's clock, and is never read from a platform.** Every
5
+ * other engine's animated texture reads a wall clock, so replaying the same simulation shows
6
+ * different texels — which matters for a rollback session, for a deterministic capture, and for
7
+ * every automated visual comparison this repository runs.
8
+ *
9
+ * **Latent interpolation is why a long animation is nearly free.** A sixty-frame animation shares
10
+ * one latent and moves only the time slice, so it costs a little more than one frame rather than
11
+ * sixty times one.
12
+ */
13
+ /** Which frame of a flipbook this time lands on. Never negative, never past the end. */
14
+ export function flipbookFrame(t, frames, fps, loop) {
15
+ if (frames <= 0)
16
+ return 0;
17
+ const raw = Math.floor(t * fps);
18
+ if (loop) {
19
+ /* Modulo that stays non-negative for negative time, which `%` alone does not. */
20
+ return ((raw % frames) + frames) % frames;
21
+ }
22
+ return Math.min(frames - 1, Math.max(0, raw));
23
+ }
24
+ /**
25
+ * The two keys surrounding this time, and how far between them it sits.
26
+ *
27
+ * At or past the end it lands on the last key with no mix, rather than wrapping — a caller that
28
+ * wants looping says so by wrapping `t` before calling.
29
+ */
30
+ export function latentLerpWeights(t, keyCount, duration, out) {
31
+ if (keyCount <= 1 || duration <= 0) {
32
+ out.a = 0;
33
+ out.b = 0;
34
+ out.mix = 0;
35
+ return;
36
+ }
37
+ const spans = keyCount - 1;
38
+ const position = Math.min(spans, Math.max(0, (t / duration) * spans));
39
+ const index = Math.min(spans - 1, Math.floor(position));
40
+ out.a = index;
41
+ out.b = index + 1;
42
+ out.mix = position - index;
43
+ if (position >= spans) {
44
+ out.a = spans - 1;
45
+ out.b = spans;
46
+ out.mix = 1;
47
+ }
48
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@driftengine/texture",
3
+ "version": "4.0.0",
4
+ "description": "DriftTexture: a texture as a compiled, sampled field rather than an image",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "drift-source": "./src/index.ts",
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./package.json": "./package.json",
16
+ "./*": "./*"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "!src/**/*.test.ts",
22
+ "!src/**/*.test.mjs",
23
+ "!src/**/__snapshots__",
24
+ "README.md",
25
+ "LICENSE",
26
+ "NOTICE"
27
+ ],
28
+ "sideEffects": false,
29
+ "peerDependencies": {
30
+ "@driftengine/core": "4.0.0"
31
+ },
32
+ "author": "Drift Technologies",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/drftrun/driftengine.git",
36
+ "directory": "packages/texture"
37
+ },
38
+ "homepage": "https://github.com/drftrun/driftengine#readme",
39
+ "bugs": "https://github.com/drftrun/driftengine/issues",
40
+ "keywords": [
41
+ "driftengine",
42
+ "3d",
43
+ "webgl",
44
+ "webgpu",
45
+ "typescript",
46
+ "texture",
47
+ "compression",
48
+ "materials"
49
+ ],
50
+ "engines": {
51
+ "node": ">=22.12.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "scripts": {
57
+ "prepack": "node ../../scripts/build.mjs @driftengine/texture"
58
+ }
59
+ }