use core::{
fmt::{self, Display},
ops::Mul,
};
use libm::{asinf, atan2f, cosf, sinf, sqrtf};
use crate::{vec3, Mat3x3, Vec3};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Quaternion {
pub w: f32,
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Quaternion {
pub fn new(w: f32, x: f32, y: f32, z: f32) -> Self {
Self { w, x, y, z }
}
pub fn identity() -> Self {
Self {
w: 1.0,
x: 0.0,
y: 0.0,
z: 0.0,
}
}
pub fn from_euler(yaw: f32, pitch: f32, roll: f32) -> Self {
let roll = roll / 2.0;
let pitch = pitch / 2.0;
let yaw = yaw / 2.0;
let q0 = cosf(roll) * cosf(pitch) * cosf(yaw) + sinf(roll) * sinf(pitch) * sinf(yaw);
let q1 = -cosf(roll) * sinf(pitch) * sinf(yaw) + cosf(pitch) * cosf(yaw) * sinf(roll);
let q2 = cosf(roll) * cosf(yaw) * sinf(pitch) + sinf(roll) * cosf(pitch) * sinf(yaw);
let q3 = cosf(roll) * cosf(pitch) * sinf(yaw) - sinf(roll) * cosf(yaw) * sinf(pitch);
Self {
w: q0,
x: q1,
y: q2,
z: q3,
}
}
pub fn from_rotation_matrix(rotation_matrix: &Mat3x3) -> Self {
let r = rotation_matrix.data;
let t0 = 1.0 + r[0] + r[4] + r[8]; let t1 = 1.0 + r[0] - r[4] - r[8]; let t2 = 1.0 - r[0] + r[4] - r[8]; let t3 = 1.0 - r[0] - r[4] + r[8];
if t0 >= t1 && t0 >= t2 && t0 >= t3 {
let q0 = 0.5 * sqrtf(t0);
let q1 = (r[7] - r[5]) / (4.0 * q0); let q2 = (r[2] - r[6]) / (4.0 * q0); let q3 = (r[3] - r[1]) / (4.0 * q0); Self {
w: q0,
x: q1,
y: q2,
z: q3,
}
} else if t1 >= t2 && t1 >= t3 {
let q1 = 0.5 * sqrtf(t1);
let q0 = (r[7] - r[5]) / (4.0 * q1); let q2 = (r[1] + r[3]) / (4.0 * q1); let q3 = (r[2] + r[6]) / (4.0 * q1); Self {
w: q0,
x: q1,
y: q2,
z: q3,
}
} else if t2 >= t3 {
let q2 = 0.5 * sqrtf(t2);
let q0 = (r[2] - r[6]) / (4.0 * q2); let q1 = (r[1] + r[3]) / (4.0 * q2); let q3 = (r[5] + r[7]) / (4.0 * q2); Self {
w: q0,
x: q1,
y: q2,
z: q3,
}
} else {
let q3 = 0.5 * sqrtf(t3);
let q0 = (r[3] - r[1]) / (4.0 * q3); let q1 = (r[2] + r[6]) / (4.0 * q3); let q2 = (r[5] + r[7]) / (4.0 * q3); Self {
w: q0,
x: q1,
y: q2,
z: q3,
}
}
}
pub fn conjugate(&self) -> Self {
Self {
w: self.w,
x: -self.x,
y: -self.y,
z: -self.z,
}
}
pub fn inverse(&self) -> Self {
Self {
w: self.w,
x: -self.x,
y: -self.y,
z: -self.z,
}
}
pub fn rotation_matrix_i_wrt_b(&self) -> Mat3x3 {
let qvec = vec3(self.x, self.y, self.z);
let sk_q = Mat3x3::skew_symmetric(qvec); Mat3x3::identity() * (self.w * self.w - qvec.magnitude_squared())
+ Mat3x3::outer_product(qvec, qvec) * 2.0
+ sk_q * 2.0 * self.w
}
pub fn rotation_matrix_b_wrt_i(&self) -> Mat3x3 {
self.rotation_matrix_i_wrt_b().transpose()
}
pub fn yaw(&self) -> f32 {
let yaw_denominator = self.w * self.w + self.x * self.x - self.y * self.y - self.z * self.z;
atan2f(2.0 * (self.x * self.y + self.w * self.z), yaw_denominator)
}
pub fn pitch(&self) -> f32 {
asinf(-2.0 * (self.x * self.z - self.w * self.y))
}
pub fn roll(&self) -> f32 {
let roll_denominator =
self.w * self.w - self.x * self.x - self.y * self.y + self.z * self.z;
atan2f(2.0 * (self.y * self.z + self.w * self.x), roll_denominator)
}
pub fn to_gibbs_vector(&self) -> Vec3<f32> {
if self.w == 0.0 {
1e20 * vec3(self.x, self.y, self.z)
} else {
vec3(self.x, self.y, self.z) / self.w
}
}
pub fn to_euler(&self) -> Vec3<f32> {
vec3(self.roll(), self.pitch(), self.yaw())
}
pub fn is_finite(&self) -> bool {
self.w.is_finite() && self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
}
pub fn magnitude(&self) -> f32 {
sqrtf(self.w * self.w + self.x * self.x + self.y * self.y + self.z * self.z)
}
pub fn normalize(&self) -> Quaternion {
let mag = self.magnitude();
if mag.abs() < f32::EPSILON {
Quaternion::identity() } else {
Quaternion::new(self.w / mag, self.x / mag, self.y / mag, self.z / mag)
}
}
}
impl Mul<Quaternion> for Quaternion {
type Output = Quaternion;
fn mul(self, p: Quaternion) -> Quaternion {
let q = self;
let c0 = q.w * p.w - q.x * p.x - q.y * p.y - q.z * p.z; let c1 = q.w * p.x + q.x * p.w + q.y * p.z - q.z * p.y; let c2 = q.w * p.y + q.y * p.w + q.z * p.x - q.x * p.z; let c3 = q.w * p.z + q.z * p.w + q.x * p.y - q.y * p.x;
Quaternion {
w: c0,
x: c1,
y: c2,
z: c3,
}
}
}
impl Default for Quaternion {
fn default() -> Self {
Quaternion {
w: 1.0,
x: 0.0,
y: 0.0,
z: 0.0,
}
}
}
impl Display for Quaternion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:.3} {:.3} {:.3} {:.3}", self.w, self.x, self.y, self.z)
}
}