[ODE] cylinder collisions
Ivo Kwee
ivo at idsia.ch
Wed Jan 23 09:10:02 2002
Hi,
I needed the cylinder primitive, so started to implement collision
functions for the (flat ended) cylinder. The cylinder/plane kind of
works, but I got stuck with the cylinder/cylinder collisions function.
I got this far:
- Collisions on the sides are detected if the minimum distance between
the cylinder axes are less than (cyl1->radius+cyl2->radius). The
collision point is then defined in the center but the collision point
must be within the length of both cylinders.
- Collisions on the top/bottom sides are checked by creating an
auxiliary plane coinciding the side, and performing a cylinder/plane
collision. If all the contact points are inside the radius of the disk
everything is OK. If they are all out, no collision is reported. The
problem is if the other cylinder collides on the rim.
So how should I determine the contact points on the rim? Should I do
something like the CappedCylinder but moving "disks" instead of spheres?
Can I maybe reuse Box-routines with appropriate rotated boxes (which are
the projections of the cylinder)?
There is very little on cylinder collissions I could find on the www.
Any ideas?
More generally, are Vortex/Havok using analyic collision detection for
primitives? Or do they triangulate bodies first?
Ivo
==================== CODE SNIPPET ========================
// Given a disk centered at c, radius r, with rotation matrix R and
// a plane with normal vector n, and n.x = z, this computes the point p
// on the disk circumference closest to the plane. Knowing the equation
// of the disk and plane,
// disk: p = rx.cos(phi) + ry.sin(phi)
// plane: p.n = z
// phi can be solved:
// phi = asin ( (z-n.c) / d ) - arctan ( n.rx / n.ry )
// where
// d = (n.rx)^2 + (n.ry)^2
static
void collideDiskPlane ( dVector3 c, dReal r, dMatrix3 R,
dVector3 n, dReal z,
dReal *phi)
{
dReal dx, dy, rr;
// dVector3 nx, ny;
// nx[0] = r*R[0]; nx[1] = r*R[4]; nx[2] = r*R[8];
// ny[0] = r*R[1]; ny[1] = r*R[5]; ny[2] = r*R[9];
dx = r * dDOT14(n,R);
dy = r * dDOT14(n,R+1);
rr = sqrt(dx*dx + dy*dy);
// printf("collideDiskPlane::dx = %f, dy = %f, d = %f\n",dx, dy, rr);
// The collision candidates are where the sinus is +1/-1.
double z1 = rr + dDOT(n,c);
double z2 = -rr + dDOT(n,c);
dReal phi1 = 0.5*M_PI - atan2(dx,dy);
dReal phi2 = -0.5*M_PI - atan2(dx,dy);
/*
printf("collideDiskPlane::z=%f \t z1= %f z2=%f \n",z,z1,z2);
printf("collideDiskPlane::phi1= %f phi2=%f\n",phi1,phi2);
*/
/* if ( rr < 0.001*r ) {
printf("collideDiskPlane::flat on side??\n");
}
*/
// Return deepest point
// *phi = fabs(z1-z) < fabs(z2-z) ? phi1 : phi2;
*phi = (z1-z) < (z2-z) ? phi1 : phi2;
return;
}
int dCollideYP (const dxGeom *o1, const dxGeom *o2, int flags,
dContactGeom *contact, int skip)
{
dIASSERT (skip >= (int)sizeof(dContactGeom));
dIASSERT (o1->_class->num == dCylinderClass);
dIASSERT (o2->_class->num == dPlaneClass);
dxCylinder *cyl = (dxCylinder*) CLASSDATA(o1);
dxPlane *plane = (dxPlane*) CLASSDATA(o2);
//@@@ need early return check?
/*
if ( dDOT(plane->p,o1->pos)-plane->p[3] > (cyl->lz+cyl->radius) )
return 0;
*/
// collide the deepest disk with the plane
dReal sign = (dDOT14 (plane->p,o1->R+2) > 0) ? REAL(-1.0) : REAL(1.0);
dVector3 p, cp;
p[0] = o1->pos[0] + o1->R[2] * cyl->lz * REAL(0.5) * sign;
p[1] = o1->pos[1] + o1->R[6] * cyl->lz * REAL(0.5) * sign;
p[2] = o1->pos[2] + o1->R[10] * cyl->lz * REAL(0.5) * sign;
dReal phi;
collideDiskPlane( p, cyl->radius, o1->R, plane->p, plane->p[3], &phi );
cp[0] = p[0] + cyl->radius * ( o1->R[0]*cos(phi) + o1->R[1]*sin(phi) );
cp[1] = p[1] + cyl->radius * ( o1->R[4]*cos(phi) + o1->R[5]*sin(phi) );
cp[2] = p[2] + cyl->radius * ( o1->R[8]*cos(phi) + o1->R[9]*sin(phi) );
// check depth
dReal k = dDOT (cp, plane->p);
dReal depth = plane->p[3] - k;
if (depth < 0) return 0;
// printf("dCollideYP:: c= %f %f %f \td= %f\n",cp[0],cp[1],cp[2],depth);
contact->normal[0] = plane->p[0];
contact->normal[1] = plane->p[1];
contact->normal[2] = plane->p[2];
contact->pos[0] = cp[0];
contact->pos[1] = cp[1];
contact->pos[2] = cp[2];
contact->depth = depth;
int ncontacts = 1;
// check opposite disk in case cylinder is almost parallel to
// the plane. And add a second contact point on opposite side.
// @@@ any faster check?
if ((flags & NUMC_MASK) >= 2) {
// collide the other disk with the plane
cp[0] -= o1->R[2] * cyl->lz * sign;
cp[1] -= o1->R[6] * cyl->lz * sign;
cp[2] -= o1->R[10] * cyl->lz * sign;
k = dDOT (cp, plane->p);
depth = plane->p[3] - k;
if (depth >= 0) {
// printf("dCollideYP:: cylinder parallel! depth = %f\n",depth);
// printf("dCollideYP:: cp2 = %f %f %f\n",cp[0],cp[1],cp[2]);
dContactGeom *c2 = CONTACT(contact,skip);
c2->normal[0] = plane->p[0];
c2->normal[1] = plane->p[1];
c2->normal[2] = plane->p[2];
c2->pos[0] = cp[0];
c2->pos[1] = cp[1];
c2->pos[2] = cp[2];
c2->depth = depth;
ncontacts = 2;
}
}
// Create extra contacts if the cylinder stands up
int maxc = flags & NUMC_MASK;
if (maxc < 1) maxc = 1;
if (maxc > 3) maxc = 3; // no more than 3 contacts per box allowed
int nc = maxc - ncontacts;
if ( ncontacts < maxc && (flags & NUMC_MASK) >= 2 ) {
for ( int i = 0; i < nc; i++) {
phi += 2 * M_PI / maxc;
cp[0] = p[0] + cyl->radius*( o1->R[0]*cos(phi) + o1->R[1]*sin(phi) );
cp[1] = p[1] + cyl->radius*( o1->R[4]*cos(phi) + o1->R[5]*sin(phi) );
cp[2] = p[2] + cyl->radius*( o1->R[8]*cos(phi) + o1->R[9]*sin(phi) );
// check depth
dReal k = dDOT (cp, plane->p);
dReal depth = plane->p[3] - k;
if (depth >= 0) {
// printf("dCollideYP:: cylinder on side! depth = %f\n",depth);
// printf("dCollideYP:: cp2 = %f %f %f\n",cp[0],cp[1],cp[2]);
dContactGeom *c2 = CONTACT(contact, ncontacts*skip);
c2->normal[0] = plane->p[0];
c2->normal[1] = plane->p[1];
c2->normal[2] = plane->p[2];
c2->pos[0] = cp[0];
c2->pos[1] = cp[1];
c2->pos[2] = cp[2];
c2->depth = depth;
++ncontacts;
}
}
}
// printf("dCollideYP:: ncontacts = %d\n",ncontacts);
for (int i=0; i < ncontacts; i++) {
CONTACT(contact,i*skip)->g1 = const_cast<dxGeom*> (o1);
CONTACT(contact,i*skip)->g2 = const_cast<dxGeom*> (o2);
}
return ncontacts;
}
// Collide two cylinders
int dCollideYY (const dxGeom *o1, const dxGeom *o2,
int flags, dContactGeom *contact, int skip)
{
int i;
const dReal tolerance = REAL(1e-5);
dIASSERT (skip >= (int)sizeof(dContactGeom));
dIASSERT (o1->_class->num == dCylinderClass);
dIASSERT (o2->_class->num == dCylinderClass);
contact->g1 = const_cast<dxGeom*> (o1);
contact->g2 = const_cast<dxGeom*> (o2);
dxCylinder *cyl1 = (dxCylinder*) CLASSDATA(o1);
dxCylinder *cyl2 = (dxCylinder*) CLASSDATA(o2);
// copy out some variables, for convenience
dReal lz1 = cyl1->lz * REAL(0.5);
dReal lz2 = cyl2->lz * REAL(0.5);
dReal *pos1 = o1->pos;
dReal *pos2 = o2->pos;
dReal axis1[3],axis2[3];
axis1[0] = o1->R[2];
axis1[1] = o1->R[6];
axis1[2] = o1->R[10];
axis2[0] = o2->R[2];
axis2[1] = o2->R[6];
axis2[2] = o2->R[10];
// first do a quick check
dReal alpha1,alpha2;
lineClosestApproach( o1->pos, axis1, o2->pos, axis2, &alpha1,
&alpha2 );
dReal dx = pos1[0] + alpha1*axis1[0];
dReal dy = pos1[1] + alpha1*axis1[1];
dReal dz = pos1[2] + alpha1*axis1[2];
dx -= (pos2[0] + alpha2*axis2[0]);
dy -= (pos2[1] + alpha2*axis2[1]);
dz -= (pos2[2] + alpha2*axis2[2]);
dReal r = sqrt(dx*dx + dy*dy + dz*dz);
if ( r > ( cyl1->radius + cyl2->radius ) )
return 0;
// First case is easy: if the closest points are within the
// length of the cylinders, there is only one collision point
// just in the middle.
// @@@ IVO: what if they are parallel??
if ( alpha1 >= -lz1 && alpha1 <= lz1 &&
alpha2 >= -lz2 && alpha2 <= lz2 ) {
contact->pos[0] = 0.5*(pos1[0]+alpha1*axis1[0] +
pos2[0]+alpha2*axis2[0]);
contact->pos[1] = 0.5*(pos1[1]+alpha1*axis1[1] +
pos2[1]+alpha2*axis2[1]);
contact->pos[2] = 0.5*(pos1[2]+alpha1*axis1[2] +
pos2[2]+alpha2*axis2[2]);
contact->normal[0] = dx / r;
contact->normal[1] = dy / r;
contact->normal[2] = dz / r;
contact->depth = cyl1->radius + cyl2->radius - r;
return 1;
}
// Else, the check is more elaborate. We have to check top and bottom
// disk of each cylinder against other cylinder. We do as follows:
// create a auxiliary plane at top/bottom disk, collide other cylinder
// with this plane, check if collision point(s) are inside the radius,
// clip CP within circle. Repeat with other cylinder.
dxGeom *g = dCreatePlane(0,0,0,0,0);
dxPlane *pl = (dxPlane*) CLASSDATA(g);
dVector3 ca;
int ntouch = 0;
pl->p[0] = axis2[0];
pl->p[1] = axis2[1];
pl->p[2] = axis2[2];
ca[0] = pos2[0] + lz2 * axis2[0];
ca[1] = pos2[1] + lz2 * axis2[1];
ca[2] = pos2[2] + lz2 * axis2[2];
pl->p[3] = dDOT( pl->p, ca);
ntouch = dCollideYP( o1, g, flags, contact, skip);
if ( ntouch ) {
int inside = 0;
for (int i = 0; i < ntouch; ++i) {
if ( dDISTANCE(contact->pos[i], ca) < cyl2->radius )
++inside;
}
// if all contactpoints are inside: easy
if (inside==ntouch) {
printf("dCollideYY:: cylinder A collision on top B\n");
printf("dCollideYY:: all contacts inside\n");
return ntouch;
}
else {
printf("dCollideYY:: ntouch = %d inside = %d\n",ntouch,inside);
// clip_contacts( ntouch, contact, ca, cyl2->radius );
}
}
// Need to check other side, and other cylinder too...
/*
dCollideYP( o1, g2b, flags, contact, skip);
dCollideYP( o2, g1b, flags, contact, skip);
dCollideYP( o2, g1t, flags, contact, skip);
*/
return 0;
}