Files
ttstd-3d-director/src/editor/canvas/DirectorCanvas.grid.test.ts

113 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
applyExternalViewSnapshot,
applySnapshotToControls,
DEFAULT_DIRECTOR_VIEW_SNAPSHOT,
getViewportGizmoHitButtonStyle,
getViewportSnapshotFromGizmoDirection,
shouldRenderViewportGrid,
} from "./DirectorCanvas";
import { createDefaultDirectorProject } from "../store/directorStore";
import { getCameraViewSnapshotFromShot } from "../schema/cameraGeometry";
import { PerspectiveCamera, Vector3 } from "three";
function expectTupleToBeCloseTo(
received: [number, number, number],
expected: [number, number, number]
) {
received.forEach((value, index) => {
expect(value).toBeCloseTo(expected[index], 5);
});
}
it("keeps the viewport grid visible when there is no panorama background", () => {
expect(shouldRenderViewportGrid(false, false)).toBe(true);
});
it("flushes leftover orbit inertia before writing the reset view back", () => {
const dampingStateDuringUpdate: boolean[] = [];
const controls = {
enableDamping: true,
target: new Vector3(9, 9, 9),
update: () => dampingStateDuringUpdate.push(controls.enableDamping),
};
const camera = new PerspectiveCamera();
applySnapshotToControls(controls, camera, DEFAULT_DIRECTOR_VIEW_SNAPSHOT);
// 两次 update 都在「阻尼关闭」状态下跑:第一次结算残余惯性,第二次稳在新视角上。
// 如果带着残余惯性直接写回,画布还在滑行时点重置视角就会被继续推走(表现为重置无效)。
expect(dampingStateDuringUpdate).toEqual([false, false]);
// 用完把阻尼恢复原状
expect(controls.enableDamping).toBe(true);
expect(controls.target.toArray()).toEqual(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.target);
expect(camera.fov).toBe(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.fov);
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);
});
it("keeps the viewport grid visible when snap to grid is enabled during a panorama background", () => {
expect(shouldRenderViewportGrid(true, true)).toBe(true);
});
it("starts the director and default camera view from a centered front composition", () => {
const defaultCamera = createDefaultDirectorProject().cameras[0];
expect(DEFAULT_DIRECTOR_VIEW_SNAPSHOT).toEqual({
fov: 50,
position: [0, 1.55, 5.4],
target: [0, 1.05, 0],
});
const defaultCameraView = getCameraViewSnapshotFromShot(defaultCamera);
expect(defaultCameraView.fov).toBe(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.fov);
expectTupleToBeCloseTo(defaultCameraView.position, defaultCamera.transform.position);
expect(defaultCameraView.target).toEqual(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.target);
expect(defaultCamera.transform.position[0]).toBe(0);
expect(defaultCamera.transform.position[1]).toBeGreaterThan(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.target[1]);
});
it("keeps the current orbit distance when native gizmo axis clicks switch viewport direction", () => {
const snapshot = getViewportSnapshotFromGizmoDirection(
DEFAULT_DIRECTOR_VIEW_SNAPSHOT,
new Vector3(1, 0, 0)
);
expect(snapshot.fov).toBe(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.fov);
expect(snapshot.target).toEqual(DEFAULT_DIRECTOR_VIEW_SNAPSHOT.target);
expectTupleToBeCloseTo(snapshot.position, [5.423099, 1.05, 0]);
});
it("positions the transparent native gizmo hit target over the visible X axis head", () => {
const style = getViewportGizmoHitButtonStyle(DEFAULT_DIRECTOR_VIEW_SNAPSHOT, [1, 0, 0]);
// 命中框以 .viewport-gizmo-overlay 的 80px 图层为基准(见 index.css 契约测试):
// 中心 40px,X 轴正向落在屏幕半径 25px 处,15px 命中框需回退半个身位。
expect(style.left).toBe("57.5px");
expect(style.top).toBe("32.5px");
expect(style.zIndex).toBe(100);
});