src/hcode/mech.lostracer.c

Go to the documentation of this file.
00001 
00002 /*
00003  *
00004  * Original author: unknown
00005  *
00006  * Copyright (c) 1996-2002 Markus Stenberg
00007  * Copyright (c) 1998-2002 Thomas Wouters 
00008  * Copyright (c) 2000-2002 Cord Awtry 
00009  *
00010  */
00011 
00012 /*************************************************************
00013 ****  LOS code  **********************************************
00014 **************************************************************
00015 
00016 What happens in the code now:
00017 (pretty close, anyway -- I haven't spent THAT much time studying it.
00018 
00019 Right now, the code just finds a line between the hexes and steps
00020 along it in units of 1 hex, checking each hex it enters.  This is,
00021 well, wrong.  It makes no allowance whatsoever that the LOS line might
00022 "clip" the edge of a hex, and will occasionally skip hexes:
00023  __    __    __
00024 /# \__/  \__/# \
00025 \__/  \__/  \__/  is possible, when it SHOULD be:
00026  __    __    __
00027 /# \__/  \__/# \  A 1 hex wide wall in the first case would be
00028 \__/  \__/  \__/  skipped entirely.
00029    \__/  \__/   
00030 
00031 Now my, (correct, but tougher) algorithm:
00032 
00033 OK, first FASA rules: "The LOS is checked by laying a straightedge
00034 ... from the center of the attackers's hex to the center of the
00035 target's hex.  Any hex that the straightedge crosses is in the LOS.
00036 If the straightedge passes directly between two hexes, the defender
00037 chooses which hex ist passes through." -- Compendium, p21
00038 
00039 It is fairly easy to convince yourself that the case of the line lying
00040 directly between two hexes is a well-defined special case: it happens
00041 only when the hexes lie exactly on a 30, 90, 150, 210, 270, or 330
00042 line.  This is easy to check for, so we'll put that check in as the
00043 first stage of the routine.  We'll assume from then on that the line
00044 is not a special case  (which is good -- the numbers explode to
00045 infinity for 90 degree lines).
00046 
00047 Now we iterate as the current muse code does.  We start at the hex at
00048 one end of the line, find the next one, and repeat until we get to the
00049 end.  Here is the algorithm for finding the "next" hex:
00050 
00051 First, we need only check the 6 adjacent hexes (obviously -- but the
00052 current code ignores this and sometimes returns "next" hexes that are
00053 not adjacent).
00054 
00055 In addition, an eligible hex muse be "on the line."  That is, the
00056 imaginary line drawn between the endpoints must pass through the hex.
00057 Figuring this out is the "meat" of this algorithm.  The best way to
00058 determine this (that I have discovered, anyway) is to find the
00059 (absolute, cartesian, floating point) distance between the center
00060 point of the hex in question and the line (i.e. the length of a line
00061 drawn from the center of hex, perpendicular to the LOS line). If this
00062 distance is less than the "effective radius" of the hex, then the line
00063 lies inside the hex.  By "effective radius" I mean the width of the
00064 hex along a line perpendicular to the LOS line.  This will vary
00065 (depending on the angle of the line) between 0.5 (if the hex is lying
00066 flat along the line -- i.e. a line pointing at 30, 90, 150, 210, 270,
00067 or 330 degrees) and (1/sqrt(3)) (if the line passes only through the
00068 point of the hex -- i.e. a line pointing at 0, 60, 120, 180, 240, or
00069 300)
00070 
00071   _Effective_Radius_
00072   _____        _____
00073  /     \ |    /     \   /     (Hope this diagram makes sense!
00074 /       \|   /   *_  \ /       the *'s are the line representing
00075 \       /|   \     * //        the effective radius)
00076  \_____/ |    \_____// 
00077     *****|          /
00078 
00079 (1/sqrt(3))     0.5
00080 
00081 For other angles, the effective radius varies as a cosine.  Since the
00082 whole thing is periodic with intervals of 60 deg, we can "modulo" it
00083 by subtracting 60 deg until we get the angle to within a -30 < angle <
00084 30 degrees region and then taking the cosine.
00085 
00086 Hoof!  That's the worst of it.  Now, from among the adjacent, eligible
00087 hexes, the "next" one is the one that is closest "along the line" to
00088 the one we are in. (But is still closer to the endpoint than the
00089 current hex.  We have to check here to make sure we don't hop back
00090 into the hex we came from!)
00091 
00092 Now performance issues:
00093 
00094 The main calculations here are the distance-to-line determination, and
00095 the effective radius calculation.  Each of these must be performed 5
00096 times (6 adjacent hexes, but we don't have to check the one we came
00097 from).  Distance-to-line is best found by using a matrix to rotate the
00098 coordinates to a frame where the line is horizontal and taking the
00099 difference in y coords between the hex and any point on the line (one
00100 of the endpoints will do).  The angle to rotate is -(slope of the LOS
00101 line), which is -atan(y1-y2/x1-x2).  The atan function need only be
00102 called once, since the slope of the line is unchanging.  That leaves
00103 us with the matrix:   |cos(u) -sin(u)|
00104                       |sin(u)  cos(u)|   where u=-atan(y1-y2/x1-x2)
00105 
00106 This contains four more trigonometric functions, but again each need
00107 only be called once (and there are really only two anyway, sin(u) and
00108 cos(u)).
00109 
00110 The effective radius involves a cos() function.  Again, though, since
00111 the slope of the line is unchanging, the effective radius of the hex
00112 is a constant throughout the iterations, and need only be calculated
00113 once.
00114 
00115 So now we have one time costs:
00116 
00117 *Calculate matrix coefficients:  1 atan(), 1 cos(), 1 sin()
00118                                  2 fp subtractions, 1 fp divide.
00119 
00120 *Find effective radius:          1 cos(),
00121                                 <6 fp subtractions (to get -30<u<30)
00122 
00123 And per-hex-travelled costs:
00124 
00125 *Test 5 hexes if on line:       10 fp mults, 5 fp additions
00126                                  5 fp compares
00127 
00128 *Test for closest forward hex:  <5 fp compares
00129 
00130 This is actually, I would think, not much more than the current LOS
00131 code, which makes many calls to RealCoordToMapCoord() (a costly
00132 function) and is generally messy, with lots of repeated mults by
00133 constants such as ZSCALE (which would optimize out, if we did, but we
00134 don't :)
00135 
00136 **********************************************************************
00137 
00138 OK, enough of this.  Let's get on to the code.  
00139 */
00140 
00141 #include <stdio.h>
00142 #include <stdlib.h>
00143 #include <math.h>
00144 #include "mech.h"
00145 #include "btmacros.h"
00146 
00147 #define DEG60 1.0471976                 /* In radians, of course ;) */
00148 #define DEG30 0.5235988
00149 #define ROOT3 1.7320508
00150 #define TRACESCALEMAP 1
00151 
00152 typedef enum { N, NE, SE, S, SW, NW } hexdir;
00153 
00154 #define Store_Hex(store_x,store_y) \
00155 do { found_coords[found_count].x = store_x; \
00156      found_coords[found_count++].y = store_y; } \
00157 while (0)
00158 
00159 #define Store_BestOf(x1,y1,x2,y2) \
00160 if ( ((x1) < 0) || ((x1) >= map->map_width) || \
00161      ((y1) < 0) || ((y1) >= map->map_height) ) \
00162     { Store_Hex((x2), (y2)); } \
00163 else if ( ((x2) < 0) || ((x2) >= map->map_width) || \
00164           ((y2) < 0) || ((y2) >= map->map_height) ) \
00165        { Store_Hex((x1), (y1)); }\
00166 else if ((Elevation(map,(x1),(y1))) > (Elevation(map, (x2), (y2)))) \
00167     { Store_Hex((x1),(y1)); } \
00168 else { Store_Hex((x2),(y2)); }
00169 
00170 #define LOS_MapCoordToRealCoord(x,y,cx,cy) \
00171 do { cx = (float)x * ROOT3 / 2 * TRACESCALEMAP; \
00172   cy = ((float)y - 0.5*(x%2))*TRACESCALEMAP;} while (0)
00173 
00174 static void GetAdjHex(int currx, int curry, int nexthex, int *x, int *y)
00175 {
00176         switch (nexthex) {
00177         case N:
00178                 *x = currx;
00179                 *y = curry - 1;
00180                 break;
00181         case NE:
00182                 *x = currx + 1;
00183                 *y = curry - (currx % 2);
00184                 break;
00185         case SE:
00186                 *x = currx + 1;
00187                 *y = curry - (currx % 2) + 1;
00188                 break;
00189         case S:
00190                 *x = currx;
00191                 *y = curry + 1;
00192                 break;
00193         case SW:
00194                 *x = currx - 1;
00195                 *y = curry - (currx % 2) + 1;
00196                 break;
00197         case NW:
00198                 *x = currx - 1;
00199                 *y = curry - (currx % 2);
00200                 break;
00201         default:                                        /* Mostly there to satisfy gcc */
00202                 fprintf(stderr, "XXX ARGH: TraceLos doesn't know where to go!\n");
00203                 *x = currx + 1;                 /* Just grab some values that aren't x/y */
00204                 *y = curry + 1;                 /* so we can break out of the loop */
00205         }
00206 }
00207 
00208 int TraceLOS(MAP * map, int ax, int ay, int bx, int by,
00209                          lostrace_info ** result)
00210 {
00211 
00212         int i;                                          /* Generic counter */
00213         float acx, acy, bcx, bcy;       /* endpoints CARTESIAN coords */
00214         float currcx, currcy;           /* current hex CARTESIAN coords */
00215         int currx, curry;                       /* current hex being worked from */
00216         int nextx, nexty;                       /* x & y coords of next hex */
00217         int bestx = 0, besty = 0;       /* best found so far */
00218         int lastx, lasty;                       /* Holding place for final intervening hex */
00219         int xmul, ymul;                         /* Used in 30/150/210/330 special case */
00220         hexdir nexthex;                         /* potential next hex being examined */
00221         float nextcx, nextcy;           /* Next hex's CARTESIAN coords */
00222         float slope;                            /* slope of line */
00223         float uangle;                           /* angle of line (in STD CARTESIAN FORM!) */
00224         float sinu;                                     /* sin of -uangle */
00225         float cosu;                                     /* cos of same */
00226         float liney;                            /* TRANSFORMED y coord of the line */
00227         float tempangle;                        /* temporary uangle used for effrad calc */
00228         float effrad;                           /* effective radius of hex */
00229         float currdist;                         /* distance along line of current hex */
00230         float nextdist;                         /* distance along the line of potential hex */
00231         float bestdist;                         /* "best" (not furthest) distance tried */
00232         float enddist;                          /* distance along at end of line */
00233         static lostrace_info found_coords[4000];
00234         int found_count = 0;
00235 
00236         /* Before doing anything, let's check for special circumstances, this */
00237         /* means vertical lines (which work using the current code, but depend */
00238         /* on atan returning proper vaules for atan(-Inf) -- which is probably */
00239         /* slow and may break on non-ANSI systems; and also lines at 30, 90 */
00240         /* etc.. degrees which contain 'ties' between hexes.  FASA rules here */
00241         /* say that the 'best' hex for the defender (the one that breaks LOS, */
00242         /* or gives a greater BTH penalty) should be used. */
00243 
00244         /* THE base case */
00245         if((ax == bx) && (ay == by)) {
00246                 Store_Hex(bx, by);
00247                 *result = found_coords;
00248                 return found_count;
00249         }
00250         /* Is it vertical? */
00251         if(ax == bx) {
00252                 if(ay > by)
00253                         for(i = ay - 1; i > by; i--)
00254                                 Store_Hex(ax, i);
00255                 else
00256                         for(i = ay + 1; i < by; i++)
00257                                 Store_Hex(ax, i);
00258                 Store_Hex(bx, by);
00259                 *result = found_coords;
00260                 return found_count;
00261         }
00262 
00263         /* Does it lie along a 90 degree 'tie' direction? */
00264         /* IF(even-number-of-cols apart AND same-y-coord) */
00265         if(!((bx - ax) % 2) && ay == by) {
00266                 currx = ax;
00267                 i = bx > ax ? 1 : -1;
00268                 while (currx != bx) {
00269                         /* Do best of (currx+1,by-currx%2)   */
00270                         /*         or (currx+1,by-currx%2+1) */
00271                         Store_BestOf(currx + 1 * i, by - currx % 2, currx + 1 * i,
00272                                                  by - currx % 2 + 1);
00273 
00274                         if(currx != bx)
00275                                 Store_Hex(currx + 2 * i, by);
00276 
00277                         currx += 2 * i;
00278                 }
00279 
00280 /*      Store_Hex(bx,by); */
00281                 *result = found_coords;
00282                 return found_count;
00283         }
00284 
00285         /* Does it lie along a 30, 150, 210, 330 degree 'tie' direction? */
00286         /* This expression is messy, but it just means that a hex is along */
00287         /* 30 degrees if the y distance is (the integer part of) 3/2 */
00288         /* times the x distance, plus 1 if the x difference is odd, AND */
00289         /* the original hex was in an even column and heads in the +y  */
00290         /* direction, or odd and goes -y.  It works, try it :) */
00291         if(abs(by - ay) ==
00292            (3 * abs(bx - ax) / 2) + abs((bx - ax) % 2) * abs((by <
00293                                                                                                                   ay) ? (ax %
00294                                                                                                                                  2) : (1 -
00295                                                                                                                                            ax %
00296                                                                                                                                            2))) {
00297 
00298                 /* First get the x and y 'multipliers' -- either 1 or -1 */
00299                 /* they determine the direction of the movement */
00300                 if(bx > ax)
00301                         if(by > ay) {
00302                                 xmul = 1;
00303                                 ymul = 1;
00304                         } else {
00305                                 xmul = 1;
00306                                 ymul = -1;
00307                 } else if(by > ay) {
00308                         xmul = -1;
00309                         ymul = 1;
00310                 } else {
00311                         xmul = -1;
00312                         ymul = -1;
00313                 }
00314 
00315                 currx = ax;
00316                 curry = ay;
00317                 while ((currx != bx) && (curry != by)) {
00318 
00319                         Store_BestOf(currx, curry + ymul, currx + xmul,
00320                                                  ymul ==
00321                                                  1 ? curry + 1 - currx % 2 : curry - currx % 2);
00322 
00323                         curry += (ymul == 1) ? (2 - currx % 2) : (-1 - currx % 2);
00324                         currx += xmul;
00325 
00326                         if(currx == bx && curry == by)
00327                                 continue;
00328 
00329                         Store_Hex(currx, curry);
00330                 }
00331                 Store_Hex(currx, curry);
00332                 *result = found_coords;
00333                 return found_count;
00334         }
00335 
00336 /****************************************************************************/
00337 
00338 /****  OK, now we know it's not a special case ******************************/
00339 
00340 /****************************************************************************/
00341 
00342 /* First get the necessary constants set up */
00343 
00344         LOS_MapCoordToRealCoord(ax, ay, acx, acy);
00345         LOS_MapCoordToRealCoord(bx, by, bcx, bcy);
00346 
00347         slope = (float) (acy - bcy) / (float) (acx - bcx);
00348 
00349         uangle = -atan(slope);
00350 
00351         sinu = sin(uangle);
00352         cosu = cos(uangle);
00353 
00354         liney = acx * sinu + acy * cosu;        /* we could just as */
00355         /* correctly use bx, by */
00356 
00357         enddist = bcx * cosu - bcy * sinu;
00358 
00359         tempangle = fabs(uangle);
00360         while (tempangle > DEG60)
00361                 tempangle -= DEG60;
00362         effrad = cos(tempangle - DEG30) * TRACESCALEMAP / ROOT3;
00363 
00364         /*****************************************************************/
00365 
00368         /*****************************************************************/
00369 
00370         currx = ax;
00371         curry = ay;
00372         LOS_MapCoordToRealCoord(currx, curry, currcx, currcy);
00373         currdist = currcx * cosu - currcy * sinu;
00374         bestdist = enddist;                     /* set this to the endpoint, the worst */
00375         /* possible point to go to  */
00376 
00377         while (!(currx == bx && curry == by)) {
00378 
00379                 for(nexthex = N; nexthex <= NW; nexthex++) {
00380 
00381                         GetAdjHex(currx, curry, nexthex, &nextx, &nexty);
00382                         LOS_MapCoordToRealCoord(nextx, nexty, nextcx, nextcy);
00383 
00384                         /* Is it on the line? */
00385                         if(fabs((nextcx * sinu + nextcy * cosu) - liney) > effrad)
00386                                 continue;
00387 
00388                         /* Where is it?  Find the transformed x coord */
00389                         nextdist = nextcx * cosu - nextcy * sinu;
00390 
00391                         /* is it forward of the current hex? */
00392                         if(fabs(enddist - nextdist) > fabs(enddist - currdist))
00393                                 continue;
00394 
00395                         /* Is it better than what we have? */
00396                         if(fabs(enddist - nextdist) >= fabs(enddist - bestdist)) {
00397                                 bestdist = nextdist;
00398                                 bestx = nextx;
00399                                 besty = nexty;
00400                         }
00401                 }
00402 
00403                 if(bestx == bx && besty == by) {        /* If we've found the last hex, record */
00404                         lastx = currx;          /* the current hex as the last */
00405                         lasty = curry;          /* intervening hex, save currx/y, */
00406                         currx = bestx;          /* and jump to the end of the loop */
00407                         curry = besty;
00408                         continue;
00409                 }
00410 
00411                 /* ********************************************************* */
00412                 /* HERE is where you put the test code for intervening hexes */
00413                 /* ********************************************************* */
00414                 Store_Hex(bestx, besty);
00415                 /* ********************************************************* */
00416 
00417                 currx = bestx;                  /* Reset the curr hex for the next iteration */
00418                 curry = besty;
00419                 currdist = bestdist;
00420                 bestdist = enddist;             /* reset to worst possible value */
00421 
00422         }
00423 
00424         /* **************************************************** */
00425         /* HERE is where you put the test code for the LAST hex */
00426         /* **************************************************** */
00427         Store_Hex(currx, curry);
00428         /* ********************************************************* */
00429         *result = found_coords;
00430         return found_count;
00431 }

Generated on Mon May 28 04:25:24 2007 for BattletechMUX by  doxygen 1.4.7