enhance vl_terraform API

This commit is contained in:
kno10 2024-10-31 20:14:29 +01:00
parent dfeffc8522
commit e8944f202c
14 changed files with 225 additions and 171 deletions

View File

@ -80,7 +80,7 @@ local cold = {
y_max = water_level - 6,
y_offset = -1,
flags = "place_center_x, place_center_z, force_placement",
prepare = { foundation = -2, clear = false, mode="water" },
prepare = { foundation = -2, clear = false, surface = "water" },
filenames = {
modpath.."/schematics/mcl_structures_ocean_ruins_cold_1.mts",
modpath.."/schematics/mcl_structures_ocean_ruins_cold_2.mts",

View File

@ -82,7 +82,7 @@ vl_structures.register_structure("ocean_temple",{
},
flags = "force_placement",
force_placement = true,
prepare = { tolerance = 8, clear = false, foundation = 3, mode="water" },
prepare = { tolerance = 8, clear = false, foundation = 3, surface = "water" },
biomes = ocean_biomes,
y_max = water_level-4,
y_min = mcl_vars.mg_overworld_min,

View File

@ -77,7 +77,7 @@ vl_structures.register_structure("shipwreck",{
y_max = water_level-5,
y_offset = function(pr) return pr:next(-3,-1) end,
flags = "place_center_x, place_center_z, force_placement",
prepare = { tolerance = -1, clear = false, foundation = false, mode = "water" },
prepare = { tolerance = 99, clear = false, foundation = false, surface = "water", mode = "min" },
filenames = {
--schematics by chmodsayshello
modpath.."/schematics/mcl_structures_shipwreck_full_damaged.mts",

View File

@ -48,7 +48,7 @@ vl_structures.register_structure("witch_hut",{
num_spawn_by = 3,
flags = "place_center_x, place_center_z, all_surfaces",
chunk_probability = 8,
prepare = { mode="under_air", tolerance=4, clear_bottom=3, padding=0, corners=1, foundation=false },
prepare = { surface = "under_air", tolerance = 4, clear_bottom = 3, padding = 0, corners = 1, foundation = false },
y_max = mcl_vars.mg_overworld_max,
y_min = -5,
y_offset = 0,

View File

@ -8,7 +8,7 @@ vl_structures.register_structure("obelisk_sand",{
y_max = mcl_vars.mg_overworld_max,
y_min = 1,
y_offset = -3,
prepare = { tolerance=3, padding = 0, clear=false },
prepare = { tolerance = 3, padding = 0, clear = false },
biomes = { "Desert" },
filenames = {
modpath.."/schematics/obelisk_sand_1.mts",
@ -23,7 +23,7 @@ vl_structures.register_structure("obelisk_light",{
y_max = mcl_vars.mg_overworld_max,
y_min = 1,
y_offset = -2,
prepare = { tolerance=2, padding = 0, clear=false },
prepare = { tolerance = 2, padding = 0, clear = false },
biomes = { "Desert" },
filenames = {
modpath.."/schematics/obelisk_fire.mts",
@ -42,7 +42,7 @@ vl_structures.register_structure("obelisk_cobble",{
y_max = mcl_vars.mg_overworld_max,
y_min = 1,
y_offset = -2,
prepare = { tolerance=2, padding=0, clear=false },
prepare = { tolerance = 2, padding = 0, clear = false },
biomes = { "Plains", "SunflowerPlains", "Forest", "FlowerForest", "BrichForest", "Taiga", "RoofedForest", "MegaTaiga", "MegaSpruceTaiga", },
filenames = {
modpath.."/schematics/obelisk_cobble.mts",

View File

@ -15,8 +15,8 @@ local function parse_prepare(prepare)
end
-- check "enabled" tolerances
local function tolerance_enabled(tolerance, mode)
return mode ~= "off" and tolerance and (tolerance == "max" or tolerance == "min" or tolerance >= 0) and true
local function tolerance_enabled(tolerance, surface, mode)
return tolerance ~= "off" and (tolerance or surface or mode) and true
end
--- Main palcement step, when the area has been emerged
@ -45,12 +45,13 @@ local function emerge_schematics(blockpos, action, calls_remaining, param)
-- Step 1: adjust ground to a more level position
-- todo: also support checking ground of daughter schematics, but not used by current schematics
if pos and size and prepare and tolerance_enabled(prepare.tolerance, prepare.mode) then
pos, surface_mat = vl_terraforming.find_level(pos, size, prepare.tolerance, prepare.mode)
if pos and size and prepare and tolerance_enabled(prepare.tolerance, prepare.surface, prepare.mode) then
pos, surface_mat = vl_terraforming.find_level(pos, size, prepare.tolerance, prepare.surface, prepare.mode)
if not pos then
minetest.log("warning", "[vl_structures] Not spawning "..tostring(def.name or param.schematic.name).." at "..minetest.pos_to_string(param.pos).." because ground is too uneven.")
return
end
pos.y = pos.y + 1 -- above surface
-- obey height restrictions, to not violate nether roof
if def.y_max and pos.y - yoffset > def.y_max then pos.y = def.y_max - yoffset end
if def.y_min and pos.y - yoffset < def.y_min then pos.y = def.y_min - yoffset end

View File

@ -1,4 +1,4 @@
# vl_terraforming
# `vl_terraforming` -- Terraforming module
Terraforming module built with VoxeLibre and MineClonia in mind, but also useful for other games.
@ -9,6 +9,8 @@ This module provides the following key functionalities:
- build a baseplate for a building
- clear the area above a building
All methods have a `_vm` version to work with Lua Voxel Manipulators
## Rounded corners support
To get nicer looking baseplates, the code supports rounded corners.
@ -23,77 +25,91 @@ The ellipse condition $dx^2/a^2+dz^2/b^2 \leq 1$ then yields $dx^2/(0.5 sx^2) +
We use $wx2=2 sx^-2$, $wz2=2 sz^-2$ and then $dx^2 wx2 + dz^2 wz2 \leq 1$.
## vl_terraforming.find_ground_vm(vm, pos)
## `vl_terraforming.find_ground(pos)`
Find ground starting at the given position. When in a solid area, moves up; otherwise searches downwards.
This will ignore trees, mushrooms, and similar surface decorations.
## vl_terraforming.find_under_air_vm(vm, pos)
## `vl_terraforming.find_under_air(pos)`
Find ground or liquid surface, starting at the given position. When in a solid or liquid area, moves up; otherwise searches downwards.
This will ignore trees, mushrooms, and similar surface decorations.
## vl_terraforming.find_liquid_surface_vm(vm, pos)
## `vl_terraforming.find_liquid_surface(pos)`
Find a liquid surface starting at the given position. When in a solid or liquid area, moves up; otherwise searches downwards.
This will ignore trees, mushrooms, and similar surface decorations.
## `vl_terraforming.find_under_water_surface(pos)`
## vl_terraforming.find_level_vm(vm, cpos, size, tolerance, mode)
Find a solid surface covered by water starting at the given position. When in a solid area, moves up; otherwise searches downwards.
Find "level" ground for a building, centered at the given position, and of the given size.
This will ignore trees, mushrooms, and similar surface decorations.
## `vl_terraforming.find_level(cpos, size, tolerance, surface, mode)`
Find "level" (sufficiently even) ground for a structure, centered at the given position, and of the given size.
For this, five samples are taken: center, top left, top right, bottom left, and bottom right.
One of these values may be "extreme", and tolerance specifies the maximum height difference of the remaining four values.
The (rounded) median of these values is used, unless tolerance is set to "min" or "max".
The `surface` can be set to:
- `"solid"` (default, i.e., solid under air)
- `"liquid"` (liquid under air)
- `"under_air"` (both liquid and solid surfaces)
- `"under_water"` (solid under water)
The "mode" can be set to "solid" (default), "liquid" (liquid surfaces only), "under_air" (both liquid and solid surfaces), "under_water" (solid below water).
The `mode` can be set to:
- `"median"` (default, use the median height, rounded)
- `"min"` (use the lowest support coordinate)
- `"max"` (use the highest support coordinate)
## vl_terraforming.foundation_vm(vm, px, py, pz, sx, sy, sz, corners, surface_mat, platform_mat, stone_mat, dust_mat, pr)
## `vl_terraforming.foundation(px, py, pz, sx, sy, sz, corners, surface_mat, platform_mat, stone_mat, dust_mat, pr)`
The position (px, py, pz) and the size (sx, sy, sz) give the volume of the main base plate,
where sy < 0, so that you can later place the structure at (px, py, pz).
The position `(px, py, pz)` and the size `(sx, sy, sz)` give the volume of the main base plate,
where `sy < 0`, so that you can later place the structure at `(px, py, pz)`.
The baseplate will be grown by 1 in the level below, to allow mobs to enter, then randomly fade away below.
-sy can be used to control a minimum depth.
The negative depth `sy` can be used to control a minimum depth.
Corners specifies how much to cut the corners, use 0 for a square baseplate.
The materials specified (as lua nodes, to have param2 support) are used a follows:
The materials specified (as lua nodes, to have `param2` coloring support) are used a follows:
- surface_mat for surface nodes
- platform_mat below surface nodes
- stone_mat randomly used below platform_mat
- dust_mat on top of surface nodes (snow cover, optional)
- `surface_mat` for surface nodes
- `platform_mat` below surface nodes
- `stone_mat` randomly used below `platform_mat`
- `dust_mat` on top of surface nodes (snow cover, optional)
pr is a PcgRandom random generator
`pr` is a PcgRandom random generator
## vl_terraforming.clearance_vm(vm, px, py, pz, sx, sy, sz, corners, surface_mat, dust_mat, pr)
## `vl_terraforming.clearance(px, py, pz, sx, sy, sz, corners, surface_mat, dust_mat, pr)`
The position (px, py, pz) and the size (sx, sy, sz) give the volume overhead to clear.
The position `(px, py, pz)` and the size `(sx, sy, sz)` give the volume overhead to clear.
The area will be grown by 1 above, to allow mobs to enter, then randomly fade away as height increases beyond sy.
The area will be grown by 1 above, to allow mobs to enter, then randomly fade away as height increases beyond `sy`.
Corners specifies how much to cut the corners, use 0 for a square area.
`corners` specifies how much to cut the corners, use 0 for a square area.
The surface_mat will be used to turn nodes into surface nodes when widening the area.
`surface_mat` is the node used to turn nodes into surface nodes when widening the area. If set, the `dust_mat` will be sprinkled on top.
`pr` is a PcgRandom random generator
pr is a PcgRandom random generator
## TODO
- [ ] make even more configurable
- [ ] add ceiling placement
- [ ] add an API that works on VM buffers
- [ ] add an API version working on the non-VM API
- [ ] benchmark if VM is actually faster than not using VM (5.9 has some optimizations not yet in VM)
- [ ] benchmark when VM is faster than not using VM (5.9 has some optimizations not yet in VM)
- [ ] improve tree removal

View File

@ -1,13 +1,17 @@
local AIR = {name = "air"}
local AIR = vl_terraforming._AIR
local abs = math.abs
local max = math.max
local floor = math.floor
local vector_new = vector.new
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
local is_tree_not_leaves = vl_terraforming._is_tree_not_leaves
local get_node = core.get_node
local swap_node = core.swap_node
local is_air = vl_terraforming._is_air
local immutable = vl_terraforming._immutable
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
local is_tree_not_leaves = vl_terraforming._is_tree_not_leaves
local is_tree_or_leaves = vl_terraforming._is_tree_or_leaves
--- Clear an area for a structure
--
-- Rounding: we model an ellipse. At zero rounding, we want the line go through the corner, at sx/2, sz/2.
@ -47,7 +51,7 @@ function vl_terraforming.clearance(px, py, pz, sx, sy, sz, corners, surface_mat,
vec.z = zi
if xi >= px and xi < px+sx and zi >= pz and zi < pz+sz and dx2+dz2 <= 1 then
vec.y = py
if get_node(vec).name ~= "mcl_core:bedrock" then swap_node(vec, AIR) end
if not immutable(get_node(vec)) then swap_node(vec, AIR) end
vec.y = py - 1
local n = get_node(vec)
if n and n.name ~= surface_mat.name and is_solid_not_tree(n) then
@ -55,14 +59,13 @@ function vl_terraforming.clearance(px, py, pz, sx, sy, sz, corners, surface_mat,
end
for yi = py+1,min_clear do -- full height for inner area
vec.y = yi
if get_node(vec).name ~= "mcl_core:bedrock" then swap_node(vec, AIR) end
if not immutable(get_node(vec)) then swap_node(vec, AIR) end
end
elseif dx21+dz21 <= 1 then
-- widen the cave above by 1, to make easier to enter for mobs
-- todo: make configurable?
vec.y = py + 1
local name = get_node(vec).name
if name ~= "mcl_core:bedrock" then
if not immutable(get_node(vec)) then
local mat = AIR
if dust_mat then
vec.y = py
@ -73,7 +76,7 @@ function vl_terraforming.clearance(px, py, pz, sx, sy, sz, corners, surface_mat,
end
for yi = py+2,min_clear-1 do
vec.y = yi
if get_node(vec).name ~= "mcl_core:bedrock" then swap_node(vec, AIR) end
if not immutable(get_node(vec)) then swap_node(vec, AIR) end
if yi > py+4 then
local p = (yi-py) / (max_clear-py)
--minetest.log(tostring(p).."^2 "..tostring(p*p).." rand: "..pr:next(0,1e9)/1e9)
@ -88,14 +91,14 @@ function vl_terraforming.clearance(px, py, pz, sx, sy, sz, corners, surface_mat,
swap_node(vec, surface_mat)
if dust_mat and yi == py then
vec.y = yi + 1
if get_node(vec).name == "air" then swap_node(vec, dust_mat) end
if is_air(get_node(vec)) then swap_node(vec, dust_mat) end
end
else
if n and n.name ~= surface_mat.name and is_solid_not_tree(n) then
swap_node(vec, surface_mat)
if dust_mat then
vec.y = yi + 1
if get_node(vec).name == "air" then swap_node(vec, dust_mat) end
if is_air(get_node(vec)) then swap_node(vec, dust_mat) end
end
end
break
@ -118,12 +121,12 @@ function vl_terraforming.clearance(px, py, pz, sx, sy, sz, corners, surface_mat,
if py+4 < sy then
for yi = py+2,py+4 do
vec = vector_new(xi, yi, zi)
if get_node(vec).name ~= "mcl_core:bedrock" then swap_node(vec, v) end
if not immutable(get_node(vec)) then swap_node(vec, v) end
end
end
for yi = py+1,py-1,-1 do
local n = get_node(vector_new(xi, yi, zi))
if is_tree_bot_leaves(n) and n.name ~= "mcl_core:bedrock" then
if is_tree_not_leaves(n) and not immutable(n) then
swap_node(vector_new(xi, yi, zi), AIR)
else
if n and n.name ~= surface_mat.name and is_solid_not_tree(n) then
@ -147,17 +150,15 @@ function vl_terraforming.clearance(px, py, pz, sx, sy, sz, corners, surface_mat,
local keep_trees = (xi<px or xi>=px+sx) or (zi<pz or zi>=pz+sz) -- TODO make parameter?
if dx22+dy2+dz22 <= 1 then
vec.x, vec.y, vec.z = xi, yi, zi
local name = get_node(vec).name
local nod = get_node(vec)
-- don't break bedrock or air
if name == "air" or name == "ignore" or name == "mcl_core:bedrock" or name == "mcl_villages:no_paths" then goto continue end
local meta = minetest.registered_items[name]
local groups = meta and meta.groups
local is_tree = groups.leaves or groups.tree or (groups.compostability or 0 > 50)
if is_air(nod) or immutable(nod) then goto continue end
local is_tree = is_tree_or_leaves(nod)
if keep_trees and is_tree then goto continue end
vec.y = yi-1
-- do not clear above solid
local name_below = get_node(vec).name
if name_below ~= "air" and name_below ~= "ignore" and name_below ~= "mcl_core:bedrock" then goto continue end
local nod_below = get_node(vec)
if not is_air(nod_below) and not immutable(nod_below) then goto continue end
-- try to completely remove trees overhead
-- stop randomly depending on fill, to narrow down the caves
if not keep_trees and not is_tree and (pr:next(0,1e9)/1e9)^0.5 > 1-(dx22+dy2+dz22-0.1) then goto continue end

View File

@ -1,10 +1,14 @@
local AIR = {name = "air"}
local AIR = vl_terraforming._AIR
local abs = math.abs
local max = math.max
local floor = math.floor
local vector_new = vector.new
local is_air = vl_terraforming._is_air
local immutable = vl_terraforming._immutable
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
local is_tree_not_leaves = vl_terraforming._is_tree_not_leaves
local is_tree_or_leaves = vl_terraforming._is_tree_or_leaves
--- Clear an area for a structure
--
@ -28,8 +32,8 @@ local is_tree_not_leaves = vl_terraforming._is_tree_not_leaves
-- @param pr PcgRandom: random generator
function vl_terraforming.clearance_vm(vm, px, py, pz, sx, sy, sz, corners, surface_mat, dust_mat, pr)
if sx <= 0 or sy <= 0 or sz <= 0 then return end
local get_node_at = vm.get_node_at
local set_node_at = vm.set_node_at
local get_node = vm.get_node_at
local swap_node = vm.set_node_at
corners = corners or 0
local wx2, wz2 = max(sx - corners, 1)^-2 * 2, max(sz - corners, 1)^-2 * 2
local cx, cz = px + sx * 0.5 - 0.5, pz + sz * 0.5 - 0.5
@ -48,33 +52,32 @@ function vl_terraforming.clearance_vm(vm, px, py, pz, sx, sy, sz, corners, surfa
vec.z = zi
if xi >= px and xi < px+sx and zi >= pz and zi < pz+sz and dx2+dz2 <= 1 then
vec.y = py
if get_node_at(vm, vec).name ~= "mcl_core:bedrock" then set_node_at(vm, vec, AIR) end
if not immutable(get_node(vm, vec)) then swap_node(vm, vec, AIR) end
vec.y = py - 1
local n = get_node_at(vm, vec)
local n = get_node(vm, vec)
if n and n.name ~= surface_mat.name and is_solid_not_tree(n) then
set_node_at(vm, vec, surface_mat)
swap_node(vm, vec, surface_mat)
end
for yi = py+1,min_clear do -- full height for inner area
vec.y = yi
if get_node_at(vm, vec).name ~= "mcl_core:bedrock" then set_node_at(vm, vec, AIR) end
if not immutable(get_node(vm, vec)) then swap_node(vm, vec, AIR) end
end
elseif dx21+dz21 <= 1 then
-- widen the cave above by 1, to make easier to enter for mobs
-- todo: make configurable?
vec.y = py + 1
local name = get_node_at(vm, vec).name
if name ~= "mcl_core:bedrock" then
if not immutable(get_node(vm, vec)) then
local mat = AIR
if dust_mat then
vec.y = py
if get_node_at(vm, vec).name == surface_mat.name then mat = dust_mat end
if get_node(vm, vec).name == surface_mat.name then mat = dust_mat end
vec.y = py + 1
end
set_node_at(vm, vec, mat)
swap_node(vm, vec, mat)
end
for yi = py+2,min_clear-1 do
vec.y = yi
if get_node_at(vm, vec).name ~= "mcl_core:bedrock" then set_node_at(vm, vec, AIR) end
if not immutable(get_node(vm, vec)) then swap_node(vm, vec, AIR) end
if yi > py+4 then
local p = (yi-py) / (max_clear-py)
--minetest.log(tostring(p).."^2 "..tostring(p*p).." rand: "..pr:next(0,1e9)/1e9)
@ -84,19 +87,19 @@ function vl_terraforming.clearance_vm(vm, px, py, pz, sx, sy, sz, corners, surfa
-- remove some tree parts and fix surfaces down
for yi = py,py-1,-1 do
vec.y = yi
local n = get_node_at(vm, vec)
local n = get_node(vm, vec)
if is_tree_not_leaves(n) then
set_node_at(vm, vec, surface_mat)
swap_node(vm, vec, surface_mat)
if dust_mat and yi == py then
vec.y = yi + 1
if get_node_at(vm, vec).name == "air" then set_node_at(vm, vec, dust_mat) end
if is_air(get_node(vm, vec)) then swap_node(vm, vec, dust_mat) end
end
else
if n and n.name ~= surface_mat.name and is_solid_not_tree(n) then
set_node_at(vm, vec, surface_mat)
swap_node(vm, vec, surface_mat)
if dust_mat then
vec.y = yi + 1
if get_node_at(vm, vec).name == "air" then set_node_at(vm, vec, dust_mat) end
if is_air(get_node(vm, vec)) then swap_node(vm, vec, dust_mat) end
end
end
break
@ -119,16 +122,16 @@ function vl_terraforming.clearance_vm(vm, px, py, pz, sx, sy, sz, corners, surfa
if py+4 < sy then
for yi = py+2,py+4 do
vec = vector_new(xi, yi, zi)
if get_node_at(vm, vec).name ~= "mcl_core:bedrock" then set_node_at(vm, vec, v) end
if not immutable(get_node(vm, vec)) then swap_node(vm, vec, v) end
end
end
for yi = py+1,py-1,-1 do
local n = get_node_at(vm, vector_new(xi, yi, zi))
if is_tree_bot_leaves(n) and n.name ~= "mcl_core:bedrock" then
set_node_at(vm, vector_new(xi, yi, zi), AIR)
local n = get_node(vm, vector_new(xi, yi, zi))
if is_tree_not_leaves(n) and not immutable(n) then
swap_node(vm, vector_new(xi, yi, zi), AIR)
else
if n and n.name ~= surface_mat.name and is_solid_not_tree(n) then
set_node_at(vm, vector_new(xi, yi, zi), surface_mat)
swap_node(vm, vector_new(xi, yi, zi), surface_mat)
end
break
end
@ -148,22 +151,20 @@ function vl_terraforming.clearance_vm(vm, px, py, pz, sx, sy, sz, corners, surfa
local keep_trees = (xi<px or xi>=px+sx) or (zi<pz or zi>=pz+sz) -- TODO make parameter?
if dx22+dy2+dz22 <= 1 then
vec.x, vec.y, vec.z = xi, yi, zi
local name = get_node_at(vm, vec).name
local nod = get_node(vm, vec)
-- don't break bedrock or air
if name == "air" or name == "ignore" or name == "mcl_core:bedrock" or name == "mcl_villages:no_paths" then goto continue end
local meta = minetest.registered_items[name]
local groups = meta and meta.groups
local is_tree = groups.leaves or groups.tree or (groups.compostability or 0 > 50)
if is_air(nod) or immutable(nod) then goto continue end
local is_tree = is_tree_or_leaves(nod)
if keep_trees and is_tree then goto continue end
vec.y = yi-1
-- do not clear above solid
local name_below = get_node_at(vm, vec).name
if name_below ~= "air" and name_below ~= "ignore" and name_below ~= "mcl_core:bedrock" then goto continue end
local nod_below = get_node(vm, vec)
if not is_air(nod_below) and not immutable(nod_below) then goto continue end
-- try to completely remove trees overhead
-- stop randomly depending on fill, to narrow down the caves
if not keep_trees and not is_tree and (pr:next(0,1e9)/1e9)^0.5 > 1-(dx22+dy2+dz22-0.1) then goto continue end
vec.x, vec.y, vec.z = xi, yi, zi
set_node_at(vm, vec, AIR)
swap_node(vm, vec, AIR)
active = true
::continue::
end

View File

@ -1,12 +1,14 @@
local abs = math.abs
local max = math.max
local vector_new = vector.new
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
local make_solid = vl_terraforming._make_solid
local get_node = core.get_node
local swap_node = core.swap_node
local is_air = vl_terraforming._is_air
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
local immutable = vl_terraforming._immutable
local make_solid = vl_terraforming._make_solid
--- Grow the foundation downwards
-- @param xi number: x coordinate
-- @param yi number: y coordinate
@ -34,7 +36,7 @@ local function grow_foundation(xi,yi,zi,pr,surface_mat,platform_mat,stone_mat)
-- TODO: allow controlling the random depth with an additional parameter?
if (pr:next(0,1e9)/1e9)^2 > c/9.1 then return false end
pos.x, pos.y, pos.z = xi, yi, zi
if get_node(pos).name == "mcl_core:bedrock" then return false end
if immutable(get_node(pos)) then return false end
swap_node(pos, platform_mat)
return true
end
@ -77,11 +79,11 @@ function vl_terraforming.foundation(px, py, pz, sx, sy, sz, corners, surface_mat
pos.z = zi
if xi >= px and xi < px+sx and zi >= pz and zi < pz+sz and dx2+dz2 <= 1 then
pos.y = py
if get_node(pos).name ~= "mcl_core:bedrock" then
if not immutable(get_node(pos)) then
swap_node(pos, surface_mat)
if dust_mat then
pos.y = py + 1
if get_node(pos).name == "air" then swap_node(pos, dust_mat) end
if is_air(get_node(pos)) then swap_node(pos, dust_mat) end
end
pos.y = py - 1
make_solid(pos, platform_mat)
@ -92,7 +94,7 @@ function vl_terraforming.foundation(px, py, pz, sx, sy, sz, corners, surface_mat
make_solid(pos, surface_mat)
if dust_mat then
pos.y = py
if get_node(pos).name == "air" then swap_node(pos, dust_mat) end
if is_air(get_node(pos)) then swap_node(pos, dust_mat) end
end
end
end

View File

@ -2,7 +2,9 @@ local abs = math.abs
local max = math.max
local vector_new = vector.new
local is_air = vl_terraforming._is_air
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
local immutable = vl_terraforming._immutable
local make_solid_vm = vl_terraforming._make_solid_vm
--- Grow the foundation downwards
@ -15,11 +17,12 @@ local make_solid_vm = vl_terraforming._make_solid_vm
-- @param platform_mat Node: platform material node
-- @param stone_mat Node: stone material node
local function grow_foundation_vm(vm,xi,yi,zi,pr,surface_mat,platform_mat,stone_mat)
local get_node_at = vm.get_node_at
local get_node = vm.get_node_at
local swap_node = vm.set_node_at
local pos, n, c = vector_new(xi,yi,zi), nil, 0
if is_solid_not_tree(get_node_at(vm, pos)) then return false end -- already solid, nothing to do
if is_solid_not_tree(get_node(vm, pos)) then return false end -- already solid, nothing to do
pos.y = pos.y + 1
local cur = get_node_at(vm, pos)
local cur = get_node(vm, pos)
if not is_solid_not_tree(cur) then return false end -- above is empty, do not fill below
if cur and cur.name and cur.name ~= surface_mat.name then platform_mat = cur end
if pr:next(1,4) == 1 then platform_mat = stone_mat end -- randomly switch to stone sometimes
@ -27,15 +30,15 @@ local function grow_foundation_vm(vm,xi,yi,zi,pr,surface_mat,platform_mat,stone_
for x = xi-1,xi+1 do
for z = zi-1,zi+1 do
pos.x, pos.z = x, z
if is_solid_not_tree(get_node_at(vm, pos)) then c = c + 1 end
if is_solid_not_tree(get_node(vm, pos)) then c = c + 1 end
end
end
-- stop randomly depending on fill, to narrow down the foundation
-- TODO: allow controlling the random depth with an additional parameter?
if (pr:next(0,1e9)/1e9)^2 > c/9.1 then return false end
pos.x, pos.y, pos.z = xi, yi, zi
if get_node_at(vm, pos).name == "mcl_core:bedrock" then return false end
vm:set_node_at(pos, platform_mat)
if immutable(get_node(vm, pos)) then return false end
swap_node(vm, pos, platform_mat)
return true
end
--- Generate a foundation from px,py,pz with size sx,sy,sz (sy < 0) plus some margin
@ -63,8 +66,8 @@ end
-- @param pr PcgRandom: random generator
function vl_terraforming.foundation_vm(vm, px, py, pz, sx, sy, sz, corners, surface_mat, platform_mat, stone_mat, dust_mat, pr)
if sx <= 0 or sy >= 0 or sz <= 0 then return end
local get_node_at = vm.get_node_at
local set_node_at = vm.set_node_at
local get_node = vm.get_node_at
local swap_node = vm.set_node_at
corners = corners or 0
local wx2, wz2 = max(sx - corners, 1)^-2 * 2, max(sz - corners, 1)^-2 * 2
local cx, cz = px + sx * 0.5 - 0.5, pz + sz * 0.5 - 0.5
@ -80,11 +83,11 @@ function vl_terraforming.foundation_vm(vm, px, py, pz, sx, sy, sz, corners, surf
pos.z = zi
if xi >= px and xi < px+sx and zi >= pz and zi < pz+sz and dx2+dz2 <= 1 then
pos.y = py
if get_node_at(vm, pos).name ~= "mcl_core:bedrock" then
set_node_at(vm, pos, surface_mat)
if not immutable(get_node(vm, pos)) then
swap_node(vm, pos, surface_mat)
if dust_mat then
pos.y = py + 1
if get_node_at(vm, pos).name == "air" then set_node_at(vm, pos, dust_mat) end
if is_air(get_node(vm, pos)) then swap_node(vm, pos, dust_mat) end
end
pos.y = py - 1
make_solid_vm(vm, pos, platform_mat)
@ -95,7 +98,7 @@ function vl_terraforming.foundation_vm(vm, px, py, pz, sx, sy, sz, corners, surf
make_solid_vm(vm, pos, surface_mat)
if dust_mat then
pos.y = py
if get_node_at(vm, pos).name == "air" then set_node_at(vm, pos, dust_mat) end
if is_air(get_node(vm, pos)) then swap_node(vm, pos, dust_mat) end
end
end
end

View File

@ -203,18 +203,19 @@ local find_under_water_surface = vl_terraforming.find_under_water_surface
--- find suitable height for a structure of this size
-- @param cpos vector: center
-- @param size vector: area size
-- @param tolerance number or string: maximum height difference allowed, default 8.
-- @param mode string: "solid" (default), "liquid_surface", "under_air"
-- @param tolerance number or string: maximum height difference allowed, default 8,
-- @param surface string: "solid" (default), "liquid_surface", "under_air"
-- @param mode string: "median" (default), "min" and "max"
-- @return position over surface, surface material (or nil, nil)
function vl_terraforming.find_level(cpos, size, tolerance, mode)
function vl_terraforming.find_level(cpos, size, tolerance, surface, mode)
local _find_ground = find_ground
if mode == "liquid_surface" or mode == "liquid" then _find_ground = find_liquid_surface end
if mode == "under_water" or mode == "water" then _find_ground = find_under_water_surface end
if mode == "under_air" then _find_ground = find_under_air end
if surface == "liquid_surface" or surface == "liquid" then _find_ground = find_liquid_surface end
if surface == "under_water" or surface == "water" then _find_ground = find_under_water_surface end
if surface == "under_air" then _find_ground = find_under_air end
-- begin at center, then top-left and clockwise
local pos, surface_material = _find_ground(cpos)
if not pos then
-- minetest.log("action", "[vl_terraforming] no ground at starting position "..minetest.pos_to_string(cpos).." mode "..tostring(mode or "default"))
-- minetest.log("action", "[vl_terraforming] no ground at starting position "..minetest.pos_to_string(cpos).." surface "..tostring(surface or "default"))
return nil, nil
end
local ys = { pos.y }
@ -239,21 +240,19 @@ function vl_terraforming.find_level(cpos, size, tolerance, mode)
table.sort(ys)
tolerance = tolerance or 8
if tolerance == "min" then
cpos.y = ys[1] + 1
return cpos, surface_material
end
if tolerance == "max" then
cpos.y = ys[#ys] + 1
return cpos, surface_material
end
-- well supported base, not too uneven?
if #ys < 5 or min(ys[#ys-1]-ys[1], ys[#ys]-ys[2]) > tolerance then
-- minetest.log("action", "[vl_terraforming] ground too uneven: "..#ys.." positions: "..({dump(ys):gsub("[\n\t ]+", " ")})[1]
-- .." tolerance "..tostring(#ys > 2 and min(ys[#ys-1]-ys[1], ys[#ys]-ys[2])).." > "..tolerance)
return nil, nil
end
cpos.y = floor(0.5 * (ys[floor(1 + (#ys - 1) * 0.5)] + ys[ceil(1 + (#ys - 1) * 0.5)]) + 1) -- median except for largest, rounded, over surface
return cpos, surface_material
if mode == "min" then
pos.y = ys[1]
elseif mode == "max" then
pos.y = ys[#ys]
else -- median except for largest
pos.y = floor(0.5 * (ys[floor(1 + (#ys - 1) * 0.5)] + ys[ceil(1 + (#ys - 1) * 0.5)])) -- rounded
end
return pos, surface_material
end

View File

@ -11,8 +11,9 @@ local is_solid_not_tree = vl_terraforming._is_solid_not_tree
-- @return position and material of surface
function vl_terraforming.find_ground_vm(vm, pos)
if not pos then return nil, nil end
local get_node = vm.get_node_at
pos = vector_copy(pos)
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if cur.name == "ignore" then
local e1, e2 = vm:get_emerged_area()
minetest.log("warning", "find_ground with invalid position (outside of emerged area?) at "..minetest.pos_to_string(pos)
@ -23,7 +24,7 @@ function vl_terraforming.find_ground_vm(vm, pos)
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." over "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
@ -38,7 +39,7 @@ function vl_terraforming.find_ground_vm(vm, pos)
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
@ -60,8 +61,9 @@ local find_ground_vm = vl_terraforming.find_ground_vm
-- @return position and material of surface
function vl_terraforming.find_under_air_vm(vm, pos)
if not pos then return nil, nil end
local get_node = vm.get_node_at
pos = vector_copy(pos)
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if cur.name == "ignore" then
local e1, e2 = vm:get_emerged_area()
minetest.log("warning", "find_under_air with invalid position (outside of emerged area?) at "..minetest.pos_to_string(pos)
@ -72,7 +74,7 @@ function vl_terraforming.find_under_air_vm(vm, pos)
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." over "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
@ -88,7 +90,7 @@ function vl_terraforming.find_under_air_vm(vm, pos)
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
@ -108,8 +110,9 @@ local find_under_air_vm = vl_terraforming.find_under_air_vm
-- @return position and material of surface
function vl_terraforming.find_liquid_surface_vm(vm, pos)
if not pos then return nil, nil end
local get_node = vm.get_node_at
pos = vector_copy(pos)
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if cur.name == "ignore" then
local e1, e2 = vm:get_emerged_area()
minetest.log("warning", "find_liquid_surface with invalid position (outside of emerged area?) at "..minetest.pos_to_string(pos)
@ -120,7 +123,7 @@ function vl_terraforming.find_liquid_surface_vm(vm, pos)
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." over "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
@ -136,7 +139,7 @@ function vl_terraforming.find_liquid_surface_vm(vm, pos)
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
@ -160,8 +163,9 @@ local find_liquid_surface_vm = vl_terraforming.find_liquid_surface_vm
-- @return position and material of surface
function vl_terraforming.find_under_water_surface_vm(vm, pos)
if not pos then return nil, nil end
local get_node = vm.get_node_at
pos = vector_copy(pos)
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if cur.name == "ignore" then
local e1, e2 = vm:get_emerged_area()
minetest.log("warning", "find_under_water_surface with invalid position (outside of emerged area?) at "..minetest.pos_to_string(pos)
@ -172,7 +176,7 @@ function vl_terraforming.find_under_water_surface_vm(vm, pos)
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." over "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
@ -188,7 +192,7 @@ function vl_terraforming.find_under_water_surface_vm(vm, pos)
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(pos)
local cur = get_node(vm, pos)
if not cur or cur.name == "ignore" then
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil
@ -211,18 +215,19 @@ local find_under_water_surface_vm = vl_terraforming.find_under_water_surface_vm
-- @param vm VoxelManip: to read data
-- @param cpos vector: center
-- @param size vector: area size
-- @param tolerance number or string: maximum height difference allowed, default 8.
-- @param mode string: "solid" (default), "liquid_surface", "under_air"
-- @param tolerance number or string: maximum height difference allowed, default 8,
-- @param surface string: "solid" (default), "liquid_surface", "under_air"
-- @param mode string: "median" (default), "min" and "max"
-- @return position over surface, surface material (or nil, nil)
function vl_terraforming.find_level_vm(vm, cpos, size, tolerance, mode)
local find_ground = find_ground_vm
if mode == "liquid_surface" or mode == "liquid" then find_ground = find_liquid_surface_vm end
if mode == "under_water" or mode == "water" then find_ground = find_under_water_surface_vm end
if mode == "under_air" then find_ground = find_under_air_vm end
function vl_terraforming.find_level_vm(vm, cpos, size, tolerance, surface, mode)
local _find_ground = find_ground_vm
if surface == "liquid_surface" or surface == "liquid" then _find_ground = find_liquid_surface_vm end
if surface == "under_water" or surface == "water" then _find_ground = find_under_water_surface_vm end
if surface == "under_air" then _find_ground = find_under_air_vm end
-- begin at center, then top-left and clockwise
local pos, surface_material = find_ground(vm, cpos)
local pos, surface_material = _find_ground(vm, cpos)
if not pos then
-- minetest.log("action", "[vl_terraforming] no ground at starting position "..minetest.pos_to_string(cpos).." mode "..tostring(mode or "default"))
-- minetest.log("action", "[vl_terraforming] no ground at starting position "..minetest.pos_to_string(cpos).." surface "..tostring(surface or "default"))
return nil, nil
end
local ys = { pos.y }
@ -230,38 +235,36 @@ function vl_terraforming.find_level_vm(vm, cpos, size, tolerance, mode)
if size.x == 1 and size.z == 1 then return pos end
-- move to top left corner
pos.x, pos.z = pos.x - floor((size.x-1)/2), pos.z - floor((size.z-1)/2)
local pos_c = find_ground(vm, pos)
local pos_c = _find_ground(vm, pos)
if pos_c then table.insert(ys, pos_c.y) end
-- move to top right corner
pos.x = pos.x + size.x - 1
local pos_c = find_ground(vm, pos)
local pos_c = _find_ground(vm, pos)
if pos_c then table.insert(ys, pos_c.y) end
-- move to bottom right corner
pos.z = pos.z + size.z - 1
local pos_c = find_ground(vm, pos)
local pos_c = _find_ground(vm, pos)
if pos_c then table.insert(ys, pos_c.y) end
-- move to bottom left corner
pos.x = pos.x - (size.x - 1)
local pos_c = find_ground(vm, pos)
local pos_c = _find_ground(vm, pos)
if pos_c then table.insert(ys, pos_c.y) end
table.sort(ys)
tolerance = tolerance or 8
if tolerance == "min" then
cpos.y = ys[1] + 1
return cpos, surface_material
end
if tolerance == "max" then
cpos.y = ys[#ys] + 1
return cpos, surface_material
end
-- well supported base, not too uneven?
if #ys < 5 or min(ys[#ys-1]-ys[1], ys[#ys]-ys[2]) > tolerance then
-- minetest.log("action", "[vl_terraforming] ground too uneven: "..#ys.." positions: "..({dump(ys):gsub("[\n\t ]+", " ")})[1]
-- .." tolerance "..tostring(#ys > 2 and min(ys[#ys-1]-ys[1], ys[#ys]-ys[2])).." > "..tolerance)
return nil, nil
end
cpos.y = floor(0.5 * (ys[floor(1 + (#ys - 1) * 0.5)] + ys[ceil(1 + (#ys - 1) * 0.5)]) + 1) -- median except for largest, rounded, over surface
return cpos, surface_material
if mode == "min" then
pos.y = ys[1]
elseif mode == "max" then
pos.y = ys[#ys]
else -- median except for largest
pos.y = floor(0.5 * (ys[floor(1 + (#ys - 1) * 0.5)] + ys[ceil(1 + (#ys - 1) * 0.5)])) -- rounded
end
return pos, surface_material
end

View File

@ -1,6 +1,17 @@
local get_node = core.get_node
local swap_node = core.swap_node
--- node that is used to place air
vl_terraforming._AIR = {name = "air"}
--- immutable nodes where we have to stop
-- @param node string or Node: node or node name
-- @return true if this must never be changed
function vl_terraforming._immutable(node)
local name = node.name or node
return name == "ignore" or name == "mcl_core:bedrock"
end
--- fairly strict: air, ignore, or no_paths marker
-- @param node string or Node: node or node name
-- @return true for air and ignore nodes
@ -13,31 +24,48 @@ end
-- @param node LUA node or node name
-- @return truthy when solid but not tree/decoration/fungi
function vl_terraforming._is_solid_not_tree(node)
local name = node.name or node
local name = node.name
if name == "air" or name == "ignore" or name == "mcl_villages:no_paths" or name == "mcl_core:bedrock" then return false end
if name == "mcl_nether:soul_sand" then return true end -- not "solid". Other exceptions we need?
if name == "mcl_nether:nether_wart_block" then return false end -- crimson forest, treat as tree
-- is deco_block if name == "mcl_crimson:warped_wart_block" then return false end -- warped forest, treat as tree
-- is deco_block if name == "mcl_crimson:shroomlight" then return false end -- crimson forest, treat as tree
-- is deco_block if name == "mcl_core:snow" then return false end
if name == "mcl_nether:soul_sand" then return true end -- not "walkable". Other exceptions we need?
if name == "mcl_crimson:crimson_hyphae" then return false end -- crimson forest, treat as tree
if name == "mcl_nether:nether_wart_block" then return false end -- crimson forest, treat as leaves
if name == "mcl_crimson:warped_hyphae" then return false end -- warped forest, treat as tree
if name == "mcl_crimson:warped_wart_block" then return false end -- warped forest, treat as leaves
if name == "mcl_crimson:shroomlight" then return false end -- crimson forest, treat as tree
if name == "mcl_core:snow" then return false end
-- is walkable if name == "mcl_core:snowblock" then return true end
local meta = minetest.registered_items[name]
local groups = meta and meta.groups
return meta and meta.walkable and not (groups and ((groups.deco_block or 0) > 0 or (groups.tree or 0) > 0 or (groups.leaves or 0) > 0 or (groups.plant or 0) > 0))
return groups and meta.walkable and not ((groups.tree or 0) > 0 or (groups.leaves or 0) > 0 or (groups.plant or 0) > 0 or (groups.huge_mushroom or 0) > 0)
end
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
--- check if a node is tree
--- check if a node is tree or leaves
-- @param node string or Node: node or node name
-- @return true for tree, leaves
-- @return true for tree or leaves, also other compostable things
function vl_terraforming._is_tree_or_leaves(node)
local name = node.name or node
if name == "mcl_crimson:crimson_hyphae" then return true end -- crimson forest, treat as tree
if name == "mcl_nether:nether_wart_block" then return true end -- crimson forest, treat as leaves
if name == "mcl_crimson:warped_hyphae" then return true end -- warped forest, treat as tree
if name == "mcl_crimson:warped_wart_block" then return true end -- warped forest, treat as leaves
if name == "mcl_crimson:shroomlight" then return true end -- crimson forest, treat as tree
local meta = minetest.registered_items[node]
local groups = meta and meta.groups
return groups and ((groups.tree or 0) > 0 or (groups.leaves or 0) > 0 or (groups.plant or 0) > 0 or (groups.huge_mushroom or 0) > 0)
end
--- check if a node is tree trunk
-- @param node string or Node: node or node name
-- @return true for tree, but not leaves
function vl_terraforming._is_tree_not_leaves(node)
local name = node.name or node
if name == "air" or name == "ignore" or name == "mcl_villages:no_paths" then return false end
-- if name == "mcl_nether:nether_wart_block" then return true end -- crimson forest, treat as tree
-- if name == "mcl_crimson:warped_wart_block" then return true end -- warped forest, treat as tree
-- if name == "mcl_crimson:shroomlight" then return true end -- crimson forest, treat as tree
if name == "mcl_crimson:crimson_hyphae" then return true end -- crimson forest, treat as tree
if name == "mcl_crimson:warped_hyphae" then return true end -- warped forest, treat as tree
local meta = minetest.registered_items[name]
return meta and meta.groups and (meta.groups.tree or 0) > 0
local groups = meta and meta.groups
return groups and ((groups.tree or 0) > 0 or (groups.huge_mushroom_stem or 0) > 0)
end
--- check if a node is liquid