/*************************************************************************
 *                                                                       *
 * Open Dynamics Engine, Copyright (C) 2001,2002 Russell L. Smith.       *
 * All rights reserved.  Email: russ@q12.org   Web: www.q12.org          *
 *                                                                       *
 * This library is free software; you can redistribute it and/or         *
 * modify it under the terms of EITHER:                                  *
 *   (1) The GNU Lesser General Public License as published by the Free  *
 *       Software Foundation; either version 2.1 of the License, or (at  *
 *       your option) any later version. The text of the GNU Lesser      *
 *       General Public License is included with this library in the     *
 *       file LICENSE.TXT.                                               *
 *   (2) The BSD-style license that is included with this library in     *
 *       the file LICENSE-BSD.TXT.                                       *
 *                                                                       *
 * This library is distributed in the hope that it will be useful,       *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files    *
 * LICENSE.TXT and LICENSE-BSD.TXT for more details.                     *
 *                                                                       *
 *************************************************************************/

/*

old collision code. this code is deprecated and will eventually be removed
from ODE.

the rule is that only the low level primitive collision functions should set
dContactGeom::g1 and dContactGeom::g2.

*/

#define SHARED_GEOM_H_INCLUDED_FROM_DEFINING_FILE 1
#include <ode/common.h>
#include <ode/geom.h>
#include <ode/rotation.h>
#include <ode/odemath.h>
#include <ode/memory.h>
#include <ode/misc.h>
#include <ode/objects.h>
#include <ode/matrix.h>
#include "int-objects.h"
#include "array.h"
#include "geom_internal.h"

//****************************************************************************
// collision utilities.

// given a pointer `p' to a dContactGeom, return the dContactGeom at
// p + skip bytes.

#define CONTACT(p,skip) ((dContactGeom*) (((char*)p) + (skip)))


// if the spheres (p1,r1) and (p2,r2) collide, set the contact `c' and
// return 1, else return 0.

static int dCollideSpheres (dVector3 p1, dReal r1,
			    dVector3 p2, dReal r2, dContactGeom *c)
{
  // printf ("d=%.2f  (%.2f %.2f %.2f) (%.2f %.2f %.2f) r1=%.2f r2=%.2f\n",
  //	  d,p1[0],p1[1],p1[2],p2[0],p2[1],p2[2],r1,r2);

  dReal d = dDISTANCE (p1,p2);
  if (d > (r1 + r2)) return 0;
  if (d <= 0) {
    c->pos[0] = p1[0];
    c->pos[1] = p1[1];
    c->pos[2] = p1[2];
    c->normal[0] = 1;
    c->normal[1] = 0;
    c->normal[2] = 0;
    c->depth = r1 + r2;
  }
  else {
    dReal d1 = dRecip (d);
    c->normal[0] = (p1[0]-p2[0])*d1;
    c->normal[1] = (p1[1]-p2[1])*d1;
    c->normal[2] = (p1[2]-p2[2])*d1;
    dReal k = REAL(0.5) * (r2 - r1 - d);
    c->pos[0] = p1[0] + c->normal[0]*k;
    c->pos[1] = p1[1] + c->normal[1]*k;
    c->pos[2] = p1[2] + c->normal[2]*k;
    c->depth = r1 + r2 - d;
  }
  return 1;
}


// given two lines
//    qa = pa + alpha* ua
//    qb = pb + beta * ub
// where pa,pb are two points, ua,ub are two unit length vectors, and alpha,
// beta go from [-inf,inf], return alpha and beta such that qa and qb are
// as close as possible

static void lineClosestApproach (const dVector3 pa, const dVector3 ua,
				 const dVector3 pb, const dVector3 ub,
				 dReal *alpha, dReal *beta)
{
  dVector3 p;
  p[0] = pb[0] - pa[0];
  p[1] = pb[1] - pa[1];
  p[2] = pb[2] - pa[2];
  dReal uaub = dDOT(ua,ub);
  dReal q1 =  dDOT(ua,p);
  dReal q2 = -dDOT(ub,p);
  dReal d = 1-uaub*uaub;
  if (d <= 0) {
    // @@@ this needs to be made more robust
    *alpha = 0;
    *beta  = 0;
  }
  else {
    d = dRecip(d);
    *alpha = (q1 + uaub*q2)*d;
    *beta  = (uaub*q1 + q2)*d;
  }
}


// given two line segments A and B with endpoints a1-a2 and b1-b2, return the
// points on A and B that are closest to each other (in cp1 and cp2).
// in the case of parallel lines where there are multiple solutions, a
// solution involving the endpoint of at least one line will be returned.
// this will work correctly for zero length lines, e.g. if a1==a2 and/or
// b1==b2.
//
// the algorithm works by applying the voronoi clipping rule to the features
// of the line segments. the three features of each line segment are the two
// endpoints and the line between them. the voronoi clipping rule states that,
// for feature X on line A and feature Y on line B, the closest points PA and
// PB between X and Y are globally the closest points if PA is in V(Y) and
// PB is in V(X), where V(X) is the voronoi region of X.


void dClosestLineSegmentPoints (dVector3 const a1, dVector3 const a2,
				dVector3 const b1, dVector3 const b2,
				dVector3 cp1, dVector3 cp2)
{
  dVector3 a1a2,b1b2,a1b1,a1b2,a2b1,a2b2,n;
  dReal la,lb,k,da1,da2,da3,da4,db1,db2,db3,db4,det;

#define SET2(a,b) a[0]=b[0]; a[1]=b[1]; a[2]=b[2];
#define SET3(a,b,op,c) a[0]=b[0] op c[0]; a[1]=b[1] op c[1]; a[2]=b[2] op c[2];

  // check vertex-vertex features

  SET3 (a1a2,a2,-,a1);
  SET3 (b1b2,b2,-,b1);
  SET3 (a1b1,b1,-,a1);
  da1 = dDOT(a1a2,a1b1);
  db1 = dDOT(b1b2,a1b1);
  if (da1 <= 0 && db1 >= 0) {
    SET2 (cp1,a1);
    SET2 (cp2,b1);
    return;
  }

  SET3 (a1b2,b2,-,a1);
  da2 = dDOT(a1a2,a1b2);
  db2 = dDOT(b1b2,a1b2);
  if (da2 <= 0 && db2 <= 0) {
    SET2 (cp1,a1);
    SET2 (cp2,b2);
    return;
  }

  SET3 (a2b1,b1,-,a2);
  da3 = dDOT(a1a2,a2b1);
  db3 = dDOT(b1b2,a2b1);
  if (da3 >= 0 && db3 >= 0) {
    SET2 (cp1,a2);
    SET2 (cp2,b1);
    return;
  }

  SET3 (a2b2,b2,-,a2);
  da4 = dDOT(a1a2,a2b2);
  db4 = dDOT(b1b2,a2b2);
  if (da4 >= 0 && db4 <= 0) {
    SET2 (cp1,a2);
    SET2 (cp2,b2);
    return;
  }

  // check edge-vertex features.
  // if one or both of the lines has zero length, we will never get to here,
  // so we do not have to worry about the following divisions by zero.

  la = dDOT(a1a2,a1a2);
  if (da1 >= 0 && da3 <= 0) {
    k = da1 / la;
    SET3 (n,a1b1,-,k*a1a2);
    if (dDOT(b1b2,n) >= 0) {
      SET3 (cp1,a1,+,k*a1a2);
      SET2 (cp2,b1);
      return;
    }
  }

  if (da2 >= 0 && da4 <= 0) {
    k = da2 / la;
    SET3 (n,a1b2,-,k*a1a2);
    if (dDOT(b1b2,n) <= 0) {
      SET3 (cp1,a1,+,k*a1a2);
      SET2 (cp2,b2);
      return;
    }
  }

  lb = dDOT(b1b2,b1b2);
  if (db1 <= 0 && db2 >= 0) {
    k = -db1 / lb;
    SET3 (n,-a1b1,-,k*b1b2);
    if (dDOT(a1a2,n) >= 0) {
      SET2 (cp1,a1);
      SET3 (cp2,b1,+,k*b1b2);
      return;
    }
  }

  if (db3 <= 0 && db4 >= 0) {
    k = -db3 / lb;
    SET3 (n,-a2b1,-,k*b1b2);
    if (dDOT(a1a2,n) <= 0) {
      SET2 (cp1,a2);
      SET3 (cp2,b1,+,k*b1b2);
      return;
    }
  }

  // it must be edge-edge

  k = dDOT(a1a2,b1b2);
  det = la*lb - k*k;
  if (det <= 0) {
    // this should never happen, but just in case...
    SET2(cp1,a1);
    SET2(cp2,b1);
    return;
  }
  det = dRecip (det);
  dReal alpha = (lb*da1 -  k*db1) * det;
  dReal beta  = ( k*da1 - la*db1) * det;
  SET3 (cp1,a1,+,alpha*a1a2);
  SET3 (cp2,b1,+,beta*b1b2);

# undef SET2
# undef SET3
}


// given a line segment p1-p2 and a box (center 'c', rotation 'R', side length
// vector 'side'), compute the points of closest approach between the box
// and the line. return these points in 'lret' (the point on the line) and
// 'bret' (the point on the box). if the line actually penetrates the box
// then the solution is not unique, but only one solution will be returned.
// in this case the solution points will coincide.
//
// a simple root finding algorithm is used to find the value of 't' that
// satisfies:
//		d|D(t)|^2/dt = 0
// where:
//		|D(t)| = |p(t)-b(t)|
// where p(t) is a point on the line parameterized by t:
//		p(t) = p1 + t*(p2-p1)
// and b(t) is that same point clipped to the boundary of the box. in box-
// relative coordinates d|D(t)|^2/dt is the sum of three x,y,z components
// each of which looks like this:
//
//	    t_lo     /
//	      ______/    -->t
//	     /     t_hi
//	    /
//
// t_lo and t_hi are the t values where the line passes through the planes
// corresponding to the sides of the box. the algorithm computes d|D(t)|^2/dt
// in a piecewise fashion from t=0 to t=1, stopping at the point where
// d|D(t)|^2/dt crosses from negative to positive.

static void dClosestLineBoxPoints (const dVector3 p1, const dVector3 p2,
				   const dVector3 c, const dMatrix3 R,
				   const dVector3 side,
				   dVector3 lret, dVector3 bret)
{
  int i;

  // compute the start and delta of the line p1-p2 relative to the box.
  // we will do all subsequent computations in this box-relative coordinate
  // system. we have to do a translation and rotation for each point.
  dVector3 tmp,s,v;
  tmp[0] = p1[0] - c[0];
  tmp[1] = p1[1] - c[1];
  tmp[2] = p1[2] - c[2];
  dMULTIPLY1_331 (s,R,tmp);
  tmp[0] = p2[0] - p1[0];
  tmp[1] = p2[1] - p1[1];
  tmp[2] = p2[2] - p1[2];
  dMULTIPLY1_331 (v,R,tmp);

  // mirror the line so that v has all components >= 0
  dVector3 sign;
  for (i=0; i<3; i++) {
    if (v[i] < 0) {
      s[i] = -s[i];
      v[i] = -v[i];
      sign[i] = -1;
    }
    else sign[i] = 1;
  }

  // compute v^2
  dVector3 v2;
  v2[0] = v[0]*v[0];
  v2[1] = v[1]*v[1];
  v2[2] = v[2]*v[2];

  // compute the half-sides of the box
  dReal h[3];
  h[0] = REAL(0.5) * side[0];
  h[1] = REAL(0.5) * side[1];
  h[2] = REAL(0.5) * side[2];

  // region is -1,0,+1 depending on which side of the box planes each
  // coordinate is on. tanchor in the next t value at which there is a
  // transition, or the last one if there are no more.
  int region[3];
  dReal tanchor[3];

  // find the region and tanchor values for p1
  for (i=0; i<3; i++) {
    if (v[i] > 0) {
      if (s[i] < -h[i]) {
	region[i] = -1;
	tanchor[i] = (-h[i]-s[i])/v[i];
      }
      else {
	region[i] = (s[i] > h[i]);
	tanchor[i] = (h[i]-s[i])/v[i];
      }
    }
    else {
      region[i] = 0;
      tanchor[i] = 2;		// this will never be a valid tanchor
    }
  }

  // compute d|d|^2/dt for t=0. if it's >= 0 then p1 is the closest point
  dReal t=0;
  dReal dd2dt = 0;
  for (i=0; i<3; i++) dd2dt -= (region[i] ? v2[i] : 0) * tanchor[i];
  if (dd2dt >= 0) goto got_answer;

  do {
    // find the point on the line that is at the next clip plane boundary
    dReal next_t = 1;
    for (i=0; i<3; i++) {
      if (tanchor[i] > t && tanchor[i] < 1 && tanchor[i] < next_t)
        next_t = tanchor[i];
    }

    // compute d|d|^2/dt for the next t
    dReal next_dd2dt = 0;
    for (i=0; i<3; i++) {
      next_dd2dt += (region[i] ? v2[i] : 0) * (next_t - tanchor[i]);
    }

    // if the sign of d|d|^2/dt has changed, solution = the crossover point
    if (next_dd2dt >= 0) {
      dReal m = (next_dd2dt-dd2dt)/(next_t - t);
      t -= dd2dt/m;
      goto got_answer;
    }

    // advance to the next anchor point / region
    for (i=0; i<3; i++) {
      if (tanchor[i] == next_t) {
	tanchor[i] = (h[i]-s[i])/v[i];
	region[i]++;
      }
    }
    t = next_t;
    dd2dt = next_dd2dt;
  }
  while (t < 1);
  t = 1;

  got_answer:

  // compute closest point on the line
  for (i=0; i<3; i++) lret[i] = p1[i] + t*tmp[i];	// note: tmp=p2-p1

  // compute closest point on the box
  for (i=0; i<3; i++) {
    tmp[i] = sign[i] * (s[i] + t*v[i]);
    if (tmp[i] < -h[i]) tmp[i] = -h[i];
    else if (tmp[i] > h[i]) tmp[i] = h[i];
  }
  dMULTIPLY0_331 (s,R,tmp);
  for (i=0; i<3; i++) bret[i] = s[i] + c[i];
}


// given a box (R,side), `R' is the rotation matrix for the box, and `side'
// is a vector of x/y/z side lengths, return the size of the interval of the
// box projected along the given axis. if the axis has unit length then the
// return value will be the actual diameter, otherwise the result will be
// scaled by the axis length.

static inline dReal boxDiameter (const dMatrix3 R, const dVector3 side,
				 const dVector3 axis)
{
  dVector3 q;
  dMULTIPLY1_331 (q,R,axis);	// transform axis to body-relative
  return dFabs(q[0])*side[0] + dFabs(q[1])*side[1] + dFabs(q[2])*side[2];
}


// given boxes (p1,R1,side1) and (p1,R1,side1), return 1 if they intersect
// or 0 if not.

int dBoxTouchesBox (const dVector3 p1, const dMatrix3 R1,
		    const dVector3 side1, const dVector3 p2,
		    const dMatrix3 R2, const dVector3 side2)
{
  // two boxes are disjoint if (and only if) there is a separating axis
  // perpendicular to a face from one box or perpendicular to an edge from
  // either box. the following tests are derived from:
  //    "OBB Tree: A Hierarchical Structure for Rapid Interference Detection",
  //    S.Gottschalk, M.C.Lin, D.Manocha., Proc of ACM Siggraph 1996.

  // Rij is R1'*R2, i.e. the relative rotation between R1 and R2.
  // Qij is abs(Rij)
  dVector3 p,pp;
  dReal A1,A2,A3,B1,B2,B3,R11,R12,R13,R21,R22,R23,R31,R32,R33,
    Q11,Q12,Q13,Q21,Q22,Q23,Q31,Q32,Q33;

  // get vector from centers of box 1 to box 2, relative to box 1
  p[0] = p2[0] - p1[0];
  p[1] = p2[1] - p1[1];
  p[2] = p2[2] - p1[2];
  dMULTIPLY1_331 (pp,R1,p);		// get pp = p relative to body 1

  // get side lengths / 2
  A1 = side1[0]*REAL(0.5); A2 = side1[1]*REAL(0.5); A3 = side1[2]*REAL(0.5);
  B1 = side2[0]*REAL(0.5); B2 = side2[1]*REAL(0.5); B3 = side2[2]*REAL(0.5);

  // for the following tests, excluding computation of Rij, in the worst case,
  // 15 compares, 60 adds, 81 multiplies, and 24 absolutes.
  // notation: R1=[u1 u2 u3], R2=[v1 v2 v3]

  // separating axis = u1,u2,u3
  R11 = dDOT44(R1+0,R2+0); R12 = dDOT44(R1+0,R2+1); R13 = dDOT44(R1+0,R2+2);
  Q11 = dFabs(R11); Q12 = dFabs(R12); Q13 = dFabs(R13);
  if (dFabs(pp[0]) > (A1 + B1*Q11 + B2*Q12 + B3*Q13)) return 0;
  R21 = dDOT44(R1+1,R2+0); R22 = dDOT44(R1+1,R2+1); R23 = dDOT44(R1+1,R2+2);
  Q21 = dFabs(R21); Q22 = dFabs(R22); Q23 = dFabs(R23);
  if (dFabs(pp[1]) > (A2 + B1*Q21 + B2*Q22 + B3*Q23)) return 0;
  R31 = dDOT44(R1+2,R2+0); R32 = dDOT44(R1+2,R2+1); R33 = dDOT44(R1+2,R2+2);
  Q31 = dFabs(R31); Q32 = dFabs(R32); Q33 = dFabs(R33);
  if (dFabs(pp[2]) > (A3 + B1*Q31 + B2*Q32 + B3*Q33)) return 0;

  // separating axis = v1,v2,v3
  if (dFabs(dDOT41(R2+0,p)) > (A1*Q11 + A2*Q21 + A3*Q31 + B1)) return 0;
  if (dFabs(dDOT41(R2+1,p)) > (A1*Q12 + A2*Q22 + A3*Q32 + B2)) return 0;
  if (dFabs(dDOT41(R2+2,p)) > (A1*Q13 + A2*Q23 + A3*Q33 + B3)) return 0;

  // separating axis = u1 x (v1,v2,v3)
  if (dFabs(pp[2]*R21-pp[1]*R31) > A2*Q31 + A3*Q21 + B2*Q13 + B3*Q12) return 0;
  if (dFabs(pp[2]*R22-pp[1]*R32) > A2*Q32 + A3*Q22 + B1*Q13 + B3*Q11) return 0;
  if (dFabs(pp[2]*R23-pp[1]*R33) > A2*Q33 + A3*Q23 + B1*Q12 + B2*Q11) return 0;

  // separating axis = u2 x (v1,v2,v3)
  if (dFabs(pp[0]*R31-pp[2]*R11) > A1*Q31 + A3*Q11 + B2*Q23 + B3*Q22) return 0;
  if (dFabs(pp[0]*R32-pp[2]*R12) > A1*Q32 + A3*Q12 + B1*Q23 + B3*Q21) return 0;
  if (dFabs(pp[0]*R33-pp[2]*R13) > A1*Q33 + A3*Q13 + B1*Q22 + B2*Q21) return 0;

  // separating axis = u3 x (v1,v2,v3)
  if (dFabs(pp[1]*R11-pp[0]*R21) > A1*Q21 + A2*Q11 + B2*Q33 + B3*Q32) return 0;
  if (dFabs(pp[1]*R12-pp[0]*R22) > A1*Q22 + A2*Q12 + B1*Q33 + B3*Q31) return 0;
  if (dFabs(pp[1]*R13-pp[0]*R23) > A1*Q23 + A2*Q13 + B1*Q32 + B2*Q31) return 0;

  return 1;
}


// given two boxes (p1,R1,side1) and (p2,R2,side2), collide them together and
// generate contact points. this returns 0 if there is no contact otherwise
// it returns the number of contacts generated.
// `normal' returns the contact normal.
// `depth' returns the maximum penetration depth along that normal.
// `code' returns a number indicating the type of contact that was detected:
//        1,2,3 = box 2 intersects with a face of box 1
//        4,5,6 = box 1 intersects with a face of box 2
//        7..15 = edge-edge contact
// `maxc' is the maximum number of contacts allowed to be generated, i.e.
// the size of the `contact' array.
// `contact' and `skip' are the contact array information provided to the
// collision functions. this function only fills in the position and depth
// fields.
//
// @@@ some stuff to optimize here, reuse code in contact point calculations.

int dBoxBox (const dVector3 p1, const dMatrix3 R1,
	     const dVector3 side1, const dVector3 p2,
	     const dMatrix3 R2, const dVector3 side2,
	     dVector3 normal, dReal *depth, int *code,
	     int maxc, dContactGeom *contact, int skip)
{
  dVector3 p,pp,normalC;
  const dReal *normalR = 0;
  dReal A1,A2,A3,B1,B2,B3,R11,R12,R13,R21,R22,R23,R31,R32,R33,
    Q11,Q12,Q13,Q21,Q22,Q23,Q31,Q32,Q33,s,s2,l;
  int i,invert_normal;

  // get vector from centers of box 1 to box 2, relative to box 1
  p[0] = p2[0] - p1[0];
  p[1] = p2[1] - p1[1];
  p[2] = p2[2] - p1[2];
  dMULTIPLY1_331 (pp,R1,p);		// get pp = p relative to body 1

  // get side lengths / 2
  A1 = side1[0]*REAL(0.5); A2 = side1[1]*REAL(0.5); A3 = side1[2]*REAL(0.5);
  B1 = side2[0]*REAL(0.5); B2 = side2[1]*REAL(0.5); B3 = side2[2]*REAL(0.5);

  // Rij is R1'*R2, i.e. the relative rotation between R1 and R2
  R11 = dDOT44(R1+0,R2+0); R12 = dDOT44(R1+0,R2+1); R13 = dDOT44(R1+0,R2+2);
  R21 = dDOT44(R1+1,R2+0); R22 = dDOT44(R1+1,R2+1); R23 = dDOT44(R1+1,R2+2);
  R31 = dDOT44(R1+2,R2+0); R32 = dDOT44(R1+2,R2+1); R33 = dDOT44(R1+2,R2+2);

  Q11 = dFabs(R11); Q12 = dFabs(R12); Q13 = dFabs(R13);
  Q21 = dFabs(R21); Q22 = dFabs(R22); Q23 = dFabs(R23);
  Q31 = dFabs(R31); Q32 = dFabs(R32); Q33 = dFabs(R33);

  // for all 15 possible separating axes:
  //   * see if the axis separates the boxes. if so, return 0.
  //   * find the depth of the penetration along the separating axis (s2)
  //   * if this is the largest depth so far, record it.
  // the normal vector will be set to the separating axis with the smallest
  // depth. note: normalR is set to point to a column of R1 or R2 if that is
  // the smallest depth normal so far. otherwise normalR is 0 and normalC is
  // set to a vector relative to body 1. invert_normal is 1 if the sign of
  // the normal should be flipped.

#define TEST(expr1,expr2,norm,cc) \
  s2 = dFabs(expr1) - (expr2); \
  if (s2 > 0) return 0; \
  if (s2 > s) { \
    s = s2; \
    normalR = norm; \
    invert_normal = ((expr1) < 0); \
    *code = (cc); \
  }

  s = -dInfinity;
  invert_normal = 0;
  *code = 0;

  // separating axis = u1,u2,u3
  TEST (pp[0],(A1 + B1*Q11 + B2*Q12 + B3*Q13),R1+0,1);
  TEST (pp[1],(A2 + B1*Q21 + B2*Q22 + B3*Q23),R1+1,2);
  TEST (pp[2],(A3 + B1*Q31 + B2*Q32 + B3*Q33),R1+2,3);

  // separating axis = v1,v2,v3
  TEST (dDOT41(R2+0,p),(A1*Q11 + A2*Q21 + A3*Q31 + B1),R2+0,4);
  TEST (dDOT41(R2+1,p),(A1*Q12 + A2*Q22 + A3*Q32 + B2),R2+1,5);
  TEST (dDOT41(R2+2,p),(A1*Q13 + A2*Q23 + A3*Q33 + B3),R2+2,6);

  // note: cross product axes need to be scaled when s is computed.
  // normal (n1,n2,n3) is relative to box 1.
#undef TEST
#define TEST(expr1,expr2,n1,n2,n3,cc) \
  s2 = dFabs(expr1) - (expr2); \
  if (s2 > 0) return 0; \
  l = dSqrt ((n1)*(n1) + (n2)*(n2) + (n3)*(n3)); \
  if (l > 0) { \
    s2 /= l; \
    if (s2 > s) { \
      s = s2; \
      normalR = 0; \
      normalC[0] = (n1)/l; normalC[1] = (n2)/l; normalC[2] = (n3)/l; \
      invert_normal = ((expr1) < 0); \
      *code = (cc); \
    } \
  }

  // separating axis = u1 x (v1,v2,v3)
  TEST(pp[2]*R21-pp[1]*R31,(A2*Q31+A3*Q21+B2*Q13+B3*Q12),0,-R31,R21,7);
  TEST(pp[2]*R22-pp[1]*R32,(A2*Q32+A3*Q22+B1*Q13+B3*Q11),0,-R32,R22,8);
  TEST(pp[2]*R23-pp[1]*R33,(A2*Q33+A3*Q23+B1*Q12+B2*Q11),0,-R33,R23,9);

  // separating axis = u2 x (v1,v2,v3)
  TEST(pp[0]*R31-pp[2]*R11,(A1*Q31+A3*Q11+B2*Q23+B3*Q22),R31,0,-R11,10);
  TEST(pp[0]*R32-pp[2]*R12,(A1*Q32+A3*Q12+B1*Q23+B3*Q21),R32,0,-R12,11);
  TEST(pp[0]*R33-pp[2]*R13,(A1*Q33+A3*Q13+B1*Q22+B2*Q21),R33,0,-R13,12);

  // separating axis = u3 x (v1,v2,v3)
  TEST(pp[1]*R11-pp[0]*R21,(A1*Q21+A2*Q11+B2*Q33+B3*Q32),-R21,R11,0,13);
  TEST(pp[1]*R12-pp[0]*R22,(A1*Q22+A2*Q12+B1*Q33+B3*Q31),-R22,R12,0,14);
  TEST(pp[1]*R13-pp[0]*R23,(A1*Q23+A2*Q13+B1*Q32+B2*Q31),-R23,R13,0,15);

#undef TEST

  // if we get to this point, the boxes interpenetrate. compute the normal
  // in global coordinates.
  if (normalR) {
    normal[0] = normalR[0];
    normal[1] = normalR[4];
    normal[2] = normalR[8];
  }
  else {
    dMULTIPLY0_331 (normal,R1,normalC);
  }
  if (invert_normal) {
    normal[0] = -normal[0];
    normal[1] = -normal[1];
    normal[2] = -normal[2];
  }
  *depth = -s;

  // compute contact point(s)

  if (*code > 6) {
    // an edge from box 1 touches an edge from box 2.
    // find a point pa on the intersecting edge of box 1
    dVector3 pa;
    dReal sign;
    for (i=0; i<3; i++) pa[i] = p1[i];
    sign = (dDOT14(normal,R1+0) > 0) ? REAL(1.0) : REAL(-1.0);
    for (i=0; i<3; i++) pa[i] += sign * A1 * R1[i*4];
    sign = (dDOT14(normal,R1+1) > 0) ? REAL(1.0) : REAL(-1.0);
    for (i=0; i<3; i++) pa[i] += sign * A2 * R1[i*4+1];
    sign = (dDOT14(normal,R1+2) > 0) ? REAL(1.0) : REAL(-1.0);
    for (i=0; i<3; i++) pa[i] += sign * A3 * R1[i*4+2];

    // find a point pb on the intersecting edge of box 2
    dVector3 pb;
    for (i=0; i<3; i++) pb[i] = p2[i];
    sign = (dDOT14(normal,R2+0) > 0) ? REAL(-1.0) : REAL(1.0);
    for (i=0; i<3; i++) pb[i] += sign * B1 * R2[i*4];
    sign = (dDOT14(normal,R2+1) > 0) ? REAL(-1.0) : REAL(1.0);
    for (i=0; i<3; i++) pb[i] += sign * B2 * R2[i*4+1];
    sign = (dDOT14(normal,R2+2) > 0) ? REAL(-1.0) : REAL(1.0);
    for (i=0; i<3; i++) pb[i] += sign * B3 * R2[i*4+2];

    dReal alpha,beta;
    dVector3 ua,ub;
    for (i=0; i<3; i++) ua[i] = R1[((*code)-7)/3 + i*4];
    for (i=0; i<3; i++) ub[i] = R2[((*code)-7)%3 + i*4];

    lineClosestApproach (pa,ua,pb,ub,&alpha,&beta);
    for (i=0; i<3; i++) pa[i] += ua[i]*alpha;
    for (i=0; i<3; i++) pb[i] += ub[i]*beta;

    for (i=0; i<3; i++) contact[0].pos[i] = REAL(0.5)*(pa[i]+pb[i]);
    contact[0].depth = *depth;
    return 1;
  }

  // okay, we have a face-something intersection (because the separating
  // axis is perpendicular to a face).

  // @@@ temporary: make deepest vertex on the "other" box the contact point.
  // @@@ this kind of works, but we need multiple contact points for stability,
  // @@@ especially for face-face contact.

  dVector3 vertex;
  if (*code <= 3) {
    // face from box 1 touches a vertex/edge/face from box 2.
    dReal sign;
    for (i=0; i<3; i++) vertex[i] = p2[i];
    sign = (dDOT14(normal,R2+0) > 0) ? REAL(-1.0) : REAL(1.0);
    for (i=0; i<3; i++) vertex[i] += sign * B1 * R2[i*4];
    sign = (dDOT14(normal,R2+1) > 0) ? REAL(-1.0) : REAL(1.0);
    for (i=0; i<3; i++) vertex[i] += sign * B2 * R2[i*4+1];
    sign = (dDOT14(normal,R2+2) > 0) ? REAL(-1.0) : REAL(1.0);
    for (i=0; i<3; i++) vertex[i] += sign * B3 * R2[i*4+2];
  }
  else {
    // face from box 2 touches a vertex/edge/face from box 1.
    dReal sign;
    for (i=0; i<3; i++) vertex[i] = p1[i];
    sign = (dDOT14(normal,R1+0) > 0) ? REAL(1.0) : REAL(-1.0);
    for (i=0; i<3; i++) vertex[i] += sign * A1 * R1[i*4];
    sign = (dDOT14(normal,R1+1) > 0) ? REAL(1.0) : REAL(-1.0);
    for (i=0; i<3; i++) vertex[i] += sign * A2 * R1[i*4+1];
    sign = (dDOT14(normal,R1+2) > 0) ? REAL(1.0) : REAL(-1.0);
    for (i=0; i<3; i++) vertex[i] += sign * A3 * R1[i*4+2];
  }
  for (i=0; i<3; i++) contact[0].pos[i] = vertex[i];
  contact[0].depth = *depth;
  return 1;
}

//****************************************************************************
// general support for geometry objects and classes

struct dColliderEntry {
  dColliderFn *fn;	// collider function
  int mode;		// 1 = reverse o1 and o2, 2 = no function available
};

static dArray<dxGeomClass*> *classes=0;

// function pointers and modes for n^2 class collider functions. this is an
// n*n matrix stored by row. the functions pointers are extracted from the
// class get-collider-function function.
static dArray<dColliderEntry> *colliders=0;


static inline void initCollisionArrays()
{
  if (classes==0) {
    // old way:
    //   classes = (dArray<dxGeomClass*> *) dAllocNoFree (sizeof(dArrayBase));
    //   classes->constructor();
    classes = new dArray<dxGeomClass*>;
    classes->setSize (1);	// force allocation of array data memory
    dAllocDontReport (classes);
    dAllocDontReport (classes->data());
    classes->setSize (0);
  }
  if (colliders==0) {
    // old way:
    //   colliders=(dArray<dColliderEntry> *)dAllocNoFree (sizeof(dArrayBase));
    //   colliders->constructor();
    colliders = new dArray<dColliderEntry>;
    colliders->setSize (1);	// force allocation of array data memory
    dAllocDontReport (colliders);
    dAllocDontReport (colliders->data());
    colliders->setSize (0);
  }
}


int dCreateGeomClass (const dGeomClass *c)
{
  dUASSERT(c && c->bytes >= 0 && c->collider && c->aabb,"bad geom class");
  initCollisionArrays();

  int n = classes->size();
  dxGeomClass *gc = (dxGeomClass*) dAlloc (sizeof(dxGeomClass));
  dAllocDontReport (gc);
  gc->collider = c->collider;
  gc->aabb = c->aabb;
  gc->aabb_test = c->aabb_test;
  gc->dtor = c->dtor;
  gc->num = n;
  gc->size = SIZEOF_DXGEOM + c->bytes;
  classes->push (gc);

  // make room for n^2 class collider function pointers - these entries will
  // be filled as dCollide() is called.
  colliders->setSize ((n+1)*(n+1));
  memset (colliders->data(),0,(n+1)*(n+1)*sizeof(dColliderEntry));

  return n;
}


int dCollide (dxGeom *o1, dxGeom *o2, int flags, dContactGeom *contact,
	      int skip)
{
  int i,c1,c2,a1,a2,count,swap;
  dColliderFn *fn;
  dAASSERT(o1 && o2 && contact);
  dUASSERT(classes && colliders,"no registered geometry classes");

  // no contacts if both geoms on the same body, and the body is not 0
  if (o1->body == o2->body && o1->body) return 0;

  dColliderEntry *colliders2 = colliders->data();
  c1 = o1->_class->num;
  c2 = o2->_class->num;
  a1 = c1 * classes->size() + c2;	// address 1 in collider array
  a2 = c2 * classes->size() + c1;	// address 2 in collider array
  swap = 0;		// set to 1 to swap normals before returning

  // return if there are no collider functions available
  if ((colliders2[a1].mode==2) || (colliders2[a2].mode==2)) return 0;

  if ((fn = colliders2[a1].fn)) {
    swap = colliders2[a1].mode;
    if (swap) count = (*fn) (o2,o1,flags,contact,skip);
    else count = (*fn) (o1,o2,flags,contact,skip);
  }
  else if ((fn = (*classes)[c1]->collider (c2))) {
    colliders2 [a2].fn = fn;
    colliders2 [a2].mode = 1;
    colliders2 [a1].fn = fn;	// do mode=0 assignment second so that
    colliders2 [a1].mode = 0;	// diagonal entries will have mode 0
    count = (*fn) (o1,o2,flags,contact,skip);
    swap = 0;
  }
  else if ((fn = (*classes)[c2]->collider (c1))) {
    colliders2 [a1].fn = fn;
    colliders2 [a1].mode = 1;
    colliders2 [a2].fn = fn;	// do mode=0 assignment second so that
    colliders2 [a2].mode = 0;	// diagonal entries will have mode 0
    count = (*fn) (o2,o1,flags,contact,skip);
    swap = 1;
  }
  else {
    colliders2[a1].mode = 2;
    colliders2[a2].mode = 2;
    return 0;
  }

  if (swap) {
    for (i=0; i<count; i++) {
      dContactGeom *c = CONTACT(contact,skip*i);
      c->normal[0] = -c->normal[0];
      c->normal[1] = -c->normal[1];
      c->normal[2] = -c->normal[2];
      dxGeom *tmp = c->g1;
      c->g1 = c->g2;
      c->g2 = tmp;
    }
  }

  return count;
}


int dGeomGetClass (dxGeom *g)
{
  dAASSERT (g);
  return g->_class->num;
}


void dGeomSetData (dxGeom *g, void *data)
{
  dAASSERT (g);
  g->data = data;
}


void *dGeomGetData (dxGeom *g)
{
  dAASSERT (g);
  return g->data;
}


void dGeomSetBody (dxGeom *g, dBodyID b)
{
  dAASSERT (g);
  if (b) {
    if (!g->body) dFree (g->pos,sizeof(dxPosR));
    g->body = b;
    g->pos = b->pos;
    g->R = b->R;
  }
  else {
    if (g->body) {
      dxPosR *pr = (dxPosR*) dAlloc (sizeof(dxPosR));
      g->pos = pr->pos;
      g->R = pr->R;
      memcpy (g->pos,g->body->pos,sizeof(g->pos));
      memcpy (g->R,g->body->R,sizeof(g->R));
      g->body = 0;
    }
  }
}


dBodyID dGeomGetBody (dxGeom *g)
{
  dAASSERT (g);
  return g->body;
}


void dGeomSetPosition (dxGeom *g, dReal x, dReal y, dReal z)
{
  dAASSERT (g);
  if (g->body) dBodySetPosition (g->body,x,y,z);
  else {
    g->pos[0] = x;
    g->pos[1] = y;
    g->pos[2] = z;
  }
}


void dGeomSetRotation (dxGeom *g, const dMatrix3 R)
{
  dAASSERT (g);
  if (g->body) dBodySetRotation (g->body,R);
  else memcpy (g->R,R,sizeof(dMatrix3));
}


const dReal * dGeomGetPosition (dxGeom *g)
{
  dAASSERT (g);
  return g->pos;
}


const dReal * dGeomGetRotation (dxGeom *g)
{
  dAASSERT (g);
  return g->R;
}


// for external use only. use the CLASSDATA macro inside ODE.

void * dGeomGetClassData (dxGeom *g)
{
  dAASSERT (g);
  return (void*) CLASSDATA(g);
}


dxGeom * dCreateGeom (int classnum)
{
  dUASSERT (classes && colliders && classnum >= 0 &&
	    classnum < classes->size(),"bad class number");
  int size = (*classes)[classnum]->size;
  dxGeom *geom = (dxGeom*) dAlloc (size);
  memset (geom,0,size);		// everything is initially zeroed

  geom->_class = (*classes)[classnum];
  geom->data = 0;
  geom->body = 0;

  dxPosR *pr = (dxPosR*) dAlloc (sizeof(dxPosR));
  geom->pos = pr->pos;
  geom->R = pr->R;
  dSetZero (geom->pos,4);
  dRSetIdentity (geom->R);

  return geom;
}


void dGeomDestroy (dxGeom *g)
{
  dAASSERT (g);
  if (g->spaceid) dSpaceRemove (g->spaceid,g);
  if (g->_class->dtor) g->_class->dtor (g);
  if (!g->body) dFree (g->pos,sizeof(dxPosR));
  dFree (g,g->_class->size);
}


void dGeomGetAABB (dxGeom *g, dReal aabb[6])
{
  dAASSERT (g);
  g->_class->aabb (g,aabb);
}


dReal *dGeomGetSpaceAABB (dxGeom *g)
{
  dAASSERT (g);
  return g->space_aabb;
}

//****************************************************************************
// data for the standard classes

struct dxSphere {
  dReal radius;		// sphere radius
};

struct dxBox {
  dVector3 side;	// side lengths (x,y,z)
};

struct dxCCylinder {	// capped cylinder
  dReal radius,lz;	// radius, length along z axis */
};

struct dxPlane {
  dReal p[4];
};

struct dxGeomGroup {
  dArray<dxGeom*> parts;	// all the geoms that make up the group
};

//****************************************************************************
// primitive collision functions
// same interface as dCollide().
// S=sphere, B=box, C=capped cylinder, P=plane, G=group, T=transform

int dCollideSS (const dxGeom *o1, const dxGeom *o2, int flags,
		dContactGeom *contact, int skip)
{
  dIASSERT (skip >= (int)sizeof(dContactGeom));
  dIASSERT (o1->_class->num == dSphereClass);
  dIASSERT (o2->_class->num == dSphereClass);
  dxSphere *s1 = (dxSphere*) CLASSDATA(o1);
  dxSphere *s2 = (dxSphere*) CLASSDATA(o2);
  contact->g1 = const_cast<dxGeom*> (o1);
  contact->g2 = const_cast<dxGeom*> (o2);
  return dCollideSpheres (o1->pos,s1->radius,
			  o2->pos,s2->radius,contact);
}


int dCollideSB (const dxGeom *o1, const dxGeom *o2, int flags,
		dContactGeom *contact, int skip)
{
  // this is easy. get the sphere center `p' relative to the box, and then clip
  // that to the boundary of the box (call that point `q'). if q is on the
  // boundary of the box and |p-q| is <= sphere radius, they touch.
  // if q is inside the box, the sphere is inside the box, so set a contact
  // normal to push the sphere to the closest box edge.

  dVector3 l,t,p,q,r;
  dReal depth;
  int onborder = 0;

  dIASSERT (skip >= (int)sizeof(dContactGeom));
  dIASSERT (o1->_class->num == dSphereClass);
  dIASSERT (o2->_class->num == dBoxClass);
  dxSphere *sphere = (dxSphere*) CLASSDATA(o1);
  dxBox *box = (dxBox*) CLASSDATA(o2);

  contact->g1 = const_cast<dxGeom*> (o1);
  contact->g2 = const_cast<dxGeom*> (o2);

  p[0] = o1->pos[0] - o2->pos[0];
  p[1] = o1->pos[1] - o2->pos[1];
  p[2] = o1->pos[2] - o2->pos[2];

  l[0] = box->side[0]*REAL(0.5);
  t[0] = dDOT14(p,o2->R);
  if (t[0] < -l[0]) { t[0] = -l[0]; onborder = 1; }
  if (t[0] >  l[0]) { t[0] =  l[0]; onborder = 1; }

  l[1] = box->side[1]*REAL(0.5);
  t[1] = dDOT14(p,o2->R+1);
  if (t[1] < -l[1]) { t[1] = -l[1]; onborder = 1; }
  if (t[1] >  l[1]) { t[1] =  l[1]; onborder = 1; }

  t[2] = dDOT14(p,o2->R+2);
  l[2] = box->side[2]*REAL(0.5);
  if (t[2] < -l[2]) { t[2] = -l[2]; onborder = 1; }
  if (t[2] >  l[2]) { t[2] =  l[2]; onborder = 1; }

  if (!onborder) {
    // sphere center inside box. find largest `t' value
    dReal max = dFabs(t[0]);
    int maxi = 0;
    for (int i=1; i<3; i++) {
      dReal tt = dFabs(t[i]);
      if (tt > max) {
	max = tt;
	maxi = i;
      }
    }
    // contact position = sphere center
    contact->pos[0] = o1->pos[0];
    contact->pos[1] = o1->pos[1];
    contact->pos[2] = o1->pos[2];
    // contact normal aligned with box edge along largest `t' value
    dVector3 tmp;
    tmp[0] = 0;
    tmp[1] = 0;
    tmp[2] = 0;
    tmp[maxi] = (t[maxi] > 0) ? REAL(1.0) : REAL(-1.0);
    dMULTIPLY0_331 (contact->normal,o2->R,tmp);
    // contact depth = distance to wall along normal plus radius
    contact->depth = l[maxi] - max + sphere->radius;
    return 1;
  }

  t[3] = 0;			//@@@ hmmm
  dMULTIPLY0_331 (q,o2->R,t);
  r[0] = p[0] - q[0];
  r[1] = p[1] - q[1];
  r[2] = p[2] - q[2];
  depth = sphere->radius - dSqrt(dDOT(r,r));
  if (depth < 0) return 0;
  contact->pos[0] = q[0] + o2->pos[0];
  contact->pos[1] = q[1] + o2->pos[1];
  contact->pos[2] = q[2] + o2->pos[2];
  contact->normal[0] = r[0];
  contact->normal[1] = r[1];
  contact->normal[2] = r[2];
  dNormalize3 (contact->normal);
  contact->depth = depth;
  return 1;
}


int dCollideSP (const dxGeom *o1, const dxGeom *o2, int flags,
		dContactGeom *contact, int skip)
{
  dIASSERT (skip >= (int)sizeof(dContactGeom));
  dIASSERT (o1->_class->num == dSphereClass);
  dIASSERT (o2->_class->num == dPlaneClass);
  contact->g1 = const_cast<dxGeom*> (o1);
  contact->g2 = const_cast<dxGeom*> (o2);
  dxSphere *sphere = (dxSphere*) CLASSDATA(o1);
  dxPlane *plane = (dxPlane*) CLASSDATA(o2);
  dReal k = dDOT (o1->pos,plane->p);
  dReal depth = plane->p[3] - k + sphere->radius;
  if (depth >= 0) {
    contact->normal[0] = plane->p[0];
    contact->normal[1] = plane->p[1];
    contact->normal[2] = plane->p[2];
    contact->pos[0] = o1->pos[0] - plane->p[0] * sphere->radius;
    contact->pos[1] = o1->pos[1] - plane->p[1] * sphere->radius;
    contact->pos[2] = o1->pos[2] - plane->p[2] * sphere->radius;
    contact->depth = depth;
    return 1;
  }
  else return 0;
}


int dCollideBB (const dxGeom *o1, const dxGeom *o2, int flags,
		dContactGeom *contact, int skip)
{
  dVector3 normal;
  dReal depth;
  int code;
  dxBox *b1 = (dxBox*) CLASSDATA(o1);
  dxBox *b2 = (dxBox*) CLASSDATA(o2);
  int num = dBoxBox (o1->pos,o1->R,b1->side, o2->pos,o2->R,b2->side,
		     normal,&depth,&code,flags & NUMC_MASK,contact,skip);
  for (int i=0; i<num; i++) {
    CONTACT(contact,i*skip)->normal[0] = -normal[0];
    CONTACT(contact,i*skip)->normal[1] = -normal[1];
    CONTACT(contact,i*skip)->normal[2] = -normal[2];
    CONTACT(contact,i*skip)->g1 = const_cast<dxGeom*> (o1);
    CONTACT(contact,i*skip)->g2 = const_cast<dxGeom*> (o2);
  }
  return num;
}


int dCollideBP (const dxGeom *o1, const dxGeom *o2,
		int flags, dContactGeom *contact, int skip)
{
  dIASSERT (skip >= (int)sizeof(dContactGeom));
  dIASSERT (o1->_class->num == dBoxClass);
  dIASSERT (o2->_class->num == dPlaneClass);
  contact->g1 = const_cast<dxGeom*> (o1);
  contact->g2 = const_cast<dxGeom*> (o2);
  dxBox *box = (dxBox*) CLASSDATA(o1);
  dxPlane *plane = (dxPlane*) CLASSDATA(o2);
  int ret = 0;

  //@@@ problem: using 4-vector (plane->p) as 3-vector (normal).
  const dReal *R = o1->R;		// rotation of box
  const dReal *n = plane->p;		// normal vector

  // project sides lengths along normal vector, get absolute values
  dReal Q1 = dDOT14(n,R+0);
  dReal Q2 = dDOT14(n,R+1);
  dReal Q3 = dDOT14(n,R+2);
  dReal A1 = box->side[0] * Q1;
  dReal A2 = box->side[1] * Q2;
  dReal A3 = box->side[2] * Q3;
  dReal B1 = dFabs(A1);
  dReal B2 = dFabs(A2);
  dReal B3 = dFabs(A3);

  // early exit test
  dReal depth = plane->p[3] + REAL(0.5)*(B1+B2+B3) - dDOT(n,o1->pos);
  if (depth < 0) return 0;

  // find number of contacts requested
  int maxc = flags & NUMC_MASK;
  if (maxc < 1) maxc = 1;
  if (maxc > 3) maxc = 3;	// no more than 3 contacts per box allowed

  // find deepest point
  dVector3 p;
  p[0] = o1->pos[0];
  p[1] = o1->pos[1];
  p[2] = o1->pos[2];
#define FOO(i,op) \
  p[0] op REAL(0.5)*box->side[i] * R[0+i]; \
  p[1] op REAL(0.5)*box->side[i] * R[4+i]; \
  p[2] op REAL(0.5)*box->side[i] * R[8+i];
#define BAR(i,iinc) if (A ## iinc > 0) { FOO(i,-=) } else { FOO(i,+=) }
  BAR(0,1);
  BAR(1,2);
  BAR(2,3);
#undef FOO
#undef BAR

  // the deepest point is the first contact point
  contact->pos[0] = p[0];
  contact->pos[1] = p[1];
  contact->pos[2] = p[2];
  contact->normal[0] = n[0];
  contact->normal[1] = n[1];
  contact->normal[2] = n[2];
  contact->depth = depth;
  ret = 1;		// ret is number of contact points found so far
  if (maxc == 1) goto done;

  // get the second and third contact points by starting from `p' and going
  // along the two sides with the smallest projected length.

#define FOO(i,j,op) \
  CONTACT(contact,i*skip)->pos[0] = p[0] op box->side[j] * R[0+j]; \
  CONTACT(contact,i*skip)->pos[1] = p[1] op box->side[j] * R[4+j]; \
  CONTACT(contact,i*skip)->pos[2] = p[2] op box->side[j] * R[8+j];
#define BAR(ctact,side,sideinc) \
  depth -= B ## sideinc; \
  if (depth < 0) goto done; \
  if (A ## sideinc > 0) { FOO(ctact,side,+) } else { FOO(ctact,side,-) } \
  CONTACT(contact,ctact*skip)->depth = depth; \
  ret++;

  CONTACT(contact,skip)->normal[0] = n[0];
  CONTACT(contact,skip)->normal[1] = n[1];
  CONTACT(contact,skip)->normal[2] = n[2];
  if (maxc == 3) {
    CONTACT(contact,2*skip)->normal[0] = n[0];
    CONTACT(contact,2*skip)->normal[1] = n[1];
    CONTACT(contact,2*skip)->normal[2] = n[2];
  }

  if (B1 < B2) {
    if (B3 < B1) goto use_side_3; else {
      BAR(1,0,1);	// use side 1
      if (maxc == 2) goto done;
      if (B2 < B3) goto contact2_2; else goto contact2_3;
    }
  }
  else {
    if (B3 < B2) {
      use_side_3:	// use side 3
      BAR(1,2,3);
      if (maxc == 2) goto done;
      if (B1 < B2) goto contact2_1; else goto contact2_2;
    }
    else {
      BAR(1,1,2);	// use side 2
      if (maxc == 2) goto done;
      if (B1 < B3) goto contact2_1; else goto contact2_3;
    }
  }

  contact2_1: BAR(2,0,1); goto done;
  contact2_2: BAR(2,1,2); goto done;
  contact2_3: BAR(2,2,3); goto done;
#undef FOO
#undef BAR

 done:
  for (int i=0; i<ret; i++) {
    CONTACT(contact,i*skip)->g1 = const_cast<dxGeom*> (o1);
    CONTACT(contact,i*skip)->g2 = const_cast<dxGeom*> (o2);
  }
  return ret;
}


int dCollideCS (const dxGeom *o1, const dxGeom *o2, int flags,
		dContactGeom *contact, int skip)
{
  dIASSERT (skip >= (int)sizeof(dContactGeom));
  dIASSERT (o1->_class->num == dCCylinderClass);
  dIASSERT (o2->_class->num == dSphereClass);
  contact->g1 = const_cast<dxGeom*> (o1);
  contact->g2 = const_cast<dxGeom*> (o2);
  dxCCylinder *ccyl = (dxCCylinder*) CLASSDATA(o1);
  dxSphere *sphere = (dxSphere*) CLASSDATA(o2);

  // find the point on the cylinder axis that is closest to the sphere
  dReal alpha = 
    o1->R[2]  * (o2->pos[0] - o1->pos[0]) +
    o1->R[6]  * (o2->pos[1] - o1->pos[1]) +
    o1->R[10] * (o2->pos[2] - o1->pos[2]);
  dReal lz2 = ccyl->lz * REAL(0.5);
  if (alpha > lz2) alpha = lz2;
  if (alpha < -lz2) alpha = -lz2;

  // collide the spheres
  dVector3 p;
  p[0] = o1->pos[0] + alpha * o1->R[2];
  p[1] = o1->pos[1] + alpha * o1->R[6];
  p[2] = o1->pos[2] + alpha * o1->R[10];
  return dCollideSpheres (p,ccyl->radius,o2->pos,sphere->radius,contact);
}


int dCollideCB (const dxGeom *o1, const dxGeom *o2, int flags,
		dContactGeom *contact, int skip)
{
  dIASSERT (skip >= (int)sizeof(dContactGeom));
  dIASSERT (o1->_class->num == dCCylinderClass);
  dIASSERT (o2->_class->num == dBoxClass);
  contact->g1 = const_cast<dxGeom*> (o1);
  contact->g2 = const_cast<dxGeom*> (o2);
  dxCCylinder *cyl = (dxCCylinder*) CLASSDATA(o1);
  dxBox *box = (dxBox*) CLASSDATA(o2);

  // get p1,p2 = cylinder axis endpoints, get radius
  dVector3 p1,p2;
  dReal clen = cyl->lz * REAL(0.5);
  p1[0] = o1->pos[0] + clen * o1->R[2];
  p1[1] = o1->pos[1] + clen * o1->R[6];
  p1[2] = o1->pos[2] + clen * o1->R[10];
  p2[0] = o1->pos[0] - clen * o1->R[2];
  p2[1] = o1->pos[1] - clen * o1->R[6];
  p2[2] = o1->pos[2] - clen * o1->R[10];
  dReal radius = cyl->radius;

  // copy out box center, rotation matrix, and side array
  dReal *c = o2->pos;
  dReal *R = o2->R;
  dReal *side = box->side;

  // get the closest point between the cylinder axis and the box
  dVector3 pl,pb;
  dClosestLineBoxPoints (p1,p2,c,R,side,pl,pb);

  // generate contact point
  return dCollideSpheres (pl,radius,pb,0,contact);
}


// this returns at most one contact point when the two cylinder's axes are not
// aligned, and at most two (for stability) when they are aligned.
// the algorithm minimizes the distance between two "sample spheres" that are
// positioned along the cylinder axes according to:
//    sphere1 = pos1 + alpha1 * axis1
//    sphere2 = pos2 + alpha2 * axis2
// alpha1 and alpha2 are limited to +/- half the length of the cylinders.
// the algorithm works by finding a solution that has both alphas free, or
// a solution that has one or both alphas fixed to the ends of the cylinder.

int dCollideCC (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 == dCCylinderClass);
  dIASSERT (o2->_class->num == dCCylinderClass);
  contact->g1 = const_cast<dxGeom*> (o1);
  contact->g2 = const_cast<dxGeom*> (o2);
  dxCCylinder *cyl1 = (dxCCylinder*) CLASSDATA(o1);
  dxCCylinder *cyl2 = (dxCCylinder*) 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];

  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 = const_cast<dxGeom*> (o1);
		  c2->g2 = const_cast<dxGeom*> (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;
	    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);
	  }
	  else return 0;
	}
	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;
}


int dCollideCP (const dxGeom *o1, const dxGeom *o2, int flags,
		dContactGeom *contact, int skip)
{
  dIASSERT (skip >= (int)sizeof(dContactGeom));
  dIASSERT (o1->_class->num == dCCylinderClass);
  dIASSERT (o2->_class->num == dPlaneClass);
  dxCCylinder *ccyl = (dxCCylinder*) CLASSDATA(o1);
  dxPlane *plane = (dxPlane*) CLASSDATA(o2);

  // collide the deepest capping sphere with the plane
  dReal sign = (dDOT14 (plane->p,o1->R+2) > 0) ? REAL(-1.0) : REAL(1.0);
  dVector3 p;
  p[0] = o1->pos[0] + o1->R[2]  * ccyl->lz * REAL(0.5) * sign;
  p[1] = o1->pos[1] + o1->R[6]  * ccyl->lz * REAL(0.5) * sign;
  p[2] = o1->pos[2] + o1->R[10] * ccyl->lz * REAL(0.5) * sign;

  dReal k = dDOT (p,plane->p);
  dReal depth = plane->p[3] - k + ccyl->radius;
  if (depth < 0) return 0;
  contact->normal[0] = plane->p[0];
  contact->normal[1] = plane->p[1];
  contact->normal[2] = plane->p[2];
  contact->pos[0] = p[0] - plane->p[0] * ccyl->radius;
  contact->pos[1] = p[1] - plane->p[1] * ccyl->radius;
  contact->pos[2] = p[2] - plane->p[2] * ccyl->radius;
  contact->depth = depth;

  int ncontacts = 1;
  if ((flags & NUMC_MASK) >= 2) {
    // collide the other capping sphere with the plane
    p[0] = o1->pos[0] - o1->R[2]  * ccyl->lz * REAL(0.5) * sign;
    p[1] = o1->pos[1] - o1->R[6]  * ccyl->lz * REAL(0.5) * sign;
    p[2] = o1->pos[2] - o1->R[10] * ccyl->lz * REAL(0.5) * sign;

    k = dDOT (p,plane->p);
    depth = plane->p[3] - k + ccyl->radius;
    if (depth >= 0) {
      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] = p[0] - plane->p[0] * ccyl->radius;
      c2->pos[1] = p[1] - plane->p[1] * ccyl->radius;
      c2->pos[2] = p[2] - plane->p[2] * ccyl->radius;
      c2->depth = depth;
      ncontacts = 2;
    }
  }

  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;
}


// this collides a group with another geom. the other geom can also be a
// group, but this case is not handled specially.

int dCollideG (const dxGeom *o1, const dxGeom *o2, int flags,
	       dContactGeom *contact, int skip)
{
  dxGeomGroup *gr = (dxGeomGroup*) CLASSDATA(o1);
  int numleft = flags & NUMC_MASK;
  if (numleft == 0) numleft = 1;
  flags &= ~NUMC_MASK;
  int num=0,i=0;
  while (i < gr->parts.size() && numleft > 0) {
    int n = dCollide (gr->parts[i],const_cast<dxGeom*>(o2),
		      flags | numleft,contact,skip);
    contact = CONTACT (contact,skip*n);
    numleft -= n;
    num += n;
    i++;
  }
  return num;
}

//****************************************************************************
// standard classes

int dSphereClass = -1;
int dBoxClass = -1;
int dCCylinderClass = -1;
int dPlaneClass = -1;


static dColliderFn * dSphereColliderFn (int num)
{
  if (num == dSphereClass) return (dColliderFn *) &dCollideSS;
  if (num == dBoxClass) return (dColliderFn *) &dCollideSB;
  if (num == dPlaneClass) return (dColliderFn *) &dCollideSP;
  return 0;
}


static void dSphereAABB (dxGeom *geom, dReal aabb[6])
{
  dxSphere *s = (dxSphere*) CLASSDATA(geom);
  aabb[0] = geom->pos[0] - s->radius;
  aabb[1] = geom->pos[0] + s->radius;
  aabb[2] = geom->pos[1] - s->radius;
  aabb[3] = geom->pos[1] + s->radius;
  aabb[4] = geom->pos[2] - s->radius;
  aabb[5] = geom->pos[2] + s->radius;
}


static dColliderFn * dBoxColliderFn (int num)
{
  if (num == dBoxClass) return (dColliderFn *) &dCollideBB;
  if (num == dPlaneClass) return (dColliderFn *) &dCollideBP;
  return 0;
}


static void dBoxAABB (dxGeom *geom, dReal aabb[6])
{
  dxBox *b = (dxBox*) CLASSDATA(geom);
  dReal xrange = REAL(0.5) * (dFabs (geom->R[0] * b->side[0]) +
    dFabs (geom->R[1] * b->side[1]) + dFabs (geom->R[2] * b->side[2]));
  dReal yrange = REAL(0.5) * (dFabs (geom->R[4] * b->side[0]) +
    dFabs (geom->R[5] * b->side[1]) + dFabs (geom->R[6] * b->side[2]));
  dReal zrange = REAL(0.5) * (dFabs (geom->R[8] * b->side[0]) +
    dFabs (geom->R[9] * b->side[1]) + dFabs (geom->R[10] * b->side[2]));
  aabb[0] = geom->pos[0] - xrange;
  aabb[1] = geom->pos[0] + xrange;
  aabb[2] = geom->pos[1] - yrange;
  aabb[3] = geom->pos[1] + yrange;
  aabb[4] = geom->pos[2] - zrange;
  aabb[5] = geom->pos[2] + zrange;
}


static dColliderFn * dCCylinderColliderFn (int num)
{
  if (num == dSphereClass) return (dColliderFn *) &dCollideCS;
  if (num == dPlaneClass) return (dColliderFn *) &dCollideCP;
  if (num == dCCylinderClass) return (dColliderFn *) &dCollideCC;
  if (num == dBoxClass) return (dColliderFn *) &dCollideCB;
  return 0;
}


static void dCCylinderAABB (dxGeom *geom, dReal aabb[6])
{
  dxCCylinder *c = (dxCCylinder*) CLASSDATA(geom);
  dReal xrange = dFabs(geom->R[2]  * c->lz) * REAL(0.5) + c->radius;
  dReal yrange = dFabs(geom->R[6]  * c->lz) * REAL(0.5) + c->radius;
  dReal zrange = dFabs(geom->R[10] * c->lz) * REAL(0.5) + c->radius;
  aabb[0] = geom->pos[0] - xrange;
  aabb[1] = geom->pos[0] + xrange;
  aabb[2] = geom->pos[1] - yrange;
  aabb[3] = geom->pos[1] + yrange;
  aabb[4] = geom->pos[2] - zrange;
  aabb[5] = geom->pos[2] + zrange;
}


dColliderFn * dPlaneColliderFn (int num)
{
  return 0;
}


static void dPlaneAABB (dxGeom *geom, dReal aabb[6])
{
  // @@@ planes that have normal vectors aligned along an axis can use a
  // @@@ less comprehensive bounding box.
  aabb[0] = -dInfinity;
  aabb[1] = dInfinity;
  aabb[2] = -dInfinity;
  aabb[3] = dInfinity;
  aabb[4] = -dInfinity;
  aabb[5] = dInfinity;
}


dxGeom *dCreateSphere (dSpaceID space, dReal radius)
{
  dAASSERT (radius > 0);
  if (dSphereClass == -1) {
    dGeomClass c;
    c.bytes = sizeof (dxSphere);
    c.collider = &dSphereColliderFn;
    c.aabb = &dSphereAABB;
    c.aabb_test = 0;
    c.dtor = 0;
    dSphereClass = dCreateGeomClass (&c);
  }

  dxGeom *g = dCreateGeom (dSphereClass);
  if (space) dSpaceAdd (space,g);
  dxSphere *s = (dxSphere*) CLASSDATA(g);
  s->radius = radius;
  return g;
}


dxGeom *dCreateBox (dSpaceID space, dReal lx, dReal ly, dReal lz)
{
  dAASSERT (lx > 0 && ly > 0 && lz > 0);
  if (dBoxClass == -1) {
    dGeomClass c;
    c.bytes = sizeof (dxBox);
    c.collider = &dBoxColliderFn;
    c.aabb = &dBoxAABB;
    c.aabb_test = 0;
    c.dtor = 0;
    dBoxClass = dCreateGeomClass (&c);
  }

  dxGeom *g = dCreateGeom (dBoxClass);
  if (space) dSpaceAdd (space,g);
  dxBox *b = (dxBox*) CLASSDATA(g);
  b->side[0] = lx;
  b->side[1] = ly;
  b->side[2] = lz;
  return g;
}


dxGeom * dCreateCCylinder (dSpaceID space, dReal radius, dReal length)
{
  dAASSERT (radius > 0 && length > 0);
  if (dCCylinderClass == -1) {
    dGeomClass c;
    c.bytes = sizeof (dxCCylinder);
    c.collider = &dCCylinderColliderFn;
    c.aabb = &dCCylinderAABB;
    c.aabb_test = 0;
    c.dtor = 0;
    dCCylinderClass = dCreateGeomClass (&c);
  }

  dxGeom *g = dCreateGeom (dCCylinderClass);
  if (space) dSpaceAdd (space,g);
  dxCCylinder *c = (dxCCylinder*) CLASSDATA(g);
  c->radius = radius;
  c->lz = length;
  return g;
}


dxGeom *dCreatePlane (dSpaceID space,
		      dReal a, dReal b, dReal c, dReal d)
{
  if (dPlaneClass == -1) {
    dGeomClass c;
    c.bytes = sizeof (dxPlane);
    c.collider = &dPlaneColliderFn;
    c.aabb = &dPlaneAABB;
    c.aabb_test = 0;
    c.dtor = 0;
    dPlaneClass = dCreateGeomClass (&c);
  }

  dxGeom *g = dCreateGeom (dPlaneClass);
  if (space) dSpaceAdd (space,g);
  dxPlane *p = (dxPlane*) CLASSDATA(g);

  // make sure plane normal has unit length
  dReal l = a*a + b*b + c*c;
  if (l > 0) {
    l = dRecipSqrt(l);
    p->p[0] = a*l;
    p->p[1] = b*l;
    p->p[2] = c*l;
    p->p[3] = d*l;
  }
  else {
    p->p[0] = 1;
    p->p[1] = 0;
    p->p[2] = 0;
    p->p[3] = 0;
  }
  return g;
}


void dGeomSphereSetRadius (dGeomID g, dReal radius)
{
  dUASSERT (g && g->_class->num == dSphereClass,"argument not a sphere");
  dAASSERT (radius > 0);
  dxSphere *s = (dxSphere*) CLASSDATA(g);
  s->radius = radius;
}


void dGeomBoxSetLengths (dGeomID g, dReal lx, dReal ly, dReal lz)
{
  dUASSERT (g && g->_class->num == dBoxClass,"argument not a box");
  dAASSERT (lx > 0 && ly > 0 && lz > 0);
  dxBox *b = (dxBox*) CLASSDATA(g);
  b->side[0] = lx;
  b->side[1] = ly;
  b->side[2] = lz;
}


void dGeomPlaneSetParams (dGeomID g, dReal a, dReal b, dReal c, dReal d)
{
  dUASSERT (g && g->_class->num == dPlaneClass,"argument not a plane");
  dxPlane *p = (dxPlane*) CLASSDATA(g);
  p->p[0] = a;
  p->p[1] = b;
  p->p[2] = c;
  p->p[3] = d;
}


void dGeomCCylinderSetParams (dGeomID g, dReal radius, dReal length)
{
  dUASSERT (g && g->_class->num == dCCylinderClass,"argument not a ccylinder");
  dAASSERT (radius > 0 && length > 0);
  dxCCylinder *c = (dxCCylinder*) CLASSDATA(g);
  c->radius = radius;
  c->lz = length;
}


dReal dGeomSphereGetRadius (dGeomID g)
{
  dUASSERT (g && g->_class->num == dSphereClass,"argument not a sphere");
  dxSphere *s = (dxSphere*) CLASSDATA(g);
  return s->radius;
}


void  dGeomBoxGetLengths (dGeomID g, dVector3 result)
{
  dUASSERT (g && g->_class->num == dBoxClass,"argument not a box");
  dxBox *b = (dxBox*) CLASSDATA(g);
  result[0] = b->side[0];
  result[1] = b->side[1];
  result[2] = b->side[2];
}


void  dGeomPlaneGetParams (dGeomID g, dVector4 result)
{
  dUASSERT (g && g->_class->num == dPlaneClass,"argument not a plane");
  dxPlane *p = (dxPlane*) CLASSDATA(g);
  result[0] = p->p[0];
  result[1] = p->p[1];
  result[2] = p->p[2];
  result[3] = p->p[3];
}


void dGeomCCylinderGetParams (dGeomID g, dReal *radius, dReal *length)
{
  dUASSERT (g && g->_class->num == dCCylinderClass,"argument not a ccylinder");
  dxCCylinder *c = (dxCCylinder*) CLASSDATA(g);
  *radius = c->radius;
  *length = c->lz;
}

//****************************************************************************
// geom group

int dGeomGroupClass = -1;

static dColliderFn * dGeomGroupColliderFn (int num)
{
  return (dColliderFn *) &dCollideG;
}


static void dGeomGroupAABB (dxGeom *geom, dReal aabb[6])
{
  dxGeomGroup *gr = (dxGeomGroup*) CLASSDATA(geom);
  aabb[0] = dInfinity;
  aabb[1] = -dInfinity;
  aabb[2] = dInfinity;
  aabb[3] = -dInfinity;
  aabb[4] = dInfinity;
  aabb[5] = -dInfinity;
  int i,j;
  for (i=0; i < gr->parts.size(); i++) {
    dReal aabb2[6];
    gr->parts[i]->_class->aabb (gr->parts[i],aabb2);
    for (j=0; j<6; j += 2) if (aabb2[j] < aabb[j]) aabb[j] = aabb2[j];
    for (j=1; j<6; j += 2) if (aabb2[j] > aabb[j]) aabb[j] = aabb2[j];
  }
}


static void dGeomGroupDtor (dxGeom *geom)
{
  dxGeomGroup *gr = (dxGeomGroup*) CLASSDATA(geom);
  gr->parts.~dArray();
}


dxGeom *dCreateGeomGroup (dSpaceID space)
{
  if (dGeomGroupClass == -1) {
    dGeomClass c;
    c.bytes = sizeof (dxGeomGroup);
    c.collider = &dGeomGroupColliderFn;
    c.aabb = &dGeomGroupAABB;
    c.aabb_test = 0;
    c.dtor = &dGeomGroupDtor;
    dGeomGroupClass = dCreateGeomClass (&c);
  }

  dxGeom *g = dCreateGeom (dGeomGroupClass);
  if (space) dSpaceAdd (space,g);
  dxGeomGroup *gr = (dxGeomGroup*) CLASSDATA(g);
  gr->parts.constructor();
  return g;
}


void dGeomGroupAdd (dxGeom *g, dxGeom *x)
{
  dUASSERT (g && g->_class->num == dGeomGroupClass,"argument not a geomgroup");
  dxGeomGroup *gr = (dxGeomGroup*) CLASSDATA(g);
  gr->parts.push (x);
}


void dGeomGroupRemove (dxGeom *g, dxGeom *x)
{
  dUASSERT (g && g->_class->num == dGeomGroupClass,"argument not a geomgroup");
  dxGeomGroup *gr = (dxGeomGroup*) CLASSDATA(g);
  for (int i=0; i < gr->parts.size(); i++) {
    if (gr->parts[i] == x) {
      gr->parts.remove (i);
      return;
    }
  }
}


int dGeomGroupGetNumGeoms (dxGeom *g)
{
  dUASSERT (g && g->_class->num == dGeomGroupClass,"argument not a geomgroup");
  dxGeomGroup *gr = (dxGeomGroup*) CLASSDATA(g);
  return gr->parts.size();
}


dxGeom * dGeomGroupGetGeom (dxGeom *g, int i)
{
  dUASSERT (g && g->_class->num == dGeomGroupClass,"argument not a geomgroup");
  dxGeomGroup *gr = (dxGeomGroup*) CLASSDATA(g);
  dAASSERT (i >= 0 && i < gr->parts.size());
  return gr->parts[i];
}

//****************************************************************************
// transformed geom

int dGeomTransformClass = -1;

struct dxGeomTransform {
  dxGeom *obj;		// object that is being transformed
  int cleanup;		// 1 to destroy obj when destroyed
  int infomode;		// 1 to put Tx geom in dContactGeom g1
  dVector3 final_pos;	// final tx (body tx + relative tx) of the object.
  dMatrix3 final_R;	//   this is only set if the AABB function is called
};			//   by space collision before the collide fn is called


// compute final pos and R for the encapsulated geom object

static void compute_final_tx (const dxGeom *g)
{
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(g);
  dMULTIPLY0_331 (tr->final_pos,g->R,tr->obj->pos);
  tr->final_pos[0] += g->pos[0];
  tr->final_pos[1] += g->pos[1];
  tr->final_pos[2] += g->pos[2];
  dMULTIPLY0_333 (tr->final_R,g->R,tr->obj->R);
}



// this collides a transformed geom with another geom. the other geom can
// also be a transformed geom, but this case is not handled specially.

int dCollideT (const dxGeom *o1, const dxGeom *o2, int flags,
	       dContactGeom *contact, int skip)
{
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(o1);
  if (!tr->obj) return 0;
  dUASSERT (tr->obj->spaceid==0,
	    "GeomTransform encapsulated object must not be in a space");
  dUASSERT (tr->obj->body==0,
	    "GeomTransform encapsulated object must not be attach to a body");

  // backup the relative pos and R pointers of the encapsulated geom object,
  // and the body pointer
  dReal *posbak = tr->obj->pos;
  dReal *Rbak = tr->obj->R;
  dxBody *bodybak = tr->obj->body;

  // compute temporary pos and R for the encapsulated geom object
  if (!o1->space_aabb) compute_final_tx (o1);
  tr->obj->pos = tr->final_pos;
  tr->obj->R = tr->final_R;
  tr->obj->body = o1->body;

  // do the collision
  int n = dCollide (tr->obj,const_cast<dxGeom*>(o2),flags,contact,skip);

  // if required, adjust the 'g1' values in the generated contacts so that
  // thay indicated the GeomTransform object instead of the encapsulated
  // object.
  if (tr->infomode) {
    for (int i=0; i<n; i++) {
      dContactGeom *c = CONTACT(contact,skip*i);
      c->g1 = const_cast<dxGeom*> (o1);
    }
  }

  // restore the pos, R and body
  tr->obj->pos = posbak;
  tr->obj->R = Rbak;
  tr->obj->body = bodybak;
  return n;
}


static dColliderFn * dGeomTransformColliderFn (int num)
{
  return (dColliderFn *) &dCollideT;
}


static void dGeomTransformAABB (dxGeom *geom, dReal aabb[6])
{
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(geom);
  if (!tr->obj) {
    dSetZero (aabb,6);
    return;
  }

  // backup the relative pos and R pointers of the encapsulated geom object
  dReal *posbak = tr->obj->pos;
  dReal *Rbak = tr->obj->R;

  // compute temporary pos and R for the encapsulated geom object
  compute_final_tx (geom);
  tr->obj->pos = tr->final_pos;
  tr->obj->R = tr->final_R;

  // compute the AABB
  tr->obj->_class->aabb (tr->obj,aabb);

  // restore the pos and R
  tr->obj->pos = posbak;
  tr->obj->R = Rbak;
}


static void dGeomTransformDtor (dxGeom *geom)
{
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(geom);
  if (tr->obj && tr->cleanup) {
    dGeomDestroy (tr->obj);
  }
}


dxGeom *dCreateGeomTransform (dSpaceID space)
{
  if (dGeomTransformClass == -1) {
    dGeomClass c;
    c.bytes = sizeof (dxGeomTransform);
    c.collider = &dGeomTransformColliderFn;
    c.aabb = &dGeomTransformAABB;
    c.aabb_test = 0;
    c.dtor = dGeomTransformDtor;
    dGeomTransformClass = dCreateGeomClass (&c);
  }

  dxGeom *g = dCreateGeom (dGeomTransformClass);
  if (space) dSpaceAdd (space,g);

  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(g);
  tr->obj = 0;
  tr->cleanup = 0;
  tr->infomode = 0;
  dSetZero (tr->final_pos,4);
  dRSetIdentity (tr->final_R);

  return g;
}


void dGeomTransformSetGeom (dxGeom *g, dxGeom *obj)
{
  dUASSERT (g && g->_class->num == dGeomTransformClass,
	    "argument not a geom transform");
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(g);
  if (tr->obj && tr->cleanup) {
    dGeomDestroy (tr->obj);
  }
  tr->obj = obj;
}


dxGeom * dGeomTransformGetGeom (dxGeom *g)
{
  dUASSERT (g && g->_class->num == dGeomTransformClass,
	    "argument not a geom transform");
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(g);
  return tr->obj;
}


void dGeomTransformSetCleanup (dGeomID g, int mode)
{
  dUASSERT (g && g->_class->num == dGeomTransformClass,
	    "argument not a geom transform");
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(g);
  tr->cleanup = mode;
}


int dGeomTransformGetCleanup (dGeomID g)
{
  dUASSERT (g && g->_class->num == dGeomTransformClass,
	    "argument not a geom transform");
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(g);
  return tr->cleanup;
}


void dGeomTransformSetInfo (dGeomID g, int mode)
{
  dUASSERT (g && g->_class->num == dGeomTransformClass,
	    "argument not a geom transform");
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(g);
  tr->infomode = mode;
}


int dGeomTransformGetInfo (dGeomID g)
{
  dUASSERT (g && g->_class->num == dGeomTransformClass,
	    "argument not a geom transform");
  dxGeomTransform *tr = (dxGeomTransform*) CLASSDATA(g);
  return tr->infomode;
}

//****************************************************************************
// other utility functions

void dInfiniteAABB (dxGeom *geom, dReal aabb[6])
{
  aabb[0] = -dInfinity;
  aabb[1] = dInfinity;
  aabb[2] = -dInfinity;
  aabb[3] = dInfinity;
  aabb[4] = -dInfinity;
  aabb[5] = dInfinity;
}


void dCloseODE()
{
  if (colliders) {
    delete colliders;
    colliders = 0;
  }
  if (classes) {
    for (int i=0; i < classes->size(); i++) {
      dFree ((*classes)[i], sizeof (dxGeomClass));
    }
    delete classes;
    classes = 0;
  }

  // reset geom class vars
  dSphereClass = -1;
  dBoxClass = -1;
  dCCylinderClass = -1;
  dPlaneClass = -1;
  dGeomGroupClass = -1;
  dGeomTransformClass = -1;

  // if you're using contrib code you may want to uncomment the following:
  // dTriListClass = -1;
  // dRayClass = -1;
}

//****************************************************************************
// geom notification functions that are required to co-exist with the new
// collision infrastructure.

void dGeomMoved (dxGeom *)
{
}


dxGeom *dGeomGetBodyNext (dxGeom *)
{
  return 0;
}
