ODE仿真引擎使用(六)
本次简要介绍碰撞检测。在前面的介绍中,可以知道dynamics和the collision detection在ODE中分别使用。为了计算dynamics,首先通过dWorldCreate ()创造一个世界,其次创造一个body,最后通过dWorldStep()计算dynamics。 A geometry是一个物体的形状。它用于碰撞检测。同时,为了计算碰撞检测,首先需要通过dHashSpaceCreate ()创造一个space,其次在space中创造一个geomotry,最后通过dSpaceCollide()检测一个碰撞。在下面的例子中,在97行,一个sphere geometry通过dCreateSphere()创造。在98行,ball.geom,球的geometry通过dGeomSetBody()与ball.body相关联。在ODE中的物体由body和geometry。 接着,在simLoop函数中调用dSpaceCollide(),nearCallback()的参数o1和o2是可能相互碰撞的几何体。你可以在nearCallback()函数中设置参数,例如接触点的上限,摩擦系数,反弹系数,摩擦模型,刚度系数等等。此外,你必须小心设置参数o1和o2,有些时候有些东西并不会碰撞,为了知道碰撞,通过dCollide()来检测返回值。如果有碰撞,返回值将大于0。 在88行,首先通过dJointGroupCreate()创造一个接触组,它是接触点的容器。在53行,通过dJointGroupEmpty()在每一步的仿真中清除这个容器。
1 // 小球碰撞 2 #include <ode/ode.h> 3 #include <drawstuff/drawstuff.h> 4 5 static dWorldID world; 6 static dSpaceID space; 7 static dGeomID ground; 8 static dJointGroupID contactgroup; 9 static int flag = 0; 10 dsFunctions fn; 11 12 const dReal radius = 0.2; 13 const dReal mass = 1.0; 14 15 typedef struct { 16 dBodyID body; 17 dGeomID geom; 18 } MyObject; 19 MyObject ball; 20 21 static void nearCallback(void *data, dGeomID o1, dGeomID o2) 22 { 23 const int N = 10; 24 dContact contact[N]; 25 26 int isGround = ((ground == o1) || (ground == o2)); 27 28 int n = dCollide(o1,o2,N,&contact[0].geom,sizeof(dContact)); 29 30 if (isGround) { 31 if (n >= 1) flag = 1; 32 else flag = 0; 33 for (int i = 0; i < n; i++) { 34 contact[i].surface.mode = dContactBounce; 35 contact[i].surface.mu = dInfinity; 36 contact[i].surface.bounce = 0.0; // (0.0~1.0) restitution parameter 37 contact[i].surface.bounce_vel = 0.0; // minimum incoming velocity for bounce 38 dJointID c = dJointCreateContact(world,contactgroup,&contact[i]); 39 dJointAttach (c,dGeomGetBody(contact[i].geom.g1),dGeomGetBody(contact[i].geom.g2)); 40 } 41 } 42 } 43 44 static void simLoop (int pause) 45 { 46 const dReal pos,R; 47 48 flag = 0; 49 dSpaceCollide(space,0,&nearCallback); 50 51 dWorldStep(world,0.01); 52 53 dJointGroupEmpty(contactgroup); 54 55 if (flag == 0) dsSetColor(1.0, 0.0, 0.0); 56 else dsSetColor(0.0, 0.0, 1.0); 57 pos = dBodyGetPosition(ball.body); 58 R = dBodyGetRotation(ball.body); 59 dsDrawSphere(pos,R,radius); 60 } 61 62 void start() 63 { 64 static float xyz[3] = {0.0,-3.0,1.0}; 65 static float hpr[3] = {90.0,0.0,0.0}; 66 dsSetViewpoint (xyz,hpr); 67 } 68 69 void prepDrawStuff() { 70 fn.version = DS_VERSION; 71 fn.start = &start; 72 fn.step = &simLoop; 73 fn.command = NULL; 74 fn.stop = NULL; 75 fn.path_to_textures = "../../drawstuff/textures"; 76 } 77 78 int main (int argc, char *argv[]) 79 { 80 dReal x0 = 0.0, y0 = 0.0, z0 = 2.0; 81 dMass m1; 82 83 prepDrawStuff(); 84 85 dInitODE(); 86 world = dWorldCreate(); 87 space = dHashSpaceCreate(0); 88 contactgroup = dJointGroupCreate(0); 89 90 dWorldSetGravity(world,0,0,-0.5); 91 92 // Create a ground 93 ground = dCreatePlane(space,0,0,1,0); 94 95 // Create a ball 96 ball.body = dBodyCreate(world); 97 dMassSetZero(&m1); 98 dMassSetSphereTotal(&m1,mass,radius); 99 dBodySetMass(ball.body,&m1); 100 dBodySetPosition(ball.body, x0, y0, z0); 101 102 ball.geom = dCreateSphere(space,radius); 103 dGeomSetBody(ball.geom,ball.body); 104 105 dsSimulationLoop (argc,argv,352,288,&fn); 106 107 dWorldDestroy (world); 108 dCloseODE(); 109 110 return 0; 111 }
在这个程序中,body和geometry是MyObject结构的成员。dSpaceCollide(), 检测函数, 在simLoop函数中调用。你必须在dWorldStep()之前调用。dSpaceCollide()会寻找接触点,并且它们会受动态计算的约束。dSpaceCollide()会调用一个回调函数nearCallback()。dSpaceCollide()两个可能碰撞的几何体,o1和o2。在28行dCollide()的返回值是接触点的数量。在第30行,if(isGround)语句从第30行到第41行,只处理可能发生碰撞的几何图形。换句话说,我们不关心不是地面的几何图形。如果您想检测非地面的几何图形的碰撞,请取消对if语句的注释。
APIs for collision detection
• dSpaceID dHashSpaceCreate(0) 创建一个space,返回值是space的ID。
• dGeomID dCreatePlane (dSpaceID space, dReal a, dReal b, dReal c, dReal d) 创建一个plane,参数是平面方程中的参数ax+by+cz = d.
• dGeomID dCreateSphere (dSpaceID space, dReal r) 创建一个球体,r是球体的半径。
• void dGeomSetBody (dGeomID geom, dBodyID body) 将geometry与body关联。
• dJointGroupID dJointGroupCreate (0) 创建一个关节组,它是接触点的容器。
• void dJointGroupEmpty (dJointGroupID) 清空关节组。
碰撞检测“world”是通过创建一个space,然后将geoms添加到该space中来创建的。在每个时间步骤中,我们都要为所有相互交叉的geoms生成接触列表。这需要三个函数:
• dCollide 相交两个geoms并生成接触点。
• dSpaceCollide确定空间中的哪些geoms可能会相交,并使用每个候选geoms调用一个回调函数。这并不直接生成接触点,因为用户可能希望特别处理一些对—例如忽略它们或使用不同的接触生成策略。这样的决定是在回调函数中做出的,它可以选择是否为每一对调用dcollision。
• dSpaceCollide2 确定一个空间中的geoms可能与另一个空间中的geoms相交,并使用每个候选对调用一个回调函数。它还可以测试一个单独的非空间geom对一个空间,或者将一个较低包含子层次的空间视为一个geom对一个较高层次的空间。当存在冲突层次结构时,即当存在包含其他空间的空间时,此函数非常有用。
碰撞系统的设计给了用户最大的灵活性来决定哪些对象将互相测试。这就是为什么会有三个碰撞函数,而不是一个只生成所有接触点的函数。
Spaces可以包含其他spaces。这些子空间通常表示位于彼此附近的geom(或其他空间)的集合。这对于通过将冲突世界划分为层次结构来获得额外的冲突性能非常有用。这是一个有用的例子:
假设你有两辆车行驶在某个地形上。每辆车是由许多齿轮组成的。如果所有这些geoms都插入到相同的空间中,那么两辆车之间的碰撞计算时间总是与geoms的总数成比例(甚至与这个数的平方成比例,这取决于使用的空间类型)。
为了加速碰撞,创建了一个单独的空间来表示每辆车。车轨插入车位,车位插入顶层空间。在每一步dSpaceCollide被称为顶层空间。这将在汽车空间(实际上是它们的边界框之间)之间执行一个交叉测试,并在它们接触时调用回调。然后,回调函数可以使用dSpaceCollide2相互测试汽车空间中的geoms。如果两个车不在一起,那么就不会调用回调,也不会浪费时间执行不必要的测试。
如果使用了空间层次结构,则可以递归地调用回调函数,例如,如果dSpaceCollide调用回调函数,而后者又使用相同的回调函数调用dSpaceCollide。在这种情况下,用户必须确保回调函数是可重入的。
下面是一个示例回调函数,它遍历所有空间和子空间,为所有相交的geoms生成所有可能的接触点:
1 void nearCallback (void *data, dGeomID o1, dGeomID o2) 2 { 3 if (dGeomIsSpace (o1) || dGeomIsSpace (o2)) { 4 // colliding a space with something : 空间与某物碰撞: 5 dSpaceCollide2 (o1, o2, data,&nearCallback); 6 // collide all geoms internal to the space(s) 7 if (dGeomIsSpace (o1)) 8 dSpaceCollide ((dSpaceID)o1, data, &nearCallback); 9 if (dGeomIsSpace (o2)) 10 dSpaceCollide ((dSpaceID)o2, data, &nearCallback); 11 } else { 12 // colliding two non-space geoms, so generate contact 13 // points between o1 and o2 14 int num_contact = dCollide (o1, o2, max_contacts,contact_array, skip); 15 // add these contact points to the simulation ... 16 } 17 } 18 ... // collide all objects together 19 dSpaceCollide (top_level_space,0,&nearCallback);
在使用dSpaceCollide或dSpaceCollide2处理space时,不允许空间回调函数修改space。例如,您不能在空间中添加或删除geoms,也不能在空间中重新定位geoms。这样做将在ODE的调试构建中触发一个运行时错误。