::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