import type { ScalarAnimationKey } from "@/animation/types"; const BEZIER_SOLVE_ITERATIONS = 20; export function getBezierPoint({ progress, p0, p1, p2, p3, }: { progress: number; p0: number; p1: number; p2: number; p3: number; }) { const mt = 1 - progress; return ( mt * mt * mt * p0 + 3 * mt * mt * progress * p1 + 3 * mt * progress * progress * p2 + progress * progress * progress * p3 ); } export function getDefaultRightHandle({ leftKey, rightKey, }: { leftKey: ScalarAnimationKey; rightKey: ScalarAnimationKey; }) { const span = rightKey.time - leftKey.time; const valueDelta = rightKey.value - leftKey.value; return { dt: span / 3, dv: valueDelta / 3, }; } export function getDefaultLeftHandle({ leftKey, rightKey, }: { leftKey: ScalarAnimationKey; rightKey: ScalarAnimationKey; }) { const span = rightKey.time - leftKey.time; const valueDelta = rightKey.value - leftKey.value; return { dt: -span / 3, dv: -valueDelta / 3, }; } export function solveBezierProgressForTime({ time, leftKey, rightKey, }: { time: number; leftKey: ScalarAnimationKey; rightKey: ScalarAnimationKey; }) { let lower = 0; let upper = 1; const rightHandle = leftKey.rightHandle ?? getDefaultRightHandle({ leftKey, rightKey }); const leftHandle = rightKey.leftHandle ?? getDefaultLeftHandle({ leftKey, rightKey }); for (let iteration = 0; iteration < BEZIER_SOLVE_ITERATIONS; iteration++) { const mid = (lower + upper) / 2; const estimate = getBezierPoint({ progress: mid, p0: leftKey.time, p1: leftKey.time + rightHandle.dt, p2: rightKey.time + leftHandle.dt, p3: rightKey.time, }); if (estimate < time) { lower = mid; } else { upper = mid; } } return (lower + upper) / 2; }