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