mirror of
https://github.com/glest/glest-source.git
synced 2025-02-25 04:02:30 +01:00
* 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
288 lines
9.1 KiB
C++
288 lines
9.1 KiB
C++
// ==============================================================
|
|
// 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
|