#include "gamelib/aas2/aas2_local.h" #include "idlib/lib_print.h" #include #include #include #include #include #include #include namespace { constexpr int ROUTE_INFINITY = 0x3FFFFFFF; bool ReachAllowed(const aas2Reachability_t& reach, const int travelFlags) { return (reach.travelFlags & AAS_TFL_INVALID) == 0 && (reach.travelFlags & travelFlags) != 0; } } // namespace idAASRoutingCache::idAASRoutingCache(const int cacheSize) : type(0), size((std::max)(0, cacheSize)), cluster(0), areaNum(0), travelFlags(0), next(nullptr), prev(nullptr), time_next(nullptr), time_prev(nullptr), startTravelTime(0), reachabilities(nullptr), travelTimes(nullptr) { if (size > 0) { reachabilities = new std::uint8_t[size](); travelTimes = new std::uint16_t[size](); } } idAASRoutingCache::~idAASRoutingCache() { delete[] reachabilities; delete[] travelTimes; } void idAAS2Local::RoutingStats() const { idLibPrint::Printf("%6d kB routing cache\n", totalCacheMemory >> 10); } void idAAS2Local::UnlinkCache(idAASRoutingCache* const cache) { if (cache == nullptr) return; if (cache->time_next != nullptr) cache->time_next->time_prev = cache->time_prev; else cacheListEnd = cache->time_prev; if (cache->time_prev != nullptr) cache->time_prev->time_next = cache->time_next; else cacheListStart = cache->time_next; cache->time_next = nullptr; cache->time_prev = nullptr; } void idAAS2Local::LinkCache(idAASRoutingCache* const cache) { if (cache == nullptr) return; if (cache->time_next != nullptr || cache->time_prev != nullptr || cacheListStart == cache) UnlinkCache(cache); cache->time_prev = cacheListEnd; cache->time_next = nullptr; if (cacheListEnd != nullptr) cacheListEnd->time_next = cache; else cacheListStart = cache; cacheListEnd = cache; } int idAAS2Local::CalcTravelTime(const idVec3& start, const idVec3& end, const int travelFlags) const { const idVec3 delta = end - start; const float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y + delta.z * delta.z); const float multiplier = (travelFlags & AAS_TFL_WATER) != 0 ? waterSpeedMultiplier : groundSpeedMultiplier; return (std::max)(1, static_cast(multiplier * distance)); } int idAAS2Local::AreaTravelTime(const int areaNum, const idVec3& start, const idVec3& end) const { return file != nullptr && areaNum > 0 && areaNum < file->areas.Num() ? CalcTravelTime(start, end, file->areas[areaNum].travelFlags) : 0; } void idAAS2Local::CalculateAreaTravelTimes() { delete[] areaTravelTimes; areaTravelTimes = nullptr; numAreaTravelTimes = 0; if (file == nullptr) return; for (int areaNum = 1; areaNum < file->areas.Num(); ++areaNum) { const aas2Area_t& area = file->areas[areaNum]; int count = 0; for (idIndex index = area.reach; index.IsValid();) { const int reachNum = index.Get(); if (reachNum < 0 || reachNum >= file->reachabilities.Num()) break; ++count; index = file->reachabilities[reachNum].next; } numAreaTravelTimes += count * count; } if (numAreaTravelTimes > 0) { areaTravelTimes = new std::uint16_t[numAreaTravelTimes](); } } void idAAS2Local::SetupRoutingCache() { if (file == nullptr) return; areaCacheIndexSize = 0; const int clusterCount = file->clusters.Num(); areaCacheIndex = clusterCount > 0 ? new idAASRoutingCache**[clusterCount]() : nullptr; for (int cluster = 0; cluster < clusterCount; ++cluster) { const int count = (std::max)(0, file->clusters[cluster].numReachableAreas); areaCacheIndexSize += count; areaCacheIndex[cluster] = count > 0 ? new idAASRoutingCache*[count]() : nullptr; } portalCacheIndexSize = file->areas.Num(); portalCacheIndex = portalCacheIndexSize > 0 ? new idAASRoutingCache*[portalCacheIndexSize]() : nullptr; areaUpdate = file->areas.Num() > 0 ? new idRoutingUpdate[file->areas.Num()]() : nullptr; portalUpdate = file->portals.Num() >= 0 ? new idRoutingUpdate[file->portals.Num() + 1]() : nullptr; goalAreaTravelTimes = file->areas.Num() > 0 ? new std::uint16_t[file->areas.Num()]() : nullptr; cacheListStart = nullptr; cacheListEnd = nullptr; totalCacheMemory = 0; } void idAAS2Local::DeleteClusterCache(const int clusterNum) { if (file == nullptr || areaCacheIndex == nullptr || clusterNum < 0 || clusterNum >= file->clusters.Num()) return; const int count = file->clusters[clusterNum].numReachableAreas; for (int slot = 0; slot < count; ++slot) { idAASRoutingCache* cache = areaCacheIndex[clusterNum][slot]; areaCacheIndex[clusterNum][slot] = nullptr; while (cache != nullptr) { idAASRoutingCache* const nextCache = cache->next; UnlinkCache(cache); totalCacheMemory -= sizeof(*cache) + 3 * cache->size; delete cache; cache = nextCache; } } } void idAAS2Local::DeletePortalCache() { if (portalCacheIndex == nullptr) return; for (int slot = 0; slot < portalCacheIndexSize; ++slot) { idAASRoutingCache* cache = portalCacheIndex[slot]; portalCacheIndex[slot] = nullptr; while (cache != nullptr) { idAASRoutingCache* const nextCache = cache->next; UnlinkCache(cache); totalCacheMemory -= sizeof(*cache) + 3 * cache->size; delete cache; cache = nextCache; } } } void idAAS2Local::ShutdownRoutingCache() { if (areaCacheIndex != nullptr && file != nullptr) { for (int cluster = 0; cluster < file->clusters.Num(); ++cluster) { DeleteClusterCache(cluster); delete[] areaCacheIndex[cluster]; } } DeletePortalCache(); delete[] areaCacheIndex; delete[] portalCacheIndex; delete[] areaUpdate; delete[] portalUpdate; delete[] goalAreaTravelTimes; areaCacheIndex = nullptr; portalCacheIndex = nullptr; areaUpdate = nullptr; portalUpdate = nullptr; goalAreaTravelTimes = nullptr; areaCacheIndexSize = 0; portalCacheIndexSize = 0; cacheListStart = nullptr; cacheListEnd = nullptr; totalCacheMemory = 0; } void idAAS2Local::SetupRouting() { if (file == nullptr) return; groundSpeedMultiplier = file->settings.groundSpeed != 0.0f ? 100.0f / file->settings.groundSpeed : 1.0f; waterSpeedMultiplier = file->settings.waterSpeed != 0.0f ? 100.0f / file->settings.waterSpeed : 1.0f; CalculateAreaTravelTimes(); SetupRoutingCache(); } void idAAS2Local::ShutdownRouting() { delete[] areaTravelTimes; areaTravelTimes = nullptr; numAreaTravelTimes = 0; ShutdownRoutingCache(); } void idAAS2Local::RemoveRoutingCacheUsingArea(const int) { if (file == nullptr) return; for (int cluster = 0; cluster < file->clusters.Num(); ++cluster) { DeleteClusterCache(cluster); } DeletePortalCache(); } bool idAAS2Local::ChangeAreaTravelFlags_r(int nodeNum, const idBounds& bounds, const int areaFlags, const int travelFlags, const bool set) { bool changed = false; if (file == nullptr || nodeNum == 0) return false; if (nodeNum < 0) { const int areaNum = -nodeNum; if (areaNum <= 0 || areaNum >= file->areas.Num()) return false; aas2Area_t& area = file->areas[areaNum]; if ((area.flags & areaFlags) == 0) return false; const std::uint32_t oldFlags = area.travelFlags; area.travelFlags = set ? oldFlags | travelFlags : oldFlags & ~std::uint32_t(travelFlags); if (area.travelFlags != oldFlags) { RemoveRoutingCacheUsingArea(areaNum); changed = true; } return changed; } if (nodeNum >= file->nodes.Num()) return false; const aas2Node_t& node = file->nodes[nodeNum]; if (node.planeNum >= static_cast(file->planes.Num())) { return false; } const int side = bounds.PlaneSide(file->planes[node.planeNum], 0.1f); if (side != 1) changed |= ChangeAreaTravelFlags_r(node.children[0], bounds, areaFlags, travelFlags, set); if (side != 0) changed |= ChangeAreaTravelFlags_r(node.children[1], bounds, areaFlags, travelFlags, set); return changed; } bool idAAS2Local::ChangeAreaTravelFlags(const idBounds& bounds, const int areaFlags, const int travelFlags, const bool set) { if (file == nullptr || file->trees.Num() == 0) return false; idBounds expanded; expanded[0] = bounds[0] - file->settings.boundingBox[1]; expanded[1] = bounds[1] - file->settings.boundingBox[0]; return ChangeAreaTravelFlags_r(file->trees[0].headNode, expanded, areaFlags, travelFlags, set); } bool idAAS2Local::ChangeReachabilityTravelFlags(const char* const nameValue, const int travelFlags, const bool set) { if (file == nullptr) return false; const idIndex index = file->FindReachabilityByName(nameValue); return index.IsValid() && ChangeReachabilityTravelFlags(index, travelFlags, set); } bool idAAS2Local::ChangeReachabilityTravelFlags( const idIndex index, const int travelFlags, const bool set) { const int reachNum = index.Get(); if (file == nullptr || reachNum < 0 || reachNum >= file->reachabilities.Num()) return false; aas2Reachability_t& reach = file->reachabilities[reachNum]; reach.travelFlags = set ? reach.travelFlags | travelFlags : reach.travelFlags & ~std::uint32_t(travelFlags); RemoveRoutingCacheUsingArea(reach.fromAreaNum); RemoveRoutingCacheUsingArea(reach.toAreaNum); return true; } idIndex idAAS2Local::GetAreaReachability( const int areaNum, int reachabilityNum) const { if (file == nullptr || areaNum <= 0 || areaNum >= file->areas.Num()) { return idIndex(); } idIndex index = file->areas[areaNum].reach; while (index.IsValid() && reachabilityNum-- > 0) { const int value = index.Get(); if (value < 0 || value >= file->reachabilities.Num()) { return idIndex(); } index = file->reachabilities[value].next; } return index; } int idAAS2Local::ClusterAreaNum(const int clusterNum, const int areaNum) const { if (file == nullptr || areaNum <= 0 || areaNum >= file->areas.Num()) { return 0; } const aas2Area_t& area = file->areas[areaNum]; if (area.cluster > 0) return area.clusterAreaNum; const int portalNum = -area.cluster; if (portalNum <= 0 || portalNum >= file->portals.Num()) return 0; const aas2Portal_t& portal = file->portals[portalNum]; return portal.clusterAreaNum[clusterNum != portal.clusters[0] ? 1 : 0]; } bool idAAS2Local::RouteToGoalArea(const int startAreaNum, const idVec3& startOrigin, const int goalAreaNum, const idVec3& goalOrigin, const int travelFlags, int& travelTime, idIndex& reachability) { travelTime = 0; reachability.Invalidate(); if (file == nullptr || startAreaNum <= 0 || goalAreaNum <= 0 || startAreaNum >= file->areas.Num() || goalAreaNum >= file->areas.Num()) return false; if (startAreaNum == goalAreaNum) { travelTime = AreaTravelTime(startAreaNum, startOrigin, goalOrigin); return true; } using queueValue_t = std::pair; std::priority_queue, std::greater> queue; std::vector costs(static_cast(file->areas.Num()), ROUTE_INFINITY); std::vector> firstReach( static_cast(file->areas.Num())); costs[startAreaNum] = 0; queue.push(queueValue_t(0, startAreaNum)); while (!queue.empty()) { const int cost = queue.top().first; const int areaNum = queue.top().second; queue.pop(); if (cost != costs[areaNum]) continue; if (areaNum == goalAreaNum) break; const aas2Area_t& area = file->areas[areaNum]; for (idIndex index = area.reach; index.IsValid();) { const int reachNum = index.Get(); if (reachNum < 0 || reachNum >= file->reachabilities.Num()) break; const aas2Reachability_t& reach = file->reachabilities[reachNum]; const idIndex next = reach.next; if (ReachAllowed(reach, travelFlags) && reach.toAreaNum > 0 && reach.toAreaNum < file->areas.Num()) { const idVec3 from = reach.Start(); const idVec3 to = reach.End(); int stepCost = reach.travelTime; stepCost += AreaTravelTime(areaNum, areaNum == startAreaNum ? startOrigin : from, from); stepCost += AreaTravelTime(reach.toAreaNum, from, to); const int nextCost = cost + (std::max)(1, stepCost); if (nextCost < costs[reach.toAreaNum]) { costs[reach.toAreaNum] = nextCost; firstReach[reach.toAreaNum] = areaNum == startAreaNum ? index : firstReach[areaNum]; queue.push(queueValue_t(nextCost, reach.toAreaNum)); } } index = next; } } if (costs[goalAreaNum] == ROUTE_INFINITY) return false; travelTime = costs[goalAreaNum] + AreaTravelTime(goalAreaNum, AreaCenter(goalAreaNum), goalOrigin); reachability = firstReach[goalAreaNum]; return true; } int idAAS2Local::TravelTimeToGoalArea(const int startAreaNum, const idVec3& startOrigin, const int goalAreaNum, const idVec3& goalOrigin, const int travelFlags, const idAAS2TravelSpeeds*) { int time = 0; idIndex reachability; return RouteToGoalArea(startAreaNum, startOrigin, goalAreaNum, goalOrigin, travelFlags, time, reachability) ? time : 0; } bool idAAS2Local::FindNearestGoal(idAAS2Goal& goal, const int startAreaNum, const idVec3& startOrigin, const int travelFlags, idAAS2Callback& callback) { if (file == nullptr || startAreaNum <= 0 || startAreaNum >= file->areas.Num()) return false; using queueValue_t = std::pair; std::priority_queue, std::greater> queue; std::vector costs(static_cast(file->areas.Num()), ROUTE_INFINITY); costs[startAreaNum] = 0; queue.push(queueValue_t(0, startAreaNum)); while (!queue.empty()) { const int cost = queue.top().first; const int areaNum = queue.top().second; queue.pop(); if (cost != costs[areaNum]) continue; const idVec3 center = AreaCenter(areaNum); if (callback.AreaIsGoal(this, areaNum, ¢er)) { goal.areaNum = areaNum; goal.origin = center; return true; } const aas2Area_t& area = file->areas[areaNum]; for (idIndex index = area.reach; index.IsValid();) { const int reachNum = index.Get(); if (reachNum < 0 || reachNum >= file->reachabilities.Num()) break; const aas2Reachability_t& reach = file->reachabilities[reachNum]; const idIndex next = reach.next; const idVec3 from = reach.Start(); const idVec3 to = reach.End(); if (ReachAllowed(reach, travelFlags) && callback.PathValid(this, &from, &to)) { const int extra = callback.AdditionalTravelTimeForPath( this, &from, &to); const int nextCost = cost + reach.travelTime + AreaTravelTime(areaNum, center, from) + extra; if (reach.toAreaNum > 0 && reach.toAreaNum < file->areas.Num() && nextCost < costs[reach.toAreaNum]) { costs[reach.toAreaNum] = nextCost; queue.push(queueValue_t(nextCost, reach.toAreaNum)); } } index = next; } } return false; } bool idAAS2Local::NearestPointReachable( const idIndex tree, const idVec3& origin, const int areaNum, const idVec3& destination, const idBounds& searchBounds, const int travelFlags, idAAS2NearestReachable& nearest) const { std::memset(&nearest, 0, sizeof(nearest)); if (file == nullptr || areaNum <= 0) return false; idBounds destinationBounds(searchBounds); destinationBounds[0] = destinationBounds[0] + destination; destinationBounds[1] = destinationBounds[1] + destination; int candidates[256] = {}; const int count = file->BoundsAreaNums(tree.Get(), destinationBounds, candidates, 256); float nearestDistance = (std::numeric_limits::max)(); int quickestTime = ROUTE_INFINITY; bool found = false; for (int index = 0; index < count; ++index) { const int candidate = candidates[index]; if (candidate <= 0 || candidate >= file->areas.Num()) continue; idVec3 point = destination; file->PushPointIntoAreaNum(candidate, point); const idVec3 delta = point - destination; const float distance = delta.x * delta.x + delta.y * delta.y + delta.z * delta.z; int time = 0; idIndex first; if (!const_cast(this)->RouteToGoalArea(areaNum, origin, candidate, point, travelFlags, time, first)) continue; if (distance < nearestDistance) { nearestDistance = distance; nearest.nearestDestArea = candidate; nearest.nearestDestPos = point; nearest.nearestTravelTime = time; } if (time < quickestTime) { quickestTime = time; nearest.quickestDestArea = candidate; nearest.quickestDestPos = point; nearest.quickestTravelTime = time; } found = true; } return found; } void idAAS2Local::DeleteOldestCache() { idAASRoutingCache* const cache = cacheListStart; if (cache == nullptr) return; if (cache->type == 0 && areaCacheIndex != nullptr && cache->cluster >= 0 && cache->cluster < file->clusters.Num()) { const int slot = ClusterAreaNum(cache->cluster, cache->areaNum); idAASRoutingCache** link = &areaCacheIndex[cache->cluster][slot]; while (*link != nullptr && *link != cache) link = &(*link)->next; if (*link == cache) *link = cache->next; } else if (portalCacheIndex != nullptr && cache->areaNum >= 0 && cache->areaNum < portalCacheIndexSize) { idAASRoutingCache** link = &portalCacheIndex[cache->areaNum]; while (*link != nullptr && *link != cache) link = &(*link)->next; if (*link == cache) *link = cache->next; } UnlinkCache(cache); totalCacheMemory -= sizeof(*cache) + 3 * cache->size; delete cache; } void idAAS2Local::UpdateAreaRoutingCache(idAASRoutingCache* const cache) { if (cache == nullptr || file == nullptr) return; for (int area = 0; area < cache->size; ++area) { const int sourceArea = area + 1; if (sourceArea >= file->areas.Num()) break; int time = 0; idIndex first; if (RouteToGoalArea(sourceArea, AreaCenter(sourceArea), cache->areaNum, AreaCenter(cache->areaNum), cache->travelFlags, time, first)) { cache->travelTimes[area] = static_cast( (std::min)(time, 65535)); int ordinal = 0; for (idIndex index = file->areas[sourceArea].reach; index.IsValid(); ++ordinal) { if (index == first) break; index = file->reachabilities[index.Get()].next; } cache->reachabilities[area] = static_cast(ordinal); } } } idAASRoutingCache* idAAS2Local::GetAreaRoutingCache(const int clusterNum, const int areaNum, const int travelFlags) { if (file == nullptr || areaCacheIndex == nullptr || clusterNum < 0 || clusterNum >= file->clusters.Num()) return nullptr; const int slot = ClusterAreaNum(clusterNum, areaNum); if (slot < 0 || slot >= file->clusters[clusterNum].numReachableAreas) { return nullptr; } idAASRoutingCache** link = &areaCacheIndex[clusterNum][slot]; while (*link != nullptr && (*link)->travelFlags != travelFlags) { link = &(*link)->next; } if (*link == nullptr) { idAASRoutingCache* const cache = new idAASRoutingCache(file->areas.Num() - 1); cache->type = 0; cache->cluster = clusterNum; cache->areaNum = areaNum; cache->travelFlags = travelFlags; *link = cache; totalCacheMemory += sizeof(*cache) + 3 * cache->size; UpdateAreaRoutingCache(cache); } LinkCache(*link); return *link; } void idAAS2Local::UpdatePortalRoutingCache(idAASRoutingCache* const cache) { UpdateAreaRoutingCache(cache); } idAASRoutingCache* idAAS2Local::GetPortalRoutingCache(const int clusterNum, const int areaNum, const int travelFlags) { if (file == nullptr || portalCacheIndex == nullptr || areaNum < 0 || areaNum >= portalCacheIndexSize) return nullptr; idAASRoutingCache** link = &portalCacheIndex[areaNum]; while (*link != nullptr && (*link)->travelFlags != travelFlags) { link = &(*link)->next; } if (*link == nullptr) { idAASRoutingCache* const cache = new idAASRoutingCache(file->areas.Num() - 1); cache->type = 1; cache->cluster = clusterNum; cache->areaNum = areaNum; cache->travelFlags = travelFlags; *link = cache; totalCacheMemory += sizeof(*cache) + 3 * cache->size; UpdatePortalRoutingCache(cache); } LinkCache(*link); return *link; } bool idAAS2Local::GetClusterRoute(const int startAreaNum, const idVec3& startOrigin, const int, const int goalAreaNum, const int travelFlags, int& travelTime, idIndex& bestReachability) { return RouteToGoalArea(startAreaNum, startOrigin, goalAreaNum, AreaCenter(goalAreaNum), travelFlags, travelTime, bestReachability); }