summaryrefslogtreecommitdiff
path: root/apps/web/src/animation/bezier.ts
blob: 6f9a1d1820aade17c6c494ab9bdc7bf8ac880f32 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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;
}