@glissade/core 0.1.0 → 0.2.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/index.d.ts +67 -5
- package/dist/index.js +249 -23
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -49,12 +49,20 @@ declare function untracked<T>(fn: () => T): T;
|
|
|
49
49
|
* (spring overshoot); non-extrapolating types clamp.
|
|
50
50
|
*/
|
|
51
51
|
type Vec2 = readonly [number, number];
|
|
52
|
+
/** Transition handoff policies (v2 addendum §A.4/§B.1); 'crossfade' reserved. */
|
|
53
|
+
type HandoffKind = 'cut' | 'decay' | 'spring' | 'blend-from-frozen';
|
|
52
54
|
interface ValueType<T> {
|
|
53
55
|
id: string;
|
|
54
56
|
lerp(a: T, b: T, t: number): T;
|
|
55
57
|
/** Accepts easedT outside [0,1] (spring overshoot)? Otherwise clamped. */
|
|
56
58
|
extrapolates: boolean;
|
|
57
59
|
equals(a: T, b: T): boolean;
|
|
60
|
+
/** Optional linear-space operators (offset decay + reserved additive blending, §B.6). */
|
|
61
|
+
add?(a: T, b: T): T;
|
|
62
|
+
sub?(a: T, b: T): T;
|
|
63
|
+
scale?(a: T, k: number): T;
|
|
64
|
+
/** Type-class handoff default (§B.1): spring for kinetic, cut for hold-only. */
|
|
65
|
+
defaultHandoff?: HandoffKind;
|
|
58
66
|
}
|
|
59
67
|
type ValueTypeId = 'number' | 'vec2' | 'color' | 'string' | 'boolean' | (string & {});
|
|
60
68
|
declare function registerValueType<T>(vt: ValueType<T>): void;
|
|
@@ -104,6 +112,12 @@ type EaseSpec = string | {
|
|
|
104
112
|
mass: number;
|
|
105
113
|
};
|
|
106
114
|
declare const easings: Record<string, EasingFn>;
|
|
115
|
+
/**
|
|
116
|
+
* Analytic derivatives d(u) of every named ease (§B.6) — closed-form, used
|
|
117
|
+
* for reading velocity off in-flight curves at interruption time. Property-
|
|
118
|
+
* tested against central differences at interior points.
|
|
119
|
+
*/
|
|
120
|
+
declare const easingDerivatives: Record<string, EasingFn>;
|
|
107
121
|
/** Default property-tween ease (Motion Canvas precedent). */
|
|
108
122
|
declare const DEFAULT_EASE = "easeInOutCubic";
|
|
109
123
|
/**
|
|
@@ -111,6 +125,8 @@ declare const DEFAULT_EASE = "easeInOutCubic";
|
|
|
111
125
|
* with a bisection fallback for the flat-derivative regions.
|
|
112
126
|
*/
|
|
113
127
|
declare function cubicBezier(p1x: number, p1y: number, p2x: number, p2y: number): EasingFn;
|
|
128
|
+
/** Analytic dy/dx of a cubic bézier ease: y'(s)/x'(s) at the solved parameter (§B.6). */
|
|
129
|
+
declare function cubicBezierDerivative(p1x: number, p1y: number, p2x: number, p2y: number): EasingFn;
|
|
114
130
|
declare class UnknownEasingError extends Error {
|
|
115
131
|
constructor(name: string);
|
|
116
132
|
}
|
|
@@ -144,10 +160,24 @@ declare function duration(cfg: SpringConfig, opts?: {
|
|
|
144
160
|
declare function value(cfg: SpringConfig, t: number, opts?: {
|
|
145
161
|
settleTolerance?: number;
|
|
146
162
|
}): number;
|
|
163
|
+
/**
|
|
164
|
+
* Velocity-matched retarget oscillator (v2 addendum §B.3): the same damped
|
|
165
|
+
* harmonic oscillator on an OFFSET in value units with nonzero initial
|
|
166
|
+
* velocity — y(0)=x0, y'(0)=v0, decaying to 0. No affine rescale (the target
|
|
167
|
+
* is exactly 0). Pure closed forms; seek-safe at any τ.
|
|
168
|
+
*/
|
|
169
|
+
interface RetargetSpring {
|
|
170
|
+
value(tau: number): number;
|
|
171
|
+
velocity(tau: number): number;
|
|
172
|
+
/** Earliest τ after which |value| stays within tol (default 0.005·|x0|+1e-6 floor). */
|
|
173
|
+
settleTime(tol?: number): number;
|
|
174
|
+
}
|
|
175
|
+
declare function retarget(cfg: SpringConfig, x0: number, v0: number): RetargetSpring;
|
|
147
176
|
interface SpringFactory {
|
|
148
177
|
(cfg: SpringConfig): SpringEase;
|
|
149
178
|
duration: typeof duration;
|
|
150
179
|
value: typeof value;
|
|
180
|
+
retarget: typeof retarget;
|
|
151
181
|
}
|
|
152
182
|
declare const spring: SpringFactory;
|
|
153
183
|
/**
|
|
@@ -155,6 +185,12 @@ declare const spring: SpringFactory;
|
|
|
155
185
|
* spring.duration(cfg) (validated at the document layer, §2.7).
|
|
156
186
|
*/
|
|
157
187
|
declare function springEasing(cfg: SpringConfig): EasingFn;
|
|
188
|
+
/**
|
|
189
|
+
* Analytic d/dp of springEasing (§B.6): oscillator derivative × the affine
|
|
190
|
+
* rescale factor × duration (chain rule p → t = p·D). Flat past p=1,
|
|
191
|
+
* matching value()'s clamp (right-derivative convention).
|
|
192
|
+
*/
|
|
193
|
+
declare function springEasingDerivative(cfg: SpringConfig): EasingFn;
|
|
158
194
|
//#endregion
|
|
159
195
|
//#region src/color.d.ts
|
|
160
196
|
/**
|
|
@@ -215,9 +251,29 @@ declare function track<T>(target: string, type: ValueTypeId, keys: Key<T>[], opt
|
|
|
215
251
|
editable?: boolean;
|
|
216
252
|
}): Track<T>;
|
|
217
253
|
declare function resolveEase(spec: EaseSpec | undefined): EasingFn;
|
|
254
|
+
/**
|
|
255
|
+
* Analytic d(u) for an ease spec (§B.6). Custom-registered eases without a
|
|
256
|
+
* derivative fall back to a symmetric difference with a one-time dev warning.
|
|
257
|
+
*/
|
|
258
|
+
declare function resolveEaseDerivative(spec: EaseSpec | undefined): EasingFn;
|
|
259
|
+
/**
|
|
260
|
+
* Analytic track derivative at time t, in value-units per second of local
|
|
261
|
+
* track time (v2 addendum §B.3/§B.6 conventions, pinned):
|
|
262
|
+
* (a) at a key boundary, velocity is the RIGHT derivative;
|
|
263
|
+
* (b) hold segments and the clamped regions outside the keys have v = 0;
|
|
264
|
+
* (c) types without sub/scale operators return null (no kinetic velocity).
|
|
265
|
+
*/
|
|
266
|
+
declare function velocityAt<T>(tr: Track<T>, t: number): T | null;
|
|
218
267
|
/** Pure sample of a track at time t (§2.4). */
|
|
219
268
|
declare function sampleTrack<T>(tr: Track<T>, t: number): T;
|
|
220
269
|
//#endregion
|
|
270
|
+
//#region src/devWarning.d.ts
|
|
271
|
+
/** The configurable dev-warning channel (no DOM lib in core; console may not exist). */
|
|
272
|
+
type DevWarning = (message: string) => void;
|
|
273
|
+
declare function setDevWarning(fn: DevWarning): void;
|
|
274
|
+
/** Internal: emit through the configurable channel. */
|
|
275
|
+
declare function emitDevWarning(message: string): void;
|
|
276
|
+
//#endregion
|
|
221
277
|
//#region src/timeline.d.ts
|
|
222
278
|
type Json = null | boolean | number | string | Json[] | {
|
|
223
279
|
[k: string]: Json;
|
|
@@ -293,10 +349,6 @@ interface CompiledTimeline {
|
|
|
293
349
|
/** Audio clips rebased to the root time axis (§5.3); sync timeScale scales playbackRate. */
|
|
294
350
|
audio: AudioClip[];
|
|
295
351
|
}
|
|
296
|
-
type DevWarning = (message: string) => void;
|
|
297
|
-
declare function setDevWarning(fn: DevWarning): void;
|
|
298
|
-
/** Internal: emit through the configurable dev-warning channel. */
|
|
299
|
-
|
|
300
352
|
declare function compileTimeline(doc: Timeline): CompiledTimeline;
|
|
301
353
|
//#endregion
|
|
302
354
|
//#region src/targetRef.d.ts
|
|
@@ -365,8 +417,18 @@ interface BindTarget {
|
|
|
365
417
|
bindSource(fn: () => unknown): void;
|
|
366
418
|
unbindSource(): void;
|
|
367
419
|
}
|
|
420
|
+
/** Analytic value/velocity access to one bound target (v2 addendum §B.6). */
|
|
421
|
+
interface CurveSampler {
|
|
422
|
+
readonly track: Track;
|
|
423
|
+
/** Pure sample at local timeline time t. */
|
|
424
|
+
value(t: number): unknown;
|
|
425
|
+
/** Analytic derivative per §B.3 conventions; null for types without operators. */
|
|
426
|
+
velocity(t: number): unknown | null;
|
|
427
|
+
}
|
|
368
428
|
interface BoundTimeline {
|
|
369
429
|
playhead: Playhead;
|
|
430
|
+
/** Per-target analytic samplers (additive, v2 §B.6); machines read these. */
|
|
431
|
+
samplers: ReadonlyMap<string, CurveSampler>;
|
|
370
432
|
/** Detach every track binding, freezing signals at their last values. */
|
|
371
433
|
unbind(): void;
|
|
372
434
|
}
|
|
@@ -459,4 +521,4 @@ declare function normalizeEditedKeys(keys: Key[]): Key[];
|
|
|
459
521
|
*/
|
|
460
522
|
declare function mergeSidecar(code: Timeline, sidecar: SidecarDoc | null | undefined): Timeline;
|
|
461
523
|
//#endregion
|
|
462
|
-
export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindTarget, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type CompiledTimeline, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type Json, type Key, type KeyOpts, type Marker, type OkLab, type Playhead, type Position, PositionError, type ReadonlySignal, type Rgba, type Rng, type SidecarDoc, SidecarVersionError, type Signal, type SignalOptions, type SpringConfig, type SpringEase, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Signal, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, easings, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, random, registerValueType, resolveEase, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type };
|
|
524
|
+
export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindTarget, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type CompiledTimeline, type CurveSampler, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type HandoffKind, type Json, type Key, type KeyOpts, type Marker, type OkLab, type Playhead, type Position, PositionError, type ReadonlySignal, type RetargetSpring, type Rgba, type Rng, type SidecarDoc, SidecarVersionError, type Signal, type SignalOptions, type SpringConfig, type SpringEase, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Signal, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
package/dist/index.js
CHANGED
|
@@ -312,20 +312,29 @@ const numberType = {
|
|
|
312
312
|
id: "number",
|
|
313
313
|
lerp: (a, b, t) => a + (b - a) * t,
|
|
314
314
|
extrapolates: true,
|
|
315
|
-
equals: Object.is
|
|
315
|
+
equals: Object.is,
|
|
316
|
+
add: (a, b) => a + b,
|
|
317
|
+
sub: (a, b) => a - b,
|
|
318
|
+
scale: (a, k) => a * k,
|
|
319
|
+
defaultHandoff: "spring"
|
|
316
320
|
};
|
|
317
321
|
const vec2Equals = (a, b) => a[0] === b[0] && a[1] === b[1];
|
|
318
322
|
const vec2Type = {
|
|
319
323
|
id: "vec2",
|
|
320
324
|
lerp: (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t],
|
|
321
325
|
extrapolates: true,
|
|
322
|
-
equals: vec2Equals
|
|
326
|
+
equals: vec2Equals,
|
|
327
|
+
add: (a, b) => [a[0] + b[0], a[1] + b[1]],
|
|
328
|
+
sub: (a, b) => [a[0] - b[0], a[1] - b[1]],
|
|
329
|
+
scale: (a, k) => [a[0] * k, a[1] * k],
|
|
330
|
+
defaultHandoff: "spring"
|
|
323
331
|
};
|
|
324
332
|
const colorType = {
|
|
325
333
|
id: "color",
|
|
326
334
|
lerp: lerpColor,
|
|
327
335
|
extrapolates: true,
|
|
328
|
-
equals: (a, b) => a === b
|
|
336
|
+
equals: (a, b) => a === b,
|
|
337
|
+
defaultHandoff: "blend-from-frozen"
|
|
329
338
|
};
|
|
330
339
|
/** Discrete types: hold-only by construction (§2.2); lerp snaps at t=1. */
|
|
331
340
|
function discrete(id) {
|
|
@@ -333,7 +342,8 @@ function discrete(id) {
|
|
|
333
342
|
id,
|
|
334
343
|
lerp: (a, b, t) => t >= 1 ? b : a,
|
|
335
344
|
extrapolates: false,
|
|
336
|
-
equals: (a, b) => Object.is(a, b)
|
|
345
|
+
equals: (a, b) => Object.is(a, b),
|
|
346
|
+
defaultHandoff: "cut"
|
|
337
347
|
};
|
|
338
348
|
}
|
|
339
349
|
const stringType = discrete("string");
|
|
@@ -456,13 +466,75 @@ const easings = {
|
|
|
456
466
|
easeOutBounce: bounceOut,
|
|
457
467
|
easeInOutBounce: (t) => t < .5 ? (1 - bounceOut(1 - 2 * t)) / 2 : (1 + bounceOut(2 * t - 1)) / 2
|
|
458
468
|
};
|
|
459
|
-
|
|
460
|
-
const
|
|
469
|
+
function bounceOutD(t) {
|
|
470
|
+
const n1 = 7.5625;
|
|
471
|
+
const d1 = 2.75;
|
|
472
|
+
if (t < 1 / d1) return 2 * n1 * t;
|
|
473
|
+
if (t < 2 / d1) return 2 * n1 * (t - 1.5 / d1);
|
|
474
|
+
if (t < 2.5 / d1) return 2 * n1 * (t - 2.25 / d1);
|
|
475
|
+
return 2 * n1 * (t - 2.625 / d1);
|
|
476
|
+
}
|
|
477
|
+
const LN2 = Math.LN2;
|
|
461
478
|
/**
|
|
462
|
-
*
|
|
463
|
-
*
|
|
479
|
+
* Analytic derivatives d(u) of every named ease (§B.6) — closed-form, used
|
|
480
|
+
* for reading velocity off in-flight curves at interruption time. Property-
|
|
481
|
+
* tested against central differences at interior points.
|
|
464
482
|
*/
|
|
465
|
-
|
|
483
|
+
const easingDerivatives = {
|
|
484
|
+
linear: () => 1,
|
|
485
|
+
easeInQuad: (t) => 2 * t,
|
|
486
|
+
easeOutQuad: (t) => 2 * (1 - t),
|
|
487
|
+
easeInOutQuad: (t) => t < .5 ? 4 * t : 4 * (1 - t),
|
|
488
|
+
easeInCubic: (t) => 3 * t ** 2,
|
|
489
|
+
easeOutCubic: (t) => 3 * (1 - t) ** 2,
|
|
490
|
+
easeInOutCubic: (t) => t < .5 ? 12 * t ** 2 : 12 * (1 - t) ** 2,
|
|
491
|
+
easeInQuart: (t) => 4 * t ** 3,
|
|
492
|
+
easeOutQuart: (t) => 4 * (1 - t) ** 3,
|
|
493
|
+
easeInOutQuart: (t) => t < .5 ? 32 * t ** 3 : 32 * (1 - t) ** 3,
|
|
494
|
+
easeInQuint: (t) => 5 * t ** 4,
|
|
495
|
+
easeOutQuint: (t) => 5 * (1 - t) ** 4,
|
|
496
|
+
easeInOutQuint: (t) => t < .5 ? 80 * t ** 4 : 80 * (1 - t) ** 4,
|
|
497
|
+
easeInSine: (t) => Math.PI / 2 * Math.sin(t * Math.PI / 2),
|
|
498
|
+
easeOutSine: (t) => Math.PI / 2 * Math.cos(t * Math.PI / 2),
|
|
499
|
+
easeInOutSine: (t) => Math.PI / 2 * Math.sin(Math.PI * t),
|
|
500
|
+
easeInExpo: (t) => t === 0 ? 0 : 10 * LN2 * 2 ** (10 * t - 10),
|
|
501
|
+
easeOutExpo: (t) => t === 1 ? 0 : 10 * LN2 * 2 ** (-10 * t),
|
|
502
|
+
easeInOutExpo: (t) => t === 0 || t === 1 ? 0 : t < .5 ? 10 * LN2 * 2 ** (20 * t - 10) : 10 * LN2 * 2 ** (-20 * t + 10),
|
|
503
|
+
easeInCirc: (t) => t / Math.sqrt(1 - t * t),
|
|
504
|
+
easeOutCirc: (t) => (1 - t) / Math.sqrt(1 - (t - 1) * (t - 1)),
|
|
505
|
+
easeInOutCirc: (t) => t < .5 ? 2 * t / Math.sqrt(1 - 4 * t * t) : (2 - 2 * t) / Math.sqrt(1 - (-2 * t + 2) ** 2),
|
|
506
|
+
easeInBack: (t) => 3 * c3 * t ** 2 - 2 * c1 * t,
|
|
507
|
+
easeOutBack: (t) => 3 * c3 * (t - 1) ** 2 + 2 * c1 * (t - 1),
|
|
508
|
+
easeInOutBack: (t) => t < .5 ? 12 * 3.5949095 * t ** 2 - 4 * c2 * t : 3 * 3.5949095 * (2 * t - 2) ** 2 + 2 * c2 * (2 * t - 2),
|
|
509
|
+
easeInElastic: (t) => {
|
|
510
|
+
if (t === 0 || t === 1) return 0;
|
|
511
|
+
const theta = (t * 10 - 10.75) * c4;
|
|
512
|
+
const amp = 2 ** (10 * t - 10);
|
|
513
|
+
return -(10 * LN2 * amp * Math.sin(theta) + amp * 10 * c4 * Math.cos(theta));
|
|
514
|
+
},
|
|
515
|
+
easeOutElastic: (t) => {
|
|
516
|
+
if (t === 0 || t === 1) return 0;
|
|
517
|
+
const phi = (t * 10 - .75) * c4;
|
|
518
|
+
const amp = 2 ** (-10 * t);
|
|
519
|
+
return -10 * LN2 * amp * Math.sin(phi) + amp * 10 * c4 * Math.cos(phi);
|
|
520
|
+
},
|
|
521
|
+
easeInOutElastic: (t) => {
|
|
522
|
+
if (t === 0 || t === 1) return 0;
|
|
523
|
+
const psi = (20 * t - 11.125) * c5;
|
|
524
|
+
if (t < .5) {
|
|
525
|
+
const amp = 2 ** (20 * t - 10);
|
|
526
|
+
return -(20 * LN2 * amp * Math.sin(psi) + amp * 20 * c5 * Math.cos(psi)) / 2;
|
|
527
|
+
}
|
|
528
|
+
const amp = 2 ** (-20 * t + 10);
|
|
529
|
+
return (-20 * LN2 * amp * Math.sin(psi) + amp * 20 * c5 * Math.cos(psi)) / 2;
|
|
530
|
+
},
|
|
531
|
+
easeInBounce: (t) => bounceOutD(1 - t),
|
|
532
|
+
easeOutBounce: bounceOutD,
|
|
533
|
+
easeInOutBounce: (t) => t < .5 ? bounceOutD(1 - 2 * t) : bounceOutD(2 * t - 1)
|
|
534
|
+
};
|
|
535
|
+
/** Default property-tween ease (Motion Canvas precedent). */
|
|
536
|
+
const DEFAULT_EASE = "easeInOutCubic";
|
|
537
|
+
function bezierKernel(p1x, p1y, p2x, p2y) {
|
|
466
538
|
const ax = 3 * p1x - 3 * p2x + 1;
|
|
467
539
|
const bx = 3 * p2x - 6 * p1x;
|
|
468
540
|
const cx = 3 * p1x;
|
|
@@ -472,6 +544,7 @@ function cubicBezier(p1x, p1y, p2x, p2y) {
|
|
|
472
544
|
const sampleX = (u) => ((ax * u + bx) * u + cx) * u;
|
|
473
545
|
const sampleY = (u) => ((ay * u + by) * u + cy) * u;
|
|
474
546
|
const sampleDX = (u) => (3 * ax * u + 2 * bx) * u + cx;
|
|
547
|
+
const sampleDY = (u) => (3 * ay * u + 2 * by) * u + cy;
|
|
475
548
|
const solveU = (x) => {
|
|
476
549
|
let u = x;
|
|
477
550
|
for (let i = 0; i < 8; i++) {
|
|
@@ -491,10 +564,33 @@ function cubicBezier(p1x, p1y, p2x, p2y) {
|
|
|
491
564
|
}
|
|
492
565
|
return u;
|
|
493
566
|
};
|
|
567
|
+
return {
|
|
568
|
+
sampleY,
|
|
569
|
+
sampleDX,
|
|
570
|
+
sampleDY,
|
|
571
|
+
solveU
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* CSS-style cubic bézier where x is time and y is progress. Newton's method
|
|
576
|
+
* with a bisection fallback for the flat-derivative regions.
|
|
577
|
+
*/
|
|
578
|
+
function cubicBezier(p1x, p1y, p2x, p2y) {
|
|
579
|
+
const k = bezierKernel(p1x, p1y, p2x, p2y);
|
|
494
580
|
return (t) => {
|
|
495
581
|
if (t <= 0) return 0;
|
|
496
582
|
if (t >= 1) return 1;
|
|
497
|
-
return sampleY(solveU(t));
|
|
583
|
+
return k.sampleY(k.solveU(t));
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
/** Analytic dy/dx of a cubic bézier ease: y'(s)/x'(s) at the solved parameter (§B.6). */
|
|
587
|
+
function cubicBezierDerivative(p1x, p1y, p2x, p2y) {
|
|
588
|
+
const k = bezierKernel(p1x, p1y, p2x, p2y);
|
|
589
|
+
return (t) => {
|
|
590
|
+
const u = k.solveU(Math.min(1, Math.max(0, t)));
|
|
591
|
+
const dx = k.sampleDX(u);
|
|
592
|
+
if (Math.abs(dx) < 1e-9) return 0;
|
|
593
|
+
return k.sampleDY(u) / dx;
|
|
498
594
|
};
|
|
499
595
|
}
|
|
500
596
|
var UnknownEasingError = class extends Error {
|
|
@@ -519,6 +615,20 @@ function params(cfg) {
|
|
|
519
615
|
zeta: cfg.damping / (2 * Math.sqrt(cfg.stiffness * mass))
|
|
520
616
|
};
|
|
521
617
|
}
|
|
618
|
+
/** Raw closed-form spring velocity at time t — d/dt of rawValue's three branches. */
|
|
619
|
+
function rawDerivative(cfg, t) {
|
|
620
|
+
if (t <= 0) return 0;
|
|
621
|
+
const { w0, zeta } = params(cfg);
|
|
622
|
+
if (Math.abs(zeta - 1) < 1e-9) return w0 * w0 * t * Math.exp(-w0 * t);
|
|
623
|
+
if (zeta < 1) {
|
|
624
|
+
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
625
|
+
return Math.exp(-zeta * w0 * t) * (w0 * w0 / wd) * Math.sin(wd * t);
|
|
626
|
+
}
|
|
627
|
+
const s = Math.sqrt(zeta * zeta - 1);
|
|
628
|
+
const r1 = -w0 * (zeta - s);
|
|
629
|
+
const r2 = -w0 * (zeta + s);
|
|
630
|
+
return r1 * r2 * (Math.exp(r1 * t) - Math.exp(r2 * t)) / (r1 - r2);
|
|
631
|
+
}
|
|
522
632
|
/** Raw closed-form spring position at time t (seconds). Approaches 1, may overshoot. */
|
|
523
633
|
function rawValue(cfg, t) {
|
|
524
634
|
if (t <= 0) return 0;
|
|
@@ -565,6 +675,53 @@ function value(cfg, t, opts) {
|
|
|
565
675
|
const d = duration(cfg, opts);
|
|
566
676
|
return rawValue(cfg, Math.min(t, d)) / rawValue(cfg, d);
|
|
567
677
|
}
|
|
678
|
+
function retarget(cfg, x0, v0) {
|
|
679
|
+
const { w0, zeta } = params(cfg);
|
|
680
|
+
let value0;
|
|
681
|
+
let velocity0;
|
|
682
|
+
let envelope;
|
|
683
|
+
if (Math.abs(zeta - 1) < 1e-9) {
|
|
684
|
+
const b = v0 + w0 * x0;
|
|
685
|
+
value0 = (tau) => tau <= 0 ? x0 : Math.exp(-w0 * tau) * (x0 + b * tau);
|
|
686
|
+
velocity0 = (tau) => tau <= 0 ? v0 : Math.exp(-w0 * tau) * (v0 - w0 * b * tau);
|
|
687
|
+
envelope = (tau) => Math.exp(-w0 * tau) * (Math.abs(x0) + Math.abs(b) * tau);
|
|
688
|
+
} else if (zeta < 1) {
|
|
689
|
+
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
690
|
+
const c2v = (v0 + zeta * w0 * x0) / wd;
|
|
691
|
+
const amp = Math.hypot(x0, c2v);
|
|
692
|
+
value0 = (tau) => tau <= 0 ? x0 : Math.exp(-zeta * w0 * tau) * (x0 * Math.cos(wd * tau) + c2v * Math.sin(wd * tau));
|
|
693
|
+
velocity0 = (tau) => tau <= 0 ? v0 : Math.exp(-zeta * w0 * tau) * (v0 * Math.cos(wd * tau) - (w0 * w0 * x0 + zeta * w0 * v0) / wd * Math.sin(wd * tau));
|
|
694
|
+
envelope = (tau) => Math.exp(-zeta * w0 * tau) * amp;
|
|
695
|
+
} else {
|
|
696
|
+
const s = Math.sqrt(zeta * zeta - 1);
|
|
697
|
+
const rp = w0 * (-zeta + s);
|
|
698
|
+
const rm = w0 * (-zeta - s);
|
|
699
|
+
const cp = (v0 - rm * x0) / (rp - rm);
|
|
700
|
+
const cm = (rp * x0 - v0) / (rp - rm);
|
|
701
|
+
value0 = (tau) => tau <= 0 ? x0 : cp * Math.exp(rp * tau) + cm * Math.exp(rm * tau);
|
|
702
|
+
velocity0 = (tau) => tau <= 0 ? v0 : cp * rp * Math.exp(rp * tau) + cm * rm * Math.exp(rm * tau);
|
|
703
|
+
envelope = (tau) => Math.abs(cp) * Math.exp(rp * tau) + Math.abs(cm) * Math.exp(rm * tau);
|
|
704
|
+
}
|
|
705
|
+
const settleTime = (tol) => {
|
|
706
|
+
const eps = tol ?? Math.abs(x0) * .005 + 1e-6;
|
|
707
|
+
if (envelope(0) <= eps && Math.abs(v0) < 1e-12) return 0;
|
|
708
|
+
let hi = 1 / w0;
|
|
709
|
+
let guard = 0;
|
|
710
|
+
while (envelope(hi) > eps && guard++ < 64) hi *= 2;
|
|
711
|
+
let lo = 0;
|
|
712
|
+
for (let i = 0; i < 64 && hi - lo > 1e-9; i++) {
|
|
713
|
+
const mid = (lo + hi) / 2;
|
|
714
|
+
if (Math.max(envelope(mid), envelope(mid * 1.05 + 1e-6)) > eps) lo = mid;
|
|
715
|
+
else hi = mid;
|
|
716
|
+
}
|
|
717
|
+
return hi;
|
|
718
|
+
};
|
|
719
|
+
return {
|
|
720
|
+
value: value0,
|
|
721
|
+
velocity: velocity0,
|
|
722
|
+
settleTime
|
|
723
|
+
};
|
|
724
|
+
}
|
|
568
725
|
const spring = Object.assign((cfg) => {
|
|
569
726
|
params(cfg);
|
|
570
727
|
return {
|
|
@@ -575,7 +732,8 @@ const spring = Object.assign((cfg) => {
|
|
|
575
732
|
};
|
|
576
733
|
}, {
|
|
577
734
|
duration,
|
|
578
|
-
value
|
|
735
|
+
value,
|
|
736
|
+
retarget
|
|
579
737
|
});
|
|
580
738
|
/**
|
|
581
739
|
* The spring as a normalized easing over a segment whose length must equal
|
|
@@ -585,6 +743,28 @@ function springEasing(cfg) {
|
|
|
585
743
|
const d = duration(cfg);
|
|
586
744
|
return (p) => value(cfg, p * d);
|
|
587
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* Analytic d/dp of springEasing (§B.6): oscillator derivative × the affine
|
|
748
|
+
* rescale factor × duration (chain rule p → t = p·D). Flat past p=1,
|
|
749
|
+
* matching value()'s clamp (right-derivative convention).
|
|
750
|
+
*/
|
|
751
|
+
function springEasingDerivative(cfg) {
|
|
752
|
+
const d = duration(cfg);
|
|
753
|
+
const scale = d / rawValue(cfg, d);
|
|
754
|
+
return (p) => p >= 1 ? 0 : rawDerivative(cfg, p * d) * scale;
|
|
755
|
+
}
|
|
756
|
+
//#endregion
|
|
757
|
+
//#region src/devWarning.ts
|
|
758
|
+
let devWarn = (msg) => {
|
|
759
|
+
globalThis.console?.warn(`[glissade] ${msg}`);
|
|
760
|
+
};
|
|
761
|
+
function setDevWarning(fn) {
|
|
762
|
+
devWarn = fn;
|
|
763
|
+
}
|
|
764
|
+
/** Internal: emit through the configurable channel. */
|
|
765
|
+
function emitDevWarning(message) {
|
|
766
|
+
devWarn(message);
|
|
767
|
+
}
|
|
588
768
|
//#endregion
|
|
589
769
|
//#region src/track.ts
|
|
590
770
|
/**
|
|
@@ -645,6 +825,27 @@ function resolveEase(spec) {
|
|
|
645
825
|
if (spec.kind === "cubicBezier") return cubicBezier(...spec.pts);
|
|
646
826
|
return springEasing(spec);
|
|
647
827
|
}
|
|
828
|
+
const warnedNumericDerivative = /* @__PURE__ */ new Set();
|
|
829
|
+
/**
|
|
830
|
+
* Analytic d(u) for an ease spec (§B.6). Custom-registered eases without a
|
|
831
|
+
* derivative fall back to a symmetric difference with a one-time dev warning.
|
|
832
|
+
*/
|
|
833
|
+
function resolveEaseDerivative(spec) {
|
|
834
|
+
if (spec === void 0) return easingDerivatives["linear"];
|
|
835
|
+
if (typeof spec === "string") {
|
|
836
|
+
const d = easingDerivatives[spec];
|
|
837
|
+
if (d) return d;
|
|
838
|
+
const fn = namedEasing(spec);
|
|
839
|
+
if (!warnedNumericDerivative.has(spec)) {
|
|
840
|
+
warnedNumericDerivative.add(spec);
|
|
841
|
+
emitDevWarning(`easing '${spec}' has no registered derivative; velocity uses a numeric fallback — register one in easingDerivatives for exact interruption handoff`);
|
|
842
|
+
}
|
|
843
|
+
const h = 1e-5;
|
|
844
|
+
return (u) => (fn(Math.min(1, u + h)) - fn(Math.max(0, u - h))) / (Math.min(1, u + h) - Math.max(0, u - h));
|
|
845
|
+
}
|
|
846
|
+
if (spec.kind === "cubicBezier") return cubicBezierDerivative(...spec.pts);
|
|
847
|
+
return springEasingDerivative(spec);
|
|
848
|
+
}
|
|
648
849
|
const samplerStates = /* @__PURE__ */ new WeakMap();
|
|
649
850
|
function state(tr) {
|
|
650
851
|
let s = samplerStates.get(tr);
|
|
@@ -684,6 +885,34 @@ function findSegment(keys, t, hint) {
|
|
|
684
885
|
}
|
|
685
886
|
return lo;
|
|
686
887
|
}
|
|
888
|
+
/**
|
|
889
|
+
* Analytic track derivative at time t, in value-units per second of local
|
|
890
|
+
* track time (v2 addendum §B.3/§B.6 conventions, pinned):
|
|
891
|
+
* (a) at a key boundary, velocity is the RIGHT derivative;
|
|
892
|
+
* (b) hold segments and the clamped regions outside the keys have v = 0;
|
|
893
|
+
* (c) types without sub/scale operators return null (no kinetic velocity).
|
|
894
|
+
*/
|
|
895
|
+
function velocityAt(tr, t) {
|
|
896
|
+
const vt = getValueType(tr.type);
|
|
897
|
+
if (!vt.sub || !vt.scale) return null;
|
|
898
|
+
const keys = tr.keys;
|
|
899
|
+
const n = keys.length;
|
|
900
|
+
const s = state(tr);
|
|
901
|
+
const i = findSegment(keys, t, s.cursor);
|
|
902
|
+
const zero = vt.scale(vt.sub(keys[0].value, keys[0].value), 0);
|
|
903
|
+
if (i === 0 || i >= n) return zero;
|
|
904
|
+
const arrival = keys[i];
|
|
905
|
+
if (arrival.interp === "hold") return zero;
|
|
906
|
+
const prev = keys[i - 1];
|
|
907
|
+
const segDur = arrival.t - prev.t;
|
|
908
|
+
const p = (t - prev.t) / segDur;
|
|
909
|
+
if (!vt.extrapolates) {
|
|
910
|
+
const eased = easeFor(tr, s, i)(p);
|
|
911
|
+
if (eased < 0 || eased > 1) return zero;
|
|
912
|
+
}
|
|
913
|
+
const d = resolveEaseDerivative(arrival.ease)(p);
|
|
914
|
+
return vt.scale(vt.sub(arrival.value, prev.value), d / segDur);
|
|
915
|
+
}
|
|
687
916
|
/** Pure sample of a track at time t (§2.4). */
|
|
688
917
|
function sampleTrack(tr, t) {
|
|
689
918
|
const keys = tr.keys;
|
|
@@ -740,16 +969,6 @@ function validateSpringKeys(tr) {
|
|
|
740
969
|
}
|
|
741
970
|
}
|
|
742
971
|
}
|
|
743
|
-
let devWarn = (msg) => {
|
|
744
|
-
globalThis.console?.warn(`[glissade] ${msg}`);
|
|
745
|
-
};
|
|
746
|
-
function setDevWarning(fn) {
|
|
747
|
-
devWarn = fn;
|
|
748
|
-
}
|
|
749
|
-
/** Internal: emit through the configurable dev-warning channel. */
|
|
750
|
-
function emitDevWarning(message) {
|
|
751
|
-
devWarn(message);
|
|
752
|
-
}
|
|
753
972
|
function rebaseKeys(keys, at, timeScale) {
|
|
754
973
|
return keys.map((k) => ({
|
|
755
974
|
...k,
|
|
@@ -797,7 +1016,7 @@ function coalesce(entries) {
|
|
|
797
1016
|
const existingStart = existing.keys[0].t;
|
|
798
1017
|
const existingEnd = existing.keys[existing.keys.length - 1].t;
|
|
799
1018
|
const kept = existing.keys.filter((k) => k.t < start || k.t > end);
|
|
800
|
-
if (existingStart <= end && start <= existingEnd)
|
|
1019
|
+
if (existingStart <= end && start <= existingEnd) emitDevWarning(`overlapping tracks for '${tr.target}' in [${start}, ${end}]: later insertion wins (${existing.keys.length - kept.length} earlier key(s) dropped)`);
|
|
801
1020
|
existing.keys = [...kept, ...tr.keys].sort((a, b) => a.t - b.t);
|
|
802
1021
|
}
|
|
803
1022
|
return byTarget;
|
|
@@ -1103,14 +1322,21 @@ var UnboundTargetError = class extends Error {
|
|
|
1103
1322
|
*/
|
|
1104
1323
|
function bindTimeline(compiled, resolve, playhead = createPlayhead()) {
|
|
1105
1324
|
const bound = [];
|
|
1325
|
+
const samplers = /* @__PURE__ */ new Map();
|
|
1106
1326
|
for (const [target, tr] of compiled.tracks) {
|
|
1107
1327
|
const sig = resolve(target);
|
|
1108
1328
|
if (!sig) throw new UnboundTargetError(target);
|
|
1109
1329
|
sig.bindSource(() => sampleTrack(tr, playhead()));
|
|
1110
1330
|
bound.push(sig);
|
|
1331
|
+
samplers.set(target, {
|
|
1332
|
+
track: tr,
|
|
1333
|
+
value: (t) => sampleTrack(tr, t),
|
|
1334
|
+
velocity: (t) => velocityAt(tr, t)
|
|
1335
|
+
});
|
|
1111
1336
|
}
|
|
1112
1337
|
return {
|
|
1113
1338
|
playhead,
|
|
1339
|
+
samplers,
|
|
1114
1340
|
unbind: () => {
|
|
1115
1341
|
for (const sig of bound) sig.unbindSource();
|
|
1116
1342
|
}
|
|
@@ -1328,4 +1554,4 @@ function mergeSidecar(code, sidecar) {
|
|
|
1328
1554
|
return merged;
|
|
1329
1555
|
}
|
|
1330
1556
|
//#endregion
|
|
1331
|
-
export { BakeError, CircularDependencyError, ColorParseError, DEFAULT_EASE, PositionError, SidecarVersionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, easings, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, random, registerValueType, resolveEase, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type };
|
|
1557
|
+
export { BakeError, CircularDependencyError, ColorParseError, DEFAULT_EASE, PositionError, SidecarVersionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
package/package.json
CHANGED