VoxeLibre/mods/MAPGEN/vl_terraforming/level.lua
2024-10-29 19:28:43 +01:00

267 lines
10 KiB
Lua

local min = math.min
local floor = math.floor
local ceil = math.ceil
local vector_copy = vector.copy
local is_liquid = vl_terraforming._is_liquid
local is_solid_not_tree = vl_terraforming._is_solid_not_tree
--- Find ground below a given position
-- @param vm VoxelManip: buffer
-- @param pos vector: Start position
-- @return position and material of surface
function vl_terraforming.find_ground_vm(vm, pos)
if not pos then return nil, nil end
pos = vector_copy(pos)
local cur = vm:get_node_at(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)
..": "..tostring(cur and cur.name).." area: "..minetest.pos_to_string(e1).." "..minetest.pos_to_string(e2))
return nil
end
if is_solid_not_tree(cur) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(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
end
if not is_solid_not_tree(cur) then
pos.y = pos.y - 1
return pos, prev
end
prev = cur
end
else -- find down
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(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
end
if is_liquid(cur) then
return nil
end
if is_solid_not_tree(cur) then
return pos, cur
end
end
end
end
local find_ground_vm = vl_terraforming.find_ground_vm
--- Find ground or liquid surface for a given position
-- @param vm VoxelManip: buffer
-- @param pos vector: Start position
-- @return position and material of surface
function vl_terraforming.find_under_air_vm(vm, pos)
if not pos then return nil, nil end
pos = vector_copy(pos)
local cur = vm:get_node_at(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)
..": "..tostring(cur and cur.name).." area: "..minetest.pos_to_string(e1).." "..minetest.pos_to_string(e2))
return nil
end
if is_solid_not_tree(cur) or is_liquid(cur) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(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
end
if not is_solid_not_tree(cur) and not is_liquid(cur) then
pos.y = pos.y - 1
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..tostring(prev and prev.name).." under "..tostring(cur and cur.name))
return pos, prev
end
prev = cur
end
else -- find down
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(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
end
if is_solid_not_tree(cur) or is_liquid(cur) then
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..(cur and cur.name).." over "..(prev and prev.name))
return pos, cur
end
end
end
end
local find_under_air_vm = vl_terraforming.find_under_air_vm
--- Find liquid surface for a given position
-- @param vm VoxelManip: buffer
-- @param pos vector: Start position
-- @return position and material of surface
function vl_terraforming.find_liquid_surface_vm(vm, pos)
if not pos then return nil, nil end
pos = vector_copy(pos)
local cur = vm:get_node_at(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)
..": "..tostring(cur and cur.name).." area: "..minetest.pos_to_string(e1).." "..minetest.pos_to_string(e2))
return nil
end
if is_liquid(cur) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(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
end
if not is_liquid(cur) then
pos.y = pos.y - 1
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..tostring(prev and prev.name).." under "..tostring(cur and cur.name))
return pos, prev
end
prev = cur
end
else -- find down
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(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
end
if is_solid_not_tree(cur) 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
end
if is_liquid(cur) then
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..(cur and cur.name).." over "..(prev and prev.name))
return pos, cur
end
end
end
end
local find_liquid_surface_vm = vl_terraforming.find_liquid_surface_vm
--- Find under water surface for a given position
-- @param vm VoxelManip: buffer
-- @param pos vector: Start position
-- @return position and material of surface
function vl_terraforming.find_under_water_surface_vm(vm, pos)
if not pos then return nil, nil end
pos = vector_copy(pos)
local cur = vm:get_node_at(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)
..": "..tostring(cur and cur.name).." area: "..minetest.pos_to_string(e1).." "..minetest.pos_to_string(e2))
return nil
end
if is_solid_not_tree(cur) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
local cur = vm:get_node_at(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
end
if is_liquid(cur) then
pos.y = pos.y - 1
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..tostring(prev and prev.name).." under "..tostring(cur and cur.name))
return pos, prev
end
prev = cur
end
else -- find down
while true do
pos.y = pos.y - 1
local prev = cur
local cur = vm:get_node_at(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
end
if is_solid_not_tree(cur) then
if is_liquid(prev) then
-- minetest.log("action", "Found surface: "..minetest.pos_to_string(pos).." "..(cur and cur.name).." over "..(prev and prev.name))
return pos, cur
else
-- minetest.log("action", "No ground, "..tostring(cur and cur.name).." below "..tostring(prev and prev.name).." at "..minetest.pos_to_string(pos))
return nil, nil
end
end
end
end
end
local find_under_water_surface_vm = vl_terraforming.find_under_water_surface_vm
--- find suitable height for a structure of this size
-- @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"
-- @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
-- begin at center, then top-left and clockwise
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"))
return nil, nil
end
local ys = { pos.y }
pos.y = pos.y + 1 -- position above surface
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)
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)
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)
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)
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
end