From bdd4f306f480de66253713a25e449745e886478f Mon Sep 17 00:00:00 2001 From: James McCulloch Date: Sun, 11 Jul 2010 18:31:02 +0000 Subject: [PATCH] * added GAE path finder (SearchEngine and friends) * some small changes to Unit, UnitType, Vec2<> & Map needed for the new path finder * compiles, but not hooked up yet, old PathFinder still in use --- mk/windoze/Glest.suo | Bin 309248 -> 345600 bytes mk/windoze/glest_game.vcproj | 68 +- mk/windoze/shared_lib.vcproj | 8 + source/glest_game/ai/annotated_map.cpp | 382 ++++++++ source/glest_game/ai/annotated_map.h | 192 ++++ source/glest_game/ai/cartographer.cpp | 255 ++++++ source/glest_game/ai/cartographer.h | 218 +++++ source/glest_game/ai/cluster_map.cpp | 678 ++++++++++++++ source/glest_game/ai/cluster_map.h | 319 +++++++ source/glest_game/ai/influence_map.h | 255 ++++++ source/glest_game/ai/node_map.cpp | 291 ++++++ source/glest_game/ai/node_map.h | 185 ++++ source/glest_game/ai/node_pool.cpp | 133 +++ source/glest_game/ai/node_pool.h | 194 ++++ source/glest_game/ai/path_finder.cpp | 306 +++++++ source/glest_game/ai/route_planner.cpp | 843 ++++++++++++++++++ source/glest_game/ai/route_planner.h | 261 ++++++ source/glest_game/ai/search_engine.h | 287 ++++++ source/glest_game/ai/search_functions.inl | 155 ++++ source/glest_game/facilities/pos_iterator.cpp | 57 ++ source/glest_game/facilities/pos_iterator.h | 120 +++ source/glest_game/game/game_constants.h | 3 + source/glest_game/type_instances/unit.cpp | 55 +- source/glest_game/type_instances/unit.h | 55 +- source/glest_game/types/unit_type.cpp | 8 + source/glest_game/types/unit_type.h | 8 +- source/glest_game/world/map.cpp | 33 +- source/glest_game/world/map.h | 2 +- source/glest_game/world/unit_updater.cpp | 2 +- source/glest_game/world/world.h | 6 + source/shared_lib/include/graphics/vec.h | 5 + .../shared_lib/include/platform/win32/types.h | 36 + source/shared_lib/include/util/heap.h | 119 +++ source/shared_lib/include/util/line.h | 83 ++ .../shared_lib/sources/platform/win32/glob.c | 1 + 35 files changed, 5547 insertions(+), 76 deletions(-) create mode 100644 source/glest_game/ai/annotated_map.cpp create mode 100644 source/glest_game/ai/annotated_map.h create mode 100644 source/glest_game/ai/cartographer.cpp create mode 100644 source/glest_game/ai/cartographer.h create mode 100644 source/glest_game/ai/cluster_map.cpp create mode 100644 source/glest_game/ai/cluster_map.h create mode 100644 source/glest_game/ai/influence_map.h create mode 100644 source/glest_game/ai/node_map.cpp create mode 100644 source/glest_game/ai/node_map.h create mode 100644 source/glest_game/ai/node_pool.cpp create mode 100644 source/glest_game/ai/node_pool.h create mode 100644 source/glest_game/ai/path_finder.cpp create mode 100644 source/glest_game/ai/route_planner.cpp create mode 100644 source/glest_game/ai/route_planner.h create mode 100644 source/glest_game/ai/search_engine.h create mode 100644 source/glest_game/ai/search_functions.inl create mode 100644 source/glest_game/facilities/pos_iterator.cpp create mode 100644 source/glest_game/facilities/pos_iterator.h create mode 100644 source/shared_lib/include/platform/win32/types.h create mode 100644 source/shared_lib/include/util/heap.h create mode 100644 source/shared_lib/include/util/line.h diff --git a/mk/windoze/Glest.suo b/mk/windoze/Glest.suo index 012db673e761e82cb05a263ed05f756e8463cd83..4988714895905b62ef2ff023068668734a3ff899 100755 GIT binary patch delta 48191 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 delta 5336 zcma)Ae_T~%nm^C`o{LCG_aZQ0fHy)S1p-t=G`!-Cam)Z2yplR&IHJTZ??ar*Zo3-ukd|I2-YPX$ja=*{H2e|s_k2(0g z&-Z-Kd*1Ut&-?3n4YwzUFC=cuOZHfnMSyd8ElYv;I;M|4(6EO(!uCP6Qe7K(-&>WH z`8H`h>;00eB-z1Fiud3|3sJy0KfzH(zpe{fvdnLl&?4*TZr zX|a!bJxO>Yg?+Ri@IB{`|1_@8&uQX+#f7*SkEsNC1iV~ZmZ|*9J&~1 zUqBni$LKIbTFew5p5Ys)u={=ilD-R}LXupKQ)>94^a z2Ks?t14jUPPp(d_Q&A@MRFrl)igQrwyizIYL7$v+pZw9a{|5Up@Qr&f<>azaRxH1$ zb)D_7@MY!i@!Pt~x4kxCuK(p?Zl0z`@tIfbgr3J!^n*VBAX6uCO$_;LbkR~S+MyPE z8bQ;Hue+L;oS*_Oyr}#-*|N;Ek`-igOAE~~fySK9aT;}x)#p5B!~9izrj_EkKT(G~ z6H6>>Bv*CmVpCubQ^mxmT2wLL_8mRJ1S=o9_7Uq5>tU+1 zc#)Rz%AcrI-dBuf7Cxcw;&2-!QoHdL%`m+iYdlS8Te~6u&9mfPAm1v1Ed=tdBK!q` zeCVatB&hj7;hpQ~ys4wey1|`bPFw z>ks+hEDG_t`)L$4a$lmJcU8QpL|$Z5b9i>7Kob4XwCo=_Vwu&9Kg%k{uvyR3pP?qs zTTe;q35yGNs|xNdr7F6QuRp5TyjNGkfrUDjXUQ)+CG=PIS0-=Up<=EKurG*CE5RTvv}V9gv?{s(2JM$2pCz** zmcvt2I{S|*n^*5qVGbOJ7YDbfkZGQn#RX^W1YY}v${aor*BGAjrJ7>S)u(pOp~ty- zwyNaw4R{A*rHF z-6<19TW`hio_?rNV?=$JsAmnmk~8p1p(!{UkFF17=y^krXAOmw*JhLV$`MR6$d$f2&M+oxp>c;_dL+nKwrc8N#CZMV zDmzkS?igK8_0rykeH$@D3vZ(FG8h9~^F54&t{XjhjFq#FM|V?HhdHyAjdxLzsi|z> z_>(HNa|?y(1g|=wUZBl<05-%A^>BEr>SSi40(~&>Ua~D{a$*6_l2m6vP3 zR!h0>lu9+Nb1Te&syWig1U`0<8iu|;dvG8)J|wiE^ROGpejtO}x(;8$^5k5(_?4_< z95A4vh@*{nwGpsoKpZf`)v!}xiMK4Vx4PQ3u(tuJ!0oOk+0Te7{Go%dz&bg3(l)d+#gvpS5{OfEBb^#`y6XjZkU}$@Dn_JOmtdHJRUU%G{TQ=q*>23T5A247}rN z^71KHd)L*Z+!^2&;C)wxD=&*1uowFZaqBM<4!nn6#2kJ`%sf9K&qP00oeJXCmlTi`TY0PNw z70~>yHUfN{tL3?xggpmbmbrXa6Gz5_-w!(>N-GC{9on=g?N(WPw?V9o605+a?`op7 z5V$Ol^P;r*;6H@6AWB;V?sQ+2b~pGdDA(v}vNk^lF5%zX=8DokFMzLtebLpVKjrkf z0rsb^Cf>aPz8SVRN;?LAFSOr8X}<&C4()i9b`tzyXz#dMn^f3`o1ekT5f%TO@V^K0 zD)>LT+8FQ=nD26VVWpfj(C2DNu9gQa6I^D{XjjVy&jOcj%5^mvgyWReW_dyKqr_rx z>DTeDCJihD7muezX#wy~Xw###S>O*syDdr!fmcAQceQ!ocY({c+NjX~;z<^84@B8R zo7{rp$x84t*n3?~JYI2?CeQDORtme;EhpYTgNnsI>}sTN4o6cF;W|N1rg9!F;HATL zBDX$@JAYj~pNiF9+U+zdVVzF!OoH$AIHyhG-Y#TaUH^`wOdwv{=Ja>vIEOR$2<@jX z$??tpyGyxhgmzNc$N1SCeGeZkR8!SVi@S1k>7lXq!Zo}_dG;Q9+MXFhliZN*wd;tc zL~g!#-X0NSo~>%BCdR)!3=6Mh&$#{{O^#We diff --git a/mk/windoze/glest_game.vcproj b/mk/windoze/glest_game.vcproj index 3f06fedc..07b2e5a8 100755 --- a/mk/windoze/glest_game.vcproj +++ b/mk/windoze/glest_game.vcproj @@ -327,7 +327,11 @@ > + + @@ -361,7 +365,47 @@ > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mk/windoze/shared_lib.vcproj b/mk/windoze/shared_lib.vcproj index 4fb2ed39..6f508990 100755 --- a/mk/windoze/shared_lib.vcproj +++ b/mk/windoze/shared_lib.vcproj @@ -884,10 +884,18 @@ RelativePath="..\..\source\shared_lib\include\util\factory.h" > + + + + diff --git a/source/glest_game/ai/annotated_map.cpp b/source/glest_game/ai/annotated_map.cpp new file mode 100644 index 00000000..d7a70f8e --- /dev/null +++ b/source/glest_game/ai/annotated_map.cpp @@ -0,0 +1,382 @@ +// ============================================================== +// This file is part of The Glest Advanced Engine +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ==============================================================// +// File: annotated_map.cpp +// +// Annotated Map, for use in pathfinding. +// + +#include "annotated_map.h" +#include "world.h" +#include "pos_iterator.h" +/* +#include "path_finder.h" +#include "cartographer.h" +#include "cluster_map.h" +*/ +#include "profiler.h" + +namespace Glest { namespace Game { + +/** Construct AnnotatedMap object, 'g_map' must be constructed and loaded + * @param master true if this is the master map, false for a foggy map (default true) + */ +AnnotatedMap::AnnotatedMap(World *world) + : cellMap(NULL) { + //_PROFILE_FUNCTION(); + assert(world && world->getMap()); + cellMap = world->getMap(); + width = cellMap->getW(); + height = cellMap->getH(); + metrics.init(width, height); + for (int f = fLand; f < fieldCount; ++f) { + maxClearance[f] = 0; + } + const int &ftCount = world->getTechTree()->getTypeCount(); + const FactionType *factionType; + for (int i = 0; i < ftCount; ++i) { + factionType = world->getTechTree()->getType(i); + const UnitType *unitType; + for (int j = 0; j < factionType->getUnitTypeCount(); ++j) { + unitType = factionType->getUnitType(j); + if (unitType->isMobile()) { + if (unitType->getSize() > maxClearance[unitType->getField()]) { + maxClearance[unitType->getField()] = unitType->getSize(); + } + } + } + } + initMapMetrics(); +} + +AnnotatedMap::~AnnotatedMap() { +} + +/** Initialise clearance data for a master map. */ +void AnnotatedMap::initMapMetrics() { +// _PROFILE_FUNCTION(); + Util::ReverseRectIterator iter(Vec2i(0,0), Vec2i(width - 1, height - 1)); + while (iter.more()) { + computeClearances(iter.next()); + } +} + +#define LOG_CLUSTER_DIRTYING(x) {} +//#define LOG_CLUSTER_DIRTYING(x) {cout << x;} + +struct MudFlinger { + //ClusterMap *cm; + + inline void setDirty(const Vec2i &pos) { + /* + Vec2i cluster = ClusterMap::cellToCluster(pos); + cm->setClusterDirty(cluster); + LOG_CLUSTER_DIRTYING( "MapMetrics changed @ pos = " << pos << endl ) + LOG_CLUSTER_DIRTYING( cout << "\tCluster = " << cluster << " dirty\n" ) + int ymod = pos.y % GameConstants::clusterSize; + if (ymod == 0) { + cm->setNorthBorderDirty(cluster); + LOG_CLUSTER_DIRTYING( "\tNorth border dirty\n" ) + } else if (ymod == GameConstants::clusterSize - 1) { + cm->setNorthBorderDirty(Vec2i(cluster.x, cluster.y + 1)); + LOG_CLUSTER_DIRTYING( "\tSouth border dirty\n" ) + } + int xmod = pos.x % GameConstants::clusterSize; + if (xmod == 0) { + cm->setWestBorderDirty(cluster); + LOG_CLUSTER_DIRTYING( "\tWest border dirty\n" ) + } else if (xmod == GameConstants::clusterSize - 1) { + cm->setWestBorderDirty(Vec2i(cluster.x + 1, cluster.y)); + LOG_CLUSTER_DIRTYING( "\tEast border dirty\n" ) + } + */ + } +} mudFlinger; + +/** Update clearance data, when an obstactle is placed or removed from the map * + * @param pos the cell co-ordinates of the obstacle added/removed * + * @param size the size of the obstacle */ +void AnnotatedMap::updateMapMetrics(const Vec2i &pos, const int size) { + assert(cellMap->isInside(pos)); + assert(cellMap->isInside(pos.x + size - 1, pos.y + size - 1)); + //_PROFILE_FUNCTION(); + + // need to throw mud on the ClusterMap +// mudFlinger.cm = World::getInstance().getCartographer()->getClusterMap(); + + // 1. re-evaluate the cells occupied (or formerly occupied) + for (int i = size - 1; i >= 0 ; --i) { + for (int j = size - 1; j >= 0; --j) { + Vec2i occPos = pos; + occPos.x += i; occPos.y += j; + CellMetrics old = metrics[occPos]; + computeClearances(occPos); + if (old != metrics[occPos]) { + mudFlinger.setDirty(occPos); + } + } + } + // 2. propegate changes... + cascadingUpdate(pos, size); +} + +/** Perform a 'cascading update' of clearance metrics having just changed clearances * + * @param pos the cell co-ordinates of the obstacle added/removed * + * @param size the size of the obstacle * + * @param field the field to update (temporary), or fieldCount to update all fields (permanent) */ +void AnnotatedMap::cascadingUpdate(const Vec2i &pos, const int size, const Field field) { + list *leftList, *aboveList, leftList1, leftList2, aboveList1, aboveList2; + leftList = &leftList1; + aboveList = &aboveList1; + // both the left and above lists need to be sorted, bigger values first (right->left, bottom->top) + for (int i = size - 1; i >= 0; --i) { + // Check if positions are on map, (the '+i' components are along the sides of the building/object, + // so we assume they are ok). If so, list them + if (pos.x-1 >= 0) { + leftList->push_back(Vec2i(pos.x-1,pos.y+i)); + } + if (pos.y-1 >= 0) { + aboveList->push_back(Vec2i(pos.x+i,pos.y-1)); + } + } + // the cell to the nothwest... + Vec2i *corner = NULL; + Vec2i cornerHolder(pos.x - 1, pos.y - 1); + if (pos.x - 1 >= 0 && pos.y - 1 >= 0) { + corner = &cornerHolder; + } + while (!leftList->empty() || !aboveList->empty() || corner) { + // the left and above lists for the next loop iteration + list *newLeftList, *newAboveList; + newLeftList = leftList == &leftList1 ? &leftList2 : &leftList1; + newAboveList = aboveList == &aboveList1 ? &aboveList2 : &aboveList1; + if (!leftList->empty()) { + for (VLIt it = leftList->begin(); it != leftList->end(); ++it) { + if (updateCell(*it, field) && it->x - 1 >= 0) { + // if we updated and there is a cell to the left, add it to + newLeftList->push_back(Vec2i(it->x-1,it->y)); // the new left list + } + } + } + if (!aboveList->empty()) { + for (VLIt it = aboveList->begin(); it != aboveList->end(); ++it) { + if (updateCell(*it, field) && it->y - 1 >= 0) { + newAboveList->push_back(Vec2i(it->x,it->y-1)); + } + } + } + if (corner) { + // Deal with the corner... + if (updateCell(*corner, field)) { + int x = corner->x, y = corner->y; + if (x - 1 >= 0) { + newLeftList->push_back(Vec2i(x-1,y)); + if (y - 1 >= 0) { + *corner = Vec2i(x-1,y-1); + } else { + corner = NULL; + } + } else { + corner = NULL; + } + if (y - 1 >= 0) { + newAboveList->push_back(Vec2i(x,y-1)); + } + } else { + corner = NULL; + } + } + leftList->clear(); + leftList = newLeftList; + aboveList->clear(); + aboveList = newAboveList; + }// end while +} + +/** cascadingUpdate() helper */ +bool AnnotatedMap::updateCell(const Vec2i &pos, const Field field) { + if (field == fieldCount) { // permanent annotation, update all + //if (eMap && !eMap->isExplored(Map::toTileCoords(pos))) { + // if not master map, stop if cells are unexplored + // return false; + //} + CellMetrics old = metrics[pos]; + computeClearances(pos); + if (old != metrics[pos]) { + mudFlinger.setDirty(pos); + return true; + } + } else { // local annotation, only check field, store original clearances + uint32 old = metrics[pos].get(field); + if (old) { + computeClearance(pos, field); + if (old > metrics[pos].get(field)) { + if (localAnnt.find(pos) == localAnnt.end()) { + localAnnt[pos] = old; // was original clearance + } + return true; + } + } + } + return false; +} + +/** Compute clearances (all fields) for a location + * @param pos the cell co-ordinates + */ +void AnnotatedMap::computeClearances(const Vec2i &pos) { + assert(cellMap->isInside(pos)); + if (pos.x >= cellMap->getW() - 2 || pos.y >= cellMap->getH() - 2) { + metrics[pos].setAll(0); + return; + } + Cell *cell = cellMap->getCell(pos); + // is there a building here, or an object on the tile ?? + bool surfaceBlocked = (cell->getUnit(fLand) && !cell->getUnit(fLand)->getType()->isMobile()) + || !cellMap->getSurfaceCell(cellMap->toSurfCoords(pos))->isFree(); + // Walkable + if (surfaceBlocked || cellMap->getDeepSubmerged(cell)) + metrics[pos].set(fLand, 0); + else + computeClearance(pos, fLand); +/* + // Any Water + if ( surfaceBlocked || !cell->isSubmerged() || !maxClearance[Field::ANY_WATER] ) + metrics[pos].set(Field::ANY_WATER, 0); + else + computeClearance(pos, Field::ANY_WATER); + + // Deep Water + if ( surfaceBlocked || !cell->isDeepSubmerged() || !maxClearance[Field::DEEP_WATER] ) + metrics[pos].set(Field::DEEP_WATER, 0); + else + computeClearance(pos, Field::DEEP_WATER); + + // Amphibious: + if ( surfaceBlocked || !maxClearance[Field::AMPHIBIOUS] ) + metrics[pos].set(Field::AMPHIBIOUS, 0); + else + computeClearance(pos, Field::AMPHIBIOUS); +*/ + // Air + computeClearance(pos, fAir); +} + +/** Computes clearance based on metrics to the south and east. + * Does NOT check if this cell is an obstactle, assumes metrics of cells to + * the south, south-east & east are correct + * @param pos the co-ordinates of the cell + * @param field the field to update + */ +uint32 AnnotatedMap::computeClearance( const Vec2i &pos, Field f ) { + uint32 clear = metrics[Vec2i(pos.x, pos.y + 1)].get(f); + if ( clear > metrics[Vec2i(pos.x + 1, pos.y + 1)].get(f) ) { + clear = metrics[Vec2i(pos.x + 1, pos.y + 1)].get(f); + } + if ( clear > metrics[Vec2i(pos.x + 1, pos.y)].get(f) ) { + clear = metrics[Vec2i(pos.x + 1, pos.y)].get(f); + } + clear ++; + if ( clear > maxClearance[f] ) { + clear = maxClearance[f]; + } + metrics[pos].set(f, clear); + return clear; +} +/** Perform 'local annotations', annotate the map to treat other mobile units in + * the vincinity of unit as obstacles + * @param unit the unit about to perform a search + * @param field the field that the unit is about to search in + */ +void AnnotatedMap::annotateLocal(const Unit *unit) { + //_PROFILE_FUNCTION(); + const Field &field = unit->getCurrField(); + const Vec2i &pos = unit->getPos(); + const int &size = unit->getType()->getSize(); + assert(cellMap->isInside(pos)); + assert(cellMap->isInside(pos.x + size - 1, pos.y + size - 1)); + const int dist = 3; + set annotate; + + // find surrounding units + for ( int y = pos.y - dist; y < pos.y + size + dist; ++y ) { + for ( int x = pos.x - dist; x < pos.x + size + dist; ++x ) { + if ( cellMap->isInside(x, y) && metrics[Vec2i(x, y)].get(field) ) { // clearance != 0 + Unit *u = cellMap->getCell(x, y)->getUnit(field); + if ( u && u != unit ) { // the set will take care of duplicates for us + annotate.insert(u); + } + } + } + } + // annotate map for each nearby unit + for ( set::iterator it = annotate.begin(); it != annotate.end(); ++it ) { + annotateUnit(*it, field); + } +} + +/** Temporarily annotate the map, to treat unit as an obstacle + * @param unit the unit to treat as an obstacle + * @param field the field to annotate + */ +void AnnotatedMap::annotateUnit(const Unit *unit, const Field field) { + const int size = unit->getType()->getSize(); + const Vec2i &pos = unit->getPos(); + assert(cellMap->isInside(pos)); + assert(cellMap->isInside(pos.x + size - 1, pos.y + size - 1)); + // first, re-evaluate the cells occupied + for (int i = size - 1; i >= 0 ; --i) { + for (int j = size - 1; j >= 0; --j) { + Vec2i occPos = pos; + occPos.x += i; occPos.y += j; + if (!unit->getType()->hasCellMap() || unit->getType()->getCellMapCell(i, j, unit->getModelFacing())) { + if (localAnnt.find(occPos) == localAnnt.end()) { + localAnnt[occPos] = metrics[occPos].get(field); + } + metrics[occPos].set(field, 0); + } else { + uint32 old = metrics[occPos].get(field); + computeClearance(occPos, field); + if (old != metrics[occPos].get(field) && localAnnt.find(occPos) == localAnnt.end()) { + localAnnt[occPos] = old; + } + } + } + } + // propegate changes to left and above + cascadingUpdate(pos, size, field); +} + +/** Clear all local annotations * + * @param field the field annotations were applied to */ +void AnnotatedMap::clearLocalAnnotations(const Unit *unit) { + //_PROFILE_FUNCTION(); + const Field &field = unit->getCurrField(); + for ( map::iterator it = localAnnt.begin(); it != localAnnt.end(); ++ it ) { + assert(it->second <= maxClearance[field]); + assert(cellMap->isInside(it->first)); + metrics[it->first].set(field, it->second); + } + localAnnt.clear(); +} + +#if _GAE_DEBUG_EDITION_ + +list >* AnnotatedMap::getLocalAnnotations() { + list > *ret = new list >(); + for ( map::iterator it = localAnnt.begin(); it != localAnnt.end(); ++ it ) + ret->push_back(pair(it->first,metrics[it->first].get(fLand))); + return ret; +} + +#endif + +}} + diff --git a/source/glest_game/ai/annotated_map.h b/source/glest_game/ai/annotated_map.h new file mode 100644 index 00000000..3a6dec73 --- /dev/null +++ b/source/glest_game/ai/annotated_map.h @@ -0,0 +1,192 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== +// +// File: annotated_map.h +// +// Annotated Map, for use in pathfinding. +// + +#ifndef _GLEST_GAME_ANNOTATED_MAP_H_ +#define _GLEST_GAME_ANNOTATED_MAP_H_ + +#include "vec.h" +#include "map.h" + +namespace Glest { namespace Game { + +using Shared::Platform::int64; + +typedef list::iterator VLIt; +typedef list::const_iterator VLConIt; +typedef list::reverse_iterator VLRevIt; +typedef list::const_reverse_iterator VLConRevIt; + +class World; + +// ===================================================== +// struct CellMetrics +// ===================================================== +/** Stores clearance metrics for a cell. + * 3 bits are used per field, allowing for a maximum moveable unit size of 7. + * The left over bit is used for per team 'foggy' maps, the dirty bit is set when + * an obstacle has been placed or removed in an area the team cannot currently see. + * The team's annotated map is thus not updated until that cell becomes visible again. + */ +struct CellMetrics { + CellMetrics() { memset(this, 0, sizeof(*this)); } + + uint16 get(const Field field) const { /**< get metrics for field */ + switch (field) { + case fLand: return field0; + case fAir: return field1; + //case Field::ANY_WATER: return field2; + //case Field::DEEP_WATER: return field3; + //case Field::AMPHIBIOUS: return field4; + default: throw runtime_error("Unknown Field passed to CellMetrics::get()"); + } + } + + void set(const Field field, uint16 val) { /**< set metrics for field */ + switch (field) { + case fLand: field0 = val; return; + case fAir: field1 = val; return; + //case Field::ANY_WATER: field2 = val; return; + //case Field::DEEP_WATER: field3 = val; return; + //case Field::AMPHIBIOUS: field4 = val; return; + default: throw runtime_error("Unknown Field passed to CellMetrics::set()"); + } + } + + void setAll(uint16 val) { /**< set clearance of all fields to val */ + field0 = field1 = field2 = field3 = field4 = val; + } + + bool operator!=(CellMetrics &that) const { /**< comparison, ignoring dirty bit */ + if (field0 == that.field0 && field1 == that.field1 + && field2 == that.field2 && field3 == that.field3 && field4 == that.field4) { + return false; + } + return true; + } + + bool isDirty() const { return dirty; } /**< is this cell dirty */ + void setDirty(const bool val) { dirty = val; } /**< set dirty flag */ + +private: + uint16 field0 : 3; /**< fLand = land + shallow water */ + uint16 field1 : 3; /**< fAir = air */ + uint16 field2 : 3; /**< Field::ANY_WATER = shallow + deep water */ + uint16 field3 : 3; /**< Field::DEEP_WATER = deep water */ + uint16 field4 : 3; /**< Field::AMPHIBIOUS = land + shallow + deep water */ + + uint16 dirty : 1; /**< used in 'team' maps as a 'dirty bit' (clearances have changed + * but team hasn't seen that change yet). */ +}; + +// ===================================================== +// class MetricMap +// ===================================================== +/** A wrapper class for the array of CellMetrics + */ +class MetricMap { +private: + CellMetrics *metrics; + int width,height; + +public: + MetricMap() : metrics(NULL), width(0), height(0) { } + ~MetricMap() { delete [] metrics; } + + void init(int w, int h) { + assert ( w > 0 && h > 0); + width = w; + height = h; + metrics = new CellMetrics[w * h]; + } + + void zero() { memset(metrics, 0, sizeof(CellMetrics) * width * height); } + + CellMetrics& operator[](const Vec2i &pos) const { return metrics[pos.y * width + pos.x]; } +}; + +// ===================================================== +// class AnnotatedMap +// ===================================================== +/** A 'search' map, annotated with clearance data and explored status + *

A compact representation of the map with clearance information for each cell. + * The clearance values are stored for each cell & field, and represent the + * clearance to the south and east of the cell. That is, the maximum size unit + * that can be 'positioned' in this cell (with units in Glest always using the + * north-west most cell they occupy as their 'position').

+ */ +//TODO: pretty pictures for the doco... +class AnnotatedMap { + friend class ClusterMap; + +private: + int width, height; + Map *cellMap; + +public: + AnnotatedMap(World *world); + ~AnnotatedMap(); + + int getWidth() { return width; } + int getHeight() { return height; } + + /** Maximum clearance allowed by the game. Hence, also maximum moveable unit size supported. */ + static const int maxClearanceValue = 7; // don't change me without also changing CellMetrics + + int maxClearance[fieldCount]; // maximum clearances needed for this world + + void initMapMetrics(); + + void revealTile(const Vec2i &pos); + + void updateMapMetrics(const Vec2i &pos, const int size); + + /** Interface to the clearance metrics, can a unit of size occupy a cell(s) + * @param pos position agent wishes to occupy + * @param size size of agent + * @param field field agent moves in + * @return true if position can be occupied, else false + */ + bool canOccupy(const Vec2i &pos, int size, Field field) const { + assert(cellMap->isInside(pos)); + return metrics[pos].get(field) >= size ? true : false; + } + + bool isDirty(const Vec2i &pos) const { return metrics[pos].isDirty(); } + void setDirty(const Vec2i &pos, const bool val) { metrics[pos].setDirty(val); } + + void annotateLocal(const Unit *unit); + void clearLocalAnnotations(const Unit *unit); + +private: + // for initMetrics() and updateMapMetrics () + void computeClearances(const Vec2i &); + uint32 computeClearance(const Vec2i &, Field); + + void cascadingUpdate(const Vec2i &pos, const int size, const Field field = fieldCount); + void annotateUnit(const Unit *unit, const Field field); + + bool updateCell(const Vec2i &pos, const Field field); + + + /** the original values of locations that have had local annotations applied */ + std::map localAnnt; + /** The metrics */ + MetricMap metrics; +}; + +}} + +#endif diff --git a/source/glest_game/ai/cartographer.cpp b/source/glest_game/ai/cartographer.cpp new file mode 100644 index 00000000..59eac03b --- /dev/null +++ b/source/glest_game/ai/cartographer.cpp @@ -0,0 +1,255 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#include "cartographer.h" +#include "game_constants.h" +#include "route_planner.h" +#include "node_map.h" + +#include "pos_iterator.h" + +#include "game.h" +#include "unit.h" +#include "unit_type.h" + +//#include "profiler.h" +//#include "leak_dumper.h" + +#include + +#if _GAE_DEBUG_EDITION_ +# include "debug_renderer.h" +#endif + +using namespace Shared::Graphics; +using namespace Shared::Util; + +namespace Glest { namespace Game { + +/** Construct Cartographer object. Requires game settings, factions & cell map to have been loaded. + */ +Cartographer::Cartographer(World *world) + : world(world), cellMap(0), routePlanner(0) { +// _PROFILE_FUNCTION(); + Logger::getInstance().add("Cartographer", true); + + cellMap = world->getMap(); + int w = cellMap->getW(), h = cellMap->getH(); + + routePlanner = world->getRoutePlanner(); + + nodeMap = new NodeMap(cellMap); + GridNeighbours gNeighbours(w, h); + nmSearchEngine = new SearchEngine(gNeighbours, nodeMap, true); + nmSearchEngine->setStorage(nodeMap); + nmSearchEngine->setInvalidKey(Vec2i(-1)); + nmSearchEngine->getNeighbourFunc().setSearchSpace(ssCellMap); + + masterMap = new AnnotatedMap(world); + clusterMap = new ClusterMap(masterMap, this); + + const TechTree *tt = world->getTechTree(); + vector harvestResourceTypes; + for (int i = 0; i < tt->getResourceTypeCount(); ++i) { + rt_ptr rt = tt->getResourceType(i); + if (rt->getClass() == rcTech || rt->getClass() == rcTileset) { + harvestResourceTypes.push_back(rt); + } + } + for (int i = 0; i < tt->getTypeCount(); ++i) { + const FactionType *ft = tt->getType(i); + for (int j = 0; j < ft->getUnitTypeCount(); ++j) { + const UnitType *ut = ft->getUnitType(j); + for (int k=0; k < ut->getCommandTypeCount(); ++k) { + const CommandType *ct = ut->getCommandType(k); + if (ct->getClass() == ccHarvest) { + const HarvestCommandType *hct = static_cast(ct); + for (vector::iterator it = harvestResourceTypes.begin(); + it != harvestResourceTypes.end(); ++it) { + if (hct->canHarvest(*it)) { + ResourceMapKey key(*it, ut->getField(), ut->getSize()); + resourceMapKeys.insert(key); + } + } + } + } + } + } + + // find and catalog all resources... + for (int x=0; x < cellMap->getSurfaceW() - 1; ++x) { + for (int y=0; y < cellMap->getSurfaceH() - 1; ++y) { + const Resource * const r = cellMap->getSurfaceCell(x,y)->getResource(); + if (r) { + resourceLocations[r->getType()].push_back(Vec2i(x,y)); + } + } + } + + Rectangle rect(0, 0, cellMap->getW() - 3, cellMap->getH() - 3); + for (set::iterator it = resourceMapKeys.begin(); it != resourceMapKeys.end(); ++it) { + PatchMap<1> *pMap = new PatchMap<1>(rect, 0); + initResourceMap(*it, pMap); + resourceMaps[*it] = pMap; + } +} + +/** Destruct */ +Cartographer::~Cartographer() { + delete nodeMap; + delete masterMap; + delete clusterMap; + delete nmSearchEngine; + + // Goal Maps + deleteMapValues(resourceMaps.begin(), resourceMaps.end()); + resourceMaps.clear(); + deleteMapValues(storeMaps.begin(), storeMaps.end()); + storeMaps.clear(); + deleteMapValues(siteMaps.begin(), siteMaps.end()); + siteMaps.clear(); +} + +void Cartographer::initResourceMap(ResourceMapKey key, PatchMap<1> *pMap) { + const int &size = key.workerSize; + const Field &field = key.workerField; + const Map &map = *world->getMap(); + pMap->zeroMap(); + for (vector::iterator it = resourceLocations[key.resourceType].begin(); + it != resourceLocations[key.resourceType].end(); ++it) { + Resource *r = world->getMap()->getSurfaceCell(*it)->getResource(); + assert(r); + +// r->Depleted.connect(this, &Cartographer::onResourceDepleted); + + Vec2i tl = *it * GameConstants::cellScale + OrdinalOffsets[odNorthWest] * size; + Vec2i br(tl.x + size + 2, tl.y + size + 2); + + Util::PerimeterIterator iter(tl, br); + while (iter.more()) { + Vec2i pos = iter.next(); + if (map.isInside(pos) && masterMap->canOccupy(pos, size, field)) { + pMap->setInfluence(pos, 1); + } + } + } +} + + +void Cartographer::onResourceDepleted(Vec2i pos) { + const ResourceType *rt = cellMap->getSurfaceCell(pos/GameConstants::cellScale)->getResource()->getType(); + resDirtyAreas[rt].push_back(pos); +// Vec2i tl = pos + OrdinalOffsets[odNorthWest]; +// Vec2i br = pos + OrdinalOffsets[OrdinalDir::SOUTH_EAST] * 2; +// resDirtyAreas[rt].push_back(pair(tl,br)); +} + +void Cartographer::fixupResourceMaps(const ResourceType *rt, const Vec2i &pos) { + const Map &map = *world->getMap(); + Vec2i junk; + for (set::iterator it = resourceMapKeys.begin(); it != resourceMapKeys.end(); ++it) { + if (it->resourceType == rt) { + PatchMap<1> *pMap = resourceMaps[*it]; + const int &size = it->workerSize; + const Field &field = it->workerField; + + Vec2i tl = pos + OrdinalOffsets[odNorthWest] * size; + Vec2i br(tl.x + size + 2, tl.y + size + 2); + + Util::RectIterator iter(tl, br); + while (iter.more()) { + Vec2i cur = iter.next(); + if (map.isInside(cur) && masterMap->canOccupy(cur, size, field) + && map.isResourceNear(cur, size, rt, junk)) { + pMap->setInfluence(cur, 1); + } else { + pMap->setInfluence(cur, 0); + } + } + } + } +} + +void Cartographer::onStoreDestroyed(Unit *unit) { + ///@todo fixme +// delete storeMaps[unit]; +// storeMaps.erase(unit); +} + +PatchMap<1>* Cartographer::buildAdjacencyMap(const UnitType *uType, const Vec2i &pos, Field f, int size) { + const Vec2i mapPos = pos + (OrdinalOffsets[odNorthWest] * size); + const int sx = pos.x; + const int sy = pos.y; + + Rectangle rect(mapPos.x, mapPos.y, uType->getSize() + 2 + size, uType->getSize() + 2 + size); + PatchMap<1> *pMap = new PatchMap<1>(rect, 0); + pMap->zeroMap(); + + PatchMap<1> tmpMap(rect, 0); + tmpMap.zeroMap(); + + // mark cells occupied by unitType at pos (on tmpMap) + Util::RectIterator rIter(pos, pos + Vec2i(uType->getSize() - 1)); + while (rIter.more()) { + Vec2i gpos = rIter.next(); + if (!uType->hasCellMap() || uType->getCellMapCell(gpos.x - sx, gpos.y - sy, CardinalDir::NORTH)) { + tmpMap.setInfluence(gpos, 1); + } + } + + // mark goal cells on result map + rIter = Util::RectIterator(mapPos, pos + Vec2i(uType->getSize())); + while (rIter.more()) { + Vec2i gpos = rIter.next(); + if (tmpMap.getInfluence(gpos) || !masterMap->canOccupy(gpos, size, f)) { + continue; // building or obstacle + } + Util::PerimeterIterator pIter(gpos - Vec2i(1), gpos + Vec2i(size)); + while (pIter.more()) { + if (tmpMap.getInfluence(pIter.next())) { + pMap->setInfluence(gpos, 1); + break; + } + } + } + return pMap; +} + +/*IF_DEBUG_EDITION( + void Cartographer::debugAddBuildSiteMap(PatchMap<1> *siteMap) { + Rectangle mapBounds = siteMap->getBounds(); + for (int ly = 0; ly < mapBounds.h; ++ly) { + int y = mapBounds.y + ly; + for (int lx = 0; lx < mapBounds.w; ++lx) { + Vec2i pos(mapBounds.x + lx, y); + if (siteMap->getInfluence(pos)) { + g_debugRenderer.addBuildSiteCell(pos); + } + } + } + } +) +*/ +void Cartographer::tick() { + if (clusterMap->isDirty()) { + clusterMap->update(); + } + for (ResourcePosMap::iterator it = resDirtyAreas.begin(); it != resDirtyAreas.end(); ++it) { + if (!it->second.empty()) { + for (V2iList::iterator posIt = it->second.begin(); posIt != it->second.end(); ++posIt) { + fixupResourceMaps(it->first, *posIt); + } + it->second.clear(); + } + } +} + +}} diff --git a/source/glest_game/ai/cartographer.h b/source/glest_game/ai/cartographer.h new file mode 100644 index 00000000..a0833392 --- /dev/null +++ b/source/glest_game/ai/cartographer.h @@ -0,0 +1,218 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#ifndef _GLEST_GAME_CARTOGRAPHER_H_ +#define _GLEST_GAME_CARTOGRAPHER_H_ + +#include "game_constants.h" +#include "influence_map.h" +#include "annotated_map.h" +#include "node_map.h" + +#include "world.h" +#include "config.h" + +#include "search_engine.h" +#include "resource_type.h" + +namespace Glest { namespace Game { + +using std::make_pair; + +class RoutePlanner; + +struct ResourceMapKey { + const ResourceType *resourceType; + Field workerField; + int workerSize; + + ResourceMapKey(const ResourceType *type, Field f, int s) + : resourceType(type), workerField(f), workerSize(s) {} + + bool operator<(const ResourceMapKey &that) const { + return (memcmp(this, &that, sizeof(ResourceMapKey)) < 0); + } +}; + +struct StoreMapKey { + const Unit *storeUnit; + Field workerField; + int workerSize; + + StoreMapKey(const Unit *store, Field f, int s) + : storeUnit(store), workerField(f), workerSize(s) {} + + bool operator<(const StoreMapKey &that) const { + return (memcmp(this, &that, sizeof(StoreMapKey)) < 0); + } +}; + +struct BuildSiteMapKey { + const UnitType *buildingType; + Vec2i buildingPosition; + Field workerField; + int workerSize; + + BuildSiteMapKey(const UnitType *type, const Vec2i &pos, Field f, int s) + : buildingType(type), buildingPosition(pos), workerField(f), workerSize(s) {} + + bool operator<(const BuildSiteMapKey &that) const { + return (memcmp(this, &that, sizeof(BuildSiteMapKey)) < 0); + } +}; + +// +// Cartographer: 'Map' Manager +// +class Cartographer { +private: + /** Master annotated map, always correct */ + AnnotatedMap *masterMap; + + /** The ClusterMap (Hierarchical map abstraction) */ + ClusterMap *clusterMap; + + typedef const ResourceType* rt_ptr; + typedef vector V2iList; + + typedef map*> ResourceMaps; // goal maps for harvester path searches to resourecs + typedef map*> StoreMaps; // goal maps for harvester path searches to store + typedef map*> SiteMaps; // goal maps for building sites. + + typedef list > ResourcePosList; + typedef map ResourcePosMap; + + // Resources + /** The locations of each and every resource on the map */ + ResourcePosMap resourceLocations; + + set resourceMapKeys; + + /** areas where resources have been depleted and updates are required */ + ResourcePosMap resDirtyAreas; + + ResourceMaps resourceMaps; /**< Goal Maps for each tech & tileset resource */ + StoreMaps storeMaps; /**< goal maps for resource stores */ + SiteMaps siteMaps; /**< goal maps for building sites */ + + // A* stuff + NodeMap *nodeMap; + SearchEngine *nmSearchEngine; + + World *world; + Map *cellMap; + RoutePlanner *routePlanner; + + void initResourceMap(ResourceMapKey key, PatchMap<1> *pMap); + void fixupResourceMaps(const ResourceType *rt, const Vec2i &pos); + + PatchMap<1>* buildAdjacencyMap(const UnitType *uType, const Vec2i &pos, Field f, int sz); + PatchMap<1>* buildAdjacencyMap(const UnitType *uType, const Vec2i &pos) { + return buildAdjacencyMap(uType, pos, fLand, 1); + } + + PatchMap<1>* buildStoreMap(StoreMapKey key) { + return (storeMaps[key] = buildAdjacencyMap(key.storeUnit->getType(), + key.storeUnit->getPos(), key.workerField, key.workerSize)); + } + +// IF_DEBUG_EDITION( void debugAddBuildSiteMap(PatchMap<1>*); ) + + PatchMap<1>* buildSiteMap(BuildSiteMapKey key) { + PatchMap<1> *sMap = siteMaps[key] = buildAdjacencyMap(key.buildingType, key.buildingPosition, + key.workerField, key.workerSize); +// IF_DEBUG_EDITION( debugAddBuildSiteMap(sMap); ) + return sMap; + } + + // slots + void onResourceDepleted(Vec2i pos); + void onStoreDestroyed(Unit *unit); + + void onUnitBorn(Unit *unit); + void onUnitMoved(Unit *unit); + void onUnitMorphed(Unit *unit, const UnitType *type); + void onUnitDied(Unit *unit); + + void maintainUnitVisibility(Unit *unit, bool add); + + void saveResourceState(XmlNode *node); + void loadResourceState(XmlNode *node); + +public: + Cartographer(World *world); + virtual ~Cartographer(); + + SearchEngine* getSearchEngine() { return nmSearchEngine; } + RoutePlanner* getRoutePlanner() { return routePlanner; } + + /** Update the annotated maps when an obstacle has been added or removed from the map. + * Unconditionally updates the master map, updates team maps if the team can see the cells, + * or mark as 'dirty' if they cannot currently see the change. @todo implement team maps + * @param pos position (north-west most cell) of obstacle + * @param size size of obstacle */ + void updateMapMetrics(const Vec2i &pos, const int size) { + masterMap->updateMapMetrics(pos, size); + // who can see it ? update their maps too. + // set cells as dirty for those that can't see it + } + + void tick(); + + PatchMap<1>* getResourceMap(ResourceMapKey key) { + return resourceMaps[key]; + } + + PatchMap<1>* getStoreMap(StoreMapKey key, bool build=true) { + StoreMaps::iterator it = storeMaps.find(key); + if (it != storeMaps.end()) { + return it->second; + } + if (build) { + return buildStoreMap(key); + } else { + return 0; + } + } + + PatchMap<1>* getStoreMap(const Unit *store, const Unit *worker) { + StoreMapKey key(store, worker->getCurrField(), worker->getType()->getSize()); + return getStoreMap(key); + } + + PatchMap<1>* getSiteMap(BuildSiteMapKey key) { + SiteMaps::iterator it = siteMaps.find(key); + if (it != siteMaps.end()) { + return it->second; + } + return buildSiteMap(key); + + } + + PatchMap<1>* getSiteMap(const UnitType *ut, const Vec2i &pos, Unit *worker) { + BuildSiteMapKey key(ut, pos, worker->getCurrField(), worker->getType()->getSize()); + return getSiteMap(key); + } + + void adjustGlestimalMap(Field f, TypeMap &iMap, const Vec2i &pos, float range); + void buildGlestimalMap(Field f, V2iList &positions); + + ClusterMap* getClusterMap() const { return clusterMap; } + + AnnotatedMap* getMasterMap() const { return masterMap; } + AnnotatedMap* getAnnotatedMap(int team ) { return masterMap;/*teamMaps[team];*/ } + AnnotatedMap* getAnnotatedMap(const Faction *faction) { return getAnnotatedMap(faction->getTeam()); } + AnnotatedMap* getAnnotatedMap(const Unit *unit) { return getAnnotatedMap(unit->getTeam()); } +}; + +}} + +#endif diff --git a/source/glest_game/ai/cluster_map.cpp b/source/glest_game/ai/cluster_map.cpp new file mode 100644 index 00000000..0bf6b349 --- /dev/null +++ b/source/glest_game/ai/cluster_map.cpp @@ -0,0 +1,678 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#include + +#include "cluster_map.h" +#include "node_pool.h" +#include "route_planner.h" + +#include "line.h" + +using std::min; +using Shared::Util::line; + +#define _USE_LINE_PATH_ 1 + +#if _GAE_DEBUG_EDITION_ +# include "debug_renderer.h" +#endif + +namespace Glest { namespace Game { + +int Edge::numEdges[fieldCount]; +int Transition::numTransitions[fieldCount]; + +void Edge::zeroCounters() { + for (int f = 0; f < fieldCount; ++f) { + numEdges[f] = 0; + } +} + +void Transition::zeroCounters() { + for (int f = 0; f < fieldCount; ++f) { + numTransitions[f] = 0; + } +} + +ClusterMap::ClusterMap(AnnotatedMap *aMap, Cartographer *carto) + : carto(carto), aMap(aMap), dirty(false) { + //_PROFILE_FUNCTION(); + w = aMap->getWidth() / GameConstants::clusterSize; + h = aMap->getHeight() / GameConstants::clusterSize; + vertBorders = new ClusterBorder[(w-1)*h]; + horizBorders = new ClusterBorder[w*(h-1)]; + + Edge::zeroCounters(); + Transition::zeroCounters(); + + //g_logger.setClusterCount(w * h); + + // init Borders (and hence inter-cluster edges) & evaluate clusters (intra-cluster edges) + for (int i = h - 1; i >= 0; --i) { + for (int j = w - 1; j >= 0; --j) { + Vec2i cluster(j, i); + initCluster(cluster); + evalCluster(cluster); + //g_logger.clusterInit(); + } + } +} + +ClusterMap::~ClusterMap() { + delete [] vertBorders; + delete [] horizBorders; + for (int f = 0; f < fieldCount; ++f) { + assert(Edge::NumEdges(f) == 0); + assert(Transition::NumTransitions(f) == 0); + if (Edge::NumEdges(Field(f)) != 0 || Transition::NumTransitions(Field(f)) != 0) { + throw runtime_error("memory leak"); + } + } +} + +#define LOG(x) {} + +void ClusterMap::assertValid() { + bool valid[fieldCount]; + bool inUse[fieldCount]; + int numNodes[fieldCount]; + int numEdges[fieldCount]; + + for (int f = 0; f < fieldCount; ++f) { + typedef set TSet; + TSet tSet; + + typedef pair TKey; + typedef map TKMap; + + TKMap tkMap; + + valid[f] = true; + numNodes[f] = 0; + numEdges[f] = 0; + inUse[f] = aMap->maxClearance[f] != 0; + if (f == fAir) inUse[f] = false; + if (!inUse[f]) { + continue; + } + + // collect all transitions, checking for membership in tSet and tkMap (indicating an error) + // and filling tSet and tkMap + for (int i=0; i < (w - 1) * h; ++i) { // vertical borders + ClusterBorder *b = &vertBorders[i]; + for (int j=0; j < b->transitions[f].n; ++j) { + const Transition *t = b->transitions[f].transitions[j]; + if (tSet.find(t) != tSet.end()) { + LOG("single transition on multiple borders.\n"); + valid[f] = false; + } else { + tSet.insert(t); + TKey key(t->nwPos, t->vertical); + if (tkMap.find(key) != tkMap.end()) { + LOG("seperate transitions of same orientation on same cell.\n"); + valid[f] = false; + } else { + tkMap[key] = t; + } + } + ++numNodes[f]; + } + } + for (int i=0; i < w * (h - 1); ++i) { // horizontal borders + ClusterBorder *b = &horizBorders[i]; + for (int j=0; j < b->transitions[f].n; ++j) { + const Transition *t = b->transitions[f].transitions[j]; + if (tSet.find(t) != tSet.end()) { + LOG("single transition on multiple borders.\n"); + valid[f] = false; + } else { + tSet.insert(t); + TKey key(t->nwPos, t->vertical); + if (tkMap.find(key) != tkMap.end()) { + LOG("seperate transitions of same orientation on same cell.\n"); + valid[f] = false; + } else { + tkMap[key] = t; + } + } + ++numNodes[f]; + } + } + + // with a complete collection, iterate, check all dest transitions + for (TSet::iterator it = tSet.begin(); it != tSet.end(); ++it) { + const Edges &edges = (*it)->edges; + for (Edges::const_iterator eit = edges.begin(); eit != edges.end(); ++eit) { + TSet::iterator it2 = tSet.find((*eit)->transition()); + if (it2 == tSet.end()) { + LOG("Invalid edge.\n"); + valid[f] = false; + } else { + if (*it == *it2) { + LOG("self referential transition.\n"); + valid[f] = false; + } + } + ++numEdges[f]; + } + } + } + LOG("\nClusterMap::assertValid()"); + LOG("\n=========================\n"); + for (int f = 0; f < fieldCount; ++f) { + if (!inUse[f]) { + LOG("Field::" << FieldNames[f] << " not in use.\n"); + } else { + LOG("Field::" << FieldNames[f] << " in use and " << (!valid[f]? "NOT " : "") << "valid.\n"); + LOG("\t" << numNodes[f] << " transitions inspected.\n"); + LOG("\t" << numEdges[f] << " edges inspected.\n"); + } + } +} + +/** Entrance init helper class */ +class InsideOutIterator { +private: + int centre, incr, end; + bool flip; + +public: + InsideOutIterator(int low, int high) + : flip(false) { + centre = low + (high - low) / 2; + incr = 0; + end = ((high - low) % 2 != 0) ? low - 1 : high + 1; + } + + int operator*() const { + return centre + (flip ? incr : -incr); + } + + void operator++() { + flip = !flip; + if (flip) ++incr; + } + + bool more() { + return **this != end; + } +}; + +void ClusterMap::addBorderTransition(EntranceInfo &info) { + assert(info.max_clear > 0 && info.startPos != -1 && info.endPos != -1); + if (info.run < 12) { + // find central most pos with max clearance + InsideOutIterator it(info.endPos, info.startPos); + while (it.more()) { + if (eClear[info.startPos - *it] == info.max_clear) { + Transition *t; + if (info.vert) { + t = new Transition(Vec2i(info.pos, *it), info.max_clear, true, info.f); + } else { + t = new Transition(Vec2i(*it, info.pos), info.max_clear, false, info.f); + } + info.cb->transitions[info.f].add(t); + return; + } + ++it; + } + assert(false); + } else { + // look for two, as close as possible to 1/4 and 3/4 accross + int l1 = info.endPos; + int h1 = info.endPos + (info.startPos - info.endPos) / 2 - 1; + int l2 = info.endPos + (info.startPos - info.endPos) / 2; + int h2 = info.startPos; + InsideOutIterator it(l1, h1); + int first_at = -1; + while (it.more()) { + if (eClear[info.startPos - *it] == info.max_clear) { + first_at = *it; + break; + } + ++it; + } + if (first_at != -1) { + it = InsideOutIterator(l2, h2); + int next_at = -1; + while (it.more()) { + if (eClear[info.startPos - *it] == info.max_clear) { + next_at = *it; + break; + } + ++it; + } + if (next_at != -1) { + Transition *t1, *t2; + if (info.vert) { + t1 = new Transition(Vec2i(info.pos, first_at), info.max_clear, true, info.f); + t2 = new Transition(Vec2i(info.pos, next_at), info.max_clear, true, info.f); + } else { + t1 = new Transition(Vec2i(first_at, info.pos), info.max_clear, false, info.f); + t2 = new Transition(Vec2i(next_at, info.pos), info.max_clear, false, info.f); + } + info.cb->transitions[info.f].add(t1); + info.cb->transitions[info.f].add(t2); + return; + } + } + // failed to find two, just add one... + it = InsideOutIterator(info.endPos, info.startPos); + while (it.more()) { + if (eClear[info.startPos - *it] == info.max_clear) { + Transition *t; + if (info.vert) { + t = new Transition(Vec2i(info.pos, *it), info.max_clear, true, info.f); + } else { + t = new Transition(Vec2i(*it, info.pos), info.max_clear, false, info.f); + } + info.cb->transitions[info.f].add(t); + return; + } + ++it; + } + assert(false); + } +} + +void ClusterMap::initClusterBorder(const Vec2i &cluster, bool north) { + //_PROFILE_FUNCTION(); + ClusterBorder *cb = north ? getNorthBorder(cluster) : getWestBorder(cluster); + EntranceInfo inf; + inf.cb = cb; + inf.vert = !north; + if (cb != &sentinel) { + int pos = north ? cluster.y * GameConstants::clusterSize - 1 + : cluster.x * GameConstants::clusterSize - 1; + inf.pos = pos; + int pos2 = pos + 1; + bool clear = false; // true while evaluating a Transition, false when obstacle hit + inf.max_clear = -1; // max clearance seen for current Transition + inf.startPos = -1; // start position of entrance + inf.endPos = -1; // end position of entrance + inf.run = 0; // to count entrance 'width' + for (int f = 0; f < fieldCount; ++f) { + if (!aMap->maxClearance[f] || f == fAir) continue; +/* + IF_DEBUG_EDITION( + if (f == fLand) { + for (int i=0; i < cb->transitions[f].n; ++i) { + g_debugRenderer.getCMOverlay().entranceCells.erase( + cb->transitions[f].transitions[i]->nwPos + ); + } + } + ) // DEBUG_EDITION +*/ + cb->transitions[f].clear(); + clear = false; + inf.f = Field(f); + inf.max_clear = -1; + for (int i=0; i < GameConstants::clusterSize; ++i) { + int clear1, clear2; + if (north) { + clear1 = aMap->metrics[Vec2i(POS_X,pos)].get(inf.f); + clear2 = aMap->metrics[Vec2i(POS_X,pos2)].get(inf.f); + } else { + clear1 = aMap->metrics[Vec2i(pos, POS_Y)].get(Field(f)); + clear2 = aMap->metrics[Vec2i(pos2, POS_Y)].get(Field(f)); + } + int local = min(clear1, clear2); + if (local) { + if (!clear) { + clear = true; + inf.startPos = north ? POS_X : POS_Y; + } + eClear[inf.run++] = local; + inf.endPos = north ? POS_X : POS_Y; + if (local > inf.max_clear) { + inf.max_clear = local; + } + } else { + if (clear) { + addBorderTransition(inf); + inf.run = 0; + inf.startPos = inf.endPos = inf.max_clear = -1; + clear = false; + } + } + } // for i < clusterSize + if (clear) { + addBorderTransition(inf); + inf.run = 0; + inf.startPos = inf.endPos = inf.max_clear = -1; + clear = false; + } + }// for each Field +/* + IF_DEBUG_EDITION( + for (int i=0; i < cb->transitions[fLand].n; ++i) { + g_debugRenderer.getCMOverlay().entranceCells.insert( + cb->transitions[fLand].transitions[i]->nwPos + ); + } + ) // DEBUG_EDITION +*/ + } // if not sentinel +} + +/** function object for line alg. 'visit' */ +struct Visitor { + vector &results; + Visitor(vector &res) : results(res) {} + + void operator()(int x, int y) { + results.push_back(Vec2i(x, y)); + } +}; + +/** compute path length using midpoint line algorithm, @return infinite if path not possible, else cost */ +float ClusterMap::linePathLength(Field f, int size, const Vec2i &start, const Vec2i &dest) { + //_PROFILE_FUNCTION(); + if (start == dest) { + return 0.f; + } + vector linePath; + Visitor visitor(linePath); + line(start.x, start.y, dest.x, dest.y, visitor); + assert(linePath.size() >= 2); + MoveCost costFunc(f, size, aMap); + vector::iterator it = linePath.begin(); + vector::iterator nIt = it + 1; + float cost = 0.f; + while (nIt != linePath.end() && cost != -1.f) { + cost += costFunc(*it++, *nIt++); + } + return cost; +} + +/** compute path length using A* (with node limit), @return infinite if path not possible, else cost */ +float ClusterMap::aStarPathLength(Field f, int size, const Vec2i &start, const Vec2i &dest) { + //_PROFILE_FUNCTION(); + if (start == dest) { + return 0.f; + } + SearchEngine *se = carto->getRoutePlanner()->getSearchEngine(); + MoveCost costFunc(f, size, aMap); + DiagonalDistance dd(dest); + se->setNodeLimit(GameConstants::clusterSize * GameConstants::clusterSize); + se->setStart(start, dd(start)); + AStarResult res = se->aStar(PosGoal(dest), costFunc, dd); + Vec2i goalPos = se->getGoalPos(); + if (res != asrComplete || goalPos != dest) { + return -1.f; + } + return se->getCostTo(goalPos); +} + +void ClusterMap::getTransitions(const Vec2i &cluster, Field f, Transitions &t) { + ClusterBorder *b = getNorthBorder(cluster); + for (int i=0; i < b->transitions[f].n; ++i) { + t.push_back(b->transitions[f].transitions[i]); + } + b = getEastBorder(cluster); + for (int i=0; i < b->transitions[f].n; ++i) { + t.push_back(b->transitions[f].transitions[i]); + } + b = getSouthBorder(cluster); + for (int i=0; i < b->transitions[f].n; ++i) { + t.push_back(b->transitions[f].transitions[i]); + } + b = getWestBorder(cluster); + for (int i=0; i < b->transitions[f].n; ++i) { + t.push_back(b->transitions[f].transitions[i]); + } +} + +void ClusterMap::disconnectCluster(const Vec2i &cluster) { + //cout << "Disconnecting cluster " << cluster << endl; + for (int f = 0; f < fieldCount; ++f) { + if (!aMap->maxClearance[f] || f == fAir) continue; + Transitions t; + getTransitions(cluster, Field(f), t); + set tset; + for (Transitions::iterator it = t.begin(); it != t.end(); ++it) { + tset.insert(*it); + } + //int del = 0; + for (Transitions::iterator it = t.begin(); it != t.end(); ++it) { + Transition *t = const_cast(*it); + Edges::iterator eit = t->edges.begin(); + while (eit != t->edges.end()) { + if (tset.find((*eit)->transition()) != tset.end()) { + delete *eit; + eit = t->edges.erase(eit); + //++del; + } else { + ++eit; + } + } + } + //cout << "\tDeleted " << del << " edges in Field = " << FieldNames[f] << ".\n"; + } + +} + +void ClusterMap::update() { + //_PROFILE_FUNCTION(); + //cout << "ClusterMap::update()" << endl; + for (set::iterator it = dirtyNorthBorders.begin(); it != dirtyNorthBorders.end(); ++it) { + if (it->y > 0 && it->y < h) { + dirtyClusters.insert(Vec2i(it->x, it->y)); + dirtyClusters.insert(Vec2i(it->x, it->y - 1)); + } + } + for (set::iterator it = dirtyWestBorders.begin(); it != dirtyWestBorders.end(); ++it) { + if (it->x > 0 && it->x < w) { + dirtyClusters.insert(Vec2i(it->x, it->y)); + dirtyClusters.insert(Vec2i(it->x - 1, it->y)); + } + } + for (set::iterator it = dirtyClusters.begin(); it != dirtyClusters.end(); ++it) { + //cout << "cluster " << *it << " dirty." << endl; + disconnectCluster(*it); + } + for (set::iterator it = dirtyNorthBorders.begin(); it != dirtyNorthBorders.end(); ++it) { + //cout << "cluster " << *it << " north border dirty." << endl; + initClusterBorder(*it, true); + } + for (set::iterator it = dirtyWestBorders.begin(); it != dirtyWestBorders.end(); ++it) { + //cout << "cluster " << *it << " west border dirty." << endl; + initClusterBorder(*it, false); + } + for (set::iterator it = dirtyClusters.begin(); it != dirtyClusters.end(); ++it) { + evalCluster(*it); + } + + dirtyClusters.clear(); + dirtyNorthBorders.clear(); + dirtyWestBorders.clear(); + dirty = false; +} + + +/** compute intra-cluster path lengths */ +void ClusterMap::evalCluster(const Vec2i &cluster) { + //_PROFILE_FUNCTION(); + int linePathSuccess = 0, linePathFail = 0; + SearchEngine *se = carto->getRoutePlanner()->getSearchEngine(); + se->getNeighbourFunc().setSearchCluster(cluster); + Transitions transitions; + for (int f = 0; f < fieldCount; ++f) { + if (!aMap->maxClearance[f] || f == fAir) continue; + transitions.clear(); + getTransitions(cluster, Field(f), transitions); + Transitions::iterator it = transitions.begin(); + for ( ; it != transitions.end(); ++it) { // foreach transition + Transition *t = const_cast(*it); + Vec2i start = t->nwPos; + Transitions::iterator it2 = transitions.begin(); + for ( ; it2 != transitions.end(); ++it2) { // foreach other transition + const Transition* &t2 = *it2; + if (t == t2) continue; + Vec2i dest = t2->nwPos; +# if _USE_LINE_PATH_ + float cost = linePathLength(Field(f), 1, start, dest); + if (cost == -1.f) { + cost = aStarPathLength(Field(f), 1, start, dest); + } +# else + float cost = aStarPathLength(Field(f), 1, start, dest); +# endif + if (cost == -1.f) continue; + Edge *e = new Edge(t2, Field(f)); + t->edges.push_back(e); + e->addWeight(cost); + int size = 2; + int maxClear = t->clearance > t2->clearance ? t2->clearance : t->clearance; + while (size <= maxClear) { +# if _USE_LINE_PATH_ + cost = linePathLength(Field(f), 1, start, dest); + if (cost == -1.f) { + cost = aStarPathLength(Field(f), size, start, dest); + } +# else + float cost = aStarPathLength(Field(f), size, start, dest); +# endif + if (cost == -1.f) { + break; + } + e->addWeight(cost); + assert(size == e->maxClear()); + ++size; + } + } // for each other transition + } // for each transition + } // for each Field + se->getNeighbourFunc().setSearchSpace(ssCellMap); +} + +// ======================================================== +// class TransitionNodeStorage +// ======================================================== + +TransitionAStarNode* TransitionNodeStore::getNode() { + if (nodeCount == size) { + //assert(false); + return NULL; + } + return &stock[nodeCount++]; +} + +void TransitionNodeStore::insertIntoOpen(TransitionAStarNode *node) { + if (openList.empty()) { + openList.push_front(node); + return; + } + list::iterator it = openList.begin(); + while (it != openList.end() && (*it)->est() <= node->est()) { + ++it; + } + openList.insert(it, node); +} + +bool TransitionNodeStore::assertOpen() { + if (openList.size() < 2) { + return true; + } + set seen; + list::iterator it1, it2 = openList.begin(); + it1 = it2; + seen.insert((*it1)->pos); + for (++it2; it2 != openList.end(); ++it2) { + if (seen.find((*it2)->pos) != seen.end()) { + LOG("open list has cycle... that's bad."); + return false; + } + seen.insert((*it2)->pos); + if ((*it1)->est() > (*it2)->est() + 0.0001f) { // stupid inaccurate fp + LOG("Open list corrupt: it1.est() == " << (*it1)->est() + << " > it2.est() == " << (*it2)->est()); + return false; + } + } + set::iterator it = open.begin(); + for ( ; it != open.end(); ++it) { + if (seen.find(*it) == seen.end()) { + LOG("node marked open not on open list."); + return false; + } + } + it = seen.begin(); + for ( ; it != seen.end(); ++it) { + if (open.find(*it) == open.end()) { + LOG("node on open list not marked open."); + return false; + } + } + return true; +} + +Transition* TransitionNodeStore::getBestSeen() { + assert(false); + return NULL; +} + +bool TransitionNodeStore::setOpen(const Transition* pos, const Transition* prev, float h, float d) { + assert(open.find(pos) == open.end()); + assert(closed.find(pos) == closed.end()); + + //REMOVE + //assert(assertOpen()); + + TransitionAStarNode *node = getNode(); + if (!node) return false; + node->pos = pos; + node->prev = prev; + node->distToHere = d; + node->heuristic = h; + open.insert(pos); + insertIntoOpen(node); + listed[pos] = node; + + //REMOVE + //assert(assertOpen()); + + return true; +} + +void TransitionNodeStore::updateOpen(const Transition* pos, const Transition* &prev, const float cost) { + assert(open.find(pos) != open.end()); + assert(closed.find(prev) != closed.end()); + + //REMOVE + //assert(assertOpen()); + + TransitionAStarNode *prevNode = listed[prev]; + TransitionAStarNode *posNode = listed[pos]; + if (prevNode->distToHere + cost < posNode->distToHere) { + openList.remove(posNode); + posNode->prev = prev; + posNode->distToHere = prevNode->distToHere + cost; + insertIntoOpen(posNode); + } + + //REMOVE + //assert(assertOpen()); +} + +const Transition* TransitionNodeStore::getBestCandidate() { + if (openList.empty()) return NULL; + TransitionAStarNode *node = openList.front(); + const Transition *best = node->pos; + openList.pop_front(); + open.erase(open.find(best)); + closed.insert(best); + return best; +} + +}} diff --git a/source/glest_game/ai/cluster_map.h b/source/glest_game/ai/cluster_map.h new file mode 100644 index 00000000..1bddb940 --- /dev/null +++ b/source/glest_game/ai/cluster_map.h @@ -0,0 +1,319 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#ifndef _GLEST_GAME_CLUSTER_MAP_H_ +#define _GLEST_GAME_CLUSTER_MAP_H_ + +#include "util.h" +#include "game_constants.h" +#include "skill_type.h" +#include "math_util.h" + +#include +#include +#include + +namespace Glest { namespace Game { + +using std::set; +using std::map; +using std::list; +using Shared::Util::deleteValues; + +class AnnotatedMap; +class Cartographer; +struct Transition; + +/** uni-directional edge, is owned by the source transition, contains pointer to dest */ +struct Edge { +private: + static int numEdges[fieldCount]; + + const Transition *dest; + vector weights; + + Field f; // for diagnostics... remove this one day + +public: + Edge(const Transition *t, Field f) : f(f) { + dest = t; + ++numEdges[f]; + } + + ~Edge() { + --numEdges[f]; + } + + void addWeight(const float w) { weights.push_back(w); } + const Transition* transition() const { return dest; } + float cost(int size) const { return weights[size-1]; } + int maxClear() { return weights.size(); } + + static int NumEdges(Field f) { return numEdges[f]; } + static void zeroCounters(); +}; +typedef list Edges; + +/** */ +struct Transition { +private: + static int numTransitions[fieldCount]; + Field f; + +public: + int clearance; + Vec2i nwPos; + bool vertical; + Edges edges; + + Transition(Vec2i pos, int clear, bool vert, Field f) + : f(f), clearance(clear), nwPos(pos), vertical(vert) { + ++numTransitions[f]; + } + ~Transition() { + deleteValues(edges.begin(), edges.end()); + --numTransitions[f]; + } + + static int NumTransitions(Field f) { return numTransitions[f]; } + static void zeroCounters(); +}; + +typedef vector Transitions; + +struct TransitionCollection { + Transition *transitions[GameConstants::clusterSize / 2]; + unsigned n; + + TransitionCollection() { + n = 0; + memset(transitions, 0, sizeof(Transition*) * GameConstants::clusterSize / 2); + } + + void add(Transition *t) { + assert(n < GameConstants::clusterSize / 2); + transitions[n++] = t; + } + + void clear() { + for (int i=0; i < n; ++i) { + delete transitions[i]; + } + n = 0; + } + +}; + +struct ClusterBorder { + TransitionCollection transitions[fieldCount]; + + ~ClusterBorder() { + for (int f = 0; f < fieldCount; ++f) { + transitions[f].clear(); + } + } +}; + +/** NeighbourFunc, describes abstract search space */ +class TransitionNeighbours { +public: + void operator()(const Transition* pos, vector &neighbours) const { + for (Edges::const_iterator it = pos->edges.begin(); it != pos->edges.end(); ++it) { + neighbours.push_back((*it)->transition()); + } + } +}; + +/** cost function for searching cluster map */ +class TransitionCost { + Field field; + int size; + +public: + TransitionCost(Field f, int s) : field(f), size(s) {} + float operator()(const Transition *t, const Transition *t2) const { + Edges::const_iterator it = t->edges.begin(); + for ( ; it != t->edges.end(); ++it) { + if ((*it)->transition() == t2) { + break; + } + } + if (it == t->edges.end()) { + throw runtime_error("bad connection in ClusterMap."); + } + if ((*it)->maxClear() >= size) { + return (*it)->cost(size); + } + return -1.f; + } +}; + +/** goal function for search on cluster map */ +class TransitionGoal { + set goals; +public: + TransitionGoal() {} + set& goalTransitions() {return goals;} + bool operator()(const Transition *t, const float d) const { + return goals.find(t) != goals.end(); + } +}; + +#define POS_X ((cluster.x + 1) * GameConstants::clusterSize - i - 1) +#define POS_Y ((cluster.y + 1) * GameConstants::clusterSize - i - 1) + +class ClusterMap { +#if _GAE_DEBUG_EDITION_ + friend class DebugRenderer; +#endif +private: + int w, h; + ClusterBorder *vertBorders, *horizBorders, sentinel; + Cartographer *carto; + AnnotatedMap *aMap; + + set dirtyClusters; + set dirtyNorthBorders; + set dirtyWestBorders; + bool dirty; + + int eClear[GameConstants::clusterSize]; + +public: + ClusterMap(AnnotatedMap *aMap, Cartographer *carto); + ~ClusterMap(); + + int getWidth() const { return w; } + int getHeight() const { return h; } + + static Vec2i cellToCluster (const Vec2i &cellPos) { + return Vec2i(cellPos.x / GameConstants::clusterSize, cellPos.y / GameConstants::clusterSize); + } + // ClusterBorder getters + ClusterBorder* getNorthBorder(Vec2i cluster) { + return ( cluster.y == 0 ) ? &sentinel : &horizBorders[(cluster.y - 1) * w + cluster.x ]; + } + ClusterBorder* getEastBorder(Vec2i cluster) { + return ( cluster.x == w - 1 ) ? &sentinel : &vertBorders[cluster.y * (w - 1) + cluster.x ]; + } + ClusterBorder* getSouthBorder(Vec2i cluster) { + return ( cluster.y == h - 1 ) ? &sentinel : &horizBorders[cluster.y * w + cluster.x]; + } + ClusterBorder* getWestBorder(Vec2i cluster) { + return ( cluster.x == 0 ) ? &sentinel : &vertBorders[cluster.y * (w - 1) + cluster.x - 1]; + } + ClusterBorder* getBorder(Vec2i cluster, CardinalDir dir) { + switch (dir) { + case CardinalDir::NORTH: + return getNorthBorder(cluster); + case CardinalDir::EAST: + return getEastBorder(cluster); + case CardinalDir::SOUTH: + return getSouthBorder(cluster); + case CardinalDir::WEST: + return getWestBorder(cluster); + default: + throw runtime_error("ClusterMap::getBorder() passed dodgey direction"); + } + return 0; // keep compiler quiet + } + void getTransitions(const Vec2i &cluster, Field f, Transitions &t); + + bool isDirty() { return dirty; } + void update(); + + void setClusterDirty(const Vec2i &cluster) { dirty = true; dirtyClusters.insert(cluster); } + void setNorthBorderDirty(const Vec2i &cluster) { dirty = true; dirtyNorthBorders.insert(cluster); } + void setWestBorderDirty(const Vec2i &cluster) { dirty = true; dirtyWestBorders.insert(cluster); } + + void assertValid(); + +private: + struct EntranceInfo { + ClusterBorder *cb; + Field f; + bool vert; + int pos, max_clear, startPos, endPos, run; + }; + void addBorderTransition(EntranceInfo &info); + void initClusterBorder(const Vec2i &cluster, bool north); + + /** initialise/re-initialise cluster (evaluates north and west borders) */ + void initCluster(const Vec2i &cluster) { + initClusterBorder(cluster, true); + initClusterBorder(cluster, false); + } + + void evalCluster(const Vec2i &cluster); + + float linePathLength(Field f, int size, const Vec2i &start, const Vec2i &dest); + float aStarPathLength(Field f, int size, const Vec2i &start, const Vec2i &dest); + + void disconnectCluster(const Vec2i &cluster); +}; + +struct TransitionAStarNode { + const Transition *pos, *prev; + float heuristic; /**< estimate of distance to goal */ + float distToHere; /**< cost from origin to this node */ + float est() const { /**< estimate, costToHere + heuristic */ + return distToHere + heuristic; + } +}; + +// ======================================================== +// class TransitionNodeStorage +// ======================================================== +// NodeStorage template interface +class TransitionNodeStore { +private: + list openList; + set open; + set closed; + map listed; + + int size, nodeCount; + TransitionAStarNode *stock; + + TransitionAStarNode* getNode(); + void insertIntoOpen(TransitionAStarNode *node); + bool assertOpen(); + +public: + TransitionNodeStore(int size) : size(size), stock(NULL) { + stock = new TransitionAStarNode[size]; + reset(); + } + ~TransitionNodeStore() { + delete [] stock; + } + + void reset() { nodeCount = 0; open.clear(); closed.clear(); openList.clear(); listed.clear(); } + void setMaxNodes(int limit) { } + + bool isOpen(const Transition* pos) { return open.find(pos) != open.end(); } + bool isClosed(const Transition* pos) { return closed.find(pos) != closed.end(); } + + bool setOpen(const Transition* pos, const Transition* prev, float h, float d); + void updateOpen(const Transition* pos, const Transition* &prev, const float cost); + const Transition* getBestCandidate(); + Transition* getBestSeen(); + + float getHeuristicAt(const Transition* &pos) { return listed[pos]->heuristic; } + float getCostTo(const Transition* pos) { return listed[pos]->distToHere; } + float getEstimateFor(const Transition* pos) { return listed[pos]->est(); } + const Transition* getBestTo(const Transition* pos) { return listed[pos]->prev; } +}; + + +}} + +#endif diff --git a/source/glest_game/ai/influence_map.h b/source/glest_game/ai/influence_map.h new file mode 100644 index 00000000..fcbb781a --- /dev/null +++ b/source/glest_game/ai/influence_map.h @@ -0,0 +1,255 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + + +#ifndef _GAME_SEARCH_INLUENCE_MAP_H_ +#define _GAME_SEARCH_INLUENCE_MAP_H_ + +#include "vec.h" +#include "types.h" + +#include + +namespace Glest { namespace Game { + +using Shared::Platform::uint32; + +typedef Shared::Graphics::Vec2i Point; + +struct Rectangle { + Rectangle() : x(0), y(0), w(0), h(0) {} + Rectangle(int x, int y, int w, int h) : x(x), y(y), w(w), h(h) {} + int x, y, w, h; +}; + +const Point invalidPos(-1,-1); + +// ===================================================== +// class InfluenceMap +// ===================================================== +template class InfluenceMap { +public: + InfluenceMap(Rectangle b, iType def) : def(def), x(b.x), y(b.y), w(b.w), h(b.h) {} + + iType getInfluence(Point pos) { + pos = translate(pos); + if (pos != invalidPos) { + return static_cast(this)->get(pos); + } + return def; + } + bool setInfluence(Point pos, iType val) { + pos = translate(pos); + if (pos != invalidPos) { + static_cast(this)->set(pos, val); + return true; + } + return false; + } + Point translate(Point p) { + const int nx = p.x - x; + const int ny = p.y - y; + if (nx >= 0 && ny >=0 && nx < w && ny < h) { + return Point(nx, ny); + } + return invalidPos; + } + Point getPos() const { return Point(x, y); } + Rectangle getBounds() const { return Rectangle(x, y, w, h); } + +protected: + iType def; // defualt value for positions not within (this) maps dimensions + int x , y, w, h; // Dimensions of this map. +}; + +// ===================================================== +// class TypeMap +// ===================================================== +template class TypeMap : public InfluenceMap > { + friend class InfluenceMap >; +public: + TypeMap(Rectangle b, type def) : InfluenceMap >(b,def) { + data = new type[b.w*b.h]; + } + ~TypeMap() { delete [] data; } + void zeroMap() { memset(data, 0, sizeof(type) * this->w * this->h); } + void clearMap(type val) { std::fill_n(data, this->w * this->h, val); } + +private: + type get(Point p) { return data[p.y * this->w + p.x]; } + void set(Point p, type v) { data[p.y * this->w + p.x] = v; } + type *data; +}; + +// ===================================================== +// class PatchMap +// ===================================================== +/** bit packed InfluenceMap, values are bit packed into 32 bit 'sections' (possibly padded) + * Not usefull for bits >= 7, just use TypeMap, TypeMap etc... + * bit wastage: + * bits == 1, 2 or 4: no wastage in full sections + * bits == 3, 5 or 6: 2 bits wasted per full section + */ +template class PatchMap : public InfluenceMap > { + friend class InfluenceMap >; +public: + PatchMap(Rectangle b, uint32 def) : InfluenceMap >(b,def) { + //cout << "PatchMap<" << bits << "> max_value = "<< max_value <<", [width = " << b.w << " height = " << b.h << "]\n"; + sectionsPerRow = b.w / sectionSize + 1; + //cout << "section size = " << sectionSize << ", sections per row = " << sectionsPerRow << endl; + data = new uint32[b.h * sectionsPerRow]; + } + //only for infuence_map_test.cpp:249 + PatchMap &operator=(const PatchMap &op){ + //FIXME: better when moved to InfluenceMap::operator=... + this->def = op.def; + this->x = op.x; this->y = op.y; this->w = op.w; this->h = op.h; + // + + sectionsPerRow = op.sectionsPerRow; + delete[] data; + data = new uint32[op.h * sectionsPerRow]; + memcpy(data, op.data, op.h * sectionsPerRow); + return *this; + } + ~PatchMap() { delete [] data; } + void zeroMap() { memset(data, 0, sizeof(uint32) * sectionsPerRow * this->h); } + void clearMap(uint32 val) { +// assert(val < max_value); + data[0] = 0; + for ( int i=0; i < sectionSize - 1; ++i) { + data[0] |= val; + data[0] <<= bits; + } + data[0] |= val; + for ( int i=1; i < sectionsPerRow; ++i ) { + data[i] = data[0]; + } + const int rowSize = sectionsPerRow * sizeof(uint32); + for ( int i=1; i < this->h; ++i ) { + uint32 *ptr = &data[i * sectionsPerRow]; + memcpy(ptr, data, rowSize); + } + } +private: + uint32 get(Point p) { + int sectionNdx = p.y * sectionsPerRow + p.x / sectionSize; + int subSectionNdx = p.x % sectionSize; + uint32 val = (data[sectionNdx] >> (subSectionNdx * bits)) & max_value; + //cout << "get(" << p.x << "," << p.y << "). section=" << sectionNdx + // << ", subsection=" << subSectionNdx << " = " << val < max_value) { //clamp? + v = max_value; + } + int sectionNdx = p.y * sectionsPerRow + p.x / sectionSize; + int subSectionNdx = p.x % sectionSize; + uint32 val = v << bits * subSectionNdx; + uint32 mask = max_value << bits * subSectionNdx; + data[sectionNdx] &= ~mask; // blot out old value + data[sectionNdx] |= val; // put in the new value + }/* + void logSection(int s) { + cout << "["; + for ( int j = 31; j >= 0; --j ) { + uint32 bitmask = 1 << j; + if ( data[s] & bitmask ) cout << "1"; + else cout << "0"; + if ( j && j % bits == 0 ) cout << " "; + } + cout << "]" << endl; + }*/ + static const uint32 max_value = (1 << bits) - 1; + static const int sectionSize = 32 / bits; + int sectionsPerRow; + uint32 *data; +}; + +// ===================================================== +// class FlowMap +// ===================================================== +/** bit packed 'offset' map, where offset is of the form -1 <= x <= 1 && -1 <= y <= 1 */ +class FlowMap : public InfluenceMap { + friend class InfluenceMap; +private: + Point expand(uint32 v) { + Point res(0,0); + if ( v & 8 ) { res.x = -1; } + else if ( v & 4 ) { res.x = 1; } + if ( v & 2 ) { res.y = -1; } + else if ( v & 1 ) { res.y = 1; } + return res; + } + uint32 pack(Point v) { + uint32 res = 0; + if ( v.x ) { res = 1 << (v.x > 0 ? 2 : 3); } + if ( v.y ) { res |= 1 << (v.y > 0 ? 0 : 1); } + return res; + } +public: + FlowMap(Rectangle b, Point def) : InfluenceMap(b,def) { + blocksPerRow = b.w / 8 + 1; + data = new uint32[b.h * blocksPerRow]; + } + ~FlowMap() { delete [] data; } + void zeroMap() { memset(data, 0, sizeof(uint32) * blocksPerRow * h); } + void clearMap(Point val) { + uint32 v = pack(val); + data[0] = 0; + for ( int i=0; i < 7; ++i ) { + data[0] |= v; + data[0] <<= 4; + } + data[0] |= v; + for ( int i=1; i < blocksPerRow; ++i ) { + data[i] = data[0]; + } + const int rowSize = blocksPerRow * sizeof(uint32); + for ( int i=1; i < h; ++i ) { + uint32 *ptr = &data[i * blocksPerRow]; + memcpy(ptr, data, rowSize); + } + } + +private: + Point get(Point p) { + const int blockNdx = p.y * blocksPerRow + p.x / 8; + const int subBlockNdx = p.x % 8; + uint32 val = (data[blockNdx] >> (subBlockNdx * 4)) & 15; + return expand(val); + } + void set(Point p, Point v) { + int blockNdx = p.y * blocksPerRow + p.x / 8; + int subBlockNdx = p.x % 8; + uint32 val = pack(v) << 4 * subBlockNdx; + uint32 mask = 15 << 4 * subBlockNdx; + data[blockNdx] &= ~mask; // blot out old value + data[blockNdx] |= val; // put in the new value + }/* + void logSection(int s) { + cout << "["; + for ( int j = 31; j >= 0; --j ) { + uint32 bitmask = 1 << j; + if ( data[s] & bitmask ) cout << "1"; + else cout << "0"; + if ( j && j % 4 == 0 ) cout << " "; + } + cout << "]" << endl; + }*/ + int blocksPerRow; + uint32 *data; // 8 values each +}; + +}} + +#endif diff --git a/source/glest_game/ai/node_map.cpp b/source/glest_game/ai/node_map.cpp new file mode 100644 index 00000000..6908e4ac --- /dev/null +++ b/source/glest_game/ai/node_map.cpp @@ -0,0 +1,291 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#include + +#include "node_map.h" +#include "annotated_map.h" // iterator typedefs + +namespace Glest { namespace Game { + +/** Construct a NodeMap */ +NodeMap::NodeMap(Map *map) + : cellMap(map) + , nodeLimit(-1) + , searchCounter(1) + , nodeCount(0) + , openTop(-1) { + invalidPos.x = invalidPos.y = 65535; + assert(!invalidPos.valid()); + bestH = invalidPos; + stride = cellMap->getW(); + nodeMap.init(cellMap->getW(), cellMap->getH()); +} + +/** resets the NodeMap for use */ +void NodeMap::reset() { + bestH = invalidPos; + searchCounter += 2; + nodeLimit = -1; + nodeCount = 0; + openTop = Vec2i(-1); +#if _GAE_DEBUG_EDITION_ + listedNodes.clear(); +#endif +} + +/** get the best candidate from the open list, and close it. + * @return the lowest estimate node from the open list, or -1,-1 if open list empty */ +Vec2i NodeMap::getBestCandidate() { + if (openTop.x < 0) { + return Vec2i(-1); // empty + } + assert(nodeMap[openTop].mark == searchCounter); + nodeMap[openTop].mark++; // set pos closed + Vec2i ret = openTop; + // set new openTop... + if (nodeMap[openTop].nextOpen.valid()) { + openTop = nodeMap[openTop].nextOpen; + } else { + openTop.x = -1; + } + return ret; +} + +/** marks an unvisited position as open + * @param pos the position to open + * @param prev the best known path to pos is from + * @param h the heuristic for pos + * @param d the costSoFar for pos + * @return true if added, false if node limit reached + */ +bool NodeMap::setOpen(const Vec2i &pos, const Vec2i &prev, float h, float d) { + assert(nodeMap[pos].mark < searchCounter); + if (nodeCount == nodeLimit) { + return false; + } + nodeMap[pos].prevNode = prev; + nodeMap[pos].mark = searchCounter; + nodeMap[pos].heuristic = h; + nodeMap[pos].distToHere = d; + float est = h + d; + nodeCount ++; + +# if _GAE_DEBUG_EDITION_ + listedNodes.push_back ( pos ); +# endif + + if (!bestH.valid() || nodeMap[pos].heuristic < nodeMap[bestH].heuristic) { + bestH = pos; + } + if (openTop.x == -1) { // top + openTop = pos; + nodeMap[pos].nextOpen = invalidPos; + return true; + } + PackedPos prevOpen, looking = openTop; + // find insert spot... + while (true) { + assert(looking.x != 255); + assert(isOpen(looking)); + + if (est < nodeMap[looking].estimate()) { + // pos better than looking, insert before 'looking' + nodeMap[pos].nextOpen = looking; + if (openTop == looking) { + openTop = pos; + } else { + assert(nodeMap[prevOpen].nextOpen == looking); + assert(prevOpen.valid()); + nodeMap[prevOpen].nextOpen = pos; + } + break; + } else { // est >= nodeMap[looking].estimate() + if (nodeMap[looking].nextOpen.valid()) { + prevOpen = looking; + looking = nodeMap[looking].nextOpen; + } else { // end of list + // insert after nodeMap[looking], set nextOpen - 1 + nodeMap[looking].nextOpen = pos; + nodeMap[pos].nextOpen = invalidPos; + break; + } + } + } // while + return true; +} +/** conditionally update a node on the open list. Tests if a path through a new nieghbour + * is better than the existing known best path to pos, updates if so. + * @param pos the open postion to test + * @param prev the new path from + * @param d the distance to here through prev + */ +void NodeMap::updateOpen(const Vec2i &pos, const Vec2i &prev, const float d) { + const float dist = nodeMap[prev].distToHere + d; + if (dist < nodeMap[pos].distToHere) { + //LOG ("Updating open node."); + nodeMap[pos].distToHere = dist; + nodeMap[pos].prevNode = prev; + const float est = nodeMap[pos].estimate(); + + if (pos == openTop) { // is top of open list anyway + //LOG("was already top"); + return; + } + PackedPos oldNext = nodeMap[pos].nextOpen; + if (est < nodeMap[openTop].estimate()) { + nodeMap[pos].nextOpen = openTop; + openTop = pos; + PackedPos ptmp, tmp = nodeMap[pos].nextOpen; + while (pos != tmp) { + assert(nodeMap[tmp].nextOpen.valid()); + ptmp = tmp; + tmp = nodeMap[tmp].nextOpen; + } + nodeMap[ptmp].nextOpen = oldNext; + //LOG ( "moved to top" ); + return; + } + PackedPos ptmp = openTop, tmp = nodeMap[openTop].nextOpen; + while (true) { + if (pos == tmp) { // no move needed + //LOG("was not moved"); + return; + } + if (est < nodeMap[tmp].estimate()) { + nodeMap[pos].nextOpen = tmp; + nodeMap[ptmp].nextOpen = pos; + while (pos != tmp) { + assert(nodeMap[tmp].nextOpen.valid()); + ptmp = tmp; + tmp = nodeMap[tmp].nextOpen; + } + //LOG("was moved up"); + nodeMap[ptmp].nextOpen = oldNext; + return; + } + ptmp = tmp; + assert(nodeMap[tmp].nextOpen.valid()); + tmp = nodeMap[tmp].nextOpen; + } + throw runtime_error("SearchMap::updateOpen() called with non-open position"); + } +} + +#define LOG(x) {} // {std::cout << x << endl;} + +void NodeMap::logOpen() { + if (openTop == Vec2i(-1)) { + LOG("Open list is empty."); + return; + } + static char buffer[4096]; + char *ptr = buffer; + PackedPos p = openTop; + while (p.valid()) { + ptr += sprintf(ptr, "%d,%d", p.x, p.y); + if (nodeMap[p].nextOpen.valid()) { + ptr += sprintf(ptr, " => "); + if (ptr - buffer > 4000) { + sprintf(ptr, " => plus more . . ."); + break; + } + } + p = nodeMap[p].nextOpen; + } + LOG(buffer); +} + +bool NodeMap::assertOpen() { + PackedPos cp; + set seen; + if (openTop.x == -1) { + return true; + } + // iterate over list, build 'seen' set, checking nextOpen is not there already + cp = openTop; + while (cp.valid()) { + seen.insert(cp); + if (seen.find(nodeMap[cp].nextOpen) != seen.end()) { + LOG("BIG TIME ERROR: open list is cyclic."); + return false; + } + cp = nodeMap[cp].nextOpen; + } + // scan entire grid, check that all nodes marked open are indeed on the open list... + set valid; + for (int y=0; y < cellMap->getH(); ++y) { + for (int x=0; x < cellMap->getW(); ++x) { + Vec2i pos(x, y); + if (isOpen(pos)) { + if (seen.find(pos) == seen.end()) { + LOG("ERROR: open list missing open node, or non open node claiming to be open."); + return false; + } + valid.insert(pos); + } + } + } + // ... and that all nodes on the list are marked open + for (set::iterator it = seen.begin(); it != seen.end(); ++it) { + if (valid.find(*it) == valid.end()) { + LOG("ERROR: node on open list was not marked open"); + return false; + } + } + return true; +} + +bool NodeMap::assertValidPath(list &path) { + if (path.size() < 2) return true; + VLIt it = path.begin(); + Vec2i prevPos = *it; + for (++it; it != path.end(); ++it) { + if (prevPos.dist(*it) < 0.99f || prevPos.dist(*it) > 1.42f) { + return false; + } + prevPos = *it; + } + return true; +} + +#if _GAE_DEBUG_EDITION_ +/* +list>* SearchMap::getLocalAnnotations() { + list> *ret = new list>(); + for ( map::iterator it = localAnnt.begin(); it != localAnnt.end(); ++ it ) { + ret->push_back(pair(it->first,nodeMap[it->first].getClearance(Field::LAND))); + } + return ret; +} +*/ + +list* NodeMap::getOpenNodes() { + list *ret = new list(); + list::iterator it = listedNodes.begin(); + for ( ; it != listedNodes.end(); ++it ) { + if ( nodeMap[*it].mark == searchCounter ) ret->push_back(*it); + } + return ret; +} + +list* NodeMap::getClosedNodes() { + list *ret = new list(); + list::iterator it = listedNodes.begin(); + for ( ; it != listedNodes.end(); ++it ) { + if ( nodeMap[*it].mark == searchCounter + 1 ) ret->push_back(*it); + } + return ret; +} + +#endif // defined ( _GAE_DEBUG_EDITION_ ) + +}} diff --git a/source/glest_game/ai/node_map.h b/source/glest_game/ai/node_map.h new file mode 100644 index 00000000..e7647504 --- /dev/null +++ b/source/glest_game/ai/node_map.h @@ -0,0 +1,185 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#ifndef _GLEST_GAME_ASTAR_NODE_MAP_H_ +#define _GLEST_GAME_ASTAR_NODE_MAP_H_ + +#include "game_constants.h" +#include "vec.h" +#include "world.h" + +namespace Glest { namespace Game { + +using Shared::Graphics::Vec2i; +using Shared::Platform::uint16; +using Shared::Platform::uint32; + +#define MAX_MAP_COORD_BITS 16 +#define MAX_MAP_COORD ((1<Node status for this search,

+ *
  • mark < NodeMap::searchCounter => unvisited
  • + *
  • mark == NodeMap::searchCounter => open
  • + *
  • mark == NodeMap::searchCounter + 1 => closed
*/ + uint32 mark; + + /** best route to here is from, valid only if this node is closed */ + PackedPos prevNode; + /** 'next' node in open list, valid only if this node is open */ + PackedPos nextOpen; + /** heuristic from this cell, valid only if node visited */ + float heuristic; + /** cost to this cell, valid only if node visited */ + float distToHere; + + /** Construct NodeMapCell */ + NodeMapCell() { memset(this, 0, sizeof(*this)); } + + /** the estimate function, costToHere + heuristic */ + float estimate() { return heuristic + distToHere; } +}; + +#pragma pack(pop) + +/** Wrapper for the NodeMapCell array */ +class NodeMapCellArray { +private: + NodeMapCell *array; + int stride; +public: + NodeMapCellArray() { array = NULL; } + ~NodeMapCellArray() { delete [] array; } + + void init(int w, int h) { delete [] array; array = new NodeMapCell[w * h]; stride = w; } + + /** index by Vec2i */ + NodeMapCell& operator[] (const Vec2i &pos) { return array[pos.y * stride + pos.x]; } + /** index by PackedPos */ + NodeMapCell& operator[] (const PackedPos pos) { return array[pos.y * stride + pos.x]; } +}; + +/** A NodeStorage (template interface) compliant NodeMap. Keeps a store of nodes the size of + * the map, and embeds the open and cosed list within. Uses some memory, but goes fast. + */ +class NodeMap { +public: + NodeMap(Map *map); + + // NodeStorage template interface + // + void reset(); + /** set a maximum number of nodes to expand */ + void setMaxNodes(int limit) { nodeLimit = limit > 0 ? limit : -1; } + /** is the node at pos open */ + bool isOpen(const Vec2i &pos) { return nodeMap[pos].mark == searchCounter; } + /** is the node at pos closed */ + bool isClosed(const Vec2i &pos) { return nodeMap[pos].mark == searchCounter + 1; } + + bool setOpen(const Vec2i &pos, const Vec2i &prev, float h, float d); + void updateOpen(const Vec2i &pos, const Vec2i &prev, const float cost); + Vec2i getBestCandidate(); + /** get the best heuristic node seen this search */ + Vec2i getBestSeen() { return bestH.valid() ? Vec2i(bestH) : Vec2i(-1); } + + /** get the heuristic of the node at pos [known to be visited] */ + float getHeuristicAt(const Vec2i &pos) { return nodeMap[pos].heuristic; } + /** get the cost to the node at pos [known to be visited] */ + float getCostTo(const Vec2i &pos) { return nodeMap[pos].distToHere; } + /** get the estimate for the node at pos [known to be visited] */ + float getEstimateFor(const Vec2i &pos) { return nodeMap[pos].estimate(); } + /** get the best path to the node at pos [known to be visited] */ + Vec2i getBestTo(const Vec2i &pos) { + return nodeMap[pos].prevNode.valid() ? Vec2i(nodeMap[pos].prevNode) : Vec2i(-1); + } + +private: + /** The array of nodes */ + NodeMapCellArray nodeMap; + + Map *cellMap; + + /** The stride of the array */ + int stride; + /** The current node expansion limit */ + int nodeLimit; + /** A counter, with NodeMapCell::mark determines validity of nodes in current search */ + uint32 searchCounter, + /** Number of nodes expanded this/last search */ + nodeCount; + /** An invalid PackedPos */ + PackedPos invalidPos; + /** The lowest heuristic node seen this/last search */ + PackedPos bestH; + /** The top of the open list is at */ + Vec2i openTop; + + /** Debug */ + bool assertValidPath(list &path); + /** Debug */ + bool assertOpen(); + /** Debug */ + void logOpen(); + +#ifdef _GAE_DEBUG_EDITION_ +public: + list* getOpenNodes (); + list* getClosedNodes (); + list listedNodes; +#endif + +}; + +}} + +#endif diff --git a/source/glest_game/ai/node_pool.cpp b/source/glest_game/ai/node_pool.cpp new file mode 100644 index 00000000..d3286cd4 --- /dev/null +++ b/source/glest_game/ai/node_pool.cpp @@ -0,0 +1,133 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== +// +// File: node_pool.cpp +// +#include "node_pool.h" +#include "world.h" +#include "map.h" + +namespace Glest { namespace Game { + +// ===================================================== +// class NodePool +// ===================================================== + +NodePool::NodePool(int w, int h) + : counter(0) + , leastH(NULL) + , numNodes(0) + , tmpMaxNodes(size) + , markerArray(w, h) { + stock = new AStarNode[size]; +} + +NodePool::~NodePool() { + delete [] stock; +} + +/** reset the node pool for a new search (resets tmpMaxNodes too) */ +void NodePool::reset() { + numNodes = 0; + counter = 0; + tmpMaxNodes = size; + leastH = NULL; + markerArray.newSearch(); + openHeap.clear(); + //IF_DEBUG_EDITION( listedNodes.clear(); ) +} +/** set a maximum number of nodes to expand */ +void NodePool::setMaxNodes(const int max) { + assert(max >= 32 && max <= size); // reasonable number ? + assert(!numNodes); // can't do this after we've started using it. + tmpMaxNodes = max; +} + +/** marks an unvisited position as open + * @param pos the position to open + * @param prev the best known path to pos is from + * @param h the heuristic for pos + * @param d the costSoFar for pos + * @return true if added, false if node limit reached */ +bool NodePool::setOpen(const Vec2i &pos, const Vec2i &prev, float h, float d) { + assert(!isOpen(pos)); + AStarNode *node = newNode(); + if (!node) { // NodePool exhausted + return false; + } + //IF_DEBUG_EDITION( listedNodes.push_back(pos); ) + node->posOff = pos; + if (prev.x >= 0) { + node->posOff.ox = prev.x - pos.x; + node->posOff.oy = prev.y - pos.y; + } else { + node->posOff.ox = 0; + node->posOff.oy = 0; + } + node->distToHere = d; + node->heuristic = h; + addOpenNode(node); + if (!numNodes || h < leastH->heuristic) { + leastH = node; + } + numNodes++; + return true; +} + +/** add a new node to the open list @param node pointer to the node to add */ +void NodePool::addOpenNode(AStarNode *node) { + assert(!isOpen(node->pos())); + markerArray.setOpen(node->pos()); + markerArray.set(node->pos(), node); + openHeap.insert(node); +} + +/** conditionally update a node on the open list. Tests if a path through a new nieghbour + * is better than the existing known best path to pos, updates if so. + * @param pos the open postion to test + * @param prev the new path from + * @param d the distance to here through prev */ +void NodePool::updateOpen(const Vec2i &pos, const Vec2i &prev, const float cost) { + //assert(isClosed(prev)); + AStarNode *posNode, *prevNode; + posNode = markerArray.get(pos); + prevNode = markerArray.get(prev); + if (prevNode->distToHere + cost < posNode->distToHere) { + posNode->posOff.ox = prev.x - pos.x; + posNode->posOff.oy = prev.y - pos.y; + posNode->distToHere = prevNode->distToHere + cost; + openHeap.promote(posNode); + } +} + +#if _GAE_DEBUG_EDITION_ + +list* NodePool::getOpenNodes() { + list *ret = new list(); + list::iterator it = listedNodes.begin(); + for ( ; it != listedNodes.end (); ++it) { + if (isOpen(*it)) ret->push_back(*it); + } + return ret; +} + +list* NodePool::getClosedNodes() { + list *ret = new list(); + list::iterator it = listedNodes.begin(); + for ( ; it != listedNodes.end(); ++it) { + if (isClosed(*it)) ret->push_back(*it); + } + return ret; +} + +#endif // _GAE_DEBUG_EDITION_ + +}} \ No newline at end of file diff --git a/source/glest_game/ai/node_pool.h b/source/glest_game/ai/node_pool.h new file mode 100644 index 00000000..4a4b265d --- /dev/null +++ b/source/glest_game/ai/node_pool.h @@ -0,0 +1,194 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== +// +// File: node_pool.h +// + +#ifndef _GLEST_GAME_PATHFINDER_NODE_POOL_H_ +#define _GLEST_GAME_PATHFINDER_NODE_POOL_H_ + +#include "vec.h" +#include "game_constants.h" + +#include "heap.h" +#include "types.h" + +#include +#include +#include +//#include + +namespace Glest { namespace Game { + +using Shared::Util::MinHeap; +using Shared::Graphics::Vec2i; +using namespace Shared::Platform; + +#pragma pack(push, 4) +struct PosOff { /**< A bit packed position (Vec2i) and offset (direction) pair */ + PosOff() : x(0), y(0), ox(0), oy(0) {} /**< Construct a PosOff [0,0] */ + PosOff(Vec2i pos) : ox(0),oy(0) { *this = pos; } /**< Construct a PosOff [pos.x, pos.y] */ + PosOff(int x, int y) : x(x) ,y(y) ,ox(0), oy(0) {} /**< Construct a PosOff [x,y] */ + PosOff& operator=(Vec2i pos) { /**< Assign from Vec2i */ + assert(pos.x <= 8191 && pos.y <= 8191); + x = pos.x; y = pos.y; return *this; + } + bool operator==(PosOff &p) + { return x == p.x && y == p.y; } /**< compare position components only */ + Vec2i getPos() { return Vec2i(x, y); } /**< this packed pos as Vec2i */ + Vec2i getPrev() { return Vec2i(x + ox, y + oy); } /**< return pos + offset */ + Vec2i getOffset() { return Vec2i(ox, oy); } /**< return offset */ + bool hasOffset() { return ox || oy; } /**< has an offset */ + bool valid() { return x >= 0 && y >= 0; } /**< is this position valid */ + + int32 x : 14; /**< x coordinate */ + int32 y : 14; /**< y coordinate */ + int32 ox : 2; /**< x offset */ + int32 oy : 2; /**< y offset */ +}; +#pragma pack(pop) + +// ===================================================== +// struct AStarNode +// ===================================================== +#pragma pack(push, 2) +struct AStarNode { /**< A node structure for A* with NodePool */ + PosOff posOff; /**< position of this node, and direction of best path to it */ + float heuristic; /**< estimate of distance to goal */ + float distToHere; /**< cost from origin to this node */ + + float est() const { return distToHere + heuristic;} /**< estimate, costToHere + heuristic */ + Vec2i pos() { return posOff.getPos(); } /**< position of this node */ + Vec2i prev() { return posOff.getPrev(); } /**< best path to this node is from */ + bool hasPrev() { return posOff.hasOffset(); } /**< has valid previous 'pointer' */ + + int32 heap_ndx; + void setHeapIndex(int ndx) { heap_ndx = ndx; } + int getHeapIndex() const { return heap_ndx; } + + bool operator<(const AStarNode &that) const { + const float diff = (distToHere + heuristic) - (that.distToHere + that.heuristic); + if (diff < 0) return true; + else if (diff > 0) return false; + // tie, prefer closer to goal... + if (heuristic < that.heuristic) return true; + if (heuristic > that.heuristic) return false; + // still tied... just distinguish them somehow... + return this < &that; + } +}; // == 128 bits (16 bytes) +#pragma pack(pop) + +// ======================================================== +// class NodePool +// ======================================================== +class NodePool { /**< A NodeStorage class (template interface) for A* */ +private: + static const int size = 512; /**< total number of AStarNodes in each pool */ + AStarNode *stock; /**< The block of nodes */ + int counter; /**< current counter */ + + // ===================================================== + // struct MarkerArray + // ===================================================== + /** An Marker & Pointer Array supporting two mark types, open and closed. */ + ///@todo replace pointers with indices, interleave mark and index arrays + struct MarkerArray { + private: + int stride; /**< stride of array */ + unsigned int counter; /**< the counter */ + unsigned int *marker; /**< the mark array */ + AStarNode **pArray; /**< the pointer array */ + public: + MarkerArray(int w, int h) : stride(w), counter(0) { + marker = new unsigned int[w * h]; + memset(marker, 0, w * h * sizeof(unsigned int)); + pArray = new AStarNode*[w * h]; + memset(pArray, 0, w * h * sizeof(AStarNode*)); + } + ~MarkerArray() { delete [] marker; delete [] pArray; } + inline void newSearch() { counter += 2; } + inline void setOpen(const Vec2i &pos) { marker[pos.y * stride + pos.x] = counter; } + inline void setClosed(const Vec2i &pos) { marker[pos.y * stride + pos.x] = counter + 1; } + inline bool isOpen(const Vec2i &pos) { return marker[pos.y * stride + pos.x] == counter; } + inline bool isClosed(const Vec2i &pos) { return marker[pos.y * stride + pos.x] == counter + 1; } + inline bool isListed(const Vec2i &pos) { return marker[pos.y * stride + pos.x] >= counter; } /**< @deprecated not needed? */ + inline void setNeither(const Vec2i &pos){ marker[pos.y * stride + pos.x] = 0; } /**< @deprecated not needed? */ + + inline void set(const Vec2i &pos, AStarNode *ptr) { pArray[pos.y * stride + pos.x] = ptr; } /**< set the pointer for pos to ptr */ + inline AStarNode* get(const Vec2i &pos) { return pArray[pos.y * stride + pos.x]; } /**< get the pointer for pos */ + }; + +private: + AStarNode *leastH; /**< The 'best' node seen so far this search */ + int numNodes; /**< number of nodes used so far this search */ + int tmpMaxNodes; /**< a temporary maximum number of nodes to use */ + + MarkerArray markerArray; /**< An array the size of the map, indicating node status (unvisited, open, closed) */ + MinHeap openHeap; /**< the open list, binary heap with index aware nodes */ + +public: + NodePool(int w, int h); + ~NodePool(); + + // NodeStorage template interface + // + void setMaxNodes(const int max); + void reset(); + + bool isOpen(const Vec2i &pos) { return markerArray.isOpen(pos); } /**< test if a position is open */ + bool isClosed(const Vec2i &pos) { return markerArray.isClosed(pos); } /**< test if a position is closed */ + bool isListed(const Vec2i &pos) { return markerArray.isListed(pos); } /**< @deprecated needed for canPathOut() */ + + bool setOpen(const Vec2i &pos, const Vec2i &prev, float h, float d); + void updateOpen(const Vec2i &pos, const Vec2i &prev, const float cost); + + /** get the best candidate from the open list, and close it. + * @return the lowest estimate node from the open list, or -1,-1 if open list empty */ + Vec2i getBestCandidate() { + if (openHeap.empty()) { + return Vec2i(-1); + } + AStarNode *ptr = openHeap.extract(); + markerArray.setClosed(ptr->pos()); + return ptr->pos(); + } + /** get the best heuristic node seen this search */ + Vec2i getBestSeen() { return leastH->pos(); } + /** get the heuristic of the node at pos [known to be visited] */ + float getHeuristicAt(const Vec2i &pos) { return markerArray.get(pos)->heuristic; } + /** get the cost to the node at pos [known to be visited] */ + float getCostTo(const Vec2i &pos) { return markerArray.get(pos)->distToHere; } + /** get the estimate for the node at pos [known to be visited] */ + float getEstimateFor(const Vec2i &pos) { return markerArray.get(pos)->est(); } + /** get the best path to the node at pos [known to be closed] */ + Vec2i getBestTo(const Vec2i &pos) { + AStarNode *ptr = markerArray.get(pos); + assert(ptr); + return ptr->hasPrev() ? ptr->prev() : Vec2i(-1); + } + +private: + void addOpenNode(AStarNode *node); + AStarNode* newNode() { return ( counter < size ? &stock[counter++] : NULL ); } + +#if _GAE_DEBUG_EDITION_ +public: + // interface to support debugging textures + list* getOpenNodes(); + list* getClosedNodes(); + list listedNodes; +#endif +}; + +}} + +#endif diff --git a/source/glest_game/ai/path_finder.cpp b/source/glest_game/ai/path_finder.cpp new file mode 100644 index 00000000..e7328f4e --- /dev/null +++ b/source/glest_game/ai/path_finder.cpp @@ -0,0 +1,306 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2001-2008 Martiņo Figueroa +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#include "path_finder.h" + +#include +#include + +#include "config.h" +#include "map.h" +#include "unit.h" +#include "unit_type.h" +#include "leak_dumper.h" + +using namespace std; +using namespace Shared::Graphics; +using namespace Shared::Util; + +namespace Glest{ namespace Game{ + +// ===================================================== +// class PathFinder +// ===================================================== + +// ===================== PUBLIC ======================== + +const int PathFinder::maxFreeSearchRadius= 10; +const int PathFinder::pathFindNodesMax= 400; +const int PathFinder::pathFindRefresh= 10; + + +PathFinder::PathFinder(){ + nodePool= NULL; +} + +PathFinder::PathFinder(const Map *map){ + init(map); + nodePool= NULL; +} + +void PathFinder::init(const Map *map){ + nodePool= new Node[pathFindNodesMax]; + this->map= map; +} + +PathFinder::~PathFinder(){ + delete [] nodePool; +} + +PathFinder::TravelState PathFinder::findPath(Unit *unit, const Vec2i &finalPos){ + + //route cache + UnitPath *path= unit->getPath(); + if(finalPos==unit->getPos()){ + //if arrived + unit->setCurrSkill(scStop); + return tsArrived; + } + else if(!path->empty()){ + //route cache + Vec2i pos= path->peek(); + if(map->canMove(unit, unit->getPos(), pos)){ + path->pop(); + unit->setTargetPos(pos); + return tsOnTheWay; + } + } + + //route cache miss + TravelState ts= aStar(unit, finalPos); + + //post actions + switch(ts){ + case tsBlocked: + case tsArrived: + unit->setCurrSkill(scStop); + break; + case tsOnTheWay: + Vec2i pos= path->peek(); + if(map->canMove(unit, unit->getPos(), pos)){ + path->pop(); + unit->setTargetPos(pos); + } + else{ + unit->setCurrSkill(scStop); + return tsBlocked; + } + break; + } + return ts; +} + +// ==================== PRIVATE ==================== + +//route a unit using A* algorithm +PathFinder::TravelState PathFinder::aStar(Unit *unit, const Vec2i &targetPos){ + + nodePoolCount= 0; + const Vec2i finalPos= computeNearestFreePos(unit, targetPos); + + //if arrived + if(finalPos==unit->getPos()){ + return tsArrived; + } + + //path find algorithm + + //a) push starting pos into openNodes + Node *firstNode= newNode(); + assert(firstNode!=NULL);; + firstNode->next= NULL; + firstNode->prev= NULL; + firstNode->pos= unit->getPos(); + firstNode->heuristic= heuristic(unit->getPos(), finalPos); + firstNode->exploredCell= true; + openNodes.push_back(firstNode); + + //b) loop + bool pathFound= true; + bool nodeLimitReached= false; + Node *node= NULL; + + while(!nodeLimitReached){ + + //b1) is open nodes is empty => failed to find the path + if(openNodes.empty()){ + pathFound= false; + break; + } + + //b2) get the minimum heuristic node + Nodes::iterator it = minHeuristic(); + node= *it; + + //b3) if minHeuristic is the finalNode, or the path is no more explored => path was found + if(node->pos==finalPos || !node->exploredCell){ + pathFound= true; + break; + } + + //b4) move this node from closedNodes to openNodes + //add all succesors that are not in closedNodes or openNodes to openNodes + closedNodes.push_back(node); + openNodes.erase(it); + for(int i=-1; i<=1 && !nodeLimitReached; ++i){ + for(int j=-1; j<=1 && !nodeLimitReached; ++j){ + Vec2i sucPos= node->pos + Vec2i(i, j); + if(!openPos(sucPos) && map->aproxCanMove(unit, node->pos, sucPos)){ + //if node is not open and canMove then generate another node + Node *sucNode= newNode(); + if(sucNode!=NULL){ + sucNode->pos= sucPos; + sucNode->heuristic= heuristic(sucNode->pos, finalPos); + sucNode->prev= node; + sucNode->next= NULL; + sucNode->exploredCell= map->getSurfaceCell(Map::toSurfCoords(sucPos))->isExplored(unit->getTeam()); + openNodes.push_back(sucNode); + } + else{ + nodeLimitReached= true; + } + } + } + } + }//while + + Node *lastNode= node; + + //if consumed all nodes find best node (to avoid strage behaviour) + if(nodeLimitReached){ + for(Nodes::iterator it= closedNodes.begin(); it!=closedNodes.end(); ++it){ + if((*it)->heuristic < lastNode->heuristic){ + lastNode= *it; + } + } + } + + //check results of path finding + TravelState ts; + UnitPath *path= unit->getPath(); + if(pathFound==false || lastNode==firstNode){ + //blocked + ts= tsBlocked; + path->incBlockCount(); + } + else { + //on the way + ts= tsOnTheWay; + + //build next pointers + Node *currNode= lastNode; + while(currNode->prev!=NULL){ + currNode->prev->next= currNode; + currNode= currNode->prev; + } + //store path + path->clear(); + + currNode= firstNode; + for(int i=0; currNode->next!=NULL && inext, i++){ + path->push(currNode->next->pos); + } + } + + //clean nodes + openNodes.clear(); + closedNodes.clear(); + + return ts; +} + +PathFinder::Node *PathFinder::newNode(){ + if(nodePoolCountgetPos(); + int size= unit->getType()->getSize(); + Field field= unit->getCurrField(); + int teamIndex= unit->getTeam(); + + //if finalPos is free return it + if(map->isAproxFreeCells(finalPos, size, field, teamIndex)){ + return finalPos; + } + + //find nearest pos + Vec2i nearestPos= unitPos; + float nearestDist= unitPos.dist(finalPos); + for(int i= -maxFreeSearchRadius; i<=maxFreeSearchRadius; ++i){ + for(int j= -maxFreeSearchRadius; j<=maxFreeSearchRadius; ++j){ + Vec2i currPos= finalPos + Vec2i(i, j); + if(map->isAproxFreeCells(currPos, size, field, teamIndex)){ + float dist= currPos.dist(finalPos); + + //if nearer from finalPos + if(distheuristic < (*minNodeIt)->heuristic){ + minNodeIt= it; + } + } + + return minNodeIt; +} + +bool PathFinder::openPos(const Vec2i &sucPos){ + + for(Nodes::reverse_iterator it= closedNodes.rbegin(); it!=closedNodes.rend(); ++it){ + if(sucPos==(*it)->pos){ + return true; + } + } + + //use reverse iterator to find a node faster + for(Nodes::reverse_iterator it= openNodes.rbegin(); it!=openNodes.rend(); ++it){ + if(sucPos==(*it)->pos){ + return true; + } + } + + return false; +} + +}} //end namespace diff --git a/source/glest_game/ai/route_planner.cpp b/source/glest_game/ai/route_planner.cpp new file mode 100644 index 00000000..b7b19aec --- /dev/null +++ b/source/glest_game/ai/route_planner.cpp @@ -0,0 +1,843 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2001-2008 Martiņo Figueroa +// 2009-2010 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#include "route_planner.h" +#include "node_pool.h" + +#include "config.h" +#include "map.h" +#include "game.h" +#include "unit.h" +#include "unit_type.h" + +#include "leak_dumper.h" + +#if _GAE_DEBUG_EDITION_ +# include "debug_renderer.h" +#endif + +#ifndef PATHFINDER_DEBUG_MESSAGES +# define PATHFINDER_DEBUG_MESSAGES 0 +#endif + +#if PATHFINDER_DEBUG_MESSAGES +# define CONSOLE_LOG(x) {g_console.addLine(x); g_logger.add(x);} +#else +# define CONSOLE_LOG(x) {} +#endif + +#define _PROFILE_PATHFINDER() +//#define _PROFILE_PATHFINDER() _PROFILE_FUNCTION() + +using namespace Shared::Graphics; +using namespace Shared::Util; + +namespace Glest { namespace Game { + +#if _GAE_DEBUG_EDITION_ + template + void collectOpenClosed(NodeStorage *ns) { + list *nodes = ns->getOpenNodes(); + list::iterator nit = nodes->begin(); + for ( ; nit != nodes->end(); ++nit ) + g_debugRenderer.getPFCallback().openSet.insert(*nit); + delete nodes; + nodes = ns->getClosedNodes(); + for ( nit = nodes->begin(); nit != nodes->end(); ++nit ) + g_debugRenderer.getPFCallback().closedSet.insert(*nit); + delete nodes; + } + + void collectPath(const Unit *unit) { + const UnitPath &path = *unit->getPath(); + for (UnitPath::const_iterator pit = path.begin(); pit != path.end(); ++pit) + g_debugRenderer.getPFCallback().pathSet.insert(*pit); + } + + void collectWaypointPath(const Unit *unit) { + const WaypointPath &path = *unit->getWaypointPath(); + g_debugRenderer.clearWaypoints(); + WaypointPath::const_iterator it = path.begin(); + for ( ; it != path.end(); ++it) { + Vec3f vert = g_world.getMap()->getTile(Map::toTileCoords(*it))->getVertex(); + vert.x += it->x % GameConstants::cellScale + 0.5f; + vert.z += it->y % GameConstants::cellScale + 0.5f; + vert.y += 0.15f; + g_debugRenderer.addWaypoint(vert); + } + } + + void clearOpenClosed(const Vec2i &start, const Vec2i &target) { + g_debugRenderer.getPFCallback().pathStart = start; + g_debugRenderer.getPFCallback().pathDest = target; + g_debugRenderer.getPFCallback().pathSet.clear(); + g_debugRenderer.getPFCallback().openSet.clear(); + g_debugRenderer.getPFCallback().closedSet.clear(); + } +#endif // _GAE_DEBUG_EDITION_ + +// ===================================================== +// class RoutePlanner +// ===================================================== + +// ===================== PUBLIC ======================== + +/** Construct RoutePlanner object */ +RoutePlanner::RoutePlanner(World *world) + : world(world) + , nsgSearchEngine(NULL) + , nodeStore(NULL) + , tSearchEngine(NULL) + , tNodeStore(NULL) { + Logger::getInstance().add( "Initialising SearchEngine", true ); + + const int &w = world->getMap()->getW(); + const int &h = world->getMap()->getH(); + + //cout << "NodePool SearchEngine\n"; + nodeStore = new NodePool(w, h); + GridNeighbours gNeighbours(w, h); + nsgSearchEngine = new SearchEngine(gNeighbours, nodeStore, true); + nsgSearchEngine->setInvalidKey(Vec2i(-1)); + nsgSearchEngine->getNeighbourFunc().setSearchSpace(ssCellMap); + + //cout << "Transition SearchEngine\n"; + int numNodes = w * h / 4096 * 250; // 250 nodes for every 16 clusters + tNodeStore = new TransitionNodeStore(numNodes); + TransitionNeighbours tNeighbours; + tSearchEngine = new TransitionSearchEngine(tNeighbours, tNodeStore, true); + tSearchEngine->setInvalidKey(NULL); +} + +/** delete SearchEngine objects */ +RoutePlanner::~RoutePlanner() { + delete nsgSearchEngine; + delete tSearchEngine; +} + +/** Determine legality of a proposed move for a unit. This function is the absolute last say + * in such matters. + * @param unit the unit attempting to move + * @param pos2 the position unit desires to move to + * @return true if move may proceed, false if not legal. + */ +bool RoutePlanner::isLegalMove(Unit *unit, const Vec2i &pos2) const { + assert(world->getMap()->isInside(pos2)); + assert(unit->getPos().dist(pos2) < 1.5); + + if (unit->getPos().dist(pos2) > 1.5) { + throw runtime_error("Boo!!!"); + } + + const Vec2i &pos1 = unit->getPos(); + const int &size = unit->getType()->getSize(); + const Field &field = unit->getCurrField(); + + AnnotatedMap *annotatedMap = world->getCartographer()->getMasterMap(); + if (!annotatedMap->canOccupy(pos2, size, field)) { + return false; // obstruction in field + } + if ( pos1.x != pos2.x && pos1.y != pos2.y ) { + // Proposed move is diagonal, check if cells either 'side' are free. + // eg.. XXXX + // X1FX The Cells marked 'F' must both be free + // XF2X for the move 1->2 to be legit + // XXXX + Vec2i diag1, diag2; + getDiags(pos1, pos2, size, diag1, diag2); + if (!annotatedMap->canOccupy(diag1, 1, field) || !annotatedMap->canOccupy(diag2, 1, field) + || !world->getMap()->getCell(diag1)->isFree(field) + || !world->getMap()->getCell(diag2)->isFree(field)) { + return false; // obstruction, can not move to pos2 + } + } + for (int i = pos2.x; i < unit->getType()->getSize() + pos2.x; ++i) { + for (int j = pos2.y; j < unit->getType()->getSize() + pos2.y; ++j) { + if (world->getMap()->getCell(i,j)->getUnit(field) != unit + && !world->getMap()->isFreeCell(Vec2i(i, j), field)) { + return false; // blocked by another unit + } + } + } + // pos2 is free, and nothing is in the way + return true; +} + +float RoutePlanner::quickSearch(Field field, int size, const Vec2i &start, const Vec2i &dest) { + // setup search + MoveCost moveCost(field, size, world->getCartographer()->getMasterMap()); + DiagonalDistance heuristic(dest); + nsgSearchEngine->setStart(start, heuristic(start)); + + AStarResult r = nsgSearchEngine->aStar(PosGoal(dest), moveCost, heuristic); + if (r == asrComplete && nsgSearchEngine->getGoalPos() == dest) { + return nsgSearchEngine->getCostTo(dest); + } + return -1.f; +} + +HAAStarResult RoutePlanner::setupHierarchicalSearch(Unit *unit, const Vec2i &dest, TransitionGoal &goalFunc) { + // get Transitions for start cluster + Transitions transitions; + Vec2i startCluster = ClusterMap::cellToCluster(unit->getPos()); + ClusterMap *clusterMap = world->getCartographer()->getClusterMap(); + clusterMap->getTransitions(startCluster, unit->getCurrField(), transitions); + + DiagonalDistance dd(dest); + nsgSearchEngine->getNeighbourFunc().setSearchCluster(startCluster); + + bool startTrap = true; + // attempt quick path from unit->pos to each transition, + // if successful add transition to open list + + AnnotatedMap *aMap = world->getCartographer()->getMasterMap(); + aMap->annotateLocal(unit); + for (Transitions::iterator it = transitions.begin(); it != transitions.end(); ++it) { + float cost = quickSearch(unit->getCurrField(), unit->getType()->getSize(), unit->getPos(), (*it)->nwPos); + if (cost != -1.f) { + tSearchEngine->setOpen(*it, dd((*it)->nwPos), cost); + startTrap = false; + } + } + if (startTrap) { + // do again, without annnotations, return TRAPPED if all else goes well + aMap->clearLocalAnnotations(unit); + bool locked = true; + for (Transitions::iterator it = transitions.begin(); it != transitions.end(); ++it) { + float cost = quickSearch(unit->getCurrField(), unit->getType()->getSize(), unit->getPos(), (*it)->nwPos); + if (cost != -1.f) { + tSearchEngine->setOpen(*it, dd((*it)->nwPos), cost); + locked = false; + } + } + if (locked) { + return hsrFailed; + } + } + + // transitions to goal + transitions.clear(); + Vec2i cluster = ClusterMap::cellToCluster(dest); + clusterMap->getTransitions(cluster, unit->getCurrField(), transitions); + + nsgSearchEngine->getNeighbourFunc().setSearchCluster(cluster); + + bool goalTrap = true; + bool stillAnnotated = startCluster.dist(cluster) < 1.5; + if (!stillAnnotated) { + aMap->clearLocalAnnotations(unit); + } + if (stillAnnotated && startTrap) { + stillAnnotated = false; + } + // attempt quick path from dest to each transition, + // if successful add transition to goal set + for (Transitions::iterator it = transitions.begin(); it != transitions.end(); ++it) { + float cost = quickSearch(unit->getCurrField(), unit->getType()->getSize(), dest, (*it)->nwPos); + if (cost != -1.f) { + goalFunc.goalTransitions().insert(*it); + goalTrap = false; + } + } + if (stillAnnotated) { + aMap->clearLocalAnnotations(unit); + } + if (goalTrap) { + if (stillAnnotated) { + for (Transitions::iterator it = transitions.begin(); it != transitions.end(); ++it) { + float cost = quickSearch(unit->getCurrField(), unit->getType()->getSize(), dest, (*it)->nwPos); + if (cost != -1.f) { + goalFunc.goalTransitions().insert(*it); + } + } + if (goalFunc.goalTransitions().empty()) { + return hsrFailed; + } + } + } + return startTrap ? hsrStartTrap + : goalTrap ? hsrGoalTrap + : hsrComplete; +} + +HAAStarResult RoutePlanner::findWaypointPath(Unit *unit, const Vec2i &dest, WaypointPath &waypoints) { + TransitionGoal goal; + HAAStarResult setupResult = setupHierarchicalSearch(unit, dest, goal); + nsgSearchEngine->getNeighbourFunc().setSearchSpace(ssCellMap); + if (setupResult == hsrFailed) { + return hsrFailed; + } + TransitionCost cost(unit->getCurrField(), unit->getType()->getSize()); + TransitionHeuristic heuristic(dest); + AStarResult res = tSearchEngine->aStar(goal,cost,heuristic); + if (res == asrComplete) { + WaypointPath &wpPath = *unit->getWaypointPath(); + wpPath.clear(); + waypoints.push(dest); + const Transition *t = tSearchEngine->getGoalPos(); + while (t) { + waypoints.push(t->nwPos); + t = tSearchEngine->getPreviousPos(t); + } + return setupResult; + } + return hsrFailed; +} + +/** refine waypoint path, extend low level path to next waypoint. + * @return true if successful, in which case waypoint will have been popped. + * false on failure, in which case waypoint will not be popped. */ +bool RoutePlanner::refinePath(Unit *unit) { + WaypointPath &wpPath = *unit->getWaypointPath(); + UnitPath &path = *unit->getPath(); + assert(!wpPath.empty()); + + const Vec2i &startPos = path.empty() ? unit->getPos() : path.back(); + const Vec2i &destPos = wpPath.front(); + AnnotatedMap *aMap = world->getCartographer()->getAnnotatedMap(unit); + + MoveCost cost(unit, aMap); + DiagonalDistance dd(destPos); + PosGoal posGoal(destPos); + + nsgSearchEngine->setStart(startPos, dd(startPos)); + AStarResult res = nsgSearchEngine->aStar(posGoal, cost, dd); + if (res != asrComplete) { + return false; + } +// IF_DEBUG_EDITION( collectOpenClosed(nsgSearchEngine->getStorage()); ) + // extract path + Vec2i pos = nsgSearchEngine->getGoalPos(); + assert(pos == destPos); + list::iterator it = path.end(); + while (pos.x != -1) { + it = path.insert(it, pos); + pos = nsgSearchEngine->getPreviousPos(pos); + } + // erase start point (already on path or is start pos) + it = path.erase(it); + // pop waypoint + wpPath.pop(); + return true; +} + +#undef max + +void RoutePlanner::smoothPath(Unit *unit) { + if (unit->getPath()->size() < 3) { + return; + } + AnnotatedMap* const &aMap = world->getCartographer()->getMasterMap(); + int min_x = 1 << 17, + max_x = -1, + min_y = 1 << 17, + max_y = -1; + set onPath; + UnitPath::iterator it = unit->getPath()->begin(); + for ( ; it != unit->getPath()->end(); ++it) { + if (it->x < min_x) min_x = it->x; + if (it->x > max_x) max_x = it->x; + if (it->y < min_y) min_y = it->y; + if (it->y > max_y) max_y = it->y; + onPath.insert(*it); + } + Rect2i bounds(min_x, min_y, max_x + 1, max_y + 1); + + it = unit->getPath()->begin(); + UnitPath::iterator nit = it; + ++nit; + + while (nit != unit->getPath()->end()) { + onPath.erase(*it); + Vec2i sp = *it; + for (int d = 0; d < odCount; ++d) { + Vec2i p = *it + OrdinalOffsets[d]; + if (p == *nit) continue; + Vec2i intersect(-1); + while (bounds.isInside(p)) { + if (!aMap->canOccupy(p, unit->getType()->getSize(), unit->getCurrField())) { + break; + } + if (d % 2 == 1) { // diagonal + Vec2i off1, off2; + getDiags(p - OrdinalOffsets[d], p, unit->getType()->getSize(), off1, off2); + if (!aMap->canOccupy(off1, 1, unit->getCurrField()) + || !aMap->canOccupy(off2, 1, unit->getCurrField())) { + break; + } + } + if (onPath.find(p) != onPath.end()) { + intersect = p; + break; + } + p += OrdinalOffsets[d]; + } + if (intersect != Vec2i(-1)) { + UnitPath::iterator eit = nit; + while (*eit != intersect) { + onPath.erase(*eit++); + } + nit = unit->getPath()->erase(nit, eit); + sp += OrdinalOffsets[d]; + while (sp != intersect) { + unit->getPath()->insert(nit, sp); + onPath.insert(sp); // do we need this? Can these get us further hits ?? + sp += OrdinalOffsets[d]; + } + break; // break foreach direction + } // if shortcut + } // foreach direction + nit = ++it; + ++nit; + } +} + +TravelState RoutePlanner::doRouteCache(Unit *unit) { + UnitPath &path = *unit->getPath(); + WaypointPath &wpPath = *unit->getWaypointPath(); + assert(unit->getPos().dist(path.front()) < 1.5f); + if (attemptMove(unit)) { + if (!wpPath.empty() && path.size() < 12) { + // if there are less than 12 steps left on this path, and there are more waypoints +// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), wpPath.back()); ) + while (!wpPath.empty() && path.size() < 24) { + // refine path to at least 24 steps (or end of path) + if (!refinePath(unit)) { + CONSOLE_LOG( "refinePath() failed. [route cache]" ) + wpPath.clear(); + break; + } + } + smoothPath(unit); +// IF_DEBUG_EDITION( collectPath(unit); ) + } + return tsMoving; + } + // path blocked, quickSearch to next waypoint... +// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), wpPath.empty() ? path.back() : wpPath.front()); ) + if (repairPath(unit) && attemptMove(unit)) { +// IF_DEBUG_EDITION( collectPath(unit); ) + return tsMoving; + } + return tsBlocked; +} + +TravelState RoutePlanner::doQuickPathSearch(Unit *unit, const Vec2i &target) { + AnnotatedMap *aMap = world->getCartographer()->getAnnotatedMap(unit); + UnitPath &path = *unit->getPath(); +// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), target); ) + aMap->annotateLocal(unit); + float cost = quickSearch(unit->getCurrField(), unit->getType()->getSize(), unit->getPos(), target); + aMap->clearLocalAnnotations(unit); +// IF_DEBUG_EDITION( collectOpenClosed(nodeStore); ) + if (cost != -1.f) { + Vec2i pos = nsgSearchEngine->getGoalPos(); + while (pos.x != -1) { + path.push_front(pos); + pos = nsgSearchEngine->getPreviousPos(pos); + } + if (path.size() > 1) { + path.pop(); + if (attemptMove(unit)) { +// IF_DEBUG_EDITION( collectPath(unit); ) + return tsMoving; + } + } + path.clear(); + } + return tsBlocked; +} + +TravelState RoutePlanner::findAerialPath(Unit *unit, const Vec2i &targetPos) { + AnnotatedMap *aMap = world->getCartographer()->getMasterMap(); + UnitPath &path = *unit->getPath(); + PosGoal goal(targetPos); + MoveCost cost(unit, aMap); + DiagonalDistance dd(targetPos); + + nsgSearchEngine->setNodeLimit(256); + nsgSearchEngine->setStart(unit->getPos(), dd(unit->getPos())); + + aMap->annotateLocal(unit); + AStarResult res = nsgSearchEngine->aStar(goal, cost, dd); + aMap->clearLocalAnnotations(unit); + if (res == asrComplete || res == asrNodeLimit) { + Vec2i pos = nsgSearchEngine->getGoalPos(); + while (pos.x != -1) { + path.push_front(pos); + pos = nsgSearchEngine->getPreviousPos(pos); + } + if (path.size() > 1) { + path.pop(); + if (attemptMove(unit)) { + return tsMoving; + } + } else { + path.clear(); + } + } + path.incBlockCount(); + return tsBlocked; +} + +/** Find a path to a location. + * @param unit the unit requesting the path + * @param finalPos the position the unit desires to go to + * @return ARRIVED, MOVING, BLOCKED or IMPOSSIBLE + */ +TravelState RoutePlanner::findPathToLocation(Unit *unit, const Vec2i &finalPos) { + UnitPath &path = *unit->getPath(); + WaypointPath &wpPath = *unit->getWaypointPath(); + + // if arrived (where we wanted to go) + if(finalPos == unit->getPos()) { + unit->setCurrSkill(scStop); + return tsArrived; + } + // route cache + if (!path.empty()) { + if (doRouteCache(unit) == tsMoving) { + return tsMoving; + } + } + // route cache miss or blocked + const Vec2i &target = computeNearestFreePos(unit, finalPos); + + // if arrived (as close as we can get to it) + if (target == unit->getPos()) { + unit->setCurrSkill(scStop); + return tsArrived; + } + path.clear(); + wpPath.clear(); + + if (unit->getCurrField() == fAir) { + return findAerialPath(unit, target); + } + + // QuickSearch if close to target + Vec2i startCluster = ClusterMap::cellToCluster(unit->getPos()); + Vec2i destCluster = ClusterMap::cellToCluster(target); + if (startCluster.dist(destCluster) < 3.f) { + if (doQuickPathSearch(unit, target) == tsMoving) { + return tsMoving; + } + } + // Hierarchical Search + tSearchEngine->reset(); + HAAStarResult res = findWaypointPath(unit, target, wpPath); + if (res == hsrFailed) { + if (unit->getFaction()->getThisFaction()) { + world->getGame()->getConsole()->addLine(Lang::getInstance().get("ImpossibleRoute")); + //g_console.addLine("Can not reach destination."); + } + return tsImpossible; + } else if (res == hsrStartTrap) { + if (wpPath.size() < 2) { + CONSOLE_LOG( "START_TRAP" ); + return tsBlocked; + } + } + +// IF_DEBUG_EDITION( collectWaypointPath(unit); ) + //CONSOLE_LOG( "WaypointPath size : " + intToStr(wpPath.size()) ) + //TODO post process, scan wpPath, if prev.dist(pos) < 4 cull prev + assert(wpPath.size() > 1); + wpPath.pop(); +// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), target); ) + // refine path, to at least 20 steps (or end of path) + AnnotatedMap *aMap = world->getCartographer()->getMasterMap(); + aMap->annotateLocal(unit); + wpPath.condense(); + while (!wpPath.empty() && path.size() < 20) { + if (!refinePath(unit)) { +// if (res == hsrStartTrap) { +// CONSOLE_LOG( "refinePath failed. [START_TRAP]" ) +// } else { +// CONSOLE_LOG( "refinePath failed. [fresh path]" ) +// } + aMap->clearLocalAnnotations(unit); + path.incBlockCount(); + //CONSOLE_LOG( " blockCount = " + intToStr(path.getBlockCount()) ) + return tsBlocked; + } + } + smoothPath(unit); + aMap->clearLocalAnnotations(unit); +// IF_DEBUG_EDITION( collectPath(unit); ) + if (path.empty()) { + CONSOLE_LOG( "post hierarchical search failure, path empty." ); + return tsBlocked; + } + if (attemptMove(unit)) { + return tsMoving; + } + CONSOLE_LOG( "Hierarchical refined path blocked ? valid ?!?" ) + unit->setCurrSkill(scStop); + path.incBlockCount(); + return tsBlocked; +} + +TravelState RoutePlanner::customGoalSearch(PMap1Goal &goal, Unit *unit, const Vec2i &target) { + UnitPath &path = *unit->getPath(); + WaypointPath &wpPath = *unit->getWaypointPath(); + const Vec2i &start = unit->getPos(); + // setup search + MoveCost moveCost(unit->getCurrField(), unit->getType()->getSize(), world->getCartographer()->getMasterMap()); + DiagonalDistance heuristic(target); + nsgSearchEngine->setNodeLimit(512); + nsgSearchEngine->setStart(start, heuristic(start)); + + AStarResult r; + AnnotatedMap *aMap = world->getCartographer()->getMasterMap(); + aMap->annotateLocal(unit); + r = nsgSearchEngine->aStar(goal, moveCost, heuristic); + aMap->clearLocalAnnotations(unit); + if (r == asrComplete) { + Vec2i pos = nsgSearchEngine->getGoalPos(); +// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), pos); ) +// IF_DEBUG_EDITION( collectOpenClosed(nsgSearchEngine->getStorage()); ) + while (pos.x != -1) { + path.push_front(pos); + pos = nsgSearchEngine->getPreviousPos(pos); + } + if (!path.empty()) path.pop(); +// IF_DEBUG_EDITION( collectPath(unit); ) + if (attemptMove(unit)) { + return tsMoving; + } + path.clear(); + } + return tsBlocked; +} + +TravelState RoutePlanner::findPathToGoal(Unit *unit, PMap1Goal &goal, const Vec2i &target) { + UnitPath &path = *unit->getPath(); + WaypointPath &wpPath = *unit->getWaypointPath(); + + // if at goal + if (goal(unit->getPos(), 0.f)) { + unit->setCurrSkill(scStop); + return tsArrived; + } + // route chache + if (!path.empty()) { + if (doRouteCache(unit) == tsMoving) { + return tsMoving; + } + path.clear(); + wpPath.clear(); + } + // try customGoalSearch if close to target + if (unit->getPos().dist(target) < 50.f) { + if (customGoalSearch(goal, unit, target) == tsMoving) { + return tsMoving; + } else { + return tsBlocked; + } + } + // Hierarchical Search + tSearchEngine->reset(); + if (!findWaypointPath(unit, target, wpPath)) { + if (unit->getFaction()->getThisFaction()) { + CONSOLE_LOG( "Destination unreachable? [Custom Goal Search]" ) + } + return tsImpossible; + } +// IF_DEBUG_EDITION( collectWaypointPath(unit); ) + assert(wpPath.size() > 1); + wpPath.pop(); +// IF_DEBUG_EDITION( clearOpenClosed(unit->getPos(), target); ) + // cull destination and waypoints close to it, when we get to the last remaining + // waypoint we'll do a 'customGoalSearch' to the target + while (wpPath.size() > 1 && wpPath.back().dist(target) < 32.f) { + wpPath.pop_back(); + } + // refine path, to at least 20 steps (or end of path) + AnnotatedMap *aMap = world->getCartographer()->getMasterMap(); + aMap->annotateLocal(unit); + while (!wpPath.empty() && path.size() < 20) { + if (!refinePath(unit)) { + CONSOLE_LOG( "refinePath failed! [Custom Goal Search]" ) + aMap->clearLocalAnnotations(unit); + return tsBlocked; + } + } + smoothPath(unit); + aMap->clearLocalAnnotations(unit); +// IF_DEBUG_EDITION( collectPath(unit); ) + if (attemptMove(unit)) { + return tsMoving; + } + CONSOLE_LOG( "Hierarchical refined path blocked ? valid ?!? [Custom Goal Search]" ) + unit->setCurrSkill(scStop); + return tsBlocked; +} + +/** repair a blocked path + * @param unit unit whose path is blocked + * @return true if repair succeeded */ +bool RoutePlanner::repairPath(Unit *unit) { + UnitPath &path = *unit->getPath(); + WaypointPath &wpPath = *unit->getWaypointPath(); + + Vec2i dest; + if (path.size() < 10 && !wpPath.empty()) { + dest = wpPath.front(); + } else { + dest = path.back(); + } + path.clear(); + + AnnotatedMap *aMap = world->getCartographer()->getAnnotatedMap(unit); + aMap->annotateLocal(unit); + if (quickSearch(unit->getCurrField(), unit->getType()->getSize(), unit->getPos(), dest) != -1.f) { + Vec2i pos = nsgSearchEngine->getGoalPos(); + while (pos.x != -1) { + path.push_front(pos); + pos = nsgSearchEngine->getPreviousPos(pos); + } + if (path.size() > 2) { + path.pop(); + if (!wpPath.empty() && wpPath.front() == path.back()) { + wpPath.pop(); + } + } else { + path.clear(); + } + } + aMap->clearLocalAnnotations(unit); + if (!path.empty()) { +// IF_DEBUG_EDITION ( +// collectOpenClosed(nsgSearchEngine->getStorage()); +// collectPath(unit); +// ) + return true; + } + return false; +} + +#if _GAE_DEBUG_EDITION_ + +TravelState RoutePlanner::doFullLowLevelAStar(Unit *unit, const Vec2i &dest) { + UnitPath &path = *unit->getPath(); + WaypointPath &wpPath = *unit->getWaypointPath(); + const Vec2i &target = computeNearestFreePos(unit, dest); + // if arrived (as close as we can get to it) + if (target == unit->getPos()) { + unit->setCurrSkill(scStop); + return tsArrived; + } + path.clear(); + wpPath.clear(); + + // Low Level Search with NodeMap (this is for testing purposes only, not node limited) + + // this is the 'straight' A* using the NodeMap (no node limit) + AnnotatedMap *aMap = g_world.getCartographer()->getAnnotatedMap(unit); + SearchEngine *se = g_world.getCartographer()->getSearchEngine(); + DiagonalDistance dd(target); + MoveCost cost(unit, aMap); + PosGoal goal(target); + se->setNodeLimit(-1); + se->setStart(unit->getPos(), dd(unit->getPos())); + AStarResult res = se->aStar(goal,cost,dd); + list::iterator it; + IF_DEBUG_EDITION ( + list *nodes = NULL; + NodeMap* nm = se->getStorage(); + ) + Vec2i goalPos, pos; + switch (res) { + case asrComplete: + goalPos = se->getGoalPos(); + pos = goalPos; + while (pos.x != -1) { + path.push(pos); + pos = se->getPreviousPos(pos); + } + if (!path.empty()) path.pop(); + IF_DEBUG_EDITION ( + collectOpenClosed(se->getStorage()); + collectPath(unit); + ) + break; // case asrComplete + + case AStarResult::FAILURE: + return tsImpossible; + + default: + throw runtime_error("Something that shouldn't have happened, did happen :("); + } + if (path.empty()) { + unit->setCurrSkill(scStop); + return tsArrived; + } + if (attemptMove(unit)) return tsMoving; // should always succeed (if local annotations were applied) + unit->setCurrSkill(scStop); + path.incBlockCount(); + return tsBlocked; +} + +#endif // _GAE_DEBUG_EDITION_ + +// ==================== PRIVATE ==================== + +// return finalPos if free, else a nearest free pos within maxFreeSearchRadius +// cells, or unit's current position if none found +// +/** find nearest free position a unit can occupy + * @param unit the unit in question + * @param finalPos the position unit wishes to be + * @return finalPos if free and occupyable by unit, else the closest such position, or the unit's + * current position if none found + * @todo reimplement with Dijkstra search + */ +Vec2i RoutePlanner::computeNearestFreePos(const Unit *unit, const Vec2i &finalPos) { + //unit data + Vec2i unitPos= unit->getPos(); + int size= unit->getType()->getSize(); + Field field = unit->getCurrField(); + int teamIndex= unit->getTeam(); + + //if finalPos is free return it + if (world->getMap()->isAproxFreeCells(finalPos, size, field, teamIndex)) { + return finalPos; + } + + //find nearest pos + Vec2i nearestPos = unitPos; + float nearestDist = unitPos.dist(finalPos); + for (int i = -maxFreeSearchRadius; i <= maxFreeSearchRadius; ++i) { + for (int j = -maxFreeSearchRadius; j <= maxFreeSearchRadius; ++j) { + Vec2i currPos = finalPos + Vec2i(i, j); + if (world->getMap()->isAproxFreeCells(currPos, size, field, teamIndex)) { + float dist = currPos.dist(finalPos); + + //if nearer from finalPos + if (dist < nearestDist) { + nearestPos = currPos; + nearestDist = dist; + //if the distance is the same compare distance to unit + } else if (dist == nearestDist) { + if (currPos.dist(unitPos) < nearestPos.dist(unitPos)) { + nearestPos = currPos; + } + } + } + } + } + return nearestPos; +} + +}} // end namespace Glest::Game::Search + diff --git a/source/glest_game/ai/route_planner.h b/source/glest_game/ai/route_planner.h new file mode 100644 index 00000000..0dd28ad3 --- /dev/null +++ b/source/glest_game/ai/route_planner.h @@ -0,0 +1,261 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2001-2008 Martiņo Figueroa +// 2009-2010 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#ifndef _GLEST_GAME_ROUTEPLANNER_H_ +#define _GLEST_GAME_ROUTEPLANNER_H_ + +#include "game_constants.h" +#include "influence_map.h" +#include "annotated_map.h" +#include "cluster_map.h" +#include "config.h" +#include "profiler.h" + +#include "search_engine.h" +#include "cartographer.h" +#include "node_pool.h" + +#include "world.h" +#include "types.h" + +using Shared::Graphics::Vec2i; +using Shared::Platform::uint32; + +namespace Glest { namespace Game { + +/** maximum radius to search for a free position from a target position */ +const int maxFreeSearchRadius = 10; +/** @deprecated not in use */ +const int pathFindNodesMax = 2048; + +typedef SearchEngine TransitionSearchEngine; + +class PMap1Goal { +protected: + PatchMap<1> *pMap; + +public: + PMap1Goal(PatchMap<1> *pMap) : pMap(pMap) {} + + bool operator()(const Vec2i &pos, const float) const { + if (pMap->getInfluence(pos)) { + return true; + } + return false; + } +}; + +/** gets the two 'diagonal' cells to check for obstacles when a unit is moving diagonally + * @param s start pos + * @param d destination pos + * @param size size of unit + * @return d1 & d2, the two cells to check + * @warning assumes s & d are indeed diagonal, abs(s.x - d.x) == 1 && abs(s.y - d.y) == 1 + */ +__inline void getDiags(const Vec2i &s, const Vec2i &d, const int size, Vec2i &d1, Vec2i &d2) { +# define _SEARCH_ENGINE_GET_DIAGS_DEFINED_ + assert(abs(s.x - d.x) == 1 && abs(s.y - d.y) == 1); + if (size == 1) { + d1.x = s.x; d1.y = d.y; + d2.x = d.x; d2.y = s.y; + return; + } + if (d.x > s.x) { // travelling east + if (d.y > s.y) { // se + d1.x = d.x + size - 1; d1.y = s.y; + d2.x = s.x; d2.y = d.y + size - 1; + } else { // ne + d1.x = s.x; d1.y = d.y; + d2.x = d.x + size - 1; d2.y = s.y - size + 1; + } + } else { // travelling west + if (d.y > s.y) { // sw + d1.x = d.x; d1.y = s.y; + d2.x = s.x + size - 1; d2.y = d.y + size - 1; + } else { // nw + d1.x = d.x; d1.y = s.y - size + 1; + d2.x = s.x + size - 1; d2.y = d.y; + } + } +} + +/** The movement cost function */ +class MoveCost { +private: + const int size; /**< size of agent */ + const Field field; /**< field to search in */ + const AnnotatedMap *aMap; /**< map to search on */ + +public: + MoveCost(const Unit *unit, const AnnotatedMap *aMap) + : size(unit->getType()->getSize()), field(unit->getCurrField()), aMap(aMap) {} + MoveCost(const Field field, const int size, const AnnotatedMap *aMap ) + : size(size), field(field), aMap(aMap) {} + + /** The cost function @param p1 position 1 @param p2 position 2 ('adjacent' p1) + * @return cost of move, possibly infinite */ + float operator()(const Vec2i &p1, const Vec2i &p2) const { + assert(p1.dist(p2) < 1.5 && p1 != p2); + assert(g_map.isInside(p2)); + if (!aMap->canOccupy(p2, size, field)) { + return -1.f; + } + if (p1.x != p2.x && p1.y != p2.y) { + Vec2i d1, d2; + getDiags(p1, p2, size, d1, d2); + assert(g_map.isInside(d1) && g_map.isInside(d2)); + if (!aMap->canOccupy(d1, 1, field) || !aMap->canOccupy(d2, 1, field) ) { + return -1.f; + } + return SQRT2; + } + return 1.f; + // todo... height + } +}; + +enum TravelState{ + tsArrived, + tsMoving, + tsBlocked, + tsImpossible +}; + +enum HAAStarResult { + hsrFailed, + hsrComplete, + hsrStartTrap, + hsrGoalTrap +}; + +// ===================================================== +// class RoutePlanner +// ===================================================== +/** Finds paths for units using SearchEngine<>::aStar<>() */ +class RoutePlanner { +public: + RoutePlanner(World *world); + ~RoutePlanner(); + + TravelState findPathToLocation(Unit *unit, const Vec2i &finalPos); + + /** @see findPathToLocation() */ + TravelState findPath(Unit *unit, const Vec2i &finalPos) { + return findPathToLocation(unit, finalPos); + } + + TravelState findPathToResource(Unit *unit, const Vec2i &targetPos, const ResourceType *rt) { + assert(rt->getClass() == rcTechTree || rt->getClass() == rcTileset); + ResourceMapKey mapKey(rt, unit->getCurrField(), unit->getType()->getSize()); + PMap1Goal goal(world->getCartographer()->getResourceMap(mapKey)); + return findPathToGoal(unit, goal, targetPos); + } + + TravelState findPathToStore(Unit *unit, const Unit *store) { + Vec2i target = store->getCenteredPos(); + PMap1Goal goal(world->getCartographer()->getStoreMap(store, unit)); + return findPathToGoal(unit, goal, target); + } + + TravelState findPathToBuildSite(Unit *unit, const UnitType *buildingType, const Vec2i &buildingPos) { + PMap1Goal goal(world->getCartographer()->getSiteMap(buildingType, buildingPos, unit)); + return findPathToGoal(unit, goal, unit->getTargetPos()); + } + + bool isLegalMove(Unit *unit, const Vec2i &pos) const; + + SearchEngine* getSearchEngine() { return nsgSearchEngine; } + +private: + bool repairPath(Unit *unit); + + TravelState findAerialPath(Unit *unit, const Vec2i &targetPos); + + TravelState doRouteCache(Unit *unit); + TravelState doQuickPathSearch(Unit *unit, const Vec2i &target); + + TravelState findPathToGoal(Unit *unit, PMap1Goal &goal, const Vec2i &targetPos); + TravelState customGoalSearch(PMap1Goal &goal, Unit *unit, const Vec2i &target); + + float quickSearch(Field field, int Size, const Vec2i &start, const Vec2i &dest); + bool refinePath(Unit *unit); + void smoothPath(Unit *unit); + + HAAStarResult setupHierarchicalSearch(Unit *unit, const Vec2i &dest, TransitionGoal &goalFunc); + HAAStarResult findWaypointPath(Unit *unit, const Vec2i &dest, WaypointPath &waypoints); + + World *world; + SearchEngine *nsgSearchEngine; + NodePool *nodeStore; + TransitionSearchEngine *tSearchEngine; + TransitionNodeStore *tNodeStore; + + Vec2i computeNearestFreePos(const Unit *unit, const Vec2i &targetPos); + + bool attemptMove(Unit *unit) const { + assert(!unit->getPath()->empty()); + Vec2i pos = unit->getPath()->peek(); + if (isLegalMove(unit, pos)) { + unit->setTargetPos(pos); + unit->getPath()->pop(); + return true; + } + return false; + } +#if _GAE_DEBUG_EDITION_ + TravelState doFullLowLevelAStar(Unit *unit, const Vec2i &dest); +#endif +#if DEBUG_SEARCH_TEXTURES +public: + enum { SHOW_PATH_ONLY, SHOW_OPEN_CLOSED_SETS, SHOW_LOCAL_ANNOTATIONS } debug_texture_action; +#endif +}; // class RoutePlanner + + +//TODO: put these somewhere sensible +class TransitionHeuristic { + DiagonalDistance dd; +public: + TransitionHeuristic(const Vec2i &target) : dd(target) {} + bool operator()(const Transition *t) const { + return dd(t->nwPos); + } +}; + +#if 0 == 1 +// +// just use DiagonalDistance to waypoint ?? +class AbstractAssistedHeuristic { +public: + AbstractAssistedHeuristic(const Vec2i &target, const Vec2i &waypoint, float wpCost) + : target(target), waypoint(waypoint), wpCost(wpCost) {} + /** search target */ + Vec2i target, waypoint; + float wpCost; + /** The heuristic function. + * @param pos the position to calculate the heuristic for + * @return an estimate of the cost to target + */ + float operator()(const Vec2i &pos) const { + float dx = (float)abs(pos.x - waypoint.x), + dy = (float)abs(pos.y - waypoint.y); + float diag = dx < dy ? dx : dy; + float straight = dx + dy - 2 * diag; + return 1.4 * diag + straight + wpCost; + + } +}; +#endif + +}} // namespace + +#endif diff --git a/source/glest_game/ai/search_engine.h b/source/glest_game/ai/search_engine.h new file mode 100644 index 00000000..5775ecba --- /dev/null +++ b/source/glest_game/ai/search_engine.h @@ -0,0 +1,287 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== +// +// search_engine.h + +#ifndef _GLEST_GAME_SEARCH_ENGINE_ +#define _GLEST_GAME_SEARCH_ENGINE_ + +#include "math_util.h" +#include "game_constants.h" + +/** Home of the templated A* algorithm and some of the bits that plug into it.*/ +namespace Glest { namespace Game { + +using Shared::Graphics::Vec2i; + +static const float SQRT2 = Shared::Graphics::sqrt2; + +#include "search_functions.inl" + +enum OrdinalDir { + odNorth, odNorthEast, odEast, odSouthEast, odSouth, odSouthWest, odWest, odNorthWest, odCount +}; + +const Vec2i OrdinalOffsets[odCount] = { + Vec2i( 0, -1), // n + Vec2i( 1, -1), // ne + Vec2i( 1, 0), // e + Vec2i( 1, 1), // se + Vec2i( 0, 1), // s + Vec2i(-1, 1), // sw + Vec2i(-1, 0), // w + Vec2i(-1, -1) // nw +}; + +/* + // NodeStorage template interface + // + class NodeStorage { + public: + void reset(); + void setMaxNode( int limit ); + + bool isOpen( const Vec2i &pos ); + bool isClosed( const Vec2i &pos ); + + bool setOpen( const Vec2i &pos, const Vec2i &prev, float h, float d ); + void updateOpen( const Vec2i &pos, const Vec2i &prev, const float cost ); + Vec2i getBestCandidate(); + Vec2i getBestSeen(); + + float getHeuristicAt( const Vec2i &pos ); + float getCostTo( const Vec2i &pos ); + float getEstimateFor( const Vec2i &pos ); + Vec2i getBestTo( const Vec2i &pos ); + }; +*/ + +enum SearchSpace { ssCellMap, ssTileMap }; + +/** Neighbour function for 'octile grid map' as search domain */ +class GridNeighbours { +private: + int x, y; + int width, height; + const int cellWidth, cellHeight; + +public: + GridNeighbours(int cellWidth, int cellHeight) + : x(0), y(0) + , width(cellWidth) + , height(cellHeight) + , cellWidth(cellWidth) + , cellHeight(cellHeight) { + assert(cellWidth % GameConstants::cellScale == 0); + assert(cellHeight % GameConstants::cellScale == 0); + } + + void operator()(Vec2i &pos, vector &neighbours) const { + for (int i = 0; i < odCount; ++i) { + Vec2i nPos = pos + OrdinalOffsets[i]; + if (nPos.x >= x && nPos.x < x + width && nPos.y >= y && nPos.y < y + height) { + neighbours.push_back(nPos); + } + } + } + /** Kludge to search on Cellmap or Tilemap... templated search domain should deprecate this */ + void setSearchSpace(SearchSpace s) { + if (s == ssCellMap) { + x = y = 0; + width = cellWidth; + height = cellHeight; + } else if (s == ssTileMap) { + x = y = 0; + width = cellWidth / GameConstants::cellScale; + height= cellHeight / GameConstants::cellScale; + } + } + + /** more kludgy search restriction stuff... */ + void setSearchCluster(Vec2i cluster) { + x = cluster.x * GameConstants::clusterSize - 1; + if (x < 0) x = 0; + y = cluster.y * GameConstants::clusterSize - 1; + if (y < 0) y = 0; + width = GameConstants::clusterSize + 1; + height = GameConstants::clusterSize + 1; + } +}; + +enum AStarResult { + asrFailed, asrComplete, asrNodeLimit, asrTimeLimit +}; + +// ======================================================== +// class SearchEngine +// ======================================================== +/** Generic (template) A* + * @param NodeStorage NodeStorage class to use, must conform to implicit interface, see elsewhere + * @param NeighbourFunc the function to generate neighbours for a search domain + * @param DomainKey the key type of the search domain + */ +template< typename NodeStorage, typename NeighbourFunc = GridNeighbours, typename DomainKey = Vec2i > +class SearchEngine { +private: + NodeStorage *nodeStorage;/**< NodeStorage for this SearchEngine */ + DomainKey goalPos; /**< The goal pos (the 'result') from the last A* search */ + DomainKey invalidKey; /**< The DomainKey value indicating an invalid 'position' */ + int expandLimit, /**< limit on number of nodes to expand */ + nodeLimit, /**< limit on number of nodes to use */ + expanded; /**< number of nodes expanded this/last run */ + bool ownStore; /**< wether or not this SearchEngine 'owns' its storage */ + NeighbourFunc neighbourFunc; + +public: + /** construct & initialise NodeStorage + * @param store NodeStorage to use + * @param own wether the SearchEngine should own the storage + */ + SearchEngine(NeighbourFunc neighbourFunc, NodeStorage *store = 0, bool own=false) + : nodeStorage(store) + , expandLimit(-1) + , nodeLimit(-1) + , expanded(0) + , ownStore(own) + , neighbourFunc(neighbourFunc) { + } + + /** Detruct, will delete storage if ownStore was set true */ + ~SearchEngine() { + if (ownStore) { + delete nodeStorage; + } + } + + /** Attach NodeStorage + * @param store NodeStorage to use + * @param own wether the SearchEngine should own the storage + */ + void setStorage(NodeStorage *store, bool own=false) { + nodeStorage = store; + ownStore = own; + } + + /** Set the DomainKey value indicating an invalid position, this must be set before use! + * @param key the invalid DomainKey value + */ + void setInvalidKey(DomainKey key) { invalidKey = key; } + + /** @return a pointer to this engine's node storage */ + NodeStorage* getStorage() { return nodeStorage; } + + NeighbourFunc& getNeighbourFunc() { return neighbourFunc; } + + /** Reset the node storage */ + void reset() { nodeStorage->reset(); nodeStorage->setMaxNodes(nodeLimit > 0 ? nodeLimit : -1); } + + /** Add a position to the open set with 'd' cost to here and invalid prev (a start position) + * @param pos position to set as open + * @param h heuristc, estimate to goal from pos + * @param d distance or cost to node (optional, defaults to 0) + */ + void setOpen(DomainKey pos, float h, float d = 0.f) { + nodeStorage->setOpen(pos, invalidKey, h, d); + } + + /** Reset the node storage and add pos to open + * @param pos position to set as open + * @param h heuristc, estimate to goal from pos + * @param d distance or cost to node (optional, defaults to 0) + */ + void setStart(DomainKey pos, float h, float d = 0.f) { + nodeStorage->reset(); + if ( nodeLimit > 0 ) { + nodeStorage->setMaxNodes(nodeLimit); + } + nodeStorage->setOpen(pos, invalidKey, h, d); + } + + /** Retrieve the goal of last search, position of last goal reached */ + DomainKey getGoalPos() { return goalPos; } + + /** Best path to pos is from */ + DomainKey getPreviousPos(const DomainKey &pos) { return nodeStorage->getBestTo(pos); } + + /** limit search to use at most limit nodes */ + void setNodeLimit(int limit) { nodeLimit = limit > 0 ? limit : -1; } + + /** set an 'expanded nodes' limit, for a resumable search */ + void setTimeLimit(int limit) { expandLimit = limit > 0 ? limit : -1; } + + /** How many nodes were expanded last search */ + int getExpandedLastRun() { return expanded; } + + /** Retrieves cost to the node at pos (known to be visited) */ + float getCostTo(const DomainKey &pos) { + assert(nodeStorage->isOpen(pos) || nodeStorage->isClosed(pos)); + return nodeStorage->getCostTo(pos); + } + + /** A* Algorithm (Just the loop, does not do any setup or post-processing) + * @param GoalFunc template parameter Goal function class + * @param CostFunc template parameter Cost function class + * @param Heuristic template parameter Heuristic function class + * @param goalFunc goal function object + * @param costFunc cost function object + * @param heuristic heuristic function object + */ + template< typename GoalFunc, typename CostFunc, typename Heuristic > + AStarResult aStar(GoalFunc goalFunc, CostFunc costFunc, Heuristic heuristic) { + expanded = 0; + DomainKey minPos(invalidKey); + vector neighbours; + while (true) { + // get best open + minPos = nodeStorage->getBestCandidate(); + if (minPos == invalidKey) { // failure + goalPos = invalidKey; + return asrFailed; + } + if (goalFunc(minPos, nodeStorage->getCostTo(minPos))) { // success + goalPos = minPos; + return asrComplete; + } + // expand it... + neighbourFunc(minPos, neighbours); + while (!neighbours.empty()) { + DomainKey nPos = neighbours.back(); + neighbours.pop_back(); + if (nodeStorage->isClosed(nPos)) { + continue; + } + float cost = costFunc(minPos, nPos); + if (cost == -1.f) { + continue; + } + if (nodeStorage->isOpen(nPos)) { + nodeStorage->updateOpen(nPos, minPos, cost); + } else { + const float &costToMin = nodeStorage->getCostTo(minPos); + if (!nodeStorage->setOpen(nPos, minPos, heuristic(nPos), costToMin + cost)) { + goalPos = nodeStorage->getBestSeen(); + return asrNodeLimit; + } + } + } + expanded++; + if (expanded == expandLimit) { // run limit + goalPos = invalidKey; + return asrTimeLimit; + } + } + return asrFailed; // impossible... just keeping the compiler from complaining + } +}; + +}} + +#endif diff --git a/source/glest_game/ai/search_functions.inl b/source/glest_game/ai/search_functions.inl new file mode 100644 index 00000000..18923239 --- /dev/null +++ b/source/glest_game/ai/search_functions.inl @@ -0,0 +1,155 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== +// +// search_functions.inl +// +// INLINE FILE, do not place any silly namespaces or what-not in here, this +// is a part of search_engine.h +// + +/** Goal function for 'normal' search */ +class PosGoal { +private: + Vec2i target; /**< search target */ + +public: + PosGoal(const Vec2i &target) : target(target) {} + + /** The goal function @param pos position to test + * @param costSoFar the cost of the shortest path to pos + * @return true if pos is target, else false */ + bool operator()(const Vec2i &pos, const float costSoFar) const { + return pos == target; + } +}; + +/** Goal function for 'get within x of' searches */ +class RangeGoal { +private: + Vec2i target; /**< search target */ + float range; /**< range to get within */ + +public: + RangeGoal(const Vec2i &target, float range) : target(target), range(range) {} + + /** The goal function @param pos position to test + * @param costSoFar the cost of the shortest path to pos + * @return true if pos is within range of target, else false */ + bool operator()(const Vec2i &pos, const float costSoFar) const { + return pos.dist(target) <= range; + } +}; + +/** The 'No Goal' function. Just returns false. Use with care! Use Cost function to control termination + * by exhausting the open list. */ +class NoGoal { +public: + NoGoal(){} + /** The goal function @param pos the position to ignore + * @param costSoFar the cost of the shortest path to pos + * @return false */ + bool operator()(const Vec2i &pos, const float costSoFar) const { + return false; + } +}; + +/** A uniform cost function */ +class UniformCost { +private: + float cost; /**< The uniform cost to return */ + +public: + UniformCost(float cost) : cost(cost) {} + + /** The cost function @param p1 position 1 @param p2 position 2 ('adjacent' p1) + * @return cost */ + float operator()(const Vec2i &p1, const Vec2i &p2) const { return cost; } +}; + +/** distance cost, no obstacle checks */ +class DistanceCost { +public: + DistanceCost(){} + /** The cost function @param p1 position 1 @param p2 position 2 ('adjacent' p1) + * @return 1.0 if p1 and p2 are 'in line', else SQRT2 */ + float operator()(const Vec2i &p1, const Vec2i &p2) const { + assert ( p1.dist(p2) < 1.5 ); + if ( p1.x != p2.x && p1.y != p2.y ) { + return SQRT2; + } + return 1.0f; + } +}; + +/** Diaginal Distance Heuristic */ +class DiagonalDistance { +public: + DiagonalDistance(const Vec2i &target) : target(target) {} + /** search target */ + Vec2i target; + /** The heuristic function. @param pos the position to calculate the heuristic for + * @return an estimate of the cost to target */ + float operator()(const Vec2i &pos) const { + float dx = (float)abs(pos.x - target.x), + dy = (float)abs(pos.y - target.y); + float diag = dx < dy ? dx : dy; + float straight = dx + dy - 2 * diag; + return 1.4 * diag + straight; + } +}; + +/** Diagonal Distance Overestimating Heuristic */ +class OverEstimate { +private: + Vec2i target; /**< search target */ + +public: + OverEstimate(const Vec2i &target) : target(target) {} + + /** The heuristic function. @param pos the position to calculate the heuristic for + * @return an (over) estimate of the cost to target */ + float operator()(const Vec2i &pos) const { + float dx = (float)abs(pos.x - target.x), + dy = (float)abs(pos.y - target.y); + float diag = dx < dy ? dx : dy; + float estimate = 1.4 * diag + (dx + dy - 2 * diag); + estimate *= 1.25; + return estimate; + } +}; + +/** Diagonal Distance underestimating Heuristic */ +class UnderEstimate { +private: + Vec2i target; /**< search target */ + +public: + UnderEstimate(const Vec2i &target) : target(target) {} + + /** The heuristic function. @param pos the position to calculate the heuristic for + * @return an (over) estimate of the cost to target */ + float operator()(const Vec2i &pos) const { + float dx = (float)abs(pos.x - target.x), + dy = (float)abs(pos.y - target.y); + float diag = dx < dy ? dx : dy; + float estimate = 1.4 * diag + (dx + dy - 2 * diag); + estimate *= 0.75; + return estimate; + } +}; + +/** The Zero Heuristic, for doing Dijkstra searches */ +class ZeroHeuristic { +public: + ZeroHeuristic(){} + /** The 'no heuristic' function. @param pos the position to ignore @return 0.f */ + float operator()(const Vec2i &pos) const { return 0.0f; } +}; diff --git a/source/glest_game/facilities/pos_iterator.cpp b/source/glest_game/facilities/pos_iterator.cpp new file mode 100644 index 00000000..af8a64b6 --- /dev/null +++ b/source/glest_game/facilities/pos_iterator.cpp @@ -0,0 +1,57 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009-2010 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#include "pos_iterator.h" + +#include "leak_dumper.h" + +namespace Glest { namespace Util { + +Vec2i PerimeterIterator::next() { + Vec2i n(cx, cy); + switch (state) { + case 0: // top edge, left->right + if (cx == ex) { + state = 1; + ++cy; + } else { + ++cx; + } + break; + case 1: // right edge, top->bottom + if (cy == sy) { + state = 2; + --cx; + } else { + ++cy; + } + break; + case 2: + if (cx == wx) { + state = 3; + --cy; + } else { + --cx; + } + break; + case 3: + if (cy == ny + 1) { + state = 4; + } else { + --cy; + } + break; + } + return n; +} + +}}//end namespace + diff --git a/source/glest_game/facilities/pos_iterator.h b/source/glest_game/facilities/pos_iterator.h new file mode 100644 index 00000000..45c6a490 --- /dev/null +++ b/source/glest_game/facilities/pos_iterator.h @@ -0,0 +1,120 @@ +// ============================================================== +// This file is part of Glest (www.glest.org) +// +// Copyright (C) 2009-2010 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#ifndef _GLEST_GAME_UTIL_POSITERATOR_H_ +#define _GLEST_GAME_UTIL_POSITERATOR_H_ + +#include + +#include "math_util.h" + +namespace Glest { namespace Util { + +using std::runtime_error; +using Shared::Graphics::Vec2i; +using Shared::Graphics::Rect2i; + +class RectIteratorBase { +protected: + int ex, wx, sy, ny; + +public: + RectIteratorBase(const Vec2i &p1, const Vec2i &p2) { + if (p1.x > p2.x) { + ex = p1.x; wx = p2.x; + } else { + ex = p2.x; wx = p1.x; + } + if (p1.y > p2.y) { + sy = p1.y; ny = p2.y; + } else { + sy = p2.y; ny = p1.y; + } + } +}; + +/** An iterator over a rectangular region that starts at the 'top-left' and proceeds left + * to right, top to bottom. */ +class RectIterator : public RectIteratorBase { +private: + int cx, cy; + +public: + RectIterator(const Rect2i &rect) + : RectIteratorBase(rect.p[0], rect.p[1]) { + cx = wx; + cy = ny; + } + + RectIterator(const Vec2i &p1, const Vec2i &p2) + : RectIteratorBase(p1, p2) { + cx = wx; + cy = ny; + } + + bool more() const { return cy <= sy; } + + Vec2i next() { + Vec2i n(cx, cy); + if (cx == ex) { + cx = wx; ++cy; + } else { + ++cx; + } + return n; + } +}; + +/** An iterator over a rectangular region that starts at the 'bottom-right' and proceeds right + * to left, bottom to top. */ +class ReverseRectIterator : public RectIteratorBase { +private: + int cx, cy; + +public: + ReverseRectIterator(const Vec2i &p1, const Vec2i &p2) + : RectIteratorBase(p1, p2) { + cx = ex; + cy = sy; + } + + bool more() const { return cy >= ny; } + + Vec2i next() { + Vec2i n(cx,cy); + if (cx == wx) { + cx = ex; cy--; + } else { + cx--; + } + return n; + } +}; + +class PerimeterIterator : public RectIteratorBase { +private: + int cx, cy; + int state; + +public: + PerimeterIterator(const Vec2i &p1, const Vec2i &p2) + : RectIteratorBase(p1, p2), state(0) { + cx = wx; + cy = ny; + } + + bool more() const { return state != 4; } + Vec2i next(); +}; + +}} // namespace Glest::Util + +#endif // _GLEST_GAME_UTIL_POSITERATOR_H_ diff --git a/source/glest_game/game/game_constants.h b/source/glest_game/game/game_constants.h index b8619726..b9a8e2d7 100644 --- a/source/glest_game/game/game_constants.h +++ b/source/glest_game/game/game_constants.h @@ -42,6 +42,9 @@ public: //static const int networkExtraLatency= 200; static const int maxClientConnectHandshakeSecs= 10; + static const int cellScale = 2; + static const int clusterSize = 16; + static const char *folder_path_maps; static const char *folder_path_scenarios; static const char *folder_path_techs; diff --git a/source/glest_game/type_instances/unit.cpp b/source/glest_game/type_instances/unit.cpp index 2cd7124a..1ef47673 100644 --- a/source/glest_game/type_instances/unit.cpp +++ b/source/glest_game/type_instances/unit.cpp @@ -38,47 +38,28 @@ namespace Glest{ namespace Game{ // class UnitPath // ===================================================== -const int UnitPath::maxBlockCount= 10; - -UnitPath::UnitPath() { - this->blockCount = 0; - this->pathQueue.clear(); -} - -bool UnitPath::isEmpty(){ - return pathQueue.empty(); -} - -bool UnitPath::isBlocked(){ - return blockCount>=maxBlockCount; -} - -void UnitPath::clear(){ - pathQueue.clear(); - blockCount= 0; -} - -void UnitPath::incBlockCount(){ - pathQueue.clear(); - blockCount++; -} - -void UnitPath::push(const Vec2i &path){ - pathQueue.push_back(path); -} - -Vec2i UnitPath::pop(){ - Vec2i p= pathQueue.front(); - pathQueue.erase(pathQueue.begin()); - return p; -} +void WaypointPath::condense() { + if (size() < 2) { + return; + } + iterator prev, curr; + prev = curr = begin(); + while (++curr != end()) { + if (prev->dist(*curr) < 3.f) { + prev = erase(prev); + } else { + ++prev; + } + } +} std::string UnitPath::toString() const { std::string result = ""; - result = "unit path blockCount = " + intToStr(blockCount) + " pathQueue size = " + intToStr(pathQueue.size()); - for(int idx = 0; idx < pathQueue.size(); idx++) { - result += " index = " + intToStr(idx) + " " + pathQueue[idx].getString(); + result = "unit path blockCount = " + intToStr(blockCount) + " pathQueue size = " + intToStr(size()); + result += " path = "; + for (const_iterator it = begin(); it != end(); ++it) { + result += " [" + intToStr(it->x) + "," + intToStr(it->y) + "]"; } return result; diff --git a/source/glest_game/type_instances/unit.h b/source/glest_game/type_instances/unit.h index 630c7a51..75c26875 100644 --- a/source/glest_game/type_instances/unit.h +++ b/source/glest_game/type_instances/unit.h @@ -97,31 +97,50 @@ public: // ===================================================== // class UnitPath -// -/// Holds the next cells of a Unit movement // ===================================================== - -class UnitPath { +/** Holds the next cells of a Unit movement + * @extends std::list + */ +class UnitPath : public list { private: - static const int maxBlockCount; + static const int maxBlockCount = 10; /**< number of command updates to wait on a blocked path */ private: - int blockCount; - vector pathQueue; + int blockCount; /**< number of command updates this path has been blocked */ public: - UnitPath(); - bool isBlocked(); - bool isEmpty(); + UnitPath() : blockCount(0) {} /**< Construct path object */ + bool isBlocked() const {return blockCount >= maxBlockCount;} /**< is this path blocked */ + bool empty() const {return list::empty();} /**< is path empty */ + int size() const {return list::size();} /**< size of path */ + void clear() {list::clear(); blockCount = 0;} /**< clear the path */ + void incBlockCount() {++blockCount;} /**< increment block counter */ + void push(Vec2i &pos) {push_front(pos);} /**< push onto front of path */ + +#if 1 + // old style, to work with original PathFinder + Vec2i peek() {return back();} /**< peek at the next position */ + void pop() {this->pop_back();} /**< pop the next position off the path */ +#else + // new style, for the new RoutePlanner + Vec2i peek() {return front();} /**< peek at the next position */ + void pop() {erase(begin());} /**< pop the next position off the path */ +#endif + int getBlockCount() const { return blockCount; } - void clear(); - void incBlockCount(); - void push(const Vec2i &path); - Vec2i pop(); - - std::string toString() const; + std::string UnitPath::toString() const; }; +class WaypointPath : public list { +public: + WaypointPath() {} + void push(const Vec2i &pos) { push_front(pos); } + Vec2i peek() const {return front();} + void pop() {erase(begin());} + void condense(); +}; + + // =============================== // class Unit // @@ -187,6 +206,7 @@ private: Map *map; UnitPath unitPath; + WaypointPath waypointPath; Commands commands; Observers observers; @@ -238,6 +258,7 @@ public: string getFullName() const; const UnitPath *getPath() const {return &unitPath;} UnitPath *getPath() {return &unitPath;} + WaypointPath *getWaypointPath() {return &waypointPath;} //pos Vec2i getPos() const {return pos;} @@ -315,7 +336,7 @@ public: void applyCommand(Command *command); void setModelFacing(CardinalDir value); - CardinalDir getModelFacing() { return modelFacing; } + CardinalDir getModelFacing() const { return modelFacing; } bool isMeetingPointSettable() const; diff --git a/source/glest_game/types/unit_type.cpp b/source/glest_game/types/unit_type.cpp index fbc90f18..60cbf5de 100644 --- a/source/glest_game/types/unit_type.cpp +++ b/source/glest_game/types/unit_type.cpp @@ -196,6 +196,14 @@ void UnitType::load(int id,const string &dir, const TechTree *techTree, const Fa } } + if (fields[fLand]) { + field = fLand; + } else if (fields[fAir]) { + field = fAir; + } else { + throw runtime_error("Unit has no field: " + path); + } + //properties const XmlNode *propertiesNode= parametersNode->getChild("properties"); for(int i=0; igetChildCount(); ++i){ diff --git a/source/glest_game/types/unit_type.h b/source/glest_game/types/unit_type.h index 95b4b29b..52ba1cce 100644 --- a/source/glest_game/types/unit_type.h +++ b/source/glest_game/types/unit_type.h @@ -86,7 +86,11 @@ private: int hpRegeneration; int maxEp; int epRegeneration; - bool fields[fieldCount]; //fields: land, sea or air + + ///@todo remove fields, multiple fields are not supported by the engine + bool fields[fieldCount]; //fields: land, sea or air + Field field; + bool properties[pCount]; //properties int armor; //armor const ArmorType *armorType; @@ -134,6 +138,7 @@ public: int getMaxEp() const {return maxEp;} int getEpRegeneration() const {return epRegeneration;} bool getField(Field field) const {return fields[field];} + Field getField() const {return field;} bool getProperty(Property property) const {return properties[property];} int getArmor() const {return armor;} const ArmorType *getArmorType() const {return armorType;} @@ -154,6 +159,7 @@ public: const Resource *getStoredResource(int i) const {return &storedResources[i];} bool getCellMapCell(int x, int y, CardinalDir facing) const; bool getMeetingPoint() const {return meetingPoint;} + bool isMobile() const {return firstSkillTypeOfClass[scMove];} Texture2D *getMeetingPointImage() const {return meetingPointImage;} StaticSound *getSelectionSound() const {return selectionSounds.getRandSound();} StaticSound *getCommandSound() const {return commandSounds.getRandSound();} diff --git a/source/glest_game/world/map.cpp b/source/glest_game/world/map.cpp index e40ac449..ec26b565 100644 --- a/source/glest_game/world/map.cpp +++ b/source/glest_game/world/map.cpp @@ -23,7 +23,7 @@ #include "util.h" #include "game_settings.h" #include "platform_util.h" - +#include "pos_iterator.h" #include "leak_dumper.h" using namespace Shared::Graphics; @@ -248,24 +248,23 @@ bool Map::isInsideSurface(const Vec2i &sPos) const{ } //returns if there is a resource next to a unit, in "resourcePos" is stored the relative position of the resource -bool Map::isResourceNear(const Vec2i &pos, const ResourceType *rt, Vec2i &resourcePos) const{ - for(int i=-1; i<=1; ++i){ - for(int j=-1; j<=1; ++j){ - if(isInside(pos.x+i, pos.y+j)){ - Resource *r= getSurfaceCell(toSurfCoords(Vec2i(pos.x+i, pos.y+j)))->getResource(); - if(r!=NULL){ - if(r->getType()==rt){ - resourcePos= pos + Vec2i(i,j); - return true; - } - } - } - } - } - return false; +bool Map::isResourceNear(const Vec2i &pos, int size, const ResourceType *rt, Vec2i &resourcePos) const { + Vec2i p1 = pos + Vec2i(-1); + Vec2i p2 = pos + Vec2i(size); + Util::PerimeterIterator iter(p1, p2); + while (iter.more()) { + Vec2i cur = iter.next(); + if (isInside(cur)) { + Resource *r = getSurfaceCell(toSurfCoords(cur))->getResource(); + if (r && r->getType() == rt) { + resourcePos = cur; + return true; + } + } + } + return false; } - // ==================== free cells ==================== bool Map::isFreeCell(const Vec2i &pos, Field field) const{ diff --git a/source/glest_game/world/map.h b/source/glest_game/world/map.h index 5915b192..24797fb7 100755 --- a/source/glest_game/world/map.h +++ b/source/glest_game/world/map.h @@ -196,7 +196,7 @@ public: bool isInside(const Vec2i &pos) const; bool isInsideSurface(int sx, int sy) const; bool isInsideSurface(const Vec2i &sPos) const; - bool isResourceNear(const Vec2i &pos, const ResourceType *rt, Vec2i &resourcePos) const; + bool isResourceNear(const Vec2i &pos, int size, const ResourceType *rt, Vec2i &resourcePos) const; //free cells bool isFreeCell(const Vec2i &pos, Field field) const; diff --git a/source/glest_game/world/unit_updater.cpp b/source/glest_game/world/unit_updater.cpp index 6e5d0fdd..4031dabc 100644 --- a/source/glest_game/world/unit_updater.cpp +++ b/source/glest_game/world/unit_updater.cpp @@ -379,7 +379,7 @@ void UnitUpdater::updateHarvest(Unit *unit){ if(r!=NULL && hct->canHarvest(r->getType())){ //if can harvest dest. pos if(unit->getPos().dist(command->getPos())isResourceNear(unit->getPos(), r->getType(), targetPos)) { + map->isResourceNear(unit->getPos(), unit->getType()->getSize(), r->getType(), targetPos)) { //if it finds resources it starts harvesting unit->setCurrSkill(hct->getHarvestSkillType()); unit->setTargetPos(targetPos); diff --git a/source/glest_game/world/world.h b/source/glest_game/world/world.h index 8757c43d..03bc8fa8 100644 --- a/source/glest_game/world/world.h +++ b/source/glest_game/world/world.h @@ -43,6 +43,8 @@ class Config; class Game; class GameSettings; class ScriptManager; +class Cartographer; +class RoutePlanner; // ===================================================== // class World @@ -78,6 +80,8 @@ private: RandomGen random; ScriptManager* scriptManager; + Cartographer *cartographer; + RoutePlanner *routePlanner; int thisFactionIndex; int thisTeamIndex; @@ -112,6 +116,8 @@ public: const TimeFlow *getTimeFlow() const {return &timeFlow;} Tileset *getTileset() {return &tileset;} Map *getMap() {return ↦} + Cartographer* getCartographer() {return cartographer;} + RoutePlanner* getRoutePlanner() {return routePlanner;} const Faction *getFaction(int i) const {return &factions[i];} Faction *getFaction(int i) {return &factions[i];} const Minimap *getMinimap() const {return &minimap;} diff --git a/source/shared_lib/include/graphics/vec.h b/source/shared_lib/include/graphics/vec.h index 217b3807..f0de5547 100644 --- a/source/shared_lib/include/graphics/vec.h +++ b/source/shared_lib/include/graphics/vec.h @@ -125,6 +125,11 @@ public: return Vec2(v-*this).length(); } + // strict week ordering, so Vec2 can be used as key for set<> or map<> + bool operator<(const Vec2 &v) const { + return x < v.x || (x == v.x && y < v.y); + } + float length() const{ #ifdef USE_STREFLOP return static_cast(streflop::sqrt(static_cast(x*x + y*y))); diff --git a/source/shared_lib/include/platform/win32/types.h b/source/shared_lib/include/platform/win32/types.h new file mode 100644 index 00000000..23b27937 --- /dev/null +++ b/source/shared_lib/include/platform/win32/types.h @@ -0,0 +1,36 @@ +// ============================================================== +// This file is part of Glest Shared Library (www.glest.org) +// +// Copyright (C) 2001-2008 Martiņo Figueroa +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== + +#ifndef _SHARED_PLATFORM_TYPES_H_ +#define _SHARED_PLATFORM_TYPES_H_ + +#define NOMINMAX +#include + +namespace Shared{ namespace Platform{ + +typedef HWND WindowHandle; +typedef HDC DeviceContextHandle; +typedef HGLRC GlContextHandle; + +typedef float float32; +typedef double float64; +typedef char int8; +typedef unsigned char uint8; +typedef short int int16; +typedef unsigned short int uint16; +typedef int int32; +typedef unsigned int uint32; +typedef long long int64; + +}}//end namespace + +#endif diff --git a/source/shared_lib/include/util/heap.h b/source/shared_lib/include/util/heap.h new file mode 100644 index 00000000..af504bae --- /dev/null +++ b/source/shared_lib/include/util/heap.h @@ -0,0 +1,119 @@ +// ============================================================== +// This file is part of the Glest Advanced Engine +// +// Copyright (C) 2010 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== +#ifndef _MIN_HEAP_INCLUDED_ +#define _MIN_HEAP_INCLUDED_ + +#include + +namespace Shared { namespace Util { + +/** 'Nodes' nead to implement this implicit interface: + +struct SomeNode { + + void setHeapIndex(int ndx); // typically, { heap_ndx = ndx; } + int getHeapIndex() const; // typically, { return heap_ndx; } + + bool operator<(const SomeNode &that) const; +}; +*/ + +/** (Min) Heap, supporting node 'index awareness'. + * stores pointers to Nodes, user needs to supply the actual nodes, preferably in single block + * of memory, and preferably with as compact a node structure as is possible (to the point that the + * int 'heap_ndx' should be a bitfield using as few bits as you can get away with). + */ +template class MinHeap { +private: + Node** data; + int counter; + int capacity; + +public: + /** Construct MinHeap with a given capacity */ + MinHeap(int capacity = 1024) : counter(0), capacity(capacity) { + data = new Node*[capacity]; + } + + ~MinHeap() { + delete [] data; + } + + /** add a new node to the min heap */ + bool insert(Node *node) { + if (counter == capacity) { + return false; + } + data[counter] = node; + data[counter]->setHeapIndex(counter); + promoteNode(counter++); + return true; + } + + /** pop the best node off the min heap */ + Node* extract() { + assert(counter); + Node *res = data[0]; + if (--counter) { + data[0] = data[counter]; + data[0]->setHeapIndex(0); + demoteNode(); + } + return res; + } + + /** indicate a node has had its key decreased */ + void promote(Node *node) { + assert(data[node->getHeapIndex()] == node); + promoteNode(node->getHeapIndex()); + } + + int size() const { return counter; } + void clear() { counter = 0; } + bool empty() const { return !counter; } + +private: + inline int parent(int ndx) const { return (ndx - 1) / 2; } + inline int left(int ndx) const { return (ndx * 2) + 1; } + + void promoteNode(int ndx) { + assert(ndx >= 0 && ndx < counter); + while (ndx > 0 && *data[ndx] < *data[parent(ndx)]) { + Node *tmp = data[parent(ndx)]; + data[parent(ndx)] = data[ndx]; + data[ndx] = tmp; + data[ndx]->setHeapIndex(ndx); + ndx = parent(ndx); + data[ndx]->setHeapIndex(ndx); + } + } + + void demoteNode(int ndx = 0) { + assert(counter); + while (true) { + int cndx = left(ndx); // child index + int sndx = ndx; // smallest (priority) of data[ndx] and any children + if (cndx < counter && *data[cndx] < *data[ndx]) sndx = cndx; + if (++cndx < counter && *data[cndx] < *data[sndx]) sndx = cndx; + if (sndx == ndx) return; + Node *tmp = data[sndx]; + data[sndx] = data[ndx]; + data[ndx] = tmp; + data[ndx]->setHeapIndex(ndx); + ndx = sndx; + data[ndx]->setHeapIndex(ndx); + } + } +}; + +}} // end namespace Shared::Util + +#endif // _MIN_HEAP_INCLUDED_ diff --git a/source/shared_lib/include/util/line.h b/source/shared_lib/include/util/line.h new file mode 100644 index 00000000..2f3fd5a4 --- /dev/null +++ b/source/shared_lib/include/util/line.h @@ -0,0 +1,83 @@ +// ============================================================== +// This file is part of the Glest Advanced Engine +// +// Copyright (C) 2010 James McCulloch +// +// You can redistribute this code and/or modify it under +// the terms of the GNU General Public License as published +// by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version +// ============================================================== +#ifndef _LINE_ALGORITHM_INCLUDED_ +#define _LINE_ALGORITHM_INCLUDED_ + +#include + +namespace Shared { namespace Util { + +/** midpoint line algorithm, 'Visit' specifies the 'pixel visit' function, * + * and must take two int params (x & y co-ords) */ +template void line(int x0, int y0, int x1, int y1, VisitFunc visitor) { + bool mirror_x, mirror_y; + int pivot_x, pivot_y; + if (x0 > x1) { + mirror_x = true; + pivot_x = x0; + x1 = (x0 << 1) - x1; + } else { + mirror_x = false; + } + if (y0 > y1) { + mirror_y = true; + pivot_y = y0; + y1 = (y0 << 1) - y1; + } else { + mirror_y = false; + } + // Visit(x,y) => Visit(mirror_x ? (pivot_x << 1) - x : x, mirror_y ? (pivot_y << 1) - y : y); + assert(y0 <= y1 && x0 <= x1); + int dx = x1 - x0, + dy = y1 - y0; + int x = x0, + y = y0; + + if (dx == 0) { + while (y <= y1) { + visitor(mirror_x ? (pivot_x << 1) - x : x, mirror_y ? (pivot_y << 1) - y : y); + ++y; + } + } else if (dy == 0) { + while (x <= x1) { + visitor(mirror_x ? (pivot_x << 1) - x : x, mirror_y ? (pivot_y << 1) - y : y); + ++x; + } + } else if (dy > dx) { + int d = 2 * dx - dy; + int incrS = 2 * dx; + int incrSE = 2 * (dx - dy); + do { + visitor(mirror_x ? (pivot_x << 1) - x : x, mirror_y ? (pivot_y << 1) - y : y); + if (d <= 0) { + d += incrS; ++y; + } else { + d += incrSE; ++x; ++y; + } + } while (y <= y1); + } else { + int d = 2 * dy - dx; + int incrE = 2 * dy; + int incrSE = 2 * (dy - dx); + do { + visitor(mirror_x ? (pivot_x << 1) - x : x, mirror_y ? (pivot_y << 1) - y : y); + if (d <= 0) { + d += incrE; ++x; + } else { + d += incrSE; ++x; ++y; + } + } while (x <= x1); + } +} + +}} // end namespace Shared::Util + +#endif // !def _LINE_ALGORITHM_INCLUDED_ diff --git a/source/shared_lib/sources/platform/win32/glob.c b/source/shared_lib/sources/platform/win32/glob.c index c50467e0..82322614 100644 --- a/source/shared_lib/sources/platform/win32/glob.c +++ b/source/shared_lib/sources/platform/win32/glob.c @@ -45,6 +45,7 @@ #include #include #include +#define NOMINMAX #include #define NUM_ELEMENTS(ar) (sizeof(ar) / sizeof(ar[0]))