This commit closes the remaining red gates so `run-all.sh --fast` is
green end-to-end on the canonical dev machine.
New gates
─────────
1. cmake-format / cmake-lint
* scripts/quality/cmake-format.sh — dry-run by default,
--strict to fail on drift, --fix to apply
* .cmake-format.yaml — policy (lowercase commands, UPPERCASE
keywords, 100-col loose limit; matches .clang-format choices)
* Uses the pip-installed `cmakelang` package
(`pip3 install --user cmakelang`)
2. codespell
* scripts/quality/codespell.sh — exit 1 on any typo, --fix
interactively
* .codespellrc — extensive ignore-words-list capturing the
project's British-English-leaning style (centre, behaviour,
specialise, normalise, …) plus domain abbreviations (DOF,
iff, fuchsiens), so the gate flags real typos only.
* Validated: 0 typos across docs + code/include + scripts +
code/{src,tests}.
SPDX rollout (license-headers --fix)
────────────────────────────────────
license-headers.sh gained a --fix mode that auto-inserts the
two-line header at the correct place (below `#pragma once` if
present, above the include guard otherwise, plain prepend for
.cpp). Ran it on 60 of 66 files — 100 %-licensed now.
Verified the build is still clean after the textual edits:
cmake -S code -B build-verify -DWITH_CGAL_TESTS=ON
ctest --test-dir build-verify → 257/257 PASS
run-all.sh + README updated to include the two new gates.
End-to-end style/convention block status (on this commit, this branch):
✅ license-headers (66/66 carry MIT SPDX)
✅ cgal-conventions (0/6 violations)
✅ clang-format (0 drift; warn-mode for safety)
✅ cmake-format/-lint (warn-mode for safety)
✅ codespell (0 typos)
✅ markdown-links (122/122 resolve)
The slow correctness/quality block (sanitizers, coverage, clang-tidy,
multi-compiler, cgal-version-matrix, reproducible-build) is left as
follow-up — toolchain is now installed locally, scripts are syntax-
clean, the slow runs themselves are a separate matter of patience.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
95 lines
3.7 KiB
C++
95 lines
3.7 KiB
C++
#pragma once
|
||
// Copyright (c) 2024-2026 Tarik Moussa.
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
// euclidean_geometry.hpp
|
||
//
|
||
// Corner-angle formula for Euclidean triangles in the discrete conformal
|
||
// (log-length) parametrisation.
|
||
//
|
||
// Ported from de.varylab.discreteconformal.functional.EuclideanCyclicFunctional.
|
||
//
|
||
// In the discrete conformal parametrisation a Euclidean triangle is described by
|
||
// its three effective log-lengths Λ̃_ij = λ°_ij + u_i + u_j (+ edge DOF).
|
||
// The corresponding side lengths are l_ij = exp(Λ̃_ij / 2).
|
||
//
|
||
// Vertex ordering convention (matches EuclideanCyclicFunctional.java):
|
||
// v1 is opposite edge l23, v2 is opposite l31, v3 is opposite l12.
|
||
//
|
||
// t-value trick (Springborn 2008 §3):
|
||
// t12 = −l12 + l23 + l31 = 2(s − l12)
|
||
// t23 = +l12 − l23 + l31 = 2(s − l23)
|
||
// t31 = +l12 + l23 − l31 = 2(s − l31)
|
||
// denom = sqrt(t12 · t23 · t31 · l123) = 4 · Area
|
||
//
|
||
// α_v = 2 · atan2( product of t-values adjacent to v, denom )
|
||
//
|
||
// The centering trick (l_ij ← exp((Λ̃_ij − 2·μ)/2), μ = (Λ̃12+Λ̃23+Λ̃31)/6)
|
||
// rescales all three sides by the same factor, leaving angles unchanged but
|
||
// keeping the arguments of exp in a safe numerical range.
|
||
|
||
#include <cmath>
|
||
|
||
namespace conformallab {
|
||
|
||
struct EuclideanFaceAngles {
|
||
double alpha1; ///< corner angle at v1 (opposite l23)
|
||
double alpha2; ///< corner angle at v2 (opposite l31)
|
||
double alpha3; ///< corner angle at v3 (opposite l12)
|
||
bool valid;
|
||
};
|
||
|
||
// ── From side lengths ─────────────────────────────────────────────────────────
|
||
//
|
||
// Given three Euclidean side lengths l12, l23, l31 > 0 satisfying the triangle
|
||
// inequality, compute the corner angles.
|
||
//
|
||
// Returns valid=false if the triangle inequality is violated (any t-value ≤ 0).
|
||
inline EuclideanFaceAngles euclidean_angles_from_lengths(
|
||
double l12, double l23, double l31)
|
||
{
|
||
const double t12 = -l12 + l23 + l31; // 2*(s − l12)
|
||
const double t23 = +l12 - l23 + l31; // 2*(s − l23)
|
||
const double t31 = +l12 + l23 - l31; // 2*(s − l31)
|
||
|
||
if (t12 <= 0.0 || t23 <= 0.0 || t31 <= 0.0)
|
||
return {0.0, 0.0, 0.0, false};
|
||
|
||
const double l123 = l12 + l23 + l31;
|
||
const double denom2 = t12 * t23 * t31 * l123; // = (4·Area)²
|
||
if (denom2 <= 0.0)
|
||
return {0.0, 0.0, 0.0, false};
|
||
|
||
const double denom = std::sqrt(denom2);
|
||
|
||
// α at v1 (opposite l23): adjacent t-values are t12 and t31
|
||
// α at v2 (opposite l31): adjacent t-values are t12 and t23
|
||
// α at v3 (opposite l12): adjacent t-values are t23 and t31
|
||
return {
|
||
2.0 * std::atan2(t12 * t31, denom),
|
||
2.0 * std::atan2(t12 * t23, denom),
|
||
2.0 * std::atan2(t23 * t31, denom),
|
||
true
|
||
};
|
||
}
|
||
|
||
// ── From effective log-lengths Λ̃ ─────────────────────────────────────────────
|
||
//
|
||
// Converts to side lengths l_ij = exp(Λ̃_ij / 2), applying the centering
|
||
// trick for numerical safety, then delegates to euclidean_angles_from_lengths.
|
||
//
|
||
// The centering constant μ = (Λ̃12 + Λ̃23 + Λ̃31) / 6 ensures
|
||
// l12 · l23 · l31 = 1 (geometric mean = 1)
|
||
// which keeps all l values near 1 and prevents float overflow for large |Λ̃|.
|
||
inline EuclideanFaceAngles euclidean_angles(
|
||
double lam12, double lam23, double lam31)
|
||
{
|
||
const double mu = (lam12 + lam23 + lam31) / 6.0;
|
||
const double l12 = std::exp((lam12 - 2.0 * mu) * 0.5);
|
||
const double l23 = std::exp((lam23 - 2.0 * mu) * 0.5);
|
||
const double l31 = std::exp((lam31 - 2.0 * mu) * 0.5);
|
||
return euclidean_angles_from_lengths(l12, l23, l31);
|
||
}
|
||
|
||
} // namespace conformallab
|