@sarmal/core 0.25.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto-init.cjs +107 -104
- package/dist/auto-init.cjs.map +1 -1
- package/dist/auto-init.js +107 -104
- package/dist/auto-init.js.map +1 -1
- package/dist/cli.js +1048 -0
- package/dist/cli.js.map +1 -0
- package/dist/curves/artemis2.cjs +5 -5
- package/dist/curves/artemis2.cjs.map +1 -1
- package/dist/curves/artemis2.d.cts +1 -1
- package/dist/curves/artemis2.d.ts +1 -1
- package/dist/curves/artemis2.js +5 -5
- package/dist/curves/artemis2.js.map +1 -1
- package/dist/curves/astroid.cjs +3 -3
- package/dist/curves/astroid.cjs.map +1 -1
- package/dist/curves/astroid.d.cts +1 -1
- package/dist/curves/astroid.d.ts +1 -1
- package/dist/curves/astroid.js +3 -3
- package/dist/curves/astroid.js.map +1 -1
- package/dist/curves/deltoid.cjs +3 -3
- package/dist/curves/deltoid.cjs.map +1 -1
- package/dist/curves/deltoid.d.cts +1 -1
- package/dist/curves/deltoid.d.ts +1 -1
- package/dist/curves/deltoid.js +3 -3
- package/dist/curves/deltoid.js.map +1 -1
- package/dist/curves/epicycloid3.cjs +3 -3
- package/dist/curves/epicycloid3.cjs.map +1 -1
- package/dist/curves/epicycloid3.d.cts +1 -1
- package/dist/curves/epicycloid3.d.ts +1 -1
- package/dist/curves/epicycloid3.js +3 -3
- package/dist/curves/epicycloid3.js.map +1 -1
- package/dist/curves/epitrochoid7.cjs +7 -7
- package/dist/curves/epitrochoid7.cjs.map +1 -1
- package/dist/curves/epitrochoid7.d.cts +1 -1
- package/dist/curves/epitrochoid7.d.ts +1 -1
- package/dist/curves/epitrochoid7.js +7 -7
- package/dist/curves/epitrochoid7.js.map +1 -1
- package/dist/curves/index.cjs +63 -63
- package/dist/curves/index.cjs.map +1 -1
- package/dist/curves/index.d.cts +1 -1
- package/dist/curves/index.d.ts +1 -1
- package/dist/curves/index.js +63 -63
- package/dist/curves/index.js.map +1 -1
- package/dist/curves/lame.cjs +4 -4
- package/dist/curves/lame.cjs.map +1 -1
- package/dist/curves/lame.d.cts +1 -1
- package/dist/curves/lame.d.ts +1 -1
- package/dist/curves/lame.js +4 -4
- package/dist/curves/lame.js.map +1 -1
- package/dist/curves/lissajous32.cjs +4 -4
- package/dist/curves/lissajous32.cjs.map +1 -1
- package/dist/curves/lissajous32.d.cts +1 -1
- package/dist/curves/lissajous32.d.ts +1 -1
- package/dist/curves/lissajous32.js +4 -4
- package/dist/curves/lissajous32.js.map +1 -1
- package/dist/curves/lissajous43.cjs +4 -4
- package/dist/curves/lissajous43.cjs.map +1 -1
- package/dist/curves/lissajous43.d.cts +1 -1
- package/dist/curves/lissajous43.d.ts +1 -1
- package/dist/curves/lissajous43.js +4 -4
- package/dist/curves/lissajous43.js.map +1 -1
- package/dist/curves/rose3.cjs +4 -4
- package/dist/curves/rose3.cjs.map +1 -1
- package/dist/curves/rose3.d.cts +1 -1
- package/dist/curves/rose3.d.ts +1 -1
- package/dist/curves/rose3.js +4 -4
- package/dist/curves/rose3.js.map +1 -1
- package/dist/curves/rose5.cjs +4 -4
- package/dist/curves/rose5.cjs.map +1 -1
- package/dist/curves/rose5.d.cts +1 -1
- package/dist/curves/rose5.d.ts +1 -1
- package/dist/curves/rose5.js +4 -4
- package/dist/curves/rose5.js.map +1 -1
- package/dist/curves/rose52.cjs +4 -4
- package/dist/curves/rose52.cjs.map +1 -1
- package/dist/curves/rose52.d.cts +1 -1
- package/dist/curves/rose52.d.ts +1 -1
- package/dist/curves/rose52.js +4 -4
- package/dist/curves/rose52.js.map +1 -1
- package/dist/curves/star.cjs +6 -6
- package/dist/curves/star.cjs.map +1 -1
- package/dist/curves/star.d.cts +1 -1
- package/dist/curves/star.d.ts +1 -1
- package/dist/curves/star.js +6 -6
- package/dist/curves/star.js.map +1 -1
- package/dist/curves/star4.cjs +6 -6
- package/dist/curves/star4.cjs.map +1 -1
- package/dist/curves/star4.d.cts +2 -2
- package/dist/curves/star4.d.ts +2 -2
- package/dist/curves/star4.js +6 -6
- package/dist/curves/star4.js.map +1 -1
- package/dist/curves/star7.cjs +6 -6
- package/dist/curves/star7.cjs.map +1 -1
- package/dist/curves/star7.d.cts +2 -2
- package/dist/curves/star7.d.ts +2 -2
- package/dist/curves/star7.js +6 -6
- package/dist/curves/star7.js.map +1 -1
- package/dist/index.cjs +107 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -45
- package/dist/index.d.ts +13 -45
- package/dist/index.js +107 -104
- package/dist/index.js.map +1 -1
- package/dist/renderer-shared-OR--cv-t.d.ts +49 -0
- package/dist/renderer-shared-jqw_Q1WO.d.cts +49 -0
- package/dist/terminal.cjs +593 -0
- package/dist/terminal.cjs.map +1 -0
- package/dist/terminal.d.cts +44 -0
- package/dist/terminal.d.ts +44 -0
- package/dist/terminal.js +585 -0
- package/dist/terminal.js.map +1 -0
- package/dist/{types-Z9i1_AQZ.d.cts → types-zbxUgcmZ.d.cts} +30 -30
- package/dist/{types-Z9i1_AQZ.d.ts → types-zbxUgcmZ.d.ts} +30 -30
- package/package.json +11 -1
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1048 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// src/engine.ts
|
|
3
|
+
var TWO_PI = Math.PI * 2;
|
|
4
|
+
var POINTS_PER_PERIOD_UNIT = 50;
|
|
5
|
+
function lerp(start, end, t) {
|
|
6
|
+
return start + (end - start) * t;
|
|
7
|
+
}
|
|
8
|
+
var EMPTY_PARAMS = {};
|
|
9
|
+
var CircularBuffer = class {
|
|
10
|
+
constructor(capacity) {
|
|
11
|
+
this.head = 0;
|
|
12
|
+
this.count = 0;
|
|
13
|
+
this.capacity = capacity;
|
|
14
|
+
this.data = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
|
|
15
|
+
this.result = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
|
|
16
|
+
}
|
|
17
|
+
/** Mutates in-place */
|
|
18
|
+
push(x, y) {
|
|
19
|
+
const slot = this.data[this.head];
|
|
20
|
+
slot.x = x;
|
|
21
|
+
slot.y = y;
|
|
22
|
+
this.head = (this.head + 1) % this.capacity;
|
|
23
|
+
if (this.count < this.capacity) {
|
|
24
|
+
this.count++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Copies ordered points into the pre-allocated result buffer and returns it
|
|
29
|
+
* Note: The *same* array reference is returned every call,
|
|
30
|
+
* so `result.length` is also always `capacity`
|
|
31
|
+
*/
|
|
32
|
+
toArray() {
|
|
33
|
+
const start = this.count < this.capacity ? 0 : this.head;
|
|
34
|
+
for (let i = 0; i < this.count; i++) {
|
|
35
|
+
const src = this.data[(start + i) % this.capacity];
|
|
36
|
+
const dst = this.result[i];
|
|
37
|
+
dst.x = src.x;
|
|
38
|
+
dst.y = src.y;
|
|
39
|
+
}
|
|
40
|
+
return this.result;
|
|
41
|
+
}
|
|
42
|
+
clear() {
|
|
43
|
+
this.head = 0;
|
|
44
|
+
this.count = 0;
|
|
45
|
+
}
|
|
46
|
+
get length() {
|
|
47
|
+
return this.count;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function resolveCurve(curveDef) {
|
|
51
|
+
const period = curveDef.period ?? TWO_PI;
|
|
52
|
+
if (!Number.isFinite(period) || period <= 0) {
|
|
53
|
+
throw new RangeError(`[sarmal] period must be a positive finite number, got ${period}`);
|
|
54
|
+
}
|
|
55
|
+
const speed = curveDef.speed ?? 1;
|
|
56
|
+
if (!Number.isFinite(speed)) {
|
|
57
|
+
throw new RangeError(`[sarmal] speed must be a finite number, got ${speed}`);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
name: curveDef.name,
|
|
61
|
+
fn: curveDef.fn,
|
|
62
|
+
period,
|
|
63
|
+
speed,
|
|
64
|
+
skeleton: curveDef.skeleton,
|
|
65
|
+
skeletonFn: curveDef.skeletonFn,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function createEngine(curveDef, trailLength = 120) {
|
|
69
|
+
if (!Number.isFinite(trailLength) || trailLength <= 0) {
|
|
70
|
+
throw new RangeError(
|
|
71
|
+
`[sarmal] trailLength must be a positive finite number, got ${trailLength}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
let curve = resolveCurve(curveDef);
|
|
75
|
+
const trail = new CircularBuffer(trailLength);
|
|
76
|
+
let phase = 0;
|
|
77
|
+
let actualTime = 0;
|
|
78
|
+
let userSpeedOverride = null;
|
|
79
|
+
let morphCurveB = null;
|
|
80
|
+
let _morphAlpha = null;
|
|
81
|
+
let _morphStrategy = "normalized";
|
|
82
|
+
let _speedTransition = null;
|
|
83
|
+
function sampleSkeleton(c, samplePhase) {
|
|
84
|
+
if (c.skeletonFn) {
|
|
85
|
+
return c.skeletonFn(samplePhase);
|
|
86
|
+
}
|
|
87
|
+
if (c.skeleton === "live") {
|
|
88
|
+
return c.fn(samplePhase, actualTime, EMPTY_PARAMS);
|
|
89
|
+
}
|
|
90
|
+
return c.fn(samplePhase, 0, EMPTY_PARAMS);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
tick(deltaTime) {
|
|
94
|
+
if (_speedTransition !== null) {
|
|
95
|
+
_speedTransition.elapsed += deltaTime * 1e3;
|
|
96
|
+
const alpha = Math.min(_speedTransition.elapsed / _speedTransition.duration, 1);
|
|
97
|
+
userSpeedOverride = lerp(_speedTransition.from, _speedTransition.to, alpha);
|
|
98
|
+
if (alpha >= 1) {
|
|
99
|
+
userSpeedOverride = _speedTransition.to;
|
|
100
|
+
_speedTransition.resolve();
|
|
101
|
+
_speedTransition = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
let effectiveSpeed = userSpeedOverride ?? curve.speed;
|
|
105
|
+
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
106
|
+
effectiveSpeed = lerp(effectiveSpeed, morphCurveB.speed, _morphAlpha);
|
|
107
|
+
}
|
|
108
|
+
phase = (phase + effectiveSpeed * deltaTime) % curve.period;
|
|
109
|
+
actualTime += deltaTime;
|
|
110
|
+
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
111
|
+
const a = curve.fn(phase, actualTime, EMPTY_PARAMS);
|
|
112
|
+
const phaseB =
|
|
113
|
+
_morphStrategy === "normalized" ? (phase / curve.period) * morphCurveB.period : phase;
|
|
114
|
+
const b = morphCurveB.fn(phaseB, actualTime, EMPTY_PARAMS);
|
|
115
|
+
trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
|
|
116
|
+
} else {
|
|
117
|
+
const point = curve.fn(phase, actualTime, EMPTY_PARAMS);
|
|
118
|
+
trail.push(point.x, point.y);
|
|
119
|
+
}
|
|
120
|
+
return trail.toArray();
|
|
121
|
+
},
|
|
122
|
+
get trailCount() {
|
|
123
|
+
return trail.length;
|
|
124
|
+
},
|
|
125
|
+
get trailLength() {
|
|
126
|
+
return trailLength;
|
|
127
|
+
},
|
|
128
|
+
get isLiveSkeleton() {
|
|
129
|
+
return curve.skeleton === "live";
|
|
130
|
+
},
|
|
131
|
+
get morphAlpha() {
|
|
132
|
+
return _morphAlpha;
|
|
133
|
+
},
|
|
134
|
+
reset() {
|
|
135
|
+
phase = 0;
|
|
136
|
+
actualTime = 0;
|
|
137
|
+
trail.clear();
|
|
138
|
+
},
|
|
139
|
+
jump(newPhase, { clearTrail = false } = {}) {
|
|
140
|
+
phase = ((newPhase % curve.period) + curve.period) % curve.period;
|
|
141
|
+
if (clearTrail) {
|
|
142
|
+
trail.clear();
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
seek(targetPhase, { wrap = false, step = curve.period / trailLength } = {}) {
|
|
146
|
+
const advance = curve.speed * step;
|
|
147
|
+
const target = ((targetPhase % curve.period) + curve.period) % curve.period;
|
|
148
|
+
const targetTime = target / curve.speed;
|
|
149
|
+
phase = target;
|
|
150
|
+
actualTime = targetTime;
|
|
151
|
+
trail.clear();
|
|
152
|
+
const pointsFromStart = Math.floor(target / advance) + 1;
|
|
153
|
+
const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
|
|
154
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
155
|
+
const samplePhase = target - i * advance;
|
|
156
|
+
const wrappedPhase = ((samplePhase % curve.period) + curve.period) % curve.period;
|
|
157
|
+
const elapsed = targetTime - i * step;
|
|
158
|
+
const point = curve.fn(wrappedPhase, elapsed, EMPTY_PARAMS);
|
|
159
|
+
trail.push(point.x, point.y);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
startMorph(target, strategy = "normalized") {
|
|
163
|
+
const resolvedTarget = resolveCurve(target);
|
|
164
|
+
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
165
|
+
const frozenAlpha = _morphAlpha;
|
|
166
|
+
const frozenA = curve;
|
|
167
|
+
const frozenB = morphCurveB;
|
|
168
|
+
const frozenStrategy = _morphStrategy;
|
|
169
|
+
curve = {
|
|
170
|
+
...frozenB,
|
|
171
|
+
fn: (samplePhase, elapsed, params) => {
|
|
172
|
+
const a = frozenA.fn(samplePhase, elapsed, params);
|
|
173
|
+
const phaseB =
|
|
174
|
+
frozenStrategy === "normalized"
|
|
175
|
+
? (samplePhase / frozenA.period) * frozenB.period
|
|
176
|
+
: samplePhase;
|
|
177
|
+
const b = frozenB.fn(phaseB, elapsed, params);
|
|
178
|
+
return {
|
|
179
|
+
x: a.x + (b.x - a.x) * frozenAlpha,
|
|
180
|
+
y: a.y + (b.y - a.y) * frozenAlpha,
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
_morphStrategy = strategy;
|
|
186
|
+
morphCurveB = resolvedTarget;
|
|
187
|
+
_morphAlpha = 0;
|
|
188
|
+
},
|
|
189
|
+
setMorphAlpha(alpha) {
|
|
190
|
+
_morphAlpha = alpha;
|
|
191
|
+
},
|
|
192
|
+
completeMorph() {
|
|
193
|
+
if (morphCurveB !== null) {
|
|
194
|
+
if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
|
|
195
|
+
phase = (phase / curve.period) * morphCurveB.period;
|
|
196
|
+
}
|
|
197
|
+
curve = morphCurveB;
|
|
198
|
+
}
|
|
199
|
+
morphCurveB = null;
|
|
200
|
+
_morphAlpha = null;
|
|
201
|
+
},
|
|
202
|
+
getSarmalSkeleton() {
|
|
203
|
+
const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);
|
|
204
|
+
const points2 = new Array(steps);
|
|
205
|
+
if (morphCurveB !== null && _morphAlpha !== null) {
|
|
206
|
+
for (let i = 0; i < steps; i++) {
|
|
207
|
+
const samplePhase = (i / (steps - 1)) * curve.period;
|
|
208
|
+
const a = sampleSkeleton(curve, samplePhase);
|
|
209
|
+
const phaseB =
|
|
210
|
+
_morphStrategy === "normalized"
|
|
211
|
+
? (samplePhase / curve.period) * morphCurveB.period
|
|
212
|
+
: samplePhase;
|
|
213
|
+
const b = sampleSkeleton(morphCurveB, phaseB);
|
|
214
|
+
points2[i] = {
|
|
215
|
+
x: a.x + (b.x - a.x) * _morphAlpha,
|
|
216
|
+
y: a.y + (b.y - a.y) * _morphAlpha,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return points2;
|
|
220
|
+
}
|
|
221
|
+
for (let i = 0; i < steps; i++) {
|
|
222
|
+
const samplePhase = (i / (steps - 1)) * curve.period;
|
|
223
|
+
points2[i] = sampleSkeleton(curve, samplePhase);
|
|
224
|
+
}
|
|
225
|
+
return points2;
|
|
226
|
+
},
|
|
227
|
+
setSpeed(speed) {
|
|
228
|
+
if (!Number.isFinite(speed)) {
|
|
229
|
+
throw new Error("speed must be a finite number");
|
|
230
|
+
}
|
|
231
|
+
if (_speedTransition !== null) {
|
|
232
|
+
_speedTransition.reject(new Error("Speed transition cancelled"));
|
|
233
|
+
_speedTransition = null;
|
|
234
|
+
}
|
|
235
|
+
userSpeedOverride = speed;
|
|
236
|
+
},
|
|
237
|
+
getSpeed() {
|
|
238
|
+
return userSpeedOverride ?? curve.speed;
|
|
239
|
+
},
|
|
240
|
+
resetSpeed() {
|
|
241
|
+
userSpeedOverride = null;
|
|
242
|
+
},
|
|
243
|
+
setSpeedOver(speed, duration) {
|
|
244
|
+
if (!Number.isFinite(speed)) {
|
|
245
|
+
throw new Error("speed must be a finite number");
|
|
246
|
+
}
|
|
247
|
+
if (!Number.isFinite(duration) || duration <= 0) {
|
|
248
|
+
throw new Error("duration must be a finite number greater than 0");
|
|
249
|
+
}
|
|
250
|
+
if (_speedTransition !== null) {
|
|
251
|
+
_speedTransition.reject(new Error("Speed transition cancelled"));
|
|
252
|
+
_speedTransition = null;
|
|
253
|
+
}
|
|
254
|
+
const from = userSpeedOverride ?? curve.speed;
|
|
255
|
+
return new Promise((resolve, reject) => {
|
|
256
|
+
_speedTransition = { from, to: speed, elapsed: 0, duration, resolve, reject };
|
|
257
|
+
});
|
|
258
|
+
},
|
|
259
|
+
cancelSpeedTransition() {
|
|
260
|
+
if (_speedTransition !== null) {
|
|
261
|
+
_speedTransition.reject(new Error("Speed transition cancelled"));
|
|
262
|
+
_speedTransition = null;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/renderer-shared.ts
|
|
269
|
+
var FIT_PADDING = 0.1;
|
|
270
|
+
var FIT_PADDING_MIN = 4;
|
|
271
|
+
function computeBoundaries(pts, logicalWidth, logicalHeight, minPaddingPx = FIT_PADDING_MIN) {
|
|
272
|
+
if (pts.length === 0) return null;
|
|
273
|
+
const first = pts[0];
|
|
274
|
+
let minX = first.x,
|
|
275
|
+
maxX = first.x,
|
|
276
|
+
minY = first.y,
|
|
277
|
+
maxY = first.y;
|
|
278
|
+
for (const p of pts) {
|
|
279
|
+
if (p.x < minX) {
|
|
280
|
+
minX = p.x;
|
|
281
|
+
}
|
|
282
|
+
if (p.x > maxX) {
|
|
283
|
+
maxX = p.x;
|
|
284
|
+
}
|
|
285
|
+
if (p.y < minY) {
|
|
286
|
+
minY = p.y;
|
|
287
|
+
}
|
|
288
|
+
if (p.y > maxY) {
|
|
289
|
+
maxY = p.y;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const w = maxX - minX;
|
|
293
|
+
const h = maxY - minY;
|
|
294
|
+
if (w === 0 && h === 0) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
"[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t.",
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
const scaleXProportional = logicalWidth / (w * (1 + FIT_PADDING * 2));
|
|
300
|
+
const scaleYProportional = logicalHeight / (h * (1 + FIT_PADDING * 2));
|
|
301
|
+
const scaleXMinPadding = (logicalWidth - minPaddingPx * 2) / w;
|
|
302
|
+
const scaleYMinPadding = (logicalHeight - minPaddingPx * 2) / h;
|
|
303
|
+
const scale = Math.min(
|
|
304
|
+
scaleXProportional,
|
|
305
|
+
scaleYProportional,
|
|
306
|
+
scaleXMinPadding,
|
|
307
|
+
scaleYMinPadding,
|
|
308
|
+
);
|
|
309
|
+
return {
|
|
310
|
+
scale,
|
|
311
|
+
offsetX: (logicalWidth - w * scale) / 2 - minX * scale,
|
|
312
|
+
offsetY: (logicalHeight - h * scale) / 2 - minY * scale,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function hexToRgb(hex) {
|
|
316
|
+
const n = parseInt(hex.slice(1), 16);
|
|
317
|
+
return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/terminal.ts
|
|
321
|
+
var DEFAULT_TRAIL_HEX = "#ec5571";
|
|
322
|
+
var DEFAULT_FPS = 30;
|
|
323
|
+
var DEFAULT_SIZE = 16;
|
|
324
|
+
var BRIGHTNESS_HEAD = 1;
|
|
325
|
+
var BRIGHTNESS_TAIL = 0.15;
|
|
326
|
+
var BRAILLE_BASE = 10240;
|
|
327
|
+
var BRAILLE_BIT = [
|
|
328
|
+
[1, 8],
|
|
329
|
+
[2, 16],
|
|
330
|
+
[4, 32],
|
|
331
|
+
[64, 128],
|
|
332
|
+
];
|
|
333
|
+
function brailleChar(bits) {
|
|
334
|
+
return String.fromCodePoint(BRAILLE_BASE + (bits & 255));
|
|
335
|
+
}
|
|
336
|
+
function dotToCell(dotCol, dotRow) {
|
|
337
|
+
return {
|
|
338
|
+
charCol: Math.floor(dotCol / 2),
|
|
339
|
+
charRow: Math.floor(dotRow / 4),
|
|
340
|
+
dotColInCell: dotCol % 2,
|
|
341
|
+
dotRowInCell: dotRow % 4,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function detectColor() {
|
|
345
|
+
const colorterm = (process.env.COLORTERM ?? "").toLowerCase();
|
|
346
|
+
if (colorterm === "truecolor" || colorterm === "24bit") {
|
|
347
|
+
return "truecolor";
|
|
348
|
+
}
|
|
349
|
+
const term = (process.env.TERM ?? "").toLowerCase();
|
|
350
|
+
if (term.includes("256color")) {
|
|
351
|
+
return "256-color";
|
|
352
|
+
}
|
|
353
|
+
if (term.includes("truecolor") || term.includes("24bit")) {
|
|
354
|
+
return "truecolor";
|
|
355
|
+
}
|
|
356
|
+
if (colorterm !== "") {
|
|
357
|
+
return "256-color";
|
|
358
|
+
}
|
|
359
|
+
if (term !== "" && term !== "linux" && term !== "dumb") {
|
|
360
|
+
return "256-color";
|
|
361
|
+
}
|
|
362
|
+
return "monochrome";
|
|
363
|
+
}
|
|
364
|
+
function rgbTo256(r, g, b) {
|
|
365
|
+
const avg = Math.round((r + g + b) / 3);
|
|
366
|
+
if (Math.abs(r - avg) <= 4 && Math.abs(g - avg) <= 4 && Math.abs(b - avg) <= 4) {
|
|
367
|
+
if (avg <= 8) {
|
|
368
|
+
return 16;
|
|
369
|
+
}
|
|
370
|
+
return 232 + Math.min(23, Math.round((avg - 8) / 10));
|
|
371
|
+
}
|
|
372
|
+
const ri = Math.round((r / 255) * 5);
|
|
373
|
+
const gi = Math.round((g / 255) * 5);
|
|
374
|
+
const bi = Math.round((b / 255) * 5);
|
|
375
|
+
return 16 + 36 * ri + 6 * gi + bi;
|
|
376
|
+
}
|
|
377
|
+
var AR = "\x1B[0m";
|
|
378
|
+
function ansiTruecolorFg(r, g, b) {
|
|
379
|
+
return `\x1B[38;2;${r};${g};${b}m`;
|
|
380
|
+
}
|
|
381
|
+
function ansi256Fg(code) {
|
|
382
|
+
return `\x1B[38;5;${code}m`;
|
|
383
|
+
}
|
|
384
|
+
function ansiColor(r, g, b, colorCap) {
|
|
385
|
+
if (colorCap === "truecolor") {
|
|
386
|
+
return ansiTruecolorFg(r, g, b);
|
|
387
|
+
}
|
|
388
|
+
if (colorCap === "256-color") {
|
|
389
|
+
return ansi256Fg(rgbTo256(r, g, b));
|
|
390
|
+
}
|
|
391
|
+
return "";
|
|
392
|
+
}
|
|
393
|
+
function snapCol(col, max) {
|
|
394
|
+
return Math.max(0, Math.min(max - 1, Math.round(col)));
|
|
395
|
+
}
|
|
396
|
+
function applyBoundary(x, y, scale, offsetX, offsetY) {
|
|
397
|
+
return {
|
|
398
|
+
screenX: x * scale + offsetX,
|
|
399
|
+
screenY: y * scale + offsetY,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function renderFrame(
|
|
403
|
+
trail,
|
|
404
|
+
trailCount,
|
|
405
|
+
charWidth,
|
|
406
|
+
charHeight,
|
|
407
|
+
scale,
|
|
408
|
+
offsetX,
|
|
409
|
+
offsetY,
|
|
410
|
+
trailRgb,
|
|
411
|
+
headRgb,
|
|
412
|
+
colorCap,
|
|
413
|
+
) {
|
|
414
|
+
const dotWidth = charWidth * 2;
|
|
415
|
+
const dotHeight = charHeight * 4;
|
|
416
|
+
const grid = Array.from({ length: charHeight }, () =>
|
|
417
|
+
Array.from({ length: charWidth }, () => ({ bits: 0, brightness: 0, isHead: false })),
|
|
418
|
+
);
|
|
419
|
+
for (let i = 0; i < trailCount; i++) {
|
|
420
|
+
const pt = trail[i];
|
|
421
|
+
const t = trailCount > 1 ? i / (trailCount - 1) : 1;
|
|
422
|
+
const brightness = BRIGHTNESS_TAIL + (BRIGHTNESS_HEAD - BRIGHTNESS_TAIL) * t;
|
|
423
|
+
const { screenX, screenY } = applyBoundary(pt.x, pt.y, scale, offsetX, offsetY);
|
|
424
|
+
const dotCol = snapCol(screenX, dotWidth);
|
|
425
|
+
const dotRow = snapCol(screenY, dotHeight);
|
|
426
|
+
const cell = dotToCell(dotCol, dotRow);
|
|
427
|
+
if (
|
|
428
|
+
cell.charRow < 0 ||
|
|
429
|
+
cell.charRow >= charHeight ||
|
|
430
|
+
cell.charCol < 0 ||
|
|
431
|
+
cell.charCol >= charWidth
|
|
432
|
+
) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const c = grid[cell.charRow][cell.charCol];
|
|
436
|
+
c.bits |= BRAILLE_BIT[cell.dotRowInCell][cell.dotColInCell];
|
|
437
|
+
if (brightness > c.brightness) {
|
|
438
|
+
c.brightness = brightness;
|
|
439
|
+
}
|
|
440
|
+
if (i === trailCount - 1) {
|
|
441
|
+
c.isHead = true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (colorCap !== "monochrome") {
|
|
445
|
+
return renderColorFrame(grid, charWidth, charHeight, trailRgb, headRgb, colorCap);
|
|
446
|
+
}
|
|
447
|
+
return renderMonochromeFrame(grid, charWidth, charHeight);
|
|
448
|
+
}
|
|
449
|
+
function renderColorFrame(grid, charWidth, charHeight, trailRgb, headRgb, colorCap) {
|
|
450
|
+
const lines = [];
|
|
451
|
+
for (let row = 0; row < charHeight; row++) {
|
|
452
|
+
let line = "";
|
|
453
|
+
for (let col = 0; col < charWidth; col++) {
|
|
454
|
+
const cell = grid[row][col];
|
|
455
|
+
if (cell.bits === 0) {
|
|
456
|
+
line += " ";
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const ch = brailleChar(cell.bits);
|
|
460
|
+
if (cell.isHead) {
|
|
461
|
+
line += ansiColor(headRgb.r, headRgb.g, headRgb.b, colorCap) + ch + AR;
|
|
462
|
+
} else {
|
|
463
|
+
const r = Math.round(trailRgb.r * cell.brightness);
|
|
464
|
+
const g = Math.round(trailRgb.g * cell.brightness);
|
|
465
|
+
const b = Math.round(trailRgb.b * cell.brightness);
|
|
466
|
+
line += ansiColor(r, g, b, colorCap) + ch + AR;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
lines.push(line);
|
|
470
|
+
}
|
|
471
|
+
return lines.join("\n");
|
|
472
|
+
}
|
|
473
|
+
function renderMonochromeFrame(grid, charWidth, charHeight) {
|
|
474
|
+
const lines = [];
|
|
475
|
+
for (let row = 0; row < charHeight; row++) {
|
|
476
|
+
let line = "";
|
|
477
|
+
for (let col = 0; col < charWidth; col++) {
|
|
478
|
+
const cell = grid[row][col];
|
|
479
|
+
if (cell.bits === 0) {
|
|
480
|
+
line += " ";
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (cell.isHead) {
|
|
484
|
+
line += "\u28FF";
|
|
485
|
+
} else {
|
|
486
|
+
line += brailleChar(cell.bits);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
lines.push(line);
|
|
490
|
+
}
|
|
491
|
+
return lines.join("\n");
|
|
492
|
+
}
|
|
493
|
+
function terminalSarmal(stream, curveDef, options) {
|
|
494
|
+
if (!stream.isTTY) {
|
|
495
|
+
return () => {};
|
|
496
|
+
}
|
|
497
|
+
const size = options?.size ?? DEFAULT_SIZE;
|
|
498
|
+
const fps = options?.fps ?? DEFAULT_FPS;
|
|
499
|
+
const trailHex = options?.trailColor ?? DEFAULT_TRAIL_HEX;
|
|
500
|
+
const headHex = options?.headColor ?? trailHex;
|
|
501
|
+
const userSpeed = options?.speed;
|
|
502
|
+
const colorCap = detectColor();
|
|
503
|
+
const trailRgb = hexToRgb(trailHex);
|
|
504
|
+
const headRgb = hexToRgb(headHex);
|
|
505
|
+
const engine = createEngine(curveDef);
|
|
506
|
+
if (userSpeed !== void 0) {
|
|
507
|
+
engine.setSpeed(userSpeed);
|
|
508
|
+
}
|
|
509
|
+
const charWidth = size;
|
|
510
|
+
const charHeight = Math.ceil(size / 2);
|
|
511
|
+
const skeleton = engine.getSarmalSkeleton();
|
|
512
|
+
const b = computeBoundaries(skeleton, charWidth * 2, charHeight * 4, 1);
|
|
513
|
+
if (!b) {
|
|
514
|
+
return () => {};
|
|
515
|
+
}
|
|
516
|
+
const { scale, offsetX, offsetY } = b;
|
|
517
|
+
let running = true;
|
|
518
|
+
let firstFrame = true;
|
|
519
|
+
stream.write("\x1B[?25l");
|
|
520
|
+
function cleanup() {
|
|
521
|
+
running = false;
|
|
522
|
+
stream.write("\x1B[?25h");
|
|
523
|
+
stream.write("\n");
|
|
524
|
+
}
|
|
525
|
+
const onSigint = () => {
|
|
526
|
+
cleanup();
|
|
527
|
+
process.exit(0);
|
|
528
|
+
};
|
|
529
|
+
process.on("SIGINT", onSigint);
|
|
530
|
+
function render() {
|
|
531
|
+
const delta = 1 / fps;
|
|
532
|
+
const trail = engine.tick(delta);
|
|
533
|
+
const trailCount = engine.trailCount;
|
|
534
|
+
const frame = renderFrame(
|
|
535
|
+
trail,
|
|
536
|
+
trailCount,
|
|
537
|
+
charWidth,
|
|
538
|
+
charHeight,
|
|
539
|
+
scale,
|
|
540
|
+
offsetX,
|
|
541
|
+
offsetY,
|
|
542
|
+
trailRgb,
|
|
543
|
+
headRgb,
|
|
544
|
+
colorCap,
|
|
545
|
+
);
|
|
546
|
+
if (firstFrame) {
|
|
547
|
+
firstFrame = false;
|
|
548
|
+
stream.write(frame + "\n");
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const rows = charHeight;
|
|
552
|
+
stream.write(`\x1B[${rows}A`);
|
|
553
|
+
const lines = frame.split("\n");
|
|
554
|
+
for (const line of lines) {
|
|
555
|
+
stream.write(line + "\n");
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
render();
|
|
559
|
+
const interval = setInterval(() => {
|
|
560
|
+
if (!running) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
render();
|
|
564
|
+
}, 1e3 / fps);
|
|
565
|
+
function stop() {
|
|
566
|
+
clearInterval(interval);
|
|
567
|
+
process.off("SIGINT", onSigint);
|
|
568
|
+
cleanup();
|
|
569
|
+
}
|
|
570
|
+
return stop;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/catmull-rom.ts
|
|
574
|
+
var PERIOD = 2 * Math.PI;
|
|
575
|
+
function catmullRom1D(p0, p1, p2, p3, u) {
|
|
576
|
+
const u2 = u * u;
|
|
577
|
+
const u3 = u2 * u;
|
|
578
|
+
return (
|
|
579
|
+
0.5 *
|
|
580
|
+
(2 * p1 +
|
|
581
|
+
(-p0 + p2) * u +
|
|
582
|
+
(2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 +
|
|
583
|
+
(-p0 + 3 * p1 - 3 * p2 + p3) * u3)
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
function evaluateCatmullRom(points2, phase) {
|
|
587
|
+
const N = points2.length;
|
|
588
|
+
if (N === 0) {
|
|
589
|
+
return { x: 0, y: 0 };
|
|
590
|
+
}
|
|
591
|
+
if (N === 1) {
|
|
592
|
+
return { x: points2[0][0], y: points2[0][1] };
|
|
593
|
+
}
|
|
594
|
+
phase = ((phase % PERIOD) + PERIOD) % PERIOD;
|
|
595
|
+
const segmentSize = PERIOD / N;
|
|
596
|
+
let i = Math.floor(phase / segmentSize);
|
|
597
|
+
if (i >= N) {
|
|
598
|
+
i = N - 1;
|
|
599
|
+
}
|
|
600
|
+
let u = (phase - i * segmentSize) / segmentSize;
|
|
601
|
+
u = Math.max(0, Math.min(1, u));
|
|
602
|
+
const p0 = points2[(i - 1 + N) % N];
|
|
603
|
+
const p1 = points2[i];
|
|
604
|
+
const p2 = points2[(i + 1) % N];
|
|
605
|
+
const p3 = points2[(i + 2) % N];
|
|
606
|
+
return {
|
|
607
|
+
x: catmullRom1D(p0[0], p1[0], p2[0], p3[0], u),
|
|
608
|
+
y: catmullRom1D(p0[1], p1[1], p2[1], p3[1], u),
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function drawCurve(points2, opts) {
|
|
612
|
+
if (points2.length < 3) {
|
|
613
|
+
throw new Error(`drawCurve requires at least 3 points, received ${points2.length}.`);
|
|
614
|
+
}
|
|
615
|
+
const first = points2[0];
|
|
616
|
+
if (points2.every((p) => p[0] === first[0] && p[1] === first[1])) {
|
|
617
|
+
console.warn(
|
|
618
|
+
"[sarmal].drawCurve: all control points are identical. The curve will be a single point.",
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
const maxAbs = points2.reduce((m, p) => Math.max(m, Math.abs(p[0]), Math.abs(p[1])), 0);
|
|
622
|
+
if (maxAbs > 2) {
|
|
623
|
+
console.warn(
|
|
624
|
+
`[sarmal].drawCurve: control points extend to \xB1${maxAbs.toFixed(1)}, which may render off-screen. Coordinates should be in [-1, 1].`,
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
const pts = points2.map(([x, y]) => [x, y]);
|
|
628
|
+
return {
|
|
629
|
+
name: opts?.name ?? "drawn",
|
|
630
|
+
fn: (phase) => evaluateCatmullRom(pts, phase),
|
|
631
|
+
period: PERIOD,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// src/curves/artemis2.ts
|
|
636
|
+
var points = [
|
|
637
|
+
[-0.44, -0.45],
|
|
638
|
+
[-0.53, -0.77],
|
|
639
|
+
[-0.82, -0.66],
|
|
640
|
+
[-0.82, -0.18],
|
|
641
|
+
[-0.25, -0.04],
|
|
642
|
+
[0.16, -0.49],
|
|
643
|
+
[-0.03, -0.87],
|
|
644
|
+
[-0.68, -0.94],
|
|
645
|
+
[-0.95, -0.61],
|
|
646
|
+
[-0.87, -0],
|
|
647
|
+
[-0.34, 0.21],
|
|
648
|
+
[0.27, -0.04],
|
|
649
|
+
[0.87, 0.06],
|
|
650
|
+
[0.87, 0.57],
|
|
651
|
+
[0.32, 0.66],
|
|
652
|
+
[-0.21, -0.43],
|
|
653
|
+
[-0.43, -0.81],
|
|
654
|
+
[-0.69, -0.84],
|
|
655
|
+
[-0.87, -0.66],
|
|
656
|
+
[-0.9, -0.47],
|
|
657
|
+
[-0.76, -0.35],
|
|
658
|
+
];
|
|
659
|
+
var artemis2 = {
|
|
660
|
+
...drawCurve(points, { name: "Artemis II" }),
|
|
661
|
+
speed: 0.7,
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// src/curves/astroid.ts
|
|
665
|
+
var TWO_PI2 = Math.PI * 2;
|
|
666
|
+
function astroidFn(phase, _elapsed, _params) {
|
|
667
|
+
const c = Math.cos(phase);
|
|
668
|
+
const s = Math.sin(phase);
|
|
669
|
+
return {
|
|
670
|
+
x: c * c * c,
|
|
671
|
+
y: s * s * s,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
var astroid = {
|
|
675
|
+
name: "Astroid",
|
|
676
|
+
fn: astroidFn,
|
|
677
|
+
period: TWO_PI2,
|
|
678
|
+
speed: 1.1,
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/curves/deltoid.ts
|
|
682
|
+
var TWO_PI3 = Math.PI * 2;
|
|
683
|
+
function deltoidFn(phase, _elapsed, _params) {
|
|
684
|
+
return {
|
|
685
|
+
x: 2 * Math.cos(phase) + Math.cos(2 * phase),
|
|
686
|
+
y: 2 * Math.sin(phase) - Math.sin(2 * phase),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
var deltoid = {
|
|
690
|
+
name: "Deltoid",
|
|
691
|
+
fn: deltoidFn,
|
|
692
|
+
period: TWO_PI3,
|
|
693
|
+
speed: 0.9,
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// src/curves/epicycloid3.ts
|
|
697
|
+
var TWO_PI4 = Math.PI * 2;
|
|
698
|
+
function epicycloid3Fn(phase, _elapsed, _params) {
|
|
699
|
+
return {
|
|
700
|
+
x: 4 * Math.cos(phase) - Math.cos(4 * phase),
|
|
701
|
+
y: 4 * Math.sin(phase) - Math.sin(4 * phase),
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
var epicycloid3 = {
|
|
705
|
+
name: "Epicycloid (n=3)",
|
|
706
|
+
fn: epicycloid3Fn,
|
|
707
|
+
period: TWO_PI4,
|
|
708
|
+
speed: 0.75,
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
// src/curves/epitrochoid7.ts
|
|
712
|
+
var TWO_PI5 = Math.PI * 2;
|
|
713
|
+
function epitrochoid7Fn(phase, _elapsed, _params) {
|
|
714
|
+
const d = 1 + 0.55 * Math.sin(phase * 0.5);
|
|
715
|
+
return {
|
|
716
|
+
x: 7 * Math.cos(phase) - d * Math.cos(7 * phase),
|
|
717
|
+
y: 7 * Math.sin(phase) - d * Math.sin(7 * phase),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function epitrochoid7SkeletonFn(phase) {
|
|
721
|
+
const d = 1.275;
|
|
722
|
+
return {
|
|
723
|
+
x: 7 * Math.cos(phase) - d * Math.cos(7 * phase),
|
|
724
|
+
y: 7 * Math.sin(phase) - d * Math.sin(7 * phase),
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
var epitrochoid7 = {
|
|
728
|
+
name: "Epitrochoid",
|
|
729
|
+
fn: epitrochoid7Fn,
|
|
730
|
+
period: TWO_PI5,
|
|
731
|
+
speed: 1.4,
|
|
732
|
+
skeletonFn: epitrochoid7SkeletonFn,
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
// src/curves/lissajous32.ts
|
|
736
|
+
var TWO_PI6 = Math.PI * 2;
|
|
737
|
+
function lissajous32Fn(phase, elapsed, _params) {
|
|
738
|
+
const phi = elapsed * 0.45;
|
|
739
|
+
return {
|
|
740
|
+
x: Math.sin(3 * phase + phi),
|
|
741
|
+
y: Math.sin(2 * phase),
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
var lissajous32 = {
|
|
745
|
+
name: "Lissajous 3:2",
|
|
746
|
+
fn: lissajous32Fn,
|
|
747
|
+
period: TWO_PI6,
|
|
748
|
+
speed: 2,
|
|
749
|
+
skeleton: "live",
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
// src/curves/lissajous43.ts
|
|
753
|
+
var TWO_PI7 = Math.PI * 2;
|
|
754
|
+
function lissajous43Fn(phase, elapsed, _params) {
|
|
755
|
+
const phi = elapsed * 0.38;
|
|
756
|
+
return {
|
|
757
|
+
x: Math.sin(4 * phase + phi),
|
|
758
|
+
y: Math.sin(3 * phase),
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
var lissajous43 = {
|
|
762
|
+
name: "Lissajous 4:3",
|
|
763
|
+
fn: lissajous43Fn,
|
|
764
|
+
period: TWO_PI7,
|
|
765
|
+
speed: 1.8,
|
|
766
|
+
skeleton: "live",
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// src/curves/lame.ts
|
|
770
|
+
var TWO_PI8 = Math.PI * 2;
|
|
771
|
+
function lameFn(phase, elapsed, _params) {
|
|
772
|
+
const p = 1.75 + 1.25 * Math.sin(elapsed * 0.48);
|
|
773
|
+
const c = Math.cos(phase),
|
|
774
|
+
s = Math.sin(phase);
|
|
775
|
+
return {
|
|
776
|
+
x: Math.sign(c) * Math.pow(Math.abs(c), p),
|
|
777
|
+
y: Math.sign(s) * Math.pow(Math.abs(s), p),
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
var lame = {
|
|
781
|
+
name: "Lam\xE9 Curve",
|
|
782
|
+
fn: lameFn,
|
|
783
|
+
period: TWO_PI8,
|
|
784
|
+
speed: 1,
|
|
785
|
+
skeleton: "live",
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// src/curves/rose3.ts
|
|
789
|
+
var TWO_PI9 = Math.PI * 2;
|
|
790
|
+
function rose3Fn(phase, _elapsed, _params) {
|
|
791
|
+
const r = Math.cos(3 * phase);
|
|
792
|
+
return {
|
|
793
|
+
x: r * Math.cos(phase),
|
|
794
|
+
y: r * Math.sin(phase),
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
var rose3 = {
|
|
798
|
+
name: "Rose (n=3)",
|
|
799
|
+
fn: rose3Fn,
|
|
800
|
+
period: TWO_PI9,
|
|
801
|
+
speed: 1.15,
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
// src/curves/rose5.ts
|
|
805
|
+
var TWO_PI10 = Math.PI * 2;
|
|
806
|
+
function rose5Fn(phase, _elapsed, _params) {
|
|
807
|
+
const r = Math.cos(5 * phase);
|
|
808
|
+
return {
|
|
809
|
+
x: r * Math.cos(phase),
|
|
810
|
+
y: r * Math.sin(phase),
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
var rose5 = {
|
|
814
|
+
name: "Rose (n=5)",
|
|
815
|
+
fn: rose5Fn,
|
|
816
|
+
period: TWO_PI10,
|
|
817
|
+
speed: 1,
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// src/curves/rose52.ts
|
|
821
|
+
var FOUR_PI = Math.PI * 4;
|
|
822
|
+
function rose52Fn(phase, _elapsed, _params) {
|
|
823
|
+
const r = Math.cos((5 / 2) * phase);
|
|
824
|
+
return {
|
|
825
|
+
x: r * Math.cos(phase),
|
|
826
|
+
y: r * Math.sin(phase),
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
var rose52 = {
|
|
830
|
+
name: "Rose (n=5/2)",
|
|
831
|
+
fn: rose52Fn,
|
|
832
|
+
period: FOUR_PI,
|
|
833
|
+
speed: 0.8,
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// src/curves/star.ts
|
|
837
|
+
var TWO_PI11 = Math.PI * 2;
|
|
838
|
+
function starFn(phase, _elapsed, _params) {
|
|
839
|
+
const r =
|
|
840
|
+
Math.abs(Math.cos((5 / 2) * phase)) +
|
|
841
|
+
0.35 * Math.abs(Math.cos((15 / 2) * phase)) +
|
|
842
|
+
0.15 * Math.abs(Math.cos((25 / 2) * phase));
|
|
843
|
+
return {
|
|
844
|
+
x: r * Math.cos(phase),
|
|
845
|
+
y: r * Math.sin(phase),
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
var star = {
|
|
849
|
+
name: "Star",
|
|
850
|
+
fn: starFn,
|
|
851
|
+
period: TWO_PI11,
|
|
852
|
+
speed: 1,
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// src/curves/star4.ts
|
|
856
|
+
var TWO_PI12 = Math.PI * 2;
|
|
857
|
+
function star4Fn(phase, _elapsed, _params) {
|
|
858
|
+
const r =
|
|
859
|
+
Math.abs(Math.cos(2 * phase)) +
|
|
860
|
+
0.35 * Math.abs(Math.cos(6 * phase)) +
|
|
861
|
+
0.15 * Math.abs(Math.cos(10 * phase));
|
|
862
|
+
return {
|
|
863
|
+
x: r * Math.cos(phase),
|
|
864
|
+
y: r * Math.sin(phase),
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
var star4 = {
|
|
868
|
+
name: "Star (4-arm)",
|
|
869
|
+
fn: star4Fn,
|
|
870
|
+
period: TWO_PI12,
|
|
871
|
+
speed: 1,
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
// src/curves/star7.ts
|
|
875
|
+
var TWO_PI13 = Math.PI * 2;
|
|
876
|
+
function star7Fn(phase, _elapsed, _params) {
|
|
877
|
+
const r =
|
|
878
|
+
Math.abs(Math.cos((7 / 2) * phase)) +
|
|
879
|
+
0.35 * Math.abs(Math.cos((21 / 2) * phase)) +
|
|
880
|
+
0.15 * Math.abs(Math.cos((35 / 2) * phase));
|
|
881
|
+
return {
|
|
882
|
+
x: r * Math.cos(phase),
|
|
883
|
+
y: r * Math.sin(phase),
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
var star7 = {
|
|
887
|
+
name: "Star (7-arm)",
|
|
888
|
+
fn: star7Fn,
|
|
889
|
+
period: TWO_PI13,
|
|
890
|
+
speed: 1,
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
// src/curves/index.ts
|
|
894
|
+
var curves = {
|
|
895
|
+
artemis2,
|
|
896
|
+
epitrochoid7,
|
|
897
|
+
astroid,
|
|
898
|
+
deltoid,
|
|
899
|
+
rose3,
|
|
900
|
+
rose5,
|
|
901
|
+
rose52,
|
|
902
|
+
star,
|
|
903
|
+
star4,
|
|
904
|
+
star7,
|
|
905
|
+
lissajous32,
|
|
906
|
+
lissajous43,
|
|
907
|
+
epicycloid3,
|
|
908
|
+
lame,
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
// src/cli.ts
|
|
912
|
+
function usage() {
|
|
913
|
+
process.stdout.write(
|
|
914
|
+
[
|
|
915
|
+
"Usage: npx @sarmal/core [options]",
|
|
916
|
+
"",
|
|
917
|
+
"Options:",
|
|
918
|
+
" --name <id> Curve name (e.g. deltoid, rose3). Random if omitted.",
|
|
919
|
+
" --fps <num> Frame rate (default: 30).",
|
|
920
|
+
" --speed <num> Animation speed multiplier (default: curve default).",
|
|
921
|
+
" --size <num> Spinner width in characters (default: 16).",
|
|
922
|
+
" --color <hex> Trail color as 6-digit hex (default: #ec5571).",
|
|
923
|
+
" --verbose Show curve name and hint above spinner.",
|
|
924
|
+
" --help Show this message.",
|
|
925
|
+
"",
|
|
926
|
+
"Examples:",
|
|
927
|
+
" npx @sarmal/core",
|
|
928
|
+
" npx @sarmal/core --name deltoid --verbose",
|
|
929
|
+
" npx @sarmal/core --name rose3 --fps 15 --speed 3",
|
|
930
|
+
"",
|
|
931
|
+
].join("\n") + "\n",
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
function matchArg(argv, i, flag) {
|
|
935
|
+
const value = argv[i + 1];
|
|
936
|
+
if (value === void 0 || value.startsWith("-")) {
|
|
937
|
+
process.stderr.write(`[sarmal] ${flag} requires a value
|
|
938
|
+
`);
|
|
939
|
+
process.exit(1);
|
|
940
|
+
}
|
|
941
|
+
return value;
|
|
942
|
+
}
|
|
943
|
+
function main() {
|
|
944
|
+
const args = process.argv.slice(2);
|
|
945
|
+
let name;
|
|
946
|
+
let verbose = false;
|
|
947
|
+
let fps;
|
|
948
|
+
let speed;
|
|
949
|
+
let size;
|
|
950
|
+
let color;
|
|
951
|
+
for (let i = 0; i < args.length; i++) {
|
|
952
|
+
const arg = args[i];
|
|
953
|
+
switch (arg) {
|
|
954
|
+
case "--name": {
|
|
955
|
+
name = matchArg(args, i, "--name");
|
|
956
|
+
i++;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
case "--fps": {
|
|
960
|
+
const raw = matchArg(args, i, "--fps");
|
|
961
|
+
fps = Number(raw);
|
|
962
|
+
if (!Number.isFinite(fps) || fps <= 0) {
|
|
963
|
+
process.stderr.write(`[sarmal] --fps must be a positive number, got "${raw}"
|
|
964
|
+
`);
|
|
965
|
+
process.exit(1);
|
|
966
|
+
}
|
|
967
|
+
i++;
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
case "--speed": {
|
|
971
|
+
const raw = matchArg(args, i, "--speed");
|
|
972
|
+
speed = Number(raw);
|
|
973
|
+
if (!Number.isFinite(speed)) {
|
|
974
|
+
process.stderr.write(`[sarmal] --speed must be a finite number, got "${raw}"
|
|
975
|
+
`);
|
|
976
|
+
process.exit(1);
|
|
977
|
+
}
|
|
978
|
+
i++;
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
981
|
+
case "--size": {
|
|
982
|
+
const raw = matchArg(args, i, "--size");
|
|
983
|
+
size = Math.round(Number(raw));
|
|
984
|
+
if (!Number.isFinite(size) || size < 1) {
|
|
985
|
+
process.stderr.write(`[sarmal] --size must be a positive integer, got "${raw}"
|
|
986
|
+
`);
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
i++;
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
case "--color": {
|
|
993
|
+
const raw = matchArg(args, i, "--color");
|
|
994
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(raw)) {
|
|
995
|
+
process.stderr.write(`[sarmal] --color must be a 6-digit hex string, got "${raw}"
|
|
996
|
+
`);
|
|
997
|
+
process.exit(1);
|
|
998
|
+
}
|
|
999
|
+
color = raw;
|
|
1000
|
+
i++;
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
case "--verbose": {
|
|
1004
|
+
verbose = true;
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
case "--help": {
|
|
1008
|
+
usage();
|
|
1009
|
+
process.exit(0);
|
|
1010
|
+
}
|
|
1011
|
+
default: {
|
|
1012
|
+
process.stderr.write(`[sarmal] Unknown option: ${arg}
|
|
1013
|
+
`);
|
|
1014
|
+
process.stderr.write("Run with --help for usage.\n");
|
|
1015
|
+
process.exit(1);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
const allCurves = Object.values(curves);
|
|
1020
|
+
let curve = allCurves[Math.floor(Math.random() * allCurves.length)];
|
|
1021
|
+
if (name) {
|
|
1022
|
+
const lower = name.toLowerCase();
|
|
1023
|
+
const key = Object.keys(curves).find((k) => k.toLowerCase() === lower);
|
|
1024
|
+
const found = key ? curves[key] : void 0;
|
|
1025
|
+
if (!found) {
|
|
1026
|
+
process.stderr.write(
|
|
1027
|
+
`[sarmal] Unknown curve: "${name}". Available: ${Object.keys(curves).join(", ")}
|
|
1028
|
+
`,
|
|
1029
|
+
);
|
|
1030
|
+
process.exit(1);
|
|
1031
|
+
}
|
|
1032
|
+
curve = found;
|
|
1033
|
+
}
|
|
1034
|
+
if (verbose) {
|
|
1035
|
+
process.stdout.write(`\x1B[2m${curve.name} \u2014 Ctrl+C to stop\x1B[0m
|
|
1036
|
+
|
|
1037
|
+
`);
|
|
1038
|
+
}
|
|
1039
|
+
terminalSarmal(process.stdout, curve, {
|
|
1040
|
+
...(fps !== void 0 ? { fps } : {}),
|
|
1041
|
+
...(speed !== void 0 ? { speed } : {}),
|
|
1042
|
+
...(size !== void 0 ? { size } : {}),
|
|
1043
|
+
...(color !== void 0 ? { trailColor: color } : {}),
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
main();
|
|
1047
|
+
//# sourceMappingURL=cli.js.map
|
|
1048
|
+
//# sourceMappingURL=cli.js.map
|