Merge pull request #5: Phase 7.5 — language unification + Doxygen + Phase 8 Hybrid MVP strategy
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:
@@ -11,8 +11,8 @@ on:
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Job 1 — test-fast
|
||||
# Pure-math tests (Clausen, ImLi₂, Hyper-ideal Geometrie).
|
||||
# Kein CGAL, kein Boost. Nur Eigen + GTest. Läuft auf ALLEN Branches.
|
||||
# Pure-math tests (Clausen, ImLi₂, Hyper-ideal geometry).
|
||||
# No CGAL, no Boost. Eigen + GTest only. Runs on ALL branches.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
jobs:
|
||||
test-fast:
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
--output-on-failure
|
||||
--output-junit test-results.xml
|
||||
|
||||
- name: Zusammenfassung
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f test-results.xml ]; then
|
||||
@@ -48,14 +48,14 @@ jobs:
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Job 2 — test-cgal
|
||||
# Vollständige CGAL-Test-Suite (Phase 3–7, 158 Tests).
|
||||
# Läuft NUR bei Pull Requests (nicht bei direkten Pushes auf dev/main).
|
||||
# Startet erst nach erfolgreichem test-fast.
|
||||
# Full CGAL test suite (Phase 3–7, 158 tests).
|
||||
# Runs ONLY on pull requests (not on direct pushes to dev/main).
|
||||
# Starts only after test-fast succeeds.
|
||||
#
|
||||
# Verwendet -DWITH_CGAL_TESTS=ON (nicht -DWITH_CGAL=ON), damit kein
|
||||
# Viewer/GLFW gebaut wird — der CI-Container hat kein wayland-scanner.
|
||||
# Uses -DWITH_CGAL_TESTS=ON (not -DWITH_CGAL=ON) to avoid building
|
||||
# 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:
|
||||
needs: test-fast
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
steps:
|
||||
- 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
|
||||
|
||||
- name: Build CGAL-Tests
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
--output-on-failure
|
||||
--output-junit cgal-results.xml
|
||||
|
||||
- name: Zusammenfassung
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f cgal-results.xml ]; then
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -27,3 +27,7 @@ Testing/
|
||||
|
||||
# Claude Code worktrees
|
||||
.claude/
|
||||
|
||||
# Doxygen output
|
||||
doc/doxygen/
|
||||
*.dox.tmp
|
||||
|
||||
33
CLAUDE.md
33
CLAUDE.md
@@ -249,7 +249,38 @@ Expected results: **36 non-CGAL tests pass**, **176 CGAL tests pass, 0 skipped**
|
||||
## Release state
|
||||
|
||||
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 3–5 days
|
||||
3. Phase 9a — Inversive-Distance against new traits 3–5 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:
|
||||
- `CITATION.cff` — machine-readable citation (Sechelmann 2016, Springborn 2020, Bobenko–Springborn 2004)
|
||||
|
||||
126
Doxyfile
Normal file
126
Doxyfile
Normal 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"
|
||||
@@ -34,6 +34,10 @@ ctest --test-dir build -R "^cgal\." --output-on-failure
|
||||
# Full build with CLI + viewer (requires Wayland/X11 dev headers)
|
||||
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
|
||||
|
||||
# API documentation (requires doxygen: brew/apt install doxygen)
|
||||
cmake --build build --target doc
|
||||
open doc/doxygen/html/index.html
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -140,3 +140,25 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../CITATION.cff
|
||||
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()
|
||||
|
||||
@@ -36,7 +36,7 @@ void simple_visualize_mesh(Eigen::MatrixXd& V, Eigen::MatrixXi& F) {
|
||||
viewer.data().set_mesh(V, F);
|
||||
viewer.launch();
|
||||
}
|
||||
// Zero-Copy Map für V (optional)
|
||||
// Zero-copy map for V (optional)
|
||||
template <typename Kernel>
|
||||
Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::RowMajor>>
|
||||
get_vertex_map(CGAL::Surface_mesh<typename Kernel::Point_3>& mesh) {
|
||||
|
||||
@@ -44,10 +44,10 @@ add_executable(conformallab_cgal_tests
|
||||
# period matrix, fundamental domain, tiling
|
||||
test_phase7.cpp
|
||||
|
||||
# ── Java-Parität: Geometrie-Utility-Tests ─────────────────────────────────
|
||||
# Portiert aus CuttinUtilityTest, UnwrapUtilityTest,
|
||||
# ConvergenceUtilityTests, HomologyTest (Tests 1–6).
|
||||
# Test 7 (Genus-2-Homologie) als GTEST_SKIP-Stub bis Phase 8.
|
||||
# ── Java parity: geometry utility tests ──────────────────────────────────
|
||||
# Ported from CuttinUtilityTest, UnwrapUtilityTest,
|
||||
# ConvergenceUtilityTests, HomologyTest (tests 1–6).
|
||||
# Test 7 (genus-2 homology) as GTEST_SKIP stub until Phase 8.
|
||||
test_geometry_utils.cpp
|
||||
|
||||
# ── Scalability smoke tests ────────────────────────────────────────────────
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
// 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_True PORTIERT
|
||||
// UnwrapUtilityTest.java testGetAngleReturnsPI PORTIERT
|
||||
// ConvergenceUtilityTests.java testGetTextureCircumRadius PORTIERT
|
||||
// ConvergenceUtilityTests.java testGetTextureTriangleArea PORTIERT
|
||||
// ConvergenceUtilityTests.java testScaleInvariantCircumCircleRadius PORTIERT
|
||||
// HomologyTest.java testHomology PORTIERT
|
||||
// EuclideanLayoutTest.java testDoLayout PORTIERT
|
||||
// EuclideanCyclicConvergenceTest.java testEuclideanConvergence PORTIERT
|
||||
// SphericalConvergenceTest.java testSphericalConvergence PORTIERT
|
||||
// CuttinUtilityTest.java testIsInConvexTextureFace_False PORTED
|
||||
// CuttinUtilityTest.java testIsInConvexTextureFace_True PORTED
|
||||
// UnwrapUtilityTest.java testGetAngleReturnsPI PORTED
|
||||
// ConvergenceUtilityTests.java testGetTextureCircumRadius PORTED
|
||||
// ConvergenceUtilityTests.java testGetTextureTriangleArea PORTED
|
||||
// ConvergenceUtilityTests.java testScaleInvariantCircumCircleRadius PORTED
|
||||
// HomologyTest.java testHomology PORTED
|
||||
// EuclideanLayoutTest.java testDoLayout PORTED
|
||||
// EuclideanCyclicConvergenceTest.java testEuclideanConvergence PORTED
|
||||
// SphericalConvergenceTest.java testSphericalConvergence PORTED
|
||||
//
|
||||
// ─── Geometrische Grundlage ──────────────────────────────────────────────────────────
|
||||
// ─── Geometric background ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Tests 1–2 Punkt-in-konvexem-Dreieck (2D UV-Raum, baryzentrische Vorzeichen-Methode)
|
||||
// Tests 1–2 Point-in-convex-triangle (2D UV space, barycentric sign method)
|
||||
// Java: CuttingUtility.isInConvexTextureFace(pp, face, adapters)
|
||||
// Hinweis: Java-Test 2 hat ein 5-elementiges T-Array mit w=0 (Punkt im
|
||||
// Unendlichen), was ein Tippfehler im Original ist. Hier werden
|
||||
// äquivalente, wohlgeformte Koordinaten verwendet.
|
||||
// Note: Java test 2 has a 5-element T-array with w=0 (point at
|
||||
// infinity), which is a typo in the original. Equivalent, well-formed
|
||||
// coordinates are used here instead.
|
||||
//
|
||||
// Test 3 Eckenwinkel für kollineare Vertices über den Kosinussatz.
|
||||
// Java: UnwrapUtility.getAngle(edge, adapters) — gibt den Winkel am
|
||||
// Zielknoten zurück. Für v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) ist
|
||||
// der Winkel bei v1 genau π (Dreiecksungleichung entartet).
|
||||
// Test 3 Corner angle for collinear vertices via the law of cosines.
|
||||
// Java: UnwrapUtility.getAngle(edge, adapters) — returns the angle at
|
||||
// the target vertex. For v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) the
|
||||
// angle at v1 is exactly π (degenerate triangle inequality).
|
||||
//
|
||||
// Tests 4–5 2D Umkreisradius und Dreiecksfläche.
|
||||
// Tests 4–5 2D circumradius and triangle area.
|
||||
// Java: ConvergenceUtility.getTextureCircumCircleRadius(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)
|
||||
//
|
||||
// Test 6 Skaleninvarianter Umkreisradius über ein Mesh.
|
||||
// Test 6 Scale-invariant circumradius over a mesh.
|
||||
// Java: ConvergenceUtility.getMaxMeanSumScaleInvariantCircumRadius(hds)
|
||||
// Gibt [max, mean, sum] von R_f / sqrt(total_texture_area) zurück.
|
||||
// Invariant unter uniformer Skalierung der Texturkoordinaten (Test mit
|
||||
// homogenem Gewicht w: Position = (T[0]/w, T[1]/w)).
|
||||
// Returns [max, mean, sum] of R_f / sqrt(total_texture_area).
|
||||
// Invariant under uniform scaling of texture coordinates (tested with
|
||||
// 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)
|
||||
// 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
|
||||
// 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 8–9 Layout-Kanten-Längenerhalt (tetraflat.obj).
|
||||
// Tests 8–9 Layout edge-length preservation (tetraflat.obj).
|
||||
// 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.
|
||||
// Java: EuclideanLayoutTest.testLayout02 (130-Werte-Array für cathead.heml)
|
||||
// C++: Newton ab u=0, prüft Konvergenz + Σα_v ≈ 2π für alle inneren Knoten.
|
||||
// Test 10 Euclidean Newton on cathead.obj — convergence + angle deficit.
|
||||
// Java: EuclideanLayoutTest.testLayout02 (130-value array for cathead.heml)
|
||||
// C++: Newton from u=0, checks convergence + Σα_v ≈ 2π for all interior nodes.
|
||||
//
|
||||
// Test 11 Sphärischer Newton auf Oktaeder — Konvergenz + Winkeldefekt.
|
||||
// Java: SphericalConvergenceTest.testSphericalConvergence (Oktaeder, zufällig
|
||||
// störe Radien, seed=1). C++: konstruierter regulärer Oktaeder, prüft
|
||||
// Konvergenz und dass Σα_v ≈ 2π (Target für Sphäre nach prepareInvariantData).
|
||||
// Test 11 Spherical Newton on octahedron — convergence + angle deficit.
|
||||
// Java: SphericalConvergenceTest.testSphericalConvergence (octahedron, randomly
|
||||
// perturbed radii, seed=1). C++: constructed regular octahedron, checks
|
||||
// convergence and that Σα_v ≈ 2π (target for sphere after prepareInvariantData).
|
||||
//
|
||||
// ─────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -81,12 +81,12 @@
|
||||
using namespace conformallab;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Lokale Geometrie-Hilfsfunktionen
|
||||
// (portiert aus Java CuttingUtility / ConvergenceUtility)
|
||||
// Local geometry helper functions
|
||||
// (ported from Java CuttingUtility / ConvergenceUtility)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Punkt-in-Dreieck Test (2D, baryzentrische Vorzeichenmethode).
|
||||
/// Gibt true zurück wenn p strikt innerhalb oder auf dem Rand von v0-v1-v2 liegt.
|
||||
/// Point-in-triangle test (2D, barycentric sign method).
|
||||
/// Returns true if p lies strictly inside or on the boundary of v0-v1-v2.
|
||||
/// Java: CuttingUtility.isInConvexTextureFace
|
||||
static bool point_in_triangle_2d(
|
||||
Eigen::Vector2d p,
|
||||
@@ -103,7 +103,7 @@ static bool point_in_triangle_2d(
|
||||
return !(has_neg && has_pos);
|
||||
}
|
||||
|
||||
/// 2D Dreiecksfläche (halbes Kreuzprodukt).
|
||||
/// 2D triangle area (half cross product).
|
||||
/// Java: ConvergenceUtility.getTextureTriangleArea
|
||||
static double triangle_area_2d(
|
||||
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;
|
||||
}
|
||||
|
||||
/// 2D Umkreisradius: R = (a·b·c) / (4·Area).
|
||||
/// 2D circumradius: R = (a·b·c) / (4·Area).
|
||||
/// Java: ConvergenceUtility.getTextureCircumCircleRadius
|
||||
static double circumradius_2d(
|
||||
Eigen::Vector2d A, Eigen::Vector2d B, Eigen::Vector2d C)
|
||||
@@ -125,17 +125,17 @@ static double circumradius_2d(
|
||||
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)
|
||||
/// Gibt {max, mean, sum} über alle Flächen zurück.
|
||||
/// Returns {max, mean, sum} over all faces.
|
||||
/// 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(
|
||||
const std::vector<Eigen::Vector2d>& verts,
|
||||
const std::vector<std::array<int, 3>>& faces)
|
||||
{
|
||||
// Gesamtfläche
|
||||
// Total area
|
||||
double total_area = 0.0;
|
||||
for (auto& f : faces)
|
||||
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 1–2 — CuttingUtility: Punkt-in-konvexem-Dreieck (2D UV-Raum)
|
||||
// Tests 1–2 — CuttingUtility: point-in-convex-triangle (2D UV space)
|
||||
// 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)
|
||||
{
|
||||
// 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 v1(0.7487811940754379, 0.06289451051246124);
|
||||
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);
|
||||
|
||||
EXPECT_FALSE(point_in_triangle_2d(pp, v0, v1, v2));
|
||||
}
|
||||
|
||||
// Test 2: Punkt liegt innerhalb
|
||||
// Hinweis: Das originale Java-Array p2 hat 5 Elemente mit w=0 (Tippfehler im
|
||||
// Java-Original). Hier werden äquivalente, wohlgeformte Koordinaten verwendet,
|
||||
// die dasselbe geometrische Szenario abbilden.
|
||||
// Test 2: point lies inside
|
||||
// Note: the original Java array p2 has 5 elements with w=0 (typo in the
|
||||
// Java original). Equivalent, well-formed coordinates are used here
|
||||
// that represent the same geometric scenario.
|
||||
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 v1(1e-8, 0.0);
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
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_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: v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) kollinear.
|
||||
// Kante e von v2 nach v1. getAngle(e) = Winkel bei v1 = π.
|
||||
// Java: v0=(-1,0,0), v1=(0,0,0), v2=(1,0,0) collinear.
|
||||
// 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 → γ = π
|
||||
TEST(UnwrapUtility, GetAngle_CollinearVertices_ReturnsPI)
|
||||
{
|
||||
const double a = 1.0; // |v0 − v1|
|
||||
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);
|
||||
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);
|
||||
EXPECT_NEAR(M_PI, angle, 1e-15);
|
||||
}
|
||||
|
||||
// Gegenkontrolle: gleichseitiges Dreieck → Winkel = π/3
|
||||
// Counter-check: equilateral triangle → angle = π/3
|
||||
TEST(UnwrapUtility, GetAngle_EquilateralTriangle_ReturnsPiOver3)
|
||||
{
|
||||
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
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
TEST(ConvergenceUtility, TextureCircumRadius_RightTriangle)
|
||||
{
|
||||
// A=(0,0), B=(1,0), C=(0,1): rechtwinkliges gleichschenkliges Dreieck
|
||||
// Seiten: 1, 1, √2. R = √2 / (4 · 0.5) = √2/2
|
||||
// A=(0,0), B=(1,0), C=(0,1): right isosceles triangle
|
||||
// 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);
|
||||
EXPECT_NEAR(std::sqrt(2.0) / 2.0, circumradius_2d(A, B, C), 1e-10);
|
||||
}
|
||||
|
||||
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}
|
||||
// Seiten: √0.5, √0.5, 1. Area = 0.25. R = (√0.5·√0.5·1)/(4·0.25) = 0.5
|
||||
// A=(0,0), B=(0.5,0.5), C=(0,1): Java variant with B.T={0.5,0.5,0,1}
|
||||
// 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);
|
||||
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
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
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);
|
||||
EXPECT_NEAR(0.5, triangle_area_2d(A, B, C), 1e-10);
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
//
|
||||
// Mesh: 4 Vertices (v1..v4), 2 Flächen (f1: v1-v2-v3, f2: v1-v3-v4).
|
||||
// Skaleninvariante Größe: R_f / sqrt(total_area) — invariant unter
|
||||
// uniformer Skalierung (homogeneous weight w: pos = (x/w, y/w)).
|
||||
// Mesh: 4 vertices (v1..v4), 2 faces (f1: v1-v2-v3, f2: v1-v3-v4).
|
||||
// Scale-invariant quantity: R_f / sqrt(total_area) — invariant under
|
||||
// uniform scaling (homogeneous weight w: pos = (x/w, y/w)).
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
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 = {
|
||||
{0.0, 0.0}, // v1
|
||||
{1.0, 0.0}, // v2
|
||||
@@ -287,13 +287,13 @@ TEST(ConvergenceUtility, ScaleInvariantCircumRadius_BaseScale)
|
||||
// f1: v1-v2-v3, f2: v1-v3-v4
|
||||
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[2], verts[3]), 1e-10);
|
||||
|
||||
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), mean_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)
|
||||
{
|
||||
// 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)
|
||||
std::vector<Eigen::Vector2d> verts = {
|
||||
{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} };
|
||||
|
||||
// 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[2], verts[3]), 1e-10);
|
||||
|
||||
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), mean_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-Test:
|
||||
// CoHDS hds = TestUtility.readOBJ("brezel2.obj"); // Genus-2-Brezel-Fläche
|
||||
// Java test:
|
||||
// CoHDS hds = TestUtility.readOBJ("brezel2.obj"); // genus-2 pretzel surface
|
||||
// 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");
|
||||
// CutGraph cg = compute_cut_graph(mesh);
|
||||
// EXPECT_EQ(4u, cg.cut_edge_indices.size()); // 2g = 4
|
||||
// EXPECT_EQ(2, cg.genus);
|
||||
//
|
||||
// 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)
|
||||
@@ -359,17 +359,17 @@ TEST(HomologyGenerators, Genus2_FourCutEdges)
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// Tests 8–9 — EuclideanLayoutTest: Kantenlängenerhalt auf tetraflat.obj
|
||||
// Tests 8–9 — EuclideanLayoutTest: edge-length preservation on tetraflat.obj
|
||||
// Java: EuclideanLayoutTest.testDoLayout
|
||||
//
|
||||
// Java-Test:
|
||||
// Vector u = new SparseVector(n); // u = 0 (kein konformer Faktor)
|
||||
// Java test:
|
||||
// Vector u = new SparseVector(n); // u = 0 (no conformal factor)
|
||||
// EuclideanLayout.doLayout(hds, fun, u);
|
||||
// for (CoEdge e : hds.getEdges())
|
||||
// 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).
|
||||
// Das Layout muss die ursprünglichen 3D-Kantenlängen exakt reproduzieren.
|
||||
// Meaning: with u=0 the conformal factor is 0, so ℓ̃ = ℓ (no deformation).
|
||||
// The layout must reproduce the original 3D edge lengths exactly.
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
TEST(EuclideanLayout, DoLayout_TetraFlat_EdgeLengthsPreserved)
|
||||
@@ -414,18 +414,18 @@ TEST(EuclideanLayout, DoLayout_TetraFlat_EdgeLengthsPreserved)
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// Test 10 — EuclideanCyclicConvergenceTest: Newton auf cathead.obj
|
||||
// Java: EuclideanLayoutTest.testLayout02 (130-Werte-Regression auf cathead.heml)
|
||||
// Test 10 — EuclideanCyclicConvergenceTest: Newton on cathead.obj
|
||||
// Java: EuclideanLayoutTest.testLayout02 (130-value regression on cathead.heml)
|
||||
// EuclideanCyclicConvergenceTest.testEuclideanConvergence
|
||||
//
|
||||
// Java-Test:
|
||||
// Java test:
|
||||
// EuclideanLayout.doLayout(hdsCat, fun, uCat);
|
||||
// for (CoVertex v : interior vertices)
|
||||
// assertEquals(2*PI, calculateAngleSum(v), 1E-6);
|
||||
// for (CoEdge e : positiveEdges)
|
||||
// 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
|
||||
// depends on vertex ordering in the Java CoHDS — not portable directly.
|
||||
// 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-Test:
|
||||
// Java test:
|
||||
// FunctionalTest.createOctahedron(hds, aSet);
|
||||
// // randomly perturb vertex radii (seed=1)
|
||||
// prepareInvariantDataHyperbolicAndSpherical(functional, hds, aSet, u);
|
||||
// optimizer.minimize(u, opt);
|
||||
// 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,
|
||||
// prüft Konvergenz + Restgradienten (≡ Winkeldefekt = 0 nach Konvergenz).
|
||||
// C++: regular octahedron (all vertices on S², no perturbation), spherical Newton,
|
||||
// checks convergence + residual gradients (≡ angle deficit = 0 after convergence).
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
TEST(SphericalLayout, SphericalTetrahedron_NewtonConverges_AngleSumsTwoPi)
|
||||
|
||||
@@ -1,77 +1,211 @@
|
||||
# Phase 8 — CGAL Package Design
|
||||
|
||||
> **Status: planned.** This document describes the target architecture for Phase 8.
|
||||
> No code has been written yet. The design is informed by the CGAL package submission
|
||||
> guidelines at https://www.cgal.org/developers.html
|
||||
> **Status: design frozen, implementation planned.**
|
||||
> This document captures the strategic decisions taken before the first
|
||||
> 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:
|
||||
`Discrete_conformal_map`. The package must satisfy all CGAL submission requirements:
|
||||
traits-class design, Doxygen documentation, CGAL-format test suite, and coverage of
|
||||
the CGAL coding conventions.
|
||||
| Question | Decision | Rationale |
|
||||
|---|---|---|
|
||||
| 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. |
|
||||
| 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
|
||||
|
||||
The current code is tightly coupled to `CGAL::Surface_mesh<Point3>`. Phase 8a introduces
|
||||
a traits class that separates the mesh type from the algorithm:
|
||||
### `ConformalMapTraits` concept
|
||||
|
||||
The concept lists the types and operations every Traits model must provide.
|
||||
|
||||
```cpp
|
||||
// TODO(Phase 8a): implement this header
|
||||
// include/CGAL/Conformal_map_traits.h
|
||||
namespace CGAL {
|
||||
|
||||
template<
|
||||
typename MeshType, // any CGAL halfedge mesh
|
||||
typename KernelType, // CGAL kernel
|
||||
typename ScalarType = double
|
||||
>
|
||||
struct Conformal_map_traits {
|
||||
using Mesh = MeshType;
|
||||
using Kernel = KernelType;
|
||||
using FT = ScalarType;
|
||||
// ... vertex/edge/face descriptor types
|
||||
// ... property map access
|
||||
// Concept (documentation only; no code):
|
||||
struct ConformalMapTraits {
|
||||
// Types
|
||||
using Triangle_mesh = ...; // model of FaceGraph + HalfedgeGraph
|
||||
using FT = ...; // typically double
|
||||
using Vertex_descriptor = boost::graph_traits<Triangle_mesh>::vertex_descriptor;
|
||||
using Halfedge_descriptor = ...;
|
||||
using Face_descriptor = ...;
|
||||
|
||||
// Read access (input geometry)
|
||||
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/
|
||||
Discrete_conformal_map.h ← single user-facing include
|
||||
Conformal_map_traits.h
|
||||
Conformal_newton_solver.h
|
||||
Conformal_layout.h
|
||||
Conformal_cut_graph.h
|
||||
conformal_map_package.h ← PackageDescription
|
||||
├── Discrete_conformal_map.h ← user-facing entry (1 include for casual use)
|
||||
├── Conformal_map_traits.h ← concept + Default_conformal_map_traits
|
||||
├── Conformal_map_concept_checks.h
|
||||
├── Conformal_newton_solver.h ← standalone Newton (advanced users)
|
||||
├── Conformal_layout.h ← layout + holonomy
|
||||
├── 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
|
||||
|
||||
```
|
||||
doc/Conformal_map/
|
||||
PackageDescription.txt
|
||||
User_manual.md
|
||||
Reference_manual.md
|
||||
fig/ ← pipeline diagrams, mathematical figures
|
||||
├── PackageDescription.txt ← CGAL Doxygen package file
|
||||
├── Conformal_map.txt ← Doxygen User_manual
|
||||
├── examples.txt ← linkable example code
|
||||
├── dependencies ← textual 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/
|
||||
CMakeLists.txt ← CGAL-format, uses find_package(CGAL)
|
||||
test_euclidean_functional.cpp
|
||||
test_newton_solver.cpp
|
||||
...
|
||||
├── CMakeLists.txt ← CGAL-format, uses find_package(CGAL)
|
||||
├── test_euclidean_traits.cpp ← traits concept checks
|
||||
├── 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
|
||||
target, following the CGAL test infrastructure conventions.
|
||||
The existing GTest suite at `code/tests/cgal/` remains; CGAL-format tests are
|
||||
added alongside as a separate target. CI runs both.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
before execution.
|
||||
|
||||
**Full concept & design specification:** [doc/concepts/declarative-pipeline.md](../concepts/declarative-pipeline.md)
|
||||
— token vocabulary, validation algorithm, 5 complete examples, implementation plan.
|
||||
|
||||
Abbreviated example:
|
||||
**Full spec:** [doc/concepts/declarative-pipeline.md](../concepts/declarative-pipeline.md)
|
||||
— token vocabulary, validation algorithm, 5 complete examples.
|
||||
|
||||
```yaml
|
||||
pipeline:
|
||||
name: flat_torus_period
|
||||
geometry: euclidean
|
||||
|
||||
input:
|
||||
source: data/torus.off
|
||||
|
||||
input: { source: data/torus.off }
|
||||
steps:
|
||||
- id: setup
|
||||
unit: setup_euclidean_maps
|
||||
provide: [maps_initialised]
|
||||
|
||||
- id: gauss_bonnet
|
||||
unit: enforce_gauss_bonnet
|
||||
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]
|
||||
|
||||
- { id: setup, unit: setup_euclidean_maps, provide: [maps_initialised] }
|
||||
- { id: gb, unit: enforce_gauss_bonnet, require: [maps_initialised], provide: [gauss_bonnet_satisfied] }
|
||||
- { 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: layout, unit: euclidean_layout, require: [x_converged, cut_graph], provide: [layout_uv, holonomy] }
|
||||
- { id: period, unit: compute_period_matrix, require: [holonomy], provide: [tau] }
|
||||
output:
|
||||
layout: out/torus_layout.off
|
||||
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)
|
||||
- [ ] Define concept requirements for `MeshType` (8a)
|
||||
- [ ] Create `include/CGAL/` header skeleton (8b)
|
||||
- [ ] Write `PackageDescription.txt` (8c)
|
||||
- [ ] Port GTest tests to CGAL format (8d)
|
||||
- [ ] Implement YAML validator (8e)
|
||||
- [ ] CLI: `--pipeline` flag (8e)
|
||||
Before any Phase 8 code, the existing API surface must be extractable.
|
||||
This is the prerequisite that bridges Phase 7 → Phase 8.
|
||||
|
||||
```
|
||||
Phase 7.5 — Doxygen infrastructure
|
||||
──────────────────────────────────
|
||||
• 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 (3–5 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 (3–5 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 (3–4 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, 6–8 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)
|
||||
|
||||
@@ -28,22 +28,22 @@ Java reference implementation: [github.com/varylab/conformallab](https://github.
|
||||
|
||||
## geometry-central cross-reference *(optional comparison track)*
|
||||
|
||||
> Diese Referenzen beziehen sich auf eine alternative Implementierung desselben
|
||||
> mathematischen Problems. Sie sind keine Voraussetzung für conformallab++,
|
||||
> aber relevant für Kreuz-Validierung und mögliche algorithmische Adoptionen
|
||||
> (→ GC-1/2/3 im Phasen-Roadmap, → Abschnitt 9 in `validation.md`).
|
||||
> These references relate to an alternative implementation of the same
|
||||
> mathematical problem. They are not prerequisites for conformallab++,
|
||||
> but are relevant for cross-validation and possible algorithmic adoptions
|
||||
> (→ 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. |
|
||||
| **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). |
|
||||
| **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 | Algorithmic basis for `SignpostIntrinsicTriangulation` in geometry-central — relevant for GC-2 (optional pre-conditioning). |
|
||||
|
||||
**Hinweis zu Springborn 2020:**
|
||||
Das Papier *"Ideal Hyperbolic Polyhedra and Discrete Uniformization"*
|
||||
(Springborn, Discrete & Computational Geometry 2020) ist **in conformallab++
|
||||
bereits implementiert** — es ist die direkte Referenz für den HyperIdeal-Geometriemodus
|
||||
(`hyper_ideal_geometry.hpp`). Die geometry-central Implementierung (Gillespie 2021)
|
||||
baut auf diesem Papier auf und ergänzt es um Ptolemäische Flips.
|
||||
**Note on Springborn 2020:**
|
||||
The paper *"Ideal Hyperbolic Polyhedra and Discrete Uniformization"*
|
||||
(Springborn, Discrete & Computational Geometry 2020) is **already implemented in
|
||||
conformallab++** — it is the direct reference for the HyperIdeal geometry mode
|
||||
(`hyper_ideal_geometry.hpp`). The geometry-central implementation (Gillespie 2021)
|
||||
builds on this paper and augments it with Ptolemaic flips.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -193,66 +193,58 @@ These are the **holonomy consistency** checks implemented in `test_phase7.cpp`
|
||||
|
||||
## 9 — Cross-validation with geometry-central *(optional / hypothetical)*
|
||||
|
||||
> **Hinweis:** Dieser Abschnitt beschreibt eine mögliche externe Kreuz-Validierung,
|
||||
> die keine Voraussetzung für die Korrektheit der Implementierung ist.
|
||||
> Sie ist interessant, weil geometry-central denselben mathematischen Kern
|
||||
> implementiert (Gillespie, Springborn, Crane — SIGGRAPH 2021, aufbauend auf
|
||||
> Springborn 2020), aber mit einer anderen algorithmischen Strategie
|
||||
> (Ptolemäische Flips + intrinsische Triangulierungen statt Newton auf der
|
||||
> Original-Triangulierung).
|
||||
> **Note:** This section describes a possible external cross-validation that is not
|
||||
> a prerequisite for the correctness of the implementation.
|
||||
> It is of interest because geometry-central implements the same mathematical core
|
||||
> (Gillespie, Springborn, Crane — SIGGRAPH 2021, building on
|
||||
> Springborn 2020), but with a different algorithmic strategy
|
||||
> (Ptolemaic flips + intrinsic triangulations instead of Newton on the
|
||||
> 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 |
|
||||
| UV-Koordinaten | `layout.uv[v]` | konforme Parametrisierung | ✓ bis auf Möbius-Transformation |
|
||||
| Gauss-Bonnet Defekt | `gauss_bonnet_sum()` | implizit via Krümmungsfluss | ✓ (analytisch identisch) |
|
||||
| Anzahl Newton-Iterationen | `res.iterations` | Yamabe-Schritte | ~ (anderer Algorithmus) |
|
||||
| Period-Matrix τ | `pd.tau_reduced` | **nicht vorhanden** | ✗ |
|
||||
| Möbius-Holonomie | `hol.T_a, T_b` | **nicht vorhanden** | ✗ |
|
||||
| u-vector (scale parameters) | `res.x` | `u` after Yamabe flow | ✓ after normalisation |
|
||||
| UV coordinates | `layout.uv[v]` | conformal parameterisation | ✓ up to Möbius transformation |
|
||||
| Gauss-Bonnet deficit | `gauss_bonnet_sum()` | implicit via curvature flow | ✓ (analytically identical) |
|
||||
| Number of Newton iterations | `res.iterations` | Yamabe steps | ~ (different algorithm) |
|
||||
| Period matrix τ | `pd.tau_reduced` | **not available** | ✗ |
|
||||
| Möbius holonomy | `hol.T_a, T_b` | **not available** | ✗ |
|
||||
|
||||
### Normalisierungsabgleich
|
||||
### Normalisation alignment
|
||||
|
||||
Der u-Vektor in conformallab++ hat einen Freiheitsgrad (globale additive Konstante —
|
||||
Eichfreiheit nach Pin-Fixierung). geometry-central kann eine andere Konvention nutzen.
|
||||
Vor dem Vergleich normalisieren:
|
||||
The u-vector in conformallab++ has one degree of freedom (global additive constant —
|
||||
gauge freedom after pin-fixing). geometry-central may use a different convention.
|
||||
Normalise before comparing:
|
||||
|
||||
```cpp
|
||||
// conformallab++: u zentrieren
|
||||
// conformallab++: centre u
|
||||
double mean_u = std::accumulate(x.begin(), x.end(), 0.0) / x.size();
|
||||
std::vector<double> x_norm(x.size());
|
||||
for (int i = 0; i < x.size(); ++i) x_norm[i] = x[i] - mean_u;
|
||||
|
||||
// Dann mit geometry-central u-Vektor (ebenfalls zentriert) vergleichen:
|
||||
// max|x_norm[i] - gc_u[i]| < 1e-8 → identischer Konvergenzpunkt
|
||||
// Then compare with the geometry-central u-vector (also centred):
|
||||
// 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 |
|
||||
| **Nach Phase 8** | Automatisiertes Vergleichsskript (Python oder separates C++-Binary) |
|
||||
| **Phase 10 (Forschung)** | Algorithmus-Vergleich: Newton vs. Ptolemäische Flips auf schwierigen Netzen |
|
||||
| **Now (Phase 7)** | Manual comparison using the same `.off`/`.obj` test meshes |
|
||||
| **After Phase 8** | Automated comparison script (Python or separate C++ binary) |
|
||||
| **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)
|
||||
2. Gleiche Gauss-Bonnet-Zielkrümmungen (Θᵥ = 2π für alle v, geschlossene Fläche)
|
||||
3. u-Normalisierung abgeglichen (zentriert, gleiche Eichfixierung)
|
||||
4. Konvergenztoleranz synchronisiert (max. Gradientnorm < 1e-8)
|
||||
|
||||
### Verbindung zur Literatur
|
||||
|
||||
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 Bobenko–Springborn 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).
|
||||
The Springborn 2020 paper ("Ideal Hyperbolic Polyhedra and Discrete Uniformization")
|
||||
is **already implemented in conformallab++** — it is the mathematical foundation
|
||||
for the HyperIdeal geometry mode (Phase 2/3). The geometry-central implementation
|
||||
is based on the extension by Gillespie, Springborn & Crane (2021), which uses the
|
||||
same variational principle of Bobenko–Springborn 2004 but additionally applies
|
||||
Ptolemaic flips to improve the triangulation during optimisation — an idea not yet
|
||||
implemented in conformallab++ (→ GC-2 in the phase roadmap).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -97,58 +97,57 @@ Java features from `de.varylab.discreteconformal` not yet in C++:
|
||||
|
||||
## ◼ Optional / Hypothetical — geometry-central Cross-Comparison
|
||||
|
||||
> **Status: keine geplante Phase — rein explorativ.**
|
||||
> Diese Punkte sind keine Voraussetzung für Phase 8–10. Sie sind
|
||||
> interessant, weil geometry-central (Keenan Crane, CMU) auf denselben
|
||||
> mathematischen Grundlagen wie conformallab++ aufbaut — insbesondere auf
|
||||
> **Springborn 2020** und der direkten Weiterentwicklung durch
|
||||
> **Status: no planned phase — purely exploratory.**
|
||||
> These items are not prerequisites for Phase 8–10. They are
|
||||
> of interest because geometry-central (Keenan Crane, CMU) is built on the same
|
||||
> mathematical foundations as conformallab++ — in particular
|
||||
> **Springborn 2020** and its direct extension by
|
||||
> **Gillespie, Springborn & Crane (SIGGRAPH 2021)**.
|
||||
> Der entscheidende Unterschied: geometry-central löst dasselbe Problem
|
||||
> (diskrete konforme Äquivalenz) mit **intrinsischen Triangulierungen +
|
||||
> Ptolemäischen Flips**, während conformallab++ **Newton auf der
|
||||
> Original-Triangulierung** anwendet.
|
||||
> The key difference: geometry-central solves the same problem
|
||||
> (discrete conformal equivalence) using **intrinsic triangulations +
|
||||
> Ptolemaic flips**, while conformallab++ applies **Newton on the
|
||||
> original triangulation**.
|
||||
|
||||
```
|
||||
GC-1 [optional, jetzt möglich]
|
||||
Mathematischer Output-Vergleich
|
||||
→ gleiche Testnetze (cathead.obj, brezel.obj, torus_4x4.off) in
|
||||
beide Bibliotheken laden
|
||||
→ UV-Koordinaten, u-Vektor, Residualnorm vergleichen
|
||||
→ Normalisierungskonventionen abgleichen (u-Mittelwert, Skalierung)
|
||||
Ziel: unabhängige Kreuz-Validierung der Konvergenzpunkte.
|
||||
Aufwand: kleines Python/C++ Vergleichsskript, kein Bibliotheks-Umbau.
|
||||
GC-1 [optional, possible now]
|
||||
Mathematical output comparison
|
||||
→ load the same test meshes (cathead.obj, brezel.obj, torus_4x4.off) into
|
||||
both libraries
|
||||
→ compare UV coordinates, u-vector, residual norm
|
||||
→ align normalisation conventions (u mean, scaling)
|
||||
Goal: independent cross-validation of convergence points.
|
||||
Effort: small Python/C++ comparison script, no library restructuring.
|
||||
|
||||
GC-2 [optional, sinnvoll nach Phase 8]
|
||||
Intrinsic Delaunay Pre-Conditioning
|
||||
→ Vor dem Newton-Solver: geometry-central SignpostIntrinsicTriangulation
|
||||
auf die Eingabe anwenden
|
||||
→ Ptolemäische Flips konditionieren die Hessian-Matrix vor
|
||||
→ Hypothese: weniger Newton-Iterationen auf nicht-Delaunay-Eingaben
|
||||
→ Implementierbar als optionaler cmake-Flag: -DWITH_GC_PRECOND=ON
|
||||
Abhängigkeit: geometry-central als optionale externe Abhängigkeit
|
||||
(header-only Teile genügen für den Flip-Algorithmus).
|
||||
GC-2 [optional, useful after Phase 8]
|
||||
Intrinsic Delaunay pre-conditioning
|
||||
→ before the Newton solver: apply geometry-central SignpostIntrinsicTriangulation
|
||||
to the input
|
||||
→ Ptolemaic flips pre-condition the Hessian matrix
|
||||
→ hypothesis: fewer Newton iterations on non-Delaunay inputs
|
||||
→ implementable as an optional cmake flag: -DWITH_GC_PRECOND=ON
|
||||
Dependency: geometry-central as an optional external dependency
|
||||
(header-only parts suffice for the flip algorithm).
|
||||
|
||||
GC-3 [hypothetisch, Phase 10+ Forschung]
|
||||
Ptolemäische Flip-basierter Solver als alternativer Backend
|
||||
→ Statt Newton: Ptolemäische Flips + penultimate-step Normalisierung
|
||||
(Gillespie–Springborn–Crane 2021 Algorithmus)
|
||||
→ Vergleich: Konvergenzradius, Robustheit auf pathologischen Netzen,
|
||||
numerische Stabilität auf hohen Genus-Flächen
|
||||
→ Für conformallab++ interessant, weil der Newton-Ansatz auf
|
||||
stark nicht-Delaunay Netzen (z.B. nach Remeshing) instabil
|
||||
werden kann.
|
||||
Keine Implementierung geplant — Konzeptnotiz für Phase 10-Forschung.
|
||||
GC-3 [hypothetical, Phase 10+ research]
|
||||
Ptolemaic flip-based solver as an alternative backend
|
||||
→ instead of Newton: Ptolemaic flips + penultimate-step normalisation
|
||||
(Gillespie–Springborn–Crane 2021 algorithm)
|
||||
→ comparison: convergence radius, robustness on pathological meshes,
|
||||
numerical stability on high-genus surfaces
|
||||
→ relevant for conformallab++ because the Newton approach can become
|
||||
unstable on strongly non-Delaunay meshes (e.g. after remeshing).
|
||||
No implementation planned — conceptual note for Phase 10 research.
|
||||
```
|
||||
|
||||
**Verbindung zur Literatur:**
|
||||
Das Springborn 2020-Papier ("Ideal Hyperbolic Polyhedra and Discrete
|
||||
Uniformization") ist in conformallab++ als HyperIdeal-Geometriemodus
|
||||
bereits implementiert (Phase 2/3). Die Gillespie–Springborn–Crane
|
||||
2021-Erweiterung — die geometry-central implementiert — ergänzt dies um
|
||||
intrinsische Triangulierungen und macht den Algorithmus robust gegen
|
||||
schlechte Eingangs-Triangulierungen. Beide teilen denselben
|
||||
mathematischen Kern (diskrete konforme Äquivalenz, Gauss–Bonnet,
|
||||
Variationsprinzip von Bobenko–Springborn 2004).
|
||||
**Connection to the literature:**
|
||||
The Springborn 2020 paper ("Ideal Hyperbolic Polyhedra and Discrete
|
||||
Uniformization") is already implemented in conformallab++ as the HyperIdeal
|
||||
geometry mode (Phase 2/3). The Gillespie–Springborn–Crane
|
||||
2021 extension — implemented in geometry-central — augments this with
|
||||
intrinsic triangulations and makes the algorithm robust against
|
||||
poor input triangulations. Both share the same
|
||||
mathematical core (discrete conformal equivalence, Gauss–Bonnet,
|
||||
variational principle of Bobenko–Springborn 2004).
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user