/** * Geometric occultation: given a position (relative to a body's center) * and the radii of the occluder (R1) and the body the observer is on (R2), * is the observer currently in shadow? * * Used for both: * - "is this vessel in the planet's shadow?" (R1 = planet radius, R2 ≈ 0) * - "is this ground station blocked by the local terrain?" (R1 = planet, R2 = earth station) * * Returns the fraction (0..1) of the line of sight to the sun that is * occluded. 0 = full sun, 1 = total eclipse. * * Note: the canonical way to do this is to compute the half-angle between * the sun and the occluding body as seen by the observer. We treat the * sun as effectively at infinity (parallel rays) which is fine for KSP * since Kerbol is the system root and we're never going to need parallax * precision at this scale. */ export function shadowFraction( observerToSun: { x: number; y: number; z: number }, occluderToObserver: { x: number; y: number; z: number }, occluderRadius: number, ): number { // Vector from observer to sun, normalized const sunDist = Math.hypot(observerToSun.x, observerToSun.y, observerToSun.z); if (sunDist === 0) return 0; const sx = observerToSun.x / sunDist; const sy = observerToSun.y / sunDist; const sz = observerToSun.z / sunDist; // Project occluder center onto the sun-direction line const proj = occluderToObserver.x * sx + occluderToObserver.y * sy + occluderToObserver.z * sz; if (proj >= 0) { // Occluder is behind the observer relative to the sun → no eclipse return 0; } // Perpendicular distance from occluder center to sun ray const px = occluderToObserver.x - proj * sx; const py = occluderToObserver.y - proj * sy; const pz = occluderToObserver.z - proj * sz; const perpDist = Math.hypot(px, py, pz); if (perpDist >= occluderRadius) return 0; // Approximate the angular size of the sun as seen from the occluder // vs the angular size of the occluder; we use 1.0 for the sun // (i.e. effectively point source) — good enough for visualization. return Math.min(1, 1 - perpDist / occluderRadius); }