summary refs log tree commit diff
path: root/src/Math/Ray.hpp
blob: 4050a02d38b033ae8ccba601e2aacf2e44cbd0fa (plain)
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
#pragma once

#include "../Common/Lambda.hpp"
#include "AABB.hpp"
#include "Vector.hpp"

struct Ray {
    struct RaycastResult {
        Bool hit = false;
        Vec3 point;
        Vec3 normal;
    };

    // References:
    // [1]: https://gdbooks.gitbooks.io/3dcollisions/content/Chapter3/raycast_aabb.html
    // [2]: https://tavianator.com/2015/ray_box_nan.html
    RaycastResult cast(AABB box, Real max_distance = 0) const {
        if (direction.is_zero()) return {};

        RaycastResult hit{ true };

        // `t` is the time at which the ray intersects a plane of the AABB.
        // A single vector defines 3 planes, one for each axis.
        // Our AABB ends up having 6 planes, taken from the `from` and `to` points,
        // hence the 6 `t`s.
        Vec3 t_from = (box.min - origin) / direction;
        Vec3 t_to = (box.max - origin) / direction;

        // NOTE: We have to use std::fmin and std::fmax here, since
        // std::min and std::max are not IEEE 754 compliant. [2]
        Vec3 smaller_t_values = t_from.zip(t_to, LAMBDA(std::fmin, 2));
        Vec3 bigger_t_values = t_from.zip(t_to, LAMBDA(std::fmax, 2));

        Real biggest_min_t = smaller_t_values.reduce(LAMBDA(std::fmax, 2));
        Real smallest_max_t = bigger_t_values.reduce(LAMBDA(std::fmin, 2));

        // The normal of the collision is a normal of the plane which is intersected
        // by the ray at the biggest smaller `t` value.
        // Since a plane always has two normals, we know which one to pick by looking
        // at the direction of the ray.
        for (U8 a = 0; a < 3; a++) {
            if (Math::floats_equal(smaller_t_values[a], biggest_min_t)) {
                hit.normal[a] = Math::sign(direction[a]);
                break;
            }
        }

        // If the smallest `t` is negative, the ray is pointing away from the AABB.
        // If the biggest `t` is smaller than the smallest `t`, the ray is missing the AABB,
        // since that means we do not reach all the planes defined by `from` before we reach
        // a plane defined by `to`, which only happens if there is no intersection.
        if (smallest_max_t < 0 || biggest_min_t > smallest_max_t) return {};

        // Get the intersection point from the collision time `t`
        // with the formula `p = o + dt`.
        hit.point = origin + direction * biggest_min_t;
        if (max_distance > 0 && (hit.point - origin).magnitude() > max_distance)
            return {};

        return hit;
    }

    Vec3 origin;
    Vec3 direction;
};