rustic_zen/
geom.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Rustic Zen's native geometry primitives.
6//!
7//! Comment at <https://gitlab.com/IGBC/rustic-zen/-/issues/17> what
8//! your favourite geometry interoperablity library is and why we
9//! should port to it.
10//!
11//! Until a succesful port to a math interoperabilty library happens
12//! a scene must be constructed using these primitives. Sorry.
13//! Thankfully 2D points, vectors, and 2x2 matricies are available,
14//! with quite a few helper functions. The raytracer core is written
15//! with these primitives so they are pretty fast too, leveraging matrix
16//! operations wherever it makes sense.
17//!
18//! `Points` and `Vectors` are structurally identical, and can be freely cast
19//! between, however in the rustic zen api `Points` and `Vectors` are used to distingish
20//! between coordinates in the scene, and other vectors such as directions or normals.
21
22use std::ops::{Add, Mul, Neg, Sub};
23use std::option::Option;
24
25#[derive(PartialOrd, PartialEq, Copy, Clone, Debug)]
26/// A meaningful spacial coordinate.
27///
28/// Most transformations of this type return a `Vector`, that is not
29/// bound to mean a location in space. `Vectors` can be converted back
30/// into points if the programmer determines they again represent a
31/// coordinate in space with `Vector::p()`
32pub struct Point {
33    #[allow(missing_docs)]
34    pub x: f64,
35    #[allow(missing_docs)]
36    pub y: f64,
37}
38
39#[derive(PartialOrd, PartialEq, Copy, Clone, Debug)]
40/// A standard 2D vector implementaion.
41pub struct Vector {
42    #[allow(missing_docs)]
43    pub x: f64,
44    #[allow(missing_docs)]
45    pub y: f64,
46}
47
48#[derive(Copy, Clone, Debug)]
49/// A standard 2x2 matrix implmentation
50pub struct Matrix {
51    /// Top Left
52    pub a1: f64,
53    /// Top Right
54    pub b1: f64,
55    /// Bottom Left
56    pub a2: f64,
57    /// Bottom Right
58    pub b2: f64,
59}
60
61#[derive(PartialOrd, PartialEq, Copy, Clone, Debug)]
62pub(crate) struct Rect {
63    pub top_left: Point,
64    pub bottom_right: Point,
65}
66
67impl Neg for Vector {
68    type Output = Vector;
69    fn neg(self) -> Vector {
70        Vector {
71            x: -self.x,
72            y: -self.y,
73        }
74    }
75}
76
77impl Sub<Matrix> for Matrix {
78    type Output = Matrix;
79    fn sub(self, rhs: Matrix) -> Matrix {
80        Matrix {
81            a1: self.a1 - rhs.a1,
82            a2: self.a2 - rhs.a2,
83            b1: self.b1 - rhs.b1,
84            b2: self.b2 - rhs.b2,
85        }
86    }
87}
88
89impl Add<Matrix> for Matrix {
90    type Output = Matrix;
91    fn add(self, rhs: Matrix) -> Matrix {
92        Matrix {
93            a1: self.a1 + rhs.a1,
94            a2: self.a2 + rhs.a2,
95            b1: self.b1 + rhs.b1,
96            b2: self.b2 + rhs.b2,
97        }
98    }
99}
100
101impl Mul<Matrix> for Matrix {
102    type Output = Matrix;
103    fn mul(self, rhs: Matrix) -> Matrix {
104        Matrix {
105            a1: (self.a1 * rhs.a1) + (self.b1 * rhs.a2),
106            a2: (self.a2 * rhs.a1) + (self.b2 * rhs.a2),
107            b1: (self.a1 * rhs.b1) + (self.b1 * rhs.b2),
108            b2: (self.a2 * rhs.b1) + (self.b2 * rhs.b2),
109        }
110    }
111}
112
113impl Mul<f64> for Matrix {
114    type Output = Matrix;
115    fn mul(self, rhs: f64) -> Matrix {
116        Matrix {
117            a1: self.a1 * rhs,
118            a2: self.a2 * rhs,
119            b1: self.b1 * rhs,
120            b2: self.b2 * rhs,
121        }
122    }
123}
124
125impl Mul<Point> for Matrix {
126    type Output = Point;
127    fn mul(self, rhs: Point) -> Point {
128        Point {
129            x: (self.a1 * rhs.x) + (self.b1 * rhs.y),
130            y: (self.a2 * rhs.x) + (self.b2 * rhs.y),
131        }
132    }
133}
134
135impl Mul<Vector> for Matrix {
136    type Output = Vector;
137    fn mul(self, rhs: Vector) -> Vector {
138        Vector {
139            x: (self.a1 * rhs.x) + (self.b1 * rhs.y),
140            y: (self.a2 * rhs.x) + (self.b2 * rhs.y),
141        }
142    }
143}
144
145impl Matrix {
146    /// calulates the determinant of the matrix then inverses it.
147    /// Useful for attempting matrix division.
148    pub fn inverse(self) -> Option<Matrix> {
149        let det = (self.a1 * self.b2) - (self.b1 * self.a2);
150        if det == 0.0 {
151            None
152        } else {
153            Some(
154                Matrix {
155                    a1: self.b2,
156                    b1: -self.b1,
157                    a2: -self.a2,
158                    b2: self.a1,
159                } * (1.0 / det),
160            )
161        }
162    }
163}
164
165impl Add<Vector> for Vector {
166    type Output = Vector;
167    fn add(self, rhs: Vector) -> Vector {
168        Vector {
169            x: self.x + rhs.x,
170            y: self.y + rhs.y,
171        }
172    }
173}
174
175impl Sub<Vector> for Vector {
176    type Output = Vector;
177    fn sub(self, rhs: Vector) -> Vector {
178        Vector {
179            x: self.x - rhs.x,
180            y: self.y - rhs.y,
181        }
182    }
183}
184
185impl Mul<f64> for Vector {
186    type Output = Vector;
187    fn mul(self, rhs: f64) -> Vector {
188        Vector {
189            x: self.x * rhs,
190            y: self.y * rhs,
191        }
192    }
193}
194
195impl Sub<Vector> for Point {
196    type Output = Point;
197    fn sub(self, rhs: Vector) -> Point {
198        Point {
199            x: self.x - rhs.x,
200            y: self.y - rhs.y,
201        }
202    }
203}
204
205impl Add<Vector> for Point {
206    type Output = Point;
207    fn add(self, rhs: Vector) -> Point {
208        Point {
209            x: self.x + rhs.x,
210            y: self.y + rhs.y,
211        }
212    }
213}
214
215impl Sub<Point> for Vector {
216    type Output = Point;
217    fn sub(self, rhs: Point) -> Point {
218        Point {
219            x: self.x - rhs.x,
220            y: self.y - rhs.y,
221        }
222    }
223}
224
225impl Add<Point> for Vector {
226    type Output = Point;
227    fn add(self, rhs: Point) -> Point {
228        Point {
229            x: self.x + rhs.x,
230            y: self.y + rhs.y,
231        }
232    }
233}
234
235impl Sub<Point> for Point {
236    type Output = Vector;
237    fn sub(self, rhs: Point) -> Vector {
238        Vector {
239            x: self.x - rhs.x,
240            y: self.y - rhs.y,
241        }
242    }
243}
244
245// much of this us unused, but may be needed for scene partitioning in the future, so will not be removed.
246// there are no tests, so it may be rotting away :(
247#[allow(unused)]
248impl Rect {
249    pub(crate) fn centered_with_radius(p1: &Point, radius: f64) -> Rect {
250        let v = Vector {
251            x: radius,
252            y: radius,
253        };
254        Rect::from_points(&(*p1 - v), &(*p1 + v))
255    }
256
257    pub(crate) fn from_points(p1: &Point, p2: &Point) -> Rect {
258        let mut r = Rect::null_at(&p1);
259        r.expand_to_include(&p2);
260        r
261    }
262
263    pub(crate) fn from_point_and_size(point: &Point, size: &Vector) -> Rect {
264        assert!(size.x > 0.0);
265        assert!(size.y > 0.0);
266        Rect {
267            top_left: *point,
268            bottom_right: *point + *size,
269        }
270    }
271
272    pub(crate) fn null() -> Rect {
273        let nan = ::std::f64::NAN;
274        Rect {
275            top_left: Point { x: nan, y: nan },
276            bottom_right: Point { x: nan, y: nan },
277        }
278    }
279
280    pub(crate) fn null_at(point: &Point) -> Rect {
281        Rect {
282            top_left: *point,
283            bottom_right: *point,
284        }
285    }
286
287    pub(crate) fn expand(&self, left: f64, top: f64, right: f64, bottom: f64) -> Rect {
288        let top_left_vec = Vector { x: left, y: top };
289        let bottom_right_vec = Vector {
290            x: right,
291            y: bottom,
292        };
293        Rect {
294            top_left: self.top_left - top_left_vec,
295            bottom_right: self.bottom_right + bottom_right_vec,
296        }
297    }
298
299    pub(crate) fn width(&self) -> f64 {
300        self.bottom_right.x - self.top_left.x
301    }
302
303    pub(crate) fn height(&self) -> f64 {
304        self.bottom_right.y - self.top_left.y
305    }
306
307    pub(crate) fn left(&self) -> f64 {
308        self.top_left.x
309    }
310
311    pub(crate) fn right(&self) -> f64 {
312        self.bottom_right.x
313    }
314
315    pub(crate) fn top(&self) -> f64 {
316        self.top_left.y
317    }
318
319    pub(crate) fn bottom(&self) -> f64 {
320        self.bottom_right.y
321    }
322
323    pub(crate) fn top_left(&self) -> Point {
324        self.top_left
325    }
326
327    pub(crate) fn bottom_right(&self) -> Point {
328        self.bottom_right
329    }
330
331    pub(crate) fn bottom_left(&self) -> Point {
332        Point {
333            x: self.top_left().x,
334            y: self.bottom_right().y,
335        }
336    }
337
338    pub(crate) fn top_right(&self) -> Point {
339        Point {
340            x: self.bottom_right().x,
341            y: self.top_left().y,
342        }
343    }
344
345    pub(crate) fn north(&self) -> Point {
346        Point {
347            x: self.left() + self.width() / 2.0,
348            y: self.top(),
349        }
350    }
351
352    pub(crate) fn south(&self) -> Point {
353        Point {
354            x: self.left() + self.width() / 2.0,
355            y: self.bottom(),
356        }
357    }
358
359    pub(crate) fn west(&self) -> Point {
360        Point {
361            x: self.left(),
362            y: self.top() + self.height() / 2.0,
363        }
364    }
365
366    pub(crate) fn east(&self) -> Point {
367        Point {
368            x: self.right(),
369            y: self.top() + self.height() / 2.0,
370        }
371    }
372
373    pub(crate) fn expanded_by(&self, point: &Point) -> Rect {
374        let mut r = self.clone();
375        r.expand_to_include(point);
376        r
377    }
378
379    pub(crate) fn is_null(&self) -> bool {
380        self.top_left.x.is_nan()
381            || self.top_left.y.is_nan()
382            || self.bottom_right.x.is_nan()
383            || self.bottom_right.y.is_nan()
384    }
385
386    pub(crate) fn expand_to_include(&mut self, point: &Point) {
387        fn min(a: f64, b: f64) -> f64 {
388            if a.is_nan() {
389                return b;
390            }
391            if b.is_nan() {
392                return a;
393            }
394            if a < b {
395                return a;
396            }
397            return b;
398        }
399
400        fn max(a: f64, b: f64) -> f64 {
401            if a.is_nan() {
402                return b;
403            }
404            if b.is_nan() {
405                return a;
406            }
407            if a > b {
408                return a;
409            }
410            return b;
411        }
412
413        self.top_left.x = min(self.top_left.x, point.x);
414        self.top_left.y = min(self.top_left.y, point.y);
415
416        self.bottom_right.x = max(self.bottom_right.x, point.x);
417        self.bottom_right.y = max(self.bottom_right.y, point.y);
418    }
419
420    pub(crate) fn union_with(&self, other: &Rect) -> Rect {
421        let mut r = self.clone();
422        r.expand_to_include(&other.top_left);
423        r.expand_to_include(&other.bottom_right);
424        r
425    }
426
427    pub(crate) fn contains(&self, p: &Point) -> bool {
428        p.x >= self.top_left.x
429            && p.x < self.bottom_right.x
430            && p.y >= self.top_left.y
431            && p.y < self.bottom_right.y
432    }
433
434    pub(crate) fn does_intersect(&self, other: &Rect) -> bool {
435        let r1 = self;
436        let r2 = other;
437
438        // From stack overflow:
439        // http://gamedev.stackexchange.com/a/913
440        !(r2.left() > r1.right()
441            || r2.right() < r1.left()
442            || r2.top() > r1.bottom()
443            || r2.bottom() < r1.top())
444    }
445
446    pub(crate) fn intersect_with(&self, other: &Rect) -> Rect {
447        if !self.does_intersect(other) {
448            return Rect::null();
449        }
450        let left = self.left().max(other.left());
451        let right = self.right().min(other.right());
452
453        let top = self.top().max(other.top());
454        let bottom = self.bottom().min(other.bottom());
455
456        Rect::from_points(
457            &Point { x: left, y: top },
458            &Point {
459                x: right,
460                y: bottom,
461            },
462        )
463    }
464
465    pub(crate) fn midpoint(&self) -> Point {
466        let half = Vector {
467            x: self.width() / 2.0,
468            y: self.height() / 2.0,
469        };
470        self.top_left() + half
471    }
472
473    pub(crate) fn split_vert(&self) -> (Rect, Rect) {
474        let half_size = Vector {
475            x: self.width() / 2.0,
476            y: self.height(),
477        };
478        let half_offset = Vector {
479            x: self.width() / 2.0,
480            y: 0.0,
481        };
482        (
483            Rect::from_point_and_size(&self.top_left, &half_size),
484            Rect::from_point_and_size(&(self.top_left + half_offset), &half_size),
485        )
486    }
487
488    pub(crate) fn split_hori(&self) -> (Rect, Rect) {
489        let half_size = Vector {
490            x: self.width(),
491            y: self.height() / 2.0,
492        };
493        let half_offset = Vector {
494            x: 0.0,
495            y: self.height() / 2.0,
496        };
497        (
498            Rect::from_point_and_size(&self.top_left, &half_size),
499            Rect::from_point_and_size(&(self.top_left + half_offset), &half_size),
500        )
501    }
502
503    pub(crate) fn split_quad(&self) -> [Rect; 4] {
504        let half = Vector {
505            x: self.width() / 2.0,
506            y: self.height() / 2.0,
507        };
508        [
509            // x _
510            // _ _
511            Rect::from_point_and_size(&self.top_left, &half),
512            // _ x
513            // _ _
514            Rect::from_point_and_size(
515                &Point {
516                    x: self.top_left.x + half.x,
517                    ..self.top_left
518                },
519                &half,
520            ),
521            // _ _
522            // x _
523            Rect::from_point_and_size(
524                &Point {
525                    y: self.top_left.y + half.y,
526                    ..self.top_left
527                },
528                &half,
529            ),
530            // _ _
531            // _ x
532            Rect::from_point_and_size(&(self.top_left + half), &half),
533        ]
534    }
535
536    pub(crate) fn close_to(&self, other: &Rect, epsilon: f64) -> bool {
537        self.top_left.close_to(&other.top_left, epsilon)
538            && self.bottom_right.close_to(&other.bottom_right, epsilon)
539    }
540}
541
542impl Vector {
543    /// Create new vector. This is just a helper given the fields are public.
544    pub fn new(x: f64, y: f64) -> Self {
545        Self { x, y }
546    }
547
548    /// gets the linear size of the vector.
549    pub fn magnitude(&self) -> f64 {
550        (self.x * self.x + self.y * self.y).sqrt()
551    }
552
553    /// returns a copy of this vector with a length of 1
554    pub fn normalized(&self) -> Vector {
555        let m = self.magnitude();
556        Vector {
557            x: self.x / m,
558            y: self.y / m,
559        }
560    }
561
562    /// get normal of this vector
563    pub fn normal(&self) -> Vector {
564        Vector {
565            x: -self.y,
566            y: self.x,
567        }
568    }
569
570    /// element wise multiply (it can come in handy)
571    pub fn mul_e(&self, other: &Vector) -> Vector {
572        Vector {
573            x: self.x * other.x,
574            y: self.y * other.y,
575        }
576    }
577
578    /// element wise scaling with seperate x and y scaling factors
579    pub fn scale_e(&self, sx: f64, sy: f64) -> Vector {
580        Vector {
581            x: self.x * sx,
582            y: self.y * sy,
583        }
584    }
585
586    /// apply a rotation matrix constructed around `theta` to this vector
587    pub fn rotate(&self, theta: f64) -> Vector {
588        Matrix {
589            a1: f64::cos(theta),
590            b1: -f64::sin(theta),
591            a2: f64::sin(theta),
592            b2: f64::cos(theta),
593        } * *self
594    }
595
596    /// Cross product of this vector with `other`
597    pub fn cross(&self, other: &Vector) -> f64 {
598        self.x * other.y - self.y * other.x
599    }
600
601    /// Dot product of this vector with `other`
602    pub fn dot(&self, other: &Vector) -> f64 {
603        self.x * other.x + self.y * other.y
604    }
605
606    /*
607     * Does *not* require 'normal' to already be normalized
608     */
609    /// Caluate a normal reflection of this vector as a direction vector
610    /// against a surface with a normal vector of `normal`.
611    /// # Parameters:
612    ///   * __self__: a `Vector` representing the direction to be relected.
613    ///   * __normal__: a `Vector` representing the normal of the surface being reflected off.
614    ///
615    ///  Neither vector needs to be normalised.
616    pub fn reflect(&self, normal: &Vector) -> Vector {
617        let t: f64 = 2.0 * (normal.x * self.x + normal.y * self.y)
618            / (normal.x * normal.x + normal.y * normal.y);
619        let x = self.x - t * normal.x;
620        let y = self.y - t * normal.y;
621        Vector { x, y }
622    }
623
624    /// Turn this vector back into a `Point`.
625    ///
626    /// Only use when you are sure this vector actually represents a spacial coordinate.
627    pub fn p(self) -> Point {
628        Point {
629            x: self.x,
630            y: self.y,
631        }
632    }
633}
634
635impl From<(f64, f64)> for Vector {
636    fn from(value: (f64, f64)) -> Self {
637        Self {
638            x: value.0,
639            y: value.1,
640        }
641    }
642}
643
644impl Point {
645    /// Create new point. This is just a helper given the fields are public.
646    pub fn new(x: f64, y: f64) -> Self {
647        Self { x, y }
648    }
649
650    /// Returns true of this point is within `epsilon` distance of `other`
651    /// uses euclidian squared distance internally for speed.
652    pub fn close_to(&self, other: &Point, epsilon: f64) -> bool {
653        self.distance_2(other) < epsilon * epsilon
654    }
655
656    /// calulates the euclidian distance between this point and `other`
657    pub fn distance(&self, other: &Point) -> f64 {
658        self.distance_2(other).sqrt()
659    }
660
661    /// calulates the euclidian squared distance between this point and `other`
662    /// mostly useful as an optimisation to save the square root when comparing distnaces
663    pub fn distance_2(&self, other: &Point) -> f64 {
664        let dx = self.x - other.x;
665        let dy = self.y - other.y;
666        dx * dx + dy * dy
667    }
668
669    /// Turn this point back into a vector.
670    pub fn v(self) -> Vector {
671        Vector {
672            x: self.x,
673            y: self.y,
674        }
675    }
676}
677
678impl From<(f64, f64)> for Point {
679    fn from(value: (f64, f64)) -> Self {
680        Self {
681            x: value.0,
682            y: value.1,
683        }
684    }
685}
686
687#[cfg(test)]
688mod tests {
689    use super::{Matrix, Vector};
690    #[test]
691    fn matrix_inverse() {
692        let m = Matrix {
693            a1: 1.0,
694            b1: 2.0,
695            a2: 3.0,
696            b2: 4.0,
697        };
698        let n = m.inverse().expect("This should have an inverse");
699        assert_eq!(n.a1, -2.0);
700        assert_eq!(n.b1, 1.0);
701        assert_eq!(n.a2, 1.5);
702        assert_eq!(n.b2, -0.5);
703    }
704
705    #[test]
706    fn matrix_inverse_identity() {
707        let m = Matrix {
708            a1: 1.0,
709            b1: 0.0,
710            a2: 0.0,
711            b2: 1.0,
712        };
713        let n = m.inverse().expect("This should have an inverse");
714        assert_eq!(n.a1, 1.0);
715        assert_eq!(n.b1, 0.0);
716        assert_eq!(n.a2, 0.0);
717        assert_eq!(n.b2, 1.0);
718    }
719
720    #[test]
721    fn matrix_inverse_singular() {
722        let m = Matrix {
723            a1: 0.0,
724            b1: 0.0,
725            a2: 0.0,
726            b2: 0.0,
727        };
728        let n = m.inverse();
729        if n.is_none() {
730            return;
731        } else {
732            panic!("This should be singular");
733        }
734    }
735
736    #[test]
737    fn vector_dot() {
738        let a = Vector { x: 1.0, y: 0.0 };
739        let b = Vector {
740            x: f64::cos(1.0),
741            y: f64::cos(1.0),
742        };
743        let t = f64::acos(a.dot(&b));
744        assert!(t < 1.0001);
745        assert!(t > 0.9999);
746    }
747
748    #[test]
749    fn vector_dot_2() {
750        let a = Vector { x: 0.0, y: 1.0 };
751        let b = Vector {
752            x: f64::sin(1.0),
753            y: f64::cos(1.0),
754        };
755        let t = f64::acos(a.dot(&b));
756        assert!(t < 1.0001);
757        assert!(t > 0.9999);
758    }
759
760    #[test]
761    fn vector_rotate() {
762        let v = Vector { x: 6.0, y: 4.0 };
763        let r = v.rotate(-std::f64::consts::PI / 2.0);
764
765        assert_eq!(r.x.round(), 4.0);
766        assert_eq!(r.y.round(), -6.0);
767    }
768
769    #[test]
770    fn vector_rotate_2() {
771        let v = Vector { x: 6.0, y: 4.0 };
772        let r = v.rotate(std::f64::consts::PI);
773
774        assert_eq!(r.x.round(), -6.0);
775        assert_eq!(r.y.round(), -4.0);
776    }
777}