반응형

픽킹 ( 광선과 오브젝트 충돌판정 )


http://blog.naver.com/zeowing/90082924441


copyrightⓒ 김성수 [2002년 05월 21일]

BLACK&WHITE 같은 마우스를 만들려면 마우스의 2D위치를 3D공간좌표로 변환하는것이 필요해집니다. 등장하는 '신의손'이 실제 공간좌표를 갖기때문이죠. 이것은 상당히 효과만점인 반면, 물체를 Pick할줄만 알면 아주 간단하게 끝나는 아주 쉬운 기법입니다.
D3D의 Pick예제를 보면 물체를 picking하는 기법이 깔끔하게 나와있으니 그걸 그대로 사용하도록 하겠습니다.
2D마우스 좌표를 3D좌표로 변환하는 순서는 다음과 같습니다.
(1) 마우스 좌표를 가지고 공간상에 반직선을 만든다. (언프로젝션)
(2) 반직선과 교차하는 폴리곤중 가장 가까운것을 택한다. ( picking )
(3) 직선과 폴리곤의 교점을 구한다.( == 3D마우스 좌표 )

1. 언프로젝션
우선 마우스 좌표에 해당하는 3D점을 하나 구합니다.
( 아래식이 이해가 안되시는 분들은 '언프로젝션 ( Unprojection )' 과 그것에 링크된문서들을 꼭 읽어보세요. 안읽어보셔도 당장 써먹는데야 지장이 없겠지만....배워서 남주는거 아니니깐요.)

 
 m_pd3dDevice->GetViewport(&vp);
 m_pd3dDevice->GetTransform( D3DTS_PROJECTION, &matProj );
 GetCursorPos( &ptCursor );
 ScreenToClient( m_hWnd, &ptCursor );
 v.x = ((  (((ptCursor.x-vp.X)*2.0f/vp.Width ) - 1.0f)) - matProjection._31 ) / matProjection._11;
 v.y = ((- (((ptCursor.y-vp.Y)*2.0f/vp.Height) - 1.0f)) - matProjection._32 ) / matProjection._22;
 v.z =  1.0f;

언프로젝션하는 부분을 주의하세요. D3D예제에 있는대로 하시면 안될경우가 있습니다.
( 투영행렬에 클리핑행렬이 포함된것 같은경우 )
...
이제 카메라 좌표와 구해진 점을 잇는 반직선을 만들면 되죠,

        m_pd3dDevice->GetTransform( D3DTS_VIEW, &matView );
        D3DXMatrixInverse( &m, NULL, &matView );
        vPickRayDir.x  = v.x*m._11 + v.y*m._21 + v.z*m._31;
        vPickRayDir.y  = v.x*m._12 + v.y*m._22 + v.z*m._32;
        vPickRayDir.z  = v.x*m._13 + v.y*m._23 + v.z*m._33;
        vPickRayOrig.x = m._41;
        vPickRayOrig.y = m._42;
        vPickRayOrig.z = m._43;

2. Picking
D3D예제를 보시면 IntersectTriangle 함수를 사용해서 위에서 구한 반직선이 삼각형에 교차하는지를 검사해서 Pick를 수행하게 됩니다.
근디 우리가 해야할건...그중에서 카메라에 젤 가까운넘을 골라내는겁니다.
거럼 ...어떤넘이 카메라에 가장 가까운 넘인가???

BOOL CMyD3DApplication::IntersectTriangle( const D3DXVECTOR3& orig,
                                       const D3DXVECTOR3& dir, D3DXVECTOR3& v0,
                                       D3DXVECTOR3& v1, D3DXVECTOR3& v2,
                                       FLOAT* t, FLOAT* u, FLOAT* v )

IntersectTriangle함수의 6번째 인자(t) 는 출력용 인자입니다. 폴리곤과 반직선의 교점이 카메라에서 얼마나 떨어져 있는지를 알려주죠.
다시말하면....교차가 발견될때마다 출력된 t 값을 조사해서...젤 작은넘을 유지하도록 하면 됩니다.
t가 제일 작은 폴리곤이 실제로 picking된 폴리곤이죠.

3. 3D마우스 좌표계산
최종적인 3D좌표는 반직선과 IntersectTriangle함수의 출력으로 얻어진 t값으로부터 다음과 같이 구해집니다.

3D마우스 좌표 = vPickRayOrig + vPickRayDir * t ;



자...끝났습니다.

참....마우스가 화면상의 어떤것도 선택하고 있지 않을경우를 별도 처리 해야한다는 것을 잊지마세요.
아무것도 선택되지 않았다면 '카메라로부터의 기본거리'를 적용하는 것이 가장 자연스러울듯 합니다.

t = 기본거리 / D3DXVec3Length( vPickRayDir ) ;


이렇게 처리하면 되죠.

이거...응용할데가 많겠죠?
레벨편집기라던가, RPG, 전략시뮬같은데서도 유용하게 쓰일듯...

펌 http://www.gamza.net/









[출처] [Direct3D] 픽킹(Picking) - 지형 픽킹|작성자 dovcat

 

 

 

 

 

 

 

소스 코드

 

 FLOAT PointX;
 FLOAT PointY;

 // 뷰 포트 정보를 받아옵니다.
 D3DVIEWPORT9 ViewPortInfo;
 mDevice->GetViewport(&ViewPortInfo);

 // 프로젝션 매트릭스를 얻어옵니다.
 D3DXMATRIX Proj;
 mDevice->GetTransform(D3DTS_PROJECTION, &Proj);
  
 // 마우스 포인트를 투영창의 좌표로 변환합니다.
 PointX = (2.0f * LOWORD(lParam) / ViewPortInfo.Width - 1.0f) / Proj(0, 0);
 PointY = (-2.0f * HIWORD(lParam) / ViewPortInfo.Height + 1.0f) / Proj(1, 1);

 // 투영창의 좌표로 변환된 포인트를 가지고 광선을 생성합니다.
 RAY Ray;
 Ray.origin = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
 Ray.dir  = D3DXVECTOR3(PointX, PointY, 1.0f);

 // 뷰 매트릭스를 얻어옵니다.
 D3DXMATRIX View;
 mDevice->GetTransform(D3DTS_VIEW, &View);

 // 뷰 매트릭스의 역행렬을 구합니다.
 D3DXMATRIX ViewInverse;
 D3DXMatrixInverse(&ViewInverse, 0, &View);

 // 포인트를 월드 스페이스의 좌표로 변환합니다.
 D3DXVec3TransformCoord(&Ray.origin, &Ray.origin, &ViewInverse);
 D3DXVec3TransformNormal(&Ray.dir, &Ray.dir, &ViewInverse);
 D3DXVec3Normalize(&Ray.dir, &Ray.dir);

 

그리고 이 놈으로 광선과 지형(삼각형(face))의 교차를 검사하세요.

D3DXIntersectTri()

Direct3D에서 제공하는 제 입장에선 아주 강력한 함수로군요.

광선과 평면식의 조합을 어쩌구저쩌구 해서 교차 테스트릴 해도 마음대로 성공하지 못했는데...

이 녀석 하나로 코드도 줄고 테스트도 한 번에 되더군요... 흑흑....


 

 

한가지 덧붙이자면 월드까지 간 형태에서 로컬좌표까지 내려가 충돌판정을 할 수 있는데

 

이는 동일하게 광선을 로컬좌표까지 변환하여 충돌판정을 하면 된다

 

윈도우좌표부터 로컬좌표까지 어느좌표에서의 충돌판정이 좋다라기보다는 각 상황에 맞게 최적의 좌표에서 충돌판정하는 것이

 

최적이라 할 수 있겠다

 

 

 

IntersectTriangle 의 좀 더 자세한 설명을 감자닷넷에서 퍼왔다,

 

 

1. 삼각형 점포함 테스트
IntersectTriangle이 반직선과 삼각형의 교차판정을 하는방법은 2차원적으로 해석될수 있습니다.
반직선과 삼각형 평면의 교점이 삼각형에 포함되는지를 검사하는것이죠.
그런데 특별한 사정때문에 이전에 제가 썼던 글에 있는 점포함테스트를 사용하지 않습니다.
약간은 특이한 방법을 쓰게되죠.

그림을 보시면 벡터가 세개 나옵니다. OA,OB는 고정된 벡터이고 OP는 임의의 벡터입니다.
임의의 벡터 OP를 벡터 OA,OB를 가지고 표현해 봅시다.

OP = u * OA + v * OB   ( u,v는 임의의 실수 )

벡터의 영역문제에서 자주본 꼴이죠? (생각이안나는 사람은..정석을 뒤져봅시다)
u와 v에 제약을 가하면 점P는 특정한 영역을 표현하게 됩니다.
옴옴...뭘하려는지 알겠다고요?
그렇습니다. u,v에 어떤 조건을 붙여서 P가 삼각형 OAB영역을 표현하도록 하려는겁니다.
P가 OAB영역을 표현하기위한 조건은, 삼각형의 변(①②③)에대해 각각 하나씩 있습니다.
① :     0 ≤ u
② :     0 ≤ v
③ : u + v ≤ 1
이 조건들은 알아서덜 증명을... (①②번 조건은 말이 필요없고...③번조건은 직선 AB가 t*OA + (1-t)*OB 임을 이용)
위조건을 만족하는 u,v에 대해 P가 표현하는 영역은 삼각형 OAB입니다.
점 P가 삼각형 OAB에 포함되는지를 검사하는것은 u,v가 위조건을 만족하는지를 검사하는것과 같다는 얘기.
IntersectTriangle이 출력하는 u,v가 지금껏 이야기해온 u,v입니다. 반직선과 삼각형 평면의 교점이 P, 삼각형의 각 꼭지점이 O,A,B인 셈이죠. 따라서 반직선과 삼각형의 교차판정은 코드중에 다음처럼 나타납니다.

    if( *u < 0.0f || *u > det )
        return FALSE;
    if( *v < 0.0f || *u + *v > det )
        return FALSE;

Picking일 경우 IntersectTriangle이 출력하는 u,v를 사용하면 마우스의 3차원 위치와 카메라와의 거리를 쉽게 구할수 있습니다.

마우스의 3차원좌표 = v0 + u * ( v1 - v0 ) + v * ( v2 - v0 )
카메라와의 거리 = |마우스의 3차원좌표-orig|
 
 
 
p.s 좀더 자세한 설명은 다음에...

반응형

+ Recent posts