scummvm/engines/bladerunner/obstacles.cpp
2021-12-26 18:48:43 +01:00

989 lines
30 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/>.
*
*/
#include "bladerunner/obstacles.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/actor.h"
#include "bladerunner/savefile.h"
#include "bladerunner/scene.h" // for debug
#include "bladerunner/set.h"
#include "bladerunner/view.h"
#include "common/debug.h"
#define DISABLE_PATHFINDING 0
#define USE_PATHFINDING_EXPERIMENTAL_FIX_2 0 // Alternate Fix: Allows polygons merged on one point
#define WITHIN_TOLERANCE(a, b) (((a) - 0.009) < (b) && ((a) + 0.009) > (b))
namespace BladeRunner {
Obstacles::Obstacles(BladeRunnerEngine *vm) {
_vm = vm;
_polygons = new Polygon[kPolygonCount];
_polygonsBackup = new Polygon[kPolygonCount];
_path = new Vector2[kVertexCount];
clear();
}
Obstacles::~Obstacles() {
clear();
delete[] _polygons;
_polygons = nullptr;
delete[] _polygonsBackup;
_polygonsBackup = nullptr;
delete[] _path;
_path = nullptr;
}
void Obstacles::clear() {
for (int i = 0; i < kPolygonCount; ++i) {
_polygons[i].isPresent = false;
_polygons[i].verticeCount = 0;
for (int j = 0; j < kPolygonVertexCount; ++j) {
_polygons[i].vertices[j].x = 0.0f;
_polygons[i].vertices[j].y = 0.0f;
}
}
_pathSize = 0;
_backup = false;
_count = 0;
}
#define IN_RANGE(v, start, end) ((start) <= (v) && (v) <= (end))
/*
* This function is limited to finding intersections between
* horizontal and vertical lines!
*
* The original implementation is more general but obstacle
* polygons only consists of horizontal and vertical lines,
* and this is more numerically stable.
*/
bool Obstacles::lineLineIntersection(LineSegment a, LineSegment b, Vector2 *intersection) {
assert(a.start.x == a.end.x || a.start.y == a.end.y);
assert(b.start.x == b.end.x || b.start.y == b.end.y);
if (a.start.x > a.end.x) SWAP(a.start.x, a.end.x);
if (a.start.y > a.end.y) SWAP(a.start.y, a.end.y);
if (b.start.x > b.end.x) SWAP(b.start.x, b.end.x);
if (b.start.y > b.end.y) SWAP(b.start.y, b.end.y);
if (a.start.x == a.end.x && b.start.y == b.end.y && IN_RANGE(a.start.x, b.start.x, b.end.x) && IN_RANGE(b.start.y, a.start.y, a.end.y)) {
// A is vertical, B is horizontal
*intersection = Vector2(a.start.x, b.start.y);
return true;
}
if (a.start.y == a.end.y && b.start.x == b.end.x && IN_RANGE(a.start.y, b.start.y, b.end.y) && IN_RANGE(b.start.x, a.start.x, a.end.x)) {
// A is horizontal, B is vertical
*intersection = Vector2(b.start.x, a.start.y);
return true;
}
return false;
}
bool Obstacles::linePolygonIntersection(LineSegment lineA, VertexType lineAType, Polygon *polyB, Vector2 *intersectionPoint, int *intersectionIndex, int pathLengthSinceLastIntersection) {
bool hasIntersection = false;
float nearestIntersectionDistance = 0.0f;
for (int i = 0; i != polyB->verticeCount; ++i) {
LineSegment lineB;
lineB.start = polyB->vertices[i];
lineB.end = polyB->vertices[(i+1) % polyB->verticeCount];
VertexType lineBType = polyB->vertexType[i];
Vector2 newIntersectionPoint;
if (lineLineIntersection(lineA, lineB, &newIntersectionPoint)) {
if ((lineAType == TOP_RIGHT && lineBType == TOP_LEFT)
|| (lineAType == BOTTOM_RIGHT && lineBType == TOP_RIGHT)
|| (lineAType == BOTTOM_LEFT && lineBType == BOTTOM_RIGHT)
|| (lineAType == TOP_LEFT && lineBType == BOTTOM_LEFT)
) {
if ( (pathLengthSinceLastIntersection > 2)
|| ( (!(WITHIN_TOLERANCE(lineB.end.x, intersectionPoint->x) && WITHIN_TOLERANCE(lineB.end.y, intersectionPoint->y)))
&& (newIntersectionPoint != *intersectionPoint) )) {
float newIntersectionDistance = getLength(lineA.start.x, lineA.start.y, newIntersectionPoint.x, newIntersectionPoint.y);
if (!hasIntersection || newIntersectionDistance < nearestIntersectionDistance) {
hasIntersection = true;
nearestIntersectionDistance = newIntersectionDistance;
*intersectionPoint = newIntersectionPoint;
*intersectionIndex = i;
}
}
}
}
}
return hasIntersection;
}
/*
* Polygons vertices are defined in clock-wise order
* starting at the top-most, right-most corner.
*
* When merging two polygons, we start at the top-most, right-most vertex.
* The polygon with this vertex starts is the primary polygon.
* We follow the edges until we find an intersection with the secondary polygon,
* in which case we switch primary and secondary and continue following the new edges.
*
* Luckily the first two polygons added in RC01 (A, then B) are laid as as below,
* making an ideal test case.
*
* Merge order: (B0,B1) (B1,B2) (B2,J) (J,A2) (A2,A3) (A3,A0) (A0,I) (I,B0)
*
* 0,0 ---> x
* |
* | primary
* | B 0 ----- 1
* | | |
* | A 0 --I-- 1 |
* | | | | |
* | | 3 --J-- 2
* | | |
* | 3 ----- 2
* | secondary
* v y
*/
bool Obstacles::mergePolygons(Polygon &polyA, Polygon &polyB) {
bool flagDidMergePolygons = false;
Polygon polyMerged;
polyMerged.rect = merge(polyA.rect, polyB.rect);
Polygon *polyPrimary, *polySecondary;
if (polyA.rect.y0 < polyB.rect.y0 || (polyA.rect.y0 == polyB.rect.y0 && polyA.rect.x0 < polyB.rect.x0)) {
polyPrimary = &polyA;
polySecondary = &polyB;
} else {
polyPrimary = &polyB;
polySecondary = &polyA;
}
Vector2 intersectionPoint;
LineSegment polyLine;
bool flagAddVertexToVertexList = true;
bool flagDidFindIntersection = false;
int vertIndex = 0;
int pathLengthSinceLastIntersection = 0; // Part of pathfinding fix 2. It's only updated when enabling that fix, otherwise it is always zero (0).
Polygon *startingPolygon = polyPrimary;
int flagDone = false;
while (!flagDone) {
VertexType polyPrimaryType;
polyLine.start = flagDidFindIntersection ? intersectionPoint : polyPrimary->vertices[vertIndex];
polyLine.end = polyPrimary->vertices[(vertIndex + 1) % polyPrimary->verticeCount];
// TODO(madmoose): How does this work when adding a new intersection point?
polyPrimaryType = polyPrimary->vertexType[vertIndex];
if (flagAddVertexToVertexList) {
#if USE_PATHFINDING_EXPERIMENTAL_FIX_2
assert(polyMerged.verticeCount < kPolygonVertexCount);
#else
// In some cases polygons will have only one intersection (touching corners) and because of that second SWAP never occurs,
// algorithm will stop only when the merged polygon is full.
if (polyMerged.verticeCount >= kPolygonVertexCount) {
flagDidMergePolygons = false;
break;
}
#endif
polyMerged.vertices[polyMerged.verticeCount] = polyLine.start;
polyMerged.vertexType[polyMerged.verticeCount] = polyPrimaryType;
++(polyMerged.verticeCount);
}
flagAddVertexToVertexList = true;
int polySecondaryIntersectionIndex = -1;
if (linePolygonIntersection(polyLine, polyPrimaryType, polySecondary, &intersectionPoint, &polySecondaryIntersectionIndex, pathLengthSinceLastIntersection)) {
if (WITHIN_TOLERANCE(intersectionPoint.x, polyLine.start.x) && WITHIN_TOLERANCE(intersectionPoint.y, polyLine.start.y)) {
flagAddVertexToVertexList = false;
// TODO(madmoose): How would this work?
--(polyMerged.verticeCount);
} else {
// Obstacles::nop
}
vertIndex = polySecondaryIntersectionIndex;
flagDidFindIntersection = true;
SWAP(polyPrimary, polySecondary);
flagDidMergePolygons = true;
#if USE_PATHFINDING_EXPERIMENTAL_FIX_2
pathLengthSinceLastIntersection = 0;
#endif
} else {
vertIndex = (vertIndex + 1) % polyPrimary->verticeCount;
#if USE_PATHFINDING_EXPERIMENTAL_FIX_2
++pathLengthSinceLastIntersection;
#endif
flagDidFindIntersection = false;
}
if (polyPrimary->vertices[vertIndex] == startingPolygon->vertices[0]) {
flagDone = true;
}
}
if (flagDidMergePolygons) {
*startingPolygon = polyMerged;
startingPolygon->isPresent = true;
if (startingPolygon == &polyA) {
polyB.isPresent = false;
} else {
polyA.isPresent = false;
}
}
return flagDidMergePolygons;
}
void Obstacles::add(RectFloat rect) {
int polygonIndex = findEmptyPolygon();
if (polygonIndex < 0) {
return;
}
rect.expand(12.0f);
rect.trunc_2_decimals();
Polygon &poly = _polygons[polygonIndex];
poly.rect = rect;
poly.vertices[0] = Vector2(rect.x0, rect.y0);
poly.vertexType[0] = TOP_LEFT;
poly.vertices[1] = Vector2(rect.x1, rect.y0);
poly.vertexType[1] = TOP_RIGHT;
poly.vertices[2] = Vector2(rect.x1, rect.y1);
poly.vertexType[2] = BOTTOM_RIGHT;
poly.vertices[3] = Vector2(rect.x0, rect.y1);
poly.vertexType[3] = BOTTOM_LEFT;
poly.isPresent = true;
poly.verticeCount = 4;
restart:
for (int i = 0; i < kPolygonCount; ++i) {
Polygon &polyA = _polygons[i];
if (!polyA.isPresent) {
continue;
}
if (polyA.verticeCount == 0) {
continue;
}
for (int j = i+1; j < kPolygonCount; ++j) {
Polygon &polyB = _polygons[j];
if (!polyB.isPresent) {
continue;
}
if (polyB.verticeCount == 0) {
continue;
}
if (!overlaps(polyA.rect, polyB.rect)) {
continue;
}
if (mergePolygons(polyA, polyB)) {
goto restart;
}
}
}
}
int Obstacles::findEmptyPolygon() const {
for (int i = 0; i < kPolygonCount; ++i) {
if (!_polygons[i].isPresent) {
return i;
}
}
return -1;
}
float Obstacles::getLength(float x0, float z0, float x1, float z1) {
if (x0 == x1) {
return fabs(z1 - z0);
}
return fabs(x1 - x0);
}
#if DISABLE_PATHFINDING
bool Obstacles::findNextWaypoint(const Vector3 &from, const Vector3 &to, Vector3 *next) {
*next = to;
return true;
}
#else
bool Obstacles::findNextWaypoint(const Vector3 &from, const Vector3 &to, Vector3 *next) {
static int recursionLevel = 0;
static bool polygonVisited[kPolygonCount];
if (++recursionLevel == 1) {
clearPath();
for (int i = 0; i != kPolygonCount; ++i) {
polygonVisited[i] = false;
}
}
int polyIndex = -1;
int polyNearVertIndex = -1;
float polyNearDist = 0.0f;
Vector2 polyNearPos;
int polyFarVertIndex = -1;
float polyFarDist = 0.0f;
Vector2 polyFarPos;
for (int i = 0; i != kPolygonCount; ++i) {
Polygon &poly = _polygons[i];
if (!poly.isPresent || polygonVisited[i]) {
continue;
}
int nearVertIndex;
float nearDist;
Vector2 nearPos;
if (!findIntersectionNearest(i, from.xz(), to.xz(), &nearVertIndex, &nearDist, &nearPos)) {
continue;
}
int farVertIndex;
float farDist;
Vector2 farPos;
int hasFar = findIntersectionFarthest(i, from.xz(), to.xz(), &farVertIndex, &farDist, &farPos);
assert(hasFar);
if (polyIndex == -1 || nearDist < polyNearDist) {
polyNearDist = nearDist;
polyNearPos = nearPos;
polyFarDist = farDist;
polyFarPos = farPos;
polyIndex = i;
polyNearVertIndex = nearVertIndex;
polyFarVertIndex = farVertIndex;
}
}
if (polyIndex < 0) {
assert(_pathSize < kVertexCount);
_path[_pathSize++] = to.xz();
} else {
polygonVisited[polyIndex] = true;
if (polyNearDist == 0.0f && polyFarDist == 0.0f) {
assert(_pathSize < kVertexCount);
_path[_pathSize++] = polyNearPos;
} else {
Vector2 pathA[kMaxPathSize];
Vector2 pathB[kMaxPathSize];
bool pathABlocked;
bool pathBBlocked;
int pathASize = buildNegativePath(polyIndex, polyNearVertIndex, polyNearPos, polyFarVertIndex, polyFarPos, pathA, kMaxPathSize, &pathABlocked);
int pathBSize = buildPositivePath(polyIndex, polyNearVertIndex, polyNearPos, polyFarVertIndex, polyFarPos, pathB, kMaxPathSize, &pathBBlocked);
float pathATotalDistance = pathTotalDistance(pathA, pathASize, from.xz(), to.xz());
float pathBTotalDistance = pathTotalDistance(pathB, pathBSize, from.xz(), to.xz());
bool usePathA;
if (pathABlocked && !pathBBlocked) {
usePathA = false;
} else if (pathBBlocked && !pathABlocked) {
usePathA = true;
} else {
usePathA = pathATotalDistance <= pathBTotalDistance;
}
if (usePathA) {
assert(_pathSize + pathASize < kVertexCount);
for (int i = 0; i != pathASize; ++i) {
_path[_pathSize + i] = pathA[i];
}
_pathSize += pathASize;
} else {
assert(_pathSize + pathBSize < kVertexCount);
for (int i = 0; i != pathBSize; ++i) {
_path[_pathSize + i] = pathB[i];
}
_pathSize += pathBSize;
}
}
assert(_pathSize > 0);
Vector3 lastPathPos(_path[_pathSize - 1].x, from.y, _path[_pathSize - 1].y);
findNextWaypoint(lastPathPos, to, next);
}
if (--recursionLevel > 1) {
return false;
}
return findFarthestAvailablePathVertex(_path, _pathSize, from, next);
}
#endif
bool Obstacles::findIntersectionNearest(int polygonIndex, Vector2 from, Vector2 to,
int *outVertexIndex, float *outDistance, Vector2 *out) const
{
float minDistance = 0.0f;
Vector2 minIntersection;
int minVertexIndex = -1;
bool hasDistance = false;
for (int i = 0; i < _polygons[polygonIndex].verticeCount; ++i) {
int nextIndex = (i + 1) % _polygons[polygonIndex].verticeCount;
Vector2 *vertices = _polygons[polygonIndex].vertices;
Vector2 intersection;
bool intersects = lineIntersection(from, to, vertices[i], vertices[nextIndex], &intersection);
if (intersects) {
float distance2 = distance(from, intersection);
if (!hasDistance || distance2 < minDistance) {
minDistance = distance2;
minIntersection = intersection;
minVertexIndex = i;
hasDistance = true;
}
}
}
*outDistance = minDistance;
*outVertexIndex = minVertexIndex;
*out = minIntersection;
return minVertexIndex != -1;
}
bool Obstacles::findIntersectionFarthest(int polygonIndex, Vector2 from, Vector2 to,
int *outVertexIndex, float *outDistance, Vector2 *out) const
{
float maxDistance = 0.0f;
Vector2 maxIntersection;
int maxVertexIndex = -1;
bool hasDistance = false;
for (int i = 0; i < _polygons[polygonIndex].verticeCount; ++i) {
int nextIndex = (i + 1) % _polygons[polygonIndex].verticeCount;
Vector2 *vertices = _polygons[polygonIndex].vertices;
Vector2 intersection;
bool intersects = lineIntersection(from, to, vertices[i], vertices[nextIndex], &intersection);
if (intersects) {
float distance2 = distance(from, intersection);
if (!hasDistance || distance2 > maxDistance) {
maxDistance = distance2;
maxIntersection = intersection;
maxVertexIndex = i;
hasDistance = true;
}
}
}
*outDistance = maxDistance;
*outVertexIndex = maxVertexIndex;
*out = maxIntersection;
return maxVertexIndex != -1;
}
float Obstacles::pathTotalDistance(const Vector2 *path, int pathSize, Vector2 from, Vector2 to) const {
// Yes, 'to' and 'from' are ignored.
float totalDistance = 0.0f;
for (int i = 0; i != pathSize - 1; ++i) {
totalDistance += distance(path[i], path[i+1]);
}
return totalDistance;
}
bool Obstacles::findPolygonVerticeByXZ(int *polygonIndex, int *verticeIndex, int *verticeCount, float x, float z) const {
*polygonIndex = -1;
*verticeIndex = -1;
*verticeCount = -1;
for (int i = 0; i != kPolygonCount; ++i) {
if (!_polygons[i].isPresent || _polygons[i].verticeCount == 0) {
continue;
}
for (int j = 0; j != _polygons[i].verticeCount; ++j) {
if (_polygons[i].vertices[j].x == x && _polygons[i].vertices[j].y == z) {
*polygonIndex = i;
*verticeIndex = j;
*verticeCount = _polygons[i].verticeCount;
return true;
}
}
}
return false;
}
bool Obstacles::findPolygonVerticeByXZWithinTolerance(float x, float z, int *polygonIndex, int *verticeIndex, int startSearchFromPolygonIdx) const {
*polygonIndex = -1;
*verticeIndex = -1;
// for (int i = 0; i != kPolygonCount; ++i) {
for (int countUp = 0, i = startSearchFromPolygonIdx; countUp != kPolygonCount; ++countUp, ++i) {
i = i % kPolygonCount; // we want to circle around to go through all polygons
if (!_polygons[i].isPresent || _polygons[i].verticeCount == 0) {
continue;
}
for (int j = 0; j != _polygons[i].verticeCount; ++j) {
if (WITHIN_TOLERANCE(_polygons[i].vertices[j].x, x) && WITHIN_TOLERANCE(_polygons[i].vertices[j].y, z)) {
*polygonIndex = i;
*verticeIndex = j;
return true;
}
}
}
return false;
}
void Obstacles::clearPath() {
_pathSize = 0;
}
int Obstacles::buildNegativePath(int polyIndex, int vertStartIndex, Vector2 startPos, int vertEndIndex, Vector2 endPos, Vector2 *path, int pathCapacity, bool *pathBlocked) {
int pathSize = 0;
*pathBlocked = false;
Polygon *poly = &_polygons[polyIndex];
/* Add start position to path */
if (_vm->_scene->_set->findWalkbox(startPos.x, startPos.y) == -1) {
*pathBlocked = true;
}
assert(pathSize < pathCapacity);
path[pathSize++] = startPos;
int i = vertStartIndex;
/* Add polygon vertices in negative iteration order */
while (true) {
Vector2 v = poly->vertices[i];
if (_vm->_scene->_set->findWalkbox(v.x, v.y) == -1) {
*pathBlocked = true;
}
assert(pathSize < pathCapacity);
path[pathSize++] = v;
i = (i + poly->verticeCount - 1) % poly->verticeCount;
if (i == vertEndIndex) {
break;
}
}
/* Add end position to path */
if (_vm->_scene->_set->findWalkbox(endPos.x, endPos.y) == -1) {
*pathBlocked = true;
}
assert(pathSize < pathCapacity);
path[pathSize++] = endPos;
return pathSize;
}
int Obstacles::buildPositivePath(int polyIndex, int vertStartIndex, Vector2 startPos, int vertEndIndex, Vector2 endPos, Vector2 *path, int pathCapacity, bool *pathBlocked) {
int pathSize = 0;
*pathBlocked = false;
Polygon *poly = &_polygons[polyIndex];
/* Add start position to path */
if (_vm->_scene->_set->findWalkbox(startPos.x, startPos.y) == -1) {
*pathBlocked = true;
}
assert(pathSize < pathCapacity);
path[pathSize++] = startPos;
int i = (vertStartIndex + 1) % poly->verticeCount;
/* Add polygon vertices in positive iteration order */
while (true) {
Vector2 v = poly->vertices[i];
if (_vm->_scene->_set->findWalkbox(v.x, v.y) == -1) {
*pathBlocked = true;
}
assert(pathSize < pathCapacity);
path[pathSize++] = v;
if (i == vertEndIndex) {
break;
}
i = (i + 1) % poly->verticeCount;
}
/* Add end position to path */
if (_vm->_scene->_set->findWalkbox(endPos.x, endPos.y) == -1) {
*pathBlocked = true;
}
assert(pathSize < pathCapacity);
path[pathSize++] = endPos;
return pathSize;
}
bool Obstacles::verticesCanIntersect(int lineType0, int lineType1, float x0, float y0, float x1, float y1) const {
if (lineType0 == TOP_LEFT && lineType1 == TOP_RIGHT) {
if (x0 > x1 && y0 < y1) return true;
}
if (lineType0 == TOP_RIGHT && lineType1 == BOTTOM_RIGHT) {
if (x0 > x1 && y0 > y1) return true;
}
if (lineType0 == BOTTOM_RIGHT && lineType1 == BOTTOM_LEFT) {
if (x0 < x1 && y0 > y1) return true;
}
if (lineType0 == BOTTOM_LEFT && lineType1 == TOP_LEFT) {
if (x0 < x1 && y0 < y1) return true;
}
if (lineType0 == TOP_RIGHT && lineType1 == TOP_LEFT) {
if (x0 > x1 || y0 < y1) return true;
}
if (lineType0 == BOTTOM_RIGHT && lineType1 == TOP_RIGHT) {
if (x0 > x1 || y0 > y1) return true;
}
if (lineType0 == BOTTOM_LEFT && lineType1 == BOTTOM_RIGHT) {
if (x0 < x1 || y0 > y1) return true;
}
if (lineType0 == TOP_LEFT && lineType1 == BOTTOM_LEFT) {
if (x0 < x1 || y0 < y1) return true;
}
return false;
}
bool Obstacles::findFarthestAvailablePathVertex(Vector2 *path, int pathSize, Vector3 start, Vector3 *next) const {
if (pathSize == 0) {
*next = start;
return false;
}
int vertexTypeStart = -1;
int vertexTypeStartPrev = -1;
int polygonIndexStart = -1;
int vertexIndexStart = -1;
bool startOnPolygon = findPolygonVerticeByXZWithinTolerance(start.x, start.z, &polygonIndexStart, &vertexIndexStart, 0);
if (startOnPolygon) {
int vertexIndexStartPrev = (vertexIndexStart - 1 + _polygons[polygonIndexStart].verticeCount) % _polygons[polygonIndexStart].verticeCount;
vertexTypeStart = _polygons[polygonIndexStart].vertexType[vertexIndexStart];
vertexTypeStartPrev = _polygons[polygonIndexStart].vertexType[vertexIndexStartPrev];
}
signed int farthestPathIndex = -1;
for (int pathVertexIdx = 0; pathVertexIdx < pathSize; ++pathVertexIdx) {
bool foundVertexNeighbor = false;
int polygonIndexPath = -1;
int vertexIndexPath = -1;
bool pathVertexOnPolygon = findPolygonVerticeByXZWithinTolerance(path[pathVertexIdx].x, path[pathVertexIdx].y, &polygonIndexPath, &vertexIndexPath, 0) == 1;
//start and current path vertices are on same polygon and are next to each other
if (pathVertexOnPolygon && polygonIndexStart == polygonIndexPath) {
int vertexIndexStartPrev = (vertexIndexStart - 1 + _polygons[polygonIndexPath].verticeCount) % _polygons[polygonIndexPath].verticeCount;
int vertexIndexStartNext = (vertexIndexStart + 1 ) % _polygons[polygonIndexPath].verticeCount;
if (vertexIndexPath == vertexIndexStartNext || vertexIndexPath == vertexIndexStartPrev || vertexIndexPath == vertexIndexStart) {
foundVertexNeighbor = true;
}
}
// neighboring vertices are always available
if (foundVertexNeighbor) {
farthestPathIndex = pathVertexIdx;
continue;
}
bool pathVertexAvailable = true;
for (int currentPolygonIdx = 0; currentPolygonIdx < kPolygonCount && pathVertexAvailable; ++currentPolygonIdx) {
Polygon *polygon = &_polygons[currentPolygonIdx];
if (!polygon->isPresent || polygon->verticeCount == 0) {
continue;
}
for (int polygonVertexIdx = 0; polygonVertexIdx < polygon->verticeCount && pathVertexAvailable; ++polygonVertexIdx) {
int polygonVertexNextIdx = (polygonVertexIdx + 1) % polygon->verticeCount;
// check intersection between start -> path and polygon edge
Vector2 intersection;
if (!lineIntersection(Vector2(start.x, start.z), path[pathVertexIdx], polygon->vertices[polygonVertexIdx], polygon->vertices[polygonVertexNextIdx], &intersection)) {
continue;
}
// intersection has to be at end of one of these points (either on this polygon or on the path or at start)
if (!(
(WITHIN_TOLERANCE(intersection.x, start.x) && WITHIN_TOLERANCE(intersection.y, start.z) )
|| (WITHIN_TOLERANCE(intersection.x, path[pathVertexIdx].x) && WITHIN_TOLERANCE(intersection.y, path[pathVertexIdx].y) )
|| (WITHIN_TOLERANCE(intersection.x, polygon->vertices[polygonVertexIdx].x) && WITHIN_TOLERANCE(intersection.y, polygon->vertices[polygonVertexIdx].y) )
|| (WITHIN_TOLERANCE(intersection.x, polygon->vertices[polygonVertexNextIdx].x) && WITHIN_TOLERANCE(intersection.y, polygon->vertices[polygonVertexNextIdx].y))
)) {
pathVertexAvailable = false;
break;
}
int polygonIndexIntersection = -1;
int vertexIndexIntersection = -1;
if (findPolygonVerticeByXZWithinTolerance(intersection.x, intersection.y, &polygonIndexIntersection, &vertexIndexIntersection, currentPolygonIdx)) {
// Intersection has to be vertex only on current polygon
// Part of pathfinding fix 2 (dealing with merge on only one edge point)
// but also speeds up process:
// we start (a cyclical) searching in Polygons array
// beginning from the current polygon index
assert(polygonIndexIntersection == currentPolygonIdx);
if (verticesCanIntersect(vertexTypeStartPrev, vertexTypeStart, start.x, start.z, path[pathVertexIdx].x, path[pathVertexIdx].y)) {
pathVertexAvailable = false;
break;
}
if ((currentPolygonIdx == polygonIndexPath && vertexIndexIntersection == vertexIndexPath)
|| (currentPolygonIdx == polygonIndexStart && vertexIndexIntersection == vertexIndexStart)
) {
continue;
}
int vertexIndexIntersectionprev = (vertexIndexIntersection - 1 + _polygons[polygonIndexIntersection].verticeCount ) % _polygons[polygonIndexIntersection].verticeCount;
if (verticesCanIntersect(_polygons[polygonIndexIntersection].vertexType[vertexIndexIntersectionprev], _polygons[polygonIndexIntersection].vertexType[vertexIndexIntersection], intersection.x, intersection.y, path[pathVertexIdx].x, path[pathVertexIdx].y)) {
pathVertexAvailable = false;
break;
}
} else {
bool startIntersectionWithinTolerance = false;
if (WITHIN_TOLERANCE(intersection.x, start.x)
&& WITHIN_TOLERANCE(intersection.y, start.z)
) {
startIntersectionWithinTolerance = true;
}
if (currentPolygonIdx == polygonIndexStart || startIntersectionWithinTolerance) {
if (polygonIndexStart >= 0 || !startIntersectionWithinTolerance) {
pathVertexAvailable = false;
break;
}
int polygonVertexType = polygon->vertexType[polygonVertexIdx];
if ((polygonVertexType == TOP_LEFT && intersection.y < path[pathVertexIdx].y)
|| (polygonVertexType == TOP_RIGHT && intersection.x > path[pathVertexIdx].x)
|| (polygonVertexType == BOTTOM_RIGHT && intersection.y > path[pathVertexIdx].y)
|| (polygonVertexType == BOTTOM_LEFT && intersection.x < path[pathVertexIdx].x)
) {
pathVertexAvailable = false;
break;
}
}
}
}
}
if (pathVertexAvailable) {
farthestPathIndex = pathVertexIdx;
}
}
if (farthestPathIndex == -1) {
*next = start;
return false;
}
next->x = path[farthestPathIndex].x;
next->z = path[farthestPathIndex].y;
bool walkboxFound;
float walkboxAltitude = _vm->_scene->_set->getAltitudeAtXZ(next->x, next->z, &walkboxFound);
if (walkboxFound) {
next->y = walkboxAltitude;
return true;
} else {
next->y = start.y;
return false;
}
}
void Obstacles::backup() {
for (int i = 0; i != kPolygonCount; ++i) {
_polygonsBackup[i].isPresent = false;
}
int count = 0;
for (int i = 0; i != kPolygonCount; ++i) {
if (_polygons[i].isPresent) {
_polygonsBackup[count] = _polygons[i];
++count;
}
}
for (int i = 0; i != kPolygonCount; ++i) {
_polygons[i] = _polygonsBackup[count];
}
_count = count;
_backup = true;
}
void Obstacles::restore() {
for (int i = 0; i != kPolygonCount; ++i) {
_polygons[i].isPresent = false;
}
for (int i = 0; i != kPolygonCount; ++i) {
_polygons[i] = _polygonsBackup[i];
}
}
void Obstacles::save(SaveFileWriteStream &f) {
f.writeBool(_backup);
f.writeInt(_count);
for (int i = 0; i < _count; ++i) {
Polygon &p = _polygonsBackup[i];
f.writeBool(p.isPresent);
f.writeInt(p.verticeCount);
f.writeFloat(p.rect.x0);
f.writeFloat(p.rect.y0);
f.writeFloat(p.rect.x1);
f.writeFloat(p.rect.y1);
for (int j = 0; j < kPolygonVertexCount; ++j) {
f.writeVector2(p.vertices[j]);
}
for (int j = 0; j < kPolygonVertexCount; ++j) {
f.writeInt(p.vertexType[j]);
}
}
for (int i = 0; i < kVertexCount; ++i) {
f.writeVector2(_path[i]);
}
f.writeInt(_pathSize);
}
void Obstacles::load(SaveFileReadStream &f) {
for (int i = 0; i < kPolygonCount; ++i) {
_polygons[i].isPresent = false;
_polygons[i].verticeCount = 0;
_polygonsBackup[i].isPresent = false;
_polygonsBackup[i].verticeCount = 0;
}
_backup = f.readBool();
_count = f.readInt();
for (int i = 0; i < _count; ++i) {
Polygon &p = _polygonsBackup[i];
p.isPresent = f.readBool();
p.verticeCount = f.readInt();
p.rect.x0 = f.readFloat();
p.rect.y0 = f.readFloat();
p.rect.x1 = f.readFloat();
p.rect.y1 = f.readFloat();
for (int j = 0; j < kPolygonVertexCount; ++j) {
p.vertices[j] = f.readVector2();
}
for (int j = 0; j < kPolygonVertexCount; ++j) {
p.vertexType[j] = (VertexType)f.readInt();
}
}
for (int i = 0; i < kPolygonCount; ++i) {
_polygons[i] = _polygonsBackup[i];
}
for (int i = 0; i < kVertexCount; ++i) {
_path[i] = f.readVector2();
}
_pathSize = f.readInt();
}
void Obstacles::draw() {
float y = _vm->_playerActor->getY();
for (int i = 0; i != kPolygonCount; ++i) {
if (!_polygons[i].isPresent) {
continue;
}
Vector3 p0 = _vm->_view->calculateScreenPosition(Vector3(
_polygons[i].vertices[_polygons[i].verticeCount - 1].x,
y,
_polygons[i].vertices[_polygons[i].verticeCount - 1].y
));
for (int j = 0; j != _polygons[i].verticeCount; ++j) {
Vector3 p1 = _vm->_view->calculateScreenPosition(Vector3(
_polygons[i].vertices[j].x,
y,
_polygons[i].vertices[j].y
));
_vm->_surfaceFront.drawLine(p0.x, p0.y, p1.x, p1.y, _vm->_surfaceFront.format.RGBToColor(255, 255, 255));
p0 = p1;
}
}
// draw actor's box
{
Vector3 playerPos = _vm->_playerActor->getXYZ();
Vector3 p0 = _vm->_view->calculateScreenPosition(playerPos + Vector3(-12.0f, 0.0f, -12.0f));
Vector3 p1 = _vm->_view->calculateScreenPosition(playerPos + Vector3( 12.0f, 0.0f, -12.0f));
Vector3 p2 = _vm->_view->calculateScreenPosition(playerPos + Vector3( 12.0f, 0.0f, 12.0f));
Vector3 p3 = _vm->_view->calculateScreenPosition(playerPos + Vector3(-12.0f, 0.0f, 12.0f));
_vm->_surfaceFront.drawLine(p0.x, p0.y, p1.x, p1.y, _vm->_surfaceFront.format.RGBToColor(255, 0, 0));
_vm->_surfaceFront.drawLine(p1.x, p1.y, p2.x, p2.y, _vm->_surfaceFront.format.RGBToColor(255, 0, 0));
_vm->_surfaceFront.drawLine(p2.x, p2.y, p3.x, p3.y, _vm->_surfaceFront.format.RGBToColor(255, 0, 0));
_vm->_surfaceFront.drawLine(p3.x, p3.y, p0.x, p0.y, _vm->_surfaceFront.format.RGBToColor(255, 0, 0));
}
// draw path along polygons
for (int i = 1; i < _pathSize; ++i) {
Vector3 p0 = _vm->_view->calculateScreenPosition(Vector3(_path[i - 1].x, y, _path[i - 1].y));
Vector3 p1 = _vm->_view->calculateScreenPosition(Vector3(_path[i].x, y, _path[i].y));
_vm->_surfaceFront.drawLine(p0.x, p0.y, p1.x, p1.y, _vm->_surfaceFront.format.RGBToColor(255, 0, 0));
}
// draw "next" vertex
{
//TODO
}
}
} // End of namespace BladeRunner