visioncortex/
point.rs

1use flo_curves::{Coordinate, Coordinate2D};
2use num_traits::Float;
3use std::{convert::{From, Into}, fmt::Display, ops::*};
4
5/// Generic point in 2D space
6#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct Point2<T> {
8    pub x: T,
9    pub y: T,
10}
11
12pub trait PointType: Default + Copy {
13    fn from<P: PointType>(p: &P) -> Self;
14
15    fn to<T: PointType>(&self) -> T {
16        T::from(self)
17    }
18
19    fn to_point_f64(&self) -> PointF64;
20
21    fn to_point_i32(&self) -> PointI32;
22}
23
24pub trait ToSvgString {
25    fn to_svg_string(&self, precision: Option<u32>) -> String;
26}
27
28impl<T> ToSvgString for Point2<T> 
29where
30    T: Copy + NumberFormat
31{
32    fn to_svg_string(&self, precision: Option<u32>) -> String {
33        format!("{},{}", Self::number_format(self.x, precision), Self::number_format(self.y, precision))
34    }
35}
36
37pub trait NumberFormat: Display {
38    fn number_format(num: Self, precision: Option<u32>) -> String;
39}
40
41impl NumberFormat for i32 {
42    fn number_format(num: Self, _precision: Option<u32>) -> String {
43        format!("{}", num)
44    }
45}
46
47impl NumberFormat for f64 {
48    fn number_format(num: Self, precision: Option<u32>) -> String {
49        match precision {
50            None => format!("{}", num),
51            Some(0) => format!("{1:.0$}", 0, num),
52            Some(p) => {
53                let mut string: String = format!("{1:.0$}", p as usize, num);
54                string = string.trim_end_matches('0').trim_end_matches('.').to_owned();
55                string
56            },
57        }
58    }
59}
60
61impl<T> Point2<T>
62where
63    T: NumberFormat,
64{
65    #[inline]
66    pub(crate) fn number_format(num: T, precision: Option<u32>) -> String {
67        NumberFormat::number_format(num, precision)
68    }
69}
70
71impl<T> Point2<T> {
72    #[inline]
73    pub const fn new(x: T, y: T) -> Self {
74        Self { x, y }
75    }
76}
77
78impl<T> Point2<T>
79where
80    T: Add<Output = T> + Mul<Output = T>,
81{
82    #[inline]
83    pub fn dot(self, v: Self) -> T {
84        self.x * v.x + self.y * v.y
85    }
86}
87
88impl<T> Point2<T>
89where
90    T: Add<Output = T>
91{
92    #[inline]
93    pub fn translate(self, vector: Self) -> Self {
94        self + vector
95    }
96}
97
98impl<T> Point2<T>
99where
100    T: Add<Output = T> + Copy + Neg<Output = T> + Sub<Output = T>,
101{
102    #[inline]
103    /// Assumes a coordinate system with origin at the top-left. The behavior
104    /// is undefined otherwise.
105    pub fn rotate_90deg(&self, origin: Self, clockwise: bool) -> Self {
106        let o = origin;
107
108        if !clockwise {
109            Self {
110                x: (self.y - o.y) + o.x,
111                y: -(self.x - o.x) + o.y,
112            }
113        } else {
114            Self {
115                x: -(self.y - o.y) + o.x,
116                y: (self.x - o.x) + o.y,
117            }
118        }
119    }
120}
121
122impl<T> Point2<T>
123where
124    T: Float,
125{
126    #[inline]
127    pub fn rotate(&self, origin: Self, angle: T) -> Self {
128        let o = origin;
129        let a = angle;
130        Self {
131            x: a.cos() * (self.x - o.x) - a.sin() * (self.y - o.y) + o.x,
132            y: a.sin() * (self.x - o.x) + a.cos() * (self.y - o.y) + o.y,
133        }
134    }
135
136    #[inline]
137    /// The L2-norm
138    pub fn norm(self) -> T {
139        self.dot(self).sqrt()
140    }
141
142    #[inline]
143    /// The euclidean distance
144    pub fn distance_to(&self, other: Point2<T>) -> T {
145        (*self - other).norm()
146    }
147}
148
149impl<T> Point2<T>
150where
151    T: Default + Float,
152{
153    #[inline]
154    pub fn get_normalized(&self) -> Self {
155        let norm = self.norm();
156        if norm != T::zero() {
157            *self / norm
158        } else {
159            Self::default()
160        }
161    }
162
163}
164
165impl<T> Neg for Point2<T>
166where
167    T: Neg<Output = T>,
168{
169    type Output = Self;
170    #[inline]
171    fn neg(self) -> Self::Output {
172        Self {
173            x: self.x.neg(),
174            y: self.y.neg(),
175        }
176    }
177}
178
179impl<T> Add for Point2<T>
180where
181    T: Add<Output = T>,
182{
183    type Output = Self;
184    #[inline]
185    fn add(self, other: Self) -> Self {
186        Self {
187            x: self.x.add(other.x),
188            y: self.y.add(other.y),
189        }
190    }
191}
192
193impl<T> AddAssign for Point2<T>
194where
195    T: AddAssign,
196{   #[inline]
197    fn add_assign(&mut self, other: Self) {
198        self.x.add_assign(other.x);
199        self.y.add_assign(other.y);
200    }
201}
202
203impl<T> Sub for Point2<T>
204where
205    T: Sub<Output = T>,
206{
207    type Output = Self;
208    #[inline]
209    fn sub(self, other: Self) -> Self {
210        Self {
211            x: self.x.sub(other.x),
212            y: self.y.sub(other.y),
213        }
214    }
215}
216
217impl<T> SubAssign for Point2<T>
218where
219    T: SubAssign,
220{
221    #[inline]
222    fn sub_assign(&mut self, other: Self) {
223        self.x.sub_assign(other.x);
224        self.y.sub_assign(other.y);
225    }
226}
227
228impl<T, F> Mul<F> for Point2<T>
229where
230    T: Mul<F, Output = T>,
231    F: Float,
232{
233    type Output = Self;
234
235    fn mul(self, rhs: F) -> Self::Output {
236        Self {
237            x: self.x.mul(rhs),
238            y: self.y.mul(rhs),
239        }
240    }
241}
242
243impl<T, F> MulAssign<F> for Point2<T>
244where
245    T: MulAssign<F>,
246    F: Float,
247{
248    fn mul_assign(&mut self, rhs: F) {
249        self.x.mul_assign(rhs);
250        self.y.mul_assign(rhs);
251    }
252}
253
254impl<T, F> Div<F> for Point2<T>
255where
256    T: Div<F, Output = T>,
257    F: Float,
258{
259    type Output = Self;
260
261    #[inline]
262    fn div(self, rhs: F) -> Self::Output {
263        Self {
264            x: self.x.div(rhs),
265            y: self.y.div(rhs),
266        }
267    }
268}
269
270impl<T, F> DivAssign<F> for Point2<T>
271where
272    T: DivAssign<F>,
273    F: Float,
274{
275    #[inline]
276    fn div_assign(&mut self, rhs: F) {
277        self.x.div_assign(rhs);
278        self.y.div_assign(rhs);
279    }
280}
281
282impl<F> Coordinate2D for Point2<F>
283where
284    F: Copy + Into<f64>,
285{
286    fn x(&self) -> f64 {
287        self.x.into()
288    }
289
290    fn y(&self) -> f64 {
291        self.y.into()
292    }
293}
294
295impl<F> Coordinate for Point2<F>
296where
297    F: Add<Output = F> + Copy + Default + Float + From<f64> + Into<f64> + Mul<f64, Output = F> + PartialEq + Sub<Output = F>,
298{
299    #[inline]
300    fn from_components(components: &[f64]) -> Self {
301        Self::new(components[0].into(), components[1].into())
302    }
303
304    #[inline]
305    fn origin() -> Self {
306        Self::default()
307    }
308
309    #[inline]
310    fn len() -> usize {
311        2
312    }
313
314    #[inline]
315    fn get(&self, index: usize) -> f64 {
316        match index {
317            0 => self.x.into(),
318            1 => self.y.into(),
319            _ => panic!("Point2 only has two components")
320        }
321    }
322
323    fn from_biggest_components(p1: Self, p2: Self) -> Self {
324        Self::new(
325            f64::from_biggest_components(p1.x.into(), p2.x.into()).into(),
326            f64::from_biggest_components(p1.y.into(), p2.y.into()).into(),
327        )
328    }
329
330    fn from_smallest_components(p1: Self, p2: Self) -> Self {
331        Self::new(
332            f64::from_smallest_components(p1.x.into(), p2.x.into()).into(),
333            f64::from_smallest_components(p1.y.into(), p2.y.into()).into(),
334        )
335    }
336}
337
338/// 2D Point with `u8` component
339pub type PointU8 = Point2<u8>;
340/// 2D Point with `usize` component
341pub type PointUsize = Point2<usize>;
342/// 2D Point with `i32` component
343pub type PointI32 = Point2<i32>;
344/// 2D Point with `f32` component
345pub type PointF32 = Point2<f32>;
346/// 2D Point with `f64` component
347pub type PointF64 = Point2<f64>;
348
349impl PointI32 {
350    pub fn to_point_usize(&self) -> PointUsize {
351        PointUsize {x: self.x as usize, y: self.y as usize}
352    }
353
354    pub fn to_point_f64(&self) -> PointF64 {
355        PointF64 { x: self.x as f64, y: self.y as f64 }
356    }
357}
358
359impl PointF64 {
360    pub fn to_point_i32(&self) -> PointI32 {
361        PointI32 { x: self.x as i32, y: self.y as i32 }
362    }
363
364    pub fn to_point_f32(&self) -> PointF32 {
365        PointF32 { x: self.x as f32, y: self.y as f32 }
366    }
367}
368
369impl PointType for PointI32 {
370    fn from<P: PointType>(p: &P) -> Self {
371        p.to_point_i32()
372    }
373
374    #[inline]
375    fn to_point_f64(&self) -> PointF64 {
376        self.to_point_f64()
377    }
378
379    #[inline]
380    fn to_point_i32(&self) -> PointI32 {
381        *self
382    }
383}
384
385impl PointType for PointF64 {
386    fn from<P: PointType>(p: &P) -> Self {
387        p.to_point_f64()
388    }
389
390    #[inline]
391    fn to_point_f64(&self) -> PointF64 {
392        *self
393    }
394
395    #[inline]
396    fn to_point_i32(&self) -> PointI32 {
397        self.to_point_i32()
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404
405    #[test]
406    /// rotate counter clockwise by 90 degrees
407    fn pointf64_rotate() {
408        let p = PointF64 { x: 1.0, y: 0.0 };
409        let r = p.rotate(PointF64 { x: 0.0, y: 0.0 }, std::f64::consts::PI / 2.0);
410        // should be close to PointF64 { x: 0.0, y: 1.0 }
411        assert!(-0.000000001 < r.x && r.x < 0.000000001);
412        assert!(1.0 - 0.000000001 < r.y && r.y < 1.0 + 0.000000001);
413    }
414
415    #[test]
416    fn test_round_i32() {
417        let z = PointI32 { x: 0, y: 2 };
418        assert_eq!(z.to_svg_string(None), "0,2");
419        assert_eq!(z.to_svg_string(Some(5)), "0,2");
420
421        let r = PointI32 { x: 1, y: 2 };
422        assert_eq!(r.to_svg_string(None), "1,2");
423        assert_eq!(r.to_svg_string(Some(5)), "1,2");
424    }
425
426    #[test]
427    fn test_round_f64() {
428        let z = PointF64 { x: 0.0, y: 0.1 };
429        assert_eq!(z.to_svg_string(Some(0)), "0,0");
430        assert_eq!(z.to_svg_string(Some(1)), "0,0.1");
431        assert_eq!(z.to_svg_string(Some(2)), "0,0.1");
432        assert_eq!(z.to_svg_string(None), "0,0.1");
433
434        let p = PointF64 { x: 1.21786434, y: 2.98252586 };
435        assert_eq!(p.to_svg_string(Some(0)), "1,3");
436        assert_eq!(p.to_svg_string(Some(1)), "1.2,3");
437        assert_eq!(p.to_svg_string(Some(2)), "1.22,2.98");
438        assert_eq!(p.to_svg_string(Some(3)), "1.218,2.983");
439        assert_eq!(p.to_svg_string(Some(4)), "1.2179,2.9825");
440        assert_eq!(p.to_svg_string(Some(5)), "1.21786,2.98253");
441        assert_eq!(p.to_svg_string(Some(6)), "1.217864,2.982526");
442        assert_eq!(p.to_svg_string(Some(7)), "1.2178643,2.9825259");
443        assert_eq!(p.to_svg_string(None), "1.21786434,2.98252586");
444    }
445
446    #[test]
447    /// rotate clockwise by 90 degrees
448    fn pointi32_rotate() {
449        let p = PointI32 { x: 1, y: 0 };
450        let r = p.rotate_90deg(PointI32::default(), true);
451        assert_eq!(PointI32::new(0, 1), r);
452    }
453}