feat(timeline): 支持拖拽调整时长与运镜/轨迹关键帧时间
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
applyExternalViewSnapshot,
|
||||
applySnapshotToControls,
|
||||
DEFAULT_DIRECTOR_VIEW_SNAPSHOT,
|
||||
getViewportGizmoHitButtonStyle,
|
||||
@@ -43,6 +44,27 @@ it("flushes leftover orbit inertia before writing the reset view back", () => {
|
||||
expect(camera.position.toArray()).toEqual(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.position);
|
||||
});
|
||||
|
||||
it("writes an external view change on the spot instead of deferring it to the next render", () => {
|
||||
// 机位视角下没有 OrbitControls:写不进去,交给切回导演视角时的副作用补写
|
||||
expect(applyExternalViewSnapshot(null, DEFAULT_DIRECTOR_VIEW_SNAPSHOT)).toBe(false);
|
||||
|
||||
const dampingStateDuringUpdate: boolean[] = [];
|
||||
const controls = {
|
||||
enableDamping: true,
|
||||
target: new Vector3(9, 9, 9),
|
||||
object: new PerspectiveCamera(),
|
||||
update: () => dampingStateDuringUpdate.push(controls.enableDamping),
|
||||
};
|
||||
|
||||
expect(applyExternalViewSnapshot(controls, DEFAULT_DIRECTOR_VIEW_SNAPSHOT)).toBe(true);
|
||||
|
||||
// 当场就把残余惯性结算掉并落到目标视角(惯性滑行中点重置视角不会再被推走)
|
||||
expect(dampingStateDuringUpdate).toEqual([false, false]);
|
||||
expect(controls.target.toArray()).toEqual(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.target);
|
||||
expect(controls.object.position.toArray()).toEqual(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.position);
|
||||
expect(controls.enableDamping).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps the viewport grid visible when a panorama background is active", () => {
|
||||
expect(shouldRenderViewportGrid(true, false)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -166,6 +166,20 @@ function applySnapshotToCamera(camera: ThreePerspectiveCamera, snapshot: CameraS
|
||||
camera.updateMatrixWorld();
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部改视角(重置视角 / 坐标控件切轴)时「当场」结算残余惯性并把视角写回相机与 OrbitControls。
|
||||
* 返回是否真的写回去了 —— 机位视角下没有 OrbitControls,只能等切回导演视角时由副作用补写。
|
||||
*/
|
||||
export function applyExternalViewSnapshot(
|
||||
controls: { enableDamping: boolean; target: Vector3; update: () => void; object: unknown } | null,
|
||||
snapshot: CameraShotSnapshot
|
||||
): boolean {
|
||||
if (!controls) return false;
|
||||
|
||||
applySnapshotToControls(controls, controls.object as ThreePerspectiveCamera, snapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把视角快照写回相机 + OrbitControls(重置视角、坐标控件切轴、切回导演视角都走这里)。
|
||||
*
|
||||
@@ -747,13 +761,28 @@ export function DirectorCanvas() {
|
||||
return viewportCameraSnapshotRef.current;
|
||||
}
|
||||
|
||||
/** 记录/广播导演视角快照;source 决定 DirectorViewCameraSync 要不要把它写回相机 */
|
||||
/**
|
||||
* 记录/广播导演视角快照;source 决定这次变化是谁产生的。
|
||||
*
|
||||
* 外部改视角(重置视角 / 坐标控件切轴)时**当场写回**相机 + OrbitControls,
|
||||
* 不能只丢给 DirectorViewCameraSync 的 layout effect。惯性滑行时有两道闸门会把它吞掉:
|
||||
* 1. 快照可能因为「和当前视角足够接近」被 React 合并掉 → 状态没变 → 副作用不会重跑;
|
||||
* 2. OrbitControls 每帧都抛 change,把来源标成 "controls",effect 里那道
|
||||
* 「用户自己在操作就别写回」的闸门会顺手拦掉这次外部请求。
|
||||
* 两者都表现为「点了重置视角没有反应、镜头继续滑」。当场结算残余惯性最稳;
|
||||
* 随后 effect 再跑到也只是同值重写(幂等)。
|
||||
*/
|
||||
function updateDirectorViewSnapshot(
|
||||
snapshot: CameraShotSnapshot,
|
||||
source: ViewportSnapshotSource = "external"
|
||||
) {
|
||||
viewportCameraSnapshotRef.current = snapshot;
|
||||
viewportSnapshotSourceRef.current = source;
|
||||
|
||||
if (source === "external") {
|
||||
applyExternalViewSnapshot(controlsRef.current, snapshot);
|
||||
}
|
||||
|
||||
setDirectorViewSnapshot((currentSnapshot) =>
|
||||
areCameraSnapshotsClose(currentSnapshot, snapshot) ? currentSnapshot : snapshot
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
VIEWPORT_CAMERA_VISUAL_SCALE,
|
||||
} from "../schema/cameraGeometry";
|
||||
import { VIEWPORT_OBJECT_LABEL_VERTICAL_GAP } from "../schema/viewportLabels";
|
||||
import { evaluateTrajectoryMotionAtTime, getTrajectoryPointTime } from "../schema/trajectoryMotion";
|
||||
import { evaluateTrajectoryMotionAtTime, getTrajectoryPointTimes } from "../schema/trajectoryMotion";
|
||||
import type { TransformMode } from "../store/directorStore";
|
||||
import { useDirectorStore } from "../store/directorStore";
|
||||
import { usePlaybackStore } from "../runtime/playbackStore";
|
||||
@@ -920,7 +920,7 @@ function TrajectoryPath({
|
||||
if (pointIndex < 0) return;
|
||||
|
||||
setPlaying(false);
|
||||
setPlayhead(getTrajectoryPointTime(pointIndex, points.length, playbackDuration));
|
||||
setPlayhead(getTrajectoryPointTimes(points, playbackDuration)[pointIndex] ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user