148 lines
4.9 KiB
TypeScript
148 lines
4.9 KiB
TypeScript
import type { DirectorScene, DirectorObject, DirectorVector } from "./director-scene";
|
|
export type DirectorObjectFrame = {
|
|
id: string;
|
|
time: number;
|
|
position: DirectorVector;
|
|
rotation: DirectorVector;
|
|
scale: DirectorVector;
|
|
visible: boolean;
|
|
};
|
|
export type DirectorObjectTrack = {
|
|
muted: boolean;
|
|
easing: "linear" | "smooth";
|
|
keyframes: DirectorObjectFrame[];
|
|
};
|
|
export function readObjectTracks(
|
|
value: unknown,
|
|
objects: DirectorObject[],
|
|
duration: number,
|
|
): Record<string, DirectorObjectTrack> {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
const vector = (v: any, positive = false) =>
|
|
Array.isArray(v) &&
|
|
v.length === 3 &&
|
|
v.every(
|
|
(n: any) =>
|
|
typeof n === "number" && Number.isFinite(n) && Math.abs(n) <= 10000 && (!positive || n > 0),
|
|
);
|
|
return Object.fromEntries(
|
|
Object.entries(value)
|
|
.filter(([id]) => objects.some((o) => o.id === id))
|
|
.map(([id, raw]) => {
|
|
const t = raw as any,
|
|
seen = new Set<number>(),
|
|
ids = new Set<string>();
|
|
const keyframes = (Array.isArray(t?.keyframes) ? t.keyframes : [])
|
|
.filter((k: any) => {
|
|
if (
|
|
!k ||
|
|
typeof k.id !== "string" ||
|
|
ids.has(k.id) ||
|
|
typeof k.time !== "number" ||
|
|
!Number.isFinite(k.time) ||
|
|
k.time < 0 ||
|
|
k.time > duration ||
|
|
seen.has(k.time) ||
|
|
!vector(k.position) ||
|
|
!vector(k.rotation) ||
|
|
!vector(k.scale, true)
|
|
)
|
|
return false;
|
|
seen.add(k.time);
|
|
ids.add(k.id);
|
|
return true;
|
|
})
|
|
.slice(0, 100)
|
|
.map((k: any) => ({
|
|
id: k.id,
|
|
time: k.time,
|
|
position: [...k.position],
|
|
rotation: [...k.rotation],
|
|
scale: [...k.scale],
|
|
visible: k.visible !== false,
|
|
}))
|
|
.sort((a: any, b: any) => a.time - b.time);
|
|
return [
|
|
id,
|
|
{
|
|
muted: t?.muted === true,
|
|
easing: t?.easing === "smooth" ? "smooth" : "linear",
|
|
keyframes,
|
|
},
|
|
];
|
|
}),
|
|
);
|
|
}
|
|
export function sampleDirectorObjects(scene: DirectorScene, time: number | null): DirectorObject[] {
|
|
if (time === null) return scene.objects;
|
|
return scene.objects.map((object) => {
|
|
const track = scene.objectTracks?.[object.id];
|
|
if (!track || track.muted || !track.keyframes.length) return object;
|
|
const keys = track.keyframes,
|
|
t = Math.max(0, Math.min(scene.motion.duration, time)),
|
|
right = keys.findIndex((k) => k.time > t);
|
|
const a = right < 0 ? keys[keys.length - 1] : keys[Math.max(0, right - 1)],
|
|
b = right < 0 ? a : keys[right];
|
|
let u = a === b ? 0 : Math.max(0, Math.min(1, (t - a.time) / (b.time - a.time)));
|
|
if (track.easing === "smooth") u = u * u * (3 - 2 * u);
|
|
const vector = (x: DirectorVector, y: DirectorVector, rotation = false): DirectorVector =>
|
|
x.map((n, i) => {
|
|
let delta = y[i] - n;
|
|
if (rotation) delta = Math.atan2(Math.sin(delta), Math.cos(delta));
|
|
return n + delta * u;
|
|
}) as DirectorVector;
|
|
return {
|
|
...object,
|
|
position: vector(a.position, b.position),
|
|
rotation: vector(a.rotation, b.rotation, true),
|
|
scale: vector(a.scale, b.scale),
|
|
visible: a.visible,
|
|
};
|
|
});
|
|
}
|
|
export function recordObjectFrames(
|
|
scene: DirectorScene,
|
|
ids: string[],
|
|
time: number,
|
|
id: () => string,
|
|
): DirectorScene {
|
|
if (!Number.isFinite(time) || time < 0 || time > scene.motion.duration)
|
|
throw Error("关键帧时间超出时间轴");
|
|
const tracks = { ...scene.objectTracks };
|
|
for (const object of scene.objects.filter((o) => ids.includes(o.id))) {
|
|
const track = tracks[object.id] || { muted: false, easing: "linear" as const, keyframes: [] };
|
|
const existing = track.keyframes.find((k) => Math.abs(k.time - time) < 0.01);
|
|
if (!existing && track.keyframes.length >= 100) throw Error("每个对象最多 100 个关键帧");
|
|
const frame = {
|
|
id: existing?.id || id(),
|
|
time: existing?.time ?? Math.round(time * 100) / 100,
|
|
position: [...object.position] as DirectorVector,
|
|
rotation: [...object.rotation] as DirectorVector,
|
|
scale: [...object.scale] as DirectorVector,
|
|
visible: object.visible !== false,
|
|
};
|
|
tracks[object.id] = {
|
|
...track,
|
|
keyframes: [...track.keyframes.filter((k) => k.id !== frame.id), frame].sort(
|
|
(a, b) => a.time - b.time,
|
|
),
|
|
};
|
|
}
|
|
return { ...scene, objectTracks: tracks };
|
|
}
|
|
export function resizeObjectTracks(
|
|
tracks: Record<string, DirectorObjectTrack> | undefined,
|
|
oldDuration: number,
|
|
newDuration: number,
|
|
) {
|
|
return Object.fromEntries(
|
|
Object.entries(tracks || {}).map(([id, t]) => [
|
|
id,
|
|
{
|
|
...t,
|
|
keyframes: t.keyframes.map((k) => ({ ...k, time: (k.time * newDuration) / oldDuration })),
|
|
},
|
|
]),
|
|
);
|
|
}
|