// Copyright Epic Games, Inc. All Rights Reserved. #include "PhysicsConstraintComponentDetails.h" #include "Layout/Visibility.h" #include "Layout/Margin.h" #include "Misc/Attribute.h" #include "Input/Reply.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SWidget.h" #include "Widgets/SBoxPanel.h" #include "Styling/SlateTypes.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Styling/AppStyle.h" #include "PhysicsEngine/PhysicsConstraintActor.h" #include "PropertyHandle.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "IDetailGroup.h" #include "IDetailPropertyRow.h" #include "DetailCategoryBuilder.h" #include "PhysicsEngine/ConstraintTypes.h" #include "PhysicsEngine/ConstraintDrives.h" #include "PhysicsEngine/ConstraintInstance.h" #include "PhysicsEngine/PhysicsConstraintTemplate.h" #include "PhysicsEngine/PhysicsConstraintComponent.h" #include "Widgets/Input/SNumericEntryBox.h" #include "ScopedTransaction.h" #include "Widgets/Text/SRichTextBlock.h" #define LOCTEXT_NAMESPACE "PhysicsConstraintComponentDetails" namespace ConstraintDetails { bool GetBoolProperty(TSharedPtr Prop) { bool bIsEnabled = false; if (Prop->GetValue(bIsEnabled) == FPropertyAccess::Result::Success) { return bIsEnabled; } return false; } TSharedRef JoinPropertyWidgets(TSharedPtr TargetProperty, FName TargetChildName, TSharedPtr ParentProperty, FName CheckPropertyName, TSharedPtr& StoreCheckProperty) { StoreCheckProperty = ParentProperty->GetChildHandle(CheckPropertyName); StoreCheckProperty->MarkHiddenByCustomization(); TSharedRef TargetWidget = TargetProperty->GetChildHandle(TargetChildName)->CreatePropertyValueWidget(); TargetWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda([StoreCheckProperty]() { bool bSet; if (StoreCheckProperty->GetValue(bSet) == FPropertyAccess::Result::Success) { return bSet; } return false; }))); return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0, 0, 5, 0) [ StoreCheckProperty->CreatePropertyValueWidget() ] + SHorizontalBox::Slot() [ TargetWidget ]; } TSharedRef CreateTriFloatWidget(TSharedPtr Prop1, TSharedPtr Prop2, TSharedPtr Prop3, const FText& TransactionName) { auto GetMultipleFloats = [Prop1, Prop2, Prop3]() { // RerunConstructionScripts gets run when the new value is set (if the component // is part of a blueprint). This causes the Objects being edited to be cleared, // and will cause GetValue to fail. Skip checking the values in that case. if (Prop1->GetNumPerObjectValues()) { float Val1, Val2, Val3; ensure(Prop1->GetValue(Val1) != FPropertyAccess::Fail); ensure(Prop2->GetValue(Val2) != FPropertyAccess::Fail); ensure(Prop3->GetValue(Val3) != FPropertyAccess::Fail); if (Val1 == Val2 && Val2 == Val3) { return TOptional(Val1); } } return TOptional(); }; auto SetMultipleFloatsCommitted = [Prop1, TransactionName, GetMultipleFloats](float NewValue, ETextCommit::Type) { TOptional CommonFloat = GetMultipleFloats(); if(!CommonFloat.IsSet() || CommonFloat.GetValue() != NewValue) //don't bother doing it twice { // Only set the first property. Others should be handled in PostEditChangeChainProperty. // This prevents an issue where multiple sets fail when using BlueprintComponents // due to RerunConstructionScripts destroying the edit list. FScopedTransaction Transaction(TransactionName); ensure(Prop1->SetValue(NewValue) == FPropertyAccess::Result::Success); } }; return SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(SNumericEntryBox) .OnValueCommitted_Lambda(SetMultipleFloatsCommitted) .Value_Lambda(GetMultipleFloats) .MinValue(0.f) ] + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(SButton) .OnClicked_Lambda([Prop1, Prop2, Prop3, TransactionName]() { FScopedTransaction Transaction(TransactionName); Prop1->ResetToDefault(); Prop2->ResetToDefault(); Prop3->ResetToDefault(); return FReply::Handled(); } ) .Visibility_Lambda([Prop1]() { return Prop1->DiffersFromDefault() ? EVisibility::Visible : EVisibility::Collapsed; }) .ContentPadding(FMargin(5.f, 0.f)) .ToolTipText(Prop1->GetResetToDefaultLabel()) .ButtonStyle(FAppStyle::Get(), "NoBorder") .Content() [ SNew(SImage) .Image(FAppStyle::GetBrush("PropertyWindow.DiffersFromDefault")) ] ]; } bool IsAngularPropertyEqual(TSharedPtr Prop, EAngularConstraintMotion CheckMotion) { uint8 Val; if (Prop->GetValue(Val) == FPropertyAccess::Result::Success) { return Val == CheckMotion; } return false; } } TSharedRef FPhysicsConstraintComponentDetails::MakeInstance() { return MakeShareable(new FPhysicsConstraintComponentDetails()); } void FPhysicsConstraintComponentDetails::AddConstraintBehaviorProperties(IDetailLayoutBuilder& DetailBuilder, TSharedPtr ConstraintInstance, TSharedPtr ProfilePropertiesProperty) { IDetailCategoryBuilder& ConstraintCat = DetailBuilder.EditCategory("Constraint Behavior"); //hide the inner structs that we customize elsewhere ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, LinearLimit))->MarkHiddenByCustomization(); ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, ConeLimit))->MarkHiddenByCustomization(); ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, TwistLimit))->MarkHiddenByCustomization(); ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, LinearDrive))->MarkHiddenByCustomization(); ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, AngularDrive))->MarkHiddenByCustomization(); ProfilePropertiesProperty->MarkHiddenByCustomization(); //Add properties we want in specific order ConstraintCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, bDisableCollision))); ConstraintCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, bParentDominates))); IDetailGroup& ProjectionGroup = ConstraintCat.AddGroup("Projection", LOCTEXT("Projection", "Projection"), false, true); ProjectionGroup.AddPropertyRow(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, bEnableProjection)).ToSharedRef()); ProjectionGroup.AddPropertyRow(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, ProjectionLinearTolerance)).ToSharedRef()); ProjectionGroup.AddPropertyRow(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, ProjectionAngularTolerance)).ToSharedRef()); ProjectionGroup.AddPropertyRow(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, ProjectionLinearAlpha)).ToSharedRef()); ProjectionGroup.AddPropertyRow(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, ProjectionAngularAlpha)).ToSharedRef()); IDetailGroup& ShockPropGroup = ConstraintCat.AddGroup("ShockPropagation", LOCTEXT("ShockPropagation", "Shock Propagation"), false, false); ShockPropGroup.AddPropertyRow(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, bEnableShockPropagation)).ToSharedRef()); ShockPropGroup.AddPropertyRow(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, ShockPropagationAlpha)).ToSharedRef()); //Add the rest uint32 NumProfileProperties = 0; ProfilePropertiesProperty->GetNumChildren(NumProfileProperties); for (uint32 ProfileChildIdx = 0; ProfileChildIdx < NumProfileProperties; ++ProfileChildIdx) { TSharedPtr ProfileChildProp = ProfilePropertiesProperty->GetChildHandle(ProfileChildIdx); if (!ProfileChildProp->IsCustomized()) { ConstraintCat.AddProperty(ProfileChildProp); } } } void FPhysicsConstraintComponentDetails::AddLinearLimits(IDetailLayoutBuilder& DetailBuilder, TSharedPtr ConstraintInstance, TSharedPtr ProfilePropertiesProperty) { IDetailCategoryBuilder& LinearLimitCat = DetailBuilder.EditCategory("Linear Limits"); TSharedPtr LinearConstraintProperty = ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, LinearLimit)); TSharedPtr LinearXMotionProperty = LinearConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearConstraint, XMotion)); TSharedPtr LinearYMotionProperty = LinearConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearConstraint, YMotion)); TSharedPtr LinearZMotionProperty = LinearConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearConstraint, ZMotion)); TArray> LinearLimitOptionNames; TArray LinearLimitOptionTooltips; TArray LinearLimitOptionRestrictItems; const int32 ExpectedLinearLimitOptionCount = 3; LinearXMotionProperty->GeneratePossibleValues(LinearLimitOptionNames, LinearLimitOptionTooltips, LinearLimitOptionRestrictItems); checkf(LinearLimitOptionNames.Num() == ExpectedLinearLimitOptionCount && LinearLimitOptionTooltips.Num() == ExpectedLinearLimitOptionCount && LinearLimitOptionRestrictItems.Num() == ExpectedLinearLimitOptionCount, TEXT("It seems the number of enum entries in ELinearConstraintMotion has changed. This must be handled here as well. ")); uint8 LinearLimitEnum[LCM_MAX] = { LCM_Free, LCM_Limited, LCM_Locked }; TSharedPtr LinearLimitProperties[] = { LinearXMotionProperty, LinearYMotionProperty, LinearZMotionProperty }; for (int32 PropertyIdx = 0; PropertyIdx < 3; ++PropertyIdx) { TSharedPtr CurProperty = LinearLimitProperties[PropertyIdx]; LinearLimitCat.AddProperty(CurProperty).CustomWidget() .NameContent() [ SNew(STextBlock) .Font(DetailBuilder.GetDetailFont()) .Text(CurProperty->GetPropertyDisplayName()) .ToolTipText(CurProperty->GetToolTipText()) ] .ValueContent() .MinDesiredWidth(125.0f * 3.0f) .MaxDesiredWidth(125.0f * 3.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) [ SNew(SCheckBox) .Style(FAppStyle::Get(), "RadioButton") .IsChecked(this, &FPhysicsConstraintComponentDetails::IsLimitRadioChecked, CurProperty, LinearLimitEnum[0]) .OnCheckStateChanged(this, &FPhysicsConstraintComponentDetails::OnLimitRadioChanged, CurProperty, LinearLimitEnum[0]) .ToolTipText(LinearLimitOptionTooltips[0]) [ SNew(STextBlock) .Text(FText::FromString(*LinearLimitOptionNames[0].Get())) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(5, 0, 0, 0) [ SNew(SCheckBox) .Style(FAppStyle::Get(), "RadioButton") .IsChecked(this, &FPhysicsConstraintComponentDetails::IsLimitRadioChecked, CurProperty, LinearLimitEnum[1]) .OnCheckStateChanged(this, &FPhysicsConstraintComponentDetails::OnLimitRadioChanged, CurProperty, LinearLimitEnum[1]) .ToolTipText(LinearLimitOptionTooltips[1]) [ SNew(STextBlock) .Text(FText::FromString(*LinearLimitOptionNames[1].Get())) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(5, 0, 0, 0) [ SNew(SCheckBox) .Style(FAppStyle::Get(), "RadioButton") .IsChecked(this, &FPhysicsConstraintComponentDetails::IsLimitRadioChecked, CurProperty, LinearLimitEnum[2]) .OnCheckStateChanged(this, &FPhysicsConstraintComponentDetails::OnLimitRadioChanged, CurProperty, LinearLimitEnum[2]) .ToolTipText(LinearLimitOptionTooltips[2]) [ SNew(STextBlock) .Text(FText::FromString(*LinearLimitOptionNames[2].Get())) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] ]; } auto IsLinearMotionLimited = [LinearXMotionProperty, LinearYMotionProperty, LinearZMotionProperty]() { uint8 XMotion, YMotion, ZMotion; if (LinearXMotionProperty->GetValue(XMotion) == FPropertyAccess::Result::Success && LinearYMotionProperty->GetValue(YMotion) == FPropertyAccess::Result::Success && LinearZMotionProperty->GetValue(ZMotion) == FPropertyAccess::Result::Success) { return XMotion == LCM_Limited || YMotion == LCM_Limited || ZMotion == LCM_Limited; } return false; }; TSharedPtr SoftProperty = LinearConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearConstraint, bSoftConstraint)); auto IsRestitutionEnabled = [IsLinearMotionLimited, SoftProperty]() { return !ConstraintDetails::GetBoolProperty(SoftProperty) && IsLinearMotionLimited(); }; LinearLimitCat.AddProperty(LinearConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearConstraint, Limit))).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(IsLinearMotionLimited))); LinearLimitCat.AddProperty(ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, bScaleLinearLimits))).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(IsLinearMotionLimited))); LinearLimitCat.AddProperty(LinearConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearConstraint, bSoftConstraint))).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(IsLinearMotionLimited))); LinearLimitCat.AddProperty(LinearConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearConstraint, Stiffness))).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(IsLinearMotionLimited))); LinearLimitCat.AddProperty(LinearConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearConstraint, Damping))).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(IsLinearMotionLimited))); LinearLimitCat.AddProperty(LinearConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearConstraint, Restitution))).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(IsRestitutionEnabled))); LinearLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, bLinearBreakable))); LinearLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, LinearBreakThreshold))); LinearLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, bLinearPlasticity)).ToSharedRef()); LinearLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, LinearPlasticityType)).ToSharedRef()); LinearLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, LinearPlasticityThreshold)).ToSharedRef()); // Mass Scale Properties LinearLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, ContactTransferScale)).ToSharedRef()); } void FPhysicsConstraintComponentDetails::AddAngularLimits(IDetailLayoutBuilder& DetailBuilder, TSharedPtr ConstraintInstance, TSharedPtr ProfilePropertiesProperty) { IDetailCategoryBuilder& AngularLimitCat = DetailBuilder.EditCategory("Angular Limits"); TSharedPtr ConeConstraintProperty = ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, ConeLimit)); TSharedPtr TwistConstraintProperty = ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, TwistLimit)); AngularSwing1MotionProperty = ConeConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConeConstraint, Swing1Motion)); AngularSwing2MotionProperty = ConeConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConeConstraint, Swing2Motion)); AngularTwistMotionProperty = TwistConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTwistConstraint, TwistMotion)); TArray> AngularLimitOptionNames; TArray AngularLimitOptionTooltips; TArray AngularLimitOptionRestrictItems; const int32 ExpectedAngularLimitOptionCount = 3; AngularSwing1MotionProperty->GeneratePossibleValues(AngularLimitOptionNames, AngularLimitOptionTooltips, AngularLimitOptionRestrictItems); checkf(AngularLimitOptionNames.Num() == ExpectedAngularLimitOptionCount && AngularLimitOptionTooltips.Num() == ExpectedAngularLimitOptionCount && AngularLimitOptionRestrictItems.Num() == ExpectedAngularLimitOptionCount, TEXT("It seems the number of enum entries in EAngularConstraintMotion has changed. This must be handled here as well. ")); uint8 AngularLimitEnum[LCM_MAX] = { ACM_Free, LCM_Limited, LCM_Locked }; TSharedPtr AngularLimitProperties[] = { AngularSwing1MotionProperty, AngularSwing2MotionProperty, AngularTwistMotionProperty }; const FName AxisStyleNames[3] = { "PhysicsAssetEditor.RadioButtons.Red", "PhysicsAssetEditor.RadioButtons.Red", "PhysicsAssetEditor.RadioButtons.Green" }; for (int32 PropertyIdx = 0; PropertyIdx < 3; ++PropertyIdx) { TSharedPtr CurProperty = AngularLimitProperties[PropertyIdx]; AngularLimitCat.AddProperty(CurProperty).CustomWidget() .NameContent() [ SNew(STextBlock) .Font(DetailBuilder.GetDetailFont()) .Text(CurProperty->GetPropertyDisplayName()) .ToolTipText(CurProperty->GetToolTipText()) ] .ValueContent() .MinDesiredWidth(125.0f * 3.0f) .MaxDesiredWidth(125.0f * 3.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) [ SNew(SCheckBox) .Style(FAppStyle::Get(), AxisStyleNames[PropertyIdx]) .IsChecked(this, &FPhysicsConstraintComponentDetails::IsLimitRadioChecked, CurProperty, AngularLimitEnum[0]) .OnCheckStateChanged(this, &FPhysicsConstraintComponentDetails::OnLimitRadioChanged, CurProperty, AngularLimitEnum[0]) .ToolTipText(AngularLimitOptionTooltips[0]) [ SNew(STextBlock) .Text(FText::FromString(*AngularLimitOptionNames[0].Get())) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(5, 0, 0, 0) [ SNew(SCheckBox) .Style(FAppStyle::Get(), AxisStyleNames[PropertyIdx]) .IsChecked(this, &FPhysicsConstraintComponentDetails::IsLimitRadioChecked, CurProperty, AngularLimitEnum[1]) .OnCheckStateChanged(this, &FPhysicsConstraintComponentDetails::OnLimitRadioChanged, CurProperty, AngularLimitEnum[1]) .ToolTipText(AngularLimitOptionTooltips[1]) [ SNew(STextBlock) .Text(FText::FromString(*AngularLimitOptionNames[1].Get())) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(5, 0, 0, 0) [ SNew(SCheckBox) .Style(FAppStyle::Get(), AxisStyleNames[PropertyIdx]) .IsChecked(this, &FPhysicsConstraintComponentDetails::IsLimitRadioChecked, CurProperty, AngularLimitEnum[2]) .OnCheckStateChanged(this, &FPhysicsConstraintComponentDetails::OnLimitRadioChanged, CurProperty, AngularLimitEnum[2]) .ToolTipText(AngularLimitOptionTooltips[2]) [ SNew(STextBlock) .Text(FText::FromString(*AngularLimitOptionNames[2].Get())) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] ]; } AngularLimitCat.AddProperty(ConeConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConeConstraint, Swing1LimitDegrees)).ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::AngularSwing1Limit))); AngularLimitCat.AddProperty(ConeConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConeConstraint, Swing2LimitDegrees)).ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::AngularSwing2Limit))); AngularLimitCat.AddProperty(TwistConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTwistConstraint, TwistLimitDegrees)).ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::AngularTwistLimit))); TSharedPtr SoftSwingProperty = ConeConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConeConstraint, bSoftConstraint)); auto SwingRestitutionEnabled = [this, SoftSwingProperty]() { return !ConstraintDetails::GetBoolProperty(SoftSwingProperty) && (IsPropertyEnabled(EPropertyType::AngularSwing1Limit) || IsPropertyEnabled(EPropertyType::AngularSwing2Limit)); }; IDetailGroup& SwingGroup = AngularLimitCat.AddGroup("Swing Limits", LOCTEXT("SwingLimits", "Swing Limits"), true, true); SwingGroup.AddPropertyRow(SoftSwingProperty.ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::AngularSwingLimit))); SwingGroup.AddPropertyRow(ConeConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConeConstraint, Stiffness)).ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::AngularSwingLimit))); SwingGroup.AddPropertyRow(ConeConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConeConstraint, Damping)).ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::AngularSwingLimit))); SwingGroup.AddPropertyRow(ConeConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConeConstraint, Restitution)).ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(SwingRestitutionEnabled))); TSharedPtr SoftTwistProperty = TwistConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTwistConstraint, bSoftConstraint)); auto TwistRestitutionEnabled = [this, SoftTwistProperty]() { return !ConstraintDetails::GetBoolProperty(SoftTwistProperty) && IsPropertyEnabled(EPropertyType::AngularTwistLimit); }; IDetailGroup& TwistGroup = AngularLimitCat.AddGroup("Twist Limits", LOCTEXT("TwistLimits", "Twist Limits"), true, true); TwistGroup.AddPropertyRow(SoftTwistProperty.ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::AngularTwistLimit))); TwistGroup.AddPropertyRow(TwistConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTwistConstraint, Stiffness)).ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::AngularTwistLimit))); TwistGroup.AddPropertyRow(TwistConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTwistConstraint, Damping)).ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::AngularTwistLimit))); TwistGroup.AddPropertyRow(TwistConstraintProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTwistConstraint, Restitution)).ToSharedRef()).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(TwistRestitutionEnabled))); if (bInPhat == false) { AngularLimitCat.AddProperty(ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, AngularRotationOffset)).ToSharedRef()); } else { AngularLimitCat.AddProperty(ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, AngularRotationOffset)).ToSharedRef()) .Visibility(EVisibility::Collapsed); } AngularLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, bAngularBreakable)).ToSharedRef()); AngularLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, AngularBreakThreshold)).ToSharedRef()); AngularLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, bAngularPlasticity)).ToSharedRef()); AngularLimitCat.AddProperty(ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, AngularPlasticityThreshold)).ToSharedRef()); } void FPhysicsConstraintComponentDetails::AddLinearDrive(IDetailLayoutBuilder& DetailBuilder, TSharedPtr ConstraintInstance, TSharedPtr ProfilePropertiesProperty) { IDetailCategoryBuilder& LinearMotorCat = DetailBuilder.EditCategory("LinearMotor"); TSharedPtr LinearDriveProperty = ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, LinearDrive)); IDetailGroup& PositionGroup = LinearMotorCat.AddGroup("Linear Position Drive", LOCTEXT("LinearPositionDrive", "Linear Position Drive"), false, true); TSharedRef LinearPositionTargetProperty = LinearDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearDriveConstraint, PositionTarget)).ToSharedRef(); TSharedPtr XDriveProperty = LinearDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearDriveConstraint, XDrive)); TSharedPtr YDriveProperty = LinearDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearDriveConstraint, YDrive)); TSharedPtr ZDriveProperty = LinearDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearDriveConstraint, ZDrive)); LinearXPositionDriveProperty = XDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnablePositionDrive)); LinearYPositionDriveProperty = YDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnablePositionDrive)); LinearZPositionDriveProperty = ZDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnablePositionDrive)); TSharedRef LinearPositionXWidget = ConstraintDetails::JoinPropertyWidgets(LinearPositionTargetProperty, FName("X"), XDriveProperty, GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnablePositionDrive), LinearXPositionDriveProperty); TSharedRef LinearPositionYWidget = ConstraintDetails::JoinPropertyWidgets(LinearPositionTargetProperty, FName("Y"), YDriveProperty, GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnablePositionDrive), LinearYPositionDriveProperty); TSharedRef LinearPositionZWidget = ConstraintDetails::JoinPropertyWidgets(LinearPositionTargetProperty, FName("Z"), ZDriveProperty, GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnablePositionDrive), LinearZPositionDriveProperty); FDetailWidgetRow& LinearPositionTargetWidget = PositionGroup.HeaderProperty(LinearPositionTargetProperty).CustomWidget() .NameContent() [ LinearPositionTargetProperty->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(125 * 3 + 18 * 3) .MaxDesiredWidth(125 * 3 + 18 * 3) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ LinearPositionXWidget ] + SHorizontalBox::Slot() .Padding(5, 0, 0, 0) [ LinearPositionYWidget ] + SHorizontalBox::Slot() .Padding(5, 0, 0, 0) [ LinearPositionZWidget ] ]; TSharedPtr StiffnessXProperty = XDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Stiffness)); TSharedRef StiffnessWidget = ConstraintDetails::CreateTriFloatWidget(StiffnessXProperty, YDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Stiffness)), ZDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Stiffness)), LOCTEXT("EditStrength", "Edit Strength")); StiffnessWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::LinearPositionDrive))); PositionGroup.AddWidgetRow() .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("Strength", "Strength")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ StiffnessWidget ]; // VELOCITY IDetailGroup& VelocityGroup = LinearMotorCat.AddGroup("Linear Velocity Drive", LOCTEXT("LinearVelocityDrive", "Linear Velocity Drive"), false, true); TSharedRef LinearVelocityTargetProperty = LinearDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearDriveConstraint, VelocityTarget)).ToSharedRef(); LinearXVelocityDriveProperty = XDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnableVelocityDrive)); LinearYVelocityDriveProperty = YDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnableVelocityDrive)); LinearZVelocityDriveProperty = ZDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnableVelocityDrive)); TSharedRef LinearVelocityXWidget = ConstraintDetails::JoinPropertyWidgets(LinearVelocityTargetProperty, FName("X"), XDriveProperty, GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnableVelocityDrive), LinearXVelocityDriveProperty); TSharedRef LinearVelocityYWidget = ConstraintDetails::JoinPropertyWidgets(LinearVelocityTargetProperty, FName("Y"), YDriveProperty, GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnableVelocityDrive), LinearYVelocityDriveProperty); TSharedRef LinearVelocityZWidget = ConstraintDetails::JoinPropertyWidgets(LinearVelocityTargetProperty, FName("Z"), ZDriveProperty, GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnableVelocityDrive), LinearZVelocityDriveProperty); FDetailWidgetRow& LinearVelocityTargetWidget = VelocityGroup.HeaderProperty(LinearVelocityTargetProperty).CustomWidget(true); LinearVelocityTargetWidget.NameContent() [ LinearVelocityTargetProperty->CreatePropertyNameWidget() ]; LinearVelocityTargetWidget.ValueContent() .MinDesiredWidth(125 * 3 + 18 * 3) .MaxDesiredWidth(125 * 3 + 18 * 3) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ LinearVelocityXWidget ] + SHorizontalBox::Slot() .Padding(5, 0, 0, 0) [ LinearVelocityYWidget ] + SHorizontalBox::Slot() .Padding(5, 0, 0, 0) [ LinearVelocityZWidget ] ]; TSharedPtr XDampingProperty = XDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Damping)); TSharedRef DampingWidget = ConstraintDetails::CreateTriFloatWidget(XDampingProperty, YDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Damping)), ZDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Damping)), LOCTEXT("EditStrength", "Edit Strength")); DampingWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::LinearVelocityDrive))); VelocityGroup.AddWidgetRow() .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("Strength", "Strength")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ DampingWidget ]; // max force limit TSharedPtr MaxForceProperty = XDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, MaxForce)); TSharedRef MaxForceWidget = ConstraintDetails::CreateTriFloatWidget(MaxForceProperty, YDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, MaxForce)), ZDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, MaxForce)), LOCTEXT("EditMaxForce", "Edit Max Force")); MaxForceWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsPropertyEnabled, EPropertyType::LinearDrive))); LinearMotorCat.AddCustomRow(LOCTEXT("MaxForce", "Max Force"), true) .NameContent() [ MaxForceProperty->CreatePropertyNameWidget() ] .ValueContent() [ MaxForceWidget ]; } void FPhysicsConstraintComponentDetails::AddAngularDrive(IDetailLayoutBuilder& DetailBuilder, TSharedPtr ConstraintInstance, TSharedPtr ProfilePropertiesProperty) { IDetailCategoryBuilder& AngularMotorCat = DetailBuilder.EditCategory("AngularMotor"); TSharedPtr AngularDriveProperty = ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintProfileProperties, AngularDrive)); TSharedPtr AngularDriveModeProperty = AngularDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAngularDriveConstraint, AngularDriveMode)); TSharedPtr SlerpDriveProperty = AngularDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAngularDriveConstraint, SlerpDrive)); TSharedPtr SwingDriveProperty = AngularDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAngularDriveConstraint, SwingDrive)); TSharedPtr TwistDriveProperty = AngularDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAngularDriveConstraint, TwistDrive)); TSharedPtr SlerpPositionDriveProperty = SlerpDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnablePositionDrive)); TSharedPtr SlerpVelocityDriveProperty = SlerpDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnableVelocityDrive)); TSharedPtr SwingPositionDriveProperty = SwingDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnablePositionDrive)); TSharedPtr SwingVelocityDriveProperty = SwingDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnableVelocityDrive)); TSharedPtr TwistPositionDriveProperty = TwistDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnablePositionDrive)); TSharedPtr TwistVelocityDriveProperty = TwistDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, bEnableVelocityDrive)); auto IsAngularMode = [AngularDriveModeProperty](EAngularDriveMode::Type CheckMode) { uint8 DriveMode; if (AngularDriveModeProperty->GetValue(DriveMode) == FPropertyAccess::Result::Success) { return DriveMode == CheckMode; } return false; }; auto EligibleForSLERP = [this, IsAngularMode]() { return IsAngularMode(EAngularDriveMode::SLERP) && !ConstraintDetails::IsAngularPropertyEqual(AngularSwing1MotionProperty, ACM_Locked) && !ConstraintDetails::IsAngularPropertyEqual(AngularSwing2MotionProperty, ACM_Locked) && !ConstraintDetails::IsAngularPropertyEqual(AngularTwistMotionProperty, ACM_Locked); }; auto EligibleForTwistAndSwing = [IsAngularMode]() { return IsAngularMode(EAngularDriveMode::TwistAndSwing); }; auto OrientationEnabled = [EligibleForSLERP, EligibleForTwistAndSwing, TwistPositionDriveProperty, SwingPositionDriveProperty, SlerpPositionDriveProperty]() { if(EligibleForSLERP()) { return ConstraintDetails::GetBoolProperty(SlerpPositionDriveProperty); } else if(EligibleForTwistAndSwing()) { return ConstraintDetails::GetBoolProperty(TwistPositionDriveProperty) || ConstraintDetails::GetBoolProperty(SwingPositionDriveProperty); } return false; }; auto VelocityEnabled = [EligibleForSLERP, EligibleForTwistAndSwing, TwistVelocityDriveProperty, SwingVelocityDriveProperty, SlerpVelocityDriveProperty]() { if (EligibleForSLERP()) { return ConstraintDetails::GetBoolProperty(SlerpVelocityDriveProperty); } else if (EligibleForTwistAndSwing()) { return ConstraintDetails::GetBoolProperty(TwistVelocityDriveProperty) || ConstraintDetails::GetBoolProperty(SwingVelocityDriveProperty); } return false; }; auto VelocityOrOrientationEnabled = [VelocityEnabled, OrientationEnabled]() { return VelocityEnabled() || OrientationEnabled(); }; AngularMotorCat.AddProperty(AngularDriveModeProperty); IDetailGroup& OrientationGroup = AngularMotorCat.AddGroup("Orientation Drive", LOCTEXT("OrientrationDrive", "Orientation Drive"), false, true); OrientationGroup.HeaderProperty(AngularDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAngularDriveConstraint, OrientationTarget)).ToSharedRef()).DisplayName(LOCTEXT("TargetOrientation", "Target Orientation")).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(OrientationEnabled))); TSharedRef SlerpPositionWidget = SlerpPositionDriveProperty->CreatePropertyValueWidget(); TSharedRef SlerpVelocityWidget = SlerpVelocityDriveProperty->CreatePropertyValueWidget(); SlerpPositionWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(EligibleForSLERP))); SlerpVelocityWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(EligibleForSLERP))); TSharedRef TwistPositionWidget = TwistPositionDriveProperty->CreatePropertyValueWidget(); TSharedRef TwistVelocityWidget = TwistVelocityDriveProperty->CreatePropertyValueWidget(); TwistPositionWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(EligibleForTwistAndSwing))); TwistVelocityWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(EligibleForTwistAndSwing))); TSharedRef SwingPositionWidget = SwingPositionDriveProperty->CreatePropertyValueWidget(); TSharedRef SwingVelocityWidget = SwingVelocityDriveProperty->CreatePropertyValueWidget(); SwingPositionWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(EligibleForTwistAndSwing))); SwingVelocityWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(EligibleForTwistAndSwing))); OrientationGroup.AddWidgetRow() .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("TwistSwingSlerpDrive", "Drives")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .MinDesiredWidth(125 * 3 + 18 * 3) .MaxDesiredWidth(125 * 3 + 18 * 3) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SlerpDriveProperty->CreatePropertyNameWidget() ] + SHorizontalBox::Slot() [ SlerpPositionWidget ] ] + SHorizontalBox::Slot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ TwistDriveProperty->CreatePropertyNameWidget() ] + SHorizontalBox::Slot() [ TwistPositionWidget ] ] + SHorizontalBox::Slot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SwingDriveProperty->CreatePropertyNameWidget() ] + SHorizontalBox::Slot() [ SwingPositionWidget ] ] ]; TSharedPtr StiffnessSlerpProperty = SlerpDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Stiffness)); TSharedRef OrientationStrengthWidget = ConstraintDetails::CreateTriFloatWidget(StiffnessSlerpProperty, TwistDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Stiffness)), SwingDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Stiffness)), LOCTEXT("EditStrength", "Edit Strength")); OrientationStrengthWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(OrientationEnabled))); OrientationGroup.AddWidgetRow() .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("Strength", "Strength")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ OrientationStrengthWidget ]; IDetailGroup& AngularVelocityGroup = AngularMotorCat.AddGroup("Velocity Drive", LOCTEXT("VelocityDrive", "Velocity Drive"), false, true); AngularVelocityGroup.HeaderProperty(AngularDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAngularDriveConstraint, AngularVelocityTarget)).ToSharedRef()).DisplayName(LOCTEXT("TargetVelocity", "Target Velocity")).IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(VelocityEnabled))); AngularVelocityGroup.AddWidgetRow() .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("TwistSwingSlerpDrive", "Drives")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .MinDesiredWidth(125 * 3 + 18 * 3) .MaxDesiredWidth(125 * 3 + 18 * 3) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SlerpDriveProperty->CreatePropertyNameWidget() ] + SHorizontalBox::Slot() [ SlerpVelocityWidget ] ] + SHorizontalBox::Slot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ TwistDriveProperty->CreatePropertyNameWidget() ] + SHorizontalBox::Slot() [ TwistVelocityWidget ] ] + SHorizontalBox::Slot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SwingDriveProperty->CreatePropertyNameWidget() ] + SHorizontalBox::Slot() [ SwingVelocityWidget ] ] ]; TSharedPtr DampingSlerpProperty = SlerpDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Damping)); TSharedRef DampingSlerpWidget = ConstraintDetails::CreateTriFloatWidget(DampingSlerpProperty, TwistDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Damping)), SwingDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, Damping)), LOCTEXT("EditStrength", "Edit Strength")); DampingSlerpWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(VelocityEnabled))); AngularVelocityGroup.AddWidgetRow() .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("Strength", "Strength")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ DampingSlerpWidget ]; // max force limit TSharedPtr MaxForcePropertySlerp = SlerpDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, MaxForce)); TSharedRef MaxForceWidget = ConstraintDetails::CreateTriFloatWidget(MaxForcePropertySlerp, TwistDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, MaxForce)), SwingDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintDrive, MaxForce)), LOCTEXT("EditMaxForce", "Edit Max Force")); MaxForceWidget->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda(VelocityOrOrientationEnabled))); AngularMotorCat.AddCustomRow(LOCTEXT("MaxForce", "Max Force"), true) .NameContent() [ MaxForcePropertySlerp->CreatePropertyNameWidget() ] .ValueContent() [ MaxForceWidget ]; } void FPhysicsConstraintComponentDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { TArray> Objects; DetailBuilder.GetObjectsBeingCustomized(Objects); TSharedPtr ConstraintInstance; UPhysicsConstraintComponent* ConstraintComp = NULL; APhysicsConstraintActor* OwningConstraintActor = NULL; bInPhat = false; for (int32 i=0; i < Objects.Num(); ++i) { if (!Objects[i].IsValid()) { continue; } if (Objects[i]->IsA(UPhysicsConstraintTemplate::StaticClass())) { ConstraintInstance = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPhysicsConstraintTemplate, DefaultInstance)); bInPhat = true; break; } else if (Objects[i]->IsA(UPhysicsConstraintComponent::StaticClass())) { ConstraintInstance = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPhysicsConstraintComponent, ConstraintInstance)); ConstraintComp = (UPhysicsConstraintComponent*)Objects[i].Get(); OwningConstraintActor = Cast(ConstraintComp->GetOwner()); break; } } IDetailCategoryBuilder& ConstraintCategory = DetailBuilder.EditCategory("Constraint"); //Create this category first so it's at the top DetailBuilder.EditCategory("Constraint Behavior"); //Create this category first so it's at the top TSharedPtr ProfileInstance = ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, ProfileInstance)); AddLinearLimits(DetailBuilder, ConstraintInstance, ProfileInstance); AddAngularLimits(DetailBuilder, ConstraintInstance, ProfileInstance); AddLinearDrive(DetailBuilder, ConstraintInstance, ProfileInstance); AddAngularDrive(DetailBuilder, ConstraintInstance, ProfileInstance); AddConstraintBehaviorProperties(DetailBuilder, ConstraintInstance, ProfileInstance); //Now we've added all the complex UI, just dump the rest into Constraint category if(bInPhat) { ConstraintCategory.HeaderContent( SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Right) .VAlign(VAlign_Center) [ SNew(SRichTextBlock) .DecoratorStyleSet(&FAppStyle::Get()) .Text_Lambda([Objects]() { if (Objects.Num() > 0) { if (UPhysicsConstraintTemplate* Constraint = Cast(Objects[0].Get())) { FName CurrentProfileName = Constraint->GetCurrentConstraintProfileName(); if(CurrentProfileName != NAME_None) { if(Constraint->ContainsConstraintProfile(CurrentProfileName)) { return FText::Format(LOCTEXT("ProfileFormatAssigned", "Assigned to Profile: {0}"), FText::FromName(CurrentProfileName)); } else { return FText::Format(LOCTEXT("ProfileFormatNotAssigned", "Not Assigned to Profile: {0}"), FText::FromName(CurrentProfileName)); } } else { return LOCTEXT("ProfileFormatNone", "Current Profile: None"); } } } return FText(); }) ]); } } bool FPhysicsConstraintComponentDetails::IsPropertyEnabled( EPropertyType::Type Type ) const { bool bIsVisible = false; switch (Type) { case EPropertyType::LinearXPositionDrive: return ConstraintDetails::GetBoolProperty(LinearXPositionDriveProperty); case EPropertyType::LinearYPositionDrive: return ConstraintDetails::GetBoolProperty(LinearYPositionDriveProperty); case EPropertyType::LinearZPositionDrive: return ConstraintDetails::GetBoolProperty(LinearZPositionDriveProperty); case EPropertyType::LinearXVelocityDrive: return ConstraintDetails::GetBoolProperty(LinearXVelocityDriveProperty); case EPropertyType::LinearYVelocityDrive: return ConstraintDetails::GetBoolProperty(LinearYVelocityDriveProperty); case EPropertyType::LinearZVelocityDrive: return ConstraintDetails::GetBoolProperty(LinearZVelocityDriveProperty); case EPropertyType::LinearPositionDrive: return ConstraintDetails::GetBoolProperty(LinearXPositionDriveProperty) || ConstraintDetails::GetBoolProperty(LinearYPositionDriveProperty) || ConstraintDetails::GetBoolProperty(LinearZPositionDriveProperty); case EPropertyType::LinearVelocityDrive: return ConstraintDetails::GetBoolProperty(LinearXVelocityDriveProperty) || ConstraintDetails::GetBoolProperty(LinearYVelocityDriveProperty) || ConstraintDetails::GetBoolProperty(LinearZVelocityDriveProperty); case EPropertyType::LinearDrive: return ConstraintDetails::GetBoolProperty(LinearXPositionDriveProperty) || ConstraintDetails::GetBoolProperty(LinearYPositionDriveProperty) || ConstraintDetails::GetBoolProperty(LinearZPositionDriveProperty) || ConstraintDetails::GetBoolProperty(LinearXVelocityDriveProperty) || ConstraintDetails::GetBoolProperty(LinearYVelocityDriveProperty) || ConstraintDetails::GetBoolProperty(LinearZVelocityDriveProperty); case EPropertyType::AngularSwing1Limit: return ConstraintDetails::IsAngularPropertyEqual(AngularSwing1MotionProperty, ACM_Limited); case EPropertyType::AngularSwing2Limit: return ConstraintDetails::IsAngularPropertyEqual(AngularSwing2MotionProperty, ACM_Limited); case EPropertyType::AngularSwingLimit: return ConstraintDetails::IsAngularPropertyEqual(AngularSwing1MotionProperty, ACM_Limited) || ConstraintDetails::IsAngularPropertyEqual(AngularSwing2MotionProperty, ACM_Limited); case EPropertyType::AngularTwistLimit: return ConstraintDetails::IsAngularPropertyEqual(AngularTwistMotionProperty, ACM_Limited); case EPropertyType::AngularAnyLimit: return ConstraintDetails::IsAngularPropertyEqual(AngularSwing1MotionProperty, ACM_Limited) || ConstraintDetails::IsAngularPropertyEqual(AngularSwing2MotionProperty, ACM_Limited) || ConstraintDetails::IsAngularPropertyEqual(AngularTwistMotionProperty, ACM_Limited); } return bIsVisible; } ECheckBoxState FPhysicsConstraintComponentDetails::IsLimitRadioChecked( TSharedPtr Property, uint8 Value ) const { uint8 PropertyEnumValue = 0; if (Property.IsValid() && Property->GetValue(PropertyEnumValue) == FPropertyAccess::Result::Success) { return PropertyEnumValue == Value ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FPhysicsConstraintComponentDetails::OnLimitRadioChanged( ECheckBoxState CheckType, TSharedPtr Property, uint8 Value ) { if (Property.IsValid() && CheckType == ECheckBoxState::Checked) { Property->SetValue(Value); } } #undef LOCTEXT_NAMESPACE