// Copyright Epic Games, Inc. All Rights Reserved. #include "CollisionAnalyzer.h" #include "HAL/FileManager.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Engine/GameViewportClient.h" #include "Engine/HitResult.h" #include "DrawDebugHelpers.h" #include "SCollisionAnalyzer.h" #include "CollisionAnalyzerLog.h" #include "CollisionDebugDrawingPublic.h" /** Magic value, determining that file is a collision analyzer file. */ #define COLLISION_ANALYZER_MAGIC 0x2DFF34FC /** Version of collision analyzer. Incremented on serialization changes. */ #define COLLISION_ANALYZER_VERSION 0 FCAQuery::FCAQuery() : Params(NAME_None, FCollisionQueryParams::GetUnknownStatId()) { } FCAQuery::~FCAQuery() = default; /** Util for serializing an FHitResult struct */ void SerializeHitResult(FArchive& Ar, FHitResult& Result) { bool bTempBlocking = Result.bBlockingHit; Ar << bTempBlocking; Result.bBlockingHit = bTempBlocking; bool bTempPenetrating = Result.bStartPenetrating; Ar << bTempPenetrating; Result.bStartPenetrating = bTempPenetrating; Ar << Result.Time; Ar << Result.Distance; Ar << Result.Location; Ar << Result.ImpactPoint; Ar << Result.Normal; Ar << Result.ImpactNormal; Ar << Result.TraceStart; Ar << Result.TraceEnd; Ar << Result.PenetrationDepth; Ar << Result.BoneName; Ar << Result.PhysMaterial; Ar << Result.HitObjectHandle; Ar << Result.Component; Ar << Result.FaceIndex; } FArchive& operator << (FArchive& Ar, FCAQuery& Query) { Ar << Query.Start; Ar << Query.End; Ar << Query.Rot; Ar << (int32&)Query.Type; Ar << (int32&)Query.Shape; Ar << (int32&)Query.Mode; Ar << Query.Dims; Ar << (int32&)Query.Channel; Ar << Query.Params.TraceTag; Ar << Query.Params.OwnerTag; bool bTraceAsyncDeprecated = false; Ar << bTraceAsyncDeprecated; Ar << Query.Params.bTraceComplex; Ar << Query.Params.bFindInitialOverlaps; Ar << Query.Params.bReturnFaceIndex; Ar << Query.Params.bReturnPhysicalMaterial; int32 NumResults = 0; if (Ar.IsLoading()) { // Load array. Ar << NumResults; Query.Results.SetNum(NumResults); } else if(Ar.IsSaving()) { // Save array. NumResults = Query.Results.Num(); Ar << NumResults; } for (int32 i = 0; i < NumResults; i++) { SerializeHitResult(Ar, Query.Results[i]); } Ar << Query.FrameNum; Ar << Query.CPUTime; Ar << Query.ID; return Ar; } ////////////////////////////////////////////////////////////////////////// void FCollisionAnalyzer::CaptureQuery( const FVector& Start, const FVector& End, const FQuat& Rot, ECAQueryType::Type QueryType, ECAQueryShape::Type QueryShape, ECAQueryMode::Type QueryMode, const FVector& Dims, ECollisionChannel TraceChannel, const struct FCollisionQueryParams& Params, const FCollisionResponseParams& ResponseParams, const FCollisionObjectQueryParams& ObjectParams, const TArray& Results, const TArray& TouchAllResults, double CPUTime) { if(bIsRecording) { int32 NewQueryId = Queries.AddZeroed(); FCAQuery& NewQuery = Queries[NewQueryId]; NewQuery.Start = Start; NewQuery.End = End; NewQuery.Rot = Rot; NewQuery.Type = QueryType; NewQuery.Shape = QueryShape; NewQuery.Mode = QueryMode; NewQuery.Dims = Dims; NewQuery.Channel = TraceChannel; NewQuery.Params = Params; NewQuery.ResponseParams = ResponseParams; NewQuery.ObjectParams = ObjectParams; NewQuery.Results = Results; NewQuery.TouchAllResults = TouchAllResults; NewQuery.FrameNum = CurrentFrameNum; NewQuery.CPUTime = CPUTime * 1000.f; NewQuery.ID = NewQueryId; QueryAddedEvent.Broadcast(); } } TSharedPtr FCollisionAnalyzer::SummonUI() { TSharedPtr ReturnWidget; UE_LOG(LogCollisionAnalyzer, Log, TEXT("Opening CollisionAnalyzer...")); if( IsInGameThread() ) { // Make a window ReturnWidget = SNew(SCollisionAnalyzer, this); } else { UE_LOG(LogCollisionAnalyzer, Warning, TEXT("FCollisionAnalyzer::DisplayUI: Not in game thread.")); } return ReturnWidget; } bool FCollisionAnalyzer::IsRecording() { return bIsRecording; } void FCollisionAnalyzer::TickAnalyzer(UWorld* World) { if(bIsRecording) { // Increment frame number CurrentFrameNum++; } // Draw any queries requested for(int32 DrawIdx=0; DrawIdxClose(); delete FileWriter; FileWriter = NULL; UE_LOG(LogCollisionAnalyzer, Log, TEXT("Saved collision analyzer data to file '%s'."), *ProfileFileName); } else { UE_LOG(LogCollisionAnalyzer, Warning, TEXT("Unable to save collision analyzer data to file '%s'."), *ProfileFileName); } } void FCollisionAnalyzer::LoadCollisionProfileData(FString ProfileFileName) { FArchive* FileReader = IFileManager::Get().CreateFileReader(*ProfileFileName); bool bSuccess = false; if (FileReader != nullptr) { FCollisionAnalyzerProxyArchive Ar(*FileReader); int32 Magic; Ar << Magic; if(Magic == COLLISION_ANALYZER_MAGIC) { int32 Version; Ar << Version; if(Version == COLLISION_ANALYZER_VERSION) { // First clear old data Queries.Reset(); DrawQueryIndices.Empty(); CurrentFrameNum = 0; // Load data from file Ar << Queries; // Invoke event that data has changed QueriesChangedEvent.Broadcast(); UE_LOG(LogCollisionAnalyzer, Log, TEXT("Loaded collision analyzer data from file '%s'."), *ProfileFileName); bSuccess = true; } } // Close file FileReader->Close(); delete FileReader; FileReader = NULL; } if(!bSuccess) { UE_LOG(LogCollisionAnalyzer, Warning, TEXT("Unable to load collision analyzer data from file '%s'."), *ProfileFileName); } }