tg-geom 0.0.0

Rust bindings for the `TG` Geometry library.
Documentation
//! Axis-aligned bounding rectangle.

use crate::Point;

/// Defined by minimum and maximum corner points.
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct Rect {
    pub min: Point,
    pub max: Point,
}

impl Rect {
    #[inline]
    pub const fn new(min: Point, max: Point) -> Self {
        Self { min, max }
    }

    #[inline]
    pub const fn from_coords(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
        Self {
            min: Point::new(min_x, min_y),
            max: Point::new(max_x, max_y),
        }
    }

    #[inline]
    pub fn center(self) -> Point {
        let p = unsafe { tg_geom_sys::tg_rect_center(self.into()) };
        p.into()
    }

    /// Returns the union with `other`.
    #[inline]
    pub fn expand(self, other: Rect) -> Rect {
        let r = unsafe { tg_geom_sys::tg_rect_expand(self.into(), other.into()) };
        r.into()
    }

    /// Expands to include `point`.
    #[inline]
    pub fn expand_point(self, point: Point) -> Rect {
        let r = unsafe { tg_geom_sys::tg_rect_expand_point(self.into(), point.into()) };
        r.into()
    }

    #[inline]
    pub fn intersects_rect(self, other: Rect) -> bool {
        unsafe { tg_geom_sys::tg_rect_intersects_rect(self.into(), other.into()) }
    }

    #[inline]
    pub fn intersects_point(self, point: Point) -> bool {
        unsafe { tg_geom_sys::tg_rect_intersects_point(self.into(), point.into()) }
    }

    #[inline]
    pub fn width(self) -> f64 {
        self.max.x - self.min.x
    }

    #[inline]
    pub fn height(self) -> f64 {
        self.max.y - self.min.y
    }

    #[inline]
    pub fn area(self) -> f64 {
        self.width() * self.height()
    }
}

impl From<tg_geom_sys::tg_rect> for Rect {
    #[inline]
    fn from(r: tg_geom_sys::tg_rect) -> Self {
        Self {
            min: r.min.into(),
            max: r.max.into(),
        }
    }
}

impl From<Rect> for tg_geom_sys::tg_rect {
    #[inline]
    fn from(r: Rect) -> Self {
        Self {
            min: r.min.into(),
            max: r.max.into(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn p(x: f64, y: f64) -> Point {
        Point::new(x, y)
    }

    fn r(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Rect {
        Rect::from_coords(min_x, min_y, max_x, max_y)
    }

    #[test]
    fn test_rect_center() {
        assert_eq!(r(0.0, 0.0, 10.0, 10.0).center(), p(5.0, 5.0));
        assert_eq!(r(0.0, 0.0, 0.0, 0.0).center(), p(0.0, 0.0));
        assert_eq!(r(-10.0, -10.0, 10.0, 10.0).center(), p(0.0, 0.0));
    }

    #[test]
    fn test_rect_intersects_rect() {
        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, 0.0, 10.0, 10.0)));
        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(2.0, 2.0, 8.0, 8.0)));
        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(-1.0, 0.0, 10.0, 10.0)));
        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, -1.0, 10.0, 10.0)));
        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, 0.0, 11.0, 10.0)));
        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, 0.0, 10.0, 11.0)));
        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(11.0, 0.0, 21.0, 10.0)));
        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(0.0, 11.0, 10.0, 21.0)));
        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(11.0, 11.0, 21.0, 21.0)));
        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_rect(r(-11.0, -11.0, -1.0, -1.0)));
    }

    #[test]
    fn test_rect_intersects_point() {
        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_point(p(5.0, 5.0)));
        assert!(!r(0.0, 0.0, 10.0, 10.0).intersects_point(p(15.0, 15.0)));
        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_point(p(0.0, 0.0)));
        assert!(r(0.0, 0.0, 10.0, 10.0).intersects_point(p(5.0, 0.0)));
    }

    #[test]
    fn test_rect_new() {
        let rect = Rect::new(p(1.0, 2.0), p(3.0, 4.0));
        assert_eq!(rect.min, p(1.0, 2.0));
        assert_eq!(rect.max, p(3.0, 4.0));
    }

    #[test]
    fn test_rect_from_coords() {
        let rect = Rect::from_coords(1.0, 2.0, 3.0, 4.0);
        assert_eq!(rect.min, p(1.0, 2.0));
        assert_eq!(rect.max, p(3.0, 4.0));
    }

    #[test]
    fn test_rect_width_height_area() {
        let rect = r(1.0, 2.0, 5.0, 8.0);
        assert!((rect.width() - 4.0).abs() < 1e-10);
        assert!((rect.height() - 6.0).abs() < 1e-10);
        assert!((rect.area() - 24.0).abs() < 1e-10);

        let h_line = r(0.0, 5.0, 10.0, 5.0);
        assert_eq!(h_line.height(), 0.0);
        assert_eq!(h_line.area(), 0.0);

        let v_line = r(5.0, 0.0, 5.0, 10.0);
        assert_eq!(v_line.width(), 0.0);
        assert_eq!(v_line.area(), 0.0);

        let point_rect = r(5.0, 5.0, 5.0, 5.0);
        assert_eq!(point_rect.width(), 0.0);
        assert_eq!(point_rect.height(), 0.0);
        assert_eq!(point_rect.area(), 0.0);
    }

    #[test]
    fn test_rect_expand() {
        let r1 = r(0.0, 0.0, 1.0, 1.0);
        let r2 = r(2.0, 2.0, 3.0, 3.0);
        let expanded = r1.expand(r2);
        assert_eq!(expanded.min, p(0.0, 0.0));
        assert_eq!(expanded.max, p(3.0, 3.0));

        let r3 = r(-2.0, -2.0, -1.0, -1.0);
        let expanded2 = r1.expand(r3);
        assert_eq!(expanded2.min, p(-2.0, -2.0));
        assert_eq!(expanded2.max, p(1.0, 1.0));

        assert_eq!(r1.expand(r1), r1);
    }

    #[test]
    fn test_rect_expand_point() {
        let rect = r(0.0, 0.0, 1.0, 1.0);
        let expanded = rect.expand_point(p(5.0, 5.0));
        assert_eq!(expanded.max, p(5.0, 5.0));

        let r2 = r(0.0, 0.0, 10.0, 10.0);
        let expanded2 = r2.expand_point(p(5.0, 5.0));
        assert_eq!(expanded2, r2);

        let expanded3 = rect.expand_point(p(-5.0, -5.0));
        assert_eq!(expanded3.min, p(-5.0, -5.0));
    }

    #[test]
    fn test_rect_sys_roundtrip() {
        let rect = r(1.0, 2.0, 3.0, 4.0);
        let sys_rect: tg_geom_sys::tg_rect = rect.into();
        let back: Rect = sys_rect.into();
        assert_eq!(rect, back);
    }

    #[test]
    fn test_rect_eq() {
        assert_eq!(r(0.0, 0.0, 1.0, 1.0), r(0.0, 0.0, 1.0, 1.0));
        assert_ne!(r(0.0, 0.0, 1.0, 1.0), r(0.0, 0.0, 2.0, 2.0));
    }

    #[test]
    fn test_rect_debug() {
        let rect = r(1.0, 2.0, 3.0, 4.0);
        let debug_str = format!("{rect:?}");
        assert!(debug_str.contains("Rect"));
    }

    #[test]
    fn test_rect_negative() {
        let rect = r(-10.0, -10.0, -5.0, -5.0);
        assert_eq!(rect.center(), p(-7.5, -7.5));
        assert!(rect.intersects_point(p(-7.5, -7.5)));
        assert!(!rect.intersects_point(p(0.0, 0.0)));
    }
}