@sarmal/core 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- "use strict";
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,16 +134,13 @@ 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 =
138
- frozenStrategy === "normalized"
139
- ? (sampleT / frozenA.period) * frozenB.period
140
- : sampleT;
137
+ const tB = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
141
138
  const b = frozenB.fn(tB, time, params);
142
139
  return {
143
140
  x: a.x + (b.x - a.x) * frozenAlpha,
144
- y: a.y + (b.y - a.y) * frozenAlpha,
141
+ y: a.y + (b.y - a.y) * frozenAlpha
145
142
  };
146
- },
143
+ }
147
144
  };
148
145
  }
149
146
  _morphStrategy = strategy;
@@ -155,6 +152,9 @@ function createEngine(curveDef, trailLength = 120) {
155
152
  },
156
153
  completeMorph() {
157
154
  if (morphCurveB !== null) {
155
+ if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
156
+ t = t / curve.period * morphCurveB.period;
157
+ }
158
158
  curve = morphCurveB;
159
159
  }
160
160
  morphCurveB = null;
@@ -165,26 +165,23 @@ function createEngine(curveDef, trailLength = 120) {
165
165
  const points = new Array(steps);
166
166
  if (morphCurveB !== null && _morphAlpha !== null) {
167
167
  for (let i = 0; i < steps; i++) {
168
- const sampleT = (i / (steps - 1)) * curve.period;
168
+ const sampleT = i / (steps - 1) * curve.period;
169
169
  const a = sampleSkeleton(curve, sampleT);
170
- const tB =
171
- _morphStrategy === "normalized"
172
- ? (sampleT / curve.period) * morphCurveB.period
173
- : sampleT;
170
+ const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
174
171
  const b = sampleSkeleton(morphCurveB, tB);
175
172
  points[i] = {
176
173
  x: a.x + (b.x - a.x) * _morphAlpha,
177
- y: a.y + (b.y - a.y) * _morphAlpha,
174
+ y: a.y + (b.y - a.y) * _morphAlpha
178
175
  };
179
176
  }
180
177
  return points;
181
178
  }
182
179
  for (let i = 0; i < steps; i++) {
183
- const sampleT = (i / (steps - 1)) * curve.period;
180
+ const sampleT = i / (steps - 1) * curve.period;
184
181
  points[i] = sampleSkeleton(curve, sampleT);
185
182
  }
186
183
  return points;
187
- },
184
+ }
188
185
  };
189
186
  }
190
187
 
@@ -204,7 +201,7 @@ var GLOW_INNER_EDGE = 0.4;
204
201
  var GLOW_FALLOFF_OPACITY = 0.53;
205
202
  function hexToRgbComponents(hex) {
206
203
  const n = parseInt(hex.slice(1), 16);
207
- return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
204
+ return `${n >> 16},${n >> 8 & 255},${n & 255}`;
208
205
  }
209
206
  function createRenderer(options) {
210
207
  const canvas = options.canvas;
@@ -218,7 +215,7 @@ function createRenderer(options) {
218
215
  trailColor: options.trailColor ?? "#ffffff",
219
216
  headColor: options.headColor ?? "#ffffff",
220
217
  headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
221
- glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE,
218
+ glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE
222
219
  };
223
220
  const trailRgb = hexToRgbComponents(opts.trailColor);
224
221
  const headRgbFalloff = `rgba(${hexToRgbComponents(opts.headColor)},${GLOW_FALLOFF_OPACITY})`;
@@ -234,32 +231,16 @@ function createRenderer(options) {
234
231
  let lastTime = 0;
235
232
  let morphResolve = null;
236
233
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
237
- let morphTarget = null;
238
234
  let morphAlpha = 0;
239
- let skeletonCanvasA = null;
240
- let skeletonCanvasB = null;
241
- function calculateBoundaries() {
242
- if (skeleton.length === 0) {
243
- return;
244
- }
245
- const first = skeleton[0];
246
- let minX = first.x,
247
- maxX = first.x,
248
- minY = first.y,
249
- maxY = first.y;
250
- for (const p of skeleton) {
251
- if (p.x < minX) {
252
- minX = p.x;
253
- }
254
- if (p.x > maxX) {
255
- maxX = p.x;
256
- }
257
- if (p.y < minY) {
258
- minY = p.y;
259
- }
260
- if (p.y > maxY) {
261
- maxY = p.y;
262
- }
235
+ function computeBoundaries(pts) {
236
+ if (pts.length === 0) return null;
237
+ const first = pts[0];
238
+ let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
239
+ for (const p of pts) {
240
+ if (p.x < minX) minX = p.x;
241
+ if (p.x > maxX) maxX = p.x;
242
+ if (p.y < minY) minY = p.y;
243
+ if (p.y > maxY) maxY = p.y;
263
244
  }
264
245
  const width = maxX - minX;
265
246
  const height = maxY - minY;
@@ -267,11 +248,22 @@ function createRenderer(options) {
267
248
  const canvasHeight = canvas.height;
268
249
  const scaleX = canvasWidth / (width * (1 + FIT_PADDING * 2));
269
250
  const scaleY = canvasHeight / (height * (1 + FIT_PADDING * 2));
270
- scale = Math.min(scaleX, scaleY);
271
- const boundsWidth = width * scale;
272
- const boundsHeight = height * scale;
273
- offsetX = (canvasWidth - boundsWidth) / 2 - minX * scale;
274
- offsetY = (canvasHeight - boundsHeight) / 2 - minY * scale;
251
+ const s = Math.min(scaleX, scaleY);
252
+ const boundsWidth = width * s;
253
+ const boundsHeight = height * s;
254
+ return {
255
+ scale: s,
256
+ offsetX: (canvasWidth - boundsWidth) / 2 - minX * s,
257
+ offsetY: (canvasHeight - boundsHeight) / 2 - minY * s
258
+ };
259
+ }
260
+ function calculateBoundaries() {
261
+ const b = computeBoundaries(skeleton);
262
+ if (b) {
263
+ scale = b.scale;
264
+ offsetX = b.offsetX;
265
+ offsetY = b.offsetY;
266
+ }
275
267
  }
276
268
  function buildSkeletonCanvas() {
277
269
  if (skeleton.length < 2) return;
@@ -288,20 +280,23 @@ function createRenderer(options) {
288
280
  }
289
281
  skeletonCtx.stroke();
290
282
  }
283
+ function drawSkeletonPath(pts, opacity) {
284
+ if (pts.length < 2) return;
285
+ ctx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${opacity})`;
286
+ ctx.lineWidth = 1.5;
287
+ ctx.beginPath();
288
+ ctx.moveTo(pts[0].x * scale + offsetX, pts[0].y * scale + offsetY);
289
+ for (let i = 1; i < pts.length; i++) {
290
+ ctx.lineTo(pts[i].x * scale + offsetX, pts[i].y * scale + offsetY);
291
+ }
292
+ ctx.stroke();
293
+ }
291
294
  function drawSkeleton() {
292
295
  if (opts.skeletonColor === "transparent") {
293
296
  return;
294
297
  }
295
298
  if (engine.morphAlpha !== null) {
296
- if (skeletonCanvasA) {
297
- ctx.globalAlpha = (1 - morphAlpha) * DEFAULT_SKELETON_OPACITY;
298
- ctx.drawImage(skeletonCanvasA, 0, 0);
299
- }
300
- if (skeletonCanvasB) {
301
- ctx.globalAlpha = morphAlpha * DEFAULT_SKELETON_OPACITY;
302
- ctx.drawImage(skeletonCanvasB, 0, 0);
303
- }
304
- ctx.globalAlpha = 1;
299
+ drawSkeletonPath(engine.getSarmalSkeleton(), DEFAULT_SKELETON_OPACITY);
305
300
  return;
306
301
  }
307
302
  if (engine.isLiveSkeleton) {
@@ -373,23 +368,29 @@ function createRenderer(options) {
373
368
  if (engine.morphAlpha !== null) {
374
369
  morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
375
370
  engine.setMorphAlpha(morphAlpha);
376
- skeleton = engine.getSarmalSkeleton();
377
- calculateBoundaries();
371
+ const interpolatedSkeleton = engine.getSarmalSkeleton();
372
+ const bounds = computeBoundaries(interpolatedSkeleton);
373
+ if (bounds) {
374
+ scale = bounds.scale;
375
+ offsetX = bounds.offsetX;
376
+ offsetY = bounds.offsetY;
377
+ }
378
378
  if (morphAlpha >= 1) {
379
379
  engine.completeMorph();
380
380
  morphResolve?.();
381
381
  morphResolve = null;
382
- morphTarget = null;
383
382
  morphAlpha = 0;
384
- skeletonCanvasA = null;
385
- skeletonCanvasB = null;
383
+ skeleton = engine.getSarmalSkeleton();
384
+ if (!engine.isLiveSkeleton) {
385
+ buildSkeletonCanvas();
386
+ }
386
387
  }
387
388
  }
388
389
  trail = engine.tick(deltaTime);
389
390
  trailCount = engine.trailCount;
390
391
  head = trailCount > 0 ? trail[trailCount - 1] : null;
391
392
  ctx.clearRect(0, 0, canvas.width, canvas.height);
392
- if (engine.isLiveSkeleton || engine.morphAlpha !== null) {
393
+ if (engine.isLiveSkeleton && engine.morphAlpha === null) {
393
394
  skeleton = engine.getSarmalSkeleton();
394
395
  calculateBoundaries();
395
396
  }
@@ -441,78 +442,40 @@ function createRenderer(options) {
441
442
  morphResolve();
442
443
  morphResolve = null;
443
444
  morphAlpha = 0;
444
- skeletonCanvasA = null;
445
- skeletonCanvasB = null;
446
445
  }
447
446
  morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS;
448
- morphTarget = target;
449
447
  morphAlpha = 0;
450
- const currentSkeleton = engine.getSarmalSkeleton();
451
- if (currentSkeleton.length >= 2) {
452
- skeletonCanvasA = new OffscreenCanvas(canvas.width, canvas.height);
453
- const ctxA = skeletonCanvasA.getContext("2d");
454
- ctxA.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
455
- ctxA.lineWidth = 1.5;
456
- ctxA.beginPath();
457
- const first = currentSkeleton[0];
458
- ctxA.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);
459
- for (let i = 1; i < currentSkeleton.length; i++) {
460
- const p = currentSkeleton[i];
461
- ctxA.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
462
- }
463
- ctxA.stroke();
464
- }
465
448
  engine.startMorph(target, options2?.morphStrategy);
466
- if (morphTarget && !engine.isLiveSkeleton) {
467
- skeletonCanvasB = new OffscreenCanvas(canvas.width, canvas.height);
468
- const skeletonCtx = skeletonCanvasB.getContext("2d");
469
- skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
470
- skeletonCtx.lineWidth = 1.5;
471
- skeletonCtx.beginPath();
472
- const period = morphTarget.period ?? Math.PI * 2;
473
- const samples = Math.max(50, Math.round(period * 20));
474
- const firstB = morphTarget.fn(0, 0, {});
475
- skeletonCtx.moveTo(firstB.x * scale + offsetX, firstB.y * scale + offsetY);
476
- for (let i = 1; i <= samples; i++) {
477
- const t = (i / samples) * period;
478
- const p = morphTarget.fn(t, 0, {});
479
- skeletonCtx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
480
- }
481
- skeletonCtx.stroke();
482
- }
483
449
  return new Promise((resolve) => {
484
450
  morphResolve = resolve;
485
451
  });
486
- },
452
+ }
487
453
  };
488
454
  }
489
455
 
490
456
  // src/curves.ts
491
457
  var TWO_PI2 = Math.PI * 2;
492
458
  function artemis2(t, _time, _params) {
493
- const a = 0.35,
494
- b = 0.15,
495
- ox = 0.175;
496
- const s = Math.sin(t),
497
- c = Math.cos(t);
459
+ const a = 0.35, b = 0.15, ox = 0.175;
460
+ const s = Math.sin(t), c = Math.cos(t);
498
461
  const denom = 1 + s * s;
499
462
  return {
500
- x: (c * (1 + a * c)) / denom - ox,
501
- y: (s * c * (1 + b * c)) / denom,
463
+ x: c * (1 + a * c) / denom - ox,
464
+ y: s * c * (1 + b * c) / denom
502
465
  };
503
466
  }
504
467
  function epitrochoid7(t, _time, _params) {
505
468
  const d = 1 + 0.55 * Math.sin(t * 0.5);
506
469
  return {
507
470
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
508
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
471
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
509
472
  };
510
473
  }
511
474
  function epitrochoid7Skeleton(t) {
512
475
  const d = 1.275;
513
476
  return {
514
477
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
515
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
478
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
516
479
  };
517
480
  }
518
481
  function astroid(t, _time, _params) {
@@ -520,56 +483,55 @@ function astroid(t, _time, _params) {
520
483
  const s = Math.sin(t);
521
484
  return {
522
485
  x: c * c * c,
523
- y: s * s * s,
486
+ y: s * s * s
524
487
  };
525
488
  }
526
489
  function deltoid(t, _time, _params) {
527
490
  return {
528
491
  x: 2 * Math.cos(t) + Math.cos(2 * t),
529
- y: 2 * Math.sin(t) - Math.sin(2 * t),
492
+ y: 2 * Math.sin(t) - Math.sin(2 * t)
530
493
  };
531
494
  }
532
495
  function rose5(t, _time, _params) {
533
496
  const r = Math.cos(5 * t);
534
497
  return {
535
498
  x: r * Math.cos(t),
536
- y: r * Math.sin(t),
499
+ y: r * Math.sin(t)
537
500
  };
538
501
  }
539
502
  function rose3(t, _time, _params) {
540
503
  const r = Math.cos(3 * t);
541
504
  return {
542
505
  x: r * Math.cos(t),
543
- y: r * Math.sin(t),
506
+ y: r * Math.sin(t)
544
507
  };
545
508
  }
546
509
  function lissajous32(t, time, _params) {
547
510
  const phi = time * 0.45;
548
511
  return {
549
512
  x: Math.sin(3 * t + phi),
550
- y: Math.sin(2 * t),
513
+ y: Math.sin(2 * t)
551
514
  };
552
515
  }
553
516
  function lissajous43(t, time, _params) {
554
517
  const phi = time * 0.38;
555
518
  return {
556
519
  x: Math.sin(4 * t + phi),
557
- y: Math.sin(3 * t),
520
+ y: Math.sin(3 * t)
558
521
  };
559
522
  }
560
523
  function epicycloid3(t, _time, _params) {
561
524
  return {
562
525
  x: 4 * Math.cos(t) - Math.cos(4 * t),
563
- y: 4 * Math.sin(t) - Math.sin(4 * t),
526
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
564
527
  };
565
528
  }
566
529
  function lame(t, time, _params) {
567
530
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
568
- const c = Math.cos(t),
569
- s = Math.sin(t);
531
+ const c = Math.cos(t), s = Math.sin(t);
570
532
  return {
571
533
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
572
- y: Math.sign(s) * Math.pow(Math.abs(s), p),
534
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
573
535
  };
574
536
  }
575
537
  var curves = {
@@ -577,66 +539,66 @@ var curves = {
577
539
  name: "Artemis II",
578
540
  fn: artemis2,
579
541
  period: TWO_PI2,
580
- speed: 0.7,
542
+ speed: 0.7
581
543
  },
582
544
  epitrochoid7: {
583
545
  name: "Epitrochoid",
584
546
  fn: epitrochoid7,
585
547
  period: TWO_PI2,
586
548
  speed: 1.4,
587
- skeletonFn: epitrochoid7Skeleton,
549
+ skeletonFn: epitrochoid7Skeleton
588
550
  },
589
551
  astroid: {
590
552
  name: "Astroid",
591
553
  fn: astroid,
592
554
  period: TWO_PI2,
593
- speed: 1.1,
555
+ speed: 1.1
594
556
  },
595
557
  deltoid: {
596
558
  name: "Deltoid",
597
559
  fn: deltoid,
598
560
  period: TWO_PI2,
599
- speed: 0.9,
561
+ speed: 0.9
600
562
  },
601
563
  rose5: {
602
564
  name: "Rose (n=5)",
603
565
  fn: rose5,
604
566
  period: TWO_PI2,
605
- speed: 1,
567
+ speed: 1
606
568
  },
607
569
  rose3: {
608
570
  name: "Rose (n=3)",
609
571
  fn: rose3,
610
572
  period: TWO_PI2,
611
- speed: 1.15,
573
+ speed: 1.15
612
574
  },
613
575
  lissajous32: {
614
576
  name: "Lissajous 3:2",
615
577
  fn: lissajous32,
616
578
  period: TWO_PI2,
617
579
  speed: 2,
618
- skeleton: "live",
580
+ skeleton: "live"
619
581
  },
620
582
  lissajous43: {
621
583
  name: "Lissajous 4:3",
622
584
  fn: lissajous43,
623
585
  period: TWO_PI2,
624
586
  speed: 1.8,
625
- skeleton: "live",
587
+ skeleton: "live"
626
588
  },
627
589
  epicycloid3: {
628
590
  name: "Epicycloid (n=3)",
629
591
  fn: epicycloid3,
630
592
  period: TWO_PI2,
631
- speed: 0.75,
593
+ speed: 0.75
632
594
  },
633
595
  lame: {
634
596
  name: "Lam\xE9 Curve",
635
597
  fn: lame,
636
598
  period: TWO_PI2,
637
599
  speed: 1,
638
- skeleton: "live",
639
- },
600
+ skeleton: "live"
601
+ }
640
602
  };
641
603
 
642
604
  // src/index.ts
@@ -659,12 +621,12 @@ function init() {
659
621
  return console.error(`[sarmal] "${curveName}" is not a valid curve name`);
660
622
  }
661
623
  const sarmal = createSarmal(canvas, curveDef, {
662
- ...(canvas.dataset.trailColor && { trailColor: canvas.dataset.trailColor }),
663
- ...(canvas.dataset.skeletonColor && { skeletonColor: canvas.dataset.skeletonColor }),
664
- ...(canvas.dataset.headColor && { headColor: canvas.dataset.headColor }),
665
- ...(canvas.dataset.headRadius && { headRadius: parseFloat(canvas.dataset.headRadius) }),
666
- ...(canvas.dataset.glowSize && { glowSize: parseInt(canvas.dataset.glowSize, 10) }),
667
- ...(canvas.dataset.trailLength && { trailLength: parseInt(canvas.dataset.trailLength, 10) }),
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) }
668
630
  });
669
631
  sarmal.start();
670
632
  });
@@ -675,4 +637,4 @@ if (document.readyState === "loading") {
675
637
  init();
676
638
  }
677
639
  //# sourceMappingURL=auto-init.cjs.map
678
- //# sourceMappingURL=auto-init.cjs.map
640
+ //# sourceMappingURL=auto-init.cjs.map