ai-chatbot/lib/video-canvas/director-extensions.ts

138 lines
4.4 KiB
TypeScript

import { resizeObjectTracks } from "./director-object-tracks";
import {
defaultDirectorMotion,
resizeDirectorMotion,
type DirectorPose,
type DirectorMotion,
} from "./director-motion";
import type {
DirectorScene,
DirectorCamera,
DirectorVector,
DirectorObject,
} from "./director-scene";
export function commitDirectorScene(previous: DirectorScene, next: DirectorScene): DirectorScene {
const motionTracks = {
...previous.motionTracks,
...next.motionTracks,
[previous.activeCameraId]: previous.motion,
};
const motion =
previous.activeCameraId === next.activeCameraId
? next.motion
: motionTracks[next.activeCameraId] || {
...defaultDirectorMotion(),
duration: previous.motion.duration,
};
motionTracks[next.activeCameraId] = motion;
for (const id of Object.keys(motionTracks))
if (!next.cameras.some((c) => c.id === id)) delete motionTracks[id];
let objectTracks = next.objectTracks;
if (
previous.activeCameraId === next.activeCameraId &&
previous.motion.duration !== next.motion.duration
) {
for (const id of Object.keys(motionTracks))
if (id !== next.activeCameraId)
motionTracks[id] = resizeDirectorMotion(motionTracks[id], next.motion.duration);
objectTracks = resizeObjectTracks(objectTracks, previous.motion.duration, next.motion.duration);
}
objectTracks = Object.fromEntries(
Object.entries(objectTracks || {}).filter(([id]) => next.objects.some((o) => o.id === id)),
);
return { ...next, motion, motionTracks, objectTracks };
}
export function resolveDirectorCamera(scene: DirectorScene, pose?: DirectorPose): DirectorCamera {
const camera = scene.cameras.find((c) => c.id === scene.activeCameraId) || scene.cameras[0];
const subject = scene.objects.find((o) => o.id === camera.followObjectId);
const result = { ...camera, ...pose };
if (subject) {
const target: DirectorVector = [
subject.position[0],
subject.position[1] + subject.scale[1],
subject.position[2],
];
if (Math.hypot(...target.map((v, i) => v - result.position[i])) > 0.001) result.target = target;
}
return result;
}
export function cameraAngles(camera: DirectorPose) {
const [x, y, z] = camera.target.map((v, i) => v - camera.position[i]);
return {
yaw: (Math.atan2(x, -z) * 180) / Math.PI,
pitch: (Math.atan2(y, Math.hypot(x, z)) * 180) / Math.PI,
};
}
export function rotateDirectorCamera(
camera: DirectorPose,
yaw: number,
pitch: number,
): DirectorVector {
const distance = Math.max(
0.01,
Math.hypot(...camera.target.map((v, i) => v - camera.position[i])),
);
const y = (yaw * Math.PI) / 180,
p = (Math.max(-89, Math.min(89, pitch)) * Math.PI) / 180;
return [
camera.position[0] + distance * Math.sin(y) * Math.cos(p),
camera.position[1] + distance * Math.sin(p),
camera.position[2] - distance * Math.cos(y) * Math.cos(p),
];
}
export const directorBodies = {
标准: [1, 1, 1],
高挑: [0.85, 1.15, 1],
矮壮: [1.2, 0.85, 1.1],
纤细: [0.7, 1, 0.95],
健壮: [1.35, 1.08, 1],
宽厚: [1.5, 1, 1.05],
幼童: [0.7, 0.6, 1.4],
大头: [1, 0.85, 1.5],
} as const;
export function createDirectorCrowd(
object: DirectorObject,
existingCount: number,
id: () => string,
) {
if (existingCount + 8 > 200) throw Error("场景最多支持 200 个对象");
return Array.from({ length: 8 }, (_, index) => {
const cell = index + 1;
return {
...object,
id: id(),
name: `${object.name} 阵列 ${cell}`,
position: [
object.position[0] + (cell % 3) * 1.5,
object.position[1],
object.position[2] + Math.floor(cell / 3) * 1.5,
] as DirectorVector,
};
});
}
export function transformDirectorMotion(
motion: DirectorMotion,
translation: DirectorVector,
yawDegrees: number,
): DirectorMotion {
const origin = motion.keyframes[0]?.target || [0, 0, 0],
angle = (yawDegrees * Math.PI) / 180;
const transform = (v: DirectorVector): DirectorVector => {
const x = v[0] - origin[0],
z = v[2] - origin[2];
return [
origin[0] + x * Math.cos(angle) - z * Math.sin(angle) + translation[0],
v[1] + translation[1],
origin[2] + x * Math.sin(angle) + z * Math.cos(angle) + translation[2],
];
};
return {
...motion,
keyframes: motion.keyframes.map((k) => ({
...k,
position: transform(k.position),
target: transform(k.target),
})),
};
}