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), })), }; }