rustbatch 0.4.0

purely game dewelopment crate that offers simple but powerfull 2D rendering and some fast solutions for game world bottle necks
Documentation
use crate::{Vect, Rect, Circ};
use crate::math::{Intersection, IntersectionPoints};

#[derive(Debug, Copy, Clone)]
pub struct Ray {
    pub o: Vect,
    pub v: Vect,
}

impl Ray {
    #[inline]
    pub fn new(ox: f32, oy: f32, vx: f32, vy: f32) -> Self {
        Self { o: Vect { x: ox, y: oy }, v: Vect { x: vx, y: vy } }
    }

    /// ray can be also expressed as line from a to b
    #[inline]
    pub fn from_points(a: Vect, b: Vect) -> Self {
        Self { o: a, v: b - a, }
    }

    /// returns whether point belongs to the INFINITE LINE Ray expresses
    #[inline]
    pub fn is_on(&self, pos: Vect) -> bool {
        self.v.y * pos.x - self.v.x * pos.y - self.v.y * self.o.x + self.v.x * self.o.y == 0.0
    }

    /// returns whether point in in AABB this raycast expresses
    #[inline]
    pub fn within(&self, pos: Vect) -> bool {
        let (mix, max) = if self.v.x > 0.0 {
            (self.o.x, self.o.x + self.v.x)
        } else {
            (self.o.x + self.v.x, self.o.x)
        };

        let (miy, may) = if self.v.y > 0.0 {
            (self.o.y, self.o.y + self.v.y)
        } else {
            (self.o.y + self.v.y, self.o.y)
        };

        pos.x >= mix && pos.x <= max && pos.y >= miy && pos.y <= may
    }

    /// combination of within and is_on, if it returns true point belongs to a Ray
    #[inline]
    pub fn contains(&self, pos: Vect) -> bool {
        self.within(pos) && self.is_on(pos)
    }

    /// projects x coordinate to y coordinate. This is mainly an helper for intersection calculations
    #[inline]
    pub fn prj_x(&self, x: f32) -> f32 {
        (self.v.y * x - self.v.y * self.o.x + self.v.x * self.o.y) / self.v.x
    }

    /// opposite of prj_x
    #[inline]
    pub fn prj_y(&self, y: f32) -> f32 {
        (self.v.x * y + self.v.y * self.o.x - self.v.x * self.o.y) / self.v.y
    }

    #[inline]
    pub fn project_to_grid(&self, grid_resolution: Vect) -> Self {
        Self { o: self.o / grid_resolution, v: self.v / grid_resolution }
    }

    /// returns ray in opposite direction
    #[inline]
    pub fn inverted(&self) -> Self {
        Self { o: self.o + self.v, v: self.v.inverted() }
    }
}

impl Intersection<Ray> for Ray {
    #[inline]
    fn intersects(&self, o: &Ray) -> bool {
        self.intersects_points(o)[0].is_some()
    }
}

impl Intersection<Rect> for Ray {
    #[inline]
    fn intersects(&self, o: &Rect) -> bool {
        let res = self.intersects_points(o);
        res[0].is_some() || res[1].is_some()
    }
}

impl Intersection<Circ> for Ray {
    #[inline]
    fn intersects(&self, o: &Circ) -> bool {
        let res = self.intersects_points(o);
        res[0].is_some() || res[1].is_some()
    }
}

impl IntersectionPoints<Ray> for Ray {
    #[inline]
    fn intersects_points(&self, o: &Ray) -> [Option<Vect>; 2] {
        let a = self.v.y * self.o.x * o.v.x - self.v.x * self.o.y * o.v.x;
        let b = o.v.y * o.o.x * self.v.x - o.v.x * o.o.y * self.v.x;
        let c = o.v.y * self.v.x - self.v.y * o.v.x;

        if c == 0.0 {
            return [None, None]
        }

        let x = (b - a) / c;
        let res = Vect::new(x, self.prj_x(x));
        if o.within(res) {
            return [Some(res), None]
        }

        [None, Some(res)]
    }
}

impl IntersectionPoints<Rect> for Ray {
    #[inline]
    fn intersects_points(&self, o: &Rect) -> [Option<Vect>; 2] {
        let mut points = [None; 2];
        let mut i = 0;
        for ray in o.to_rays().iter() {
            let res = self.intersects_points(ray)[0];
            if res.is_some() {
                points[i] = res;
                i += 1;
            }
        }
        points
    }
}

impl IntersectionPoints<Circ> for Ray {
    #[inline]
    fn intersects_points(&self, o: &Circ) -> [Option<Vect>; 2] {
        let xv2 = self.v.x * self.v.x;
        let yv2 = self.v.y * self.v.y;

        let a = yv2 + xv2;
        let b = 2.0 * (
            self.v.y * self.v.x * self.o.y +
            self.v.y * o.c.y * self.v.x -
            xv2 * o.c.x -
            yv2 * self.o.x
        );
        let c = xv2 * ( self.o.y.powi(2) + o.c.y.powi(2) - o.r.powi(2) + o.c.x.powi(2) ) +
            yv2 * self.o.x.powi(2) +
            2.0 * self.v.y * self.o.x * self.v.x * (self.o.y - o.c.y) +
            2.0 * self.o.y * o.c.y * xv2;

        let mut dis = b * b - 4.0 * c * a;
        if dis < 0.0 {
            return [None, None];
        } else if dis == 0.0 {
            let x = -b / (2.0 * a);
            let v = Vect::new(x, self.prj_x(x));
            return [ if self.within(v) {Some(v)} else {None}, None]
        }

        dis = dis.sqrt();
        let x1 = (-b - dis)/(2.0 * a);
        let x2 = (-b + dis)/(2.0 * a);

        let v1 = Vect::new(x1, self.prj_x(x1));
        let v2 = Vect::new(x2, self.prj_x(x2));

        [if self.within(v1) {Some(v1)} else {None}, if self.within(v2) {Some(v2)} else {None}]
    }
}

#[cfg(test)]
mod tests {
    use super::Ray;
    use crate::{ Circ ,Vect, IntersectionPoints};
    use std::fs::read;

    #[test]
    fn intersect_ray_test() {
        let base = ray!(0, 0, 100, 0);
        assert_eq!([Some(vect!(0, 0)), None], base.intersects_points(&ray!(0, 0, 0, 1)));
        assert_eq!([Some(vect!(4, 0)), None], base.intersects_points(&ray!(4, 0, 0, 1)));
        assert_eq!([None, Some(vect!(4, 0))], base.intersects_points(&ray!(4, 2, 0, 1)));
        assert_eq!([None, None], base.intersects_points(&ray!(4, 2, 1, 0)));
    }

    #[test]
    fn intersect_circle_test() {
        let base = ray!(-5, 0, 10, 0);
        assert_eq!([Some(vect!(-4.0, 0.0)), Some(vect!(4.0, 0.0))] ,base.intersects_points(&circ!(0, 0; 4)));
        assert_eq!([Some(vect!(0.0, 0.0)), None] ,base.intersects_points(&circ!(0, 4; 4)));
        assert_eq!([None, None] ,base.intersects_points(&circ!(0, 5; 4)));
        assert_eq!([None, None] ,base.intersects_points(&circ!(0, 0; 10)));
    }
}