mirror of
https://github.com/sbrl/Minetest-WorldEditAdditions.git
synced 2024-11-22 15:33:00 +00:00
Implement slope constraint for //layers, but it isn't tested yet
This commit is contained in:
parent
902d5ddc8b
commit
db830c6633
5 changed files with 105 additions and 33 deletions
|
@ -9,6 +9,7 @@ Note to self: See the bottom of this file for the release template text.
|
||||||
- Add `//airapply` for applying commands only to air nodes in the defined region
|
- Add `//airapply` for applying commands only to air nodes in the defined region
|
||||||
- Use [luacheck](https://github.com/mpeterv/luacheck) to find and fix a large number of bugs and other issues
|
- Use [luacheck](https://github.com/mpeterv/luacheck) to find and fix a large number of bugs and other issues
|
||||||
- Multiple commands: Allow using quotes (`"thing"`, `'thing'`) to quote values when splitting
|
- Multiple commands: Allow using quotes (`"thing"`, `'thing'`) to quote values when splitting
|
||||||
|
- Add optional slope constraint to `//layers` (inspired by [WorldPainter](https://worldpainter.net/))
|
||||||
|
|
||||||
|
|
||||||
## v1.12: The selection tools update (26th June 2021)
|
## v1.12: The selection tools update (26th June 2021)
|
||||||
|
|
|
@ -1,8 +1,21 @@
|
||||||
--- Overlap command. Places a specified node on top of each column.
|
-- ██ █████ ██ ██ ███████ ██████ ███████
|
||||||
-- @module worldeditadditions.layers
|
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||||
|
-- ██ ███████ ████ █████ ██████ ███████
|
||||||
|
-- ██ ██ ██ ██ ██ ██ ██ ██
|
||||||
|
-- ███████ ██ ██ ██ ███████ ██ ██ ███████
|
||||||
|
|
||||||
function worldeditadditions.layers(pos1, pos2, node_weights)
|
local wea = worldeditadditions
|
||||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
|
||||||
|
--- Replaces the non-air nodes in each column with a list of nodes from top to bottom.
|
||||||
|
-- @param pos1 Vector Position 1 of the region to operate on
|
||||||
|
-- @param pos2 Vector Position 2 of the region to operate on
|
||||||
|
-- @param node_weights string[]
|
||||||
|
-- @param min_slope number?
|
||||||
|
-- @param max_slope number?
|
||||||
|
function worldeditadditions.layers(pos1, pos2, node_weights, min_slope, max_slope)
|
||||||
|
pos1, pos2 = wea.vector3.sort(pos1, pos2)
|
||||||
|
if not min_slope then min_slope = math.rad(0) end
|
||||||
|
if not max_slope then max_slope = math.rad(180) end
|
||||||
-- pos2 will always have the highest co-ordinates now
|
-- pos2 will always have the highest co-ordinates now
|
||||||
|
|
||||||
-- Fetch the nodes in the specified area
|
-- Fetch the nodes in the specified area
|
||||||
|
@ -11,38 +24,53 @@ function worldeditadditions.layers(pos1, pos2, node_weights)
|
||||||
|
|
||||||
local node_id_ignore = minetest.get_content_id("ignore")
|
local node_id_ignore = minetest.get_content_id("ignore")
|
||||||
|
|
||||||
local node_ids, node_ids_count = worldeditadditions.unwind_node_list(node_weights)
|
local node_ids, node_ids_count = wea.unwind_node_list(node_weights)
|
||||||
|
|
||||||
-- minetest.log("action", "pos1: " .. worldeditadditions.vector.tostring(pos1))
|
local heightmap, heightmap_size = wea.make_heightmap(
|
||||||
-- minetest.log("action", "pos2: " .. worldeditadditions.vector.tostring(pos2))
|
pos1, pos2,
|
||||||
|
manip, area, data
|
||||||
|
)
|
||||||
|
local slopemap = wea.calculate_slopes(heightmap, heightmap_size)
|
||||||
|
--luacheck:ignore 311
|
||||||
|
heightmap = nil -- Just in case Lua wants to garbage collect it
|
||||||
|
|
||||||
|
-- minetest.log("action", "pos1: " .. wea.vector.tostring(pos1))
|
||||||
|
-- minetest.log("action", "pos2: " .. wea.vector.tostring(pos2))
|
||||||
-- for i,v in ipairs(node_ids) do
|
-- for i,v in ipairs(node_ids) do
|
||||||
-- print("[layer] i", i, "node id", v)
|
-- print("[layer] i", i, "node id", v)
|
||||||
-- end
|
-- end
|
||||||
-- z y x is the preferred loop order, but that isn't really possible here
|
-- z y x is the preferred loop order, but that isn't really possible here
|
||||||
|
|
||||||
local changes = { replaced = 0, skipped_columns = 0 }
|
local changes = { replaced = 0, skipped_columns = 0, skipped_columns_slope = 0 }
|
||||||
for z = pos2.z, pos1.z, -1 do
|
for z = pos2.z, pos1.z, -1 do
|
||||||
for x = pos2.x, pos1.x, -1 do
|
for x = pos2.x, pos1.x, -1 do
|
||||||
local next_index = 1 -- We use table.insert() in make_weighted
|
local next_index = 1 -- We use table.insert() in make_weighted
|
||||||
local placed_node = false
|
local placed_node = false
|
||||||
|
|
||||||
for y = pos2.y, pos1.y, -1 do
|
local hi = z*heightmap_size.x + x
|
||||||
local i = area:index(x, y, z)
|
|
||||||
|
|
||||||
local is_air = worldeditadditions.is_airlike(data[i])
|
-- Again, Lua 5.1 doesn't have a continue statement :-/
|
||||||
local is_ignore = data[i] == node_id_ignore
|
if slopemap[hi] >= min_slope and slopemap[hi] <= max_slope then
|
||||||
|
for y = pos2.y, pos1.y, -1 do
|
||||||
|
local i = area:index(x, y, z)
|
||||||
|
|
||||||
if not is_air and not is_ignore then
|
local is_air = wea.is_airlike(data[i])
|
||||||
-- It's not an airlike node or something else odd
|
local is_ignore = data[i] == node_id_ignore
|
||||||
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 not is_air and not is_ignore then
|
||||||
if next_index > #node_ids then
|
-- It's not an airlike node or something else odd
|
||||||
break
|
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
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
changes.skipped_columns_slope = changes.skipped_columns_slope + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if not placed_node then
|
if not placed_node then
|
||||||
|
|
|
@ -91,7 +91,7 @@ end
|
||||||
-- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.make_heightmap().
|
-- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.make_heightmap().
|
||||||
-- @param heightmap_size int[] The size of the heightmap in the form [ z, x ]
|
-- @param heightmap_size int[] The size of the heightmap in the form [ z, x ]
|
||||||
-- @return Vector[] The calculated slope map, in the same form as the input heightmap. Each element of the array is a (floating-point) number representing the slope in that cell in radians.
|
-- @return Vector[] The calculated slope map, in the same form as the input heightmap. Each element of the array is a (floating-point) number representing the slope in that cell in radians.
|
||||||
function worldeditadditions.calculate_slope(heightmap, heightmap_size)
|
function worldeditadditions.calculate_slopes(heightmap, heightmap_size)
|
||||||
local normals = worldeditadditions.calculate_normals(heightmap, heightmap_size)
|
local normals = worldeditadditions.calculate_normals(heightmap, heightmap_size)
|
||||||
local slopes = { }
|
local slopes = { }
|
||||||
|
|
||||||
|
|
|
@ -256,7 +256,8 @@ end
|
||||||
--- Sorts the components of the given vectors.
|
--- Sorts the components of the given vectors.
|
||||||
-- pos1 will contain the minimum values, and pos2 the maximum values.
|
-- pos1 will contain the minimum values, and pos2 the maximum values.
|
||||||
-- Returns 2 new vectors.
|
-- Returns 2 new vectors.
|
||||||
-- Note that the vectors provided do not *have* to be instances of Vector3.
|
-- Note that for this specific function
|
||||||
|
-- the vectors provided do not *have* to be instances of Vector3.
|
||||||
-- It is only required that they have the keys x, y, and z.
|
-- It is only required that they have the keys x, y, and z.
|
||||||
-- Vector3 instances are always returned.
|
-- Vector3 instances are always returned.
|
||||||
-- This enables convenient ingesting of positions from outside.
|
-- This enables convenient ingesting of positions from outside.
|
||||||
|
|
|
@ -1,11 +1,34 @@
|
||||||
|
local function parse_slope_range(text)
|
||||||
|
if string.match(text, "%.%.") then
|
||||||
|
-- It's in the form a..b
|
||||||
|
local parts = worldeditadditions.split(text, "..", true)
|
||||||
|
if not parts then return nil end
|
||||||
|
if #parts ~= 2 then return false, "Error: Exactly 2 numbers may be separated by a double dot '..' (e.g. 10..45)" end
|
||||||
|
local min_slope = tonumber(parts[1])
|
||||||
|
local max_slope = tonumber(parts[2])
|
||||||
|
if not min_slope then return false, "Error: Failed to parse the specified min_slope '"..tostring(min_slope).."' value as a number." end
|
||||||
|
if not max_slope then return false, "Error: Failed to parse the specified max_slope '"..tostring(max_slope).."' value as a number." end
|
||||||
|
|
||||||
|
-- math.rad converts degrees to radians
|
||||||
|
return true, math.rad(min_slope), math.rad(max_slope)
|
||||||
|
else
|
||||||
|
-- It's a single value
|
||||||
|
local max_slope = tonumber(text)
|
||||||
|
if not max_slope then return nil end
|
||||||
|
|
||||||
|
return true, 0, math.rad(max_slope)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- ██████ ██ ██ ███████ ██████ ██ █████ ██ ██
|
-- ██████ ██ ██ ███████ ██████ ██ █████ ██ ██
|
||||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||||
-- ██ ██ ██ ██ █████ ██████ ██ ███████ ████
|
-- ██ ██ ██ ██ █████ ██████ ██ ███████ ████
|
||||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||||
-- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██
|
-- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██
|
||||||
worldedit.register_command("layers", {
|
worldedit.register_command("layers", {
|
||||||
params = "[<node_name_1> [<layer_count_1>]] [<node_name_2> [<layer_count_2>]] ...",
|
params = "[<max_slope|min_slope..max_slope>] [<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",
|
description = "Replaces the topmost non-airlike nodes with layers of the given nodes from top to bottom. Like WorldEdit for MC's //naturalize command. Optionally takes a maximum or minimum and maximum slope value. If a column's slope value falls outside the defined range, then it's skipped. Default: dirt_with_grass dirt 3",
|
||||||
privs = { worldedit = true },
|
privs = { worldedit = true },
|
||||||
require_pos = 2,
|
require_pos = 2,
|
||||||
parse = function(params_text)
|
parse = function(params_text)
|
||||||
|
@ -13,21 +36,40 @@ worldedit.register_command("layers", {
|
||||||
params_text = "dirt_with_grass dirt 3"
|
params_text = "dirt_with_grass dirt 3"
|
||||||
end
|
end
|
||||||
|
|
||||||
local success, node_list = worldeditadditions.parse.weighted_nodes(
|
local parts = worldeditadditions.split_shell(params_text)
|
||||||
worldeditadditions.split_shell(params_text),
|
local success, min_slope, max_slope
|
||||||
|
|
||||||
|
if #parts > 0 then
|
||||||
|
success, min_slope, max_slope = parse_slope_range(parts[1])
|
||||||
|
if success then
|
||||||
|
table.remove(parts, 1) -- Automatically shifts other values down
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not min_slope then min_slope = 0 end
|
||||||
|
if not max_slope then max_slope = 180 end
|
||||||
|
|
||||||
|
|
||||||
|
local node_list
|
||||||
|
success, node_list = worldeditadditions.parse.weighted_nodes(
|
||||||
|
parts,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
return success, node_list
|
return success, node_list, min_slope, max_slope
|
||||||
end,
|
end,
|
||||||
nodes_needed = function(name)
|
nodes_needed = function(name)
|
||||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||||
end,
|
end,
|
||||||
func = function(name, node_list)
|
func = function(name, node_list, min_slope, max_slope)
|
||||||
local start_time = worldeditadditions.get_ms_time()
|
local start_time = worldeditadditions.get_ms_time()
|
||||||
local changes = worldeditadditions.layers(worldedit.pos1[name], worldedit.pos2[name], node_list)
|
local changes = worldeditadditions.layers(
|
||||||
|
worldedit.pos1[name], worldedit.pos2[name],
|
||||||
|
node_list,
|
||||||
|
min_slope, max_slope
|
||||||
|
)
|
||||||
local time_taken = worldeditadditions.get_ms_time() - start_time
|
local time_taken = worldeditadditions.get_ms_time() - 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")
|
minetest.log("action", name .. " used //layers at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. changes.replaced .. " nodes and skipping " .. changes.skipped_columns .. " columns ("..changes.skipped_columns_slope.." due to slope constraints) in " .. time_taken .. "s")
|
||||||
return true, changes.replaced .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped in " .. worldeditadditions.format.human_time(time_taken)
|
return true, changes.replaced .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped ("..changes.skipped_columns_slope.." due to slope constraints) in " .. worldeditadditions.format.human_time(time_taken)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue