// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "CollisionAnalyzerPCH.h" #define LOCTEXT_NAMESPACE "SCAQueryDetails" /** Util to give written explanation for why we missed something */ FString GetReasonForMiss(const UPrimitiveComponent* MissedComp, const FCAQuery* Query) { if(MissedComp != NULL && Query != NULL) { if(MissedComp->GetOwner() && !MissedComp->GetOwner()->GetActorEnableCollision()) { return FString::Printf(TEXT("Owning Actor '%s' has all collision disabled (SetActorEnableCollision)"), *MissedComp->GetOwner()->GetName()); } if(!MissedComp->IsCollisionEnabled()) { return FString::Printf(TEXT("Component '%s' has CollisionEnabled == NoCollision"), *MissedComp->GetName()); } if(MissedComp->GetCollisionResponseToChannel(Query->Channel) == ECR_Ignore) { return FString::Printf(TEXT("Component '%s' ignores this channel."), *MissedComp->GetName()); } if(Query->ResponseParams.CollisionResponse.GetResponse(MissedComp->GetCollisionObjectType()) == ECR_Ignore) { return FString::Printf(TEXT("Query ignores Component '%s' movement channel."), *MissedComp->GetName()); } } return TEXT("Unknown"); } /** Implements a row widget for result list. */ class SHitResultRow : public SMultiColumnTableRow< TSharedPtr > { public: SLATE_BEGIN_ARGS(SHitResultRow) {} SLATE_ARGUMENT(TSharedPtr, Info) SLATE_ARGUMENT(TSharedPtr, OwnerDetailsPtr) SLATE_END_ARGS() public: void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView) { Info = InArgs._Info; OwnerDetailsPtr = InArgs._OwnerDetailsPtr; SMultiColumnTableRow< TSharedPtr >::Construct(FSuperRowType::FArguments(), InOwnerTableView); } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override { // Get info to apply to all columns (color and tooltip) FSlateColor ResultColor = FSlateColor::UseForeground(); FString TooltipString; if(Info->bMiss) { ResultColor = FLinearColor(0.4f,0.4f,0.65f); TooltipString = LOCTEXT("MissPrefix", "Miss: ").ToString() + GetReasonForMiss(Info->Result.Component.Get(), OwnerDetailsPtr.Pin()->GetCurrentQuery()); } else if(Info->Result.bBlockingHit && Info->Result.bStartPenetrating) { ResultColor = FLinearColor(1.f,0.25f,0.25f); } // Generate widget for column if (ColumnName == TEXT("Time")) { return SNew(STextBlock) .ColorAndOpacity( ResultColor ) .ToolTipText( TooltipString ) .Text( FString::Printf(TEXT("%.3f"), Info->Result.Time) ); } else if (ColumnName == TEXT("Type")) { FString TypeString; if(Info->bMiss) { TypeString = TEXT("Miss"); } else if(Info->Result.bBlockingHit) { TypeString = TEXT("Block"); } else { TypeString = TEXT("Touch"); } return SNew(STextBlock) .ColorAndOpacity( ResultColor ) .ToolTipText( TooltipString ) .Text( TypeString ); } else if (ColumnName == TEXT("Component")) { FString LongName = TEXT("Invalid"); if(Info->Result.Component.IsValid()) { LongName = Info->Result.Component.Get()->GetReadableName(); } return SNew(STextBlock) .ColorAndOpacity( ResultColor ) .ToolTipText( TooltipString ) .Text( LongName ); } else if (ColumnName == TEXT("Normal")) { return SNew(STextBlock) .ColorAndOpacity( ResultColor ) .ToolTipText( TooltipString ) .Text( FString::Printf(TEXT("%s"), *Info->Result.Normal.ToString()) ); } return SNullWidget::NullWidget; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION private: /** Result to display */ TSharedPtr Info; /** Show details of */ TWeakPtr OwnerDetailsPtr; }; ////////////////////////////////////////////////////////////////////////// BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SCAQueryDetails::Construct(const FArguments& InArgs, TSharedPtr OwningAnalyzerWidget) { bDisplayQuery = false; bShowMisses = false; OwningAnalyzerWidgetPtr = OwningAnalyzerWidget; ChildSlot [ SNew(SVerticalBox) // Top area is info on the trace +SVerticalBox::Slot() .AutoHeight() [ SNew( SBorder ) .BorderImage( FEditorStyle::GetBrush( "ToolBar.Background" ) ) [ SNew(SHorizontalBox) // Left is start/end locations +SHorizontalBox::Slot() .FillWidth(1) [ SNew(SGridPanel) +SGridPanel::Slot(0,0) .Padding(2) [ SNew(STextBlock) .Text(LOCTEXT("QueryStart", "Start:")) ] +SGridPanel::Slot(1,0) .Padding(2) [ SNew(STextBlock) .Text(this, &SCAQueryDetails::GetStartText) ] +SGridPanel::Slot(0,1) .Padding(2) [ SNew(STextBlock) .Text(LOCTEXT("QueryEnd", "End:")) ] +SGridPanel::Slot(1,1) .Padding(2) [ SNew(STextBlock) .Text(this, &SCAQueryDetails::GetEndText) ] ] // Right has controls +SHorizontalBox::Slot() .FillWidth(1) .VAlign(VAlign_Top) .Padding(4,0) [ SNew(SCheckBox) .OnCheckStateChanged(this, &SCAQueryDetails::OnToggleShowMisses) .Content() [ SNew(STextBlock) .Text(LOCTEXT("ShowMisses", "Show Misses")) ] ] ] ] // Bottom area is list of hits +SVerticalBox::Slot() .FillHeight(1) [ SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("Menu.Background")) .Padding(1.0) [ SAssignNew(ResultListWidget, SListView< TSharedPtr >) .ItemHeight(20) .ListItemsSource(&ResultList) .SelectionMode(ESelectionMode::Single) .OnSelectionChanged(this, &SCAQueryDetails::ResultListSelectionChanged) .OnGenerateRow(this, &SCAQueryDetails::ResultListGenerateRow) .HeaderRow( SNew(SHeaderRow) +SHeaderRow::Column("Time").DefaultLabel(LOCTEXT("ResultListTimeHeader", "Time")).FillWidth(0.7) +SHeaderRow::Column("Type").DefaultLabel(LOCTEXT("ResultListTypeHeader", "Type")).FillWidth(0.7) +SHeaderRow::Column("Component").DefaultLabel(LOCTEXT("ResultListComponentHeader", "Component")).FillWidth(3) +SHeaderRow::Column("Normal").DefaultLabel(LOCTEXT("ResultListNormalHeader", "Normal")).FillWidth(1.8) ) ] ] ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION FText SCAQueryDetails::GetStartText() const { return bDisplayQuery ? CurrentQuery.Start.ToText() : FText::GetEmpty(); } FText SCAQueryDetails::GetEndText() const { return bDisplayQuery ? CurrentQuery.End.ToText() : FText::GetEmpty(); } TSharedRef SCAQueryDetails::ResultListGenerateRow(TSharedPtr Info, const TSharedRef& OwnerTable) { return SNew(SHitResultRow, OwnerTable) .Info(Info) .OwnerDetailsPtr(SharedThis(this)); } void SCAQueryDetails::UpdateDisplayedBox() { FCollisionAnalyzer* Analyzer = OwningAnalyzerWidgetPtr.Pin()->Analyzer; Analyzer->DrawBox = FBox(0); if(bDisplayQuery) { TArray< TSharedPtr > SelectedInfos = ResultListWidget->GetSelectedItems(); if(SelectedInfos.Num() > 0) { UPrimitiveComponent* HitComp = SelectedInfos[0]->Result.Component.Get(); if(HitComp != NULL) { Analyzer->DrawBox = HitComp->Bounds.GetBox(); } } } } void SCAQueryDetails::ResultListSelectionChanged(TSharedPtr SelectedInfos, ESelectInfo::Type SelectInfo) { UpdateDisplayedBox(); } void SCAQueryDetails::OnToggleShowMisses(ESlateCheckBoxState::Type InCheckboxState) { bShowMisses = (InCheckboxState == ESlateCheckBoxState::Checked); UpdateResultList(); } ESlateCheckBoxState::Type SCAQueryDetails::GetShowMissesState() const { return bShowMisses ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked; } /** See if an array of results contains a particular component */ static bool ResultsContainComponent(const TArray Results, UPrimitiveComponent* Component) { for(int32 i=0; i A, const TSharedPtr B ) const { check(A.IsValid()); check(B.IsValid()); return A->Result.Time < B->Result.Time; } }; ResultList.Sort( FCompareFCAHitInfo() ); } // Finally refresh display widget ResultListWidget->RequestListRefresh(); } void SCAQueryDetails::SetCurrentQuery(const FCAQuery& NewQuery) { bDisplayQuery = true; CurrentQuery = NewQuery; UpdateResultList(); } void SCAQueryDetails::ClearCurrentQuery() { bDisplayQuery = false; ResultList.Empty(); UpdateDisplayedBox(); } FCAQuery* SCAQueryDetails::GetCurrentQuery() { return bDisplayQuery ? &CurrentQuery : NULL; } #undef LOCTEXT_NAMESPACE