Pathfinder.cpp

Go to the documentation of this file.
00001 /*
00002 Copyright (C) 2003-2007 The Pentagram team
00003 
00004 This program is free software; you can redistribute it and/or
00005 modify it under the terms of the GNU General Public License
00006 as published by the Free Software Foundation; either version 2
00007 of the License, or (at your option) any later version.
00008 
00009 This program is distributed in the hope that it will be useful,
00010 but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012 GNU General Public License for more details.
00013 
00014 You should have received a copy of the GNU General Public License
00015 along with this program; if not, write to the Free Software
00016 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00017 */
00018 
00019 #include "pent_include.h"
00020 #include "Pathfinder.h"
00021 #include "Actor.h"
00022 #include "AnimationTracker.h"
00023 #include "SDL_timer.h"
00024 #include <cmath>
00025 
00026 #include "RenderSurface.h"
00027 #include "GameMapGump.h"
00028 #include "GUIApp.h"
00029 
00030 #ifdef DEBUG
00031 ObjId Pathfinder::visualdebug_actor = 0xFFFF;
00032 #endif
00033 
00034 struct PathNode
00035 {
00036         PathfindingState state;
00037         unsigned int depth;
00038         unsigned int cost;
00039         unsigned int heuristicTotalCost;
00040         PathNode* parent;
00041         uint32 stepsfromparent;
00042 };
00043 
00044 // NOTE: this is just to keep some statistics
00045 static unsigned int expandednodes = 0;
00046 
00047 void PathfindingState::load(Actor* actor)
00048 {
00049         actor->getLocation(x, y, z);
00050         lastanim = actor->getLastAnim();
00051         direction = actor->getDir();
00052         firststep = (actor->getActorFlags() & Actor::ACT_FIRSTSTEP) != 0;
00053         flipped = (actor->getFlags() & Item::FLG_FLIPPED) != 0;
00054         combat = actor->isInCombat();
00055 }
00056 
00057 bool PathfindingState::checkPoint(sint32 x_, sint32 y_, sint32 z_,
00058                                                                   int range)
00059 {
00060         int distance = (x - x_) * (x - x_) + (y - y_) * (y - y_) + (z - z_) * (z - z_); 
00061         return distance < range * range;
00062 }
00063 
00064 bool PathfindingState::checkItem(Item* item, int xyRange, int zRange)
00065 {
00066         sint32 itemX, itemY, itemZ;
00067         sint32 itemXd, itemYd, itemZd;
00068         sint32 itemXmin, itemYmin;
00069 
00070         item->getLocationAbsolute(itemX, itemY, itemZ);
00071         item->getFootpadWorld(itemXd, itemYd, itemZd);
00072 
00073         itemXmin = itemX - itemXd;
00074         itemYmin = itemY - itemYd;
00075 
00076         int range = 0;
00077         if (x - itemX > range)
00078                 range = x - itemX;
00079         if (itemXmin - x > range)
00080                 range = itemXmin - x;
00081         if (y - itemY > range)
00082                 range = y - itemY;
00083         if (itemYmin - y > range)
00084                 range = itemYmin - y;
00085 
00086         // FIXME: check z properly
00087 
00088         return (range <= xyRange);
00089 }
00090 
00091 bool PathfindingState::checkHit(Actor* actor, Actor* target)
00092 {
00093 #if 0
00094         pout << "Trying hit in direction " << actor->getDirToItemCentre(*target) << std::endl;
00095 #endif
00096         AnimationTracker tracker;
00097         if (!tracker.init(actor, Animation::attack,
00098                                           actor->getDirToItemCentre(*target), this))
00099         {
00100                 return false;
00101         }
00102 
00103         while (tracker.step()) {
00104                 if (tracker.hitSomething()) break;
00105         }
00106 
00107         ObjId hit = tracker.hitSomething();
00108         if (hit == target->getObjId()) return true;
00109 
00110         return false;   
00111 }
00112 
00113 bool PathNodeCmp::operator()(PathNode* n1, PathNode* n2)
00114 {
00115         return (n1->heuristicTotalCost > n2->heuristicTotalCost);
00116 }
00117 
00118 Pathfinder::Pathfinder()
00119 {
00120         expandednodes = 0;
00121 }
00122 
00123 Pathfinder::~Pathfinder()
00124 {
00125 #if 1
00126         pout << "~Pathfinder: " << nodelist.size() << " nodes, "
00127                  << expandednodes << " expanded nodes in " << expandtime << "ms." << std::endl;
00128 #endif
00129 
00130         // clean up nodes
00131         std::list<PathNode*>::iterator iter;
00132         for (iter = nodelist.begin(); iter != nodelist.end(); ++iter)
00133                 delete *iter;
00134         nodelist.clear();
00135 }
00136 
00137 void Pathfinder::init(Actor* actor_, PathfindingState* state)
00138 {
00139         actor = actor_;
00140 
00141         actor->getFootpadWorld(actor_xd,actor_yd,actor_zd);
00142 
00143         if (state)
00144                 start = *state;
00145         else
00146                 start.load(actor);
00147 }
00148 
00149 void Pathfinder::setTarget(sint32 x, sint32 y, sint32 z)
00150 {
00151         targetx = x;
00152         targety = y;
00153         targetz = z;
00154         targetitem = 0;
00155         hitmode = false;
00156 }
00157 
00158 void Pathfinder::setTarget(Item* item, bool hit)
00159 {
00160         targetitem = item;
00161         while (targetitem->getParentAsContainer())
00162                 targetitem = targetitem->getParentAsContainer();
00163 
00164         // set target to centre of item for the cost heuristic
00165         item->getCentre(targetx, targety, targetz);
00166         targetz = item->getZ();
00167 
00168         if (hit) {
00169                 assert(start.combat);
00170                 assert(p_dynamic_cast<Actor*>(targetitem));
00171                 hitmode = true;
00172         } else {
00173                 hitmode = false;
00174         }
00175 }
00176 
00177 bool Pathfinder::canReach()
00178 {
00179         std::vector<PathfindingAction> path;
00180         return pathfind(path);
00181 }
00182 
00183 bool Pathfinder::alreadyVisited(sint32 x, sint32 y, sint32 z)
00184 {
00186 
00187         std::list<PathfindingState>::iterator iter;
00188 
00189         for (iter = visited.begin(); iter != visited.end(); ++iter)
00190                 if (iter->checkPoint(x,y,z,8))
00191                         return true;
00192 
00193         return false;
00194 }
00195 
00196 bool Pathfinder::checkTarget(PathNode* node)
00197 {
00198         // TODO: these ranges are probably a bit too high,
00199         // but otherwise it won't work properly yet -wjp
00200         if (targetitem) {
00201                 if (hitmode) {
00202                         return node->state.checkHit(actor,
00203                                                                                 p_dynamic_cast<Actor*>(targetitem));
00204                 } else {
00205                         return node->state.checkItem(targetitem, 32, 8);
00206                 }
00207         } else {
00208                 return node->state.checkPoint(targetx, targety, targetz, 48);
00209         }
00210 }
00211 
00212 unsigned int Pathfinder::costHeuristic(PathNode* node)
00213 {
00214         unsigned int cost = node->cost;
00215 
00216 #if 0
00217         double sqrddist;
00218 
00219         sqrddist = (targetx - node->state.x + actor_xd/2) *
00220                 (targetx - node->state.x + actor_xd/2);
00221         sqrddist += (targety - node->state.y + actor_yd/2) *
00222                 (targety - node->state.y + actor_yd/2);
00223 
00224         unsigned int dist = static_cast<unsigned int>(std::sqrt(sqrddist));
00225 #else
00226         // This calculates the distance to the target using only lines in
00227         // the 8 available directions (instead of the straight line above)
00228         int xd = abs(targetx - node->state.x + actor_xd/2);
00229         int yd = abs(targety - node->state.y + actor_yd/2);
00230         double m = (xd < yd) ? xd : yd;
00231         unsigned int dist = abs(xd-yd) + static_cast<unsigned int>(m*1.41421356);
00232 
00233 #endif
00234 
00235 #if 0
00237         // (using 32 for now)
00238         dist /= 32;
00239 
00240         node->heuristicTotalCost = cost + (dist*4); 
00241 #else
00242 
00243         // Weigh remaining distance more than already travelled distance,
00244         // to try to explore more nodes closer to the target.
00245         node->heuristicTotalCost = 2*cost + 3*dist;
00246 #endif
00247 
00248         return node->heuristicTotalCost;
00249 }
00250 
00251 
00252 #ifdef DEBUG
00253 
00254 // FIXME: these functions assume that we're using a 2x scaler...
00255 // (and the whole system is generally a very big hack...)
00256 
00257 static void drawbox(Item* item)
00258 {
00259         RenderSurface* screen = GUIApp::get_instance()->getScreen();
00260         sint32 cx, cy, cz;
00261 
00262         GUIApp::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
00263 
00264         Pentagram::Rect d;
00265         screen->GetSurfaceDims(d);
00266 
00267         sint32 ix, iy, iz;
00268         item->getLocation(ix, iy, iz);
00269 
00270         sint32 xd, yd, zd;
00271         item->getFootpadWorld(xd, yd, zd);
00272 
00273         ix -= cx;
00274         iy -= cy;
00275         iz -= cz;
00276 
00277         sint32 x0,y0,x1,y1,x2,y2,x3,y3;
00278 
00279         x0 = (d.w/2) + (ix - iy)/2;
00280         y0 = (d.h/2) + (ix + iy)/4 - iz*2;
00281 
00282         x1 = (d.w/2) + (ix - iy)/2;
00283         y1 = (d.h/2) + (ix + iy)/4 - (iz+zd)*2;
00284 
00285         x2 = (d.w/2) + (ix-xd - iy)/2;
00286         y2 = (d.h/2) + (ix-xd + iy)/4 - iz*2;
00287 
00288         x3 = (d.w/2) + (ix - iy+yd)/2;
00289         y3 = (d.h/2) + (ix + iy-yd)/4 - iz*2;
00290 
00291         screen->Fill32(0xFF0000FF, x0-1, y0-1, 3, 3);
00292 
00293         screen->DrawLine32(0xFF00FF00, x0, y0, x1, y1);
00294         screen->DrawLine32(0xFF00FF00, x0, y0, x2, y2);
00295         screen->DrawLine32(0xFF00FF00, x0, y0, x3, y3);
00296 }
00297 
00298 static void drawdot(sint32 x, sint32 y, sint32 z, int size, uint32 rgb)
00299 {
00300         RenderSurface* screen = GUIApp::get_instance()->getScreen();
00301         sint32 cx, cy, cz;
00302 
00303         GUIApp::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
00304 
00305         Pentagram::Rect d;
00306         screen->GetSurfaceDims(d);
00307         x -= cx;
00308         y -= cy;
00309         z -= cz;
00310         sint32 x0,y0;
00311         x0 = (d.w/2) + (x - y)/2;
00312         y0 = (d.h/2) + (x + y)/4 - z*2;
00313         screen->Fill32(rgb, x0-size, y0-size, 2*size+1, 2*size+1);
00314 }
00315 
00316 static void drawedge(PathNode* from, PathNode* to, uint32 rgb)
00317 {
00318         RenderSurface* screen = GUIApp::get_instance()->getScreen();
00319         sint32 cx, cy, cz;
00320 
00321         GUIApp::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
00322 
00323         Pentagram::Rect d;
00324         screen->GetSurfaceDims(d);
00325 
00326         sint32 x0, y0, x1, y1;
00327 
00328         cx = from->state.x - cx;
00329         cy = from->state.y - cy;
00330         cz = from->state.z - cz;
00331 
00332         x0 = (d.w/2) + (cx - cy)/2;
00333         y0 = (d.h/2) + (cx + cy)/4 - cz*2;
00334 
00335         GUIApp::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
00336 
00337         cx = to->state.x - cx;
00338         cy = to->state.y - cy;
00339         cz = to->state.z - cz;
00340 
00341         x1 = (d.w/2) + (cx - cy)/2;
00342         y1 = (d.h/2) + (cx + cy)/4 - cz*2;
00343 
00344         screen->DrawLine32(rgb, x0, y0, x1, y1);
00345 }
00346 
00347 static void drawpath(PathNode* to, uint32 rgb, bool done)
00348 {
00349         PathNode* n1 = to;
00350         PathNode* n2 = to->parent;
00351 
00352         while (n2) {
00353                 drawedge(n1, n2, rgb);
00354 
00355                 if (done && n1 == to)
00356                         drawdot(n1->state.x, n1->state.y, n1->state.z, 2, 0xFFFF0000);
00357                 else
00358                         drawdot(n1->state.x, n1->state.y, n1->state.z, 1, 0xFFFFFFFF);
00359 
00360 
00361                 drawdot(n2->state.x, n2->state.y, n2->state.z, 2, 0xFFFFFFFF);
00362 
00363                 n1 = n2;
00364                 n2 = n1->parent;
00365         }
00366 }
00367 
00368 #endif
00369 
00370 void Pathfinder::newNode(PathNode* oldnode, PathfindingState& state,
00371                                                  unsigned int steps)
00372 {
00373         PathNode* newnode = new PathNode();
00374         nodelist.push_back(newnode); // for garbage collection
00375         newnode->state = state;
00376         newnode->parent = oldnode;
00377         newnode->depth = oldnode->depth + 1;
00378         newnode->stepsfromparent = 0;
00379         
00380         double sqrddist;
00381         
00382         sqrddist = ((newnode->state.x - oldnode->state.x)*
00383                                 (newnode->state.x - oldnode->state.x));
00384         sqrddist += ((newnode->state.y - oldnode->state.y)*
00385                                  (newnode->state.y - oldnode->state.y));
00386         sqrddist += ((newnode->state.z - oldnode->state.z)*
00387                                  (newnode->state.z - oldnode->state.z));
00388         
00389         unsigned int dist;
00390         dist = static_cast<unsigned int>(std::sqrt(sqrddist));
00391         
00392         int turn = 0;
00393         
00394         if (oldnode->depth > 0) {
00395                 turn = state.direction - oldnode->state.direction;
00396                 if (turn < 0) turn = -turn;
00397                 if (turn > 4) turn = 8 - turn;
00398         }
00399         
00400         newnode->cost = oldnode->cost + dist + 32*turn; 
00401 
00402         bool done = checkTarget(newnode);
00403         if (done)
00404                 newnode->heuristicTotalCost = 0;
00405         else
00406                 costHeuristic(newnode);
00407 
00408 #if 0
00409         perr << "trying dir " << state.direction;
00410 
00411         if (steps > 0) {
00412                 perr << ", " << steps << " steps";
00413         }
00414         perr << " from ("
00415                  << oldnode->state.x << "," << oldnode->state.y << ") to ("
00416                  << newnode->state.x << "," << newnode->state.y
00417                  << "), cost = " << newnode->cost << ", heurtotcost = "
00418                  << newnode->heuristicTotalCost << std::endl;
00419 #endif
00420 
00421 #ifdef DEBUG
00422         if (actor->getObjId() == visualdebug_actor) {
00423                 RenderSurface* screen = GUIApp::get_instance()->getScreen();
00424                 screen->BeginPainting();
00425                 drawpath(newnode, 0xFFFFFF00, done);
00426                 screen->EndPainting();
00427                 SDL_Delay(250);
00428                 if (!done) {
00429                         screen->BeginPainting();
00430                         drawpath(newnode, 0xFFB0B000, done);
00431                         screen->EndPainting();
00432                 }
00433         }
00434 #endif
00435 
00436         nodes.push(newnode);    
00437 }
00438 
00439 void Pathfinder::expandNode(PathNode* node)
00440 {
00441         Animation::Sequence walkanim = Animation::walk;
00442         PathfindingState state, closeststate;
00443         AnimationTracker tracker;
00444         expandednodes++;
00445 
00446         if (actor->isInCombat())
00447                 walkanim = Animation::advance;
00448 
00449         // try walking in all 8 directions
00450         for (uint32 dir = 0; dir < 8; ++dir) {
00451                 state = node->state;
00452                 state.lastanim = walkanim;
00453                 state.direction = dir;
00454                 state.combat = actor->isInCombat();
00455                 uint32 steps = 0, beststeps = 0;
00456                 int bestsqdist;
00457                 bestsqdist = (targetx - node->state.x + actor_xd/2) *
00458                         (targetx - node->state.x + actor_xd/2);
00459                 bestsqdist += (targety - node->state.y + actor_yd/2) *
00460                         (targety - node->state.y + actor_yd/2);
00461 
00462                 if (!tracker.init(actor, walkanim, dir, &state)) continue;
00463 
00464                 // determine how far the actor will travel if the animation runs to completion
00465                 sint32 max_endx, max_endy;
00466                 tracker.evaluateMaxAnimTravel(max_endx, max_endy, dir);
00467                 if(alreadyVisited(max_endx, max_endy, state.z)) continue;
00468                 int sqrddist;
00469                 int x_travel = abs(max_endx - state.x);
00470                 int xy_maxtravel = x_travel;    // don't have the max(a,b) macro...
00471                 int y_travel = abs(max_endy - state.y);
00472                 if(y_travel > xy_maxtravel) xy_maxtravel = y_travel;
00473 
00474                 sqrddist = x_travel * x_travel + y_travel * y_travel;
00475                 if(sqrddist > 400)
00476                 {
00477                         // range is greater than 20; see if a node has been visited at range 10
00478                         if(alreadyVisited(      state.x + x_travel * 10 / xy_maxtravel,
00479                                                                 state.y + y_travel * 10 / xy_maxtravel,
00480                                                                 state.z)) {
00481                                 continue;
00482                         }
00483                 }
00484                 while (tracker.step())
00485                 {
00486                         steps++;
00487                         tracker.updateState(state);
00488 
00489                         int sqrddist;
00490                         sqrddist = (targetx - state.x + actor_xd/2) *
00491                                 (targetx - state.x + actor_xd/2);
00492                         sqrddist += (targety - state.y + actor_yd/2) *
00493                                 (targety - state.y + actor_yd/2);
00494 
00495                         if (sqrddist < bestsqdist) {
00496                                 bestsqdist = sqrddist;
00497                                 beststeps = steps;
00498                                 closeststate = state;
00499                         }
00500                 }
00501 
00502                 if (tracker.isDone()) {
00503                         tracker.updateState(state);
00504                         if (!alreadyVisited(state.x, state.y, state.z)) {
00505                                 newNode(node, state, 0);
00506                                 visited.push_back(state);
00507                         }
00508                 }
00509                 else
00510                 {
00511                         // an obstruction was encountered, so generate a visited node to block
00512                         // future evaluation at the endpoint.
00513                         visited.push_back(state);
00514                 }
00515 
00516                 // TODO: maybe only allow partial steps close to target?
00517                 if (beststeps != 0 && (beststeps != steps ||
00518                                                            (!tracker.isDone() && targetitem)))
00519                 {
00520                         newNode(node, closeststate, beststeps);
00521                         visited.push_back(closeststate);
00522                 }
00523         }
00524 }
00525 
00526 bool Pathfinder::pathfind(std::vector<PathfindingAction>& path)
00527 {
00528 #if 0
00529         pout << "Actor " << actor->getObjId();
00530 
00531         if (targetitem) {
00532                 pout << " pathfinding to item: ";
00533                 targetitem->dumpInfo();
00534         } else {
00535                 pout << " pathfinding to (" << targetx << "," << targety << "," << targetz << ")" << std::endl;
00536         }
00537 #endif
00538 
00539 #ifdef DEBUG
00540         if (actor->getObjId() == visualdebug_actor) {
00541                 RenderSurface* screen = GUIApp::get_instance()->getScreen();
00542                 screen->BeginPainting();
00543                 if (targetitem)
00544                         drawbox(targetitem);
00545                 else
00546                         drawdot(targetx, targety, targetz, 2, 0xFF0000FF);
00547                 screen->EndPainting();
00548         }
00549 #endif
00550 
00551 
00552         path.clear();
00553 
00554         PathNode* startnode = new PathNode();
00555         startnode->state = start;
00556         startnode->cost = 0;
00557         startnode->parent = 0;
00558         startnode->depth = 0;
00559         startnode->stepsfromparent = 0;
00560         nodelist.push_back(startnode);
00561         nodes.push(startnode);
00562 
00563         unsigned int expandednodes = 0;
00564         const unsigned int NODELIMIT_MIN = 30;  
00565         const unsigned int NODELIMIT_MAX = 200; 
00566         bool found = false;
00567         Uint32 starttime = SDL_GetTicks();
00568 
00569         while (expandednodes < NODELIMIT_MAX && !nodes.empty() && !found) {
00570                 PathNode* node = nodes.top(); nodes.pop();
00571 
00572 #if 0
00573                 pout << "Trying node: (" << node->state.x << "," << node->state.y
00574                          << "," << node->state.z << ") target=(" << targetx << ","
00575                          << targety << "," << targetz << ")" << std::endl;
00576 #endif
00577 
00578                 if (checkTarget(node)) {
00579                         // done!
00580 
00581                         // find path length
00582                         PathNode* n = node;
00583                         unsigned int length = 0;
00584                         while (n->parent) {
00585                                 n = n->parent;
00586                                 length++;
00587                         }
00588 #if 0
00589                         pout << "Pathfinder: path found (length = " << length << ")"
00590                                  << std::endl;
00591 #endif
00592 
00593                         unsigned int i = length;
00594                         if (length > 0) length++; // add space for final 'stand' action
00595                         path.resize(length);
00596 
00597                         // now backtrack through the nodes to assemble the final animation
00598                         while (node->parent) {
00599                                 PathfindingAction action;
00600                                 action.action = node->state.lastanim;
00601                                 action.direction = node->state.direction;
00602                                 action.steps = node->stepsfromparent;
00603                                 path[--i] = action;
00604 #if 0
00605                                 pout << "anim = " << node->state.lastanim << ", dir = "
00606                                          << node->state.direction << ", steps = "
00607                                          << node->stepsfromparent << std::endl;
00608 #endif
00609 
00610                                 //TODO: check how turns work
00611                                 //TODO: append final 'stand' animation
00612 
00613                                 node = node->parent;
00614                         }
00615 
00616                         if (length) {
00617                                 if (node->state.combat)
00618                                         path[length-1].action = Animation::combatStand;
00619                                 else
00620                                         path[length-1].action = Animation::stand;
00621                                 path[length-1].direction = path[length-2].direction;
00622                         }
00623 
00624                         expandtime = SDL_GetTicks() - starttime;
00625                         return true;
00626                 }
00627 
00628                 expandNode(node);
00629                 expandednodes++;
00630 
00631                 if(expandednodes >= NODELIMIT_MIN && ((expandednodes) % 5) == 0)
00632                 {
00633                         Uint32 elapsed_ms = SDL_GetTicks() - starttime;
00634                         if(elapsed_ms > 350) break;
00635                 }
00636         }
00637 
00638         expandtime = SDL_GetTicks() - starttime;
00639 
00640 #if 0
00641         static sint32 pfcalls = 0;
00642         static sint32 pftotaltime = 0;
00643         pfcalls++;
00644         pftotaltime += expandtime;
00645         pout << "maxout average = " << (pftotaltime / pfcalls) << "ms." << std::endl;
00646 #endif
00647 
00648         return false;
00649 }
00650 
00651 
00652 #ifdef DEBUG
00653 void Pathfinder::ConCmd_visualDebug(const Console::ArgvType &argv)
00654 {
00655         if (argv.size() != 2) {
00656                 pout << "Usage: Pathfinder::visualDebug objid" << std::endl;
00657                 pout << "Specify objid -1 to stop tracing." << std::endl;
00658                 return;
00659         }
00660         int p = strtol(argv[1].c_str(), 0, 0);
00661         if (p == -1) {
00662                 visualdebug_actor = 0xFFFF;
00663                 pout << "Pathfinder: stopped visual tracing" << std::endl;
00664         } else {
00665                 visualdebug_actor = (uint16)p;
00666                 pout << "Pathfinder: visually tracing actor " << visualdebug_actor << std::endl;
00667         }
00668 }
00669 #endif

Generated on Fri Jul 27 22:27:29 2007 for pentagram by  doxygen 1.4.7