Tutoriál 7: Detekce kolizí
Přeložil: Martin Zima alias "RedDragCZ"
V tomto tutoriálu si ukážeme jak na detekci kolizí v Irrlicht Enginu, a popíšeme si tři různé metody: automatickou detekci kolizí pro pohyb 3d světy, se stoupáním do schodů a slidováním stěn, ruční výběr trianglů, a ruční výber scénových uzlů
Výsledek bude vypadat takto:
|
| Začínáme! |
Pro začátek si vezmeme zdrojový kód programu, který jsme vytvořili v tutoriálu č. 2, a kde jsme si nahráli do enginu a zobrazili Quake 3 mapu. Použijeme ji pro předvedení zmíněné detekce kolizí. Následující kód nastaví a spustí engine, a nahraje do něj .bsp mapu. Nebudu jej zde vysvětlovat, protože jsem to již učinil ve druhém tutoriálu.
#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.5\n"\ " (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\ " (f) NullDevice\n (otherKey) exit\n\n");
char i; std::cin >> i;
switch(i) { case 'a': driverType = video::EDT_DIRECT3D9;break; case 'b': driverType = video::EDT_DIRECT3D8;break; case 'c': driverType = video::EDT_OPENGL; break; case 'd': driverType = video::EDT_SOFTWARE; break; case 'e': driverType = video::EDT_SOFTWARE2;break; case 'f': 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));
|
Takže, nahráli jsme do enginu Quake 3 mapu jako v tutoriálu 2. Nyní přichází první rozdíl: vytvoříme si tzv. triangle selector. Triangle selector je třída, které shromažďuje triangly (tojúhelníky) pro provádění všemožných věcí s nimi, např. právě detekci kolizí. V Irrlicht Enginu existuje standartně hned několik druhé selectorů, které dokáže scénový manažer (ISceneManager) vytvořit. V této ukázce použijeme OctTree triangle selector, který je optimalizován pro velké množství trianglů, což se právě hodí u větších 3d meshů, jako jsou Quake 3 mapy. (Všimněte si také, že stejný druh optimalizace používáme i u samotného vykreslování modelu.)
Poté, co ho vytvoříme, ho přiřadíme k danému scénovému uzlu. Tahle akce není nutná, nicméně se poté již nebudeme muset o daný selector starat (např. ho zahodit, až ho nebudeme potřebovat, atd.).
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();
} |
Nyní přidáme do scény FPS kameru, opět jako v tutoriálu č. 2. V tomto případě ovšem navíc ještě ke kameře přidáme speciálníhí animátor - tzv. Collision Response Animátor. Ten provádí detekci kolizí, a následně také provádí na uzlu, ke kterému je přiřazen, odpovídající reakce. V našem případě se tedy kamera nebude moci pohybovat skrz zdmi mapy a bude ovlivněna gravitací. Po nastavení animátoru a jeho přiřazení ke kameře již nepotřebujeme dělat cokoli jiného, vše je prováděno automaticky, a veškerý kód níže je už pouze pro výběr trianglů a uzlů ve scéně.
Nyní se podívejme blíže na parametry funkce createCollisionResponseAnimator().
První parametr je triangle selector, který drží všechny triangly modelu, se kterým bude testována kolize. Druhý parametr je scénový uzel, který bude ovlivněn detekcí kolizí, v našem případě to bude kamera. Třetí parametr specifikuje, jak velký daný pohyblivý objekt (kamera) bude, a zadává se jako radius ellipsoidu. Můžete si s tím trochu zaexperimentovat, pokud zadáte hodnotu menší, bude kamera schopna se přiblížit se kde zdem blíže, atd. Dalším parametrem je směr a síla gravitace, v případě, že chcete gravitaci zakázat, použijte vector3df(0, 0, 0). Předposledním parametrem je translace (posunutí) ellipsoidu. Bez nastavení tohoto parametru by byla detekce kolizí prováděna přesně rovnoměrně okolo celé kamery, která by pak byla uprostřed daného ellipsoidu. Jenže my jako lidé máme oči (kameru) v horní polovině svého těla, a nikoli uprostřed, takže ji umístíme 50 jednotek nad střed ellipsoidu. A to je celé, detekce kolizí je nyní funkční.
scene::ICameraSceneNode* camera = camera = smgr->addCameraSceneNodeFPS(0,100.0f,300.0f);
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(); |
Protože detekce kolizí není v Irrlichtu příliš náročná, ukážeme si ještě pár pokročilejších věcí. Ještě ale před tím si trošku přípravíme scénu, budeme potřebovat tři animované modely postav, které použijeme na ukázku zmíněného výběru scénových uzlů, jejich dynamické osvětlení, billboard pro zvýraznění místa, kde jsme našli intersekci s mapou, a ano, potřebujeme se zbavit kursoru myši. :)
// skryjeme kurzor
device->getCursorControl()->setVisible(false);
// přidáme 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->setSize(core::dimension2d<f32>(20.0f, 20.0f));
// přidáme 3 animované faerie.
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;
// přidáme dynamické osvětlení
smgr->addLightSceneNode(0, core::vector3df(-60,100,400),
video::SColorf(1.0f,1.0f,1.0f,1.0f),
600.0f); |
Pro zjednodušení budeme provádět výběr trianglů a scénových uzlů přímo v hlavní smyčce programu. Vytvoříme si dva ukazatele na uchovávání aktuálního a minulého vybraného uzlu.
scene::ISceneNode* selectedSceneNode = 0;
scene::ISceneNode* lastSelectedSceneNode = 0;
int lastFPS = -1;
while(device->run()) if (device->isWindowActive())
{
driver->beginScene(true, true, 0);
smgr->drawAll(); |
Po vykreslevní celé scény pomocí smgr->drawAll(), provedeme první výběr: Budeme chtít vědět, na který triangle mapy se zrovna díváme. Navíc budeme také chtít vědet přesný bod Quake 3 mapy na který se díváme. Vytvoříme si tedy 3d čáru začínající v bode pozice kamery, a procházející jejím lookAt-targetem (tedy směr pohledu). Pal se zeptáme collision managera, jestli tato čára koliduje s nějakým trianglem mapy uloženým v triangle selecteru. A pokud ano, vykreslíme zvýrazněně daný trojúhelník, a nastavíme pozici billboardu na místo protnutí.
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));
} |
Další typ výběru v Irrlichtu je výběr scénových uzlů založený na bounding boxech. Každý scénový uzel má svůj bounding box, který ho ohraničuje, takže je kupříkladu velmi rychlé zjistit, na který uzel ve scéně se kamera aktuálně dívá.
Opět, požádáme o to collision managera, a pokud získáme nějaký uzel, otestujeme, zda-li se nejedná o billboard nebo Quake 3 mapu, a případně ho pak zvýrazníme zakázáním nasvětlování v jeho materiálu.
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; |
A to je vše, nyní nám už pouze zbývá ukončit vykreslování.
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;
}
|
Zkompilujte a spusťte.
|
|
|