@sarmal/core 0.5.0 → 0.6.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/dist/auto-init.cjs +216 -60
- package/dist/auto-init.cjs.map +1 -1
- package/dist/auto-init.d.cts +1 -2
- package/dist/auto-init.d.ts +1 -2
- package/dist/auto-init.js +215 -59
- package/dist/auto-init.js.map +1 -1
- package/dist/index.cjs +326 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +228 -163
- package/dist/index.d.ts +228 -163
- package/dist/index.js +325 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42,24 +42,46 @@ var CircularBuffer = class {
|
|
|
42
42
|
return this.count;
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
|
-
function
|
|
46
|
-
|
|
45
|
+
function resolveCurve(curveDef) {
|
|
46
|
+
return {
|
|
47
47
|
name: curveDef.name,
|
|
48
48
|
fn: curveDef.fn,
|
|
49
49
|
period: curveDef.period ?? TWO_PI,
|
|
50
50
|
speed: curveDef.speed ?? 1,
|
|
51
51
|
skeleton: curveDef.skeleton,
|
|
52
|
-
skeletonFn: curveDef.skeletonFn
|
|
52
|
+
skeletonFn: curveDef.skeletonFn,
|
|
53
53
|
};
|
|
54
|
+
}
|
|
55
|
+
function createEngine(curveDef, trailLength = 120) {
|
|
56
|
+
let curve = resolveCurve(curveDef);
|
|
54
57
|
const trail = new CircularBuffer(trailLength);
|
|
55
58
|
let t = 0;
|
|
56
59
|
let actualTime = 0;
|
|
60
|
+
let morphCurveB = null;
|
|
61
|
+
let _morphAlpha = null;
|
|
62
|
+
let _morphStrategy = "normalized";
|
|
63
|
+
function sampleSkeleton(c, sampleT) {
|
|
64
|
+
if (c.skeletonFn) {
|
|
65
|
+
return c.skeletonFn(sampleT);
|
|
66
|
+
}
|
|
67
|
+
if (c.skeleton === "live") {
|
|
68
|
+
return c.fn(sampleT, actualTime, {});
|
|
69
|
+
}
|
|
70
|
+
return c.fn(sampleT, 0, {});
|
|
71
|
+
}
|
|
57
72
|
return {
|
|
58
73
|
tick(deltaTime) {
|
|
59
74
|
t = (t + curve.speed * deltaTime) % curve.period;
|
|
60
75
|
actualTime += deltaTime;
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
77
|
+
const a = curve.fn(t, actualTime, {});
|
|
78
|
+
const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
|
|
79
|
+
const b = morphCurveB.fn(tB, actualTime, {});
|
|
80
|
+
trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
|
|
81
|
+
} else {
|
|
82
|
+
const point = curve.fn(t, actualTime, {});
|
|
83
|
+
trail.push(point.x, point.y);
|
|
84
|
+
}
|
|
63
85
|
return trail.toArray();
|
|
64
86
|
},
|
|
65
87
|
get trailCount() {
|
|
@@ -68,20 +90,23 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
68
90
|
get isLiveSkeleton() {
|
|
69
91
|
return curve.skeleton === "live";
|
|
70
92
|
},
|
|
93
|
+
get morphAlpha() {
|
|
94
|
+
return _morphAlpha;
|
|
95
|
+
},
|
|
71
96
|
reset() {
|
|
72
97
|
t = 0;
|
|
73
98
|
actualTime = 0;
|
|
74
99
|
trail.clear();
|
|
75
100
|
},
|
|
76
101
|
seek(newT, { clearTrail = false } = {}) {
|
|
77
|
-
t = (newT % curve.period + curve.period) % curve.period;
|
|
102
|
+
t = ((newT % curve.period) + curve.period) % curve.period;
|
|
78
103
|
if (clearTrail) {
|
|
79
104
|
trail.clear();
|
|
80
105
|
}
|
|
81
106
|
},
|
|
82
107
|
seekWithTrail(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
|
|
83
108
|
const advance = curve.speed * step;
|
|
84
|
-
const target = (targetT % curve.period + curve.period) % curve.period;
|
|
109
|
+
const target = ((targetT % curve.period) + curve.period) % curve.period;
|
|
85
110
|
const targetTime = target / curve.speed;
|
|
86
111
|
t = target;
|
|
87
112
|
actualTime = targetTime;
|
|
@@ -96,31 +121,73 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
96
121
|
trail.push(point.x, point.y);
|
|
97
122
|
}
|
|
98
123
|
},
|
|
124
|
+
startMorph(target, strategy = "normalized") {
|
|
125
|
+
const resolvedTarget = resolveCurve(target);
|
|
126
|
+
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
127
|
+
const frozenAlpha = _morphAlpha;
|
|
128
|
+
const frozenA = curve;
|
|
129
|
+
const frozenB = morphCurveB;
|
|
130
|
+
const frozenStrategy = _morphStrategy;
|
|
131
|
+
curve = {
|
|
132
|
+
...frozenB,
|
|
133
|
+
fn: (sampleT, time, params) => {
|
|
134
|
+
const a = frozenA.fn(sampleT, time, params);
|
|
135
|
+
const tB =
|
|
136
|
+
frozenStrategy === "normalized"
|
|
137
|
+
? (sampleT / frozenA.period) * frozenB.period
|
|
138
|
+
: sampleT;
|
|
139
|
+
const b = frozenB.fn(tB, time, params);
|
|
140
|
+
return {
|
|
141
|
+
x: a.x + (b.x - a.x) * frozenAlpha,
|
|
142
|
+
y: a.y + (b.y - a.y) * frozenAlpha,
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
_morphStrategy = strategy;
|
|
148
|
+
morphCurveB = resolvedTarget;
|
|
149
|
+
_morphAlpha = 0;
|
|
150
|
+
},
|
|
151
|
+
setMorphAlpha(alpha) {
|
|
152
|
+
_morphAlpha = alpha;
|
|
153
|
+
},
|
|
154
|
+
completeMorph() {
|
|
155
|
+
if (morphCurveB !== null) {
|
|
156
|
+
curve = morphCurveB;
|
|
157
|
+
}
|
|
158
|
+
morphCurveB = null;
|
|
159
|
+
_morphAlpha = null;
|
|
160
|
+
},
|
|
99
161
|
getSarmalSkeleton() {
|
|
100
162
|
const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);
|
|
101
163
|
const points = new Array(steps);
|
|
102
|
-
if (
|
|
164
|
+
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
103
165
|
for (let i = 0; i < steps; i++) {
|
|
104
|
-
const sampleT = i / (steps - 1) * curve.period;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
points[i] = curve.fn(sampleT, 0, {});
|
|
166
|
+
const sampleT = (i / (steps - 1)) * curve.period;
|
|
167
|
+
const a = sampleSkeleton(curve, sampleT);
|
|
168
|
+
const tB =
|
|
169
|
+
_morphStrategy === "normalized"
|
|
170
|
+
? (sampleT / curve.period) * morphCurveB.period
|
|
171
|
+
: sampleT;
|
|
172
|
+
const b = sampleSkeleton(morphCurveB, tB);
|
|
173
|
+
points[i] = {
|
|
174
|
+
x: a.x + (b.x - a.x) * _morphAlpha,
|
|
175
|
+
y: a.y + (b.y - a.y) * _morphAlpha,
|
|
176
|
+
};
|
|
116
177
|
}
|
|
178
|
+
return points;
|
|
179
|
+
}
|
|
180
|
+
for (let i = 0; i < steps; i++) {
|
|
181
|
+
const sampleT = (i / (steps - 1)) * curve.period;
|
|
182
|
+
points[i] = sampleSkeleton(curve, sampleT);
|
|
117
183
|
}
|
|
118
184
|
return points;
|
|
119
|
-
}
|
|
185
|
+
},
|
|
120
186
|
};
|
|
121
187
|
}
|
|
122
188
|
|
|
123
189
|
// src/renderer.ts
|
|
190
|
+
var DEFAULT_MORPH_DURATION_MS = 300;
|
|
124
191
|
var DEFAULT_HEAD_RADIUS = 4;
|
|
125
192
|
var DEFAULT_GLOW_SIZE = 20;
|
|
126
193
|
var DEFAULT_SKELETON_COLOR = "#ffffff";
|
|
@@ -135,7 +202,7 @@ var GLOW_INNER_EDGE = 0.4;
|
|
|
135
202
|
var GLOW_FALLOFF_OPACITY = 0.53;
|
|
136
203
|
function hexToRgbComponents(hex) {
|
|
137
204
|
const n = parseInt(hex.slice(1), 16);
|
|
138
|
-
return `${n >> 16},${n >> 8 & 255},${n & 255}`;
|
|
205
|
+
return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
|
|
139
206
|
}
|
|
140
207
|
function createRenderer(options) {
|
|
141
208
|
const canvas = options.canvas;
|
|
@@ -149,7 +216,7 @@ function createRenderer(options) {
|
|
|
149
216
|
trailColor: options.trailColor ?? "#ffffff",
|
|
150
217
|
headColor: options.headColor ?? "#ffffff",
|
|
151
218
|
headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
|
|
152
|
-
glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE
|
|
219
|
+
glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE,
|
|
153
220
|
};
|
|
154
221
|
const trailRgb = hexToRgbComponents(opts.trailColor);
|
|
155
222
|
const headRgbFalloff = `rgba(${hexToRgbComponents(opts.headColor)},${GLOW_FALLOFF_OPACITY})`;
|
|
@@ -163,12 +230,21 @@ function createRenderer(options) {
|
|
|
163
230
|
let offsetY = 0;
|
|
164
231
|
let animationId = null;
|
|
165
232
|
let lastTime = 0;
|
|
233
|
+
let morphResolve = null;
|
|
234
|
+
let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
|
|
235
|
+
let morphTarget = null;
|
|
236
|
+
let morphAlpha = 0;
|
|
237
|
+
let skeletonCanvasA = null;
|
|
238
|
+
let skeletonCanvasB = null;
|
|
166
239
|
function calculateBoundaries() {
|
|
167
240
|
if (skeleton.length === 0) {
|
|
168
241
|
return;
|
|
169
242
|
}
|
|
170
243
|
const first = skeleton[0];
|
|
171
|
-
let minX = first.x,
|
|
244
|
+
let minX = first.x,
|
|
245
|
+
maxX = first.x,
|
|
246
|
+
minY = first.y,
|
|
247
|
+
maxY = first.y;
|
|
172
248
|
for (const p of skeleton) {
|
|
173
249
|
if (p.x < minX) {
|
|
174
250
|
minX = p.x;
|
|
@@ -214,6 +290,18 @@ function createRenderer(options) {
|
|
|
214
290
|
if (opts.skeletonColor === "transparent") {
|
|
215
291
|
return;
|
|
216
292
|
}
|
|
293
|
+
if (engine.morphAlpha !== null) {
|
|
294
|
+
if (skeletonCanvasA) {
|
|
295
|
+
ctx.globalAlpha = (1 - morphAlpha) * DEFAULT_SKELETON_OPACITY;
|
|
296
|
+
ctx.drawImage(skeletonCanvasA, 0, 0);
|
|
297
|
+
}
|
|
298
|
+
if (skeletonCanvasB) {
|
|
299
|
+
ctx.globalAlpha = morphAlpha * DEFAULT_SKELETON_OPACITY;
|
|
300
|
+
ctx.drawImage(skeletonCanvasB, 0, 0);
|
|
301
|
+
}
|
|
302
|
+
ctx.globalAlpha = 1;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
217
305
|
if (engine.isLiveSkeleton) {
|
|
218
306
|
if (skeleton.length < 2) {
|
|
219
307
|
return;
|
|
@@ -280,11 +368,26 @@ function createRenderer(options) {
|
|
|
280
368
|
const now = performance.now();
|
|
281
369
|
const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
|
|
282
370
|
lastTime = now;
|
|
371
|
+
if (engine.morphAlpha !== null) {
|
|
372
|
+
morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
|
|
373
|
+
engine.setMorphAlpha(morphAlpha);
|
|
374
|
+
skeleton = engine.getSarmalSkeleton();
|
|
375
|
+
calculateBoundaries();
|
|
376
|
+
if (morphAlpha >= 1) {
|
|
377
|
+
engine.completeMorph();
|
|
378
|
+
morphResolve?.();
|
|
379
|
+
morphResolve = null;
|
|
380
|
+
morphTarget = null;
|
|
381
|
+
morphAlpha = 0;
|
|
382
|
+
skeletonCanvasA = null;
|
|
383
|
+
skeletonCanvasB = null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
283
386
|
trail = engine.tick(deltaTime);
|
|
284
387
|
trailCount = engine.trailCount;
|
|
285
388
|
head = trailCount > 0 ? trail[trailCount - 1] : null;
|
|
286
389
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
287
|
-
if (engine.isLiveSkeleton) {
|
|
390
|
+
if (engine.isLiveSkeleton || engine.morphAlpha !== null) {
|
|
288
391
|
skeleton = engine.getSarmalSkeleton();
|
|
289
392
|
calculateBoundaries();
|
|
290
393
|
}
|
|
@@ -329,11 +432,61 @@ function createRenderer(options) {
|
|
|
329
432
|
},
|
|
330
433
|
seekWithTrail(t) {
|
|
331
434
|
engine.seekWithTrail(t);
|
|
332
|
-
}
|
|
435
|
+
},
|
|
436
|
+
morphTo(target, options2) {
|
|
437
|
+
if (morphResolve !== null) {
|
|
438
|
+
engine.completeMorph();
|
|
439
|
+
morphResolve();
|
|
440
|
+
morphResolve = null;
|
|
441
|
+
morphAlpha = 0;
|
|
442
|
+
skeletonCanvasA = null;
|
|
443
|
+
skeletonCanvasB = null;
|
|
444
|
+
}
|
|
445
|
+
morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS;
|
|
446
|
+
morphTarget = target;
|
|
447
|
+
morphAlpha = 0;
|
|
448
|
+
const currentSkeleton = engine.getSarmalSkeleton();
|
|
449
|
+
if (currentSkeleton.length >= 2) {
|
|
450
|
+
skeletonCanvasA = new OffscreenCanvas(canvas.width, canvas.height);
|
|
451
|
+
const ctxA = skeletonCanvasA.getContext("2d");
|
|
452
|
+
ctxA.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
|
|
453
|
+
ctxA.lineWidth = 1.5;
|
|
454
|
+
ctxA.beginPath();
|
|
455
|
+
const first = currentSkeleton[0];
|
|
456
|
+
ctxA.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);
|
|
457
|
+
for (let i = 1; i < currentSkeleton.length; i++) {
|
|
458
|
+
const p = currentSkeleton[i];
|
|
459
|
+
ctxA.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
|
|
460
|
+
}
|
|
461
|
+
ctxA.stroke();
|
|
462
|
+
}
|
|
463
|
+
engine.startMorph(target, options2?.morphStrategy);
|
|
464
|
+
if (morphTarget && !engine.isLiveSkeleton) {
|
|
465
|
+
skeletonCanvasB = new OffscreenCanvas(canvas.width, canvas.height);
|
|
466
|
+
const skeletonCtx = skeletonCanvasB.getContext("2d");
|
|
467
|
+
skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
|
|
468
|
+
skeletonCtx.lineWidth = 1.5;
|
|
469
|
+
skeletonCtx.beginPath();
|
|
470
|
+
const period = morphTarget.period ?? Math.PI * 2;
|
|
471
|
+
const samples = Math.max(50, Math.round(period * 20));
|
|
472
|
+
const firstB = morphTarget.fn(0, 0, {});
|
|
473
|
+
skeletonCtx.moveTo(firstB.x * scale + offsetX, firstB.y * scale + offsetY);
|
|
474
|
+
for (let i = 1; i <= samples; i++) {
|
|
475
|
+
const t = (i / samples) * period;
|
|
476
|
+
const p = morphTarget.fn(t, 0, {});
|
|
477
|
+
skeletonCtx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
|
|
478
|
+
}
|
|
479
|
+
skeletonCtx.stroke();
|
|
480
|
+
}
|
|
481
|
+
return new Promise((resolve) => {
|
|
482
|
+
morphResolve = resolve;
|
|
483
|
+
});
|
|
484
|
+
},
|
|
333
485
|
};
|
|
334
486
|
}
|
|
335
487
|
|
|
336
488
|
// src/renderer-svg.ts
|
|
489
|
+
var DEFAULT_MORPH_DURATION_MS2 = 300;
|
|
337
490
|
var TRAIL_BATCH_COUNT = 12;
|
|
338
491
|
var TRAIL_FADE_CURVE2 = 1.5;
|
|
339
492
|
var TRAIL_MAX_OPACITY2 = 0.88;
|
|
@@ -355,7 +508,7 @@ function createSVGRenderer(options) {
|
|
|
355
508
|
headColor: options.headColor ?? "#ffffff",
|
|
356
509
|
headRadius: options.headRadius ?? 4,
|
|
357
510
|
glowSize: options.glowSize ?? 20,
|
|
358
|
-
ariaLabel: options.ariaLabel ?? "Loading"
|
|
511
|
+
ariaLabel: options.ariaLabel ?? "Loading",
|
|
359
512
|
};
|
|
360
513
|
const uid = ++instanceCount;
|
|
361
514
|
const gradientId = `sarmal-glow-${uid}`;
|
|
@@ -398,6 +551,20 @@ function createSVGRenderer(options) {
|
|
|
398
551
|
skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY2));
|
|
399
552
|
skeletonPath.setAttribute("stroke-width", "1.5");
|
|
400
553
|
svg.appendChild(skeletonPath);
|
|
554
|
+
const skeletonPathA = el("path");
|
|
555
|
+
skeletonPathA.setAttribute("fill", "none");
|
|
556
|
+
skeletonPathA.setAttribute("stroke", opts.skeletonColor);
|
|
557
|
+
skeletonPathA.setAttribute("stroke-width", "1.5");
|
|
558
|
+
skeletonPathA.setAttribute("visibility", "hidden");
|
|
559
|
+
svg.appendChild(skeletonPathA);
|
|
560
|
+
const skeletonPathB = el("path");
|
|
561
|
+
skeletonPathB.setAttribute("fill", "none");
|
|
562
|
+
skeletonPathB.setAttribute("stroke", opts.skeletonColor);
|
|
563
|
+
skeletonPathB.setAttribute("stroke-width", "1.5");
|
|
564
|
+
skeletonPathB.setAttribute("visibility", "hidden");
|
|
565
|
+
svg.appendChild(skeletonPathB);
|
|
566
|
+
let morphPathABuilt = "";
|
|
567
|
+
let morphPathBBuilt = "";
|
|
401
568
|
const trailPaths = [];
|
|
402
569
|
for (let i = 0; i < TRAIL_BATCH_COUNT; i++) {
|
|
403
570
|
const path = el("path");
|
|
@@ -425,7 +592,10 @@ function createSVGRenderer(options) {
|
|
|
425
592
|
return;
|
|
426
593
|
}
|
|
427
594
|
const first = skeleton2[0];
|
|
428
|
-
let minX = first.x,
|
|
595
|
+
let minX = first.x,
|
|
596
|
+
maxX = first.x,
|
|
597
|
+
minY = first.y,
|
|
598
|
+
maxY = first.y;
|
|
429
599
|
for (const p of skeleton2) {
|
|
430
600
|
if (p.x < minX) {
|
|
431
601
|
minX = p.x;
|
|
@@ -512,11 +682,73 @@ function createSVGRenderer(options) {
|
|
|
512
682
|
}
|
|
513
683
|
let animationId = null;
|
|
514
684
|
let lastTime = 0;
|
|
515
|
-
const prefersReducedMotion =
|
|
685
|
+
const prefersReducedMotion =
|
|
686
|
+
typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
687
|
+
let morphResolve = null;
|
|
688
|
+
let morphDurationMs = DEFAULT_MORPH_DURATION_MS2;
|
|
689
|
+
let morphTarget = null;
|
|
690
|
+
let morphAlpha = 0;
|
|
691
|
+
function buildSkeletonPath(target, scale2, offsetX2, offsetY2) {
|
|
692
|
+
const period = target.period ?? Math.PI * 2;
|
|
693
|
+
const samples = Math.max(50, Math.round(period * 20));
|
|
694
|
+
const points = [];
|
|
695
|
+
for (let i = 0; i <= samples; i++) {
|
|
696
|
+
const t = (i / samples) * period;
|
|
697
|
+
const p = target.fn(t, 0, {});
|
|
698
|
+
points.push(p);
|
|
699
|
+
}
|
|
700
|
+
if (points.length < 2) {
|
|
701
|
+
return "";
|
|
702
|
+
}
|
|
703
|
+
const px2 = (p) => (p.x * scale2 + offsetX2).toFixed(2);
|
|
704
|
+
const py2 = (p) => (p.y * scale2 + offsetY2).toFixed(2);
|
|
705
|
+
let d = `M${px2(points[0])} ${py2(points[0])}`;
|
|
706
|
+
for (let i = 1; i < points.length; i++) {
|
|
707
|
+
d += ` L${px2(points[i])} ${py2(points[i])}`;
|
|
708
|
+
}
|
|
709
|
+
d += " Z";
|
|
710
|
+
return d;
|
|
711
|
+
}
|
|
516
712
|
function renderFrame() {
|
|
517
713
|
const now = performance.now();
|
|
518
714
|
const dt = Math.min((now - lastTime) / 1e3, 1 / 30);
|
|
519
715
|
lastTime = now;
|
|
716
|
+
if (engine.morphAlpha !== null) {
|
|
717
|
+
morphAlpha = Math.min(1, morphAlpha + dt / (morphDurationMs / 1e3));
|
|
718
|
+
engine.setMorphAlpha(morphAlpha);
|
|
719
|
+
const morphSkeleton = engine.getSarmalSkeleton();
|
|
720
|
+
calculateBoundaries(morphSkeleton);
|
|
721
|
+
if (!engine.isLiveSkeleton) {
|
|
722
|
+
updateSkeleton(morphSkeleton);
|
|
723
|
+
}
|
|
724
|
+
if (morphPathABuilt) {
|
|
725
|
+
skeletonPathA.setAttribute("d", morphPathABuilt);
|
|
726
|
+
skeletonPathA.setAttribute("visibility", "visible");
|
|
727
|
+
skeletonPathA.setAttribute(
|
|
728
|
+
"stroke-opacity",
|
|
729
|
+
String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2),
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
if (morphPathBBuilt) {
|
|
733
|
+
skeletonPathB.setAttribute("d", morphPathBBuilt);
|
|
734
|
+
skeletonPathB.setAttribute("visibility", "visible");
|
|
735
|
+
skeletonPathB.setAttribute(
|
|
736
|
+
"stroke-opacity",
|
|
737
|
+
String(morphAlpha * DEFAULT_SKELETON_OPACITY2),
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
if (morphAlpha >= 1) {
|
|
741
|
+
engine.completeMorph();
|
|
742
|
+
morphResolve?.();
|
|
743
|
+
morphResolve = null;
|
|
744
|
+
morphTarget = null;
|
|
745
|
+
morphAlpha = 0;
|
|
746
|
+
morphPathABuilt = "";
|
|
747
|
+
morphPathBBuilt = "";
|
|
748
|
+
skeletonPathA.setAttribute("visibility", "hidden");
|
|
749
|
+
skeletonPathB.setAttribute("visibility", "hidden");
|
|
750
|
+
}
|
|
751
|
+
}
|
|
520
752
|
const trail = engine.tick(dt);
|
|
521
753
|
const trailCount = engine.trailCount;
|
|
522
754
|
if (engine.isLiveSkeleton) {
|
|
@@ -560,7 +792,39 @@ function createSVGRenderer(options) {
|
|
|
560
792
|
},
|
|
561
793
|
seekWithTrail(t) {
|
|
562
794
|
engine.seekWithTrail(t);
|
|
563
|
-
}
|
|
795
|
+
},
|
|
796
|
+
morphTo(target, options2) {
|
|
797
|
+
if (morphResolve !== null) {
|
|
798
|
+
engine.completeMorph();
|
|
799
|
+
morphResolve();
|
|
800
|
+
morphResolve = null;
|
|
801
|
+
morphAlpha = 0;
|
|
802
|
+
skeletonPathA.setAttribute("visibility", "hidden");
|
|
803
|
+
skeletonPathB.setAttribute("visibility", "hidden");
|
|
804
|
+
}
|
|
805
|
+
morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS2;
|
|
806
|
+
morphTarget = target;
|
|
807
|
+
morphAlpha = 0;
|
|
808
|
+
const currentSkeleton = engine.getSarmalSkeleton();
|
|
809
|
+
if (currentSkeleton.length >= 2) {
|
|
810
|
+
const px2 = (p) => (p.x * scale + offsetX).toFixed(2);
|
|
811
|
+
const py2 = (p) => (p.y * scale + offsetY).toFixed(2);
|
|
812
|
+
morphPathABuilt = `M${px2(currentSkeleton[0])} ${py2(currentSkeleton[0])}`;
|
|
813
|
+
for (let i = 1; i < currentSkeleton.length; i++) {
|
|
814
|
+
morphPathABuilt += ` L${px2(currentSkeleton[i])} ${py2(currentSkeleton[i])}`;
|
|
815
|
+
}
|
|
816
|
+
morphPathABuilt += " Z";
|
|
817
|
+
} else {
|
|
818
|
+
morphPathABuilt = "";
|
|
819
|
+
}
|
|
820
|
+
engine.startMorph(target, options2?.morphStrategy);
|
|
821
|
+
if (morphTarget) {
|
|
822
|
+
morphPathBBuilt = buildSkeletonPath(morphTarget, scale, offsetX, offsetY);
|
|
823
|
+
}
|
|
824
|
+
return new Promise((resolve) => {
|
|
825
|
+
morphResolve = resolve;
|
|
826
|
+
});
|
|
827
|
+
},
|
|
564
828
|
};
|
|
565
829
|
}
|
|
566
830
|
function createSarmalSVG(container, curveDef, options) {
|
|
@@ -572,26 +836,29 @@ function createSarmalSVG(container, curveDef, options) {
|
|
|
572
836
|
// src/curves.ts
|
|
573
837
|
var TWO_PI2 = Math.PI * 2;
|
|
574
838
|
function artemis2(t, _time, _params) {
|
|
575
|
-
const a = 0.35,
|
|
576
|
-
|
|
839
|
+
const a = 0.35,
|
|
840
|
+
b = 0.15,
|
|
841
|
+
ox = 0.175;
|
|
842
|
+
const s = Math.sin(t),
|
|
843
|
+
c = Math.cos(t);
|
|
577
844
|
const denom = 1 + s * s;
|
|
578
845
|
return {
|
|
579
|
-
x: c * (1 + a * c) / denom - ox,
|
|
580
|
-
y: s * c * (1 + b * c) / denom
|
|
846
|
+
x: (c * (1 + a * c)) / denom - ox,
|
|
847
|
+
y: (s * c * (1 + b * c)) / denom,
|
|
581
848
|
};
|
|
582
849
|
}
|
|
583
850
|
function epitrochoid7(t, _time, _params) {
|
|
584
851
|
const d = 1 + 0.55 * Math.sin(t * 0.5);
|
|
585
852
|
return {
|
|
586
853
|
x: 7 * Math.cos(t) - d * Math.cos(7 * t),
|
|
587
|
-
y: 7 * Math.sin(t) - d * Math.sin(7 * t)
|
|
854
|
+
y: 7 * Math.sin(t) - d * Math.sin(7 * t),
|
|
588
855
|
};
|
|
589
856
|
}
|
|
590
857
|
function epitrochoid7Skeleton(t) {
|
|
591
858
|
const d = 1.275;
|
|
592
859
|
return {
|
|
593
860
|
x: 7 * Math.cos(t) - d * Math.cos(7 * t),
|
|
594
|
-
y: 7 * Math.sin(t) - d * Math.sin(7 * t)
|
|
861
|
+
y: 7 * Math.sin(t) - d * Math.sin(7 * t),
|
|
595
862
|
};
|
|
596
863
|
}
|
|
597
864
|
function astroid(t, _time, _params) {
|
|
@@ -599,55 +866,56 @@ function astroid(t, _time, _params) {
|
|
|
599
866
|
const s = Math.sin(t);
|
|
600
867
|
return {
|
|
601
868
|
x: c * c * c,
|
|
602
|
-
y: s * s * s
|
|
869
|
+
y: s * s * s,
|
|
603
870
|
};
|
|
604
871
|
}
|
|
605
872
|
function deltoid(t, _time, _params) {
|
|
606
873
|
return {
|
|
607
874
|
x: 2 * Math.cos(t) + Math.cos(2 * t),
|
|
608
|
-
y: 2 * Math.sin(t) - Math.sin(2 * t)
|
|
875
|
+
y: 2 * Math.sin(t) - Math.sin(2 * t),
|
|
609
876
|
};
|
|
610
877
|
}
|
|
611
878
|
function rose5(t, _time, _params) {
|
|
612
879
|
const r = Math.cos(5 * t);
|
|
613
880
|
return {
|
|
614
881
|
x: r * Math.cos(t),
|
|
615
|
-
y: r * Math.sin(t)
|
|
882
|
+
y: r * Math.sin(t),
|
|
616
883
|
};
|
|
617
884
|
}
|
|
618
885
|
function rose3(t, _time, _params) {
|
|
619
886
|
const r = Math.cos(3 * t);
|
|
620
887
|
return {
|
|
621
888
|
x: r * Math.cos(t),
|
|
622
|
-
y: r * Math.sin(t)
|
|
889
|
+
y: r * Math.sin(t),
|
|
623
890
|
};
|
|
624
891
|
}
|
|
625
892
|
function lissajous32(t, time, _params) {
|
|
626
893
|
const phi = time * 0.45;
|
|
627
894
|
return {
|
|
628
895
|
x: Math.sin(3 * t + phi),
|
|
629
|
-
y: Math.sin(2 * t)
|
|
896
|
+
y: Math.sin(2 * t),
|
|
630
897
|
};
|
|
631
898
|
}
|
|
632
899
|
function lissajous43(t, time, _params) {
|
|
633
900
|
const phi = time * 0.38;
|
|
634
901
|
return {
|
|
635
902
|
x: Math.sin(4 * t + phi),
|
|
636
|
-
y: Math.sin(3 * t)
|
|
903
|
+
y: Math.sin(3 * t),
|
|
637
904
|
};
|
|
638
905
|
}
|
|
639
906
|
function epicycloid3(t, _time, _params) {
|
|
640
907
|
return {
|
|
641
908
|
x: 4 * Math.cos(t) - Math.cos(4 * t),
|
|
642
|
-
y: 4 * Math.sin(t) - Math.sin(4 * t)
|
|
909
|
+
y: 4 * Math.sin(t) - Math.sin(4 * t),
|
|
643
910
|
};
|
|
644
911
|
}
|
|
645
912
|
function lame(t, time, _params) {
|
|
646
913
|
const p = 1.75 + 1.25 * Math.sin(time * 0.48);
|
|
647
|
-
const c = Math.cos(t),
|
|
914
|
+
const c = Math.cos(t),
|
|
915
|
+
s = Math.sin(t);
|
|
648
916
|
return {
|
|
649
917
|
x: Math.sign(c) * Math.pow(Math.abs(c), p),
|
|
650
|
-
y: Math.sign(s) * Math.pow(Math.abs(s), p)
|
|
918
|
+
y: Math.sign(s) * Math.pow(Math.abs(s), p),
|
|
651
919
|
};
|
|
652
920
|
}
|
|
653
921
|
var curves = {
|
|
@@ -655,66 +923,66 @@ var curves = {
|
|
|
655
923
|
name: "Artemis II",
|
|
656
924
|
fn: artemis2,
|
|
657
925
|
period: TWO_PI2,
|
|
658
|
-
speed: 0.7
|
|
926
|
+
speed: 0.7,
|
|
659
927
|
},
|
|
660
928
|
epitrochoid7: {
|
|
661
929
|
name: "Epitrochoid",
|
|
662
930
|
fn: epitrochoid7,
|
|
663
931
|
period: TWO_PI2,
|
|
664
932
|
speed: 1.4,
|
|
665
|
-
skeletonFn: epitrochoid7Skeleton
|
|
933
|
+
skeletonFn: epitrochoid7Skeleton,
|
|
666
934
|
},
|
|
667
935
|
astroid: {
|
|
668
936
|
name: "Astroid",
|
|
669
937
|
fn: astroid,
|
|
670
938
|
period: TWO_PI2,
|
|
671
|
-
speed: 1.1
|
|
939
|
+
speed: 1.1,
|
|
672
940
|
},
|
|
673
941
|
deltoid: {
|
|
674
942
|
name: "Deltoid",
|
|
675
943
|
fn: deltoid,
|
|
676
944
|
period: TWO_PI2,
|
|
677
|
-
speed: 0.9
|
|
945
|
+
speed: 0.9,
|
|
678
946
|
},
|
|
679
947
|
rose5: {
|
|
680
948
|
name: "Rose (n=5)",
|
|
681
949
|
fn: rose5,
|
|
682
950
|
period: TWO_PI2,
|
|
683
|
-
speed: 1
|
|
951
|
+
speed: 1,
|
|
684
952
|
},
|
|
685
953
|
rose3: {
|
|
686
954
|
name: "Rose (n=3)",
|
|
687
955
|
fn: rose3,
|
|
688
956
|
period: TWO_PI2,
|
|
689
|
-
speed: 1.15
|
|
957
|
+
speed: 1.15,
|
|
690
958
|
},
|
|
691
959
|
lissajous32: {
|
|
692
960
|
name: "Lissajous 3:2",
|
|
693
961
|
fn: lissajous32,
|
|
694
962
|
period: TWO_PI2,
|
|
695
963
|
speed: 2,
|
|
696
|
-
skeleton: "live"
|
|
964
|
+
skeleton: "live",
|
|
697
965
|
},
|
|
698
966
|
lissajous43: {
|
|
699
967
|
name: "Lissajous 4:3",
|
|
700
968
|
fn: lissajous43,
|
|
701
969
|
period: TWO_PI2,
|
|
702
970
|
speed: 1.8,
|
|
703
|
-
skeleton: "live"
|
|
971
|
+
skeleton: "live",
|
|
704
972
|
},
|
|
705
973
|
epicycloid3: {
|
|
706
974
|
name: "Epicycloid (n=3)",
|
|
707
975
|
fn: epicycloid3,
|
|
708
976
|
period: TWO_PI2,
|
|
709
|
-
speed: 0.75
|
|
977
|
+
speed: 0.75,
|
|
710
978
|
},
|
|
711
979
|
lame: {
|
|
712
980
|
name: "Lam\xE9 Curve",
|
|
713
981
|
fn: lame,
|
|
714
982
|
period: TWO_PI2,
|
|
715
983
|
speed: 1,
|
|
716
|
-
skeleton: "live"
|
|
717
|
-
}
|
|
984
|
+
skeleton: "live",
|
|
985
|
+
},
|
|
718
986
|
};
|
|
719
987
|
|
|
720
988
|
// src/index.ts
|
|
@@ -726,4 +994,4 @@ function createSarmal(canvas, curveDef, options) {
|
|
|
726
994
|
|
|
727
995
|
export { createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, curves };
|
|
728
996
|
//# sourceMappingURL=index.js.map
|
|
729
|
-
//# sourceMappingURL=index.js.map
|
|
997
|
+
//# sourceMappingURL=index.js.map
|