--- 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