@pixerate/editor 0.271.0 → 0.272.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.
- package/dist/animation-editor/index.cjs +60 -60
- package/dist/animation-editor/index.js +4 -4
- package/dist/brushes/index.cjs +69 -66
- package/dist/brushes/index.js +6 -3
- package/dist/brushes/patch.d.cts +1 -1
- package/dist/brushes/patch.d.ts +1 -1
- package/dist/brushes/schema.d.cts +1 -0
- package/dist/brushes/schema.d.ts +1 -0
- package/dist/brushes/types.d.cts +8 -0
- package/dist/brushes/types.d.ts +8 -0
- package/dist/{brushstrokeRender-WR2N5HHY.cjs → brushstrokeRender-5YBCLFDQ.cjs} +15 -15
- package/dist/{brushstrokeRender-A727TTNQ.js → brushstrokeRender-DZGFU3J4.js} +3 -3
- package/dist/canvas/index.cjs +13 -13
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-YNFSH72F.js → chunk-2F3YDXCN.js} +1 -1
- package/dist/{chunk-QYXUE37Q.js → chunk-2XDNF73E.js} +1 -1
- package/dist/{chunk-3KNQ2W6X.cjs → chunk-3JKCREEU.cjs} +59 -53
- package/dist/{chunk-B5W677L7.js → chunk-5HP2VSAS.js} +1 -1
- package/dist/{chunk-BPJSI4NT.cjs → chunk-7W2EQQBP.cjs} +2 -2
- package/dist/{chunk-LETF75CA.js → chunk-CLB2I2FU.js} +1 -1
- package/dist/{chunk-BAZMCFVL.js → chunk-DZVRH63Y.js} +1 -1
- package/dist/{chunk-U2ASNGO6.cjs → chunk-FXVCB6YG.cjs} +35 -35
- package/dist/{chunk-EU2ZZA6L.cjs → chunk-GHGBIAWQ.cjs} +126 -39
- package/dist/{chunk-2WOGXEDX.cjs → chunk-M3RZOF3G.cjs} +1 -1
- package/dist/{chunk-GSTSZPSR.js → chunk-N3SKPQEH.js} +225 -18
- package/dist/{chunk-J5VQYK7T.cjs → chunk-N5IMDAHM.cjs} +251 -44
- package/dist/{chunk-ININNHLM.js → chunk-NAHJV3O6.js} +11 -5
- package/dist/{chunk-JJ2H25JG.cjs → chunk-SNAFFCAX.cjs} +200 -174
- package/dist/{chunk-M2ZVPU7L.cjs → chunk-TNLKFW7D.cjs} +23 -23
- package/dist/{chunk-KZI3YWKW.js → chunk-UXYCY3HP.js} +93 -6
- package/dist/{chunk-DDL3ZBHK.cjs → chunk-YJANF2BL.cjs} +86 -86
- package/dist/{chunk-TNHDCP4R.js → chunk-ZEI7ZEA4.js} +31 -5
- package/dist/image-editor/index.cjs +789 -789
- package/dist/image-editor/index.js +5 -5
- package/dist/paint/index.cjs +2 -2
- package/dist/paint/index.js +1 -1
- package/dist/paint/wet/world.d.cts +37 -1
- package/dist/paint/wet/world.d.ts +37 -1
- package/dist/paint-editor/brush-controls.d.cts +2 -0
- package/dist/paint-editor/brush-controls.d.ts +2 -0
- package/dist/paint-editor/brush-model.d.cts +14 -1
- package/dist/paint-editor/brush-model.d.ts +14 -1
- package/dist/paint-editor/index.cjs +343 -306
- package/dist/paint-editor/index.js +48 -11
- package/dist/paint-editor/ui.d.cts +1 -1
- package/dist/paint-editor/ui.d.ts +1 -1
- package/dist/paint-editor/wet.d.cts +8 -1
- package/dist/paint-editor/wet.d.ts +8 -1
- package/dist/vector/index.cjs +999 -999
- package/dist/vector/index.js +5 -5
- package/package.json +1 -1
- package/skills/pixerate-brushes/SKILL.md +1 -0
- package/skills/pixerate-paint-editor/SKILL.md +4 -11
- package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
- package/skills/pixerate-paint-editor/references/ghost.md +47 -0
- package/skills/pixerate-paint-watercolour/SKILL.md +1 -0
|
@@ -849,9 +849,13 @@ var SLOT_FLOATS = SLOT_PLANES * PLANE;
|
|
|
849
849
|
var S2 = SLOT_SIDE;
|
|
850
850
|
var SNAP_PLANES = SET_PLANES + MASS_CHANNELS;
|
|
851
851
|
var noLight = () => new TiledSurface(1, /* @__PURE__ */ new Map(), 0);
|
|
852
|
-
function
|
|
852
|
+
function cloneGhost(g) {
|
|
853
|
+
return g ? { pigment: [...g.pigment], remaining: g.remaining, total: g.total, linger: g.linger } : null;
|
|
854
|
+
}
|
|
855
|
+
function newLane(id, index, params, flow, flowStrength, base, light, ghost) {
|
|
853
856
|
const l = light ?? noLight();
|
|
854
|
-
|
|
857
|
+
const g = cloneGhost(ghost);
|
|
858
|
+
return { id, index, params, flow, flowStrength, base, shaded: /* @__PURE__ */ new Map(), urgent: /* @__PURE__ */ new Set(), stamp: 1, display: null, shadedVersion: 0, displayKey: "", light: l, lit: !g && l.tiles.size > 0, ghost: g, lightShaded: /* @__PURE__ */ new Map(), lightDisplay: null, lightKey: "" };
|
|
855
859
|
}
|
|
856
860
|
var WetWorldError = class extends Error {
|
|
857
861
|
constructor(message) {
|
|
@@ -1012,11 +1016,12 @@ var WetWorld = class {
|
|
|
1012
1016
|
if (options.params) this.setParams(id, options.params);
|
|
1013
1017
|
if (options.base) this.setBase(id, options.base);
|
|
1014
1018
|
if (options.light) this.setLight(id, options.light);
|
|
1019
|
+
if (options.ghost !== void 0) this.setGhost(id, options.ghost);
|
|
1015
1020
|
return;
|
|
1016
1021
|
}
|
|
1017
1022
|
const index = this.lanes.indexOf(null);
|
|
1018
1023
|
if (index < 0) throw new WetWorldError(`At most ${MAX_WET_LANES} layers can hold wet paint at once; dry one (dry_paint) first.`);
|
|
1019
|
-
this.lanes[index] = newLane(id, index, normalizeWetParams(options.params ?? {}, DEFAULT_WET_PARAMS), options.flow ?? null, options.flow ? options.flowStrength ?? 1 : 0, options.base ?? new TiledSurface(4, /* @__PURE__ */ new Map(), 0), options.light);
|
|
1024
|
+
this.lanes[index] = newLane(id, index, normalizeWetParams(options.params ?? {}, DEFAULT_WET_PARAMS), options.flow ?? null, options.flow ? options.flowStrength ?? 1 : 0, options.base ?? new TiledSurface(4, /* @__PURE__ */ new Map(), 0), options.light, options.ghost);
|
|
1020
1025
|
this.pack(this.lanes[index]);
|
|
1021
1026
|
}
|
|
1022
1027
|
/** Drops a wet layer and its wet paint (nothing is baked). */
|
|
@@ -1089,17 +1094,46 @@ var WetWorld = class {
|
|
|
1089
1094
|
}
|
|
1090
1095
|
/** Whether a layer has light paint, under its wet paint or in it. */
|
|
1091
1096
|
isLit(id) {
|
|
1092
|
-
|
|
1097
|
+
const l = this.laneOf(id);
|
|
1098
|
+
return l.lit && !l.ghost;
|
|
1093
1099
|
}
|
|
1094
1100
|
/** Replaces the light paint under a layer's wet paint. */
|
|
1095
1101
|
setLight(id, light) {
|
|
1096
1102
|
const lane = this.laneOf(id);
|
|
1097
1103
|
if (light === lane.light) return;
|
|
1098
1104
|
lane.light = light;
|
|
1099
|
-
if (light.tiles.size) lane.lit = true;
|
|
1105
|
+
if (light.tiles.size && !lane.ghost) lane.lit = true;
|
|
1100
1106
|
lane.lightShaded.clear();
|
|
1101
1107
|
lane.stamp++;
|
|
1102
1108
|
}
|
|
1109
|
+
/** Active ghost paint state on the layer, or `null`. */
|
|
1110
|
+
getGhost(id) {
|
|
1111
|
+
return cloneGhost(this.laneOf(id).ghost);
|
|
1112
|
+
}
|
|
1113
|
+
/** Sets or clears a layer's active ghost paint state. */
|
|
1114
|
+
setGhost(id, ghost) {
|
|
1115
|
+
this.requireIdle();
|
|
1116
|
+
const lane = this.laneOf(id);
|
|
1117
|
+
lane.ghost = cloneGhost(ghost);
|
|
1118
|
+
if (lane.ghost) lane.lit = false;
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Starts a ghost paint fade on `id` with `pigment` per unit tracer and `linger` seconds. If an
|
|
1122
|
+
* earlier ghost of a different colour is still fading on the layer, it finishes at once first.
|
|
1123
|
+
*/
|
|
1124
|
+
startGhost(id, pigment, linger, stepsPerSecond = 120) {
|
|
1125
|
+
this.requireIdle();
|
|
1126
|
+
const lane = this.laneOf(id);
|
|
1127
|
+
const p = [Math.fround(pigment[0]), Math.fround(pigment[1]), Math.fround(pigment[2]), Math.fround(pigment[3])];
|
|
1128
|
+
if (lane.ghost) {
|
|
1129
|
+
const g = lane.ghost.pigment;
|
|
1130
|
+
const same = Math.abs(g[0] - p[0]) <= 1e-6 && Math.abs(g[1] - p[1]) <= 1e-6 && Math.abs(g[2] - p[2]) <= 1e-6 && Math.abs(g[3] - p[3]) <= 1e-6;
|
|
1131
|
+
if (!same) this.fadeGhost(id, 1);
|
|
1132
|
+
}
|
|
1133
|
+
const total = Math.max(1, Math.round(linger * stepsPerSecond / FREE_INTERVAL));
|
|
1134
|
+
lane.ghost = { pigment: p, remaining: total, total, linger };
|
|
1135
|
+
lane.lit = false;
|
|
1136
|
+
}
|
|
1103
1137
|
pack(lane) {
|
|
1104
1138
|
packWetParams(lane.params, this.paper ?? DEFAULT_WET_PAPER, this.tilt, lane.flowStrength, this.prm);
|
|
1105
1139
|
this.f32.set(this.prm, this.layout.params + lane.index * PARAM_FLOATS * 4 >> 2);
|
|
@@ -1332,6 +1366,13 @@ var WetWorld = class {
|
|
|
1332
1366
|
let woke = false;
|
|
1333
1367
|
for (const key of wakeups) woke = this.activate(key) || woke;
|
|
1334
1368
|
if (this.clock % FREE_INTERVAL === 0) {
|
|
1369
|
+
for (const lane of this.lanes) {
|
|
1370
|
+
if (!lane?.ghost) continue;
|
|
1371
|
+
const g = lane.ghost;
|
|
1372
|
+
const amount = g.remaining <= 1 ? 1 : 1 / g.remaining;
|
|
1373
|
+
g.remaining = Math.max(0, g.remaining - 1);
|
|
1374
|
+
this.fadeGhost(lane.id, amount);
|
|
1375
|
+
}
|
|
1335
1376
|
const dead = this.active.filter((key) => {
|
|
1336
1377
|
if (this.u32[lastBase + key] & FLAG_ALIVE) return false;
|
|
1337
1378
|
const laneBase = this.laneIndexOf(key) * perLane;
|
|
@@ -1531,7 +1572,10 @@ var WetWorld = class {
|
|
|
1531
1572
|
paint(id, rect, cov, water, pigment, field = null, light = 0) {
|
|
1532
1573
|
if (!(water >= 0)) throw new WetWorldError(`water ${water} must be 0 or more.`);
|
|
1533
1574
|
if (!(light >= 0)) throw new WetWorldError(`light ${light} must be 0 or more.`);
|
|
1534
|
-
if (light > 0)
|
|
1575
|
+
if (light > 0) {
|
|
1576
|
+
const l = this.laneOf(id);
|
|
1577
|
+
if (!l.ghost) l.lit = true;
|
|
1578
|
+
}
|
|
1535
1579
|
this.footprint(id, rect, cov, (c, w, x, y) => {
|
|
1536
1580
|
const dw = Math.fround(water * w);
|
|
1537
1581
|
if (dw > 0) this.addWaterAt(c, dw);
|
|
@@ -1683,12 +1727,12 @@ var WetWorld = class {
|
|
|
1683
1727
|
* the paper as settled pigment, so water can move it again, and the same share of its light
|
|
1684
1728
|
* paint (`light`) as settled light. Only where there is surface water.
|
|
1685
1729
|
*/
|
|
1686
|
-
rewet(id, rect, cov, amount, paper) {
|
|
1730
|
+
rewet(id, rect, cov, amount, paper, suspend = false) {
|
|
1687
1731
|
const lane = this.laneOf(id);
|
|
1688
1732
|
const pig = [0, 0, 0, 0];
|
|
1689
1733
|
const tiles = new Map(lane.base.tiles);
|
|
1690
1734
|
const written = /* @__PURE__ */ new Set();
|
|
1691
|
-
const lightTiles = lane.lit ? new Map(lane.light.tiles) : null;
|
|
1735
|
+
const lightTiles = lane.lit && !lane.ghost ? new Map(lane.light.tiles) : null;
|
|
1692
1736
|
const lightWritten = /* @__PURE__ */ new Set();
|
|
1693
1737
|
const scratch2 = new Uint8ClampedArray(4);
|
|
1694
1738
|
this.footprint(
|
|
@@ -1730,8 +1774,8 @@ var WetWorld = class {
|
|
|
1730
1774
|
const clear = new Uint8ClampedArray([0, 0, 0, 0]);
|
|
1731
1775
|
filmPixel(pig[0] * (1 - a), pig[1] * (1 - a), pig[2] * (1 - a), 0, clear, 0, paper, 0, scratch2, 0);
|
|
1732
1776
|
tile.data.set(scratch2, i);
|
|
1733
|
-
const
|
|
1734
|
-
for (let q = 0; q < 3; q++) this.f32[
|
|
1777
|
+
const into = suspend ? c + P_P0 * PLANE : this.single(c) + P_D0 * PLANE;
|
|
1778
|
+
for (let q = 0; q < 3; q++) this.f32[into + q * PLANE] += Math.fround(pig[q] * a);
|
|
1735
1779
|
},
|
|
1736
1780
|
false
|
|
1737
1781
|
);
|
|
@@ -1752,6 +1796,114 @@ var WetWorld = class {
|
|
|
1752
1796
|
this.setLight(id, new TiledSurface(1, lightTiles, 0));
|
|
1753
1797
|
}
|
|
1754
1798
|
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Subtracts `amount` (0..1) of a ghost lane's remaining tracer (`P_L`, `P_DL`) and proportional
|
|
1801
|
+
* ghost pigment (`lane.ghost.pigment`) across all live tiles of `id`. Speeds up in cells that
|
|
1802
|
+
* are nearly dry so drying paint never pops. Clears the lane's `ghost` state once no tracer remains.
|
|
1803
|
+
*/
|
|
1804
|
+
fadeGhost(id, amount) {
|
|
1805
|
+
this.requireIdle();
|
|
1806
|
+
const lane = this.laneOf(id);
|
|
1807
|
+
if (!lane.ghost) return;
|
|
1808
|
+
const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
|
|
1809
|
+
let anyRemaining = false;
|
|
1810
|
+
for (const key of keys) {
|
|
1811
|
+
if (this.fadeGhostKey(key, lane, amount)) anyRemaining = true;
|
|
1812
|
+
}
|
|
1813
|
+
if (!anyRemaining || amount >= 1 - 1e-6) lane.ghost = null;
|
|
1814
|
+
this.refreshHalos();
|
|
1815
|
+
}
|
|
1816
|
+
/** Whether any live tile of `lane` still holds ghost tracer (`P_L > 0` or `P_DL > 0`). */
|
|
1817
|
+
hasGhostTracer(lane) {
|
|
1818
|
+
const f = this.f32;
|
|
1819
|
+
for (const key of this.active) {
|
|
1820
|
+
if (this.laneIndexOf(key) !== lane.index) continue;
|
|
1821
|
+
const b = this.slotBase(this.slotOf(key));
|
|
1822
|
+
const cur = b + this.parity * SET_PLANES * PLANE;
|
|
1823
|
+
for (let y = 1; y <= WET_TILE; y++) {
|
|
1824
|
+
for (let x = 1; x <= WET_TILE; x++) {
|
|
1825
|
+
const c = y * S2 + x;
|
|
1826
|
+
if (f[cur + P_L * PLANE + c] > 0 || f[b + P_DL * PLANE + c] > 0) return true;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return false;
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Fades ghost tracer and pigment in one slot by `amount` (0..1). Returns whether any tracer
|
|
1834
|
+
* remains in the slot.
|
|
1835
|
+
*/
|
|
1836
|
+
fadeGhostKey(key, lane, amount) {
|
|
1837
|
+
if (!lane.ghost) return false;
|
|
1838
|
+
const pig = lane.ghost.pigment;
|
|
1839
|
+
const slot = this.slotOf(key);
|
|
1840
|
+
if (slot < 0) return false;
|
|
1841
|
+
const b = this.slotBase(slot);
|
|
1842
|
+
const cur = b + this.parity * SET_PLANES * PLANE;
|
|
1843
|
+
const f = this.f32;
|
|
1844
|
+
const baseAmt = Math.min(1, Math.max(0, amount));
|
|
1845
|
+
let remainingTracer = false;
|
|
1846
|
+
let yMin = WET_TILE + 1;
|
|
1847
|
+
let yMax = 0;
|
|
1848
|
+
for (let y = 1; y <= WET_TILE; y++) {
|
|
1849
|
+
for (let x = 1; x <= WET_TILE; x++) {
|
|
1850
|
+
const c = y * S2 + x;
|
|
1851
|
+
const tl = f[cur + P_L * PLANE + c];
|
|
1852
|
+
const dl = f[b + P_DL * PLANE + c];
|
|
1853
|
+
if (!(tl > 0 || dl > 0)) continue;
|
|
1854
|
+
const water = f[cur + P_RHO * PLANE + c] + f[cur + P_SW * PLANE + c];
|
|
1855
|
+
const dryFade = water <= DRY_EPS ? 1 : water < 0.02 ? (1 - water / 0.02) * 0.5 : 0;
|
|
1856
|
+
const a = baseAmt >= 1 - 1e-6 ? 1 : Math.min(1, Math.max(baseAmt, dryFade));
|
|
1857
|
+
if (!(a > 0)) {
|
|
1858
|
+
remainingTracer = true;
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
if (y < yMin) yMin = y;
|
|
1862
|
+
if (y > yMax) yMax = y;
|
|
1863
|
+
if (tl > 0) {
|
|
1864
|
+
const done = a >= 1 || tl * (1 - a) <= 1e-7;
|
|
1865
|
+
const dt = done ? tl : Math.fround(tl * a);
|
|
1866
|
+
const tNew = done ? 0 : Math.fround(tl - dt);
|
|
1867
|
+
f[cur + P_L * PLANE + c] = tNew;
|
|
1868
|
+
for (let q = 0; q < 4; q++) {
|
|
1869
|
+
const idx = cur + (P_P0 + q) * PLANE + c;
|
|
1870
|
+
const eps = Math.max(1e-3, pig[q] * 2e-4);
|
|
1871
|
+
const v = Math.max(0, Math.fround(f[idx] - Math.fround(dt * pig[q])));
|
|
1872
|
+
f[idx] = tNew === 0 && v <= eps ? 0 : v;
|
|
1873
|
+
}
|
|
1874
|
+
if (tNew > 0) remainingTracer = true;
|
|
1875
|
+
}
|
|
1876
|
+
if (dl > 0) {
|
|
1877
|
+
const done = a >= 1 || dl * (1 - a) <= 1e-7;
|
|
1878
|
+
const dd = done ? dl : Math.fround(dl * a);
|
|
1879
|
+
const dNew = done ? 0 : Math.fround(dl - dd);
|
|
1880
|
+
f[b + P_DL * PLANE + c] = dNew;
|
|
1881
|
+
for (let q = 0; q < 4; q++) {
|
|
1882
|
+
const idx = b + (P_D0 + q) * PLANE + c;
|
|
1883
|
+
const eps = Math.max(1e-3, pig[q] * 2e-4);
|
|
1884
|
+
const v = Math.max(0, Math.fround(f[idx] - Math.fround(dd * pig[q])));
|
|
1885
|
+
f[idx] = dNew === 0 && v <= eps ? 0 : v;
|
|
1886
|
+
}
|
|
1887
|
+
if (dNew > 0) remainingTracer = true;
|
|
1888
|
+
}
|
|
1889
|
+
if (f[cur + P_L * PLANE + c] === 0 && f[b + P_DL * PLANE + c] === 0) {
|
|
1890
|
+
for (let q = 0; q < 4; q++) {
|
|
1891
|
+
const eps = Math.max(1e-3, pig[q] * 2e-4);
|
|
1892
|
+
const pi = cur + (P_P0 + q) * PLANE + c;
|
|
1893
|
+
if (f[pi] <= eps) f[pi] = 0;
|
|
1894
|
+
const di = b + (P_D0 + q) * PLANE + c;
|
|
1895
|
+
if (f[di] <= eps) f[di] = 0;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
if (yMax >= yMin) {
|
|
1901
|
+
lane.urgent.add(this.tileKeyOf(key));
|
|
1902
|
+
this.gpuDirtySlot(slot, yMin, yMax);
|
|
1903
|
+
lane.stamp++;
|
|
1904
|
+
}
|
|
1905
|
+
return remainingTracer;
|
|
1906
|
+
}
|
|
1755
1907
|
/**
|
|
1756
1908
|
* Dries wet paint at once (everything settles where it is, the water goes): in `rect`, or the
|
|
1757
1909
|
* whole layer. Whole tiles that end up dry are baked into the base and freed. Returns the
|
|
@@ -1779,10 +1931,28 @@ var WetWorld = class {
|
|
|
1779
1931
|
continue;
|
|
1780
1932
|
}
|
|
1781
1933
|
const f = this.f32;
|
|
1934
|
+
const pig = lane.ghost?.pigment ?? null;
|
|
1782
1935
|
for (let y = y0; y < y1; y++) {
|
|
1783
1936
|
for (let x = x0; x < x1; x++) {
|
|
1784
1937
|
const c = this.cell(lane, x, y);
|
|
1785
1938
|
const d = this.single(c) + P_D0 * PLANE;
|
|
1939
|
+
if (pig) {
|
|
1940
|
+
const tl = f[c + P_L * PLANE];
|
|
1941
|
+
const dl = f[d + 4 * PLANE];
|
|
1942
|
+
if (tl > 0 || dl > 0) {
|
|
1943
|
+
f[c + P_L * PLANE] = 0;
|
|
1944
|
+
f[d + 4 * PLANE] = 0;
|
|
1945
|
+
for (let q = 0; q < 4; q++) {
|
|
1946
|
+
const eps = Math.max(1e-3, pig[q] * 2e-4);
|
|
1947
|
+
const pi = c + (P_P0 + q) * PLANE;
|
|
1948
|
+
const pv = Math.max(0, Math.fround(f[pi] - Math.fround(tl * pig[q])));
|
|
1949
|
+
f[pi] = pv <= eps ? 0 : pv;
|
|
1950
|
+
const di = d + q * PLANE;
|
|
1951
|
+
const dv = Math.max(0, Math.fround(f[di] - Math.fround(dl * pig[q])));
|
|
1952
|
+
f[di] = dv <= eps ? 0 : dv;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1786
1956
|
for (let q = 0; q < MASS_CHANNELS; q++) {
|
|
1787
1957
|
f[d + q * PLANE] += f[c + (P_P0 + q) * PLANE];
|
|
1788
1958
|
f[c + (P_P0 + q) * PLANE] = 0;
|
|
@@ -1797,6 +1967,7 @@ var WetWorld = class {
|
|
|
1797
1967
|
bounds = unionRect(bounds, { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
|
|
1798
1968
|
this.markEdited(lane, x0, y0, x1, y1);
|
|
1799
1969
|
}
|
|
1970
|
+
if (lane.ghost && !this.hasGhostTracer(lane)) lane.ghost = null;
|
|
1800
1971
|
this.syncList();
|
|
1801
1972
|
this.refreshHalos();
|
|
1802
1973
|
lane.stamp++;
|
|
@@ -1808,10 +1979,14 @@ var WetWorld = class {
|
|
|
1808
1979
|
*/
|
|
1809
1980
|
bakeKey(key, all) {
|
|
1810
1981
|
const lane = this.lanes[this.laneIndexOf(key)];
|
|
1982
|
+
if (lane.ghost) {
|
|
1983
|
+
this.fadeGhostKey(key, lane, 1);
|
|
1984
|
+
if (!this.hasGhostTracer(lane)) lane.ghost = null;
|
|
1985
|
+
}
|
|
1811
1986
|
const [tx, ty] = this.tileXY(key);
|
|
1812
1987
|
const tk = tileKey(tx, ty);
|
|
1813
1988
|
let lightChanged = false;
|
|
1814
|
-
if (lane.lit) {
|
|
1989
|
+
if (lane.lit && !lane.ghost) {
|
|
1815
1990
|
const lout = this.shadeLightSlot(key, lane.light.tiles.get(tk) ?? null, all ? "bake-all" : "bake");
|
|
1816
1991
|
if (lout) {
|
|
1817
1992
|
const tiles2 = new Map(lane.light.tiles);
|
|
@@ -1857,7 +2032,8 @@ var WetWorld = class {
|
|
|
1857
2032
|
const out = src ? new Uint8ClampedArray(src) : new Uint8ClampedArray(TILE * TILE * 4);
|
|
1858
2033
|
const w = Math.min(WET_TILE, this.width - tx * WET_TILE);
|
|
1859
2034
|
const h = Math.min(WET_TILE, this.height - ty * WET_TILE);
|
|
1860
|
-
const
|
|
2035
|
+
const lane = this.lanes[this.laneIndexOf(key)];
|
|
2036
|
+
const lit = lane.lit && !lane.ghost;
|
|
1861
2037
|
const changed = shadeTile(
|
|
1862
2038
|
this.f32,
|
|
1863
2039
|
PLANE,
|
|
@@ -1885,7 +2061,38 @@ var WetWorld = class {
|
|
|
1885
2061
|
for (const key of this.active) {
|
|
1886
2062
|
if (this.laneIndexOf(key) !== lane.index) continue;
|
|
1887
2063
|
const tk = this.tileKeyOf(key);
|
|
1888
|
-
|
|
2064
|
+
let out;
|
|
2065
|
+
if (lane.ghost) {
|
|
2066
|
+
const b = this.slotBase(this.slotOf(key));
|
|
2067
|
+
const cur = b + this.parity * SET_PLANES * PLANE;
|
|
2068
|
+
const savedP = this.f32.slice(cur + P_P0 * PLANE, cur + (P_P0 + MASS_CHANNELS) * PLANE);
|
|
2069
|
+
const savedD = this.f32.slice(b + P_D0 * PLANE, b + (P_D0 + MASS_CHANNELS) * PLANE);
|
|
2070
|
+
const pig = lane.ghost.pigment;
|
|
2071
|
+
for (let y = 1; y <= WET_TILE; y++) {
|
|
2072
|
+
for (let x = 1; x <= WET_TILE; x++) {
|
|
2073
|
+
const c = y * S2 + x;
|
|
2074
|
+
const tl = this.f32[cur + P_L * PLANE + c];
|
|
2075
|
+
const dl = this.f32[b + P_DL * PLANE + c];
|
|
2076
|
+
if (!(tl > 0 || dl > 0)) continue;
|
|
2077
|
+
this.f32[cur + P_L * PLANE + c] = 0;
|
|
2078
|
+
this.f32[b + P_DL * PLANE + c] = 0;
|
|
2079
|
+
for (let q = 0; q < 4; q++) {
|
|
2080
|
+
const eps = Math.max(1e-3, pig[q] * 2e-4);
|
|
2081
|
+
const pi = cur + (P_P0 + q) * PLANE + c;
|
|
2082
|
+
const pv = Math.max(0, Math.fround(this.f32[pi] - Math.fround(tl * pig[q])));
|
|
2083
|
+
this.f32[pi] = pv <= eps ? 0 : pv;
|
|
2084
|
+
const di = b + (P_D0 + q) * PLANE + c;
|
|
2085
|
+
const dv = Math.max(0, Math.fround(this.f32[di] - Math.fround(dl * pig[q])));
|
|
2086
|
+
this.f32[di] = dv <= eps ? 0 : dv;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
out = this.shadeSlot(key, lane.base.tiles.get(tk)?.data ?? null, "bake-all");
|
|
2091
|
+
this.f32.set(savedP, cur + P_P0 * PLANE);
|
|
2092
|
+
this.f32.set(savedD, b + P_D0 * PLANE);
|
|
2093
|
+
} else {
|
|
2094
|
+
out = this.shadeSlot(key, lane.base.tiles.get(tk)?.data ?? null, "bake-all");
|
|
2095
|
+
}
|
|
1889
2096
|
if (!out) continue;
|
|
1890
2097
|
const t = finishTile(out, 4, 0);
|
|
1891
2098
|
if (t) tiles.set(tk, t);
|
|
@@ -1897,7 +2104,7 @@ var WetWorld = class {
|
|
|
1897
2104
|
bakedLight(id) {
|
|
1898
2105
|
this.requireIdle();
|
|
1899
2106
|
const lane = this.laneOf(id);
|
|
1900
|
-
if (!lane.lit) return lane.light;
|
|
2107
|
+
if (!lane.lit || lane.ghost) return lane.light;
|
|
1901
2108
|
const tiles = new Map(lane.light.tiles);
|
|
1902
2109
|
for (const key of this.active) {
|
|
1903
2110
|
if (this.laneIndexOf(key) !== lane.index) continue;
|
|
@@ -2045,7 +2252,7 @@ var WetWorld = class {
|
|
|
2045
2252
|
*/
|
|
2046
2253
|
lightDisplay(id) {
|
|
2047
2254
|
const lane = this.laneOf(id);
|
|
2048
|
-
if (!lane.lit) return null;
|
|
2255
|
+
if (!lane.lit || lane.ghost) return null;
|
|
2049
2256
|
const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
|
|
2050
2257
|
let changed = false;
|
|
2051
2258
|
const live = /* @__PURE__ */ new Set();
|
|
@@ -2291,7 +2498,7 @@ var WetWorld = class {
|
|
|
2291
2498
|
slots.set(key, { planes, last: this.u32[(this.layout.last >> 2) + key] });
|
|
2292
2499
|
bytes += planes.byteLength;
|
|
2293
2500
|
}
|
|
2294
|
-
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, light: l.light, lit: l.lit }));
|
|
2501
|
+
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, light: l.light, lit: l.lit, ghost: cloneGhost(l.ghost) }));
|
|
2295
2502
|
return { clock: this.clock, tilt: { ...this.tilt }, lanes, slots, bytes };
|
|
2296
2503
|
}
|
|
2297
2504
|
/** Puts a snapshot back exactly (the simulation then continues as it would have). */
|
|
@@ -2301,8 +2508,8 @@ var WetWorld = class {
|
|
|
2301
2508
|
this.lanes.fill(null);
|
|
2302
2509
|
this.tilt = { ...snap.tilt };
|
|
2303
2510
|
for (const l of snap.lanes) {
|
|
2304
|
-
const lane = newLane(l.id, l.index, { ...l.params }, l.flow, l.flowStrength, l.base, l.light);
|
|
2305
|
-
lane.lit = lane.lit || Boolean(l.lit);
|
|
2511
|
+
const lane = newLane(l.id, l.index, { ...l.params }, l.flow, l.flowStrength, l.base, l.light, l.ghost);
|
|
2512
|
+
lane.lit = !lane.ghost && (lane.lit || Boolean(l.lit));
|
|
2306
2513
|
this.lanes[l.index] = lane;
|
|
2307
2514
|
this.pack(this.lanes[l.index]);
|
|
2308
2515
|
}
|