// example_viewer.cpp // // conformallab++ — Interactive viewer example (requires -DWITH_VIEWER=ON) // // This example demonstrates the full end-to-end pipeline WITH visual output: // // 1. Load (or synthesise) a mesh // 2. Compute the Euclidean discrete conformal map (Newton solver) // 3. Display the result in an interactive libigl / GLFW window // • Left pane: input mesh, coloured by per-vertex conformal factor u_i // • Right pane: a flat parameterisation (future, placeholder) // // The viewer is split into two data sets using libigl's multi-mesh API so // users can inspect geometry and solution simultaneously. // // Build (requires -DWITH_CGAL=ON -DWITH_VIEWER=ON): // cmake -S code -B build -DWITH_CGAL=ON -DWITH_VIEWER=ON // cmake --build build --target example_viewer // ./build/examples/example_viewer [input.off] // // Navigation (libigl default): // Mouse drag — rotate // Scroll — zoom // C — toggle camera mode (trackball / 2D) // Z / X / Y — snap to axis-aligned view // Q / Esc — quit #include "conformal_mesh.hpp" #include "mesh_builder.hpp" #include "mesh_io.hpp" #include "euclidean_functional.hpp" #include "mesh_utils.hpp" #include "newton_solver.hpp" #include #include #include #include #include #include #include using namespace conformallab; // ── Jet colour map: scalar → RGB ───────────────────────────────────────────── static Eigen::RowVector3d jet(double t) { t = std::max(0.0, std::min(1.0, t)); double r = std::clamp(1.5 - std::abs(4.0 * t - 3.0), 0.0, 1.0); double g = std::clamp(1.5 - std::abs(4.0 * t - 2.0), 0.0, 1.0); double b = std::clamp(1.5 - std::abs(4.0 * t - 1.0), 0.0, 1.0); return {r, g, b}; } int main(int argc, char* argv[]) { // ── Step 1: load or synthesise mesh ─────────────────────────────────── ConformalMesh mesh; std::string input_path = (argc > 1) ? argv[1] : ""; if (input_path.empty()) { std::cout << "[example_viewer] No input file — using make_quad_strip().\n"; mesh = make_quad_strip(); } else { std::cout << "[example_viewer] Loading: " << input_path << "\n"; try { mesh = load_mesh(input_path); } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << "\n"; return 1; } } std::cout << "[example_viewer] Mesh: " << mesh.number_of_vertices() << " vertices, " << mesh.number_of_faces() << " faces.\n"; // ── Step 2: solve the Euclidean discrete conformal map ──────────────── auto maps = setup_euclidean_maps(mesh); compute_euclidean_lambda0_from_mesh(mesh, maps); // Pin first vertex auto vit = mesh.vertices().begin(); maps.v_idx[*vit++] = -1; int idx = 0; for (; vit != mesh.vertices().end(); ++vit) maps.v_idx[*vit] = idx++; const int n = idx; // Natural equilibrium { 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)]; } } // Perturb and solve std::vector x0(static_cast(n), -0.08); auto result = newton_euclidean(mesh, x0, maps, 1e-9, 200); if (result.converged) std::cout << "[example_viewer] Converged in " << result.iterations << " iterations.\n"; else std::cout << "[example_viewer] Warning: did not converge fully " "(||G||_inf = " << result.grad_inf_norm << ").\n"; // ── Step 3: build Eigen V / F for libigl ───────────────────────────── using Kernel = CGAL::Simple_cartesian; Eigen::MatrixXd V; Eigen::MatrixXi F; mesh_utils::cgal_to_eigen(mesh, V, F); // Per-vertex colour: conformal factor u_i, mapped via jet palette const int nv = static_cast(V.rows()); Eigen::MatrixXd C(nv, 3); // Collect all u values to normalise std::vector u_all(static_cast(nv), 0.0); for (auto v : mesh.vertices()) { int iv = maps.v_idx[v]; if (iv >= 0) u_all[static_cast(v.idx())] = result.x[static_cast(iv)]; } double u_min = *std::min_element(u_all.begin(), u_all.end()); double u_max = *std::max_element(u_all.begin(), u_all.end()); double u_range = (u_max > u_min) ? (u_max - u_min) : 1.0; for (int vi = 0; vi < nv; ++vi) { double t = (u_all[static_cast(vi)] - u_min) / u_range; C.row(vi) = jet(t); } // ── Step 4: launch interactive viewer ───────────────────────────────── igl::opengl::glfw::Viewer viewer; viewer.data().set_mesh(V, F); viewer.data().set_colors(C); viewer.data().show_lines = true; viewer.data().show_overlay = true; // Status text overlay viewer.data().add_label( Eigen::Vector3d(V.col(0).mean(), V.col(1).mean(), V.col(2).maxCoeff()), "Euclidean conformal factor u_i (jet: blue=min, red=max)"); std::cout << "[example_viewer] Launching viewer. Press Q or Esc to quit.\n"; viewer.launch(); return 0; }