diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-26 02:50:04 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-26 02:50:04 +0200 |
| commit | eea6d43c82e1fd09d94d73001090dad205b1f1d2 (patch) | |
| tree | 384c92e467effb708bcc96e25117c064b18464b8 /apps/web/src/animation | |
| parent | f1b45b4328cff6a016720ce7b38b87ac2992dc2d (diff) | |
| download | opencut-eea6d43c82e1fd09d94d73001090dad205b1f1d2.tar.gz | |
fix: model editor time as wasm MediaTime ticks end-to-end
Diffstat (limited to 'apps/web/src/animation')
| -rw-r--r-- | apps/web/src/animation/curve-bridge.ts | 5 | ||||
| -rw-r--r-- | apps/web/src/animation/interpolation.ts | 17 | ||||
| -rw-r--r-- | apps/web/src/animation/keyframes.ts | 62 | ||||
| -rw-r--r-- | apps/web/src/animation/types.ts | 7 |
4 files changed, 57 insertions, 34 deletions
diff --git a/apps/web/src/animation/curve-bridge.ts b/apps/web/src/animation/curve-bridge.ts index af411dd1..c947e7dc 100644 --- a/apps/web/src/animation/curve-bridge.ts +++ b/apps/web/src/animation/curve-bridge.ts @@ -7,6 +7,7 @@ import type { NormalizedCubicBezier, ScalarAnimationKey, } from "@/animation/types"; +import { roundMediaTime } from "@/wasm"; const VALUE_EPSILON = 1e-6; @@ -84,11 +85,11 @@ export function getCurveHandlesForNormalizedCubicBezier({ return { rightHandle: { - dt: spanTime * x1, + dt: roundMediaTime({ time: spanTime * x1 }), dv: effectiveSpanValue * y1, }, leftHandle: { - dt: spanTime * (x2 - 1), + dt: roundMediaTime({ time: spanTime * (x2 - 1) }), dv: effectiveSpanValue * (y2 - 1), }, }; diff --git a/apps/web/src/animation/interpolation.ts b/apps/web/src/animation/interpolation.ts index 277bfbb7..2169024a 100644 --- a/apps/web/src/animation/interpolation.ts +++ b/apps/web/src/animation/interpolation.ts @@ -9,6 +9,7 @@ import type { ScalarSegmentType, } from "@/animation/types"; import { clamp } from "@/utils/math"; +import { mediaTime } from "@/wasm"; import { getBezierPoint, getDefaultLeftHandle, @@ -63,9 +64,13 @@ function normalizeRightHandle({ return undefined; } - const span = Math.max(1, rightKey.time - leftKey.time); + const span = mediaTime({ + ticks: Math.max(1, rightKey.time - leftKey.time), + }); return { - dt: Math.min(span, Math.max(0, handle.dt)), + dt: mediaTime({ + ticks: Math.min(span, Math.max(0, handle.dt)), + }), dv: handle.dv, }; } @@ -83,9 +88,13 @@ function normalizeLeftHandle({ return undefined; } - const span = Math.max(1, rightKey.time - leftKey.time); + const span = mediaTime({ + ticks: Math.max(1, rightKey.time - leftKey.time), + }); return { - dt: Math.max(-span, Math.min(0, handle.dt)), + dt: mediaTime({ + ticks: Math.max(-span, Math.min(0, handle.dt)), + }), dv: handle.dv, }; } diff --git a/apps/web/src/animation/keyframes.ts b/apps/web/src/animation/keyframes.ts index 12dc777d..bdfe7c4c 100644 --- a/apps/web/src/animation/keyframes.ts +++ b/apps/web/src/animation/keyframes.ts @@ -35,6 +35,12 @@ import { coerceAnimationValueForProperty, getAnimationPropertyDefinition, } from "./property-registry"; +import { + type MediaTime, + roundMediaTime, + subMediaTime, + ZERO_MEDIA_TIME, +} from "@/wasm"; function isNearlySameTime({ leftTime, @@ -171,7 +177,7 @@ function createScalarKey({ previousKey, }: { id: string; - time: number; + time: MediaTime; value: number; interpolation?: AnimationInterpolation; previousKey?: ScalarAnimationKey; @@ -195,7 +201,7 @@ function createDiscreteKey({ value, }: { id: string; - time: number; + time: MediaTime; value: string | boolean; }): DiscreteAnimationKey { return { @@ -241,7 +247,7 @@ function getTargetKeyMetadata({ keyframeId, }: { channel: AnimationChannel | undefined; - time: number; + time: MediaTime; keyframeId?: string; }) { const normalizedChannel = @@ -280,7 +286,7 @@ function upsertDiscreteChannelKey({ keyframeId, }: { channel: DiscreteAnimationChannel | undefined; - time: number; + time: MediaTime; value: string | boolean; keyframeId?: string; }): DiscreteAnimationChannel { @@ -337,7 +343,7 @@ function upsertScalarChannelKey({ keyframeId, }: { channel: ScalarAnimationChannel | undefined; - time: number; + time: MediaTime; value: number; interpolation?: AnimationInterpolation; defaultInterpolation?: AnimationInterpolation; @@ -442,7 +448,7 @@ export function upsertPathKeyframe({ }: { animations: ElementAnimations | undefined; propertyPath: AnimationPath; - time: number; + time: MediaTime; value: AnimationValue; interpolation?: AnimationInterpolation; keyframeId?: string; @@ -537,7 +543,7 @@ export function upsertElementKeyframe({ }: { animations: ElementAnimations | undefined; propertyPath: AnimationPropertyPath; - time: number; + time: MediaTime; value: AnimationValue; interpolation?: AnimationInterpolation; keyframeId?: string; @@ -576,7 +582,7 @@ export function upsertKeyframe({ keyframeId, }: { channel: AnimationChannel | undefined; - time: number; + time: MediaTime; value: AnimationValue; interpolation?: AnimationInterpolation; keyframeId?: string; @@ -642,7 +648,7 @@ export function retimeKeyframe({ }: { channel: AnimationChannel | undefined; keyframeId: string; - time: number; + time: MediaTime; }): AnimationChannel | undefined { if (!channel) { return undefined; @@ -887,7 +893,7 @@ export function clampAnimationsToDuration({ duration, }: { animations: ElementAnimations | undefined; - duration: number; + duration: MediaTime; }): ElementAnimations | undefined { if (!animations || duration <= 0) { return undefined; @@ -923,7 +929,7 @@ function splitDiscreteChannelAtTime({ shouldIncludeSplitBoundary, }: { channel: DiscreteAnimationChannel | undefined; - splitTime: number; + splitTime: MediaTime; leftBoundaryId: string; rightBoundaryId: string; shouldIncludeSplitBoundary: boolean; @@ -939,7 +945,10 @@ function splitDiscreteChannelAtTime({ let leftKeys = normalizedChannel.keys.filter((key) => key.time <= splitTime); let rightKeys = normalizedChannel.keys .filter((key) => key.time >= splitTime) - .map((key) => ({ ...key, time: key.time - splitTime })); + .map((key) => ({ + ...key, + time: subMediaTime({ a: key.time, b: splitTime }), + })); if (shouldIncludeSplitBoundary) { const hasBoundaryOnLeft = leftKeys.some((key) => @@ -967,7 +976,7 @@ function splitDiscreteChannelAtTime({ rightKeys = [ createDiscreteKey({ id: rightBoundaryId, - time: 0, + time: ZERO_MEDIA_TIME, value: boundaryValue as string | boolean, }), ...rightKeys, @@ -993,7 +1002,7 @@ function splitScalarChannelAtTime({ shouldIncludeSplitBoundary, }: { channel: ScalarAnimationChannel | undefined; - splitTime: number; + splitTime: MediaTime; leftBoundaryId: string; rightBoundaryId: string; shouldIncludeSplitBoundary: boolean; @@ -1009,7 +1018,10 @@ function splitScalarChannelAtTime({ let leftKeys = normalizedChannel.keys.filter((key) => key.time <= splitTime); let rightKeys = normalizedChannel.keys .filter((key) => key.time >= splitTime) - .map((key) => ({ ...key, time: key.time - splitTime })); + .map((key) => ({ + ...key, + time: subMediaTime({ a: key.time, b: splitTime }), + })); const hasBoundaryOnLeft = leftKeys.some((key) => isNearlySameTime({ leftTime: key.time, rightTime: splitTime }), @@ -1089,7 +1101,7 @@ function splitScalarChannelAtTime({ { ...leftKey, rightHandle: { - dt: q0.x - p0.x, + dt: roundMediaTime({ time: q0.x - p0.x }), dv: q0.y - p0.y, }, }, @@ -1098,7 +1110,7 @@ function splitScalarChannelAtTime({ time: splitTime, value: boundaryValue, leftHandle: { - dt: r0.x - splitPoint.x, + dt: roundMediaTime({ time: r0.x - splitPoint.x }), dv: r0.y - splitPoint.y, }, segmentToNext: leftKey.segmentToNext, @@ -1108,10 +1120,10 @@ function splitScalarChannelAtTime({ rightKeys = [ { id: rightBoundaryId, - time: 0, + time: ZERO_MEDIA_TIME, value: boundaryValue, rightHandle: { - dt: r1.x - splitPoint.x, + dt: roundMediaTime({ time: r1.x - splitPoint.x }), dv: r1.y - splitPoint.y, }, segmentToNext: "bezier", @@ -1119,9 +1131,9 @@ function splitScalarChannelAtTime({ }, { ...rightKey, - time: rightKey.time - splitTime, + time: subMediaTime({ a: rightKey.time, b: splitTime }), leftHandle: { - dt: q2.x - p3.x, + dt: roundMediaTime({ time: q2.x - p3.x }), dv: q2.y - p3.y, }, }, @@ -1129,7 +1141,7 @@ function splitScalarChannelAtTime({ .filter((key) => key.time > rightKey.time) .map((key) => ({ ...key, - time: key.time - splitTime, + time: subMediaTime({ a: key.time, b: splitTime }), })), ]; } else { @@ -1145,7 +1157,7 @@ function splitScalarChannelAtTime({ rightKeys = [ createScalarKey({ id: rightBoundaryId, - time: 0, + time: ZERO_MEDIA_TIME, value: boundaryValue, interpolation: getScalarSegmentInterpolation({ segment: leftKey.segmentToNext, @@ -1201,7 +1213,7 @@ export function splitAnimationsAtTime({ shouldIncludeSplitBoundary = true, }: { animations: ElementAnimations | undefined; - splitTime: number; + splitTime: MediaTime; shouldIncludeSplitBoundary?: boolean; }): { leftAnimations: ElementAnimations | undefined; @@ -1317,7 +1329,7 @@ export function retimeElementKeyframe({ animations: ElementAnimations | undefined; propertyPath: AnimationPath; keyframeId: string; - time: number; + time: MediaTime; }): ElementAnimations | undefined { const binding = getBinding({ animations, propertyPath }); if (!binding) { diff --git a/apps/web/src/animation/types.ts b/apps/web/src/animation/types.ts index 3afddfa1..e02c8ba8 100644 --- a/apps/web/src/animation/types.ts +++ b/apps/web/src/animation/types.ts @@ -1,4 +1,5 @@ import type { ParamValues } from "@/params"; +import type { MediaTime } from "@/wasm"; export const ANIMATION_PROPERTY_PATHS = [ "transform.positionX", @@ -77,13 +78,13 @@ export type TangentMode = "auto" | "aligned" | "broken" | "flat"; export type ChannelExtrapolationMode = "hold" | "linear"; export interface CurveHandle { - dt: number; + dt: MediaTime; dv: number; } interface BaseAnimationKeyframe<TValue extends number | DiscreteValue> { id: string; - time: number; // relative to element start time + time: MediaTime; // relative to element start time value: TValue; } @@ -209,7 +210,7 @@ export interface ScalarCurveKeyframePatch { export interface ElementKeyframe { propertyPath: AnimationPath; id: string; - time: number; + time: MediaTime; value: AnimationValue; interpolation: AnimationInterpolation; } |
