138 lines
4.4 KiB
TypeScript
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),
|
|
})),
|
|
};
|
|
}
|