chore: .gitignore + vergessene Doc-Dateien nachgetragen
.gitignore: build-Verzeichnisse, .DS_Store, .claude/, CMake-Artefakte Doc-Dateien die beim Restructure-Commit fehlten: doc/api/headers.md — alle 24 Public-Header mit Beschreibung doc/api/tests.md — 26 Suiten, 158 Tests, Einzelzahlen doc/architecture/design-decisions.md — Architekturentscheidungen + Begründung doc/architecture/project-structure.md — Verzeichnisbaum + Build-Targets README.md: Links zu den vier neuen Doc-Dateien ergänzt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
124
doc/architecture/design-decisions.md
Normal file
124
doc/architecture/design-decisions.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Key Design Decisions
|
||||
|
||||
Rationale for the architectural choices that distinguish conformallab++ from the
|
||||
Java original and from generic geometry-processing frameworks.
|
||||
|
||||
---
|
||||
|
||||
## CGAL `Surface_mesh` as the halfedge data structure
|
||||
|
||||
The Java library uses `CoHDS` — a custom intrusive halfedge data structure with
|
||||
`CoVertex`, `CoEdge`, `CoFace` types that carry domain-specific data directly as fields.
|
||||
|
||||
conformallab++ replaces this with `CGAL::Surface_mesh<Point3>` and attaches data via
|
||||
**named property maps**:
|
||||
|
||||
```cpp
|
||||
// Java: vertex.getLambda() → C++: maps.lambda0[v]
|
||||
// Java: edge.getAlpha() → C++: maps.e_alpha[e]
|
||||
// Java: vertex.getSolverIndex() → C++: maps.v_idx[v]
|
||||
|
||||
auto [lambda0, ok] = mesh.add_property_map<Edge_index, double>("e:lambda0", 0.0);
|
||||
lambda0[e] = 1.234;
|
||||
```
|
||||
|
||||
This decouples the mesh topology from the algorithm data, makes it straightforward
|
||||
to attach multiple independent data sets to the same mesh, and will enable the Phase 8
|
||||
traits-class design to work with any CGAL-conforming mesh type.
|
||||
|
||||
---
|
||||
|
||||
## DOF vector convention
|
||||
|
||||
All three functionals use the same indexing scheme: a flat `std::vector<double> x`
|
||||
indexed by `v_idx[v]` (vertices) and `e_idx[e]` (edges, HyperIdeal only).
|
||||
Index value `-1` means "pinned" — the DOF is fixed at zero and excluded from the
|
||||
Newton system.
|
||||
|
||||
```
|
||||
x[maps.v_idx[v]] = uᵥ (conformal scale factor, Euclidean/Spherical)
|
||||
x[maps.e_idx[e]] = λₑ (edge log-length variable, HyperIdeal only)
|
||||
-1 pinned — u_v = 0 / λ_e = 0
|
||||
```
|
||||
|
||||
This is consistent across all three geometry modes, enabling the same Newton solver
|
||||
and linear system infrastructure to serve all three without branching.
|
||||
|
||||
---
|
||||
|
||||
## Priority-BFS layout
|
||||
|
||||
A naive BFS layout places faces in arbitrary order; trilateration errors accumulate
|
||||
along the BFS frontier. conformallab++ uses a **priority min-heap on BFS depth**:
|
||||
|
||||
```
|
||||
depth(face) = max(depth[v_src], depth[v_tgt]) + 1 for each new face
|
||||
```
|
||||
|
||||
Faces with smaller depth (closer to the root) are placed first. This means each
|
||||
face's trilateration uses the two most accurately-placed adjacent vertices, minimising
|
||||
error propagation across the mesh.
|
||||
|
||||
Root face selection: largest 3-D area face, with an additional 1.5× bonus for
|
||||
interior faces over boundary faces. This heuristic places the root where metric
|
||||
distortion is lowest.
|
||||
|
||||
---
|
||||
|
||||
## `halfedge_uv` semantics
|
||||
|
||||
`layout.uv[v.idx()]` gives the *primary* UV coordinate of vertex `v` — the position
|
||||
from the shallowest BFS visit. At seam edges this is insufficient for GPU rendering:
|
||||
two faces sharing a seam vertex need *different* UV values for that vertex.
|
||||
|
||||
`layout.halfedge_uv[h.idx()]` stores the UV of `source(h)` **as seen from `face(h)`**:
|
||||
|
||||
```
|
||||
halfedge h → face(h) → source(h) has UV = halfedge_uv[h.idx()]
|
||||
opposite(h) → face(h') → source(h) has UV = halfedge_uv[opposite(h).idx()]
|
||||
(different value at a seam)
|
||||
```
|
||||
|
||||
At seam halfedges the two opposite halfedges carry different UV values — each face
|
||||
gets its own copy of the seam vertex. This enables a proper GPU texture atlas
|
||||
without vertex duplication in the index buffer.
|
||||
|
||||
---
|
||||
|
||||
## Spherical Hessian sign convention
|
||||
|
||||
The spherical energy functional is **concave** (negative semidefinite Hessian).
|
||||
Standard Newton would require solving `H·Δx = −G` with NSD `H`, which Cholesky
|
||||
cannot handle.
|
||||
|
||||
`newton_spherical()` solves `(−H)·Δx = G` instead — algebraically identical,
|
||||
but `−H` is PSD and `SimplicialLDLT` works correctly. This sign flip is handled
|
||||
transparently inside `newton_spherical()`; callers need not be aware of it.
|
||||
|
||||
The gradient sign in spherical mode is also flipped vs. Euclidean:
|
||||
- Euclidean: `G_v = actual_sum − Θᵥ`
|
||||
- Spherical: `G_v = Θᵥ − actual_sum`
|
||||
|
||||
Both conventions drive the same equilibrium condition `G = 0`.
|
||||
|
||||
---
|
||||
|
||||
## HyperIdeal Hessian via finite differences
|
||||
|
||||
The analytic HyperIdeal Hessian requires differentiating through the chain
|
||||
`(bᵢ, aₑ) → lᵢⱼ → ζ₁₃/ζ₁₄/ζ₁₅ → αᵢⱼ/βᵢ` with four vertex-type combinations
|
||||
per edge — substantial implementation complexity.
|
||||
|
||||
conformallab++ uses a **symmetric finite-difference Hessian** instead:
|
||||
|
||||
```
|
||||
H[i,j] = (G(x + ε·eⱼ)[i] − G(x − ε·eⱼ)[i]) / (2ε), ε = 1e-5
|
||||
```
|
||||
|
||||
Properties:
|
||||
- O(ε²) accuracy — relative error ≈ 10⁻¹⁰ at ε = 10⁻⁵
|
||||
- PSD guaranteed by strict convexity of the HyperIdeal energy (Springborn 2020)
|
||||
- Symmetrised automatically: `H = (H + Hᵀ) / 2`
|
||||
- Cost: n extra gradient evaluations per Newton step (acceptable for < 500 DOFs)
|
||||
|
||||
The analytic Hessian is deferred to Phase 9b. See [roadmap/java-parity.md](../roadmap/java-parity.md).
|
||||
115
doc/architecture/project-structure.md
Normal file
115
doc/architecture/project-structure.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Project Structure
|
||||
|
||||
```
|
||||
ConformalLabpp/
|
||||
├── CLAUDE.md # Claude Code context file
|
||||
├── README.md # Entry point — what/why, quick start, doc index
|
||||
├── LICENSE # MIT
|
||||
│
|
||||
├── code/ # All C++ source
|
||||
│ ├── CMakeLists.txt # Root: three build modes (default / CGAL_TESTS / CGAL)
|
||||
│ │
|
||||
│ ├── include/ # Header-only library — all algorithms here
|
||||
│ │ ├── conformal_mesh.hpp # ConformalMesh = CGAL::Surface_mesh<Point3>
|
||||
│ │ ├── constants.hpp # conformallab::PI, TWO_PI
|
||||
│ │ ├── clausen.hpp # Cl₂, Lobachevsky Л, ImLi₂
|
||||
│ │ ├── hyper_ideal_geometry.hpp # ζ₁₃/₁₄/₁₅, lᵢⱼ, αᵢⱼ, σᵢ, σᵢⱼ
|
||||
│ │ ├── hyper_ideal_utility.hpp # Tetrahedron volumes (Meyerhoff / Kolpakov–Mednykh)
|
||||
│ │ ├── hyper_ideal_visualization_utility.hpp # Poincaré disk projection, circumcircle helpers
|
||||
│ │ ├── hyper_ideal_functional.hpp # HyperIdeal energy + gradient on ConformalMesh
|
||||
│ │ ├── hyper_ideal_hessian.hpp # HyperIdeal Hessian (symmetric FD, Phase 9b: analytic)
|
||||
│ │ ├── spherical_geometry.hpp # Spherical arc-length, half-angle formula
|
||||
│ │ ├── spherical_functional.hpp # Spherical energy + gradient + gauge-fix
|
||||
│ │ ├── spherical_hessian.hpp # Spherical Hessian (∂α/∂u via law of cosines)
|
||||
│ │ ├── euclidean_geometry.hpp # Euclidean corner angle (t-value / atan2)
|
||||
│ │ ├── euclidean_functional.hpp # Euclidean energy + gradient
|
||||
│ │ ├── euclidean_hessian.hpp # Cotangent Laplacian Hessian (Pinkall–Polthier)
|
||||
│ │ ├── newton_solver.hpp # newton_{euclidean,spherical,hyper_ideal} + solve_linear_system
|
||||
│ │ ├── gauss_bonnet.hpp # euler_characteristic, genus, check/enforce_gauss_bonnet
|
||||
│ │ ├── cut_graph.hpp # CutGraph + compute_cut_graph (tree-cotree)
|
||||
│ │ ├── layout.hpp # Priority-BFS layout, MobiusMap, halfedge_uv, HolonomyData
|
||||
│ │ ├── mesh_builder.hpp # make_triangle / make_tetrahedron / make_quad_strip / make_fan
|
||||
│ │ ├── mesh_io.hpp # load_mesh / save_mesh (OFF/OBJ/PLY via CGAL::IO)
|
||||
│ │ ├── mesh_utils.hpp # CGAL → Eigen conversion (cgal_to_eigen)
|
||||
│ │ ├── serialization.hpp # save/load_result_json + save/load_result_xml
|
||||
│ │ ├── period_matrix.hpp # PeriodData, compute_period_matrix, SL(2,ℤ) reduction
|
||||
│ │ └── fundamental_domain.hpp # FundamentalDomain, tiling_copy, tiling_neighbourhood
|
||||
│ │
|
||||
│ ├── src/
|
||||
│ │ ├── apps/v0/conformallab_cli.cpp # CLI app (Phase 5): load → solve → layout → export
|
||||
│ │ └── viewer/simple_viewer.cpp # libigl/GLFW viewer wrapper
|
||||
│ │
|
||||
│ ├── examples/ # Standalone example programs (require WITH_CGAL=ON)
|
||||
│ │ ├── example_euclidean.cpp # Euclidean pipeline end-to-end
|
||||
│ │ ├── example_hyper_ideal.cpp # HyperIdeal pipeline end-to-end
|
||||
│ │ ├── example_layout.cpp # Full pipeline: solve → layout → OFF/JSON/XML + round-trip
|
||||
│ │ └── example_viewer.cpp # Interactive libigl viewer
|
||||
│ │
|
||||
│ ├── tests/
|
||||
│ │ ├── CMakeLists.txt
|
||||
│ │ ├── test_clausen.cpp
|
||||
│ │ ├── test_hyper_ideal_utility.cpp
|
||||
│ │ ├── test_matrix_utility.cpp
|
||||
│ │ ├── test_surface_curve_utility.cpp
|
||||
│ │ ├── test_discrete_elliptic_utility.cpp
|
||||
│ │ ├── test_p2_utility.cpp
|
||||
│ │ ├── test_hyper_ideal_visualization_utility.cpp
|
||||
│ │ └── cgal/ # CGAL test suite (WITH_CGAL_TESTS or WITH_CGAL)
|
||||
│ │ ├── test_conformal_mesh.cpp
|
||||
│ │ ├── test_hyper_ideal_functional.cpp
|
||||
│ │ ├── test_spherical_functional.cpp
|
||||
│ │ ├── test_euclidean_functional.cpp
|
||||
│ │ ├── test_euclidean_hessian.cpp
|
||||
│ │ ├── test_spherical_hessian.cpp
|
||||
│ │ ├── test_newton_solver.cpp
|
||||
│ │ ├── test_mesh_io.cpp
|
||||
│ │ ├── test_pipeline.cpp
|
||||
│ │ ├── test_layout.cpp
|
||||
│ │ ├── test_phase6.cpp
|
||||
│ │ ├── test_phase7.cpp
|
||||
│ │ └── test_geometry_utils.cpp
|
||||
│ │
|
||||
│ └── deps/ # All dependencies as bundled tarballs
|
||||
│ ├── tarballs/
|
||||
│ │ ├── eigen-3.4.0.tar.gz
|
||||
│ │ ├── CGAL-6.1.1.tar.xz
|
||||
│ │ ├── libigl-2.6.0.tar.gz
|
||||
│ │ ├── libigl-glad.tar.gz
|
||||
│ │ └── glfw-3.4.tar.gz
|
||||
│ └── single_includes/ # CLI11.hpp, json.hpp (header-only)
|
||||
│
|
||||
├── doc/
|
||||
│ ├── getting-started.md
|
||||
│ ├── contributing.md
|
||||
│ ├── api/
|
||||
│ │ ├── pipeline.md # Full pipeline API with code examples
|
||||
│ │ ├── extending.md # New functionals, geometry modes, Java porting
|
||||
│ │ ├── contracts.md # Processing unit preconditions/provides table
|
||||
│ │ └── cgal-package.md # Phase 8 CGAL package design (TODO)
|
||||
│ ├── architecture/
|
||||
│ │ ├── overall_pipeline.md # Mermaid diagram + stage descriptions
|
||||
│ │ ├── design-decisions.md # Key architectural choices + rationale
|
||||
│ │ └── project-structure.md # This file
|
||||
│ ├── math/
|
||||
│ │ ├── geometry-modes.md # Euclidean / Spherical / HyperIdeal comparison
|
||||
│ │ └── references.md # All papers by module
|
||||
│ └── roadmap/
|
||||
│ ├── phases.md # Phases 1–10 with porting/research boundary
|
||||
│ └── java-parity.md # Java vs C++ feature parity table
|
||||
│
|
||||
└── .gitea/
|
||||
├── docker/Dockerfile.ci-cpp # Ubuntu 22.04 ARM64 CI image
|
||||
└── workflows/cpp-tests.yml # Two-job CI pipeline
|
||||
```
|
||||
|
||||
## Build targets
|
||||
|
||||
| Target | Mode | Description |
|
||||
|---|---|---|
|
||||
| `conformallab_tests` | default | 36 non-CGAL pure-math tests |
|
||||
| `conformallab_cgal_tests` | `WITH_CGAL_TESTS` or `WITH_CGAL` | 158 CGAL tests |
|
||||
| `conformallab_core` | `WITH_CGAL` | CLI application |
|
||||
| `example_euclidean` | `WITH_CGAL` | Euclidean example program |
|
||||
| `example_hyper_ideal` | `WITH_CGAL` | HyperIdeal example program |
|
||||
| `example_layout` | `WITH_CGAL` | Full pipeline example |
|
||||
| `example_viewer` | `WITH_CGAL` | Interactive viewer |
|
||||
Reference in New Issue
Block a user