* 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:
parent
277230657c
commit
bdd4f306f4
Binary file not shown.
|
@ -327,7 +327,11 @@
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
<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>
|
</File>
|
||||||
</Filter>
|
</Filter>
|
||||||
|
@ -361,7 +365,47 @@
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
<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>
|
||||||
<File
|
<File
|
||||||
|
@ -372,6 +416,18 @@
|
||||||
RelativePath="..\..\source\glest_game\ai\path_finder.h"
|
RelativePath="..\..\source\glest_game\ai\path_finder.h"
|
||||||
>
|
>
|
||||||
</File>
|
</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>
|
||||||
<Filter
|
<Filter
|
||||||
Name="game"
|
Name="game"
|
||||||
|
@ -592,6 +648,14 @@
|
||||||
RelativePath="..\..\source\glest_game\menu\menu_state_about.h"
|
RelativePath="..\..\source\glest_game\menu\menu_state_about.h"
|
||||||
>
|
>
|
||||||
</File>
|
</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
|
<File
|
||||||
RelativePath="..\..\source\glest_game\menu\menu_state_custom_game.cpp"
|
RelativePath="..\..\source\glest_game\menu\menu_state_custom_game.cpp"
|
||||||
>
|
>
|
||||||
|
|
|
@ -884,10 +884,18 @@
|
||||||
RelativePath="..\..\source\shared_lib\include\util\factory.h"
|
RelativePath="..\..\source\shared_lib\include\util\factory.h"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\source\shared_lib\include\util\heap.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\..\source\shared_lib\include\util\leak_dumper.h"
|
RelativePath="..\..\source\shared_lib\include\util\leak_dumper.h"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\source\shared_lib\include\util\line.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\..\source\shared_lib\include\util\profiler.h"
|
RelativePath="..\..\source\shared_lib\include\util\profiler.h"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
|
@ -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
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}}
|
|
@ -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
|
|
@ -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
|
|
@ -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_ )
|
||||||
|
|
||||||
|
}}
|
|
@ -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
|
|
@ -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_
|
||||||
|
|
||||||
|
}}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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; }
|
||||||
|
};
|
|
@ -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
|
||||||
|
|
|
@ -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_
|
|
@ -42,6 +42,9 @@ public:
|
||||||
//static const int networkExtraLatency= 200;
|
//static const int networkExtraLatency= 200;
|
||||||
static const int maxClientConnectHandshakeSecs= 10;
|
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_maps;
|
||||||
static const char *folder_path_scenarios;
|
static const char *folder_path_scenarios;
|
||||||
static const char *folder_path_techs;
|
static const char *folder_path_techs;
|
||||||
|
|
|
@ -38,47 +38,28 @@ namespace Glest{ namespace Game{
|
||||||
// class UnitPath
|
// class UnitPath
|
||||||
// =====================================================
|
// =====================================================
|
||||||
|
|
||||||
const int UnitPath::maxBlockCount= 10;
|
void WaypointPath::condense() {
|
||||||
|
if (size() < 2) {
|
||||||
UnitPath::UnitPath() {
|
return;
|
||||||
this->blockCount = 0;
|
}
|
||||||
this->pathQueue.clear();
|
iterator prev, curr;
|
||||||
}
|
prev = curr = begin();
|
||||||
|
while (++curr != end()) {
|
||||||
bool UnitPath::isEmpty(){
|
if (prev->dist(*curr) < 3.f) {
|
||||||
return pathQueue.empty();
|
prev = erase(prev);
|
||||||
}
|
} else {
|
||||||
|
++prev;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string UnitPath::toString() const {
|
std::string UnitPath::toString() const {
|
||||||
std::string result = "";
|
std::string result = "";
|
||||||
|
|
||||||
result = "unit path blockCount = " + intToStr(blockCount) + " pathQueue size = " + intToStr(pathQueue.size());
|
result = "unit path blockCount = " + intToStr(blockCount) + " pathQueue size = " + intToStr(size());
|
||||||
for(int idx = 0; idx < pathQueue.size(); idx++) {
|
result += " path = ";
|
||||||
result += " index = " + intToStr(idx) + " " + pathQueue[idx].getString();
|
for (const_iterator it = begin(); it != end(); ++it) {
|
||||||
|
result += " [" + intToStr(it->x) + "," + intToStr(it->y) + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -97,31 +97,50 @@ public:
|
||||||
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
// class UnitPath
|
// class UnitPath
|
||||||
//
|
|
||||||
/// Holds the next cells of a Unit movement
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
|
/** Holds the next cells of a Unit movement
|
||||||
class UnitPath {
|
* @extends std::list<Shared::Math::Vec2i>
|
||||||
|
*/
|
||||||
|
class UnitPath : public list<Vec2i> {
|
||||||
private:
|
private:
|
||||||
static const int maxBlockCount;
|
static const int maxBlockCount = 10; /**< number of command updates to wait on a blocked path */
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int blockCount;
|
int blockCount; /**< number of command updates this path has been blocked */
|
||||||
vector<Vec2i> pathQueue;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UnitPath();
|
UnitPath() : blockCount(0) {} /**< Construct path object */
|
||||||
bool isBlocked();
|
bool isBlocked() const {return blockCount >= maxBlockCount;} /**< is this path blocked */
|
||||||
bool isEmpty();
|
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();
|
std::string UnitPath::toString() const;
|
||||||
void incBlockCount();
|
|
||||||
void push(const Vec2i &path);
|
|
||||||
Vec2i pop();
|
|
||||||
|
|
||||||
std::string 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
|
// class Unit
|
||||||
//
|
//
|
||||||
|
@ -187,6 +206,7 @@ private:
|
||||||
Map *map;
|
Map *map;
|
||||||
|
|
||||||
UnitPath unitPath;
|
UnitPath unitPath;
|
||||||
|
WaypointPath waypointPath;
|
||||||
|
|
||||||
Commands commands;
|
Commands commands;
|
||||||
Observers observers;
|
Observers observers;
|
||||||
|
@ -238,6 +258,7 @@ public:
|
||||||
string getFullName() const;
|
string getFullName() const;
|
||||||
const UnitPath *getPath() const {return &unitPath;}
|
const UnitPath *getPath() const {return &unitPath;}
|
||||||
UnitPath *getPath() {return &unitPath;}
|
UnitPath *getPath() {return &unitPath;}
|
||||||
|
WaypointPath *getWaypointPath() {return &waypointPath;}
|
||||||
|
|
||||||
//pos
|
//pos
|
||||||
Vec2i getPos() const {return pos;}
|
Vec2i getPos() const {return pos;}
|
||||||
|
@ -315,7 +336,7 @@ public:
|
||||||
void applyCommand(Command *command);
|
void applyCommand(Command *command);
|
||||||
|
|
||||||
void setModelFacing(CardinalDir value);
|
void setModelFacing(CardinalDir value);
|
||||||
CardinalDir getModelFacing() { return modelFacing; }
|
CardinalDir getModelFacing() const { return modelFacing; }
|
||||||
|
|
||||||
bool isMeetingPointSettable() const;
|
bool isMeetingPointSettable() const;
|
||||||
|
|
||||||
|
|
|
@ -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
|
//properties
|
||||||
const XmlNode *propertiesNode= parametersNode->getChild("properties");
|
const XmlNode *propertiesNode= parametersNode->getChild("properties");
|
||||||
for(int i=0; i<propertiesNode->getChildCount(); ++i){
|
for(int i=0; i<propertiesNode->getChildCount(); ++i){
|
||||||
|
|
|
@ -86,7 +86,11 @@ private:
|
||||||
int hpRegeneration;
|
int hpRegeneration;
|
||||||
int maxEp;
|
int maxEp;
|
||||||
int epRegeneration;
|
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
|
bool properties[pCount]; //properties
|
||||||
int armor; //armor
|
int armor; //armor
|
||||||
const ArmorType *armorType;
|
const ArmorType *armorType;
|
||||||
|
@ -134,6 +138,7 @@ public:
|
||||||
int getMaxEp() const {return maxEp;}
|
int getMaxEp() const {return maxEp;}
|
||||||
int getEpRegeneration() const {return epRegeneration;}
|
int getEpRegeneration() const {return epRegeneration;}
|
||||||
bool getField(Field field) const {return fields[field];}
|
bool getField(Field field) const {return fields[field];}
|
||||||
|
Field getField() const {return field;}
|
||||||
bool getProperty(Property property) const {return properties[property];}
|
bool getProperty(Property property) const {return properties[property];}
|
||||||
int getArmor() const {return armor;}
|
int getArmor() const {return armor;}
|
||||||
const ArmorType *getArmorType() const {return armorType;}
|
const ArmorType *getArmorType() const {return armorType;}
|
||||||
|
@ -154,6 +159,7 @@ public:
|
||||||
const Resource *getStoredResource(int i) const {return &storedResources[i];}
|
const Resource *getStoredResource(int i) const {return &storedResources[i];}
|
||||||
bool getCellMapCell(int x, int y, CardinalDir facing) const;
|
bool getCellMapCell(int x, int y, CardinalDir facing) const;
|
||||||
bool getMeetingPoint() const {return meetingPoint;}
|
bool getMeetingPoint() const {return meetingPoint;}
|
||||||
|
bool isMobile() const {return firstSkillTypeOfClass[scMove];}
|
||||||
Texture2D *getMeetingPointImage() const {return meetingPointImage;}
|
Texture2D *getMeetingPointImage() const {return meetingPointImage;}
|
||||||
StaticSound *getSelectionSound() const {return selectionSounds.getRandSound();}
|
StaticSound *getSelectionSound() const {return selectionSounds.getRandSound();}
|
||||||
StaticSound *getCommandSound() const {return commandSounds.getRandSound();}
|
StaticSound *getCommandSound() const {return commandSounds.getRandSound();}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "game_settings.h"
|
#include "game_settings.h"
|
||||||
#include "platform_util.h"
|
#include "platform_util.h"
|
||||||
|
#include "pos_iterator.h"
|
||||||
#include "leak_dumper.h"
|
#include "leak_dumper.h"
|
||||||
|
|
||||||
using namespace Shared::Graphics;
|
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
|
//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{
|
bool Map::isResourceNear(const Vec2i &pos, int size, const ResourceType *rt, Vec2i &resourcePos) const {
|
||||||
for(int i=-1; i<=1; ++i){
|
Vec2i p1 = pos + Vec2i(-1);
|
||||||
for(int j=-1; j<=1; ++j){
|
Vec2i p2 = pos + Vec2i(size);
|
||||||
if(isInside(pos.x+i, pos.y+j)){
|
Util::PerimeterIterator iter(p1, p2);
|
||||||
Resource *r= getSurfaceCell(toSurfCoords(Vec2i(pos.x+i, pos.y+j)))->getResource();
|
while (iter.more()) {
|
||||||
if(r!=NULL){
|
Vec2i cur = iter.next();
|
||||||
if(r->getType()==rt){
|
if (isInside(cur)) {
|
||||||
resourcePos= pos + Vec2i(i,j);
|
Resource *r = getSurfaceCell(toSurfCoords(cur))->getResource();
|
||||||
return true;
|
if (r && r->getType() == rt) {
|
||||||
}
|
resourcePos = cur;
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==================== free cells ====================
|
// ==================== free cells ====================
|
||||||
|
|
||||||
bool Map::isFreeCell(const Vec2i &pos, Field field) const{
|
bool Map::isFreeCell(const Vec2i &pos, Field field) const{
|
||||||
|
|
|
@ -196,7 +196,7 @@ public:
|
||||||
bool isInside(const Vec2i &pos) const;
|
bool isInside(const Vec2i &pos) const;
|
||||||
bool isInsideSurface(int sx, int sy) const;
|
bool isInsideSurface(int sx, int sy) const;
|
||||||
bool isInsideSurface(const Vec2i &sPos) 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
|
//free cells
|
||||||
bool isFreeCell(const Vec2i &pos, Field field) const;
|
bool isFreeCell(const Vec2i &pos, Field field) const;
|
||||||
|
|
|
@ -379,7 +379,7 @@ void UnitUpdater::updateHarvest(Unit *unit){
|
||||||
if(r!=NULL && hct->canHarvest(r->getType())){
|
if(r!=NULL && hct->canHarvest(r->getType())){
|
||||||
//if can harvest dest. pos
|
//if can harvest dest. pos
|
||||||
if(unit->getPos().dist(command->getPos())<harvestDistance &&
|
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
|
//if it finds resources it starts harvesting
|
||||||
unit->setCurrSkill(hct->getHarvestSkillType());
|
unit->setCurrSkill(hct->getHarvestSkillType());
|
||||||
unit->setTargetPos(targetPos);
|
unit->setTargetPos(targetPos);
|
||||||
|
|
|
@ -43,6 +43,8 @@ class Config;
|
||||||
class Game;
|
class Game;
|
||||||
class GameSettings;
|
class GameSettings;
|
||||||
class ScriptManager;
|
class ScriptManager;
|
||||||
|
class Cartographer;
|
||||||
|
class RoutePlanner;
|
||||||
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
// class World
|
// class World
|
||||||
|
@ -78,6 +80,8 @@ private:
|
||||||
RandomGen random;
|
RandomGen random;
|
||||||
|
|
||||||
ScriptManager* scriptManager;
|
ScriptManager* scriptManager;
|
||||||
|
Cartographer *cartographer;
|
||||||
|
RoutePlanner *routePlanner;
|
||||||
|
|
||||||
int thisFactionIndex;
|
int thisFactionIndex;
|
||||||
int thisTeamIndex;
|
int thisTeamIndex;
|
||||||
|
@ -112,6 +116,8 @@ public:
|
||||||
const TimeFlow *getTimeFlow() const {return &timeFlow;}
|
const TimeFlow *getTimeFlow() const {return &timeFlow;}
|
||||||
Tileset *getTileset() {return &tileset;}
|
Tileset *getTileset() {return &tileset;}
|
||||||
Map *getMap() {return ↦}
|
Map *getMap() {return ↦}
|
||||||
|
Cartographer* getCartographer() {return cartographer;}
|
||||||
|
RoutePlanner* getRoutePlanner() {return routePlanner;}
|
||||||
const Faction *getFaction(int i) const {return &factions[i];}
|
const Faction *getFaction(int i) const {return &factions[i];}
|
||||||
Faction *getFaction(int i) {return &factions[i];}
|
Faction *getFaction(int i) {return &factions[i];}
|
||||||
const Minimap *getMinimap() const {return &minimap;}
|
const Minimap *getMinimap() const {return &minimap;}
|
||||||
|
|
|
@ -125,6 +125,11 @@ public:
|
||||||
return Vec2<T>(v-*this).length();
|
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{
|
float length() const{
|
||||||
#ifdef USE_STREFLOP
|
#ifdef USE_STREFLOP
|
||||||
return static_cast<float>(streflop::sqrt(static_cast<float>(x*x + y*y)));
|
return static_cast<float>(streflop::sqrt(static_cast<float>(x*x + y*y)));
|
||||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -45,6 +45,7 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#define NOMINMAX
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#define NUM_ELEMENTS(ar) (sizeof(ar) / sizeof(ar[0]))
|
#define NUM_ELEMENTS(ar) (sizeof(ar) / sizeof(ar[0]))
|
||||||
|
|
Loading…
Reference in New Issue