feat(timeline): 支持角色轨迹关键帧与轨道拖拽排序
This commit is contained in:
@@ -30,6 +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 type { TransformMode } from "../store/directorStore";
|
||||
import { useDirectorStore } from "../store/directorStore";
|
||||
import { usePlaybackStore } from "../runtime/playbackStore";
|
||||
@@ -498,14 +499,20 @@ function ObjectSceneNode({
|
||||
const updateObjectTransform = useDirectorStore((state) => state.updateObjectTransform);
|
||||
const isPlaying = usePlaybackStore((state) => state.isPlaying);
|
||||
const motionTime = usePlaybackStore((state) => state.playhead);
|
||||
// 当前选中的轨迹点若属于本角色,则把角色「预览」到该路点的位置与朝向上,
|
||||
// 让用户直接看到角色在某个轨迹点的姿态(水平角/俯仰角/横滚角)。
|
||||
// 播放时角色要沿路径动画,故预览只在非播放时生效(拖拽 gizmo 也已排除播放态)。
|
||||
const playbackDuration = usePlaybackStore((state) => state.duration);
|
||||
// 当前选中的轨迹点若属于本角色,则抑制角色自身的变换 gizmo(拖拽交给轨迹点 gizmo)。
|
||||
const selectedTrajectoryPoint = useDirectorStore((state) => state.selectedTrajectoryPoint);
|
||||
const activeTrajectoryPoint =
|
||||
!isPlaying && selectedTrajectoryPoint?.objectId === item.id
|
||||
? item.trajectoryPoints?.find((point) => point.id === selectedTrajectoryPoint.pointId)
|
||||
: undefined;
|
||||
// 带轨迹的角色:位置 / 朝向由「人物轨迹 + 播放头」决定。
|
||||
// 这里把同一个结果也算进 props,和 PlaybackTransformSync 每帧的写入保持一致,
|
||||
// 否则 React 每次重渲染都会把角色拉回起始姿态,和轨迹走位互相打架(表现为抖动/闪回)。
|
||||
const trajectoryMotion = useMemo(
|
||||
() => evaluateTrajectoryMotionAtTime(item, playbackDuration, motionTime),
|
||||
[item, playbackDuration, motionTime]
|
||||
);
|
||||
const isImportedModel = asset?.sourceType === "model";
|
||||
const characterLabelKey = `${item.id}:${item.bodyType ?? ""}:${item.characterRig?.rigType ?? ""}`;
|
||||
const fallbackCharacterLabelY =
|
||||
@@ -549,10 +556,10 @@ function ObjectSceneNode({
|
||||
const node = (
|
||||
<group
|
||||
ref={groupRef}
|
||||
position={activeTrajectoryPoint ? activeTrajectoryPoint.position : item.transform.position}
|
||||
position={trajectoryMotion ? trajectoryMotion.position : item.transform.position}
|
||||
// 数据模型里旋转是度数,应用到 Three.js 的 rotation 属性(弧度)前要转换,
|
||||
// 否则 360° 会被当成 360 弧度 ≈ 57 圈。
|
||||
rotation={degreesRotationToRadians(activeTrajectoryPoint ? activeTrajectoryPoint.rotation : item.transform.rotation)}
|
||||
rotation={degreesRotationToRadians(trajectoryMotion ? trajectoryMotion.rotation : item.transform.rotation)}
|
||||
scale={item.transform.scale}
|
||||
userData={{ directorObjectId: item.id }}
|
||||
onClick={(event) => {
|
||||
@@ -867,6 +874,9 @@ function TrajectoryPath({
|
||||
const selectTrajectoryPoint = useDirectorStore((state) => state.selectTrajectoryPoint);
|
||||
const updateTrajectoryPoint = useDirectorStore((state) => state.updateTrajectoryPoint);
|
||||
const isPlaying = usePlaybackStore((state) => state.isPlaying);
|
||||
const setPlaying = usePlaybackStore((state) => state.setPlaying);
|
||||
const setPlayhead = usePlaybackStore((state) => state.setPlayhead);
|
||||
const playbackDuration = usePlaybackStore((state) => state.duration);
|
||||
const translationSnap = useDirectorStore((state) => (state.project.scene.snapToGrid ? 1 : null));
|
||||
const selectedGroupRef = useRef<Group>(null!);
|
||||
const points = owner.trajectoryPoints ?? [];
|
||||
@@ -903,6 +913,14 @@ function TrajectoryPath({
|
||||
function selectPoint(pointId: string, event: ThreeEvent<MouseEvent>) {
|
||||
event.stopPropagation();
|
||||
selectTrajectoryPoint(owner.id, pointId);
|
||||
|
||||
// 轨迹点本身就是时间轴上的关键帧:选中即把指针定位到该点时间,
|
||||
// 角色按「沿轨迹行走」的插值正好预览到这一点的位置与朝向(0s = 角色起始位置)。
|
||||
const pointIndex = points.findIndex((point) => point.id === pointId);
|
||||
if (pointIndex < 0) return;
|
||||
|
||||
setPlaying(false);
|
||||
setPlayhead(getTrajectoryPointTime(pointIndex, points.length, playbackDuration));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user