Merge pull request 'feat(geometry): pn_geometry.hpp — Pn projective-metric substrate (jReality port)' (#31) from feat/pn-geometry-substrate into main
Some checks failed
C++ Tests / test-fast (push) Successful in 2m21s
API Docs / doc-build (push) Has been skipped
Markdown link check / check (push) Successful in 54s
Mirror to Codeberg / mirror (push) Successful in 43s
C++ Tests / test-cgal (push) Has been skipped
C++ Tests / quality-gates (push) Has been cancelled
Some checks failed
C++ Tests / test-fast (push) Successful in 2m21s
API Docs / doc-build (push) Has been skipped
Markdown link check / check (push) Successful in 54s
Mirror to Codeberg / mirror (push) Successful in 43s
C++ Tests / test-cgal (push) Has been skipped
C++ Tests / quality-gates (push) Has been cancelled
This commit is contained in:
@@ -90,6 +90,11 @@ add_executable(conformallab_cgal_tests
|
||||
# Low-level half-edge genus-2 generator + golden-vector convergence.
|
||||
test_lawson_hyperideal.cpp
|
||||
|
||||
# ── Pn projective-metric substrate (de.jreality.math.Pn port) ───────────
|
||||
# Inner product / norm / distance / geodesic interpolation in
|
||||
# Euclidean, Elliptic, Hyperbolic signatures.
|
||||
test_pn_geometry.cpp
|
||||
|
||||
# ── Phase 8b-Lite: CGAL entry wrappers for the 4 non-Euclidean modes ─────
|
||||
# Spherical, HyperIdeal, CircleP-Euclidean, Inversive-Distance via
|
||||
# <CGAL/Discrete_*.h> public API + Conformal_layout.h wrapper.
|
||||
|
||||
135
code/tests/cgal/test_pn_geometry.cpp
Normal file
135
code/tests/cgal/test_pn_geometry.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2024-2026 Tarik Moussa.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// test_pn_geometry.cpp
|
||||
//
|
||||
// Verifies pn_geometry.hpp against:
|
||||
// (a) closed-form analytic golden values, and
|
||||
// (b) the already-verified projective_math.hpp::hyperbolicDistance
|
||||
// (regression anchor for the HYPERBOLIC sign convention).
|
||||
//
|
||||
// Mirrored Java source: de.jreality.math.Pn
|
||||
|
||||
#include "pn_geometry.hpp"
|
||||
#include "projective_math.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
#include <Eigen/Core>
|
||||
#include <cmath>
|
||||
|
||||
using namespace conformallab;
|
||||
|
||||
namespace {
|
||||
Eigen::VectorXd V(std::initializer_list<double> xs) {
|
||||
Eigen::VectorXd v(static_cast<int>(xs.size()));
|
||||
int i = 0;
|
||||
for (double x : xs) v(i++) = x;
|
||||
return v;
|
||||
}
|
||||
constexpr double PN_PI = 3.14159265358979323846;
|
||||
} // namespace
|
||||
|
||||
// ── Inner product per signature ──────────────────────────────────────────────
|
||||
TEST(PnGeometry, InnerProductSignatures)
|
||||
{
|
||||
auto u = V({1.0, 2.0, 3.0}); // spatial=(1,2), last=3
|
||||
auto v = V({4.0, 5.0, 6.0}); // spatial=(4,5), last=6
|
||||
// spat = 1·4 + 2·5 = 14, last = 3·6 = 18
|
||||
EXPECT_NEAR(pn_inner_product(u, v, PN_EUCLIDEAN), 14.0, 1e-12);
|
||||
EXPECT_NEAR(pn_inner_product(u, v, PN_ELLIPTIC), 14.0+18.0, 1e-12);
|
||||
EXPECT_NEAR(pn_inner_product(u, v, PN_HYPERBOLIC), 18.0-14.0, 1e-12);
|
||||
}
|
||||
|
||||
// ── Euclidean distance ────────────────────────────────────────────────────────
|
||||
TEST(PnGeometry, EuclideanDistance)
|
||||
{
|
||||
// 3-4-5 right triangle in the plane (homogeneous w=1).
|
||||
auto p = V({0.0, 0.0, 1.0});
|
||||
auto q = V({3.0, 4.0, 1.0});
|
||||
EXPECT_NEAR(pn_distance_between(p, q, PN_EUCLIDEAN), 5.0, 1e-12);
|
||||
|
||||
// Homogeneous scaling must not change the distance.
|
||||
auto q2 = V({6.0, 8.0, 2.0}); // same point as q after dehomogenize
|
||||
EXPECT_NEAR(pn_distance_between(p, q2, PN_EUCLIDEAN), 5.0, 1e-12);
|
||||
}
|
||||
|
||||
// ── Elliptic distance = spherical angle ───────────────────────────────────────
|
||||
TEST(PnGeometry, EllipticDistanceIsAngle)
|
||||
{
|
||||
auto e1 = V({1.0, 0.0, 0.0});
|
||||
auto e2 = V({0.0, 1.0, 0.0});
|
||||
auto d = V({1.0, 1.0, 0.0}); // 45° between e1 and e2
|
||||
|
||||
EXPECT_NEAR(pn_distance_between(e1, e2, PN_ELLIPTIC), PN_PI / 2.0, 1e-12);
|
||||
EXPECT_NEAR(pn_distance_between(e1, d, PN_ELLIPTIC), PN_PI / 4.0, 1e-12);
|
||||
EXPECT_NEAR(pn_distance_between(e1, e1, PN_ELLIPTIC), 0.0, 1e-12);
|
||||
}
|
||||
|
||||
// ── Hyperbolic distance: closed form + projective_math.hpp anchor ─────────────
|
||||
TEST(PnGeometry, HyperbolicDistanceClosedFormAndAnchor)
|
||||
{
|
||||
// Upper hyperboloid: p = apex (0,0,1); q = (sinh r, 0, cosh r) at distance r.
|
||||
const double r = 0.873;
|
||||
auto p = V({0.0, 0.0, 1.0});
|
||||
auto q = V({std::sinh(r), 0.0, std::cosh(r)});
|
||||
|
||||
EXPECT_NEAR(pn_distance_between(p, q, PN_HYPERBOLIC), r, 1e-10);
|
||||
|
||||
// Regression anchor: identical to projective_math.hpp::hyperbolicDistance.
|
||||
EXPECT_NEAR(pn_distance_between(p, q, PN_HYPERBOLIC),
|
||||
hyperbolicDistance(p, q), 1e-12);
|
||||
|
||||
// Scale-invariance: homogeneous rescaling must not change distance.
|
||||
auto q2 = (2.5 * q).eval();
|
||||
EXPECT_NEAR(pn_distance_between(p, q2, PN_HYPERBOLIC), r, 1e-10);
|
||||
}
|
||||
|
||||
// ── Norm / setToLength / normalize ───────────────────────────────────────────
|
||||
TEST(PnGeometry, NormAndScaling)
|
||||
{
|
||||
auto p = V({3.0, 4.0, 0.0});
|
||||
EXPECT_NEAR(pn_norm(p, PN_ELLIPTIC), 5.0, 1e-12);
|
||||
|
||||
auto s = pn_set_to_length(p, 2.0, PN_ELLIPTIC);
|
||||
EXPECT_NEAR(pn_norm(s, PN_ELLIPTIC), 2.0, 1e-12);
|
||||
|
||||
auto u = pn_normalize(p, PN_ELLIPTIC);
|
||||
EXPECT_NEAR(pn_norm(u, PN_ELLIPTIC), 1.0, 1e-12);
|
||||
|
||||
// A unit hyperboloid sheet point already has hyperbolic norm 1.
|
||||
auto h = V({std::sinh(0.6), 0.0, std::cosh(0.6)});
|
||||
EXPECT_NEAR(pn_norm(h, PN_HYPERBOLIC), 1.0, 1e-12);
|
||||
}
|
||||
|
||||
// ── Geodesic interpolation ────────────────────────────────────────────────────
|
||||
TEST(PnGeometry, LinearInterpolationGeodesic)
|
||||
{
|
||||
// EUCLIDEAN: affine midpoint.
|
||||
{
|
||||
auto p = V({0.0, 0.0, 1.0});
|
||||
auto q = V({4.0, 0.0, 1.0});
|
||||
auto m = pn_linear_interpolation(p, q, 0.5, PN_EUCLIDEAN);
|
||||
EXPECT_NEAR(m(0), 2.0, 1e-12);
|
||||
}
|
||||
|
||||
// ELLIPTIC: midpoint of e1,e2 is at half the π/2 arc = π/4 from each.
|
||||
{
|
||||
auto e1 = V({1.0, 0.0, 0.0});
|
||||
auto e2 = V({0.0, 1.0, 0.0});
|
||||
auto m = pn_linear_interpolation(e1, e2, 0.5, PN_ELLIPTIC);
|
||||
EXPECT_NEAR(pn_distance_between(e1, m, PN_ELLIPTIC), PN_PI / 4.0, 1e-10);
|
||||
EXPECT_NEAR(pn_distance_between(e2, m, PN_ELLIPTIC), PN_PI / 4.0, 1e-10);
|
||||
// Endpoint recovery at t=0.
|
||||
auto m0 = pn_linear_interpolation(e1, e2, 0.0, PN_ELLIPTIC);
|
||||
EXPECT_NEAR(pn_distance_between(e1, m0, PN_ELLIPTIC), 0.0, 1e-10);
|
||||
}
|
||||
|
||||
// HYPERBOLIC: midpoint is at exactly half the geodesic distance from each end.
|
||||
{
|
||||
const double r = 1.2;
|
||||
auto p = V({0.0, 0.0, 1.0});
|
||||
auto q = V({std::sinh(r), 0.0, std::cosh(r)});
|
||||
auto m = pn_linear_interpolation(p, q, 0.5, PN_HYPERBOLIC);
|
||||
EXPECT_NEAR(pn_distance_between(p, m, PN_HYPERBOLIC), r / 2.0, 1e-9);
|
||||
EXPECT_NEAR(pn_distance_between(q, m, PN_HYPERBOLIC), r / 2.0, 1e-9);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user