geo_buf/util/ray/
mod.rs

1use crate::util::*;
2use std::fmt;
3
4/// This structure conceptually represents a half-line (which also known as "Ray").
5///
6/// A ray has a "start vertex" **r<sub>0</sub>**, that is, **r<sub>0</sub>** is a part of the ray itself,
7/// but we cannot make a disk of radius ε which contained in the ray around **r<sub>0</sub>** for arbitrary ε > 0.
8///
9/// If we consider the vectors from **r<sub>0</sub>** to each point on the ray, then they are all
10/// pairwise parallel. Therefore, there exists a "direction vector" **v** and we can
11/// represent each point on ray by the parameterized equation:
12///
13/// <span style="display: inline-block; width: 100%; text-align:center;"> **r<sub>0</sub>** + *t***v** (*t* ≥ 0), </span>
14///
15/// where *t* is the parameter which is greater than or equal to zero.
16///
17/// We can also think of a ray as the locus of a moving point at a constant velocity from the starting point **r<sub>0</sub>** as time passes.
18/// In this case, the location of the point after time *t* (*t* ≥ 0) is equal to **r<sub>0</sub>** + *t***v**.
19#[derive(Clone, Default, Debug, Copy)]
20pub struct Ray {
21    pub(crate) origin: Coordinate,
22    pub(crate) angle: Coordinate,
23}
24
25impl fmt::Display for Ray {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        write!(
28            f,
29            "Origin : (x, y) = ({}, {}) / Angle : (dx, dy) = ({}, {})",
30            self.origin.0, self.origin.1, self.angle.0, self.angle.1
31        )
32    }
33}
34
35impl Ray {
36    /// Creates and returns a [Ray] w.r.t. the given arguments.
37    ///  
38    /// # Arguments
39    ///  
40    /// + `src`: The starting point of the ray.
41    /// + `dst`: The point for which `src` is heading.
42    ///
43    /// # Return
44    ///
45    /// A `Ray` that starts from `src` towards `dst` with arrival time 1.
46    ///
47    /// # Example
48    ///
49    /// ```
50    /// use geo_buf::{Coordinate, Ray};
51    ///
52    /// let c1 = (1., 2.).into();
53    /// let c2 = (2., 3.).into();
54    /// let r1 = Ray::new(c1, c2);
55    ///
56    /// ```
57    ///
58    pub fn new(src: Coordinate, dst: Coordinate) -> Self {
59        Self {
60            origin: src,
61            angle: dst - src,
62        }
63    }
64
65    /// Returns the "starting point" of the given ray.
66    ///
67    /// # Example
68    ///
69    /// ```
70    /// use geo_buf::{Coordinate, Ray};
71    ///
72    /// let c1 = (1., 2.).into();
73    /// let c2 = (2., 3.).into();
74    /// let r1 = Ray::new(c1, c2);
75    ///
76    /// assert!(c1.eq(&r1.point()));
77    ///
78    /// ```
79    pub fn point(&self) -> Coordinate {
80        self.point_by_ratio(0.)
81    }
82
83    /// Returns the value of parameterized equation **r<sub>0</sub>** + *t***v** by the given ratio *t*.
84    ///
85    /// # Example
86    ///
87    /// ```
88    /// use geo_buf::{Coordinate, Ray};
89    ///
90    /// let c1 = (1., 2.).into();
91    /// let c2 = (2., 3.).into();
92    /// let r1 = Ray::new(c1, c2);
93    ///
94    /// assert!(r1.point_by_ratio(2.).eq(&(3., 4.).into()));
95    /// ```
96    pub fn point_by_ratio(&self, ratio: f64) -> Coordinate {
97        self.origin + self.angle * ratio
98    }
99
100    pub(crate) fn bisector(&self, rhs: &Ray, origin: Coordinate, orient: bool) -> Self {
101        let mut ray = self.angle * rhs.angle.norm() + rhs.angle * self.angle.norm();
102        if feq(ray.0, 0.) && feq(ray.1, 0.) {
103            ray = (-self.angle.1, self.angle.0).into();
104            if orient {
105                ray = ray * -1.;
106            }
107        } else {
108            if orient && self.angle.outer_product(&ray) > 0.0 {
109                ray = ray * -1.0;
110            }
111            if !orient && self.angle.outer_product(&ray) < 0.0 {
112                ray = ray * -1.0;
113            }
114        }
115        // else {
116        //     if orient == true && tmp_angle.outer_product(&ray) > 0.0 {ray = ray*-1.0;}
117        //     if orient == false && tmp_angle.outer_product(&ray) < 0.0 {ray = ray*-1.0;}
118        // }
119        Self { origin, angle: ray }
120    }
121
122    /// Checks whether `self` contains the given Cartesian coordinate.
123    ///
124    /// Note that this function considers `self` as a open-ended line.
125    /// That is, if the given point lies on the extended line of `self`, this function returns `true`.
126    ///
127    /// # Return
128    ///
129    /// + `true` if the given point lies on `self`,
130    /// + `false` otherwise.
131    ///
132    /// # Example
133    ///
134    /// ```
135    /// use geo_buf::{Coordinate, Ray};
136    ///
137    /// let c1 = (1., 2.).into();
138    /// let c2 = (2., 3.).into();
139    /// let r1 = Ray::new(c1, c2);
140    ///
141    /// assert!(r1.is_contain(&(3., 4.).into()));
142    /// ```
143    pub fn is_contain(&self, rhs: &Coordinate) -> bool {
144        if self.is_degenerated() {
145            return feq(self.origin.0, rhs.0) && feq(self.origin.1, rhs.1);
146        }
147        feq((*rhs - self.origin).outer_product(&self.angle), 0.)
148    }
149
150    /// Checks whether the given two rays are intersecting with each other.
151    /// More precisely, it checks whether they have one or more common points.
152    ///
153    /// # Return
154    ///
155    /// + `true` if the given rays have one or more common points,
156    /// + `false` otherwise.
157    ///
158    /// # Example
159    ///
160    /// ```
161    /// use geo_buf::{Coordinate, Ray};
162    ///
163    /// let c1 = (1., 2.).into();
164    /// let c2 = (2., 3.).into();
165    /// let r1 = Ray::new(c1, c2);
166    ///
167    /// assert!(r1.is_contain(&(3., 4.).into()));
168    /// ```
169    pub fn is_intersect(&self, rhs: &Ray) -> bool {
170        let op = self.angle.outer_product(&rhs.angle);
171        if feq(op, 0.0) {
172            if self.is_contain(&rhs.origin) {
173                return true;
174            }
175            if rhs.is_contain(&self.origin) {
176                return true;
177            }
178            return false;
179        }
180        let i = (rhs.origin - self.origin).outer_product(&rhs.angle)
181            / self.angle.outer_product(&rhs.angle);
182        let j = (rhs.origin - self.origin).outer_product(&self.angle)
183            / self.angle.outer_product(&rhs.angle);
184        if fgeq(i, 0.) && fgeq(j, 0.) {
185            return true;
186        }
187        false
188    }
189
190    /// Returns a common point of the given rays. If they have more than 2 common points, then returns a
191    /// middle point of the starting points of the given rays.
192    ///
193    /// Note that this function considers the rays as a open-ended line.
194    /// That is, if the common point lies on the extended line(s) of them, this function returns the point.
195    ///
196    /// # Example
197    ///
198    /// ```
199    /// use geo_buf::{Coordinate, Ray};
200    ///
201    /// let c1 = (0., 0.).into();
202    /// let c2 = (1., 1.).into();
203    /// let c3 = (4., 0.).into();
204    /// let c4 = (0., 4.).into();
205    /// let r1 = Ray::new(c1, c2);
206    /// let r2 = Ray::new(c3, c4);
207    ///
208    /// assert!(r1.intersect(&r2).eq(&(2., 2.).into()));
209    ///
210    /// ```
211    pub fn intersect(&self, rhs: &Ray) -> Coordinate {
212        let op = self.angle.outer_product(&rhs.angle);
213        if feq(op, 0.) {
214            if self.is_contain(&rhs.origin) {
215                if fgt((rhs.origin - self.origin) / self.angle, 0.) {
216                    return rhs.origin;
217                } else {
218                    return self.origin;
219                }
220            }
221            return (self.origin + rhs.origin) / 2.0;
222        }
223        let i = (rhs.origin - self.origin).outer_product(&rhs.angle)
224            / self.angle.outer_product(&rhs.angle);
225        self.origin + self.angle * i
226    }
227
228    /// Checks whether the given two rays are parallel. If they have more than 2 common points,
229    /// they are not considered as parallel.
230    ///
231    /// # Return
232    ///
233    /// + `true` if the given rays are parallel,
234    /// + `false` otherwise.
235    ///
236    /// # Example
237    ///
238    /// ```
239    /// use geo_buf::{Coordinate, Ray};
240    ///
241    /// let c1 = (0., 0.).into();
242    /// let c2 = (1., 1.).into();
243    /// let c3 = (0., 1.).into();
244    /// let c4 = (1., 2.).into();
245    /// let r1 = Ray::new(c1, c2);
246    /// let r2 = Ray::new(c3, c4);
247    ///
248    /// assert!(r1.is_parallel(&r2));
249    /// ```
250    pub fn is_parallel(&self, rhs: &Ray) -> bool {
251        let op = self.angle.outer_product(&rhs.angle);
252        if feq(op, 0.0) && !self.is_contain(&rhs.origin) {
253            return true;
254        }
255        false
256    }
257
258    pub(crate) fn is_degenerated(&self) -> bool {
259        feq(self.angle.0, 0.) && feq(self.angle.1, 0.)
260    }
261
262    /// Normalizes the given `Ray`. The magnitude of the 'velocity' becomes 1. Does nothing if it is 0.
263    ///
264    /// # Example
265    ///
266    /// ```
267    /// use geo_buf::{Coordinate, Ray};
268    ///
269    /// let c1 = (0., 0.).into();
270    /// let c2 = (3., 4.).into();
271    /// let mut r1 = Ray::new(c1, c2);
272    /// r1.normalize();
273    ///
274    /// assert!(r1.point_by_ratio(1.).eq(&(0.6, 0.8).into()));
275    /// ```
276    pub fn normalize(&mut self) {
277        if self.is_degenerated() {
278            return;
279        }
280        self.angle = self.angle / self.angle.norm();
281    }
282
283    pub(crate) fn orientation(&self, rhs: &Coordinate) -> i32 {
284        let res = self.angle.outer_product(&(*rhs - self.origin));
285        if feq(res, 0.) {
286            return 0;
287        }
288        if fgt(res, 0.) {
289            return 1;
290        }
291        -1
292    }
293
294    /// Returns the reversed ray of the given ray. The returned ray has the same starting point
295    /// and the opposite direction to the given ray.
296    ///
297    /// # Example
298    ///
299    /// ```
300    /// use geo_buf::{Coordinate, Ray};
301    ///
302    /// let c1 = (0., 0.).into();
303    /// let c2 = (3., 4.).into();
304    /// let r1 = Ray::new(c1, c2);
305    /// let r2 = r1.reverse();
306    ///
307    /// assert!(r2.point_by_ratio(1.).eq(&(-3., -4.).into()));
308    /// ```
309    pub fn reverse(&self) -> Self {
310        Self {
311            origin: self.origin,
312            angle: self.angle * -1.,
313        }
314    }
315
316    /// Returns a ray which performs a rotation of the given vector through the given angle (in radian).
317    /// The direction of rotation is counter-clockwise direction and the center of rotation is the starting point of the given vector.
318    ///
319    /// # Example
320    ///
321    /// ```
322    /// use geo_buf::{Coordinate, Ray};
323    ///
324    /// let c1 = (0., 0.).into();
325    /// let c2 = (3., 4.).into();
326    /// let r1 = Ray::new(c1, c2);
327    /// let r2 = r1.rotate_by(std::f64::consts::PI/2.);
328    ///
329    /// assert!(r2.point_by_ratio(1.).eq(&(-4., 3.).into()));
330    /// ```
331    pub fn rotate_by(&self, angle: f64) -> Self {
332        let nx = self.angle.0 * f64::cos(angle) - self.angle.1 * f64::sin(angle);
333        let ny = self.angle.0 * f64::sin(angle) + self.angle.1 * f64::cos(angle);
334        Self {
335            origin: self.origin,
336            angle: (nx, ny).into(),
337        }
338    }
339}