mirror of
https://github.com/sbrl/Minetest-WorldEditAdditions.git
synced 2024-11-22 15:33:00 +00:00
Implement (untested!) manip interface on top of the raw convolutional code
This commit is contained in:
parent
9b9a471aa8
commit
6dac57c53e
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
|
||||
|
|
17
worldeditadditions/utils/tables.lua
Normal file
17
worldeditadditions/utils/tables.lua
Normal file
|
@ -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
|
Loading…
Reference in a new issue