@weasel-js/core 1.7.3 → 1.8.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/CHANGELOG.md +47 -0
- package/dist/animation/colorHelpers.js +58 -58
- package/dist/animation/colorHelpers.js.map +1 -1
- package/dist/animation/engine/axes.js +36 -0
- package/dist/animation/engine/axes.js.map +1 -0
- package/dist/animation/engine/codec.js +170 -0
- package/dist/animation/engine/codec.js.map +1 -0
- package/dist/animation/engine/integrator.js +12 -0
- package/dist/animation/engine/integrator.js.map +1 -0
- package/dist/animation/useAnimator.js +199 -157
- package/dist/animation/useAnimator.js.map +1 -1
- package/dist/{builtins-C-gRyVRx.d.ts → builtins-BW32dOXy.d.ts} +74 -8
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +12 -12
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
- package/dist/canvas/pickWalk.js +2 -2
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/sceneSlotWiring.js +1 -1
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +1 -1
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/core/scene/effectivePose.js +1 -1
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/poseOverrides.js +42 -22
- package/dist/core/scene/poseOverrides.js.map +1 -1
- package/dist/core/scene/types.js.map +1 -1
- package/dist/index.d.ts +28 -18
- package/dist/renderer/WeaselRenderer.js +4 -1
- package/dist/renderer/WeaselRenderer.js.map +1 -1
- package/dist/renderer/batchClip.js +46 -0
- package/dist/renderer/batchClip.js.map +1 -0
- package/dist/renderer/draw.js +367 -294
- package/dist/renderer/draw.js.map +1 -1
- package/dist/renderer/drawBatch.js +75 -45
- package/dist/renderer/drawBatch.js.map +1 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/tools/builtin/text/useTextTool.js +8 -8
- package/dist/tools/builtin/text/useTextTool.js.map +1 -1
- package/dist/tools/prefs.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +16 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c1aa1f6: A pref's label shortens to fit the room it is given. **Breaking:** `short` on a pref leaf and on an enum option is now a list of shorter forms, longest first (`short: ['Track', 'VA']`), where it was one string; wrap an existing value in an array. A toggle segment, a flag bar, and a `ToolOptionsBar` label show the longest form that fits, stepping every label in the bar down together, and `name` stays the accessible name. A leaf's `icon` is still drawn in place of any text form where it resolves; a flag with neither no longer falls back to its name's first letter.
|
|
8
|
+
|
|
9
|
+
The mechanism is public: `useFitScope(ref)` measures an element and its parent for overflow, `<FitScope>` hands the step to the `<FitLabel forms>` beneath it, and `ToggleBar` is a scope on its own. `PropertyOption` and `PropertyBooleanFieldProps` take `short` beside `glyph`, which now means only a drawn glyph.
|
|
10
|
+
|
|
11
|
+
<!-- bump-approved: minor: maintainer — minor release requested 2026-10-05 -->
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- d24f51f: The animator computes tweens, springs, physics and decay in blits. Every signature is unchanged.
|
|
16
|
+
|
|
17
|
+
- Springs, physics and decay follow closed forms, so a spring lands in the same place at any frame rate. Their values differ slightly from the previous integrator's, and they settle on a different frame. A value that isn't a number, a number array or an object of numeric fields, or constants the closed forms can't solve, still uses the previous integrator.
|
|
18
|
+
- A tween of a number array or an object of numbers no longer needs an `interpolate`.
|
|
19
|
+
- A tween of anything else with no `interpolate` or `interpolator` now throws when `tween` is called, instead of on its first frame. `setTarget` and `setVelocity` throw when the value's shape differs from `from`'s.
|
|
20
|
+
|
|
21
|
+
At 10,000 tweens a frame costs about 3× what it did; at 1,000, 0.06 ms against 0.02. Springs cost slightly less than before.
|
|
22
|
+
- 4db0f2e: `PoseOverrides` has a new method, `read(id)`. It returns what a node is drawn and picked with: every source of overrides for that node folded into one, as of the last write or `commit()`. `get(id)` still returns the entry a writer stored, by reference. Painting, picking and `effectivePose` now call `read`. Today the only source is the entries table, so `read` and `get` agree. Once animations write overrides too, `read` will fold them in with the table.
|
|
23
|
+
|
|
24
|
+
Anyone who implements `PoseOverrides`, or the `overrides` of a `PoseSource` stand-in or of diagram's `ParticipantScene`, has to add `read`. For a stand-in, `read: (id) => entries.get(id)` reproduces the old behavior.
|
|
25
|
+
|
|
26
|
+
Core now depends on `@msb235/blits`, which does the folding.
|
|
27
|
+
- 2432ce3: Quantities can be infinite. Every display shows ±Infinity as `∞`, spoken "infinity", and reads `∞`, `inf` and `infinity` back; `endless(display, 'never')` gives a display its own word, shown without a unit beside it and read back when typed. A zoom display used to print `Infinity` here.
|
|
28
|
+
|
|
29
|
+
A slider can make an end stand for infinity. `Slider` and `PropertyField` take `endless: 'max' | 'min' | 'both'`: the end stop reports ±Infinity, a value of ±Infinity sits there, and the readout shows the display's word. A number pref leaf takes `endless` and `infinity` (its word), and labkit's number builder takes `.endless('never')`, so `f.number(Infinity).range(0, 5000).suffix('ms').endless('never')` reads "never" at the top of its track instead of "5000 ms".
|
|
30
|
+
- 9b1ff50: A clip that is an axis-aligned rect no longer breaks the batch. The renderer
|
|
31
|
+
cuts staged geometry to it on the CPU instead of writing it to the stencil, so
|
|
32
|
+
a clipped group's content shares a draw with what surrounds it. The clip falls
|
|
33
|
+
back to the stencil only where it must: for content that draws for itself, and
|
|
34
|
+
for content with a slanted edge across the clip's edge. Rotated or non-rect
|
|
35
|
+
clips are unchanged.
|
|
36
|
+
- Updated dependencies [2432ce3]
|
|
37
|
+
- @weasel-js/quantity@1.8.0
|
|
38
|
+
- @weasel-js/cursor@1.8.0
|
|
39
|
+
- @weasel-js/font@1.8.0
|
|
40
|
+
- @weasel-js/geom@1.8.0
|
|
41
|
+
- @weasel-js/gestures@1.8.0
|
|
42
|
+
- @weasel-js/history@1.8.0
|
|
43
|
+
- @weasel-js/modes@1.8.0
|
|
44
|
+
- @weasel-js/paint@1.8.0
|
|
45
|
+
- @weasel-js/react@1.8.0
|
|
46
|
+
- @weasel-js/registry@1.8.0
|
|
47
|
+
- @weasel-js/routing@1.8.0
|
|
48
|
+
- @weasel-js/text@1.8.0
|
|
49
|
+
|
|
3
50
|
## 1.7.3
|
|
4
51
|
|
|
5
52
|
### Patch Changes
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
import { hslToRgb as e } from "../renderer/math/color.js";
|
|
2
2
|
import { lerpColorArray as t } from "@weasel-js/paint";
|
|
3
|
-
import {
|
|
3
|
+
import { linear as n, resolveEasing as r } from "@weasel-js/geom";
|
|
4
4
|
//#region src/animation/colorHelpers.ts
|
|
5
|
-
function
|
|
5
|
+
function i(e, t) {
|
|
6
6
|
if (e.length !== t.length) throw Error(`vertex colors: length mismatch (from=${e.length}, to=${t.length})`);
|
|
7
7
|
if (e.length === 0 || e.length % 4 != 0) throw Error(`vertex colors: length ${e.length} must be a positive multiple of 4`);
|
|
8
8
|
}
|
|
9
|
-
function
|
|
9
|
+
function a(e) {
|
|
10
10
|
if (e.interpolate) return e.interpolate;
|
|
11
11
|
let n = e.interpolation ?? "rgb";
|
|
12
12
|
return (e, r, i) => t(e, r, i, n);
|
|
13
13
|
}
|
|
14
|
-
var
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
let n =
|
|
14
|
+
var o = (e, t) => `colors:${e}:${t}`;
|
|
15
|
+
function s(e, t) {
|
|
16
|
+
i(t.from, t.to);
|
|
17
|
+
let n = a(t), { id: r, channel: s } = t;
|
|
18
18
|
return e.tween({
|
|
19
19
|
from: 0,
|
|
20
20
|
to: 1,
|
|
21
21
|
ms: t.ms,
|
|
22
22
|
easing: t.easing,
|
|
23
|
-
cancelKey:
|
|
23
|
+
cancelKey: o(r, s),
|
|
24
24
|
interpolate: (e, t, n) => e + (t - e) * n,
|
|
25
|
-
onTick: (
|
|
26
|
-
e.colorOverrides.set(
|
|
25
|
+
onTick: (i) => {
|
|
26
|
+
e.colorOverrides.set(r, s, n(t.from, t.to, i));
|
|
27
27
|
},
|
|
28
28
|
onDone: () => {
|
|
29
|
-
e.colorOverrides.clear(
|
|
29
|
+
e.colorOverrides.clear(r, s), t.onDone?.();
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
let n =
|
|
33
|
+
function c(e, t) {
|
|
34
|
+
i(t.from, t.to);
|
|
35
|
+
let n = a(t), { id: r, channel: s } = t;
|
|
36
36
|
return e.spring({
|
|
37
37
|
from: 0,
|
|
38
38
|
to: 1,
|
|
@@ -40,96 +40,96 @@ function s(e, t) {
|
|
|
40
40
|
stiffness: t.stiffness,
|
|
41
41
|
damping: t.damping,
|
|
42
42
|
mass: t.mass,
|
|
43
|
-
cancelKey:
|
|
43
|
+
cancelKey: o(r, s),
|
|
44
44
|
interpolate: (e, t, n) => e + (t - e) * n,
|
|
45
|
-
onTick: (
|
|
46
|
-
let
|
|
47
|
-
e.colorOverrides.set(
|
|
45
|
+
onTick: (i) => {
|
|
46
|
+
let a = Math.max(0, Math.min(1, i));
|
|
47
|
+
e.colorOverrides.set(r, s, n(t.from, t.to, a));
|
|
48
48
|
},
|
|
49
49
|
onDone: () => {
|
|
50
|
-
e.colorOverrides.clear(
|
|
50
|
+
e.colorOverrides.clear(r, s), t.onDone?.();
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
-
function
|
|
55
|
-
let
|
|
56
|
-
e.colorOverrides.set(
|
|
54
|
+
function l(e, t) {
|
|
55
|
+
let n = a(t), { id: i, channel: o } = t, s = t.direction ?? 1, c = r(t.easing);
|
|
56
|
+
e.colorOverrides.set(i, o, (e, r) => {
|
|
57
57
|
let i = e.length / 4;
|
|
58
58
|
if (i === 0) return e.slice();
|
|
59
|
-
let a =
|
|
59
|
+
let a = r / t.msPerCycle * i * s / i, o = a - Math.floor(a), l = c(o), u = ((Math.floor(a) + l) * i % i + i) % i, d = Math.floor(u), f = u - d, p = Array(e.length);
|
|
60
60
|
for (let t = 0; t < i; t++) {
|
|
61
|
-
let
|
|
62
|
-
e[
|
|
63
|
-
e[
|
|
64
|
-
e[
|
|
65
|
-
e[
|
|
61
|
+
let r = (t + d) % i * 4, a = (t + d + 1) % i * 4, o = [
|
|
62
|
+
e[r],
|
|
63
|
+
e[r + 1],
|
|
64
|
+
e[r + 2],
|
|
65
|
+
e[r + 3]
|
|
66
66
|
], s = [
|
|
67
67
|
e[a],
|
|
68
68
|
e[a + 1],
|
|
69
69
|
e[a + 2],
|
|
70
70
|
e[a + 3]
|
|
71
|
-
], c =
|
|
71
|
+
], c = n(o, s, f), l = t * 4;
|
|
72
72
|
p[l] = c[0], p[l + 1] = c[1], p[l + 2] = c[2], p[l + 3] = c[3];
|
|
73
73
|
}
|
|
74
74
|
return p;
|
|
75
75
|
});
|
|
76
76
|
let l = e.keepAlive();
|
|
77
77
|
return { cancel() {
|
|
78
|
-
e.colorOverrides.clear(
|
|
78
|
+
e.colorOverrides.clear(i, o), l();
|
|
79
79
|
} };
|
|
80
80
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
let
|
|
84
|
-
let t = Array(
|
|
85
|
-
for (let n = 0; n <
|
|
86
|
-
let r = Math.abs(n -
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
],
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
],
|
|
97
|
-
t[
|
|
81
|
+
function u(e, t) {
|
|
82
|
+
i(t.from, t.to);
|
|
83
|
+
let s = a(t), { id: c, channel: l, from: u, to: d, anchorMs: f, perAnchorDelay: p } = t, m = u.length / 4, h = r(t.easing), g = t.origin === "last" ? m - 1 : typeof t.origin == "number" ? Math.max(0, Math.min(m - 1, t.origin)) : 0, _ = Math.max(g, m - 1 - g) * p + f, v = (e) => {
|
|
84
|
+
let t = Array(u.length);
|
|
85
|
+
for (let n = 0; n < m; n++) {
|
|
86
|
+
let r = Math.abs(n - g) * p, i = Math.max(0, Math.min(1, (e - r) / f)), a = h(i), o = n * 4, c = [
|
|
87
|
+
u[o],
|
|
88
|
+
u[o + 1],
|
|
89
|
+
u[o + 2],
|
|
90
|
+
u[o + 3]
|
|
91
|
+
], l = [
|
|
92
|
+
d[o],
|
|
93
|
+
d[o + 1],
|
|
94
|
+
d[o + 2],
|
|
95
|
+
d[o + 3]
|
|
96
|
+
], m = s(c, l, a);
|
|
97
|
+
t[o] = m[0], t[o + 1] = m[1], t[o + 2] = m[2], t[o + 3] = m[3];
|
|
98
98
|
}
|
|
99
99
|
return t;
|
|
100
100
|
};
|
|
101
|
-
return e.colorOverrides.set(
|
|
101
|
+
return e.colorOverrides.set(c, l, v(0)), e.tween({
|
|
102
102
|
from: 0,
|
|
103
103
|
to: 1,
|
|
104
|
-
ms:
|
|
105
|
-
easing:
|
|
106
|
-
cancelKey:
|
|
104
|
+
ms: _,
|
|
105
|
+
easing: n,
|
|
106
|
+
cancelKey: o(c, l),
|
|
107
107
|
interpolate: (e, t, n) => e + (t - e) * n,
|
|
108
108
|
onTick: (t) => {
|
|
109
|
-
e.colorOverrides.set(
|
|
109
|
+
e.colorOverrides.set(c, l, v(t * _));
|
|
110
110
|
},
|
|
111
111
|
onDone: () => {
|
|
112
|
-
e.colorOverrides.clear(
|
|
112
|
+
e.colorOverrides.clear(c, l), t.onDone?.();
|
|
113
113
|
}
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
|
-
function
|
|
116
|
+
function d(e, t = {}) {
|
|
117
117
|
let n = t.saturation ?? .8, r = t.lightness ?? .6, i = t.alpha ?? 1, a = [];
|
|
118
118
|
for (let t = 0; t < e; t++) {
|
|
119
|
-
let [o, s, c] =
|
|
119
|
+
let [o, s, c] = p(t * 360 / Math.max(1, e), n, r);
|
|
120
120
|
a.push(o, s, c, i);
|
|
121
121
|
}
|
|
122
122
|
return a;
|
|
123
123
|
}
|
|
124
|
-
function
|
|
124
|
+
function f(e, t, n, r, i = 1) {
|
|
125
125
|
let a = [];
|
|
126
126
|
for (let o = 0; o < e; o++) a.push(t, n, r, i);
|
|
127
127
|
return a;
|
|
128
128
|
}
|
|
129
|
-
function
|
|
129
|
+
function p(t, n, r) {
|
|
130
130
|
return e(t, n, r);
|
|
131
131
|
}
|
|
132
132
|
//#endregion
|
|
133
|
-
export {
|
|
133
|
+
export { l as cycleVertexColors, d as rainbowVertexColors, f as solidVertexColors, c as springVertexColors, u as staggerVertexColors, s as tweenVertexColors };
|
|
134
134
|
|
|
135
135
|
//# sourceMappingURL=colorHelpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"colorHelpers.js","names":["validateLengths","from","to","resolveInterpolator","opts","space","lerpColorArray","a","b","t","cancelKeyFor","id","channel","tweenVertexColors","interp","animator","springVertexColors","clamped","cycleVertexColors","direction","easing","resolveEasing","n","base","cycles","tMs","cycleFrac","easedFrac","phase","phaseInt","phaseFrac","out","i","aIdx","bIdx","blended","k","releaseKeepAlive","staggerVertexColors","originIndex","totalMs","perAnchorDelay","anchorMs","colorsAt","startMs","localT","elapsedMs","eased","fSlice","tSlice","rainbowVertexColors","saturation","lightness","alpha","r","g","hslToRgbUnit","solidVertexColors","hslToRgb","h","s","l"],"sources":["../../src/animation/colorHelpers.ts"],"sourcesContent":["import { resolveEasing } from '@weasel-js/geom';\nimport type {\n AnimationHandle,\n Animator,\n EasingSpec,\n SpringPresetName,\n} from './types';\nimport type { VertexColorChannel } from './colorRegistry';\nimport { lerpColorArray, type ColorSpace } from '@weasel-js/paint';\nimport { hslToRgb } from '../renderer/math/color';\n\n/**\n * Color interpolator: receives two flat RGBA float arrays (values in\n * 0..1, the renderer's color space — same shape as\n * `stroke.vertexColors`) and a normalized progress `t` in 0..1.\n * Returns the same-length blended array, also in 0..1 floats. Override\n * the default per-channel lerp via `TweenVertexColorsOptions.interpolate`\n * when a custom color-space or shaping is needed.\n */\nexport type ColorInterpolate = (\n from: readonly number[],\n to: readonly number[],\n t: number,\n) => number[];\n\n/** Options for `tweenVertexColors`. */\nexport interface TweenVertexColorsOptions {\n id: string;\n channel: VertexColorChannel;\n /** Per-anchor RGBA, flat, in 0..1 floats (matches the renderer's\n * `stroke.vertexColors` / `PathDrawCommand.vertexColors` color space).\n * Length must equal `from.length`. */\n to: readonly number[];\n /** Per-anchor RGBA, flat, in 0..1 floats. Length must equal `to.length`\n * and be a positive multiple of 4. */\n from: readonly number[];\n ms: number;\n easing?: EasingSpec;\n interpolation?: ColorSpace;\n interpolate?: ColorInterpolate;\n onDone?: () => void;\n}\n\nfunction validateLengths(from: readonly number[], to: readonly number[]): void {\n if (from.length !== to.length) {\n throw new Error(\n `vertex colors: length mismatch (from=${from.length}, to=${to.length})`,\n );\n }\n if (from.length === 0 || from.length % 4 !== 0) {\n throw new Error(\n `vertex colors: length ${from.length} must be a positive multiple of 4`,\n );\n }\n}\n\nfunction resolveInterpolator(\n opts: { interpolation?: ColorSpace; interpolate?: ColorInterpolate },\n): ColorInterpolate {\n if (opts.interpolate) return opts.interpolate;\n const space: ColorSpace = opts.interpolation ?? 'rgb';\n return (a, b, t) => lerpColorArray(a, b, t, space);\n}\n\nconst cancelKeyFor = (id: string, channel: VertexColorChannel): string =>\n `colors:${id}:${channel}`;\n\n/**\n * Animate one node's per-anchor colors from `from` to `to` over `ms`.\n *\n * The colors are published to the animator's `colorOverrides` registry rather\n * than written into the scene, so nothing is recorded in history and the\n * node's own data is untouched; the override is dropped when the animation\n * finishes. Starting another color animation on the same node and channel\n * cancels this one.\n */\nexport function tweenVertexColors(\n animator: Animator,\n opts: TweenVertexColorsOptions,\n): AnimationHandle {\n validateLengths(opts.from, opts.to);\n const interp = resolveInterpolator(opts);\n const { id, channel } = opts;\n return animator.tween<number>({\n from: 0,\n to: 1,\n ms: opts.ms,\n easing: opts.easing,\n cancelKey: cancelKeyFor(id, channel),\n interpolate: (a, b, t) => a + (b - a) * t,\n onTick: (t) => {\n animator.colorOverrides.set(id, channel, interp(opts.from, opts.to, t));\n },\n onDone: () => {\n animator.colorOverrides.clear(id, channel);\n opts.onDone?.();\n },\n });\n}\n\n/** Options for `springVertexColors`. */\nexport interface SpringVertexColorsOptions {\n id: string;\n channel: VertexColorChannel;\n /** Per-anchor RGBA in 0..1 floats. See `TweenVertexColorsOptions.to`. */\n to: readonly number[];\n /** Per-anchor RGBA in 0..1 floats. See `TweenVertexColorsOptions.from`. */\n from: readonly number[];\n preset?: SpringPresetName;\n stiffness?: number;\n damping?: number;\n mass?: number;\n interpolation?: ColorSpace;\n interpolate?: ColorInterpolate;\n onDone?: () => void;\n}\n\n/** `tweenVertexColors` driven by a spring instead of a duration. Same override\n * semantics. */\nexport function springVertexColors(\n animator: Animator,\n opts: SpringVertexColorsOptions,\n): AnimationHandle {\n validateLengths(opts.from, opts.to);\n const interp = resolveInterpolator(opts);\n const { id, channel } = opts;\n return animator.spring<number>({\n from: 0,\n to: 1,\n preset: opts.preset,\n stiffness: opts.stiffness,\n damping: opts.damping,\n mass: opts.mass,\n cancelKey: cancelKeyFor(id, channel),\n interpolate: (a, b, t) => a + (b - a) * t,\n onTick: (t) => {\n const clamped = Math.max(0, Math.min(1, t));\n animator.colorOverrides.set(id, channel, interp(opts.from, opts.to, clamped));\n },\n onDone: () => {\n animator.colorOverrides.clear(id, channel);\n opts.onDone?.();\n },\n });\n}\n\n/** Options for `cycleVertexColors`. */\nexport interface CycleVertexColorsOptions {\n id: string;\n channel: VertexColorChannel;\n msPerCycle: number;\n direction?: 1 | -1;\n easing?: EasingSpec;\n interpolation?: ColorSpace;\n /** Custom interpolator. Receives two RGBA arrays in 0..1 floats and a\n * progress `t` in 0..1; returns the blended array in 0..1 floats. */\n interpolate?: ColorInterpolate;\n}\n\n/** Removes an installed color cycle. Unlike the other color helpers this is\n * not an `AnimationHandle`, because a cycle has no progress to pause or\n * scale — it only ends. */\nexport interface CycleHandle {\n cancel(): void;\n}\n\n/** Register a function override that phase-rotates the base color array\n * along the path index. Returns a handle whose `cancel()` removes the\n * override.\n *\n * The override computes its value purely from `performance.now()`, so\n * there's no per-frame progress to register with the animator's tick\n * loop. To keep the loop alive (so `<SceneCanvas animator>`'s onTick\n * subscription continues firing redraws while the cycle is installed),\n * cycleVertexColors holds an `animator.keepAlive()` entry for the\n * lifetime of the cycle; `cancel()` releases it. */\nexport function cycleVertexColors(\n animator: Animator,\n opts: CycleVertexColorsOptions,\n): CycleHandle {\n const interp = resolveInterpolator(opts);\n const { id, channel } = opts;\n const direction = opts.direction ?? 1;\n const easing = resolveEasing(opts.easing);\n\n const override = (base: readonly number[], tMs: number): number[] => {\n const n = base.length / 4;\n if (n === 0) return base.slice();\n const raw = (tMs / opts.msPerCycle) * n * direction;\n const cycles = raw / n;\n const cycleFrac = cycles - Math.floor(cycles);\n const easedFrac = easing(cycleFrac);\n const easedRaw = (Math.floor(cycles) + easedFrac) * n;\n const phase = ((easedRaw % n) + n) % n;\n const phaseInt = Math.floor(phase);\n const phaseFrac = phase - phaseInt;\n\n const out = new Array<number>(base.length);\n for (let i = 0; i < n; i++) {\n const aIdx = ((i + phaseInt) % n) * 4;\n const bIdx = ((i + phaseInt + 1) % n) * 4;\n const a = [base[aIdx], base[aIdx + 1], base[aIdx + 2], base[aIdx + 3]];\n const b = [base[bIdx], base[bIdx + 1], base[bIdx + 2], base[bIdx + 3]];\n const blended = interp(a, b, phaseFrac);\n const k = i * 4;\n out[k] = blended[0];\n out[k + 1] = blended[1];\n out[k + 2] = blended[2];\n out[k + 3] = blended[3];\n }\n return out;\n };\n\n animator.colorOverrides.set(id, channel, override);\n // Cycle's \"value\" is a function of wall-clock time, not a tween-style\n // progress. We don't register a per-frame ticking animation, so without\n // an explicit keep-alive entry the animator's RAF loop would idle and\n // SceneCanvas's onTick redraw subscription would never fire. Keep the\n // loop running until cancel().\n const releaseKeepAlive = animator.keepAlive();\n\n return {\n cancel(): void {\n animator.colorOverrides.clear(id, channel);\n releaseKeepAlive();\n },\n };\n}\n\n/** Options for `staggerVertexColors`. `origin` picks which anchor the wave\n * starts from and `perAnchorDelay` how far behind each successive anchor\n * runs. */\nexport interface StaggerVertexColorsOptions {\n id: string;\n channel: VertexColorChannel;\n /** Per-anchor RGBA in 0..1 floats. See `TweenVertexColorsOptions.to`. */\n to: readonly number[];\n /** Per-anchor RGBA in 0..1 floats. See `TweenVertexColorsOptions.from`. */\n from: readonly number[];\n anchorMs: number;\n perAnchorDelay: number;\n origin?: 'first' | 'last' | number;\n easing?: EasingSpec;\n interpolation?: ColorSpace;\n interpolate?: ColorInterpolate;\n onDone?: () => void;\n}\n\n/** Run the same color change across a node's anchors as a wave, each anchor\n * starting later than the one before it. Same override semantics as\n * `tweenVertexColors`. */\nexport function staggerVertexColors(\n animator: Animator,\n opts: StaggerVertexColorsOptions,\n): AnimationHandle {\n validateLengths(opts.from, opts.to);\n const interp = resolveInterpolator(opts);\n const { id, channel, from, to, anchorMs, perAnchorDelay } = opts;\n const n = from.length / 4;\n const easing = resolveEasing(opts.easing);\n\n const originIndex =\n opts.origin === 'last' ? n - 1 :\n typeof opts.origin === 'number' ? Math.max(0, Math.min(n - 1, opts.origin)) :\n 0;\n\n const maxDistance = Math.max(originIndex, n - 1 - originIndex);\n const totalMs = maxDistance * perAnchorDelay + anchorMs;\n\n const colorsAt = (elapsedMs: number): number[] => {\n const out = new Array<number>(from.length);\n for (let i = 0; i < n; i++) {\n const distance = Math.abs(i - originIndex);\n const startMs = distance * perAnchorDelay;\n const localT = Math.max(0, Math.min(1, (elapsedMs - startMs) / anchorMs));\n const eased = easing(localT);\n const k = i * 4;\n const fSlice = [from[k], from[k + 1], from[k + 2], from[k + 3]];\n const tSlice = [to[k], to[k + 1], to[k + 2], to[k + 3]];\n const blended = interp(fSlice, tSlice, eased);\n out[k] = blended[0];\n out[k + 1] = blended[1];\n out[k + 2] = blended[2];\n out[k + 3] = blended[3];\n }\n return out;\n };\n\n // A plain array, not a function override: painters call those with their\n // own clock (`performance.now()`), not time since this stagger began.\n animator.colorOverrides.set(id, channel, colorsAt(0));\n\n return animator.tween<number>({\n from: 0,\n to: 1,\n ms: totalMs,\n easing: (t) => t,\n cancelKey: cancelKeyFor(id, channel),\n interpolate: (a, b, t) => a + (b - a) * t,\n onTick: (t) => {\n animator.colorOverrides.set(id, channel, colorsAt(t * totalMs));\n },\n onDone: () => {\n animator.colorOverrides.clear(id, channel);\n opts.onDone?.();\n },\n });\n}\n\n// ─── Color-array constructors ───────────────────────────────────────\n//\n// Helpers that build the per-anchor RGBA arrays the tween/cycle/stagger\n// helpers above (and `stroke.vertexColors` on draw commands) consume.\n// Values are in the 0..1 range the renderer expects — emitting bytes\n// (0..255) clamps every channel to 1.0 on the GPU and renders white.\n\n/** Build an n-anchor rainbow RGBA array sweeping the hue wheel. The\n * output is suitable to pass directly into `stroke.vertexColors`,\n * `tweenVertexColors({ from: rainbowVertexColors(n) })`, or any\n * other API that wants per-anchor color floats. */\nexport function rainbowVertexColors(\n n: number,\n opts: { saturation?: number; lightness?: number; alpha?: number } = {},\n): number[] {\n const saturation = opts.saturation ?? 0.8;\n const lightness = opts.lightness ?? 0.6;\n const alpha = opts.alpha ?? 1;\n const out: number[] = [];\n for (let i = 0; i < n; i++) {\n const h = (i * 360) / Math.max(1, n);\n const [r, g, b] = hslToRgbUnit(h, saturation, lightness);\n out.push(r, g, b, alpha);\n }\n return out;\n}\n\n/** Build an n-anchor solid RGBA array. Shorthand for\n * `[r, g, b, a, r, g, b, a, …]` — the array a `tweenVertexColors`\n * `to`-target typically needs. */\nexport function solidVertexColors(\n n: number,\n r: number,\n g: number,\n b: number,\n a = 1,\n): number[] {\n const out: number[] = [];\n for (let i = 0; i < n; i++) out.push(r, g, b, a);\n return out;\n}\n\n/** HSL → RGB in 0..1 (hue in degrees). Delegates to the single internal\n * implementation in `renderer/math/color.ts` so there's only one HSL→RGB\n * formula in the package. Kept local so consumers building hue-cycled\n * color arrays via `huesweepVertexColors` don't have to roll their own. */\nfunction hslToRgbUnit(h: number, s: number, l: number): [number, number, number] {\n return hslToRgb(h, s, l);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"colorHelpers.js","names":["validateLengths","from","to","resolveInterpolator","opts","space","lerpColorArray","a","b","t","cancelKeyFor","id","channel","tweenVertexColors","interp","animator","springVertexColors","clamped","cycleVertexColors","direction","easing","resolveEasing","n","base","cycles","tMs","cycleFrac","easedFrac","phase","phaseInt","phaseFrac","out","i","aIdx","bIdx","blended","k","releaseKeepAlive","staggerVertexColors","originIndex","totalMs","perAnchorDelay","anchorMs","colorsAt","startMs","localT","elapsedMs","eased","fSlice","tSlice","linear","rainbowVertexColors","saturation","lightness","alpha","r","g","hslToRgbUnit","solidVertexColors","hslToRgb","h","s","l"],"sources":["../../src/animation/colorHelpers.ts"],"sourcesContent":["import { linear, resolveEasing } from '@weasel-js/geom';\nimport type {\n AnimationHandle,\n Animator,\n EasingSpec,\n SpringPresetName,\n} from './types';\nimport type { VertexColorChannel } from './colorRegistry';\nimport { lerpColorArray, type ColorSpace } from '@weasel-js/paint';\nimport { hslToRgb } from '../renderer/math/color';\n\n/**\n * Color interpolator: receives two flat RGBA float arrays (values in\n * 0..1, the renderer's color space — same shape as\n * `stroke.vertexColors`) and a normalized progress `t` in 0..1.\n * Returns the same-length blended array, also in 0..1 floats. Override\n * the default per-channel lerp via `TweenVertexColorsOptions.interpolate`\n * when a custom color-space or shaping is needed.\n */\nexport type ColorInterpolate = (\n from: readonly number[],\n to: readonly number[],\n t: number,\n) => number[];\n\n/** Options for `tweenVertexColors`. */\nexport interface TweenVertexColorsOptions {\n id: string;\n channel: VertexColorChannel;\n /** Per-anchor RGBA, flat, in 0..1 floats (matches the renderer's\n * `stroke.vertexColors` / `PathDrawCommand.vertexColors` color space).\n * Length must equal `from.length`. */\n to: readonly number[];\n /** Per-anchor RGBA, flat, in 0..1 floats. Length must equal `to.length`\n * and be a positive multiple of 4. */\n from: readonly number[];\n ms: number;\n easing?: EasingSpec;\n interpolation?: ColorSpace;\n interpolate?: ColorInterpolate;\n onDone?: () => void;\n}\n\nfunction validateLengths(from: readonly number[], to: readonly number[]): void {\n if (from.length !== to.length) {\n throw new Error(\n `vertex colors: length mismatch (from=${from.length}, to=${to.length})`,\n );\n }\n if (from.length === 0 || from.length % 4 !== 0) {\n throw new Error(\n `vertex colors: length ${from.length} must be a positive multiple of 4`,\n );\n }\n}\n\nfunction resolveInterpolator(\n opts: { interpolation?: ColorSpace; interpolate?: ColorInterpolate },\n): ColorInterpolate {\n if (opts.interpolate) return opts.interpolate;\n const space: ColorSpace = opts.interpolation ?? 'rgb';\n return (a, b, t) => lerpColorArray(a, b, t, space);\n}\n\nconst cancelKeyFor = (id: string, channel: VertexColorChannel): string =>\n `colors:${id}:${channel}`;\n\n/**\n * Animate one node's per-anchor colors from `from` to `to` over `ms`.\n *\n * The colors are published to the animator's `colorOverrides` registry rather\n * than written into the scene, so nothing is recorded in history and the\n * node's own data is untouched; the override is dropped when the animation\n * finishes. Starting another color animation on the same node and channel\n * cancels this one.\n */\nexport function tweenVertexColors(\n animator: Animator,\n opts: TweenVertexColorsOptions,\n): AnimationHandle {\n validateLengths(opts.from, opts.to);\n const interp = resolveInterpolator(opts);\n const { id, channel } = opts;\n return animator.tween<number>({\n from: 0,\n to: 1,\n ms: opts.ms,\n easing: opts.easing,\n cancelKey: cancelKeyFor(id, channel),\n interpolate: (a, b, t) => a + (b - a) * t,\n onTick: (t) => {\n animator.colorOverrides.set(id, channel, interp(opts.from, opts.to, t));\n },\n onDone: () => {\n animator.colorOverrides.clear(id, channel);\n opts.onDone?.();\n },\n });\n}\n\n/** Options for `springVertexColors`. */\nexport interface SpringVertexColorsOptions {\n id: string;\n channel: VertexColorChannel;\n /** Per-anchor RGBA in 0..1 floats. See `TweenVertexColorsOptions.to`. */\n to: readonly number[];\n /** Per-anchor RGBA in 0..1 floats. See `TweenVertexColorsOptions.from`. */\n from: readonly number[];\n preset?: SpringPresetName;\n stiffness?: number;\n damping?: number;\n mass?: number;\n interpolation?: ColorSpace;\n interpolate?: ColorInterpolate;\n onDone?: () => void;\n}\n\n/** `tweenVertexColors` driven by a spring instead of a duration. Same override\n * semantics. */\nexport function springVertexColors(\n animator: Animator,\n opts: SpringVertexColorsOptions,\n): AnimationHandle {\n validateLengths(opts.from, opts.to);\n const interp = resolveInterpolator(opts);\n const { id, channel } = opts;\n return animator.spring<number>({\n from: 0,\n to: 1,\n preset: opts.preset,\n stiffness: opts.stiffness,\n damping: opts.damping,\n mass: opts.mass,\n cancelKey: cancelKeyFor(id, channel),\n interpolate: (a, b, t) => a + (b - a) * t,\n onTick: (t) => {\n const clamped = Math.max(0, Math.min(1, t));\n animator.colorOverrides.set(id, channel, interp(opts.from, opts.to, clamped));\n },\n onDone: () => {\n animator.colorOverrides.clear(id, channel);\n opts.onDone?.();\n },\n });\n}\n\n/** Options for `cycleVertexColors`. */\nexport interface CycleVertexColorsOptions {\n id: string;\n channel: VertexColorChannel;\n msPerCycle: number;\n direction?: 1 | -1;\n easing?: EasingSpec;\n interpolation?: ColorSpace;\n /** Custom interpolator. Receives two RGBA arrays in 0..1 floats and a\n * progress `t` in 0..1; returns the blended array in 0..1 floats. */\n interpolate?: ColorInterpolate;\n}\n\n/** Removes an installed color cycle. Unlike the other color helpers this is\n * not an `AnimationHandle`, because a cycle has no progress to pause or\n * scale — it only ends. */\nexport interface CycleHandle {\n cancel(): void;\n}\n\n/** Register a function override that phase-rotates the base color array\n * along the path index. Returns a handle whose `cancel()` removes the\n * override.\n *\n * The override computes its value purely from `performance.now()`, so\n * there's no per-frame progress to register with the animator's tick\n * loop. To keep the loop alive (so `<SceneCanvas animator>`'s onTick\n * subscription continues firing redraws while the cycle is installed),\n * cycleVertexColors holds an `animator.keepAlive()` entry for the\n * lifetime of the cycle; `cancel()` releases it. */\nexport function cycleVertexColors(\n animator: Animator,\n opts: CycleVertexColorsOptions,\n): CycleHandle {\n const interp = resolveInterpolator(opts);\n const { id, channel } = opts;\n const direction = opts.direction ?? 1;\n const easing = resolveEasing(opts.easing);\n\n const override = (base: readonly number[], tMs: number): number[] => {\n const n = base.length / 4;\n if (n === 0) return base.slice();\n const raw = (tMs / opts.msPerCycle) * n * direction;\n const cycles = raw / n;\n const cycleFrac = cycles - Math.floor(cycles);\n const easedFrac = easing(cycleFrac);\n const easedRaw = (Math.floor(cycles) + easedFrac) * n;\n const phase = ((easedRaw % n) + n) % n;\n const phaseInt = Math.floor(phase);\n const phaseFrac = phase - phaseInt;\n\n const out = new Array<number>(base.length);\n for (let i = 0; i < n; i++) {\n const aIdx = ((i + phaseInt) % n) * 4;\n const bIdx = ((i + phaseInt + 1) % n) * 4;\n const a = [base[aIdx], base[aIdx + 1], base[aIdx + 2], base[aIdx + 3]];\n const b = [base[bIdx], base[bIdx + 1], base[bIdx + 2], base[bIdx + 3]];\n const blended = interp(a, b, phaseFrac);\n const k = i * 4;\n out[k] = blended[0];\n out[k + 1] = blended[1];\n out[k + 2] = blended[2];\n out[k + 3] = blended[3];\n }\n return out;\n };\n\n animator.colorOverrides.set(id, channel, override);\n // Cycle's \"value\" is a function of wall-clock time, not a tween-style\n // progress. We don't register a per-frame ticking animation, so without\n // an explicit keep-alive entry the animator's RAF loop would idle and\n // SceneCanvas's onTick redraw subscription would never fire. Keep the\n // loop running until cancel().\n const releaseKeepAlive = animator.keepAlive();\n\n return {\n cancel(): void {\n animator.colorOverrides.clear(id, channel);\n releaseKeepAlive();\n },\n };\n}\n\n/** Options for `staggerVertexColors`. `origin` picks which anchor the wave\n * starts from and `perAnchorDelay` how far behind each successive anchor\n * runs. */\nexport interface StaggerVertexColorsOptions {\n id: string;\n channel: VertexColorChannel;\n /** Per-anchor RGBA in 0..1 floats. See `TweenVertexColorsOptions.to`. */\n to: readonly number[];\n /** Per-anchor RGBA in 0..1 floats. See `TweenVertexColorsOptions.from`. */\n from: readonly number[];\n anchorMs: number;\n perAnchorDelay: number;\n origin?: 'first' | 'last' | number;\n easing?: EasingSpec;\n interpolation?: ColorSpace;\n interpolate?: ColorInterpolate;\n onDone?: () => void;\n}\n\n/** Run the same color change across a node's anchors as a wave, each anchor\n * starting later than the one before it. Same override semantics as\n * `tweenVertexColors`. */\nexport function staggerVertexColors(\n animator: Animator,\n opts: StaggerVertexColorsOptions,\n): AnimationHandle {\n validateLengths(opts.from, opts.to);\n const interp = resolveInterpolator(opts);\n const { id, channel, from, to, anchorMs, perAnchorDelay } = opts;\n const n = from.length / 4;\n const easing = resolveEasing(opts.easing);\n\n const originIndex =\n opts.origin === 'last' ? n - 1 :\n typeof opts.origin === 'number' ? Math.max(0, Math.min(n - 1, opts.origin)) :\n 0;\n\n const maxDistance = Math.max(originIndex, n - 1 - originIndex);\n const totalMs = maxDistance * perAnchorDelay + anchorMs;\n\n const colorsAt = (elapsedMs: number): number[] => {\n const out = new Array<number>(from.length);\n for (let i = 0; i < n; i++) {\n const distance = Math.abs(i - originIndex);\n const startMs = distance * perAnchorDelay;\n const localT = Math.max(0, Math.min(1, (elapsedMs - startMs) / anchorMs));\n const eased = easing(localT);\n const k = i * 4;\n const fSlice = [from[k], from[k + 1], from[k + 2], from[k + 3]];\n const tSlice = [to[k], to[k + 1], to[k + 2], to[k + 3]];\n const blended = interp(fSlice, tSlice, eased);\n out[k] = blended[0];\n out[k + 1] = blended[1];\n out[k + 2] = blended[2];\n out[k + 3] = blended[3];\n }\n return out;\n };\n\n // A plain array, not a function override: painters call those with their\n // own clock (`performance.now()`), not time since this stagger began.\n animator.colorOverrides.set(id, channel, colorsAt(0));\n\n return animator.tween<number>({\n from: 0,\n to: 1,\n ms: totalMs,\n easing: linear,\n cancelKey: cancelKeyFor(id, channel),\n interpolate: (a, b, t) => a + (b - a) * t,\n onTick: (t) => {\n animator.colorOverrides.set(id, channel, colorsAt(t * totalMs));\n },\n onDone: () => {\n animator.colorOverrides.clear(id, channel);\n opts.onDone?.();\n },\n });\n}\n\n// ─── Color-array constructors ───────────────────────────────────────\n//\n// Helpers that build the per-anchor RGBA arrays the tween/cycle/stagger\n// helpers above (and `stroke.vertexColors` on draw commands) consume.\n// Values are in the 0..1 range the renderer expects — emitting bytes\n// (0..255) clamps every channel to 1.0 on the GPU and renders white.\n\n/** Build an n-anchor rainbow RGBA array sweeping the hue wheel. The\n * output is suitable to pass directly into `stroke.vertexColors`,\n * `tweenVertexColors({ from: rainbowVertexColors(n) })`, or any\n * other API that wants per-anchor color floats. */\nexport function rainbowVertexColors(\n n: number,\n opts: { saturation?: number; lightness?: number; alpha?: number } = {},\n): number[] {\n const saturation = opts.saturation ?? 0.8;\n const lightness = opts.lightness ?? 0.6;\n const alpha = opts.alpha ?? 1;\n const out: number[] = [];\n for (let i = 0; i < n; i++) {\n const h = (i * 360) / Math.max(1, n);\n const [r, g, b] = hslToRgbUnit(h, saturation, lightness);\n out.push(r, g, b, alpha);\n }\n return out;\n}\n\n/** Build an n-anchor solid RGBA array. Shorthand for\n * `[r, g, b, a, r, g, b, a, …]` — the array a `tweenVertexColors`\n * `to`-target typically needs. */\nexport function solidVertexColors(\n n: number,\n r: number,\n g: number,\n b: number,\n a = 1,\n): number[] {\n const out: number[] = [];\n for (let i = 0; i < n; i++) out.push(r, g, b, a);\n return out;\n}\n\n/** HSL → RGB in 0..1 (hue in degrees). Delegates to the single internal\n * implementation in `renderer/math/color.ts` so there's only one HSL→RGB\n * formula in the package. Kept local so consumers building hue-cycled\n * color arrays via `huesweepVertexColors` don't have to roll their own. */\nfunction hslToRgbUnit(h: number, s: number, l: number): [number, number, number] {\n return hslToRgb(h, s, l);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isPlainObject as e } from "../../core/isPlainObject.js";
|
|
2
|
+
//#region src/animation/engine/axes.ts
|
|
3
|
+
function t(t) {
|
|
4
|
+
if (typeof t == "number") return {
|
|
5
|
+
count: 1,
|
|
6
|
+
shape: "number",
|
|
7
|
+
to: (e) => [e],
|
|
8
|
+
from: (e, t = 0) => e[t]
|
|
9
|
+
};
|
|
10
|
+
if (Array.isArray(t)) return t.every((e) => typeof e == "number") ? {
|
|
11
|
+
count: t.length,
|
|
12
|
+
shape: `array:${t.length}`,
|
|
13
|
+
to: (e) => e.slice(),
|
|
14
|
+
from: (e, n = 0) => {
|
|
15
|
+
let r = Array(t.length);
|
|
16
|
+
for (let t = 0; t < r.length; t++) r[t] = e[n + t];
|
|
17
|
+
return r;
|
|
18
|
+
}
|
|
19
|
+
} : null;
|
|
20
|
+
if (!e(t)) return null;
|
|
21
|
+
let n = Object.keys(t).sort();
|
|
22
|
+
return n.length === 0 || !n.every((e) => typeof t[e] == "number") ? null : {
|
|
23
|
+
count: n.length,
|
|
24
|
+
shape: `object:${JSON.stringify(n)}`,
|
|
25
|
+
to: (e) => n.map((t) => e[t]),
|
|
26
|
+
from: (e, t = 0) => {
|
|
27
|
+
let r = {};
|
|
28
|
+
for (let i = 0; i < n.length; i++) r[n[i]] = e[t + i];
|
|
29
|
+
return r;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { t as axesOf };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=axes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axes.js","names":["axesOf","sample","v","a","at","x","out","i","isPlainObject","keys","k"],"sources":["../../../src/animation/engine/axes.ts"],"sourcesContent":["import { isPlainObject } from '../../core/isPlainObject';\n\n/** How a value maps to the flat numbers blits moves, and back. */\nexport interface Axes<T> {\n count: number;\n /** Equal for two values exactly when their axes line up one for one. */\n shape: string;\n to(v: T): number[];\n /** The value whose `count` axes start at `at` in `a`. */\n from(a: ArrayLike<number>, at?: number): T;\n}\n\n/** The axes of `sample`'s shape: a number, a plain array of numbers, or a plain object whose\n * fields are all numbers. Null for any other shape, which blits cannot move. */\nexport function axesOf<T>(sample: T): Axes<T> | null {\n if (typeof sample === 'number') {\n return { count: 1, shape: 'number', to: (v) => [v as number], from: (a, at = 0) => a[at] as T };\n }\n if (Array.isArray(sample)) {\n if (!sample.every((x) => typeof x === 'number')) return null;\n return {\n count: sample.length,\n shape: `array:${sample.length}`,\n to: (v) => (v as number[]).slice(),\n from: (a, at = 0) => {\n const out = new Array<number>(sample.length);\n for (let i = 0; i < out.length; i++) out[i] = a[at + i]!;\n return out as T;\n },\n };\n }\n if (!isPlainObject(sample)) return null;\n const keys = Object.keys(sample).sort();\n if (keys.length === 0 || !keys.every((k) => typeof sample[k] === 'number')) return null;\n return {\n count: keys.length,\n shape: `object:${JSON.stringify(keys)}`,\n to: (v) => keys.map((k) => (v as Record<string, number>)[k]!),\n from: (a, at = 0) => {\n const out: Record<string, number> = {};\n for (let i = 0; i < keys.length; i++) out[keys[i]!] = a[at + i]!;\n return out as T;\n },\n };\n}\n"],"mappings":";;AAcA,SAAgBA,EAAU,GAA2B;CACnD,IAAI,OAAOC,KAAW,UACpB,OAAO;EAAE,OAAO;EAAG,OAAO;EAAU,KAAK,MAAM,CAACC,CAAW;EAAG,OAAO,GAAG,IAAK,MAAMC,EAAEC;CAAS;CAEhG,IAAI,MAAM,QAAQH,CAAM,GAEtB,OADKA,EAAO,OAAO,MAAM,OAAOI,KAAM,QAAQ,IACvC;EACL,OAAOJ,EAAO;EACd,OAAO,SAASA,EAAO;EACvB,KAAK,MAAOC,EAAe,MAAM;EACjC,OAAO,GAAG,IAAK,MAAM;GACnB,IAAMI,IAAU,MAAcL,EAAO,MAAM;GAC3C,KAAK,IAAIM,IAAI,GAAGA,IAAID,EAAI,QAAQ,KAAK,EAAIC,KAAKJ,EAAEC,IAAKG;GACrD,OAAOD;EACT;CACF,IAVwD;CAY1D,IAAI,CAACE,EAAcP,CAAM,GAAG,OAAO;CACnC,IAAMQ,IAAO,OAAO,KAAKR,CAAM,CAAC,CAAC,KAAK;CAEtC,OADIQ,EAAK,WAAW,KAAK,CAACA,EAAK,OAAO,MAAM,OAAOR,EAAOS,MAAO,QAAQ,IAAU,OAC5E;EACL,OAAOD,EAAK;EACZ,OAAO,UAAU,KAAK,UAAUA,CAAI;EACpC,KAAK,MAAMA,EAAK,KAAK,MAAOP,EAA6BQ,EAAG;EAC5D,OAAO,GAAG,IAAK,MAAM;GACnB,IAAMJ,IAA8B,CAAC;GACrC,KAAK,IAAI,IAAI,GAAG,IAAIG,EAAK,QAAQ,KAAK,EAAIA,EAAK,MAAON,EAAEC,IAAK;GAC7D,OAAOE;EACT;CACF;AACF"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { glide as e, kit as t, mix as n, spring as r, sum as i, tween as a, vec as o } from "@msb235/blits";
|
|
2
|
+
//#region src/animation/engine/codec.ts
|
|
3
|
+
function s() {
|
|
4
|
+
let s = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Map(), l = [], u = 0, d = (e) => {
|
|
5
|
+
let r = s.get(e);
|
|
6
|
+
if (!r) {
|
|
7
|
+
let a = n(t({ v: o(e, i()) }));
|
|
8
|
+
a.sync(u), r = {
|
|
9
|
+
axes: e,
|
|
10
|
+
mix: a,
|
|
11
|
+
ids: [],
|
|
12
|
+
subs: [],
|
|
13
|
+
pending: /* @__PURE__ */ new Set(),
|
|
14
|
+
dirty: !1,
|
|
15
|
+
idle: 0,
|
|
16
|
+
cols: { v: /* @__PURE__ */ new Float64Array() }
|
|
17
|
+
}, s.set(e, r);
|
|
18
|
+
}
|
|
19
|
+
return r;
|
|
20
|
+
}, f = (e, t) => {
|
|
21
|
+
e.slot = -1, e.cols = e.held, e.at = 0, e.lane.pending.add(t), e.lane.dirty = !0;
|
|
22
|
+
}, p = (e, t) => {
|
|
23
|
+
let n = e.lane;
|
|
24
|
+
if (n.dirty = !0, e.slot < 0) {
|
|
25
|
+
n.pending.delete(t);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let r = n.ids.pop(), i = n.subs.pop();
|
|
29
|
+
if (r === t) return;
|
|
30
|
+
let a = n.axes, o = n.ids.length * a;
|
|
31
|
+
n.cols.v.copyWithin(e.slot * a, o, o + a), n.ids[e.slot] = r, n.subs[e.slot] = i, i.slot = e.slot, i.at = e.slot * a;
|
|
32
|
+
}, m = (e) => {
|
|
33
|
+
let t = e.ids.slice(), n = e.subs;
|
|
34
|
+
for (let r of e.pending) {
|
|
35
|
+
let i = c.get(r);
|
|
36
|
+
i.slot = t.length, i.at = i.slot * e.axes, i.cols = e.cols.v, t.push(r), n.push(i);
|
|
37
|
+
}
|
|
38
|
+
if (e.pending.clear(), e.ids = t, e.cols.v.length !== t.length * e.axes) {
|
|
39
|
+
e.cols = { v: new Float64Array(t.length * e.axes) };
|
|
40
|
+
for (let t of n) t.cols = e.cols.v;
|
|
41
|
+
}
|
|
42
|
+
e.dirty = !1;
|
|
43
|
+
}, h = (t, n, i) => {
|
|
44
|
+
let a = i.to == null ? e("v", {
|
|
45
|
+
from: i.from,
|
|
46
|
+
ms: 1e3 * i.mass / i.damping
|
|
47
|
+
}) : r("v", {
|
|
48
|
+
from: i.from,
|
|
49
|
+
to: i.from,
|
|
50
|
+
stiffness: i.stiffness,
|
|
51
|
+
damping: i.damping,
|
|
52
|
+
mass: i.mass
|
|
53
|
+
}), o = t.mix.cue({
|
|
54
|
+
patch: a,
|
|
55
|
+
subjects: [n]
|
|
56
|
+
});
|
|
57
|
+
return i.to && a.to(n, i.to, 0), a.push(n, i.velocity, 0), {
|
|
58
|
+
handle: o,
|
|
59
|
+
patch: a
|
|
60
|
+
};
|
|
61
|
+
}, g = (e) => {
|
|
62
|
+
let t = c.get(e);
|
|
63
|
+
if (!t) throw Error(`codec: no subject ${e}`);
|
|
64
|
+
return t;
|
|
65
|
+
}, _ = (e, t) => e.patch.read(t) ?? {
|
|
66
|
+
value: e.spring.from.slice(),
|
|
67
|
+
velocity: e.spring.velocity.slice()
|
|
68
|
+
};
|
|
69
|
+
function v(e) {
|
|
70
|
+
let t = c.get(e);
|
|
71
|
+
t && (t.queued ? t.queued = void 0 : (t.handle.fade({ over: 0 }), t.lane.mix.drop(e)), p(t, e), c.delete(e));
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
frame(e) {
|
|
75
|
+
for (let e of l) {
|
|
76
|
+
let t = e.queued;
|
|
77
|
+
if (!t) continue;
|
|
78
|
+
e.queued = void 0;
|
|
79
|
+
let n = a("v", {
|
|
80
|
+
from: t.from.slice(),
|
|
81
|
+
to: t.from.slice(),
|
|
82
|
+
ms: t.ms,
|
|
83
|
+
ease: t.ease
|
|
84
|
+
});
|
|
85
|
+
e.handle = e.lane.mix.cue({
|
|
86
|
+
patch: n,
|
|
87
|
+
subjects: [t.id]
|
|
88
|
+
}), e.patch = n, n.to(t.id, t.to.slice(), 0), e.queuedRate !== void 0 && (e.handle.rate = e.queuedRate);
|
|
89
|
+
}
|
|
90
|
+
l.length = 0, u += e;
|
|
91
|
+
for (let e of s.values()) {
|
|
92
|
+
if (e.dirty && m(e), e.ids.length === 0 && ++e.idle >= 2) {
|
|
93
|
+
s.delete(e.axes);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
e.mix.sync(u), e.ids.length !== 0 && (e.idle = 0, e.mix.pull(e.ids, e.cols));
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
tween(e) {
|
|
100
|
+
if (e.to.length !== e.from.length) throw Error(`codec: tween ${e.id} goes from ${e.from.length} axes to ${e.to.length}`);
|
|
101
|
+
let t = d(e.from.length), n = Float64Array.from(e.from), r = {
|
|
102
|
+
kind: "tween",
|
|
103
|
+
lane: t,
|
|
104
|
+
slot: -1,
|
|
105
|
+
held: n,
|
|
106
|
+
cols: n,
|
|
107
|
+
at: 0,
|
|
108
|
+
queued: e
|
|
109
|
+
};
|
|
110
|
+
return c.set(e.id, r), f(r, e.id), l.push(r), r;
|
|
111
|
+
},
|
|
112
|
+
spring(e) {
|
|
113
|
+
let t = {
|
|
114
|
+
...e,
|
|
115
|
+
from: e.from.slice(),
|
|
116
|
+
to: e.to && e.to.slice(),
|
|
117
|
+
velocity: e.velocity.slice()
|
|
118
|
+
}, n = d(t.axes), { handle: r, patch: i } = h(n, e.id, t), a = Float64Array.from(t.from), o = {
|
|
119
|
+
kind: "spring",
|
|
120
|
+
lane: n,
|
|
121
|
+
handle: r,
|
|
122
|
+
patch: i,
|
|
123
|
+
spring: t,
|
|
124
|
+
slot: -1,
|
|
125
|
+
held: a,
|
|
126
|
+
cols: a,
|
|
127
|
+
at: 0
|
|
128
|
+
};
|
|
129
|
+
c.set(e.id, o), f(o, e.id);
|
|
130
|
+
let s = e.id;
|
|
131
|
+
return o.motion = () => _(o, s), o;
|
|
132
|
+
},
|
|
133
|
+
retarget(e, t) {
|
|
134
|
+
let n = g(e);
|
|
135
|
+
if (n.kind === "tween") throw Error(`codec: subject ${e} is a tween; tweens are not retargeted`);
|
|
136
|
+
let r = n.spring;
|
|
137
|
+
if (t == null == (r.to == null)) {
|
|
138
|
+
if (!t) return;
|
|
139
|
+
r.to = t.slice(), n.patch.to(e, r.to);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let i = _(n, e), a = n.handle.rate, o = {
|
|
143
|
+
...r,
|
|
144
|
+
to: t && t.slice(),
|
|
145
|
+
from: i.value,
|
|
146
|
+
velocity: i.velocity
|
|
147
|
+
};
|
|
148
|
+
n.handle.fade({ over: 0 });
|
|
149
|
+
let { handle: s, patch: c } = h(n.lane, e, o);
|
|
150
|
+
s.rate = a, n.handle = s, n.patch = c, n.spring = o;
|
|
151
|
+
},
|
|
152
|
+
push(e, t) {
|
|
153
|
+
let n = g(e);
|
|
154
|
+
n.spring && !n.patch.read(e) && (n.spring.velocity = t.slice()), n.patch.push?.(e, t);
|
|
155
|
+
},
|
|
156
|
+
rate(e, t) {
|
|
157
|
+
let n = g(e);
|
|
158
|
+
n.queued ? n.queuedRate = t : n.handle.rate = t;
|
|
159
|
+
},
|
|
160
|
+
stop: v,
|
|
161
|
+
has: (e) => c.has(e),
|
|
162
|
+
voiceCount() {
|
|
163
|
+
return c.size;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
export { s as createCodec };
|
|
169
|
+
|
|
170
|
+
//# sourceMappingURL=codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.js","names":["springPatch","tweenPatch","createCodec","lanes","subjects","queue","time","laneOf","l","axes","m","mix","kit","vec","sum","join","s","id","depart","last","moved","n","from","rebuild","ids","subs","springVoice","patch","sp","glide","spring","handle","get","current","stop","sub","q","tween","dtMs","held","to","now","rate","next","velocity"],"sources":["../../../src/animation/engine/codec.ts"],"sourcesContent":["import {\n glide, kit, mix, spring as springPatch, sum, tween as tweenPatch, vec,\n type Handle, type Mix, type Motion, type Patch,\n} from '@msb235/blits';\n\nexport interface TweenStart {\n id: number; ms: number; ease: (u: number) => number;\n /** Endpoints as axes. A tween whose caller blends its own values passes [0] and [1]. */\n from: number[]; to: number[];\n}\nexport interface SpringStart {\n id: number; axes: number;\n from: number[]; to: number[] | null; velocity: number[];\n stiffness: number; damping: number; mass: number;\n}\n/** One subject's value, kept current in place by the codec. Valid until the subject stops. */\nexport interface Reader {\n /** The array holding the subject's axes as the last `frame` read them, from `at` on. It is the\n * codec's own, shared with other subjects and valid until the next `frame`. */\n readonly cols: Float64Array;\n /** Where the subject's axes start in `cols`. */\n readonly at: number;\n}\nexport interface SpringReader extends Reader {\n /** Position and velocity at the last `frame`. */\n motion(): { value: number[]; velocity: number[] };\n}\nexport interface Codec {\n /** Advances the codec's clock by `dtMs` of animator time, then reads every subject. */\n frame(dtMs: number): void;\n tween(s: TweenStart): Reader;\n spring(s: SpringStart): SpringReader;\n retarget(id: number, to: number[] | null): void;\n push(id: number, velocity: number[]): void;\n /** Plays `id` at rate `rate` from where it is, without a jump. */\n rate(id: number, rate: number): void;\n stop(id: number): void;\n /** Whether `id` is one of the codec's subjects. */\n has(id: number): boolean;\n /** How many voices the codec is running, one per subject; for tests and diagnostics. */\n voiceCount(): number;\n}\n\ntype Out = { v: number[] };\n\n/** What the codec calls on a motion patch: a tween has no `push`, a glide no `to`. */\ninterface Moves {\n to?(id: number, target: number[], at?: number): void;\n read(id: number, at?: number): Motion<number[]> | undefined;\n push?(id: number, velocity: number[], at?: number): void;\n}\n\n/** One mix per axis count, since a kit's channel width is fixed. */\ninterface Lane {\n axes: number;\n mix: Mix<number, Out>;\n /** The subjects `pull` reads, by slot. Joins wait in `pending` until the next `frame`. */\n ids: number[];\n /** Each slot's subject, beside `ids`. */\n subs: Subject[];\n pending: Set<number>;\n dirty: boolean;\n /** Frames in a row it has had no subjects at; it retires at the second. */\n idle: number;\n cols: { v: Float64Array };\n}\n\ninterface Subject extends Reader {\n cols: Float64Array;\n at: number;\n kind: 'tween' | 'spring';\n lane: Lane;\n handle: Handle<number>;\n patch: Moves;\n /** The codec's own copy, kept at the subject's motion as of its last re-cue. */\n spring?: SpringStart;\n /** Index into the lane's `ids`; -1 while no frame has read it. */\n slot: number;\n /** What `cols` points at while `slot` is -1. */\n held: Float64Array;\n /** A tween's start, until the next `frame` cues its voice; `handle` and `patch` exist only after. */\n queued?: TweenStart;\n /** A rate asked for while queued. */\n queuedRate?: number;\n motion?(): { value: number[]; velocity: number[] };\n}\n\nexport function createCodec(): Codec {\n const lanes = new Map<number, Lane>();\n const subjects = new Map<number, Subject>();\n // Tweens are cued together at the next frame, not one by one as they start: a burst of starts then\n // keeps the caller's objects together rather than each a voice apart, which the frame loop reads.\n const queue: Subject[] = [];\n let time = 0;\n\n const laneOf = (axes: number): Lane => {\n let l = lanes.get(axes);\n if (!l) {\n const m = mix<number, Out>(kit<Out>({ v: vec(axes, sum()) }));\n m.sync(time);\n l = { axes, mix: m, ids: [], subs: [], pending: new Set(), dirty: false, idle: 0, cols: { v: new Float64Array(0) } };\n lanes.set(axes, l);\n }\n return l;\n };\n const join = (s: Subject, id: number): void => {\n s.slot = -1;\n s.cols = s.held;\n s.at = 0;\n s.lane.pending.add(id);\n s.lane.dirty = true;\n };\n const depart = (s: Subject, id: number): void => {\n const l = s.lane;\n l.dirty = true;\n if (s.slot < 0) { l.pending.delete(id); return; }\n const last = l.ids.pop()!;\n const moved = l.subs.pop()!;\n if (last === id) return;\n const n = l.axes;\n const from = l.ids.length * n;\n l.cols.v.copyWithin(s.slot * n, from, from + n);\n l.ids[s.slot] = last;\n l.subs[s.slot] = moved;\n moved.slot = s.slot;\n moved.at = s.slot * n;\n };\n const rebuild = (l: Lane): void => {\n const ids = l.ids.slice();\n const subs = l.subs;\n for (const id of l.pending) {\n const s = subjects.get(id)!;\n s.slot = ids.length;\n s.at = s.slot * l.axes;\n s.cols = l.cols.v;\n ids.push(id);\n subs.push(s);\n }\n l.pending.clear();\n l.ids = ids;\n if (l.cols.v.length !== ids.length * l.axes) {\n l.cols = { v: new Float64Array(ids.length * l.axes) };\n for (const s of subs) s.cols = l.cols.v;\n }\n l.dirty = false;\n };\n\n // A voice cued now reads voice time 0 at codec time `time`. Each patch holds its subject at rest\n // at `from`, and the codec releases it with a `to` or `push` timed at 0.\n const springVoice = (l: Lane, id: number, sp: SpringStart): { handle: Handle<number>; patch: Moves } => {\n // With no target the spring force is zero whatever the stiffness, leaving damping alone:\n // velocity falls as exp(-t * damping / mass), a glide whose time constant is mass / damping.\n const patch: Moves & Patch<number, Out, void> = sp.to == null\n ? glide<number, Out, number[]>('v', { from: sp.from, ms: (1000 * sp.mass) / sp.damping })\n : springPatch<number, Out, number[]>('v', {\n from: sp.from, to: sp.from, stiffness: sp.stiffness, damping: sp.damping, mass: sp.mass,\n });\n const handle = l.mix.cue({ patch, subjects: [id] });\n if (sp.to) patch.to!(id, sp.to, 0);\n patch.push!(id, sp.velocity, 0);\n return { handle, patch };\n };\n\n const get = (id: number): Subject => {\n const s = subjects.get(id);\n if (!s) throw new Error(`codec: no subject ${id}`);\n return s;\n };\n /** A spring subject's motion now: as its voice last read it, else where it was last placed. */\n const current = (s: Subject, id: number): { value: number[]; velocity: number[] } =>\n s.patch.read(id) ?? { value: s.spring!.from.slice(), velocity: s.spring!.velocity.slice() };\n\n function stop(id: number): void {\n const s = subjects.get(id);\n if (!s) return;\n if (s.queued) s.queued = undefined;\n else {\n s.handle.fade({ over: 0 });\n s.lane.mix.drop(id);\n }\n depart(s, id);\n subjects.delete(id);\n }\n\n return {\n frame(dtMs) {\n for (const sub of queue) {\n const q = sub.queued;\n if (!q) continue;\n sub.queued = undefined;\n const patch = tweenPatch<number, Out, number[]>('v', { from: q.from.slice(), to: q.from.slice(), ms: q.ms, ease: q.ease });\n sub.handle = sub.lane.mix.cue({ patch, subjects: [q.id] });\n sub.patch = patch;\n patch.to(q.id, q.to.slice(), 0);\n if (sub.queuedRate !== undefined) sub.handle.rate = sub.queuedRate;\n }\n queue.length = 0;\n time += dtMs;\n for (const l of lanes.values()) {\n if (l.dirty) rebuild(l);\n if (l.ids.length === 0 && ++l.idle >= 2) {\n lanes.delete(l.axes);\n continue;\n }\n l.mix.sync(time);\n if (l.ids.length === 0) continue;\n l.idle = 0;\n l.mix.pull(l.ids, l.cols);\n }\n },\n tween(s) {\n if (s.to.length !== s.from.length) {\n throw new Error(`codec: tween ${s.id} goes from ${s.from.length} axes to ${s.to.length}`);\n }\n const l = laneOf(s.from.length);\n const held = Float64Array.from(s.from);\n const sub = { kind: 'tween', lane: l, slot: -1, held, cols: held, at: 0, queued: s } as Subject;\n subjects.set(s.id, sub);\n join(sub, s.id);\n queue.push(sub);\n return sub;\n },\n spring(s) {\n const sp = { ...s, from: s.from.slice(), to: s.to && s.to.slice(), velocity: s.velocity.slice() };\n const l = laneOf(sp.axes);\n const { handle, patch } = springVoice(l, s.id, sp);\n const held = Float64Array.from(sp.from);\n const sub: Subject = { kind: 'spring', lane: l, handle, patch, spring: sp, slot: -1, held, cols: held, at: 0 };\n subjects.set(s.id, sub);\n join(sub, s.id);\n const id = s.id;\n sub.motion = () => current(sub, id);\n return sub as Subject & SpringReader;\n },\n retarget(id, to) {\n const s = get(id);\n if (s.kind === 'tween') throw new Error(`codec: subject ${id} is a tween; tweens are not retargeted`);\n const sp = s.spring!;\n if ((to == null) === (sp.to == null)) {\n if (!to) return;\n sp.to = to.slice();\n s.patch.to!(id, sp.to);\n return;\n }\n // Held and free run different patches, so the subject is re-cued from where it is.\n const now = current(s, id);\n const rate = s.handle.rate;\n const next = { ...sp, to: to && to.slice(), from: now.value, velocity: now.velocity };\n s.handle.fade({ over: 0 });\n const { handle, patch } = springVoice(s.lane, id, next);\n handle.rate = rate;\n s.handle = handle;\n s.patch = patch;\n s.spring = next;\n },\n push(id, velocity) {\n const s = get(id);\n if (s.spring && !s.patch.read(id)) s.spring.velocity = velocity.slice();\n s.patch.push?.(id, velocity);\n },\n rate(id, rate) {\n const s = get(id);\n if (s.queued) s.queuedRate = rate;\n else s.handle.rate = rate;\n },\n stop,\n has: (id) => subjects.has(id),\n voiceCount() {\n return subjects.size;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/animation/engine/integrator.ts
|
|
2
|
+
function e(e, t, n, r, i, a, o, s) {
|
|
3
|
+
let c = r ?? t, l = e.subtract(t, c), u = e.scale(l, r == null ? 0 : -i), d = e.scale(n, -a), f = e.scale(e.add(u, d), 1 / o), p = e.add(n, e.scale(f, s));
|
|
4
|
+
return {
|
|
5
|
+
value: e.add(t, e.scale(p, s)),
|
|
6
|
+
velocity: p
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { e as stepSpring };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=integrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integrator.js","names":["stepSpring","ref","target","value","displacement","ops","springForce","k","dampingForce","velocity","damping","accel","mass","v","dt"],"sources":["../../../src/animation/engine/integrator.ts"],"sourcesContent":["export interface VectorOps<T> {\n add(a: T, b: T): T;\n subtract(a: T, b: T): T;\n scale(v: T, k: number): T;\n}\n\n/** One semi-implicit Euler step of a damped spring toward `target`, or of pure damping when\n * `target` is null. Returns the new value and velocity. */\nexport function stepSpring<T>(\n ops: VectorOps<T>,\n value: T, velocity: T, target: T | null,\n k: number, damping: number, mass: number, dt: number,\n): { value: T; velocity: T } {\n const ref = target ?? value;\n const displacement = ops.subtract(value, ref);\n const springForce = ops.scale(displacement, target == null ? 0 : -k);\n const dampingForce = ops.scale(velocity, -damping);\n const accel = ops.scale(ops.add(springForce, dampingForce), 1 / mass);\n const v = ops.add(velocity, ops.scale(accel, dt));\n return { value: ops.add(value, ops.scale(v, dt)), velocity: v };\n}\n"],"mappings":";AAQA,SAAgBA,EACd,GACA,GAAU,GAAa,GACvB,GAAW,GAAiB,GAAc,GACf;CAC3B,IAAMC,IAAMC,KAAUC,GAChBC,IAAeC,EAAI,SAASF,GAAOF,CAAG,GACtCK,IAAcD,EAAI,MAAMD,GAAcF,KAAU,OAAO,IAAI,CAACK,CAAC,GAC7DC,IAAeH,EAAI,MAAMI,GAAU,CAACC,CAAO,GAC3CC,IAAQN,EAAI,MAAMA,EAAI,IAAIC,GAAaE,CAAY,GAAG,IAAII,CAAI,GAC9DC,IAAIR,EAAI,IAAII,GAAUJ,EAAI,MAAMM,GAAOG,CAAE,CAAC;CAChD,OAAO;EAAE,OAAOT,EAAI,IAAIF,GAAOE,EAAI,MAAMQ,GAAGC,CAAE,CAAC;EAAG,UAAUD;CAAE;AAChE"}
|