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}