#include #include #include #include #include /* Demo program for the Oxford University Computing Laboratory implementation of Gilbert, Johnson and Keerthi's algorithm for computing the minimum distance between two convex polyhedra. (c) Oxford University Computing Laboratory 1996. Version History: 0.9 pre-release 1.0 First public release, December 1996. The program usage is gjkdemo [-h] [-H] [-Rfactor] [-Tfactor] [-q] [-Q] [-rrepeats] [-iinstances] [ -sseed ] n_runs [ n_pts ] If the -h flag is present, it simply prints a help message. If the -H flag is present, it inhibits the use of hill-climbing. If the -R flag is present, it enables rotations, and the factor scales the mean rotation velocity (set to 1 if missing). If the -T flag is present, the factor scales the mean translational velocity (set to 1 if missing). If the -q flag is present then the program runs in quiet mode, in which the normal per instance output of the program is suppressed. If the -Q flag is present, and QHULL is defined, then convex hulls of random point sets are used instead of the semi-regular hulls used otherwise. Each run of GJK is repeated several times in order to improve the timing accuracy. The number of repeats used can be changed with the -r option. (Timing accuracy is likely to be low unless this number is large -- say >> 100.) The instance count is given by the -i option, and defaults to 1. It says how many times to use any one pair of hulls, moving them slightly between calls. For each example the program lists the seed integer used in the random number generator (as an unsigned string of 8 hexadecimal digits), in a form suitable to be passed to the program in the -s argument. If the seed argument is missing a random seed is used, derived from the system clock. n_runs gives the number of runs of the program, that is, the number of different hull pairs given to GJK to solve. Each hull is generated by the number of points given (or DEF_PTS if not given). */ /* A Note on Array Sizes: Euler's formula relates the numver of vertices, edges and faces in a convex polyhedron as V - E + F = 2 We find it more convenient to replace E by H, the number of half-edges, with H = 2E. Every face is bounded by at least three half-edges, giving F <= 3H. Using this inequality produces H <= 6V - 12 and consequently F <= 2V - 4. In fact these bounds are tight: consider a generalised diamond shape with N vertices, with one at (0,0,1), one at (0,0,-1), and the other N-2 arranged on the perimeter of the circle x^2 + y^2 = 1, z = 0. Convenient approximations to these inequalities, used below, are F <= 2V and H <= 6V. The ring arrays require an entry for each vertex showing where each ring starts, an entry for each vertex marking the end of each ring, and an entry for each half-edge, and therefore the ring arrays are bounded in size by 8V. */ #include "gjk.h" #define ERROR_EPSILON EPSILON /* EPSILON is defined in gjk.h */ #define MAX_POINTS 1000 /* maximum number of points per hull */ #define DEF_PTS 20 /* default number of points */ #define TERMINATOR -1 /* terminator for edge lists */ /* the following four quantities affect the random hulls and boxes generated; see the routines create_polyball(), create_hull() and shift_hull_random() for precisely how. */ #define MEAN_RADIUS 50 #define RADIUS_RATIO 0.2 #define LENGTH_RATIO 0.6 #define TRANSLATION_WIDTH 500 #define TRANSLATION_SPEED 1 #define ROTATION_SINE_AZIMUTH 0.1 #define ROTATION_SINE_INCLIN 0.05 /* this vector is the constant vector by which one of the hulls is shifted for each distinct instance. */ static const REAL delta_vector[3] = { 1, -0.5, -1 }; /* External functions, not declared in gjk.h */ #ifdef QHULL int sac_qhull( int npts, const double (*input_array)[DIM], double (*points)[DIM], int * size_rings, int rings[], int * num_faces, double (*faces)[DIM+1]); #endif /* local functions */ #ifdef QHULL static void create_hull( int npts, REAL (*pts)[DIM], INDEX * rings); static int num_outside( int npts, double * err, const REAL (*pts)[DIM], REAL witness[DIM]); #endif static void create_polyball( int npts, REAL (*pts)[DIM], INDEX * rings); static void generate_initial_random_shift( REAL pts[DIM]); static void add_delta_random_shift( REAL pts[DIM]); static void add_delta_random_rotation( int npts, REAL (*pts)[DIM]); static void shift_and_copy_hull( int npts, const REAL (*src)[DIM], REAL (*pts)[DIM], const REAL delta[DIM]); static int num_further( int npts, double * err, REAL (*pts)[DIM], REAL direction[DIM], REAL witness[DIM]); static double dot_product( REAL v1[3], REAL v2[3]); static double clocktime( void); static double unit_random( unsigned long * seed); static void give_help_and_exit( void); static double rotation_factor = 1, translation_factor = 1, shift_factor = 1, radius_factor = 1; static char * prog_name; static unsigned long seed; /* current value of the seed for the random number generator */ int main( int argc, char ** argv) { /* vertex arrays: both objects, and a centred form of the second object */ REAL points1[MAX_POINTS][3], points2[MAX_POINTS][3], centred_points2[MAX_POINTS][3]; /* edge ring arrays for both objects */ INDEX fixed_rings1[MAX_RING_SIZE_MULTIPLIER * MAX_POINTS]; INDEX fixed_rings2[MAX_RING_SIZE_MULTIPLIER * MAX_POINTS]; INDEX * rings1, * rings2; unsigned long this_seed, original_seed; /* random number seeds */ int num_pts, repeat, num_repeats, instance, num_instances; long run, num_runs, num_errors, total_g_test, total_simplices, total_vertices, total_dp, total_sdp, total_mult_divide_only, total_ops, num_zero_runs; double aver_verts, aver_dp, aver_sdp, aver_ops, aver_time; double aver_time_g, aver_time_simp, aver_time_dp, aver_time_op; REAL dist, sqdist, errorval, total_dist, aver_dist, aver_zeros; REAL wit1[3], wit2[3], one_to_two[3], two_to_one[3], hull2_shift[3]; struct simplex_point simplex_record, initial_simplex_record; double start_time, base_time, total_time, initial_time, elapsed_time; char outbuff[256]; /* line output buffer */ int d, nbad1, nbad2; REAL err1, err2; /* various flags */ int quiet = 0, rotating = 0, use_polyballs = 1, allow_hill_climbing = 1; num_instances = 10; num_repeats = 100; prog_name = argv[0]; /* now deal with setting the intial value of the seed. */ original_seed = (unsigned long) time( NULL); /* Parse the optional arguments */ while ( argc>=2 && argv[1][0]==(char) '-' ) { if ( !strcmp( "-h", argv[1]) ) give_help_and_exit(); else if ( !strcmp( "-H", argv[1]) ) allow_hill_climbing = 0; else if ( !strcmp( "-q", argv[1]) ) quiet = 1; #ifdef QHULL else if ( !strcmp( "-Q", argv[1]) ) use_polyballs = 0; #endif else if ( !strncmp( "-R", argv[1], 2) ) { rotating = 1; if ( argv[1][2]!=0 ) rotation_factor = atof( argv[1]+2); } else if ( !strncmp( "-T", argv[1], 2) ) { if ( argv[1][2]!=(char) '\0' ) shift_factor = atof( argv[1]+2); } else if ( !strncmp( "-r", argv[1], 2) ) num_repeats = atoi( argv[1]+2); else if ( !strncmp( "-i", argv[1], 2) ) num_instances = atoi( argv[1]+2); else if ( !strncmp( "-s", argv[1], 2) ) /* decode the argument as a long, unsigned hexadecimal string */ (void) sscanf( argv[1]+2, "%lx", &original_seed); else give_help_and_exit(); argv++; argc--; } if ( argc<2 || argc>3 ) /* wrong number of arguments */ give_help_and_exit(); num_runs = atol( argv[1]); /* grab number of runs */ /* set the number of points for each hull */ num_pts = ( argc>2 ) ? atoi( argv[2]) : DEF_PTS; if ( num_pts<4 || num_pts>MAX_POINTS ) { fprintf( stderr, "Bad number of points (%d requested, minimum 4, maximum %d)\n", num_pts, MAX_POINTS); exit( 1); } printf( "Enhanced GJK demo v1.0 (c) OUCL 1996\n" "\t%d runs using %d instances, %d repeats\n" "\t%d vertices per %s, seed %08lx, hill-climbing %s\n", num_runs, num_instances, num_repeats, num_pts, ( use_polyballs ) ? "polyball" : "hull", original_seed, ( allow_hill_climbing ? "enabled" : "disabled" ) ); num_errors = total_g_test = total_simplices = total_vertices = total_dp = total_sdp = total_mult_divide_only = 0; num_zero_runs = 0; total_dist = 0; total_time = 0; /* check whether we are doing hill-climbing */ if ( allow_hill_climbing ) { rings1 = fixed_rings1; rings2 = fixed_rings2; } else { rings1 = rings2 = 0; } initial_time = clocktime(); /* Now the main loop to run GJK itself */ repeat = 0; instance = 0; run = 0; seed = original_seed; while ( run0)); } total_time += clocktime() - start_time; /* update running totals */ total_g_test += gjk_num_g_test; total_simplices += gjk_num_simplices; total_dp += gjk_num_dot_products; total_sdp += gjk_num_support_dp; total_mult_divide_only += gjk_num_other_ops; total_vertices += 2*num_pts; dist = sqrt( sqdist); total_dist += dist; if ( sqdist==0.0 ) num_zero_runs++; sprintf( outbuff+strlen( outbuff), " %6.2lf", dist); /* Now to test the answers. Compute the displacement vectors Don't assume that the witness points were returned. */ gjk_extract_point( &simplex_record, points1, 1, wit1); gjk_extract_point( &simplex_record, points2, 2, wit2); for ( d=0 ; d<3 ; d++ ) { two_to_one[d] = wit1[d] - wit2[d]; one_to_two[d] = - two_to_one[d]; } /* errorval should be zero, to the accuracy of our arithmetic */ errorval = sqdist - dot_product( two_to_one, two_to_one); if ( sqdist>ZERO ) { /* now check, for each hull, how many of its points lie to the wrong side of a plane that lies through the witness point and perpendicular to the displacement vector */ nbad1 = num_further( num_pts, &err1, points1, one_to_two, wit1); nbad2 = num_further( num_pts, &err2, points2, two_to_one, wit2); } else { /* zero was returned, so check that the witness point lies within both hulls. Requires QHULL */ #ifdef QHULL nbad1 = num_outside( num_pts, &err1, points1, wit1); nbad2 = num_outside( num_pts, &err2, points2, wit2); #else nbad1 = nbad2 = 0; /* can't test, assume OK */ #endif /* QHULL */ } if ( errorval < -ERROR_EPSILON || errorval > ERROR_EPSILON || ( nbad1>0 || nbad2>0 ) ) { num_errors++; printf( "%s", outbuff); printf( " ERROR=%lf (%lf,%lf), sqdist=%.2lf, nbad=%d,%d\n", errorval, err1, err2, sqdist, nbad1, nbad2); } else if ( !quiet ) printf( "%s\n", outbuff); instance = ( instance+1 ) % num_instances; run++; } /* end of main loop that calls GJK */ elapsed_time = clocktime() - initial_time; /* scale total_time down to that for just one repeat */ total_time /= num_repeats; total_ops = 3 * total_dp + total_mult_divide_only; aver_verts = ((double) total_vertices)/num_runs/2; aver_dp = ((double) total_dp)/num_runs; aver_sdp = ((double) total_sdp)/num_runs; aver_ops = ((double) total_ops)/num_runs; aver_zeros = ((double) num_zero_runs)/num_runs; aver_dist = total_dist/num_runs; aver_time = total_time/num_runs * 1000000.0; aver_time_g = total_time/total_g_test * 1000000.0; aver_time_simp = total_time/total_simplices * 1000000.0; aver_time_dp = total_time/total_dp * 1000000.0; aver_time_op = total_time/total_ops * 1000000.0; printf( "\n\tCompleted %ld runs using %d instances and %d vertex %s\n" "\t%.2lfus average time timed over %ld repeats, %d errors\n" "\taverage of %.1lf ops, average time per op = %.3lfus\n" "\t%.1lf%% zeros, average dist = %.2lf, %.1lfs cpu total\n" , num_runs, num_instances, num_pts, ( use_polyballs ? "polyballs" : "hulls" ), aver_time, num_repeats, num_errors, aver_ops, aver_time_op, aver_zeros*100, aver_dist, elapsed_time); exit( 0); } static void give_help_and_exit( void) { fprintf( stderr, "Usage: %s [optional arguments] number_of_runs [num_points]\n" " where the optional arguments are\n" "\t-h gives this message\n" "\t-H disables hill-climbing\n" "\t-q quiet mode\n" "\t-Q use random hulls (if Qhull linked)\n" "\t-R[value] use relative rotations\n" "\t-T[value] use relative translations\n" "\t-r change repeat count\n" "\t-i define number of instances\n" "\t-s define initial seed\n" , prog_name); exit( 1); } #ifdef QHULL /* Create a random ball-shaped object */ static void create_hull( int npts, REAL (*pts)[DIM], INDEX * rings) { int i, num_vertices, num_rings; double azimuth, sine_inclin, cosine_inclin, radius; double (* input_points)[DIM]; double input_array[MAX_POINTS][3]; if ( rings==0 ) /* then no hill-climbing */ input_points = pts; else input_points = input_array; /* Compute each of the required points independently. Each point is classified by a radius, and azimuth, and an inclination. The azimuth is like an angle of longitude on the Earth's surface, and is choosen from a uniform random variable ranging from 0 to 2pi. However as circles of latitude get shorter as we approach the poles, we wish to bias the angles of inclination (which measures angle out of the equatorial plane) towards the lower absolute values. Using an inverse sinusoidal function provides the correct bias. */ for ( i=0 ; i=0 ; thisr-- ) { thisn = n - thisr; /* Now factorise thisn, minimising the difference between the factors. The factors will be i and j, and the best difference so far will be m */ /* set i to floor( sqrt(thisn) ). Not efficient, but we don't expect to see thisn > 1000, i.e., i should be small */ for ( i=1 ; i*i<= thisn ; i++ ) ; i--; /* initialise k to floor( thisn/i ), and establish ik<=thisn */ k = thisn / i; /* keep trying for this value of i whilst k-i0 ) ? npts-2 : npts-1; /* compute vertex coordinates */ /* bottom vertex, if it exists */ if ( r==2 ) { pts[0][0] = pts[0][1] = 0; pts[0][2] = - radius; } /* normal vertices */ /* cache sines and cosines first */ sines[0] = 0; cosines[0] = 1; s = sin( 2*M_PI / p ); c = cos( 2*M_PI / p ); for ( i = 1 ; i < p ; i++ ) { sines[i] = cosines[i-1]*s + sines[i-1]*c; cosines[i] = cosines[i-1]*c - sines[i-1]*s; } nextv = firstv; for ( slice=1 ; slice<=q ; slice++ ) { z = radius * ((double) 2*slice - ( q+1) ) / (q+1); xyr = sqrt( radius * radius - z * z ); for ( i = 0 ; i < p ; i++ ) { pts[nextv][0] = xyr * cosines[i]; pts[nextv][1] = xyr * sines[i]; pts[nextv][2] = z; nextv++; } } /* top vertex, if it exists */ if ( r>0 ) { pts[npts-1][0] = pts[npts-1][1] = 0; pts[npts-1][2] = radius; } if ( rings==0 ) /* then no hill-climbing */ return; /* otherwise set up the edge lists. We process them in vertex number order. */ nextr = npts; if ( r==2 ) { /* then form an entry for the first, bottom vertex */ rings[0] = nextr; for ( i=0 ; i

=0 ) rings[nextr++] = lower; rings[nextr++] = ( ((v+p+1-firstv)/p)==slice ) ? v+1 : v+1-p; upper = v+p; if ( upper>lastv ) upper = ( r>0 ) ? npts-1 : -1; if ( upper>=0 ) rings[nextr++] = upper; rings[nextr++] = ( ((v+p-1-firstv)/p)==slice ) ? v-1 : v-1+p; rings[nextr++] = TERMINATOR; } if ( r>0 ) { /* then form an entry for the last, top vertex */ rings[npts-1] = nextr; for ( i=0 ; i

test_val ) { val = dot_product( pts[p], direction) - test_val + ERROR_EPSILON; if ( ( nbad==0 ) || ( val > worst ) ) worst = test_val; nbad++; } if ( err!=0 ) *err = ( nbad>0 ) ? worst : 0.0; return nbad; } #ifdef QHULL /* Compute how many planes of the hull of the given set of points the given witness point lies outside of. */ static int num_outside( int npts, double * err, const REAL (*pts)[DIM], REAL witness[DIM]) { double val, worst; int f, nf, nv, nbad; double faces[2*MAX_POINTS][4]; /* construct the hull */ nv = sac_qhull( npts, pts, (double (*)[3]) 0, (int *) 0, (int *) 0, &nf, faces); nbad = 0; for ( f=0 ; f ERROR_EPSILON ) { if ( ( nbad==0 ) || ( val > worst ) ) worst = val; nbad++; } } if ( err!=0 ) *err = ( nbad>0 ) ? worst : 0.0; return nbad; } #endif /* QHULL */ /* Vector dot-product function. We don't use the one defined in gjk.h in order to provide some measure of independence. */ double dot_product( REAL v1[3], REAL v2[3]) { int i; double val = 0; for ( i=0 ; i<3 ; i++ ) val += v1[i]*v2[i]; return val; } /* clocktime should return a double, indicating time passing as a number of seconds. We use the system function clock, that returns a tick value in microseconds on a Sparc, but in units of 18.2/s = CLK_TCK on a PC. */ #ifndef CLK_TCK #define CLK_TCK 1000000 #endif static double clocktime( void) { double num, den; num = (double) clock(); den = (double) CLK_TCK; return num / den; } /* random number generator. Generate a double between zero and one. */ #include /* RAND_MAX should be defined here */ #ifndef RAND_MAX #define RAND_MAX UINT_MAX #endif /* !defined( RAND_MAX) */ static double unit_random( unsigned long * seed) /* return a random number in range 0-1 */ { srand( (unsigned) * seed); * seed = (unsigned) rand(); return (double) ( * seed) / RAND_MAX; }