Files
Quake_4_Alpha/src/aas/AASFile.cpp
T
2026-08-13 11:39:42 -07:00

943 lines
35 KiB
C++

/*
===========================================================================
Quake 4 Reconstructed GPL Source Code
Copyright (C) 2026 Justin Marshall(IceColdDuke).
This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?).
Quake 4 Reconstructed Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Quake 4 Reconstructed Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake 4 Reconstructed Source Code. If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#if defined( Q4_AAS_LEGACY_SEED )
#include "AASFile_legacy.inc"
#else
/*
===========================================================================
Quake 4 source reconstruction
The SDK exposes these methods through idAASFile but does not include their
engine implementation. The matching PDB supplies the class layout and method
signatures; the retail Hex-Rays listing supplies the non-trivial behavior.
===========================================================================
*/
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "AASFile.h"
#include "AASFile_local.h"
idAASFileLocal AASFileLocal;
idAASFile *AASFile = &AASFileLocal;
static bool Reachability_Write( idFile *file, const idReachability *reach ) {
file->WriteFloatString( "\t\t%d %d (%f %f %f) (%f %f %f) %d %d",
reach->travelType, reach->toAreaNum,
reach->start.x, reach->start.y, reach->start.z,
reach->end.x, reach->end.y, reach->end.z,
reach->edgeNum, reach->travelTime );
return true;
}
static bool Reachability_Read( Lexer &src, idReachability &reach ) {
reach.travelType = src.ParseInt();
reach.toAreaNum = src.ParseInt();
if ( !src.Parse1DMatrix( 3, reach.start.ToFloatPtr() ) ||
!src.Parse1DMatrix( 3, reach.end.ToFloatPtr() ) ) {
return false;
}
reach.edgeNum = src.ParseInt();
reach.travelTime = src.ParseInt();
return true;
}
static bool Reachability_Special_Write( idFile *file, const idReachability_Special *reach ) {
file->WriteFloatString( "\n\t\t{\n" );
for ( int i = 0; i < reach->dict.GetNumKeyVals(); i++ ) {
const idKeyValue *kv = reach->dict.GetKeyVal( i );
file->WriteFloatString( "\t\t\t\"%s\" \"%s\"\n", kv->GetKey().c_str(), kv->GetValue().c_str() );
}
file->WriteFloatString( "\t\t}" );
return true;
}
static bool Reachability_Special_Read( Lexer &src, idReachability_Special &reach ) {
idToken key;
idToken value;
if ( !src.ExpectTokenString( "{" ) ) {
return false;
}
while ( src.ReadToken( &key ) ) {
if ( key == "}" ) {
return true;
}
if ( !src.ExpectTokenType( TT_STRING, 0, &value ) ) {
return false;
}
reach.dict.Set( key, value );
}
return false;
}
void idReachability::CopyBase( idReachability &reach ) {
travelType = reach.travelType;
toAreaNum = reach.toAreaNum;
start = reach.start;
end = reach.end;
edgeNum = reach.edgeNum;
travelTime = reach.travelTime;
}
idAASSettings::idAASSettings() {
numBoundingBoxes = 1;
boundingBoxes[0] = idBounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) );
usePatches = writeBrushMap = playerFlood = noOptimize = false;
allowSwimReachabilities = allowFlyReachabilities = false;
generateTacticalFeatures = false;
iAASOnly = 0;
fileExtension = "aas48";
gravity.Set( 0, 0, -1066 );
gravityDir = gravity;
gravityValue = gravityDir.Normalize();
invGravityDir = -gravityDir;
maxStepHeight = 14.0f;
maxBarrierHeight = 32.0f;
maxWaterJumpHeight = 20.0f;
maxFallHeight = 64.0f;
minFloorCos = 0.7f;
tt_barrierJump = tt_startCrouching = tt_waterJump = tt_startWalkOffLedge = 100;
debugColor.Set( 1, 0, 0, 1 );
debugDraw = true;
}
bool idAASSettings::WriteToFile( idFile *file ) const {
file->WriteFloatString( "{\n" );
file->WriteFloatString( "\tbboxes\n\t{\n" );
for ( int i = 0; i < numBoundingBoxes; i++ ) {
file->WriteFloatString( "\t\t(%f %f %f)-(%f %f %f)\n",
boundingBoxes[i][0].x, boundingBoxes[i][0].y, boundingBoxes[i][0].z,
boundingBoxes[i][1].x, boundingBoxes[i][1].y, boundingBoxes[i][1].z );
}
file->WriteFloatString( "\t}\n" );
file->WriteFloatString( "\tusePatches = %d\n", usePatches );
file->WriteFloatString( "\twriteBrushMap = %d\n", writeBrushMap );
file->WriteFloatString( "\tplayerFlood = %d\n", playerFlood );
file->WriteFloatString( "\tallowSwimReachabilities = %d\n", allowSwimReachabilities );
file->WriteFloatString( "\tallowFlyReachabilities = %d\n", allowFlyReachabilities );
file->WriteFloatString( "\tgenerateTacticalFeatures = %d\n", generateTacticalFeatures );
file->WriteFloatString( "\tfileExtension = \"%s\"\n", fileExtension.c_str() );
file->WriteFloatString( "\tgravity = (%f %f %f)\n", gravity.x, gravity.y, gravity.z );
file->WriteFloatString( "\tmaxStepHeight = %f\n", maxStepHeight );
file->WriteFloatString( "\tmaxBarrierHeight = %f\n", maxBarrierHeight );
file->WriteFloatString( "\tmaxWaterJumpHeight = %f\n", maxWaterJumpHeight );
file->WriteFloatString( "\tmaxFallHeight = %f\n", maxFallHeight );
file->WriteFloatString( "\tminFloorCos = %f\n", minFloorCos );
file->WriteFloatString( "\ttt_barrierJump = %d\n", tt_barrierJump );
file->WriteFloatString( "\ttt_startCrouching = %d\n", tt_startCrouching );
file->WriteFloatString( "\ttt_waterJump = %d\n", tt_waterJump );
file->WriteFloatString( "\ttt_startWalkOffLedge = %d\n", tt_startWalkOffLedge );
file->WriteFloatString( "}\n" );
return true;
}
bool idAASSettings::ParseBool( Lexer &src, bool &value ) {
if ( !src.ExpectTokenString( "=" ) ) return false;
value = src.ParseBool();
return true;
}
bool idAASSettings::ParseInt( Lexer &src, int &value ) {
if ( !src.ExpectTokenString( "=" ) ) return false;
value = src.ParseInt();
return true;
}
bool idAASSettings::ParseFloat( Lexer &src, float &value ) {
if ( !src.ExpectTokenString( "=" ) ) return false;
value = src.ParseFloat();
return true;
}
bool idAASSettings::ParseVector( Lexer &src, idVec3 &value ) {
return src.ExpectTokenString( "=" ) && src.Parse1DMatrix( 3, value.ToFloatPtr() );
}
bool idAASSettings::ParseBBoxes( Lexer &src ) {
idToken token;
numBoundingBoxes = 0;
if ( !src.ExpectTokenString( "{" ) ) return false;
while ( src.ReadToken( &token ) ) {
if ( token == "}" ) return numBoundingBoxes > 0;
if ( numBoundingBoxes >= MAX_AAS_BOUNDING_BOXES ) {
src.Error( "more than %d AAS bounding boxes", MAX_AAS_BOUNDING_BOXES );
return false;
}
src.UnreadToken( &token );
idBounds bounds;
if ( !src.Parse1DMatrix( 3, bounds[0].ToFloatPtr() ) ||
!src.ExpectTokenString( "-" ) ||
!src.Parse1DMatrix( 3, bounds[1].ToFloatPtr() ) ) return false;
boundingBoxes[numBoundingBoxes++] = bounds;
}
return false;
}
bool idAASSettings::FromParser( Lexer &src ) {
idToken token;
if ( !src.ExpectTokenString( "{" ) ) return false;
while ( src.ReadToken( &token ) ) {
if ( token == "}" ) return numBoundingBoxes > 0;
if ( token == "bboxes" ) { if ( !ParseBBoxes( src ) ) return false; }
else if ( token == "usePatches" ) { if ( !ParseBool( src, usePatches ) ) return false; }
else if ( token == "writeBrushMap" ) { if ( !ParseBool( src, writeBrushMap ) ) return false; }
else if ( token == "playerFlood" ) { if ( !ParseBool( src, playerFlood ) ) return false; }
else if ( token == "noOptimize" ) { if ( !ParseBool( src, noOptimize ) ) return false; }
else if ( token == "allowSwimReachabilities" ) { if ( !ParseBool( src, allowSwimReachabilities ) ) return false; }
else if ( token == "allowFlyReachabilities" ) { if ( !ParseBool( src, allowFlyReachabilities ) ) return false; }
else if ( token == "generateTacticalFeatures" ) { if ( !ParseBool( src, generateTacticalFeatures ) ) return false; }
else if ( token == "iAASOnly" ) { if ( !ParseInt( src, iAASOnly ) ) return false; }
else if ( token == "fileExtension" ) { if ( !src.ExpectTokenString( "=" ) || !src.ReadToken( &token ) ) return false; fileExtension = token; }
else if ( token == "gravity" ) { if ( !ParseVector( src, gravity ) ) return false; gravityDir = gravity; gravityValue = gravityDir.Normalize(); invGravityDir = -gravityDir; }
else if ( token == "maxStepHeight" ) { if ( !ParseFloat( src, maxStepHeight ) ) return false; }
else if ( token == "maxBarrierHeight" ) { if ( !ParseFloat( src, maxBarrierHeight ) ) return false; }
else if ( token == "maxWaterJumpHeight" ) { if ( !ParseFloat( src, maxWaterJumpHeight ) ) return false; }
else if ( token == "maxFallHeight" ) { if ( !ParseFloat( src, maxFallHeight ) ) return false; }
else if ( token == "minFloorCos" ) { if ( !ParseFloat( src, minFloorCos ) ) return false; }
else if ( token == "tt_barrierJump" ) { if ( !ParseInt( src, tt_barrierJump ) ) return false; }
else if ( token == "tt_startCrouching" ) { if ( !ParseInt( src, tt_startCrouching ) ) return false; }
else if ( token == "tt_waterJump" ) { if ( !ParseInt( src, tt_waterJump ) ) return false; }
else if ( token == "tt_startWalkOffLedge" ) { if ( !ParseInt( src, tt_startWalkOffLedge ) ) return false; }
else if ( token == "debugDraw" ) { if ( !ParseBool( src, debugDraw ) ) return false; }
else if ( token == "debugColor" ) { idVec3 rgb; if ( !ParseVector( src, rgb ) ) return false; debugColor.Set( rgb.x, rgb.y, rgb.z, 1 ); }
else src.Warning( "unknown AAS setting '%s'", token.c_str() );
}
return false;
}
idAASFileLocal::idAASFileLocal() {
planeList.SetGranularity( 4096 );
vertices.SetGranularity( 4096 );
edges.SetGranularity( 4096 );
edgeIndex.SetGranularity( 4096 );
faces.SetGranularity( 1024 );
faceIndex.SetGranularity( 4096 );
areas.SetGranularity( 1024 );
nodes.SetGranularity( 1024 );
portals.SetGranularity( 1024 );
portalIndex.SetGranularity( 4096 );
clusters.SetGranularity( 1024 );
featureIndex.SetGranularity( 4096 );
features.SetGranularity( 1024 );
crc = 0;
memset( isDummy, 0, sizeof( isDummy ) );
}
idAASFileLocal::~idAASFileLocal() {
DeleteReachabilities();
}
void idAASFileLocal::Init() {
aasEdge_t edge;
aasFace_t face;
aasArea_t area;
aasNode_t node;
memset( &edge, 0, sizeof( edge ) );
memset( &face, 0, sizeof( face ) );
memset( &area, 0, sizeof( area ) );
memset( &node, 0, sizeof( node ) );
edges.Append( edge );
faces.Append( face );
areas.Append( area );
nodes.Append( node );
}
void idAASFileLocal::SetSizes( sizeEstimate_t size ) {
planeList.Resize( size.numNodes / 2, 1024 );
vertices.Resize( size.numEdgeIndexes / 3, 1024 );
edges.Resize( size.numEdgeIndexes / 2, 1024 );
edgeIndex.Resize( size.numEdgeIndexes, 4096 );
faces.Resize( size.numFaceIndexes, 1024 );
faceIndex.Resize( size.numFaceIndexes, 4096 );
areas.Resize( size.numAreas, 1024 );
nodes.Resize( size.numNodes, 1024 );
featureIndex.Resize( 2048, 2048 );
features.Resize( 2048, 2048 );
}
bool idAASFileLocal::ParseIndex( Lexer &src, idList<aasIndex_t> &indexes, int dummyIndex ) {
const int count = src.ParseInt();
indexes.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) {
return false;
}
for ( int i = 0; i < count; i++ ) {
src.ParseInt();
if ( !src.ExpectTokenString( "(" ) ) {
return false;
}
const aasIndex_t index = src.ParseInt();
if ( !src.ExpectTokenString( ")" ) ) {
return false;
}
indexes.Append( index );
}
if ( !src.ExpectTokenString( "}" ) ) {
return false;
}
if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) {
isDummy[dummyIndex] = true;
}
return true;
}
bool idAASFileLocal::ParsePlanes( Lexer &src, int dummyIndex ) {
const int count = src.ParseInt();
planeList.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) {
return false;
}
for ( int i = 0; i < count; i++ ) {
idVec4 value;
src.ParseInt();
if ( !src.Parse1DMatrix( 4, value.ToFloatPtr() ) ) {
return false;
}
idPlane plane;
plane.SetNormal( value.ToVec3() );
plane.SetDist( value.w );
planeList.Append( plane );
}
if ( !src.ExpectTokenString( "}" ) ) {
return false;
}
if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) {
isDummy[dummyIndex] = true;
}
return true;
}
bool idAASFileLocal::ParseVertices( Lexer &src, int dummyIndex ) {
const int count = src.ParseInt();
vertices.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) {
return false;
}
for ( int i = 0; i < count; i++ ) {
aasVertex_t vertex;
src.ParseInt();
if ( !src.Parse1DMatrix( 3, vertex.ToFloatPtr() ) ) {
return false;
}
vertices.Append( vertex );
}
if ( !src.ExpectTokenString( "}" ) ) {
return false;
}
if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) {
isDummy[dummyIndex] = true;
}
return true;
}
bool idAASFileLocal::ParseEdges( Lexer &src, int dummyIndex ) {
const int count = src.ParseInt();
edges.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) {
return false;
}
for ( int i = 0; i < count; i++ ) {
aasEdge_t edge;
src.ParseInt();
if ( !src.ExpectTokenString( "(" ) ) return false;
edge.vertexNum[0] = src.ParseInt();
edge.vertexNum[1] = src.ParseInt();
if ( !src.ExpectTokenString( ")" ) ) return false;
edges.Append( edge );
}
if ( !src.ExpectTokenString( "}" ) ) return false;
if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true;
return true;
}
bool idAASFileLocal::ParseFaces( Lexer &src, int dummyIndex ) {
const int count = src.ParseInt();
faces.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) return false;
for ( int i = 0; i < count; i++ ) {
aasFace_t face;
memset( &face, 0, sizeof( face ) );
src.ParseInt();
if ( !src.ExpectTokenString( "(" ) ) return false;
face.planeNum = src.ParseInt();
face.flags = src.ParseInt();
face.areas[0] = src.ParseInt();
face.areas[1] = src.ParseInt();
face.firstEdge = src.ParseInt();
face.numEdges = src.ParseInt();
if ( !src.ExpectTokenString( ")" ) ) return false;
faces.Append( face );
}
if ( !src.ExpectTokenString( "}" ) ) return false;
if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true;
return true;
}
bool idAASFileLocal::ParseReachabilities( Lexer &src, int areaNum ) {
aasArea_t &area = areas[areaNum];
const int count = src.ParseInt();
if ( !src.ExpectTokenString( "{" ) ) return false;
area.reach = NULL;
area.rev_reach = NULL;
area.travelFlags = AreaContentsTravelFlags( areaNum );
for ( int i = 0; i < count; i++ ) {
idReachability base;
memset( &base, 0, sizeof( base ) );
if ( !Reachability_Read( src, base ) ) return false;
idReachability *reach;
if ( base.travelType == TFL_SPECIAL ) {
idReachability_Special *special = new idReachability_Special;
if ( !Reachability_Special_Read( src, *special ) ) {
delete special;
return false;
}
reach = special;
} else {
reach = new idReachability;
}
memset( static_cast<void *>( reach ), 0, sizeof( idReachability ) );
reach->CopyBase( base );
reach->fromAreaNum = areaNum;
reach->next = area.reach;
area.reach = reach;
}
return src.ExpectTokenString( "}" ) != 0;
}
bool idAASFileLocal::ParseAreas( Lexer &src, int dummyIndex ) {
const int count = src.ParseInt();
areas.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) return false;
for ( int i = 0; i < count; i++ ) {
aasArea_t area;
memset( &area, 0, sizeof( area ) );
src.ParseInt();
if ( !src.ExpectTokenString( "(" ) ) return false;
area.flags = src.ParseInt();
area.contents = src.ParseInt();
area.firstFace = src.ParseInt();
area.numFaces = src.ParseInt();
area.cluster = src.ParseInt();
area.clusterAreaNum = src.ParseInt();
if ( version != "1.07" ) {
area.numFeatures = src.ParseInt();
area.firstFeature = src.ParseInt();
}
if ( !src.ExpectTokenString( ")" ) ) return false;
areas.Append( area );
if ( !ParseReachabilities( src, i ) ) return false;
}
if ( !src.ExpectTokenString( "}" ) ) return false;
if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true;
LinkReversedReachability();
return true;
}
bool idAASFileLocal::ParseNodes( Lexer &src, int dummyIndex ) {
const int count = src.ParseInt();
nodes.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) return false;
for ( int i = 0; i < count; i++ ) {
aasNode_t node;
memset( &node, 0, sizeof( node ) );
src.ParseInt();
if ( !src.ExpectTokenString( "(" ) ) return false;
node.planeNum = src.ParseInt();
node.children[0] = src.ParseInt();
node.children[1] = src.ParseInt();
if ( !src.ExpectTokenString( ")" ) ) return false;
nodes.Append( node );
}
if ( !src.ExpectTokenString( "}" ) ) return false;
if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true;
return true;
}
bool idAASFileLocal::ParsePortals( Lexer &src, int dummyIndex ) {
const int count = src.ParseInt();
portals.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) return false;
for ( int i = 0; i < count; i++ ) {
aasPortal_t portal;
memset( &portal, 0, sizeof( portal ) );
src.ParseInt();
if ( !src.ExpectTokenString( "(" ) ) return false;
portal.areaNum = src.ParseInt();
portal.clusters[0] = src.ParseInt();
portal.clusters[1] = src.ParseInt();
portal.clusterAreaNum[0] = src.ParseInt();
portal.clusterAreaNum[1] = src.ParseInt();
if ( !src.ExpectTokenString( ")" ) ) return false;
portals.Append( portal );
}
if ( !src.ExpectTokenString( "}" ) ) return false;
if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true;
return true;
}
bool idAASFileLocal::ParseClusters( Lexer &src, int dummyIndex ) {
const int count = src.ParseInt();
clusters.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) return false;
for ( int i = 0; i < count; i++ ) {
aasCluster_t cluster;
memset( &cluster, 0, sizeof( cluster ) );
src.ParseInt();
if ( !src.ExpectTokenString( "(" ) ) return false;
cluster.numAreas = src.ParseInt();
cluster.numReachableAreas = src.ParseInt();
cluster.firstPortal = src.ParseInt();
cluster.numPortals = src.ParseInt();
if ( !src.ExpectTokenString( ")" ) ) return false;
clusters.Append( cluster );
}
if ( !src.ExpectTokenString( "}" ) ) return false;
if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true;
return true;
}
bool idAASFileLocal::ParseFeatures( Lexer &src ) {
const int count = src.ParseInt();
features.Resize( count );
if ( !src.ExpectTokenString( "{" ) ) return false;
for ( int i = 0; i < count; i++ ) {
aasFeature_t feature;
memset( &feature, 0, sizeof( feature ) );
src.ParseInt();
if ( !src.ExpectTokenString( "(" ) ) return false;
feature.flags = src.ParseInt();
feature.height = src.ParseInt();
feature.normalx = src.ParseInt();
feature.normaly = src.ParseInt();
feature.x = src.ParseInt();
feature.y = src.ParseInt();
feature.z = src.ParseInt();
if ( !src.ExpectTokenString( ")" ) ) return false;
features.Append( feature );
}
return src.ExpectTokenString( "}" ) != 0;
}
bool idAASFileLocal::Load( const idStr &fileName, unsigned int mapFileCRC ) {
Lexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGESCAPECHARS |
LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES );
idToken token;
name = fileName;
crc = mapFileCRC;
common->Printf( "[Load AAS]\nloading %s\n", name.c_str() );
if ( !src.LoadFile( name.c_str() ) ) {
return false;
}
if ( !src.ExpectTokenString( AAS_FILEID ) ) {
common->Warning( "Not an AAS file: '%s'", name.c_str() );
return false;
}
if ( !src.ReadToken( &token ) ) {
return false;
}
version = token;
if ( version != AAS_FILEVERSION && version != "1.07" ) {
common->Warning( "AAS file '%s' has version %s instead of %s", name.c_str(), version.c_str(), AAS_FILEVERSION );
}
if ( !src.ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) ) {
common->Warning( "AAS file '%s' has no map file CRC", name.c_str() );
return false;
}
const unsigned int fileCRC = token.GetUnsignedLongValue();
if ( mapFileCRC && fileCRC != mapFileCRC ) {
common->Warning( "AAS file '%s' is out of date", name.c_str() );
return false;
}
Clear();
while ( src.ReadToken( &token ) ) {
bool parsed = false;
if ( token == "settings" ) parsed = settings.FromParser( src );
else if ( token == "planes" ) parsed = ParsePlanes( src, 0 );
else if ( token == "vertices" ) parsed = ParseVertices( src, 1 );
else if ( token == "edges" ) parsed = ParseEdges( src, 2 );
else if ( token == "edgeIndex" ) parsed = ParseIndex( src, edgeIndex, 3 );
else if ( token == "faces" ) parsed = ParseFaces( src, 4 );
else if ( token == "faceIndex" ) parsed = ParseIndex( src, faceIndex, 5 );
else if ( token == "areas" ) parsed = ParseAreas( src, 6 );
else if ( token == "nodes" ) parsed = ParseNodes( src, 7 );
else if ( token == "portals" ) parsed = ParsePortals( src, 8 );
else if ( token == "portalIndex" ) parsed = ParseIndex( src, portalIndex, 9 );
else if ( token == "clusters" ) parsed = ParseClusters( src, 10 );
else if ( token == "featureIndex" ) parsed = ParseIndex( src, featureIndex, 11 );
else if ( token == "features" ) parsed = ParseFeatures( src );
else {
src.Error( "idAASFileLocal::Load: bad token \"%s\"", token.c_str() );
return false;
}
if ( !parsed ) {
return false;
}
}
FinishAreas();
if ( !IsDummyFile( mapFileCRC ) ) {
if ( !vertices.Num() || !edges.Num() || !faces.Num() ) {
common->Warning( "error loading %s (invalid data)", fileName.c_str() );
return false;
}
const int depth = MaxTreeDepth();
if ( depth > MAX_AAS_TREE_DEPTH ) {
common->Warning( "idAASFileLocal::Load: tree depth = %d", depth );
}
common->Printf( "done loading %s (size %d).\n", fileName.c_str(), MemorySize() );
}
return !src.HadError();
}
bool idAASFileLocal::Write( const idStr &fileName, unsigned int mapFileCRC ) {
common->Printf( "[Write AAS]\nwriting %s\n", fileName.c_str() );
name = fileName;
crc = mapFileCRC;
idFile *file = fileSystem->OpenFileWrite( fileName, "fs_devpath" );
if ( !file ) {
common->Warning( "Error opening %s", fileName.c_str() );
return false;
}
file->WriteFloatString( "%s \"%s\"\n\n%u\n\n", AAS_FILEID, AAS_FILEVERSION, mapFileCRC );
file->WriteFloatString( "settings\n" );
settings.WriteToFile( file );
file->WriteFloatString( "planes %d {\n", planeList.Num() );
for ( int i = 0; i < planeList.Num(); i++ ) file->WriteFloatString( "\t%d ( %f %f %f %f )\n", i, planeList[i].Normal().x, planeList[i].Normal().y, planeList[i].Normal().z, planeList[i].Dist() );
file->WriteFloatString( "}\nvertices %d {\n", vertices.Num() );
for ( int i = 0; i < vertices.Num(); i++ ) file->WriteFloatString( "\t%d ( %f %f %f )\n", i, vertices[i].x, vertices[i].y, vertices[i].z );
file->WriteFloatString( "}\nedges %d {\n", edges.Num() );
for ( int i = 0; i < edges.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d )\n", i, edges[i].vertexNum[0], edges[i].vertexNum[1] );
file->WriteFloatString( "}\nedgeIndex %d {\n", edgeIndex.Num() );
for ( int i = 0; i < edgeIndex.Num(); i++ ) file->WriteFloatString( "\t%d ( %d )\n", i, edgeIndex[i] );
file->WriteFloatString( "}\nfaces %d {\n", faces.Num() );
for ( int i = 0; i < faces.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d %d %d %d )\n", i, faces[i].planeNum, faces[i].flags, faces[i].areas[0], faces[i].areas[1], faces[i].firstEdge, faces[i].numEdges );
file->WriteFloatString( "}\nfaceIndex %d {\n", faceIndex.Num() );
for ( int i = 0; i < faceIndex.Num(); i++ ) file->WriteFloatString( "\t%d ( %d )\n", i, faceIndex[i] );
file->WriteFloatString( "}\nareas %d {\n", areas.Num() );
for ( int i = 0; i < areas.Num(); i++ ) {
int reachCount = 0;
for ( idReachability *reach = areas[i].reach; reach; reach = reach->next ) reachCount++;
file->WriteFloatString( "\t%d ( %d %d %d %d %d %d %d %d ) %d {\n", i, areas[i].flags, areas[i].contents, areas[i].firstFace, areas[i].numFaces, areas[i].cluster, areas[i].clusterAreaNum, areas[i].numFeatures, areas[i].firstFeature, reachCount );
for ( idReachability *reach = areas[i].reach; reach; reach = reach->next ) {
Reachability_Write( file, reach );
if ( reach->travelType == TFL_SPECIAL ) Reachability_Special_Write( file, static_cast<idReachability_Special *>( reach ) );
file->WriteFloatString( "\n" );
}
file->WriteFloatString( "\t}\n" );
}
file->WriteFloatString( "}\nnodes %d {\n", nodes.Num() );
for ( int i = 0; i < nodes.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d )\n", i, nodes[i].planeNum, nodes[i].children[0], nodes[i].children[1] );
file->WriteFloatString( "}\nportals %d {\n", portals.Num() );
for ( int i = 0; i < portals.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d %d %d )\n", i, portals[i].areaNum, portals[i].clusters[0], portals[i].clusters[1], portals[i].clusterAreaNum[0], portals[i].clusterAreaNum[1] );
file->WriteFloatString( "}\nportalIndex %d {\n", portalIndex.Num() );
for ( int i = 0; i < portalIndex.Num(); i++ ) file->WriteFloatString( "\t%d ( %d )\n", i, portalIndex[i] );
file->WriteFloatString( "}\nclusters %d {\n", clusters.Num() );
for ( int i = 0; i < clusters.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d %d )\n", i, clusters[i].numAreas, clusters[i].numReachableAreas, clusters[i].firstPortal, clusters[i].numPortals );
file->WriteFloatString( "}\n" );
if ( features.Num() ) {
file->WriteFloatString( "featureIndex %d {\n", featureIndex.Num() );
for ( int i = 0; i < featureIndex.Num(); i++ ) file->WriteFloatString( "\t%d ( %d )\n", i, featureIndex[i] );
file->WriteFloatString( "}\nfeatures %d {\n", features.Num() );
for ( int i = 0; i < features.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d %d %d %d %d )\n", i, features[i].flags, features[i].height, features[i].normalx, features[i].normaly, features[i].x, features[i].y, features[i].z );
file->WriteFloatString( "}\n" );
}
fileSystem->CloseFile( file );
common->Printf( "done.\n" );
return true;
}
int idAASFileLocal::MemorySize() const {
return planeList.Size() + vertices.Size() + edges.Size() + edgeIndex.Size() +
faces.Size() + faceIndex.Size() + areas.Size() + nodes.Size() + portals.Size() +
portalIndex.Size() + clusters.Size() + featureIndex.Size() + features.Size() +
sizeof( idReachability_Walk ) * NumReachabilities();
}
idAASFile *idAASFileLocal::CreateNew( void ) {
return new idAASFileLocal;
}
idAASSettings *idAASFileLocal::CreateAASSettings( void ) {
return new idAASSettings;
}
idReachability *idAASFileLocal::CreateReachability( int type ) {
switch ( type ) {
case RE_WALK: return new idReachability_Walk;
case RE_WALKOFFLEDGE: return new idReachability_WalkOffLedge;
case RE_FLY: return new idReachability_Fly;
case RE_SWIM: return new idReachability_Swim;
case RE_WATERJUMP: return new idReachability_WaterJump;
case RE_BARRIERJUMP: return new idReachability_BarrierJump;
case RE_SPECIAL: return new idReachability_Special;
default: return NULL;
}
}
bool idAASFileLocal::FromParser( idAASSettings *edit, Lexer &src ) {
return edit->FromParser( src );
}
const char *idAASFileLocal::GetName( void ) const {
return name.c_str();
}
unsigned int idAASFileLocal::GetCRC( void ) const {
return crc;
}
int idAASFileLocal::GetNumPlanes( void ) const { return planeList.Num(); }
idPlane &idAASFileLocal::GetPlane( int index ) { return planeList[index]; }
int idAASFileLocal::FindPlane( const idPlane &plane, const float normalEps, const float distEps ) { return planeList.FindPlane( plane, normalEps, distEps ); }
int idAASFileLocal::GetNumVertices( void ) const { return vertices.Num(); }
aasVertex_t &idAASFileLocal::GetVertex( int index ) { return vertices[index]; }
int idAASFileLocal::AppendVertex( aasVertex_t &vertex ) { return vertices.Append( vertex ); }
int idAASFileLocal::GetNumEdges( void ) const { return edges.Num(); }
aasEdge_t &idAASFileLocal::GetEdge( int index ) { return edges[index]; }
int idAASFileLocal::AppendEdge( aasEdge_t &edge ) { return edges.Append( edge ); }
int idAASFileLocal::GetNumEdgeIndexes( void ) const { return edgeIndex.Num(); }
aasIndex_t &idAASFileLocal::GetEdgeIndex( int index ) { return edgeIndex[index]; }
int idAASFileLocal::AppendEdgeIndex( aasIndex_t &index ) { return edgeIndex.Append( index ); }
int idAASFileLocal::GetNumFaces( void ) const { return faces.Num(); }
aasFace_t &idAASFileLocal::GetFace( int index ) { return faces[index]; }
int idAASFileLocal::AppendFace( aasFace_t &face ) { return faces.Append( face ); }
int idAASFileLocal::GetNumFaceIndexes( void ) const { return faceIndex.Num(); }
aasIndex_t &idAASFileLocal::GetFaceIndex( int index ) { return faceIndex[index]; }
int idAASFileLocal::AppendFaceIndex( aasIndex_t &index ) { return faceIndex.Append( index ); }
int idAASFileLocal::GetNumAreas( void ) const { return areas.Num(); }
aasArea_t &idAASFileLocal::GetArea( int index ) { return areas[index]; }
int idAASFileLocal::AppendArea( aasArea_t &area ) { return areas.Append( area ); }
int idAASFileLocal::GetNumNodes( void ) const { return nodes.Num(); }
aasNode_t &idAASFileLocal::GetNode( int index ) { return nodes[index]; }
int idAASFileLocal::AppendNode( aasNode_t &node ) { return nodes.Append( node ); }
void idAASFileLocal::SetNumNodes( int num ) { nodes.SetNum( num ); }
int idAASFileLocal::GetNumPortals( void ) const { return portals.Num(); }
aasPortal_t &idAASFileLocal::GetPortal( int index ) { return portals[index]; }
int idAASFileLocal::AppendPortal( aasPortal_t &portal ) { return portals.Append( portal ); }
int idAASFileLocal::GetNumPortalIndexes( void ) const { return portalIndex.Num(); }
aasIndex_t &idAASFileLocal::GetPortalIndex( int index ) { return portalIndex[index]; }
int idAASFileLocal::AppendPortalIndex( aasIndex_t &index, int clusterNum ) {
clusters[clusterNum].numPortals++;
return portalIndex.Append( index );
}
int idAASFileLocal::GetNumClusters( void ) const { return clusters.Num(); }
aasCluster_t &idAASFileLocal::GetCluster( int index ) { return clusters[index]; }
int idAASFileLocal::AppendCluster( aasCluster_t &cluster ) { return clusters.Append( cluster ); }
void idAASFileLocal::ClearTactical( void ) {
featureIndex.Clear();
features.Clear();
}
int idAASFileLocal::GetNumFeatureIndexes( void ) const { return featureIndex.Num(); }
aasIndex_t &idAASFileLocal::GetFeatureIndex( int index ) { return featureIndex[index]; }
int idAASFileLocal::AppendFeatureIndex( aasIndex_t &index ) { return featureIndex.Append( index ); }
int idAASFileLocal::GetNumFeatures( void ) const { return features.Num(); }
aasFeature_t &idAASFileLocal::GetFeature( int index ) { return features[index]; }
int idAASFileLocal::AppendFeature( aasFeature_t &feature ) { return features.Append( feature ); }
idAASSettings &idAASFileLocal::GetSettings( void ) { return settings; }
void idAASFileLocal::SetSettings( const idAASSettings &in ) { settings = in; }
void idAASFileLocal::SetPortalMaxTravelTime( int index, int time ) { portals[index].maxAreaTravelTime = time; }
void idAASFileLocal::SetAreaTravelFlag( int index, int flag ) { areas[index].travelFlags |= flag; }
void idAASFileLocal::RemoveAreaTravelFlag( int index, int flag ) { areas[index].travelFlags &= ~flag; }
size_t idAASFileLocal::GetMemorySize( void ) {
int numReachableAreas = 0;
int routingMemory = 0;
for ( int i = 0; i < clusters.Num(); i++ ) {
numReachableAreas += clusters[i].numReachableAreas;
routingMemory += Square( clusters[i].numReachableAreas );
}
return MemorySize() + routingMemory + 3 * numReachableAreas * portals.Num();
}
bool idAASFileLocal::IsDummyFile( unsigned int mapFileCRC ) {
for ( int i = 0; i < 11; i++ ) {
if ( !isDummy[i] ) {
return false;
}
}
return crc == mapFileCRC;
}
const idDict &idAASFileLocal::GetReachabilitySpecialDict( idReachability *reach ) const {
return static_cast<idReachability_Special *>( reach )->dict;
}
void idAASFileLocal::SetReachabilitySpecialDictKeyValue( idReachability *reach, const char *key, const char *value ) {
static_cast<idReachability_Special *>( reach )->dict.Set( key, value );
}
void idAASFileLocal::Clear() {
DeleteReachabilities();
planeList.Clear();
vertices.Clear();
edges.Clear();
edgeIndex.Clear();
faces.Clear();
faceIndex.Clear();
areas.Clear();
nodes.Clear();
portals.Clear();
portalIndex.Clear();
clusters.Clear();
ClearTactical();
memset( isDummy, 0, sizeof( isDummy ) );
}
void idAASFileLocal::LinkReversedReachability() {
for ( int i = 0; i < areas.Num(); i++ ) {
areas[i].rev_reach = NULL;
}
for ( int i = 0; i < areas.Num(); i++ ) {
for ( idReachability *reach = areas[i].reach; reach; reach = reach->next ) {
if ( reach->toAreaNum <= 0 || reach->toAreaNum >= areas.Num() ) {
continue;
}
reach->rev_next = areas[reach->toAreaNum].rev_reach;
areas[reach->toAreaNum].rev_reach = reach;
}
}
}
void idAASFileLocal::FinishAreas() {
// Quake 4 stores the usable ceiling height in every AAS area. The Doom 3
// baseline only calculated center and bounds, which leaves the Raven field
// uninitialized and breaks tactical cover queries in the game DLL.
if ( areas.Num() < 2 ) {
for ( int i = 0; i < areas.Num(); i++ ) {
areas[i].center.Zero();
areas[i].bounds.Clear();
areas[i].ceiling = 0.0f;
}
return;
}
for ( int i = 0; i < areas.Num(); i++ ) {
areas[i].center = AreaReachableGoal( i );
areas[i].bounds = AreaBounds( i );
}
for ( int i = 0; i < areas.Num(); i++ ) {
if ( areas[i].flags & AREA_FLOOR ) {
int traceAreas[10];
idVec3 tracePoints[10];
aasTrace_t trace;
trace.maxAreas = 10;
trace.areas = traceAreas;
trace.points = tracePoints;
idVec3 end = areas[i].center;
end.z += 500.0f;
Trace( trace, areas[i].center, end );
areas[i].ceiling = trace.endpos.z;
for ( int j = 0; j < trace.numAreas; j++ ) {
if ( traceAreas[j] != i ) {
areas[i].ceiling = tracePoints[j].z;
}
}
} else {
idVec3 point( 0.0f, 0.0f, 131072.0f );
PushPointIntoAreaNum( i, point );
areas[i].ceiling = point.z;
}
}
}
int idAASFileLocal::NumReachabilities() const {
int count = 0;
for ( int i = 0; i < areas.Num(); i++ ) {
for ( const idReachability *reach = areas[i].reach; reach; reach = reach->next ) {
count++;
}
}
return count;
}
void idAASFileLocal::PrintInfo() const {
common->Printf( "%6d KB file size\n", MemorySize() >> 10 );
common->Printf( "%6d areas\n", areas.Num() );
common->Printf( "%6d max tree depth\n", MaxTreeDepth() );
ReportRoutingEfficiency();
}
void idAASFileLocal::ReportRoutingEfficiency() const {
int reachableAreas = 0;
int total = 0;
for ( int i = 0; i < clusters.Num(); i++ ) {
const int count = clusters[i].numReachableAreas;
reachableAreas += count;
total += count * count;
}
total += reachableAreas * portals.Num();
common->Printf( "%6d reachable areas\n", reachableAreas );
common->Printf( "%6d reachabilities\n", NumReachabilities() );
common->Printf( "%6d KB max routing cache\n", ( total * 3 ) >> 10 );
}
void idAASFileLocal::DeleteReachabilities() {
for ( int i = 0; i < areas.Num(); i++ ) {
idReachability *reach = areas[i].reach;
while ( reach ) {
idReachability *next = reach->next;
delete reach;
reach = next;
}
areas[i].reach = NULL;
areas[i].rev_reach = NULL;
}
}
void idAASFileLocal::DeleteClusters() {
aasPortal_t portal;
aasCluster_t cluster;
memset( &portal, 0, sizeof( portal ) );
memset( &cluster, 0, sizeof( cluster ) );
portals.Clear();
portalIndex.Clear();
clusters.Clear();
portals.Append( portal );
clusters.Append( cluster );
}
#endif