#include "gjk.h" /* * Implementation of the Gilbert, Johnson and Keerthi routine to compute * the minimum distance between two convex polyhedra. * * Version 2.4, July 1998, (c) Stephen Cameron 1996, 1997, 1998 * */ /* The code uses some tables that essentially encode the constant topology of simplices in DIM-dimensional space. The original version of this program did this by constructing the tables each time the code was activated (but not once per run of gjk_distance!). In this version of the code we can define the constant USE_CONST_TABLES to use pre-defined versions of these tables instead. Not defining USE_CONST_TABLES makes the code behave as before. This takes around 5000 integer operations at code initialisation when DIM==3. Defining USE_CONST_TABLES uses the pre-defined tables. Defining DUMP_CONST_TABLES makes the code construct appropriate versions of the tables for space of dimension <= DIM on the first call of gjk_distance, and to simply dump these to standard ouput and immediately call exit(0) to abort the programme. This may be used if either the pre-defined tables have become corrupted, or if you wish to use pre-defined tables for a higher dimension. */ #define USE_CONST_TABLES /**/ /* standard definitions, derived from those in gjk.h */ #define TWO_TO_DIM (1<0 && NumVertices( obj2)>0 ); use_default = first_iteration = 1; #ifdef CONSTRUCT_TABLES initialise_simplex_distance(); /* will return immediately if already initialised */ #else assert( PRE_DEFINED_TABLE_DIM >= DIM ); #endif /* CONSTRUCT_TABLES */ compute_both_witnesses = ( wpt1!=0 ) || ( wpt2!=0 ) || ( tr1!=0 ) || ( tr2!=0 ); if ( wpt1==0 ) wpt1 = local_witness1; if ( wpt2==0 ) wpt2 = local_witness2; fdisp = IdentityTransform( tr1) ? displacementv : local_fdisp; rdisp = IdentityTransform( tr2) ? reverse_displacementv : local_rdisp; if ( simplex==0 ) { use_seed = 0; simplex = & local_simplex; } if ( use_seed==0 ) { simplex->simplex1[0] = 0; simplex->simplex2[0] = 0; simplex->npts = 1; simplex->lambdas[0] = ONE; simplex->last_best1 = 0; simplex->last_best2 = 0; add_simplex_vertex( simplex, 0, obj1, FirstVertex( obj1), tr1, obj2, FirstVertex( obj2), tr2); } else { /* If we are being told to use this seed point, there is a good chance that the near point will be on the current simplex. Besides, if we don't confirm that the seed point given satisfies the invariant (that the witness points given are the closest points on the current simplex) things can and will fall down. */ for ( v=0 ; vnpts ; v++ ) add_simplex_vertex( simplex, v, obj1, simplex->simplex1[v], tr1, obj2, simplex->simplex2[v], tr2); } /* Now the main loop. We first compute the distance between the current simplicies, the check whether this gives the globally correct answer, and if not construct new simplices and try again. */ max_iterations = NumVertices( obj1)*NumVertices( obj2) ; /* in practice we never see more than about 6 iterations. */ /* Counting the iterations in this way should not be necessary; a while( 1) should do just as well. */ while ( max_iterations-- > 0 ) { if ( simplex->npts==1 ) { /* simple case */ simplex->lambdas[0] = ONE; } else { /* normal case */ compute_subterms( simplex); if ( use_default ) { use_default = default_distance( simplex); } if ( !use_default ) { backup_distance( simplex); } } /* compute at least the displacement vectors given by the simplex_point structure. If we are to provide both witness points, it's slightly faster to compute those first. */ if ( compute_both_witnesses ) { compute_point( wpt1, simplex->npts, simplex->coords1, simplex->lambdas); compute_point( wpt2, simplex->npts, simplex->coords2, simplex->lambdas); overd( d) { displacementv[ d] = wpt2[d] - wpt1[d]; reverse_displacementv[ d] = - displacementv[d]; } } else { overd( d) { displacementv[d] = 0; for ( p=0 ; pnpts ; p++ ) displacementv[d] += DO_MULTIPLY( simplex->lambdas[p], simplex->coords2[p][d] - simplex->coords1[p][d]); reverse_displacementv[ d] = - displacementv[d]; } } sqrd = OTHER_DOT_PRODUCT( displacementv, displacementv); /* if we are using a c-space simplex with DIM_PLUS_ONE points, this is interior to the simplex, and indicates that the original hulls overlap, as does the distance between them being too small. */ if ( sqrderror = EPSILON; return sqrd; } if ( ! IdentityTransform( tr1) ) ApplyInverseRotation( tr1, displacementv, fdisp); if ( ! IdentityTransform( tr2) ) ApplyInverseRotation( tr2, reverse_displacementv, rdisp); /* find the point in obj1 that is maximal in the direction displacement, and the point in obj2 that is minimal in direction displacement; */ maxp = support_function( obj1, ( use_seed ? simplex->last_best1 : InvalidVertexID), &maxv, fdisp ); minp = support_function( obj2, ( use_seed ? simplex->last_best2 : InvalidVertexID), &minus_minv, rdisp ); /* Now apply the G-test on this pair of points */ INCREMENT_G_TEST_COUNTER; g_val = sqrd + maxv + minus_minv; if ( ! IdentityTransform( tr1) ) { ExtractTranslation( tr1, trv); g_val += OTHER_DOT_PRODUCT( displacementv, trv); } if ( ! IdentityTransform( tr2) ) { ExtractTranslation( tr2, trv); g_val += OTHER_DOT_PRODUCT( reverse_displacementv, trv); } if ( g_val < 0.0 ) /* not sure how, but it happens! */ g_val = 0; if ( g_val < EPSILON ) { /* then no better points - finish */ simplex->error = g_val; return sqrd; } /* check for good calculation above */ if ( (first_iteration || (sqrd < oldsqrd)) && (simplex->npts <= DIM ) ) { /* Normal case: add the new c-space points to the current simplex, and call simplex_distance() */ simplex->simplex1[ simplex->npts] = simplex->last_best1 = maxp; simplex->simplex2[ simplex->npts] = simplex->last_best2 = minp; simplex->lambdas[ simplex->npts] = ZERO; add_simplex_vertex( simplex, simplex->npts, obj1, maxp, tr1, obj2, minp, tr2); simplex->npts++; oldsqrd = sqrd; first_iteration = 0; use_default = 1; continue; } /* Abnormal cases! */ if ( use_default ) { use_default = 0; } else { /* give up trying! */ simplex->error = g_val; return sqrd; } } /* end of `while ( 1 )' */ return 0.0; /* we never actually get here, but it keeps some fussy compilers happy. */ } /* * A subsidary routine, that given a simplex record, the point arrays to * which it refers, an integer 1 or 2, and a pointer to a vector, sets * the coordinates of that vector to the coordinates of either the first * or second witness point given by the simplex record. */ int gjk_extract_point( struct simplex_point *simp, int whichpoint, REAL vector[]) { REAL (* coords)[DIM]; assert( whichpoint==1 || whichpoint==2 ); coords = ( whichpoint==1 ) ? simp->coords1 : simp->coords2; compute_point( vector, simp->npts, coords, simp->lambdas); return 1; } static REAL delta[TWICE_TWO_TO_DIM]; /* The simplex_distance routine requires the computation of a number of delta terms. These are computed here. */ static void compute_subterms( struct simplex_point * simp) { int i, j, ielt, jelt, s, jsubset, size = simp->npts; REAL sum, c_space_points[DIM_PLUS_ONE][DIM]; /* compute the coordinates of the simplex as C-space obstacle points */ for ( i=0 ; icoords1[i][j] - simp->coords2[i][j]; /* compute the dot product terms */ for ( i=0 ; i2, so use the general case */ /* for each element of this subset s, namely elts( s, j) */ for ( j=0 ; jnpts; INCREMENT_SIMPLICES_COUNTER; assert( size>0 && size<=DIM_PLUS_ONE ); /* for every subset s of the given set of points ... */ for ( s=1 ; s < TWICE_TWO_TO_DIM && max_elt( s) < size ; s++ ) { /* delta[s] will accumulate the sum of the delta expressions for this subset, and ok will remain TRUE whilst this subset can still be thought to be a candidate simplex for the shortest distance. */ delta[s] = ZERO; ok=1; /* Now the first check is whether the simplex formed by this subset holds the foot of the perpendicular from the origin to the point/line/plane passing through the simplex. This will be the case if all the delta terms for each predecessor subset are (strictly) positive. */ for ( j=0 ; ok && jZERO ) delta[s] += delta( s, elts( s, j)); else ok = 0; } /* If the subset survives the previous test, we still need to check whether the true minimum distance is to a larger piece of geometry, or indeed to another piece of geometry of the same dimensionality. A necessary and sufficient condition for it to fail at this stage is if the delta term for any successor subset is positive, as this indicates a direction on the appropriate higher dimensional simplex in which the distance gets shorter. */ for ( k=0 ; ok && k < size - card( s) ; k++ ) { if ( delta( succ( s, k), non_elts( s, k))>0 ) ok = 0; } #ifdef TEST_BACKUP_PROCEDURE /* define TEST_BACKUP_PROCEDURE in gjk.h to test accuracy of the the backup procedure */ ok = 0; #endif if ( ok && delta[s]>=TINY ) /* then we've found a viable subset */ break; } if ( ok ) { reset_simplex( s, simplex); return 1; } else return 0; } /* A version of GJK's `Backup Procedure'. Note that it requires that the delta[s] entries have been computed for all viable s within simplex_distance. */ static void backup_distance( struct simplex_point * simplex) { int s, i, j, k, bests; int size = simplex->npts; REAL distsq_num[TWICE_TWO_TO_DIM], distsq_den[TWICE_TWO_TO_DIM]; INCREMENT_BACKUP_COUNTER; /* for every subset s of the given set of points ... */ bests = 0; for ( s=1 ; s < TWICE_TWO_TO_DIM && max_elt( s) < size ; s++ ) { if ( delta[s] <= ZERO ) continue; for ( i=0 ; i=j, which is true as they are stored in ascending order. */ oldpos = elts( subset, j); if ( oldpos!=j ) { simplex->simplex1[j] = simplex->simplex1[oldpos]; simplex->simplex2[j] = simplex->simplex2[oldpos]; overd(i ) { simplex->coords1[j][i] = simplex->coords1[oldpos][i]; simplex->coords2[j][i] = simplex->coords2[oldpos][i]; } } simplex->lambdas[j] = DO_DIVIDE( delta( subset, elts( subset, j)), delta[subset]); } simplex->npts = card( subset); return; } /* * The implementation of the support function. Given a direction and * a hull, this function returns a vertex of the hull that is maximal * in that direction, and the value (i.e., dot-product of the maximal * vertex and the direction) associated. * * If there is no topological information given for the hull * then an exhaustive search of the vertices is used. Otherwise, * hill-climbing is performed. If EAGER_HILL_CLIMBING is defined * then the hill-climbing moves to a new vertex as soon as a better * vertex is found, and if it is not defined then every vertex leading * from the current vertex is explored before moving to the best one. * Initial conclusions are that fewer vertices are explored with * EAGER_HILL_CLIMBING defined, but that the code runs slighty slower! * This is presumably due to pipeline bubbles and/or cache misses. * */ static VertexID support_function( Object obj, VertexID start, REAL * supportval, REAL * direction) { if ( !ValidRings( obj) ) { /* then no information for hill-climbing. Use brute-force instead. */ return support_simple( obj, start, supportval, direction); } else { return support_hill_climbing( obj, start, supportval, direction); } } static VertexID support_simple( Object obj, VertexID start, REAL * supportval, REAL * direction) { VertexID p, maxp; REAL maxv, thisv; /* then no information for hill-climbing. Use brute-force instead. */ p = maxp = FirstVertex( obj); maxv = SupportValue( obj, maxp, direction); for ( IncrementVertex( obj, p) ; !LastVertex( obj, p) ; IncrementVertex( obj, p) ) { thisv = SupportValue( obj, p, direction); if ( thisv>maxv ) { maxv = thisv; maxp = p; } } *supportval = maxv; return maxp; } static VertexID support_hill_climbing( Object obj, VertexID start, REAL * supportval, REAL * direction) { VertexID p, maxp, lastvisited, neighbour; EdgeID index; REAL maxv, thisv; /* Use hill-climbing */ p = lastvisited = InvalidVertexID; maxp = ( !ValidVertex( obj, start) ) ? FirstVertex( obj) : start; maxv = SupportValue( obj, maxp, direction); while ( p != maxp ) { p = maxp; /* Check each neighbour of the current point. */ for ( index = FirstEdge( obj, p) ; ValidEdge( obj, index) ; IncrementEdge( obj, index) ) { neighbour = VertexOfEdge( obj, index); /* Check that we haven't already visited this one in the last outer iteration. This is to avoid us calculating the dot-product with vertices we've already looked at. */ if ( neighbour==lastvisited ) continue; thisv = SupportValue( obj, neighbour, direction); if ( thisv>maxv ) { maxv = thisv; maxp = neighbour; #ifdef EAGER_HILL_CLIMBING /* The next line gives Gilbert & Ong's eager behaviour. */ break; #endif } } lastvisited = p; } *supportval = maxv; return p; } /* Computes the coordinates of a simplex point. Takes an array into which the stuff the result, the number of vertices that make up a simplex point, one of the point arrays, the indices of which of the points are used in the for the simplex points, and an array of the lambda values. */ static void compute_point( REAL pt[DIM], int len, REAL (* vertices)[DIM], REAL lambdas[]) { int d, i; overd( d) { pt[d] = 0; for ( i=0 ; icoords1[pos]); ApplyTransform( t2, obj2, v2, s->coords2[pos]); return; } #ifdef DUMP_CONST_TABLES #define dump1d( array) \ printf( "static const int " #array "[%d] = {\n\t%2d", \ TWICE_TWO_TO_DIM, array[0]); \ for ( s=1 ; s