Added AAS2File code.
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
@@ -1,24 +1,27 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
|
#include "aas2file/aas2debugareamodeldata.h"
|
||||||
// Original PDB header: w:\tech5\engine\aas2file\aas2debugareamodel.h
|
#include "framework/resource.h"
|
||||||
// Recovered logical types: 1
|
|
||||||
// Signatures retain Xbox 360 ABI evidence and may still require manual review.
|
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
// IDA Local Type ordinal 23519; PDB kind: class.
|
class idAAS2DebugAreaModel : public idResource {
|
||||||
class idAAS2DebugAreaModel : public idResource
|
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
// Recovered virtual interface; IDA vtable ordinal 23520.
|
idAAS2DebugAreaModel();
|
||||||
virtual ~idAAS2DebugAreaModel();
|
~idAAS2DebugAreaModel() override;
|
||||||
virtual void LoadResource();
|
|
||||||
virtual bool ReloadIfStale();
|
|
||||||
virtual void WriteResourceFile();
|
|
||||||
virtual idResourceList *GetResourceList();
|
|
||||||
virtual void Print();
|
|
||||||
virtual void List();
|
|
||||||
|
|
||||||
idAAS2DebugAreaModelData data;
|
void LoadResource() override;
|
||||||
unsigned int sourceTimestamp;
|
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;
|
||||||
|
}
|
||||||
@@ -1,37 +1,47 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
|
#include "framework/resource.h"
|
||||||
// Original PDB header: w:\tech5\engine\aas2file\aas2debugareamodeldata.h
|
#include "idlib/containers/list.h"
|
||||||
// Recovered logical types: 4
|
#include "idlib/math/vector.h"
|
||||||
// Signatures retain Xbox 360 ABI evidence and may still require manual review.
|
#include "idlib/text/str.h"
|
||||||
|
|
||||||
|
class idRenderWorld;
|
||||||
|
|
||||||
// IDA Local Type ordinal 23514; PDB kind: struct.
|
class idAAS2DebugAreaModelData {
|
||||||
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
|
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
idList<idVec3,5> vertices;
|
struct areaTri_t { int v0; int v1; int v2; };
|
||||||
idList<idAAS2DebugAreaModelData::debugAreaInfo_t,5> areas;
|
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.
|
#if INTPTR_MAX == INT32_MAX
|
||||||
class idAAS2DebugAreaModelData::LoadBinary::__l2::idLocalFileBuffer
|
static_assert(sizeof(idAAS2DebugAreaModelData::areaTri_t) == 12,
|
||||||
{
|
"Recovered debug area triangle ABI changed");
|
||||||
public:
|
static_assert(sizeof(idAAS2DebugAreaModelData::debugAreaInfo_t) == 16,
|
||||||
unsigned int dataLen;
|
"Recovered debug area info ABI changed");
|
||||||
char *data;
|
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
|
#pragma once
|
||||||
|
|
||||||
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
|
#include <cstdint>
|
||||||
// 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.
|
|
||||||
|
|
||||||
|
class idAAS2DebugAreaModelData;
|
||||||
|
|
||||||
// IDA Local Type ordinal 23855; PDB kind: class.
|
class idAAS2DebugAreaModelGenerator {
|
||||||
class idAAS2DebugAreaModelGenerator
|
|
||||||
{
|
|
||||||
public:
|
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
@@ -1,94 +1,475 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
|
#include "aas2file/aastraversalchaindata.h"
|
||||||
// Original PDB header: w:\tech5\engine\aas2file\aas2file.h
|
#include "framework/resource.h"
|
||||||
// Recovered logical types: 5
|
#include "idlib/bv/bounds.h"
|
||||||
// Signatures retain Xbox 360 ABI evidence and may still require manual review.
|
#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.
|
class idFile;
|
||||||
struct idAAS2File::bspTree_t
|
class idLexer;
|
||||||
{
|
|
||||||
idVec3 floorNormal;
|
enum aas2TravelFlag_t : int {
|
||||||
int headNode;
|
AAS_TFL_INVALID = 0x1,
|
||||||
int firstArea;
|
AAS_TFL_INVALID_TEAM1 = 0x2,
|
||||||
int lastArea;
|
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.
|
enum aas2AreaFlag_t : int {
|
||||||
class idAAS2File : public idResource
|
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:
|
public:
|
||||||
// Recovered virtual interface; IDA vtable ordinal 22001.
|
enum type_t : int { AAS_PLAYER = 0, AAS_MONSTER = 1, AAS_VEHICLE = 2, AAS_MAX = 3 };
|
||||||
virtual ~idAAS2File();
|
enum aasPrimitiveMode_t : int {
|
||||||
virtual void LoadResource();
|
AAS_PRIMITIVE_MODE_DEFAULT = 0,
|
||||||
virtual bool ReloadIfStale();
|
AAS_PRIMITIVE_MODE_NEVER = 1,
|
||||||
virtual void WriteResourceFile();
|
AAS_PRIMITIVE_MODE_ALWAYS = 2
|
||||||
virtual idResourceList *GetResourceList();
|
};
|
||||||
virtual void Print();
|
|
||||||
virtual void List();
|
|
||||||
|
|
||||||
unsigned int crc;
|
idAAS2Settings();
|
||||||
unsigned int timestamp;
|
bool ParseInt(idLexer& source, int& value);
|
||||||
idList<int,37> visitedAreas;
|
bool ParseFloat(idLexer& source, float& value);
|
||||||
idAAS2Settings settings;
|
bool ParseVector(idLexer& source, idVec3& vector);
|
||||||
int major;
|
bool ParseBounds(idLexer& source, idBounds& bounds);
|
||||||
int minor;
|
bool ValidForBounds(const idBounds& bounds) const;
|
||||||
int firstFakeVertex;
|
bool ReadFromFile(idLexer& source);
|
||||||
int firstFakeEdge;
|
bool WriteToFileBinary(idFile& file) const;
|
||||||
int firstFakeEdgeIndex;
|
bool ReadFromFileBinary(idFile& file);
|
||||||
int firstFakeArea;
|
|
||||||
idList<idAAS2File::bspTree_t,37> trees;
|
type_t type;
|
||||||
idList<idPlane,37> planes;
|
idStr fileExtensionAAS;
|
||||||
idList<idVec3,37> vertices;
|
idStr groupName;
|
||||||
idList<aas2Edge_t,37> edges;
|
idStr explicitGroupName;
|
||||||
idList<int,37> edgeIndex;
|
idBounds boundingBox;
|
||||||
idList<aas2Reachability_t,37> reachabilities;
|
int primitiveModeBrush;
|
||||||
idList<aas2Area_t,37> areas;
|
int primitiveModePatch;
|
||||||
idList<aas2Node_t,37> nodes;
|
int primitiveModeModel;
|
||||||
idList<aas2Portal_t,37> portals;
|
idVec3 gravityDir;
|
||||||
idList<int,37> portalIndex;
|
float gravityValue;
|
||||||
idList<aas2Cluster_t,37> clusters;
|
float maxStepHeight;
|
||||||
idList<unsigned char,37> obstaclePVS;
|
float maxBarrierHeight;
|
||||||
idList<aas2Name_t,37> reachabilityNames;
|
float maxWaterJumpHeight;
|
||||||
idList<aas2AnimName_t,37> animNames;
|
float maxFallHeight;
|
||||||
idList<aas2DependencyName_t,37> dependencyNames;
|
float minFloorCos;
|
||||||
idList<aas2InteractionEntityName_t,37> interactionEntityNames;
|
float minHighCeiling;
|
||||||
idList<aas2TraversalEntityName_t,37> traversalEntityNames;
|
float groundSpeed;
|
||||||
idList<aas2Cover_t,37> cover;
|
float waterSpeed;
|
||||||
idList<int,37> areaCoverIndex;
|
float ladderSpeed;
|
||||||
idList<int,37> touchingCoverIndex;
|
float wallCornerEdgeRadius;
|
||||||
idList<aas2ChokePoint_t,37> chokePoints;
|
float ledgeCornerEdgeRadius;
|
||||||
idList<aas2Traversal_t,37> traversalPoints;
|
float obstaclePVSRadius;
|
||||||
idList<aas2HintNode_t,37> hintNodes;
|
float wallCornerReachabilityBackoff;
|
||||||
idList<aas2AreaBounds_t,37> areaBounds;
|
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 aas2Edge_t { int vertexNum[2]; int flags; };
|
||||||
struct idAAS2File::bestReachableArea_t
|
struct aas2Node_t { std::uint32_t planeNum; std::uint32_t flags; int children[2]; };
|
||||||
{
|
struct aas2Portal_t {
|
||||||
float bboxHeight;
|
std::uint16_t areaNum;
|
||||||
float maxEdgeDist;
|
std::int16_t clusters[2];
|
||||||
int areaFlags;
|
std::uint16_t clusterAreaNum[2];
|
||||||
int excludeTravelFlags;
|
std::uint16_t maxAreaTravelTime;
|
||||||
int pointAreaNum;
|
};
|
||||||
float pointAreaFloorDist;
|
struct aas2Cluster_t { int numAreas; int numReachableAreas; int numPortals; int firstPortal; };
|
||||||
int boundsAreaNum;
|
struct aas2Name_t { char name[128]; int index; };
|
||||||
float boundsAreaFloorDist;
|
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 aas2Area_t {
|
||||||
struct idAAS2File::floorEdgeSplitPoint_t
|
std::uint32_t travelFlags;
|
||||||
{
|
std::uint16_t flags;
|
||||||
idVec3 point;
|
std::int16_t numEdges;
|
||||||
float dist;
|
int firstEdge;
|
||||||
int edgeNum;
|
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 aas2Cover_t {
|
||||||
struct idAAS2File::GetObstaclePVSWallEdges::__l25::wallEdge_t
|
idVec3 origin;
|
||||||
{
|
idVec3 dir;
|
||||||
int edgeNum;
|
std::int16_t areaNum;
|
||||||
int verts[2];
|
std::int16_t flags;
|
||||||
idAAS2File::GetObstaclePVSWallEdges::__l25::wallEdge_t *next;
|
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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,69 +1,85 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata.
|
#include "idlib/index.h"
|
||||||
// Original PDB header: w:\tech5\engine\aas2file\aastraversalchaindata.h
|
#include "idlib/math/vector.h"
|
||||||
// Recovered logical types: 6
|
|
||||||
// Signatures retain Xbox 360 ABI evidence and may still require manual review.
|
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
// IDA Local Type ordinal 2227; PDB kind: enum.
|
enum aasType_t : int {
|
||||||
enum idAASTraversalChainData::localFlags_t : __int32
|
AAS_MONSTER16 = 0,
|
||||||
{
|
AAS_MONSTER24 = 1,
|
||||||
EVASION_ONLY = 0x1,
|
AAS_MONSTER32 = 2,
|
||||||
EMERGENCY_ONLY = 0x2,
|
AAS_MONSTER48 = 3,
|
||||||
COMBAT_ONLY = 0x4,
|
AAS_MONSTER96 = 4,
|
||||||
ENABLED = 0x8,
|
AAS_MONSTER128 = 5,
|
||||||
RUN_WHEN_DONE = 0x10,
|
AAS_MONSTER160 = 6,
|
||||||
DELTA_CORRECT = 0x20,
|
AAS_FILE_EXTENSION_MAX = 8
|
||||||
MAX_FLAG_BITS = 0x6,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// IDA Local Type ordinal 2933; PDB kind: enum.
|
enum invalidReachability_t : int { INVALID_REACHABILITY_INDEX = -1 };
|
||||||
enum idAASTraversalChainData::localClass_t : __int32
|
enum invalidAASAnimIndex_t : int { INVALID_AAS_ANIM_INDEX = -1 };
|
||||||
{
|
enum invalidAASTree_t : int { INVALID_AAS_TREE_INDEX = -1 };
|
||||||
CLASS_A = 0x1,
|
enum invalidAASDependencyIndex_t : int { INVALID_AAS_DEPENDENCY_NODE = -1 };
|
||||||
CLASS_B = 0x2,
|
enum invalidAASInteractionEntIndex_t : int { INVALID_AAS_INTERACTION_ENT = -1 };
|
||||||
CLASS_C = 0x4,
|
enum invalidAASTraversalNameIndex_t : int { INVALID_AAS_TRAVERSAL_NAME = -1 };
|
||||||
CLASS_D = 0x8,
|
|
||||||
CLASS_E = 0x10,
|
struct aas2Traversal_t {
|
||||||
MAX_CLASS_BITS = 0x5,
|
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.
|
using aasTraversalNameIndex_t =
|
||||||
const struct aas2Traversal_t
|
idIndex<short, invalidAASTraversalNameIndex_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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// IDA Local Type ordinal 20467; PDB kind: class.
|
class idDeclAnimWeb;
|
||||||
class idAASTraversalChainData
|
|
||||||
{
|
class idAASTraversalChainData {
|
||||||
public:
|
public:
|
||||||
aasType_t mAASType;
|
enum localFlags_t : int {
|
||||||
idAASTraversalChainData::localClass_t mClass;
|
EVASION_ONLY = 0x1,
|
||||||
idAASTraversalChainData::localFlags_t mFlags;
|
EMERGENCY_ONLY = 0x2,
|
||||||
idVec3 mExtrusionEndpoint;
|
COMBAT_ONLY = 0x4,
|
||||||
const idDeclAnimWeb *mAnimWeb;
|
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.
|
#if INTPTR_MAX == INT32_MAX
|
||||||
struct idAASTraversalChainData::flagMap_t
|
static_assert(sizeof(aas2Traversal_t) == 56,
|
||||||
{
|
"Recovered aas2Traversal_t ABI changed");
|
||||||
int gameFlag;
|
static_assert(sizeof(idAASTraversalChainData) == 28,
|
||||||
int aasFlag;
|
"Recovered idAASTraversalChainData ABI changed");
|
||||||
};
|
#endif
|
||||||
|
|
||||||
// IDA Local Type ordinal 27754; PDB kind: typedef.
|
|
||||||
typedef idIndex<short,enum invalidAASTraversalNameIndex_t> aasTraversalNameIndex_t;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user