@pixerate/editor 0.115.0 → 0.116.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 (117) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -0
  2. package/dist/animation-editor/index.cjs +54 -54
  3. package/dist/animation-editor/index.d.cts +2 -2
  4. package/dist/animation-editor/index.d.ts +2 -2
  5. package/dist/animation-editor/index.js +2 -2
  6. package/dist/{chunk-DFQEX3AM.cjs → chunk-2UT4GXYA.cjs} +133 -8
  7. package/dist/chunk-2UT4GXYA.cjs.map +1 -0
  8. package/dist/{chunk-VXR7XWMF.js → chunk-7LNZAWSJ.js} +437 -41
  9. package/dist/chunk-7LNZAWSJ.js.map +1 -0
  10. package/dist/{chunk-LKCYANMW.js → chunk-ARQ7PQ6Q.js} +129 -4
  11. package/dist/chunk-ARQ7PQ6Q.js.map +1 -0
  12. package/dist/chunk-COAMRKVI.cjs +159 -0
  13. package/dist/chunk-COAMRKVI.cjs.map +1 -0
  14. package/dist/{chunk-C6C5PDKU.js → chunk-D53KSJUV.js} +3 -322
  15. package/dist/chunk-D53KSJUV.js.map +1 -0
  16. package/dist/chunk-D53VQGEJ.js +3262 -0
  17. package/dist/chunk-D53VQGEJ.js.map +1 -0
  18. package/dist/chunk-EJVDBITD.cjs +501 -0
  19. package/dist/chunk-EJVDBITD.cjs.map +1 -0
  20. package/dist/{chunk-LPLCWZTM.cjs → chunk-FODKPD44.cjs} +555 -159
  21. package/dist/chunk-FODKPD44.cjs.map +1 -0
  22. package/dist/chunk-HOCPSFGA.cjs +3262 -0
  23. package/dist/chunk-HOCPSFGA.cjs.map +1 -0
  24. package/dist/chunk-IPFRWILU.js +1908 -0
  25. package/dist/chunk-IPFRWILU.js.map +1 -0
  26. package/dist/{chunk-FV5AXTSF.cjs → chunk-JSSBHMMU.cjs} +36 -188
  27. package/dist/chunk-JSSBHMMU.cjs.map +1 -0
  28. package/dist/chunk-LF4WOZXZ.js +159 -0
  29. package/dist/chunk-LF4WOZXZ.js.map +1 -0
  30. package/dist/{chunk-26PGXHY7.cjs → chunk-M7O4BJRH.cjs} +36 -36
  31. package/dist/{chunk-26PGXHY7.cjs.map → chunk-M7O4BJRH.cjs.map} +1 -1
  32. package/dist/chunk-N3467VD6.js +501 -0
  33. package/dist/chunk-N3467VD6.js.map +1 -0
  34. package/dist/{chunk-ST7E3NEE.cjs → chunk-PAOWHJ5O.cjs} +18 -337
  35. package/dist/chunk-PAOWHJ5O.cjs.map +1 -0
  36. package/dist/{chunk-XICA4PVO.js → chunk-SEBYNBHW.js} +10 -10
  37. package/dist/chunk-SMJGE7HM.cjs +1908 -0
  38. package/dist/chunk-SMJGE7HM.cjs.map +1 -0
  39. package/dist/{chunk-MMVHBKMM.js → chunk-T2P5TIZ6.js} +2 -2
  40. package/dist/{chunk-FGW5OWJ4.js → chunk-VJUML33I.js} +4 -156
  41. package/dist/chunk-VJUML33I.js.map +1 -0
  42. package/dist/{chunk-I7FQBWMQ.cjs → chunk-ZYZGEILO.cjs} +239 -239
  43. package/dist/{chunk-I7FQBWMQ.cjs.map → chunk-ZYZGEILO.cjs.map} +1 -1
  44. package/dist/export/index.cjs +4 -2
  45. package/dist/export/index.cjs.map +1 -1
  46. package/dist/export/index.js +5 -3
  47. package/dist/{export-BJE8T0YA.d.cts → export-BXxSGBCq.d.cts} +2 -2
  48. package/dist/{export-Con3UKkm.d.ts → export-Dm44YUvS.d.ts} +2 -2
  49. package/dist/export-Dv0-ZVrV.d.cts +501 -0
  50. package/dist/export-Dv0-ZVrV.d.ts +501 -0
  51. package/dist/{retouch-Do6tBs2H.d.ts → flow-BMtHAmg4.d.ts} +561 -2
  52. package/dist/{retouch-Bxkuc0KH.d.cts → flow-COCB42TP.d.cts} +561 -2
  53. package/dist/image-editor/index.cjs +957 -795
  54. package/dist/image-editor/index.cjs.map +1 -1
  55. package/dist/image-editor/index.d.cts +6 -6
  56. package/dist/image-editor/index.d.ts +6 -6
  57. package/dist/image-editor/index.js +180 -18
  58. package/dist/image-editor/index.js.map +1 -1
  59. package/dist/index.cjs +4 -2
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.js +5 -3
  62. package/dist/index.js.map +1 -1
  63. package/dist/kernel-wasm-WYCGNZR4.js +10 -0
  64. package/dist/kernel-wasm-WYCGNZR4.js.map +1 -0
  65. package/dist/kernel-wasm-YPVAEMVN.cjs +10 -0
  66. package/dist/kernel-wasm-YPVAEMVN.cjs.map +1 -0
  67. package/dist/paint/index.cjs +165 -3
  68. package/dist/paint/index.cjs.map +1 -1
  69. package/dist/paint/index.d.cts +225 -3
  70. package/dist/paint/index.d.ts +225 -3
  71. package/dist/paint/index.js +166 -4
  72. package/dist/paint-editor/index.cjs +4690 -845
  73. package/dist/paint-editor/index.cjs.map +1 -1
  74. package/dist/paint-editor/index.d.cts +1525 -277
  75. package/dist/paint-editor/index.d.ts +1525 -277
  76. package/dist/paint-editor/index.js +4560 -715
  77. package/dist/paint-editor/index.js.map +1 -1
  78. package/dist/raster/index.cjs +4 -3
  79. package/dist/raster/index.cjs.map +1 -1
  80. package/dist/raster/index.js +10 -9
  81. package/dist/{svg-BnRj0q8i.d.cts → svg-CVdFc80-.d.cts} +2 -2
  82. package/dist/{svg-CmmBFfGT.d.ts → svg-DlEuhwoX.d.ts} +2 -2
  83. package/dist/{types-1SUQKDAw.d.ts → types-CDLGhAZp.d.ts} +40 -2
  84. package/dist/{types-yINF17Ta.d.cts → types-Tfly4Aya.d.cts} +40 -2
  85. package/dist/vector/index.cjs +1897 -975
  86. package/dist/vector/index.cjs.map +1 -1
  87. package/dist/vector/index.d.cts +292 -32
  88. package/dist/vector/index.d.ts +292 -32
  89. package/dist/vector/index.js +928 -6
  90. package/dist/vector/index.js.map +1 -1
  91. package/dist/video-editor/index.cjs +502 -2165
  92. package/dist/video-editor/index.cjs.map +1 -1
  93. package/dist/video-editor/index.d.cts +3 -501
  94. package/dist/video-editor/index.d.ts +3 -501
  95. package/dist/video-editor/index.js +151 -1814
  96. package/dist/video-editor/index.js.map +1 -1
  97. package/package.json +1 -1
  98. package/skills/pixerate-image-editor/references/photoshop-files.md +7 -3
  99. package/skills/pixerate-vector/SKILL.md +3 -0
  100. package/dist/chunk-C6C5PDKU.js.map +0 -1
  101. package/dist/chunk-DFQEX3AM.cjs.map +0 -1
  102. package/dist/chunk-FGW5OWJ4.js.map +0 -1
  103. package/dist/chunk-FV5AXTSF.cjs.map +0 -1
  104. package/dist/chunk-GX332W2C.js +0 -181
  105. package/dist/chunk-GX332W2C.js.map +0 -1
  106. package/dist/chunk-LKCYANMW.js.map +0 -1
  107. package/dist/chunk-LPLCWZTM.cjs.map +0 -1
  108. package/dist/chunk-MUUXNRU4.cjs +0 -181
  109. package/dist/chunk-MUUXNRU4.cjs.map +0 -1
  110. package/dist/chunk-ST7E3NEE.cjs.map +0 -1
  111. package/dist/chunk-TCKIXI46.cjs +0 -1136
  112. package/dist/chunk-TCKIXI46.cjs.map +0 -1
  113. package/dist/chunk-UBIXVXPR.js +0 -1136
  114. package/dist/chunk-UBIXVXPR.js.map +0 -1
  115. package/dist/chunk-VXR7XWMF.js.map +0 -1
  116. /package/dist/{chunk-XICA4PVO.js.map → chunk-SEBYNBHW.js.map} +0 -0
  117. /package/dist/{chunk-MMVHBKMM.js.map → chunk-T2P5TIZ6.js.map} +0 -0
@@ -0,0 +1,3262 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9;
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+ var _chunkTFOPHZ3Ucjs = require('./chunk-TFOPHZ3U.cjs');
16
+
17
+ // src/paint/brush.ts
18
+ var DEFAULT_BRUSH = {
19
+ size: 20,
20
+ hardness: 1,
21
+ spacing: 0.1,
22
+ angle: 0,
23
+ roundness: 1,
24
+ opacity: 1,
25
+ flow: 1,
26
+ smoothing: 0,
27
+ pressureSize: true,
28
+ pressureOpacity: false,
29
+ blend: "normal"
30
+ };
31
+ var MAX_BRUSH_SIZE = 2500;
32
+ function normalizeBrush(raw = {}) {
33
+ const num = (v, lo, hi, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : d;
34
+ return {
35
+ size: num(raw.size, 1, MAX_BRUSH_SIZE, DEFAULT_BRUSH.size),
36
+ hardness: num(raw.hardness, 0, 1, DEFAULT_BRUSH.hardness),
37
+ spacing: num(raw.spacing, 0.01, 10, DEFAULT_BRUSH.spacing),
38
+ angle: num(raw.angle, -360, 360, DEFAULT_BRUSH.angle),
39
+ roundness: num(raw.roundness, 0.01, 1, DEFAULT_BRUSH.roundness),
40
+ opacity: num(raw.opacity, 0, 1, DEFAULT_BRUSH.opacity),
41
+ flow: num(raw.flow, 0, 1, DEFAULT_BRUSH.flow),
42
+ smoothing: num(raw.smoothing, 0, 0.95, DEFAULT_BRUSH.smoothing),
43
+ pressureSize: typeof raw.pressureSize === "boolean" ? raw.pressureSize : DEFAULT_BRUSH.pressureSize,
44
+ pressureOpacity: typeof raw.pressureOpacity === "boolean" ? raw.pressureOpacity : DEFAULT_BRUSH.pressureOpacity,
45
+ blend: _nullishCoalesce(raw.blend, () => ( "normal"))
46
+ };
47
+ }
48
+ var TIP_SHAPE = [
49
+ [0, 0.664],
50
+ [0.14, 0.58],
51
+ [0.28, 0.5],
52
+ [0.4, 0.44],
53
+ [0.52, 0.36],
54
+ [0.63, 0.29],
55
+ [0.7, 0.24],
56
+ [0.78, 0.19],
57
+ [0.84, 0.15],
58
+ [0.89, 0.11],
59
+ [1, 0]
60
+ ];
61
+ var TIP_EDGE_PX = 1.2;
62
+ var SMALL_TIP_RADIUS = 3;
63
+ var SUBPIXEL = [-0.375, -0.125, 0.125, 0.375];
64
+ function tipShape(radius, hardness) {
65
+ const h = (Number.isNaN(hardness) ? 0 : Math.min(1, Math.max(0, hardness))) * 10;
66
+ const i = Math.min(9, Math.floor(h));
67
+ const t = h - i;
68
+ const a = TIP_SHAPE[i];
69
+ const b = TIP_SHAPE[i + 1];
70
+ const r0 = a[0] + (b[0] - a[0]) * t;
71
+ const s = a[1] + (b[1] - a[1]) * t;
72
+ const r = Number.isFinite(radius) ? Math.max(0, radius) : 0;
73
+ const edge = Math.max(0.25, Math.min(TIP_EDGE_PX, 0.3 * r));
74
+ return [Math.max(0, Math.min(r0 * r, r - edge)), Math.max(1e-3, s * r, edge)];
75
+ }
76
+ function softExp(u) {
77
+ let g = Math.min(1, Math.max(0, 1 - u / 64));
78
+ for (let k = 0; k < 6; k++) g *= g;
79
+ return g;
80
+ }
81
+ function tipFalloff(d, radius, hardness) {
82
+ const [r0, s] = tipShape(radius, hardness);
83
+ if (d <= r0) return 1;
84
+ const x = (d - r0) / s;
85
+ return softExp(x * x);
86
+ }
87
+ function tipReach(radius, hardness) {
88
+ const [r0, s] = tipShape(radius, hardness);
89
+ return r0 + 2.6 * s;
90
+ }
91
+ function dabCoverage(d, radius, hardness) {
92
+ if (Number.isNaN(d) || d >= tipReach(radius, hardness) + 0.5) return 0;
93
+ return tipFalloff(d, radius, hardness);
94
+ }
95
+ function smoothPoints(points, amount) {
96
+ const a = 1 - Math.min(0.95, Math.max(0, amount));
97
+ if (a >= 1 || points.length < 3) return points.slice();
98
+ const out = [];
99
+ let cur = null;
100
+ for (const p of points) {
101
+ const n = cur ? { ...p, x: cur.x + (p.x - cur.x) * a, y: cur.y + (p.y - cur.y) * a } : p;
102
+ out.push(n);
103
+ cur = n;
104
+ }
105
+ out[out.length - 1] = points[points.length - 1];
106
+ return out;
107
+ }
108
+ var pressureOf = (p) => typeof p.pressure === "number" && Number.isFinite(p.pressure) ? Math.min(1, Math.max(0, p.pressure)) : 1;
109
+ var DabWalker = (_class = class {
110
+ constructor(brush) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);
111
+ this.brush = brush;
112
+ }
113
+
114
+ __init() {this.last = null}
115
+ __init2() {this.nextAt = 0}
116
+ __init3() {this.pendingFirst = null}
117
+ diameter(p) {
118
+ return this.brush.pressureSize ? this.brush.size * pressureOf(p) : this.brush.size;
119
+ }
120
+ stepLen(p) {
121
+ return Math.max(0.5, Math.max(1, this.diameter(p)) * Math.max(0.01, this.brush.spacing));
122
+ }
123
+ dab(p) {
124
+ const b = this.brush;
125
+ return {
126
+ x: p.x,
127
+ y: p.y,
128
+ radius: Math.max(0.5, this.diameter(p) / 2),
129
+ alpha: Math.min(1, Math.max(0, b.flow)),
130
+ opacity: b.pressureOpacity ? pressureOf(p) : 1,
131
+ angle: b.angle * Math.PI / 180,
132
+ roundness: Math.min(1, Math.max(0.01, b.roundness))
133
+ };
134
+ }
135
+ push(p, out) {
136
+ const a = this.last;
137
+ if (!a) {
138
+ this.last = p;
139
+ this.pendingFirst = p;
140
+ this.nextAt = this.stepLen(p);
141
+ return;
142
+ }
143
+ if (this.pendingFirst) {
144
+ out.push(this.dab(this.pendingFirst));
145
+ this.pendingFirst = null;
146
+ }
147
+ const dx = p.x - a.x;
148
+ const dy = p.y - a.y;
149
+ const len = Math.hypot(dx, dy);
150
+ if (len > 1e-9) {
151
+ while (this.nextAt <= len + 1e-9) {
152
+ const f = Math.min(1, this.nextAt / len);
153
+ const q = {
154
+ x: a.x + dx * f,
155
+ y: a.y + dy * f,
156
+ pressure: pressureOf(a) + (pressureOf(p) - pressureOf(a)) * f
157
+ };
158
+ out.push(this.dab(q));
159
+ this.nextAt += this.stepLen(q);
160
+ }
161
+ this.nextAt -= len;
162
+ }
163
+ this.last = p;
164
+ }
165
+ finish(out) {
166
+ if (this.pendingFirst) {
167
+ out.push(this.dab(this.pendingFirst));
168
+ this.pendingFirst = null;
169
+ }
170
+ }
171
+ }, _class);
172
+ function walkDabs(points, brush) {
173
+ const w = new DabWalker(brush);
174
+ const out = [];
175
+ for (const p of smoothPoints(points, brush.smoothing)) w.push(p, out);
176
+ w.finish(out);
177
+ return out;
178
+ }
179
+ function dabRect(d, hardness) {
180
+ const ro = Math.min(1, Math.max(0.5 / d.radius, d.roundness));
181
+ const rm = Math.max(0.5, d.radius * ro);
182
+ const r = Math.max(rm, tipReach(rm, hardness)) / ro + 1.5;
183
+ const x0 = Math.floor(d.x - r);
184
+ const y0 = Math.floor(d.y - r);
185
+ return { x: x0, y: y0, width: Math.ceil(d.x + r) - x0, height: Math.ceil(d.y + r) - y0 };
186
+ }
187
+ function rasterizeDab(d, hardness, rect, out) {
188
+ const w = rect.width;
189
+ const h = rect.height;
190
+ out.fill(0, 0, w * h);
191
+ const r = d.radius;
192
+ const ro = Math.min(1, Math.max(0.5 / r, d.roundness));
193
+ const rm = Math.max(0.5, r * ro);
194
+ const reach = Math.max(rm, tipReach(rm, hardness));
195
+ const sn = Math.sin(d.angle);
196
+ const cs = Math.cos(d.angle);
197
+ const toTip = (dx, dy) => {
198
+ const ux = dx;
199
+ const uy = -dy;
200
+ return [ux * cs + uy * sn, -ux * sn + uy * cs];
201
+ };
202
+ const small = rm < SMALL_TIP_RADIUS;
203
+ const reach2 = (reach + (small ? 0.75 : 0.5)) ** 2;
204
+ for (let yy = 0; yy < h; yy++) {
205
+ const dy = rect.y + yy + 0.5 - d.y;
206
+ for (let xx = 0; xx < w; xx++) {
207
+ const dx = rect.x + xx + 0.5 - d.x;
208
+ const [u, v] = toTip(dx, dy);
209
+ const d2 = (u * ro) ** 2 + v * v;
210
+ if (d2 >= reach2) continue;
211
+ let val;
212
+ if (small) {
213
+ let sum = 0;
214
+ for (const sy of SUBPIXEL) {
215
+ for (const sx of SUBPIXEL) {
216
+ const [su, sv] = toTip(dx + sx, dy + sy);
217
+ sum += tipFalloff(Math.sqrt((su * ro) ** 2 + sv * sv), rm, hardness);
218
+ }
219
+ }
220
+ val = sum / 16;
221
+ } else {
222
+ val = dabCoverage(Math.sqrt(d2), rm, hardness);
223
+ }
224
+ if (val > 0) out[yy * w + xx] = val * d.alpha;
225
+ }
226
+ }
227
+ }
228
+ var TILE_PIXELS = _chunkTFOPHZ3Ucjs.TILE * _chunkTFOPHZ3Ucjs.TILE;
229
+ var CoverageMap = (_class2 = class {constructor() { _class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this); }
230
+ __init4() {this.tiles = /* @__PURE__ */ new Map()}
231
+ __init5() {this.bounds = null}
232
+ __init6() {this.dirty = /* @__PURE__ */ new Set()}
233
+ get(x, y) {
234
+ const tx = Math.floor(x / _chunkTFOPHZ3Ucjs.TILE);
235
+ const ty = Math.floor(y / _chunkTFOPHZ3Ucjs.TILE);
236
+ const t = this.tiles.get(_chunkTFOPHZ3Ucjs.tileKey.call(void 0, tx, ty));
237
+ return t ? t[(y - ty * _chunkTFOPHZ3Ucjs.TILE) * _chunkTFOPHZ3Ucjs.TILE + (x - tx * _chunkTFOPHZ3Ucjs.TILE)] : 0;
238
+ }
239
+ /** Builds up `vals` (over `rect`) with flow towards `ceil`: `c ← c + v·(ceil − c)`. */
240
+ accumulate(rect, vals, ceil) {
241
+ if (rect.width <= 0 || rect.height <= 0) return;
242
+ const w = rect.width;
243
+ const tx0 = Math.floor(rect.x / _chunkTFOPHZ3Ucjs.TILE);
244
+ const ty0 = Math.floor(rect.y / _chunkTFOPHZ3Ucjs.TILE);
245
+ const tx1 = Math.floor((rect.x + rect.width - 1) / _chunkTFOPHZ3Ucjs.TILE);
246
+ const ty1 = Math.floor((rect.y + rect.height - 1) / _chunkTFOPHZ3Ucjs.TILE);
247
+ let touched = false;
248
+ for (let ty = ty0; ty <= ty1; ty++) {
249
+ for (let tx = tx0; tx <= tx1; tx++) {
250
+ const x0 = Math.max(rect.x, tx * _chunkTFOPHZ3Ucjs.TILE);
251
+ const y0 = Math.max(rect.y, ty * _chunkTFOPHZ3Ucjs.TILE);
252
+ const x1 = Math.min(rect.x + rect.width, tx * _chunkTFOPHZ3Ucjs.TILE + _chunkTFOPHZ3Ucjs.TILE);
253
+ const y1 = Math.min(rect.y + rect.height, ty * _chunkTFOPHZ3Ucjs.TILE + _chunkTFOPHZ3Ucjs.TILE);
254
+ let tile;
255
+ for (let y = y0; y < y1; y++) {
256
+ const row = (y - rect.y) * w;
257
+ const trow = (y - ty * _chunkTFOPHZ3Ucjs.TILE) * _chunkTFOPHZ3Ucjs.TILE;
258
+ for (let x = x0; x < x1; x++) {
259
+ const v = vals[row + (x - rect.x)];
260
+ if (v <= 0) continue;
261
+ if (!tile) {
262
+ const key = _chunkTFOPHZ3Ucjs.tileKey.call(void 0, tx, ty);
263
+ tile = this.tiles.get(key);
264
+ if (!tile) {
265
+ tile = new Float32Array(TILE_PIXELS);
266
+ this.tiles.set(key, tile);
267
+ }
268
+ this.dirty.add(key);
269
+ touched = true;
270
+ }
271
+ const ti = trow + (x - tx * _chunkTFOPHZ3Ucjs.TILE);
272
+ const c = tile[ti];
273
+ if (ceil > c) tile[ti] = c + v * (ceil - c);
274
+ }
275
+ }
276
+ }
277
+ }
278
+ if (touched) this.bounds = unionRect(this.bounds, rect);
279
+ }
280
+ /** Tiles changed since the last call. */
281
+ takeDirty() {
282
+ const out = [...this.dirty];
283
+ this.dirty.clear();
284
+ return out;
285
+ }
286
+ }, _class2);
287
+ function unionRect(a, b) {
288
+ if (!a) return { ...b };
289
+ const x = Math.min(a.x, b.x);
290
+ const y = Math.min(a.y, b.y);
291
+ return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
292
+ }
293
+ var StrokeCoverage = (_class3 = class {
294
+ constructor(brush) {;_class3.prototype.__init7.call(this);_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);
295
+ this.brush = brush;
296
+ this.walker = new DabWalker(brush);
297
+ this.smoothing = brush.smoothing;
298
+ }
299
+
300
+ __init7() {this.coverage = new CoverageMap()}
301
+
302
+
303
+ __init8() {this.smoothPos = null}
304
+ __init9() {this.buf = new Float32Array(0)}
305
+ __init10() {this.dabCount = 0}
306
+ /** Feeds input points; returns the document rectangles the new dabs touched. */
307
+ push(points) {
308
+ const dabs = [];
309
+ for (const p of points) this.walker.push(this.smooth(p), dabs);
310
+ return this.paint(dabs);
311
+ }
312
+ /** Ends the stroke (a single click still paints one dab). */
313
+ finish(last) {
314
+ const dabs = [];
315
+ if (last && this.smoothPos && (this.smoothPos.x !== last.x || this.smoothPos.y !== last.y)) this.walker.push(last, dabs);
316
+ this.walker.finish(dabs);
317
+ return this.paint(dabs);
318
+ }
319
+ smooth(p) {
320
+ const a = 1 - Math.min(0.95, Math.max(0, this.smoothing));
321
+ const cur = this.smoothPos;
322
+ const n = cur && a < 1 ? { ...p, x: cur.x + (p.x - cur.x) * a, y: cur.y + (p.y - cur.y) * a } : p;
323
+ this.smoothPos = n;
324
+ return n;
325
+ }
326
+ paint(dabs) {
327
+ const rects = [];
328
+ for (const d of dabs) {
329
+ const rect = dabRect(d, this.brush.hardness);
330
+ const n = rect.width * rect.height;
331
+ if (this.buf.length < n) this.buf = new Float32Array(n);
332
+ const vals = this.buf.subarray(0, n);
333
+ rasterizeDab(d, this.brush.hardness, rect, vals);
334
+ this.coverage.accumulate(rect, vals, d.opacity);
335
+ rects.push(rect);
336
+ this.dabCount++;
337
+ }
338
+ return rects;
339
+ }
340
+ }, _class3);
341
+ function applyStrokeToTile(coverage, tileData, channels, tx, ty, s, options = {}) {
342
+ const out = options.into ? new Uint8ClampedArray(options.into) : new Uint8ClampedArray(tileData);
343
+ const ox = tx * _chunkTFOPHZ3Ucjs.TILE + s.offset.x;
344
+ const oy = ty * _chunkTFOPHZ3Ucjs.TILE + s.offset.y;
345
+ let x0 = 0;
346
+ let y0 = 0;
347
+ let x1 = _chunkTFOPHZ3Ucjs.TILE;
348
+ let y1 = _chunkTFOPHZ3Ucjs.TILE;
349
+ if (options.region) {
350
+ const r = options.region;
351
+ x0 = Math.max(0, Math.floor(r.x) - ox);
352
+ y0 = Math.max(0, Math.floor(r.y) - oy);
353
+ x1 = Math.min(_chunkTFOPHZ3Ucjs.TILE, Math.ceil(r.x + r.width) - ox);
354
+ y1 = Math.min(_chunkTFOPHZ3Ucjs.TILE, Math.ceil(r.y + r.height) - oy);
355
+ if (x1 <= x0 || y1 <= y0) return out;
356
+ if (options.into) {
357
+ for (let y = y0; y < y1; y++) out.set(tileData.subarray((y * _chunkTFOPHZ3Ucjs.TILE + x0) * channels, (y * _chunkTFOPHZ3Ucjs.TILE + x1) * channels), (y * _chunkTFOPHZ3Ucjs.TILE + x0) * channels);
358
+ }
359
+ }
360
+ const sel = s.selection;
361
+ const cb = [0, 0, 0, 0];
362
+ const cs = [s.color[0] / 255, s.color[1] / 255, s.color[2] / 255, s.color[3] / 255];
363
+ const bg = [s.background[0] / 255, s.background[1] / 255, s.background[2] / 255, s.background[3] / 255];
364
+ const aligned = s.offset.x % _chunkTFOPHZ3Ucjs.TILE === 0 && s.offset.y % _chunkTFOPHZ3Ucjs.TILE === 0;
365
+ const covTile = aligned ? coverage.tiles.get(_chunkTFOPHZ3Ucjs.tileKey.call(void 0, Math.floor(ox / _chunkTFOPHZ3Ucjs.TILE), Math.floor(oy / _chunkTFOPHZ3Ucjs.TILE))) : void 0;
366
+ if (aligned && !covTile) return out;
367
+ for (let y = y0; y < y1; y++) {
368
+ const dy = oy + y;
369
+ for (let x = x0; x < x1; x++) {
370
+ const dx = ox + x;
371
+ const c = covTile ? covTile[y * _chunkTFOPHZ3Ucjs.TILE + x] : coverage.get(dx, dy);
372
+ if (c <= 0) continue;
373
+ let k = c * s.opacity;
374
+ if (sel) {
375
+ if (dx < 0 || dy < 0 || dx >= sel.width || dy >= sel.height) continue;
376
+ k *= sel.data[dy * sel.width + dx] / 255;
377
+ }
378
+ if (k > 1) k = 1;
379
+ if (k <= 0) continue;
380
+ const p = y * _chunkTFOPHZ3Ucjs.TILE + x;
381
+ if (channels === 1) {
382
+ const target = s.mode === "erase" ? s.background[0] : s.color[0];
383
+ out[p] = Math.round(out[p] + (target - out[p]) * k);
384
+ continue;
385
+ }
386
+ const i = p * 4;
387
+ if (s.mode === "erase" && !s.lockTransparency) {
388
+ out[i + 3] = Math.round(out[i + 3] * (1 - k));
389
+ continue;
390
+ }
391
+ if (s.lockTransparency && out[i + 3] === 0) continue;
392
+ const src = s.mode === "erase" ? bg : cs;
393
+ if (s.blend === "pigment") {
394
+ const bytes = s.mode === "erase" ? s.background : s.color;
395
+ const as = src[3] * k;
396
+ if (!(as > 0)) continue;
397
+ const ao = as + out[i + 3] / 255 * (1 - as);
398
+ _chunkTFOPHZ3Ucjs.mixPigment8.call(void 0, out[i], out[i + 1], out[i + 2], bytes[0], bytes[1], bytes[2], as / ao, cb);
399
+ out[i] = Math.round(cb[0] * 255);
400
+ out[i + 1] = Math.round(cb[1] * 255);
401
+ out[i + 2] = Math.round(cb[2] * 255);
402
+ if (!s.lockTransparency) out[i + 3] = Math.round(ao * 255);
403
+ continue;
404
+ }
405
+ for (let ch = 0; ch < 4; ch++) cb[ch] = out[i + ch] / 255;
406
+ const o = _chunkTFOPHZ3Ucjs.blendPixel.call(void 0, s.blend, cb, src, k);
407
+ out[i] = Math.round(o[0] * 255);
408
+ out[i + 1] = Math.round(o[1] * 255);
409
+ out[i + 2] = Math.round(o[2] * 255);
410
+ if (!s.lockTransparency) out[i + 3] = Math.round(o[3] * 255);
411
+ }
412
+ }
413
+ return out;
414
+ }
415
+ function coverageTileCoord(key) {
416
+ return _chunkTFOPHZ3Ucjs.tileCoord.call(void 0, key);
417
+ }
418
+
419
+ // src/paint/exchange.ts
420
+ var MIXING_MODELS = ["rgb", "pigment"];
421
+ var EMPTY_MIXER_LOAD = { reservoir: null, amount: 0, pickup: null };
422
+ var MIXER_PICKUP_RATE = 0.25;
423
+ var MIXER_DRAIN = 0.08;
424
+ function exchangeRates(pickup, mix, load, mixing) {
425
+ return { pickup: MIXER_PICKUP_RATE * pickup, mix: mix * pickup, drain: MIXER_DRAIN * (1 - load), mixing };
426
+ }
427
+ var PaintExchange = (_class4 = class {
428
+ constructor(rates, load) {;_class4.prototype.__init11.call(this);
429
+ this.rates = rates;
430
+ this.reservoir = load.reservoir ? [load.reservoir[0], load.reservoir[1], load.reservoir[2]] : null;
431
+ this.amount = this.reservoir ? Math.min(1, Math.max(0, load.amount)) : 0;
432
+ this.well = load.pickup ? [load.pickup[0], load.pickup[1], load.pickup[2]] : null;
433
+ this.pig = rates.mixing === "pigment" ? { res: _chunkTFOPHZ3Ucjs.createPigment.call(void 0, _nullishCoalesce(this.reservoir, () => ( [0, 0, 0]))), well: _chunkTFOPHZ3Ucjs.createPigment.call(void 0, _nullishCoalesce(this.well, () => ( [0, 0, 0]))), paint: _chunkTFOPHZ3Ucjs.createPigment.call(void 0, [0, 0, 0]), canvas: _chunkTFOPHZ3Ucjs.createPigment.call(void 0, [0, 0, 0]) } : null;
434
+ }
435
+
436
+
437
+
438
+
439
+
440
+ __init11() {this.out = [0, 0, 0]}
441
+ /** What the brush carries now. */
442
+ get load() {
443
+ return { reservoir: this.reservoir ? [...this.reservoir] : null, amount: this.amount, pickup: this.well ? [...this.well] : null };
444
+ }
445
+ /** The colour picked up so far, or `null`. */
446
+ get pickup() {
447
+ return this.well;
448
+ }
449
+ /** The prepared pigment of the last `paint` colour (pigment mixing only), for `deposit`. */
450
+ get paintPigment() {
451
+ return _nullishCoalesce(_optionalChain([this, 'access', _ => _.pig, 'optionalAccess', _2 => _2.paint]), () => ( null));
452
+ }
453
+ /** Recolours the reservoir without refilling it (a multi-coloured brush, per dab). */
454
+ setReservoirColor(r, g, b) {
455
+ if (!this.reservoir) {
456
+ this.reservoir = [r, g, b];
457
+ this.amount = 1;
458
+ } else {
459
+ this.reservoir[0] = r;
460
+ this.reservoir[1] = g;
461
+ this.reservoir[2] = b;
462
+ }
463
+ if (this.pig) _chunkTFOPHZ3Ucjs.setPigmentColor.call(void 0, this.pig.res, r, g, b);
464
+ }
465
+ /** `a` mixed `t` of the way to `b` into `this.out`, by the mixing model. */
466
+ mix(a, pa, b, pb, t) {
467
+ const o = this.out;
468
+ if (pa && pb) _chunkTFOPHZ3Ucjs.lerpPigmentInto.call(void 0, pa, pb, t, o, 0);
469
+ else for (let c = 0; c < 3; c++) o[c] = a[c] + (b[c] - a[c]) * t;
470
+ return o;
471
+ }
472
+ /** `a` mixed `t` of the way to `b` (sRGB 0..1) by the mixing model, into `out`. */
473
+ mixColors(a, b, t, out) {
474
+ if (this.pig) {
475
+ _chunkTFOPHZ3Ucjs.setPigmentColor.call(void 0, this.pig.canvas, a[0], a[1], a[2]);
476
+ _chunkTFOPHZ3Ucjs.setPigmentColor.call(void 0, this.pig.paint, b[0], b[1], b[2]);
477
+ _chunkTFOPHZ3Ucjs.lerpPigmentInto.call(void 0, this.pig.canvas, this.pig.paint, t, out, 0);
478
+ } else for (let c = 0; c < 3; c++) out[c] = a[c] + (b[c] - a[c]) * t;
479
+ return out;
480
+ }
481
+ /** 1. Pick-up: the well moves towards the sampled colour (or takes it, when clean). */
482
+ pickUp(sample) {
483
+ if (!sample || !(this.rates.pickup > 0)) return;
484
+ const pig = this.pig;
485
+ if (!this.well) this.well = [sample.rgb[0], sample.rgb[1], sample.rgb[2]];
486
+ else {
487
+ if (pig) _chunkTFOPHZ3Ucjs.setPigmentColor.call(void 0, pig.canvas, sample.rgb[0], sample.rgb[1], sample.rgb[2]);
488
+ const o = this.mix(this.well, _nullishCoalesce(_optionalChain([pig, 'optionalAccess', _3 => _3.well]), () => ( null)), sample.rgb, _nullishCoalesce(_optionalChain([pig, 'optionalAccess', _4 => _4.canvas]), () => ( null)), this.rates.pickup * sample.contact);
489
+ this.well = [o[0], o[1], o[2]];
490
+ }
491
+ if (pig) _chunkTFOPHZ3Ucjs.setPigmentColor.call(void 0, pig.well, this.well[0], this.well[1], this.well[2]);
492
+ }
493
+ /**
494
+ * 2. The dab's paint: the reservoir mixed with the well, written to `out`. Returns the total
495
+ * weight (≤ 0: nothing to lay down); deposit `min(1, weight)` of it. With pigment mixing,
496
+ * `paintPigment` is the result's pigment afterwards.
497
+ */
498
+ paint(out) {
499
+ const res = this.reservoir;
500
+ const wr = res ? this.amount * (1 - this.rates.mix) : 0;
501
+ const wp = this.well ? this.rates.mix : 0;
502
+ const total = wr + wp;
503
+ if (!(total > 1e-6)) return 0;
504
+ const pig = this.pig;
505
+ if (res && this.well) {
506
+ const m = this.mix(res, _nullishCoalesce(_optionalChain([pig, 'optionalAccess', _5 => _5.res]), () => ( null)), this.well, _nullishCoalesce(_optionalChain([pig, 'optionalAccess', _6 => _6.well]), () => ( null)), wp / total);
507
+ out[0] = m[0];
508
+ out[1] = m[1];
509
+ out[2] = m[2];
510
+ } else {
511
+ const c = _nullishCoalesce(res, () => ( this.well));
512
+ out[0] = c[0];
513
+ out[1] = c[1];
514
+ out[2] = c[2];
515
+ }
516
+ if (pig) _chunkTFOPHZ3Ucjs.setPigmentColor.call(void 0, pig.paint, out[0], out[1], out[2]);
517
+ return total;
518
+ }
519
+ /** 3. The reservoir drains. */
520
+ drain() {
521
+ this.amount *= 1 - this.rates.drain;
522
+ }
523
+ }, _class4);
524
+ var TileMedium = (_class5 = class {
525
+ constructor(target, options = {}) {;_class5.prototype.__init12.call(this);_class5.prototype.__init13.call(this);_class5.prototype.__init14.call(this);_class5.prototype.__init15.call(this);_class5.prototype.__init16.call(this);_class5.prototype.__init17.call(this);_class5.prototype.__init18.call(this);_class5.prototype.__init19.call(this);_class5.prototype.__init20.call(this);_class5.prototype.__init21.call(this);
526
+ this.target = target;
527
+ this.options = options;
528
+ }
529
+
530
+
531
+ // The tile last looked up by `locate` (and whether it came from `writeTile`).
532
+ __init12() {this.tx = NaN}
533
+ __init13() {this.ty = NaN}
534
+ __init14() {this.tile = null}
535
+ __init15() {this.writable = false}
536
+ __init16() {this.canvasPig = _chunkTFOPHZ3Ucjs.createPigment.call(void 0, [0, 0, 0])}
537
+ __init17() {this.paintPig = _chunkTFOPHZ3Ucjs.createPigment.call(void 0, [0, 0, 0])}
538
+ __init18() {this.paintKey = -1}
539
+ __init19() {this.cb = [0, 0, 0, 0]}
540
+ __init20() {this.src = [0, 0, 0, 1]}
541
+ __init21() {this.o = [0, 0, 0]}
542
+ /** Forgets the cached tile (call when another writer may have replaced tiles). */
543
+ reset() {
544
+ this.tx = NaN;
545
+ }
546
+ /**
547
+ * The tile holding document pixel (dx, dy) (`pixels`) and the pixel's byte index in it, or -1
548
+ * when reading an empty tile.
549
+ */
550
+ locate(dx, dy, write) {
551
+ const t = this.target;
552
+ const lx = dx - t.offset.x;
553
+ const ly = dy - t.offset.y;
554
+ const tx = Math.floor(lx / _chunkTFOPHZ3Ucjs.TILE);
555
+ const ty = Math.floor(ly / _chunkTFOPHZ3Ucjs.TILE);
556
+ if (tx !== this.tx || ty !== this.ty || write && !this.writable) {
557
+ this.tx = tx;
558
+ this.ty = ty;
559
+ this.tile = write ? t.writeTile(tx, ty) : t.readTile(tx, ty);
560
+ this.writable = write;
561
+ }
562
+ return this.tile ? ((ly - ty * _chunkTFOPHZ3Ucjs.TILE) * _chunkTFOPHZ3Ucjs.TILE + (lx - tx * _chunkTFOPHZ3Ucjs.TILE)) * 4 : -1;
563
+ }
564
+ /** The tile `locate` last found. */
565
+ get pixels() {
566
+ return this.tile;
567
+ }
568
+ /** Selection coverage at a document pixel, 0..1 (1 without a selection). */
569
+ selectionAt(dx, dy) {
570
+ const sel = this.target.selection;
571
+ if (!sel) return 1;
572
+ if (dx < 0 || dy < 0 || dx >= sel.width || dy >= sel.height) return 0;
573
+ return sel.data[dy * sel.width + dx] / 255;
574
+ }
575
+ sample(rect, cov) {
576
+ const w = rect.width;
577
+ this.tx = NaN;
578
+ let ar = 0;
579
+ let ag = 0;
580
+ let ab = 0;
581
+ let wsum = 0;
582
+ let asum = 0;
583
+ for (let yy = 0; yy < rect.height; yy++) {
584
+ for (let xx = 0; xx < w; xx++) {
585
+ const k = cov[yy * w + xx];
586
+ if (k <= 0) continue;
587
+ wsum += k;
588
+ const i = this.locate(rect.x + xx, rect.y + yy, false);
589
+ if (i < 0) continue;
590
+ const px = this.tile;
591
+ const al = px[i + 3] / 255;
592
+ if (al <= 0) continue;
593
+ const ka = k * al;
594
+ ar += px[i] / 255 * ka;
595
+ ag += px[i + 1] / 255 * ka;
596
+ ab += px[i + 2] / 255 * ka;
597
+ asum += ka;
598
+ }
599
+ }
600
+ if (!(asum > 1e-6)) return null;
601
+ return { rgb: [ar / asum, ag / asum, ab / asum], contact: asum / Math.max(1e-6, wsum) };
602
+ }
603
+ deposit(rect, cov, dep, paint, paintPigment, field = null) {
604
+ const t = this.target;
605
+ const sel = t.selection;
606
+ const w = rect.width;
607
+ const o = this.o;
608
+ const blend = this.options.blend;
609
+ const pigment = blend === void 0 ? paintPigment !== null : blend === "pigment";
610
+ const general = blend !== void 0 && blend !== "normal" && blend !== "pigment";
611
+ let lastKey = -1;
612
+ this.tx = NaN;
613
+ for (let yy = 0; yy < rect.height; yy++) {
614
+ const dy = rect.y + yy;
615
+ for (let xx = 0; xx < w; xx++) {
616
+ const p = yy * w + xx;
617
+ let k = cov[p] * dep;
618
+ if (k <= 0) continue;
619
+ const dx = rect.x + xx;
620
+ if (sel) {
621
+ if (dx < 0 || dy < 0 || dx >= sel.width || dy >= sel.height) continue;
622
+ k *= sel.data[dy * sel.width + dx] / 255;
623
+ if (k <= 0) continue;
624
+ }
625
+ if (k > 1) k = 1;
626
+ if (t.lockTransparency) {
627
+ const ri = this.locate(dx, dy, false);
628
+ if (ri < 0 || this.tile[ri + 3] === 0) continue;
629
+ }
630
+ const i = this.locate(dx, dy, true);
631
+ const px = this.tile;
632
+ const pr = field ? field[p * 3] : paint[0];
633
+ const pg = field ? field[p * 3 + 1] : paint[1];
634
+ const pb = field ? field[p * 3 + 2] : paint[2];
635
+ if (general) {
636
+ const cb = this.cb;
637
+ for (let c = 0; c < 4; c++) cb[c] = px[i + c] / 255;
638
+ const src = this.src;
639
+ src[0] = pr;
640
+ src[1] = pg;
641
+ src[2] = pb;
642
+ const r = _chunkTFOPHZ3Ucjs.blendPixel.call(void 0, blend, cb, src, k);
643
+ px[i] = Math.round(r[0] * 255);
644
+ px[i + 1] = Math.round(r[1] * 255);
645
+ px[i + 2] = Math.round(r[2] * 255);
646
+ if (!t.lockTransparency) px[i + 3] = Math.round(r[3] * 255);
647
+ continue;
648
+ }
649
+ const a = px[i + 3] / 255;
650
+ const na = t.lockTransparency ? a : a + (1 - a) * k;
651
+ const share = t.lockTransparency ? k : na > 0 ? k / na : 1;
652
+ if (a <= 0 || share >= 1) {
653
+ o[0] = pr;
654
+ o[1] = pg;
655
+ o[2] = pb;
656
+ } else if (pigment && field) {
657
+ _chunkTFOPHZ3Ucjs.mixPigment8.call(void 0, px[i], px[i + 1], px[i + 2], Math.round(pr * 255), Math.round(pg * 255), Math.round(pb * 255), share, o);
658
+ } else if (pigment) {
659
+ const key = px[i] << 16 | px[i + 1] << 8 | px[i + 2];
660
+ if (key !== lastKey) {
661
+ _chunkTFOPHZ3Ucjs.setPigmentColor.call(void 0, this.canvasPig, px[i] / 255, px[i + 1] / 255, px[i + 2] / 255);
662
+ lastKey = key;
663
+ }
664
+ let pp = paintPigment;
665
+ if (!pp) {
666
+ const pkey = Math.round(pr * 255) << 16 | Math.round(pg * 255) << 8 | Math.round(pb * 255);
667
+ if (pkey !== this.paintKey) {
668
+ _chunkTFOPHZ3Ucjs.setPigmentColor.call(void 0, this.paintPig, pr, pg, pb);
669
+ this.paintKey = pkey;
670
+ }
671
+ pp = this.paintPig;
672
+ }
673
+ _chunkTFOPHZ3Ucjs.lerpPigmentInto.call(void 0, this.canvasPig, pp, share, o, 0);
674
+ } else {
675
+ o[0] = px[i] / 255 + (pr - px[i] / 255) * share;
676
+ o[1] = px[i + 1] / 255 + (pg - px[i + 1] / 255) * share;
677
+ o[2] = px[i + 2] / 255 + (pb - px[i + 2] / 255) * share;
678
+ }
679
+ px[i] = Math.round(o[0] * 255);
680
+ px[i + 1] = Math.round(o[1] * 255);
681
+ px[i + 2] = Math.round(o[2] * 255);
682
+ px[i + 3] = Math.round(na * 255);
683
+ }
684
+ }
685
+ }
686
+ }, _class5);
687
+
688
+ // src/paint/mixer.ts
689
+ var DEFAULT_MIXER = {
690
+ wet: 0.5,
691
+ load: 0.5,
692
+ mix: 0.5,
693
+ loadEachStroke: true,
694
+ cleanEachStroke: true,
695
+ mixing: "rgb"
696
+ };
697
+ function normalizeMixer(raw = {}) {
698
+ const unit3 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
699
+ const bool = (v, d) => typeof v === "boolean" ? v : d;
700
+ return {
701
+ wet: unit3(raw.wet, DEFAULT_MIXER.wet),
702
+ load: unit3(raw.load, DEFAULT_MIXER.load),
703
+ mix: unit3(raw.mix, DEFAULT_MIXER.mix),
704
+ loadEachStroke: bool(raw.loadEachStroke, DEFAULT_MIXER.loadEachStroke),
705
+ cleanEachStroke: bool(raw.cleanEachStroke, DEFAULT_MIXER.cleanEachStroke),
706
+ mixing: raw.mixing === "pigment" ? "pigment" : "rgb"
707
+ };
708
+ }
709
+ function mixerRates(settings) {
710
+ return exchangeRates(settings.wet, settings.mix, settings.load, settings.mixing);
711
+ }
712
+ function loadMixer(load, color) {
713
+ const c = (v) => Math.min(1, Math.max(0, Number.isFinite(v) ? v : 0));
714
+ return { reservoir: [c(color[0]), c(color[1]), c(color[2])], amount: 1, pickup: load.pickup };
715
+ }
716
+ function cleanMixer(load) {
717
+ return { reservoir: load.reservoir, amount: load.amount, pickup: null };
718
+ }
719
+ var MixerStroke = (_class6 = class {
720
+ constructor(brush, settings, load, target) {;_class6.prototype.__init22.call(this);_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);
721
+ this.brush = brush;
722
+ this.walker = new DabWalker(brush);
723
+ this.exchange = new PaintExchange(mixerRates(normalizeMixer(settings)), load);
724
+ this.medium = new TileMedium(target);
725
+ }
726
+
727
+ __init22() {this.dabCount = 0}
728
+ __init23() {this.bounds = null}
729
+
730
+ __init24() {this.smoothPos = null}
731
+ __init25() {this.buf = new Float32Array(0)}
732
+
733
+
734
+ __init26() {this.paintColor = [0, 0, 0]}
735
+ /** The brush load after the dabs so far. */
736
+ get load() {
737
+ return this.exchange.load;
738
+ }
739
+ push(points) {
740
+ const dabs = [];
741
+ for (const p of points) this.walker.push(this.smooth(p), dabs);
742
+ return this.paint(dabs);
743
+ }
744
+ finish(last) {
745
+ const dabs = [];
746
+ if (last && this.smoothPos && (this.smoothPos.x !== last.x || this.smoothPos.y !== last.y)) this.walker.push(last, dabs);
747
+ this.walker.finish(dabs);
748
+ return this.paint(dabs);
749
+ }
750
+ smooth(p) {
751
+ const a = 1 - Math.min(0.95, Math.max(0, this.brush.smoothing));
752
+ const cur = this.smoothPos;
753
+ const n = cur && a < 1 ? { ...p, x: cur.x + (p.x - cur.x) * a, y: cur.y + (p.y - cur.y) * a } : p;
754
+ this.smoothPos = n;
755
+ return n;
756
+ }
757
+ paint(dabs) {
758
+ const rects = [];
759
+ for (const d of dabs) {
760
+ const rect = dabRect(d, this.brush.hardness);
761
+ const n = rect.width * rect.height;
762
+ if (this.buf.length < n) this.buf = new Float32Array(n);
763
+ const cov = this.buf.subarray(0, n);
764
+ rasterizeDab({ ...d, alpha: 1 }, this.brush.hardness, rect, cov);
765
+ this.dab(d, rect, cov);
766
+ rects.push(rect);
767
+ this.bounds = union(this.bounds, rect);
768
+ this.dabCount++;
769
+ }
770
+ return rects;
771
+ }
772
+ dab(d, rect, cov) {
773
+ const ex = this.exchange;
774
+ ex.pickUp(this.medium.sample(rect, cov));
775
+ const total = ex.paint(this.paintColor);
776
+ if (total > 0) {
777
+ const dep = Math.min(1, total) * this.brush.flow * this.brush.opacity * d.opacity;
778
+ if (dep > 0) this.medium.deposit(rect, cov, dep, this.paintColor, ex.paintPigment);
779
+ }
780
+ ex.drain();
781
+ }
782
+ }, _class6);
783
+ function union(a, b) {
784
+ if (!a) return { ...b };
785
+ const x = Math.min(a.x, b.x);
786
+ const y = Math.min(a.y, b.y);
787
+ return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
788
+ }
789
+
790
+ // src/paint/blending.ts
791
+ var DEFAULT_BLENDER = { strength: 0.5, distance: 0.35, mixing: "pigment" };
792
+ var DEFAULT_SMUDGE = { strength: 0.7, length: 0.85, mixing: "rgb" };
793
+ var unit = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
794
+ function normalizeBlender(raw = {}, base = DEFAULT_BLENDER) {
795
+ return { strength: unit(raw.strength, base.strength), distance: unit(raw.distance, base.distance), mixing: raw.mixing === "rgb" || raw.mixing === "pigment" ? raw.mixing : base.mixing };
796
+ }
797
+ function normalizeSmudge(raw = {}, base = DEFAULT_SMUDGE) {
798
+ return { strength: unit(raw.strength, base.strength), length: unit(raw.length, base.length), mixing: raw.mixing === "rgb" || raw.mixing === "pigment" ? raw.mixing : base.mixing };
799
+ }
800
+ function blendRadius(settings, radius) {
801
+ return Math.max(1, Math.round(settings.distance * 2 * radius));
802
+ }
803
+ function createBlenderScratch() {
804
+ return { sums: new Float64Array(0), mix: { rgb: [0, 0, 0], strength: 1, ks: new Float32Array(_chunkTFOPHZ3Ucjs.PIGMENT_BANDS), scatter: 1 }, out: [0, 0, 0] };
805
+ }
806
+ var N = _chunkTFOPHZ3Ucjs.PIGMENT_BANDS;
807
+ function blenderDab(medium, rect, cov, opacity, radius, settings, scratch2) {
808
+ const R = blendRadius(settings, radius);
809
+ const W = rect.width + 2 * R;
810
+ const H = rect.height + 2 * R;
811
+ const pigment = settings.mixing === "pigment";
812
+ const C = pigment ? N + 2 : 4;
813
+ const stride = (W + 1) * C;
814
+ const size = stride * (H + 1);
815
+ if (scratch2.sums.length < size) scratch2.sums = new Float64Array(size);
816
+ const I = scratch2.sums;
817
+ I.fill(0, 0, size);
818
+ medium.reset();
819
+ const row = new Float64Array(C);
820
+ for (let y = 0; y < H; y++) {
821
+ row.fill(0);
822
+ const dy = rect.y - R + y;
823
+ for (let x = 0; x < W; x++) {
824
+ const dx = rect.x - R + x;
825
+ const i = medium.locate(dx, dy, false);
826
+ if (i >= 0) {
827
+ const px = medium.pixels;
828
+ const a = px[i + 3] / 255;
829
+ if (a > 0) {
830
+ if (pigment) {
831
+ const p = _chunkTFOPHZ3Ucjs.pigment8.call(void 0, px[i], px[i + 1], px[i + 2]);
832
+ const sa = p.scatter * p.strength * a;
833
+ for (let k = 0; k < N; k++) row[k] += sa * p.ks[k];
834
+ row[N] += sa;
835
+ row[N + 1] += a;
836
+ } else {
837
+ row[0] += px[i] / 255 * a;
838
+ row[1] += px[i + 1] / 255 * a;
839
+ row[2] += px[i + 2] / 255 * a;
840
+ row[3] += a;
841
+ }
842
+ }
843
+ }
844
+ const o = (y + 1) * stride + (x + 1) * C;
845
+ const up = y * stride + (x + 1) * C;
846
+ for (let c = 0; c < C; c++) I[o + c] = I[up + c] + row[c];
847
+ }
848
+ }
849
+ const area = (2 * R + 1) * (2 * R + 1);
850
+ const strength = settings.strength * opacity;
851
+ const lock = medium.target.lockTransparency;
852
+ const mixPig = scratch2.mix;
853
+ const out = scratch2.out;
854
+ const sum = (c, x1, y1, x2, y2) => I[y2 * stride + x2 * C + c] - I[y1 * stride + x2 * C + c] - I[y2 * stride + x1 * C + c] + I[y1 * stride + x1 * C + c];
855
+ const w = rect.width;
856
+ medium.reset();
857
+ for (let yy = 0; yy < rect.height; yy++) {
858
+ for (let xx = 0; xx < w; xx++) {
859
+ const k = cov[yy * w + xx];
860
+ if (k <= 0) continue;
861
+ const dx = rect.x + xx;
862
+ const dy = rect.y + yy;
863
+ let t = strength * k * medium.selectionAt(dx, dy);
864
+ if (t <= 0) continue;
865
+ if (t > 1) t = 1;
866
+ const x1 = xx;
867
+ const y1 = yy;
868
+ const x2 = xx + 2 * R + 1;
869
+ const y2 = yy + 2 * R + 1;
870
+ const asum = sum(pigment ? N + 1 : 3, x1, y1, x2, y2);
871
+ const ri = medium.locate(dx, dy, false);
872
+ const a = ri < 0 ? 0 : medium.pixels[ri + 3] / 255;
873
+ if (lock && a <= 0) continue;
874
+ const avgA = asum / area;
875
+ const na = lock ? a : (1 - t) * a + t * avgA;
876
+ if (asum <= 1e-9) {
877
+ if (lock || a <= 0) continue;
878
+ const i2 = medium.locate(dx, dy, true);
879
+ medium.pixels[i2 + 3] = Math.round(na * 255);
880
+ continue;
881
+ }
882
+ const share = lock ? t : na > 0 ? t * avgA / na : 1;
883
+ const i = medium.locate(dx, dy, true);
884
+ const px = medium.pixels;
885
+ if (pigment) {
886
+ const sa = sum(N, x1, y1, x2, y2);
887
+ for (let c = 0; c < N; c++) mixPig.ks[c] = sum(c, x1, y1, x2, y2) / sa;
888
+ mixPig.scatter = sa / asum;
889
+ if (a <= 0 || share >= 1) _chunkTFOPHZ3Ucjs.lerpPigmentInto.call(void 0, mixPig, mixPig, 0, out, 0);
890
+ else _chunkTFOPHZ3Ucjs.lerpPigmentInto.call(void 0, _chunkTFOPHZ3Ucjs.pigment8.call(void 0, px[i], px[i + 1], px[i + 2]), mixPig, share, out, 0);
891
+ } else {
892
+ for (let c = 0; c < 3; c++) {
893
+ const m = sum(c, x1, y1, x2, y2) / asum;
894
+ out[c] = a <= 0 || share >= 1 ? m : px[i + c] / 255 + (m - px[i + c] / 255) * share;
895
+ }
896
+ }
897
+ px[i] = Math.round(out[0] * 255);
898
+ px[i + 1] = Math.round(out[1] * 255);
899
+ px[i + 2] = Math.round(out[2] * 255);
900
+ if (!lock) px[i + 3] = Math.round(na * 255);
901
+ }
902
+ }
903
+ }
904
+ var SmudgeTip = (_class7 = class {
905
+ constructor(settings, diameter) {;_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);
906
+ this.settings = settings;
907
+ this.size = Math.max(2, Math.min(512, Math.ceil(diameter)));
908
+ this.patch = new Float32Array(this.size * this.size * 4);
909
+ }
910
+
911
+
912
+
913
+ __init27() {this.loaded = false}
914
+ __init28() {this.out = [0, 0, 0]}
915
+ cell(d, px, py) {
916
+ const s = this.size;
917
+ const r = Math.max(0.5, d.radius);
918
+ const bx = Math.min(s - 1, Math.max(0, Math.floor(((px - d.x) / r + 1) / 2 * s)));
919
+ const by = Math.min(s - 1, Math.max(0, Math.floor(((py - d.y) / r + 1) / 2 * s)));
920
+ return (by * s + bx) * 4;
921
+ }
922
+ /** Fills the patch from the pixels under the dab. */
923
+ load(medium, d) {
924
+ const s = this.size;
925
+ const r = Math.max(0.5, d.radius);
926
+ medium.reset();
927
+ for (let by = 0; by < s; by++) {
928
+ for (let bx = 0; bx < s; bx++) {
929
+ const dx = Math.floor(d.x + ((bx + 0.5) / s * 2 - 1) * r);
930
+ const dy = Math.floor(d.y + ((by + 0.5) / s * 2 - 1) * r);
931
+ const i = medium.locate(dx, dy, false);
932
+ const c = (by * s + bx) * 4;
933
+ if (i < 0) {
934
+ this.patch.fill(0, c, c + 4);
935
+ continue;
936
+ }
937
+ const px = medium.pixels;
938
+ this.patch[c] = px[i] / 255;
939
+ this.patch[c + 1] = px[i + 1] / 255;
940
+ this.patch[c + 2] = px[i + 2] / 255;
941
+ this.patch[c + 3] = px[i + 3] / 255;
942
+ }
943
+ }
944
+ this.loaded = true;
945
+ }
946
+ /** `a` mixed `t` of the way to `b` (both 0..1) into `out`. */
947
+ mix(a0, a1, a2, b0, b1, b2, t) {
948
+ const o = this.out;
949
+ if (this.settings.mixing === "pigment") {
950
+ _chunkTFOPHZ3Ucjs.mixPigment8.call(void 0, Math.round(a0 * 255), Math.round(a1 * 255), Math.round(a2 * 255), Math.round(b0 * 255), Math.round(b1 * 255), Math.round(b2 * 255), t, o);
951
+ } else {
952
+ o[0] = a0 + (b0 - a0) * t;
953
+ o[1] = a1 + (b1 - a1) * t;
954
+ o[2] = a2 + (b2 - a2) * t;
955
+ }
956
+ return o;
957
+ }
958
+ /** One dab: lays the carried patch over the tip's footprint (`cov` over `rect`), then picks up. */
959
+ dab(medium, d, rect, cov) {
960
+ if (!this.loaded) {
961
+ this.load(medium, d);
962
+ return;
963
+ }
964
+ const { strength, length } = this.settings;
965
+ const lock = medium.target.lockTransparency;
966
+ const patch = this.patch;
967
+ const w = rect.width;
968
+ medium.reset();
969
+ for (let yy = 0; yy < rect.height; yy++) {
970
+ for (let xx = 0; xx < w; xx++) {
971
+ const cv = cov[yy * w + xx];
972
+ if (cv <= 0) continue;
973
+ const dx = rect.x + xx;
974
+ const dy = rect.y + yy;
975
+ let k = strength * cv * d.opacity * medium.selectionAt(dx, dy);
976
+ if (k <= 0) continue;
977
+ if (k > 1) k = 1;
978
+ const c = this.cell(d, dx + 0.5, dy + 0.5);
979
+ const ba = patch[c + 3];
980
+ const ri = medium.locate(dx, dy, false);
981
+ const a = ri < 0 ? 0 : medium.pixels[ri + 3] / 255;
982
+ if (lock && a <= 0) continue;
983
+ const px0 = ri >= 0 ? medium.pixels : null;
984
+ const o0 = px0 ? px0[ri] / 255 : 0;
985
+ const o1 = px0 ? px0[ri + 1] / 255 : 0;
986
+ const o2 = px0 ? px0[ri + 2] / 255 : 0;
987
+ if (a > 0 || ba > 0) {
988
+ const na = lock ? a : a + (ba - a) * k;
989
+ const share = lock ? k * ba : na > 0 ? k * ba / na : 0;
990
+ let r = o0;
991
+ let g = o1;
992
+ let b = o2;
993
+ if (a <= 0 || share >= 1) {
994
+ r = patch[c];
995
+ g = patch[c + 1];
996
+ b = patch[c + 2];
997
+ } else if (share > 0) {
998
+ const m = this.mix(o0, o1, o2, patch[c], patch[c + 1], patch[c + 2], share);
999
+ r = m[0];
1000
+ g = m[1];
1001
+ b = m[2];
1002
+ }
1003
+ const i = medium.locate(dx, dy, true);
1004
+ const out = medium.pixels;
1005
+ out[i] = Math.round(r * 255);
1006
+ out[i + 1] = Math.round(g * 255);
1007
+ out[i + 2] = Math.round(b * 255);
1008
+ out[i + 3] = Math.round(na * 255);
1009
+ }
1010
+ const pick = (1 - length) * Math.min(1, cv * d.opacity);
1011
+ if (pick > 0) {
1012
+ const pa = ba + (a - ba) * pick;
1013
+ const s = pa > 0 ? a * pick / pa : 0;
1014
+ if (s > 0) {
1015
+ const m = ba <= 0 || s >= 1 ? [o0, o1, o2] : this.mix(patch[c], patch[c + 1], patch[c + 2], o0, o1, o2, s);
1016
+ patch[c] = m[0];
1017
+ patch[c + 1] = m[1];
1018
+ patch[c + 2] = m[2];
1019
+ }
1020
+ patch[c + 3] = pa;
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ }, _class7);
1026
+
1027
+ // src/paint/pigment-tools.ts
1028
+ var HEX = /^#?([0-9a-f]{6})$/i;
1029
+ function parseHex(value, index) {
1030
+ const m = typeof value === "string" ? HEX.exec(value.trim()) : null;
1031
+ if (!m) throw new Error(`colors[${index}].color is ${JSON.stringify(value)}; use #rrggbb, e.g. "#1e40af".`);
1032
+ const n = parseInt(m[1], 16);
1033
+ return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
1034
+ }
1035
+ function toHex(rgb) {
1036
+ return "#" + rgb.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("");
1037
+ }
1038
+ function createPigmentTools() {
1039
+ const mixColors = {
1040
+ name: "mix_colors",
1041
+ title: "Mix colours like paint",
1042
+ description: "Mixes colours as paint would (model pigment, the default: Kubelka-Munk mixing over reflectance spectra, so blue and yellow make green and white lightens a colour into a tint instead of greying it), or as light on screen (model rgb: the weighted RGB average). Changes nothing. Returns mixed (#rrggbb, by the chosen model), the other model's result for comparison (rgbLerp and pigment), the normalised weights and the model.",
1043
+ inputSchema: {
1044
+ type: "object",
1045
+ properties: {
1046
+ colors: {
1047
+ type: "array",
1048
+ minItems: 1,
1049
+ maxItems: 16,
1050
+ description: "The paints to mix (1 to 16).",
1051
+ items: {
1052
+ type: "object",
1053
+ properties: {
1054
+ color: { type: "string", description: "Paint colour #rrggbb." },
1055
+ weight: { type: "number", minimum: 0, description: "Parts of this paint, any scale (e.g. 1 and 3 for a 1:3 mix). At least one weight must be above 0." },
1056
+ strength: {
1057
+ type: "number",
1058
+ exclusiveMinimum: 0,
1059
+ maximum: 100,
1060
+ description: "Tinting strength (default 1): each part counts as this many, so a strong dark paint (e.g. 4) tints a lot of white."
1061
+ }
1062
+ },
1063
+ required: ["color", "weight"],
1064
+ additionalProperties: false
1065
+ }
1066
+ },
1067
+ model: {
1068
+ type: "string",
1069
+ enum: ["pigment", "rgb"],
1070
+ description: "pigment (default): mix like paint (Kubelka-Munk), as the pigment blend mode, Mixer Brush and mixing pad do; rgb: average the RGB values, as normal blending and rgb mixing brushes do (strength is ignored)."
1071
+ }
1072
+ },
1073
+ required: ["colors"],
1074
+ additionalProperties: false
1075
+ },
1076
+ annotations: { title: "Mix colours like paint", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
1077
+ handler: (args) => {
1078
+ const items = args.colors;
1079
+ const rgbs = items.map((item, i) => parseHex(item.color, i));
1080
+ const total = items.reduce((sum, item) => sum + item.weight, 0);
1081
+ if (!(total > 0)) throw new Error("Every weight is 0; give at least one colour a weight above 0.");
1082
+ const pigments = rgbs.map((rgb2, i) => _chunkTFOPHZ3Ucjs.createPigment.call(void 0, rgb2, { strength: items[i].strength }));
1083
+ const mixed = _chunkTFOPHZ3Ucjs.mixPigments.call(void 0, pigments, items.map((item) => item.weight));
1084
+ const lerp = [0, 1, 2].map((c) => rgbs.reduce((sum, rgb2, i) => sum + rgb2[c] * items[i].weight, 0) / total);
1085
+ const rgb = args.model === "rgb";
1086
+ return {
1087
+ mixed: toHex(rgb ? lerp : mixed),
1088
+ rgbLerp: toHex(lerp),
1089
+ pigment: toHex(mixed),
1090
+ weights: items.map((item) => item.weight / total),
1091
+ model: rgb ? "rgb" : "kubelka-munk"
1092
+ };
1093
+ }
1094
+ };
1095
+ return [mixColors];
1096
+ }
1097
+
1098
+ // src/paint/wet/model.ts
1099
+ var WET_TILE = 256;
1100
+ var SLOT_SIDE = 258;
1101
+ var PLANE = SLOT_SIDE * SLOT_SIDE;
1102
+ var PLANE_BYTES = PLANE * 4;
1103
+ var SET_PLANES = 18;
1104
+ var P_RHO = 9;
1105
+ var P_UX = 10;
1106
+ var P_UY = 11;
1107
+ var P_SW = 12;
1108
+ var P_P0 = 13;
1109
+ var P_EDGE = 17;
1110
+ var P_D0 = 2 * SET_PLANES;
1111
+ var P_H = P_D0 + 4;
1112
+ var P_FX = P_H + 1;
1113
+ var P_FY = P_H + 2;
1114
+ var SLOT_PLANES = P_FY + 1;
1115
+ var SLOT_BYTES = SLOT_PLANES * PLANE_BYTES;
1116
+ var PIGMENT_CHANNELS = 4;
1117
+ var EX = [0, 1, 0, -1, 0, 1, -1, -1, 1];
1118
+ var EY = [0, 0, 1, 0, -1, 1, 1, -1, -1];
1119
+ var OPP = [0, 3, 4, 1, 2, 7, 8, 5, 6];
1120
+ var W9 = [4 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 36, 1 / 36, 1 / 36, 1 / 36];
1121
+ var DRY_EPS = 1e-3;
1122
+ var U_MAX = 0.25;
1123
+ var STRIP = 64;
1124
+ var MAX_BATCH = 32;
1125
+ var FREE_INTERVAL = 32;
1126
+ var FLAG_ALIVE = 1;
1127
+ var FLAG_LEFT = 2;
1128
+ var FLAG_RIGHT = 4;
1129
+ var FLAG_TOP = 8;
1130
+ var FLAG_BOTTOM = 16;
1131
+ var FLAG_WET = 32;
1132
+ var PARAM_FLOATS = 32;
1133
+ var PARAM = {
1134
+ omega: 0,
1135
+ tau: 1,
1136
+ evap: 2,
1137
+ edgeEvap: 3,
1138
+ // times the edge field
1139
+ mobility: 4,
1140
+ // half the pigment mobility (the face flux uses the sum of two velocities)
1141
+ depRate: 5,
1142
+ pickup: 6,
1143
+ absorption: 7,
1144
+ invCap: 8,
1145
+ blockScale: 9,
1146
+ rough: 10,
1147
+ pinT: 11,
1148
+ gx: 12,
1149
+ gy: 13,
1150
+ flowK: 14,
1151
+ granulation: 15,
1152
+ edgeDep: 16,
1153
+ // times the edge field
1154
+ bloom: 17,
1155
+ paperEvap: 18,
1156
+ hasFlow: 19,
1157
+ bleed: 20
1158
+ };
1159
+ var DEFAULT_WET_PARAMS = {
1160
+ viscosity: 0.3,
1161
+ evaporation: 0.45,
1162
+ edgeDarkening: 0.5,
1163
+ pigmentWeight: 0.3,
1164
+ granulation: 0.4,
1165
+ pickup: 0.2,
1166
+ fixture: 0.2,
1167
+ bloom: 0.4,
1168
+ bleed: 0.3
1169
+ };
1170
+ var WET_PARAM_KEYS = Object.keys(DEFAULT_WET_PARAMS);
1171
+ var DEFAULT_WET_PAPER = { absorption: 0.7, permeability: 0.5, roughness: 0.6 };
1172
+ var FLAT = { angle: 0, direction: 0 };
1173
+ var unit2 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
1174
+ function normalizeWetParams(raw = {}, base = DEFAULT_WET_PARAMS) {
1175
+ const out = {};
1176
+ for (const k of WET_PARAM_KEYS) out[k] = unit2(raw[k], base[k]);
1177
+ return out;
1178
+ }
1179
+ function normalizeTilt(raw = {}, base = FLAT) {
1180
+ const angle = typeof raw.angle === "number" && Number.isFinite(raw.angle) ? Math.min(90, Math.max(0, raw.angle)) : base.angle;
1181
+ let direction = typeof raw.direction === "number" && Number.isFinite(raw.direction) ? raw.direction : base.direction;
1182
+ direction = (direction % 360 + 360) % 360;
1183
+ return { angle, direction };
1184
+ }
1185
+ function latticeViscosity(v) {
1186
+ return 0.02 * Math.pow(75, v);
1187
+ }
1188
+ var PAPER_CAPACITY = 0.08;
1189
+ var MAX_TILT_ACCEL = 0.025;
1190
+ function packWetParams(params, paper, tilt, flowStrength, out = new Float32Array(PARAM_FLOATS)) {
1191
+ const nu = latticeViscosity(params.viscosity);
1192
+ const tau = 3 * nu + 0.5;
1193
+ const g = MAX_TILT_ACCEL * Math.sin(tilt.angle * Math.PI / 180);
1194
+ const dir = tilt.direction * Math.PI / 180;
1195
+ out.fill(0);
1196
+ out[PARAM.omega] = 1 / tau;
1197
+ out[PARAM.tau] = tau;
1198
+ out[PARAM.evap] = 1e-3 * params.evaporation * params.evaporation;
1199
+ out[PARAM.edgeEvap] = params.edgeDarkening * 12;
1200
+ out[PARAM.mobility] = 0.5 * (1 - 0.5 * params.pigmentWeight);
1201
+ out[PARAM.depRate] = 0.019 * params.fixture * (0.5 + params.pigmentWeight);
1202
+ out[PARAM.pickup] = 0.01 * params.pickup;
1203
+ out[PARAM.absorption] = 15e-4 * paper.absorption;
1204
+ out[PARAM.invCap] = 1 / PAPER_CAPACITY;
1205
+ out[PARAM.blockScale] = 0.05 * (1 - paper.permeability);
1206
+ out[PARAM.rough] = 0.15 * paper.roughness;
1207
+ out[PARAM.pinT] = 0.2;
1208
+ out[PARAM.gx] = -g * Math.sin(dir);
1209
+ out[PARAM.gy] = g * Math.cos(dir);
1210
+ out[PARAM.flowK] = flowStrength;
1211
+ out[PARAM.granulation] = 1.2 * params.granulation * paper.roughness;
1212
+ out[PARAM.edgeDep] = params.edgeDarkening * 3;
1213
+ out[PARAM.bloom] = 60 * params.bloom;
1214
+ out[PARAM.paperEvap] = 5e-4 * params.evaporation * params.evaporation;
1215
+ out[PARAM.hasFlow] = flowStrength !== 0 ? 1 : 0;
1216
+ out[PARAM.bleed] = 0.05 * params.bleed;
1217
+ return out;
1218
+ }
1219
+ function edgeBits(lx, ly) {
1220
+ let b = 0;
1221
+ if (lx < STRIP) b |= FLAG_LEFT;
1222
+ if (lx >= WET_TILE - STRIP) b |= FLAG_RIGHT;
1223
+ if (ly < STRIP) b |= FLAG_TOP;
1224
+ if (ly >= WET_TILE - STRIP) b |= FLAG_BOTTOM;
1225
+ return b;
1226
+ }
1227
+
1228
+ // src/paint/wet/film.ts
1229
+ var PIGMENT_UNIT = 0.25;
1230
+ var HIDE = 1.5;
1231
+ var BODY_SCATTER = 3;
1232
+ var R_MIN = 1e-3;
1233
+ var f64 = new Float64Array(1);
1234
+ var u32 = new Uint32Array(f64.buffer);
1235
+ var LITTLE = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
1236
+ var HI = LITTLE ? 1 : 0;
1237
+ var LN2 = 0.6931471805599453;
1238
+ function pow2(k) {
1239
+ u32[HI] = k + 1023 << 20;
1240
+ u32[1 - HI] = 0;
1241
+ return f64[0];
1242
+ }
1243
+ function dexp(x) {
1244
+ if (!(x > -700)) return x !== x ? x : 0;
1245
+ if (x > 700) return Infinity;
1246
+ const k = Math.round(x / LN2);
1247
+ const r = x - k * LN2;
1248
+ let p = 1 / 39916800;
1249
+ p = p * r + 1 / 3628800;
1250
+ p = p * r + 1 / 362880;
1251
+ p = p * r + 1 / 40320;
1252
+ p = p * r + 1 / 5040;
1253
+ p = p * r + 1 / 720;
1254
+ p = p * r + 1 / 120;
1255
+ p = p * r + 1 / 24;
1256
+ p = p * r + 1 / 6;
1257
+ p = p * r + 0.5;
1258
+ p = p * r + 1;
1259
+ p = p * r + 1;
1260
+ return p * pow2(k);
1261
+ }
1262
+ function dlog(x) {
1263
+ if (!(x > 0)) return x === 0 ? -Infinity : NaN;
1264
+ f64[0] = x;
1265
+ let e = (u32[HI] >>> 20 & 2047) - 1023;
1266
+ if (e === -1023) {
1267
+ return dlog(x * 18014398509481984) - 54 * LN2;
1268
+ }
1269
+ u32[HI] = u32[HI] & 2148532223 | 1023 << 20;
1270
+ let m = f64[0];
1271
+ if (m > 1.4142135623730951) {
1272
+ m /= 2;
1273
+ e += 1;
1274
+ }
1275
+ const s = (m - 1) / (m + 1);
1276
+ const s2 = s * s;
1277
+ let t = s2;
1278
+ let sum = 1;
1279
+ for (let i = 3; i < 40; i += 2) {
1280
+ sum += t / i;
1281
+ t *= s2;
1282
+ }
1283
+ return 2 * s * sum + e * LN2;
1284
+ }
1285
+ function dpow(x, y) {
1286
+ if (x <= 0) return 0;
1287
+ return dexp(y * dlog(x));
1288
+ }
1289
+ var SRGB_TO_LINEAR = new Float64Array(256);
1290
+ for (let i = 0; i < 256; i++) {
1291
+ const v = i / 255;
1292
+ SRGB_TO_LINEAR[i] = v <= 0.04045 ? v / 12.92 : dpow((v + 0.055) / 1.055, 2.4);
1293
+ }
1294
+ function srgbByteToLinear(v) {
1295
+ if (!(v > 0)) return 0;
1296
+ if (v >= 255) return 1;
1297
+ const i = Math.floor(v);
1298
+ return SRGB_TO_LINEAR[i] + (SRGB_TO_LINEAR[i + 1] - SRGB_TO_LINEAR[i]) * (v - i);
1299
+ }
1300
+ var ENC_STEPS = 16384;
1301
+ var ENCODE = new Uint8Array(ENC_STEPS + 1);
1302
+ for (let i = 0; i <= ENC_STEPS; i++) {
1303
+ const v = i / ENC_STEPS;
1304
+ const e = v <= 31308e-7 ? 12.92 * v : 1.055 * dpow(v, 1 / 2.4) - 0.055;
1305
+ ENCODE[i] = Math.min(255, Math.max(0, Math.round(e * 255)));
1306
+ }
1307
+ function linearToSrgb8(v) {
1308
+ if (!(v > 0)) return 0;
1309
+ if (v >= 1) return 255;
1310
+ return ENCODE[Math.round(v * ENC_STEPS)];
1311
+ }
1312
+ function pigmentOf(rgb, body, out = new Float64Array(4)) {
1313
+ const b = Math.min(1, Math.max(0, body));
1314
+ const s = b * BODY_SCATTER / PIGMENT_UNIT;
1315
+ for (let c = 0; c < 3; c++) {
1316
+ const v = Math.min(255, Math.max(0, Math.round(rgb[c] * 255)));
1317
+ const r = Math.max(R_MIN, SRGB_TO_LINEAR[v]);
1318
+ const kt = -dlog(r) / 2 / PIGMENT_UNIT;
1319
+ const ko = (1 - r) * (1 - r) / (2 * r) * s;
1320
+ out[c] = (1 - b) * kt + b * ko;
1321
+ }
1322
+ out[3] = s;
1323
+ return out;
1324
+ }
1325
+ var EXP_RES = 256;
1326
+ var EXP_MAX = 24;
1327
+ var EXP_TABLE = new Float64Array(EXP_MAX * EXP_RES + 2);
1328
+ for (let i = 0; i < EXP_TABLE.length; i++) EXP_TABLE[i] = dexp(-i / EXP_RES);
1329
+ function expNeg(x) {
1330
+ if (!(x > 0)) return 1;
1331
+ if (x >= EXP_MAX) return 0;
1332
+ const t = x * EXP_RES;
1333
+ const i = Math.floor(t);
1334
+ const fr = t - i;
1335
+ return EXP_TABLE[i] + (EXP_TABLE[i + 1] - EXP_TABLE[i]) * fr;
1336
+ }
1337
+ function filmReflectance(k0, k1, k2, s, b0, b1, b2, out) {
1338
+ const hide = s > 0 ? 1 - expNeg(HIDE * s) : 0;
1339
+ const keep = 1 - hide;
1340
+ const inv = s > 1e-9 ? 1 / s : 0;
1341
+ out[0] = (hide > 0 ? hide * rinf(k0 * inv) : 0) + keep * expNeg(2 * k0) * b0;
1342
+ out[1] = (hide > 0 ? hide * rinf(k1 * inv) : 0) + keep * expNeg(2 * k1) * b1;
1343
+ out[2] = (hide > 0 ? hide * rinf(k2 * inv) : 0) + keep * expNeg(2 * k2) * b2;
1344
+ }
1345
+ function rinf(ks) {
1346
+ return 1 + ks - Math.sqrt(ks * ks + 2 * ks);
1347
+ }
1348
+ function pigmentColor(k0, k1, k2, s, out = [0, 0, 0]) {
1349
+ const lin = [0, 0, 0];
1350
+ filmReflectance(k0, k1, k2, s, 1, 1, 1, lin);
1351
+ for (let c = 0; c < 3; c++) out[c] = linearToSrgb8(lin[c]) / 255;
1352
+ return out;
1353
+ }
1354
+ var scratch = new Float64Array(3);
1355
+ function filmPixel(k0, k1, k2, s, px, i, paper, wet, out, o) {
1356
+ const pa = px[i + 3];
1357
+ if (!(k0 > 1e-7 || k1 > 1e-7 || k2 > 1e-7 || s > 1e-7) && wet <= 0) {
1358
+ out[o] = px[i];
1359
+ out[o + 1] = px[i + 1];
1360
+ out[o + 2] = px[i + 2];
1361
+ out[o + 3] = pa;
1362
+ return;
1363
+ }
1364
+ const a = pa / 255;
1365
+ const p0 = paper[0];
1366
+ const p1 = paper[1];
1367
+ const p2 = paper[2];
1368
+ const b0 = srgbByteToLinear(a * px[i] + (1 - a) * p0);
1369
+ const b1 = srgbByteToLinear(a * px[i + 1] + (1 - a) * p1);
1370
+ const b2 = srgbByteToLinear(a * px[i + 2] + (1 - a) * p2);
1371
+ filmReflectance(k0, k1, k2, s, b0, b1, b2, scratch);
1372
+ const dark = 1 - 0.07 * wet;
1373
+ const e0 = linearToSrgb8(scratch[0] * dark);
1374
+ const e1 = linearToSrgb8(scratch[1] * dark);
1375
+ const e2 = linearToSrgb8(scratch[2] * dark);
1376
+ let alpha = a;
1377
+ alpha = needAlpha(e0, p0, alpha);
1378
+ alpha = needAlpha(e1, p1, alpha);
1379
+ alpha = needAlpha(e2, p2, alpha);
1380
+ if (alpha <= 0) {
1381
+ out[o] = px[i];
1382
+ out[o + 1] = px[i + 1];
1383
+ out[o + 2] = px[i + 2];
1384
+ out[o + 3] = 0;
1385
+ return;
1386
+ }
1387
+ const a8 = Math.max(Math.round(Math.min(1, alpha) * 255), pa);
1388
+ const al = a8 / 255;
1389
+ const inv = 1 / al;
1390
+ const k = 1 - al;
1391
+ out[o] = Math.round(Math.min(255, Math.max(0, (e0 - k * p0) * inv)));
1392
+ out[o + 1] = Math.round(Math.min(255, Math.max(0, (e1 - k * p1) * inv)));
1393
+ out[o + 2] = Math.round(Math.min(255, Math.max(0, (e2 - k * p2) * inv)));
1394
+ out[o + 3] = a8;
1395
+ }
1396
+ function needAlpha(e, p, alpha) {
1397
+ if (e < p) return Math.max(alpha, 1 - e / p);
1398
+ if (e > p) return Math.max(alpha, (e - p) / (255 - p));
1399
+ return alpha;
1400
+ }
1401
+ function pixelPigment(px, i, paper, out) {
1402
+ const a = px[i + 3] / 255;
1403
+ for (let c = 0; c < 3; c++) {
1404
+ const p = Math.max(R_MIN, SRGB_TO_LINEAR[paper[c]]);
1405
+ const below = Math.max(R_MIN, srgbByteToLinear(a * px[i + c] + (1 - a) * paper[c]));
1406
+ out[c] = below < p ? -dlog(below / p) / 2 : 0;
1407
+ }
1408
+ out[3] = 0;
1409
+ }
1410
+ function shadeTile(f, plane, side, d, p, rho, sw, src, out, w, h, paper) {
1411
+ const p0 = paper[0];
1412
+ const p1 = paper[1];
1413
+ const p2 = paper[2];
1414
+ const lp0 = SRGB_TO_LINEAR[p0];
1415
+ const lp1 = SRGB_TO_LINEAR[p1];
1416
+ const lp2 = SRGB_TO_LINEAR[p2];
1417
+ const tab = EXP_TABLE;
1418
+ const enc = ENCODE;
1419
+ let any = false;
1420
+ for (let y = 0; y < h; y++) {
1421
+ for (let x = 0; x < w; x++) {
1422
+ const c = (y + 1) * side + x + 1;
1423
+ let k0 = f[d + c];
1424
+ let k1 = f[d + plane + c];
1425
+ let k2 = f[d + 2 * plane + c];
1426
+ let s = f[d + 3 * plane + c];
1427
+ if (p >= 0) {
1428
+ k0 += f[p + c];
1429
+ k1 += f[p + plane + c];
1430
+ k2 += f[p + 2 * plane + c];
1431
+ s += f[p + 3 * plane + c];
1432
+ }
1433
+ let wet = 0;
1434
+ if (rho >= 0) {
1435
+ wet = f[rho + c] * 4 + f[sw + c];
1436
+ if (wet > 1) wet = 1;
1437
+ }
1438
+ if (!(k0 > 1e-7 || k1 > 1e-7 || k2 > 1e-7 || s > 1e-7) && wet <= 0) continue;
1439
+ any = true;
1440
+ const i = (y * 256 + x) * 4;
1441
+ const pa = src ? src[i + 3] : 0;
1442
+ const a = pa / 255;
1443
+ const na = 1 - a;
1444
+ const b0 = src ? srgbByteToLinear(a * src[i] + na * p0) : lp0;
1445
+ const b1 = src ? srgbByteToLinear(a * src[i + 1] + na * p1) : lp1;
1446
+ const b2 = src ? srgbByteToLinear(a * src[i + 2] + na * p2) : lp2;
1447
+ let hide = 0;
1448
+ let x0;
1449
+ if (s > 0) {
1450
+ x0 = HIDE * s;
1451
+ hide = 1 - (x0 >= EXP_MAX ? 0 : interp(tab, x0 * EXP_RES));
1452
+ }
1453
+ const keep = 1 - hide;
1454
+ const inv = s > 1e-9 ? 1 / s : 0;
1455
+ let r0 = keep * expT(tab, 2 * k0) * b0;
1456
+ let r1 = keep * expT(tab, 2 * k1) * b1;
1457
+ let r2 = keep * expT(tab, 2 * k2) * b2;
1458
+ if (hide > 0) {
1459
+ r0 += hide * rinf(k0 * inv);
1460
+ r1 += hide * rinf(k1 * inv);
1461
+ r2 += hide * rinf(k2 * inv);
1462
+ }
1463
+ const dark = 1 - 0.07 * wet;
1464
+ const e0 = encode(enc, r0 * dark);
1465
+ const e1 = encode(enc, r1 * dark);
1466
+ const e2 = encode(enc, r2 * dark);
1467
+ let alpha = a;
1468
+ alpha = needAlpha(e0, p0, alpha);
1469
+ alpha = needAlpha(e1, p1, alpha);
1470
+ alpha = needAlpha(e2, p2, alpha);
1471
+ if (alpha <= 0) {
1472
+ out[i + 3] = 0;
1473
+ continue;
1474
+ }
1475
+ const a8 = Math.max(Math.round(Math.min(1, alpha) * 255), pa);
1476
+ const al = a8 / 255;
1477
+ const ia = 1 / al;
1478
+ const k = 1 - al;
1479
+ out[i] = Math.round(Math.min(255, Math.max(0, (e0 - k * p0) * ia)));
1480
+ out[i + 1] = Math.round(Math.min(255, Math.max(0, (e1 - k * p1) * ia)));
1481
+ out[i + 2] = Math.round(Math.min(255, Math.max(0, (e2 - k * p2) * ia)));
1482
+ out[i + 3] = a8;
1483
+ }
1484
+ }
1485
+ return any;
1486
+ }
1487
+ function interp(tab, t) {
1488
+ const i = Math.floor(t);
1489
+ return tab[i] + (tab[i + 1] - tab[i]) * (t - i);
1490
+ }
1491
+ function expT(tab, x) {
1492
+ if (!(x > 0)) return 1;
1493
+ if (x >= EXP_MAX) return 0;
1494
+ return interp(tab, x * EXP_RES);
1495
+ }
1496
+ function encode(enc, v) {
1497
+ if (!(v > 0)) return 0;
1498
+ if (v >= 1) return 255;
1499
+ return enc[Math.round(v * ENC_STEPS)];
1500
+ }
1501
+
1502
+ // src/paint/wet/kernel.ts
1503
+ var F = Math.fround;
1504
+ var S = SLOT_SIDE;
1505
+ var EPS = F(DRY_EPS);
1506
+ var UMAX2 = F(U_MAX * U_MAX);
1507
+ var UMAXF = F(U_MAX);
1508
+ var W0 = F(4 / 9);
1509
+ var W1 = F(1 / 9);
1510
+ var W2 = F(1 / 36);
1511
+ var TINY = F(1e-9);
1512
+ var DECAY = F(0.99);
1513
+ var NB = [0, -1, -S, 1, S, -S - 1, -S + 1, S + 1, S - 1];
1514
+ var OPP2 = [0, 3, 4, 1, 2, 7, 8, 5, 6];
1515
+ var fin = new Float32Array(9);
1516
+ var pNew = new Float32Array(4);
1517
+ function stepSlotTs(f, base, parity, prm) {
1518
+ const src = base + parity * SET_PLANES * PLANE;
1519
+ const dst = base + (1 - parity) * SET_PLANES * PLANE;
1520
+ const hP = base + P_H * PLANE;
1521
+ const dP = base + P_D0 * PLANE;
1522
+ const rhoS = src + P_RHO * PLANE;
1523
+ const uxS = src + P_UX * PLANE;
1524
+ const uyS = src + P_UY * PLANE;
1525
+ const swS = src + P_SW * PLANE;
1526
+ const pS = src + P_P0 * PLANE;
1527
+ const eS = src + P_EDGE * PLANE;
1528
+ const omega = prm[PARAM.omega];
1529
+ const tau = prm[PARAM.tau];
1530
+ const evap = prm[PARAM.evap];
1531
+ const edgeEvap = prm[PARAM.edgeEvap];
1532
+ const mobility = prm[PARAM.mobility];
1533
+ const depRate = prm[PARAM.depRate];
1534
+ const pickup = prm[PARAM.pickup];
1535
+ const absorption = prm[PARAM.absorption];
1536
+ const invCap = prm[PARAM.invCap];
1537
+ const blockScale = prm[PARAM.blockScale];
1538
+ const rough = prm[PARAM.rough];
1539
+ const pinT = prm[PARAM.pinT];
1540
+ const gx0 = prm[PARAM.gx];
1541
+ const gy0 = prm[PARAM.gy];
1542
+ const flowK = prm[PARAM.flowK];
1543
+ const gran = prm[PARAM.granulation];
1544
+ const edgeDep = prm[PARAM.edgeDep];
1545
+ const bloom = prm[PARAM.bloom];
1546
+ const paperEvap = prm[PARAM.paperEvap];
1547
+ const hasFlow = prm[PARAM.hasFlow] !== 0;
1548
+ const bleed = prm[PARAM.bleed];
1549
+ let flags = 0;
1550
+ for (let y = 1; y <= 256; y++) {
1551
+ for (let x = 1; x <= 256; x++) {
1552
+ const c = y * S + x;
1553
+ const hx = f[hP + c];
1554
+ const kx = F(blockScale * hx);
1555
+ const rx = f[rhoS + c];
1556
+ const sx = f[swS + c];
1557
+ const damp = Math.max(0, F(1 - F(sx * invCap)));
1558
+ const pinX = F(F(pinT + F(rough * hx)) * damp);
1559
+ const rxDry = rx < EPS;
1560
+ const xWall = hx < 0;
1561
+ fin[0] = f[src + c];
1562
+ let dryNb = 0;
1563
+ for (let k = 1; k <= 8; k++) {
1564
+ const n = c + NB[k];
1565
+ const hn = f[hP + n];
1566
+ const rn = f[rhoS + n];
1567
+ const sn = f[swS + n];
1568
+ const wall = hn < 0;
1569
+ const rnDry = rn < EPS;
1570
+ const dampN = Math.max(0, F(1 - F(sn * invCap)));
1571
+ if (rnDry && !wall) dryNb = F(dryNb + dampN);
1572
+ let b = Math.max(kx, F(blockScale * hn));
1573
+ const pinN = F(F(pinT + F(rough * hn)) * dampN);
1574
+ if (wall || xWall || rxDry && rn < pinX || rnDry && rx < pinN) b = 1;
1575
+ fin[k] = F(F(F(1 - b) * f[src + k * PLANE + n]) + F(b * f[src + OPP2[k] * PLANE + c]));
1576
+ }
1577
+ const rho = F(F(F(F(F(F(F(F(fin[0] + fin[1]) + fin[2]) + fin[3]) + fin[4]) + fin[5]) + fin[6]) + fin[7]) + fin[8]);
1578
+ const mx = F(F(F(F(F(fin[1] - fin[3]) + fin[5]) - fin[6]) - fin[7]) + fin[8]);
1579
+ const my = F(F(F(F(F(fin[2] - fin[4]) + fin[5]) + fin[6]) - fin[7]) - fin[8]);
1580
+ let gx = gx0;
1581
+ let gy = gy0;
1582
+ if (hasFlow) {
1583
+ gx = F(gx + F(flowK * f[base + P_FX * PLANE + c]));
1584
+ gy = F(gy + F(flowK * f[base + P_FY * PLANE + c]));
1585
+ }
1586
+ let ux = 0;
1587
+ let uy = 0;
1588
+ if (rho > EPS) {
1589
+ ux = F(F(mx / rho) + F(tau * gx));
1590
+ uy = F(F(my / rho) + F(tau * gy));
1591
+ }
1592
+ const sp2 = F(F(ux * ux) + F(uy * uy));
1593
+ if (sp2 > UMAX2) {
1594
+ const sc = F(UMAXF / F(Math.sqrt(sp2)));
1595
+ ux = F(ux * sc);
1596
+ uy = F(uy * sc);
1597
+ }
1598
+ const absorbed = Math.min(rho, F(F(F(absorption * F(1 - F(0.5 * hx))) * rho) * damp));
1599
+ const ex = f[eS + c];
1600
+ const ev = rho > 0 ? F(evap * F(1 + F(edgeEvap * ex))) : 0;
1601
+ const rhoNew = Math.max(0, F(F(rho - absorbed) - ev));
1602
+ const scale = rho > TINY ? F(rhoNew / rho) : 0;
1603
+ const sNew = Math.max(0, F(F(sx + absorbed) - (sx > 0 ? paperEvap : 0)));
1604
+ f[dst + P_SW * PLANE + c] = sNew;
1605
+ const usq = F(1.5 * F(F(ux * ux) + F(uy * uy)));
1606
+ const wr0 = F(W0 * rho);
1607
+ const wr1 = F(W1 * rho);
1608
+ const wr2 = F(W2 * rho);
1609
+ let feq = F(wr0 * F(1 - usq));
1610
+ f[dst + c] = F(F(fin[0] + F(omega * F(feq - fin[0]))) * scale);
1611
+ for (let k = 1; k <= 8; k++) {
1612
+ let eu;
1613
+ let wr = wr1;
1614
+ switch (k) {
1615
+ case 1:
1616
+ eu = ux;
1617
+ break;
1618
+ case 2:
1619
+ eu = uy;
1620
+ break;
1621
+ case 3:
1622
+ eu = -ux;
1623
+ break;
1624
+ case 4:
1625
+ eu = -uy;
1626
+ break;
1627
+ case 5:
1628
+ eu = F(ux + uy);
1629
+ wr = wr2;
1630
+ break;
1631
+ case 6:
1632
+ eu = F(uy - ux);
1633
+ wr = wr2;
1634
+ break;
1635
+ case 7:
1636
+ eu = -F(ux + uy);
1637
+ wr = wr2;
1638
+ break;
1639
+ default:
1640
+ eu = F(ux - uy);
1641
+ wr = wr2;
1642
+ break;
1643
+ }
1644
+ feq = F(wr * F(F(F(1 + F(3 * eu)) + F(F(4.5 * eu) * eu)) - usq));
1645
+ f[dst + k * PLANE + c] = F(F(fin[k] + F(omega * F(feq - fin[k]))) * scale);
1646
+ }
1647
+ const rxWet = !rxDry;
1648
+ const uxc = f[uxS + c];
1649
+ const uyc = f[uyS + c];
1650
+ for (let q = 0; q < 4; q++) pNew[q] = f[pS + q * PLANE + c];
1651
+ for (let face = 0; face < 4; face++) {
1652
+ const nb = c + (face === 0 ? 1 : face === 1 ? -1 : face === 2 ? S : -S);
1653
+ const open = rxWet && f[rhoS + nb] >= EPS;
1654
+ const uf = face === 0 ? F(mobility * F(uxc + f[uxS + nb])) : face === 1 ? F(mobility * F(f[uxS + nb] + uxc)) : face === 2 ? F(mobility * F(uyc + f[uyS + nb])) : F(mobility * F(f[uyS + nb] + uyc));
1655
+ const out = face === 0 || face === 2;
1656
+ for (let q = 0; q < 4; q++) {
1657
+ const pc = f[pS + q * PLANE + c];
1658
+ const pn = f[pS + q * PLANE + nb];
1659
+ const flux = out ? uf > 0 ? F(uf * pc) : F(uf * pn) : uf > 0 ? F(uf * pn) : F(uf * pc);
1660
+ const diff = F(bleed * F(pn - pc));
1661
+ const v = out ? F(F(pNew[q] - flux) + diff) : F(F(pNew[q] + flux) + diff);
1662
+ pNew[q] = open ? v : pNew[q];
1663
+ }
1664
+ }
1665
+ const dryNow = rhoNew < EPS;
1666
+ const rate = F(F(depRate * F(1 + F(gran * F(1 - hx)))) * F(1 + F(edgeDep * ex)));
1667
+ const lift = dryNow ? 0 : F(F(pickup * Math.min(rhoNew, 1)) * F(1 + F(bloom * Math.max(0, F(rhoNew - rx)))));
1668
+ for (let q = 0; q < 4; q++) {
1669
+ const pq = Math.max(0, pNew[q]);
1670
+ const dq = f[dP + q * PLANE + c];
1671
+ const dep = dryNow ? pq : Math.min(pq, F(pq * rate));
1672
+ const lq = F(dq * lift);
1673
+ f[dst + (P_P0 + q) * PLANE + c] = F(F(pq - dep) + lq);
1674
+ f[dP + q * PLANE + c] = F(F(dq + dep) - lq);
1675
+ }
1676
+ const mean4 = F(F(F(F(f[eS + c + 1] + f[eS + c - 1]) + f[eS + c + S]) + f[eS + c - S]) * 0.25);
1677
+ const eNew = Math.min(1, Math.max(Math.min(1, F(dryNb * 0.25)), F(DECAY * mean4)));
1678
+ f[dst + P_EDGE * PLANE + c] = dryNow ? 0 : eNew;
1679
+ f[dst + P_RHO * PLANE + c] = rhoNew;
1680
+ f[dst + P_UX * PLANE + c] = dryNow ? 0 : ux;
1681
+ f[dst + P_UY * PLANE + c] = dryNow ? 0 : uy;
1682
+ const alive = rhoNew > 0 || sNew > 0;
1683
+ if (alive) flags |= FLAG_ALIVE | edgeBits(x - 1, y - 1);
1684
+ if (!dryNow) flags |= FLAG_WET;
1685
+ }
1686
+ }
1687
+ return flags;
1688
+ }
1689
+
1690
+ // src/paint/wet/wasm.ts
1691
+ var modules = {};
1692
+ function decode(base64) {
1693
+ const g = globalThis;
1694
+ if (g.Buffer) return new Uint8Array(g.Buffer.from(base64, "base64"));
1695
+ const bin = g.atob(base64);
1696
+ const out = new Uint8Array(bin.length);
1697
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
1698
+ return out;
1699
+ }
1700
+ function loadWetKernel(shared) {
1701
+ const key = shared ? "shared" : "single";
1702
+ let p = modules[key];
1703
+ if (!p) {
1704
+ p = Promise.resolve().then(() => _interopRequireWildcard(require("./kernel-wasm-YPVAEMVN.cjs"))).then((m) => WebAssembly.compile(decode(shared ? m.WET_KERNEL_SHARED : m.WET_KERNEL_SINGLE)));
1705
+ p.catch(() => {
1706
+ modules = { ...modules, [key]: void 0 };
1707
+ });
1708
+ modules[key] = p;
1709
+ }
1710
+ return p;
1711
+ }
1712
+ function canShareMemory() {
1713
+ const g = globalThis;
1714
+ if (typeof g.SharedArrayBuffer !== "function") return false;
1715
+ if (g.crossOriginIsolated === false) return false;
1716
+ try {
1717
+ new WebAssembly.Memory({ initial: 1, maximum: 1, shared: true });
1718
+ return true;
1719
+ } catch (e3) {
1720
+ return false;
1721
+ }
1722
+ }
1723
+ var simdCache = null;
1724
+ async function wetSimdSupported() {
1725
+ if (simdCache !== null) return simdCache;
1726
+ if (typeof WebAssembly === "undefined") return simdCache = false;
1727
+ try {
1728
+ await loadWetKernel(false);
1729
+ simdCache = true;
1730
+ } catch (e4) {
1731
+ simdCache = false;
1732
+ }
1733
+ return simdCache;
1734
+ }
1735
+ function wetSupport(simd = _nullishCoalesce(simdCache, () => ( typeof WebAssembly !== "undefined"))) {
1736
+ const g = globalThis;
1737
+ const sharedMemory = canShareMemory();
1738
+ const workers = typeof g.Worker === "function" && typeof Blob === "function" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function";
1739
+ const cores = Math.max(1, Math.floor(_nullishCoalesce(_optionalChain([g, 'access', _7 => _7.navigator, 'optionalAccess', _8 => _8.hardwareConcurrency]), () => ( 1))));
1740
+ let message = null;
1741
+ if (!simd) message = "This browser has no WebAssembly SIMD, so wet paint runs in plain JavaScript: it flows much more slowly (the results are the same).";
1742
+ else if (workers && !sharedMemory)
1743
+ message = 'Wet paint runs on one thread because this page is not cross-origin isolated, so it flows more slowly. Serve the page with "Cross-Origin-Opener-Policy: same-origin" and "Cross-Origin-Embedder-Policy: require-corp" (or "credentialless") to use every core.';
1744
+ return { simd, sharedMemory, workers, cores, message };
1745
+ }
1746
+ function wetWorkerMain() {
1747
+ const scope = self;
1748
+ let exports = null;
1749
+ let memory = null;
1750
+ let L = null;
1751
+ let id = 0;
1752
+ function barrier(ctl, n) {
1753
+ const gen = Atomics.load(ctl, 1);
1754
+ if (Atomics.add(ctl, 0, 1) === n - 1) {
1755
+ Atomics.store(ctl, 0, 0);
1756
+ Atomics.add(ctl, 1, 1);
1757
+ Atomics.notify(ctl, 1);
1758
+ } else {
1759
+ while (Atomics.load(ctl, 1) === gen) Atomics.wait(ctl, 1, gen);
1760
+ }
1761
+ }
1762
+ function run(steps, n) {
1763
+ const ex = exports;
1764
+ const l = L;
1765
+ const buf = memory.buffer;
1766
+ const ctl = new Int32Array(buf, l.ctl, 16);
1767
+ const table = new Int32Array(buf, l.table, l.keys);
1768
+ const list = new Int32Array(buf, l.list, l.keys);
1769
+ const flags = new Uint32Array(buf, l.flags, l.keys);
1770
+ const last = new Uint32Array(buf, l.last, l.keys);
1771
+ const count = ctl[4];
1772
+ let parity = ctl[5];
1773
+ for (let s = 0; s < steps; s++) {
1774
+ for (; ; ) {
1775
+ const a = Atomics.add(ctl, 2, 1);
1776
+ if (a >= count) break;
1777
+ const key = list[a];
1778
+ const lane = key / l.perLane | 0;
1779
+ const f = ex.step(l.slots + table[key] * l.slotBytes, parity, l.params + lane * 128);
1780
+ flags[key] |= f;
1781
+ last[key] = f;
1782
+ }
1783
+ if (id === 0) Atomics.store(ctl, 3, 0);
1784
+ barrier(ctl, n);
1785
+ for (; ; ) {
1786
+ const a = Atomics.add(ctl, 3, 1);
1787
+ if (a >= count) break;
1788
+ ex.halo(l.table, l.slots, list[a], l.perLane, l.tilesX, l.tilesY, 1 - parity);
1789
+ }
1790
+ if (id === 0) Atomics.store(ctl, 2, 0);
1791
+ barrier(ctl, n);
1792
+ parity = 1 - parity;
1793
+ }
1794
+ }
1795
+ scope.onmessage = (e) => {
1796
+ const m = e.data;
1797
+ if (m.type === "init") {
1798
+ memory = m.memory;
1799
+ L = m.layout;
1800
+ id = m.id;
1801
+ WebAssembly.instantiate(m.module, { env: { memory } }).then(
1802
+ (inst) => {
1803
+ exports = inst.exports;
1804
+ scope.postMessage({ type: "ready", id });
1805
+ },
1806
+ (err) => scope.postMessage({ type: "error", id, message: String(err) })
1807
+ );
1808
+ } else if (m.type === "layout") {
1809
+ L = m.layout;
1810
+ } else if (m.type === "run") {
1811
+ try {
1812
+ run(m.steps, m.workers);
1813
+ scope.postMessage({ type: "done", id });
1814
+ } catch (err) {
1815
+ scope.postMessage({ type: "error", id, message: String(err) });
1816
+ }
1817
+ }
1818
+ };
1819
+ }
1820
+ var WetWorkerPool = class _WetWorkerPool {
1821
+ constructor(workers, url) {
1822
+ this.workers = workers;
1823
+ this.url = url;
1824
+ }
1825
+
1826
+
1827
+ get size() {
1828
+ return this.workers.length;
1829
+ }
1830
+ /** Starts `count` workers on `memory` (shared) with the shared kernel. Rejects if any fails. */
1831
+ static async create(count, module, memory, layout) {
1832
+ const source = `(${wetWorkerMain.toString()})();`;
1833
+ const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
1834
+ const workers = [];
1835
+ try {
1836
+ const ready = [];
1837
+ for (let i = 0; i < count; i++) {
1838
+ const w = new Worker(url);
1839
+ workers.push(w);
1840
+ ready.push(
1841
+ new Promise((resolve, reject) => {
1842
+ w.onmessage = (e) => {
1843
+ if (_optionalChain([e, 'access', _9 => _9.data, 'optionalAccess', _10 => _10.type]) === "ready") resolve();
1844
+ else if (_optionalChain([e, 'access', _11 => _11.data, 'optionalAccess', _12 => _12.type]) === "error") reject(new Error(e.data.message));
1845
+ };
1846
+ w.onerror = (e) => reject(new Error(e.message || "The wet-media worker failed to start."));
1847
+ })
1848
+ );
1849
+ w.postMessage({ type: "init", module, memory, layout, id: i });
1850
+ }
1851
+ await Promise.all(ready);
1852
+ } catch (e) {
1853
+ for (const w of workers) w.terminate();
1854
+ URL.revokeObjectURL(url);
1855
+ throw e;
1856
+ }
1857
+ return new _WetWorkerPool(workers, url);
1858
+ }
1859
+ /** Runs `steps` steps over the active list in shared memory; resolves when every worker is done. */
1860
+ run(steps) {
1861
+ const n = this.workers.length;
1862
+ return Promise.all(
1863
+ this.workers.map(
1864
+ (w, i) => new Promise((resolve, reject) => {
1865
+ w.onmessage = (e) => {
1866
+ if (_optionalChain([e, 'access', _13 => _13.data, 'optionalAccess', _14 => _14.type]) === "done") resolve();
1867
+ else if (_optionalChain([e, 'access', _15 => _15.data, 'optionalAccess', _16 => _16.type]) === "error") reject(new Error(e.data.message));
1868
+ };
1869
+ w.postMessage({ type: "run", steps, workers: n, id: i });
1870
+ })
1871
+ )
1872
+ ).then(() => void 0);
1873
+ }
1874
+ destroy() {
1875
+ for (const w of this.workers) w.terminate();
1876
+ URL.revokeObjectURL(this.url);
1877
+ }
1878
+ };
1879
+
1880
+ // src/paint/wet/world.ts
1881
+ var MAX_WET_LANES = 8;
1882
+ var MAX_WET_SLOTS = 352;
1883
+ var SLOT_FLOATS = SLOT_PLANES * PLANE;
1884
+ var S2 = SLOT_SIDE;
1885
+ var SNAP_PLANES = SET_PLANES + 4;
1886
+ var WetWorldError = class extends Error {
1887
+ constructor(message) {
1888
+ super(message);
1889
+ this.name = "WetWorldError";
1890
+ }
1891
+ };
1892
+ function makeLayout(width, height) {
1893
+ const tilesX = Math.ceil(width / WET_TILE);
1894
+ const tilesY = Math.ceil(height / WET_TILE);
1895
+ const perLane = tilesX * tilesY;
1896
+ const keys = perLane * MAX_WET_LANES;
1897
+ const ctl = 0;
1898
+ const params = 1024;
1899
+ const table = params + MAX_WET_LANES * PARAM_FLOATS * 4;
1900
+ const list = table + keys * 4;
1901
+ const flags = list + keys * 4;
1902
+ const last = flags + keys * 4;
1903
+ const slots = Math.ceil((last + keys * 4) / 65536) * 65536;
1904
+ return { width, height, tilesX, tilesY, perLane, keys, ctl, params, table, list, flags, last, slots, slotBytes: SLOT_BYTES };
1905
+ }
1906
+ function quantizeHeight(v) {
1907
+ return Math.round(Math.min(1, Math.max(0, v)) * 65536) / 65536;
1908
+ }
1909
+ var WetWorld = (_class8 = class {
1910
+
1911
+
1912
+
1913
+ /** Steps simulated so far. */
1914
+ __init29() {this.clock = 0}
1915
+ /** Messages for the user (slots ran out, a backend failed). */
1916
+ __init30() {this.warnings = []}
1917
+
1918
+
1919
+
1920
+
1921
+
1922
+ __init31() {this.slotCount = 0}
1923
+ __init32() {this.freeSlots = []}
1924
+ __init33() {this.active = []}
1925
+ __init34() {this.parity = 0}
1926
+ __init35() {this.lanes = new Array(MAX_WET_LANES).fill(null)}
1927
+ __init36() {this.tilt = { ...FLAT }}
1928
+
1929
+ __init37() {this.paperBytes = new Uint8Array(3)}
1930
+ __init38() {this.kind = "typescript"}
1931
+ __init39() {this.wasm = null}
1932
+ __init40() {this.pool = null}
1933
+ __init41() {this.busyFlag = false}
1934
+ __init42() {this.destroyed = false}
1935
+
1936
+ __init43() {this.prm = new Float32Array(PARAM_FLOATS)}
1937
+ /** Resolves when the fastest available kernel is running (it may stay `typescript`). */
1938
+
1939
+ constructor(width, height, paper, options = {}) {;_class8.prototype.__init29.call(this);_class8.prototype.__init30.call(this);_class8.prototype.__init31.call(this);_class8.prototype.__init32.call(this);_class8.prototype.__init33.call(this);_class8.prototype.__init34.call(this);_class8.prototype.__init35.call(this);_class8.prototype.__init36.call(this);_class8.prototype.__init37.call(this);_class8.prototype.__init38.call(this);_class8.prototype.__init39.call(this);_class8.prototype.__init40.call(this);_class8.prototype.__init41.call(this);_class8.prototype.__init42.call(this);_class8.prototype.__init43.call(this);
1940
+ if (!(width >= 1 && height >= 1 && width <= 16384 && height <= 16384)) throw new WetWorldError(`A wet canvas must be 1..16384 pixels per side, not ${width}\xD7${height}.`);
1941
+ this.width = Math.round(width);
1942
+ this.height = Math.round(height);
1943
+ this.paper = paper;
1944
+ this.paperBytes.set(paper.color);
1945
+ this.layout = makeLayout(this.width, this.height);
1946
+ const wantKernel = _nullishCoalesce(options.kernel, () => ( "auto"));
1947
+ const threads = _nullishCoalesce(options.threads, () => ( "auto"));
1948
+ this.shared = wantKernel === "auto" && threads !== 0 && canShareMemory();
1949
+ const pages = this.layout.slots / 65536 + Math.ceil(2 * SLOT_BYTES / 65536);
1950
+ const maxPages = Math.min(65536, Math.ceil((this.layout.slots + MAX_WET_SLOTS * SLOT_BYTES) / 65536));
1951
+ this.memory = this.shared ? new WebAssembly.Memory({ initial: pages, maximum: maxPages, shared: true }) : new WebAssembly.Memory({ initial: pages });
1952
+ this.views();
1953
+ new Int32Array(this.memory.buffer, this.layout.table, this.layout.keys).fill(-1);
1954
+ this.support = wetSupport(true);
1955
+ this.ready = wantKernel === "typescript" ? Promise.resolve(this.backend()) : this.start(threads);
1956
+ }
1957
+ // --- Backends ------------------------------------------------------------------
1958
+ views() {
1959
+ const buf = this.memory.buffer;
1960
+ this.f32 = new Float32Array(buf);
1961
+ this.i32 = new Int32Array(buf);
1962
+ this.u32 = new Uint32Array(buf);
1963
+ }
1964
+ async start(threads) {
1965
+ try {
1966
+ const simd = await wetSimdSupported();
1967
+ this.support = wetSupport(simd);
1968
+ if (!simd || this.destroyed) return this.backend();
1969
+ const module = await loadWetKernel(this.shared);
1970
+ const inst = await WebAssembly.instantiate(module, { env: { memory: this.memory } });
1971
+ if (this.destroyed) return this.backend();
1972
+ this.wasm = inst.exports;
1973
+ this.kind = "wasm";
1974
+ const n = threads === "auto" ? Math.max(1, Math.min(10, this.support.cores - 1)) : Math.max(0, Math.floor(threads));
1975
+ if (this.shared && this.support.workers && n >= 2) {
1976
+ try {
1977
+ const pool = await WetWorkerPool.create(n, module, this.memory, this.layout);
1978
+ if (this.destroyed) pool.destroy();
1979
+ else {
1980
+ this.pool = pool;
1981
+ this.kind = "wasm-threads";
1982
+ }
1983
+ } catch (e) {
1984
+ this.warnings.push(`Wet paint could not start its worker threads (${e.message}); it runs on one thread.`);
1985
+ }
1986
+ }
1987
+ } catch (e) {
1988
+ this.warnings.push(`The WebAssembly wet-media kernel did not load (${e.message}); wet paint runs in plain JavaScript.`);
1989
+ }
1990
+ return this.backend();
1991
+ }
1992
+ /** The kernel in use and why it is not faster. */
1993
+ backend() {
1994
+ const threads = _nullishCoalesce(_optionalChain([this, 'access', _17 => _17.pool, 'optionalAccess', _18 => _18.size]), () => ( 1));
1995
+ let message = this.support.message;
1996
+ if (this.kind === "typescript" && !message) message = this.wasm ? null : "Wet paint runs in plain JavaScript until the WebAssembly kernel has loaded.";
1997
+ if (this.kind === "wasm-threads") message = null;
1998
+ return { kind: this.kind, threads, message };
1999
+ }
2000
+ /** Whether a batch is running (edits throw until it ends). */
2001
+ get busy() {
2002
+ return this.busyFlag;
2003
+ }
2004
+ destroy() {
2005
+ this.destroyed = true;
2006
+ _optionalChain([this, 'access', _19 => _19.pool, 'optionalAccess', _20 => _20.destroy, 'call', _21 => _21()]);
2007
+ this.pool = null;
2008
+ }
2009
+ // --- Lanes ---------------------------------------------------------------------
2010
+ laneOf(id) {
2011
+ const l = this.lanes.find((x) => _optionalChain([x, 'optionalAccess', _22 => _22.id]) === id);
2012
+ if (!l) throw new WetWorldError(`There is no wet layer "${id}".`);
2013
+ return l;
2014
+ }
2015
+ hasLane(id) {
2016
+ return this.lanes.some((x) => _optionalChain([x, 'optionalAccess', _23 => _23.id]) === id);
2017
+ }
2018
+ laneIds() {
2019
+ return this.lanes.filter((l) => Boolean(l)).map((l) => l.id);
2020
+ }
2021
+ /** Adds a wet layer (or returns it). Throws when `MAX_WET_LANES` are in use. */
2022
+ ensureLane(id, options = {}) {
2023
+ this.requireIdle();
2024
+ const existing = this.lanes.find((x) => _optionalChain([x, 'optionalAccess', _24 => _24.id]) === id);
2025
+ if (existing) {
2026
+ if (options.params) this.setParams(id, options.params);
2027
+ if (options.base) this.setBase(id, options.base);
2028
+ return;
2029
+ }
2030
+ const index = this.lanes.indexOf(null);
2031
+ if (index < 0) throw new WetWorldError(`At most ${MAX_WET_LANES} layers can hold wet paint at once; dry one (dry_paint) first.`);
2032
+ this.lanes[index] = {
2033
+ id,
2034
+ index,
2035
+ params: normalizeWetParams(_nullishCoalesce(options.params, () => ( {})), DEFAULT_WET_PARAMS),
2036
+ flow: _nullishCoalesce(options.flow, () => ( null)),
2037
+ flowStrength: options.flow ? _nullishCoalesce(options.flowStrength, () => ( 1)) : 0,
2038
+ base: _nullishCoalesce(options.base, () => ( new (0, _chunkTFOPHZ3Ucjs.TiledSurface)(4, /* @__PURE__ */ new Map(), 0))),
2039
+ shaded: /* @__PURE__ */ new Map(),
2040
+ urgent: /* @__PURE__ */ new Set(),
2041
+ stamp: 1,
2042
+ display: null,
2043
+ shadedVersion: 0,
2044
+ displayKey: ""
2045
+ };
2046
+ this.pack(this.lanes[index]);
2047
+ }
2048
+ /** Drops a wet layer and its wet paint (nothing is baked). */
2049
+ removeLane(id) {
2050
+ this.requireIdle();
2051
+ const lane = this.laneOf(id);
2052
+ for (const key of [...this.active]) if (this.laneIndexOf(key) === lane.index) this.free(key);
2053
+ this.lanes[lane.index] = null;
2054
+ }
2055
+ getParams(id) {
2056
+ return { ...this.laneOf(id).params };
2057
+ }
2058
+ setParams(id, params) {
2059
+ this.requireIdle();
2060
+ const lane = this.laneOf(id);
2061
+ lane.params = normalizeWetParams(params, lane.params);
2062
+ this.pack(lane);
2063
+ return { ...lane.params };
2064
+ }
2065
+ getTilt() {
2066
+ return { ...this.tilt };
2067
+ }
2068
+ setTilt(tilt) {
2069
+ this.requireIdle();
2070
+ this.tilt = normalizeTilt(tilt, this.tilt);
2071
+ for (const l of this.lanes) if (l) this.pack(l);
2072
+ return { ...this.tilt };
2073
+ }
2074
+ /** Changes the paper: heights of live tiles are refilled, rates repacked. */
2075
+ setPaper(paper) {
2076
+ this.requireIdle();
2077
+ this.paper = paper;
2078
+ this.paperBytes.set(paper.color);
2079
+ for (const key of this.active) this.fillPaper(key);
2080
+ for (const l of this.lanes) {
2081
+ if (!l) continue;
2082
+ this.pack(l);
2083
+ this.touchAll(l);
2084
+ }
2085
+ this.refreshHalos();
2086
+ }
2087
+ /** Sets a layer's flow map (`null` removes it). */
2088
+ setFlow(id, flow, strength = 1) {
2089
+ this.requireIdle();
2090
+ const lane = this.laneOf(id);
2091
+ lane.flow = flow;
2092
+ lane.flowStrength = flow ? strength : 0;
2093
+ this.pack(lane);
2094
+ for (const key of this.active) if (this.laneIndexOf(key) === lane.index) this.fillFlow(key);
2095
+ }
2096
+ hasFlow(id) {
2097
+ return Boolean(this.laneOf(id).flow);
2098
+ }
2099
+ /** The pixels under a layer's wet paint. */
2100
+ getBase(id) {
2101
+ return this.laneOf(id).base;
2102
+ }
2103
+ /** Replaces the pixels under a layer's wet paint. */
2104
+ setBase(id, base) {
2105
+ const lane = this.laneOf(id);
2106
+ if (base === lane.base) return;
2107
+ lane.base = base;
2108
+ lane.shaded.clear();
2109
+ lane.shadedVersion++;
2110
+ lane.stamp++;
2111
+ }
2112
+ pack(lane) {
2113
+ packWetParams(lane.params, _nullishCoalesce(this.paper, () => ( DEFAULT_WET_PAPER)), this.tilt, lane.flowStrength, this.prm);
2114
+ this.f32.set(this.prm, this.layout.params + lane.index * PARAM_FLOATS * 4 >> 2);
2115
+ }
2116
+ touchAll(lane) {
2117
+ lane.stamp++;
2118
+ }
2119
+ // --- Slots ---------------------------------------------------------------------
2120
+ laneIndexOf(key) {
2121
+ return Math.floor(key / this.layout.perLane);
2122
+ }
2123
+ slotOf(key) {
2124
+ return this.i32[(this.layout.table >> 2) + key];
2125
+ }
2126
+ slotBase(slot) {
2127
+ return (this.layout.slots >> 2) + slot * SLOT_FLOATS;
2128
+ }
2129
+ requireIdle() {
2130
+ if (this.busyFlag) throw new WetWorldError("The wet simulation is mid-step; wait for it (whenIdle) before changing it.");
2131
+ }
2132
+ /** Writes the active list into memory for the kernels. */
2133
+ syncList() {
2134
+ const base = this.layout.list >> 2;
2135
+ this.active.sort((a, b) => a - b);
2136
+ for (let i = 0; i < this.active.length; i++) this.i32[base + i] = this.active[i];
2137
+ this.i32[(this.layout.ctl >> 2) + 4] = this.active.length;
2138
+ }
2139
+ grow(slots) {
2140
+ const need = this.layout.slots + slots * SLOT_BYTES;
2141
+ const have = this.memory.buffer.byteLength;
2142
+ if (need <= have) return true;
2143
+ try {
2144
+ this.memory.grow(Math.ceil((need - have) / 65536));
2145
+ } catch (e5) {
2146
+ return false;
2147
+ }
2148
+ this.views();
2149
+ return true;
2150
+ }
2151
+ /** Activates a tile of a lane. Returns false when no slot can be had. */
2152
+ activate(key) {
2153
+ if (this.slotOf(key) >= 0) return true;
2154
+ let slot = this.freeSlots.pop();
2155
+ if (slot === void 0) {
2156
+ if (this.slotCount >= MAX_WET_SLOTS || !this.grow(this.slotCount + 1)) {
2157
+ const msg = `Wet paint reached its memory limit (${MAX_WET_SLOTS} tiles of 256 px); water spreading further is lost. Dry some paint (dry_paint) to free memory.`;
2158
+ if (!this.warnings.includes(msg)) this.warnings.push(msg);
2159
+ return false;
2160
+ }
2161
+ slot = this.slotCount++;
2162
+ }
2163
+ const base = this.slotBase(slot);
2164
+ this.f32.fill(0, base, base + SLOT_FLOATS);
2165
+ this.i32[(this.layout.table >> 2) + key] = slot;
2166
+ this.u32[(this.layout.flags >> 2) + key] = 0;
2167
+ this.u32[(this.layout.last >> 2) + key] = 0;
2168
+ this.active.push(key);
2169
+ this.fillPaper(key);
2170
+ this.fillFlow(key);
2171
+ return true;
2172
+ }
2173
+ free(key) {
2174
+ const slot = this.slotOf(key);
2175
+ if (slot < 0) return;
2176
+ this.i32[(this.layout.table >> 2) + key] = -1;
2177
+ this.freeSlots.push(slot);
2178
+ const i = this.active.indexOf(key);
2179
+ if (i >= 0) this.active.splice(i, 1);
2180
+ const lane = this.lanes[this.laneIndexOf(key)];
2181
+ if (lane) {
2182
+ if (lane.shaded.delete(this.tileKeyOf(key))) lane.shadedVersion++;
2183
+ lane.stamp++;
2184
+ }
2185
+ }
2186
+ tileXY(key) {
2187
+ const t = key % this.layout.perLane;
2188
+ return [t % this.layout.tilesX, Math.floor(t / this.layout.tilesX)];
2189
+ }
2190
+ tileKeyOf(key) {
2191
+ const [tx, ty] = this.tileXY(key);
2192
+ return _chunkTFOPHZ3Ucjs.tileKey.call(void 0, tx, ty);
2193
+ }
2194
+ fillPaper(key) {
2195
+ const [tx, ty] = this.tileXY(key);
2196
+ const h = this.slotBase(this.slotOf(key)) + P_H * PLANE;
2197
+ const { width, height } = this;
2198
+ for (let y = 0; y < S2; y++) {
2199
+ const gy = ty * WET_TILE + y - 1;
2200
+ for (let x = 0; x < S2; x++) {
2201
+ const gx = tx * WET_TILE + x - 1;
2202
+ this.f32[h + y * S2 + x] = gx < 0 || gy < 0 || gx >= width || gy >= height ? -1 : quantizeHeight(this.paper.heightAt(gx + 0.5, gy + 0.5));
2203
+ }
2204
+ }
2205
+ }
2206
+ fillFlow(key) {
2207
+ const lane = this.lanes[this.laneIndexOf(key)];
2208
+ const [tx, ty] = this.tileXY(key);
2209
+ const b = this.slotBase(this.slotOf(key));
2210
+ const fx = b + P_FX * PLANE;
2211
+ const fy = b + P_FY * PLANE;
2212
+ for (let y = 1; y <= WET_TILE; y++) {
2213
+ for (let x = 1; x <= WET_TILE; x++) {
2214
+ const v = lane.flow ? lane.flow(tx * WET_TILE + x - 1 + 0.5, ty * WET_TILE + y - 1 + 0.5) : null;
2215
+ this.f32[fx + y * S2 + x] = v ? Math.fround(v[0]) : 0;
2216
+ this.f32[fy + y * S2 + x] = v ? Math.fround(v[1]) : 0;
2217
+ }
2218
+ }
2219
+ }
2220
+ /** Activates every tile of a lane within `STRIP` cells of a document rectangle. */
2221
+ wake(lane, x0, y0, x1, y1) {
2222
+ const { tilesX, tilesY, perLane } = this.layout;
2223
+ const tx0 = Math.max(0, Math.floor((x0 - STRIP) / WET_TILE));
2224
+ const ty0 = Math.max(0, Math.floor((y0 - STRIP) / WET_TILE));
2225
+ const tx1 = Math.min(tilesX - 1, Math.floor((x1 + STRIP) / WET_TILE));
2226
+ const ty1 = Math.min(tilesY - 1, Math.floor((y1 + STRIP) / WET_TILE));
2227
+ let added = false;
2228
+ for (let ty = ty0; ty <= ty1; ty++) for (let tx = tx0; tx <= tx1; tx++) added = this.activate(lane.index * perLane + ty * tilesX + tx) || added;
2229
+ if (added) this.syncList();
2230
+ }
2231
+ /** Fills one slot's halo in the current set from its neighbours (TypeScript). */
2232
+ haloTs(key, set) {
2233
+ const { tilesX, tilesY, perLane } = this.layout;
2234
+ const laneBase = this.laneIndexOf(key) * perLane;
2235
+ const [tx, ty] = this.tileXY(key);
2236
+ const f = this.f32;
2237
+ const nb = (dx, dy) => {
2238
+ const x = tx + dx;
2239
+ const y = ty + dy;
2240
+ if (x < 0 || y < 0 || x >= tilesX || y >= tilesY) return -1;
2241
+ return this.slotOf(laneBase + y * tilesX + x);
2242
+ };
2243
+ const me = this.slotBase(this.slotOf(key)) + set * SET_PLANES * PLANE;
2244
+ const n = nb(0, -1);
2245
+ const s = nb(0, 1);
2246
+ const w = nb(-1, 0);
2247
+ const e = nb(1, 0);
2248
+ const nw = nb(-1, -1);
2249
+ const ne = nb(1, -1);
2250
+ const sw = nb(-1, 1);
2251
+ const se = nb(1, 1);
2252
+ const off = set * SET_PLANES * PLANE;
2253
+ for (let k = 0; k < SET_PLANES; k++) {
2254
+ const mp = me + k * PLANE;
2255
+ const o = off + k * PLANE;
2256
+ if (n >= 0) f.copyWithin(mp + 1, this.slotBase(n) + o + 256 * S2 + 1, this.slotBase(n) + o + 256 * S2 + 257);
2257
+ if (s >= 0) f.copyWithin(mp + 257 * S2 + 1, this.slotBase(s) + o + S2 + 1, this.slotBase(s) + o + S2 + 257);
2258
+ if (w >= 0) for (let y = 1; y <= 256; y++) f[mp + y * S2] = f[this.slotBase(w) + o + y * S2 + 256];
2259
+ if (e >= 0) for (let y = 1; y <= 256; y++) f[mp + y * S2 + 257] = f[this.slotBase(e) + o + y * S2 + 1];
2260
+ if (nw >= 0) f[mp] = f[this.slotBase(nw) + o + 256 * S2 + 256];
2261
+ if (ne >= 0) f[mp + 257] = f[this.slotBase(ne) + o + 256 * S2 + 1];
2262
+ if (sw >= 0) f[mp + 257 * S2] = f[this.slotBase(sw) + o + S2 + 256];
2263
+ if (se >= 0) f[mp + 257 * S2 + 257] = f[this.slotBase(se) + o + S2 + 1];
2264
+ }
2265
+ }
2266
+ halo(key, set) {
2267
+ if (this.wasm) {
2268
+ const l = this.layout;
2269
+ this.wasm.halo(l.table, l.slots, key, l.perLane, l.tilesX, l.tilesY, set);
2270
+ } else this.haloTs(key, set);
2271
+ }
2272
+ /** Refills every live slot's halo in the current set (after edits or wake-ups). */
2273
+ refreshHalos() {
2274
+ for (const key of this.active) this.halo(key, this.parity);
2275
+ }
2276
+ // --- Stepping ------------------------------------------------------------------
2277
+ /** Whether anything is being simulated (in `id`, or any layer). */
2278
+ isActive(id) {
2279
+ if (id === void 0) return this.active.length > 0;
2280
+ const lane = this.laneOf(id);
2281
+ return this.active.some((k) => this.laneIndexOf(k) === lane.index);
2282
+ }
2283
+ /** Steps until the next batch boundary: at most `want`, `MAX_BATCH`, and the next free check. */
2284
+ batchSize(want) {
2285
+ const toFree = FREE_INTERVAL - this.clock % FREE_INTERVAL;
2286
+ return Math.max(1, Math.min(want, MAX_BATCH, toFree));
2287
+ }
2288
+ /** Runs `n` steps on this thread (TypeScript or wasm kernel). */
2289
+ runHere(n) {
2290
+ const l = this.layout;
2291
+ const flagsBase = l.flags >> 2;
2292
+ const lastBase = l.last >> 2;
2293
+ const prmBase = l.params >> 2;
2294
+ for (let s = 0; s < n; s++) {
2295
+ for (const key of this.active) {
2296
+ const slot = this.slotOf(key);
2297
+ const lane = this.laneIndexOf(key);
2298
+ let fl;
2299
+ if (this.wasm) fl = this.wasm.step(l.slots + slot * SLOT_BYTES, this.parity, l.params + lane * PARAM_FLOATS * 4);
2300
+ else fl = stepSlotTs(this.f32, this.slotBase(slot), this.parity, this.f32.subarray(prmBase + lane * PARAM_FLOATS, prmBase + (lane + 1) * PARAM_FLOATS));
2301
+ this.u32[flagsBase + key] |= fl;
2302
+ this.u32[lastBase + key] = fl;
2303
+ }
2304
+ for (const key of this.active) this.halo(key, 1 - this.parity);
2305
+ this.parity = 1 - this.parity;
2306
+ }
2307
+ }
2308
+ /** After a batch: wake neighbours from the flags, free dead tiles on a free step, restamp. */
2309
+ afterBatch(n) {
2310
+ this.clock += n;
2311
+ const l = this.layout;
2312
+ const flagsBase = l.flags >> 2;
2313
+ const lastBase = l.last >> 2;
2314
+ const { tilesX, tilesY, perLane } = l;
2315
+ const wakeups = [];
2316
+ for (const key of this.active) {
2317
+ const fl = this.u32[flagsBase + key];
2318
+ this.u32[flagsBase + key] = 0;
2319
+ if (!(fl & FLAG_ALIVE)) continue;
2320
+ const laneBase = this.laneIndexOf(key) * perLane;
2321
+ const [tx, ty] = this.tileXY(key);
2322
+ const L = fl & FLAG_LEFT;
2323
+ const R = fl & FLAG_RIGHT;
2324
+ const T = fl & FLAG_TOP;
2325
+ const B = fl & FLAG_BOTTOM;
2326
+ const push = (x, y) => {
2327
+ if (x >= 0 && y >= 0 && x < tilesX && y < tilesY && this.slotOf(laneBase + y * tilesX + x) < 0) wakeups.push(laneBase + y * tilesX + x);
2328
+ };
2329
+ if (L) push(tx - 1, ty);
2330
+ if (R) push(tx + 1, ty);
2331
+ if (T) push(tx, ty - 1);
2332
+ if (B) push(tx, ty + 1);
2333
+ if (L && T) push(tx - 1, ty - 1);
2334
+ if (R && T) push(tx + 1, ty - 1);
2335
+ if (L && B) push(tx - 1, ty + 1);
2336
+ if (R && B) push(tx + 1, ty + 1);
2337
+ }
2338
+ let woke = false;
2339
+ for (const key of wakeups) woke = this.activate(key) || woke;
2340
+ if (this.clock % FREE_INTERVAL === 0) {
2341
+ const dead = this.active.filter((key) => {
2342
+ if (this.u32[lastBase + key] & FLAG_ALIVE) return false;
2343
+ const laneBase = this.laneIndexOf(key) * perLane;
2344
+ const [tx, ty] = this.tileXY(key);
2345
+ for (let dy = -1; dy <= 1; dy++)
2346
+ for (let dx = -1; dx <= 1; dx++) {
2347
+ const x = tx + dx;
2348
+ const y = ty + dy;
2349
+ if ((dx || dy) && x >= 0 && y >= 0 && x < tilesX && y < tilesY) {
2350
+ const nk = laneBase + y * tilesX + x;
2351
+ if (this.slotOf(nk) >= 0 && this.u32[lastBase + nk] & FLAG_ALIVE) return false;
2352
+ }
2353
+ }
2354
+ return true;
2355
+ });
2356
+ for (const key of dead) {
2357
+ this.bakeKey(key, false);
2358
+ this.free(key);
2359
+ }
2360
+ if (dead.length) woke = true;
2361
+ }
2362
+ if (woke) {
2363
+ this.syncList();
2364
+ this.refreshHalos();
2365
+ }
2366
+ for (const lane of this.lanes) if (lane) lane.stamp++;
2367
+ }
2368
+ /**
2369
+ * Steps the simulation `steps` times on this thread (the TypeScript or single-thread wasm
2370
+ * kernel). Throws while worker threads are in use; use `step` there.
2371
+ */
2372
+ stepSync(steps) {
2373
+ this.requireIdle();
2374
+ if (this.pool) throw new WetWorldError("This wet world steps in worker threads; call step (async) instead.");
2375
+ let left = Math.max(0, Math.floor(steps));
2376
+ while (left > 0) {
2377
+ if (!this.active.length) {
2378
+ this.clock += left;
2379
+ break;
2380
+ }
2381
+ const n = this.batchSize(left);
2382
+ this.runHere(n);
2383
+ this.afterBatch(n);
2384
+ left -= n;
2385
+ }
2386
+ return this.clock;
2387
+ }
2388
+ /** Steps the simulation `steps` times (in worker threads when there are any). */
2389
+ async step(steps) {
2390
+ this.requireIdle();
2391
+ if (!this.pool) return this.stepSync(steps);
2392
+ let left = Math.max(0, Math.floor(steps));
2393
+ while (left > 0) {
2394
+ if (!this.active.length) {
2395
+ this.clock += left;
2396
+ break;
2397
+ }
2398
+ const n = this.batchSize(left);
2399
+ const ctl = this.layout.ctl >> 2;
2400
+ this.i32[ctl + 2] = 0;
2401
+ this.i32[ctl + 3] = 0;
2402
+ this.i32[ctl + 5] = this.parity;
2403
+ this.busyFlag = true;
2404
+ try {
2405
+ await this.pool.run(n);
2406
+ } finally {
2407
+ this.busyFlag = false;
2408
+ }
2409
+ this.parity = (this.parity + n) % 2;
2410
+ this.afterBatch(n);
2411
+ left -= n;
2412
+ }
2413
+ return this.clock;
2414
+ }
2415
+ /** Steps until nothing is wet or `maxSteps` have run. Returns the steps run. */
2416
+ async stepUntilDry(maxSteps) {
2417
+ const start = this.clock;
2418
+ while (this.active.length && this.clock - start < maxSteps) await this.step(Math.min(FREE_INTERVAL, maxSteps - (this.clock - start)));
2419
+ return this.clock - start;
2420
+ }
2421
+ // --- Edits ---------------------------------------------------------------------
2422
+ /** Float index of a cell's plane-0 value in the current set, or −1 (inactive / off the canvas). */
2423
+ cell(lane, x, y) {
2424
+ if (x < 0 || y < 0 || x >= this.width || y >= this.height) return -1;
2425
+ const { tilesX, perLane } = this.layout;
2426
+ const slot = this.slotOf(lane.index * perLane + (y >> 8) * tilesX + (x >> 8));
2427
+ if (slot < 0) return -1;
2428
+ return this.slotBase(slot) + this.parity * SET_PLANES * PLANE + ((y & 255) + 1) * S2 + (x & 255) + 1;
2429
+ }
2430
+ /** Index of the same cell in the single (deposit, height, flow) planes, given `cell`. */
2431
+ single(c) {
2432
+ return c - this.parity * SET_PLANES * PLANE;
2433
+ }
2434
+ /**
2435
+ * Runs `fn` for every cell of a footprint (`cov` over `rect`, document cells) on a lane, waking
2436
+ * the tiles around it first, then refreshes halos. `fn` gets the cell index and its coverage.
2437
+ */
2438
+ footprint(id, rect, cov, fn, wake = true) {
2439
+ this.requireIdle();
2440
+ const lane = this.laneOf(id);
2441
+ const x0 = Math.max(0, rect.x);
2442
+ const y0 = Math.max(0, rect.y);
2443
+ const x1 = Math.min(this.width, rect.x + rect.width);
2444
+ const y1 = Math.min(this.height, rect.y + rect.height);
2445
+ if (x1 <= x0 || y1 <= y0) return;
2446
+ if (wake) this.wake(lane, x0, y0, x1 - 1, y1 - 1);
2447
+ for (let y = y0; y < y1; y++) {
2448
+ for (let x = x0; x < x1; x++) {
2449
+ const w = cov ? cov[(y - rect.y) * rect.width + (x - rect.x)] : 1;
2450
+ if (!(w > 0)) continue;
2451
+ const c = this.cell(lane, x, y);
2452
+ if (c >= 0) fn(c, w, x, y);
2453
+ }
2454
+ }
2455
+ this.markEdited(lane, x0, y0, x1, y1);
2456
+ this.refreshHalos();
2457
+ }
2458
+ markEdited(lane, x0, y0, x1, y1) {
2459
+ for (let ty = y0 >> 8; ty <= y1 - 1 >> 8; ty++) for (let tx = x0 >> 8; tx <= x1 - 1 >> 8; tx++) lane.urgent.add(_chunkTFOPHZ3Ucjs.tileKey.call(void 0, tx, ty));
2460
+ lane.stamp++;
2461
+ }
2462
+ /** Adds water (depth units) at rest. */
2463
+ addWaterAt(c, dw) {
2464
+ const f = this.f32;
2465
+ for (let k = 0; k < 9; k++) f[c + k * PLANE] += W9[k] * dw;
2466
+ f[c + P_RHO * PLANE] += dw;
2467
+ }
2468
+ /**
2469
+ * Lays paint: `water` depth and pigment mass `pigment` (K r, g, b, S) per unit of coverage, or
2470
+ * per pixel from `field` (4 values per pixel of `rect`). Pigment without water lands straight in
2471
+ * the paper (settled).
2472
+ */
2473
+ paint(id, rect, cov, water, pigment, field = null) {
2474
+ if (!(water >= 0)) throw new WetWorldError(`water ${water} must be 0 or more.`);
2475
+ this.footprint(id, rect, cov, (c, w, x, y) => {
2476
+ const dw = Math.fround(water * w);
2477
+ if (dw > 0) this.addWaterAt(c, dw);
2478
+ const into = dw > 0 ? c + P_P0 * PLANE : this.single(c) + P_D0 * PLANE;
2479
+ if (field) {
2480
+ const o = ((y - rect.y) * rect.width + (x - rect.x)) * 4;
2481
+ for (let q = 0; q < 4; q++) this.f32[into + q * PLANE] += field[o + q] * w;
2482
+ } else if (pigment) for (let q = 0; q < 4; q++) this.f32[into + q * PLANE] += pigment[q] * w;
2483
+ });
2484
+ }
2485
+ /** Damp paper: fills the paper's water towards its capacity by `amount` (0..1) × coverage, plus a thin film of surface water. */
2486
+ wetPaper(id, rect, cov, amount) {
2487
+ const capacity = PAPER_CAPACITY;
2488
+ this.footprint(id, rect, cov, (c, w) => {
2489
+ const a = Math.min(1, Math.max(0, amount * w));
2490
+ const s = c + P_SW * PLANE;
2491
+ this.f32[s] = Math.max(this.f32[s], Math.fround(capacity * a));
2492
+ this.addWaterAt(c, Math.fround(0.03 * a));
2493
+ });
2494
+ }
2495
+ /**
2496
+ * Takes water (and suspended pigment, and settled pigment) away: `water`, `suspended`,
2497
+ * `settled` are fractions 0..1 × coverage. A hair dryer takes water only (the pigment stays and
2498
+ * settles); blotting takes all three.
2499
+ */
2500
+ remove(id, rect, cov, water, suspended, settled, paperWater = water) {
2501
+ this.footprint(
2502
+ id,
2503
+ rect,
2504
+ cov,
2505
+ (c, w) => {
2506
+ const f = this.f32;
2507
+ const kw = 1 - Math.min(1, Math.max(0, water * w));
2508
+ const kp = 1 - Math.min(1, Math.max(0, suspended * w));
2509
+ const kd = 1 - Math.min(1, Math.max(0, settled * w));
2510
+ const ks = 1 - Math.min(1, Math.max(0, paperWater * w));
2511
+ if (kw < 1) {
2512
+ for (let k = 0; k < 9; k++) f[c + k * PLANE] *= kw;
2513
+ f[c + P_RHO * PLANE] *= kw;
2514
+ }
2515
+ if (ks < 1) f[c + P_SW * PLANE] *= ks;
2516
+ if (kp < 1) for (let q = 0; q < 4; q++) f[c + (P_P0 + q) * PLANE] *= kp;
2517
+ if (kd < 1) {
2518
+ const d = this.single(c) + P_D0 * PLANE;
2519
+ for (let q = 0; q < 4; q++) f[d + q * PLANE] *= kd;
2520
+ }
2521
+ },
2522
+ false
2523
+ );
2524
+ }
2525
+ /** Pushes the water: adds velocity (`ux`, `uy` cells per step, each at most 0.1) × coverage. */
2526
+ push(id, rect, cov, ux, uy) {
2527
+ const cap = 0.1;
2528
+ const sp = Math.hypot(ux, uy);
2529
+ if (sp > cap) {
2530
+ ux *= cap / sp;
2531
+ uy *= cap / sp;
2532
+ }
2533
+ this.footprint(
2534
+ id,
2535
+ rect,
2536
+ cov,
2537
+ (c, w) => {
2538
+ const f = this.f32;
2539
+ const rho = f[c + P_RHO * PLANE];
2540
+ if (!(rho >= DRY_EPS)) return;
2541
+ for (let k = 1; k < 9; k++) {
2542
+ const eu = EX[k] * ux * w + EY[k] * uy * w;
2543
+ f[c + k * PLANE] = Math.max(0, f[c + k * PLANE] + 3 * W9[k] * rho * eu);
2544
+ }
2545
+ },
2546
+ false
2547
+ );
2548
+ }
2549
+ /** Blends wet paint: each wet cell's suspended pigment moves `strength` × coverage towards the footprint's mean. */
2550
+ blend(id, rect, cov, strength) {
2551
+ const lane = this.laneOf(id);
2552
+ const sums = [0, 0, 0, 0];
2553
+ let wsum = 0;
2554
+ const cells = [];
2555
+ this.footprint(
2556
+ id,
2557
+ rect,
2558
+ cov,
2559
+ (c, w) => {
2560
+ if (!(this.f32[c + P_RHO * PLANE] >= DRY_EPS)) return;
2561
+ cells.push([c, w]);
2562
+ wsum += w;
2563
+ for (let q = 0; q < 4; q++) sums[q] += this.f32[c + (P_P0 + q) * PLANE] * w;
2564
+ },
2565
+ false
2566
+ );
2567
+ if (!(wsum > 0)) return;
2568
+ const k = Math.min(1, Math.max(0, strength));
2569
+ for (const [c, w] of cells) for (let q = 0; q < 4; q++) {
2570
+ const i = c + (P_P0 + q) * PLANE;
2571
+ this.f32[i] += k * w * (sums[q] / wsum - this.f32[i]);
2572
+ }
2573
+ lane.stamp++;
2574
+ this.refreshHalos();
2575
+ }
2576
+ /**
2577
+ * Drags wet paint by (`dx`, `dy`) cells: each footprint cell takes `strength` × coverage of the
2578
+ * water and suspended pigment of the cell it came from (a smudge through wet paint).
2579
+ */
2580
+ drag(id, rect, cov, dx, dy, strength) {
2581
+ const lane = this.laneOf(id);
2582
+ const sx = Math.round(dx);
2583
+ const sy = Math.round(dy);
2584
+ if (!sx && !sy) return;
2585
+ const moves = [];
2586
+ this.footprint(
2587
+ id,
2588
+ rect,
2589
+ cov,
2590
+ (c, w, x, y) => {
2591
+ const from = this.cell(lane, x - sx, y - sy);
2592
+ if (from < 0) return;
2593
+ moves.push([from, c, Math.min(1, Math.max(0, strength * w))]);
2594
+ },
2595
+ true
2596
+ );
2597
+ const f = this.f32;
2598
+ const taken = moves.map(([from, , a]) => {
2599
+ const t = new Float32Array(14);
2600
+ for (let k = 0; k < 9; k++) {
2601
+ t[k] = f[from + k * PLANE] * a;
2602
+ f[from + k * PLANE] -= t[k];
2603
+ }
2604
+ t[9] = f[from + P_RHO * PLANE] * a;
2605
+ f[from + P_RHO * PLANE] -= t[9];
2606
+ for (let q = 0; q < 4; q++) {
2607
+ t[10 + q] = f[from + (P_P0 + q) * PLANE] * a;
2608
+ f[from + (P_P0 + q) * PLANE] -= t[10 + q];
2609
+ }
2610
+ return t;
2611
+ });
2612
+ moves.forEach(([, to], i) => {
2613
+ const t = taken[i];
2614
+ for (let k = 0; k < 9; k++) f[to + k * PLANE] += t[k];
2615
+ f[to + P_RHO * PLANE] += t[9];
2616
+ for (let q = 0; q < 4; q++) f[to + (P_P0 + q) * PLANE] += t[10 + q];
2617
+ });
2618
+ this.refreshHalos();
2619
+ }
2620
+ /**
2621
+ * Re-wetting: lifts `amount` × coverage of the dried paint in the layer's pixels (`base`) back into
2622
+ * the paper as settled pigment, so water can move it again. Only where there is surface water.
2623
+ */
2624
+ rewet(id, rect, cov, amount, paper) {
2625
+ const lane = this.laneOf(id);
2626
+ const pig = [0, 0, 0, 0];
2627
+ const tiles = new Map(lane.base.tiles);
2628
+ const written = /* @__PURE__ */ new Set();
2629
+ const scratch2 = new Uint8ClampedArray(4);
2630
+ this.footprint(
2631
+ id,
2632
+ rect,
2633
+ cov,
2634
+ (c, w, x, y) => {
2635
+ if (!(this.f32[c + P_RHO * PLANE] >= DRY_EPS)) return;
2636
+ const a = Math.min(1, Math.max(0, amount * w));
2637
+ if (!(a > 0)) return;
2638
+ const key = _chunkTFOPHZ3Ucjs.tileKey.call(void 0, x >> 8, y >> 8);
2639
+ let tile = tiles.get(key);
2640
+ if (!tile) return;
2641
+ if (!written.has(key)) {
2642
+ tile = { data: new Uint8ClampedArray(tile.data), uniform: null };
2643
+ tiles.set(key, tile);
2644
+ written.add(key);
2645
+ }
2646
+ const i = ((y & 255) * _chunkTFOPHZ3Ucjs.TILE + (x & 255)) * 4;
2647
+ if (tile.data[i + 3] === 0) return;
2648
+ pixelPigment(tile.data, i, paper, pig);
2649
+ if (!(pig[0] > 0 || pig[1] > 0 || pig[2] > 0)) return;
2650
+ const clear = new Uint8ClampedArray([0, 0, 0, 0]);
2651
+ filmPixel(pig[0] * (1 - a), pig[1] * (1 - a), pig[2] * (1 - a), 0, clear, 0, paper, 0, scratch2, 0);
2652
+ tile.data.set(scratch2, i);
2653
+ const d = this.single(c) + P_D0 * PLANE;
2654
+ for (let q = 0; q < 3; q++) this.f32[d + q * PLANE] += Math.fround(pig[q] * a);
2655
+ },
2656
+ false
2657
+ );
2658
+ if (written.size) {
2659
+ for (const key of written) {
2660
+ const t = _chunkTFOPHZ3Ucjs.finishTile.call(void 0, tiles.get(key).data, 4, 0);
2661
+ if (t) tiles.set(key, t);
2662
+ else tiles.delete(key);
2663
+ }
2664
+ this.setBase(id, new (0, _chunkTFOPHZ3Ucjs.TiledSurface)(4, tiles, 0));
2665
+ }
2666
+ }
2667
+ /**
2668
+ * Dries wet paint at once (everything settles where it is, the water goes): in `rect`, or the
2669
+ * whole layer. Whole tiles that end up dry are baked into the base and freed. Returns the
2670
+ * document rectangle that changed, or `null`.
2671
+ */
2672
+ dry(id, rect) {
2673
+ this.requireIdle();
2674
+ const lane = this.laneOf(id);
2675
+ const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
2676
+ if (!keys.length) return null;
2677
+ let bounds = null;
2678
+ const area = _nullishCoalesce(rect, () => ( { x: 0, y: 0, width: this.width, height: this.height }));
2679
+ for (const key of keys) {
2680
+ const [tx, ty] = this.tileXY(key);
2681
+ const tr = { x: tx * WET_TILE, y: ty * WET_TILE, width: WET_TILE, height: WET_TILE };
2682
+ const x0 = Math.max(area.x, tr.x);
2683
+ const y0 = Math.max(area.y, tr.y);
2684
+ const x1 = Math.min(area.x + area.width, tr.x + tr.width, this.width);
2685
+ const y1 = Math.min(area.y + area.height, tr.y + tr.height, this.height);
2686
+ if (x1 <= x0 || y1 <= y0) continue;
2687
+ const whole = x0 === tr.x && y0 === tr.y && x1 >= Math.min(tr.x + WET_TILE, this.width) && y1 >= Math.min(tr.y + WET_TILE, this.height);
2688
+ if (whole) {
2689
+ if (this.bakeKey(key, true)) bounds = unionRect2(bounds, { x: tr.x, y: tr.y, width: Math.min(WET_TILE, this.width - tr.x), height: Math.min(WET_TILE, this.height - tr.y) });
2690
+ this.free(key);
2691
+ continue;
2692
+ }
2693
+ const f = this.f32;
2694
+ for (let y = y0; y < y1; y++) {
2695
+ for (let x = x0; x < x1; x++) {
2696
+ const c = this.cell(lane, x, y);
2697
+ const d = this.single(c) + P_D0 * PLANE;
2698
+ for (let q = 0; q < 4; q++) {
2699
+ f[d + q * PLANE] += f[c + (P_P0 + q) * PLANE];
2700
+ f[c + (P_P0 + q) * PLANE] = 0;
2701
+ }
2702
+ for (let k = 0; k < 9; k++) f[c + k * PLANE] = 0;
2703
+ f[c + P_RHO * PLANE] = 0;
2704
+ f[c + P_UX * PLANE] = 0;
2705
+ f[c + P_UY * PLANE] = 0;
2706
+ f[c + P_SW * PLANE] = 0;
2707
+ }
2708
+ }
2709
+ bounds = unionRect2(bounds, { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
2710
+ this.markEdited(lane, x0, y0, x1, y1);
2711
+ }
2712
+ this.syncList();
2713
+ this.refreshHalos();
2714
+ lane.stamp++;
2715
+ return bounds;
2716
+ }
2717
+ /**
2718
+ * Bakes a slot's pigment into its lane's base (settling suspended pigment too when `all`).
2719
+ * Returns whether any pixel changed.
2720
+ */
2721
+ bakeKey(key, all) {
2722
+ const lane = this.lanes[this.laneIndexOf(key)];
2723
+ const [tx, ty] = this.tileXY(key);
2724
+ const tk = _chunkTFOPHZ3Ucjs.tileKey.call(void 0, tx, ty);
2725
+ const src = _nullishCoalesce(_optionalChain([lane, 'access', _25 => _25.base, 'access', _26 => _26.tiles, 'access', _27 => _27.get, 'call', _28 => _28(tk), 'optionalAccess', _29 => _29.data]), () => ( null));
2726
+ const out = this.shadeSlot(key, src, all ? "bake-all" : "bake");
2727
+ if (!out) return false;
2728
+ const tiles = new Map(lane.base.tiles);
2729
+ const t = _chunkTFOPHZ3Ucjs.finishTile.call(void 0, out, 4, 0);
2730
+ if (t) tiles.set(tk, t);
2731
+ else tiles.delete(tk);
2732
+ this.setBase(lane.id, new (0, _chunkTFOPHZ3Ucjs.TiledSurface)(4, tiles, 0));
2733
+ return true;
2734
+ }
2735
+ /**
2736
+ * Shades one slot over a base tile. `display`: the wet look (suspended + settled, wet paper a
2737
+ * little darker); `bake`: settled pigment only; `bake-all`: suspended and settled (dry now).
2738
+ * Returns `null` when nothing would change the base.
2739
+ */
2740
+ shadeSlot(key, src, mode) {
2741
+ const [tx, ty] = this.tileXY(key);
2742
+ const b = this.slotBase(this.slotOf(key));
2743
+ const cur = b + this.parity * SET_PLANES * PLANE;
2744
+ const out = src ? new Uint8ClampedArray(src) : new Uint8ClampedArray(_chunkTFOPHZ3Ucjs.TILE * _chunkTFOPHZ3Ucjs.TILE * 4);
2745
+ const w = Math.min(WET_TILE, this.width - tx * WET_TILE);
2746
+ const h = Math.min(WET_TILE, this.height - ty * WET_TILE);
2747
+ const changed = shadeTile(
2748
+ this.f32,
2749
+ PLANE,
2750
+ S2,
2751
+ b + P_D0 * PLANE,
2752
+ mode === "bake" ? -1 : cur + P_P0 * PLANE,
2753
+ mode === "display" ? cur + P_RHO * PLANE : -1,
2754
+ cur + P_SW * PLANE,
2755
+ src,
2756
+ out,
2757
+ w,
2758
+ h,
2759
+ this.paperBytes
2760
+ );
2761
+ return changed ? out : null;
2762
+ }
2763
+ /** The layer's pixels with all its wet paint dried where it is (the bake of "dry on save"); changes nothing. */
2764
+ bakedSurface(id) {
2765
+ this.requireIdle();
2766
+ const lane = this.laneOf(id);
2767
+ const tiles = new Map(lane.base.tiles);
2768
+ for (const key of this.active) {
2769
+ if (this.laneIndexOf(key) !== lane.index) continue;
2770
+ const tk = this.tileKeyOf(key);
2771
+ const out = this.shadeSlot(key, _nullishCoalesce(_optionalChain([lane, 'access', _30 => _30.base, 'access', _31 => _31.tiles, 'access', _32 => _32.get, 'call', _33 => _33(tk), 'optionalAccess', _34 => _34.data]), () => ( null)), "bake-all");
2772
+ if (!out) continue;
2773
+ const t = _chunkTFOPHZ3Ucjs.finishTile.call(void 0, out, 4, 0);
2774
+ if (t) tiles.set(tk, t);
2775
+ else tiles.delete(tk);
2776
+ }
2777
+ return new (0, _chunkTFOPHZ3Ucjs.TiledSurface)(4, tiles, 0);
2778
+ }
2779
+ // --- Reading -------------------------------------------------------------------
2780
+ /**
2781
+ * Sums over a footprint of a lane, weighted by coverage: `weight` (coverage), `water` (surface
2782
+ * depth), `wet` (coverage of wet cells), `pigment` (suspended, 4 channels), `settled` (4 channels).
2783
+ */
2784
+ sample(id, rect, cov) {
2785
+ const lane = this.laneOf(id);
2786
+ const r = { weight: 0, water: 0, wet: 0, pigment: [0, 0, 0, 0], settled: [0, 0, 0, 0] };
2787
+ const x0 = Math.max(0, rect.x);
2788
+ const y0 = Math.max(0, rect.y);
2789
+ const x1 = Math.min(this.width, rect.x + rect.width);
2790
+ const y1 = Math.min(this.height, rect.y + rect.height);
2791
+ for (let y = y0; y < y1; y++) {
2792
+ for (let x = x0; x < x1; x++) {
2793
+ const w = cov ? cov[(y - rect.y) * rect.width + (x - rect.x)] : 1;
2794
+ if (!(w > 0)) continue;
2795
+ r.weight += w;
2796
+ const c = this.cell(lane, x, y);
2797
+ if (c < 0) continue;
2798
+ const rho = this.f32[c + P_RHO * PLANE];
2799
+ r.water += rho * w;
2800
+ if (rho >= DRY_EPS) r.wet += w;
2801
+ const d = this.single(c) + P_D0 * PLANE;
2802
+ for (let q = 0; q < 4; q++) {
2803
+ r.pigment[q] += this.f32[c + (P_P0 + q) * PLANE] * w;
2804
+ r.settled[q] += this.f32[d + q * PLANE] * w;
2805
+ }
2806
+ }
2807
+ }
2808
+ return r;
2809
+ }
2810
+ /** Surface water depth at a document cell (0 where nothing is simulated). */
2811
+ waterAt(id, x, y) {
2812
+ const c = this.cell(this.laneOf(id), Math.floor(x), Math.floor(y));
2813
+ return c < 0 ? 0 : this.f32[c + P_RHO * PLANE];
2814
+ }
2815
+ /** Totals for a layer (scans its live tiles). */
2816
+ stats(id) {
2817
+ const lane = this.laneOf(id);
2818
+ const out = { activeTiles: 0, wetCells: 0, water: 0, pigment: 0, wetBounds: null };
2819
+ let bx0 = Infinity;
2820
+ let by0 = Infinity;
2821
+ let bx1 = -Infinity;
2822
+ let by1 = -Infinity;
2823
+ const f = this.f32;
2824
+ for (const key of this.active) {
2825
+ if (this.laneIndexOf(key) !== lane.index) continue;
2826
+ out.activeTiles++;
2827
+ const [tx, ty] = this.tileXY(key);
2828
+ const b = this.slotBase(this.slotOf(key));
2829
+ const cur = b + this.parity * SET_PLANES * PLANE;
2830
+ for (let y = 1; y <= 256; y++) {
2831
+ for (let x = 1; x <= 256; x++) {
2832
+ const c = y * S2 + x;
2833
+ const rho = f[cur + P_RHO * PLANE + c];
2834
+ out.water += rho + f[cur + P_SW * PLANE + c];
2835
+ for (let q = 0; q < 4; q++) out.pigment += f[cur + (P_P0 + q) * PLANE + c] + f[b + (P_D0 + q) * PLANE + c];
2836
+ if (rho >= DRY_EPS) {
2837
+ out.wetCells++;
2838
+ const gx = tx * WET_TILE + x - 1;
2839
+ const gy = ty * WET_TILE + y - 1;
2840
+ if (gx < bx0) bx0 = gx;
2841
+ if (gy < by0) by0 = gy;
2842
+ if (gx > bx1) bx1 = gx;
2843
+ if (gy > by1) by1 = gy;
2844
+ }
2845
+ }
2846
+ }
2847
+ }
2848
+ if (out.wetCells) out.wetBounds = { x: bx0, y: by0, width: bx1 - bx0 + 1, height: by1 - by0 + 1 };
2849
+ return out;
2850
+ }
2851
+ /** Whether a layer (or any) has surface water anywhere. */
2852
+ isWet(id) {
2853
+ const ids = id === void 0 ? this.laneIds() : [id];
2854
+ return ids.some((x) => this.isActive(x));
2855
+ }
2856
+ /** Document rectangles of a layer's live tiles. */
2857
+ activeRects(id) {
2858
+ const lane = this.laneOf(id);
2859
+ return this.active.filter((k) => this.laneIndexOf(k) === lane.index).map((k) => {
2860
+ const [tx, ty] = this.tileXY(k);
2861
+ return { x: tx * WET_TILE, y: ty * WET_TILE, width: Math.min(WET_TILE, this.width - tx * WET_TILE), height: Math.min(WET_TILE, this.height - ty * WET_TILE) };
2862
+ });
2863
+ }
2864
+ // --- Display -------------------------------------------------------------------
2865
+ /** A number that changes whenever a layer's display may have changed. */
2866
+ stamp(id) {
2867
+ return this.laneOf(id).stamp;
2868
+ }
2869
+ /**
2870
+ * The layer's pixels with its wet paint shaded in: the base with each live tile shaded over it.
2871
+ * At most `maxTiles` stale tiles are reshaded per call (edited tiles first); the rest keep their
2872
+ * last shading until the next call. Returns the surface and the tiles reshaded.
2873
+ */
2874
+ display(id, maxTiles = Infinity) {
2875
+ const lane = this.laneOf(id);
2876
+ const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
2877
+ const stale = keys.filter((k) => _optionalChain([lane, 'access', _35 => _35.shaded, 'access', _36 => _36.get, 'call', _37 => _37(this.tileKeyOf(k)), 'optionalAccess', _38 => _38.stamp]) !== lane.stamp);
2878
+ stale.sort((a, b) => Number(lane.urgent.has(this.tileKeyOf(b))) - Number(lane.urgent.has(this.tileKeyOf(a))) || (_nullishCoalesce(_optionalChain([lane, 'access', _39 => _39.shaded, 'access', _40 => _40.get, 'call', _41 => _41(this.tileKeyOf(a)), 'optionalAccess', _42 => _42.stamp]), () => ( 0))) - (_nullishCoalesce(_optionalChain([lane, 'access', _43 => _43.shaded, 'access', _44 => _44.get, 'call', _45 => _45(this.tileKeyOf(b)), 'optionalAccess', _46 => _46.stamp]), () => ( 0))));
2879
+ const changed = [];
2880
+ let done = 0;
2881
+ for (const key of stale) {
2882
+ if (done >= maxTiles) break;
2883
+ const tk = this.tileKeyOf(key);
2884
+ const src = _nullishCoalesce(_optionalChain([lane, 'access', _47 => _47.base, 'access', _48 => _48.tiles, 'access', _49 => _49.get, 'call', _50 => _50(tk), 'optionalAccess', _51 => _51.data]), () => ( null));
2885
+ const out = this.shadeSlot(key, src, "display");
2886
+ const tile = out ? _chunkTFOPHZ3Ucjs.finishTile.call(void 0, out, 4, 0) : _nullishCoalesce(lane.base.tiles.get(tk), () => ( null));
2887
+ lane.shaded.set(tk, { stamp: lane.stamp, tile: _nullishCoalesce(tile, () => ( { data: new Uint8ClampedArray(0), uniform: 0 })) });
2888
+ lane.shadedVersion++;
2889
+ lane.urgent.delete(tk);
2890
+ const [tx, ty] = this.tileXY(key);
2891
+ changed.push({ x: tx * WET_TILE, y: ty * WET_TILE, width: WET_TILE, height: WET_TILE });
2892
+ done++;
2893
+ }
2894
+ const live = new Set(keys.map((k) => this.tileKeyOf(k)));
2895
+ for (const tk of [...lane.shaded.keys()]) if (!live.has(tk) && lane.shaded.delete(tk)) lane.shadedVersion++;
2896
+ const displayKey = `${lane.base.id}:${lane.shadedVersion}`;
2897
+ if (lane.display && lane.displayKey === displayKey) return { surface: lane.display, changed, pending: stale.length - done };
2898
+ const tiles = new Map(lane.base.tiles);
2899
+ for (const [tk, { tile }] of lane.shaded) {
2900
+ if (tile.data.length === 0) tiles.delete(tk);
2901
+ else tiles.set(tk, tile);
2902
+ }
2903
+ lane.display = new (0, _chunkTFOPHZ3Ucjs.TiledSurface)(4, tiles, 0);
2904
+ lane.displayKey = displayKey;
2905
+ return { surface: lane.display, changed, pending: stale.length - done };
2906
+ }
2907
+ /**
2908
+ * The water as an overlay tile (RGBA, blue, alpha from surface depth and damp paper) for a
2909
+ * document tile, or `null` where nothing is wet.
2910
+ */
2911
+ waterOverlay(id, tx, ty) {
2912
+ const lane = this.laneOf(id);
2913
+ const { tilesX, tilesY, perLane } = this.layout;
2914
+ if (tx < 0 || ty < 0 || tx >= tilesX || ty >= tilesY) return null;
2915
+ const slot = this.slotOf(lane.index * perLane + ty * tilesX + tx);
2916
+ if (slot < 0) return null;
2917
+ const cur = this.slotBase(slot) + this.parity * SET_PLANES * PLANE;
2918
+ const out = new Uint8ClampedArray(_chunkTFOPHZ3Ucjs.TILE * _chunkTFOPHZ3Ucjs.TILE * 4);
2919
+ let any = false;
2920
+ for (let y = 0; y < WET_TILE; y++) {
2921
+ for (let x = 0; x < WET_TILE; x++) {
2922
+ const c = (y + 1) * S2 + x + 1;
2923
+ const rho = this.f32[cur + P_RHO * PLANE + c];
2924
+ const sw = this.f32[cur + P_SW * PLANE + c];
2925
+ const a = Math.min(1, rho * 2.5) * 0.7 + Math.min(1, sw * 4) * 0.2;
2926
+ if (a <= 4e-3) continue;
2927
+ const i = (y * _chunkTFOPHZ3Ucjs.TILE + x) * 4;
2928
+ out[i] = 40;
2929
+ out[i + 1] = 120;
2930
+ out[i + 2] = 255;
2931
+ out[i + 3] = Math.round(a * 255);
2932
+ any = true;
2933
+ }
2934
+ }
2935
+ return any ? out : null;
2936
+ }
2937
+ // --- Snapshots -----------------------------------------------------------------
2938
+ /** A copy of everything (for undo): live slots, layers, clock and tilt. */
2939
+ snapshot() {
2940
+ this.requireIdle();
2941
+ const slots = /* @__PURE__ */ new Map();
2942
+ let bytes = 0;
2943
+ for (const key of this.active) {
2944
+ const b = this.slotBase(this.slotOf(key));
2945
+ const planes = new Float32Array(SNAP_PLANES * PLANE);
2946
+ planes.set(this.f32.subarray(b + this.parity * SET_PLANES * PLANE, b + (this.parity + 1) * SET_PLANES * PLANE), 0);
2947
+ planes.set(this.f32.subarray(b + P_D0 * PLANE, b + (P_D0 + 4) * PLANE), SET_PLANES * PLANE);
2948
+ slots.set(key, { planes, last: this.u32[(this.layout.last >> 2) + key] });
2949
+ bytes += planes.byteLength;
2950
+ }
2951
+ const lanes = this.lanes.filter((l) => Boolean(l)).map((l) => ({ id: l.id, index: l.index, params: { ...l.params }, flow: l.flow, flowStrength: l.flowStrength, base: l.base }));
2952
+ return { clock: this.clock, tilt: { ...this.tilt }, lanes, slots, bytes };
2953
+ }
2954
+ /** Puts a snapshot back exactly (the simulation then continues as it would have). */
2955
+ restore(snap) {
2956
+ this.requireIdle();
2957
+ for (const key of [...this.active]) this.free(key);
2958
+ this.lanes.fill(null);
2959
+ this.tilt = { ...snap.tilt };
2960
+ for (const l of snap.lanes) {
2961
+ this.lanes[l.index] = { id: l.id, index: l.index, params: { ...l.params }, flow: l.flow, flowStrength: l.flowStrength, base: l.base, shaded: /* @__PURE__ */ new Map(), urgent: /* @__PURE__ */ new Set(), stamp: 1, display: null, shadedVersion: 0, displayKey: "" };
2962
+ this.pack(this.lanes[l.index]);
2963
+ }
2964
+ for (const [key, { planes, last }] of snap.slots) {
2965
+ if (!this.activate(key)) continue;
2966
+ const b = this.slotBase(this.slotOf(key));
2967
+ this.f32.set(planes.subarray(0, SET_PLANES * PLANE), b + this.parity * SET_PLANES * PLANE);
2968
+ this.f32.set(planes.subarray(SET_PLANES * PLANE), b + P_D0 * PLANE);
2969
+ this.u32[(this.layout.last >> 2) + key] = last;
2970
+ }
2971
+ this.clock = snap.clock;
2972
+ this.syncList();
2973
+ this.refreshHalos();
2974
+ }
2975
+ /** Bytes of wasm memory held. */
2976
+ memoryBytes() {
2977
+ return this.memory.buffer.byteLength;
2978
+ }
2979
+ /** Live tiles across all layers. */
2980
+ activeTiles() {
2981
+ return this.active.length;
2982
+ }
2983
+ /** Memory for tests and tools: the raw f32 view (read only, please). */
2984
+ get rawFloats() {
2985
+ return this.f32;
2986
+ }
2987
+ /** The current set's parity (tests). */
2988
+ get currentParity() {
2989
+ return this.parity;
2990
+ }
2991
+ /** Float index of a slot's first plane (tests). */
2992
+ slotIndex(id, tx, ty) {
2993
+ const lane = this.laneOf(id);
2994
+ const slot = this.slotOf(lane.index * this.layout.perLane + ty * this.layout.tilesX + tx);
2995
+ return slot < 0 ? -1 : this.slotBase(slot);
2996
+ }
2997
+ }, _class8);
2998
+ function unionRect2(a, b) {
2999
+ if (!a) return { ...b };
3000
+ const x = Math.min(a.x, b.x);
3001
+ const y = Math.min(a.y, b.y);
3002
+ return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
3003
+ }
3004
+
3005
+ // src/paint/wet/flow.ts
3006
+ var WET_FLOW_KINDS = ["none", "linear", "swirl", "radial", "turbulent"];
3007
+ var DEFAULT_FLOW = { kind: "none", strength: 0.5, angle: 0, clockwise: true, scale: 120, seed: 1 };
3008
+ var FLOW_ACCEL = 0.01;
3009
+ function dsincos(a) {
3010
+ const twoPi = 6.283185307179586;
3011
+ let x = a - Math.round(a / twoPi) * twoPi;
3012
+ const q = Math.round(x / (twoPi / 4));
3013
+ x -= q * (twoPi / 4);
3014
+ const x2 = x * x;
3015
+ let s = x;
3016
+ let c = 1;
3017
+ let ts = x;
3018
+ let tc = 1;
3019
+ for (let i = 1; i < 12; i++) {
3020
+ ts *= -x2 / (2 * i * (2 * i + 1));
3021
+ tc *= -x2 / ((2 * i - 1) * (2 * i));
3022
+ s += ts;
3023
+ c += tc;
3024
+ }
3025
+ switch ((q % 4 + 4) % 4) {
3026
+ case 0:
3027
+ return [s, c];
3028
+ case 1:
3029
+ return [c, -s];
3030
+ case 2:
3031
+ return [-s, -c];
3032
+ default:
3033
+ return [-c, s];
3034
+ }
3035
+ }
3036
+ function hash(x, y, seed) {
3037
+ let h = Math.imul(x | 0, 374761393) + Math.imul(y | 0, 668265263) + Math.imul(seed | 0, 1442695041) | 0;
3038
+ h = Math.imul(h ^ h >>> 13, 1274126177);
3039
+ h ^= h >>> 16;
3040
+ return (h >>> 0) / 4294967296;
3041
+ }
3042
+ function valueNoise(x, y, seed) {
3043
+ const x0 = Math.floor(x);
3044
+ const y0 = Math.floor(y);
3045
+ const fx = x - x0;
3046
+ const fy = y - y0;
3047
+ const sx = fx * fx * (3 - 2 * fx);
3048
+ const sy = fy * fy * (3 - 2 * fy);
3049
+ const a = hash(x0, y0, seed);
3050
+ const b = hash(x0 + 1, y0, seed);
3051
+ const c = hash(x0, y0 + 1, seed);
3052
+ const d = hash(x0 + 1, y0 + 1, seed);
3053
+ return a + (b - a) * sx + (c - a) * sy + (a - b - c + d) * sx * sy;
3054
+ }
3055
+ function normalizeFlow(raw = {}, base = DEFAULT_FLOW) {
3056
+ const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
3057
+ const kind = WET_FLOW_KINDS.includes(raw.kind) ? raw.kind : base.kind;
3058
+ const center = raw.center && Number.isFinite(raw.center.x) && Number.isFinite(raw.center.y) ? { x: raw.center.x, y: raw.center.y } : base.center;
3059
+ return {
3060
+ kind,
3061
+ strength: Math.min(1, Math.max(0, num(raw.strength, base.strength))),
3062
+ angle: (num(raw.angle, base.angle) % 360 + 360) % 360,
3063
+ ...center ? { center } : {},
3064
+ clockwise: typeof raw.clockwise === "boolean" ? raw.clockwise : base.clockwise,
3065
+ scale: Math.min(1024, Math.max(8, num(raw.scale, base.scale))),
3066
+ seed: Math.abs(Math.round(num(raw.seed, base.seed))) % 2147483647
3067
+ };
3068
+ }
3069
+ function flowField(spec, width, height) {
3070
+ const k = FLOW_ACCEL;
3071
+ const cx = _nullishCoalesce(_optionalChain([spec, 'access', _52 => _52.center, 'optionalAccess', _53 => _53.x]), () => ( width / 2));
3072
+ const cy = _nullishCoalesce(_optionalChain([spec, 'access', _54 => _54.center, 'optionalAccess', _55 => _55.y]), () => ( height / 2));
3073
+ const r0 = Math.max(width, height) / 2;
3074
+ switch (spec.kind) {
3075
+ case "none":
3076
+ return null;
3077
+ case "linear": {
3078
+ const [s, c] = dsincos(spec.angle * Math.PI / 180);
3079
+ const v = [-s * k, c * k];
3080
+ return () => v;
3081
+ }
3082
+ case "swirl": {
3083
+ const sign = spec.clockwise ? 1 : -1;
3084
+ return (x, y) => [-(y - cy) / r0 * k * sign, (x - cx) / r0 * k * sign];
3085
+ }
3086
+ case "radial":
3087
+ return (x, y) => [(x - cx) / r0 * k, (y - cy) / r0 * k];
3088
+ case "turbulent": {
3089
+ const sc = spec.scale;
3090
+ const e = 0.5;
3091
+ return (x, y) => {
3092
+ const u = x / sc;
3093
+ const v = y / sc;
3094
+ const dndy = (valueNoise(u, v + e / sc, spec.seed) - valueNoise(u, v - e / sc, spec.seed)) * sc;
3095
+ const dndx = (valueNoise(u + e / sc, v, spec.seed) - valueNoise(u - e / sc, v, spec.seed)) * sc;
3096
+ return [dndy * k * 0.5, -dndx * k * 0.5];
3097
+ };
3098
+ }
3099
+ }
3100
+ }
3101
+
3102
+ // src/paint/wet/medium.ts
3103
+ var SAMPLE_K = 3;
3104
+ var WetMedium = (_class9 = class {
3105
+ constructor(world, laneId, options) {;_class9.prototype.__init44.call(this);
3106
+ this.world = world;
3107
+ this.laneId = laneId;
3108
+ this.options = options;
3109
+ }
3110
+
3111
+
3112
+
3113
+ __init44() {this.pig = new Float64Array(4)}
3114
+ sample(rect, cov) {
3115
+ const s = this.world.sample(this.laneId, rect, cov);
3116
+ if (!(s.wet > 0) || !(s.weight > 0)) return null;
3117
+ const k = s.pigment;
3118
+ const kmax = Math.max(k[0], k[1], k[2]);
3119
+ if (!(kmax > 1e-9)) return null;
3120
+ const f = SAMPLE_K / kmax;
3121
+ const rgb = pigmentColor(k[0] * f, k[1] * f, k[2] * f, k[3] * f);
3122
+ const density = Math.min(1, (k[0] + k[1] + k[2]) / s.wet / 0.06);
3123
+ return { rgb, contact: s.wet / s.weight * density };
3124
+ }
3125
+ deposit(rect, cov, dep, paint, _pigment, field = null) {
3126
+ const { water, mass, body } = this.options;
3127
+ if (field) {
3128
+ const n = rect.width * rect.height;
3129
+ const out = new Float32Array(n * 4);
3130
+ const one = this.pig;
3131
+ for (let p = 0; p < n; p++) {
3132
+ pigmentOf([field[p * 3], field[p * 3 + 1], field[p * 3 + 2]], body, one);
3133
+ for (let q = 0; q < 4; q++) out[p * 4 + q] = one[q] * mass * dep;
3134
+ }
3135
+ this.world.paint(this.laneId, rect, cov, water * dep, null, out);
3136
+ return;
3137
+ }
3138
+ pigmentOf(paint, body, this.pig);
3139
+ const pig = [0, 1, 2, 3].map((q) => this.pig[q] * mass * dep);
3140
+ this.world.paint(this.laneId, rect, cov, water * dep, mass > 0 ? pig : null);
3141
+ }
3142
+ }, _class9);
3143
+
3144
+
3145
+
3146
+
3147
+
3148
+
3149
+
3150
+
3151
+
3152
+
3153
+
3154
+
3155
+
3156
+
3157
+
3158
+
3159
+
3160
+
3161
+
3162
+
3163
+
3164
+
3165
+
3166
+
3167
+
3168
+
3169
+
3170
+
3171
+
3172
+
3173
+
3174
+
3175
+
3176
+
3177
+
3178
+
3179
+
3180
+
3181
+
3182
+
3183
+
3184
+
3185
+
3186
+
3187
+
3188
+
3189
+
3190
+
3191
+
3192
+
3193
+
3194
+
3195
+
3196
+
3197
+
3198
+
3199
+
3200
+
3201
+
3202
+
3203
+
3204
+
3205
+
3206
+
3207
+
3208
+
3209
+
3210
+
3211
+
3212
+
3213
+
3214
+
3215
+
3216
+
3217
+
3218
+
3219
+
3220
+
3221
+
3222
+
3223
+
3224
+
3225
+
3226
+
3227
+
3228
+
3229
+
3230
+
3231
+
3232
+
3233
+
3234
+
3235
+
3236
+
3237
+
3238
+
3239
+
3240
+
3241
+
3242
+
3243
+
3244
+
3245
+
3246
+
3247
+
3248
+
3249
+
3250
+
3251
+
3252
+
3253
+
3254
+
3255
+
3256
+
3257
+
3258
+
3259
+
3260
+
3261
+ exports.DEFAULT_BRUSH = DEFAULT_BRUSH; exports.MAX_BRUSH_SIZE = MAX_BRUSH_SIZE; exports.normalizeBrush = normalizeBrush; exports.tipShape = tipShape; exports.tipFalloff = tipFalloff; exports.tipReach = tipReach; exports.dabCoverage = dabCoverage; exports.smoothPoints = smoothPoints; exports.DabWalker = DabWalker; exports.walkDabs = walkDabs; exports.dabRect = dabRect; exports.rasterizeDab = rasterizeDab; exports.CoverageMap = CoverageMap; exports.StrokeCoverage = StrokeCoverage; exports.applyStrokeToTile = applyStrokeToTile; exports.coverageTileCoord = coverageTileCoord; exports.MIXING_MODELS = MIXING_MODELS; exports.EMPTY_MIXER_LOAD = EMPTY_MIXER_LOAD; exports.MIXER_PICKUP_RATE = MIXER_PICKUP_RATE; exports.MIXER_DRAIN = MIXER_DRAIN; exports.exchangeRates = exchangeRates; exports.PaintExchange = PaintExchange; exports.TileMedium = TileMedium; exports.DEFAULT_MIXER = DEFAULT_MIXER; exports.normalizeMixer = normalizeMixer; exports.mixerRates = mixerRates; exports.loadMixer = loadMixer; exports.cleanMixer = cleanMixer; exports.MixerStroke = MixerStroke; exports.DEFAULT_BLENDER = DEFAULT_BLENDER; exports.DEFAULT_SMUDGE = DEFAULT_SMUDGE; exports.normalizeBlender = normalizeBlender; exports.normalizeSmudge = normalizeSmudge; exports.blendRadius = blendRadius; exports.createBlenderScratch = createBlenderScratch; exports.blenderDab = blenderDab; exports.SmudgeTip = SmudgeTip; exports.createPigmentTools = createPigmentTools; exports.WET_TILE = WET_TILE; exports.SLOT_SIDE = SLOT_SIDE; exports.PLANE = PLANE; exports.PLANE_BYTES = PLANE_BYTES; exports.SET_PLANES = SET_PLANES; exports.P_RHO = P_RHO; exports.P_UX = P_UX; exports.P_UY = P_UY; exports.P_SW = P_SW; exports.P_P0 = P_P0; exports.P_EDGE = P_EDGE; exports.P_D0 = P_D0; exports.P_H = P_H; exports.P_FX = P_FX; exports.P_FY = P_FY; exports.SLOT_PLANES = SLOT_PLANES; exports.SLOT_BYTES = SLOT_BYTES; exports.PIGMENT_CHANNELS = PIGMENT_CHANNELS; exports.EX = EX; exports.EY = EY; exports.OPP = OPP; exports.W9 = W9; exports.DRY_EPS = DRY_EPS; exports.U_MAX = U_MAX; exports.STRIP = STRIP; exports.MAX_BATCH = MAX_BATCH; exports.FREE_INTERVAL = FREE_INTERVAL; exports.FLAG_ALIVE = FLAG_ALIVE; exports.FLAG_LEFT = FLAG_LEFT; exports.FLAG_RIGHT = FLAG_RIGHT; exports.FLAG_TOP = FLAG_TOP; exports.FLAG_BOTTOM = FLAG_BOTTOM; exports.FLAG_WET = FLAG_WET; exports.PARAM_FLOATS = PARAM_FLOATS; exports.PARAM = PARAM; exports.DEFAULT_WET_PARAMS = DEFAULT_WET_PARAMS; exports.WET_PARAM_KEYS = WET_PARAM_KEYS; exports.DEFAULT_WET_PAPER = DEFAULT_WET_PAPER; exports.FLAT = FLAT; exports.normalizeWetParams = normalizeWetParams; exports.normalizeTilt = normalizeTilt; exports.latticeViscosity = latticeViscosity; exports.PAPER_CAPACITY = PAPER_CAPACITY; exports.MAX_TILT_ACCEL = MAX_TILT_ACCEL; exports.packWetParams = packWetParams; exports.edgeBits = edgeBits; exports.PIGMENT_UNIT = PIGMENT_UNIT; exports.HIDE = HIDE; exports.BODY_SCATTER = BODY_SCATTER; exports.dexp = dexp; exports.dlog = dlog; exports.dpow = dpow; exports.SRGB_TO_LINEAR = SRGB_TO_LINEAR; exports.srgbByteToLinear = srgbByteToLinear; exports.linearToSrgb8 = linearToSrgb8; exports.pigmentOf = pigmentOf; exports.expNeg = expNeg; exports.filmReflectance = filmReflectance; exports.pigmentColor = pigmentColor; exports.filmPixel = filmPixel; exports.pixelPigment = pixelPigment; exports.shadeTile = shadeTile; exports.stepSlotTs = stepSlotTs; exports.loadWetKernel = loadWetKernel; exports.canShareMemory = canShareMemory; exports.wetSimdSupported = wetSimdSupported; exports.wetSupport = wetSupport; exports.WetWorkerPool = WetWorkerPool; exports.MAX_WET_LANES = MAX_WET_LANES; exports.MAX_WET_SLOTS = MAX_WET_SLOTS; exports.WetWorldError = WetWorldError; exports.WetWorld = WetWorld; exports.WET_FLOW_KINDS = WET_FLOW_KINDS; exports.DEFAULT_FLOW = DEFAULT_FLOW; exports.dsincos = dsincos; exports.normalizeFlow = normalizeFlow; exports.flowField = flowField; exports.WetMedium = WetMedium;
3262
+ //# sourceMappingURL=chunk-HOCPSFGA.cjs.map