cga2d/
blade.rs

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
11/// ∞, also called n ͚, representing the point at infinity.
12pub const NI: Blade1 = Blade1 {
13    m: 1.0,
14    p: 1.0,
15    x: 0.0,
16    y: 0.0,
17};
18
19/// nₒ, representing the origin.
20pub 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
67/// Multivector of a compile-time-known grade.
68pub trait Blade: Multivector {
69    /// Number of basis vectors comprising each term of the blade.
70    const GRADE: u8;
71
72    /// Returns `!self ^ other`
73    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    /// Returns whether the blade is "flat" (whether one of its simple component
81    /// blades is ∞).
82    ///
83    /// Uses [`Precision::DEFAULT`].
84    fn is_flat(self) -> bool
85    where
86        Self: Wedge<Blade1>,
87    {
88        self.is_flat_with_prec(Precision::DEFAULT)
89    }
90
91    /// Returns whether the blade is "flat" (whether one of its simple component
92    /// blades is ∞).
93    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/// 1-blade, used to represent points, vectors, and round points.
137#[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    /// Returns the ∞ component of the blade (also called n ͚).
157    pub fn ni(self) -> Scalar {
158        (self.m + self.p) / 2.0
159    }
160    /// Returns the nₒ component of the blade.
161    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    /// Returns the coordinates of a point in 2D Euclidean space, or `None` if
169    /// the point is not on the conformal sphere.
170    ///
171    /// Uses [`Precision::DEFAULT`].
172    pub fn unpack(self) -> Option<Point> {
173        self.unpack_with_prec(Precision::DEFAULT)
174    }
175    /// Returns the coordinates of a point in 2D Euclidean space, or `None` if
176    /// the point is not on the conformal sphere.
177    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    /// Returns the coordinates of a point in 2D Euclidean space.
195    ///
196    /// The result is undefined if the point is not on the conformal sphere.
197    ///
198    /// Uses [`Precision::DEFAULT`].
199    pub fn unpack_unchecked(self) -> Point {
200        self.unpack_unchecked_with_prec(Precision::DEFAULT)
201    }
202    /// Returns the coordinates of a point in 2D Euclidean space.
203    ///
204    /// The result is undefined if the point is not on the conformal sphere.
205    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    /// Normalizes a point with respect to nₒ.
215    #[must_use]
216    pub fn normalize_point(self) -> Self {
217        self / self.no()
218    }
219}
220
221/// Euclidean point.
222#[derive(Debug, Copy, Clone, PartialEq)]
223pub enum Point {
224    /// Finite point with X and Y coordinates.
225    Finite([Scalar; 2]),
226    /// Point at infinity.
227    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    /// Returns the finite coordinates of the point, or `None` if it is not a
275    /// real finite point.
276    pub fn finite(self) -> Option<[Scalar; 2]> {
277        match self {
278            Point::Finite(xy) => Some(xy),
279            _ => None,
280        }
281    }
282
283    /// Returns a blade representing the point.
284    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/// 2-blade, used to represent point pairs (real and imaginary), tangent
293/// vectors, and flat points.
294#[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    /// Returns the real, imaginary, or tangent pair of points in a point pair.
323    ///
324    /// Uses [`Precision::DEFAULT`].
325    pub fn unpack(self) -> Dipole {
326        self.unpack_with_prec(Precision::DEFAULT)
327    }
328
329    /// Returns the real, imaginary, or tangent pair of points in a point pair.
330    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], // (mx, my) = (px, py)
346                };
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    /// Unpacks a real point pair or tangent vector.
354    ///
355    /// The result is undefined for an imaginary point pair.
356    ///
357    /// - To get both points of a real point, use `distances = [1.0, -1.0]`.
358    /// - To get the point of a tangent vector, use `distances = [0.0]`.
359    fn unpack_raw<const N: usize>(
360        self,
361        mag: f64,
362        prec: Precision,
363        distances: [f64; N],
364    ) -> [Point; N] {
365        // We need to use an arbitrary point that is not in the point
366        // pair. Of these three points, there is guaranteed to be one
367        // with a nonzero result.
368        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    /// Rotates a tangent vector counterclockwise by `angle` (in radians).
382    pub fn rotate(self, angle: Scalar) -> Self {
383        self * angle.cos() + self.dual() * angle.sin()
384    }
385}
386
387/// Real or imaginary point pair in Euclidean space.
388#[derive(Debug, Copy, Clone, PartialEq)]
389pub enum Dipole {
390    /// Pair of real points.
391    Real([Point; 2]),
392    /// Tangent point, represented by a point and a Euclidean tangent vector.
393    Tangent(Point, [Scalar; 2]),
394    /// Imaginary point pair, represented by the points in its dual.
395    Imaginary([Point; 2]),
396    /// Degenerate point pair.
397    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    /// Normalizes tangent points so that the vector is unit-length.
482    #[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    /// Returns the real point pair, or `None` if it is not real.
494    pub fn real(self) -> Option<[Point; 2]> {
495        match self {
496            Dipole::Real(pair) => Some(pair),
497            _ => None,
498        }
499    }
500
501    /// Returns a blade representing the dipole.
502    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/// 3-blade, used to represent circles (real and imaginary).
513#[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    /// Returns the line or circle in Euclidean space.
538    ///
539    /// Uses [`Precision::DEFAULT`].
540    pub fn unpack(self) -> Circle {
541        self.unpack_with_prec(Precision::DEFAULT)
542    }
543
544    /// Returns the line or circle in Euclidean space.
545    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)) // m = p
554            } 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/// Euclidean line or circle.
579#[derive(Debug, Copy, Clone, PartialEq)]
580#[allow(missing_docs)]
581pub enum Circle {
582    /// Line described by the equation _ax+by=c_.
583    ///
584    /// The vector `(a, b)` represents the normal vector of the line. `a` and
585    /// `b` cannot both be zero. When returned from [`Blade3::unpack()`] or
586    /// [`Blade3::unpack_with_prec()`], the vector `(a, b)` is unit-length.
587    Line { a: Scalar, b: Scalar, c: Scalar },
588    /// Real or imaginary circle described by the equation
589    /// _(x-cx)^2+(y-cy)^2=r^2_. If _r_ is positive, then the circle is real; if
590    /// _r_ is negative, then the circle is imaginary.
591    ///
592    /// Imaginary circles are actually circles with an _imaginary_ radius (so
593    /// their radius _squared_ is negative) but we represent them with a
594    /// negative radius for convenience.
595    ///
596    /// The radius may be zero, in which case the circle is the dual of a real
597    /// point.
598    Circle {
599        cx: Scalar,
600        cy: Scalar,
601        r: Scalar,
602        ori: Orientation,
603    },
604    /// Circle with zero radius centered on the point at infinity.
605    Infinity(Orientation),
606    /// Degenerate circle.
607    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    /// Normalizes lines so that the vector `(a, b)` is unit-length.
748    #[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    /// Returns a blade representing the circle.
764    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/// Orientation of an object.
775#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
776pub enum Orientation {
777    /// Positive or counterclockwise.
778    #[default]
779    Pos = 1,
780    /// Negative or clockwise.
781    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/// 4-blade, used to represent pseudoscalar quantities.
814#[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}