(1) https://youtu.be/7LKwqkRgvIU (2) https://youtu.be/tlpOCqDfeLQ
3D에서 사용하는 충돌은 대부분 이 방법으로 충돌을 검출한다. 첫 째는 경계 구 충돌이며 두 번째는 경계 사각형 충돌을 통해 3D 물체의 충돌을 판단하며 이 두 가지 충돌을 병행하여 사용한다. |
|
14.1 경계 구 충돌
경계 구를 이용한 충돌은 다른 충돌에 비해 빠르며 계산량이 상당히 적으나 정확도가 낮은 편이다.
그럼에도 불구하고 경계 구 충돌이 자주 사용되는 것은 계산량과 속도 때문이다.
경계 구는 3D 물체의 일정부위에 경계 구를 씌워 해당 부위에 대한 충돌을 계산하거나 전체 충돌에 따른
1차적인 충돌을 체크한다.
[그림 10-1] 경계 구와 경계 구를 설정한 캐릭터
1) 경계 구를 이용한 충돌 체크
경계 구를 이용한 방법은 경계 구의 반지름과 중심점을 이용하여 두 경계 구의 충돌을 체크 한다.
먼저 경계 구가 충돌 되지 않을 조건은 아래의 [식 10.1]과 같다.
(Radius1 + Radius2 ) < ( vCenter2 - vCenter1 ) |
[식 14-1] 경계 구가 충돌하지 않을 식
vCenter2 – vCenter1은 벡터의 차이므로 두 구의 중심점 간의 거리가 나온다.
[그림 14-2] 경계 구의 충돌
[식 14-1]에서 소개한 식과는 반대로 충돌할 경계 식은 다음과 같다.
(Radius1 + Radius2 ) >= ( vCenter2 - vCenter1 ) |
[식 14-2] 경계 구가 충돌할 식
[그림 14-3] 경계 구가 충돌
경계 구의 중심점과 벡터의 크기는 다음의 함수를 이용하여 구할 수도 있다.
HRESULT D3DXComputeBoundingSphere( __in const D3DXVECTOR3 *pFirstPosition, __in DWORD NumVertices, __in DWORD dwStride, __out D3DXVECTOR3 *pCenter, __out FLOAT *pRadius );
FLOAT D3DXVec3Length( __in const D3DXVECTOR3 *pV ); |
pFirstPosition은 메쉬의 첫 정점 벡터를 설정하며 정점 버퍼를 Lock()을 하고 첫 정점을 설정하면 된다.
NumVertices는 정점의 개수를 설정하는데 LPD3DXMESH의 멤버 함수인 GetNumVertices()를 이용하면 메쉬 안의
정점 개수를 알 수 있다.
dwStride는 정점의 크기를 설정하는 부분으로 GetNumBytesPerVertex()와
D3DXGetFVFVertexSize()를 통해 구할 수 있다.
특히 D3DXGetFVFVertexSize()를 사용하기 위해서는 D3DFVF를 설정해야 하는데 LPD3DXMESH의 GetFVF()를
이용하면 현재 메쉬에 설정된 D3DFVF를 알아 낼 수 있다.
위에서 살펴본 경계 구 충돌을 함수로 만들면 다음과 같다.
BOOL CheckSphereIntersect( D3DXVECTOR3 vCenter1, float fRadius1, D3DXVECTOR3 vCenter2, float fRadius2 ) { float fDistance; D3DXVECTOR3 vDiff;
vDiff = vCenter2 - vCenter1; fDistance = D3DXVec3Length( &vDiff );
if( fDistance <= (fRadius1 + fRadius2 ) ) return TRUE; // 충돌 return FALSE; // 비 충돌 } |
2) 실습 예제
크기와 위치가 다른 두 개의 구 메쉬를 출력하고 구(sphere) 하나를 방향키로 이용시켜 충돌이 되게 하고
충돌과 비충돌 메시지를 화면에 출력해 보자.
#pragma once #include "d3dapp.h" #include <d3dx9math.h> #include <D3dx9shape.h>
struct SPHERE_PROPERTY { D3DXVECTOR3 vTans; // 중심점 역할 float fScaling; float fRadius; };
class CGameEdu01 : public CD3DApp { virtual void OnInit(); virtual void OnRender(); virtual void OnUpdate(); virtual void OnRelease();
D3DXMATRIX m_matView; D3DXMATRIX m_matProj; D3DXVECTOR3 m_Eye, m_At, m_Up;
SPHERE_PROPERTY m_Sphere[2]; BOOL m_bIsCollision; LPD3DXMESH m_pMesh;
LPD3DXFONT m_pFont;
DWORD m_dwElapsedTime;
public: CGameEdu01(void); ~CGameEdu01(void); }; |
#include "StdAfx.h" #include "GameEdu01.h" #include <D3dx9math.h> #include <stdio.h>
BOOL CheckSphereIntersect( D3DXVECTOR3 vCenter1, float fRadius1, D3DXVECTOR3 vCenter2, float fRadius2 ) { float fDistance; D3DXVECTOR3 vDiff;
vDiff = vCenter2 - vCenter1; fDistance = D3DXVec3Length( &vDiff );
if( fDistance <= (fRadius1 + fRadius2 ) ) return TRUE; return FALSE; }
CGameEdu01::CGameEdu01(void) { }
CGameEdu01::~CGameEdu01(void) { }
void CGameEdu01::OnInit() { int i;
RECT rect; D3DVIEWPORT9 vp; GetClientRect( m_hWnd, &rect );
vp.X = 0; vp.Y = 0; vp.Width = rect.right - rect.left; vp.Height = rect.bottom - rect.top; vp.MinZ = 0.0f; vp.MaxZ = 1.0f;
m_Eye.x = 0.0f; m_Eye.y = 5.0f; m_Eye.z = -20.0f;
m_At.x = 0.0f; m_At.y = 0.0f; m_At.z = 0.0f;
m_Up.x = 0.0f; m_Up.y = 1.0f; m_Up.z = 0.0f;
D3DXMatrixLookAtLH( &m_matView, &m_Eye, &m_At, &m_Up ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_matView );
D3DXMatrixPerspectiveFovLH( &m_matProj, D3DX_PI / 4, 1.0f, 1.0f, 100.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &m_matProj ); m_pd3dDevice->SetViewport( &vp );
D3DXCreateFont( m_pd3dDevice, 15, 0, FW_NORMAL, 1, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "돋음체", &m_pFont );
D3DXCreateSphere( m_pd3dDevice, 1.0f, 20, 20, &m_pMesh, NULL );
m_Sphere[0].fRadius = 1.0f; m_Sphere[0].fScaling = 1.0f; m_Sphere[0].vTans = D3DXVECTOR3( 0, 0, 0 );
m_Sphere[1].fRadius = 1.0f; m_Sphere[1].fScaling = 2.0f; m_Sphere[1].vTans = D3DXVECTOR3( 5, 0, 0 );
m_bIsCollision = FALSE;
m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME ); }
void CGameEdu01::OnRender() { char string[100]; RECT rt; D3DXMATRIX matWorld, matScale, matTrans;
for( int i = 0 ; i < 2 ; i++ ) { D3DXMatrixTranslation( &matTrans, m_Sphere[i].vTans.x, m_Sphere[i].vTans.y, m_Sphere[i].vTans.z ); D3DXMatrixScaling( &matScale, m_Sphere[i].fScaling, m_Sphere[i].fScaling, m_Sphere[i].fScaling ); matWorld = matScale * matTrans; m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); m_pMesh->DrawSubset(0); }
SetRect( &rt, 10, 10, 0, 0 );
if( m_bIsCollision ) m_pFont->DrawText( NULL, "충돌, -1, &rt, DT_NOCLIP, D3DXCOLOR( 1.0f, 1.0f, 0.0f, 1.0f ) ); else m_pFont->DrawText( NULL, "비충돌", -1, &rt, DT_NOCLIP, D3DXCOLOR( 1.0f, 1.0f, 0.0f, 1.0f ) ); }
void CGameEdu01::OnUpdate() { DWORD dwCurTime = GetTickCount(); static DWORD dwOldTime = GetTickCount(); m_dwElapsedTime = dwCurTime - dwOldTime; dwOldTime = dwCurTime;
if( GetAsyncKeyState( VK_LEFT ) ) m_Sphere[0].vTans.x -= m_dwElapsedTime * 0.003f;
if( GetAsyncKeyState( VK_RIGHT ) ) m_Sphere[0].vTans.x += m_dwElapsedTime * 0.003f;
m_bIsCollision = CheckSphereIntersect( m_Sphere[0].vTans, m_Sphere[0].fRadius * m_Sphere[0].fScaling, m_Sphere[1].vTans, m_Sphere[1].fRadius * m_Sphere[1].fScaling ); }
void CGameEdu01::OnRelease() { m_pMesh->Release(); m_pFont->Release(); } |
14.2 경계 사각형 충돌
1) 경계 사각형
경계 사각형을 이용한 충돌은 움직이지 않는 오브젝트 충돌에 매우 효과적이며 에니메이션이 들어 있는
오브젝트에 대해서는 추가적으로 AABB, OBB등과 같은 기법이 활용 된다.
경계 사각형은 최대점과 최소점만 알면 경계 사각형을 생성하거나 충돌 체크를 할 수 있다.
[그림 14-4] 최대점과 최소점
경계 사각형 충돌을 보다 쉽게 이해하기 위해서는 경계 사각형을 2차원 x, z 평면에 놓고 생각을 해보면 된다.
두 개의 사각형이 충돌 되는 조건은 아래와 같다.
[그림4]
if x1 <= x4 and x2 >= x3 and y1 <= y4 and y2 >= y3 |
[식 14-4]
이를 3D로 확장하면 아래와 같은 [식 14-5]가 된다.
if( ( x1 <= x4 and x2 >= x3 ) and ( y1 <= y4 and y2 >= y3 ) and ( z1 <= z4 and z2 >= z3 ) |
[식 14-5]
메쉬에 경계 사각형을 설정하기 위한 최대점과 최소점은 아래 함수를 이용하면 쉽게 구할 수 있다.
HRESULT D3DXComputeBoundingBox( __in const D3DXVECTOR3 *pFirstPosition, __in DWORD NumVertices, __in DWORD dwStride, __out D3DXVECTOR3 *pMin, __out D3DXVECTOR3 *pMax ); |
매개변수의 설정은 경계 구의 충돌 함수인 D3DXComputeBoundingSphere()와 같으며 pMin과 pMax를 구하는
것이 중요하다.
위의 내용으로 경계 사각형 충돌 함수를 만들면 아래와 같다.
BOOL CheckCubeIntersection( D3DXVECTOR3* vMin1, D3DXVECTOR3* vMax1, D3DXVECTOR3* vMin2, D3DXVECTOR3* vMax2 ) { if( vMin1->x <= vMax2->x && vMax1->x >= vMin2->x && vMin1->y <= vMax2->y && vMax1->y >= vMin2->y && vMin1->z <= vMax2->z && vMax1->z >= vMin2->z ) return TRUE; return FALSE; } |
2) 실습 예제
크기와 위치가 다른 두 개의 사각형을 출력하고 그 중에서 사각형 하나를 방향키로 이용시켜 충돌이 되게 하고
충돌과 비충돌 메시지를 화면에 출력해 보자.
#pragma once #include "d3dapp.h" #include <d3dx9math.h> #include <D3dx9shape.h>
struct BOX_PROPERTY { D3DXVECTOR3 vTans; float fScaling; D3DXVECTOR3 vMin, vMax; };
class CGameEdu01 : public CD3DApp { virtual void OnInit(); virtual void OnRender(); virtual void OnUpdate(); virtual void OnRelease();
D3DXMATRIX m_matView; D3DXMATRIX m_matProj; D3DXVECTOR3 m_Eye, m_At, m_Up;
BOX_PROPERTY m_Box[2]; D3DXVECTOR3 m_vMin, m_vMax; BOOL m_bIsCollision; LPD3DXMESH m_pMesh;
LPD3DXFONT m_pFont;
DWORD m_dwElapsedTime;
public: CGameEdu01(void); ~CGameEdu01(void); }; |
#include "StdAfx.h" #include "GameEdu01.h" #include <D3dx9math.h> #include <stdio.h>
BOOL CheckCubeIntersection( D3DXVECTOR3* vMin1, D3DXVECTOR3* vMax1, D3DXVECTOR3* vMin2, D3DXVECTOR3* vMax2 ) { if( vMin1->x <= vMax2->x && vMax1->x >= vMin2->x && vMin1->y <= vMax2->y && vMax1->y >= vMin2->y && vMin1->z <= vMax2->z && vMax1->z >= vMin2->z ) return TRUE; return FALSE; }
CGameEdu01::CGameEdu01(void) { }
CGameEdu01::~CGameEdu01(void) { }
void CGameEdu01::OnInit() { int i;
RECT rect; D3DVIEWPORT9 vp; GetClientRect( m_hWnd, &rect );
vp.X = 0; vp.Y = 0; vp.Width = rect.right - rect.left; vp.Height = rect.bottom - rect.top; vp.MinZ = 0.0f; vp.MaxZ = 1.0f;
m_Eye.x = 0.0f; m_Eye.y = 5.0f; m_Eye.z = -20.0f;
m_At.x = 0.0f; m_At.y = 0.0f; m_At.z = 0.0f;
m_Up.x = 0.0f; m_Up.y = 1.0f; m_Up.z = 0.0f;
D3DXMatrixLookAtLH( &m_matView, &m_Eye, &m_At, &m_Up ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_matView );
D3DXMatrixPerspectiveFovLH( &m_matProj, D3DX_PI / 4, 1.0f, 1.0f, 100.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &m_matProj ); m_pd3dDevice->SetViewport( &vp );
D3DXCreateFont( m_pd3dDevice, 15, 0, FW_NORMAL, 1, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "돋음체", &m_pFont );
D3DXCreateBox( m_pd3dDevice, 1.0f, 1.0f, 1.0f, &m_pMesh, NULL ); D3DXVECTOR3 *pVertices;
m_pMesh->LockVertexBuffer( D3DLOCK_READONLY, (void**)&pVertices ); D3DXComputeBoundingBox( pVertices, m_pMesh->GetNumVertices(), m_pMesh->GetNumBytesPerVertex(), &m_vMin, &m_vMax ); m_pMesh->UnlockVertexBuffer();
D3DXMATRIX matScale, matTrans, matWorld; m_Box[0].fScaling = 2.0f; m_Box[0].vTans = D3DXVECTOR3( 5.0f, 0.0f, 0.0f ); D3DXMatrixTranslation( &matTrans, m_Box[0].vTans.x, m_Box[0].vTans.y, m_Box[0].vTans.z ); D3DXMatrixScaling( &matScale, m_Box[0].fScaling, m_Box[0].fScaling, m_Box[0].fScaling ); matWorld = matScale * matTrans; D3DXVec3TransformCoord( &m_Box[0].vMin, &m_vMin, &matWorld ); D3DXVec3TransformCoord( &m_Box[0].vMax, &m_vMax, &matWorld );
m_Box[1].fScaling = 1.0f; m_Box[1].vTans = D3DXVECTOR3( 0.0f, 0.0f, 0.0f ); D3DXMatrixTranslation( &matTrans, m_Box[1].vTans.x, m_Box[1].vTans.y, m_Box[1].vTans.z ); D3DXMatrixScaling( &matScale, m_Box[1].fScaling, m_Box[1].fScaling, m_Box[1].fScaling ); matWorld = matScale * matTrans; D3DXVec3TransformCoord( &m_Box[1].vMin, &m_vMin, &matWorld ); D3DXVec3TransformCoord( &m_Box[1].vMax, &m_vMax, &matWorld );
m_bIsCollision = FALSE;
m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME ); }
void CGameEdu01::OnRender() { char string[100]; RECT rt; D3DXMATRIX matWorld, matScale, matTrans;
for( int i = 0 ; i < 2 ; i++ ) { D3DXMatrixTranslation( &matTrans, m_Box[i].vTans.x, m_Box[i].vTans.y, m_Box[i].vTans.z ); D3DXMatrixScaling( &matScale, m_Box[i].fScaling, m_Box[i].fScaling, m_Box[i].fScaling ); matWorld = matScale * matTrans;
m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); m_pMesh->DrawSubset(0); }
SetRect( &rt, 10, 10, 0, 0 );
if( m_bIsCollision ) m_pFont->DrawText( NULL, "충돌", -1, &rt, DT_NOCLIP, D3DXCOLOR( 1.0f, 1.0f, 0.0f, 1.0f ) ); else m_pFont->DrawText( NULL, "비충돌", -1, &rt, DT_NOCLIP, D3DXCOLOR( 1.0f, 1.0f, 0.0f, 1.0f ) ); }
void CGameEdu01::OnUpdate() { DWORD dwCurTime = GetTickCount(); static DWORD dwOldTime = GetTickCount(); m_dwElapsedTime = dwCurTime - dwOldTime; dwOldTime = dwCurTime;
if( GetAsyncKeyState( VK_LEFT ) ) m_Box[0].vTans.x -= m_dwElapsedTime * 0.003f;
if( GetAsyncKeyState( VK_RIGHT ) ) m_Box[0].vTans.x += m_dwElapsedTime * 0.003f;
D3DXMATRIX matScale, matTrans, matWorld; D3DXMatrixTranslation( &matTrans, m_Box[0].vTans.x, m_Box[0].vTans.y, m_Box[0].vTans.z ); D3DXMatrixScaling( &matScale, m_Box[0].fScaling, m_Box[0].fScaling, m_Box[0].fScaling ); matWorld = matScale * matTrans; D3DXVec3TransformCoord( &m_Box[0].vMin, &m_vMin, &matWorld ); D3DXVec3TransformCoord( &m_Box[0].vMax, &m_vMax, &matWorld );
m_bIsCollision = CheckCubeIntersection( &m_Box[0].vMin, &m_Box[0].vMax, &m_Box[1].vMin, &m_Box[1].vMax ); }
void CGameEdu01::OnRelease() { m_pMesh->Release(); m_pFont->Release(); } |

