680 lines
22 KiB
C++
680 lines
22 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 "alcachofa/shape.h"
|
|
|
|
using namespace Common;
|
|
using namespace Math;
|
|
|
|
namespace Alcachofa {
|
|
|
|
static int sideOfLine(Point a, Point b, Point q) {
|
|
return (b.x - a.x) * (q.y - a.y) - (b.y - a.y) * (q.x - a.x);
|
|
}
|
|
|
|
static bool lineIntersects(Point a1, Point b1, Point a2, Point b2) {
|
|
return (sideOfLine(a1, b1, a2) > 0) != (sideOfLine(a1, b1, b2) > 0);
|
|
}
|
|
|
|
static bool segmentsIntersect(Point a1, Point b1, Point a2, Point b2) {
|
|
// as there are a number of special cases to consider,
|
|
// this method is a direct translation of the original engine
|
|
// rather than using common Math:: code
|
|
if (a2.x > b2.x) {
|
|
if (a1.x > b1.x)
|
|
return lineIntersects(b1, a1, b2, a2) && lineIntersects(b2, a2, b1, a1); //-V764
|
|
else
|
|
return lineIntersects(a1, b1, b2, a2) && lineIntersects(b2, a2, a1, b1); //-V764
|
|
} else {
|
|
if (a1.x > b1.x)
|
|
return lineIntersects(b1, a1, a2, b2) && lineIntersects(a2, b2, b1, a1); //-V764
|
|
else
|
|
return lineIntersects(a1, b1, a2, b2) && lineIntersects(a2, b2, a1, b1);
|
|
}
|
|
}
|
|
|
|
EdgeDistances::EdgeDistances(Point edgeA, Point edgeB, Point query) {
|
|
Vector2d
|
|
a = as2D(edgeA),
|
|
b = as2D(edgeB),
|
|
q = as2D(query);
|
|
float edgeLength = a.getDistanceTo(b);
|
|
Vector2d edgeDir = (b - a) / edgeLength;
|
|
Vector2d edgeNormal(-edgeDir.getY(), edgeDir.getX());
|
|
_edgeLength = edgeLength;
|
|
_onEdge = edgeDir.dotProduct(q - a);
|
|
_toEdge = abs(edgeNormal.dotProduct(q) - edgeNormal.dotProduct(a));
|
|
}
|
|
|
|
bool Polygon::contains(Point query) const {
|
|
switch (_points.size()) {
|
|
case 0:
|
|
return false;
|
|
case 1:
|
|
return query == _points[0];
|
|
case 2:
|
|
return edgeDistances(0, query)._toEdge < 2.0f;
|
|
default:
|
|
// we assume that the polygon is convex
|
|
for (uint i = 1; i < _points.size(); i++) {
|
|
if (sideOfLine(_points[i - 1], _points[i], query) < 0)
|
|
return false;
|
|
}
|
|
return sideOfLine(_points[_points.size() - 1], _points[0], query) >= 0;
|
|
}
|
|
}
|
|
|
|
bool Polygon::intersectsEdge(uint startPointI, Point a, Point b) const {
|
|
assert(startPointI < _points.size());
|
|
uint endPointI = (startPointI + 1) % _points.size();
|
|
return segmentsIntersect(_points[startPointI], _points[endPointI], a, b);
|
|
}
|
|
|
|
EdgeDistances Polygon::edgeDistances(uint startPointI, Point query) const {
|
|
assert(startPointI < _points.size());
|
|
uint endPointI = startPointI + 1 == _points.size() ? 0 : startPointI + 1;
|
|
return EdgeDistances(_points[startPointI], _points[endPointI], query);
|
|
}
|
|
|
|
static Point wiggleOnToLine(Point a, Point b, Point q) {
|
|
// due to rounding errors contains(bestPoint) might be false for on-edge closest points, let's fix that
|
|
// maybe there is a more mathematical solution to this, but it suffices for now
|
|
if (sideOfLine(a, b, q) >= 0) return q;
|
|
if (sideOfLine(a, b, q + Point(+1, 0)) >= 0) return q + Point(+1, 0);
|
|
if (sideOfLine(a, b, q + Point(-1, 0)) >= 0) return q + Point(-1, 0);
|
|
if (sideOfLine(a, b, q + Point(0, +1)) >= 0) return q + Point(0, +1);
|
|
if (sideOfLine(a, b, q + Point(0, -1)) >= 0) return q + Point(0, -1);
|
|
assert(false && "More than two pixels means some more serious math error occured");
|
|
return q;
|
|
}
|
|
|
|
Point Polygon::closestPointTo(Point query, float &distanceSqr) const {
|
|
assert(_points.size() > 0);
|
|
Common::Point bestPoint = {};
|
|
distanceSqr = std::numeric_limits<float>::infinity();
|
|
for (uint i = 0; i < _points.size(); i++) {
|
|
auto edgeDists = edgeDistances(i, query);
|
|
if (edgeDists._onEdge < 0.0f) {
|
|
float pointDistSqr = as2D(query - _points[i]).getSquareMagnitude();
|
|
if (pointDistSqr < distanceSqr) {
|
|
bestPoint = _points[i];
|
|
distanceSqr = pointDistSqr;
|
|
}
|
|
}
|
|
if (edgeDists._onEdge >= 0.0f && edgeDists._onEdge <= edgeDists._edgeLength) {
|
|
float edgeDistSqr = powf(edgeDists._toEdge, 2.0f);
|
|
if (edgeDistSqr < distanceSqr) {
|
|
distanceSqr = edgeDistSqr;
|
|
uint j = (i + 1) % _points.size();
|
|
bestPoint = _points[i] + (_points[j] - _points[i]) * (edgeDists._onEdge / edgeDists._edgeLength);
|
|
bestPoint = wiggleOnToLine(_points[i], _points[j], bestPoint);
|
|
}
|
|
}
|
|
}
|
|
return bestPoint;
|
|
}
|
|
|
|
Point Polygon::midPoint() const {
|
|
assert(_points.size() > 0);
|
|
Common::Point sum = {};
|
|
for (uint i = 0; i < _points.size(); i++)
|
|
sum += _points[i];
|
|
return sum / (int16)_points.size();
|
|
}
|
|
|
|
static float depthAtForLine(Point a, Point b, Point q, int8 depthA, int8 depthB) {
|
|
return (sqrtf(a.sqrDist(q)) / a.sqrDist(b) * depthB + depthA) * 0.01f;
|
|
}
|
|
|
|
static float depthAtForConvex(const PathFindingPolygon &p, Point q) {
|
|
float sumDepths = 0, sumDistances = 0;
|
|
for (uint i = 0; i < p._points.size(); i++) {
|
|
uint j = i + 1 == p._points.size() ? 0 : i + 1;
|
|
auto distances = p.edgeDistances(i, q);
|
|
float depthOnEdge = p._pointDepths[i] + distances._onEdge * (p._pointDepths[j] - p._pointDepths[i]) / distances._edgeLength;
|
|
if (distances._toEdge < epsilon) // q is directly on the edge
|
|
return depthOnEdge * 0.01f;
|
|
sumDepths += 1 / distances._toEdge * depthOnEdge;
|
|
sumDistances += 1 / distances._toEdge;
|
|
}
|
|
return sumDistances < epsilon ? 0
|
|
: sumDepths / sumDistances * 0.01f;
|
|
}
|
|
|
|
float PathFindingPolygon::depthAt(Point query) const {
|
|
switch (_points.size()) {
|
|
case 0:
|
|
case 1:
|
|
return 1.0f;
|
|
case 2:
|
|
return depthAtForLine(_points[0], _points[1], query, _pointDepths[0], _pointDepths[1]);
|
|
default:
|
|
return depthAtForConvex(*this, query);
|
|
}
|
|
}
|
|
|
|
uint PathFindingPolygon::findSharedPoints(
|
|
const PathFindingPolygon &other,
|
|
Common::Span<SharedPoint> sharedPoints) const {
|
|
uint count = 0;
|
|
for (uint outerI = 0; outerI < _points.size(); outerI++) {
|
|
for (uint innerI = 0; innerI < other._points.size(); innerI++) {
|
|
if (_points[outerI] == other._points[innerI]) {
|
|
assert(count < sharedPoints.size());
|
|
sharedPoints[count++] = { outerI, innerI };
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static Color colorAtForLine(Point a, Point b, Point q, Color colorA, Color colorB) {
|
|
// I highly suspect RGB calculation being very bugged, so for now I just ignore and only calc alpha
|
|
float phase = sqrtf(q.sqrDist(a)) / a.sqrDist(b);
|
|
colorA.a += phase * colorB.a;
|
|
return colorA;
|
|
}
|
|
|
|
static Color colorAtForConvex(const FloorColorPolygon &p, Point query) {
|
|
// This is a quite literal translation of the original engine
|
|
// There may very well be a better way than this...
|
|
float weights[FloorColorShape::kPointsPerPolygon];
|
|
fill(weights, weights + FloorColorShape::kPointsPerPolygon, 0.0f);
|
|
|
|
for (uint i = 0; i < p._points.size(); i++) {
|
|
EdgeDistances distances = p.edgeDistances(i, query);
|
|
float edgeWeight = distances._toEdge * ABS(distances._onEdge) / distances._edgeLength;
|
|
if (distances._edgeLength > 1) {
|
|
weights[i] += edgeWeight;
|
|
weights[i + 1 == p._points.size() ? 0 : i + 1] += edgeWeight;
|
|
}
|
|
}
|
|
float weightSum = 0;
|
|
for (uint i = 0; i < p._points.size(); i++)
|
|
weightSum += weights[i];
|
|
for (uint i = 0; i < p._points.size(); i++) {
|
|
if (weights[i] < epsilon)
|
|
return p._pointColors[i];
|
|
weights[i] = weightSum / weights[i];
|
|
}
|
|
|
|
weightSum = 0;
|
|
for (uint i = 0; i < p._points.size(); i++)
|
|
weightSum += weights[i];
|
|
for (uint i = 0; i < p._points.size(); i++)
|
|
weights[i] /= weightSum;
|
|
|
|
float r = 0, g = 0, b = 0, a = 0.5f;
|
|
for (uint i = 0; i < p._points.size(); i++) {
|
|
r += p._pointColors[i].r * weights[i];
|
|
g += p._pointColors[i].g * weights[i];
|
|
b += p._pointColors[i].b * weights[i];
|
|
a += p._pointColors[i].a * weights[i];
|
|
}
|
|
return {
|
|
(byte)MIN(255, MAX(0, (int)r)),
|
|
(byte)MIN(255, MAX(0, (int)g)),
|
|
(byte)MIN(255, MAX(0, (int)b)),
|
|
(byte)MIN(255, MAX(0, (int)a)),
|
|
};
|
|
}
|
|
|
|
Color FloorColorPolygon::colorAt(Point query) const {
|
|
switch (_points.size()) {
|
|
case 0:
|
|
return kWhite;
|
|
case 1:
|
|
return { 255, 255, 255, _pointColors[0].a };
|
|
case 2:
|
|
return colorAtForLine(_points[0], _points[1], query, _pointColors[0], _pointColors[1]);
|
|
default:
|
|
return colorAtForConvex(*this, query);
|
|
}
|
|
}
|
|
|
|
Shape::Shape() {}
|
|
|
|
Shape::Shape(ReadStream &stream) {
|
|
byte complexity = stream.readByte();
|
|
uint8 pointsPerPolygon;
|
|
if (complexity > 3)
|
|
error("Invalid shape complexity %d", complexity);
|
|
else if (complexity == 3)
|
|
pointsPerPolygon = 0; // read in per polygon
|
|
else
|
|
pointsPerPolygon = 1 << complexity;
|
|
|
|
int polygonCount = stream.readUint16LE();
|
|
_polygons.reserve(polygonCount);
|
|
_points.reserve(MIN(3, (int)pointsPerPolygon) * polygonCount);
|
|
for (int i = 0; i < polygonCount; i++) {
|
|
auto pointCount = pointsPerPolygon == 0
|
|
? stream.readByte()
|
|
: pointsPerPolygon;
|
|
for (int j = 0; j < pointCount; j++)
|
|
_points.push_back(readPoint(stream));
|
|
addPolygon(pointCount);
|
|
}
|
|
}
|
|
|
|
uint Shape::addPolygon(uint maxCount) {
|
|
// Common functionality of shapes is that polygons are reduced
|
|
// so that the first point is not duplicated
|
|
uint firstI = empty() ? 0 : _polygons.back().first + _polygons.back().second;
|
|
uint newCount = maxCount;
|
|
if (maxCount > 1) {
|
|
for (newCount = 1; newCount < maxCount; newCount++) {
|
|
if (_points[firstI + newCount] == _points[firstI])
|
|
break;
|
|
}
|
|
_points.resize(firstI + newCount);
|
|
}
|
|
_polygons.push_back({ firstI, newCount });
|
|
return newCount;
|
|
}
|
|
|
|
Polygon Shape::at(uint index) const {
|
|
auto range = _polygons[index];
|
|
Polygon p;
|
|
p._index = index;
|
|
p._points = Span<const Point>(_points.data() + range.first, range.second);
|
|
return p;
|
|
}
|
|
|
|
int32 Shape::polygonContaining(Point query) const {
|
|
for (uint i = 0; i < _polygons.size(); i++) {
|
|
if (at(i).contains(query))
|
|
return (int32)i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool Shape::contains(Point query) const {
|
|
return polygonContaining(query) >= 0;
|
|
}
|
|
|
|
Point Shape::closestPointTo(Point query, int32 &polygonI) const {
|
|
assert(_polygons.size() > 0);
|
|
float bestDistanceSqr = std::numeric_limits<float>::infinity();
|
|
Point bestPoint = {};
|
|
for (uint i = 0; i < _polygons.size(); i++) {
|
|
float curDistanceSqr = std::numeric_limits<float>::infinity();
|
|
Point curPoint = at(i).closestPointTo(query, curDistanceSqr);
|
|
if (curDistanceSqr < bestDistanceSqr) {
|
|
bestDistanceSqr = curDistanceSqr;
|
|
bestPoint = curPoint;
|
|
polygonI = (int32)i;
|
|
}
|
|
}
|
|
return bestPoint;
|
|
}
|
|
|
|
void Shape::setAsRectangle(const Rect &rect) {
|
|
_polygons.resize(1);
|
|
_polygons[0] = { 0, 4 };
|
|
_points.resize(4);
|
|
_points[0] = { rect.left, rect.top };
|
|
_points[1] = { rect.right, rect.top };
|
|
_points[2] = { rect.right, rect.bottom };
|
|
_points[3] = { rect.left, rect.bottom };
|
|
}
|
|
|
|
PathFindingShape::PathFindingShape() {}
|
|
|
|
PathFindingShape::PathFindingShape(ReadStream &stream) {
|
|
auto polygonCount = stream.readUint16LE();
|
|
_polygons.reserve(polygonCount);
|
|
_polygonOrders.reserve(polygonCount);
|
|
_points.reserve(polygonCount * kPointsPerPolygon);
|
|
_pointDepths.reserve(polygonCount * kPointsPerPolygon);
|
|
|
|
for (int i = 0; i < polygonCount; i++) {
|
|
for (uint j = 0; j < kPointsPerPolygon; j++)
|
|
_points.push_back(readPoint(stream));
|
|
_polygonOrders.push_back(stream.readSByte());
|
|
for (uint j = 0; j < kPointsPerPolygon; j++)
|
|
_pointDepths.push_back(stream.readByte());
|
|
|
|
uint pointCount = addPolygon(kPointsPerPolygon);
|
|
assert(pointCount <= kPointsPerPolygon);
|
|
_pointDepths.resize(_points.size());
|
|
}
|
|
|
|
setupLinks();
|
|
initializeFloydWarshall();
|
|
calculateFloydWarshall();
|
|
}
|
|
|
|
PathFindingPolygon PathFindingShape::at(uint index) const {
|
|
auto range = _polygons[index];
|
|
PathFindingPolygon p;
|
|
p._index = index;
|
|
p._points = Span<const Point>(_points.data() + range.first, range.second);
|
|
p._pointDepths = Span<const uint8>(_pointDepths.data() + range.first, range.second);
|
|
p._order = _polygonOrders[index];
|
|
return p;
|
|
}
|
|
|
|
int8 PathFindingShape::orderAt(Point query) const {
|
|
int32 polygon = polygonContaining(query);
|
|
return polygon < 0 ? 49 : _polygonOrders[polygon];
|
|
}
|
|
|
|
float PathFindingShape::depthAt(Point query) const {
|
|
int32 polygon = polygonContaining(query);
|
|
return polygon < 0 ? 1.0f : at(polygon).depthAt(query);
|
|
}
|
|
|
|
PathFindingShape::LinkPolygonIndices::LinkPolygonIndices() {
|
|
Common::fill(_points, _points + kPointsPerPolygon, LinkIndex(-1, -1));
|
|
}
|
|
|
|
static Pair<int32, int32> orderPoints(const Polygon &polygon, int32 point1, int32 point2) {
|
|
if (point1 > point2) {
|
|
int32 tmp = point1;
|
|
point1 = point2;
|
|
point2 = tmp;
|
|
}
|
|
const int32 maxPointI = polygon._points.size() - 1;
|
|
if (point1 == 0 && point2 == maxPointI)
|
|
return { maxPointI, 0 };
|
|
return { point1, point2 };
|
|
}
|
|
|
|
void PathFindingShape::setupLinks() {
|
|
// just a heuristic, each polygon will be attached to at least one other
|
|
_linkPoints.reserve(polygonCount() * 3);
|
|
_linkIndices.resize(polygonCount());
|
|
_targetQuads.resize(polygonCount() * kPointsPerPolygon);
|
|
Common::fill(_targetQuads.begin(), _targetQuads.end(), -1);
|
|
Pair<uint, uint> sharedPoints[2];
|
|
for (uint outerI = 0; outerI < polygonCount(); outerI++) {
|
|
const auto outer = at(outerI);
|
|
for (uint innerI = outerI + 1; innerI < polygonCount(); innerI++) {
|
|
const auto inner = at(innerI);
|
|
uint sharedPointCount = outer.findSharedPoints(inner, { sharedPoints, 2 });
|
|
if (sharedPointCount > 0)
|
|
setupLinkPoint(outer, inner, sharedPoints[0]);
|
|
if (sharedPointCount > 1) {
|
|
auto outerPoints = orderPoints(outer, sharedPoints[0].first, sharedPoints[1].first);
|
|
auto innerPoints = orderPoints(inner, sharedPoints[0].second, sharedPoints[1].second);
|
|
setupLinkEdge(outer, inner, outerPoints, innerPoints);
|
|
setupLinkPoint(outer, inner, sharedPoints[1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PathFindingShape::setupLinkPoint(
|
|
const PathFindingPolygon &outer,
|
|
const PathFindingPolygon &inner,
|
|
PathFindingPolygon::SharedPoint pointI) {
|
|
auto &outerLink = _linkIndices[outer._index]._points[pointI.first];
|
|
auto &innerLink = _linkIndices[inner._index]._points[pointI.second];
|
|
if (outerLink.first < 0) {
|
|
outerLink.first = _linkPoints.size();
|
|
_linkPoints.push_back(outer._points[pointI.first]);
|
|
}
|
|
innerLink.first = outerLink.first;
|
|
}
|
|
|
|
void PathFindingShape::setupLinkEdge(
|
|
const PathFindingPolygon &outer,
|
|
const PathFindingPolygon &inner,
|
|
PathFindingShape::LinkIndex outerP,
|
|
PathFindingShape::LinkIndex innerP) {
|
|
_targetQuads[outer._index * kPointsPerPolygon + outerP.first] = inner._index;
|
|
_targetQuads[inner._index * kPointsPerPolygon + innerP.first] = outer._index;
|
|
auto &outerLink = _linkIndices[outer._index]._points[outerP.first];
|
|
auto &innerLink = _linkIndices[inner._index]._points[innerP.first];
|
|
if (outerLink.second < 0) {
|
|
outerLink.second = _linkPoints.size();
|
|
_linkPoints.push_back((outer._points[outerP.first] + outer._points[outerP.second]) / 2);
|
|
}
|
|
innerLink.second = outerLink.second;
|
|
}
|
|
|
|
void PathFindingShape::initializeFloydWarshall() {
|
|
_distanceMatrix.resize(_linkPoints.size() * _linkPoints.size());
|
|
_previousTarget.resize(_linkPoints.size() * _linkPoints.size());
|
|
Common::fill(_distanceMatrix.begin(), _distanceMatrix.end(), UINT_MAX);
|
|
Common::fill(_previousTarget.begin(), _previousTarget.end(), -1);
|
|
|
|
// every linkpoint is the shortest path to itself
|
|
for (uint i = 0; i < _linkPoints.size(); i++) {
|
|
_distanceMatrix[i * _linkPoints.size() + i] = 0;
|
|
_previousTarget[i * _linkPoints.size() + i] = i;
|
|
}
|
|
|
|
// every linkpoint to linkpoint within the same polygon *is* the shortest path
|
|
// between them. Therefore these are our initial paths for Floyd-Warshall
|
|
for (const auto &linkPolygon : _linkIndices) {
|
|
for (uint i = 0; i < 2 * kPointsPerPolygon; i++) {
|
|
LinkIndex linkFrom = linkPolygon._points[i / 2];
|
|
int32 linkFromI = (i % 2) ? linkFrom.second : linkFrom.first;
|
|
if (linkFromI < 0)
|
|
continue;
|
|
for (uint j = i + 1; j < 2 * kPointsPerPolygon; j++) {
|
|
LinkIndex linkTo = linkPolygon._points[j / 2];
|
|
int32 linkToI = (j % 2) ? linkTo.second : linkTo.first;
|
|
if (linkToI >= 0) {
|
|
const int32 linkFromFullI = linkFromI * _linkPoints.size() + linkToI;
|
|
const int32 linkToFullI = linkToI * _linkPoints.size() + linkFromI;
|
|
_distanceMatrix[linkFromFullI] = _distanceMatrix[linkToFullI] =
|
|
(uint)sqrtf(_linkPoints[linkFromI].sqrDist(_linkPoints[linkToI]) + 0.5f);
|
|
_previousTarget[linkFromFullI] = linkFromI;
|
|
_previousTarget[linkToFullI] = linkToI;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PathFindingShape::calculateFloydWarshall() {
|
|
const auto distance = [&] (uint a, uint b) -> uint &{
|
|
return _distanceMatrix[a * _linkPoints.size() + b];
|
|
};
|
|
const auto previousTarget = [&] (uint a, uint b) -> int32 &{
|
|
return _previousTarget[a * _linkPoints.size() + b];
|
|
};
|
|
for (uint over = 0; over < _linkPoints.size(); over++) {
|
|
for (uint from = 0; from < _linkPoints.size(); from++) {
|
|
for (uint to = 0; to < _linkPoints.size(); to++) {
|
|
if (distance(from, over) != UINT_MAX && distance(over, to) != UINT_MAX &&
|
|
distance(from, over) + distance(over, to) < distance(from, to)) {
|
|
distance(from, to) = distance(from, over) + distance(over, to);
|
|
previousTarget(from, to) = previousTarget(over, to);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// in the game all floors should be fully connected
|
|
assert(find(_previousTarget.begin(), _previousTarget.end(), -1) == _previousTarget.end());
|
|
}
|
|
|
|
bool PathFindingShape::findPath(Point from, Point to_, Stack<Point> &path) const {
|
|
Point to = to_; // we might want to correct it
|
|
path.clear();
|
|
|
|
int32 fromContaining = polygonContaining(from);
|
|
if (fromContaining < 0)
|
|
return false;
|
|
int32 toContaining = polygonContaining(to);
|
|
if (toContaining < 0) {
|
|
to = closestPointTo(to, toContaining);
|
|
assert(toContaining >= 0);
|
|
}
|
|
//if (canGoStraightThrough(from, to, fromContaining, toContaining)) {
|
|
if (canGoStraightThrough(from, to, fromContaining, toContaining)) {
|
|
path.push(to);
|
|
return true;
|
|
}
|
|
floydWarshallPath(from, to, fromContaining, toContaining, path);
|
|
return true;
|
|
}
|
|
|
|
int32 PathFindingShape::edgeTarget(uint polygonI, uint pointI) const {
|
|
assert(polygonI < polygonCount() && pointI < kPointsPerPolygon);
|
|
uint fullI = polygonI * kPointsPerPolygon + pointI;
|
|
return _targetQuads[fullI];
|
|
}
|
|
|
|
bool PathFindingShape::canGoStraightThrough(
|
|
Point from, Point to,
|
|
int32 fromContainingI, int32 toContainingI) const {
|
|
int32 lastContainingI = -1;
|
|
while (fromContainingI != toContainingI) {
|
|
auto toContaining = at(toContainingI);
|
|
bool foundPortal = false;
|
|
for (uint i = 0; i < toContaining._points.size(); i++) {
|
|
int32 target = edgeTarget((uint)toContainingI, i);
|
|
if (target < 0 || target == lastContainingI)
|
|
continue;
|
|
|
|
if (toContaining.intersectsEdge(i, from, to)) {
|
|
foundPortal = true;
|
|
lastContainingI = toContainingI;
|
|
toContainingI = target;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundPortal)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PathFindingShape::floydWarshallPath(
|
|
Point from, Point to,
|
|
int32 fromContaining, int32 toContaining,
|
|
Stack<Point> &path) const {
|
|
path.push(to);
|
|
// first find the tuple of link points to be used
|
|
uint fromLink = UINT_MAX, toLink = UINT_MAX, bestDistance = UINT_MAX;
|
|
const auto &fromIndices = _linkIndices[fromContaining];
|
|
const auto &toIndices = _linkIndices[toContaining];
|
|
for (uint i = 0; i < 2 * kPointsPerPolygon; i++) {
|
|
const auto &curFromPoint = fromIndices._points[i / 2];
|
|
int32 curFromLink = (i % 2) ? curFromPoint.second : curFromPoint.first;
|
|
if (curFromLink < 0)
|
|
continue;
|
|
uint curFromDistance = (uint)sqrtf(from.sqrDist(_linkPoints[curFromLink]) + 0.5f);
|
|
|
|
for (uint j = 0; j < 2 * kPointsPerPolygon; j++) {
|
|
const auto &curToPoint = toIndices._points[j / 2];
|
|
int32 curToLink = (j % 2) ? curToPoint.second : curToPoint.first;
|
|
if (curToLink < 0)
|
|
continue;
|
|
uint totalDistance =
|
|
curFromDistance +
|
|
_distanceMatrix[curFromLink * _linkPoints.size() + curToLink] +
|
|
(uint)sqrtf(to.sqrDist(_linkPoints[curToLink]) + 0.5f);
|
|
if (totalDistance < bestDistance) {
|
|
bestDistance = totalDistance;
|
|
fromLink = curFromLink;
|
|
toLink = curToLink;
|
|
}
|
|
}
|
|
}
|
|
assert(fromLink != UINT_MAX && toLink != UINT_MAX);
|
|
|
|
// then walk the matrix back to reconstruct the path
|
|
while (fromLink != toLink) {
|
|
path.push(_linkPoints[toLink]);
|
|
toLink = _previousTarget[fromLink * _linkPoints.size() + toLink];
|
|
assert(toLink < _linkPoints.size());
|
|
}
|
|
path.push(_linkPoints[fromLink]);
|
|
}
|
|
|
|
bool PathFindingShape::findEvadeTarget(
|
|
Point centerTarget,
|
|
float depthScale, float minDistSqr,
|
|
Point &evadeTarget) const {
|
|
|
|
for (float tryDistBase = 60; tryDistBase < 250; tryDistBase += 10) {
|
|
for (int tryAngleI = 0; tryAngleI < 6; tryAngleI++) {
|
|
const float tryAngle = tryAngleI / 3.0f * M_PI + deg2rad(30.0f);
|
|
const float tryDist = tryDistBase * depthScale;
|
|
const Point tryPos = evadeTarget + Point(
|
|
(int16)(cosf(tryAngle) * tryDist),
|
|
(int16)(sinf(tryAngle) * tryDist));
|
|
|
|
if (contains(tryPos) && tryPos.sqrDist(centerTarget) > minDistSqr) {
|
|
evadeTarget = tryPos;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FloorColorShape::FloorColorShape() {}
|
|
|
|
FloorColorShape::FloorColorShape(ReadStream &stream) {
|
|
auto polygonCount = stream.readUint16LE();
|
|
_polygons.reserve(polygonCount);
|
|
_points.reserve(polygonCount * kPointsPerPolygon);
|
|
_pointColors.reserve(polygonCount * kPointsPerPolygon);
|
|
|
|
for (int i = 0; i < polygonCount; i++) {
|
|
for (uint j = 0; j < kPointsPerPolygon; j++)
|
|
_points.push_back(readPoint(stream));
|
|
|
|
// For the colors the alpha channel is not used so we store the brightness into it instead
|
|
// Brightness is store 0-100, but we can scale it up here
|
|
int firstColorI = _pointColors.size();
|
|
_pointColors.resize(_pointColors.size() + kPointsPerPolygon);
|
|
for (uint j = 0; j < kPointsPerPolygon; j++)
|
|
_pointColors[firstColorI + j].a = (uint8)MIN(255, stream.readByte() * 255 / 100);
|
|
for (uint j = 0; j < kPointsPerPolygon; j++) {
|
|
_pointColors[firstColorI + j].r = stream.readByte();
|
|
_pointColors[firstColorI + j].g = stream.readByte();
|
|
_pointColors[firstColorI + j].b = stream.readByte();
|
|
stream.readByte(); // second alpha value is ignored
|
|
}
|
|
stream.readByte(); // unused byte per polygon
|
|
|
|
uint pointCount = addPolygon(kPointsPerPolygon);
|
|
assert(pointCount <= kPointsPerPolygon);
|
|
_pointColors.resize(_points.size());
|
|
}
|
|
}
|
|
|
|
FloorColorPolygon FloorColorShape::at(uint index) const {
|
|
auto range = _polygons[index];
|
|
FloorColorPolygon p;
|
|
p._index = index;
|
|
p._points = Span<const Point>(_points.data() + range.first, range.second);
|
|
p._pointColors = Span<const Color>(_pointColors.data() + range.first, range.second);
|
|
return p;
|
|
}
|
|
|
|
OptionalColor FloorColorShape::colorAt(Point query) const {
|
|
int32 polygon = polygonContaining(query);
|
|
return polygon < 0
|
|
? OptionalColor(false, kClear)
|
|
: OptionalColor(true, at(polygon).colorAt(query));
|
|
}
|
|
|
|
}
|