在此例中,我将会告诉大家如何使用IRR引擎进行鼠标拣选和碰撞检测。

/*
在此例中,我将会告诉大家如何使用IRR引擎进行鼠标拣选和碰撞检测。
我将会说明3种方法:与3D世界的场景节点碰撞检测,普通的三角形拣选,以及普通的场景节点拣选

首先,我们先打开02.Quake3Map的例子,读取一个Q3的地图,我们在这张地图上进行移动
(即增加了与场景节点之间的碰撞检测)并且对这个地图进行其三角形的拣选(进行了鼠标拣选)。
另外我们增加了三个模型,目的是实现我们的场景节点拣选。

下面的简单代码我不再加注释,若不理解,请认真看例子02.Quake3Map
*/
#include <irrlicht.h>
#include <iostream>

using namespace irr;

#pragma comment(lib, "Irrlicht.lib")


int main()
{
// 用户进行设备选择

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) Burning's 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_BURNINGSVIDEO;break;
   case 'f': driverType = video::EDT_NULL;     break;
   default: return 0;
}

// 创建irr设备

IrrlichtDevice *device =
   createDevice(driverType, core::dimension2d<s32>(640, 480), 16, false);
  
if (device == 0)
   return 1;

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));

/*
到现在为止,我们读取了一个Quake3的地形。现在,我们来增加一些与例子02不同的一些东西:
我们创建一个三角形选择器。TriangleSelector(三角形选择器)是一个类对象,它能够从
场景节点中拣选出其中的三角形。
拿碰撞检测来说吧,实际上就是有很多被场景管理器创建出的三角形选择器共同实现的。
在本例中,我们创建了一个八叉树三角型选择器,它在巨大的场景中常被使用,能够
有效又高效的进行碰撞检测。
在我们创建过三角型选择器之后,我们把它绑定在一个Q3场景节点上。当然我们也可以不进行
绑定,但是若绑定后,我们就可以不再对其进行复杂的管理了,例如,它会随场景一起释放掉,
否则,我们就需要手动drop掉。
*/

scene::ITriangleSelector* selector = 0;

if (q3node)
{  
   q3node->setPosition(core::vector3df(-1350,-130,-1400));

   selector = smgr->createOctTreeTriangleSelector(q3levelmesh->getMesh(0), q3node, 128);
   q3node->setTriangleSelector(selector);
   selector->drop();
}


/*
我们象例子02一样在场景中增加了一个FPS类型的摄象机。但是,我们这次为摄象机增加了一个
Animator动画器:使它成为一个能碰撞检测的摄象机。这样的话摄象机就会与场景节点做一些处理,
使它不会穿越墙壁和地表。
想做这样的一个摄象机,我们仅需要告诉Animator动画器一些场景节点的属性,这个场景节点多大,
重力多大,设备完毕后再将Animator动画器与摄象机绑定就OK了,不需要再做其他的。一切都由
引擎做完,在这之下的代码目的仅仅是进行拣选的代码而已了。

请注意另一个很酷的特性:这个碰撞检测动画器能够与非摄象机的其他的所有场景节点进行绑定。
同样,一个摄象机上也允许绑定多个动画器。正因为这个特性的寻在,碰撞检测在Irr引擎中的实现
是非常容易做到的。

现在我们介绍一下createCollisionResponseAnimator()函数的各个参数。
第一个参数是已经指定了三角型选择器。第二个参数是进行碰撞检测的场景节点,在我们这个例子里面,
这个需要碰撞检测的场景节点是摄象机对象。第三个参数是进行检测的对象的大小,它是一个椭球状
的包围体,我们可以设置它的三围。第四个参数是重力的方向和强度。你若是设置为0,0,0则代表无重力。
最后一个参数是一个偏移:默认的时候摄象机是在椭球包围体中间,但是人类眼睛的话,是在身体上方,
所以我们应当设置一个偏移量,使摄象机处于包围体的上方。现在就OK了,碰撞检测设置OK了。
*/

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();

/*
因为碰撞检测不是Irr引擎的主要研究部分。我将在一会告诉大家如何去实现两种不同的
鼠标拣选,但是在此之前,我们再场景加一些东西。我需要三个角色用来协助我的拣选测试,
还需要给它们一点动态光照亮他们。另外,我们在鼠标处绘制一个公告版,让它去替代鼠标吧。
*/

// 屏蔽鼠标指针

device->getCursorControl()->setVisible(false);

// 增加一个公告版场景节点

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));

// 增加了三个活生生的仙子角色

video::SMaterial material;
material.Textures[0] = 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.Textures[0] = 0;
material.Lighting = false;

// 增加一个光源

smgr->addLightSceneNode(0, core::vector3df(-60,100,400),
   video::SColorf(1.0f,1.0f,1.0f,1.0f),
   2000.0f);


/*
为了不使例子变的太复杂,我在绘制循环中进行鼠标拣选。
另外我们创建了两个指针分别用来存储:当前的场景节点和最后选择的场景节点。
那么,我们开始循环。
*/


scene::ISceneNode* selectedSceneNode = 0;
scene::ISceneNode* lastSelectedSceneNode = 0;


int lastFPS = -1;

while(device->run())
if (device->isWindowActive())
{
   driver->beginScene(true, true, 0);

   smgr->drawAll();

   /*
   在我们使用场景管理器DrawAll()了之后,我们开始做第一个鼠标拣选:我们们想知道我们鼠标
   在当前世界中选择了哪一个三角面。另外,我们想知道我们鼠标当前位置。
   那么,我们创建一个3D的拣选线,它从我们的摄象机出发到我们的摄象机观察点。然后我们
   告诉碰撞管理器,如果我们这条线和场景中的一个三角面碰撞了,那么我们就描绘这个三角面,
   并且设置我们的公告版位置在交点处。(此时的公告版位置与鼠标类似,然而会多一个深度概念)
   */

   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));
   }


   /*
   另外一个Irr引擎支持的拣选是基于场景节点包围盒的拣选。每一个场景节点在创建时都必定
   有自己的包围盒,所以我们可以很快的获取摄象机拣选的那个场景节点。
   好,我们再次设置一个碰撞管理器,当我们选择到一个非公告版和地图的场景节点时,让它材质高亮。
   */

   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;


   /*
   这样,拣选的任务就完成了,我们需要做的就是EndScene了。
   */

   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;
}