(译 注:ODE是个开源的刚性物理引擎,听起来很吓人.其实不复杂,和渲染引擎结合起来并不难.只要几个小步骤,将数据一转换,调用一下ODE过程,渲染引擎 就有了强大的碰撞检测及其他物理特性.这篇文章,通过一个小例子,将Irrlicht引擎与ODE结合起来,可以迅速对ODE有个了解.更进一步,你可以 看ODE的文档及ODE中的多个例子.本文适合对ODE全然没有接触过的人士.(对Irrlicht引擎不熟也没关系,使用的几个参数与Mesh与其他引 擎大同小异)
原文参见http://thomas.webtracker.ch/jahia/Jahia/pid/481
)
本教程讲述如何整合ODE(Open Dynamics Engine:http://www.ode.org)与Irrlicht.
这里不细讲ODE过程,而是通过具体的例子,对ODE有个基本的了解.明白了这个例子后,你可以再详细读ODE手册,可以实现更多的功能.
一,ODE在Windows下的编译要点:
在Makefile.msvc或msvc-dll(在ODE目录/config下)加以下内容:
ifeq ($(BUILD),release)
OPT=2
C_FLAGS+=/Oy /MD
endif
ifeq ($(BUILD),debug)
C_FLAGS+=/MDd
OPT=d
endif
(注意,工程属性要设为/MT,或MTd)
需要下载ODE 0.039或更高版本(带OPCODE支持).Irrlicht为0.6以上版.
二,弹跳的例子
我们下面建立的程序包含起伏的地形,许多方盒子不断下落,在地上弹起.
我们可以在下落的盒子中移动摄影机(W,S,D,A键控制前后左右),鼠标控制视角,Tab键暂停,回车键改变盒子下落距离,ESC键退出.
(程序画面,我不会贴图,是一大堆绿盒子掉到山上)
1,开始:
使用Visual Studio .Net 2003 ,把ODE的include,lib加到系统设置.还有Irrlicht的include,lib.执行目录还需要有Irrlicht的dll.
打开Bounce工程文件.
2.程序入口
非常简单:
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpCmdLine,int nCmdShow)
{
Bounce::Bounceable::RunApplication(); //执行下列弹跳的类
return 0;
}
见BounceMain.cpp, line 12-15
3.弹跳主过程(Bounceable类)
类的具体实现不细讲了,请参见程序.重点讲一下关键部分
参数:
dWorldID world; 物体位置句柄
dSpaceID space; 物体空间句柄(用于碰撞检测的)
dBodyID body; 物体的体积数据句柄,这是用于物理交互作用
dGeomID geom; 物体的几何体句柄,用于碰撞交互作用.
方法函数:
static void nearCollisionCallback(void* data, dGeomID o1, dGeomID o2);
碰撞回调函数:ODE执行过程,两个几何体靠得足够近后,发生碰撞,将调用这个回调函数.
static void QuaternionToEuler(const dQuaternion quaternion,irr::core::vector3df &euler);
旋转参数转换函数:将Irrlicht的旋转参数转换为ODE的四元数组.
void setGeomData(irr::scene::IMesh* m); Convert the irrlicht mesh structure to an OPCODE collision structure for TriMeshes
几何体转换:将Irrlicht的Mesh数据转换为ODE的OPCODE的TriMeshes结构.
void setGeomData();
包围盒转换:将Irrlicht的包围盒转换为OPCODE的碰撞结构,作为物理过程检测的外形.
三,运行ODE仿真
基本仿真的循环过程如下:
1.加一个力,使ODE的物体发生移动位置及旋转.
2.计算不同物体间的碰撞.
3.清除结合的信息
4.得到物体的新的位置及旋转角度,设置并显示物体
5,循环执行第一步
本例子从简考虑,不加外力.所以跳过第一步,我们的ODE仿真过程如下:
if(simulate){
//updateEntitiesBeforePhysics();
// 把碰撞回调函数加入ODE
dSpaceCollide(theSpace,0,&nearCollisionCallback);
// 设定仿真的步长
dWorldStep(theWorld,0.1f);
//可以用下列过程,快一些
//dWorldStepFast1(theWorld,0.1,1);
// 清除节点组
dJointGroupEmpty(theJointGroup);
// 取得新的位置及旋转
updateEntitiesAfterPhysics();
}
见bounce.cpp, line 136-151
碰撞回调函数:
void Bounceable::nearCollisionCallback(void* data, dGeomID o1, dGeomID o2){
int i=0;
dBodyID b1=dGeomGetBody(o1); //几何体1
dBodyID b2=dGeomGetBody(o2); //几何体2
if(b1 && b2 && dAreConnectedExcluding(b1,b2,dJointTypeContact))return; //如果两个物体是连接的,不检测
dContact contact[MAX_CONTACTS];
for(i=0;i contact[i].surface.mode=dContactBounce | dContactSoftCFM;
contact[i].surface.mu=dInfinity;
contact[i].surface.mu2=0;
contact[i].surface.bounce=1e-5f;
contact[i].surface.bounce_vel=1e-9f;
contact[i].surface.soft_cfm=1e-6f;
}
int numc=dCollide(o1,o2,MAX_CONTACTS,&contact[0].geom,sizeof(dContact)); //计算接触点
//如果有其他的力的作用,我们可以加在这里,进行检测
if(numc>0){ //有接触
for(i=0;i
dJointID c=dJointCreateContact(theWorld,theJointGroup,&contact[i]); //取得接触点
dJointAttach(c,b1,b2); //添加影响(本例是反弹),我们可以把碰撞发声加在这里
}
}
}
见bounce.cpp, line 326-358
取得物体变化结果:
void Bounceable::updateEntitiesAfterPhysics(){
irr::core::vector3df pos;
irr::core::vector3df rot;
std::list::iterator iter=NULL;
for(iter=bounceables.begin();iter!=bounceables.end();++iter){
Bounceable* entity=(*iter);
dGeomID geom=entity->geom;
if(geom!=0){
dReal* ode_pos=(dReal*)dGeomGetPosition(geom); //从ODE得到新位置
pos.set((irr::f32)ode_pos[0],(irr::f32)ode_pos[1],(irr::f32)ode_pos[2]); //设置Irrlicht物体的位置
entity->node->setPosition(pos);
dQuaternion result;
dGeomGetQuaternion(geom, result); //取得ODE旋转数值
QuaternionToEuler(result,rot); //转换一下
entity->node->setRotation(rot); //设置Irrlicht的物体旋转
}
}
}
见bounce.cpp, line 174-196
ODE的旋转四元数组转换为Irrlicht的旋转参数:
void Bounceable::QuaternionToEuler(const dQuaternion quaternion, vector3df &euler){
dReal w,x,y,z;
w=quaternion[0];
x=quaternion[1];
y=quaternion[2];
z=quaternion[3];
double sqw = w*w;
double sqx = x*x;
double sqy = y*y;
double sqz = z*z;
euler.Z = (irr::f32) (atan2(2.0 * (x*y + z*w),(sqx - sqy - sqz + sqw)) //不多结实,一目了然
*irr::core::GRAD_PI);
euler.X = (irr::f32) (atan2(2.0 * (y*z + x*w),(-sqx - sqy + sqz + sqw))
*irr::core::GRAD_PI);
euler.Y = (irr::f32) (asin(-2.0 * (x*z - y*w))
*irr::core::GRAD_PI);
}
见bounce.cpp, line 359-375
把Irrlicht的Meshes几何体数据转换为ODE的TriMesh几何数据:
(不多解释了,无非是不同的顶点和面)
void Bounceable::setGeomData(irr::scene::IMesh* m){
// do nothing if the mesh or node is NULL
if(mesh==NULL || node==NULL) return;
int i,j,ci,cif,cv;
indexcount=0;
vertexcount=0;
// count vertices and indices
for(i=0;igetMeshBufferCount();i++){
irr::scene::IMeshBuffer* mb=mesh->getMeshBuffer(i);
indexcount+=mb->getIndexCount();
vertexcount+=mb->getVertexCount();
}
// build structure for ode trimesh geom
vertices=new dVector3[vertexcount];
indices=new int[indexcount];
// fill trimesh geom
ci=0; // current index in the indices array
cif=0; // offset of the irrlicht-vertex-index in the vetices array
cv=0; // current index in the vertices array
for(i=0;igetMeshBufferCount();i++){
irr::scene::IMeshBuffer* mb=mesh->getMeshBuffer(i);
// fill indices
irr::u16* mb_indices=mb->getIndices();
for(j=0;jgetIndexCount();j++){
// scale the indices from multiple meshbuffers to single index array
indices[ci]=cif+mb_indices[j];
ci++;
}
// update the offset for the next meshbuffer
cif=cif+mb->getVertexCount();
// fill vertices
if(mb->getVertexType()==irr::video::EVT_STANDARD){
irr::video::S3DVertex* mb_vertices=
(irr::video::S3DVertex*)mb->getVertices();
for(j=0;jgetVertexCount();j++){
vertices[cv][0]=mb_vertices[j].Pos.X;
vertices[cv][1]=mb_vertices[j].Pos.Y;
vertices[cv][2]=mb_vertices[j].Pos.Z;
cv++;
}
}else if(mb->getVertexType()==irr::video::EVT_2TCOORDS){
irr::video::S3DVertex2TCoords* mb_vertices=
(irr::video::S3DVertex2TCoords*)mb->getVertices();
for(j=0;jgetVertexCount();j++){
vertices[cv][0]=mb_vertices[j].Pos.X;
vertices[cv][1]=mb_vertices[j].Pos.Y;
vertices[cv][2]=mb_vertices[j].Pos.Z;
cv++;
}
}
}
irr::core::vector3df pos=node->getPosition();
// build the trimesh data
dTriMeshDataID data=dGeomTriMeshDataCreate();
dGeomTriMeshDataBuildSimple(data,(dReal*)vertices,
vertexcount, indices, indexcount);
// build the trimesh geom
geom=dCreateTriMesh(space,data,0,0,0);
// set the geom position
dGeomSetPosition(geom,pos.X,pos.Y,pos.Z);
// lets have a pointer to our bounceable
// we could need this in the collision callback
dGeomSetData(geom,(void*)this);
// in our application we don't want geoms
// converted from meshes to have a body
dGeomSetBody(geom,0);
}
见bounce.cpp, line 261-319
设置Irrlicht物体的包围盒为ODE检测的物体及几何体:
方盒子的包围盒与物体一样,圆球就不行了.当然,针对不同的物体,我们应该制作简单的碰撞几何体,要是都把本身的Mesh去检测,那非把ODE累死不可.
(据说已有硬件支持的ODE了,不过什么时候能广泛使用还很难说)
下列过程也不必细解释了
void Bounceable::setGeomData(){
// get the boundingbox
irr::core::aabbox3d box=node->getBoundingBox();
irr::core::vector3df extend=box.getExtend();
// get the position of the scenenode
irr::core::vector3df pos=node->getPosition();
// build a box shaped geometry for ODE
geom=dCreateBox(space,(dReal)extend.X,(dReal)extend.Y,(dReal)extend.Z);
// set the position of the ODE geom
dGeomSetPosition(geom,pos.X,pos.Y,pos.Z);
// set a pointer to our Bounceable,
// this will come in handy when we do more complicated collisions
dGeomSetData(geom,(void*)this);
// create a body for this object
body=dBodyCreate(world);
// setup the mass
dMassSetBox(&mass,5.0,(dReal)extend.X,(dReal)extend.Y,(dReal)extend.Z);
// combine body and mass
dBodySetMass(body,&mass);
// add the body to the geom
dGeomSetBody(geom,body);
// set the bodys position (same as geom position)
dBodySetPosition(body,pos.X,pos.Y,pos.Z);
dBodySetData(body,(void*)this);
}
见bounce.cpp, line 262-277
结束语:
经过如上简单过程,Irrlicht有了强大的碰撞检测功能.别的渲染引擎也可如法炮制哦