forked from ValveSoftware/source-sdk-2013
-
Notifications
You must be signed in to change notification settings - Fork 169
Expand file tree
/
Copy pathnav_generate.cpp
More file actions
4962 lines (4159 loc) · 150 KB
/
Copy pathnav_generate.cpp
File metadata and controls
4962 lines (4159 loc) · 150 KB
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//========= 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 (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "util_shared.h"
#include "nav_mesh.h"
#include "nav_node.h"
#include "nav_pathfind.h"
#include "viewport_panel_names.h"
//#include "terror/TerrorShared.h"
#include "fmtstr.h"
#ifdef TERROR
#include "func_simpleladder.h"
#endif
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
enum { MAX_BLOCKED_AREAS = 256 };
static unsigned int blockedID[ MAX_BLOCKED_AREAS ];
static int blockedIDCount = 0;
static float lastMsgTime = 0.0f;
bool TraceAdjacentNode( int depth, const Vector& start, const Vector& end, trace_t *trace, float zLimit = DeathDrop );
bool StayOnFloor( trace_t *trace, float zLimit = DeathDrop );
ConVar nav_slope_limit( "nav_slope_limit", "0.7", FCVAR_CHEAT, "The ground unit normal's Z component must be greater than this for nav areas to be generated." );
ConVar nav_slope_tolerance( "nav_slope_tolerance", "0.1", FCVAR_CHEAT, "The ground unit normal's Z component must be this close to the nav area's Z component to be generated." );
ConVar nav_displacement_test( "nav_displacement_test", "10000", FCVAR_CHEAT, "Checks for nodes embedded in displacements (useful for in-development maps)" );
ConVar nav_generate_fencetops( "nav_generate_fencetops", "1", FCVAR_CHEAT, "Autogenerate nav areas on fence and obstacle tops" );
ConVar nav_generate_fixup_jump_areas( "nav_generate_fixup_jump_areas", "1", FCVAR_CHEAT, "Convert obsolete jump areas into 2-way connections" );
ConVar nav_generate_jump_connections( "nav_generate_jump_connections", "1", FCVAR_CHEAT, "If disabled, don't generate jump connections from jump areas" );
ConVar nav_generate_incremental_range( "nav_generate_incremental_range", "2000", FCVAR_CHEAT );
ConVar nav_generate_incremental_tolerance( "nav_generate_incremental_tolerance", "0", FCVAR_CHEAT, "Z tolerance for adding new nav areas." );
ConVar nav_area_max_size( "nav_area_max_size", "50", FCVAR_CHEAT, "Max area size created in nav generation" );
// Common bounding box for traces
Vector NavTraceMins( -0.45, -0.45, 0 );
Vector NavTraceMaxs( 0.45, 0.45, HumanCrouchHeight );
bool FindGroundForNode( Vector *pos, Vector *normal ); // find a ground Z for pos that is clear for NavTraceMins -> NavTraceMaxs
const float MaxTraversableHeight = StepHeight; // max internal obstacle height that can occur between nav nodes and safely disregarded
const float MinObstacleAreaWidth = 10.0f; // min width of a nav area we will generate on top of an obstacle
//--------------------------------------------------------------------------------------------------------------
/**
* Shortest path cost, paying attention to "blocked" areas
*/
class ApproachAreaCost
{
public:
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator )
{
// 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;
}
}
};
//--------------------------------------------------------------------------------------------------------------
/**
* Start at given position and find first area in given direction
*/
inline CNavArea *findFirstAreaInDirection( const Vector *start, NavDirType dir, float range, float beneathLimit, CBaseEntity *traceIgnore = NULL, Vector *closePos = NULL )
{
CNavArea *area = NULL;
Vector pos = *start;
int end = (int)((range / GenerationStepSize) + 0.5f);
for( int i=1; i<=end; i++ )
{
AddDirectionVector( &pos, dir, GenerationStepSize );
// make sure we dont look thru the wall
trace_t result;
UTIL_TraceHull( *start, pos, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), traceIgnore, COLLISION_GROUP_NONE, &result );
if (result.fraction < 1.0f)
break;
area = TheNavMesh->GetNavArea( pos, beneathLimit );
if (area)
{
if (closePos)
{
closePos->x = pos.x;
closePos->y = pos.y;
closePos->z = area->GetZ( pos.x, pos.y );
}
break;
}
}
return area;
}
//--------------------------------------------------------------------------------------------------------------
/**
* For each ladder in the map, create a navigation representation of it.
*/
void CNavMesh::BuildLadders( void )
{
// remove any left-over ladders
DestroyLadders();
#ifdef TERROR
CFuncSimpleLadder *ladder = NULL;
while( (ladder = dynamic_cast< CFuncSimpleLadder * >(gEntList.FindEntityByClassname( ladder, "func_simpleladder" ))) != NULL )
{
Vector mins, maxs;
ladder->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs );
CreateLadder( mins, maxs, 0.0f );
}
#endif
}
//--------------------------------------------------------------------------------------------------------------
/**
* Create a navigation representation of a ladder.
*/
void CNavMesh::CreateLadder( const Vector& absMin, const Vector& absMax, float maxHeightAboveTopArea )
{
CNavLadder *ladder = new CNavLadder;
// compute top & bottom of ladder
ladder->m_top.x = (absMin.x + absMax.x) / 2.0f;
ladder->m_top.y = (absMin.y + absMax.y) / 2.0f;
ladder->m_top.z = absMax.z;
ladder->m_bottom.x = ladder->m_top.x;
ladder->m_bottom.y = ladder->m_top.y;
ladder->m_bottom.z = absMin.z;
// determine facing - assumes "normal" runged ladder
float xSize = absMax.x - absMin.x;
float ySize = absMax.y - absMin.y;
trace_t result;
if (xSize > ySize)
{
// ladder is facing north or south - determine which way
// "pull in" traceline from bottom and top in case ladder abuts floor and/or ceiling
Vector from = ladder->m_bottom + Vector( 0.0f, GenerationStepSize, GenerationStepSize/2 );
Vector to = ladder->m_top + Vector( 0.0f, GenerationStepSize, -GenerationStepSize/2 );
UTIL_TraceLine( from, to, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
if (result.fraction != 1.0f || result.startsolid)
ladder->SetDir( NORTH );
else
ladder->SetDir( SOUTH );
ladder->m_width = xSize;
}
else
{
// ladder is facing east or west - determine which way
Vector from = ladder->m_bottom + Vector( GenerationStepSize, 0.0f, GenerationStepSize/2 );
Vector to = ladder->m_top + Vector( GenerationStepSize, 0.0f, -GenerationStepSize/2 );
UTIL_TraceLine( from, to, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
if (result.fraction != 1.0f || result.startsolid)
ladder->SetDir( WEST );
else
ladder->SetDir( EAST );
ladder->m_width = ySize;
}
// adjust top and bottom of ladder to make sure they are reachable
// (cs_office has a crate right in front of the base of a ladder)
Vector along = ladder->m_top - ladder->m_bottom;
float length = along.NormalizeInPlace();
Vector on, out;
const float minLadderClearance = 32.0f;
// adjust bottom to bypass blockages
const float inc = 10.0f;
float t;
for( t = 0.0f; t <= length; t += inc )
{
on = ladder->m_bottom + t * along;
out = on + ladder->GetNormal() * minLadderClearance;
UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
if (result.fraction == 1.0f && !result.startsolid)
{
// found viable ladder bottom
ladder->m_bottom = on;
break;
}
}
// adjust top to bypass blockages
for( t = 0.0f; t <= length; t += inc )
{
on = ladder->m_top - t * along;
out = on + ladder->GetNormal() * minLadderClearance;
UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
if (result.fraction == 1.0f && !result.startsolid)
{
// found viable ladder top
ladder->m_top = on;
break;
}
}
ladder->m_length = (ladder->m_top - ladder->m_bottom).Length();
ladder->SetDir( ladder->GetDir() ); // now that we've adjusted the top and bottom, re-check the normal
ladder->m_bottomArea = NULL;
ladder->m_topForwardArea = NULL;
ladder->m_topLeftArea = NULL;
ladder->m_topRightArea = NULL;
ladder->m_topBehindArea = NULL;
ladder->ConnectGeneratedLadder( maxHeightAboveTopArea );
// add ladder to global list
m_ladders.AddToTail( ladder );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Create a navigation representation of a ladder.
*/
void CNavMesh::CreateLadder( const Vector &top, const Vector &bottom, float width, const Vector2D &ladderDir, float maxHeightAboveTopArea )
{
CNavLadder *ladder = new CNavLadder;
ladder->m_top = top;
ladder->m_bottom = bottom;
ladder->m_width = width;
if ( fabs( ladderDir.x ) > fabs( ladderDir.y ) )
{
if ( ladderDir.x > 0.0f )
{
ladder->SetDir( EAST );
}
else
{
ladder->SetDir( WEST );
}
}
else
{
if ( ladderDir.y > 0.0f )
{
ladder->SetDir( SOUTH );
}
else
{
ladder->SetDir( NORTH );
}
}
// adjust top and bottom of ladder to make sure they are reachable
// (cs_office has a crate right in front of the base of a ladder)
Vector along = ladder->m_top - ladder->m_bottom;
float length = along.NormalizeInPlace();
Vector on, out;
const float minLadderClearance = 32.0f;
// adjust bottom to bypass blockages
const float inc = 10.0f;
float t;
trace_t result;
for( t = 0.0f; t <= length; t += inc )
{
on = ladder->m_bottom + t * along;
out = on + ladder->GetNormal() * minLadderClearance;
UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
if (result.fraction == 1.0f && !result.startsolid)
{
// found viable ladder bottom
ladder->m_bottom = on;
break;
}
}
// adjust top to bypass blockages
for( t = 0.0f; t <= length; t += inc )
{
on = ladder->m_top - t * along;
out = on + ladder->GetNormal() * minLadderClearance;
UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
if (result.fraction == 1.0f && !result.startsolid)
{
// found viable ladder top
ladder->m_top = on;
break;
}
}
ladder->m_length = (ladder->m_top - ladder->m_bottom).Length();
ladder->SetDir( ladder->GetDir() ); // now that we've adjusted the top and bottom, re-check the normal
ladder->m_bottomArea = NULL;
ladder->m_topForwardArea = NULL;
ladder->m_topLeftArea = NULL;
ladder->m_topRightArea = NULL;
ladder->m_topBehindArea = NULL;
ladder->ConnectGeneratedLadder( maxHeightAboveTopArea );
// add ladder to global list
m_ladders.AddToTail( ladder );
}
//--------------------------------------------------------------------------------------------------------------
void CNavLadder::ConnectGeneratedLadder( float maxHeightAboveTopArea )
{
const float nearLadderRange = 75.0f; // 50
//
// Find naviagtion area at bottom of ladder
//
// get approximate postion of player on ladder
Vector center = m_bottom + Vector( 0, 0, GenerationStepSize );
AddDirectionVector( ¢er, m_dir, HalfHumanWidth );
m_bottomArea = TheNavMesh->GetNearestNavArea( center, true );
if (!m_bottomArea)
{
DevMsg( "ERROR: Unconnected ladder bottom at ( %g, %g, %g )\n", m_bottom.x, m_bottom.y, m_bottom.z );
}
else
{
// store reference to ladder in the area
m_bottomArea->AddLadderUp( this );
}
//
// Find adjacent navigation areas at the top of the ladder
//
// get approximate postion of player on ladder
center = m_top + Vector( 0, 0, GenerationStepSize );
AddDirectionVector( ¢er, m_dir, HalfHumanWidth );
float beneathLimit = MIN( 120.0f, m_top.z - m_bottom.z + HalfHumanWidth );
// find "ahead" area
m_topForwardArea = findFirstAreaInDirection( ¢er, OppositeDirection( m_dir ), nearLadderRange, beneathLimit, NULL );
if (m_topForwardArea == m_bottomArea)
m_topForwardArea = NULL;
// find "left" area
m_topLeftArea = findFirstAreaInDirection( ¢er, DirectionLeft( m_dir ), nearLadderRange, beneathLimit, NULL );
if (m_topLeftArea == m_bottomArea)
m_topLeftArea = NULL;
// find "right" area
m_topRightArea = findFirstAreaInDirection( ¢er, DirectionRight( m_dir ), nearLadderRange, beneathLimit, NULL );
if (m_topRightArea == m_bottomArea)
m_topRightArea = NULL;
// find "behind" area - must look farther, since ladder is against the wall away from this area
m_topBehindArea = findFirstAreaInDirection( ¢er, m_dir, 2.0f*nearLadderRange, beneathLimit, NULL );
if (m_topBehindArea == m_bottomArea)
m_topBehindArea = NULL;
// can't include behind area, since it is not used when going up a ladder
if (!m_topForwardArea && !m_topLeftArea && !m_topRightArea)
DevMsg( "ERROR: Unconnected ladder top at ( %g, %g, %g )\n", m_top.x, m_top.y, m_top.z );
// store reference to ladder in the area(s)
if (m_topForwardArea)
m_topForwardArea->AddLadderDown( this );
if (m_topLeftArea)
m_topLeftArea->AddLadderDown( this );
if (m_topRightArea)
m_topRightArea->AddLadderDown( this );
if (m_topBehindArea)
{
m_topBehindArea->AddLadderDown( this );
Disconnect( m_topBehindArea );
}
// adjust top of ladder to highest connected area
float topZ = m_bottom.z + 5.0f;
bool topAdjusted = false;
CNavArea *topAreaList[4];
topAreaList[0] = m_topForwardArea;
topAreaList[1] = m_topLeftArea;
topAreaList[2] = m_topRightArea;
topAreaList[3] = m_topBehindArea;
for( int a=0; a<4; ++a )
{
CNavArea *topArea = topAreaList[a];
if (topArea == NULL)
continue;
Vector close;
topArea->GetClosestPointOnArea( m_top, &close );
if (topZ < close.z)
{
topZ = close.z;
topAdjusted = true;
}
}
if (topAdjusted)
{
if ( maxHeightAboveTopArea > 0.0f )
{
m_top.z = MIN( topZ + maxHeightAboveTopArea, m_top.z );
}
else
{
m_top.z = topZ; // not manually specifying a top, so snap exactly
}
}
//
// Determine whether this ladder is "dangling" or not
// "Dangling" ladders are too high to go up
//
if (m_bottomArea)
{
Vector bottomSpot;
m_bottomArea->GetClosestPointOnArea( m_bottom, &bottomSpot );
if (m_bottom.z - bottomSpot.z > HumanHeight)
{
m_bottomArea->Disconnect( this );
}
}
}
//--------------------------------------------------------------------------------------------------------
class JumpConnector
{
public:
bool operator()( CNavArea *jumpArea )
{
if ( !nav_generate_jump_connections.GetBool() )
{
return true;
}
if ( !(jumpArea->GetAttributes() & NAV_MESH_JUMP) )
{
return true;
}
for ( int i=0; i<NUM_DIRECTIONS; ++i )
{
NavDirType incomingDir = (NavDirType)i;
NavDirType outgoingDir = OppositeDirection( incomingDir );
const NavConnectVector *incoming = jumpArea->GetIncomingConnections( incomingDir );
const NavConnectVector *from = jumpArea->GetAdjacentAreas( incomingDir );
const NavConnectVector *dest = jumpArea->GetAdjacentAreas( outgoingDir );
TryToConnect( jumpArea, incoming, dest, outgoingDir );
TryToConnect( jumpArea, from, dest, outgoingDir );
}
return true;
}
private:
struct Connection
{
CNavArea *source;
CNavArea *dest;
NavDirType direction;
};
void TryToConnect( CNavArea *jumpArea, const NavConnectVector *source, const NavConnectVector *dest, NavDirType outgoingDir )
{
FOR_EACH_VEC( (*source), sourceIt )
{
CNavArea *sourceArea = const_cast< CNavArea * >( (*source)[ sourceIt ].area );
if ( !sourceArea->IsConnected( jumpArea, outgoingDir ) )
{
continue;
}
if ( sourceArea->HasAttributes( NAV_MESH_JUMP ) )
{
NavDirType incomingDir = OppositeDirection( outgoingDir );
const NavConnectVector *in1 = sourceArea->GetIncomingConnections( incomingDir );
const NavConnectVector *in2 = sourceArea->GetAdjacentAreas( incomingDir );
TryToConnect( jumpArea, in1, dest, outgoingDir );
TryToConnect( jumpArea, in2, dest, outgoingDir );
continue;
}
TryToConnect( jumpArea, sourceArea, dest, outgoingDir );
}
}
void TryToConnect( CNavArea *jumpArea, CNavArea *sourceArea, const NavConnectVector *dest, NavDirType outgoingDir )
{
FOR_EACH_VEC( (*dest), destIt )
{
CNavArea *destArea = const_cast< CNavArea * >( (*dest)[ destIt ].area );
if ( destArea->HasAttributes( NAV_MESH_JUMP ) )
{
// Don't connect areas across 2 jump areas. This means we'll have some missing links due to sampling errors.
// This is preferable to generating incorrect links across multiple jump areas, which is far more common.
continue;
}
Vector center;
float halfWidth;
sourceArea->ComputePortal( destArea, outgoingDir, ¢er, &halfWidth );
// Don't create corner-to-corner connections
if ( halfWidth <= 0.0f )
{
continue;
}
Vector dir( vec3_origin );
AddDirectionVector( &dir, outgoingDir, 5.0f );
if ( halfWidth > 0.0f )
{
Vector sourcePos, destPos;
sourceArea->GetClosestPointOnArea( center, &sourcePos );
destArea->GetClosestPointOnArea( center, &destPos );
// No jumping up from stairs.
if ( sourceArea->HasAttributes( NAV_MESH_STAIRS ) && sourcePos.z + StepHeight < destPos.z )
{
continue;
}
if ( (sourcePos-destPos).AsVector2D().IsLengthLessThan( GenerationStepSize * 3 ) )
{
sourceArea->ConnectTo( destArea, outgoingDir );
// DevMsg( "Connected %d->%d via %d (len %f)\n",
// sourceArea->GetID(), destArea->GetID(), jumpArea->GetID(), sourcePos.DistTo( destPos ) );
}
}
}
}
};
//--------------------------------------------------------------------------------------------------------------
void CNavMesh::MarkPlayerClipAreas( void )
{
#ifdef TERROR
FOR_EACH_VEC( TheNavAreas, it )
{
TerrorNavArea *area = static_cast< TerrorNavArea * >(TheNavAreas[it]);
// Trace upward a bit from our center point just colliding wtih PLAYERCLIP to see if we're in one, if we are, mark us as accordingly.
trace_t trace;
Vector start = area->GetCenter() + Vector(0.0f, 0.0f, 16.0f );
Vector end = area->GetCenter() + Vector(0.0f, 0.0f, 32.0f );
UTIL_TraceHull( start, end, Vector(0,0,0), Vector(0,0,0), CONTENTS_PLAYERCLIP, NULL, &trace);
if ( trace.fraction < 1.0 )
{
area->SetAttributes( area->GetAttributes() | TerrorNavArea::NAV_PLAYERCLIP );
}
}
#endif
}
//--------------------------------------------------------------------------------------------------------------
/**
* Mark all areas that require a jump to get through them.
* This currently relies on jump areas having extreme slope.
*/
void CNavMesh::MarkJumpAreas( void )
{
FOR_EACH_VEC( TheNavAreas, it )
{
CNavArea *area = TheNavAreas[ it ];
if ( !area->HasNodes() )
continue;
Vector normal, otherNormal;
area->ComputeNormal( &normal );
area->ComputeNormal( &otherNormal, true );
float lowestNormalZ = MIN( normal.z, otherNormal.z );
if (lowestNormalZ < nav_slope_limit.GetFloat())
{
// The area is a jump area, and we don't merge jump areas together
area->SetAttributes( area->GetAttributes() | NAV_MESH_JUMP | NAV_MESH_NO_MERGE );
}
else if ( lowestNormalZ < nav_slope_limit.GetFloat() + nav_slope_tolerance.GetFloat() )
{
Vector testPos = area->GetCenter();
testPos.z += HalfHumanHeight;
Vector groundNormal;
float dummy;
if ( GetSimpleGroundHeight( testPos, &dummy, &groundNormal ) )
{
// If the ground normal is divergent from the area's normal, mark it as a jump area - it's not
// really representative of the ground.
float deltaNormalZ = fabs( groundNormal.z - lowestNormalZ );
if ( deltaNormalZ > nav_slope_tolerance.GetFloat() )
{
// The area is a jump area, and we don't merge jump areas together
area->SetAttributes( area->GetAttributes() | NAV_MESH_JUMP | NAV_MESH_NO_MERGE );
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Remove all areas marked as jump areas and connect the areas connecting to them
*
*/
void CNavMesh::StichAndRemoveJumpAreas( void )
{
// Now, go through and remove jump areas, connecting areas to make up for it
JumpConnector connector;
ForAllAreas( connector );
RemoveJumpAreas();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Adjusts obstacle start and end distances such that obstacle width (end-start) is not less than MinObstacleAreaWidth,
* and end distance is not greater than maxAllowedDist
*/
void AdjustObstacleDistances( float *pObstacleStartDist, float *pObstacleEndDist, float maxAllowedDist )
{
float obstacleWidth = *pObstacleEndDist - *pObstacleStartDist;
// is the obstacle width too narrow?
if ( obstacleWidth < MinObstacleAreaWidth )
{
float halfDelta = ( MinObstacleAreaWidth - obstacleWidth ) /2;
// move start so it's half of min width from center, but no less than zero
*pObstacleStartDist = MAX( *pObstacleStartDist - halfDelta, 0 );
// move end so it's min width from start
*pObstacleEndDist = *pObstacleStartDist + MinObstacleAreaWidth;
// if this pushes the end past max allowed distance, pull start and end back so that end is within allowed distance
if ( *pObstacleEndDist > maxAllowedDist )
{
float delta = *pObstacleEndDist - maxAllowedDist;
*pObstacleStartDist -= delta;
*pObstacleEndDist -= delta;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Makes sure tall, slim obstacles like fencetops, railings and narrow walls have nav areas placed on top of them
* to allow climbing & traversal
*/
void CNavMesh::HandleObstacleTopAreas( void )
{
if ( !nav_generate_fencetops.GetBool() )
return;
// For any 1x1 area that is internally blocked by an obstacle, raise it on top of the obstacle and size to fit.
RaiseAreasWithInternalObstacles();
// Create new areas as required
CreateObstacleTopAreas();
// It's possible for obstacle top areas to wind up overlapping one another, fix any such cases
RemoveOverlappingObstacleTopAreas();
}
//--------------------------------------------------------------------------------------------------------------
/**
* For any nav area that has internal obstacles between its corners of greater than traversable height,
* raise that nav area to sit at the top of the obstacle, and shrink it to fit the obstacle. Such nav
* areas are already restricted to be 1x1 so this will only be performed on areas that are already small.
*/
void CNavMesh::RaiseAreasWithInternalObstacles()
{
// obstacle areas next to stairs are bad - delete them
CUtlVector< CNavArea * > areasToDelete;
FOR_EACH_VEC( TheNavAreas, it )
{
CNavArea *area = TheNavAreas[ it ];
// any nav area with internal obstacles will be 1x1 (width and height = GenerationStepSize), so
// only need to consider areas of that size
if ( ( area->GetSizeX() != GenerationStepSize ) || (area->GetSizeY() != GenerationStepSize ) )
continue;
float obstacleZ[2] = { -FLT_MAX, -FLT_MAX };
float obstacleZMax = -FLT_MAX;
NavDirType obstacleDir = NORTH;
float obstacleStartDist = GenerationStepSize;
float obstacleEndDist = 0;
bool isStairNeighbor = false;
// Look at all 4 directions and determine if there are obstacles in that direction. Find the direction with the highest obstacle, if any.
for ( int i = 0; i < NUM_DIRECTIONS; i++ )
{
NavDirType dir = (NavDirType) i;
// For this direction, look at the left and right edges of the nav area relative to this direction and determined if they are both blocked
// by obstacles. We only consider this area obstructed if both edges are blocked (e.g. fence runs all the way through it).
NavCornerType corner[2];
int iEdgesBlocked = 0;
corner[0] = (NavCornerType) ( ( i + 3 ) % NUM_CORNERS ); // lower left-hand corner relative to current direction
corner[1] = (NavCornerType) ( ( i + 2 ) % NUM_CORNERS ); // lower right-hand corner relative to current direction
float obstacleZThisDir[2] = { -FLT_MAX, -FLT_MAX }; // absolute Z pos of obstacle for left and right edge in this direction
float obstacleStartDistThisDir = GenerationStepSize; // closest obstacle start distance in this direction
float obstacleEndDistThisDir = 0; // farthest obstacle end distance in this direction
// consider left and right edges of nav area relative to current direction
for ( int iEdge = 0; iEdge < 2; iEdge++ )
{
NavCornerType cornerType = corner[iEdge];
CNavNode *nodeFrom = area->m_node[cornerType];
if ( nodeFrom )
{
// is there an obstacle going from corner to corner along this edge?
float obstacleHeight = nodeFrom->m_obstacleHeight[dir];
if ( obstacleHeight > MaxTraversableHeight )
{
// yes, this edge is blocked
iEdgesBlocked++;
// keep track of obstacle height and start and end distance for this edge
float obstacleZ = nodeFrom->GetPosition()->z + obstacleHeight;
if ( obstacleZ > obstacleZThisDir[iEdge] )
{
obstacleZThisDir[iEdge] = obstacleZ;
}
obstacleStartDistThisDir = MIN( nodeFrom->m_obstacleStartDist[dir], obstacleStartDistThisDir );
obstacleEndDistThisDir = MAX( nodeFrom->m_obstacleEndDist[dir], obstacleEndDistThisDir );
}
}
}
int BlockedEdgeCutoff = 2;
const NavConnectVector *connections = area->GetAdjacentAreas( dir );
if ( connections )
{
for ( int conIndex=0; conIndex<connections->Count(); ++conIndex )
{
const CNavArea *connectedArea = connections->Element( conIndex ).area;
if ( connectedArea && connectedArea->HasAttributes( NAV_MESH_STAIRS ) )
{
isStairNeighbor = true;
BlockedEdgeCutoff = 1; // one blocked edge is already too much when we're next to a stair
break;
}
}
}
// are both edged blocked in this direction, and is the obstacle height in this direction the tallest we've seen?
if ( (iEdgesBlocked >= BlockedEdgeCutoff ) && ( MAX( obstacleZThisDir[0], obstacleZThisDir[1] ) ) > obstacleZMax )
{
// this is the tallest obstacle we've encountered so far, remember its details
obstacleZ[0] = obstacleZThisDir[0];
obstacleZ[1] = obstacleZThisDir[1];
obstacleZMax = MAX( obstacleZ[0], obstacleZ[1] );
obstacleDir = dir;
obstacleStartDist = obstacleStartDistThisDir;
obstacleEndDist = obstacleStartDistThisDir;
}
}
if ( isStairNeighbor && obstacleZMax > -FLT_MAX )
{
areasToDelete.AddToTail( area );
continue;
}
// if we found an obstacle, raise this nav areas and size it to fit
if ( obstacleZMax > -FLT_MAX )
{
// enforce minimum obstacle width so we don't shrink to become a teensy nav area
AdjustObstacleDistances( &obstacleStartDist, &obstacleEndDist, GenerationStepSize );
Assert( obstacleEndDist - obstacleStartDist >= MinObstacleAreaWidth );
// get current corner coords
Vector corner[4];
for ( int i = NORTH_WEST; i < NUM_CORNERS; i++ )
{
corner[i] = area->GetCorner( (NavCornerType) i );
}
// adjust our size to fit the obstacle
switch ( obstacleDir )
{
case NORTH:
corner[NORTH_WEST].y = corner[SOUTH_WEST].y - obstacleEndDist;
corner[NORTH_EAST].y = corner[SOUTH_EAST].y - obstacleEndDist;
corner[SOUTH_WEST].y -= obstacleStartDist;
corner[SOUTH_EAST].y -= obstacleStartDist;
break;
case SOUTH:
corner[SOUTH_WEST].y = corner[NORTH_WEST].y + obstacleEndDist;
corner[SOUTH_EAST].y = corner[NORTH_EAST].y + obstacleEndDist;
corner[NORTH_WEST].y += obstacleStartDist;
corner[NORTH_EAST].y += obstacleStartDist;
::V_swap( obstacleZ[0], obstacleZ[1] ); // swap left and right Z heights for obstacle so we can run common code below
break;
case EAST:
corner[NORTH_EAST].x = corner[NORTH_WEST].x + obstacleEndDist;
corner[SOUTH_EAST].x = corner[SOUTH_WEST].x + obstacleEndDist;
corner[NORTH_WEST].x += obstacleStartDist;
corner[SOUTH_WEST].x += obstacleStartDist;
case WEST:
corner[NORTH_WEST].x = corner[NORTH_EAST].x - obstacleEndDist;
corner[SOUTH_WEST].x = corner[SOUTH_EAST].x - obstacleEndDist;
corner[NORTH_EAST].x -= obstacleStartDist;
corner[SOUTH_EAST].x -= obstacleStartDist;
::V_swap( obstacleZ[0], obstacleZ[1] ); // swap left and right Z heights for obstacle so we can run common code below
break;
}
// adjust Z positions to be z pos of obstacle top
corner[NORTH_WEST].z = obstacleZ[0];
corner[NORTH_EAST].z = obstacleZ[1];
corner[SOUTH_EAST].z = obstacleZ[1];
corner[SOUTH_WEST].z = obstacleZ[0];
// move the area
RemoveNavArea( area );
area->Build( corner[NORTH_WEST], corner[NORTH_EAST], corner[SOUTH_EAST], corner[SOUTH_WEST] );
Assert( !area->IsDegenerate() );
AddNavArea( area );
// remove side-to-side connections if there are any so AI does try to do things like run along fencetops
area->RemoveOrthogonalConnections( obstacleDir );
area->SetAttributes( area->GetAttributes() | NAV_MESH_NO_MERGE | NAV_MESH_OBSTACLE_TOP );
area->SetAttributes( area->GetAttributes() & ( ~NAV_MESH_JUMP ) );
// clear out the nodes associated with this area's corners -- corners don't match the node positions any more
for ( int i = 0; i < NUM_CORNERS; i++ )
{
area->m_node[i] = NULL;
}
}
}
for ( int i=0; i<areasToDelete.Count(); ++i )
{
TheNavAreas.FindAndRemove( areasToDelete[i] );
DestroyArea( areasToDelete[i] );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* For any two nav areas that have an obstacle between them such as a fence, railing or small wall, creates a new
* nav area on top of the obstacle and connects it between the areas
*/
void CNavMesh::CreateObstacleTopAreas()
{
// enumerate all areas
FOR_EACH_VEC( TheNavAreas, it )
{
CNavArea *area = TheNavAreas[ it ];
// if this is a jump node (which will ultimately get removed) or is an obstacle top, ignore it
if ( area->GetAttributes() & ( NAV_MESH_JUMP | NAV_MESH_OBSTACLE_TOP ) )
return;
// Look in all directions
for ( int i = NORTH; i < NUM_DIRECTIONS; i++ )
{
NavDirType dir = (NavDirType) i;
// Look at all adjacent areas in this direction
int iConnections = area->GetAdjacentCount( dir );
for ( int j = 0; j < iConnections; j++ )
{
CNavArea *areaOther = area->GetAdjacentArea( dir, j );
// if this is a jump node (which will ultimately get removed) or is an obstacle top, ignore it
if ( areaOther->GetAttributes() & ( NAV_MESH_JUMP | NAV_MESH_OBSTACLE_TOP ) )
continue;
// create an obstacle top if there is a one-node separation between the areas and there is an intra-node obstacle within that separation
if ( !CreateObstacleTopAreaIfNecessary( area, areaOther, dir, false ) )
{
// if not, create an obstacle top if there is a two-node separation between the areas and the intermediate node is significantly
// higher than the two areas, which means there's some geometry there that causes the middle node to be higher
CreateObstacleTopAreaIfNecessary( area, areaOther, dir, true );
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Creates a new nav area if an obstacle exists between the two nav areas. If bMultiNode is false, this checks
* if there's a one-node separation between the areas, and if so if there is an obstacle detected between the nodes.
* If bMultiNode is true, checks if there is a two-node separation between the areas, and if so if the middle node is
* higher than the two areas, suggesting an obstacle in the middle.
*/
bool CNavMesh::CreateObstacleTopAreaIfNecessary( CNavArea *area, CNavArea *areaOther, NavDirType dir, bool bMultiNode )
{
float obstacleHeightMin = FLT_MAX;
float obstacleHeightMax = 0;
float obstacleHeightStart = 0;
float obstacleHeightEnd = 0;
float obstacleDistMin = GenerationStepSize;
float obstacleDistMax = 0;
Vector center;
float halfPortalWidth;
area->ComputePortal( areaOther, dir, ¢er, &halfPortalWidth );
if ( halfPortalWidth > 0 )
{
// get the corners to left and right of direction toward other area
NavCornerType cornerStart = (NavCornerType) dir;
NavCornerType cornerEnd = (NavCornerType) ( ( dir + 1 ) % NUM_CORNERS );
CNavNode *node = area->m_node[cornerStart];
CNavNode *nodeEnd = area->m_node[cornerEnd];
NavDirType dirEdge = (NavDirType) ( ( dir + 1 ) % NUM_DIRECTIONS );
obstacleHeightMin = FLT_MAX;
float zStart = 0, zEnd = 0;
// along the edge of this area that faces the other area, look at every node that's in the portal between the two
while ( node )
{
Vector vecToPortalCenter = *node->GetPosition() - center;
vecToPortalCenter.z = 0;
if ( vecToPortalCenter.IsLengthLessThan( halfPortalWidth + 1.0f ) )
{
// this node is in the portal
float obstacleHeight = 0;
float obstacleDistStartCur = node->m_obstacleStartDist[dir];
float obstacleDistEndCur = node->m_obstacleEndDist[dir];