Skip to main content

polyscope_render/
reflection.rs

1//! Planar reflection utilities.
2
3use glam::{Mat4, Vec3, Vec4};
4
5/// Computes a reflection matrix for a plane.
6///
7/// The plane is defined by a point on the plane and its normal.
8/// The resulting matrix reflects points across this plane.
9#[must_use]
10pub fn reflection_matrix(plane_point: Vec3, plane_normal: Vec3) -> Mat4 {
11    let n = plane_normal.normalize();
12    let d = -plane_point.dot(n);
13
14    // Reflection matrix formula:
15    // | 1-2nx²   -2nxny   -2nxnz   -2nxd |
16    // | -2nxny   1-2ny²   -2nynz   -2nyd |
17    // | -2nxnz   -2nynz   1-2nz²   -2nzd |
18    // |    0        0        0       1   |
19
20    Mat4::from_cols(
21        Vec4::new(
22            1.0 - 2.0 * n.x * n.x,
23            -2.0 * n.x * n.y,
24            -2.0 * n.x * n.z,
25            0.0,
26        ),
27        Vec4::new(
28            -2.0 * n.x * n.y,
29            1.0 - 2.0 * n.y * n.y,
30            -2.0 * n.y * n.z,
31            0.0,
32        ),
33        Vec4::new(
34            -2.0 * n.x * n.z,
35            -2.0 * n.y * n.z,
36            1.0 - 2.0 * n.z * n.z,
37            0.0,
38        ),
39        Vec4::new(-2.0 * n.x * d, -2.0 * n.y * d, -2.0 * n.z * d, 1.0),
40    )
41}
42
43/// Computes a reflection matrix for a horizontal ground plane at given height.
44///
45/// Assumes Y-up coordinate system.
46#[must_use]
47pub fn ground_reflection_matrix(height: f32) -> Mat4 {
48    reflection_matrix(Vec3::new(0.0, height, 0.0), Vec3::Y)
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn test_reflection_matrix_identity_at_origin() {
57        let mat = reflection_matrix(Vec3::ZERO, Vec3::Y);
58
59        // Point above plane should reflect below
60        let point = Vec3::new(1.0, 2.0, 3.0);
61        let reflected = mat.transform_point3(point);
62
63        assert!((reflected.x - point.x).abs() < 0.001);
64        assert!((reflected.y - (-point.y)).abs() < 0.001);
65        assert!((reflected.z - point.z).abs() < 0.001);
66    }
67
68    #[test]
69    fn test_ground_reflection_at_height() {
70        let height = 1.0;
71        let mat = ground_reflection_matrix(height);
72
73        // Point at height 3 should reflect to height -1
74        let point = Vec3::new(0.0, 3.0, 0.0);
75        let reflected = mat.transform_point3(point);
76
77        // Distance from plane is 2, so reflected should be 2 below plane
78        assert!((reflected.y - (-1.0)).abs() < 0.001);
79    }
80
81    #[test]
82    fn test_reflection_is_involution() {
83        let mat = reflection_matrix(Vec3::new(0.0, 1.0, 0.0), Vec3::Y);
84        let double = mat * mat;
85
86        // Reflecting twice should give identity
87        for i in 0..4 {
88            for j in 0..4 {
89                let expected = if i == j { 1.0 } else { 0.0 };
90                assert!((double.col(j)[i] - expected).abs() < 0.001);
91            }
92        }
93    }
94}