@sarmal/core 0.2.0 → 0.3.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 +7 -0
- package/dist/auto-init.cjs +111 -45
- package/dist/auto-init.cjs.map +1 -1
- package/dist/auto-init.js +111 -45
- package/dist/auto-init.js.map +1 -1
- package/dist/index.cjs +331 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -3
- package/dist/index.d.ts +48 -3
- package/dist/index.js +330 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -9,28 +9,32 @@ var CircularBuffer = class {
|
|
|
9
9
|
this.count = 0;
|
|
10
10
|
this.capacity = capacity;
|
|
11
11
|
this.data = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
|
|
12
|
+
this.result = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
|
|
12
13
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Array elements are pre-allocated and `head` pointer is manually assigned,
|
|
15
|
-
* because AI said using `Array.shift` would be `O(n)` and this would be `O(1)`
|
|
16
|
-
* and I don't know any better.
|
|
17
|
-
*/
|
|
14
|
+
/** Mutates in-place */
|
|
18
15
|
push(x, y) {
|
|
19
|
-
this.data[this.head]
|
|
16
|
+
const slot = this.data[this.head];
|
|
17
|
+
slot.x = x;
|
|
18
|
+
slot.y = y;
|
|
20
19
|
this.head = (this.head + 1) % this.capacity;
|
|
21
20
|
if (this.count < this.capacity) {
|
|
22
21
|
this.count++;
|
|
23
22
|
}
|
|
24
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Copies ordered points into the pre-allocated result buffer and returns it
|
|
26
|
+
* Note: The *same* array reference is returned every call,
|
|
27
|
+
* so `result.length` is also always `capacity`
|
|
28
|
+
*/
|
|
25
29
|
toArray() {
|
|
26
|
-
const result = new Array(this.count);
|
|
27
30
|
const start = this.count < this.capacity ? 0 : this.head;
|
|
28
31
|
for (let i = 0; i < this.count; i++) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
+
const src = this.data[(start + i) % this.capacity];
|
|
33
|
+
const dst = this.result[i];
|
|
34
|
+
dst.x = src.x;
|
|
35
|
+
dst.y = src.y;
|
|
32
36
|
}
|
|
33
|
-
return result;
|
|
37
|
+
return this.result;
|
|
34
38
|
}
|
|
35
39
|
clear() {
|
|
36
40
|
this.head = 0;
|
|
@@ -58,6 +62,9 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
58
62
|
trail.push(point.x, point.y);
|
|
59
63
|
return trail.toArray();
|
|
60
64
|
},
|
|
65
|
+
get trailCount() {
|
|
66
|
+
return trail.length;
|
|
67
|
+
},
|
|
61
68
|
reset() {
|
|
62
69
|
t = 0;
|
|
63
70
|
actualTime = 0;
|
|
@@ -82,16 +89,16 @@ var DEFAULT_GLOW_SIZE = 20;
|
|
|
82
89
|
var DEFAULT_SKELETON_COLOR = "#ffffff";
|
|
83
90
|
var DEFAULT_SKELETON_OPACITY = 0.15;
|
|
84
91
|
var FIT_PADDING = 0.1;
|
|
85
|
-
var TRAIL_BATCH_SIZE =
|
|
92
|
+
var TRAIL_BATCH_SIZE = 20;
|
|
86
93
|
var TRAIL_FADE_CURVE = 1.5;
|
|
87
94
|
var TRAIL_MAX_OPACITY = 0.88;
|
|
88
95
|
var TRAIL_MIN_WIDTH = 0.5;
|
|
89
96
|
var TRAIL_MAX_WIDTH = 2.5;
|
|
90
97
|
var GLOW_INNER_EDGE = 0.4;
|
|
91
98
|
var GLOW_FALLOFF_OPACITY = 0.53;
|
|
92
|
-
function
|
|
99
|
+
function hexToRgbComponents(hex) {
|
|
93
100
|
const n = parseInt(hex.slice(1), 16);
|
|
94
|
-
return
|
|
101
|
+
return `${n >> 16},${n >> 8 & 255},${n & 255}`;
|
|
95
102
|
}
|
|
96
103
|
function createRenderer(options) {
|
|
97
104
|
const canvas = options.canvas;
|
|
@@ -107,8 +114,12 @@ function createRenderer(options) {
|
|
|
107
114
|
headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
|
|
108
115
|
glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE
|
|
109
116
|
};
|
|
117
|
+
const trailRgb = hexToRgbComponents(opts.trailColor);
|
|
118
|
+
const headRgbFalloff = `rgba(${hexToRgbComponents(opts.headColor)},${GLOW_FALLOFF_OPACITY})`;
|
|
110
119
|
let skeleton = [];
|
|
120
|
+
let skeletonCanvas = null;
|
|
111
121
|
let trail = [];
|
|
122
|
+
let trailCount = 0;
|
|
112
123
|
let head = null;
|
|
113
124
|
let scale = 1;
|
|
114
125
|
let offsetX = 0;
|
|
@@ -147,49 +158,49 @@ function createRenderer(options) {
|
|
|
147
158
|
offsetX = (canvasWidth - boundsWidth) / 2 - minX * scale;
|
|
148
159
|
offsetY = (canvasHeight - boundsHeight) / 2 - minY * scale;
|
|
149
160
|
}
|
|
150
|
-
function
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
161
|
+
function buildSkeletonCanvas() {
|
|
162
|
+
if (skeleton.length < 2) return;
|
|
163
|
+
skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
|
|
164
|
+
const skeletonCtx = skeletonCanvas.getContext("2d");
|
|
165
|
+
skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
|
|
166
|
+
skeletonCtx.lineWidth = 1.5;
|
|
167
|
+
skeletonCtx.beginPath();
|
|
168
|
+
const first = skeleton[0];
|
|
169
|
+
skeletonCtx.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);
|
|
170
|
+
for (let i = 1; i < skeleton.length; i++) {
|
|
171
|
+
const p = skeleton[i];
|
|
172
|
+
skeletonCtx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
|
|
173
|
+
}
|
|
174
|
+
skeletonCtx.stroke();
|
|
155
175
|
}
|
|
156
176
|
function drawSkeleton() {
|
|
157
|
-
if (
|
|
177
|
+
if (!skeletonCanvas) {
|
|
158
178
|
return;
|
|
159
179
|
}
|
|
160
|
-
ctx.
|
|
161
|
-
ctx.lineWidth = 1.5;
|
|
162
|
-
ctx.beginPath();
|
|
163
|
-
const firstPixel = transformCoordinateToPixel(skeleton[0]);
|
|
164
|
-
ctx.moveTo(firstPixel.x, firstPixel.y);
|
|
165
|
-
for (let i = 1; i < skeleton.length; i++) {
|
|
166
|
-
const pixel = transformCoordinateToPixel(skeleton[i]);
|
|
167
|
-
ctx.lineTo(pixel.x, pixel.y);
|
|
168
|
-
}
|
|
169
|
-
ctx.stroke();
|
|
180
|
+
ctx.drawImage(skeletonCanvas, 0, 0);
|
|
170
181
|
}
|
|
171
182
|
function drawTrail() {
|
|
172
|
-
if (
|
|
183
|
+
if (trailCount < 2) {
|
|
173
184
|
return;
|
|
174
185
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
186
|
+
ctx.lineJoin = "round";
|
|
187
|
+
ctx.lineCap = "round";
|
|
188
|
+
for (let batchIndex = 0; batchIndex < trailCount - 1; batchIndex += TRAIL_BATCH_SIZE) {
|
|
189
|
+
const bEnd = Math.min(batchIndex + TRAIL_BATCH_SIZE, trailCount - 1);
|
|
190
|
+
const progress = (batchIndex + bEnd) / 2 / (trailCount - 1);
|
|
178
191
|
const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
|
|
179
192
|
const lineWidth = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
|
|
180
193
|
ctx.beginPath();
|
|
181
|
-
for (let i =
|
|
182
|
-
const
|
|
183
|
-
if (i ===
|
|
184
|
-
ctx.moveTo(
|
|
194
|
+
for (let i = batchIndex; i <= bEnd; i++) {
|
|
195
|
+
const point = trail[i];
|
|
196
|
+
if (i === batchIndex) {
|
|
197
|
+
ctx.moveTo(point.x * scale + offsetX, point.y * scale + offsetY);
|
|
185
198
|
} else {
|
|
186
|
-
ctx.lineTo(
|
|
199
|
+
ctx.lineTo(point.x * scale + offsetX, point.y * scale + offsetY);
|
|
187
200
|
}
|
|
188
201
|
}
|
|
189
|
-
ctx.strokeStyle =
|
|
202
|
+
ctx.strokeStyle = `rgba(${trailRgb},${alpha})`;
|
|
190
203
|
ctx.lineWidth = lineWidth;
|
|
191
|
-
ctx.lineJoin = "round";
|
|
192
|
-
ctx.lineCap = "round";
|
|
193
204
|
ctx.stroke();
|
|
194
205
|
}
|
|
195
206
|
}
|
|
@@ -197,10 +208,11 @@ function createRenderer(options) {
|
|
|
197
208
|
if (!head) {
|
|
198
209
|
return;
|
|
199
210
|
}
|
|
200
|
-
const
|
|
211
|
+
const x = head.x * scale + offsetX;
|
|
212
|
+
const y = head.y * scale + offsetY;
|
|
201
213
|
const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);
|
|
202
214
|
gradient.addColorStop(0, opts.headColor);
|
|
203
|
-
gradient.addColorStop(GLOW_INNER_EDGE,
|
|
215
|
+
gradient.addColorStop(GLOW_INNER_EDGE, headRgbFalloff);
|
|
204
216
|
gradient.addColorStop(1, "transparent");
|
|
205
217
|
ctx.fillStyle = gradient;
|
|
206
218
|
ctx.beginPath();
|
|
@@ -216,7 +228,8 @@ function createRenderer(options) {
|
|
|
216
228
|
const deltaTime = (now - lastTime) / 1e3;
|
|
217
229
|
lastTime = now;
|
|
218
230
|
trail = engine.tick(deltaTime);
|
|
219
|
-
|
|
231
|
+
trailCount = engine.trailCount;
|
|
232
|
+
head = trailCount > 0 ? trail[trailCount - 1] : null;
|
|
220
233
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
221
234
|
drawSkeleton();
|
|
222
235
|
drawTrail();
|
|
@@ -225,6 +238,7 @@ function createRenderer(options) {
|
|
|
225
238
|
}
|
|
226
239
|
skeleton = engine.getSarmalSkeleton();
|
|
227
240
|
calculateBoundaries();
|
|
241
|
+
buildSkeletonCanvas();
|
|
228
242
|
return {
|
|
229
243
|
start() {
|
|
230
244
|
if (animationId !== null) {
|
|
@@ -254,6 +268,224 @@ function createRenderer(options) {
|
|
|
254
268
|
};
|
|
255
269
|
}
|
|
256
270
|
|
|
271
|
+
// src/renderer-svg.ts
|
|
272
|
+
var TRAIL_BATCH_COUNT = 12;
|
|
273
|
+
var TRAIL_FADE_CURVE2 = 1.5;
|
|
274
|
+
var TRAIL_MAX_OPACITY2 = 0.88;
|
|
275
|
+
var TRAIL_MIN_WIDTH2 = 0.5;
|
|
276
|
+
var TRAIL_MAX_WIDTH2 = 2.5;
|
|
277
|
+
var DEFAULT_SKELETON_OPACITY2 = 0.15;
|
|
278
|
+
var DEFAULT_GLOW_INNER_STOP = 0.4;
|
|
279
|
+
var DEFAULT_GLOW_FALLOFF_OPACITY = 0.53;
|
|
280
|
+
var FIT_PADDING2 = 0.1;
|
|
281
|
+
var instanceCount = 0;
|
|
282
|
+
function el(tag) {
|
|
283
|
+
return document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
284
|
+
}
|
|
285
|
+
function createSVGRenderer(options) {
|
|
286
|
+
const { container, engine } = options;
|
|
287
|
+
const opts = {
|
|
288
|
+
skeletonColor: options.skeletonColor ?? "#ffffff",
|
|
289
|
+
trailColor: options.trailColor ?? "#ffffff",
|
|
290
|
+
headColor: options.headColor ?? "#ffffff",
|
|
291
|
+
headRadius: options.headRadius ?? 4,
|
|
292
|
+
glowSize: options.glowSize ?? 20,
|
|
293
|
+
ariaLabel: options.ariaLabel ?? "Loading"
|
|
294
|
+
};
|
|
295
|
+
const uid = ++instanceCount;
|
|
296
|
+
const gradientId = `sarmal-glow-${uid}`;
|
|
297
|
+
const rect = container.getBoundingClientRect();
|
|
298
|
+
const width = rect.width || 200;
|
|
299
|
+
const height = rect.height || 200;
|
|
300
|
+
const svg = el("svg");
|
|
301
|
+
svg.setAttribute("width", String(width));
|
|
302
|
+
svg.setAttribute("height", String(height));
|
|
303
|
+
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
304
|
+
svg.setAttribute("role", "img");
|
|
305
|
+
svg.setAttribute("aria-label", opts.ariaLabel);
|
|
306
|
+
const titleEl = el("title");
|
|
307
|
+
titleEl.textContent = opts.ariaLabel;
|
|
308
|
+
svg.appendChild(titleEl);
|
|
309
|
+
const defs = el("defs");
|
|
310
|
+
const gradient = el("radialGradient");
|
|
311
|
+
gradient.id = gradientId;
|
|
312
|
+
gradient.setAttribute("cx", "50%");
|
|
313
|
+
gradient.setAttribute("cy", "50%");
|
|
314
|
+
gradient.setAttribute("r", "50%");
|
|
315
|
+
const stop0 = el("stop");
|
|
316
|
+
stop0.setAttribute("offset", "0%");
|
|
317
|
+
stop0.setAttribute("stop-color", opts.headColor);
|
|
318
|
+
stop0.setAttribute("stop-opacity", "1");
|
|
319
|
+
const stopMid = el("stop");
|
|
320
|
+
stopMid.setAttribute("offset", `${DEFAULT_GLOW_INNER_STOP * 100}%`);
|
|
321
|
+
stopMid.setAttribute("stop-color", opts.headColor);
|
|
322
|
+
stopMid.setAttribute("stop-opacity", String(DEFAULT_GLOW_FALLOFF_OPACITY));
|
|
323
|
+
const stop1 = el("stop");
|
|
324
|
+
stop1.setAttribute("offset", "100%");
|
|
325
|
+
stop1.setAttribute("stop-color", opts.headColor);
|
|
326
|
+
stop1.setAttribute("stop-opacity", "0");
|
|
327
|
+
gradient.append(stop0, stopMid, stop1);
|
|
328
|
+
defs.appendChild(gradient);
|
|
329
|
+
svg.appendChild(defs);
|
|
330
|
+
const skeletonPath = el("path");
|
|
331
|
+
skeletonPath.setAttribute("fill", "none");
|
|
332
|
+
skeletonPath.setAttribute("stroke", opts.skeletonColor);
|
|
333
|
+
skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY2));
|
|
334
|
+
skeletonPath.setAttribute("stroke-width", "1.5");
|
|
335
|
+
svg.appendChild(skeletonPath);
|
|
336
|
+
const trailPaths = [];
|
|
337
|
+
for (let i = 0; i < TRAIL_BATCH_COUNT; i++) {
|
|
338
|
+
const path = el("path");
|
|
339
|
+
path.setAttribute("fill", "none");
|
|
340
|
+
path.setAttribute("stroke", opts.trailColor);
|
|
341
|
+
path.setAttribute("stroke-linecap", "round");
|
|
342
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
343
|
+
svg.appendChild(path);
|
|
344
|
+
trailPaths.push(path);
|
|
345
|
+
}
|
|
346
|
+
const glowCircle = el("circle");
|
|
347
|
+
glowCircle.setAttribute("fill", `url(#${gradientId})`);
|
|
348
|
+
glowCircle.setAttribute("r", String(opts.glowSize));
|
|
349
|
+
svg.appendChild(glowCircle);
|
|
350
|
+
const headCircle = el("circle");
|
|
351
|
+
headCircle.setAttribute("fill", opts.headColor);
|
|
352
|
+
headCircle.setAttribute("r", String(opts.headRadius));
|
|
353
|
+
svg.appendChild(headCircle);
|
|
354
|
+
container.appendChild(svg);
|
|
355
|
+
let scale = 1;
|
|
356
|
+
let offsetX = 0;
|
|
357
|
+
let offsetY = 0;
|
|
358
|
+
function calculateBoundaries(skeleton2) {
|
|
359
|
+
if (skeleton2.length === 0) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const first = skeleton2[0];
|
|
363
|
+
let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
|
|
364
|
+
for (const p of skeleton2) {
|
|
365
|
+
if (p.x < minX) {
|
|
366
|
+
minX = p.x;
|
|
367
|
+
}
|
|
368
|
+
if (p.x > maxX) {
|
|
369
|
+
maxX = p.x;
|
|
370
|
+
}
|
|
371
|
+
if (p.y < minY) {
|
|
372
|
+
minY = p.y;
|
|
373
|
+
}
|
|
374
|
+
if (p.y > maxY) {
|
|
375
|
+
maxY = p.y;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const w = maxX - minX;
|
|
379
|
+
const h = maxY - minY;
|
|
380
|
+
const scaleX = width / (w * (1 + FIT_PADDING2 * 2));
|
|
381
|
+
const scaleY = height / (h * (1 + FIT_PADDING2 * 2));
|
|
382
|
+
scale = Math.min(scaleX, scaleY);
|
|
383
|
+
offsetX = (width - w * scale) / 2 - minX * scale;
|
|
384
|
+
offsetY = (height - h * scale) / 2 - minY * scale;
|
|
385
|
+
}
|
|
386
|
+
function px(p) {
|
|
387
|
+
return (p.x * scale + offsetX).toFixed(2);
|
|
388
|
+
}
|
|
389
|
+
function py(p) {
|
|
390
|
+
return (p.y * scale + offsetY).toFixed(2);
|
|
391
|
+
}
|
|
392
|
+
const skeleton = engine.getSarmalSkeleton();
|
|
393
|
+
calculateBoundaries(skeleton);
|
|
394
|
+
if (skeleton.length >= 2) {
|
|
395
|
+
let d = `M${px(skeleton[0])} ${py(skeleton[0])}`;
|
|
396
|
+
for (let i = 1; i < skeleton.length; i++) {
|
|
397
|
+
d += ` L${px(skeleton[i])} ${py(skeleton[i])}`;
|
|
398
|
+
}
|
|
399
|
+
d += " Z";
|
|
400
|
+
skeletonPath.setAttribute("d", d);
|
|
401
|
+
}
|
|
402
|
+
function updateTrail(trail, trailCount) {
|
|
403
|
+
if (trailCount < 2) {
|
|
404
|
+
for (const p of trailPaths) {
|
|
405
|
+
p.setAttribute("d", "");
|
|
406
|
+
}
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const batchSize = Math.ceil(trailCount / TRAIL_BATCH_COUNT);
|
|
410
|
+
for (let b = 0; b < TRAIL_BATCH_COUNT; b++) {
|
|
411
|
+
const start = b * batchSize;
|
|
412
|
+
const end = Math.min(start + batchSize, trailCount - 1);
|
|
413
|
+
if (start >= trailCount - 1) {
|
|
414
|
+
trailPaths[b].setAttribute("d", "");
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const progress = (start + end) / 2 / (trailCount - 1);
|
|
418
|
+
const opacity = Math.pow(progress, TRAIL_FADE_CURVE2) * TRAIL_MAX_OPACITY2;
|
|
419
|
+
const strokeWidth = TRAIL_MIN_WIDTH2 + progress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
|
|
420
|
+
let d = `M${px(trail[start])} ${py(trail[start])}`;
|
|
421
|
+
for (let i = start + 1; i <= end; i++) {
|
|
422
|
+
d += ` L${px(trail[i])} ${py(trail[i])}`;
|
|
423
|
+
}
|
|
424
|
+
trailPaths[b].setAttribute("d", d);
|
|
425
|
+
trailPaths[b].setAttribute("stroke-opacity", opacity.toFixed(3));
|
|
426
|
+
trailPaths[b].setAttribute("stroke-width", strokeWidth.toFixed(2));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function updateHead(trail, trailCount) {
|
|
430
|
+
if (trailCount === 0) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const head = trail[trailCount - 1];
|
|
434
|
+
const x = px(head);
|
|
435
|
+
const y = py(head);
|
|
436
|
+
glowCircle.setAttribute("cx", x);
|
|
437
|
+
glowCircle.setAttribute("cy", y);
|
|
438
|
+
headCircle.setAttribute("cx", x);
|
|
439
|
+
headCircle.setAttribute("cy", y);
|
|
440
|
+
}
|
|
441
|
+
let animationId = null;
|
|
442
|
+
let lastTime = 0;
|
|
443
|
+
const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
444
|
+
function renderFrame() {
|
|
445
|
+
const now = performance.now();
|
|
446
|
+
const dt = (now - lastTime) / 1e3;
|
|
447
|
+
lastTime = now;
|
|
448
|
+
const trail = engine.tick(dt);
|
|
449
|
+
const trailCount = engine.trailCount;
|
|
450
|
+
updateTrail(trail, trailCount);
|
|
451
|
+
updateHead(trail, trailCount);
|
|
452
|
+
if (!prefersReducedMotion) {
|
|
453
|
+
animationId = requestAnimationFrame(renderFrame);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
start() {
|
|
458
|
+
if (animationId !== null) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
lastTime = performance.now();
|
|
462
|
+
renderFrame();
|
|
463
|
+
},
|
|
464
|
+
stop() {
|
|
465
|
+
if (animationId === null) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
cancelAnimationFrame(animationId);
|
|
469
|
+
animationId = null;
|
|
470
|
+
},
|
|
471
|
+
reset() {
|
|
472
|
+
engine.reset();
|
|
473
|
+
},
|
|
474
|
+
destroy() {
|
|
475
|
+
if (animationId !== null) {
|
|
476
|
+
cancelAnimationFrame(animationId);
|
|
477
|
+
animationId = null;
|
|
478
|
+
}
|
|
479
|
+
svg.remove();
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function createSarmalSVG(container, curveDef, options) {
|
|
484
|
+
const { trailLength, ...rendererOpts } = options ?? {};
|
|
485
|
+
const engine = createEngine(curveDef, trailLength);
|
|
486
|
+
return createSVGRenderer({ container, engine, ...rendererOpts });
|
|
487
|
+
}
|
|
488
|
+
|
|
257
489
|
// src/curves.ts
|
|
258
490
|
var TWO_PI2 = Math.PI * 2;
|
|
259
491
|
function artemis2(t, _time, _params) {
|
|
@@ -300,6 +532,34 @@ function rose3(t, _time, _params) {
|
|
|
300
532
|
y: r * Math.sin(t)
|
|
301
533
|
};
|
|
302
534
|
}
|
|
535
|
+
function lissajous32(t, time, _params) {
|
|
536
|
+
const phi = time * 0.45;
|
|
537
|
+
return {
|
|
538
|
+
x: Math.sin(3 * t + phi),
|
|
539
|
+
y: Math.sin(2 * t)
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function lissajous43(t, time, _params) {
|
|
543
|
+
const phi = time * 0.38;
|
|
544
|
+
return {
|
|
545
|
+
x: Math.sin(4 * t + phi),
|
|
546
|
+
y: Math.sin(3 * t)
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function epicycloid3(t, _time, _params) {
|
|
550
|
+
return {
|
|
551
|
+
x: 4 * Math.cos(t) - Math.cos(4 * t),
|
|
552
|
+
y: 4 * Math.sin(t) - Math.sin(4 * t)
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function lame(t, time, _params) {
|
|
556
|
+
const p = 1.75 + 1.25 * Math.sin(time * 0.48);
|
|
557
|
+
const c = Math.cos(t), s = Math.sin(t);
|
|
558
|
+
return {
|
|
559
|
+
x: Math.sign(c) * Math.pow(Math.abs(c), p),
|
|
560
|
+
y: Math.sign(s) * Math.pow(Math.abs(s), p)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
303
563
|
var curves = {
|
|
304
564
|
artemis2: {
|
|
305
565
|
name: "Artemis II",
|
|
@@ -336,6 +596,30 @@ var curves = {
|
|
|
336
596
|
fn: rose3,
|
|
337
597
|
period: TWO_PI2,
|
|
338
598
|
speed: 1.15
|
|
599
|
+
},
|
|
600
|
+
lissajous32: {
|
|
601
|
+
name: "Lissajous 3:2",
|
|
602
|
+
fn: lissajous32,
|
|
603
|
+
period: TWO_PI2,
|
|
604
|
+
speed: 2
|
|
605
|
+
},
|
|
606
|
+
lissajous43: {
|
|
607
|
+
name: "Lissajous 4:3",
|
|
608
|
+
fn: lissajous43,
|
|
609
|
+
period: TWO_PI2,
|
|
610
|
+
speed: 1.8
|
|
611
|
+
},
|
|
612
|
+
epicycloid3: {
|
|
613
|
+
name: "Epicycloid (n=3)",
|
|
614
|
+
fn: epicycloid3,
|
|
615
|
+
period: TWO_PI2,
|
|
616
|
+
speed: 0.75
|
|
617
|
+
},
|
|
618
|
+
lame: {
|
|
619
|
+
name: "Lam\xE9 Curve",
|
|
620
|
+
fn: lame,
|
|
621
|
+
period: TWO_PI2,
|
|
622
|
+
speed: 1
|
|
339
623
|
}
|
|
340
624
|
};
|
|
341
625
|
|
|
@@ -348,7 +632,9 @@ function createSarmal(canvas, curveDef, options) {
|
|
|
348
632
|
|
|
349
633
|
exports.createEngine = createEngine;
|
|
350
634
|
exports.createRenderer = createRenderer;
|
|
635
|
+
exports.createSVGRenderer = createSVGRenderer;
|
|
351
636
|
exports.createSarmal = createSarmal;
|
|
637
|
+
exports.createSarmalSVG = createSarmalSVG;
|
|
352
638
|
exports.curves = curves;
|
|
353
639
|
//# sourceMappingURL=index.cjs.map
|
|
354
640
|
//# sourceMappingURL=index.cjs.map
|