coily 0.12.2 → 0.14.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.
@@ -1,976 +0,0 @@
1
- //#region src/motion-set.ts
2
- var MotionSet = class {
3
- #motions = /* @__PURE__ */ new Set();
4
- #debug;
5
- #lastSize = 0;
6
- constructor(debug = false) {
7
- this.#debug = debug;
8
- }
9
- add(motion) {
10
- this.#motions.add(motion);
11
- }
12
- remove(motion) {
13
- this.#motions.delete(motion);
14
- }
15
- tick(dt) {
16
- for (const motion of this.#motions) {
17
- motion.tick(dt);
18
- if (motion.isResting) this.#motions.delete(motion);
19
- }
20
- if (this.#debug && this.#motions.size !== this.#lastSize) {
21
- this.#lastSize = this.#motions.size;
22
- console.log(`coily: ${this.#lastSize} active motions`);
23
- }
24
- }
25
- };
26
- //#endregion
27
- //#region src/util.ts
28
- function invariant(condition, message) {
29
- if (condition) return;
30
- throw new Error(message ?? "Invariant Failed");
31
- }
32
- //#endregion
33
- //#region src/config.ts
34
- /**
35
- * Immutable spring configuration.
36
- */
37
- var SpringConfig = class SpringConfig {
38
- static default = new SpringConfig({
39
- dampingRatio: 1,
40
- duration: 500
41
- });
42
- /** @internal Incremented by `assign` to signal mutations. Do not read or write directly. */
43
- _version = 0;
44
- mass;
45
- tension;
46
- damping;
47
- precision;
48
- naturalFrequency;
49
- criticalDamping;
50
- dampingRatio;
51
- precisionMultiplier;
52
- restingMagnitude;
53
- constructor(input) {
54
- const raw = input;
55
- const mass = raw.mass;
56
- const tension = raw.tension;
57
- const damping = raw.damping;
58
- const bounce = raw.bounce;
59
- const duration = raw.duration !== void 0 ? raw.duration / 1e3 : void 0;
60
- const displacement = raw.displacement ?? 1;
61
- const precision = raw.precision ?? 2;
62
- if (mass !== void 0) invariant(mass > 0, "Mass must be greater than 0");
63
- if (tension !== void 0) invariant(tension > 0, "Tension must be greater than 0");
64
- if (damping !== void 0) invariant(damping >= 0, "Damping must be greater than or equal to 0");
65
- if (bounce !== void 0) invariant(bounce >= -1 && bounce <= 1, "Bounce must be between -1 and 1");
66
- const dampingRatio = raw.dampingRatio ?? (bounce !== void 0 ? Math.max(1e-4, 1 - bounce) : void 0);
67
- if (dampingRatio !== void 0) invariant(dampingRatio >= 0, "Damping ratio must be greater than or equal to 0");
68
- if (duration !== void 0) invariant(duration > 0, "Duration must be greater than 0");
69
- invariant(displacement !== 0, "Displacement must not be 0");
70
- invariant(precision >= 0, "Precision must be greater than or equal to 0");
71
- this.precision = precision;
72
- this.precisionMultiplier = 10 ** precision;
73
- this.restingMagnitude = .5 / this.precisionMultiplier;
74
- const hasM = mass !== void 0;
75
- const hasK = tension !== void 0;
76
- const hasC = damping !== void 0;
77
- const hasZ = dampingRatio !== void 0;
78
- const hasT = duration !== void 0;
79
- if (hasK && hasC && !hasZ && !hasT) {
80
- this.mass = mass ?? 1;
81
- this.tension = tension;
82
- this.damping = damping;
83
- } else if (hasK && hasZ && !hasC && !hasT) {
84
- this.mass = mass ?? 1;
85
- this.tension = tension;
86
- this.damping = 2 * dampingRatio * Math.sqrt(this.mass * tension);
87
- } else if (hasC && hasZ && !hasK && !hasT) {
88
- invariant(dampingRatio > 0, "dampingRatio must be > 0 to derive tension");
89
- this.mass = mass ?? 1;
90
- this.tension = damping ** 2 / (4 * dampingRatio ** 2 * this.mass);
91
- this.damping = damping;
92
- } else if (hasK && hasC && hasZ && !hasT) {
93
- invariant(dampingRatio > 0, "dampingRatio must be > 0 to derive mass");
94
- this.mass = damping ** 2 / (4 * dampingRatio ** 2 * tension);
95
- this.tension = tension;
96
- this.damping = damping;
97
- } else if (hasZ && hasT) {
98
- const threshold = .005;
99
- const x0 = Math.abs(displacement);
100
- invariant(x0 > threshold, "displacement must be greater than the precision threshold");
101
- let sigmaCoeff;
102
- if (dampingRatio < 1) sigmaCoeff = dampingRatio;
103
- else if (dampingRatio === 1) sigmaCoeff = 1;
104
- else sigmaCoeff = dampingRatio - Math.sqrt(dampingRatio ** 2 - 1);
105
- invariant(sigmaCoeff > 0, "dampingRatio must be > 0 for duration-based config");
106
- const wn = 2 * Math.log(x0 / threshold) / (sigmaCoeff * duration);
107
- if (!hasK && !hasC) {
108
- this.mass = mass ?? 1;
109
- this.tension = wn ** 2 * this.mass;
110
- this.damping = 2 * dampingRatio * this.mass * wn;
111
- } else if (hasK && !hasM && !hasC) {
112
- this.mass = tension / wn ** 2;
113
- this.tension = tension;
114
- this.damping = 2 * dampingRatio * this.mass * wn;
115
- } else if (hasC && !hasM && !hasK) {
116
- this.mass = damping / (2 * dampingRatio * wn);
117
- this.tension = wn ** 2 * this.mass;
118
- this.damping = damping;
119
- } else throw new Error("Invalid spring config: provide one of the supported input shapes.");
120
- } else throw new Error("Invalid spring config: provide one of the supported input shapes.");
121
- this.naturalFrequency = Math.sqrt(this.tension / this.mass);
122
- this.criticalDamping = 2 * this.mass * this.naturalFrequency;
123
- this.dampingRatio = this.damping / this.criticalDamping;
124
- }
125
- /** @internal Copies physical parameters from `source` onto this config and bumps `_version`. */
126
- assign(source) {
127
- this._version++;
128
- const t = this;
129
- t.mass = source.mass;
130
- t.tension = source.tension;
131
- t.damping = source.damping;
132
- t.precision = source.precision;
133
- t.naturalFrequency = source.naturalFrequency;
134
- t.criticalDamping = source.criticalDamping;
135
- t.dampingRatio = source.dampingRatio;
136
- t.precisionMultiplier = source.precisionMultiplier;
137
- t.restingMagnitude = source.restingMagnitude;
138
- }
139
- /**
140
- * Analytically estimates the time remaining for a spring to come to rest,
141
- * given the current displacement and velocity.
142
- *
143
- * Uses the exponential decay envelope common to all three damping regimes.
144
- * The decay rate depends on the regime:
145
- * - Underdamped (ζ < 1): σ = ζωₙ
146
- * - Critically damped (ζ = 1): σ = ωₙ
147
- * - Overdamped (ζ > 1): σ = (ζ - √(ζ²-1))ωₙ (the slower eigenvalue)
148
- *
149
- * The effective initial amplitude accounts for both displacement and velocity,
150
- * since kinetic energy can convert to additional displacement:
151
- * A₀ = |x₀| + |v₀| / ωₙ
152
- *
153
- * Settling time: t = ln(A₀ / threshold) / σ, with a 2× safety factor
154
- * to account for polynomial terms in the critically damped solution.
155
- *
156
- * Returns the estimated time in milliseconds.
157
- */
158
- computeTimeRemaining(state) {
159
- const { position, velocity } = state;
160
- const a0 = Math.abs(position) + Math.abs(velocity) / this.naturalFrequency;
161
- if (a0 <= this.restingMagnitude) return 0;
162
- let sigma;
163
- if (this.dampingRatio < 1) sigma = this.dampingRatio * this.naturalFrequency;
164
- else if (this.dampingRatio === 1) sigma = this.naturalFrequency;
165
- else sigma = (this.dampingRatio - Math.sqrt(this.dampingRatio * this.dampingRatio - 1)) * this.naturalFrequency;
166
- if (sigma <= 0) return Infinity;
167
- return 2 * Math.log(a0 / this.restingMagnitude) / sigma * 1e3;
168
- }
169
- };
170
- function defineSpring(input) {
171
- return new SpringConfig(input);
172
- }
173
- //#endregion
174
- //#region src/emitter.ts
175
- var Emitter = class {
176
- #update = [];
177
- #start = [];
178
- #stop = [];
179
- on(type, handler) {
180
- this.#list(type).push(handler);
181
- return () => {
182
- this.off(type, handler);
183
- };
184
- }
185
- off(type, handler) {
186
- const list = this.#list(type);
187
- if (handler) list.splice(list.indexOf(handler) >>> 0, 1);
188
- else list.length = 0;
189
- }
190
- clear() {
191
- this.#update.length = 0;
192
- this.#start.length = 0;
193
- this.#stop.length = 0;
194
- }
195
- emit(type) {
196
- const list = this.#list(type);
197
- if (list.length === 1) list[0]();
198
- else if (list.length > 1) for (const handler of list.slice()) handler();
199
- }
200
- #list(type) {
201
- switch (type) {
202
- case "update": return this.#update;
203
- case "start": return this.#start;
204
- case "stop": return this.#stop;
205
- }
206
- }
207
- };
208
- //#endregion
209
- //#region src/state.ts
210
- var State = class {
211
- #config;
212
- #position;
213
- #velocity;
214
- constructor(config, position, velocity) {
215
- this.#config = config;
216
- this.#position = position;
217
- this.#velocity = velocity;
218
- }
219
- configure(config) {
220
- this.#config = config;
221
- }
222
- /** Returns the position rounded to the configured precision. */
223
- get position() {
224
- return Math.round(this.#position * this.#config.precisionMultiplier) / this.#config.precisionMultiplier;
225
- }
226
- set position(value) {
227
- this.#position = value;
228
- }
229
- /** Returns the velocity rounded to the configured precision. */
230
- get velocity() {
231
- return Math.round(this.#velocity * this.#config.precisionMultiplier) / this.#config.precisionMultiplier;
232
- }
233
- set velocity(value) {
234
- this.#velocity = value;
235
- }
236
- /** A spring is resting when its rounded position and velocity are both zero. */
237
- get isResting() {
238
- return this.position === 0 && this.velocity === 0;
239
- }
240
- };
241
- //#endregion
242
- //#region src/solver.ts
243
- var UnderdampedSolver = class {
244
- #state;
245
- #dampedFrequency = 0;
246
- #decayRate = 0;
247
- #t = 0;
248
- #c1 = 0;
249
- #c2 = 0;
250
- constructor(state) {
251
- this.#state = state;
252
- }
253
- configure(config) {
254
- if (config) {
255
- this.#decayRate = config.dampingRatio * config.naturalFrequency;
256
- this.#dampedFrequency = config.naturalFrequency * Math.sqrt(1 - config.dampingRatio ** 2);
257
- }
258
- this.#t = 0;
259
- this.#c1 = this.#state.position;
260
- this.#c2 = (this.#state.velocity + this.#decayRate * this.#state.position) / this.#dampedFrequency;
261
- }
262
- tick(dt) {
263
- this.#t += dt;
264
- const sin = Math.sin(this.#dampedFrequency * this.#t);
265
- const cos = Math.cos(this.#dampedFrequency * this.#t);
266
- const decay = Math.exp(-this.#decayRate * this.#t);
267
- const decayVelocity = -this.#decayRate * decay;
268
- const oscillation = this.#c1 * cos + this.#c2 * sin;
269
- const oscillationVelocity = -this.#c1 * this.#dampedFrequency * sin + this.#c2 * this.#dampedFrequency * cos;
270
- this.#state.position = decay * oscillation;
271
- this.#state.velocity = decay * oscillationVelocity + decayVelocity * oscillation;
272
- }
273
- };
274
- var CriticallyDampedSolver = class {
275
- #state;
276
- #naturalFrequency = 0;
277
- #t = 0;
278
- #c1 = 0;
279
- #c2 = 0;
280
- constructor(state) {
281
- this.#state = state;
282
- }
283
- configure(config) {
284
- if (config) this.#naturalFrequency = config.naturalFrequency;
285
- this.#t = 0;
286
- this.#c1 = this.#state.position;
287
- this.#c2 = this.#state.velocity + this.#naturalFrequency * this.#state.position;
288
- }
289
- tick(dt) {
290
- this.#t += dt;
291
- const decay = Math.exp(-this.#naturalFrequency * this.#t);
292
- const decayVelocity = -this.#naturalFrequency * decay;
293
- const scale = this.#c1 + this.#c2 * this.#t;
294
- const scaleVelocity = this.#c2;
295
- this.#state.position = scale * decay;
296
- this.#state.velocity = scaleVelocity * decay + decayVelocity * scale;
297
- }
298
- };
299
- var OverdampedSolver = class {
300
- #state;
301
- #dampedFrequency = 0;
302
- #decayRate = 0;
303
- #t = 0;
304
- #c1 = 0;
305
- #c2 = 0;
306
- constructor(state) {
307
- this.#state = state;
308
- }
309
- configure(config) {
310
- if (config) {
311
- this.#decayRate = config.dampingRatio * config.naturalFrequency;
312
- this.#dampedFrequency = config.naturalFrequency * Math.sqrt(config.dampingRatio ** 2 - 1);
313
- }
314
- this.#t = 0;
315
- this.#c1 = this.#state.velocity + this.#decayRate * this.#state.position;
316
- this.#c2 = this.#state.position * this.#dampedFrequency;
317
- }
318
- tick(dt) {
319
- this.#t += dt;
320
- const decay = Math.exp(-this.#decayRate * this.#t);
321
- const decayVelocity = -this.#decayRate * decay;
322
- const clamped = Math.min(this.#dampedFrequency * this.#t, 300);
323
- const sinh = Math.sinh(clamped);
324
- const cosh = Math.cosh(clamped);
325
- const scale = this.#c1 * sinh + this.#c2 * cosh;
326
- const scaleVelocity = this.#c1 * this.#dampedFrequency * cosh + this.#c2 * this.#dampedFrequency * sinh;
327
- this.#state.position = scale * decay / this.#dampedFrequency;
328
- this.#state.velocity = (scale * decayVelocity + scaleVelocity * decay) / this.#dampedFrequency;
329
- }
330
- };
331
- //#endregion
332
- //#region src/motion.ts
333
- var Motion = class {
334
- #config;
335
- #state;
336
- #underdampedSolver = null;
337
- #criticallyDampedSolver = null;
338
- #overdampedSolver = null;
339
- #currentSolver = null;
340
- #needsUpdate = false;
341
- #needsReset = false;
342
- #configVersion = 0;
343
- #timeRemaining = 0;
344
- #emitter;
345
- constructor(config, position, velocity) {
346
- this.#config = config;
347
- this.#state = new State(config, position, velocity);
348
- this.#emitter = new Emitter();
349
- this.#updateSolver();
350
- this.#timeRemaining = this.#config.computeTimeRemaining(this.#state);
351
- }
352
- get position() {
353
- return this.#state.position;
354
- }
355
- set position(value) {
356
- this.#state.position = value;
357
- this.#needsReset = true;
358
- if (!this.#state.isResting) this.#emitter.emit("start");
359
- }
360
- get velocity() {
361
- return this.#state.velocity;
362
- }
363
- set velocity(value) {
364
- this.#state.velocity = value;
365
- this.#needsReset = true;
366
- }
367
- get timeRemaining() {
368
- return this.#timeRemaining;
369
- }
370
- get isResting() {
371
- return this.#state.isResting;
372
- }
373
- configure(config) {
374
- this.#config = config;
375
- this.#state.configure(config);
376
- this.#configVersion = config._version;
377
- this.#needsUpdate = true;
378
- }
379
- tick(dt, emit = true) {
380
- invariant(this.#currentSolver, "Cannot tick a disposed motion");
381
- if (this.#config._version !== this.#configVersion) {
382
- this.#state.configure(this.#config);
383
- this.#configVersion = this.#config._version;
384
- this.#needsUpdate = true;
385
- }
386
- const needsTimeRemaining = this.#needsUpdate || this.#needsReset;
387
- if (this.#needsUpdate) {
388
- this.#updateSolver();
389
- this.#needsUpdate = false;
390
- this.#needsReset = false;
391
- } else if (this.#needsReset) {
392
- this.#currentSolver.configure();
393
- this.#needsReset = false;
394
- }
395
- if (needsTimeRemaining) this.#timeRemaining = this.#config.computeTimeRemaining(this.#state);
396
- this.#currentSolver.tick(dt);
397
- this.#timeRemaining = Math.max(0, this.#timeRemaining - dt * 1e3);
398
- if (emit) {
399
- this.#emitter.emit("update");
400
- if (this.#state.isResting) {
401
- this.#timeRemaining = 0;
402
- this.#emitter.emit("stop");
403
- }
404
- }
405
- }
406
- onUpdate(callback) {
407
- return this.#emitter.on("update", callback);
408
- }
409
- onStart(callback) {
410
- return this.#emitter.on("start", callback);
411
- }
412
- onStop(callback) {
413
- return this.#emitter.on("stop", callback);
414
- }
415
- dispose() {
416
- this.#emitter.clear();
417
- this.#underdampedSolver = null;
418
- this.#criticallyDampedSolver = null;
419
- this.#overdampedSolver = null;
420
- this.#currentSolver = null;
421
- }
422
- #updateSolver() {
423
- if (this.#config.dampingRatio < 1) {
424
- this.#underdampedSolver ||= new UnderdampedSolver(this.#state);
425
- this.#currentSolver = this.#underdampedSolver;
426
- } else if (this.#config.dampingRatio === 1) {
427
- this.#criticallyDampedSolver ||= new CriticallyDampedSolver(this.#state);
428
- this.#currentSolver = this.#criticallyDampedSolver;
429
- } else {
430
- this.#overdampedSolver ||= new OverdampedSolver(this.#state);
431
- this.#currentSolver = this.#overdampedSolver;
432
- }
433
- this.#currentSolver.configure(this.#config);
434
- }
435
- };
436
- //#endregion
437
- //#region src/spring.ts
438
- function normalizeTarget(target) {
439
- if (target instanceof Spring) return {
440
- spring: target,
441
- offset: 0
442
- };
443
- if (typeof target === "object" && target !== null && "spring" in target && target.spring instanceof Spring) return {
444
- spring: target.spring,
445
- offset: target.offset ?? 0
446
- };
447
- return null;
448
- }
449
- var Spring = class {
450
- #target;
451
- #config;
452
- #motion;
453
- #motions;
454
- #leader = null;
455
- #offset = 0;
456
- #ownsConfig;
457
- #unsubLeader = null;
458
- constructor(motions, position, config) {
459
- this.#motions = motions;
460
- let numericTarget;
461
- let value;
462
- let normalized = null;
463
- if (typeof position === "number") {
464
- numericTarget = position;
465
- value = position;
466
- } else {
467
- normalized = normalizeTarget(position.target);
468
- if (normalized) {
469
- numericTarget = normalized.spring.#target + normalized.spring.#motion.position + normalized.offset;
470
- value = position.value ?? numericTarget;
471
- } else {
472
- numericTarget = position.target ?? position.value ?? 0;
473
- value = position.value ?? numericTarget;
474
- }
475
- }
476
- const hasOverride = config !== void 0;
477
- this.#ownsConfig = hasOverride || !normalized;
478
- if (normalized && !hasOverride) this.#config = normalized.spring.#config;
479
- else this.#config = config ?? SpringConfig.default;
480
- this.#target = numericTarget;
481
- this.#motion = new Motion(this.#config, value - numericTarget, 0);
482
- if (!this.#motion.isResting) this.#motions.add(this.#motion);
483
- if (normalized) {
484
- this.#leader = normalized.spring;
485
- this.#offset = normalized.offset;
486
- this.#unsubLeader = normalized.spring.onUpdate(() => {
487
- this.#setTarget(this.#leader.#target + this.#leader.#motion.position + this.#offset);
488
- });
489
- }
490
- }
491
- get target() {
492
- return this.#target;
493
- }
494
- set target(value) {
495
- if (typeof value === "number") {
496
- this.#unfollow();
497
- this.#setTarget(value);
498
- } else {
499
- const normalized = normalizeTarget(value);
500
- this.#follow(normalized.spring, normalized.offset);
501
- }
502
- }
503
- get value() {
504
- return this.#target + this.#motion.position;
505
- }
506
- set value(value) {
507
- const position = value - this.#target;
508
- if (position !== this.#motion.position) {
509
- this.#motions.add(this.#motion);
510
- this.#motion.position = position;
511
- this.#motion.tick(0);
512
- }
513
- }
514
- get velocity() {
515
- return this.#motion.velocity;
516
- }
517
- set velocity(value) {
518
- this.#motions.add(this.#motion);
519
- this.#motion.velocity = value;
520
- }
521
- get config() {
522
- return this.#config;
523
- }
524
- get mass() {
525
- return this.#config.mass;
526
- }
527
- get tension() {
528
- return this.#config.tension;
529
- }
530
- get damping() {
531
- return this.#config.damping;
532
- }
533
- get dampingRatio() {
534
- return this.#config.dampingRatio;
535
- }
536
- get precision() {
537
- return this.#config.precision;
538
- }
539
- set config(value) {
540
- if (value) {
541
- if (this.#leader && !this.#ownsConfig) {
542
- this.#ownsConfig = true;
543
- this.#config = new SpringConfig({
544
- tension: value.tension,
545
- damping: value.damping,
546
- mass: value.mass,
547
- precision: value.precision
548
- });
549
- this.#motion.configure(this.#config);
550
- } else this.#config.assign(value);
551
- this.#motions.add(this.#motion);
552
- } else if (this.#ownsConfig) {
553
- this.#ownsConfig = false;
554
- this.#config = this.#leader ? this.#leader.#config : SpringConfig.default;
555
- this.#motion.configure(this.#config);
556
- this.#motions.add(this.#motion);
557
- }
558
- }
559
- get timeRemaining() {
560
- return this.#motion.timeRemaining;
561
- }
562
- get isResting() {
563
- return this.#motion.isResting;
564
- }
565
- jumpTo(value) {
566
- this.#target = value;
567
- this.#motion.position = 0;
568
- this.#motion.velocity = 0;
569
- this.#motion.tick(0);
570
- }
571
- dispose() {
572
- if (this.#unsubLeader) {
573
- this.#unsubLeader();
574
- this.#unsubLeader = null;
575
- }
576
- this.#motions.remove(this.#motion);
577
- this.#motion.dispose();
578
- }
579
- onUpdate(callback) {
580
- return this.#motion.onUpdate(callback);
581
- }
582
- onStart(callback) {
583
- return this.#motion.onStart(callback);
584
- }
585
- onStop(callback) {
586
- return this.#motion.onStop(callback);
587
- }
588
- #setTarget(value) {
589
- if (value !== this.#target) {
590
- this.#motions.add(this.#motion);
591
- const rawValue = this.#target + this.#motion.position;
592
- this.#target = value;
593
- this.#motion.position = rawValue - this.#target;
594
- this.#motion.tick(0);
595
- }
596
- }
597
- #follow(leader, offset) {
598
- if (this.#unsubLeader) this.#unsubLeader();
599
- this.#leader = leader;
600
- this.#offset = offset;
601
- if (!this.#ownsConfig) {
602
- this.#config = leader.#config;
603
- this.#motion.configure(this.#config);
604
- }
605
- this.#unsubLeader = leader.onUpdate(() => {
606
- this.#setTarget(this.#leader.#target + this.#leader.#motion.position + this.#offset);
607
- });
608
- this.#setTarget(leader.#target + leader.#motion.position + offset);
609
- }
610
- #unfollow() {
611
- if (!this.#leader) return;
612
- this.#unsubLeader();
613
- this.#unsubLeader = null;
614
- this.#leader = null;
615
- this.#offset = 0;
616
- if (!this.#ownsConfig) {
617
- this.#ownsConfig = true;
618
- this.#config = new SpringConfig({
619
- tension: this.#config.tension,
620
- damping: this.#config.damping,
621
- mass: this.#config.mass,
622
- precision: this.#config.precision
623
- });
624
- this.#motion.configure(this.#config);
625
- }
626
- }
627
- };
628
- //#endregion
629
- //#region src/vector2.ts
630
- function isVector2(value) {
631
- return typeof value === "object" && value !== null && "x" in value && typeof value.x === "number";
632
- }
633
- //#endregion
634
- //#region src/spring2d.ts
635
- function normalizeTarget2D(target) {
636
- if (target instanceof Spring2D) return {
637
- spring: target,
638
- offset: void 0
639
- };
640
- if (typeof target === "object" && target !== null && "spring" in target && target.spring instanceof Spring2D) return {
641
- spring: target.spring,
642
- offset: target.offset
643
- };
644
- return null;
645
- }
646
- function toScalarTarget(spring, offset) {
647
- return offset ? {
648
- spring,
649
- offset
650
- } : spring;
651
- }
652
- function toScalarPos(target, value) {
653
- if (typeof target === "number") return value !== void 0 ? {
654
- target,
655
- value
656
- } : target;
657
- return {
658
- target,
659
- value
660
- };
661
- }
662
- var Spring2D = class Spring2D {
663
- #x;
664
- #y;
665
- #value = {
666
- x: 0,
667
- y: 0
668
- };
669
- #velocity = {
670
- x: 0,
671
- y: 0
672
- };
673
- constructor(motions, position, config) {
674
- let xPos;
675
- let yPos;
676
- if (isVector2(position)) {
677
- xPos = position.x;
678
- yPos = position.y;
679
- } else {
680
- const normalized = normalizeTarget2D(position.target);
681
- if (normalized) {
682
- const lx = normalized.spring.#x;
683
- const ly = normalized.spring.#y;
684
- const xTarget = toScalarTarget(lx, normalized.offset?.x);
685
- const yTarget = toScalarTarget(ly, normalized.offset?.y);
686
- xPos = toScalarPos(xTarget, position.value?.x);
687
- yPos = toScalarPos(yTarget, position.value?.y);
688
- } else if (isVector2(position.target)) {
689
- const t = position.target;
690
- xPos = {
691
- target: t.x,
692
- value: position.value?.x
693
- };
694
- yPos = {
695
- target: t.y,
696
- value: position.value?.y
697
- };
698
- } else {
699
- xPos = {
700
- target: position.target,
701
- value: position.value?.x
702
- };
703
- yPos = {
704
- target: void 0,
705
- value: position.value?.y
706
- };
707
- }
708
- }
709
- this.#x = new Spring(motions, xPos, config);
710
- this.#y = new Spring(motions, yPos, config);
711
- }
712
- get target() {
713
- return {
714
- x: this.#x.target,
715
- y: this.#y.target
716
- };
717
- }
718
- set target(value) {
719
- if (value instanceof Spring2D) {
720
- this.#x.target = value.#x;
721
- this.#y.target = value.#y;
722
- } else if ("spring" in value && value.spring instanceof Spring2D) {
723
- const { spring, offset } = value;
724
- this.#x.target = toScalarTarget(spring.#x, offset?.x);
725
- this.#y.target = toScalarTarget(spring.#y, offset?.y);
726
- } else {
727
- const v = value;
728
- this.#x.target = v.x;
729
- this.#y.target = v.y;
730
- }
731
- }
732
- get value() {
733
- this.#value.x = this.#x.value;
734
- this.#value.y = this.#y.value;
735
- return this.#value;
736
- }
737
- set value(value) {
738
- this.#x.value = value.x;
739
- this.#y.value = value.y;
740
- }
741
- get velocity() {
742
- this.#velocity.x = this.#x.velocity;
743
- this.#velocity.y = this.#y.velocity;
744
- return this.#velocity;
745
- }
746
- set velocity(value) {
747
- this.#x.velocity = value.x;
748
- this.#y.velocity = value.y;
749
- }
750
- get config() {
751
- return this.#x.config;
752
- }
753
- get mass() {
754
- return this.#x.mass;
755
- }
756
- get tension() {
757
- return this.#x.tension;
758
- }
759
- get damping() {
760
- return this.#x.damping;
761
- }
762
- get dampingRatio() {
763
- return this.#x.dampingRatio;
764
- }
765
- get precision() {
766
- return this.#x.precision;
767
- }
768
- set config(value) {
769
- this.#x.config = value;
770
- this.#y.config = value;
771
- }
772
- get timeRemaining() {
773
- return Math.max(this.#x.timeRemaining, this.#y.timeRemaining);
774
- }
775
- get isResting() {
776
- return this.#x.isResting && this.#y.isResting;
777
- }
778
- jumpTo(value) {
779
- this.#x.jumpTo(value.x);
780
- this.#y.jumpTo(value.y);
781
- }
782
- dispose() {
783
- this.#x.dispose();
784
- this.#y.dispose();
785
- }
786
- onUpdate(callback) {
787
- const a = this.#x.onUpdate(callback);
788
- const b = this.#y.onUpdate(callback);
789
- return () => {
790
- a();
791
- b();
792
- };
793
- }
794
- onStart(callback) {
795
- const a = this.#x.onStart(callback);
796
- const b = this.#y.onStart(callback);
797
- return () => {
798
- a();
799
- b();
800
- };
801
- }
802
- onStop(callback) {
803
- const check = () => {
804
- if (this.isResting) callback();
805
- };
806
- const a = this.#x.onStop(check);
807
- const b = this.#y.onStop(check);
808
- return () => {
809
- a();
810
- b();
811
- };
812
- }
813
- };
814
- //#endregion
815
- //#region src/ticker.ts
816
- const hasWindow = typeof window !== "undefined";
817
- const request = hasWindow ? (cb) => window.requestAnimationFrame(cb) : (cb) => setTimeout(cb, 1);
818
- const cancel = hasWindow ? (id) => window.cancelAnimationFrame(id) : (id) => clearTimeout(id);
819
- var Ticker = class {
820
- #fps;
821
- #gap;
822
- #lagThreshold;
823
- #adjustedLag;
824
- #motion;
825
- #frame = 0;
826
- #time = 0;
827
- #previousTime = 0;
828
- #delta = 0;
829
- #nextTime;
830
- #lastWallTime = 0;
831
- #id = 0;
832
- #stopped = true;
833
- constructor(motions, options) {
834
- this.#motion = motions;
835
- const fps = options?.fps ?? 60;
836
- const lagThreshold = options?.lagThreshold ?? 500;
837
- const adjustedLag = options?.adjustedLag ?? 33;
838
- invariant(fps > 0, "FPS must be greater than 0");
839
- invariant(lagThreshold >= 0, "Lag threshold must be greater than or equal to 0");
840
- invariant(adjustedLag >= 0, "Adjusted lag must be greater than or equal to 0");
841
- this.#fps = fps;
842
- this.#gap = 1e3 / fps;
843
- this.#lagThreshold = lagThreshold === 0 ? 1e8 : lagThreshold;
844
- this.#adjustedLag = Math.min(adjustedLag, this.#lagThreshold);
845
- this.#nextTime = this.#gap;
846
- }
847
- get fps() {
848
- return this.#fps;
849
- }
850
- set fps(value) {
851
- invariant(value > 0, "FPS must be greater than 0");
852
- this.#fps = value;
853
- this.#gap = 1e3 / value;
854
- this.#nextTime = this.#previousTime + this.#gap;
855
- }
856
- get lagThreshold() {
857
- return this.#lagThreshold;
858
- }
859
- set lagThreshold(value) {
860
- invariant(value >= 0, "Lag threshold must be greater than or equal to 0");
861
- this.#lagThreshold = value === 0 ? 1e8 : value;
862
- this.#adjustedLag = Math.min(this.#adjustedLag, this.#lagThreshold);
863
- }
864
- get adjustedLag() {
865
- return this.#adjustedLag;
866
- }
867
- set adjustedLag(value) {
868
- invariant(value >= 0, "Adjusted lag must be greater than or equal to 0");
869
- this.#adjustedLag = Math.min(value, this.#lagThreshold);
870
- }
871
- get time() {
872
- return this.#time;
873
- }
874
- get frame() {
875
- return this.#frame;
876
- }
877
- get delta() {
878
- return this.#delta;
879
- }
880
- get deltaRatio() {
881
- return this.#delta / this.#gap;
882
- }
883
- get stopped() {
884
- return this.#stopped;
885
- }
886
- start() {
887
- if (this.#stopped) {
888
- this.#stopped = false;
889
- this.#lastWallTime = performance.now();
890
- this.#id = request((t) => this.#onFrame(t));
891
- }
892
- }
893
- stop() {
894
- if (!this.#stopped) {
895
- cancel(this.#id);
896
- this.#stopped = true;
897
- }
898
- }
899
- /** Manually advance one frame. */
900
- tick() {
901
- this.#step(this.#gap);
902
- }
903
- #onFrame(timestamp) {
904
- if (this.#stopped) return;
905
- const wallElapsed = timestamp - this.#lastWallTime;
906
- this.#lastWallTime = timestamp;
907
- const elapsed = wallElapsed > this.#lagThreshold ? this.#adjustedLag : wallElapsed;
908
- this.#time += elapsed;
909
- if (this.#time - this.#nextTime >= 0) {
910
- this.#frame++;
911
- this.#delta = this.#time - this.#previousTime;
912
- this.#previousTime = this.#time;
913
- this.#nextTime += this.#gap;
914
- this.#motion.tick(this.#delta / 1e3);
915
- }
916
- this.#id = request((t) => this.#onFrame(t));
917
- }
918
- #step(dt) {
919
- this.#time += dt;
920
- this.#frame++;
921
- this.#delta = dt;
922
- this.#previousTime = this.#time;
923
- this.#motion.tick(dt / 1e3);
924
- }
925
- };
926
- //#endregion
927
- //#region src/system.ts
928
- var SpringSystemImpl = class {
929
- #motion;
930
- #ticker;
931
- constructor(options) {
932
- this.#motion = new MotionSet(options?.debug);
933
- this.#ticker = new Ticker(this.#motion, options);
934
- }
935
- createSpring(position, config) {
936
- return new Spring(this.#motion, position, config);
937
- }
938
- createSpring2D(position, config) {
939
- return new Spring2D(this.#motion, position, config);
940
- }
941
- advance(dt) {
942
- this.#motion.tick(dt / 1e3);
943
- }
944
- start() {
945
- this.#ticker.start();
946
- }
947
- stop() {
948
- this.#ticker.stop();
949
- }
950
- get running() {
951
- return !this.#ticker.stopped;
952
- }
953
- get fps() {
954
- return this.#ticker.fps;
955
- }
956
- set fps(value) {
957
- this.#ticker.fps = value;
958
- }
959
- get lagThreshold() {
960
- return this.#ticker.lagThreshold;
961
- }
962
- set lagThreshold(value) {
963
- this.#ticker.lagThreshold = value;
964
- }
965
- get adjustedLag() {
966
- return this.#ticker.adjustedLag;
967
- }
968
- set adjustedLag(value) {
969
- this.#ticker.adjustedLag = value;
970
- }
971
- };
972
- function createSpringSystem(options) {
973
- return new SpringSystemImpl(options);
974
- }
975
- //#endregion
976
- export { SpringConfig as n, defineSpring as r, createSpringSystem as t };