docs(doxygen): 100% public-API coverage (228 → 0 undocumented)
Some checks failed
C++ Tests / test-fast (pull_request) Successful in 2m40s
API Docs / doc-build (pull_request) Successful in 1m2s
C++ Tests / test-cgal (pull_request) Failing after 11m52s

Completes the work begun in the previous commit on this branch.  Every
public symbol under code/include/ now carries a brief Doxygen comment
(0 undocumented per scripts/doxygen-coverage.sh, with the `detail::`
implementation namespaces excluded as before).

Trajectory on this branch:
  start (after Doxyfile fix):  24.0 %  (165 / 437 in the no-detail set
                                       was 105 / 437 when detail counted)
  after PR #17 base commit  :  42.4 %  (165 / 396)
  this commit               : 100.0 %  (396 / 396)

Files touched (all .hpp / .h headers under code/include/):
  * cgal/Conformal_map_traits.h
  * clausen.hpp, conformal_mesh.hpp, constants.hpp (already docd)
  * cp_euclidean_functional.hpp, cut_graph.hpp, discrete_elliptic_utility.hpp
  * euclidean_functional.hpp, euclidean_geometry.hpp, euclidean_hessian.hpp
  * fundamental_domain.hpp, gauss_bonnet.hpp
  * hyper_ideal_{functional,geometry,hessian,utility,visualization_utility}.hpp
  * inversive_distance_functional.hpp, layout.hpp
  * matrix_utility.hpp, mesh_builder.hpp, mesh_io.hpp
  * newton_solver.hpp, p2_utility.hpp, period_matrix.hpp, projective_math.hpp
  * serialization.hpp, spherical_functional.hpp, spherical_geometry.hpp
  * spherical_hessian.hpp, viewer_utils.h

CI:
.gitea/workflows/doxygen-pages.yml now enforces
`scripts/doxygen-coverage.sh --threshold 100`, so any future regression
(a new public function landed without a `///` brief) fails the build
before the Doxygen HTML is published to Codeberg Pages.

Doxygen warnings remain at 0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-24 04:22:49 +02:00
parent f6722d7e84
commit 62b02f88b9
30 changed files with 427 additions and 355 deletions

View File

@@ -75,28 +75,38 @@ namespace conformallab {
// For hyperbolic holonomy the map is an orientation-preserving isometry of
// the Poincaré disk (SU(1,1) element).
// ─────────────────────────────────────────────────────────────────────────────
/// Möbius transformation `T(z) = (a·z + b) / (c·z + d)` of the Riemann
/// sphere; restricted to SU(1,1) for hyperbolic holonomy on the
/// Poincaré disk.
struct MobiusMap {
/// Complex scalar used for all entries.
using C = std::complex<double>;
C a{1.0, 0.0};
C b{0.0, 0.0};
C c{0.0, 0.0};
C d{1.0, 0.0};
C a{1.0, 0.0}; ///< Top-left coefficient.
C b{0.0, 0.0}; ///< Top-right coefficient.
C c{0.0, 0.0}; ///< Bottom-left coefficient.
C d{1.0, 0.0}; ///< Bottom-right coefficient.
/// Apply the transformation to a complex point.
C apply(C z) const { return (a * z + b) / (c * z + d); }
/// Apply the transformation to a 2-D real point (interpreted as `x + iy`).
Eigen::Vector2d apply(const Eigen::Vector2d& p) const {
C w = apply(C(p.x(), p.y()));
return Eigen::Vector2d(w.real(), w.imag());
}
/// Identity map.
static MobiusMap identity() { return {C(1), C(0), C(0), C(1)}; }
/// Inverse map.
MobiusMap inverse() const { return {d, -b, -c, a}; }
/// Composition `(*this) ∘ T`, i.e. apply `T` first then `*this`.
MobiusMap compose(const MobiusMap& T) const {
return { a*T.a + b*T.c, a*T.b + b*T.d,
c*T.a + d*T.c, c*T.b + d*T.d };
}
/// `true` iff the map is the identity up to tolerance `tol`.
bool is_identity(double tol = 1e-9) const {
if (std::abs(d) < 1e-14) return false;
C a_ = a/d, b_ = b/d, c_ = c/d;
@@ -121,6 +131,9 @@ struct MobiusMap {
// ── Result types ──────────────────────────────────────────────────────────────
/// Result of a 2-D layout (`euclidean_layout`, `hyper_ideal_layout`):
/// per-vertex UV coordinates plus a per-half-edge UV atlas for seamed
/// textures.
struct Layout2D {
/// uv[v.idx()] — primary 2-D position (first / shallowest-BFS-depth visit).
std::vector<Eigen::Vector2d> uv;
@@ -136,14 +149,15 @@ struct Layout2D {
/// Size = mesh.number_of_halfedges(). Border halfedges = (0,0).
std::vector<Eigen::Vector2d> halfedge_uv;
bool success = false;
bool has_seam = false; ///< true when a vertex was reached via two paths
bool success = false; ///< `true` iff the BFS placed every vertex.
bool has_seam = false; ///< `true` when a vertex was reached via two paths.
};
/// Result of a 3-D layout (`spherical_layout`): per-vertex positions on S².
struct Layout3D {
std::vector<Eigen::Vector3d> pos;
bool success = false;
bool has_seam = false;
std::vector<Eigen::Vector3d> pos; ///< Per-vertex spherical positions.
bool success = false; ///< `true` iff the BFS placed every vertex.
bool has_seam = false; ///< `true` when a vertex was reached via two paths.
};
/// Per-cut-edge holonomy.
@@ -156,9 +170,9 @@ struct Layout3D {
/// trilaterated virtual position obtained by continuing the unfolding across
/// the cut.
struct HolonomyData {
std::vector<Eigen::Vector2d> translations; ///< Euclidean / spherical
std::vector<MobiusMap> mobius_maps; ///< hyperbolic (Phase 7)
std::vector<std::size_t> cut_edge_indices;
std::vector<Eigen::Vector2d> translations; ///< Euclidean / spherical translation per cut edge.
std::vector<MobiusMap> mobius_maps; ///< Hyperbolic Möbius isometry per cut edge (Phase 7).
std::vector<std::size_t> cut_edge_indices; ///< Index (in the cut-graph edge list) of each holonomy entry.
};
// ── Internal helpers ──────────────────────────────────────────────────────────
@@ -343,7 +357,8 @@ inline void center_poincare_disk_weighted(
} // namespace detail
// ── Vertex Voronoi area weights ───────────────────────────────────────────────
/// Compute per-vertex area weights (sum of 1/3 of each adjacent triangle area).
/// Used by area-weighted layout normalisation routines.
inline std::vector<double> compute_vertex_area_weights(const ConformalMesh& mesh)
{
std::vector<double> w(mesh.number_of_vertices(), 0.0);
@@ -357,6 +372,8 @@ inline std::vector<double> compute_vertex_area_weights(const ConformalMesh& mesh
// ── Layout normalisation ──────────────────────────────────────────────────────
/// Euclidean canonical normalisation: translate centroid to the origin
/// and rotate the principal axis of the UV cloud onto the x-axis.
inline void normalise_euclidean(Layout2D& layout)
{
if (!layout.success || layout.uv.empty()) return;
@@ -388,6 +405,8 @@ inline void normalise_hyperbolic(Layout2D& layout, const ConformalMesh& mesh)
// halfedge_uv follows the same Möbius map
detail::center_poincare_disk(layout.halfedge_uv);
}
/// Hyperbolic canonical normalisation, mesh-free fallback: uniform
/// (unweighted) iterative Möbius centring of the Poincaré disk.
inline void normalise_hyperbolic(Layout2D& layout) // fallback without mesh
{
if (!layout.success || layout.uv.empty()) return;
@@ -395,6 +414,9 @@ inline void normalise_hyperbolic(Layout2D& layout) // fallback without mesh
detail::center_poincare_disk(layout.halfedge_uv);
}
/// Spherical canonical normalisation: rotate the layout so that the
/// per-vertex centroid (projected back to S²) coincides with the north
/// pole (Rodrigues rotation).
inline void normalise_spherical(Layout3D& layout)
{
if (!layout.success || layout.pos.empty()) return;
@@ -852,6 +874,8 @@ inline Layout2D hyper_ideal_layout(
// ── Convenience: save layout as OFF ──────────────────────────────────────────
/// Write a 2-D layout to disk in OFF format with z = 0. Convenience
/// helper for quickly inspecting the UV result in any OFF viewer.
inline void save_layout_off(
const std::string& path, ConformalMesh& mesh, const Layout2D& layout)
{
@@ -865,6 +889,7 @@ inline void save_layout_off(
}
}
/// Write a 3-D (spherical) layout to disk in OFF format.
inline void save_layout_off(
const std::string& path, ConformalMesh& mesh, const Layout3D& layout)
{