| 1 | import { Quaternion, Vector3 } from "three"; |
| 2 | const 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. */ |
| 5 | export 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 | } |