@sarmal/core 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # Sarmal Core
2
+
3
+ ## Publishing
4
+
5
+ ``` bash
6
+ npm publish --access public
7
+ ```
@@ -11,7 +11,7 @@ var CircularBuffer = class {
11
11
  this.data = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
12
12
  this.result = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
13
13
  }
14
- /** Mutates the pre-allocated slot in-place — no allocation */
14
+ /** Mutates in-place */
15
15
  push(x, y) {
16
16
  const slot = this.data[this.head];
17
17
  slot.x = x;
@@ -22,9 +22,9 @@ var CircularBuffer = class {
22
22
  }
23
23
  }
24
24
  /**
25
- * Copies ordered points into the pre-allocated result buffer and returns it.
26
- * The same array reference is returned every call — result.length is always `capacity`.
27
- * Read only indices 0..count-1; the rest are stale pre-allocated slots.
25
+ * Copies ordered points into the pre-allocated result buffer and returns it
26
+ * Note: The *same* array reference is returned every call,
27
+ * so `result.length` is also always `capacity`
28
28
  */
29
29
  toArray() {
30
30
  const start = this.count < this.capacity ? 0 : this.head;
@@ -70,6 +70,30 @@ function createEngine(curveDef, trailLength = 120) {
70
70
  actualTime = 0;
71
71
  trail.clear();
72
72
  },
73
+ seek(newT, { clearTrail = false } = {}) {
74
+ t = (newT % curve.period + curve.period) % curve.period;
75
+ if (clearTrail) {
76
+ trail.clear();
77
+ }
78
+ },
79
+ seekWithTrail(targetT, { wrap = false } = {}) {
80
+ const STEP = 1 / 60;
81
+ const advance = curve.speed * STEP;
82
+ const target = (targetT % curve.period + curve.period) % curve.period;
83
+ const targetTime = target / curve.speed;
84
+ t = target;
85
+ actualTime = targetTime;
86
+ trail.clear();
87
+ const pointsFromStart = Math.floor(target / advance) + 1;
88
+ const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
89
+ for (let step = count - 1; step >= 0; step--) {
90
+ const sampleT = target - step * advance;
91
+ const wrappedT = sampleT < 0 ? sampleT + curve.period : sampleT;
92
+ const time = targetTime - step * STEP;
93
+ const point = curve.fn(wrappedT, time, {});
94
+ trail.push(point.x, point.y);
95
+ }
96
+ },
73
97
  getSarmalSkeleton() {
74
98
  const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);
75
99
  const points = new Array(steps);
@@ -133,10 +157,18 @@ function createRenderer(options) {
133
157
  const first = skeleton[0];
134
158
  let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
135
159
  for (const p of skeleton) {
136
- if (p.x < minX) minX = p.x;
137
- if (p.x > maxX) maxX = p.x;
138
- if (p.y < minY) minY = p.y;
139
- if (p.y > maxY) maxY = p.y;
160
+ if (p.x < minX) {
161
+ minX = p.x;
162
+ }
163
+ if (p.x > maxX) {
164
+ maxX = p.x;
165
+ }
166
+ if (p.y < minY) {
167
+ minY = p.y;
168
+ }
169
+ if (p.y > maxY) {
170
+ maxY = p.y;
171
+ }
140
172
  }
141
173
  const width = maxX - minX;
142
174
  const height = maxY - minY;
@@ -153,38 +185,42 @@ function createRenderer(options) {
153
185
  function buildSkeletonCanvas() {
154
186
  if (skeleton.length < 2) return;
155
187
  skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
156
- const sCtx = skeletonCanvas.getContext("2d");
157
- sCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
158
- sCtx.lineWidth = 1.5;
159
- sCtx.beginPath();
188
+ const skeletonCtx = skeletonCanvas.getContext("2d");
189
+ skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
190
+ skeletonCtx.lineWidth = 1.5;
191
+ skeletonCtx.beginPath();
160
192
  const first = skeleton[0];
161
- sCtx.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);
193
+ skeletonCtx.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);
162
194
  for (let i = 1; i < skeleton.length; i++) {
163
195
  const p = skeleton[i];
164
- sCtx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
196
+ skeletonCtx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
165
197
  }
166
- sCtx.stroke();
198
+ skeletonCtx.stroke();
167
199
  }
168
200
  function drawSkeleton() {
169
- if (!skeletonCanvas) return;
201
+ if (!skeletonCanvas) {
202
+ return;
203
+ }
170
204
  ctx.drawImage(skeletonCanvas, 0, 0);
171
205
  }
172
206
  function drawTrail() {
173
- if (trailCount < 2) return;
207
+ if (trailCount < 2) {
208
+ return;
209
+ }
174
210
  ctx.lineJoin = "round";
175
211
  ctx.lineCap = "round";
176
- for (let b = 0; b < trailCount - 1; b += TRAIL_BATCH_SIZE) {
177
- const bEnd = Math.min(b + TRAIL_BATCH_SIZE, trailCount - 1);
178
- const progress = (b + bEnd) / 2 / (trailCount - 1);
212
+ for (let batchIndex = 0; batchIndex < trailCount - 1; batchIndex += TRAIL_BATCH_SIZE) {
213
+ const bEnd = Math.min(batchIndex + TRAIL_BATCH_SIZE, trailCount - 1);
214
+ const progress = (batchIndex + bEnd) / 2 / (trailCount - 1);
179
215
  const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
180
216
  const lineWidth = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
181
217
  ctx.beginPath();
182
- for (let i = b; i <= bEnd; i++) {
183
- const p = trail[i];
184
- if (i === b) {
185
- ctx.moveTo(p.x * scale + offsetX, p.y * scale + offsetY);
218
+ for (let i = batchIndex; i <= bEnd; i++) {
219
+ const point = trail[i];
220
+ if (i === batchIndex) {
221
+ ctx.moveTo(point.x * scale + offsetX, point.y * scale + offsetY);
186
222
  } else {
187
- ctx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
223
+ ctx.lineTo(point.x * scale + offsetX, point.y * scale + offsetY);
188
224
  }
189
225
  }
190
226
  ctx.strokeStyle = `rgba(${trailRgb},${alpha})`;
@@ -193,7 +229,9 @@ function createRenderer(options) {
193
229
  }
194
230
  }
195
231
  function drawHead() {
196
- if (!head) return;
232
+ if (!head) {
233
+ return;
234
+ }
197
235
  const x = head.x * scale + offsetX;
198
236
  const y = head.y * scale + offsetY;
199
237
  const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);
@@ -211,7 +249,7 @@ function createRenderer(options) {
211
249
  }
212
250
  function render() {
213
251
  const now = performance.now();
214
- const deltaTime = (now - lastTime) / 1e3;
252
+ const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
215
253
  lastTime = now;
216
254
  trail = engine.tick(deltaTime);
217
255
  trailCount = engine.trailCount;
@@ -227,12 +265,16 @@ function createRenderer(options) {
227
265
  buildSkeletonCanvas();
228
266
  return {
229
267
  start() {
230
- if (animationId !== null) return;
268
+ if (animationId !== null) {
269
+ return;
270
+ }
231
271
  lastTime = performance.now();
232
272
  render();
233
273
  },
234
274
  stop() {
235
- if (animationId === null) return;
275
+ if (animationId === null) {
276
+ return;
277
+ }
236
278
  cancelAnimationFrame(animationId);
237
279
  animationId = null;
238
280
  },
@@ -246,6 +288,12 @@ function createRenderer(options) {
246
288
  cancelAnimationFrame(animationId);
247
289
  animationId = null;
248
290
  }
291
+ },
292
+ seek(t, options2) {
293
+ engine.seek(t, options2);
294
+ },
295
+ seekWithTrail(t) {
296
+ engine.seekWithTrail(t);
249
297
  }
250
298
  };
251
299
  }
@@ -296,6 +344,34 @@ function rose3(t, _time, _params) {
296
344
  y: r * Math.sin(t)
297
345
  };
298
346
  }
347
+ function lissajous32(t, time, _params) {
348
+ const phi = time * 0.45;
349
+ return {
350
+ x: Math.sin(3 * t + phi),
351
+ y: Math.sin(2 * t)
352
+ };
353
+ }
354
+ function lissajous43(t, time, _params) {
355
+ const phi = time * 0.38;
356
+ return {
357
+ x: Math.sin(4 * t + phi),
358
+ y: Math.sin(3 * t)
359
+ };
360
+ }
361
+ function epicycloid3(t, _time, _params) {
362
+ return {
363
+ x: 4 * Math.cos(t) - Math.cos(4 * t),
364
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
365
+ };
366
+ }
367
+ function lame(t, time, _params) {
368
+ const p = 1.75 + 1.25 * Math.sin(time * 0.48);
369
+ const c = Math.cos(t), s = Math.sin(t);
370
+ return {
371
+ x: Math.sign(c) * Math.pow(Math.abs(c), p),
372
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
373
+ };
374
+ }
299
375
  var curves = {
300
376
  artemis2: {
301
377
  name: "Artemis II",
@@ -332,6 +408,30 @@ var curves = {
332
408
  fn: rose3,
333
409
  period: TWO_PI2,
334
410
  speed: 1.15
411
+ },
412
+ lissajous32: {
413
+ name: "Lissajous 3:2",
414
+ fn: lissajous32,
415
+ period: TWO_PI2,
416
+ speed: 2
417
+ },
418
+ lissajous43: {
419
+ name: "Lissajous 4:3",
420
+ fn: lissajous43,
421
+ period: TWO_PI2,
422
+ speed: 1.8
423
+ },
424
+ epicycloid3: {
425
+ name: "Epicycloid (n=3)",
426
+ fn: epicycloid3,
427
+ period: TWO_PI2,
428
+ speed: 0.75
429
+ },
430
+ lame: {
431
+ name: "Lam\xE9 Curve",
432
+ fn: lame,
433
+ period: TWO_PI2,
434
+ speed: 1
335
435
  }
336
436
  };
337
437
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/engine.ts","../src/renderer.ts","../src/curves.ts","../src/index.ts","../src/auto-init.ts"],"names":["TWO_PI"],"mappings":"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","file":"auto-init.cjs","sourcesContent":["import type { CurveDef, Engine, Point } from \"./types\";\n\nconst TWO_PI = Math.PI * 2;\nconst POINTS_PER_PERIOD_UNIT = 50;\n\n/**\n * A fixed-size list of points with first in, last out method.\n * The oldest entry is automatically discarded when the list is at capacity.\n *\n * Zero heap allocations per frame:\n * - push() mutates the pre-allocated data slot in-place\n * - toArray() copies into a pre-allocated result buffer and returns the same reference every call\n * - result.length is NEVER changed — callers use the separate `count` getter to know valid size\n */\nclass CircularBuffer {\n private data: Array<Point>;\n private result: Array<Point>;\n private capacity: number;\n private head: number = 0;\n private count: number = 0;\n\n constructor(capacity: number) {\n this.capacity = capacity;\n this.data = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));\n this.result = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));\n }\n\n /** Mutates the pre-allocated slot in-place — no allocation */\n push(x: number, y: number): void {\n const slot = this.data[this.head]!;\n slot.x = x;\n slot.y = y;\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) {\n this.count++;\n }\n }\n\n /**\n * Copies ordered points into the pre-allocated result buffer and returns it.\n * The same array reference is returned every call — result.length is always `capacity`.\n * Read only indices 0..count-1; the rest are stale pre-allocated slots.\n */\n toArray(): Array<Point> {\n const start = this.count < this.capacity ? 0 : this.head;\n for (let i = 0; i < this.count; i++) {\n const src = this.data[(start + i) % this.capacity]!;\n const dst = this.result[i]!;\n dst.x = src.x;\n dst.y = src.y;\n }\n return this.result;\n }\n\n clear(): void {\n this.head = 0;\n this.count = 0;\n }\n\n get length(): number {\n return this.count;\n }\n}\n\n/**\n * Creates the core simulation engine for a sarmal\n *\n * it runs a clock (time `t`), asks the curve for the current Point position at that time,\n * and remembers the last N positions so the renderer can draw the trail\n *\n * The engine is only responsible for math coordinates,\n * so it is not responsible for drawing or colors\n *\n * @param curveDef A curve definition\n * @param trailLength default: `120`\n */\nexport function createEngine(curveDef: CurveDef, trailLength: number = 120): Engine {\n const curve = {\n name: curveDef.name,\n fn: curveDef.fn,\n period: curveDef.period ?? TWO_PI,\n speed: curveDef.speed ?? 1,\n };\n const trail = new CircularBuffer(trailLength);\n let t = 0;\n let actualTime = 0;\n\n return {\n tick(deltaTime: number): Array<Point> {\n t = (t + curve.speed * deltaTime) % curve.period;\n actualTime += deltaTime;\n const point = curve.fn(t, actualTime, {});\n trail.push(point.x, point.y);\n return trail.toArray();\n },\n\n get trailCount(): number {\n return trail.length;\n },\n\n reset(): void {\n t = 0;\n actualTime = 0;\n trail.clear();\n },\n\n getSarmalSkeleton(): Array<Point> {\n const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);\n // oxlint-disable-next-line unicorn/no-new-array -- array is pre-allocated, filled immediately below\n const points: Array<Point> = new Array(steps);\n for (let i = 0; i < steps; i++) {\n const sampleT = (i / (steps - 1)) * curve.period;\n const point = curve.fn(sampleT, 0, {});\n points[i] = point;\n }\n return points;\n },\n };\n}\n","import type { Point, RendererOptions, SarmalInstance } from \"./types\";\n\nconst DEFAULT_HEAD_RADIUS = 4;\nconst DEFAULT_GLOW_SIZE = 20;\nconst DEFAULT_SKELETON_COLOR = \"#ffffff\";\nconst DEFAULT_SKELETON_OPACITY = 0.15;\n\n/** Fraction of the bounding box added as padding when fitting the curve to the canvas */\nconst FIT_PADDING = 0.1;\n\n/**\n * The trail is drawn in batches of points.\n * Each batch has lower opacity than the one that comes before it\n * (0 = oldest/tail, 1 = newest/head).\n * Larger batch size = fewer GPU stroke calls per frame.\n */\nconst TRAIL_BATCH_SIZE = 20;\n/** Higher values = sharper fade near the tail, more of the trail appears faint */\nconst TRAIL_FADE_CURVE = 1.5;\nconst TRAIL_MAX_OPACITY = 0.88;\n/** Line width of tail */\nconst TRAIL_MIN_WIDTH = 0.5;\n/** Line width of head */\nconst TRAIL_MAX_WIDTH = 2.5;\n\nconst GLOW_INNER_EDGE = 0.4;\n/** Opacity at the inner edge of the glow falloff */\nconst GLOW_FALLOFF_OPACITY = 0.53;\n\n/** Parses a hex color into its \"r,g,b\" string for use in rgba() — called once at init */\nfunction hexToRgbComponents(hex: string): string {\n const n = parseInt(hex.slice(1), 16);\n return `${n >> 16},${(n >> 8) & 255},${n & 255}`;\n}\n\n/**\n * Creates a Canvas 2D renderer for sarmal animations.\n * Renders the skeleton (via offscreen canvas), the trail, and the glowing dot.\n */\nexport function createRenderer(options: RendererOptions): SarmalInstance {\n const canvas = options.canvas;\n if (!canvas.getContext(\"2d\")) {\n throw new Error(\"Could not get 2d context from canvas\");\n }\n const ctx = canvas.getContext(\"2d\")!;\n\n const engine = options.engine;\n const opts = {\n skeletonColor: options.skeletonColor ?? DEFAULT_SKELETON_COLOR,\n trailColor: options.trailColor ?? \"#ffffff\",\n headColor: options.headColor ?? \"#ffffff\",\n headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,\n glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE,\n };\n\n // Pre-parse hex colors once — avoids parseInt on every draw call\n const trailRgb = hexToRgbComponents(opts.trailColor);\n const headRgbFalloff = `rgba(${hexToRgbComponents(opts.headColor)},${GLOW_FALLOFF_OPACITY})`;\n\n let skeleton: Array<Point> = [];\n let skeletonCanvas: OffscreenCanvas | null = null;\n let trail: Array<Point> = [];\n let trailCount = 0;\n let head: Point | null = null;\n let scale = 1;\n let offsetX = 0;\n let offsetY = 0;\n let animationId: number | null = null;\n let lastTime = 0;\n\n /**\n * Computes how to map engine coordinates to canvas pixels.\n *\n * Steps: curve fn -> coordinate point -> (scale + offset) -> pixel\n *\n * 1. Find the bounding box of the skeleton (min/max x/y in coordinates)\n * 2. Compute a scale factor that fits the bounds into the canvas with padding\n * 3. Compute offsets to center the curve in the canvas\n */\n function calculateBoundaries() {\n if (skeleton.length === 0) {\n return;\n }\n\n const first = skeleton[0]!;\n let minX = first.x,\n maxX = first.x,\n minY = first.y,\n maxY = first.y;\n for (const p of skeleton) {\n if (p.x < minX) minX = p.x;\n if (p.x > maxX) maxX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.y > maxY) maxY = p.y;\n }\n\n const width = maxX - minX;\n const height = maxY - minY;\n const canvasWidth = canvas.width;\n const canvasHeight = canvas.height;\n\n const scaleX = canvasWidth / (width * (1 + FIT_PADDING * 2));\n const scaleY = canvasHeight / (height * (1 + FIT_PADDING * 2));\n scale = Math.min(scaleX, scaleY);\n\n const boundsWidth = width * scale;\n const boundsHeight = height * scale;\n offsetX = (canvasWidth - boundsWidth) / 2 - minX * scale;\n offsetY = (canvasHeight - boundsHeight) / 2 - minY * scale;\n }\n\n /**\n * Draws the skeleton once into an OffscreenCanvas so that every frame\n * only needs a single ctx.drawImage() instead of rebuilding the full path.\n */\n function buildSkeletonCanvas() {\n if (skeleton.length < 2) return;\n\n skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);\n const sCtx = skeletonCanvas.getContext(\"2d\")!;\n\n sCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;\n sCtx.lineWidth = 1.5;\n sCtx.beginPath();\n\n const first = skeleton[0]!;\n sCtx.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);\n for (let i = 1; i < skeleton.length; i++) {\n const p = skeleton[i]!;\n sCtx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);\n }\n sCtx.stroke();\n }\n\n function drawSkeleton() {\n if (!skeletonCanvas) return;\n ctx.drawImage(skeletonCanvas, 0, 0);\n }\n\n function drawTrail() {\n if (trailCount < 2) return;\n\n // Set constant state once outside the batch loop\n ctx.lineJoin = \"round\";\n ctx.lineCap = \"round\";\n\n for (let b = 0; b < trailCount - 1; b += TRAIL_BATCH_SIZE) {\n const bEnd = Math.min(b + TRAIL_BATCH_SIZE, trailCount - 1);\n /** Normalized position of this batch along the trail (0 = tail, 1 = head) */\n const progress = (b + bEnd) / 2 / (trailCount - 1);\n const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;\n const lineWidth = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);\n\n ctx.beginPath();\n for (let i = b; i <= bEnd; i++) {\n const p = trail[i]!;\n if (i === b) {\n ctx.moveTo(p.x * scale + offsetX, p.y * scale + offsetY);\n } else {\n ctx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);\n }\n }\n\n ctx.strokeStyle = `rgba(${trailRgb},${alpha})`;\n ctx.lineWidth = lineWidth;\n ctx.stroke();\n }\n }\n\n function drawHead() {\n if (!head) return;\n\n const x = head.x * scale + offsetX;\n const y = head.y * scale + offsetY;\n\n const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);\n gradient.addColorStop(0, opts.headColor);\n gradient.addColorStop(GLOW_INNER_EDGE, headRgbFalloff);\n gradient.addColorStop(1, \"transparent\");\n\n ctx.fillStyle = gradient;\n ctx.beginPath();\n ctx.arc(x, y, opts.glowSize, 0, Math.PI * 2);\n ctx.fill();\n\n ctx.fillStyle = opts.headColor;\n ctx.beginPath();\n ctx.arc(x, y, opts.headRadius, 0, Math.PI * 2);\n ctx.fill();\n }\n\n function render() {\n const now = performance.now();\n const deltaTime = (now - lastTime) / 1000;\n lastTime = now;\n\n trail = engine.tick(deltaTime);\n trailCount = engine.trailCount;\n head = trailCount > 0 ? trail[trailCount - 1]! : null;\n\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n drawSkeleton();\n drawTrail();\n drawHead();\n\n animationId = requestAnimationFrame(render);\n }\n\n // Initialize skeleton and offscreen canvas on creation\n skeleton = engine.getSarmalSkeleton();\n calculateBoundaries();\n buildSkeletonCanvas();\n\n return {\n start() {\n if (animationId !== null) return;\n lastTime = performance.now();\n render();\n },\n\n stop() {\n if (animationId === null) return;\n cancelAnimationFrame(animationId);\n animationId = null;\n },\n\n reset() {\n engine.reset();\n trail = [];\n head = null;\n },\n\n destroy() {\n if (animationId !== null) {\n cancelAnimationFrame(animationId);\n animationId = null;\n }\n },\n };\n}\n","import type { CurveDef, Point } from \"./types\";\n\nconst TWO_PI = Math.PI * 2;\n\n/**\n * Artemis II free-return lunar trajectory\n * @see https://www.nasa.gov/wp-content/uploads/2025/09/artemis-ii-map-508.pdf\n * a = x-axis asymmetry (widens one lobe),\n * b = y-axis asymmetry,\n * ox = horizontal offset to visually center the shape\n */\nfunction artemis2(t: number, _time: number, _params: Record<string, number>): Point {\n const a = 0.35,\n b = 0.15,\n ox = 0.175;\n const s = Math.sin(t),\n c = Math.cos(t);\n const denom = 1 + s * s;\n return {\n x: (c * (1 + a * c)) / denom - ox,\n y: (s * c * (1 + b * c)) / denom,\n };\n}\n\n/** 7-lobed epitrochoid with a breathing distance parameter — d oscillates with t, making the loops pulse in and out */\nfunction epitrochoid7(t: number, _time: number, _params: Record<string, number>): Point {\n const d = 1.0 + 0.55 * Math.sin(t * 0.5);\n return {\n x: 7 * Math.cos(t) - d * Math.cos(7 * t),\n y: 7 * Math.sin(t) - d * Math.sin(7 * t),\n };\n}\n\nfunction astroid(t: number, _time: number, _params: Record<string, number>): Point {\n const c = Math.cos(t);\n const s = Math.sin(t);\n return {\n x: c * c * c,\n y: s * s * s,\n };\n}\n\nfunction deltoid(t: number, _time: number, _params: Record<string, number>): Point {\n return {\n x: 2 * Math.cos(t) + Math.cos(2 * t),\n y: 2 * Math.sin(t) - Math.sin(2 * t),\n };\n}\n\nfunction rose5(t: number, _time: number, _params: Record<string, number>): Point {\n const r = Math.cos(5 * t);\n return {\n x: r * Math.cos(t),\n y: r * Math.sin(t),\n };\n}\n\nfunction rose3(t: number, _time: number, _params: Record<string, number>): Point {\n const r = Math.cos(3 * t);\n return {\n x: r * Math.cos(t),\n y: r * Math.sin(t),\n };\n}\n\nexport const curves: Record<string, CurveDef> = {\n artemis2: {\n name: \"Artemis II\",\n fn: artemis2,\n period: TWO_PI,\n speed: 0.7,\n },\n epitrochoid7: {\n name: \"Epitrochoid\",\n fn: epitrochoid7,\n period: TWO_PI,\n speed: 1.4,\n },\n astroid: {\n name: \"Astroid\",\n fn: astroid,\n period: TWO_PI,\n speed: 1.1,\n },\n deltoid: {\n name: \"Deltoid\",\n fn: deltoid,\n period: TWO_PI,\n speed: 0.9,\n },\n rose5: {\n name: \"Rose (n=5)\",\n fn: rose5,\n period: TWO_PI,\n speed: 1.0,\n },\n rose3: {\n name: \"Rose (n=3)\",\n fn: rose3,\n period: TWO_PI,\n speed: 1.15,\n },\n};\n","export type {\n Point,\n CurveDef,\n Engine,\n SarmalInstance,\n RendererOptions,\n SarmalOptions,\n} from \"./types\";\n\nexport { createEngine } from \"./engine\";\nexport { createRenderer } from \"./renderer\";\nexport { curves } from \"./curves\";\n\nimport type { CurveDef, SarmalInstance, SarmalOptions } from \"./types\";\nimport { createEngine } from \"./engine\";\nimport { createRenderer } from \"./renderer\";\n\n/**\n * Creates a sarmal animation on a canvas element\n *\n * @example\n * ```ts\n * import { createSarmal, curves } from '@sarmal/core'\n * const sarmal = createSarmal(canvas, curves.artemis2)\n * sarmal.start()\n * ```\n */\nexport function createSarmal(\n canvas: HTMLCanvasElement,\n curveDef: CurveDef,\n options?: SarmalOptions,\n): SarmalInstance {\n const { trailLength, ...rendererOpts } = options ?? {};\n const engine = createEngine(curveDef, trailLength);\n\n return createRenderer({ canvas, engine, ...rendererOpts });\n}\n","/**\n * Scans for `<canvas data-sarmal=\"curveName\">` when DOMContentLoaded is triggered,\n * and creates a Sarmal instance for each one\n *\n * Usage (CDN):\n * <script src=\"https://unpkg.com/@sarmal/core/dist/auto-init.js\"></script>\n * <canvas data-sarmal=\"artemis2\" width=\"200\" height=\"200\"></canvas>\n *\n * Usage (ESM):\n * import '@sarmal/core/auto'\n */\nimport { createSarmal } from \"./index\";\nimport { curves } from \"./curves\";\n\nfunction init(): void {\n const canvases = document.querySelectorAll<HTMLCanvasElement>(\"canvas[data-sarmal]\");\n\n canvases.forEach((canvas) => {\n const curveName = canvas.getAttribute(\"data-sarmal\");\n if (curveName == null) {\n return console.warn(\"[sarmal] curveName isrequried\");\n }\n\n const curveDef = curves[curveName];\n if (!curveDef) {\n return console.error(`[sarmal] \"${curveName}\" is not a valid curve name`);\n }\n\n const sarmal = createSarmal(canvas, curveDef, {\n ...(canvas.dataset.trailColor && { trailColor: canvas.dataset.trailColor }),\n ...(canvas.dataset.skeletonColor && { skeletonColor: canvas.dataset.skeletonColor }),\n ...(canvas.dataset.headColor && { headColor: canvas.dataset.headColor }),\n ...(canvas.dataset.headRadius && { headRadius: parseFloat(canvas.dataset.headRadius) }),\n ...(canvas.dataset.glowSize && { glowSize: parseInt(canvas.dataset.glowSize, 10) }),\n ...(canvas.dataset.trailLength && { trailLength: parseInt(canvas.dataset.trailLength, 10) }),\n });\n sarmal.start();\n });\n}\n\nif (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", init);\n} else {\n init();\n}\n"]}
1
+ {"version":3,"sources":["../src/engine.ts","../src/renderer.ts","../src/curves.ts","../src/index.ts","../src/auto-init.ts"],"names":["options","TWO_PI"],"mappings":"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","file":"auto-init.cjs","sourcesContent":["import type { CurveDef, Engine, Point, SeekOptions, SeekWithTrailOptions } from \"./types\";\n\nconst TWO_PI = Math.PI * 2;\nconst POINTS_PER_PERIOD_UNIT = 50;\n\n/**\n * A fixed-size list of points with first in, last out method\n * The oldest entry is automatically discarded when the list is at capacity\n *\n * Note: `result.length` is *never* changed,\n * so callers use the separate `count` getter to know valid size\n */\nclass CircularBuffer {\n private data: Array<Point>;\n private result: Array<Point>;\n private capacity: number;\n private head: number = 0;\n private count: number = 0;\n\n constructor(capacity: number) {\n this.capacity = capacity;\n this.data = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));\n this.result = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));\n }\n\n /** Mutates in-place */\n push(x: number, y: number): void {\n const slot = this.data[this.head]!;\n\n slot.x = x;\n slot.y = y;\n this.head = (this.head + 1) % this.capacity;\n\n if (this.count < this.capacity) {\n this.count++;\n }\n }\n\n /**\n * Copies ordered points into the pre-allocated result buffer and returns it\n * Note: The *same* array reference is returned every call,\n * so `result.length` is also always `capacity`\n */\n toArray(): Array<Point> {\n const start = this.count < this.capacity ? 0 : this.head;\n\n for (let i = 0; i < this.count; i++) {\n const src = this.data[(start + i) % this.capacity]!;\n const dst = this.result[i]!;\n dst.x = src.x;\n dst.y = src.y;\n }\n\n return this.result;\n }\n\n clear(): void {\n this.head = 0;\n this.count = 0;\n }\n\n get length() {\n return this.count;\n }\n}\n\n/**\n * Creates the core simulation engine for a sarmal\n *\n * it runs a clock (time `t`), asks the curve for the current Point position at that time,\n * and remembers the last N positions so the renderer can draw the trail\n *\n * The engine is only responsible for math coordinates,\n * so it is not responsible for drawing or colors\n *\n * @param curveDef A curve definition\n * @param trailLength default: `120`\n */\nexport function createEngine(curveDef: CurveDef, trailLength: number = 120): Engine {\n const curve = {\n name: curveDef.name,\n fn: curveDef.fn,\n period: curveDef.period ?? TWO_PI,\n speed: curveDef.speed ?? 1,\n };\n const trail = new CircularBuffer(trailLength);\n let t = 0;\n let actualTime = 0;\n\n return {\n tick(deltaTime: number): Array<Point> {\n t = (t + curve.speed * deltaTime) % curve.period;\n actualTime += deltaTime;\n const point = curve.fn(t, actualTime, {});\n trail.push(point.x, point.y);\n return trail.toArray();\n },\n\n get trailCount() {\n return trail.length;\n },\n\n reset() {\n t = 0;\n actualTime = 0;\n trail.clear();\n },\n\n seek(newT: number, { clearTrail = false }: SeekOptions = {}) {\n t = ((newT % curve.period) + curve.period) % curve.period;\n if (clearTrail) {\n trail.clear();\n }\n },\n\n seekWithTrail(targetT: number, { wrap = false }: SeekWithTrailOptions = {}) {\n // TODO: assumes 60fps, will need to further evaluate approach\n const STEP = 1 / 60;\n const advance = curve.speed * STEP;\n const target = ((targetT % curve.period) + curve.period) % curve.period;\n const targetTime = target / curve.speed;\n\n t = target;\n actualTime = targetTime;\n trail.clear();\n\n const pointsFromStart = Math.floor(target / advance) + 1;\n const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);\n\n for (let step = count - 1; step >= 0; step--) {\n const sampleT = target - step * advance;\n const wrappedT = sampleT < 0 ? sampleT + curve.period : sampleT;\n const time = targetTime - step * STEP;\n const point = curve.fn(wrappedT, time, {});\n trail.push(point.x, point.y);\n }\n },\n\n getSarmalSkeleton(): Array<Point> {\n const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);\n // oxlint-disable-next-line unicorn/no-new-array -- array is pre-allocated, filled immediately below\n const points: Array<Point> = new Array(steps);\n for (let i = 0; i < steps; i++) {\n const sampleT = (i / (steps - 1)) * curve.period;\n const point = curve.fn(sampleT, 0, {});\n points[i] = point;\n }\n return points;\n },\n };\n}\n","import type { Point, RendererOptions, SarmalInstance } from \"./types\";\n\nconst DEFAULT_HEAD_RADIUS = 4;\n// TODO: Re-evaluate glow implementation. Current approach looks TERRIBLE!\n// Consider: remove glow entirely, replace with a sharper bloom, or make it opt-in only (default 0).\nconst DEFAULT_GLOW_SIZE = 20;\nconst DEFAULT_SKELETON_COLOR = \"#ffffff\";\nconst DEFAULT_SKELETON_OPACITY = 0.15;\n\n/** Fraction of the bounding box added as padding when fitting the curve to the canvas */\nconst FIT_PADDING = 0.1;\n\n/**\n * The trail is drawn in batches of points\n * Each batch has lower opacity than the one that comes before it\n * (0 = oldest/tail, 1 = newest/head)\n *\n * ! Performance note: Larger batch size = fewer GPU stroke calls per frame\n */\nconst TRAIL_BATCH_SIZE = 20;\n/** Higher values = sharper fade near the tail, more of the trail appears faint */\nconst TRAIL_FADE_CURVE = 1.5;\nconst TRAIL_MAX_OPACITY = 0.88;\n/** Line width of tail */\nconst TRAIL_MIN_WIDTH = 0.5;\n/** Line width of head */\nconst TRAIL_MAX_WIDTH = 2.5;\n\nconst GLOW_INNER_EDGE = 0.4;\n/** Opacity at the inner edge of the glow falloff */\nconst GLOW_FALLOFF_OPACITY = 0.53;\n\n/** Parses a hex color into its \"r,g,b\" string for use in rgba() — called once at init */\nfunction hexToRgbComponents(hex: string): string {\n const n = parseInt(hex.slice(1), 16);\n return `${n >> 16},${(n >> 8) & 255},${n & 255}`;\n}\n\n/**\n * Creates a Canvas 2D renderer for sarmal animations\n * Renders the skeleton, the trail, and the glowing dot\n */\nexport function createRenderer(options: RendererOptions): SarmalInstance {\n const canvas = options.canvas;\n if (!canvas.getContext(\"2d\")) {\n throw new Error(\"Could not get 2d context from canvas\");\n }\n const ctx = canvas.getContext(\"2d\")!;\n\n const engine = options.engine;\n const opts = {\n skeletonColor: options.skeletonColor ?? DEFAULT_SKELETON_COLOR,\n trailColor: options.trailColor ?? \"#ffffff\",\n headColor: options.headColor ?? \"#ffffff\",\n headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,\n glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE,\n };\n\n const trailRgb = hexToRgbComponents(opts.trailColor);\n const headRgbFalloff = `rgba(${hexToRgbComponents(opts.headColor)},${GLOW_FALLOFF_OPACITY})`;\n\n let skeleton: Array<Point> = [];\n let skeletonCanvas: OffscreenCanvas | null = null;\n let trail: Array<Point> = [];\n let trailCount = 0;\n let head: Point | null = null;\n let scale = 1;\n let offsetX = 0;\n let offsetY = 0;\n let animationId: number | null = null;\n let lastTime = 0;\n\n /**\n * Computes how to map engine coordinates to canvas pixels\n *\n * Steps are roughly: curve fn -> coordinate point -> (scale + offset) -> pixel\n *\n * 1. Find the bounding box of the skeleton (min/max x/y in coordinates)\n * 2. Compute a scale factor within the bounds into the canvas with padding\n * 3. Compute offsets to center the curve in the canvas\n */\n function calculateBoundaries() {\n if (skeleton.length === 0) {\n return;\n }\n\n const first = skeleton[0]!;\n let minX = first.x,\n maxX = first.x,\n minY = first.y,\n maxY = first.y;\n for (const p of skeleton) {\n if (p.x < minX) {\n minX = p.x;\n }\n\n if (p.x > maxX) {\n maxX = p.x;\n }\n\n if (p.y < minY) {\n minY = p.y;\n }\n\n if (p.y > maxY) {\n maxY = p.y;\n }\n }\n\n const width = maxX - minX;\n const height = maxY - minY;\n const canvasWidth = canvas.width;\n const canvasHeight = canvas.height;\n\n const scaleX = canvasWidth / (width * (1 + FIT_PADDING * 2));\n const scaleY = canvasHeight / (height * (1 + FIT_PADDING * 2));\n scale = Math.min(scaleX, scaleY);\n\n const boundsWidth = width * scale;\n const boundsHeight = height * scale;\n offsetX = (canvasWidth - boundsWidth) / 2 - minX * scale;\n offsetY = (canvasHeight - boundsHeight) / 2 - minY * scale;\n }\n\n /**\n * Draws the skeleton once into an OffscreenCanvas so that every frame\n * only needs a single ctx.drawImage() instead of rebuilding the full path.\n */\n function buildSkeletonCanvas() {\n if (skeleton.length < 2) return;\n\n skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);\n const skeletonCtx = skeletonCanvas.getContext(\"2d\")!;\n\n skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;\n skeletonCtx.lineWidth = 1.5;\n skeletonCtx.beginPath();\n\n const first = skeleton[0]!;\n skeletonCtx.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);\n\n for (let i = 1; i < skeleton.length; i++) {\n const p = skeleton[i]!;\n skeletonCtx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);\n }\n\n skeletonCtx.stroke();\n }\n\n function drawSkeleton() {\n if (!skeletonCanvas) {\n return;\n }\n\n ctx.drawImage(skeletonCanvas, 0, 0);\n }\n\n function drawTrail() {\n if (trailCount < 2) {\n return;\n }\n\n // Set constant state once outside the batch loop\n ctx.lineJoin = \"round\";\n ctx.lineCap = \"round\";\n\n for (let batchIndex = 0; batchIndex < trailCount - 1; batchIndex += TRAIL_BATCH_SIZE) {\n const bEnd = Math.min(batchIndex + TRAIL_BATCH_SIZE, trailCount - 1);\n /** Normalized position of this batch along the trail (0 = tail, 1 = head) */\n const progress = (batchIndex + bEnd) / 2 / (trailCount - 1);\n const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;\n const lineWidth = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);\n\n ctx.beginPath();\n for (let i = batchIndex; i <= bEnd; i++) {\n const point = trail[i]!;\n\n if (i === batchIndex) {\n ctx.moveTo(point.x * scale + offsetX, point.y * scale + offsetY);\n } else {\n ctx.lineTo(point.x * scale + offsetX, point.y * scale + offsetY);\n }\n }\n\n // ! AI Note\n // FIXME: still allocates a new string every batch every frame (~20x/frame).\n // `trailRgb` avoids re-parsing the hex, but alpha is a continuous float so the full\n // rgba string can't be pre-computed. Fix: discretize alpha into N buckets at init\n // and do a lookup (e.g. trailColors[Math.round(progress * N)]) instead of a template literal.\n ctx.strokeStyle = `rgba(${trailRgb},${alpha})`;\n ctx.lineWidth = lineWidth;\n ctx.stroke();\n }\n }\n\n function drawHead() {\n if (!head) {\n return;\n }\n\n const x = head.x * scale + offsetX;\n const y = head.y * scale + offsetY;\n\n const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);\n gradient.addColorStop(0, opts.headColor);\n gradient.addColorStop(GLOW_INNER_EDGE, headRgbFalloff);\n gradient.addColorStop(1, \"transparent\");\n\n ctx.fillStyle = gradient;\n ctx.beginPath();\n ctx.arc(x, y, opts.glowSize, 0, Math.PI * 2);\n ctx.fill();\n\n ctx.fillStyle = opts.headColor;\n ctx.beginPath();\n ctx.arc(x, y, opts.headRadius, 0, Math.PI * 2);\n ctx.fill();\n }\n\n function render() {\n const now = performance.now();\n const deltaTime = Math.min((now - lastTime) / 1000, 1 / 30);\n lastTime = now;\n\n trail = engine.tick(deltaTime);\n trailCount = engine.trailCount;\n head = trailCount > 0 ? trail[trailCount - 1]! : null;\n\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n drawSkeleton();\n drawTrail();\n drawHead();\n\n animationId = requestAnimationFrame(render);\n }\n\n // Initialize skeleton and offscreen canvas on creation\n skeleton = engine.getSarmalSkeleton();\n calculateBoundaries();\n buildSkeletonCanvas();\n\n return {\n start() {\n if (animationId !== null) {\n return;\n }\n\n lastTime = performance.now();\n render();\n },\n\n stop() {\n if (animationId === null) {\n return;\n }\n\n cancelAnimationFrame(animationId);\n animationId = null;\n },\n\n reset() {\n engine.reset();\n trail = [];\n head = null;\n },\n\n destroy() {\n if (animationId !== null) {\n cancelAnimationFrame(animationId);\n animationId = null;\n }\n },\n\n seek(t, options) {\n engine.seek(t, options);\n },\n\n seekWithTrail(t) {\n engine.seekWithTrail(t);\n },\n };\n}\n","import type { CurveDef, Point } from \"./types\";\n\nconst TWO_PI = Math.PI * 2;\n\n/**\n * Artemis II free-return lunar trajectory\n * @see https://www.nasa.gov/wp-content/uploads/2025/09/artemis-ii-map-508.pdf\n * a = x-axis asymmetry (widens one lobe),\n * b = y-axis asymmetry,\n * ox = horizontal offset to visually center the shape\n */\nfunction artemis2(t: number, _time: number, _params: Record<string, number>): Point {\n const a = 0.35,\n b = 0.15,\n ox = 0.175;\n const s = Math.sin(t),\n c = Math.cos(t);\n const denom = 1 + s * s;\n return {\n x: (c * (1 + a * c)) / denom - ox,\n y: (s * c * (1 + b * c)) / denom,\n };\n}\n\nfunction epitrochoid7(t: number, _time: number, _params: Record<string, number>): Point {\n const d = 1.0 + 0.55 * Math.sin(t * 0.5);\n return {\n x: 7 * Math.cos(t) - d * Math.cos(7 * t),\n y: 7 * Math.sin(t) - d * Math.sin(7 * t),\n };\n}\n\nfunction astroid(t: number, _time: number, _params: Record<string, number>): Point {\n const c = Math.cos(t);\n const s = Math.sin(t);\n return {\n x: c * c * c,\n y: s * s * s,\n };\n}\n\nfunction deltoid(t: number, _time: number, _params: Record<string, number>): Point {\n return {\n x: 2 * Math.cos(t) + Math.cos(2 * t),\n y: 2 * Math.sin(t) - Math.sin(2 * t),\n };\n}\n\nfunction rose5(t: number, _time: number, _params: Record<string, number>): Point {\n const r = Math.cos(5 * t);\n return {\n x: r * Math.cos(t),\n y: r * Math.sin(t),\n };\n}\n\nfunction rose3(t: number, _time: number, _params: Record<string, number>): Point {\n const r = Math.cos(3 * t);\n return {\n x: r * Math.cos(t),\n y: r * Math.sin(t),\n };\n}\n\nfunction lissajous32(t: number, time: number, _params: Record<string, number>): Point {\n const phi = time * 0.45;\n return {\n x: Math.sin(3 * t + phi),\n y: Math.sin(2 * t),\n };\n}\n\nfunction lissajous43(t: number, time: number, _params: Record<string, number>): Point {\n const phi = time * 0.38;\n return {\n x: Math.sin(4 * t + phi),\n y: Math.sin(3 * t),\n };\n}\n\nfunction epicycloid3(t: number, _time: number, _params: Record<string, number>): Point {\n return {\n x: 4 * Math.cos(t) - Math.cos(4 * t),\n y: 4 * Math.sin(t) - Math.sin(4 * t),\n };\n}\n\nfunction lame(t: number, time: number, _params: Record<string, number>): Point {\n const p = 1.75 + 1.25 * Math.sin(time * 0.48);\n const c = Math.cos(t),\n s = Math.sin(t);\n return {\n x: Math.sign(c) * Math.pow(Math.abs(c), p),\n y: Math.sign(s) * Math.pow(Math.abs(s), p),\n };\n}\nexport const curves: Record<string, CurveDef> = {\n artemis2: {\n name: \"Artemis II\",\n fn: artemis2,\n period: TWO_PI,\n speed: 0.7,\n },\n epitrochoid7: {\n name: \"Epitrochoid\",\n fn: epitrochoid7,\n period: TWO_PI,\n speed: 1.4,\n },\n astroid: {\n name: \"Astroid\",\n fn: astroid,\n period: TWO_PI,\n speed: 1.1,\n },\n deltoid: {\n name: \"Deltoid\",\n fn: deltoid,\n period: TWO_PI,\n speed: 0.9,\n },\n rose5: {\n name: \"Rose (n=5)\",\n fn: rose5,\n period: TWO_PI,\n speed: 1.0,\n },\n rose3: {\n name: \"Rose (n=3)\",\n fn: rose3,\n period: TWO_PI,\n speed: 1.15,\n },\n lissajous32: {\n name: \"Lissajous 3:2\",\n fn: lissajous32,\n period: TWO_PI,\n speed: 2.0,\n },\n lissajous43: {\n name: \"Lissajous 4:3\",\n fn: lissajous43,\n period: TWO_PI,\n speed: 1.8,\n },\n epicycloid3: {\n name: \"Epicycloid (n=3)\",\n fn: epicycloid3,\n period: TWO_PI,\n speed: 0.75,\n },\n lame: {\n name: \"Lamé Curve\",\n fn: lame,\n period: TWO_PI,\n speed: 1.0,\n },\n};\n","export type { SVGRendererOptions, SVGSarmalOptions } from \"./renderer-svg\";\nexport type {\n Point,\n CurveDef,\n Engine,\n SarmalInstance,\n SeekOptions,\n SeekWithTrailOptions,\n RendererOptions,\n SarmalOptions,\n} from \"./types\";\n\nexport { createEngine } from \"./engine\";\nexport { createRenderer } from \"./renderer\";\nexport { createSVGRenderer, createSarmalSVG } from \"./renderer-svg\";\nexport { curves } from \"./curves\";\n\nimport type { CurveDef, SarmalInstance, SarmalOptions } from \"./types\";\nimport { createEngine } from \"./engine\";\nimport { createRenderer } from \"./renderer\";\n\n/**\n * Creates a sarmal animation on a canvas element\n *\n * @example\n * ```ts\n * import { createSarmal, curves } from '@sarmal/core'\n * const sarmal = createSarmal(canvas, curves.artemis2)\n * sarmal.start()\n * ```\n */\nexport function createSarmal(\n canvas: HTMLCanvasElement,\n curveDef: CurveDef,\n options?: SarmalOptions,\n): SarmalInstance {\n const { trailLength, ...rendererOpts } = options ?? {};\n const engine = createEngine(curveDef, trailLength);\n\n return createRenderer({ canvas, engine, ...rendererOpts });\n}\n","/**\n * Scans for `<canvas data-sarmal=\"curveName\">` when DOMContentLoaded is triggered,\n * and creates a Sarmal instance for each one\n *\n * Usage (CDN):\n * <script src=\"https://unpkg.com/@sarmal/core/dist/auto-init.js\"></script>\n * <canvas data-sarmal=\"artemis2\" width=\"200\" height=\"200\"></canvas>\n *\n * Usage (ESM):\n * import '@sarmal/core/auto'\n */\nimport { createSarmal } from \"./index\";\nimport { curves } from \"./curves\";\n\nfunction init(): void {\n const canvases = document.querySelectorAll<HTMLCanvasElement>(\"canvas[data-sarmal]\");\n\n canvases.forEach((canvas) => {\n const curveName = canvas.getAttribute(\"data-sarmal\");\n if (curveName == null) {\n return console.warn(\"[sarmal] curveName isrequried\");\n }\n\n const curveDef = curves[curveName];\n if (!curveDef) {\n return console.error(`[sarmal] \"${curveName}\" is not a valid curve name`);\n }\n\n const sarmal = createSarmal(canvas, curveDef, {\n ...(canvas.dataset.trailColor && { trailColor: canvas.dataset.trailColor }),\n ...(canvas.dataset.skeletonColor && { skeletonColor: canvas.dataset.skeletonColor }),\n ...(canvas.dataset.headColor && { headColor: canvas.dataset.headColor }),\n ...(canvas.dataset.headRadius && { headRadius: parseFloat(canvas.dataset.headRadius) }),\n ...(canvas.dataset.glowSize && { glowSize: parseInt(canvas.dataset.glowSize, 10) }),\n ...(canvas.dataset.trailLength && { trailLength: parseInt(canvas.dataset.trailLength, 10) }),\n });\n sarmal.start();\n });\n}\n\nif (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", init);\n} else {\n init();\n}\n"]}
package/dist/auto-init.js CHANGED
@@ -9,7 +9,7 @@ var CircularBuffer = class {
9
9
  this.data = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
10
10
  this.result = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
11
11
  }
12
- /** Mutates the pre-allocated slot in-place — no allocation */
12
+ /** Mutates in-place */
13
13
  push(x, y) {
14
14
  const slot = this.data[this.head];
15
15
  slot.x = x;
@@ -20,9 +20,9 @@ var CircularBuffer = class {
20
20
  }
21
21
  }
22
22
  /**
23
- * Copies ordered points into the pre-allocated result buffer and returns it.
24
- * The same array reference is returned every call — result.length is always `capacity`.
25
- * Read only indices 0..count-1; the rest are stale pre-allocated slots.
23
+ * Copies ordered points into the pre-allocated result buffer and returns it
24
+ * Note: The *same* array reference is returned every call,
25
+ * so `result.length` is also always `capacity`
26
26
  */
27
27
  toArray() {
28
28
  const start = this.count < this.capacity ? 0 : this.head;
@@ -68,6 +68,30 @@ function createEngine(curveDef, trailLength = 120) {
68
68
  actualTime = 0;
69
69
  trail.clear();
70
70
  },
71
+ seek(newT, { clearTrail = false } = {}) {
72
+ t = (newT % curve.period + curve.period) % curve.period;
73
+ if (clearTrail) {
74
+ trail.clear();
75
+ }
76
+ },
77
+ seekWithTrail(targetT, { wrap = false } = {}) {
78
+ const STEP = 1 / 60;
79
+ const advance = curve.speed * STEP;
80
+ const target = (targetT % curve.period + curve.period) % curve.period;
81
+ const targetTime = target / curve.speed;
82
+ t = target;
83
+ actualTime = targetTime;
84
+ trail.clear();
85
+ const pointsFromStart = Math.floor(target / advance) + 1;
86
+ const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
87
+ for (let step = count - 1; step >= 0; step--) {
88
+ const sampleT = target - step * advance;
89
+ const wrappedT = sampleT < 0 ? sampleT + curve.period : sampleT;
90
+ const time = targetTime - step * STEP;
91
+ const point = curve.fn(wrappedT, time, {});
92
+ trail.push(point.x, point.y);
93
+ }
94
+ },
71
95
  getSarmalSkeleton() {
72
96
  const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);
73
97
  const points = new Array(steps);
@@ -131,10 +155,18 @@ function createRenderer(options) {
131
155
  const first = skeleton[0];
132
156
  let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
133
157
  for (const p of skeleton) {
134
- if (p.x < minX) minX = p.x;
135
- if (p.x > maxX) maxX = p.x;
136
- if (p.y < minY) minY = p.y;
137
- if (p.y > maxY) maxY = p.y;
158
+ if (p.x < minX) {
159
+ minX = p.x;
160
+ }
161
+ if (p.x > maxX) {
162
+ maxX = p.x;
163
+ }
164
+ if (p.y < minY) {
165
+ minY = p.y;
166
+ }
167
+ if (p.y > maxY) {
168
+ maxY = p.y;
169
+ }
138
170
  }
139
171
  const width = maxX - minX;
140
172
  const height = maxY - minY;
@@ -151,38 +183,42 @@ function createRenderer(options) {
151
183
  function buildSkeletonCanvas() {
152
184
  if (skeleton.length < 2) return;
153
185
  skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
154
- const sCtx = skeletonCanvas.getContext("2d");
155
- sCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
156
- sCtx.lineWidth = 1.5;
157
- sCtx.beginPath();
186
+ const skeletonCtx = skeletonCanvas.getContext("2d");
187
+ skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
188
+ skeletonCtx.lineWidth = 1.5;
189
+ skeletonCtx.beginPath();
158
190
  const first = skeleton[0];
159
- sCtx.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);
191
+ skeletonCtx.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);
160
192
  for (let i = 1; i < skeleton.length; i++) {
161
193
  const p = skeleton[i];
162
- sCtx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
194
+ skeletonCtx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
163
195
  }
164
- sCtx.stroke();
196
+ skeletonCtx.stroke();
165
197
  }
166
198
  function drawSkeleton() {
167
- if (!skeletonCanvas) return;
199
+ if (!skeletonCanvas) {
200
+ return;
201
+ }
168
202
  ctx.drawImage(skeletonCanvas, 0, 0);
169
203
  }
170
204
  function drawTrail() {
171
- if (trailCount < 2) return;
205
+ if (trailCount < 2) {
206
+ return;
207
+ }
172
208
  ctx.lineJoin = "round";
173
209
  ctx.lineCap = "round";
174
- for (let b = 0; b < trailCount - 1; b += TRAIL_BATCH_SIZE) {
175
- const bEnd = Math.min(b + TRAIL_BATCH_SIZE, trailCount - 1);
176
- const progress = (b + bEnd) / 2 / (trailCount - 1);
210
+ for (let batchIndex = 0; batchIndex < trailCount - 1; batchIndex += TRAIL_BATCH_SIZE) {
211
+ const bEnd = Math.min(batchIndex + TRAIL_BATCH_SIZE, trailCount - 1);
212
+ const progress = (batchIndex + bEnd) / 2 / (trailCount - 1);
177
213
  const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
178
214
  const lineWidth = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
179
215
  ctx.beginPath();
180
- for (let i = b; i <= bEnd; i++) {
181
- const p = trail[i];
182
- if (i === b) {
183
- ctx.moveTo(p.x * scale + offsetX, p.y * scale + offsetY);
216
+ for (let i = batchIndex; i <= bEnd; i++) {
217
+ const point = trail[i];
218
+ if (i === batchIndex) {
219
+ ctx.moveTo(point.x * scale + offsetX, point.y * scale + offsetY);
184
220
  } else {
185
- ctx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
221
+ ctx.lineTo(point.x * scale + offsetX, point.y * scale + offsetY);
186
222
  }
187
223
  }
188
224
  ctx.strokeStyle = `rgba(${trailRgb},${alpha})`;
@@ -191,7 +227,9 @@ function createRenderer(options) {
191
227
  }
192
228
  }
193
229
  function drawHead() {
194
- if (!head) return;
230
+ if (!head) {
231
+ return;
232
+ }
195
233
  const x = head.x * scale + offsetX;
196
234
  const y = head.y * scale + offsetY;
197
235
  const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);
@@ -209,7 +247,7 @@ function createRenderer(options) {
209
247
  }
210
248
  function render() {
211
249
  const now = performance.now();
212
- const deltaTime = (now - lastTime) / 1e3;
250
+ const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
213
251
  lastTime = now;
214
252
  trail = engine.tick(deltaTime);
215
253
  trailCount = engine.trailCount;
@@ -225,12 +263,16 @@ function createRenderer(options) {
225
263
  buildSkeletonCanvas();
226
264
  return {
227
265
  start() {
228
- if (animationId !== null) return;
266
+ if (animationId !== null) {
267
+ return;
268
+ }
229
269
  lastTime = performance.now();
230
270
  render();
231
271
  },
232
272
  stop() {
233
- if (animationId === null) return;
273
+ if (animationId === null) {
274
+ return;
275
+ }
234
276
  cancelAnimationFrame(animationId);
235
277
  animationId = null;
236
278
  },
@@ -244,6 +286,12 @@ function createRenderer(options) {
244
286
  cancelAnimationFrame(animationId);
245
287
  animationId = null;
246
288
  }
289
+ },
290
+ seek(t, options2) {
291
+ engine.seek(t, options2);
292
+ },
293
+ seekWithTrail(t) {
294
+ engine.seekWithTrail(t);
247
295
  }
248
296
  };
249
297
  }
@@ -294,6 +342,34 @@ function rose3(t, _time, _params) {
294
342
  y: r * Math.sin(t)
295
343
  };
296
344
  }
345
+ function lissajous32(t, time, _params) {
346
+ const phi = time * 0.45;
347
+ return {
348
+ x: Math.sin(3 * t + phi),
349
+ y: Math.sin(2 * t)
350
+ };
351
+ }
352
+ function lissajous43(t, time, _params) {
353
+ const phi = time * 0.38;
354
+ return {
355
+ x: Math.sin(4 * t + phi),
356
+ y: Math.sin(3 * t)
357
+ };
358
+ }
359
+ function epicycloid3(t, _time, _params) {
360
+ return {
361
+ x: 4 * Math.cos(t) - Math.cos(4 * t),
362
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
363
+ };
364
+ }
365
+ function lame(t, time, _params) {
366
+ const p = 1.75 + 1.25 * Math.sin(time * 0.48);
367
+ const c = Math.cos(t), s = Math.sin(t);
368
+ return {
369
+ x: Math.sign(c) * Math.pow(Math.abs(c), p),
370
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
371
+ };
372
+ }
297
373
  var curves = {
298
374
  artemis2: {
299
375
  name: "Artemis II",
@@ -330,6 +406,30 @@ var curves = {
330
406
  fn: rose3,
331
407
  period: TWO_PI2,
332
408
  speed: 1.15
409
+ },
410
+ lissajous32: {
411
+ name: "Lissajous 3:2",
412
+ fn: lissajous32,
413
+ period: TWO_PI2,
414
+ speed: 2
415
+ },
416
+ lissajous43: {
417
+ name: "Lissajous 4:3",
418
+ fn: lissajous43,
419
+ period: TWO_PI2,
420
+ speed: 1.8
421
+ },
422
+ epicycloid3: {
423
+ name: "Epicycloid (n=3)",
424
+ fn: epicycloid3,
425
+ period: TWO_PI2,
426
+ speed: 0.75
427
+ },
428
+ lame: {
429
+ name: "Lam\xE9 Curve",
430
+ fn: lame,
431
+ period: TWO_PI2,
432
+ speed: 1
333
433
  }
334
434
  };
335
435