Implement (untested!) manip interface on top of the raw convolutional code

This commit is contained in:
Starbeamrainbowlabs 2020-06-09 20:43:29 +01:00
parent 9b9a471aa8
commit 6dac57c53e
No known key found for this signature in database
GPG Key ID: 1BE5172E637709C2
5 changed files with 168 additions and 1 deletions

@ -10,6 +10,7 @@ worldeditadditions.modpath = minetest.get_modpath("worldeditadditions")
dofile(worldeditadditions.modpath.."/utils/strings.lua")
dofile(worldeditadditions.modpath.."/utils/numbers.lua")
dofile(worldeditadditions.modpath.."/utils/nodes.lua")
dofile(worldeditadditions.modpath.."/utils/tables.lua")
dofile(worldeditadditions.modpath.."/utils.lua")
dofile(worldeditadditions.modpath.."/lib/floodfill.lua")

@ -4,3 +4,86 @@ dofile(worldeditadditions.modpath.."/lib/conv/kernels.lua")
dofile(worldeditadditions.modpath.."/lib/conv/kernel_gaussian.lua")
dofile(worldeditadditions.modpath.."/lib/conv/convolve.lua")
--- Creates a new kernel.
-- Note that the gaussian kernel only allows for creating a square kernel.
-- arg is only used by the gaussian kernel as the sigma value (default: 2)
-- @param name string The name of the kernel to create (possible values: box, pascal, gaussian).
-- @param width number The width of the kernel to create (must be an odd integer).
-- @param height number The height of the kernel to create (must be an odd integer).
-- @param arg number The argument to pass when creating the kernel. Currently only used by gaussian kernel as the sigma value.
function worldeditadditions.get_kernel(name, width, height, arg)
if width % 2 ~= 1 then
return false, "Error: The width must be an odd integer.";
end
if height % 2 ~= 1 then
return false, "Error: The height must be an odd integer";
end
if name == "box" then
return true, worldeditadditions.conv.kernel_box(width, height)
elseif name == "pascal" then
return true, worldeditadditions.conv.kernel_pascal(width, height, true)
elseif name == "gaussian" then
if width ~= height then
return false, "Error: When using a gaussian kernel the width and height must be identical."
end
-- Default to sigma = 2
if arg == nil then arg = 2 end
local success, result = worldeditadditions.conv.kernel_gaussian(width, arg)
return success, result
end
end
function worldeditadditions.convolve(pos1, pos2, kernel, kernel_size)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
local data = manip:get_data()
local stats = { added = 0, removed = 0 }
local node_id_air = minetest.get_content_id("air")
local heightmap = worldeditadditions.make_heightmap(pos1, pos2, manip, area, data)
local heightmap_conv = worldeditadditions.shallowcopy(heightmap)
local heightmap_size = {}
heightmap_size[0] = pos2.z - pos1.z
heightmap_size[1] = pos2.x - pos1.x
worldeditadditions.conv.convolve(
heightmap_conv,
heightmap_size,
kernel,
kernel_size
)
for z = heightmap_size[0], 0, -1 do
for x = heightmap_size[1], 0, -1 do
local hi = z*heightmap_size[1] + x
local diff = heightmap_conv[hi] - heightmap[hi]
-- Lua doesn't have a continue statement :-/
if diff ~= 0 then
local node_id = data[area:index(pos1.x + x, pos1.y + heightmap[hi], pos1.z + z)]
if diff > 0 then
stats.added = stats.added + diff
for y = pos1.y + heightmap[hi], pos1.y + heightmap_conv[hi], 1 do
data[area:index(pos1.x + x, y, pos1.z + z)] = node_id
end
else
stats.removed = stats.removed + math.abs(diff)
for y = pos1.y + heightmap_conv[hi], pos1.y + heightmap[hi], 1 do
data[area:index(pos1.x + x, y, pos1.z + z)] = node_id_air
end
end
end
end
end
worldedit.manip_helpers.finish(manip, data)
return true, stats
end

@ -47,5 +47,5 @@ function worldeditadditions.conv.kernel_gaussian(dimension, sigma)
kernel[k] = kernel[k] / sum
end
return kernel
return true, kernel
end

@ -14,3 +14,69 @@ function worldeditadditions.make_weighted(tbl)
end
return result, #result
end
local node_id_air = minetest.get_content_id("air")
local node_id_ignore = minetest.get_content_id("ignore")
--- Determines whether the given node/content id is an airlike node or not.
-- @param id number The content/node id to check.
-- @return bool Whether the given node/content id is an airlike node or not.
function worldeditadditions.is_airlike(id)
-- Do a fast check against air and ignore
if id == node_id_air then
return true
elseif id == node_id_ignore then -- ignore = not loaded yet IIRC (so it could be anything)
return false
end
local name = minetest.get_name_from_content_id(id)
-- If the node isn't registered, then it might not be an air node
if not minetest.registered_nodes[id] then return false end
if minetest.registered_nodes[id].sunlight_propagates == true then
return true
end
-- Check for membership of the airlike group
local airlike_value = minetest.get_item_group(name, "airlike")
if airlike_value ~= nil and airlike_value > 0 then
return true
end
-- Just in case
return false
end
--- Given a manip object and associates, generates a 2D x/z heightmap.
-- Note that pos1 and pos2 should have already been pushed through
-- worldedit.sort_pos(pos1, pos2) before passing them to this function.
-- @param pos1 Vector Position 1 of the region to operate on
-- @param pos2 Vector Position 2 of the region to operate on
-- @param manip VoxelManip The VoxelManip object.
-- @param area area The associated area object.
-- @param data table The associated data object.
-- @return table The ZERO-indexed heightmap data (as 1 single flat array).
function worldeditadditions.make_heightmap(pos1, pos2, manip, area, data)
-- z y x (in reverse for little-endian machines) is the preferred loop order, but that isn't really possible here
local heightmap = {}
local hi = 0
local changes = { updated = 0, skipped_columns = 0 }
for z = pos1.z, pos2.z, 1 do
for x = pos1.x, pos2.x, 1 do
local found_node = false
-- Scan each column top to bottom
for y = pos2.y, pos1.y, -1 do
local i = area:index(x, y, z)
if not worldeditadditions.is_airlike(data[i]) then
-- It's the first non-airlike node in this column
heightmap[hi] = pos1.y - y
found_node = true
break
end
end
if not found_node then heightmap[hi] = -1 end
i = i + 1
end
end
return heightmap
end

@ -0,0 +1,17 @@
--- Shallow clones a table.
-- @source http://lua-users.org/wiki/CopyTable
-- @param orig table The table to clone.
-- @return table The cloned table.
function worldeditadditions.shallowcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in pairs(orig) do
copy[orig_key] = orig_value
end
else -- number, string, boolean, etc
copy = orig
end
return copy
end