fix(canvas): 导演台人物 - 缩小 Dock 并统一基础人偶初始化
This commit is contained in:
@@ -337,10 +337,10 @@ function ObjectInspector({ object, playhead, selectedBone, onSelectBone, onUpdat
|
||||
const applyPose = (pose: DirectorPose) => onUpdate({ pose, activeMotionClipId: undefined, boneOverrides: {} });
|
||||
return <Inspector title={object.name} onTitleChange={(name) => onUpdate({ name })} onDelete={onDelete}>
|
||||
<TransformFields transform={object.transform} onChange={(transform) => onUpdate({ transform })} />
|
||||
{object.kind === "actor"
|
||||
{object.kind === "actor" || object.primitive === "character"
|
||||
? <Field label="角色颜色"><div className="director-actor-colors">{DIRECTOR_ACTOR_COLORS.map((color) => <button key={color} type="button" className={`director-actor-color ${object.color.toLowerCase() === color ? "is-active" : ""}`} style={{ background: color }} aria-label={`设置颜色 ${color}`} onClick={() => onUpdate({ color })} />)}<ColorPicker value={object.color} size="small" onChange={(_, color) => onUpdate({ color })} /></div></Field>
|
||||
: <Field label="颜色"><ColorPicker value={object.color} onChange={(_, color) => onUpdate({ color })} /></Field>}
|
||||
{object.kind === "actor" || motionClips.length ? <>
|
||||
{object.kind === "actor" || object.primitive === "character" || motionClips.length ? <>
|
||||
<section className="director-pose-section">
|
||||
<div className="director-inspector-section-title"><span>姿势预设</span><span>{directorPoseLabel(object.pose || "stand")}</span></div>
|
||||
<div className="director-pose-grid">{poseOptions.map((option) => <button key={option.value} type="button" className={`director-pose-button ${object.pose === option.value && !object.activeMotionClipId ? "is-active" : ""}`} title={option.label} onClick={() => applyPose(option.value)}>{option.label}</button>)}</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AnimationClip, AnimationMixer, Box3, Bone, Color, Group, LoopOnce, Loop
|
||||
import type { Material } from "three";
|
||||
import { GLTFLoader, SkeletonUtils } from "three-stdlib";
|
||||
|
||||
import { directorPoseBoneDeltas, interpolateDirectorBoneRotation, interpolateDirectorTransform } from "@/lib/canvas/director/director-scene";
|
||||
import { DIRECTOR_DEFAULT_ACTOR_URL, directorPoseBoneDeltas, interpolateDirectorBoneRotation, interpolateDirectorTransform } from "@/lib/canvas/director/director-scene";
|
||||
import { resolveMediaUrl } from "@/services/file-storage";
|
||||
import type { DirectorCamera, DirectorHumanoidBone, DirectorLight, DirectorObject, DirectorQuat, DirectorRenderMode, DirectorRig, DirectorScene, DirectorTransform, DirectorVec3 } from "@/types/director";
|
||||
|
||||
@@ -181,7 +181,7 @@ function DirectorObjectView({ object, selected, selectedBone, transformMode, pla
|
||||
}
|
||||
|
||||
function DirectorObjectVisual({ object, selected, selectedBone, playhead, onSelectBone, onBoneTransform, onActorRigReady }: { object: DirectorObject; selected: boolean; selectedBone: string | null; playhead: number; onSelectBone: (bone: string | null) => void; onBoneTransform: (bone: string, rotation: DirectorQuat) => void; onActorRigReady: (rig: DirectorRig, animations: AnimationClip[]) => void }) {
|
||||
if ((object.kind === "model" || object.kind === "actor") && object.url) return <DirectorModel object={object} selected={selected} selectedBone={selectedBone} playhead={playhead} onSelectBone={onSelectBone} onBoneTransform={onBoneTransform} onActorRigReady={onActorRigReady} />;
|
||||
if ((object.kind === "model" || object.kind === "actor" || object.primitive === "character") && (object.url || object.primitive === "character")) return <DirectorModel object={object} selected={selected} selectedBone={selectedBone} playhead={playhead} onSelectBone={onSelectBone} onBoneTransform={onBoneTransform} onActorRigReady={onActorRigReady} />;
|
||||
if (object.kind === "billboard" && object.url) return <DirectorBillboard object={object} selected={selected} />;
|
||||
if (object.primitive === "character") return <DirectorCharacter object={object} selected={selected} />;
|
||||
const material = <meshStandardMaterial color={selected ? "#2f8cff" : object.color} roughness={0.68} metalness={0.05} />;
|
||||
@@ -224,18 +224,19 @@ function DirectorModel({ object, selected, selectedBone, playhead, onSelectBone,
|
||||
const selectedBoneObject = selectedBone && rig?.boneMap[selectedBone as DirectorHumanoidBone] ? model?.getObjectByName(rig.boneMap[selectedBone as DirectorHumanoidBone]!) : null;
|
||||
const motion = object.motionClips?.find((item) => item.id === object.activeMotionClipId);
|
||||
const activeAnimation = motion ? animations.find((item) => item.name === motion.sourceAnimation) : undefined;
|
||||
const modelUrl = object.kind === "actor" || object.primitive === "character" ? DIRECTOR_DEFAULT_ACTOR_URL : object.url;
|
||||
|
||||
useEffect(() => { onActorRigReadyRef.current = onActorRigReady; }, [onActorRigReady]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
const loader = new GLTFLoader();
|
||||
void resolveMediaUrl(object.storageKey, object.url).then((url) => loader.load(url, (gltf) => {
|
||||
void resolveMediaUrl(object.storageKey, modelUrl).then((url) => loader.load(url, (gltf) => {
|
||||
if (!active) return;
|
||||
const next = SkeletonUtils.clone(gltf.scene);
|
||||
normalizeModel(next, object.castShadow, object.receiveShadow);
|
||||
const nextRig = inferDirectorRig(next, gltf.animations.map((clip) => clip.name));
|
||||
if (object.kind === "actor") applyActorReferenceMaterial(next, object.color);
|
||||
if (object.kind === "actor" || object.primitive === "character") applyActorReferenceMaterial(next, object.color);
|
||||
mixerRef.current = new AnimationMixer(next);
|
||||
setRig(nextRig);
|
||||
setRestRotations(readRigRestRotations(next, nextRig));
|
||||
@@ -248,7 +249,7 @@ function DirectorModel({ object, selected, selectedBone, playhead, onSelectBone,
|
||||
mixerRef.current?.stopAllAction();
|
||||
mixerRef.current = null;
|
||||
};
|
||||
}, [object.kind, object.storageKey, object.url]);
|
||||
}, [modelUrl, object.kind, object.storageKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!model) return;
|
||||
@@ -262,7 +263,7 @@ function DirectorModel({ object, selected, selectedBone, playhead, onSelectBone,
|
||||
}, [invalidate, model, object.castShadow, object.receiveShadow]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!model || object.kind !== "actor") return;
|
||||
if (!model || (object.kind !== "actor" && object.primitive !== "character")) return;
|
||||
updateActorReferenceColor(model, object.color);
|
||||
invalidate();
|
||||
}, [invalidate, model, object.color, object.kind]);
|
||||
|
||||
@@ -26,7 +26,7 @@ const cameraMoveLabels: Record<DirectorShot["cameraMove"], string> = {
|
||||
export function compileDirectorPrompt(scene: DirectorScene, shot: DirectorShot) {
|
||||
const camera = scene.cameras.find((item) => item.id === shot.cameraId) || scene.cameras[0];
|
||||
const visibleObjects = scene.objects.filter((item) => item.visible);
|
||||
const actors = visibleObjects.filter((item) => item.kind === "actor");
|
||||
const actors = visibleObjects.filter((item) => item.kind === "actor" || item.primitive === "character");
|
||||
return [
|
||||
shot.prompt.trim(),
|
||||
`镜头设计:${shotSizeLabels[shot.shotSize]},${cameraMoveLabels[shot.cameraMove]},时长 ${formatNumber(shot.duration)} 秒。`,
|
||||
@@ -52,7 +52,7 @@ function cameraPrompt(camera: DirectorCamera) {
|
||||
function objectPrompt(object: DirectorObject) {
|
||||
const [x, y, z] = object.transform.position;
|
||||
const pose = object.pose ? `,姿势${directorPoseLabel(object.pose)}` : "";
|
||||
const color = object.kind === "actor" ? `,${directorColorLabel(object.color)}参考人偶` : "";
|
||||
const color = object.kind === "actor" || object.primitive === "character" ? `,${directorColorLabel(object.color)}参考人偶` : "";
|
||||
return `${object.name}${color}位于 (${formatNumber(x)}, ${formatNumber(y)}, ${formatNumber(z)})${pose}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Color, Euler, Quaternion } from "three";
|
||||
|
||||
import type { DirectorBoneKeyframe, DirectorBoneTrack, DirectorCamera, DirectorHumanoidBone, DirectorKeyframe, DirectorLight, DirectorObject, DirectorPose, DirectorQuat, DirectorScene, DirectorTransform, DirectorVec3 } from "@/types/director";
|
||||
|
||||
export const DIRECTOR_DEFAULT_ACTOR_URL = "https://cdn.jsdelivr.net/gh/mrdoob/three.js@r185/examples/models/gltf/Soldier.glb";
|
||||
export const DIRECTOR_DEFAULT_ACTOR_URL = "https://cdn.jsdelivr.net/gh/mrdoob/three.js@r185/examples/models/gltf/Xbot.glb";
|
||||
export const DIRECTOR_ACTOR_COLORS = ["#f1f3f5", "#202329", "#2f7de1", "#d84949", "#dfae3f", "#34a276"] as const;
|
||||
|
||||
export const directorIdentityTransform = (position: DirectorVec3 = [0, 0, 0]): DirectorTransform => ({ position, rotation: [0, 0, 0], scale: [1, 1, 1] });
|
||||
@@ -19,10 +19,7 @@ export function createDirectorScene(title = "未命名场景"): DirectorScene {
|
||||
background: "#d8dde3",
|
||||
environmentIntensity: 0.7,
|
||||
gridVisible: true,
|
||||
objects: [
|
||||
createDirectorActor("演员 1", [0, 0, 0]),
|
||||
createDirectorObject("box", "前景道具", [1.8, 0.45, 0.8], "#8b7355"),
|
||||
],
|
||||
objects: [createDirectorActor("演员 1", [0, 0, 0])],
|
||||
cameras: [camera],
|
||||
lights: [createDirectorLight("directional", "主光", [4, 6, 4], 2.4), createDirectorLight("directional", "轮廓光", [-4, 3, -2], 1.1), createDirectorLight("ambient", "环境光", [0, 0, 0], 0.65)],
|
||||
shots: [{ id: shotId, name: "镜头 1", cameraId: camera.id, duration: 5, fps: 24, shotSize: "medium", cameraMove: "static", prompt: "" }],
|
||||
|
||||
@@ -349,6 +349,7 @@
|
||||
--director-dock-surface: rgba(17, 19, 21, 0.92);
|
||||
--director-dock-fg: rgba(255, 255, 255, 0.62);
|
||||
--director-dock-fg-strong: rgba(255, 255, 255, 0.96);
|
||||
--director-dock-active-surface: rgba(255, 255, 255, 0.14);
|
||||
--director-control-hover: rgba(255, 255, 255, 0.09);
|
||||
--director-swatch-inset: rgba(0, 0, 0, 0.18);
|
||||
|
||||
@@ -476,6 +477,7 @@
|
||||
--director-dock-surface: rgba(17, 19, 21, 0.92);
|
||||
--director-dock-fg: rgba(255, 255, 255, 0.62);
|
||||
--director-dock-fg-strong: rgba(255, 255, 255, 0.96);
|
||||
--director-dock-active-surface: rgba(255, 255, 255, 0.14);
|
||||
--director-control-hover: rgba(255, 255, 255, 0.09);
|
||||
--director-swatch-inset: rgba(0, 0, 0, 0.18);
|
||||
|
||||
@@ -555,8 +557,8 @@
|
||||
--director-sequencer-row-height: 30px;
|
||||
--director-sequencer-base-width: 860px;
|
||||
--director-sequencer-shot-select-width: 144px;
|
||||
--director-dock-button-size: var(--space-10);
|
||||
--director-dock-bottom: var(--space-3);
|
||||
--director-dock-button-size: var(--space-8);
|
||||
--director-dock-bottom: var(--space-2);
|
||||
--director-pose-button-height: var(--space-8);
|
||||
--director-color-swatch-size: var(--space-6);
|
||||
|
||||
@@ -5185,8 +5187,8 @@ html:not(.dark) .creation-model-picker-menu .canvas-model-picker-option > span:f
|
||||
display: flex;
|
||||
max-width: calc(100% - var(--space-6));
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1-half);
|
||||
gap: var(--space-half);
|
||||
padding: var(--space-1);
|
||||
overflow-x: auto;
|
||||
border: var(--stroke-1) solid var(--director-sequencer-border);
|
||||
border-radius: var(--r-lg);
|
||||
@@ -5208,8 +5210,8 @@ html:not(.dark) .creation-model-picker-menu .canvas-model-picker-option > span:f
|
||||
}
|
||||
|
||||
.director-viewport-dock-button > svg {
|
||||
width: var(--space-5);
|
||||
height: var(--space-5);
|
||||
width: var(--space-4);
|
||||
height: var(--space-4);
|
||||
}
|
||||
|
||||
.director-viewport-dock-button:hover {
|
||||
@@ -5222,14 +5224,14 @@ html:not(.dark) .creation-model-picker-menu .canvas-model-picker-option > span:f
|
||||
}
|
||||
|
||||
.director-viewport-dock-button.is-active {
|
||||
background: var(--workspace-accent);
|
||||
color: var(--color-neutral-0);
|
||||
box-shadow: var(--shadow-glow-sm);
|
||||
background: var(--director-dock-active-surface);
|
||||
color: var(--director-dock-fg-strong);
|
||||
box-shadow: inset 0 0 0 var(--stroke-1) var(--director-sequencer-border);
|
||||
}
|
||||
|
||||
.director-viewport-dock-divider {
|
||||
width: var(--stroke-1);
|
||||
height: var(--space-6);
|
||||
height: var(--space-5);
|
||||
flex: 0 0 var(--stroke-1);
|
||||
margin: 0 var(--space-half);
|
||||
background: var(--director-sequencer-border);
|
||||
|
||||
Reference in New Issue
Block a user