EinsDreiDreiSieben/mods/futil/minetest/vector.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