summaryrefslogtreecommitdiff
path: root/apps/web/src/animation/resolve.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/resolve.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/resolve.ts')
-rw-r--r--apps/web/src/animation/resolve.ts184
1 files changed, 184 insertions, 0 deletions
diff --git a/apps/web/src/animation/resolve.ts b/apps/web/src/animation/resolve.ts
new file mode 100644
index 00000000..1b51c126
--- /dev/null
+++ b/apps/web/src/animation/resolve.ts
@@ -0,0 +1,184 @@
+import type {
+ AnimationColorPropertyPath,
+ AnimationNumericPropertyPath,
+ AnimationPath,
+ AnimationPropertyPath,
+ AnimationValueForPath,
+ ElementAnimations,
+} from "@/animation/types";
+import type { Transform } from "@/rendering";
+import {
+ type AnimationComponentValue,
+ composeAnimationValue,
+ decomposeAnimationValue,
+} from "./binding-values";
+import {
+ getChannelValueAtTime,
+} from "./interpolation";
+
+export function getElementLocalTime({
+ timelineTime,
+ elementStartTime,
+ elementDuration,
+}: {
+ timelineTime: number;
+ elementStartTime: number;
+ elementDuration: number;
+}): number {
+ const localTime = timelineTime - elementStartTime;
+ if (localTime <= 0) {
+ return 0;
+ }
+
+ if (localTime >= elementDuration) {
+ return elementDuration;
+ }
+
+ return localTime;
+}
+
+export function resolveTransformAtTime({
+ baseTransform,
+ animations,
+ localTime,
+}: {
+ baseTransform: Transform;
+ animations: ElementAnimations | undefined;
+ localTime: number;
+}): Transform {
+ const safeLocalTime = Math.max(0, localTime);
+ return {
+ position: {
+ x: resolveAnimationPathValueAtTime({
+ animations,
+ propertyPath: "transform.positionX",
+ localTime: safeLocalTime,
+ fallbackValue: baseTransform.position.x,
+ }),
+ y: resolveAnimationPathValueAtTime({
+ animations,
+ propertyPath: "transform.positionY",
+ localTime: safeLocalTime,
+ fallbackValue: baseTransform.position.y,
+ }),
+ },
+ scaleX: resolveAnimationPathValueAtTime({
+ animations,
+ propertyPath: "transform.scaleX",
+ localTime: safeLocalTime,
+ fallbackValue: baseTransform.scaleX,
+ }),
+ scaleY: resolveAnimationPathValueAtTime({
+ animations,
+ propertyPath: "transform.scaleY",
+ localTime: safeLocalTime,
+ fallbackValue: baseTransform.scaleY,
+ }),
+ rotate: resolveAnimationPathValueAtTime({
+ animations,
+ propertyPath: "transform.rotate",
+ localTime: safeLocalTime,
+ fallbackValue: baseTransform.rotate,
+ }),
+ };
+}
+
+export function resolveOpacityAtTime({
+ baseOpacity,
+ animations,
+ localTime,
+}: {
+ baseOpacity: number;
+ animations: ElementAnimations | undefined;
+ localTime: number;
+}): number {
+ return resolveAnimationPathValueAtTime({
+ animations,
+ propertyPath: "opacity",
+ localTime: Math.max(0, localTime),
+ fallbackValue: baseOpacity,
+ });
+}
+
+export function resolveNumberAtTime({
+ baseValue,
+ animations,
+ propertyPath,
+ localTime,
+}: {
+ baseValue: number;
+ animations: ElementAnimations | undefined;
+ propertyPath: AnimationNumericPropertyPath;
+ localTime: number;
+}): number {
+ return resolveAnimationPathValueAtTime({
+ animations,
+ propertyPath,
+ localTime: Math.max(0, localTime),
+ fallbackValue: baseValue,
+ });
+}
+
+export function resolveColorAtTime({
+ baseColor,
+ animations,
+ propertyPath,
+ localTime,
+}: {
+ baseColor: string;
+ animations: ElementAnimations | undefined;
+ propertyPath: AnimationColorPropertyPath;
+ localTime: number;
+}): string {
+ return resolveAnimationPathValueAtTime({
+ animations,
+ propertyPath,
+ localTime: Math.max(0, localTime),
+ fallbackValue: baseColor,
+ });
+}
+
+export function resolveAnimationPathValueAtTime<TPath extends AnimationPath>({
+ animations,
+ propertyPath,
+ localTime,
+ fallbackValue,
+}: {
+ animations: ElementAnimations | undefined;
+ propertyPath: TPath;
+ localTime: number;
+ fallbackValue: AnimationValueForPath<TPath>;
+}): AnimationValueForPath<TPath> {
+ const binding = animations?.bindings[propertyPath];
+ if (!binding) {
+ return fallbackValue;
+ }
+
+ const fallbackComponents = decomposeAnimationValue({
+ kind: binding.kind,
+ value: fallbackValue,
+ });
+ if (!fallbackComponents) {
+ return fallbackValue;
+ }
+
+ const componentValues = Object.fromEntries(
+ binding.components.map((component) => {
+ const channel = animations?.channels[component.channelId];
+ return [
+ component.key,
+ getChannelValueAtTime({
+ channel,
+ time: localTime,
+ fallbackValue:
+ fallbackComponents[component.key] ??
+ (channel?.kind === "discrete" ? false : 0),
+ }),
+ ];
+ }),
+ ) as Record<string, AnimationComponentValue | undefined>;
+ return (composeAnimationValue({
+ binding,
+ componentValues,
+ }) ?? fallbackValue) as AnimationValueForPath<TPath>;
+}