BVB Source Codes

CRYENGINE Show MovementPlanner.cpp Source code

Return Download CRYENGINE: download MovementPlanner.cpp Source code - Download CRYENGINE Source code - Type:.cpp
  1. // Copyright 2001-2016 Crytek GmbH / Crytek Group. All rights reserved.
  2.  
  3. #include "StdAfx.h"
  4. #include "MovementPlanner.h"
  5. #include "MovementActor.h"
  6. #include <CryAISystem/MovementUpdateContext.h>
  7. #include "MovementSystem.h"
  8.  
  9. #include "MovementBlock_FollowPath.h"
  10. #include "MovementBlock_HarshStop.h"
  11. #include "MovementBlock_InstallAgentInCover.h"
  12. #include "MovementBlock_SetupPipeUserCoverInformation.h"
  13. #include "MovementBlock_TurnTowardsPosition.h"
  14. #include "MovementBlock_UninstallAgentFromCover.h"
  15. #include "MovementBlock_UseSmartObject.h"
  16. #include "MovementBlock_UseExactPositioning.h"
  17.  
  18. #include "../Navigation/NavigationSystem/NavigationSystem.h"
  19.  
  20. namespace Movement
  21. {
  22. GenericPlanner::GenericPlanner(NavigationAgentTypeID navigationAgentTypeID)
  23.         : m_navigationAgentTypeID(navigationAgentTypeID)
  24.         , m_pendingPathReplanning()
  25.         , m_pathfinderRequestQueued(false)
  26. {
  27.         gAIEnv.pNavigationSystem->AddMeshChangeCallback(m_navigationAgentTypeID, functor(*this, &GenericPlanner::OnNavigationMeshChanged));
  28. }
  29.  
  30. GenericPlanner::~GenericPlanner()
  31. {
  32.         gAIEnv.pNavigationSystem->RemoveMeshChangeCallback(m_navigationAgentTypeID, functor(*this, &GenericPlanner::OnNavigationMeshChanged));
  33. }
  34.  
  35. bool GenericPlanner::IsUpdateNeeded() const
  36. {
  37.         return m_plan.HasBlocks() || m_pathfinderRequestQueued;
  38. }
  39.  
  40. void GenericPlanner::StartWorkingOnRequest(const MovementRequestID& requestId, const MovementRequest& request, const MovementUpdateContext& context)
  41. {
  42.         m_replanningAfterFailCount = 0;
  43.         m_pendingPathReplanning.Clear();
  44.         StartWorkingOnRequest_Internal(requestId, request, context);
  45. }
  46.  
  47. void GenericPlanner::StartWorkingOnRequest_Internal(const MovementRequestID& requestId, const MovementRequest& request, const MovementUpdateContext& context)
  48. {
  49.         assert(IsReadyForNewRequest());
  50.  
  51.         m_requestId = requestId;
  52.         m_request = request;
  53.  
  54.         if (request.type == MovementRequest::MoveTo)
  55.         {
  56.                 if (!m_request.style.IsMovingAlongDesignedPath())
  57.                 {
  58.                         // Future: We could path find from a bit further along the current plan.
  59.  
  60.                         context.actor.RequestPathTo(request.destination, request.lengthToTrimFromThePathEnd, request.dangersFlags, request.considerActorsAsPathObstacles);
  61.                         m_pathfinderRequestQueued = true;
  62.  
  63.                         //
  64.                         // Problem
  65.                         //  What if the controller starts planning the path from the current
  66.                         //  position while following a path and then keeps executing the plan
  67.                         //  and starts using a smart object and then a path comes back?
  68.                         //  This is not cool because we can't replace the current plan with
  69.                         //  the new one while traversing the smart object.
  70.                         //
  71.                         // Solution
  72.                         //  The controller can only start planning when not traversing a
  73.                         //  smart object and it must cut off all upcoming smart object blocks.
  74.                         //  This guarantees that when we get the path we'll be running a block
  75.                         //  that is interruptible, or no block. Good, this is what we want.
  76.                         //
  77.  
  78.                         m_plan.CutOffAfterCurrentBlock();
  79.                 }
  80.                 else
  81.                 {
  82.                         // If we are following a path we don't need to request a path.
  83.                         // We can directly produce a plan.
  84.                         ProducePlan(context);
  85.                 }
  86.         }
  87.         else if (request.type == MovementRequest::Stop)
  88.         {
  89.                 ProducePlan(context);
  90.         }
  91.         else
  92.         {
  93.                 assert(false);   // Unsupported request type
  94.         }
  95. }
  96.  
  97. void GenericPlanner::CancelCurrentRequest(MovementActor& actor)
  98. {
  99.         //
  100.         // The request has been canceled but the plan remains intact.
  101.         // This means that if the actor is running along a path he will keep
  102.         // keep running along that path.
  103.         //
  104.         // The idea is that an actor should only stop if a stop is
  105.         // explicitly requested.
  106.         //
  107.         // Let's see how it works out in practice :)
  108.         //
  109.  
  110.         m_requestId = MovementRequestID();
  111. }
  112.  
  113. IPlanner::Status GenericPlanner::Update(const MovementUpdateContext& context)
  114. {
  115.         IPlanner::Status status;
  116.  
  117.         if (m_pathfinderRequestQueued)
  118.         {
  119.                 CheckOnPathfinder(context, OUT status);
  120.  
  121.                 if (status.HasPathfinderFailed())
  122.                 {
  123.                         return status;
  124.                 }
  125.         }
  126.  
  127.         if (!m_pendingPathReplanning.bNavMeshChanged)
  128.         {
  129.                 CheckForNeedToPathReplanningDueToNavMeshChanges(context);
  130.         }
  131.  
  132.         if (m_pendingPathReplanning.IsPending())
  133.         {
  134.                 if (IsReadyForNewRequest())
  135.                 {
  136.                         context.actor.Log("Movement plan couldn't be finished either due to path-invalidation from NavMesh change or due to sudden non-interruptible block, re-planning now.");
  137.                         StartWorkingOnRequest(m_requestId, m_request, context);
  138.                 }
  139.         }
  140.  
  141.         ExecuteCurrentPlan(context, OUT status);
  142.  
  143.         return status;
  144. }
  145.  
  146. void GenericPlanner::OnNavigationMeshChanged(NavigationAgentTypeID navigationAgentTypeID, NavigationMeshID meshID, uint32 tileID)
  147. {
  148.         if (gAIEnv.CVars.MovementSystemPathReplanningEnabled)
  149.         {
  150.                 //
  151.                 // We're interested in the NavMesh-change only when we have a plan that we're executing or are still path-finding.
  152.                 //
  153.                 // Notice: The case where we're still waiting for the MNMPathfinder to find us a path is already handled by the MNMPathfinder itself.
  154.                 //         The NavigationSystem::Update() method is carefully designed such that the order of [MNMPathfinder gets notified of NavMesh-changes],
  155.                 //         [re-plans], [notifies of his found path] and the [time until GenericPlanner builds the plan by a call from MovementSystem::Update()] is even handling
  156.                 //         the case where a freshly found path would immediately become invalid on the next update loop and get detected by GenericPlanner as he already has
  157.                 //         a plan by that time.
  158.                 //         In other words: an extra OR-check for "m_pathfinderRequestQueued == true" is not necessary here.
  159.                 //
  160.                 if (m_plan.HasBlocks())
  161.                 {
  162.                         //
  163.                         // Ignore NavMesh changes when moving along a designer-placed path. It's the responsibility of the
  164.                         // level designer to place paths such that they will not interfere with (his scripted) NavMesh changes.
  165.                         //
  166.                         if (!m_request.style.IsMovingAlongDesignedPath())
  167.                         {
  168.                                 // extra check for whether we're already aware of some previous NavMesh-change having invalidated our path (we're just waiting for the SmartObject-traversal to finish before re-planning)
  169.                                 if (!m_pendingPathReplanning.bNavMeshChanged)
  170.                                 {
  171.                                         stl::push_back_unique(m_queuedNavMeshChanges, MeshIDAndTileID(meshID, tileID));
  172.                                 }
  173.                         }
  174.                 }
  175.         }
  176. }
  177.  
  178. void GenericPlanner::CheckForNeedToPathReplanningDueToNavMeshChanges(const MovementUpdateContext& context)
  179. {
  180.         AIAssert(!m_pendingPathReplanning.bNavMeshChanged);
  181.  
  182.         //
  183.         // React to NavMesh changes:
  184.         // If we're no longer path-finding, we must have a plan that we might be executing already, so check with the path-follower if any NavMesh change will affect the path that
  185.         // is being traversed by the plan.
  186.         //
  187.  
  188.         if (!m_pathfinderRequestQueued && m_plan.HasBlocks())
  189.         {
  190.                 if (!m_pendingPathReplanning.bNavMeshChanged && !m_queuedNavMeshChanges.empty())
  191.                 {
  192.                         //
  193.                         // Check with the path-follower for whether any of the NavMesh changes affects the path we're traversing.
  194.                         //
  195.  
  196.                         for (std::vector<MeshIDAndTileID>::const_iterator it = m_queuedNavMeshChanges.begin(), end = m_queuedNavMeshChanges.end(); it != end; ++it)
  197.                         {
  198.                                 if (context.pathFollower.IsRemainingPathAffectedByNavMeshChange(it->meshID, it->tileID))
  199.                                 {
  200.                                         m_pendingPathReplanning.bNavMeshChanged = true;
  201.                                         break;
  202.                                 }
  203.                         }
  204.                 }
  205.  
  206.                 //
  207.                 // Now, we don't need the collected NavMesh changes anymore.
  208.                 //
  209.  
  210.                 stl::free_container(m_queuedNavMeshChanges);
  211.         }
  212. }
  213.  
  214. bool GenericPlanner::IsReadyForNewRequest() const
  215. {
  216.         if (m_pathfinderRequestQueued)
  217.                 return false;
  218.  
  219.         return m_plan.InterruptibleNow();
  220. }
  221.  
  222. void GenericPlanner::GetStatus(MovementRequestStatus& status) const
  223. {
  224.         if (m_pathfinderRequestQueued)
  225.         {
  226.                 status.id = MovementRequestStatus::FindingPath;
  227.         }
  228.         else
  229.         {
  230.                 status.id = MovementRequestStatus::ExecutingPlan;
  231.         }
  232.  
  233.         if (m_plan.GetBlockCount() > 0)
  234.         {
  235.                 status.planStatus.abandoned = m_requestId.IsInvalid();
  236.                 status.planStatus.currentBlockIndex = m_plan.GetCurrentBlockIndex();
  237.  
  238.                 switch (m_plan.GetLastStatus())
  239.                 {
  240.                 case Plan::Status::Running:
  241.                         status.planStatus.status = MovementRequestStatus::PlanStatus::Running;
  242.                         break;
  243.                 case Plan::Status::CantBeFinished:
  244.                         status.planStatus.status = MovementRequestStatus::PlanStatus::CantFinish;
  245.                         break;
  246.                 case Plan::Status::Finished:
  247.                         status.planStatus.status = MovementRequestStatus::PlanStatus::Finished;
  248.                         break;
  249.                 default:
  250.                         status.planStatus.status = MovementRequestStatus::PlanStatus::None;
  251.                         break;
  252.                 }
  253.  
  254.                 for (uint32 i = 0, n = m_plan.GetBlockCount(); i < n; ++i)
  255.                 {
  256.                         const char* blockName = m_plan.GetBlock(i)->GetName();
  257.                         status.planStatus.blockInfos.push_back(blockName);
  258.                 }
  259.         }
  260.         else
  261.         {
  262.                 status.planStatus.status = MovementRequestStatus::PlanStatus::None;
  263.                 status.planStatus.abandoned = false;
  264.                 status.planStatus.currentBlockIndex = Plan::NoBlockIndex;
  265.         }
  266. }
  267.  
  268. void GenericPlanner::CheckOnPathfinder(const MovementUpdateContext& context, OUT IPlanner::Status& status)
  269. {
  270.         const PathfinderState state = context.actor.GetPathfinderState();
  271.  
  272.         const bool pathfinderFinished = (state != StillFinding);
  273.         if (pathfinderFinished)
  274.         {
  275.                 m_pathfinderRequestQueued = false;
  276.  
  277.                 if (state == FoundPath)
  278.                 {
  279.                         // Careful: of course, we started pathfinding at a time when only interruptible blocks were running, but:
  280.                         //          At the time we received the resulting path, a UseSmartObject block might have just transitioned from its internal "Prepare" state to "Traverse" which
  281.                         //          means that this block suddenly is no longer interruptible (!) So, just don't replace the ongoing plan, but set a pending-flag to let Update() do a full re-pathfinding
  282.                         //          at an appropriate time.
  283.  
  284.                         if (m_plan.InterruptibleNow())
  285.                         {
  286.                                 // fine, the pathfind request started and finished within an interruptible block
  287.                                 ProducePlan(context);
  288.                         }
  289.                         else
  290.                         {
  291.                                 // oh oh... we started in an interruptible block (fine), but received the path when that particular block was suddenly no longer interruptible!
  292.                                 m_pendingPathReplanning.bSuddenNonInterruptibleBlock = true;
  293.                         }
  294.                 }
  295.                 else
  296.                 {
  297.                         status.SetPathfinderFailed(m_requestId);
  298.                 }
  299.         }
  300. }
  301.  
  302. void GenericPlanner::ExecuteCurrentPlan(const MovementUpdateContext& context, OUT IPlanner::Status& status)
  303. {
  304.         if (m_plan.HasBlocks())
  305.         {
  306.                 const Plan::Status s = m_plan.Execute(context);
  307.  
  308.                 if (s == Plan::Finished)
  309.                 {
  310.                         status.SetRequestSatisfied(m_plan.GetRequestId());
  311.                         m_plan.Clear(context.actor);
  312.                 }
  313.                 else if (s == Plan::CantBeFinished)
  314.                 {
  315.                         // The current plan can't be finished. We'll re-plan as soon as
  316.                         // we can to see if we can satisfy the request.
  317.                         // Note: it could become necessary to add a "retry count".
  318.                         if (m_request.style.IsMovingAlongDesignedPath())
  319.                         {
  320.                                 status.SetMovingAlongPathFailed(m_plan.GetRequestId());
  321.                         }
  322.                         else if (IsReadyForNewRequest())
  323.                         {
  324.                                 if (CanReplan(m_request))
  325.                                 {
  326.                                         ++m_replanningAfterFailCount;
  327.  
  328.                                         context.actor.Log("Movement plan couldn't be finished, re-planning.");
  329.                                         const MovementRequest replanRequest = m_request;
  330.                                         StartWorkingOnRequest_Internal(m_requestId, replanRequest, context);
  331.                                 }
  332.                                 else
  333.                                 {
  334.                                         status.SetReachedMaxAllowedReplans(m_plan.GetRequestId());
  335.                                 }
  336.                         }
  337.                 }
  338.                 else
  339.                 {
  340.                         assert(s == Plan::Running);
  341.                 }
  342.         }
  343. }
  344.  
  345. void GenericPlanner::ProducePlan(const MovementUpdateContext& context)
  346. {
  347.         // Future: It would be good to respect the current plan and patch it
  348.         // up with the newly found path. This would also require that we
  349.         // kick off the path finding process with a predicted future position.
  350.  
  351.         // We should have shaved off non-interruptible movement blocks in
  352.         // StartWorkingOnRequest before we started path finding.
  353.         assert(m_plan.InterruptibleNow());
  354.  
  355.         m_plan.Clear(context.actor);
  356.         m_plan.SetRequestId(m_requestId);
  357.  
  358.         switch (m_request.type)
  359.         {
  360.         case MovementRequest::MoveTo:
  361.                 ProduceMoveToPlan(context);
  362.                 break;
  363.  
  364.         case MovementRequest::Stop:
  365.                 ProduceStopPlan(context);
  366.                 break;
  367.  
  368.         default:
  369.                 assert(false);
  370.         }
  371.  
  372.         context.actor.GetAdapter().OnMovementPlanProduced();
  373. }
  374.  
  375. void GenericPlanner::ProduceMoveToPlan(const MovementUpdateContext& context)
  376. {
  377.         using namespace Movement::MovementBlocks;
  378.         if (m_request.style.IsMovingAlongDesignedPath())
  379.         {
  380.                 // If the agent is forced to follow an AIPath then only the
  381.                 // FollowPath block is needed
  382.                 SShape designedPath;
  383.                 if (!context.actor.GetAdapter().GetDesignedPath(designedPath))
  384.                         return;
  385.  
  386.                 if (designedPath.shape.empty())
  387.                         return;
  388.  
  389.                 TPathPoints fullPath(designedPath.shape.begin(), designedPath.shape.end());
  390.                 CNavPath navPath;
  391.                 navPath.SetPathPoints(fullPath);
  392.                 m_plan.AddBlock(BlockPtr(new FollowPath(navPath, .0f, m_request.style, false)));
  393.         }
  394.         else
  395.         {
  396.                 const INavPath* pNavPath = context.actor.GetCallbacks().getPathFunction();
  397.                 const TPathPoints fullPath = pNavPath->GetPath();
  398.  
  399.                 if (m_request.style.ShouldTurnTowardsMovementDirectionBeforeMoving())
  400.                 {
  401.                         if (fullPath.size() >= 2)
  402.                         {
  403.                                 // Stop moving before we turn. The turn expects to be standing still.
  404.                                 m_plan.AddBlock<HarshStop>();
  405.  
  406.                                 // Using the second point of the path. Ideally, we should turn
  407.                                 // towards a position once we've processed the path with the
  408.                                 // smart path follower to be sure we're really turning towards
  409.                                 // the direction we will later move towards. But the path has
  410.                                 // been somewhat straightened out already so this should be fine.
  411.                                 TPathPoints::const_iterator it = fullPath.begin();
  412.                                 ++it;
  413.                                 const Vec3 positionToTurnTowards = it->vPos;
  414.  
  415.                                 // Adding a dead-zone here because small inaccuracies
  416.                                 // in the positioning will otherwise make the object turn in a
  417.                                 // seemingly 'random' direction each time when it is ordered to
  418.                                 // move to the spot where it already is at.
  419.                                 static const float distanceThreshold = 0.2f;
  420.                                 static const float distanceThresholdSq = distanceThreshold * distanceThreshold;
  421.                                 const float distanceToTurnPointSq = Distance::Point_PointSq(positionToTurnTowards, context.actor.GetAdapter().GetPhysicsPosition());
  422.                                 if (distanceToTurnPointSq >= distanceThresholdSq)
  423.                                 {
  424.                                         m_plan.AddBlock(BlockPtr(new TurnTowardsPosition(positionToTurnTowards)));
  425.                                 }
  426.                         }
  427.                 }
  428.  
  429.                 if (context.actor.GetAdapter().IsInCover())
  430.                 {
  431.                         m_plan.AddBlock(BlockPtr(new UninstallAgentFromCover(m_request.style.GetStance())));
  432.                 }
  433.  
  434.                 if (m_request.style.IsMovingToCover())
  435.                 {
  436.                         m_plan.AddBlock<SetupActorCoverInformation>();
  437.                 }
  438.  
  439.                 // Go through the full path from start to end and split it up into
  440.                 // FollowPath & UseSmartObject blocks.
  441.                 TPathPoints::const_iterator first = fullPath.begin();
  442.                 TPathPoints::const_iterator curr = fullPath.begin();
  443.                 TPathPoints::const_iterator end = fullPath.end();
  444.  
  445.                 std::shared_ptr<UseSmartObject> lastAddedSmartObjectBlock;
  446.  
  447.                 assert(curr != end);
  448.  
  449.                 while (curr != end)
  450.                 {
  451.                         TPathPoints::const_iterator next = curr;
  452.                         ++next;
  453.  
  454.                         const PathPointDescriptor& point = *curr;
  455.  
  456.                         const bool isSmartObject = point.navType == IAISystem::NAV_SMARTOBJECT;
  457.                         const bool isCustomObject = point.navType == IAISystem::NAV_CUSTOM_NAVIGATION;
  458.                         const bool isLastNode = next == end;
  459.  
  460.                         CNavPath path;
  461.  
  462.                         if (isCustomObject || isSmartObject || isLastNode)
  463.                         {
  464.                                 // Extract the path between the first point and
  465.                                 // the smart object/last node we just found.
  466.                                 TPathPoints points;
  467.                                 points.assign(first, next);
  468.                                 path.SetPathPoints(points);
  469.  
  470.                                 const bool blockAfterThisIsUseExactPositioning = isLastNode && (m_request.style.GetExactPositioningRequest() != 0);
  471.                                 const bool blockAfterThisUsesSomeFormOfExactPositioning = isSmartObject || isCustomObject || blockAfterThisIsUseExactPositioning;
  472.                                 const float endDistance = blockAfterThisUsesSomeFormOfExactPositioning ? 2.5f : 0.0f;   // The value 2.5 meters was used prior to Crysis 3
  473.                                 const bool endsInCover = isLastNode && m_request.style.IsMovingToCover();
  474.                                 m_plan.AddBlock(BlockPtr(new FollowPath(path, endDistance, m_request.style, endsInCover)));
  475.  
  476.                                 if (lastAddedSmartObjectBlock)
  477.                                 {
  478.                                         lastAddedSmartObjectBlock->SetUpcomingPath(path);
  479.                                         lastAddedSmartObjectBlock->SetUpcomingStyle(m_request.style);
  480.                                 }
  481.  
  482.                                 if (blockAfterThisIsUseExactPositioning)
  483.                                 {
  484.                                         assert(!isSmartObject);
  485.                                         assert(!path.Empty());
  486.                                         m_plan.AddBlock(BlockPtr(new UseExactPositioning(path, m_request.style)));
  487.                                 }
  488.                         }
  489.  
  490.                         if (isSmartObject || isCustomObject)
  491.                         {
  492.                                 assert(!path.Empty());
  493.                                 if (isSmartObject)
  494.                                 {
  495.                                         std::shared_ptr<UseSmartObject> useSmartObjectBlock(new UseSmartObject(path, point.offMeshLinkData, m_request.style));
  496.                                         lastAddedSmartObjectBlock = useSmartObjectBlock;
  497.                                         m_plan.AddBlock(useSmartObjectBlock);
  498.                                 }
  499.                                 else
  500.                                 {
  501.                                         MovementSystem aiMovementSystem = static_cast<MovementSystem&>(context.movementSystem);
  502.                                         m_plan.AddBlock(aiMovementSystem.CreateCustomBlock(path, point.offMeshLinkData, m_request.style));
  503.                                 }
  504.  
  505.                                 ++curr;
  506.                                 first = curr;
  507.                         }
  508.                         else
  509.                         {
  510.                                 ++curr;
  511.                         }
  512.                 }
  513.  
  514.                 if (m_request.style.IsMovingToCover())
  515.                 {
  516.                         m_plan.AddBlock<InstallAgentInCover>();
  517.                 }
  518.         }
  519. }
  520.  
  521. void GenericPlanner::ProduceStopPlan(const MovementUpdateContext& context)
  522. {
  523.         using namespace Movement::MovementBlocks;
  524.  
  525.         m_plan.AddBlock(BlockPtr(new HarshStop()));
  526. }
  527.  
  528. bool GenericPlanner::CanReplan(const MovementRequest& request) const
  529. {
  530.         return m_replanningAfterFailCount < s_maxAllowedReplanning;
  531. }
  532. }
  533.  
downloadMovementPlanner.cpp Source code - Download CRYENGINE Source code
Related Source Codes/Software:
postal - 2017-06-11
reactide - Reactide is the first dedicated IDE for React web ... 2017-06-11
rkt - rkt is a pod-native container engine for Linux. It... 2017-06-11
uWebSockets - Tiny WebSockets https://for... 2017-06-11
realworld - TodoMVC for the RealWorld - Exemplary fullstack Me... 2017-06-11
CRYENGINE - CRYENGINE is a powerful real-time game development... 2017-06-11
goreplay - GoReplay is an open-source tool for capturing and ... 2017-06-10
pyenv - Simple Python version management 2017-06-10
redux-saga - An alternative side effect model for Redux apps ... 2017-06-10
angular-starter - 2017-06-10

 Back to top