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>
172 lines
8.7 KiB
C++
172 lines
8.7 KiB
C++
// example_cgal_api.cpp
|
|
//
|
|
// conformallab++ — CGAL public API example
|
|
//
|
|
// This example demonstrates the HIGH-LEVEL CGAL API defined in
|
|
// <CGAL/Discrete_conformal_map.h>. 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<FT> (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 <CGAL/Simple_cartesian.h>
|
|
#include <CGAL/Surface_mesh.h>
|
|
#include <CGAL/Discrete_conformal_map.h> // CGAL public API
|
|
#include <CGAL/Conformal_layout.h> // 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 <iostream>
|
|
#include <iomanip>
|
|
#include <string>
|
|
#include <algorithm>
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
using Kernel = CGAL::Simple_cartesian<double>;
|
|
using Mesh = CGAL::Surface_mesh<Kernel::Point_3>;
|
|
|
|
// ── 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<std::size_t>(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<std::size_t>(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<Mesh::Vertex_index, double>(
|
|
// "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 <CGAL/Discrete_conformal_map.h> 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;
|
|
}
|