@sarmal/core 0.33.0 → 0.35.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/README.md +2 -2
- package/dist/auto-init.cjs.map +1 -1
- package/dist/auto-init.js.map +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/curves/artemis2.d.cts +1 -1
- package/dist/curves/artemis2.d.ts +1 -1
- package/dist/curves/astroid.d.cts +1 -1
- package/dist/curves/astroid.d.ts +1 -1
- package/dist/curves/deltoid.d.cts +1 -1
- package/dist/curves/deltoid.d.ts +1 -1
- package/dist/curves/epicycloid3.d.cts +1 -1
- package/dist/curves/epicycloid3.d.ts +1 -1
- package/dist/curves/epitrochoid7.d.cts +1 -1
- package/dist/curves/epitrochoid7.d.ts +1 -1
- package/dist/curves/index.d.cts +1 -1
- package/dist/curves/index.d.ts +1 -1
- package/dist/curves/lame.d.cts +1 -1
- package/dist/curves/lame.d.ts +1 -1
- package/dist/curves/lissajous32.d.cts +1 -1
- package/dist/curves/lissajous32.d.ts +1 -1
- package/dist/curves/lissajous43.d.cts +1 -1
- package/dist/curves/lissajous43.d.ts +1 -1
- package/dist/curves/rose3.d.cts +1 -1
- package/dist/curves/rose3.d.ts +1 -1
- package/dist/curves/rose5.d.cts +1 -1
- package/dist/curves/rose5.d.ts +1 -1
- package/dist/curves/rose52.d.cts +1 -1
- package/dist/curves/rose52.d.ts +1 -1
- package/dist/curves/star.d.cts +1 -1
- package/dist/curves/star.d.ts +1 -1
- package/dist/curves/star4.d.cts +1 -1
- package/dist/curves/star4.d.ts +1 -1
- package/dist/curves/star7.d.cts +1 -1
- package/dist/curves/star7.d.ts +1 -1
- package/dist/index.cjs +380 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -4
- package/dist/index.d.ts +80 -4
- package/dist/index.js +380 -1
- package/dist/index.js.map +1 -1
- package/dist/{renderer-shared-Ke9BeK1P.d.cts → renderer-shared-C3KCEABq.d.cts} +1 -1
- package/dist/{renderer-shared-Bdca4O4G.d.ts → renderer-shared-DyOI68gd.d.ts} +1 -1
- package/dist/terminal.cjs.map +1 -1
- package/dist/terminal.d.cts +2 -2
- package/dist/terminal.d.ts +2 -2
- package/dist/terminal.js.map +1 -1
- package/dist/{types-BBuUk6nn.d.cts → types-_f27GDkU.d.cts} +21 -9
- package/dist/{types-BBuUk6nn.d.ts → types-_f27GDkU.d.ts} +21 -9
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { B as BaseRendererOptions, E as Engine, S as SarmalInstance, C as CurveDef, R as RendererOptions,
|
|
2
|
-
export {
|
|
1
|
+
import { B as BaseRendererOptions, E as Engine, S as SarmalInstance, C as CurveDef, T as TrailColor, a as TrailStyle, D as DotMatrixRuntimeRenderOptions, R as RendererOptions, b as ControlPoint, P as Point, c as SarmalOptions } from './types-_f27GDkU.cjs';
|
|
2
|
+
export { d as BaseRuntimeRenderOptions, J as JumpOptions, e as RuntimeRenderOptions, f as SeekOptions } from './types-_f27GDkU.cjs';
|
|
3
3
|
export { CurveName, curves } from './curves/index.cjs';
|
|
4
|
-
export { B as BoundaryResult, S as SarmalPalette, c as computeBoundaries, p as palettes } from './renderer-shared-
|
|
4
|
+
export { B as BoundaryResult, S as SarmalPalette, c as computeBoundaries, p as palettes } from './renderer-shared-C3KCEABq.cjs';
|
|
5
5
|
export { artemis2 } from './curves/artemis2.cjs';
|
|
6
6
|
export { astroid } from './curves/astroid.cjs';
|
|
7
7
|
export { deltoid } from './curves/deltoid.cjs';
|
|
@@ -53,6 +53,82 @@ declare function createSVGRenderer(options: SVGRendererOptions): SarmalInstance;
|
|
|
53
53
|
*/
|
|
54
54
|
declare function createSarmalSVG(container: SVGSVGElement, curveDef: CurveDef, options?: SVGSarmalOptions): SarmalInstance;
|
|
55
55
|
|
|
56
|
+
interface DotMatrixSarmalOptions extends Pick<BaseRendererOptions, "autoStart" | "pauseOnHidden" | "initialPhase"> {
|
|
57
|
+
/**
|
|
58
|
+
* Number of dot columns in the grid.
|
|
59
|
+
* @default 32
|
|
60
|
+
*/
|
|
61
|
+
cols?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Number of dot rows in the grid.
|
|
64
|
+
* @default 32
|
|
65
|
+
*/
|
|
66
|
+
rows?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Controls the corner rounding of each dot.
|
|
69
|
+
* `0` renders as a sharp-cornered square,
|
|
70
|
+
* `1` renders as a full circle.
|
|
71
|
+
* Values between `0` and `1` give rounded rectangles.
|
|
72
|
+
* @default 1
|
|
73
|
+
*/
|
|
74
|
+
roundness?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Number of trail points to keep.
|
|
77
|
+
* Larger values mean the trail extends further back from the head.
|
|
78
|
+
* @default cols * 3
|
|
79
|
+
*/
|
|
80
|
+
trailLength?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Color of lit dots. Single color string for solid mode; array of two or more colors for gradient mode.
|
|
83
|
+
* Gradient mode samples a color per dot based on its position in the trail (tail → head).
|
|
84
|
+
* Background dots always use the first color at 5% opacity.
|
|
85
|
+
* @default '#ffffff'
|
|
86
|
+
*/
|
|
87
|
+
trailColor?: TrailColor;
|
|
88
|
+
/**
|
|
89
|
+
* Trail rendering style.
|
|
90
|
+
* - `'default'` — solid color, alpha varies by intensity.
|
|
91
|
+
* - `'gradient-static'` — each dot's color is sampled from the `trailColor` gradient. Requires `trailColor` array.
|
|
92
|
+
* - `'gradient-animated'` — same as `gradient-static` but the gradient phase shifts over time.
|
|
93
|
+
* @default 'default'
|
|
94
|
+
*/
|
|
95
|
+
trailStyle?: TrailStyle;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a dot matrix renderer for a sarmal animation on a canvas element.
|
|
99
|
+
*
|
|
100
|
+
* The renderer maps the animation's trail to a grid of dots.
|
|
101
|
+
* Each frame, the grid is cleared and rebuilt: dots near the head of the trail are bright,
|
|
102
|
+
* dots near the tail are dim, and dots with no trail activity are barely visible (5% opacity).
|
|
103
|
+
*
|
|
104
|
+
* Grid geometry is derived from `cols` and `rows`.
|
|
105
|
+
* For example, a 240x240 canvas with `cols: 32, rows: 32` produces 1024 dots with cells approximately 7.5x7.5 px each.
|
|
106
|
+
*
|
|
107
|
+
* At init, a pixel mask is computed that records which canvas pixels belong to each dot.
|
|
108
|
+
* Each frame, RGBA values are written directly into a typed array (one entry per lit pixel)
|
|
109
|
+
* and flushed to the canvas with a single `ctx.putImageData` call.
|
|
110
|
+
* Frame cost is flat regardless of how many dots are lit or what grid size is used.
|
|
111
|
+
*
|
|
112
|
+
* @param canvas - The canvas element to draw into.
|
|
113
|
+
* Its `width` and `height` HTML attributes determine the rendering area.
|
|
114
|
+
* CSS display size is not read.
|
|
115
|
+
* @param curveDef - The curve to animate.
|
|
116
|
+
* @param options - Optional configuration for grid size, color, roundness, and lifecycle.
|
|
117
|
+
* @returns A `SarmalInstance` with the standard `play` / `pause` / `destroy` / `morphTo` interface.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { createSarmalDotMatrix, curves } from '@sarmal/core'
|
|
122
|
+
*
|
|
123
|
+
* const instance = createSarmalDotMatrix(canvas, curves.lissajous43, {
|
|
124
|
+
* cols: 32,
|
|
125
|
+
* rows: 32,
|
|
126
|
+
* trailColor: '#2dd4bf',
|
|
127
|
+
* })
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
declare function createSarmalDotMatrix(canvas: HTMLCanvasElement, curveDef: CurveDef, options?: DotMatrixSarmalOptions): SarmalInstance<DotMatrixRuntimeRenderOptions>;
|
|
131
|
+
|
|
56
132
|
declare function createEngine(curveDef: CurveDef, trailLength?: number): Engine;
|
|
57
133
|
|
|
58
134
|
/**
|
|
@@ -119,4 +195,4 @@ declare function drawCurve(points: Array<ControlPoint>, opts?: {
|
|
|
119
195
|
*/
|
|
120
196
|
declare function createSarmal(canvas: HTMLCanvasElement, curveDef: CurveDef, options?: SarmalOptions): SarmalInstance;
|
|
121
197
|
|
|
122
|
-
export { BaseRendererOptions, CurveDef, Engine, Point, RendererOptions, type SVGRendererOptions, type SVGSarmalOptions, SarmalInstance, SarmalOptions, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, drawCurve, evaluateCatmullRom };
|
|
198
|
+
export { BaseRendererOptions, CurveDef, DotMatrixRuntimeRenderOptions, type DotMatrixSarmalOptions, Engine, Point, RendererOptions, type SVGRendererOptions, type SVGSarmalOptions, SarmalInstance, SarmalOptions, TrailColor, TrailStyle, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalDotMatrix, createSarmalSVG, drawCurve, evaluateCatmullRom };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { B as BaseRendererOptions, E as Engine, S as SarmalInstance, C as CurveDef, R as RendererOptions,
|
|
2
|
-
export {
|
|
1
|
+
import { B as BaseRendererOptions, E as Engine, S as SarmalInstance, C as CurveDef, T as TrailColor, a as TrailStyle, D as DotMatrixRuntimeRenderOptions, R as RendererOptions, b as ControlPoint, P as Point, c as SarmalOptions } from './types-_f27GDkU.js';
|
|
2
|
+
export { d as BaseRuntimeRenderOptions, J as JumpOptions, e as RuntimeRenderOptions, f as SeekOptions } from './types-_f27GDkU.js';
|
|
3
3
|
export { CurveName, curves } from './curves/index.js';
|
|
4
|
-
export { B as BoundaryResult, S as SarmalPalette, c as computeBoundaries, p as palettes } from './renderer-shared-
|
|
4
|
+
export { B as BoundaryResult, S as SarmalPalette, c as computeBoundaries, p as palettes } from './renderer-shared-DyOI68gd.js';
|
|
5
5
|
export { artemis2 } from './curves/artemis2.js';
|
|
6
6
|
export { astroid } from './curves/astroid.js';
|
|
7
7
|
export { deltoid } from './curves/deltoid.js';
|
|
@@ -53,6 +53,82 @@ declare function createSVGRenderer(options: SVGRendererOptions): SarmalInstance;
|
|
|
53
53
|
*/
|
|
54
54
|
declare function createSarmalSVG(container: SVGSVGElement, curveDef: CurveDef, options?: SVGSarmalOptions): SarmalInstance;
|
|
55
55
|
|
|
56
|
+
interface DotMatrixSarmalOptions extends Pick<BaseRendererOptions, "autoStart" | "pauseOnHidden" | "initialPhase"> {
|
|
57
|
+
/**
|
|
58
|
+
* Number of dot columns in the grid.
|
|
59
|
+
* @default 32
|
|
60
|
+
*/
|
|
61
|
+
cols?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Number of dot rows in the grid.
|
|
64
|
+
* @default 32
|
|
65
|
+
*/
|
|
66
|
+
rows?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Controls the corner rounding of each dot.
|
|
69
|
+
* `0` renders as a sharp-cornered square,
|
|
70
|
+
* `1` renders as a full circle.
|
|
71
|
+
* Values between `0` and `1` give rounded rectangles.
|
|
72
|
+
* @default 1
|
|
73
|
+
*/
|
|
74
|
+
roundness?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Number of trail points to keep.
|
|
77
|
+
* Larger values mean the trail extends further back from the head.
|
|
78
|
+
* @default cols * 3
|
|
79
|
+
*/
|
|
80
|
+
trailLength?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Color of lit dots. Single color string for solid mode; array of two or more colors for gradient mode.
|
|
83
|
+
* Gradient mode samples a color per dot based on its position in the trail (tail → head).
|
|
84
|
+
* Background dots always use the first color at 5% opacity.
|
|
85
|
+
* @default '#ffffff'
|
|
86
|
+
*/
|
|
87
|
+
trailColor?: TrailColor;
|
|
88
|
+
/**
|
|
89
|
+
* Trail rendering style.
|
|
90
|
+
* - `'default'` — solid color, alpha varies by intensity.
|
|
91
|
+
* - `'gradient-static'` — each dot's color is sampled from the `trailColor` gradient. Requires `trailColor` array.
|
|
92
|
+
* - `'gradient-animated'` — same as `gradient-static` but the gradient phase shifts over time.
|
|
93
|
+
* @default 'default'
|
|
94
|
+
*/
|
|
95
|
+
trailStyle?: TrailStyle;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a dot matrix renderer for a sarmal animation on a canvas element.
|
|
99
|
+
*
|
|
100
|
+
* The renderer maps the animation's trail to a grid of dots.
|
|
101
|
+
* Each frame, the grid is cleared and rebuilt: dots near the head of the trail are bright,
|
|
102
|
+
* dots near the tail are dim, and dots with no trail activity are barely visible (5% opacity).
|
|
103
|
+
*
|
|
104
|
+
* Grid geometry is derived from `cols` and `rows`.
|
|
105
|
+
* For example, a 240x240 canvas with `cols: 32, rows: 32` produces 1024 dots with cells approximately 7.5x7.5 px each.
|
|
106
|
+
*
|
|
107
|
+
* At init, a pixel mask is computed that records which canvas pixels belong to each dot.
|
|
108
|
+
* Each frame, RGBA values are written directly into a typed array (one entry per lit pixel)
|
|
109
|
+
* and flushed to the canvas with a single `ctx.putImageData` call.
|
|
110
|
+
* Frame cost is flat regardless of how many dots are lit or what grid size is used.
|
|
111
|
+
*
|
|
112
|
+
* @param canvas - The canvas element to draw into.
|
|
113
|
+
* Its `width` and `height` HTML attributes determine the rendering area.
|
|
114
|
+
* CSS display size is not read.
|
|
115
|
+
* @param curveDef - The curve to animate.
|
|
116
|
+
* @param options - Optional configuration for grid size, color, roundness, and lifecycle.
|
|
117
|
+
* @returns A `SarmalInstance` with the standard `play` / `pause` / `destroy` / `morphTo` interface.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { createSarmalDotMatrix, curves } from '@sarmal/core'
|
|
122
|
+
*
|
|
123
|
+
* const instance = createSarmalDotMatrix(canvas, curves.lissajous43, {
|
|
124
|
+
* cols: 32,
|
|
125
|
+
* rows: 32,
|
|
126
|
+
* trailColor: '#2dd4bf',
|
|
127
|
+
* })
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
declare function createSarmalDotMatrix(canvas: HTMLCanvasElement, curveDef: CurveDef, options?: DotMatrixSarmalOptions): SarmalInstance<DotMatrixRuntimeRenderOptions>;
|
|
131
|
+
|
|
56
132
|
declare function createEngine(curveDef: CurveDef, trailLength?: number): Engine;
|
|
57
133
|
|
|
58
134
|
/**
|
|
@@ -119,4 +195,4 @@ declare function drawCurve(points: Array<ControlPoint>, opts?: {
|
|
|
119
195
|
*/
|
|
120
196
|
declare function createSarmal(canvas: HTMLCanvasElement, curveDef: CurveDef, options?: SarmalOptions): SarmalInstance;
|
|
121
197
|
|
|
122
|
-
export { BaseRendererOptions, CurveDef, Engine, Point, RendererOptions, type SVGRendererOptions, type SVGSarmalOptions, SarmalInstance, SarmalOptions, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, drawCurve, evaluateCatmullRom };
|
|
198
|
+
export { BaseRendererOptions, CurveDef, DotMatrixRuntimeRenderOptions, type DotMatrixSarmalOptions, Engine, Point, RendererOptions, type SVGRendererOptions, type SVGSarmalOptions, SarmalInstance, SarmalOptions, TrailColor, TrailStyle, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalDotMatrix, createSarmalSVG, drawCurve, evaluateCatmullRom };
|
package/dist/index.js
CHANGED
|
@@ -518,6 +518,7 @@ function getPaletteColor(palette, position, timeOffset = 0) {
|
|
|
518
518
|
return lerpOklab(c1, c2, t);
|
|
519
519
|
}
|
|
520
520
|
var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
|
|
521
|
+
var BASE_RENDER_OPTION_KEYS = /* @__PURE__ */ new Set(["trailColor", "trailStyle"]);
|
|
521
522
|
var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
522
523
|
"trailColor",
|
|
523
524
|
"headColor",
|
|
@@ -526,6 +527,19 @@ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
526
527
|
"headRadius",
|
|
527
528
|
"trailWidth"
|
|
528
529
|
]);
|
|
530
|
+
function validateBaseRenderOptions(partial) {
|
|
531
|
+
for (const key of Object.keys(partial)) {
|
|
532
|
+
if (!BASE_RENDER_OPTION_KEYS.has(key)) {
|
|
533
|
+
throw new TypeError(`[sarmal] setRenderOptions: unsupported key "${key}" for this renderer`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (partial.trailColor !== void 0) {
|
|
537
|
+
assertTrailColor(partial.trailColor);
|
|
538
|
+
}
|
|
539
|
+
if (partial.trailStyle !== void 0) {
|
|
540
|
+
assertTrailStyle(partial.trailStyle);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
529
543
|
function validateRenderOptions(partial) {
|
|
530
544
|
for (const key of Object.keys(partial)) {
|
|
531
545
|
if (!RENDER_OPTION_KEYS.has(key)) {
|
|
@@ -1401,6 +1415,371 @@ function createSarmalSVG(container, curveDef, options) {
|
|
|
1401
1415
|
return createSVGRenderer({ container, engine, ...rendererOpts });
|
|
1402
1416
|
}
|
|
1403
1417
|
|
|
1418
|
+
// src/renderer-dot-matrix.ts
|
|
1419
|
+
function createSarmalDotMatrix(canvas, curveDef, options) {
|
|
1420
|
+
const {
|
|
1421
|
+
cols = 32,
|
|
1422
|
+
rows = 32,
|
|
1423
|
+
roundness = 1,
|
|
1424
|
+
trailLength: trailLengthOpt,
|
|
1425
|
+
trailColor: initialColor = "#ffffff",
|
|
1426
|
+
trailStyle: initialTrailStyle = "default",
|
|
1427
|
+
autoStart = true,
|
|
1428
|
+
pauseOnHidden: pauseOnHiddenOpt = true,
|
|
1429
|
+
initialPhase
|
|
1430
|
+
} = options ?? {};
|
|
1431
|
+
const trailLength = trailLengthOpt ?? cols * 3;
|
|
1432
|
+
const engine = createEngine(curveDef, trailLength);
|
|
1433
|
+
if (!canvas.getContext("2d")) {
|
|
1434
|
+
throw new Error("[sarmal] Could not get 2d context from canvas");
|
|
1435
|
+
}
|
|
1436
|
+
const ctx = canvas.getContext("2d");
|
|
1437
|
+
const W = canvas.width;
|
|
1438
|
+
const H = canvas.height;
|
|
1439
|
+
const cellW = W / cols;
|
|
1440
|
+
const cellH = H / rows;
|
|
1441
|
+
const dotR = Math.min(cellW, cellH) * 0.36;
|
|
1442
|
+
let gradientRgb;
|
|
1443
|
+
if (Array.isArray(initialColor)) {
|
|
1444
|
+
validateBaseRenderOptions({ trailColor: initialColor });
|
|
1445
|
+
gradientRgb = initialColor.map(colorToRgb);
|
|
1446
|
+
} else {
|
|
1447
|
+
gradientRgb = null;
|
|
1448
|
+
}
|
|
1449
|
+
let colorRgb = gradientRgb ? gradientRgb[0] : colorToRgb(initialColor);
|
|
1450
|
+
let currentTrailStyle = initialTrailStyle;
|
|
1451
|
+
let animTime = 0;
|
|
1452
|
+
const ANIM_PERIOD = 6;
|
|
1453
|
+
const grid = new Float32Array(cols * rows);
|
|
1454
|
+
let scale = 1;
|
|
1455
|
+
let offsetX = 0;
|
|
1456
|
+
let offsetY = 0;
|
|
1457
|
+
let pixelMaskStarts = new Uint32Array(0);
|
|
1458
|
+
let pixelMaskLengths = new Uint32Array(0);
|
|
1459
|
+
let pixelMaskIndices = new Uint32Array(0);
|
|
1460
|
+
let pixelMaskCoverages = new Float32Array(0);
|
|
1461
|
+
let bgImageData = null;
|
|
1462
|
+
let frameImageData = null;
|
|
1463
|
+
let animationId = null;
|
|
1464
|
+
let lastTime = 0;
|
|
1465
|
+
let pausedByVisibility = false;
|
|
1466
|
+
let morphResolve = null;
|
|
1467
|
+
let morphReject = null;
|
|
1468
|
+
let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
|
|
1469
|
+
let morphProgress = 0;
|
|
1470
|
+
function computePixelMask() {
|
|
1471
|
+
const starts = new Uint32Array(cols * rows);
|
|
1472
|
+
const lengths = new Uint32Array(cols * rows);
|
|
1473
|
+
const allIndices = [];
|
|
1474
|
+
const allCoverages = [];
|
|
1475
|
+
const cornerR = roundness * dotR;
|
|
1476
|
+
const cornerR2 = cornerR * cornerR;
|
|
1477
|
+
const SSAA = 4;
|
|
1478
|
+
const SSAA2 = SSAA * SSAA;
|
|
1479
|
+
for (let row = 0; row < rows; row++) {
|
|
1480
|
+
for (let col = 0; col < cols; col++) {
|
|
1481
|
+
const dotIdx = row * cols + col;
|
|
1482
|
+
const cx = (col + 0.5) * cellW;
|
|
1483
|
+
const cy = (row + 0.5) * cellH;
|
|
1484
|
+
const x0 = Math.max(0, Math.floor(cx - dotR - 1));
|
|
1485
|
+
const x1 = Math.min(W - 1, Math.ceil(cx + dotR + 1));
|
|
1486
|
+
const y0 = Math.max(0, Math.floor(cy - dotR - 1));
|
|
1487
|
+
const y1 = Math.min(H - 1, Math.ceil(cy + dotR + 1));
|
|
1488
|
+
starts[dotIdx] = allIndices.length;
|
|
1489
|
+
let count = 0;
|
|
1490
|
+
for (let py = y0; py <= y1; py++) {
|
|
1491
|
+
for (let px = x0; px <= x1; px++) {
|
|
1492
|
+
let hits = 0;
|
|
1493
|
+
for (let sy = 0; sy < SSAA; sy++) {
|
|
1494
|
+
const spyCenter = py + (sy + 0.5) / SSAA;
|
|
1495
|
+
for (let sx = 0; sx < SSAA; sx++) {
|
|
1496
|
+
const spxCenter = px + (sx + 0.5) / SSAA;
|
|
1497
|
+
const dx = Math.max(Math.abs(spxCenter - cx) - (dotR - cornerR), 0);
|
|
1498
|
+
const dy = Math.max(Math.abs(spyCenter - cy) - (dotR - cornerR), 0);
|
|
1499
|
+
if (dx * dx + dy * dy <= cornerR2) {
|
|
1500
|
+
hits++;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (hits > 0) {
|
|
1505
|
+
allIndices.push((py * W + px) * 4);
|
|
1506
|
+
allCoverages.push(hits / SSAA2);
|
|
1507
|
+
count++;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
lengths[dotIdx] = count;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
pixelMaskStarts = starts;
|
|
1515
|
+
pixelMaskLengths = lengths;
|
|
1516
|
+
pixelMaskIndices = new Uint32Array(allIndices);
|
|
1517
|
+
pixelMaskCoverages = new Float32Array(allCoverages);
|
|
1518
|
+
}
|
|
1519
|
+
function buildBgImageData() {
|
|
1520
|
+
bgImageData = new ImageData(W, H);
|
|
1521
|
+
const bg = gradientRgb ? gradientRgb[0] : colorRgb;
|
|
1522
|
+
const baseAlpha = 0.05 * 255;
|
|
1523
|
+
const { data } = bgImageData;
|
|
1524
|
+
const n = cols * rows;
|
|
1525
|
+
for (let dotIdx = 0; dotIdx < n; dotIdx++) {
|
|
1526
|
+
const start = pixelMaskStarts[dotIdx];
|
|
1527
|
+
const len = pixelMaskLengths[dotIdx];
|
|
1528
|
+
for (let k = 0; k < len; k++) {
|
|
1529
|
+
const px = pixelMaskIndices[start + k];
|
|
1530
|
+
const coverage = pixelMaskCoverages[start + k];
|
|
1531
|
+
data[px] = bg.r;
|
|
1532
|
+
data[px + 1] = bg.g;
|
|
1533
|
+
data[px + 2] = bg.b;
|
|
1534
|
+
data[px + 3] = Math.round(baseAlpha * coverage);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
function sampleGradientRgb(stops, t) {
|
|
1539
|
+
const n = stops.length;
|
|
1540
|
+
const scaled = Math.max(0, Math.min(1, t)) * (n - 1);
|
|
1541
|
+
const i = Math.min(Math.floor(scaled), n - 2);
|
|
1542
|
+
const a = stops[i];
|
|
1543
|
+
const bStop = stops[i + 1];
|
|
1544
|
+
const mix = scaled - i;
|
|
1545
|
+
return {
|
|
1546
|
+
r: Math.round(a.r + (bStop.r - a.r) * mix),
|
|
1547
|
+
g: Math.round(a.g + (bStop.g - a.g) * mix),
|
|
1548
|
+
b: Math.round(a.b + (bStop.b - a.b) * mix)
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function calculateBoundaries(skel) {
|
|
1552
|
+
const b = computeBoundaries(skel, W, H);
|
|
1553
|
+
if (b) {
|
|
1554
|
+
scale = b.scale;
|
|
1555
|
+
offsetX = b.offsetX;
|
|
1556
|
+
offsetY = b.offsetY;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
function mapPt(x, y) {
|
|
1560
|
+
const px = x * scale + offsetX;
|
|
1561
|
+
const py = y * scale + offsetY;
|
|
1562
|
+
return [
|
|
1563
|
+
Math.max(0, Math.min(cols - 1, Math.round(px / W * (cols - 1)))),
|
|
1564
|
+
Math.max(0, Math.min(rows - 1, Math.round(py / H * (rows - 1))))
|
|
1565
|
+
];
|
|
1566
|
+
}
|
|
1567
|
+
function stamp(c, r, intensity) {
|
|
1568
|
+
const idx = r * cols + c;
|
|
1569
|
+
if (intensity > grid[idx]) {
|
|
1570
|
+
grid[idx] = intensity;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
function buildGrid(deltaTime) {
|
|
1574
|
+
const trail = engine.tick(deltaTime);
|
|
1575
|
+
const count = engine.trailCount;
|
|
1576
|
+
grid.fill(0);
|
|
1577
|
+
for (let i = 0; i < count; i++) {
|
|
1578
|
+
const pt = trail[i];
|
|
1579
|
+
const intensity = (i + 1) / count;
|
|
1580
|
+
const [c, r] = mapPt(pt.x, pt.y);
|
|
1581
|
+
stamp(c, r, intensity);
|
|
1582
|
+
if (i < count - 1) {
|
|
1583
|
+
const next = trail[i + 1];
|
|
1584
|
+
const [nc, nr] = mapPt(next.x, next.y);
|
|
1585
|
+
const steps = Math.ceil(Math.max(Math.abs(nc - c), Math.abs(nr - r))) * 2;
|
|
1586
|
+
for (let s = 1; s < steps; s++) {
|
|
1587
|
+
const t = s / steps;
|
|
1588
|
+
const ix = pt.x + (next.x - pt.x) * t;
|
|
1589
|
+
const iy = pt.y + (next.y - pt.y) * t;
|
|
1590
|
+
const ii = intensity + 1 / count * t;
|
|
1591
|
+
const [ic, ir] = mapPt(ix, iy);
|
|
1592
|
+
stamp(ic, ir, ii);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
function draw() {
|
|
1598
|
+
if (!bgImageData || !frameImageData) {
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
frameImageData.data.set(bgImageData.data);
|
|
1602
|
+
const { data } = frameImageData;
|
|
1603
|
+
const sineOffset = currentTrailStyle === "gradient-animated" ? 0.15 * Math.sin(animTime / ANIM_PERIOD * 2 * Math.PI) : 0;
|
|
1604
|
+
const n = cols * rows;
|
|
1605
|
+
for (let dotIdx = 0; dotIdx < n; dotIdx++) {
|
|
1606
|
+
const intensity = grid[dotIdx];
|
|
1607
|
+
if (intensity <= 0) {
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
let r, g, b;
|
|
1611
|
+
if (gradientRgb !== null) {
|
|
1612
|
+
const t = Math.max(0, Math.min(1, intensity + sineOffset));
|
|
1613
|
+
({ r, g, b } = sampleGradientRgb(gradientRgb, t));
|
|
1614
|
+
} else {
|
|
1615
|
+
({ r, g, b } = colorRgb);
|
|
1616
|
+
}
|
|
1617
|
+
const baseA = (0.08 + intensity * 0.92) * 255;
|
|
1618
|
+
const start = pixelMaskStarts[dotIdx];
|
|
1619
|
+
const len = pixelMaskLengths[dotIdx];
|
|
1620
|
+
for (let k = 0; k < len; k++) {
|
|
1621
|
+
const px = pixelMaskIndices[start + k];
|
|
1622
|
+
const coverage = pixelMaskCoverages[start + k];
|
|
1623
|
+
data[px] = r;
|
|
1624
|
+
data[px + 1] = g;
|
|
1625
|
+
data[px + 2] = b;
|
|
1626
|
+
data[px + 3] = Math.round(baseA * coverage);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
ctx.putImageData(frameImageData, 0, 0);
|
|
1630
|
+
}
|
|
1631
|
+
function renderFrame(deltaTime) {
|
|
1632
|
+
if (engine.morphAlpha !== null) {
|
|
1633
|
+
morphProgress = Math.min(1, morphProgress + deltaTime / (morphDurationMs / 1e3));
|
|
1634
|
+
engine.setMorphAlpha(morphProgress);
|
|
1635
|
+
calculateBoundaries(engine.getSarmalSkeleton());
|
|
1636
|
+
if (morphProgress >= 1) {
|
|
1637
|
+
engine.completeMorph();
|
|
1638
|
+
morphResolve?.();
|
|
1639
|
+
morphResolve = null;
|
|
1640
|
+
morphReject = null;
|
|
1641
|
+
morphProgress = 0;
|
|
1642
|
+
calculateBoundaries(engine.getSarmalSkeleton());
|
|
1643
|
+
}
|
|
1644
|
+
} else if (engine.isLiveSkeleton) {
|
|
1645
|
+
calculateBoundaries(engine.getSarmalSkeleton());
|
|
1646
|
+
}
|
|
1647
|
+
if (currentTrailStyle === "gradient-animated") {
|
|
1648
|
+
animTime += deltaTime;
|
|
1649
|
+
}
|
|
1650
|
+
buildGrid(deltaTime);
|
|
1651
|
+
draw();
|
|
1652
|
+
}
|
|
1653
|
+
function loop(timestamp = performance.now()) {
|
|
1654
|
+
const deltaTime = Math.min((timestamp - lastTime) / 1e3, 1 / 30);
|
|
1655
|
+
lastTime = timestamp;
|
|
1656
|
+
renderFrame(deltaTime);
|
|
1657
|
+
animationId = requestAnimationFrame(loop);
|
|
1658
|
+
}
|
|
1659
|
+
calculateBoundaries(engine.getSarmalSkeleton());
|
|
1660
|
+
computePixelMask();
|
|
1661
|
+
frameImageData = new ImageData(W, H);
|
|
1662
|
+
buildBgImageData();
|
|
1663
|
+
if (initialPhase !== void 0) {
|
|
1664
|
+
engine.seek(initialPhase);
|
|
1665
|
+
}
|
|
1666
|
+
renderFrame(0);
|
|
1667
|
+
const instance = {
|
|
1668
|
+
/** Starts the animation loop. Does nothing if already running. */
|
|
1669
|
+
play() {
|
|
1670
|
+
if (animationId !== null) return;
|
|
1671
|
+
lastTime = performance.now();
|
|
1672
|
+
loop();
|
|
1673
|
+
},
|
|
1674
|
+
/** Pauses the animation loop. Preserves current trail state. */
|
|
1675
|
+
pause() {
|
|
1676
|
+
if (animationId === null) return;
|
|
1677
|
+
cancelAnimationFrame(animationId);
|
|
1678
|
+
animationId = null;
|
|
1679
|
+
engine.cancelSpeedTransition();
|
|
1680
|
+
},
|
|
1681
|
+
/** Resets the animation to the start of the curve and clears the grid. */
|
|
1682
|
+
reset() {
|
|
1683
|
+
engine.reset();
|
|
1684
|
+
grid.fill(0);
|
|
1685
|
+
},
|
|
1686
|
+
/** Stops the animation and removes all event listeners. */
|
|
1687
|
+
destroy() {
|
|
1688
|
+
if (animationId !== null) {
|
|
1689
|
+
cancelAnimationFrame(animationId);
|
|
1690
|
+
animationId = null;
|
|
1691
|
+
}
|
|
1692
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1693
|
+
if (morphReject !== null) {
|
|
1694
|
+
morphReject(new Error("[sarmal] Instance destroyed during morph"));
|
|
1695
|
+
morphResolve = null;
|
|
1696
|
+
morphReject = null;
|
|
1697
|
+
}
|
|
1698
|
+
},
|
|
1699
|
+
...enginePassthroughs(engine),
|
|
1700
|
+
/**
|
|
1701
|
+
* Smoothly transitions from the current curve to `target`.
|
|
1702
|
+
* If a morph is already in progress, it is snapped to completion before the new one starts.
|
|
1703
|
+
* @returns A Promise that resolves when the transition finishes.
|
|
1704
|
+
*/
|
|
1705
|
+
morphTo(target, opts) {
|
|
1706
|
+
if (morphResolve !== null) {
|
|
1707
|
+
engine.completeMorph();
|
|
1708
|
+
morphResolve();
|
|
1709
|
+
morphResolve = null;
|
|
1710
|
+
morphReject = null;
|
|
1711
|
+
morphProgress = 0;
|
|
1712
|
+
}
|
|
1713
|
+
morphDurationMs = opts?.duration ?? DEFAULT_MORPH_DURATION_MS;
|
|
1714
|
+
morphProgress = 0;
|
|
1715
|
+
engine.startMorph(target, opts?.morphStrategy);
|
|
1716
|
+
return new Promise((resolve, reject) => {
|
|
1717
|
+
morphResolve = resolve;
|
|
1718
|
+
morphReject = reject;
|
|
1719
|
+
});
|
|
1720
|
+
},
|
|
1721
|
+
/**
|
|
1722
|
+
* Updates render options on a live instance without stopping the animation.
|
|
1723
|
+
*
|
|
1724
|
+
* Supported: `trailColor` and `trailStyle`.
|
|
1725
|
+
* ! Unsupported fields (`headColor`, `skeletonColor`, `headRadius`, `trailWidth`) throw.
|
|
1726
|
+
* ! Validation fails the entire call if any field is invalid, leaving options unchanged.
|
|
1727
|
+
*/
|
|
1728
|
+
setRenderOptions(partial) {
|
|
1729
|
+
validateBaseRenderOptions(partial);
|
|
1730
|
+
let needsRebuildBg = false;
|
|
1731
|
+
if (partial.trailColor !== void 0) {
|
|
1732
|
+
if (Array.isArray(partial.trailColor)) {
|
|
1733
|
+
gradientRgb = partial.trailColor.map(colorToRgb);
|
|
1734
|
+
colorRgb = gradientRgb[0];
|
|
1735
|
+
} else {
|
|
1736
|
+
gradientRgb = null;
|
|
1737
|
+
colorRgb = colorToRgb(partial.trailColor);
|
|
1738
|
+
}
|
|
1739
|
+
needsRebuildBg = true;
|
|
1740
|
+
}
|
|
1741
|
+
if (partial.trailStyle !== void 0) {
|
|
1742
|
+
currentTrailStyle = partial.trailStyle;
|
|
1743
|
+
if (currentTrailStyle === "default") {
|
|
1744
|
+
animTime = 0;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
if (needsRebuildBg) {
|
|
1748
|
+
buildBgImageData();
|
|
1749
|
+
}
|
|
1750
|
+
if (currentTrailStyle !== "default" && gradientRgb === null) {
|
|
1751
|
+
console.warn(
|
|
1752
|
+
"[sarmal] dot matrix: gradient trailStyle has no effect without a trailColor array"
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1757
|
+
function handleVisibilityChange() {
|
|
1758
|
+
if (document.hidden) {
|
|
1759
|
+
if (animationId !== null) {
|
|
1760
|
+
instance.pause();
|
|
1761
|
+
pausedByVisibility = true;
|
|
1762
|
+
}
|
|
1763
|
+
} else {
|
|
1764
|
+
if (pausedByVisibility) {
|
|
1765
|
+
pausedByVisibility = false;
|
|
1766
|
+
instance.play();
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
if (pauseOnHiddenOpt) {
|
|
1771
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1772
|
+
}
|
|
1773
|
+
const shouldAutoStart = autoStart !== false;
|
|
1774
|
+
const actuallyAutoStart = shouldAutoStart && !(pauseOnHiddenOpt && document.hidden);
|
|
1775
|
+
if (actuallyAutoStart) {
|
|
1776
|
+
instance.play();
|
|
1777
|
+
} else if (shouldAutoStart) {
|
|
1778
|
+
pausedByVisibility = true;
|
|
1779
|
+
}
|
|
1780
|
+
return instance;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1404
1783
|
// src/catmull-rom.ts
|
|
1405
1784
|
var PERIOD = 2 * Math.PI;
|
|
1406
1785
|
function catmullRom1D(p0, p1, p2, p3, u) {
|
|
@@ -1730,6 +2109,6 @@ function createSarmal(canvas, curveDef, options) {
|
|
|
1730
2109
|
return createRenderer({ canvas, engine, ...rendererOpts });
|
|
1731
2110
|
}
|
|
1732
2111
|
|
|
1733
|
-
export { artemis2, astroid, computeBoundaries, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, curves, deltoid, drawCurve, epicycloid3, epitrochoid7, evaluateCatmullRom, lame, lissajous32, lissajous43, palettes, rose3, rose5 };
|
|
2112
|
+
export { artemis2, astroid, computeBoundaries, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalDotMatrix, createSarmalSVG, curves, deltoid, drawCurve, epicycloid3, epitrochoid7, evaluateCatmullRom, lame, lissajous32, lissajous43, palettes, rose3, rose5 };
|
|
1734
2113
|
//# sourceMappingURL=index.js.map
|
|
1735
2114
|
//# sourceMappingURL=index.js.map
|