402 lines
11 KiB
Lua
402 lines
11 KiB
Lua
local m_abs = math.abs
|
|
local m_acos = math.acos
|
|
local m_asin = math.asin
|
|
local m_atan2 = math.atan2
|
|
local m_cos = math.cos
|
|
local m_floor = math.floor
|
|
local m_min = math.min
|
|
local m_max = math.max
|
|
local m_pi = math.pi
|
|
local m_pow = math.pow
|
|
local m_random = math.random
|
|
local m_sin = math.sin
|
|
|
|
local v_add = vector.add
|
|
local v_new = vector.new
|
|
local v_sort = vector.sort
|
|
local v_sub = vector.subtract
|
|
|
|
local in_bounds = futil.math.in_bounds
|
|
local bound = futil.math.bound
|
|
|
|
local mapblock_size = 16 -- can be redefined, but effectively hard-coded
|
|
local chunksize = m_floor(tonumber(minetest.settings:get("chunksize")) or 5) -- # of mapblocks in a chunk (1 dim)
|
|
local chunksize_nodes = mapblock_size * chunksize -- # of nodes in a chunk (1 dim)
|
|
local max_mapgen_limit = 31007 -- hard coded
|
|
local mapgen_limit =
|
|
bound(0, m_floor(tonumber(minetest.settings:get("mapgen_limit")) or max_mapgen_limit), max_mapgen_limit)
|
|
local mapgen_limit_b = m_floor(mapgen_limit / mapblock_size) -- # of mapblocks
|
|
|
|
-- *actual* minimum and maximum coordinates - one mapblock short of the theoretical min and max
|
|
local map_min_i = (-mapgen_limit_b * mapblock_size) + chunksize_nodes
|
|
local map_max_i = ((mapgen_limit_b + 1) * mapblock_size - 1) - chunksize_nodes
|
|
|
|
local map_min_p = v_new(map_min_i, map_min_i, map_min_i)
|
|
local map_max_p = v_new(map_max_i, map_max_i, map_max_i)
|
|
|
|
futil.vector = {}
|
|
|
|
function futil.vector.get_bounds(pos, radius)
|
|
return v_sub(pos, radius), v_add(pos, radius)
|
|
end
|
|
|
|
futil.get_bounds = futil.vector.get_bounds
|
|
|
|
function futil.vector.get_world_bounds()
|
|
return map_min_p, map_max_p
|
|
end
|
|
|
|
futil.get_world_bounds = futil.vector.get_world_bounds
|
|
|
|
function futil.vector.get_blockpos(pos)
|
|
return v_new(m_floor(pos.x / mapblock_size), m_floor(pos.y / mapblock_size), m_floor(pos.z / mapblock_size))
|
|
end
|
|
|
|
futil.get_blockpos = futil.vector.get_blockpos
|
|
|
|
function futil.vector.get_block_min(blockpos)
|
|
return v_new(blockpos.x * mapblock_size, blockpos.y * mapblock_size, blockpos.z * mapblock_size)
|
|
end
|
|
|
|
function futil.vector.get_block_max(blockpos)
|
|
return v_new(
|
|
blockpos.x * mapblock_size + (mapblock_size - 1),
|
|
blockpos.y * mapblock_size + (mapblock_size - 1),
|
|
blockpos.z * mapblock_size + (mapblock_size - 1)
|
|
)
|
|
end
|
|
|
|
function futil.vector.get_block_bounds(blockpos)
|
|
return futil.vector.get_block_min(blockpos), futil.vector.get_block_max(blockpos)
|
|
end
|
|
|
|
futil.get_block_bounds = futil.vector.get_block_bounds
|
|
|
|
function futil.vector.get_block_center(blockpos)
|
|
return v_add(futil.vector.get_block_min(blockpos), 8) -- 8 = 16 / 2
|
|
end
|
|
|
|
function futil.vector.get_chunkpos(pos)
|
|
return v_new(
|
|
m_floor((pos.x - map_min_i) / chunksize_nodes),
|
|
m_floor((pos.y - map_min_i) / chunksize_nodes),
|
|
m_floor((pos.z - map_min_i) / chunksize_nodes)
|
|
)
|
|
end
|
|
|
|
futil.get_chunkpos = futil.vector.get_chunkpos
|
|
|
|
function futil.vector.get_chunk_bounds(chunkpos)
|
|
return v_new(
|
|
chunkpos.x * chunksize_nodes + map_min_i,
|
|
chunkpos.y * chunksize_nodes + map_min_i,
|
|
chunkpos.z * chunksize_nodes + map_min_i
|
|
),
|
|
v_new(
|
|
chunkpos.x * chunksize_nodes + map_min_i + (chunksize_nodes - 1),
|
|
chunkpos.y * chunksize_nodes + map_min_i + (chunksize_nodes - 1),
|
|
chunkpos.z * chunksize_nodes + map_min_i + (chunksize_nodes - 1)
|
|
)
|
|
end
|
|
|
|
futil.get_chunk_bounds = futil.vector.get_chunk_bounds
|
|
|
|
function futil.vector.formspec_pos(pos)
|
|
return ("%i,%i,%i"):format(pos.x, pos.y, pos.z)
|
|
end
|
|
|
|
futil.formspec_pos = futil.vector.formspec_pos
|
|
|
|
function futil.vector.iterate_area(minp, maxp)
|
|
minp, maxp = v_sort(minp, maxp)
|
|
local min_x = minp.x
|
|
local min_z = minp.z
|
|
|
|
local x = min_x - 1
|
|
local y = minp.y
|
|
local z = min_z
|
|
|
|
local max_x = maxp.x
|
|
local max_y = maxp.y
|
|
local max_z = maxp.z
|
|
|
|
return function()
|
|
if y > max_y then
|
|
return
|
|
end
|
|
|
|
x = x + 1
|
|
if x > max_x then
|
|
x = min_x
|
|
z = z + 1
|
|
end
|
|
|
|
if z > max_z then
|
|
z = min_z
|
|
y = y + 1
|
|
end
|
|
|
|
if y <= max_y then
|
|
return v_new(x, y, z)
|
|
end
|
|
end
|
|
end
|
|
|
|
futil.iterate_area = futil.vector.iterate_area
|
|
|
|
function futil.vector.iterate_volume(pos, radius)
|
|
return futil.iterate_area(futil.get_bounds(pos, radius))
|
|
end
|
|
|
|
futil.iterate_volume = futil.vector.iterate_volume
|
|
|
|
function futil.is_pos_in_bounds(minp, pos, maxp)
|
|
minp, maxp = v_sort(minp, maxp)
|
|
return (in_bounds(minp.x, pos.x, maxp.x) and in_bounds(minp.y, pos.y, maxp.y) and in_bounds(minp.z, pos.z, maxp.z))
|
|
end
|
|
|
|
function futil.vector.is_inside_world_bounds(pos)
|
|
return futil.is_pos_in_bounds(map_min_p, pos, map_max_p)
|
|
end
|
|
|
|
function futil.vector.is_blockpos_inside_world_bounds(blockpos)
|
|
return futil.vector.is_inside_world_bounds(futil.vector.get_block_min(blockpos))
|
|
end
|
|
|
|
futil.is_inside_world_bounds = futil.vector.is_inside_world_bounds
|
|
|
|
function futil.vector.bound_position_to_world(pos)
|
|
return v_new(
|
|
bound(map_min_i, pos.x, map_max_i),
|
|
bound(map_min_i, pos.y, map_max_i),
|
|
bound(map_min_i, pos.z, map_max_i)
|
|
)
|
|
end
|
|
|
|
futil.bound_position_to_world = futil.vector.bound_position_to_world
|
|
|
|
function futil.vector.volume(pos1, pos2)
|
|
local minp, maxp = v_sort(pos1, pos2)
|
|
return (maxp.x - minp.x + 1) * (maxp.y - minp.y + 1) * (maxp.z - minp.z + 1)
|
|
end
|
|
|
|
function futil.split_region_by_mapblock(pos1, pos2, num_blocks)
|
|
local chunk_size = 16 * (num_blocks or 1)
|
|
local chunk_span = chunk_size - 1
|
|
|
|
pos1, pos2 = vector.sort(pos1, pos2)
|
|
|
|
local min_x = pos1.x
|
|
local min_y = pos1.y
|
|
local min_z = pos1.z
|
|
local max_x = pos2.x
|
|
local max_y = pos2.y
|
|
local max_z = pos2.z
|
|
|
|
local x1 = min_x - (min_x % chunk_size)
|
|
local x2 = max_x - (max_x % chunk_size) + chunk_span
|
|
local y1 = min_y - (min_y % chunk_size)
|
|
local y2 = max_y - (max_y % chunk_size) + chunk_span
|
|
local z1 = min_z - (min_z % chunk_size)
|
|
local z2 = max_z - (max_z % chunk_size) + chunk_span
|
|
|
|
local chunks = {}
|
|
for y = y1, y2, chunk_size do
|
|
local y_min = m_max(min_y, y)
|
|
local y_max = m_min(max_y, y + chunk_span)
|
|
|
|
for x = x1, x2, chunk_size do
|
|
local x_min = m_max(min_x, x)
|
|
local x_max = m_min(max_x, x + chunk_span)
|
|
|
|
for z = z1, z2, chunk_size do
|
|
local z_min = m_max(min_z, z)
|
|
local z_max = m_min(max_z, z + chunk_span)
|
|
|
|
chunks[#chunks + 1] = { v_new(x_min, y_min, z_min), v_new(x_max, y_max, z_max) }
|
|
end
|
|
end
|
|
end
|
|
|
|
return chunks
|
|
end
|
|
|
|
function futil.random_unit_vector()
|
|
local u = m_random()
|
|
local v = m_random()
|
|
local lambda = m_acos(2 * u - 1) - (m_pi / 2)
|
|
local phi = 2 * m_pi * v
|
|
return v_new(m_cos(lambda) * m_cos(phi), m_cos(lambda) * m_sin(phi), m_sin(lambda))
|
|
end
|
|
|
|
---- https://math.stackexchange.com/a/205589
|
|
--function futil.random_unit_vector_in_solid_angle(theta, direction)
|
|
-- local z = m_random() * (1 - m_cos(theta)) - 1
|
|
-- local phi = m_random() * 2 * m_pi
|
|
-- local z2 = (1 - z*z) ^ 0.5
|
|
-- local ruv = v_new(z2 * m_cos(phi), z2 * m_sin(phi), z)
|
|
-- direction = direction:normalize()
|
|
-- ...
|
|
--end
|
|
|
|
function futil.is_indoors(pos, distance, trials, hits_needed)
|
|
distance = distance or 20
|
|
trials = trials or 11
|
|
hits_needed = hits_needed or 9
|
|
local num_hits = 0
|
|
for _ = 1, trials do
|
|
local ruv = futil.random_unit_vector()
|
|
local target = pos + (distance * ruv)
|
|
local hit = Raycast(pos, target, false, false)()
|
|
if hit then
|
|
num_hits = num_hits + 1
|
|
if num_hits == hits_needed then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function futil.can_see_sky(pos, distance, trials, max_hits)
|
|
distance = distance or 200
|
|
trials = trials or 11
|
|
max_hits = max_hits or 9
|
|
local num_hits = 0
|
|
for _ = 1, trials do
|
|
local ruv = futil.random_unit_vector()
|
|
ruv.y = m_abs(ruv.y) -- look up, not at the ground
|
|
local target = pos + (distance * ruv)
|
|
local hit = Raycast(pos, target, false, false)()
|
|
if hit then
|
|
num_hits = num_hits + 1
|
|
if num_hits > max_hits then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function futil.vector.is_valid_position(pos)
|
|
if type(pos) ~= "table" then
|
|
return false
|
|
elseif not (type(pos.x) == "number" and type(pos.y) == "number" and type(pos.z) == "number") then
|
|
return false
|
|
else
|
|
return futil.is_inside_world_bounds(vector.round(pos))
|
|
end
|
|
end
|
|
|
|
-- minetest.hash_node_position only works with integer coordinates
|
|
function futil.vector.hash(pos)
|
|
return string.format("%a:%a:%a", pos.x, pos.y, pos.z)
|
|
end
|
|
|
|
function futil.vector.unhash(string)
|
|
local x, y, z = string:match("^([^:]+):([^:]+):([^:]+)$")
|
|
x, y, z = tonumber(x), tonumber(y), tonumber(z)
|
|
if not (x and y and z) then
|
|
return
|
|
end
|
|
return v_new(x, y, z)
|
|
end
|
|
|
|
function futil.vector.ldistance(pos1, pos2, p)
|
|
if p == math.huge then
|
|
return m_max(m_abs(pos1.x - pos2.x), m_abs(pos1.y - pos2.y), m_abs(pos1.z - pos2.z))
|
|
else
|
|
return m_pow(
|
|
m_pow(m_abs(pos1.x - pos2.x), p) + m_pow(m_abs(pos1.y - pos2.y), p) + m_pow(m_abs(pos1.z - pos2.z), p),
|
|
1 / p
|
|
)
|
|
end
|
|
end
|
|
|
|
function futil.vector.round(pos, mult)
|
|
local round = futil.math.round
|
|
return v_new(round(pos.x, mult), round(pos.y, mult), round(pos.z, mult))
|
|
end
|
|
|
|
-- https://msl.cs.uiuc.edu/planning/node102.html
|
|
function futil.vector.rotation_to_matrix(rotation)
|
|
local cosp = m_cos(rotation.x)
|
|
local sinp = m_sin(rotation.x)
|
|
local pitch = {
|
|
{ cosp, 0, sinp },
|
|
{ 0, 1, 0 },
|
|
{ -sinp, 0, cosp },
|
|
}
|
|
local cosy = m_cos(rotation.y)
|
|
local siny = m_sin(rotation.y)
|
|
local yaw = {
|
|
{ cosy, -siny, 0 },
|
|
{ siny, cosy, 0 },
|
|
{ 0, 0, 1 },
|
|
}
|
|
local cosr = m_cos(rotation.z)
|
|
local sinr = m_sin(rotation.z)
|
|
local roll = {
|
|
{ 1, 0, 0 },
|
|
{ 0, cosr, -sinr },
|
|
{ 0, sinr, cosr },
|
|
}
|
|
return futil.matrix.multiply(futil.matrix.multiply(yaw, pitch), roll)
|
|
end
|
|
|
|
-- https://msl.cs.uiuc.edu/planning/node103.html
|
|
function futil.vector.matrix_to_rotation(matrix)
|
|
local pitch = m_atan2(matrix[2][1], matrix[1][1])
|
|
local yaw = m_asin(-matrix[3][1])
|
|
local roll = m_atan2(matrix[3][2], matrix[3][3])
|
|
return v_new(pitch, yaw, roll)
|
|
end
|
|
|
|
function futil.vector.inverse_rotation(rot)
|
|
-- since the determinant of a rotation matrix is 1, the inverse is just the transpose and i don't have to write
|
|
-- a matrix inverter
|
|
return futil.vector.matrix_to_rotation(futil.matrix.transpose(futil.vector.rotation_to_matrix(rot)))
|
|
end
|
|
|
|
-- assumed in radians
|
|
function futil.vector.compose_rotations(rot1, rot2)
|
|
local m1 = futil.vector.rotation_to_matrix(rot1)
|
|
local m2 = futil.vector.rotation_to_matrix(rot2)
|
|
return futil.vector.matrix_to_rotation(futil.matrix.multiply(m1, m2))
|
|
end
|
|
|
|
-- https://palitri.com/vault/stuff/maths/Rays%20closest%20point.pdf
|
|
-- this was originally part of the ballistics mod but i don't need it there anymore
|
|
function futil.vector.closest_point_to_two_lines(last_pos, last_vel, cur_pos, cur_vel, threshold)
|
|
threshold = threshold or 0.0001 -- if certain values are too close to 0, the results will not be good
|
|
local a = cur_vel
|
|
local b = last_vel
|
|
local a2 = a:dot(a)
|
|
if a2 < threshold then
|
|
return
|
|
end
|
|
local b2 = b:dot(b)
|
|
if b2 < threshold then
|
|
return
|
|
end
|
|
local ab = a:dot(b)
|
|
local denom = (a2 * b2) - (ab * ab)
|
|
if denom < threshold then
|
|
return
|
|
end
|
|
local A = cur_pos
|
|
local B = last_pos
|
|
local c = last_pos - cur_pos
|
|
local bc = b:dot(c)
|
|
local ac = a:dot(c)
|
|
local D = A + a * ((ac * b2 - ab * bc) / denom)
|
|
local E = B + b * ((ab * ac - bc * a2) / denom)
|
|
return (D + E) / 2
|
|
end
|
|
|
|
function futil.vector.v2f_to_float_32(v)
|
|
return {
|
|
x = futil.math.to_float32(v.x),
|
|
y = futil.math.to_float32(v.y),
|
|
}
|
|
end
|