#pragma once // Copyright (c) 2024-2026 Tarik Moussa. // SPDX-License-Identifier: MIT // mesh_utils.hpp // // Conversions between CGAL::Surface_mesh and Eigen matrices. Used // primarily by the viewer / example programs to bridge to libigl, which // expects (V, F) matrix pairs rather than a halfedge data structure. // // All functions are templated on the kernel so the same code works // with `Simple_cartesian` (production) and with any CGAL // `Kernel_d::Point_3` (test scaffolding). #include #include #include namespace mesh_utils { /// Copy `mesh` into an Eigen `(V, F)` pair (libigl convention). /// /// **Side effect:** `mesh` is triangulated in place via /// `CGAL::Polygon_mesh_processing::triangulate_faces` so the output /// `F` is guaranteed to be a 3-column matrix. If `mesh` is already a /// triangle mesh this is a no-op. /// /// \param mesh Input surface mesh. **Modified in place** if any face /// has more than 3 vertices. /// \param V Output: `(num_vertices, 3)` matrix of vertex positions. /// \param F Output: `(num_faces, 3)` matrix of vertex indices per /// face (rows are individual triangles). template void cgal_to_eigen(CGAL::Surface_mesh& mesh, Eigen::MatrixXd& V, Eigen::MatrixXi& F) { CGAL::Polygon_mesh_processing::triangulate_faces(mesh); V.resize(mesh.num_vertices(), 3); F.resize(mesh.num_faces(), 3); for (auto v : mesh.vertices()) { auto p = mesh.point(v); V.row(v.idx()) << p.x(), p.y(), p.z(); } Eigen::Index face_idx = 0; for (auto f : mesh.faces()) { int vertex_count = 0; for (auto h : CGAL::halfedges_around_face(mesh.halfedge(f), mesh)) { auto v = mesh.target(h); F(face_idx, vertex_count) = v.idx(); ++vertex_count; } face_idx++; } } /// Quick interactive visualisation via libigl + GLFW. /// /// **Requires** `WITH_VIEWER=ON` at CMake time (which is implied by /// `WITH_CGAL=ON`). Blocks until the viewer window is closed. /// Not suitable for CI / headless contexts. /// /// Typical use: /// \code{.cpp} /// Eigen::MatrixXd V; Eigen::MatrixXi F; /// mesh_utils::cgal_to_eigen(mesh, V, F); /// mesh_utils::simple_visualize_mesh(V, F); /// \endcode template void simple_visualize_mesh(Eigen::MatrixXd& V, Eigen::MatrixXi& F) { igl::opengl::glfw::Viewer viewer; viewer.data().set_mesh(V, F); viewer.launch(); } /// Zero-copy `Eigen::Map` view of `mesh`'s vertex positions. /// /// Returns a row-major `(N, 3)` `Eigen::Map` that aliases the /// `mesh.points()` storage directly — no allocation, O(1). /// /// **Lifetime warning:** the returned `Map` references memory owned by /// `mesh`. Adding or removing vertices may invalidate the underlying /// storage; use the `Map` only as long as `mesh` is structurally stable. /// /// This is the read-write counterpart to `cgal_to_eigen` for cases /// where the caller wants to *modify* vertex positions through Eigen /// (e.g. apply a Möbius transformation) without an intermediate copy. template Eigen::Map> get_vertex_map(CGAL::Surface_mesh& mesh) { auto& points = mesh.points(); double* data = reinterpret_cast(&points[0][0]); return {data, static_cast(points.size()), 3}; } } // namespace