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