2024-08-23 10:55:30 +02:00
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
2024-10-29 19:41:33 +01:00
local get_node = core.get_node
2024-08-23 10:55:30 +02:00
--- Find ground below a given position
-- @param pos vector: Start position
-- @return position and material of surface
2024-10-29 19:41:33 +01:00
function vl_terraforming . find_ground ( pos )
2024-08-23 10:55:30 +02:00
if not pos then return nil , nil end
pos = vector_copy ( pos )
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-08-23 10:55:30 +02:00
if cur.name == " ignore " then
2024-09-05 01:03:33 +02:00
minetest.log ( " warning " , " find_ground with invalid position (outside of emerged area?) at " .. minetest.pos_to_string ( pos )
2024-10-29 19:41:33 +01:00
.. " : " .. tostring ( cur and cur.name ) )
2024-08-23 10:55:30 +02:00
return nil
end
if is_solid_not_tree ( cur ) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-08-23 10:55:30 +02:00
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
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-08-23 10:55:30 +02:00
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
2024-10-29 19:41:33 +01:00
local find_ground = vl_terraforming.find_ground
2024-08-23 10:55:30 +02:00
--- Find ground or liquid surface for a given position
-- @param pos vector: Start position
-- @return position and material of surface
2024-10-29 19:41:33 +01:00
function vl_terraforming . find_under_air ( pos )
2024-08-23 10:55:30 +02:00
if not pos then return nil , nil end
pos = vector_copy ( pos )
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-08-23 10:55:30 +02:00
if cur.name == " ignore " then
2024-09-05 01:03:33 +02:00
minetest.log ( " warning " , " find_under_air with invalid position (outside of emerged area?) at " .. minetest.pos_to_string ( pos )
2024-10-29 19:41:33 +01:00
.. " : " .. tostring ( cur and cur.name ) )
2024-08-23 10:55:30 +02:00
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
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-08-23 10:55:30 +02:00
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
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-08-23 10:55:30 +02:00
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
2024-10-29 19:41:33 +01:00
local find_under_air = vl_terraforming.find_under_air
2024-08-23 10:55:30 +02:00
--- Find liquid surface for a given position
-- @param pos vector: Start position
-- @return position and material of surface
2024-10-29 19:41:33 +01:00
function vl_terraforming . find_liquid_surface ( pos )
2024-08-23 10:55:30 +02:00
if not pos then return nil , nil end
pos = vector_copy ( pos )
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-08-23 10:55:30 +02:00
if cur.name == " ignore " then
2024-09-05 01:03:33 +02:00
minetest.log ( " warning " , " find_liquid_surface with invalid position (outside of emerged area?) at " .. minetest.pos_to_string ( pos )
2024-10-29 19:41:33 +01:00
.. " : " .. tostring ( cur and cur.name ) )
2024-08-23 10:55:30 +02:00
return nil
end
if is_liquid ( cur ) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-08-23 10:55:30 +02:00
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
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-08-23 10:55:30 +02:00
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
2024-10-29 19:41:33 +01:00
local find_liquid_surface = vl_terraforming.find_liquid_surface
2024-08-23 10:55:30 +02:00
2024-09-05 01:03:33 +02:00
--- Find under water surface for a given position
-- @param pos vector: Start position
-- @return position and material of surface
2024-10-29 19:41:33 +01:00
function vl_terraforming . find_under_water_surface ( pos )
2024-09-05 01:03:33 +02:00
if not pos then return nil , nil end
pos = vector_copy ( pos )
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-09-05 01:03:33 +02:00
if cur.name == " ignore " then
minetest.log ( " warning " , " find_under_water_surface with invalid position (outside of emerged area?) at " .. minetest.pos_to_string ( pos )
2024-10-29 19:41:33 +01:00
.. " : " .. tostring ( cur and cur.name ) )
2024-09-05 01:03:33 +02:00
return nil
end
if is_solid_not_tree ( cur ) then -- find up
local prev = cur
while true do
pos.y = pos.y + 1
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-09-05 01:03:33 +02:00
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
2024-10-29 19:41:33 +01:00
local cur = get_node ( pos )
2024-09-05 01:03:33 +02:00
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
2024-10-29 19:41:33 +01:00
local find_under_water_surface = vl_terraforming.find_under_water_surface
2024-09-05 01:03:33 +02:00
2024-08-23 10:55:30 +02:00
--- find suitable height for a structure of this size
-- @param cpos vector: center
-- @param size vector: area size
2024-10-31 20:14:29 +01:00
-- @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"
2024-08-23 10:55:30 +02:00
-- @return position over surface, surface material (or nil, nil)
2024-10-31 20:14:29 +01:00
function vl_terraforming . find_level ( cpos , size , tolerance , surface , mode )
2024-10-29 19:41:33 +01:00
local _find_ground = find_ground
2024-10-31 20:14:29 +01:00
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
2024-08-24 19:24:31 +02:00
-- begin at center, then top-left and clockwise
2024-10-29 19:41:33 +01:00
local pos , surface_material = _find_ground ( cpos )
2024-09-05 01:03:33 +02:00
if not pos then
2024-10-31 20:14:29 +01:00
-- minetest.log("action", "[vl_terraforming] no ground at starting position "..minetest.pos_to_string(cpos).." surface "..tostring(surface or "default"))
2024-09-05 01:03:33 +02:00
return nil , nil
end
2024-08-23 10:55:30 +02:00
local ys = { pos.y }
2024-08-24 19:24:31 +02:00
pos.y = pos.y + 1 -- position above surface
2024-08-23 10:55:30 +02:00
if size.x == 1 and size.z == 1 then return pos end
2024-08-24 19:24:31 +02:00
-- move to top left corner
pos.x , pos.z = pos.x - floor ( ( size.x - 1 ) / 2 ) , pos.z - floor ( ( size.z - 1 ) / 2 )
2024-10-29 19:41:33 +01:00
local pos_c = _find_ground ( pos )
2024-08-23 10:55:30 +02:00
if pos_c then table.insert ( ys , pos_c.y ) end
2024-08-24 19:24:31 +02:00
-- move to top right corner
pos.x = pos.x + size.x - 1
2024-10-29 19:41:33 +01:00
local pos_c = _find_ground ( pos )
2024-08-23 10:55:30 +02:00
if pos_c then table.insert ( ys , pos_c.y ) end
2024-08-24 19:24:31 +02:00
-- move to bottom right corner
pos.z = pos.z + size.z - 1
2024-10-29 19:41:33 +01:00
local pos_c = _find_ground ( pos )
2024-08-23 10:55:30 +02:00
if pos_c then table.insert ( ys , pos_c.y ) end
2024-08-24 19:24:31 +02:00
-- move to bottom left corner
pos.x = pos.x - ( size.x - 1 )
2024-10-29 19:41:33 +01:00
local pos_c = _find_ground ( pos )
2024-08-23 10:55:30 +02:00
if pos_c then table.insert ( ys , pos_c.y ) end
table.sort ( ys )
2024-11-01 00:28:11 +01:00
if # ys < 5 then return nil , nil end -- not fully supported
2024-08-23 10:55:30 +02:00
2024-11-01 00:28:11 +01:00
tolerance = tolerance or 6 -- default value
if mode == " min " then -- ignore the largest when using min
if ys [ # ys - 1 ] - ys [ 1 ] > tolerance then return nil , nil end
cpos.y = ys [ 1 ]
elseif mode == " max " then -- ignore the smallest when using max
if ys [ # ys ] - ys [ 2 ] > tolerance then return nil , nil end
cpos.y = ys [ # ys ]
else -- median
if 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 ) ] ) ) -- rounded
2024-10-31 20:14:29 +01:00
end
2024-11-01 00:28:11 +01:00
return cpos , surface_material
2024-08-23 10:55:30 +02:00
end