Finding-U1 and Finding-U2 from doc/reviewer/usability-audit-2026-05-31.md.
The existing examples (example_euclidean, example_layout, example_hyper_ideal)
all used the "natural theta" pattern which makes x*=0 trivially the
equilibrium — u_v ≈ 0 everywhere, no deformation. A new user following
these examples saw solver output but not conformal geometry.
New: example_flatten.cpp
- PRIMARY USE CASE: conformally flatten a mesh to the plane
- Sets Θ_v = 2π for all interior vertices (flat target)
- Pins boundary vertices (no Gauss-Bonnet check for open meshes)
- Demonstrates non-trivial u_v (cathead.obj: range ≈ 2.96, 5 Newton iters)
- Documents the difference from "natural theta" explicitly
New: example_cgal_api.cpp
- Demonstrates CGAL::discrete_conformal_map_euclidean (Discrete_conformal_map.h)
- First runnable CGAL public API example; contrast with internal API
- Documents the "natural theta" default behaviour and explains why u_v=0
- Explains when to use CGAL API vs internal API
Both examples registered in code/examples/CMakeLists.txt and compile
cleanly with -DWITH_CGAL=ON.
Updated:
- example_euclidean.cpp: prominent "TESTING CONVENTION" warning
- example_layout.cpp: same warning on set_natural_theta helper
- doc/getting-started.md: example_flatten is now the recommended
"start here" example; note on natural-theta behaviour added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
126 lines
5.4 KiB
C++
126 lines
5.4 KiB
C++
// example_euclidean.cpp
|
||
//
|
||
// conformallab++ — Euclidean discrete conformal map (headless example)
|
||
//
|
||
// This program demonstrates the full library pipeline for the EUCLIDEAN
|
||
// discrete conformal functional:
|
||
//
|
||
// 1. Load a triangle mesh from an OFF file
|
||
// 2. Set up the Euclidean functional maps
|
||
// 3. Pin one vertex (gauge fix for open surfaces)
|
||
// 4. Set target angles via "natural equilibrium" (x* = x_input)
|
||
// 5. Solve with Newton + backtracking line search
|
||
// 6. Print per-vertex conformal factors u_i = x[v_idx[v]]
|
||
// 7. Save the result mesh (same geometry, solver state printed)
|
||
//
|
||
// Build (requires -DWITH_CGAL=ON):
|
||
// cmake -S code -B build -DWITH_CGAL=ON
|
||
// cmake --build build --target example_euclidean
|
||
// ./build/examples/example_euclidean [input.off] [output.off]
|
||
//
|
||
// If no input file is given the built-in make_quad_strip() mesh is used.
|
||
|
||
#include "conformal_mesh.hpp"
|
||
#include "mesh_builder.hpp"
|
||
#include "mesh_io.hpp"
|
||
#include "euclidean_functional.hpp"
|
||
#include "newton_solver.hpp"
|
||
#include <iostream>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
using namespace conformallab;
|
||
|
||
int main(int argc, char* argv[])
|
||
{
|
||
// ── Step 1: obtain mesh ───────────────────────────────────────────────
|
||
ConformalMesh mesh;
|
||
std::string input_path = (argc > 1) ? argv[1] : "";
|
||
std::string output_path = (argc > 2) ? argv[2] : "/tmp/conformallab_euclidean_out.off";
|
||
|
||
if (input_path.empty()) {
|
||
std::cout << "[example_euclidean] No input file given — using make_quad_strip().\n";
|
||
mesh = make_quad_strip();
|
||
} else {
|
||
std::cout << "[example_euclidean] Loading mesh from: " << input_path << "\n";
|
||
try { mesh = load_mesh(input_path); }
|
||
catch (const std::exception& e) {
|
||
std::cerr << "Error loading mesh: " << e.what() << "\n";
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
std::cout << "[example_euclidean] Mesh: "
|
||
<< mesh.number_of_vertices() << " vertices, "
|
||
<< mesh.number_of_faces() << " faces.\n";
|
||
|
||
// ── Step 2: set up functional maps ────────────────────────────────────
|
||
auto maps = setup_euclidean_maps(mesh);
|
||
compute_euclidean_lambda0_from_mesh(mesh, maps);
|
||
|
||
// ── Step 3: pin the first vertex (gauge fix) ──────────────────────────
|
||
auto vit = mesh.vertices().begin();
|
||
Vertex_index v_pinned = *vit++;
|
||
maps.v_idx[v_pinned] = -1; // pinned: u[v_pinned] = 0 (fixed)
|
||
int idx = 0;
|
||
for (; vit != mesh.vertices().end(); ++vit)
|
||
maps.v_idx[*vit] = idx++;
|
||
const int n = idx;
|
||
|
||
std::cout << "[example_euclidean] DOFs: " << n << " (1 vertex pinned).\n";
|
||
|
||
// ── Step 4: natural equilibrium — set theta_v = actual angle sum at x=0 ─
|
||
//
|
||
// ⚠ TESTING CONVENTION — NOT A REAL CONFORMAL MAP
|
||
//
|
||
// "Natural theta" sets Θ_v = actual angle sum at x = 0, so x* = 0 is the
|
||
// equilibrium by construction. The solver converges in 0–1 iterations and
|
||
// u_v ≈ 0 everywhere. This is useful for testing the solver pipeline but
|
||
// produces NO conformal deformation.
|
||
//
|
||
// For a REAL conformal flattening (the primary use case):
|
||
// → see example_flatten.cpp
|
||
// which sets Θ_v = 2π (flat interior target) and produces non-trivial u_v.
|
||
{
|
||
std::vector<double> x0(static_cast<std::size_t>(n), 0.0);
|
||
auto G0 = euclidean_gradient(mesh, x0, maps);
|
||
for (auto v : mesh.vertices()) {
|
||
int iv = maps.v_idx[v];
|
||
if (iv >= 0) maps.theta_v[v] -= G0[static_cast<std::size_t>(iv)];
|
||
}
|
||
}
|
||
|
||
// ── Step 5: solve from a small perturbation to demonstrate Newton ─────
|
||
std::vector<double> x0(static_cast<std::size_t>(n), -0.05);
|
||
std::cout << "[example_euclidean] Solving Newton system…\n";
|
||
auto result = newton_euclidean(mesh, x0, maps, /*tol=*/1e-9, /*max_iter=*/100);
|
||
|
||
// ── Step 6: report ────────────────────────────────────────────────────
|
||
if (result.converged) {
|
||
std::cout << "[example_euclidean] Converged in " << result.iterations
|
||
<< " iterations. ||G||_inf = " << result.grad_inf_norm << "\n";
|
||
} else {
|
||
std::cout << "[example_euclidean] Did NOT converge after " << result.iterations
|
||
<< " iterations. ||G||_inf = " << result.grad_inf_norm << "\n";
|
||
}
|
||
|
||
std::cout << "[example_euclidean] Per-vertex conformal factors u_i:\n";
|
||
for (auto v : mesh.vertices()) {
|
||
int iv = maps.v_idx[v];
|
||
double u = (iv >= 0) ? result.x[static_cast<std::size_t>(iv)] : 0.0;
|
||
std::cout << " v" << v << " u = " << u;
|
||
if (iv < 0) std::cout << " (pinned)";
|
||
std::cout << "\n";
|
||
}
|
||
|
||
// ── Step 7: write output mesh ─────────────────────────────────────────
|
||
try {
|
||
save_mesh(output_path, mesh);
|
||
std::cout << "[example_euclidean] Mesh saved to: " << output_path << "\n";
|
||
} catch (const std::exception& e) {
|
||
std::cerr << "Warning: could not write output: " << e.what() << "\n";
|
||
}
|
||
|
||
return result.converged ? 0 : 1;
|
||
}
|