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 "../Game_local.h"
35 ===============================================================================
37 Dynamic Obstacle Avoidance
39 - assumes the AI lives inside a bounding box aligned with the gravity direction
40 - obstacles in proximity of the AI are gathered
41 - if obstacles are found the AAS walls are also considered as obstacles
42 - every obstacle is represented by an oriented bounding box (OBB)
43 - an OBB is projected onto a 2D plane orthogonal to AI's gravity direction
44 - the 2D windings of the projections are expanded for the AI bbox
45 - a path tree is build using clockwise and counter clockwise edge walks along the winding edges
46 - the path tree is pruned and optimized
47 - the shortest path is chosen for navigation
49 ===============================================================================
52 const float MAX_OBSTACLE_RADIUS = 256.0f;
53 const float PUSH_OUTSIDE_OBSTACLES = 0.5f;
54 const float CLIP_BOUNDS_EPSILON = 10.0f;
55 const int MAX_AAS_WALL_EDGES = 256;
56 const int MAX_OBSTACLES = 256;
57 const int MAX_PATH_NODES = 256;
58 const int MAX_OBSTACLE_PATH = 64;
60 typedef struct obstacle_s {
66 typedef struct pathNode_s {
74 struct pathNode_s * parent;
75 struct pathNode_s * children[2];
76 struct pathNode_s * next;
80 void pathNode_s::Init() {
87 parent = children[0] = children[1] = next = NULL;
90 idBlockAlloc<pathNode_t, 128> pathNodeAllocator;
98 bool LineIntersectsPath( const idVec2 &start, const idVec2 &end, const pathNode_t *node ) {
100 idVec3 plane1, plane2;
102 plane1 = idWinding2D::Plane2DFromPoints( start, end );
103 d0 = plane1.x * node->pos.x + plane1.y * node->pos.y + plane1.z;
104 while( node->parent ) {
105 d1 = plane1.x * node->parent->pos.x + plane1.y * node->parent->pos.y + plane1.z;
106 if ( FLOATSIGNBITSET( d0 ) ^ FLOATSIGNBITSET( d1 ) ) {
107 plane2 = idWinding2D::Plane2DFromPoints( node->pos, node->parent->pos );
108 d2 = plane2.x * start.x + plane2.y * start.y + plane2.z;
109 d3 = plane2.x * end.x + plane2.y * end.y + plane2.z;
110 if ( FLOATSIGNBITSET( d2 ) ^ FLOATSIGNBITSET( d3 ) ) {
125 int PointInsideObstacle( const obstacle_t *obstacles, const int numObstacles, const idVec2 &point ) {
128 for ( i = 0; i < numObstacles; i++ ) {
130 const idVec2 *bounds = obstacles[i].bounds;
131 if ( point.x < bounds[0].x || point.y < bounds[0].y || point.x > bounds[1].x || point.y > bounds[1].y ) {
135 if ( !obstacles[i].winding.PointInside( point, 0.1f ) ) {
147 GetPointOutsideObstacles
150 void GetPointOutsideObstacles( const obstacle_t *obstacles, const int numObstacles, idVec2 &point, int *obstacle, int *edgeNum ) {
151 int i, j, k, n, bestObstacle, bestEdgeNum, queueStart, queueEnd, edgeNums[2];
152 float d, bestd, scale[2];
153 idVec3 plane, bestPlane;
154 idVec2 newPoint, dir, bestPoint;
156 bool *obstacleVisited;
166 bestObstacle = PointInsideObstacle( obstacles, numObstacles, point );
167 if ( bestObstacle == -1 ) {
171 const idWinding2D &w = obstacles[bestObstacle].winding;
172 bestd = idMath::INFINITY;
174 for ( i = 0; i < w.GetNumPoints(); i++ ) {
175 plane = idWinding2D::Plane2DFromPoints( w[(i+1)%w.GetNumPoints()], w[i], true );
176 d = plane.x * point.x + plane.y * point.y + plane.z;
182 // if this is a wall always try to pop out at the first edge
183 if ( obstacles[bestObstacle].entity == NULL ) {
188 newPoint = point - ( bestd + PUSH_OUTSIDE_OBSTACLES ) * bestPlane.ToVec2();
189 if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) {
192 *obstacle = bestObstacle;
195 *edgeNum = bestEdgeNum;
200 queue = (int *) _alloca( numObstacles * sizeof( queue[0] ) );
201 obstacleVisited = (bool *) _alloca( numObstacles * sizeof( obstacleVisited[0] ) );
205 queue[0] = bestObstacle;
207 memset( obstacleVisited, 0, numObstacles * sizeof( obstacleVisited[0] ) );
208 obstacleVisited[bestObstacle] = true;
210 bestd = idMath::INFINITY;
211 for ( i = queue[0]; queueStart < queueEnd; i = queue[++queueStart] ) {
212 w1 = obstacles[i].winding;
213 w1.Expand( PUSH_OUTSIDE_OBSTACLES );
215 for ( j = 0; j < numObstacles; j++ ) {
216 // if the obstacle has been visited already
217 if ( obstacleVisited[j] ) {
220 // if the bounds do not intersect
221 if ( obstacles[j].bounds[0].x > obstacles[i].bounds[1].x || obstacles[j].bounds[0].y > obstacles[i].bounds[1].y ||
222 obstacles[j].bounds[1].x < obstacles[i].bounds[0].x || obstacles[j].bounds[1].y < obstacles[i].bounds[0].y ) {
226 queue[queueEnd++] = j;
227 obstacleVisited[j] = true;
229 w2 = obstacles[j].winding;
232 for ( k = 0; k < w1.GetNumPoints(); k++ ) {
233 dir = w1[(k+1)%w1.GetNumPoints()] - w1[k];
234 if ( !w2.RayIntersection( w1[k], dir, scale[0], scale[1], edgeNums ) ) {
237 for ( n = 0; n < 2; n++ ) {
238 newPoint = w1[k] + scale[n] * dir;
239 if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) {
240 d = ( newPoint - point ).LengthSqr();
243 bestPoint = newPoint;
244 bestEdgeNum = edgeNums[n];
252 if ( bestd < idMath::INFINITY ) {
255 *obstacle = bestObstacle;
258 *edgeNum = bestEdgeNum;
263 gameLocal.Warning( "GetPointOutsideObstacles: no valid point found" );
268 GetFirstBlockingObstacle
271 bool GetFirstBlockingObstacle( const obstacle_t *obstacles, int numObstacles, int skipObstacle, const idVec2 &startPos, const idVec2 &delta, float &blockingScale, int &blockingObstacle, int &blockingEdgeNum ) {
273 float dist, scale1, scale2;
276 // get bounds for the current movement delta
277 bounds[0] = startPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
278 bounds[1] = startPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
279 bounds[FLOATSIGNBITNOTSET(delta.x)].x += delta.x;
280 bounds[FLOATSIGNBITNOTSET(delta.y)].y += delta.y;
282 // test for obstacles blocking the path
283 blockingScale = idMath::INFINITY;
284 dist = delta.Length();
285 for ( i = 0; i < numObstacles; i++ ) {
286 if ( i == skipObstacle ) {
289 if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y ||
290 bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) {
293 if ( obstacles[i].winding.RayIntersection( startPos, delta, scale1, scale2, edgeNums ) ) {
294 if ( scale1 < blockingScale && scale1 * dist > -0.01f && scale2 * dist > 0.01f ) {
295 blockingScale = scale1;
296 blockingObstacle = i;
297 blockingEdgeNum = edgeNums[0];
301 return ( blockingScale < 1.0f );
309 int GetObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, int areaNum, const idVec3 &startPos, const idVec3 &seekPos, obstacle_t *obstacles, int maxObstacles, idBounds &clipBounds ) {
310 int i, j, numListedClipModels, numObstacles, numVerts, clipMask, blockingObstacle, blockingEdgeNum;
311 int wallEdges[MAX_AAS_WALL_EDGES], numWallEdges, verts[2], lastVerts[2], nextVerts[2];
312 float stepHeight, headHeight, blockingScale, min, max;
313 idVec3 seekDelta, silVerts[32], start, end, nextStart, nextEnd;
314 idVec2 expBounds[2], edgeDir, edgeNormal, nextEdgeDir, nextEdgeNormal, lastEdgeNormal;
319 idClipModel *clipModel;
320 idClipModel *clipModelList[ MAX_GENTITIES ];
324 seekDelta = seekPos - startPos;
325 expBounds[0] = physics->GetBounds()[0].ToVec2() - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
326 expBounds[1] = physics->GetBounds()[1].ToVec2() + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
328 physics->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), stepHeight, headHeight );
329 stepHeight += aas->GetSettings()->maxStepHeight;
331 // clip bounds for the obstacle search space
332 clipBounds[0] = clipBounds[1] = startPos;
333 clipBounds.AddPoint( seekPos );
334 clipBounds.ExpandSelf( MAX_OBSTACLE_RADIUS );
335 clipMask = physics->GetClipMask();
337 // find all obstacles touching the clip bounds
338 numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipMask, clipModelList, MAX_GENTITIES );
340 for ( i = 0; i < numListedClipModels && numObstacles < MAX_OBSTACLES; i++ ) {
341 clipModel = clipModelList[i];
342 obEnt = clipModel->GetEntity();
344 if ( !clipModel->IsTraceModel() ) {
348 if ( obEnt->IsType( idActor::Type ) ) {
349 obPhys = obEnt->GetPhysics();
350 // ignore myself, my enemy, and dead bodies
351 if ( ( obPhys == physics ) || ( obEnt == ignore ) || ( obEnt->health <= 0 ) ) {
354 // if the actor is moving
355 idVec3 v1 = obPhys->GetLinearVelocity();
356 if ( v1.LengthSqr() > Square( 10.0f ) ) {
357 idVec3 v2 = physics->GetLinearVelocity();
358 if ( v2.LengthSqr() > Square( 10.0f ) ) {
359 // if moving in about the same direction
360 if ( v1 * v2 > 0.0f ) {
365 } else if ( obEnt->IsType( idMoveable::Type ) ) {
366 // moveables are considered obstacles
368 // ignore everything else
372 // check if we can step over the object
373 clipModel->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), min, max );
374 if ( max < stepHeight || min > headHeight ) {
375 // can step over this one
379 // project a box containing the obstacle onto the floor plane
380 box = idBox( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() );
381 numVerts = box.GetParallelProjectionSilhouetteVerts( physics->GetGravityNormal(), silVerts );
383 // create a 2D winding for the obstacle;
384 obstacle_t &obstacle = obstacles[numObstacles++];
385 obstacle.winding.Clear();
386 for ( j = 0; j < numVerts; j++ ) {
387 obstacle.winding.AddPoint( silVerts[j].ToVec2() );
390 if ( ai_showObstacleAvoidance.GetBool() ) {
391 for ( j = 0; j < numVerts; j++ ) {
392 silVerts[j].z = startPos.z;
394 for ( j = 0; j < numVerts; j++ ) {
395 gameRenderWorld->DebugArrow( colorWhite, silVerts[j], silVerts[(j+1)%numVerts], 4 );
399 // expand the 2D winding for collision with a 2D box
400 obstacle.winding.ExpandForAxialBox( expBounds );
401 obstacle.winding.GetBounds( obstacle.bounds );
402 obstacle.entity = obEnt;
405 // if there are no dynamic obstacles the path should be through valid AAS space
406 if ( numObstacles == 0 ) {
410 // if the current path doesn't intersect any dynamic obstacles the path should be through valid AAS space
411 if ( PointInsideObstacle( obstacles, numObstacles, startPos.ToVec2() ) == -1 ) {
412 if ( !GetFirstBlockingObstacle( obstacles, numObstacles, -1, startPos.ToVec2(), seekDelta.ToVec2(), blockingScale, blockingObstacle, blockingEdgeNum ) ) {
417 // create obstacles for AAS walls
419 float halfBoundsSize = ( expBounds[ 1 ].x - expBounds[ 0 ].x ) * 0.5f;
421 numWallEdges = aas->GetWallEdges( areaNum, clipBounds, TFL_WALK, wallEdges, MAX_AAS_WALL_EDGES );
422 aas->SortWallEdges( wallEdges, numWallEdges );
424 lastVerts[0] = lastVerts[1] = 0;
425 lastEdgeNormal.Zero();
426 nextVerts[0] = nextVerts[1] = 0;
427 for ( i = 0; i < numWallEdges && numObstacles < MAX_OBSTACLES; i++ ) {
428 aas->GetEdge( wallEdges[i], start, end );
429 aas->GetEdgeVertexNumbers( wallEdges[i], verts );
430 edgeDir = end.ToVec2() - start.ToVec2();
432 edgeNormal.x = edgeDir.y;
433 edgeNormal.y = -edgeDir.x;
434 if ( i < numWallEdges-1 ) {
435 aas->GetEdge( wallEdges[i+1], nextStart, nextEnd );
436 aas->GetEdgeVertexNumbers( wallEdges[i+1], nextVerts );
437 nextEdgeDir = nextEnd.ToVec2() - nextStart.ToVec2();
438 nextEdgeDir.Normalize();
439 nextEdgeNormal.x = nextEdgeDir.y;
440 nextEdgeNormal.y = -nextEdgeDir.x;
443 obstacle_t &obstacle = obstacles[numObstacles++];
444 obstacle.winding.Clear();
445 obstacle.winding.AddPoint( end.ToVec2() );
446 obstacle.winding.AddPoint( start.ToVec2() );
447 obstacle.winding.AddPoint( start.ToVec2() - edgeDir - edgeNormal * halfBoundsSize );
448 obstacle.winding.AddPoint( end.ToVec2() + edgeDir - edgeNormal * halfBoundsSize );
449 if ( lastVerts[1] == verts[0] ) {
450 obstacle.winding[2] -= lastEdgeNormal * halfBoundsSize;
452 obstacle.winding[1] -= edgeDir;
454 if ( verts[1] == nextVerts[0] ) {
455 obstacle.winding[3] -= nextEdgeNormal * halfBoundsSize;
457 obstacle.winding[0] += edgeDir;
459 obstacle.winding.GetBounds( obstacle.bounds );
460 obstacle.entity = NULL;
462 memcpy( lastVerts, verts, sizeof( lastVerts ) );
463 lastEdgeNormal = edgeNormal;
468 if ( ai_showObstacleAvoidance.GetBool() ) {
469 for ( i = 0; i < numObstacles; i++ ) {
470 obstacle_t &obstacle = obstacles[i];
471 for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) {
472 silVerts[j].ToVec2() = obstacle.winding[j];
473 silVerts[j].z = startPos.z;
475 for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) {
476 gameRenderWorld->DebugArrow( colorGreen, silVerts[j], silVerts[(j+1)%obstacle.winding.GetNumPoints()], 4 );
489 void FreePathTree_r( pathNode_t *node ) {
490 if ( node->children[0] ) {
491 FreePathTree_r( node->children[0] );
493 if ( node->children[1] ) {
494 FreePathTree_r( node->children[1] );
496 pathNodeAllocator.Free( node );
504 void DrawPathTree( const pathNode_t *root, const float height ) {
507 const pathNode_t *node;
509 for ( node = root; node; node = node->next ) {
510 for ( i = 0; i < 2; i++ ) {
511 if ( node->children[i] ) {
512 start.ToVec2() = node->pos;
514 end.ToVec2() = node->children[i]->pos;
516 gameRenderWorld->DebugArrow( node->edgeNum == -1 ? colorYellow : i ? colorBlue : colorRed, start, end, 1 );
528 bool GetPathNodeDelta( pathNode_t *node, const obstacle_t *obstacles, const idVec2 &seekPos, bool blocked ) {
529 int numPoints, edgeNum;
531 idVec2 seekDelta, dir;
534 numPoints = obstacles[node->obstacle].winding.GetNumPoints();
536 // get delta along the current edge
538 edgeNum = ( node->edgeNum + node->dir ) % numPoints;
539 node->delta = obstacles[node->obstacle].winding[edgeNum] - node->pos;
540 if ( node->delta.LengthSqr() > 0.01f ) {
543 node->edgeNum = ( node->edgeNum + numPoints + ( 2 * node->dir - 1 ) ) % numPoints;
549 // test if the current edge faces the goal
550 seekDelta = seekPos - node->pos;
551 facing = ( ( 2 * node->dir - 1 ) * ( node->delta.x * seekDelta.y - node->delta.y * seekDelta.x ) ) >= 0.0f;
553 // if the current edge faces goal and the line from the current
554 // position to the goal does not intersect the current path
555 if ( facing && !LineIntersectsPath( node->pos, seekPos, node->parent ) ) {
556 node->delta = seekPos - node->pos;
561 // if the delta is along the obstacle edge
562 if ( node->edgeNum != -1 ) {
563 // if the edge is found going from this node to the root node
564 for ( n = node->parent; n; n = n->parent ) {
566 if ( node->obstacle != n->obstacle || node->edgeNum != n->edgeNum ) {
570 // test whether or not the edge segments actually overlap
571 if ( n->pos * node->delta > ( node->pos + node->delta ) * node->delta ) {
574 if ( node->pos * node->delta > ( n->pos + n->delta ) * node->delta ) {
592 pathNode_t *BuildPathTree( const obstacle_t *obstacles, int numObstacles, const idBounds &clipBounds, const idVec2 &startPos, const idVec2 &seekPos, obstaclePath_t &path ) {
593 int blockingEdgeNum, blockingObstacle, obstaclePoints, bestNumNodes = MAX_OBSTACLE_PATH;
595 pathNode_t *root, *node, *child;
597 idQueueTemplate<pathNode_t, offsetof( pathNode_t, next ) > pathNodeQueue, treeQueue;
598 root = pathNodeAllocator.Alloc();
600 root->pos = startPos;
602 root->delta = seekPos - root->pos;
604 pathNodeQueue.Add( root );
606 for ( node = pathNodeQueue.Get(); node && pathNodeAllocator.GetAllocCount() < MAX_PATH_NODES; node = pathNodeQueue.Get() ) {
608 treeQueue.Add( node );
610 // if this path has more than twice the number of nodes than the best path so far
611 if ( node->numNodes > bestNumNodes * 2 ) {
615 // don't move outside of the clip bounds
616 idVec2 endPos = node->pos + node->delta;
617 if ( endPos.x - CLIP_BOUNDS_EPSILON < clipBounds[0].x || endPos.x + CLIP_BOUNDS_EPSILON > clipBounds[1].x ||
618 endPos.y - CLIP_BOUNDS_EPSILON < clipBounds[0].y || endPos.y + CLIP_BOUNDS_EPSILON > clipBounds[1].y ) {
622 // if an obstacle is blocking the path
623 if ( GetFirstBlockingObstacle( obstacles, numObstacles, node->obstacle, node->pos, node->delta, blockingScale, blockingObstacle, blockingEdgeNum ) ) {
625 if ( path.firstObstacle == NULL ) {
626 path.firstObstacle = obstacles[blockingObstacle].entity;
629 node->delta *= blockingScale;
631 if ( node->edgeNum == -1 ) {
632 node->children[0] = pathNodeAllocator.Alloc();
633 node->children[0]->Init();
634 node->children[1] = pathNodeAllocator.Alloc();
635 node->children[1]->Init();
636 node->children[0]->dir = 0;
637 node->children[1]->dir = 1;
638 node->children[0]->parent = node->children[1]->parent = node;
639 node->children[0]->pos = node->children[1]->pos = node->pos + node->delta;
640 node->children[0]->obstacle = node->children[1]->obstacle = blockingObstacle;
641 node->children[0]->edgeNum = node->children[1]->edgeNum = blockingEdgeNum;
642 node->children[0]->numNodes = node->children[1]->numNodes = node->numNodes + 1;
643 if ( GetPathNodeDelta( node->children[0], obstacles, seekPos, true ) ) {
644 pathNodeQueue.Add( node->children[0] );
646 if ( GetPathNodeDelta( node->children[1], obstacles, seekPos, true ) ) {
647 pathNodeQueue.Add( node->children[1] );
650 node->children[node->dir] = child = pathNodeAllocator.Alloc();
652 child->dir = node->dir;
653 child->parent = node;
654 child->pos = node->pos + node->delta;
655 child->obstacle = blockingObstacle;
656 child->edgeNum = blockingEdgeNum;
657 child->numNodes = node->numNodes + 1;
658 if ( GetPathNodeDelta( child, obstacles, seekPos, true ) ) {
659 pathNodeQueue.Add( child );
663 node->children[node->dir] = child = pathNodeAllocator.Alloc();
665 child->dir = node->dir;
666 child->parent = node;
667 child->pos = node->pos + node->delta;
668 child->numNodes = node->numNodes + 1;
670 // there is a free path towards goal
671 if ( node->edgeNum == -1 ) {
672 if ( node->numNodes < bestNumNodes ) {
673 bestNumNodes = node->numNodes;
678 child->obstacle = node->obstacle;
679 obstaclePoints = obstacles[node->obstacle].winding.GetNumPoints();
680 child->edgeNum = ( node->edgeNum + obstaclePoints + ( 2 * node->dir - 1 ) ) % obstaclePoints;
682 if ( GetPathNodeDelta( child, obstacles, seekPos, false ) ) {
683 pathNodeQueue.Add( child );
696 void PrunePathTree( pathNode_t *root, const idVec2 &seekPos ) {
699 pathNode_t *node, *lastNode, *n, *bestNode;
704 node->dist = ( seekPos - node->pos ).LengthSqr();
706 if ( node->children[0] ) {
707 node = node->children[0];
708 } else if ( node->children[1] ) {
709 node = node->children[1];
712 // find the node closest to the goal along this path
713 bestDist = idMath::INFINITY;
715 for ( n = node; n; n = n->parent ) {
716 if ( n->children[0] && n->children[1] ) {
719 if ( n->dist < bestDist ) {
725 // free tree down from the best node
726 for ( i = 0; i < 2; i++ ) {
727 if ( bestNode->children[i] ) {
728 FreePathTree_r( bestNode->children[i] );
729 bestNode->children[i] = NULL;
733 for ( lastNode = bestNode, node = bestNode->parent; node; lastNode = node, node = node->parent ) {
734 if ( node->children[1] && ( node->children[1] != lastNode ) ) {
735 node = node->children[1];
748 int OptimizePath( const pathNode_t *root, const pathNode_t *leafNode, const obstacle_t *obstacles, int numObstacles, idVec2 optimizedPath[MAX_OBSTACLE_PATH] ) {
749 int i, numPathPoints, edgeNums[2];
750 const pathNode_t *curNode, *nextNode;
751 idVec2 curPos, curDelta, bounds[2];
752 float scale1, scale2, curLength;
754 optimizedPath[0] = root->pos;
757 for ( nextNode = curNode = root; curNode != leafNode; curNode = nextNode ) {
759 for ( nextNode = leafNode; nextNode->parent != curNode; nextNode = nextNode->parent ) {
761 // can only take shortcuts when going from one object to another
762 if ( nextNode->obstacle == curNode->obstacle ) {
766 curPos = curNode->pos;
767 curDelta = nextNode->pos - curPos;
768 curLength = curDelta.Length();
770 // get bounds for the current movement delta
771 bounds[0] = curPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
772 bounds[1] = curPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
773 bounds[FLOATSIGNBITNOTSET(curDelta.x)].x += curDelta.x;
774 bounds[FLOATSIGNBITNOTSET(curDelta.y)].y += curDelta.y;
776 // test if the shortcut intersects with any obstacles
777 for ( i = 0; i < numObstacles; i++ ) {
778 if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y ||
779 bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) {
782 if ( obstacles[i].winding.RayIntersection( curPos, curDelta, scale1, scale2, edgeNums ) ) {
783 if ( scale1 >= 0.0f && scale1 <= 1.0f && ( i != nextNode->obstacle || scale1 * curLength < curLength - 0.5f ) ) {
786 if ( scale2 >= 0.0f && scale2 <= 1.0f && ( i != nextNode->obstacle || scale2 * curLength < curLength - 0.5f ) ) {
791 if ( i >= numObstacles ) {
796 // store the next position along the optimized path
797 optimizedPath[numPathPoints++] = nextNode->pos;
800 return numPathPoints;
808 float PathLength( idVec2 optimizedPath[MAX_OBSTACLE_PATH], int numPathPoints, const idVec2 &curDir ) {
812 // calculate the path length
814 for ( i = 0; i < numPathPoints-1; i++ ) {
815 pathLength += ( optimizedPath[i+1] - optimizedPath[i] ).LengthFast();
818 // add penalty if this path does not go in the current direction
819 if ( curDir * ( optimizedPath[1] - optimizedPath[0] ) < 0.0f ) {
820 pathLength += 100.0f;
829 Returns true if there is a path all the way to the goal.
832 bool FindOptimalPath( const pathNode_t *root, const obstacle_t *obstacles, int numObstacles, const float height, const idVec3 &curDir, idVec3 &seekPos ) {
833 int i, numPathPoints, bestNumPathPoints;
834 const pathNode_t *node, *lastNode, *bestNode;
835 idVec2 optimizedPath[MAX_OBSTACLE_PATH];
836 float pathLength, bestPathLength;
837 bool pathToGoalExists, optimizedPathCalculated;
842 pathToGoalExists = false;
843 optimizedPathCalculated = false;
846 bestNumPathPoints = 0;
847 bestPathLength = idMath::INFINITY;
852 pathToGoalExists |= ( node->dist < 0.1f );
854 if ( node->dist <= bestNode->dist ) {
856 if ( idMath::Fabs( node->dist - bestNode->dist ) < 0.1f ) {
858 if ( !optimizedPathCalculated ) {
859 bestNumPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath );
860 bestPathLength = PathLength( optimizedPath, bestNumPathPoints, curDir.ToVec2() );
861 seekPos.ToVec2() = optimizedPath[1];
864 numPathPoints = OptimizePath( root, node, obstacles, numObstacles, optimizedPath );
865 pathLength = PathLength( optimizedPath, numPathPoints, curDir.ToVec2() );
867 if ( pathLength < bestPathLength ) {
869 bestNumPathPoints = numPathPoints;
870 bestPathLength = pathLength;
871 seekPos.ToVec2() = optimizedPath[1];
873 optimizedPathCalculated = true;
878 optimizedPathCalculated = false;
882 if ( node->children[0] ) {
883 node = node->children[0];
884 } else if ( node->children[1] ) {
885 node = node->children[1];
887 for ( lastNode = node, node = node->parent; node; lastNode = node, node = node->parent ) {
888 if ( node->children[1] && node->children[1] != lastNode ) {
889 node = node->children[1];
896 if ( !pathToGoalExists ) {
897 seekPos.ToVec2() = root->children[0]->pos;
898 } else if ( !optimizedPathCalculated ) {
899 OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath );
900 seekPos.ToVec2() = optimizedPath[1];
903 if ( ai_showObstacleAvoidance.GetBool() ) {
905 start.z = end.z = height + 4.0f;
906 numPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath );
907 for ( i = 0; i < numPathPoints-1; i++ ) {
908 start.ToVec2() = optimizedPath[i];
909 end.ToVec2() = optimizedPath[i+1];
910 gameRenderWorld->DebugArrow( colorCyan, start, end, 1 );
914 return pathToGoalExists;
919 idAI::FindPathAroundObstacles
921 Finds a path around dynamic obstacles using a path tree with clockwise and counter clockwise edge walks.
924 bool idAI::FindPathAroundObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ) {
925 int numObstacles, areaNum, insideObstacle;
926 obstacle_t obstacles[MAX_OBSTACLES];
930 bool pathToGoalExists;
932 path.seekPos = seekPos;
933 path.firstObstacle = NULL;
934 path.startPosOutsideObstacles = startPos;
935 path.startPosObstacle = NULL;
936 path.seekPosOutsideObstacles = seekPos;
937 path.seekPosObstacle = NULL;
943 bounds[1] = aas->GetSettings()->boundingBoxes[0][1];
944 bounds[0] = -bounds[1];
947 // get the AAS area number and a valid point inside that area
948 areaNum = aas->PointReachableAreaNum( path.startPosOutsideObstacles, bounds, (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) );
949 aas->PushPointIntoAreaNum( areaNum, path.startPosOutsideObstacles );
951 // get all the nearby obstacles
952 numObstacles = GetObstacles( physics, aas, ignore, areaNum, path.startPosOutsideObstacles, path.seekPosOutsideObstacles, obstacles, MAX_OBSTACLES, clipBounds );
954 // get a source position outside the obstacles
955 GetPointOutsideObstacles( obstacles, numObstacles, path.startPosOutsideObstacles.ToVec2(), &insideObstacle, NULL );
956 if ( insideObstacle != -1 ) {
957 path.startPosObstacle = obstacles[insideObstacle].entity;
960 // get a goal position outside the obstacles
961 GetPointOutsideObstacles( obstacles, numObstacles, path.seekPosOutsideObstacles.ToVec2(), &insideObstacle, NULL );
962 if ( insideObstacle != -1 ) {
963 path.seekPosObstacle = obstacles[insideObstacle].entity;
966 // if start and destination are pushed to the same point, we don't have a path around the obstacle
967 if ( ( path.seekPosOutsideObstacles.ToVec2() - path.startPosOutsideObstacles.ToVec2() ).LengthSqr() < Square( 1.0f ) ) {
968 if ( ( seekPos.ToVec2() - startPos.ToVec2() ).LengthSqr() > Square( 2.0f ) ) {
974 root = BuildPathTree( obstacles, numObstacles, clipBounds, path.startPosOutsideObstacles.ToVec2(), path.seekPosOutsideObstacles.ToVec2(), path );
976 // draw the path tree
977 if ( ai_showObstacleAvoidance.GetBool() ) {
978 DrawPathTree( root, physics->GetOrigin().z );
982 PrunePathTree( root, path.seekPosOutsideObstacles.ToVec2() );
984 // find the optimal path
985 pathToGoalExists = FindOptimalPath( root, obstacles, numObstacles, physics->GetOrigin().z, physics->GetLinearVelocity(), path.seekPos );
988 FreePathTree_r( root );
990 return pathToGoalExists;
995 idAI::FreeObstacleAvoidanceNodes
998 void idAI::FreeObstacleAvoidanceNodes( void ) {
999 pathNodeAllocator.Shutdown();
1004 ===============================================================================
1008 Uses the AAS to quickly and accurately predict a path for a certain
1009 period of time based on an initial position and velocity.
1011 ===============================================================================
1014 const float OVERCLIP = 1.001f;
1015 const int MAX_FRAME_SLIDE = 5;
1017 typedef struct pathTrace_s {
1021 const idEntity * blockingEntity;
1028 Returns true if a stop event was triggered.
1031 bool PathTrace( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &end, int stopEvent, struct pathTrace_s &trace, predictedPath_t &path ) {
1033 aasTrace_t aasTrace;
1035 memset( &trace, 0, sizeof( trace ) );
1037 if ( !aas || !aas->GetSettings() ) {
1039 gameLocal.clip.Translation( clipTrace, start, end, ent->GetPhysics()->GetClipModel(),
1040 ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent );
1042 // NOTE: could do (expensive) ledge detection here for when there is no AAS file
1044 trace.fraction = clipTrace.fraction;
1045 trace.endPos = clipTrace.endpos;
1046 trace.normal = clipTrace.c.normal;
1047 trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ];
1049 aasTrace.getOutOfSolid = true;
1050 if ( stopEvent & SE_ENTER_LEDGE_AREA ) {
1051 aasTrace.flags |= AREA_LEDGE;
1053 if ( stopEvent & SE_ENTER_OBSTACLE ) {
1054 aasTrace.travelFlags |= TFL_INVALID;
1057 aas->Trace( aasTrace, start, end );
1059 gameLocal.clip.TranslationEntities( clipTrace, start, aasTrace.endpos, ent->GetPhysics()->GetClipModel(),
1060 ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent );
1062 if ( clipTrace.fraction >= 1.0f ) {
1064 trace.fraction = aasTrace.fraction;
1065 trace.endPos = aasTrace.endpos;
1066 trace.normal = aas->GetPlane( aasTrace.planeNum ).Normal();
1067 trace.blockingEntity = gameLocal.world;
1069 if ( aasTrace.fraction < 1.0f ) {
1070 if ( stopEvent & SE_ENTER_LEDGE_AREA ) {
1071 if ( aas->AreaFlags( aasTrace.blockingAreaNum ) & AREA_LEDGE ) {
1072 path.endPos = trace.endPos;
1073 path.endNormal = trace.normal;
1074 path.endEvent = SE_ENTER_LEDGE_AREA;
1075 path.blockingEntity = trace.blockingEntity;
1077 if ( ai_debugMove.GetBool() ) {
1078 gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos );
1083 if ( stopEvent & SE_ENTER_OBSTACLE ) {
1084 if ( aas->AreaTravelFlags( aasTrace.blockingAreaNum ) & TFL_INVALID ) {
1085 path.endPos = trace.endPos;
1086 path.endNormal = trace.normal;
1087 path.endEvent = SE_ENTER_OBSTACLE;
1088 path.blockingEntity = trace.blockingEntity;
1090 if ( ai_debugMove.GetBool() ) {
1091 gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos );
1098 trace.fraction = clipTrace.fraction;
1099 trace.endPos = clipTrace.endpos;
1100 trace.normal = clipTrace.c.normal;
1101 trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ];
1105 if ( trace.fraction >= 1.0f ) {
1106 trace.blockingEntity = NULL;
1116 Can also be used when there is no AAS file available however ledges are not detected.
1119 bool idAI::PredictPath( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path ) {
1120 int i, j, step, numFrames, curFrameTime;
1121 idVec3 delta, curStart, curEnd, curVelocity, lastEnd, stepUp, tmpStart;
1122 idVec3 gravity, gravityDir, invGravityDir;
1123 float maxStepHeight, minFloorCos;
1126 if ( aas && aas->GetSettings() ) {
1127 gravity = aas->GetSettings()->gravity;
1128 gravityDir = aas->GetSettings()->gravityDir;
1129 invGravityDir = aas->GetSettings()->invGravityDir;
1130 maxStepHeight = aas->GetSettings()->maxStepHeight;
1131 minFloorCos = aas->GetSettings()->minFloorCos;
1133 gravity = DEFAULT_GRAVITY_VEC3;
1134 gravityDir = idVec3( 0, 0, -1 );
1135 invGravityDir = idVec3( 0, 0, 1 );
1136 maxStepHeight = 14.0f;
1140 path.endPos = start;
1141 path.endVelocity = velocity;
1142 path.endNormal.Zero();
1145 path.blockingEntity = NULL;
1148 curVelocity = velocity;
1150 numFrames = ( totalTime + frameTime - 1 ) / frameTime;
1151 curFrameTime = frameTime;
1152 for ( i = 0; i < numFrames; i++ ) {
1154 if ( i == numFrames-1 ) {
1155 curFrameTime = totalTime - i * curFrameTime;
1158 delta = curVelocity * curFrameTime * 0.001f;
1160 path.endVelocity = curVelocity;
1161 path.endTime = i * frameTime;
1163 // allow sliding along a few surfaces per frame
1164 for ( j = 0; j < MAX_FRAME_SLIDE; j++ ) {
1166 idVec3 lineStart = curStart;
1168 // allow stepping up three times per frame
1169 for ( step = 0; step < 3; step++ ) {
1171 curEnd = curStart + delta;
1172 if ( PathTrace( ent, aas, curStart, curEnd, stopEvent, trace, path ) ) {
1178 // step down at end point
1179 tmpStart = trace.endPos;
1180 curEnd = tmpStart - stepUp;
1181 if ( PathTrace( ent, aas, tmpStart, curEnd, stopEvent, trace, path ) ) {
1185 // if not moved any further than without stepping up, or if not on a floor surface
1186 if ( (lastEnd - start).LengthSqr() > (trace.endPos - start).LengthSqr() - 0.1f ||
1187 ( trace.normal * invGravityDir ) < minFloorCos ) {
1188 if ( stopEvent & SE_BLOCKED ) {
1189 path.endPos = lastEnd;
1190 path.endEvent = SE_BLOCKED;
1192 if ( ai_debugMove.GetBool() ) {
1193 gameRenderWorld->DebugLine( colorRed, lineStart, lastEnd );
1204 path.endNormal = trace.normal;
1205 path.blockingEntity = trace.blockingEntity;
1207 // if the trace is not blocked or blocked by a floor surface
1208 if ( trace.fraction >= 1.0f || ( trace.normal * invGravityDir ) > minFloorCos ) {
1209 curStart = trace.endPos;
1214 lastEnd = trace.endPos;
1217 stepUp = invGravityDir * maxStepHeight;
1218 if ( PathTrace( ent, aas, curStart, curStart + stepUp, stopEvent, trace, path ) ) {
1221 stepUp *= trace.fraction;
1222 curStart = trace.endPos;
1225 if ( ai_debugMove.GetBool() ) {
1226 gameRenderWorld->DebugLine( colorRed, lineStart, curStart );
1229 if ( trace.fraction >= 1.0f ) {
1233 delta.ProjectOntoPlane( trace.normal, OVERCLIP );
1234 curVelocity.ProjectOntoPlane( trace.normal, OVERCLIP );
1236 if ( stopEvent & SE_BLOCKED ) {
1237 // if going backwards
1238 if ( (curVelocity - gravityDir * curVelocity * gravityDir ) *
1239 (velocity - gravityDir * velocity * gravityDir) < 0.0f ) {
1240 path.endPos = curStart;
1241 path.endEvent = SE_BLOCKED;
1248 if ( j >= MAX_FRAME_SLIDE ) {
1249 if ( stopEvent & SE_BLOCKED ) {
1250 path.endPos = curStart;
1251 path.endEvent = SE_BLOCKED;
1257 curVelocity += gravity * frameTime * 0.001f;
1260 path.endTime = totalTime;
1261 path.endVelocity = curVelocity;
1262 path.endPos = curStart;
1270 ===============================================================================
1272 Trajectory Prediction
1274 Finds the best collision free trajectory for a clip model based on an
1275 initial position, target position and speed.
1277 ===============================================================================
1281 =====================
1284 get the ideal aim pitch angle in order to hit the target
1285 also get the time it takes for the projectile to arrive at the target
1286 =====================
1288 typedef struct ballistics_s {
1289 float angle; // angle in degrees in the range [-180, 180]
1290 float time; // time it takes before the projectile arrives
1293 static int Ballistics( const idVec3 &start, const idVec3 &end, float speed, float gravity, ballistics_t bal[2] ) {
1295 float x, y, a, b, c, d, sqrtd, inva, p[2];
1297 x = ( end.ToVec2() - start.ToVec2() ).Length();
1298 y = end[2] - start[2];
1300 a = 4.0f * y * y + 4.0f * x * x;
1301 b = -4.0f * speed * speed - 4.0f * y * gravity;
1302 c = gravity * gravity;
1304 d = b * b - 4.0f * a * c;
1305 if ( d <= 0.0f || a == 0.0f ) {
1308 sqrtd = idMath::Sqrt( d );
1310 p[0] = ( - b + sqrtd ) * inva;
1311 p[1] = ( - b - sqrtd ) * inva;
1313 for ( i = 0; i < 2; i++ ) {
1314 if ( p[i] <= 0.0f ) {
1317 d = idMath::Sqrt( p[i] );
1318 bal[n].angle = atan2( 0.5f * ( 2.0f * y * p[i] - gravity ) / d, d * x );
1319 bal[n].time = x / ( cos( bal[n].angle ) * speed );
1320 bal[n].angle = idMath::AngleNormalize180( RAD2DEG( bal[n].angle ) );
1328 =====================
1331 Returns the maximum hieght of a given trajectory
1332 =====================
1334 static float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ) {
1338 // maximum height of projectile
1339 maxHeight = start.z - 0.5f * gravity * ( t * t );
1345 =====================
1346 idAI::TestTrajectory
1347 =====================
1349 bool idAI::TestTrajectory( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ) {
1351 float maxHeight, t, t2;
1357 // maximum height of projectile
1358 maxHeight = start.z - 0.5f * gravity * ( t * t );
1359 // time it takes to fall from the top to the end height
1360 t = idMath::Sqrt( ( maxHeight - end.z ) / ( 0.5f * -gravity ) );
1362 // start of parabolic
1367 // point in the middle between top and start
1368 t2 = ( time - t ) * 0.5f;
1369 points[1].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time );
1370 points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
1373 points[2].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time );
1374 points[2].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
1375 // point in the middel between top and end
1376 t2 = time - t * 0.5f;
1377 points[3].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time );
1378 points[3].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
1381 // point halfway through
1383 points[1].ToVec2() = start.ToVec2() + ( end.ToVec2() - start.ToVec2() ) * 0.5f;
1384 points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
1388 points[numSegments] = end;
1391 for ( i = 0; i < numSegments; i++ ) {
1392 gameRenderWorld->DebugLine( colorRed, points[i], points[i+1], drawtime );
1396 // make sure projectile doesn't go higher than we want it to go
1397 for ( i = 0; i < numSegments; i++ ) {
1398 if ( points[i].z > max_height ) {
1399 // goes higher than we want to allow
1405 for ( i = 0; i < numSegments; i++ ) {
1406 gameLocal.clip.Translation( trace, points[i], points[i+1], clip, mat3_identity, clipmask, ignore );
1407 if ( trace.fraction < 1.0f ) {
1408 if ( gameLocal.GetTraceEntity( trace ) == targetEntity ) {
1419 gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, clip->GetBounds().Expand( 1.0f ), trace.endpos, drawtime );
1421 idBounds bnds( trace.endpos );
1422 bnds.ExpandSelf( 1.0f );
1423 gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, bnds, vec3_zero, drawtime );
1431 =====================
1432 idAI::PredictTrajectory
1434 returns true if there is a collision free trajectory for the clip model
1435 aimDir is set to the ideal aim direction in order to hit the target
1436 =====================
1438 bool idAI::PredictTrajectory( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ) {
1440 float zVel, a, t, pitch, s, c;
1442 ballistics_t ballistics[2];
1445 idVec3 lastPos, pos;
1447 assert( targetEntity );
1449 // check if the projectile starts inside the target
1450 if ( targetEntity->GetPhysics()->GetAbsBounds().IntersectsBounds( clip->GetBounds().Translate( firePos ) ) ) {
1451 aimDir = target - firePos;
1456 // if no velocity or the projectile is not affected by gravity
1457 if ( projectileSpeed <= 0.0f || projGravity == vec3_origin ) {
1459 aimDir = target - firePos;
1462 gameLocal.clip.Translation( trace, firePos, target, clip, mat3_identity, clipmask, ignore );
1465 gameRenderWorld->DebugLine( colorRed, firePos, target, drawtime );
1466 idBounds bnds( trace.endpos );
1467 bnds.ExpandSelf( 1.0f );
1468 gameRenderWorld->DebugBounds( ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ) ? colorGreen : colorYellow, bnds, vec3_zero, drawtime );
1471 return ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) );
1474 n = Ballistics( firePos, target, projectileSpeed, projGravity[2], ballistics );
1476 // there is no valid trajectory
1477 aimDir = target - firePos;
1482 // make sure the first angle is the smallest
1484 if ( ballistics[1].angle < ballistics[0].angle ) {
1485 a = ballistics[0].angle; ballistics[0].angle = ballistics[1].angle; ballistics[1].angle = a;
1486 t = ballistics[0].time; ballistics[0].time = ballistics[1].time; ballistics[1].time = t;
1490 // test if there is a collision free trajectory
1491 for ( i = 0; i < n; i++ ) {
1492 pitch = DEG2RAD( ballistics[i].angle );
1493 idMath::SinCos( pitch, s, c );
1494 dir[i] = target - firePos;
1496 dir[i] *= c * idMath::InvSqrt( dir[i].LengthSqr() );
1499 zVel = projectileSpeed * dir[i].z;
1501 if ( ai_debugTrajectory.GetBool() ) {
1502 t = ballistics[i].time / 100.0f;
1503 velocity = dir[i] * projectileSpeed;
1506 for ( j = 1; j < 100; j++ ) {
1507 pos += velocity * t;
1508 velocity += projGravity * t;
1509 gameRenderWorld->DebugLine( colorCyan, lastPos, pos );
1514 if ( TestTrajectory( firePos, target, zVel, projGravity[2], ballistics[i].time, firePos.z + max_height, clip, clipmask, ignore, targetEntity, drawtime ) ) {
1522 // there is no collision free trajectory