마우스 클릭을 통해 Object를 고를 때 사실 화면에서 보여지는 것은 2D로 변환된 Screen Space이다.
따라서 우린 Ray Casting을 사용해 레이저를 쏘듯 카메라에서 Object까지 가상의 선을 그어 Object와의 HIT여부를 검사한다.
Collider Component를 만들어 Ray cast 충돌 검사에 사용한다.
shared_ptr<GameObject> SceneManager::Pick(int32 screenX, int32 screenY)
{
shared_ptr<Camera> camera = GetActiveScene()->GetMainCamera();
float width = static_cast<float>(GEngine->GetWindow().width);
float height = static_cast<float>(GEngine->GetWindow().height);
Matrix projectionMatrix = camera->GetProjectionMatrix();
// ViewSpace에서 Picking 진행
float viewX = (+2.0f * screenX / width - 1.0f) / projectionMatrix(0, 0);
float viewY = (-2.0f * screenY / height + 1.0f) / projectionMatrix(1, 1);
Matrix viewMatrix = camera->GetViewMatrix();
Matrix viewMatrixInv = viewMatrix.Invert();
auto& gameObjects = GET_SINGLE(SceneManager)->GetActiveScene()->GetGameObjects();
float minDistance = FLT_MAX;
shared_ptr<GameObject> picked;
for (auto& gameObject : gameObjects)
{
if (gameObject->GetCollider() == nullptr)
continue;
// ViewSpace에서의 Ray 정의
Vec4 rayOrigin = Vec4(0.0f, 0.0f, 0.0f, 1.0f);
Vec4 rayDir = Vec4(viewX, viewY, 1.0f, 0.0f);
// WorldSpace에서의 Ray 정의
rayOrigin = XMVector3TransformCoord(rayOrigin, viewMatrixInv);
rayDir = XMVector3TransformNormal(rayDir, viewMatrixInv);
rayDir.Normalize();
// WorldSpace에서 연산
float distance = 0.f;
if (gameObject->GetCollider()->Intersects(rayOrigin, rayDir, OUT distance) == false)
continue;
if (distance < minDistance)
{
minDistance = distance;
picked = gameObject;
}
}
return picked;
}
스크린의 높이와 너비와 마우스의 클릭 위치를 가져와 ViewSpace에서 Picking을 한다.
World Space에서 충돌검사를 하기 위해 view Matrix의 역행렬을 이용해 View Space에서의 Ray를 WorldSpace로 변환.
WorldSpace에서 Ray와 Collider사이의 충돌 여부 계산.