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 { 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(), ids = new Set(); 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 | 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 })), }, ]), ); }