|
|
|
|
1 Picking #2004년 7월 22일
- 맵에디터에서 마우스 입력을 위해서 가장 기초적인 Picking을 구현!
카메라가 바라보는 방향이 화면에 나오고 있을때 마우스의 위치로 화면안쪽으로 향하는 반직선을 만들고 그 반직선과 교차하는 폴리곤을 선택하는 것을 Picking 이라고 한다.
이후 설명하는 대부분의 것이 제가 잘 모르는 관계로 틀린 내용일수 있습니다
즉 간단히 마우스로 가져다가 댔을때 그 바로 아래있는 폴리곤이나 오브젝트를 선택할수 있는 방법이다. 음 위의 Picking을 구현하기 위해서는 현재 자신이 바라보는 화면에 랜더링 파이프라인에서 프로젝션 단계를 거친 화면임을 이해해야 한다. 으음 나도 잘 모르니 넘어가도록 하자... 사실 나는 설명할게 없다. 일단 간단하게 내가 구현한 Picking화면을 보자
위의 스크린샷중에 Picking 이라고 되고 재대로 텍스쳐가 입혀져 있는 곳에 원래는 마우스가 있다. 흐음 딱히 설명할게 없으니 내 코드좀 올리고... 전체 프로젝트 파일올리고 여기저기 소스 배껴온 Reference 주소를 올리고 끝내자..
IntersectTriangle
- 이 함수는 하나의 직선을 의미하는 백터 2개와 하나의 폴리곤을 나타내는 백터 3개를 넣어 충돌의 여부와 충돌지점까지의 거리를 얻을수 있다. 물론 내가 구현한게 아니라... DX 샘플중 Picking 에 보면 이미 구현되어 있는 함수를 후려와서 사용했다.
이 함수는 감자닷넷의 주인장이신 감자님이 어떤식으로 구현되어 있는지 올린 글이 있다. 보고 이해는 하겠는데... 기억하지는 못한다. 쿨럭...
참고문서
BOOL IntersectTriangle( const D3DXVECTOR3& orig, const D3DXVECTOR3& dir,
D3DXVECTOR3& v0, D3DXVECTOR3& v1, D3DXVECTOR3& v2, FLOAT* t, FLOAT* u, FLOAT* v )
{
// Find vectors for two edges sharing vert0
D3DXVECTOR3 edge1 = v1 - v0;
D3DXVECTOR3 edge2 = v2 - v0;
// Begin calculating determinant - also used to calculate U parameter
D3DXVECTOR3 pvec;
D3DXVec3Cross( &pvec, &dir, &edge2 );
// If determinant is near zero, ray lies in plane of triangle
FLOAT det = D3DXVec3Dot( &edge1, &pvec );
D3DXVECTOR3 tvec;
if( det > 0 )
{
tvec = orig - v0;
}
else
{
tvec = v0 - orig;
det = -det;
}
if( det < 0.0001f )
return FALSE;
// Calculate U parameter and test bounds
*u = D3DXVec3Dot( &tvec, &pvec );
if( *u < 0.0f || *u > det )
return FALSE;
// Prepare to test V parameter
D3DXVECTOR3 qvec;
D3DXVec3Cross( &qvec, &tvec, &edge1 );
// Calculate V parameter and test bounds
*v = D3DXVec3Dot( &dir, &qvec );
if( *v < 0.0f || *u + *v > det )
return FALSE;
// Calculate t, scale parameters, ray intersects triangle
*t = D3DXVec3Dot( &edge2, &qvec );
FLOAT fInvDet = 1.0f / det;
*t *= fInvDet;
*u *= fInvDet;
*v *= fInvDet;
return TRUE;
}
BOOL Picking(HWND hWnd)
{
POINT ptCursor;
D3DXVECTOR3 vPickRayDir;
D3DXVECTOR3 vPickRayOrig;
INTERSECTION_HEIGHTMAP Intersection;
ZeroMemory(&Intersection, sizeof(INTERSECTION_HEIGHTMAP));
Intersection.m_fDist = FAR_DISTANCE;
_cdbg("Picking Start");
// 마우스 포인터로 부터 3D객체를 향해서 뻗는 3D직선의 백터를 얻는다. 언프로젝션부분
D3DVIEWPORT9 vp;
m_pGraphics->GetDeviceCOM()->GetViewport(&vp);
D3DXMATRIXA16 matProj;
m_pGraphics->GetDeviceCOM()->GetTransform( D3DTS_PROJECTION, &matProj );
GetCursorPos( &ptCursor );
ScreenToClient( hWnd, &ptCursor );
D3DXVECTOR3 v;
v.x = (( (((ptCursor.x-vp.X)*2.0f/vp.Width ) - 1.0f)) - matProj._31 ) / matProj._11;
v.y = ((- (((ptCursor.y-vp.Y)*2.0f/vp.Height) - 1.0f)) - matProj._32 ) / matProj._22;
v.z = 1.0f;
// Get the inverse view matrix
D3DXMATRIXA16 matView, m;
m_pGraphics->GetDeviceCOM()->GetTransform( D3DTS_VIEW, &matView );
D3DXMatrixInverse( &m, NULL, &matView );
// Transform the screen space pick ray into 3D space
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;
// 위에서 얻는 백터와 Picking의 대상이 되는 오브젝트의 충돌 체크를 한다.
FVF_HEIGHTMAP* pVerHeightMap;
WORD* pIndexHeightMap;
GetVertexBuffer()->Lock();
pVerHeightMap = (FVF_HEIGHTMAP*)m_pVB->GetPtr();
GetIndexBuffer()->Lock();
pIndexHeightMap = (WORD*)m_pIB->GetPtr();
FLOAT fBary1, fBary2, fDist;
for( DWORD i=0; i<m_dwPrimitives; i++ )
{
D3DXVECTOR3 v0 = pVerHeightMap[pIndexHeightMap[3*i+0]].p;
D3DXVECTOR3 v1 = pVerHeightMap[pIndexHeightMap[3*i+1]].p;
D3DXVECTOR3 v2 = pVerHeightMap[pIndexHeightMap[3*i+2]].p;
// Check if the pick ray passes through this point
// 충돌이 있다면
if( IntersectTriangle( vPickRayOrig, vPickRayDir, v0, v1, v2, &fDist, &fBary1, &fBary2 ) )
{
_cdbg("Intersection occour");
// 일단 가장 가까운 값을 유지 해야 한다
if(Intersection.m_fDist > fDist)
{
Intersection.m_fDist = fDist;
Intersection.m_verHeightMap[0].p = v0;
Intersection.m_verHeightMap[1].p = v1;
Intersection.m_verHeightMap[2].p = v2;
Intersection.m_dwPrimitiveIndex = i;
}
}
}
m_pIB->Unlock();
m_pVB->Unlock();
// Picking 된 버텍스로 출력할 것 설정
WORD Indexs[3] = {0,1,2};
m_pVBIntersect->Set(0,3,Intersection.m_verHeightMap);
m_pIBIntersect->Set(0,3,Indexs);
// Debug ///////////////////////////////////
sprintf(m_szDebugBuff,
"Primitive Index : %d, Picking Distance : %f\nMouse Position X : %d, Mouse Position Y : %d",
Intersection.m_dwPrimitiveIndex, Intersection.m_fDist, ptCursor.x, ptCursor.y);
// Debug ///////////////////////////////////
return TRUE;
}
아 그러고 보니 위의 Picking 소스는 카메라의 위치가 0,0,0 이 아니면 재대로 돌아가지 않는데 흐음... 어떤 행렬값이나 백터값에 보정을 해줘야 할것 같다. 아마도 v 백터값에 카메라 포지션을 더해주는게 아닐까 하는데... 사실 정확히는 모르겠다. -_-;;
현재는 실행시키면 돌아가기는 하지만... 카메라의 위치를 바꾸거나 윈도우의 크기를 변화시키면 안된다. 이 문제에 대해서는 Picking에서 사용하는 수학적 지식을 완전히 익혀야 해결할수 있을듯...
2004년 7월 27일 카메라의 위치와 윈도우 크기를 바꿀때 생기는 오차를 해결
- 상당히 간단했다. 일단 윈도우 크기를 바꿀때 생기는 오차는 GetviewPort로 윈도우 크기를 얻어올때 이 함수가 실제 윈도우 크기를 얻어오는게 아니라 DX에서 설정한 해상도..랄까.. 처음 설정한 크기를 얻어오기 때문에 발생했다. 사실 윈도우 크기가 바뀌더라도 윈도우에 찍히는 그림은 계속 그 해상도를 유지한다. 어쨋든 이 값보다는 실제 윈도우 크기가 필요하기 때문에서 GetClientrect 를 사용해야 한다. 코드를 보면
D3DVIEWPORT9 vp;
m_pGraphics->GetDeviceCOM()->GetViewport(&vp);
D3DXMATRIXA16 matProj;
m_pGraphics->GetDeviceCOM()->GetTransform( D3DTS_PROJECTION, &matProj );
GetCursorPos( &ptCursor );
ScreenToClient( hWnd, &ptCursor );
D3DXVECTOR3 v;
v.x = (( (((ptCursor.x-vp.X)*2.0f/vp.Width ) - 1.0f)) - matProj._31 ) / matProj._11;
v.y = ((- (((ptCursor.y-vp.Y)*2.0f/vp.Height) - 1.0f)) - matProj._32 ) / matProj._22;
v.z = 1.0f;
이 부분을
RECT rt;
GetClientRect( hWnd, &rt );
D3DXMATRIXA16 matProj;
m_pGraphics->GetDeviceCOM()->GetTransform( D3DTS_PROJECTION, &matProj );
GetCursorPos( &ptCursor );
ScreenToClient( hWnd, &ptCursor );
D3DXVECTOR3 v;
v.x = (( (((ptCursor.x-rt.left)*2.0f/rt.right ) - 1.0f)) - matProj._31 ) / matProj._11;
v.y = ((- (((ptCursor.y-rt.top)*2.0f/rt.bottom) - 1.0f)) - matProj._32 ) / matProj._22;
v.z = 1.0f;
으로 바꿔 줘야 한다. 그리고 카메라의 위치가 바꼈을때의 오차 해결은 vPickRayOrig 값의 오차를 보정해 줘야 하는데 보정값은 당연히 카메라의 위치가 된다. 위의 백터값에서 카메라의 위치값을 빼주면 된다!!
1.2 Reference #
비주얼 스튜디오 6.0 으로 작업했는데 코드는 그다지 아름답지 못하다. 2003에서 컴파일이 안될정도니..
|
|
|