145 lines
4.6 KiB
C++
145 lines
4.6 KiB
C++
#include <gtest/gtest.h>
|
|
#include "vde/mesh/delaunay_3d.h"
|
|
#include <cmath>
|
|
|
|
using namespace vde::mesh;
|
|
using namespace vde::core;
|
|
|
|
// ── Helper: compute circumsphere center and radius of 4 points ──
|
|
// Returns (center, radius_squared). If points are coplanar, radius_sq < 0.
|
|
static std::pair<Point3D, double> circumsphere(
|
|
const Point3D& a, const Point3D& b, const Point3D& c, const Point3D& d)
|
|
{
|
|
// Build the linear system using the property that ||p - center||² = r²
|
|
// for all four points. Subtract first equation from others to get 3x3 system.
|
|
Eigen::Matrix3d M;
|
|
Eigen::Vector3d rhs;
|
|
|
|
auto row = [&](const Point3D& pi, const Point3D& p0) {
|
|
Eigen::Vector3d diff = (pi - p0);
|
|
return std::make_pair(diff, (pi.squaredNorm() - p0.squaredNorm()) * 0.5);
|
|
};
|
|
|
|
auto [da, ra] = row(b, a);
|
|
auto [db, rb] = row(c, a);
|
|
auto [dc, rc] = row(d, a);
|
|
|
|
M.row(0) = da; M.row(1) = db; M.row(2) = dc;
|
|
rhs << ra, rb, rc;
|
|
|
|
// Check if singular (coplanar)
|
|
if (std::abs(M.determinant()) < 1e-12) {
|
|
return {Point3D::Zero(), -1.0};
|
|
}
|
|
|
|
Point3D center = M.colPivHouseholderQr().solve(rhs);
|
|
double r2 = (a - center).squaredNorm();
|
|
return {center, r2};
|
|
}
|
|
|
|
// ── Helper: check Delaunay empty-sphere property ──
|
|
// For every tetrahedron, no other input point should be inside its circumsphere.
|
|
static bool verify_empty_circumsphere(
|
|
const TetrahedronMesh& mesh,
|
|
const std::vector<Point3D>& input_points)
|
|
{
|
|
for (const auto& tet : mesh.tetrahedra) {
|
|
const auto& p0 = mesh.vertices[tet[0]];
|
|
const auto& p1 = mesh.vertices[tet[1]];
|
|
const auto& p2 = mesh.vertices[tet[2]];
|
|
const auto& p3 = mesh.vertices[tet[3]];
|
|
|
|
auto [center, r2] = circumsphere(p0, p1, p2, p3);
|
|
if (r2 < 0.0) continue; // degenerate
|
|
|
|
double r = std::sqrt(r2);
|
|
(void)r; // explicitly unused, computed for potential debugging
|
|
for (const auto& pt : input_points) {
|
|
// Skip vertices of this tetrahedron
|
|
double d2 = (pt - center).squaredNorm();
|
|
// Allow small epsilon for floating point
|
|
if (d2 < r2 - 1e-9) {
|
|
return false; // point inside circumsphere → not Delaunay
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// Test cases
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
TEST(Delaunay3DTest, EmptyInput_ReturnsEmpty) {
|
|
std::vector<Point3D> points;
|
|
auto result = delaunay_3d(points);
|
|
EXPECT_EQ(result.vertices.size(), 0u);
|
|
EXPECT_EQ(result.tetrahedra.size(), 0u);
|
|
}
|
|
|
|
TEST(Delaunay3DTest, FourPointsTetrahedron_OneTetrahedron) {
|
|
std::vector<Point3D> pts = {
|
|
{1, 1, 1},
|
|
{1, -1, -1},
|
|
{-1, 1, -1},
|
|
{-1, -1, 1}
|
|
};
|
|
auto result = delaunay_3d(pts);
|
|
EXPECT_EQ(result.vertices.size(), 4u);
|
|
EXPECT_GE(result.tetrahedra.size(), 1u);
|
|
}
|
|
|
|
TEST(Delaunay3DTest, FivePointsCube_VerifyDelaunayProperty) {
|
|
std::vector<Point3D> pts = {
|
|
{0, 0, 0},
|
|
{1, 0, 0},
|
|
{0, 1, 0},
|
|
{0, 0, 1},
|
|
{0.25, 0.25, 0.25} // inside the tetrahedron
|
|
};
|
|
auto result = delaunay_3d(pts);
|
|
EXPECT_EQ(result.vertices.size(), 5u);
|
|
EXPECT_GE(result.tetrahedra.size(), 2u);
|
|
}
|
|
|
|
TEST(Delaunay3DTest, FivePointsDelaunayProperty_EmptyCircumsphere) {
|
|
std::vector<Point3D> pts = {
|
|
{1, 1, 1},
|
|
{1, -1, -1},
|
|
{-1, 1, -1},
|
|
{-1, -1, 1},
|
|
{0, 0, 0}
|
|
};
|
|
auto result = delaunay_3d(pts);
|
|
EXPECT_EQ(result.vertices.size(), 5u);
|
|
EXPECT_GE(result.tetrahedra.size(), 4u);
|
|
EXPECT_TRUE(verify_empty_circumsphere(result, pts));
|
|
}
|
|
|
|
TEST(Delaunay3DTest, CollinearPoints_HandlesGracefully) {
|
|
// 4 points on a line
|
|
std::vector<Point3D> pts = {
|
|
{0, 0, 0},
|
|
{1, 0, 0},
|
|
{2, 0, 0},
|
|
{3, 0, 0}
|
|
};
|
|
auto result = delaunay_3d(pts);
|
|
// Collinear points may produce 0 tetrahedra (degenerate)
|
|
// The function should not crash
|
|
EXPECT_GE(result.tetrahedra.size(), 0u);
|
|
}
|
|
|
|
TEST(Delaunay3DTest, CoplanarPoints_HandlesGracefully) {
|
|
// 4 points on a plane
|
|
std::vector<Point3D> pts = {
|
|
{0, 0, 0},
|
|
{1, 0, 0},
|
|
{1, 1, 0},
|
|
{0, 1, 0}
|
|
};
|
|
auto result = delaunay_3d(pts);
|
|
// Coplanar points should not crash; may return degenerate result
|
|
EXPECT_GE(result.tetrahedra.size(), 0u);
|
|
}
|