From 83f9ea128d6753437c235decbaa1be193c5f6c13 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 13 Sep 2026 12:39:08 +0200 Subject: [PATCH] test: cover testSelection --- tests/testSelection.cpp | 165 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/tests/testSelection.cpp b/tests/testSelection.cpp index d4346c1..c66383a 100644 --- a/tests/testSelection.cpp +++ b/tests/testSelection.cpp @@ -155,3 +155,168 @@ VF_TEST(dilationGrowsOneCellIntoASixNeighbourCrossAndKeepsGoing) { CHECK(!once.contains({1, 1, 0})); CHECK(once.min() == Vec3i(-1, -1, -1)); + // Manhattan ball of radius two: 1 + 6 + 18 cells. + Selection twice = seed.expanded(2); + CHECK_EQ(twice.count(), 25u); + CHECK(twice.contains({1, 1, 0})); + CHECK(twice.contains({2, 0, 0})); + CHECK(!twice.contains({1, 1, 1})); +} + +VF_TEST(erosionPeelsExactlyOneShellOffASolidBoxPerStep) { + Selection box = Selection::box({0, 0, 0}, {4, 4, 4}); + CHECK_EQ(box.count(), 125u); + + Selection once = box.contracted(1); + CHECK_EQ(once.count(), 27u); + CHECK(once.contains({2, 2, 2})); + CHECK(!once.contains({0, 2, 2})); + + Selection twice = box.contracted(2); + CHECK_EQ(twice.count(), 1u); + CHECK(twice.contains({2, 2, 2})); + + Selection gone = box.contracted(3); + CHECK(gone.empty()); +} + +VF_TEST(expandingThenContractingASolidBoxReturnsTheSameCells) { + Selection box = Selection::box({-2, 7, 3}, {5, 9, 8}); + Selection roundTrip = box.expanded(2).contracted(2); + CHECK_EQ(roundTrip.count(), box.count()); + CHECK(roundTrip.mask().sameCells(box.mask())); +} + +VF_TEST(outlineKeepsOnlyCellsWithANeighbourOutsideTheSelection) { + Selection box = Selection::box({0, 0, 0}, {4, 4, 4}); + Selection shell = box.outline(); + CHECK_EQ(shell.count(), 125u - 27u); + CHECK(shell.contains({0, 0, 0})); + CHECK(shell.contains({2, 4, 2})); + CHECK(!shell.contains({2, 2, 2})); + + Selection single = Selection::box({9, 9, 9}, {9, 9, 9}); + CHECK_EQ(single.outline().count(), 1u); +} + +VF_TEST(shiftingASelectionMovesEveryCellAndKeepsItsShape) { + Selection sphere = Selection::sphere({0, 0, 0}, 3); + Selection moved = sphere.shifted({100, -50, 7}); + CHECK_EQ(moved.count(), sphere.count()); + CHECK(moved.min() == Vec3i(97, -53, 4)); + sphere.forEach([&](const Vec3i& cell) { + CHECK(moved.contains({cell.x + 100, cell.y - 50, cell.z + 7})); + }); +} + +VF_TEST(magicWandFollowsConnectedCellsOfTheSameBlockOnly) { + World world; + for (int z = 0; z < 3; ++z) + for (int x = 0; x < 3; ++x) world.setBlock(x, 0, z, Block::Stone); + world.setBlock(1, 0, 1, Block::Dirt); + world.setBlock(10, 0, 10, Block::Stone); + + Selection wand = Selection::magicWand(world, {0, 0, 0}, 1000u); + CHECK(wand.shape() == SelectionShape::Wand); + CHECK_EQ(wand.count(), 8u); + CHECK(!wand.truncated()); + CHECK(!wand.contains({1, 0, 1})); + CHECK(!wand.contains({10, 0, 10})); + CHECK(wand.contains({2, 0, 2})); + + Selection islandWand = Selection::magicWand(world, {10, 0, 10}, 1000u); + CHECK_EQ(islandWand.count(), 1u); +} + +VF_TEST(magicWandStopsAtTheCellLimitAndSaysSo) { + World world = slabWorld(40, 40, 0, Block::Stone); + Selection capped = Selection::magicWand(world, {0, 0, 0}, 50u); + CHECK_EQ(capped.count(), 50u); + CHECK(capped.truncated()); + + Selection whole = Selection::magicWand(world, {0, 0, 0}, 40u * 40u); + CHECK_EQ(whole.count(), 1600u); + CHECK(!whole.truncated()); +} + +VF_TEST(magicWandStaysInsideTheBoundingLimitItWasGiven) { + World world = slabWorld(20, 20, 0, Block::Stone); + Selection boxed = + Selection::magicWandWithin(world, {5, 0, 5}, 10000u, {2, 0, 2}, {6, 0, 6}); + CHECK_EQ(boxed.count(), 25u); + CHECK(boxed.min() == Vec3i(2, 0, 2)); + CHECK(boxed.max() == Vec3i(6, 0, 6)); + CHECK(!boxed.contains({7, 0, 5})); + CHECK_EQ(cellsInsideBox(boxed, {2, 0, 2}, {6, 0, 6}), boxed.count()); +} + +VF_TEST(selectionStatsReportCountsDimensionsAndABlockHistogram) { + World world; + for (int z = 0; z < 4; ++z) + for (int x = 0; x < 4; ++x) { + world.setBlock(x, 0, z, Block::Stone); + world.setBlock(x, 1, z, Block::Dirt); + } + world.setBlock(0, 1, 0, Block::Grass); + + Selection region = Selection::box({0, 0, 0}, {3, 2, 3}); + SelectionStats summary = region.stats(world); + CHECK_EQ(summary.blockCount, 48u); + CHECK(summary.dims == Vec3i(4, 3, 4)); + CHECK_EQ(summary.boundingVolume, 48); + CHECK_EQ(summary.countOf(Block::Stone), 16u); + CHECK_EQ(summary.countOf(Block::Dirt), 15u); + CHECK_EQ(summary.countOf(Block::Grass), 1u); + CHECK_EQ(summary.countOf(Block::Air), 16u); + CHECK_EQ(summary.solidCount, 32u); + CHECK_EQ(summary.airCount, 16u); + CHECK(summary.dominantBlock() == Block::Stone); + CHECK_EQ(summary.distinctBlocks(), 4u); +} + +VF_TEST(anEmptySelectionHasNoCellsAndNoStatistics) { + Selection nothing; + CHECK(nothing.empty()); + CHECK_EQ(nothing.count(), 0u); + CHECK_EQ(nothing.blocks().size(), 0u); + + World world; + SelectionStats summary = nothing.stats(world); + CHECK_EQ(summary.blockCount, 0u); + CHECK_EQ(summary.boundingVolume, 0); + CHECK_EQ(summary.distinctBlocks(), 0u); + CHECK(summary.dominantBlock() == Block::Air); + + CHECK_EQ(nothing.expanded(3).count(), 0u); + CHECK_EQ(nothing.outline().count(), 0u); + CHECK(nothing.unionWith(Selection::box({0, 0, 0}, {1, 1, 1})).count() == 8u); +} + +VF_TEST(setOperationsComposeIntoARingShapedSelection) { + Selection outer = Selection::sphere({0, 0, 0}, 5); + Selection inner = Selection::sphere({0, 0, 0}, 3); + Selection ring = outer.differenceWith(inner); + CHECK_EQ(ring.count(), outer.count() - inner.count()); + CHECK(ring.contains({5, 0, 0})); + CHECK(!ring.contains({0, 0, 0})); + + Selection cut = Selection::box({-10, 1, -10}, {10, 10, 10}); + Selection lowerRing = ring.differenceWith(cut); + lowerRing.forEach([&](const Vec3i& cell) { CHECK(cell.y <= 0); }); + CHECK(lowerRing.count() < ring.count()); + CHECK(lowerRing.count() > 0u); +} + +VF_TEST(trimmingAMaskShrinksTheBoundingBoxWithoutLosingCells) { + SelectionMask sparse(Vec3i{0, 0, 0}, Vec3i{16, 16, 16}); + sparse.add({4, 5, 6}); + sparse.add({7, 5, 6}); + CHECK_EQ(sparse.cellCount(), 4096u); + + SelectionMask tight = sparse.trimmed(); + CHECK_EQ(tight.count(), 2u); + CHECK(tight.min() == Vec3i(4, 5, 6)); + CHECK(tight.max() == Vec3i(7, 5, 6)); + CHECK_EQ(tight.cellCount(), 4u); + CHECK(tight.sameCells(sparse)); +}