Skip to content
Source
Sign inSign up

phoneScene.test.ts

97 lines3.9 KB

1import { PerspectiveCamera, Texture, Vector3 } from "three";
2import { describe, expect, it } from "vite-plus/test";
3import { createPhoneScene, phoneDisplayLayout } from "./phoneScene.ts";
4import { IOS_TABLET_SHAPE, IOS_PHONE_SHAPE, ANDROID_PHONE_SHAPE } from "./shapeProfile.ts";
5​
6const orientations = [
7 "portrait",
8 "landscape_left",
9 "landscape_right",
10 "portrait_upside_down",
11] as const;
12​
13describe("3D phone input", () => {
14 it.each([IOS_TABLET_SHAPE, IOS_PHONE_SHAPE, ANDROID_PHONE_SHAPE])(
15 "preserves input projection for the $id silhouette in every orientation",
16 (profile) => {
17 for (const orientation of orientations) {
18 const texture = new Texture();
19 const aspect = profile.id === "ios-tablet" ? 0.75 : 0.45;
20 const layout = phoneDisplayLayout(
21 { width: 1200 * aspect, height: 1200, orientation },
22 1200 * aspect,
23 1200,
24 );
25 const phone = createPhoneScene(texture, layout, profile);
26 const camera = new PerspectiveCamera(32, 1, 0.1, 30);
27 camera.position.z = 6;
28 camera.updateMatrixWorld(true);
29 phone.root.rotation.set(0.2, -0.3, 0, "YXZ");
30 phone.orientation.rotation.z = layout.rotation;
31 phone.root.updateMatrixWorld(true);
32 const point = new Vector3((-2.2 * aspect) / 4, -2.2 / 4, 0.043);
33 phone.orientation.localToWorld(point);
34 point.project(camera);
35 const hit = phone.screenPoint((point.x + 1) / 2, (1 - point.y) / 2, camera);
36 const expected =
37 orientation === "landscape_left"
38 ? { x: 0.25, y: 0.25 }
39 : orientation === "landscape_right"
40 ? { x: 0.75, y: 0.75 }
41 : orientation === "portrait_upside_down"
42 ? { x: 0.75, y: 0.25 }
43 : { x: 0.25, y: 0.75 };
44 expect(hit?.x).toBeCloseTo(expected.x);
45 expect(hit?.y).toBeCloseTo(expected.y);
46 phone.dispose();
47 texture.dispose();
48 }
49 },
50 );
51 it.each([0.6, 2.2])("keeps device coordinates accurate at %s camera zoom", (zoom) => {
52 const texture = new Texture();
53 const layout = phoneDisplayLayout(null, 900, 1950);
54 const phone = createPhoneScene(texture, layout);
55 const camera = new PerspectiveCamera(32, 0.65, 0.1, 30);
56 camera.position.z = 6 / zoom;
57 camera.updateMatrixWorld(true);
58 phone.root.rotation.set(0.2, -0.3, 0, "YXZ");
59 phone.root.updateMatrixWorld(true);
60 const point = new Vector3((-2.2 * layout.aspect) / 4, -2.2 / 4, 0.043);
61 phone.orientation.localToWorld(point);
62 point.project(camera);
63 const hit = phone.screenPoint((point.x + 1) / 2, (1 - point.y) / 2, camera);
64 expect(hit?.x).toBeCloseTo(0.25);
65 expect(hit?.y).toBeCloseTo(0.75);
66 phone.dispose();
67 texture.dispose();
68 });
69​
70 it("rejects new gestures outside the display, but clamps captured drags at its edge", () => {
71 const texture = new Texture();
72 const phone = createPhoneScene(texture, phoneDisplayLayout(null, 900, 1950));
73 const camera = new PerspectiveCamera(32, 1, 0.1, 30);
74 camera.position.z = 5;
75 expect(phone.screenPoint(1, 0.5, camera)).toBeNull();
76 expect(phone.screenPoint(1, 0.5, camera, true)).toEqual({ x: 1, y: 0.5 });
77 phone.root.rotation.y = Math.PI;
78 expect(phone.screenPoint(0.5, 0.5, camera)).toBeNull();
79 phone.dispose();
80 texture.dispose();
81 });
82​
83 it.each(["landscape_left", "landscape_right"] as const)(
84 "unrotates an already-landscape framebuffer in %s",
85 (orientation) => {
86 const texture = new Texture();
87 const layout = phoneDisplayLayout({ width: 1950, height: 900, orientation }, 1950, 900);
88 const phone = createPhoneScene(texture, layout);
89 phone.orientation.rotation.z = layout.rotation;
90 const camera = new PerspectiveCamera(32, 1, 0.1, 30);
91 camera.position.z = 5;
92 expect(phone.screenPoint(0.5, 0.5, camera)).toEqual({ x: 0.5, y: 0.5 });
93 phone.dispose();
94 texture.dispose();
95 },
96 );
97});