geo_buffer/util/ray/
mod.rs

1use std::fmt;
2use crate::util::*;
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!(f, "Origin : (x, y) = ({}, {}) / Angle : (dx, dy) = ({}, {})", self.origin.0, self.origin.1, self.angle.0, self.angle.1)
28    }
29}
30
31impl Ray{
32    /// Creates and returns a [Ray] w.r.t. the given arguments.
33    ///  
34    /// # Arguments
35    ///  
36    /// + `src`: The starting point of the ray.
37    /// + `dst`: The point for which `src` is heading.
38    /// 
39    /// # Return
40    /// 
41    /// A `Ray` that starts from `src` towards `dst` with arrival time 1.
42    /// 
43    /// # Example
44    /// 
45    /// ```
46    /// use geo_buffer::{Coordinate, Ray};
47    /// 
48    /// let c1 = (1., 2.).into();
49    /// let c2 = (2., 3.).into();
50    /// let r1 = Ray::new(c1, c2);
51    /// 
52    /// ```
53    /// 
54    pub fn new(src: Coordinate, dst: Coordinate) -> Self{
55        Self {
56            origin: src,
57            angle: dst-src,
58        }
59    }
60
61    /// Returns the "starting point" of the given ray.
62    /// 
63    /// # Example
64    /// 
65    /// ```
66    /// use geo_buffer::{Coordinate, Ray};
67    /// 
68    /// let c1 = (1., 2.).into();
69    /// let c2 = (2., 3.).into();
70    /// let r1 = Ray::new(c1, c2);
71    /// 
72    /// assert!(c1.eq(&r1.point()));
73    /// 
74    /// ```
75    pub fn point(&self) -> Coordinate{
76        self.point_by_ratio(0.)
77    }
78
79    /// Returns the value of parameterized equation **r<sub>0</sub>** + *t***v** by the given ratio *t*.
80    /// 
81    /// # Example
82    /// 
83    /// ```
84    /// use geo_buffer::{Coordinate, Ray};
85    /// 
86    /// let c1 = (1., 2.).into();
87    /// let c2 = (2., 3.).into();
88    /// let r1 = Ray::new(c1, c2);
89    /// 
90    /// assert!(r1.point_by_ratio(2.).eq(&(3., 4.).into()));
91    /// ```
92    pub fn point_by_ratio(&self, ratio: f64) -> Coordinate{
93        self.origin + self.angle*ratio
94    }
95
96    pub(crate) fn bisector(&self, rhs: &Ray, origin: Coordinate, orient: bool) -> Self{
97        let mut ray = self.angle*rhs.angle.norm() + rhs.angle*self.angle.norm();
98        if feq(ray.0, 0.) && feq(ray.1, 0.) {
99            ray = (-self.angle.1, self.angle.0).into();
100            if orient {ray = ray * -1.;}
101        }
102        else  {
103            if orient == true && self.angle.outer_product(&ray) > 0.0 {ray = ray*-1.0;}
104            if orient == false && self.angle.outer_product(&ray) < 0.0 {ray = ray*-1.0;}
105        }
106        // else {
107        //     if orient == true && tmp_angle.outer_product(&ray) > 0.0 {ray = ray*-1.0;}
108        //     if orient == false && tmp_angle.outer_product(&ray) < 0.0 {ray = ray*-1.0;}
109        // }
110        Self { origin: origin, angle: ray }
111    }
112
113    /// Checks whether `self` contains the given Cartesian coordinate.
114    /// 
115    /// Note that this function considers `self` as a open-ended line.
116    /// That is, if the given point lies on the extended line of `self`, this function returns `true`.
117    /// 
118    /// # Return
119    /// 
120    /// + `true` if the given point lies on `self`,
121    /// + `false` otherwise.
122    /// 
123    /// # Example
124    /// 
125    /// ```
126    /// use geo_buffer::{Coordinate, Ray};
127    /// 
128    /// let c1 = (1., 2.).into();
129    /// let c2 = (2., 3.).into();
130    /// let r1 = Ray::new(c1, c2);
131    /// 
132    /// assert!(r1.is_contain(&(3., 4.).into()));
133    /// ```
134    pub fn is_contain(&self, rhs: &Coordinate) -> bool {
135        if self.is_degenerated() {return feq(self.origin.0, rhs.0) && feq(self.origin.1, rhs.1);}
136        feq((*rhs - self.origin).outer_product(&self.angle), 0.)
137    }
138
139    /// Checks whether the given two rays are intersecting with each other.
140    /// More precisely, it checks whether they have one or more common points.
141    /// 
142    /// # Return
143    /// 
144    /// + `true` if the given rays have one or more common points,
145    /// + `false` otherwise.
146    /// 
147    /// # Example
148    /// 
149    /// ```
150    /// use geo_buffer::{Coordinate, Ray};
151    /// 
152    /// let c1 = (1., 2.).into();
153    /// let c2 = (2., 3.).into();
154    /// let r1 = Ray::new(c1, c2);
155    /// 
156    /// assert!(r1.is_contain(&(3., 4.).into()));
157    /// ```
158    pub fn is_intersect(&self, rhs: &Ray) -> bool {
159        let op = self.angle.outer_product(&rhs.angle);
160        if feq(op, 0.0){
161            if self.is_contain(&rhs.origin) {return true;}
162            if rhs.is_contain(&self.origin) {return true;}
163            return false;
164        }
165        let i = (rhs.origin - self.origin).outer_product(&rhs.angle) / self.angle.outer_product(&rhs.angle);
166        let j = (rhs.origin - self.origin).outer_product(&self.angle) / self.angle.outer_product(&rhs.angle);
167        if fgeq(i, 0.) && fgeq(j, 0.) {return true;}
168        false
169    }
170
171    /// Returns a common point of the given rays. If they have more than 2 common points, then returns a
172    /// middle point of the starting points of the given rays.
173    /// 
174    /// Note that this function considers the rays as a open-ended line.
175    /// That is, if the common point lies on the extended line(s) of them, this function returns the point.
176    /// 
177    /// # Example
178    /// 
179    /// ```
180    /// use geo_buffer::{Coordinate, Ray};
181    /// 
182    /// let c1 = (0., 0.).into();
183    /// let c2 = (1., 1.).into();
184    /// let c3 = (4., 0.).into();
185    /// let c4 = (0., 4.).into();
186    /// let r1 = Ray::new(c1, c2);
187    /// let r2 = Ray::new(c3, c4);
188    /// 
189    /// assert!(r1.intersect(&r2).eq(&(2., 2.).into()));
190    /// 
191    /// ```
192    pub fn intersect(&self, rhs: &Ray) -> Coordinate{
193        let op = self.angle.outer_product(&rhs.angle);
194        if feq(op, 0.) {
195            if self.is_contain(&rhs.origin) {
196                if fgt((rhs.origin - self.origin)/self.angle, 0.) {return rhs.origin;}
197                else {return self.origin;}
198            }
199            return (self.origin + rhs.origin)/2.0;
200        }
201        let i = (rhs.origin - self.origin).outer_product(&rhs.angle) / self.angle.outer_product(&rhs.angle);
202        self.origin + self.angle*i
203    }
204
205    /// Checks whether the given two rays are parallel. If they have more than 2 common points,
206    /// they are not considered as parallel.
207    /// 
208    /// # Return
209    /// 
210    /// + `true` if the given rays are parallel,
211    /// + `false` otherwise.
212    /// 
213    /// # Example
214    /// 
215    /// ```
216    /// use geo_buffer::{Coordinate, Ray};
217    /// 
218    /// let c1 = (0., 0.).into();
219    /// let c2 = (1., 1.).into();
220    /// let c3 = (0., 1.).into();
221    /// let c4 = (1., 2.).into();
222    /// let r1 = Ray::new(c1, c2);
223    /// let r2 = Ray::new(c3, c4);
224    /// 
225    /// assert!(r1.is_parallel(&r2));
226    /// ```
227    pub fn is_parallel(&self, rhs: &Ray) -> bool {
228        let op = self.angle.outer_product(&rhs.angle);
229        if feq(op, 0.0) && !self.is_contain(&rhs.origin) {return true;}
230        return false;
231    }
232
233    pub(crate) fn is_degenerated(&self) -> bool {
234        if feq(self.angle.0, 0.) && feq(self.angle.1, 0.) {true} else {false}
235    }
236
237    /// Normalizes the given `Ray`. The magnitude of the 'velocity' becomes 1. Does nothing if it is 0.
238    /// 
239    /// # Example
240    /// 
241    /// ```
242    /// use geo_buffer::{Coordinate, Ray};
243    /// 
244    /// let c1 = (0., 0.).into();
245    /// let c2 = (3., 4.).into();
246    /// let mut r1 = Ray::new(c1, c2);
247    /// r1.normalize();
248    /// 
249    /// assert!(r1.point_by_ratio(1.).eq(&(0.6, 0.8).into()));
250    /// ```
251    pub fn normalize(&mut self) {
252        if self.is_degenerated() {return;}
253        self.angle = self.angle/self.angle.norm();
254    }
255
256    pub(crate) fn orientation(&self, rhs: &Coordinate) -> i32 {
257        let res = self.angle.outer_product(&(*rhs - self.origin));
258        if feq(res, 0.) {return 0;}
259        if fgt(res, 0.) {return 1;}
260        return -1;
261    }
262
263    /// Returns the reversed ray of the given ray. The returned ray has the same starting point
264    /// and the opposite direction to the given ray.
265    /// 
266    /// # Example
267    /// 
268    /// ```
269    /// use geo_buffer::{Coordinate, Ray};
270    /// 
271    /// let c1 = (0., 0.).into();
272    /// let c2 = (3., 4.).into();
273    /// let r1 = Ray::new(c1, c2);
274    /// let r2 = r1.reverse();
275    /// 
276    /// assert!(r2.point_by_ratio(1.).eq(&(-3., -4.).into()));
277    /// ```
278    pub fn reverse(&self) -> Self{
279        Self{
280            origin: self.origin,
281            angle: self.angle*-1.,
282        }
283    }
284
285    /// Returns a ray which performs a rotation of the given vector through the given angle (in radian).
286    /// The direction of rotation is counter-clockwise direction and the center of rotation is the starting point of the given vector.
287    /// 
288    /// # Example
289    /// 
290    /// ```
291    /// use geo_buffer::{Coordinate, Ray};
292    /// 
293    /// let c1 = (0., 0.).into();
294    /// let c2 = (3., 4.).into();
295    /// let r1 = Ray::new(c1, c2);
296    /// let r2 = r1.rotate_by(std::f64::consts::PI/2.);
297    /// 
298    /// assert!(r2.point_by_ratio(1.).eq(&(-4., 3.).into()));
299    /// ```
300    pub fn rotate_by(&self, angle: f64) -> Self{
301        let nx = self.angle.0*f64::cos(angle) - self.angle.1*f64::sin(angle);
302        let ny = self.angle.0*f64::sin(angle) + self.angle.1*f64::cos(angle);
303        Self { origin: self.origin, angle: (nx, ny).into() }
304    }
305}