summaryrefslogtreecommitdiff
path: root/apps/web/src/animation/bezier.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
commit3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch)
tree57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/animation/bezier.ts
parent729d10592f4e802f91c555c309ff3ba4b119f843 (diff)
downloadopencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/animation/bezier.ts')
-rw-r--r--apps/web/src/animation/bezier.ts90
1 files changed, 90 insertions, 0 deletions
diff --git a/apps/web/src/animation/bezier.ts b/apps/web/src/animation/bezier.ts
new file mode 100644
index 00000000..6f9a1d18
--- /dev/null
+++ b/apps/web/src/animation/bezier.ts
@@ -0,0 +1,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;
+}