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
|
||||
- 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
|
||||
- Add optional slope constraint to `//layers` (inspired by [WorldPainter](https://worldpainter.net/))
|
||||
|
||||
|
||||
## 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)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local wea = worldeditadditions
|
||||
|
||||
--- 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
|
||||
|
||||
-- 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_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))
|
||||
-- minetest.log("action", "pos2: " .. worldeditadditions.vector.tostring(pos2))
|
||||
local heightmap, heightmap_size = wea.make_heightmap(
|
||||
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
|
||||
-- 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 }
|
||||
local changes = { replaced = 0, skipped_columns = 0, skipped_columns_slope = 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
|
||||
local hi = z*heightmap_size.x + x
|
||||
|
||||
-- Again, Lua 5.1 doesn't have a continue statement :-/
|
||||
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 we're done replacing nodes in this column, move to the next one
|
||||
if next_index > #node_ids then
|
||||
break
|
||||
local is_air = wea.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
|
||||
else
|
||||
changes.skipped_columns_slope = changes.skipped_columns_slope + 1
|
||||
end
|
||||
|
||||
if not placed_node then
|
||||
|
|
|
@ -91,7 +91,7 @@ end
|
|||
-- @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 ]
|
||||
-- @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 slopes = { }
|
||||
|
||||
|
|
|
@ -256,7 +256,8 @@ end
|
|||
--- Sorts the components of the given vectors.
|
||||
-- pos1 will contain the minimum values, and pos2 the maximum values.
|
||||
-- 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.
|
||||
-- Vector3 instances are always returned.
|
||||
-- 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", {
|
||||
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",
|
||||
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. 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 },
|
||||
require_pos = 2,
|
||||
parse = function(params_text)
|
||||
|
@ -13,21 +36,40 @@ worldedit.register_command("layers", {
|
|||
params_text = "dirt_with_grass dirt 3"
|
||||
end
|
||||
|
||||
local success, node_list = worldeditadditions.parse.weighted_nodes(
|
||||
worldeditadditions.split_shell(params_text),
|
||||
local parts = 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
|
||||
)
|
||||
return success, node_list
|
||||
return success, node_list, min_slope, max_slope
|
||||
end,
|
||||
nodes_needed = function(name)
|
||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||
end,
|
||||
func = function(name, node_list)
|
||||
func = function(name, node_list, min_slope, max_slope)
|
||||
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
|
||||
|
||||
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 " .. worldeditadditions.format.human_time(time_taken)
|
||||
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 ("..changes.skipped_columns_slope.." due to slope constraints) in " .. worldeditadditions.format.human_time(time_taken)
|
||||
end
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue