147 lines
4.4 KiB
C++
147 lines
4.4 KiB
C++
|
|
#include <gtest/gtest.h>
|
||
|
|
#include "vde/core/polygon.h"
|
||
|
|
|
||
|
|
using namespace vde::core;
|
||
|
|
|
||
|
|
// ── signed_area tests ──
|
||
|
|
|
||
|
|
TEST(PolygonTest, SignedArea_CCWSquare_Positive) {
|
||
|
|
// Counter-clockwise unit square
|
||
|
|
Polygon2D poly({{0, 0}, {1, 0}, {1, 1}, {0, 1}});
|
||
|
|
EXPECT_GT(poly.signed_area(), 0.0);
|
||
|
|
EXPECT_NEAR(poly.signed_area(), 1.0, 1e-9);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, SignedArea_CWSquare_Negative) {
|
||
|
|
// Clockwise unit square
|
||
|
|
Polygon2D poly({{0, 0}, {0, 1}, {1, 1}, {1, 0}});
|
||
|
|
EXPECT_LT(poly.signed_area(), 0.0);
|
||
|
|
EXPECT_NEAR(poly.signed_area(), -1.0, 1e-9);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, SignedArea_Triangle) {
|
||
|
|
Polygon2D poly({{0, 0}, {3, 0}, {0, 4}});
|
||
|
|
EXPECT_NEAR(std::abs(poly.signed_area()), 6.0, 1e-9);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, SignedArea_Collinear_Degenerate) {
|
||
|
|
// All points on a line → zero area
|
||
|
|
Polygon2D poly({{0, 0}, {1, 1}, {2, 2}});
|
||
|
|
EXPECT_NEAR(poly.signed_area(), 0.0, 1e-9);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, SignedArea_Empty) {
|
||
|
|
Polygon2D poly;
|
||
|
|
EXPECT_DOUBLE_EQ(poly.signed_area(), 0.0);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, Area_AbsoluteValue) {
|
||
|
|
// area() should be absolute value of signed_area()
|
||
|
|
Polygon2D ccw({{0, 0}, {2, 0}, {2, 2}, {0, 2}});
|
||
|
|
Polygon2D cw({{0, 0}, {0, 2}, {2, 2}, {2, 0}});
|
||
|
|
EXPECT_NEAR(ccw.area(), 4.0, 1e-9);
|
||
|
|
EXPECT_NEAR(cw.area(), 4.0, 1e-9);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── contains (point-in-polygon via ray casting) ──
|
||
|
|
|
||
|
|
TEST(PolygonTest, Contains_InteriorPoint) {
|
||
|
|
Polygon2D square({{0, 0}, {2, 0}, {2, 2}, {0, 2}});
|
||
|
|
// Center of the square
|
||
|
|
EXPECT_TRUE(square.contains(Point2D(1.0, 1.0)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, Contains_ExteriorPoint) {
|
||
|
|
Polygon2D square({{0, 0}, {2, 0}, {2, 2}, {0, 2}});
|
||
|
|
// Far outside
|
||
|
|
EXPECT_FALSE(square.contains(Point2D(10.0, 10.0)));
|
||
|
|
// Outside but near
|
||
|
|
EXPECT_FALSE(square.contains(Point2D(-0.1, 1.0)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, Contains_BoundaryPoint) {
|
||
|
|
Polygon2D square({{0, 0}, {2, 0}, {2, 2}, {0, 2}});
|
||
|
|
// On edge — behavior may vary; ray casting treats as intersection
|
||
|
|
// Not strictly "interior" in most implementations
|
||
|
|
Point2D on_edge(1.0, 0.0);
|
||
|
|
// Just verify it doesn't crash; boundary is valid input
|
||
|
|
bool result = square.contains(on_edge);
|
||
|
|
(void)result; // implementation-defined for boundary
|
||
|
|
SUCCEED();
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, Contains_PointOnVertex) {
|
||
|
|
Polygon2D tri({{0, 0}, {2, 0}, {0, 2}});
|
||
|
|
// Exact vertex — should not crash
|
||
|
|
bool result = tri.contains(Point2D(0.0, 0.0));
|
||
|
|
(void)result;
|
||
|
|
SUCCEED();
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, Contains_ConcavePolygon) {
|
||
|
|
// L-shaped polygon (concave)
|
||
|
|
Polygon2D L({{0, 0}, {3, 0}, {3, 1}, {2, 1}, {2, 3}, {0, 3}});
|
||
|
|
// Point in the "notch" area (1.5, 2) should be inside
|
||
|
|
EXPECT_TRUE(L.contains(Point2D(1.0, 2.0)));
|
||
|
|
// Point in the concave void
|
||
|
|
EXPECT_FALSE(L.contains(Point2D(2.5, 2.0)));
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── is_ccw ──
|
||
|
|
|
||
|
|
TEST(PolygonTest, IsCCW) {
|
||
|
|
Polygon2D ccw({{0, 0}, {1, 0}, {1, 1}, {0, 1}});
|
||
|
|
Polygon2D cw({{0, 0}, {0, 1}, {1, 1}, {1, 0}});
|
||
|
|
EXPECT_TRUE(ccw.is_ccw());
|
||
|
|
EXPECT_FALSE(cw.is_ccw());
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Basic accessors ──
|
||
|
|
|
||
|
|
TEST(PolygonTest, SizeAndEmpty) {
|
||
|
|
Polygon2D empty;
|
||
|
|
EXPECT_TRUE(empty.empty());
|
||
|
|
EXPECT_EQ(empty.size(), 0u);
|
||
|
|
|
||
|
|
Polygon2D tri({{0, 0}, {1, 0}, {0, 1}});
|
||
|
|
EXPECT_FALSE(tri.empty());
|
||
|
|
EXPECT_EQ(tri.size(), 3u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, VerticesAccessor) {
|
||
|
|
std::vector<Point2D> v = {{0, 0}, {1, 0}, {0, 1}};
|
||
|
|
Polygon2D poly(v);
|
||
|
|
EXPECT_EQ(poly.vertices().size(), 3u);
|
||
|
|
EXPECT_DOUBLE_EQ(poly.vertices()[0].x(), 0.0);
|
||
|
|
EXPECT_DOUBLE_EQ(poly.vertices()[1].y(), 0.0);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, DefaultConstructor_Empty) {
|
||
|
|
Polygon2D poly;
|
||
|
|
EXPECT_TRUE(poly.vertices().empty());
|
||
|
|
EXPECT_TRUE(poly.empty());
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Larger polygon area (regular hexagon) ──
|
||
|
|
|
||
|
|
TEST(PolygonTest, SignedArea_RegularHexagon) {
|
||
|
|
// Regular hexagon centered at origin, radius 1
|
||
|
|
// Vertices CCW: (1,0), (0.5,0.866), (-0.5,0.866), (-1,0), (-0.5,-0.866), (0.5,-0.866)
|
||
|
|
Polygon2D hex({
|
||
|
|
{1.0, 0.0}, {0.5, std::sqrt(3.0) / 2.0},
|
||
|
|
{-0.5, std::sqrt(3.0) / 2.0}, {-1.0, 0.0},
|
||
|
|
{-0.5, -std::sqrt(3.0) / 2.0}, {0.5, -std::sqrt(3.0) / 2.0}
|
||
|
|
});
|
||
|
|
// Area of regular hexagon = (3√3 / 2) * r² ≈ 2.598
|
||
|
|
EXPECT_NEAR(hex.signed_area(), 3.0 * std::sqrt(3.0) / 2.0, 1e-9);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(PolygonTest, Contains_StarShapedHexagon) {
|
||
|
|
Polygon2D hex({
|
||
|
|
{1.0, 0.0}, {0.5, 0.866}, {-0.5, 0.866},
|
||
|
|
{-1.0, 0.0}, {-0.5, -0.866}, {0.5, -0.866}
|
||
|
|
});
|
||
|
|
EXPECT_TRUE(hex.contains(Point2D(0.0, 0.0)));
|
||
|
|
EXPECT_FALSE(hex.contains(Point2D(2.0, 0.0)));
|
||
|
|
}
|