7. Collision
Posted 2007/09/15 19:23 by 수달-------------------------------------------------------------------------
튜토리얼 7 : 충돌
----------------
이번 예제에서는 IRRLICHT엔진의 '충돌 확인' 법을 보일 것이다. 세가지 방법이 있다. 첫번째는 계단과 미끄러짐을 포함한 3D 월드의 이동 관련 충돌 확인이다. 두번째는 삼각 폴리곤 피킹(역자 주 : 마우스 포인트 등을 이용해 눈에 보이는 지점의 해당 폴리곤을 찝어내는 기술)이다. 그리고 세번째로 씬 노드 피킹이다.
-------------------------------------------------------------------------
시작
----
퀘이크 3 레벨 맵을 출력했던 튜토리얼 2를 가져오도록 하자. 이 맵에서 걷고, 목표 폴리곤을 찝어내는 기술을 적용해 보겠다. 추가적으로 씬 노드 피킹을 위해 세개의 움직이는 모델을 배치한다.
다음 코드는 퀘이크 3 레벨을 엔진이 읽고 출력한 부분이다. 이에 대해선 튜토리얼 2에서 이미 말했기에 설명하지 않겠다.
#include <irrlicht.h>
#include <iostream>
using namespace irr;
#pragma comment(lib, "Irrlicht.lib")
int main()
{
// let user select driver type
video::E_DRIVER_TYPE driverType;
printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.2\n"\
" (d) Software Renderer\n (e) NullDevice\n (otherKey) exit\n\n");
char i;
std::cin >> i;
switch(i)
{
case 'a': driverType = video::EDT_DIRECTX9; break;
case 'b': driverType = video::EDT_DIRECTX8; break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_NULL; break;
default: return 0;
}
// create device
IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<s32>(640, 480), 16, false);
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");
scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");
scene::ISceneNode* q3node = 0;
if (q3levelmesh)
q3node = smgr->addOctTreeSceneNode(q3levelmesh->getMesh(0));
그럭저럭 퀘이크 3 레벨을 읽었다. 이제 좀 다른 것을 해보겠다. 먼저 삼각 폴리곤 선택자(triangle selector)를 만들겠다. 이것은 씬 노드에서 충돌 확인 등을 위해 삼각 폴리곤을 꺼내는 클래스다. 이러한 폴리곤 선택자는 여러가지가 있을 수 있고, 모두 ISceneManager로 만들어 진다. 이번 예제에서는 OctTreeTriangleSelector를 사용한다. 이것은 거대한 메시 덩어리인 퀘이크 3 레벨을 8분할을 통해 최적화 된 삼각형 추출을 해준다.
삼각형 선택자를 만든 후, 이것을 q3node에 부착시킬 것이다. 이것은 필수적이진 않지만, 이렇게 해두면 사소한 신경을 안 써도 된다. (예를 들어 나중에 필요 없어지더라도 직접 drop 시키는 등의 일을 안해도 된다.)
scene::ITriangleSelector* selector = 0;
if (q3node)
{
q3node->setPosition(core::vector3df(-1370,-130,-1400));
selector =
smgr->createOctTreeTriangleSelector(q3levelmesh->getMesh(0),
q3node, 128);
q3node->setTriangleSelector(selector);
selector->drop();
}
튜토리얼 2에서 처럼 1인칭 슈팅 게임 카메라를 붙여 퀘이크 맵을 돌아다닐 수 있게 하자. 추가적으로 이 카메라에 특별한 애니메이터 관련 설정을 할텐데, 충돌에 반응하는 애니메이터(the collision response animator)가 그것이다. 이렇게 설정하면 씬 노드에 영향을 주어 벽을 뚫지 못하게 되고 중력의 영향도 받게 된다. 설정하는데 있어서 애니메이터에게 말해줄 것은 씬 노드의 크기, 월드의 모양, 중력 적용 등 뿐이다. 충돌이 적용된 애니메이터를 카메라에 붙이게 되면, 더 이상 충돌 확인 같은 것을 코드에 넣을 필요가 없다. 모든 것은 자동으로 이뤄지며 모든 충돌 확인 관련 코드들에 피킹을 적용할 수 있게 된다. 이 애니메이터는 카메라 뿐만이 아니고 다른 모든 씬 노드에도 적용이된다. 이 방식으로 IRRLICHT 엔진은 정말 쉽게 충돌 문제를 해결한다. (역자 주 : FPS에서 카메라는 게이머의 플레이어를 말한다. 만약 몬스터에 이를 붙이면, 이 몬스터 역시 쉽게 충돌이 적용될 것이다.)
그럼 createCollisionResponseAnimator()의 여러 인자들을 상세히 살펴 보자. 첫번째 인자는 TriangleSelector로써 월드에서 어떤 방식으로 충돌 확인을 할지 정한 것이다. 두번째는 충돌 확인이 적용될 씬 노드다. 예제의 경우엔 카메라다. 세번째는 얼마나 큰 영역으로 충돌 확인을 할지 결정하는 것으로 원의 반지름을 말한다. 이 수치를 작게하면 카메라는 벽에 좀 더 붙을 수 있을 것이다. 다음 인자는 중력 방향과 속도다. (0, 0, 0)으로 정하면 중력 적용이 안된다. 그리고 마지막 인자는 변환 관련 인자다. 만약 이것이 없다면 카메라는 충돌 확인 타원의 중앙에 위치할 것이다. 그러나 사람의 경우 눈은 전체 몸의 상단에 위치해 있다. 충돌 확인 타원은 중앙이 아닌 상단에 위치해야 한다. 여기서는 중앙에서 윗 방향으로 50정도 높게 적용했다.
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0, 100.0f, 300.0f, -1, 0, 0, true);
camera->setPosition(core::vector3df(-100,50,-150));
scene::ISceneNodeAnimator* anim =
smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(30,50,30),
core::vector3df(0,-3,0),
core::vector3df(0,50,0));
camera->addAnimator(anim);
anim->drop();
충돌 확인은 IRRLICHT에선 그리 큰 영역이 아니기에, 이 예제에선 피킹 관련 두가지 방법을 기술하겠다. 이 전에 씬 관련해 약간 준비할게 있다. 우선 피킹을 하면 (그것을 확인해 주는) 동적 광원을 받는 세 개의 애니메이션 캐릭터가 필요하다. 그리고 사용자의 시점을 표현하고 무엇을 피킹할지 결정하는 교차점 빌보드와 마우스 커서 제거를 해야한다. (역자 주 : 교차점이란 FPS의 크로스 헤어처럼 화면 중앙에 위치해 시점과 목표점을 표시해주는 것을 의미. 그리고 빌보드란 카메라가 어디에 있건 동일한 모습, 방향만을 보여주는 스프라이트를 의미한다.)
// disable mouse cursor
device->getCursorControl()->setVisible(false);
// add billboard
scene::IBillboardSceneNode * bill = smgr->addBillboardSceneNode();
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
bill->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));
bill->setMaterialFlag(video::EMF_LIGHTING, false);
bill->setMaterialFlag(video::EMF_ZBUFFER, false);
bill->setSize(core::dimension2d<f32>(20.0f, 20.0f));
// add 3 animated faeries.
video::SMaterial material;
material.Texture1 = driver->getTexture("../../media/faerie2.bmp");
material.Lighting = true;
scene::IAnimatedMeshSceneNode* node = 0;
scene::IAnimatedMesh* faerie = smgr->getMesh("../../media/faerie.md2");
if (faerie)
{
node = smgr->addAnimatedMeshSceneNode(faerie);
node->setPosition(core::vector3df(-70,0,-90));
node->setMD2Animation(scene::EMAT_RUN);
node->getMaterial(0) = material;
node = smgr->addAnimatedMeshSceneNode(faerie);
node->setPosition(core::vector3df(-70,0,-30));
node->setMD2Animation(scene::EMAT_SALUTE);
node->getMaterial(0) = material;
node = smgr->addAnimatedMeshSceneNode(faerie);
node->setPosition(core::vector3df(-70,0,-60));
node->setMD2Animation(scene::EMAT_JUMP);
node->getMaterial(0) = material;
}
material.Texture1 = 0;
material.Lighting = false;
// Add a light
smgr->addLightSceneNode(0, core::vector3df(-60,100,400),
video::SColorf(1.0f,1.0f,1.0f,1.0f), 600.0f);
이 작업을 완료하려면 설정한 것들을 드로잉 루프(the drawing loop)에 실제 넣어야 한다. 현재 선택된 것과 직전 마지막으로 선택된 씬 노드를 기억할 변수 포인터 두 개를 잡은 후 메인 루프를 시작한다.
scene::ISceneNode* selectedSceneNode = 0;
scene::ISceneNode* lastSelectedSceneNode = 0;
int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, 0);
smgr->drawAll();
smgr->drawAll()로 먼저 기본 씬을 그려준 후 첫번째 피킹을 처리하자. 이는 게이머가 월드의 무엇을 (화면 상에서) 보고 있는지에 알려주는 삼각형 폴리곤 피킹이다. 추가적으로 퀘이트 3 맵에서 그 본 것의 지점을 뽑아내보자. 이를 위해 카메라에서 시작해 보이는 방향 쪽에 다다르는 3차원 선(line)을 만들어야 한다. 그런 후 충돌 확인 매니저(the collision manager)에 이 선이 삼각 선택자에 저장된 월드의 어떤 삼각형과 충돌하는지 알려줄 것을 요청한다. 만약 충돌하고 있으면 그 교차점에 3차원 삼각형을 그리고 교차점 빌보드를 위치시킬 것이다. (역자 주 : 교차점 빌보드를 위치시킨다는게 좀 애매하다. 이에 대해선 bill->setPosition(intersection) 부분을 주석 처리한 후 실험을 해봐야 한다. 주석 처리한 경우와 안 한 경우로 이 프로그램의 첫 시작점에서 마우스를 움직여 월드의 윗 방향 꼭대기를 보라. 맵이 없는 공중 부근에 가면 빌보드가 월드 끝에만 붙고 하늘에는 안 붙는 현상을 보게 될 것이다. 즉 빌보드는 눈에 보이는 화면 정 중앙에 충돌한 월드가 있어야만 그려진다. 또한 먼 거리를 보면 교차점 빌보드도 작아지는데 이는 교차점 빌보드가 충돌 지점 '그 자리'에 위치함을 증명한다. 다양한 응용이 가능한 부분이다.)
core::line3d<f32> line;
line.start = camera->getPosition();
line.end =
line.start + (camera->getTarget() - line.start).normalize() * 1000.0f;
core::vector3df intersection;
core::triangle3df tri;
if (smgr->getSceneCollisionManager()->getCollisionPoint(
line, selector, intersection, tri))
{
bill->setPosition(intersection);
driver->setTransform(video::ETS_WORLD, core::matrix4());
driver->setMaterial(material);
driver->draw3DTriangle(tri, video::SColor(0,255,0,0));
}
또 다른 형태의 IRRLICHT 엔진 지원 피킹은 바운딩 박스(the bounding box)에 기초한 씬 노드 피킹이다. (역자 주 : 바운딩 박스란 충돌 등을 검출하기 위해 임의로 설정한 공간을 말한다. 원형이나 육면체 등이 많이 쓰인다.) 모든 씬 노드는 바운딩 박스를 갖고 있다. 그리고 이 때문에 카메라가 바라보는 씬 노드를 아주 빠르게 잡아낼 수 있다. 다시 이를 충돌 메니저에 요청하겠다. 만약 씬 노드를 구한다면 그것의 재질에 하이라이트를 주겠다. (단 그 씬 노드가 빌보드나 퀘이크 3 맵이면 하이라이트를 안 준다.)
selectedSceneNode =
smgr->
getSceneCollisionManager()->
getSceneNodeFromCameraBB(camera);
if (lastSelectedSceneNode)
lastSelectedSceneNode->setMaterialFlag(video::EMF_LIGHTING, true);
if (selectedSceneNode == q3node || selectedSceneNode == bill)
selectedSceneNode = 0;
if (selectedSceneNode)
selectedSceneNode->setMaterialFlag(video::EMF_LIGHTING, false);
lastSelectedSceneNode = selectedSceneNode;
됐다. 마지막 그리기 처리를 한다.
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Collision detection example - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}
----------------------------------------------------------------------------
이번 예제를 해석하면서 내용은 쉬우나 많은 개념이 등장해 헤깔리는 면이 많았다. 빌보드가 특정으로 쓰일 때와 일반으로 쓰일 때가 있었으나 제대로 구분이 안 된 듯 하고, select나 selecting, intersection 뭐 이런 단어를 어떻게 해석할지 매번 난감하다. 직접 게임 코드를 돌려보는게 가장 좋은 듯 하다.
예제를 읽다 보니, 씬 노드와 드로잉(drawing)의 윤곽이 명확해 지는 느낌이다. 이는 IRRLICHT 엔진이 추구하는 점이라고도 하겠다. 같은 형, 같은 개념끼리 쭈욱 연결 리스트로 저장해 둔 뒤, 필요에 따라 그것을 사용한다. 자료구조와 알고리즘을 가볍고도 확실히 분리했다. 이 일이 서로 융통성이 있어서 조금의 상상으로도 많은 것을 할 수 있을 듯하다. 또한 이럼에도 불구하고 속도와 용량에서 효율적이라고 하니 대단한 일이다.
아, 참고로 여기 코드는 게시판에 보이기 위한 정렬이므로 실제 엔진 예제 코드로 연습해야 한다.
'기본 카테고리' 카테고리의 다른 글
Irricht엔진튜토리얼 09. Mesh Viewer (0) | 2009.03.19 |
---|---|
Irricht엔진튜토리얼 08. SpecialFX (0) | 2009.03.19 |
Irricht엔진튜토리얼 06. 2D Graphics (0) | 2009.03.19 |
Irricht엔진튜토리얼 05. User Interface (0) | 2009.03.19 |
Irricht엔진튜토리얼 04. Movement (0) | 2009.03.19 |