307 lines
9.5 KiB
C++
307 lines
9.5 KiB
C++
/* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
//=============================================================================
|
|
//
|
|
// New jump point search (JPS) A* pathfinder by Martin Sedlak.
|
|
//
|
|
//=============================================================================
|
|
|
|
#include "ags/engine/ac/route_finder_impl.h"
|
|
#include "ags/shared/ac/common.h" // quit()
|
|
#include "ags/engine/ac/move_list.h" // MoveList
|
|
#include "ags/shared/ac/common_defines.h"
|
|
#include "ags/shared/gfx/bitmap.h"
|
|
#include "ags/shared/debugging/out.h"
|
|
#include "ags/engine/ac/route_finder_jps.h"
|
|
#include "ags/globals.h"
|
|
|
|
namespace AGS3 {
|
|
|
|
using AGS::Shared::Bitmap;
|
|
|
|
namespace AGS {
|
|
namespace Engine {
|
|
namespace RouteFinder {
|
|
|
|
static const int MAXNAVPOINTS = MAXNEEDSTAGES;
|
|
|
|
void init_pathfinder() {
|
|
}
|
|
|
|
void shutdown_pathfinder() {
|
|
}
|
|
|
|
void set_wallscreen(Bitmap *wallscreen_) {
|
|
_G(wallscreen) = wallscreen_;
|
|
}
|
|
|
|
static void sync_nav_wallscreen() {
|
|
// FIXME: this is dumb, but...
|
|
_GP(nav).Resize(_G(wallscreen)->GetWidth(), _G(wallscreen)->GetHeight());
|
|
|
|
for (int y = 0; y < _G(wallscreen)->GetHeight(); y++)
|
|
_GP(nav).SetMapRow(y, _G(wallscreen)->GetScanLine(y));
|
|
}
|
|
|
|
int can_see_from(int x1, int y1, int x2, int y2) {
|
|
_G(lastcx) = x1;
|
|
_G(lastcy) = y1;
|
|
|
|
if ((x1 == x2) && (y1 == y2))
|
|
return 1;
|
|
|
|
sync_nav_wallscreen();
|
|
|
|
return !_GP(nav).TraceLine(x1, y1, x2, y2, _G(lastcx), _G(lastcy));
|
|
}
|
|
|
|
void get_lastcpos(int &lastcx_, int &lastcy_) {
|
|
lastcx_ = _G(lastcx);
|
|
lastcy_ = _G(lastcy);
|
|
}
|
|
|
|
// new routing using JPS
|
|
static int find_route_jps(int fromx, int fromy, int destx, int desty) {
|
|
sync_nav_wallscreen();
|
|
|
|
std::vector<int> path, cpath;
|
|
path.clear();
|
|
cpath.clear();
|
|
|
|
if (_GP(nav).NavigateRefined(fromx, fromy, destx, desty, path, cpath) == Navigation::NAV_UNREACHABLE)
|
|
return 0;
|
|
|
|
_G(num_navpoints) = 0;
|
|
|
|
// new behavior: cut path if too complex rather than abort with error message
|
|
int count = MIN<int>((int)cpath.size(), MAXNAVPOINTS);
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
int x, y;
|
|
_GP(nav).UnpackSquare(cpath[i], x, y);
|
|
|
|
_G(navpoints)[_G(num_navpoints)++] = {x, y};
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
inline fixed input_speed_to_fixed(int speed_val) {
|
|
// negative move speeds like -2 get converted to 1/2
|
|
if (speed_val < 0) {
|
|
return itofix(1) / (-speed_val);
|
|
} else {
|
|
return itofix(speed_val);
|
|
}
|
|
}
|
|
|
|
void set_route_move_speed(int speed_x, int speed_y) {
|
|
_G(move_speed_x) = input_speed_to_fixed(speed_x);
|
|
_G(move_speed_y) = input_speed_to_fixed(speed_y);
|
|
}
|
|
|
|
inline fixed calc_move_speed_at_angle(fixed speed_x, fixed speed_y, fixed xdist, fixed ydist) {
|
|
fixed useMoveSpeed;
|
|
if (speed_x == speed_y) {
|
|
useMoveSpeed = speed_x;
|
|
} else {
|
|
// different X and Y move speeds
|
|
// the X proportion of the movement is (x / (x + y))
|
|
fixed xproportion = fixdiv(xdist, (xdist + ydist));
|
|
|
|
if (speed_x > speed_y) {
|
|
// speed = y + ((1 - xproportion) * (x - y))
|
|
useMoveSpeed = speed_y + fixmul(xproportion, speed_x - speed_y);
|
|
} else {
|
|
// speed = x + (xproportion * (y - x))
|
|
useMoveSpeed = speed_x + fixmul(itofix(1) - xproportion, speed_y - speed_x);
|
|
}
|
|
}
|
|
return useMoveSpeed;
|
|
}
|
|
|
|
// Calculates the X and Y per game loop, for this stage of the movelist
|
|
void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed move_speed_y) {
|
|
// work out the x & y per move. First, opp/adj=tan, so work out the angle
|
|
if (mlsp->pos[aaa] == mlsp->pos[aaa + 1]) {
|
|
mlsp->xpermove[aaa] = 0;
|
|
mlsp->ypermove[aaa] = 0;
|
|
return;
|
|
}
|
|
|
|
short ourx = mlsp->pos[aaa].X;
|
|
short oury = mlsp->pos[aaa].Y;
|
|
short destx = mlsp->pos[aaa + 1].X;
|
|
short desty = mlsp->pos[aaa + 1].Y;
|
|
|
|
// Special case for vertical and horizontal movements
|
|
if (ourx == destx) {
|
|
mlsp->xpermove[aaa] = 0;
|
|
mlsp->ypermove[aaa] = move_speed_y;
|
|
if (desty < oury)
|
|
mlsp->ypermove[aaa] = -mlsp->ypermove[aaa];
|
|
|
|
return;
|
|
}
|
|
|
|
if (oury == desty) {
|
|
mlsp->xpermove[aaa] = move_speed_x;
|
|
mlsp->ypermove[aaa] = 0;
|
|
if (destx < ourx)
|
|
mlsp->xpermove[aaa] = -mlsp->xpermove[aaa];
|
|
|
|
return;
|
|
}
|
|
|
|
fixed xdist = itofix(abs(ourx - destx));
|
|
fixed ydist = itofix(abs(oury - desty));
|
|
|
|
fixed useMoveSpeed = calc_move_speed_at_angle(move_speed_x, move_speed_y, xdist, ydist);
|
|
|
|
fixed angl = fixatan(fixdiv(ydist, xdist));
|
|
|
|
// now, since new opp=hyp*sin, work out the Y step size
|
|
//fixed newymove = useMoveSpeed * fsin(angl);
|
|
fixed newymove = fixmul(useMoveSpeed, fixsin(angl));
|
|
|
|
// since adj=hyp*cos, work out X step size
|
|
//fixed newxmove = useMoveSpeed * fcos(angl);
|
|
fixed newxmove = fixmul(useMoveSpeed, fixcos(angl));
|
|
|
|
if (destx < ourx)
|
|
newxmove = -newxmove;
|
|
if (desty < oury)
|
|
newymove = -newymove;
|
|
|
|
mlsp->xpermove[aaa] = newxmove;
|
|
mlsp->ypermove[aaa] = newymove;
|
|
}
|
|
|
|
void recalculate_move_speeds(MoveList *mlsp, int old_speed_x, int old_speed_y, int new_speed_x, int new_speed_y) {
|
|
const fixed old_movspeed_x = input_speed_to_fixed(old_speed_x);
|
|
const fixed old_movspeed_y = input_speed_to_fixed(old_speed_y);
|
|
const fixed new_movspeed_x = input_speed_to_fixed(new_speed_x);
|
|
const fixed new_movspeed_y = input_speed_to_fixed(new_speed_y);
|
|
// save current stage's step lengths, for later onpart's update
|
|
const fixed old_stage_xpermove = mlsp->xpermove[mlsp->onstage];
|
|
const fixed old_stage_ypermove = mlsp->ypermove[mlsp->onstage];
|
|
|
|
for (int i = 0; (i < mlsp->numstage) && ((mlsp->xpermove[i] != 0) || (mlsp->ypermove[i] != 0)); ++i) {
|
|
// First three cases where the speed is a plain factor, therefore
|
|
// we may simply divide on old one and multiple on a new one
|
|
if (((old_movspeed_x == old_movspeed_y) && (new_movspeed_x == new_movspeed_y)) // diagonal move at straight 45 degrees
|
|
|| (mlsp->xpermove[i] == 0) // straight vertical move
|
|
|| (mlsp->ypermove[i] == 0)) // straight horizontal move
|
|
{
|
|
mlsp->xpermove[i] = fixdiv(fixmul(mlsp->xpermove[i], new_movspeed_x), old_movspeed_x);
|
|
mlsp->ypermove[i] = fixdiv(fixmul(mlsp->ypermove[i], new_movspeed_y), old_movspeed_y);
|
|
} else {
|
|
// Move at angle has adjusted speed factor, which we must recalculate first
|
|
short ourx = mlsp->pos[i].X;
|
|
short oury = mlsp->pos[i].Y;
|
|
short destx = mlsp->pos[i + 1].X;
|
|
short desty = mlsp->pos[i + 1].Y;
|
|
|
|
fixed xdist = itofix(abs(ourx - destx));
|
|
fixed ydist = itofix(abs(oury - desty));
|
|
fixed old_speed_at_angle = calc_move_speed_at_angle(old_movspeed_x, old_movspeed_y, xdist, ydist);
|
|
fixed new_speed_at_angle = calc_move_speed_at_angle(new_movspeed_x, new_movspeed_y, xdist, ydist);
|
|
|
|
mlsp->xpermove[i] = fixdiv(fixmul(mlsp->xpermove[i], new_speed_at_angle), old_speed_at_angle);
|
|
mlsp->ypermove[i] = fixdiv(fixmul(mlsp->ypermove[i], new_speed_at_angle), old_speed_at_angle);
|
|
}
|
|
}
|
|
|
|
// now adjust current passed stage fraction
|
|
if (mlsp->onpart >= 0.f) {
|
|
if (old_stage_xpermove != 0)
|
|
mlsp->onpart = (mlsp->onpart * fixtof(old_stage_xpermove)) / fixtof(mlsp->xpermove[mlsp->onstage]);
|
|
else
|
|
mlsp->onpart = (mlsp->onpart * fixtof(old_stage_ypermove)) / fixtof(mlsp->ypermove[mlsp->onstage]);
|
|
}
|
|
}
|
|
|
|
int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int move_id, int nocross, int ignore_walls) {
|
|
|
|
_G(wallscreen) = onscreen;
|
|
|
|
_G(num_navpoints) = 0;
|
|
|
|
if (ignore_walls || can_see_from(srcx, srcy, xx, yy)) {
|
|
_G(num_navpoints) = 2;
|
|
_G(navpoints)[0] = {srcx, srcy};
|
|
_G(navpoints)[1] = {xx, yy};
|
|
} else {
|
|
if ((nocross == 0) && (_G(wallscreen)->GetPixel(xx, yy) == 0))
|
|
return 0; // clicked on a wall
|
|
|
|
find_route_jps(srcx, srcy, xx, yy);
|
|
}
|
|
|
|
if (!_G(num_navpoints))
|
|
return 0;
|
|
|
|
// FIXME: really necessary?
|
|
if (_G(num_navpoints) == 1)
|
|
_G(navpoints)[_G(num_navpoints)++] = _G(navpoints)[0];
|
|
|
|
assert(_G(num_navpoints) <= MAXNAVPOINTS);
|
|
|
|
#ifdef DEBUG_PATHFINDER
|
|
AGS::Shared::Debug::Printf("Route from %d,%d to %d,%d - %d stages", srcx, srcy, xx, yy, _G(num_navpoints));
|
|
#endif
|
|
|
|
MoveList mlist;
|
|
mlist.numstage = _G(num_navpoints);
|
|
memcpy(&mlist.pos[0], &_G(navpoints)[0], sizeof(Point) * _G(num_navpoints));
|
|
#ifdef DEBUG_PATHFINDER
|
|
AGS::Shared::Debug::Printf("stages: %d\n", _G(num_navpoints));
|
|
#endif
|
|
|
|
const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
|
|
const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
|
|
for (int i = 0; i < _G(num_navpoints) - 1; i++) {
|
|
calculate_move_stage(&mlist, i, fix_speed_x, fix_speed_y);
|
|
}
|
|
|
|
mlist.from = {srcx, srcy};
|
|
_GP(mls)[move_id] = mlist;
|
|
return move_id;
|
|
}
|
|
|
|
bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) {
|
|
if (mlsp->numstage >= MAXNEEDSTAGES)
|
|
return false;
|
|
|
|
const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
|
|
const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
|
|
mlsp->pos[mlsp->numstage] = {x, y};
|
|
calculate_move_stage(mlsp, mlsp->numstage - 1, fix_speed_x, fix_speed_y);
|
|
mlsp->numstage++;
|
|
return true;
|
|
}
|
|
|
|
} // namespace RouteFinder
|
|
} // namespace Engine
|
|
} // namespace AGS
|
|
} // namespace AGS3
|