diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
| commit | 3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch) | |
| tree | 57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/animation/curve-bridge.ts | |
| parent | 729d10592f4e802f91c555c309ff3ba4b119f843 (diff) | |
| download | opencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz | |
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/animation/curve-bridge.ts')
| -rw-r--r-- | apps/web/src/animation/curve-bridge.ts | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/apps/web/src/animation/curve-bridge.ts b/apps/web/src/animation/curve-bridge.ts new file mode 100644 index 00000000..af411dd1 --- /dev/null +++ b/apps/web/src/animation/curve-bridge.ts @@ -0,0 +1,95 @@ +import { + getDefaultLeftHandle, + getDefaultRightHandle, +} from "@/animation/bezier"; +import type { + CurveHandle, + NormalizedCubicBezier, + ScalarAnimationKey, +} from "@/animation/types"; + +const VALUE_EPSILON = 1e-6; + +function clamp01({ value }: { value: number }): number { + return Math.max(0, Math.min(1, value)); +} + +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; + const effectiveSpanValue = + Math.abs(spanValue) > VALUE_EPSILON + ? spanValue + : referenceSpanValue !== undefined && Math.abs(referenceSpanValue) > VALUE_EPSILON + ? referenceSpanValue + : null; + + if (spanTime === 0 || effectiveSpanValue === null) { + return null; + } + + const rightHandle = + leftKey.rightHandle ?? getDefaultRightHandle({ leftKey, rightKey }); + const leftHandle = + rightKey.leftHandle ?? getDefaultLeftHandle({ leftKey, rightKey }); + + return [ + clamp01({ value: rightHandle.dt / spanTime }), + rightHandle.dv / effectiveSpanValue, + clamp01({ value: 1 + leftHandle.dt / spanTime }), + 1 + leftHandle.dv / effectiveSpanValue, + ]; +} + +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; + const effectiveSpanValue = + Math.abs(spanValue) > VALUE_EPSILON + ? spanValue + : referenceSpanValue !== undefined && Math.abs(referenceSpanValue) > VALUE_EPSILON + ? referenceSpanValue + : null; + + if (spanTime === 0 || effectiveSpanValue === null) { + return null; + } + + const [rawX1, y1, rawX2, y2] = cubicBezier; + const x1 = clamp01({ value: rawX1 }); + const x2 = clamp01({ value: rawX2 }); + + return { + rightHandle: { + dt: spanTime * x1, + dv: effectiveSpanValue * y1, + }, + leftHandle: { + dt: spanTime * (x2 - 1), + dv: effectiveSpanValue * (y2 - 1), + }, + }; +} |
