https://en.wikipedia.org/wiki/Slab_method
참고블로그 : https://ingun37.wordpress.com/2013/06/20/선육면체-충돌처리-slabs-methodraybox-intersection/

초록선은 충돌이 생기는 케이스, 빨간선은 충돌이 생기지 않는 케이스
x_min 과 x_max → x축에 평행한 두 개의 면y_min 과 y_max → y축에 평행한 두 개의 면
$t_{enter}$는 $t_{x_{min}}$ 이고, $t_{exit}$는 $t_{y_{max}}$인 상황
bool RaycastAABB(const Ray& ray, const AABBCollider* aabb, float maxDistance, float& outDistance) {
// AABB의 좌상단 기준 좌표와 크기를 이용해 최소/최대 좌표 계산
Vector2<float> aabbMin(static_cast<float>(aabb->GetX()), static_cast<float>(aabb->GetY()));
Vector2<float> aabbMax(static_cast<float>(aabb->GetX() + aabb->GetWidth()),
static_cast<float>(aabb->GetY() + aabb->GetHeight()));
// 레이 원점이 AABB 내부에 있으면 즉시 충돌로 처리
if (ray.origin.x >= aabbMin.x && ray.origin.x <= aabbMax.x &&
ray.origin.y >= aabbMin.y && ray.origin.y <= aabbMax.y) {
outDistance = 0.0f;
return true;
}
/*
Slab Test
핵심 개념: 각 축(x, y)에서 박스의 최소/최대 평면과 광선이 교차하는 매개변수 t를 구하는 것
직선의 매개변수 방정식 : P(t) = O + tD
- P(t): 광선 위의 한 점
- O: 광선의 시작점 (ray.origin)
- D: 광선의 방향 벡터 (ray.direction)
- t: 광선의 진행 매개변수 (ray가 진행하는 거리 비율)
각 축의 AABB 평면과 교차하는 t값을 구하기 (위의 수식을 t에 대해 정리)
tmin = (AABB_Min - ray.origin) / ray.direction
tmax = (AABB_Max - ray.origin) / ray.direction
tmin과 tmax는 교차 구간을 정의하며, 모든 축에서 공통 구간이 존재하면 충돌.
변수명에서 t는 위 수식에서의 매개변수 t를 의미.
*/
float txmin, txmax, tymin, tymax;
// x 축 처리: Slab Test를 통해 광선과 AABB의 x축 평면 교차 여부를 계산 (Kay-Kajiya test)
// 방향 벡터의 x 성분이 0인 경우, slab 교차 여부를 직접 계산할 수 없으므로 별도로 처리
if (ray.direction.x != 0.0f) {
// P(t) = O + tD 의 t값을 유도
// t = ( P(t) - O ) / D 수식을 그대로 적은 것
txmin = (aabbMin.x - ray.origin.x) / ray.direction.x; // AABB Box의 x의 최솟값일 때의 t값
txmax = (aabbMax.x - ray.origin.x) / ray.direction.x; // AABB Box의 y의 최솟값일 때의 t값
// txmin > txmax인 경우는 Ray가 우측에서 오는 경우
// swap에 의해 뒷 부분의 t_enter는 항상 왼쪽에서 시작하는 것으로 바꿔서 고려
if (txmin > txmax)
{
std::swap(txmin, txmax);
}
}
else {
// x축 성분이 0인데 시작점의 x가 AABB 박스의 x범위를 벗어난다면 false
// 왜냐하면 y축성분이 얼마든지 간에 평행하게 됨
if (ray.origin.x < aabbMin.x || ray.origin.x > aabbMax.x)
return false;
// 값을 각각 음수 무한대, 양수 무한대로 두어 해당 값은 고려하지 않도록 함
// 즉 해당 축에 대해서는 Slab 교차여부를 판정하지 않음
txmin = -FLT_MAX;
txmax = FLT_MAX;
}
// y 축 처리
if (ray.direction.y != 0.0f) {
// P(t) = O + tD 의 t값을 유도
// t = ( P(t) - O ) / D 수식을 그대로 적은 것
tymin = (aabbMin.y - ray.origin.y) / ray.direction.y; // AABB Box의 y의 최솟값일 때의 t값
tymax = (aabbMax.y - ray.origin.y) / ray.direction.y; // AABB Box의 y의 최댓값일 때의 t값
// tymin > tymax인 경우는 Ray가 위쪽에서 오는 경우
// swap에 의해 뒷 부분의 t_enter는 항상 아래쪽에서 시작하는 것으로 바꿔서 고려
if (tymin > tymax) {
std::swap(tymin, tymax);
}
}
else {
// y축 성분이 0인데 시작점의 y가 AABB 박스 범위의 y를 벗어난다면 false
// 왜냐하면 x축성분이 얼마든지 간에 평행하게 됨
if (ray.origin.y < aabbMin.y || ray.origin.y > aabbMax.y)
return false;
// 값을 각각 음수 무한대, 양수 무한대로 두어 해당 값은 고려하지 않도록 함
// 즉 해당 축에 대해서는 Slab 교차여부를 판정하지 않음
tymin = -FLT_MAX;
tymax = FLT_MAX;
}
// 두 축의 교차 구간이 겹치지 않으면 충돌 없음
if ((txmin > tymax) || (tymin > txmax))
return false;
// 위에서 swap 처리를 하였기에 아래 로직이 항상 성립
// tmin: AABB에 진입하는 순간의 매개변수 t값 (즉, AABB를 처음으로 만나는 지점)
// tmax: AABB에서 이탈하는 순간의 매개변수 t값 (즉, AABB를 마지막으로 만나는 지점)
float t_min, t_max;
// 최종 tmin, tmax 결정
t_min = std::max<float>(txmin, tymin);
t_max = std::min<float>(txmax, tymax);
// Ray가 AABB 뒤쪽에서 시작하는 경우
if (t_max < 0)
return false;
// 충돌이 maxDistance 이내인지 확인
if (t_min <= maxDistance) {
outDistance = t_min;
return true;
}
return false;
}