#include "testHarness.h" #include "vf/Selection.h" #include #include using namespace vf; namespace { World slabWorld(int spanX, int spanZ, int surfaceY, Block block) { World world; for (int z = 0; z < spanZ; ++z) for (int x = 0; x < spanX; ++x) world.setBlock(x, surfaceY, z, block); return world; } std::size_t cellsInsideBox(const Selection& selection, const Vec3i& lo, const Vec3i& hi) { std::size_t inside = 0; selection.forEach([&](const Vec3i& cell) { if (cell.x >= lo.x && cell.x <= hi.x && cell.y >= lo.y && cell.y <= hi.y && cell.z >= lo.z && cell.z <= hi.z) ++inside; }); return inside; } } // namespace VF_TEST(boxSelectionCoversEveryCellBetweenTheTwoCorners) { Selection box = Selection::box({4, 1, -2}, {1, 3, 0}); CHECK(box.shape() == SelectionShape::Box); CHECK(box.min() == Vec3i(1, 1, -2)); CHECK(box.max() == Vec3i(4, 3, 0)); CHECK(box.dims() == Vec3i(4, 3, 3)); CHECK_EQ(box.count(), 36u); CHECK(box.contains({1, 1, -2})); CHECK(box.contains({4, 3, 0})); CHECK(!box.contains({5, 3, 0})); CHECK(!box.contains({1, 0, 0})); } VF_TEST(sphereSelectionIsSymmetricAroundItsCentreAndFitsItsBoundingBox) { Selection sphere = Selection::sphere({10, 10, 10}, 4); CHECK(sphere.shape() == SelectionShape::Sphere); CHECK(sphere.dims() == Vec3i(9, 9, 9)); CHECK(sphere.contains({10, 10, 10})); CHECK(sphere.contains({14, 10, 10})); CHECK(!sphere.contains({14, 14, 10})); CHECK(sphere.count() < 9u * 9u * 9u); CHECK(sphere.count() > 200u); sphere.forEach([&](const Vec3i& cell) { Vec3i mirrored{20 - cell.x, 20 - cell.y, 20 - cell.z}; CHECK(sphere.contains(mirrored)); }); } VF_TEST(ellipsoidSelectionRespectsAPerAxisRadius) { Selection blob = Selection::ellipsoid({0, 0, 0}, {6, 1, 2}); CHECK(blob.dims() == Vec3i(13, 3, 5)); CHECK(blob.contains({6, 0, 0})); CHECK(blob.contains({0, 1, 0})); CHECK(blob.contains({0, 0, 2})); CHECK(!blob.contains({6, 1, 2})); CHECK(!blob.contains({0, 2, 0})); } VF_TEST(cylinderSelectionRepeatsTheSameDiscOnEveryLayer) { Selection tube = Selection::cylinder({0, 5, 0}, 3, 4); CHECK(tube.min() == Vec3i(-3, 5, -3)); CHECK(tube.max() == Vec3i(3, 8, 3)); std::size_t perLayer = 0; for (int z = -3; z <= 3; ++z) for (int x = -3; x <= 3; ++x) if (tube.contains({x, 5, z})) ++perLayer; CHECK(perLayer > 0u); CHECK_EQ(tube.count(), perLayer * 4u); for (int y = 5; y <= 8; ++y) { std::size_t layer = 0; for (int z = -3; z <= 3; ++z) for (int x = -3; x <= 3; ++x) if (tube.contains({x, y, z})) ++layer; CHECK_EQ(layer, perLayer); } CHECK(!tube.contains({0, 9, 0})); } VF_TEST(hollowBoxKeepsOnlyTheWallsOfTheRequestedThickness) { Selection walls = Selection::hollowBox({0, 0, 0}, {4, 4, 4}, 1); CHECK_EQ(walls.count(), 125u - 27u); CHECK(walls.contains({0, 0, 0})); CHECK(walls.contains({4, 2, 2})); CHECK(!walls.contains({2, 2, 2})); Selection thick = Selection::hollowBox({0, 0, 0}, {6, 6, 6}, 2); CHECK_EQ(thick.count(), 343u - 27u); CHECK(thick.contains({1, 1, 1})); CHECK(!thick.contains({3, 3, 3})); } VF_TEST(maskUnionCollectsCellsFromBothOperandsWithoutDoubleCounting) { Selection left = Selection::box({0, 0, 0}, {3, 0, 0}); Selection right = Selection::box({2, 0, 0}, {5, 0, 0}); Selection merged = left.unionWith(right); CHECK_EQ(merged.count(), 6u); CHECK(merged.min() == Vec3i(0, 0, 0)); CHECK(merged.max() == Vec3i(5, 0, 0)); for (int x = 0; x <= 5; ++x) CHECK(merged.contains({x, 0, 0})); CHECK(merged.shape() == SelectionShape::Mask); } VF_TEST(maskIntersectionKeepsOnlyTheOverlapAndIsEmptyWhenBoxesMiss) { Selection left = Selection::box({0, 0, 0}, {3, 3, 3}); Selection right = Selection::box({2, 2, 2}, {5, 5, 5}); Selection shared = left.intersectWith(right); CHECK_EQ(shared.count(), 8u); CHECK(shared.min() == Vec3i(2, 2, 2)); CHECK(shared.max() == Vec3i(3, 3, 3)); Selection apart = Selection::box({20, 20, 20}, {21, 21, 21}); CHECK(left.intersectWith(apart).empty()); CHECK_EQ(left.intersectWith(apart).count(), 0u); } VF_TEST(maskDifferenceRemovesEveryCellOfTheSecondOperand) { Selection block = Selection::box({0, 0, 0}, {3, 3, 3}); Selection bite = Selection::box({0, 0, 0}, {1, 1, 1}); Selection carved = block.differenceWith(bite); CHECK_EQ(carved.count(), 64u - 8u); CHECK(!carved.contains({0, 0, 0})); CHECK(carved.contains({2, 0, 0})); CHECK(carved.min() == Vec3i(0, 0, 0)); } VF_TEST(maskNegationFlipsEveryCellInsideTheBoundingBox) { Selection sphere = Selection::sphere({0, 0, 0}, 3); Selection hole = sphere.inverted(); CHECK_EQ(sphere.count() + hole.count(), 7u * 7u * 7u); CHECK(hole.contains({3, 3, 3})); CHECK(!hole.contains({0, 0, 0})); CHECK(sphere.intersectWith(hole).empty()); CHECK_EQ(sphere.unionWith(hole).count(), 7u * 7u * 7u); } VF_TEST(dilationGrowsOneCellIntoASixNeighbourCrossAndKeepsGoing) { Selection seed = Selection::box({0, 0, 0}, {0, 0, 0}); CHECK_EQ(seed.count(), 1u); Selection once = seed.expanded(1); CHECK_EQ(once.count(), 7u); CHECK(once.contains({1, 0, 0})); CHECK(once.contains({0, -1, 0})); CHECK(!once.contains({1, 1, 0})); CHECK(once.min() == Vec3i(-1, -1, -1));