Files
ConformalLabpp/doc/architecture/overall_pipeline.md
Tarik Moussa 66d52fc028
Some checks failed
C++ Tests / test-fast (push) Successful in 2m13s
C++ Tests / test-cgal (push) Failing after 3h1m49s
docs: fill information gaps — headers, tests, design decisions, project structure
doc/api/headers.md              — all 24 public headers with descriptions
doc/api/tests.md                — 26 test suites, individual counts, run instructions
doc/architecture/design-decisions.md  — 5 key design choices with rationale
doc/architecture/project-structure.md — full directory tree + build targets

README + overall_pipeline.md link tables updated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:40:16 +02:00

17 KiB
Raw Blame History

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["GaussBonnet 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).

GaussBonnet 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 GaussBonnet 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 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:

  1. Evaluate gradient G (angle-sum defect per vertex)
  2. Evaluate Hessian H (analytical for Euclidean/Spherical; symmetric FD for HyperIdeal)
  3. Solve H·Δx = G — try SimplicialLDLT, fall back to SparseQR on rank deficiency
  4. Backtracking line search (up to 20 halvings)

Preconditions: mesh triangulated + manifold · GaussBonnet 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 (EricksonWhittlesey 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
Public headers (all 24, with descriptions) api/headers.md
Test suites (26 suites, 158 tests, individual counts) api/tests.md
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
Key design decisions + rationale architecture/design-decisions.md
Project structure (directory tree + build targets) architecture/project-structure.md
Three geometry modes in detail math/geometry-modes.md
References and papers math/references.md
Development roadmap (Phases 110) roadmap/phases.md
Java vs. C++ feature parity table roadmap/java-parity.md