diff --git a/include/omath/3d_primitives/aabb.hpp b/include/omath/3d_primitives/aabb.hpp index 62e83cf..afd1872 100644 --- a/include/omath/3d_primitives/aabb.hpp +++ b/include/omath/3d_primitives/aabb.hpp @@ -7,6 +7,8 @@ namespace omath::primitives { + enum class UpAxis { X, Y, Z }; + template struct Aabb final { @@ -24,6 +26,37 @@ namespace omath::primitives { return (max - min) / static_cast(2); } + + template + [[nodiscard]] + constexpr Vector3 top() const noexcept + { + const auto aabb_center = center(); + if constexpr (Up == UpAxis::Z) + return {aabb_center.x, aabb_center.y, max.z}; + else if constexpr (Up == UpAxis::X) + return {max.x, aabb_center.y, aabb_center.z}; + else if constexpr (Up == UpAxis::Y) + return {aabb_center.x, max.y, aabb_center.z}; + else + std::unreachable(); + } + + template + [[nodiscard]] + constexpr Vector3 bottom() const noexcept + { + const auto aabb_center = center(); + if constexpr (Up == UpAxis::Z) + return {aabb_center.x, aabb_center.y, min.z}; + else if constexpr (Up == UpAxis::X) + return {min.x, aabb_center.y, aabb_center.z}; + else if constexpr (Up == UpAxis::Y) + return {aabb_center.x, min.y, aabb_center.z}; + else + std::unreachable(); + } + [[nodiscard]] constexpr bool is_collide(const Aabb& other) const noexcept { diff --git a/tests/general/unit_test_aabb.cpp b/tests/general/unit_test_aabb.cpp index 11a1e7e..02d3a90 100644 --- a/tests/general/unit_test_aabb.cpp +++ b/tests/general/unit_test_aabb.cpp @@ -11,8 +11,8 @@ using Vec3 = omath::Vector3; TEST(AabbTests, CenterOfSymmetricBox) { - const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - const auto c = box.center(); + constexpr AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; + constexpr auto c = box.center(); EXPECT_FLOAT_EQ(c.x, 0.f); EXPECT_FLOAT_EQ(c.y, 0.f); EXPECT_FLOAT_EQ(c.z, 0.f); @@ -20,8 +20,8 @@ TEST(AabbTests, CenterOfSymmetricBox) TEST(AabbTests, CenterOfOffsetBox) { - const AABB box{{1.f, 2.f, 3.f}, {3.f, 6.f, 7.f}}; - const auto c = box.center(); + constexpr AABB box{{1.f, 2.f, 3.f}, {3.f, 6.f, 7.f}}; + constexpr auto c = box.center(); EXPECT_FLOAT_EQ(c.x, 2.f); EXPECT_FLOAT_EQ(c.y, 4.f); EXPECT_FLOAT_EQ(c.z, 5.f); @@ -29,8 +29,8 @@ TEST(AabbTests, CenterOfOffsetBox) TEST(AabbTests, CenterOfDegenerateBox) { - const AABB box{{5.f, 5.f, 5.f}, {5.f, 5.f, 5.f}}; - const auto c = box.center(); + constexpr AABB box{{5.f, 5.f, 5.f}, {5.f, 5.f, 5.f}}; + constexpr auto c = box.center(); EXPECT_FLOAT_EQ(c.x, 5.f); EXPECT_FLOAT_EQ(c.y, 5.f); EXPECT_FLOAT_EQ(c.z, 5.f); @@ -40,8 +40,8 @@ TEST(AabbTests, CenterOfDegenerateBox) TEST(AabbTests, ExtentsOfSymmetricBox) { - const AABB box{{-2.f, -3.f, -4.f}, {2.f, 3.f, 4.f}}; - const auto e = box.extents(); + constexpr AABB box{{-2.f, -3.f, -4.f}, {2.f, 3.f, 4.f}}; + constexpr auto e = box.extents(); EXPECT_FLOAT_EQ(e.x, 2.f); EXPECT_FLOAT_EQ(e.y, 3.f); EXPECT_FLOAT_EQ(e.z, 4.f); @@ -49,8 +49,8 @@ TEST(AabbTests, ExtentsOfSymmetricBox) TEST(AabbTests, ExtentsOfUnitBox) { - const AABB box{{0.f, 0.f, 0.f}, {2.f, 2.f, 2.f}}; - const auto e = box.extents(); + constexpr AABB box{{0.f, 0.f, 0.f}, {2.f, 2.f, 2.f}}; + constexpr auto e = box.extents(); EXPECT_FLOAT_EQ(e.x, 1.f); EXPECT_FLOAT_EQ(e.y, 1.f); EXPECT_FLOAT_EQ(e.z, 1.f); @@ -58,86 +58,183 @@ TEST(AabbTests, ExtentsOfUnitBox) TEST(AabbTests, ExtentsOfDegenerateBox) { - const AABB box{{3.f, 3.f, 3.f}, {3.f, 3.f, 3.f}}; - const auto e = box.extents(); + constexpr AABB box{{3.f, 3.f, 3.f}, {3.f, 3.f, 3.f}}; + constexpr auto e = box.extents(); EXPECT_FLOAT_EQ(e.x, 0.f); EXPECT_FLOAT_EQ(e.y, 0.f); EXPECT_FLOAT_EQ(e.z, 0.f); } +using UpAxis = omath::primitives::UpAxis; + +// --- top() --- + +TEST(AabbTests, TopYUpSymmetricBox) +{ + constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}}; + constexpr auto t = box.top(); + EXPECT_FLOAT_EQ(t.x, 0.f); + EXPECT_FLOAT_EQ(t.y, 2.f); + EXPECT_FLOAT_EQ(t.z, 0.f); +} + +TEST(AabbTests, TopYUpOffsetBox) +{ + constexpr AABB box{{1.f, 4.f, 2.f}, {3.f, 10.f, 6.f}}; + constexpr auto t = box.top(); + EXPECT_FLOAT_EQ(t.x, 2.f); + EXPECT_FLOAT_EQ(t.y, 10.f); + EXPECT_FLOAT_EQ(t.z, 4.f); +} + +TEST(AabbTests, TopZUpSymmetricBox) +{ + constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}}; + constexpr auto t = box.top(); + EXPECT_FLOAT_EQ(t.x, 0.f); + EXPECT_FLOAT_EQ(t.y, 0.f); + EXPECT_FLOAT_EQ(t.z, 3.f); +} + +TEST(AabbTests, TopZUpOffsetBox) +{ + constexpr AABB box{{1.f, 4.f, 2.f}, {3.f, 10.f, 6.f}}; + constexpr auto t = box.top(); + EXPECT_FLOAT_EQ(t.x, 2.f); + EXPECT_FLOAT_EQ(t.y, 7.f); + EXPECT_FLOAT_EQ(t.z, 6.f); +} + +TEST(AabbTests, TopDefaultIsYUp) +{ + constexpr AABB box{{0.f, 0.f, 0.f}, {2.f, 4.f, 6.f}}; + EXPECT_EQ(box.top(), box.top()); +} + +// --- bottom() --- + +TEST(AabbTests, BottomYUpSymmetricBox) +{ + constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}}; + constexpr auto b = box.bottom(); + EXPECT_FLOAT_EQ(b.x, 0.f); + EXPECT_FLOAT_EQ(b.y, -2.f); + EXPECT_FLOAT_EQ(b.z, 0.f); +} + +TEST(AabbTests, BottomYUpOffsetBox) +{ + constexpr AABB box{{1.f, 4.f, 2.f}, {3.f, 10.f, 6.f}}; + constexpr auto b = box.bottom(); + EXPECT_FLOAT_EQ(b.x, 2.f); + EXPECT_FLOAT_EQ(b.y, 4.f); + EXPECT_FLOAT_EQ(b.z, 4.f); +} + +TEST(AabbTests, BottomZUpSymmetricBox) +{ + constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}}; + constexpr auto b = box.bottom(); + EXPECT_FLOAT_EQ(b.x, 0.f); + EXPECT_FLOAT_EQ(b.y, 0.f); + EXPECT_FLOAT_EQ(b.z, -3.f); +} + +TEST(AabbTests, BottomZUpOffsetBox) +{ + constexpr AABB box{{1.f, 4.f, 2.f}, {3.f, 10.f, 6.f}}; + constexpr auto b = box.bottom(); + EXPECT_FLOAT_EQ(b.x, 2.f); + EXPECT_FLOAT_EQ(b.y, 7.f); + EXPECT_FLOAT_EQ(b.z, 2.f); +} + +TEST(AabbTests, BottomDefaultIsYUp) +{ + constexpr AABB box{{0.f, 0.f, 0.f}, {2.f, 4.f, 6.f}}; + EXPECT_EQ(box.bottom(), box.bottom()); +} + +TEST(AabbTests, TopAndBottomAreSymmetric) +{ + constexpr AABB box{{-1.f, -2.f, -3.f}, {1.f, 2.f, 3.f}}; + EXPECT_FLOAT_EQ(box.top().y, -box.bottom().y); + EXPECT_FLOAT_EQ(box.top().z, -box.bottom().z); +} + // --- is_collide() --- TEST(AabbTests, OverlappingBoxesCollide) { - const AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - const AABB b{{0.f, 0.f, 0.f}, {2.f, 2.f, 2.f}}; + constexpr AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; + constexpr AABB b{{0.f, 0.f, 0.f}, {2.f, 2.f, 2.f}}; EXPECT_TRUE(a.is_collide(b)); EXPECT_TRUE(b.is_collide(a)); } TEST(AabbTests, SeparatedBoxesDoNotCollide) { - const AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - const AABB b{{2.f, 2.f, 2.f}, {4.f, 4.f, 4.f}}; + constexpr AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; + constexpr AABB b{{2.f, 2.f, 2.f}, {4.f, 4.f, 4.f}}; EXPECT_FALSE(a.is_collide(b)); EXPECT_FALSE(b.is_collide(a)); } TEST(AabbTests, TouchingFacesCollide) { - const AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - const AABB b{{1.f, -1.f, -1.f}, {3.f, 1.f, 1.f}}; + constexpr AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; + constexpr AABB b{{1.f, -1.f, -1.f}, {3.f, 1.f, 1.f}}; EXPECT_TRUE(a.is_collide(b)); EXPECT_TRUE(b.is_collide(a)); } TEST(AabbTests, ContainedBoxCollides) { - const AABB outer{{-3.f, -3.f, -3.f}, {3.f, 3.f, 3.f}}; - const AABB inner{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; + constexpr AABB outer{{-3.f, -3.f, -3.f}, {3.f, 3.f, 3.f}}; + constexpr AABB inner{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; EXPECT_TRUE(outer.is_collide(inner)); EXPECT_TRUE(inner.is_collide(outer)); } TEST(AabbTests, SeparatedOnXAxisDoNotCollide) { - const AABB a{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}}; - const AABB b{{2.f, 0.f, 0.f}, {3.f, 1.f, 1.f}}; + constexpr AABB a{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}}; + constexpr AABB b{{2.f, 0.f, 0.f}, {3.f, 1.f, 1.f}}; EXPECT_FALSE(a.is_collide(b)); } TEST(AabbTests, SeparatedOnYAxisDoNotCollide) { - const AABB a{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}}; - const AABB b{{0.f, 2.f, 0.f}, {1.f, 3.f, 1.f}}; + constexpr AABB a{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}}; + constexpr AABB b{{0.f, 2.f, 0.f}, {1.f, 3.f, 1.f}}; EXPECT_FALSE(a.is_collide(b)); } TEST(AabbTests, SeparatedOnZAxisDoNotCollide) { - const AABB a{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}}; - const AABB b{{0.f, 0.f, 2.f}, {1.f, 1.f, 3.f}}; + constexpr AABB a{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}}; + constexpr AABB b{{0.f, 0.f, 2.f}, {1.f, 1.f, 3.f}}; EXPECT_FALSE(a.is_collide(b)); } TEST(AabbTests, IdenticalBoxesCollide) { - const AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; + constexpr AABB a{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; EXPECT_TRUE(a.is_collide(a)); } TEST(AabbTests, DegeneratePointBoxCollidesWhenInsideOther) { - const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - const AABB point{{0.f, 0.f, 0.f}, {0.f, 0.f, 0.f}}; + constexpr AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; + constexpr AABB point{{0.f, 0.f, 0.f}, {0.f, 0.f, 0.f}}; EXPECT_TRUE(box.is_collide(point)); EXPECT_TRUE(point.is_collide(box)); } TEST(AabbTests, DegeneratePointBoxDoesNotCollideWhenOutside) { - const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; - const AABB point{{5.f, 0.f, 0.f}, {5.f, 0.f, 0.f}}; + constexpr AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}}; + constexpr AABB point{{5.f, 0.f, 0.f}, {5.f, 0.f, 0.f}}; EXPECT_FALSE(box.is_collide(point)); EXPECT_FALSE(point.is_collide(box)); }