mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-12-14 23:01:26 +00:00
557 lines
14 KiB
C++
557 lines
14 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
|
|
|
|
Doom 3 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.
|
|
|
|
Doom 3 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 Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "../../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "AASFile.h"
|
|
#include "AASFile_local.h"
|
|
#include "AASCluster.h"
|
|
|
|
|
|
/*
|
|
================
|
|
idAASCluster::UpdatePortal
|
|
================
|
|
*/
|
|
bool idAASCluster::UpdatePortal( int areaNum, int clusterNum ) {
|
|
int portalNum;
|
|
aasPortal_t *portal;
|
|
|
|
// find the portal for this area
|
|
for ( portalNum = 1; portalNum < file->portals.Num(); portalNum++ ) {
|
|
if ( file->portals[portalNum].areaNum == areaNum ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( portalNum >= file->portals.Num() ) {
|
|
common->Error( "no portal for area %d", areaNum );
|
|
return true;
|
|
}
|
|
|
|
portal = &file->portals[portalNum];
|
|
|
|
// if the portal is already fully updated
|
|
if ( portal->clusters[0] == clusterNum ) {
|
|
return true;
|
|
}
|
|
if ( portal->clusters[1] == clusterNum ) {
|
|
return true;
|
|
}
|
|
// if the portal has no front cluster yet
|
|
if ( !portal->clusters[0] ) {
|
|
portal->clusters[0] = clusterNum;
|
|
}
|
|
// if the portal has no back cluster yet
|
|
else if ( !portal->clusters[1] )
|
|
{
|
|
portal->clusters[1] = clusterNum;
|
|
}
|
|
else
|
|
{
|
|
// remove the cluster portal flag contents
|
|
file->areas[areaNum].contents &= ~AREACONTENTS_CLUSTERPORTAL;
|
|
return false;
|
|
}
|
|
|
|
// set the area cluster number to the negative portal number
|
|
file->areas[areaNum].cluster = -portalNum;
|
|
|
|
// add the portal to the cluster using the portal index
|
|
file->portalIndex.Append( portalNum );
|
|
file->clusters[clusterNum].numPortals++;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASCluster::FloodClusterAreas_r
|
|
================
|
|
*/
|
|
bool idAASCluster::FloodClusterAreas_r( int areaNum, int clusterNum ) {
|
|
aasArea_t *area;
|
|
aasFace_t *face;
|
|
int faceNum, i;
|
|
idReachability *reach;
|
|
|
|
area = &file->areas[areaNum];
|
|
|
|
// if the area is already part of a cluster
|
|
if ( area->cluster > 0 ) {
|
|
if ( area->cluster == clusterNum ) {
|
|
return true;
|
|
}
|
|
// there's a reachability going from one cluster to another only in one direction
|
|
common->Error( "cluster %d touched cluster %d at area %d\r\n", clusterNum, file->areas[areaNum].cluster, areaNum );
|
|
return false;
|
|
}
|
|
|
|
// if this area is a cluster portal
|
|
if ( area->contents & AREACONTENTS_CLUSTERPORTAL ) {
|
|
return UpdatePortal( areaNum, clusterNum );
|
|
}
|
|
|
|
// set the area cluster number
|
|
area->cluster = clusterNum;
|
|
|
|
if ( !noFaceFlood ) {
|
|
// use area faces to flood into adjacent areas
|
|
for ( i = 0; i < area->numFaces; i++ ) {
|
|
faceNum = abs(file->faceIndex[area->firstFace + i]);
|
|
face = &file->faces[faceNum];
|
|
if ( face->areas[0] == areaNum ) {
|
|
if ( face->areas[1] ) {
|
|
if ( !FloodClusterAreas_r( face->areas[1], clusterNum ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if ( face->areas[0] ) {
|
|
if ( !FloodClusterAreas_r( face->areas[0], clusterNum ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// use the reachabilities to flood into other areas
|
|
for ( reach = file->areas[areaNum].reach; reach; reach = reach->next ) {
|
|
if ( !FloodClusterAreas_r( reach->toAreaNum, clusterNum) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// use the reversed reachabilities to flood into other areas
|
|
for ( reach = file->areas[areaNum].rev_reach; reach; reach = reach->rev_next ) {
|
|
if ( !FloodClusterAreas_r( reach->fromAreaNum, clusterNum) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASCluster::RemoveAreaClusterNumbers
|
|
================
|
|
*/
|
|
void idAASCluster::RemoveAreaClusterNumbers( void ) {
|
|
int i;
|
|
|
|
for ( i = 1; i < file->areas.Num(); i++ ) {
|
|
file->areas[i].cluster = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASCluster::NumberClusterAreas
|
|
================
|
|
*/
|
|
void idAASCluster::NumberClusterAreas( int clusterNum ) {
|
|
int i, portalNum;
|
|
aasCluster_t *cluster;
|
|
aasPortal_t *portal;
|
|
|
|
cluster = &file->clusters[clusterNum];
|
|
cluster->numAreas = 0;
|
|
cluster->numReachableAreas = 0;
|
|
|
|
// number all areas in this cluster WITH reachabilities
|
|
for ( i = 1; i < file->areas.Num(); i++ ) {
|
|
|
|
if ( file->areas[i].cluster != clusterNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !(file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
|
|
continue;
|
|
}
|
|
|
|
file->areas[i].clusterAreaNum = cluster->numAreas++;
|
|
cluster->numReachableAreas++;
|
|
}
|
|
|
|
// number all portals in this cluster WITH reachabilities
|
|
for ( i = 0; i < cluster->numPortals; i++ ) {
|
|
portalNum = file->portalIndex[cluster->firstPortal + i];
|
|
portal = &file->portals[portalNum];
|
|
|
|
if ( !(file->areas[portal->areaNum].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( portal->clusters[0] == clusterNum ) {
|
|
portal->clusterAreaNum[0] = cluster->numAreas++;
|
|
}
|
|
else {
|
|
portal->clusterAreaNum[1] = cluster->numAreas++;
|
|
}
|
|
cluster->numReachableAreas++;
|
|
}
|
|
|
|
// number all areas in this cluster WITHOUT reachabilities
|
|
for ( i = 1; i < file->areas.Num(); i++ ) {
|
|
|
|
if ( file->areas[i].cluster != clusterNum ) {
|
|
continue;
|
|
}
|
|
|
|
if ( file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
|
|
continue;
|
|
}
|
|
|
|
file->areas[i].clusterAreaNum = cluster->numAreas++;
|
|
}
|
|
|
|
// number all portals in this cluster WITHOUT reachabilities
|
|
for ( i = 0; i < cluster->numPortals; i++ ) {
|
|
portalNum = file->portalIndex[cluster->firstPortal + i];
|
|
portal = &file->portals[portalNum];
|
|
|
|
if ( file->areas[portal->areaNum].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( portal->clusters[0] == clusterNum ) {
|
|
portal->clusterAreaNum[0] = cluster->numAreas++;
|
|
}
|
|
else {
|
|
portal->clusterAreaNum[1] = cluster->numAreas++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASCluster::FindClusters
|
|
================
|
|
*/
|
|
bool idAASCluster::FindClusters( void ) {
|
|
int i, clusterNum;
|
|
aasCluster_t cluster;
|
|
|
|
RemoveAreaClusterNumbers();
|
|
|
|
for ( i = 1; i < file->areas.Num(); i++ ) {
|
|
// if the area is already part of a cluster
|
|
if ( file->areas[i].cluster ) {
|
|
continue;
|
|
}
|
|
|
|
// if not flooding through faces only use areas that have reachabilities
|
|
if ( noFaceFlood ) {
|
|
if ( !(file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// if the area is a cluster portal
|
|
if ( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) {
|
|
continue;
|
|
}
|
|
|
|
cluster.numAreas = 0;
|
|
cluster.numReachableAreas = 0;
|
|
cluster.firstPortal = file->portalIndex.Num();
|
|
cluster.numPortals = 0;
|
|
clusterNum = file->clusters.Num();
|
|
file->clusters.Append( cluster );
|
|
|
|
// flood the areas in this cluster
|
|
if ( !FloodClusterAreas_r( i, clusterNum ) ) {
|
|
return false;
|
|
}
|
|
|
|
// number the cluster areas
|
|
NumberClusterAreas( clusterNum );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASCluster::CreatePortals
|
|
================
|
|
*/
|
|
void idAASCluster::CreatePortals( void ) {
|
|
int i;
|
|
aasPortal_t portal;
|
|
|
|
for ( i = 1; i < file->areas.Num(); i++ ) {
|
|
// if the area is a cluster portal
|
|
if ( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) {
|
|
portal.areaNum = i;
|
|
portal.clusters[0] = portal.clusters[1] = 0;
|
|
portal.maxAreaTravelTime = 0;
|
|
file->portals.Append( portal );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASCluster::TestPortals
|
|
================
|
|
*/
|
|
bool idAASCluster::TestPortals( void ) {
|
|
int i;
|
|
aasPortal_t *portal, *portal2;
|
|
aasArea_t *area, *area2;
|
|
idReachability *reach;
|
|
bool ok;
|
|
|
|
ok = true;
|
|
for ( i = 1; i < file->portals.Num(); i++ ) {
|
|
portal = &file->portals[i];
|
|
area = &file->areas[portal->areaNum];
|
|
|
|
// if this portal was already removed
|
|
if ( !( area->contents & AREACONTENTS_CLUSTERPORTAL) ) {
|
|
continue;
|
|
}
|
|
|
|
// may not removed this portal if it has a reachability to a removed portal
|
|
for ( reach = area->reach; reach; reach = reach->next ) {
|
|
area2 = &file->areas[ reach->toAreaNum ];
|
|
if ( area2->contents & AREACONTENTS_CLUSTERPORTAL ) {
|
|
continue;
|
|
}
|
|
if ( area2->cluster < 0 ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( reach ) {
|
|
continue;
|
|
}
|
|
|
|
// may not removed this portal if it has a reversed reachability to a removed portal
|
|
for ( reach = area->rev_reach; reach; reach = reach->rev_next ) {
|
|
area2 = &file->areas[ reach->toAreaNum ];
|
|
if ( area2->contents & AREACONTENTS_CLUSTERPORTAL ) {
|
|
continue;
|
|
}
|
|
if ( area2->cluster < 0 ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( reach ) {
|
|
continue;
|
|
}
|
|
|
|
// portal should have two clusters set
|
|
if ( !portal->clusters[0] ) {
|
|
area->contents &= ~AREACONTENTS_CLUSTERPORTAL;
|
|
ok = false;
|
|
continue;
|
|
}
|
|
if ( !portal->clusters[1] ) {
|
|
area->contents &= ~AREACONTENTS_CLUSTERPORTAL;
|
|
ok = false;
|
|
continue;
|
|
}
|
|
|
|
// this portal may not have reachabilities to a portal that doesn't seperate the same clusters
|
|
for ( reach = area->reach; reach; reach = reach->next ) {
|
|
area2 = &file->areas[ reach->toAreaNum ];
|
|
|
|
if ( !(area2->contents & AREACONTENTS_CLUSTERPORTAL) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( area2->cluster > 0 ) {
|
|
area2->contents &= ~AREACONTENTS_CLUSTERPORTAL;
|
|
ok = false;
|
|
continue;
|
|
}
|
|
|
|
portal2 = &file->portals[ -file->areas[ reach->toAreaNum ].cluster ];
|
|
|
|
if ( ( portal2->clusters[0] != portal->clusters[0] && portal2->clusters[0] != portal->clusters[1] ) ||
|
|
( portal2->clusters[1] != portal->clusters[0] && portal2->clusters[1] != portal->clusters[1] ) ) {
|
|
area2->contents &= ~AREACONTENTS_CLUSTERPORTAL;
|
|
ok = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASCluster::RemoveInvalidPortals
|
|
================
|
|
*/
|
|
void idAASCluster::RemoveInvalidPortals( void ) {
|
|
int i, j, k, face1Num, face2Num, otherAreaNum, numOpenAreas, numInvalidPortals;
|
|
aasFace_t *face1, *face2;
|
|
|
|
numInvalidPortals = 0;
|
|
for ( i = 0; i < file->areas.Num(); i++ ) {
|
|
if ( !( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) ) {
|
|
continue;
|
|
}
|
|
|
|
numOpenAreas = 0;
|
|
for ( j = 0; j < file->areas[i].numFaces; j++ ) {
|
|
face1Num = file->faceIndex[ file->areas[i].firstFace + j ];
|
|
face1 = &file->faces[ abs(face1Num) ];
|
|
otherAreaNum = face1->areas[ face1Num < 0 ];
|
|
|
|
if ( !otherAreaNum ) {
|
|
continue;
|
|
}
|
|
|
|
for ( k = 0; k < j; k++ ) {
|
|
face2Num = file->faceIndex[ file->areas[i].firstFace + k ];
|
|
face2 = &file->faces[ abs(face2Num) ];
|
|
if ( otherAreaNum == face2->areas[ face2Num < 0 ] ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( k < j ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !( file->areas[otherAreaNum].contents & AREACONTENTS_CLUSTERPORTAL ) ) {
|
|
numOpenAreas++;
|
|
}
|
|
}
|
|
|
|
if ( numOpenAreas <= 1 ) {
|
|
file->areas[i].contents &= AREACONTENTS_CLUSTERPORTAL;
|
|
numInvalidPortals++;
|
|
}
|
|
}
|
|
|
|
common->Printf( "\r%6d invalid portals removed\n", numInvalidPortals );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASCluster::Build
|
|
================
|
|
*/
|
|
bool idAASCluster::Build( idAASFileLocal *file ) {
|
|
|
|
common->Printf( "[Clustering]\n" );
|
|
|
|
this->file = file;
|
|
this->noFaceFlood = true;
|
|
|
|
RemoveInvalidPortals();
|
|
|
|
while( 1 ) {
|
|
|
|
// delete all existing clusters
|
|
file->DeleteClusters();
|
|
|
|
// create the portals from the portal areas
|
|
CreatePortals();
|
|
|
|
common->Printf( "\r%6d", file->portals.Num() );
|
|
|
|
// find the clusters
|
|
if ( !FindClusters() ) {
|
|
continue;
|
|
}
|
|
|
|
// test the portals
|
|
if ( !TestPortals() ) {
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
common->Printf( "\r%6d portals\n", file->portals.Num() );
|
|
common->Printf( "%6d clusters\n", file->clusters.Num() );
|
|
|
|
for ( int i = 0; i < file->clusters.Num(); i++ ) {
|
|
common->Printf( "%6d reachable areas in cluster %d\n", file->clusters[i].numReachableAreas, i );
|
|
}
|
|
|
|
file->ReportRoutingEfficiency();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASCluster::BuildSingleCluster
|
|
================
|
|
*/
|
|
bool idAASCluster::BuildSingleCluster( idAASFileLocal *file ) {
|
|
int i, numAreas;
|
|
aasCluster_t cluster;
|
|
|
|
common->Printf( "[Clustering]\n" );
|
|
|
|
this->file = file;
|
|
|
|
// delete all existing clusters
|
|
file->DeleteClusters();
|
|
|
|
cluster.firstPortal = 0;
|
|
cluster.numPortals = 0;
|
|
cluster.numAreas = file->areas.Num();
|
|
cluster.numReachableAreas = 0;
|
|
// give all reachable areas in the cluster a number
|
|
for ( i = 0; i < file->areas.Num(); i++ ) {
|
|
file->areas[i].cluster = file->clusters.Num();
|
|
if ( file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
|
|
file->areas[i].clusterAreaNum = cluster.numReachableAreas++;
|
|
}
|
|
}
|
|
// give the remaining areas a number within the cluster
|
|
numAreas = cluster.numReachableAreas;
|
|
for ( i = 0; i < file->areas.Num(); i++ ) {
|
|
if ( file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
|
|
continue;
|
|
}
|
|
file->areas[i].clusterAreaNum = numAreas++;
|
|
}
|
|
file->clusters.Append( cluster );
|
|
|
|
common->Printf( "%6d portals\n", file->portals.Num() );
|
|
common->Printf( "%6d clusters\n", file->clusters.Num() );
|
|
|
|
for ( i = 0; i < file->clusters.Num(); i++ ) {
|
|
common->Printf( "%6d reachable areas in cluster %d\n", file->clusters[i].numReachableAreas, i );
|
|
}
|
|
|
|
file->ReportRoutingEfficiency();
|
|
|
|
return true;
|
|
}
|