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

70 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { z } from "zod";
import { defaultDirectorScene, type DirectorScene } from "./director-scene";
import { validDirectorPose } from "./director-motion";
const vector = z.tuple([
z.number().finite().min(-100).max(100),
z.number().finite().min(-100).max(100),
z.number().finite().min(-100).max(100),
]);
const scale = z.tuple([
z.number().min(0.05).max(30),
z.number().min(0.05).max(30),
z.number().min(0.05).max(30),
]);
const reconstructionSchema = z.object({
summary: z.string().min(1).max(2000),
objects: z
.array(
z.object({
name: z.string().min(1).max(80),
kind: z.enum(["actor", "box", "sphere"]),
position: vector,
rotation: vector,
scale,
color: z.string().regex(/^#[0-9a-f]{6}$/i),
}),
)
.min(1)
.max(30),
camera: z
.object({ position: vector, target: vector, fov: z.number().min(15).max(90) })
.refine(validDirectorPose, "相机不能与注视点重合"),
});
export function reconstructionPrompt(prompt: string) {
return `根据参考图重建可编辑的3D布景(灰盒),不是输出图片或GLB。只输出JSON,字段:summary、objects、camera。只重建看得见的主要人物和物体,忽略细碎纹理,不编造遮挡后的形状。以米为近似单位,Y轴向上,地面y=0;对象局部几何 actor 约2米高,box边长1米,sphere直径1米,scale必须据此设置;对象中心/脚底位置遵循 actor脚底、box和sphere中心。rotation是弧度。所有坐标在±100内,scale在0.05到30内。objects最多30项,每项{name,kind:"actor"|"box"|"sphere",position:[x,y,z],rotation:[rx,ry,rz],scale:[sx,sy,sz],color:"#RRGGBB"}。camera={position:[x,y,z],target:[x,y,z],fov:15到90},还原参考图的构图。summary说明不确定的比例与缺失信息。用户布景要求:${prompt || "尽量保留原图主体布局与机位"}`;
}
export function parseReconstructedScene(value: unknown): {
summary: string;
directorScene: DirectorScene;
} {
const parsed = reconstructionSchema.parse(value),
base = defaultDirectorScene();
return {
summary: parsed.summary,
directorScene: {
...base,
objects: parsed.objects.map((o, i) => ({ ...o, id: `reconstructed-${i}` })),
cameras: [{ ...parsed.camera, id: "reconstructed-camera", name: "参考图机位" }],
activeCameraId: "reconstructed-camera",
},
};
}
export function insertReconstructedScene(
current: DirectorScene,
generated: DirectorScene,
replace: boolean,
id: () => string,
): DirectorScene {
const objects = generated.objects.map((o) => ({ ...o, id: id() }));
if ((replace ? 0 : current.objects.length) + objects.length > 200)
throw Error("场景最多支持 200 个对象");
const camera = { ...generated.cameras[0], id: id(), followObjectId: undefined };
return {
...current,
objects: replace ? objects : [...current.objects, ...objects],
cameras: [...current.cameras, camera],
activeCameraId: camera.id,
...(replace ? { objectTracks: {} } : {}),
};
}