Files
UnrealEngineUWP/Engine/Source/Editor/DetailCustomizations/Private/BodyInstanceCustomization.cpp
Matthew Griffin bb70b349ce Merging CL 2804086 from //UE4/Release-4.11 to Dev-Main (//UE4/Dev-Main) to isolate copyright update
#lockdown Nick.Penwarden

[CL 2819020 by Matthew Griffin in Main branch]
2016-01-07 08:17:16 -05:00

1553 lines
48 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "DetailCustomizationsPrivatePCH.h"
#include "BodyInstanceCustomization.h"
#include "ScopedTransaction.h"
#include "Editor/Documentation/Public/IDocumentation.h"
#include "Engine/CollisionProfile.h"
#include "SNumericEntryBox.h"
#include "PhysicsEngine/BodySetup.h"
#include "ObjectEditorUtils.h"
#define LOCTEXT_NAMESPACE "BodyInstanceCustomization"
#define RowWidth_Customization 50
////////////////////////////////////////////////////////////////
FBodyInstanceCustomization::FBodyInstanceCustomization()
{
CollisionProfile = UCollisionProfile::Get();
RefreshCollisionProfiles();
}
void FBodyInstanceCustomization::RefreshCollisionProfiles()
{
int32 NumProfiles = CollisionProfile->GetNumOfProfiles();
// first create profile combo list
CollisionProfileComboList.Empty(NumProfiles + 1);
// first one is default one
CollisionProfileComboList.Add(MakeShareable(new FString(TEXT("Custom..."))));
// go through profile and see if it has mine
for (int32 ProfileId = 0; ProfileId < NumProfiles; ++ProfileId)
{
CollisionProfileComboList.Add(MakeShareable(new FString(CollisionProfile->GetProfileByIndex(ProfileId)->Name.ToString())));
}
}
void FBodyInstanceCustomization::AddCollisionCategory(TSharedRef<class IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
CollisionProfileNameHandle = StructPropertyHandle->GetChildHandle(TEXT("CollisionProfileName"));
CollisionEnabledHandle = StructPropertyHandle->GetChildHandle(TEXT("CollisionEnabled"));
ObjectTypeHandle = StructPropertyHandle->GetChildHandle(TEXT("ObjectType"));
CollisionResponsesHandle = StructPropertyHandle->GetChildHandle(TEXT("CollisionResponses"));
check (CollisionProfileNameHandle.IsValid());
check (CollisionEnabledHandle.IsValid());
check (ObjectTypeHandle.IsValid());
// need to find profile name
FName ProfileName;
TSharedPtr< FString > DisplayName = CollisionProfileComboList[0];
bool bDisplayAdvancedCollisionSettings = true;
// if I have valid profile name
if (CollisionProfileNameHandle->GetValue(ProfileName) == FPropertyAccess::Result::Success && FBodyInstance::IsValidCollisionProfileName(ProfileName) )
{
DisplayName = GetProfileString(ProfileName);
bDisplayAdvancedCollisionSettings = false;
}
const FString PresetsDocLink = TEXT("Shared/Collision");
TSharedPtr<SToolTip> ProfileTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("SelectCollisionPreset", "Select collision presets. You can set this data in Project settings."), NULL, PresetsDocLink, TEXT("PresetDetail"));
IDetailGroup& CollisionGroup = StructBuilder.AddChildGroup( TEXT("Collision"), LOCTEXT("CollisionPresetsLabel", "Collision Presets") );
CollisionGroup.HeaderRow()
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("CollisionPresetsLabel", "Collision Presets"))
.ToolTip(ProfileTooltip)
.Font( IDetailLayoutBuilder::GetDetailFont() )
]
.ValueContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SAssignNew(CollsionProfileComboBox, SComboBox< TSharedPtr<FString> >)
.OptionsSource(&CollisionProfileComboList)
.OnGenerateWidget(this, &FBodyInstanceCustomization::MakeCollisionProfileComboWidget)
.OnSelectionChanged(this, &FBodyInstanceCustomization::OnCollisionProfileChanged, &CollisionGroup )
.OnComboBoxOpening(this, &FBodyInstanceCustomization::OnCollisionProfileComboOpening)
.InitiallySelectedItem(DisplayName)
.IsEnabled( FSlateApplication::Get().GetNormalExecutionAttribute() )
.ContentPadding(2)
.Content()
[
SNew( STextBlock )
.Text(this, &FBodyInstanceCustomization::GetCollisionProfileComboBoxContent)
.Font( IDetailLayoutBuilder::GetDetailFont() )
.ToolTipText(this, &FBodyInstanceCustomization::GetCollisionProfileComboBoxToolTip)
]
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding( 2.0f )
.AutoWidth()
[
SNew(SButton)
.OnClicked(this, &FBodyInstanceCustomization::SetToDefaultProfile)
.Visibility(this, &FBodyInstanceCustomization::ShouldShowResetToDefaultProfile)
.ContentPadding(0.f)
.ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to Default"))
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
.Content()
[
SNew(SImage)
.Image( FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault") )
]
]
];
CollisionGroup.ToggleExpansion(bDisplayAdvancedCollisionSettings);
// now create custom set up
CreateCustomCollisionSetup( StructPropertyHandle, CollisionGroup );
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void FBodyInstanceCustomization::CustomizeChildren( TSharedRef<class IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils )
{
// copy all bodyinstances I'm accessing right now
TArray<void*> StructPtrs;
StructPropertyHandle->AccessRawData(StructPtrs);
check(StructPtrs.Num() != 0);
BodyInstances.AddUninitialized(StructPtrs.Num());
for (auto Iter = StructPtrs.CreateIterator(); Iter; ++Iter)
{
check(*Iter);
BodyInstances[Iter.GetIndex()] = (FBodyInstance*)(*Iter);
}
AddCollisionCategory(StructPropertyHandle, StructBuilder, StructCustomizationUtils);
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
int32 FBodyInstanceCustomization::InitializeObjectTypeComboList()
{
ObjectTypeComboList.Empty();
ObjectTypeValues.Empty();
UEnum * Enum = FindObject<UEnum>(ANY_PACKAGE, TEXT("ECollisionChannel"), true);
const FString KeyName = TEXT("DisplayName");
const FString QueryType = TEXT("TraceQuery");
int32 NumEnum = Enum->NumEnums();
int32 Selected = 0;
uint8 ObjectTypeIndex = 0;
if (ObjectTypeHandle->GetValue(ObjectTypeIndex) != FPropertyAccess::Result::Success)
{
ObjectTypeIndex = 0; // if multi, just let it be 0
}
// go through enum and fill up the list
for (int32 EnumIndex = 0; EnumIndex < NumEnum; ++EnumIndex)
{
// make sure the enum entry is object channel
FString MetaData = Enum->GetMetaData(*QueryType, EnumIndex);
// if query type is object, we allow it to be on movement channel
if (MetaData.Len() == 0 || MetaData[0] == '0')
{
MetaData = Enum->GetMetaData(*KeyName, EnumIndex);
if ( MetaData.Len() > 0 )
{
int32 NewIndex = ObjectTypeComboList.Add( MakeShareable( new FString (MetaData) ));
// @todo: I don't think this would work well if we customize entry, but I don't think we can do that yet
// i.e. enum a { a1=5, a2=6 }
ObjectTypeValues.Add((ECollisionChannel)EnumIndex);
// this solution poses problem when the item was saved with ALREADY INVALID movement channel
// that will automatically select 0, but I think that is the right solution
if (ObjectTypeIndex == EnumIndex)
{
Selected = NewIndex;
}
}
}
}
// it can't be zero. If so you need to fix it
check ( ObjectTypeComboList.Num() > 0 );
return Selected;
}
TSharedPtr<FString> FBodyInstanceCustomization::GetProfileString(FName ProfileName) const
{
FString ProfileNameString = ProfileName.ToString();
// go through profile and see if it has mine
int32 NumProfiles = CollisionProfile->GetNumOfProfiles();
// refresh collision count
if( NumProfiles+1==CollisionProfileComboList.Num() )
{
for(int32 ProfileId = 0; ProfileId < NumProfiles; ++ProfileId)
{
if(*CollisionProfileComboList[ProfileId+1].Get() == ProfileNameString)
{
return CollisionProfileComboList[ProfileId+1];
}
}
}
return CollisionProfileComboList[0];
}
// filter through find valid index of enum values matching each item
// this needs refresh when displayname of the enum has change
// which can happen when we have engine project settings in place working
void FBodyInstanceCustomization::UpdateValidCollisionChannels()
{
// find the enum
UEnum * Enum = FindObject<UEnum>(ANY_PACKAGE, TEXT("ECollisionChannel"), true);
// we need this Enum
check (Enum);
const FString KeyName = TEXT("DisplayName");
const FString TraceType = TEXT("TraceQuery");
// need to initialize displaynames separate
int32 NumEnum = Enum->NumEnums();
ValidCollisionChannels.Empty(NumEnum);
// first go through enum entry, and add suffix to displaynames
for ( int32 EnumIndex=0; EnumIndex<NumEnum; ++EnumIndex )
{
const FString MetaData = Enum->GetMetaData(*KeyName, EnumIndex);
if ( MetaData.Len() > 0 )
{
FCollisionChannelInfo Info;
Info.DisplayName = MetaData;
Info.CollisionChannel = (ECollisionChannel)EnumIndex;
if (Enum->GetMetaData(*TraceType, EnumIndex) == TEXT("1"))
{
Info.TraceType = true;
}
else
{
Info.TraceType = false;
}
ValidCollisionChannels.Add(Info);
}
}
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void FBodyInstanceCustomization::CreateCustomCollisionSetup( TSharedRef<class IPropertyHandle> StructPropertyHandle, class IDetailGroup& CollisionGroup )
{
UpdateValidCollisionChannels();
if (ValidCollisionChannels.Num() == 0)
{
return;
}
int32 TotalNumChildren = ValidCollisionChannels.Num();
TAttribute<bool> CustomCollisionEnabled( this, &FBodyInstanceCustomization::ShouldEnableCustomCollisionSetup );
TAttribute<EVisibility> CustomCollisionVisibility(this, &FBodyInstanceCustomization::ShouldShowCustomCollisionSetup);
// initialize ObjectTypeComboList
// we only display things that has "DisplayName"
int32 IndexSelected = InitializeObjectTypeComboList();
CollisionGroup.AddPropertyRow(CollisionEnabledHandle.ToSharedRef())
.IsEnabled(CustomCollisionEnabled)
.Visibility(CustomCollisionVisibility);
if (!StructPropertyHandle->GetProperty()->GetBoolMetaData(TEXT("HideObjectType")))
{
CollisionGroup.AddWidgetRow()
.IsEnabled(CustomCollisionEnabled)
.Visibility(CustomCollisionVisibility)
.NameContent()
[
ObjectTypeHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
SAssignNew(ObjectTypeComboBox, SComboBox<TSharedPtr<FString>>)
.OptionsSource(&ObjectTypeComboList)
.OnGenerateWidget(this, &FBodyInstanceCustomization::MakeObjectTypeComboWidget)
.OnSelectionChanged(this, &FBodyInstanceCustomization::OnObjectTypeChanged)
.InitiallySelectedItem(ObjectTypeComboList[IndexSelected])
.IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute())
.ContentPadding(2)
.Content()
[
SNew(STextBlock)
.Text(this, &FBodyInstanceCustomization::GetObjectTypeComboBoxContent)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
];
}
// Add Title
CollisionGroup.AddWidgetRow()
.IsEnabled(CustomCollisionEnabled)
.Visibility(CustomCollisionVisibility)
.ValueContent()
.MaxDesiredWidth(0)
.MinDesiredWidth(0)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(RowWidth_Customization)
.HAlign( HAlign_Left )
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("IgnoreCollisionLabel", "Ignore"))
.Font( IDetailLayoutBuilder::GetDetailFontBold() )
]
]
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.HAlign( HAlign_Left )
.WidthOverride(RowWidth_Customization)
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("OverlapCollisionLabel", "Overlap"))
.Font( IDetailLayoutBuilder::GetDetailFontBold() )
]
]
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(LOCTEXT("BlockCollisionLabel", "Block"))
.Font( IDetailLayoutBuilder::GetDetailFontBold() )
]
];
// Add All check box
CollisionGroup.AddWidgetRow()
.IsEnabled(CustomCollisionEnabled)
.Visibility(CustomCollisionVisibility)
.NameContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.Padding( 2.0f )
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew( STextBlock )
.Text(LOCTEXT("CollisionResponsesLabel", "Collision Responses"))
.Font( IDetailLayoutBuilder::GetDetailFontBold() )
.ToolTipText(LOCTEXT("CollsionResponse_ToolTip", "When trace by channel, this information will be used for filtering."))
]
+SHorizontalBox::Slot()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
IDocumentation::Get()->CreateAnchor( TEXT("Engine/Physics/Collision") )
]
]
.ValueContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(RowWidth_Customization)
.Content()
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &FBodyInstanceCustomization::OnAllCollisionChannelChanged, ECR_Ignore )
.IsChecked( this, &FBodyInstanceCustomization::IsAllCollisionChannelChecked, ECR_Ignore )
]
]
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(RowWidth_Customization)
.Content()
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &FBodyInstanceCustomization::OnAllCollisionChannelChanged, ECR_Overlap )
.IsChecked( this, &FBodyInstanceCustomization::IsAllCollisionChannelChecked, ECR_Overlap )
]
]
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(RowWidth_Customization)
.Content()
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &FBodyInstanceCustomization::OnAllCollisionChannelChanged, ECR_Block )
.IsChecked( this, &FBodyInstanceCustomization::IsAllCollisionChannelChecked, ECR_Block )
]
]
];
// add header
// Add Title
CollisionGroup.AddWidgetRow()
.IsEnabled(CustomCollisionEnabled)
.Visibility(CustomCollisionVisibility)
.NameContent()
[
SNew(SBox)
.Padding(FMargin(10, 0))
.Content()
[
SNew(STextBlock)
.Text(LOCTEXT("CollisionTraceResponsesLabel", "Trace Responses"))
.Font(IDetailLayoutBuilder::GetDetailFontBold())
]
];
// each channel set up
FName MetaData(TEXT("DisplayName"));
// Add option for each channel - first do trace
for ( int32 Index=0; Index<TotalNumChildren; ++Index )
{
if (ValidCollisionChannels[Index].TraceType)
{
FString DisplayName = ValidCollisionChannels[Index].DisplayName;
EVisibility Visibility = EVisibility::Visible;
CollisionGroup.AddWidgetRow()
.IsEnabled(CustomCollisionEnabled)
.Visibility(CustomCollisionVisibility)
.NameContent()
[
SNew(SBox)
.Padding(FMargin(15, 0))
.Content()
[
SNew(STextBlock)
.Text(FText::FromString(DisplayName))
.Font( IDetailLayoutBuilder::GetDetailFont() )
]
]
.ValueContent()
[
SNew( SHorizontalBox )
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SBox)
.WidthOverride(RowWidth_Customization)
.Content()
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &FBodyInstanceCustomization::OnCollisionChannelChanged, Index, ECR_Ignore )
.IsChecked( this, &FBodyInstanceCustomization::IsCollisionChannelChecked, Index, ECR_Ignore )
]
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SBox)
.WidthOverride(RowWidth_Customization)
.Content()
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &FBodyInstanceCustomization::OnCollisionChannelChanged, Index, ECR_Overlap )
.IsChecked( this, &FBodyInstanceCustomization::IsCollisionChannelChecked, Index, ECR_Overlap )
]
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &FBodyInstanceCustomization::OnCollisionChannelChanged, Index, ECR_Block )
.IsChecked( this, &FBodyInstanceCustomization::IsCollisionChannelChecked, Index, ECR_Block )
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SNew(SButton)
.OnClicked(this, &FBodyInstanceCustomization::SetToDefaultResponse, Index)
.Visibility(this, &FBodyInstanceCustomization::ShouldShowResetToDefaultResponse, Index)
.ContentPadding(0.f)
.ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to Default"))
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
.Content()
[
SNew(SImage)
.Image( FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault") )
]
]
];
}
}
// Add Title
CollisionGroup.AddWidgetRow()
.IsEnabled(CustomCollisionEnabled)
.Visibility(CustomCollisionVisibility)
.NameContent()
[
SNew(SBox)
.Padding(FMargin(10, 0))
.Content()
[
SNew( STextBlock )
.Text(LOCTEXT("CollisionObjectResponses", "Object Responses"))
.Font( IDetailLayoutBuilder::GetDetailFontBold() )
]
];
for ( int32 Index=0; Index<TotalNumChildren; ++Index )
{
if (!ValidCollisionChannels[Index].TraceType)
{
FString DisplayName = ValidCollisionChannels[Index].DisplayName;
EVisibility Visibility = EVisibility::Visible;
CollisionGroup.AddWidgetRow()
.IsEnabled(CustomCollisionEnabled)
.Visibility(CustomCollisionVisibility)
.NameContent()
[
SNew(SBox)
.Padding(FMargin(15, 0))
.Content()
[
SNew(STextBlock)
.Text(FText::FromString(DisplayName))
.Font( IDetailLayoutBuilder::GetDetailFont() )
]
]
.ValueContent()
[
SNew( SHorizontalBox )
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SBox)
.WidthOverride(RowWidth_Customization)
.Content()
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &FBodyInstanceCustomization::OnCollisionChannelChanged, Index, ECR_Ignore )
.IsChecked( this, &FBodyInstanceCustomization::IsCollisionChannelChecked, Index, ECR_Ignore )
]
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SBox)
.WidthOverride(RowWidth_Customization)
.Content()
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &FBodyInstanceCustomization::OnCollisionChannelChanged, Index, ECR_Overlap )
.IsChecked( this, &FBodyInstanceCustomization::IsCollisionChannelChecked, Index, ECR_Overlap )
]
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SBox)
.WidthOverride(RowWidth_Customization)
.Content()
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &FBodyInstanceCustomization::OnCollisionChannelChanged, Index, ECR_Block )
.IsChecked( this, &FBodyInstanceCustomization::IsCollisionChannelChecked, Index, ECR_Block )
]
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)
[
SNew(SButton)
.OnClicked(this, &FBodyInstanceCustomization::SetToDefaultResponse, Index)
.Visibility(this, &FBodyInstanceCustomization::ShouldShowResetToDefaultResponse, Index)
.ContentPadding(0.f)
.ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to Default"))
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
.Content()
[
SNew(SImage)
.Image( FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault") )
]
]
];
}
}
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef<SWidget> FBodyInstanceCustomization::MakeObjectTypeComboWidget( TSharedPtr<FString> InItem )
{
return SNew(STextBlock) .Text( FText::FromString(*InItem) ) .Font( IDetailLayoutBuilder::GetDetailFont() );
}
void FBodyInstanceCustomization::OnObjectTypeChanged( TSharedPtr<FString> NewSelection, ESelectInfo::Type SelectInfo )
{
// if it's set from code, we did that on purpose
if (SelectInfo != ESelectInfo::Direct)
{
FString NewValue = *NewSelection.Get();
uint8 NewEnumVal = ECC_WorldStatic;
// find index of NewValue
for (auto Iter = ObjectTypeComboList.CreateConstIterator(); Iter; ++Iter)
{
// if value is same
if (*(Iter->Get()) == NewValue)
{
NewEnumVal = ObjectTypeValues[Iter.GetIndex()];
}
}
ensure(ObjectTypeHandle->SetValue(NewEnumVal) == FPropertyAccess::Result::Success);
}
}
FText FBodyInstanceCustomization::GetObjectTypeComboBoxContent() const
{
FName ObjectTypeName;
if (ObjectTypeHandle->GetValue(ObjectTypeName) == FPropertyAccess::Result::MultipleValues)
{
return LOCTEXT("MultipleValues", "Multiple Values");
}
return FText::FromString(*ObjectTypeComboBox.Get()->GetSelectedItem().Get());
}
TSharedRef<SWidget> FBodyInstanceCustomization::MakeCollisionProfileComboWidget(TSharedPtr<FString> InItem)
{
FString ProfileMessage;
FCollisionResponseTemplate ProfileData;
if (CollisionProfile->GetProfileTemplate(FName(**InItem), ProfileData))
{
ProfileMessage = ProfileData.HelpMessage;
}
return
SNew(STextBlock)
.Text(FText::FromString(*InItem))
.ToolTipText(FText::FromString(ProfileMessage))
.Font(IDetailLayoutBuilder::GetDetailFont());
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE! I have a lot of ensure to make sure it's set correctly
// in case for if type changes or any set up changes, this won't work, but ensure will remind you that! :)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FBodyInstanceCustomization::OnCollisionProfileComboOpening()
{
FName ProfileName;
if (CollisionProfileNameHandle->GetValue(ProfileName) != FPropertyAccess::Result::MultipleValues)
{
TSharedPtr<FString> ComboStringPtr = GetProfileString(ProfileName);
if( ComboStringPtr.IsValid() )
{
CollsionProfileComboBox->SetSelectedItem(ComboStringPtr);
}
}
}
void FBodyInstanceCustomization::OnCollisionProfileChanged( TSharedPtr<FString> NewSelection, ESelectInfo::Type SelectInfo, IDetailGroup* CollisionGroup )
{
// if it's set from code, we did that on purpose
if (SelectInfo != ESelectInfo::Direct)
{
FString NewValue = *NewSelection.Get();
int32 NumProfiles = CollisionProfile->GetNumOfProfiles();
for (int32 ProfileId = 0; ProfileId < NumProfiles; ++ProfileId)
{
const FCollisionResponseTemplate* CurProfile = CollisionProfile->GetProfileByIndex(ProfileId);
if ( NewValue == CurProfile->Name.ToString() )
{
// trigget transaction before UpdateCollisionProfile
const FScopedTransaction Transaction( LOCTEXT( "ChangeCollisionProfile", "Change Collision Profile" ) );
// set profile set up
ensure ( CollisionProfileNameHandle->SetValue(NewValue) == FPropertyAccess::Result::Success );
UpdateCollisionProfile();
return;
}
}
if( NewSelection == CollisionProfileComboList[0] )
{
// Force expansion when the user chooses the selected item
CollisionGroup->ToggleExpansion( true );
}
// if none of them found, clear it
FName Name=UCollisionProfile::CustomCollisionProfileName;
ensure ( CollisionProfileNameHandle->SetValue(Name) == FPropertyAccess::Result::Success );
}
}
void FBodyInstanceCustomization::UpdateCollisionProfile()
{
FName ProfileName;
// if I have valid profile name
if (CollisionProfileNameHandle->GetValue(ProfileName) == FPropertyAccess::Result::Success && FBodyInstance::IsValidCollisionProfileName(ProfileName) )
{
int32 NumProfiles = CollisionProfile->GetNumOfProfiles();
for (int32 ProfileId = 0; ProfileId < NumProfiles; ++ProfileId)
{
// find the profile
const FCollisionResponseTemplate* CurProfile = CollisionProfile->GetProfileByIndex(ProfileId);
if (ProfileName == CurProfile->Name)
{
// set the profile set up
ensure(CollisionEnabledHandle->SetValue((uint8)CurProfile->CollisionEnabled) == FPropertyAccess::Result::Success);
ensure(ObjectTypeHandle->SetValue((uint8)CurProfile->ObjectType) == FPropertyAccess::Result::Success);
SetCollisionResponseContainer(CurProfile->ResponseToChannels);
// now update combo box
CollsionProfileComboBox.Get()->SetSelectedItem(CollisionProfileComboList[ProfileId+1]);
if (ObjectTypeComboBox.IsValid())
{
for (auto Iter = ObjectTypeValues.CreateConstIterator(); Iter; ++Iter)
{
if (*Iter == CurProfile->ObjectType)
{
ObjectTypeComboBox.Get()->SetSelectedItem(ObjectTypeComboList[Iter.GetIndex()]);
break;
}
}
}
return;
}
}
}
CollsionProfileComboBox.Get()->SetSelectedItem(CollisionProfileComboList[0]);
}
FReply FBodyInstanceCustomization::SetToDefaultProfile()
{
// trigger transaction before UpdateCollisionProfile
const FScopedTransaction Transaction( LOCTEXT( "ResetCollisionProfile", "Reset Collision Profile" ) );
CollisionProfileNameHandle.Get()->ResetToDefault();
UpdateCollisionProfile();
return FReply::Handled();
}
EVisibility FBodyInstanceCustomization::ShouldShowResetToDefaultProfile() const
{
if (CollisionProfileNameHandle.Get()->DiffersFromDefault())
{
return EVisibility::Visible;
}
return EVisibility::Hidden;
}
FReply FBodyInstanceCustomization::SetToDefaultResponse(int32 ValidIndex)
{
if ( ValidCollisionChannels.IsValidIndex(ValidIndex) )
{
const FScopedTransaction Transaction( LOCTEXT( "ResetCollisionResponse", "Reset Collision Response" ) );
const ECollisionResponse DefaultResponse = FCollisionResponseContainer::GetDefaultResponseContainer().GetResponse(ValidCollisionChannels[ValidIndex].CollisionChannel);
SetResponse(ValidIndex, DefaultResponse);
return FReply::Handled();
}
return FReply::Unhandled();
}
EVisibility FBodyInstanceCustomization::ShouldShowResetToDefaultResponse(int32 ValidIndex) const
{
if ( ValidCollisionChannels.IsValidIndex(ValidIndex) )
{
const ECollisionResponse DefaultResponse = FCollisionResponseContainer::GetDefaultResponseContainer().GetResponse(ValidCollisionChannels[ValidIndex].CollisionChannel);
if (IsCollisionChannelChecked(ValidIndex, DefaultResponse) != ECheckBoxState::Checked)
{
return EVisibility::Visible;
}
}
return EVisibility::Hidden;
}
bool FBodyInstanceCustomization::ShouldEnableCustomCollisionSetup() const
{
FName ProfileName;
if (CollisionProfileNameHandle->GetValue(ProfileName) == FPropertyAccess::Result::Success && FBodyInstance::IsValidCollisionProfileName(ProfileName) == false)
{
return true;
}
return false;
}
EVisibility FBodyInstanceCustomization::ShouldShowCustomCollisionSetup() const
{
return EVisibility::Visible;//return (bDisplayAdvancedCollisionSettings)? EVisibility::Visible : EVisibility::Hidden;
}
FText FBodyInstanceCustomization::GetCollisionProfileComboBoxContent() const
{
FName ProfileName;
if (CollisionProfileNameHandle->GetValue(ProfileName) == FPropertyAccess::Result::MultipleValues)
{
return LOCTEXT("MultipleValues", "Multiple Values");
}
return FText::FromString(*GetProfileString(ProfileName).Get());
}
FText FBodyInstanceCustomization::GetCollisionProfileComboBoxToolTip() const
{
FName ProfileName;
if (CollisionProfileNameHandle->GetValue(ProfileName) == FPropertyAccess::Result::Success)
{
FCollisionResponseTemplate ProfileData;
if ( CollisionProfile->GetProfileTemplate(ProfileName, ProfileData) )
{
return FText::FromString(ProfileData.HelpMessage);
}
return FText::GetEmpty();
}
return LOCTEXT("MultipleValues", "Multiple Values");
}
void FBodyInstanceCustomization::OnCollisionChannelChanged(ECheckBoxState InNewValue, int32 ValidIndex, ECollisionResponse InCollisionResponse)
{
if ( ValidCollisionChannels.IsValidIndex(ValidIndex) )
{
SetResponse(ValidIndex, InCollisionResponse);
}
}
void FBodyInstanceCustomization::SetResponse(int32 ValidIndex, ECollisionResponse InCollisionResponse)
{
const FScopedTransaction Transaction( LOCTEXT( "ChangeIndividualChannel", "Change Individual Channel" ) );
CollisionResponsesHandle->NotifyPreChange();
for (auto Iter=BodyInstances.CreateIterator(); Iter; ++Iter)
{
FBodyInstance* BodyInstance = *Iter;
BodyInstance->CollisionResponses.SetResponse(ValidCollisionChannels[ValidIndex].CollisionChannel, InCollisionResponse);
}
CollisionResponsesHandle->NotifyPostChange();
}
ECheckBoxState FBodyInstanceCustomization::IsCollisionChannelChecked( int32 ValidIndex, ECollisionResponse InCollisionResponse) const
{
TArray<uint8> CollisionResponses;
if ( ValidCollisionChannels.IsValidIndex(ValidIndex) )
{
for (auto Iter=BodyInstances.CreateConstIterator(); Iter; ++Iter)
{
FBodyInstance* BodyInstance = *Iter;
CollisionResponses.AddUnique(BodyInstance->CollisionResponses.GetResponse(ValidCollisionChannels[ValidIndex].CollisionChannel));
}
if (CollisionResponses.Num() == 1)
{
if (CollisionResponses[0] == InCollisionResponse)
{
return ECheckBoxState::Checked;
}
else
{
return ECheckBoxState::Unchecked;
}
}
else if (CollisionResponses.Contains(InCollisionResponse))
{
return ECheckBoxState::Undetermined;
}
// if it didn't contain and it's not found, return Unchecked
return ECheckBoxState::Unchecked;
}
return ECheckBoxState::Undetermined;
}
void FBodyInstanceCustomization::OnAllCollisionChannelChanged(ECheckBoxState InNewValue, ECollisionResponse InCollisionResponse)
{
FCollisionResponseContainer NewContainer;
NewContainer.SetAllChannels(InCollisionResponse);
SetCollisionResponseContainer(NewContainer);
}
ECheckBoxState FBodyInstanceCustomization::IsAllCollisionChannelChecked(ECollisionResponse InCollisionResponse) const
{
ECheckBoxState State = ECheckBoxState::Undetermined;
uint32 TotalNumChildren = ValidCollisionChannels.Num();
if (TotalNumChildren >= 1)
{
State = IsCollisionChannelChecked(0, InCollisionResponse);
for (uint32 Index = 1; Index < TotalNumChildren; ++Index)
{
if (State != IsCollisionChannelChecked(Index, InCollisionResponse))
{
State = ECheckBoxState::Undetermined;
break;
}
}
}
return State;
}
void FBodyInstanceCustomization::SetCollisionResponseContainer(const FCollisionResponseContainer& ResponseContainer)
{
// trigget transaction before UpdateCollisionProfile
const FScopedTransaction Transaction( LOCTEXT( "Collision", "Collision Channel Changes" ) );
CollisionResponsesHandle->NotifyPreChange();
uint32 TotalNumChildren = ValidCollisionChannels.Num();
// only go through valid channels
for (uint32 Index = 0; Index < TotalNumChildren; ++Index)
{
ECollisionChannel Channel = ValidCollisionChannels[Index].CollisionChannel;
ECollisionResponse Response = ResponseContainer.GetResponse(Channel);
// iterate through bodyinstance and fix it
for (FBodyInstance* BodyInstance : BodyInstances)
{
BodyInstance->CollisionResponses.SetResponse(Channel, Response);
}
}
CollisionResponsesHandle->NotifyPostChange();
}
FBodyInstanceCustomizationHelper::FBodyInstanceCustomizationHelper(const TArray<TWeakObjectPtr<UObject>>& InObjectsCustomized)
: ObjectsCustomized(InObjectsCustomized)
{
}
void FBodyInstanceCustomizationHelper::UpdateFilters()
{
bDisplayMass = true;
bDisplayConstraints = true;
bDisplayEnablePhysics = true;
for (int32 i = 0; i < ObjectsCustomized.Num(); ++i)
{
if (ObjectsCustomized[i].IsValid())
{
if(ObjectsCustomized[i]->IsA(UDestructibleComponent::StaticClass()))
{
bDisplayMass = false;
bDisplayConstraints = false;
}
else
{
if(ObjectsCustomized[i]->IsA(UBodySetup::StaticClass()))
{
bDisplayEnablePhysics = false;
bDisplayConstraints = false;
}
}
}
}
}
void FBodyInstanceCustomizationHelper::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder, TSharedRef<IPropertyHandle> BodyInstanceHandler)
{
if( BodyInstanceHandler->IsValidHandle() )
{
UpdateFilters();
IDetailCategoryBuilder& PhysicsCategory = DetailBuilder.EditCategory("Physics");
TSharedRef<IPropertyHandle> PhysicsEnable = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bSimulatePhysics)).ToSharedRef();
if(bDisplayEnablePhysics)
{
PhysicsCategory.AddProperty(PhysicsEnable).EditCondition(TAttribute<bool>(this, &FBodyInstanceCustomizationHelper::IsSimulatePhysicsEditable), NULL);
}
else
{
PhysicsEnable->MarkHiddenByCustomization();
}
AddMassInKg(PhysicsCategory, BodyInstanceHandler);
PhysicsCategory.AddProperty(BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, LinearDamping)));
PhysicsCategory.AddProperty(BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, AngularDamping)));
PhysicsCategory.AddProperty(BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bEnableGravity)));
AddBodyConstraint(PhysicsCategory, BodyInstanceHandler);
//ADVANCED
PhysicsCategory.AddProperty(BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bAutoWeld)))
.Visibility(TAttribute<EVisibility>(this, &FBodyInstanceCustomizationHelper::IsAutoWeldVisible))
.EditCondition(TAttribute<bool>(this, &FBodyInstanceCustomizationHelper::IsAutoWeldEditable), NULL);
PhysicsCategory.AddProperty(BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bStartAwake)));
PhysicsCategory.AddProperty(BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, COMNudge)));
PhysicsCategory.AddProperty(BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, MassScale)));
AddMaxAngularVelocity(PhysicsCategory, BodyInstanceHandler);
TSharedRef<IPropertyHandle> AsyncEnabled = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bUseAsyncScene)).ToSharedRef();
PhysicsCategory.AddProperty(AsyncEnabled).EditCondition(TAttribute<bool>(this, &FBodyInstanceCustomizationHelper::IsUseAsyncEditable), NULL);
//Add the rest
uint32 NumChildren = 0;
BodyInstanceHandler->GetNumChildren(NumChildren);
for(uint32 ChildIdx = 0; ChildIdx < NumChildren; ++ChildIdx)
{
TSharedPtr<IPropertyHandle> ChildProp = BodyInstanceHandler->GetChildHandle(ChildIdx);
FName CategoryName = FObjectEditorUtils::GetCategoryFName(ChildProp->GetProperty());
if(ChildProp->IsCustomized() == false && CategoryName == FName(TEXT("Physics"))) //add the rest of the physics properties
{
PhysicsCategory.AddProperty(ChildProp);
}
}
}
}
bool FBodyInstanceCustomizationHelper::IsSimulatePhysicsEditable() const
{
// Check whether to enable editing of bSimulatePhysics - this will happen if all objects are UPrimitiveComponents & have collision geometry.
bool bEnableSimulatePhysics = ObjectsCustomized.Num() > 0;
for (TWeakObjectPtr<UObject> CustomizedObject : ObjectsCustomized)
{
if (UPrimitiveComponent* PrimitiveComponent = Cast<UPrimitiveComponent>(CustomizedObject.Get()))
{
if (!PrimitiveComponent->CanEditSimulatePhysics())
{
bEnableSimulatePhysics = false;
break;
}
}
}
return bEnableSimulatePhysics;
}
bool FBodyInstanceCustomizationHelper::IsUseAsyncEditable() const
{
// Check whether to enable editing of bUseAsyncScene - this will happen if all objects are movable and the project uses an AsyncScene
if (!UPhysicsSettings::Get()->bEnableAsyncScene)
{
return false;
}
bool bEnableUseAsyncScene = ObjectsCustomized.Num() > 0;
for (auto ObjectIt = ObjectsCustomized.CreateConstIterator(); ObjectIt; ++ObjectIt)
{
if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UPrimitiveComponent::StaticClass()))
{
TWeakObjectPtr<USceneComponent> SceneComponent = CastChecked<USceneComponent>(ObjectIt->Get());
if (SceneComponent.IsValid() && SceneComponent->Mobility != EComponentMobility::Movable)
{
bEnableUseAsyncScene = false;
break;
}
// Skeletal mesh uses a physics asset which will have multiple bodies - these bodies have their own bUseAsyncScene which is what we actually use - the flag on the skeletal mesh is not used
TWeakObjectPtr<USkeletalMeshComponent> SkeletalMeshComponent = Cast<USkeletalMeshComponent>(ObjectIt->Get());
if (SkeletalMeshComponent.IsValid())
{
bEnableUseAsyncScene = false;
break;
}
}
else if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UBodySetup::StaticClass()))
{
continue;
}
else
{
bEnableUseAsyncScene = false;
break;
}
}
return bEnableUseAsyncScene;
}
EVisibility FBodyInstanceCustomizationHelper::IsMassVisible(bool bOverrideMass) const
{
bool bIsMassReadOnly = IsBodyMassReadOnly();
if (bOverrideMass)
{
return bIsMassReadOnly ? EVisibility::Collapsed : EVisibility::Visible;
}
else
{
return bIsMassReadOnly ? EVisibility::Visible : EVisibility::Collapsed;
}
}
bool FBodyInstanceCustomizationHelper::IsBodyMassReadOnly() const
{
for (auto ObjectIt = ObjectsCustomized.CreateConstIterator(); ObjectIt; ++ObjectIt)
{
if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UPrimitiveComponent::StaticClass()))
{
if (UPrimitiveComponent* Comp = Cast<UPrimitiveComponent>(ObjectIt->Get()))
{
if (Comp->BodyInstance.bOverrideMass == false) { return true; }
}
}else if(ObjectIt->IsValid() && (*ObjectIt)->IsA(UBodySetup::StaticClass()))
{
UBodySetup* BS = Cast<UBodySetup>(ObjectIt->Get());
if (BS->DefaultInstance.bOverrideMass == false)
{
return true;
}
}
}
return false;
}
TOptional<float> FBodyInstanceCustomizationHelper::OnGetBodyMaxAngularVelocity() const
{
UPrimitiveComponent* Comp = nullptr;
const float MaxAngularVelocity = UPhysicsSettings::Get()->MaxAngularVelocity;
for (auto ObjectIt = ObjectsCustomized.CreateConstIterator(); ObjectIt; ++ObjectIt)
{
if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UPrimitiveComponent::StaticClass()))
{
Comp = Cast<UPrimitiveComponent>(ObjectIt->Get());
Comp->BodyInstance.MaxAngularVelocity = MaxAngularVelocity; //update max angular velocity so that overriding gives the same value initially
}
}
return MaxAngularVelocity;
}
bool FBodyInstanceCustomizationHelper::IsMaxAngularVelocityReadOnly() const
{
for (auto ObjectIt = ObjectsCustomized.CreateConstIterator(); ObjectIt; ++ObjectIt)
{
if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UPrimitiveComponent::StaticClass()))
{
if (UPrimitiveComponent* Comp = Cast<UPrimitiveComponent>(ObjectIt->Get()))
{
if (Comp->BodyInstance.bOverrideMaxAngularVelocity == false) { return true; }
}
}
}
return false;
}
EVisibility FBodyInstanceCustomizationHelper::IsMaxAngularVelocityVisible(bool bOverrideMaxAngularVelocity) const
{
bool bIsMaxAngularVelocityReadOnly = IsMaxAngularVelocityReadOnly();
if (bOverrideMaxAngularVelocity)
{
return bIsMaxAngularVelocityReadOnly ? EVisibility::Collapsed : EVisibility::Visible;
}
else
{
return bIsMaxAngularVelocityReadOnly ? EVisibility::Visible : EVisibility::Collapsed;
}
}
bool FBodyInstanceCustomizationHelper::IsAutoWeldEditable() const
{
for (int32 i = 0; i < ObjectsCustomized.Num(); ++i)
{
if (UPrimitiveComponent * SceneComponent = Cast<UPrimitiveComponent>(ObjectsCustomized[i].Get()))
{
if (FBodyInstance* BI = SceneComponent->GetBodyInstance())
{
if (BI->ShouldInstanceSimulatingPhysics())
{
return false;
}
}
}
}
return true;
}
EVisibility FBodyInstanceCustomizationHelper::IsAutoWeldVisible() const
{
for (int32 i = 0; i < ObjectsCustomized.Num(); ++i)
{
if (ObjectsCustomized[i].IsValid() && !(ObjectsCustomized[i]->IsA(UStaticMeshComponent::StaticClass()) || ObjectsCustomized[i]->IsA(UShapeComponent::StaticClass())))
{
return EVisibility::Collapsed;
}
}
return EVisibility::Visible;
}
TOptional<float> FBodyInstanceCustomizationHelper::OnGetBodyMass() const
{
UPrimitiveComponent* Comp = nullptr;
UBodySetup* BS = nullptr;
float Mass = 0.0f;
bool bMultipleValue = false;
for (auto ObjectIt = ObjectsCustomized.CreateConstIterator(); ObjectIt; ++ObjectIt)
{
if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UPrimitiveComponent::StaticClass()))
{
Comp = Cast<UPrimitiveComponent>(ObjectIt->Get());
float CompMass = Comp->CalculateMass();
Comp->BodyInstance.MassInKg = CompMass; //update the component's override mass to be the calculated one
if (Mass == 0.0f || FMath::Abs(Mass - CompMass) < 0.1f)
{
Mass = CompMass;
}
else
{
bMultipleValue = true;
break;
}
}
else if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UBodySetup::StaticClass()))
{
BS = Cast<UBodySetup>(ObjectIt->Get());
float BSMass = BS->CalculateMass();
if (Mass == 0.0f || FMath::Abs(Mass - BSMass) < 0.1f)
{
Mass = BSMass;
}
else
{
bMultipleValue = true;
break;
}
}
}
if (bMultipleValue)
{
return TOptional<float>();
}
return Mass;
}
EVisibility FBodyInstanceCustomizationHelper::IsDOFMode(EDOFMode::Type Mode) const
{
bool bVisible = false;
if (DOFModeProperty.IsValid() && bDisplayConstraints)
{
uint8 CurrentMode;
if (DOFModeProperty->GetValue(CurrentMode) == FPropertyAccess::Success)
{
EDOFMode::Type PropertyDOF = FBodyInstance::ResolveDOFMode(static_cast<EDOFMode::Type>(CurrentMode));
bVisible = PropertyDOF == Mode;
}
}
return bVisible ? EVisibility::Visible : EVisibility::Collapsed;
}
void FBodyInstanceCustomizationHelper::AddMassInKg(IDetailCategoryBuilder& PhysicsCategory, TSharedRef<IPropertyHandle> BodyInstanceHandler)
{
if (bDisplayMass)
{
TSharedRef<IPropertyHandle> MassInKGHandle = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, MassInKg)).ToSharedRef();
PhysicsCategory.AddProperty(MassInKGHandle).CustomWidget()
.NameContent()
[
MassInKGHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.Padding(0.f, 0.f, 10.f, 0.f)
[
SNew(SNumericEntryBox<float>)
.IsEnabled(false)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Value(this, &FBodyInstanceCustomizationHelper::OnGetBodyMass)
.Visibility(this, &FBodyInstanceCustomizationHelper::IsMassVisible, false)
]
+ SVerticalBox::Slot()
.Padding(0.f, 0.f, 10.f, 0.f)
[
SNew(SVerticalBox)
.Visibility(this, &FBodyInstanceCustomizationHelper::IsMassVisible, true)
+ SVerticalBox::Slot()
.AutoHeight()
[
MassInKGHandle->CreatePropertyValueWidget()
]
]
];
}
}
void FBodyInstanceCustomizationHelper::AddMaxAngularVelocity(IDetailCategoryBuilder& PhysicsCategory, TSharedRef<IPropertyHandle> BodyInstanceHandler)
{
TSharedRef<IPropertyHandle> MaxAngularVelocityHandle = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, MaxAngularVelocity)).ToSharedRef();
PhysicsCategory.AddProperty(MaxAngularVelocityHandle).CustomWidget()
.NameContent()
[
MaxAngularVelocityHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.Padding(0.f, 0.f, 10.f, 0.f)
[
SNew(SNumericEntryBox<float>)
.IsEnabled(false)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Value(this, &FBodyInstanceCustomizationHelper::OnGetBodyMaxAngularVelocity)
.Visibility(this, &FBodyInstanceCustomizationHelper::IsMaxAngularVelocityVisible, false)
]
+ SVerticalBox::Slot()
.Padding(0.f, 0.f, 10.f, 0.f)
[
SNew(SVerticalBox)
.Visibility(this, &FBodyInstanceCustomizationHelper::IsMaxAngularVelocityVisible, true)
+ SVerticalBox::Slot()
.AutoHeight()
[
MaxAngularVelocityHandle->CreatePropertyValueWidget()
]
]
];
}
void FBodyInstanceCustomizationHelper::AddBodyConstraint(IDetailCategoryBuilder& PhysicsCategory, TSharedRef<IPropertyHandle> BodyInstanceHandler)
{
const float XYZPadding = 5.f;
TSharedPtr<IPropertyHandle> bLockXTranslation = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bLockXTranslation));
bLockXTranslation->MarkHiddenByCustomization();
TSharedPtr<IPropertyHandle> bLockYTranslation = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bLockYTranslation));
bLockYTranslation->MarkHiddenByCustomization();
TSharedPtr<IPropertyHandle> bLockZTranslation = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bLockZTranslation));
bLockZTranslation->MarkHiddenByCustomization();
TSharedPtr<IPropertyHandle> bLockXRotation = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bLockXRotation));
bLockXRotation->MarkHiddenByCustomization();
TSharedPtr<IPropertyHandle> bLockYRotation = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bLockYRotation));
bLockYRotation->MarkHiddenByCustomization();
TSharedPtr<IPropertyHandle> bLockZRotation = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bLockZRotation));
bLockZRotation->MarkHiddenByCustomization();
DOFModeProperty = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, DOFMode)).ToSharedRef();
DOFModeProperty->MarkHiddenByCustomization();
TSharedRef<IPropertyHandle> bLockRotation = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bLockRotation)).ToSharedRef();
bLockRotation->MarkHiddenByCustomization();
TSharedRef<IPropertyHandle> bLockTranslation = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bLockTranslation)).ToSharedRef();
bLockTranslation->MarkHiddenByCustomization();
TSharedRef<IPropertyHandle> CustomDOFPlaneNormal = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, CustomDOFPlaneNormal)).ToSharedRef();
CustomDOFPlaneNormal->MarkHiddenByCustomization();
//the above are all marked hidden even if we don't display constraints because the user wants to hide it anyway
if (bDisplayConstraints)
{
IDetailGroup& ConstraintsGroup = PhysicsCategory.AddGroup(TEXT("ConstraintsGroup"), LOCTEXT("Constraints", "Constraints"));
ConstraintsGroup.AddWidgetRow()
.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FBodyInstanceCustomizationHelper::IsDOFMode, EDOFMode::SixDOF)))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("LockPositionLabel", "Lock Position"))
.ToolTipText(LOCTEXT("LockPositionTooltip", "Locks movement along the specified axis"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(0.f, 0.f, XYZPadding, 0.f)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockXTranslation->CreatePropertyNameWidget()
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockXTranslation->CreatePropertyValueWidget()
]
]
+ SHorizontalBox::Slot()
.Padding(0.f, 0.f, XYZPadding, 0.f)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockYTranslation->CreatePropertyNameWidget()
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockYTranslation->CreatePropertyValueWidget()
]
]
+ SHorizontalBox::Slot()
.Padding(0.f, 0.f, XYZPadding, 0.f)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockZTranslation->CreatePropertyNameWidget()
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockZTranslation->CreatePropertyValueWidget()
]
]
];
ConstraintsGroup.AddWidgetRow()
.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FBodyInstanceCustomizationHelper::IsDOFMode, EDOFMode::SixDOF)))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("LockRotationLabel", "Lock Rotation"))
.ToolTipText(LOCTEXT("LockRotationTooltip", "Locks rotation about the specified axis"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(0.f, 0.f, XYZPadding, 0.f)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockXRotation->CreatePropertyNameWidget()
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockXRotation->CreatePropertyValueWidget()
]
]
+ SHorizontalBox::Slot()
.Padding(0.f, 0.f, XYZPadding, 0.f)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockYRotation->CreatePropertyNameWidget()
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockYRotation->CreatePropertyValueWidget()
]
]
+ SHorizontalBox::Slot()
.Padding(0.f, 0.f, XYZPadding, 0.f)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockZRotation->CreatePropertyNameWidget()
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
bLockZRotation->CreatePropertyValueWidget()
]
]
];
//we only show the custom plane normal if we've selected that mode
ConstraintsGroup.AddPropertyRow(CustomDOFPlaneNormal).Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FBodyInstanceCustomizationHelper::IsDOFMode, EDOFMode::CustomPlane)));
ConstraintsGroup.AddPropertyRow(bLockTranslation).Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FBodyInstanceCustomizationHelper::IsDOFMode, EDOFMode::CustomPlane)));
ConstraintsGroup.AddPropertyRow(bLockRotation).Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FBodyInstanceCustomizationHelper::IsDOFMode, EDOFMode::CustomPlane)));
ConstraintsGroup.AddPropertyRow(DOFModeProperty.ToSharedRef());
}
}
////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE
#undef RowWidth_Customization