scummvm/engines/toon/path.cpp
Sylvain Dupont 96f4cc4b8a TOON: Pathfinding & walking improvements
Use direct line path if possible
Smooth facing direction to avoid flickering (entering zanidu shuttle for example)
Fix possible lockups

svn-id: r55624
2011-01-29 20:12:27 +00:00

455 lines
10 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#include "toon/path.h"
namespace Toon {
PathFindingHeap::PathFindingHeap() {
_count = 0;
_alloc = 0;
_data = NULL;
}
PathFindingHeap::~PathFindingHeap() {
delete[] _data;
}
int32 PathFindingHeap::init(int32 size) {
debugC(1, kDebugPath, "init(%d)", size);
delete[] _data;
_data = new HeapDataGrid[size * 2];
memset(_data, 0, sizeof(HeapDataGrid) * size * 2);
_count = 0;
_alloc = size;
return size;
}
int32 PathFindingHeap::unload() {
delete[] _data;
_data = NULL;
return 0;
}
int32 PathFindingHeap::clear() {
//debugC(1, kDebugPath, "clear()");
_count = 0;
memset(_data, 0, sizeof(HeapDataGrid) * _alloc * 2);
return 1;
}
int32 PathFindingHeap::push(int32 x, int32 y, int32 weight) {
//debugC(6, kDebugPath, "push(%d, %d, %d)", x, y, weight);
_count++;
_data[_count]._x = x;
_data[_count]._y = y;
_data[_count]._weight = weight;
int32 lMax = _count;
int32 lT = 0;
while (1) {
lT = lMax / 2;
if (lT < 1)
break;
if (_data[lT]._weight > _data[lMax]._weight) {
HeapDataGrid temp;
temp = _data[lT];
_data[lT] = _data[lMax];
_data[lMax] = temp;
lMax = lT;
} else {
break;
}
}
return 1;
}
int32 PathFindingHeap::pop(int32 *x, int32 *y, int32 *weight) {
//debugC(6, kDebugPath, "pop(x, y, weight)");
if (!_count)
return 0;
*x = _data[1]._x;
*y = _data[1]._y;
*weight = _data[1]._weight;
_data[1] = _data[_count];
_count--;
if (!_count)
return 0;
int32 lMin = 1;
int32 lT = 1;
while (1) {
lT = lMin << 1;
if (lT <= _count) {
if (lT < _count) {
if (_data[lT + 1]._weight < _data[lT]._weight)
lT++;
}
if (_data[lT]._weight <= _data[lMin]._weight) {
HeapDataGrid temp;
temp = _data[lMin];
_data[lMin] = _data[lT];
_data[lT] = temp;
lMin = lT;
} else {
break;
}
} else {
break;
}
}
return 0;
}
PathFinding::PathFinding(ToonEngine *vm) : _vm(vm) {
_width = 0;
_height = 0;
_heap = new PathFindingHeap();
_gridTemp = NULL;
_numBlockingRects = 0;
}
PathFinding::~PathFinding(void) {
if (_heap)
_heap->unload();
delete _heap;
delete[] _gridTemp;
}
bool PathFinding::isLikelyWalkable(int32 x, int32 y) {
for (int32 i = 0; i < _numBlockingRects; i++) {
if (_blockingRects[i][4] == 0) {
if (x >= _blockingRects[i][0] && x <= _blockingRects[i][2] && y >= _blockingRects[i][1] && y < _blockingRects[i][3])
return false;
} else {
int32 dx = abs(_blockingRects[i][0] - x);
int32 dy = abs(_blockingRects[i][1] - y);
if ((dx << 8) / _blockingRects[i][2] < (1 << 8) && (dy << 8) / _blockingRects[i][3] < (1 << 8)) {
return false;
}
}
}
return true;
}
bool PathFinding::isWalkable(int32 x, int32 y) {
//debugC(6, kDebugPath, "isWalkable(%d, %d)", x, y);
bool maskWalk = (_currentMask->getData(x, y) & 0x1f) > 0;
return maskWalk;
}
int32 PathFinding::findClosestWalkingPoint(int32 xx, int32 yy, int32 *fxx, int32 *fyy, int origX, int origY) {
debugC(1, kDebugPath, "findClosestWalkingPoint(%d, %d, fxx, fyy, %d, %d)", xx, yy, origX, origY);
int32 currentFound = -1;
int32 dist = -1;
int32 dist2 = -1;
if (origX == -1)
origX = xx;
if (origY == -1)
origY = yy;
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
if (isWalkable(x, y) && isLikelyWalkable(x,y)) {
int32 ndist = (x - xx) * (x - xx) + (y - yy) * (y - yy);
int32 ndist2 = (x - origX) * (x - origX) + (y - origY) * (y - origY);
if (currentFound < 0 || ndist < dist || (ndist == dist && ndist2 < dist2)) {
dist = ndist;
dist2 = ndist2;
currentFound = y * _width + x;
}
}
}
}
if (currentFound != -1) {
*fxx = currentFound % _width;
*fyy = currentFound / _width;
return 1;
} else {
*fxx = 0;
*fyy = 0;
return 0;
}
}
bool PathFinding::walkLine(int32 x, int32 y, int32 x2, int32 y2) {
uint32 bx = x << 16;
int32 dx = x2 - x;
uint32 by = y << 16;
int32 dy = y2 - y;
uint32 adx = abs(dx);
uint32 ady = abs(dy);
int32 t = 0;
if (adx <= ady)
t = ady;
else
t = adx;
int32 cdx = (dx << 16) / t;
int32 cdy = (dy << 16) / t;
int32 i = t;
_gridPathCount = 0;
while (i) {
_tempPathX[i] = bx >> 16;
_tempPathY[i] = by >> 16;
_gridPathCount++;
bx += cdx;
by += cdy;
i--;
}
_tempPathX[0] = x2;
_tempPathY[0] = y2;
return true;
}
bool PathFinding::lineIsWalkable(int32 x, int32 y, int32 x2, int32 y2) {
uint32 bx = x << 16;
int32 dx = x2 - x;
uint32 by = y << 16;
int32 dy = y2 - y;
uint32 adx = abs(dx);
uint32 ady = abs(dy);
int32 t = 0;
if (adx <= ady)
t = ady;
else
t = adx;
int32 cdx = (dx << 16) / t;
int32 cdy = (dy << 16) / t;
int32 i = t;
while (i) {
if (!isWalkable(bx >> 16, by >> 16))
return false;
bx += cdx;
by += cdy;
i--;
}
return true;
}
int32 PathFinding::findPath(int32 x, int32 y, int32 destx, int32 desty) {
debugC(1, kDebugPath, "findPath(%d, %d, %d, %d)", x, y, destx, desty);
if (x == destx && y == desty) {
_gridPathCount = 0;
return true;
}
// ignore path finding if the character is outside the screen
if (x < 0 || x > 1280 || y < 0 || y > 400 || destx < 0 || destx > 1280 || desty < 0 || desty > 400) {
_gridPathCount = 0;
return true;
}
// first test direct line
if (lineIsWalkable(x,y,destx,desty)) {
walkLine(x,y,destx,desty);
return true;
}
// no direct line, we use the standard A* algorithm
memset(_gridTemp , 0, _width * _height * sizeof(int32));
_heap->clear();
int32 curX = x;
int32 curY = y;
int32 curWeight = 0;
int32 *sq = _gridTemp;
sq[curX + curY *_width] = 1;
_heap->push(curX, curY, abs(destx - x) + abs(desty - y));
int wei = 0;
while (_heap->_count) {
wei = 0;
_heap->pop(&curX, &curY, &curWeight);
int curNode = curX + curY * _width;
int32 endX = MIN<int32>(curX + 1, _width - 1);
int32 endY = MIN<int32>(curY + 1, _height - 1);
int32 startX = MAX<int32>(curX - 1, 0);
int32 startY = MAX<int32>(curY - 1, 0);
for (int32 px = startX; px <= endX; px++) {
for (int py = startY; py <= endY; py++) {
if (px != curX || py != curY) {
wei = ((abs(px - curX) + abs(py - curY)));
int32 curPNode = px + py * _width;
if (isWalkable(px, py)) { // walkable ?
int sum = sq[curNode] + wei * (1 + (isLikelyWalkable(px,py) ? 5 : 0));
if (sq[curPNode] > sum || !sq[curPNode]) {
int newWeight = abs(destx - px) + abs(desty - py);
sq[curPNode] = sum;
_heap->push(px, py, sq[curPNode] + newWeight);
if (!newWeight)
goto next; // we found it !
}
}
}
}
}
}
next:
// let's see if we found a result !
if (!_gridTemp[destx + desty * _width]) {
// didn't find anything
_gridPathCount = 0;
return false;
}
curX = destx;
curY = desty;
int32 retPathX[4096];
int32 retPathY[4096];
int32 numpath = 0;
retPathX[numpath] = curX;
retPathY[numpath] = curY;
numpath++;
int32 bestscore = sq[destx + desty * _width];
while (1) {
int32 bestX = -1;
int32 bestY = -1;
int32 endX = MIN<int32>(curX + 1, _width - 1);
int32 endY = MIN<int32>(curY + 1, _height - 1);
int32 startX = MAX<int32>(curX - 1, 0);
int32 startY = MAX<int32>(curY - 1, 0);
for (int32 px = startX; px <= endX; px++) {
for (int32 py = startY; py <= endY; py++) {
if (px != curX || py != curY) {
wei = abs(px - curX) + abs(py - curY);
int PNode = px + py * _width;
if (sq[PNode] && (isWalkable(px, py))) {
if (sq[PNode] < bestscore) {
bestscore = sq[PNode];
bestX = px;
bestY = py;
}
}
}
}
}
if (bestX < 0 || bestY < 0)
return 0;
retPathX[numpath] = bestX;
retPathY[numpath] = bestY;
numpath++;
if ((bestX == x && bestY == y)) {
_gridPathCount = numpath;
memcpy(_tempPathX, retPathX, sizeof(int32) * numpath);
memcpy(_tempPathY, retPathY, sizeof(int32) * numpath);
return true;
}
curX = bestX;
curY = bestY;
}
return false;
}
void PathFinding::init(Picture *mask) {
debugC(1, kDebugPath, "init(mask)");
_width = mask->getWidth();
_height = mask->getHeight();
_currentMask = mask;
_heap->unload();
_heap->init(_width * _height);
delete[] _gridTemp;
_gridTemp = new int32[_width*_height];
}
void PathFinding::resetBlockingRects() {
_numBlockingRects = 0;
}
void PathFinding::addBlockingRect(int32 x1, int32 y1, int32 x2, int32 y2) {
debugC(1, kDebugPath, "addBlockingRect(%d, %d, %d, %d)", x1, y1, x2, y2);
_blockingRects[_numBlockingRects][0] = x1;
_blockingRects[_numBlockingRects][1] = y1;
_blockingRects[_numBlockingRects][2] = x2;
_blockingRects[_numBlockingRects][3] = y2;
_blockingRects[_numBlockingRects][4] = 0;
_numBlockingRects++;
}
void PathFinding::addBlockingEllipse(int32 x1, int32 y1, int32 w, int32 h) {
debugC(1, kDebugPath, "addBlockingRect(%d, %d, %d, %d)", x1, y1, w, h);
_blockingRects[_numBlockingRects][0] = x1;
_blockingRects[_numBlockingRects][1] = y1;
_blockingRects[_numBlockingRects][2] = w;
_blockingRects[_numBlockingRects][3] = h;
_blockingRects[_numBlockingRects][4] = 1;
_numBlockingRects++;
}
int32 PathFinding::getPathNodeCount() const {
return _gridPathCount;
}
int32 PathFinding::getPathNodeX(int32 nodeId) const {
return _tempPathX[ _gridPathCount - nodeId - 1];
}
int32 PathFinding::getPathNodeY(int32 nodeId) const {
return _tempPathY[ _gridPathCount - nodeId - 1];
}
} // End of namespace Toon