Minetest-WorldEditAdditions/worldeditadditions/floodfill.lua

100 lines
2.9 KiB
Lua

--- Flood-fill command for complex lakes etc.
-- @module worldeditadditions.floodfill
-------------------------------------------------------------------------------
--- A Queue implementation, taken & adapted from https://www.lua.org/pil/11.4.html
-- @submodule worldeditadditions.utils.queue
local Queue = {}
function Queue.new()
return { first = 0, last = -1 }
end
function Queue.enqueue(queue, value)
local new_last = queue.last + 1
queue.last = new_last
queue[new_last] = value
end
function Queue.is_empty(queue)
return queue.first > queue.last
end
function Queue.dequeue(queue)
local first = queue.first
if Queue.is_empty(queue) then
error("Error: The queue is empty!")
end
local value = queue[first]
queue[first] = nil -- Help the garbage collector out
queue.first = first + 1
return value
end
-------------------------------------------------------------------------------
function worldedit.floodfill(start_pos, radius, replace_node)
-- Calculate the area we want to modify
local pos1 = vector.add(start_pos, { x = radius, y = 0, z = radius })
local pos2 = vector.subtract(start_pos, { x = radius, y = radius, z = radius })
pos1, pos2 = worldedit.sort_pos(pos1, pos2) -- Just in case
minetest.log("action", "radius: " .. radius)
minetest.log("action", "pos1: (" .. pos1.x .. ", " .. pos1.y .. ", " .. pos1.z .. ")")
minetest.log("action", "pos2: (" .. pos2.x .. ", " .. pos2.y .. ", " .. pos2.z .. ")")
-- Fetch the nodes in the specified area
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
local data = manip:get_data()
-- Setup for the floodfill operation itself
local start_pos_index = area:index(start_pos.x, start_pos.y, start_pos.z);
local search_id = data[start_pos_index]
local replace_id = minetest.get_content_id(replace_node)
minetest.log("action", "ids: " .. search_id .. " -> " .. replace_id)
local count = 0
local remaining_nodes = Queue.new()
Queue.enqueue(remaining_nodes, start_pos_index)
-- Do the floodfill
while Queue.is_empty(remaining_nodes) == false do
local cur = Queue.dequeue(remaining_nodes)
-- TODO: Check distance from start_pos
-- Replace this node
data[cur] = replace_id
count = count + 1
-- Check all the nearby nodes
-- We don't need to go upwards here, since we're filling in lake-style
if data[cur + 1] == search_id then -- +X
Queue.enqueue(remaining_nodes, cur + 1)
end
if data[cur - 1] == search_id then -- -X
Queue.enqueue(remaining_nodes, cur - 1)
end
if data[cur + area.zstride] == search_id then -- +Z
Queue.enqueue(remaining_nodes, cur + area.zstride)
end
if data[cur - area.zstride] == search_id then -- -Z
Queue.enqueue(remaining_nodes, cur - area.zstride)
end
if data[cur - area.ystride] == search_id then -- -Y
Queue.enqueue(remaining_nodes, cur - area.ystride)
end
end
-- Save the modified nodes back to disk & return
worldedit.manip_helpers.finish(manip, data)
return count
end
return floodfill