coily 0.14.0 → 0.15.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/css.js ADDED
@@ -0,0 +1,294 @@
1
+ import { i as State, n as OverdampedSolver, o as invariant, r as UnderdampedSolver, t as CriticallyDampedSolver } from "./solver-CVXOTDkZ.js";
2
+ //#region src/css.ts
3
+ function pickSolver(config, state) {
4
+ const solver = config.dampingRatio < 1 ? new UnderdampedSolver(state) : config.dampingRatio === 1 ? new CriticallyDampedSolver(state) : new OverdampedSolver(state);
5
+ solver.configure(config);
6
+ return solver;
7
+ }
8
+ function walk(config, displacement, spanSec, scale, stopAtRest, velocity = 0) {
9
+ const state = new State(config, displacement, velocity);
10
+ const solver = pickSolver(config, state);
11
+ const period = 2 * Math.PI / config.naturalFrequency;
12
+ const dt = Math.max(1e-4, Math.min(period / 48, spanSec / 160));
13
+ const points = [{
14
+ x: 0,
15
+ y: 0
16
+ }];
17
+ let t = 0;
18
+ while (spanSec - t > 1e-9) {
19
+ const step = Math.min(dt, spanSec - t);
20
+ solver.tick(step);
21
+ t += step;
22
+ points.push({
23
+ x: t / spanSec,
24
+ y: (1 - state.position / displacement) * scale
25
+ });
26
+ if (stopAtRest && state.isResting) break;
27
+ }
28
+ return points;
29
+ }
30
+ function buildEasing(points, maxError) {
31
+ const simplified = simplify(points, maxError);
32
+ return {
33
+ easing: format(simplified),
34
+ stops: simplified.length,
35
+ maxError: reconstructionError(points, simplified)
36
+ };
37
+ }
38
+ function buildSettle(config, displacement, durationMs, maxError, velocity = 0) {
39
+ const points = walk(config, displacement, durationMs / 1e3, 1, true, velocity);
40
+ const last = points[points.length - 1];
41
+ if (1 - last.x > 1e-6) points.push({
42
+ x: 1,
43
+ y: 1
44
+ });
45
+ else {
46
+ last.x = 1;
47
+ last.y = 1;
48
+ }
49
+ return {
50
+ mode: "settle",
51
+ duration: durationMs,
52
+ ...buildEasing(points, maxError)
53
+ };
54
+ }
55
+ function buildLoop(config, displacement, maxError) {
56
+ const halfSec = Math.PI / config.naturalFrequency;
57
+ const points = walk(config, displacement, halfSec, .5, false);
58
+ const last = points[points.length - 1];
59
+ last.x = 1;
60
+ last.y = 1;
61
+ return {
62
+ mode: "loop",
63
+ duration: halfSec * 1e3,
64
+ direction: "alternate",
65
+ iterations: "infinite",
66
+ ...buildEasing(points, maxError)
67
+ };
68
+ }
69
+ function simplify(points, epsilon) {
70
+ const keep = Array.from({ length: points.length }, () => false);
71
+ keep[0] = true;
72
+ keep[points.length - 1] = true;
73
+ const stack = [[0, points.length - 1]];
74
+ while (stack.length > 0) {
75
+ const [lo, hi] = stack.pop();
76
+ const a = points[lo];
77
+ const b = points[hi];
78
+ const dx = b.x - a.x;
79
+ let worst = -1;
80
+ let worstIndex = -1;
81
+ for (let i = lo + 1; i < hi; i += 1) {
82
+ const p = points[i];
83
+ const f = dx === 0 ? 0 : (p.x - a.x) / dx;
84
+ const interpolated = a.y + f * (b.y - a.y);
85
+ const distance = Math.abs(p.y - interpolated);
86
+ if (distance > worst) {
87
+ worst = distance;
88
+ worstIndex = i;
89
+ }
90
+ }
91
+ if (worst > epsilon && worstIndex !== -1) {
92
+ keep[worstIndex] = true;
93
+ stack.push([lo, worstIndex], [worstIndex, hi]);
94
+ }
95
+ }
96
+ return points.filter((_, i) => keep[i]);
97
+ }
98
+ function trim(value, places) {
99
+ return Number(value.toFixed(places)).toString();
100
+ }
101
+ function format(points) {
102
+ return `linear(${points.map((point, i) => {
103
+ const output = trim(point.y, 5);
104
+ if (i === 0 || i === points.length - 1) return output;
105
+ return `${output} ${trim(point.x * 100, 2)}%`;
106
+ }).join(", ")})`;
107
+ }
108
+ function reconstructionError(dense, simplified) {
109
+ let worst = 0;
110
+ let j = 0;
111
+ for (const point of dense) {
112
+ while (j < simplified.length - 2 && simplified[j + 1].x < point.x) j += 1;
113
+ const a = simplified[j];
114
+ const b = simplified[j + 1];
115
+ const dx = b.x - a.x;
116
+ const f = dx === 0 ? 0 : (point.x - a.x) / dx;
117
+ worst = Math.max(worst, Math.abs(point.y - (a.y + f * (b.y - a.y))));
118
+ }
119
+ return worst;
120
+ }
121
+ /**
122
+ * Turns a resolved spring config into a CSS `linear()` easing and the
123
+ * duration in ms to pair it with. A spring that settles returns a
124
+ * `'settle'` easing. A pure undamped config (`dampingRatio: 0` with
125
+ * passthrough `arrival`) has no friction and never settles, so it
126
+ * returns a `'loop'` easing for an infinite alternating animation.
127
+ */
128
+ function springToLinear(config, options = {}) {
129
+ const displacement = options.displacement ?? 1;
130
+ const maxError = options.maxError ?? .0025;
131
+ const maxDuration = options.maxDuration ?? 1e4;
132
+ if (config.dampingRatio === 0 && config.arrival === 1) return buildLoop(config, displacement, maxError);
133
+ const settleTime = config.computeTimeRemaining({
134
+ position: displacement,
135
+ velocity: 0
136
+ });
137
+ return buildSettle(config, displacement, Math.min(settleTime, maxDuration), maxError);
138
+ }
139
+ /**
140
+ * Like `springToLinear`, but from a spring caught mid-flight rather than at
141
+ * rest: `state` carries both the displacement from the target (`position`)
142
+ * and the current `velocity`, so the easing continues the motion smoothly
143
+ * instead of restarting. Use it to regenerate an animation when its target
144
+ * changes — the interruption carries momentum instead of stalling. The
145
+ * from-rest shape trick doesn't apply here (the curve now depends on the
146
+ * velocity), so this is computed per interruption, not cached per config.
147
+ *
148
+ * Always a settling easing; `position` must be nonzero.
149
+ */
150
+ function springFromState(config, state, options = {}) {
151
+ const maxError = options.maxError ?? .0025;
152
+ const maxDuration = options.maxDuration ?? 1e4;
153
+ invariant(Math.abs(state.position) > 1e-9, "springFromState needs a nonzero position (the value and target must differ)");
154
+ const settleTime = config.computeTimeRemaining(state);
155
+ return buildSettle(config, state.position, Math.min(settleTime, maxDuration), maxError, state.velocity);
156
+ }
157
+ /**
158
+ * Evolves a spring `state` forward by `elapsedMs`, returning the new
159
+ * displacement and velocity. Pair it with a running `element.animate` — read
160
+ * the animation's `currentTime`, evolve the state it started from, and you
161
+ * have the exact live velocity the Web Animations API never exposes, ready
162
+ * to hand to `springFromState` on interruption.
163
+ */
164
+ function springStateAt(config, state, elapsedMs) {
165
+ if (elapsedMs <= 0) return {
166
+ position: state.position,
167
+ velocity: state.velocity
168
+ };
169
+ const live = new State(config, state.position, state.velocity);
170
+ pickSolver(config, live).tick(elapsedMs / 1e3);
171
+ return {
172
+ position: live.position,
173
+ velocity: live.velocity
174
+ };
175
+ }
176
+ function resolveSpecs(config, specs, options) {
177
+ const withVelocity = specs.some((spec) => (spec.velocity ?? 0) !== 0);
178
+ invariant(!withVelocity || specs.length === 1, "velocity handoff is single-property: a from-state easing depends on that property, so run one animation per property");
179
+ const first = specs[0];
180
+ const easing = withVelocity ? springFromState(config, {
181
+ position: first.from - first.to,
182
+ velocity: first.velocity
183
+ }, options) : springToLinear(config, {
184
+ ...options,
185
+ displacement: options.displacement ?? (Math.max(...specs.map((s) => Math.abs(s.to - s.from))) || 1)
186
+ });
187
+ const from = {};
188
+ const to = {};
189
+ for (const spec of specs) {
190
+ const format = spec.format ?? ((value) => `${Number(value.toFixed(3))}${spec.unit ?? ""}`);
191
+ const far = easing.mode === "loop" ? 2 * spec.to - spec.from : spec.to;
192
+ const start = format(spec.from);
193
+ const end = format(far);
194
+ from[spec.property] = from[spec.property] ? `${from[spec.property]} ${start}` : start;
195
+ to[spec.property] = to[spec.property] ? `${to[spec.property]} ${end}` : end;
196
+ }
197
+ return {
198
+ easing,
199
+ from,
200
+ to
201
+ };
202
+ }
203
+ function asArray(spec) {
204
+ return Array.isArray(spec) ? spec : [spec];
205
+ }
206
+ /**
207
+ * Builds arguments for `element.animate(keyframes, options)`: two keyframes
208
+ * carrying the endpoint values, and options carrying the spring's `linear()`
209
+ * easing and duration. Pass an array of specs to drive several properties
210
+ * from one spring. A settling spring fills forwards; an undamped one repeats
211
+ * infinitely in alternating directions.
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * const { keyframes, options } = springToWaapi(config, {
216
+ * property: 'translate',
217
+ * from: 0,
218
+ * to: 300,
219
+ * unit: 'px',
220
+ * })
221
+ * element.animate(keyframes, options)
222
+ * ```
223
+ */
224
+ function springToWaapi(config, spec, options = {}) {
225
+ const { easing, from, to } = resolveSpecs(config, asArray(spec), options);
226
+ const keyframes = [from, to];
227
+ const shared = {
228
+ duration: easing.duration,
229
+ easing: easing.easing
230
+ };
231
+ return {
232
+ keyframes,
233
+ options: easing.mode === "loop" ? {
234
+ ...shared,
235
+ iterations: Number.POSITIVE_INFINITY,
236
+ direction: "alternate"
237
+ } : {
238
+ ...shared,
239
+ fill: "forwards"
240
+ }
241
+ };
242
+ }
243
+ /**
244
+ * Builds CSS text for the animation: a `@keyframes` rule and an `animation`
245
+ * shorthand value, driving one property or several from one spring. The
246
+ * `name` option (default `'coily'`) names the `@keyframes` rule and is
247
+ * referenced by the `animation` value.
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * const { keyframes, animation } = springToCss(config, {
252
+ * property: 'translate',
253
+ * from: 0,
254
+ * to: 300,
255
+ * unit: 'px',
256
+ * })
257
+ * const style = document.createElement('style')
258
+ * style.textContent = keyframes
259
+ * document.head.append(style)
260
+ * element.style.animation = animation
261
+ * ```
262
+ */
263
+ function springToCss(config, spec, options = {}) {
264
+ const { easing, from, to } = resolveSpecs(config, asArray(spec), options);
265
+ const name = options.name ?? "coily";
266
+ const ms = `${Number(easing.duration.toFixed(1))}ms`;
267
+ const declare = (values) => Object.entries(values).map(([property, value]) => `${property}: ${value};`).join(" ");
268
+ return {
269
+ keyframes: `@keyframes ${name} {\n from { ${declare(from)} }\n to { ${declare(to)} }\n}`,
270
+ animation: easing.mode === "loop" ? `${ms} ${easing.easing} infinite alternate ${name}` : `${ms} ${easing.easing} forwards ${name}`
271
+ };
272
+ }
273
+ /**
274
+ * Builds a CSS `transition` value that springs a property change: set it on
275
+ * the element, then change the property (on `:hover`, a toggled class, a
276
+ * state attribute) to animate. Pass several specs for one `transition`
277
+ * across properties.
278
+ *
279
+ * Only settling springs — a transition can't loop. It also can't carry
280
+ * momentum: a change interrupted mid-flight restarts the spring from rest
281
+ * instead of reversing through its current velocity, so it stalls at the
282
+ * turn. For motion that's reversed often, drive a live `Spring` instead. The
283
+ * specs' `from`/`to` only tune the duration; the actual values live in your
284
+ * CSS rules.
285
+ */
286
+ function springToTransition(config, spec, options = {}) {
287
+ const specs = asArray(spec);
288
+ const { easing } = resolveSpecs(config, specs, options);
289
+ invariant(easing.mode === "settle", "springToTransition requires a settling spring; an undamped (looping) config has no single end state to transition to");
290
+ const ms = `${Number(easing.duration.toFixed(1))}ms`;
291
+ return specs.map((s) => `${s.property} ${ms} ${easing.easing}`).join(", ");
292
+ }
293
+ //#endregion
294
+ export { springFromState, springStateAt, springToCss, springToLinear, springToTransition, springToWaapi };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import { C as SpringDefinitionOptions, S as SpringDefinition, T as defineSpring, _ as accelerationOf, a as createSpringSystem, b as SpringSourceSymbol, c as ConfigShape, g as KinematicSource, h as SpringTarget, i as SpringSystemOptions, m as Spring, n as SpringOptions, o as CompositeSpring, p as Purpose, r as SpringSystem, s as CompositeSpringTarget, t as CompositeSpringOptions, u as PurposeShape, v as velocityOf, w as SpringState, x as mapSpring, y as SpringSource } from "./system-Ci-R4RaI.js";
1
+ import { i as defineSpring, n as SpringDefinitionOptions, r as SpringState, t as SpringDefinition } from "./config-OOMMRdK8.js";
2
+ import { _ as accelerationOf, a as createSpringSystem, b as SpringSourceSymbol, c as ConfigShape, g as KinematicSource, h as SpringTarget, i as SpringSystemOptions, m as Spring, n as SpringOptions, o as CompositeSpring, p as Purpose, r as SpringSystem, s as CompositeSpringTarget, t as CompositeSpringOptions, u as PurposeShape, v as velocityOf, x as mapSpring, y as SpringSource } from "./system-Du_fXXmO.js";
2
3
  export { type CompositeSpring, type CompositeSpringOptions, type CompositeSpringTarget, type ConfigShape, type KinematicSource, type Purpose, type PurposeShape, type Spring, type SpringDefinition, type SpringDefinitionOptions, type SpringOptions, type SpringSource, SpringSourceSymbol, type SpringState, type SpringSystem, type SpringSystemOptions, type SpringTarget, accelerationOf, createSpringSystem, defineSpring, mapSpring, velocityOf };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { i as mapSpring, n as SpringSourceSymbol, o as defineSpring, r as isSpringSource, s as invariant, t as createSpringSystem } from "./system-BXxAHM2a.js";
1
+ import { a as registerRecipe, i as mapSpring, n as SpringSourceSymbol, r as isSpringSource, s as defineSpring, t as createSpringSystem } from "./system-Br_cPnaN.js";
2
+ import { o as invariant } from "./solver-CVXOTDkZ.js";
2
3
  //#region src/kinematic-source.ts
3
4
  function isKinematicSource(value) {
4
5
  if (!isSpringSource(value)) return false;
@@ -32,13 +33,19 @@ const NOT_KINEMATIC = "expected a source in motion (a Spring or CompositeSpring)
32
33
  function velocityOf(source) {
33
34
  invariant(isKinematicSource(source), NOT_KINEMATIC);
34
35
  const api = source[SpringSourceSymbol];
35
- return Object.freeze({ [SpringSourceSymbol]: Object.freeze({
36
+ const velocity = Object.freeze({ [SpringSourceSymbol]: Object.freeze({
36
37
  get value() {
37
38
  return api.velocity;
38
39
  },
39
40
  onUpdate: (callback) => api.onUpdate(callback),
40
41
  onDispose: (callback) => api.onDispose(callback)
41
42
  }) });
43
+ registerRecipe(velocity, {
44
+ sources: [source],
45
+ read: () => api.velocity,
46
+ fns: []
47
+ });
48
+ return velocity;
42
49
  }
43
50
  /**
44
51
  * Derives a source from another's acceleration — the rate its velocity
@@ -66,13 +73,19 @@ function velocityOf(source) {
66
73
  function accelerationOf(source) {
67
74
  invariant(isKinematicSource(source), NOT_KINEMATIC);
68
75
  const api = source[SpringSourceSymbol];
69
- return Object.freeze({ [SpringSourceSymbol]: Object.freeze({
76
+ const acceleration = Object.freeze({ [SpringSourceSymbol]: Object.freeze({
70
77
  get value() {
71
78
  return api.acceleration;
72
79
  },
73
80
  onUpdate: (callback) => api.onUpdate(callback),
74
81
  onDispose: (callback) => api.onDispose(callback)
75
82
  }) });
83
+ registerRecipe(acceleration, {
84
+ sources: [source],
85
+ read: () => api.acceleration,
86
+ fns: []
87
+ });
88
+ return acceleration;
76
89
  }
77
90
  //#endregion
78
91
  export { SpringSourceSymbol, accelerationOf, createSpringSystem, defineSpring, mapSpring, velocityOf };
package/dist/nuxt.d.ts CHANGED
@@ -30,6 +30,14 @@ interface CoilyModuleOptions {
30
30
  * @default 33
31
31
  */
32
32
  adjustedLag?: number;
33
+ /**
34
+ * How far a spring may trail a source it follows within one frame, in
35
+ * the value's own units. Frames whose motion would exceed it split
36
+ * into internal sub-steps; smaller is tighter and costs more solver
37
+ * work.
38
+ * @default 0.1
39
+ */
40
+ couplingTolerance?: number;
33
41
  /**
34
42
  * When springs snap to their targets instead of animating: `'user'`
35
43
  * follows the OS prefers-reduced-motion setting, including live
package/dist/nuxt.js CHANGED
@@ -6,6 +6,7 @@ function getPluginTemplate(options) {
6
6
  if (options.fps !== void 0) systemOptions.fps = options.fps;
7
7
  if (options.lagThreshold !== void 0) systemOptions.lagThreshold = options.lagThreshold;
8
8
  if (options.adjustedLag !== void 0) systemOptions.adjustedLag = options.adjustedLag;
9
+ if (options.couplingTolerance !== void 0) systemOptions.couplingTolerance = options.couplingTolerance;
9
10
  if (options.reducedMotion) systemOptions.reducedMotion = options.reducedMotion;
10
11
  return `import { createSpringSystem } from 'coily'
11
12
  import { provideSpringSystem } from 'coily/vue'
@@ -0,0 +1,235 @@
1
+ //#region src/util.ts
2
+ function invariant(condition, message) {
3
+ if (condition) return;
4
+ const resolved = typeof message === "function" ? message() : message;
5
+ throw new Error(resolved ?? "Invariant Failed");
6
+ }
7
+ /**
8
+ * True for plain objects only. Class instances are excluded so shape
9
+ * walks reject values like a `Spring` instead of descending into their
10
+ * fields as if they were nested shapes.
11
+ */
12
+ function isRecord(value) {
13
+ if (typeof value !== "object" || value === null) return false;
14
+ const proto = Object.getPrototypeOf(value);
15
+ return proto === Object.prototype || proto === null;
16
+ }
17
+ function isRecordOrArray(value) {
18
+ return isRecord(value) || Array.isArray(value);
19
+ }
20
+ function isArray(value) {
21
+ return Array.isArray(value);
22
+ }
23
+ function isNumber(value) {
24
+ return typeof value === "number";
25
+ }
26
+ const RESOLVED = Promise.resolve();
27
+ //#endregion
28
+ //#region src/state.ts
29
+ /**
30
+ * The mutable position/velocity pair a motion and its solvers share, in
31
+ * displacement space: position 0 means at the target.
32
+ */
33
+ var State = class {
34
+ #config;
35
+ #position;
36
+ #velocity;
37
+ constructor(config, position, velocity) {
38
+ this.#config = config;
39
+ this.#position = position;
40
+ this.#velocity = velocity;
41
+ }
42
+ configure(config) {
43
+ this.#config = config;
44
+ }
45
+ get position() {
46
+ return this.#position;
47
+ }
48
+ set position(value) {
49
+ this.#position = value;
50
+ }
51
+ get velocity() {
52
+ return this.#velocity;
53
+ }
54
+ set velocity(value) {
55
+ this.#velocity = value;
56
+ }
57
+ /**
58
+ * The rest test: displacement, plus the displacement the current
59
+ * velocity could still convert into (|x| + |v| / wn), is inside the
60
+ * config's resting threshold.
61
+ */
62
+ get isResting() {
63
+ return Math.abs(this.#position) + Math.abs(this.#velocity) / this.#config.naturalFrequency <= this.#config.restingMagnitude;
64
+ }
65
+ };
66
+ //#endregion
67
+ //#region src/solver.ts
68
+ /** dampingRatio < 1: decaying oscillation, x(t) = exp(-sigma*t) * (c1*cos(wd*t) + c2*sin(wd*t)). */
69
+ var UnderdampedSolver = class {
70
+ #state;
71
+ #dampedFrequency = 0;
72
+ #decayRate = 0;
73
+ #arrival = 1;
74
+ #t = 0;
75
+ #c1 = 0;
76
+ #c2 = 0;
77
+ #crossing = Infinity;
78
+ constructor(state) {
79
+ this.#state = state;
80
+ }
81
+ configure(config) {
82
+ if (config) {
83
+ this.#decayRate = config.dampingRatio * config.naturalFrequency;
84
+ this.#dampedFrequency = config.naturalFrequency * Math.sqrt(1 - config.dampingRatio ** 2);
85
+ this.#arrival = config.arrival;
86
+ }
87
+ this.#t = 0;
88
+ this.#c1 = this.#state.position;
89
+ this.#c2 = (this.#state.velocity + this.#decayRate * this.#state.position) / this.#dampedFrequency;
90
+ if (this.#arrival === 1) this.#crossing = Infinity;
91
+ else {
92
+ let theta = Math.atan2(-this.#c1, this.#c2) % Math.PI;
93
+ if (theta <= 0) theta += Math.PI;
94
+ this.#crossing = theta / this.#dampedFrequency;
95
+ }
96
+ }
97
+ tick(dt) {
98
+ let t = this.#t + dt;
99
+ while (t >= this.#crossing) {
100
+ t -= this.#crossing;
101
+ this.#arrive();
102
+ if (this.#state.isResting) return;
103
+ }
104
+ this.#t = t;
105
+ this.#evaluate();
106
+ }
107
+ #arrive() {
108
+ this.#t = this.#crossing;
109
+ this.#evaluate();
110
+ this.#state.position = 0;
111
+ this.#state.velocity *= this.#arrival;
112
+ this.configure();
113
+ }
114
+ #evaluate() {
115
+ const sin = Math.sin(this.#dampedFrequency * this.#t);
116
+ const cos = Math.cos(this.#dampedFrequency * this.#t);
117
+ const decay = Math.exp(-this.#decayRate * this.#t);
118
+ const decayVelocity = -this.#decayRate * decay;
119
+ const oscillation = this.#c1 * cos + this.#c2 * sin;
120
+ const oscillationVelocity = -this.#c1 * this.#dampedFrequency * sin + this.#c2 * this.#dampedFrequency * cos;
121
+ this.#state.position = decay * oscillation;
122
+ this.#state.velocity = decay * oscillationVelocity + decayVelocity * oscillation;
123
+ }
124
+ };
125
+ /** dampingRatio = 1: fastest non-oscillating decay, x(t) = (c1 + c2*t) * exp(-wn*t). */
126
+ var CriticallyDampedSolver = class {
127
+ #state;
128
+ #naturalFrequency = 0;
129
+ #arrival = 1;
130
+ #t = 0;
131
+ #c1 = 0;
132
+ #c2 = 0;
133
+ #crossing = Infinity;
134
+ constructor(state) {
135
+ this.#state = state;
136
+ }
137
+ configure(config) {
138
+ if (config) {
139
+ this.#naturalFrequency = config.naturalFrequency;
140
+ this.#arrival = config.arrival;
141
+ }
142
+ this.#t = 0;
143
+ this.#c1 = this.#state.position;
144
+ this.#c2 = this.#state.velocity + this.#naturalFrequency * this.#state.position;
145
+ if (this.#arrival === 1) this.#crossing = Infinity;
146
+ else {
147
+ const t = -this.#c1 / this.#c2;
148
+ this.#crossing = t > 0 ? t : Infinity;
149
+ }
150
+ }
151
+ tick(dt) {
152
+ let t = this.#t + dt;
153
+ while (t >= this.#crossing) {
154
+ t -= this.#crossing;
155
+ this.#arrive();
156
+ if (this.#state.isResting) return;
157
+ }
158
+ this.#t = t;
159
+ this.#evaluate();
160
+ }
161
+ #arrive() {
162
+ this.#t = this.#crossing;
163
+ this.#evaluate();
164
+ this.#state.position = 0;
165
+ this.#state.velocity *= this.#arrival;
166
+ this.configure();
167
+ }
168
+ #evaluate() {
169
+ const decay = Math.exp(-this.#naturalFrequency * this.#t);
170
+ const decayVelocity = -this.#naturalFrequency * decay;
171
+ const scale = this.#c1 + this.#c2 * this.#t;
172
+ const scaleVelocity = this.#c2;
173
+ this.#state.position = scale * decay;
174
+ this.#state.velocity = scaleVelocity * decay + decayVelocity * scale;
175
+ }
176
+ };
177
+ /** dampingRatio > 1: slow non-oscillating decay, x(t) = exp(-sigma*t) * (c1*sinh(wd*t) + c2*cosh(wd*t)) / wd. */
178
+ var OverdampedSolver = class {
179
+ #state;
180
+ #dampedFrequency = 0;
181
+ #decayRate = 0;
182
+ #arrival = 1;
183
+ #t = 0;
184
+ #c1 = 0;
185
+ #c2 = 0;
186
+ #crossing = Infinity;
187
+ constructor(state) {
188
+ this.#state = state;
189
+ }
190
+ configure(config) {
191
+ if (config) {
192
+ this.#decayRate = config.dampingRatio * config.naturalFrequency;
193
+ this.#dampedFrequency = config.naturalFrequency * Math.sqrt(config.dampingRatio ** 2 - 1);
194
+ this.#arrival = config.arrival;
195
+ }
196
+ this.#t = 0;
197
+ this.#c1 = this.#state.velocity + this.#decayRate * this.#state.position;
198
+ this.#c2 = this.#state.position * this.#dampedFrequency;
199
+ if (this.#arrival === 1) this.#crossing = Infinity;
200
+ else {
201
+ const theta = Math.atanh(-this.#c2 / this.#c1);
202
+ this.#crossing = theta > 0 ? theta / this.#dampedFrequency : Infinity;
203
+ }
204
+ }
205
+ tick(dt) {
206
+ let t = this.#t + dt;
207
+ while (t >= this.#crossing) {
208
+ t -= this.#crossing;
209
+ this.#arrive();
210
+ if (this.#state.isResting) return;
211
+ }
212
+ this.#t = t;
213
+ this.#evaluate();
214
+ }
215
+ #arrive() {
216
+ this.#t = this.#crossing;
217
+ this.#evaluate();
218
+ this.#state.position = 0;
219
+ this.#state.velocity *= this.#arrival;
220
+ this.configure();
221
+ }
222
+ #evaluate() {
223
+ const decay = Math.exp(-this.#decayRate * this.#t);
224
+ const decayVelocity = -this.#decayRate * decay;
225
+ const clamped = Math.min(this.#dampedFrequency * this.#t, 300);
226
+ const sinh = Math.sinh(clamped);
227
+ const cosh = Math.cosh(clamped);
228
+ const scale = this.#c1 * sinh + this.#c2 * cosh;
229
+ const scaleVelocity = this.#c1 * this.#dampedFrequency * cosh + this.#c2 * this.#dampedFrequency * sinh;
230
+ this.#state.position = scale * decay / this.#dampedFrequency;
231
+ this.#state.velocity = (scale * decayVelocity + scaleVelocity * decay) / this.#dampedFrequency;
232
+ }
233
+ };
234
+ //#endregion
235
+ export { RESOLVED as a, isNumber as c, State as i, isRecord as l, OverdampedSolver as n, invariant as o, UnderdampedSolver as r, isArray as s, CriticallyDampedSolver as t, isRecordOrArray as u };