// ============================================================== // 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" #define PF_TRACE() SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d]\n",__FILE__,__FUNCTION__,__LINE__) using Shared::Graphics::Vec2i; using Shared::Platform::uint32; namespace Glest { namespace Game { /** maximum radius to search for a free position from a target position */ const int maxFreeSearchRadius = 10; /** @deprecated not in use */ const int pathFindNodesMax = 2048; typedef SearchEngine TransitionSearchEngine; class PMap1Goal { protected: PatchMap<1> *pMap; public: PMap1Goal(PatchMap<1> *pMap) : pMap(pMap) {} bool operator()(const Vec2i &pos, const float) const { if (pMap->getInfluence(pos)) { return true; } return false; } }; /** gets the two 'diagonal' cells to check for obstacles when a unit is moving diagonally * @param s start pos * @param d destination pos * @param size size of unit * @return d1 & d2, the two cells to check * @warning assumes s & d are indeed diagonal, abs(s.x - d.x) == 1 && abs(s.y - d.y) == 1 */ __inline void getDiags(const Vec2i &s, const Vec2i &d, const int size, Vec2i &d1, Vec2i &d2) { # define _SEARCH_ENGINE_GET_DIAGS_DEFINED_ assert(abs(s.x - d.x) == 1 && abs(s.y - d.y) == 1); if (size == 1) { d1.x = s.x; d1.y = d.y; d2.x = d.x; d2.y = s.y; return; } if (d.x > s.x) { // travelling east if (d.y > s.y) { // se d1.x = d.x + size - 1; d1.y = s.y; d2.x = s.x; d2.y = d.y + size - 1; } else { // ne d1.x = s.x; d1.y = d.y; d2.x = d.x + size - 1; d2.y = s.y + size - 1; } } else { // travelling west if (d.y > s.y) { // sw d1.x = d.x; d1.y = s.y; d2.x = s.x + size - 1; d2.y = d.y + size - 1; } else { // nw d1.x = d.x; d1.y = s.y + size - 1; d2.x = s.x + size - 1; d2.y = d.y; } } } /** The movement cost function */ class MoveCost { private: const int size; /**< size of agent */ const Field field; /**< field to search in */ const AnnotatedMap *aMap; /**< map to search on */ public: MoveCost(const Unit *unit, const AnnotatedMap *aMap) : size(unit->getType()->getSize()), field(unit->getCurrField()), aMap(aMap) {} MoveCost(const Field field, const int size, const AnnotatedMap *aMap ) : size(size), field(field), aMap(aMap) {} /** The cost function @param p1 position 1 @param p2 position 2 ('adjacent' p1) * @return cost of move, possibly infinite */ float operator()(const Vec2i &p1, const Vec2i &p2) const { assert(p1.dist(p2) < 1.5 && p1 != p2); //assert(g_map.isInside(p2)); if (!aMap->canOccupy(p2, size, field)) { return -1.f; } if (p1.x != p2.x && p1.y != p2.y) { Vec2i d1, d2; getDiags(p1, p2, size, d1, d2); //assert(g_map.isInside(d1) && g_map.isInside(d2)); if (!aMap->canOccupy(d1, 1, field) || !aMap->canOccupy(d2, 1, field) ) { return -1.f; } return SQRT2; } return 1.f; // todo... height } }; enum 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); TravelState findPathToStore(Unit *unit, const Unit *store); TravelState findPathToBuildSite(Unit *unit, const UnitType *bType, const Vec2i &bPos, CardinalDir bFacing); bool isLegalMove(Unit *unit, const Vec2i &pos) const; SearchEngine* getSearchEngine() { return nsgSearchEngine; } private: bool repairPath(Unit *unit); TravelState findAerialPath(Unit *unit, const Vec2i &targetPos); TravelState doRouteCache(Unit *unit); TravelState doQuickPathSearch(Unit *unit, const Vec2i &target); TravelState findPathToGoal(Unit *unit, PMap1Goal &goal, const Vec2i &targetPos); TravelState customGoalSearch(PMap1Goal &goal, Unit *unit, const Vec2i &target); float quickSearch(Field field, int Size, const Vec2i &start, const Vec2i &dest); bool refinePath(Unit *unit); void smoothPath(Unit *unit); HAAStarResult setupHierarchicalOpenList(Unit *unit, const Vec2i &target); HAAStarResult setupHierarchicalSearch(Unit *unit, const Vec2i &dest, TransitionGoal &goalFunc); HAAStarResult findWaypointPath(Unit *unit, const Vec2i &dest, WaypointPath &waypoints); HAAStarResult findWaypointPathUnExplored(Unit *unit, const Vec2i &dest, WaypointPath &waypoints); World *world; SearchEngine *nsgSearchEngine; NodePool *nodeStore; TransitionSearchEngine *tSearchEngine; TransitionNodeStore *tNodeStore; Vec2i computeNearestFreePos(const Unit *unit, const Vec2i &targetPos); bool attemptMove(Unit *unit) const { UnitPathInterface *path = unit->getPath(); UnitPath *advPath = dynamic_cast(path); if(advPath == NULL) { throw runtime_error("Invalid or NULL unit path pointer!"); } assert(advPath->isEmpty() == false); Vec2i pos = advPath->peek(); if (isLegalMove(unit, pos)) { unit->setTargetPos(pos); advPath->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 /** 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; } }; /** 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; } }; //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) != 0); } }; #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