2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 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.
24 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.
26 ===========================================================================
29 #include "../../idlib/precompiled.h"
32 #include "AAS_local.h"
34 #define SUBSAMPLE_WALK_PATH 1
35 #define SUBSAMPLE_FLY_PATH 0
37 const int maxWalkPathIterations = 10;
38 const float maxWalkPathDistance = 500.0f;
39 const float walkPathSampleDistance = 8.0f;
41 const int maxFlyPathIterations = 10;
42 const float maxFlyPathDistance = 500.0f;
43 const float flyPathSampleDistance = 8.0f;
48 idAASLocal::EdgeSplitPoint
50 calculates split point of the edge with the plane
51 returns true if the split point is between the edge vertices
54 bool idAASLocal::EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const {
55 const aasEdge_t *edge;
59 edge = &file->GetEdge( edgeNum );
60 v1 = file->GetVertex( edge->vertexNum[0] );
61 v2 = file->GetVertex( edge->vertexNum[1] );
62 d1 = v1 * plane.Normal() - plane.Dist();
63 d2 = v2 * plane.Normal() - plane.Dist();
65 //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) {
66 if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) {
69 split = v1 + (d1 / (d1 - d2)) * (v2 - v1);
75 idAASLocal::FloorEdgeSplitPoint
77 calculates either the closest or furthest point on the floor of the area which also lies on the pathPlane
78 the point has to be on the front side of the frontPlane to be valid
81 bool idAASLocal::FloorEdgeSplitPoint( idVec3 &bestSplit, int areaNum, const idPlane &pathPlane, const idPlane &frontPlane, bool closest ) const {
82 int i, j, faceNum, edgeNum;
83 const aasArea_t *area;
84 const aasFace_t *face;
89 bestDist = maxWalkPathDistance;
94 area = &file->GetArea( areaNum );
96 for ( i = 0; i < area->numFaces; i++ ) {
97 faceNum = file->GetFaceIndex( area->firstFace + i );
98 face = &file->GetFace( abs(faceNum) );
100 if ( !(face->flags & FACE_FLOOR ) ) {
104 for ( j = 0; j < face->numEdges; j++ ) {
105 edgeNum = file->GetEdgeIndex( face->firstEdge + j );
107 if ( !EdgeSplitPoint( split, abs( edgeNum ), pathPlane ) ) {
110 dist = frontPlane.Distance( split );
112 if ( dist >= -0.1f && dist < bestDist ) {
117 if ( dist > bestDist ) {
126 return ( bestDist < maxWalkPathDistance );
128 return ( bestDist > -0.1f );
134 idAASLocal::WalkPathValid
136 returns true if one can walk in a straight line between origin and goalOrigin
139 bool idAASLocal::WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const {
140 int curAreaNum, lastAreaNum, lastAreas[4], lastAreaIndex;
141 idPlane pathPlane, frontPlane, farPlane;
142 idReachability *reach;
143 const aasArea_t *area;
146 if ( file == NULL ) {
152 lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum;
155 pathPlane.SetNormal( (goalOrigin - origin).Cross( file->GetSettings().gravityDir ) );
156 pathPlane.Normalize();
157 pathPlane.FitThroughPoint( origin );
159 frontPlane.SetNormal( goalOrigin - origin );
160 frontPlane.Normalize();
161 frontPlane.FitThroughPoint( origin );
163 farPlane.SetNormal( frontPlane.Normal() );
164 farPlane.FitThroughPoint( goalOrigin );
166 curAreaNum = areaNum;
167 lastAreaNum = curAreaNum;
171 // find the furthest floor face split point on the path
172 if ( !FloorEdgeSplitPoint( endPos, curAreaNum, pathPlane, frontPlane, false ) ) {
176 // if we found a point near or further than the goal we're done
177 if ( farPlane.Distance( endPos ) > -0.5f ) {
181 // if we reached the goal area we're done
182 if ( curAreaNum == goalAreaNum ) {
186 frontPlane.SetDist( frontPlane.Normal() * endPos );
188 area = &file->GetArea( curAreaNum );
190 for ( reach = area->reach; reach; reach = reach->next ) {
191 if ( reach->travelType != TFL_WALK ) {
195 // if the reachability goes back to a previous area
196 if ( reach->toAreaNum == lastAreas[0] || reach->toAreaNum == lastAreas[1] ||
197 reach->toAreaNum == lastAreas[2] || reach->toAreaNum == lastAreas[3] ) {
201 // if undesired travel flags are required to travel through the area
202 if ( file->GetArea( reach->toAreaNum ).travelFlags & ~travelFlags ) {
206 // don't optimize through an area near a ledge
207 if ( file->GetArea( reach->toAreaNum ).flags & AREA_LEDGE ) {
211 // find the closest floor face split point on the path
212 if ( !FloorEdgeSplitPoint( p, reach->toAreaNum, pathPlane, frontPlane, true ) ) {
216 // direction parallel to gravity
217 dir = ( file->GetSettings().gravityDir * endPos * file->GetSettings().gravityDir ) -
218 ( file->GetSettings().gravityDir * p * file->GetSettings().gravityDir );
219 if ( dir.LengthSqr() > Square( file->GetSettings().maxStepHeight ) ) {
223 // direction orthogonal to gravity
224 dir = endPos - p - dir;
225 if ( dir.LengthSqr() > Square( 0.2f ) ) {
236 lastAreas[lastAreaIndex] = curAreaNum;
237 lastAreaIndex = ( lastAreaIndex + 1 ) & 3;
239 curAreaNum = reach->toAreaNum;
242 endAreaNum = curAreaNum;
249 idAASLocal::SubSampleWalkPath
252 idVec3 idAASLocal::SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const {
253 int i, numSamples, curAreaNum;
254 idVec3 dir, point, nextPoint, endPos;
257 numSamples = (int) (dir.Length() / walkPathSampleDistance) + 1;
260 for ( i = 1; i < numSamples; i++ ) {
261 nextPoint = start + dir * ((float) i / numSamples);
262 if ( (point - nextPoint).LengthSqr() > Square( maxWalkPathDistance ) ) {
265 if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) {
269 endAreaNum = curAreaNum;
276 idAASLocal::WalkPathToGoal
278 FIXME: don't stop optimizing on first failure ?
281 bool idAASLocal::WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const {
282 int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum;
283 idReachability *reach;
286 path.type = PATHTYPE_WALK;
287 path.moveGoal = origin;
288 path.moveAreaNum = areaNum;
289 path.secondaryGoal = origin;
290 path.reachability = NULL;
292 if ( file == NULL || areaNum == goalAreaNum ) {
293 path.moveGoal = goalOrigin;
297 lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum;
300 curAreaNum = areaNum;
302 for ( i = 0; i < maxWalkPathIterations; i++ ) {
304 if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) {
312 // no need to check through the first area
313 if ( areaNum != curAreaNum ) {
314 // only optimize a limited distance ahead
315 if ( (reach->start - origin).LengthSqr() > Square( maxWalkPathDistance ) ) {
316 #if SUBSAMPLE_WALK_PATH
317 path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum );
322 if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) {
323 #if SUBSAMPLE_WALK_PATH
324 path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum );
330 path.moveGoal = reach->start;
331 path.moveAreaNum = curAreaNum;
333 if ( reach->travelType != TFL_WALK ) {
337 if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) {
341 path.moveGoal = reach->end;
342 path.moveAreaNum = reach->toAreaNum;
344 if ( reach->toAreaNum == goalAreaNum ) {
345 if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) {
346 #if SUBSAMPLE_WALK_PATH
347 path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum );
351 path.moveGoal = goalOrigin;
352 path.moveAreaNum = goalAreaNum;
356 lastAreas[lastAreaIndex] = curAreaNum;
357 lastAreaIndex = ( lastAreaIndex + 1 ) & 3;
359 curAreaNum = reach->toAreaNum;
361 if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] ||
362 curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) {
363 common->Warning( "idAASLocal::WalkPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum );
372 switch( reach->travelType ) {
373 case TFL_WALKOFFLEDGE:
374 path.type = PATHTYPE_WALKOFFLEDGE;
375 path.secondaryGoal = reach->end;
376 path.reachability = reach;
378 case TFL_BARRIERJUMP:
379 path.type |= PATHTYPE_BARRIERJUMP;
380 path.secondaryGoal = reach->end;
381 path.reachability = reach;
384 path.type |= PATHTYPE_JUMP;
385 path.secondaryGoal = reach->end;
386 path.reachability = reach;
397 idAASLocal::FlyPathValid
399 returns true if one can fly in a straight line between origin and goalOrigin
402 bool idAASLocal::FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const {
405 if ( file == NULL ) {
411 file->Trace( trace, origin, goalOrigin );
413 endPos = trace.endpos;
414 endAreaNum = trace.lastAreaNum;
416 if ( trace.fraction >= 1.0f ) {
425 idAASLocal::SubSampleFlyPath
428 idVec3 idAASLocal::SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const {
429 int i, numSamples, curAreaNum;
430 idVec3 dir, point, nextPoint, endPos;
433 numSamples = (int) (dir.Length() / flyPathSampleDistance) + 1;
436 for ( i = 1; i < numSamples; i++ ) {
437 nextPoint = start + dir * ((float) i / numSamples);
438 if ( (point - nextPoint).LengthSqr() > Square( maxFlyPathDistance ) ) {
441 if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) {
445 endAreaNum = curAreaNum;
452 idAASLocal::FlyPathToGoal
454 FIXME: don't stop optimizing on first failure ?
457 bool idAASLocal::FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const {
458 int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum;
459 idReachability *reach;
462 path.type = PATHTYPE_WALK;
463 path.moveGoal = origin;
464 path.moveAreaNum = areaNum;
465 path.secondaryGoal = origin;
466 path.reachability = NULL;
468 if ( file == NULL || areaNum == goalAreaNum ) {
469 path.moveGoal = goalOrigin;
473 lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum;
476 curAreaNum = areaNum;
478 for ( i = 0; i < maxFlyPathIterations; i++ ) {
480 if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) {
488 // no need to check through the first area
489 if ( areaNum != curAreaNum ) {
490 if ( (reach->start - origin).LengthSqr() > Square( maxFlyPathDistance ) ) {
491 #if SUBSAMPLE_FLY_PATH
492 path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum );
497 if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) {
498 #if SUBSAMPLE_FLY_PATH
499 path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum );
505 path.moveGoal = reach->start;
506 path.moveAreaNum = curAreaNum;
508 if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) {
512 path.moveGoal = reach->end;
513 path.moveAreaNum = reach->toAreaNum;
515 if ( reach->toAreaNum == goalAreaNum ) {
516 if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) {
517 #if SUBSAMPLE_FLY_PATH
518 path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum );
522 path.moveGoal = goalOrigin;
523 path.moveAreaNum = goalAreaNum;
527 lastAreas[lastAreaIndex] = curAreaNum;
528 lastAreaIndex = ( lastAreaIndex + 1 ) & 3;
530 curAreaNum = reach->toAreaNum;
532 if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] ||
533 curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) {
534 common->Warning( "idAASLocal::FlyPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum );
546 typedef struct wallEdge_s {
549 struct wallEdge_s * next;
554 idAASLocal::SortWallEdges
557 void idAASLocal::SortWallEdges( int *edges, int numEdges ) const {
558 int i, j, k, numSequences;
559 wallEdge_t **sequenceFirst, **sequenceLast, *wallEdges, *wallEdge;
561 wallEdges = (wallEdge_t *) _alloca16( numEdges * sizeof( wallEdge_t ) );
562 sequenceFirst = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) );
563 sequenceLast = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) );
565 for ( i = 0; i < numEdges; i++ ) {
566 wallEdges[i].edgeNum = edges[i];
567 GetEdgeVertexNumbers( edges[i], wallEdges[i].verts );
568 wallEdges[i].next = NULL;
569 sequenceFirst[i] = &wallEdges[i];
570 sequenceLast[i] = &wallEdges[i];
572 numSequences = numEdges;
574 for ( i = 0; i < numSequences; i++ ) {
575 for ( j = i+1; j < numSequences; j++ ) {
576 if ( sequenceFirst[i]->verts[0] == sequenceLast[j]->verts[1] ) {
577 sequenceLast[j]->next = sequenceFirst[i];
578 sequenceFirst[i] = sequenceFirst[j];
581 if ( sequenceLast[i]->verts[1] == sequenceFirst[j]->verts[0] ) {
582 sequenceLast[i]->next = sequenceFirst[j];
586 if ( j < numSequences ) {
588 for ( k = j; k < numSequences; k++ ) {
589 sequenceFirst[k] = sequenceFirst[k+1];
590 sequenceLast[k] = sequenceLast[k+1];
597 for ( i = 0; i < numSequences; i++ ) {
598 for ( wallEdge = sequenceFirst[i]; wallEdge; wallEdge = wallEdge->next ) {
599 edges[k++] = wallEdge->edgeNum;
606 idAASLocal::GetWallEdges
609 int idAASLocal::GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const {
610 int i, j, k, l, face1Num, face2Num, edge1Num, edge2Num, numEdges, absEdge1Num;
611 int *areaQueue, curArea, queueStart, queueEnd;
613 const aasArea_t *area;
614 const aasFace_t *face1, *face2;
615 idReachability *reach;
623 areasVisited = (byte *) _alloca16( file->GetNumAreas() );
624 memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) );
625 areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) );
629 areaQueue[0] = areaNum;
630 areasVisited[areaNum] = true;
632 for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) {
634 area = &file->GetArea( curArea );
636 for ( i = 0; i < area->numFaces; i++ ) {
637 face1Num = file->GetFaceIndex( area->firstFace + i );
638 face1 = &file->GetFace( abs(face1Num) );
640 if ( !(face1->flags & FACE_FLOOR ) ) {
644 for ( j = 0; j < face1->numEdges; j++ ) {
645 edge1Num = file->GetEdgeIndex( face1->firstEdge + j );
646 absEdge1Num = abs( edge1Num );
648 // test if the edge is shared by another floor face of this area
649 for ( k = 0; k < area->numFaces; k++ ) {
653 face2Num = file->GetFaceIndex( area->firstFace + k );
654 face2 = &file->GetFace( abs(face2Num) );
656 if ( !(face2->flags & FACE_FLOOR ) ) {
660 for ( l = 0; l < face2->numEdges; l++ ) {
661 edge2Num = abs( file->GetEdgeIndex( face2->firstEdge + l ) );
662 if ( edge2Num == absEdge1Num ) {
666 if ( l < face2->numEdges ) {
670 if ( k < area->numFaces ) {
674 // test if the edge is used by a reachability
675 for ( reach = area->reach; reach; reach = reach->next ) {
676 if ( reach->travelType & travelFlags ) {
677 if ( reach->edgeNum == absEdge1Num ) {
686 // test if the edge is already in the list
687 for ( k = 0; k < numEdges; k++ ) {
688 if ( edge1Num == edges[k] ) {
692 if ( k < numEdges ) {
696 // add the edge to the list
697 edges[numEdges++] = edge1Num;
698 if ( numEdges >= maxEdges ) {
704 // add new areas to the queue
705 for ( reach = area->reach; reach; reach = reach->next ) {
706 if ( reach->travelType & travelFlags ) {
707 // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds
708 if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) {
709 areaQueue[queueEnd++] = reach->toAreaNum;
710 areasVisited[reach->toAreaNum] = true;