#include "gamelib/aas2/aas2_local.h" #include #include #include 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 reachability, idList& traversals) const { return file != nullptr ? file->GetTraversalsForReachability(reachability, traversals) : 0; } int idAAS2Local::GetTraversalsForInteractableEntity( const idIndex entity, idList& 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 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 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& 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 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 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 reachability, const idVec3& currentPoint, const idVec3& goalPoint, idVec3& traversalStart, idVec3& traversalEnd, idIndex& dependency, const int travelFlags, const bool useClosestPointOnFail) const { idList traversals; const int count = GetTraversalsForReachability(reachability, traversals); traversalStart.Zero(); traversalEnd.Zero(); dependency.Invalidate(); int bestIndex = -1; float bestDistance = (std::numeric_limits::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; }