#pragma once #include "Vec3.hpp" #include #include // Equal Earth projection (Savric, Patterson & Jenny, 2018): an equal-area // pseudocylindrical projection with a natural, low-distortion "globe" look. // Forward is closed-form; the inverse uses a few Newton steps (cheap, used per // mouse point for hover). Kept raylib-free so it can be unit-tested headless. // // Convention: y is "up". lat = asin(unit.y), lon = atan2(unit.z, unit.x). namespace EqualEarth { constexpr double A1 = 1.340264; constexpr double A2 = -0.081106; constexpr double A3 = 0.000893; constexpr double A4 = 0.003796; inline double M_() { return std::sqrt(3.0) / 2.0; } // sin(theta) scale inline double K_() { return 2.0 * std::sqrt(3.0) / 3.0; } // x scale factor // dy/dtheta == x denominator; shared by forward and inverse. inline double denom(double th2, double th6) { return A1 + 3.0 * A2 * th2 + th6 * (7.0 * A3 + 9.0 * A4 * th2); } // (lon,lat) radians -> projection (x,y). inline void forward(double lon, double lat, double& x, double& y) { double th = std::asin(M_() * std::sin(lat)); double th2 = th * th, th6 = th2 * th2 * th2; x = K_() * lon * std::cos(th) / denom(th2, th6); y = th * (A1 + A2 * th2 + th6 * (A3 + A4 * th2)); // A1 th + A2 th^3 + A3 th^7 + A4 th^9 } // projection (x,y) -> (lon,lat) radians. Returns false if (x,y) is outside the // projected globe (so callers can reject hovers off the map). inline bool inverse(double x, double y, double& lon, double& lat) { double th = y; // good initial guess (y ~ A1*theta near 0) for (int it = 0; it < 16; ++it) { double th2 = th * th, th6 = th2 * th2 * th2; double fy = th * (A1 + A2 * th2 + th6 * (A3 + A4 * th2)) - y; double d = fy / denom(th2, th6); th -= d; if (std::fabs(d) < 1e-12) break; } double s = std::sin(th) / M_(); if (s < -1.0 - 1e-9 || s > 1.0 + 1e-9) return false; lat = std::asin(std::clamp(s, -1.0, 1.0)); double th2 = th * th, th6 = th2 * th2 * th2; double c = std::cos(th); if (std::fabs(c) < 1e-12) return false; lon = x * denom(th2, th6) / (K_() * c); if (lon < -M_PI - 1e-6 || lon > M_PI + 1e-6) return false; return true; } // Half-extents of the projected map (x in [-halfW,halfW], y in [-halfH,halfH]). inline double halfWidth() { double x, y; forward(M_PI, 0.0, x, y); return x; } inline double halfHeight() { double x, y; forward(0.0, M_PI / 2.0, x, y); return y; } } // namespace EqualEarth // Sphere direction <-> geographic coordinates (y up). inline void dirToLonLat(const Vec3& u, double& lon, double& lat) { lat = std::asin(std::clamp(u.y, -1.0, 1.0)); lon = std::atan2(u.z, u.x); } inline Vec3 lonLatToDir(double lon, double lat) { double c = std::cos(lat); return Vec3{ c * std::cos(lon), std::sin(lat), c * std::sin(lon) }; }