You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
ba3ad4c356
#lockdown Nick.Penwarden
==========================
MAJOR FEATURES + CHANGES
==========================
Change 2839897 on 2016/01/22 by Ori.Cohen
Allow static mesh editor to specify a default collision profile.
#rb Lina.Halper
#UE-2836
Change 2840489 on 2016/01/22 by Ori.Cohen
Fix collision customization so that it respects const editing property
#rb Marc.Audy
Change 2840528 on 2016/01/22 by Ori.Cohen
Fix compile error and actually get value from attribute
Change 2840672 on 2016/01/22 by Zak.Middleton
#ue4 - Include data from USkinnedMeshComponent in USkeletalMeshComponent::GetResourceSize().
#rb Michael.Noland
Change 2841314 on 2016/01/24 by Marc.Audy
Fix depressingly frequent misspellings of 'suppress'
Change 2841323 on 2016/01/24 by Marc.Audy
Reserve worst case memory for TSet Intersect, Union, and Difference to avoid memory allocations during iteration
Ensure that TSet Intersect considers the least number of elements possible
Early out from TSet Contains if Other is larger than this
Clarify comment on TSet Difference
#rb Steve.Robb
Change 2841380 on 2016/01/24 by Aaron.McLeran
UE-25586 Audio assets not correctly reporting resource memory usage
Tested on PC/PS4 and with Editor builds. Memory reporting is working for all cases now.
Change 2841385 on 2016/01/24 by Aaron.McLeran
UE-21210 Adding subtitle priority to USoundWave
Change 2841386 on 2016/01/24 by Marc.Audy
Return null for GameNetDriver if World is null instead of crashing
Change 2841409 on 2016/01/24 by Aaron.McLeran
UE-25514 Removing load for default objects for every sound wave
Change 2841858 on 2016/01/25 by Ori.Cohen
Make sure that PIE face index results are consistent with runtime
#rb Benn.Gallagher
Change 2841977 on 2016/01/25 by Ori.Cohen
Fix object type customization so that it's only enabled when custom is selected. (Accidently broke this in recent change)
Change 2841982 on 2016/01/25 by Marc.Audy
Minor optimization by avoiding recreating FNames repeatedly in constructor
Change 2842169 on 2016/01/25 by Benn.Gallagher
Fixes to animBP compiler and instance to store and double buffer internal machine state weights on the instance. So they can be queried cross-machine without issue.
#rb Lina.Halper
Change 2842390 on 2016/01/25 by Ori.Cohen
Fix in world editing of BodyInstance not working.
No longer serializing Scale3D as this is allways initialized in InitBody.
No longer overwriting MassInKg and renamed to to MassInKgOverride which better reflects what this variable does.
#JIRA UE-25518
#rb Lina.Halper
Change 2843579 on 2016/01/26 by Marc.Audy
Only update replication when it actually changes
Don't check calling SetIsReplicated if the class cannot replicate, instead output an error message
Fix spelling in comment
#rb Ori.Cohen
Change 2843627 on 2016/01/26 by Marc.Audy
Add \\ as a default console key for Italian keyboard layouts
#jira UE-25198
#rb James.Golding
Change 2843628 on 2016/01/26 by Marc.Audy
Don't reconstruct FName on each call to GetHitResultAtScreenPosition
#rb James.Golding
Change 2843671 on 2016/01/26 by Martin.Wilson
Fix incorrect bone transforms being pushed to the renderer during SetSkeletalMesh. This presented as motion blur artifacts in editor
#rb Thomas.Sarkanen
Change 2843768 on 2016/01/26 by Marc.Audy
Inline Get Component functions in TriggerBase
Change 2844003 on 2016/01/26 by Zak.Middleton
#ue4 - Fix FMath::Fmod(X, Y) sometimes returning small negative values for positive X and Y due to float imprecision. Added tests to math tests at startup to check this, and also to better handle results close to Y. Wrap the ensure on Y=0 within a conditional so a breakpoint can be used during debugging (to distinguish between zero and very small input).
#codereview Laurent.Delayen
Change 2844005 on 2016/01/26 by Zak.Middleton
#ue4 - Convert uses of fmod() and fmodf() to use FMath::Fmod() instead.
Also see CL 2844003
[CL 2855709 by Marc Audy in Main branch]
1643 lines
50 KiB
C++
1643 lines
50 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(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0.f, 0.f, 10.f, 0.f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility(this, &FBodyInstanceCustomization::IsCollisionPresetVisible)
|
|
.IsEnabled(this, &FBodyInstanceCustomization::IsCollisionEnabled)
|
|
+ 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)
|
|
.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)
|
|
.ContentPadding(0.f)
|
|
.ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to Default"))
|
|
.ButtonStyle(FEditorStyle::Get(), "NoBorder")
|
|
.IsEnabled(this, &FBodyInstanceCustomization::IsCollisionEnabled)
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault"))
|
|
]
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0.f, 0.f, 10.f, 0.f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DefaultCollision", "Default Collision"))
|
|
.Visibility(this, &FBodyInstanceCustomization::IsDefaultCollisionVisible)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ToolTipText(LOCTEXT("DefaultCollisionToolTip", "Default Collision. See the StaticMesh asset to find out collision settings"))
|
|
]
|
|
|
|
|
|
|
|
];
|
|
|
|
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 )
|
|
{
|
|
BodyInstanceHandle = StructPropertyHandle;
|
|
|
|
// 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);
|
|
}
|
|
|
|
|
|
// get all parent instances
|
|
TSharedPtr<IPropertyHandle> ParentPropertyHandle = StructPropertyHandle->GetParentHandle();
|
|
bool bFoundValidProperty = false; //Need to go up root until we find a valid property. This is a bad hack but is needed because GetChildHandle assumes property is always non-null
|
|
while(ParentPropertyHandle.IsValid() && !bFoundValidProperty)
|
|
{
|
|
if(ParentPropertyHandle->GetProperty())
|
|
{
|
|
bFoundValidProperty = true;
|
|
}
|
|
else
|
|
{
|
|
ParentPropertyHandle = ParentPropertyHandle->GetParentHandle();
|
|
}
|
|
}
|
|
|
|
|
|
if(ParentPropertyHandle.IsValid())
|
|
{
|
|
|
|
UseDefaultCollisionHandle = ParentPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(UStaticMeshComponent, bUseDefaultCollision));
|
|
StaticMeshHandle = ParentPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(UStaticMeshComponent, StaticMesh));
|
|
}
|
|
|
|
|
|
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> 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();
|
|
|
|
CollisionGroup.AddPropertyRow(CollisionEnabledHandle.ToSharedRef())
|
|
.IsEnabled(CustomCollisionEnabled)
|
|
.Visibility(CustomCollisionVisibility);
|
|
|
|
if (!StructPropertyHandle->GetProperty()->GetBoolMetaData(TEXT("HideObjectType")))
|
|
{
|
|
CollisionGroup.AddWidgetRow()
|
|
.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(CustomCollisionEnabled)
|
|
.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::AreAllCollisionUsingDefault() const
|
|
{
|
|
if(UseDefaultCollisionHandle.IsValid())
|
|
{
|
|
bool bUseDefaultCollision = false;
|
|
if (UseDefaultCollisionHandle->GetValue(bUseDefaultCollision) == FPropertyAccess::Result::Success && bUseDefaultCollision)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
EVisibility FBodyInstanceCustomization::IsCollisionPresetVisible() const
|
|
{
|
|
if (StaticMeshHandle.IsValid())
|
|
{
|
|
UObject* StaticMesh = nullptr;
|
|
if (StaticMeshHandle->GetValue(StaticMesh) == FPropertyAccess::Result::Success && StaticMesh)
|
|
{
|
|
return AreAllCollisionUsingDefault() ? EVisibility::Collapsed : EVisibility::Visible;
|
|
}
|
|
}
|
|
|
|
return EVisibility::Visible;
|
|
}
|
|
|
|
EVisibility FBodyInstanceCustomization::IsDefaultCollisionVisible() const
|
|
{
|
|
return IsCollisionPresetVisible() == EVisibility::Visible ? EVisibility::Collapsed : EVisibility::Visible;
|
|
}
|
|
|
|
bool FBodyInstanceCustomization::IsCollisionEnabled() const
|
|
{
|
|
bool bEnabled = false;
|
|
if(BodyInstanceHandle.IsValid())
|
|
{
|
|
bEnabled = !BodyInstanceHandle->IsEditConst() && FSlateApplication::Get().GetNormalExecutionAttribute().Get();
|
|
}
|
|
|
|
return bEnabled;
|
|
}
|
|
|
|
bool FBodyInstanceCustomization::ShouldEnableCustomCollisionSetup() const
|
|
{
|
|
FName ProfileName;
|
|
if (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
|
|
{
|
|
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;
|
|
}
|
|
|
|
void FBodyInstanceCustomizationHelper::OnSetBodyMass(float BodyMass, ETextCommit::Type Commit)
|
|
{
|
|
UPrimitiveComponent* Comp = nullptr;
|
|
UBodySetup* BS = nullptr;
|
|
|
|
for (auto ObjectIt = ObjectsCustomized.CreateConstIterator(); ObjectIt; ++ObjectIt)
|
|
{
|
|
if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UPrimitiveComponent::StaticClass()))
|
|
{
|
|
Comp = Cast<UPrimitiveComponent>(ObjectIt->Get());
|
|
Comp->SetMassOverrideInKg(NAME_None, BodyMass);
|
|
}
|
|
else if (ObjectIt->IsValid() && (*ObjectIt)->IsA(UBodySetup::StaticClass()))
|
|
{
|
|
BS = Cast<UBodySetup>(ObjectIt->Get());
|
|
BS->DefaultInstance.SetMassOverride(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)
|
|
{
|
|
if (bDisplayMass)
|
|
{
|
|
TSharedRef<IPropertyHandle> MassInKgOverrideHandle = BodyInstanceHandler->GetChildHandle(GET_MEMBER_NAME_CHECKED(FBodyInstance, MassInKgOverride)).ToSharedRef();
|
|
|
|
PhysicsCategory.AddProperty(MassInKgOverrideHandle).CustomWidget()
|
|
.NameContent()
|
|
[
|
|
MassInKgOverrideHandle->CreatePropertyNameWidget()
|
|
]
|
|
.ValueContent()
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0.f, 0.f, 10.f, 0.f)
|
|
[
|
|
SNew(SNumericEntryBox<float>)
|
|
.IsEnabled(&FBodyInstanceCustomizationHelper::IsBodyMassEnabled)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.Value(this, &FBodyInstanceCustomizationHelper::OnGetBodyMass)
|
|
.OnValueCommitted(this, &FBodyInstanceCustomizationHelper::OnSetBodyMass)
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
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
|