1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_generate.cpp
// Auto-generate a Navigation Mesh by sampling the current map
// Author: Michael S. Booth ([email protected]), 2003
#include "cbase.h"
#include "util_shared.h"
#include "nav_mesh.h"
#include "cs_nav_area.h"
#include "cs_nav_node.h"
#include "cs_nav_pathfind.h"
#include "viewport_panel_names.h"
enum { MAX_BLOCKED_AREAS = 256 };
static unsigned int blockedID[ MAX_BLOCKED_AREAS ];
static int blockedIDCount = 0;
static float lastMsgTime = 0.0f;
//ConVar nav_slope_limit( "nav_slope_limit", "0.7", FCVAR_GAMEDLL, "The ground unit normal's Z component must be greater than this for nav areas to be generated." );
ConVar nav_restart_after_analysis( "nav_restart_after_analysis", "1", FCVAR_GAMEDLL, "When nav nav_restart_after_analysis finishes, restart the server. Turning this off can cause crashes, but is useful for incremental generation." );
//--------------------------------------------------------------------------------------------------------------
/**
* Shortest path cost, paying attention to "blocked" areas
*/
class ApproachAreaCost
{
public:
// HPE_TODO[pmf]: check that these new parameters are okay to be ignored
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
{
// check if this area is "blocked"
for( int i=0; i<blockedIDCount; ++i )
{
if (area->GetID() == blockedID[i])
{
return -1.0f;
}
}
if (fromArea == NULL)
{
// first area in path, no cost
return 0.0f;
}
else
{
// compute distance traveled along path so far
float dist;
if (ladder)
{
dist = ladder->m_length;
}
else
{
dist = (area->GetCenter() - fromArea->GetCenter()).Length();
}
float cost = dist + fromArea->GetCostSoFar();
return cost;
}
}
};
/*
* Determine the set of "approach areas".
* An approach area is an area representing a place where players
* move into/out of our local neighborhood of areas.
* @todo Optimize by search from eye outward and modifying pathfinder to treat all links as bi-directional
*/
void CCSNavArea::ComputeApproachAreas( void )
{
m_approachCount = 0;
if (nav_quicksave.GetBool())
return;
// use the center of the nav area as the "view" point
Vector eye = m_center;
if (TheNavMesh->GetGroundHeight( eye, &eye.z ) == false)
return;
// approximate eye position
if (GetAttributes() & NAV_MESH_CROUCH)
eye.z += 0.9f * HalfHumanHeight;
else
eye.z += 0.9f * HumanHeight;
enum { MAX_PATH_LENGTH = 256 };
CNavArea *path[ MAX_PATH_LENGTH ];
ApproachAreaCost cost;
enum SearchType
{
FROM_EYE, ///< start search from our eyepoint outward to farArea
TO_EYE, ///< start search from farArea beack towards our eye
SEARCH_FINISHED
};
//
// In order to *completely* enumerate all of the approach areas, we
// need to search from our eyepoint outward, as well as from outwards
// towards our eyepoint
//
for( int searchType = FROM_EYE; searchType != SEARCH_FINISHED; ++searchType )
{
//
// In order to enumerate all of the approach areas, we need to
// run the algorithm many times, once for each "far away" area
// and keep the union of the approach area sets
//
int it;
for( it = 0; it < TheNavAreas.Count(); ++it )
{
CNavArea *farArea = TheNavAreas[ it ];
blockedIDCount = 0;
// skip the small areas
const float minSize = 200.0f; // 150
Extent extent;
farArea->GetExtent(&extent);
if (extent.SizeX() < minSize || extent.SizeY() < minSize)
{
continue;
}
// if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak
if (farArea->IsVisible( eye ))
{
continue;
}
//
// Keep building paths to farArea and blocking them off until we
// cant path there any more.
// As areas are blocked off, all exits will be enumerated.
//
while( m_approachCount < MAX_APPROACH_AREAS )
{
CNavArea *from, *to;
if (searchType == FROM_EYE)
{
// find another path *to* 'farArea'
// we must pathfind from us in order to pick up one-way paths OUT OF our area
from = this;
to = farArea;
}
else // TO_EYE
{
// find another path *from* 'farArea'
// we must pathfind to us in order to pick up one-way paths INTO our area
from = farArea;
to = this;
}
// build the actual path
if (NavAreaBuildPath( from, to, NULL, cost ) == false)
{
break;
}
// find number of areas on path
int count = 0;
CNavArea *area;
for( area = to; area; area = area->GetParent() )
{
++count;
}
if (count > MAX_PATH_LENGTH)
{
count = MAX_PATH_LENGTH;
}
// if the path is only two areas long, there can be no approach points
if (count <= 2)
{
break;
}
// build path starting from eye
int i = 0;
if (searchType == FROM_EYE)
{
for( area = to; i < count && area; area = area->GetParent() )
{
path[ count-i-1 ] = area;
++i;
}
}
else // TO_EYE
{
for( area = to; i < count && area; area = area->GetParent() )
{
path[ i++ ] = area;
}
}
// traverse path to find first area we cannot see (skip the first area)
for( i=1; i<count; ++i )
{
// if we see this area, continue on
if (path[i]->IsVisible( eye ))
{
continue;
}
// we can't see this area - mark this area as "blocked" and unusable by subsequent approach paths
if (blockedIDCount == MAX_BLOCKED_AREAS)
{
Msg( "Overflow computing approach areas for area #%d.\n", GetID());
return;
}
// if the area to be blocked is actually farArea, block the one just prior
// (blocking farArea will cause all subsequent pathfinds to fail)
int block = (path[i] == farArea) ? i-1 : i;
// dont block the start area, or all subsequence pathfinds will fail
if (block == 0)
{
continue;
}
blockedID[ blockedIDCount++ ] = path[ block ]->GetID();
// store new approach area if not already in set
int a;
for( a=0; a<m_approachCount; ++a )
{
if (m_approach[a].here.area == path[block-1])
{
break;
}
}
if (a == m_approachCount)
{
m_approach[ m_approachCount ].prev.area = (block >= 2) ? path[block-2] : NULL;
m_approach[ m_approachCount ].here.area = path[block-1];
m_approach[ m_approachCount ].prevToHereHow = path[block-1]->GetParentHow();
m_approach[ m_approachCount ].next.area = path[block];
m_approach[ m_approachCount ].hereToNextHow = path[block]->GetParentHow();
++m_approachCount;
}
// we are done with this path
break;
}
}
}
}
}
|