use crate::{
vec::{Vec3, Vec4},
Quat,
};
use approx::AbsDiffEq;
use std::f32::consts::{FRAC_PI_2, PI};
fn quat_approx_eq(a: Quat, b: Quat) -> bool {
a.abs_diff_eq(&b, 1e-5) || a.abs_diff_eq(&-b, 1e-5)
}
fn vec3_approx_eq(a: Vec3, b: Vec3) -> bool {
a.abs_diff_eq(&b, 1e-5)
}
#[test]
fn test_euler_angles_conversion() {
let roll = 0.1;
let pitch = 0.2;
let yaw = 0.3;
let q = Quat::from_euler_angles(roll, pitch, yaw);
let (r2, p2, y2) = q.to_euler_angles();
let q2 = Quat::from_euler_angles(r2, p2, y2);
let dot = q.dot(q2);
assert!(
(1.0 - dot.abs()) < 1e-5,
"Quats differ: q={:?}, q2={:?}, dot={}",
q,
q2,
dot
);
}
#[test]
fn test_swing_twist() {
let twist_axis = Vec3::new(0.0, 1.0, 0.0).normalize();
let rot = Quat::from_euler_angles(0.5, 0.8, 0.3);
let (swing, twist) = rot.to_swing_twist(twist_axis);
assert!(quat_approx_eq(rot, swing * twist));
let (twist_rot_axis, twist_angle) = twist.to_axis_angle();
if twist_angle.abs() > 1e-6 {
assert!(
vec3_approx_eq(twist_rot_axis, twist_axis)
|| vec3_approx_eq(twist_rot_axis, -twist_axis)
);
}
let (swing_rot_axis, swing_angle) = swing.to_axis_angle();
if swing_angle.abs() > 1e-6 {
assert!(swing_rot_axis.dot(twist_axis).abs() < 1e-6);
}
}
#[test]
fn test_construction_and_constants() {
let q = Quat::new(1.0, 2.0, 3.0, 4.0);
assert_eq!(q.x, 1.0);
assert_eq!(q.y, 2.0);
assert_eq!(q.z, 3.0);
assert_eq!(q.w, 4.0);
assert_eq!(Quat::IDENTITY, Quat::new(0.0, 0.0, 0.0, 1.0));
assert_eq!(Quat::ZERO, Quat::new(0.0, 0.0, 0.0, 0.0));
assert!(Quat::NAN.is_nan());
}
#[test]
fn test_from_axis_angle() {
let axis = Vec3::new(0.0, 1.0, 0.0);
let angle = FRAC_PI_2; let q = Quat::from_axis_angle(axis, angle);
let expected = Quat::new(0.0, (angle / 2.0).sin(), 0.0, (angle / 2.0).cos());
assert!(quat_approx_eq(q, expected));
let q_zero = Quat::from_axis_angle(axis, 0.0);
assert!(quat_approx_eq(q_zero, Quat::IDENTITY));
}
#[test]
fn test_from_rotation_axes() {
let angle = FRAC_PI_2;
let qx = Quat::from_rotation_x(angle);
let qy = Quat::from_rotation_y(angle);
let qz = Quat::from_rotation_z(angle);
assert!(quat_approx_eq(
qx,
Quat::from_axis_angle(Vec3::new(1.0, 0.0, 0.0), angle)
));
assert!(quat_approx_eq(
qy,
Quat::from_axis_angle(Vec3::new(0.0, 1.0, 0.0), angle)
));
assert!(quat_approx_eq(
qz,
Quat::from_axis_angle(Vec3::new(0.0, 0.0, 1.0), angle)
));
}
#[test]
fn test_vec4_conversion() {
let v = Vec4::new(1.0, 2.0, 3.0, 4.0);
let q = Quat::from_vec4(v);
assert_eq!(q.x, 1.0);
assert_eq!(q.y, 2.0);
assert_eq!(q.z, 3.0);
assert_eq!(q.w, 4.0);
let v_back = q.to_vec4();
assert_eq!(v, v_back);
}
#[test]
fn test_vector_and_scalar_parts() {
let q = Quat::new(1.0, 2.0, 3.0, 4.0);
assert_eq!(q.vector_part(), Vec3::new(1.0, 2.0, 3.0));
assert_eq!(q.scalar_part(), 4.0);
}
#[test]
fn test_length_and_normalization() {
let q = Quat::new(1.0, 2.0, 3.0, 4.0);
assert!((q.length() - (30.0_f32).sqrt()).abs() < 1e-6);
assert!((q.squared_length() - 30.0).abs() < 1e-6);
let normalized = q.normalize();
assert!((normalized.length() - 1.0).abs() < 1e-6);
assert!(normalized.is_normalized());
let zero_norm = Quat::ZERO.normalize();
assert_eq!(zero_norm, Quat::IDENTITY);
assert!(q.try_normalize().is_some());
assert!(Quat::ZERO.try_normalize().is_none());
}
#[test]
fn test_inverse_and_conjugate() {
let q = Quat::from_axis_angle(Vec3::new(1.0, 2.0, 3.0).normalize(), 0.5);
let inv = q.inverse();
let conj = q.conjugate();
assert!(quat_approx_eq(inv, conj));
let identity = q * inv;
assert!(quat_approx_eq(identity, Quat::IDENTITY));
}
#[test]
fn test_vector_rotation() {
let angle = FRAC_PI_2; let q = Quat::from_rotation_y(angle);
let v = Vec3::new(1.0, 0.0, 0.0);
let rotated_v = q.rotate_vec3(v);
let expected_v = Vec3::new(0.0, 0.0, -1.0);
assert!(vec3_approx_eq(rotated_v, expected_v));
let rotated_v_op = q * v;
assert!(vec3_approx_eq(rotated_v_op, expected_v));
}
#[test]
fn test_from_to_rotation() {
let from = Vec3::new(1.0, 0.0, 0.0);
let to = Vec3::new(0.0, 1.0, 0.0);
let q = Quat::from_to_rotation(from, to);
let rotated = q * from;
assert!(vec3_approx_eq(rotated, to));
let from_opp = Vec3::new(1.0, 0.0, 0.0);
let to_opp = Vec3::new(-1.0, 0.0, 0.0);
let q_opp = Quat::from_to_rotation(from_opp, to_opp);
let rotated_opp = q_opp * from_opp;
assert!(vec3_approx_eq(rotated_opp, to_opp));
let q_ident = Quat::from_to_rotation(from, from);
assert!(quat_approx_eq(q_ident, Quat::IDENTITY));
}
#[test]
fn test_slerp_and_nlerp() {
let q1 = Quat::IDENTITY;
let q2 = Quat::from_rotation_y(FRAC_PI_2);
let slerp_half = q1.slerp(q2, 0.5);
let expected = Quat::from_rotation_y(FRAC_PI_2 / 2.0);
assert!(quat_approx_eq(slerp_half, expected));
let nlerp_half = q1.nlerp(q2, 0.5);
assert!(nlerp_half.is_normalized());
assert!(nlerp_half.dot(expected) > 0.99);
let q3 = Quat::from_rotation_y(PI * 0.1);
let q4 = -Quat::from_rotation_y(PI * 0.3); let slerp_short = q3.slerp(q4, 0.5);
let expected_short = Quat::from_rotation_y(PI * 0.2);
assert!(quat_approx_eq(slerp_short, expected_short));
}