[ODE] Bug in CCylynder vs CCylynder collision test

Michal Bacik michal at lonelycatgames.com
Tue Feb 25 10:16:01 2003


> There seems to be bug in CCylynder vs CCylynder collision test - when they
> are dropped one onto another, sharing exactly the same XY coordinates, the
> collision won't detect it and they penetrate without generating contact
> points.
>
> How to repro: run test_boxstack.cpp example, press 'r' (no random
> position),
> and press 'c' two times. You should see 2 capped cylinders
> falling one onto
> the other (and actually go into each other).
>
> - m

I'm posting fix suggestion for the bug above. Can someone check it and/or
add it into the CVS?
Btw, it's not about sharing XY coords, but rather about geoms' axes being
near-parallel.
- Michal



int dCollideCCylinderCCylinder (dxGeom *o1, dxGeom *o2,
    int flags, dContactGeom *contact, int skip)
{
  int i;
  const dReal tolerance = REAL(1e-5);

  dIASSERT (skip >= (int)sizeof(dContactGeom));
  dIASSERT (o1->type == dCCylinderClass);
  dIASSERT (o2->type == dCCylinderClass);
  dxCCylinder *cyl1 = (dxCCylinder*) o1;
  dxCCylinder *cyl2 = (dxCCylinder*) o2;

  contact->g1 = o1;
  contact->g2 = 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];

  dReal alpha1,alpha2,sphere1[3],sphere2[3];
  int fix1 = 0;  // 0 if alpha1 is free, +/-1 to fix at +/- lz1
  int fix2 = 0;  // 0 if alpha2 is free, +/-1 to fix at +/- lz2

  for (int count=0; count<9; count++) {
    // find a trial solution by fixing or not fixing the alphas
    if (fix1) {
      if (fix2) {
 // alpha1 and alpha2 are fixed, so the solution is easy
 if (fix1 > 0) alpha1 = lz1; else alpha1 = -lz1;
 if (fix2 > 0) alpha2 = lz2; else alpha2 = -lz2;
 for (i=0; i<3; i++) sphere1[i] = pos1[i] + alpha1*axis1[i];
 for (i=0; i<3; i++) sphere2[i] = pos2[i] + alpha2*axis2[i];
      }
      else {
 // fix alpha1 but let alpha2 be free
 if (fix1 > 0) alpha1 = lz1; else alpha1 = -lz1;
 for (i=0; i<3; i++) sphere1[i] = pos1[i] + alpha1*axis1[i];
 alpha2 = (axis2[0]*(sphere1[0]-pos2[0]) +
    axis2[1]*(sphere1[1]-pos2[1]) +
    axis2[2]*(sphere1[2]-pos2[2]));
 for (i=0; i<3; i++) sphere2[i] = pos2[i] + alpha2*axis2[i];
      }
    }
    else {
      if (fix2) {
 // fix alpha2 but let alpha1 be free
 if (fix2 > 0) alpha2 = lz2; else alpha2 = -lz2;
 for (i=0; i<3; i++) sphere2[i] = pos2[i] + alpha2*axis2[i];
 alpha1 = (axis1[0]*(sphere2[0]-pos1[0]) +
    axis1[1]*(sphere2[1]-pos1[1]) +
    axis1[2]*(sphere2[2]-pos1[2]));
 for (i=0; i<3; i++) sphere1[i] = pos1[i] + alpha1*axis1[i];
      }
      else {
 // let alpha1 and alpha2 be free
 // compute determinant of d(d^2)\d(alpha) jacobian
 dReal a1a2 = dDOT (axis1,axis2);
 dReal det = REAL(1.0)-a1a2*a1a2;
 if (det < tolerance) {
   // the cylinder axes (almost) parallel, so we will generate up to two
   // contacts. the solution matrix is rank deficient so alpha1 and
   // alpha2 are related by:
   //       alpha2 =   alpha1 + (pos1-pos2)'*axis1   (if axis1==axis2)
   //    or alpha2 = -(alpha1 + (pos1-pos2)'*axis1)  (if axis1==-axis2)
   // first compute where the two cylinders overlap in alpha1 space:
   if (a1a2 < 0) {
     axis2[0] = -axis2[0];
     axis2[1] = -axis2[1];
     axis2[2] = -axis2[2];
   }
   dReal q[3];
   for (i=0; i<3; i++) q[i] = pos1[i]-pos2[i];
   dReal k = dDOT (axis1,q);
   dReal a1lo = -lz1;
   dReal a1hi = lz1;
   dReal a2lo = -lz2 - k;
   dReal a2hi = lz2 - k;
   dReal lo = (a1lo > a2lo) ? a1lo : a2lo;
   dReal hi = (a1hi < a2hi) ? a1hi : a2hi;
   if (lo <= hi) {
     int num_contacts = flags & NUMC_MASK;
     if (num_contacts >= 2 && lo < hi) {
       // generate up to two contacts. if one of those contacts is
       // not made, fall back on the one-contact strategy.
       for (i=0; i<3; i++) sphere1[i] = pos1[i] + lo*axis1[i];
       for (i=0; i<3; i++) sphere2[i] = pos2[i] + (lo+k)*axis2[i];
       int n1 = dCollideSpheres (sphere1,cyl1->radius,
     sphere2,cyl2->radius,contact);
       if (n1) {
  for (i=0; i<3; i++) sphere1[i] = pos1[i] + hi*axis1[i];
  for (i=0; i<3; i++) sphere2[i] = pos2[i] + (hi+k)*axis2[i];
  dContactGeom *c2 = CONTACT(contact,skip);
  int n2 = dCollideSpheres (sphere1,cyl1->radius,
       sphere2,cyl2->radius, c2);
  if (n2) {
    c2->g1 = o1;
    c2->g2 = o2;
    return 2;
  }
       }
     }

     // just one contact to generate, so put it in the middle of
     // the range
     alpha1 = (lo + hi) * REAL(0.5);
     alpha2 = alpha1 + k;
   }else
     {
     //collide by their capped sphere-halfs, which are closer to neighbour's
position
         alpha1 = lz1;
         if(dDOT(q,axis1)>0.0f)
            alpha1 = -alpha1;
         alpha2 = lz2;
         if(dDOT(q,axis2)<0.0f)
            alpha2 = -alpha2;
     }
     for (i=0; i<3; i++) sphere1[i] = pos1[i] + alpha1*axis1[i];
     for (i=0; i<3; i++) sphere2[i] = pos2[i] + alpha2*axis2[i];
     return dCollideSpheres (sphere1,cyl1->radius,
        sphere2,cyl2->radius,contact);
 }
 det = REAL(1.0)/det;
 dReal delta[3];
 for (i=0; i<3; i++) delta[i] = pos1[i] - pos2[i];
 dReal q1 = dDOT (delta,axis1);
 dReal q2 = dDOT (delta,axis2);
 alpha1 = det*(a1a2*q2-q1);
 alpha2 = det*(q2-a1a2*q1);
 for (i=0; i<3; i++) sphere1[i] = pos1[i] + alpha1*axis1[i];
 for (i=0; i<3; i++) sphere2[i] = pos2[i] + alpha2*axis2[i];
      }
    }

    // if the alphas are outside their allowed ranges then fix them and
    // try again
    if (fix1==0) {
      if (alpha1 < -lz1) {
 fix1 = -1;
 continue;
      }
      if (alpha1 > lz1) {
 fix1 = 1;
 continue;
      }
    }
    if (fix2==0) {
      if (alpha2 < -lz2) {
 fix2 = -1;
 continue;
      }
      if (alpha2 > lz2) {
 fix2 = 1;
 continue;
      }
    }

    // unfix the alpha variables if the local distance gradient indicates
    // that we are not yet at the minimum
    dReal tmp[3];
    for (i=0; i<3; i++) tmp[i] = sphere1[i] - sphere2[i];
    if (fix1) {
      dReal gradient = dDOT (tmp,axis1);
      if ((fix1 > 0 && gradient > 0) || (fix1 < 0 && gradient < 0)) {
 fix1 = 0;
 continue;
      }
    }
    if (fix2) {
      dReal gradient = -dDOT (tmp,axis2);
      if ((fix2 > 0 && gradient > 0) || (fix2 < 0 && gradient < 0)) {
 fix2 = 0;
 continue;
      }
    }
    return dCollideSpheres
(sphere1,cyl1->radius,sphere2,cyl2->radius,contact);
  }
  // if we go through the loop too much, then give up. we should NEVER get
to
  // this point (i hope).
  dMessage (0,"dCollideCC(): too many iterations");
  return 0;
}