1import { Matrix4, Quaternion, Vector3 } from "three";
2import { nearestDeviceView } from "./deviceViewSnap.ts";
3import type { DeviceScreenSize } from "./stream.ts";
4
5export type DuoRestFace = "cover" | "inside" | "left" | "right";
6export type DuoRestFrame = { face: DuoRestFace; normal: Vector3; up: Vector3; center: Vector3 };
7export type DuoViewSnap = {
8 rotation: Quaternion;
9 face: DuoRestFace;
10 orientation: DeviceScreenSize["orientation"];
11 center: Vector3;
12 yawLimit: number;
13};
14const orientations = [
15 "portrait",
16 "landscape_left",
17 "portrait_upside_down",
18 "landscape_right",
19] as const;
20const z = new Vector3(0, 0, 1);
21const y = new Vector3(0, 1, 0);
22
23export function duoViewSnaps(frames: readonly DuoRestFrame[], panel: 1 | 3) {
24 const snaps: DuoViewSnap[] = [];
25 for (const frame of frames) {
26 const normal = frame.normal.clone().normalize();
27 if (normal.lengthSq() < 0.5) continue;
28 const right = frame.up.clone().cross(normal).normalize();
29 const up = normal.clone().cross(right).normalize();
30 const faceRotation = new Quaternion()
31 .setFromRotationMatrix(new Matrix4().makeBasis(right, up, normal))
32 .invert();
33 for (let index = 0; index < orientations.length; index++) {
34 if (frame.face === "right" && index !== 0) continue;
35 if (frame.face === "left" && index !== 2) continue;
36 const roll = (panel === 3 ? Math.PI / 2 : 0) - (index * Math.PI) / 2;
37 const rotation = faceRotation.clone().premultiply(new Quaternion().setFromAxisAngle(z, roll));
38 if (frame.face === "left" || frame.face === "right")
39 rotation.premultiply(new Quaternion().setFromAxisAngle(new Vector3(1, 0, 0), Math.PI / 12));
40 if (
41 panel === 3 &&
42 frames.some(
43 (leaf) =>
44 (leaf.face === "left" || leaf.face === "right") &&
45 leaf.normal.clone().applyQuaternion(rotation).z < 0.04,
46 )
47 )
48 continue;
49 let yawLimit = frame.face === "cover" ? Math.PI / 9 : Math.PI / 3;
50 if (panel === 3) {
51 for (const sign of [-1, 1]) {
52 for (let step = 1; step <= 60; step++) {
53 const turn = new Quaternion().setFromAxisAngle(y, (sign * step * Math.PI) / 180);
54 if (
55 frames.some(
56 (leaf) =>
57 (leaf.face === "left" || leaf.face === "right") &&
58 leaf.normal.clone().applyQuaternion(rotation).applyQuaternion(turn).z < 0.04,
59 )
60 ) {
61 yawLimit = Math.min(yawLimit, ((step - 1) * Math.PI) / 180);
62 break;
63 }
64 }
65 }
66 }
67 snaps.push({
68 rotation,
69 face: frame.face,
70 orientation: orientations[index]!,
71 center: frame.center.clone(),
72 yawLimit,
73 });
74 }
75 }
76 return snaps;
77}
78
79export const nearestDuoView = nearestDeviceView<DuoViewSnap>;