1import { Quaternion, Vector3 } from "three";
2const y = new Vector3(0, 1, 0);
3
4/** Each useful view permits a small yaw. Pick the closest member of each family, then the closest family. */
5export function nearestDeviceView<T extends { rotation: Quaternion; yawLimit: number }>(
6 rotation: Quaternion,
7 snaps: readonly T[],
8) {
9 let closest: T | null = null;
10 let distance = Infinity;
11 for (const snap of snaps) {
12 const relative = rotation.clone().multiply(snap.rotation.clone().invert());
13 const turn = 2 * Math.atan2(relative.y, relative.w);
14 const yaw = Math.max(
15 -snap.yawLimit,
16 Math.min(snap.yawLimit, Math.atan2(Math.sin(turn), Math.cos(turn))),
17 );
18 const candidate = snap.rotation.clone().premultiply(new Quaternion().setFromAxisAngle(y, yaw));
19 const nextDistance = candidate.angleTo(rotation);
20 if (nextDistance < distance - 1e-8) {
21 distance = nextDistance;
22 closest = { ...snap, rotation: candidate };
23 }
24 }
25 return closest;
26}