@sarmal/core 0.7.1 → 0.8.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/LICENSE +9 -0
- package/README.md +89 -3
- package/dist/auto-init.cjs +139 -89
- package/dist/auto-init.cjs.map +1 -1
- package/dist/auto-init.js +138 -88
- package/dist/auto-init.js.map +1 -1
- package/dist/index.cjs +175 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +174 -145
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
- package/dist/auto-init.d.cts +0 -2
- package/dist/auto-init.d.ts +0 -2
- package/dist/index.d.cts +0 -262
- package/dist/index.d.ts +0 -262
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Sarmal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,7 +1,93 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sarmal/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>Parametric curve animations for loading/thinking indicators</strong>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://sarmal.art" target="_blank">Live Demo at sarmal.art</a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
**@sarmal/core** is a lightweight library for rendering elegant parametric curve animations.
|
|
14
|
+
|
|
15
|
+
The animations can be used anywhere you want. Use it for loading spinners, progress indicators, or to indicate that your very special AI model is _thinking_, up to you.
|
|
16
|
+
|
|
17
|
+
In web applications and perhaps also in the terminal maybe if possible!
|
|
18
|
+
|
|
19
|
+
- **Canvas & SVG renderers**: choose one or the other, but why not both?
|
|
20
|
+
- **standard curves**: default cliche curves any LLM can generate in seconds, from classic spirals to custom parametric paths
|
|
21
|
+
- **TIME CONTROL**: programmatic time stepping, seeking, and trail effects
|
|
22
|
+
- **Zero dependencies**: tiny bundle, quick to get started
|
|
23
|
+
- **TypeScript-first**: because who would build anyhing complex in pure JS?!
|
|
24
|
+
- full type safety, but no assurance it will work in runtime!
|
|
25
|
+
|
|
26
|
+
## Install
|
|
4
27
|
|
|
5
28
|
```bash
|
|
6
|
-
npm
|
|
29
|
+
npm install @sarmal/core
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or use directly from CDN:
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<script type="module">
|
|
36
|
+
import { createSarmal, curves } from "https://cdn.jsdelivr.net/npm/@sarmal/core/+esm";
|
|
37
|
+
// your code here
|
|
38
|
+
</script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
import { createSarmal, curves } from "@sarmal/core";
|
|
45
|
+
|
|
46
|
+
const canvas = document.getElementById("my-canvas");
|
|
47
|
+
const sarmal = createSarmal(canvas, curves.artemis2, {
|
|
48
|
+
trailLength: 30,
|
|
49
|
+
strokeStyle: "#00ffaa",
|
|
50
|
+
lineWidth: 2,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
sarmal.start();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or with **auto-init** without having to write any JS:
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<script src="https://cdn.jsdelivr.net/npm/@sarmal/core/dist/auto-init.js"></script>
|
|
60
|
+
<canvas data-sarmal="artemis2" width="200" height="200"></canvas>
|
|
7
61
|
```
|
|
62
|
+
|
|
63
|
+
## Standard Curves
|
|
64
|
+
|
|
65
|
+
| Name | Description |
|
|
66
|
+
| -------------- | ------------------------------------------ |
|
|
67
|
+
| `artemis2` | Artemis II free-return lunar trajectory |
|
|
68
|
+
| `epitrochoid7` | 7-lobed epitrochoid with dynamic variation |
|
|
69
|
+
| `astroid` | 4-cusped astroid |
|
|
70
|
+
| `deltoid` | 3-cusped deltoid |
|
|
71
|
+
| `rose5` | 5-petal rose curve |
|
|
72
|
+
| `rose3` | 3-petal rose curve |
|
|
73
|
+
| `lissajous32` | Lissajous 3:2 with live skeleton |
|
|
74
|
+
| `lissajous43` | Lissajous 4:3 with live skeleton |
|
|
75
|
+
| `epicycloid3` | 3-cusped epicycloid |
|
|
76
|
+
| `lame` | Lamé curve with live skeleton |
|
|
77
|
+
|
|
78
|
+
## Documentation
|
|
79
|
+
|
|
80
|
+
Full API reference, examples, SVG renderer usage, engine time control (`seek`, `seekWithTrail`), custom curve definitions, and framework guides are available at [sarmal.art/docs](https://sarmal.art/docs)
|
|
81
|
+
|
|
82
|
+
## Inspiration
|
|
83
|
+
|
|
84
|
+
Inspired by [@bbssppllvv's tweet](https://x.com/bbssppllvv/status/2038718410318659763)
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT © [Alper Halil](https://aktasalper.com)
|
|
89
|
+
|
|
90
|
+
## Links
|
|
91
|
+
|
|
92
|
+
- [Homepage](https://sarmal.art): See all curves in action
|
|
93
|
+
- [npm](https://www.npmjs.com/package/@sarmal/core): Package registry
|
package/dist/auto-init.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
// src/engine.ts
|
|
4
4
|
var TWO_PI = Math.PI * 2;
|
|
@@ -51,7 +51,7 @@ function resolveCurve(curveDef) {
|
|
|
51
51
|
period: curveDef.period ?? TWO_PI,
|
|
52
52
|
speed: curveDef.speed ?? 1,
|
|
53
53
|
skeleton: curveDef.skeleton,
|
|
54
|
-
skeletonFn: curveDef.skeletonFn
|
|
54
|
+
skeletonFn: curveDef.skeletonFn,
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
function createEngine(curveDef, trailLength = 120) {
|
|
@@ -77,7 +77,7 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
77
77
|
actualTime += deltaTime;
|
|
78
78
|
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
79
79
|
const a = curve.fn(t, actualTime, {});
|
|
80
|
-
const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
|
|
80
|
+
const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
|
|
81
81
|
const b = morphCurveB.fn(tB, actualTime, {});
|
|
82
82
|
trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
|
|
83
83
|
} else {
|
|
@@ -101,14 +101,14 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
101
101
|
trail.clear();
|
|
102
102
|
},
|
|
103
103
|
seek(newT, { clearTrail = false } = {}) {
|
|
104
|
-
t = (newT % curve.period + curve.period) % curve.period;
|
|
104
|
+
t = ((newT % curve.period) + curve.period) % curve.period;
|
|
105
105
|
if (clearTrail) {
|
|
106
106
|
trail.clear();
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
109
|
seekWithTrail(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
|
|
110
110
|
const advance = curve.speed * step;
|
|
111
|
-
const target = (targetT % curve.period + curve.period) % curve.period;
|
|
111
|
+
const target = ((targetT % curve.period) + curve.period) % curve.period;
|
|
112
112
|
const targetTime = target / curve.speed;
|
|
113
113
|
t = target;
|
|
114
114
|
actualTime = targetTime;
|
|
@@ -134,13 +134,16 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
134
134
|
...frozenB,
|
|
135
135
|
fn: (sampleT, time, params) => {
|
|
136
136
|
const a = frozenA.fn(sampleT, time, params);
|
|
137
|
-
const tB =
|
|
137
|
+
const tB =
|
|
138
|
+
frozenStrategy === "normalized"
|
|
139
|
+
? (sampleT / frozenA.period) * frozenB.period
|
|
140
|
+
: sampleT;
|
|
138
141
|
const b = frozenB.fn(tB, time, params);
|
|
139
142
|
return {
|
|
140
143
|
x: a.x + (b.x - a.x) * frozenAlpha,
|
|
141
|
-
y: a.y + (b.y - a.y) * frozenAlpha
|
|
144
|
+
y: a.y + (b.y - a.y) * frozenAlpha,
|
|
142
145
|
};
|
|
143
|
-
}
|
|
146
|
+
},
|
|
144
147
|
};
|
|
145
148
|
}
|
|
146
149
|
_morphStrategy = strategy;
|
|
@@ -153,7 +156,7 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
153
156
|
completeMorph() {
|
|
154
157
|
if (morphCurveB !== null) {
|
|
155
158
|
if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
|
|
156
|
-
t = t / curve.period * morphCurveB.period;
|
|
159
|
+
t = (t / curve.period) * morphCurveB.period;
|
|
157
160
|
}
|
|
158
161
|
curve = morphCurveB;
|
|
159
162
|
}
|
|
@@ -165,43 +168,74 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
165
168
|
const points = new Array(steps);
|
|
166
169
|
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
167
170
|
for (let i = 0; i < steps; i++) {
|
|
168
|
-
const sampleT = i / (steps - 1) * curve.period;
|
|
171
|
+
const sampleT = (i / (steps - 1)) * curve.period;
|
|
169
172
|
const a = sampleSkeleton(curve, sampleT);
|
|
170
|
-
const tB =
|
|
173
|
+
const tB =
|
|
174
|
+
_morphStrategy === "normalized"
|
|
175
|
+
? (sampleT / curve.period) * morphCurveB.period
|
|
176
|
+
: sampleT;
|
|
171
177
|
const b = sampleSkeleton(morphCurveB, tB);
|
|
172
178
|
points[i] = {
|
|
173
179
|
x: a.x + (b.x - a.x) * _morphAlpha,
|
|
174
|
-
y: a.y + (b.y - a.y) * _morphAlpha
|
|
180
|
+
y: a.y + (b.y - a.y) * _morphAlpha,
|
|
175
181
|
};
|
|
176
182
|
}
|
|
177
183
|
return points;
|
|
178
184
|
}
|
|
179
185
|
for (let i = 0; i < steps; i++) {
|
|
180
|
-
const sampleT = i / (steps - 1) * curve.period;
|
|
186
|
+
const sampleT = (i / (steps - 1)) * curve.period;
|
|
181
187
|
points[i] = sampleSkeleton(curve, sampleT);
|
|
182
188
|
}
|
|
183
189
|
return points;
|
|
184
|
-
}
|
|
190
|
+
},
|
|
185
191
|
};
|
|
186
192
|
}
|
|
187
193
|
|
|
188
194
|
// src/renderer.ts
|
|
189
195
|
var DEFAULT_MORPH_DURATION_MS = 300;
|
|
190
196
|
var DEFAULT_HEAD_RADIUS = 4;
|
|
191
|
-
var DEFAULT_GLOW_SIZE = 20;
|
|
192
197
|
var DEFAULT_SKELETON_COLOR = "#ffffff";
|
|
193
198
|
var DEFAULT_SKELETON_OPACITY = 0.15;
|
|
194
199
|
var FIT_PADDING = 0.1;
|
|
195
|
-
var TRAIL_BATCH_SIZE = 20;
|
|
196
200
|
var TRAIL_FADE_CURVE = 1.5;
|
|
197
201
|
var TRAIL_MAX_OPACITY = 0.88;
|
|
198
202
|
var TRAIL_MIN_WIDTH = 0.5;
|
|
199
203
|
var TRAIL_MAX_WIDTH = 2.5;
|
|
200
|
-
var GLOW_INNER_EDGE = 0.4;
|
|
201
|
-
var GLOW_FALLOFF_OPACITY = 0.53;
|
|
202
204
|
function hexToRgbComponents(hex) {
|
|
203
205
|
const n = parseInt(hex.slice(1), 16);
|
|
204
|
-
return `${n >> 16},${n >> 8 & 255},${n & 255}`;
|
|
206
|
+
return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
|
|
207
|
+
}
|
|
208
|
+
function computeTangent(trail, i) {
|
|
209
|
+
const count = trail.length;
|
|
210
|
+
if (count < 2) {
|
|
211
|
+
return { x: 1, y: 0 };
|
|
212
|
+
}
|
|
213
|
+
if (i === 0) {
|
|
214
|
+
const dx2 = trail[1].x - trail[0].x;
|
|
215
|
+
const dy2 = trail[1].y - trail[0].y;
|
|
216
|
+
const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
|
|
217
|
+
return { x: dx2 / len2, y: dy2 / len2 };
|
|
218
|
+
}
|
|
219
|
+
if (i === count - 1) {
|
|
220
|
+
const dx2 = trail[count - 1].x - trail[count - 2].x;
|
|
221
|
+
const dy2 = trail[count - 1].y - trail[count - 2].y;
|
|
222
|
+
const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
|
|
223
|
+
return { x: dx2 / len2, y: dy2 / len2 };
|
|
224
|
+
}
|
|
225
|
+
const dx = trail[i + 1].x - trail[i - 1].x;
|
|
226
|
+
const dy = trail[i + 1].y - trail[i - 1].y;
|
|
227
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
228
|
+
return { x: dx / len, y: dy / len };
|
|
229
|
+
}
|
|
230
|
+
function computeNormal(trail, i) {
|
|
231
|
+
const tangent = computeTangent(trail, i);
|
|
232
|
+
return { x: -tangent.y, y: tangent.x };
|
|
233
|
+
}
|
|
234
|
+
function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
|
|
235
|
+
target.style.width = `${logicalWidth}px`;
|
|
236
|
+
target.style.height = `${logicalHeight}px`;
|
|
237
|
+
target.width = logicalWidth * dpr;
|
|
238
|
+
target.height = logicalHeight * dpr;
|
|
205
239
|
}
|
|
206
240
|
function createRenderer(options) {
|
|
207
241
|
const canvas = options.canvas;
|
|
@@ -215,10 +249,19 @@ function createRenderer(options) {
|
|
|
215
249
|
trailColor: options.trailColor ?? "#ffffff",
|
|
216
250
|
headColor: options.headColor ?? "#ffffff",
|
|
217
251
|
headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
|
|
218
|
-
glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE
|
|
219
252
|
};
|
|
220
253
|
const trailRgb = hexToRgbComponents(opts.trailColor);
|
|
221
|
-
const
|
|
254
|
+
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
255
|
+
function setupCanvas() {
|
|
256
|
+
const rect = canvas.getBoundingClientRect();
|
|
257
|
+
const lw = rect.width || 200;
|
|
258
|
+
const lh = rect.height || 200;
|
|
259
|
+
applyDprSizing(canvas, lw, lh, dpr);
|
|
260
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
261
|
+
}
|
|
262
|
+
setupCanvas();
|
|
263
|
+
let logicalWidth = canvas.width / dpr;
|
|
264
|
+
let logicalHeight = canvas.height / dpr;
|
|
222
265
|
let skeleton = [];
|
|
223
266
|
let skeletonCanvas = null;
|
|
224
267
|
let trail = [];
|
|
@@ -235,7 +278,10 @@ function createRenderer(options) {
|
|
|
235
278
|
function computeBoundaries(pts) {
|
|
236
279
|
if (pts.length === 0) return null;
|
|
237
280
|
const first = pts[0];
|
|
238
|
-
let minX = first.x,
|
|
281
|
+
let minX = first.x,
|
|
282
|
+
maxX = first.x,
|
|
283
|
+
minY = first.y,
|
|
284
|
+
maxY = first.y;
|
|
239
285
|
for (const p of pts) {
|
|
240
286
|
if (p.x < minX) minX = p.x;
|
|
241
287
|
if (p.x > maxX) maxX = p.x;
|
|
@@ -244,17 +290,15 @@ function createRenderer(options) {
|
|
|
244
290
|
}
|
|
245
291
|
const width = maxX - minX;
|
|
246
292
|
const height = maxY - minY;
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const scaleX = canvasWidth / (width * (1 + FIT_PADDING * 2));
|
|
250
|
-
const scaleY = canvasHeight / (height * (1 + FIT_PADDING * 2));
|
|
293
|
+
const scaleX = logicalWidth / (width * (1 + FIT_PADDING * 2));
|
|
294
|
+
const scaleY = logicalHeight / (height * (1 + FIT_PADDING * 2));
|
|
251
295
|
const s = Math.min(scaleX, scaleY);
|
|
252
296
|
const boundsWidth = width * s;
|
|
253
297
|
const boundsHeight = height * s;
|
|
254
298
|
return {
|
|
255
299
|
scale: s,
|
|
256
|
-
offsetX: (
|
|
257
|
-
offsetY: (
|
|
300
|
+
offsetX: (logicalWidth - boundsWidth) / 2 - minX * s,
|
|
301
|
+
offsetY: (logicalHeight - boundsHeight) / 2 - minY * s,
|
|
258
302
|
};
|
|
259
303
|
}
|
|
260
304
|
function calculateBoundaries() {
|
|
@@ -269,6 +313,7 @@ function createRenderer(options) {
|
|
|
269
313
|
if (skeleton.length < 2) return;
|
|
270
314
|
skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
|
|
271
315
|
const skeletonCtx = skeletonCanvas.getContext("2d");
|
|
316
|
+
skeletonCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
272
317
|
skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
|
|
273
318
|
skeletonCtx.lineWidth = 1.5;
|
|
274
319
|
skeletonCtx.beginPath();
|
|
@@ -314,32 +359,41 @@ function createRenderer(options) {
|
|
|
314
359
|
}
|
|
315
360
|
ctx.stroke();
|
|
316
361
|
} else if (skeletonCanvas) {
|
|
317
|
-
ctx.drawImage(skeletonCanvas, 0, 0);
|
|
362
|
+
ctx.drawImage(skeletonCanvas, 0, 0, logicalWidth, logicalHeight);
|
|
318
363
|
}
|
|
319
364
|
}
|
|
320
365
|
function drawTrail() {
|
|
321
366
|
if (trailCount < 2) {
|
|
322
367
|
return;
|
|
323
368
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const bEnd = Math.min(batchIndex + TRAIL_BATCH_SIZE, trailCount - 1);
|
|
328
|
-
const progress = (batchIndex + bEnd) / 2 / (trailCount - 1);
|
|
369
|
+
for (let i = 0; i < trailCount - 1; i++) {
|
|
370
|
+
const progress = i / (trailCount - 1);
|
|
371
|
+
const nextProgress = (i + 1) / (trailCount - 1);
|
|
329
372
|
const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
|
|
330
|
-
const
|
|
373
|
+
const width = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
|
|
374
|
+
const nextWidth = TRAIL_MIN_WIDTH + nextProgress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
|
|
375
|
+
const curr = trail[i];
|
|
376
|
+
const next = trail[i + 1];
|
|
377
|
+
const n0 = computeNormal(trail, i);
|
|
378
|
+
const n1 = computeNormal(trail, i + 1);
|
|
379
|
+
const halfW0 = width / 2;
|
|
380
|
+
const halfW1 = nextWidth / 2;
|
|
381
|
+
const l0x = curr.x * scale + offsetX + n0.x * halfW0;
|
|
382
|
+
const l0y = curr.y * scale + offsetY + n0.y * halfW0;
|
|
383
|
+
const r0x = curr.x * scale + offsetX - n0.x * halfW0;
|
|
384
|
+
const r0y = curr.y * scale + offsetY - n0.y * halfW0;
|
|
385
|
+
const l1x = next.x * scale + offsetX + n1.x * halfW1;
|
|
386
|
+
const l1y = next.y * scale + offsetY + n1.y * halfW1;
|
|
387
|
+
const r1x = next.x * scale + offsetX - n1.x * halfW1;
|
|
388
|
+
const r1y = next.y * scale + offsetY - n1.y * halfW1;
|
|
389
|
+
ctx.fillStyle = `rgba(${trailRgb},${alpha})`;
|
|
331
390
|
ctx.beginPath();
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
ctx.strokeStyle = `rgba(${trailRgb},${alpha})`;
|
|
341
|
-
ctx.lineWidth = lineWidth;
|
|
342
|
-
ctx.stroke();
|
|
391
|
+
ctx.moveTo(l0x, l0y);
|
|
392
|
+
ctx.lineTo(l1x, l1y);
|
|
393
|
+
ctx.lineTo(r1x, r1y);
|
|
394
|
+
ctx.lineTo(r0x, r0y);
|
|
395
|
+
ctx.closePath();
|
|
396
|
+
ctx.fill();
|
|
343
397
|
}
|
|
344
398
|
}
|
|
345
399
|
function drawHead() {
|
|
@@ -348,14 +402,6 @@ function createRenderer(options) {
|
|
|
348
402
|
}
|
|
349
403
|
const x = head.x * scale + offsetX;
|
|
350
404
|
const y = head.y * scale + offsetY;
|
|
351
|
-
const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);
|
|
352
|
-
gradient.addColorStop(0, opts.headColor);
|
|
353
|
-
gradient.addColorStop(GLOW_INNER_EDGE, headRgbFalloff);
|
|
354
|
-
gradient.addColorStop(1, "transparent");
|
|
355
|
-
ctx.fillStyle = gradient;
|
|
356
|
-
ctx.beginPath();
|
|
357
|
-
ctx.arc(x, y, opts.glowSize, 0, Math.PI * 2);
|
|
358
|
-
ctx.fill();
|
|
359
405
|
ctx.fillStyle = opts.headColor;
|
|
360
406
|
ctx.beginPath();
|
|
361
407
|
ctx.arc(x, y, opts.headRadius, 0, Math.PI * 2);
|
|
@@ -389,7 +435,7 @@ function createRenderer(options) {
|
|
|
389
435
|
trail = engine.tick(deltaTime);
|
|
390
436
|
trailCount = engine.trailCount;
|
|
391
437
|
head = trailCount > 0 ? trail[trailCount - 1] : null;
|
|
392
|
-
ctx.clearRect(0, 0,
|
|
438
|
+
ctx.clearRect(0, 0, logicalWidth, logicalHeight);
|
|
393
439
|
if (engine.isLiveSkeleton && engine.morphAlpha === null) {
|
|
394
440
|
skeleton = engine.getSarmalSkeleton();
|
|
395
441
|
calculateBoundaries();
|
|
@@ -449,33 +495,36 @@ function createRenderer(options) {
|
|
|
449
495
|
return new Promise((resolve) => {
|
|
450
496
|
morphResolve = resolve;
|
|
451
497
|
});
|
|
452
|
-
}
|
|
498
|
+
},
|
|
453
499
|
};
|
|
454
500
|
}
|
|
455
501
|
|
|
456
502
|
// src/curves.ts
|
|
457
503
|
var TWO_PI2 = Math.PI * 2;
|
|
458
504
|
function artemis2(t, _time, _params) {
|
|
459
|
-
const a = 0.35,
|
|
460
|
-
|
|
505
|
+
const a = 0.35,
|
|
506
|
+
b = 0.15,
|
|
507
|
+
ox = 0.175;
|
|
508
|
+
const s = Math.sin(t),
|
|
509
|
+
c = Math.cos(t);
|
|
461
510
|
const denom = 1 + s * s;
|
|
462
511
|
return {
|
|
463
|
-
x: c * (1 + a * c) / denom - ox,
|
|
464
|
-
y: s * c * (1 + b * c) / denom
|
|
512
|
+
x: (c * (1 + a * c)) / denom - ox,
|
|
513
|
+
y: (s * c * (1 + b * c)) / denom,
|
|
465
514
|
};
|
|
466
515
|
}
|
|
467
516
|
function epitrochoid7(t, _time, _params) {
|
|
468
517
|
const d = 1 + 0.55 * Math.sin(t * 0.5);
|
|
469
518
|
return {
|
|
470
519
|
x: 7 * Math.cos(t) - d * Math.cos(7 * t),
|
|
471
|
-
y: 7 * Math.sin(t) - d * Math.sin(7 * t)
|
|
520
|
+
y: 7 * Math.sin(t) - d * Math.sin(7 * t),
|
|
472
521
|
};
|
|
473
522
|
}
|
|
474
523
|
function epitrochoid7Skeleton(t) {
|
|
475
524
|
const d = 1.275;
|
|
476
525
|
return {
|
|
477
526
|
x: 7 * Math.cos(t) - d * Math.cos(7 * t),
|
|
478
|
-
y: 7 * Math.sin(t) - d * Math.sin(7 * t)
|
|
527
|
+
y: 7 * Math.sin(t) - d * Math.sin(7 * t),
|
|
479
528
|
};
|
|
480
529
|
}
|
|
481
530
|
function astroid(t, _time, _params) {
|
|
@@ -483,55 +532,56 @@ function astroid(t, _time, _params) {
|
|
|
483
532
|
const s = Math.sin(t);
|
|
484
533
|
return {
|
|
485
534
|
x: c * c * c,
|
|
486
|
-
y: s * s * s
|
|
535
|
+
y: s * s * s,
|
|
487
536
|
};
|
|
488
537
|
}
|
|
489
538
|
function deltoid(t, _time, _params) {
|
|
490
539
|
return {
|
|
491
540
|
x: 2 * Math.cos(t) + Math.cos(2 * t),
|
|
492
|
-
y: 2 * Math.sin(t) - Math.sin(2 * t)
|
|
541
|
+
y: 2 * Math.sin(t) - Math.sin(2 * t),
|
|
493
542
|
};
|
|
494
543
|
}
|
|
495
544
|
function rose5(t, _time, _params) {
|
|
496
545
|
const r = Math.cos(5 * t);
|
|
497
546
|
return {
|
|
498
547
|
x: r * Math.cos(t),
|
|
499
|
-
y: r * Math.sin(t)
|
|
548
|
+
y: r * Math.sin(t),
|
|
500
549
|
};
|
|
501
550
|
}
|
|
502
551
|
function rose3(t, _time, _params) {
|
|
503
552
|
const r = Math.cos(3 * t);
|
|
504
553
|
return {
|
|
505
554
|
x: r * Math.cos(t),
|
|
506
|
-
y: r * Math.sin(t)
|
|
555
|
+
y: r * Math.sin(t),
|
|
507
556
|
};
|
|
508
557
|
}
|
|
509
558
|
function lissajous32(t, time, _params) {
|
|
510
559
|
const phi = time * 0.45;
|
|
511
560
|
return {
|
|
512
561
|
x: Math.sin(3 * t + phi),
|
|
513
|
-
y: Math.sin(2 * t)
|
|
562
|
+
y: Math.sin(2 * t),
|
|
514
563
|
};
|
|
515
564
|
}
|
|
516
565
|
function lissajous43(t, time, _params) {
|
|
517
566
|
const phi = time * 0.38;
|
|
518
567
|
return {
|
|
519
568
|
x: Math.sin(4 * t + phi),
|
|
520
|
-
y: Math.sin(3 * t)
|
|
569
|
+
y: Math.sin(3 * t),
|
|
521
570
|
};
|
|
522
571
|
}
|
|
523
572
|
function epicycloid3(t, _time, _params) {
|
|
524
573
|
return {
|
|
525
574
|
x: 4 * Math.cos(t) - Math.cos(4 * t),
|
|
526
|
-
y: 4 * Math.sin(t) - Math.sin(4 * t)
|
|
575
|
+
y: 4 * Math.sin(t) - Math.sin(4 * t),
|
|
527
576
|
};
|
|
528
577
|
}
|
|
529
578
|
function lame(t, time, _params) {
|
|
530
579
|
const p = 1.75 + 1.25 * Math.sin(time * 0.48);
|
|
531
|
-
const c = Math.cos(t),
|
|
580
|
+
const c = Math.cos(t),
|
|
581
|
+
s = Math.sin(t);
|
|
532
582
|
return {
|
|
533
583
|
x: Math.sign(c) * Math.pow(Math.abs(c), p),
|
|
534
|
-
y: Math.sign(s) * Math.pow(Math.abs(s), p)
|
|
584
|
+
y: Math.sign(s) * Math.pow(Math.abs(s), p),
|
|
535
585
|
};
|
|
536
586
|
}
|
|
537
587
|
var curves = {
|
|
@@ -539,66 +589,66 @@ var curves = {
|
|
|
539
589
|
name: "Artemis II",
|
|
540
590
|
fn: artemis2,
|
|
541
591
|
period: TWO_PI2,
|
|
542
|
-
speed: 0.7
|
|
592
|
+
speed: 0.7,
|
|
543
593
|
},
|
|
544
594
|
epitrochoid7: {
|
|
545
595
|
name: "Epitrochoid",
|
|
546
596
|
fn: epitrochoid7,
|
|
547
597
|
period: TWO_PI2,
|
|
548
598
|
speed: 1.4,
|
|
549
|
-
skeletonFn: epitrochoid7Skeleton
|
|
599
|
+
skeletonFn: epitrochoid7Skeleton,
|
|
550
600
|
},
|
|
551
601
|
astroid: {
|
|
552
602
|
name: "Astroid",
|
|
553
603
|
fn: astroid,
|
|
554
604
|
period: TWO_PI2,
|
|
555
|
-
speed: 1.1
|
|
605
|
+
speed: 1.1,
|
|
556
606
|
},
|
|
557
607
|
deltoid: {
|
|
558
608
|
name: "Deltoid",
|
|
559
609
|
fn: deltoid,
|
|
560
610
|
period: TWO_PI2,
|
|
561
|
-
speed: 0.9
|
|
611
|
+
speed: 0.9,
|
|
562
612
|
},
|
|
563
613
|
rose5: {
|
|
564
614
|
name: "Rose (n=5)",
|
|
565
615
|
fn: rose5,
|
|
566
616
|
period: TWO_PI2,
|
|
567
|
-
speed: 1
|
|
617
|
+
speed: 1,
|
|
568
618
|
},
|
|
569
619
|
rose3: {
|
|
570
620
|
name: "Rose (n=3)",
|
|
571
621
|
fn: rose3,
|
|
572
622
|
period: TWO_PI2,
|
|
573
|
-
speed: 1.15
|
|
623
|
+
speed: 1.15,
|
|
574
624
|
},
|
|
575
625
|
lissajous32: {
|
|
576
626
|
name: "Lissajous 3:2",
|
|
577
627
|
fn: lissajous32,
|
|
578
628
|
period: TWO_PI2,
|
|
579
629
|
speed: 2,
|
|
580
|
-
skeleton: "live"
|
|
630
|
+
skeleton: "live",
|
|
581
631
|
},
|
|
582
632
|
lissajous43: {
|
|
583
633
|
name: "Lissajous 4:3",
|
|
584
634
|
fn: lissajous43,
|
|
585
635
|
period: TWO_PI2,
|
|
586
636
|
speed: 1.8,
|
|
587
|
-
skeleton: "live"
|
|
637
|
+
skeleton: "live",
|
|
588
638
|
},
|
|
589
639
|
epicycloid3: {
|
|
590
640
|
name: "Epicycloid (n=3)",
|
|
591
641
|
fn: epicycloid3,
|
|
592
642
|
period: TWO_PI2,
|
|
593
|
-
speed: 0.75
|
|
643
|
+
speed: 0.75,
|
|
594
644
|
},
|
|
595
645
|
lame: {
|
|
596
646
|
name: "Lam\xE9 Curve",
|
|
597
647
|
fn: lame,
|
|
598
648
|
period: TWO_PI2,
|
|
599
649
|
speed: 1,
|
|
600
|
-
skeleton: "live"
|
|
601
|
-
}
|
|
650
|
+
skeleton: "live",
|
|
651
|
+
},
|
|
602
652
|
};
|
|
603
653
|
|
|
604
654
|
// src/index.ts
|
|
@@ -621,12 +671,12 @@ function init() {
|
|
|
621
671
|
return console.error(`[sarmal] "${curveName}" is not a valid curve name`);
|
|
622
672
|
}
|
|
623
673
|
const sarmal = createSarmal(canvas, curveDef, {
|
|
624
|
-
...canvas.dataset.trailColor && { trailColor: canvas.dataset.trailColor },
|
|
625
|
-
...canvas.dataset.skeletonColor && { skeletonColor: canvas.dataset.skeletonColor },
|
|
626
|
-
...canvas.dataset.headColor && { headColor: canvas.dataset.headColor },
|
|
627
|
-
...canvas.dataset.headRadius && { headRadius: parseFloat(canvas.dataset.headRadius) },
|
|
628
|
-
...canvas.dataset.glowSize && { glowSize: parseInt(canvas.dataset.glowSize, 10) },
|
|
629
|
-
...canvas.dataset.trailLength && { trailLength: parseInt(canvas.dataset.trailLength, 10) }
|
|
674
|
+
...(canvas.dataset.trailColor && { trailColor: canvas.dataset.trailColor }),
|
|
675
|
+
...(canvas.dataset.skeletonColor && { skeletonColor: canvas.dataset.skeletonColor }),
|
|
676
|
+
...(canvas.dataset.headColor && { headColor: canvas.dataset.headColor }),
|
|
677
|
+
...(canvas.dataset.headRadius && { headRadius: parseFloat(canvas.dataset.headRadius) }),
|
|
678
|
+
...(canvas.dataset.glowSize && { glowSize: parseInt(canvas.dataset.glowSize, 10) }),
|
|
679
|
+
...(canvas.dataset.trailLength && { trailLength: parseInt(canvas.dataset.trailLength, 10) }),
|
|
630
680
|
});
|
|
631
681
|
sarmal.start();
|
|
632
682
|
});
|
|
@@ -637,4 +687,4 @@ if (document.readyState === "loading") {
|
|
|
637
687
|
init();
|
|
638
688
|
}
|
|
639
689
|
//# sourceMappingURL=auto-init.cjs.map
|
|
640
|
-
//# sourceMappingURL=auto-init.cjs.map
|
|
690
|
+
//# sourceMappingURL=auto-init.cjs.map
|