@sarmal/core 0.7.0 → 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 +145 -111
- package/dist/auto-init.cjs.map +1 -1
- package/dist/auto-init.js +144 -110
- package/dist/auto-init.js.map +1 -1
- package/dist/index.cjs +181 -168
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +180 -167
- 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/dist/index.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 = [];
|
|
@@ -232,12 +275,13 @@ function createRenderer(options) {
|
|
|
232
275
|
let morphResolve = null;
|
|
233
276
|
let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
|
|
234
277
|
let morphAlpha = 0;
|
|
235
|
-
let morphBoundsA = null;
|
|
236
|
-
let morphBoundsB = null;
|
|
237
278
|
function computeBoundaries(pts) {
|
|
238
279
|
if (pts.length === 0) return null;
|
|
239
280
|
const first = pts[0];
|
|
240
|
-
let minX = first.x,
|
|
281
|
+
let minX = first.x,
|
|
282
|
+
maxX = first.x,
|
|
283
|
+
minY = first.y,
|
|
284
|
+
maxY = first.y;
|
|
241
285
|
for (const p of pts) {
|
|
242
286
|
if (p.x < minX) minX = p.x;
|
|
243
287
|
if (p.x > maxX) maxX = p.x;
|
|
@@ -246,17 +290,15 @@ function createRenderer(options) {
|
|
|
246
290
|
}
|
|
247
291
|
const width = maxX - minX;
|
|
248
292
|
const height = maxY - minY;
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const scaleX = canvasWidth / (width * (1 + FIT_PADDING * 2));
|
|
252
|
-
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));
|
|
253
295
|
const s = Math.min(scaleX, scaleY);
|
|
254
296
|
const boundsWidth = width * s;
|
|
255
297
|
const boundsHeight = height * s;
|
|
256
298
|
return {
|
|
257
299
|
scale: s,
|
|
258
|
-
offsetX: (
|
|
259
|
-
offsetY: (
|
|
300
|
+
offsetX: (logicalWidth - boundsWidth) / 2 - minX * s,
|
|
301
|
+
offsetY: (logicalHeight - boundsHeight) / 2 - minY * s,
|
|
260
302
|
};
|
|
261
303
|
}
|
|
262
304
|
function calculateBoundaries() {
|
|
@@ -271,6 +313,7 @@ function createRenderer(options) {
|
|
|
271
313
|
if (skeleton.length < 2) return;
|
|
272
314
|
skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
|
|
273
315
|
const skeletonCtx = skeletonCanvas.getContext("2d");
|
|
316
|
+
skeletonCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
274
317
|
skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
|
|
275
318
|
skeletonCtx.lineWidth = 1.5;
|
|
276
319
|
skeletonCtx.beginPath();
|
|
@@ -316,32 +359,41 @@ function createRenderer(options) {
|
|
|
316
359
|
}
|
|
317
360
|
ctx.stroke();
|
|
318
361
|
} else if (skeletonCanvas) {
|
|
319
|
-
ctx.drawImage(skeletonCanvas, 0, 0);
|
|
362
|
+
ctx.drawImage(skeletonCanvas, 0, 0, logicalWidth, logicalHeight);
|
|
320
363
|
}
|
|
321
364
|
}
|
|
322
365
|
function drawTrail() {
|
|
323
366
|
if (trailCount < 2) {
|
|
324
367
|
return;
|
|
325
368
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const bEnd = Math.min(batchIndex + TRAIL_BATCH_SIZE, trailCount - 1);
|
|
330
|
-
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);
|
|
331
372
|
const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
|
|
332
|
-
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})`;
|
|
333
390
|
ctx.beginPath();
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
ctx.strokeStyle = `rgba(${trailRgb},${alpha})`;
|
|
343
|
-
ctx.lineWidth = lineWidth;
|
|
344
|
-
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();
|
|
345
397
|
}
|
|
346
398
|
}
|
|
347
399
|
function drawHead() {
|
|
@@ -350,14 +402,6 @@ function createRenderer(options) {
|
|
|
350
402
|
}
|
|
351
403
|
const x = head.x * scale + offsetX;
|
|
352
404
|
const y = head.y * scale + offsetY;
|
|
353
|
-
const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);
|
|
354
|
-
gradient.addColorStop(0, opts.headColor);
|
|
355
|
-
gradient.addColorStop(GLOW_INNER_EDGE, headRgbFalloff);
|
|
356
|
-
gradient.addColorStop(1, "transparent");
|
|
357
|
-
ctx.fillStyle = gradient;
|
|
358
|
-
ctx.beginPath();
|
|
359
|
-
ctx.arc(x, y, opts.glowSize, 0, Math.PI * 2);
|
|
360
|
-
ctx.fill();
|
|
361
405
|
ctx.fillStyle = opts.headColor;
|
|
362
406
|
ctx.beginPath();
|
|
363
407
|
ctx.arc(x, y, opts.headRadius, 0, Math.PI * 2);
|
|
@@ -370,20 +414,18 @@ function createRenderer(options) {
|
|
|
370
414
|
if (engine.morphAlpha !== null) {
|
|
371
415
|
morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
|
|
372
416
|
engine.setMorphAlpha(morphAlpha);
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
scale =
|
|
377
|
-
offsetX =
|
|
378
|
-
offsetY =
|
|
417
|
+
const interpolatedSkeleton = engine.getSarmalSkeleton();
|
|
418
|
+
const bounds = computeBoundaries(interpolatedSkeleton);
|
|
419
|
+
if (bounds) {
|
|
420
|
+
scale = bounds.scale;
|
|
421
|
+
offsetX = bounds.offsetX;
|
|
422
|
+
offsetY = bounds.offsetY;
|
|
379
423
|
}
|
|
380
424
|
if (morphAlpha >= 1) {
|
|
381
425
|
engine.completeMorph();
|
|
382
426
|
morphResolve?.();
|
|
383
427
|
morphResolve = null;
|
|
384
428
|
morphAlpha = 0;
|
|
385
|
-
morphBoundsA = null;
|
|
386
|
-
morphBoundsB = null;
|
|
387
429
|
skeleton = engine.getSarmalSkeleton();
|
|
388
430
|
if (!engine.isLiveSkeleton) {
|
|
389
431
|
buildSkeletonCanvas();
|
|
@@ -393,7 +435,7 @@ function createRenderer(options) {
|
|
|
393
435
|
trail = engine.tick(deltaTime);
|
|
394
436
|
trailCount = engine.trailCount;
|
|
395
437
|
head = trailCount > 0 ? trail[trailCount - 1] : null;
|
|
396
|
-
ctx.clearRect(0, 0,
|
|
438
|
+
ctx.clearRect(0, 0, logicalWidth, logicalHeight);
|
|
397
439
|
if (engine.isLiveSkeleton && engine.morphAlpha === null) {
|
|
398
440
|
skeleton = engine.getSarmalSkeleton();
|
|
399
441
|
calculateBoundaries();
|
|
@@ -441,46 +483,31 @@ function createRenderer(options) {
|
|
|
441
483
|
engine.seekWithTrail(t);
|
|
442
484
|
},
|
|
443
485
|
morphTo(target, options2) {
|
|
444
|
-
const interruptBounds = morphResolve !== null ? { scale, offsetX, offsetY } : null;
|
|
445
486
|
if (morphResolve !== null) {
|
|
446
487
|
engine.completeMorph();
|
|
447
488
|
morphResolve();
|
|
448
489
|
morphResolve = null;
|
|
449
490
|
morphAlpha = 0;
|
|
450
|
-
morphBoundsA = null;
|
|
451
|
-
morphBoundsB = null;
|
|
452
491
|
}
|
|
453
492
|
morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS;
|
|
454
493
|
morphAlpha = 0;
|
|
455
|
-
morphBoundsA = interruptBounds ?? computeBoundaries(engine.getSarmalSkeleton()) ?? { scale, offsetX, offsetY };
|
|
456
494
|
engine.startMorph(target, options2?.morphStrategy);
|
|
457
|
-
const period = target.period ?? Math.PI * 2;
|
|
458
|
-
const samples = Math.max(50, Math.round(period * 20));
|
|
459
|
-
const skeletonFn = target.skeletonFn ?? ((t) => target.fn(t, 0, {}));
|
|
460
|
-
const skeletonB = Array.from(
|
|
461
|
-
{ length: samples + 1 },
|
|
462
|
-
(_, i) => skeletonFn(i / samples * period)
|
|
463
|
-
);
|
|
464
|
-
morphBoundsB = computeBoundaries(skeletonB) ?? { scale, offsetX, offsetY };
|
|
465
495
|
return new Promise((resolve) => {
|
|
466
496
|
morphResolve = resolve;
|
|
467
497
|
});
|
|
468
|
-
}
|
|
498
|
+
},
|
|
469
499
|
};
|
|
470
500
|
}
|
|
471
501
|
|
|
472
502
|
// src/renderer-svg.ts
|
|
473
503
|
var DEFAULT_MORPH_DURATION_MS2 = 300;
|
|
474
|
-
var
|
|
504
|
+
var MAX_TRAIL_SEGMENTS = 200;
|
|
475
505
|
var TRAIL_FADE_CURVE2 = 1.5;
|
|
476
506
|
var TRAIL_MAX_OPACITY2 = 0.88;
|
|
477
507
|
var TRAIL_MIN_WIDTH2 = 0.5;
|
|
478
508
|
var TRAIL_MAX_WIDTH2 = 2.5;
|
|
479
509
|
var DEFAULT_SKELETON_OPACITY2 = 0.15;
|
|
480
|
-
var DEFAULT_GLOW_INNER_STOP = 0.4;
|
|
481
|
-
var DEFAULT_GLOW_FALLOFF_OPACITY = 0.53;
|
|
482
510
|
var FIT_PADDING2 = 0.1;
|
|
483
|
-
var instanceCount = 0;
|
|
484
511
|
function el(tag) {
|
|
485
512
|
return document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
486
513
|
}
|
|
@@ -491,11 +518,8 @@ function createSVGRenderer(options) {
|
|
|
491
518
|
trailColor: options.trailColor ?? "#ffffff",
|
|
492
519
|
headColor: options.headColor ?? "#ffffff",
|
|
493
520
|
headRadius: options.headRadius ?? 4,
|
|
494
|
-
|
|
495
|
-
ariaLabel: options.ariaLabel ?? "Loading"
|
|
521
|
+
ariaLabel: options.ariaLabel ?? "Loading",
|
|
496
522
|
};
|
|
497
|
-
const uid = ++instanceCount;
|
|
498
|
-
const gradientId = `sarmal-glow-${uid}`;
|
|
499
523
|
const rect = container.getBoundingClientRect();
|
|
500
524
|
const width = rect.width || 200;
|
|
501
525
|
const height = rect.height || 200;
|
|
@@ -508,27 +532,6 @@ function createSVGRenderer(options) {
|
|
|
508
532
|
const titleEl = el("title");
|
|
509
533
|
titleEl.textContent = opts.ariaLabel;
|
|
510
534
|
svg.appendChild(titleEl);
|
|
511
|
-
const defs = el("defs");
|
|
512
|
-
const gradient = el("radialGradient");
|
|
513
|
-
gradient.id = gradientId;
|
|
514
|
-
gradient.setAttribute("cx", "50%");
|
|
515
|
-
gradient.setAttribute("cy", "50%");
|
|
516
|
-
gradient.setAttribute("r", "50%");
|
|
517
|
-
const stop0 = el("stop");
|
|
518
|
-
stop0.setAttribute("offset", "0%");
|
|
519
|
-
stop0.setAttribute("stop-color", opts.headColor);
|
|
520
|
-
stop0.setAttribute("stop-opacity", "1");
|
|
521
|
-
const stopMid = el("stop");
|
|
522
|
-
stopMid.setAttribute("offset", `${DEFAULT_GLOW_INNER_STOP * 100}%`);
|
|
523
|
-
stopMid.setAttribute("stop-color", opts.headColor);
|
|
524
|
-
stopMid.setAttribute("stop-opacity", String(DEFAULT_GLOW_FALLOFF_OPACITY));
|
|
525
|
-
const stop1 = el("stop");
|
|
526
|
-
stop1.setAttribute("offset", "100%");
|
|
527
|
-
stop1.setAttribute("stop-color", opts.headColor);
|
|
528
|
-
stop1.setAttribute("stop-opacity", "0");
|
|
529
|
-
gradient.append(stop0, stopMid, stop1);
|
|
530
|
-
defs.appendChild(gradient);
|
|
531
|
-
svg.appendChild(defs);
|
|
532
535
|
const skeletonPath = el("path");
|
|
533
536
|
skeletonPath.setAttribute("fill", "none");
|
|
534
537
|
skeletonPath.setAttribute("stroke", opts.skeletonColor);
|
|
@@ -550,19 +553,12 @@ function createSVGRenderer(options) {
|
|
|
550
553
|
let morphPathABuilt = "";
|
|
551
554
|
let morphPathBBuilt = "";
|
|
552
555
|
const trailPaths = [];
|
|
553
|
-
for (let i = 0; i <
|
|
556
|
+
for (let i = 0; i < MAX_TRAIL_SEGMENTS; i++) {
|
|
554
557
|
const path = el("path");
|
|
555
|
-
path.setAttribute("fill",
|
|
556
|
-
path.setAttribute("stroke", opts.trailColor);
|
|
557
|
-
path.setAttribute("stroke-linecap", "round");
|
|
558
|
-
path.setAttribute("stroke-linejoin", "round");
|
|
558
|
+
path.setAttribute("fill", opts.trailColor);
|
|
559
559
|
svg.appendChild(path);
|
|
560
560
|
trailPaths.push(path);
|
|
561
561
|
}
|
|
562
|
-
const glowCircle = el("circle");
|
|
563
|
-
glowCircle.setAttribute("fill", `url(#${gradientId})`);
|
|
564
|
-
glowCircle.setAttribute("r", String(opts.glowSize));
|
|
565
|
-
svg.appendChild(glowCircle);
|
|
566
562
|
const headCircle = el("circle");
|
|
567
563
|
headCircle.setAttribute("fill", opts.headColor);
|
|
568
564
|
headCircle.setAttribute("r", String(opts.headRadius));
|
|
@@ -576,7 +572,10 @@ function createSVGRenderer(options) {
|
|
|
576
572
|
return;
|
|
577
573
|
}
|
|
578
574
|
const first = skeleton2[0];
|
|
579
|
-
let minX = first.x,
|
|
575
|
+
let minX = first.x,
|
|
576
|
+
maxX = first.x,
|
|
577
|
+
minY = first.y,
|
|
578
|
+
maxY = first.y;
|
|
580
579
|
for (const p of skeleton2) {
|
|
581
580
|
if (p.x < minX) {
|
|
582
581
|
minX = p.x;
|
|
@@ -629,24 +628,32 @@ function createSVGRenderer(options) {
|
|
|
629
628
|
}
|
|
630
629
|
return;
|
|
631
630
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
const end = Math.min(start + batchSize, trailCount - 1);
|
|
636
|
-
if (start >= trailCount - 1) {
|
|
637
|
-
trailPaths[b].setAttribute("d", "");
|
|
638
|
-
continue;
|
|
639
|
-
}
|
|
640
|
-
const progress = (start + end) / 2 / (trailCount - 1);
|
|
631
|
+
for (let i = 0; i < trailCount - 1; i++) {
|
|
632
|
+
const progress = i / (trailCount - 1);
|
|
633
|
+
const nextProgress = (i + 1) / (trailCount - 1);
|
|
641
634
|
const opacity = Math.pow(progress, TRAIL_FADE_CURVE2) * TRAIL_MAX_OPACITY2;
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
635
|
+
const width2 = TRAIL_MIN_WIDTH2 + progress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
|
|
636
|
+
const nextWidth = TRAIL_MIN_WIDTH2 + nextProgress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
|
|
637
|
+
const curr = trail[i];
|
|
638
|
+
const next = trail[i + 1];
|
|
639
|
+
const n0 = computeNormal(trail, i);
|
|
640
|
+
const n1 = computeNormal(trail, i + 1);
|
|
641
|
+
const halfW0 = width2 / 2;
|
|
642
|
+
const halfW1 = nextWidth / 2;
|
|
643
|
+
const l0x = px(curr) + n0.x * halfW0;
|
|
644
|
+
const l0y = py(curr) + n0.y * halfW0;
|
|
645
|
+
const r0x = px(curr) - n0.x * halfW0;
|
|
646
|
+
const r0y = py(curr) - n0.y * halfW0;
|
|
647
|
+
const l1x = px(next) + n1.x * halfW1;
|
|
648
|
+
const l1y = py(next) + n1.y * halfW1;
|
|
649
|
+
const r1x = px(next) - n1.x * halfW1;
|
|
650
|
+
const r1y = py(next) - n1.y * halfW1;
|
|
651
|
+
const d = `M${l0x.toFixed(2)} ${l0y.toFixed(2)} L${l1x.toFixed(2)} ${l1y.toFixed(2)} L${r1x.toFixed(2)} ${r1y.toFixed(2)} L${r0x.toFixed(2)} ${r0y.toFixed(2)} Z`;
|
|
652
|
+
trailPaths[i].setAttribute("d", d);
|
|
653
|
+
trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
|
|
654
|
+
}
|
|
655
|
+
for (let i = trailCount - 1; i < trailPaths.length; i++) {
|
|
656
|
+
trailPaths[i].setAttribute("d", "");
|
|
650
657
|
}
|
|
651
658
|
}
|
|
652
659
|
function updateHead(trail, trailCount) {
|
|
@@ -656,14 +663,13 @@ function createSVGRenderer(options) {
|
|
|
656
663
|
const head = trail[trailCount - 1];
|
|
657
664
|
const x = px(head);
|
|
658
665
|
const y = py(head);
|
|
659
|
-
glowCircle.setAttribute("cx", x);
|
|
660
|
-
glowCircle.setAttribute("cy", y);
|
|
661
666
|
headCircle.setAttribute("cx", x);
|
|
662
667
|
headCircle.setAttribute("cy", y);
|
|
663
668
|
}
|
|
664
669
|
let animationId = null;
|
|
665
670
|
let lastTime = 0;
|
|
666
|
-
const prefersReducedMotion =
|
|
671
|
+
const prefersReducedMotion =
|
|
672
|
+
typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
667
673
|
let morphResolve = null;
|
|
668
674
|
let morphDurationMs = DEFAULT_MORPH_DURATION_MS2;
|
|
669
675
|
let morphTarget = null;
|
|
@@ -673,7 +679,7 @@ function createSVGRenderer(options) {
|
|
|
673
679
|
const samples = Math.max(50, Math.round(period * 20));
|
|
674
680
|
const points = [];
|
|
675
681
|
for (let i = 0; i <= samples; i++) {
|
|
676
|
-
const t = i / samples * period;
|
|
682
|
+
const t = (i / samples) * period;
|
|
677
683
|
const p = target.fn(t, 0, {});
|
|
678
684
|
points.push(p);
|
|
679
685
|
}
|
|
@@ -701,13 +707,16 @@ function createSVGRenderer(options) {
|
|
|
701
707
|
skeletonPathA.setAttribute("visibility", "visible");
|
|
702
708
|
skeletonPathA.setAttribute(
|
|
703
709
|
"stroke-opacity",
|
|
704
|
-
String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2)
|
|
710
|
+
String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2),
|
|
705
711
|
);
|
|
706
712
|
}
|
|
707
713
|
if (morphPathBBuilt) {
|
|
708
714
|
skeletonPathB.setAttribute("d", morphPathBBuilt);
|
|
709
715
|
skeletonPathB.setAttribute("visibility", "visible");
|
|
710
|
-
skeletonPathB.setAttribute(
|
|
716
|
+
skeletonPathB.setAttribute(
|
|
717
|
+
"stroke-opacity",
|
|
718
|
+
String(morphAlpha * DEFAULT_SKELETON_OPACITY2),
|
|
719
|
+
);
|
|
711
720
|
}
|
|
712
721
|
if (morphAlpha >= 1) {
|
|
713
722
|
engine.completeMorph();
|
|
@@ -799,7 +808,7 @@ function createSVGRenderer(options) {
|
|
|
799
808
|
return new Promise((resolve) => {
|
|
800
809
|
morphResolve = resolve;
|
|
801
810
|
});
|
|
802
|
-
}
|
|
811
|
+
},
|
|
803
812
|
};
|
|
804
813
|
}
|
|
805
814
|
function createSarmalSVG(container, curveDef, options) {
|
|
@@ -811,26 +820,29 @@ function createSarmalSVG(container, curveDef, options) {
|
|
|
811
820
|
// src/curves.ts
|
|
812
821
|
var TWO_PI2 = Math.PI * 2;
|
|
813
822
|
function artemis2(t, _time, _params) {
|
|
814
|
-
const a = 0.35,
|
|
815
|
-
|
|
823
|
+
const a = 0.35,
|
|
824
|
+
b = 0.15,
|
|
825
|
+
ox = 0.175;
|
|
826
|
+
const s = Math.sin(t),
|
|
827
|
+
c = Math.cos(t);
|
|
816
828
|
const denom = 1 + s * s;
|
|
817
829
|
return {
|
|
818
|
-
x: c * (1 + a * c) / denom - ox,
|
|
819
|
-
y: s * c * (1 + b * c) / denom
|
|
830
|
+
x: (c * (1 + a * c)) / denom - ox,
|
|
831
|
+
y: (s * c * (1 + b * c)) / denom,
|
|
820
832
|
};
|
|
821
833
|
}
|
|
822
834
|
function epitrochoid7(t, _time, _params) {
|
|
823
835
|
const d = 1 + 0.55 * Math.sin(t * 0.5);
|
|
824
836
|
return {
|
|
825
837
|
x: 7 * Math.cos(t) - d * Math.cos(7 * t),
|
|
826
|
-
y: 7 * Math.sin(t) - d * Math.sin(7 * t)
|
|
838
|
+
y: 7 * Math.sin(t) - d * Math.sin(7 * t),
|
|
827
839
|
};
|
|
828
840
|
}
|
|
829
841
|
function epitrochoid7Skeleton(t) {
|
|
830
842
|
const d = 1.275;
|
|
831
843
|
return {
|
|
832
844
|
x: 7 * Math.cos(t) - d * Math.cos(7 * t),
|
|
833
|
-
y: 7 * Math.sin(t) - d * Math.sin(7 * t)
|
|
845
|
+
y: 7 * Math.sin(t) - d * Math.sin(7 * t),
|
|
834
846
|
};
|
|
835
847
|
}
|
|
836
848
|
function astroid(t, _time, _params) {
|
|
@@ -838,55 +850,56 @@ function astroid(t, _time, _params) {
|
|
|
838
850
|
const s = Math.sin(t);
|
|
839
851
|
return {
|
|
840
852
|
x: c * c * c,
|
|
841
|
-
y: s * s * s
|
|
853
|
+
y: s * s * s,
|
|
842
854
|
};
|
|
843
855
|
}
|
|
844
856
|
function deltoid(t, _time, _params) {
|
|
845
857
|
return {
|
|
846
858
|
x: 2 * Math.cos(t) + Math.cos(2 * t),
|
|
847
|
-
y: 2 * Math.sin(t) - Math.sin(2 * t)
|
|
859
|
+
y: 2 * Math.sin(t) - Math.sin(2 * t),
|
|
848
860
|
};
|
|
849
861
|
}
|
|
850
862
|
function rose5(t, _time, _params) {
|
|
851
863
|
const r = Math.cos(5 * t);
|
|
852
864
|
return {
|
|
853
865
|
x: r * Math.cos(t),
|
|
854
|
-
y: r * Math.sin(t)
|
|
866
|
+
y: r * Math.sin(t),
|
|
855
867
|
};
|
|
856
868
|
}
|
|
857
869
|
function rose3(t, _time, _params) {
|
|
858
870
|
const r = Math.cos(3 * t);
|
|
859
871
|
return {
|
|
860
872
|
x: r * Math.cos(t),
|
|
861
|
-
y: r * Math.sin(t)
|
|
873
|
+
y: r * Math.sin(t),
|
|
862
874
|
};
|
|
863
875
|
}
|
|
864
876
|
function lissajous32(t, time, _params) {
|
|
865
877
|
const phi = time * 0.45;
|
|
866
878
|
return {
|
|
867
879
|
x: Math.sin(3 * t + phi),
|
|
868
|
-
y: Math.sin(2 * t)
|
|
880
|
+
y: Math.sin(2 * t),
|
|
869
881
|
};
|
|
870
882
|
}
|
|
871
883
|
function lissajous43(t, time, _params) {
|
|
872
884
|
const phi = time * 0.38;
|
|
873
885
|
return {
|
|
874
886
|
x: Math.sin(4 * t + phi),
|
|
875
|
-
y: Math.sin(3 * t)
|
|
887
|
+
y: Math.sin(3 * t),
|
|
876
888
|
};
|
|
877
889
|
}
|
|
878
890
|
function epicycloid3(t, _time, _params) {
|
|
879
891
|
return {
|
|
880
892
|
x: 4 * Math.cos(t) - Math.cos(4 * t),
|
|
881
|
-
y: 4 * Math.sin(t) - Math.sin(4 * t)
|
|
893
|
+
y: 4 * Math.sin(t) - Math.sin(4 * t),
|
|
882
894
|
};
|
|
883
895
|
}
|
|
884
896
|
function lame(t, time, _params) {
|
|
885
897
|
const p = 1.75 + 1.25 * Math.sin(time * 0.48);
|
|
886
|
-
const c = Math.cos(t),
|
|
898
|
+
const c = Math.cos(t),
|
|
899
|
+
s = Math.sin(t);
|
|
887
900
|
return {
|
|
888
901
|
x: Math.sign(c) * Math.pow(Math.abs(c), p),
|
|
889
|
-
y: Math.sign(s) * Math.pow(Math.abs(s), p)
|
|
902
|
+
y: Math.sign(s) * Math.pow(Math.abs(s), p),
|
|
890
903
|
};
|
|
891
904
|
}
|
|
892
905
|
var curves = {
|
|
@@ -894,66 +907,66 @@ var curves = {
|
|
|
894
907
|
name: "Artemis II",
|
|
895
908
|
fn: artemis2,
|
|
896
909
|
period: TWO_PI2,
|
|
897
|
-
speed: 0.7
|
|
910
|
+
speed: 0.7,
|
|
898
911
|
},
|
|
899
912
|
epitrochoid7: {
|
|
900
913
|
name: "Epitrochoid",
|
|
901
914
|
fn: epitrochoid7,
|
|
902
915
|
period: TWO_PI2,
|
|
903
916
|
speed: 1.4,
|
|
904
|
-
skeletonFn: epitrochoid7Skeleton
|
|
917
|
+
skeletonFn: epitrochoid7Skeleton,
|
|
905
918
|
},
|
|
906
919
|
astroid: {
|
|
907
920
|
name: "Astroid",
|
|
908
921
|
fn: astroid,
|
|
909
922
|
period: TWO_PI2,
|
|
910
|
-
speed: 1.1
|
|
923
|
+
speed: 1.1,
|
|
911
924
|
},
|
|
912
925
|
deltoid: {
|
|
913
926
|
name: "Deltoid",
|
|
914
927
|
fn: deltoid,
|
|
915
928
|
period: TWO_PI2,
|
|
916
|
-
speed: 0.9
|
|
929
|
+
speed: 0.9,
|
|
917
930
|
},
|
|
918
931
|
rose5: {
|
|
919
932
|
name: "Rose (n=5)",
|
|
920
933
|
fn: rose5,
|
|
921
934
|
period: TWO_PI2,
|
|
922
|
-
speed: 1
|
|
935
|
+
speed: 1,
|
|
923
936
|
},
|
|
924
937
|
rose3: {
|
|
925
938
|
name: "Rose (n=3)",
|
|
926
939
|
fn: rose3,
|
|
927
940
|
period: TWO_PI2,
|
|
928
|
-
speed: 1.15
|
|
941
|
+
speed: 1.15,
|
|
929
942
|
},
|
|
930
943
|
lissajous32: {
|
|
931
944
|
name: "Lissajous 3:2",
|
|
932
945
|
fn: lissajous32,
|
|
933
946
|
period: TWO_PI2,
|
|
934
947
|
speed: 2,
|
|
935
|
-
skeleton: "live"
|
|
948
|
+
skeleton: "live",
|
|
936
949
|
},
|
|
937
950
|
lissajous43: {
|
|
938
951
|
name: "Lissajous 4:3",
|
|
939
952
|
fn: lissajous43,
|
|
940
953
|
period: TWO_PI2,
|
|
941
954
|
speed: 1.8,
|
|
942
|
-
skeleton: "live"
|
|
955
|
+
skeleton: "live",
|
|
943
956
|
},
|
|
944
957
|
epicycloid3: {
|
|
945
958
|
name: "Epicycloid (n=3)",
|
|
946
959
|
fn: epicycloid3,
|
|
947
960
|
period: TWO_PI2,
|
|
948
|
-
speed: 0.75
|
|
961
|
+
speed: 0.75,
|
|
949
962
|
},
|
|
950
963
|
lame: {
|
|
951
964
|
name: "Lam\xE9 Curve",
|
|
952
965
|
fn: lame,
|
|
953
966
|
period: TWO_PI2,
|
|
954
967
|
speed: 1,
|
|
955
|
-
skeleton: "live"
|
|
956
|
-
}
|
|
968
|
+
skeleton: "live",
|
|
969
|
+
},
|
|
957
970
|
};
|
|
958
971
|
|
|
959
972
|
// src/index.ts
|
|
@@ -970,4 +983,4 @@ exports.createSarmal = createSarmal;
|
|
|
970
983
|
exports.createSarmalSVG = createSarmalSVG;
|
|
971
984
|
exports.curves = curves;
|
|
972
985
|
//# sourceMappingURL=index.cjs.map
|
|
973
|
-
//# sourceMappingURL=index.cjs.map
|
|
986
|
+
//# sourceMappingURL=index.cjs.map
|