@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.js
CHANGED
|
@@ -49,7 +49,7 @@ function resolveCurve(curveDef) {
|
|
|
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
54
|
}
|
|
55
55
|
function createEngine(curveDef, trailLength = 120) {
|
|
@@ -75,7 +75,7 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
75
75
|
actualTime += deltaTime;
|
|
76
76
|
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
77
77
|
const a = curve.fn(t, actualTime, {});
|
|
78
|
-
const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
|
|
78
|
+
const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
|
|
79
79
|
const b = morphCurveB.fn(tB, actualTime, {});
|
|
80
80
|
trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
|
|
81
81
|
} else {
|
|
@@ -99,14 +99,14 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
99
99
|
trail.clear();
|
|
100
100
|
},
|
|
101
101
|
seek(newT, { clearTrail = false } = {}) {
|
|
102
|
-
t = (newT % curve.period + curve.period) % curve.period;
|
|
102
|
+
t = ((newT % curve.period) + curve.period) % curve.period;
|
|
103
103
|
if (clearTrail) {
|
|
104
104
|
trail.clear();
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
107
|
seekWithTrail(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
|
|
108
108
|
const advance = curve.speed * step;
|
|
109
|
-
const target = (targetT % curve.period + curve.period) % curve.period;
|
|
109
|
+
const target = ((targetT % curve.period) + curve.period) % curve.period;
|
|
110
110
|
const targetTime = target / curve.speed;
|
|
111
111
|
t = target;
|
|
112
112
|
actualTime = targetTime;
|
|
@@ -132,13 +132,16 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
132
132
|
...frozenB,
|
|
133
133
|
fn: (sampleT, time, params) => {
|
|
134
134
|
const a = frozenA.fn(sampleT, time, params);
|
|
135
|
-
const tB =
|
|
135
|
+
const tB =
|
|
136
|
+
frozenStrategy === "normalized"
|
|
137
|
+
? (sampleT / frozenA.period) * frozenB.period
|
|
138
|
+
: sampleT;
|
|
136
139
|
const b = frozenB.fn(tB, time, params);
|
|
137
140
|
return {
|
|
138
141
|
x: a.x + (b.x - a.x) * frozenAlpha,
|
|
139
|
-
y: a.y + (b.y - a.y) * frozenAlpha
|
|
142
|
+
y: a.y + (b.y - a.y) * frozenAlpha,
|
|
140
143
|
};
|
|
141
|
-
}
|
|
144
|
+
},
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
147
|
_morphStrategy = strategy;
|
|
@@ -151,7 +154,7 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
151
154
|
completeMorph() {
|
|
152
155
|
if (morphCurveB !== null) {
|
|
153
156
|
if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
|
|
154
|
-
t = t / curve.period * morphCurveB.period;
|
|
157
|
+
t = (t / curve.period) * morphCurveB.period;
|
|
155
158
|
}
|
|
156
159
|
curve = morphCurveB;
|
|
157
160
|
}
|
|
@@ -163,43 +166,74 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
163
166
|
const points = new Array(steps);
|
|
164
167
|
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
165
168
|
for (let i = 0; i < steps; i++) {
|
|
166
|
-
const sampleT = i / (steps - 1) * curve.period;
|
|
169
|
+
const sampleT = (i / (steps - 1)) * curve.period;
|
|
167
170
|
const a = sampleSkeleton(curve, sampleT);
|
|
168
|
-
const tB =
|
|
171
|
+
const tB =
|
|
172
|
+
_morphStrategy === "normalized"
|
|
173
|
+
? (sampleT / curve.period) * morphCurveB.period
|
|
174
|
+
: sampleT;
|
|
169
175
|
const b = sampleSkeleton(morphCurveB, tB);
|
|
170
176
|
points[i] = {
|
|
171
177
|
x: a.x + (b.x - a.x) * _morphAlpha,
|
|
172
|
-
y: a.y + (b.y - a.y) * _morphAlpha
|
|
178
|
+
y: a.y + (b.y - a.y) * _morphAlpha,
|
|
173
179
|
};
|
|
174
180
|
}
|
|
175
181
|
return points;
|
|
176
182
|
}
|
|
177
183
|
for (let i = 0; i < steps; i++) {
|
|
178
|
-
const sampleT = i / (steps - 1) * curve.period;
|
|
184
|
+
const sampleT = (i / (steps - 1)) * curve.period;
|
|
179
185
|
points[i] = sampleSkeleton(curve, sampleT);
|
|
180
186
|
}
|
|
181
187
|
return points;
|
|
182
|
-
}
|
|
188
|
+
},
|
|
183
189
|
};
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
// src/renderer.ts
|
|
187
193
|
var DEFAULT_MORPH_DURATION_MS = 300;
|
|
188
194
|
var DEFAULT_HEAD_RADIUS = 4;
|
|
189
|
-
var DEFAULT_GLOW_SIZE = 20;
|
|
190
195
|
var DEFAULT_SKELETON_COLOR = "#ffffff";
|
|
191
196
|
var DEFAULT_SKELETON_OPACITY = 0.15;
|
|
192
197
|
var FIT_PADDING = 0.1;
|
|
193
|
-
var TRAIL_BATCH_SIZE = 20;
|
|
194
198
|
var TRAIL_FADE_CURVE = 1.5;
|
|
195
199
|
var TRAIL_MAX_OPACITY = 0.88;
|
|
196
200
|
var TRAIL_MIN_WIDTH = 0.5;
|
|
197
201
|
var TRAIL_MAX_WIDTH = 2.5;
|
|
198
|
-
var GLOW_INNER_EDGE = 0.4;
|
|
199
|
-
var GLOW_FALLOFF_OPACITY = 0.53;
|
|
200
202
|
function hexToRgbComponents(hex) {
|
|
201
203
|
const n = parseInt(hex.slice(1), 16);
|
|
202
|
-
return `${n >> 16},${n >> 8 & 255},${n & 255}`;
|
|
204
|
+
return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
|
|
205
|
+
}
|
|
206
|
+
function computeTangent(trail, i) {
|
|
207
|
+
const count = trail.length;
|
|
208
|
+
if (count < 2) {
|
|
209
|
+
return { x: 1, y: 0 };
|
|
210
|
+
}
|
|
211
|
+
if (i === 0) {
|
|
212
|
+
const dx2 = trail[1].x - trail[0].x;
|
|
213
|
+
const dy2 = trail[1].y - trail[0].y;
|
|
214
|
+
const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
|
|
215
|
+
return { x: dx2 / len2, y: dy2 / len2 };
|
|
216
|
+
}
|
|
217
|
+
if (i === count - 1) {
|
|
218
|
+
const dx2 = trail[count - 1].x - trail[count - 2].x;
|
|
219
|
+
const dy2 = trail[count - 1].y - trail[count - 2].y;
|
|
220
|
+
const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
|
|
221
|
+
return { x: dx2 / len2, y: dy2 / len2 };
|
|
222
|
+
}
|
|
223
|
+
const dx = trail[i + 1].x - trail[i - 1].x;
|
|
224
|
+
const dy = trail[i + 1].y - trail[i - 1].y;
|
|
225
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
226
|
+
return { x: dx / len, y: dy / len };
|
|
227
|
+
}
|
|
228
|
+
function computeNormal(trail, i) {
|
|
229
|
+
const tangent = computeTangent(trail, i);
|
|
230
|
+
return { x: -tangent.y, y: tangent.x };
|
|
231
|
+
}
|
|
232
|
+
function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
|
|
233
|
+
target.style.width = `${logicalWidth}px`;
|
|
234
|
+
target.style.height = `${logicalHeight}px`;
|
|
235
|
+
target.width = logicalWidth * dpr;
|
|
236
|
+
target.height = logicalHeight * dpr;
|
|
203
237
|
}
|
|
204
238
|
function createRenderer(options) {
|
|
205
239
|
const canvas = options.canvas;
|
|
@@ -213,10 +247,19 @@ function createRenderer(options) {
|
|
|
213
247
|
trailColor: options.trailColor ?? "#ffffff",
|
|
214
248
|
headColor: options.headColor ?? "#ffffff",
|
|
215
249
|
headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
|
|
216
|
-
glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE
|
|
217
250
|
};
|
|
218
251
|
const trailRgb = hexToRgbComponents(opts.trailColor);
|
|
219
|
-
const
|
|
252
|
+
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
253
|
+
function setupCanvas() {
|
|
254
|
+
const rect = canvas.getBoundingClientRect();
|
|
255
|
+
const lw = rect.width || 200;
|
|
256
|
+
const lh = rect.height || 200;
|
|
257
|
+
applyDprSizing(canvas, lw, lh, dpr);
|
|
258
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
259
|
+
}
|
|
260
|
+
setupCanvas();
|
|
261
|
+
let logicalWidth = canvas.width / dpr;
|
|
262
|
+
let logicalHeight = canvas.height / dpr;
|
|
220
263
|
let skeleton = [];
|
|
221
264
|
let skeletonCanvas = null;
|
|
222
265
|
let trail = [];
|
|
@@ -230,12 +273,13 @@ function createRenderer(options) {
|
|
|
230
273
|
let morphResolve = null;
|
|
231
274
|
let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
|
|
232
275
|
let morphAlpha = 0;
|
|
233
|
-
let morphBoundsA = null;
|
|
234
|
-
let morphBoundsB = null;
|
|
235
276
|
function computeBoundaries(pts) {
|
|
236
277
|
if (pts.length === 0) return null;
|
|
237
278
|
const first = pts[0];
|
|
238
|
-
let minX = first.x,
|
|
279
|
+
let minX = first.x,
|
|
280
|
+
maxX = first.x,
|
|
281
|
+
minY = first.y,
|
|
282
|
+
maxY = first.y;
|
|
239
283
|
for (const p of pts) {
|
|
240
284
|
if (p.x < minX) minX = p.x;
|
|
241
285
|
if (p.x > maxX) maxX = p.x;
|
|
@@ -244,17 +288,15 @@ function createRenderer(options) {
|
|
|
244
288
|
}
|
|
245
289
|
const width = maxX - minX;
|
|
246
290
|
const height = maxY - minY;
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const scaleX = canvasWidth / (width * (1 + FIT_PADDING * 2));
|
|
250
|
-
const scaleY = canvasHeight / (height * (1 + FIT_PADDING * 2));
|
|
291
|
+
const scaleX = logicalWidth / (width * (1 + FIT_PADDING * 2));
|
|
292
|
+
const scaleY = logicalHeight / (height * (1 + FIT_PADDING * 2));
|
|
251
293
|
const s = Math.min(scaleX, scaleY);
|
|
252
294
|
const boundsWidth = width * s;
|
|
253
295
|
const boundsHeight = height * s;
|
|
254
296
|
return {
|
|
255
297
|
scale: s,
|
|
256
|
-
offsetX: (
|
|
257
|
-
offsetY: (
|
|
298
|
+
offsetX: (logicalWidth - boundsWidth) / 2 - minX * s,
|
|
299
|
+
offsetY: (logicalHeight - boundsHeight) / 2 - minY * s,
|
|
258
300
|
};
|
|
259
301
|
}
|
|
260
302
|
function calculateBoundaries() {
|
|
@@ -269,6 +311,7 @@ function createRenderer(options) {
|
|
|
269
311
|
if (skeleton.length < 2) return;
|
|
270
312
|
skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
|
|
271
313
|
const skeletonCtx = skeletonCanvas.getContext("2d");
|
|
314
|
+
skeletonCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
272
315
|
skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
|
|
273
316
|
skeletonCtx.lineWidth = 1.5;
|
|
274
317
|
skeletonCtx.beginPath();
|
|
@@ -314,32 +357,41 @@ function createRenderer(options) {
|
|
|
314
357
|
}
|
|
315
358
|
ctx.stroke();
|
|
316
359
|
} else if (skeletonCanvas) {
|
|
317
|
-
ctx.drawImage(skeletonCanvas, 0, 0);
|
|
360
|
+
ctx.drawImage(skeletonCanvas, 0, 0, logicalWidth, logicalHeight);
|
|
318
361
|
}
|
|
319
362
|
}
|
|
320
363
|
function drawTrail() {
|
|
321
364
|
if (trailCount < 2) {
|
|
322
365
|
return;
|
|
323
366
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const bEnd = Math.min(batchIndex + TRAIL_BATCH_SIZE, trailCount - 1);
|
|
328
|
-
const progress = (batchIndex + bEnd) / 2 / (trailCount - 1);
|
|
367
|
+
for (let i = 0; i < trailCount - 1; i++) {
|
|
368
|
+
const progress = i / (trailCount - 1);
|
|
369
|
+
const nextProgress = (i + 1) / (trailCount - 1);
|
|
329
370
|
const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
|
|
330
|
-
const
|
|
371
|
+
const width = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
|
|
372
|
+
const nextWidth = TRAIL_MIN_WIDTH + nextProgress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
|
|
373
|
+
const curr = trail[i];
|
|
374
|
+
const next = trail[i + 1];
|
|
375
|
+
const n0 = computeNormal(trail, i);
|
|
376
|
+
const n1 = computeNormal(trail, i + 1);
|
|
377
|
+
const halfW0 = width / 2;
|
|
378
|
+
const halfW1 = nextWidth / 2;
|
|
379
|
+
const l0x = curr.x * scale + offsetX + n0.x * halfW0;
|
|
380
|
+
const l0y = curr.y * scale + offsetY + n0.y * halfW0;
|
|
381
|
+
const r0x = curr.x * scale + offsetX - n0.x * halfW0;
|
|
382
|
+
const r0y = curr.y * scale + offsetY - n0.y * halfW0;
|
|
383
|
+
const l1x = next.x * scale + offsetX + n1.x * halfW1;
|
|
384
|
+
const l1y = next.y * scale + offsetY + n1.y * halfW1;
|
|
385
|
+
const r1x = next.x * scale + offsetX - n1.x * halfW1;
|
|
386
|
+
const r1y = next.y * scale + offsetY - n1.y * halfW1;
|
|
387
|
+
ctx.fillStyle = `rgba(${trailRgb},${alpha})`;
|
|
331
388
|
ctx.beginPath();
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
ctx.strokeStyle = `rgba(${trailRgb},${alpha})`;
|
|
341
|
-
ctx.lineWidth = lineWidth;
|
|
342
|
-
ctx.stroke();
|
|
389
|
+
ctx.moveTo(l0x, l0y);
|
|
390
|
+
ctx.lineTo(l1x, l1y);
|
|
391
|
+
ctx.lineTo(r1x, r1y);
|
|
392
|
+
ctx.lineTo(r0x, r0y);
|
|
393
|
+
ctx.closePath();
|
|
394
|
+
ctx.fill();
|
|
343
395
|
}
|
|
344
396
|
}
|
|
345
397
|
function drawHead() {
|
|
@@ -348,14 +400,6 @@ function createRenderer(options) {
|
|
|
348
400
|
}
|
|
349
401
|
const x = head.x * scale + offsetX;
|
|
350
402
|
const y = head.y * scale + offsetY;
|
|
351
|
-
const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);
|
|
352
|
-
gradient.addColorStop(0, opts.headColor);
|
|
353
|
-
gradient.addColorStop(GLOW_INNER_EDGE, headRgbFalloff);
|
|
354
|
-
gradient.addColorStop(1, "transparent");
|
|
355
|
-
ctx.fillStyle = gradient;
|
|
356
|
-
ctx.beginPath();
|
|
357
|
-
ctx.arc(x, y, opts.glowSize, 0, Math.PI * 2);
|
|
358
|
-
ctx.fill();
|
|
359
403
|
ctx.fillStyle = opts.headColor;
|
|
360
404
|
ctx.beginPath();
|
|
361
405
|
ctx.arc(x, y, opts.headRadius, 0, Math.PI * 2);
|
|
@@ -368,20 +412,18 @@ function createRenderer(options) {
|
|
|
368
412
|
if (engine.morphAlpha !== null) {
|
|
369
413
|
morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
|
|
370
414
|
engine.setMorphAlpha(morphAlpha);
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
scale =
|
|
375
|
-
offsetX =
|
|
376
|
-
offsetY =
|
|
415
|
+
const interpolatedSkeleton = engine.getSarmalSkeleton();
|
|
416
|
+
const bounds = computeBoundaries(interpolatedSkeleton);
|
|
417
|
+
if (bounds) {
|
|
418
|
+
scale = bounds.scale;
|
|
419
|
+
offsetX = bounds.offsetX;
|
|
420
|
+
offsetY = bounds.offsetY;
|
|
377
421
|
}
|
|
378
422
|
if (morphAlpha >= 1) {
|
|
379
423
|
engine.completeMorph();
|
|
380
424
|
morphResolve?.();
|
|
381
425
|
morphResolve = null;
|
|
382
426
|
morphAlpha = 0;
|
|
383
|
-
morphBoundsA = null;
|
|
384
|
-
morphBoundsB = null;
|
|
385
427
|
skeleton = engine.getSarmalSkeleton();
|
|
386
428
|
if (!engine.isLiveSkeleton) {
|
|
387
429
|
buildSkeletonCanvas();
|
|
@@ -391,7 +433,7 @@ function createRenderer(options) {
|
|
|
391
433
|
trail = engine.tick(deltaTime);
|
|
392
434
|
trailCount = engine.trailCount;
|
|
393
435
|
head = trailCount > 0 ? trail[trailCount - 1] : null;
|
|
394
|
-
ctx.clearRect(0, 0,
|
|
436
|
+
ctx.clearRect(0, 0, logicalWidth, logicalHeight);
|
|
395
437
|
if (engine.isLiveSkeleton && engine.morphAlpha === null) {
|
|
396
438
|
skeleton = engine.getSarmalSkeleton();
|
|
397
439
|
calculateBoundaries();
|
|
@@ -439,46 +481,31 @@ function createRenderer(options) {
|
|
|
439
481
|
engine.seekWithTrail(t);
|
|
440
482
|
},
|
|
441
483
|
morphTo(target, options2) {
|
|
442
|
-
const interruptBounds = morphResolve !== null ? { scale, offsetX, offsetY } : null;
|
|
443
484
|
if (morphResolve !== null) {
|
|
444
485
|
engine.completeMorph();
|
|
445
486
|
morphResolve();
|
|
446
487
|
morphResolve = null;
|
|
447
488
|
morphAlpha = 0;
|
|
448
|
-
morphBoundsA = null;
|
|
449
|
-
morphBoundsB = null;
|
|
450
489
|
}
|
|
451
490
|
morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS;
|
|
452
491
|
morphAlpha = 0;
|
|
453
|
-
morphBoundsA = interruptBounds ?? computeBoundaries(engine.getSarmalSkeleton()) ?? { scale, offsetX, offsetY };
|
|
454
492
|
engine.startMorph(target, options2?.morphStrategy);
|
|
455
|
-
const period = target.period ?? Math.PI * 2;
|
|
456
|
-
const samples = Math.max(50, Math.round(period * 20));
|
|
457
|
-
const skeletonFn = target.skeletonFn ?? ((t) => target.fn(t, 0, {}));
|
|
458
|
-
const skeletonB = Array.from(
|
|
459
|
-
{ length: samples + 1 },
|
|
460
|
-
(_, i) => skeletonFn(i / samples * period)
|
|
461
|
-
);
|
|
462
|
-
morphBoundsB = computeBoundaries(skeletonB) ?? { scale, offsetX, offsetY };
|
|
463
493
|
return new Promise((resolve) => {
|
|
464
494
|
morphResolve = resolve;
|
|
465
495
|
});
|
|
466
|
-
}
|
|
496
|
+
},
|
|
467
497
|
};
|
|
468
498
|
}
|
|
469
499
|
|
|
470
500
|
// src/renderer-svg.ts
|
|
471
501
|
var DEFAULT_MORPH_DURATION_MS2 = 300;
|
|
472
|
-
var
|
|
502
|
+
var MAX_TRAIL_SEGMENTS = 200;
|
|
473
503
|
var TRAIL_FADE_CURVE2 = 1.5;
|
|
474
504
|
var TRAIL_MAX_OPACITY2 = 0.88;
|
|
475
505
|
var TRAIL_MIN_WIDTH2 = 0.5;
|
|
476
506
|
var TRAIL_MAX_WIDTH2 = 2.5;
|
|
477
507
|
var DEFAULT_SKELETON_OPACITY2 = 0.15;
|
|
478
|
-
var DEFAULT_GLOW_INNER_STOP = 0.4;
|
|
479
|
-
var DEFAULT_GLOW_FALLOFF_OPACITY = 0.53;
|
|
480
508
|
var FIT_PADDING2 = 0.1;
|
|
481
|
-
var instanceCount = 0;
|
|
482
509
|
function el(tag) {
|
|
483
510
|
return document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
484
511
|
}
|
|
@@ -489,11 +516,8 @@ function createSVGRenderer(options) {
|
|
|
489
516
|
trailColor: options.trailColor ?? "#ffffff",
|
|
490
517
|
headColor: options.headColor ?? "#ffffff",
|
|
491
518
|
headRadius: options.headRadius ?? 4,
|
|
492
|
-
|
|
493
|
-
ariaLabel: options.ariaLabel ?? "Loading"
|
|
519
|
+
ariaLabel: options.ariaLabel ?? "Loading",
|
|
494
520
|
};
|
|
495
|
-
const uid = ++instanceCount;
|
|
496
|
-
const gradientId = `sarmal-glow-${uid}`;
|
|
497
521
|
const rect = container.getBoundingClientRect();
|
|
498
522
|
const width = rect.width || 200;
|
|
499
523
|
const height = rect.height || 200;
|
|
@@ -506,27 +530,6 @@ function createSVGRenderer(options) {
|
|
|
506
530
|
const titleEl = el("title");
|
|
507
531
|
titleEl.textContent = opts.ariaLabel;
|
|
508
532
|
svg.appendChild(titleEl);
|
|
509
|
-
const defs = el("defs");
|
|
510
|
-
const gradient = el("radialGradient");
|
|
511
|
-
gradient.id = gradientId;
|
|
512
|
-
gradient.setAttribute("cx", "50%");
|
|
513
|
-
gradient.setAttribute("cy", "50%");
|
|
514
|
-
gradient.setAttribute("r", "50%");
|
|
515
|
-
const stop0 = el("stop");
|
|
516
|
-
stop0.setAttribute("offset", "0%");
|
|
517
|
-
stop0.setAttribute("stop-color", opts.headColor);
|
|
518
|
-
stop0.setAttribute("stop-opacity", "1");
|
|
519
|
-
const stopMid = el("stop");
|
|
520
|
-
stopMid.setAttribute("offset", `${DEFAULT_GLOW_INNER_STOP * 100}%`);
|
|
521
|
-
stopMid.setAttribute("stop-color", opts.headColor);
|
|
522
|
-
stopMid.setAttribute("stop-opacity", String(DEFAULT_GLOW_FALLOFF_OPACITY));
|
|
523
|
-
const stop1 = el("stop");
|
|
524
|
-
stop1.setAttribute("offset", "100%");
|
|
525
|
-
stop1.setAttribute("stop-color", opts.headColor);
|
|
526
|
-
stop1.setAttribute("stop-opacity", "0");
|
|
527
|
-
gradient.append(stop0, stopMid, stop1);
|
|
528
|
-
defs.appendChild(gradient);
|
|
529
|
-
svg.appendChild(defs);
|
|
530
533
|
const skeletonPath = el("path");
|
|
531
534
|
skeletonPath.setAttribute("fill", "none");
|
|
532
535
|
skeletonPath.setAttribute("stroke", opts.skeletonColor);
|
|
@@ -548,19 +551,12 @@ function createSVGRenderer(options) {
|
|
|
548
551
|
let morphPathABuilt = "";
|
|
549
552
|
let morphPathBBuilt = "";
|
|
550
553
|
const trailPaths = [];
|
|
551
|
-
for (let i = 0; i <
|
|
554
|
+
for (let i = 0; i < MAX_TRAIL_SEGMENTS; i++) {
|
|
552
555
|
const path = el("path");
|
|
553
|
-
path.setAttribute("fill",
|
|
554
|
-
path.setAttribute("stroke", opts.trailColor);
|
|
555
|
-
path.setAttribute("stroke-linecap", "round");
|
|
556
|
-
path.setAttribute("stroke-linejoin", "round");
|
|
556
|
+
path.setAttribute("fill", opts.trailColor);
|
|
557
557
|
svg.appendChild(path);
|
|
558
558
|
trailPaths.push(path);
|
|
559
559
|
}
|
|
560
|
-
const glowCircle = el("circle");
|
|
561
|
-
glowCircle.setAttribute("fill", `url(#${gradientId})`);
|
|
562
|
-
glowCircle.setAttribute("r", String(opts.glowSize));
|
|
563
|
-
svg.appendChild(glowCircle);
|
|
564
560
|
const headCircle = el("circle");
|
|
565
561
|
headCircle.setAttribute("fill", opts.headColor);
|
|
566
562
|
headCircle.setAttribute("r", String(opts.headRadius));
|
|
@@ -574,7 +570,10 @@ function createSVGRenderer(options) {
|
|
|
574
570
|
return;
|
|
575
571
|
}
|
|
576
572
|
const first = skeleton2[0];
|
|
577
|
-
let minX = first.x,
|
|
573
|
+
let minX = first.x,
|
|
574
|
+
maxX = first.x,
|
|
575
|
+
minY = first.y,
|
|
576
|
+
maxY = first.y;
|
|
578
577
|
for (const p of skeleton2) {
|
|
579
578
|
if (p.x < minX) {
|
|
580
579
|
minX = p.x;
|
|
@@ -627,24 +626,32 @@ function createSVGRenderer(options) {
|
|
|
627
626
|
}
|
|
628
627
|
return;
|
|
629
628
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const
|
|
633
|
-
const end = Math.min(start + batchSize, trailCount - 1);
|
|
634
|
-
if (start >= trailCount - 1) {
|
|
635
|
-
trailPaths[b].setAttribute("d", "");
|
|
636
|
-
continue;
|
|
637
|
-
}
|
|
638
|
-
const progress = (start + end) / 2 / (trailCount - 1);
|
|
629
|
+
for (let i = 0; i < trailCount - 1; i++) {
|
|
630
|
+
const progress = i / (trailCount - 1);
|
|
631
|
+
const nextProgress = (i + 1) / (trailCount - 1);
|
|
639
632
|
const opacity = Math.pow(progress, TRAIL_FADE_CURVE2) * TRAIL_MAX_OPACITY2;
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
633
|
+
const width2 = TRAIL_MIN_WIDTH2 + progress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
|
|
634
|
+
const nextWidth = TRAIL_MIN_WIDTH2 + nextProgress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
|
|
635
|
+
const curr = trail[i];
|
|
636
|
+
const next = trail[i + 1];
|
|
637
|
+
const n0 = computeNormal(trail, i);
|
|
638
|
+
const n1 = computeNormal(trail, i + 1);
|
|
639
|
+
const halfW0 = width2 / 2;
|
|
640
|
+
const halfW1 = nextWidth / 2;
|
|
641
|
+
const l0x = px(curr) + n0.x * halfW0;
|
|
642
|
+
const l0y = py(curr) + n0.y * halfW0;
|
|
643
|
+
const r0x = px(curr) - n0.x * halfW0;
|
|
644
|
+
const r0y = py(curr) - n0.y * halfW0;
|
|
645
|
+
const l1x = px(next) + n1.x * halfW1;
|
|
646
|
+
const l1y = py(next) + n1.y * halfW1;
|
|
647
|
+
const r1x = px(next) - n1.x * halfW1;
|
|
648
|
+
const r1y = py(next) - n1.y * halfW1;
|
|
649
|
+
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`;
|
|
650
|
+
trailPaths[i].setAttribute("d", d);
|
|
651
|
+
trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
|
|
652
|
+
}
|
|
653
|
+
for (let i = trailCount - 1; i < trailPaths.length; i++) {
|
|
654
|
+
trailPaths[i].setAttribute("d", "");
|
|
648
655
|
}
|
|
649
656
|
}
|
|
650
657
|
function updateHead(trail, trailCount) {
|
|
@@ -654,14 +661,13 @@ function createSVGRenderer(options) {
|
|
|
654
661
|
const head = trail[trailCount - 1];
|
|
655
662
|
const x = px(head);
|
|
656
663
|
const y = py(head);
|
|
657
|
-
glowCircle.setAttribute("cx", x);
|
|
658
|
-
glowCircle.setAttribute("cy", y);
|
|
659
664
|
headCircle.setAttribute("cx", x);
|
|
660
665
|
headCircle.setAttribute("cy", y);
|
|
661
666
|
}
|
|
662
667
|
let animationId = null;
|
|
663
668
|
let lastTime = 0;
|
|
664
|
-
const prefersReducedMotion =
|
|
669
|
+
const prefersReducedMotion =
|
|
670
|
+
typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
665
671
|
let morphResolve = null;
|
|
666
672
|
let morphDurationMs = DEFAULT_MORPH_DURATION_MS2;
|
|
667
673
|
let morphTarget = null;
|
|
@@ -671,7 +677,7 @@ function createSVGRenderer(options) {
|
|
|
671
677
|
const samples = Math.max(50, Math.round(period * 20));
|
|
672
678
|
const points = [];
|
|
673
679
|
for (let i = 0; i <= samples; i++) {
|
|
674
|
-
const t = i / samples * period;
|
|
680
|
+
const t = (i / samples) * period;
|
|
675
681
|
const p = target.fn(t, 0, {});
|
|
676
682
|
points.push(p);
|
|
677
683
|
}
|
|
@@ -699,13 +705,16 @@ function createSVGRenderer(options) {
|
|
|
699
705
|
skeletonPathA.setAttribute("visibility", "visible");
|
|
700
706
|
skeletonPathA.setAttribute(
|
|
701
707
|
"stroke-opacity",
|
|
702
|
-
String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2)
|
|
708
|
+
String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2),
|
|
703
709
|
);
|
|
704
710
|
}
|
|
705
711
|
if (morphPathBBuilt) {
|
|
706
712
|
skeletonPathB.setAttribute("d", morphPathBBuilt);
|
|
707
713
|
skeletonPathB.setAttribute("visibility", "visible");
|
|
708
|
-
skeletonPathB.setAttribute(
|
|
714
|
+
skeletonPathB.setAttribute(
|
|
715
|
+
"stroke-opacity",
|
|
716
|
+
String(morphAlpha * DEFAULT_SKELETON_OPACITY2),
|
|
717
|
+
);
|
|
709
718
|
}
|
|
710
719
|
if (morphAlpha >= 1) {
|
|
711
720
|
engine.completeMorph();
|
|
@@ -797,7 +806,7 @@ function createSVGRenderer(options) {
|
|
|
797
806
|
return new Promise((resolve) => {
|
|
798
807
|
morphResolve = resolve;
|
|
799
808
|
});
|
|
800
|
-
}
|
|
809
|
+
},
|
|
801
810
|
};
|
|
802
811
|
}
|
|
803
812
|
function createSarmalSVG(container, curveDef, options) {
|
|
@@ -809,26 +818,29 @@ function createSarmalSVG(container, curveDef, options) {
|
|
|
809
818
|
// src/curves.ts
|
|
810
819
|
var TWO_PI2 = Math.PI * 2;
|
|
811
820
|
function artemis2(t, _time, _params) {
|
|
812
|
-
const a = 0.35,
|
|
813
|
-
|
|
821
|
+
const a = 0.35,
|
|
822
|
+
b = 0.15,
|
|
823
|
+
ox = 0.175;
|
|
824
|
+
const s = Math.sin(t),
|
|
825
|
+
c = Math.cos(t);
|
|
814
826
|
const denom = 1 + s * s;
|
|
815
827
|
return {
|
|
816
|
-
x: c * (1 + a * c) / denom - ox,
|
|
817
|
-
y: s * c * (1 + b * c) / denom
|
|
828
|
+
x: (c * (1 + a * c)) / denom - ox,
|
|
829
|
+
y: (s * c * (1 + b * c)) / denom,
|
|
818
830
|
};
|
|
819
831
|
}
|
|
820
832
|
function epitrochoid7(t, _time, _params) {
|
|
821
833
|
const d = 1 + 0.55 * Math.sin(t * 0.5);
|
|
822
834
|
return {
|
|
823
835
|
x: 7 * Math.cos(t) - d * Math.cos(7 * t),
|
|
824
|
-
y: 7 * Math.sin(t) - d * Math.sin(7 * t)
|
|
836
|
+
y: 7 * Math.sin(t) - d * Math.sin(7 * t),
|
|
825
837
|
};
|
|
826
838
|
}
|
|
827
839
|
function epitrochoid7Skeleton(t) {
|
|
828
840
|
const d = 1.275;
|
|
829
841
|
return {
|
|
830
842
|
x: 7 * Math.cos(t) - d * Math.cos(7 * t),
|
|
831
|
-
y: 7 * Math.sin(t) - d * Math.sin(7 * t)
|
|
843
|
+
y: 7 * Math.sin(t) - d * Math.sin(7 * t),
|
|
832
844
|
};
|
|
833
845
|
}
|
|
834
846
|
function astroid(t, _time, _params) {
|
|
@@ -836,55 +848,56 @@ function astroid(t, _time, _params) {
|
|
|
836
848
|
const s = Math.sin(t);
|
|
837
849
|
return {
|
|
838
850
|
x: c * c * c,
|
|
839
|
-
y: s * s * s
|
|
851
|
+
y: s * s * s,
|
|
840
852
|
};
|
|
841
853
|
}
|
|
842
854
|
function deltoid(t, _time, _params) {
|
|
843
855
|
return {
|
|
844
856
|
x: 2 * Math.cos(t) + Math.cos(2 * t),
|
|
845
|
-
y: 2 * Math.sin(t) - Math.sin(2 * t)
|
|
857
|
+
y: 2 * Math.sin(t) - Math.sin(2 * t),
|
|
846
858
|
};
|
|
847
859
|
}
|
|
848
860
|
function rose5(t, _time, _params) {
|
|
849
861
|
const r = Math.cos(5 * t);
|
|
850
862
|
return {
|
|
851
863
|
x: r * Math.cos(t),
|
|
852
|
-
y: r * Math.sin(t)
|
|
864
|
+
y: r * Math.sin(t),
|
|
853
865
|
};
|
|
854
866
|
}
|
|
855
867
|
function rose3(t, _time, _params) {
|
|
856
868
|
const r = Math.cos(3 * t);
|
|
857
869
|
return {
|
|
858
870
|
x: r * Math.cos(t),
|
|
859
|
-
y: r * Math.sin(t)
|
|
871
|
+
y: r * Math.sin(t),
|
|
860
872
|
};
|
|
861
873
|
}
|
|
862
874
|
function lissajous32(t, time, _params) {
|
|
863
875
|
const phi = time * 0.45;
|
|
864
876
|
return {
|
|
865
877
|
x: Math.sin(3 * t + phi),
|
|
866
|
-
y: Math.sin(2 * t)
|
|
878
|
+
y: Math.sin(2 * t),
|
|
867
879
|
};
|
|
868
880
|
}
|
|
869
881
|
function lissajous43(t, time, _params) {
|
|
870
882
|
const phi = time * 0.38;
|
|
871
883
|
return {
|
|
872
884
|
x: Math.sin(4 * t + phi),
|
|
873
|
-
y: Math.sin(3 * t)
|
|
885
|
+
y: Math.sin(3 * t),
|
|
874
886
|
};
|
|
875
887
|
}
|
|
876
888
|
function epicycloid3(t, _time, _params) {
|
|
877
889
|
return {
|
|
878
890
|
x: 4 * Math.cos(t) - Math.cos(4 * t),
|
|
879
|
-
y: 4 * Math.sin(t) - Math.sin(4 * t)
|
|
891
|
+
y: 4 * Math.sin(t) - Math.sin(4 * t),
|
|
880
892
|
};
|
|
881
893
|
}
|
|
882
894
|
function lame(t, time, _params) {
|
|
883
895
|
const p = 1.75 + 1.25 * Math.sin(time * 0.48);
|
|
884
|
-
const c = Math.cos(t),
|
|
896
|
+
const c = Math.cos(t),
|
|
897
|
+
s = Math.sin(t);
|
|
885
898
|
return {
|
|
886
899
|
x: Math.sign(c) * Math.pow(Math.abs(c), p),
|
|
887
|
-
y: Math.sign(s) * Math.pow(Math.abs(s), p)
|
|
900
|
+
y: Math.sign(s) * Math.pow(Math.abs(s), p),
|
|
888
901
|
};
|
|
889
902
|
}
|
|
890
903
|
var curves = {
|
|
@@ -892,66 +905,66 @@ var curves = {
|
|
|
892
905
|
name: "Artemis II",
|
|
893
906
|
fn: artemis2,
|
|
894
907
|
period: TWO_PI2,
|
|
895
|
-
speed: 0.7
|
|
908
|
+
speed: 0.7,
|
|
896
909
|
},
|
|
897
910
|
epitrochoid7: {
|
|
898
911
|
name: "Epitrochoid",
|
|
899
912
|
fn: epitrochoid7,
|
|
900
913
|
period: TWO_PI2,
|
|
901
914
|
speed: 1.4,
|
|
902
|
-
skeletonFn: epitrochoid7Skeleton
|
|
915
|
+
skeletonFn: epitrochoid7Skeleton,
|
|
903
916
|
},
|
|
904
917
|
astroid: {
|
|
905
918
|
name: "Astroid",
|
|
906
919
|
fn: astroid,
|
|
907
920
|
period: TWO_PI2,
|
|
908
|
-
speed: 1.1
|
|
921
|
+
speed: 1.1,
|
|
909
922
|
},
|
|
910
923
|
deltoid: {
|
|
911
924
|
name: "Deltoid",
|
|
912
925
|
fn: deltoid,
|
|
913
926
|
period: TWO_PI2,
|
|
914
|
-
speed: 0.9
|
|
927
|
+
speed: 0.9,
|
|
915
928
|
},
|
|
916
929
|
rose5: {
|
|
917
930
|
name: "Rose (n=5)",
|
|
918
931
|
fn: rose5,
|
|
919
932
|
period: TWO_PI2,
|
|
920
|
-
speed: 1
|
|
933
|
+
speed: 1,
|
|
921
934
|
},
|
|
922
935
|
rose3: {
|
|
923
936
|
name: "Rose (n=3)",
|
|
924
937
|
fn: rose3,
|
|
925
938
|
period: TWO_PI2,
|
|
926
|
-
speed: 1.15
|
|
939
|
+
speed: 1.15,
|
|
927
940
|
},
|
|
928
941
|
lissajous32: {
|
|
929
942
|
name: "Lissajous 3:2",
|
|
930
943
|
fn: lissajous32,
|
|
931
944
|
period: TWO_PI2,
|
|
932
945
|
speed: 2,
|
|
933
|
-
skeleton: "live"
|
|
946
|
+
skeleton: "live",
|
|
934
947
|
},
|
|
935
948
|
lissajous43: {
|
|
936
949
|
name: "Lissajous 4:3",
|
|
937
950
|
fn: lissajous43,
|
|
938
951
|
period: TWO_PI2,
|
|
939
952
|
speed: 1.8,
|
|
940
|
-
skeleton: "live"
|
|
953
|
+
skeleton: "live",
|
|
941
954
|
},
|
|
942
955
|
epicycloid3: {
|
|
943
956
|
name: "Epicycloid (n=3)",
|
|
944
957
|
fn: epicycloid3,
|
|
945
958
|
period: TWO_PI2,
|
|
946
|
-
speed: 0.75
|
|
959
|
+
speed: 0.75,
|
|
947
960
|
},
|
|
948
961
|
lame: {
|
|
949
962
|
name: "Lam\xE9 Curve",
|
|
950
963
|
fn: lame,
|
|
951
964
|
period: TWO_PI2,
|
|
952
965
|
speed: 1,
|
|
953
|
-
skeleton: "live"
|
|
954
|
-
}
|
|
966
|
+
skeleton: "live",
|
|
967
|
+
},
|
|
955
968
|
};
|
|
956
969
|
|
|
957
970
|
// src/index.ts
|
|
@@ -963,4 +976,4 @@ function createSarmal(canvas, curveDef, options) {
|
|
|
963
976
|
|
|
964
977
|
export { createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, curves };
|
|
965
978
|
//# sourceMappingURL=index.js.map
|
|
966
|
-
//# sourceMappingURL=index.js.map
|
|
979
|
+
//# sourceMappingURL=index.js.map
|