You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
884 lines
37 KiB
C++
884 lines
37 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
#include "LmbrCentral_precompiled.h"
|
|
#include "NavigationComponent.h"
|
|
#include "EditorNavigationUtil.h"
|
|
|
|
#include <IPathfinder.h>
|
|
#include <physinterface.h>
|
|
#include <MathConversion.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzFramework/Physics/RigidBodyBus.h>
|
|
#include <AzFramework/Physics/CharacterBus.h>
|
|
#include <AzFramework/Physics/PhysicsScene.h>
|
|
#include <AzFramework/Physics/Common/PhysicsTypes.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/Interface/Interface.h>
|
|
#ifdef LMBR_CENTRAL_EDITOR
|
|
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
|
#endif
|
|
|
|
namespace LmbrCentral
|
|
{
|
|
// Behavior Context forwarder for NavigationComponentNotificationBus
|
|
class BehaviorNavigationComponentNotificationBusHandler : public NavigationComponentNotificationBus::Handler, public AZ::BehaviorEBusHandler
|
|
{
|
|
public:
|
|
AZ_EBUS_BEHAVIOR_BINDER_WITH_DOC(BehaviorNavigationComponentNotificationBusHandler,"{6D060202-06BA-470E-8F6B-E1982360C752}",AZ::SystemAllocator
|
|
, OnSearchingForPath, ({"RequestId","Navigation request Id"})
|
|
, OnTraversalStarted, ({"RequestId","Navigation request Id"})
|
|
, OnTraversalPathUpdate, ({"RequestId","Navigation request Id"},{"NextPathPosition","Next path position"},{"InflectionPosition","Next inflection position"})
|
|
, OnTraversalInProgress, ({"RequestId","Navigation request Id"},{"Distance","Distance remaining"})
|
|
, OnTraversalComplete, ({"RequestId","Navigation request Id"})
|
|
, OnTraversalCancelled, ({"RequestId","Navigation request Id"}));
|
|
|
|
void OnSearchingForPath(PathfindRequest::NavigationRequestId requestId) override
|
|
{
|
|
Call(FN_OnSearchingForPath, requestId);
|
|
}
|
|
|
|
void OnTraversalStarted(PathfindRequest::NavigationRequestId requestId) override
|
|
{
|
|
Call(FN_OnTraversalStarted, requestId);
|
|
}
|
|
|
|
void OnTraversalPathUpdate(PathfindRequest::NavigationRequestId requestId, const AZ::Vector3& nextPathPosition, const AZ::Vector3& inflectionPosition) override
|
|
{
|
|
Call(FN_OnTraversalPathUpdate, requestId, nextPathPosition, inflectionPosition);
|
|
}
|
|
|
|
void OnTraversalInProgress(PathfindRequest::NavigationRequestId requestId, float distanceRemaining) override
|
|
{
|
|
Call(FN_OnTraversalInProgress, requestId, distanceRemaining);
|
|
}
|
|
|
|
void OnTraversalComplete(PathfindRequest::NavigationRequestId requestId) override
|
|
{
|
|
Call(FN_OnTraversalComplete, requestId);
|
|
}
|
|
|
|
void OnTraversalCancelled(PathfindRequest::NavigationRequestId requestId) override
|
|
{
|
|
Call(FN_OnTraversalCancelled, requestId);
|
|
}
|
|
};
|
|
|
|
|
|
PathfindRequest::NavigationRequestId PathfindResponse::s_nextRequestId = kInvalidRequestId;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//=========================================================================
|
|
bool NavigationComponentVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
|
|
{
|
|
if (classElement.GetVersion() < 4)
|
|
{
|
|
// "Move Physically" changed to "Movement Method"
|
|
constexpr const char* movePhysicallyName = "Move Physically";
|
|
constexpr const char* movementMethodName = "Movement Method";
|
|
int movePhysicallyIndex = classElement.FindElement(AZ_CRC(movePhysicallyName));
|
|
if (movePhysicallyIndex != -1)
|
|
{
|
|
bool movePhysically = false;
|
|
classElement.GetSubElement(movePhysicallyIndex).GetData(movePhysically);
|
|
classElement.RemoveElement(movePhysicallyIndex);
|
|
|
|
if (movePhysically)
|
|
{
|
|
classElement.AddElementWithData(context, movementMethodName, NavigationComponentRequests::MovementMethod::Physics);
|
|
}
|
|
else
|
|
{
|
|
classElement.AddElementWithData(context, movementMethodName, NavigationComponentRequests::MovementMethod::Transform);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void NavigationComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
|
{
|
|
serializeContext->Class<NavigationComponent, AZ::Component>()
|
|
->Version(4, &NavigationComponentVersionConverter)
|
|
->Field("Agent Type", &NavigationComponent::m_agentType)
|
|
->Field("Agent Speed", &NavigationComponent::m_agentSpeed)
|
|
->Field("Agent Radius", &NavigationComponent::m_agentRadius)
|
|
->Field("Arrival Distance Threshold", &NavigationComponent::m_arrivalDistanceThreshold)
|
|
->Field("Repath Threshold", &NavigationComponent::m_repathThreshold)
|
|
->Field("Movement Method", &NavigationComponent::m_movementMethod)
|
|
->Field("Allow Vertical Navigation", &NavigationComponent::m_allowVerticalNavigation);
|
|
|
|
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
|
{
|
|
editContext->Class<NavigationComponent>(
|
|
"Navigation", "The Navigation component provides basic pathfinding and pathfollowing services to an entity")
|
|
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
->Attribute(AZ::Edit::Attributes::Category, "AI")
|
|
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Navigation.svg")
|
|
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Navigation.png")
|
|
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
|
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.o3de.org/docs/user-guide/components/reference/navigation/")
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &NavigationComponent::m_agentSpeed, "Agent Speed",
|
|
"The speed of the agent while navigating ")
|
|
->DataElement(AZ::Edit::UIHandlers::ComboBox, &NavigationComponent::m_agentType, "Agent Type",
|
|
"Describes the type of the Entity for navigation purposes. ")
|
|
#ifdef LMBR_CENTRAL_EDITOR
|
|
->Attribute(AZ::Edit::Attributes::StringList, &NavigationComponent::PopulateAgentTypeList)
|
|
->Attribute(AZ::Edit::Attributes::ChangeNotify, &NavigationComponent::HandleAgentTypeChanged)
|
|
#endif
|
|
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &NavigationComponent::m_agentRadius, "Agent Radius",
|
|
"Radius of this Navigation Agent")
|
|
->Attribute(AZ::Edit::Attributes::ReadOnly, true)
|
|
->Attribute("Suffix", " m")
|
|
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &NavigationComponent::m_arrivalDistanceThreshold,
|
|
"Arrival Distance Threshold", "Describes the distance from the end point that an entity needs to be before its movement is to be stopped and considered complete")
|
|
->Attribute("Suffix", " m")
|
|
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &NavigationComponent::m_repathThreshold,
|
|
"Repath Threshold", "Describes the distance from its previously known location that a target entity needs to move before a new path is calculated")
|
|
->Attribute("Suffix", " m")
|
|
|
|
->DataElement(AZ::Edit::UIHandlers::ComboBox, &NavigationComponent::m_movementMethod,
|
|
"Movement Method", "Indicates the method used to move the entity, the default 'Transform' method will modify the position using the TransformBus")
|
|
->Attribute(AZ::Edit::Attributes::EnumValues,
|
|
AZStd::vector<AZ::Edit::EnumConstant<NavigationComponentRequests::MovementMethod>>
|
|
{
|
|
AZ::Edit::EnumConstant<NavigationComponentRequests::MovementMethod>(NavigationComponentRequests::MovementMethod::Transform,
|
|
"Transform"),
|
|
AZ::Edit::EnumConstant<NavigationComponentRequests::MovementMethod>(NavigationComponentRequests::MovementMethod::Physics,
|
|
"Physics"),
|
|
AZ::Edit::EnumConstant<NavigationComponentRequests::MovementMethod>(NavigationComponentRequests::MovementMethod::Custom,
|
|
"Custom")
|
|
})
|
|
|
|
->DataElement(AZ::Edit::UIHandlers::CheckBox, &NavigationComponent::m_allowVerticalNavigation,
|
|
"Allow Vertical Navigation", "Indicates whether vertical navigation is allowed or if navigation is constrained to the X and Y plane");
|
|
|
|
}
|
|
}
|
|
|
|
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
behaviorContext->EBus<NavigationComponentRequestBus>("NavigationComponentRequestBus")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
|
->Attribute(AZ::Script::Attributes::Category, "Navigation")
|
|
->Attribute(AZ::Script::Attributes::Module, "navigation")
|
|
->Event("FindPathToEntity", &NavigationComponentRequestBus::Events::FindPathToEntity, { { {"EntityId","The entity to follow"} } })
|
|
->Event("FindPathToPosition", &NavigationComponentRequestBus::Events::FindPathToPosition, { { {"Position","The position to navigate to"} } })
|
|
->Event("Stop", &NavigationComponentRequestBus::Events::Stop, { { {"RequestId","The request Id of the navigation process to stop"} } })
|
|
->Event("GetAgentSpeed", &NavigationComponentRequestBus::Events::GetAgentSpeed)
|
|
->Event("SetAgentSpeed", &NavigationComponentRequestBus::Events::SetAgentSpeed, { { {"Speed","The agent speed in meters per second"} } })
|
|
->Event("GetAgentMovementMethod", &NavigationComponentRequestBus::Events::GetAgentMovementMethod)
|
|
->Event("SetAgentMovementMethod", &NavigationComponentRequestBus::Events::SetAgentMovementMethod, { { {"Method","The movement method: Transform, Physics or Custom"} } });
|
|
|
|
behaviorContext->EBus<NavigationComponentNotificationBus>("NavigationComponentNotificationBus")
|
|
->Handler<BehaviorNavigationComponentNotificationBusHandler>();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Pathfind response
|
|
//! @param navComponent The navigation component being serviced by this response
|
|
PathfindResponse::PathfindResponse()
|
|
: m_requestId(kInvalidRequestId)
|
|
, m_pathfinderRequestId(kInvalidRequestId)
|
|
, m_responseStatus(PathfindResponse::Status::Uninitialized)
|
|
, m_navigationComponent(nullptr)
|
|
, m_previousAgentVelocity(AZ::Vector3::CreateZero())
|
|
, m_pathFollower(nullptr)
|
|
, m_nextPathPosition(AZ::Vector3::CreateZero())
|
|
, m_inflectionPosition(AZ::Vector3::CreateZero())
|
|
{
|
|
}
|
|
|
|
void PathfindResponse::SetupForNewRequest(NavigationComponent* ownerComponent, const PathfindRequest& request)
|
|
{
|
|
AZ_Assert(ownerComponent, "Invalid parent component.");
|
|
|
|
m_navigationComponent = ownerComponent;
|
|
m_request = request;
|
|
m_requestId = ++s_nextRequestId;
|
|
m_currentDestination = request.GetDestinationLocation();
|
|
m_previousAgentVelocity = AZ::Vector3::CreateZero();
|
|
|
|
// Reset State information
|
|
m_pathfinderRequestId = kInvalidRequestId;
|
|
m_currentPath.reset();
|
|
|
|
// Setup pathfollower instance.
|
|
PathFollowerParams params;
|
|
params.endAccuracy = m_navigationComponent->GetArrivalDistance();
|
|
params.normalSpeed = m_navigationComponent->GetAgentSpeed();
|
|
params.passRadius = m_navigationComponent->GetAgentRadius();
|
|
params.minSpeed = params.normalSpeed * 0.8f;
|
|
params.maxSpeed = params.normalSpeed * 1.2f;
|
|
params.stopAtEnd = true;
|
|
params.use2D = !m_navigationComponent->GetAllowVerticalNavigation();
|
|
m_pathFollower = nullptr;
|
|
|
|
// Disconnect from any notifications from earlier requests
|
|
AZ::TransformNotificationBus::Handler::BusDisconnect();
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
|
|
SetStatus(Status::Initialized);
|
|
|
|
// If this request is to follow a moving entity then connect to the transform notification bus for the target
|
|
if (m_request.HasTargetEntity())
|
|
{
|
|
SetStatus(Status::WaitingForTargetEntity);
|
|
AZ::TransformNotificationBus::Handler::BusConnect(m_request.GetTargetEntityId());
|
|
AZ::EntityBus::Handler::BusConnect(m_request.GetTargetEntityId());
|
|
}
|
|
}
|
|
|
|
void PathfindResponse::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world)
|
|
{
|
|
if (!m_navigationComponent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((m_responseStatus == Status::TraversalStarted) || (m_responseStatus == Status::TraversalInProgress))
|
|
{
|
|
auto delta = (world.GetTranslation() - GetCurrentDestination()).GetLength();
|
|
|
|
if (delta > m_navigationComponent->m_repathThreshold)
|
|
{
|
|
m_currentDestination = world.GetTranslation();
|
|
SetPathfinderRequestId(m_navigationComponent->RequestPath());
|
|
}
|
|
}
|
|
}
|
|
|
|
void PathfindResponse::OnEntityActivated(const AZ::EntityId&)
|
|
{
|
|
// Get the target entity's position
|
|
AZ::Transform entityTransform = AZ::Transform::CreateIdentity();
|
|
AZ::TransformBus::EventResult(entityTransform, m_request.GetTargetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
|
|
m_currentDestination = entityTransform.GetTranslation();
|
|
|
|
if (m_responseStatus == Status::WaitingForTargetEntity)
|
|
{
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
m_navigationComponent->FindPathImpl();
|
|
}
|
|
}
|
|
|
|
void PathfindResponse::OnEntityDeactivated(const AZ::EntityId&)
|
|
{
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void PathfindResponse::SetOwningComponent(NavigationComponent* navComponent)
|
|
{
|
|
m_navigationComponent = navComponent;
|
|
}
|
|
|
|
const PathfindRequest& PathfindResponse::GetRequest() const
|
|
{
|
|
return m_request;
|
|
}
|
|
|
|
PathfindRequest::NavigationRequestId PathfindResponse::GetRequestId() const
|
|
{
|
|
return m_requestId;
|
|
}
|
|
|
|
PathfindResponse::PathfinderRequestId PathfindResponse::GetPathfinderRequestId() const
|
|
{
|
|
return m_pathfinderRequestId;
|
|
}
|
|
|
|
void PathfindResponse::SetPathfinderRequestId(PathfinderRequestId pathfinderRequestId)
|
|
{
|
|
m_pathfinderRequestId = pathfinderRequestId;
|
|
}
|
|
|
|
const AZ::Vector3& PathfindResponse::GetCurrentDestination() const
|
|
{
|
|
return m_currentDestination;
|
|
}
|
|
|
|
PathfindResponse::Status PathfindResponse::GetStatus() const
|
|
{
|
|
return m_responseStatus;
|
|
}
|
|
|
|
void PathfindResponse::SetStatus(Status status)
|
|
{
|
|
m_responseStatus = status;
|
|
|
|
// If the traversal was cancelled or completed and the request was following an entity
|
|
if ((status >= Status::TraversalComplete) && m_request.HasTargetEntity())
|
|
{
|
|
// Disconnect from any notifications on the transform bust
|
|
AZ::TransformNotificationBus::Handler::BusDisconnect();
|
|
}
|
|
}
|
|
|
|
void PathfindResponse::SetCurrentPath(const INavPathPtr& currentPath)
|
|
{
|
|
m_currentPath = currentPath;
|
|
|
|
if (m_pathFollower)
|
|
{
|
|
m_pathFollower->AttachToPath(m_currentPath.get());
|
|
}
|
|
}
|
|
|
|
INavPathPtr PathfindResponse::GetCurrentPath()
|
|
{
|
|
return m_currentPath;
|
|
}
|
|
|
|
void PathfindResponse::Reset()
|
|
{
|
|
PathfindResponse::Status lastResponseStatus = GetStatus();
|
|
|
|
// If there is already a Request being serviced
|
|
if (lastResponseStatus > PathfindResponse::Status::Initialized
|
|
&& lastResponseStatus < PathfindResponse::Status::TraversalComplete)
|
|
{
|
|
// If the pathfinding request was still being serviced by the pathfinder
|
|
if (lastResponseStatus >= PathfindResponse::Status::SearchingForPath
|
|
&& lastResponseStatus <= PathfindResponse::Status::TraversalInProgress)
|
|
{
|
|
// and If the request was a valid one
|
|
if (GetRequestId() != PathfindResponse::kInvalidRequestId)
|
|
{
|
|
// Cancel that request with the pathfinder
|
|
IMNMPathfinder* pathFinder = nullptr; // INavigationSystem will be converted to an AZInterface (LY-111343)
|
|
if (pathFinder)
|
|
{
|
|
pathFinder->CancelPathRequest(GetPathfinderRequestId());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Indicate that traversal on this request was cancelled
|
|
SetStatus(PathfindResponse::Status::TraversalCancelled);
|
|
|
|
// Inform every listener on this entity that traversal was cancelled.
|
|
NavigationComponentNotificationBus::Event(m_navigationComponent->GetEntityId(),
|
|
&NavigationComponentNotificationBus::Events::OnTraversalCancelled, GetRequestId());
|
|
}
|
|
|
|
m_pathFollower.reset();
|
|
}
|
|
|
|
const AZ::Vector3& PathfindResponse::GetLastKnownAgentVelocity() const
|
|
{
|
|
return m_previousAgentVelocity;
|
|
}
|
|
|
|
void PathfindResponse::SetLastKnownAgentVelocity(const AZ::Vector3& newVelocity)
|
|
{
|
|
m_previousAgentVelocity = newVelocity;
|
|
}
|
|
|
|
const AZ::Vector3& PathfindResponse::GetNextPathPosition() const
|
|
{
|
|
return m_nextPathPosition;
|
|
}
|
|
|
|
void PathfindResponse::SetNextPathPosition(const AZ::Vector3& newPosition)
|
|
{
|
|
m_nextPathPosition = newPosition;
|
|
}
|
|
|
|
const AZ::Vector3& PathfindResponse::GetInflectionPosition() const
|
|
{
|
|
return m_inflectionPosition;
|
|
}
|
|
|
|
void PathfindResponse::SetInflectionPosition(const AZ::Vector3& newPosition)
|
|
{
|
|
m_inflectionPosition = newPosition;
|
|
}
|
|
|
|
IPathFollowerPtr PathfindResponse::GetPathFollower()
|
|
{
|
|
return m_pathFollower;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
NavigationComponent::NavigationComponent()
|
|
: m_agentSpeed(1.f)
|
|
, m_agentRadius(4.f)
|
|
, m_arrivalDistanceThreshold(0.25f)
|
|
, m_repathThreshold(1.f)
|
|
, m_movementMethod(NavigationComponentRequests::MovementMethod::Physics)
|
|
, m_usesCharacterPhysics(false)
|
|
, m_allowVerticalNavigation(false)
|
|
, m_sceneStartSimHandler([this](
|
|
[[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
|
|
float fixedDeltatime
|
|
)
|
|
{
|
|
this->MoveEntity(fixedDeltatime);
|
|
}, aznumeric_cast<int32_t>(AzPhysics::SceneEvents::PhysicsStartFinishSimulationPriority::Components))
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// AZ::Component interface implementation
|
|
void NavigationComponent::Init()
|
|
{
|
|
m_lastResponseCache.SetOwningComponent(this);
|
|
const INavigationSystem* navigationSystem = nullptr; // INavigationSystem will be converted to an AZInterface (LY-111343)
|
|
if (navigationSystem)
|
|
{
|
|
m_agentTypeId = navigationSystem->GetAgentTypeID(m_agentType.c_str());
|
|
}
|
|
}
|
|
|
|
void NavigationComponent::Activate()
|
|
{
|
|
const AZ::EntityId& entityId = GetEntityId();
|
|
|
|
NavigationComponentRequestBus::Handler::BusConnect(entityId);
|
|
AZ::TransformNotificationBus::Handler::BusConnect(entityId);
|
|
|
|
if (m_movementMethod == NavigationComponentRequests::MovementMethod::Physics)
|
|
{
|
|
bool usesLegacyCharacterPhysics = false;
|
|
|
|
|
|
const bool usesAZCharacterPhysics = Physics::CharacterRequestBus::FindFirstHandler(entityId) != nullptr;
|
|
m_usesCharacterPhysics = usesLegacyCharacterPhysics || usesAZCharacterPhysics;
|
|
|
|
AZ_Warning("NavigationComponent",
|
|
usesAZCharacterPhysics || Physics::RigidBodyRequestBus::FindFirstHandler(entityId),
|
|
"Entity %s cannot be moved physically because it is missing a physics component", GetEntity()->GetName().c_str());
|
|
}
|
|
|
|
AZ::TransformBus::EventResult(m_entityTransform, entityId, &AZ::TransformBus::Events::GetWorldTM);
|
|
}
|
|
|
|
void NavigationComponent::Deactivate()
|
|
{
|
|
NavigationComponentRequestBus::Handler::BusDisconnect();
|
|
AZ::TransformNotificationBus::Handler::BusDisconnect();
|
|
|
|
Reset();
|
|
}
|
|
#ifdef LMBR_CENTRAL_EDITOR
|
|
float NavigationComponent::CalculateAgentNavigationRadius(const char* agentTypeName)
|
|
{
|
|
float agentRadius = -1.0f;
|
|
AzToolsFramework::EditorRequests::Bus::BroadcastResult(
|
|
agentRadius, &AzToolsFramework::EditorRequests::Bus::Events::CalculateAgentNavigationRadius
|
|
, agentTypeName);
|
|
|
|
return agentRadius;
|
|
}
|
|
|
|
const char* NavigationComponent::GetDefaultAgentNavigationTypeName()
|
|
{
|
|
const char* agentTypeName = "";
|
|
AzToolsFramework::EditorRequests::Bus::BroadcastResult(
|
|
agentTypeName, &AzToolsFramework::EditorRequests::Bus::Events::GetDefaultAgentNavigationTypeName);
|
|
|
|
return agentTypeName;
|
|
}
|
|
|
|
AZStd::vector<AZStd::string> NavigationComponent::PopulateAgentTypeList()
|
|
{
|
|
if (m_agentType.size() == 0)
|
|
{
|
|
// If no previously stored agent type select a default one (usually on component added)
|
|
m_agentType = GetDefaultAgentNavigationTypeName();
|
|
}
|
|
HandleAgentTypeChanged();
|
|
return LmbrCentral::PopulateAgentTypeList();
|
|
}
|
|
|
|
AZ::u32 NavigationComponent::HandleAgentTypeChanged()
|
|
{
|
|
float agentRadius = CalculateAgentNavigationRadius(m_agentType.c_str());
|
|
if (agentRadius >= 0.0f)
|
|
{
|
|
m_agentRadius = agentRadius;
|
|
}
|
|
else
|
|
{
|
|
AZ_Error("Editor", false, "Unable to find navigation radius data for agent type '%s'", m_agentType.c_str());
|
|
}
|
|
return AZ::Edit::PropertyRefreshLevels::ValuesOnly;
|
|
}
|
|
#endif
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// NavigationComponentRequestBus::Handler interface implementation
|
|
|
|
PathfindRequest::NavigationRequestId NavigationComponent::FindPath(const PathfindRequest& request)
|
|
{
|
|
// If neither the position nor the destination has been set
|
|
if (!(request.HasTargetEntity() || request.HasTargetLocation()))
|
|
{
|
|
// Return an invalid id to indicate that the request is bad
|
|
return PathfindResponse::kInvalidRequestId;
|
|
}
|
|
|
|
// Reset the Navigation component to deal with a new pathfind request
|
|
Reset();
|
|
|
|
m_lastResponseCache.SetupForNewRequest(this, request);
|
|
|
|
if (!request.HasTargetEntity())
|
|
{
|
|
FindPathImpl();
|
|
}
|
|
|
|
return m_lastResponseCache.GetRequestId();
|
|
}
|
|
|
|
void NavigationComponent::FindPathImpl()
|
|
{
|
|
// Request for a path
|
|
PathfindResponse::PathfinderRequestId pathfinderRequestID = RequestPath();
|
|
m_lastResponseCache.SetPathfinderRequestId(pathfinderRequestID);
|
|
|
|
if (pathfinderRequestID != MNM::Constants::eQueuedPathID_InvalidID)
|
|
{
|
|
// Indicate that the path is being looked for
|
|
m_lastResponseCache.SetStatus(PathfindResponse::Status::SearchingForPath);
|
|
|
|
// Inform every listener on this entity about the "Searching For Path" event
|
|
NavigationComponentNotificationBus::Event(m_entity->GetId(),
|
|
&NavigationComponentNotificationBus::Events::OnSearchingForPath, m_lastResponseCache.GetRequestId());
|
|
}
|
|
else
|
|
{
|
|
m_lastResponseCache.SetStatus(PathfindResponse::Status::TraversalCancelled);
|
|
|
|
// Inform every listener on this entity about the "Traversal cancelled" event
|
|
NavigationComponentNotificationBus::Event(m_entity->GetId(),
|
|
&NavigationComponentNotificationBus::Events::OnTraversalCancelled, m_lastResponseCache.GetRequestId());
|
|
}
|
|
}
|
|
|
|
PathfindResponse::PathfinderRequestId NavigationComponent::RequestPath()
|
|
{
|
|
// Create a new pathfind request
|
|
MNMPathRequest pathfinderRequest;
|
|
|
|
// 1. Set the current entity's position as the start location
|
|
pathfinderRequest.startLocation = AZVec3ToLYVec3(m_entityTransform.GetTranslation());
|
|
|
|
// 2. Set the requested destination
|
|
pathfinderRequest.endLocation = AZVec3ToLYVec3(m_lastResponseCache.GetCurrentDestination());
|
|
|
|
// 3. Set the type of the Navigation agent
|
|
pathfinderRequest.agentTypeID = m_agentTypeId;
|
|
|
|
// 4. Set the callback
|
|
pathfinderRequest.resultCallback = AZStd::bind(&NavigationComponent::OnPathResult, this,
|
|
AZStd::placeholders::_1, AZStd::placeholders::_2);
|
|
|
|
// 5. Request the path.
|
|
IMNMPathfinder* pathFinder = nullptr; // INavigationSystem will be converted to an AZInterface (LY-111343)
|
|
return pathFinder ? pathFinder->RequestPathTo(this, pathfinderRequest) : 0;
|
|
}
|
|
|
|
void NavigationComponent::OnPathResult(const MNM::QueuedPathID& pathfinderRequestId, MNMPathRequestResult& result)
|
|
{
|
|
// If the pathfinding result is for the latest pathfinding request (Otherwise ignore)
|
|
if (pathfinderRequestId == m_lastResponseCache.GetPathfinderRequestId())
|
|
{
|
|
if (result.HasPathBeenFound() &&
|
|
(m_lastResponseCache.GetRequestId() != PathfindResponse::kInvalidRequestId))
|
|
{
|
|
m_lastResponseCache.SetCurrentPath(result.pPath->Clone());
|
|
|
|
// If this request was in fact looking for a path (and this isn't just a path update request)
|
|
if (m_lastResponseCache.GetStatus() == PathfindResponse::Status::SearchingForPath)
|
|
{
|
|
// Set the status of this request
|
|
m_lastResponseCache.SetStatus(PathfindResponse::Status::PathFound);
|
|
|
|
// Inform every listener on this entity that a path has been found
|
|
bool shouldPathBeTraversed = true;
|
|
|
|
NavigationComponentNotificationBus::EventResult(shouldPathBeTraversed, m_entity->GetId(),
|
|
&NavigationComponentNotificationBus::Events::OnPathFound, m_lastResponseCache.GetRequestId(),
|
|
m_lastResponseCache.GetCurrentPath());
|
|
|
|
if (shouldPathBeTraversed)
|
|
{
|
|
// Connect to physics bus if appropriate, else tick bus
|
|
if ((m_movementMethod == NavigationComponentRequests::MovementMethod::Physics))
|
|
{
|
|
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
|
|
{
|
|
AzPhysics::SceneHandle sceneHandle = sceneInterface->GetSceneHandle(AzPhysics::DefaultPhysicsSceneName);
|
|
sceneInterface->RegisterSceneSimulationStartHandler(sceneHandle, m_sceneStartSimHandler);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AZ::TickBus::Handler::BusConnect();
|
|
}
|
|
|
|
// Set the status of this request
|
|
m_lastResponseCache.SetStatus(PathfindResponse::Status::TraversalStarted);
|
|
|
|
// Inform every listener on this entity that traversal is in progress
|
|
NavigationComponentNotificationBus::Event(m_entity->GetId(),
|
|
&NavigationComponentNotificationBus::Events::OnTraversalStarted,
|
|
m_lastResponseCache.GetRequestId());
|
|
}
|
|
else
|
|
{
|
|
// Set the status of this request
|
|
m_lastResponseCache.SetStatus(PathfindResponse::Status::TraversalCancelled);
|
|
|
|
// Inform every listener on this entity that a path could not be found
|
|
NavigationComponentNotificationBus::Event(m_entity->GetId(),
|
|
&NavigationComponentNotificationBus::Events::OnTraversalCancelled,
|
|
m_lastResponseCache.GetRequestId());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set the status of this request
|
|
m_lastResponseCache.SetStatus(PathfindResponse::Status::TraversalCancelled);
|
|
|
|
// Inform every listener on this entity that a path could not be found
|
|
NavigationComponentNotificationBus::Event(m_entity->GetId(),
|
|
&NavigationComponentNotificationBus::Events::OnTraversalCancelled,
|
|
m_lastResponseCache.GetRequestId());
|
|
}
|
|
}
|
|
}
|
|
|
|
void NavigationComponent::Stop(PathfindRequest::NavigationRequestId requestId)
|
|
{
|
|
if ((m_lastResponseCache.GetRequestId() == requestId)
|
|
&& requestId != PathfindResponse::kInvalidRequestId)
|
|
{
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
float NavigationComponent::GetAgentSpeed()
|
|
{
|
|
return m_agentSpeed;
|
|
}
|
|
|
|
void NavigationComponent::SetAgentSpeed(float agentSpeed)
|
|
{
|
|
m_agentSpeed = agentSpeed;
|
|
|
|
IPathFollowerPtr pathFollower = m_lastResponseCache.GetPathFollower();
|
|
|
|
if (pathFollower)
|
|
{
|
|
PathFollowerParams currentParams = pathFollower->GetParams();
|
|
currentParams.normalSpeed = agentSpeed;
|
|
currentParams.minSpeed = currentParams.normalSpeed * 0.8f;
|
|
currentParams.maxSpeed = currentParams.normalSpeed * 1.2f;
|
|
|
|
pathFollower->SetParams(currentParams);
|
|
}
|
|
}
|
|
|
|
NavigationComponentRequests::MovementMethod NavigationComponent::GetAgentMovementMethod()
|
|
{
|
|
return m_movementMethod;
|
|
}
|
|
|
|
void NavigationComponent::SetAgentMovementMethod(NavigationComponentRequests::MovementMethod movementMethod)
|
|
{
|
|
m_movementMethod = movementMethod;
|
|
}
|
|
|
|
void NavigationComponent::MoveEntity(float deltaTime)
|
|
{
|
|
// If there isn't a valid path
|
|
if (!m_lastResponseCache.GetCurrentPath())
|
|
{
|
|
// Come back next frame
|
|
return;
|
|
}
|
|
|
|
AZ::Vector3 currentVelocity = AZ::Vector3::CreateZero();
|
|
float mass = 0.f;
|
|
|
|
if (m_movementMethod == NavigationComponentRequests::MovementMethod::Physics)
|
|
{
|
|
{
|
|
Physics::RigidBodyRequestBus::EventResult(currentVelocity, GetEntityId(),
|
|
&Physics::RigidBodyRequestBus::Events::GetLinearVelocity);
|
|
|
|
Physics::RigidBodyRequestBus::EventResult(mass, GetEntityId(),
|
|
&Physics::RigidBodyRequestBus::Events::GetMass);
|
|
}
|
|
|
|
m_lastResponseCache.SetLastKnownAgentVelocity(currentVelocity);
|
|
}
|
|
|
|
// Update path-following and extract desired velocity.
|
|
AZ::Vector3 nextPathPosition = AZ::Vector3::CreateZero();
|
|
AZ::Vector3 inflectionPosition = AZ::Vector3::CreateZero();
|
|
AZ::Vector3 targetVelocity = AZ::Vector3::CreateZero();
|
|
float distanceToEnd = 0.f;
|
|
|
|
auto pathFollower = m_lastResponseCache.GetPathFollower();
|
|
if (pathFollower)
|
|
{
|
|
const AZ::Vector3 agentPosition = m_entityTransform.GetTranslation();
|
|
const AZ::Vector3 agentVelocity = m_lastResponseCache.GetLastKnownAgentVelocity();
|
|
|
|
PathFollowResult result;
|
|
|
|
const bool arrived = pathFollower->Update(
|
|
result,
|
|
AZVec3ToLYVec3(agentPosition),
|
|
AZVec3ToLYVec3(agentVelocity),
|
|
deltaTime);
|
|
|
|
nextPathPosition = LYVec3ToAZVec3(result.followTargetPos);
|
|
inflectionPosition = LYVec3ToAZVec3(result.inflectionPoint);
|
|
targetVelocity = LYVec3ToAZVec3(result.velocityOut);
|
|
distanceToEnd = result.distanceToEnd;
|
|
}
|
|
|
|
if (targetVelocity == AZ::Vector3::CreateZero())
|
|
{
|
|
if (m_movementMethod == NavigationComponentRequests::MovementMethod::Physics)
|
|
{
|
|
if (!m_usesCharacterPhysics)
|
|
{
|
|
Physics::RigidBodyRequestBus::Event(GetEntityId(),
|
|
&Physics::RigidBodyRequestBus::Events::SetLinearVelocity, AZ::Vector3::CreateZero());
|
|
}
|
|
}
|
|
|
|
// Set the status of this request
|
|
m_lastResponseCache.SetStatus(PathfindResponse::Status::TraversalComplete);
|
|
|
|
// Reset the pathfinding component
|
|
Reset();
|
|
|
|
// Inform every listener on this entity that the path has been finished
|
|
NavigationComponentNotificationBus::Event(m_entity->GetId(),
|
|
&NavigationComponentNotificationBus::Events::OnTraversalComplete, m_lastResponseCache.GetRequestId());
|
|
}
|
|
else
|
|
{
|
|
if (m_movementMethod == NavigationComponentRequests::MovementMethod::Custom)
|
|
{
|
|
if (!nextPathPosition.IsClose(m_lastResponseCache.GetNextPathPosition()) ||
|
|
!inflectionPosition.IsClose(m_lastResponseCache.GetInflectionPosition()))
|
|
{
|
|
m_lastResponseCache.SetNextPathPosition(nextPathPosition);
|
|
m_lastResponseCache.SetInflectionPosition(inflectionPosition);
|
|
|
|
// when using the custom movement method we just update the path and rely on
|
|
// the user to move the entity
|
|
NavigationComponentNotificationBus::Event(m_entity->GetId(),
|
|
&NavigationComponentNotificationBus::Events::OnTraversalPathUpdate,
|
|
m_lastResponseCache.GetRequestId(),
|
|
nextPathPosition, inflectionPosition);
|
|
|
|
}
|
|
}
|
|
else if (m_movementMethod == NavigationComponentRequests::MovementMethod::Physics)
|
|
{
|
|
{
|
|
AZ::Vector3 forceRequired = (targetVelocity - currentVelocity) * mass;
|
|
forceRequired.SetZ(0);
|
|
|
|
if (m_usesCharacterPhysics)
|
|
{
|
|
Physics::CharacterRequestBus::Event(GetEntityId(),
|
|
&Physics::CharacterRequestBus::Events::AddVelocity, targetVelocity);
|
|
}
|
|
else
|
|
{
|
|
Physics::RigidBodyRequestBus::Event(GetEntityId(),
|
|
&Physics::RigidBodyRequestBus::Events::ApplyLinearImpulse, forceRequired);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set the position of the entity
|
|
AZ::Transform newEntityTransform = m_entityTransform;
|
|
AZ::Vector3 movementDelta = targetVelocity * deltaTime;
|
|
AZ::Vector3 currentPosition = m_entityTransform.GetTranslation();
|
|
AZ::Vector3 newPosition = m_entityTransform.GetTranslation() + movementDelta;
|
|
newEntityTransform.SetTranslation(newPosition);
|
|
AZ::TransformBus::Event(m_entity->GetId(), &AZ::TransformBus::Events::SetWorldTM, newEntityTransform);
|
|
|
|
m_lastResponseCache.SetLastKnownAgentVelocity(targetVelocity);
|
|
}
|
|
|
|
m_lastResponseCache.SetStatus(PathfindResponse::Status::TraversalInProgress);
|
|
|
|
NavigationComponentNotificationBus::Event(m_entity->GetId(),
|
|
&NavigationComponentNotificationBus::Events::OnTraversalInProgress, m_lastResponseCache.GetRequestId(),
|
|
distanceToEnd);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// AZ::TickBus::Handler implementation
|
|
|
|
void NavigationComponent::OnTick(float deltaTime, AZ::ScriptTimePoint /*time*/)
|
|
{
|
|
MoveEntity(deltaTime);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// AZ::TransformNotificationBus::Handler implementation
|
|
// If the transform on the entity has changed
|
|
void NavigationComponent::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world)
|
|
{
|
|
m_entityTransform = world;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// NavigationComponent implementation
|
|
|
|
void NavigationComponent::Reset()
|
|
{
|
|
m_lastResponseCache.Reset();
|
|
|
|
// Disconnect from tick bus and physics bus
|
|
AZ::TickBus::Handler::BusDisconnect();
|
|
m_sceneStartSimHandler.Disconnect();
|
|
}
|
|
|
|
PathfindRequest::NavigationRequestId NavigationComponent::FindPathToEntity(AZ::EntityId targetEntityId)
|
|
{
|
|
PathfindRequest request;
|
|
request.SetTargetEntityId(targetEntityId);
|
|
return FindPath(request);
|
|
}
|
|
|
|
PathfindRequest::NavigationRequestId NavigationComponent::FindPathToPosition(const AZ::Vector3& destination)
|
|
{
|
|
PathfindRequest request;
|
|
request.SetDestinationLocation(destination);
|
|
return FindPath(request);
|
|
}
|
|
} // namespace LmbrCentral
|