src/hcode/btech/map.los.c

Go to the documentation of this file.
00001 
00002 /*
00003  * $Id: map.los.c,v 1.1.1.1 2005/01/11 21:18:08 kstevens Exp $
00004  * 
00005  * Author: Thomas Wouters <thomas@xs4all.net>
00006  *
00007  * Copyright (c) 2002 Thomas Wouters
00008  *     All rights reserved
00009  *
00010  */
00011 
00012 #include "mech.h"
00013 #include "btmacros.h"
00014 #include "mech.sensor.h"
00015 #include "map.los.h"
00016 #include "p.mech.utils.h"
00017 
00018 #define INDEX2X(i)              ((i%(losmap.xsize))+(losmap.startx))
00019 #define INDEX2Y(i)              ((i/(losmap.xsize))+(losmap.starty))
00020 
00021 extern int TraceLOS(MAP * map, int ax, int ay, int bx, int by,
00022                                         lostrace_info ** result);
00023 
00024 static hexlosmap_info losmap;
00025 
00026 int LOSMap_Hex2Index(hexlosmap_info * losmap, int x, int y)
00027 {
00028         if(x < losmap->startx || x > losmap->startx + losmap->xsize ||
00029            y < losmap->starty || y > losmap->starty + losmap->ysize) {
00030                 SendError(tprintf("LOSMap request from out of bounds hex: %d,%d",
00031                                                   x, y));
00032                 return 0;
00033         }
00034         return ((y - losmap->starty) * losmap->xsize) + (x - losmap->startx);
00035 }
00036 
00037 static float MechHeight(MECH * mech)
00038 {
00039         switch (MechType(mech)) {
00040         case CLASS_MECH:
00041                 return 0.2 + !Fallen(mech);
00042         case CLASS_SPHEROID_DS:
00043                 return 4.2;
00044         case CLASS_DS:
00045                 return 2.2;
00046         case CLASS_MW:
00047         case CLASS_VEH_NAVAL:
00048                 return 0.01;
00049         }
00050         return 0.2;
00051 }
00052 
00053 static void set_hexlosinfo(int x, int y, int flag)
00054 {
00055         if(x < (losmap.startx) || x >= (losmap.startx) + (losmap.xsize) ||
00056            y < (losmap.starty) || y >= (losmap.starty) + (losmap.ysize)) {
00057                 return;
00058         }
00059         losmap.map[LOSMap_Hex2Index(&losmap, x, y)] |= (flag | MAPLOSHEX_SEEN);
00060 }
00061 
00062 static int hexlit(int x, int y)
00063 {
00064         if(x < (losmap.startx) || x >= (losmap.startx) + (losmap.xsize) ||
00065            y < (losmap.starty) || y >= (losmap.starty) + (losmap.ysize)) {
00066                 return 0;
00067         }
00068 
00069         return (losmap.map[LOSMap_Hex2Index(&losmap, x, y)] & MAPLOSHEX_LIT);
00070 }
00071 
00072 static void set_sliteinfo(int x, int y, int flag)
00073 {
00074         if(x < (losmap.startx) || x >= (losmap.startx) + (losmap.xsize) ||
00075            y < (losmap.starty) || y >= (losmap.starty) + (losmap.ysize)) {
00076                 return;
00077         }
00078         losmap.flags |= MAPLOS_FLAG_SLITE;
00079         losmap.map[LOSMap_Hex2Index(&losmap, x, y)] |= flag;
00080 }
00081 
00082 /* To efficiently set all hexes NOLOS if neither sensor supports seeing
00083  * terrain, and (in the future) to set all hexes LOSALL if either sensor
00084  * sees all terrain (e.g. 'sattelite downlink' sensor)
00085  */
00086 
00087 static void set_hexlosall(int flag)
00088 {
00089         memset(&(losmap.map), flag | MAPLOSHEX_SEEN, losmap.xsize * losmap.ysize);
00090 }
00091 
00092 /* The following functions are, effectively, STUBS. They should be
00093    replaced with functions in the sensor struct, instead of their
00094    functionality being copied all over the tree. */
00095 
00096 static int MechSeesThroughWoods(MECH * mech, MAP * map, int nwoods,
00097                                                                 int sensor)
00098 {
00099         int sn = MechSensor(mech)[sensor];
00100         int fake_losflag = nwoods * MECHLOSFLAG_WOOD;
00101         int res = sensors[sn].cansee_func(mech, NULL, map, 1, fake_losflag);
00102 
00103         return res;
00104 }
00105 
00106 static int MechSeesOverMountain(MECH * mech, MAP * map, int sensor)
00107 {
00108         int sn = MechSensor(mech)[sensor];
00109         int fake_losflag = MECHLOSFLAG_MNTN;
00110 
00111         return sensors[sn].cansee_func(mech, NULL, map, 1, fake_losflag);
00112 }
00113 
00114 static int MechSeesThroughWater(MECH * mech, MAP * map, int nwater,
00115                                                                 int sensor)
00116 {
00117         int sn = MechSensor(mech)[sensor];
00118         int fake_losflag = nwater * MECHLOSFLAG_WATER;
00119 
00120         return sensors[sn].cansee_func(mech, NULL, map, 1, fake_losflag);
00121 }
00122 
00123 static int MechSeesRange(MECH * mech, MAP * map, int x, int y, int z,
00124                                                  int sensor)
00125 {
00126         int sn = MechSensor(mech)[sensor];
00127         float fx, fy, range, maxvis = sensors[sn].maxvis;
00128 
00129         MapCoordToRealCoord(x, y, &fx, &fy);
00130         range = FindRange(MechFX(mech), MechFY(mech), MechFZ(mech),
00131                                           fx, fy, ZSCALE * z);
00132 
00133         /* XXX HACK: code duplication. this should be replaced with sensor
00134          * functions
00135          */
00136 
00137         if(sn < 2)                                      /* V or L sensors */
00138                 maxvis = map->mapvis;
00139         if(sn == 1 && map->maplight == 0)       /* L sensors in darkness */
00140                 maxvis *= 2;
00141 
00142         if(!sensors[sn].fullvision) {
00143                 int arc = InWeaponArc(mech, fx, fy);
00144 
00145                 if(!(arc & (FORWARDARC | TURRETARC))) {
00146                         if(MechSensor(mech)[0] == MechSensor(mech)[1])
00147                                 maxvis = (maxvis * 200 / 300);
00148                         else
00149                                 maxvis = 0;
00150                 }
00151         }
00152 
00153         if(sn == 0 && maxvis && range >= maxvis &&
00154            (losmap.flags & MAPLOS_FLAG_SLITE))
00155                 return -1;
00156 
00157         return range < maxvis;
00158 }
00159 
00160 static int MechSLitesRange(MECH * mech, int x, int y, int z)
00161 {
00162         float fx, fy, range;
00163         int arc, maxvis = 60;
00164 
00165         MapCoordToRealCoord(x, y, &fx, &fy);
00166         arc = InWeaponArc(mech, fx, fy);
00167         if(!(arc & (FORWARDARC | TURRETARC))) {
00168                 return 0;
00169         }
00170 
00171         range = FindRange(MechFX(mech), MechFY(mech), MechFZ(mech),
00172                                           fx, fy, ZSCALE * z);
00173         return range < maxvis;
00174 }
00175 
00176 static int MechSeesTerrain(MECH * mech, int sn)
00177 {
00178         return MechSensor(mech)[sn] < 4;
00179 }
00180 
00181 /* General idea: stateful LOS checking.
00182 
00183  * To minimize the number of lostracing we do, we calculate the losmap by
00184  * tracing los to all 'edge' hexes, and traversing that line of hexes
00185  * marking each hex as 'seen' and as 'los-or-not'. For each sensor on the
00186  * 'mech, we keep track of how steep the angle has to be in order for the
00187  * sensor to 'see' the terrain. The start angle is -20 (which should be low
00188  * enough for common purposes, even on jumping 'mechs) for sensors that can
00189  * see terrain, and 1000 for sensors that can't -- basically flagging the
00190  * whole line of sight as 'not seen' for that sensor.
00191 
00192  * In order to take wood-blockage into account, we also keep track of the
00193  * minimum 'block' angle. That is, if it is not equal to minangle,
00194  * blockangle is the angle below which 'woodcount' woods stand between the
00195  * current hex and the seeing 'mech. If we end up with a hex between
00196  * minangle and blockangle, we need to check if the sensor can see through
00197  * that many woods.
00198  
00199  * Blocking entirely, because of water- or EM-effects, is done by setting
00200  * the minangle and blockangle to 1000, a value high enough to block los to
00201  * all following hexes. To determine whether a sensors sees through a hex,
00202  * fake losflags are passed to the regular sensor functions... hacks, and
00203  * logic-duplication (the worst kind) but they work for now.
00204  
00205  * This is all proof-of-concept, based on Cord Awtry's ideas for
00206  * 'underground' maps. This should all be rewritten, together with the
00207  * sensor code, to have one general 'tracelos' function, which calls
00208  * callbacks defined on a state struct and stores its state info on that
00209  * same struct. That way, map-los, 'mech-los, searchlight-los and such can
00210  * all use the same routine, using different callbacks.
00211 
00212  * Known bugs / problems:
00213  * - It behaves awkwardly around water. It doesn't handle the transition as
00214  *   it should. This requires sufficient rewriting that I do not plan to do
00215  *   it before the whole sensor overhaul.
00216 
00217  * - It has too great a leniency in what terrain height you can see. You can
00218  *   sometimes see a level 1 hex behind a level 2 hex if you are in a 'mech,
00219  *   fallen on a level 1 hex. (you shouldn't.)
00220 
00221  */
00222 
00223 static void trace_slitelos(MAP * map, MECH * mech, int index,
00224                                                    float start_height)
00225 {
00226         float minangle = -20;
00227         lostrace_info *trace_coords;
00228         int trace_range = 0;
00229         int trace_x, trace_y, trace_height;
00230         float trace_a;
00231         int trace_coordnum = TraceLOS(map, MechX(mech), MechY(mech),
00232                                                                   INDEX2X(index), INDEX2Y(index),
00233                                                                   &trace_coords);
00234 
00235         for(; trace_range < trace_coordnum; trace_range++) {
00236                 trace_x = trace_coords[trace_range].x;
00237                 trace_y = trace_coords[trace_range].y;
00238 
00239                 trace_height = MAX(0, Elevation(map, trace_x, trace_y));
00240 
00241                 if(!MechSLitesRange(mech, trace_x, trace_y, trace_height))
00242                         return;
00243 
00244                 trace_a = (trace_height - start_height) / (trace_range + 1);
00245                 switch (GetTerrain(map, trace_x, trace_y)) {
00246                 case HEAVY_FOREST:
00247                 case LIGHT_FOREST:
00248                 case SMOKE:
00249                         trace_a += 2;
00250                 }
00251 
00252                 if(trace_a < minangle)
00253                         continue;
00254 
00255                 set_sliteinfo(trace_x, trace_y, MAPLOSHEX_LIT);
00256                 minangle = trace_a;
00257         }
00258 }
00259 
00260 static void litemark_callback(MAP * map, int x, int y)
00261 {
00262         set_sliteinfo(x, y, MAPLOSHEX_LIT);
00263 }
00264 
00265 static void litemark_map(MAP * map)
00266 {
00267         MECH *mech;
00268         int i;
00269         int index;
00270         mapobj *fire;
00271 
00272         for(fire = first_mapobj(map, TYPE_FIRE); fire; fire = next_mapobj(fire)) {
00273                 set_sliteinfo(fire->x, fire->y, MAPLOSHEX_LIT);
00274                 visit_neighbor_hexes(map, fire->x, fire->y, litemark_callback);
00275         }
00276 
00277         for(i = 0; i < map->first_free; i++) {
00278                 if(map->mechsOnMap[i] < 0)
00279                         continue;
00280                 mech = FindObjectsData(map->mechsOnMap[i]);
00281                 if(!mech)
00282                         continue;
00283 
00284                 if(Jellied(mech)) {
00285                         set_sliteinfo(MechX(mech), MechY(mech), MAPLOSHEX_LIT);
00286                         visit_neighbor_hexes(map, MechX(mech), MechY(mech),
00287                                                                  litemark_callback);
00288                 }
00289 
00290                 if(!MechLites(mech))
00291                         continue;
00292 
00293                 for(index = 0; index < losmap.xsize * losmap.ysize; index++) {
00294                         trace_slitelos(map, mech, index, MechZ(mech) + MechHeight(mech));
00295                 }
00296         }
00297 }
00298 
00299 #define DEF_MINA(mech, sn) (MechSeesTerrain(mech, sn) ? -20 : 1000)
00300 
00301 static void trace_maphexlos(MAP * map, MECH * mech, int index, int tracew,
00302                                                         float start_height)
00303 {
00304         int trace_water[MAX_SENSORS] = { tracew, tracew };
00305         float minangle[MAX_SENSORS] = { DEF_MINA(mech, 0), DEF_MINA(mech, 1) };
00306         float blockangle[MAX_SENSORS] = { DEF_MINA(mech, 0), DEF_MINA(mech, 1) };
00307         int woodcount[MAX_SENSORS] = { 0, 0 };
00308         int watercount[MAX_SENSORS] = { 0, 0 };
00309         lostrace_info *trace_coords;
00310         int trace_range = 0;
00311 
00312         int trace_coordnum = TraceLOS(map, MechX(mech), MechY(mech),
00313                                                                   INDEX2X(index), INDEX2Y(index),
00314                                                                   &trace_coords);
00315 
00316         for(; trace_range < trace_coordnum; trace_range++) {
00317                 int seestate;
00318                 int trace_x = trace_coords[trace_range].x;
00319                 int trace_y = trace_coords[trace_range].y;
00320                 int trace_height = Elevation(map, trace_x, trace_y);
00321 
00322                 float trace_a = (trace_height - start_height) / (trace_range + 1);
00323                 float trace_ba =
00324                         ((trace_height + 2 - start_height)) / (trace_range + 1);
00325                 int trace_terrain = GetRTerrain(map, trace_x, trace_y);
00326                 int nsensor, newwoods;
00327 
00328                 for(nsensor = 0; nsensor < NUMSENSORS(mech); nsensor++) {
00329 
00330                         /* If the current hex and all its terrain ('blockangle') lies
00331                          * below our minimum angle of sight, we won't see it at all;
00332                          * jump straight ahead to the water/mountain checks. This check
00333                          * is made first, because it is the cheapest check and the
00334                          * general mechanism to signal 'no more visibility on this line
00335                          * of sight' is to set trace_ba to an impossible angle.
00336                          */
00337 
00338                         if(trace_ba < minangle[nsensor]) {
00339                                 set_hexlosinfo(trace_x, trace_y, MAPLOSHEX_NOLOS);
00340                                 goto hexinfluence;
00341 
00342                         }
00343 
00344                         /* Then we check for range. */
00345                         seestate = MechSeesRange(mech, map, trace_x, trace_y,
00346                                                                          trace_height, nsensor);
00347 
00348                         if(seestate == 0) {
00349                                 set_hexlosinfo(trace_x, trace_y, MAPLOSHEX_NOLOS);
00350                                 minangle[nsensor] = blockangle[nsensor] = 1000;
00351                                 goto hexinfluence;
00352                         }
00353 
00354                         /* Count the number of woods. */
00355                         newwoods = 0;
00356                         switch (trace_terrain) {
00357                         case HEAVY_FOREST:
00358                                 newwoods++;
00359                                 /* FALLTHROUGH */
00360                         case LIGHT_FOREST:
00361                                 newwoods++;
00362                                 /* Because we aren't in water, we stop tracing below water */
00363                                 trace_water[nsensor] = 0;
00364                                 break;
00365                         }
00366 
00367                         if(!newwoods) {
00368 
00369                                 if(trace_a < minangle[nsensor] || (seestate < 0 &&
00370                                                                                                    !hexlit(trace_x,
00371                                                                                                                    trace_y))) {
00372                                         set_hexlosinfo(trace_x, trace_y, MAPLOSHEX_NOLOS);
00373                                 } else {
00374                                         set_hexlosinfo(trace_x, trace_y, MAPLOSHEX_SEE);
00375                                         blockangle[nsensor] = minangle[nsensor] = trace_a;
00376                                         woodcount[nsensor] = 0;
00377                                 }
00378                                 goto hexinfluence;
00379                         }
00380 
00381                         if(blockangle[nsensor] < trace_a) {
00382                                 minangle[nsensor] = trace_a;
00383                                 blockangle[nsensor] = trace_ba;
00384                                 woodcount[nsensor] = newwoods;
00385                         } else if(!MechSeesThroughWoods(mech, map, woodcount[nsensor] +
00386                                                                                         newwoods, nsensor)) {
00387                                 if(trace_ba >= blockangle[nsensor]) {
00388                                         minangle[nsensor] = blockangle[nsensor];
00389                                         blockangle[nsensor] = trace_ba;
00390                                         woodcount[nsensor] = newwoods;
00391                                 } else
00392                                         minangle[nsensor] = trace_ba;
00393                         } else {
00394                                 minangle[nsensor] = trace_a;
00395                                 woodcount[nsensor] += newwoods;
00396                         }
00397 
00398                         if(trace_terrain == WATER) {
00399                                 if(trace_water[nsensor])
00400                                         watercount[nsensor]++;
00401                                 if(!trace_water[nsensor] ||
00402                                    !MechSeesThroughWater(mech, map, watercount[nsensor],
00403                                                                                  nsensor)) {
00404                                         if(seestate < 0 && !hexlit(trace_x, trace_y))
00405                                                 set_hexlosinfo(trace_x, trace_y, MAPLOSHEX_NOLOS);
00406                                         else
00407                                                 set_hexlosinfo(trace_x, trace_y,
00408                                                                            MAPLOSHEX_SEETERRAIN);
00409                                 }
00410                         } else if(seestate < 0 && !hexlit(trace_x, trace_y))
00411                                 set_hexlosinfo(trace_x, trace_y, MAPLOSHEX_NOLOS);
00412                         else
00413                                 set_hexlosinfo(trace_x, trace_y, MAPLOSHEX_SEE);
00414 
00415                   hexinfluence:
00416                         if(trace_terrain == WATER &&
00417                            !MechSeesThroughWater(mech, map, 1, nsensor)) {
00418                                 set_hexlosinfo(trace_x, trace_y, MAPLOSHEX_NOLOS);
00419                                 minangle[nsensor] = blockangle[nsensor] = 1000;
00420                                 continue;
00421                         }
00422                         trace_water[nsensor] = 0;
00423                         if(trace_terrain == MOUNTAINS &&
00424                            !MechSeesOverMountain(mech, map, nsensor)) {
00425                                 set_hexlosinfo(trace_x, trace_y, MAPLOSHEX_NOLOS);
00426                                 minangle[nsensor] = blockangle[nsensor] = 1000;
00427                                 continue;
00428                         }
00429                 }
00430         }
00431 }
00432 
00433 hexlosmap_info *CalculateLOSMap(MAP * map, MECH * mech, int sx,
00434                                                                 int sy, int xsz, int ysz)
00435 {
00436         int index, underterrain, bothworlds;
00437         float start_height;
00438 
00439         /* Some safeguarding on size */
00440 
00441         if(xsz > MAPLOS_MAXX || ysz > MAPLOS_MAXY) {
00442                 SendError(tprintf("xsize (%d vs %d) or ysize (%d vs %d) "
00443                                                   "to CalculateLOSMap too large, for mech #%d",
00444                                                   xsz, MAPLOS_MAXX, ysz, MAPLOS_MAXY, mech->mynum));
00445                 return NULL;
00446         }
00447 
00448         losmap.startx = sx;
00449         losmap.starty = sy;
00450         losmap.xsize = xsz;
00451         losmap.ysize = ysz;
00452         losmap.flags = 0;
00453         memset(losmap.map, 0, xsz * ysz);
00454 
00455         underterrain = MechZ(mech) <= -1;
00456         if(IsWater(MechRTerrain(mech))
00457            && ((MechType(mech) == CLASS_MECH && MechZ(mech) == -1)
00458                    || ((WaterBeast(mech) || MechMove(mech) == MOVE_HOVER)
00459                            && MechZ(mech) == 0))) {
00460                 bothworlds = 1;
00461         } else
00462                 bothworlds = 0;
00463 
00464         start_height = MechZ(mech) + MechHeight(mech);
00465 
00466         if(MechCritStatus(mech) & CLAIRVOYANT) {
00467                 set_hexlosall(MAPLOSHEX_SEE);
00468                 return &losmap;
00469         }
00470 
00471         if(!MechSeesTerrain(mech, 0) && !MechSeesTerrain(mech, 1)) {
00472                 set_hexlosall(MAPLOSHEX_NOLOS);
00473                 return &losmap;
00474         }
00475 
00476         /* In order for slites to properly light terrain, we have to mark the
00477          * losmap with all lit hexes first. Which means going over all 'mechs on
00478          * the map and tag all hexes that they light.
00479          */
00480 
00481         litemark_map(map);
00482 
00483         /* In order to do the most efficient lostracing, we make losmaps by
00484          * first tracing from the 'mech hex to the upper Y-row, the lower Y-row,
00485          * the leftmost X-row, the rightmost X-row, and then all hexes starting
00486          * at the upper left corner to make sure we have seen all hexes. (It is
00487          * entirely possible for a hex not to be visited yet, even if we traced
00488          * to every other hex.)
00489          */
00490 
00491         for(index = 0; index < xsz; index++) {
00492                 if(losmap.map[index] & MAPLOSHEX_SEEN)
00493                         continue;
00494                 trace_maphexlos(map, mech, index, underterrain || bothworlds,
00495                                                 start_height);
00496         }
00497         for(index = (ysz - 1) * xsz; index < ysz * xsz; index++) {
00498                 if(losmap.map[index] & MAPLOSHEX_SEEN)
00499                         continue;
00500                 trace_maphexlos(map, mech, index, underterrain || bothworlds,
00501                                                 start_height);
00502         }
00503         for(index = xsz; index < ysz * xsz; index += xsz) {
00504                 if(losmap.map[index] & MAPLOSHEX_SEEN)
00505                         continue;
00506                 trace_maphexlos(map, mech, index, underterrain || bothworlds,
00507                                                 start_height);
00508         }
00509         for(index = 2 * xsz - 1; index < ysz * xsz; index += xsz) {
00510                 if(losmap.map[index] & MAPLOSHEX_SEEN)
00511                         continue;
00512                 trace_maphexlos(map, mech, index, underterrain || bothworlds,
00513                                                 start_height);
00514         }
00515         for(index = 0; index < xsz * ysz; index++) {
00516                 if(losmap.map[index] & MAPLOSHEX_SEEN)
00517                         continue;
00518                 trace_maphexlos(map, mech, index, underterrain || bothworlds,
00519                                                 start_height);
00520         }
00521 
00522         return &losmap;
00523 }

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