/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/pathfinder/dir_finder.h"
#include "ultima/nuvie/pathfinder/seek_path.h"
namespace Ultima {
namespace Nuvie {
using Std::vector;
SeekPath::SeekPath() {
}
SeekPath::~SeekPath() {
}
/* Get two relative directions that a line can travel to trace around an
obstacle towards `xdir',`ydir'. */
bool SeekPath::get_obstacle_tracer(const MapCoord &start, sint32 xdir, sint32 ydir,
sint32 &Axdir, sint32 &Aydir,
sint32 &Bxdir, sint32 &Bydir) {
if (xdir && ydir) { // original direction is diagonal
MapCoord checkA(start.x + xdir, start.y, start.z);
MapCoord checkB(start.x, start.y + ydir, start.z);
if (check_loc(checkA)) { // can go in X
Axdir = xdir;
Aydir = 0; // Horizontal; in X direction
} else { // X is blocked, must go in Y
Axdir = 0;
Aydir = -ydir; // Vertical; opposite Y direction
}
if (check_loc(checkB)) { // can go in Y
Bxdir = 0;
Bydir = ydir; // Vertical; in Y direction
} else { // Y is blocked, must go in X
Bxdir = -xdir;
Bydir = 0; // Horizontal; opposite X direction
}
} else { // orthagonal
// scan in perpendicular straight line
Axdir = ydir;
Aydir = xdir;
Bxdir = -Axdir;
Bydir = -Aydir;
}
return false;
}
/* Returns true if an opening is found along the original line. */
bool SeekPath::trace_check_obstacle(bool &turned, MapCoord &line, sint32 &deltax, sint32 &deltay, sint32 &xdir, sint32 &ydir, Std::vector *scan) {
MapCoord obstacle(line.x + xdir, line.y + ydir, line.z);
if (check_loc(obstacle)) { // no obstacle here; able to move closer
if (scan->empty() || scan->back() != line)
scan->push_back(line); // *ADD TRACE NODE*
if (!turned) {
scan->push_back(obstacle); // *ADD TRACE NODE*
return true;
}
// bend line TOWARDS obstacle
line.x += xdir;
line.y += ydir; // step forward
sint32 old_deltax = deltax, old_deltay = deltay;
deltax = xdir;
deltay = ydir; // now moving in that direction
xdir = -old_deltax;
ydir = -old_deltay; // and looking away from old delta
turned = false;
}
return false;
}
void SeekPath::trace_around_corner(MapCoord &line, sint32 &deltax, sint32 &deltay, sint32 &xdir, sint32 &ydir, Std::vector *scan) {
line.x -= deltax;
line.y -= deltay; // step back
if (scan->empty() || scan->back() != line)
scan->push_back(line); // *ADD TRACE NODE*
sint8 old_xdir = xdir, old_ydir = ydir;
xdir = deltax;
ydir = deltay; // now looking in that direction
deltax = -old_xdir;
deltay = -old_ydir; // and moving scan away from old obstacle
}
/* Trace an obstacle from 'start' towards the direction 'deltax' and 'deltay',
looking for openings towards 'xdir' and 'ydir'. The scan can bend 90 degrees
to get around walls. Returns true if something that looks like an opening
has been found. Trace nodes are placed at turns and where the scan ends. */
bool SeekPath::trace_obstacle(MapCoord line, sint32 deltax, sint32 deltay, sint32 xdir, sint32 ydir, Std::vector *scan) {
const uint32 scan_max = 8; // number of squares to check before giving up
bool bend = false; // true if the scanning line is rotated 90 degrees
uint32 s = 0;
do {
line.x += deltax;
line.y += deltay;
if (!check_loc(line)) {
if (!bend) { // bend line AWAY from obstacle
trace_around_corner(line, deltax, deltay, xdir, ydir, scan);
bend = true;
} else // blocked (we only allow one turn)
break;
} else if (trace_check_obstacle(bend, line, deltax, deltay, xdir, ydir, scan))
return true;
} while (++s < scan_max);
scan->resize(0);
return false;
}
// choose which set of nodes traced around an obstacle should be used for a path
Std::vector *SeekPath::get_best_scan(const MapCoord &start, const MapCoord &goal) {
if (A_scan.empty() && B_scan.empty())
return 0;
if (A_scan.empty())
return &B_scan;
if (B_scan.empty())
return &A_scan;
if (B_scan.back().distance(goal) < A_scan.back().distance(goal))
return &B_scan;
return &A_scan;
}
// copy A or B nodes to the path
void SeekPath::create_path(const MapCoord &start, const MapCoord &goal) {
vector *nodes = get_best_scan(start, goal); // points to line A or B
MapCoord prev_node(start);
// these nodes are only at certain locations in the path, so all steps in
// between have to be added
while (nodes && !nodes->empty()) {
// create steps from prev_node to this_node
MapCoord this_node = nodes->front();
nodes->erase(nodes->begin());
if (this_node == start) // start is the first prev_node, which results in duplicate steps
continue;
sint16 dx = clamp(this_node.x - prev_node.x, -1, 1), dy = clamp(this_node.y - prev_node.y, -1, 1);
do {
prev_node = prev_node.abs_coords(dx, dy); // add dx & dy
add_step(prev_node);
} while (prev_node != this_node);
prev_node = this_node;
}
}
/* Returns true if a path is found around the obstacle between locations. */
bool SeekPath::path_search(const MapCoord &start, const MapCoord &goal) {
sint8 xdir = 0, ydir = 0; // direction start->goal
DirFinder::get_normalized_dir(start, goal, xdir, ydir); // init xdir & ydir
// confirm that goal is more than one square away
if ((start.x + xdir) == goal.x && (start.y + ydir) == goal.y)
return false;
// determine if each line (A and B) will be vertical or horizontal
sint32 Ax = 0, Ay = 0, Bx = 0, By = 0; // vector of line segments to scan
get_obstacle_tracer(start, xdir, ydir, Ax, Ay, Bx, By);
// direction from line to scan is perpendicular to line, towards obstacle
delete_nodes();
bool successA = trace_obstacle(start, Ax, Ay, (Ay) ? xdir : 0, (Ax) ? ydir : 0, &A_scan);
bool successB = trace_obstacle(start, Bx, By, (By) ? xdir : 0, (Bx) ? ydir : 0, &B_scan);
if (successA || successB)
create_path(start, goal); // create path from available nodes
delete_nodes();
return (successA || successB);
}
void SeekPath::delete_nodes() {
A_scan.clear();
B_scan.clear();
}
} // End of namespace Nuvie
} // End of namespace Ultima