프로그래밍 공부
작성일
2023. 11. 6. 16:45
작성자
WDmil
728x90

3D 상에서의 Box to Box Collider연산을 구상해보자.

 

일반적으로 2D상에서의 Collider는, AABBOBB의 연산을 사용한다.

 

그러나, AABB연산의 경우, 회전하지 않는 사각형 의 충돌연산에 사용하기 때문에, 회전체의 충돌연산을 진행할 수 없다는 단점이 있다.

 

3D에서는 AABB연산이 가능한 경우가 거의 없음으로 고려하지 않는다.


3D상에서의 OBB연산

 

평면상에서는 OBB연산을 단 한번만 진행해주면 되지만, 입체상 에서는 z축이 한개 추가된다.

 

그럼으로 정6면체 기준으로 6면의 충돌범위가 생김으로.

 

각 면마다 다른 면이 충돌하였는지 검사해야 할것이다.

 

여기에 필요한 데이터는 다음과 같다.

	struct ObbDesc
	{
		Vector3 pos; // 박스의 중간위치
		Vector3 axis[3]; // xyz 3개의 축
		Vector3 halfSize; // 크기의 반이 필요하다.
	};

박스의 위치, up, front, left를 계산할 사이즈, 그리고 축이다.

 

즉, 3차원 좌표도마다 평면충돌연산 * 각 사각형끼리의 충돌연산 횟수 임으로. 연산횟수가, 15번 진행되어야 한다.

 

각 축에대해 투영거리를 계산하는 OBB계산함수는 다음과 같다.

bool BoxCollider::IsSeperateAxis(const Vector3 D, const Vector3 axis, const ObbDesc& box1, const ObbDesc& box2)
{
    // 각 축에 대하여 투영된 거리를 계산하여. 모든 축에대해 OBB를 계산하는 함수이다.
    // 1. 축에 대한 거리를 계산합니다.
    float distance = abs(Vector3::Dot(D, axis));

    // 2. 각 박스의 축을 따라서 해당 축에 투영된 크기를 계산합니다.
    float a = 0.0f; // 첫 번째 박스의 투영된 크기 를 담을 float
    float b = 0.0f; // 두 번째 박스의 투영된 크기 를 담을 float

    // 3. 각 박스의 3개 축에 대해 반복합니다.
    FOR(3)
    {
        // 4. 현재 축에 해당하는 박스의 반향 크기를 해당 축과의 내적으로 계산합니다.
        Vector3 temp = box1.axis[i] * box1.halfSize[i];
        a += abs(Vector3::Dot(temp, axis));

        // 5. 두 번째 박스에 대해서도 동일한 작업을 수행합니다.
        temp = box2.axis[i] * box2.halfSize[i];
        b += abs(Vector3::Dot(temp, axis));
    }

    // 6. 거리가 두 박스의 투영된 크기 합보다 큰 경우, 축에 대한 간격이 있으므로 두 박스는 겹치지 않습니다.
    return distance > (a + b);
}

 

위 코드에서 볼 수 있듯이, 각 축에대한 투영거리를 계산하여 모든축에 대해 OBB를 계산하는 함수이다.

 

각 축에대해 내적으로 box1과 box2에 대해 OBB를 계산하고, 결과를 비교한다.

 

각 축에대해 사각형을 평면으로 생각하고 연산하는걸 볼 수 있다.


 

bool BoxCollider::IsBoxCollision(BoxCollider* collider)
{
    ObbDesc box1, box2; // 두 개의 OBB(축 정렬된 바운딩 박스)를 선언합니다.

    GetObb(box1); // 첫 번째 박스의 정보를 얻어옵니다.
    collider->GetObb(box2); // 두 번째 박스의 정보를 얻어옵니다.

    Vector3 D = box2.pos - box1.pos; // 두 박스 중심 사이의 거리 벡터 D를 계산합니다.

    FOR(3) // i를 0부터 2까지 반복합니다. (FOR은 반복문을 나타냅니다.)
    {
        // 각 x,y,z축 기준의 axis로 바라보았을 때 두개의 사각형 사이의 거리를 계산하고 그걸 기준으로 충돌검사를 진행한다.
        if (IsSeperateAxis(D, box1.axis[i], box1, box2)) return false; // 충돌 축이 있는지 검사합니다.
        if (IsSeperateAxis(D, box2.axis[i], box1, box2)) return false; // 두 박스 간의 충돌 축이 있는지 다시 검사합니다.
    }

    // 축이 같은것이 한개라도 존재한다면, (벡터가 수직할때), 충돌축이 없을경우, 충돌하지 않는다
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (box1.axis[i] == box2.axis[j]) return true; // 두 박스의 축 중 어느 것이 같은지 확인하여 충돌 여부를 결정합니다.
        }
    }

    // 겹치는 축이 하나도 없다. 그러면 외적을 다시 계산한다.
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            Vector3 cross = Vector3::Cross(box1.axis[i], box2.axis[j]); // 두 박스 축 간의 외적을 계산합니다.
            if (IsSeperateAxis(D, cross, box1, box2)) return false; // 외적 축으로 다시 충돌 검사를 수행합니다.
        }
    }

    return true; // 결과가 나타나지 않을경우 충돌로 확인한다.
}

BoxCollision을 계산하는 함수이다.

 

각 BOX의 정보를 받아와서, 두 박스 중심 사이의 벡터를 계산하고.

 

모든 모서리에 대해 collision을 계산한다.

 

과정상. 연산이 필요하지 않을경우 false를 반환하여 겹치지 않는다고 판단한다.

 

그 무엇도 확인되지 않을경우. 충돌되지 않을경우에 포함되지 않음으로 충돌했다고 판단한다.

 

 

충돌을 확인할 수 있다.

 


Box to Ray 충돌

게임을 플레이 하면 당연하게 사용해야 하는것은. 마우스포인터와 어떠한 객체가 충돌했을 때.

 

색이 변하거나 클릭하면 변화가 이루어지는것이다.

 

어떠한 객체를 클릭하거나 어떠한 객체에 마우스가 올라갈경우. 자세한 상태가 표시되거나 하는 상호작용 시스템을 넣어야 할경우에 사용되는 충돌방식이다.

 

이것을 Slab Method라고 하는데,

 

선분과 Box간의 충돌연산을 말한다.

bool BoxCollider::IsRayCollision(IN const Ray& ray, OUT Contact* contact)
{
    ObbDesc box;
    GetObb(box); // 박스의 정보를 얻어옵니다.

    // Obb의 경계를 나타낸다. 레이의 충돌를 확인하는데 사용된다.
    Vector3 min = box.halfSize * -1.0f; // 박스의 최소 꼭짓점을 계산합니다.
    Vector3 max = box.halfSize; // 박스의 최대 꼭짓점을 계산합니다.

    // OBB와 레이의 상대위치를 나타냄
    Vector3 delta = box.pos - ray.pos; // 레이의 출발점과 박스 중심 사이의 벡터를 계산합니다.
    Vector3 D = ray.dir.GetNormalized(); // 레이의 방향을 단위 벡터로 정규화합니다. (레이 방향이 0벡터이면 에러가 발생할 수 있음.)

    // 후속 계산시 업데이트하며, 충돌시점 추적하는데 사용함.
    float tMin = 0.0f; // 충돌 시점의 최소값을 초기화합니다.
    float tMax = FLT_MAX; // 충돌 시점의 최대값을 FLT_MAX로 초기화합니다. (실수의 최대값)

    FOR(3) // 0부터 2까지 반복합니다.
    {
        Vector3 axis = box.axis[i]; // 박스의 로컬 축을 가져옵니다.
        float e = Vector3::Dot(axis, delta); // 박스의 축과 delta 벡터의 내적을 계산합니다.
        float f = Vector3::Dot(axis, D); // 박스의 축과 레이의 방향 벡터의 내적을 계산합니다.

        // 위에서 밑으로 수직으로 바라보았을때 해당된다.
        if (MATH->NearlyEqual(e, f)) // e와 f가 거의 같으면 (레이와 축이 평행할 때) 
        {
            if (min[i] > e || max[i] < e) return false; // 박스와 레이가 평행하면서 박스의 최소 또는 최대값을 벗어나면 충돌이 없습니다.
        }

        // 시작지점에서 교차된다.
        else // e와 f가 다른 경우 (레이와 축이 교차할 때)
        {
            float t1 = (e + min[i]) / f; // t1과 t2는 레이가 박스와 교차하는 지점의 보간값입니다.
            float t2 = (e + max[i]) / f;

            if (t1 > t2) swap(t1, t2); // t1이 항상 작은 값이 되도록 정렬합니다.

            if (t2 < tMax) tMax = t2; // 최대 충돌 시점을 업데이트합니다.
            if (t1 > tMin) tMin = t1; // 최소 충돌 시점을 업데이트합니다.

            if (tMin > tMax) return false; // 최소 충돌 시점이 최대 충돌 시점보다 크면 레이와 박스는 겹치지 않습니다.
        }
    }

    // 충돌 최외각지점을 contact의 hitPoint에 저장한다.
    if (contact != nullptr)
    {
        contact->distance = tMin; // 충돌 시점의 거리를 저장합니다.
        contact->hitPoint = (Vector3)ray.pos + ray.dir * tMin; // 충돌 지점을 계산하여 저장합니다.
    }

    return true; // 레이와 박스가 교차하면 true를 반환합니다.
}

위와같은 코드로 정의할 수 있다.

Ray를 쏘았을 때, 해당 Ray가 Box와 교차하는지, 확인하고,

교차지점을 얻는다.

 

각각 t1은, xyz축에 대한 최소값을 의미하고,

e는 현재축과 RAY의 시작점과 OBB중심 사이의 거리. ( OBB충돌축 에 대한 투영) 을 나타낸다. OBB와 RAY의 상대위치를 표현한다.

 

즉, t1은 e에서 시작된 min[i]까지의 거리를 f로 나누어 스칼라값을 얻어낸것이다.

t2또한 max[i]만 다르지 나머지는 같은 의미이다.

 

그 스칼라값을 t1이 항상 작은값이 되도록 정렬한다 ( 연산의 최적화를 위해서 스왑한다. 최솟값이 x인지 y인지 판별해야하기 때문)

그렇게 하고, tMin과 tMax를 업데이트하고. 레이와 Box의 충돌연산을 구성한다.

이미지상에는 보이지 않지만, 마우스를 올리면 사각형이 빨간색으로 변한다.

728x90