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에서 컴파일이 안될정도니..

HeightMap Using Texture Matrix ( using one Texture )

1 Make HeightMap Vertex,Index Buffer #

2004년 7월 18일 쯤

  • 현재 하이트 맵의 가장 간단한건 구현… 와이어프레임으로 랜더링하고 있다.

raw 파일에서 64*64 크기의 높이 정보를 읽어와서 버텍스들을 생성하고 인덱스버퍼를 만들어 랜더링한다. 랜더링 상태를 SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME) 으로 설정해서 와이어프레임이 꽉꽉차서 보인다.(과거 이 옵션을 몰라서 랜더링 옵션을 D3DPT_LINESTRIP 으로 하고 랜더링된 와이어프래임을 보고 “어째서 나의 와이어 프레임은 부실하게 나올까” 하고 고민한적이있다….)

음 하이트맵의 데이타를 생성하는 코드를 써보면

         unsigned char TmpHeight;

        // 버텍스버퍼와 인덱스버퍼를 위한 중간 버퍼들
        FVF_HEIGHTMAP*  pVertexData = new FVF_HEIGHTMAP[m_dwVertices];
        WORD*                   pIndexData  = new WORD[m_dwPrimitives*3];

        //파일에서 높이 정보를 읽어오고 버텍스정보를 삽입한다.
        FILE* pFile;
        UINT nIndex = 0;
        pFile = fopen(szFilename, "rb");
        for(unsigned y = 0;y < (HEIMAP_Y + 1); y++)
        {
                for(unsigned x = 0;x < (HEIMAP_X + 1); x++)
                {
                        TmpHeight = fgetc(pFile);

                        // 버텍스 데이타 구성
                        pVertexData[x + y * (HEIMAP_X+1)].fX = (float)x;
                        pVertexData[x + y * (HEIMAP_X+1)].fY = (float)TmpHeight / 15.0f - 40;
                        pVertexData[x + y * (HEIMAP_X+1)].fZ = (float)y;
                        pVertexData[x + y * (HEIMAP_X+1)].dwColor = 0xffffffff;

                        if( (y < HEIMAP_Y) && (x < HEIMAP_X) )
                        {
                                // 인덱스 데이타 구성
                                pIndexData[(x+y*(HEIMAP_X))*6 +0] = x   + y     * (HEIMAP_X+1);
                                pIndexData[(x+y*(HEIMAP_X))*6 +1] = x+1 + y     * (HEIMAP_X+1);
                                pIndexData[(x+y*(HEIMAP_X))*6 +2] = x   + (y+1) * (HEIMAP_X+1);

                                pIndexData[(x+y*(HEIMAP_X))*6 +3] = x   + y     * (HEIMAP_X+1);
                                pIndexData[(x+y*(HEIMAP_X))*6 +4] = x+1 + (y+1) * (HEIMAP_X+1);
                                pIndexData[(x+y*(HEIMAP_X))*6 +5] = x   + (y+1) * (HEIMAP_X+1);
                        }
                }
        }
        fclose(pFile);

        // 버텍스버퍼를 구성한다. 
        m_pVB->Create(m_pGraphics, m_dwVertices, sizeof(FVF_HEIGHTMAP), D3DFVF_HEIGHTMAP);
        m_pVB->Set(0, m_dwVertices, pVertexData);

        //인덱스데이타를 구성한다.
        m_pIB->Create(m_pGraphics, m_dwPrimitives*3);
        m_pIB->Set(0, m_dwPrimitives*3, pIndexData);

        // 버덱스데이타, 인덱스데이타 메모리를 해재한다.
        delete [] pVertexData;
        delete [] pIndexData;

이정도… 간단하다. 밑은 스크린샷 이제는 텍스쳐 매트릭스를 적용하여 한 중류의 텍스쳐로 덥어보자

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

2 Using Texture Matrix #

http://myevan.net/phpBB/viewtopic.php?t=1445 (빗자루님의 포럼) 에 텍스쳐 메트릭스에 관한 내용이 있다.

음 위의 내용을 이해하면 텍스쳐매트릭스를 사용하는데는 지장이 없을듯 하다. 문제는 다이렉트X SDK인데… 나만 그러는지 모르겠지만 D3DXMatrixDeterminant 를 사용했을때 런타임에러가 발생한다. 그래서 2003 Summer Update로 업데이트를 했는데 일단 위의 문제는 해결됐다. 다만 인테페이스가 바뀐게 좀 있어서 짜증이 난다. -_-;;; 쓰던 코드를 좀 손봐야 할듯하다.

2004년 7월 22일

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

위의 화면처럼 나오는데… 여러종류의 텍스쳐를 입히는것도 아주 쉬울듯하다. 그냥 텍스쳐별로 따로따로 인덱스버퍼를 구성하면 되니… 그런데 그렇게 할려면 맵의 파일 포멧이 좀 바껴야 할태고 그럴려면 이왕에 전용 에디터를 만드는쪽이 좆타… 라는 결론이 나와서 -_-;;; 좀 귀찮다. 일단 위의 하이트맵을 랜더링하는 코드를 보면

void CHeightMap::Rendering(HEIGHTMAP_DRAW_OPTION hdOption)
{
        D3DXMATRIX matrixView;
        m_pGraphics->GetDeviceCOM()->GetTransform(D3DTS_VIEW, &matrixView);
        FLOAT fDeterminantMatView = D3DXMatrixDeterminant(&matrixView);

        D3DXMATRIX matViewInv;
        D3DXMatrixInverse(&matViewInv, &fDeterminantMatView, &matrixView);

        D3DXMATRIX matScale;
        ZeroMemory(&matScale, sizeof(D3DXMATRIX));
        matScale._11=1.0f/1.0f;
        matScale._32=1.0f/1.0f;

        D3DXMATRIX matTex;
        D3DXMatrixMultiply(&matTex, &matViewInv, &matScale);

        // 텍스쳐 좌표 자동생성을 위한 세팅
        m_pGraphics->GetDeviceCOM()->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);
        m_pGraphics->GetDeviceCOM()->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
        m_pGraphics->GetDeviceCOM()->SetTransform(D3DTS_TEXTURE0, &matTex);

        // 랜더링 옵션이 WIRE이면 랜더링 상태를 설정
        if(hdOption == RENDER_WIRE)
                m_pGraphics->GetDeviceCOM()->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);

        m_pIB->Render( D3DPT_TRIANGLELIST, 0, 0, m_pVB->GetNumVertices(), 0, m_dwPrimitives, m_pVB, m_pTextureDefault );

        // 랜더링 옵션이 WIRE이면 랜더링 상태를 해제
        if(hdOption == RENDER_WIRE)
                m_pGraphics->GetDeviceCOM()->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);


        // 텍스쳐 좌표 자동생성을 위한 세팅 해제
        m_pGraphics->GetDeviceCOM()->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU);
        m_pGraphics->GetDeviceCOM()->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
}

이정도… 자세한 내용은 빗자루님의 글을 참고하는게 좋다. 간단히 설명하면 카메라의 위치와 버텍스의 위치값으로 UV값을 자동 생성해서 텍스쳐를 입히는 것이다.

자 그럼 다음은 맵에디터다 -_-

2.1 Texture Matrix Reference #

  • http://myevan.net/
    에서 texture matrix 로 검색을 한뒤 링크지식/여치님의 하드웨어가속 타일링 을 찾아보면 있다.