133 lines
4.4 KiB
Lua
133 lines
4.4 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.contains(queue, value)
|
|
for i=queue.first,queue.last do
|
|
if queue[i] == value then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
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
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- These really should be in a utilities file, but Lua is being stupid and preventing access to it (and minetest is also being stupid, as we can't modularise our code the way you ought to be able to - or at least the documentation on dofile() is so poor I've no idea at this point)
|
|
local function vector2string(v)
|
|
return "(" .. v.x ..", " .. v.y ..", " .. v.z ..")"
|
|
end
|
|
|
|
local clock = os.clock
|
|
function sleep(n) -- seconds
|
|
local t0 = clock()
|
|
while clock() - t0 <= n do end
|
|
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: " .. vector2string(pos1))
|
|
minetest.log("action", "pos2: " .. vector2string(pos2))
|
|
|
|
-- 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
|
|
local xplus = cur + 1 -- +X
|
|
if data[xplus] == search_id and not Queue.contains(remaining_nodes, xplus) then
|
|
-- minetest.log("action", "[floodfill] [+X] index " .. xplus .. " is a " .. data[xplus] .. ", searching for a " .. search_id)
|
|
Queue.enqueue(remaining_nodes, xplus)
|
|
end
|
|
local xminus = cur - 1 -- -X
|
|
if data[xminus] == search_id and not Queue.contains(remaining_nodes, xminus) then
|
|
-- minetest.log("action", "[floodfill] [-X] index " .. xminus .. " is a " .. data[xminus] .. ", searching for a " .. search_id)
|
|
Queue.enqueue(remaining_nodes, xminus)
|
|
end
|
|
local zplus = cur + area.zstride -- +Z
|
|
if data[zplus] == search_id and not Queue.contains(remaining_nodes, zplus) then
|
|
-- minetest.log("action", "[floodfill] [+Z] index " .. zplus .. " is a " .. data[zplus] .. ", searching for a " .. search_id)
|
|
Queue.enqueue(remaining_nodes, zplus)
|
|
end
|
|
local zminus = cur - area.zstride -- -Z
|
|
if data[zminus] == search_id and not Queue.contains(remaining_nodes, zminus) then
|
|
-- minetest.log("action", "[floodfill] [-Z] index " .. zminus .. " is a " .. data[zminus] .. ", searching for a " .. search_id)
|
|
Queue.enqueue(remaining_nodes, zminus)
|
|
end
|
|
local yminus = cur - area.ystride -- -Y
|
|
if data[yminus] == search_id and not Queue.contains(remaining_nodes, yminus) then
|
|
-- minetest.log("action", "[floodfill] [-Y] index " .. yminus .. " is a " .. data[yminus] .. ", searching for a " .. search_id)
|
|
Queue.enqueue(remaining_nodes, yminus)
|
|
end
|
|
|
|
count = count + 1
|
|
end
|
|
|
|
-- Save the modified nodes back to disk & return
|
|
worldedit.manip_helpers.finish(manip, data)
|
|
|
|
return count
|
|
end
|
|
|
|
|
|
return floodfill
|