pybevy_math 0.2.1

Math types (Vec2, Vec3, Vec4, Quat, Mat3, Mat4) for PyBevy
Documentation
use bevy::math::{Rect, Vec2};
use pyo3::{exceptions::PyValueError, prelude::*};

use crate::vec2::PyVec2;

#[pyclass(name = "Rect", frozen)]
#[derive(Debug, Clone)]
pub struct PyRect {
    #[pyo3(get)]
    min: PyVec2,
    #[pyo3(get)]
    max: PyVec2,
}

#[pymethods]
impl PyRect {
    #[new]
    pub fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
        let rect = Rect::new(x0, y0, x1, y1);
        Self {
            min: rect.min.into(),
            max: rect.max.into(),
        }
    }

    #[staticmethod]
    pub fn from_corners(p0: PyVec2, p1: PyVec2) -> Self {
        let rect = Rect::from_corners(p0.into(), p1.into());
        Self {
            min: rect.min.into(),
            max: rect.max.into(),
        }
    }

    #[staticmethod]
    pub fn from_center_size(origin: PyVec2, size: PyVec2) -> Self {
        let rect = Rect::from_center_size(origin.into(), size.into());
        Self {
            min: rect.min.into(),
            max: rect.max.into(),
        }
    }

    #[staticmethod]
    pub fn from_center_half_size(origin: PyVec2, half_size: PyVec2) -> Self {
        let rect = Rect::from_center_half_size(origin.into(), half_size.into());
        Self {
            min: rect.min.into(),
            max: rect.max.into(),
        }
    }

    pub fn center(&self) -> PyVec2 {
        self.to_bevy().center().into()
    }

    pub fn size(&self) -> PyVec2 {
        self.to_bevy().size().into()
    }

    pub fn half_size(&self) -> PyVec2 {
        self.to_bevy().half_size().into()
    }

    pub fn width(&self) -> f32 {
        self.to_bevy().width()
    }

    pub fn height(&self) -> f32 {
        self.to_bevy().height()
    }

    pub fn contains(&self, point: PyVec2) -> bool {
        self.to_bevy().contains(point.into())
    }

    pub fn is_empty(&self) -> bool {
        self.to_bevy().is_empty()
    }

    pub fn intersect(&self, other: &PyRect) -> PyResult<PyRect> {
        let result = self.to_bevy().intersect(other.to_bevy());
        if result.is_empty() {
            Err(PyValueError::new_err("Rectangles do not intersect"))
        } else {
            Ok(Self {
                min: result.min.into(),
                max: result.max.into(),
            })
        }
    }

    pub fn union(&self, other: &PyRect) -> PyRect {
        let result = self.to_bevy().union(other.to_bevy());
        Self {
            min: result.min.into(),
            max: result.max.into(),
        }
    }

    pub fn union_point(&self, point: PyVec2) -> PyRect {
        let result = self.to_bevy().union_point(point.into());
        Self {
            min: result.min.into(),
            max: result.max.into(),
        }
    }

    pub fn inflate(&self, expansion: f32) -> PyRect {
        let result = self.to_bevy().inflate(expansion);
        Self {
            min: result.min.into(),
            max: result.max.into(),
        }
    }

    pub fn __repr__(&self) -> String {
        let min_vec: Vec2 = (&self.min).into();
        let max_vec: Vec2 = (&self.max).into();
        format!(
            "Rect(min=Vec2({}, {}), max=Vec2({}, {}))",
            min_vec.x, min_vec.y, max_vec.x, max_vec.y
        )
    }
}

impl PartialEq for PyRect {
    fn eq(&self, other: &Self) -> bool {
        self.to_bevy() == other.to_bevy()
    }
}

impl PyRect {
    fn to_bevy(&self) -> Rect {
        Rect {
            min: (&self.min).into(),
            max: (&self.max).into(),
        }
    }
}

impl From<Rect> for PyRect {
    fn from(rect: Rect) -> Self {
        Self {
            min: rect.min.into(),
            max: rect.max.into(),
        }
    }
}

impl From<PyRect> for Rect {
    fn from(rect: PyRect) -> Self {
        Rect {
            min: rect.min.into(),
            max: rect.max.into(),
        }
    }
}

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

    #[test]
    fn test_rect_creation() {
        let rect = PyRect::new(0.0, 0.0, 10.0, 20.0);
        assert_eq!(rect.width(), 10.0);
        assert_eq!(rect.height(), 20.0);
    }

    #[test]
    fn test_rect_from_corners() {
        let p0 = PyVec2::new(0.0, 0.0);
        let p1 = PyVec2::new(10.0, 20.0);
        let rect = PyRect::from_corners(p0, p1);
        assert_eq!(rect.width(), 10.0);
        assert_eq!(rect.height(), 20.0);
    }

    #[test]
    fn test_rect_from_center_size() {
        let center = PyVec2::new(5.0, 10.0);
        let size = PyVec2::new(10.0, 20.0);
        let rect = PyRect::from_center_size(center.clone(), size.clone());
        assert_eq!(rect.center(), center);
        assert_eq!(rect.size(), size);
    }

    #[test]
    fn test_rect_contains() {
        let rect = PyRect::new(0.0, 0.0, 10.0, 20.0);
        assert!(rect.contains(PyVec2::new(5.0, 10.0)));
        assert!(!rect.contains(PyVec2::new(15.0, 10.0)));
    }

    #[test]
    fn test_rect_intersect() {
        let rect1 = PyRect::new(0.0, 0.0, 10.0, 10.0);
        let rect2 = PyRect::new(5.0, 5.0, 15.0, 15.0);
        let intersection = rect1.intersect(&rect2).unwrap();
        assert_eq!(intersection.width(), 5.0);
        assert_eq!(intersection.height(), 5.0);
    }

    #[test]
    fn test_rect_union() {
        let rect1 = PyRect::new(0.0, 0.0, 10.0, 10.0);
        let rect2 = PyRect::new(5.0, 5.0, 15.0, 15.0);
        let union = rect1.union(&rect2);
        let min_vec: Vec2 = (&union.min).into();
        let max_vec: Vec2 = (&union.max).into();
        assert_eq!(min_vec.x, 0.0);
        assert_eq!(min_vec.y, 0.0);
        assert_eq!(max_vec.x, 15.0);
        assert_eq!(max_vec.y, 15.0);
    }
}