feat: 旋转单位改为度并支持轨迹点拖拽

统一数据模型中的旋转为度数并在渲染时转弧度,新增 InspectorAngleField 角度控件,为场景与轨迹点提供滑块和重置按钮;选中轨迹点时角色预览到该点并支持拖拽 gizmo 调整位置;机位点击统一走 selectObject 以正确切换右侧属性卡片。
This commit is contained in:
2026-09-17 11:18:58 +08:00
parent e74cf6d28b
commit 0794486857
21 changed files with 659 additions and 99 deletions

View File

@@ -2,6 +2,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { Box3, DoubleSide, Vector3 } from "three";
import { afterEach, beforeEach, vi } from "vitest";
import { VIEWPORT_CAMERA_VISUAL_SCALE } from "../schema/cameraGeometry";
import { degreesRotationToRadians } from "../schema/directorProject";
import { createInitialDirectorState, useDirectorStore } from "../store/directorStore";
import { getImportedModelNormalization, getTrajectoryGroundPoint, SceneRoot } from "./SceneRoot";
import { AXIS_COLORS } from "./sceneAxes";
@@ -331,7 +332,7 @@ it("keeps the ground plane inside the shared scene transform group", () => {
...useDirectorStore.getState().project.scene,
scale: 2,
position: [1, 3, -2],
rotation: [0, 0.5, 0],
rotation: [0, 90, 0],
groundHeight: 1.5,
},
},
@@ -342,7 +343,8 @@ it("keeps the ground plane inside the shared scene transform group", () => {
// 地面所在的场景组必须和视口网格用同一套变换,否则会分成两层不同高度的地面
expect(groundPlane).toHaveAttribute("position", "1,3,-2");
expect(groundPlane).toHaveAttribute("rotation", "0,0.5,0");
// 场景旋转存的是度数,写进 Three.js group 前会转成弧度(90° → π/2)
expect(groundPlane).toHaveAttribute("rotation", degreesRotationToRadians([0, 90, 0]).join(","));
expect(groundPlane).toHaveAttribute("scale", "2,2,2");
});
@@ -676,6 +678,23 @@ it("renders solid trajectory spheres and a smooth curve through the waypoints",
expect(container.querySelectorAll('mesh[name^="trajectory-point-"]')).toHaveLength(2);
});
it("shows a translate gizmo on the selected trajectory point so it can be dragged", () => {
useDirectorStore.getState().addTrajectoryPoint("char_default_a", [1, 0, 2]);
const pointId = useDirectorStore
.getState()
.project.objects.find((item) => item.id === "char_default_a")!
.trajectoryPoints![0].id;
useDirectorStore.getState().selectTrajectoryPoint("char_default_a", pointId);
render(<SceneRoot />);
// 选中轨迹点后给该点挂拖拽 gizmo(角色自身的变换 gizmo 此时被抑制,故只有一个)
const gizmo = screen.getByTestId("transform-controls");
expect(gizmo).toHaveAttribute("data-mode", "translate");
expect(gizmo).toHaveAttribute("data-has-object", "true");
expect(gizmo).toHaveAttribute("data-hide-from-capture", "true");
});
it("mounts an invisible ground plane only while placing trajectory points", () => {
const { container: idleContainer } = render(<SceneRoot />);
expect(idleContainer.querySelector('mesh[name="trajectory-placement-plane"]')).not.toBeInTheDocument();