voronoice/
bounding_box.rs

1use crate::utils::{abs_diff_eq, EQ_EPSILON};
2
3use super::Point;
4
5/// Defines a rectangular bounding box.
6///
7/// The Y axis convention is downwards.
8#[derive(Debug, Clone)]
9pub struct BoundingBox {
10    /// The center point of a rectangle.
11    center: Point,
12
13    /// The top right point of a rectangle.
14    top_right: Point,
15
16    /// The bottom left point of a rectangle.
17    bottom_left: Point,
18}
19
20impl Default for BoundingBox {
21    fn default() -> Self {
22        Self::new_centered_square(2.0) // square from [-1, 1] on xy
23    }
24}
25
26impl BoundingBox {
27    /// Constructs a new bounding box.
28    ///
29    /// # Arguments
30    ///
31    /// * `center` - The position of the center of the bounding box
32    /// * `width` - The bounding box's width
33    /// * `height` - The bounding box's height
34    ///
35    pub fn new(center: Point, width: f64, height: f64) -> Self {
36        Self {
37            top_right: Point { x: center.x + width / 2.0, y: center.y - height / 2.0 },
38            bottom_left: Point { x: center.x - width / 2.0, y: center.y + height / 2.0 },
39            center,
40        }
41    }
42
43    /// Constructs a new bounding box centeterd at origin with the provided width and height.
44    pub fn new_centered(width: f64, height: f64) -> Self {
45        Self::new(Point { x: 0.0, y: 0.0 }, width, height)
46    }
47
48    /// Constructs a new square bounding box centeterd at origin with the provided width.
49    pub fn new_centered_square(width: f64) -> Self {
50        Self::new_centered(width, width)
51    }
52
53    /// Gets the position of the box's center.
54    #[inline]
55    pub fn center(&self) -> &Point {
56        &self.center
57    }
58
59    /// Gets the position of the top right corner of the bounding box.
60    #[inline]
61    pub fn top_right(&self) -> &Point {
62        &self.top_right
63    }
64
65    /// Gets the position of the bottom left corner of the bounding box.
66    #[inline]
67    pub fn bottom_left(&self) -> &Point {
68        &self.bottom_left
69    }
70
71    /// Gets the width of the bounding box.
72    #[inline]
73    pub fn width(&self) -> f64 {
74        self.top_right.x - self.bottom_left.x
75    }
76
77    /// Gets the height of the bounding box.
78    #[inline]
79    pub fn height(&self) -> f64 {
80        self.bottom_left.y - self.top_right.y
81    }
82
83    /// Gets the Y coordinate of the top of the bounding box.
84    #[inline]
85    pub fn top(&self) -> f64 {
86        self.top_right.y
87    }
88
89    /// Gets the Y coordinate of the bottom of the bounding box.
90    #[inline]
91    pub fn bottom(&self) -> f64 {
92        self.bottom_left.y
93    }
94
95    /// Gets the X coordinate of the left of the bounding box.
96    #[inline]
97    pub fn left(&self) -> f64 {
98        self.bottom_left.x
99    }
100
101    /// Gets the X coordinate of the right of the bounding box.
102    #[inline]
103    pub fn right(&self) -> f64 {
104        self.top_right.x
105    }
106
107    /// Gets a slice of corners oriented counter-clockwise.
108    pub fn corners(&self) -> [Point; 4] {
109        [
110            Point { x: self.left(), y: self.top() }, // top left
111            self.bottom_left().clone(),
112            Point { x: self.right(), y: self.bottom() }, // bottom right
113            self.top_right().clone(),
114        ]
115    }
116
117    /// Returns whether a given point is inside (or on the edges) of the bounding box.
118    #[inline]
119    pub fn is_inside(&self, point: &Point) -> bool {
120        let horizonal_ok = point.x >= self.left() && point.x <= self.right();
121        let vertical_ok = point.y >= self.top() && point.y <= self.bottom();
122
123        horizonal_ok && vertical_ok
124    }
125
126    // /// Same as inside, but return false if point is on the box edge.
127    #[inline]
128    pub fn is_exclusively_inside(&self, point: &Point) -> bool {
129        self.is_inside(point) && self.which_edge(point).is_none()
130    }
131
132    /// Returns the index of the corner representing the edge ```point``` is on.
133    ///
134    /// corners() method can be called to get the list of corners.
135    ///
136    /// # Example
137    /// If point is on middle of the top edge, the top left corner will be returned.
138    #[inline]
139    pub (crate) fn which_edge(&self, point: &Point) -> Option<usize> {
140        if abs_diff_eq(point.y, self.top(), EQ_EPSILON) {
141            // top
142            Some(0)
143        } else if abs_diff_eq(point.y, self.bottom(), EQ_EPSILON) {
144            // bottom
145            Some(2)
146        } else {
147            if abs_diff_eq(point.x, self.right(), EQ_EPSILON) {
148                // right
149                Some(3)
150            } else if abs_diff_eq(point.x, self.left(), EQ_EPSILON) {
151                // left
152                Some(1)
153            } else {
154                None
155            }
156        }
157    }
158
159    /// Intersects a line represented by points 'a' and 'b' and returns the two intersecting points with the box, or None
160    pub (crate) fn intersect_line(&self, a: &Point, b: &Point) -> (Option<Point>, Option<Point>) {
161        let c_x = b.x - a.x;
162        let c_y = b.y - a.y;
163        let c = c_y / c_x;
164        let d = a.y - (a.x * c);
165
166        let mut f = None;
167        let mut g = None;
168        let mut h = None;
169        let mut i = None;
170
171        // intersection left, right edges
172        if c_x.abs() > 4. * std::f64::EPSILON {
173            // y = c*x + d
174            let right_y = (self.right() * c) + d;
175            let left_y = (self.left() * c) + d;
176
177            if right_y >= self.top()
178            && right_y <= self.bottom() {
179                f = Some(Point { x: self.right(), y: right_y });
180            }
181
182            if left_y >= self.top()
183            && left_y <= self.bottom() {
184                g = Some(Point { x: self.left(), y: left_y })
185            }
186
187            if g.is_some() && f.is_some() {
188                // can't have more than 2 intersections, we are done
189                return (f, g);
190            }
191        } // else line is parallel to y, won't intersect with left/right
192
193        // intersection top, bottom edges
194        if c_y.abs() > 4. * std::f64::EPSILON {
195            if c_x.abs() < 4. * std::f64::EPSILON {
196                // line is parallel to y
197                if a.x <= self.right()
198                && a.x >= self.left() {
199                    // and crosses box
200                    return (
201                        Some(Point { x: a.x, y: self.top() }),
202                        Some(Point { x: a.x, y: self.bottom() })
203                    );
204                } else {
205                    // does not cross box
206                    return (None, None);
207                }
208            }
209
210            // x = (y - d) / c
211            let top_x = (self.top() - d) / c;
212            let bottom_x = (self.bottom() - d) / c;
213
214            if top_x <= self.right()
215                && top_x >= self.left() {
216                h = Some(Point { x: top_x, y: self.top() })
217            }
218
219            if bottom_x <= self.right()
220                && bottom_x >= self.left() {
221                i = Some(Point { x: bottom_x, y: self.bottom() })
222            }
223
224            if h.is_some() && i.is_some() {
225                // can't have more than 2 intersections, we are done
226                return (h, i);
227            }
228        } // else line is parallel to x, won't intersect with top / bottom
229
230        (f.or(g), h.or(i))
231    }
232
233    /// Intersects a ray with the bounding box. The first intersection is returned first.
234    pub (crate) fn project_ray(&self, point: &Point, direction: &Point) -> (Option<Point>, Option<Point>) {
235        let b = Point { x: point.x + direction.x, y: point.y + direction.y };
236        let (a, b) = self.intersect_line(point, &b);
237        order_points_on_ray(point, direction, a, b)
238    }
239}
240
241/// Given a ray defined by `point` and `direction`, and two points `a` and `b` on such ray, returns a tuple (w, z) where point <= w <= z.
242/// If either `a` or `b` are smaller than `point`, None is returned.
243pub (crate) fn order_points_on_ray(point: &Point, direction: &Point, a: Option<Point>, b: Option<Point>) -> (Option<Point>, Option<Point>) {
244    match (a,b) {
245        (None, None) => { // no points, no intersection
246            (None, None)
247        }
248        (Some(va), Some(vb)) => { // both a and b are reachable
249            // point a and b are just a scalar times direction, so we can compare any non-zero
250            // direction component, use largest
251            let (d, da, db) = if direction.x.abs() > direction.y.abs() {
252                // use x for comparison
253                (direction.x, va.x - point.x, vb.x - point.x)
254            } else {
255                (direction.y, va.y - point.y, vb.y - point.y)
256            };
257
258            match (d.signum() == da.signum(), d.signum() == db.signum()) {
259                (true, true) => {
260                    if da.abs() > db.abs() {
261                        // b is closer
262                        (Some(vb), Some(va))
263                    } else {
264                        // a is closer
265                        (Some(va), Some(vb))
266                    }
267                },
268                (true, false) => { // only a reachable
269                    (Some(va), None)
270                },
271                (false, true) => { // only b reachably
272                    (Some(vb), None)
273                },
274                (false, false) => { // neither a nor b is reachable, no intersection
275                    (None, None)
276                }
277            }
278        },
279        (Some(va), None) => {
280            if direction.x.signum() == va.x.signum() && direction.y.signum() == va.y.signum() {
281                // a is in the right direction
282                (Some(va), None)
283            } else {
284                // a can't be reached
285                (None, None)
286            }
287        },
288        (None, Some(vb)) => {
289            if direction.x.signum() == vb.x.signum() && direction.y.signum() == vb.y.signum() {
290                // b is in the right direction
291                (Some(vb), None)
292            } else {
293                // b can't be reached
294                (None, None)
295            }
296        }
297    }
298}
299
300#[inline]
301pub (crate) fn next_edge(edge: usize) -> usize {
302    (edge + 1) % 4
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    fn line(x: f64, c: f64, d: f64) -> Point {
310        Point { x, y: (x * c) + d }
311    }
312    // direction-vector b to a, not a to b!
313    fn direction(a: &Point, b: &Point) -> Point {
314        Point { x: a.x - b.x, y: a.y - b.y }
315    }
316
317    #[test]
318    fn intersect_line_tests() {
319        // square centered on origin with edges on x = +-1, y= +-1
320        let bbox = BoundingBox::new_centered_square(2.0);
321
322        // line parallel to x, outside box
323        let (a, b) = bbox.intersect_line(&Point { x: 5.0, y: 0.0 }, &Point { x: 5.0, y: 1.0 }); // x = 5
324        assert_eq!(a, None, "No intersection expected for a parallel line to X outside of the box");
325        assert_eq!(b, None, "No intersection expected for a parallel line to X outside of the box");
326
327        // line parallel to y, outside box
328        let (a, b) = bbox.intersect_line(&Point { x: 0.0, y: 5.0 }, &Point { x: 1.0, y: 5.0 }); // y = 5
329        assert_eq!(a, None, "No intersection expected for a parallel line to Y outside of the box");
330        assert_eq!(b, None, "No intersection expected for a parallel line to Y outside of the box");
331
332        // line parallel to x, crossing box
333        let (a, b) = bbox.intersect_line(&Point { x: 0.0, y: 0.0 }, &Point { x: 0.0, y: 1.0 }); // x = 0
334        assert_eq!(Some(Point { x: 0.0, y: bbox.top() }), a, "Expected intersection with top edge");
335        assert_eq!(Some(Point { x: 0.0, y: bbox.bottom() }), b, "Expected intersection with bottom edge");
336
337        // line parallel to y, crossing box
338        let (a, b) = bbox.intersect_line(&Point { x: 0.0, y: 0.0 }, &Point { x: 1.0, y: 0.0 }); // y = 0
339        assert_eq!(Some(Point { x: 1.0, y: 0.0 }), a, "Expected intersection with right edge");
340        assert_eq!(Some(Point { x: -1.0, y: 0.0 }), b, "Expected intersection with left edge");
341
342        // line congruent to top edge
343        let (a, b) = bbox.intersect_line(&Point { x: 0.0, y: bbox.top() }, &Point { x: 1.0, y: bbox.top() }); // y = top
344        assert_eq!(Some(Point { x: bbox.right(), y: bbox.top() }), a, "Expected intersection with top right corner");
345        assert_eq!(Some(Point { x: bbox.left(), y: bbox.top() }), b, "Expected intersection with top left corner");
346
347        // line congruent to bottom edge
348        let (a, b) = bbox.intersect_line(&Point { x: 0.0, y: bbox.bottom() }, &Point { x: 1.0, y: bbox.bottom() }); // y = bottom
349        assert_eq!(Some(Point { x: bbox.right(), y: bbox.bottom() }), a, "Expected intersection with bottom right corner");
350        assert_eq!(Some(Point { x: bbox.left(), y: bbox.bottom() }), b, "Expected intersection with bottom left corner");
351
352        // line congruent to right edge
353        let (a, b) = bbox.intersect_line(&Point { x: bbox.right(), y: 0.0 }, &Point { x: bbox.right(), y: 1.0 }); // x = right
354        assert_eq!(Some(Point { x: bbox.right(), y: bbox.top() }), a, "Expected intersection with top right corner");
355        assert_eq!(Some(Point { x: bbox.right(), y: bbox.bottom() }), b, "Expected intersection with bottom right corner");
356
357        // line congruent to left edge
358        let (a, b) = bbox.intersect_line(&Point { x: bbox.left(), y: 0.0 }, &Point { x: bbox.left(), y: 1.0 }); // x = left
359        assert_eq!(Some(Point { x: bbox.left(), y: bbox.top() }), a, "Expected intersection with top left corner");
360        assert_eq!(Some(Point { x: bbox.left(), y: bbox.bottom() }), b, "Expected intersection with bottom left corner");
361
362        // -45 degree line from box origin
363        let (a, b) = bbox.intersect_line(&Point { x: 0.0, y: 0.0 }, &Point { x: bbox.right(), y: bbox.top() });
364        assert_eq!(Some(Point { x: bbox.right(), y: bbox.top() }), a, "Expected intersection with top right corner");
365        assert_eq!(Some(Point { x: bbox.left(), y: bbox.bottom() }), b, "Expected intersection with left bottom corner");
366
367        // 45 degree line from box origin
368        let (a, b) = bbox.intersect_line(&Point { x: 0.0, y: 0.0 }, &Point { x: bbox.left(), y: bbox.bottom() });
369        assert_eq!(Some(Point { x: bbox.right(), y: bbox.top() }), a, "Expected intersection with top right corner");
370        assert_eq!(Some(Point { x: bbox.left(), y: bbox.bottom() }), b, "Expected intersection with left bottom corner");
371
372        // -45 degree line translated by (0.5,0.5) - top right ear
373        let (a, b) = bbox.intersect_line(&Point { x: 0.5, y: 0.5 }, &Point { x: 0.4, y: 0.6 });
374        assert_eq!(Some(Point { x: 1.0, y: 0.0 }), a, "Expected intersection with middle of the right edge");
375        assert_eq!(Some(Point { x: 0.0, y: 1.0 }), b, "Expected intersection with middle of the top edge");
376
377        // 45 degree line translated by (-0.5,0.5) - top left ear
378        let (a, b) = bbox.intersect_line(&Point { x: -0.5, y: 0.5 }, &Point { x: -0.4, y: 0.6 });
379        assert_eq!(Some(Point { x: -1.0, y: 0.0 }), a, "Expected intersection with middle of the left edge");
380        assert_eq!(Some(Point { x: 0.0, y: 1.0 }), b, "Expected intersection with middle of the top edge");
381
382        // -45 degree line translated by (-0.5,-0.5) - bottom left ear
383        let (a, b) = bbox.intersect_line(&Point { x: -0.5, y: -0.5 }, &Point { x: -0.4, y: -0.6 });
384        assert_eq!(Some(Point { x: -1.0, y: 0.0 }), a, "Expected intersection with middle of the left edge");
385        assert_eq!(Some(Point { x: 0.0, y: -1.0 }), b, "Expected intersection with middle of the bottom edge");
386
387        // 45 degree line translated by (0.5,-0.5) - bottom right ear
388        let (a, b) = bbox.intersect_line(&Point { x: 0.5, y: -0.5 }, &Point { x: 0.4, y: -0.6 });
389        assert_eq!(Some(Point { x: 1.0, y: 0.0 }), a, "Expected intersection with middle of the right edge");
390        assert_eq!(Some(Point { x: 0.0, y: -1.0 }), b, "Expected intersection with middle of the bottom edge");
391    }
392
393    #[test]
394    fn project_ray_tests_centered_square() {
395        // square centered on origin with edges on x = +-1, y= +-1
396        let bbox = BoundingBox::new_centered_square(2.0);
397
398        // point to the right of right side, directed to origin
399        let (a, b) = bbox.project_ray(&Point { x: 2.0, y: 0.0 }, &Point { x: -0.1, y: 0.0 });
400        assert_eq!(Some(Point { x: 1.0, y: 0.0 }), a, "Expected to hit right side first");
401        assert_eq!(Some(Point { x: -1.0, y: 0.0 }), b, "And then hit the left side");
402
403        // point to the left of right side, inside, directed to origin
404        let (a, b) = bbox.project_ray(&Point { x: 0.9, y: 0.0 }, &Point { x: -0.1, y: 0.0 });
405        assert_eq!(Some(Point { x: -1.0, y: 0.0 }), a, "Expected to hit left side first");
406        assert_eq!(None, b, "and only that");
407
408        // point to the right of left side, inside, directed to origin
409        let (a, b) = bbox.project_ray(&Point { x: -0.9, y: 0.0 }, &Point { x: 0.1, y: 0.0 });
410        assert_eq!(Some(Point { x: 1.0, y: 0.0 }), a, "Expected to hit right side first");
411        assert_eq!(None, b, "and only that");
412
413        // point to the left of left side, directed to origin
414        let (a, b) = bbox.project_ray(&Point { x: -2.0, y: 0.0 }, &Point { x: 0.1, y: 0.0 });
415        assert_eq!(Some(Point { x: -1.0, y: 0.0 }), a, "Expected to hit left side first");
416        assert_eq!(Some(Point { x: 1.0, y: 0.0 }), b, "And then hit the right side");
417
418        // point to the top of top side, directed to origin
419        let (a, b) = bbox.project_ray(&Point { x: 0.0, y: 3.0 }, &Point { x: 0.0, y: -10.0 });
420        assert_eq!(Some(Point { x: 0.0, y: 1.0 }), a, "Expected to hit top side first");
421        assert_eq!(Some(Point { x: 0.0, y: -1.0 }), b, "And then hit the bottom side");
422
423        // point to the bottom of top side, inside, directed to origin
424        let (a, b) = bbox.project_ray(&Point { x: 0.0, y: 0.5 }, &Point { x: 0.0, y: -10.0 });
425        assert_eq!(Some(Point { x: 0.0, y: -1.0 }), a, "Expected to hit bottom side first");
426        assert_eq!(None, b, "and only that");
427
428        // point to the top of bottom side, directed to origin
429        let (a, b) = bbox.project_ray(&Point { x: 0.0, y: -0.5 }, &Point { x: 0.0, y: 0.2 });
430        assert_eq!(Some(Point { x: 0.0, y: 1.0 }), a, "Expected to hit top side first");
431        assert_eq!(None, b, "and only that");
432
433        // point to the bottom of the bottom side, directed to origin
434        let (a, b) = bbox.project_ray(&Point { x: 0.0, y: -3.0 }, &Point { x: 0.0, y: 10.0 });
435        assert_eq!(Some(Point { x: 0.0, y: -1.0 }), a, "Expected to hit bottom side first");
436        assert_eq!(Some(Point { x: 0.0, y: 1.0 }), b, "And then hit the top side");
437
438        // point to the right of top side, inside, directed to origin
439        let (a, b) = bbox.project_ray(&Point { x: 0.0, y: 0.5 }, &Point { x: 0.0, y: -10.0 });
440        assert_eq!(Some(Point { x: 0.0, y: -1.0 }), a, "Expected to hit bottom side first");
441        assert_eq!(None, b, "and only that");
442
443        // point to the right, outside box, negatively inclined
444        let c = -0.8;
445        let d = 1.0;
446        let (a, b) = bbox.project_ray(&line(2.0, c, d), &direction(&line(-20.0, c, d), &line(2.0, c, d)));
447        assert!(a.is_some() && b.is_some(), "Expected two intersections, a: {:?}, b: {:?}", a, b);
448        assert!(
449            abs_diff_eq(line(1.0, c, d).x, a.clone().unwrap().x, EQ_EPSILON)
450            && abs_diff_eq(line(1.0, c, d).y, a.unwrap().y, EQ_EPSILON)
451            , "Expected to hit right side first"
452        );
453        assert!(
454            abs_diff_eq(line(0.0, c, d).x, b.clone().unwrap().x, EQ_EPSILON)
455            && abs_diff_eq(line(0.0, c, d).y, b.unwrap().y, EQ_EPSILON)
456            , "And then top side"
457        );
458
459        // point to the inside box, negatively inclined
460        let (a, b) = bbox.project_ray(&Point { x: -0.5, y: 0.0 }, &Point { x: -1.0, y: 0.8 });
461        assert_eq!(Some(Point { x: -1.0, y: 0.4 }), a, "Expected to hit left side first");
462        assert_eq!(None, b, "And only that");
463
464        // point to the left, outside box, positively inclined
465        let (a, b) = bbox.project_ray(&Point { x: -10.0, y: 0.0 }, &Point { x: 1.0, y: 0.8 });
466        assert!(a.is_none() && b.is_none(), "Expected no intersection");
467    }
468
469    #[test]
470    /// Tests multiple non-centered rects in all 4 quadrants of the (x,y)-plane
471    fn project_ray_tests_non_centered_rect() {
472        let width_height_ratio = [
473            1.5, //     exactly representable by float
474            1.1, // not exactly representable by float (if only this fails == float equality checks might fail due to rounding errors)
475            std::f64::consts::E // because why not
476        ];
477        let width = 1.;
478        let base_origin = Point {x: 3.1, y: 2.6};
479
480        // different rect side ratios
481        for &ratio in width_height_ratio.iter() {
482            let height = width * ratio;
483            for i in 1..=2 {
484                for j in 1..=2 {
485                    // 4 (i,j)-combinations to mirror origin around x- and y-axes to obtain origins in all 4 quadrants
486                    // -1^1 = -1
487                    // -1^2 =  1
488                    let origin = Point {
489                        x: base_origin.x * -1_f64.powi(i),
490                        y: base_origin.y * -1_f64.powi(j),
491                    };
492
493                    let bbox = BoundingBox::new(
494                        origin.clone(),
495                        width,
496                        height
497                    );
498
499                    let top = origin.y + 0.5 * height;
500                    let bottom = origin.y - 0.5 * height;
501                    let right = origin.x + 0.5 * width;
502                    let left = origin.x - 0.5 * width;
503
504                    // point to the right of the right side, directed to origin
505                    let point = &Point { x: origin.x + width, y: origin.y };
506                    let dir = &Point { x: -0.1, y: 0.0 };
507                    let (a, b) = bbox.project_ray(point, dir);
508                    assert!(a.is_some() && b.is_some(), "Expected two intersections, a: {:?}, b: {:?}", a, b);
509                    let expected_intersection_a = Point{x: right, y: origin.y};
510                    assert!(
511                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
512                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
513                        , "Expected to hit right side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
514                    );
515                    let expected_intersection_b = Point{x: left, y: origin.y};
516                    assert!(
517                        abs_diff_eq(expected_intersection_b.x, b.clone().unwrap().x, EQ_EPSILON)
518                        && abs_diff_eq(expected_intersection_b.y, b.clone().unwrap().y, EQ_EPSILON)
519                        , "And then hit left side. found: {:?}, expected: {:?}", b.unwrap(), expected_intersection_b
520                    );
521
522                    // point to the left of left side, directed to origin
523                    let point = &Point { x: origin.x - width, y: origin.y };
524                    let dir = &Point { x: 0.1, y: 0.0 };
525                    let (a, b) = bbox.project_ray(point, dir);
526                    assert!(a.is_some() && b.is_some(), "Expected two intersections, a: {:?}, b: {:?}", a, b);
527                    let expected_intersection_a = Point{x: left, y: origin.y};
528                    assert!(
529                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
530                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
531                        , "Expected to hit left side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
532                    );
533                    let expected_intersection_b = Point{x: right, y: origin.y};
534                    assert!(
535                        abs_diff_eq(expected_intersection_b.x, b.clone().unwrap().x, EQ_EPSILON)
536                        && abs_diff_eq(expected_intersection_b.y, b.clone().unwrap().y, EQ_EPSILON)
537                        , "And then hit right side. found: {:?}, expected: {:?}", b.unwrap(), expected_intersection_b
538                    );
539
540                    // point to the left of right side, inside, directed to origin
541                    let point = &Point { x: origin.x + 0.4 * width, y: origin.y };
542                    let dir = &Point { x: -0.1, y: 0.0 };
543                    let (a, b) = bbox.project_ray(point, dir);
544                    assert!(a.is_some(), "Expected one intersection");
545                    let expected_intersection_a = Point{x: left, y: origin.y};
546                    assert!(
547                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
548                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
549                        , "Expected to hit left side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
550                    );
551                    assert_eq!(None, b, "And only that");
552
553                    // point to the right of left side, inside, directed to origin
554                    let point = &Point { x: origin.x - 0.4 * width, y: origin.y };
555                    let dir = &Point { x: 0.1, y: 0.0 };
556                    let (a, b) = bbox.project_ray(point, dir);
557                    assert!(a.is_some(), "Expected one intersection");
558                    let expected_intersection_a = Point{x: right, y: origin.y};
559                    assert!(
560                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
561                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
562                        , "Expected to hit right side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
563                    );
564                    assert_eq!(None, b, "And only that");
565
566                    // point to the top of top side, directed to origin
567                    let point = &Point { x: origin.x, y: origin.y + height};
568                    let dir = &Point { x: 0.0, y: -0.1 };
569                    let (a, b) = bbox.project_ray(point, dir);
570                    assert!(a.is_some() && b.is_some(), "Expected two intersections, a: {:?}, b: {:?}", a, b);
571                    let expected_intersection_a = Point{x: origin.x, y: top};
572                    assert!(
573                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
574                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
575                        , "Expected to hit top side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
576                    );
577                    let expected_intersection_b = Point{x: origin.x, y: bottom};
578                    assert!(
579                        abs_diff_eq(expected_intersection_b.x, b.clone().unwrap().x, EQ_EPSILON)
580                        && abs_diff_eq(expected_intersection_b.y, b.clone().unwrap().y, EQ_EPSILON)
581                        , "And then hit bottom side. found: {:?}, expected: {:?}", b.unwrap(), expected_intersection_b
582                    );
583
584                    // point to the bottom of the bottom side, directed to origin
585                    let point = &Point { x: origin.x, y: origin.y - height};
586                    let dir = &Point { x: 0.0, y: 0.1 };
587                    let (a, b) = bbox.project_ray(point, dir);
588                    assert!(a.is_some() && b.is_some(), "Expected two intersections, a: {:?}, b: {:?}", a, b);
589                    let expected_intersection_a = Point{x: origin.x, y: bottom};
590                    assert!(
591                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
592                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
593                        , "Expected to hit bottom side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
594                    );
595                    let expected_intersection_b = Point{x: origin.x, y: top};
596                    assert!(
597                        abs_diff_eq(expected_intersection_b.x, b.clone().unwrap().x, EQ_EPSILON)
598                        && abs_diff_eq(expected_intersection_b.y, b.clone().unwrap().y, EQ_EPSILON)
599                        , "And then hit top side. found: {:?}, expected: {:?}", b.unwrap(), expected_intersection_b
600                    );
601
602                    // point to the bottom of top side, inside, directed to origin
603                    let point = &Point { x: origin.x, y: origin.y + 0.4 * height};
604                    let dir = &Point { x: 0.0, y: -0.1 };
605                    let (a, b) = bbox.project_ray(point, dir);
606                    assert!(a.is_some(), "Expected one intersection");
607                    let expected_intersection_a = Point{x: origin.x, y: bottom};
608                    assert!(
609                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
610                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
611                        , "Expected to hit bottom side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
612                    );
613                    assert_eq!(None, b, "And only that");
614
615                    // point to the top of bottom side, directed to origin
616                    let point = &Point { x: origin.x, y: origin.y - 0.4 * height};
617                    let dir = &Point { x: 0.0, y: 0.1 };
618                    let (a, b) = bbox.project_ray(point, dir);
619                    assert!(a.is_some(), "Expected one intersection");
620                    let expected_intersection_a = Point{x: origin.x, y: top};
621                    assert!(
622                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
623                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
624                        , "Expected to hit top side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
625                    );
626                    assert_eq!(None, b, "And only that");
627
628                    // point to the right-top of of the origin, inside, directed to bottom
629                    let point = &Point { x: origin.x + 0.3 * width, y: origin.y + 0.3 * height };
630                    let dir = &Point { x: 0.0, y: -10.0 };
631                    let (a, b) = bbox.project_ray(point, dir);
632                    assert!(a.is_some(), "Expected one intersection");
633                    let expected_intersection_a = Point{x: origin.x + 0.3 * width, y: bottom};
634                    assert!(
635                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
636                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
637                        , "Expected to hit bottom side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
638                    );
639                    assert_eq!(None, b, "And only that");
640
641                    // point to the right, outside box, negatively inclined
642                    let c = -0.8 * ratio; // to make sure the line is not too steep and hits bottom instead of right side
643                    let d = -(c * origin.x) + top; // d = -(c*x) + y -> provoke intersection at (origin.x, top)
644                    let point = &line(right + 0.5, c, d); // right of right side
645                    let dir = &direction(&line(left - 2., c, d), point); // pointing towards box
646                    let (a, b) = bbox.project_ray(point, dir);
647                    assert!(a.is_some() && b.is_some(), "Expected two intersections, a: {:?}, b: {:?}", a, b);
648                    let expected_intersection_a = line(right, c, d);
649                    assert!(
650                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
651                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
652                        , "Expected to hit right side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
653                    );
654                    let expected_intersection_b = line(origin.x, c, d);
655                    assert!(
656                        abs_diff_eq(line(origin.x, c, d).x, b.clone().unwrap().x, EQ_EPSILON)
657                        && abs_diff_eq(line(origin.x, c, d).y, b.clone().unwrap().y, EQ_EPSILON)
658                        , "And then top side. found: {:?}, expected: {:?}", b.unwrap(), expected_intersection_b
659                    );
660
661                    // point to the top-left of of the origin, inside, pointing up-left outwards
662                    let c = -0.8 * ratio; // to make sure the line is not too steep and unwantedly hits bottom instead of right side
663                    let d = -(c * left) + origin.y; // d = -(c*x) + y -> provoke intersection at (left, origin.y)
664                    let point = &line(left + 0.2 * width, c, d); // right of left side, inside
665                    let dir = &direction(&line(left - width, c, d), point); // outward direction
666                    let (a, b) = bbox.project_ray(point, dir);
667                    assert!(a.is_some(), "Expected one intersection");
668                    let expected_intersection_a = line(left, c, d);
669                    assert!(
670                        abs_diff_eq(expected_intersection_a.x, a.clone().unwrap().x, EQ_EPSILON)
671                        && abs_diff_eq(expected_intersection_a.y, a.clone().unwrap().y, EQ_EPSILON)
672                        , "Expected to hit left side first. found: {:?}, expected: {:?}", a.unwrap(), expected_intersection_a
673                    );
674                    assert_eq!(None, b, "And only that");
675
676                    // point to the left, outside box, positively inclined
677                    let point = &Point { x: left - width, y: origin.y};
678                    let dir = &Point { x: 0.9 * width, y: ratio }; // makes sure that the incline is steep enough to pass the top-left corner on the outside
679                    let (a, b) = bbox.project_ray(point, dir);
680                    assert!(a.is_none() && b.is_none(), "Expected no intersection");
681                }
682            }
683        }
684    }
685}