import * as THREE from 'three'; /** * Camera that contains a sphere (2026-11-03): keeps the view direction of `position → target`, moves the target to the * sphere centre, and backs off until the sphere fits both the vertical and the horizontal field of view. */ export function fitCamera( position: THREE.Vector3, target: THREE.Vector3, sphere: THREE.Sphere, fovDeg: number, aspect: number, margin = 1.1, ): { position: THREE.Vector3; target: THREE.Vector3 } { const halfV = THREE.MathUtils.degToRad(fovDeg / 2); // The narrower of the two half-angles decides how far back the camera must stand. const halfH = Math.atan(Math.tan(halfV) * aspect); const halfFov = Math.min(halfV, halfH); const fit = (sphere.radius / Math.sin(halfFov)) * margin; const dir = position.clone().sub(target); const current = dir.length(); const dist = Math.max(current, fit); dir.normalize(); return { position: sphere.center.clone().add(dir.multiplyScalar(dist)), target: sphere.center.clone(), }; }