208 lines
6.4 KiB
Rust
208 lines
6.4 KiB
Rust
//! Tests for three by three matrices.
|
|
//!
|
|
//! 三阶矩阵的测试。
|
|
|
|
use apsis_math::{Mat3, Quat, Vec3};
|
|
use core::f64::consts::FRAC_PI_2;
|
|
|
|
const EPS: f64 = 1e-12;
|
|
|
|
fn assert_mat_close(lhs: Mat3, rhs: Mat3) {
|
|
for row in 0..3 {
|
|
for column in 0..3 {
|
|
let (lhs, rhs) = (
|
|
lhs.get(row, column).expect("indices are in range"),
|
|
rhs.get(row, column).expect("indices are in range"),
|
|
);
|
|
assert!(
|
|
(lhs - rhs).abs() < EPS,
|
|
"at ({row}, {column}): {lhs} vs {rhs}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn constructors_place_entries_in_the_right_slots() {
|
|
let m = Mat3::from_rows([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]);
|
|
assert_eq!(m.get(0, 0), Some(1.0));
|
|
assert_eq!(m.get(0, 1), Some(2.0));
|
|
assert_eq!(m.get(0, 2), Some(3.0));
|
|
assert_eq!(m.get(1, 0), Some(4.0));
|
|
assert_eq!(m.get(2, 2), Some(9.0));
|
|
assert_eq!(m.get(3, 0), None);
|
|
assert_eq!(m.get(0, 3), None);
|
|
|
|
assert_eq!(m.row(1), Some(Vec3::new(4.0, 5.0, 6.0)));
|
|
assert_eq!(m.column(1), Some(Vec3::new(2.0, 5.0, 8.0)));
|
|
assert_eq!(m.row(3), None);
|
|
assert_eq!(m.column(3), None);
|
|
}
|
|
|
|
#[test]
|
|
fn from_columns_matches_from_rows() {
|
|
let c0 = Vec3::new(1.0, 4.0, 7.0);
|
|
let c1 = Vec3::new(2.0, 5.0, 8.0);
|
|
let c2 = Vec3::new(3.0, 6.0, 9.0);
|
|
let expected = Mat3::from_rows([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]);
|
|
assert_eq!(Mat3::from_columns(c0, c1, c2), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn identity_is_the_multiplicative_unit() {
|
|
let m = Mat3::from_rotation_z(FRAC_PI_2);
|
|
assert_eq!(Mat3::IDENTITY * m, m);
|
|
assert_eq!(m * Mat3::IDENTITY, m);
|
|
assert_eq!(Mat3::IDENTITY.determinant(), 1.0);
|
|
assert_eq!(Mat3::default(), Mat3::ZERO);
|
|
}
|
|
|
|
#[test]
|
|
fn zero_annihilates() {
|
|
assert_eq!(Mat3::ZERO * Vec3::new(1.0, 2.0, 3.0), Vec3::ZERO);
|
|
assert_eq!(Mat3::ZERO * Mat3::IDENTITY, Mat3::ZERO);
|
|
assert_eq!(Mat3::ZERO.determinant(), 0.0);
|
|
}
|
|
|
|
#[test]
|
|
fn transpose_swaps_rows_and_columns() {
|
|
let m = Mat3::from_rows([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]);
|
|
let t = m.transpose();
|
|
assert_eq!(t.row(0), m.column(0));
|
|
assert_eq!(t.row(1), m.column(1));
|
|
assert_eq!(t.row(2), m.column(2));
|
|
assert_eq!(t.transpose(), m);
|
|
}
|
|
|
|
#[test]
|
|
fn determinant_matches_hand_computation() {
|
|
let m = Mat3::from_rows([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]);
|
|
assert!((m.determinant() + 3.0).abs() < EPS);
|
|
assert_eq!(
|
|
Mat3::from_diagonal(Vec3::new(2.0, 3.0, 4.0)).determinant(),
|
|
24.0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn trace_sums_the_diagonal() {
|
|
let m = Mat3::from_rows([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]);
|
|
assert_eq!(m.trace(), 15.0);
|
|
}
|
|
|
|
#[test]
|
|
fn matrix_vector_product_uses_rows() {
|
|
let m = Mat3::from_rows([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]);
|
|
let v = Vec3::new(1.0, 1.0, 1.0);
|
|
assert_eq!(m * v, Vec3::new(6.0, 15.0, 24.0));
|
|
}
|
|
|
|
#[test]
|
|
fn matrix_product_is_associative() {
|
|
let a = Mat3::from_rows([1.0, 2.0, 0.0], [0.0, 1.0, 3.0], [1.0, 0.0, 1.0]);
|
|
let b = Mat3::from_rows([0.0, 1.0, 2.0], [1.0, 0.0, 1.0], [2.0, 1.0, 0.0]);
|
|
let c = Mat3::from_rows([1.0, 1.0, 1.0], [2.0, 2.0, 2.0], [3.0, 3.0, 3.0]);
|
|
assert_eq!((a * b) * c, a * (b * c));
|
|
}
|
|
|
|
#[test]
|
|
fn matrix_product_preserves_the_determinant_product() {
|
|
let a = Mat3::from_rows([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]);
|
|
let b = Mat3::from_rows([2.0, 0.0, 1.0], [1.0, 3.0, 2.0], [0.0, 1.0, 4.0]);
|
|
let expected = a.determinant() * b.determinant();
|
|
assert!(((a * b).determinant() - expected).abs() < 1e-9);
|
|
}
|
|
|
|
#[test]
|
|
fn scalar_product_scales_every_entry() {
|
|
let m = Mat3::from_rows([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]);
|
|
let scaled = m * 2.0;
|
|
assert_eq!(scaled.get(0, 0), Some(2.0));
|
|
assert_eq!(scaled.get(2, 2), Some(18.0));
|
|
assert_eq!(2.0 * m, scaled);
|
|
}
|
|
|
|
#[test]
|
|
fn rotation_matrices_satisfy_the_rotation_predicate() {
|
|
let m = Mat3::from_rotation_z(FRAC_PI_2);
|
|
assert!(m.is_rotation(1e-12));
|
|
assert!((m.determinant() - 1.0).abs() < EPS);
|
|
assert_mat_close(m * m.transpose(), Mat3::IDENTITY);
|
|
}
|
|
|
|
#[test]
|
|
fn is_rotation_rejects_non_rotations() {
|
|
assert!(!Mat3::ZERO.is_rotation(1e-9));
|
|
assert!(!Mat3::from_diagonal(Vec3::splat(2.0)).is_rotation(1e-9));
|
|
// A reflection has determinant minus one.
|
|
// 反射的行列式为负一。
|
|
assert!(!Mat3::from_diagonal(Vec3::new(1.0, 1.0, -1.0)).is_rotation(1e-9));
|
|
}
|
|
|
|
#[test]
|
|
fn axis_angle_matches_the_quaternion_rotation() {
|
|
let axis = Vec3::new(0.5, -2.0, 1.0);
|
|
let m = Mat3::from_axis_angle(axis, 0.9).expect("non-zero axis");
|
|
let q = Quat::from_axis_angle(axis, 0.9).expect("non-zero axis");
|
|
let v = Vec3::new(1.0, -2.0, 3.0);
|
|
assert!((m * v - q.rotate(v)).norm() < EPS);
|
|
assert_eq!(Mat3::from_axis_angle(Vec3::ZERO, 1.0), None);
|
|
}
|
|
|
|
#[test]
|
|
fn rotation_maps_axes_as_expected() {
|
|
let m = Mat3::from_rotation_z(FRAC_PI_2);
|
|
assert!((m * Vec3::X - Vec3::Y).norm() < EPS);
|
|
assert!((m * Vec3::Y + Vec3::X).norm() < EPS);
|
|
assert!((m * Vec3::Z - Vec3::Z).norm() < EPS);
|
|
}
|
|
|
|
#[test]
|
|
fn per_axis_constructors_agree_with_the_axis_angle_form() {
|
|
let angle = 0.6;
|
|
let about_x = Mat3::from_rotation_x(angle);
|
|
assert_eq!(
|
|
about_x,
|
|
Mat3::from_axis_angle(Vec3::X, angle).expect("unit axis")
|
|
);
|
|
assert_eq!(about_x * Vec3::X, Vec3::X);
|
|
|
|
let about_y = Mat3::from_rotation_y(angle);
|
|
assert_eq!(
|
|
about_y,
|
|
Mat3::from_axis_angle(Vec3::Y, angle).expect("unit axis")
|
|
);
|
|
assert_eq!(about_y * Vec3::Y, Vec3::Y);
|
|
|
|
let about_z = Mat3::from_rotation_z(angle);
|
|
assert_eq!(
|
|
about_z,
|
|
Mat3::from_axis_angle(Vec3::Z, angle).expect("unit axis")
|
|
);
|
|
assert_eq!(about_z * Vec3::Z, Vec3::Z);
|
|
assert!((about_z.determinant() - 1.0).abs() < EPS);
|
|
}
|
|
|
|
#[test]
|
|
fn finiteness_is_reported() {
|
|
assert!(Mat3::IDENTITY.is_finite());
|
|
let mut m = Mat3::IDENTITY;
|
|
m.m01 = f64::NAN;
|
|
assert!(!m.is_finite());
|
|
}
|
|
|
|
#[test]
|
|
fn display_uses_row_major_groups() {
|
|
let m = Mat3::from_diagonal(Vec3::new(1.0, 2.0, 3.0));
|
|
assert_eq!(m.to_string(), "[1, 0, 0; 0, 2, 0; 0, 0, 3]");
|
|
}
|
|
|
|
#[test]
|
|
fn from_quat_is_consistent_with_quaternion_rotation() {
|
|
let q = Quat::from_axis_angle(Vec3::new(1.0, 1.0, 0.0), 1.3).expect("non-zero axis");
|
|
let m = Mat3::from_quat(q);
|
|
let v = Vec3::new(2.0, -1.0, 0.5);
|
|
assert!((m * v - q.rotate(v)).norm() < EPS);
|
|
assert!(m.is_rotation(1e-12));
|
|
}
|