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}