trackball/
plane.rs

1use approx::{AbsDiffEq, RelativeEq, UlpsEq};
2use nalgebra::{Isometry3, Point3, RealField, Reflection3, Unit, UnitQuaternion, Vector3};
3use simba::scalar::SubsetOf;
4
5/// Plane encoding position with singed bias along unit normal.
6///
7/// Realizes plane equation `a*x+b*y+c*z+d=0` with unit normal `[x, y, z]` and signed bias `d`.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[repr(C)]
11pub struct Plane<N: Copy + RealField> {
12	/// Plane unit normal.
13	pub normal: Unit<Vector3<N>>,
14	/// Signed bias along unit normal.
15	pub bias: N,
16}
17
18impl<N: Copy + RealField> Plane<N> {
19	/// Plane from unit `normal` and signed `distance` from the origin.
20	///
21	/// ```
22	/// use trackball::{
23	///     nalgebra::{Point3, Vector3},
24	///     Plane,
25	/// };
26	///
27	/// // Plane intersecting y-axis at `5.0`.
28	/// let plane = Plane::new(Vector3::y_axis(), 5.0);
29	///
30	/// // Origin projected onto plane where plane intersects y-axis.
31	/// let point = plane.project_point(&Point3::origin());
32	/// assert_eq!(point, Point3::new(0.0, 5.0, 0.0));
33	///
34	/// // Bias is negated distance.
35	/// assert_eq!(plane.distance(), 5.0);
36	/// assert_eq!(plane.bias, -5.0);
37	///
38	/// // Plane intersecting y-axis at `-5.0`.
39	/// let plane = Plane::new(Vector3::y_axis(), -5.0);
40	///
41	/// // Origin projected onto plane where plane intersects y-axis.
42	/// let point = plane.project_point(&Point3::origin());
43	/// assert_eq!(point, Point3::new(0.0, -5.0, 0.0));
44	///
45	/// // Bias is negated distance.
46	/// assert_eq!(plane.distance(), -5.0);
47	/// assert_eq!(plane.bias, 5.0);
48	/// ```
49	#[must_use]
50	pub fn new(normal: Unit<Vector3<N>>, distance: N) -> Self {
51		Self {
52			normal,
53			bias: -distance,
54		}
55	}
56	/// Plane from unit normal with point in plane.
57	#[must_use]
58	pub fn with_point(normal: Unit<Vector3<N>>, point: &Point3<N>) -> Self {
59		Self::new(normal, normal.dot(&point.coords))
60	}
61	/// Signed orthogonal distance from the origin.
62	#[must_use]
63	pub fn distance(&self) -> N {
64		-self.bias
65	}
66	/// Signed orthogonal distance from `point`.
67	#[must_use]
68	pub fn distance_from(&self, point: &Point3<N>) -> N {
69		self.distance() - self.normal.dot(&point.coords)
70	}
71	/// Projects point onto plane.
72	#[must_use]
73	pub fn project_point(&self, point: &Point3<N>) -> Point3<N> {
74		self.project_vector(&point.coords).into()
75	}
76	/// Projects axis onto plane.
77	#[must_use]
78	pub fn project_axis(&self, axis: &Unit<Vector3<N>>) -> Unit<Vector3<N>> {
79		Unit::new_normalize(self.project_vector(&axis.into_inner()))
80	}
81	/// Projects vector onto plane.
82	#[must_use]
83	pub fn project_vector(&self, vector: &Vector3<N>) -> Vector3<N> {
84		vector - self.normal.into_inner() * (self.normal.dot(vector) + self.bias)
85	}
86	/// Singed angle from `a` to `b` where both vectors are in the plane.
87	#[must_use]
88	pub fn angle_between(&self, a: &Vector3<N>, b: &Vector3<N>) -> N {
89		let angle = a.angle(b);
90		let axis = a.cross(b);
91		if self.normal.dot(&axis).is_sign_negative() {
92			-angle
93		} else {
94			angle
95		}
96	}
97	/// Rotates plane.
98	#[must_use]
99	pub fn rotate_by(self, rot: &UnitQuaternion<N>) -> Self {
100		Self {
101			normal: Unit::new_unchecked(rot.transform_vector(&self.normal)),
102			bias: self.bias,
103		}
104	}
105	/// Translates plane.
106	#[must_use]
107	pub fn translate_by(self, vec: &Vector3<N>) -> Self {
108		Self {
109			normal: self.normal,
110			bias: self.bias - self.normal.dot(vec),
111		}
112	}
113	/// Transforms plane by direct isometry, i.e., rotation followed by translation.
114	///
115	/// ```
116	/// use core::f64::{
117	///     consts::FRAC_PI_2,
118	///     EPSILON,
119	/// };
120	/// use trackball::{
121	///     approx::AbsDiffEq,
122	///     nalgebra::{Isometry3, Vector3},
123	///     Plane,
124	/// };
125	///
126	/// // Plane intersecting y-axis at `5.0`.
127	/// let plane = Plane::new(Vector3::y_axis(), 5.0)
128	///     .transform_by(&Isometry3::new(
129	///         // Translation by after rotation.
130	///         Vector3::new(-5.0, 0.0, 0.0),
131	///         // Rotation by 90 degrees before translation.
132	///         Vector3::new(0.0, 0.0, FRAC_PI_2),
133	///     ));
134	/// // Plane intersecting x-axis at `-10.0`.
135	/// assert!(plane.abs_diff_eq(&Plane::new(-Vector3::x_axis(), 10.0), EPSILON));
136	/// ```
137	#[must_use]
138	pub fn transform_by(self, iso: &Isometry3<N>) -> Self {
139		self.rotate_by(&iso.rotation)
140			.translate_by(&iso.translation.vector)
141	}
142	/// Casts components to another type, e.g., between [`f32`] and [`f64`].
143	#[must_use]
144	pub fn cast<M: Copy + RealField>(self) -> Plane<M>
145	where
146		N: SubsetOf<M>,
147	{
148		Plane {
149			normal: self.normal.cast(),
150			bias: self.bias.to_superset(),
151		}
152	}
153}
154
155impl<N: Copy + RealField> From<Reflection3<N>> for Plane<N> {
156	fn from(reflection: Reflection3<N>) -> Self {
157		Self::new(Unit::new_unchecked(*reflection.axis()), reflection.bias())
158	}
159}
160
161impl<N: Copy + RealField> From<Plane<N>> for Reflection3<N> {
162	fn from(plane: Plane<N>) -> Self {
163		Self::new(plane.normal, plane.distance())
164	}
165}
166
167impl<N: Copy + RealField + AbsDiffEq> AbsDiffEq for Plane<N>
168where
169	N::Epsilon: Copy,
170{
171	type Epsilon = N::Epsilon;
172
173	fn default_epsilon() -> N::Epsilon {
174		N::default_epsilon()
175	}
176
177	fn abs_diff_eq(&self, other: &Self, epsilon: N::Epsilon) -> bool {
178		self.normal.abs_diff_eq(&other.normal, epsilon)
179			&& self.bias.abs_diff_eq(&other.bias, epsilon)
180	}
181}
182
183impl<N: Copy + RealField + RelativeEq> RelativeEq for Plane<N>
184where
185	N::Epsilon: Copy,
186{
187	fn default_max_relative() -> N::Epsilon {
188		N::default_max_relative()
189	}
190
191	fn relative_eq(&self, other: &Self, epsilon: N::Epsilon, max_relative: N::Epsilon) -> bool {
192		self.normal
193			.relative_eq(&other.normal, epsilon, max_relative)
194			&& self.bias.relative_eq(&other.bias, epsilon, max_relative)
195	}
196}
197
198impl<N: Copy + RealField + UlpsEq> UlpsEq for Plane<N>
199where
200	N::Epsilon: Copy,
201{
202	fn default_max_ulps() -> u32 {
203		N::default_max_ulps()
204	}
205
206	fn ulps_eq(&self, other: &Self, epsilon: N::Epsilon, max_ulps: u32) -> bool {
207		self.normal.ulps_eq(&other.normal, epsilon, max_ulps)
208			&& self.bias.ulps_eq(&other.bias, epsilon, max_ulps)
209	}
210}
211
212#[cfg(feature = "rkyv")]
213impl<N: Copy + RealField> rkyv::Archive for Plane<N> {
214	type Archived = Self;
215	type Resolver = ();
216
217	#[inline]
218	#[allow(unsafe_code)]
219	unsafe fn resolve(&self, _: usize, (): Self::Resolver, out: *mut Self::Archived) {
220		unsafe {
221			out.write(rkyv::to_archived!(*self as Self));
222		}
223	}
224}
225
226#[cfg(feature = "rkyv")]
227impl<Ser: rkyv::Fallible + ?Sized, N: Copy + RealField> rkyv::Serialize<Ser> for Plane<N> {
228	#[inline]
229	fn serialize(&self, _: &mut Ser) -> Result<Self::Resolver, Ser::Error> {
230		Ok(())
231	}
232}
233
234#[cfg(feature = "rkyv")]
235impl<De: rkyv::Fallible + ?Sized, N: Copy + RealField> rkyv::Deserialize<Self, De> for Plane<N> {
236	#[inline]
237	fn deserialize(&self, _: &mut De) -> Result<Self, De::Error> {
238		Ok(rkyv::from_archived!(*self))
239	}
240}