studio3d / src /utils /timeline.ts
varunm2004's picture
Create timeline.ts
1cb6ef5 verified
export interface Keyframe {
id: string;
time: number;
position: [number, number, number];
rotation: [number, number, number];
scale: [number, number, number];
easing: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
}
export interface Track {
objectId: string;
keyframes: Keyframe[];
}
export function interpolateKF(
a: Keyframe,
b: Keyframe,
t: number
): { position: [number,number,number]; rotation: [number,number,number]; scale: [number,number,number] } {
const ease = (x: number, type: Keyframe['easing']): number => {
switch (type) {
case 'ease-in': return x * x;
case 'ease-out': return 1 - (1 - x) * (1 - x);
case 'ease-in-out': return x < 0.5 ? 2*x*x : 1 - Math.pow(-2*x+2, 2)/2;
default: return x;
}
};
const et = ease(t, b.easing);
const lerp = (a: number, b: number) => a + (b - a) * et;
const lerpV3 = (
a: [number,number,number],
b: [number,number,number]
): [number,number,number] => [
lerp(a[0], b[0]),
lerp(a[1], b[1]),
lerp(a[2], b[2]),
];
return {
position: lerpV3(a.position, b.position),
rotation: lerpV3(a.rotation, b.rotation),
scale: lerpV3(a.scale, b.scale),
};
}
export function getFramesAtTime(keyframes: Keyframe[], time: number) {
const sorted = [...keyframes].sort((a, b) => a.time - b.time);
if (!sorted.length) return null;
if (time <= sorted[0].time) return { a: sorted[0], b: sorted[0], t: 0 };
const last = sorted[sorted.length - 1];
if (time >= last.time) return { a: last, b: last, t: 0 };
for (let i = 0; i < sorted.length - 1; i++) {
if (time >= sorted[i].time && time <= sorted[i+1].time) {
const span = sorted[i+1].time - sorted[i].time;
const t = span === 0 ? 0 : (time - sorted[i].time) / span;
return { a: sorted[i], b: sorted[i+1], t };
}
}
return null;
}