mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-12-02 17:22:32 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
943 lines
28 KiB
C++
943 lines
28 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 "sys/platform.h"
|
|
|
|
#include "tools/compilers/aas/AASReach.h"
|
|
|
|
#define INSIDEUNITS 2.0f
|
|
#define INSIDEUNITS_WALKEND 0.5f
|
|
#define INSIDEUNITS_WALKSTART 0.1f
|
|
#define INSIDEUNITS_SWIMEND 0.5f
|
|
#define INSIDEUNITS_FLYEND 0.5f
|
|
#define INSIDEUNITS_WATERJUMP 15.0f
|
|
|
|
/*
|
|
================
|
|
idAASReach::ReachabilityExists
|
|
================
|
|
*/
|
|
bool idAASReach::ReachabilityExists( int fromAreaNum, int toAreaNum ) {
|
|
aasArea_t *area;
|
|
idReachability *reach;
|
|
|
|
area = &file->areas[fromAreaNum];
|
|
for ( reach = area->reach; reach; reach = reach->next ) {
|
|
if ( reach->toAreaNum == toAreaNum ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::CanSwimInArea
|
|
================
|
|
*/
|
|
ID_INLINE bool idAASReach::CanSwimInArea( int areaNum ) {
|
|
return ( file->areas[areaNum].contents & AREACONTENTS_WATER ) != 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::AreaHasFloor
|
|
================
|
|
*/
|
|
ID_INLINE bool idAASReach::AreaHasFloor( int areaNum ) {
|
|
return ( file->areas[areaNum].flags & AREA_FLOOR ) != 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::AreaIsClusterPortal
|
|
================
|
|
*/
|
|
ID_INLINE bool idAASReach::AreaIsClusterPortal( int areaNum ) {
|
|
return ( file->areas[areaNum].contents & AREACONTENTS_CLUSTERPORTAL ) != 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::AddReachabilityToArea
|
|
================
|
|
*/
|
|
void idAASReach::AddReachabilityToArea( idReachability *reach, int areaNum ) {
|
|
aasArea_t *area;
|
|
|
|
area = &file->areas[areaNum];
|
|
reach->next = area->reach;
|
|
area->reach = reach;
|
|
numReachabilities++;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::Reachability_Fly
|
|
================
|
|
*/
|
|
void idAASReach::Reachability_Fly( int areaNum ) {
|
|
int i, faceNum, otherAreaNum;
|
|
aasArea_t *area;
|
|
aasFace_t *face;
|
|
idReachability_Fly *reach;
|
|
|
|
area = &file->areas[areaNum];
|
|
|
|
for ( i = 0; i < area->numFaces; i++ ) {
|
|
faceNum = file->faceIndex[area->firstFace + i];
|
|
face = &file->faces[abs(faceNum)];
|
|
|
|
otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
|
|
|
|
if ( otherAreaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ReachabilityExists( areaNum, otherAreaNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
// create reachability going through this face
|
|
reach = new idReachability_Fly();
|
|
reach->travelType = TFL_FLY;
|
|
reach->toAreaNum = otherAreaNum;
|
|
reach->fromAreaNum = areaNum;
|
|
reach->edgeNum = 0;
|
|
reach->travelTime = 1;
|
|
reach->start = file->FaceCenter( abs(faceNum) );
|
|
if ( faceNum < 0 ) {
|
|
reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND;
|
|
} else {
|
|
reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND;
|
|
}
|
|
AddReachabilityToArea( reach, areaNum );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::Reachability_Swim
|
|
================
|
|
*/
|
|
void idAASReach::Reachability_Swim( int areaNum ) {
|
|
int i, faceNum, otherAreaNum;
|
|
aasArea_t *area;
|
|
aasFace_t *face;
|
|
idReachability_Swim *reach;
|
|
|
|
if ( !CanSwimInArea( areaNum ) ) {
|
|
return;
|
|
}
|
|
|
|
area = &file->areas[areaNum];
|
|
|
|
for ( i = 0; i < area->numFaces; i++ ) {
|
|
faceNum = file->faceIndex[area->firstFace + i];
|
|
face = &file->faces[abs(faceNum)];
|
|
|
|
otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
|
|
|
|
if ( otherAreaNum == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !CanSwimInArea( otherAreaNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ReachabilityExists( areaNum, otherAreaNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
// create reachability going through this face
|
|
reach = new idReachability_Swim();
|
|
reach->travelType = TFL_SWIM;
|
|
reach->toAreaNum = otherAreaNum;
|
|
reach->fromAreaNum = areaNum;
|
|
reach->edgeNum = 0;
|
|
reach->travelTime = 1;
|
|
reach->start = file->FaceCenter( abs(faceNum) );
|
|
if ( faceNum < 0 ) {
|
|
reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND;
|
|
} else {
|
|
reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND;
|
|
}
|
|
AddReachabilityToArea( reach, areaNum );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::Reachability_EqualFloorHeight
|
|
================
|
|
*/
|
|
void idAASReach::Reachability_EqualFloorHeight( int areaNum ) {
|
|
int i, k, l, m, n, faceNum, face1Num, face2Num, otherAreaNum, edge1Num, edge2Num;
|
|
aasArea_t *area, *otherArea;
|
|
aasFace_t *face, *face1, *face2;
|
|
idReachability_Walk *reach;
|
|
|
|
if ( !AreaHasFloor( areaNum ) ) {
|
|
return;
|
|
}
|
|
|
|
area = &file->areas[areaNum];
|
|
|
|
for ( i = 0; i < area->numFaces; i++ ) {
|
|
faceNum = file->faceIndex[area->firstFace + i];
|
|
face = &file->faces[abs(faceNum)];
|
|
|
|
otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
|
|
if ( !AreaHasFloor( otherAreaNum ) ) {
|
|
continue;
|
|
}
|
|
|
|
otherArea = &file->areas[otherAreaNum];
|
|
|
|
for ( k = 0; k < area->numFaces; k++ ) {
|
|
face1Num = file->faceIndex[area->firstFace + k];
|
|
face1 = &file->faces[abs(face1Num)];
|
|
|
|
if ( !( face1->flags & FACE_FLOOR ) ) {
|
|
continue;
|
|
}
|
|
for ( l = 0; l < otherArea->numFaces; l++ ) {
|
|
face2Num = file->faceIndex[otherArea->firstFace + l];
|
|
face2 = &file->faces[abs(face2Num)];
|
|
|
|
if ( !( face2->flags & FACE_FLOOR ) ) {
|
|
continue;
|
|
}
|
|
|
|
for ( m = 0; m < face1->numEdges; m++ ) {
|
|
edge1Num = abs(file->edgeIndex[face1->firstEdge + m]);
|
|
for ( n = 0; n < face2->numEdges; n++ ) {
|
|
edge2Num = abs(file->edgeIndex[face2->firstEdge + n]);
|
|
if ( edge1Num == edge2Num ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( n < face2->numEdges ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( m < face1->numEdges ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( l < otherArea->numFaces ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( k < area->numFaces ) {
|
|
// create reachability
|
|
reach = new idReachability_Walk();
|
|
reach->travelType = TFL_WALK;
|
|
reach->toAreaNum = otherAreaNum;
|
|
reach->fromAreaNum = areaNum;
|
|
reach->edgeNum = abs( edge1Num );
|
|
reach->travelTime = 1;
|
|
reach->start = file->EdgeCenter( edge1Num );
|
|
if ( faceNum < 0 ) {
|
|
reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND;
|
|
}
|
|
else {
|
|
reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND;
|
|
}
|
|
AddReachabilityToArea( reach, areaNum );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge
|
|
================
|
|
*/
|
|
bool idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge( int area1num, int area2num ) {
|
|
int i, j, k, l, edge1Num, edge2Num, areas[10];
|
|
int floor_bestArea1FloorEdgeNum, floor_foundReach;
|
|
int water_foundReach;
|
|
int side1, faceSide1, floorFace1Num;
|
|
float dist, dist1, dist2, diff, /*invGravityDot, */ orthogonalDot;
|
|
float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y;
|
|
float length, floor_bestLength, water_bestLength, floor_bestDist, water_bestDist;
|
|
idVec3 v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2;
|
|
idVec3 normal, orthogonal, edgeVec, start, end;
|
|
idVec3 floor_bestStart, floor_bestEnd, floor_bestNormal;
|
|
idVec3 water_bestStart, water_bestEnd, water_bestNormal;
|
|
idVec3 testPoint;
|
|
idPlane *plane;
|
|
aasArea_t *area1, *area2;
|
|
aasFace_t *floorFace1, *floorFace2;
|
|
aasEdge_t *edge1, *edge2;
|
|
idReachability_Walk *walkReach;
|
|
idReachability_BarrierJump *barrierJumpReach;
|
|
idReachability_WaterJump *waterJumpReach;
|
|
idReachability_WalkOffLedge *walkOffLedgeReach;
|
|
aasTrace_t trace;
|
|
|
|
// must be able to walk or swim in the first area
|
|
if ( !AreaHasFloor( area1num ) && !CanSwimInArea( area1num ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !AreaHasFloor( area2num ) && !CanSwimInArea( area2num ) ) {
|
|
return false;
|
|
}
|
|
|
|
area1 = &file->areas[area1num];
|
|
area2 = &file->areas[area2num];
|
|
|
|
// if the areas are not near anough in the x-y direction
|
|
for ( i = 0; i < 2; i++ ) {
|
|
if ( area1->bounds[0][i] > area2->bounds[1][i] + 2.0f ) {
|
|
return false;
|
|
}
|
|
if ( area1->bounds[1][i] < area2->bounds[0][i] - 2.0f ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
floor_bestArea1FloorEdgeNum = 0;
|
|
|
|
floor_bestStart.Zero();
|
|
floor_bestEnd.Zero();
|
|
floor_bestNormal.Zero();
|
|
water_bestStart.Zero();
|
|
water_bestEnd.Zero();
|
|
water_bestNormal.Zero();
|
|
|
|
floor_foundReach = false;
|
|
floor_bestDist = 99999;
|
|
floor_bestLength = 0;
|
|
|
|
water_foundReach = false;
|
|
water_bestDist = 99999;
|
|
water_bestLength = 0;
|
|
|
|
for ( i = 0; i < area1->numFaces; i++ ) {
|
|
floorFace1Num = file->faceIndex[area1->firstFace + i];
|
|
faceSide1 = floorFace1Num < 0;
|
|
floorFace1 = &file->faces[abs(floorFace1Num)];
|
|
|
|
// if this isn't a floor face
|
|
if ( !(floorFace1->flags & FACE_FLOOR) ) {
|
|
|
|
// if we can swim in the first area
|
|
if ( CanSwimInArea( area1num ) ) {
|
|
|
|
// face plane must be more or less horizontal
|
|
plane = &file->planeList[ floorFace1->planeNum ^ (!faceSide1) ];
|
|
if ( plane->Normal() * file->settings.invGravityDir < file->settings.minFloorCos ) {
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
// if we can't swim in the area it must be a ground face
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for ( k = 0; k < floorFace1->numEdges; k++ ) {
|
|
edge1Num = file->edgeIndex[floorFace1->firstEdge + k];
|
|
side1 = (edge1Num < 0);
|
|
// NOTE: for water faces we must take the side area 1 is on into
|
|
// account because the face is shared and doesn't have to be oriented correctly
|
|
if ( !(floorFace1->flags & FACE_FLOOR) ) {
|
|
side1 = (side1 == faceSide1);
|
|
}
|
|
edge1Num = abs(edge1Num);
|
|
edge1 = &file->edges[edge1Num];
|
|
// vertices of the edge
|
|
v1 = file->vertices[edge1->vertexNum[!side1]];
|
|
v2 = file->vertices[edge1->vertexNum[side1]];
|
|
// get a vertical plane through the edge
|
|
// NOTE: normal is pointing into area 2 because the face edges are stored counter clockwise
|
|
edgeVec = v2 - v1;
|
|
normal = edgeVec.Cross( file->settings.invGravityDir );
|
|
normal.Normalize();
|
|
dist = normal * v1;
|
|
|
|
// check the faces from the second area
|
|
for ( j = 0; j < area2->numFaces; j++ ) {
|
|
floorFace2 = &file->faces[abs(file->faceIndex[area2->firstFace + j])];
|
|
// must be a ground face
|
|
if ( !(floorFace2->flags & FACE_FLOOR) ) {
|
|
continue;
|
|
}
|
|
// check the edges of this ground face
|
|
for ( l = 0; l < floorFace2->numEdges; l++ ) {
|
|
edge2Num = abs(file->edgeIndex[floorFace2->firstEdge + l]);
|
|
edge2 = &file->edges[edge2Num];
|
|
// vertices of the edge
|
|
v3 = file->vertices[edge2->vertexNum[0]];
|
|
v4 = file->vertices[edge2->vertexNum[1]];
|
|
// check the distance between the two points and the vertical plane through the edge of area1
|
|
diff = normal * v3 - dist;
|
|
if ( diff < -0.2f || diff > 0.2f ) {
|
|
continue;
|
|
}
|
|
diff = normal * v4 - dist;
|
|
if ( diff < -0.2f || diff > 0.2f ) {
|
|
continue;
|
|
}
|
|
|
|
// project the two ground edges into the step side plane
|
|
// and calculate the shortest distance between the two
|
|
// edges if they overlap in the direction orthogonal to
|
|
// the gravity direction
|
|
orthogonal = file->settings.invGravityDir.Cross( normal );
|
|
//invGravityDot = file->settings.invGravityDir * file->settings.invGravityDir;
|
|
orthogonalDot = orthogonal * orthogonal;
|
|
// projection into the step plane
|
|
// NOTE: since gravity is vertical this is just the z coordinate
|
|
y1 = v1[2];//(v1 * file->settings.invGravity) / invGravityDot;
|
|
y2 = v2[2];//(v2 * file->settings.invGravity) / invGravityDot;
|
|
y3 = v3[2];//(v3 * file->settings.invGravity) / invGravityDot;
|
|
y4 = v4[2];//(v4 * file->settings.invGravity) / invGravityDot;
|
|
|
|
x1 = (v1 * orthogonal) / orthogonalDot;
|
|
x2 = (v2 * orthogonal) / orthogonalDot;
|
|
x3 = (v3 * orthogonal) / orthogonalDot;
|
|
x4 = (v4 * orthogonal) / orthogonalDot;
|
|
|
|
if ( x1 > x2 ) {
|
|
tmp = x1; x1 = x2; x2 = tmp;
|
|
tmp = y1; y1 = y2; y2 = tmp;
|
|
tmpv = v1; v1 = v2; v2 = tmpv;
|
|
}
|
|
if ( x3 > x4 ) {
|
|
tmp = x3; x3 = x4; x4 = tmp;
|
|
tmp = y3; y3 = y4; y4 = tmp;
|
|
tmpv = v3; v3 = v4; v4 = tmpv;
|
|
}
|
|
// if the two projected edge lines have no overlap
|
|
if ( x2 <= x3 || x4 <= x1 ) {
|
|
continue;
|
|
}
|
|
// if the two lines fully overlap
|
|
if ( (x1 - 0.5f < x3 && x4 < x2 + 0.5f) && (x3 - 0.5f < x1 && x2 < x4 + 0.5f) ) {
|
|
dist1 = y3 - y1;
|
|
dist2 = y4 - y2;
|
|
p1area1 = v1;
|
|
p2area1 = v2;
|
|
p1area2 = v3;
|
|
p2area2 = v4;
|
|
}
|
|
else {
|
|
// if the points are equal
|
|
if ( x1 > x3 - 0.1f && x1 < x3 + 0.1f ) {
|
|
dist1 = y3 - y1;
|
|
p1area1 = v1;
|
|
p1area2 = v3;
|
|
}
|
|
else if ( x1 < x3 ) {
|
|
y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1);
|
|
dist1 = y3 - y;
|
|
p1area1 = v3;
|
|
p1area1[2] = y;
|
|
p1area2 = v3;
|
|
}
|
|
else {
|
|
y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3);
|
|
dist1 = y - y1;
|
|
p1area1 = v1;
|
|
p1area2 = v1;
|
|
p1area2[2] = y;
|
|
}
|
|
// if the points are equal
|
|
if ( x2 > x4 - 0.1f && x2 < x4 + 0.1f ) {
|
|
dist2 = y4 - y2;
|
|
p2area1 = v2;
|
|
p2area2 = v4;
|
|
}
|
|
else if ( x2 < x4 ) {
|
|
y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3);
|
|
dist2 = y - y2;
|
|
p2area1 = v2;
|
|
p2area2 = v2;
|
|
p2area2[2] = y;
|
|
}
|
|
else {
|
|
y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1);
|
|
dist2 = y4 - y;
|
|
p2area1 = v4;
|
|
p2area1[2] = y;
|
|
p2area2 = v4;
|
|
}
|
|
}
|
|
|
|
// if both distances are pretty much equal then we take the middle of the points
|
|
if ( dist1 > dist2 - 1.0f && dist1 < dist2 + 1.0f ) {
|
|
dist = dist1;
|
|
start = ( p1area1 + p2area1 ) * 0.5f;
|
|
end = ( p1area2 + p2area2 ) * 0.5f;
|
|
}
|
|
else if (dist1 < dist2) {
|
|
dist = dist1;
|
|
start = p1area1;
|
|
end = p1area2;
|
|
}
|
|
else {
|
|
dist = dist2;
|
|
start = p2area1;
|
|
end = p2area2;
|
|
}
|
|
|
|
// get the length of the overlapping part of the edges of the two areas
|
|
length = (p2area2 - p1area2).Length();
|
|
|
|
if ( floorFace1->flags & FACE_FLOOR ) {
|
|
// if the vertical distance is smaller
|
|
if ( dist < floor_bestDist ||
|
|
// or the vertical distance is pretty much the same
|
|
// but the overlapping part of the edges is longer
|
|
(dist < floor_bestDist + 1.0f && length > floor_bestLength) ) {
|
|
floor_bestDist = dist;
|
|
floor_bestLength = length;
|
|
floor_foundReach = true;
|
|
floor_bestArea1FloorEdgeNum = edge1Num;
|
|
floor_bestStart = start;
|
|
floor_bestNormal = normal;
|
|
floor_bestEnd = end;
|
|
}
|
|
}
|
|
else {
|
|
// if the vertical distance is smaller
|
|
if ( dist < water_bestDist ||
|
|
//or the vertical distance is pretty much the same
|
|
//but the overlapping part of the edges is longer
|
|
(dist < water_bestDist + 1.0f && length > water_bestLength) ) {
|
|
water_bestDist = dist;
|
|
water_bestLength = length;
|
|
water_foundReach = true;
|
|
water_bestStart = start; // best start point in area1
|
|
water_bestNormal = normal; // normal is pointing into area2
|
|
water_bestEnd = end; // best point towards area2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// NOTE: swim reachabilities should already be filtered out
|
|
//
|
|
// Steps
|
|
//
|
|
// ---------
|
|
// | step height -> TFL_WALK
|
|
// --------|
|
|
//
|
|
// ---------
|
|
// ~~~~~~~~| step height and low water -> TFL_WALK
|
|
// --------|
|
|
//
|
|
// ~~~~~~~~~~~~~~~~~~
|
|
// ---------
|
|
// | step height and low water up to the step -> TFL_WALK
|
|
// --------|
|
|
//
|
|
// check for a step reachability
|
|
if ( floor_foundReach ) {
|
|
// if area2 is higher but lower than the maximum step height
|
|
// NOTE: floor_bestDist >= 0 also catches equal floor reachabilities
|
|
if ( floor_bestDist >= 0 && floor_bestDist < file->settings.maxStepHeight ) {
|
|
// create walk reachability from area1 to area2
|
|
walkReach = new idReachability_Walk();
|
|
walkReach->travelType = TFL_WALK;
|
|
walkReach->toAreaNum = area2num;
|
|
walkReach->fromAreaNum = area1num;
|
|
walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
|
|
walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
|
|
walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
|
|
walkReach->travelTime = 0;
|
|
if ( area2->flags & AREA_CROUCH ) {
|
|
walkReach->travelTime += file->settings.tt_startCrouching;
|
|
}
|
|
AddReachabilityToArea( walkReach, area1num );
|
|
return true;
|
|
}
|
|
}
|
|
//
|
|
// Water Jumps
|
|
//
|
|
// ---------
|
|
// |
|
|
// ~~~~~~~~|
|
|
// |
|
|
// | higher than step height and water up to waterjump height -> TFL_WATERJUMP
|
|
// --------|
|
|
//
|
|
// ~~~~~~~~~~~~~~~~~~
|
|
// ---------
|
|
// |
|
|
// |
|
|
// |
|
|
// | higher than step height and low water up to the step -> TFL_WATERJUMP
|
|
// --------|
|
|
//
|
|
// check for a waterjump reachability
|
|
if ( water_foundReach ) {
|
|
// get a test point a little bit towards area1
|
|
testPoint = water_bestEnd - INSIDEUNITS * water_bestNormal;
|
|
// go down the maximum waterjump height
|
|
testPoint[2] -= file->settings.maxWaterJumpHeight;
|
|
// if there IS water the sv_maxwaterjump height below the bestend point
|
|
if ( area1->flags & AREA_LIQUID ) {
|
|
// don't create rediculous water jump reachabilities from areas very far below the water surface
|
|
if ( water_bestDist < file->settings.maxWaterJumpHeight + 24 ) {
|
|
// water jumping from or towards a crouch only areas is not possible
|
|
if ( !(area1->flags & AREA_CROUCH) && !(area2->flags & AREA_CROUCH) ) {
|
|
// create water jump reachability from area1 to area2
|
|
waterJumpReach = new idReachability_WaterJump();
|
|
waterJumpReach->travelType = TFL_WATERJUMP;
|
|
waterJumpReach->toAreaNum = area2num;
|
|
waterJumpReach->fromAreaNum = area1num;
|
|
waterJumpReach->start = water_bestStart;
|
|
waterJumpReach->end = water_bestEnd + INSIDEUNITS_WATERJUMP * water_bestNormal;
|
|
waterJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
|
|
waterJumpReach->travelTime = file->settings.tt_waterJump;
|
|
AddReachabilityToArea( waterJumpReach, area1num );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Barrier Jumps
|
|
//
|
|
// ---------
|
|
// |
|
|
// |
|
|
// |
|
|
// | higher than max step height lower than max barrier height -> TFL_BARRIERJUMP
|
|
// --------|
|
|
//
|
|
// ---------
|
|
// |
|
|
// |
|
|
// |
|
|
// ~~~~~~~~| higher than max step height lower than max barrier height
|
|
// --------| and a thin layer of water in the area to jump from -> TFL_BARRIERJUMP
|
|
//
|
|
// check for a barrier jump reachability
|
|
if ( floor_foundReach ) {
|
|
//if area2 is higher but lower than the maximum barrier jump height
|
|
if ( floor_bestDist > 0 && floor_bestDist < file->settings.maxBarrierHeight ) {
|
|
//if no water in area1 or a very thin layer of water on the ground
|
|
if ( !water_foundReach || (floor_bestDist - water_bestDist < 16) ) {
|
|
// cannot perform a barrier jump towards or from a crouch area
|
|
if ( !(area1->flags & AREA_CROUCH) && !(area2->flags & AREA_CROUCH) ) {
|
|
// create barrier jump reachability from area1 to area2
|
|
barrierJumpReach = new idReachability_BarrierJump();
|
|
barrierJumpReach->travelType = TFL_BARRIERJUMP;
|
|
barrierJumpReach->toAreaNum = area2num;
|
|
barrierJumpReach->fromAreaNum = area1num;
|
|
barrierJumpReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
|
|
barrierJumpReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
|
|
barrierJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
|
|
barrierJumpReach->travelTime = file->settings.tt_barrierJump;
|
|
AddReachabilityToArea( barrierJumpReach, area1num );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Walk and Walk Off Ledge
|
|
//
|
|
// --------|
|
|
// | can walk or step back -> TFL_WALK
|
|
// ---------
|
|
//
|
|
// --------|
|
|
// |
|
|
// |
|
|
// |
|
|
// | cannot walk/step back -> TFL_WALKOFFLEDGE
|
|
// ---------
|
|
//
|
|
// --------|
|
|
// |
|
|
// |~~~~~~~~
|
|
// |
|
|
// | cannot step back but can waterjump back -> TFL_WALKOFFLEDGE
|
|
// --------- FIXME: create TFL_WALK reach??
|
|
//
|
|
// check for a walk or walk off ledge reachability
|
|
if ( floor_foundReach ) {
|
|
if ( floor_bestDist < 0 ) {
|
|
if ( floor_bestDist > -file->settings.maxStepHeight ) {
|
|
// create walk reachability from area1 to area2
|
|
walkReach = new idReachability_Walk();
|
|
walkReach->travelType = TFL_WALK;
|
|
walkReach->toAreaNum = area2num;
|
|
walkReach->fromAreaNum = area1num;
|
|
walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
|
|
walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
|
|
walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
|
|
walkReach->travelTime = 1;
|
|
AddReachabilityToArea( walkReach, area1num );
|
|
return true;
|
|
}
|
|
// if no maximum fall height set or less than the max
|
|
if ( !file->settings.maxFallHeight || idMath::Fabs(floor_bestDist) < file->settings.maxFallHeight ) {
|
|
// trace a bounding box vertically to check for solids
|
|
floor_bestEnd += INSIDEUNITS * floor_bestNormal;
|
|
start = floor_bestEnd;
|
|
start[2] = floor_bestStart[2];
|
|
end = floor_bestEnd;
|
|
end[2] += 4;
|
|
trace.areas = areas;
|
|
trace.maxAreas = sizeof(areas) / sizeof(int);
|
|
file->Trace( trace, start, end );
|
|
// if the trace didn't start in solid and nothing was hit
|
|
if ( trace.lastAreaNum && trace.fraction >= 1.0f ) {
|
|
// the trace end point must be in the goal area
|
|
if ( trace.lastAreaNum == area2num ) {
|
|
// don't create reachability if going through a cluster portal
|
|
for (i = 0; i < trace.numAreas; i++) {
|
|
if ( AreaIsClusterPortal( trace.areas[i] ) ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( i >= trace.numAreas ) {
|
|
// create a walk off ledge reachability from area1 to area2
|
|
walkOffLedgeReach = new idReachability_WalkOffLedge();
|
|
walkOffLedgeReach->travelType = TFL_WALKOFFLEDGE;
|
|
walkOffLedgeReach->toAreaNum = area2num;
|
|
walkOffLedgeReach->fromAreaNum = area1num;
|
|
walkOffLedgeReach->start = floor_bestStart;
|
|
walkOffLedgeReach->end = floor_bestEnd;
|
|
walkOffLedgeReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
|
|
walkOffLedgeReach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs(floor_bestDist) * 50 / file->settings.gravityValue;
|
|
AddReachabilityToArea( walkOffLedgeReach, area1num );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::Reachability_WalkOffLedge
|
|
================
|
|
*/
|
|
void idAASReach::Reachability_WalkOffLedge( int areaNum ) {
|
|
int i, j, faceNum, edgeNum, side, reachAreaNum, p, areas[10];
|
|
aasArea_t *area;
|
|
aasFace_t *face;
|
|
aasEdge_t *edge;
|
|
idPlane *plane;
|
|
idVec3 v1, v2, mid, dir, testEnd;
|
|
idReachability_WalkOffLedge *reach;
|
|
aasTrace_t trace;
|
|
|
|
if ( !AreaHasFloor( areaNum ) || CanSwimInArea( areaNum ) ) {
|
|
return;
|
|
}
|
|
|
|
area = &file->areas[areaNum];
|
|
|
|
for ( i = 0; i < area->numFaces; i++ ) {
|
|
faceNum = file->faceIndex[area->firstFace + i];
|
|
face = &file->faces[abs(faceNum)];
|
|
|
|
// face must be a floor face
|
|
if ( !(face->flags & FACE_FLOOR) ) {
|
|
continue;
|
|
}
|
|
|
|
for ( j = 0; j < face->numEdges; j++ ) {
|
|
|
|
edgeNum = file->edgeIndex[face->firstEdge + j];
|
|
edge = &file->edges[abs(edgeNum)];
|
|
|
|
//if ( !(edge->flags & EDGE_LEDGE) ) {
|
|
// continue;
|
|
//}
|
|
|
|
side = edgeNum < 0;
|
|
|
|
v1 = file->vertices[edge->vertexNum[side]];
|
|
v2 = file->vertices[edge->vertexNum[!side]];
|
|
|
|
plane = &file->planeList[face->planeNum ^ INTSIGNBITSET(faceNum) ];
|
|
|
|
// get the direction into the other area
|
|
dir = plane->Normal().Cross( v2 - v1 );
|
|
dir.Normalize();
|
|
|
|
mid = ( v1 + v2 ) * 0.5f;
|
|
testEnd = mid + INSIDEUNITS_WALKEND * dir;
|
|
testEnd[2] -= file->settings.maxFallHeight + 1.0f;
|
|
trace.areas = areas;
|
|
trace.maxAreas = sizeof(areas) / sizeof(int);
|
|
file->Trace( trace, mid, testEnd );
|
|
|
|
reachAreaNum = trace.lastAreaNum;
|
|
if ( !reachAreaNum || reachAreaNum == areaNum ) {
|
|
continue;
|
|
}
|
|
if ( idMath::Fabs( mid[2] - trace.endpos[2] ) > file->settings.maxFallHeight ) {
|
|
continue;
|
|
}
|
|
if ( !AreaHasFloor( reachAreaNum ) && !CanSwimInArea( reachAreaNum ) ) {
|
|
continue;
|
|
}
|
|
if ( ReachabilityExists( areaNum, reachAreaNum) ) {
|
|
continue;
|
|
}
|
|
// if not going through a cluster portal
|
|
for ( p = 0; p < trace.numAreas; p++ ) {
|
|
if ( AreaIsClusterPortal( trace.areas[p] ) ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( p < trace.numAreas ) {
|
|
continue;
|
|
}
|
|
|
|
reach = new idReachability_WalkOffLedge();
|
|
reach->travelType = TFL_WALKOFFLEDGE;
|
|
reach->toAreaNum = reachAreaNum;
|
|
reach->fromAreaNum = areaNum;
|
|
reach->start = mid;
|
|
reach->end = trace.endpos;
|
|
reach->edgeNum = abs( edgeNum );
|
|
reach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs(mid[2] - trace.endpos[2]) * 50 / file->settings.gravityValue;
|
|
AddReachabilityToArea( reach, areaNum );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::FlagReachableAreas
|
|
================
|
|
*/
|
|
void idAASReach::FlagReachableAreas( idAASFileLocal *file ) {
|
|
int i, numReachableAreas;
|
|
|
|
numReachableAreas = 0;
|
|
for ( i = 1; i < file->areas.Num(); i++ ) {
|
|
|
|
if ( ( file->areas[i].flags & ( AREA_FLOOR | AREA_LADDER ) ) ||
|
|
( file->areas[i].contents & AREACONTENTS_WATER ) ) {
|
|
file->areas[i].flags |= AREA_REACHABLE_WALK;
|
|
}
|
|
if ( file->GetSettings().allowFlyReachabilities ) {
|
|
file->areas[i].flags |= AREA_REACHABLE_FLY;
|
|
}
|
|
numReachableAreas++;
|
|
}
|
|
|
|
common->Printf( "%6d reachable areas\n", numReachableAreas );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAASReach::Build
|
|
================
|
|
*/
|
|
bool idAASReach::Build( const idMapFile *mapFile, idAASFileLocal *file ) {
|
|
int i, j, lastPercent, percent;
|
|
|
|
this->mapFile = mapFile;
|
|
this->file = file;
|
|
numReachabilities = 0;
|
|
|
|
common->Printf( "[Reachability]\n" );
|
|
|
|
// delete all existing reachabilities
|
|
file->DeleteReachabilities();
|
|
|
|
FlagReachableAreas( file );
|
|
|
|
for ( i = 1; i < file->areas.Num(); i++ ) {
|
|
if ( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) {
|
|
continue;
|
|
}
|
|
if ( file->GetSettings().allowSwimReachabilities ) {
|
|
Reachability_Swim( i );
|
|
}
|
|
Reachability_EqualFloorHeight( i );
|
|
}
|
|
|
|
lastPercent = -1;
|
|
for ( i = 1; i < file->areas.Num(); i++ ) {
|
|
|
|
if ( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) {
|
|
continue;
|
|
}
|
|
|
|
for ( j = 0; j < file->areas.Num(); j++ ) {
|
|
if ( i == j ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !( file->areas[j].flags & AREA_REACHABLE_WALK ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ReachabilityExists( i, j ) ) {
|
|
continue;
|
|
}
|
|
if ( Reachability_Step_Barrier_WaterJump_WalkOffLedge( i, j ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//Reachability_WalkOffLedge( i );
|
|
|
|
percent = 100 * i / file->areas.Num();
|
|
if ( percent > lastPercent ) {
|
|
common->Printf( "\r%6d%%", percent );
|
|
lastPercent = percent;
|
|
}
|
|
}
|
|
|
|
if ( file->GetSettings().allowFlyReachabilities ) {
|
|
for ( i = 1; i < file->areas.Num(); i++ ) {
|
|
Reachability_Fly( i );
|
|
}
|
|
}
|
|
|
|
file->LinkReversedReachability();
|
|
|
|
common->Printf( "\r%6d reachabilities\n", numReachabilities );
|
|
|
|
return true;
|
|
}
|