Add //layers command

Also add aliases //naturalise and //naturalize
This commit is contained in:
Starbeamrainbowlabs 2020-06-11 00:38:16 +01:00
parent 905ccc2d50
commit 75a17ed64f
No known key found for this signature in database
GPG Key ID: 1BE5172E637709C2
9 changed files with 134 additions and 12 deletions

@ -210,7 +210,7 @@ Here are all the above examples together:
```
### `//convolve <kernel> [<width>[,<height>]] [<sigma>]`
Advanced version of `//smooth` from we_env, and so far the _only_ WorldEditAdditions command to have any aliases (`//smoothadv` and `//conv`).
Advanced version of `//smooth` from we_env, and one of the few WorldEditAdditions commands to have any aliases (`//smoothadv` and `//conv`).
Extracts a heightmap from the defined region and then proceeds to [convolve](https://en.wikipedia.org/wiki/Kernel_(image_processing)) over it with the specified kernel. The kernel can be thought of as the filter that will be applied to the heightmap. Once done, the newly convolved heightmap is applied to the terrain.

@ -15,6 +15,7 @@ dofile(worldeditadditions.modpath.."/utils/tables.lua")
dofile(worldeditadditions.modpath.."/utils.lua")
dofile(worldeditadditions.modpath.."/lib/floodfill.lua")
dofile(worldeditadditions.modpath.."/lib/overlay.lua")
dofile(worldeditadditions.modpath.."/lib/layers.lua")
dofile(worldeditadditions.modpath.."/lib/ellipsoid.lua")
dofile(worldeditadditions.modpath.."/lib/torus.lua")
dofile(worldeditadditions.modpath.."/lib/walls.lua")

@ -0,0 +1,58 @@
--- Overlap command. Places a specified node on top of each column.
-- @module worldeditadditions.overlay
function worldeditadditions.layers(pos1, pos2, node_weights)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
-- pos2 will always have the highest co-ordinates now
-- Fetch the nodes in the specified area
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
local data = manip:get_data()
local node_id_ignore = minetest.get_content_id("ignore")
local node_ids, node_ids_count = worldeditadditions.unwind_node_list(node_weights)
-- minetest.log("action", "pos1: " .. worldeditadditions.vector.tostring(pos1))
-- minetest.log("action", "pos2: " .. worldeditadditions.vector.tostring(pos2))
for i,v in ipairs(node_ids) do
print("[layer] i", i, "node id", v)
end
-- z y x is the preferred loop order, but that isn't really possible here
local changes = { replaced = 0, skipped_columns = 0 }
for z = pos2.z, pos1.z, -1 do
for x = pos2.x, pos1.x, -1 do
local next_index = 1 -- We use table.insert() in make_weighted
local placed_node = false
for y = pos2.y, pos1.y, -1 do
local i = area:index(x, y, z)
local is_air = worldeditadditions.is_airlike(data[i])
local is_ignore = data[i] == node_id_ignore
if not is_air and not is_ignore then
-- It's not an airlike node or something else odd
data[i] = node_ids[next_index]
next_index = next_index + 1
changes.replaced = changes.replaced + 1
-- If we're done replacing nodes in this column, move to the next one
if next_index > #node_ids then
break
end
end
end
if not placed_node then
changes.skipped_columns = changes.skipped_columns + 1
end
end
end
-- Save the modified nodes back to disk & return
worldedit.manip_helpers.finish(manip, data)
return changes
end

@ -9,7 +9,6 @@ function worldeditadditions.overlay(pos1, pos2, node_weights)
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
local data = manip:get_data()
local node_id_air = minetest.get_content_id("air")
local node_id_ignore = minetest.get_content_id("ignore")
local node_ids, node_ids_count = worldeditadditions.make_weighted(node_weights)
@ -28,7 +27,7 @@ function worldeditadditions.overlay(pos1, pos2, node_weights)
for y = pos2.y, pos1.y, -1 do
local i = area:index(x, y, z)
local is_air = data[i] == node_id_air
local is_air = worldeditadditions.is_airlike(data[i])
if not is_air then -- wielded_light nodes are airlike too
local this_node_name = minetest.get_name_from_content_id(data[i])
is_air = is_air or worldeditadditions.string_starts(this_node_name, "wielded_light")

@ -6,10 +6,23 @@ function worldeditadditions.make_weighted(tbl)
local next_id = minetest.get_content_id(node_name)
print("[make_weighted] seen "..node_name.." @ weight "..weight.." → id "..next_id)
for i = 1, weight do
table.insert(
result,
next_id
)
table.insert(result, next_id)
end
end
return result, #result
end
--- Unwinds a list of { node = string, weight = number } tables into a list of node ids.
-- The node ids will be repeated multiple times according to their weights
-- (e.g. an entry with a weight of 2 will be repeated twice).
-- @param list table[] The list to unwind.
-- @return number[],number The unwound list of node ids, follows by the number of node ids in total.
function worldeditadditions.unwind_node_list(list)
local result = {}
for i,item in ipairs(list) do
local node_id = minetest.get_content_id(item.node)
for i = 1, item.weight do
table.insert(result, node_id)
end
end
return result, #result
@ -41,6 +54,10 @@ function worldeditadditions.is_airlike(id)
return true
end
-- Just in case
if worldeditadditions.string_starts(this_node_name, "wielded_light") then
return true
end
-- Just in case
return false
end

@ -155,8 +155,10 @@ end
--- Parses a list of strings as a list of weighted nodes - e.g. like in the //mix command.
-- @param parts string[] The list of strings to parse (try worldeditadditions.split)
-- @param as_list bool If true, then table.insert() successive { node = string, weight = number } subtables when parsing instead of populating as an associative array.
-- @returns table A table in the form node_name => weight.
function worldeditadditions.parse_weighted_nodes(parts)
function worldeditadditions.parse_weighted_nodes(parts, as_list)
if as_list == nil then as_list = false end
local MODE_EITHER = 1
local MODE_NODE = 2
@ -177,23 +179,31 @@ function worldeditadditions.parse_weighted_nodes(parts)
print("mode: either");
local chance = tonumber(part)
if not chance then
print("not a chance, trying a node name")
local node_name = worldedit.normalize_nodename(part)
if not node_name then
return false, "Error: Invalid number '"..chance.."'"
end
if last_node_name then
result[last_node_name] = 1
if as_list then table.insert(result, { node = last_node_name, weight = 1 })
else result[last_node_name] = 1 end
end
last_node_name = node_name
mode = MODE_EITHER
else
result[last_node_name] = math.floor(chance)
print("it's a chance: ", chance, "for", last_node_name)
chance = math.floor(chance)
if as_list then table.insert(result, { node = last_node_name, weight = chance })
else result[last_node_name] = chance end
last_node_name = nil
mode = MODE_NODE
end
end
end
if last_node_name then
result[last_node_name] = 1
print("caught trailing node name: ", last_node_name)
if as_list then table.insert(result, { node = last_node_name, weight = 1 })
else result[last_node_name] = 1 end
end
return true, result

@ -69,7 +69,7 @@ worldedit.register_command("convolve", {
minetest.log("action", name.." used //convolve at "..worldeditadditions.vector.tostring(worldedit.pos1[name]).." - "..worldeditadditions.vector.tostring(worldedit.pos2[name])..", adding "..stats.added.." nodes and removing "..stats.removed.." nodes in "..time_taken.."s")
return true, "Added "..stats.added.." and removed "..stats.removed.." nodes"
return true, "Added "..stats.added.." and removed "..stats.removed.." nodes in " .. time_taken .. "s"
end
})

@ -0,0 +1,36 @@
-- ██████ ██ ██ ███████ ██████ ██ █████ ██ ██
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ██ ██ █████ ██████ ██ ███████ ████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██
worldedit.register_command("layers", {
params = "[<node_name_1> [<layer_count_1>]] [<node_name_2> [<layer_count_2>]] ...",
description = "Replaces the topmost non-airlike nodes with layers of the given nodes from top to bottom. Like WorldEdit for MC's //naturalize command. Default: dirt_with_grass dirt 3",
privs = { worldedit = true },
require_pos = 2,
parse = function(params_text)
if not params_text or params_text == "" then
params_text = "dirt_with_grass dirt 3"
end
local success, node_list = worldeditadditions.parse_weighted_nodes(
worldeditadditions.split(params_text, "%s+", false),
true
)
return success, node_list
end,
nodes_needed = function(name)
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
end,
func = function(name, node_list)
local start_time = os.clock()
local changes = worldeditadditions.layers(worldedit.pos1[name], worldedit.pos2[name], node_list)
local time_taken = os.clock() - start_time
minetest.log("action", name .. " used //layers at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. changes.replaced .. " nodes and skipping " .. changes.skipped_columns .. " columns in " .. time_taken .. "s")
return true, changes.replaced .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped in " .. time_taken .. "s"
end
})
worldedit.alias_command("naturalise", "layers")
worldedit.alias_command("naturalize", "layers")

@ -19,6 +19,7 @@ dofile(we_c.modpath.."/multi.lua")
dofile(we_c.modpath.."/commands/floodfill.lua")
dofile(we_c.modpath.."/commands/overlay.lua")
dofile(we_c.modpath.."/commands/layers.lua")
dofile(we_c.modpath.."/commands/ellipsoid.lua")
dofile(we_c.modpath.."/commands/torus.lua")
dofile(we_c.modpath.."/commands/walls.lua")