Merge pull request #5: Phase 7.5 — language unification + Doxygen + Phase 8 Hybrid MVP strategy
All checks were successful
C++ Tests / test-fast (push) Successful in 2m5s
Mirror to Codeberg / mirror (push) Successful in 29s
C++ Tests / test-cgal (push) Has been skipped

PR contains:
• Language unification: all German prose translated to English
• Doxygen infrastructure: Doxyfile + CMake doc target + README quickstart
• Phase 8 strategic decisions frozen (full design in doc/api/cgal-package.md)
• Phase 8 strategy refined to Hybrid MVP — minimum traits + 9a acceptance test

CI test-cgal failure is pre-existing (predates this PR), all 176 + 36 tests pass locally.
This commit is contained in:
2026-05-19 19:43:46 +00:00
13 changed files with 741 additions and 326 deletions

View File

@@ -11,8 +11,8 @@ on:
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Job 1 — test-fast # Job 1 — test-fast
# Pure-math tests (Clausen, ImLi₂, Hyper-ideal Geometrie). # Pure-math tests (Clausen, ImLi₂, Hyper-ideal geometry).
# Kein CGAL, kein Boost. Nur Eigen + GTest. Läuft auf ALLEN Branches. # No CGAL, no Boost. Eigen + GTest only. Runs on ALL branches.
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
jobs: jobs:
test-fast: test-fast:
@@ -35,7 +35,7 @@ jobs:
--output-on-failure --output-on-failure
--output-junit test-results.xml --output-junit test-results.xml
- name: Zusammenfassung - name: Summary
if: always() if: always()
run: | run: |
if [ -f test-results.xml ]; then if [ -f test-results.xml ]; then
@@ -48,14 +48,14 @@ jobs:
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Job 2 — test-cgal # Job 2 — test-cgal
# Vollständige CGAL-Test-Suite (Phase 37, 158 Tests). # Full CGAL test suite (Phase 37, 158 tests).
# Läuft NUR bei Pull Requests (nicht bei direkten Pushes auf dev/main). # Runs ONLY on pull requests (not on direct pushes to dev/main).
# Startet erst nach erfolgreichem test-fast. # Starts only after test-fast succeeds.
# #
# Verwendet -DWITH_CGAL_TESTS=ON (nicht -DWITH_CGAL=ON), damit kein # Uses -DWITH_CGAL_TESTS=ON (not -DWITH_CGAL=ON) to avoid building
# Viewer/GLFW gebaut wird — der CI-Container hat kein wayland-scanner. # Viewer/GLFW — the CI container has no wayland-scanner.
# #
# Boost (libboost-dev) ist seit Image-Rebuild bereits im Container. # Boost (libboost-dev) is already present in the container since the image rebuild.
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
test-cgal: test-cgal:
needs: test-fast needs: test-fast
@@ -68,7 +68,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Configure (WITH_CGAL_TESTS — kein Viewer, kein wayland-scanner) - name: Configure (WITH_CGAL_TESTS — no viewer, no wayland-scanner)
run: cmake -S code -B build -DWITH_CGAL_TESTS=ON -DCMAKE_BUILD_TYPE=Release run: cmake -S code -B build -DWITH_CGAL_TESTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build CGAL-Tests - name: Build CGAL-Tests
@@ -81,7 +81,7 @@ jobs:
--output-on-failure --output-on-failure
--output-junit cgal-results.xml --output-junit cgal-results.xml
- name: Zusammenfassung - name: Summary
if: always() if: always()
run: | run: |
if [ -f cgal-results.xml ]; then if [ -f cgal-results.xml ]; then

4
.gitignore vendored
View File

@@ -27,3 +27,7 @@ Testing/
# Claude Code worktrees # Claude Code worktrees
.claude/ .claude/
# Doxygen output
doc/doxygen/
*.dox.tmp

View File

@@ -249,7 +249,38 @@ Expected results: **36 non-CGAL tests pass**, **176 CGAL tests pass, 0 skipped**
## Release state ## Release state
Current release: **v0.7.0** (tag on `origin/dev`, PR to `main` open). Current release: **v0.7.0** (tag on `origin/dev`, PR to `main` open).
Phase 7 is complete. Phase 8 (CGAL package) is next. Phase 7 is complete. Phase 7.5 (Doxygen) and Phase 8 (CGAL package) are next.
## Phase 8 strategic decisions (2026-05-19)
The CGAL-package architecture was frozen on 2026-05-19. Full design:
[`doc/api/cgal-package.md`](doc/api/cgal-package.md). Key decisions:
| Decision | Choice |
|---|---|
| Submission to upstream CGAL | **Pre-submission-ready, not bound.** 12+ months horizon. |
| License | **MIT preserved** (no LGPL switch). |
| Mesh-type flexibility | **Generic `FaceGraph + HalfedgeGraph`** in target design; MVP starts Surface_mesh-only. |
| Parameter style | **Named Parameters** (`CGAL::parameters::...`). |
| Default kernel | **`Simple_cartesian<double>`** (status quo). |
| Backward compatibility | **Dual-layer wrapper**`code/include/*.hpp` stays as implementation, `include/CGAL/*.h` is thin wrapper. No algorithm duplication. |
| Implementation strategy | **Hybrid MVP** — minimum Phase 8 (traits + one wrapper) first, then Phase 9 in full, then Phase 8 extensions only on concrete demand. |
| Phase-8 MVP acceptance test | **Phase 9a (Inversive-Distance)** as the first new client of the new traits API. |
### Implementation sequence (committed)
```
1. Phase 7.5 Doxygen + cleanup done ✅
2. Phase 8 MVP — traits + one euclidean wrapper 35 days
3. Phase 9a — Inversive-Distance against new traits 35 days
4. Phase 9b — analytic HyperIdeal Hessian 1 week
5. Phase 9c — 4g-polygon for genus g > 1 1 week
→ port really complete, v0.9.0 release
```
Phase 8 extensions (8a.2 generic FaceGraph, 8c full Doxygen manuals, 8d
CGAL-format tests, 8e YAML pipeline) are deferred to on-demand status —
no speculative architecture for an uncertain submission.
Root-level files added at v0.7.0: Root-level files added at v0.7.0:
- `CITATION.cff` — machine-readable citation (Sechelmann 2016, Springborn 2020, BobenkoSpringborn 2004) - `CITATION.cff` — machine-readable citation (Sechelmann 2016, Springborn 2020, BobenkoSpringborn 2004)

126
Doxyfile Normal file
View File

@@ -0,0 +1,126 @@
# Doxyfile for conformallab++
#
# Phase 7.5 — minimal CGAL-style Doxygen configuration.
# Only non-default values are set; Doxygen ≥ 1.9.5 supplies the rest.
#
# Usage:
# doxygen Doxyfile # generates HTML into doc/doxygen/html/
# open doc/doxygen/html/index.html
#
# Or via CMake:
# cmake --build build --target doc
# ── Project identity ─────────────────────────────────────────────────────────
PROJECT_NAME = "conformallab++"
PROJECT_NUMBER = 0.7.0
PROJECT_BRIEF = "Discrete conformal maps on triangle meshes — C++17 reimplementation of ConformalLab (TU Berlin)"
PROJECT_LOGO =
OUTPUT_DIRECTORY = doc/doxygen
USE_MDFILE_AS_MAINPAGE = README.md
# ── Input ────────────────────────────────────────────────────────────────────
INPUT = README.md \
CLAUDE.md \
code/include \
doc/api \
doc/architecture \
doc/math
FILE_PATTERNS = *.hpp *.h *.cpp *.md
RECURSIVE = YES
EXCLUDE_PATTERNS = */build*/* \
*/deps/* \
*/.git/* \
*/test-reports/* \
*/* 2.hpp
EXCLUDE_SYMBOLS = Eigen::* boost::* std::*
# ── Source browsing ──────────────────────────────────────────────────────────
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
HIDE_UNDOC_MEMBERS = NO
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = NO
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
REFERENCES_LINK_SOURCE = YES
# ── Build options ────────────────────────────────────────────────────────────
JAVADOC_AUTOBRIEF = YES
QT_AUTOBRIEF = NO
MARKDOWN_SUPPORT = YES
AUTOLINK_SUPPORT = YES
BUILTIN_STL_SUPPORT = YES
DISTRIBUTE_GROUP_DOC = YES
GROUP_NESTED_COMPOUNDS = YES
SUBGROUPING = YES
INLINE_GROUPED_CLASSES = NO
INLINE_SIMPLE_STRUCTS = NO
TYPEDEF_HIDES_STRUCT = NO
EXTENSION_MAPPING = h=C++ hpp=C++
# ── Warnings ─────────────────────────────────────────────────────────────────
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = NO
WARN_IF_DOC_ERROR = YES
WARN_IF_INCOMPLETE_DOC = YES
WARN_NO_PARAMDOC = NO
WARN_AS_ERROR = NO
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE = doc/doxygen/doxygen-warnings.log
# ── HTML output ──────────────────────────────────────────────────────────────
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_COLORSTYLE = LIGHT
HTML_COLORSTYLE_HUE = 220
HTML_COLORSTYLE_SAT = 100
HTML_COLORSTYLE_GAMMA = 80
HTML_TIMESTAMP = NO
HTML_DYNAMIC_SECTIONS = YES
GENERATE_TREEVIEW = YES
DISABLE_INDEX = NO
ENUM_VALUES_PER_LINE = 1
TREEVIEW_WIDTH = 280
EXT_LINKS_IN_WINDOW = NO
SEARCHENGINE = YES
SERVER_BASED_SEARCH = NO
# ── Disabled outputs (we only want HTML) ─────────────────────────────────────
GENERATE_LATEX = NO
GENERATE_RTF = NO
GENERATE_MAN = NO
GENERATE_XML = NO
GENERATE_DOCBOOK = NO
GENERATE_AUTOGEN_DEF = NO
GENERATE_PERLMOD = NO
# ── Preprocessor ─────────────────────────────────────────────────────────────
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
SEARCH_INCLUDES = YES
INCLUDE_PATH = code/include
PREDEFINED = CGAL_DISABLE_GMP \
CGAL_DISABLE_MPFR \
DOXYGEN_RUNNING
# ── Diagrams ─────────────────────────────────────────────────────────────────
HAVE_DOT = NO
CLASS_GRAPH = YES
COLLABORATION_GRAPH = NO
GROUP_GRAPHS = YES
INCLUDE_GRAPH = NO
INCLUDED_BY_GRAPH = NO
CALL_GRAPH = NO
CALLER_GRAPH = NO
# ── Aliases (CGAL-style) ─────────────────────────────────────────────────────
ALIASES += "concept{1}=\xrefitem concept \"Concept\" \"Concepts\" \1"
ALIASES += "models{1}=\xrefitem models \"Models\" \"Models\" \1"
ALIASES += "cgalRequires{1}=\par Requirements: \n\1"
ALIASES += "cgalParam{2}=\param \1 \2"

View File

@@ -34,6 +34,10 @@ ctest --test-dir build -R "^cgal\." --output-on-failure
# Full build with CLI + viewer (requires Wayland/X11 dev headers) # Full build with CLI + viewer (requires Wayland/X11 dev headers)
cmake -S code -B build -DWITH_CGAL=ON && cmake --build build -j$(nproc) cmake -S code -B build -DWITH_CGAL=ON && cmake --build build -j$(nproc)
./bin/conformallab_core -i input.off -g euclidean -o layout.off -j result.json ./bin/conformallab_core -i input.off -g euclidean -o layout.off -j result.json
# API documentation (requires doxygen: brew/apt install doxygen)
cmake --build build --target doc
open doc/doxygen/html/index.html
``` ```
--- ---

View File

@@ -140,3 +140,25 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE
${CMAKE_CURRENT_SOURCE_DIR}/../CITATION.cff ${CMAKE_CURRENT_SOURCE_DIR}/../CITATION.cff
DESTINATION ${CMAKE_INSTALL_DATADIR}/conformallab) DESTINATION ${CMAKE_INSTALL_DATADIR}/conformallab)
# ── Doxygen documentation target (Phase 7.5) ──────────────────────────────────
# Generates HTML API documentation into doc/doxygen/html/.
# Usage:
# cmake --build build --target doc
# open doc/doxygen/html/index.html
#
# Optional dependency: install Doxygen via `brew install doxygen` (macOS) or
# `apt install doxygen graphviz` (Linux). The target is silently disabled
# if Doxygen is not found.
find_package(Doxygen QUIET)
if(DOXYGEN_FOUND)
set(DOXYGEN_PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
add_custom_target(doc
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_PROJECT_ROOT}/Doxyfile
WORKING_DIRECTORY ${DOXYGEN_PROJECT_ROOT}
COMMENT "Generating API documentation with Doxygen"
VERBATIM)
message(STATUS "Doxygen found: target 'doc' available (cmake --build build --target doc)")
else()
message(STATUS "Doxygen not found — 'doc' target unavailable (install: brew/apt install doxygen)")
endif()

View File

@@ -36,7 +36,7 @@ void simple_visualize_mesh(Eigen::MatrixXd& V, Eigen::MatrixXi& F) {
viewer.data().set_mesh(V, F); viewer.data().set_mesh(V, F);
viewer.launch(); viewer.launch();
} }
// Zero-Copy Map für V (optional) // Zero-copy map for V (optional)
template <typename Kernel> template <typename Kernel>
Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::RowMajor>> Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::RowMajor>>
get_vertex_map(CGAL::Surface_mesh<typename Kernel::Point_3>& mesh) { get_vertex_map(CGAL::Surface_mesh<typename Kernel::Point_3>& mesh) {

View File

@@ -44,10 +44,10 @@ add_executable(conformallab_cgal_tests
# period matrix, fundamental domain, tiling # period matrix, fundamental domain, tiling
test_phase7.cpp test_phase7.cpp
# ── Java-Parität: Geometrie-Utility-Tests ───────────────────────────────── # ── Java parity: geometry utility tests ─────────────────────────────────
# Portiert aus CuttinUtilityTest, UnwrapUtilityTest, # Ported from CuttinUtilityTest, UnwrapUtilityTest,
# ConvergenceUtilityTests, HomologyTest (Tests 16). # ConvergenceUtilityTests, HomologyTest (tests 16).
# Test 7 (Genus-2-Homologie) als GTEST_SKIP-Stub bis Phase 8. # Test 7 (genus-2 homology) as GTEST_SKIP stub until Phase 8.
test_geometry_utils.cpp test_geometry_utils.cpp
# ── Scalability smoke tests ──────────────────────────────────────────────── # ── Scalability smoke tests ────────────────────────────────────────────────

View File

@@ -1,64 +1,64 @@
// test_geometry_utils.cpp // test_geometry_utils.cpp
// //
// Portierung der Java ConformalLab Geometrie-Utility-Tests. // Port of the Java ConformalLab geometry utility tests.
// //
// Java-Quelle Java-Testmethode Status // Java source Java test method Status
// ───────────────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────────────
// CuttinUtilityTest.java testIsInConvexTextureFace_False PORTIERT // CuttinUtilityTest.java testIsInConvexTextureFace_False PORTED
// CuttinUtilityTest.java testIsInConvexTextureFace_True PORTIERT // CuttinUtilityTest.java testIsInConvexTextureFace_True PORTED
// UnwrapUtilityTest.java testGetAngleReturnsPI PORTIERT // UnwrapUtilityTest.java testGetAngleReturnsPI PORTED
// ConvergenceUtilityTests.java testGetTextureCircumRadius PORTIERT // ConvergenceUtilityTests.java testGetTextureCircumRadius PORTED
// ConvergenceUtilityTests.java testGetTextureTriangleArea PORTIERT // ConvergenceUtilityTests.java testGetTextureTriangleArea PORTED
// ConvergenceUtilityTests.java testScaleInvariantCircumCircleRadius PORTIERT // ConvergenceUtilityTests.java testScaleInvariantCircumCircleRadius PORTED
// HomologyTest.java testHomology PORTIERT // HomologyTest.java testHomology PORTED
// EuclideanLayoutTest.java testDoLayout PORTIERT // EuclideanLayoutTest.java testDoLayout PORTED
// EuclideanCyclicConvergenceTest.java testEuclideanConvergence PORTIERT // EuclideanCyclicConvergenceTest.java testEuclideanConvergence PORTED
// SphericalConvergenceTest.java testSphericalConvergence PORTIERT // SphericalConvergenceTest.java testSphericalConvergence PORTED
// //
// ─── Geometrische Grundlage ────────────────────────────────────────────────────────── // ─── Geometric background ────────────────────────────────────────────────────────────
// //
// Tests 12 Punkt-in-konvexem-Dreieck (2D UV-Raum, baryzentrische Vorzeichen-Methode) // Tests 12 Point-in-convex-triangle (2D UV space, barycentric sign method)
// Java: CuttingUtility.isInConvexTextureFace(pp, face, adapters) // Java: CuttingUtility.isInConvexTextureFace(pp, face, adapters)
// Hinweis: Java-Test 2 hat ein 5-elementiges T-Array mit w=0 (Punkt im // Note: Java test 2 has a 5-element T-array with w=0 (point at
// Unendlichen), was ein Tippfehler im Original ist. Hier werden // infinity), which is a typo in the original. Equivalent, well-formed
// äquivalente, wohlgeformte Koordinaten verwendet. // coordinates are used here instead.
// //
// Test 3 Eckenwinkel für kollineare Vertices über den Kosinussatz. // Test 3 Corner angle for collinear vertices via the law of cosines.
// Java: UnwrapUtility.getAngle(edge, adapters) — gibt den Winkel am // Java: UnwrapUtility.getAngle(edge, adapters) — returns the angle at
// Zielknoten zurück. Für v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) ist // the target vertex. For v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) the
// der Winkel bei v1 genau π (Dreiecksungleichung entartet). // angle at v1 is exactly π (degenerate triangle inequality).
// //
// Tests 45 2D Umkreisradius und Dreiecksfläche. // Tests 45 2D circumradius and triangle area.
// Java: ConvergenceUtility.getTextureCircumCircleRadius(face) // Java: ConvergenceUtility.getTextureCircumCircleRadius(face)
// ConvergenceUtility.getTextureTriangleArea(face) // ConvergenceUtility.getTextureTriangleArea(face)
// Formeln: Area = |det([B-A, C-A])| / 2 // Formulas: Area = |det([B-A, C-A])| / 2
// R = (a·b·c) / (4·Area) // R = (a·b·c) / (4·Area)
// //
// Test 6 Skaleninvarianter Umkreisradius über ein Mesh. // Test 6 Scale-invariant circumradius over a mesh.
// Java: ConvergenceUtility.getMaxMeanSumScaleInvariantCircumRadius(hds) // Java: ConvergenceUtility.getMaxMeanSumScaleInvariantCircumRadius(hds)
// Gibt [max, mean, sum] von R_f / sqrt(total_texture_area) zurück. // Returns [max, mean, sum] of R_f / sqrt(total_texture_area).
// Invariant unter uniformer Skalierung der Texturkoordinaten (Test mit // Invariant under uniform scaling of texture coordinates (tested with
// homogenem Gewicht w: Position = (T[0]/w, T[1]/w)). // homogeneous weight w: position = (T[0]/w, T[1]/w)).
// //
// Test 7 Genus-2 Homologie-Generatoren. // Test 7 Genus-2 homology generators.
// Java: HomologyTest.testHomology (brezel2.obj) // Java: HomologyTest.testHomology (brezel2.obj)
// Erwartet: getGeneratorPaths(root).size() == 4 (2g = 4 für g = 2) // Expected: getGeneratorPaths(root).size() == 4 (2g = 4 for g = 2)
// C++: compute_cut_graph(mesh).cut_edge_indices.size() == 4 // C++: compute_cut_graph(mesh).cut_edge_indices.size() == 4
// Mesh: code/data/obj/brezel2.obj (V=2622, F=5248, χ=2, g=2) // Mesh: code/data/obj/brezel2.obj (V=2622, F=5248, χ=2, g=2)
// Pfad zur Compile-Zeit via CONFORMALLAB_DATA_DIR (CMakeLists.txt). // Path set at compile time via CONFORMALLAB_DATA_DIR (CMakeLists.txt).
// //
// Tests 89 Layout-Kanten-Längenerhalt (tetraflat.obj). // Tests 89 Layout edge-length preservation (tetraflat.obj).
// Java: EuclideanLayoutTest.testDoLayout // Java: EuclideanLayoutTest.testDoLayout
// Nach Layout mit u=0 müssen UV-Kantenlängen == 3D-Kantenlängen (±1e-10). // After layout with u=0, UV edge lengths must equal 3D edge lengths (±1e-10).
// //
// Test 10 Euklidischer Newton auf cathead.obj — Konvergenz + Winkeldefekt. // Test 10 Euclidean Newton on cathead.obj — convergence + angle deficit.
// Java: EuclideanLayoutTest.testLayout02 (130-Werte-Array für cathead.heml) // Java: EuclideanLayoutTest.testLayout02 (130-value array for cathead.heml)
// C++: Newton ab u=0, prüft Konvergenz + Σα_v ≈ 2π für alle inneren Knoten. // C++: Newton from u=0, checks convergence + Σα_v ≈ 2π for all interior nodes.
// //
// Test 11 Sphärischer Newton auf Oktaeder — Konvergenz + Winkeldefekt. // Test 11 Spherical Newton on octahedronconvergence + angle deficit.
// Java: SphericalConvergenceTest.testSphericalConvergence (Oktaeder, zufällig // Java: SphericalConvergenceTest.testSphericalConvergence (octahedron, randomly
// störe Radien, seed=1). C++: konstruierter regulärer Oktaeder, prüft // perturbed radii, seed=1). C++: constructed regular octahedron, checks
// Konvergenz und dass Σα_v ≈ 2π (Target für Sphäre nach prepareInvariantData). // convergence and that Σα_v ≈ 2π (target for sphere after prepareInvariantData).
// //
// ───────────────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────────────
@@ -81,12 +81,12 @@
using namespace conformallab; using namespace conformallab;
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
// Lokale Geometrie-Hilfsfunktionen // Local geometry helper functions
// (portiert aus Java CuttingUtility / ConvergenceUtility) // (ported from Java CuttingUtility / ConvergenceUtility)
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
/// Punkt-in-Dreieck Test (2D, baryzentrische Vorzeichenmethode). /// Point-in-triangle test (2D, barycentric sign method).
/// Gibt true zurück wenn p strikt innerhalb oder auf dem Rand von v0-v1-v2 liegt. /// Returns true if p lies strictly inside or on the boundary of v0-v1-v2.
/// Java: CuttingUtility.isInConvexTextureFace /// Java: CuttingUtility.isInConvexTextureFace
static bool point_in_triangle_2d( static bool point_in_triangle_2d(
Eigen::Vector2d p, Eigen::Vector2d p,
@@ -103,7 +103,7 @@ static bool point_in_triangle_2d(
return !(has_neg && has_pos); return !(has_neg && has_pos);
} }
/// 2D Dreiecksfläche (halbes Kreuzprodukt). /// 2D triangle area (half cross product).
/// Java: ConvergenceUtility.getTextureTriangleArea /// Java: ConvergenceUtility.getTextureTriangleArea
static double triangle_area_2d( static double triangle_area_2d(
Eigen::Vector2d A, Eigen::Vector2d B, Eigen::Vector2d C) Eigen::Vector2d A, Eigen::Vector2d B, Eigen::Vector2d C)
@@ -112,7 +112,7 @@ static double triangle_area_2d(
- (B - A).y() * (C - A).x()) * 0.5; - (B - A).y() * (C - A).x()) * 0.5;
} }
/// 2D Umkreisradius: R = (a·b·c) / (4·Area). /// 2D circumradius: R = (a·b·c) / (4·Area).
/// Java: ConvergenceUtility.getTextureCircumCircleRadius /// Java: ConvergenceUtility.getTextureCircumCircleRadius
static double circumradius_2d( static double circumradius_2d(
Eigen::Vector2d A, Eigen::Vector2d B, Eigen::Vector2d C) Eigen::Vector2d A, Eigen::Vector2d B, Eigen::Vector2d C)
@@ -125,17 +125,17 @@ static double circumradius_2d(
return (a * b * c) / (4.0 * area); return (a * b * c) / (4.0 * area);
} }
/// Skaleninvarianter Umkreisradius für ein Mesh: /// Scale-invariant circumradius for a mesh:
/// scale_R_f = R_f / sqrt(total_area) /// scale_R_f = R_f / sqrt(total_area)
/// Gibt {max, mean, sum} über alle Flächen zurück. /// Returns {max, mean, sum} over all faces.
/// Java: ConvergenceUtility.getMaxMeanSumScaleInvariantCircumRadius /// Java: ConvergenceUtility.getMaxMeanSumScaleInvariantCircumRadius
/// ///
/// Homogene Koordinaten: Position = (x/w, y/w). /// Homogeneous coordinates: position = (x/w, y/w).
static std::array<double, 3> scale_invariant_circumradius_stats( static std::array<double, 3> scale_invariant_circumradius_stats(
const std::vector<Eigen::Vector2d>& verts, const std::vector<Eigen::Vector2d>& verts,
const std::vector<std::array<int, 3>>& faces) const std::vector<std::array<int, 3>>& faces)
{ {
// Gesamtfläche // Total area
double total_area = 0.0; double total_area = 0.0;
for (auto& f : faces) for (auto& f : faces)
total_area += triangle_area_2d(verts[f[0]], verts[f[1]], verts[f[2]]); total_area += triangle_area_2d(verts[f[0]], verts[f[1]], verts[f[2]]);
@@ -154,70 +154,70 @@ static std::array<double, 3> scale_invariant_circumradius_stats(
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Tests 12 — CuttingUtility: Punkt-in-konvexem-Dreieck (2D UV-Raum) // Tests 12 — CuttingUtility: point-in-convex-triangle (2D UV space)
// Java: CuttinUtilityTest.testIsInConvexTextureFace_False / _True // Java: CuttinUtilityTest.testIsInConvexTextureFace_False / _True
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 1: Punkt liegt weit außerhalb — exakte Java-Koordinaten // Test 1: point lies far outside — exact Java coordinates
TEST(CuttingUtility, IsInConvexTextureFace_False) TEST(CuttingUtility, IsInConvexTextureFace_False)
{ {
// Winziges Dreieck um (0.7488, 0.0629) — Java-Testkoordinaten (T[3]=1, w=1) // Tiny triangle around (0.7488, 0.0629) — Java test coordinates (T[3]=1, w=1)
Eigen::Vector2d v0(0.7488102998904661, 0.06293998610761144); Eigen::Vector2d v0(0.7488102998904661, 0.06293998610761144);
Eigen::Vector2d v1(0.7487811940754379, 0.06289451051246124); Eigen::Vector2d v1(0.7487811940754379, 0.06289451051246124);
Eigen::Vector2d v2(0.7487254625255592, 0.06291429499873116); Eigen::Vector2d v2(0.7487254625255592, 0.06291429499873116);
// Testpunkt weit entfernt bei (0.447, 0.000228) // Test point far away at (0.447, 0.000228)
Eigen::Vector2d pp(0.44661534423161037, 2.2808373704822393e-4); Eigen::Vector2d pp(0.44661534423161037, 2.2808373704822393e-4);
EXPECT_FALSE(point_in_triangle_2d(pp, v0, v1, v2)); EXPECT_FALSE(point_in_triangle_2d(pp, v0, v1, v2));
} }
// Test 2: Punkt liegt innerhalb // Test 2: point lies inside
// Hinweis: Das originale Java-Array p2 hat 5 Elemente mit w=0 (Tippfehler im // Note: the original Java array p2 has 5 elements with w=0 (typo in the
// Java-Original). Hier werden äquivalente, wohlgeformte Koordinaten verwendet, // Java original). Equivalent, well-formed coordinates are used here
// die dasselbe geometrische Szenario abbilden. // that represent the same geometric scenario.
TEST(CuttingUtility, IsInConvexTextureFace_True) TEST(CuttingUtility, IsInConvexTextureFace_True)
{ {
// Dreieck: (0,0) — (1e-8, 0) — (0, 1e-8) // Triangle: (0,0) — (1e-8, 0) — (0, 1e-8)
Eigen::Vector2d v0(0.0, 0.0); Eigen::Vector2d v0(0.0, 0.0);
Eigen::Vector2d v1(1e-8, 0.0); Eigen::Vector2d v1(1e-8, 0.0);
Eigen::Vector2d v2(0.0, 1e-8); Eigen::Vector2d v2(0.0, 1e-8);
// Schwerpunkt des Dreiecks — liegt immer innen // Centroid of the triangle — always lies inside
Eigen::Vector2d pp(1e-8 / 3.0, 1e-8 / 3.0); Eigen::Vector2d pp(1e-8 / 3.0, 1e-8 / 3.0);
EXPECT_TRUE(point_in_triangle_2d(pp, v0, v1, v2)); EXPECT_TRUE(point_in_triangle_2d(pp, v0, v1, v2));
} }
// Zusätzlich: einfaches Einheitsdreieck für Klarheit // Additional: simple unit triangle for clarity
TEST(CuttingUtility, IsInConvexTextureFace_UnitTriangle_InAndOut) TEST(CuttingUtility, IsInConvexTextureFace_UnitTriangle_InAndOut)
{ {
Eigen::Vector2d v0(0.0, 0.0), v1(1.0, 0.0), v2(0.0, 1.0); Eigen::Vector2d v0(0.0, 0.0), v1(1.0, 0.0), v2(0.0, 1.0);
EXPECT_TRUE( point_in_triangle_2d(Eigen::Vector2d(0.25, 0.25), v0, v1, v2)); EXPECT_TRUE( point_in_triangle_2d(Eigen::Vector2d(0.25, 0.25), v0, v1, v2));
EXPECT_FALSE(point_in_triangle_2d(Eigen::Vector2d(2.0, 2.0), v0, v1, v2)); EXPECT_FALSE(point_in_triangle_2d(Eigen::Vector2d(2.0, 2.0), v0, v1, v2));
EXPECT_FALSE(point_in_triangle_2d(Eigen::Vector2d(0.6, 0.6), v0, v1, v2)); // jenseits Hypotenuse EXPECT_FALSE(point_in_triangle_2d(Eigen::Vector2d(0.6, 0.6), v0, v1, v2)); // beyond hypotenuse
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 3 — UnwrapUtility: Eckenwinkel = π für kollineare Vertices // Test 3 — UnwrapUtility: corner angle = π for collinear vertices
// Java: UnwrapUtilityTest.testGetAngleReturnsPI // Java: UnwrapUtilityTest.testGetAngleReturnsPI
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Java: v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) kollinear. // Java: v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) collinear.
// Kante e von v2 nach v1. getAngle(e) = Winkel bei v1 = π. // Edge e from v2 to v1. getAngle(e) = angle at v1 = π.
// //
// C++: Kosinussatz mit Kantenlängen a=|v0-v1|=1, b=|v1-v2|=1, c=|v0-v2|=2. // C++: law of cosines with edge lengths a=|v0-v1|=1, b=|v1-v2|=1, c=|v0-v2|=2.
// cos(γ_v1) = (a² + b² c²) / (2ab) = (1 + 1 4) / 2 = 1 → γ = π // cos(γ_v1) = (a² + b² c²) / (2ab) = (1 + 1 4) / 2 = 1 → γ = π
TEST(UnwrapUtility, GetAngle_CollinearVertices_ReturnsPI) TEST(UnwrapUtility, GetAngle_CollinearVertices_ReturnsPI)
{ {
const double a = 1.0; // |v0 v1| const double a = 1.0; // |v0 v1|
const double b = 1.0; // |v1 v2| const double b = 1.0; // |v1 v2|
const double c = 2.0; // |v0 v2| (= a + b, entartet) const double c = 2.0; // |v0 v2| (= a + b, degenerate)
double cos_angle = (a*a + b*b - c*c) / (2.0 * a * b); double cos_angle = (a*a + b*b - c*c) / (2.0 * a * b);
cos_angle = std::max(-1.0, std::min(1.0, cos_angle)); // numerisches Clamp cos_angle = std::max(-1.0, std::min(1.0, cos_angle)); // numeric clamp
double angle = std::acos(cos_angle); double angle = std::acos(cos_angle);
EXPECT_NEAR(M_PI, angle, 1e-15); EXPECT_NEAR(M_PI, angle, 1e-15);
} }
// Gegenkontrolle: gleichseitiges Dreieck → Winkel = π/3 // Counter-check: equilateral triangle → angle = π/3
TEST(UnwrapUtility, GetAngle_EquilateralTriangle_ReturnsPiOver3) TEST(UnwrapUtility, GetAngle_EquilateralTriangle_ReturnsPiOver3)
{ {
const double s = 1.0; const double s = 1.0;
@@ -227,57 +227,57 @@ TEST(UnwrapUtility, GetAngle_EquilateralTriangle_ReturnsPiOver3)
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 4 — ConvergenceUtility: 2D Umkreisradius // Test 4 — ConvergenceUtility: 2D circumradius
// Java: ConvergenceUtilityTests.testGetTextureCircumRadius // Java: ConvergenceUtilityTests.testGetTextureCircumRadius
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(ConvergenceUtility, TextureCircumRadius_RightTriangle) TEST(ConvergenceUtility, TextureCircumRadius_RightTriangle)
{ {
// A=(0,0), B=(1,0), C=(0,1): rechtwinkliges gleichschenkliges Dreieck // A=(0,0), B=(1,0), C=(0,1): right isosceles triangle
// Seiten: 1, 1, √2. R = √2 / (4 · 0.5) = √2/2 // Sides: 1, 1, √2. R = √2 / (4 · 0.5) = √2/2
Eigen::Vector2d A(0.0, 0.0), B(1.0, 0.0), C(0.0, 1.0); Eigen::Vector2d A(0.0, 0.0), B(1.0, 0.0), C(0.0, 1.0);
EXPECT_NEAR(std::sqrt(2.0) / 2.0, circumradius_2d(A, B, C), 1e-10); EXPECT_NEAR(std::sqrt(2.0) / 2.0, circumradius_2d(A, B, C), 1e-10);
} }
TEST(ConvergenceUtility, TextureCircumRadius_SmallerTriangle) TEST(ConvergenceUtility, TextureCircumRadius_SmallerTriangle)
{ {
// A=(0,0), B=(0.5,0.5), C=(0,1): Java-Variante mit B.T={0.5,0.5,0,1} // A=(0,0), B=(0.5,0.5), C=(0,1): Java variant with B.T={0.5,0.5,0,1}
// Seiten: √0.5, √0.5, 1. Area = 0.25. R = (√0.5·√0.5·1)/(4·0.25) = 0.5 // Sides: √0.5, √0.5, 1. Area = 0.25. R = (√0.5·√0.5·1)/(4·0.25) = 0.5
Eigen::Vector2d A(0.0, 0.0), B(0.5, 0.5), C(0.0, 1.0); Eigen::Vector2d A(0.0, 0.0), B(0.5, 0.5), C(0.0, 1.0);
EXPECT_NEAR(0.5, circumradius_2d(A, B, C), 1e-10); EXPECT_NEAR(0.5, circumradius_2d(A, B, C), 1e-10);
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 5 — ConvergenceUtility: 2D Dreiecksfläche // Test 5 — ConvergenceUtility: 2D triangle area
// Java: ConvergenceUtilityTests.testGetTextureTriangleArea // Java: ConvergenceUtilityTests.testGetTextureTriangleArea
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(ConvergenceUtility, TextureTriangleArea_RightTriangle) TEST(ConvergenceUtility, TextureTriangleArea_RightTriangle)
{ {
// A=(0,0), B=(1,0), C=(0,1) → Fläche = 0.5 // A=(0,0), B=(1,0), C=(0,1) → area = 0.5
Eigen::Vector2d A(0.0, 0.0), B(1.0, 0.0), C(0.0, 1.0); Eigen::Vector2d A(0.0, 0.0), B(1.0, 0.0), C(0.0, 1.0);
EXPECT_NEAR(0.5, triangle_area_2d(A, B, C), 1e-10); EXPECT_NEAR(0.5, triangle_area_2d(A, B, C), 1e-10);
} }
TEST(ConvergenceUtility, TextureTriangleArea_SmallerTriangle) TEST(ConvergenceUtility, TextureTriangleArea_SmallerTriangle)
{ {
// A=(0,0), B=(0.5,0.5), C=(0,1) → Fläche = 0.25 // A=(0,0), B=(0.5,0.5), C=(0,1) → area = 0.25
Eigen::Vector2d A(0.0, 0.0), B(0.5, 0.5), C(0.0, 1.0); Eigen::Vector2d A(0.0, 0.0), B(0.5, 0.5), C(0.0, 1.0);
EXPECT_NEAR(0.25, triangle_area_2d(A, B, C), 1e-10); EXPECT_NEAR(0.25, triangle_area_2d(A, B, C), 1e-10);
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 6 — ConvergenceUtility: Skaleninvarianter Umkreisradius // Test 6 — ConvergenceUtility: scale-invariant circumradius
// Java: ConvergenceUtilityTests.testScaleInvariantCircumCircleRadius // Java: ConvergenceUtilityTests.testScaleInvariantCircumCircleRadius
// //
// Mesh: 4 Vertices (v1..v4), 2 Flächen (f1: v1-v2-v3, f2: v1-v3-v4). // Mesh: 4 vertices (v1..v4), 2 faces (f1: v1-v2-v3, f2: v1-v3-v4).
// Skaleninvariante Größe: R_f / sqrt(total_area) — invariant unter // Scale-invariant quantity: R_f / sqrt(total_area) — invariant under
// uniformer Skalierung (homogeneous weight w: pos = (x/w, y/w)). // uniform scaling (homogeneous weight w: pos = (x/w, y/w)).
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(ConvergenceUtility, ScaleInvariantCircumRadius_BaseScale) TEST(ConvergenceUtility, ScaleInvariantCircumRadius_BaseScale)
{ {
// Positionen bei w=1 (T[3]=1): v1=(0,0), v2=(1,0), v3=(0,1), v4=(-1,0) // Positions at w=1 (T[3]=1): v1=(0,0), v2=(1,0), v3=(0,1), v4=(-1,0)
std::vector<Eigen::Vector2d> verts = { std::vector<Eigen::Vector2d> verts = {
{0.0, 0.0}, // v1 {0.0, 0.0}, // v1
{1.0, 0.0}, // v2 {1.0, 0.0}, // v2
@@ -287,13 +287,13 @@ TEST(ConvergenceUtility, ScaleInvariantCircumRadius_BaseScale)
// f1: v1-v2-v3, f2: v1-v3-v4 // f1: v1-v2-v3, f2: v1-v3-v4
std::vector<std::array<int, 3>> faces = { {0, 1, 2}, {0, 2, 3} }; std::vector<std::array<int, 3>> faces = { {0, 1, 2}, {0, 2, 3} };
// Einzelflächen-Prüfung (Java testGetTextureTriangleArea-Anforderung) // Per-face check (Java testGetTextureTriangleArea requirement)
EXPECT_NEAR(0.5, triangle_area_2d(verts[0], verts[1], verts[2]), 1e-10); EXPECT_NEAR(0.5, triangle_area_2d(verts[0], verts[1], verts[2]), 1e-10);
EXPECT_NEAR(0.5, triangle_area_2d(verts[0], verts[2], verts[3]), 1e-10); EXPECT_NEAR(0.5, triangle_area_2d(verts[0], verts[2], verts[3]), 1e-10);
auto [max_r, mean_r, sum_r] = scale_invariant_circumradius_stats(verts, faces); auto [max_r, mean_r, sum_r] = scale_invariant_circumradius_stats(verts, faces);
// Erwartet: sin(π/4) = √2/2 für max und mean (beide Dreiecke identisch) // Expected: sin(π/4) = √2/2 for max and mean (both triangles identical)
EXPECT_NEAR(std::sin(M_PI / 4.0), max_r, 1e-10); EXPECT_NEAR(std::sin(M_PI / 4.0), max_r, 1e-10);
EXPECT_NEAR(std::sin(M_PI / 4.0), mean_r, 1e-10); EXPECT_NEAR(std::sin(M_PI / 4.0), mean_r, 1e-10);
EXPECT_NEAR(2.0 * std::sin(M_PI / 4.0), sum_r, 1e-10); EXPECT_NEAR(2.0 * std::sin(M_PI / 4.0), sum_r, 1e-10);
@@ -301,7 +301,7 @@ TEST(ConvergenceUtility, ScaleInvariantCircumRadius_BaseScale)
TEST(ConvergenceUtility, ScaleInvariantCircumRadius_HalvedByW2_SameResult) TEST(ConvergenceUtility, ScaleInvariantCircumRadius_HalvedByW2_SameResult)
{ {
// Skalierung durch w=2: alle Positionen halbiert (homogene Koordinaten) // Scaling by w=2: all positions halved (homogeneous coordinates)
// pos_scaled = (T[0]/2, T[1]/2) // pos_scaled = (T[0]/2, T[1]/2)
std::vector<Eigen::Vector2d> verts = { std::vector<Eigen::Vector2d> verts = {
{0.0, 0.0}, // v1/2 {0.0, 0.0}, // v1/2
@@ -311,35 +311,35 @@ TEST(ConvergenceUtility, ScaleInvariantCircumRadius_HalvedByW2_SameResult)
}; };
std::vector<std::array<int, 3>> faces = { {0, 1, 2}, {0, 2, 3} }; std::vector<std::array<int, 3>> faces = { {0, 1, 2}, {0, 2, 3} };
// Flächen sind ein Viertel der ursprünglichen (Längen halbiert → Area / 4) // Areas are one quarter of the original (lengths halved → Area / 4)
EXPECT_NEAR(0.125, triangle_area_2d(verts[0], verts[1], verts[2]), 1e-10); EXPECT_NEAR(0.125, triangle_area_2d(verts[0], verts[1], verts[2]), 1e-10);
EXPECT_NEAR(0.125, triangle_area_2d(verts[0], verts[2], verts[3]), 1e-10); EXPECT_NEAR(0.125, triangle_area_2d(verts[0], verts[2], verts[3]), 1e-10);
auto [max_r, mean_r, sum_r] = scale_invariant_circumradius_stats(verts, faces); auto [max_r, mean_r, sum_r] = scale_invariant_circumradius_stats(verts, faces);
// Skaleninvariante Größe muss identisch zu w=1 sein // Scale-invariant quantity must be identical to the w=1 case
EXPECT_NEAR(std::sin(M_PI / 4.0), max_r, 1e-10); EXPECT_NEAR(std::sin(M_PI / 4.0), max_r, 1e-10);
EXPECT_NEAR(std::sin(M_PI / 4.0), mean_r, 1e-10); EXPECT_NEAR(std::sin(M_PI / 4.0), mean_r, 1e-10);
EXPECT_NEAR(2.0 * std::sin(M_PI / 4.0), sum_r, 1e-10); EXPECT_NEAR(2.0 * std::sin(M_PI / 4.0), sum_r, 1e-10);
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 7 — HomologyTest: Genus-2 Homologie-Generatoren // Test 7 — HomologyTest: genus-2 homology generators
// Java: HomologyTest.testHomology // Java: HomologyTest.testHomology
// //
// Java-Test: // Java test:
// CoHDS hds = TestUtility.readOBJ("brezel2.obj"); // Genus-2-Brezel-Fläche // CoHDS hds = TestUtility.readOBJ("brezel2.obj"); // genus-2 pretzel surface
// List<Set<CoEdge>> paths = getGeneratorPaths(hds.getVertex(0), weightAdapter); // List<Set<CoEdge>> paths = getGeneratorPaths(hds.getVertex(0), weightAdapter);
// Assert.assertEquals(4, paths.size()); // 2g = 4 für g = 2 // Assert.assertEquals(4, paths.size()); // 2g = 4 for g = 2
// //
// C++quivalent: // C++ equivalent:
// ConformalMesh mesh = load_mesh("code/data/obj/brezel2.obj"); // ConformalMesh mesh = load_mesh("code/data/obj/brezel2.obj");
// CutGraph cg = compute_cut_graph(mesh); // CutGraph cg = compute_cut_graph(mesh);
// EXPECT_EQ(4u, cg.cut_edge_indices.size()); // 2g = 4 // EXPECT_EQ(4u, cg.cut_edge_indices.size()); // 2g = 4
// EXPECT_EQ(2, cg.genus); // EXPECT_EQ(2, cg.genus);
// //
// Mesh: V=2622, F=5248, E=7872, χ=2, genus=2. // Mesh: V=2622, F=5248, E=7872, χ=2, genus=2.
// Pfad via CONFORMALLAB_DATA_DIR (CMakeLists.txt: ${CMAKE_SOURCE_DIR}/data). // Path via CONFORMALLAB_DATA_DIR (CMakeLists.txt: ${CMAKE_SOURCE_DIR}/data).
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(HomologyGenerators, Genus2_FourCutEdges) TEST(HomologyGenerators, Genus2_FourCutEdges)
@@ -359,17 +359,17 @@ TEST(HomologyGenerators, Genus2_FourCutEdges)
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Tests 89 — EuclideanLayoutTest: Kantenlängenerhalt auf tetraflat.obj // Tests 89 — EuclideanLayoutTest: edge-length preservation on tetraflat.obj
// Java: EuclideanLayoutTest.testDoLayout // Java: EuclideanLayoutTest.testDoLayout
// //
// Java-Test: // Java test:
// Vector u = new SparseVector(n); // u = 0 (kein konformer Faktor) // Vector u = new SparseVector(n); // u = 0 (no conformal factor)
// EuclideanLayout.doLayout(hds, fun, u); // EuclideanLayout.doLayout(hds, fun, u);
// for (CoEdge e : hds.getEdges()) // for (CoEdge e : hds.getEdges())
// assertEquals(Pn.distanceBetween(s.P, t.P), Pn.distanceBetween(s.T, t.T), 1E-11); // assertEquals(Pn.distanceBetween(s.P, t.P), Pn.distanceBetween(s.T, t.T), 1E-11);
// //
// Bedeutung: Mit u=0 ist der konforme Faktor 0, also ℓ̃ = (keine Verformung). // Meaning: with u=0 the conformal factor is 0, so ℓ̃ = (no deformation).
// Das Layout muss die ursprünglichen 3D-Kantenlängen exakt reproduzieren. // The layout must reproduce the original 3D edge lengths exactly.
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(EuclideanLayout, DoLayout_TetraFlat_EdgeLengthsPreserved) TEST(EuclideanLayout, DoLayout_TetraFlat_EdgeLengthsPreserved)
@@ -414,18 +414,18 @@ TEST(EuclideanLayout, DoLayout_TetraFlat_EdgeLengthsPreserved)
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 10 — EuclideanCyclicConvergenceTest: Newton auf cathead.obj // Test 10 — EuclideanCyclicConvergenceTest: Newton on cathead.obj
// Java: EuclideanLayoutTest.testLayout02 (130-Werte-Regression auf cathead.heml) // Java: EuclideanLayoutTest.testLayout02 (130-value regression on cathead.heml)
// EuclideanCyclicConvergenceTest.testEuclideanConvergence // EuclideanCyclicConvergenceTest.testEuclideanConvergence
// //
// Java-Test: // Java test:
// EuclideanLayout.doLayout(hdsCat, fun, uCat); // EuclideanLayout.doLayout(hdsCat, fun, uCat);
// for (CoVertex v : interior vertices) // for (CoVertex v : interior vertices)
// assertEquals(2*PI, calculateAngleSum(v), 1E-6); // assertEquals(2*PI, calculateAngleSum(v), 1E-6);
// for (CoEdge e : positiveEdges) // for (CoEdge e : positiveEdges)
// assertEquals(fun.getNewLength(e, u), tLength, 1E-6); // assertEquals(fun.getNewLength(e, u), tLength, 1E-6);
// //
// C++quivalent: Newton converges on cathead.obj; interior angle sums ≈ 2π. // C++ equivalent: Newton converges on cathead.obj; interior angle sums ≈ 2π.
// The 130-value u-vector from the Java test is cathead-topology-specific and // The 130-value u-vector from the Java test is cathead-topology-specific and
// depends on vertex ordering in the Java CoHDS — not portable directly. // depends on vertex ordering in the Java CoHDS — not portable directly.
// Instead we verify the same mathematical invariant: convergence + angle sums. // Instead we verify the same mathematical invariant: convergence + angle sums.
@@ -468,18 +468,18 @@ TEST(EuclideanLayout, CatHead_NewtonConverges_AngleSumsTwoPi)
} }
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
// Test 11 — SphericalConvergenceTest: Newton auf Oktaeder // Test 11 — SphericalConvergenceTest: Newton on octahedron
// Java: SphericalConvergenceTest.testSphericalConvergence // Java: SphericalConvergenceTest.testSphericalConvergence
// //
// Java-Test: // Java test:
// FunctionalTest.createOctahedron(hds, aSet); // FunctionalTest.createOctahedron(hds, aSet);
// // randomly perturb vertex radii (seed=1) // // randomly perturb vertex radii (seed=1)
// prepareInvariantDataHyperbolicAndSpherical(functional, hds, aSet, u); // prepareInvariantDataHyperbolicAndSpherical(functional, hds, aSet, u);
// optimizer.minimize(u, opt); // optimizer.minimize(u, opt);
// for (CoVertex v) assertEquals(2*PI, sum of angles at v, 1E-8); // for (CoVertex v) assertEquals(2*PI, sum of angles at v, 1E-8);
// //
// C++: regulärer Oktaeder (alle Knoten auf S², keine Störung), sphärischer Newton, // C++: regular octahedron (all vertices on S², no perturbation), spherical Newton,
// prüft Konvergenz + Restgradienten (≡ Winkeldefekt = 0 nach Konvergenz). // checks convergence + residual gradients (≡ angle deficit = 0 after convergence).
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
TEST(SphericalLayout, SphericalTetrahedron_NewtonConverges_AngleSumsTwoPi) TEST(SphericalLayout, SphericalTetrahedron_NewtonConverges_AngleSumsTwoPi)

View File

@@ -1,77 +1,211 @@
# Phase 8 — CGAL Package Design # Phase 8 — CGAL Package Design
> **Status: planned.** This document describes the target architecture for Phase 8. > **Status: design frozen, implementation planned.**
> No code has been written yet. The design is informed by the CGAL package submission > This document captures the strategic decisions taken before the first
> guidelines at https://www.cgal.org/developers.html > line of Phase 8 code is written. The decisions were taken on 2026-05-19
> after the docstring/architecture audit at the end of Phase 7.
>
> The design is informed by the CGAL package submission guidelines at
> https://www.cgal.org/developers.html and by reading the existing
> `Polygon_mesh_processing` and `Surface_mesh_parameterization` packages.
--- ---
## Goal ## Strategic position
Integrate conformallab++ into the CGAL library as a proper CGAL package: | Question | Decision | Rationale |
`Discrete_conformal_map`. The package must satisfy all CGAL submission requirements: |---|---|---|
traits-class design, Doxygen documentation, CGAL-format test suite, and coverage of | Submission to CGAL? | **Pre-submission-ready, not submission-bound.** 12+ months horizon, optional. | Keep design freedom, no editor-review pressure. Structure is valuable on its own. |
the CGAL coding conventions. | License | **MIT preserved.** | CGAL submission would require LGPL — deferred. Current users (academic + industrial) profit from MIT. |
| Mesh-type flexibility | **Generic `FaceGraph + HalfedgeGraph`.** | Maximum CGAL value: works with `Surface_mesh`, `Polyhedron_3`, OpenMesh-adapter, pmp. |
| Parameter style | **Named Parameters** (`CGAL::parameters::vertex_curvature_map(...).max_iterations(50)`). | CGAL standard; identical UX to `PMP::triangulate_*`. |
| Default kernel | **`CGAL::Simple_cartesian<double>`.** | Status quo. Conformal geometry does not require exact predicates. |
| Backward compatibility | **Dual-layer wrapper.** `code/include/*.hpp` stays as implementation; `include/CGAL/*.h` is thin wrapper. | Existing 176 + 36 tests unchanged. New API gets new tests. |
| Algorithm code | **No duplication.** New CGAL headers delegate to existing code via property-map adapters. | Single source of truth; no parallel maintenance. |
---
## Architecture
### Three-layer model
```
┌──────────────────────────────────────────────────────────────────┐
│ Layer 3: Public CGAL API include/CGAL/*.h │
│ ───────────────────────── │
│ • Conformal_map_traits.h ← concept + default model │
│ • Discrete_conformal_map.h ← user-facing entry │
│ • Conformal_layout.h, ... │
│ Named parameters, generic over FaceGraph, Doxygen-documented. │
└──────────────────────────────────────────────────────────────────┘
│ thin wrapper, no algorithm code
┌──────────────────────────────────────────────────────────────────┐
│ Layer 2: Adapter / Traits include/CGAL/Conformal_map/ │
│ ───────────────────────── │
│ • Default_traits.h ← maps generic FaceGraph to │
│ Surface_mesh property maps │
│ • Property_map_adapter.h ← read/write u, θ, α via │
│ boost::property_map traits │
└──────────────────────────────────────────────────────────────────┘
│ uses existing algorithms as-is
┌──────────────────────────────────────────────────────────────────┐
│ Layer 1: Implementation code/include/*.hpp │
│ ───────────────────────── │
│ euclidean_functional.hpp, layout.hpp, newton_solver.hpp, ... │
│ Hardcoded to Surface_mesh + Simple_cartesian — unchanged. │
└──────────────────────────────────────────────────────────────────┘
```
--- ---
## 8a — Traits class & concepts ## 8a — Traits class & concepts
The current code is tightly coupled to `CGAL::Surface_mesh<Point3>`. Phase 8a introduces ### `ConformalMapTraits` concept
a traits class that separates the mesh type from the algorithm:
The concept lists the types and operations every Traits model must provide.
```cpp ```cpp
// TODO(Phase 8a): implement this header namespace CGAL {
// include/CGAL/Conformal_map_traits.h
template< // Concept (documentation only; no code):
typename MeshType, // any CGAL halfedge mesh struct ConformalMapTraits {
typename KernelType, // CGAL kernel // Types
typename ScalarType = double using Triangle_mesh = ...; // model of FaceGraph + HalfedgeGraph
> using FT = ...; // typically double
struct Conformal_map_traits { using Vertex_descriptor = boost::graph_traits<Triangle_mesh>::vertex_descriptor;
using Mesh = MeshType; using Halfedge_descriptor = ...;
using Kernel = KernelType; using Face_descriptor = ...;
using FT = ScalarType;
// ... vertex/edge/face descriptor types // Read access (input geometry)
// ... property map access using Vertex_point_map = ...; // model of ReadablePropertyMap
// key: Vertex_descriptor
// value: K::Point_3
// Read/write access (conformal data)
using Lambda_pmap = ...; // u_v (scale factor) RW
using Theta_pmap = ...; // Θ_v (target curvature) R
using Vertex_index_pmap = ...; // DOF index (1 = pinned) RW
using Edge_alpha_pmap = ...; // α_e (hyperbolic only) RW
using Face_type_pmap = ...; // geometry tag per face R
// Optional output
using UV_pmap = ...; // halfedge → (u, v) ∈ ℝ² W
using Holonomy_pmap = ...; // seam edge → ω ∈ W
};
}
```
### `Default_conformal_map_traits<TM>`
The default model wraps `Surface_mesh` property maps so existing code keeps
working through the new public API.
```cpp
template <class TriangleMesh,
class K = CGAL::Simple_cartesian<double>>
struct Default_conformal_map_traits;
// Specialisation for Surface_mesh:
template <class K>
struct Default_conformal_map_traits<CGAL::Surface_mesh<typename K::Point_3>, K> {
using Triangle_mesh = CGAL::Surface_mesh<typename K::Point_3>;
using FT = typename K::FT;
using Vertex_point_map = typename Triangle_mesh::Point_property_map;
using Lambda_pmap = typename Triangle_mesh::template Property_map<vertex_descriptor, FT>;
// ... etc, using "conformal:lambda" property names
};
// Generic specialisation for other FaceGraph models will be added in 8a.2.
```
### Concept-checks
```cpp
// include/CGAL/Conformal_map_concept_checks.h
template <class Traits>
struct Conformal_map_traits_check {
static_assert(boost::is_same<...>::value, "Traits::FT must be a floating-point type");
static_assert(is_face_graph<Traits::Triangle_mesh>::value);
// ...
}; };
``` ```
Concept checks will ensure any user-provided mesh satisfies the halfedge mesh concept.
--- ---
## 8b — Public header hierarchy ## 8b — Public CGAL header hierarchy
A clean public API separate from the internal implementation: ### User-facing entry
```cpp
// include/CGAL/Discrete_conformal_map.h
namespace CGAL {
template <class TriangleMesh, class NamedParameters = parameters::Default_named_parameters>
bool discrete_conformal_map_euclidean(TriangleMesh& mesh,
const NamedParameters& np = parameters::default_values());
template <class TriangleMesh, class NamedParameters = ...>
bool discrete_conformal_map_spherical(TriangleMesh& mesh,
const NamedParameters& np = ...);
template <class TriangleMesh, class NamedParameters = ...>
bool discrete_conformal_map_hyperbolic(TriangleMesh& mesh,
const NamedParameters& np = ...);
} // namespace CGAL
```
### Named parameter vocabulary
| Parameter | Type | Default | Meaning |
|---|---|---|---|
| `vertex_curvature_map(pmap)` | ReadablePropertyMap | `2π` at interior, `π` at boundary | Θᵥ values |
| `fixed_vertex_pmap(pmap)` | ReadablePropertyMap<bool> | First vertex pinned | Which vertices are pinned (gauge) |
| `max_iterations(n)` | int | 200 | Newton iteration limit |
| `gradient_tolerance(eps)` | FT | 1e-10 | `‖G‖∞` threshold |
| `vertex_index_map(pmap)` | LvaluePropertyMap | DOF auto-assigned | Allows user to override DOF assignment |
| `output_uv_map(pmap)` | WritablePropertyMap | none | If set, writes UV layout into pmap |
| `cut_graph(cg)` | `Conformal_cut_graph` | auto-computed | Pre-computed seam edges (mandatory for closed surfaces) |
| `geom_traits(t)` | model of ConformalMapTraits | `Default_*` | Custom traits |
### Modular headers
``` ```
include/CGAL/ include/CGAL/
Discrete_conformal_map.h ← single user-facing include ├── Discrete_conformal_map.h ← user-facing entry (1 include for casual use)
Conformal_map_traits.h ├── Conformal_map_traits.h ← concept + Default_conformal_map_traits
Conformal_newton_solver.h ├── Conformal_map_concept_checks.h
Conformal_layout.h ├── Conformal_newton_solver.h ← standalone Newton (advanced users)
Conformal_cut_graph.h ├── Conformal_layout.h ← layout + holonomy
conformal_map_package.h ← PackageDescription ├── Conformal_cut_graph.h ← orthogonal algorithm
├── Conformal_period_matrix.h ← genus-1 τ (conformallab++ unique)
├── Conformal_holonomy.h ← Möbius holonomy (conformallab++ unique)
└── Conformal_map/ ← CGAL convention: implementation details
├── Default_traits.h
├── Property_map_adapter.h
├── Newton_iteration.h
└── Internal_helpers.h
``` ```
All existing `include/*.hpp` headers remain as internal implementation details,
not part of the public CGAL API.
--- ---
## 8c — CGAL-style documentation ## 8c — CGAL-style documentation
``` ```
doc/Conformal_map/ doc/Conformal_map/
PackageDescription.txt ├── PackageDescription.txt ← CGAL Doxygen package file
User_manual.md ├── Conformal_map.txt ← Doxygen User_manual
Reference_manual.md ├── examples.txt ← linkable example code
fig/ pipeline diagrams, mathematical figures ├── dependenciestextual list
└── fig/ ← pipeline diagrams, math figures
``` ```
All public functions and concepts require Doxygen comments following the CGAL style. All public functions, concepts, and types require Doxygen. See **Phase 7.5** (below).
--- ---
@@ -79,88 +213,191 @@ All public functions and concepts require Doxygen comments following the CGAL st
``` ```
test/Conformal_map/ test/Conformal_map/
CMakeLists.txt ← CGAL-format, uses find_package(CGAL) ├── CMakeLists.txt ← CGAL-format, uses find_package(CGAL)
test_euclidean_functional.cpp ├── test_euclidean_traits.cpp ← traits concept checks
test_newton_solver.cpp ├── test_polyhedron_3_backend.cpp ← tests with Polyhedron_3 as mesh
... ├── test_named_parameters.cpp
└── data/ ← test meshes
``` ```
The existing GTest suite remains. CGAL-format tests are added alongside as a separate The existing GTest suite at `code/tests/cgal/` remains; CGAL-format tests are
target, following the CGAL test infrastructure conventions. added alongside as a separate target. CI runs both.
--- ---
## 8e — Declarative YAML pipeline ## 8e — Declarative YAML pipeline
A lightweight YAML format for reproducible experiments. The CLI accepts A lightweight YAML format for reproducible experiments. CLI accepts
`--pipeline experiment.yml`; the validator checks `require`/`provide` tokens `--pipeline experiment.yml`; the validator checks `require`/`provide` tokens
before execution. before execution.
**Full concept & design specification:** [doc/concepts/declarative-pipeline.md](../concepts/declarative-pipeline.md) **Full spec:** [doc/concepts/declarative-pipeline.md](../concepts/declarative-pipeline.md)
— token vocabulary, validation algorithm, 5 complete examples, implementation plan. — token vocabulary, validation algorithm, 5 complete examples.
Abbreviated example:
```yaml ```yaml
pipeline: pipeline:
name: flat_torus_period name: flat_torus_period
geometry: euclidean geometry: euclidean
input: { source: data/torus.off }
input:
source: data/torus.off
steps: steps:
- id: setup - { id: setup, unit: setup_euclidean_maps, provide: [maps_initialised] }
unit: setup_euclidean_maps - { id: gb, unit: enforce_gauss_bonnet, require: [maps_initialised], provide: [gauss_bonnet_satisfied] }
provide: [maps_initialised] - { id: solve, unit: newton_euclidean, require: [gauss_bonnet_satisfied], provide: [x_converged] }
- { id: cut, unit: compute_cut_graph, require: [mesh_closed], provide: [cut_graph] }
- id: gauss_bonnet - { id: layout, unit: euclidean_layout, require: [x_converged, cut_graph], provide: [layout_uv, holonomy] }
unit: enforce_gauss_bonnet - { id: period, unit: compute_period_matrix, require: [holonomy], provide: [tau] }
require: [maps_initialised]
provide: [gauss_bonnet_satisfied]
- id: solve
unit: newton_euclidean
require: [gauss_bonnet_satisfied]
params:
tol: 1.0e-10
max_iter: 200
provide: [x_converged]
- id: cut
unit: compute_cut_graph
require: [mesh_closed]
provide: [cut_graph]
- id: layout
unit: euclidean_layout
require: [x_converged, cut_graph]
params:
normalise: true
provide: [layout_uv, holonomy]
- id: period
unit: compute_period_matrix
require: [holonomy]
provide: [tau]
output: output:
layout: out/torus_layout.off layout: out/torus_layout.off
json: out/torus_result.json json: out/torus_result.json
tau: out/torus_tau.txt
``` ```
The contract table in [contracts.md](contracts.md) defines the valid `require`/`provide`
token vocabulary.
--- ---
## TODO ## Phase 7.5 — Doxygen infrastructure (prerequisite)
- [ ] Design `Conformal_map_traits.h` interface (8a) Before any Phase 8 code, the existing API surface must be extractable.
- [ ] Define concept requirements for `MeshType` (8a) This is the prerequisite that bridges Phase 7 → Phase 8.
- [ ] Create `include/CGAL/` header skeleton (8b)
- [ ] Write `PackageDescription.txt` (8c) ```
- [ ] Port GTest tests to CGAL format (8d) Phase 7.5 — Doxygen infrastructure
- [ ] Implement YAML validator (8e) ──────────────────────────────────
- [ ] CLI: `--pipeline` flag (8e) • Doxyfile (CGAL-conform: INPUT=code/include + include/CGAL,
EXCLUDE_PATTERNS="* 2.hpp")
• doxygen-awesome-css as theme (matches CGAL house style)
• CMake target: cmake --build build --target doc
• CI job: doc-build → publishes to Codeberg Pages or gitea-pages
• Extract baseline once → snapshot what is actually exported today
• Top-5 central headers (3 functional + conformal_mesh + layout)
upgraded to Doxygen comments; the rest follows during Phase 8 implementation
```
The baseline snapshot doubles as the API-design review tool: before designing
the public CGAL wrapper, we see exactly which functions, classes and free
operators exist and need to be wrapped or hidden.
---
## Validation criteria
Phase 8a is "done" when:
1. `cgal.ConformalTraits.Polyhedron_3_works` passes.
2. `cgal.ConformalTraits.Surface_mesh_default_works` passes — identical results to the legacy API.
3. The Inversive-Distance functional (Phase 9a) is implementable as the *first* new client of the traits API without architectural changes — no breaking changes to the trait concept.
4. A user can write `#include <CGAL/Discrete_conformal_map.h>` and call `discrete_conformal_map_euclidean(mesh, parameters::vertex_curvature_map(theta))` against a `Polyhedron_3` and get a valid layout.
If any of these fail, the design is iterated before continuing.
---
## Implementation strategy — "Hybrid MVP" (decided 2026-05-19)
After cost/benefit re-evaluation, the plan is **not** to build Phase 8 in full
before resuming the port. Instead:
```
┌────────────────────────────────────────────────────────────────────┐
│ PHASE 8 MVP (35 days) │
│ ───────────────────── │
│ Just enough CGAL-style architecture for Phase 9a to validate it. │
│ │
│ • Conformal_map_traits.h concept + Default<Surface_mesh,K> │
│ • Discrete_conformal_map.h ONE entry: _euclidean() │
│ • 4 named parameters Θ-map, max_iter, tol, pin │
│ • Concept-check header │
│ • Doxygen on these 3 headers │
└────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ PHASE 9a — Inversive-Distance (35 days) │
│ ────────────────────────────────── │
│ Built directly against the new traits API. This is the │
│ acceptance test for the MVP. │
│ │
│ If painless → MVP design is sound, continue with Phase 9b/9c │
│ If painful → iterate the traits design before going further │
└────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ PHASE 9b — Analytic HyperIdeal Hessian (1 week) │
│ PHASE 9c — 4g-polygon fundamental domain (1 week) │
│ ──────────────────────────────────────── │
│ Port really finished. v0.9.0 release possible. │
└────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ PHASE 8 EXTENSIONS — only on demand │
│ ───────────────────────────────── │
│ • 8a.2 generic FaceGraph specialisation when Polyhedron_3 user │
│ • 8b extend to spherical + hyperbolic when 9a pattern proven │
│ • 8c full User_manual + Reference_manual when submission planned│
│ • 8d CGAL-format test directory when submission planned│
│ • 8e YAML pipeline + CLI flag orthogonal, any time │
│ │
│ Each extension only when there is a concrete trigger. No │
│ speculative architecture for a hypothetical CGAL submission. │
└────────────────────────────────────────────────────────────────────┘
```
**Why this order?**
- Port-completion (Phase 9) is the higher-confidence value: well-defined,
~3 weeks of work, finishes Goal A.
- Full Phase 8 (34 weeks) speculative — only pays off if CGAL submission
actually happens, which is uncertain.
- Phase 8 MVP captures the architectural insight (traits + named params)
without the long tail. If the rest of Phase 8 is ever wanted, it's
additive — nothing built in the MVP needs to be thrown away.
**Total committed budget: 2 weeks (MVP + 9a) + 2 weeks (9b + 9c) = ~4 weeks
net work, 68 weeks calendar.** After that, the port is finished.
## Phase 8 MVP scope — what is and isn't in the first cut
| Item | MVP | Later | Reason |
|---|:---:|:---:|---|
| `Conformal_map_traits.h` concept | ✅ | — | Core abstraction |
| `Default_conformal_map_traits<Surface_mesh, K>` | ✅ | — | Status-quo wrapper |
| Generic `FaceGraph` specialisation | — | ✅ 8a.2 | Speculative until asked |
| `Discrete_conformal_map.h``_euclidean()` | ✅ | — | First entry function |
| `Discrete_conformal_map.h``_spherical()`, `_hyperbolic()` | — | ✅ 8b.2 | Pattern-replicates once 9a works |
| Named parameters: `vertex_curvature_map`, `max_iterations`, `gradient_tolerance`, `fixed_vertex_pmap` | ✅ | — | Essential 4 |
| Named parameters: rest (`output_uv_map`, `cut_graph`, …) | — | ✅ 8b.2 | Additive |
| Doxygen on MVP headers | ✅ | — | Same time anyway |
| Doxygen on legacy `code/include/*` | partial | ✅ 8c | Bulk later |
| `PackageDescription.txt` | — | ✅ 8c | Only if submitting |
| User_manual.md | — | ✅ 8c | Only if submitting |
| `test/Conformal_map/` CGAL-style | — | ✅ 8d | Only if submitting |
| YAML pipeline + CLI flag | — | ✅ 8e | Orthogonal, any time |
---
## TODO checklist
### MVP track (committed work, ~4 weeks)
- [x] Phase 7.5: Doxyfile + CMake doc target + duplicate cleanup
- [ ] **Phase 8 MVP — Traits + one wrapper**
- [ ] `Conformal_map_traits.h` — concept documentation
- [ ] `Default_conformal_map_traits<Surface_mesh, K>`
- [ ] `Conformal_map_concept_checks.h`
- [ ] `Discrete_conformal_map.h` with `_euclidean()` only
- [ ] 4 named parameters: Θ-map, max_iter, tol, pin
- [ ] Test: `cgal.ConformalTraits.Surface_mesh_default_works`
- [ ] **Phase 9a — Inversive-Distance (acceptance test for MVP)**
- [ ] `inversive_distance_functional.hpp` against new traits
- [ ] Gradient check + Newton convergence tests
- [ ] Doxygen on new headers
- [ ] **Phase 9b — Analytic HyperIdeal Hessian**
- [ ] Replace FD in `hyper_ideal_hessian.hpp`
- [ ] Symmetry + PSD checks unchanged
- [ ] **Phase 9c — 4g-polygon for genus g > 1**
- [ ] Extend `compute_fundamental_domain()` beyond genus 1
### On-demand track (only with concrete trigger)
- [ ] 8a.2: Generic `FaceGraph` specialisation (trigger: Polyhedron_3 user)
- [ ] 8b.2: `_spherical()` + `_hyperbolic()` entry functions (trigger: pattern proven)
- [ ] 8b.2: `Conformal_layout.h`, `Conformal_cut_graph.h` wrappers
- [ ] 8c: `doc/Conformal_map/PackageDescription.txt` (trigger: submission planned)
- [ ] 8c: User_manual + Reference_manual (trigger: submission planned)
- [ ] 8d: `test/Conformal_map/` CGAL-style tests (trigger: submission planned)
- [ ] 8e: YAML validator + CLI `--pipeline` flag (orthogonal, any time)

View File

@@ -28,22 +28,22 @@ Java reference implementation: [github.com/varylab/conformallab](https://github.
## geometry-central cross-reference *(optional comparison track)* ## geometry-central cross-reference *(optional comparison track)*
> Diese Referenzen beziehen sich auf eine alternative Implementierung desselben > These references relate to an alternative implementation of the same
> mathematischen Problems. Sie sind keine Voraussetzung für conformallab++, > mathematical problem. They are not prerequisites for conformallab++,
> aber relevant für Kreuz-Validierung und mögliche algorithmische Adoptionen > but are relevant for cross-validation and possible algorithmic adoptions
> (→ GC-1/2/3 im Phasen-Roadmap, → Abschnitt 9 in `validation.md`). > (→ GC-1/2/3 in the phase roadmap, → Section 9 in `validation.md`).
| Reference | Relevanz | | Reference | Relevance |
|---|---| |---|---|
| **Gillespie, Springborn, Crane***Discrete Conformal Equivalence of Polyhedral Surfaces*, ACM SIGGRAPH 2021. DOI: [10.1145/3450626.3459763](https://doi.org/10.1145/3450626.3459763) | Implementiert in **geometry-central**. Erweitert Springborn 2020 um intrinsische Triangulierungen und Ptolemäische Flips. Löst dasselbe DCE-Problem wie conformallab++, aber mit anderem Algorithmus. | | **Gillespie, Springborn, Crane***Discrete Conformal Equivalence of Polyhedral Surfaces*, ACM SIGGRAPH 2021. DOI: [10.1145/3450626.3459763](https://doi.org/10.1145/3450626.3459763) | Implemented in **geometry-central**. Extends Springborn 2020 with intrinsic triangulations and Ptolemaic flips. Solves the same DCE problem as conformallab++, but with a different algorithm. |
| **Sharp, Soliman, Crane***Navigating Intrinsic Triangulations*, ACM SIGGRAPH 2019 | Algorithmische Grundlage für `SignpostIntrinsicTriangulation` in geometry-central — relevant für GC-2 (optionales Pre-Conditioning). | | **Sharp, Soliman, Crane***Navigating Intrinsic Triangulations*, ACM SIGGRAPH 2019 | Algorithmic basis for `SignpostIntrinsicTriangulation` in geometry-central — relevant for GC-2 (optional pre-conditioning). |
**Hinweis zu Springborn 2020:** **Note on Springborn 2020:**
Das Papier *"Ideal Hyperbolic Polyhedra and Discrete Uniformization"* The paper *"Ideal Hyperbolic Polyhedra and Discrete Uniformization"*
(Springborn, Discrete & Computational Geometry 2020) ist **in conformallab++ (Springborn, Discrete & Computational Geometry 2020) is **already implemented in
bereits implementiert** — es ist die direkte Referenz für den HyperIdeal-Geometriemodus conformallab++** — it is the direct reference for the HyperIdeal geometry mode
(`hyper_ideal_geometry.hpp`). Die geometry-central Implementierung (Gillespie 2021) (`hyper_ideal_geometry.hpp`). The geometry-central implementation (Gillespie 2021)
baut auf diesem Papier auf und ergänzt es um Ptolemäische Flips. builds on this paper and augments it with Ptolemaic flips.
--- ---

View File

@@ -193,66 +193,58 @@ These are the **holonomy consistency** checks implemented in `test_phase7.cpp`
## 9 — Cross-validation with geometry-central *(optional / hypothetical)* ## 9 — Cross-validation with geometry-central *(optional / hypothetical)*
> **Hinweis:** Dieser Abschnitt beschreibt eine mögliche externe Kreuz-Validierung, > **Note:** This section describes a possible external cross-validation that is not
> die keine Voraussetzung für die Korrektheit der Implementierung ist. > a prerequisite for the correctness of the implementation.
> Sie ist interessant, weil geometry-central denselben mathematischen Kern > It is of interest because geometry-central implements the same mathematical core
> implementiert (Gillespie, Springborn, Crane — SIGGRAPH 2021, aufbauend auf > (Gillespie, Springborn, Crane — SIGGRAPH 2021, building on
> Springborn 2020), aber mit einer anderen algorithmischen Strategie > Springborn 2020), but with a different algorithmic strategy
> (Ptolemäische Flips + intrinsische Triangulierungen statt Newton auf der > (Ptolemaic flips + intrinsic triangulations instead of Newton on the
> Original-Triangulierung). > original triangulation).
### Welche Outputs sind vergleichbar? ### Which outputs are comparable?
| Output | conformallab++ | geometry-central | Vergleichbar? | | Output | conformallab++ | geometry-central | Comparable? |
|---|---|---|---| |---|---|---|---|
| u-Vektor (Skalierungsparameter) | `res.x` | `u` nach Yamabe flow | ✓ nach Normalisierung | | u-vector (scale parameters) | `res.x` | `u` after Yamabe flow | ✓ after normalisation |
| UV-Koordinaten | `layout.uv[v]` | konforme Parametrisierung | ✓ bis auf Möbius-Transformation | | UV coordinates | `layout.uv[v]` | conformal parameterisation | ✓ up to Möbius transformation |
| Gauss-Bonnet Defekt | `gauss_bonnet_sum()` | implizit via Krümmungsfluss | ✓ (analytisch identisch) | | Gauss-Bonnet deficit | `gauss_bonnet_sum()` | implicit via curvature flow | ✓ (analytically identical) |
| Anzahl Newton-Iterationen | `res.iterations` | Yamabe-Schritte | ~ (anderer Algorithmus) | | Number of Newton iterations | `res.iterations` | Yamabe steps | ~ (different algorithm) |
| Period-Matrix τ | `pd.tau_reduced` | **nicht vorhanden** | ✗ | | Period matrix τ | `pd.tau_reduced` | **not available** | ✗ |
| Möbius-Holonomie | `hol.T_a, T_b` | **nicht vorhanden** | ✗ | | Möbius holonomy | `hol.T_a, T_b` | **not available** | ✗ |
### Normalisierungsabgleich ### Normalisation alignment
Der u-Vektor in conformallab++ hat einen Freiheitsgrad (globale additive Konstante The u-vector in conformallab++ has one degree of freedom (global additive constant —
Eichfreiheit nach Pin-Fixierung). geometry-central kann eine andere Konvention nutzen. gauge freedom after pin-fixing). geometry-central may use a different convention.
Vor dem Vergleich normalisieren: Normalise before comparing:
```cpp ```cpp
// conformallab++: u zentrieren // conformallab++: centre u
double mean_u = std::accumulate(x.begin(), x.end(), 0.0) / x.size(); double mean_u = std::accumulate(x.begin(), x.end(), 0.0) / x.size();
std::vector<double> x_norm(x.size()); std::vector<double> x_norm(x.size());
for (int i = 0; i < x.size(); ++i) x_norm[i] = x[i] - mean_u; for (int i = 0; i < x.size(); ++i) x_norm[i] = x[i] - mean_u;
// Dann mit geometry-central u-Vektor (ebenfalls zentriert) vergleichen: // Then compare with the geometry-central u-vector (also centred):
// max|x_norm[i] - gc_u[i]| < 1e-8 → identischer Konvergenzpunkt // max|x_norm[i] - gc_u[i]| < 1e-8 → identical convergence point
``` ```
### Wann ist der Vergleich sinnvoll? ### When is the comparison useful?
| Zeitpunkt | Was ist möglich | | Point in time | What is possible |
|---|---| |---|---|
| **Jetzt (Phase 7)** | Manueller Vergleich mit denselben `.off`/`.obj` Testnetzen | | **Now (Phase 7)** | Manual comparison using the same `.off`/`.obj` test meshes |
| **Nach Phase 8** | Automatisiertes Vergleichsskript (Python oder separates C++-Binary) | | **After Phase 8** | Automated comparison script (Python or separate C++ binary) |
| **Phase 10 (Forschung)** | Algorithmus-Vergleich: Newton vs. Ptolemäische Flips auf schwierigen Netzen | | **Phase 10 (research)** | Algorithm comparison: Newton vs. Ptolemaic flips on difficult meshes |
### Voraussetzungen für einen fairen Vergleich ### Connection to the literature
1. Identische Eingabenetze (OFF/OBJ, gleiche Vertex-Orientierung) The Springborn 2020 paper ("Ideal Hyperbolic Polyhedra and Discrete Uniformization")
2. Gleiche Gauss-Bonnet-Zielkrümmungen (Θᵥ = 2π für alle v, geschlossene Fläche) is **already implemented in conformallab++** — it is the mathematical foundation
3. u-Normalisierung abgeglichen (zentriert, gleiche Eichfixierung) for the HyperIdeal geometry mode (Phase 2/3). The geometry-central implementation
4. Konvergenztoleranz synchronisiert (max. Gradientnorm < 1e-8) is based on the extension by Gillespie, Springborn & Crane (2021), which uses the
same variational principle of BobenkoSpringborn 2004 but additionally applies
### Verbindung zur Literatur Ptolemaic flips to improve the triangulation during optimisation — an idea not yet
implemented in conformallab++ (→ GC-2 in the phase roadmap).
Das Springborn 2020-Papier ("Ideal Hyperbolic Polyhedra and Discrete Uniformization")
ist **in conformallab++ bereits implementiert** es ist die mathematische Grundlage
für den HyperIdeal-Geometriemodus (Phase 2/3). Die geometry-central Implementierung
basiert auf der Weiterentwicklung von Gillespie, Springborn & Crane (2021), die
denselben Variationsprinzip von BobenkoSpringborn 2004 verwendet, aber zusätzlich
Ptolemäische Flips einsetzt, um die Triangulierung während der Optimierung zu
verbessern eine Idee, die in conformallab++ noch nicht implementiert ist (→ GC-2
im Phasen-Roadmap).
--- ---

View File

@@ -97,58 +97,57 @@ Java features from `de.varylab.discreteconformal` not yet in C++:
## ◼ Optional / Hypothetical — geometry-central Cross-Comparison ## ◼ Optional / Hypothetical — geometry-central Cross-Comparison
> **Status: keine geplante Phase — rein explorativ.** > **Status: no planned phase — purely exploratory.**
> Diese Punkte sind keine Voraussetzung für Phase 810. Sie sind > These items are not prerequisites for Phase 810. They are
> interessant, weil geometry-central (Keenan Crane, CMU) auf denselben > of interest because geometry-central (Keenan Crane, CMU) is built on the same
> mathematischen Grundlagen wie conformallab++ aufbaut — insbesondere auf > mathematical foundations as conformallab++ — in particular
> **Springborn 2020** und der direkten Weiterentwicklung durch > **Springborn 2020** and its direct extension by
> **Gillespie, Springborn & Crane (SIGGRAPH 2021)**. > **Gillespie, Springborn & Crane (SIGGRAPH 2021)**.
> Der entscheidende Unterschied: geometry-central löst dasselbe Problem > The key difference: geometry-central solves the same problem
> (diskrete konforme Äquivalenz) mit **intrinsischen Triangulierungen + > (discrete conformal equivalence) using **intrinsic triangulations +
> Ptolemäischen Flips**, während conformallab++ **Newton auf der > Ptolemaic flips**, while conformallab++ applies **Newton on the
> Original-Triangulierung** anwendet. > original triangulation**.
``` ```
GC-1 [optional, jetzt möglich] GC-1 [optional, possible now]
Mathematischer Output-Vergleich Mathematical output comparison
gleiche Testnetze (cathead.obj, brezel.obj, torus_4x4.off) in load the same test meshes (cathead.obj, brezel.obj, torus_4x4.off) into
beide Bibliotheken laden both libraries
UV-Koordinaten, u-Vektor, Residualnorm vergleichen compare UV coordinates, u-vector, residual norm
Normalisierungskonventionen abgleichen (u-Mittelwert, Skalierung) align normalisation conventions (u mean, scaling)
Ziel: unabhängige Kreuz-Validierung der Konvergenzpunkte. Goal: independent cross-validation of convergence points.
Aufwand: kleines Python/C++ Vergleichsskript, kein Bibliotheks-Umbau. Effort: small Python/C++ comparison script, no library restructuring.
GC-2 [optional, sinnvoll nach Phase 8] GC-2 [optional, useful after Phase 8]
Intrinsic Delaunay Pre-Conditioning Intrinsic Delaunay pre-conditioning
Vor dem Newton-Solver: geometry-central SignpostIntrinsicTriangulation before the Newton solver: apply geometry-central SignpostIntrinsicTriangulation
auf die Eingabe anwenden to the input
→ Ptolemäische Flips konditionieren die Hessian-Matrix vor → Ptolemaic flips pre-condition the Hessian matrix
Hypothese: weniger Newton-Iterationen auf nicht-Delaunay-Eingaben hypothesis: fewer Newton iterations on non-Delaunay inputs
Implementierbar als optionaler cmake-Flag: -DWITH_GC_PRECOND=ON implementable as an optional cmake flag: -DWITH_GC_PRECOND=ON
Abhängigkeit: geometry-central als optionale externe Abhängigkeit Dependency: geometry-central as an optional external dependency
(header-only Teile genügen für den Flip-Algorithmus). (header-only parts suffice for the flip algorithm).
GC-3 [hypothetisch, Phase 10+ Forschung] GC-3 [hypothetical, Phase 10+ research]
Ptolemäische Flip-basierter Solver als alternativer Backend Ptolemaic flip-based solver as an alternative backend
Statt Newton: Ptolemäische Flips + penultimate-step Normalisierung instead of Newton: Ptolemaic flips + penultimate-step normalisation
(GillespieSpringbornCrane 2021 Algorithmus) (GillespieSpringbornCrane 2021 algorithm)
Vergleich: Konvergenzradius, Robustheit auf pathologischen Netzen, comparison: convergence radius, robustness on pathological meshes,
numerische Stabilität auf hohen Genus-Flächen numerical stability on high-genus surfaces
r conformallab++ interessant, weil der Newton-Ansatz auf relevant for conformallab++ because the Newton approach can become
stark nicht-Delaunay Netzen (z.B. nach Remeshing) instabil unstable on strongly non-Delaunay meshes (e.g. after remeshing).
werden kann. No implementation planned — conceptual note for Phase 10 research.
Keine Implementierung geplant — Konzeptnotiz für Phase 10-Forschung.
``` ```
**Verbindung zur Literatur:** **Connection to the literature:**
Das Springborn 2020-Papier ("Ideal Hyperbolic Polyhedra and Discrete The Springborn 2020 paper ("Ideal Hyperbolic Polyhedra and Discrete
Uniformization") ist in conformallab++ als HyperIdeal-Geometriemodus Uniformization") is already implemented in conformallab++ as the HyperIdeal
bereits implementiert (Phase 2/3). Die GillespieSpringbornCrane geometry mode (Phase 2/3). The GillespieSpringbornCrane
2021-Erweiterung — die geometry-central implementiert — ergänzt dies um 2021 extension — implemented in geometry-central — augments this with
intrinsische Triangulierungen und macht den Algorithmus robust gegen intrinsic triangulations and makes the algorithm robust against
schlechte Eingangs-Triangulierungen. Beide teilen denselben poor input triangulations. Both share the same
mathematischen Kern (diskrete konforme Äquivalenz, GaussBonnet, mathematical core (discrete conformal equivalence, GaussBonnet,
Variationsprinzip von BobenkoSpringborn 2004). variational principle of BobenkoSpringborn 2004).
--- ---