Files
UnrealEngine/Engine/Source/Runtime/NavigationSystem/Private/NavModifierComponent.cpp
2025-05-18 13:04:45 +08:00

280 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NavModifierComponent.h"
#include "GameFramework/Actor.h"
#include "Components/PrimitiveComponent.h"
#include "AI/NavigationModifier.h"
#include "NavAreas/NavArea_Null.h"
#include "PhysicsEngine/BodySetup.h"
#include "NavigationSystem.h"
#include "GeometryCollection/GeometryCollectionComponent.h"
#include "GeometryCollection/GeometryCollectionObject.h"
#include "Engine/StaticMesh.h"
#include "VisualLogger/VisualLogger.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(NavModifierComponent)
namespace UE::Navigation::ModComponent::Private
{
#if WITH_EDITOR
void OnNavAreaRegistrationChanged(UNavModifierComponent& ModifierComponent, const UWorld& World, const UClass* NavAreaClass)
{
if (NavAreaClass && (NavAreaClass == ModifierComponent.AreaClass || NavAreaClass == ModifierComponent.AreaClassToReplace) && &World == ModifierComponent.GetWorld())
{
ModifierComponent.RefreshNavigationModifiers();
}
}
#endif // WITH_EDITOR
FAreaNavModifier CreateAreaModifier(const FBox& Box, const FQuat& Quat, const TSubclassOf<UNavArea>& AreaClass, const TSubclassOf<UNavArea>& AreaClassToReplace, const bool bIncludeAgentHeight)
{
FAreaNavModifier AreaNavModifier(Box, FTransform(Quat), AreaClass);
AreaNavModifier.SetIncludeAgentHeight(bIncludeAgentHeight);
if (AreaClassToReplace)
{
AreaNavModifier.SetAreaClassToReplace(AreaClassToReplace);
}
return AreaNavModifier;
}
} // UE::Navigation::ModComponent::Private
UNavModifierComponent::UNavModifierComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
AreaClass = UNavArea_Null::StaticClass();
AreaClassToReplace = nullptr;
FailsafeExtent = FVector(100, 100, 100);
bIncludeAgentHeight = true;
NavMeshResolution = ENavigationDataResolution::Invalid;
}
void UNavModifierComponent::OnRegister()
{
Super::OnRegister();
#if WITH_EDITOR
if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject))
{
OnNavAreaRegisteredDelegateHandle = UNavigationSystemBase::OnNavAreaRegisteredDelegate().AddUObject(this, &UNavModifierComponent::OnNavAreaRegistered);
OnNavAreaUnregisteredDelegateHandle = UNavigationSystemBase::OnNavAreaUnregisteredDelegate().AddUObject(this, &UNavModifierComponent::OnNavAreaUnregistered);
}
#endif // WITH_EDITOR
}
void UNavModifierComponent::OnUnregister()
{
#if WITH_EDITOR
if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject))
{
UNavigationSystemBase::OnNavAreaRegisteredDelegate().Remove(OnNavAreaRegisteredDelegateHandle);
UNavigationSystemBase::OnNavAreaUnregisteredDelegate().Remove(OnNavAreaUnregisteredDelegateHandle);
}
#endif // WITH_EDITOR
Super::OnUnregister();
}
#if WITH_EDITOR
// This function is only called if GIsEditor == true for non default objects components that are registered.
void UNavModifierComponent::OnNavAreaRegistered(const UWorld& World, const UClass* NavAreaClass)
{
UE::Navigation::ModComponent::Private::OnNavAreaRegistrationChanged(*this, World, NavAreaClass);
}
// This function is only called if GIsEditor == true for non default objects components that are registered.
void UNavModifierComponent::OnNavAreaUnregistered(const UWorld& World, const UClass* NavAreaClass)
{
UE::Navigation::ModComponent::Private::OnNavAreaRegistrationChanged(*this, World, NavAreaClass);
}
#endif // WITH_EDITOR
void UNavModifierComponent::PopulateComponentBounds(FTransform InParentTransform, const UBodySetup& InBodySetup) const
{
const FVector Scale3D = InParentTransform.GetScale3D();
InParentTransform.RemoveScaling();
for (int32 SphereIdx = 0; SphereIdx < InBodySetup.AggGeom.SphereElems.Num(); SphereIdx++)
{
const FKSphereElem& ElemInfo = InBodySetup.AggGeom.SphereElems[SphereIdx];
FTransform ElemTM = ElemInfo.GetTransform();
ElemTM.ScaleTranslation(Scale3D);
ElemTM *= InParentTransform;
const FBox SphereBounds = FBox::BuildAABB(ElemTM.GetLocation(), ElemInfo.Radius * Scale3D);
ComponentBounds.Add(FRotatedBox(SphereBounds, ElemTM.GetRotation()));
}
for (int32 BoxIdx = 0; BoxIdx < InBodySetup.AggGeom.BoxElems.Num(); BoxIdx++)
{
const FKBoxElem& ElemInfo = InBodySetup.AggGeom.BoxElems[BoxIdx];
FTransform ElemTM = ElemInfo.GetTransform();
ElemTM.ScaleTranslation(Scale3D);
ElemTM *= InParentTransform;
const FBox BoxBounds = FBox::BuildAABB(ElemTM.GetLocation(), FVector(ElemInfo.X, ElemInfo.Y, ElemInfo.Z) * Scale3D * 0.5f);
ComponentBounds.Add(FRotatedBox(BoxBounds, ElemTM.GetRotation()));
}
for (int32 SphylIdx = 0; SphylIdx < InBodySetup.AggGeom.SphylElems.Num(); SphylIdx++)
{
const FKSphylElem& ElemInfo = InBodySetup.AggGeom.SphylElems[SphylIdx];
FTransform ElemTM = ElemInfo.GetTransform();
ElemTM.ScaleTranslation(Scale3D);
ElemTM *= InParentTransform;
const FBox SphylBounds = FBox::BuildAABB(ElemTM.GetLocation(), FVector(ElemInfo.Radius, ElemInfo.Radius, ElemInfo.Length) * Scale3D);
ComponentBounds.Add(FRotatedBox(SphylBounds, ElemTM.GetRotation()));
}
for (int32 ConvexIdx = 0; ConvexIdx < InBodySetup.AggGeom.ConvexElems.Num(); ConvexIdx++)
{
const FKConvexElem& ElemInfo = InBodySetup.AggGeom.ConvexElems[ConvexIdx];
FTransform ElemTM = ElemInfo.GetTransform();
const FBox ConvexBounds = FBox::BuildAABB(InParentTransform.TransformPosition(ElemInfo.ElemBox.GetCenter() * Scale3D), ElemInfo.ElemBox.GetExtent() * Scale3D);
ComponentBounds.Add(FRotatedBox(ConvexBounds, ElemTM.GetRotation() * InParentTransform.GetRotation()));
}
}
void UNavModifierComponent::CalculateBounds() const
{
const AActor* MyOwner = GetOwner();
if (!MyOwner)
{
return;
}
Bounds = FBox(ForceInit);
ComponentBounds.Reset();
for (UActorComponent* Component : MyOwner->GetComponents())
{
UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(Component);
if (PrimComp && PrimComp->IsRegistered() && PrimComp->IsCollisionEnabled() && PrimComp->CanEverAffectNavigation())
{
UBodySetup* BodySetup = PrimComp->GetBodySetup();
if (BodySetup)
{
Bounds += PrimComp->Bounds.GetBox();
const FTransform& ParentTM = PrimComp->GetComponentTransform();
PopulateComponentBounds(ParentTM, *BodySetup);
}
else if (const UGeometryCollectionComponent* GeometryCollection = Cast<UGeometryCollectionComponent>(PrimComp))
{
// If it's a GC, use the bodySetups from the proxyMeshes.
if (const TObjectPtr<const UGeometryCollection> RestCollection = GeometryCollection->RestCollection)
{
Bounds += GeometryCollection->Bounds.GetBox();
const FGeometryCollectionProxyMeshData& ProxyMeshData = RestCollection->RootProxyData;
for (int32 MeshIdx = 0; MeshIdx < ProxyMeshData.ProxyMeshes.Num(); ++MeshIdx)
{
const TObjectPtr<UStaticMesh>& ProxyMesh = ProxyMeshData.ProxyMeshes[MeshIdx];
if (ProxyMesh != nullptr)
{
const UBodySetup* Body = ProxyMesh->GetBodySetup();
if (Body)
{
const FTransform& ParentTM = PrimComp->GetComponentTransform();
const FTransform LocalMeshTransform(ProxyMeshData.GetMeshTransform(MeshIdx));
PopulateComponentBounds(LocalMeshTransform* ParentTM, *Body);
}
}
}
}
}
}
}
if (ComponentBounds.Num() == 0)
{
Bounds = FBox::BuildAABB(MyOwner->GetActorLocation(), FailsafeExtent);
ComponentBounds.Add(FRotatedBox(Bounds, MyOwner->GetActorQuat()));
}
for (int32 Idx = 0; Idx < ComponentBounds.Num(); Idx++)
{
const FVector BoxOrigin = ComponentBounds[Idx].Box.GetCenter();
const FVector BoxExtent = ComponentBounds[Idx].Box.GetExtent();
const FVector NavModBoxOrigin = FTransform(ComponentBounds[Idx].Quat).InverseTransformPosition(BoxOrigin);
ComponentBounds[Idx].Box = FBox::BuildAABB(NavModBoxOrigin, BoxExtent);
}
}
void UNavModifierComponent::CalcAndCacheBounds() const
{
const AActor* MyOwner = GetOwner();
if (MyOwner)
{
CachedTransform = MyOwner->GetActorTransform();
if (TransformUpdateHandle.IsValid() == false && MyOwner->GetRootComponent())
{
// binding to get notifies when the root component moves. We need
// this only when the rootcomp is nav-irrelevant (since the default
// mechanisms won't kick in) but we're binding without checking it since
// this property can change without re-running CalcAndCacheBounds.
// We're filtering for nav relevancy in OnTransformUpdated.
TransformUpdateHandle = MyOwner->GetRootComponent()->TransformUpdated.AddUObject(const_cast<UNavModifierComponent*>(this), &UNavModifierComponent::OnTransformUpdated);
}
}
CalculateBounds();
UE_SUPPRESS(LogNavigation, VeryVerbose,
{
TArray<FAreaNavModifier> Areas;
for (int32 Idx = 0; Idx < ComponentBounds.Num(); Idx++)
{
Areas.Add(UE::Navigation::ModComponent::Private::CreateAreaModifier(ComponentBounds[Idx].Box, ComponentBounds[Idx].Quat, AreaClass, AreaClassToReplace, bIncludeAgentHeight));
}
for(const FAreaNavModifier& Modifier : Areas)
{
UE_VLOG_BOX(this, LogNavigation, VeryVerbose, Modifier.GetBounds(), FColor::Yellow, TEXT(""));
}
});
}
void UNavModifierComponent::GetNavigationData(FNavigationRelevantData& Data) const
{
for (int32 Idx = 0; Idx < ComponentBounds.Num(); Idx++)
{
Data.Modifiers.Add(UE::Navigation::ModComponent::Private::CreateAreaModifier(ComponentBounds[Idx].Box, ComponentBounds[Idx].Quat, AreaClass, AreaClassToReplace, bIncludeAgentHeight));
}
Data.Modifiers.SetNavMeshResolution(NavMeshResolution);
}
void UNavModifierComponent::SetAreaClass(TSubclassOf<UNavArea> NewAreaClass)
{
if (AreaClass != NewAreaClass)
{
AreaClass = NewAreaClass;
RefreshNavigationModifiers();
}
}
void UNavModifierComponent::SetAreaClassToReplace(TSubclassOf<UNavArea> NewAreaClassToReplace)
{
if (AreaClassToReplace != NewAreaClassToReplace)
{
AreaClassToReplace = NewAreaClassToReplace;
RefreshNavigationModifiers();
}
}
void UNavModifierComponent::OnTransformUpdated(USceneComponent* RootComponent, EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport)
{
// force bounds recaching next time GetNavigationBounds gets called.
bBoundsInitialized = false;
// otherwise the update will be handled by the default path
if (RootComponent && RootComponent->CanEverAffectNavigation() == false)
{
// since the parent is not nav-relevant we need to manually tell nav sys to update
RefreshNavigationModifiers();
}
}