#pragma once // Copyright (c) 2024-2026 Tarik Moussa. // SPDX-License-Identifier: MIT // spherical_geometry.hpp // // Pure-math building blocks for the spherical discrete conformal map. // Ported from de.varylab.discreteconformal.functional.SphericalFunctional // (the geometry helpers embedded there). // // Notation: // u_i – vertex conformal factor (DOF) // λ°_e – base log-length of edge e (fixed initial value) // λ_ij – effective log-length = λ°_ij + u_i + u_j // l_ij – spherical arc length = 2·asin(min(exp(λ_ij/2), 1)) // α_k – interior angle of the spherical triangle at vertex k #include "constants.hpp" #include #include namespace conformallab { /// Backward-compatible alias — prefer conformallab::PI in new code. constexpr double PI_SPHER = PI; // ── Effective spherical arc length ──────────────────────────────────────────── // l(λ) = 2·asin(min(exp(λ/2), 1)). // Clamps exp(λ/2) to [0, 1] so the arcsin stays in domain. inline double spherical_l(double lambda) { double half = std::exp(lambda * 0.5); if (half >= 1.0) half = 1.0 - 1e-15; if (half <= 0.0) return 0.0; return 2.0 * std::asin(half); } // ── Interior angles of a spherical triangle ────────────────────────────────── struct SphericalFaceAngles { double alpha1, alpha2, alpha3; // corner angles at v1, v2, v3 bool valid; // false when the three lengths fail the // spherical triangle inequality }; // Compute corner angles from spherical arc lengths using the half-angle formula. // // Convention (matching the halfedge cycle h0→v1→v2, h1→v2→v3, h2→v3→v1): // l12 – arc length of edge opposite v3 (edge e12) // l23 – arc length of edge opposite v1 (edge e23) // l31 – arc length of edge opposite v2 (edge e31) // // Half-angle formula (spherical law of cosines): // α_k = 2·atan2(sqrt(sin(s-a)·sin(s-b)), sqrt(sin(s)·sin(s-c))) // where a,b are the two edges ADJACENT to vertex k, c is the opposite edge. // // Equivalently (in terms of s-deficiencies): // α1 = 2·atan2( sqrt(sin(s12)·sin(s31)), sqrt(sin(s)·sin(s23)) ) // α2 = 2·atan2( sqrt(sin(s12)·sin(s23)), sqrt(sin(s)·sin(s31)) ) // α3 = 2·atan2( sqrt(sin(s23)·sin(s31)), sqrt(sin(s)·sin(s12)) ) // // where s = (l12+l23+l31)/2 and s_ij = s - l_ij. inline SphericalFaceAngles spherical_angles(double l12, double l23, double l31) { double s = (l12 + l23 + l31) * 0.5; double s12 = s - l12; double s23 = s - l23; double s31 = s - l31; // Spherical triangle inequalities: all s-deficiencies > 0 and s < π. if (s12 <= 0.0 || s23 <= 0.0 || s31 <= 0.0 || s >= PI_SPHER) return {0.0, 0.0, 0.0, false}; const double ss = std::sin(s); const double ss12 = std::sin(s12); const double ss23 = std::sin(s23); const double ss31 = std::sin(s31); double a1 = 2.0 * std::atan2(std::sqrt(ss12 * ss31), std::sqrt(ss * ss23)); double a2 = 2.0 * std::atan2(std::sqrt(ss12 * ss23), std::sqrt(ss * ss31)); double a3 = 2.0 * std::atan2(std::sqrt(ss23 * ss31), std::sqrt(ss * ss12)); return {a1, a2, a3, true}; } } // namespace conformallab