use glam::{Mat4, Vec3, Vec4};
#[must_use]
pub fn reflection_matrix(plane_point: Vec3, plane_normal: Vec3) -> Mat4 {
let n = plane_normal.normalize();
let d = -plane_point.dot(n);
Mat4::from_cols(
Vec4::new(
1.0 - 2.0 * n.x * n.x,
-2.0 * n.x * n.y,
-2.0 * n.x * n.z,
0.0,
),
Vec4::new(
-2.0 * n.x * n.y,
1.0 - 2.0 * n.y * n.y,
-2.0 * n.y * n.z,
0.0,
),
Vec4::new(
-2.0 * n.x * n.z,
-2.0 * n.y * n.z,
1.0 - 2.0 * n.z * n.z,
0.0,
),
Vec4::new(-2.0 * n.x * d, -2.0 * n.y * d, -2.0 * n.z * d, 1.0),
)
}
#[must_use]
pub fn ground_reflection_matrix(height: f32) -> Mat4 {
reflection_matrix(Vec3::new(0.0, height, 0.0), Vec3::Y)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reflection_matrix_identity_at_origin() {
let mat = reflection_matrix(Vec3::ZERO, Vec3::Y);
let point = Vec3::new(1.0, 2.0, 3.0);
let reflected = mat.transform_point3(point);
assert!((reflected.x - point.x).abs() < 0.001);
assert!((reflected.y - (-point.y)).abs() < 0.001);
assert!((reflected.z - point.z).abs() < 0.001);
}
#[test]
fn test_ground_reflection_at_height() {
let height = 1.0;
let mat = ground_reflection_matrix(height);
let point = Vec3::new(0.0, 3.0, 0.0);
let reflected = mat.transform_point3(point);
assert!((reflected.y - (-1.0)).abs() < 0.001);
}
#[test]
fn test_reflection_is_involution() {
let mat = reflection_matrix(Vec3::new(0.0, 1.0, 0.0), Vec3::Y);
let double = mat * mat;
for i in 0..4 {
for j in 0..4 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!((double.col(j)[i] - expected).abs() < 0.001);
}
}
}
}