// example_cgal_api.cpp // // conformallab++ — CGAL public API example // // This example demonstrates the HIGH-LEVEL CGAL API defined in // . It is the recommended entry point for // users who want a simple one-call interface without managing Maps bundles, // DOF assignment, or Newton solver details. // // Contrast with example_euclidean.cpp / example_flatten.cpp which use the // INTERNAL API (setup_euclidean_maps + newton_euclidean + euclidean_layout). // // ┌─────────────────────────────────────────────────────────────────────┐ // │ When to use the CGAL API vs the internal API │ // │ │ // │ CGAL API (this file): │ // │ • One call, sensible defaults │ // │ • Named parameters for tuning (tolerance, cone angles, …) │ // │ • Result type: Conformal_map_result (u_per_vertex indexed │ // │ by raw vertex index) │ // │ • Layout via CGAL::euclidean_layout (Conformal_layout.h) │ // │ │ // │ Internal API (example_flatten.cpp, example_layout.cpp): │ // │ • Full control over every pipeline step │ // │ • Access to holonomy, period matrix, cut graph │ // │ • Result type: NewtonResult (x indexed by DOF index) │ // │ • Required for closed surfaces (genus ≥ 1) │ // └─────────────────────────────────────────────────────────────────────┘ // // Build (requires -DWITH_CGAL=ON): // cmake -S code -B build -DWITH_CGAL=ON // cmake --build build --target example_cgal_api // // Run: // ./build/examples/example_cgal_api # built-in mesh // ./build/examples/example_cgal_api code/data/obj/cathead.obj flat.off #include #include #include // CGAL public API #include // CGAL layout wrapper // For loading meshes and saving results we still use the internal helpers. #include "mesh_io.hpp" #include "mesh_builder.hpp" #include "layout.hpp" #include #include #include #include int main(int argc, char* argv[]) { using Kernel = CGAL::Simple_cartesian; using Mesh = CGAL::Surface_mesh; // ── Step 1: obtain mesh ─────────────────────────────────────────────────── Mesh mesh; std::string input_path = (argc > 1) ? argv[1] : ""; std::string output_path = (argc > 2) ? argv[2] : "/tmp/conformallab_cgal_api_out.off"; if (input_path.empty()) { std::cout << "[example_cgal_api] No input given. Using make_quad_strip().\n" << " For a non-trivial result, supply a 3-D mesh:\n" << " ./example_cgal_api code/data/obj/cathead.obj\n\n"; mesh = conformallab::make_quad_strip(); } else { std::cout << "[example_cgal_api] Loading mesh: " << input_path << "\n"; try { mesh = conformallab::load_mesh(input_path); } catch (const std::exception& e) { std::cerr << "Error loading mesh: " << e.what() << "\n"; return 1; } } std::cout << "[example_cgal_api] Mesh: " << mesh.number_of_vertices() << " vertices, " << mesh.number_of_faces() << " faces.\n"; // ── Step 2: CGAL API call ───────────────────────────────────────────────── // // Default invocation — one function call, no explicit target angles: // • No vertex_curvature_map supplied → "natural theta" default: // Θ_v is set to the ACTUAL angle sum at x = 0. This makes x* = 0 // the equilibrium, so Newton converges in 0 iterations with u_v = 0. // // ⚠ Natural theta is a TESTING CONVENTION, not a conformal flattening. // The output u_v = 0 means "no deformation" — the map is identity. // For real conformal flattening use: // • example_flatten.cpp (internal API, recommended for open meshes) // • Supply vertex_curvature_map(theta_map) with Θ_v = 2π for a // closed mesh (must satisfy Gauss-Bonnet; see below) // // The function sets up maps, computes λ°, assigns DOFs, runs Newton, // and returns the converged result. auto result = CGAL::discrete_conformal_map_euclidean(mesh); std::cout << "[example_cgal_api] CGAL API result (natural theta — identity map):\n" << " converged = " << std::boolalpha << result.converged << "\n" << " iterations = " << result.iterations << " (0 = natural theta, trivially at equilibrium)\n" << " |grad|_inf = " << std::scientific << std::setprecision(2) << result.gradient_norm << "\n"; // ── Step 3: inspect the result ──────────────────────────────────────────── // // result.u_per_vertex is indexed by raw vertex index (v.idx()), NOT by // DOF index. Length = num_vertices(mesh). Pinned vertices have u = 0. // // This differs from the internal API (NewtonResult.x) which is indexed // by DOF index (v_idx[v]). The CGAL API handles the mapping internally. double u_min = 0.0, u_max = 0.0; for (auto v : mesh.vertices()) { double u = result.u_per_vertex[static_cast(v.idx())]; u_min = std::min(u_min, u); u_max = std::max(u_max, u); } std::cout << " u_v range = [" << std::fixed << std::setprecision(4) << u_min << ", " << u_max << "]\n\n"; // Print a few per-vertex values std::cout << "[example_cgal_api] First 6 per-vertex scale factors u_v:\n"; int shown = 0; for (auto v : mesh.vertices()) { if (shown++ >= 6) break; double u = result.u_per_vertex[static_cast(v.idx())]; std::cout << " v" << v.idx() << " u = " << std::fixed << std::setprecision(6) << u << "\n"; } // ── Step 4: tuning via named parameters ─────────────────────────────────── // // The CGAL API accepts named parameters for fine-grained control. // Example: tighter tolerance and more iterations. // // auto result2 = CGAL::discrete_conformal_map_euclidean( // mesh, // CGAL::parameters::gradient_tolerance(1e-12) // .max_iterations(500)); // // Example: supply explicit cone angles (requires Gauss-Bonnet to hold): // // auto angle_map = mesh.add_property_map( // "my:angles", 2.0 * M_PI).first; // // … set angle_map[v] for cone vertices … // auto result3 = CGAL::discrete_conformal_map_euclidean( // mesh, // CGAL::parameters::vertex_curvature_map(angle_map)); // // See for all named parameters. // ── Step 5: compute layout via the CGAL layout wrapper ──────────────────── // // The CGAL API does not return a layout directly; call CGAL::euclidean_layout // (Conformal_layout.h) with the converged DOF vector and the internal maps. // Note: to access the DOF vector and maps from a CGAL-API call, use the // result.x field (if exposed) or call the internal API directly. // // For Phase 8b-Lite, the cleanest way to get a layout after the CGAL call // is to use the internal API (example_flatten.cpp) — the CGAL result carries // u_per_vertex for inspection but the full pipeline (layout, holonomy, period // matrix) still requires the internal Maps bundle. if (result.converged) std::cout << "\n[example_cgal_api] ✓ Conformal map computed successfully.\n" << " For UV layout output, see example_flatten.cpp (internal API)\n" << " which provides direct access to euclidean_layout().\n"; return result.converged ? 0 : 1; }