Files

346 lines
14 KiB
C++

#include "gamelib/aas2/aas2_local.h"
#include <algorithm>
#include <cmath>
#include <limits>
namespace {
float LengthSqr(const idVec3& value) {
return value.x * value.x + value.y * value.y + value.z * value.z;
}
bool TraversalClassAllowed(const aas2Traversal_t& traversal,
const int travelFlags) {
const std::uint32_t flags = traversal.flags;
return ((flags & 0x02000000u) != 0
&& (travelFlags & AAS_TFL_TRAVERSAL_CLASS_A) != 0)
|| ((flags & 0x04000000u) != 0
&& (travelFlags & AAS_TFL_TRAVERSAL_CLASS_B) != 0)
|| ((flags & 0x08000000u) != 0
&& (travelFlags & AAS_TFL_TRAVERSAL_CLASS_C) != 0)
|| ((flags & 0x10000000u) != 0
&& (travelFlags & AAS_TFL_TRAVERSAL_CLASS_D) != 0)
|| ((flags & 0x20000000u) != 0
&& (travelFlags & AAS_TFL_TRAVERSAL_CLASS_E) != 0);
}
int PathTypeForTravelFlags(const std::uint32_t flags) {
if ((flags & AAS_TFL_WALKOFFLEDGE) != 0) return 1;
if ((flags & AAS_TFL_BARRIERJUMP) != 0) return 2;
if ((flags & AAS_TFL_LADDER) != 0) return 5;
if ((flags & AAS_TFL_ELEVATOR) != 0) return 8;
if ((flags & AAS_TFL_TELEPORT) != 0) return 9;
return 0;
}
} // namespace
int idAAS2Local::GetTraversalsForReachability(
const idIndex<short, invalidReachability_t> reachability,
idList<int, 5>& traversals) const {
return file != nullptr
? file->GetTraversalsForReachability(reachability, traversals) : 0;
}
int idAAS2Local::GetTraversalsForInteractableEntity(
const idIndex<short, invalidAASInteractionEntIndex_t> entity,
idList<int, 5>& traversals) const {
return file != nullptr
? file->GetTraversalsForInteractableEntity(entity, traversals) : 0;
}
bool idAAS2Local::HopPathIsValid(const int startAreaNum,
const idVec3& startOrigin, const int goalAreaNum,
const idVec3& goalOrigin, const idAAS2HopPathParms& parms) const {
if (file == nullptr || startAreaNum <= 0 || goalAreaNum <= 0
|| startAreaNum >= file->areas.Num()
|| goalAreaNum >= file->areas.Num()
|| (file->areas[goalAreaNum].flags & AAS_AREA_OUTSIDE) == 0) {
return false;
}
const idVec3* const normal = file->GetFloorNormalForArea(startAreaNum);
if (normal == nullptr) return false;
const idVec3 delta = goalOrigin - startOrigin;
const float vertical = delta.x * normal->x + delta.y * normal->y
+ delta.z * normal->z;
const idVec3 planar = delta - *normal * vertical;
if (LengthSqr(planar) > parms.maxDistance * parms.maxDistance) {
return false;
}
idVec3 points[512];
aas2TraceHeight_t trace{512, 0, points};
file->TraceHeight(file->GetTreeForArea(startAreaNum).Get(), trace,
startOrigin, goalOrigin);
if (trace.numPoints >= 512) return false;
for (int index = 1; index < trace.numPoints; ++index) {
const idVec3 pointDelta = points[index] - startOrigin;
const float pointVertical = pointDelta.x * normal->x
+ pointDelta.y * normal->y + pointDelta.z * normal->z;
const idVec3 pointPlanar = pointDelta - *normal * pointVertical;
float maximumHeight = std::sqrt(LengthSqr(pointPlanar))
* parms.maxSlope;
maximumHeight = (std::max)(parms.minHeight,
(std::min)(parms.maxHeight, maximumHeight));
if (pointVertical > maximumHeight) return false;
}
return true;
}
bool idAAS2Local::ExtendHopPathToGoal(idAAS2Path& path,
const int startAreaNum, const idVec3& startOrigin,
const int goalAreaNum, const idVec3& goalOrigin,
const int travelFlags, const int obstacleFlags,
const idAAS2HopPathParms& parms) {
if (file == nullptr) return false;
if (path.moveGoal.x == goalOrigin.x && path.moveGoal.y == goalOrigin.y
&& path.moveGoal.z == goalOrigin.z) {
return HopPathIsValid(startAreaNum, startOrigin,
path.moveAreaNum, path.moveGoal, parms);
}
if (HopPathIsValid(startAreaNum, startOrigin, goalAreaNum,
goalOrigin, parms)) {
path = idAAS2Path();
path.type = 0;
path.moveGoal = goalOrigin;
path.moveAreaNum = goalAreaNum;
path.obstacleGoal = goalOrigin;
path.obstacleAreaNum = goalAreaNum;
path.travelTime = AreaTravelTime(startAreaNum,
startOrigin, goalOrigin);
path.pathMaxHeight = (std::max)(0.0f,
goalOrigin.z - startOrigin.z);
path.obstacleRoute.numAreas = 1;
path.obstacleRoute.areas[0].areaNum = startAreaNum;
path.obstacleRoute.areas[0].start = startOrigin;
path.obstacleRoute.areas[0].end = goalOrigin;
return true;
}
if (!WalkPathToGoal(path, startAreaNum, startOrigin, goalAreaNum,
goalOrigin, travelFlags, obstacleFlags)) return false;
return HopPathIsValid(startAreaNum, startOrigin, path.moveAreaNum,
path.moveGoal, parms);
}
bool idAAS2Local::GetPath(const idVec3& origin,
const idVec3& goalOrigin, const int areaNum,
const int goalAreaNum, const int travelFlags,
idAAS2PathPoint* const points, const int maxPoints,
int& numPoints) {
numPoints = 0;
if (file == nullptr || points == nullptr || maxPoints <= 0
|| areaNum <= 0 || goalAreaNum <= 0) return false;
points[0].areaNum = areaNum;
points[0].origin = origin;
points[0].nextReachIndex.Invalidate();
points[0].travelTime = 0;
numPoints = 1;
int currentArea = areaNum;
idVec3 currentOrigin = origin;
int cumulativeTime = 0;
while (currentArea != goalAreaNum && numPoints < maxPoints) {
int routeTime = 0;
idIndex<short, invalidReachability_t> reachIndex;
if (!RouteToGoalArea(currentArea, currentOrigin, goalAreaNum,
goalOrigin, travelFlags, routeTime, reachIndex)
|| !reachIndex.IsValid()) break;
const int reachNum = reachIndex.Get();
if (reachNum < 0 || reachNum >= file->reachabilities.Num()) break;
const aas2Reachability_t& reach = file->reachabilities[reachNum];
points[numPoints - 1].nextReachIndex = reachIndex;
cumulativeTime += reach.travelTime
+ AreaTravelTime(currentArea, currentOrigin, reach.Start());
points[numPoints].areaNum = reach.toAreaNum;
points[numPoints].origin = reach.End();
points[numPoints].nextReachIndex.Invalidate();
points[numPoints].travelTime = cumulativeTime;
currentArea = reach.toAreaNum;
currentOrigin = reach.End();
++numPoints;
}
if (currentArea != goalAreaNum) return false;
if (numPoints < maxPoints) {
points[numPoints].areaNum = goalAreaNum;
points[numPoints].origin = goalOrigin;
points[numPoints].nextReachIndex.Invalidate();
points[numPoints].travelTime = cumulativeTime
+ AreaTravelTime(goalAreaNum, currentOrigin, goalOrigin);
++numPoints;
} else {
points[numPoints - 1].origin = goalOrigin;
}
return true;
}
bool idAAS2Local::WalkPathIsValid(const int startAreaNum,
const idVec3& startOrigin, const int goalAreaNum,
const idVec3& goalOrigin, const int travelFlags,
int& endAreaNum, aas2EdgeCrossed_t& firstEdge) const {
int areas[128] = {};
idIndex<short, invalidReachability_t> reachIndices[128];
aas2TraceFloor_t trace{};
trace.maxAreas = 128;
trace.areas = areas;
trace.maxReachIndices = 128;
trace.reachIndices = reachIndices;
if (!TraceFloor(trace, startOrigin, startAreaNum, goalOrigin,
travelFlags, true, false, true)) {
endAreaNum = trace.lastAreaNum;
firstEdge = trace.firstEdge;
return false;
}
endAreaNum = trace.lastAreaNum;
firstEdge = trace.firstEdge;
return trace.lastAreaNum == goalAreaNum;
}
void idAAS2Local::SubSampleWalkPath(const int startAreaNum,
const idVec3& startOrigin, const int pathAreaNum,
const idVec3& pathStart, const idVec3& pathEnd,
const int travelFlags, idVec3& endPos, int& endAreaNum,
aas2EdgeCrossed_t& firstEdge) const {
endPos = pathStart;
endAreaNum = pathAreaNum;
firstEdge = aas2EdgeCrossed_t{};
const idVec3 delta = pathEnd - pathStart;
float validFraction = 0.0f;
for (int sample = 1; sample <= 8; ++sample) {
const float fraction = float(sample) / 8.0f;
const idVec3 candidate = pathStart + delta * fraction;
int candidateArea = pathAreaNum;
aas2EdgeCrossed_t candidateEdge{};
if (!WalkPathIsValid(startAreaNum, startOrigin, pathAreaNum,
candidate, travelFlags, candidateArea, candidateEdge)) break;
validFraction = fraction;
endPos = candidate;
endAreaNum = candidateArea;
if (firstEdge.edgeNum == 0 && candidateEdge.edgeNum != 0) {
firstEdge = candidateEdge;
}
}
if (validFraction == 0.0f) endPos = pathStart;
}
bool idAAS2Local::GetOptimizedWalkPath(const int startAreaNum,
const idVec3& startOrigin, const int goalAreaNum,
const idVec3& goalOrigin, const int travelFlags,
const int, idList<idVec3, 5>& path) {
path.Clear();
idAAS2PathPoint rawPath[256];
int count = 0;
if (!GetPath(startOrigin, goalOrigin, startAreaNum, goalAreaNum,
travelFlags, rawPath, 256, count)) return false;
int anchor = 0;
path.Append(rawPath[0].origin);
while (anchor < count - 1) {
int farthest = anchor + 1;
for (int candidate = count - 1; candidate > anchor + 1;
--candidate) {
int endArea = rawPath[anchor].areaNum;
aas2EdgeCrossed_t edge{};
if (WalkPathIsValid(rawPath[anchor].areaNum,
rawPath[anchor].origin, rawPath[candidate].areaNum,
rawPath[candidate].origin, travelFlags, endArea, edge)) {
farthest = candidate;
break;
}
}
path.Append(rawPath[farthest].origin);
anchor = farthest;
}
return true;
}
bool idAAS2Local::WalkPathToGoal(idAAS2Path& path,
const int startAreaNum, const idVec3& startOrigin,
const int goalAreaNum, const idVec3& goalOrigin,
const int travelFlags, const int) {
path = idAAS2Path();
path.type = 0;
path.moveGoal = startOrigin;
path.moveAreaNum = startAreaNum;
path.obstacleGoal = startOrigin;
path.obstacleAreaNum = startAreaNum;
path.obstacleRoute.numAreas = 1;
path.obstacleRoute.areas[0].areaNum = startAreaNum;
path.obstacleRoute.areas[0].start = startOrigin;
path.obstacleRoute.areas[0].end = startOrigin;
if (file == nullptr) return false;
if (startAreaNum == goalAreaNum) {
path.moveGoal = goalOrigin;
path.obstacleGoal = goalOrigin;
path.travelTime = AreaTravelTime(startAreaNum,
startOrigin, goalOrigin);
path.obstacleRoute.areas[0].end = goalOrigin;
path.pathMaxHeight = (std::max)(0.0f,
goalOrigin.z - startOrigin.z);
return true;
}
int routeTime = 0;
idIndex<short, invalidReachability_t> reachIndex;
if (!RouteToGoalArea(startAreaNum, startOrigin, goalAreaNum,
goalOrigin, travelFlags, routeTime, reachIndex)
|| !reachIndex.IsValid()) return false;
const aas2Reachability_t* const reach = GetReachability(reachIndex);
if (reach == nullptr) return false;
path.reachIndex = reachIndex;
path.type = PathTypeForTravelFlags(reach->travelFlags);
path.moveGoal = reach->Start();
path.moveAreaNum = startAreaNum;
path.obstacleGoal = path.moveGoal;
path.obstacleAreaNum = startAreaNum;
path.travelTime = routeTime;
path.obstacleRoute.areas[0].end = path.moveGoal;
path.pathMaxHeight = (std::max)(0.0f,
path.moveGoal.z - startOrigin.z);
idVec3 traversalStart;
idVec3 traversalEnd;
idIndex<short, invalidAASDependencyIndex_t> dependency;
const int traversal = FindBestTraversalForReachability(reachIndex,
startOrigin, goalOrigin, traversalStart, traversalEnd, dependency,
travelFlags, false);
if (traversal >= 0) {
path.type = 9;
path.traversalIndex = traversal;
path.traversalStart = traversalStart;
path.traversalEnd = traversalEnd;
path.dependencyIndex = dependency;
path.moveGoal = traversalStart;
}
return true;
}
int idAAS2Local::FindBestTraversalForReachability(
const idIndex<short, invalidReachability_t> reachability,
const idVec3& currentPoint, const idVec3& goalPoint,
idVec3& traversalStart, idVec3& traversalEnd,
idIndex<short, invalidAASDependencyIndex_t>& dependency,
const int travelFlags, const bool useClosestPointOnFail) const {
idList<int, 5> traversals;
const int count = GetTraversalsForReachability(reachability, traversals);
traversalStart.Zero();
traversalEnd.Zero();
dependency.Invalidate();
int bestIndex = -1;
float bestDistance = (std::numeric_limits<float>::max)();
for (int index = 0; index < count; ++index) {
const int traversalIndex = traversals[index];
const aas2Traversal_t* const traversal = GetTraversal(traversalIndex);
if (traversal == nullptr) continue;
const bool enabled = (traversal->flags & 1u) != 0;
if ((!enabled && !useClosestPointOnFail)
|| !TraversalClassAllowed(*traversal, travelFlags)) continue;
const float distance = LengthSqr(traversal->startPoint - currentPoint)
+ LengthSqr(traversal->endPoint - goalPoint);
if (distance < bestDistance) {
bestDistance = distance;
bestIndex = traversalIndex;
traversalStart = traversal->startPoint;
traversalEnd = traversal->endPoint;
dependency = traversal->dependencyIndex;
}
}
return bestIndex;
}