Files
2025-05-18 13:04:45 +08:00

423 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ZoneGraphTestingActor.h"
#include "SceneView.h"
#include "UObject/ConstructorHelpers.h"
#include "PrimitiveViewRelevance.h"
#include "PrimitiveSceneProxy.h"
#include "ZoneGraphSubsystem.h"
#include "ZoneGraphDelegates.h"
#include "ZoneGraphRenderingUtilities.h"
#include "ZoneGraphData.h"
#include "ZoneGraphAStar.h"
UZoneGraphTestingComponent::UZoneGraphTestingComponent(const FObjectInitializer& ObjectInitialize)
: Super(ObjectInitialize)
{
SearchExtent = FVector(150.0f);
AdvanceDistance = 300.0f;
NearestTestOffset = FVector(300, 0, 0);
}
#if WITH_EDITOR
void UZoneGraphTestingComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
UpdateTests();
}
#endif
void UZoneGraphTestingComponent::OnRegister()
{
Super::OnRegister();
#if WITH_EDITOR
// Force to update tests when ever the data changes.
OnDataChangedHandle = UE::ZoneGraphDelegates::OnZoneGraphDataBuildDone.AddUObject(this, &UZoneGraphTestingComponent::OnZoneGraphDataBuildDone);
#endif
OnDataAddedHandle = UE::ZoneGraphDelegates::OnPostZoneGraphDataAdded.AddUObject(this, &UZoneGraphTestingComponent::OnZoneGraphDataChanged);
OnDataRemovedHandle = UE::ZoneGraphDelegates::OnPreZoneGraphDataRemoved.AddUObject(this, &UZoneGraphTestingComponent::OnZoneGraphDataChanged);
ZoneGraph = UWorld::GetSubsystem<UZoneGraphSubsystem>(GetWorld());
ExecuteOnEachCustomTest([this](UZoneLaneTest& Test) { Test.SetOwner(this); });
UpdateTests();
}
void UZoneGraphTestingComponent::OnUnregister()
{
Super::OnUnregister();
#if WITH_EDITOR
UE::ZoneGraphDelegates::OnZoneGraphDataBuildDone.Remove(OnDataChangedHandle);
#endif
UE::ZoneGraphDelegates::OnPostZoneGraphDataAdded.Remove(OnDataAddedHandle);
UE::ZoneGraphDelegates::OnPreZoneGraphDataRemoved.Remove(OnDataRemovedHandle);
}
void UZoneGraphTestingComponent::OnZoneGraphDataChanged(const AZoneGraphData* ZoneGraphData)
{
// Only consider valid graph from our world
if (ZoneGraphData == nullptr || ZoneGraphData->GetWorld() != GetWorld())
{
return;
}
UpdateTests();
}
const FZoneGraphStorage* UZoneGraphTestingComponent::GetZoneGraphStorage(const FZoneGraphLaneHandle& LaneHandle) const
{
if (ZoneGraph == nullptr)
{
return nullptr;
}
const AZoneGraphData* ZoneGraphData = ZoneGraph->GetZoneGraphData(LaneHandle.DataHandle);
return (ZoneGraphData != nullptr) ? &(ZoneGraphData->GetStorage()) : nullptr;
}
void UZoneGraphTestingComponent::EnableCustomTests()
{
bCustomTestsDisabled = false;
ExecuteOnEachCustomTest([this](UZoneLaneTest& Test) { Test.OnLaneLocationUpdated(FZoneGraphLaneLocation(), LaneLocation); });
}
void UZoneGraphTestingComponent::DisableCustomTests()
{
bCustomTestsDisabled = true;
ExecuteOnEachCustomTest([this](UZoneLaneTest& Test) { Test.OnLaneLocationUpdated(LaneLocation, FZoneGraphLaneLocation()); });
}
#if WITH_EDITOR
void UZoneGraphTestingComponent::OnZoneGraphDataBuildDone(const struct FZoneGraphBuildData& BuildData)
{
UpdateTests();
}
#endif
FBoxSphereBounds UZoneGraphTestingComponent::CalcBounds(const FTransform& LocalToWorld) const
{
const FVector ActorPosition = LocalToWorld.GetTranslation();
return FBox(ActorPosition - SearchExtent, ActorPosition + SearchExtent);
}
void UZoneGraphTestingComponent::UpdateTests()
{
if (!ZoneGraph)
{
return;
}
const FVector ActorPosition = GetOwner()->GetActorLocation();
// Find nearest
float DistanceSqr = 0.0f;
const FZoneGraphLaneLocation PrevLaneLocation = LaneLocation;
ZoneGraph->FindNearestLane(FBox(ActorPosition - SearchExtent, ActorPosition + SearchExtent), QueryFilter, LaneLocation, DistanceSqr);
if (!bCustomTestsDisabled)
{
ExecuteOnEachCustomTest([PrevLaneLocation, this](UZoneLaneTest& Test) { Test.OnLaneLocationUpdated(PrevLaneLocation, LaneLocation); });
}
// Test advance
NextLaneLocation.Reset();
float DistanceToGo = AdvanceDistance;
if (ZoneGraph->AdvanceLaneLocation(LaneLocation, DistanceToGo, NextLaneLocation))
{
DistanceToGo -= (NextLaneLocation.DistanceAlongLane - LaneLocation.DistanceAlongLane);
// If hit the end of a lane, keep going on until we have advanced far enough or hit a dead end.
while (DistanceToGo > KINDA_SMALL_NUMBER)
{
TArray<FZoneGraphLinkedLane> NextLanes;
if (ZoneGraph->GetLinkedLanes(NextLaneLocation.LaneHandle, EZoneLaneLinkType::Outgoing, EZoneLaneLinkFlags::All, EZoneLaneLinkFlags::None, NextLanes) && NextLanes.Num() > 0)
{
// Advance to first link.
ZoneGraph->CalculateLocationAlongLane(NextLanes[0].DestLane, DistanceToGo, NextLaneLocation);
DistanceToGo -= NextLaneLocation.DistanceAlongLane;
}
else
{
break;
}
}
}
// Test nearest location on specific lane
ZoneGraph->FindNearestLocationOnLane(LaneLocation.LaneHandle, FBox(ActorPosition + NearestTestOffset - SearchExtent, ActorPosition + NearestTestOffset + SearchExtent), NearestLaneLocation, DistanceSqr);
if (bDrawLinkedLanes)
{
// Find linked lanes
LinkedLanes.Reset();
ZoneGraph->GetLinkedLanes(LaneLocation.LaneHandle, EZoneLaneLinkType::All, EZoneLaneLinkFlags::All, EZoneLaneLinkFlags::None, LinkedLanes);
}
if (bDrawLanePath)
{
// Simple pathfind to to the linked AZoneGraphTestingActor for testing purposes
UZoneGraphTestingComponent* OtherTestingComp = OtherActor ? Cast<UZoneGraphTestingComponent>(OtherActor->GetComponentByClass(UZoneGraphTestingComponent::StaticClass())) : nullptr;
if (NearestLaneLocation.IsValid() &&
OtherTestingComp && OtherTestingComp->NearestLaneLocation.IsValid() &&
NearestLaneLocation.LaneHandle.DataHandle == OtherTestingComp->NearestLaneLocation.LaneHandle.DataHandle)
{
const AZoneGraphData* Data = ZoneGraph->GetZoneGraphData(NearestLaneLocation.LaneHandle.DataHandle);
if (Data)
{
const FZoneGraphStorage& ZoneGraphStorage = Data->GetStorage();
FZoneGraphAStarWrapper Graph(ZoneGraphStorage);
FZoneGraphAStar Pathfinder(Graph);
// @todo: pass FZoneGraphLaneLocation directly to the constructor
FZoneGraphAStarNode StartNode(NearestLaneLocation.LaneHandle.Index, NearestLaneLocation.Position);
FZoneGraphAStarNode EndNode(OtherTestingComp->NearestLaneLocation.LaneHandle.Index, OtherTestingComp->NearestLaneLocation.Position);
FZoneGraphPathFilter PathFilter(ZoneGraphStorage, NearestLaneLocation, OtherTestingComp->NearestLaneLocation, QueryFilter);
// @todo: see if we can return directly a path of lane handles
TArray<FZoneGraphAStarWrapper::FNodeRef> ResultPath;
EGraphAStarResult Result = Pathfinder.FindPath(StartNode, EndNode, PathFilter, ResultPath);
if (Result == SearchSuccess)
{
//Store the resulting lanes
LanePath.Reset(ResultPath.Num());
LanePath.StartLaneLocation = NearestLaneLocation;
LanePath.EndLaneLocation = OtherTestingComp->NearestLaneLocation;
for (FZoneGraphAStarWrapper::FNodeRef Node : ResultPath)
{
LanePath.Add(FZoneGraphLaneHandle(Node, NearestLaneLocation.LaneHandle.DataHandle));
}
}
}
}
}
MarkRenderStateDirty();
}
#if !UE_BUILD_SHIPPING
FPrimitiveSceneProxy* UZoneGraphTestingComponent::CreateSceneProxy()
{
class FZoneGraphTestingSceneProxy final : public FPrimitiveSceneProxy
{
public:
friend class UZoneGraphTestingComponent;
virtual SIZE_T GetTypeHash() const override
{
static size_t UniquePointer;
return reinterpret_cast<size_t>(&UniquePointer);
}
FZoneGraphTestingSceneProxy(const UZoneGraphTestingComponent& InComponent)
: FPrimitiveSceneProxy(&InComponent)
, Component(&InComponent)
{
}
virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_ShapeSceneProxy_GetDynamicMeshElements);
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
if (VisibilityMap & (1 << ViewIndex))
{
const FSceneView* View = Views[ViewIndex];
FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);
const FMatrix& LocalToWorld = GetLocalToWorld();
// Taking into account the min and maximum drawing distance
const double DistanceSqr = (View->ViewMatrices.GetViewOrigin() - LocalToWorld.GetOrigin()).SizeSquared();
if (DistanceSqr < FMath::Square(GetMinDrawDistance()) || DistanceSqr > FMath::Square(GetMaxDrawDistance()))
{
continue;
}
// Draw
DrawView(PDI);
}
}
}
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
{
FPrimitiveViewRelevance Result;
Result.bDrawRelevance = IsShown(View);
Result.bDynamicRelevance = true;
Result.bShadowRelevance = IsShadowCast(View);
Result.bEditorPrimitiveRelevance = UseEditorCompositing(View);
return Result;
}
virtual uint32 GetMemoryFootprint(void) const override { return sizeof(*this) + GetAllocatedSize(); }
uint32 GetAllocatedSize(void) const { return (uint32)FPrimitiveSceneProxy::GetAllocatedSize(); }
void DrawView(FPrimitiveDrawInterface* PDI) const
{
const FZoneGraphLaneLocation& LaneLoc = Component->LaneLocation;
const FZoneGraphLaneLocation& NextLaneLoc = Component->NextLaneLocation;
const FZoneGraphLaneLocation& NearestLaneLoc = Component->NearestLaneLocation;
const FMatrix& LocalToWorld = GetLocalToWorld();
const FVector Center = LocalToWorld.GetOrigin();
const FVector Offset(0, 0, 10.0f);
static constexpr float TickSize = 25.0f;
static constexpr FColor QueryBoxColor(0, 255, 192);
static constexpr FColor NearestColor(255, 64, 0);
static constexpr FColor AdvanceColor(64, 255, 0);
static constexpr FColor NearestLaneColor(0, 64, 255);
DrawWireBox(PDI, FBox(Center - Component->SearchExtent, Center + Component->SearchExtent), FColor::Blue, SDPG_World);
PDI->DrawLine(Center - FVector(TickSize, 0, 0), Center + FVector(25.0f, 0, 0), QueryBoxColor, SDPG_World);
PDI->DrawLine(Center - FVector(0, TickSize, 0), Center + FVector(0, 25.0f, 0), QueryBoxColor, SDPG_World);
PDI->DrawLine(Center - FVector(0, 0, TickSize), Center + FVector(0, 0, 25.0f), QueryBoxColor, SDPG_World);
if (LaneLoc.IsValid())
{
PDI->DrawLine(Offset + LaneLoc.Position - LaneLoc.Up * TickSize, Offset + LaneLoc.Position + LaneLoc.Up * TickSize * 3.0f, NearestColor, SDPG_World, 2.0f, 0.01f);
PDI->DrawLine(Center, Offset + LaneLoc.Position, NearestColor, SDPG_World, 2.0f, 0.01f);
}
if (LaneLoc.IsValid() && NextLaneLoc.IsValid())
{
PDI->DrawLine(Offset + NextLaneLoc.Position - NextLaneLoc.Up * TickSize, Offset + NextLaneLoc.Position + NextLaneLoc.Up * TickSize * 3.0f, AdvanceColor, SDPG_World, 2.0f, 0.01f);
PDI->DrawLine(Offset + LaneLoc.Position, Offset + NextLaneLoc.Position, AdvanceColor, SDPG_World, 2.0f, 0.01f);
}
PDI->DrawLine(Center, Center + Component->NearestTestOffset, FColor::Black, SDPG_World, 0.0f, 0.01f);
if (NearestLaneLoc.IsValid())
{
PDI->DrawLine(Offset + NearestLaneLoc.Position - NearestLaneLoc.Up * TickSize, Offset + NearestLaneLoc.Position + NearestLaneLoc.Up * TickSize * 3.0f, NearestLaneColor, SDPG_World, 2.0f, 0.01f);
PDI->DrawLine(Center + Component->NearestTestOffset, Offset + NearestLaneLoc.Position, NearestLaneColor, SDPG_World, 2.0f, 0.01f);
}
if (LaneLoc.IsValid())
{
const FZoneGraphStorage* Storage = Component->GetZoneGraphStorage(LaneLoc.LaneHandle);
if (Storage != nullptr)
{
// Drawing linked lanes from LaneLocation
if (Component->bDrawLinkedLanes)
{
UE::ZoneGraph::RenderingUtilities::DrawLinkedLanes(*Storage, PDI, LaneLoc.LaneHandle, Component->LinkedLanes);
}
// Draw lanes from the resulting path (starting from LaneLocation)
if (Component->bDrawLanePath)
{
UE::ZoneGraph::RenderingUtilities::DrawLanePath(*Storage, PDI, Component->LanePath, FColor::Orange, /*thickness=*/12.f);
}
Component->ExecuteOnEachCustomTest([&PDI](const UZoneLaneTest& Test) { Test.Draw(PDI); });
if (Component->bDrawLaneTangentVectors)
{
UE::ZoneGraph::RenderingUtilities::DrawLaneDirections(*Storage, PDI, LaneLoc.LaneHandle, FColor(128,16,0));
}
if (Component->bDrawLaneSmoothing)
{
UE::ZoneGraph::RenderingUtilities::DrawLaneSmoothing(*Storage, PDI, LaneLoc.LaneHandle, FColor(64,8,0));
}
}
}
if (Component->bDrawBVTreeQuery)
{
UZoneGraphSubsystem* ZoneGraphSubsystem = UWorld::GetSubsystem<UZoneGraphSubsystem>(Component->GetWorld());
check(ZoneGraphSubsystem);
const FBox QueryBox(Center - Component->SearchExtent, Center + Component->SearchExtent);
for (const FRegisteredZoneGraphData& Registered : ZoneGraphSubsystem->GetRegisteredZoneGraphData())
{
if (Registered.bInUse && Registered.ZoneGraphData != nullptr)
{
const FZoneGraphStorage& Storage = Registered.ZoneGraphData->GetStorage();
if (Storage.Bounds.Intersect(QueryBox))
{
// Draw quantized query box
FZoneGraphBVNode QueryNode = Storage.ZoneBVTree.CalcNodeBounds(QueryBox);
DrawWireBox(PDI, Storage.ZoneBVTree.CalcWorldBounds(QueryNode), FColor::Orange, SDPG_World);
// Draw query results
Storage.ZoneBVTree.Query(QueryBox, [PDI, Storage](const FZoneGraphBVNode& Node)
{
const FBox Bounds = Storage.ZoneBVTree.CalcWorldBounds(Node);
DrawWireBox(PDI, Bounds, FColor::Red, SDPG_World);
});
}
}
}
}
}
private:
const UZoneGraphTestingComponent* Component;
};
return new FZoneGraphTestingSceneProxy(*this);
}
#endif // !UE_BUILD_SHIPPING
void UZoneGraphTestingComponent::ExecuteOnEachCustomTest(TFunctionRef<void(UZoneLaneTest&)> ExecFunc)
{
for (UZoneLaneTest* Test : CustomTests)
{
if (Test != nullptr)
{
ExecFunc(*Test);
}
}
}
void UZoneGraphTestingComponent::ExecuteOnEachCustomTest(TFunctionRef<void(const UZoneLaneTest&)> ExecFunc) const
{
for (const UZoneLaneTest* Test : CustomTests)
{
if (Test != nullptr)
{
ExecFunc(*Test);
}
}
}
AZoneGraphTestingActor::AZoneGraphTestingActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
DebugComp = CreateDefaultSubobject<UZoneGraphTestingComponent>(TEXT("DebugComp"));
RootComponent = DebugComp;
SetCanBeDamaged(false);
}
#if WITH_EDITOR
void AZoneGraphTestingActor::PostEditMove(bool bFinished)
{
if (DebugComp)
{
DebugComp->UpdateTests();
}
}
#endif
void AZoneGraphTestingActor::EnableCustomTests()
{
if (DebugComp)
{
DebugComp->EnableCustomTests();
}
}
void AZoneGraphTestingActor::DisableCustomTests()
{
if (DebugComp)
{
DebugComp->DisableCustomTests();
}
}