feat: expand Selection

This commit is contained in:
dualfroz committed 2026-09-09 17:28:02 +02:00
1 parent 03b0fa187b
commit f61d9eed00
1 file changed
+164
+164
View File
@@ -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<Vec3i, Vec3iHash> seen;
std::deque<Vec3i> frontier;
std::vector<Vec3i> 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<std::size_t>(Block::Count), 0u);
summary.dims = cells.dims();
summary.boundingVolume = static_cast<long long>(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<std::size_t>(block)];
if (isAir(block)) {
++summary.airCount;
} else {
++summary.solidCount;
}
});
return summary;
}
std::vector<Vec3i> Selection::blocks() const { return cells.cells(); }
void Selection::forEach(const std::function<void(const Vec3i&)>& visit) const {
cells.forEach(visit);
}
} // namespace vf