diff options
author | truelight <truelight@openttd.org> | 2004-08-09 17:04:08 +0000 |
---|---|---|
committer | truelight <truelight@openttd.org> | 2004-08-09 17:04:08 +0000 |
commit | efaeb275f78e18d594d9ee8ff04eccd2dc59512c (patch) | |
tree | bc8e1f56d77706d14d048cb2d99e53291930b520 /pathfind.c | |
download | openttd-efaeb275f78e18d594d9ee8ff04eccd2dc59512c.tar.xz |
(svn r1) Import of revision 975 of old (crashed) SVN
Diffstat (limited to 'pathfind.c')
-rw-r--r-- | pathfind.c | 722 |
1 files changed, 722 insertions, 0 deletions
diff --git a/pathfind.c b/pathfind.c new file mode 100644 index 000000000..02c2b1de7 --- /dev/null +++ b/pathfind.c @@ -0,0 +1,722 @@ +#include "stdafx.h" +#include "ttd.h" +#include "pathfind.h" + +// remember which tiles we have already visited so we don't visit them again. +static bool TPFSetTileBit(TrackPathFinder *tpf, uint tile, int dir) +{ + uint hash, val, offs; + TrackPathFinderLink *link, *new_link; + uint bits = 1 << dir; + + if (tpf->disable_tile_hash) + return true; + + hash = PATHFIND_HASH_TILE(tile); + + val = tpf->hash_head[hash]; + + if (val == 0) { + /* unused hash entry, set the appropriate bit in it and return true + * to indicate that a bit was set. */ + tpf->hash_head[hash] = bits; + tpf->hash_tile[hash] = (TileIndex)tile; + return true; + } else if (!(val & 0x8000)) { + /* single tile */ + + if ( (TileIndex)tile == tpf->hash_tile[hash] ) { + /* found another bit for the same tile, + * check if this bit is already set, if so, return false */ + if (val & bits) + return false; + + /* otherwise set the bit and return true to indicate that the bit + * was set */ + tpf->hash_head[hash] = val | bits; + return true; + } else { + /* two tiles with the same hash, need to make a link */ + + /* allocate a link. if out of links, handle this by returning + * that a tile was already visisted. */ + if (tpf->num_links_left == 0) + return false; + tpf->num_links_left--; + link = tpf->new_link++; + + /* move the data that was previously in the hash_??? variables + * to the link struct, and let the hash variables point to the link */ + link->tile = tpf->hash_tile[hash]; + tpf->hash_tile[hash] = PATHFIND_GET_LINK_OFFS(tpf, link); + + link->flags = tpf->hash_head[hash]; + tpf->hash_head[hash] = 0xFFFF; /* multi link */ + + link->next = 0xFFFF; + } + } else { + /* a linked list of many tiles, + * find the one corresponding to the tile, if it exists. + * otherwise make a new link */ + + offs = tpf->hash_tile[hash]; + do { + link = PATHFIND_GET_LINK_PTR(tpf, offs); + if ( (TileIndex)tile == link->tile) { + /* found the tile in the link list, + * check if the bit was alrady set, if so return false to indicate that the + * bit was already set */ + if (link->flags & bits) + return false; + link->flags |= bits; + return true; + } + } while ((offs=link->next) != 0xFFFF); + } + + /* get here if we need to add a new link to link, + * first, allocate a new link, in the same way as before */ + if (tpf->num_links_left == 0) + return false; + tpf->num_links_left--; + new_link = tpf->new_link++; + + /* then fill the link with the new info, and establish a ptr from the old + * link to the new one */ + new_link->tile = (TileIndex)tile; + new_link->flags = bits; + new_link->next = 0xFFFF; + + link->next = PATHFIND_GET_LINK_OFFS(tpf, new_link); + return true; +} + +static const byte _bits_mask[4] = { + 0x19, + 0x16, + 0x25, + 0x2A, +}; + +static const byte _tpf_new_direction[14] = { + 0,1,0,1,2,1, 0,0, + 2,3,3,2,3,0, +}; + +static const byte _tpf_prev_direction[14] = { + 0,1,1,0,1,2, 0,0, + 2,3,2,3,0,3, +}; + + +static const byte _otherdir_mask[4] = { + 0x10, + 0, + 0x5, + 0x2A, +}; + +#ifdef DEBUG_TILE_PUSH +extern void dbg_push_tile(uint tile, int track); +extern void dbg_pop_tile(); +#endif + +void TPFMode2(TrackPathFinder *tpf, uint tile, int direction) +{ + uint bits; + int i; + RememberData rd; + + // This addition will sometimes overflow by a single tile. + // The use of TILE_MASK here makes sure that we still point at a valid + // tile, and then this tile will be in the sentinel row/col, so GetTileTrackStatus will fail. + tile = TILE_MASK(tile + _tileoffs_by_dir[direction]); + + if (++tpf->rd.cur_length > 50) + return; + + bits = GetTileTrackStatus(tile, tpf->tracktype); + bits = (byte)((bits | (bits >> 8)) & _bits_mask[direction]); + if (bits == 0) + return; + + assert(GET_TILE_X(tile) != 255 && GET_TILE_Y(tile) != 255); + + if ( (bits & (bits - 1)) == 0 ) { + /* only one direction */ + i = 0; + while (!(bits&1)) + i++, bits>>=1; + + rd = tpf->rd; + goto continue_here; + } + /* several directions */ + i=0; + do { + if (!(bits & 1)) continue; + rd = tpf->rd; + + // Change direction 4 times only + if ((byte)i != tpf->rd.pft_var6) { + if(++tpf->rd.depth > 4) { + tpf->rd = rd; + return; + } + tpf->rd.pft_var6 = (byte)i; + } + +continue_here:; + tpf->the_dir = HASBIT(_otherdir_mask[direction],i) ? (i+8) : i; + +#ifdef DEBUG_TILE_PUSH + dbg_push_tile(tile, tpf->the_dir); +#endif + if (!tpf->enum_proc(tile, tpf->userdata, tpf->the_dir, tpf->rd.cur_length, NULL)) { + TPFMode2(tpf, tile, _tpf_new_direction[tpf->the_dir]); + } +#ifdef DEBUG_TILE_PUSH + dbg_pop_tile(); +#endif + + tpf->rd = rd; + } while (++i, bits>>=1); + +} + +static const int8 _get_tunlen_inc[5] = { -16, 0, 16, 0, -16 }; + +FindLengthOfTunnelResult FindLengthOfTunnel(uint tile, int direction, byte type) +{ + FindLengthOfTunnelResult flotr; + int x,y; + byte z; + + flotr.length = 0; + + x = GET_TILE_X(tile) * 16; + y = GET_TILE_Y(tile) * 16; + + z = GetSlopeZ(x+8, y+8); + + for(;;) { + flotr.length++; + + x += _get_tunlen_inc[direction]; + y += _get_tunlen_inc[direction+1]; + + tile = TILE_FROM_XY(x,y); + + if (IS_TILETYPE(tile, MP_TUNNELBRIDGE) && + (_map5[tile] & 0xF0) == 0 && + ((_map5[tile]>>1)&6) == type && + ((_map5[tile] & 3)^2) == direction && + GetSlopeZ(x+8, y+8) == z) + break; + } + + flotr.tile = tile; + return flotr; +} + +static const uint16 _tpfmode1_and[4] = { 0x1009, 0x16, 0x520, 0x2A00 }; + +static uint SkipToEndOfTunnel(TrackPathFinder *tpf, uint tile, int direction) { + FindLengthOfTunnelResult flotr; + TPFSetTileBit(tpf, tile, 14); + flotr = FindLengthOfTunnel(tile, direction, tpf->tracktype); + tpf->rd.cur_length += flotr.length; + TPFSetTileBit(tpf, flotr.tile, 14); + return flotr.tile; +} + +const byte _ffb_64[128] = { +0,0,1,0,2,0,1,0, +3,0,1,0,2,0,1,0, +4,0,1,0,2,0,1,0, +3,0,1,0,2,0,1,0, +5,0,1,0,2,0,1,0, +3,0,1,0,2,0,1,0, +4,0,1,0,2,0,1,0, +3,0,1,0,2,0,1,0, + +0,0,0,2,0,4,4,6, +0,8,8,10,8,12,12,14, +0,16,16,18,16,20,20,22, +16,24,24,26,24,28,28,30, +0,32,32,34,32,36,36,38, +32,40,40,42,40,44,44,46, +32,48,48,50,48,52,52,54, +48,56,56,58,56,60,60,62, +}; + +void TPFMode1(TrackPathFinder *tpf, uint tile, int direction) +{ + uint bits; + int i; + RememberData rd; + uint tile_org = tile; + + if (IS_TILETYPE(tile, MP_TUNNELBRIDGE) && (_map5[tile] & 0xF0)==0) { + if ((_map5[tile] & 3) != direction || ((_map5[tile]>>1)&6) != tpf->tracktype) + return; + tile = SkipToEndOfTunnel(tpf, tile, direction); + } + tile += _tileoffs_by_dir[direction]; + tpf->rd.cur_length++; + + bits = GetTileTrackStatus(tile, tpf->tracktype); + + if ((byte)bits != tpf->var2) { + bits &= _tpfmode1_and[direction]; + bits = bits | (bits>>8); + } + bits &= 0xBF; + + if (bits != 0) { + if (!tpf->disable_tile_hash || (tpf->rd.cur_length <= 64 && (KILL_FIRST_BIT(bits) == 0 || ++tpf->rd.depth <= 7))) { + do { + i = FIND_FIRST_BIT(bits); + bits = KILL_FIRST_BIT(bits); + + tpf->the_dir = (_otherdir_mask[direction] & (byte)(1 << i)) ? (i+8) : i; + rd = tpf->rd; + +#ifdef DEBUG_TILE_PUSH + dbg_push_tile(tile, tpf->the_dir); +#endif + if (TPFSetTileBit(tpf, tile, tpf->the_dir) && + !tpf->enum_proc(tile, tpf->userdata, tpf->the_dir, tpf->rd.cur_length, &tpf->rd.pft_var6) ) { + TPFMode1(tpf, tile, _tpf_new_direction[tpf->the_dir]); + } +#ifdef DEBUG_TILE_PUSH + dbg_pop_tile(); +#endif + tpf->rd = rd; + } while (bits != 0); + } + } + + /* the next is only used when signals are checked. + * seems to go in 2 directions simultaneously */ + + /* if i can get rid of this, tail end recursion can be used to minimize + * stack space dramatically. */ + if (tpf->hasbit_13) + return; + + tile = tile_org; + direction ^= 2; + + bits = GetTileTrackStatus(tile, tpf->tracktype); + bits |= (bits >> 8); + + if ( (byte)bits != tpf->var2) { + bits &= _bits_mask[direction]; + } + + bits &= 0xBF; + if (bits == 0) + return; + + do { + i = FIND_FIRST_BIT(bits); + bits = KILL_FIRST_BIT(bits); + + tpf->the_dir = (_otherdir_mask[direction] & (byte)(1 << i)) ? (i+8) : i; + rd = tpf->rd; + if (TPFSetTileBit(tpf, tile, tpf->the_dir) && + !tpf->enum_proc(tile, tpf->userdata, tpf->the_dir, tpf->rd.cur_length, &tpf->rd.pft_var6) ) { + TPFMode1(tpf, tile, _tpf_new_direction[tpf->the_dir]); + } + tpf->rd = rd; + } while (bits != 0); +} + +void FollowTrack(uint tile, uint16 flags, byte direction, TPFEnumProc *enum_proc, TPFAfterProc *after_proc, void *data) +{ + TrackPathFinder *tpf = alloca(sizeof(TrackPathFinder)); + + assert(direction < 4); + + /* initialize path finder variables */ + tpf->userdata = data; + tpf->enum_proc = enum_proc; + tpf->new_link = tpf->links; + tpf->num_links_left = 0x400; + + tpf->rd.cur_length = 0; + tpf->rd.depth = 0; + tpf->rd.pft_var6 = 0; + + tpf->var2 = HASBIT(flags, 15) ? 0x43 : 0xFF; /* 0x8000 */ + + tpf->disable_tile_hash = HASBIT(flags, 12) != 0; /* 0x1000 */ + tpf->hasbit_13 = HASBIT(flags, 13) != 0; /* 0x2000 */ + + + tpf->tracktype = (byte)flags; + + if (HASBIT(flags, 11)) { + tpf->rd.pft_var6 = 0xFF; + tpf->enum_proc(tile, data, 0, 0, 0); + TPFMode2(tpf, tile, direction); + } else { + /* clear the hash_heads */ + memset(tpf->hash_head, 0, sizeof(tpf->hash_head)); + TPFMode1(tpf, tile, direction); + } + + if (after_proc != NULL) + after_proc(tpf); +} + +typedef struct { + TileIndex tile; + uint16 cur_length; + byte track; + byte depth; + byte state; + byte first_track; +} StackedItem; + +static const byte _new_dir[6][4] = { +{0,0xff,2,0xff,}, +{0xff,1,0xff,3,}, +{0xff,0,3,0xff,}, +{1,0xff,0xff,2,}, +{3,2,0xff,0xff,}, +{0xff,0xff,1,0,}, +}; + +static const byte _new_track[6][4] = { +{0,0xff,8,0xff,}, +{0xff,1,0xff,9,}, +{0xff,2,10,0xff,}, +{3,0xff,0xff,11,}, +{12,4,0xff,0xff,}, +{0xff,0xff,5,13,}, +}; + +typedef struct HashLink { + TileIndex tile; + uint16 typelength; + uint16 next; +} HashLink; + +typedef struct { + TPFEnumProc *enum_proc; + void *userdata; + + byte tracktype; + uint maxlength; + + HashLink *new_link; + uint num_links_left; + + int nstack; + StackedItem stack[256]; // priority queue of stacked items + + uint16 hash_head[0x400]; // hash heads. 0 means unused. 0xFFC0 = length, 0x3F = type + TileIndex hash_tile[0x400]; // tiles. or links. + + HashLink links[0x400]; // hash links + +} NewTrackPathFinder; +#define NTP_GET_LINK_OFFS(tpf, link) ((byte*)(link) - (byte*)tpf->links) +#define NTP_GET_LINK_PTR(tpf, link_offs) (HashLink*)((byte*)tpf->links + (link_offs)) + +#define ARR(i) tpf->stack[(i)-1] + +// called after a new element was added in the queue at the last index. +// move it down to the proper position +static void INLINE HeapifyUp(NewTrackPathFinder *tpf) +{ + StackedItem si; + int i = ++tpf->nstack; + + while (i != 1 && ARR(i).cur_length < ARR(i>>1).cur_length) { + // the child element is larger than the parent item. + // swap the child item and the parent item. + si = ARR(i); ARR(i) = ARR(i>>1); ARR(i>>1) = si; + i>>=1; + } +} + +// called after the element 0 was eaten. fill it with a new element +static void INLINE HeapifyDown(NewTrackPathFinder *tpf) +{ + StackedItem si; + int i = 1, j; + int n = --tpf->nstack; + + if (n == 0) return; // heap is empty so nothing to do? + + // copy the last item to index 0. we use it as base for heapify. + ARR(1) = ARR(n+1); + + while ((j=i*2) <= n) { + // figure out which is smaller of the children. + if (j != n && ARR(j).cur_length > ARR(j+1).cur_length) + j++; // right item is smaller + + assert(i <= n && j <= n); + if (ARR(i).cur_length <= ARR(j).cur_length) + break; // base elem smaller than smallest, done! + + // swap parent with the child + si = ARR(i); ARR(i) = ARR(j); ARR(j) = si; + i = j; + } +} + +// mark a tile as visited and store the length of the path. +// if we already had a better path to this tile, return false. +// otherwise return true. +static bool NtpVisit(NewTrackPathFinder *tpf, uint tile, uint dir, uint length) +{ + uint hash,head; + HashLink *link, *new_link; + + assert(length < 1024); + + hash = PATHFIND_HASH_TILE(tile); + + // never visited before? + if ((head=tpf->hash_head[hash]) == 0) { + tpf->hash_tile[hash] = tile; + tpf->hash_head[hash] = dir | (length << 2); + return true; + } + + if (head != 0xffff) { + if ( (TileIndex)tile == tpf->hash_tile[hash] && (head & 0x3) == dir ) { + + // longer length + if (length >= (head >> 2)) return false; + + tpf->hash_head[hash] = dir | (length << 2); + return true; + } + // two tiles with the same hash, need to make a link + // allocate a link. if out of links, handle this by returning + // that a tile was already visisted. + if (tpf->num_links_left == 0) + return false; + tpf->num_links_left--; + link = tpf->new_link++; + + /* move the data that was previously in the hash_??? variables + * to the link struct, and let the hash variables point to the link */ + link->tile = tpf->hash_tile[hash]; + tpf->hash_tile[hash] = NTP_GET_LINK_OFFS(tpf, link); + + link->typelength = tpf->hash_head[hash]; + tpf->hash_head[hash] = 0xFFFF; /* multi link */ + link->next = 0xFFFF; + } else { + // a linked list of many tiles, + // find the one corresponding to the tile, if it exists. + // otherwise make a new link + + uint offs = tpf->hash_tile[hash]; + do { + link = NTP_GET_LINK_PTR(tpf, offs); + if ( (TileIndex)tile == link->tile && (uint)(link->typelength & 0x3) == dir) { + if (length >= (uint)(link->typelength >> 2)) return false; + link->typelength = dir | (length << 2); + return true; + } + } while ((offs=link->next) != 0xFFFF); + } + + /* get here if we need to add a new link to link, + * first, allocate a new link, in the same way as before */ + if (tpf->num_links_left == 0) + return false; + tpf->num_links_left--; + new_link = tpf->new_link++; + + /* then fill the link with the new info, and establish a ptr from the old + * link to the new one */ + new_link->tile = (TileIndex)tile; + new_link->typelength = dir | (length << 2); + new_link->next = 0xFFFF; + + link->next = NTP_GET_LINK_OFFS(tpf, new_link); + return true; +} + +static bool NtpCheck(NewTrackPathFinder *tpf, uint tile, uint dir, uint length) +{ + uint hash,head,offs; + HashLink *link; + + hash = PATHFIND_HASH_TILE(tile); + head=tpf->hash_head[hash]; + assert(head); + + if (head != 0xffff) { + assert( tpf->hash_tile[hash] == tile && (head & 3) == dir); + assert( (head >> 2) <= length); + return length == (head >> 2); + } + + // else it's a linked list of many tiles + offs = tpf->hash_tile[hash]; + for(;;) { + link = NTP_GET_LINK_PTR(tpf, offs); + if ( (TileIndex)tile == link->tile && (uint)(link->typelength & 0x3) == dir) { + assert( (uint)(link->typelength >> 2) <= length); + return length == (uint)(link->typelength >> 2); + } + offs = link->next; + assert(offs != 0xffff); + } +} + + +// new more optimized pathfinder for trains... +void NTPEnum(NewTrackPathFinder *tpf, uint tile, uint direction) +{ + uint bits, tile_org; + int i; + StackedItem si; + FindLengthOfTunnelResult flotr; + + si.cur_length = 0; + si.depth = 0; + si.state = 0; + +restart: + if (IS_TILETYPE(tile, MP_TUNNELBRIDGE) && (_map5[tile] & 0xF0)==0) { + if ( (uint)(_map5[tile] & 3) != direction || ((_map5[tile]>>1)&6) != tpf->tracktype) + goto popnext; + flotr = FindLengthOfTunnel(tile, direction, tpf->tracktype); + si.cur_length += flotr.length; + tile = flotr.tile; + } + + // remember the start tile so we know if we're in an inf loop. + tile_org = tile; + + for(;;) { + tile += _tileoffs_by_dir[direction]; + + // too long search length? bail out. + if (++si.cur_length >= tpf->maxlength) + goto popnext; + + // not a regular rail tile? + if (!IS_TILETYPE(tile, MP_RAILWAY) || (bits = _map5[tile]) & 0xC0) { + bits = GetTileTrackStatus(tile, 0) & _tpfmode1_and[direction]; + bits = (bits | (bits >> 8)) & 0x3F; + break; + } + + // regular rail tile, determine the tracks that are actually reachable. + bits &= _bits_mask[direction]; + if (bits == 0) goto popnext; // no tracks there? stop searching. + + // complex tile?, let the generic handler handle that.. + if (KILL_FIRST_BIT(bits) != 0) break; + + // don't bother calling the callback when we have regular tracks only. + // it's usually not needed anyway. that will speed up things. + direction = _new_dir[FIND_FIRST_BIT(bits)][direction]; + assert(direction != 0xFF); + if (tile == tile_org) goto popnext; // detect infinite loop.. + } + + if (!bits) goto popnext; + + // if only one reachable track, use tail recursion optimization. + if (KILL_FIRST_BIT(bits) == 0) { + i = _new_track[FIND_FIRST_BIT(bits)][direction]; + // call the callback + if (tpf->enum_proc(tile, tpf->userdata, i, si.cur_length, &si.state)) + goto popnext; // we should stop searching in this direction. + + // we should continue searching. determine new direction. + direction = _tpf_new_direction[i]; + goto restart; // use tail recursion optimization. + } + + // too high recursion depth.. bail out.. + if (si.depth >= _patches.pf_maxdepth) + goto popnext; + + si.depth++; // increase recursion depth. + + // see if this tile was already visited..? + if (NtpVisit(tpf, tile, direction, si.cur_length)) { + // push all possible alternatives + si.tile = tile; + do { + si.track = _new_track[FIND_FIRST_BIT(bits)][direction]; + + // out of stack items, bail out? + if (tpf->nstack >= lengthof(tpf->stack)) + break; + tpf->stack[tpf->nstack] = si; + HeapifyUp(tpf); + } while ((bits = KILL_FIRST_BIT(bits)) != 0); + + // if this is the first recursion step, we need to fill the first_track member. + // so the code outside knows which path is better. + // also randomize the order in which we search through them. + if (si.depth == 1) { + uint32 r = Random(); + assert(tpf->nstack == 2 || tpf->nstack == 3); + if (r&1) swap_byte(&tpf->stack[0].track, &tpf->stack[1].track); + if (tpf->nstack != 2) { + byte t = tpf->stack[2].track; + if (r&2) swap_byte(&tpf->stack[0].track, &t); + if (r&4) swap_byte(&tpf->stack[1].track, &t); + tpf->stack[2].first_track = tpf->stack[2].track = t; + } + tpf->stack[0].first_track = tpf->stack[0].track; + tpf->stack[1].first_track = tpf->stack[1].track; + } + } + +popnext: + // where to continue. + do { + if (tpf->nstack == 0) return; // nothing left? + si = tpf->stack[0]; + tile = si.tile; + HeapifyDown(tpf); + } while ( + !NtpCheck(tpf, tile, _tpf_prev_direction[si.track], si.cur_length) || // already have better path to that tile? + tpf->enum_proc(tile, tpf->userdata, si.track, si.cur_length, &si.state) + ); + + direction = _tpf_new_direction[si.track]; + goto restart; +} + + +// new pathfinder for trains. better and faster. +void NewTrainPathfind(uint tile, byte direction, TPFEnumProc *enum_proc, void *data, byte *cache) +{ + if (!_patches.new_pathfinding) { + FollowTrack(tile, 0x3000, direction, enum_proc, NULL, data); + } else { + NewTrackPathFinder *tpf; + + tpf = alloca(sizeof(NewTrackPathFinder)); + tpf->userdata = data; + tpf->enum_proc = enum_proc; + tpf->tracktype = 0; + tpf->maxlength = _patches.pf_maxlength; + tpf->nstack = 0; + tpf->new_link = tpf->links; + tpf->num_links_left = 0x400; + memset(tpf->hash_head, 0, sizeof(tpf->hash_head)); + + NTPEnum(tpf, tile, direction); + } +} + |