1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use super::intersection_aabb_aabb;
use glam::Vec2;

#[derive(Debug, Clone)]
pub struct Ray {
    pub origin: Vec2,
    pub dir: Vec2,
    pub toi: f32,
}

#[derive(Debug, Clone)]
pub struct Raycast {
    pub toi: f32,
    pub normal: Vec2,
}

// ported https://github.com/RandyGaul/cute_headers/blob/master/cute_c2.h#L1427

#[allow(clippy::float_cmp)]
pub fn contact_ray_aabb(ray: &Ray, aabb_pos: Vec2, aabb_half_exts: Vec2) -> Option<Raycast> {
    // test if collision with ray is possible by simplifying to aabb2aabb test

    let dest = ray.origin + ray.dir * ray.toi;
    let ray_box_min = ray.origin.min(dest);
    let ray_box_max = ray.origin.max(dest);
    let ray_box_dim = ray_box_max - ray_box_min;

    if !intersection_aabb_aabb(
        ray_box_min + ray_box_dim * 0.5,
        ray_box_dim * 0.5,
        aabb_pos,
        aabb_half_exts,
    ) {
        return None;
    }

    let ab = dest - ray.origin;
    let n = clockwise_90_turn(ab);
    let abs_n = n.abs();
    let d = n.dot(ray.origin - aabb_pos).abs() - abs_n.dot(aabb_half_exts);
    if d > 0. {
        return None;
    }

    let t_left = ray_plane_1d(
        Vec2::new(ray.origin.x(), dest.x()),
        -1.,
        aabb_pos.x() - aabb_half_exts.x(),
    );
    let t_right = ray_plane_1d(
        Vec2::new(ray.origin.x(), dest.x()),
        1.,
        aabb_pos.x() + aabb_half_exts.x(),
    );
    let t_top = ray_plane_1d(
        Vec2::new(ray.origin.y(), dest.y()),
        -1.,
        aabb_pos.y() - aabb_half_exts.y(),
    );
    let t_bottom = ray_plane_1d(
        Vec2::new(ray.origin.y(), dest.y()),
        1.,
        aabb_pos.y() + aabb_half_exts.y(),
    );

    // Calculate hit predicate

    let hit_left = t_left < 1.;
    let hit_right = t_right < 1.;
    let hit_top = t_top < 1.;
    let hit_bottom = t_bottom < 1.;
    let hit = hit_left | hit_right | hit_top | hit_bottom;

    if hit {
        // Remap t's within 0-1 range, where >= 1 is treated as 0

        let t_left = hit_left as u8 as f32 * t_left;
        let t_right = hit_right as u8 as f32 * t_right;
        let t_top = hit_top as u8 as f32 * t_top;
        let t_bottom = hit_bottom as u8 as f32 * t_bottom;

        let t_max = t_left.max(t_right).max(t_top).max(t_bottom);
        let toi = ray.toi * t_max;

        // result of multiple calls to `max` so there shouldn't be any issue with floating point error

        let normal = if t_left == t_max {
            Vec2::unit_x() * -1.
        } else if t_right == t_max {
            Vec2::unit_x()
        } else if t_top == t_max {
            Vec2::unit_y() * -1.
        } else {
            Vec2::unit_y()
        };
        Some(Raycast { toi, normal })
    } else {
        None
    }
}

fn clockwise_90_turn(vec: Vec2) -> Vec2 {
    Vec2::new(-vec.y(), vec.x())
}

// both ray and plane are one dimensional

// returns value between 0 to 1 describing time of impact, 1 means no impact

fn ray_plane_1d(ray_1d: Vec2, normal: f32, plane_1d: f32) -> f32 {
    let d = (ray_1d - Vec2::splat(plane_1d)) * normal;

    ray_plane_1d_time(d.x(), d.y())
}

fn ray_plane_1d_time(da: f32, db: f32) -> f32 {
    // Ray started behind plane

    if da < 0. {
        0.
    }
    // Ray doesn't intersect the plane

    else if da * db >= 0. {
        1.
    }
    // Ray is too tiny

    else if (da - db) < f32::EPSILON {
        0.
    } else {
        da / (da - db)
    }
}