#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; };