feat(timeline): 支持拖拽调整时长与运镜/轨迹关键帧时间
This commit is contained in:
1 parent
bab92c05b8
commit
8d67f17a84
21 files changed
+2556
-194
No files matched your search
@@ -37,11 +37,18 @@ import {
|
||||
removeCameraKeyframe as removeTimelineCameraKeyframe,
|
||||
updateCameraKeyframeTime as updateTimelineCameraKeyframeTime,
|
||||
updateCameraKeyframeValue as updateTimelineCameraKeyframeValue,
|
||||
setCameraKeyframeTimes as setTimelineCameraKeyframeTimes,
|
||||
updateTimelineDuration,
|
||||
updateTimelinePlaybackSettings,
|
||||
normalizeTimelineDuration,
|
||||
normalizeTimelineFps,
|
||||
DEFAULT_TIMELINE_DURATION,
|
||||
createKeyframeId,
|
||||
type KeyframeTransformValue,
|
||||
type CameraKeyframeValue,
|
||||
type Timeline,
|
||||
} from "../schema/timeline";
|
||||
import { getTrajectoryStartTime, shiftTrajectoryClip } from "../schema/trajectoryMotion";
|
||||
|
||||
export type TransformMode = "translate" | "rotate" | "scale";
|
||||
|
||||
@@ -141,8 +148,12 @@ export interface DirectorActions {
|
||||
updateTrajectoryPoint: (
|
||||
objectId: string,
|
||||
pointId: string,
|
||||
patch: Partial<Pick<DirectorTrajectoryPoint, "position" | "rotation">>
|
||||
patch: Partial<Pick<DirectorTrajectoryPoint, "position" | "rotation" | "time">>
|
||||
) => void;
|
||||
/** 一次性覆盖整条轨迹的所有关键帧时间(拖动「轨迹时长」把手时按比例缩放用) */
|
||||
setTrajectoryPointTimes: (objectId: string, times: number[]) => void;
|
||||
/** 整体平移人物轨迹片段(拖轨迹条本体):片段起点 + 所有轨迹点时间同步平移 */
|
||||
setTrajectoryClipStart: (objectId: string, startTime: number) => void;
|
||||
removeTrajectoryPoint: (objectId: string, pointId: string) => void;
|
||||
selectTrajectoryPoint: (objectId: string, pointId: string) => void;
|
||||
updateScene: (patch: Partial<SceneSettings>) => void;
|
||||
@@ -203,9 +214,13 @@ export interface DirectorActions {
|
||||
removeCameraKeyframe: (cameraId: string, keyframeId: string) => void;
|
||||
updateCameraKeyframeTime: (cameraId: string, keyframeId: string, time: number) => void;
|
||||
updateCameraKeyframeValue: (cameraId: string, keyframeId: string, value: CameraKeyframeValue) => void;
|
||||
/** 一次性覆盖整条运镜的所有关键帧时间(拖动「运镜时长」把手按比例缩放用) */
|
||||
setCameraKeyframeTimes: (cameraId: string, times: number[]) => void;
|
||||
applyCameraKeyframeToShot: (cameraId: string, value: CameraKeyframeValue) => void;
|
||||
// 时长
|
||||
setTimelineDuration: (seconds: number) => void;
|
||||
/** 时间轴播放设置(每秒帧数 / 是否循环):设置弹窗里点「完成」时一次性写入 */
|
||||
setTimelinePlaybackSettings: (patch: { fps?: number; loop?: boolean }) => void;
|
||||
clearAllKeyframes: () => void;
|
||||
beginUndoBatch: () => void;
|
||||
endUndoBatch: () => void;
|
||||
@@ -443,13 +458,21 @@ function withPersistedLocalAssets(project: DirectorProject, includePersistedLoca
|
||||
|
||||
function withTimeline(project: DirectorProject): DirectorProject {
|
||||
const timeline = project.timeline ?? createDefaultTimeline();
|
||||
// 旧工程里没有 fps / loop:补默认值(30fps、不循环),duration 也顺手夹一次
|
||||
const duration =
|
||||
typeof timeline.duration === "number" && Number.isFinite(timeline.duration)
|
||||
? timeline.duration
|
||||
: DEFAULT_TIMELINE_DURATION;
|
||||
|
||||
return {
|
||||
...project,
|
||||
version: 2,
|
||||
timeline: {
|
||||
duration: timeline.duration,
|
||||
duration: normalizeTimelineDuration(duration),
|
||||
tracks: timeline.tracks ?? [],
|
||||
cameraTracks: timeline.cameraTracks ?? [],
|
||||
fps: normalizeTimelineFps(timeline.fps),
|
||||
loop: timeline.loop === true,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1674,12 +1697,57 @@ export const useDirectorStore = create<DirectorStore>((set, get) => {
|
||||
normalizeTrajectoryRotation(2, patch.rotation[2]),
|
||||
] as [number, number, number])
|
||||
: point.rotation,
|
||||
// 关键帧时间:拖到哪儿就是哪儿(合法性由拖动时的夹取保证)
|
||||
time:
|
||||
typeof patch.time === "number" && Number.isFinite(patch.time)
|
||||
? patch.time
|
||||
: point.time,
|
||||
}
|
||||
: point
|
||||
),
|
||||
})),
|
||||
},
|
||||
})),
|
||||
setTrajectoryPointTimes: (objectId, times) =>
|
||||
commitMutation((state) => ({
|
||||
...state,
|
||||
project: {
|
||||
...state.project,
|
||||
objects: updateObjectById(state.project.objects, objectId, (item) => ({
|
||||
...item,
|
||||
trajectoryPoints: (item.trajectoryPoints ?? []).map((point, index) => {
|
||||
const time = times[index];
|
||||
return typeof time === "number" && Number.isFinite(time) ? { ...point, time } : point;
|
||||
}),
|
||||
})),
|
||||
},
|
||||
})),
|
||||
setTrajectoryClipStart: (objectId, startTime) =>
|
||||
commitMutation((state) => ({
|
||||
...state,
|
||||
project: {
|
||||
...state.project,
|
||||
objects: updateObjectById(state.project.objects, objectId, (item) => {
|
||||
const points = item.trajectoryPoints ?? [];
|
||||
if (points.length === 0) return item;
|
||||
|
||||
// 片段刚性平移:起点夹在时间轴内,所有轨迹点跟着走同样的量。
|
||||
// 点的时间在这里落成显式值,避免默认推导把片段「压缩」而不是「平移」。
|
||||
const { startTime: nextStart, times } = shiftTrajectoryClip(
|
||||
points,
|
||||
state.project.timeline.duration,
|
||||
getTrajectoryStartTime(item, state.project.timeline.duration),
|
||||
startTime
|
||||
);
|
||||
|
||||
return {
|
||||
...item,
|
||||
trajectoryStartTime: nextStart,
|
||||
trajectoryPoints: points.map((point, index) => ({ ...point, time: times[index] })),
|
||||
};
|
||||
}),
|
||||
},
|
||||
})),
|
||||
removeTrajectoryPoint: (objectId, pointId) =>
|
||||
commitMutation((state) => ({
|
||||
...state,
|
||||
@@ -2568,6 +2636,14 @@ export const useDirectorStore = create<DirectorStore>((set, get) => {
|
||||
timeline: updateTimelineCameraKeyframeValue(state.project.timeline, cameraId, keyframeId, value),
|
||||
},
|
||||
})),
|
||||
setCameraKeyframeTimes: (cameraId, times) =>
|
||||
commitMutation((state) => ({
|
||||
...state,
|
||||
project: {
|
||||
...state.project,
|
||||
timeline: setTimelineCameraKeyframeTimes(state.project.timeline, cameraId, times),
|
||||
},
|
||||
})),
|
||||
applyCameraKeyframeToShot: (cameraId, value) => {
|
||||
// 只改 position/target/fov,rotation/scale 保留 shot 原值(相机朝向由 target 决定)
|
||||
const camera = get().project.cameras.find((item) => item.id === cameraId);
|
||||
@@ -2583,7 +2659,15 @@ export const useDirectorStore = create<DirectorStore>((set, get) => {
|
||||
...state,
|
||||
project: {
|
||||
...state.project,
|
||||
timeline: { ...state.project.timeline, duration: Math.max(0.1, seconds) },
|
||||
timeline: updateTimelineDuration(state.project.timeline, seconds),
|
||||
},
|
||||
})),
|
||||
setTimelinePlaybackSettings: (patch) =>
|
||||
commitMutation((state) => ({
|
||||
...state,
|
||||
project: {
|
||||
...state.project,
|
||||
timeline: updateTimelinePlaybackSettings(state.project.timeline, patch),
|
||||
},
|
||||
})),
|
||||
clearAllKeyframes: () =>
|
||||
|
||||
Reference in new issue
Block a user