From 6dac57c53e28711a0c6fbce5c1213fa5bcc5b982 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 9 Jun 2020 20:43:29 +0100 Subject: [PATCH] Implement (untested!) manip interface on top of the raw convolutional code --- worldeditadditions/init.lua | 1 + .../lib/convolution/convolution.lua | 83 +++++++++++++++++++ .../lib/convolution/kernel_gaussian.lua | 2 +- worldeditadditions/utils/nodes.lua | 66 +++++++++++++++ worldeditadditions/utils/tables.lua | 17 ++++ 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 worldeditadditions/utils/tables.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 98932be..e00e5da 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -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") diff --git a/worldeditadditions/lib/convolution/convolution.lua b/worldeditadditions/lib/convolution/convolution.lua index b68663f..75fa928 100644 --- a/worldeditadditions/lib/convolution/convolution.lua +++ b/worldeditadditions/lib/convolution/convolution.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 diff --git a/worldeditadditions/lib/convolution/kernel_gaussian.lua b/worldeditadditions/lib/convolution/kernel_gaussian.lua index d4025a9..ef2c239 100644 --- a/worldeditadditions/lib/convolution/kernel_gaussian.lua +++ b/worldeditadditions/lib/convolution/kernel_gaussian.lua @@ -47,5 +47,5 @@ function worldeditadditions.conv.kernel_gaussian(dimension, sigma) kernel[k] = kernel[k] / sum end - return kernel + return true, kernel end diff --git a/worldeditadditions/utils/nodes.lua b/worldeditadditions/utils/nodes.lua index 8350d26..3677fe4 100644 --- a/worldeditadditions/utils/nodes.lua +++ b/worldeditadditions/utils/nodes.lua @@ -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 diff --git a/worldeditadditions/utils/tables.lua b/worldeditadditions/utils/tables.lua new file mode 100644 index 0000000..c41b47d --- /dev/null +++ b/worldeditadditions/utils/tables.lua @@ -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