Added AAS2File code.

This commit is contained in:
Justin Marshall
2026-08-09 02:48:01 -07:00
parent a7fdbe32ad
commit cdf7d52214
11 changed files with 3560 additions and 190 deletions
@@ -0,0 +1,50 @@
#include "aas2file/aas2debugareamodel.h"
#include "aas2file/aas2debugareamodelgenerator.h"
#include "framework/resourcelist.h"
#include "idlib/filesystem/filesystem.h"
idResourceList idAAS2DebugAreaModel::resourceList("aasDebugAreaModel");
idAAS2DebugAreaModel::idAAS2DebugAreaModel()
: data(), sourceTimestamp(std::uint32_t(-1)) {
}
idAAS2DebugAreaModel::~idAAS2DebugAreaModel() = default;
idResourceList* idAAS2DebugAreaModel::GetResourceList() {
return &resourceList;
}
void idAAS2DebugAreaModel::LoadResource() {
data.Free();
sourceTimestamp = std::uint32_t(-1);
char binaryPath[256] = {};
fileSystem->FixLongFilename("generated",
idAAS2DebugAreaModelData::BINARY_FILE_EXTENSION, GetName(),
binaryPath, sizeof(binaryPath));
idStr error;
const idResource::resourceError_t loadError =
data.LoadBinary(binaryPath, error);
if (loadError != idResource::RESOURCE_ERROR_NONE) {
resourceError = "Could not load generated AAS debug area model";
}
}
bool idAAS2DebugAreaModel::ReloadIfStale() {
if (fileSystem->GetTimestamp(GetName(), false) == sourceTimestamp) {
return false;
}
char binaryPath[256] = {};
fileSystem->FixLongFilename("generated",
idAAS2DebugAreaModelData::BINARY_FILE_EXTENSION, GetName(),
binaryPath, sizeof(binaryPath));
fileSystem->RemoveFile(binaryPath, FSPATH_BASE);
LoadResource();
return true;
}
void idAAS2DebugAreaModel::WriteResourceFile() {
idAAS2DebugAreaModelGenerator::WriteToBinary(GetName(), GetName(),
sourceTimestamp, data);
}
+20 -17
View File
@@ -1,24 +1,27 @@
#pragma once
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
// Original PDB header: w:\tech5\engine\aas2file\aas2debugareamodel.h
// Recovered logical types: 1
// Signatures retain Xbox 360 ABI evidence and may still require manual review.
#include "aas2file/aas2debugareamodeldata.h"
#include "framework/resource.h"
#include <cstdint>
// IDA Local Type ordinal 23519; PDB kind: class.
class idAAS2DebugAreaModel : public idResource
{
class idAAS2DebugAreaModel : public idResource {
public:
// Recovered virtual interface; IDA vtable ordinal 23520.
virtual ~idAAS2DebugAreaModel();
virtual void LoadResource();
virtual bool ReloadIfStale();
virtual void WriteResourceFile();
virtual idResourceList *GetResourceList();
virtual void Print();
virtual void List();
idAAS2DebugAreaModel();
~idAAS2DebugAreaModel() override;
idAAS2DebugAreaModelData data;
unsigned int sourceTimestamp;
void LoadResource() override;
bool ReloadIfStale() override;
void WriteResourceFile() override;
idResourceList* GetResourceList() override;
static idResourceList resourceList;
idAAS2DebugAreaModelData data;
std::uint32_t sourceTimestamp;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idAAS2DebugAreaModel) == 72,
"Recovered idAAS2DebugAreaModel ABI changed");
#endif
@@ -0,0 +1,224 @@
#include "aas2file/aas2debugareamodeldata.h"
#include "framework/resource.h"
#include "idlib/filesystem/file.h"
#include "idlib/filesystem/filesystem.h"
#include <cstring>
namespace {
constexpr std::uint32_t DEBUG_AAS_RESOURCE_ID = 0x41415344u;
#pragma pack(push, 4)
struct DebugResourceHeader {
std::uint16_t headerVersionHi;
std::uint16_t headerVersionLo;
std::uint32_t resourceId;
std::uint16_t versionHi;
std::uint16_t versionLo;
std::uint32_t sourceTimestamp;
std::uint16_t sourceFileNameLen;
std::uint16_t uniqueIdNameLen;
std::uint8_t platform;
std::uint8_t pad[3];
std::uint64_t uniqueId;
std::uint64_t hash;
std::uint64_t dataOffset;
std::uint64_t totalSize;
};
#pragma pack(pop)
static_assert(sizeof(DebugResourceHeader) == 56,
"Recovered debug resource header layout changed");
idAAS2DebugAreaModelData::drawTriangleCallback_t drawTriangleCallback = nullptr;
bool ReadExact(idFile& file, void* data, const unsigned int bytes) {
return bytes == 0 || file.Read(data, bytes) == bytes;
}
std::uint16_t ByteSwap16(const std::uint16_t value) {
return static_cast<std::uint16_t>((value << 8) | (value >> 8));
}
std::uint32_t ByteSwap32(const std::uint32_t value) {
return (value << 24) | ((value << 8) & 0x00FF0000u)
| ((value >> 8) & 0x0000FF00u) | (value >> 24);
}
std::uint64_t ByteSwap64(const std::uint64_t value) {
return (static_cast<std::uint64_t>(ByteSwap32(
static_cast<std::uint32_t>(value))) << 32)
| ByteSwap32(static_cast<std::uint32_t>(value >> 32));
}
void SwapHeader(DebugResourceHeader& header) {
header.headerVersionHi = ByteSwap16(header.headerVersionHi);
header.headerVersionLo = ByteSwap16(header.headerVersionLo);
header.resourceId = ByteSwap32(header.resourceId);
header.versionHi = ByteSwap16(header.versionHi);
header.versionLo = ByteSwap16(header.versionLo);
header.sourceTimestamp = ByteSwap32(header.sourceTimestamp);
header.sourceFileNameLen = ByteSwap16(header.sourceFileNameLen);
header.uniqueIdNameLen = ByteSwap16(header.uniqueIdNameLen);
header.uniqueId = ByteSwap64(header.uniqueId);
header.hash = ByteSwap64(header.hash);
header.dataOffset = ByteSwap64(header.dataOffset);
header.totalSize = ByteSwap64(header.totalSize);
}
void SwapWord(void* const pointer) {
std::uint32_t value = 0;
std::memcpy(&value, pointer, sizeof(value));
value = ByteSwap32(value);
std::memcpy(pointer, &value, sizeof(value));
}
} // namespace
idAAS2DebugAreaModelData::idAAS2DebugAreaModelData()
: vertices(0), areas(0) {
}
idAAS2DebugAreaModelData::~idAAS2DebugAreaModelData() = default;
void idAAS2DebugAreaModelData::SetDrawTriangleCallback(
const drawTriangleCallback_t callback) {
drawTriangleCallback = callback;
}
void idAAS2DebugAreaModelData::Draw(idRenderWorld* const renderWorld,
const idList<int, 5>& visibleAreas) const {
if (renderWorld == nullptr || drawTriangleCallback == nullptr) return;
const float color[4] = { 0.0f, 1.0f, 0.0f, 0.5f };
for (int visibleIndex = 0; visibleIndex < visibleAreas.Num(); ++visibleIndex) {
const int areaNum = visibleAreas[visibleIndex];
if (areaNum < 0 || areaNum >= areas.Num()) continue;
const debugAreaInfo_t& area = areas[areaNum];
for (int triangleIndex = 0; triangleIndex < area.tris.Num(); ++triangleIndex) {
const areaTri_t& triangle = area.tris[triangleIndex];
if (triangle.v0 < 0 || triangle.v0 >= vertices.Num()
|| triangle.v1 < 0 || triangle.v1 >= vertices.Num()
|| triangle.v2 < 0 || triangle.v2 >= vertices.Num()) continue;
drawTriangleCallback(renderWorld, vertices[triangle.v0],
vertices[triangle.v1], vertices[triangle.v2], color);
}
}
}
void idAAS2DebugAreaModelData::BuildResourceNameFromAASName(
const char* const aasName, idStr& resourceName) {
resourceName = aasName;
resourceName.Append(TEXT_FILE_EXTENSION);
}
void idAAS2DebugAreaModelData::Free() {
vertices.Clear();
areas.ClearFree();
}
idResource::resourceError_t idAAS2DebugAreaModelData::LoadBinary(
const char* const binaryFileName,
idStr& errorMessage) {
Free();
idFileLocal file(fileSystem->OpenFileRead(binaryFileName, true, false));
if (file.file == nullptr) {
errorMessage = "file not found";
return idResource::RESOURCE_ERROR_FILE_NOT_FOUND;
}
DebugResourceHeader header{};
if (!ReadExact(*file.file, &header, sizeof(header))) {
errorMessage = "truncated resource header";
return idResource::RESOURCE_ERROR_TRUNCATED;
}
const bool byteSwap = header.headerVersionHi != 1
&& ByteSwap16(header.headerVersionHi) == 1;
if (byteSwap) SwapHeader(header);
if (header.headerVersionHi != 1 || header.headerVersionLo != 1) {
errorMessage = "resource header version mismatch";
return idResource::RESOURCE_ERROR_HEADER_VERSION_MISMATCH;
}
if (header.resourceId != DEBUG_AAS_RESOURCE_ID) {
errorMessage = "resource type mismatch";
return idResource::RESOURCE_ERROR_WRONG_TYPE;
}
if (header.versionHi != BINARY_VERSION_HI
|| header.versionLo != BINARY_VERSION_LO) {
errorMessage = "debug AAS resource version mismatch";
return idResource::RESOURCE_ERROR_RESOURCE_VERSION_MISMATCH;
}
if (header.sourceFileNameLen >= 256 || header.uniqueIdNameLen >= 256) {
errorMessage = "invalid resource name";
return idResource::RESOURCE_ERROR_INVALID_NAME;
}
if (header.dataOffset < sizeof(header)
|| header.totalSize < header.dataOffset
|| header.totalSize > static_cast<std::uint64_t>(file.file->Length())) {
errorMessage = "invalid debug AAS resource offsets";
return idResource::RESOURCE_ERROR_TRUNCATED;
}
if (file.file->Seek(static_cast<std::int64_t>(header.dataOffset),
FS_SEEK_SET) != 0) {
errorMessage = "could not seek debug AAS resource data";
return idResource::RESOURCE_ERROR_TRUNCATED;
}
int vertexCount = 0;
if (!ReadExact(*file.file, &vertexCount, 4)) {
errorMessage = "Bad number of vertices";
return idResource::RESOURCE_ERROR_FATAL;
}
if (byteSwap) SwapWord(&vertexCount);
if (vertexCount <= 0
|| !vertices.SetNum(vertexCount)
|| !ReadExact(*file.file, vertices.Ptr(), vertexCount * 12u)) {
errorMessage = "Bad number of vertices";
Free();
return idResource::RESOURCE_ERROR_FATAL;
}
if (byteSwap) {
for (int vertex = 0; vertex < vertexCount; ++vertex) {
SwapWord(&vertices[vertex].x); SwapWord(&vertices[vertex].y);
SwapWord(&vertices[vertex].z);
}
}
int areaCount = 0;
if (!ReadExact(*file.file, &areaCount, 4)) {
errorMessage = "Bad number of areas";
Free();
return idResource::RESOURCE_ERROR_FATAL;
}
if (byteSwap) SwapWord(&areaCount);
if (areaCount <= 0
|| !areas.SetNum(areaCount)) {
errorMessage = "Bad number of areas";
Free();
return idResource::RESOURCE_ERROR_FATAL;
}
for (int areaNum = 0; areaNum < areaCount; ++areaNum) {
int triangleCount = 0;
if (!ReadExact(*file.file, &triangleCount, 4)) {
errorMessage = "Bad number of area tris";
Free();
return idResource::RESOURCE_ERROR_FATAL;
}
if (byteSwap) SwapWord(&triangleCount);
if (triangleCount < 0
|| !areas[areaNum].tris.SetNum(triangleCount)
|| !ReadExact(*file.file, areas[areaNum].tris.Ptr(),
triangleCount * 12u)) {
errorMessage = "Bad number of area tris";
Free();
return idResource::RESOURCE_ERROR_FATAL;
}
if (byteSwap) {
for (int triangle = 0; triangle < triangleCount; ++triangle) {
SwapWord(&areas[areaNum].tris[triangle].v0);
SwapWord(&areas[areaNum].tris[triangle].v1);
SwapWord(&areas[areaNum].tris[triangle].v2);
}
}
}
errorMessage.Clear();
return idResource::RESOURCE_ERROR_NONE;
}
+40 -30
View File
@@ -1,37 +1,47 @@
#pragma once
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
// Original PDB header: w:\tech5\engine\aas2file\aas2debugareamodeldata.h
// Recovered logical types: 4
// Signatures retain Xbox 360 ABI evidence and may still require manual review.
#include "framework/resource.h"
#include "idlib/containers/list.h"
#include "idlib/math/vector.h"
#include "idlib/text/str.h"
class idRenderWorld;
// IDA Local Type ordinal 23514; PDB kind: struct.
struct idAAS2DebugAreaModelData::areaTri_t
{
int v0;
int v1;
int v2;
};
// IDA Local Type ordinal 23516; PDB kind: struct.
struct idAAS2DebugAreaModelData::debugAreaInfo_t
{
idList<idAAS2DebugAreaModelData::areaTri_t,5> tris;
};
// IDA Local Type ordinal 23518; PDB kind: class.
class idAAS2DebugAreaModelData
{
class idAAS2DebugAreaModelData {
public:
idList<idVec3,5> vertices;
idList<idAAS2DebugAreaModelData::debugAreaInfo_t,5> areas;
struct areaTri_t { int v0; int v1; int v2; };
struct debugAreaInfo_t { idList<areaTri_t, 5> tris; };
idAAS2DebugAreaModelData();
~idAAS2DebugAreaModelData();
using drawTriangleCallback_t = void (*)(idRenderWorld* renderWorld,
const idVec3& v0, const idVec3& v1, const idVec3& v2,
const float color[4]);
static void SetDrawTriangleCallback(drawTriangleCallback_t callback);
void Draw(idRenderWorld* renderWorld,
const idList<int, 5>& visibleAreas) const;
static void BuildResourceNameFromAASName(const char* aasName,
idStr& resourceName);
void Free();
idResource::resourceError_t LoadBinary(const char* binaryFileName,
idStr& errorMessage);
static constexpr int BINARY_VERSION_HI = 1;
static constexpr int BINARY_VERSION_LO = 0;
static constexpr const char* BINARY_FILE_EXTENSION = "baasd";
static constexpr const char* TEXT_FILE_EXTENSION = ".aasd";
idList<idVec3, 5> vertices;
idList<debugAreaInfo_t, 5> areas;
};
// IDA Local Type ordinal 23883; PDB kind: class.
class idAAS2DebugAreaModelData::LoadBinary::__l2::idLocalFileBuffer
{
public:
unsigned int dataLen;
char *data;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idAAS2DebugAreaModelData::areaTri_t) == 12,
"Recovered debug area triangle ABI changed");
static_assert(sizeof(idAAS2DebugAreaModelData::debugAreaInfo_t) == 16,
"Recovered debug area info ABI changed");
static_assert(sizeof(idAAS2DebugAreaModelData) == 32,
"Recovered debug area data ABI changed");
#endif
@@ -0,0 +1,109 @@
#include "aas2file/aas2debugareamodelgenerator.h"
#include "aas2file/aas2debugareamodeldata.h"
#include "idlib/filesystem/file.h"
#include "idlib/filesystem/filesystem.h"
#include "idlib/hashing/murmur.h"
#include <algorithm>
#include <cstring>
namespace {
constexpr std::uint32_t DEBUG_AAS_RESOURCE_ID = 0x41415344u;
#pragma pack(push, 4)
struct DebugResourceHeader {
std::uint16_t headerVersionHi;
std::uint16_t headerVersionLo;
std::uint32_t resourceId;
std::uint16_t versionHi;
std::uint16_t versionLo;
std::uint32_t sourceTimestamp;
std::uint16_t sourceFileNameLen;
std::uint16_t uniqueIdNameLen;
std::uint8_t platform;
std::uint8_t pad[3];
std::uint64_t uniqueId;
std::uint64_t hash;
std::uint64_t dataOffset;
std::uint64_t totalSize;
};
#pragma pack(pop)
bool WriteExact(idFile& file, const void* data, const unsigned int bytes) {
return bytes == 0 || file.Write(data, bytes) == bytes;
}
} // namespace
bool idAAS2DebugAreaModelGenerator::WriteToBinary(
const char* const uniqueIdName, const char* const fileName,
const std::uint32_t sourceTimestamp,
const idAAS2DebugAreaModelData& data) {
char binaryPath[256] = {};
fileSystem->FixLongFilename("generated",
idAAS2DebugAreaModelData::BINARY_FILE_EXTENSION, fileName,
binaryPath, sizeof(binaryPath));
const char* const sourceName = fileName != nullptr ? fileName : "";
const char* const uniqueName = uniqueIdName != nullptr ? uniqueIdName : "";
const std::uint16_t sourceLength = static_cast<std::uint16_t>(
(std::min)(std::size_t(255), std::strlen(sourceName)));
const std::uint16_t uniqueLength = static_cast<std::uint16_t>(
(std::min)(std::size_t(255), std::strlen(uniqueName)));
std::uint64_t payloadSize = 8u + data.vertices.Num() * 12ull;
for (int area = 0; area < data.areas.Num(); ++area) {
payloadSize += 4 + data.areas[area].tris.Num() * 12ull;
}
if (payloadSize > static_cast<std::uint64_t>(UINT_MAX)) return false;
idFile_Memory payload;
const int vertexCount = data.vertices.Num();
const int areaCount = data.areas.Num();
if (!WriteExact(payload, &vertexCount, 4)
|| !WriteExact(payload, data.vertices.Ptr(), vertexCount * 12u)
|| !WriteExact(payload, &areaCount, 4)) return false;
for (int area = 0; area < areaCount; ++area) {
const int triangleCount = data.areas[area].tris.Num();
if (!WriteExact(payload, &triangleCount, 4)
|| !WriteExact(payload, data.areas[area].tris.Ptr(),
triangleCount * 12u)) return false;
}
idFileLocal file(fileSystem->OpenFileWrite(binaryPath, FSPATH_BASE));
if (file.file == nullptr) return false;
DebugResourceHeader header{};
header.headerVersionHi = 1;
header.headerVersionLo = 1;
header.resourceId = DEBUG_AAS_RESOURCE_ID;
header.versionHi = idAAS2DebugAreaModelData::BINARY_VERSION_HI;
header.versionLo = idAAS2DebugAreaModelData::BINARY_VERSION_LO;
header.sourceTimestamp = sourceTimestamp;
header.sourceFileNameLen = sourceLength;
header.uniqueIdNameLen = uniqueLength;
// The recovered writer stores RESOURCEPLATFORM_360. This is the PC
// port's equivalent resource and therefore advertises Windows (0).
header.platform = 0;
header.uniqueId = 0;
header.hash = MurMur64_HashData(payload.GetDataPtr(),
static_cast<int>(payload.Length()), 0);
header.dataOffset = (sizeof(header) + sourceLength + uniqueLength + 15u)
& ~std::uint64_t(15u);
header.totalSize = header.dataOffset + payloadSize;
if (!WriteExact(*file.file, &header, sizeof(header))
|| !WriteExact(*file.file, sourceName, sourceLength)
|| !WriteExact(*file.file, uniqueName, uniqueLength)) return false;
const std::uint8_t zeroBytes[16] = {};
const std::uint64_t writtenHeaderBytes =
sizeof(header) + sourceLength + uniqueLength;
const unsigned int padding = static_cast<unsigned int>(
header.dataOffset - writtenHeaderBytes);
if (!WriteExact(*file.file, zeroBytes, padding)
|| !WriteExact(*file.file, payload.GetDataPtr(),
static_cast<unsigned int>(payload.Length()))) {
return false;
}
return true;
}
@@ -1,13 +1,15 @@
#pragma once
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
// Original PDB header: w:\tech5\engine\aas2file\aas2debugareamodelgenerator.h
// Recovered logical types: 1
// Signatures retain Xbox 360 ABI evidence and may still require manual review.
#include <cstdint>
class idAAS2DebugAreaModelData;
// IDA Local Type ordinal 23855; PDB kind: class.
class idAAS2DebugAreaModelGenerator
{
class idAAS2DebugAreaModelGenerator {
public:
static bool WriteToBinary(const char* uniqueIdName, const char* fileName,
std::uint32_t sourceTimestamp,
const idAAS2DebugAreaModelData& data);
};
static_assert(sizeof(idAAS2DebugAreaModelGenerator) == 1,
"Recovered stateless AAS2 debug generator ABI changed");
File diff suppressed because it is too large Load Diff
+460 -79
View File
@@ -1,94 +1,475 @@
#pragma once
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
// Original PDB header: w:\tech5\engine\aas2file\aas2file.h
// Recovered logical types: 5
// Signatures retain Xbox 360 ABI evidence and may still require manual review.
#include "aas2file/aastraversalchaindata.h"
#include "framework/resource.h"
#include "idlib/bv/bounds.h"
#include "idlib/containers/list.h"
#include "idlib/math/plane.h"
#include "idlib/text/str.h"
#include <cstdint>
// IDA Local Type ordinal 21931; PDB kind: struct.
struct idAAS2File::bspTree_t
{
idVec3 floorNormal;
int headNode;
int firstArea;
int lastArea;
class idFile;
class idLexer;
enum aas2TravelFlag_t : int {
AAS_TFL_INVALID = 0x1,
AAS_TFL_INVALID_TEAM1 = 0x2,
AAS_TFL_INVALID_TEAM2 = 0x4,
AAS_TFL_AIR = 0x8,
AAS_TFL_WATER = 0x10,
AAS_TFL_WALK = 0x20,
AAS_TFL_WALKOFFLEDGE = 0x40,
AAS_TFL_WALKOFFBARRIER = 0x80,
AAS_TFL_BARRIERJUMP = 0x100,
AAS_TFL_JUMP = 0x200,
AAS_TFL_LADDER = 0x400,
AAS_TFL_SWIM = 0x800,
AAS_TFL_TELEPORT = 0x1000,
AAS_TFL_ELEVATOR = 0x2000,
AAS_TFL_CROUCH = 0x4000,
AAS_TFL_LEAP = 0x8000,
AAS_TFL_TRAVERSAL_CLASS_A = 0x10000,
AAS_TFL_TRAVERSAL_CLASS_B = 0x20000,
AAS_TFL_TRAVERSAL_CLASS_C = 0x40000,
AAS_TFL_TRAVERSAL_EVASION = 0x80000,
AAS_TFL_CLIMB = 0x100000,
AAS_TFL_FLY = 0x200000,
AAS_TFL_TRAVERSAL_EMERGENCY = 0x400000,
AAS_TFL_TRAVERSAL_CLASS_D = 0x800000,
AAS_TFL_TRAVERSAL_CLASS_E = 0x1000000,
AAS_TFL_TRAVERSAL_COMBAT = 0x2000000,
AAS_TFL_BLOCK_PLAYER = 0x4000000,
AAS_TFL_MELEE = 0x8000000,
AAS_TFL_MAX = 0x8000001,
AAS_TFL_VALID_TEAM1 = static_cast<int>(0xFFFFFFFCu),
AAS_TFL_VALID_TEAM2 = static_cast<int>(0xFFFFFFFAu),
AAS_TFL_VALID_BOTH_TEAMS = static_cast<int>(0xFFFFFFFEu),
AAS_TFL_VALID_WALK_TEAM1 = 0x3C,
AAS_TFL_VALID_WALK_TEAM2 = 0x3A,
AAS_TFL_VALID_WALK_BOTH_TEAMS = 0x3E,
AAS_TFL_TRAVERSAL_ANY_CLASS = 0x1870000
};
// IDA Local Type ordinal 22000; PDB kind: class.
class idAAS2File : public idResource
{
enum aas2AreaFlag_t : int {
AAS_AREA_LEDGE = 0x1,
AAS_AREA_REACHABLE_WALK = 0x2,
AAS_AREA_OUTSIDE = 0x4,
AAS_AREA_HIGH_CEILING = 0x8,
AAS_AREA_NOPUSH = 0x10,
AAS_AREA_CONTENTS_SOLID = 0x100,
AAS_AREA_CONTENTS_WATER = 0x200,
AAS_AREA_CONTENTS_CLUSTERPORTAL = 0x400,
AAS_AREA_CONTENTS_OBSTACLE = 0x800,
AAS_AREA_FAKE = 0x1000,
AAS_AREA_CONTENTS_FLY = 0x2000,
AAS_AREA_FLOOD_VISITED = 0x8000,
AAS_AREA_MAX = 0x8001
};
enum aas2EdgeFlag_t : int {
AAS_EDGE_WALL = 0x1,
AAS_EDGE_LEDGE = 0x2,
AAS_EDGE_WALL_CORNER = 0x4,
AAS_EDGE_LEDGE_CORNER = 0x8,
AAS_EDGE_STEP_TOP = 0x10,
AAS_EDGE_STEP_BOTTOM = 0x20,
AAS_EDGE_VERTICAL = 0x40,
AAS_EDGE_WATER = 0x80,
AAS_EDGE_LADDER = 0x100,
AAS_EDGE_STEP_SIDE = 0x200,
AAS_EDGE_WALL_CORNER_V0 = 0x400,
AAS_EDGE_WALL_CORNER_V1 = 0x800,
AAS_EDGE_FAKE = 0x1000,
AAS_EDGE_MAX = 0x1001
};
enum aas2CoverFlag_t : int {
AAS_COVERFLAG_CROUCH = 0x1,
AAS_COVERFLAG_LEANLEFT = 0x2,
AAS_COVERFLAG_LEANRIGHT = 0x4,
AAS_COVERFLAG_STEPLEFT = 0x8,
AAS_COVERFLAG_STEPRIGHT = 0x10,
AAS_COVERFLAG_FIREOVER = 0x20,
AAS_COVERFLAG_FIRELEANLEFT = 0x40,
AAS_COVERFLAG_FIRELEANRIGHT = 0x80,
AAS_COVERFLAG_FIRESTEPLEFT = 0x100,
AAS_COVERFLAG_FIRESTEPRIGHT = 0x200,
AAS_COVERFLAG_EXPOSED = 0x400,
AAS_COVERFLAG_EXPLICIT = 0x800,
AAS_COVERFLAG_FORCE = 0x1000,
AAS_COVERFLAG_INVALID = 0x2000,
AAS_COVERFLAG_TRANSITION_APPROACH_FORWARD = 0x4000,
AAS_COVERFLAG_TRANSITION_APPROACH_LEFT = 0x8000,
AAS_COVERFLAG_TRANSITION_APPROACH_TURN_AROUND_LEFT = 0x10000,
AAS_COVERFLAG_TRANSITION_APPROACH_WRAP_AROUND_LEFT = 0x20000,
AAS_COVERFLAG_TRANSITION_APPROACH_RIGHT = 0x40000,
AAS_COVERFLAG_TRANSITION_APPROACH_TURN_AROUND_RIGHT = 0x80000,
AAS_COVERFLAG_TRANSITION_APPROACH_WRAP_AROUND_RIGHT = 0x100000,
AAS_COVERFLAG_TRANSITION_APPROACH_JUMPOVER = 0x200000,
AAS_COVERFLAG_AVOID = 0x400000,
AAS_COVERFLAG_TRANSITION_ALL = 0x3FC000
};
class idAAS2Settings {
public:
// Recovered virtual interface; IDA vtable ordinal 22001.
virtual ~idAAS2File();
virtual void LoadResource();
virtual bool ReloadIfStale();
virtual void WriteResourceFile();
virtual idResourceList *GetResourceList();
virtual void Print();
virtual void List();
enum type_t : int { AAS_PLAYER = 0, AAS_MONSTER = 1, AAS_VEHICLE = 2, AAS_MAX = 3 };
enum aasPrimitiveMode_t : int {
AAS_PRIMITIVE_MODE_DEFAULT = 0,
AAS_PRIMITIVE_MODE_NEVER = 1,
AAS_PRIMITIVE_MODE_ALWAYS = 2
};
unsigned int crc;
unsigned int timestamp;
idList<int,37> visitedAreas;
idAAS2Settings settings;
int major;
int minor;
int firstFakeVertex;
int firstFakeEdge;
int firstFakeEdgeIndex;
int firstFakeArea;
idList<idAAS2File::bspTree_t,37> trees;
idList<idPlane,37> planes;
idList<idVec3,37> vertices;
idList<aas2Edge_t,37> edges;
idList<int,37> edgeIndex;
idList<aas2Reachability_t,37> reachabilities;
idList<aas2Area_t,37> areas;
idList<aas2Node_t,37> nodes;
idList<aas2Portal_t,37> portals;
idList<int,37> portalIndex;
idList<aas2Cluster_t,37> clusters;
idList<unsigned char,37> obstaclePVS;
idList<aas2Name_t,37> reachabilityNames;
idList<aas2AnimName_t,37> animNames;
idList<aas2DependencyName_t,37> dependencyNames;
idList<aas2InteractionEntityName_t,37> interactionEntityNames;
idList<aas2TraversalEntityName_t,37> traversalEntityNames;
idList<aas2Cover_t,37> cover;
idList<int,37> areaCoverIndex;
idList<int,37> touchingCoverIndex;
idList<aas2ChokePoint_t,37> chokePoints;
idList<aas2Traversal_t,37> traversalPoints;
idList<aas2HintNode_t,37> hintNodes;
idList<aas2AreaBounds_t,37> areaBounds;
idAAS2Settings();
bool ParseInt(idLexer& source, int& value);
bool ParseFloat(idLexer& source, float& value);
bool ParseVector(idLexer& source, idVec3& vector);
bool ParseBounds(idLexer& source, idBounds& bounds);
bool ValidForBounds(const idBounds& bounds) const;
bool ReadFromFile(idLexer& source);
bool WriteToFileBinary(idFile& file) const;
bool ReadFromFileBinary(idFile& file);
type_t type;
idStr fileExtensionAAS;
idStr groupName;
idStr explicitGroupName;
idBounds boundingBox;
int primitiveModeBrush;
int primitiveModePatch;
int primitiveModeModel;
idVec3 gravityDir;
float gravityValue;
float maxStepHeight;
float maxBarrierHeight;
float maxWaterJumpHeight;
float maxFallHeight;
float minFloorCos;
float minHighCeiling;
float groundSpeed;
float waterSpeed;
float ladderSpeed;
float wallCornerEdgeRadius;
float ledgeCornerEdgeRadius;
float obstaclePVSRadius;
float wallCornerReachabilityBackoff;
float highQualityReachabilityBackoff;
float minCrouchingCoverHeight;
float minStandingCoverHeight;
float crouchingFireHeight;
float standingFireHeight;
float minWallWidth;
float maxWallWidth;
float minDoorWidth;
float maxDoorWidth;
float coverCornerDistance;
float coverWallDistance;
float chokePointWidth;
int tt_barrierJump;
int tt_waterJump;
int tt_startWalkOffLedge;
int tt_startLadderClimb;
};
// IDA Local Type ordinal 22021; PDB kind: struct.
struct idAAS2File::bestReachableArea_t
{
float bboxHeight;
float maxEdgeDist;
int areaFlags;
int excludeTravelFlags;
int pointAreaNum;
float pointAreaFloorDist;
int boundsAreaNum;
float boundsAreaFloorDist;
struct aas2Edge_t { int vertexNum[2]; int flags; };
struct aas2Node_t { std::uint32_t planeNum; std::uint32_t flags; int children[2]; };
struct aas2Portal_t {
std::uint16_t areaNum;
std::int16_t clusters[2];
std::uint16_t clusterAreaNum[2];
std::uint16_t maxAreaTravelTime;
};
struct aas2Cluster_t { int numAreas; int numReachableAreas; int numPortals; int firstPortal; };
struct aas2Name_t { char name[128]; int index; };
struct aas2AnimName_t { char name[128]; };
struct aas2DependencyName_t { char name[128]; };
struct aas2InteractionEntityName_t { char name[128]; };
struct aas2TraversalEntityName_t { char name[128]; };
struct aas2AreaBounds_t { std::int16_t min[3]; std::int16_t max[3]; };
struct aas2Reachability_t {
std::uint32_t travelFlags;
std::uint16_t travelTime;
std::uint16_t fromAreaNum;
std::uint16_t toAreaNum;
std::uint16_t padding;
std::int16_t start[3];
std::int16_t end[3];
std::uint32_t areaTTOfsAndNumber;
idIndex<short, invalidReachability_t> next;
idIndex<short, invalidReachability_t> rev_next;
idVec3 Start() const { return idVec3(float(start[0]), float(start[1]), float(start[2])); }
idVec3 End() const { return idVec3(float(end[0]), float(end[1]), float(end[2])); }
};
// IDA Local Type ordinal 22022; PDB kind: struct.
struct idAAS2File::floorEdgeSplitPoint_t
{
idVec3 point;
float dist;
int edgeNum;
struct aas2Area_t {
std::uint32_t travelFlags;
std::uint16_t flags;
std::int16_t numEdges;
int firstEdge;
std::int16_t cluster;
std::uint16_t clusterAreaNum;
std::uint32_t obstaclePVSOffset;
idIndex<short, invalidReachability_t> reach;
idIndex<short, invalidReachability_t> rev_reach;
std::uint16_t firstChokePoint;
std::int16_t numChokePoints;
std::uint16_t firstCover;
std::uint16_t numCover;
std::uint16_t firstTraversal;
std::uint16_t numTraversals;
std::uint16_t firstHintNode;
std::uint16_t numHintNodes;
aas2Area_t();
};
// IDA Local Type ordinal 23832; PDB kind: struct.
struct idAAS2File::GetObstaclePVSWallEdges::__l25::wallEdge_t
{
int edgeNum;
int verts[2];
idAAS2File::GetObstaclePVSWallEdges::__l25::wallEdge_t *next;
struct aas2Cover_t {
idVec3 origin;
idVec3 dir;
std::int16_t areaNum;
std::int16_t flags;
int numTouching;
int firstTouching;
float durationSec;
float minRange;
float maxRange;
int reservedBy;
int usableTime;
aas2Cover_t();
};
struct aas2ChokePoint_t { idVec3 points[2]; std::uint16_t rooms[2]; };
struct aas2HintNode_t {
enum hintNodeType_t : int {
HINT_NODE_TYPE_SEARCH_AUTO = 0,
HINT_NODE_TYPE_SEARCH_ANIMATION = 1,
HINT_NODE_TYPE_GRENADE = 2
};
idVec3 origin;
std::int16_t areaNum;
std::int16_t radius;
std::uint8_t hintType;
std::uint8_t orientation;
std::uint8_t dirFlags;
std::uint8_t grouping;
int hintData;
aas2HintNode_t();
void GetOrientationVector(idVec3& direction) const;
};
struct aas2Trace_t {
int flags;
int travelFlags;
int maxAreas;
int getOutOfSolid;
float fraction;
idVec3 endpos;
int planeNum;
int lastAreaNum;
int blockingAreaNum;
int numAreas;
int* areas;
idVec3* points;
};
struct aas2TraceHeight_t { int maxPoints; int numPoints; idVec3* points; };
struct aas2EdgeCrossed_t { int toAreaNum; int edgeNum; idVec3 edgePoint; };
struct aas2TraceFloor_t {
float fraction;
idVec3 endpos;
int lastAreaNum;
aas2EdgeCrossed_t firstEdge;
aas2EdgeCrossed_t lastEdge;
int maxAreas;
int numAreas;
int maxReachIndices;
int numReachIndices;
int* areas;
idIndex<short, invalidReachability_t>* reachIndices;
};
class idAAS2File : public idResource {
public:
struct bspTree_t { idVec3 floorNormal; int headNode; int firstArea; int lastArea; };
struct bestReachableArea_t {
float bboxHeight;
float maxEdgeDist;
int areaFlags;
int excludeTravelFlags;
int pointAreaNum;
float pointAreaFloorDist;
int boundsAreaNum;
float boundsAreaFloorDist;
};
struct floorEdgeSplitPoint_t { idVec3 point; float dist; int edgeNum; };
idAAS2File();
~idAAS2File() override;
void LoadResource() override;
bool ReloadIfStale() override;
idResourceList* GetResourceList() override;
void Clear();
void MakeDefault();
unsigned int MemorySize() const;
bool LoadBinary(const char* fileName, const char* binaryFileName,
std::uint32_t mapFileCRC, std::uint32_t sourceTimestamp);
bool WriteBinary(const char* fileName, const char* binaryFileName,
std::uint32_t mapFileCRC, std::uint32_t sourceTimestamp);
bool GetAASAnim(idIndex<short, invalidAASAnimIndex_t> index,
const aas2AnimName_t** value) const;
idIndex<short, invalidAASAnimIndex_t> GetAASAnimIndexByName(const char* name) const;
bool GetAASDependency(idIndex<short, invalidAASDependencyIndex_t> index,
const aas2DependencyName_t** value) const;
idIndex<short, invalidAASDependencyIndex_t> GetAASDependencyIndexByName(const char* name) const;
bool GetAASInteractionEntity(idIndex<short, invalidAASInteractionEntIndex_t> index,
const aas2InteractionEntityName_t** value) const;
idIndex<short, invalidAASInteractionEntIndex_t> GetAASInteractionEntityIndexByName(const char* name) const;
bool GetAASTraversalNameIndex(idIndex<short, invalidAASTraversalNameIndex_t> index,
const aas2TraversalEntityName_t** value) const;
idIndex<short, invalidAASTraversalNameIndex_t> GetAASTraversalNameIndexByName(const char* name) const;
int GetAASTraversalIndexByNameIndex(idIndex<short, invalidAASTraversalNameIndex_t> index) const;
bool SetTraversalFlag(int index, int flags);
bool ClearTraversalFlag(int index, int flags);
bool GetAASTraversalAreas(int index, int& startArea, int& goalArea) const;
idIndex<short, invalidReachability_t> FindReachabilityByName(const char* name) const;
int GetTraversalsForReachability(idIndex<short, invalidReachability_t> reachIndex,
idList<int, 5>& traversals) const;
int GetTraversalsForInteractableEntity(idIndex<short, invalidAASInteractionEntIndex_t> index,
idList<int, 5>& traversals) const;
void FlagNoPushAreas();
void ResetCover();
idIndex<int, invalidAASTree_t> GetTreeForFloorNormal(const idVec3& normal) const;
idIndex<int, invalidAASTree_t> GetTreeForArea(int areaNum) const;
int GetNumAreasInTree(idIndex<int, invalidAASTree_t> treeNum) const;
const idVec3* GetFloorNormalForArea(int areaNum) const;
const idVec3* GetFloorNormalForTree(idIndex<int, invalidAASTree_t> treeNum) const;
idVec3 AreaCenter(int areaNum) const;
idBounds EdgeBounds(int edgeNum) const;
idBounds AreaBounds(int areaNum) const;
int PointAreaNum(int tree, const idVec3& origin) const;
int BoundsAreaNums(int tree, const idBounds& bounds, int* outputAreas,
int maxAreas) const;
void FlagBoundsAreas(int tree, const idBounds& bounds,
bool* areasTouched) const;
int BoundsReachableAreaNum(int tree, const idBounds& bounds,
std::uint16_t areaFlags, int excludeTravelFlags) const;
bool TraceHeight(int tree, aas2TraceHeight_t& trace,
const idVec3& start, const idVec3& end) const;
bool GetFloorEdgeSplitPoints(floorEdgeSplitPoint_t& nearest,
floorEdgeSplitPoint_t& furthest, int areaNum,
const idPlane& pathPlane, const idPlane& nearPlane) const;
int MaxTreeDepth(int tree) const;
bool Trace(int tree, aas2Trace_t& trace, const idVec3& start,
const idVec3& end) const;
int GetObstaclePVSWallEdges(int areaNum, int edgeFlags, int* outputEdges,
int maxEdges) const;
void ClipGridToAreas(const idVec3& startOrigin, int startAreaNum,
int travelFlags, unsigned int cellSize, int dimension,
std::uint8_t* reachable) const;
float GetFloorDistance(int areaNum, const idPlane& floorPlane,
const idVec3& point, float bboxHeight, float maxEdgeDist) const;
int PointReachableAreaNum(idIndex<int, invalidAASTree_t> tree,
const idVec3& origin, int areaFlags, int excludeTravelFlags) const;
bool PushPointIntoAreaNum(int areaNum, idVec3& point) const;
bool TraceFloor(aas2TraceFloor_t& trace, const idVec3& start,
int startAreaNum, const idVec3& end, int endAreaNum, int travelFlags,
bool allowFloorNormalChange, bool ignoreGravityDirectionDistance,
bool ignoreSameArea) const;
void FloodAddVisitedArea(int areaNum);
void FloodClearVisitedAreas(int offset);
static idResourceList resourceList;
std::uint32_t crc;
std::uint32_t timestamp;
idList<int, 37> visitedAreas;
idAAS2Settings settings;
int major;
int minor;
int firstFakeVertex;
int firstFakeEdge;
int firstFakeEdgeIndex;
int firstFakeArea;
idList<bspTree_t, 37> trees;
idList<idPlane, 37> planes;
idList<idVec3, 37> vertices;
idList<aas2Edge_t, 37> edges;
idList<int, 37> edgeIndex;
idList<aas2Reachability_t, 37> reachabilities;
idList<aas2Area_t, 37> areas;
idList<aas2Node_t, 37> nodes;
idList<aas2Portal_t, 37> portals;
idList<int, 37> portalIndex;
idList<aas2Cluster_t, 37> clusters;
idList<std::uint8_t, 37> obstaclePVS;
idList<aas2Name_t, 37> reachabilityNames;
idList<aas2AnimName_t, 37> animNames;
idList<aas2DependencyName_t, 37> dependencyNames;
idList<aas2InteractionEntityName_t, 37> interactionEntityNames;
idList<aas2TraversalEntityName_t, 37> traversalEntityNames;
idList<aas2Cover_t, 37> cover;
idList<int, 37> areaCoverIndex;
idList<int, 37> touchingCoverIndex;
idList<aas2ChokePoint_t, 37> chokePoints;
idList<aas2Traversal_t, 37> traversalPoints;
idList<aas2HintNode_t, 37> hintNodes;
idList<aas2AreaBounds_t, 37> areaBounds;
private:
bool LoadText(const char* fileName, std::uint32_t sourceTimestamp);
bool ParseIndex(idLexer& source, idList<int, 37>& indexes);
bool ParseNames(idLexer& source, idList<aas2Name_t, 37>& names);
template<typename nameType>
bool ParseInteractionEntityNames(idLexer& source,
idList<nameType, 37>& names);
bool ParseVertices(idLexer& source);
bool ParseEdges(idLexer& source);
bool ParseNodes(idLexer& source);
bool ParsePortals(idLexer& source);
bool ParseClusters(idLexer& source);
bool ParseObstaclePVS(idLexer& source);
bool ParsePlanes(idLexer& source);
bool ParseReachabilities(idLexer& source);
bool ParseAreas(idLexer& source);
bool ParseCover(idLexer& source);
bool ParseTraversalPoints(idLexer& source);
bool ParseHintNodes(idLexer& source);
bool ParseTrees(idLexer& source);
void CalculateAreaBounds();
void BuildReachabilityChains();
void PointBestReachableAreaNum(int tree, const idVec3& origin,
bestReachableArea_t& bestArea) const;
void BoundsBestReachableAreaNum(int tree, const idBounds& bounds,
const idVec3& origin, bestReachableArea_t& bestArea) const;
void MaxTreeDepth_r(int nodeNum, int depth, int& maxDepth) const;
};
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(idAAS2Settings) == 268,
"Recovered idAAS2Settings ABI changed");
static_assert(sizeof(aas2Edge_t) == 12, "Recovered aas2Edge_t ABI changed");
static_assert(sizeof(aas2Node_t) == 16, "Recovered aas2Node_t ABI changed");
static_assert(sizeof(aas2Portal_t) == 12, "Recovered aas2Portal_t ABI changed");
static_assert(sizeof(aas2Reachability_t) == 32,
"Recovered aas2Reachability_t ABI changed");
static_assert(sizeof(aas2Area_t) == 40, "Recovered aas2Area_t ABI changed");
static_assert(sizeof(aas2Cover_t) == 56, "Recovered aas2Cover_t ABI changed");
static_assert(sizeof(aas2ChokePoint_t) == 28,
"Recovered aas2ChokePoint_t ABI changed");
static_assert(sizeof(aas2HintNode_t) == 24,
"Recovered aas2HintNode_t ABI changed");
static_assert(sizeof(aas2Trace_t) == 56, "Recovered aas2Trace_t ABI changed");
static_assert(sizeof(aas2TraceFloor_t) == 84,
"Recovered aas2TraceFloor_t ABI changed");
static_assert(sizeof(idAAS2File) == 736, "Recovered idAAS2File ABI changed");
#endif
+971
View File
@@ -0,0 +1,971 @@
#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;
}
+485
View File
@@ -0,0 +1,485 @@
#include "aas2file/aas2file.h"
#include "idlib/text/lexer.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
namespace {
bool BeginArray(idLexer& source, int& count) {
count = source.ParseInt();
return count >= 0 && source.ExpectTokenString("{");
}
bool BeginRecord(idLexer& source) {
source.ParseInt(); // the serialized record index is informational
return source.ExpectTokenString("(");
}
std::int16_t ClampShort(const float value) {
const int integer = static_cast<int>(value);
return static_cast<std::int16_t>((std::max)(-32768,
(std::min)(32767, integer)));
}
} // namespace
bool idAAS2File::ParseIndex(idLexer& source, idList<int, 37>& indexes) {
int count = 0;
if (!BeginArray(source, count)) return false;
indexes.Clear();
if (!indexes.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
source.ParseInt();
if (!source.ExpectTokenString("(")) return false;
const int value = source.ParseInt();
if (!source.ExpectTokenString(")")) return false;
indexes.Append(value);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseNames(idLexer& source,
idList<aas2Name_t, 37>& names) {
int count = 0;
if (!BeginArray(source, count)) return false;
names.Clear();
if (!names.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
idToken token;
aas2Name_t entry{};
if (!source.ReadToken(token)) return false;
strncpy_s(entry.name, token.c_str(), _TRUNCATE);
entry.index = source.ParseInt();
names.Append(entry);
}
return source.ExpectTokenString("}");
}
template<typename nameType>
bool idAAS2File::ParseInteractionEntityNames(idLexer& source,
idList<nameType, 37>& names) {
int count = 0;
if (!BeginArray(source, count)) return false;
names.Clear();
if (!names.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
idToken token;
nameType entry{};
if (!source.ReadToken(token)) return false;
strncpy_s(entry.name, token.c_str(), _TRUNCATE);
names.Append(entry);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseVertices(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
vertices.Clear();
if (!vertices.PreAllocate(count + 1)) return false;
for (int index = 0; index < count; ++index) {
source.ParseInt();
idVec3 vertex;
if (!source.Parse1DMatrix(3, &vertex.x, false)) return false;
vertices.Append(vertex);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseEdges(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
edges.Clear();
if (!edges.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
aas2Edge_t edge{};
if (!BeginRecord(source)) return false;
edge.vertexNum[0] = source.ParseInt();
edge.vertexNum[1] = source.ParseInt();
edge.flags = source.ParseInt();
if (!source.ExpectTokenString(")")) return false;
edges.Append(edge);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseNodes(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
nodes.Clear();
if (!nodes.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
aas2Node_t node{};
if (!BeginRecord(source)) return false;
node.planeNum = source.ParseUnsignedInt();
node.flags = source.ParseUnsignedInt();
node.children[0] = source.ParseInt();
node.children[1] = source.ParseInt();
if (!source.ExpectTokenString(")")) return false;
nodes.Append(node);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParsePortals(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
portals.Clear();
if (!portals.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
aas2Portal_t portal{};
if (!BeginRecord(source)) return false;
portal.areaNum = static_cast<std::uint16_t>(source.ParseInt());
portal.clusters[0] = static_cast<std::int16_t>(source.ParseInt());
portal.clusters[1] = static_cast<std::int16_t>(source.ParseInt());
portal.clusterAreaNum[0] = static_cast<std::uint16_t>(source.ParseInt());
portal.clusterAreaNum[1] = static_cast<std::uint16_t>(source.ParseInt());
portal.maxAreaTravelTime = 0;
if (!source.ExpectTokenString(")")) return false;
portals.Append(portal);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseClusters(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
clusters.Clear();
if (!clusters.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
aas2Cluster_t cluster{};
if (!BeginRecord(source)) return false;
cluster.numAreas = source.ParseInt();
cluster.numReachableAreas = source.ParseInt();
cluster.firstPortal = source.ParseInt();
cluster.numPortals = source.ParseInt();
if (!source.ExpectTokenString(")")) return false;
clusters.Append(cluster);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseObstaclePVS(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
obstaclePVS.Clear();
if (!obstaclePVS.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
if (!BeginRecord(source)) return false;
const auto value = static_cast<std::uint8_t>(source.ParseInt());
if (!source.ExpectTokenString(")")) return false;
obstaclePVS.Append(value);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParsePlanes(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
planes.Clear();
if (!planes.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
source.ParseInt();
float values[4] = {};
if (!source.Parse1DMatrix(4, values, false)) return false;
planes.Append(idPlane(values[0], values[1], values[2], -values[3]));
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseReachabilities(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
reachabilities.Clear();
if (!reachabilities.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
aas2Reachability_t reachability{};
reachability.next.Invalidate();
reachability.rev_next.Invalidate();
if (!BeginRecord(source)) return false;
reachability.travelFlags = source.ParseUnsignedInt();
reachability.travelTime = static_cast<std::uint16_t>(source.ParseInt());
reachability.fromAreaNum = static_cast<std::uint16_t>(source.ParseInt());
reachability.toAreaNum = static_cast<std::uint16_t>(source.ParseInt());
if (!source.ExpectTokenString("(")) return false;
for (int axis = 0; axis < 3; ++axis) {
reachability.start[axis] = static_cast<std::int16_t>(source.ParseInt());
}
if (!source.ExpectTokenString(")")
|| !source.ExpectTokenString("(")) return false;
for (int axis = 0; axis < 3; ++axis) {
reachability.end[axis] = static_cast<std::int16_t>(source.ParseInt());
}
if (!source.ExpectTokenString(")")
|| !source.ExpectTokenString(")")) return false;
reachabilities.Append(reachability);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseAreas(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
areas.Clear();
if (!areas.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
aas2Area_t area;
area.reach.Invalidate();
area.rev_reach.Invalidate();
area.firstChokePoint = 0;
area.numChokePoints = 0;
if (!BeginRecord(source)) return false;
area.flags = static_cast<std::uint16_t>(source.ParseInt());
area.travelFlags = source.ParseUnsignedInt();
area.numEdges = static_cast<std::int16_t>(source.ParseInt());
area.firstEdge = source.ParseInt();
area.cluster = static_cast<std::int16_t>(source.ParseInt());
area.clusterAreaNum = static_cast<std::uint16_t>(source.ParseInt());
area.obstaclePVSOffset = source.ParseUnsignedInt();
area.firstCover = static_cast<std::uint16_t>(source.ParseInt());
area.numCover = static_cast<std::uint16_t>(source.ParseInt());
area.firstTraversal = static_cast<std::uint16_t>(source.ParseInt());
area.numTraversals = static_cast<std::uint16_t>(source.ParseInt());
area.firstHintNode = static_cast<std::uint16_t>(source.ParseInt());
area.numHintNodes = static_cast<std::uint16_t>(source.ParseInt());
if (!source.ExpectTokenString(")")) return false;
areas.Append(area);
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseCover(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
cover.Clear();
if (!cover.SetNum(count)) return false;
for (int index = 0; index < count; ++index) {
if (!BeginRecord(source)) return false;
aas2Cover_t& entry = cover[index];
const float originX = source.ParseFloat();
const float originY = source.ParseFloat();
const float originZ = source.ParseFloat();
entry.origin.Set(originX, originY, originZ);
const float dirX = source.ParseFloat();
const float dirY = source.ParseFloat();
const float dirZ = source.ParseFloat();
entry.dir.Set(dirX, dirY, dirZ);
entry.areaNum = static_cast<std::int16_t>(source.ParseInt());
entry.flags = static_cast<std::int16_t>(source.ParseInt());
entry.firstTouching = source.ParseInt();
entry.numTouching = source.ParseInt();
if (source.CheckTokenString(")")) continue;
entry.durationSec = source.ParseFloat();
entry.minRange = source.ParseFloat();
entry.maxRange = source.ParseFloat();
if (!source.ExpectTokenString(")")) return false;
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseTraversalPoints(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
traversalPoints.Clear();
if (!traversalPoints.SetNum(count)) return false;
for (int index = 0; index < count; ++index) {
if (!BeginRecord(source)) return false;
aas2Traversal_t& traversal = traversalPoints[index];
traversal.Clear();
const float startX = source.ParseFloat();
const float startY = source.ParseFloat();
const float startZ = source.ParseFloat();
traversal.startPoint.Set(startX, startY, startZ);
const float endX = source.ParseFloat();
const float endY = source.ParseFloat();
const float endZ = source.ParseFloat();
traversal.endPoint.Set(endX, endY, endZ);
traversal.orientationFwd.x = static_cast<std::int16_t>(source.ParseInt());
traversal.orientationFwd.y = static_cast<std::int16_t>(source.ParseInt());
traversal.orientationFwd.z = static_cast<std::int16_t>(source.ParseInt());
traversal.extrusionFwd.x = static_cast<std::int16_t>(source.ParseInt());
traversal.extrusionFwd.y = static_cast<std::int16_t>(source.ParseInt());
traversal.extrusionFwd.z = static_cast<std::int16_t>(source.ParseInt());
traversal.extrusionDistance = static_cast<std::int16_t>(source.ParseInt());
traversal.startAreaNum = static_cast<std::int16_t>(source.ParseInt());
traversal.endAreaNum = static_cast<std::int16_t>(source.ParseInt());
traversal.animIndex = idIndex<short, invalidAASAnimIndex_t>(
static_cast<short>(source.ParseInt()));
traversal.reachabilityIndex = idIndex<short, invalidReachability_t>(
static_cast<short>(source.ParseInt()));
traversal.flags = source.ParseUnsignedInt();
traversal.dependencyIndex = idIndex<short, invalidAASDependencyIndex_t>(
static_cast<short>(source.ParseInt()));
traversal.interactionEntIndex = idIndex<short, invalidAASInteractionEntIndex_t>(
static_cast<short>(source.ParseInt()));
traversal.traversalNameIndex = idIndex<short, invalidAASTraversalNameIndex_t>(
static_cast<short>(source.ParseInt()));
if (!source.ExpectTokenString(")")) return false;
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseHintNodes(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
hintNodes.Clear();
if (!hintNodes.SetNum(count)) return false;
for (int index = 0; index < count; ++index) {
if (!BeginRecord(source)) return false;
aas2HintNode_t& hint = hintNodes[index];
const float originX = source.ParseFloat();
const float originY = source.ParseFloat();
const float originZ = source.ParseFloat();
hint.origin.Set(originX, originY, originZ);
hint.areaNum = static_cast<std::int16_t>(source.ParseInt());
hint.radius = static_cast<std::int16_t>(source.ParseInt());
hint.hintType = static_cast<std::uint8_t>(source.ParseInt());
hint.orientation = static_cast<std::uint8_t>(source.ParseInt());
hint.dirFlags = static_cast<std::uint8_t>(source.ParseInt());
hint.grouping = static_cast<std::uint8_t>(source.ParseInt());
hint.hintData = source.ParseInt();
if (!source.ExpectTokenString(")")) return false;
}
return source.ExpectTokenString("}");
}
bool idAAS2File::ParseTrees(idLexer& source) {
int count = 0;
if (!BeginArray(source, count)) return false;
trees.Clear();
if (!trees.PreAllocate(count)) return false;
for (int index = 0; index < count; ++index) {
bspTree_t tree{};
source.ParseInt();
if (!source.ExpectTokenString("(")) return false;
const float normalX = source.ParseFloat();
const float normalY = source.ParseFloat();
const float normalZ = source.ParseFloat();
tree.floorNormal.Set(normalX, normalY, normalZ);
if (!source.ExpectTokenString(")")) return false;
tree.headNode = source.ParseInt();
tree.firstArea = source.ParseInt();
tree.lastArea = source.ParseInt();
trees.Append(tree);
}
return source.ExpectTokenString("}");
}
void idAAS2File::BuildReachabilityChains() {
for (int areaNum = 0; areaNum < areas.Num(); ++areaNum) {
areas[areaNum].reach.Invalidate();
areas[areaNum].rev_reach.Invalidate();
}
for (int index = 0; index < reachabilities.Num(); ++index) {
aas2Reachability_t& reachability = reachabilities[index];
if (reachability.fromAreaNum < areas.Num()) {
reachability.next = areas[reachability.fromAreaNum].reach;
areas[reachability.fromAreaNum].reach =
idIndex<short, invalidReachability_t>(static_cast<short>(index));
}
if (reachability.toAreaNum < areas.Num()) {
reachability.rev_next = areas[reachability.toAreaNum].rev_reach;
areas[reachability.toAreaNum].rev_reach =
idIndex<short, invalidReachability_t>(static_cast<short>(index));
}
}
}
void idAAS2File::CalculateAreaBounds() {
areaBounds.SetNum(areas.Num());
for (int areaNum = 0; areaNum < areas.Num(); ++areaNum) {
const idBounds bounds = AreaBounds(areaNum);
for (int axis = 0; axis < 3; ++axis) {
areaBounds[areaNum].min[axis] = ClampShort(std::floor(bounds[0][axis]));
areaBounds[areaNum].max[axis] = ClampShort(std::ceil(bounds[1][axis]));
}
}
}
bool idAAS2File::LoadText(const char* const fileName,
const std::uint32_t sourceTimestamp) {
idLexer source(564);
if (!source.LoadFile(fileName, false)) return false;
idToken identifier;
idToken version;
if (!source.ReadToken(identifier) || !source.ReadToken(version)) return false;
if (std::strcmp(identifier.c_str(), "DewmAAS") != 0
&& std::strcmp(identifier.c_str(), "DewmAAS2") != 0
&& std::strcmp(identifier.c_str(), "AAS2") != 0) {
return false;
}
int parsedMajor = 0;
int parsedMinor = 0;
if (std::sscanf(version.c_str(), "%d.%d", &parsedMajor, &parsedMinor) != 2
|| parsedMajor != 3 || parsedMinor != 13) {
return false;
}
const std::uint32_t parsedCRC = source.ParseUnsignedInt();
if (!source.ExpectTokenString("firstFakeVertex")) return false;
const int parsedFirstFakeVertex = source.ParseInt();
if (!source.ExpectTokenString("firstFakeEdge")) return false;
const int parsedFirstFakeEdge = source.ParseInt();
if (!source.ExpectTokenString("firstFakeEdgeIndex")) return false;
const int parsedFirstFakeEdgeIndex = source.ParseInt();
if (!source.ExpectTokenString("firstFakeArea")) return false;
const int parsedFirstFakeArea = source.ParseInt();
Clear();
major = parsedMajor;
minor = parsedMinor;
crc = parsedCRC;
timestamp = sourceTimestamp;
firstFakeVertex = parsedFirstFakeVertex;
firstFakeEdge = parsedFirstFakeEdge;
firstFakeEdgeIndex = parsedFirstFakeEdgeIndex;
firstFakeArea = parsedFirstFakeArea;
idToken section;
while (source.ReadToken(section)) {
const char* const name = section.c_str();
bool ok = false;
if (std::strcmp(name, "settings") == 0) ok = settings.ReadFromFile(source);
else if (std::strcmp(name, "planes") == 0) ok = ParsePlanes(source);
else if (std::strcmp(name, "vertices") == 0) ok = ParseVertices(source);
else if (std::strcmp(name, "edges") == 0) ok = ParseEdges(source);
else if (std::strcmp(name, "edgeIndex") == 0) ok = ParseIndex(source, edgeIndex);
else if (std::strcmp(name, "reachabilities") == 0) ok = ParseReachabilities(source);
else if (std::strcmp(name, "areas") == 0) ok = ParseAreas(source);
else if (std::strcmp(name, "nodes") == 0) ok = ParseNodes(source);
else if (std::strcmp(name, "portals") == 0) ok = ParsePortals(source);
else if (std::strcmp(name, "portalIndex") == 0) ok = ParseIndex(source, portalIndex);
else if (std::strcmp(name, "clusters") == 0) ok = ParseClusters(source);
else if (std::strcmp(name, "obstaclePVS") == 0) ok = ParseObstaclePVS(source);
else if (std::strcmp(name, "reachNames") == 0) ok = ParseNames(source, reachabilityNames);
else if (std::strcmp(name, "traversalAnimNames") == 0
|| std::strcmp(name, "animNames") == 0) ok = ParseInteractionEntityNames(source, animNames);
else if (std::strcmp(name, "dependencyNames") == 0) ok = ParseInteractionEntityNames(source, dependencyNames);
else if (std::strcmp(name, "interactionEntityNames") == 0) ok = ParseInteractionEntityNames(source, interactionEntityNames);
else if (std::strcmp(name, "traversalEntityNames") == 0) ok = ParseInteractionEntityNames(source, traversalEntityNames);
else if (std::strcmp(name, "cover") == 0) ok = ParseCover(source);
else if (std::strcmp(name, "areaCoverIndex") == 0) ok = ParseIndex(source, areaCoverIndex);
else if (std::strcmp(name, "touchingCoverIndex") == 0) ok = ParseIndex(source, touchingCoverIndex);
else if (std::strcmp(name, "traversalPoints") == 0) ok = ParseTraversalPoints(source);
else if (std::strcmp(name, "hintNodes") == 0) ok = ParseHintNodes(source);
else if (std::strcmp(name, "trees") == 0) ok = ParseTrees(source);
else return false;
if (!ok) return false;
}
if (trees.Num() == 0) {
bspTree_t tree{};
tree.floorNormal = -settings.gravityDir;
tree.headNode = 1;
tree.firstArea = 1;
tree.lastArea = areas.Num();
trees.Append(tree);
}
BuildReachabilityChains();
CalculateAreaBounds();
return true;
}
+73 -57
View File
@@ -1,69 +1,85 @@
#pragma once
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
// Original PDB header: w:\tech5\engine\aas2file\aastraversalchaindata.h
// Recovered logical types: 6
// Signatures retain Xbox 360 ABI evidence and may still require manual review.
#include "idlib/index.h"
#include "idlib/math/vector.h"
#include <cstdint>
// IDA Local Type ordinal 2227; PDB kind: enum.
enum idAASTraversalChainData::localFlags_t : __int32
{
EVASION_ONLY = 0x1,
EMERGENCY_ONLY = 0x2,
COMBAT_ONLY = 0x4,
ENABLED = 0x8,
RUN_WHEN_DONE = 0x10,
DELTA_CORRECT = 0x20,
MAX_FLAG_BITS = 0x6,
enum aasType_t : int {
AAS_MONSTER16 = 0,
AAS_MONSTER24 = 1,
AAS_MONSTER32 = 2,
AAS_MONSTER48 = 3,
AAS_MONSTER96 = 4,
AAS_MONSTER128 = 5,
AAS_MONSTER160 = 6,
AAS_FILE_EXTENSION_MAX = 8
};
// IDA Local Type ordinal 2933; PDB kind: enum.
enum idAASTraversalChainData::localClass_t : __int32
{
CLASS_A = 0x1,
CLASS_B = 0x2,
CLASS_C = 0x4,
CLASS_D = 0x8,
CLASS_E = 0x10,
MAX_CLASS_BITS = 0x5,
enum invalidReachability_t : int { INVALID_REACHABILITY_INDEX = -1 };
enum invalidAASAnimIndex_t : int { INVALID_AAS_ANIM_INDEX = -1 };
enum invalidAASTree_t : int { INVALID_AAS_TREE_INDEX = -1 };
enum invalidAASDependencyIndex_t : int { INVALID_AAS_DEPENDENCY_NODE = -1 };
enum invalidAASInteractionEntIndex_t : int { INVALID_AAS_INTERACTION_ENT = -1 };
enum invalidAASTraversalNameIndex_t : int { INVALID_AAS_TRAVERSAL_NAME = -1 };
struct aas2Traversal_t {
idVec3 startPoint;
idVec3 endPoint;
idQuantizedVec3 orientationFwd;
idQuantizedVec3 extrusionFwd;
idIndex<short, invalidAASAnimIndex_t> animIndex;
idIndex<short, invalidReachability_t> reachabilityIndex;
idIndex<short, invalidAASDependencyIndex_t> dependencyIndex;
idIndex<short, invalidAASInteractionEntIndex_t> interactionEntIndex;
std::int16_t extrusionDistance;
std::int16_t startAreaNum;
std::int16_t endAreaNum;
idIndex<short, invalidAASTraversalNameIndex_t> traversalNameIndex;
std::uint32_t flags;
aas2Traversal_t();
void Clear();
void CalcExtrusionPoint(idVec3& extrusionPoint, const idVec3& up) const;
};
// IDA Local Type ordinal 14103; PDB kind: struct.
const struct aas2Traversal_t
{
idVec3 startPoint;
idVec3 endPoint;
idQuantizedVec3 orientationFwd;
idQuantizedVec3 extrusionFwd;
idIndex<short,enum invalidAASAnimIndex_t> animIndex;
idIndex<short,enum invalidReachability_t> reachabilityIndex;
idIndex<short,enum invalidAASDependencyIndex_t> dependencyIndex;
idIndex<short,enum invalidAASInteractionEntIndex_t> interactionEntIndex;
__int16 extrusionDistance;
__int16 startAreaNum;
__int16 endAreaNum;
idIndex<short,enum invalidAASTraversalNameIndex_t> traversalNameIndex;
unsigned int flags;
};
using aasTraversalNameIndex_t =
idIndex<short, invalidAASTraversalNameIndex_t>;
// IDA Local Type ordinal 20467; PDB kind: class.
class idAASTraversalChainData
{
class idDeclAnimWeb;
class idAASTraversalChainData {
public:
aasType_t mAASType;
idAASTraversalChainData::localClass_t mClass;
idAASTraversalChainData::localFlags_t mFlags;
idVec3 mExtrusionEndpoint;
const idDeclAnimWeb *mAnimWeb;
enum localFlags_t : int {
EVASION_ONLY = 0x1,
EMERGENCY_ONLY = 0x2,
COMBAT_ONLY = 0x4,
ENABLED = 0x8,
RUN_WHEN_DONE = 0x10,
DELTA_CORRECT = 0x20,
MAX_FLAG_BITS = 6
};
enum localClass_t : int {
CLASS_A = 0x1,
CLASS_B = 0x2,
CLASS_C = 0x4,
CLASS_D = 0x8,
CLASS_E = 0x10,
MAX_CLASS_BITS = 5
};
struct flagMap_t { int gameFlag; int aasFlag; };
aasType_t mAASType;
localClass_t mClass;
localFlags_t mFlags;
idVec3 mExtrusionEndpoint;
const idDeclAnimWeb* mAnimWeb;
};
// IDA Local Type ordinal 22459; PDB kind: struct.
struct idAASTraversalChainData::flagMap_t
{
int gameFlag;
int aasFlag;
};
// IDA Local Type ordinal 27754; PDB kind: typedef.
typedef idIndex<short,enum invalidAASTraversalNameIndex_t> aasTraversalNameIndex_t;
#if INTPTR_MAX == INT32_MAX
static_assert(sizeof(aas2Traversal_t) == 56,
"Recovered aas2Traversal_t ABI changed");
static_assert(sizeof(idAASTraversalChainData) == 28,
"Recovered idAASTraversalChainData ABI changed");
#endif