modlib/quaternion.lua
2021-03-27 20:10:49 +01:00

129 lines
3.4 KiB
Lua

-- TODO OOP, extend vector
function from_euler_rotation(rotation)
rotation = vector.divide(rotation, 2)
local cos = vector.apply(rotation, math.cos)
local sin = vector.apply(rotation, math.sin)
return {
sin.z * cos.x * cos.y - cos.z * sin.x * sin.y,
cos.z * sin.x * cos.y + sin.z * cos.x * sin.y,
cos.z * cos.x * sin.y - sin.z * sin.x * cos.y,
cos.z * cos.x * cos.y + sin.z * sin.x * sin.y
}
end
function multiply(self, other)
return {
other[1] * self[1] - other[2] * self[2] - other[3] * self[3] - other[4] * self[4],
other[1] * self[2] + other[2] * self[1] - other[3] * self[4] + other[4] * self[3],
other[1] * self[3] + other[2] * self[4] + other[3] * self[1] - other[4] * self[2],
other[1] * self[4] - other[2] * self[3] + other[3] * self[2] + other[4] * self[1]
}
end
function normalize(self)
local len = math.sqrt(self[1] ^ 2 + self[2] ^ 2 + self[3] ^ 2 + (self[4] ^ 4))
local res = {}
for key, value in pairs(self) do
res[key] = value / len
end
return res
end
function conjugate(self)
return {
-self[1],
-self[2],
-self[3],
self[4]
}
end
function inverse(self)
return modlib.vector.divide_scalar(conjugate(self), self[1] ^ 2 + self[2] ^ 2 + self[3] ^ 2 + self[4] ^ 2)
end
function negate(self)
for key, value in pairs(self) do
self[key] = -value
end
end
function dot(self, other)
return self[1] * other[1] + self[2] * other[2] + self[3] * other[3] + self[4] * other[4]
end
--: self normalized quaternion
--: other normalized quaternion
function slerp(self, other, ratio)
local d = dot(self, other)
if d < 0 then
d = -d
negate(other)
end
-- Threshold beyond which linear interpolation is used
if d > 1 - 1e-10 then
return modlib.vector.interpolate(self, other, ratio)
end
local theta_0 = math.acos(d)
local theta = theta_0 * ratio
local sin_theta = math.sin(theta)
local sin_theta_0 = math.sin(theta_0)
local s_1 = sin_theta / sin_theta_0
local s_0 = math.cos(theta) - d * s_1
return modlib.vector.add(modlib.vector.multiply_scalar(self, s_0), modlib.vector.multiply_scalar(other, s_1))
end
--> {x = pitch, y = yaw, z = roll} euler rotation in degrees
function to_euler_rotation(self)
local rotation = {}
local sinr_cosp = 2 * (self[4] * self[1] + self[2] * self[3])
local cosr_cosp = 1 - 2 * (self[1] ^ 2 + self[2] ^ 2)
rotation.x = math.atan2(sinr_cosp, cosr_cosp)
local sinp = 2 * (self[4] * self[2] - self[3] * self[1])
if sinp <= -1 then
rotation.z = -math.pi/2
elseif sinp >= 1 then
rotation.z = math.pi/2
else
rotation.z = math.asin(sinp)
end
local siny_cosp = 2 * (self[4] * self[3] + self[1] * self[2])
local cosy_cosp = 1 - 2 * (self[2] ^ 2 + self[3] ^ 2)
rotation.y = math.atan2(siny_cosp, cosy_cosp)
return vector.apply(rotation, math.deg)
end
-- See https://github.com/zaki/irrlicht/blob/master/include/quaternion.h#L652
function to_euler_rotation_irrlicht(self)
local x, y, z, w = unpack(self)
local test = 2 * (y * w - x * z)
local function _calc()
if math.abs(test - 1) <= 1e-6 then
return {
z = -2 * math.atan2(x, w),
x = 0,
y = math.pi/2
}
end
if math.abs(test + 1) <= 1e-6 then
return {
z = 2 * math.atan2(x, w),
x = 0,
y = math.pi/-2
}
end
return {
z = math.atan2(2 * (x * y + z * w), x ^ 2 - y ^ 2 - z ^ 2 + w ^ 2),
x = math.atan2(2 * (y * z + x * w), -x ^ 2 - y ^ 2 + z ^ 2 + w ^ 2),
y = math.asin(math.min(math.max(test, -1), 1))
}
end
return vector.apply(_calc(), math.deg)
end