irrwowview

Tutorial 7: Collision Detection and Response

Tutorial 7: Collision Detection and Response
在这个教程中,我要用IrrLicht引擎演示如何碰撞检测。我会描述3种方法:根据3d世界的自动碰撞检测,手动三角形拾取和手动场景节点拾取。

这个程序的效果图如下:

 

Lets start!

首先,我们使用的教程2的程序,加载并显示quake3的地图。我们要在地图中走动并进行三角形的拾取。另外我们放置3个动画模型用于场景节点的拾取。下面是引擎启动和加载quake3地图的代码。我就不解释了,因为你应该在教程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.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));

 

到现在为止,都还不错,我们已经好像教程2那样加载了quake3地图。接着,来到了不同的地方:我们创建一个三角形选择器。三角形选择器(ITriangleSelector)是一个能够从场景中各个物体之间选取三角形的类。有几种不同的三角形选择器,而且都可以用ISceneManager的方法来创建。在这个例子中,我们创建一个八叉数三角形选择器(OctTreeTriangleSelector),这样可以用八叉数(octree)优化显示的三角形。这是对于类似quake3的大型地图是十分有用的。
创建好三角形选择器后,我们把它依附上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那样,我们添加一个能够在quake3地图中移动的第一人称射击摄像机(first person shooter camera)。而这次,我们加上一个特别的动画到摄像机:一个碰撞响应动画(Collision Response animator)。这个动画能使场景节点不能穿墙并贴着地面移动。要创建这样的动画,只需告诉它世界是如何的,要绑定场景节点的大小和重力加速等。摄像机绑定上这种碰撞响应动画(Collision Response animator)后,摄像机就能自动进行碰撞检测了,而接着是拾取的代码。还要注意一个很cool的特征:碰撞响应动画(Collision Response animator)不但能绑定到摄像机,而且还能绑定到其它场景节点。并且这个动画还能集合到其它场景节点动画。这样,在IrrLicth引擎中使用碰撞检测和响应是十分十分简单的。
下面要讲的是createCollisionResponseAnimator()这个函数的参数。第一个参数是三角形选择器,用来指定世界的碰撞检测类型。第二个参数是要进行碰撞检测的场景节点,在这里是一个摄像机。第三个参数是要碰撞检测的场景节点大小,分别对应椭圆的三个半径。尝试调小椭圆的半径,这样可以使摄像机走到更近墙的位置。第四个参数是重力加速度。你要取消重力加速,可以设这个参数为(000)。第五个参数是偏移:不设这个参数,则摄像机放在椭圆体的中间。而作为人类,眼睛是在身体的上面而非中间。因此我们放置摄像机节点在椭圆体中心偏上50个单位的位置。就这样,碰撞检测就可以开始工作了。

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

碰撞检测在IrrLicht中并没有什么大不了,而在下一节中还会描述两种不同的拾取方法。在这之前,要准备好场景。需要三个用于拾取的角色动画,一个动态光照着它们,并用公告板绘制出焦点,除去鼠标光标。

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

为了简单,我在绘制循环中完成拾取。我们创建两个指针分别用于保存当前和最近一次选定的场景节点,并开始循环。

         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()绘制好整个场景后,我们要做第一个拾取:想知道焦点落在哪个三角形上。另外,还想知道焦点落在quake3地图的精确位置。为此,我们创建一个开始于摄像机坐标,指向焦点的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));

 

                 }

IrrLicht引擎的另一种场景节点拾取是基于包围体的。因为每一个场景节点都设置了包围体,所以能很快地知道摄像机注视的场景节点。我们再一次查询碰撞管理器,如果能获得一个场景节点,则把该场景节点(非地图,非公告板)的材质灯光关闭。

                 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;

 

}

 

 

 

 

 

posted on 2008-07-04 11:05 shjy 阅读(452) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理