// 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 #include #include 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 ─ // After this step x* = 0 is the equilibrium (no deformation). // In a real application you would set theta_v = desired angle (e.g. 2π // for flat disks, or the cone angles for a cone metric). { std::vector x0(static_cast(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(iv)]; } } // ── Step 5: solve from a small perturbation to demonstrate Newton ───── std::vector x0(static_cast(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(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; }