1848 lines
57 KiB
C++
1848 lines
57 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "BodyInstanceCustomization.h"
|
|
|
|
#include "Components/PrimitiveComponent.h"
|
|
#include "Components/ShapeComponent.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Containers/EnumAsByte.h"
|
|
#include "DeformableInterface.h"
|
|
#include "DestructibleInterface.h"
|
|
#include "DetailCategoryBuilder.h"
|
|
#include "DetailLayoutBuilder.h"
|
|
#include "DetailWidgetRow.h"
|
|
#include "Engine/CollisionProfile.h"
|
|
#include "Fonts/SlateFontInfo.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Text/TextLayout.h"
|
|
#include "Framework/Views/TableViewTypeTraits.h"
|
|
#include "IDetailChildrenBuilder.h"
|
|
#include "IDetailGroup.h"
|
|
#include "IDetailPropertyRow.h"
|
|
#include "IDocumentation.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
#include "Kismet2/ComponentEditorUtils.h"
|
|
#include "Layout/Margin.h"
|
|
#include "Math/NumericLimits.h"
|
|
#include "Math/UnrealMathSSE.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "Misc/Attribute.h"
|
|
#include "ObjectEditorUtils.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "PhysicsEngine/PhysicsSettings.h"
|
|
#include "PropertyEditorModule.h"
|
|
#include "PropertyHandle.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "SlateOptMacros.h"
|
|
#include "SlotBase.h"
|
|
#include "Templates/Casts.h"
|
|
#include "Types/SlateStructs.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/ReflectedTypeAccessors.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "Widgets/Input/SCheckBox.h"
|
|
#include "Widgets/Input/SNumericEntryBox.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Widgets/SToolTip.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
|
|
class SWidget;
|
|
class USceneComponent;
|
|
|
|
#define LOCTEXT_NAMESPACE "BodyInstanceCustomization"
|
|
|
|
#define RowWidth_Customization 50.f
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
FBodyInstanceCustomization::FBodyInstanceCustomization()
|
|
{
|
|
CollisionProfile = UCollisionProfile::Get();
|
|
|
|
RefreshCollisionProfiles();
|
|
}
|
|
|
|
UStaticMeshComponent* FBodyInstanceCustomization::GetDefaultCollisionProvider(const FBodyInstance* BI) const
|
|
{
|
|
if (!BI)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UPrimitiveComponent* OwnerComp = BI->OwnerComponent.Get();
|
|
if (!OwnerComp)
|
|
{
|
|
TWeakObjectPtr<UPrimitiveComponent> FoundComp = BodyInstanceToPrimComponent.FindRef(BI);
|
|
OwnerComp = FoundComp.Get();
|
|
}
|
|
|
|
UStaticMeshComponent* SMC = Cast<UStaticMeshComponent>(OwnerComp);
|
|
return SMC && SMC->SupportsDefaultCollision() ? SMC : nullptr;
|
|
}
|
|
|
|
bool FBodyInstanceCustomization::CanUseDefaultCollision() const
|
|
{
|
|
bool bResult = BodyInstances.Num() > 0;
|
|
for (const FBodyInstance* BI : BodyInstances)
|
|
{
|
|
bResult &= GetDefaultCollisionProvider(BI) != nullptr;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void FBodyInstanceCustomization::RefreshCollisionProfiles()
|
|
{
|
|
int32 NumProfiles = CollisionProfile->GetNumOfProfiles();
|
|
|
|
bool bCanUseDefaultCollision = CanUseDefaultCollision();
|
|
|
|
// first create profile combo list
|
|
CollisionProfileComboList.Empty(NumProfiles + (bCanUseDefaultCollision ? 2 : 1)); //If we can use default collision we'll add a "Default" option
|
|
|
|
// first one is default one
|
|
if(bCanUseDefaultCollision)
|
|
{
|
|
CollisionProfileComboList.Add(MakeShareable(new FString(TEXT("Default"))));
|
|
}
|
|
|
|
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())));
|
|
}
|
|
|
|
if(CollisionProfileComboBox.IsValid())
|
|
{
|
|
CollisionProfileComboBox->RefreshOptions();
|
|
}
|
|
}
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void FBodyInstanceCustomization::CustomizeHeader(TSharedRef<IPropertyHandle> StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
|
|
{
|
|
BodyInstanceHandle = StructPropertyHandle;
|
|
|
|
// copy all bodyinstances I'm accessing right now
|
|
TArray<void*> StructPtrs;
|
|
StructPropertyHandle->AccessRawData(StructPtrs);
|
|
check(StructPtrs.Num() != 0);
|
|
|
|
BodyInstances.AddZeroed(StructPtrs.Num());
|
|
for (auto Iter = StructPtrs.CreateIterator(); Iter; ++Iter)
|
|
{
|
|
check(*Iter);
|
|
BodyInstances[Iter.GetIndex()] = (FBodyInstance*)(*Iter);
|
|
}
|
|
|
|
TArray<UObject*> OwningObjects;
|
|
StructPropertyHandle->GetOuterObjects(OwningObjects);
|
|
|
|
PrimComponents.Empty(OwningObjects.Num());
|
|
for (UObject* Obj : OwningObjects)
|
|
{
|
|
if (UPrimitiveComponent* PrimComponent = Cast<UPrimitiveComponent>(Obj))
|
|
{
|
|
PrimComponents.Add(PrimComponent);
|
|
|
|
if (FBodyInstance* BI = PrimComponent->GetBodyInstance())
|
|
{
|
|
BodyInstanceToPrimComponent.Add(BI, PrimComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
// get all parent instances
|
|
TSharedPtr<IPropertyHandle> CollisionCategoryHandle = StructPropertyHandle->GetParentHandle();
|
|
TSharedPtr<IPropertyHandle> StaticMeshComponentHandle = CollisionCategoryHandle->GetParentHandle();
|
|
|
|
if (CollisionCategoryHandle.IsValid())
|
|
{
|
|
UseDefaultCollisionHandle = CollisionCategoryHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(UStaticMeshComponent, bUseDefaultCollision));
|
|
}
|
|
|
|
if (StaticMeshComponentHandle.IsValid())
|
|
{
|
|
StaticMeshHandle = StaticMeshComponentHandle->GetChildHandle(UStaticMeshComponent::GetMemberNameChecked_StaticMesh());
|
|
if (StaticMeshHandle.IsValid())
|
|
{
|
|
FSimpleDelegate OnStaticMeshChangedDelegate = FSimpleDelegate::CreateSP(this, &FBodyInstanceCustomization::RefreshCollisionProfiles);
|
|
StaticMeshHandle->SetOnPropertyValueChanged(OnStaticMeshChangedDelegate);
|
|
}
|
|
}
|
|
|
|
TSharedPtr<IPropertyHandle> SimulatePhysicsPropertyHandle = BodyInstanceHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bSimulatePhysics));
|
|
if (SimulatePhysicsPropertyHandle.IsValid())
|
|
{
|
|
FSimpleDelegate OnSimulatePhysicsChangedDelegate = FSimpleDelegate::CreateSP(this, &FBodyInstanceCustomization::OnSimulatePhysicsChanged);
|
|
SimulatePhysicsPropertyHandle->SetOnPropertyValueChanged(OnSimulatePhysicsChangedDelegate);
|
|
}
|
|
|
|
// The selection of BodyInstances modifies the collision profiles available, refresh them now :
|
|
RefreshCollisionProfiles();
|
|
|
|
CollisionProfileNameHandle = StructPropertyHandle->GetChildHandle(TEXT("CollisionProfileName"));
|
|
check(CollisionProfileNameHandle.IsValid());
|
|
|
|
CollisionEnabledHandle = StructPropertyHandle->GetChildHandle(TEXT("CollisionEnabled"));
|
|
check(CollisionEnabledHandle.IsValid());
|
|
|
|
ObjectTypeHandle = StructPropertyHandle->GetChildHandle(TEXT("ObjectType"));
|
|
check(ObjectTypeHandle.IsValid());
|
|
|
|
CollisionResponsesHandle = StructPropertyHandle->GetChildHandle(TEXT("CollisionResponses"));
|
|
check (CollisionResponsesHandle.IsValid());
|
|
|
|
// Expensive to validate
|
|
// Presets update the DetailView on event, cannot be changed when custom from scripts so is safe
|
|
// to skip validation each from for.
|
|
CollisionResponsesHandle->SetIgnoreValidation(true);
|
|
|
|
// need to find profile name
|
|
FName ProfileName;
|
|
TSharedPtr< FString > DisplayName = CollisionProfileComboList[0];
|
|
bool bDisplayAdvancedCollisionSettings = true;
|
|
|
|
// if I have valid profile name
|
|
if (!AreAllCollisionUsingDefault() && 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"));
|
|
|
|
HeaderRow
|
|
.OverrideResetToDefault(FResetToDefaultOverride::Create(
|
|
TAttribute<bool>::Create([this]() { return ShouldShowResetToDefaultProfile(); }),
|
|
FSimpleDelegate::CreateLambda([this]() { SetToDefaultProfile(); })
|
|
))
|
|
.NameContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("CollisionPresetsLabel", "Collision Presets"))
|
|
.ToolTip(ProfileTooltip)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
]
|
|
]
|
|
.ValueContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.IsEnabled(this, &FBodyInstanceCustomization::IsCollisionEnabled)
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew(CollisionProfileComboBox, SComboBox< TSharedPtr<FString> >)
|
|
.OptionsSource(&CollisionProfileComboList)
|
|
.OnGenerateWidget(this, &FBodyInstanceCustomization::MakeCollisionProfileComboWidget)
|
|
.OnSelectionChanged(this, &FBodyInstanceCustomization::OnCollisionProfileChanged)
|
|
.OnComboBoxOpening(this, &FBodyInstanceCustomization::OnCollisionProfileComboOpening)
|
|
.InitiallySelectedItem(DisplayName)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &FBodyInstanceCustomization::GetCollisionProfileComboBoxContent)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ToolTipText(this, &FBodyInstanceCustomization::GetCollisionProfileComboBoxToolTip)
|
|
]
|
|
]
|
|
]
|
|
.ShouldAutoExpand(bDisplayAdvancedCollisionSettings);
|
|
}
|
|
|
|
void FBodyInstanceCustomization::CustomizeChildren( TSharedRef<IPropertyHandle> StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils )
|
|
{
|
|
IDetailCategoryBuilder& CatBuilder = StructBuilder.GetParentCategory();
|
|
DetailBuilder = &CatBuilder.GetParentLayout();
|
|
|
|
// now create custom set up
|
|
CreateCustomCollisionSetup(StructPropertyHandle, StructBuilder);
|
|
}
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
int32 FBodyInstanceCustomization::InitializeObjectTypeComboList()
|
|
{
|
|
ObjectTypeComboList.Empty();
|
|
ObjectTypeValues.Empty();
|
|
|
|
UEnum * Enum = StaticEnum<ECollisionChannel>();
|
|
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
|
|
const FString& QueryTypeMetaData = Enum->GetMetaData(*QueryType, EnumIndex);
|
|
// if query type is object, we allow it to be on movement channel
|
|
if (QueryTypeMetaData.Len() == 0 || QueryTypeMetaData[0] == '0')
|
|
{
|
|
const FString& KeyNameMetaData = Enum->GetMetaData(*KeyName, EnumIndex);
|
|
|
|
if ( KeyNameMetaData.Len() > 0 )
|
|
{
|
|
int32 NewIndex = ObjectTypeComboList.Add( MakeShareable( new FString (KeyNameMetaData) ));
|
|
// @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;
|
|
}
|
|
|
|
int32 FBodyInstanceCustomization::GetNumberOfSpecialProfiles() const
|
|
{
|
|
return CanUseDefaultCollision() ? 2 : 1;
|
|
}
|
|
|
|
int32 FBodyInstanceCustomization::GetCustomIndex() const
|
|
{
|
|
return CanUseDefaultCollision() ? 1 : 0;
|
|
}
|
|
|
|
int32 FBodyInstanceCustomization::GetDefaultIndex() const
|
|
{
|
|
ensure(CanUseDefaultCollision());
|
|
return 0;
|
|
}
|
|
|
|
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 + GetNumberOfSpecialProfiles() == CollisionProfileComboList.Num() )
|
|
{
|
|
for(int32 ProfileId = 0; ProfileId < NumProfiles; ++ProfileId)
|
|
{
|
|
if(*CollisionProfileComboList[ProfileId+GetNumberOfSpecialProfiles()].Get() == ProfileNameString)
|
|
{
|
|
return CollisionProfileComboList[ProfileId+GetNumberOfSpecialProfiles()];
|
|
}
|
|
}
|
|
}
|
|
|
|
return CollisionProfileComboList[GetCustomIndex()];
|
|
}
|
|
|
|
// 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 = StaticEnum<ECollisionChannel>();
|
|
// 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<IPropertyHandle> StructPropertyHandle, IDetailChildrenBuilder& StructBuilder)
|
|
{
|
|
UpdateValidCollisionChannels();
|
|
|
|
if (ValidCollisionChannels.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 TotalNumChildren = ValidCollisionChannels.Num();
|
|
TAttribute<bool> CollisionEnabled(this, &FBodyInstanceCustomization::IsCollisionEnabled );
|
|
TAttribute<bool> CustomCollisionEnabled( this, &FBodyInstanceCustomization::ShouldEnableCustomCollisionSetup );
|
|
TAttribute<EVisibility> CustomCollisionVisibility(this, &FBodyInstanceCustomization::ShouldShowCustomCollisionSetup);
|
|
|
|
// initialize ObjectTypeComboList
|
|
// we only display things that has "DisplayName"
|
|
int32 IndexSelected = InitializeObjectTypeComboList();
|
|
StructBuilder.AddProperty(CollisionEnabledHandle.ToSharedRef())
|
|
.IsEnabled(CustomCollisionEnabled)
|
|
.Visibility(CustomCollisionVisibility);
|
|
|
|
if (!StructPropertyHandle->GetProperty()->GetBoolMetaData(TEXT("HideObjectType")))
|
|
{
|
|
StructBuilder.AddCustomRow(LOCTEXT("ObjectType", "Object Type"))
|
|
.Visibility(CustomCollisionVisibility)
|
|
.IsEnabled(CustomCollisionEnabled)
|
|
.NameContent()
|
|
[
|
|
ObjectTypeHandle->CreatePropertyNameWidget()
|
|
]
|
|
.ValueContent()
|
|
[
|
|
SAssignNew(ObjectTypeComboBox, SComboBox<TSharedPtr<FString>>)
|
|
.OptionsSource(&ObjectTypeComboList)
|
|
.OnGenerateWidget(this, &FBodyInstanceCustomization::MakeObjectTypeComboWidget)
|
|
.OnSelectionChanged(this, &FBodyInstanceCustomization::OnObjectTypeChanged)
|
|
.InitiallySelectedItem(ObjectTypeComboList[IndexSelected])
|
|
.ContentPadding(2.f)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &FBodyInstanceCustomization::GetObjectTypeComboBoxContent)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
]
|
|
];
|
|
}
|
|
|
|
// Add Title
|
|
StructBuilder.AddCustomRow(LOCTEXT("CustomCollision", "Custom Collision"))
|
|
.IsEnabled(CustomCollisionEnabled)
|
|
.Visibility(CustomCollisionVisibility)
|
|
.ValueContent()
|
|
.MaxDesiredWidth(0.f)
|
|
.MinDesiredWidth(0.f)
|
|
[
|
|
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
|
|
StructBuilder.AddCustomRow(LOCTEXT("All", "All"))
|
|
.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
|
|
StructBuilder.AddCustomRow(LOCTEXT("TraceResponses", "Trace Responses"))
|
|
.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;
|
|
|
|
StructBuilder.AddCustomRow(LOCTEXT("CollisionChannel", "Collision Channel"))
|
|
.IsEnabled(CustomCollisionEnabled)
|
|
.Visibility(CustomCollisionVisibility)
|
|
.OverrideResetToDefault(FResetToDefaultOverride::Create(
|
|
TAttribute<bool>::Create([this, Index]() { return ShouldShowResetToDefaultResponse(Index); }),
|
|
FSimpleDelegate::CreateLambda([this, Index]() { SetToDefaultResponse(Index); })
|
|
))
|
|
.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 )
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
// Add Title
|
|
StructBuilder.AddCustomRow(LOCTEXT("ObjectResponses", "Object Responses"))
|
|
.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;
|
|
|
|
StructBuilder.AddCustomRow(LOCTEXT("CollisionChannel", "Collision Channel"))
|
|
.IsEnabled(CustomCollisionEnabled)
|
|
.Visibility(CustomCollisionVisibility)
|
|
.OverrideResetToDefault(FResetToDefaultOverride::Create(
|
|
TAttribute<bool>::Create([this, Index]() { return ShouldShowResetToDefaultResponse(Index); }),
|
|
FSimpleDelegate::CreateLambda([this, Index]() { SetToDefaultResponse(Index); })
|
|
))
|
|
.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 )
|
|
]
|
|
];
|
|
}
|
|
}
|
|
}
|
|
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();
|
|
ECollisionChannel 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((uint8)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());
|
|
}
|
|
|
|
void FBodyInstanceCustomization::OnSimulatePhysicsChanged()
|
|
{
|
|
UpdateCollisionProfile();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// 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()
|
|
{
|
|
if(AreAllCollisionUsingDefault())
|
|
{
|
|
TSharedPtr<FString> ComboStringPtr = CollisionProfileComboList[GetDefaultIndex()];
|
|
if (ComboStringPtr.IsValid())
|
|
{
|
|
CollisionProfileComboBox->SetSelectedItem(ComboStringPtr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
FName ProfileName;
|
|
if (CollisionProfileNameHandle->GetValue(ProfileName) != FPropertyAccess::Result::MultipleValues)
|
|
{
|
|
TSharedPtr<FString> ComboStringPtr = GetProfileString(ProfileName);
|
|
if( ComboStringPtr.IsValid() )
|
|
{
|
|
CollisionProfileComboBox->SetSelectedItem(ComboStringPtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBodyInstanceCustomization::MarkAllBodiesDefaultCollision(bool bUseDefaultCollision)
|
|
{
|
|
if(PrimComponents.Num() && UseDefaultCollisionHandle.IsValid()) //If we have prim components we might be coming from bp editor which needs to propagate all instances
|
|
{
|
|
for(UPrimitiveComponent* PrimComp : PrimComponents)
|
|
{
|
|
if(UStaticMeshComponent* SMC = Cast<UStaticMeshComponent>(PrimComp))
|
|
{
|
|
const bool bOldDefault = SMC->bUseDefaultCollision;
|
|
const bool bNewDefault = bUseDefaultCollision;
|
|
|
|
TSet<USceneComponent*> UpdatedInstances;
|
|
FComponentEditorUtils::PropagateDefaultValueChange(SMC, UseDefaultCollisionHandle->GetProperty(), bOldDefault, bNewDefault, UpdatedInstances);
|
|
|
|
SMC->bUseDefaultCollision = bNewDefault;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const FBodyInstance* BI : BodyInstances)
|
|
{
|
|
if (UStaticMeshComponent* SMC = GetDefaultCollisionProvider(BI))
|
|
{
|
|
SMC->bUseDefaultCollision = bUseDefaultCollision;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBodyInstanceCustomization::OnCollisionProfileChanged(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();
|
|
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
|
|
MarkAllBodiesDefaultCollision(false);
|
|
ensure ( CollisionProfileNameHandle->SetValue(NewValue) == FPropertyAccess::Result::Success );
|
|
UpdateCollisionProfile();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(CanUseDefaultCollision())
|
|
{
|
|
if(NewSelection == CollisionProfileComboList[GetDefaultIndex()])
|
|
{
|
|
CollisionResponsesHandle->NotifyPreChange();
|
|
MarkAllBodiesDefaultCollision(true);
|
|
CollisionResponsesHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if( NewSelection == CollisionProfileComboList[GetCustomIndex()])
|
|
{
|
|
// Force expansion when the user chooses the selected item
|
|
if (IDetailPropertyRow* BodyInstanceProperty = DetailBuilder->EditPropertyFromRoot(BodyInstanceHandle))
|
|
{
|
|
BodyInstanceProperty->ShouldAutoExpand(true);
|
|
}
|
|
}
|
|
|
|
// if none of them found, clear it
|
|
FName Name=UCollisionProfile::CustomCollisionProfileName;
|
|
MarkAllBodiesDefaultCollision(false);
|
|
ensure(CollisionProfileNameHandle->SetValue(Name) == FPropertyAccess::Result::Success);
|
|
}
|
|
}
|
|
|
|
void FBodyInstanceCustomization::UpdateCollisionProfile()
|
|
{
|
|
FName ProfileName;
|
|
|
|
// if I have valid profile name
|
|
if (!AreAllCollisionUsingDefault() && CollisionProfileNameHandle->GetValue(ProfileName) == FPropertyAccess::Result::Success && FBodyInstance::IsValidCollisionProfileName(ProfileName) )
|
|
{
|
|
int32 NumProfiles = CollisionProfile->GetNumOfProfiles();
|
|
const int32 NumSpecialProfiles = GetNumberOfSpecialProfiles();
|
|
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
|
|
CollisionProfileComboBox.Get()->SetSelectedItem(CollisionProfileComboList[ProfileId+NumSpecialProfiles]);
|
|
if (ObjectTypeComboBox.IsValid())
|
|
{
|
|
for (auto Iter = ObjectTypeValues.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
if (*Iter == CurProfile->ObjectType)
|
|
{
|
|
ObjectTypeComboBox.Get()->SetSelectedItem(ObjectTypeComboList[Iter.GetIndex()]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
CollisionProfileComboBox.Get()->SetSelectedItem(CollisionProfileComboList[AreAllCollisionUsingDefault() ? GetDefaultIndex() : GetCustomIndex()]);
|
|
}
|
|
|
|
void FBodyInstanceCustomization::SetToDefaultProfile()
|
|
{
|
|
// trigger transaction before UpdateCollisionProfile
|
|
const FScopedTransaction Transaction( LOCTEXT( "ResetCollisionProfile", "Reset Collision Profile" ) );
|
|
MarkAllBodiesDefaultCollision(false);
|
|
CollisionProfileNameHandle.Get()->ResetToDefault();
|
|
UpdateCollisionProfile();
|
|
}
|
|
|
|
bool FBodyInstanceCustomization::ShouldShowResetToDefaultProfile() const
|
|
{
|
|
return CollisionProfileNameHandle.Get()->DiffersFromDefault();
|
|
}
|
|
|
|
void FBodyInstanceCustomization::SetToDefaultResponse(int32 Index)
|
|
{
|
|
if (ValidCollisionChannels.IsValidIndex(Index))
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT( "ResetCollisionResponse", "Reset Collision Response" ) );
|
|
const ECollisionResponse DefaultResponse = FCollisionResponseContainer::GetDefaultResponseContainer().GetResponse(ValidCollisionChannels[Index].CollisionChannel);
|
|
|
|
SetResponse(Index, DefaultResponse);
|
|
}
|
|
}
|
|
|
|
bool FBodyInstanceCustomization::ShouldShowResetToDefaultResponse(int32 Index) const
|
|
{
|
|
|
|
if (ValidCollisionChannels.IsValidIndex(Index))
|
|
{
|
|
const ECollisionResponse DefaultResponse = FCollisionResponseContainer::GetDefaultResponseContainer().GetResponse(ValidCollisionChannels[Index].CollisionChannel);
|
|
|
|
if (IsCollisionChannelChecked(Index, DefaultResponse) != ECheckBoxState::Checked)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FBodyInstanceCustomization::AreAllCollisionUsingDefault() const
|
|
{
|
|
bool bAllUsingDefault = BodyInstances.Num() > 0;
|
|
for(const FBodyInstance* BI : BodyInstances)
|
|
{
|
|
if(UStaticMeshComponent* SMC = GetDefaultCollisionProvider(BI))
|
|
{
|
|
bAllUsingDefault &= SMC->bUseDefaultCollision;
|
|
}
|
|
else
|
|
{
|
|
bAllUsingDefault = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bAllUsingDefault;
|
|
}
|
|
|
|
bool FBodyInstanceCustomization::IsCollisionEnabled() const
|
|
{
|
|
bool bEnabled = false;
|
|
if(BodyInstanceHandle.IsValid())
|
|
{
|
|
bEnabled = BodyInstanceHandle->IsEditable() && FSlateApplication::Get().GetNormalExecutionAttribute().Get();
|
|
}
|
|
|
|
return bEnabled;
|
|
}
|
|
|
|
bool FBodyInstanceCustomization::ShouldEnableCustomCollisionSetup() const
|
|
{
|
|
FName ProfileName;
|
|
if (!AreAllCollisionUsingDefault() && CollisionProfileNameHandle->GetValue(ProfileName) == FPropertyAccess::Result::Success && FBodyInstance::IsValidCollisionProfileName(ProfileName) == false)
|
|
{
|
|
return IsCollisionEnabled();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
EVisibility FBodyInstanceCustomization::ShouldShowCustomCollisionSetup() const
|
|
{
|
|
return AreAllCollisionUsingDefault() ? EVisibility::Hidden : EVisibility::Visible;
|
|
}
|
|
|
|
FText FBodyInstanceCustomization::GetCollisionProfileComboBoxContent() const
|
|
{
|
|
bool bAllUseDefaultCollision = BodyInstances.Num() > 0;
|
|
bool bSomeUseDefaultCollision = false;
|
|
|
|
for(const FBodyInstance* BI : BodyInstances)
|
|
{
|
|
if(UStaticMeshComponent* SMC = GetDefaultCollisionProvider(BI))
|
|
{
|
|
bAllUseDefaultCollision &= SMC->bUseDefaultCollision;
|
|
bSomeUseDefaultCollision |= SMC->bUseDefaultCollision;
|
|
}
|
|
else
|
|
{
|
|
bAllUseDefaultCollision = false;
|
|
}
|
|
}
|
|
|
|
if(bAllUseDefaultCollision)
|
|
{
|
|
return FText::FromString(*CollisionProfileComboList[GetDefaultIndex()]);
|
|
}
|
|
|
|
FName ProfileName;
|
|
if (bSomeUseDefaultCollision || CollisionProfileNameHandle->GetValue(ProfileName) == FPropertyAccess::Result::MultipleValues)
|
|
{
|
|
return LOCTEXT("MultipleValues", "Multiple Values");
|
|
}
|
|
|
|
return FText::FromString(*GetProfileString(ProfileName).Get());
|
|
}
|
|
|
|
FText FBodyInstanceCustomization::GetCollisionProfileComboBoxToolTip() const
|
|
{
|
|
if(AreAllCollisionUsingDefault())
|
|
{
|
|
return LOCTEXT("DefaultCollision", "Default collision preset specified in the StaticMesh asset");
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
struct FUpdateCollisionResponseHelper
|
|
{
|
|
FUpdateCollisionResponseHelper(UPrimitiveComponent* InPrimComp, TSharedPtr<IPropertyHandle> InCollisionResponseHandle)
|
|
: PrimComp(InPrimComp)
|
|
, CollisionResponsesHandle(InCollisionResponseHandle)
|
|
{
|
|
OldCollision = PrimComp->BodyInstance.GetCollisionResponse();
|
|
}
|
|
|
|
~FUpdateCollisionResponseHelper()
|
|
{
|
|
if(CollisionResponsesHandle.IsValid())
|
|
{
|
|
const SIZE_T PropertyOffset = (SIZE_T)&((UPrimitiveComponent*)0)->BodyInstance.CollisionResponses;
|
|
check(PropertyOffset <= MAX_int32);
|
|
|
|
TSet<USceneComponent*> UpdatedInstances;
|
|
FComponentEditorUtils::PropagateDefaultValueChange(PrimComp, CollisionResponsesHandle->GetProperty(), OldCollision, PrimComp->BodyInstance.GetCollisionResponse(), UpdatedInstances, PropertyOffset);
|
|
}
|
|
}
|
|
|
|
UPrimitiveComponent* PrimComp;
|
|
TSharedPtr<IPropertyHandle> CollisionResponsesHandle;
|
|
FCollisionResponse OldCollision;
|
|
};
|
|
|
|
void FBodyInstanceCustomization::SetResponse(int32 ValidIndex, ECollisionResponse InCollisionResponse)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT( "ChangeIndividualChannel", "Change Individual Channel" ) );
|
|
|
|
CollisionResponsesHandle->NotifyPreChange();
|
|
|
|
if(PrimComponents.Num()) //If we have owning prim components we may be in blueprint editor which means we have to propagate to instances.
|
|
{
|
|
for (UPrimitiveComponent* PrimComp : PrimComponents)
|
|
{
|
|
FUpdateCollisionResponseHelper UpdateCollisionResponseHelper(PrimComp, CollisionResponsesHandle);
|
|
FBodyInstance& BodyInstance = PrimComp->BodyInstance;
|
|
BodyInstance.CollisionResponses.SetResponse(ValidCollisionChannels[ValidIndex].CollisionChannel, InCollisionResponse);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FBodyInstance* BodyInstance : BodyInstances)
|
|
{
|
|
BodyInstance->CollisionResponses.SetResponse(ValidCollisionChannels[ValidIndex].CollisionChannel, InCollisionResponse);
|
|
}
|
|
}
|
|
|
|
CollisionResponsesHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
|
|
}
|
|
|
|
ECheckBoxState FBodyInstanceCustomization::IsCollisionChannelChecked( int32 ValidIndex, ECollisionResponse InCollisionResponse) const
|
|
{
|
|
TArray<ECollisionResponse> 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
|
|
uint32 TotalNumChildren = ValidCollisionChannels.Num();
|
|
|
|
if(TotalNumChildren)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("Collision", "Collision Channel Changes"));
|
|
|
|
CollisionResponsesHandle->NotifyPreChange();
|
|
|
|
|
|
// iterate through bodyinstance and fix it
|
|
if(PrimComponents.Num()) //If we have owning prim components we may be in blueprint editor which means we have to propagate to instances.
|
|
{
|
|
|
|
for (UPrimitiveComponent* PrimComponent : PrimComponents)
|
|
{
|
|
FUpdateCollisionResponseHelper UpdateCollisionResponseHelper(PrimComponent, CollisionResponsesHandle);
|
|
|
|
FBodyInstance& BodyInstance = PrimComponent->BodyInstance;
|
|
// only go through valid channels
|
|
for (uint32 Index = 0; Index < TotalNumChildren; ++Index)
|
|
{
|
|
ECollisionChannel Channel = ValidCollisionChannels[Index].CollisionChannel;
|
|
ECollisionResponse Response = ResponseContainer.GetResponse(Channel);
|
|
|
|
BodyInstance.CollisionResponses.SetResponse(Channel, Response);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FBodyInstance* BodyInstance : BodyInstances)
|
|
{
|
|
// only go through valid channels
|
|
for (uint32 Index = 0; Index < TotalNumChildren; ++Index)
|
|
{
|
|
ECollisionChannel Channel = ValidCollisionChannels[Index].CollisionChannel;
|
|
ECollisionResponse Response = ResponseContainer.GetResponse(Channel);
|
|
|
|
BodyInstance->CollisionResponses.SetResponse(Channel, Response);
|
|
}
|
|
}
|
|
}
|
|
|
|
CollisionResponsesHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
|
|
}
|
|
|
|
}
|
|
|
|
FBodyInstanceCustomizationHelper::FBodyInstanceCustomizationHelper(const TArray<TWeakObjectPtr<UObject>>& InObjectsCustomized)
|
|
: ObjectsCustomized(InObjectsCustomized)
|
|
{
|
|
}
|
|
|
|
void FBodyInstanceCustomizationHelper::UpdateFilters()
|
|
{
|
|
bDisplayMass = true;
|
|
bDisplayConstraints = true;
|
|
bDisplayEnablePhysics = true;
|
|
bDisplayAsyncScene = true;
|
|
bDisplayLinearDamping = true;
|
|
bDisplayAngularDamping = true;
|
|
bDisplayEnableGravity = true;
|
|
bDisplayInertiaConditioning = true;
|
|
bDisplayInitialOverlapDepenetration = true;
|
|
bDisplayWalkableSlopeOverride = true;
|
|
bDisplayAutoWeld = true;
|
|
bDisplayStartAwake = true;
|
|
bDisplayCOMNudge = true;
|
|
bDisplayMassScale = true;
|
|
bDisplayMaxAngularVelocity = true;
|
|
|
|
for (int32 i = 0; i < ObjectsCustomized.Num(); ++i)
|
|
{
|
|
if (ObjectsCustomized[i].IsValid())
|
|
{
|
|
if(Cast<IDestructibleInterface>(ObjectsCustomized[i].Get()))
|
|
{
|
|
bDisplayMass = false;
|
|
bDisplayConstraints = false;
|
|
}
|
|
else if (Cast<IDeformableInterface>(ObjectsCustomized[i].Get()))
|
|
{
|
|
bDisplayMass = false;
|
|
bDisplayConstraints = false;
|
|
bDisplayEnablePhysics = false;
|
|
bDisplayAsyncScene = false;
|
|
bDisplayLinearDamping = false;
|
|
bDisplayAngularDamping = false;
|
|
bDisplayEnableGravity = false;
|
|
bDisplayInertiaConditioning = false;
|
|
bDisplayInitialOverlapDepenetration = false;
|
|
bDisplayWalkableSlopeOverride = false;
|
|
bDisplayAutoWeld = false;
|
|
bDisplayStartAwake = false;
|
|
bDisplayCOMNudge = false;
|
|
bDisplayMassScale = false;
|
|
bDisplayMaxAngularVelocity = false;
|
|
}
|
|
else
|
|
{
|
|
if(ObjectsCustomized[i]->IsA(UBodySetup::StaticClass()))
|
|
{
|
|
bDisplayEnablePhysics = false;
|
|
bDisplayConstraints = false;
|
|
}
|
|
}
|
|
|
|
if(ObjectsCustomized[i]->IsA(USkeletalMeshComponent::StaticClass()))
|
|
{
|
|
bDisplayMass = false;
|
|
bDisplayAsyncScene = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FBodyInstanceCustomizationHelper::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder, TSharedRef<IPropertyHandle> BodyInstanceHandler)
|
|
{
|
|
CustomizeDetails(DetailBuilder, BodyInstanceHandler, TFunction<void(TSharedRef<IPropertyHandle>)>());
|
|
}
|
|
|
|
void FBodyInstanceCustomizationHelper::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder, TSharedRef<IPropertyHandle> BodyInstanceHandler, TFunction<void(TSharedRef<IPropertyHandle>)> CustomizeCoMNudge)
|
|
{
|
|
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);
|
|
|
|
|
|
auto EnablePhysicsProperty = [BodyInstanceHandler, &PhysicsCategory](FName PropertyName, bool bEnable)
|
|
{
|
|
TSharedRef<IPropertyHandle> Property = BodyInstanceHandler->GetChildHandle(PropertyName).ToSharedRef();
|
|
if (bEnable)
|
|
{
|
|
PhysicsCategory.AddProperty(Property);
|
|
}
|
|
else
|
|
{
|
|
Property->MarkHiddenByCustomization();
|
|
}
|
|
return Property;
|
|
};
|
|
EnablePhysicsProperty(GET_MEMBER_NAME_CHECKED(FBodyInstance, LinearDamping), bDisplayLinearDamping);
|
|
EnablePhysicsProperty(GET_MEMBER_NAME_CHECKED(FBodyInstance, AngularDamping), bDisplayAngularDamping);
|
|
EnablePhysicsProperty(GET_MEMBER_NAME_CHECKED(FBodyInstance, bEnableGravity), bDisplayEnableGravity);
|
|
EnablePhysicsProperty(GET_MEMBER_NAME_CHECKED(FBodyInstance, bInertiaConditioning), bDisplayInertiaConditioning);
|
|
EnablePhysicsProperty(GET_MEMBER_NAME_CHECKED(FBodyInstance, WalkableSlopeOverride), bDisplayWalkableSlopeOverride);
|
|
|
|
AddBodyConstraint(PhysicsCategory, BodyInstanceHandler);
|
|
|
|
//ADVANCED
|
|
PhysicsCategory.AddProperty(BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, bAutoWeld)))
|
|
.Visibility(TAttribute<EVisibility>(this, &FBodyInstanceCustomizationHelper::IsAutoWeldVisible));
|
|
|
|
EnablePhysicsProperty(GET_MEMBER_NAME_CHECKED(FBodyInstance, bStartAwake), bDisplayStartAwake);
|
|
|
|
if (CustomizeCoMNudge && bDisplayCOMNudge)
|
|
{
|
|
CustomizeCoMNudge(BodyInstanceHandler);
|
|
}
|
|
else
|
|
{
|
|
EnablePhysicsProperty(GET_MEMBER_NAME_CHECKED(FBodyInstance, COMNudge), bDisplayCOMNudge);
|
|
}
|
|
|
|
EnablePhysicsProperty(GET_MEMBER_NAME_CHECKED(FBodyInstance, MassScale), bDisplayMassScale);
|
|
|
|
if (bDisplayMaxAngularVelocity)
|
|
{
|
|
AddMaxAngularVelocity(PhysicsCategory, BodyInstanceHandler);
|
|
}
|
|
|
|
//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;
|
|
}
|
|
|
|
|
|
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 DefaultMaxAngularVelocity = UPhysicsSettings::Get()->MaxAngularVelocity;
|
|
float MaxAngularVelocity = DefaultMaxAngularVelocity;
|
|
bool bFoundComponent = false;
|
|
|
|
for (auto ObjectIt = ObjectsCustomized.CreateConstIterator(); ObjectIt; ++ObjectIt)
|
|
{
|
|
if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UPrimitiveComponent::StaticClass()))
|
|
{
|
|
Comp = Cast<UPrimitiveComponent>(ObjectIt->Get());
|
|
|
|
const float CompMaxAngularVelocity = Comp->BodyInstance.bOverrideMaxAngularVelocity ?
|
|
Comp->BodyInstance.MaxAngularVelocity :
|
|
DefaultMaxAngularVelocity;
|
|
|
|
if (!bFoundComponent)
|
|
{
|
|
bFoundComponent = true;
|
|
MaxAngularVelocity = CompMaxAngularVelocity;
|
|
}
|
|
else if (MaxAngularVelocity != CompMaxAngularVelocity)
|
|
{
|
|
return TOptional<float>();
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void FBodyInstanceCustomizationHelper::OnSetBodyMass(float BodyMass, ETextCommit::Type Commit)
|
|
{
|
|
MassInKgOverrideHandle->SetValue(BodyMass);
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
float NewMass = 0.f;
|
|
if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UPrimitiveComponent::StaticClass()))
|
|
{
|
|
Comp = Cast<UPrimitiveComponent>(ObjectIt->Get());
|
|
NewMass = Comp->CalculateMass();
|
|
}
|
|
else if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UBodySetup::StaticClass()))
|
|
{
|
|
BS = Cast<UBodySetup>(ObjectIt->Get());
|
|
|
|
NewMass = BS->CalculateMass();
|
|
}
|
|
|
|
if (Mass == 0.0f || FMath::Abs(Mass - NewMass) < SMALL_NUMBER)
|
|
{
|
|
Mass = NewMass;
|
|
}
|
|
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)
|
|
{
|
|
MassInKgOverrideHandle = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, MassInKgOverride)).ToSharedRef();
|
|
|
|
if (bDisplayMass)
|
|
{
|
|
PhysicsCategory.AddProperty(MassInKgOverrideHandle).CustomWidget()
|
|
.NameContent()
|
|
[
|
|
MassInKgOverrideHandle->CreatePropertyNameWidget()
|
|
]
|
|
.ValueContent()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SNumericEntryBox<float>)
|
|
.IsEnabled(this, &FBodyInstanceCustomizationHelper::IsBodyMassEnabled)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.Value(this, &FBodyInstanceCustomizationHelper::OnGetBodyMass)
|
|
.OnValueCommitted(this, &FBodyInstanceCustomizationHelper::OnSetBodyMass)
|
|
];
|
|
}
|
|
else
|
|
{
|
|
MassInKgOverrideHandle->MarkHiddenByCustomization();
|
|
}
|
|
}
|
|
|
|
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()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SNumericEntryBox<float>)
|
|
.IsEnabled(false)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.Value(this, &FBodyInstanceCustomizationHelper::OnGetBodyMaxAngularVelocity)
|
|
.Visibility(this, &FBodyInstanceCustomizationHelper::IsMaxAngularVelocityVisible, false)
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
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())
|
|
.Justification(ETextJustify::Right)
|
|
]
|
|
.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
|