* 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
This commit is contained in:
James McCulloch 2010-07-11 18:31:02 +00:00
parent 277230657c
commit bdd4f306f4
35 changed files with 5547 additions and 76 deletions

Binary file not shown.

View File

@ -327,7 +327,11 @@
>
</File>
<File
RelativePath="..\..\source\glest_game\menu\menu_state_connected_game.cpp"
RelativePath="..\..\source\glest_game\facilities\pos_iterator.cpp"
>
</File>
<File
RelativePath="..\..\source\glest_game\facilities\pos_iterator.h"
>
</File>
</Filter>
@ -361,7 +365,47 @@
>
</File>
<File
RelativePath="..\..\source\glest_game\menu\menu_state_connected_game.h"
RelativePath="..\..\source\glest_game\ai\annotated_map.cpp"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\annotated_map.h"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\cartographer.cpp"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\cartographer.h"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\cluster_map.cpp"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\cluster_map.h"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\influence_map.h"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\node_map.cpp"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\node_map.h"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\node_pool.cpp"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\node_pool.h"
>
</File>
<File
@ -372,6 +416,18 @@
RelativePath="..\..\source\glest_game\ai\path_finder.h"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\route_planner.cpp"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\route_planner.h"
>
</File>
<File
RelativePath="..\..\source\glest_game\ai\search_engine.h"
>
</File>
</Filter>
<Filter
Name="game"
@ -592,6 +648,14 @@
RelativePath="..\..\source\glest_game\menu\menu_state_about.h"
>
</File>
<File
RelativePath="..\..\source\glest_game\menu\menu_state_connected_game.cpp"
>
</File>
<File
RelativePath="..\..\source\glest_game\menu\menu_state_connected_game.h"
>
</File>
<File
RelativePath="..\..\source\glest_game\menu\menu_state_custom_game.cpp"
>

View File

@ -884,10 +884,18 @@
RelativePath="..\..\source\shared_lib\include\util\factory.h"
>
</File>
<File
RelativePath="..\..\source\shared_lib\include\util\heap.h"
>
</File>
<File
RelativePath="..\..\source\shared_lib\include\util\leak_dumper.h"
>
</File>
<File
RelativePath="..\..\source\shared_lib\include\util\line.h"
>
</File>
<File
RelativePath="..\..\source\shared_lib\include\util\profiler.h"
>

View File

@ -0,0 +1,382 @@
// ==============================================================
// This file is part of The Glest Advanced Engine
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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<Vec2i> *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<Vec2i> *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<Unit*> 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<Unit*>::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<Vec2i,uint32>::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<pair<Vec2i,uint32> >* AnnotatedMap::getLocalAnnotations() {
list<pair<Vec2i,uint32> > *ret = new list<pair<Vec2i,uint32> >();
for ( map<Vec2i,uint32>::iterator it = localAnnt.begin(); it != localAnnt.end(); ++ it )
ret->push_back(pair<Vec2i,uint32>(it->first,metrics[it->first].get(fLand)));
return ret;
}
#endif
}}

View File

@ -0,0 +1,192 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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<Vec2i>::iterator VLIt;
typedef list<Vec2i>::const_iterator VLConIt;
typedef list<Vec2i>::reverse_iterator VLRevIt;
typedef list<Vec2i>::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
* <p>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').</p>
*/
//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<Vec2i,uint32> localAnnt;
/** The metrics */
MetricMap metrics;
};
}}
#endif

View File

@ -0,0 +1,255 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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 <algorithm>
#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<NodeMap, GridNeighbours>(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<rt_ptr> 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<const HarvestCommandType *>(ct);
for (vector<rt_ptr>::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<ResourceMapKey>::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<Vec2i>::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<Vec2i,Vec2i>(tl,br));
}
void Cartographer::fixupResourceMaps(const ResourceType *rt, const Vec2i &pos) {
const Map &map = *world->getMap();
Vec2i junk;
for (set<ResourceMapKey>::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();
}
}
}
}}

View File

@ -0,0 +1,218 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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<Vec2i> V2iList;
typedef map<ResourceMapKey, PatchMap<1>*> ResourceMaps; // goal maps for harvester path searches to resourecs
typedef map<StoreMapKey, PatchMap<1>*> StoreMaps; // goal maps for harvester path searches to store
typedef map<BuildSiteMapKey,PatchMap<1>*> SiteMaps; // goal maps for building sites.
typedef list<pair<rt_ptr, Vec2i> > ResourcePosList;
typedef map<rt_ptr, V2iList> ResourcePosMap;
// Resources
/** The locations of each and every resource on the map */
ResourcePosMap resourceLocations;
set<ResourceMapKey> 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<NodeMap,GridNeighbours> *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<NodeMap,GridNeighbours>* 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<float> &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

View File

@ -0,0 +1,678 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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 <algorithm>
#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<const Transition *> TSet;
TSet tSet;
typedef pair<Vec2i, bool> TKey;
typedef map<TKey, const Transition *> 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<Vec2i> &results;
Visitor(vector<Vec2i> &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<Vec2i> linePath;
Visitor visitor(linePath);
line(start.x, start.y, dest.x, dest.y, visitor);
assert(linePath.size() >= 2);
MoveCost costFunc(f, size, aMap);
vector<Vec2i>::iterator it = linePath.begin();
vector<Vec2i>::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<NodePool> *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,MoveCost,DiagonalDistance>(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<const Transition*> 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<Transition*>(*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<Vec2i>::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<Vec2i>::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<Vec2i>::iterator it = dirtyClusters.begin(); it != dirtyClusters.end(); ++it) {
//cout << "cluster " << *it << " dirty." << endl;
disconnectCluster(*it);
}
for (set<Vec2i>::iterator it = dirtyNorthBorders.begin(); it != dirtyNorthBorders.end(); ++it) {
//cout << "cluster " << *it << " north border dirty." << endl;
initClusterBorder(*it, true);
}
for (set<Vec2i>::iterator it = dirtyWestBorders.begin(); it != dirtyWestBorders.end(); ++it) {
//cout << "cluster " << *it << " west border dirty." << endl;
initClusterBorder(*it, false);
}
for (set<Vec2i>::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<NodePool> *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<Transition*>(*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<TransitionAStarNode*>::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<const Transition*> seen;
list<TransitionAStarNode*>::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<const Transition*>::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;
}
}}

View File

@ -0,0 +1,319 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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 <map>
#include <set>
#include <list>
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<float> 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<Edge*> 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<const Transition*> 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<const Transition*> &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<const Transition*> goals;
public:
TransitionGoal() {}
set<const Transition*>& 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<Vec2i> dirtyClusters;
set<Vec2i> dirtyNorthBorders;
set<Vec2i> 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<TransitionAStarNode*> openList;
set<const Transition*> open;
set<const Transition*> closed;
map<const Transition*, TransitionAStarNode*> 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

View File

@ -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 <algorithm>
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<typename iType, typename MapType> 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<MapType*>(this)->get(pos);
}
return def;
}
bool setInfluence(Point pos, iType val) {
pos = translate(pos);
if (pos != invalidPos) {
static_cast<MapType*>(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<typename type = float> class TypeMap : public InfluenceMap<type, TypeMap<type> > {
friend class InfluenceMap<type, TypeMap<type> >;
public:
TypeMap(Rectangle b, type def) : InfluenceMap<type, TypeMap<type> >(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<uint8>, TypeMap<uint16> 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<int bits> class PatchMap : public InfluenceMap<uint32, PatchMap<bits> > {
friend class InfluenceMap<uint32, PatchMap<bits> >;
public:
PatchMap(Rectangle b, uint32 def) : InfluenceMap<uint32,PatchMap<bits> >(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<bits> &operator=(const PatchMap<bits> &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 <<endl;
return val;
}
void set(Point p, uint32 v) {
if (v > 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<Point, FlowMap> {
friend class InfluenceMap<Point, FlowMap>;
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<Point,FlowMap>(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

View File

@ -0,0 +1,291 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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 <algorithm>
#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<Vec2i> 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<Vec2i> 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<Vec2i>::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<Vec2i> &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<pair<Vec2i,uint32>>* SearchMap::getLocalAnnotations() {
list<pair<Vec2i,uint32>> *ret = new list<pair<Vec2i,uint32>>();
for ( map<Vec2i,uint32>::iterator it = localAnnt.begin(); it != localAnnt.end(); ++ it ) {
ret->push_back(pair<Vec2i,uint32>(it->first,nodeMap[it->first].getClearance(Field::LAND)));
}
return ret;
}
*/
list<Vec2i>* NodeMap::getOpenNodes() {
list<Vec2i> *ret = new list<Vec2i>();
list<Vec2i>::iterator it = listedNodes.begin();
for ( ; it != listedNodes.end(); ++it ) {
if ( nodeMap[*it].mark == searchCounter ) ret->push_back(*it);
}
return ret;
}
list<Vec2i>* NodeMap::getClosedNodes() {
list<Vec2i> *ret = new list<Vec2i>();
list<Vec2i>::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_ )
}}

View File

@ -0,0 +1,185 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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<<MAX_MAP_COORD_BITS)-1)
#pragma pack(push, 1)
/** A bit packed position (Vec2i) */
struct PackedPos {
/** Construct a PackedPos [0,0] */
PackedPos() : x(0), y(0) {}
/** Construct a PackedPos [x,y] */
PackedPos(int x, int y) : x(x), y(y) {}
/** Construct a PackedPos [pos.x, pos.y] */
PackedPos(Vec2i pos) { *this = pos; }
/** Assign from Vec2i */
PackedPos& operator=(Vec2i pos) {
assert(pos.x <= MAX_MAP_COORD && pos.y <= MAX_MAP_COORD);
if (pos.x < 0 || pos.y < 0) {
x = MAX_MAP_COORD; y = MAX_MAP_COORD; // invalid
} else {
x = pos.x; y = pos.y;
}
return *this;
}
/** this packed pos as Vec2i */
operator Vec2i() { return Vec2i(x, y); }
/** Comparision operator */
bool operator==(PackedPos &that) { return x == that.x && y == that.y; }
/** is this position valid */
bool valid() {
if ((x == 65535) || (y == 65535)) {
return false;
}
return true;
}
// max == MAX_MAP_COORD,
// MAX_MAP_COORD, MAX_MAP_COORD is considered 'invalid', this is ok still on a
// MAX_MAP_COORD * MAX_MAP_COORD map in Glest, because the far east and south 'tiles'
// (2 cells) are not valid either.
uint16 x : MAX_MAP_COORD_BITS; /**< x coordinate */
uint16 y : MAX_MAP_COORD_BITS; /**< y coordinate */
};
/** The cell structure for the node map. Stores all the usual A* node information plus
* information on status (unvisited/open/closed) and an 'embedded' linked list for the
* open set. */
struct NodeMapCell {
/** <p>Node status for this search,</p>
* <ul><li>mark < NodeMap::searchCounter => unvisited</li>
* <li>mark == NodeMap::searchCounter => open</li>
* <li>mark == NodeMap::searchCounter + 1 => closed</li></ul> */
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<Vec2i> &path);
/** Debug */
bool assertOpen();
/** Debug */
void logOpen();
#ifdef _GAE_DEBUG_EDITION_
public:
list<Vec2i>* getOpenNodes ();
list<Vec2i>* getClosedNodes ();
list<Vec2i> listedNodes;
#endif
};
}}
#endif

View File

@ -0,0 +1,133 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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<Vec2i>* NodePool::getOpenNodes() {
list<Vec2i> *ret = new list<Vec2i>();
list<Vec2i>::iterator it = listedNodes.begin();
for ( ; it != listedNodes.end (); ++it) {
if (isOpen(*it)) ret->push_back(*it);
}
return ret;
}
list<Vec2i>* NodePool::getClosedNodes() {
list<Vec2i> *ret = new list<Vec2i>();
list<Vec2i>::iterator it = listedNodes.begin();
for ( ; it != listedNodes.end(); ++it) {
if (isClosed(*it)) ret->push_back(*it);
}
return ret;
}
#endif // _GAE_DEBUG_EDITION_
}}

View File

@ -0,0 +1,194 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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 <algorithm>
#include <set>
#include <list>
//#include <limits>
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<AStarNode> 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<Vec2i>* getOpenNodes();
list<Vec2i>* getClosedNodes();
list<Vec2i> listedNodes;
#endif
};
}}
#endif

View File

@ -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 <algorithm>
#include <cassert>
#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 && i<pathFindRefresh; currNode= currNode->next, i++){
path->push(currNode->next->pos);
}
}
//clean nodes
openNodes.clear();
closedNodes.clear();
return ts;
}
PathFinder::Node *PathFinder::newNode(){
if(nodePoolCount<pathFindNodesMax){
Node *node= &nodePool[nodePoolCount];
nodePoolCount++;
return node;
}
return NULL;
}
Vec2i PathFinder::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(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(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;
}
float PathFinder::heuristic(const Vec2i &pos, const Vec2i &finalPos){
return pos.dist(finalPos);
}
//returns an iterator to the lowest heuristic node
PathFinder::Nodes::iterator PathFinder::minHeuristic(){
Nodes::iterator minNodeIt= openNodes.begin();
assert(!openNodes.empty());
for(Nodes::iterator it= openNodes.begin(); it!=openNodes.end(); ++it){
if((*it)->heuristic < (*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

View File

@ -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 <typename NodeStorage>
void collectOpenClosed(NodeStorage *ns) {
list<Vec2i> *nodes = ns->getOpenNodes();
list<Vec2i>::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<NodePool>(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<NodePool>(nsgSearchEngine->getStorage()); )
// extract path
Vec2i pos = nsgSearchEngine->getGoalPos();
assert(pos == destPos);
list<Vec2i>::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<Vec2i> 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<NodePool>(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<NodePool>(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<NodePool>(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<NodeMap> *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<Vec2i>::iterator it;
IF_DEBUG_EDITION (
list<Vec2i> *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<NodeMap>(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

View File

@ -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<TransitionNodeStore,TransitionNeighbours,const Transition*> 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<NodePool>* 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<NodePool> *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

View File

@ -0,0 +1,287 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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<Vec2i> &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 <b>template parameter</b> Goal function class
* @param CostFunc <b>template parameter</b> Cost function class
* @param Heuristic <b>template parameter</b> 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<DomainKey> 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

View File

@ -0,0 +1,155 @@
// ==============================================================
// This file is part of Glest (www.glest.org)
//
// Copyright (C) 2009 James McCulloch <silnarm at gmail>
//
// 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; }
};

View File

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

View File

@ -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 <cassert>
#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_

View File

@ -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;

View File

@ -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;

View File

@ -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<Shared::Math::Vec2i>
*/
class UnitPath : public list<Vec2i> {
private:
static const int maxBlockCount;
static const int maxBlockCount = 10; /**< number of command updates to wait on a blocked path */
private:
int blockCount;
vector<Vec2i> 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<Vec2i>::empty();} /**< is path empty */
int size() const {return list<Vec2i>::size();} /**< size of path */
void clear() {list<Vec2i>::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<Vec2i> {
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;

View File

@ -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; i<propertiesNode->getChildCount(); ++i){

View File

@ -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();}

View File

@ -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{

View File

@ -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;

View File

@ -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())<harvestDistance &&
map->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);

View File

@ -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 &map;}
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;}

View File

@ -125,6 +125,11 @@ public:
return Vec2<T>(v-*this).length();
}
// strict week ordering, so Vec2<T> can be used as key for set<> or map<>
bool operator<(const Vec2<T> &v) const {
return x < v.x || (x == v.x && y < v.y);
}
float length() const{
#ifdef USE_STREFLOP
return static_cast<float>(streflop::sqrt(static_cast<float>(x*x + y*y)));

View File

@ -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 <windows.h>
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

View File

@ -0,0 +1,119 @@
// ==============================================================
// This file is part of the Glest Advanced Engine
//
// Copyright (C) 2010 James McCulloch <silnarm at gmail>
//
// 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 <cassert>
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<typename Node> 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_

View File

@ -0,0 +1,83 @@
// ==============================================================
// This file is part of the Glest Advanced Engine
//
// Copyright (C) 2010 James McCulloch <silnarm at gmail>
//
// 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 <cassert>
namespace Shared { namespace Util {
/** midpoint line algorithm, 'Visit' specifies the 'pixel visit' function, *
* and must take two int params (x & y co-ords) */
template<typename VisitFunc> 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_

View File

@ -45,6 +45,7 @@
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#define NOMINMAX
#include <windows.h>
#define NUM_ELEMENTS(ar) (sizeof(ar) / sizeof(ar[0]))