forked from Mirrorlandia_minetest/minetest
2332527765
Writing vectors as strings is very common and should belong to `vector.*`. `minetest.pos_to_string` is also too long to write, implies that one should only use it for positions and leaves no spaces after the commas.
259 lines
5.8 KiB
Lua
259 lines
5.8 KiB
Lua
|
|
vector = {}
|
|
|
|
function vector.new(a, b, c)
|
|
if type(a) == "table" then
|
|
assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()")
|
|
return {x=a.x, y=a.y, z=a.z}
|
|
elseif a then
|
|
assert(b and c, "Invalid arguments for vector.new()")
|
|
return {x=a, y=b, z=c}
|
|
end
|
|
return {x=0, y=0, z=0}
|
|
end
|
|
|
|
function vector.from_string(s, init)
|
|
local x, y, z, np = string.match(s, "^%s*%(%s*([^%s,]+)%s*[,%s]%s*([^%s,]+)%s*[,%s]" ..
|
|
"%s*([^%s,]+)%s*[,%s]?%s*%)()", init)
|
|
x = tonumber(x)
|
|
y = tonumber(y)
|
|
z = tonumber(z)
|
|
if not (x and y and z) then
|
|
return nil
|
|
end
|
|
return {x = x, y = y, z = z}, np
|
|
end
|
|
|
|
function vector.to_string(v)
|
|
return string.format("(%g, %g, %g)", v.x, v.y, v.z)
|
|
end
|
|
|
|
function vector.equals(a, b)
|
|
return a.x == b.x and
|
|
a.y == b.y and
|
|
a.z == b.z
|
|
end
|
|
|
|
function vector.length(v)
|
|
return math.hypot(v.x, math.hypot(v.y, v.z))
|
|
end
|
|
|
|
function vector.normalize(v)
|
|
local len = vector.length(v)
|
|
if len == 0 then
|
|
return {x=0, y=0, z=0}
|
|
else
|
|
return vector.divide(v, len)
|
|
end
|
|
end
|
|
|
|
function vector.floor(v)
|
|
return {
|
|
x = math.floor(v.x),
|
|
y = math.floor(v.y),
|
|
z = math.floor(v.z)
|
|
}
|
|
end
|
|
|
|
function vector.round(v)
|
|
return {
|
|
x = math.round(v.x),
|
|
y = math.round(v.y),
|
|
z = math.round(v.z)
|
|
}
|
|
end
|
|
|
|
function vector.apply(v, func)
|
|
return {
|
|
x = func(v.x),
|
|
y = func(v.y),
|
|
z = func(v.z)
|
|
}
|
|
end
|
|
|
|
function vector.distance(a, b)
|
|
local x = a.x - b.x
|
|
local y = a.y - b.y
|
|
local z = a.z - b.z
|
|
return math.hypot(x, math.hypot(y, z))
|
|
end
|
|
|
|
function vector.direction(pos1, pos2)
|
|
return vector.normalize({
|
|
x = pos2.x - pos1.x,
|
|
y = pos2.y - pos1.y,
|
|
z = pos2.z - pos1.z
|
|
})
|
|
end
|
|
|
|
function vector.angle(a, b)
|
|
local dotp = vector.dot(a, b)
|
|
local cp = vector.cross(a, b)
|
|
local crossplen = vector.length(cp)
|
|
return math.atan2(crossplen, dotp)
|
|
end
|
|
|
|
function vector.dot(a, b)
|
|
return a.x * b.x + a.y * b.y + a.z * b.z
|
|
end
|
|
|
|
function vector.cross(a, b)
|
|
return {
|
|
x = a.y * b.z - a.z * b.y,
|
|
y = a.z * b.x - a.x * b.z,
|
|
z = a.x * b.y - a.y * b.x
|
|
}
|
|
end
|
|
|
|
function vector.add(a, b)
|
|
if type(b) == "table" then
|
|
return {x = a.x + b.x,
|
|
y = a.y + b.y,
|
|
z = a.z + b.z}
|
|
else
|
|
return {x = a.x + b,
|
|
y = a.y + b,
|
|
z = a.z + b}
|
|
end
|
|
end
|
|
|
|
function vector.subtract(a, b)
|
|
if type(b) == "table" then
|
|
return {x = a.x - b.x,
|
|
y = a.y - b.y,
|
|
z = a.z - b.z}
|
|
else
|
|
return {x = a.x - b,
|
|
y = a.y - b,
|
|
z = a.z - b}
|
|
end
|
|
end
|
|
|
|
function vector.multiply(a, b)
|
|
if type(b) == "table" then
|
|
return {x = a.x * b.x,
|
|
y = a.y * b.y,
|
|
z = a.z * b.z}
|
|
else
|
|
return {x = a.x * b,
|
|
y = a.y * b,
|
|
z = a.z * b}
|
|
end
|
|
end
|
|
|
|
function vector.divide(a, b)
|
|
if type(b) == "table" then
|
|
return {x = a.x / b.x,
|
|
y = a.y / b.y,
|
|
z = a.z / b.z}
|
|
else
|
|
return {x = a.x / b,
|
|
y = a.y / b,
|
|
z = a.z / b}
|
|
end
|
|
end
|
|
|
|
function vector.offset(v, x, y, z)
|
|
return {x = v.x + x,
|
|
y = v.y + y,
|
|
z = v.z + z}
|
|
end
|
|
|
|
function vector.sort(a, b)
|
|
return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)},
|
|
{x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)}
|
|
end
|
|
|
|
local function sin(x)
|
|
if x % math.pi == 0 then
|
|
return 0
|
|
else
|
|
return math.sin(x)
|
|
end
|
|
end
|
|
|
|
local function cos(x)
|
|
if x % math.pi == math.pi / 2 then
|
|
return 0
|
|
else
|
|
return math.cos(x)
|
|
end
|
|
end
|
|
|
|
function vector.rotate_around_axis(v, axis, angle)
|
|
local cosangle = cos(angle)
|
|
local sinangle = sin(angle)
|
|
axis = vector.normalize(axis)
|
|
-- https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
|
|
local dot_axis = vector.multiply(axis, vector.dot(axis, v))
|
|
local cross = vector.cross(v, axis)
|
|
return vector.new(
|
|
cross.x * sinangle + (v.x - dot_axis.x) * cosangle + dot_axis.x,
|
|
cross.y * sinangle + (v.y - dot_axis.y) * cosangle + dot_axis.y,
|
|
cross.z * sinangle + (v.z - dot_axis.z) * cosangle + dot_axis.z
|
|
)
|
|
end
|
|
|
|
function vector.rotate(v, rot)
|
|
local sinpitch = sin(-rot.x)
|
|
local sinyaw = sin(-rot.y)
|
|
local sinroll = sin(-rot.z)
|
|
local cospitch = cos(rot.x)
|
|
local cosyaw = cos(rot.y)
|
|
local cosroll = math.cos(rot.z)
|
|
-- Rotation matrix that applies yaw, pitch and roll
|
|
local matrix = {
|
|
{
|
|
sinyaw * sinpitch * sinroll + cosyaw * cosroll,
|
|
sinyaw * sinpitch * cosroll - cosyaw * sinroll,
|
|
sinyaw * cospitch,
|
|
},
|
|
{
|
|
cospitch * sinroll,
|
|
cospitch * cosroll,
|
|
-sinpitch,
|
|
},
|
|
{
|
|
cosyaw * sinpitch * sinroll - sinyaw * cosroll,
|
|
cosyaw * sinpitch * cosroll + sinyaw * sinroll,
|
|
cosyaw * cospitch,
|
|
},
|
|
}
|
|
-- Compute matrix multiplication: `matrix` * `v`
|
|
return vector.new(
|
|
matrix[1][1] * v.x + matrix[1][2] * v.y + matrix[1][3] * v.z,
|
|
matrix[2][1] * v.x + matrix[2][2] * v.y + matrix[2][3] * v.z,
|
|
matrix[3][1] * v.x + matrix[3][2] * v.y + matrix[3][3] * v.z
|
|
)
|
|
end
|
|
|
|
function vector.dir_to_rotation(forward, up)
|
|
forward = vector.normalize(forward)
|
|
local rot = {x = math.asin(forward.y), y = -math.atan2(forward.x, forward.z), z = 0}
|
|
if not up then
|
|
return rot
|
|
end
|
|
assert(vector.dot(forward, up) < 0.000001,
|
|
"Invalid vectors passed to vector.dir_to_rotation().")
|
|
up = vector.normalize(up)
|
|
-- Calculate vector pointing up with roll = 0, just based on forward vector.
|
|
local forwup = vector.rotate({x = 0, y = 1, z = 0}, rot)
|
|
-- 'forwup' and 'up' are now in a plane with 'forward' as normal.
|
|
-- The angle between them is the absolute of the roll value we're looking for.
|
|
rot.z = vector.angle(forwup, up)
|
|
|
|
-- Since vector.angle never returns a negative value or a value greater
|
|
-- than math.pi, rot.z has to be inverted sometimes.
|
|
-- To determine wether this is the case, we rotate the up vector back around
|
|
-- the forward vector and check if it worked out.
|
|
local back = vector.rotate_around_axis(up, forward, -rot.z)
|
|
|
|
-- We don't use vector.equals for this because of floating point imprecision.
|
|
if (back.x - forwup.x) * (back.x - forwup.x) +
|
|
(back.y - forwup.y) * (back.y - forwup.y) +
|
|
(back.z - forwup.z) * (back.z - forwup.z) > 0.0000001 then
|
|
rot.z = -rot.z
|
|
end
|
|
return rot
|
|
end
|