// // common_test.cxx // math-test // // Created by Sam Jaffe on 5/5/19. // Copyright © 2019 Sam Jaffe. All rights reserved. // #include #include #include "game/math/angle.hpp" #include "game/math/common.hpp" #include "game/math/shape.hpp" #include "test_printers.h" using namespace math::dim2; using namespace testing; namespace math { bool intersects(dim2::triangle const &, dim2::triangle const &); } template decltype(auto) generate(T min, T max, T step, G && generator) { std::vector out; for (T val = min; val < max; val += step) { out.emplace_back(generator(val)); } return out; } template decltype(auto) generate(T min, T max, T step) { auto identity = [](T const & val) { return val; }; return generate(min, max, step, identity); } struct LineTest : TestWithParam> {}; TEST_P(LineTest, ExistsOnOriginLine) { point const end = std::get<0>(GetParam()); line const ln{{{0, 0}}, end}; point const pt = end * std::get<1>(GetParam()) / 100.f; EXPECT_TRUE(math::contains(ln, pt)); } TEST_P(LineTest, ColinearOutsideDoesNotContain) { point const end = std::get<0>(GetParam()); line const ln{end, end * 2}; point const pt = end * std::get<1>(GetParam()) / 100.f; EXPECT_FALSE(math::contains(ln, pt)); } std::vector const line_ends{{{1, 1}}, {{0, 1}}, {{1, 0}}, {{-1, -1}}, {{0, -1}}, {{-1, 0}}}; INSTANTIATE_TEST_SUITE_P(Contains, LineTest, Combine(ValuesIn(line_ends), ValuesIn(generate(0, 100, 10)))); struct UnitCircleTest : TestWithParam {}; TEST_P(UnitCircleTest, ExistsInCircle) { circle unit{{{0, 0}}, 1}; EXPECT_TRUE(math::contains(unit, GetParam())); } TEST_P(UnitCircleTest, OutsideSmallerCircle) { circle subunit{{{0, 0}}, 0.9999}; EXPECT_FALSE(math::contains(subunit, GetParam())); } point unit_circle_angle(math::degree degs) { return point(math::sin(degs), math::cos(degs)); } INSTANTIATE_TEST_SUITE_P(Contains, UnitCircleTest, ValuesIn(generate(0.0, 360.0, 5.0, unit_circle_angle))); struct UnitSquareTest : TestWithParam> {}; TEST_P(UnitSquareTest, PointInSquare) { square unit{{{0, 0}}, 1}; point pt{{std::get<0>(GetParam()), std::get<1>(GetParam())}}; EXPECT_TRUE(math::contains(unit, pt)); } TEST_F(UnitSquareTest, PointOutsideSquare) { square unit{{{0, 0}}, 1}; EXPECT_FALSE(math::contains(unit, {{0.f, 1.1f}})); EXPECT_FALSE(math::contains(unit, {{0.f, -0.1f}})); EXPECT_FALSE(math::contains(unit, {{-0.1f, 0.0f}})); EXPECT_FALSE(math::contains(unit, {{1.1f, 0.0f}})); } INSTANTIATE_TEST_SUITE_P(Contains, UnitSquareTest, Combine(ValuesIn(generate(0.f, 1.f, 0.25f)), ValuesIn(generate(0.f, 1.f, 0.25f)))); struct LineQuadTest : TestWithParam {}; TEST_P(LineQuadTest, OriginLineIntersectsUnitSquare) { square const unit{{{0, 0}}, 1}; line const ln{{{0, 0}}, GetParam()}; EXPECT_TRUE(math::intersects(ln, unit)); EXPECT_TRUE(math::intersects(unit, ln)); } TEST_P(LineQuadTest, CrossingLineIntersectsUnitSquare) { square const unit{{{0, 0}}, 1}; line const ln{{{0.5, 0.5}}, GetParam()}; EXPECT_TRUE(math::intersects(ln, unit)); EXPECT_TRUE(math::intersects(unit, ln)); } TEST_P(LineQuadTest, CrossingLineIntersectsSquare) { square const unit{{{0, 0}}, 0.9}; line const ln{{{0.5, 0.5}}, GetParam()}; EXPECT_TRUE(math::intersects(ln, unit)); EXPECT_TRUE(math::intersects(unit, ln)); } TEST_F(LineQuadTest, JustPointIntersection) { square const unit{{{0, 0}}, 1}; line const ln{{{1, 1}}, {{2, 1}}}; EXPECT_TRUE(math::intersects(ln, unit)); EXPECT_TRUE(math::intersects(unit, ln)); } TEST_F(LineQuadTest, EntirelyEncasedIntersection) { square const unit{{{0, 0}}, 1}; line const ln{{{0.5, 0.5}}, {{0.75, 0.75}}}; EXPECT_TRUE(math::intersects(ln, unit)); EXPECT_TRUE(math::intersects(unit, ln)); } TEST_F(LineQuadTest, OutsideByAnInch) { square const unit{{{0, 0}}, 1}; line const ln{{{1.001, 1}}, {{2, 1}}}; math::intersects(ln, unit); EXPECT_FALSE(math::intersects(ln, unit)); EXPECT_FALSE(math::intersects(unit, ln)); } INSTANTIATE_TEST_SUITE_P(Intersects, LineQuadTest, ValuesIn(line_ends)); TEST(CircleTest, CircleIntersectsSelf) { circle const c1{{{0, 0}}, 1}; circle const c2{{{0, 0}}, 1}; EXPECT_TRUE(math::intersects(c1, c2)); } TEST(CircleTest, CircleIntersectsAtOnePoint) { circle const c1{{{0, 0}}, 1}; circle const c2{{{0, 2}}, 1}; EXPECT_TRUE(math::intersects(c1, c2)); } TEST(CircleTest, CircleIntersectsWithChord) { circle const c1{{{0, 0}}, 0.5}; circle const c2{{{0, 1}}, 0.5}; EXPECT_TRUE(math::intersects(c1, c2)); } TEST(CircleTest, CircleContainedWithin) { circle const c1{{{0, 0}}, 2}; circle const c2{{{0, 1}}, 0.5}; EXPECT_TRUE(math::intersects(c1, c2)); } TEST(CircleTest, CircleOutsideDoesNotIntersect) { circle const c1{{{0, 0}}, 1}; circle const c2{{{1.5, 1.5}}, 1}; EXPECT_FALSE(math::intersects(c1, c2)); } TEST(CircleTest, LineIntersectsFromWithin) { circle const c1{{{0, 0}}, 1}; line const ln{{{0.5, 0.5}}, {{1, 1}}}; EXPECT_TRUE(math::intersects(c1, ln)); } TEST(CircleTest, LineIntersectsOnEdge) { circle const c1{{{0, 0}}, 1}; line const ln{{{-1, 1}}, {{1, 1}}}; EXPECT_TRUE(math::intersects(c1, ln)); } TEST(CircleTest, LineIntersectsWhenContained) { circle const c1{{{0, 0}}, 1}; line const ln{{{0.5, 0.5}}, {{-0.5, -0.5}}}; EXPECT_TRUE(math::intersects(c1, ln)); } TEST(CircleTest, ChordLineIntersects) { circle const c1{{{0, 0}}, 1}; line const ln{{{1.5, -0.5}}, {{-0.5, 1.5}}}; EXPECT_TRUE(math::intersects(c1, ln)); } TEST(CircleTest, OutsideLineDoesntIntersect) { circle const c1{{{0, 0}}, 1}; line const ln{{{1, 1}}, {{0, 1.5}}}; EXPECT_FALSE(math::intersects(c1, ln)); } TEST(CircleTest, OverlappingQuad) { circle const c1{{{0, 0}}, 1}; square const sq{{{0.5, 0.5}}, 0.5}; EXPECT_TRUE(math::intersects(c1, sq)); } TEST(CircleTest, IntersectsAtEdge) { circle const c1{{{0, 0}}, 1}; square const sq{{{-1, 1}}, 2}; EXPECT_TRUE(math::intersects(c1, sq)); } TEST(CircleTest, IntersectsAtCorner) { circle const c1{{{0, 0}}, 1}; square const sq{{{0, 1}}, 1}; EXPECT_TRUE(math::intersects(c1, sq)); } TEST(CircleTest, ContainingQuad) { circle const c1{{{0, 0}}, 1}; square const sq{{{0, 0}}, 0.5}; EXPECT_TRUE(math::intersects(c1, sq)); } TEST(CircleTest, ContainedInQuad) { circle const c1{{{0, 0}}, 0.5}; square const sq{{{-1, -1}}, 2}; EXPECT_TRUE(math::intersects(c1, sq)); } TEST(CircleTest, NonIntersectingTangent) { circle const c1{{{0, 0}}, 1}; square const sq{{{1, 1}}, 1}; EXPECT_FALSE(math::intersects(c1, sq)); } TEST(CircleTest, NonIntersecting) { circle const c1{{{0, 0}}, 1}; square const sq{{{1.5, 0.5}}, 0.5}; EXPECT_FALSE(math::intersects(c1, sq)); } TEST(TriangleTest, TriangleIntersectsSelf) { triangle tri{{{0, 0}}, {{0, 1}}, {{1, 0}}}; EXPECT_TRUE(math::intersects(tri, tri)); } TEST(TriangleTest, DoesNotIntersectOffset) { triangle lhs{{{0, 0}}, {{0, 1}}, {{1, 0}}}; auto tri = [](math::dim2::point const & pt) { return triangle{pt, pt, pt}; }; math::dim2::point const clock_ab{{1, 1}}; math::dim2::point const clock_bc{{-1, -1}}; math::dim2::point const clock_ca{{-1, 2}}; // Each of these expectations fails one of the check-edges calls // in the intersection algorithm, which proves that each point of // the triangle is not within (or on) the bounds of the other triangle // Each of these tests indicates one edge of the triangle 'lhs', and // tests for whether it orients around any of the points of the other // 'triangle'. // Tests are done for each edge vs. the the opposite triangle, // starting with edges for the lhs, and then the edges on the rhs. EXPECT_FALSE(math::intersects(lhs, tri(clock_ab))); EXPECT_FALSE(math::intersects(lhs, tri(clock_bc))); EXPECT_FALSE(math::intersects(lhs, tri(clock_ca))); EXPECT_FALSE(math::intersects(tri(clock_ab), lhs)); EXPECT_FALSE(math::intersects(tri(clock_bc), lhs)); EXPECT_FALSE(math::intersects(tri(clock_ca), lhs)); } TEST(QuadTest, IntersectsContain) { quad const lhs = square{{{-0.5f, -0.5f}}, 1}; quad const rhs = square{{{-.25f, -.25f}}, 0.5}; EXPECT_TRUE(math::intersects(lhs, rhs)); } TEST(QuadTest, NoIntersectionAtEdge) { quad const lhs = square{{{-0.5f, -0.5f}}, 1}; quad const rhs = square{{{0.0f, 0.5f}}, 1}; EXPECT_FALSE(math::intersects(lhs, rhs)); } TEST(QuadTest, NoIntersectionAtCorner) { quad const lhs = square{{{-0.5f, -0.5f}}, 1}; quad const rhs = square{{{0.5f, 0.5f}}, 1}; EXPECT_FALSE(math::intersects(lhs, rhs)); } TEST(QuadTest, NoIntersection) { quad const lhs = square{{{-1.0f, -0.5f}}, 1}; quad const rhs = square{{{0.5f, 0.5f}}, 1}; EXPECT_FALSE(math::intersects(lhs, rhs)); } TEST(RotateTest, RotatingSquareAroundOrigin) { math::degree degrees{90.0}; // A square with a side-length of 2 and a center at the origin math::vec2 const origin{0.f, 0.f}; quad const object = square{{{-1, -1}}, 2}; quad const rotated = math::rotate(origin, object, degrees); EXPECT_THAT(rotated.ll, Eq(math::vec2(1.f, -1.f))); EXPECT_THAT(rotated.lr, Eq(math::vec2(1.f, 1.f))); EXPECT_THAT(rotated.ur, Eq(math::vec2(-1.f, 1.f))); EXPECT_THAT(rotated.ul, Eq(math::vec2(-1.f, -1.f))); EXPECT_THAT(math::rotate(origin, rotated, -degrees), Eq(object)); } TEST(RotateTest, RotatingSquareAroundOwnPoint) { math::degree degrees{90.0}; // A square with a side-length of 2 and a center at the origin math::vec2 const axis{-1.f, -1.f}; quad const object = square{{{-1, -1}}, 2}; quad const rotated = math::rotate(axis, object, degrees); EXPECT_THAT(rotated.ll, Eq(math::vec2(-1.f, -1.f))); EXPECT_THAT(rotated.lr, Eq(math::vec2(-1.f, 1.f))); EXPECT_THAT(rotated.ur, Eq(math::vec2(-3.f, 1.f))); EXPECT_THAT(rotated.ul, Eq(math::vec2(-3.f, -1.f))); EXPECT_THAT(math::rotate(axis, rotated, -degrees), Eq(object)); }