geo_aid_script/
geometry.rs

1//! Utility crate providing functions for geometry computations.
2
3use std::{
4    fmt::Display,
5    iter::{Product, Sum},
6    ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign},
7};
8
9use geo_aid_figure::Position;
10use serde::Serialize;
11
12/// Represents a complex number
13#[derive(Debug, Clone, Copy, Serialize)]
14pub struct Complex {
15    /// The real part
16    pub real: f64,
17    /// The imaginary part.
18    pub imaginary: f64,
19}
20
21impl Complex {
22    /// Create a new complex from its real and imaginary parts.
23    #[must_use]
24    #[inline]
25    pub const fn new(real: f64, imaginary: f64) -> Self {
26        Self { real, imaginary }
27    }
28
29    /// Convert a real to a complex.
30    #[must_use]
31    pub const fn real(real: f64) -> Self {
32        Self::new(real, 0.0)
33    }
34
35    /// Create a new complex from its polar representation.
36    #[must_use]
37    pub fn polar(theta: f64, radius: f64) -> Self {
38        Self::new(theta.cos(), theta.sin()) * radius
39    }
40
41    /// Complex zero.
42    #[must_use]
43    #[inline]
44    pub const fn zero() -> Self {
45        Self::new(0.0, 0.0)
46    }
47
48    /// Complex one.
49    #[must_use]
50    #[inline]
51    pub const fn one() -> Self {
52        Self::new(1.0, 0.0)
53    }
54
55    /// The imaginary unit.
56    #[must_use]
57    pub const fn i() -> Self {
58        Self::new(0.0, 1.0)
59    }
60
61    /// Optimized multiplication by the complex unit (i).
62    #[must_use]
63    pub fn mul_i(self) -> Complex {
64        Complex::new(-self.imaginary, self.real)
65    }
66
67    /// The magnitude of the complex, also called its modulus.
68    #[must_use]
69    pub fn magnitude(self) -> f64 {
70        f64::sqrt(self.real.powi(2) + self.imaginary.powi(2))
71    }
72
73    /// The complex's conjugate (a - bi)
74    #[must_use]
75    pub fn conjugate(self) -> Complex {
76        Complex::new(self.real, -self.imaginary)
77    }
78
79    /// Multiply the complex's parts by other complex's parts (ab + cdi)
80    #[must_use]
81    pub fn partial_mul(self, other: Complex) -> Complex {
82        Complex::new(self.real * other.real, self.imaginary * other.imaginary)
83    }
84
85    /// Divide the complex's parts by other complex's parts (a/b + (c/d)i)
86    #[must_use]
87    pub fn partial_div(self, other: Complex) -> Complex {
88        Complex::new(self.real / other.real, self.imaginary / other.imaginary)
89    }
90
91    /// Compute the complex's argument
92    #[must_use]
93    pub fn arg(self) -> f64 {
94        f64::atan2(self.imaginary, self.real)
95    }
96
97    /// Normalize the complex by dividing it by its own magnitude.
98    #[must_use]
99    pub fn normalize(self) -> Complex {
100        self / self.magnitude()
101    }
102
103    /// Number-theoretical norm. Simply a^2 + b^2 with self = a + bi
104    #[must_use]
105    pub fn len_squared(self) -> f64 {
106        self.real * self.real + self.imaginary * self.imaginary
107    }
108
109    /// A square root. Chooses the one with non-negative imaginary part.
110    #[must_use]
111    pub fn sqrt(self) -> Complex {
112        // The formula used here doesn't work for negative reals. We can use a trick here to bypass that restriction.
113        // If the real part is negative, we simply negate it to get a positive part and then multiply the result by `i`.
114        if self.real > 0.0 {
115            // Use the generic formula (https://math.stackexchange.com/questions/44406/how-do-i-get-the-square-root-of-a-complex-number)
116            let r = self.magnitude();
117
118            r.sqrt() * (self + r).normalize()
119        } else {
120            (-self).sqrt().mul_i()
121        }
122    }
123
124    /// Same as sqrt, but returns a normalized result.
125    #[must_use]
126    pub fn sqrt_norm(self) -> Complex {
127        // The formula used here doesn't work for negative reals. We can use a trick here to bypass that restriction.
128        // If the real part is negative, we simply negate it to get a positive part and then multiply the result by `i`.
129        if self.real > 0.0 {
130            // Use the generic formula (https://math.stackexchange.com/questions/44406/how-do-i-get-the-square-root-of-a-complex-number)
131            let r = self.magnitude();
132
133            // We simply don't multiply by the square root of r.
134            (self + r).normalize()
135        } else {
136            (-self).sqrt_norm().mul_i() // Normalization isn't lost here.
137        }
138    }
139
140    /// Inverse of the number.
141    #[must_use]
142    pub fn inverse(self) -> Self {
143        self.conjugate() / self.len_squared()
144    }
145}
146
147impl From<Complex> for Position {
148    fn from(value: Complex) -> Self {
149        Self {
150            x: value.real,
151            y: value.imaginary,
152        }
153    }
154}
155
156impl From<Complex> for geo_aid_figure::Complex {
157    fn from(value: Complex) -> Self {
158        Self {
159            real: value.real,
160            imaginary: value.imaginary,
161        }
162    }
163}
164
165impl From<Position> for Complex {
166    fn from(value: Position) -> Self {
167        Self::new(value.x, value.y)
168    }
169}
170
171impl Mul for Complex {
172    type Output = Complex;
173
174    fn mul(self, rhs: Complex) -> Self::Output {
175        Complex::new(
176            self.real * rhs.real - self.imaginary * rhs.imaginary,
177            self.real * rhs.imaginary + rhs.real * self.imaginary,
178        )
179    }
180}
181
182impl Mul<Complex> for f64 {
183    type Output = Complex;
184
185    fn mul(self, rhs: Complex) -> Self::Output {
186        Complex::new(self * rhs.real, self * rhs.imaginary)
187    }
188}
189
190impl Mul<f64> for Complex {
191    type Output = Complex;
192
193    fn mul(self, rhs: f64) -> Self::Output {
194        Complex::new(self.real * rhs, self.imaginary * rhs)
195    }
196}
197
198impl MulAssign<f64> for Complex {
199    fn mul_assign(&mut self, rhs: f64) {
200        self.real *= rhs;
201        self.imaginary *= rhs;
202    }
203}
204
205impl Add<f64> for Complex {
206    type Output = Complex;
207
208    fn add(self, rhs: f64) -> Self::Output {
209        Complex::new(self.real + rhs, self.imaginary)
210    }
211}
212
213impl Add for Complex {
214    type Output = Complex;
215
216    fn add(self, rhs: Self) -> Self::Output {
217        Complex::new(self.real + rhs.real, self.imaginary + rhs.imaginary)
218    }
219}
220
221impl Div<f64> for Complex {
222    type Output = Complex;
223
224    fn div(self, rhs: f64) -> Self::Output {
225        Complex::new(self.real / rhs, self.imaginary / rhs)
226    }
227}
228
229impl Div for Complex {
230    type Output = Complex;
231
232    fn div(self, rhs: Complex) -> Self::Output {
233        (self * rhs.conjugate()) / (rhs.real * rhs.real + rhs.imaginary * rhs.imaginary)
234    }
235}
236
237impl Sub<f64> for Complex {
238    type Output = Complex;
239
240    fn sub(self, rhs: f64) -> Self::Output {
241        Complex::new(self.real - rhs, self.imaginary)
242    }
243}
244
245impl Sub for Complex {
246    type Output = Complex;
247
248    fn sub(self, rhs: Self) -> Self::Output {
249        Complex::new(self.real - rhs.real, self.imaginary - rhs.imaginary)
250    }
251}
252
253impl SubAssign for Complex {
254    fn sub_assign(&mut self, rhs: Self) {
255        *self = *self - rhs;
256    }
257}
258
259impl AddAssign for Complex {
260    fn add_assign(&mut self, rhs: Self) {
261        *self = *self + rhs;
262    }
263}
264
265impl MulAssign for Complex {
266    fn mul_assign(&mut self, rhs: Self) {
267        *self = *self * rhs;
268    }
269}
270
271impl Display for Complex {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        if let Some(precision) = f.precision() {
274            write!(
275                f,
276                "{2:.*} + {3:.*}i",
277                precision, precision, self.real, self.imaginary
278            )
279        } else {
280            write!(f, "{} + {}i", self.real, self.imaginary)
281        }
282    }
283}
284
285impl Neg for Complex {
286    type Output = Complex;
287
288    fn neg(self) -> Self::Output {
289        Complex::new(-self.real, -self.imaginary)
290    }
291}
292
293impl Default for Complex {
294    fn default() -> Self {
295        Self {
296            real: 0.0,
297            imaginary: 0.0,
298        }
299    }
300}
301
302impl Sum for Complex {
303    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
304        let mut v = Complex::zero();
305
306        for x in iter {
307            v += x;
308        }
309
310        v
311    }
312}
313
314impl Product for Complex {
315    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
316        let mut v = Complex::zero();
317
318        for x in iter {
319            v *= x;
320        }
321
322        v
323    }
324}
325
326/// Represents a line in a 2D Euclidean space.
327#[derive(Debug, Clone, Copy, Serialize)]
328pub struct Line {
329    /// Line's origin as a complex number.
330    pub origin: Complex,
331    /// A normalized direction vector.
332    pub direction: Complex,
333}
334
335impl Line {
336    /// Creates a new line from its origin and a direction vector
337    #[must_use]
338    pub fn new(origin: Complex, direction: Complex) -> Self {
339        Self {
340            origin,
341            direction: direction.normalize(),
342        }
343    }
344}
345
346impl From<Line> for geo_aid_figure::Line {
347    fn from(value: Line) -> Self {
348        Self {
349            origin: value.origin.into(),
350            direction: value.direction.into(),
351        }
352    }
353}
354
355/// Represents a circle in a 2D euclidean space.
356#[derive(Debug, Clone, Copy, Serialize)]
357pub struct Circle {
358    /// Circle's center.
359    pub center: Complex,
360    /// Its radius
361    pub radius: f64,
362}
363
364impl From<Circle> for geo_aid_figure::Circle {
365    fn from(value: Circle) -> Self {
366        Self {
367            center: value.center.into(),
368            radius: value.radius,
369        }
370    }
371}
372
373/// Enumerated value type for serialization.
374#[derive(Debug, Clone, Copy, Serialize)]
375pub enum ValueEnum {
376    Complex(Complex),
377    Line(Line),
378    Circle(Circle),
379}
380
381impl ValueEnum {
382    #[must_use]
383    pub fn as_complex(self) -> Option<Complex> {
384        match self {
385            Self::Complex(c) => Some(c),
386            _ => None,
387        }
388    }
389
390    #[must_use]
391    pub fn as_line(self) -> Option<Line> {
392        match self {
393            Self::Line(l) => Some(l),
394            _ => None,
395        }
396    }
397
398    #[must_use]
399    pub fn as_circle(self) -> Option<Circle> {
400        match self {
401            Self::Circle(l) => Some(l),
402            _ => None,
403        }
404    }
405}
406
407impl From<ValueEnum> for geo_aid_figure::Value {
408    fn from(value: ValueEnum) -> Self {
409        match value {
410            ValueEnum::Complex(c) => Self::Complex(c.into()),
411            ValueEnum::Line(ln) => Self::Line(ln.into()),
412            ValueEnum::Circle(c) => Self::Circle(c.into()),
413        }
414    }
415}
416
417/// Get the line going through `p1` and `p2`
418#[must_use]
419pub fn get_line(p1: Complex, p2: Complex) -> Line {
420    Line {
421        origin: p1,
422        direction: (p2 - p1).normalize(),
423    }
424}
425
426/// Gets the intersection point of two lines.
427#[must_use]
428pub fn get_intersection(k_ln: Line, l_ln: Line) -> Complex {
429    let Line {
430        origin: a,
431        direction: b,
432    } = k_ln;
433    let Line {
434        origin: c,
435        direction: d,
436    } = l_ln;
437
438    a - b * ((a - c) / d).imaginary / (b / d).imaginary
439}
440
441/// Gets the angle between two arms and the origin
442#[must_use]
443pub fn get_angle(arm1: Complex, origin: Complex, arm2: Complex) -> f64 {
444    // Get the vectors to calculate the angle between them.
445    let arm1_vec = arm1 - origin;
446    let arm2_vec = arm2 - origin;
447
448    // Get the dot product
449    let dot_product = arm1_vec.real * arm2_vec.real + arm1_vec.imaginary * arm2_vec.imaginary;
450
451    // Get the argument
452    f64::acos(dot_product / (arm1_vec.magnitude() * arm2_vec.magnitude()))
453}
454
455/// Gets the directed angle between two arms and the origin
456#[must_use]
457pub fn get_angle_directed(arm1: Complex, origin: Complex, arm2: Complex) -> f64 {
458    // Get the vectors to calculate the angle between them.
459    let arm1_vec = arm1 - origin;
460    let arm2_vec = arm2 - origin;
461
462    // decrease p2's angle by p1's angle:
463    let p2_rotated = arm2_vec / arm1_vec;
464
465    // Get the argument
466    p2_rotated.arg()
467}
468
469// Rotates p around origin by angle.
470#[must_use]
471pub fn rotate_around(p: Complex, origin: Complex, angle: f64) -> Complex {
472    (p - origin) * Complex::new(angle.cos(), angle.sin()) + origin
473}
474
475// Computes Point-Line distance.
476#[must_use]
477pub fn distance_pt_ln(point: Complex, line: Line) -> f64 {
478    // Make the point coordinates relative to the origin and rotate.
479    let point_rot = (point - line.origin) / line.direction;
480
481    // Now we can just get the imaginary part. We have to take the absolute value here.
482    point_rot.imaginary.abs()
483}
484
485// Computes Point-Point distance.
486#[must_use]
487pub fn distance_pt_pt(p1: Complex, p2: Complex) -> f64 {
488    ((p1.real - p2.real) * (p1.real - p2.real)
489        + (p1.imaginary - p2.imaginary) * (p1.imaginary - p2.imaginary))
490        .sqrt()
491}