feat(phase3a): introduce CGAL Surface_mesh as ConformalMesh foundation
Replaces the Java CoHDS with CGAL::Surface_mesh<Point3> (Simple_cartesian
kernel). Adds domain-specific property maps for lambda/theta/idx/alpha and
face geometry type — the direct C++ equivalent of CoVertex/CoEdge adapters.
New files:
include/conformal_mesh.hpp — ConformalMesh type + property-map helpers
include/mesh_builder.hpp — mesh factories (triangle, tetrahedron,
quad-strip, fan) for tests and examples
tests/cgal/ — second test executable (conformallab_cgal_tests)
built only with -DWITH_CGAL=ON
Test results (local, -DWITH_CGAL=ON):
conformallab_tests: 36 registered | 23 passed | 13 skipped | 0 failed
conformallab_cgal_tests: 14 registered | 14 passed | 0 skipped | 0 failed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
94
code/include/conformal_mesh.hpp
Normal file
94
code/include/conformal_mesh.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
// conformal_mesh.hpp
|
||||
//
|
||||
// Central mesh type for the discrete conformal mapping algorithms.
|
||||
// Replaces the Java CoHDS (de.varylab.discreteconformal.heds.CoHDS)
|
||||
// and its associated vertex/edge/face types (CoVertex, CoEdge, CoFace).
|
||||
//
|
||||
// Design
|
||||
// ------
|
||||
// Java │ C++ (this file)
|
||||
// ─────────────────────────────┼──────────────────────────────────────────
|
||||
// CoHDS │ ConformalMesh (CGAL::Surface_mesh)
|
||||
// CoVertex / CoEdge / CoFace │ Vertex_index / Edge_index / Face_index
|
||||
// HyperIdealRadiusAdapter │ property_map<Vertex_index, double>
|
||||
// HalfedgeInterface adapters │ named property maps ("v:lambda", …)
|
||||
//
|
||||
// Property-map naming convention
|
||||
// ───────────────────────────────
|
||||
// "v:lambda" per-vertex log scale factor (conformal variable u_i)
|
||||
// "v:theta" per-vertex target cone angle
|
||||
// "v:idx" per-vertex solver DOF index (-1 = pinned / boundary)
|
||||
// "e:alpha" per-edge intersection angle (α_ij, hyperbolic geometry)
|
||||
// "f:type" per-face geometry type (0=Euclidean, 1=Hyperbolic, 2=Spherical)
|
||||
//
|
||||
// All property maps are optional; add only what a given algorithm needs.
|
||||
//
|
||||
// Note on descriptor types (CGAL 6.x)
|
||||
// ────────────────────────────────────
|
||||
// CGAL::Surface_mesh exposes its index types as nested types:
|
||||
// Surface_mesh::Vertex_index, ::Halfedge_index, ::Edge_index, ::Face_index
|
||||
// The BGL graph_traits aliases expose the same types as vertex_descriptor etc.,
|
||||
// but those live in boost::graph_traits<Surface_mesh>, not in Surface_mesh itself.
|
||||
// We use the Surface_mesh member names throughout for clarity.
|
||||
|
||||
#include <CGAL/Simple_cartesian.h>
|
||||
#include <CGAL/Surface_mesh.h>
|
||||
#include <string>
|
||||
|
||||
namespace conformallab {
|
||||
|
||||
// ── Kernel ──────────────────────────────────────────────────────────────────
|
||||
// Simple double-precision Cartesian. Conformal mapping algorithms never
|
||||
// need exact arithmetic — they operate on floating-point lengths and angles.
|
||||
using Kernel = CGAL::Simple_cartesian<double>;
|
||||
using Point3 = Kernel::Point_3;
|
||||
using Point2 = Kernel::Point_2;
|
||||
|
||||
// ── Mesh type ────────────────────────────────────────────────────────────────
|
||||
using ConformalMesh = CGAL::Surface_mesh<Point3>;
|
||||
|
||||
// ── Index/descriptor aliases (CGAL 6.x naming) ───────────────────────────────
|
||||
using Vertex_index = ConformalMesh::Vertex_index;
|
||||
using Halfedge_index = ConformalMesh::Halfedge_index;
|
||||
using Edge_index = ConformalMesh::Edge_index;
|
||||
using Face_index = ConformalMesh::Face_index;
|
||||
|
||||
// ── Geometry type constant (replaces Java CoFace.type enum) ─────────────────
|
||||
enum class GeometryType : int {
|
||||
Euclidean = 0,
|
||||
Hyperbolic = 1,
|
||||
Spherical = 2
|
||||
};
|
||||
|
||||
// ── Standard property-map bundles ────────────────────────────────────────────
|
||||
|
||||
// Add the vertex properties used by all conformal-map functionals.
|
||||
// Returns {lambda, theta, idx}.
|
||||
inline auto add_vertex_properties(ConformalMesh& mesh)
|
||||
{
|
||||
auto [lambda, ok1] = mesh.add_property_map<Vertex_index, double>("v:lambda", 0.0);
|
||||
auto [theta, ok2] = mesh.add_property_map<Vertex_index, double>("v:theta", 0.0);
|
||||
auto [idx, ok3] = mesh.add_property_map<Vertex_index, int> ("v:idx", -1);
|
||||
(void)ok1; (void)ok2; (void)ok3;
|
||||
return std::make_tuple(lambda, theta, idx);
|
||||
}
|
||||
|
||||
// Add the edge intersection-angle property used by the hyperbolic functional.
|
||||
inline auto add_edge_properties(ConformalMesh& mesh)
|
||||
{
|
||||
auto [alpha, ok] = mesh.add_property_map<Edge_index, double>("e:alpha", 0.0);
|
||||
(void)ok;
|
||||
return alpha;
|
||||
}
|
||||
|
||||
// Add the face geometry-type property.
|
||||
inline auto add_face_properties(ConformalMesh& mesh)
|
||||
{
|
||||
auto [ftype, ok] = mesh.add_property_map<Face_index, int>(
|
||||
"f:type", static_cast<int>(GeometryType::Euclidean));
|
||||
(void)ok;
|
||||
return ftype;
|
||||
}
|
||||
|
||||
} // namespace conformallab
|
||||
109
code/include/mesh_builder.hpp
Normal file
109
code/include/mesh_builder.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
// mesh_builder.hpp
|
||||
//
|
||||
// Factory functions that build simple reference meshes for testing and examples.
|
||||
// All functions return a ConformalMesh (CGAL::Surface_mesh<Point3>).
|
||||
//
|
||||
// Replaces Java mesh generators:
|
||||
// CoHDS generators (convex hull, hyper-ideal generator) come later (Phase 3c/4).
|
||||
// These builders cover the minimal meshes needed for functional unit tests.
|
||||
|
||||
#include "conformal_mesh.hpp"
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace conformallab {
|
||||
|
||||
// ── Single triangle ──────────────────────────────────────────────────────────
|
||||
//
|
||||
// v2
|
||||
// | \
|
||||
// | \
|
||||
// v0 ─ v1
|
||||
//
|
||||
// Returns a mesh with 1 face, 3 vertices, 3 edges.
|
||||
// The triangle lies in the xy-plane with a right angle at v0.
|
||||
inline ConformalMesh make_triangle(
|
||||
double x0=0, double y0=0,
|
||||
double x1=1, double y1=0,
|
||||
double x2=0, double y2=1)
|
||||
{
|
||||
ConformalMesh mesh;
|
||||
auto v0 = mesh.add_vertex(Point3(x0, y0, 0));
|
||||
auto v1 = mesh.add_vertex(Point3(x1, y1, 0));
|
||||
auto v2 = mesh.add_vertex(Point3(x2, y2, 0));
|
||||
mesh.add_face(v0, v1, v2);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// ── Regular tetrahedron ──────────────────────────────────────────────────────
|
||||
//
|
||||
// 4 vertices, 4 faces, 6 edges.
|
||||
// Euler characteristic: V - E + F = 4 - 6 + 4 = 2 (sphere topology).
|
||||
// Used to test closed-surface traversal.
|
||||
inline ConformalMesh make_tetrahedron()
|
||||
{
|
||||
ConformalMesh mesh;
|
||||
|
||||
// Vertices of a regular tetrahedron centred at origin, edge length √2·2
|
||||
auto v0 = mesh.add_vertex(Point3( 1, 1, 1));
|
||||
auto v1 = mesh.add_vertex(Point3( 1, -1, -1));
|
||||
auto v2 = mesh.add_vertex(Point3(-1, 1, -1));
|
||||
auto v3 = mesh.add_vertex(Point3(-1, -1, 1));
|
||||
|
||||
// 4 outward-facing triangles (consistent winding)
|
||||
mesh.add_face(v0, v2, v1); // bottom (z=-1 side)
|
||||
mesh.add_face(v0, v1, v3); // front (y=-1 side)
|
||||
mesh.add_face(v0, v3, v2); // left (x=-1 side)
|
||||
mesh.add_face(v1, v2, v3); // back
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// ── Two-triangle strip ───────────────────────────────────────────────────────
|
||||
//
|
||||
// v2 ─ v3
|
||||
// | \ |
|
||||
// v0 ─ v1
|
||||
//
|
||||
// 4 vertices, 2 faces, 5 edges (1 interior edge v1–v2 shared by both faces).
|
||||
// Useful for testing edge-interior vs edge-boundary distinction.
|
||||
inline ConformalMesh make_quad_strip()
|
||||
{
|
||||
ConformalMesh mesh;
|
||||
auto v0 = mesh.add_vertex(Point3(0, 0, 0));
|
||||
auto v1 = mesh.add_vertex(Point3(1, 0, 0));
|
||||
auto v2 = mesh.add_vertex(Point3(0, 1, 0));
|
||||
auto v3 = mesh.add_vertex(Point3(1, 1, 0));
|
||||
|
||||
mesh.add_face(v0, v1, v2); // lower-left triangle
|
||||
mesh.add_face(v1, v3, v2); // upper-right triangle (shares edge v1–v2)
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// ── Regular flat polygon fan ─────────────────────────────────────────────────
|
||||
//
|
||||
// n triangles sharing a central vertex; forms a disk topology (boundary).
|
||||
// Used to verify valence-n vertex traversal.
|
||||
inline ConformalMesh make_fan(int n)
|
||||
{
|
||||
CGAL_precondition(n >= 3);
|
||||
ConformalMesh mesh;
|
||||
|
||||
auto center = mesh.add_vertex(Point3(0, 0, 0));
|
||||
|
||||
const double dtheta = 2.0 * M_PI / n;
|
||||
std::vector<Vertex_index> rim(n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double a = i * dtheta;
|
||||
rim[i] = mesh.add_vertex(Point3(std::cos(a), std::sin(a), 0));
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
mesh.add_face(center, rim[i], rim[(i+1) % n]);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
} // namespace conformallab
|
||||
@@ -27,3 +27,8 @@ target_link_libraries(conformallab_tests PRIVATE GTest::gtest_main)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(conformallab_tests DISCOVERY_TIMEOUT 60)
|
||||
|
||||
# ── CGAL test suite (requires -DWITH_CGAL=ON) ────────────────────────────────
|
||||
if(WITH_CGAL)
|
||||
add_subdirectory(cgal)
|
||||
endif()
|
||||
|
||||
48
code/tests/cgal/CMakeLists.txt
Normal file
48
code/tests/cgal/CMakeLists.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
# tests/cgal/CMakeLists.txt
|
||||
#
|
||||
# CGAL-dependent test target. Only built when -DWITH_CGAL=ON.
|
||||
# Requires Boost (find_package(Boost REQUIRED) is called in the root CMakeLists).
|
||||
#
|
||||
# Run with:
|
||||
# cmake -S code -B build -DWITH_CGAL=ON
|
||||
# cmake --build build --target conformallab_cgal_tests
|
||||
# ctest --test-dir build -R cgal
|
||||
|
||||
add_executable(conformallab_cgal_tests
|
||||
# ── Phase 3a: mesh infrastructure ──────────────────────────────────────
|
||||
test_conformal_mesh.cpp
|
||||
|
||||
# ── Phase 3b: HyperIdealFunctional (to be added) ──────────────────────
|
||||
# test_hyper_ideal_functional.cpp
|
||||
|
||||
# ── Phase 3c: SphericalFunctional (to be added) ──────────────────────
|
||||
# test_spherical_functional.cpp
|
||||
)
|
||||
|
||||
target_include_directories(conformallab_cgal_tests SYSTEM PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/deps/eigen-3.4.0
|
||||
${CMAKE_SOURCE_DIR}/deps/CGAL-6.1.1/include
|
||||
${Boost_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_include_directories(conformallab_cgal_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_compile_definitions(conformallab_cgal_tests PRIVATE
|
||||
CGAL_DISABLE_GMP
|
||||
CGAL_DISABLE_MPFR
|
||||
)
|
||||
|
||||
# Suppress warnings from CGAL/Boost headers
|
||||
target_compile_options(conformallab_cgal_tests PRIVATE
|
||||
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wno-unused-parameter>
|
||||
)
|
||||
|
||||
target_link_libraries(conformallab_cgal_tests PRIVATE GTest::gtest_main)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(conformallab_cgal_tests
|
||||
TEST_PREFIX "cgal."
|
||||
DISCOVERY_TIMEOUT 60
|
||||
)
|
||||
240
code/tests/cgal/test_conformal_mesh.cpp
Normal file
240
code/tests/cgal/test_conformal_mesh.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
// test_conformal_mesh.cpp
|
||||
//
|
||||
// Phase 3a — CGAL Surface_mesh infrastructure tests.
|
||||
//
|
||||
// Verifies that ConformalMesh (CGAL::Surface_mesh<Point3>) and the
|
||||
// mesh_builder factories behave correctly before we build the functionals
|
||||
// on top of them (Phase 3b).
|
||||
//
|
||||
// Test groups
|
||||
// ───────────
|
||||
// Topology – vertex/edge/face counts, Euler characteristic
|
||||
// Traversal – halfedge iteration around vertex / face / edge
|
||||
// PropertyMaps – read/write of lambda, theta, idx, alpha, f:type
|
||||
// Validity – all make_* factories produce valid, consistent meshes
|
||||
|
||||
#include "conformal_mesh.hpp"
|
||||
#include "mesh_builder.hpp"
|
||||
#include <CGAL/boost/graph/iterator.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <cmath>
|
||||
|
||||
using namespace conformallab;
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// Topology
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
// Single triangle: 3 vertices, 1 face, 3 edges.
|
||||
TEST(ConformalMeshTopology, SingleTriangle)
|
||||
{
|
||||
auto mesh = make_triangle();
|
||||
EXPECT_EQ(3u, mesh.number_of_vertices());
|
||||
EXPECT_EQ(1u, mesh.number_of_faces());
|
||||
EXPECT_EQ(3u, mesh.number_of_edges());
|
||||
}
|
||||
|
||||
// Tetrahedron: V=4, E=6, F=4 → Euler = 2 (sphere topology).
|
||||
TEST(ConformalMeshTopology, TetrahedronEuler)
|
||||
{
|
||||
auto mesh = make_tetrahedron();
|
||||
EXPECT_EQ(4u, mesh.number_of_vertices());
|
||||
EXPECT_EQ(6u, mesh.number_of_edges());
|
||||
EXPECT_EQ(4u, mesh.number_of_faces());
|
||||
|
||||
int euler = (int)mesh.number_of_vertices()
|
||||
- (int)mesh.number_of_edges()
|
||||
+ (int)mesh.number_of_faces();
|
||||
EXPECT_EQ(2, euler) << "Euler characteristic of closed sphere must be 2";
|
||||
}
|
||||
|
||||
// Two-triangle strip: V=4, E=5, F=2.
|
||||
// The interior edge (shared diagonal) has no border halfedge.
|
||||
TEST(ConformalMeshTopology, QuadStrip)
|
||||
{
|
||||
auto mesh = make_quad_strip();
|
||||
EXPECT_EQ(4u, mesh.number_of_vertices());
|
||||
EXPECT_EQ(5u, mesh.number_of_edges());
|
||||
EXPECT_EQ(2u, mesh.number_of_faces());
|
||||
|
||||
// Count interior (non-boundary) edges
|
||||
int interior = 0;
|
||||
for (auto e : mesh.edges())
|
||||
if (!mesh.is_border(e)) ++interior;
|
||||
EXPECT_EQ(1, interior) << "Only the shared diagonal should be interior";
|
||||
}
|
||||
|
||||
// Fan with n triangles: V=n+1, E=2n, F=n.
|
||||
TEST(ConformalMeshTopology, FanCounts)
|
||||
{
|
||||
for (int n : {3, 4, 6, 8}) {
|
||||
auto mesh = make_fan(n);
|
||||
EXPECT_EQ((std::size_t)(n + 1), mesh.number_of_vertices());
|
||||
EXPECT_EQ((std::size_t)(2 * n), mesh.number_of_edges());
|
||||
EXPECT_EQ((std::size_t)(n), mesh.number_of_faces());
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// Halfedge Traversal
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
// For a regular tetrahedron every vertex has valence 3.
|
||||
TEST(ConformalMeshTraversal, TetrahedronVertexValence)
|
||||
{
|
||||
auto mesh = make_tetrahedron();
|
||||
for (auto v : mesh.vertices()) {
|
||||
int degree = 0;
|
||||
for (auto h : CGAL::halfedges_around_target(v, mesh))
|
||||
{ (void)h; ++degree; }
|
||||
EXPECT_EQ(3, degree) << "Each tetrahedron vertex has degree 3";
|
||||
}
|
||||
}
|
||||
|
||||
// For a fan with n triangles the center vertex has valence n.
|
||||
TEST(ConformalMeshTraversal, FanCenterValence)
|
||||
{
|
||||
for (int n : {3, 5, 7}) {
|
||||
auto mesh = make_fan(n);
|
||||
|
||||
// Center vertex is always the first one added (index 0).
|
||||
auto center = *mesh.vertices().begin();
|
||||
int degree = 0;
|
||||
for (auto h : CGAL::halfedges_around_target(center, mesh))
|
||||
{ (void)h; ++degree; }
|
||||
EXPECT_EQ(n, degree)
|
||||
<< "Fan center vertex must have valence == n=" << n;
|
||||
}
|
||||
}
|
||||
|
||||
// Every face of the tetrahedron has exactly 3 halfedges.
|
||||
TEST(ConformalMeshTraversal, FaceHalfedgeCount)
|
||||
{
|
||||
auto mesh = make_tetrahedron();
|
||||
for (auto f : mesh.faces()) {
|
||||
int count = 0;
|
||||
for (auto h : CGAL::halfedges_around_face(mesh.halfedge(f), mesh))
|
||||
{ (void)h; ++count; }
|
||||
EXPECT_EQ(3, count) << "Each triangular face must have exactly 3 halfedges";
|
||||
}
|
||||
}
|
||||
|
||||
// opposite(h) and h share the same edge; opposite(opposite(h)) == h.
|
||||
TEST(ConformalMeshTraversal, OppositeHalfedgeConsistency)
|
||||
{
|
||||
auto mesh = make_tetrahedron();
|
||||
for (auto h : mesh.halfedges()) {
|
||||
auto opp = mesh.opposite(h);
|
||||
EXPECT_EQ(mesh.edge(h), mesh.edge(opp))
|
||||
<< "h and opposite(h) must share the same edge";
|
||||
EXPECT_EQ(h, mesh.opposite(opp))
|
||||
<< "opposite(opposite(h)) must equal h";
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// Property Maps
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
// The conformal variable lambda can be written and read back per vertex.
|
||||
TEST(ConformalMeshProperties, VertexLambdaReadWrite)
|
||||
{
|
||||
auto mesh = make_tetrahedron();
|
||||
auto [lambda, theta, idx] = add_vertex_properties(mesh);
|
||||
|
||||
double value = 0.0;
|
||||
for (auto v : mesh.vertices()) {
|
||||
lambda[v] = value;
|
||||
value += 1.0;
|
||||
}
|
||||
|
||||
value = 0.0;
|
||||
for (auto v : mesh.vertices()) {
|
||||
EXPECT_DOUBLE_EQ(value, lambda[v]);
|
||||
value += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Default solver index is -1 (pinned); can be overwritten.
|
||||
TEST(ConformalMeshProperties, VertexSolverIndex)
|
||||
{
|
||||
auto mesh = make_tetrahedron();
|
||||
auto [lambda, theta, idx] = add_vertex_properties(mesh);
|
||||
|
||||
// All vertices start at -1 (pinned / boundary)
|
||||
for (auto v : mesh.vertices())
|
||||
EXPECT_EQ(-1, idx[v]) << "Default solver index must be -1";
|
||||
|
||||
// Assign sequential indices
|
||||
int i = 0;
|
||||
for (auto v : mesh.vertices())
|
||||
idx[v] = i++;
|
||||
|
||||
i = 0;
|
||||
for (auto v : mesh.vertices())
|
||||
EXPECT_EQ(i++, idx[v]);
|
||||
}
|
||||
|
||||
// Edge alpha (intersection angle): set and retrieve per edge.
|
||||
TEST(ConformalMeshProperties, EdgeAlpha)
|
||||
{
|
||||
auto mesh = make_quad_strip();
|
||||
auto alpha = add_edge_properties(mesh);
|
||||
|
||||
const double kAlpha = M_PI / 3.0; // 60°
|
||||
for (auto e : mesh.edges())
|
||||
alpha[e] = kAlpha;
|
||||
|
||||
for (auto e : mesh.edges())
|
||||
EXPECT_DOUBLE_EQ(kAlpha, alpha[e]);
|
||||
|
||||
EXPECT_EQ(5u, mesh.number_of_edges());
|
||||
}
|
||||
|
||||
// Face geometry type: Euclidean by default, switchable to Hyperbolic.
|
||||
TEST(ConformalMeshProperties, FaceGeometryType)
|
||||
{
|
||||
auto mesh = make_tetrahedron();
|
||||
auto ftype = add_face_properties(mesh);
|
||||
|
||||
// Default: Euclidean
|
||||
for (auto f : mesh.faces())
|
||||
EXPECT_EQ(static_cast<int>(GeometryType::Euclidean), ftype[f]);
|
||||
|
||||
// Switch all to Hyperbolic
|
||||
for (auto f : mesh.faces())
|
||||
ftype[f] = static_cast<int>(GeometryType::Hyperbolic);
|
||||
|
||||
for (auto f : mesh.faces())
|
||||
EXPECT_EQ(static_cast<int>(GeometryType::Hyperbolic), ftype[f]);
|
||||
}
|
||||
|
||||
// Adding the same named property map twice: second call returns ok=false
|
||||
// and both handles alias the same storage.
|
||||
TEST(ConformalMeshProperties, PropertyMapIdempotent)
|
||||
{
|
||||
auto mesh = make_triangle();
|
||||
auto [pm1, ok1] = mesh.add_property_map<Vertex_index, double>("v:lambda", 0.0);
|
||||
auto [pm2, ok2] = mesh.add_property_map<Vertex_index, double>("v:lambda", 0.0);
|
||||
|
||||
EXPECT_TRUE(ok1) << "First add_property_map must succeed";
|
||||
EXPECT_FALSE(ok2) << "Second add_property_map on existing name must return ok=false";
|
||||
|
||||
// Both handles must alias the same storage
|
||||
auto v = *mesh.vertices().begin();
|
||||
pm1[v] = 42.0;
|
||||
EXPECT_DOUBLE_EQ(42.0, pm2[v]) << "Both handles must alias the same storage";
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// Mesh validity
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
// CGAL's built-in validity check must pass for all factory meshes.
|
||||
TEST(ConformalMeshValidity, AllBuilders)
|
||||
{
|
||||
EXPECT_TRUE(make_triangle().is_valid()) << "triangle mesh invalid";
|
||||
EXPECT_TRUE(make_tetrahedron().is_valid()) << "tetrahedron mesh invalid";
|
||||
EXPECT_TRUE(make_quad_strip().is_valid()) << "quad strip mesh invalid";
|
||||
EXPECT_TRUE(make_fan(6).is_valid()) << "fan-6 mesh invalid";
|
||||
}
|
||||
Reference in New Issue
Block a user