The built-in eases cover the usual shapes, but a game often wants its own: a coin that bounces twice, a door that overshoots and settles. A new asset type holds such a curve, and keys and the Lerp node can pick it instead of a built-in ease. - New asset type "curve": points like in Vector art, x is t and y is the value. A new curve is the straight line from (0, 0) to (1, 1). - One pure evaluation in src/core/model/curve.ts serves the timeline preview, the running game and the Lerp node. - The curve always has one value per t. The end points stay at t 0 and 1, points cannot pass each other, and a handle that reaches past its neighbour is shortened along its direction. Loading a hand-edited file applies the same rules. - Curve tab: click on the line to insert a point that keeps the shape, click on free space to put one there, drag points and handles, double-click or Delete removes, Alt-click toggles round and sharp. Every step is one undo step. - Presets: all 30 built-in eases plus Linear, fitted with adaptive subdivision. Each stays within 0.4 % of the built-in ease. - Preview: a dot runs along the curve once a second, a second dot shows only the value. The Preview button stops it. - Animation keys get "Curve" under Easing with a curve picker. Lerp gets "Curve" under Ease, and its result may overshoot b. - A missing or unpicked curve runs linear. The inspector says so, the node shows it as a problem, and deleting a used curve warns with the number of keys and Lerp nodes. Tests: - ./check.sh: 180 files, 2612 tests, CHECK EXIT 0 - GM_STEPS=1-6,25,53: ALL PASSED, 309 checks - New e2e module 53 with 13 checks, all with the real mouse. Expected values come from its own Bezier evaluation. - Counter-checks bite: without Bezier evaluation 4 unit tests fail, without handle clamping 2. In the browser, a key that cannot find its curve, a click that sets no point and a handle drag that moves the point all turn checks red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37 lines
1.5 KiB
TypeScript
37 lines
1.5 KiB
TypeScript
import { findAssetOf } from '../../core/model/Project';
|
|
import { centreOf, type HookContext } from './hookHelpers';
|
|
|
|
/** The Curve tab (#247). */
|
|
export function curveHooks({ state, editor }: HookContext) {
|
|
const { curveEditor } = editor;
|
|
return {
|
|
curve: {
|
|
current: () => curveEditor.currentAssetId,
|
|
info: () => curveEditor.shownInfo,
|
|
rect: () => curveEditor.canvasRect(),
|
|
/** Where a place on the graph (`t` right, `v` up) sits on screen. */
|
|
at: (t: number, v: number) => curveEditor.canvas.graphToScreen(t, v),
|
|
/** The points as the model holds them, handles as offsets in `t` and `v`. */
|
|
points: (assetId: string) => {
|
|
const asset = findAssetOf(state.project, assetId, 'curve');
|
|
return asset ? (JSON.parse(JSON.stringify(asset.points)) as unknown) : null;
|
|
},
|
|
picked: () => curveEditor.canvas.pickedIndex,
|
|
pointButton: () => ({
|
|
...centreOf(curveEditor.pointButton),
|
|
text: curveEditor.pointButton.text,
|
|
enabled: curveEditor.pointButton.enabled,
|
|
}),
|
|
presetButton: () => centreOf(curveEditor.presetButton),
|
|
fitAt: () => centreOf(curveEditor.fitButton),
|
|
previewButton: () => ({
|
|
...centreOf(curveEditor.previewButton),
|
|
toggled: curveEditor.previewButton.toggled,
|
|
}),
|
|
/** The preview as drawn: the share of time, the value, and where its two dots are. */
|
|
preview: () => curveEditor.canvas.previewState(),
|
|
setPreviewTime: (seconds: number) => curveEditor.canvas.setPreviewTime(seconds),
|
|
},
|
|
};
|
|
}
|