From f61d9eed00a8dd2cd8e61f0e0d0def803885117f Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 17:28:02 +0200 Subject: [PATCH] feat: expand Selection --- core/src/Selection.cpp | 164 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/core/src/Selection.cpp b/core/src/Selection.cpp index bc08cab..10c9e4c 100644 --- a/core/src/Selection.cpp +++ b/core/src/Selection.cpp @@ -306,3 +306,167 @@ Selection Selection::ellipsoid(const Vec3i& center, const Vec3i& radii) { out.kind = SelectionShape::Ellipsoid; out.cells = SelectionMask(lo, extentOf(lo, hi)); + double halfX = rx + 0.5; + double halfY = ry + 0.5; + double halfZ = rz + 0.5; + for (int y = lo.y; y <= hi.y; ++y) + for (int z = lo.z; z <= hi.z; ++z) + for (int x = lo.x; x <= hi.x; ++x) { + double nx = (x - center.x) / halfX; + double ny = (y - center.y) / halfY; + double nz = (z - center.z) / halfZ; + if (nx * nx + ny * ny + nz * nz <= 1.0) out.cells.add({x, y, z}); + } + return out; +} + +Selection Selection::cylinder(const Vec3i& center, int radius, int height) { + int r = std::max(radius, 0); + int h = std::max(height, 1); + Vec3i lo{center.x - r, center.y, center.z - r}; + Vec3i hi{center.x + r, center.y + h - 1, center.z + r}; + + Selection out; + out.kind = SelectionShape::Cylinder; + out.cells = SelectionMask(lo, extentOf(lo, hi)); + + double limit = (r + 0.5) * (r + 0.5); + for (int y = lo.y; y <= hi.y; ++y) + for (int z = lo.z; z <= hi.z; ++z) + for (int x = lo.x; x <= hi.x; ++x) { + double dx = x - center.x; + double dz = z - center.z; + if (dx * dx + dz * dz <= limit) out.cells.add({x, y, z}); + } + return out; +} + +Selection Selection::hollowBox(const Vec3i& cornerA, const Vec3i& cornerB, int thickness) { + int wall = std::max(thickness, 1); + Vec3i lo = minCorner(cornerA, cornerB); + Vec3i hi = maxCorner(cornerA, cornerB); + + Selection out; + out.kind = SelectionShape::HollowBox; + out.cells = SelectionMask(lo, extentOf(lo, hi)); + + for (int y = lo.y; y <= hi.y; ++y) + for (int z = lo.z; z <= hi.z; ++z) + for (int x = lo.x; x <= hi.x; ++x) { + bool onWall = (x - lo.x) < wall || (hi.x - x) < wall || (y - lo.y) < wall || + (hi.y - y) < wall || (z - lo.z) < wall || (hi.z - z) < wall; + if (onWall) out.cells.add({x, y, z}); + } + return out; +} + +Selection Selection::fromMask(const SelectionMask& mask) { return derived(mask); } + +Selection Selection::magicWand(const World& world, const Vec3i& seed, std::size_t maxCells) { + Vec3i lo{seed.x - wandUnboundedReach, seed.y - wandUnboundedReach, seed.z - wandUnboundedReach}; + Vec3i hi{seed.x + wandUnboundedReach, seed.y + wandUnboundedReach, seed.z + wandUnboundedReach}; + return magicWandWithin(world, seed, maxCells, lo, hi); +} + +Selection Selection::magicWandWithin(const World& world, const Vec3i& seed, std::size_t maxCells, + const Vec3i& limitMin, const Vec3i& limitMax) { + Selection out; + out.kind = SelectionShape::Wand; + if (maxCells == 0u) return out; + + Vec3i lo = minCorner(limitMin, limitMax); + Vec3i hi = maxCorner(limitMin, limitMax); + auto insideLimit = [&](const Vec3i& cell) { + return cell.x >= lo.x && cell.x <= hi.x && cell.y >= lo.y && cell.y <= hi.y && + cell.z >= lo.z && cell.z <= hi.z; + }; + if (!insideLimit(seed)) return out; + + Block wanted = world.blockAt(seed.x, seed.y, seed.z); + std::unordered_set seen; + std::deque frontier; + std::vector found; + seen.insert(seed); + frontier.push_back(seed); + + while (!frontier.empty()) { + if (found.size() >= maxCells) { + out.hitLimit = true; + break; + } + Vec3i cell = frontier.front(); + frontier.pop_front(); + found.push_back(cell); + for (const Vec3i& offset : faceOffsets) { + Vec3i next = cell + offset; + if (!insideLimit(next)) continue; + if (seen.count(next) != 0u) continue; + if (world.blockAt(next.x, next.y, next.z) != wanted) continue; + seen.insert(next); + frontier.push_back(next); + } + } + + if (found.empty()) return out; + Vec3i foundLo = found.front(); + Vec3i foundHi = found.front(); + for (const Vec3i& cell : found) { + foundLo = minCorner(foundLo, cell); + foundHi = maxCorner(foundHi, cell); + } + out.cells = SelectionMask(foundLo, extentOf(foundLo, foundHi)); + for (const Vec3i& cell : found) out.cells.add(cell); + return out; +} + +Selection Selection::unionWith(const Selection& other) const { + return derived(cells.unionWith(other.cells)); +} + +Selection Selection::intersectWith(const Selection& other) const { + return derived(cells.intersectWith(other.cells)); +} + +Selection Selection::differenceWith(const Selection& other) const { + return derived(cells.differenceWith(other.cells)); +} + +Selection Selection::inverted() const { return derived(cells.inverted()); } + +Selection Selection::expanded(int radius) const { return derived(cells.dilated(radius)); } + +Selection Selection::contracted(int radius) const { return derived(cells.eroded(radius)); } + +Selection Selection::outline() const { return derived(cells.outline()); } + +Selection Selection::shifted(const Vec3i& delta) const { + Selection out = *this; + out.cells = cells.shifted(delta); + return out; +} + +SelectionStats Selection::stats(const World& world) const { + SelectionStats summary; + summary.histogram.assign(static_cast(Block::Count), 0u); + summary.dims = cells.dims(); + summary.boundingVolume = static_cast(cells.cellCount()); + summary.blockCount = cells.count(); + cells.forEach([&](const Vec3i& cell) { + Block block = world.blockAt(cell.x, cell.y, cell.z); + ++summary.histogram[static_cast(block)]; + if (isAir(block)) { + ++summary.airCount; + } else { + ++summary.solidCount; + } + }); + return summary; +} + +std::vector Selection::blocks() const { return cells.cells(); } + +void Selection::forEach(const std::function& visit) const { + cells.forEach(visit); +} + +} // namespace vf