README.md: reduced from 703 to ~75 lines — what/why, status, quick start, minimal usage example, navigation table to doc/ files. doc/architecture/overall_pipeline.md: trimmed — roadmap, extension points, declarative pipeline YAML, and references sections removed (each now has its own dedicated file). Replaced with a link table. New files: doc/getting-started.md — build modes, single-test invocation, CLI doc/api/pipeline.md — full pipeline API with code for all 3 geometries doc/api/extending.md — new functionals, geometry modes, Java porting guide doc/api/contracts.md — processing unit preconditions/provides table doc/api/cgal-package.md — Phase 8 CGAL package design + YAML pipeline (TODO) doc/math/geometry-modes.md — Euclidean/Spherical/HyperIdeal comparison doc/math/references.md — all papers by module doc/roadmap/phases.md — Phases 1–10 with porting/research boundary doc/roadmap/java-parity.md — Java vs C++ feature parity table doc/contributing.md — language policy, test standards, release flow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
16 KiB
conformallab++ — Architecture & Pipeline
Origin
conformallab++ is a C++ reimplementation of ConformalLab, the Java research library for discrete conformal geometry by Stefan Sechelmann (TU Berlin, Institut für Mathematik, SFB/Transregio 109 Discretization in Geometry and Dynamics).
The algorithmic foundation is his doctoral dissertation:
Stefan Sechelmann —
Variational Methods for Discrete Surface Parameterization: Applications and Implementation
Doctoral thesis, Technische Universität Berlin, 2016.
DOI: 10.14279/depositonce-5415 · CC BY-SA 4.0
The dissertation develops the variational framework for discrete conformal equivalence: discrete uniformization of Riemann surfaces, cone metrics, period matrices, and holonomy — all of which are directly implemented in this library.
Further links:
Java original: github.com/varylab/conformallab ·
Website: sechel.de ·
LinkedIn: linkedin.com/in/sechel
Positioning
conformallab++ is a specialised research library for discrete conformal geometry on triangulated surfaces. It is not a general geometry-processing framework.
The library revives the algorithms of the original ConformalLab by Stefan Sechelmann in a modern C++ setting. Its core concern is one precise mathematical question:
Given a triangulated surface, find a conformally equivalent metric that satisfies prescribed curvature (angle-sum) constraints at each vertex.
Everything in the library serves this goal:
| What it is | What it is not |
|---|---|
| Discrete conformal maps (Euclidean, spherical, hyperbolic) | General mesh processing |
| Newton solver for angle-sum energy functionals | Remeshing / boolean / smoothing |
| Priority-BFS layout into ℝ², S², Poincaré disk | Point-cloud or implicit-field processing |
| Holonomy, period matrices, fundamental domains | NURBS / parametric modelling |
| Research platform — experiment-first, CLI-ready | Production rendering engine |
Relation to existing libraries.
conformallab++ sits on top of CGAL, not beside it.
CGAL::Surface_mesh is the internal mesh representation; there is no additional
abstraction layer. Eigen handles all linear algebra. libigl provides the optional
interactive viewer. The library adds the conformal-geometry layer that none of these
provide.
The conformal geometry pipeline
The full pipeline runs in three stages. All stages operate on the same
ConformalMesh (= CGAL::Surface_mesh<Point3> with attached property maps).
graph TD
IN["Input\n(OFF / OBJ / PLY / builder)"]
ADAPTER["Input Adapter\nload_mesh() · make_triangle() · make_quad_strip() …"]
subgraph PRE["① PREPROCESSING"]
MAPS["Maps Setup\nsetup_*_maps() + compute_lambda0_from_mesh()"]
DOF["DOF Assignment\nv_idx[v] · e_idx[e] · pin / free"]
THETA["Target Angles\ntheta_v[v] — cone metric or natural equilibrium"]
GB["Gauss–Bonnet Check\ncheck_gauss_bonnet() / enforce_gauss_bonnet()"]
MAPS --> DOF --> THETA --> GB
end
subgraph CORE["② PROCESSING CORE"]
NEWTON["Newton Solver\nnewton_euclidean/spherical/hyper_ideal()\n→ NewtonResult x* ∈ ℝⁿ"]
CG["Cut Graph [closed surfaces]\ncompute_cut_graph()\n→ CutGraph (2g seam edges)"]
LAYOUT["Layout / Embedding\neuclidean/spherical/hyper_ideal_layout()\n→ Layout2D / Layout3D\n · uv[v] · halfedge_uv[h]\n · HolonomyData"]
NORM["Normalisation\nnormalise_euclidean / _hyperbolic / _spherical()"]
PERIOD["Period Matrix [genus 1]\ncompute_period_matrix()\n→ PeriodData τ ∈ ℍ"]
FD["Fundamental Domain\ncompute_fundamental_domain()\n→ FundamentalDomain + tiling"]
NEWTON --> CG --> LAYOUT --> NORM --> PERIOD --> FD
end
subgraph POST["③ POSTPROCESSING"]
SERIAL["Serialisation\nsave_result_json/xml()\nsave_layout_off()"]
VIZ["Visualisation\nexample_viewer (libigl / GLFW)"]
CLI["CLI App\nconformallab_core -i -g -o -j -x -s"]
end
IN --> ADAPTER --> PRE
PRE --> CORE
CORE --> POST
style PRE fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#000
style CORE fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#000
style POST fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#000
Stage ① — Preprocessing
Input adapter
All mesh input flows through a single entry point:
ConformalMesh mesh = load_mesh("input.off"); // OFF · OBJ · PLY
ConformalMesh mesh = make_quad_strip(); // built-in test meshes
Precondition guarantee: the adapter ensures the mesh is a valid, orientable, triangulated surface with consistent halfedge structure (CGAL validity). Downstream stages never check for degeneracies — they trust the adapter.
Maps setup
Each geometry mode has its own maps struct that attaches property maps to the mesh:
| Geometry | Setup function | Key property maps |
|---|---|---|
| Euclidean (ℝ²) | setup_euclidean_maps() |
lambda0[e], theta_v[v], v_idx[v] |
| Spherical (S²) | setup_spherical_maps() |
lambda0[e], theta_v[v], v_idx[v] |
| Hyper-ideal (H²) | setup_hyper_ideal_maps() |
beta_v[v], alpha_e[e], v_idx[v], e_idx[e] |
compute_*_lambda0_from_mesh() initialises log-edge-lengths from the 3-D vertex
positions. After this step the solver works entirely in scale-factor space; the
original vertex positions are no longer needed.
DOF assignment & target angles
// Pin first vertex (gauge fix for open meshes)
maps.v_idx[*mesh.vertices().begin()] = -1;
int idx = 0;
for (auto v : rest_of_vertices) maps.v_idx[v] = idx++;
// Set target curvature (cone metric or natural equilibrium)
maps.theta_v[v] = 2 * M_PI; // flat interior vertex
maps.theta_v[v] = M_PI / 3; // 60° cone singularity
Natural equilibrium shortcut: evaluate the gradient at x = 0, subtract it from
theta_v — the solver then converges to x* = 0 identically (useful for tests).
Gauss–Bonnet check
Before solving, the prescribed angles must satisfy:
\sum_{v} (2\pi - \Theta_v) = 2\pi \cdot \chi(M)
check_gauss_bonnet(mesh, maps); // throws if violated
enforce_gauss_bonnet(mesh, maps); // distributes residual uniformly
This is the most common source of silent Newton non-convergence. Prescribing angles that violate Gauss–Bonnet means no conformal factor exists — the solver will iterate without converging.
Stage ② — Processing core
The three geometry modes
conformallab++ implements discrete conformal geometry in three model spaces:
| Mode | Space | Curvature | Typical surfaces |
|---|---|---|---|
| Euclidean | ℝ² | K = 0 | Flat tori, developable surfaces, open patches |
| Spherical | S² | K = +1 | Genus-0 (sphere-like) surfaces |
| Hyper-ideal | H² (Poincaré disk) | K = −1 | Genus-g surfaces (g ≥ 1), hyperbolic structures |
All three share the same algorithmic structure — only the angle formula, the trilateration geometry, and the Hessian sign differ.
Newton solver
NewtonResult newton_euclidean (mesh, x0, maps [, tol, max_iter])
NewtonResult newton_spherical (mesh, x0, maps [, tol, max_iter])
NewtonResult newton_hyper_ideal(mesh, x0, maps [, tol, max_iter, hess_eps])
Each iteration:
- Evaluate gradient G (angle-sum defect per vertex)
- Evaluate Hessian H (analytical for Euclidean/Spherical; symmetric FD for HyperIdeal)
- Solve H·Δx = −G — try
SimplicialLDLT, fall back toSparseQRon rank deficiency - Backtracking line search (up to 20 halvings)
Preconditions: mesh triangulated + manifold · Gauss–Bonnet satisfied · DOFs assigned
Provides: NewtonResult.x — converged scale factors; converged, iterations, grad_inf_norm
SparseQR fallback handles gauge modes on closed meshes without a pinned vertex.
The fallback is public API: solve_linear_system(H, rhs, &used_fallback).
Cut graph (closed surfaces)
For a closed genus-g surface, cutting along 2g independent homology cycles turns it into a topological disk — a prerequisite for globally consistent BFS layout.
CutGraph cg = compute_cut_graph(mesh);
// cg.cut_edge_flags[e.idx()] — true for seam edges
// cg.cut_edge_indices — ordered list of 2g seam edges
// cg.genus — g
Algorithm: tree-cotree decomposition (Erickson–Whittlesey 2005).
Preconditions: closed, orientable, triangulated mesh
Provides: exactly 2g seam edges whose removal makes the surface simply connected
Layout / Embedding
BFS-trilateration unfolds the mesh into the target geometry. The root face (largest 3-D area, 1.5× interior bonus) is placed first; all other faces are placed in priority order by BFS depth (min-heap), minimising trilateration error accumulation.
HolonomyData hol;
Layout2D layout = euclidean_layout(mesh, result.x, maps, &cg, &hol, /*normalise=*/true);
Layout3D slayout = spherical_layout(mesh, result.x, smaps);
Layout2D hlayout = hyper_ideal_layout(mesh, result.x, hmaps, &cg, &hol);
Key outputs:
| Field | Content |
|---|---|
layout.uv[v.idx()] |
Primary UV — first / shallowest-BFS visit |
layout.halfedge_uv[h.idx()] |
UV of source(h) as seen from face(h) — seam-aware |
layout.has_seam |
True when a vertex was reached via two different paths |
hol.translations[i] |
Translation ω_i across cut edge i (Euclidean / spherical) |
hol.mobius_maps[i] |
Möbius isometry T_i ∈ SU(1,1) across cut edge i (hyperbolic) |
halfedge_uv — texture atlas semantics:
At a seam edge the two opposite halfedges carry different UV values.
This gives each face its own UV copy of a seam vertex, enabling
a proper GPU texture atlas without vertex duplication.
Trilateration — geometry by mode:
| Mode | Method | Accuracy |
|---|---|---|
| Euclidean | Analytic formula in ℝ² | Exact |
| Spherical | Spherical law of cosines on S² | Exact |
| Hyper-ideal | Möbius + hyperbolic law of cosines in Poincaré disk | Exact |
Preconditions: NewtonResult.converged · mesh · maps · optional CutGraph
Provides: Layout2D/3D with uv, halfedge_uv · HolonomyData with translations / Möbius maps
Möbius maps
MobiusMap (T(z) = (az+b)/(cz+d)) is the central algebraic object for hyperbolic geometry:
MobiusMap T = MobiusMap::from_three(z1,w1, z2,w2, z3,w3); // fit to 3 correspondences
MobiusMap S = T.inverse().compose(U); // group operations
Eigen::Vector2d p2 = T.apply(p); // apply to 2-D point
Used for: hyperbolic trilateration · holonomy tracking · normalisation centering.
Normalisation
After layout, a canonical post-processing step brings the result into a standard position:
| Mode | Method | Effect |
|---|---|---|
| Euclidean | PCA — centroid → origin, major axis → x-axis | Translation + rotation |
| Hyperbolic | Weighted Möbius centering (Fréchet mean, 30 iterations) | Maps centroid to disk origin |
| Spherical | Rodrigues rotation | Maps centroid to north pole |
Both uv and halfedge_uv are transformed identically.
Period matrix (genus 1)
From the two holonomy translations ω₁, ω₂ ∈ ℂ read off from the cut graph, the conformal type of a flat torus is the SL(2,ℤ)-orbit of:
\tau = \omega_2 / \omega_1 \in \mathbb{H}
PeriodData pd = compute_period_matrix(hol);
// pd.tau — complex period ratio
// pd.omega[i] — lattice generators as complex numbers
// pd.in_fundamental_domain — after SL(2,ℤ) reduction
// pd.genus() — g = omega.size() / 2
SL(2,ℤ) reduction alternates T: τ↦τ+1 and S: τ↦−1/τ steps until τ ∈ F = {|τ| ≥ 1, −½ ≤ Re(τ) < ½}.
Note: The Siegel period matrix Ω ∈ H_g for genus g ≥ 2 requires integrating holomorphic differentials — deferred to Phase 8.
Fundamental domain
FundamentalDomain fd = compute_fundamental_domain(hol);
// genus 1: CCW parallelogram {0, ω₁, ω₁+ω₂, ω₂}
// genus g > 1: empty — 4g-polygon boundary walk deferred to Phase 8
auto tiles = tiling_neighbourhood(layout, hol, /*m_max=*/2, /*n_max=*/2);
// returns (2·m_max+1)·(2·n_max+1) translated copies of the layout
// for visualising the universal cover
Stage ③ — Postprocessing
Serialisation
// Layout as mesh file
save_layout_off("layout.off", mesh, layout);
// Full result: DOF vector + metadata + layout UVs
save_result_json("result.json", result, "euclidean", V, F, &layout);
save_result_xml ("result.xml", result, "euclidean", V, F, &layout);
// Round-trip load
NewtonResult res2; std::string geom; Layout2D uv2;
load_result_json("result.json", &res2, &geom, &uv2);
CLI app
conformallab_core \
-i input.off # input mesh
-g euclidean # geometry: euclidean | spherical | hyper_ideal
-o layout.off # layout output
-j result.json # JSON serialisation
-x result.xml # XML serialisation
-s # show input in viewer
-v # verbose solver output
Interactive viewer
example_viewer (libigl / GLFW) shows the 3-D mesh and the 2-D layout
side-by-side. Built automatically with -DWITH_CGAL=ON.
Processing unit contracts
Each stage has explicit preconditions and guarantees. A pipeline is valid if every unit's preconditions are satisfied by the outputs of all preceding units.
| Unit | Preconditions | Provides |
|---|---|---|
load_mesh |
Valid file path, supported format | Manifold, oriented, triangulated ConformalMesh |
setup_*_maps |
Triangulated mesh | Initialised property maps; lambda0 from 3-D positions |
check_gauss_bonnet |
theta_v set |
Throws if Σ(2π−Θ_v) ≠ 2π·χ |
enforce_gauss_bonnet |
theta_v set |
Σ(2π−Θ_v) = 2π·χ guaranteed |
newton_* |
GB satisfied · DOFs assigned | NewtonResult.converged · x* · gradient norm |
compute_cut_graph |
Closed, orientable mesh | 2g seam edges · CutGraph.genus |
euclidean_layout |
newton_euclidean converged |
uv[v] · halfedge_uv[h] · HolonomyData |
normalise_euclidean |
layout.success == true |
Centroid at origin · major axis = x-axis |
compute_period_matrix |
hol.translations.size() >= 2 |
τ ∈ ℍ · optionally reduced to F |
compute_fundamental_domain |
HolonomyData (genus 1) |
CCW parallelogram · edge identifications |
The three geometry modes in detail
Euclidean Spherical Hyper-ideal
─────────────────────────────────────────────────────
Space ℝ² S² H² (Poincaré disk)
Curvature K = 0 K = +1 K = −1
Genus any (cone metrics) 0 ≥ 1
Angle sum Σα_v = Θ_v Σα_v = Θ_v Σβ_v = Θ_v
Hessian PSD (cotangent-Lap.) NSD (sign-flip) PSD (FD, strict conv.)
Holonomy translations ω_i rotations (2-D) Möbius maps T_i ∈ SU(1,1)
Period τ = ω₂/ω₁ ∈ ℍ — axis of T_i
Normalise PCA centring Rodrigues to N pole weighted Möbius centring
Further documentation
| Topic | Document |
|---|---|
| Extending the library (new functionals, geometry modes, Java porting guide) | api/extending.md |
| Processing unit contracts (preconditions / provides table) | api/contracts.md |
| Phase 8 CGAL package design + declarative pipeline YAML | api/cgal-package.md |
| Three geometry modes in detail | math/geometry-modes.md |
| References and papers | math/references.md |
| Development roadmap (Phases 1–10) | roadmap/phases.md |
| Java vs. C++ feature parity table | roadmap/java-parity.md |