diff --git a/core/src/Brush.cpp b/core/src/Brush.cpp index 8e89aa0..279eb95 100644 --- a/core/src/Brush.cpp +++ b/core/src/Brush.cpp @@ -150,3 +150,148 @@ void BrushStroke::paintSmooth(const BrushParams& params, const Selection& limit, std::size_t spanZ = static_cast(hi.z - lo.z + 1); std::size_t columns = spanX * spanZ; + std::vector present(columns, 0u); + std::vector columnLow(columns, 0); + std::vector columnHigh(columns, 0); + limit.forEach([&](const Vec3i& cell) { + std::size_t column = static_cast(cell.z - lo.z) * spanX + + static_cast(cell.x - lo.x); + if (present[column] == 0u) { + present[column] = 1u; + columnLow[column] = cell.y; + columnHigh[column] = cell.y; + return; + } + columnLow[column] = std::min(columnLow[column], cell.y); + columnHigh[column] = std::max(columnHigh[column], cell.y); + }); + + std::vector surface(columns, 0); + for (std::size_t column = 0; column < columns; ++column) { + if (present[column] == 0u) continue; + int x = lo.x + static_cast(column % spanX); + int z = lo.z + static_cast(column / spanX); + int top = columnLow[column] - 1; + for (int y = columnHigh[column]; y >= columnLow[column]; --y) { + if (!isAir(world.blockAt(x, y, z))) { + top = y; + break; + } + } + surface[column] = top; + } + + // Averaged over the disc of radius `radius`, from the heights sampled above + // so a column smoothed earlier in the pass cannot drag its neighbours. + std::vector target(columns, 0); + int reach = std::max(params.radius, 1); + for (std::size_t column = 0; column < columns; ++column) { + if (present[column] == 0u) continue; + int cx = static_cast(column % spanX); + int cz = static_cast(column / spanX); + long long sum = 0; + int samples = 0; + for (int dz = -reach; dz <= reach; ++dz) + for (int dx = -reach; dx <= reach; ++dx) { + if (dx * dx + dz * dz > reach * reach) continue; + int nx = cx + dx; + int nz = cz + dz; + if (nx < 0 || nz < 0 || nx >= static_cast(spanX) || + nz >= static_cast(spanZ)) + continue; + std::size_t neighbour = + static_cast(nz) * spanX + static_cast(nx); + if (present[neighbour] == 0u) continue; + sum += surface[neighbour]; + ++samples; + } + target[column] = samples == 0 + ? surface[column] + : static_cast(std::lround(static_cast(sum) / samples)); + } + + for (std::size_t column = 0; column < columns; ++column) { + if (present[column] == 0u) continue; + int x = lo.x + static_cast(column % spanX); + int z = lo.z + static_cast(column / spanX); + int current = surface[column]; + int goal = std::min(std::max(target[column], columnLow[column] - 1), columnHigh[column]); + if (goal == current) continue; + + if (goal < current) { + for (int y = current; y > goal; --y) { + Vec3i cell{x, y, z}; + if (limit.contains(cell)) write(params, cell, Block::Air, outcome); + } + continue; + } + Block fill = current >= columnLow[column] ? world.blockAt(x, current, z) : params.block; + for (int y = current + 1; y <= goal; ++y) { + Vec3i cell{x, y, z}; + if (limit.contains(cell)) write(params, cell, fill, outcome); + } + } +} + +void BrushStroke::paintNeighbourRule(const BrushParams& params, const Selection& limit, bool erode, + BrushResult& outcome) { + std::vector targets; + limit.forEach([&](const Vec3i& cell) { + Block existing = world.blockAt(cell.x, cell.y, cell.z); + if (erode == isAir(existing)) return; + int matching = 0; + for (const Vec3i& offset : faceOffsets) { + Vec3i neighbour = cell + offset; + bool neighbourIsAir = isAir(world.blockAt(neighbour.x, neighbour.y, neighbour.z)); + if (neighbourIsAir == erode) ++matching; + } + if (matching >= params.freeNeighbourThreshold) targets.push_back(cell); + }); + + Block value = erode ? Block::Air : params.block; + for (const Vec3i& cell : targets) write(params, cell, value, outcome); +} + +void BrushStroke::paintNaturalize(const BrushParams& params, const Selection& limit, + BrushResult& outcome) { + if (limit.empty()) return; + Vec3i lo = limit.min(); + Vec3i hi = limit.max(); + for (int z = lo.z; z <= hi.z; ++z) + for (int x = lo.x; x <= hi.x; ++x) { + int depth = -1; + for (int y = hi.y; y >= lo.y; --y) { + Block existing = world.blockAt(x, y, z); + if (isAir(existing)) { + depth = -1; + continue; + } + ++depth; + Vec3i cell{x, y, z}; + if (!limit.contains(cell)) continue; + Block want = params.deepBlock; + if (depth == 0) { + want = params.surfaceBlock; + } else if (depth <= params.soilDepth) { + want = params.soilBlock; + } + write(params, cell, want, outcome); + } + } +} + +void BrushStroke::paintGradient(const BrushParams& params, const Selection& limit, + BrushResult& outcome) { + double reach = params.radius + 0.5; + limit.forEach([&](const Vec3i& cell) { + double dx = cell.x - params.center.x; + double dy = cell.y - params.center.y; + double dz = cell.z - params.center.z; + double distance = std::sqrt(dx * dx + dy * dy + dz * dz); + double mix = std::min(distance / reach, 1.0); + double sample = brushHash(params.seed, cell) / 4294967296.0; + write(params, cell, sample < mix ? params.secondaryBlock : params.block, outcome); + }); +} + +} // namespace vf