-- Localize globals local math, modlib, pairs, unpack, vector = math, modlib, pairs, unpack, vector -- Set environment local _ENV = {} setfenv(1, _ENV) -- 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 { cos.z * sin.x * cos.y + sin.z * cos.x * sin.y, cos.z * cos.x * sin.y - sin.z * sin.x * cos.y, sin.z * cos.x * cos.y - cos.z * sin.x * sin.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] ^ 2) 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 --> axis, angle function to_axis_angle(self) local axis = modlib.vector.new{self[1], self[2], self[3]} local len = axis:length() -- HACK invert axis for correct rotation in Minetest return len == 0 and axis or axis:divide_scalar(-len), 2 * math.atan2(len, self[4]) 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.y = -math.pi/2 elseif sinp >= 1 then rotation.y = math.pi/2 else rotation.y = 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.z = 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 -- Export environment return _ENV