// Copyright Epic Games, Inc. All Rights Reserved. #include "PhysicsConstraintComponentDetails.h" #include "CoreGlobals.h" #include "Customizations/MathStructProxyCustomizations.h" #include "Delegates/Delegate.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Features/IModularFeatures.h" #include "Fonts/SlateFontInfo.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Commands/UIAction.h" #include "GenericPlatform/GenericApplication.h" #include "HAL/PlatformApplicationMisc.h" #include "HAL/PlatformCrt.h" #include "IDetailGroup.h" #include "IDetailPropertyRow.h" #include "IPhysicsAssetRenderInterface.h" #include "Input/Reply.h" #include "Internationalization/Internationalization.h" #include "Layout/Margin.h" #include "Layout/Visibility.h" #include "Math/Axis.h" #include "Math/Matrix.h" #include "Math/RotationMatrix.h" #include "Math/Rotator.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "Misc/AxisDisplayInfo.h" #include "Misc/EnumClassFlags.h" #include "PhysicsEngine/ConstraintDrives.h" #include "PhysicsEngine/ConstraintInstance.h" #include "PhysicsEngine/ConstraintTypes.h" #include "PhysicsEngine/PhysicsAsset.h" #include "PhysicsEngine/PhysicsConstraintActor.h" #include "PhysicsEngine/PhysicsConstraintComponent.h" #include "PhysicsEngine/PhysicsConstraintTemplate.h" #include "PropertyEditorModule.h" #include "PropertyHandle.h" #include "ScopedTransaction.h" #include "SlotBase.h" #include "Styling/AppStyle.h" #include "Styling/SlateTypes.h" #include "Templates/Casts.h" #include "Templates/UnrealTemplate.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/UnrealNames.h" #include "UObject/WeakObjectPtr.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SWidget.h" #include "Widgets/Text/SRichTextBlock.h" #include "Widgets/Text/STextBlock.h" class IPropertyTypeCustomization; #define LOCTEXT_NAMESPACE "PhysicsConstraintComponentDetails" namespace ConstraintCopyPasteStringTokens { static const FString Position = "Position"; static const FString Rotation = "Rotation"; }; // File scope utility functions. bool IsNearlyEqual(const FVector A, const FVector B, float ErrorTolerance) { return FMath::IsNearlyEqual(A.X, B.X, ErrorTolerance) && FMath::IsNearlyEqual(A.Y, B.Y, ErrorTolerance) && FMath::IsNearlyEqual(A.Z, B.Z, ErrorTolerance); } template< typename TPropertyValueType > bool IsChildPropertyNearlyEqual(TSharedPtr ParentPropertyHandle, const FName& LhsPropertyName, const TPropertyValueType& RhsValue) { if (ParentPropertyHandle.IsValid()) { TSharedPtr LhsPropertyHandle = ParentPropertyHandle->GetChildHandle(LhsPropertyName); if (LhsPropertyHandle.IsValid()) { TPropertyValueType LhsValue; LhsPropertyHandle->GetValue(LhsValue); return IsNearlyEqual(LhsValue, RhsValue, SMALL_NUMBER); } } return false; } // Class FOrthonormalVectorPairStructCustomization. FConstraintTransformCustomization::FConstraintTransformCustomization() : CachedRotation(MakeShareable(new TProxyValue(FRotator::ZeroRotator))) , CachedRotationYaw(MakeShareable(new TProxyProperty(CachedRotation, CachedRotation->Get().Yaw))) , CachedRotationPitch(MakeShareable(new TProxyProperty(CachedRotation, CachedRotation->Get().Pitch))) , CachedRotationRoll(MakeShareable(new TProxyProperty(CachedRotation, CachedRotation->Get().Roll))) , CachedPosition(MakeShareable(new TProxyValue(FVector::ZeroVector))) , CachedPositionX(MakeShareable(new TProxyProperty(CachedPosition, CachedPosition->Get().X))) , CachedPositionY(MakeShareable(new TProxyProperty(CachedPosition, CachedPosition->Get().Y))) , CachedPositionZ(MakeShareable(new TProxyProperty(CachedPosition, CachedPosition->Get().Z))) , DefaultTransform(FTransform::Identity) , InverseDefaultTransform(FTransform::Identity) , bPositionDisplayRelativeToDefault(false) , bRotationDisplayRelativeToDefault(false) {} TSharedRef FConstraintTransformCustomization::MakeInstance() { return MakeShareable(new FConstraintTransformCustomization); } void FConstraintTransformCustomization::MakeRotationRow(TSharedRef& InPriAxisPropertyHandle, TSharedRef& InSecAxisPropertyHandle, FDetailWidgetRow& Row, TSharedRef EditSpaceToggleButtonWidget) { TWeakPtr WeakPriAxisHandlePtr = InPriAxisPropertyHandle; TWeakPtr WeakSecAxisHandlePtr = InSecAxisPropertyHandle; const FText ComponentToolTipFormatText = LOCTEXT("ConstraintTransformRotationToolTip", "{0} component of the constraint rotation relative to the {1} bone (in degrees)."); if (Row.IsPasteFromTextBound()) { Row.OnPasteFromTextDelegate.Pin()->AddSP(this, &FConstraintTransformCustomization::OnPasteRotationFromText, WeakPriAxisHandlePtr, WeakSecAxisHandlePtr); } Row .CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FConstraintTransformCustomization::OnCopyRotation, WeakPriAxisHandlePtr, WeakSecAxisHandlePtr))) .PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FConstraintTransformCustomization::OnPasteRotation, WeakPriAxisHandlePtr, WeakSecAxisHandlePtr))) .NameContent() .HAlign(HAlign_Fill) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Left) .Padding(FMargin(0.0f, 0.0f, 4.0f, 0.0f)) [ InPriAxisPropertyHandle->CreatePropertyNameWidget(LOCTEXT("RotationLabel", "Rotation"), FText::Format(LOCTEXT("RotationToolTip", "Constraint rotation relative to the {0} bone (in degrees)."), FrameLabelText)) ] + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Right) .Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f)) [ EditSpaceToggleButtonWidget ] ] .ValueContent() .MinDesiredWidth(375.0f) .MaxDesiredWidth(375.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(FMargin(0.0f, 2.0f, 3.0f, 2.0f)) [ MakeNumericProxyWidget(InPriAxisPropertyHandle, InSecAxisPropertyHandle, CachedRotationRoll, FText::Format(ComponentToolTipFormatText, LOCTEXT("RotationRoll", "Roll"), FrameLabelText), true, AxisDisplayInfo::GetAxisColor(EAxisList::X)) ] + SHorizontalBox::Slot() .Padding(FMargin(0.0f, 2.0f, 3.0f, 2.0f)) [ MakeNumericProxyWidget(InPriAxisPropertyHandle, InSecAxisPropertyHandle, CachedRotationPitch, FText::Format(ComponentToolTipFormatText, LOCTEXT("RotationPitch", "Pitch"), FrameLabelText), true, AxisDisplayInfo::GetAxisColor(EAxisList::Y)) ] + SHorizontalBox::Slot() .Padding(FMargin(0.0f, 2.0f, 0.0f, 2.0f)) [ MakeNumericProxyWidget(InPriAxisPropertyHandle, InSecAxisPropertyHandle, CachedRotationYaw, FText::Format(ComponentToolTipFormatText, LOCTEXT("RotationYaw", "Yaw"), FrameLabelText), true, AxisDisplayInfo::GetAxisColor(EAxisList::Z)) ] ]; } void FConstraintTransformCustomization::MakePositionRow(TSharedRef& InPositionPropertyHandle, FDetailWidgetRow& Row, TSharedRef EditSpaceToggleButtonWidget) { TWeakPtr WeakPositionHandlePtr = InPositionPropertyHandle; const FText ComponentToolTipFormatText = LOCTEXT("ConstraintTransformPositionToolTip", "{0} component of the constraint position relative to the {1} bone."); if (Row.IsPasteFromTextBound()) { Row.OnPasteFromTextDelegate.Pin()->AddSP(this, &FConstraintTransformCustomization::OnPastePositionFromText, WeakPositionHandlePtr); } Row .CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FConstraintTransformCustomization::OnCopyPosition, WeakPositionHandlePtr))) .PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FConstraintTransformCustomization::OnPastePosition, WeakPositionHandlePtr))) .NameContent() .HAlign(HAlign_Fill) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Left) .Padding(FMargin(0.0f, 0.0f, 4.0f, 0.0f)) [ InPositionPropertyHandle->CreatePropertyNameWidget(LOCTEXT("PositionLabel", "Position"), FText::Format(LOCTEXT("PositionToolTip", "Constraint position relative to the {0} bone."), FrameLabelText)) ] + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Right) .Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f)) [ EditSpaceToggleButtonWidget ] ] .ValueContent() .MinDesiredWidth(375.0f) .MaxDesiredWidth(375.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(FMargin(0.0f, 2.0f, 3.0f, 2.0f)) [ MakeNumericProxyWidget(InPositionPropertyHandle, CachedPositionX, FText::Format(ComponentToolTipFormatText, AxisDisplayInfo::GetAxisDisplayName(EAxisList::X), FrameLabelText), AxisDisplayInfo::GetAxisColor(EAxisList::X)) ] + SHorizontalBox::Slot() .Padding(FMargin(0.0f, 2.0f, 3.0f, 2.0f)) [ MakeNumericProxyWidget(InPositionPropertyHandle, CachedPositionY, FText::Format(ComponentToolTipFormatText, AxisDisplayInfo::GetAxisDisplayName(EAxisList::Y), FrameLabelText), AxisDisplayInfo::GetAxisColor(EAxisList::Y)) ] + SHorizontalBox::Slot() .Padding(FMargin(0.0f, 2.0f, 0.0f, 2.0f)) [ MakeNumericProxyWidget(InPositionPropertyHandle, CachedPositionZ, FText::Format(ComponentToolTipFormatText, AxisDisplayInfo::GetAxisDisplayName(EAxisList::Z), FrameLabelText), AxisDisplayInfo::GetAxisColor(EAxisList::Z)) ] ]; } void FConstraintTransformCustomization::OrthonormalVectorPairToDisplayedRotator(const FVector& PriAxis, const FVector& SecAxis, FRotator& OutRotator) const { FMatrix Rotation = FRotationMatrix::MakeFromXY(PriAxis, SecAxis); // If rotation should be displayed relative to default then transform it from bone space. if (bRotationDisplayRelativeToDefault) { Rotation = Rotation * InverseDefaultTransform.ToMatrixNoScale(); } OutRotator = Rotation.Rotator(); } void FConstraintTransformCustomization::DisplayedRotatorToOrthonormalVectorPair(const FRotator& InRotator, FVector& OutPriAxis, FVector& OutSecAxis) const { FMatrix Rotation = FRotationMatrix::Make(InRotator); // If rotation is displayed relative to default then transform it back to bone space. if (bRotationDisplayRelativeToDefault) { Rotation = Rotation * DefaultTransform.ToMatrixNoScale(); } OutPriAxis = Rotation.GetUnitAxis(EAxis::X); OutSecAxis = Rotation.GetUnitAxis(EAxis::Y); } bool FConstraintTransformCustomization::CacheValues(TWeakPtr PositionPropertyHandlePtr) const { TSharedPtr PositionPropertyHandle = PositionPropertyHandlePtr.Pin(); if (!PositionPropertyHandle.IsValid()) { return false; } FVector Position; if (PositionPropertyHandle->GetValue(Position) == FPropertyAccess::Success) { if (bPositionDisplayRelativeToDefault) { Position = InverseDefaultTransform.TransformPosition(Position); } CachedPosition->Set(Position); CachedPositionX->Set(Position.X); CachedPositionY->Set(Position.Y); CachedPositionZ->Set(Position.Z); return true; } return false; } bool FConstraintTransformCustomization::CacheValues(TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) const { TSharedPtr PriAxisPropertyHandle = PriAxisPropertyHandlePtr.Pin(); TSharedPtr SecAxisPropertyHandle = SecAxisPropertyHandlePtr.Pin(); if (!PriAxisPropertyHandle.IsValid() || !SecAxisPropertyHandle.IsValid()) { return false; } FVector PriAxis; FVector SecAxis; if ((PriAxisPropertyHandle->GetValue(PriAxis) == FPropertyAccess::Success) && (SecAxisPropertyHandle->GetValue(SecAxis) == FPropertyAccess::Success)) { FRotator CurrentRotation; OrthonormalVectorPairToDisplayedRotator(PriAxis, SecAxis, CurrentRotation); CachedRotation->Set(CurrentRotation); CachedRotationPitch->Set(CurrentRotation.Pitch); CachedRotationYaw->Set(CurrentRotation.Yaw); CachedRotationRoll->Set(CurrentRotation.Roll); return true; } return false; } bool FConstraintTransformCustomization::FlushValues(TWeakPtr PositionPropertyHandlePtr) const { // Update the constraint position from the one displayed in the details panel. TSharedPtr PositionPropertyHandle = PositionPropertyHandlePtr.Pin(); if (!PositionPropertyHandle.IsValid()) { return false; } // Read position from the property FVector Position; PositionPropertyHandle->GetValue(Position); // Update position with any changes from the UI. FVector ModifiedPosition( CachedPositionX->IsSet() ? CachedPositionX->Get() : Position.X, CachedPositionY->IsSet() ? CachedPositionY->Get() : Position.Y, CachedPositionZ->IsSet() ? CachedPositionZ->Get() : Position.Z ); // If position is displayed relative to default then transform it back to bone space. if (bPositionDisplayRelativeToDefault) { ModifiedPosition = DefaultTransform.TransformPosition(ModifiedPosition); } // Write back to the property. return PositionPropertyHandle->SetValue(ModifiedPosition) == FPropertyAccess::Success; } bool FConstraintTransformCustomization::FlushValues(TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) const { // Update the constraint rotation from the one displayed in the details panel. TSharedPtr PriAxisPropertyHandle = PriAxisPropertyHandlePtr.Pin(); TSharedPtr SecAxisPropertyHandle = SecAxisPropertyHandlePtr.Pin(); if (!PriAxisPropertyHandle.IsValid() || !SecAxisPropertyHandle.IsValid()) { return false; } FVector PriAxis; FVector SecAxis; PriAxisPropertyHandle->GetValue(PriAxis); SecAxisPropertyHandle->GetValue(SecAxis); FRotator CurrentRotation; OrthonormalVectorPairToDisplayedRotator(PriAxis, SecAxis, CurrentRotation); FRotator Rotation( CachedRotationPitch->IsSet() ? CachedRotationPitch->Get() : CurrentRotation.Pitch, CachedRotationYaw->IsSet() ? CachedRotationYaw->Get() : CurrentRotation.Yaw, CachedRotationRoll->IsSet() ? CachedRotationRoll->Get() : CurrentRotation.Roll ); DisplayedRotatorToOrthonormalVectorPair(Rotation, PriAxis, SecAxis); bool Success = true; Success &= (PriAxisPropertyHandle->SetValue(PriAxis) == FPropertyAccess::Result::Success); Success &= (SecAxisPropertyHandle->SetValue(SecAxis) == FPropertyAccess::Result::Success); return Success; } void FConstraintTransformCustomization::SetDefaultTransform(const FTransform& InTransform, TWeakPtr PositionPropertyHandlePtr, TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) { DefaultTransform = InTransform; InverseDefaultTransform = InTransform.Inverse(); CacheValues(PositionPropertyHandlePtr); CacheValues(PriAxisPropertyHandlePtr, SecAxisPropertyHandlePtr); // Update display values from actual properties (which as always stored as absolutes). } void FConstraintTransformCustomization::GetPositionAsFormattedString(FString& OutString) { FVector Position = CachedPosition->Get(); OutString = FString::Printf(TEXT("(X=%f,Y=%f,Z=%f)"), Position.X, Position.Y, Position.Z); } void FConstraintTransformCustomization::GetRotationAsFormattedString(FString& OutString) { FRotator Rotation = CachedRotation->Get(); OutString = FString::Printf(TEXT("(Pitch=%f,Yaw=%f,Roll=%f)"), Rotation.Pitch, Rotation.Yaw, Rotation.Roll); } void FConstraintTransformCustomization::GetValueAsFormattedString(FString& OutString) { FString Buffer; GetPositionAsFormattedString(Buffer); OutString += ConstraintCopyPasteStringTokens::Position + Buffer; Buffer.Reset(); GetRotationAsFormattedString(Buffer); OutString += ConstraintCopyPasteStringTokens::Rotation + Buffer; } bool FConstraintTransformCustomization::SetPositionFromFormattedString(const FString& InString) { FVector Position; if (Position.InitFromString(InString)) { CachedPosition->Set(Position); CachedPositionX->Set(Position.X); CachedPositionY->Set(Position.Y); CachedPositionZ->Set(Position.Z); return true; } return false; } bool FConstraintTransformCustomization::SetRotationFromFormattedString(const FString& InString) { FString MutableString = InString; FRotator Rotation; MutableString.ReplaceInline(TEXT("Pitch="), TEXT("P=")); MutableString.ReplaceInline(TEXT("Yaw="), TEXT("Y=")); MutableString.ReplaceInline(TEXT("Roll="), TEXT("R=")); if (Rotation.InitFromString(MutableString)) { CachedRotation->Set(Rotation); CachedRotationPitch->Set(Rotation.Pitch); CachedRotationYaw->Set(Rotation.Yaw); CachedRotationRoll->Set(Rotation.Roll); return true; } return false; } bool FConstraintTransformCustomization::SetValueFromFormattedString(const FString& InString) { bool Success = true; FString FormattedString = InString; FString SubString; FormattedString.Split(ConstraintCopyPasteStringTokens::Rotation, &FormattedString, &SubString); Success &= SetRotationFromFormattedString(SubString); FormattedString.Split(ConstraintCopyPasteStringTokens::Position, &FormattedString, &SubString); Success &= SetPositionFromFormattedString(SubString); return Success; } void FConstraintTransformCustomization::OnCopy(TWeakPtr PositionPropertyHandlePtr, TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) { TSharedPtr PositionPropertyHandle = PositionPropertyHandlePtr.Pin(); TSharedPtr PriAxisPropertyHandle = PriAxisPropertyHandlePtr.Pin(); TSharedPtr SecAxisPropertyHandle = SecAxisPropertyHandlePtr.Pin(); if (!PositionPropertyHandle.IsValid() || !PriAxisPropertyHandle.IsValid() || !SecAxisPropertyHandle.IsValid()) { return; } CacheValues(PositionPropertyHandle); CacheValues(PriAxisPropertyHandle, SecAxisPropertyHandle); FString CopyStr; GetValueAsFormattedString(CopyStr); if (!CopyStr.IsEmpty()) { FPlatformApplicationMisc::ClipboardCopy(*CopyStr); } } void FConstraintTransformCustomization::OnPaste(TWeakPtr PositionPropertyHandlePtr, TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) { FString PastedText; FPlatformApplicationMisc::ClipboardPaste(PastedText); OnPasteFromText(TEXT(""), PastedText, {}, PositionPropertyHandlePtr, PriAxisPropertyHandlePtr, SecAxisPropertyHandlePtr); } void FConstraintTransformCustomization::OnPasteFromText( const FString& InTag, const FString& InText, const TOptional& InOperationId, TWeakPtr PositionPropertyHandlePtr, TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) { TSharedPtr PositionPropertyHandle = PositionPropertyHandlePtr.Pin(); TSharedPtr PriAxisPropertyHandle = PriAxisPropertyHandlePtr.Pin(); TSharedPtr SecAxisPropertyHandle = SecAxisPropertyHandlePtr.Pin(); if (!PositionPropertyHandle.IsValid() || !PriAxisPropertyHandle.IsValid() || !SecAxisPropertyHandle.IsValid()) { return; } { FScopedTransaction Transaction(LOCTEXT("PastePosition", "Paste Position")); SetValueFromFormattedString(InText); FlushValues(PriAxisPropertyHandle, SecAxisPropertyHandle); FlushValues(PositionPropertyHandle); } } void FConstraintTransformCustomization::OnCopyPosition(TWeakPtr PositionPropertyHandlePtr) { TSharedPtr PositionPropertyHandle = PositionPropertyHandlePtr.Pin(); if (!PositionPropertyHandle.IsValid()) { return; } CacheValues(PositionPropertyHandle); FString CopyStr; GetPositionAsFormattedString(CopyStr); if (!CopyStr.IsEmpty()) { FPlatformApplicationMisc::ClipboardCopy(*CopyStr); } } void FConstraintTransformCustomization::OnCopyRotation(TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) { TSharedPtr PriAxisPropertyHandle = PriAxisPropertyHandlePtr.Pin(); TSharedPtr SecAxisPropertyHandle = SecAxisPropertyHandlePtr.Pin(); if (!PriAxisPropertyHandle.IsValid() || !SecAxisPropertyHandle.IsValid()) { return; } CacheValues(PriAxisPropertyHandle, SecAxisPropertyHandle); FString CopyStr; GetRotationAsFormattedString(CopyStr); if (!CopyStr.IsEmpty()) { FPlatformApplicationMisc::ClipboardCopy(*CopyStr); } } void FConstraintTransformCustomization::OnPastePosition(TWeakPtr PositionPropertyHandlePtr) { FString PastedText; FPlatformApplicationMisc::ClipboardPaste(PastedText); PastePositionFromText(TEXT(""), PastedText, PositionPropertyHandlePtr); } void FConstraintTransformCustomization::OnPastePositionFromText( const FString& InTag, const FString& InText, const TOptional& InOperationId, TWeakPtr PositionPropertyHandlePtr) { PastePositionFromText(InTag, InText, PositionPropertyHandlePtr); } void FConstraintTransformCustomization::PastePositionFromText( const FString& InTag, const FString& InText, TWeakPtr PositionPropertyHandlePtr) { TSharedPtr PositionPropertyHandle = PositionPropertyHandlePtr.Pin(); if (!PositionPropertyHandle.IsValid()) { return; } { FScopedTransaction Transaction(LOCTEXT("PastePosition", "Paste Position")); SetPositionFromFormattedString(InText); FlushValues(PositionPropertyHandle); } } void FConstraintTransformCustomization::OnPasteRotation(TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) { FString PastedText; FPlatformApplicationMisc::ClipboardPaste(PastedText); PasteRotationFromText(TEXT(""), PastedText, PriAxisPropertyHandlePtr, SecAxisPropertyHandlePtr); } void FConstraintTransformCustomization::OnPasteRotationFromText( const FString& InTag, const FString& InText, const TOptional& InOperationId, TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) { PasteRotationFromText(InTag, InText, PriAxisPropertyHandlePtr, SecAxisPropertyHandlePtr); } void FConstraintTransformCustomization::PasteRotationFromText( const FString& InTag, const FString& InText, TWeakPtr PriAxisPropertyHandlePtr, TWeakPtr SecAxisPropertyHandlePtr) { TSharedPtr PriAxisPropertyHandle = PriAxisPropertyHandlePtr.Pin(); TSharedPtr SecAxisPropertyHandle = SecAxisPropertyHandlePtr.Pin(); if (!PriAxisPropertyHandle.IsValid() || !SecAxisPropertyHandle.IsValid()) { return; } { FScopedTransaction Transaction(LOCTEXT("PasteRotation", "Paste Rotation")); SetRotationFromFormattedString(InText); FlushValues(PriAxisPropertyHandle, SecAxisPropertyHandle); } } template TSharedRef FConstraintTransformCustomization::MakeNumericProxyWidget(TSharedRef& PriAxisPropertyHandle, TSharedRef& SecAxisPropertyHandle, TSharedRef< TProxyProperty >& ProxyValue, const FText& ToolTipText, bool bRotationInDegrees, const FLinearColor& LabelBackgroundColor) { TWeakPtr WeakPriAxisHandlePtr = PriAxisPropertyHandle; TWeakPtr WeakSecAxisHandlePtr = SecAxisPropertyHandle; return SNew(SNumericEntryBox) .IsEnabled(this, &FConstraintTransformCustomization::IsRotationValueEnabled, WeakPriAxisHandlePtr, WeakSecAxisHandlePtr) .Value(this, &FConstraintTransformCustomization::OnGetRotationValue, WeakPriAxisHandlePtr, WeakSecAxisHandlePtr, ProxyValue) .Font(IDetailLayoutBuilder::GetDetailFont()) .UndeterminedString(NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values")) .OnValueCommitted(this, &FConstraintTransformCustomization::OnRotationValueCommitted, WeakPriAxisHandlePtr, WeakSecAxisHandlePtr, ProxyValue) .OnValueChanged(this, &FConstraintTransformCustomization::OnRotationValueChanged, WeakPriAxisHandlePtr, WeakSecAxisHandlePtr, ProxyValue) .AllowSpin(false) .MinValue(TOptional()) .MaxValue(TOptional()) .MaxSliderValue(bRotationInDegrees ? 360.0f : TOptional()) .MinSliderValue(bRotationInDegrees ? 0.0f : TOptional()) .LabelPadding(FMargin(3)) .ToolTipText(ToolTipText) .LabelLocation(SNumericEntryBox::ELabelLocation::Inside) .Label() [ SNumericEntryBox::BuildNarrowColorLabel(LabelBackgroundColor) ]; } template TSharedRef FConstraintTransformCustomization::MakeNumericProxyWidget(TSharedRef& PositionPropertyHandle, TSharedRef< TProxyProperty >& ProxyValue, const FText& ToolTipText, const FLinearColor& LabelBackgroundColor) { TWeakPtr WeakPositionHandlePtr = PositionPropertyHandle; return SNew(SNumericEntryBox) .IsEnabled(this, &FConstraintTransformCustomization::IsPositionValueEnabled, WeakPositionHandlePtr) .Value(this, &FConstraintTransformCustomization::OnGetPositionValue, WeakPositionHandlePtr, ProxyValue) .Font(IDetailLayoutBuilder::GetDetailFont()) .UndeterminedString(NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values")) .OnValueCommitted(this, &FConstraintTransformCustomization::OnPositionValueCommitted, WeakPositionHandlePtr, ProxyValue) .OnValueChanged(this, &FConstraintTransformCustomization::OnPositionValueChanged, WeakPositionHandlePtr, ProxyValue) .AllowSpin(false) .LabelPadding(FMargin(3)) .ToolTipText(ToolTipText) .LabelLocation(SNumericEntryBox::ELabelLocation::Inside) .Label() [ SNumericEntryBox::BuildNarrowColorLabel(LabelBackgroundColor) ]; } bool FConstraintTransformCustomization::IsRotationValueEnabled(TWeakPtr PriAxisWeakHandlePtr, TWeakPtr SecAxisWeakHandlePtr) const { return (PriAxisWeakHandlePtr.IsValid() && !PriAxisWeakHandlePtr.Pin()->IsEditConst()) && (SecAxisWeakHandlePtr.IsValid() && !SecAxisWeakHandlePtr.Pin()->IsEditConst()); } template TOptional FConstraintTransformCustomization::OnGetRotationValue(TWeakPtr PriAxisWeakHandlePtr, TWeakPtr SecAxisWeakHandlePtr, TSharedRef< TProxyProperty > ProxyValue) const { if (CacheValues(PriAxisWeakHandlePtr, SecAxisWeakHandlePtr)) { return ProxyValue->Get(); } return TOptional(); } template void FConstraintTransformCustomization::OnRotationValueCommitted(NumericType NewValue, ETextCommit::Type CommitType, TWeakPtr PriAxisWeakHandlePtr, TWeakPtr SecAxisWeakHandlePtr, TSharedRef< TProxyProperty > ProxyValue) { if (!bIsUsingSlider && !GIsTransacting) { ProxyValue->Set(NewValue); FlushValues(PriAxisWeakHandlePtr, SecAxisWeakHandlePtr); } } template void FConstraintTransformCustomization::OnRotationValueChanged(NumericType NewValue, TWeakPtr PriAxisWeakHandlePtr, TWeakPtr SecAxisWeakHandlePtr, TSharedRef< TProxyProperty > ProxyValue) { if (bIsUsingSlider) { ProxyValue->Set(NewValue); FlushValues(PriAxisWeakHandlePtr, SecAxisWeakHandlePtr); } } bool FConstraintTransformCustomization::IsPositionValueEnabled(TWeakPtr PositionWeakHandlePtr) const { return (PositionWeakHandlePtr.IsValid() && !PositionWeakHandlePtr.Pin()->IsEditConst()); } template TOptional FConstraintTransformCustomization::OnGetPositionValue(TWeakPtr PositionWeakHandlePtr, TSharedRef< TProxyProperty > ProxyValue) const { if (CacheValues(PositionWeakHandlePtr)) { return ProxyValue->Get(); } return TOptional(); } template void FConstraintTransformCustomization::OnPositionValueCommitted(NumericType NewValue, ETextCommit::Type CommitType, TWeakPtr PositionWeakHandlePtr, TSharedRef< TProxyProperty > ProxyValue) { if (!GIsTransacting) { ProxyValue->Set(NewValue); FlushValues(PositionWeakHandlePtr); } } template void FConstraintTransformCustomization::OnPositionValueChanged(NumericType NewValue, TWeakPtr PositionWeakHandlePtr, TSharedRef< TProxyProperty > ProxyValue) { ProxyValue->Set(NewValue); FlushValues(PositionWeakHandlePtr); } void FConstraintTransformCustomization::SetFrameLabelText(const FText InText) { FrameLabelText = InText; } FText& FConstraintTransformCustomization::GetFrameLabelText() { return FrameLabelText; } 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; } } FPhysicsConstraintComponentDetails::FPhysicsConstraintComponentDetails() : ChildTransformProxy(StaticCastSharedRef(FConstraintTransformCustomization::MakeInstance())) , ParentTransformProxy(StaticCastSharedRef(FConstraintTransformCustomization::MakeInstance())) {} TSharedRef FPhysicsConstraintComponentDetails::MakeInstance() { return MakeShareable(new FPhysicsConstraintComponentDetails()); } void FPhysicsConstraintComponentDetails::AddConstraintProperties(IDetailLayoutBuilder& DetailBuilder, TSharedPtr ConstraintInstance, TArray>& Objects) { ChildPositionPropertyHandle = ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, Pos1)); ChildPriAxisPropertyHandle = ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, PriAxis1)); ChildSecAxisPropertyHandle = ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, SecAxis1)); ParentPositionPropertyHandle = ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, Pos2)); ParentPriAxisPropertyHandle = ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, PriAxis2)); ParentSecAxisPropertyHandle = ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, SecAxis2)); IDetailCategoryBuilder& ConstraintCategory = DetailBuilder.EditCategory("Constraint"); if (bInPhat) { // Add current profile name to the constraint category header. 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(); }) ]); // Add PhAT specific components to details panel. ConstraintCategory.AddProperty(ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, ConstraintBone1))).DisplayName(LOCTEXT("ConstraintChildBoneName", "Child Bone Name")); ConstraintCategory.AddProperty(ConstraintInstance->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, ConstraintBone2))).DisplayName(LOCTEXT("ConstraintParentBoneName", "Parent Bone Name")); IDetailCategoryBuilder& ConstraintTransformsCat = DetailBuilder.EditCategory("ConstraintTransforms"); AddConstraintFrameTransform(EConstraintFrame::Frame1, ConstraintTransformsCat, ConstraintInstance); AddConstraintFrameTransform(EConstraintFrame::Frame2, ConstraintTransformsCat, ConstraintInstance); UpdateTransformProxyDisplayRelativeToDefault(ConstraintInstance); // Hide the constraint positions as they are represented by the proxy values in PhAT. ChildPositionPropertyHandle->MarkHiddenByCustomization(); ParentPositionPropertyHandle->MarkHiddenByCustomization(); } // Always hide the constraint orientation values as they are represented by the proxy values in PhAT and hidden by design everywhere else. ChildPriAxisPropertyHandle->MarkHiddenByCustomization(); ChildSecAxisPropertyHandle->MarkHiddenByCustomization(); ParentPriAxisPropertyHandle->MarkHiddenByCustomization(); ParentSecAxisPropertyHandle->MarkHiddenByCustomization(); } TSharedRef FPhysicsConstraintComponentDetails::MakeEditSpaceToggleButtonWidget(TSharedPtr ConstraintInstancePropertyHandle, const EConstraintTransformComponentFlags ComponentFlags) { auto ToolTipTextLambda = [this, ComponentFlags]() { const FText FrameText = EnumHasAnyFlags(ComponentFlags, EConstraintTransformComponentFlags::AllChild) ? LOCTEXT("Child", "Child") : LOCTEXT("Parent", "Parent"); const FText ComponentText = EnumHasAnyFlags(ComponentFlags, EConstraintTransformComponentFlags::AllPosition) ? LOCTEXT("Position", "Position") : LOCTEXT("Rotation", "Rotation"); const FText LocalFrameText = FText::Format(LOCTEXT("LocalFrameDescription", "in the frame of the {0} bone"), FrameText); const FText SnapFrameText = LOCTEXT("SnapFrameDescription", "relative to the default (snapped) transforms"); const bool IsDisplayingRelativeToDefault = this->IsDisplayingConstraintTransformComponentRelativeToDefault(ComponentFlags); const FText CurrentFrameText = IsDisplayingRelativeToDefault ? SnapFrameText : LocalFrameText; const FText AlternativeFrameText = IsDisplayingRelativeToDefault ? LocalFrameText : SnapFrameText; return FText::Format(LOCTEXT("ToggleDisplayConstraintTransformComponentRelativeToDefault", "{0} transform {1} component displayed {2}. Click here to switch to displaying {3}. Hold Shift to change all."), FrameText, ComponentText, CurrentFrameText, AlternativeFrameText); }; return SNew(SButton) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .OnClicked_Lambda([this, ConstraintInstancePropertyHandle, ComponentFlags]() { this->ToggleDisplayConstraintTransformComponentRelativeToDefault(ConstraintInstancePropertyHandle, ComponentFlags); return FReply::Handled(); }) .ButtonColorAndOpacity(FSlateColor::UseForeground()) .Content() [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image_Lambda([this, ComponentFlags]() { return (IsDisplayingConstraintTransformComponentRelativeToDefault(ComponentFlags)) ? FAppStyle::GetBrush("Icons.Snap") : FAppStyle::GetBrush("Icons.Transform"); }) .ToolTipText_Lambda(ToolTipTextLambda) ]; } void FPhysicsConstraintComponentDetails::AddConstraintFrameTransform(const EConstraintFrame::Type ConstraintFrameType, IDetailCategoryBuilder& ConstraintCat, TSharedPtr ConstraintInstancePropertyHandle) { TSharedPtr TransformProxy; TSharedPtr PositionPropertyHandle; TSharedPtr PriAxisPropertyHandle; TSharedPtr SecAxisPropertyHandle; EConstraintTransformComponentFlags PositionSnapFlag = EConstraintTransformComponentFlags::None; EConstraintTransformComponentFlags RotationSnapFlag = EConstraintTransformComponentFlags::None; FText KeyboardShortcutText; if (ConstraintFrameType == EConstraintFrame::Frame1) // Child Frame { TransformProxy = ChildTransformProxy; TransformProxy->SetFrameLabelText(LOCTEXT("Child", "Child")); KeyboardShortcutText = LOCTEXT("ShiftAlt", "[Shift + Alt]"); PositionPropertyHandle = ChildPositionPropertyHandle; PriAxisPropertyHandle = ChildPriAxisPropertyHandle; SecAxisPropertyHandle = ChildSecAxisPropertyHandle; PositionSnapFlag = EConstraintTransformComponentFlags::ChildPosition; RotationSnapFlag = EConstraintTransformComponentFlags::ChildRotation; } if (ConstraintFrameType == EConstraintFrame::Frame2) // Parent Frame { TransformProxy = ParentTransformProxy; TransformProxy->SetFrameLabelText(LOCTEXT("Parent", "Parent")); KeyboardShortcutText = LOCTEXT("Alt", "[Alt]"); PositionPropertyHandle = ParentPositionPropertyHandle; PriAxisPropertyHandle = ParentPriAxisPropertyHandle; SecAxisPropertyHandle = ParentSecAxisPropertyHandle; PositionSnapFlag = EConstraintTransformComponentFlags::ParentPosition; RotationSnapFlag = EConstraintTransformComponentFlags::ParentRotation; } const EConstraintTransformComponentFlags SnapFlag = PositionSnapFlag | RotationSnapFlag; if (TransformProxy && PositionPropertyHandle && PriAxisPropertyHandle && SecAxisPropertyHandle) { // Create list of properties to be associated with the proxy custom rows. TArray> TransformProxyPropertyHandleList; TransformProxyPropertyHandleList.Add(ConstraintInstancePropertyHandle); FDetailWidgetRow& HeaderRow = ConstraintCat.AddCustomRow(TransformProxy->GetFrameLabelText()); HeaderRow .WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Left) .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .Text(TransformProxy->GetFrameLabelText()) .TextStyle(FAppStyle::Get(), "DetailsView.CategoryTextStyle") .ColorAndOpacity(this, &FPhysicsConstraintComponentDetails::GetConstraintTransformColorAndOpacity, SnapFlag) ] + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Right) .Padding(0.0f, 0.0f, 4.0f, 0.0f) [ SNew(STextBlock) .Text(KeyboardShortcutText) .TextStyle(FAppStyle::Get(), "DetailsView.CategoryTextStyle") .ColorAndOpacity(this, &FPhysicsConstraintComponentDetails::GetConstraintTransformColorAndOpacity, SnapFlag) ] ]; HeaderRow.PropertyHandleList(TransformProxyPropertyHandleList); HeaderRow.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FPhysicsConstraintComponentDetails::OnCopyConstraintTransform, PositionPropertyHandle, PriAxisPropertyHandle, SecAxisPropertyHandle, TransformProxy))); HeaderRow.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FPhysicsConstraintComponentDetails::OnPasteConstraintTransform, PositionPropertyHandle, PriAxisPropertyHandle, SecAxisPropertyHandle, TransformProxy))); if (HeaderRow.IsPasteFromTextBound()) { HeaderRow.OnPasteFromTextDelegate.Pin()->AddSP(this, &FPhysicsConstraintComponentDetails::OnPasteConstraintTransformFromText, PositionPropertyHandle, PriAxisPropertyHandle, SecAxisPropertyHandle, TransformProxy); } HeaderRow.OverrideResetToDefault( FResetToDefaultOverride::Create( FIsResetToDefaultVisible::CreateSP(this, &FPhysicsConstraintComponentDetails::IsSnapConstraintTransformComponentVisible, SnapFlag), FResetToDefaultHandler::CreateSP(this, &FPhysicsConstraintComponentDetails::SnapConstraintTransformComponentsToDefault, SnapFlag) )); PositionPropertyHandle->MarkHiddenByCustomization(); PriAxisPropertyHandle->MarkHiddenByCustomization(); SecAxisPropertyHandle->MarkHiddenByCustomization(); TSharedRef PositionPropertyHandleRef = PositionPropertyHandle.ToSharedRef(); TSharedRef PriAxisPropertyHandleRef = PriAxisPropertyHandle.ToSharedRef(); TSharedRef SecAxisPropertyHandleRef = SecAxisPropertyHandle.ToSharedRef(); // Create Position Row { // Create a new, empty row in the details panel. FDetailWidgetRow& ProxyRow = ConstraintCat.AddCustomRow(LOCTEXT("ConstraintPositionProxy", "ConstraintPositionProxy")); // TODO - non localised ? // Associate the constraint property with the new row. ProxyRow.PropertyHandleList(TransformProxyPropertyHandleList); // Populate the new row with widgets generated by the rotation proxy object. TransformProxy->MakePositionRow(PositionPropertyHandleRef, ProxyRow, MakeEditSpaceToggleButtonWidget(ConstraintInstancePropertyHandle, PositionSnapFlag)); // Set the behavior of the reset button for the new row, so that it snaps the orientation back to the default. ProxyRow.OverrideResetToDefault( FResetToDefaultOverride::Create( FIsResetToDefaultVisible::CreateSP(this, &FPhysicsConstraintComponentDetails::IsSnapConstraintTransformComponentVisible, PositionSnapFlag), FResetToDefaultHandler::CreateSP(this, &FPhysicsConstraintComponentDetails::SnapConstraintTransformComponentsToDefault, PositionSnapFlag) )); ProxyRow.IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsConstraintTransformComponentEnabled, SnapFlag))); } // Create Rotation Row { // Create a new, empty row in the details panel. FDetailWidgetRow& ProxyRow = ConstraintCat.AddCustomRow(LOCTEXT("ConstraintTransformProxy", "ConstraintTransformProxy")); // Associate the constraint property with the new row. ProxyRow.PropertyHandleList(TransformProxyPropertyHandleList); // Populate the new row with widgets generated by the rotation proxy object. TransformProxy->MakeRotationRow(PriAxisPropertyHandleRef, SecAxisPropertyHandleRef, ProxyRow, MakeEditSpaceToggleButtonWidget(ConstraintInstancePropertyHandle, RotationSnapFlag)); // Set the behavior of the reset button for the new row, so that it snaps the orientation back to the default. ProxyRow.OverrideResetToDefault( FResetToDefaultOverride::Create( FIsResetToDefaultVisible::CreateSP(this, &FPhysicsConstraintComponentDetails::IsSnapConstraintTransformComponentVisible, RotationSnapFlag), FResetToDefaultHandler::CreateSP(this, &FPhysicsConstraintComponentDetails::SnapConstraintTransformComponentsToDefault, RotationSnapFlag) )); ProxyRow.IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FPhysicsConstraintComponentDetails::IsConstraintTransformComponentEnabled, SnapFlag))); } } } void FPhysicsConstraintComponentDetails::SnapConstraintTransformComponentsToDefault(TSharedPtr ConstraintInstancePropertyHandle, const EConstraintTransformComponentFlags SnapFlags) { if (ParentPhysicsAsset) { if (FConstraintInstance* const ConstraintInstance = GetConstraintInstance()) { ConstraintInstance->SnapTransformsToDefault(SnapFlags, ParentPhysicsAsset); } } } bool FPhysicsConstraintComponentDetails::IsSnapConstraintTransformComponentVisible(TSharedPtr ConstraintInstancePropertyHandle, const EConstraintTransformComponentFlags SnapFlags) { bool Result = false; const FTransform ParentTransform = ParentTransformProxy->GetDefaultTransform(); const FTransform ChildTransform = ChildTransformProxy->GetDefaultTransform(); if (EnumHasAnyFlags(SnapFlags, EConstraintTransformComponentFlags::ChildPosition)) { Result |= !IsChildPropertyNearlyEqual< FVector >(ConstraintInstancePropertyHandle, GET_MEMBER_NAME_CHECKED(FConstraintInstance, Pos1), ChildTransform.GetLocation()); } if (EnumHasAnyFlags(SnapFlags, EConstraintTransformComponentFlags::ChildRotation)) { Result |= !(IsChildPropertyNearlyEqual< FVector >(ConstraintInstancePropertyHandle, GET_MEMBER_NAME_CHECKED(FConstraintInstance, PriAxis1), ChildTransform.GetUnitAxis(EAxis::X)) && IsChildPropertyNearlyEqual< FVector >(ConstraintInstancePropertyHandle, GET_MEMBER_NAME_CHECKED(FConstraintInstance, SecAxis1), ChildTransform.GetUnitAxis(EAxis::Y))); } if (EnumHasAnyFlags(SnapFlags, EConstraintTransformComponentFlags::ParentPosition)) { Result |= !IsChildPropertyNearlyEqual< FVector >(ConstraintInstancePropertyHandle, GET_MEMBER_NAME_CHECKED(FConstraintInstance, Pos2), ParentTransform.GetLocation()); } if (EnumHasAnyFlags(SnapFlags, EConstraintTransformComponentFlags::ParentRotation)) { Result |= !(IsChildPropertyNearlyEqual< FVector >(ConstraintInstancePropertyHandle, GET_MEMBER_NAME_CHECKED(FConstraintInstance, PriAxis2), ParentTransform.GetUnitAxis(EAxis::X)) && IsChildPropertyNearlyEqual< FVector >(ConstraintInstancePropertyHandle, GET_MEMBER_NAME_CHECKED(FConstraintInstance, SecAxis2), ParentTransform.GetUnitAxis(EAxis::Y))); } return Result; } void FPhysicsConstraintComponentDetails::ToggleDisplayConstraintTransformComponentRelativeToDefault(TSharedPtr ConstraintInstancePropertyHandle, const EConstraintTransformComponentFlags ComponentFlags) { if (ParentPhysicsAsset) { IPhysicsAssetRenderInterface& PhysicsAssetRenderInterface = IModularFeatures::Get().GetModularFeature(IPhysicsAssetRenderInterface::GetModularFeatureName()); const bool bCurrentState = IsDisplayingConstraintTransformComponentRelativeToDefault(ComponentFlags); const EConstraintTransformComponentFlags ComponentFlagsToModify = (FSlateApplication::Get().GetModifierKeys().IsShiftDown()) ? EConstraintTransformComponentFlags::All : ComponentFlags; PhysicsAssetRenderInterface.SetDisplayConstraintTransformComponentRelativeToDefault(ParentPhysicsAsset, ComponentFlagsToModify, !bCurrentState); UpdateTransformProxyDisplayRelativeToDefault(ConstraintInstancePropertyHandle); } } bool FPhysicsConstraintComponentDetails::IsConstraintTransformComponentEnabled(const EConstraintTransformComponentFlags ComponentFlags) const { if (ParentPhysicsAsset) { IPhysicsAssetRenderInterface& PhysicsAssetRenderInterface = IModularFeatures::Get().GetModularFeature(IPhysicsAssetRenderInterface::GetModularFeatureName()); const EConstraintTransformComponentFlags ComponentManipulationFlags = PhysicsAssetRenderInterface.GetConstraintViewportManipulationFlags(ParentPhysicsAsset); return EnumHasAllFlags(ComponentManipulationFlags, ComponentFlags); } return true; } FSlateColor FPhysicsConstraintComponentDetails::GetConstraintTransformColorAndOpacity(const EConstraintTransformComponentFlags ComponentFlags) const { return IsConstraintTransformComponentEnabled(ComponentFlags) ? FSlateColor::UseForeground() : FSlateColor::UseSubduedForeground(); } void FPhysicsConstraintComponentDetails::UpdateTransformProxyDisplayRelativeToDefault(TSharedPtr ConstraintInstancePropertyHandle) { if (ParentPhysicsAsset) { IPhysicsAssetRenderInterface& PhysicsAssetRenderInterface = IModularFeatures::Get().GetModularFeature(IPhysicsAssetRenderInterface::GetModularFeatureName()); ChildTransformProxy->SetPositionDisplayRelativeToDefault(PhysicsAssetRenderInterface.IsDisplayingConstraintTransformComponentRelativeToDefault(ParentPhysicsAsset, EConstraintTransformComponentFlags::ChildPosition)); ChildTransformProxy->SetRotationDisplayRelativeToDefault(PhysicsAssetRenderInterface.IsDisplayingConstraintTransformComponentRelativeToDefault(ParentPhysicsAsset, EConstraintTransformComponentFlags::ChildRotation)); ParentTransformProxy->SetPositionDisplayRelativeToDefault(PhysicsAssetRenderInterface.IsDisplayingConstraintTransformComponentRelativeToDefault(ParentPhysicsAsset, EConstraintTransformComponentFlags::ParentPosition)); ParentTransformProxy->SetRotationDisplayRelativeToDefault(PhysicsAssetRenderInterface.IsDisplayingConstraintTransformComponentRelativeToDefault(ParentPhysicsAsset, EConstraintTransformComponentFlags::ParentRotation)); if (FConstraintInstance* const ConstraintInstance = GetConstraintInstance()) { ChildTransformProxy->SetDefaultTransform(ConstraintInstance->CalculateDefaultChildTransform(), ChildPositionPropertyHandle, ChildPriAxisPropertyHandle, ChildSecAxisPropertyHandle); ParentTransformProxy->SetDefaultTransform(ConstraintInstance->CalculateDefaultParentTransform(ParentPhysicsAsset), ParentPositionPropertyHandle, ParentPriAxisPropertyHandle, ParentSecAxisPropertyHandle); } } } bool FPhysicsConstraintComponentDetails::IsDisplayingConstraintTransformComponentRelativeToDefault(const EConstraintTransformComponentFlags ComponentFlags) { if (ParentPhysicsAsset) { return IModularFeatures::Get().GetModularFeature(IPhysicsAssetRenderInterface::GetModularFeatureName()).IsDisplayingConstraintTransformComponentRelativeToDefault(ParentPhysicsAsset, ComponentFlags); } return false; } void FPhysicsConstraintComponentDetails::OnCopyConstraintTransform(TSharedPtr PositionPropertyHandle, TSharedPtr PriAxisPropertyHandle, TSharedPtr SecAxisPropertyHandle, TSharedPtr TransformProxy) { if (TransformProxy.IsValid()) { TransformProxy->OnCopy(PositionPropertyHandle, PriAxisPropertyHandle, SecAxisPropertyHandle); } } void FPhysicsConstraintComponentDetails::OnPasteConstraintTransform(TSharedPtr PositionPropertyHandle, TSharedPtr PriAxisPropertyHandle, TSharedPtr SecAxisPropertyHandle, TSharedPtr TransformProxy) { FString PastedText; FPlatformApplicationMisc::ClipboardPaste(PastedText); PasteConstraintTransformFromText(TEXT(""), PastedText, PositionPropertyHandle, PriAxisPropertyHandle, SecAxisPropertyHandle, TransformProxy); } void FPhysicsConstraintComponentDetails::OnPasteConstraintTransformFromText( const FString& InTag, const FString& InText, const TOptional& InOperationId, TSharedPtr PositionPropertyHandle, TSharedPtr PriAxisPropertyHandle, TSharedPtr SecAxisPropertyHandle, TSharedPtr TransformProxy) { if (TransformProxy.IsValid()) { TransformProxy->OnPasteFromText(InTag, InText, InOperationId, PositionPropertyHandle, PriAxisPropertyHandle, SecAxisPropertyHandle); } } void FPhysicsConstraintComponentDetails::PasteConstraintTransformFromText( const FString& InTag, const FString& InText, TSharedPtr PositionPropertyHandle, TSharedPtr PriAxisPropertyHandle, TSharedPtr SecAxisPropertyHandle, TSharedPtr TransformProxy) { OnPasteConstraintTransformFromText(InTag, InText, {}, PositionPropertyHandle, PriAxisPropertyHandle, SecAxisPropertyHandle, TransformProxy); } 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)); TSharedPtr AccelerationModeProperty = ProfilePropertiesProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLinearDriveConstraint, bAccelerationMode)); LinearMotorCat.AddProperty(AccelerationModeProperty); 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()).ToolTipText(StiffnessXProperty->GetToolTipText()) ] .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("Damping", "Damping")) .Font(IDetailLayoutBuilder::GetDetailFont()).ToolTipText(XDampingProperty->GetToolTipText()) ] .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 AccelerationModeProperty = AngularDriveProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAngularDriveConstraint, bAccelerationMode)); 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); AngularMotorCat.AddProperty(AccelerationModeProperty); 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()).ToolTipText(StiffnessSlerpProperty->GetToolTipText()) ] .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("Damping", "Damping")) .Font(IDetailLayoutBuilder::GetDetailFont()).ToolTipText(DampingSlerpProperty->GetToolTipText()) ] .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 ConstraintInstanceProperty; APhysicsConstraintActor* OwningConstraintActor = NULL; ParentPhysicsAsset = NULL; bInPhat = false; for (int32 i=0; i < Objects.Num(); ++i) { if (!Objects[i].IsValid()) { continue; } if (Objects[i]->IsA(UPhysicsConstraintTemplate::StaticClass())) { ConstraintInstanceProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPhysicsConstraintTemplate, DefaultInstance)); ConstraintTemplate = Cast(Objects[i].Get()); ParentPhysicsAsset = Cast(Objects[i]->GetOuter()); bInPhat = true; break; } else if (Objects[i]->IsA(UPhysicsConstraintComponent::StaticClass())) { ConstraintInstanceProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UPhysicsConstraintComponent, ConstraintInstance)); ConstraintComp = (UPhysicsConstraintComponent*)Objects[i].Get(); OwningConstraintActor = Cast(ConstraintComp->GetOwner()); break; } } AddConstraintProperties(DetailBuilder, ConstraintInstanceProperty, Objects); DetailBuilder.EditCategory("Constraint Behavior"); //Create this category first so it's at the top TSharedPtr ProfileInstance = ConstraintInstanceProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FConstraintInstance, ProfileInstance)); AddLinearLimits(DetailBuilder, ConstraintInstanceProperty, ProfileInstance); AddAngularLimits(DetailBuilder, ConstraintInstanceProperty, ProfileInstance); AddLinearDrive(DetailBuilder, ConstraintInstanceProperty, ProfileInstance); AddAngularDrive(DetailBuilder, ConstraintInstanceProperty, ProfileInstance); AddConstraintBehaviorProperties(DetailBuilder, ConstraintInstanceProperty, ProfileInstance); //Now we've added all the complex UI, just dump the rest into Constraint category } 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); } } FConstraintInstance* FPhysicsConstraintComponentDetails::GetConstraintInstance() { FConstraintInstance* ConstraintInstance = nullptr; if (ConstraintTemplate) { ConstraintInstance = &ConstraintTemplate->DefaultInstance; } else if (ConstraintComp) { ConstraintInstance = &ConstraintComp->ConstraintInstance; } return ConstraintInstance; } #undef LOCTEXT_NAMESPACE