@pixerate/editor 0.275.1 → 0.277.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 +62 -62
- package/dist/animation-editor/index.js +6 -6
- package/dist/brushes/index.cjs +84 -71
- package/dist/brushes/index.js +20 -7
- package/dist/brushes/patch.d.cts +4 -4
- package/dist/brushes/patch.d.ts +4 -4
- package/dist/brushes/profiles.d.cts +4 -0
- package/dist/brushes/profiles.d.ts +4 -0
- package/dist/brushes/schema.d.cts +2 -0
- package/dist/brushes/schema.d.ts +2 -0
- package/dist/brushes/types.d.cts +32 -1
- package/dist/brushes/types.d.ts +32 -1
- package/dist/{brushstrokeRender-2RQL46WB.cjs → brushstrokeRender-7HOMKZNO.cjs} +21 -21
- package/dist/{brushstrokeRender-WGY4DZHF.js → brushstrokeRender-EIK3625T.js} +5 -5
- package/dist/canvas/index.cjs +13 -13
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-T6KNJVBP.cjs → chunk-4NZTK7LM.cjs} +35 -35
- package/dist/{chunk-LMG57L5A.js → chunk-CTHWXW5M.js} +121 -8
- package/dist/{chunk-43XJC3PP.js → chunk-DWPFGWW5.js} +1 -1
- package/dist/{chunk-MSWLJI4G.js → chunk-FNIWHSLI.js} +2 -2
- package/dist/{chunk-JDXWLCXF.cjs → chunk-H3EME3S5.cjs} +2 -2
- package/dist/{chunk-Y2ISSVPH.js → chunk-HHX7ST6Z.js} +167 -9
- package/dist/{chunk-6QNDGMK7.cjs → chunk-HRUK5TFW.cjs} +187 -45
- package/dist/{chunk-NIBFKXMD.cjs → chunk-IFWLZZCW.cjs} +619 -2
- package/dist/{chunk-W6JDUXP3.js → chunk-J23JOHPR.js} +1 -1
- package/dist/{chunk-HVO6XCI4.js → chunk-JPB65KQ6.js} +1 -1
- package/dist/{chunk-Y354ETAE.cjs → chunk-JVFGK3DK.cjs} +69 -58
- package/dist/{chunk-ACTHSQOI.js → chunk-LDZOXESA.js} +618 -1
- package/dist/{chunk-IEEY5RJV.js → chunk-LZE2QZCT.js} +1 -1
- package/dist/{chunk-DHC4E2DU.cjs → chunk-MITQGHN3.cjs} +121 -8
- package/dist/{chunk-75SYKUOT.js → chunk-N4MCWQZ7.js} +1 -1
- package/dist/{chunk-G5TCMKX5.js → chunk-NJLPW4MM.js} +17 -6
- package/dist/{chunk-STLTOISW.cjs → chunk-NPPMKMCU.cjs} +17 -17
- package/dist/{chunk-OLZXXAER.cjs → chunk-PEQWARCP.cjs} +408 -250
- package/dist/{chunk-ZM7IKQ4X.js → chunk-PPDUCZZV.js} +152 -10
- package/dist/{chunk-RAPNRK4R.cjs → chunk-QBZEC2JJ.cjs} +88 -88
- package/dist/{chunk-RJGW3RQS.js → chunk-ROURHJYT.js} +1 -1
- package/dist/{chunk-62MFUHSI.cjs → chunk-UQXULAMQ.cjs} +16 -16
- package/dist/{chunk-YYZC2EQO.cjs → chunk-VNF7A2JA.cjs} +23 -23
- package/dist/{chunk-C6VB7CPY.cjs → chunk-WFSYZO5Z.cjs} +1 -1
- package/dist/image-editor/index.cjs +880 -852
- package/dist/image-editor/index.js +36 -8
- package/dist/paint/index.cjs +33 -5
- package/dist/paint/index.d.cts +1 -0
- package/dist/paint/index.d.ts +1 -0
- package/dist/paint/index.js +32 -4
- package/dist/paint/live-patterns.d.cts +149 -0
- package/dist/paint/live-patterns.d.ts +149 -0
- package/dist/paint/wet/world.d.cts +13 -0
- package/dist/paint/wet/world.d.ts +13 -0
- package/dist/paint-editor/brush-model.d.cts +21 -1
- package/dist/paint-editor/brush-model.d.ts +21 -1
- package/dist/paint-editor/controller.d.cts +102 -1
- package/dist/paint-editor/controller.d.ts +102 -1
- package/dist/paint-editor/document.d.cts +8 -1
- package/dist/paint-editor/document.d.ts +8 -1
- package/dist/paint-editor/ferro-tools.d.cts +10 -0
- package/dist/paint-editor/ferro-tools.d.ts +10 -0
- package/dist/paint-editor/ferro.d.cts +134 -0
- package/dist/paint-editor/ferro.d.ts +134 -0
- package/dist/paint-editor/index.cjs +2291 -904
- package/dist/paint-editor/index.d.cts +2 -0
- package/dist/paint-editor/index.d.ts +2 -0
- package/dist/paint-editor/index.js +1533 -146
- package/dist/paint-editor/stroke.d.cts +14 -0
- package/dist/paint-editor/stroke.d.ts +14 -0
- package/dist/paint-editor/thick.d.cts +15 -2
- package/dist/paint-editor/thick.d.ts +15 -2
- package/dist/paint-editor/ui.d.cts +7 -1
- package/dist/paint-editor/ui.d.ts +7 -1
- package/dist/paint-editor/wet-tools.d.cts +42 -0
- package/dist/paint-editor/wet-tools.d.ts +42 -0
- package/dist/paint-editor/wet.d.cts +22 -1
- package/dist/paint-editor/wet.d.ts +22 -1
- package/dist/vector/index.cjs +1002 -1002
- package/dist/vector/index.js +7 -7
- package/package.json +1 -1
- package/skills/pixerate-brushes/SKILL.md +1 -0
- package/skills/pixerate-paint-editor/SKILL.md +11 -29
- package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
- package/skills/pixerate-paint-editor/references/ferrofluid.md +36 -0
|
@@ -50,6 +50,7 @@ import { PaintWetLayers, type PaintWetOptions, type WetEngineInfo, type WetGpuMo
|
|
|
50
50
|
import { type WetParams, type WetTilt } from '../paint/wet/model.js';
|
|
51
51
|
import { type WetFlowSpec } from '../paint/wet/flow.js';
|
|
52
52
|
import { type WetBackendInfo } from '../paint/wet/world.js';
|
|
53
|
+
import { type LivePatternKind, type LivePatternStats, type LivePatternsInput } from '../paint/live-patterns.js';
|
|
53
54
|
import { type TimeLapse, type TimeLapseOptions } from './replay.js';
|
|
54
55
|
import { EffectsSlot } from '../effects/helpers.js';
|
|
55
56
|
import type { EffectDescription, EffectProvider, EffectValue } from '../effects/types.js';
|
|
@@ -62,6 +63,7 @@ import type { PaintDirector } from './presence.js';
|
|
|
62
63
|
import type { PresenceActorInfo } from '../animation/presence.js';
|
|
63
64
|
import { type PaintProposals } from './collaboration.js';
|
|
64
65
|
import { type BreedStrokesResult, type ProposalGenomeResult, type StrokeMutationRates } from './evolution.js';
|
|
66
|
+
import { type PaintMagnet } from './ferro.js';
|
|
65
67
|
export declare class PaintEditorError extends Error {
|
|
66
68
|
constructor(message: string);
|
|
67
69
|
}
|
|
@@ -121,6 +123,13 @@ export interface PaintWetStateInfo {
|
|
|
121
123
|
/** Engine B is shading the wet paint now. */
|
|
122
124
|
gpuActive: boolean;
|
|
123
125
|
}
|
|
126
|
+
/** One layer's Gray-Scott live-pattern state and wet coverage. */
|
|
127
|
+
export interface PaintLayerLivePatternsInfo extends LivePatternStats {
|
|
128
|
+
layerId: string;
|
|
129
|
+
wet: boolean;
|
|
130
|
+
wetCells: number;
|
|
131
|
+
wetBounds: RasterRect | null;
|
|
132
|
+
}
|
|
124
133
|
export interface PaintEditorState {
|
|
125
134
|
document: PaintDocumentState;
|
|
126
135
|
/** The active brush (possibly edited from its preset). */
|
|
@@ -540,6 +549,9 @@ export declare class PaintEditorController {
|
|
|
540
549
|
private readonly litCaches;
|
|
541
550
|
/** The live impasto stroke's channels. */
|
|
542
551
|
private liveThick;
|
|
552
|
+
/** The live ferrofluid stroke's channel. */
|
|
553
|
+
private liveFerro;
|
|
554
|
+
private nextMagnetId;
|
|
543
555
|
/** The UV / blacklight setting (saved with the document, not undo steps). */
|
|
544
556
|
private uvLight;
|
|
545
557
|
private readonly uvCaches;
|
|
@@ -1278,6 +1290,51 @@ export declare class PaintEditorController {
|
|
|
1278
1290
|
layerId: string;
|
|
1279
1291
|
flattened: boolean;
|
|
1280
1292
|
};
|
|
1293
|
+
/** The document's `ferro` and `ferroSettings` without these layers. */
|
|
1294
|
+
private ferroWithout;
|
|
1295
|
+
/** Runs deterministic advection on all pixel layers with liquid ferrofluid toward `magnets`. */
|
|
1296
|
+
private advectDocumentFerro;
|
|
1297
|
+
/** Lists the magnets on the canvas. */
|
|
1298
|
+
listMagnets(): PaintMagnet[];
|
|
1299
|
+
/** Places a dipole magnet on the canvas and advects liquid ferrofluid toward it (one undo step). */
|
|
1300
|
+
addMagnet(input: {
|
|
1301
|
+
x: number;
|
|
1302
|
+
y: number;
|
|
1303
|
+
strength?: number;
|
|
1304
|
+
radius?: number;
|
|
1305
|
+
}): PaintMagnet;
|
|
1306
|
+
/** Moves or retunes a magnet and advects liquid ferrofluid toward the magnets (one undo step). */
|
|
1307
|
+
updateMagnet(id: string, update: {
|
|
1308
|
+
x?: number;
|
|
1309
|
+
y?: number;
|
|
1310
|
+
strength?: number;
|
|
1311
|
+
radius?: number;
|
|
1312
|
+
}, commitToHistory?: boolean): PaintMagnet;
|
|
1313
|
+
/** Removes magnets by id, or all magnets when `ids` is omitted or empty (one undo step). Returns the removed ids. */
|
|
1314
|
+
removeMagnets(ids?: readonly string[]): string[];
|
|
1315
|
+
/** Replaces the canvas magnets (used by replay). */
|
|
1316
|
+
replaceMagnets(magnets: readonly PaintMagnet[], advect?: boolean): void;
|
|
1317
|
+
/** Reads a layer's liquid ferrofluid coverage and active Rosensweig spike relief under the canvas magnets. */
|
|
1318
|
+
getFerrofluid(layerId?: string): {
|
|
1319
|
+
layerId: string;
|
|
1320
|
+
ferro: boolean;
|
|
1321
|
+
bounds: RasterRect | null;
|
|
1322
|
+
peak: number;
|
|
1323
|
+
coverage: number;
|
|
1324
|
+
activeSpikeArea: number;
|
|
1325
|
+
maxSpikeHeight: number;
|
|
1326
|
+
magnetCount: number;
|
|
1327
|
+
wavelength: number;
|
|
1328
|
+
threshold: number;
|
|
1329
|
+
};
|
|
1330
|
+
/** Cures a layer's liquid ferrofluid: bakes the current Rosensweig spikes and glossy metallic material into `doc.thick` and clears `doc.ferro` (one undo step). */
|
|
1331
|
+
cureFerrofluid(layerId?: string): {
|
|
1332
|
+
layerId: string;
|
|
1333
|
+
cured: boolean;
|
|
1334
|
+
bounds: RasterRect | null;
|
|
1335
|
+
maxSpikeHeight: number;
|
|
1336
|
+
activeSpikeArea: number;
|
|
1337
|
+
};
|
|
1281
1338
|
/**
|
|
1282
1339
|
* A fresh, headless editor for replaying this painting at `scale` (canvas, dpi and paper grain
|
|
1283
1340
|
* scaled; every pixel layer empty; the same brushes, lighting and wet options). Dispose of it
|
|
@@ -1406,6 +1463,50 @@ export declare class PaintEditorController {
|
|
|
1406
1463
|
ty: number;
|
|
1407
1464
|
data: Uint8ClampedArray;
|
|
1408
1465
|
}>;
|
|
1466
|
+
private layerLivePatternsInfo;
|
|
1467
|
+
private checkLivePatternsInput;
|
|
1468
|
+
/**
|
|
1469
|
+
* Reads a layer's Gray-Scott reaction-diffusion live patterns (`layerId`, default the active
|
|
1470
|
+
* layer), plus all layers with wet paint or live patterns and the built-in Pearson presets.
|
|
1471
|
+
*/
|
|
1472
|
+
getLivePatterns(layerId?: string): PaintLayerLivePatternsInfo & {
|
|
1473
|
+
layers: PaintLayerLivePatternsInfo[];
|
|
1474
|
+
presets: Readonly<Record<LivePatternKind, {
|
|
1475
|
+
feed: number;
|
|
1476
|
+
kill: number;
|
|
1477
|
+
}>>;
|
|
1478
|
+
brushPresets: readonly string[];
|
|
1479
|
+
};
|
|
1480
|
+
/**
|
|
1481
|
+
* Configures a layer's Gray-Scott reaction-diffusion live patterns (or clears them with
|
|
1482
|
+
* `enabled: false`). One undo step.
|
|
1483
|
+
*/
|
|
1484
|
+
setLivePatterns(layerId: string | undefined, update: LivePatternsInput & {
|
|
1485
|
+
enabled?: boolean;
|
|
1486
|
+
}): PaintLayerLivePatternsInfo;
|
|
1487
|
+
/**
|
|
1488
|
+
* Seeds Gray-Scott reaction-diffusion activator (`V`) discs into a layer's live-pattern field so
|
|
1489
|
+
* patterns grow where the layer holds wet water and freeze where it dries. One undo step.
|
|
1490
|
+
*/
|
|
1491
|
+
seedLivePatterns(options: {
|
|
1492
|
+
layerId?: string;
|
|
1493
|
+
points: ReadonlyArray<{
|
|
1494
|
+
x: number;
|
|
1495
|
+
y: number;
|
|
1496
|
+
strength?: number;
|
|
1497
|
+
}>;
|
|
1498
|
+
radius?: number;
|
|
1499
|
+
seed?: number;
|
|
1500
|
+
pattern?: LivePatternKind;
|
|
1501
|
+
feed?: number;
|
|
1502
|
+
kill?: number;
|
|
1503
|
+
scale?: number;
|
|
1504
|
+
contrast?: number;
|
|
1505
|
+
secondColor?: string | null;
|
|
1506
|
+
relief?: number;
|
|
1507
|
+
}): PaintLayerLivePatternsInfo & {
|
|
1508
|
+
seededCells: number;
|
|
1509
|
+
};
|
|
1409
1510
|
private sources;
|
|
1410
1511
|
/** The layers with the live stroke's surface in place. */
|
|
1411
1512
|
private currentLayers;
|
|
@@ -1425,7 +1526,7 @@ export declare class PaintEditorController {
|
|
|
1425
1526
|
* Linear Dodge (`::uv`) spill layer directly above it.
|
|
1426
1527
|
*/
|
|
1427
1528
|
private uvLayers;
|
|
1428
|
-
/** The layers with thick paint lit (each thick layer's pixels replaced by its lit tiles). */
|
|
1529
|
+
/** The layers with thick paint and ferrofluid spikes lit (each thick or ferrofluid layer's pixels replaced by its lit tiles). */
|
|
1429
1530
|
private litLayers;
|
|
1430
1531
|
/**
|
|
1431
1532
|
* Light paint over the (lit) layers: glow, rim and clarity in each layer's pixels, and its spill
|
|
@@ -13,6 +13,7 @@ import { type PaperSettings } from './paper.cjs';
|
|
|
13
13
|
import type { RecordedStroke } from './strokes.cjs';
|
|
14
14
|
import type { WetEvent } from './wet.cjs';
|
|
15
15
|
import type { ThickChannels } from './thick.cjs';
|
|
16
|
+
import type { FerroSettings, PaintMagnet } from './ferro.cjs';
|
|
16
17
|
import type { Sticker } from './stickers.cjs';
|
|
17
18
|
import type { PaintEffectSource, PaintPaperEffect } from './effects.cjs';
|
|
18
19
|
import type { HeatField } from './heat.cjs';
|
|
@@ -61,6 +62,12 @@ export interface PaintDocumentState {
|
|
|
61
62
|
thermo?: Readonly<Record<string, TiledSurface>>;
|
|
62
63
|
/** Coarse document-level heat field (1/4 resolution) for thermochromic ink and wet-paint drying. See `heat.ts`. */
|
|
63
64
|
heat?: HeatField;
|
|
65
|
+
/** Ferrofluid paint: each layer's liquid ferrofluid channel, 0..255 per pixel in layer space (layers without ferrofluid are missing). See `ferro.ts`. */
|
|
66
|
+
ferro?: Readonly<Record<string, TiledSurface>>;
|
|
67
|
+
/** Per-layer Rosensweig spike settings (`wavelength`, `threshold`) from the last ferrofluid stroke on that layer. */
|
|
68
|
+
ferroSettings?: Readonly<Record<string, FerroSettings>>;
|
|
69
|
+
/** Dipole magnets placed on the canvas (document pixel coordinates). */
|
|
70
|
+
magnets?: PaintMagnet[];
|
|
64
71
|
/** Thick paint: each layer's height and material channels (layers without relief are missing). */
|
|
65
72
|
thick?: Readonly<Record<string, ThickChannels>>;
|
|
66
73
|
/** Effect layers: the provider effect each one is drawn from (layers that are gone are ignored). */
|
|
@@ -79,7 +86,7 @@ export interface PaintDocumentState {
|
|
|
79
86
|
/** A change a replay repeats besides strokes, recorded with how many strokes came before it. */
|
|
80
87
|
export interface PaintEvent {
|
|
81
88
|
at: number;
|
|
82
|
-
kind: 'fill' | 'fill-region' | 'clear' | 'flatten' | 'stickers' | 'live-strokes';
|
|
89
|
+
kind: 'fill' | 'fill-region' | 'clear' | 'flatten' | 'stickers' | 'live-strokes' | 'magnets' | 'cure-ferrofluid';
|
|
83
90
|
layerId: string;
|
|
84
91
|
/** The fill's options (colour, opacity, grain, pressure, blend), or the layer's stickers or live strokes after an edit. */
|
|
85
92
|
data?: Record<string, unknown>;
|
|
@@ -13,6 +13,7 @@ import { type PaperSettings } from './paper.js';
|
|
|
13
13
|
import type { RecordedStroke } from './strokes.js';
|
|
14
14
|
import type { WetEvent } from './wet.js';
|
|
15
15
|
import type { ThickChannels } from './thick.js';
|
|
16
|
+
import type { FerroSettings, PaintMagnet } from './ferro.js';
|
|
16
17
|
import type { Sticker } from './stickers.js';
|
|
17
18
|
import type { PaintEffectSource, PaintPaperEffect } from './effects.js';
|
|
18
19
|
import type { HeatField } from './heat.js';
|
|
@@ -61,6 +62,12 @@ export interface PaintDocumentState {
|
|
|
61
62
|
thermo?: Readonly<Record<string, TiledSurface>>;
|
|
62
63
|
/** Coarse document-level heat field (1/4 resolution) for thermochromic ink and wet-paint drying. See `heat.ts`. */
|
|
63
64
|
heat?: HeatField;
|
|
65
|
+
/** Ferrofluid paint: each layer's liquid ferrofluid channel, 0..255 per pixel in layer space (layers without ferrofluid are missing). See `ferro.ts`. */
|
|
66
|
+
ferro?: Readonly<Record<string, TiledSurface>>;
|
|
67
|
+
/** Per-layer Rosensweig spike settings (`wavelength`, `threshold`) from the last ferrofluid stroke on that layer. */
|
|
68
|
+
ferroSettings?: Readonly<Record<string, FerroSettings>>;
|
|
69
|
+
/** Dipole magnets placed on the canvas (document pixel coordinates). */
|
|
70
|
+
magnets?: PaintMagnet[];
|
|
64
71
|
/** Thick paint: each layer's height and material channels (layers without relief are missing). */
|
|
65
72
|
thick?: Readonly<Record<string, ThickChannels>>;
|
|
66
73
|
/** Effect layers: the provider effect each one is drawn from (layers that are gone are ignored). */
|
|
@@ -79,7 +86,7 @@ export interface PaintDocumentState {
|
|
|
79
86
|
/** A change a replay repeats besides strokes, recorded with how many strokes came before it. */
|
|
80
87
|
export interface PaintEvent {
|
|
81
88
|
at: number;
|
|
82
|
-
kind: 'fill' | 'fill-region' | 'clear' | 'flatten' | 'stickers' | 'live-strokes';
|
|
89
|
+
kind: 'fill' | 'fill-region' | 'clear' | 'flatten' | 'stickers' | 'live-strokes' | 'magnets' | 'cure-ferrofluid';
|
|
83
90
|
layerId: string;
|
|
84
91
|
/** The fill's options (colour, opacity, grain, pressure, blend), or the layer's stickers or live strokes after an edit. */
|
|
85
92
|
data?: Record<string, unknown>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tools for ferrofluid paint and magnets: ferrofluid strokes are painted with `paint_stroke`
|
|
3
|
+
* and a brush whose `ferro.amount` is above 0 (`ferrofluid`, `ferro-ink`); these tools place,
|
|
4
|
+
* move and remove magnets (`add_magnet`, `update_magnet`, `remove_magnets`, `list_magnets`), read
|
|
5
|
+
* a layer's liquid ferrofluid and live Rosensweig spike stats (`get_ferrofluid`), and cure the
|
|
6
|
+
* liquid spikes into solid impasto relief (`cure_ferrofluid`).
|
|
7
|
+
*/
|
|
8
|
+
import type { PaintAgentTool } from './hosted.cjs';
|
|
9
|
+
import type { PaintEditorController } from './controller.cjs';
|
|
10
|
+
export declare function createFerroTools(c: PaintEditorController): PaintAgentTool[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tools for ferrofluid paint and magnets: ferrofluid strokes are painted with `paint_stroke`
|
|
3
|
+
* and a brush whose `ferro.amount` is above 0 (`ferrofluid`, `ferro-ink`); these tools place,
|
|
4
|
+
* move and remove magnets (`add_magnet`, `update_magnet`, `remove_magnets`, `list_magnets`), read
|
|
5
|
+
* a layer's liquid ferrofluid and live Rosensweig spike stats (`get_ferrofluid`), and cure the
|
|
6
|
+
* liquid spikes into solid impasto relief (`cure_ferrofluid`).
|
|
7
|
+
*/
|
|
8
|
+
import type { PaintAgentTool } from './hosted.js';
|
|
9
|
+
import type { PaintEditorController } from './controller.js';
|
|
10
|
+
export declare function createFerroTools(c: PaintEditorController): PaintAgentTool[];
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ferrofluid paint: a per-layer liquid ferrofluid channel (`doc.ferro`, one byte 0..255 per pixel
|
|
3
|
+
* in layer space, like `doc.light`) and document-space magnets (`doc.magnets`).
|
|
4
|
+
*
|
|
5
|
+
* Where the analytical dipole magnetic field `|B|` exceeds the critical threshold `Bc` and
|
|
6
|
+
* `ferro > 0`, the liquid rises into a hexagonal Rosensweig spike relief field whose spacing
|
|
7
|
+
* follows the capillary wavelength, whose amplitude scales as `sqrt(1 - Bc / |B|) * ferro`, and
|
|
8
|
+
* whose peaks tilt along `B`. Adding or moving a magnet runs deterministic advection steps on
|
|
9
|
+
* `doc.ferro` so the liquid gathers toward the magnets.
|
|
10
|
+
*
|
|
11
|
+
* At render time, the spike height combines with `doc.thick` height and defaults to a dark,
|
|
12
|
+
* glossy metallic material where `ferro > 0`, so `lightTile` shades the spikes with the canvas
|
|
13
|
+
* lighting. `cure_ferrofluid` bakes the current spikes into `doc.thick` and clears `doc.ferro`.
|
|
14
|
+
*/
|
|
15
|
+
import { TiledSurface, type RasterRect } from '../raster/surface.cjs';
|
|
16
|
+
import { DEFAULT_FERRO, normalizeFerro as normalizeFerroSettings, type FerroSettings, type PaintBrush } from './brushes.cjs';
|
|
17
|
+
import { type ThickChannels } from './thick.cjs';
|
|
18
|
+
export { DEFAULT_FERRO, normalizeFerroSettings, type FerroSettings };
|
|
19
|
+
/** A dipole magnet on the canvas (document pixel coordinates). */
|
|
20
|
+
export interface PaintMagnet {
|
|
21
|
+
id: string;
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
/** Dipole strength and polarity (-1 .. 1). */
|
|
25
|
+
strength: number;
|
|
26
|
+
/** Characteristic dipole falloff radius in canvas pixels (8 .. 2000). */
|
|
27
|
+
radius: number;
|
|
28
|
+
}
|
|
29
|
+
export declare const DEFAULT_MAGNET_STRENGTH = 0.75;
|
|
30
|
+
export declare const DEFAULT_MAGNET_RADIUS = 72;
|
|
31
|
+
/** Dark carrier fluid sRGB colour (`#111827`) for advected ferrofluid outside existing pigment. */
|
|
32
|
+
export declare const FERRO_CARRIER_RGB: readonly [number, number, number];
|
|
33
|
+
/** Default glossy metallic material for ferrofluid (`gloss ≈ 0.88`, `metallic ≈ 0.65`, `rim ≈ 0.25`). */
|
|
34
|
+
export declare const FERRO_DEFAULT_GLOSS = 224;
|
|
35
|
+
export declare const FERRO_DEFAULT_METALLIC = 166;
|
|
36
|
+
export declare const FERRO_DEFAULT_RIM = 64;
|
|
37
|
+
/** Validates and normalizes a magnet; throws a descriptive Error when a field is out of range. */
|
|
38
|
+
export declare function normalizeMagnet(raw: Partial<PaintMagnet> & {
|
|
39
|
+
id: string;
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
}, base?: PaintMagnet): PaintMagnet;
|
|
43
|
+
export declare function emptyFerro(): TiledSurface;
|
|
44
|
+
/** The ferrofluid channel as grey paint for a stroke's twin: white, with alpha = ferro. */
|
|
45
|
+
export declare function ferroToShadow(ferro: TiledSurface): TiledSurface;
|
|
46
|
+
/** A twin's grey paint back as ferrofluid: grey × alpha. */
|
|
47
|
+
export declare function shadowToFerro(shadow: TiledSurface): TiledSurface;
|
|
48
|
+
/** The brush a stroke's ferrofluid twin paints with: same geometry, normal blend, RGB mixing. */
|
|
49
|
+
export declare function ferroTwinBrush(brush: PaintBrush): PaintBrush;
|
|
50
|
+
/** Ferrofluid scaled by `k` (0..1), e.g. under a fill that covers it. */
|
|
51
|
+
export declare function scaleFerro(ferro: TiledSurface, k: number): TiledSurface;
|
|
52
|
+
/** Ferrofluid of an upper layer merged down onto the one below (both in lower layer's space). */
|
|
53
|
+
export declare function mergeFerro(lower: TiledSurface | undefined, upper: TiledSurface | undefined, upperColor: TiledSurface): TiledSurface | undefined;
|
|
54
|
+
export interface FerroFieldSample {
|
|
55
|
+
bx: number;
|
|
56
|
+
by: number;
|
|
57
|
+
bz: number;
|
|
58
|
+
magnitude: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Analytical 3D magnetic dipole field `B = (bx, by, bz)` at canvas point `(x, y)` from a set of
|
|
62
|
+
* dipole magnets placed on the canvas plane (`z = -radius`).
|
|
63
|
+
*/
|
|
64
|
+
export declare function ferroFieldAt(x: number, y: number, magnets: readonly PaintMagnet[]): FerroFieldSample;
|
|
65
|
+
/**
|
|
66
|
+
* Hexagonal Rosensweig instability spike height (0..255) at canvas coordinate `(x, y)` for a pixel
|
|
67
|
+
* with liquid ferrofluid byte `ferroByte` (0..255).
|
|
68
|
+
*
|
|
69
|
+
* Where `|B| > threshold` and `ferroByte > 0`, spikes rise with amplitude `∝ sqrt(1 - Bc / |B|) * ferro`
|
|
70
|
+
* at the capillary `wavelength` and tilt along the tangential field `(bx, by)`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function ferroSpikeHeightAt(x: number, y: number, ferroByte: number, magnets: readonly PaintMagnet[], settings?: FerroSettings): number;
|
|
73
|
+
/**
|
|
74
|
+
* Runs deterministic mass-gathering advection steps on a layer's `ferro` surface toward `magnets`.
|
|
75
|
+
* Each step transports liquid ferrofluid along the dipole attraction field via bilinear splatting.
|
|
76
|
+
*/
|
|
77
|
+
export declare function advectFerro(ferro: TiledSurface, magnets: readonly PaintMagnet[], offset?: {
|
|
78
|
+
x: number;
|
|
79
|
+
y: number;
|
|
80
|
+
}, steps?: number): TiledSurface;
|
|
81
|
+
export interface FerroStats {
|
|
82
|
+
bounds: RasterRect | null;
|
|
83
|
+
peak: number;
|
|
84
|
+
liquidPixels: number;
|
|
85
|
+
maxSpikeHeight: number;
|
|
86
|
+
spikePixels: number;
|
|
87
|
+
magnetCount: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Inspects a layer's liquid ferrofluid channel and active Rosensweig spike relief under `magnets`.
|
|
91
|
+
*/
|
|
92
|
+
export declare function ferroInfo(ferro: TiledSurface | undefined, magnets?: readonly PaintMagnet[], settings?: FerroSettings, offset?: {
|
|
93
|
+
x: number;
|
|
94
|
+
y: number;
|
|
95
|
+
}): FerroStats;
|
|
96
|
+
export interface FerroLayerInput {
|
|
97
|
+
surface: TiledSurface;
|
|
98
|
+
magnets: readonly PaintMagnet[];
|
|
99
|
+
settings: FerroSettings;
|
|
100
|
+
offset: {
|
|
101
|
+
x: number;
|
|
102
|
+
y: number;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export declare function ferroCacheKey(ferro: FerroLayerInput | null | undefined): string;
|
|
106
|
+
/**
|
|
107
|
+
* Bakes a layer's liquid ferrofluid and active Rosensweig spikes into permanent `ThickChannels`
|
|
108
|
+
* (and ensures carrier colour covers advected ferrofluid pixels) for `cure_ferrofluid`.
|
|
109
|
+
*/
|
|
110
|
+
export declare function bakeFerroIntoThick(color: TiledSurface, ferro: TiledSurface, thick: ThickChannels | undefined, magnets: readonly PaintMagnet[], settings?: FerroSettings, offset?: {
|
|
111
|
+
x: number;
|
|
112
|
+
y: number;
|
|
113
|
+
}): {
|
|
114
|
+
color: TiledSurface;
|
|
115
|
+
thick: ThickChannels;
|
|
116
|
+
maxSpikeHeight: number;
|
|
117
|
+
spikePixels: number;
|
|
118
|
+
curedPixels: number;
|
|
119
|
+
};
|
|
120
|
+
/** A layer's liquid ferrofluid as saved: its rect (layer space), grey PNG, and spike settings. */
|
|
121
|
+
export interface SavedFerro {
|
|
122
|
+
rect: RasterRect;
|
|
123
|
+
ferro: string;
|
|
124
|
+
wavelength?: number;
|
|
125
|
+
threshold?: number;
|
|
126
|
+
}
|
|
127
|
+
export declare function ferroToJSON(ferro: TiledSurface, settings?: FerroSettings): SavedFerro | null;
|
|
128
|
+
/** Saved form → ferrofluid surface and settings; throws a sentence when it cannot be read. */
|
|
129
|
+
export declare function ferroFromJSON(raw: unknown): {
|
|
130
|
+
surface: TiledSurface;
|
|
131
|
+
settings: FerroSettings;
|
|
132
|
+
};
|
|
133
|
+
/** Saved magnets array → validated `PaintMagnet[]`. */
|
|
134
|
+
export declare function magnetsFromJSON(raw: unknown): PaintMagnet[];
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ferrofluid paint: a per-layer liquid ferrofluid channel (`doc.ferro`, one byte 0..255 per pixel
|
|
3
|
+
* in layer space, like `doc.light`) and document-space magnets (`doc.magnets`).
|
|
4
|
+
*
|
|
5
|
+
* Where the analytical dipole magnetic field `|B|` exceeds the critical threshold `Bc` and
|
|
6
|
+
* `ferro > 0`, the liquid rises into a hexagonal Rosensweig spike relief field whose spacing
|
|
7
|
+
* follows the capillary wavelength, whose amplitude scales as `sqrt(1 - Bc / |B|) * ferro`, and
|
|
8
|
+
* whose peaks tilt along `B`. Adding or moving a magnet runs deterministic advection steps on
|
|
9
|
+
* `doc.ferro` so the liquid gathers toward the magnets.
|
|
10
|
+
*
|
|
11
|
+
* At render time, the spike height combines with `doc.thick` height and defaults to a dark,
|
|
12
|
+
* glossy metallic material where `ferro > 0`, so `lightTile` shades the spikes with the canvas
|
|
13
|
+
* lighting. `cure_ferrofluid` bakes the current spikes into `doc.thick` and clears `doc.ferro`.
|
|
14
|
+
*/
|
|
15
|
+
import { TiledSurface, type RasterRect } from '../raster/surface.js';
|
|
16
|
+
import { DEFAULT_FERRO, normalizeFerro as normalizeFerroSettings, type FerroSettings, type PaintBrush } from './brushes.js';
|
|
17
|
+
import { type ThickChannels } from './thick.js';
|
|
18
|
+
export { DEFAULT_FERRO, normalizeFerroSettings, type FerroSettings };
|
|
19
|
+
/** A dipole magnet on the canvas (document pixel coordinates). */
|
|
20
|
+
export interface PaintMagnet {
|
|
21
|
+
id: string;
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
/** Dipole strength and polarity (-1 .. 1). */
|
|
25
|
+
strength: number;
|
|
26
|
+
/** Characteristic dipole falloff radius in canvas pixels (8 .. 2000). */
|
|
27
|
+
radius: number;
|
|
28
|
+
}
|
|
29
|
+
export declare const DEFAULT_MAGNET_STRENGTH = 0.75;
|
|
30
|
+
export declare const DEFAULT_MAGNET_RADIUS = 72;
|
|
31
|
+
/** Dark carrier fluid sRGB colour (`#111827`) for advected ferrofluid outside existing pigment. */
|
|
32
|
+
export declare const FERRO_CARRIER_RGB: readonly [number, number, number];
|
|
33
|
+
/** Default glossy metallic material for ferrofluid (`gloss ≈ 0.88`, `metallic ≈ 0.65`, `rim ≈ 0.25`). */
|
|
34
|
+
export declare const FERRO_DEFAULT_GLOSS = 224;
|
|
35
|
+
export declare const FERRO_DEFAULT_METALLIC = 166;
|
|
36
|
+
export declare const FERRO_DEFAULT_RIM = 64;
|
|
37
|
+
/** Validates and normalizes a magnet; throws a descriptive Error when a field is out of range. */
|
|
38
|
+
export declare function normalizeMagnet(raw: Partial<PaintMagnet> & {
|
|
39
|
+
id: string;
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
}, base?: PaintMagnet): PaintMagnet;
|
|
43
|
+
export declare function emptyFerro(): TiledSurface;
|
|
44
|
+
/** The ferrofluid channel as grey paint for a stroke's twin: white, with alpha = ferro. */
|
|
45
|
+
export declare function ferroToShadow(ferro: TiledSurface): TiledSurface;
|
|
46
|
+
/** A twin's grey paint back as ferrofluid: grey × alpha. */
|
|
47
|
+
export declare function shadowToFerro(shadow: TiledSurface): TiledSurface;
|
|
48
|
+
/** The brush a stroke's ferrofluid twin paints with: same geometry, normal blend, RGB mixing. */
|
|
49
|
+
export declare function ferroTwinBrush(brush: PaintBrush): PaintBrush;
|
|
50
|
+
/** Ferrofluid scaled by `k` (0..1), e.g. under a fill that covers it. */
|
|
51
|
+
export declare function scaleFerro(ferro: TiledSurface, k: number): TiledSurface;
|
|
52
|
+
/** Ferrofluid of an upper layer merged down onto the one below (both in lower layer's space). */
|
|
53
|
+
export declare function mergeFerro(lower: TiledSurface | undefined, upper: TiledSurface | undefined, upperColor: TiledSurface): TiledSurface | undefined;
|
|
54
|
+
export interface FerroFieldSample {
|
|
55
|
+
bx: number;
|
|
56
|
+
by: number;
|
|
57
|
+
bz: number;
|
|
58
|
+
magnitude: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Analytical 3D magnetic dipole field `B = (bx, by, bz)` at canvas point `(x, y)` from a set of
|
|
62
|
+
* dipole magnets placed on the canvas plane (`z = -radius`).
|
|
63
|
+
*/
|
|
64
|
+
export declare function ferroFieldAt(x: number, y: number, magnets: readonly PaintMagnet[]): FerroFieldSample;
|
|
65
|
+
/**
|
|
66
|
+
* Hexagonal Rosensweig instability spike height (0..255) at canvas coordinate `(x, y)` for a pixel
|
|
67
|
+
* with liquid ferrofluid byte `ferroByte` (0..255).
|
|
68
|
+
*
|
|
69
|
+
* Where `|B| > threshold` and `ferroByte > 0`, spikes rise with amplitude `∝ sqrt(1 - Bc / |B|) * ferro`
|
|
70
|
+
* at the capillary `wavelength` and tilt along the tangential field `(bx, by)`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function ferroSpikeHeightAt(x: number, y: number, ferroByte: number, magnets: readonly PaintMagnet[], settings?: FerroSettings): number;
|
|
73
|
+
/**
|
|
74
|
+
* Runs deterministic mass-gathering advection steps on a layer's `ferro` surface toward `magnets`.
|
|
75
|
+
* Each step transports liquid ferrofluid along the dipole attraction field via bilinear splatting.
|
|
76
|
+
*/
|
|
77
|
+
export declare function advectFerro(ferro: TiledSurface, magnets: readonly PaintMagnet[], offset?: {
|
|
78
|
+
x: number;
|
|
79
|
+
y: number;
|
|
80
|
+
}, steps?: number): TiledSurface;
|
|
81
|
+
export interface FerroStats {
|
|
82
|
+
bounds: RasterRect | null;
|
|
83
|
+
peak: number;
|
|
84
|
+
liquidPixels: number;
|
|
85
|
+
maxSpikeHeight: number;
|
|
86
|
+
spikePixels: number;
|
|
87
|
+
magnetCount: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Inspects a layer's liquid ferrofluid channel and active Rosensweig spike relief under `magnets`.
|
|
91
|
+
*/
|
|
92
|
+
export declare function ferroInfo(ferro: TiledSurface | undefined, magnets?: readonly PaintMagnet[], settings?: FerroSettings, offset?: {
|
|
93
|
+
x: number;
|
|
94
|
+
y: number;
|
|
95
|
+
}): FerroStats;
|
|
96
|
+
export interface FerroLayerInput {
|
|
97
|
+
surface: TiledSurface;
|
|
98
|
+
magnets: readonly PaintMagnet[];
|
|
99
|
+
settings: FerroSettings;
|
|
100
|
+
offset: {
|
|
101
|
+
x: number;
|
|
102
|
+
y: number;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export declare function ferroCacheKey(ferro: FerroLayerInput | null | undefined): string;
|
|
106
|
+
/**
|
|
107
|
+
* Bakes a layer's liquid ferrofluid and active Rosensweig spikes into permanent `ThickChannels`
|
|
108
|
+
* (and ensures carrier colour covers advected ferrofluid pixels) for `cure_ferrofluid`.
|
|
109
|
+
*/
|
|
110
|
+
export declare function bakeFerroIntoThick(color: TiledSurface, ferro: TiledSurface, thick: ThickChannels | undefined, magnets: readonly PaintMagnet[], settings?: FerroSettings, offset?: {
|
|
111
|
+
x: number;
|
|
112
|
+
y: number;
|
|
113
|
+
}): {
|
|
114
|
+
color: TiledSurface;
|
|
115
|
+
thick: ThickChannels;
|
|
116
|
+
maxSpikeHeight: number;
|
|
117
|
+
spikePixels: number;
|
|
118
|
+
curedPixels: number;
|
|
119
|
+
};
|
|
120
|
+
/** A layer's liquid ferrofluid as saved: its rect (layer space), grey PNG, and spike settings. */
|
|
121
|
+
export interface SavedFerro {
|
|
122
|
+
rect: RasterRect;
|
|
123
|
+
ferro: string;
|
|
124
|
+
wavelength?: number;
|
|
125
|
+
threshold?: number;
|
|
126
|
+
}
|
|
127
|
+
export declare function ferroToJSON(ferro: TiledSurface, settings?: FerroSettings): SavedFerro | null;
|
|
128
|
+
/** Saved form → ferrofluid surface and settings; throws a sentence when it cannot be read. */
|
|
129
|
+
export declare function ferroFromJSON(raw: unknown): {
|
|
130
|
+
surface: TiledSurface;
|
|
131
|
+
settings: FerroSettings;
|
|
132
|
+
};
|
|
133
|
+
/** Saved magnets array → validated `PaintMagnet[]`. */
|
|
134
|
+
export declare function magnetsFromJSON(raw: unknown): PaintMagnet[];
|