Picking

1 Picking #

2004년 7월 22일

  • 맵에디터에서 마우스 입력을 위해서 가장 기초적인 Picking을 구현!

카메라가 바라보는 방향이 화면에 나오고 있을때 마우스의 위치로 화면안쪽으로 향하는 반직선을 만들고 그 반직선과 교차하는 폴리곤을 선택하는 것을 Picking 이라고 한다.

이후 설명하는 대부분의 것이 제가 잘 모르는 관계로 틀린 내용일수 있습니다 -_-

즉 간단히 마우스로 가져다가 댔을때 그 바로 아래있는 폴리곤이나 오브젝트를 선택할수 있는 방법이다. 음 위의 Picking을 구현하기 위해서는 현재 자신이 바라보는 화면에 랜더링 파이프라인에서 프로젝션 단계를 거친 화면임을 이해해야 한다. 으음 나도 잘 모르니 넘어가도록 하자… 사실 나는 설명할게 없다. 일단 간단하게 내가 구현한 Picking화면을 보자

http://lagoons.net/docdata/Height_3.jpg

위의 스크린샷중에 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;
}

Picking

  • 위의 충돌검색 함수를 사용하기 위해 언프로젝션과 기타 준비등등을 해주는 함수 위의 충돌 검색함수의 경우 하나의 폴리곤씩 검색을 하기 때문에 하이트맵에 적용하기 위해서 폴리곤 하나 단위로 루프를 도는 구문등이 있다. 실제로 피킹에서 가장 중요한것은 언프로젝션IntersectTriangle 이다.

참고문서 [http]http://www.gamza.net/ez2000/ezboard.exe?db=Algorithm&action=read&dbf=14&page=0&depth=1

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에서 사용하는 수학적 지식을 완전히 익혀야 해결할수 있을듯…

1.1 오차의 해결 #

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 #

2 소스 #

비주얼 스튜디오 6.0 으로 작업했는데 코드는 그다지 아름답지 못하다. 2003에서 컴파일이 안될정도니..

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다