From 270e2951b188af067c3d9ae915282c8db1af2b81 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 13 Apr 2026 21:59:50 +0200 Subject: feat: graph editor for flat keyframe segments --- apps/web/src/lib/animation/curve-bridge.ts | 38 ++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) (limited to 'apps/web/src/lib') diff --git a/apps/web/src/lib/animation/curve-bridge.ts b/apps/web/src/lib/animation/curve-bridge.ts index 12103c3f..385fb448 100644 --- a/apps/web/src/lib/animation/curve-bridge.ts +++ b/apps/web/src/lib/animation/curve-bridge.ts @@ -17,16 +17,23 @@ function clamp01({ value }: { value: number }): number { export function getNormalizedCubicBezierForScalarSegment({ leftKey, rightKey, + referenceSpanValue, }: { leftKey: ScalarAnimationKey; rightKey: ScalarAnimationKey; + /** Fallback Y-axis scale used when the segment is flat (spanValue ≈ 0). */ + referenceSpanValue?: number; }): NormalizedCubicBezier | null { const spanTime = rightKey.time - leftKey.time; const spanValue = rightKey.value - leftKey.value; - if ( - spanTime === 0 || - Math.abs(spanValue) <= VALUE_EPSILON - ) { + const effectiveSpanValue = + Math.abs(spanValue) > VALUE_EPSILON + ? spanValue + : referenceSpanValue !== undefined && Math.abs(referenceSpanValue) > VALUE_EPSILON + ? referenceSpanValue + : null; + + if (spanTime === 0 || effectiveSpanValue === null) { return null; } @@ -37,9 +44,9 @@ export function getNormalizedCubicBezierForScalarSegment({ return [ clamp01({ value: rightHandle.dt / spanTime }), - rightHandle.dv / spanValue, + rightHandle.dv / effectiveSpanValue, clamp01({ value: 1 + leftHandle.dt / spanTime }), - 1 + leftHandle.dv / spanValue, + 1 + leftHandle.dv / effectiveSpanValue, ]; } @@ -47,20 +54,27 @@ export function getCurveHandlesForNormalizedCubicBezier({ leftKey, rightKey, cubicBezier, + referenceSpanValue, }: { leftKey: ScalarAnimationKey; rightKey: ScalarAnimationKey; cubicBezier: NormalizedCubicBezier; + /** Fallback Y-axis scale used when the segment is flat (spanValue ≈ 0). */ + referenceSpanValue?: number; }): { rightHandle: CurveHandle; leftHandle: CurveHandle; } | null { const spanTime = rightKey.time - leftKey.time; const spanValue = rightKey.value - leftKey.value; - if ( - spanTime === 0 || - Math.abs(spanValue) <= VALUE_EPSILON - ) { + const effectiveSpanValue = + Math.abs(spanValue) > VALUE_EPSILON + ? spanValue + : referenceSpanValue !== undefined && Math.abs(referenceSpanValue) > VALUE_EPSILON + ? referenceSpanValue + : null; + + if (spanTime === 0 || effectiveSpanValue === null) { return null; } @@ -71,11 +85,11 @@ export function getCurveHandlesForNormalizedCubicBezier({ return { rightHandle: { dt: spanTime * x1, - dv: spanValue * y1, + dv: effectiveSpanValue * y1, }, leftHandle: { dt: spanTime * (x2 - 1), - dv: spanValue * (y2 - 1), + dv: effectiveSpanValue * (y2 - 1), }, }; } -- cgit v1.3.1