Skip to main content

arris_math/
reflection.rs

1//! Reflection in a plane: the one improper isometry a mirror needs.
2
3use core::fmt;
4
5use crate::frame::{is_finite3, rescaled};
6use crate::{Frame, Point3, UnitVec3, Vec3};
7
8/// Why a point and a direction are not a mirror plane.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ReflectionError {
11    /// A coordinate is NaN or infinite.
12    NonFinite,
13    /// The normal has zero length: it names no plane.
14    Degenerate,
15}
16
17impl fmt::Display for ReflectionError {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        f.write_str(match self {
20            ReflectionError::NonFinite => "mirror plane has a non-finite coordinate",
21            ReflectionError::Degenerate => "mirror plane has a zero normal",
22        })
23    }
24}
25
26impl std::error::Error for ReflectionError {}
27
28/// Reflection in the plane through `origin` with unit `normal`:
29/// `p ↦ p − 2((p − o)·n) n`. Lengths and angles are preserved and
30/// handedness is reversed, which is why it is a type of its own and not an
31/// [`crate::Isometry`], whose every value is proper (ADR-0031).
32///
33/// ```
34/// use arris_math::{Point3, Reflection, Vec3};
35///
36/// let r = Reflection::new(Point3::new(0.0, 0.0, 1.0), Vec3::z()).unwrap();
37/// assert_eq!(r.apply(Point3::new(1.0, 2.0, 3.0)), Point3::new(1.0, 2.0, -1.0));
38/// // Reflecting twice returns the point.
39/// let p = Point3::new(0.3, -0.4, 5.0);
40/// assert!((r.apply(r.apply(p)) - p).norm() < 1e-15);
41/// assert!(Reflection::new(Point3::origin(), Vec3::zeros()).is_err());
42/// ```
43#[derive(Debug, Clone, Copy, PartialEq)]
44pub struct Reflection {
45    origin: Point3,
46    normal: UnitVec3,
47}
48
49impl Reflection {
50    /// The plane through `origin` with normal along `normal` (normalised).
51    /// Errors: a non-finite input, or a zero `normal`.
52    pub fn new(origin: Point3, normal: Vec3) -> Result<Self, ReflectionError> {
53        if !(is_finite3(&origin.coords) && is_finite3(&normal)) {
54            return Err(ReflectionError::NonFinite);
55        }
56        let normal = rescaled(normal)
57            .and_then(|n| UnitVec3::try_new(n, 0.0))
58            .ok_or(ReflectionError::Degenerate)?;
59        Ok(Reflection { origin, normal })
60    }
61
62    /// The same as [`Reflection::new`]: the plane is named by a point on
63    /// it and its normal.
64    pub fn plane_through(origin: Point3, normal: Vec3) -> Result<Self, ReflectionError> {
65        Self::new(origin, normal)
66    }
67
68    /// A point on the plane.
69    pub fn origin(&self) -> Point3 {
70        self.origin
71    }
72
73    /// The plane's unit normal.
74    pub fn normal(&self) -> UnitVec3 {
75        self.normal
76    }
77
78    /// The mirror image of `p`.
79    pub fn apply(&self, p: Point3) -> Point3 {
80        let n = self.normal.into_inner();
81        p - 2.0 * (p - self.origin).dot(&n) * n
82    }
83
84    /// The image of a displacement: the plane's offset does not act on it.
85    pub fn apply_vec(&self, v: Vec3) -> Vec3 {
86        let n = self.normal.into_inner();
87        v - 2.0 * v.dot(&n) * n
88    }
89
90    /// The image of a direction, re-normalised so it is unit to rounding.
91    pub fn apply_unit(&self, u: UnitVec3) -> UnitVec3 {
92        UnitVec3::new_normalize(self.apply_vec(u.into_inner()))
93    }
94
95    /// The image of a frame as a *quadric* is placed after a mirror:
96    /// `O′ = R O`, `X′ = R X`, `Y′ = −R Y`, `Z′ = R Z`. Right-handed by
97    /// construction (`X′ × Y′ = R Z`); the surface it places is the
98    /// mirror image reparametrised by `u ↦ 2π − u` (ADR-0031 §2).
99    ///
100    /// ```
101    /// use arris_math::{Frame, Reflection, Vec3, Point3};
102    ///
103    /// let r = Reflection::new(Point3::origin(), Vec3::x()).unwrap();
104    /// let f = r.apply_frame(&Frame::world());
105    /// assert!((f.x().into_inner() - (-Vec3::x())).norm() < 1e-15);
106    /// assert!((f.y().into_inner() - (-Vec3::y())).norm() < 1e-15);
107    /// assert!((f.z().into_inner() - Vec3::z()).norm() < 1e-15);
108    /// ```
109    pub fn apply_frame(&self, frame: &Frame) -> Frame {
110        self.placed(frame, self.apply_vec(frame.z().into_inner()))
111    }
112
113    /// The image of a frame keeping the parametrisation of what it
114    /// places: `O′ = R O`, `X′ = R X`, `Y′ = R Y`, `Z′ = X′ × Y′ = −R Z`.
115    /// A plane's and a conic curve's frame after a mirror (ADR-0031 §2):
116    /// the same parameters, the normal turned the other way.
117    pub fn apply_frame_reversed(&self, frame: &Frame) -> Frame {
118        self.placed(frame, -self.apply_vec(frame.z().into_inner()))
119    }
120
121    /// A right-handed frame at the image of `frame`'s origin with axis
122    /// `z` and `x` the image of `frame`'s. The images of an orthonormal
123    /// frame's axes are orthonormal, so the construction only
124    /// re-orthonormalises to rounding.
125    fn placed(&self, frame: &Frame, z: Vec3) -> Frame {
126        Frame::orthonormalised(
127            self.apply(frame.origin()),
128            self.apply_unit(frame.x()),
129            UnitVec3::new_normalize(z),
130        )
131    }
132}