@pibbl/core 0.0.2 → 0.0.3-alpha.85.1
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/THIRD_PARTY_NOTICES.txt +81 -0
- package/dist/features/textures/index.d.ts +12 -2
- package/dist/features/textures/index.js +33 -1
- package/dist/features/textures/index.js.map +2 -2
- package/dist/features/textures/lib/chemical-field.d.ts +56 -0
- package/dist/features/textures/lib/chemical-field.js +154 -0
- package/dist/features/textures/lib/chemical-field.js.map +7 -0
- package/dist/features/textures/lib/chemical-recipes.d.ts +119 -0
- package/dist/features/textures/lib/chemical-recipes.js +275 -0
- package/dist/features/textures/lib/chemical-recipes.js.map +7 -0
- package/dist/features/textures/lib/definitions.d.ts +3 -18
- package/dist/features/textures/lib/definitions.js +15 -20
- package/dist/features/textures/lib/definitions.js.map +2 -2
- package/dist/features/textures/lib/geometric-patterns.d.ts +149 -0
- package/dist/features/textures/lib/geometric-patterns.js +305 -0
- package/dist/features/textures/lib/geometric-patterns.js.map +7 -0
- package/dist/features/textures/lib/gray-scott.d.ts +55 -0
- package/dist/features/textures/lib/gray-scott.js +39 -0
- package/dist/features/textures/lib/gray-scott.js.map +7 -0
- package/dist/features/textures/lib/patterns.d.ts +123 -0
- package/dist/features/textures/lib/patterns.js +344 -0
- package/dist/features/textures/lib/patterns.js.map +7 -0
- package/dist/features/textures/lib/reaction-diffusion.d.ts +3 -2
- package/dist/features/textures/lib/reaction-diffusion.js +9 -20
- package/dist/features/textures/lib/reaction-diffusion.js.map +2 -2
- package/dist/features/textures/lib/simulation.d.ts +10 -4
- package/dist/features/textures/lib/simulation.js +23 -13
- package/dist/features/textures/lib/simulation.js.map +2 -2
- package/dist/features/viz/index.d.ts +55 -6
- package/dist/features/viz/index.js +107 -2
- package/dist/features/viz/index.js.map +2 -2
- package/dist/features/viz/inspection/bar.d.ts +79 -0
- package/dist/features/viz/inspection/bar.js +49 -0
- package/dist/features/viz/inspection/bar.js.map +7 -0
- package/dist/features/viz/inspection/candle.d.ts +156 -0
- package/dist/features/viz/inspection/candle.js +120 -0
- package/dist/features/viz/inspection/candle.js.map +7 -0
- package/dist/features/viz/inspection/capture-types.d.ts +65 -0
- package/dist/features/viz/inspection/capture.d.ts +16 -0
- package/dist/features/viz/inspection/capture.js +49 -0
- package/dist/features/viz/inspection/capture.js.map +7 -0
- package/dist/features/viz/inspection/handler.d.ts +36 -0
- package/dist/features/viz/inspection/handler.js +91 -0
- package/dist/features/viz/inspection/handler.js.map +7 -0
- package/dist/features/viz/inspection/ohlc-bar.d.ts +132 -0
- package/dist/features/viz/inspection/ohlc-bar.js +118 -0
- package/dist/features/viz/inspection/ohlc-bar.js.map +7 -0
- package/dist/features/viz/inspection/owned-ref.d.ts +16 -0
- package/dist/features/viz/inspection/owned-ref.js +99 -0
- package/dist/features/viz/inspection/owned-ref.js.map +7 -0
- package/dist/features/viz/inspection/painted-point.d.ts +22 -0
- package/dist/features/viz/inspection/painted-point.js +29 -0
- package/dist/features/viz/inspection/painted-point.js.map +7 -0
- package/dist/features/viz/inspection/queries.d.ts +100 -0
- package/dist/features/viz/inspection/queries.js +192 -0
- package/dist/features/viz/inspection/queries.js.map +7 -0
- package/dist/features/viz/inspection/query-types.d.ts +177 -0
- package/dist/features/viz/inspection/volume.d.ts +104 -0
- package/dist/features/viz/inspection/volume.js +95 -0
- package/dist/features/viz/inspection/volume.js.map +7 -0
- package/dist/features/viz/inspection.d.ts +19 -0
- package/dist/features/viz/inspection.js +54 -0
- package/dist/features/viz/inspection.js.map +7 -0
- package/dist/features/viz/inspection.mjs +1 -0
- package/dist/features/viz/lib/area-series.d.ts +62 -0
- package/dist/features/viz/lib/area-series.js +120 -0
- package/dist/features/viz/lib/area-series.js.map +7 -0
- package/dist/features/viz/lib/axis.js +1 -1
- package/dist/features/viz/lib/axis.js.map +2 -2
- package/dist/features/viz/lib/bar-series.d.ts +3 -1
- package/dist/features/viz/lib/bar-series.js +16 -1
- package/dist/features/viz/lib/bar-series.js.map +2 -2
- package/dist/features/viz/lib/baseline-series.d.ts +55 -0
- package/dist/features/viz/lib/baseline-series.js +136 -0
- package/dist/features/viz/lib/baseline-series.js.map +7 -0
- package/dist/features/viz/lib/candlestick-series.d.ts +62 -0
- package/dist/features/viz/lib/candlestick-series.js +150 -0
- package/dist/features/viz/lib/candlestick-series.js.map +7 -0
- package/dist/features/viz/lib/censored-point-series.d.ts +71 -0
- package/dist/features/viz/lib/censored-point-series.js +129 -0
- package/dist/features/viz/lib/censored-point-series.js.map +7 -0
- package/dist/features/viz/lib/color-context.d.ts +4 -0
- package/dist/features/viz/lib/color-context.js +41 -0
- package/dist/features/viz/lib/color-context.js.map +7 -0
- package/dist/features/viz/lib/color-math.d.ts +4 -0
- package/dist/features/viz/lib/color-math.js +31 -0
- package/dist/features/viz/lib/color-math.js.map +7 -0
- package/dist/features/viz/lib/color-types.d.ts +53 -0
- package/dist/features/viz/lib/curve-definition.d.ts +49 -0
- package/dist/features/viz/lib/curve-definition.js +148 -0
- package/dist/features/viz/lib/curve-definition.js.map +7 -0
- package/dist/features/viz/lib/diverging-color-scale.d.ts +11 -0
- package/dist/features/viz/lib/diverging-color-scale.js +51 -0
- package/dist/features/viz/lib/diverging-color-scale.js.map +7 -0
- package/dist/features/viz/lib/domain-navigation.d.ts +40 -0
- package/dist/features/viz/lib/domain-navigation.js +77 -0
- package/dist/features/viz/lib/domain-navigation.js.map +7 -0
- package/dist/features/viz/lib/editable-chart-elements.d.ts +178 -0
- package/dist/features/viz/lib/editable-chart-elements.js +733 -0
- package/dist/features/viz/lib/editable-chart-elements.js.map +7 -0
- package/dist/features/viz/lib/financial-aggregation.d.ts +127 -0
- package/dist/features/viz/lib/financial-aggregation.js +116 -0
- package/dist/features/viz/lib/financial-aggregation.js.map +7 -0
- package/dist/features/viz/lib/financial-autoscale.d.ts +64 -0
- package/dist/features/viz/lib/financial-autoscale.js +61 -0
- package/dist/features/viz/lib/financial-autoscale.js.map +7 -0
- package/dist/features/viz/lib/financial-comparison.d.ts +62 -0
- package/dist/features/viz/lib/financial-comparison.js +98 -0
- package/dist/features/viz/lib/financial-comparison.js.map +7 -0
- package/dist/features/viz/lib/financial-geometry.d.ts +77 -0
- package/dist/features/viz/lib/financial-geometry.js +67 -0
- package/dist/features/viz/lib/financial-geometry.js.map +7 -0
- package/dist/features/viz/lib/financial-indicators.d.ts +114 -0
- package/dist/features/viz/lib/financial-indicators.js +135 -0
- package/dist/features/viz/lib/financial-indicators.js.map +7 -0
- package/dist/features/viz/lib/financial-oscillators.d.ts +171 -0
- package/dist/features/viz/lib/financial-oscillators.js +265 -0
- package/dist/features/viz/lib/financial-oscillators.js.map +7 -0
- package/dist/features/viz/lib/hover-data.d.ts +1 -1
- package/dist/features/viz/lib/hover-data.js +5 -2
- package/dist/features/viz/lib/hover-data.js.map +2 -2
- package/dist/features/viz/lib/interval-band-series.d.ts +49 -0
- package/dist/features/viz/lib/interval-band-series.js +136 -0
- package/dist/features/viz/lib/interval-band-series.js.map +7 -0
- package/dist/features/viz/lib/interval-diamond-series.d.ts +43 -0
- package/dist/features/viz/lib/interval-diamond-series.js +124 -0
- package/dist/features/viz/lib/interval-diamond-series.js.map +7 -0
- package/dist/features/viz/lib/interval-series.d.ts +79 -0
- package/dist/features/viz/lib/interval-series.js +158 -0
- package/dist/features/viz/lib/interval-series.js.map +7 -0
- package/dist/features/viz/lib/line-series.d.ts +1 -1
- package/dist/features/viz/lib/line-series.js +47 -28
- package/dist/features/viz/lib/line-series.js.map +2 -2
- package/dist/features/viz/lib/linear-scale.js +1 -1
- package/dist/features/viz/lib/linear-scale.js.map +2 -2
- package/dist/features/viz/lib/monotone-curve.d.ts +11 -0
- package/dist/features/viz/lib/monotone-curve.js +63 -0
- package/dist/features/viz/lib/monotone-curve.js.map +7 -0
- package/dist/features/viz/lib/numeric-range-interpolation.d.ts +15 -0
- package/dist/features/viz/lib/numeric-range-interpolation.js +36 -0
- package/dist/features/viz/lib/numeric-range-interpolation.js.map +7 -0
- package/dist/features/viz/lib/ohlc-bar-series.d.ts +55 -0
- package/dist/features/viz/lib/ohlc-bar-series.js +168 -0
- package/dist/features/viz/lib/ohlc-bar-series.js.map +7 -0
- package/dist/features/viz/lib/paired-series.d.ts +44 -0
- package/dist/features/viz/lib/paired-series.js +93 -0
- package/dist/features/viz/lib/paired-series.js.map +7 -0
- package/dist/features/viz/lib/plot-series.js +11 -6
- package/dist/features/viz/lib/plot-series.js.map +2 -2
- package/dist/features/viz/lib/plot-types.d.ts +4 -3
- package/dist/features/viz/lib/point-series.d.ts +1 -1
- package/dist/features/viz/lib/point-series.js +36 -9
- package/dist/features/viz/lib/point-series.js.map +2 -2
- package/dist/features/viz/lib/point-types.d.ts +5 -2
- package/dist/features/viz/lib/scale-context.d.ts +1 -1
- package/dist/features/viz/lib/scale-ticks.js +21 -6
- package/dist/features/viz/lib/scale-ticks.js.map +2 -2
- package/dist/features/viz/lib/sequential-color-scale.d.ts +11 -0
- package/dist/features/viz/lib/sequential-color-scale.js +49 -0
- package/dist/features/viz/lib/sequential-color-scale.js.map +7 -0
- package/dist/features/viz/lib/series-position.d.ts +12 -0
- package/dist/features/viz/lib/series-position.js +44 -0
- package/dist/features/viz/lib/series-position.js.map +7 -0
- package/dist/features/viz/lib/step-curves.d.ts +19 -0
- package/dist/features/viz/lib/step-curves.js +53 -0
- package/dist/features/viz/lib/step-curves.js.map +7 -0
- package/dist/features/viz/lib/threshold-color-scale.d.ts +43 -0
- package/dist/features/viz/lib/threshold-color-scale.js +59 -0
- package/dist/features/viz/lib/threshold-color-scale.js.map +7 -0
- package/dist/features/viz/lib/trading-time-mapping.d.ts +32 -0
- package/dist/features/viz/lib/trading-time-mapping.js +89 -0
- package/dist/features/viz/lib/trading-time-mapping.js.map +7 -0
- package/dist/features/viz/lib/trading-time-scale.d.ts +83 -0
- package/dist/features/viz/lib/trading-time-scale.js +96 -0
- package/dist/features/viz/lib/trading-time-scale.js.map +7 -0
- package/dist/features/viz/lib/trajectory-series.d.ts +71 -0
- package/dist/features/viz/lib/trajectory-series.js +111 -0
- package/dist/features/viz/lib/trajectory-series.js.map +7 -0
- package/dist/features/viz/lib/types.d.ts +20 -4
- package/dist/features/viz/lib/use-continuous-scale.js +6 -1
- package/dist/features/viz/lib/use-continuous-scale.js.map +2 -2
- package/dist/features/viz/lib/volume-series.d.ts +59 -0
- package/dist/features/viz/lib/volume-series.js +117 -0
- package/dist/features/viz/lib/volume-series.js.map +7 -0
- package/dist/features/viz/lib/weighted-point-series.d.ts +71 -0
- package/dist/features/viz/lib/weighted-point-series.js +169 -0
- package/dist/features/viz/lib/weighted-point-series.js.map +7 -0
- package/dist/filters/emboss-gpu.d.ts +4 -0
- package/dist/filters/emboss-gpu.js +39 -0
- package/dist/filters/emboss-gpu.js.map +7 -0
- package/dist/filters/emboss.d.ts +32 -0
- package/dist/filters/emboss.js +106 -0
- package/dist/filters/emboss.js.map +7 -0
- package/dist/filters/reflection-gpu.d.ts +3 -1
- package/dist/filters/reflection-gpu.js +5 -2
- package/dist/filters/reflection-gpu.js.map +2 -2
- package/dist/filters/render-emboss-boundary.d.ts +13 -0
- package/dist/filters/render-emboss-boundary.js +131 -0
- package/dist/filters/render-emboss-boundary.js.map +7 -0
- package/dist/filters.d.ts +2 -0
- package/dist/filters.js +2 -0
- package/dist/filters.js.map +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +2 -2
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +2 -2
- package/dist/lib/events/event-facade.d.ts +13 -0
- package/dist/lib/events/event-facade.js +23 -0
- package/dist/lib/events/event-facade.js.map +2 -2
- package/dist/lib/events/propagation.js +19 -14
- package/dist/lib/events/propagation.js.map +2 -2
- package/dist/lib/execute-render.js +13 -0
- package/dist/lib/execute-render.js.map +2 -2
- package/dist/lib/recording/recording.d.ts +43 -0
- package/dist/lib/recording/recording.js +555 -0
- package/dist/lib/recording/recording.js.map +7 -0
- package/dist/lib/recording/types.d.ts +118 -0
- package/dist/lib/recording/wasm-bytes.d.ts +1 -0
- package/dist/lib/recording/wasm-bytes.js +5 -0
- package/dist/lib/recording/wasm-bytes.js.map +7 -0
- package/dist/lib/recording/worker-source.d.ts +1 -0
- package/dist/lib/recording/worker-source.js +5 -0
- package/dist/lib/recording/worker-source.js.map +7 -0
- package/dist/lib/render-transaction.d.ts +5 -2
- package/dist/lib/render-transaction.js +23 -1
- package/dist/lib/render-transaction.js.map +2 -2
- package/dist/lib/series-inspection-frame.d.ts +18 -0
- package/dist/lib/series-inspection-frame.js +70 -0
- package/dist/lib/series-inspection-frame.js.map +7 -0
- package/dist/lib/types.d.ts +6 -0
- package/package.json +10 -2
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineTexture
|
|
3
|
+
} from "@pibbl/core";
|
|
4
|
+
import { paletteLut, texturePalettes, validateSeed } from "./palette.js";
|
|
5
|
+
const tau = Math.PI * 2;
|
|
6
|
+
function wrap(value) {
|
|
7
|
+
return (value % 1 + 1) % 1;
|
|
8
|
+
}
|
|
9
|
+
function bounded(name, value, min, max, integer = false) {
|
|
10
|
+
if (!Number.isFinite(value) || value < min || value > max || integer && !Number.isInteger(value))
|
|
11
|
+
throw new RangeError(
|
|
12
|
+
`${name} must be ${integer ? "an integer " : ""}in [${min}, ${max}].`
|
|
13
|
+
);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function motion(options) {
|
|
17
|
+
const phase = options.phase ?? 0;
|
|
18
|
+
if (!Number.isFinite(phase))
|
|
19
|
+
throw new RangeError("Texture phase must be finite.");
|
|
20
|
+
return {
|
|
21
|
+
phase: wrap(phase),
|
|
22
|
+
speed: bounded("Texture speed", options.speed ?? 0, 0, 20)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function pattern(validate, sample) {
|
|
26
|
+
const definition = defineTexture({
|
|
27
|
+
create(context, config) {
|
|
28
|
+
const { width, height } = context.canvas;
|
|
29
|
+
if (config.speed > 0) context.requestFrame();
|
|
30
|
+
return {
|
|
31
|
+
config,
|
|
32
|
+
phase: config.phase,
|
|
33
|
+
fresh: true,
|
|
34
|
+
fieldDirty: true,
|
|
35
|
+
field: new Float32Array(width * height),
|
|
36
|
+
image: new ImageData(width, height)
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
update(state, config, context) {
|
|
40
|
+
if (JSON.stringify(config) === JSON.stringify(state.config)) return;
|
|
41
|
+
if (config.phase !== state.config.phase) state.phase = config.phase;
|
|
42
|
+
if (config.speed !== state.config.speed) state.fresh = true;
|
|
43
|
+
state.config = config;
|
|
44
|
+
state.fieldDirty = true;
|
|
45
|
+
context.invalidate();
|
|
46
|
+
if (config.speed > 0) context.requestFrame();
|
|
47
|
+
},
|
|
48
|
+
advance(state, frame, context) {
|
|
49
|
+
if (state.config.speed === 0) return;
|
|
50
|
+
if (!state.fresh && frame.delta > 0) {
|
|
51
|
+
state.phase = wrap(
|
|
52
|
+
state.phase + frame.delta * state.config.speed / 1e3
|
|
53
|
+
);
|
|
54
|
+
state.fieldDirty = true;
|
|
55
|
+
context.invalidate();
|
|
56
|
+
}
|
|
57
|
+
state.fresh = false;
|
|
58
|
+
context.requestFrame();
|
|
59
|
+
},
|
|
60
|
+
rasterize(state, context) {
|
|
61
|
+
const { width, height } = context.canvas;
|
|
62
|
+
if (state.fieldDirty) {
|
|
63
|
+
for (let y = 0; y < height; y++)
|
|
64
|
+
for (let x = 0; x < width; x++)
|
|
65
|
+
state.field[y * width + x] = sample(
|
|
66
|
+
state.config,
|
|
67
|
+
x === width - 1 ? 0 : x / (width - 1),
|
|
68
|
+
y === height - 1 ? 0 : y / (height - 1),
|
|
69
|
+
state.phase
|
|
70
|
+
);
|
|
71
|
+
state.fieldDirty = false;
|
|
72
|
+
}
|
|
73
|
+
const palette = context.palette ?? texturePalettes.porcelain;
|
|
74
|
+
const key = palette.join();
|
|
75
|
+
if (key !== state.paletteKey) {
|
|
76
|
+
state.lut = paletteLut(palette);
|
|
77
|
+
state.paletteKey = key;
|
|
78
|
+
}
|
|
79
|
+
const image = state.image;
|
|
80
|
+
const data = image.data, lut = state.lut;
|
|
81
|
+
for (let i = 0; i < state.field.length; i++) {
|
|
82
|
+
const index = Math.round(Math.max(0, Math.min(1, state.field[i])) * 255) * 4;
|
|
83
|
+
data[i * 4] = lut[index];
|
|
84
|
+
data[i * 4 + 1] = lut[index + 1];
|
|
85
|
+
data[i * 4 + 2] = lut[index + 2];
|
|
86
|
+
data[i * 4 + 3] = 255;
|
|
87
|
+
}
|
|
88
|
+
context.canvas.getContext("2d").putImageData(image, 0, 0);
|
|
89
|
+
},
|
|
90
|
+
dispose(state) {
|
|
91
|
+
state.field = new Float32Array(0);
|
|
92
|
+
state.image = void 0;
|
|
93
|
+
state.lut = void 0;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return (options) => definition(validate(options));
|
|
97
|
+
}
|
|
98
|
+
const stripeRecipe = /* @__PURE__ */ pattern(
|
|
99
|
+
(options) => ({
|
|
100
|
+
...motion(options),
|
|
101
|
+
bands: bounded("Stripe bands", options.bands ?? 8, 1, 64, true),
|
|
102
|
+
dutyCycle: bounded("Stripe dutyCycle", options.dutyCycle ?? 0.5, 0, 1),
|
|
103
|
+
wave: bounded("Stripe wave", options.wave ?? 0, 0, 1),
|
|
104
|
+
waveCycles: bounded(
|
|
105
|
+
"Stripe waveCycles",
|
|
106
|
+
options.waveCycles ?? 1,
|
|
107
|
+
1,
|
|
108
|
+
16,
|
|
109
|
+
true
|
|
110
|
+
)
|
|
111
|
+
}),
|
|
112
|
+
(c, u, v, phase) => wrap(
|
|
113
|
+
(u + c.wave * Math.sin(tau * (v * c.waveCycles + phase))) * c.bands + phase
|
|
114
|
+
) < c.dutyCycle ? 1 : 0
|
|
115
|
+
);
|
|
116
|
+
function stripes(options = {}) {
|
|
117
|
+
return stripeRecipe(options);
|
|
118
|
+
}
|
|
119
|
+
const checkerRecipe = /* @__PURE__ */ pattern(
|
|
120
|
+
(options) => {
|
|
121
|
+
const columns = bounded(
|
|
122
|
+
"Checker columns",
|
|
123
|
+
options.columns ?? 8,
|
|
124
|
+
2,
|
|
125
|
+
64,
|
|
126
|
+
true
|
|
127
|
+
);
|
|
128
|
+
const rows = bounded("Checker rows", options.rows ?? 8, 2, 64, true);
|
|
129
|
+
if (columns % 2 || rows % 2)
|
|
130
|
+
throw new RangeError(
|
|
131
|
+
"Checker rows and columns must be even for seamless alternation."
|
|
132
|
+
);
|
|
133
|
+
return { ...motion(options), columns, rows };
|
|
134
|
+
},
|
|
135
|
+
(c, u, v, phase) => (Math.floor(wrap(u + phase) * c.columns) + Math.floor(v * c.rows)) % 2
|
|
136
|
+
);
|
|
137
|
+
function checkers(options = {}) {
|
|
138
|
+
return checkerRecipe(options);
|
|
139
|
+
}
|
|
140
|
+
function hash(index, seed) {
|
|
141
|
+
let value = Math.imul(index ^ seed, 73244475);
|
|
142
|
+
value = Math.imul(value ^ value >>> 16, 73244475);
|
|
143
|
+
return ((value ^ value >>> 16) >>> 0) / 4294967295;
|
|
144
|
+
}
|
|
145
|
+
function noise(u, v, columns, rows, seed) {
|
|
146
|
+
const x = wrap(u) * columns, y = wrap(v) * rows;
|
|
147
|
+
const ix = Math.floor(x), iy = Math.floor(y);
|
|
148
|
+
const fx = x - ix, fy = y - iy;
|
|
149
|
+
const sx = fx * fx * fx * (fx * (fx * 6 - 15) + 10);
|
|
150
|
+
const sy = fy * fy * fy * (fy * (fy * 6 - 15) + 10);
|
|
151
|
+
const a = hash(iy * columns + ix, seed);
|
|
152
|
+
const b = hash(iy * columns + (ix + 1) % columns, seed);
|
|
153
|
+
const c = hash((iy + 1) % rows * columns + ix, seed);
|
|
154
|
+
const d = hash((iy + 1) % rows * columns + (ix + 1) % columns, seed);
|
|
155
|
+
return ((a + (b - a) * sx) * (1 - sy) + (c + (d - c) * sx) * sy) * 2 - 1;
|
|
156
|
+
}
|
|
157
|
+
const woodRecipe = /* @__PURE__ */ pattern(
|
|
158
|
+
(options) => {
|
|
159
|
+
const seed = validateSeed(options.seed);
|
|
160
|
+
const knots = bounded("Wood knots", options.knots ?? 0, 0, 12, true);
|
|
161
|
+
return {
|
|
162
|
+
...motion(options),
|
|
163
|
+
seed,
|
|
164
|
+
grain: bounded("Wood grain", options.grain ?? 32, 4, 128, true),
|
|
165
|
+
distortion: bounded("Wood distortion", options.distortion ?? 0.5, 0, 1),
|
|
166
|
+
roughness: bounded("Wood roughness", options.roughness ?? 0.4, 0, 1),
|
|
167
|
+
knotSize: bounded("Wood knotSize", options.knotSize ?? 0.09, 0.02, 0.18),
|
|
168
|
+
knots: Array.from({ length: knots }, (_, i) => ({
|
|
169
|
+
u: hash(i * 7 + 11, seed),
|
|
170
|
+
v: hash(i * 7 + 21, seed),
|
|
171
|
+
aspect: 1 + 0.9 * hash(i * 7 + 31, seed)
|
|
172
|
+
}))
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
(c, u, v, phase) => {
|
|
176
|
+
v = wrap(v + phase);
|
|
177
|
+
const broad = noise(u, v, 4, 4, c.seed);
|
|
178
|
+
const medium = noise(u, v, 8, 8, c.seed ^ 1367130551);
|
|
179
|
+
const fine = noise(u, v, 16, 16, c.seed ^ 1757159915);
|
|
180
|
+
let coordinate = u + c.distortion * (0.12 * broad + 0.045 * medium + 0.015 * fine);
|
|
181
|
+
for (const knot of c.knots) {
|
|
182
|
+
const dx = wrap(u - knot.u + 0.5) - 0.5;
|
|
183
|
+
const dy = wrap(v - knot.v + 0.5) - 0.5;
|
|
184
|
+
const radius = Math.hypot(dx, dy / knot.aspect) / (c.knotSize * 0.7);
|
|
185
|
+
if (radius >= 2) continue;
|
|
186
|
+
const outside = Math.max(1, radius);
|
|
187
|
+
const influence = Math.pow((4 - outside * outside) / 3, 2);
|
|
188
|
+
coordinate -= dx / (outside * outside) * influence;
|
|
189
|
+
}
|
|
190
|
+
const growth = 0.5 + 0.5 * Math.cos(tau * (coordinate * c.grain + c.roughness * fine * 0.2));
|
|
191
|
+
const fiber = Math.pow(
|
|
192
|
+
0.5 + 0.5 * Math.cos(tau * coordinate * c.grain * 3),
|
|
193
|
+
12
|
|
194
|
+
);
|
|
195
|
+
let value = 0.72 - 0.32 * Math.pow(growth, 7) + 0.055 * broad - c.roughness * (0.09 * fiber + 0.035 * fine);
|
|
196
|
+
for (const knot of c.knots) {
|
|
197
|
+
const dx = wrap(u - knot.u + 0.5) - 0.5;
|
|
198
|
+
const dy = wrap(v - knot.v + 0.5) - 0.5;
|
|
199
|
+
const radius = Math.hypot(dx, dy / knot.aspect) / (c.knotSize * 0.7);
|
|
200
|
+
if (radius >= 1) continue;
|
|
201
|
+
const edge = Math.min(1, Math.max(0, (radius - 0.85) / 0.15));
|
|
202
|
+
const blend = 1 - edge * edge * (3 - 2 * edge);
|
|
203
|
+
const ring = Math.pow(0.5 + 0.5 * Math.cos(tau * radius * 3), 5);
|
|
204
|
+
const knotValue = 0.55 - 0.23 * ring - 0.28 * Math.pow(Math.max(0, 1 - radius * 3), 2);
|
|
205
|
+
value += (knotValue - value) * blend;
|
|
206
|
+
}
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
function woodGrain(options = {}) {
|
|
211
|
+
return woodRecipe(options);
|
|
212
|
+
}
|
|
213
|
+
function fabricConfig(options) {
|
|
214
|
+
const threads = bounded(
|
|
215
|
+
"Fabric threads",
|
|
216
|
+
options.threads ?? 16,
|
|
217
|
+
8,
|
|
218
|
+
128,
|
|
219
|
+
true
|
|
220
|
+
);
|
|
221
|
+
if (threads % 8 !== 0)
|
|
222
|
+
throw new RangeError("Fabric threads must be a multiple of 8.");
|
|
223
|
+
return {
|
|
224
|
+
...motion(options),
|
|
225
|
+
seed: validateSeed(options.seed),
|
|
226
|
+
threads,
|
|
227
|
+
threadWidth: bounded(
|
|
228
|
+
"Fabric threadWidth",
|
|
229
|
+
options.threadWidth ?? 0.8,
|
|
230
|
+
0.2,
|
|
231
|
+
1
|
|
232
|
+
),
|
|
233
|
+
relief: bounded("Fabric relief", options.relief ?? 0.65, 0, 1),
|
|
234
|
+
fuzz: bounded("Fabric fuzz", options.fuzz ?? 0.2, 0, 1)
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function fabricSample(c, u, v, warpOnTop) {
|
|
238
|
+
const x = u * c.threads, y = v * c.threads;
|
|
239
|
+
const warpFiber = noise(u, v, c.threads * 8, 16, c.seed);
|
|
240
|
+
const weftFiber = noise(u, v, 16, c.threads * 8, c.seed ^ 1757159915);
|
|
241
|
+
const warpRadius = c.threadWidth * (1 + c.fuzz * 0.14 * warpFiber);
|
|
242
|
+
const weftRadius = c.threadWidth * (1 + c.fuzz * 0.14 * weftFiber);
|
|
243
|
+
const acrossWarp = Math.abs(x % 1 - 0.5) * 2 / warpRadius;
|
|
244
|
+
const acrossWeft = Math.abs(y % 1 - 0.5) * 2 / weftRadius;
|
|
245
|
+
const softness = 0.1 + c.fuzz * 0.45;
|
|
246
|
+
const coverage = (across) => {
|
|
247
|
+
const t = Math.max(
|
|
248
|
+
0,
|
|
249
|
+
Math.min(1, (across - (1 - softness)) / (2 * softness))
|
|
250
|
+
);
|
|
251
|
+
return 1 - t * t * (3 - 2 * t);
|
|
252
|
+
};
|
|
253
|
+
const warp = coverage(acrossWarp), weft = coverage(acrossWeft);
|
|
254
|
+
const warpCrown = Math.sqrt(Math.max(0, 1 - acrossWarp * acrossWarp));
|
|
255
|
+
const weftCrown = Math.sqrt(Math.max(0, 1 - acrossWeft * acrossWeft));
|
|
256
|
+
const warpValue = 0.62 + c.relief * (0.14 * warpCrown - 0.07) + c.fuzz * 0.15 * warpFiber;
|
|
257
|
+
const weftValue = 0.43 + c.relief * (0.14 * weftCrown - 0.07) + c.fuzz * 0.15 * weftFiber;
|
|
258
|
+
const gap = 0.18;
|
|
259
|
+
return warpOnTop ? warpValue * warp + (weftValue * weft + gap * (1 - weft)) * (1 - warp) : weftValue * weft + (warpValue * warp + gap * (1 - warp)) * (1 - weft);
|
|
260
|
+
}
|
|
261
|
+
const plainWeaveRecipe = /* @__PURE__ */ pattern(
|
|
262
|
+
fabricConfig,
|
|
263
|
+
(c, u, v, phase) => {
|
|
264
|
+
v = wrap(v + phase);
|
|
265
|
+
const x = Math.floor(u * c.threads), y = Math.floor(v * c.threads);
|
|
266
|
+
return fabricSample(c, u, v, (x + y) % 2 === 0);
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
const twillRecipe = /* @__PURE__ */ pattern(fabricConfig, (c, u, v, phase) => {
|
|
270
|
+
v = wrap(v + phase);
|
|
271
|
+
const x = Math.floor(u * c.threads), y = Math.floor(v * c.threads);
|
|
272
|
+
return fabricSample(c, u, v, ((x - y) % 4 + 4) % 4 < 2);
|
|
273
|
+
});
|
|
274
|
+
const herringboneHarnesses = [0, 1, 2, 3, 1, 0, 3, 2];
|
|
275
|
+
const herringboneRecipe = /* @__PURE__ */ pattern(
|
|
276
|
+
fabricConfig,
|
|
277
|
+
(c, u, v, phase) => {
|
|
278
|
+
v = wrap(v + phase);
|
|
279
|
+
const x = Math.floor(u * c.threads) % 8, y = Math.floor(v * c.threads);
|
|
280
|
+
const harness = herringboneHarnesses[x];
|
|
281
|
+
return fabricSample(c, u, v, (harness + y) % 4 >= 2);
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
function plainWeave(options = {}) {
|
|
285
|
+
return plainWeaveRecipe(options);
|
|
286
|
+
}
|
|
287
|
+
function twill(options = {}) {
|
|
288
|
+
return twillRecipe(options);
|
|
289
|
+
}
|
|
290
|
+
function herringbone(options = {}) {
|
|
291
|
+
return herringboneRecipe(options);
|
|
292
|
+
}
|
|
293
|
+
function noiseConfig(options) {
|
|
294
|
+
return {
|
|
295
|
+
...motion(options),
|
|
296
|
+
seed: validateSeed(options.seed),
|
|
297
|
+
frequency: bounded("Noise frequency", options.frequency ?? 4, 1, 16, true),
|
|
298
|
+
octaves: bounded("Noise octaves", options.octaves ?? 4, 1, 6, true),
|
|
299
|
+
persistence: bounded("Noise persistence", options.persistence ?? 0.5, 0, 1),
|
|
300
|
+
contrast: bounded("Noise contrast", options.contrast ?? 1, 0, 4)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function layeredNoise(c, u, v, ridge) {
|
|
304
|
+
let sum = 0, weight = 0, amplitude = 1, frequency = c.frequency;
|
|
305
|
+
for (let i = 0; i < c.octaves; i++) {
|
|
306
|
+
const sample = noise(
|
|
307
|
+
u,
|
|
308
|
+
v,
|
|
309
|
+
frequency,
|
|
310
|
+
frequency,
|
|
311
|
+
c.seed ^ Math.imul(i, 1367130551)
|
|
312
|
+
);
|
|
313
|
+
sum += (ridge ? 1 - Math.abs(sample) : 0.5 + sample * 0.5) * amplitude;
|
|
314
|
+
weight += amplitude;
|
|
315
|
+
amplitude *= c.persistence;
|
|
316
|
+
frequency *= 2;
|
|
317
|
+
}
|
|
318
|
+
return 0.5 + (sum / weight - 0.5) * c.contrast;
|
|
319
|
+
}
|
|
320
|
+
const softNoiseRecipe = /* @__PURE__ */ pattern(
|
|
321
|
+
noiseConfig,
|
|
322
|
+
(c, u, v, phase) => layeredNoise(c, u, wrap(v + phase), false)
|
|
323
|
+
);
|
|
324
|
+
const ridgedNoiseRecipe = /* @__PURE__ */ pattern(
|
|
325
|
+
noiseConfig,
|
|
326
|
+
(c, u, v, phase) => layeredNoise(c, u, wrap(v + phase), true)
|
|
327
|
+
);
|
|
328
|
+
function softNoise(options = {}) {
|
|
329
|
+
return softNoiseRecipe(options);
|
|
330
|
+
}
|
|
331
|
+
function ridgedNoise(options = {}) {
|
|
332
|
+
return ridgedNoiseRecipe(options);
|
|
333
|
+
}
|
|
334
|
+
export {
|
|
335
|
+
checkers,
|
|
336
|
+
herringbone,
|
|
337
|
+
plainWeave,
|
|
338
|
+
ridgedNoise,
|
|
339
|
+
softNoise,
|
|
340
|
+
stripes,
|
|
341
|
+
twill,
|
|
342
|
+
woodGrain
|
|
343
|
+
};
|
|
344
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/features/textures/lib/patterns.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n defineTexture,\n type TextureContext,\n type TextureRecipe,\n} from \"@pibbl/core\";\nimport { paletteLut, texturePalettes, validateSeed } from \"./palette.js\";\n\n/** Shared periodic motion controls for geometric and fiber recipes. @see {@link stripes} */\nexport interface PatternMotionOptions {\n /** Initial phase in cycles; any finite value wraps. See {@link PatternMotionOptions}. */\n readonly phase?: number;\n /** Nonnegative cycles per second, up to 20; zero idles. See {@link PatternMotionOptions}. */\n readonly speed?: number;\n}\n/** Controls for periodic stripes; rotate the repeat lattice through paint placement. @see {@link stripes} */\nexport interface StripesOptions extends PatternMotionOptions {\n /** Integer stripe periods across the tile, 1\u201364; default 8. See {@link StripesOptions}. */\n readonly bands?: number;\n /** Foreground fraction of each period, 0\u20131; default 0.5. See {@link StripesOptions}. */\n readonly dutyCycle?: number;\n /** Horizontal wave amplitude in tile fractions, 0\u20131; default 0. See {@link StripesOptions}. */\n readonly wave?: number;\n /** Integer wave periods down the tile, 1\u201316; default 1. See {@link StripesOptions}. */\n readonly waveCycles?: number;\n}\n/** Controls for a two-color checkerboard. @see {@link checkers} */\nexport interface CheckersOptions extends PatternMotionOptions {\n /** Even integer columns, 2\u201364; default 8. See {@link CheckersOptions}. */\n readonly columns?: number;\n /** Even integer rows, 2\u201364; default 8. See {@link CheckersOptions}. */\n readonly rows?: number;\n}\n/** Controls for seeded seamless wood grain with optional knots. @see {@link woodGrain} */\nexport interface WoodGrainOptions extends PatternMotionOptions {\n /** Unsigned 32-bit variation seed; default 42. See {@link WoodGrainOptions}. */\n readonly seed?: number;\n /** Integer grain periods across the tile, 4\u2013128; default 32. See {@link WoodGrainOptions}. */\n readonly grain?: number;\n /** Irregular grain bending, 0\u20131; default 0.5. See {@link WoodGrainOptions}. */\n readonly distortion?: number;\n /** Fine fiber and pore variation, 0\u20131; default 0.4. See {@link WoodGrainOptions}. */\n readonly roughness?: number;\n /** Integer knot count, 0\u201312; default 0 for clear grain. See {@link WoodGrainOptions}. */\n readonly knots?: number;\n /** Knot radius in tile fractions, 0.02\u20130.18; default 0.09. See {@link WoodGrainOptions}. */\n readonly knotSize?: number;\n}\nconst tau = Math.PI * 2;\nfunction wrap(value: number) {\n return ((value % 1) + 1) % 1;\n}\nfunction bounded(\n name: string,\n value: number,\n min: number,\n max: number,\n integer = false,\n) {\n if (\n !Number.isFinite(value) ||\n value < min ||\n value > max ||\n (integer && !Number.isInteger(value))\n )\n throw new RangeError(\n `${name} must be ${integer ? \"an integer \" : \"\"}in [${min}, ${max}].`,\n );\n return value;\n}\nfunction motion(options: PatternMotionOptions) {\n const phase = options.phase ?? 0;\n if (!Number.isFinite(phase))\n throw new RangeError(\"Texture phase must be finite.\");\n return {\n phase: wrap(phase),\n speed: bounded(\"Texture speed\", options.speed ?? 0, 0, 20),\n };\n}\ntype Motion = ReturnType<typeof motion>;\ninterface PatternState<Config> {\n config: Config;\n phase: number;\n fresh: boolean;\n fieldDirty: boolean;\n field: Float32Array;\n image: ImageData | undefined;\n paletteKey?: string;\n lut?: Uint8ClampedArray;\n}\n// Shared lifecycle only. Each public factory has its own configuration and one\n// fixed return contract. Sampling and recoloring reuse bounded buffers.\nfunction pattern<Options, Config extends Motion>(\n validate: (options: Options) => Config,\n sample: (config: Config, u: number, v: number, phase: number) => number,\n) {\n const definition = defineTexture<Config, PatternState<Config>>({\n create(context, config) {\n const { width, height } = context.canvas;\n if (config.speed > 0) context.requestFrame();\n return {\n config,\n phase: config.phase,\n fresh: true,\n fieldDirty: true,\n field: new Float32Array(width * height),\n image: new ImageData(width, height),\n };\n },\n update(state, config, context) {\n if (JSON.stringify(config) === JSON.stringify(state.config)) return;\n if (config.phase !== state.config.phase) state.phase = config.phase;\n if (config.speed !== state.config.speed) state.fresh = true;\n state.config = config;\n state.fieldDirty = true;\n context.invalidate();\n if (config.speed > 0) context.requestFrame();\n },\n advance(state, frame, context) {\n if (state.config.speed === 0) return;\n if (!state.fresh && frame.delta > 0) {\n state.phase = wrap(\n state.phase + (frame.delta * state.config.speed) / 1000,\n );\n state.fieldDirty = true;\n context.invalidate();\n }\n state.fresh = false;\n context.requestFrame();\n },\n rasterize(state, context: TextureContext) {\n const { width, height } = context.canvas;\n if (state.fieldDirty) {\n for (let y = 0; y < height; y++)\n for (let x = 0; x < width; x++)\n state.field[y * width + x] = sample(\n state.config,\n x === width - 1 ? 0 : x / (width - 1),\n y === height - 1 ? 0 : y / (height - 1),\n state.phase,\n );\n state.fieldDirty = false;\n }\n const palette = context.palette ?? texturePalettes.porcelain;\n const key = palette.join();\n if (key !== state.paletteKey) {\n state.lut = paletteLut(palette);\n state.paletteKey = key;\n }\n const image = state.image!;\n const data = image.data,\n lut = state.lut!;\n for (let i = 0; i < state.field.length; i++) {\n const index =\n Math.round(Math.max(0, Math.min(1, state.field[i])) * 255) * 4;\n data[i * 4] = lut[index];\n data[i * 4 + 1] = lut[index + 1];\n data[i * 4 + 2] = lut[index + 2];\n data[i * 4 + 3] = 255;\n }\n context.canvas.getContext(\"2d\")!.putImageData(image, 0, 0);\n },\n dispose(state) {\n state.field = new Float32Array(0);\n state.image = undefined;\n state.lut = undefined;\n },\n });\n return (options: Options): TextureRecipe => definition(validate(options));\n}\nconst stripeRecipe = /* @__PURE__ */ pattern(\n (options: StripesOptions) => ({\n ...motion(options),\n bands: bounded(\"Stripe bands\", options.bands ?? 8, 1, 64, true),\n dutyCycle: bounded(\"Stripe dutyCycle\", options.dutyCycle ?? 0.5, 0, 1),\n wave: bounded(\"Stripe wave\", options.wave ?? 0, 0, 1),\n waveCycles: bounded(\n \"Stripe waveCycles\",\n options.waveCycles ?? 1,\n 1,\n 16,\n true,\n ),\n }),\n (c, u, v, phase) =>\n wrap(\n (u + c.wave * Math.sin(tau * (v * c.waveCycles + phase))) * c.bands +\n phase,\n ) < c.dutyCycle\n ? 1\n : 0,\n);\n\n/**\n * Creates seamless straight or wavy stripes using the palette endpoints.\n * @param options - Stripe geometry and periodic motion. See {@link StripesOptions}.\n * @returns A mountable texture recipe with no message commands. See {@link TextureRecipe}.\n * @see {@link StripesOptions}\n */\nexport function stripes(options: StripesOptions = {}): TextureRecipe {\n return stripeRecipe(options);\n}\nconst checkerRecipe = /* @__PURE__ */ pattern(\n (options: CheckersOptions) => {\n const columns = bounded(\n \"Checker columns\",\n options.columns ?? 8,\n 2,\n 64,\n true,\n );\n const rows = bounded(\"Checker rows\", options.rows ?? 8, 2, 64, true);\n if (columns % 2 || rows % 2)\n throw new RangeError(\n \"Checker rows and columns must be even for seamless alternation.\",\n );\n return { ...motion(options), columns, rows };\n },\n (c, u, v, phase) =>\n (Math.floor(wrap(u + phase) * c.columns) + Math.floor(v * c.rows)) % 2,\n);\n/**\n * Creates a seamless checkerboard using the palette endpoints.\n * @param options - Even row/column counts and periodic motion. See {@link CheckersOptions}.\n * @returns A mountable texture recipe with no message commands. See {@link TextureRecipe}.\n * @see {@link CheckersOptions}\n */\nexport function checkers(options: CheckersOptions = {}): TextureRecipe {\n return checkerRecipe(options);\n}\nfunction hash(index: number, seed: number) {\n let value = Math.imul(index ^ seed, 0x45d9f3b);\n value = Math.imul(value ^ (value >>> 16), 0x45d9f3b);\n return ((value ^ (value >>> 16)) >>> 0) / 0xffffffff;\n}\n// Seeded lattice noise wraps both axes. Several scales bend the growth lines\n// independently, avoiding the uniform sine-wave paths of the original study.\nfunction noise(\n u: number,\n v: number,\n columns: number,\n rows: number,\n seed: number,\n) {\n const x = wrap(u) * columns,\n y = wrap(v) * rows;\n const ix = Math.floor(x),\n iy = Math.floor(y);\n const fx = x - ix,\n fy = y - iy;\n const sx = fx * fx * fx * (fx * (fx * 6 - 15) + 10);\n const sy = fy * fy * fy * (fy * (fy * 6 - 15) + 10);\n const a = hash(iy * columns + ix, seed);\n const b = hash(iy * columns + ((ix + 1) % columns), seed);\n const c = hash(((iy + 1) % rows) * columns + ix, seed);\n const d = hash(((iy + 1) % rows) * columns + ((ix + 1) % columns), seed);\n return ((a + (b - a) * sx) * (1 - sy) + (c + (d - c) * sx) * sy) * 2 - 1;\n}\nconst woodRecipe = /* @__PURE__ */ pattern(\n (options: WoodGrainOptions) => {\n const seed = validateSeed(options.seed);\n const knots = bounded(\"Wood knots\", options.knots ?? 0, 0, 12, true);\n return {\n ...motion(options),\n seed,\n grain: bounded(\"Wood grain\", options.grain ?? 32, 4, 128, true),\n distortion: bounded(\"Wood distortion\", options.distortion ?? 0.5, 0, 1),\n roughness: bounded(\"Wood roughness\", options.roughness ?? 0.4, 0, 1),\n knotSize: bounded(\"Wood knotSize\", options.knotSize ?? 0.09, 0.02, 0.18),\n knots: Array.from({ length: knots }, (_, i) => ({\n u: hash(i * 7 + 11, seed),\n v: hash(i * 7 + 21, seed),\n aspect: 1 + 0.9 * hash(i * 7 + 31, seed),\n })),\n };\n },\n (c, u, v, phase) => {\n v = wrap(v + phase);\n const broad = noise(u, v, 4, 4, c.seed);\n const medium = noise(u, v, 8, 8, c.seed ^ 0x517cc1b7);\n const fine = noise(u, v, 16, 16, c.seed ^ 0x68bc21eb);\n let coordinate =\n u + c.distortion * (0.12 * broad + 0.045 * medium + 0.015 * fine);\n // Shortest periodic offsets let a knot crossing a tile boundary continue\n // in its neighboring copy. Compact support vanishes before antipodal points.\n for (const knot of c.knots) {\n const dx = wrap(u - knot.u + 0.5) - 0.5;\n const dy = wrap(v - knot.v + 0.5) - 0.5;\n const radius = Math.hypot(dx, dy / knot.aspect) / (c.knotSize * 0.7);\n if (radius >= 2) continue;\n // Inverse stream coordinate around an elliptical obstacle: subtracting\n // this displacement makes equal-growth contours bow away from the core.\n // At radius 1 the center streamline follows the knot boundary; the\n // compact taper restores undisturbed grain at radius 2.\n const outside = Math.max(1, radius);\n const influence = Math.pow((4 - outside * outside) / 3, 2);\n coordinate -= (dx / (outside * outside)) * influence;\n }\n const growth =\n 0.5 +\n 0.5 * Math.cos(tau * (coordinate * c.grain + c.roughness * fine * 0.2));\n const fiber = Math.pow(\n 0.5 + 0.5 * Math.cos(tau * coordinate * c.grain * 3),\n 12,\n );\n let value =\n 0.72 -\n 0.32 * Math.pow(growth, 7) +\n 0.055 * broad -\n c.roughness * (0.09 * fiber + 0.035 * fine);\n for (const knot of c.knots) {\n const dx = wrap(u - knot.u + 0.5) - 0.5;\n const dy = wrap(v - knot.v + 0.5) - 0.5;\n const radius = Math.hypot(dx, dy / knot.aspect) / (c.knotSize * 0.7);\n if (radius >= 1) continue;\n const edge = Math.min(1, Math.max(0, (radius - 0.85) / 0.15));\n const blend = 1 - edge * edge * (3 - 2 * edge);\n const ring = Math.pow(0.5 + 0.5 * Math.cos(tau * radius * 3), 5);\n const knotValue =\n 0.55 - 0.23 * ring - 0.28 * Math.pow(Math.max(0, 1 - radius * 3), 2);\n value += (knotValue - value) * blend;\n }\n return value;\n },\n);\n/**\n * Creates seamless seeded wood with irregular grain, fine fibers, and optional knots.\n * @param options - Grain frequency, distortion, roughness, knots, seed, and motion. See {@link WoodGrainOptions}.\n * @returns A mountable texture recipe with no message commands. See {@link TextureRecipe}.\n * @see {@link WoodGrainOptions}\n */\nexport function woodGrain(options: WoodGrainOptions = {}): TextureRecipe {\n return woodRecipe(options);\n}\n\n/** Shared thread geometry and shading for seamless woven fabrics. @see {@link plainWeave} */\nexport interface FabricOptions extends PatternMotionOptions {\n /** Unsigned 32-bit fiber variation seed; default 42. See {@link FabricOptions}. */\n readonly seed?: number;\n /** Thread count on each axis, a multiple of 8 in 8\u2013128; default 16. See {@link FabricOptions}. */\n readonly threads?: number;\n /** Thread width as a fraction of cell spacing, 0.2\u20131; default 0.8. See {@link FabricOptions}. */\n readonly threadWidth?: number;\n /** Rounded thread lighting strength, 0\u20131; default 0.65. See {@link FabricOptions}. */\n readonly relief?: number;\n /** Seeded directional fibers and soft, uneven yarn edges, 0\u20131; default 0.2. See {@link FabricOptions}. */\n readonly fuzz?: number;\n}\nfunction fabricConfig(options: FabricOptions) {\n const threads = bounded(\n \"Fabric threads\",\n options.threads ?? 16,\n 8,\n 128,\n true,\n );\n if (threads % 8 !== 0)\n throw new RangeError(\"Fabric threads must be a multiple of 8.\");\n return {\n ...motion(options),\n seed: validateSeed(options.seed),\n threads,\n threadWidth: bounded(\n \"Fabric threadWidth\",\n options.threadWidth ?? 0.8,\n 0.2,\n 1,\n ),\n relief: bounded(\"Fabric relief\", options.relief ?? 0.65, 0, 1),\n fuzz: bounded(\"Fabric fuzz\", options.fuzz ?? 0.2, 0, 1),\n };\n}\ntype FabricConfig = ReturnType<typeof fabricConfig>;\nfunction fabricSample(\n c: FabricConfig,\n u: number,\n v: number,\n warpOnTop: boolean,\n) {\n const x = u * c.threads,\n y = v * c.threads;\n const warpFiber = noise(u, v, c.threads * 8, 16, c.seed);\n const weftFiber = noise(u, v, 16, c.threads * 8, c.seed ^ 0x68bc21eb);\n // Spun yarn has uneven edges and a soft fiber envelope. Coverage, rather\n // than a binary inside/outside test, keeps enlarged yarn from looking cut out.\n const warpRadius = c.threadWidth * (1 + c.fuzz * 0.14 * warpFiber);\n const weftRadius = c.threadWidth * (1 + c.fuzz * 0.14 * weftFiber);\n const acrossWarp = (Math.abs((x % 1) - 0.5) * 2) / warpRadius;\n const acrossWeft = (Math.abs((y % 1) - 0.5) * 2) / weftRadius;\n const softness = 0.1 + c.fuzz * 0.45;\n const coverage = (across: number) => {\n const t = Math.max(\n 0,\n Math.min(1, (across - (1 - softness)) / (2 * softness)),\n );\n return 1 - t * t * (3 - 2 * t);\n };\n const warp = coverage(acrossWarp),\n weft = coverage(acrossWeft);\n const warpCrown = Math.sqrt(Math.max(0, 1 - acrossWarp * acrossWarp));\n const weftCrown = Math.sqrt(Math.max(0, 1 - acrossWeft * acrossWeft));\n const warpValue =\n 0.62 + c.relief * (0.14 * warpCrown - 0.07) + c.fuzz * 0.15 * warpFiber;\n const weftValue =\n 0.43 + c.relief * (0.14 * weftCrown - 0.07) + c.fuzz * 0.15 * weftFiber;\n const gap = 0.18;\n // The drawdown determines occlusion at crossings. A yarn stays continuous\n // along a float; no per-cell bevel or repeated shadow chops it into blocks.\n return warpOnTop\n ? warpValue * warp + (weftValue * weft + gap * (1 - weft)) * (1 - warp)\n : weftValue * weft + (warpValue * warp + gap * (1 - warp)) * (1 - weft);\n}\nconst plainWeaveRecipe = /* @__PURE__ */ pattern(\n fabricConfig,\n (c, u, v, phase) => {\n v = wrap(v + phase);\n const x = Math.floor(u * c.threads),\n y = Math.floor(v * c.threads);\n return fabricSample(c, u, v, (x + y) % 2 === 0);\n },\n);\nconst twillRecipe = /* @__PURE__ */ pattern(fabricConfig, (c, u, v, phase) => {\n v = wrap(v + phase);\n const x = Math.floor(u * c.threads),\n y = Math.floor(v * c.threads);\n return fabricSample(c, u, v, (((x - y) % 4) + 4) % 4 < 2);\n});\nconst herringboneHarnesses = [0, 1, 2, 3, 1, 0, 3, 2] as const;\nconst herringboneRecipe = /* @__PURE__ */ pattern(\n fabricConfig,\n (c, u, v, phase) => {\n v = wrap(v + phase);\n const x = Math.floor(u * c.threads) % 8,\n y = Math.floor(v * c.threads);\n // CottonWorks Weaving 101, p. 23: harness sequence 1,2,3,4,2,1,4,3.\n // The offset at each reversal breaks the diagonal instead of duplicating\n // the turning end, which would produce a pointed/chevron-like twill.\n const harness = herringboneHarnesses[x];\n return fabricSample(c, u, v, (harness + y) % 4 >= 2);\n },\n);\n/**\n * Creates an alternating over-under plain weave with rounded threads and seeded fibers.\n * @param options - Thread geometry, shading, seed, and motion. See {@link FabricOptions}.\n * @returns A mountable recipe with no message commands. See {@link TextureRecipe}.\n * @see {@link FabricOptions}\n */\nexport function plainWeave(options: FabricOptions = {}): TextureRecipe {\n return plainWeaveRecipe(options);\n}\n/**\n * Creates a two-over, two-under diagonal twill fabric.\n * @param options - Thread geometry, shading, seed, and motion. See {@link FabricOptions}.\n * @returns A mountable recipe with no message commands. See {@link TextureRecipe}.\n * @see {@link FabricOptions}\n */\nexport function twill(options: FabricOptions = {}): TextureRecipe {\n return twillRecipe(options);\n}\n/**\n * Creates a reversing twill fabric with repeating herringbone chevrons.\n * @param options - Thread geometry, shading, seed, and motion. See {@link FabricOptions}.\n * @returns A mountable recipe with no message commands. See {@link TextureRecipe}.\n * @see {@link FabricOptions}\n */\nexport function herringbone(options: FabricOptions = {}): TextureRecipe {\n return herringboneRecipe(options);\n}\n\n/** Seeded seamless noise layers and their tonal response. @see {@link softNoise} */\nexport interface NoiseOptions extends PatternMotionOptions {\n /** Unsigned 32-bit noise seed; default 42. See {@link NoiseOptions}. */\n readonly seed?: number;\n /** Integer base lattice periods per axis, 1\u201316; default 4. See {@link NoiseOptions}. */\n readonly frequency?: number;\n /** Number of doubled-frequency layers, integer 1\u20136; default 4. See {@link NoiseOptions}. */\n readonly octaves?: number;\n /** Relative amplitude of each successive layer, 0\u20131; default 0.5. See {@link NoiseOptions}. */\n readonly persistence?: number;\n /** Tonal contrast about the midpoint, 0\u20134; default 1. See {@link NoiseOptions}. */\n readonly contrast?: number;\n}\nfunction noiseConfig(options: NoiseOptions) {\n return {\n ...motion(options),\n seed: validateSeed(options.seed),\n frequency: bounded(\"Noise frequency\", options.frequency ?? 4, 1, 16, true),\n octaves: bounded(\"Noise octaves\", options.octaves ?? 4, 1, 6, true),\n persistence: bounded(\"Noise persistence\", options.persistence ?? 0.5, 0, 1),\n contrast: bounded(\"Noise contrast\", options.contrast ?? 1, 0, 4),\n };\n}\nfunction layeredNoise(\n c: ReturnType<typeof noiseConfig>,\n u: number,\n v: number,\n ridge: boolean,\n) {\n let sum = 0,\n weight = 0,\n amplitude = 1,\n frequency = c.frequency;\n for (let i = 0; i < c.octaves; i++) {\n const sample = noise(\n u,\n v,\n frequency,\n frequency,\n c.seed ^ Math.imul(i, 0x517cc1b7),\n );\n sum += (ridge ? 1 - Math.abs(sample) : 0.5 + sample * 0.5) * amplitude;\n weight += amplitude;\n amplitude *= c.persistence;\n frequency *= 2;\n }\n return 0.5 + (sum / weight - 0.5) * c.contrast;\n}\nconst softNoiseRecipe = /* @__PURE__ */ pattern(noiseConfig, (c, u, v, phase) =>\n layeredNoise(c, u, wrap(v + phase), false),\n);\nconst ridgedNoiseRecipe = /* @__PURE__ */ pattern(\n noiseConfig,\n (c, u, v, phase) => layeredNoise(c, u, wrap(v + phase), true),\n);\n/**\n * Creates smooth seamless multiscale lattice noise for clouds, pigment, and material variation.\n * @param options - Seed, lattice frequencies, amplitudes, contrast, and motion. See {@link NoiseOptions}.\n * @returns A mountable recipe with no message commands. See {@link TextureRecipe}.\n * @see {@link NoiseOptions}\n */\nexport function softNoise(options: NoiseOptions = {}): TextureRecipe {\n return softNoiseRecipe(options);\n}\n/**\n * Creates seamless multiscale ridges by folding each noise layer around its midpoint.\n * @param options - Seed, lattice frequencies, amplitudes, contrast, and motion. See {@link NoiseOptions}.\n * @returns A mountable recipe with no message commands. See {@link TextureRecipe}.\n * @see {@link NoiseOptions}\n */\nexport function ridgedNoise(options: NoiseOptions = {}): TextureRecipe {\n return ridgedNoiseRecipe(options);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactionDiffusionOptions, type ReactionDiffusionPreset } from "./gray-scott.js";
|
|
1
2
|
import { type TextureRenderer } from "./presentation.js";
|
|
2
3
|
import { type TexturePalette, type TextureResolution } from "./palette.js";
|
|
3
4
|
/**
|
|
@@ -20,7 +21,7 @@ export interface TextureBrush {
|
|
|
20
21
|
* @see {@link TextureBrush}
|
|
21
22
|
* @see {@link ReactionDiffusion}
|
|
22
23
|
*/
|
|
23
|
-
export interface ReactionDiffusionProps {
|
|
24
|
+
export interface ReactionDiffusionProps extends ReactionDiffusionOptions {
|
|
24
25
|
/**
|
|
25
26
|
* Renders the current data or resources using the supplied context. See {@link TextureRenderer}
|
|
26
27
|
* .
|
|
@@ -37,7 +38,7 @@ export interface ReactionDiffusionProps {
|
|
|
37
38
|
* Named pattern configuration used to initialize the simulation. See
|
|
38
39
|
* {@link ReactionDiffusionProps}.
|
|
39
40
|
*/
|
|
40
|
-
readonly preset?:
|
|
41
|
+
readonly preset?: ReactionDiffusionPreset;
|
|
41
42
|
/** Ordered colors used to present texture intensity. See {@link TexturePalette}. */
|
|
42
43
|
readonly palette?: TexturePalette;
|
|
43
44
|
/**
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
grayScottConfig
|
|
3
|
+
} from "./gray-scott.js";
|
|
1
4
|
import { defineRenderLayer } from "@pibbl/core";
|
|
2
5
|
import { TextureRaster } from "./presentation.js";
|
|
3
6
|
import { InkSimulation } from "./simulation.js";
|
|
4
7
|
import {
|
|
5
8
|
paletteLut,
|
|
6
9
|
texturePalettes,
|
|
7
|
-
validateResolution
|
|
8
|
-
validateSeed
|
|
10
|
+
validateResolution
|
|
9
11
|
} from "./palette.js";
|
|
10
12
|
function options(props) {
|
|
11
|
-
const
|
|
12
|
-
if (preset !== "spots" && preset !== "stripes")
|
|
13
|
-
throw new TypeError("Unknown reaction-diffusion preset.");
|
|
13
|
+
const chemistry = grayScottConfig(props);
|
|
14
14
|
const brush = props.brush ?? { radius: 6, strength: 0.8 };
|
|
15
15
|
if (brush && (!Number.isFinite(brush.radius) || brush.radius <= 0 || brush.radius > 128 || !Number.isFinite(brush.strength) || brush.strength < 0 || brush.strength > 1))
|
|
16
16
|
throw new RangeError(
|
|
@@ -20,8 +20,7 @@ function options(props) {
|
|
|
20
20
|
throw new RangeError("resetRevision must be a safe integer.");
|
|
21
21
|
return {
|
|
22
22
|
resolution: validateResolution(props.resolution),
|
|
23
|
-
|
|
24
|
-
preset,
|
|
23
|
+
...chemistry,
|
|
25
24
|
palette: [...props.palette ?? texturePalettes.lagoon],
|
|
26
25
|
paused: props.paused ?? false,
|
|
27
26
|
resetRevision: props.resetRevision ?? 0,
|
|
@@ -30,12 +29,7 @@ function options(props) {
|
|
|
30
29
|
}
|
|
31
30
|
function reset(r, config) {
|
|
32
31
|
const { width, height } = config.resolution;
|
|
33
|
-
const simulation = new InkSimulation(
|
|
34
|
-
width,
|
|
35
|
-
height,
|
|
36
|
-
config.seed,
|
|
37
|
-
config.preset
|
|
38
|
-
);
|
|
32
|
+
const simulation = new InkSimulation({ width, height, ...config });
|
|
39
33
|
let raster;
|
|
40
34
|
try {
|
|
41
35
|
raster = new TextureRaster(width, height);
|
|
@@ -109,12 +103,7 @@ const ReactionDiffusion = /* @__PURE__ */ defineRenderLayer({
|
|
|
109
103
|
raster,
|
|
110
104
|
output,
|
|
111
105
|
render: props.render,
|
|
112
|
-
simulation: new InkSimulation(
|
|
113
|
-
width,
|
|
114
|
-
height,
|
|
115
|
-
config.seed,
|
|
116
|
-
config.preset
|
|
117
|
-
),
|
|
106
|
+
simulation: new InkSimulation({ width, height, ...config }),
|
|
118
107
|
elapsed: 0,
|
|
119
108
|
fresh: true,
|
|
120
109
|
dirty: true,
|
|
@@ -134,7 +123,7 @@ const ReactionDiffusion = /* @__PURE__ */ defineRenderLayer({
|
|
|
134
123
|
r.lut = paletteLut(next.palette);
|
|
135
124
|
r.dirty = true;
|
|
136
125
|
}
|
|
137
|
-
if (next.seed !== old.seed || next.preset !== old.preset || next.resetRevision !== old.resetRevision || next.resolution.width !== old.resolution.width || next.resolution.height !== old.resolution.height)
|
|
126
|
+
if (next.seed !== old.seed || next.preset !== old.preset || next.feed !== old.feed || next.kill !== old.kill || next.warmupSteps !== old.warmupSteps || next.resetRevision !== old.resetRevision || next.resolution.width !== old.resolution.width || next.resolution.height !== old.resolution.height)
|
|
138
127
|
reset(r, next);
|
|
139
128
|
if (next.paused !== old.paused) {
|
|
140
129
|
r.elapsed = 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/features/textures/lib/reaction-diffusion.ts"],
|
|
4
|
-
"sourcesContent": ["import { defineRenderLayer, type PibblRenderLayerInput } from \"@pibbl/core\";\nimport { TextureRaster, type TextureRenderer } from \"./presentation.js\";\nimport { InkSimulation } from \"./simulation.js\";\nimport {\n paletteLut,\n texturePalettes,\n validateResolution,\n validateSeed,\n type TexturePalette,\n type TextureResolution,\n} from \"./palette.js\";\n\n/**\n * Radius and strength of a reaction-diffusion brush stroke.\n *\n * @see {@link ReactionDiffusionProps}\n */\nexport interface TextureBrush {\n /** Radius in the coordinate system of this geometry or effect. See {@link TextureBrush}. */\n readonly radius: number;\n /** Magnitude of the displacement or brush effect. See {@link TextureBrush}. */\n readonly strength: number;\n}\n/**\n * Authored inputs for ReactionDiffusion, including the declared data and presentation options.\n *\n * @see {@link TextureRenderer}\n * @see {@link TextureResolution}\n * @see {@link TexturePalette}\n * @see {@link TextureBrush}\n * @see {@link ReactionDiffusion}\n */\nexport interface ReactionDiffusionProps {\n /**\n * Renders the current data or resources using the supplied context. See {@link TextureRenderer}\n * .\n */\n readonly render?: TextureRenderer;\n /** Dimensions of the simulation or raster grid. See {@link TextureResolution}. */\n readonly resolution?: TextureResolution;\n /**\n * Seed used for deterministic sampling or simulation initialization. See\n * {@link ReactionDiffusionProps}.\n */\n readonly seed?: number;\n /**\n * Named pattern configuration used to initialize the simulation. See\n * {@link ReactionDiffusionProps}.\n */\n readonly preset?: \"spots\" | \"stripes\";\n /** Ordered colors used to present texture intensity. See {@link TexturePalette}. */\n readonly palette?: TexturePalette;\n /**\n * Whether simulation or playback advancement is suspended. See {@link ReactionDiffusionProps}.\n */\n readonly paused?: boolean;\n /**\n * Changing this revision requests a fresh simulation initialization. See\n * {@link ReactionDiffusionProps}.\n */\n readonly resetRevision?: number;\n /**\n * Brush configuration for interactive reaction-diffusion input, or false to disable it. See\n * {@link TextureBrush}.\n */\n readonly brush?: TextureBrush | false;\n}\ninterface Options {\n resolution: TextureResolution;\n seed: number;\n preset: \"spots\" | \"stripes\";\n palette: TexturePalette;\n paused: boolean;\n resetRevision: number;\n brush: TextureBrush | false;\n}\nfunction options(props: ReactionDiffusionProps): Options {\n const preset = props.preset ?? \"spots\";\n if (preset !== \"spots\" && preset !== \"stripes\")\n throw new TypeError(\"Unknown reaction-diffusion preset.\");\n const brush = props.brush ?? { radius: 6, strength: 0.8 };\n if (\n brush &&\n (!Number.isFinite(brush.radius) ||\n brush.radius <= 0 ||\n brush.radius > 128 ||\n !Number.isFinite(brush.strength) ||\n brush.strength < 0 ||\n brush.strength > 1)\n )\n throw new RangeError(\n \"Brush radius must be in (0,128] and strength in [0,1].\",\n );\n if (!Number.isSafeInteger(props.resetRevision ?? 0))\n throw new RangeError(\"resetRevision must be a safe integer.\");\n return {\n resolution: validateResolution(props.resolution),\n seed: validateSeed(props.seed),\n preset,\n palette: [...(props.palette ?? texturePalettes.lagoon)],\n paused: props.paused ?? false,\n resetRevision: props.resetRevision ?? 0,\n brush: brush && { ...brush },\n };\n}\ninterface Stroke {\n x: number;\n y: number;\n radius: number;\n strength: number;\n}\ninterface Resources {\n render?: TextureRenderer;\n config: Options;\n simulation: InkSimulation;\n raster: TextureRaster;\n output: OffscreenCanvasRenderingContext2D;\n lut: Uint8ClampedArray;\n elapsed: number;\n fresh: boolean;\n dirty: boolean;\n disposed: boolean;\n target: object;\n strokes: Stroke[];\n pointer?: { id: number; x: number; y: number; release: () => void };\n invalidate: () => void;\n}\nfunction reset(r: Resources, config: Options) {\n const { width, height } = config.resolution;\n const simulation = new InkSimulation(\n width,\n height,\n config.seed,\n config.preset,\n );\n let raster: TextureRaster;\n try {\n raster = new TextureRaster(width, height);\n } catch (error) {\n simulation.dispose();\n throw error;\n }\n r.simulation.dispose();\n r.simulation = simulation;\n r.raster.dispose();\n r.raster = raster;\n r.strokes.length = 0;\n r.pointer?.release();\n r.pointer = undefined;\n r.elapsed = 0;\n r.fresh = true;\n r.dirty = true;\n}\nfunction input(\n r: Resources,\n event: Readonly<PibblRenderLayerInput<object>>,\n width: number,\n height: number,\n) {\n if (r.disposed) return;\n const original = event.originalEvent as PointerEvent;\n if (event.type === \"pointerup\" || event.type === \"pointercancel\") {\n if (r.pointer?.id === original.pointerId) {\n r.pointer.release();\n r.pointer = undefined;\n }\n return;\n }\n const brush = r.config.brush;\n if (!brush || width <= 0 || height <= 0) return;\n const x = (event.point.x / width) * r.simulation.width;\n const y = (event.point.y / height) * r.simulation.height;\n if (event.type === \"pointerdown\") {\n if (r.pointer || original.button !== 0 || original.isPrimary === false)\n return;\n event.setPointerCapture(original.pointerId);\n r.pointer = {\n id: original.pointerId,\n x,\n y,\n release: () => event.releasePointerCapture(original.pointerId),\n };\n } else if (\n event.type !== \"pointermove\" ||\n r.pointer?.id !== original.pointerId\n )\n return;\n const previous = r.pointer!;\n const distance = Math.hypot(x - previous.x, y - previous.y);\n // Bound work for off-surface capture. Stamps outside the field are ignored, never clamped to its edge.\n const count = Math.min(\n 4096,\n Math.max(1, Math.ceil(distance / Math.max(0.5, brush.radius * 0.3))),\n );\n for (let i = 1; i <= count; i++) {\n const px = previous.x + ((x - previous.x) * i) / count,\n py = previous.y + ((y - previous.y) * i) / count;\n if (\n px >= 0 &&\n py >= 0 &&\n px < r.simulation.width &&\n py < r.simulation.height &&\n r.strokes.length < 8192\n )\n r.strokes.push({ x: px, y: py, ...brush });\n }\n previous.x = x;\n previous.y = y;\n r.invalidate();\n}\n\n/**\n * An independently sized, persistent Gray\u2013Scott surface, advanced by Pibbl's scheduler.\n *\n * @param props - Authored component inputs, supplied through JSX or createElement. See the linked\n * props and style types.\n * @throws When called directly; Pibbl mounts this component through JSX or createElement.\n *\n * @see {@link ReactionDiffusionProps}\n */\nexport const ReactionDiffusion = /* @__PURE__ */ defineRenderLayer<\n ReactionDiffusionProps,\n Resources\n>({\n create(context, props) {\n const config = options(props),\n lut = paletteLut(config.palette);\n const output = context.canvas.getContext(\"2d\");\n if (!output) throw new Error(\"ReactionDiffusion requires Canvas 2D.\");\n const { width, height } = config.resolution;\n const raster = new TextureRaster(width, height);\n try {\n return {\n config,\n lut,\n raster,\n output,\n render: props.render,\n simulation: new InkSimulation(\n width,\n height,\n config.seed,\n config.preset,\n ),\n elapsed: 0,\n fresh: true,\n dirty: true,\n disposed: false,\n target: {},\n strokes: [],\n invalidate: context.invalidate,\n };\n } catch (error) {\n raster.dispose();\n throw error;\n }\n },\n update(r, props) {\n const next = options(props),\n old = r.config;\n if (next.palette.join() !== old.palette.join()) {\n r.lut = paletteLut(next.palette);\n r.dirty = true;\n }\n if (\n next.seed !== old.seed ||\n next.preset !== old.preset ||\n next.resetRevision !== old.resetRevision ||\n next.resolution.width !== old.resolution.width ||\n next.resolution.height !== old.resolution.height\n )\n reset(r, next);\n if (next.paused !== old.paused) {\n r.elapsed = 0;\n r.fresh = true;\n }\n if (!next.brush) {\n r.pointer?.release();\n r.pointer = undefined;\n }\n r.config = next;\n r.render = props.render;\n },\n render(r, frame, context) {\n // The frame invalidator does not gate the committed input snapshot between brush samples.\n r.invalidate = frame.invalidate;\n for (const stroke of r.strokes)\n r.simulation.paint(stroke.x, stroke.y, stroke.radius, stroke.strength);\n if (r.strokes.length) r.dirty = true;\n r.strokes.length = 0;\n if (!r.config.paused && !r.fresh) {\n r.elapsed += Math.max(0, Math.min(50, frame.delta));\n const steps = Math.min(24, Math.floor(r.elapsed * 0.48 + 1e-9));\n r.elapsed -= steps / 0.48;\n if (steps) {\n r.simulation.advance(steps);\n r.dirty = true;\n }\n }\n r.fresh = false;\n if (r.dirty) {\n r.raster.update(r.simulation.b, r.lut, 650 / 255);\n r.dirty = false;\n }\n r.raster.draw(\n r.output,\n context.size.width,\n context.size.height,\n context.size.backingWidth,\n context.size.backingHeight,\n r.render,\n );\n if (!r.config.paused) frame.invalidate();\n },\n interaction: {\n settle(r, _props, size) {\n return {\n targets: [\n {\n target: r.target,\n cursor: r.config.brush ? \"crosshair\" : \"default\",\n },\n ],\n hitTest: () => ({ kind: \"target\", target: r.target }),\n onInput: (event) => input(r, event, size.width, size.height),\n };\n },\n },\n dispose(r) {\n if (r.disposed) return;\n r.disposed = true;\n r.pointer?.release();\n r.pointer = undefined;\n r.strokes.length = 0;\n r.invalidate = () => {};\n r.simulation.dispose();\n r.raster.dispose();\n r.render = undefined;\n },\n});\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,yBAAqD;AAC9D,SAAS,qBAA2C;AACpD,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n grayScottConfig,\n type ReactionDiffusionOptions,\n type ReactionDiffusionPreset,\n} from \"./gray-scott.js\";\nimport { defineRenderLayer, type PibblRenderLayerInput } from \"@pibbl/core\";\nimport { TextureRaster, type TextureRenderer } from \"./presentation.js\";\nimport { InkSimulation } from \"./simulation.js\";\nimport {\n paletteLut,\n texturePalettes,\n validateResolution,\n type TexturePalette,\n type TextureResolution,\n} from \"./palette.js\";\n\n/**\n * Radius and strength of a reaction-diffusion brush stroke.\n *\n * @see {@link ReactionDiffusionProps}\n */\nexport interface TextureBrush {\n /** Radius in the coordinate system of this geometry or effect. See {@link TextureBrush}. */\n readonly radius: number;\n /** Magnitude of the displacement or brush effect. See {@link TextureBrush}. */\n readonly strength: number;\n}\n/**\n * Authored inputs for ReactionDiffusion, including the declared data and presentation options.\n *\n * @see {@link TextureRenderer}\n * @see {@link TextureResolution}\n * @see {@link TexturePalette}\n * @see {@link TextureBrush}\n * @see {@link ReactionDiffusion}\n */\nexport interface ReactionDiffusionProps extends ReactionDiffusionOptions {\n /**\n * Renders the current data or resources using the supplied context. See {@link TextureRenderer}\n * .\n */\n readonly render?: TextureRenderer;\n /** Dimensions of the simulation or raster grid. See {@link TextureResolution}. */\n readonly resolution?: TextureResolution;\n /**\n * Seed used for deterministic sampling or simulation initialization. See\n * {@link ReactionDiffusionProps}.\n */\n readonly seed?: number;\n /**\n * Named pattern configuration used to initialize the simulation. See\n * {@link ReactionDiffusionProps}.\n */\n readonly preset?: ReactionDiffusionPreset;\n /** Ordered colors used to present texture intensity. See {@link TexturePalette}. */\n readonly palette?: TexturePalette;\n /**\n * Whether simulation or playback advancement is suspended. See {@link ReactionDiffusionProps}.\n */\n readonly paused?: boolean;\n /**\n * Changing this revision requests a fresh simulation initialization. See\n * {@link ReactionDiffusionProps}.\n */\n readonly resetRevision?: number;\n /**\n * Brush configuration for interactive reaction-diffusion input, or false to disable it. See\n * {@link TextureBrush}.\n */\n readonly brush?: TextureBrush | false;\n}\ninterface Options {\n resolution: TextureResolution;\n seed: number;\n preset: ReactionDiffusionPreset;\n feed: number;\n kill: number;\n warmupSteps: number;\n palette: TexturePalette;\n paused: boolean;\n resetRevision: number;\n brush: TextureBrush | false;\n}\nfunction options(props: ReactionDiffusionProps): Options {\n const chemistry = grayScottConfig(props);\n const brush = props.brush ?? { radius: 6, strength: 0.8 };\n if (\n brush &&\n (!Number.isFinite(brush.radius) ||\n brush.radius <= 0 ||\n brush.radius > 128 ||\n !Number.isFinite(brush.strength) ||\n brush.strength < 0 ||\n brush.strength > 1)\n )\n throw new RangeError(\n \"Brush radius must be in (0,128] and strength in [0,1].\",\n );\n if (!Number.isSafeInteger(props.resetRevision ?? 0))\n throw new RangeError(\"resetRevision must be a safe integer.\");\n return {\n resolution: validateResolution(props.resolution),\n ...chemistry,\n palette: [...(props.palette ?? texturePalettes.lagoon)],\n paused: props.paused ?? false,\n resetRevision: props.resetRevision ?? 0,\n brush: brush && { ...brush },\n };\n}\ninterface Stroke {\n x: number;\n y: number;\n radius: number;\n strength: number;\n}\ninterface Resources {\n render?: TextureRenderer;\n config: Options;\n simulation: InkSimulation;\n raster: TextureRaster;\n output: OffscreenCanvasRenderingContext2D;\n lut: Uint8ClampedArray;\n elapsed: number;\n fresh: boolean;\n dirty: boolean;\n disposed: boolean;\n target: object;\n strokes: Stroke[];\n pointer?: { id: number; x: number; y: number; release: () => void };\n invalidate: () => void;\n}\nfunction reset(r: Resources, config: Options) {\n const { width, height } = config.resolution;\n const simulation = new InkSimulation({ width, height, ...config });\n let raster: TextureRaster;\n try {\n raster = new TextureRaster(width, height);\n } catch (error) {\n simulation.dispose();\n throw error;\n }\n r.simulation.dispose();\n r.simulation = simulation;\n r.raster.dispose();\n r.raster = raster;\n r.strokes.length = 0;\n r.pointer?.release();\n r.pointer = undefined;\n r.elapsed = 0;\n r.fresh = true;\n r.dirty = true;\n}\nfunction input(\n r: Resources,\n event: Readonly<PibblRenderLayerInput<object>>,\n width: number,\n height: number,\n) {\n if (r.disposed) return;\n const original = event.originalEvent as PointerEvent;\n if (event.type === \"pointerup\" || event.type === \"pointercancel\") {\n if (r.pointer?.id === original.pointerId) {\n r.pointer.release();\n r.pointer = undefined;\n }\n return;\n }\n const brush = r.config.brush;\n if (!brush || width <= 0 || height <= 0) return;\n const x = (event.point.x / width) * r.simulation.width;\n const y = (event.point.y / height) * r.simulation.height;\n if (event.type === \"pointerdown\") {\n if (r.pointer || original.button !== 0 || original.isPrimary === false)\n return;\n event.setPointerCapture(original.pointerId);\n r.pointer = {\n id: original.pointerId,\n x,\n y,\n release: () => event.releasePointerCapture(original.pointerId),\n };\n } else if (\n event.type !== \"pointermove\" ||\n r.pointer?.id !== original.pointerId\n )\n return;\n const previous = r.pointer!;\n const distance = Math.hypot(x - previous.x, y - previous.y);\n // Bound work for off-surface capture. Stamps outside the field are ignored, never clamped to its edge.\n const count = Math.min(\n 4096,\n Math.max(1, Math.ceil(distance / Math.max(0.5, brush.radius * 0.3))),\n );\n for (let i = 1; i <= count; i++) {\n const px = previous.x + ((x - previous.x) * i) / count,\n py = previous.y + ((y - previous.y) * i) / count;\n if (\n px >= 0 &&\n py >= 0 &&\n px < r.simulation.width &&\n py < r.simulation.height &&\n r.strokes.length < 8192\n )\n r.strokes.push({ x: px, y: py, ...brush });\n }\n previous.x = x;\n previous.y = y;\n r.invalidate();\n}\n\n/**\n * An independently sized, persistent Gray\u2013Scott surface, advanced by Pibbl's scheduler.\n *\n * @param props - Authored component inputs, supplied through JSX or createElement. See the linked\n * props and style types.\n * @throws When called directly; Pibbl mounts this component through JSX or createElement.\n *\n * @see {@link ReactionDiffusionProps}\n */\nexport const ReactionDiffusion = /* @__PURE__ */ defineRenderLayer<\n ReactionDiffusionProps,\n Resources\n>({\n create(context, props) {\n const config = options(props),\n lut = paletteLut(config.palette);\n const output = context.canvas.getContext(\"2d\");\n if (!output) throw new Error(\"ReactionDiffusion requires Canvas 2D.\");\n const { width, height } = config.resolution;\n const raster = new TextureRaster(width, height);\n try {\n return {\n config,\n lut,\n raster,\n output,\n render: props.render,\n simulation: new InkSimulation({ width, height, ...config }),\n elapsed: 0,\n fresh: true,\n dirty: true,\n disposed: false,\n target: {},\n strokes: [],\n invalidate: context.invalidate,\n };\n } catch (error) {\n raster.dispose();\n throw error;\n }\n },\n update(r, props) {\n const next = options(props),\n old = r.config;\n if (next.palette.join() !== old.palette.join()) {\n r.lut = paletteLut(next.palette);\n r.dirty = true;\n }\n if (\n next.seed !== old.seed ||\n next.preset !== old.preset ||\n next.feed !== old.feed ||\n next.kill !== old.kill ||\n next.warmupSteps !== old.warmupSteps ||\n next.resetRevision !== old.resetRevision ||\n next.resolution.width !== old.resolution.width ||\n next.resolution.height !== old.resolution.height\n )\n reset(r, next);\n if (next.paused !== old.paused) {\n r.elapsed = 0;\n r.fresh = true;\n }\n if (!next.brush) {\n r.pointer?.release();\n r.pointer = undefined;\n }\n r.config = next;\n r.render = props.render;\n },\n render(r, frame, context) {\n // The frame invalidator does not gate the committed input snapshot between brush samples.\n r.invalidate = frame.invalidate;\n for (const stroke of r.strokes)\n r.simulation.paint(stroke.x, stroke.y, stroke.radius, stroke.strength);\n if (r.strokes.length) r.dirty = true;\n r.strokes.length = 0;\n if (!r.config.paused && !r.fresh) {\n r.elapsed += Math.max(0, Math.min(50, frame.delta));\n const steps = Math.min(24, Math.floor(r.elapsed * 0.48 + 1e-9));\n r.elapsed -= steps / 0.48;\n if (steps) {\n r.simulation.advance(steps);\n r.dirty = true;\n }\n }\n r.fresh = false;\n if (r.dirty) {\n r.raster.update(r.simulation.b, r.lut, 650 / 255);\n r.dirty = false;\n }\n r.raster.draw(\n r.output,\n context.size.width,\n context.size.height,\n context.size.backingWidth,\n context.size.backingHeight,\n r.render,\n );\n if (!r.config.paused) frame.invalidate();\n },\n interaction: {\n settle(r, _props, size) {\n return {\n targets: [\n {\n target: r.target,\n cursor: r.config.brush ? \"crosshair\" : \"default\",\n },\n ],\n hitTest: () => ({ kind: \"target\", target: r.target }),\n onInput: (event) => input(r, event, size.width, size.height),\n };\n },\n },\n dispose(r) {\n if (r.disposed) return;\n r.disposed = true;\n r.pointer?.release();\n r.pointer = undefined;\n r.strokes.length = 0;\n r.invalidate = () => {};\n r.simulation.dispose();\n r.raster.dispose();\n r.render = undefined;\n },\n});\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACE;AAAA,OAGK;AACP,SAAS,yBAAqD;AAC9D,SAAS,qBAA2C;AACpD,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAqEP,SAAS,QAAQ,OAAwC;AACvD,QAAM,YAAY,gBAAgB,KAAK;AACvC,QAAM,QAAQ,MAAM,SAAS,EAAE,QAAQ,GAAG,UAAU,IAAI;AACxD,MACE,UACC,CAAC,OAAO,SAAS,MAAM,MAAM,KAC5B,MAAM,UAAU,KAChB,MAAM,SAAS,OACf,CAAC,OAAO,SAAS,MAAM,QAAQ,KAC/B,MAAM,WAAW,KACjB,MAAM,WAAW;AAEnB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AACF,MAAI,CAAC,OAAO,cAAc,MAAM,iBAAiB,CAAC;AAChD,UAAM,IAAI,WAAW,uCAAuC;AAC9D,SAAO;AAAA,IACL,YAAY,mBAAmB,MAAM,UAAU;AAAA,IAC/C,GAAG;AAAA,IACH,SAAS,CAAC,GAAI,MAAM,WAAW,gBAAgB,MAAO;AAAA,IACtD,QAAQ,MAAM,UAAU;AAAA,IACxB,eAAe,MAAM,iBAAiB;AAAA,IACtC,OAAO,SAAS,EAAE,GAAG,MAAM;AAAA,EAC7B;AACF;AAuBA,SAAS,MAAM,GAAc,QAAiB;AAC5C,QAAM,EAAE,OAAO,OAAO,IAAI,OAAO;AACjC,QAAM,aAAa,IAAI,cAAc,EAAE,OAAO,QAAQ,GAAG,OAAO,CAAC;AACjE,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,cAAc,OAAO,MAAM;AAAA,EAC1C,SAAS,OAAO;AACd,eAAW,QAAQ;AACnB,UAAM;AAAA,EACR;AACA,IAAE,WAAW,QAAQ;AACrB,IAAE,aAAa;AACf,IAAE,OAAO,QAAQ;AACjB,IAAE,SAAS;AACX,IAAE,QAAQ,SAAS;AACnB,IAAE,SAAS,QAAQ;AACnB,IAAE,UAAU;AACZ,IAAE,UAAU;AACZ,IAAE,QAAQ;AACV,IAAE,QAAQ;AACZ;AACA,SAAS,MACP,GACA,OACA,OACA,QACA;AACA,MAAI,EAAE,SAAU;AAChB,QAAM,WAAW,MAAM;AACvB,MAAI,MAAM,SAAS,eAAe,MAAM,SAAS,iBAAiB;AAChE,QAAI,EAAE,SAAS,OAAO,SAAS,WAAW;AACxC,QAAE,QAAQ,QAAQ;AAClB,QAAE,UAAU;AAAA,IACd;AACA;AAAA,EACF;AACA,QAAM,QAAQ,EAAE,OAAO;AACvB,MAAI,CAAC,SAAS,SAAS,KAAK,UAAU,EAAG;AACzC,QAAM,IAAK,MAAM,MAAM,IAAI,QAAS,EAAE,WAAW;AACjD,QAAM,IAAK,MAAM,MAAM,IAAI,SAAU,EAAE,WAAW;AAClD,MAAI,MAAM,SAAS,eAAe;AAChC,QAAI,EAAE,WAAW,SAAS,WAAW,KAAK,SAAS,cAAc;AAC/D;AACF,UAAM,kBAAkB,SAAS,SAAS;AAC1C,MAAE,UAAU;AAAA,MACV,IAAI,SAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA,SAAS,MAAM,MAAM,sBAAsB,SAAS,SAAS;AAAA,IAC/D;AAAA,EACF,WACE,MAAM,SAAS,iBACf,EAAE,SAAS,OAAO,SAAS;AAE3B;AACF,QAAM,WAAW,EAAE;AACnB,QAAM,WAAW,KAAK,MAAM,IAAI,SAAS,GAAG,IAAI,SAAS,CAAC;AAE1D,QAAM,QAAQ,KAAK;AAAA,IACjB;AAAA,IACA,KAAK,IAAI,GAAG,KAAK,KAAK,WAAW,KAAK,IAAI,KAAK,MAAM,SAAS,GAAG,CAAC,CAAC;AAAA,EACrE;AACA,WAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,UAAM,KAAK,SAAS,KAAM,IAAI,SAAS,KAAK,IAAK,OAC/C,KAAK,SAAS,KAAM,IAAI,SAAS,KAAK,IAAK;AAC7C,QACE,MAAM,KACN,MAAM,KACN,KAAK,EAAE,WAAW,SAClB,KAAK,EAAE,WAAW,UAClB,EAAE,QAAQ,SAAS;AAEnB,QAAE,QAAQ,KAAK,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAAA,EAC7C;AACA,WAAS,IAAI;AACb,WAAS,IAAI;AACb,IAAE,WAAW;AACf;AAWO,MAAM,oBAAoC,kCAG/C;AAAA,EACA,OAAO,SAAS,OAAO;AACrB,UAAM,SAAS,QAAQ,KAAK,GAC1B,MAAM,WAAW,OAAO,OAAO;AACjC,UAAM,SAAS,QAAQ,OAAO,WAAW,IAAI;AAC7C,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,uCAAuC;AACpE,UAAM,EAAE,OAAO,OAAO,IAAI,OAAO;AACjC,UAAM,SAAS,IAAI,cAAc,OAAO,MAAM;AAC9C,QAAI;AACF,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,MAAM;AAAA,QACd,YAAY,IAAI,cAAc,EAAE,OAAO,QAAQ,GAAG,OAAO,CAAC;AAAA,QAC1D,SAAS;AAAA,QACT,OAAO;AAAA,QACP,OAAO;AAAA,QACP,UAAU;AAAA,QACV,QAAQ,CAAC;AAAA,QACT,SAAS,CAAC;AAAA,QACV,YAAY,QAAQ;AAAA,MACtB;AAAA,IACF,SAAS,OAAO;AACd,aAAO,QAAQ;AACf,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EACA,OAAO,GAAG,OAAO;AACf,UAAM,OAAO,QAAQ,KAAK,GACxB,MAAM,EAAE;AACV,QAAI,KAAK,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,GAAG;AAC9C,QAAE,MAAM,WAAW,KAAK,OAAO;AAC/B,QAAE,QAAQ;AAAA,IACZ;AACA,QACE,KAAK,SAAS,IAAI,QAClB,KAAK,WAAW,IAAI,UACpB,KAAK,SAAS,IAAI,QAClB,KAAK,SAAS,IAAI,QAClB,KAAK,gBAAgB,IAAI,eACzB,KAAK,kBAAkB,IAAI,iBAC3B,KAAK,WAAW,UAAU,IAAI,WAAW,SACzC,KAAK,WAAW,WAAW,IAAI,WAAW;AAE1C,YAAM,GAAG,IAAI;AACf,QAAI,KAAK,WAAW,IAAI,QAAQ;AAC9B,QAAE,UAAU;AACZ,QAAE,QAAQ;AAAA,IACZ;AACA,QAAI,CAAC,KAAK,OAAO;AACf,QAAE,SAAS,QAAQ;AACnB,QAAE,UAAU;AAAA,IACd;AACA,MAAE,SAAS;AACX,MAAE,SAAS,MAAM;AAAA,EACnB;AAAA,EACA,OAAO,GAAG,OAAO,SAAS;AAExB,MAAE,aAAa,MAAM;AACrB,eAAW,UAAU,EAAE;AACrB,QAAE,WAAW,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO,QAAQ,OAAO,QAAQ;AACvE,QAAI,EAAE,QAAQ,OAAQ,GAAE,QAAQ;AAChC,MAAE,QAAQ,SAAS;AACnB,QAAI,CAAC,EAAE,OAAO,UAAU,CAAC,EAAE,OAAO;AAChC,QAAE,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,MAAM,KAAK,CAAC;AAClD,YAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,MAAM,EAAE,UAAU,OAAO,IAAI,CAAC;AAC9D,QAAE,WAAW,QAAQ;AACrB,UAAI,OAAO;AACT,UAAE,WAAW,QAAQ,KAAK;AAC1B,UAAE,QAAQ;AAAA,MACZ;AAAA,IACF;AACA,MAAE,QAAQ;AACV,QAAI,EAAE,OAAO;AACX,QAAE,OAAO,OAAO,EAAE,WAAW,GAAG,EAAE,KAAK,MAAM,GAAG;AAChD,QAAE,QAAQ;AAAA,IACZ;AACA,MAAE,OAAO;AAAA,MACP,EAAE;AAAA,MACF,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK;AAAA,MACb,EAAE;AAAA,IACJ;AACA,QAAI,CAAC,EAAE,OAAO,OAAQ,OAAM,WAAW;AAAA,EACzC;AAAA,EACA,aAAa;AAAA,IACX,OAAO,GAAG,QAAQ,MAAM;AACtB,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,QAAQ,EAAE;AAAA,YACV,QAAQ,EAAE,OAAO,QAAQ,cAAc;AAAA,UACzC;AAAA,QACF;AAAA,QACA,SAAS,OAAO,EAAE,MAAM,UAAU,QAAQ,EAAE,OAAO;AAAA,QACnD,SAAS,CAAC,UAAU,MAAM,GAAG,OAAO,KAAK,OAAO,KAAK,MAAM;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAAA,EACA,QAAQ,GAAG;AACT,QAAI,EAAE,SAAU;AAChB,MAAE,WAAW;AACb,MAAE,SAAS,QAAQ;AACnB,MAAE,UAAU;AACZ,MAAE,QAAQ,SAAS;AACnB,MAAE,aAAa,MAAM;AAAA,IAAC;AACtB,MAAE,WAAW,QAAQ;AACrB,MAAE,OAAO,QAAQ;AACjB,MAAE,SAAS;AAAA,EACb;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import { grayScottConfig, type ReactionDiffusionOptions } from "./gray-scott.js";
|
|
1
2
|
/** Private WASM Gray–Scott state. Pibbl owns the clock and resource lifetime. */
|
|
2
3
|
export declare class InkSimulation {
|
|
3
|
-
readonly width: number;
|
|
4
|
-
readonly height: number;
|
|
5
|
-
readonly preset: "spots" | "stripes";
|
|
6
4
|
a: Float32Array;
|
|
7
5
|
b: Float32Array;
|
|
8
6
|
private owner?;
|
|
9
7
|
private parity;
|
|
10
8
|
steps: number;
|
|
11
|
-
|
|
9
|
+
readonly width: number;
|
|
10
|
+
readonly height: number;
|
|
11
|
+
readonly preset: ReturnType<typeof grayScottConfig>["preset"];
|
|
12
|
+
private readonly feed;
|
|
13
|
+
private readonly kill;
|
|
14
|
+
constructor(options: ReactionDiffusionOptions & {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
});
|
|
12
18
|
paint(x: number, y: number, radius: number, strength: number): void;
|
|
13
19
|
stir(x: number, y: number, dx: number, dy: number, radius: number): void;
|
|
14
20
|
advance(count: number): void;
|