summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/animation/interpolation.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/lib/animation/interpolation.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/lib/animation/interpolation.ts')
-rw-r--r--apps/web/src/lib/animation/interpolation.ts375
1 files changed, 0 insertions, 375 deletions
diff --git a/apps/web/src/lib/animation/interpolation.ts b/apps/web/src/lib/animation/interpolation.ts
deleted file mode 100644
index 1a3c0fed..00000000
--- a/apps/web/src/lib/animation/interpolation.ts
+++ /dev/null
@@ -1,375 +0,0 @@
-import type {
- AnimationChannel,
- AnimationInterpolation,
- AnimationValue,
- DiscreteAnimationChannel,
- DiscreteValue,
- ScalarAnimationChannel,
- ScalarAnimationKey,
- ScalarSegmentType,
-} from "@/lib/animation/types";
-import { clamp } from "@/utils/math";
-import {
- getBezierPoint,
- getDefaultLeftHandle,
- getDefaultRightHandle,
- solveBezierProgressForTime,
-} from "./bezier";
-
-function byTimeAscending({
- leftTime,
- rightTime,
-}: {
- leftTime: number;
- rightTime: number;
-}): number {
- return leftTime - rightTime;
-}
-
-function isWithinTimePair({
- time,
- leftTime,
- rightTime,
-}: {
- time: number;
- leftTime: number;
- rightTime: number;
-}): boolean {
- return time >= leftTime && time <= rightTime;
-}
-
-function lerpNumber({
- leftValue,
- rightValue,
- progress,
-}: {
- leftValue: number;
- rightValue: number;
- progress: number;
-}): number {
- return leftValue + (rightValue - leftValue) * progress;
-}
-
-function normalizeRightHandle({
- handle,
- leftKey,
- rightKey,
-}: {
- handle: ScalarAnimationKey["rightHandle"];
- leftKey: ScalarAnimationKey;
- rightKey: ScalarAnimationKey;
-}) {
- if (!handle) {
- return undefined;
- }
-
- const span = Math.max(1, rightKey.time - leftKey.time);
- return {
- dt: Math.min(span, Math.max(0, handle.dt)),
- dv: handle.dv,
- };
-}
-
-function normalizeLeftHandle({
- handle,
- leftKey,
- rightKey,
-}: {
- handle: ScalarAnimationKey["leftHandle"];
- leftKey: ScalarAnimationKey;
- rightKey: ScalarAnimationKey;
-}) {
- if (!handle) {
- return undefined;
- }
-
- const span = Math.max(1, rightKey.time - leftKey.time);
- return {
- dt: Math.max(-span, Math.min(0, handle.dt)),
- dv: handle.dv,
- };
-}
-
-function normalizeScalarKey({
- key,
-}: {
- key: ScalarAnimationKey;
-}): ScalarAnimationKey {
- return {
- ...key,
- tangentMode: key.tangentMode ?? "flat",
- segmentToNext: key.segmentToNext ?? "linear",
- };
-}
-
-function normalizeScalarChannel({
- channel,
-}: {
- channel: ScalarAnimationChannel;
-}): ScalarAnimationChannel {
- const sortedKeys = [...channel.keys]
- .map((key) => normalizeScalarKey({ key }))
- .sort((leftKey, rightKey) =>
- byTimeAscending({
- leftTime: leftKey.time,
- rightTime: rightKey.time,
- }),
- );
- const nextKeys = sortedKeys.map((key, index) => {
- const previousKey = sortedKeys[index - 1];
- const nextKey = sortedKeys[index + 1];
- return {
- ...key,
- leftHandle:
- previousKey != null
- ? normalizeLeftHandle({
- handle: key.leftHandle,
- leftKey: previousKey,
- rightKey: key,
- })
- : undefined,
- rightHandle:
- nextKey != null
- ? normalizeRightHandle({
- handle: key.rightHandle,
- leftKey: key,
- rightKey: nextKey,
- })
- : undefined,
- };
- });
-
- return {
- ...channel,
- keys: nextKeys,
- };
-}
-
-export function normalizeChannel<TChannel extends AnimationChannel>({
- channel,
-}: {
- channel: TChannel;
-}): TChannel {
- if (channel.kind === "scalar") {
- return normalizeScalarChannel({
- channel,
- }) as TChannel;
- }
-
- return {
- ...channel,
- keys: [...channel.keys].sort((leftKeyframe, rightKeyframe) =>
- byTimeAscending({
- leftTime: leftKeyframe.time,
- rightTime: rightKeyframe.time,
- }),
- ),
- } as TChannel;
-}
-
-function extrapolateScalarEdge({
- mode,
- edgeKey,
- neighborKey,
- time,
-}: {
- mode: "hold" | "linear";
- edgeKey: ScalarAnimationKey;
- neighborKey: ScalarAnimationKey | undefined;
- time: number;
-}) {
- if (mode === "hold" || !neighborKey) {
- return edgeKey.value;
- }
-
- const span = neighborKey.time - edgeKey.time;
- if (span === 0) {
- return edgeKey.value;
- }
-
- return edgeKey.value + ((time - edgeKey.time) / span) * (neighborKey.value - edgeKey.value);
-}
-
-export function getScalarSegmentInterpolation({
- segment,
-}: {
- segment: ScalarSegmentType;
-}): AnimationInterpolation {
- if (segment === "step") {
- return "hold";
- }
-
- return segment === "bezier" ? "bezier" : "linear";
-}
-
-export function getScalarChannelValueAtTime({
- channel,
- time,
- fallbackValue,
-}: {
- channel: ScalarAnimationChannel | undefined;
- time: number;
- fallbackValue: number;
-}): number {
- if (!channel || channel.keys.length === 0) {
- return fallbackValue;
- }
-
- const normalizedChannel = normalizeChannel({
- channel,
- });
- const firstKey = normalizedChannel.keys[0];
- const lastKey = normalizedChannel.keys[normalizedChannel.keys.length - 1];
- if (!firstKey || !lastKey) {
- return fallbackValue;
- }
-
- if (time <= firstKey.time) {
- if (time < firstKey.time) {
- return extrapolateScalarEdge({
- mode: normalizedChannel.extrapolation?.before ?? "hold",
- edgeKey: firstKey,
- neighborKey: normalizedChannel.keys[1],
- time,
- });
- }
-
- return firstKey.value;
- }
-
- if (time >= lastKey.time) {
- if (time > lastKey.time) {
- return extrapolateScalarEdge({
- mode: normalizedChannel.extrapolation?.after ?? "hold",
- edgeKey: lastKey,
- neighborKey: normalizedChannel.keys[normalizedChannel.keys.length - 2],
- time,
- });
- }
-
- return lastKey.value;
- }
-
- for (
- let keyIndex = 0;
- keyIndex < normalizedChannel.keys.length - 1;
- keyIndex++
- ) {
- const leftKey = normalizedChannel.keys[keyIndex];
- const rightKey = normalizedChannel.keys[keyIndex + 1];
- if (time === rightKey.time) {
- return rightKey.value;
- }
-
- if (
- !isWithinTimePair({
- time,
- leftTime: leftKey.time,
- rightTime: rightKey.time,
- })
- ) {
- continue;
- }
-
- if (leftKey.segmentToNext === "step") {
- return leftKey.value;
- }
-
- const span = rightKey.time - leftKey.time;
- if (span === 0) {
- return rightKey.value;
- }
-
- const progress = clamp({
- value: (time - leftKey.time) / span,
- min: 0,
- max: 1,
- });
- if (leftKey.segmentToNext === "linear") {
- return lerpNumber({
- leftValue: leftKey.value,
- rightValue: rightKey.value,
- progress,
- });
- }
-
- const curveProgress = solveBezierProgressForTime({
- time,
- leftKey,
- rightKey,
- });
- const rightHandle =
- leftKey.rightHandle ?? getDefaultRightHandle({ leftKey, rightKey });
- const leftHandle =
- rightKey.leftHandle ?? getDefaultLeftHandle({ leftKey, rightKey });
- return getBezierPoint({
- progress: curveProgress,
- p0: leftKey.value,
- p1: leftKey.value + rightHandle.dv,
- p2: rightKey.value + leftHandle.dv,
- p3: rightKey.value,
- });
- }
-
- return lastKey.value;
-}
-
-export function getDiscreteChannelValueAtTime({
- channel,
- time,
- fallbackValue,
-}: {
- channel: DiscreteAnimationChannel | undefined;
- time: number;
- fallbackValue: DiscreteValue;
-}): DiscreteValue {
- if (!channel || channel.keys.length === 0) {
- return fallbackValue;
- }
-
- const normalizedChannel = normalizeChannel({
- channel,
- });
- let currentValue = fallbackValue;
- for (const key of normalizedChannel.keys) {
- if (time < key.time) {
- break;
- }
- currentValue = key.value;
- }
- return currentValue;
-}
-
-export function getChannelValueAtTime({
- channel,
- time,
- fallbackValue,
-}: {
- channel: AnimationChannel | undefined;
- time: number;
- fallbackValue: AnimationValue;
-}): AnimationValue {
- if (!channel || channel.keys.length === 0) {
- return fallbackValue;
- }
-
- if (channel.kind === "scalar") {
- return typeof fallbackValue === "number"
- ? getScalarChannelValueAtTime({
- channel,
- time,
- fallbackValue,
- })
- : fallbackValue;
- }
-
- if (typeof fallbackValue !== "string" && typeof fallbackValue !== "boolean") {
- return fallbackValue;
- }
-
- return getDiscreteChannelValueAtTime({
- channel,
- time,
- fallbackValue,
- });
-}