Let animation keys ease with Sine to Bounce in, out or in-out
A key could only ease quadratically, which makes quick moves like a sword swing feel soft. Animators know the Penner curves from CSS, GSAP and Unity, so a key now offers all of them. - Ten curves (Sine, Quad, Cubic, Quart, Quint, Expo, Circ, Back, Elastic, Bounce), each in, out and in-out, next to Linear and Step. - The inspector shows two fields instead of one long list: Easing, and Direction only when a curve is picked. Switching the curve keeps the direction. Coming from Linear or Step starts at in-out. - Old projects keep working. ease-in/out/in-out now read as Quad and are still saved under that name. New curves save as cubic-out, back-in-out and so on. - Back and Elastic overshoot on purpose. A number property with min or max is clamped in play and preview, so a Back in from scale 0 does not flip the sprite. trackWriteAt now takes the property definition, and animatedKind* is renamed to animatedDef*. - The timeline marker still shows only the direction. - In-out is built for every curve from its in-curve and its mirror. easings.net uses own constants for Back and Elastic in-out, so our Back in-out winds up about 5 % instead of 10 %. Tests: - ./check.sh: 165 files, 2413 tests, CHECK EXIT 0. - 12 new unit tests in tests/core/easing-curves.test.ts: round trip of all 30 values, start/end/midpoint of every curve, reference values, only Back and Elastic leave 0..1, clamping in play and preview, unknown values are rejected. - e2e 25: ALL PASSED, 28 checks. New 25b-ii (3 checks) clicks both fields and undoes the four choices. Check sum 1393 -> 1396. - Counter-checks, each fails its test: no clamping (2), Quad saved as quad-* (2), mirrored in-out wrong (2), curve lookup with `in` instead of Object.hasOwn (1), no Direction field (e2e 25). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
94749b4f3e
commit
147f8ffbea
9 files changed
+500
-74
No files matched your search
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import {
|
||||
animatableDefs,
|
||||
animatedKindOf,
|
||||
animatedDefOf,
|
||||
frameCount,
|
||||
isPositionRow,
|
||||
PLACED_ROOT_ID,
|
||||
@@ -72,7 +72,7 @@ function placedHolder(root: Asset): ObjectHolder {
|
||||
function writeScene(holder: ObjectHolder, track: AnimationTrack, frame: number, reverse: boolean): void {
|
||||
const def = animatableDefs('scene').find((d) => d.key === track.property);
|
||||
if (!def) return;
|
||||
const value = trackWriteAt(track, def.kind, frame, holder.props[track.property] ?? def.default, reverse);
|
||||
const value = trackWriteAt(track, def, frame, holder.props[track.property] ?? def.default, reverse);
|
||||
if (value !== undefined) holder.props[track.property] = value;
|
||||
}
|
||||
|
||||
@@ -89,14 +89,14 @@ function writeObject(
|
||||
if (!ref || !asset) return;
|
||||
// Resolved, so a shader worn through an override or an instance edit brings its parameters (#104).
|
||||
const props = resolveSceneObjectProps(project, asset, ref.object);
|
||||
const kind = animatedKindOf(project, asset, props, track.property);
|
||||
if (!kind) return;
|
||||
const def = animatedDefOf(project, asset, props, track.property);
|
||||
if (!def) return;
|
||||
const key = track.property;
|
||||
if (isPositionRow(asset.type, key)) {
|
||||
const value = trackWriteAt(track, kind, frame, ref.object.position[key], reverse);
|
||||
const value = trackWriteAt(track, def, frame, ref.object.position[key], reverse);
|
||||
if (value !== undefined) ref.setPosition({ ...ref.object.position, [key]: Number(value) });
|
||||
return;
|
||||
}
|
||||
const value = trackWriteAt(track, kind, frame, props[key], reverse);
|
||||
const value = trackWriteAt(track, def, frame, props[key], reverse);
|
||||
if (value !== undefined) ref.setOverride(key, value);
|
||||
}
|
||||
Reference in new issue
Block a user