1use std::{
2 cmp::Ordering,
3 hash::Hash,
4 ops::{Mul, Neg},
5};
6
7use approx_collections::{ApproxCmpZero, ApproxEq, ApproxEqZero, ApproxHash, Precision};
8
9use super::{Axes, Multivector, Scalar, Term, Wedge};
10
11pub const NI: Blade1 = Blade1 {
13 m: 1.0,
14 p: 1.0,
15 x: 0.0,
16 y: 0.0,
17};
18
19pub const NO: Blade1 = Blade1 {
21 m: 0.5,
22 p: -0.5,
23 x: 0.0,
24 y: 0.0,
25};
26
27macro_rules! impl_multivector {
28 ($type:ty {
29 dual: $dual:ty,
30 len: $len:expr,
31 terms: [$(($axes:path, $field:ident)),* $(,)?] $(,)?
32 }) => {
33 impl Multivector for $type {
34 type Terms = [Term; $len];
35
36 type Dual = $dual;
37
38 fn zero() -> Self {
39 Self::default()
40 }
41
42 fn has_same_terms_as(self, _other: Self) -> bool {
43 true
44 }
45
46 fn terms(self) -> [Term; $len] {
47 [$(Term::new($axes, self.$field)),*]
48 }
49
50 fn get(&self, axes: Axes) -> Option<&Scalar> {
51 match axes {
52 $($axes => Some(&self.$field),)*
53 _ => None,
54 }
55 }
56
57 fn get_mut(&mut self, axes: Axes) -> Option<&mut Scalar> {
58 match axes {
59 $($axes => Some(&mut self.$field),)*
60 _ => None,
61 }
62 }
63 }
64 };
65}
66
67pub trait Blade: Multivector {
69 const GRADE: u8;
71
72 fn connect<R: Blade>(self, other: R) -> <Self::Dual as Wedge<R>>::Output
74 where
75 Self::Dual: Wedge<R>,
76 {
77 self.dual().wedge(other)
78 }
79
80 fn is_flat(self) -> bool
85 where
86 Self: Wedge<Blade1>,
87 {
88 self.is_flat_with_prec(Precision::DEFAULT)
89 }
90
91 fn is_flat_with_prec(self, prec: Precision) -> bool
94 where
95 Self: Wedge<Blade1>,
96 {
97 self.wedge(NI).terms().into_iter().all(|t| prec.eq_zero(t))
98 }
99}
100
101impl Multivector for Scalar {
102 type Terms = [Term; 1];
103
104 type Dual = Pseudoscalar;
105
106 fn zero() -> Self {
107 Self::default()
108 }
109
110 fn terms(self) -> Self::Terms {
111 [Term::new(Axes::S, self)]
112 }
113
114 fn has_same_terms_as(self, _other: Self) -> bool {
115 true
116 }
117
118 fn get(&self, axes: Axes) -> Option<&Scalar> {
119 match axes {
120 Axes::S => Some(self),
121 _ => None,
122 }
123 }
124
125 fn get_mut(&mut self, axes: Axes) -> Option<&mut Scalar> {
126 match axes {
127 Axes::S => Some(self),
128 _ => None,
129 }
130 }
131}
132impl Blade for Scalar {
133 const GRADE: u8 = 0;
134}
135
136#[derive(Debug, Default, Copy, Clone, PartialEq)]
138#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::NoUninit))]
139#[repr(C)]
140#[allow(missing_docs)]
141pub struct Blade1 {
142 pub m: Scalar,
143 pub p: Scalar,
144 pub x: Scalar,
145 pub y: Scalar,
146}
147impl_multivector!(Blade1 {
148 dual: Blade3,
149 len: 4,
150 terms: [(Axes::M, m), (Axes::P, p), (Axes::X, x), (Axes::Y, y)],
151});
152impl Blade for Blade1 {
153 const GRADE: u8 = 1;
154}
155impl Blade1 {
156 pub fn ni(self) -> Scalar {
158 (self.m + self.p) / 2.0
159 }
160 pub fn no(self) -> Scalar {
162 self.m - self.p
163 }
164 fn no_eq_zero(self, prec: Precision) -> bool {
165 prec.eq(self.m, self.p)
166 }
167
168 pub fn unpack(self) -> Option<Point> {
173 self.unpack_with_prec(Precision::DEFAULT)
174 }
175 pub fn unpack_with_prec(self, prec: Precision) -> Option<Point> {
178 if self.no_eq_zero(prec) {
179 if [self.x, self.y].approx_eq_zero(prec) {
180 Some(Point::Infinity)
181 } else {
182 None
183 }
184 } else {
185 if self.mag2().approx_eq_zero(prec) {
186 let no = self.no();
187 Some(Point::Finite([self.x / no, self.y / no]))
188 } else {
189 None
190 }
191 }
192 }
193
194 pub fn unpack_unchecked(self) -> Point {
200 self.unpack_unchecked_with_prec(Precision::DEFAULT)
201 }
202 pub fn unpack_unchecked_with_prec(self, prec: Precision) -> Point {
206 if self.no_eq_zero(prec) {
207 Point::Infinity
208 } else {
209 let no = self.no();
210 Point::Finite([self.x / no, self.y / no])
211 }
212 }
213
214 #[must_use]
216 pub fn normalize_point(self) -> Self {
217 self / self.no()
218 }
219}
220
221#[derive(Debug, Copy, Clone, PartialEq)]
223pub enum Point {
224 Finite([Scalar; 2]),
226 Infinity,
228}
229impl Default for Point {
230 fn default() -> Self {
231 Point::Finite([0.0; 2])
232 }
233}
234impl From<Point> for Blade1 {
235 fn from(value: Point) -> Self {
236 value.to_blade()
237 }
238}
239impl ApproxEq for Point {
240 fn approx_eq(&self, other: &Self, prec: Precision) -> bool {
241 match (self, other) {
242 (Point::Finite(a), Point::Finite(b)) => prec.eq(a, b),
243 (Point::Infinity, Point::Infinity) => true,
244
245 (Point::Finite(_), _) | (_, Point::Finite(_)) => false,
246 }
247 }
248}
249impl ApproxHash for Point {
250 fn intern_floats<F: FnMut(&mut f64)>(&mut self, f: &mut F) {
251 match self {
252 Point::Finite(xy) => xy.intern_floats(f),
253 Point::Infinity => (),
254 }
255 }
256
257 fn interned_eq(&self, other: &Self) -> bool {
258 match (self, other) {
259 (Point::Finite(f1), Point::Finite(f2)) => f1.interned_eq(f2),
260 (Point::Infinity, Point::Infinity) => true,
261
262 (Point::Finite(_) | Point::Infinity, _) => false,
263 }
264 }
265
266 fn interned_hash<H: std::hash::Hasher>(&self, state: &mut H) {
267 match self {
268 Point::Finite(xy) => xy.interned_hash(state),
269 Point::Infinity => (),
270 }
271 }
272}
273impl Point {
274 pub fn finite(self) -> Option<[Scalar; 2]> {
277 match self {
278 Point::Finite(xy) => Some(xy),
279 _ => None,
280 }
281 }
282
283 pub fn to_blade(self) -> Blade1 {
285 match self {
286 Point::Finite([x, y]) => crate::point(x, y),
287 Point::Infinity => NI,
288 }
289 }
290}
291
292#[derive(Debug, Default, Copy, Clone, PartialEq)]
295#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::NoUninit))]
296#[repr(C)]
297#[allow(missing_docs)]
298pub struct Blade2 {
299 pub mp: Scalar,
300 pub mx: Scalar,
301 pub px: Scalar,
302 pub my: Scalar,
303 pub py: Scalar,
304 pub xy: Scalar,
305}
306impl_multivector!(Blade2 {
307 dual: Blade2,
308 len: 6,
309 terms: [
310 (Axes::MP, mp),
311 (Axes::MX, mx),
312 (Axes::PX, px),
313 (Axes::MY, my),
314 (Axes::PY, py),
315 (Axes::XY, xy),
316 ],
317});
318impl Blade for Blade2 {
319 const GRADE: u8 = 2;
320}
321impl Blade2 {
322 pub fn unpack(self) -> Dipole {
326 self.unpack_with_prec(Precision::DEFAULT)
327 }
328
329 pub fn unpack_with_prec(self, prec: Precision) -> Dipole {
331 if self.approx_eq_zero(prec) {
332 return Dipole::Degenerate;
333 }
334
335 let mag2 = self.mag2();
336
337 match mag2.approx_cmp_zero(prec) {
338 Ordering::Less => {
339 Dipole::Imaginary(self.dual().unpack_raw((-mag2).sqrt(), prec, [1.0, -1.0]))
340 }
341 Ordering::Equal => {
342 let point = self.unpack_raw(0.0, prec, [0.0])[0];
343 let vector = match point {
344 Point::Finite(_) => [self.mx - self.px, self.my - self.py],
345 Point::Infinity => [self.mx, self.my], };
347 Dipole::Tangent(point, vector).normalize()
348 }
349 Ordering::Greater => Dipole::Real(self.unpack_raw(mag2.sqrt(), prec, [1.0, -1.0])),
350 }
351 }
352
353 fn unpack_raw<const N: usize>(
360 self,
361 mag: f64,
362 prec: Precision,
363 distances: [f64; N],
364 ) -> [Point; N] {
365 let candidates = [NI, NO, crate::point(1.0, 0.0)];
369 let multiplier = candidates
370 .iter()
371 .map(|&arbitrary| arbitrary << self)
372 .max_by(|a, b| f64::total_cmp(&a.mag2(), &b.mag2()))
373 .expect("empty candidates list")
374 .inv();
375
376 distances.map(|sign| {
377 ((multiplier << self) + (sign * mag * multiplier)).unpack_unchecked_with_prec(prec)
378 })
379 }
380
381 pub fn rotate(self, angle: Scalar) -> Self {
383 self * angle.cos() + self.dual() * angle.sin()
384 }
385}
386
387#[derive(Debug, Copy, Clone, PartialEq)]
389pub enum Dipole {
390 Real([Point; 2]),
392 Tangent(Point, [Scalar; 2]),
394 Imaginary([Point; 2]),
396 Degenerate,
398}
399impl From<Blade2> for Dipole {
400 fn from(value: Blade2) -> Self {
401 value.unpack()
402 }
403}
404impl From<Dipole> for Blade2 {
405 fn from(value: Dipole) -> Self {
406 value.to_blade()
407 }
408}
409impl ApproxEq for Dipole {
410 fn approx_eq(&self, other: &Self, prec: Precision) -> bool {
411 match (self, other) {
412 (Dipole::Real(pp1), Dipole::Real(pp2)) => prec.eq(pp1, pp2),
413 (Dipole::Tangent(p1, v1), Dipole::Tangent(p2, v2)) => {
414 prec.eq(p1, p2) && prec.eq(v1, v2)
415 }
416 (Dipole::Imaginary(pp1), Dipole::Imaginary(pp2)) => prec.eq(pp1, pp2),
417 (Dipole::Degenerate, Dipole::Degenerate) => true,
418
419 (
420 Dipole::Real(_) | Dipole::Tangent(_, _) | Dipole::Imaginary(_) | Dipole::Degenerate,
421 _,
422 ) => false,
423 }
424 }
425}
426impl ApproxHash for Dipole {
427 fn intern_floats<F: FnMut(&mut f64)>(&mut self, f: &mut F) {
428 match self {
429 Dipole::Real(pp) => pp.intern_floats(f),
430 Dipole::Tangent(p, v) => {
431 p.intern_floats(f);
432 v.intern_floats(f);
433 }
434 Dipole::Imaginary(pp) => pp.intern_floats(f),
435 Dipole::Degenerate => (),
436 }
437 }
438
439 fn interned_eq(&self, other: &Self) -> bool {
440 match (self, other) {
441 (Dipole::Real(pp1), Dipole::Real(pp2)) => pp1.interned_eq(pp2),
442 (Dipole::Tangent(p1, v1), Dipole::Tangent(p2, v2)) => {
443 p1.interned_eq(p2) && v1.interned_eq(v2)
444 }
445 (Dipole::Imaginary(pp1), Dipole::Imaginary(pp2)) => pp1.interned_eq(pp2),
446 (Dipole::Degenerate, Dipole::Degenerate) => true,
447
448 (
449 Dipole::Real(_) | Dipole::Tangent(_, _) | Dipole::Imaginary(_) | Dipole::Degenerate,
450 _,
451 ) => false,
452 }
453 }
454
455 fn interned_hash<H: std::hash::Hasher>(&self, state: &mut H) {
456 std::mem::discriminant(self).hash(state);
457 match self {
458 Dipole::Degenerate => (),
459 Dipole::Real(pp) => pp.interned_hash(state),
460 Dipole::Tangent(p, v) => {
461 p.interned_hash(state);
462 v.interned_hash(state);
463 }
464 Dipole::Imaginary(pp) => pp.interned_hash(state),
465 }
466 }
467}
468impl Neg for Dipole {
469 type Output = Dipole;
470
471 fn neg(self) -> Self::Output {
472 match self {
473 Dipole::Real([p, q]) => Dipole::Real([q, p]),
474 Dipole::Tangent(p, [vx, vy]) => Dipole::Tangent(p, [-vx, -vy]),
475 Dipole::Imaginary([p, q]) => Dipole::Imaginary([q, p]),
476 Dipole::Degenerate => Dipole::Degenerate,
477 }
478 }
479}
480impl Dipole {
481 #[must_use = "normalize() returns a mutated copy"]
483 pub fn normalize(self) -> Self {
484 match self {
485 Dipole::Tangent(p, [vx, vy]) => {
486 let scale = vector_mag([vx, vy]).recip();
487 Dipole::Tangent(p, [vx * scale, vy * scale])
488 }
489 _ => self,
490 }
491 }
492
493 pub fn real(self) -> Option<[Point; 2]> {
495 match self {
496 Dipole::Real(pair) => Some(pair),
497 _ => None,
498 }
499 }
500
501 pub fn to_blade(self) -> Blade2 {
503 match self {
504 Dipole::Real([p1, p2]) => p1.to_blade() ^ p2.to_blade(),
505 Dipole::Tangent(p, v) => crate::tangent_point(p, v),
506 Dipole::Imaginary([p1, p2]) => (p1.to_blade() ^ p2.to_blade()).antidual(),
507 Dipole::Degenerate => Blade2::zero(),
508 }
509 }
510}
511
512#[derive(Debug, Default, Copy, Clone, PartialEq)]
514#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::NoUninit))]
515#[repr(C)]
516#[allow(missing_docs)]
517pub struct Blade3 {
518 pub mpx: Scalar,
519 pub mpy: Scalar,
520 pub mxy: Scalar,
521 pub pxy: Scalar,
522}
523impl_multivector!(Blade3 {
524 dual: Blade1,
525 len: 4,
526 terms: [
527 (Axes::MPX, mpx),
528 (Axes::MPY, mpy),
529 (Axes::MXY, mxy),
530 (Axes::PXY, pxy),
531 ],
532});
533impl Blade for Blade3 {
534 const GRADE: u8 = 3;
535}
536impl Blade3 {
537 pub fn unpack(self) -> Circle {
541 self.unpack_with_prec(Precision::DEFAULT)
542 }
543
544 pub fn unpack_with_prec(self, prec: Precision) -> Circle {
546 if self.approx_eq_zero(prec) {
547 return Circle::Degenerate;
548 }
549
550 let dual = self.antidual();
551 if dual.no_eq_zero(prec) {
552 if [dual.x, dual.y].approx_eq_zero(prec) {
553 Circle::Infinity(Orientation::from(dual.m)) } else {
555 Circle::Line {
556 a: dual.x,
557 b: dual.y,
558 c: dual.p,
559 }
560 .normalize()
561 }
562 } else {
563 let no = dual.no();
564 let cx = dual.x / no;
565 let cy = dual.y / no;
566 let r2 = dual.mag2() / (no * no);
567 let r = r2.abs().sqrt() * r2.signum();
568 Circle::Circle {
569 cx,
570 cy,
571 r,
572 ori: Orientation::from(no),
573 }
574 }
575 }
576}
577
578#[derive(Debug, Copy, Clone, PartialEq)]
580#[allow(missing_docs)]
581pub enum Circle {
582 Line { a: Scalar, b: Scalar, c: Scalar },
588 Circle {
599 cx: Scalar,
600 cy: Scalar,
601 r: Scalar,
602 ori: Orientation,
603 },
604 Infinity(Orientation),
606 Degenerate,
608}
609impl From<Blade3> for Circle {
610 fn from(value: Blade3) -> Self {
611 value.unpack()
612 }
613}
614impl From<Circle> for Blade3 {
615 fn from(value: Circle) -> Self {
616 value.to_blade()
617 }
618}
619impl ApproxEq for Circle {
620 fn approx_eq(&self, other: &Self, prec: Precision) -> bool {
621 match (self, other) {
622 (
623 Circle::Line {
624 a: a1,
625 b: b1,
626 c: c1,
627 },
628 Circle::Line {
629 a: a2,
630 b: b2,
631 c: c2,
632 },
633 ) => prec.eq([a1, b1, c1], [a2, b2, c2]),
634 (
635 Circle::Circle {
636 cx: cx1,
637 cy: cy1,
638 r: r1,
639 ori: ori1,
640 },
641 Circle::Circle {
642 cx: cx2,
643 cy: cy2,
644 r: r2,
645 ori: ori2,
646 },
647 ) => prec.eq([cx1, cy1, r1], [cx2, cy2, r2]) && ori1 == ori2,
648 (Circle::Infinity(ori1), Circle::Infinity(ori2)) => ori1 == ori2,
649 (Circle::Degenerate, Circle::Degenerate) => true,
650
651 (
652 Circle::Line { .. }
653 | Circle::Circle { .. }
654 | Circle::Infinity(_)
655 | Circle::Degenerate,
656 _,
657 ) => false,
658 }
659 }
660}
661impl ApproxHash for Circle {
662 fn intern_floats<F: FnMut(&mut f64)>(&mut self, f: &mut F) {
663 match self {
664 Circle::Line { a, b, c } => [a, b, c].intern_floats(f),
665 Circle::Circle { cx, cy, r, ori: _ } => [cx, cy, r].intern_floats(f),
666 Circle::Infinity(_ori) => (),
667 Circle::Degenerate => (),
668 }
669 }
670
671 fn interned_eq(&self, other: &Self) -> bool {
672 match (self, other) {
673 (
674 &Circle::Line {
675 a: a1,
676 b: b1,
677 c: c1,
678 },
679 &Circle::Line {
680 a: a2,
681 b: b2,
682 c: c2,
683 },
684 ) => [a1, b1, c1].interned_eq(&[a2, b2, c2]),
685 (
686 &Circle::Circle {
687 cx: cx1,
688 cy: cy1,
689 r: r1,
690 ori: ori1,
691 },
692 &Circle::Circle {
693 cx: cx2,
694 cy: cy2,
695 r: r2,
696 ori: ori2,
697 },
698 ) => [cx1, cy1, r1].interned_eq(&[cx2, cy2, r2]) && ori1 == ori2,
699 (Circle::Infinity(ori1), Circle::Infinity(ori2)) => ori1 == ori2,
700 (Circle::Degenerate, Circle::Degenerate) => true,
701
702 (
703 Circle::Line { .. }
704 | Circle::Circle { .. }
705 | Circle::Infinity(_)
706 | Circle::Degenerate,
707 _,
708 ) => false,
709 }
710 }
711
712 fn interned_hash<H: std::hash::Hasher>(&self, state: &mut H) {
713 std::mem::discriminant(self).hash(state);
714 match *self {
715 Circle::Line { a, b, c } => [a, b, c].interned_hash(state),
716 Circle::Circle { cx, cy, r, ori } => {
717 [cx, cy, r].interned_hash(state);
718 ori.hash(state);
719 }
720 Circle::Infinity(ori) => ori.hash(state),
721 Circle::Degenerate => (),
722 }
723 }
724}
725impl Neg for Circle {
726 type Output = Circle;
727
728 fn neg(self) -> Self::Output {
729 match self {
730 Circle::Line { a, b, c } => Circle::Line {
731 a: -a,
732 b: -b,
733 c: -c,
734 },
735 Circle::Circle { cx, cy, r, ori } => Circle::Circle {
736 cx,
737 cy,
738 r,
739 ori: -ori,
740 },
741 Circle::Infinity(ori) => Circle::Infinity(-ori),
742 Circle::Degenerate => Circle::Degenerate,
743 }
744 }
745}
746impl Circle {
747 #[must_use = "normalize() returns a mutated copy"]
749 pub fn normalize(self) -> Self {
750 match self {
751 Circle::Line { a, b, c } => {
752 let scale = vector_mag([a, b]).recip();
753 Circle::Line {
754 a: a * scale,
755 b: b * scale,
756 c: c * scale,
757 }
758 }
759 _ => self,
760 }
761 }
762
763 pub fn to_blade(self) -> Blade3 {
765 match self {
766 Circle::Line { a, b, c } => crate::line(a, b, c),
767 Circle::Circle { cx, cy, r, ori } => ori * crate::circle(crate::point(cx, cy), r),
768 Circle::Infinity(ori) => ori * !crate::NI,
769 Circle::Degenerate => Blade3::zero(),
770 }
771 }
772}
773
774#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
776pub enum Orientation {
777 #[default]
779 Pos = 1,
780 Neg = -1,
782}
783impl From<Scalar> for Orientation {
784 fn from(value: Scalar) -> Self {
785 if value.is_sign_positive() {
786 Orientation::Pos
787 } else {
788 Orientation::Neg
789 }
790 }
791}
792impl<T: Neg<Output = T>> Mul<T> for Orientation {
793 type Output = T;
794
795 fn mul(self, rhs: T) -> Self::Output {
796 match self {
797 Orientation::Pos => rhs,
798 Orientation::Neg => -rhs,
799 }
800 }
801}
802impl Neg for Orientation {
803 type Output = Orientation;
804
805 fn neg(self) -> Self::Output {
806 match self {
807 Orientation::Pos => Orientation::Neg,
808 Orientation::Neg => Orientation::Pos,
809 }
810 }
811}
812
813#[derive(Debug, Default, Copy, Clone, PartialEq)]
815#[cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::NoUninit))]
816#[repr(C)]
817#[allow(missing_docs)]
818pub struct Pseudoscalar {
819 pub mpxy: Scalar,
820}
821impl_multivector!(Pseudoscalar {
822 dual: Scalar,
823 len: 1,
824 terms: [(Axes::MPXY, mpxy)],
825});
826impl Blade for Pseudoscalar {
827 const GRADE: u8 = 4;
828}
829impl ApproxCmpZero for Pseudoscalar {
830 fn approx_cmp_zero(&self, prec: Precision) -> Ordering {
831 self.mpxy.approx_cmp_zero(prec)
832 }
833}
834
835fn vector_mag([x, y]: [Scalar; 2]) -> Scalar {
836 ((x * x) + (y * y)).sqrt()
837}