1use core::fmt;
4
5use nalgebra::UnitQuaternion;
6
7use crate::{Isometry, Point2, Point3, UnitVec2, UnitVec3, Vec2, Vec3};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FrameError {
12 NonFinite,
14 ZeroAxis,
16 DegenerateHint,
19 NotOrthonormal,
22}
23
24impl fmt::Display for FrameError {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.write_str(match self {
27 FrameError::NonFinite => "frame has a non-finite coordinate",
28 FrameError::ZeroAxis => "frame axis has zero length",
29 FrameError::DegenerateHint => "frame x hint is zero or parallel to the axis",
30 FrameError::NotOrthonormal => "frame axes are not orthonormal",
31 })
32 }
33}
34
35impl std::error::Error for FrameError {}
36
37#[derive(Debug, Clone, Copy, PartialEq)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57#[cfg_attr(feature = "serde", serde(try_from = "FrameRepr", into = "FrameRepr"))]
58pub struct Frame {
59 origin: Point3,
60 x: UnitVec3,
61 y: UnitVec3,
62 z: UnitVec3,
63}
64
65#[cfg(feature = "serde")]
69#[derive(serde::Serialize, serde::Deserialize)]
70struct FrameRepr {
71 origin: Point3,
72 x: Vec3,
73 y: Vec3,
74 z: Vec3,
75}
76
77#[cfg(feature = "serde")]
78impl From<Frame> for FrameRepr {
79 fn from(f: Frame) -> Self {
80 FrameRepr {
81 origin: f.origin,
82 x: f.x.into_inner(),
83 y: f.y.into_inner(),
84 z: f.z.into_inner(),
85 }
86 }
87}
88
89#[cfg(feature = "serde")]
90impl TryFrom<FrameRepr> for Frame {
91 type Error = FrameError;
92
93 fn try_from(r: FrameRepr) -> Result<Self, FrameError> {
94 Frame::from_orthonormal(r.origin, r.x, r.y, r.z)
95 }
96}
97
98impl Frame {
99 pub fn world() -> Self {
101 Frame {
102 origin: Point3::origin(),
103 x: Vec3::x_axis(),
104 y: Vec3::y_axis(),
105 z: Vec3::z_axis(),
106 }
107 }
108
109 pub fn new(origin: Point3, z: Vec3, x_hint: Vec3) -> Result<Self, FrameError> {
118 if !(is_finite3(&origin.coords) && is_finite3(&z) && is_finite3(&x_hint)) {
119 return Err(FrameError::NonFinite);
120 }
121 let z = UnitVec3::try_new(z, 0.0).ok_or(FrameError::ZeroAxis)?;
122 let perpendicular = x_hint - z.dot(&x_hint) * z.into_inner();
123 let x = UnitVec3::try_new(perpendicular, 0.0).ok_or(FrameError::DegenerateHint)?;
124 Ok(Self::orthonormalised(origin, x, z))
125 }
126
127 pub fn from_z(origin: Point3, z: Vec3) -> Result<Self, FrameError> {
137 if !(is_finite3(&origin.coords) && is_finite3(&z)) {
138 return Err(FrameError::NonFinite);
139 }
140 let z = UnitVec3::try_new(z, 0.0).ok_or(FrameError::ZeroAxis)?;
141 let (a, b, c) = (z.x, z.y, z.z);
142 let (aa, ba, ca) = (a.abs(), b.abs(), c.abs());
143 let hint = if ba <= aa && ba <= ca {
144 if aa > ca {
145 Vec3::new(-c, 0.0, a)
146 } else {
147 Vec3::new(c, 0.0, -a)
148 }
149 } else if aa <= ba && aa <= ca {
150 if ba > ca {
151 Vec3::new(0.0, -c, b)
152 } else {
153 Vec3::new(0.0, c, -b)
154 }
155 } else if aa > ba {
156 Vec3::new(-b, a, 0.0)
157 } else {
158 Vec3::new(b, -a, 0.0)
159 };
160 let x = UnitVec3::try_new(hint, 0.0).ok_or(FrameError::ZeroAxis)?;
163 Ok(Self::orthonormalised(origin, x, z))
164 }
165
166 pub fn from_orthonormal(origin: Point3, x: Vec3, y: Vec3, z: Vec3) -> Result<Self, FrameError> {
182 if !(is_finite3(&origin.coords) && is_finite3(&x) && is_finite3(&y) && is_finite3(&z)) {
183 return Err(FrameError::NonFinite);
184 }
185 let unit = |v: &Vec3| crate::is_negligible(v.norm() - 1.0, 1.0);
186 let perpendicular = |a: &Vec3, b: &Vec3| crate::is_negligible(a.dot(b), 1.0);
187 if !(unit(&x) && unit(&y) && unit(&z))
188 || !(perpendicular(&x, &y) && perpendicular(&y, &z) && perpendicular(&z, &x))
189 || !crate::is_negligible((x.cross(&y) - z).norm(), 1.0)
190 {
191 return Err(FrameError::NotOrthonormal);
192 }
193 Ok(Frame {
194 origin,
195 x: UnitVec3::new_unchecked(x),
196 y: UnitVec3::new_unchecked(y),
197 z: UnitVec3::new_unchecked(z),
198 })
199 }
200
201 pub fn from_rotation(origin: Point3, rotation: &UnitQuaternion<f64>) -> Self {
204 let x = UnitVec3::new_normalize(rotation.transform_vector(&Vec3::x()));
205 let z = UnitVec3::new_normalize(rotation.transform_vector(&Vec3::z()));
206 Self::orthonormalised(origin, x, z)
207 }
208
209 fn orthonormalised(origin: Point3, x: UnitVec3, z: UnitVec3) -> Self {
213 let y = UnitVec3::new_normalize(z.cross(&x));
214 let x = UnitVec3::new_normalize(y.cross(&z));
215 Frame { origin, x, y, z }
216 }
217
218 pub const fn with_origin(&self, origin: Point3) -> Frame {
222 Frame {
223 origin,
224 x: self.x,
225 y: self.y,
226 z: self.z,
227 }
228 }
229
230 pub const fn origin(&self) -> Point3 {
232 self.origin
233 }
234
235 pub const fn x(&self) -> UnitVec3 {
237 self.x
238 }
239
240 pub const fn y(&self) -> UnitVec3 {
242 self.y
243 }
244
245 pub const fn z(&self) -> UnitVec3 {
247 self.z
248 }
249
250 pub fn to_local(&self, p: Point3) -> Point3 {
252 Point3::from(self.vec_to_local(p - self.origin))
253 }
254
255 pub fn to_world(&self, p: Point3) -> Point3 {
257 self.origin + self.vec_to_world(p.coords)
258 }
259
260 pub fn vec_to_local(&self, v: Vec3) -> Vec3 {
262 Vec3::new(v.dot(&self.x), v.dot(&self.y), v.dot(&self.z))
263 }
264
265 pub fn vec_to_world(&self, v: Vec3) -> Vec3 {
267 v.x * self.x.into_inner() + v.y * self.y.into_inner() + v.z * self.z.into_inner()
268 }
269
270 pub fn rotation(&self) -> UnitQuaternion<f64> {
272 UnitQuaternion::from_basis_unchecked(&[
273 self.x.into_inner(),
274 self.y.into_inner(),
275 self.z.into_inner(),
276 ])
277 }
278
279 pub fn as_isometry(&self) -> Isometry {
282 Isometry::new(self.rotation(), self.origin.coords)
283 }
284
285 pub fn transformed(&self, motion: &Isometry) -> Frame {
288 Self::orthonormalised(
289 motion.apply(self.origin),
290 motion.apply_unit(self.x),
291 motion.apply_unit(self.z),
292 )
293 }
294}
295
296fn is_finite3(v: &Vec3) -> bool {
297 v.iter().all(|c| c.is_finite())
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum Handedness {
303 Right,
305 Left,
307}
308
309#[derive(Debug, Clone, Copy, PartialEq)]
323#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
324#[cfg_attr(feature = "serde", serde(try_from = "Frame2Repr", into = "Frame2Repr"))]
325pub struct Frame2 {
326 origin: Point2,
327 x: UnitVec2,
328 y: UnitVec2,
329}
330
331#[cfg(feature = "serde")]
334#[derive(serde::Serialize, serde::Deserialize)]
335struct Frame2Repr {
336 origin: Point2,
337 x: Vec2,
338 y: Vec2,
339}
340
341#[cfg(feature = "serde")]
342impl From<Frame2> for Frame2Repr {
343 fn from(f: Frame2) -> Self {
344 Frame2Repr {
345 origin: f.origin,
346 x: f.x.into_inner(),
347 y: f.y.into_inner(),
348 }
349 }
350}
351
352#[cfg(feature = "serde")]
353impl TryFrom<Frame2Repr> for Frame2 {
354 type Error = FrameError;
355
356 fn try_from(r: Frame2Repr) -> Result<Self, FrameError> {
357 Frame2::from_orthonormal(r.origin, r.x, r.y)
358 }
359}
360
361impl Frame2 {
362 pub fn identity() -> Self {
364 Frame2 {
365 origin: Point2::origin(),
366 x: Vec2::x_axis(),
367 y: Vec2::y_axis(),
368 }
369 }
370
371 pub fn new(origin: Point2, x: Vec2, handedness: Handedness) -> Result<Self, FrameError> {
375 if !(origin.coords.iter().all(|c| c.is_finite()) && x.iter().all(|c| c.is_finite())) {
376 return Err(FrameError::NonFinite);
377 }
378 let x = UnitVec2::try_new(x, 0.0).ok_or(FrameError::ZeroAxis)?;
379 let y = match handedness {
380 Handedness::Right => Vec2::new(-x.y, x.x),
381 Handedness::Left => Vec2::new(x.y, -x.x),
382 };
383 Ok(Frame2 {
384 origin,
385 x,
386 y: UnitVec2::new_unchecked(y),
387 })
388 }
389
390 pub fn from_orthonormal(origin: Point2, x: Vec2, y: Vec2) -> Result<Self, FrameError> {
396 let finite = |v: &Vec2| v.iter().all(|c| c.is_finite());
397 if !(finite(&origin.coords) && finite(&x) && finite(&y)) {
398 return Err(FrameError::NonFinite);
399 }
400 let unit = |v: &Vec2| crate::is_negligible(v.norm() - 1.0, 1.0);
401 if !(unit(&x) && unit(&y)) || !crate::is_negligible(x.dot(&y), 1.0) {
402 return Err(FrameError::NotOrthonormal);
403 }
404 Ok(Frame2 {
405 origin,
406 x: UnitVec2::new_unchecked(x),
407 y: UnitVec2::new_unchecked(y),
408 })
409 }
410
411 pub const fn origin(&self) -> Point2 {
413 self.origin
414 }
415
416 pub fn translated(&self, by: Vec2) -> Frame2 {
426 Frame2 {
427 origin: self.origin + by,
428 x: self.x,
429 y: self.y,
430 }
431 }
432
433 pub const fn x(&self) -> UnitVec2 {
435 self.x
436 }
437
438 pub const fn y(&self) -> UnitVec2 {
440 self.y
441 }
442
443 pub fn handedness(&self) -> Handedness {
445 if self.is_right_handed() {
446 Handedness::Right
447 } else {
448 Handedness::Left
449 }
450 }
451
452 pub fn is_right_handed(&self) -> bool {
454 self.x.perp(&self.y) > 0.0
455 }
456
457 pub fn to_local(&self, p: Point2) -> Point2 {
459 Point2::from(self.vec_to_local(p - self.origin))
460 }
461
462 pub fn to_world(&self, p: Point2) -> Point2 {
464 self.origin + self.vec_to_world(p.coords)
465 }
466
467 pub fn vec_to_local(&self, v: Vec2) -> Vec2 {
469 Vec2::new(v.dot(&self.x), v.dot(&self.y))
470 }
471
472 pub fn vec_to_world(&self, v: Vec2) -> Vec2 {
474 v.x * self.x.into_inner() + v.y * self.y.into_inner()
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481
482 #[test]
483 fn world_frame_is_the_identity() {
484 let w = Frame::world();
485 let p = Point3::new(1.0, -2.0, 3.0);
486 assert_eq!(w.to_local(p), p);
487 assert_eq!(w.to_world(p), p);
488 assert_eq!(w.rotation(), UnitQuaternion::identity());
489 }
490
491 #[test]
492 fn from_z_follows_the_axis_rule() {
493 let f = Frame::from_z(Point3::origin(), Vec3::z()).unwrap();
494 assert_eq!(f.x().into_inner(), Vec3::x());
495 assert_eq!(f.y().into_inner(), Vec3::y());
496 let f = Frame::from_z(Point3::origin(), Vec3::x()).unwrap();
497 assert_eq!(f.x().into_inner(), Vec3::z());
498 assert_eq!(f.y().into_inner(), -Vec3::y());
499 let f = Frame::from_z(Point3::origin(), Vec3::y()).unwrap();
500 assert_eq!(f.x().into_inner(), Vec3::z());
501 assert_eq!(f.y().into_inner(), Vec3::x());
502 }
503
504 #[test]
505 fn errors_name_the_problem() {
506 let o = Point3::origin();
507 assert_eq!(
508 Frame::new(o, Vec3::zeros(), Vec3::x()),
509 Err(FrameError::ZeroAxis)
510 );
511 assert_eq!(
512 Frame::new(o, Vec3::z(), Vec3::z() * 2.0),
513 Err(FrameError::DegenerateHint)
514 );
515 assert_eq!(
516 Frame::new(o, Vec3::z(), Vec3::zeros()),
517 Err(FrameError::DegenerateHint)
518 );
519 assert_eq!(
520 Frame::new(o, Vec3::new(f64::NAN, 0.0, 1.0), Vec3::x()),
521 Err(FrameError::NonFinite)
522 );
523 assert_eq!(Frame::from_z(o, Vec3::zeros()), Err(FrameError::ZeroAxis));
524 assert_eq!(
525 Frame2::new(Point2::origin(), Vec2::zeros(), Handedness::Right),
526 Err(FrameError::ZeroAxis)
527 );
528 }
529
530 #[test]
531 fn frame2_handedness_round_trips() {
532 for h in [Handedness::Right, Handedness::Left] {
533 let f = Frame2::new(Point2::new(0.5, -0.5), Vec2::new(3.0, 4.0), h).unwrap();
534 assert_eq!(f.handedness(), h);
535 let p = Point2::new(0.3, 0.9);
536 assert!((f.to_local(f.to_world(p)) - p).norm() < 1e-15);
537 assert!(f.x().dot(&f.y()).abs() < 1e-15);
538 }
539 assert!(Frame2::identity().is_right_handed());
540 }
541}