Files
2026-08-09 02:48:01 -07:00

972 lines
40 KiB
C++

#include "aas2file/aas2file.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <limits>
#include <memory>
#include <vector>
namespace {
int AbsoluteEdge(const int edgeNum) {
return edgeNum < 0 ? -edgeNum : edgeNum;
}
bool ValidArea(const idAAS2File& file, const int areaNum) {
return areaNum > 0 && areaNum < file.areas.Num();
}
bool ValidEdge(const idAAS2File& file, const int edgeNum) {
const int number = AbsoluteEdge(edgeNum);
return number >= 0 && number < file.edges.Num();
}
idBounds EmptyBounds() {
idBounds result;
const float maximum = (std::numeric_limits<float>::max)();
result[0].Set(maximum, maximum, maximum);
result[1].Set(-maximum, -maximum, -maximum);
return result;
}
void Include(idBounds& bounds, const idVec3& point) {
for (int axis = 0; axis < 3; ++axis) {
bounds[0][axis] = (std::min)(bounds[0][axis], point[axis]);
bounds[1][axis] = (std::max)(bounds[1][axis], point[axis]);
}
}
bool Intersects(const idBounds& left, const idBounds& right) {
for (int axis = 0; axis < 3; ++axis) {
if (left[1][axis] < right[0][axis]
|| left[0][axis] > right[1][axis]) return false;
}
return true;
}
bool AreaMatches(const aas2Area_t& area, const int requiredAreaFlags,
const int excludedTravelFlags) {
return (area.flags & requiredAreaFlags) != 0
&& (area.travelFlags & excludedTravelFlags) == 0;
}
void CollectBoundsAreas(const idAAS2File& file, const int nodeNum,
const idBounds& bounds, std::vector<int>& result,
const int maximum) {
if (static_cast<int>(result.size()) >= maximum || nodeNum == 0) return;
if (nodeNum < 0) {
const int areaNum = -nodeNum;
if (areaNum > 0
&& std::find(result.begin(), result.end(), areaNum) == result.end()) {
result.push_back(areaNum);
}
return;
}
if (nodeNum >= file.nodes.Num()) return;
const aas2Node_t& node = file.nodes[nodeNum];
if (static_cast<int>(node.planeNum) >= file.planes.Num()) return;
const int side = bounds.PlaneSide(file.planes[node.planeNum], 0.1f);
if (side == 3) {
CollectBoundsAreas(file, node.children[0], bounds, result, maximum);
CollectBoundsAreas(file, node.children[1], bounds, result, maximum);
} else {
CollectBoundsAreas(file, node.children[side], bounds, result, maximum);
}
}
void MarkBoundsAreas(const idAAS2File& file, const int nodeNum,
const idBounds& bounds, bool* touched) {
if (nodeNum == 0) return;
if (nodeNum < 0) {
touched[-nodeNum] = true;
return;
}
if (nodeNum >= file.nodes.Num()) return;
const aas2Node_t& node = file.nodes[nodeNum];
if (static_cast<int>(node.planeNum) >= file.planes.Num()) return;
const int side = bounds.PlaneSide(file.planes[node.planeNum], 0.1f);
if (side == 3) {
MarkBoundsAreas(file, node.children[0], bounds, touched);
MarkBoundsAreas(file, node.children[1], bounds, touched);
} else {
MarkBoundsAreas(file, node.children[side], bounds, touched);
}
}
struct TraceStackEntry {
idVec3 start;
idVec3 end;
int planeNum;
int nodeNum;
};
struct HeightTraceStackEntry {
idVec3 start;
idVec3 end;
int planeNum;
int nodeNum;
};
float SegmentFraction(const idVec3& start, const idVec3& end,
const idVec3& point, const bool ignoreZ = false) {
idVec3 delta = end - start;
idVec3 traveled = point - start;
if (ignoreZ) {
delta.z = 0.0f;
traveled.z = 0.0f;
}
const float lengthSquared = delta.LengthSqr();
if (lengthSquared <= 1.0e-20f) return 0.0f;
return (std::max)(0.0f, (std::min)(1.0f,
traveled.Dot(delta) / lengthSquared));
}
void BuildFloorTracePlanes(const idVec3& floorNormal,
const idVec3& start, const idVec3& end,
idPlane& pathPlane, idPlane& nearPlane) {
idVec3 pathNormal = floorNormal.Cross(end - start);
pathNormal.NormalizeFast();
idVec3 nearNormal = pathNormal.Cross(floorNormal);
nearNormal.NormalizeFast();
pathPlane = idPlane(pathNormal, pathNormal.Dot(start));
nearPlane = idPlane(nearNormal, nearNormal.Dot(start));
}
bool SameVector(const idVec3& first, const idVec3& second) {
return first.x == second.x && first.y == second.y
&& first.z == second.z;
}
} // namespace
idVec3 idAAS2File::AreaCenter(const int areaNum) const {
idVec3 center(0.0f, 0.0f, 0.0f);
if (!ValidArea(*this, areaNum)) return center;
const aas2Area_t& area = areas[areaNum];
int count = 0;
for (int index = 0; index < area.numEdges; ++index) {
const int edgeListIndex = area.firstEdge + index;
if (edgeListIndex < 0 || edgeListIndex >= edgeIndex.Num()) continue;
const int orientedEdge = edgeIndex[edgeListIndex];
if (!ValidEdge(*this, orientedEdge)) continue;
const aas2Edge_t& edge = edges[AbsoluteEdge(orientedEdge)];
if (edge.vertexNum[0] < 0 || edge.vertexNum[0] >= vertices.Num()
|| edge.vertexNum[1] < 0 || edge.vertexNum[1] >= vertices.Num()) {
continue;
}
center = center + (vertices[edge.vertexNum[0]]
+ vertices[edge.vertexNum[1]]) * 0.5f;
++count;
}
if (count > 0) center = center * (1.0f / static_cast<float>(count));
return center;
}
idBounds idAAS2File::EdgeBounds(const int edgeNum) const {
idBounds bounds = EmptyBounds();
if (!ValidEdge(*this, edgeNum)) return bounds;
const aas2Edge_t& edge = edges[AbsoluteEdge(edgeNum)];
for (int endpoint = 0; endpoint < 2; ++endpoint) {
if (edge.vertexNum[endpoint] >= 0
&& edge.vertexNum[endpoint] < vertices.Num()) {
Include(bounds, vertices[edge.vertexNum[endpoint]]);
}
}
return bounds;
}
idBounds idAAS2File::AreaBounds(const int areaNum) const {
idBounds bounds = EmptyBounds();
if (!ValidArea(*this, areaNum)) return bounds;
const aas2Area_t& area = areas[areaNum];
for (int index = 0; index < area.numEdges; ++index) {
const int listIndex = area.firstEdge + index;
if (listIndex < 0 || listIndex >= edgeIndex.Num()) continue;
const idBounds edgeBounds = EdgeBounds(edgeIndex[listIndex]);
Include(bounds, edgeBounds[0]);
Include(bounds, edgeBounds[1]);
}
return bounds;
}
int idAAS2File::PointAreaNum(const int tree, const idVec3& origin) const {
if (tree < 0 || tree >= trees.Num()) return 0;
int nodeNum = trees[tree].headNode;
int guard = 0;
while (nodeNum > 0 && guard++ <= nodes.Num()) {
if (nodeNum >= nodes.Num()) return 0;
const aas2Node_t& node = nodes[nodeNum];
if (static_cast<int>(node.planeNum) >= planes.Num()) return 0;
nodeNum = node.children[planes[node.planeNum].Distance(origin) <= 0.0f];
}
return nodeNum < 0 ? -nodeNum : 0;
}
int idAAS2File::BoundsAreaNums(const int tree, const idBounds& bounds,
int* const outputAreas, const int maxAreas) const {
if (tree < 0 || tree >= trees.Num() || outputAreas == nullptr
|| maxAreas <= 0) return 0;
std::vector<int> found;
found.reserve((std::min)(maxAreas, 128));
CollectBoundsAreas(*this, trees[tree].headNode, bounds, found, maxAreas);
for (int index = 0; index < static_cast<int>(found.size()); ++index) {
outputAreas[index] = found[index];
}
return static_cast<int>(found.size());
}
void idAAS2File::FlagBoundsAreas(const int tree, const idBounds& bounds,
bool* const areasTouched) const {
if (tree < 0 || tree >= trees.Num() || areasTouched == nullptr) return;
MarkBoundsAreas(*this, trees[tree].headNode, bounds, areasTouched);
}
int idAAS2File::BoundsReachableAreaNum(const int tree,
const idBounds& bounds, const std::uint16_t areaFlags,
const int excludeTravelFlags) const {
int found[256] = {};
const int count = BoundsAreaNums(tree, bounds, found, 256);
for (int index = 0; index < count; ++index) {
const int areaNum = found[index];
if (ValidArea(*this, areaNum)
&& AreaMatches(areas[areaNum], areaFlags, excludeTravelFlags)) {
return areaNum;
}
}
return 0;
}
bool idAAS2File::TraceHeight(const int tree, aas2TraceHeight_t& trace,
const idVec3& start, const idVec3& end) const {
if (tree < 0 || tree >= trees.Num()) return false;
trace.numPoints = 0;
std::vector<HeightTraceStackEntry> stack;
stack.reserve(128);
stack.push_back({ start, end, 0, trees[tree].headNode });
while (!stack.empty()) {
const HeightTraceStackEntry entry = stack.back();
stack.pop_back();
if (entry.nodeNum <= 0) continue;
if (entry.nodeNum >= nodes.Num()) return false;
const aas2Node_t& node = nodes[entry.nodeNum];
if ((node.flags & 2u) != 0) {
if (trace.points != nullptr && trace.numPoints < trace.maxPoints) {
trace.points[trace.numPoints] = entry.start;
trace.points[trace.numPoints].z = static_cast<float>(
static_cast<int>(node.flags >> 2) - 0x20000000);
++trace.numPoints;
}
continue;
}
if (static_cast<int>(node.planeNum) >= planes.Num()) return false;
const idPlane& plane = planes[node.planeNum];
const float firstDistance = plane.Distance(entry.start);
const float secondDistance = plane.Distance(entry.end);
if (firstDistance >= -0.1f && secondDistance >= -0.1f) {
stack.push_back({ entry.start, entry.end, entry.planeNum,
node.children[0] });
continue;
}
if (firstDistance < 0.1f && secondDistance < 0.1f) {
stack.push_back({ entry.start, entry.end, entry.planeNum,
node.children[1] });
continue;
}
float fraction = (firstDistance >= 0.0f
? firstDistance - 0.125f : firstDistance + 0.125f)
/ (firstDistance - secondDistance);
fraction = (std::max)(0.001f, (std::min)(0.999f, fraction));
const idVec3 middle = entry.start
+ (entry.end - entry.start) * fraction;
const int firstSide = firstDistance < 0.0f ? 1 : 0;
if (stack.size() + 2 > 128) return false;
stack.push_back({ middle, entry.end,
static_cast<int>(node.planeNum), node.children[1 - firstSide] });
stack.push_back({ entry.start, middle, entry.planeNum,
node.children[firstSide] });
}
return true;
}
bool idAAS2File::GetFloorEdgeSplitPoints(floorEdgeSplitPoint_t& nearest,
floorEdgeSplitPoint_t& furthest, const int areaNum,
const idPlane& pathPlane, const idPlane& nearPlane) const {
nearest.point.Zero(); nearest.dist = 1.0e30f; nearest.edgeNum = 0;
furthest.point.Zero(); furthest.dist = -1.0e30f; furthest.edgeNum = 0;
if (!ValidArea(*this, areaNum)) return false;
const aas2Area_t& area = areas[areaNum];
int splits = 0;
for (int index = 0; index < area.numEdges; ++index) {
const int listIndex = area.firstEdge + index;
if (listIndex < 0 || listIndex >= edgeIndex.Num()) continue;
const int orientedEdge = edgeIndex[listIndex];
if (!ValidEdge(*this, orientedEdge)) continue;
const aas2Edge_t& edge = edges[AbsoluteEdge(orientedEdge)];
const idVec3& first = vertices[edge.vertexNum[0]];
const idVec3& second = vertices[edge.vertexNum[1]];
const float firstSide = pathPlane.Distance(first);
const float secondSide = pathPlane.Distance(second);
if ((firstSide < 0.0f && secondSide < 0.0f)
|| (firstSide > 0.0f && secondSide > 0.0f)) continue;
float fraction = 0.0f;
if (std::fabs(firstSide - secondSide) > 1.0e-20f) {
fraction = firstSide / (firstSide - secondSide);
}
fraction = (std::max)(0.0f, (std::min)(1.0f, fraction));
const idVec3 split = first + (second - first) * fraction;
const float distance = nearPlane.Distance(split);
if (distance < nearest.dist) {
nearest.point = split;
nearest.dist = distance;
nearest.edgeNum = orientedEdge;
}
if (distance > furthest.dist) {
furthest.point = split;
furthest.dist = distance;
furthest.edgeNum = orientedEdge;
}
++splits;
}
return splits != 0;
}
bool idAAS2File::Trace(const int tree, aas2Trace_t& trace,
const idVec3& start, const idVec3& end) const {
if (tree < 0 || tree >= trees.Num()) return false;
trace.numAreas = 0;
trace.lastAreaNum = 0;
trace.blockingAreaNum = 0;
std::vector<TraceStackEntry> stack;
stack.reserve(128);
stack.push_back({ start, end, 0, trees[tree].headNode });
const idVec3 traceDirection = end - start;
const float traceLength = traceDirection.Length();
while (!stack.empty()) {
const TraceStackEntry entry = stack.back();
stack.pop_back();
if (entry.nodeNum < 0) {
const int areaNum = -entry.nodeNum;
if (!ValidArea(*this, areaNum)) continue;
const aas2Area_t& area = areas[areaNum];
const bool areaBlocked = (area.flags & trace.flags) != 0
|| (area.travelFlags & trace.travelFlags) != 0;
if (areaBlocked) {
trace.fraction = trace.lastAreaNum != 0 && traceLength > 0.0f
? (entry.start - start).Length() / traceLength : 0.0f;
trace.endpos = entry.start;
trace.planeNum = entry.planeNum;
trace.blockingAreaNum = areaNum;
if (entry.planeNum >= 0 && entry.planeNum < planes.Num()
&& traceDirection.Dot(planes[entry.planeNum].Normal()) > 0.0f) {
trace.planeNum ^= 1;
}
return true;
}
trace.lastAreaNum = areaNum;
if (trace.numAreas < trace.maxAreas) {
if (trace.areas != nullptr) trace.areas[trace.numAreas] = areaNum;
if (trace.points != nullptr) trace.points[trace.numAreas] = entry.start;
++trace.numAreas;
}
continue;
}
if (entry.nodeNum == 0) {
trace.fraction = trace.lastAreaNum != 0 && traceLength > 0.0f
? (entry.start - start).Length() / traceLength : 0.0f;
trace.endpos = entry.start;
trace.planeNum = entry.planeNum;
trace.blockingAreaNum = 0;
if (entry.planeNum >= 0 && entry.planeNum < planes.Num()
&& traceDirection.Dot(planes[entry.planeNum].Normal()) > 0.0f) {
trace.planeNum ^= 1;
}
if (trace.lastAreaNum != 0 || trace.getOutOfSolid == 0) return true;
continue;
}
if (entry.nodeNum >= nodes.Num()) return false;
const aas2Node_t& node = nodes[entry.nodeNum];
if (static_cast<int>(node.planeNum) >= planes.Num()) return false;
const idPlane& plane = planes[node.planeNum];
const float firstDistance = plane.Distance(entry.start);
const float secondDistance = plane.Distance(entry.end);
if (firstDistance >= -0.1f && secondDistance >= -0.1f) {
stack.push_back({ entry.start, entry.end, entry.planeNum,
node.children[0] });
continue;
}
if (firstDistance < 0.1f && secondDistance < 0.1f) {
stack.push_back({ entry.start, entry.end, entry.planeNum,
node.children[1] });
continue;
}
float fraction = (firstDistance >= 0.0f
? firstDistance - 0.125f : firstDistance + 0.125f)
/ (firstDistance - secondDistance);
fraction = (std::max)(0.001f, (std::min)(0.999f, fraction));
const idVec3 middle = entry.start
+ (entry.end - entry.start) * fraction;
const int firstSide = firstDistance < 0.0f ? 1 : 0;
if (stack.size() + 2 > 128) return false;
stack.push_back({ middle, entry.end, static_cast<int>(node.planeNum),
node.children[1 - firstSide] });
stack.push_back({ entry.start, middle, entry.planeNum,
node.children[firstSide] });
}
trace.fraction = trace.lastAreaNum != 0 ? 1.0f : 0.0f;
trace.endpos = trace.lastAreaNum != 0 ? end : start;
trace.planeNum = 0;
return false;
}
int idAAS2File::GetObstaclePVSWallEdges(const int areaNum,
const int edgeFlags, int* const outputEdges, const int maxEdges) const {
if (!ValidArea(*this, areaNum) || outputEdges == nullptr || maxEdges <= 0) {
return 0;
}
std::vector<int> collected;
collected.reserve(maxEdges);
auto appendAreaEdges = [&](const int visibleArea) {
if (!ValidArea(*this, visibleArea)) return;
const aas2Area_t& area = areas[visibleArea];
for (int index = 0; index < area.numEdges
&& static_cast<int>(collected.size()) < maxEdges; ++index) {
const int listIndex = area.firstEdge + index;
if (listIndex < 0 || listIndex >= edgeIndex.Num()) continue;
const int edgeNum = edgeIndex[listIndex];
if (!ValidEdge(*this, edgeNum)
|| (edges[AbsoluteEdge(edgeNum)].flags & edgeFlags) == 0) continue;
collected.push_back(edgeNum);
}
};
std::uint32_t offset = areas[areaNum].obstaclePVSOffset;
int visibleArea = 0;
while (offset < static_cast<std::uint32_t>(obstaclePVS.Num())
&& visibleArea < areas.Num()
&& static_cast<int>(collected.size()) < maxEdges) {
const std::uint8_t encoded = obstaclePVS[offset++];
if ((encoded & 0x80u) != 0) {
int skip = encoded & 0x3Fu;
if ((encoded & 0x40u) != 0
&& offset < static_cast<std::uint32_t>(obstaclePVS.Num())) {
skip |= obstaclePVS[offset++] << 6;
}
visibleArea += skip + 1;
continue;
}
for (int bit = 0; bit < 7 && visibleArea < areas.Num(); ++bit) {
if ((encoded & (1u << bit)) != 0) appendAreaEdges(visibleArea);
++visibleArea;
}
}
if (collected.empty()) return 0;
if (static_cast<int>(collected.size()) >= maxEdges) {
for (int index = 0; index < maxEdges; ++index) {
outputEdges[index] = collected[index];
}
return maxEdges;
}
auto startVertex = [&](const int edgeNum) {
const aas2Edge_t& edge = edges[AbsoluteEdge(edgeNum)];
return edge.vertexNum[edgeNum < 0 ? 1 : 0];
};
auto endVertex = [&](const int edgeNum) {
const aas2Edge_t& edge = edges[AbsoluteEdge(edgeNum)];
return edge.vertexNum[edgeNum < 0 ? 0 : 1];
};
std::vector<std::vector<int>> chains;
chains.reserve(collected.size());
for (const int edgeNum : collected) chains.push_back({ edgeNum });
for (std::size_t first = 0; first < chains.size(); ++first) {
bool joined = true;
while (joined) {
joined = false;
for (std::size_t second = first + 1; second < chains.size(); ++second) {
if (endVertex(chains[first].back())
== startVertex(chains[second].front())) {
chains[first].insert(chains[first].end(),
chains[second].begin(), chains[second].end());
} else if (endVertex(chains[second].back())
== startVertex(chains[first].front())) {
chains[first].insert(chains[first].begin(),
chains[second].begin(), chains[second].end());
} else {
continue;
}
chains.erase(chains.begin() + second);
joined = true;
break;
}
}
}
int outputCount = 0;
for (const std::vector<int>& chain : chains) {
for (const int edgeNum : chain) outputEdges[outputCount++] = edgeNum;
}
return static_cast<int>(collected.size());
}
void idAAS2File::ClipGridToAreas(const idVec3& startOrigin,
const int startAreaNum, const int travelFlags,
const unsigned int cellSize, const int dimension,
std::uint8_t* const reachable) const {
if (reachable == nullptr || dimension <= 0 || cellSize == 0) return;
const int cellCount = dimension * dimension;
std::memset(reachable, 0, static_cast<std::size_t>(cellCount));
if (!ValidArea(*this, startAreaNum)) return;
const idIndex<int, invalidAASTree_t> treeIndex = GetTreeForArea(startAreaNum);
const int treeNum = treeIndex.Get();
if (treeNum < 0 || treeNum >= trees.Num()) return;
const float half = 0.5f * static_cast<float>(cellSize * dimension);
idBounds searchBounds;
const idVec3 extents(half, half, half);
searchBounds[0] = startOrigin - extents;
searchBounds[1] = startOrigin + extents;
std::unique_ptr<bool[]> areasTouched(new bool[areas.Num()]());
FlagBoundsAreas(treeNum, searchBounds, areasTouched.get());
std::vector<int> queue;
queue.push_back(startAreaNum);
areasTouched[startAreaNum] = false;
for (std::size_t cursor = 0; cursor < queue.size() && queue.size() < 256; ++cursor) {
const aas2Area_t& area = areas[queue[cursor]];
int reachIndex = area.reach.Get();
int guard = 0;
while (reachIndex >= 0 && reachIndex < reachabilities.Num()
&& guard++ < reachabilities.Num()) {
const aas2Reachability_t& reachability = reachabilities[reachIndex];
const int target = reachability.toAreaNum;
if (ValidArea(*this, target) && areasTouched[target]
&& (reachability.travelFlags & travelFlags) != 0
&& (reachability.travelFlags & ~travelFlags) == 0
&& (areas[target].travelFlags & travelFlags) != 0
&& (areas[target].travelFlags & ~travelFlags) == 0) {
areasTouched[target] = false;
queue.push_back(target);
}
reachIndex = reachability.next.Get();
}
}
const idVec3& floorNormal = trees[treeNum].floorNormal;
for (const int areaNum : queue) {
const aas2Area_t& area = areas[areaNum];
for (int y = 0; y < dimension; ++y) {
for (int x = 0; x < dimension; ++x) {
const idVec3 point(
startOrigin.x - half + (x + 0.5f) * cellSize,
startOrigin.y - half + (y + 0.5f) * cellSize,
startOrigin.z);
bool inside = true;
bool testedEdge = false;
for (int edgeOffset = 0; edgeOffset < area.numEdges;
++edgeOffset) {
const int listIndex = area.firstEdge + edgeOffset;
if (listIndex < 0 || listIndex >= edgeIndex.Num()) continue;
const int orientedEdge = edgeIndex[listIndex];
if (!ValidEdge(*this, orientedEdge)) continue;
const aas2Edge_t& edge = edges[AbsoluteEdge(orientedEdge)];
if ((edge.flags & AAS_EDGE_VERTICAL) != 0) continue;
const int firstIndex = orientedEdge < 0
? edge.vertexNum[1] : edge.vertexNum[0];
const int secondIndex = orientedEdge < 0
? edge.vertexNum[0] : edge.vertexNum[1];
if (firstIndex < 0 || firstIndex >= vertices.Num()
|| secondIndex < 0 || secondIndex >= vertices.Num()) {
continue;
}
const idVec3& first = vertices[firstIndex];
idVec3 inward = (vertices[secondIndex] - first).Cross(floorNormal);
if (inward.NormalizeFast() == 0.0f) continue;
testedEdge = true;
if ((point - first).Dot(inward) < 0.0f) {
inside = false;
break;
}
}
if (inside && testedEdge) reachable[y * dimension + x] = 0xFF;
}
}
}
}
float idAAS2File::GetFloorDistance(const int areaNum,
const idPlane& floorPlane, const idVec3& point, const float bboxHeight,
const float maxEdgeDist) const {
if (!ValidArea(*this, areaNum)) return 1.0e30f;
const idIndex<int, invalidAASTree_t> treeIndex = GetTreeForArea(areaNum);
const idVec3* const floorNormal = GetFloorNormalForTree(treeIndex);
if (floorNormal == nullptr) return 1.0e30f;
const float denominator = floorNormal->Dot(floorPlane.Normal());
if (std::fabs(denominator) <= 1.0e-20f) return 1.0e30f;
float floorDistance = std::fabs(floorPlane.Distance(point)) / denominator;
if (floorDistance < bboxHeight) return floorDistance;
float nearestEdgeSquared = 1.0e30f;
idVec3 nearestDelta(0.0f, 0.0f, 0.0f);
const aas2Area_t& area = areas[areaNum];
for (int index = 0; index < area.numEdges; ++index) {
const int listIndex = area.firstEdge + index;
if (listIndex < 0 || listIndex >= edgeIndex.Num()) continue;
const int edgeNum = AbsoluteEdge(edgeIndex[listIndex]);
if (edgeNum < 0 || edgeNum >= edges.Num()) continue;
const aas2Edge_t& edge = edges[edgeNum];
if (edge.vertexNum[0] < 0 || edge.vertexNum[0] >= vertices.Num()
|| edge.vertexNum[1] < 0 || edge.vertexNum[1] >= vertices.Num()) {
continue;
}
const idVec3& first = vertices[edge.vertexNum[0]];
const idVec3& second = vertices[edge.vertexNum[1]];
const idVec3 segment = second - first;
const float lengthSquared = segment.LengthSqr();
if (lengthSquared < 0.1f) continue;
float fraction = (point - first).Dot(segment) / lengthSquared;
fraction = (std::max)(0.0f, (std::min)(1.0f, fraction));
const idVec3 delta = point - (first + segment * fraction);
const float distanceSquared = delta.LengthSqr();
if (distanceSquared < nearestEdgeSquared) {
nearestEdgeSquared = distanceSquared;
nearestDelta = delta;
}
}
if (nearestEdgeSquared < maxEdgeDist * maxEdgeDist) {
const float edgeFloorDistance = std::fabs(floorNormal->Dot(nearestDelta));
if (edgeFloorDistance < floorDistance) floorDistance = edgeFloorDistance;
}
return floorDistance;
}
void idAAS2File::PointBestReachableAreaNum(const int tree,
const idVec3& origin, bestReachableArea_t& bestArea) const {
if (tree < 0 || tree >= trees.Num()) return;
int floorPlaneNum = -1;
int nodeNum = trees[tree].headNode;
while (nodeNum >= 0) {
if (nodeNum == 0 || nodeNum >= nodes.Num()) return;
const aas2Node_t& node = nodes[nodeNum];
if (static_cast<int>(node.planeNum) >= planes.Num()) return;
if (planes[node.planeNum].Distance(origin) <= 0.0f) {
nodeNum = node.children[1];
} else {
nodeNum = node.children[0];
if ((node.flags & 1u) != 0) {
floorPlaneNum = static_cast<int>(node.planeNum);
}
}
}
const int areaNum = -nodeNum;
if (!ValidArea(*this, areaNum) || floorPlaneNum < 0
|| !AreaMatches(areas[areaNum], bestArea.areaFlags,
bestArea.excludeTravelFlags)) {
return;
}
bestArea.pointAreaFloorDist = GetFloorDistance(areaNum,
planes[floorPlaneNum], origin, bestArea.bboxHeight,
bestArea.maxEdgeDist);
bestArea.pointAreaNum = areaNum;
}
void idAAS2File::BoundsBestReachableAreaNum(const int tree,
const idBounds& bounds, const idVec3& origin,
bestReachableArea_t& bestArea) const {
if (tree < 0 || tree >= trees.Num()) return;
int floorPlaneNum = -1;
std::vector<int> stack;
int nodeNum = trees[tree].headNode;
for (;;) {
while (nodeNum > 0) {
if (nodeNum >= nodes.Num()) return;
const aas2Node_t& node = nodes[nodeNum];
if (static_cast<int>(node.planeNum) >= planes.Num()) return;
const int side = bounds.PlaneSide(planes[node.planeNum], 0.1f);
if ((node.flags & 1u) != 0 && side != 1) {
floorPlaneNum = static_cast<int>(node.planeNum);
}
if (side == 3) {
stack.push_back(node.children[1]);
nodeNum = node.children[0];
} else {
nodeNum = node.children[side];
}
}
const int areaNum = -nodeNum;
if (nodeNum < 0 && ValidArea(*this, areaNum) && floorPlaneNum >= 0
&& AreaMatches(areas[areaNum], bestArea.areaFlags,
bestArea.excludeTravelFlags)) {
const float floorDistance = GetFloorDistance(areaNum,
planes[floorPlaneNum], origin, bestArea.bboxHeight,
bestArea.maxEdgeDist);
if (floorDistance < bestArea.pointAreaFloorDist
- bestArea.bboxHeight
&& floorDistance < bestArea.boundsAreaFloorDist) {
bestArea.boundsAreaFloorDist = floorDistance;
bestArea.boundsAreaNum = areaNum;
}
}
if (stack.empty()) break;
nodeNum = stack.back();
stack.pop_back();
}
}
int idAAS2File::PointReachableAreaNum(
const idIndex<int, invalidAASTree_t> tree, const idVec3& origin,
const int areaFlags, const int excludeTravelFlags) const {
const int treeNum = tree.Get();
if (treeNum < 0 || treeNum >= trees.Num()) return 0;
bestReachableArea_t bestArea{};
bestArea.bboxHeight = settings.boundingBox[1].z
- settings.boundingBox[0].z;
bestArea.maxEdgeDist = (std::min)(settings.boundingBox[1].x,
settings.boundingBox[1].y) * 0.25f * 6.0f;
bestArea.areaFlags = areaFlags;
bestArea.excludeTravelFlags = excludeTravelFlags;
bestArea.pointAreaFloorDist = 1.0e30f;
bestArea.boundsAreaFloorDist = 1.0e30f;
PointBestReachableAreaNum(treeNum, origin, bestArea);
const float expansion = (std::min)(settings.boundingBox[1].x,
settings.boundingBox[1].y) * 0.25f;
idBounds search;
search[0].Set(origin.x - expansion, origin.y - expansion, origin.z);
search[1].Set(origin.x + expansion, origin.y + expansion, origin.z);
for (int pass = 0; pass < 4; ++pass) {
BoundsBestReachableAreaNum(treeNum, search, origin, bestArea);
if (bestArea.boundsAreaNum != 0
&& bestArea.boundsAreaFloorDist < bestArea.bboxHeight * 2.0f) {
break;
}
search[0] = search[0] - idVec3(expansion, expansion, expansion);
search[1] = search[1] + idVec3(expansion, expansion, expansion);
}
return bestArea.boundsAreaNum != 0
? bestArea.boundsAreaNum : bestArea.pointAreaNum;
}
bool idAAS2File::PushPointIntoAreaNum(const int areaNum, idVec3& point) const {
if (!ValidArea(*this, areaNum)) return false;
const idIndex<int, invalidAASTree_t> treeIndex = GetTreeForArea(areaNum);
const idVec3* floorNormal = GetFloorNormalForTree(treeIndex);
if (floorNormal == nullptr) return false;
const aas2Area_t& area = areas[areaNum];
bool pushed = false;
bool liesOnEdge = false;
idVec3 nearestVertex = point;
float nearestDistance = (std::numeric_limits<float>::max)();
for (int index = 0; index < area.numEdges; ++index) {
const int listIndex = area.firstEdge + index;
if (listIndex < 0 || listIndex >= edgeIndex.Num()) continue;
const int orientedEdge = edgeIndex[listIndex];
if (!ValidEdge(*this, orientedEdge)) continue;
const aas2Edge_t& edge = edges[AbsoluteEdge(orientedEdge)];
if ((edge.flags & AAS_EDGE_VERTICAL) != 0) continue;
const int firstIndex = orientedEdge < 0 ? edge.vertexNum[1] : edge.vertexNum[0];
const int secondIndex = orientedEdge < 0 ? edge.vertexNum[0] : edge.vertexNum[1];
const idVec3& first = vertices[firstIndex];
const idVec3& second = vertices[secondIndex];
const idVec3 edgeDirection = second - first;
idVec3 inward = edgeDirection.Cross(*floorNormal);
if (inward.NormalizeFast() == 0.0f) continue;
const float distance = (point - first).Dot(inward);
if (distance < 0.0f) {
point = point - inward * distance;
pushed = true;
}
const float firstDistance = (point - first).LengthSqr();
if (firstDistance < nearestDistance) {
nearestDistance = firstDistance;
nearestVertex = first;
}
const float along = (point - first).Dot(edgeDirection);
if (std::fabs(distance) < 0.1f && along >= 0.0f
&& along <= edgeDirection.LengthSqr()) liesOnEdge = true;
}
if (pushed && !liesOnEdge) point = nearestVertex;
return pushed;
}
bool idAAS2File::TraceFloor(aas2TraceFloor_t& trace,
const idVec3& start, const int startAreaNum, const idVec3& end,
int endAreaNum, const int travelFlags,
const bool allowFloorNormalChange,
const bool ignoreGravityDirectionDistance,
const bool ignoreSameArea) const {
trace.fraction = 0.0f;
trace.endpos = start;
trace.lastAreaNum = startAreaNum;
trace.firstEdge = { 0, 0, start };
trace.lastEdge = { 0, 0, start };
trace.numAreas = 0;
trace.numReachIndices = 0;
if (!ValidArea(*this, startAreaNum)) return false;
const idVec3* initialFloorNormal = GetFloorNormalForArea(startAreaNum);
if (initialFloorNormal == nullptr) return false;
idVec3 floorNormal = *initialFloorNormal;
idPlane pathPlane;
idPlane nearPlane;
BuildFloorTracePlanes(floorNormal, start, end, pathPlane, nearPlane);
const idPlane endPlane(nearPlane.Normal(), nearPlane.Normal().Dot(end));
if (endAreaNum == 0) endAreaNum = PointAreaNum(0, end);
floorEdgeSplitPoint_t nearest;
floorEdgeSplitPoint_t furthest;
if (!GetFloorEdgeSplitPoints(nearest, furthest, startAreaNum,
pathPlane, nearPlane)) {
nearest = { start, 0.0f, 0 };
furthest = nearest;
}
// The recovered routine uses the file's mutable flood flags as scratch
// storage, then clears them before returning. Keeping the identical
// visited set locally preserves const behavior on the PC port.
std::vector<std::uint8_t> visited(static_cast<std::size_t>(areas.Num()), 0);
std::vector<int> visitedOrder;
visitedOrder.reserve(256);
int currentAreaNum = startAreaNum;
for (;;) {
if (trace.areas != nullptr && trace.numAreas < trace.maxAreas) {
trace.areas[trace.numAreas++] = currentAreaNum;
}
visited[currentAreaNum] = 1;
visitedOrder.push_back(currentAreaNum);
if ((currentAreaNum == endAreaNum
|| endPlane.Distance(furthest.point) > 0.1f)
&& ignoreSameArea) {
trace.endpos = end;
trace.fraction = 1.0f;
break;
}
trace.lastAreaNum = currentAreaNum;
trace.endpos = furthest.point;
trace.lastEdge.edgeNum = furthest.edgeNum;
trace.lastEdge.edgePoint = furthest.point;
nearPlane.SetDist(nearPlane.Normal().Dot(trace.endpos));
const std::size_t visitedBase = visitedOrder.size();
int acceptedReachIndex = -1;
int reachIndex = areas[currentAreaNum].reach.Get();
int guard = 0;
while (reachIndex >= 0 && reachIndex < reachabilities.Num()
&& guard++ < reachabilities.Num()) {
const aas2Reachability_t& reachability = reachabilities[reachIndex];
const int targetAreaNum = reachability.toAreaNum;
const std::uint32_t reachTravelFlags = reachability.travelFlags;
const bool validTravel = (reachTravelFlags & travelFlags) != 0
&& (reachTravelFlags & ~static_cast<std::uint32_t>(travelFlags)) == 0;
const bool validTarget = ValidArea(*this, targetAreaNum)
&& (areas[targetAreaNum].travelFlags & travelFlags) != 0
&& (areas[targetAreaNum].travelFlags
& ~static_cast<std::uint32_t>(travelFlags)) == 0
&& visited[targetAreaNum] == 0;
if (!validTravel || !validTarget) {
reachIndex = reachability.next.Get();
continue;
}
// This append intentionally precedes the geometric tests: the
// original records every otherwise eligible reachability it tries.
if (trace.reachIndices != nullptr
&& trace.numReachIndices < trace.maxReachIndices) {
trace.reachIndices[trace.numReachIndices++] =
idIndex<short, invalidReachability_t>(
static_cast<short>(reachIndex));
}
visited[targetAreaNum] = 1;
visitedOrder.push_back(targetAreaNum);
const idVec3* targetFloorNormal =
GetFloorNormalForArea(targetAreaNum);
if (targetFloorNormal == nullptr) {
reachIndex = reachability.next.Get();
continue;
}
bool accepted = false;
bool changedFloorNormal = false;
idPlane candidatePathPlane = pathPlane;
idPlane candidateNearPlane = nearPlane;
if (allowFloorNormalChange
&& !SameVector(*targetFloorNormal, floorNormal)) {
BuildFloorTracePlanes(*targetFloorNormal, start, end,
candidatePathPlane, candidateNearPlane);
candidateNearPlane.SetDist(
candidateNearPlane.Normal().Dot(trace.endpos));
GetFloorEdgeSplitPoints(nearest, furthest, targetAreaNum,
candidatePathPlane, candidateNearPlane);
accepted = nearest.dist < 1.0e30f && furthest.dist >= 0.1f;
changedFloorNormal = accepted;
} else {
GetFloorEdgeSplitPoints(nearest, furthest, targetAreaNum,
pathPlane, nearPlane);
if (nearest.dist < 1.0e30f && furthest.dist >= -0.1f) {
const bool fly =
(reachTravelFlags & AAS_TFL_FLY) != 0;
idVec3 delta = trace.endpos - nearest.point;
if (fly && std::fabs(furthest.dist)
< std::fabs(nearest.dist)) {
delta = trace.endpos - furthest.point;
}
const float gravityDistance = delta.Dot(floorNormal);
const idVec3 gravityDelta =
floorNormal * gravityDistance;
const idVec3 horizontalDelta = delta - gravityDelta;
accepted = (fly || gravityDelta.LengthSqr()
<= settings.maxStepHeight * settings.maxStepHeight)
&& (fly || horizontalDelta.LengthSqr()
<= 0.040000003f);
}
}
if (accepted) {
acceptedReachIndex = reachIndex;
currentAreaNum = targetAreaNum;
if (changedFloorNormal) {
floorNormal = *targetFloorNormal;
pathPlane = candidatePathPlane;
nearPlane = candidateNearPlane;
}
break;
}
reachIndex = reachability.next.Get();
}
if (acceptedReachIndex < 0) break;
for (std::size_t index = visitedBase; index < visitedOrder.size(); ++index) {
visited[visitedOrder[index]] = 0;
}
visitedOrder.resize(visitedBase);
if (trace.firstEdge.edgeNum == 0) {
trace.firstEdge.toAreaNum = currentAreaNum;
trace.firstEdge.edgeNum = nearest.edgeNum;
trace.firstEdge.edgePoint = nearest.point;
}
}
if (trace.fraction != 1.0f) {
const idVec3 total = end - start;
const float totalDistanceSquared = total.LengthSqr();
if (std::fabs(totalDistanceSquared) <=
(std::numeric_limits<float>::min)()) {
trace.fraction = 0.0f;
} else {
idVec3 traveled = trace.endpos - start;
if (ignoreGravityDirectionDistance) traveled.z = 0.0f;
trace.fraction = std::sqrt(
traveled.LengthSqr() / totalDistanceSquared);
}
}
trace.lastEdge.toAreaNum = currentAreaNum;
return true;
}