Files
tech5/source/engine/gamelib/aas2/obstacleavoidancecore.cpp

348 lines
14 KiB
C++

#include "gamelib/aas2/obstacleavoidancecore.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <limits>
#include <vector>
namespace {
float Cross2(const idVec2& first, const idVec2& second) {
return first.x * second.y - first.y * second.x;
}
idVec2 Sub2(const idVec2& first, const idVec2& second) {
return idVec2(first.x - second.x, first.y - second.y);
}
float Dot2(const idVec2& first, const idVec2& second) {
return first.x * second.x + first.y * second.y;
}
float Length2(const idVec2& value) {
return std::sqrt(Dot2(value, value));
}
idVec2 Normalize2(const idVec2& value) {
const float length = Length2(value);
return length > 1.0e-6f
? idVec2(value.x / length, value.y / length) : idVec2(0.0f, 0.0f);
}
float SegmentPointDistanceSqr(const idVec2& start, const idVec2& end,
const idVec2& point) {
const idVec2 delta = Sub2(end, start);
const float lengthSqr = Dot2(delta, delta);
float fraction = lengthSqr > 0.0f
? Dot2(Sub2(point, start), delta) / lengthSqr : 0.0f;
fraction = (std::max)(0.0f, (std::min)(1.0f, fraction));
const idVec2 closest(start.x + delta.x * fraction,
start.y + delta.y * fraction);
return Dot2(Sub2(point, closest), Sub2(point, closest));
}
bool SegmentIntersection(const idVec2& a, const idVec2& b,
const idVec2& c, const idVec2& d) {
const idVec2 r = Sub2(b, a);
const idVec2 s = Sub2(d, c);
const float denominator = Cross2(r, s);
if (std::fabs(denominator) < 1.0e-6f) return false;
const idVec2 delta = Sub2(c, a);
const float t = Cross2(delta, s) / denominator;
const float u = Cross2(delta, r) / denominator;
return t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f;
}
} // namespace
idObstacleAvoidanceCore::idObstacleAvoidanceCore()
: bspNodes(nullptr), numBspNodes(0), obstacleIndices(nullptr),
expandedObstacles(nullptr), maxExpandedObstacles(0),
numExpandedObstacles(0), windingPoints(nullptr), maxWindingPoints(0),
numWindingPoints(0), tempMemory(nullptr), tempMemorySize(0),
tempMemoryStart(nullptr), tempMemoryEnd(nullptr), pathNodes(nullptr),
maxPathNodes(0), numPathNodes(0), debugLines(nullptr),
maxDebugLines(0), numDebugLines(0), debugText(nullptr),
maxDebugText(0), numDebugText(0), inputTransformValid(false),
inputTransform(), flags(0) {
}
idObstacleAvoidanceCore::~idObstacleAvoidanceCore() = default;
void idObstacleAvoidanceCore::AddDebugLine(const avoidColor_t color,
const idVec2& start, const idVec2& end) {
if (debugLines == nullptr || numDebugLines >= maxDebugLines - 1) return;
avoidDebugLine_t& line = debugLines[numDebugLines++];
line.color = color;
line.start.Set(start.x, start.y, 0.0f);
line.end.Set(end.x, end.y, 0.0f);
}
void idObstacleAvoidanceCore::AddDebugInt(const avoidColor_t color,
const idVec2& position, const int value) {
if (debugText == nullptr || numDebugText >= maxDebugText - 1) return;
avoidDebugText_t& text = debugText[numDebugText++];
std::snprintf(text.text, sizeof(text.text), "%d", value);
text.pos.Set(position.x, position.y, 0.0f);
text.color = color;
}
int idObstacleAvoidanceCore::BoxParallelProjectionSilhouetteVerts(
const idMat3& axis, const idVec3& center, const idVec3& extents,
const idVec3& projectionDir, idVec3* const silhouette) {
if (silhouette == nullptr) return 0;
idVec3 corners[8];
int count = 0;
for (int x = -1; x <= 1; x += 2) {
for (int y = -1; y <= 1; y += 2) {
for (int z = -1; z <= 1; z += 2) {
corners[count++] = center + axis[0] * (extents.x * x)
+ axis[1] * (extents.y * y)
+ axis[2] * (extents.z * z);
}
}
}
int output = 0;
for (int index = 0; index < 8 && output < 6; ++index) {
const float facing = (corners[index] - center).Dot(projectionDir);
if (facing <= 0.0f) silhouette[output++] = corners[index];
}
return output;
}
void idObstacleAvoidanceCore::GetCirclePoints(const idVec2& center,
const float radius, idVec2* const points) {
if (points == nullptr) return;
constexpr float diagonal = 0.70710678118f;
const idVec2 directions[8] = {
idVec2(1, 0), idVec2(diagonal, diagonal), idVec2(0, 1),
idVec2(-diagonal, diagonal), idVec2(-1, 0),
idVec2(-diagonal, -diagonal), idVec2(0, -1),
idVec2(diagonal, -diagonal)
};
for (int index = 0; index < 8; ++index) {
points[index].Set(center.x + directions[index].x * radius,
center.y + directions[index].y * radius);
}
}
idVec3 idObstacleAvoidanceCore::Plane2DFromPoints(const idVec2& start,
const idVec2& end, const bool normalize) {
return Plane2DFromVecs(start, Sub2(end, start), normalize);
}
idVec3 idObstacleAvoidanceCore::Plane2DFromVecs(const idVec2& start,
const idVec2& direction, const bool normalize) {
idVec2 normal(-direction.y, direction.x);
if (normalize) normal = Normalize2(normal);
return idVec3(normal.x, normal.y,
-(normal.x * start.x + normal.y * start.y));
}
bool idObstacleAvoidanceCore::PointInsideWinding(const idVec2* const winding,
const int numPoints, const idVec2& point, const float epsilon) {
if (winding == nullptr || numPoints <= 0) return true;
float sign = 0.0f;
for (int index = 0; index < numPoints; ++index) {
const float side = Cross2(Sub2(winding[(index + 1) % numPoints],
winding[index]), Sub2(point, winding[index]));
if (std::fabs(side) <= epsilon) continue;
if (sign == 0.0f) sign = side;
else if ((sign < 0.0f) != (side < 0.0f)) return false;
}
return true;
}
void idObstacleAvoidanceCore::ExpandWinding(const idVec2* const winding,
const int numPoints, const float distance, idVec2* const expanded) {
if (winding == nullptr || expanded == nullptr || numPoints <= 0) return;
for (int index = 0; index < numPoints; ++index) {
const idVec2 previous = winding[(index + numPoints - 1) % numPoints];
const idVec2 current = winding[index];
const idVec2 next = winding[(index + 1) % numPoints];
const idVec2 first = Normalize2(idVec2(
-(current.y - previous.y), current.x - previous.x));
const idVec2 second = Normalize2(idVec2(
-(next.y - current.y), next.x - current.x));
idVec2 direction = Normalize2(idVec2(first.x + second.x,
first.y + second.y));
const float denominator = (std::max)(0.1f,
std::fabs(Dot2(direction, first)));
expanded[index].Set(current.x + direction.x * distance / denominator,
current.y + direction.y * distance / denominator);
}
}
void idObstacleAvoidanceCore::GetWindingBounds(
const idVec2* const winding, const int numPoints,
idVec2 bounds[2]) {
const float maximum = (std::numeric_limits<float>::max)();
bounds[0].Set(maximum, maximum);
bounds[1].Set(-maximum, -maximum);
for (int index = 0; index < numPoints; ++index) {
bounds[0].x = (std::min)(bounds[0].x, winding[index].x);
bounds[0].y = (std::min)(bounds[0].y, winding[index].y);
bounds[1].x = (std::max)(bounds[1].x, winding[index].x);
bounds[1].y = (std::max)(bounds[1].y, winding[index].y);
}
}
bool idObstacleAvoidanceCore::WindingRayIntersection(
const idVec2* const winding, const int numPoints,
const idVec2& start, const idVec2& direction, float& firstScale,
float& secondScale, int edgeNums[2]) {
firstScale = (std::numeric_limits<float>::max)();
secondScale = -(std::numeric_limits<float>::max)();
int intersections = 0;
for (int index = 0; index < numPoints; ++index) {
const idVec2 edgeStart = winding[index];
const idVec2 edge = Sub2(winding[(index + 1) % numPoints], edgeStart);
const float denominator = Cross2(direction, edge);
if (std::fabs(denominator) < 1.0e-6f) continue;
const idVec2 delta = Sub2(edgeStart, start);
const float scale = Cross2(delta, edge) / denominator;
const float edgeScale = Cross2(delta, direction) / denominator;
if (edgeScale < 0.0f || edgeScale > 1.0f) continue;
if (scale < firstScale) {
firstScale = scale;
if (edgeNums != nullptr) edgeNums[0] = index;
}
if (scale > secondScale) {
secondScale = scale;
if (edgeNums != nullptr) edgeNums[1] = index;
}
++intersections;
}
return intersections >= 2;
}
void idObstacleAvoidanceCore::FindPathAroundObstacles(obstaclePath_t& path,
avoidDebugLine_t* const outputDebugLines, const int outputMaxDebugLines,
avoidDebugText_t* const outputDebugText, const int outputMaxDebugText,
std::uint8_t* const temp, const int tempSize, const idBounds&,
const idVec3&, const idVec3& lastDir, const idVec4& lastCorner,
const float obstacleRadius, const float frameMoveDist,
const float, const int queryFlags, const obstacleRoute_t& route,
const obstacleBox_t* const obstacles, const int numObstacles,
const obstacleVertex_t* const vertices, const int numVertices,
const obstacleEdge_t* const edges, const int numEdges,
const obstacleCorner_t*, const int, const obstacleTrace_t*,
const int, float*) {
debugLines = outputDebugLines;
maxDebugLines = outputMaxDebugLines;
numDebugLines = 0;
debugText = outputDebugText;
maxDebugText = outputMaxDebugText;
numDebugText = 0;
tempMemory = temp;
tempMemorySize = tempSize;
flags = queryFlags;
const idVec3 start3 = route.numAreas > 0
? route.areas[0].start : path.startPosOutsideObstacles;
const idVec3 goal3 = route.numAreas > 0
? route.areas[route.numAreas - 1].end : path.seekPos[0];
const idVec2 start(start3.x, start3.y);
const idVec2 goal(goal3.x, goal3.y);
path.numSeekPos = 0;
path.firstObstacle = -1;
path.firstObstaclePosition.Zero();
path.startPosOutsideObstacles = start3;
path.seekPosOutsideObstacles = goal3;
path.startPosObstacle = -1;
path.seekPosObstacle = -1;
path.targetDist = Length2(Sub2(goal, start));
path.pathLength = path.targetDist;
path.hasValidPath = true;
path.startPosValid = true;
path.seekPosValid = true;
int blocking = -1;
float blockingRadius = 0.0f;
for (int index = 0; index < numObstacles; ++index) {
const obstacleBox_t& obstacle = obstacles[index];
if (obstacle.soft && (queryFlags & 0x200) == 0) continue;
const float radius = std::sqrt(obstacle.extents.x * obstacle.extents.x
+ obstacle.extents.y * obstacle.extents.y) + obstacleRadius;
if (SegmentPointDistanceSqr(start, goal,
idVec2(obstacle.center.x, obstacle.center.y))
< radius * radius) {
blocking = index;
blockingRadius = radius;
break;
}
}
if (blocking < 0) {
for (int index = 0; index < numEdges; ++index) {
const int first = edges[index].vertNums[0];
const int second = edges[index].vertNums[1];
if (first < 0 || second < 0 || first >= numVertices
|| second >= numVertices) continue;
const idVec2 edgeStart(vertices[first].position.x,
vertices[first].position.y);
const idVec2 edgeEnd(vertices[second].position.x,
vertices[second].position.y);
if (SegmentIntersection(start, goal, edgeStart, edgeEnd)) {
blocking = numObstacles + index;
break;
}
}
}
if (blocking >= 0 && blocking < numObstacles) {
const obstacleBox_t& obstacle = obstacles[blocking];
const idVec2 center(obstacle.center.x, obstacle.center.y);
idVec2 direction = Normalize2(Sub2(goal, start));
idVec2 perpendicular(-direction.y, direction.x);
const idVec2 candidates[2] = {
idVec2(center.x + perpendicular.x * blockingRadius,
center.y + perpendicular.y * blockingRadius),
idVec2(center.x - perpendicular.x * blockingRadius,
center.y - perpendicular.y * blockingRadius)
};
const float firstLength = Length2(Sub2(candidates[0], start))
+ Length2(Sub2(goal, candidates[0]));
const float secondLength = Length2(Sub2(candidates[1], start))
+ Length2(Sub2(goal, candidates[1]));
const idVec2 corner = firstLength <= secondLength
? candidates[0] : candidates[1];
path.seekPos[0].Set(corner.x, corner.y, start3.z);
path.seekPos[1] = goal3;
path.numSeekPos = 2;
path.firstObstacle = obstacle.id;
path.firstObstaclePosition = obstacle.center;
path.pathLength = (std::min)(firstLength, secondLength);
path.nextCorner.Set(corner.x, corner.y, start3.z,
static_cast<float>(obstacle.id));
AddDebugLine(COLOR_RED, start, corner);
AddDebugLine(COLOR_GREEN, corner, goal);
} else if (blocking >= numObstacles) {
path.hasValidPath = false;
path.seekPos[0] = start3;
path.numSeekPos = 1;
} else {
path.seekPos[0] = goal3;
path.numSeekPos = 1;
path.nextCorner.Set(goal3.x, goal3.y, goal3.z, -1.0f);
AddDebugLine(COLOR_GREEN, start, goal);
}
path.done = true;
path.useRadarForward = Length2(idVec2(lastDir.x, lastDir.y)) > 0.0f;
path.radarForward = lastDir;
if (lastCorner.w >= 0.0f && path.firstObstacle < 0) {
path.nextCorner = lastCorner;
}
if (frameMoveDist > 0.0f && path.numSeekPos > 0) {
const idVec2 move = Sub2(idVec2(path.seekPos[0].x,
path.seekPos[0].y), start);
const float moveLength = Length2(move);
if (moveLength > frameMoveDist) {
const float scale = frameMoveDist / moveLength;
path.seekPos[0].x = start.x + move.x * scale;
path.seekPos[0].y = start.y + move.y * scale;
}
}
}