fix: 优化Gizmo

This commit is contained in:
2026-09-18 03:06:34 +08:00
parent 8645ea3007
commit 9b72f10b43
23 changed files with 787 additions and 254 deletions

View File

@@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, vi } from "vitest";
import { clearViewportCaptureHandler, setViewportCaptureHandler } from "../io/captureBridge";
import { createInitialDirectorState, useDirectorStore } from "../store/directorStore";
import { DEFAULT_DIRECTOR_CAMERA_VIEW_SNAPSHOT, getCameraLookRotationFromTarget } from "../schema/cameraGeometry";
import { CameraPanel } from "./CameraPanel";
function seedCameraCapture() {
@@ -98,7 +99,9 @@ it("renders the approved camera panel fields", () => {
expect(screen.getByLabelText("切换机位")).toBeInTheDocument();
expect(screen.getByLabelText("机位位置 X")).toBeInTheDocument();
expect(screen.getByLabelText("注视目标模式")).toBeInTheDocument();
expect(screen.getByLabelText("注视坐标 X")).toBeInTheDocument();
expect(screen.getByLabelText("机位水平角")).toBeInTheDocument();
expect(screen.getByLabelText("机位俯仰角")).toBeInTheDocument();
expect(screen.getByLabelText("机位横滚角")).toBeInTheDocument();
expect(screen.getByLabelText("机位 FOV")).toBeInTheDocument();
});
@@ -181,7 +184,7 @@ it("renders target mode as the custom dropdown menu", async () => {
await user.click(screen.getByLabelText("注视目标模式"));
const menu = screen.getByRole("listbox", { name: "注视目标模式" });
const manualOption = within(menu).getByRole("option", { name: "手动坐标" });
const manualOption = within(menu).getByRole("option", { name: "手动角度" });
expect(menu).toHaveClass("inspector-dropdown-menu");
expect(manualOption).toHaveClass("is-selected");
@@ -200,7 +203,7 @@ it("lists visible viewport models as camera focus targets and centers on the sel
const menu = screen.getByRole("listbox", { name: "注视目标模式" });
const modelNames = within(menu).getAllByRole("option").map((option) => option.textContent);
expect(modelNames).toEqual(["手动坐标", "角色01", "角色02", "立方体"]);
expect(modelNames).toEqual(["手动角度", "角色01", "角色02", "立方体"]);
expect(within(menu).queryByRole("option", { name: "机位01" })).not.toBeInTheDocument();
await user.click(within(menu).getByRole("option", { name: "角色02" }));
@@ -212,14 +215,18 @@ it("lists visible viewport models as camera focus targets and centers on the sel
expect(focusedObject).toBeTruthy();
expect(camera?.targetMode).toBe("object");
expect(camera?.targetObjectId).toBe(focusedObject?.id);
expect(camera?.target).toEqual([-1.25, 0.99, 0]);
// 锁定物体 = 把「机位 → 物体」这条视线换算成注视朝向角
expect(camera?.lookRotation).toEqual(
getCameraLookRotationFromTarget(camera!.transform.position, [-1.25, 0.99, 0])
);
expect(screen.getByLabelText("注视目标模式")).toHaveTextContent("角色02");
expect(screen.getByLabelText("注视坐标 X")).toHaveValue(-1.25);
expect(screen.getByLabelText("注视坐标 Y")).toHaveValue(0.99);
expect(screen.getByLabelText("注视坐标 Z")).toHaveValue(0);
// rotation = [俯仰角 pitch(X), 水平角 yaw(Y), 横滚角 roll(Z)]
expect(screen.getByLabelText("机位水平角")).toHaveValue(camera!.lookRotation[1]);
expect(screen.getByLabelText("机位俯仰角")).toHaveValue(camera!.lookRotation[0]);
expect(screen.getByLabelText("机位横滚角")).toHaveValue(camera!.lookRotation[2]);
});
it("resets the camera position and the look-at target from their own title buttons", async () => {
it("resets the camera position and the look angles from their own buttons", async () => {
const user = userEvent.setup();
render(<CameraPanel />);
@@ -227,7 +234,12 @@ it("resets the camera position and the look-at target from their own title butto
const seededCamera = readCamera();
expect(seededCamera).toBeTruthy();
["位置", "注视坐标"].forEach((label) => {
const positionReset = within(screen.getByRole("group", { name: "位置" })).getByRole("button", { name: "重置位置" });
expect(positionReset).toHaveClass("inspector-axis-reset-button");
expect(positionReset.querySelector("svg")).toBeInTheDocument();
// 三个角度各自一行,行首带重置按钮
["水平角", "俯仰角", "横滚角"].forEach((label) => {
const button = within(screen.getByRole("group", { name: label })).getByRole("button", { name: `重置${label}` });
expect(button).toHaveClass("inspector-axis-reset-button");
@@ -236,24 +248,32 @@ it("resets the camera position and the look-at target from their own title butto
useDirectorStore.getState().updateCamera("cam_1", {
transform: { ...seededCamera!.transform, position: [7, 3, 7] },
target: [1, 2, 3],
lookRotation: [12, 34, 56],
});
await user.click(screen.getByRole("button", { name: "重置位置" }));
// 机位回到默认机位位置(不是归零:相机压在世界原点会让视线方向退化)
expect(readCamera()?.transform.position).toEqual([0, 1.55, 5.4]);
// 只重置位置,注视点保持原样
expect(readCamera()?.target).toEqual([1, 2, 3]);
// 只重置位置,注视朝向保持原样
expect(readCamera()?.lookRotation).toEqual([12, 34, 56]);
await user.click(screen.getByRole("button", { name: "重置注视坐标" }));
// 单独重置「俯仰角」→ 归零(水平),不带动水平角 / 横滚角
await user.click(within(screen.getByRole("group", { name: "俯仰角" })).getByRole("button", { name: "重置俯仰角" }));
expect(readCamera()?.lookRotation).toEqual([0, 34, 56]);
expect(readCamera()?.target).toEqual([0, 1.05, 0]);
await user.click(screen.getByRole("button", { name: "重置注视角度" }));
// 俯仰角重置为水平 0°,水平角 / 横滚角回到默认机位
expect(readCamera()?.lookRotation).toEqual([
0,
DEFAULT_DIRECTOR_CAMERA_VIEW_SNAPSHOT.lookRotation[1],
DEFAULT_DIRECTOR_CAMERA_VIEW_SNAPSHOT.lookRotation[2],
]);
expect(readCamera()?.targetMode).toBe("manual");
expect(screen.getByLabelText("注视坐标 Z")).toHaveValue(0);
});
it("updates camera position and target coordinates across all axes", async () => {
it("updates camera position and look angles across all axes", async () => {
const user = userEvent.setup();
render(<CameraPanel />);
@@ -261,14 +281,15 @@ it("updates camera position and target coordinates across all axes", async () =>
await user.type(screen.getByLabelText("机位位置 Y"), "3.4");
await user.clear(screen.getByLabelText("机位位置 Z"));
await user.type(screen.getByLabelText("机位位置 Z"), "7.5");
await user.clear(screen.getByLabelText("注视坐标 Y"));
await user.type(screen.getByLabelText("注视坐标 Y"), "1.8");
await user.clear(screen.getByLabelText("注视坐标 Z"));
await user.type(screen.getByLabelText("注视坐标 Z"), "2");
await user.clear(screen.getByLabelText("机位水平角"));
await user.type(screen.getByLabelText("机位水平角"), "210");
await user.clear(screen.getByLabelText("机位俯仰角"));
await user.type(screen.getByLabelText("机位俯仰角"), "12");
const camera = useDirectorStore.getState().project.cameras[0];
expect(camera.transform.position).toEqual([0, 3.4, 7.5]);
expect(camera.target).toEqual([0, 1.8, 2]);
// lookRotation = [俯仰角 pitch(X), 水平角 yaw(Y), 横滚角 roll(Z)]
expect(camera.lookRotation).toEqual([12, 210, 0]);
});
it("captures the current camera preview from the properties tab and shows it in the screenshots overview", async () => {