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}