// Copyright Epic Games, Inc. All Rights Reserved. #include "PhysicsAssetEditor.h" #include "Framework/MultiBox/MultiBox.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Notifications/NotificationManager.h" #include "EngineGlobals.h" #include "Engine/SkeletalMesh.h" #include "Animation/AnimationAsset.h" #include "Animation/AnimSequence.h" #include "Engine/StaticMesh.h" #include "Editor.h" #include "Misc/MessageDialog.h" #include "Modules/ModuleManager.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/SRichTextBlock.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" #include "Styling/AppStyle.h" #include "Preferences/PhysicsAssetEditorOptions.h" #include "PhysicalMaterials/PhysicalMaterial.h" #include "PhysicsAssetEditorModule.h" #include "ScopedTransaction.h" #include "PhysicsAssetEditorActions.h" #include "PhysicsAssetRenderUtils.h" #include "PhysicsAssetEditorSkeletalMeshComponent.h" #include "Templates/TypeHash.h" #include "PropertyEditorModule.h" #include "IDetailsView.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" #include "WorkflowOrientedApp/SContentReference.h" #include "MeshUtilities.h" #include "MeshUtilitiesCommon.h" #include "EngineAnalytics.h" #include "Runtime/Analytics/Analytics/Public/AnalyticsEventAttribute.h" #include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h" #include "Widgets/Docking/SDockTab.h" #include "PhysicsEngine/ConvexElem.h" #include "PhysicsEngine/BoxElem.h" #include "PhysicsEngine/SphereElem.h" #include "PhysicsEngine/SphylElem.h" #include "PhysicsEngine/TaperedCapsuleElem.h" #include "PhysicsEngine/BodySetup.h" #include "PhysicsEngine/PhysicsConstraintTemplate.h" #include "PhysicsEngine/ConstraintUtils.h" #include "PhysicsEngine/PhysicsAsset.h" #include "Engine/Selection.h" #include "PersonaModule.h" #include "PhysicsAssetEditorAnimInstance.h" #include "PhysicsAssetEditorAnimInstanceProxy.h" #include "PhysicsAssetEditorMode.h" #include "PersonaModule.h" #include "IAssetFamily.h" #include "ISkeletonEditorModule.h" #include "IPersonaToolkit.h" #include "IPersonaPreviewScene.h" #include "PhysicsAssetEditorSkeletonTreeBuilder.h" #include "BoneProxy.h" #include "SPhysicsAssetGraph.h" #include "PhysicsAssetEditorEditMode.h" #include "AssetEditorModeManager.h" #include "PhysicsAssetEditorPhysicsHandleComponent.h" #include "ISkeletonTreeItem.h" #include "Algo/Transform.h" #include "SkeletonTreeSelection.h" #include "SkeletonTreePhysicsBodyItem.h" #include "SkeletonTreePhysicsShapeItem.h" #include "SkeletonTreePhysicsConstraintItem.h" #include "Misc/ScopedSlowTask.h" #include "PhysicsAssetGenerationSettings.h" #include "Framework/Commands/GenericCommands.h" #include "UICommandList_Pinnable.h" #include "IPinnedCommandList.h" #include "Widgets/Input/SSpinBox.h" #include "Widgets/Input/SNumericEntryBox.h" #include "AnimationEditorPreviewActor.h" #include "Preferences/PersonaOptions.h" const FName PhysicsAssetEditorModes::PhysicsAssetEditorMode("PhysicsAssetEditorMode"); const FName PhysicsAssetEditorAppIdentifier = FName(TEXT("PhysicsAssetEditorApp")); DEFINE_LOG_CATEGORY(LogPhysicsAssetEditor); #define LOCTEXT_NAMESPACE "PhysicsAssetEditor" //PRAGMA_DISABLE_OPTIMIZATION namespace PhysicsAssetEditor { const float DefaultPrimSize = 15.0f; const float DuplicateXOffset = 10.0f; /** contain everything to identify a shape uniquely - used for synchronizing selection mostly */ struct FShapeData { int32 Index; int32 PrimitiveIndex; EAggCollisionShape::Type PrimitiveType; FShapeData(int32 Index, int32 PrimitiveIndex, EAggCollisionShape::Type PrimitiveType) : Index(Index) , PrimitiveIndex(PrimitiveIndex) , PrimitiveType(PrimitiveType) { } bool operator==(const FShapeData& rhs) const { return Index == rhs.Index && PrimitiveIndex == rhs.PrimitiveIndex && PrimitiveType == rhs.PrimitiveType; } friend uint32 GetTypeHash(const FShapeData& ShapeData) { return HashCombine( HashCombine( ::GetTypeHash(ShapeData.Index), ::GetTypeHash(ShapeData.PrimitiveIndex)), ::GetTypeHash(ShapeData.PrimitiveType)); } }; } void FPhysicsAssetEditor::RegisterTabSpawners(const TSharedRef& InTabManager) { WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_PhysicsAssetEditor", "PhysicsAssetEditor")); FAssetEditorToolkit::RegisterTabSpawners(InTabManager); } FPhysicsAssetEditor::~FPhysicsAssetEditor() { if( SharedData->bRunningSimulation ) { // Disable simulation when shutting down ImpToggleSimulation(); } GEditor->UnregisterForUndo(this); GEditor->GetEditorSubsystem()->OnAssetReimport.Remove(OnAssetReimportDelegateHandle); if (PersonaToolkit.IsValid()) { constexpr bool bSetPreviewMeshInAsset = false; PersonaToolkit->SetPreviewMesh(nullptr, bSetPreviewMeshInAsset); } } static void FillAddShapeMenu(FMenuBuilder& InSubMenuBuilder) { const FPhysicsAssetEditorCommands& PhysicsAssetEditorCommands = FPhysicsAssetEditorCommands::Get(); InSubMenuBuilder.BeginSection("ShapeTypeHeader", LOCTEXT("ShapeTypeHeader", "Shape Type")); InSubMenuBuilder.AddMenuEntry( PhysicsAssetEditorCommands.AddBox ); InSubMenuBuilder.AddMenuEntry( PhysicsAssetEditorCommands.AddSphere ); InSubMenuBuilder.AddMenuEntry( PhysicsAssetEditorCommands.AddSphyl ); InSubMenuBuilder.AddMenuEntry( PhysicsAssetEditorCommands.AddTaperedCapsule ); InSubMenuBuilder.EndSection(); } void FPhysicsAssetEditor::InitPhysicsAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UPhysicsAsset* ObjectToEdit) { SelectedSimulation = false; SharedData = MakeShareable(new FPhysicsAssetEditorSharedData()); SharedData->SelectionChangedEvent.AddRaw(this, &FPhysicsAssetEditor::HandleViewportSelectionChanged); SharedData->HierarchyChangedEvent.AddRaw(this, &FPhysicsAssetEditor::RefreshHierachyTree); SharedData->PreviewChangedEvent.AddRaw(this, &FPhysicsAssetEditor::RefreshPreviewViewport); SharedData->PhysicsAsset = ObjectToEdit; SharedData->CachePreviewMesh(); FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked("Persona"); FPersonaToolkitArgs PersonaToolkitArgs; PersonaToolkitArgs.OnPreviewSceneCreated = FOnPreviewSceneCreated::FDelegate::CreateSP(this, &FPhysicsAssetEditor::HandlePreviewSceneCreated); PersonaToolkit = PersonaModule.CreatePersonaToolkit(SharedData->PhysicsAsset, PersonaToolkitArgs); TSharedRef AssetFamily = PersonaModule.CreatePersonaAssetFamily(ObjectToEdit); AssetFamily->RecordAssetOpened(FAssetData(ObjectToEdit)); FSkeletonTreeArgs SkeletonTreeArgs; SkeletonTreeArgs.OnSelectionChanged = FOnSkeletonTreeSelectionChanged::CreateSP(this, &FPhysicsAssetEditor::HandleSelectionChanged); SkeletonTreeArgs.PreviewScene = PersonaToolkit->GetPreviewScene(); SkeletonTreeArgs.bShowBlendProfiles = false; SkeletonTreeArgs.bShowDebugVisualizationOptions = true; SkeletonTreeArgs.bAllowMeshOperations = false; SkeletonTreeArgs.bAllowSkeletonOperations = false; SkeletonTreeArgs.bHideBonesByDefault = true; SkeletonTreeArgs.OnGetFilterText = FOnGetFilterText::CreateSP(this, &FPhysicsAssetEditor::HandleGetFilterLabel); SkeletonTreeArgs.Extenders = MakeShared(); SkeletonTreeArgs.Extenders->AddMenuExtension("FilterOptions", EExtensionHook::After, GetToolkitCommands(), FMenuExtensionDelegate::CreateSP(this, &FPhysicsAssetEditor::HandleExtendFilterMenu)); SkeletonTreeArgs.Extenders->AddMenuExtension("SkeletonTreeContextMenu", EExtensionHook::After, GetToolkitCommands(), FMenuExtensionDelegate::CreateSP(this, &FPhysicsAssetEditor::HandleExtendContextMenu)); SkeletonTreeArgs.Extenders->AddMenuExtension("CreateNew", EExtensionHook::After, GetToolkitCommands(), FMenuExtensionDelegate::CreateStatic( &FillAddShapeMenu)); SkeletonTreeArgs.Builder = SkeletonTreeBuilder = MakeShared(ObjectToEdit); SkeletonTreeArgs.ContextName = GetToolkitFName(); ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::GetModuleChecked("SkeletonEditor"); GetMutableDefault()->bFlattenSkeletonHierarchyWhenFiltering = false; GetMutableDefault()->bHideParentsWhenFiltering = true; SkeletonTree = SkeletonEditorModule.CreateSkeletonTree(PersonaToolkit->GetSkeleton(), SkeletonTreeArgs); bSelecting = false; GEditor->RegisterForUndo(this); // If any assets we care about get reimported, we need to rebuild some stuff OnAssetReimportDelegateHandle = GEditor->GetEditorSubsystem()->OnAssetReimport.AddSP(this, &FPhysicsAssetEditor::OnAssetReimport); // Register our commands. This will only register them if not previously registered FPhysicsAssetEditorCommands::Register(); BindCommands(); const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, PhysicsAssetEditorAppIdentifier, FTabManager::FLayout::NullLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectToEdit); AddApplicationMode( PhysicsAssetEditorModes::PhysicsAssetEditorMode, MakeShareable(new FPhysicsAssetEditorMode(SharedThis(this), SkeletonTree.ToSharedRef(), PersonaToolkit->GetPreviewScene()))); SetCurrentMode(PhysicsAssetEditorModes::PhysicsAssetEditorMode); // Force disable simulation as InitArticulated can be called during viewport creation SharedData->EnableSimulation(false); GetEditorModeManager().SetDefaultMode(FPhysicsAssetEditorEditMode::ModeName); GetEditorModeManager().ActivateMode(FPersonaEditModes::SkeletonSelection); GetEditorModeManager().ActivateMode(FPhysicsAssetEditorEditMode::ModeName); static_cast(GetEditorModeManager().GetActiveMode(FPhysicsAssetEditorEditMode::ModeName))->SetSharedData(SharedThis(this), *SharedData.Get()); IPhysicsAssetEditorModule* PhysicsAssetEditorModule = &FModuleManager::LoadModuleChecked( "PhysicsAssetEditor" ); ExtendMenu(); ExtendToolbar(); RegenerateMenusAndToolbars(); } TSharedPtr FPhysicsAssetEditor::GetSharedData() const { return SharedData; } void FPhysicsAssetEditor::HandleViewportSelectionChanged(const TArray& InSelectedBodies, const TArray& InSelectedConstraints) { if (!bSelecting) { TGuardValue RecursionGuard(bSelecting, true); if (SkeletonTree.IsValid()) { SkeletonTree->DeselectAll(); } if(InSelectedBodies.Num() == 0 && InSelectedConstraints.Num() == 0) { if (PhysAssetProperties.IsValid()) { PhysAssetProperties->SetObject(nullptr); } if (PhysicsAssetGraph.IsValid()) { PhysicsAssetGraph.Pin()->SelectObjects(TArray(), TArray()); } } else { // let's store all the selection in sets so that when we go through the list of items in the list // ( which can be long ) we only do O(1) lookup for each of them TSet Objects; TSet Bodies; TSet Constraints; TSet Shapes; Algo::Transform(InSelectedBodies, Objects, [this](const FPhysicsAssetEditorSharedData::FSelection& InItem) { return SharedData->PhysicsAsset->SkeletalBodySetups[InItem.Index]; }); Algo::Transform(InSelectedConstraints, Objects, [this](const FPhysicsAssetEditorSharedData::FSelection& InItem) { return SharedData->PhysicsAsset->ConstraintSetup[InItem.Index]; }); Algo::Transform(InSelectedBodies, Bodies, [this](const FPhysicsAssetEditorSharedData::FSelection& InItem) { return SharedData->PhysicsAsset->SkeletalBodySetups[InItem.Index]; }); Algo::Transform(InSelectedConstraints, Constraints, [this](const FPhysicsAssetEditorSharedData::FSelection& InItem) { return SharedData->PhysicsAsset->ConstraintSetup[InItem.Index]; }); // NOTE: Does selecting a constraint also select a shape? // If not then this is not needed. Algo::Transform(InSelectedConstraints, Shapes, [this](const FPhysicsAssetEditorSharedData::FSelection& InItem) { return PhysicsAssetEditor::FShapeData(InItem.Index, InItem.PrimitiveIndex, InItem.PrimitiveType); }); Algo::Transform(InSelectedBodies, Shapes, [this](const FPhysicsAssetEditorSharedData::FSelection& InItem) { return PhysicsAssetEditor::FShapeData(InItem.Index, InItem.PrimitiveIndex, InItem.PrimitiveType); }); if (PhysAssetProperties.IsValid()) { PhysAssetProperties->SetObjects(Objects.Array()); } if (SkeletonTree.IsValid()) { SkeletonTree->SelectItemsBy([this, &Objects, &Constraints, &Bodies, &Shapes](const TSharedRef& InItem, bool& bInOutExpand) { if (InItem->IsOfType()) { const USkeletalBodySetup* BodySetup = Cast(InItem->GetObject()); if (Bodies.Contains(BodySetup)) { bInOutExpand = true; return true; } } else if (InItem->IsOfType()) { TSharedRef ShapeItem = StaticCastSharedRef(InItem); PhysicsAssetEditor::FShapeData ShapeData(ShapeItem->GetBodySetupIndex(), ShapeItem->GetShapeIndex(), ShapeItem->GetShapeType()); if (Shapes.Contains(ShapeData)) { bInOutExpand = true; return true; } } else if (InItem->IsOfType()) { const UPhysicsConstraintTemplate* Constraint = Cast(InItem->GetObject()); if (Constraints.Contains(Constraint)) { bInOutExpand = true; return true; } } return false; }); } if (PhysicsAssetGraph.IsValid()) { PhysicsAssetGraph.Pin()->SelectObjects(Bodies.Array(), Constraints.Array()); } } } } void FPhysicsAssetEditor::RefreshHierachyTree() { if(SkeletonTree.IsValid()) { SkeletonTree->Refresh(); } } void FPhysicsAssetEditor::RefreshPreviewViewport() { if (PersonaToolkit.IsValid()) { PersonaToolkit->GetPreviewScene()->InvalidateViews(); } } FName FPhysicsAssetEditor::GetToolkitFName() const { return FName("PhysicsAssetEditor"); } FText FPhysicsAssetEditor::GetBaseToolkitName() const { return LOCTEXT("AppLabel", "Physics Asset Editor"); } FString FPhysicsAssetEditor::GetWorldCentricTabPrefix() const { return LOCTEXT( "WorldCentricTabPrefix", "Physics Asset Editor ").ToString(); } FLinearColor FPhysicsAssetEditor::GetWorldCentricTabColorScale() const { return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f); } void FPhysicsAssetEditor::OnClose() { // Clear render settings from editor viewport. These settings must be applied to the rendering in all editors // when an asset is open in the Physics Asset Editor but should not persist after the editor has been closed. if (FPhysicsAssetRenderSettings* const RenderSettings = UPhysicsAssetRenderUtilities::GetSettings(SharedData->PhysicsAsset)) { RenderSettings->ResetEditorViewportOptions(); } if (UPhysicsAssetRenderUtilities* PhysicsAssetRenderUtilities = GetMutableDefault()) { PhysicsAssetRenderUtilities->SaveConfig(); } IPhysicsAssetEditor::OnClose(); } void FPhysicsAssetEditor::AddReferencedObjects(FReferenceCollector& Collector) { SharedData->AddReferencedObjects(Collector); } void FPhysicsAssetEditor::PostUndo(bool bSuccess) { OnPostUndo.Broadcast(); SharedData->PostUndo(); RefreshHierachyTree(); SharedData->RefreshPhysicsAssetChange(SharedData->PhysicsAsset); } void FPhysicsAssetEditor::PostRedo( bool bSuccess ) { OnPostUndo.Broadcast(); for (int32 BodyIdx=0; BodyIdx < SharedData->PhysicsAsset->SkeletalBodySetups.Num(); ++BodyIdx) { UBodySetup* Body = SharedData->PhysicsAsset->SkeletalBodySetups[BodyIdx]; bool bRecreate = false; for (int32 ElemIdx=0; ElemIdx < Body->AggGeom.ConvexElems.Num(); ++ElemIdx) { FKConvexElem& Element = Body->AggGeom.ConvexElems[ElemIdx]; if (Element.GetChaosConvexMesh() == NULL) { bRecreate = true; break; } } if (bRecreate) { Body->InvalidatePhysicsData(); Body->CreatePhysicsMeshes(); } } PostUndo(bSuccess); } void FPhysicsAssetEditor::OnAssetReimport(UObject* Object) { RecreatePhysicsState(); RefreshHierachyTree(); RefreshPreviewViewport(); if (SharedData->EditorSkelComp && SharedData->EditorSkelComp->SkeletalMesh) { IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked("MeshUtilities"); // Update various infos based on the mesh MeshUtilities.CalcBoneVertInfos(SharedData->EditorSkelComp->SkeletalMesh, SharedData->DominantWeightBoneInfos, true); MeshUtilities.CalcBoneVertInfos(SharedData->EditorSkelComp->SkeletalMesh, SharedData->AnyWeightBoneInfos, false); } } void FPhysicsAssetEditor::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) { // if simulating ignore update request if (SharedData->bRunningSimulation) { return; } FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None; // Update bounds bodies and setup when bConsiderForBounds was changed if (PropertyName == GET_MEMBER_NAME_CHECKED(UBodySetup, bConsiderForBounds)) { SharedData->PhysicsAsset->UpdateBoundsBodiesArray(); SharedData->PhysicsAsset->UpdateBodySetupIndexMap(); } // if we updated the array of shapes we should make sure we update the selection if (PropertyName == GET_MEMBER_NAME_CHECKED(UBodySetup, AggGeom)) { // reselect all the bodies that were selected when the array changed, because selection keeps a primitive type that may have changed since TArray SelectedBodyIndices; for (const FPhysicsAssetEditorSharedData::FSelection& selectedBody : SharedData->SelectedBodies) { SelectedBodyIndices.AddUnique(selectedBody.Index); } { // bulk update FScopedBulkSelection BulkSelection(SharedData); SharedData->ClearSelectedBody(); SharedData->SetSelectedBodiesAnyPrim(SelectedBodyIndices, true); } } if (UPhysicsAssetRenderUtilities* PhysicsAssetRenderUtilities = GetMutableDefault()) { PhysicsAssetRenderUtilities->SaveConfig(); } RecreatePhysicsState(); RefreshPreviewViewport(); } FText FPhysicsAssetEditor::GetRepeatLastSimulationToolTip() const { if(SelectedSimulation) { return FPhysicsAssetEditorCommands::Get().SelectedSimulation->GetDescription(); } else { if(SharedData->bNoGravitySimulation) { return FPhysicsAssetEditorCommands::Get().SimulationNoGravity->GetDescription(); } else { return FPhysicsAssetEditorCommands::Get().SimulationAll->GetDescription(); } } } FSlateIcon FPhysicsAssetEditor::GetRepeatLastSimulationIcon() const { if(SelectedSimulation) { return FPhysicsAssetEditorCommands::Get().SelectedSimulation->GetIcon(); } else { if(SharedData->bNoGravitySimulation) { return FPhysicsAssetEditorCommands::Get().SimulationNoGravity->GetIcon(); } else { return FPhysicsAssetEditorCommands::Get().SimulationAll->GetIcon(); } } } void FPhysicsAssetEditor::ExtendToolbar() { struct Local { static TSharedRef< SWidget > FillSimulateOptions(TSharedRef InCommandList) { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, InCommandList ); const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); //selected simulation MenuBuilder.BeginSection("Simulation", LOCTEXT("SimulationHeader", "Simulation")); { MenuBuilder.AddMenuEntry(Commands.SimulationAll); MenuBuilder.AddMenuEntry(Commands.SelectedSimulation); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("SimulationOptions", LOCTEXT("SimulationOptionsHeader", "Simulation Options")); { MenuBuilder.AddMenuEntry(Commands.SimulationNoGravity); MenuBuilder.AddMenuEntry(Commands.SimulationFloorCollision); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } static void FillToolbar(FToolBarBuilder& ToolbarBuilder, TSharedPtr SharedData, FPhysicsAssetEditor * PhysicsAssetEditor ) { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); TSharedRef InCommandList = PhysicsAssetEditor->GetToolkitCommands(); FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked("Persona"); FPersonaModule::FCommonToolbarExtensionArgs Args; Args.bReferencePose = true; PersonaModule.AddCommonToolbarExtensions(ToolbarBuilder, PhysicsAssetEditor->PersonaToolkit.ToSharedRef(), Args); ToolbarBuilder.BeginSection("PhysicsAssetEditorBodyTools"); { ToolbarBuilder.AddToolBarButton(Commands.EnableCollision); ToolbarBuilder.AddToolBarButton(Commands.DisableCollision); ToolbarBuilder.AddComboButton( FUIAction(FExecuteAction(), FCanExecuteAction::CreateSP(PhysicsAssetEditor, &FPhysicsAssetEditor::IsNotSimulation)), FOnGetContent::CreateLambda([PhysicsAssetEditor]() { return PhysicsAssetEditor->BuildPhysicalMaterialAssetPicker(true); }), Commands.ApplyPhysicalMaterial->GetLabel(), Commands.ApplyPhysicalMaterial->GetDescription(), Commands.ApplyPhysicalMaterial->GetIcon() ); } ToolbarBuilder.EndSection(); ToolbarBuilder.BeginSection("PhysicsAssetEditorConstraintTools"); { ToolbarBuilder.AddToolBarButton(Commands.ConvertToBallAndSocket); ToolbarBuilder.AddToolBarButton(Commands.ConvertToHinge); ToolbarBuilder.AddToolBarButton(Commands.ConvertToPrismatic); ToolbarBuilder.AddToolBarButton(Commands.ConvertToSkeletal); } ToolbarBuilder.EndSection(); ToolbarBuilder.BeginSection("PhysicsAssetEditorSimulation"); { // Simulate ToolbarBuilder.AddToolBarButton( Commands.RepeatLastSimulation, NAME_None, LOCTEXT("RepeatLastSimulation","Simulate"), TAttribute< FText >::Create( TAttribute< FText >::FGetter::CreateSP( PhysicsAssetEditor, &FPhysicsAssetEditor::GetRepeatLastSimulationToolTip) ), TAttribute< FSlateIcon >::Create( TAttribute< FSlateIcon >::FGetter::CreateSP( PhysicsAssetEditor, &FPhysicsAssetEditor::GetRepeatLastSimulationIcon) ) ); //simulate mode combo FUIAction SimulationMode; SimulationMode.CanExecuteAction = FCanExecuteAction::CreateSP(PhysicsAssetEditor, &FPhysicsAssetEditor::IsNotSimulation); { ToolbarBuilder.AddComboButton( SimulationMode, FOnGetContent::CreateStatic( &FillSimulateOptions, InCommandList ), LOCTEXT( "SimulateCombo_Label", "Simulate Options" ), LOCTEXT( "SimulateComboToolTip", "Options for Simulation" ), FSlateIcon(), true ); } } ToolbarBuilder.EndSection(); } }; // If the ToolbarExtender is valid, remove it before rebuilding it if ( ToolbarExtender.IsValid() ) { RemoveToolbarExtender( ToolbarExtender ); ToolbarExtender.Reset(); } ToolbarExtender = MakeShareable(new FExtender); ToolbarExtender->AddToolBarExtension( "Asset", EExtensionHook::After, GetToolkitCommands(), FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar, SharedData, this) ); AddToolbarExtender(ToolbarExtender); IPhysicsAssetEditorModule* PhysicsAssetEditorModule = &FModuleManager::LoadModuleChecked( "PhysicsAssetEditor" ); AddToolbarExtender(PhysicsAssetEditorModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); ToolbarExtender->AddToolBarExtension( "Asset", EExtensionHook::After, GetToolkitCommands(), FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ParentToolbarBuilder) { FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked("Persona"); TSharedRef AssetFamily = PersonaModule.CreatePersonaAssetFamily(SharedData->PhysicsAsset); AddToolbarWidget(PersonaModule.CreateAssetFamilyShortcutWidget(SharedThis(this), AssetFamily)); } )); } void FPhysicsAssetEditor::ExtendMenu() { struct Local { static void FillEdit( FMenuBuilder& MenuBarBuilder ) { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); MenuBarBuilder.BeginSection("Selection", LOCTEXT("PhatEditSelection", "Selection")); MenuBarBuilder.AddMenuEntry(Commands.SelectAllBodies); MenuBarBuilder.AddMenuEntry(Commands.SelectSimulatedBodies); MenuBarBuilder.AddMenuEntry(Commands.SelectKinematicBodies); MenuBarBuilder.AddMenuEntry(Commands.SelectAllConstraints); MenuBarBuilder.AddMenuEntry(Commands.ToggleSelectionType); MenuBarBuilder.AddMenuEntry(Commands.ToggleSelectionTypeWithUserConstraints); MenuBarBuilder.AddMenuEntry(Commands.ToggleShowSelected); MenuBarBuilder.AddMenuEntry(Commands.ShowSelected); MenuBarBuilder.AddMenuEntry(Commands.HideSelected); MenuBarBuilder.AddMenuEntry(Commands.ToggleShowOnlySelected); MenuBarBuilder.AddMenuEntry(Commands.ToggleShowOnlyColliding); MenuBarBuilder.AddMenuEntry(Commands.ToggleShowOnlyConstrained); MenuBarBuilder.AddMenuEntry(Commands.ShowAll); MenuBarBuilder.AddMenuEntry(Commands.HideAll); MenuBarBuilder.AddMenuEntry(Commands.DeselectAll); MenuBarBuilder.EndSection(); } }; MenuExtender = MakeShareable(new FExtender); MenuExtender->AddMenuExtension( "EditHistory", EExtensionHook::After, GetToolkitCommands(), FMenuExtensionDelegate::CreateStatic( &Local::FillEdit ) ); AddMenuExtender(MenuExtender); IPhysicsAssetEditorModule* PhysicsAssetEditorModule = &FModuleManager::LoadModuleChecked( "PhysicsAssetEditor" ); AddMenuExtender(PhysicsAssetEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); } void FPhysicsAssetEditor::BindCommands() { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); ToolkitCommands->MapAction( Commands.RegenerateBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::ResetBoneCollision), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.AddBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::ResetBoneCollision), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.CopyProperties, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCopyProperties), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanCopyProperties), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCopyProperties)); ToolkitCommands->MapAction( Commands.PasteProperties, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnPasteProperties), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanPasteProperties)); ToolkitCommands->MapAction( Commands.CopyBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCopyBodies), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanCopyBodies), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCopyBodies)); ToolkitCommands->MapAction( Commands.PasteBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnPasteBodies), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanPasteBodies)); ToolkitCommands->MapAction( Commands.RepeatLastSimulation, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnRepeatLastSimulation), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsToggleSimulation)); ToolkitCommands->MapAction( Commands.SimulationNoGravity, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleSimulationNoGravity), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsNoGravitySimulationEnabled)); ToolkitCommands->MapAction( Commands.SimulationFloorCollision, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleSimulationFloorCollision), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsSimulationFloorCollisionEnabled)); ToolkitCommands->MapAction( Commands.SelectedSimulation, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleSimulation, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsSelectedSimulation)); ToolkitCommands->MapAction( Commands.SimulationAll, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleSimulation, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsFullSimulation)); ToolkitCommands->MapAction( Commands.DisableCollision, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetCollision, false), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanSetCollision, false)); ToolkitCommands->MapAction( Commands.DisableCollisionAll, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetCollisionAll, false), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanSetCollisionAll, false)); ToolkitCommands->MapAction( Commands.EnableCollision, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetCollision, true), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanSetCollision, true)); ToolkitCommands->MapAction( Commands.EnableCollisionAll, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetCollisionAll, true), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanSetCollisionAll, true)); ToolkitCommands->MapAction( Commands.PrimitiveQueryAndPhysics, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetPrimitiveCollision, ECollisionEnabled::QueryAndPhysics), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanSetPrimitiveCollision, ECollisionEnabled::QueryAndPhysics), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsPrimitiveCollisionChecked, ECollisionEnabled::QueryAndPhysics)); ToolkitCommands->MapAction( Commands.PrimitiveQueryOnly, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetPrimitiveCollision, ECollisionEnabled::QueryOnly), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanSetPrimitiveCollision, ECollisionEnabled::QueryOnly), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsPrimitiveCollisionChecked, ECollisionEnabled::QueryOnly)); ToolkitCommands->MapAction( Commands.PrimitivePhysicsOnly, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetPrimitiveCollision, ECollisionEnabled::PhysicsOnly), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanSetPrimitiveCollision, ECollisionEnabled::PhysicsOnly), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsPrimitiveCollisionChecked, ECollisionEnabled::PhysicsOnly)); ToolkitCommands->MapAction( Commands.PrimitiveNoCollision, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetPrimitiveCollision, ECollisionEnabled::NoCollision), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanSetPrimitiveCollision, ECollisionEnabled::NoCollision), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsPrimitiveCollisionChecked, ECollisionEnabled::NoCollision)); ToolkitCommands->MapAction( Commands.PrimitiveContributeToMass, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetPrimitiveContributeToMass), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanSetPrimitiveContributeToMass), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::GetPrimitiveContributeToMass)); ToolkitCommands->MapAction( Commands.WeldToBody, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnWeldToBody), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanWeldToBody)); ToolkitCommands->MapAction( Commands.AddSphere, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnAddSphere), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanAddPrimitive, EAggCollisionShape::Sphere)); ToolkitCommands->MapAction( Commands.AddSphyl, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnAddSphyl), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanAddPrimitive, EAggCollisionShape::Sphyl)); ToolkitCommands->MapAction( Commands.AddBox, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnAddBox), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanAddPrimitive, EAggCollisionShape::Box)); ToolkitCommands->MapAction( Commands.AddTaperedCapsule, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnAddTaperedCapsule), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanAddPrimitive, EAggCollisionShape::TaperedCapsule)); ToolkitCommands->MapAction( Commands.DeletePrimitive, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnDeletePrimitive), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HasSelectedBodyAndIsNotSimulation)); ToolkitCommands->MapAction( Commands.DuplicatePrimitive, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnDuplicatePrimitive), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanDuplicatePrimitive)); ToolkitCommands->MapAction( Commands.ConstrainChildBodiesToParentBody, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConstrainChildBodiesToParentBody), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HasMoreThanOneSelectedBodyAndIsNotSimulation)); ToolkitCommands->MapAction( Commands.ResetConstraint, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnResetConstraint), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HasSelectedConstraintAndIsNotSimulation)); ToolkitCommands->MapAction( Commands.SnapConstraint, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSnapConstraint), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HasSelectedConstraintAndIsNotSimulation)); ToolkitCommands->MapAction( Commands.ConvertToBallAndSocket, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConvertToBallAndSocket), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanEditConstraintProperties)); ToolkitCommands->MapAction( Commands.ConvertToHinge, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConvertToHinge), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanEditConstraintProperties)); ToolkitCommands->MapAction( Commands.ConvertToPrismatic, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConvertToPrismatic), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanEditConstraintProperties)); ToolkitCommands->MapAction( Commands.ConvertToSkeletal, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConvertToSkeletal), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::CanEditConstraintProperties)); ToolkitCommands->MapAction( Commands.DeleteConstraint, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnDeleteConstraint), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HasSelectedConstraintAndIsNotSimulation)); ToolkitCommands->MapAction( Commands.MakeBodyKinematic, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetBodyPhysicsType, EPhysicsType::PhysType_Kinematic ), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsBodyPhysicsType, EPhysicsType::PhysType_Kinematic ) ); ToolkitCommands->MapAction( Commands.MakeBodySimulated, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetBodyPhysicsType, EPhysicsType::PhysType_Simulated ), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsBodyPhysicsType, EPhysicsType::PhysType_Simulated ) ); ToolkitCommands->MapAction( Commands.MakeBodyDefault, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSetBodyPhysicsType, EPhysicsType::PhysType_Default ), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsBodyPhysicsType, EPhysicsType::PhysType_Default ) ); ToolkitCommands->MapAction( Commands.KinematicAllBodiesBelow, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::SetBodiesBelowSelectedPhysicsType, EPhysicsType::PhysType_Kinematic, true), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.SimulatedAllBodiesBelow, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::SetBodiesBelowSelectedPhysicsType, EPhysicsType::PhysType_Simulated, true), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.MakeAllBodiesBelowDefault, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::SetBodiesBelowSelectedPhysicsType, EPhysicsType::PhysType_Default, true), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.DeleteBody, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnDeleteBody), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.DeleteAllBodiesBelow, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnDeleteAllBodiesBelow), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.DeleteSelected, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnDeleteSelection), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.CycleConstraintOrientation, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCycleConstraintOrientation), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.CycleConstraintActive, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCycleConstraintActive), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.ToggleSwing1, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleSwing1), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsSwing1Locked)); ToolkitCommands->MapAction( Commands.ToggleSwing2, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleSwing2), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsSwing2Locked)); ToolkitCommands->MapAction( Commands.ToggleTwist, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleTwist), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsTwistLocked)); ToolkitCommands->MapAction( Commands.SelectAllBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSelectAllBodies), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.SelectSimulatedBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSelectSimulatedBodies), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.SelectKinematicBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSelectKinematicBodies), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.SelectAllConstraints, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnSelectAllConstraints), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.ToggleSelectionType, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleSelectionType, true), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.ToggleSelectionTypeWithUserConstraints, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleSelectionType, false), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.ToggleShowSelected, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleShowSelected), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.ShowSelected, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnShowSelected), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.HideSelected, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnHideSelected), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.ToggleShowOnlyColliding, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleShowOnlyColliding), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HasOneSelectedBodyAndIsNotSimulation)); ToolkitCommands->MapAction( Commands.ToggleShowOnlyConstrained, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleShowOnlyConstrained), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HasSelectedBodyOrConstraintAndIsNotSimulation)); ToolkitCommands->MapAction( Commands.ToggleShowOnlySelected, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleShowOnlySelected), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.ShowAll, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnShowAll), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.HideAll, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnHideAll), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.DeselectAll, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnDeselectAll), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)); ToolkitCommands->MapAction( Commands.Mirror, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::Mirror), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation) ); ViewportCommandList = MakeShared(); ViewportCommandList->BeginGroup(TEXT("MeshRenderingMode")); ViewportCommandList->MapAction( Commands.MeshRenderingMode_Solid, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::Solid, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::Solid, false)); ViewportCommandList->MapAction( Commands.MeshRenderingMode_Wireframe, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::Wireframe, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::Wireframe, false)); ViewportCommandList->MapAction( Commands.MeshRenderingMode_None, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::None, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::None, false)); ViewportCommandList->EndGroup(); ViewportCommandList->BeginGroup(TEXT("CollisionRenderingMode")); ViewportCommandList->MapAction( Commands.CollisionRenderingMode_Solid, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::Solid, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::Solid, false)); ViewportCommandList->MapAction( Commands.CollisionRenderingMode_Wireframe, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::Wireframe, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::Wireframe, false)); ViewportCommandList->MapAction( Commands.CollisionRenderingMode_SolidWireframe, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::SolidWireframe, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::SolidWireframe, false)); ViewportCommandList->MapAction( Commands.CollisionRenderingMode_None, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::None, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::None, false)); ViewportCommandList->EndGroup(); ViewportCommandList->BeginGroup(TEXT("ConstraintRenderingMode")); ViewportCommandList->MapAction( Commands.ConstraintRenderingMode_None, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::None, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::None, false)); ViewportCommandList->MapAction( Commands.ConstraintRenderingMode_AllPositions, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::AllPositions, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::AllPositions, false)); ViewportCommandList->MapAction( Commands.ConstraintRenderingMode_AllLimits, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::AllLimits, false), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::AllLimits, false)); ViewportCommandList->EndGroup(); ViewportCommandList->BeginGroup(TEXT("MeshRenderingMode_Simulation")); ViewportCommandList->MapAction( Commands.MeshRenderingMode_Simulation_Solid, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::Solid, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::Solid, true)); ViewportCommandList->MapAction( Commands.MeshRenderingMode_Simulation_Wireframe, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::Wireframe, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::Wireframe, true)); ViewportCommandList->MapAction( Commands.MeshRenderingMode_Simulation_None, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::None, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsMeshRenderingMode, EPhysicsAssetEditorMeshViewMode::None, true)); ViewportCommandList->EndGroup(); ViewportCommandList->BeginGroup(TEXT("CollisionRenderingMode_Simulation")); ViewportCommandList->MapAction( Commands.CollisionRenderingMode_Simulation_Solid, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::Solid, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::Solid, true)); ViewportCommandList->MapAction( Commands.CollisionRenderingMode_Simulation_Wireframe, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::Wireframe, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::Wireframe, true)); ViewportCommandList->MapAction( Commands.CollisionRenderingMode_Simulation_SolidWireframe, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::SolidWireframe, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::SolidWireframe, true)); ViewportCommandList->MapAction( Commands.CollisionRenderingMode_Simulation_None, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::None, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsCollisionRenderingMode, EPhysicsAssetEditorCollisionViewMode::None, true)); ViewportCommandList->EndGroup(); ViewportCommandList->BeginGroup(TEXT("ConstraintRenderingMode_Simulation")); ViewportCommandList->MapAction( Commands.ConstraintRenderingMode_Simulation_None, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::None, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::None, true)); ViewportCommandList->MapAction( Commands.ConstraintRenderingMode_Simulation_AllPositions, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::AllPositions, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::AllPositions, true)); ViewportCommandList->MapAction( Commands.ConstraintRenderingMode_Simulation_AllLimits, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::AllLimits, true), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsConstraintRenderingMode, EPhysicsAssetEditorConstraintViewMode::AllLimits, true)); ViewportCommandList->EndGroup(); ViewportCommandList->MapAction( Commands.RenderOnlySelectedSolid, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::ToggleRenderOnlySelectedSolid), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsRenderingOnlySelectedSolid)); ViewportCommandList->MapAction( Commands.HideSimulatedBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::ToggleHideSimulatedBodies), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsHidingSimulatedBodies)); ViewportCommandList->MapAction( Commands.HideKinematicBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::ToggleHideKinematicBodies), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsHidingKinematicBodies)); ViewportCommandList->MapAction( Commands.DrawConstraintsAsPoints, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::ToggleDrawConstraintsAsPoints), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsDrawingConstraintsAsPoints)); ViewportCommandList->MapAction( Commands.RenderOnlySelectedConstraints, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::ToggleRenderOnlySelectedConstraints), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsRenderingOnlySelectedConstraints)); ViewportCommandList->MapAction( Commands.ToggleMassProperties, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::OnToggleMassProperties), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FPhysicsAssetEditor::IsToggleMassProperties)); SkeletonTreeCommandList = MakeShared(); SkeletonTreeCommandList->MapAction( Commands.ShowBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HandleToggleShowBodies), FCanExecuteAction(), FGetActionCheckState::CreateSP(this, &FPhysicsAssetEditor::GetShowBodiesChecked) ); SkeletonTreeCommandList->MapAction( Commands.ShowSimulatedBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HandleToggleShowSimulatedBodies), FCanExecuteAction(), FGetActionCheckState::CreateSP(this, &FPhysicsAssetEditor::GetShowSimulatedBodiesChecked) ); SkeletonTreeCommandList->MapAction( Commands.ShowKinematicBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HandleToggleShowKinematicBodies), FCanExecuteAction(), FGetActionCheckState::CreateSP(this, &FPhysicsAssetEditor::GetShowKinematicBodiesChecked) ); SkeletonTreeCommandList->MapAction( Commands.ShowConstraints, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HandleToggleShowConstraints), FCanExecuteAction(), FGetActionCheckState::CreateSP(this, &FPhysicsAssetEditor::GetShowConstraintsChecked) ); SkeletonTreeCommandList->MapAction( Commands.ShowConstraintsOnParentBodies, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HandleToggleShowConstraintsOnParentBodies), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsShowConstraintsChecked), FGetActionCheckState::CreateSP(this, &FPhysicsAssetEditor::GetShowConstraintsOnParentBodiesChecked) ); SkeletonTreeCommandList->MapAction( Commands.ShowPrimitives, FExecuteAction::CreateSP(this, &FPhysicsAssetEditor::HandleToggleShowPrimitives), FCanExecuteAction(), FGetActionCheckState::CreateSP(this, &FPhysicsAssetEditor::GetShowPrimitivesChecked) ); SkeletonTree->GetPinnedCommandList()->BindCommandList(SkeletonTreeCommandList.ToSharedRef()); } void FPhysicsAssetEditor::Mirror() { SharedData->Mirror(); RecreatePhysicsState(); RefreshHierachyTree(); RefreshPreviewViewport(); } void FPhysicsAssetEditor::AddAdvancedMenuWidget(FMenuBuilder& InMenuBuilder) { InMenuBuilder.BeginSection("Advanced", LOCTEXT("AdvancedHeading", "Advanced")); InMenuBuilder.AddSubMenu( LOCTEXT("AddCollisionfromStaticMesh", "Copy Collision From StaticMesh"), LOCTEXT("AddCollisionfromStaticMesh_Tooltip", "Copy convex collision from a specified static mesh"), FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder) { InSubMenuBuilder.AddWidget(BuildStaticMeshAssetPicker(), FText(), true); }) ); InMenuBuilder.EndSection(); } void FPhysicsAssetEditor::BuildMenuWidgetBody(FMenuBuilder& InMenuBuilder) { InMenuBuilder.PushCommandList(GetToolkitCommands()); { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); struct FLocal { static void FillPhysicsTypeMenu(FMenuBuilder& InSubMenuBuilder) { const FPhysicsAssetEditorCommands& PhysicsAssetEditorCommands = FPhysicsAssetEditorCommands::Get(); InSubMenuBuilder.BeginSection("BodyPhysicsTypeActions", LOCTEXT("BodyPhysicsTypeHeader", "Body Physics Type")); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.MakeBodyKinematic); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.MakeBodySimulated); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.MakeBodyDefault); InSubMenuBuilder.EndSection(); InSubMenuBuilder.BeginSection("BodiesBelowPhysicsTypeActions", LOCTEXT("BodiesBelowPhysicsTypeHeader", "Bodies Below Physics Type")); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.KinematicAllBodiesBelow); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.SimulatedAllBodiesBelow); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.MakeAllBodiesBelowDefault); InSubMenuBuilder.EndSection(); } static void FillCollisionMenu(FMenuBuilder& InSubMenuBuilder) { const FPhysicsAssetEditorCommands& PhysicsAssetEditorCommands = FPhysicsAssetEditorCommands::Get(); InSubMenuBuilder.BeginSection("CollisionHeader", LOCTEXT("CollisionHeader", "Collision")); InSubMenuBuilder.AddMenuEntry( PhysicsAssetEditorCommands.WeldToBody ); InSubMenuBuilder.AddMenuEntry( PhysicsAssetEditorCommands.EnableCollision ); InSubMenuBuilder.AddMenuEntry( PhysicsAssetEditorCommands.EnableCollisionAll ); InSubMenuBuilder.AddMenuEntry( PhysicsAssetEditorCommands.DisableCollision ); InSubMenuBuilder.AddMenuEntry( PhysicsAssetEditorCommands.DisableCollisionAll ); InSubMenuBuilder.EndSection(); InSubMenuBuilder.BeginSection("CollisionFilteringHeader", LOCTEXT("CollisionFilteringHeader", "CollisionFiltering")); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.PrimitiveQueryAndPhysics); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.PrimitiveQueryOnly); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.PrimitivePhysicsOnly); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.PrimitiveNoCollision); InSubMenuBuilder.EndSection(); InSubMenuBuilder.BeginSection("MassHeader", LOCTEXT("MassHeader", "Mass")); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.PrimitiveContributeToMass); InSubMenuBuilder.EndSection(); } }; InMenuBuilder.BeginSection( "BodyActions", LOCTEXT( "BodyHeader", "Body" ) ); InMenuBuilder.AddMenuEntry( Commands.RegenerateBodies ); InMenuBuilder.AddSubMenu( LOCTEXT("AddShapeMenu", "Add Shape"), LOCTEXT("AddShapeMenu_ToolTip", "Add shapes to this body"), FNewMenuDelegate::CreateStatic( &FillAddShapeMenu ) ); InMenuBuilder.AddSubMenu( LOCTEXT("CollisionMenu", "Collision"), LOCTEXT("CollisionMenu_ToolTip", "Adjust body/body collision"), FNewMenuDelegate::CreateStatic( &FLocal::FillCollisionMenu ) ); InMenuBuilder.AddMenuEntry(Commands.ConstrainChildBodiesToParentBody); InMenuBuilder.AddSubMenu( LOCTEXT("ConstraintMenu", "Constraints"), LOCTEXT("ConstraintMenu_ToolTip", "Constraint Operations"), FNewMenuDelegate::CreateSP( this, &FPhysicsAssetEditor::BuildMenuWidgetNewConstraint ) ); InMenuBuilder.AddSubMenu( LOCTEXT("BodyPhysicsTypeMenu", "Physics Type"), LOCTEXT("BodyPhysicsTypeMenu_ToolTip", "Physics Type"), FNewMenuDelegate::CreateStatic( &FLocal::FillPhysicsTypeMenu ) ); InMenuBuilder.AddSubMenu( Commands.ApplyPhysicalMaterial->GetLabel(), LOCTEXT("ApplyPhysicalMaterialSelected", "Apply a physical material to the selected bodies"), FNewMenuDelegate::CreateLambda([this](FMenuBuilder& InSubMenuBuilder) { InSubMenuBuilder.AddWidget(BuildPhysicalMaterialAssetPicker(false), FText(), true); }), FUIAction(FExecuteAction(), FCanExecuteAction::CreateSP(this, &FPhysicsAssetEditor::IsNotSimulation)), NAME_None, EUserInterfaceActionType::Button ); InMenuBuilder.AddMenuEntry(Commands.CopyBodies); InMenuBuilder.AddMenuEntry(Commands.PasteBodies); InMenuBuilder.AddMenuEntry(Commands.CopyProperties); InMenuBuilder.AddMenuEntry(Commands.PasteProperties); InMenuBuilder.AddMenuEntry( Commands.DeleteBody ); InMenuBuilder.AddMenuEntry( Commands.DeleteAllBodiesBelow ); InMenuBuilder.AddMenuEntry( Commands.Mirror ); InMenuBuilder.EndSection(); InMenuBuilder.BeginSection( "PhysicalAnimationProfile", LOCTEXT( "PhysicalAnimationProfileHeader", "Physical Animation Profile" ) ); InMenuBuilder.AddMenuEntry( Commands.AddBodyToPhysicalAnimationProfile ); InMenuBuilder.AddMenuEntry( Commands.RemoveBodyFromPhysicalAnimationProfile ); InMenuBuilder.EndSection(); AddAdvancedMenuWidget(InMenuBuilder); } InMenuBuilder.PopCommandList(); } void FPhysicsAssetEditor::BuildMenuWidgetPrimitives(FMenuBuilder& InMenuBuilder) { InMenuBuilder.PushCommandList(GetToolkitCommands()); { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); InMenuBuilder.BeginSection("PrimitiveActions", LOCTEXT("PrimitivesHeader", "Primitives")); InMenuBuilder.AddMenuEntry(FGenericCommands::Get().Rename); InMenuBuilder.AddMenuEntry(Commands.DuplicatePrimitive); InMenuBuilder.AddMenuEntry(Commands.DeletePrimitive); InMenuBuilder.EndSection(); } InMenuBuilder.PopCommandList(); } void FPhysicsAssetEditor::BuildMenuWidgetConstraint(FMenuBuilder& InMenuBuilder) { InMenuBuilder.PushCommandList(GetToolkitCommands()); { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); struct FLocal { static void FillAxesAndLimitsMenu(FMenuBuilder& InSubMenuBuilder) { const FPhysicsAssetEditorCommands& PhysicsAssetEditorCommands = FPhysicsAssetEditorCommands::Get(); InSubMenuBuilder.BeginSection("AxesAndLimitsHeader", LOCTEXT("AxesAndLimitsHeader", "Axes and Limits")); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.CycleConstraintOrientation); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.CycleConstraintActive); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.ToggleSwing1); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.ToggleSwing2); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.ToggleTwist); InSubMenuBuilder.EndSection(); } static void FillConvertMenu(FMenuBuilder& InSubMenuBuilder) { const FPhysicsAssetEditorCommands& PhysicsAssetEditorCommands = FPhysicsAssetEditorCommands::Get(); InSubMenuBuilder.BeginSection("ConvertHeader", LOCTEXT("ConvertHeader", "Convert")); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.ConvertToBallAndSocket); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.ConvertToHinge); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.ConvertToPrismatic); InSubMenuBuilder.AddMenuEntry(PhysicsAssetEditorCommands.ConvertToSkeletal); InSubMenuBuilder.EndSection(); } }; InMenuBuilder.BeginSection("EditTypeActions", LOCTEXT("ConstraintEditTypeHeader", "Edit")); InMenuBuilder.AddMenuEntry(Commands.SnapConstraint); InMenuBuilder.AddMenuEntry(Commands.ResetConstraint); InMenuBuilder.AddSubMenu( LOCTEXT("AxesAndLimitsMenu", "Axes and Limits"), LOCTEXT("AxesAndLimitsMenu_ToolTip", "Edit axes and limits of this constraint"), FNewMenuDelegate::CreateStatic( &FLocal::FillAxesAndLimitsMenu ) ); InMenuBuilder.AddSubMenu( LOCTEXT("ConvertMenu", "Convert"), LOCTEXT("ConvertMenu_ToolTip", "Convert constraint to various presets"), FNewMenuDelegate::CreateStatic( &FLocal::FillConvertMenu ) ); InMenuBuilder.AddMenuEntry(Commands.CopyBodies); InMenuBuilder.AddMenuEntry(Commands.PasteBodies); InMenuBuilder.AddMenuEntry(Commands.CopyProperties); InMenuBuilder.AddMenuEntry(Commands.PasteProperties); InMenuBuilder.AddMenuEntry(Commands.DeleteConstraint); InMenuBuilder.EndSection(); InMenuBuilder.BeginSection("ConstraintProfile", LOCTEXT( "ConstraintProfileHeader", "Constraint Profile")); InMenuBuilder.AddMenuEntry(Commands.AddConstraintToCurrentConstraintProfile); InMenuBuilder.AddMenuEntry(Commands.RemoveConstraintFromCurrentConstraintProfile); InMenuBuilder.EndSection(); } InMenuBuilder.PopCommandList(); } void FPhysicsAssetEditor::BuildMenuWidgetSelection(FMenuBuilder& InMenuBuilder) { InMenuBuilder.PushCommandList(GetToolkitCommands()); { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); InMenuBuilder.BeginSection( "Selection", LOCTEXT("Selection", "Selection" ) ); InMenuBuilder.AddMenuEntry( Commands.SelectAllBodies ); InMenuBuilder.AddMenuEntry( Commands.SelectSimulatedBodies ); InMenuBuilder.AddMenuEntry( Commands.SelectKinematicBodies ); InMenuBuilder.AddMenuEntry( Commands.SelectAllConstraints ); InMenuBuilder.AddMenuEntry( Commands.ToggleSelectionType ); InMenuBuilder.AddMenuEntry( Commands.ToggleSelectionTypeWithUserConstraints); InMenuBuilder.AddMenuEntry( Commands.ToggleShowSelected ); InMenuBuilder.AddMenuEntry( Commands.ShowSelected ); InMenuBuilder.AddMenuEntry( Commands.HideSelected ); InMenuBuilder.AddMenuEntry( Commands.ToggleShowOnlySelected ); InMenuBuilder.AddMenuEntry( Commands.ToggleShowOnlyColliding ); InMenuBuilder.AddMenuEntry( Commands.ToggleShowOnlyConstrained ); InMenuBuilder.AddMenuEntry( Commands.ShowAll ); InMenuBuilder.AddMenuEntry( Commands.HideAll ); InMenuBuilder.EndSection(); } InMenuBuilder.PopCommandList(); } void FPhysicsAssetEditor::BuildMenuWidgetNewConstraint(FMenuBuilder& InMenuBuilder) { BuildMenuWidgetNewConstraintForBody(InMenuBuilder, INDEX_NONE); } TSharedRef FPhysicsAssetEditor::BuildMenuWidgetNewConstraintForBody(FMenuBuilder& InMenuBuilder, int32 InSourceBodyIndex, SGraphEditor::FActionMenuClosed InOnActionMenuClosed) { FSkeletonTreeBuilderArgs SkeletonTreeBuilderArgs(false, false, false, false); TSharedRef Builder = MakeShared(SharedData->PhysicsAsset, SkeletonTreeBuilderArgs); Builder->bShowBodies = true; Builder->bShowSimulatedBodies = true; Builder->bShowKinematicBodies = true; Builder->bShowConstraints = false; Builder->bShowPrimitives = false; FSkeletonTreeArgs SkeletonTreeArgs; SkeletonTreeArgs.Mode = ESkeletonTreeMode::Picker; SkeletonTreeArgs.bAllowMeshOperations = false; SkeletonTreeArgs.bAllowSkeletonOperations = false; SkeletonTreeArgs.bShowBlendProfiles = false; SkeletonTreeArgs.bShowFilterMenu = false; SkeletonTreeArgs.bShowDebugVisualizationOptions = true; SkeletonTreeArgs.bHideBonesByDefault = true; SkeletonTreeArgs.Builder = Builder; SkeletonTreeArgs.PreviewScene = GetPersonaToolkit()->GetPreviewScene(); SkeletonTreeArgs.OnSelectionChanged = FOnSkeletonTreeSelectionChanged::CreateLambda([this, InSourceBodyIndex, InOnActionMenuClosed](const TArrayView>& InSelectedItems, ESelectInfo::Type SelectInfo) { if(InSelectedItems.Num() > 0) { TSharedPtr SelectedItem = InSelectedItems[0]; check(SelectedItem->IsOfType()); TSharedPtr SelectedBody = StaticCastSharedPtr(SelectedItem); if(InSourceBodyIndex != INDEX_NONE) { HandleCreateNewConstraint(InSourceBodyIndex, SelectedBody->GetBodySetupIndex()); } else if(SharedData->GetSelectedBody() != nullptr) { // make a copy to avoid changing SelectedBodies while iterating SelectedBodies TArray SourceBodyIndices; for (const FPhysicsAssetEditorSharedData::FSelection& SourceBody : SharedData->SelectedBodies) { SourceBodyIndices.Add(SourceBody.Index); } // create constraints for(const int32 SourceBodyIndex : SourceBodyIndices) { HandleCreateNewConstraint(SourceBodyIndex, SelectedBody->GetBodySetupIndex()); } } } FSlateApplication::Get().DismissAllMenus(); InOnActionMenuClosed.ExecuteIfBound(); }); ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::GetModuleChecked("SkeletonEditor"); TSharedRef SkeletonPicker = SkeletonEditorModule.CreateSkeletonTree(SkeletonTree->GetEditableSkeleton(), SkeletonTreeArgs); InMenuBuilder.BeginSection(TEXT("CreateNewConstraint"), LOCTEXT("CreateNewConstraint", "Create New Constraint With...")); { InMenuBuilder.AddWidget( SNew(SBox) .IsEnabled(this, &FPhysicsAssetEditor::IsNotSimulation) .WidthOverride(300.0f) .HeightOverride(400.0f) [ SkeletonPicker ], FText(), true, false); } InMenuBuilder.EndSection(); return SkeletonPicker; } void FPhysicsAssetEditor::BuildMenuWidgetBone(FMenuBuilder& InMenuBuilder) { InMenuBuilder.PushCommandList(GetToolkitCommands()); InMenuBuilder.BeginSection( "BodyActions", LOCTEXT( "BodyHeader", "Body" ) ); { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); InMenuBuilder.AddMenuEntry( Commands.AddBodies ); InMenuBuilder.AddSubMenu( LOCTEXT("AddShapeMenu", "Add Shape"), LOCTEXT("AddShapeMenu_ToolTip", "Add shapes to this body"), FNewMenuDelegate::CreateStatic( &FillAddShapeMenu ) ); } InMenuBuilder.EndSection(); AddAdvancedMenuWidget(InMenuBuilder); InMenuBuilder.PopCommandList(); } bool FPhysicsAssetEditor::ShouldFilterAssetBasedOnSkeleton( const FAssetData& AssetData ) { // @TODO This is a duplicate of FPersona::ShouldFilterAssetBasedOnSkeleton(), but should go away once PhysicsAssetEditor is integrated with Persona const FString SkeletonName = AssetData.GetTagValueRef("Skeleton"); if ( !SkeletonName.IsEmpty() ) { USkeletalMesh* EditorSkelMesh = SharedData->PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh != nullptr) { USkeleton* Skeleton = EditorSkelMesh->GetSkeleton(); if ( Skeleton && (*SkeletonName) == FObjectPropertyBase::GetExportPath(Skeleton) ) { return false; } } } return true; } void FPhysicsAssetEditor::CreateOrConvertConstraint(EPhysicsAssetEditorConstraintType ConstraintType) { //we have to manually call PostEditChange to ensure profiles are updated correctly FProperty* DefaultInstanceProperty = FindFProperty(UPhysicsConstraintTemplate::StaticClass(), GET_MEMBER_NAME_CHECKED(UPhysicsConstraintTemplate, DefaultInstance)); const FScopedTransaction Transaction( LOCTEXT( "CreateConvertConstraint", "Create Or Convert Constraint" ) ); for(int32 i=0; iSelectedConstraints.Num(); ++i) { UPhysicsConstraintTemplate* ConstraintSetup = SharedData->PhysicsAsset->ConstraintSetup[SharedData->SelectedConstraints[i].Index]; ConstraintSetup->PreEditChange(DefaultInstanceProperty); if(ConstraintType == EPCT_BSJoint) { ConstraintUtils::ConfigureAsBallAndSocket(ConstraintSetup->DefaultInstance); } else if(ConstraintType == EPCT_Hinge) { ConstraintUtils::ConfigureAsHinge(ConstraintSetup->DefaultInstance); } else if(ConstraintType == EPCT_Prismatic) { ConstraintUtils::ConfigureAsPrismatic(ConstraintSetup->DefaultInstance); } else if(ConstraintType == EPCT_SkelJoint) { ConstraintUtils::ConfigureAsSkelJoint(ConstraintSetup->DefaultInstance); } FPropertyChangedEvent PropertyChangedEvent(DefaultInstanceProperty); ConstraintSetup->PostEditChangeProperty(PropertyChangedEvent); } RecreatePhysicsState(); RefreshHierachyTree(); RefreshPreviewViewport(); } void FPhysicsAssetEditor::AddNewPrimitive(EAggCollisionShape::Type InPrimitiveType, bool bCopySelected) { check(!bCopySelected || SharedData->SelectedBodies.Num() == 1); //we only support this for one selection int32 NewPrimIndex = 0; TArray NewSelection; { // Make sure rendering is done - so we are not changing data being used by collision drawing. FlushRenderingCommands(); const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "AddNewPrimitive", "Add New Primitive") ); //first we need to grab all the bodies we're modifying (removes duplicates from multiple primitives) for(int32 i=0; iSelectedBodies.Num(); ++i) { NewSelection.AddUnique(FPhysicsAssetEditorSharedData::FSelection(SharedData->SelectedBodies[i].Index, EAggCollisionShape::Unknown, 0)); //only care about body index for now, we'll later update the primitive index } // Make new bodies for any bones we have selected that dont already have them TArray> Items = SkeletonTree->GetSelectedItems(); FSkeletonTreeSelection Selection(Items); TArray> BoneItems = Selection.GetSelectedItemsByTypeId("FSkeletonTreeBoneItem"); for(TSharedPtr BoneItem : BoneItems) { UBoneProxy* BoneProxy = CastChecked(BoneItem->GetObject()); int32 BoneIndex = SharedData->EditorSkelComp->GetBoneIndex(BoneProxy->BoneName); if (BoneIndex != INDEX_NONE) { const FPhysAssetCreateParams& NewBodyData = GetDefault()->CreateParams; int32 NewBodyIndex = FPhysicsAssetUtils::CreateNewBody(SharedData->PhysicsAsset, BoneProxy->BoneName, NewBodyData); NewSelection.AddUnique(FPhysicsAssetEditorSharedData::FSelection(NewBodyIndex, EAggCollisionShape::Unknown, 0)); } } for(int32 i=0; iPhysicsAsset->SkeletalBodySetups[BodyIndex]; EAggCollisionShape::Type PrimitiveType; if (bCopySelected) { PrimitiveType = SharedData->GetSelectedBody()->PrimitiveType; } else { PrimitiveType = InPrimitiveType; } BodySetup->Modify(); if (PrimitiveType == EAggCollisionShape::Sphere) { NewPrimIndex = BodySetup->AggGeom.SphereElems.Add(FKSphereElem()); NewSelection[i].PrimitiveType = EAggCollisionShape::Sphere; NewSelection[i].PrimitiveIndex = NewPrimIndex; FKSphereElem* SphereElem = &BodySetup->AggGeom.SphereElems[NewPrimIndex]; if (!bCopySelected) { SphereElem->Center = FVector::ZeroVector; SphereElem->Radius = PhysicsAssetEditor::DefaultPrimSize; } else { SphereElem->Center = BodySetup->AggGeom.SphereElems[SharedData->GetSelectedBody()->PrimitiveIndex].Center; SphereElem->Center.X += PhysicsAssetEditor::DuplicateXOffset; SphereElem->Radius = BodySetup->AggGeom.SphereElems[SharedData->GetSelectedBody()->PrimitiveIndex].Radius; } SharedData->AutoNamePrimitive(BodyIndex, PrimitiveType); } else if (PrimitiveType == EAggCollisionShape::Box) { NewPrimIndex = BodySetup->AggGeom.BoxElems.Add(FKBoxElem()); NewSelection[i].PrimitiveType = EAggCollisionShape::Box; NewSelection[i].PrimitiveIndex = NewPrimIndex; FKBoxElem* BoxElem = &BodySetup->AggGeom.BoxElems[NewPrimIndex]; if (!bCopySelected) { BoxElem->SetTransform( FTransform::Identity ); BoxElem->X = 0.5f * PhysicsAssetEditor::DefaultPrimSize; BoxElem->Y = 0.5f * PhysicsAssetEditor::DefaultPrimSize; BoxElem->Z = 0.5f * PhysicsAssetEditor::DefaultPrimSize; } else { BoxElem->SetTransform( BodySetup->AggGeom.BoxElems[SharedData->GetSelectedBody()->PrimitiveIndex].GetTransform() ); BoxElem->Center.X += PhysicsAssetEditor::DuplicateXOffset; BoxElem->X = BodySetup->AggGeom.BoxElems[SharedData->GetSelectedBody()->PrimitiveIndex].X; BoxElem->Y = BodySetup->AggGeom.BoxElems[SharedData->GetSelectedBody()->PrimitiveIndex].Y; BoxElem->Z = BodySetup->AggGeom.BoxElems[SharedData->GetSelectedBody()->PrimitiveIndex].Z; } SharedData->AutoNamePrimitive(BodyIndex, PrimitiveType); } else if (PrimitiveType == EAggCollisionShape::Sphyl) { NewPrimIndex = BodySetup->AggGeom.SphylElems.Add(FKSphylElem()); NewSelection[i].PrimitiveType = EAggCollisionShape::Sphyl; NewSelection[i].PrimitiveIndex = NewPrimIndex; FKSphylElem* SphylElem = &BodySetup->AggGeom.SphylElems[NewPrimIndex]; if (!bCopySelected) { SphylElem->SetTransform( FTransform::Identity ); SphylElem->Length = PhysicsAssetEditor::DefaultPrimSize; SphylElem->Radius = PhysicsAssetEditor::DefaultPrimSize; } else { SphylElem->SetTransform( BodySetup->AggGeom.SphylElems[SharedData->GetSelectedBody()->PrimitiveIndex].GetTransform() ); SphylElem->Center.X += PhysicsAssetEditor::DuplicateXOffset; SphylElem->Length = BodySetup->AggGeom.SphylElems[SharedData->GetSelectedBody()->PrimitiveIndex].Length; SphylElem->Radius = BodySetup->AggGeom.SphylElems[SharedData->GetSelectedBody()->PrimitiveIndex].Radius; } SharedData->AutoNamePrimitive(BodyIndex, PrimitiveType); } else if (PrimitiveType == EAggCollisionShape::Convex) { check(bCopySelected); //only support copying for Convex primitive, as there is no default vertex data NewPrimIndex = BodySetup->AggGeom.ConvexElems.Add(FKConvexElem()); NewSelection[i].PrimitiveType = EAggCollisionShape::Convex; NewSelection[i].PrimitiveIndex = NewPrimIndex; FKConvexElem* ConvexElem = &BodySetup->AggGeom.ConvexElems[NewPrimIndex]; ConvexElem->SetTransform(BodySetup->AggGeom.ConvexElems[SharedData->GetSelectedBody()->PrimitiveIndex].GetTransform()); // Copy all of the vertices of the convex element for (FVector v : BodySetup->AggGeom.ConvexElems[SharedData->GetSelectedBody()->PrimitiveIndex].VertexData) { v.X += PhysicsAssetEditor::DuplicateXOffset; ConvexElem->VertexData.Add(v); } ConvexElem->UpdateElemBox(); SharedData->AutoNamePrimitive(BodyIndex, PrimitiveType); BodySetup->InvalidatePhysicsData(); BodySetup->CreatePhysicsMeshes(); } else if (PrimitiveType == EAggCollisionShape::TaperedCapsule) { NewPrimIndex = BodySetup->AggGeom.TaperedCapsuleElems.Add(FKTaperedCapsuleElem()); NewSelection[i].PrimitiveType = EAggCollisionShape::TaperedCapsule; NewSelection[i].PrimitiveIndex = NewPrimIndex; FKTaperedCapsuleElem* TaperedCapsuleElem = &BodySetup->AggGeom.TaperedCapsuleElems[NewPrimIndex]; if (!bCopySelected) { TaperedCapsuleElem->SetTransform( FTransform::Identity ); TaperedCapsuleElem->Length = PhysicsAssetEditor::DefaultPrimSize; TaperedCapsuleElem->Radius0 = PhysicsAssetEditor::DefaultPrimSize; TaperedCapsuleElem->Radius1 = PhysicsAssetEditor::DefaultPrimSize; } else { TaperedCapsuleElem->SetTransform( BodySetup->AggGeom.TaperedCapsuleElems[SharedData->GetSelectedBody()->PrimitiveIndex].GetTransform() ); TaperedCapsuleElem->Center.X += PhysicsAssetEditor::DuplicateXOffset; TaperedCapsuleElem->Length = BodySetup->AggGeom.TaperedCapsuleElems[SharedData->GetSelectedBody()->PrimitiveIndex].Length; TaperedCapsuleElem->Radius0 = BodySetup->AggGeom.TaperedCapsuleElems[SharedData->GetSelectedBody()->PrimitiveIndex].Radius0; TaperedCapsuleElem->Radius1 = BodySetup->AggGeom.TaperedCapsuleElems[SharedData->GetSelectedBody()->PrimitiveIndex].Radius1; } SharedData->AutoNamePrimitive(BodyIndex, PrimitiveType); } else { check(0); //unrecognized primitive type } } } // ScopedTransaction //clear selection SharedData->ClearSelectedBody(); SharedData->SetSelectedBodies(NewSelection, true); RecreatePhysicsState(); RefreshHierachyTree(); RefreshPreviewViewport(); } void FPhysicsAssetEditor::SetBodiesBelowSelectedPhysicsType( EPhysicsType InPhysicsType, bool bMarkAsDirty) { TArray Indices; for(int32 i=0; iSelectedBodies.Num(); ++i) { Indices.Add(SharedData->SelectedBodies[i].Index); } SetBodiesBelowPhysicsType(InPhysicsType, Indices, bMarkAsDirty); } void FPhysicsAssetEditor::SetBodiesBelowPhysicsType( EPhysicsType InPhysicsType, const TArray & Indices, bool bMarkAsDirty) { USkeletalMesh* EditorSkelMesh = SharedData->PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh != nullptr) { TArray BelowBodies; for(int32 i=0; iPhysicsAsset->SkeletalBodySetups[Indices[i]]; SharedData->PhysicsAsset->GetBodyIndicesBelow(BelowBodies, BaseSetup->BoneName, EditorSkelMesh); // Now reset our skeletal mesh, as we don't re-init the physics state when simulating bool bSimulate = InPhysicsType == PhysType_Simulated || (InPhysicsType == EPhysicsType::PhysType_Default && SharedData->EditorSkelComp->BodyInstance.bSimulatePhysics); SharedData->EditorSkelComp->SetAllBodiesBelowSimulatePhysics(BaseSetup->BoneName, bSimulate, true); } // Make sure that the body setups are also correctly setup (the above loop just does the instances) for (int32 i = 0; i < BelowBodies.Num(); ++i) { int32 BodyIndex = BelowBodies[i]; UBodySetup* BodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[BodyIndex]; if (bMarkAsDirty) { BodySetup->Modify(); } BodySetup->PhysicsType = InPhysicsType; } } RecreatePhysicsState(); RefreshHierachyTree(); } bool FPhysicsAssetEditor::IsNotSimulation() const { return !SharedData->bRunningSimulation; } bool FPhysicsAssetEditor::HasSelectedBodyAndIsNotSimulation() const { return IsNotSimulation() && (SharedData->GetSelectedBody()); } bool FPhysicsAssetEditor::HasOneSelectedBodyAndIsNotSimulation() const { return IsNotSimulation() && (SharedData->SelectedBodies.Num() == 1); } bool FPhysicsAssetEditor::HasMoreThanOneSelectedBodyAndIsNotSimulation() const { return IsNotSimulation() && (SharedData->SelectedBodies.Num() > 1); } bool FPhysicsAssetEditor::HasSelectedBodyOrConstraintAndIsNotSimulation() const { return IsNotSimulation() && (SharedData->SelectedBodies.Num() > 0 || SharedData->SelectedConstraints.Num() > 0); } bool FPhysicsAssetEditor::CanEditConstraintProperties() const { if(IsNotSimulation() && SharedData->PhysicsAsset && SharedData->GetSelectedConstraint()) { //If we are currently editing a constraint profile, make sure all selected constraints belong to the profile if(SharedData->PhysicsAsset->CurrentConstraintProfileName != NAME_None) { for (const FPhysicsAssetEditorSharedData::FSelection& Selection : SharedData->SelectedConstraints) { UPhysicsConstraintTemplate* CS = SharedData->PhysicsAsset->ConstraintSetup[Selection.Index]; if(!CS || !CS->ContainsConstraintProfile(SharedData->PhysicsAsset->CurrentConstraintProfileName)) { //missing at least one constraint from profile so don't allow editing return false; } } } //no constraint profile so editing is fine return true; } return false; } bool FPhysicsAssetEditor::HasSelectedConstraintAndIsNotSimulation() const { return IsNotSimulation() && (SharedData->GetSelectedConstraint()); } bool FPhysicsAssetEditor::IsSelectedEditMode() const { return HasSelectedBodyAndIsNotSimulation() || HasSelectedConstraintAndIsNotSimulation(); } void FPhysicsAssetEditor::OnChangeDefaultMesh(USkeletalMesh* OldPreviewMesh, USkeletalMesh* NewPreviewMesh) { if(NewPreviewMesh != nullptr) { IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked("MeshUtilities"); // Update various infos based on the mesh MeshUtilities.CalcBoneVertInfos(NewPreviewMesh, SharedData->DominantWeightBoneInfos, true); MeshUtilities.CalcBoneVertInfos(NewPreviewMesh, SharedData->AnyWeightBoneInfos, false); RefreshHierachyTree(); SharedData->EditorSkelComp->SetDisablePostProcessBlueprint(true); } } void FPhysicsAssetEditor::ResetBoneCollision() { USkeletalMesh* EditorSkelMesh = SharedData->PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh == nullptr) { return; } // Make sure rendering is done - so we are not changing data being used by collision drawing. FlushRenderingCommands(); const FPhysAssetCreateParams& NewBodyData = GetDefault()->CreateParams; if(SharedData->SelectedBodies.Num() > 0) { TArray BodyIndices; const FScopedTransaction Transaction( LOCTEXT("ResetBoneCollision", "Reset Bone Collision") ); FScopedSlowTask SlowTask((float)SharedData->SelectedBodies.Num()); SlowTask.MakeDialog(); for(int32 i=0; iSelectedBodies.Num(); ++i) { int32 SelectedBodyIndex = SharedData->SelectedBodies[i].Index; if (SharedData->PhysicsAsset->SkeletalBodySetups.IsValidIndex(SelectedBodyIndex) == false) { continue; } UBodySetup* BodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[SelectedBodyIndex]; check(BodySetup); SlowTask.EnterProgressFrame(1.0f, FText::Format(LOCTEXT("ResetCollsionStepInfo", "Generating collision for {0}"), FText::FromName(BodySetup->BoneName))); BodySetup->Modify(); int32 BoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(BodySetup->BoneName); check(BoneIndex != INDEX_NONE); const FBoneVertInfo& UseVertInfo = NewBodyData.VertWeight == EVW_DominantWeight ? SharedData->DominantWeightBoneInfos[BoneIndex] : SharedData->AnyWeightBoneInfos[BoneIndex]; if(FPhysicsAssetUtils::CreateCollisionFromBone(BodySetup, EditorSkelMesh, BoneIndex, NewBodyData, UseVertInfo)) { SharedData->AutoNameAllPrimitives(SelectedBodyIndex, NewBodyData.GeomType); BodyIndices.AddUnique(SelectedBodyIndex); } else { FPhysicsAssetUtils::DestroyBody(SharedData->PhysicsAsset, SelectedBodyIndex); } } //deselect first SharedData->ClearSelectedBody(); SharedData->SetSelectedBodiesAnyPrim(BodyIndices, true); } else { TArray> Items = SkeletonTree->GetSelectedItems(); FSkeletonTreeSelection Selection(Items); TArray> BoneItems = Selection.GetSelectedItemsByTypeId("FSkeletonTreeBoneItem"); // If we have bones selected, make new bodies for them if(BoneItems.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("AddNewPrimitive", "Add New Bodies") ); FScopedSlowTask SlowTask((float)BoneItems.Num()); SlowTask.MakeDialog(); for(TSharedPtr BoneItem : BoneItems) { SlowTask.EnterProgressFrame(1.0f, FText::Format(LOCTEXT("ResetCollsionStepInfo", "Generating collision for {0}"), FText::FromName(BoneItem->GetRowItemName()))); UBoneProxy* BoneProxy = CastChecked(BoneItem->GetObject()); int32 BoneIndex = SharedData->EditorSkelComp->GetBoneIndex(BoneProxy->BoneName); if (BoneIndex != INDEX_NONE) { SharedData->MakeNewBody(BoneIndex); } } } else { const FScopedTransaction Transaction( LOCTEXT("ResetAllBoneCollision", "Reset All Collision") ); SharedData->PhysicsAsset->Modify(); // Deselect everything. SharedData->ClearSelectedBody(); SharedData->ClearSelectedConstraints(); // Empty current asset data. SharedData->PhysicsAsset->SkeletalBodySetups.Empty(); SharedData->PhysicsAsset->BodySetupIndexMap.Empty(); SharedData->PhysicsAsset->ConstraintSetup.Empty(); FText ErrorMessage; if (FPhysicsAssetUtils::CreateFromSkeletalMesh(SharedData->PhysicsAsset, EditorSkelMesh, NewBodyData, ErrorMessage, /*bSetToMesh=*/false) == false) { //name the resulting primitives for (int32 BodyIndex = 0; BodyIndex < SharedData->PhysicsAsset->SkeletalBodySetups.Num(); BodyIndex++) { SharedData->AutoNameAllPrimitives(BodyIndex, NewBodyData.GeomType); } FMessageDialog::Open(EAppMsgType::Ok, ErrorMessage); } } } RecreatePhysicsState(); SharedData->RefreshPhysicsAssetChange(SharedData->PhysicsAsset); RefreshPreviewViewport(); RefreshHierachyTree(); } void FPhysicsAssetEditor::ShowNotificationMessage(const FText& Message, const SNotificationItem::ECompletionState CompletionState) { FNotificationInfo Info(Message); Info.ExpireDuration = 5.0f; Info.bUseLargeFont = false; Info.bUseThrobber = false; Info.bUseSuccessFailIcons = false; TSharedPtr Notification = FSlateNotificationManager::Get().AddNotification(Info); if (Notification.IsValid()) { Notification->SetCompletionState(CompletionState); } } void FPhysicsAssetEditor::OnCopyBodies() { int32 NumCopiedBodies; int32 NumCopiedConstraints; SharedData->CopySelectedBodiesAndConstraintsToClipboard(NumCopiedBodies, NumCopiedConstraints); const FText MessageFormat = LOCTEXT("CopiedBodiesAndConstraintsToClipboard", "{0} bodies and {1} constraints copied to clipboard"); ShowNotificationMessage(FText::Format(MessageFormat, NumCopiedBodies, NumCopiedConstraints), SNotificationItem::CS_Success); } bool FPhysicsAssetEditor::IsCopyBodies() const { // todo : implement by checking the clipboard ? return true; } bool FPhysicsAssetEditor::CanCopyBodies() const { if (IsSelectedEditMode()) { return ((SharedData->SelectedBodies.Num() > 0) || (SharedData->SelectedConstraints.Num() > 0)); } return false; } void FPhysicsAssetEditor::OnPasteBodies() { int32 NumPastedBodies; int32 NumPastedConstraints; SharedData->PasteBodiesAndConstraintsFromClipboard(NumPastedBodies, NumPastedConstraints); const FText MessageFormat = LOCTEXT("PastedBodiesAndConstraintsToClipboard", "{0} bodies and {1} constraints pasted from clipboard"); ShowNotificationMessage(FText::Format(MessageFormat, NumPastedBodies, NumPastedConstraints), SNotificationItem::CS_Success); } bool FPhysicsAssetEditor::CanPasteBodies() const { // would be nice to be able to check the clipboard? return true; } void FPhysicsAssetEditor::OnCopyProperties() { if(SharedData->SelectedBodies.Num() == 1) { SharedData->CopyBodyProperties(); } else if(SharedData->SelectedConstraints.Num() == 1) { SharedData->CopyConstraintProperties(); } RefreshPreviewViewport(); } void FPhysicsAssetEditor::OnPasteProperties() { if(SharedData->SelectedBodies.Num() > 0) { SharedData->PasteBodyProperties(); } else if (SharedData->SelectedConstraints.Num() > 0) { SharedData->PasteConstraintProperties(); } RecreatePhysicsState(); SharedData->RefreshPhysicsAssetChange(SharedData->PhysicsAsset); RefreshPreviewViewport(); RefreshHierachyTree(); } bool FPhysicsAssetEditor::CanCopyProperties() const { if(IsSelectedEditMode()) { if(SharedData->SelectedBodies.Num() == 1 && SharedData->SelectedConstraints.Num() == 0) { return true; } else if(SharedData->SelectedConstraints.Num() == 1 && SharedData->SelectedBodies.Num() == 0) { return true; } } return false; } bool FPhysicsAssetEditor::CanPasteProperties() const { return IsSelectedEditMode() && IsCopyProperties() && (SharedData->SelectedBodies.Num() > 0 || SharedData->SelectedConstraints.Num() > 0); } bool FPhysicsAssetEditor::IsCopyProperties() const { return FPhysicsAssetEditorSharedData::ClipboardHasCompatibleData(); } //We need to save and restore physics states based on the mode we use to simulate void FPhysicsAssetEditor::FixPhysicsState() { UPhysicsAsset * PhysicsAsset = SharedData->PhysicsAsset; TArray& BodySetup = PhysicsAsset->SkeletalBodySetups; if(!SharedData->bRunningSimulation) { PhysicsTypeState.Reset(); for(int32 i=0; iPhysicsAsset->SkeletalBodySetups.Num(); ++i) { PhysicsTypeState.Add(BodySetup[i]->PhysicsType); } }else { for(int32 i=0; iPhysicsType = PhysicsTypeState[i]; } } } void FPhysicsAssetEditor::ImpToggleSimulation() { static const int32 PrevMaxFPS = GEngine->GetMaxFPS(); if(!SharedData->bRunningSimulation) { GEngine->SetMaxFPS(SharedData->EditorOptions->MaxFPS); } else { GEngine->SetMaxFPS(PrevMaxFPS); } SharedData->ToggleSimulation(); // add to analytics record OnAddPhatRecord(TEXT("ToggleSimulate"), true, true); } void FPhysicsAssetEditor::OnRepeatLastSimulation() { OnToggleSimulation(SelectedSimulation); } void FPhysicsAssetEditor::OnToggleSimulation(bool bInSelected) { SelectedSimulation = bInSelected; // this stores current physics types before simulate // and recovers to the previous physics types // so after this one, we can modify physics types fine FixPhysicsState(); if (IsSelectedSimulation()) { SetupSelectedSimulation(); } ImpToggleSimulation(); } void FPhysicsAssetEditor::OnToggleSimulationNoGravity() { SharedData->bNoGravitySimulation = !SharedData->bNoGravitySimulation; } bool FPhysicsAssetEditor::IsNoGravitySimulationEnabled() const { return SharedData->bNoGravitySimulation; } void FPhysicsAssetEditor::OnToggleSimulationFloorCollision() { if (SharedData && SharedData->EditorOptions) { SharedData->EditorOptions->bSimulationFloorCollisionEnabled = !SharedData->EditorOptions->bSimulationFloorCollisionEnabled; // Update collision for floor if (PersonaToolkit) { TSharedRef PersonaPreviewScene = PersonaToolkit->GetPreviewScene(); if (UStaticMeshComponent* FloorMeshComponent = const_cast(PersonaPreviewScene->GetFloorMeshComponent())) { if (SharedData->EditorOptions->bSimulationFloorCollisionEnabled) { FloorMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); } else { FloorMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); } } } } } bool FPhysicsAssetEditor::IsSimulationFloorCollisionEnabled() const { return SharedData && SharedData->EditorOptions && SharedData->EditorOptions->bSimulationFloorCollisionEnabled; } bool FPhysicsAssetEditor::IsFullSimulation() const { return !SelectedSimulation; } bool FPhysicsAssetEditor::IsSelectedSimulation() const { return SelectedSimulation; } void FPhysicsAssetEditor::SetupSelectedSimulation() { //Before starting we modify the PhysicsType so that selected are unfixed and the rest are fixed if(SharedData->bRunningSimulation == false) { UPhysicsAsset * PhysicsAsset = SharedData->PhysicsAsset; TArray& BodySetup = PhysicsAsset->SkeletalBodySetups; //first we fix all the bodies for(int32 i=0; iPhysicsAsset->SkeletalBodySetups.Num(); ++i) { BodySetup[i]->PhysicsType = PhysType_Kinematic; } //Bodies already have a function that does this SetBodiesBelowSelectedPhysicsType(PhysType_Simulated, false); //constraints need some more work TArray BodyIndices; TArray & ConstraintSetup = PhysicsAsset->ConstraintSetup; for(int32 i=0; iSelectedConstraints.Num(); ++i) { int32 ConstraintIndex = SharedData->SelectedConstraints[i].Index; FName ConstraintBone1 = ConstraintSetup[ConstraintIndex]->DefaultInstance.ConstraintBone1; //we only unfix the child bodies for(int32 j=0; jBoneName == ConstraintBone1) { BodyIndices.Add(j); } } } SetBodiesBelowPhysicsType(PhysType_Simulated, BodyIndices, false); } } bool FPhysicsAssetEditor::IsToggleSimulation() const { return SharedData->bRunningSimulation; } void FPhysicsAssetEditor::OnMeshRenderingMode(EPhysicsAssetEditorMeshViewMode Mode, bool bSimulation) { if (bSimulation) { SharedData->EditorOptions->SimulationMeshViewMode = Mode; } else { SharedData->EditorOptions->MeshViewMode = Mode; } SharedData->EditorOptions->SaveConfig(); // Changing the mesh rendering mode requires the skeletal mesh component to change its render state, which is an operation // which is deferred until after render. Hence we need to trigger another viewport refresh on the following frame. RefreshPreviewViewport(); } bool FPhysicsAssetEditor::IsMeshRenderingMode(EPhysicsAssetEditorMeshViewMode Mode, bool bSimulation) const { return Mode == SharedData->GetCurrentMeshViewMode(bSimulation); } void FPhysicsAssetEditor::OnCollisionRenderingMode(EPhysicsAssetEditorCollisionViewMode Mode, bool bSimulation) { if (bSimulation) { SharedData->EditorOptions->SimulationCollisionViewMode = Mode; } else { SharedData->EditorOptions->CollisionViewMode = Mode; } SharedData->EditorOptions->SaveConfig(); RefreshPreviewViewport(); } bool FPhysicsAssetEditor::IsCollisionRenderingMode(EPhysicsAssetEditorCollisionViewMode Mode, bool bSimulation) const { return Mode == SharedData->GetCurrentCollisionViewMode(bSimulation); } void FPhysicsAssetEditor::OnConstraintRenderingMode(EPhysicsAssetEditorConstraintViewMode Mode, bool bSimulation) { if (bSimulation) { SharedData->EditorOptions->SimulationConstraintViewMode = Mode; } else { SharedData->EditorOptions->ConstraintViewMode = Mode; } SharedData->EditorOptions->SaveConfig(); RefreshPreviewViewport(); } void FPhysicsAssetEditor::ToggleDrawConstraintsAsPoints() { SharedData->EditorOptions->bShowConstraintsAsPoints = !SharedData->EditorOptions->bShowConstraintsAsPoints; SharedData->EditorOptions->SaveConfig(); } bool FPhysicsAssetEditor::IsDrawingConstraintsAsPoints() const { return SharedData->EditorOptions->bShowConstraintsAsPoints; } void FPhysicsAssetEditor::ToggleRenderOnlySelectedConstraints() { SharedData->EditorOptions->bRenderOnlySelectedConstraints = !SharedData->EditorOptions->bRenderOnlySelectedConstraints; SharedData->EditorOptions->SaveConfig(); } bool FPhysicsAssetEditor::IsRenderingOnlySelectedConstraints() const { return SharedData->EditorOptions->bRenderOnlySelectedConstraints; } void FPhysicsAssetEditor::ToggleRenderOnlySelectedSolid() { SharedData->EditorOptions->bSolidRenderingForSelectedOnly = !SharedData->EditorOptions->bSolidRenderingForSelectedOnly; SharedData->EditorOptions->SaveConfig(); } void FPhysicsAssetEditor::ToggleHideSimulatedBodies() { SharedData->EditorOptions->bHideSimulatedBodies = !SharedData->EditorOptions->bHideSimulatedBodies; SharedData->EditorOptions->SaveConfig(); } void FPhysicsAssetEditor::ToggleHideKinematicBodies() { SharedData->EditorOptions->bHideKinematicBodies = !SharedData->EditorOptions->bHideKinematicBodies; SharedData->EditorOptions->SaveConfig(); } bool FPhysicsAssetEditor::IsRenderingOnlySelectedSolid() const { return SharedData->EditorOptions->bSolidRenderingForSelectedOnly; } bool FPhysicsAssetEditor::IsHidingSimulatedBodies() const { return SharedData->EditorOptions->bHideSimulatedBodies; } bool FPhysicsAssetEditor::IsHidingKinematicBodies() const { return SharedData->EditorOptions->bHideKinematicBodies; } bool FPhysicsAssetEditor::IsConstraintRenderingMode(EPhysicsAssetEditorConstraintViewMode Mode, bool bSimulation) const { return Mode == SharedData->GetCurrentConstraintViewMode(bSimulation); } void FPhysicsAssetEditor::OnToggleMassProperties() { SharedData->bShowCOM = !SharedData->bShowCOM; RefreshPreviewViewport(); } bool FPhysicsAssetEditor::IsToggleMassProperties() const { return SharedData->bShowCOM; } void FPhysicsAssetEditor::OnSetCollision(bool bEnable) { FScopedTransaction Transaction(LOCTEXT("SetCollision", "Set Collision")); SharedData->SetCollisionBetweenSelected(bEnable); } bool FPhysicsAssetEditor::CanSetCollision(bool bEnable) const { return SharedData->CanSetCollisionBetweenSelected(bEnable); } void FPhysicsAssetEditor::OnSetCollisionAll(bool bEnable) { FScopedTransaction Transaction(LOCTEXT("SetCollision", "Set Collision")); SharedData->SetCollisionBetweenSelectedAndAll(bEnable); } bool FPhysicsAssetEditor::CanSetCollisionAll(bool bEnable) const { return SharedData->CanSetCollisionBetweenSelectedAndAll(bEnable); } void FPhysicsAssetEditor::OnSetPrimitiveCollision(ECollisionEnabled::Type CollisionEnabled) { FScopedTransaction Transaction(LOCTEXT("SetPrimitiveCollision", "Set Primitive Collision")); SharedData->SetPrimitiveCollision(CollisionEnabled); } bool FPhysicsAssetEditor::CanSetPrimitiveCollision(ECollisionEnabled::Type CollisionEnabled) const { return SharedData->CanSetPrimitiveCollision(CollisionEnabled); } bool FPhysicsAssetEditor::IsPrimitiveCollisionChecked(ECollisionEnabled::Type CollisionEnabled) const { return SharedData->GetIsPrimitiveCollisionEnabled(CollisionEnabled); } void FPhysicsAssetEditor::OnSetPrimitiveContributeToMass() { SharedData->SetPrimitiveContributeToMass(!SharedData->GetPrimitiveContributeToMass()); } bool FPhysicsAssetEditor::CanSetPrimitiveContributeToMass() const { return SharedData->CanSetPrimitiveContributeToMass(); } bool FPhysicsAssetEditor::GetPrimitiveContributeToMass() const { return SharedData->GetPrimitiveContributeToMass(); } void FPhysicsAssetEditor::OnWeldToBody() { SharedData->WeldSelectedBodies(); } bool FPhysicsAssetEditor::CanWeldToBody() { return HasSelectedBodyAndIsNotSimulation() && SharedData->WeldSelectedBodies(false); } void FPhysicsAssetEditor::OnAddSphere() { AddNewPrimitive(EAggCollisionShape::Sphere); } void FPhysicsAssetEditor::OnAddSphyl() { AddNewPrimitive(EAggCollisionShape::Sphyl); } void FPhysicsAssetEditor::OnAddBox() { AddNewPrimitive(EAggCollisionShape::Box); } void FPhysicsAssetEditor::OnAddTaperedCapsule() { AddNewPrimitive(EAggCollisionShape::TaperedCapsule); } bool FPhysicsAssetEditor::CanAddPrimitive(EAggCollisionShape::Type InPrimitiveType) const { return IsNotSimulation(); } void FPhysicsAssetEditor::OnDeletePrimitive() { SharedData->DeleteCurrentPrim(); RecreatePhysicsState(); } void FPhysicsAssetEditor::OnDuplicatePrimitive() { AddNewPrimitive(EAggCollisionShape::Unknown, true); } bool FPhysicsAssetEditor::CanDuplicatePrimitive() const { return HasSelectedBodyAndIsNotSimulation() && SharedData->SelectedBodies.Num() == 1; } void FPhysicsAssetEditor::OnConstrainChildBodiesToParentBody() { if (SharedData->SelectedBodies.Num() > 1) { int32 ParentBodyIndex = SharedData->SelectedBodies.Last().Index; TArray ChildBodyIndices; // needed as the selection may contain multiple time the same body with different primitive index for (const FPhysicsAssetEditorSharedData::FSelection& Selection : SharedData->SelectedBodies) { if (Selection.Index != ParentBodyIndex) { ChildBodyIndices.AddUnique(Selection.Index); } } SharedData->MakeNewConstraints(ParentBodyIndex, ChildBodyIndices); } } void FPhysicsAssetEditor::OnResetConstraint() { SharedData->SetSelectedConstraintRelTM(FTransform::Identity); RefreshPreviewViewport(); } void FPhysicsAssetEditor::OnSnapConstraint() { const FScopedTransaction Transaction( LOCTEXT( "SnapConstraints", "Snap Constraints" ) ); for(int32 i=0; iSelectedConstraints.Num(); ++i) { SnapConstraintToBone(&SharedData->SelectedConstraints[i]); } RefreshPreviewViewport(); } void FPhysicsAssetEditor::OnConvertToBallAndSocket() { CreateOrConvertConstraint(EPCT_BSJoint); } void FPhysicsAssetEditor::OnConvertToHinge() { CreateOrConvertConstraint(EPCT_Hinge); } void FPhysicsAssetEditor::OnConvertToPrismatic() { CreateOrConvertConstraint(EPCT_Prismatic); } void FPhysicsAssetEditor::OnConvertToSkeletal() { CreateOrConvertConstraint(EPCT_SkelJoint); } void FPhysicsAssetEditor::OnDeleteConstraint() { SharedData->DeleteCurrentConstraint(); RecreatePhysicsState(); } void FPhysicsAssetEditor::OnSetBodyPhysicsType( EPhysicsType InPhysicsType ) { if (SharedData->GetSelectedBody()) { for(int32 i=0; iSelectedBodies.Num(); ++i) { UBodySetup* BodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[SharedData->SelectedBodies[i].Index]; BodySetup->Modify(); BodySetup->PhysicsType = InPhysicsType; } RecreatePhysicsState(); RefreshPreviewViewport(); } } bool FPhysicsAssetEditor::IsBodyPhysicsType( EPhysicsType InPhysicsType ) { for(int32 i=0; iSelectedBodies.Num(); ++i) { UBodySetup* BodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[SharedData->SelectedBodies[i].Index]; if(BodySetup->PhysicsType == InPhysicsType) { return true; } } return false; } void FPhysicsAssetEditor::OnDeleteBody() { if(SharedData->SelectedBodies.Num()) { //first build the bodysetup array because deleting bodies modifies the selected array TArray BodySetups; BodySetups.Reserve(SharedData->SelectedBodies.Num()); for(int32 i=0; iSelectedBodies.Num(); ++i) { BodySetups.Add( SharedData->PhysicsAsset->SkeletalBodySetups[SharedData->SelectedBodies[i].Index] ); } const FScopedTransaction Transaction( LOCTEXT( "DeleteBodies", "Delete Bodies" ) ); for(int32 i=0; iPhysicsAsset->FindBodyIndex(BodySetups[i]->BoneName); if(BodyIndex != INDEX_NONE) { // Use PhysicsAssetEditor function to delete action (so undo works etc) SharedData->DeleteBody(BodyIndex, false); } } SharedData->RefreshPhysicsAssetChange(SharedData->PhysicsAsset); } } void FPhysicsAssetEditor::OnDeleteAllBodiesBelow() { USkeletalMesh* EditorSkelMesh = SharedData->PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh == nullptr) { return; } TArray BodySetups; for (FPhysicsAssetEditorSharedData::FSelection SelectedBody : SharedData->SelectedBodies) { UBodySetup* BaseSetup = SharedData->PhysicsAsset->SkeletalBodySetups[SelectedBody.Index]; // Build a list of BodySetups below this one TArray BelowBodies; SharedData->PhysicsAsset->GetBodyIndicesBelow(BelowBodies, BaseSetup->BoneName, EditorSkelMesh); for (const int32 BodyIndex : BelowBodies) { UBodySetup* BodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[BodyIndex]; BodySetups.Add(BodySetup); } } if(BodySetups.Num()) { const FScopedTransaction Transaction( LOCTEXT( "DeleteBodiesBelow", "Delete Bodies Below" ) ); // Now remove each one for (UBodySetup* BodySetup : BodySetups) { // Use PhysicsAssetEditor function to delete action (so undo works etc) int32 Index = SharedData->PhysicsAsset->FindBodyIndex(BodySetup->BoneName); if(Index != INDEX_NONE) { SharedData->DeleteBody(Index, false); } } SharedData->RefreshPhysicsAssetChange(SharedData->PhysicsAsset); } } void FPhysicsAssetEditor::OnDeleteSelection() { SharedData->DeleteCurrentPrim(); SharedData->DeleteCurrentConstraint(); RecreatePhysicsState(); } void FPhysicsAssetEditor::OnCycleConstraintOrientation() { if(SharedData->GetSelectedConstraint()) { SharedData->CycleCurrentConstraintOrientation(); } } void FPhysicsAssetEditor::OnCycleConstraintActive() { if(SharedData->GetSelectedConstraint()) { SharedData->CycleCurrentConstraintActive(); } } void FPhysicsAssetEditor::OnToggleSwing1() { if(SharedData->GetSelectedConstraint()) { SharedData->ToggleConstraint(FPhysicsAssetEditorSharedData::PCT_Swing1); } } void FPhysicsAssetEditor::OnToggleSwing2() { if(SharedData->GetSelectedConstraint()) { SharedData->ToggleConstraint(FPhysicsAssetEditorSharedData::PCT_Swing2); } } void FPhysicsAssetEditor::OnToggleTwist() { if(SharedData->GetSelectedConstraint()) { SharedData->ToggleConstraint(FPhysicsAssetEditorSharedData::PCT_Twist); } } bool FPhysicsAssetEditor::IsSwing1Locked() const { return SharedData->IsAngularConstraintLocked(FPhysicsAssetEditorSharedData::PCT_Swing1); } bool FPhysicsAssetEditor::IsSwing2Locked() const { return SharedData->IsAngularConstraintLocked(FPhysicsAssetEditorSharedData::PCT_Swing2); } bool FPhysicsAssetEditor::IsTwistLocked() const { return SharedData->IsAngularConstraintLocked(FPhysicsAssetEditorSharedData::PCT_Twist); } TSharedRef FPhysicsAssetEditor::BuildStaticMeshAssetPicker() { FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); FAssetPickerConfig AssetPickerConfig; AssetPickerConfig.Filter.ClassPaths.Add(UStaticMesh::StaticClass()->GetClassPathName()); AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &FPhysicsAssetEditor::OnAssetSelectedFromStaticMeshAssetPicker); AssetPickerConfig.bAllowNullSelection = true; AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; AssetPickerConfig.bFocusSearchBoxWhenOpened = true; AssetPickerConfig.bShowBottomToolbar = false; AssetPickerConfig.SelectionMode = ESelectionMode::Single; return SNew(SBox) .IsEnabled(this, &FPhysicsAssetEditor::IsNotSimulation) .WidthOverride(300) .HeightOverride(400) [ ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig) ]; } void FPhysicsAssetEditor::OnAssetSelectedFromStaticMeshAssetPicker( const FAssetData& AssetData ) { FSlateApplication::Get().DismissAllMenus(); const FScopedTransaction Transaction( LOCTEXT("Import Convex", "Import Convex") ); // Make sure rendering is done - so we are not changing data being used by collision drawing. FlushRenderingCommands(); // get select bones TArray> Items = SkeletonTree->GetSelectedItems(); FSkeletonTreeSelection Selection(Items); TArray> BoneItems = Selection.GetSelectedItemsByTypeId("FSkeletonTreeBoneItem"); // gather all the body indices from both the body and bone selection // make sure to create a body setup if we encounter a bone with no associated body TSet BodyIndicesToUpdate; if (SharedData->GetSelectedBody() || BoneItems.Num() > 0) { for (int32 SelectedBodyIndex = 0; SelectedBodyIndex < SharedData->SelectedBodies.Num(); ++SelectedBodyIndex) { BodyIndicesToUpdate.Add(SharedData->SelectedBodies[SelectedBodyIndex].Index); } for (TSharedPtr BoneItem : BoneItems) { UBoneProxy* BoneProxy = CastChecked(BoneItem->GetObject()); int32 BodyIndex = SharedData->PhysicsAsset->FindBodyIndex(BoneProxy->BoneName); if (BodyIndex == INDEX_NONE) { // no associated body found, let's create one const FPhysAssetCreateParams& NewBodyData = GetDefault()->CreateParams; BodyIndex = FPhysicsAssetUtils::CreateNewBody(SharedData->PhysicsAsset, BoneProxy->BoneName, NewBodyData); } BodyIndicesToUpdate.Add(BodyIndex); } } if (BodyIndicesToUpdate.Num() > 0) { UStaticMesh* SM = Cast(AssetData.GetAsset()); if (SM && SM->GetBodySetup() && SM->GetBodySetup()->AggGeom.GetElementCount() > 0) { SharedData->PhysicsAsset->Modify(); for (int32 BodyIndex: BodyIndicesToUpdate) { UBodySetup* BaseSetup = SharedData->PhysicsAsset->SkeletalBodySetups[BodyIndex]; BaseSetup->Modify(); BaseSetup->AddCollisionFrom(SM->GetBodySetup()); BaseSetup->InvalidatePhysicsData(); BaseSetup->CreatePhysicsMeshes(); } SharedData->RefreshPhysicsAssetChange(SharedData->PhysicsAsset); RefreshHierachyTree(); } else { UE_LOG(LogPhysics, Warning, TEXT("Failed to import body from static mesh %s. Mesh probably has no collision setup."), *AssetData.AssetName.ToString()); } } } TSharedRef FPhysicsAssetEditor::BuildPhysicalMaterialAssetPicker(bool bForAllBodies) { FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); FAssetPickerConfig AssetPickerConfig; AssetPickerConfig.Filter.ClassPaths.Add(UPhysicalMaterial::StaticClass()->GetClassPathName()); AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &FPhysicsAssetEditor::OnAssetSelectedFromPhysicalMaterialAssetPicker, bForAllBodies); AssetPickerConfig.bAllowNullSelection = true; AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; AssetPickerConfig.bFocusSearchBoxWhenOpened = true; AssetPickerConfig.bShowBottomToolbar = false; AssetPickerConfig.SelectionMode = ESelectionMode::Single; // Find a suitable default if any UPhysicalMaterial* SelectedPhysicalMaterial = nullptr; if(bForAllBodies) { if(SharedData->PhysicsAsset->SkeletalBodySetups.Num() > 0) { SelectedPhysicalMaterial = SharedData->PhysicsAsset->SkeletalBodySetups[0]->PhysMaterial; for (int32 SelectedBodyIndex = 0; SelectedBodyIndex < SharedData->PhysicsAsset->SkeletalBodySetups.Num(); ++SelectedBodyIndex) { USkeletalBodySetup* BodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[SelectedBodyIndex]; if(BodySetup->PhysMaterial != SelectedPhysicalMaterial) { SelectedPhysicalMaterial = nullptr; break; } } } } else { if(SharedData->SelectedBodies.Num()) { SelectedPhysicalMaterial = SharedData->PhysicsAsset->SkeletalBodySetups[SharedData->SelectedBodies[0].Index]->PhysMaterial; for (int32 SelectedBodyIndex = 0; SelectedBodyIndex < SharedData->SelectedBodies.Num(); ++SelectedBodyIndex) { USkeletalBodySetup* BodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[SharedData->SelectedBodies[SelectedBodyIndex].Index]; if(BodySetup->PhysMaterial != SelectedPhysicalMaterial) { SelectedPhysicalMaterial = nullptr; break; } } } } AssetPickerConfig.InitialAssetSelection = FAssetData(SelectedPhysicalMaterial); return SNew(SBox) .IsEnabled(this, &FPhysicsAssetEditor::IsNotSimulation) .WidthOverride(300) .HeightOverride(400) [ ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig) ]; } void FPhysicsAssetEditor::OnAssetSelectedFromPhysicalMaterialAssetPicker( const FAssetData& AssetData, bool bForAllBodies ) { FSlateApplication::Get().DismissAllMenus(); if (SharedData->GetSelectedBody() || bForAllBodies) { const FScopedTransaction Transaction(LOCTEXT("SetPhysicalMaterial", "Set Physical Material")); UPhysicalMaterial* PhysicalMaterial = Cast(AssetData.GetAsset()); if(PhysicalMaterial) { if(bForAllBodies) { for (int32 SelectedBodyIndex = 0; SelectedBodyIndex < SharedData->PhysicsAsset->SkeletalBodySetups.Num(); ++SelectedBodyIndex) { USkeletalBodySetup* BodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[SelectedBodyIndex]; BodySetup->Modify(); BodySetup->PhysMaterial = PhysicalMaterial; } } else { for (int32 SelectedBodyIndex = 0; SelectedBodyIndex < SharedData->SelectedBodies.Num(); ++SelectedBodyIndex) { USkeletalBodySetup* BodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[SharedData->SelectedBodies[SelectedBodyIndex].Index]; BodySetup->Modify(); BodySetup->PhysMaterial = PhysicalMaterial; } } } } } void FPhysicsAssetEditor::OnSelectAllBodies() { UPhysicsAsset * const PhysicsAsset = SharedData->EditorSkelComp->GetPhysicsAsset(); // Block selection broadcast until we have selected all, as this can be an expensive operation FScopedBulkSelection BulkSelection(SharedData); //go through every body and add every geom TArray NewSelectedBodies; for (int32 i = 0; i < PhysicsAsset->SkeletalBodySetups.Num(); ++i) { NewSelectedBodies.Add(i); } //first deselect everything SharedData->ClearSelectedBody(); SharedData->SetSelectedBodiesAllPrim(NewSelectedBodies, true); } void FPhysicsAssetEditor::OnSelectKinematicBodies() { OnSelectBodies(EPhysicsType::PhysType_Kinematic); } void FPhysicsAssetEditor::OnSelectSimulatedBodies() { OnSelectBodies(EPhysicsType::PhysType_Simulated); } void FPhysicsAssetEditor::OnSelectBodies(EPhysicsType PhysicsType) { UPhysicsAsset * const PhysicsAsset = SharedData->EditorSkelComp->GetPhysicsAsset(); // Block selection broadcast until we have selected all, as this can be an expensive operation FScopedBulkSelection BulkSelection(SharedData); //go through every body and add every geom TArray NewSelectedBodies; for (int32 i = 0; i < PhysicsAsset->SkeletalBodySetups.Num(); ++i) { int32 BoneIndex = SharedData->EditorSkelComp->GetBoneIndex(PhysicsAsset->SkeletalBodySetups[i]->BoneName); if (PhysicsAsset->SkeletalBodySetups[i]->PhysicsType == PhysicsType) { NewSelectedBodies.Add(i); } } //first deselect everything SharedData->ClearSelectedBody(); SharedData->SetSelectedBodiesAllPrim(NewSelectedBodies, true); } void FPhysicsAssetEditor::OnSelectAllConstraints() { UPhysicsAsset * const PhysicsAsset = SharedData->EditorSkelComp->GetPhysicsAsset(); // Block selection broadcast until we have selected all, as this can be an expensive operation FScopedBulkSelection BulkSelection(SharedData); //go through every constraint and add it TArray NewSelectedConstraints; for (int32 i = 0; i < PhysicsAsset->ConstraintSetup.Num(); ++i) { int32 BoneIndex1 = SharedData->EditorSkelComp->GetBoneIndex(PhysicsAsset->ConstraintSetup[i]->DefaultInstance.ConstraintBone1); int32 BoneIndex2 = SharedData->EditorSkelComp->GetBoneIndex(PhysicsAsset->ConstraintSetup[i]->DefaultInstance.ConstraintBone2); // if bone doesn't exist, do not draw it. It crashes in random points when we try to manipulate. if (BoneIndex1 != INDEX_NONE && BoneIndex2 != INDEX_NONE) { NewSelectedConstraints.Add(i); } } //Deselect everything first SharedData->ClearSelectedConstraints(); SharedData->SetSelectedConstraints(NewSelectedConstraints, true); } void FPhysicsAssetEditor::OnToggleSelectionType(bool bIgnoreUserConstraints) { SharedData->ToggleSelectionType(bIgnoreUserConstraints); } void FPhysicsAssetEditor::OnToggleShowSelected() { SharedData->ToggleShowSelected(); } void FPhysicsAssetEditor::OnShowSelected() { SharedData->ShowSelected(); } void FPhysicsAssetEditor::OnHideSelected() { SharedData->HideSelected(); } void FPhysicsAssetEditor::OnToggleShowOnlyColliding() { SharedData->ToggleShowOnlyColliding(); } void FPhysicsAssetEditor::OnToggleShowOnlyConstrained() { SharedData->ToggleShowOnlyConstrained(); } void FPhysicsAssetEditor::OnToggleShowOnlySelected() { SharedData->ToggleShowOnlySelected(); } void FPhysicsAssetEditor::OnShowAll() { SharedData->ShowAll(); } void FPhysicsAssetEditor::OnHideAll() { SharedData->HideAll(); } void FPhysicsAssetEditor::OnDeselectAll() { SharedData->ClearSelectedBody(); SharedData->ClearSelectedConstraints(); } // record if simulating or not, or mode changed or not, or what mode it is in while simulating and what kind of simulation options void FPhysicsAssetEditor::OnAddPhatRecord(const FString& Action, bool bRecordSimulate, bool bRecordMode) { // Don't attempt to report usage stats if analytics isn't available if( Action.IsEmpty() == false && SharedData.IsValid() && FEngineAnalytics::IsAvailable()) { TArray Attribs; if (bRecordSimulate) { Attribs.Add(FAnalyticsEventAttribute(TEXT("Simulation"), SharedData->bRunningSimulation? TEXT("ON") : TEXT("OFF"))); if ( SharedData->bRunningSimulation ) { Attribs.Add(FAnalyticsEventAttribute(TEXT("Selected"), IsSelectedSimulation()? TEXT("ON") : TEXT("OFF"))); Attribs.Add(FAnalyticsEventAttribute(TEXT("Gravity"), SharedData->bNoGravitySimulation ? TEXT("ON") : TEXT("OFF"))); } } FString EventString = FString::Printf(TEXT("Editor.Usage.PHAT.%s"), *Action); FEngineAnalytics::GetProvider().RecordEvent(EventString, Attribs); } } void FPhysicsAssetEditor::Tick(float DeltaTime) { GetPersonaToolkit()->GetPreviewScene()->InvalidateViews(); } TStatId FPhysicsAssetEditor::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FPhysicsAssetEditor, STATGROUP_Tickables); } void FPhysicsAssetEditor::HandleDetailsCreated(const TSharedRef& InDetailsView) { PhysAssetProperties = InDetailsView; PhysAssetProperties->SetObject(nullptr); PhysAssetProperties->OnFinishedChangingProperties().AddSP(this, &FPhysicsAssetEditor::OnFinishedChangingProperties); PhysAssetProperties->SetEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda([this](){ return !SharedData->bRunningSimulation; }))); } void FPhysicsAssetEditor::HandlePhysicsAssetGraphCreated(const TSharedRef& InPhysicsAssetGraph) { PhysicsAssetGraph = InPhysicsAssetGraph; } void FPhysicsAssetEditor::HandleGraphObjectsSelected(const TArrayView& InObjects) { if (!bSelecting) { TGuardValue RecursionGuard(bSelecting, true); SkeletonTree->DeselectAll(); TArray Objects; Algo::TransformIf(InObjects, Objects, [](UObject* InItem) { return InItem != nullptr; }, [](UObject* InItem) { return InItem; }); if (PhysAssetProperties.IsValid()) { PhysAssetProperties->SetObjects(Objects); } // Block selection broadcast until we have selected all, as this can be an expensive operation FScopedBulkSelection BulkSelection(SharedData); // clear selection SharedData->SelectedBodies.Empty(); SharedData->SelectedConstraints.Empty(); TArray SelectedBodySetups; TArray SelectedConstraintTemplates; TArray SelectedBodyIndices; TArray SelectedConstraintIndices; for (UObject* SelectedObject : Objects) { if (USkeletalBodySetup* BodySetup = Cast(SelectedObject)) { SelectedBodySetups.Add(BodySetup); for (int32 BodySetupIndex = 0; BodySetupIndex < SharedData->PhysicsAsset->SkeletalBodySetups.Num(); ++BodySetupIndex) { if (SharedData->PhysicsAsset->SkeletalBodySetups[BodySetupIndex] == BodySetup) { SelectedBodyIndices.AddUnique(BodySetupIndex); } } } else if (UPhysicsConstraintTemplate* Constraint = Cast(SelectedObject)) { SelectedConstraintTemplates.Add(Constraint); for (int32 ConstraintIndex = 0; ConstraintIndex < SharedData->PhysicsAsset->ConstraintSetup.Num(); ++ConstraintIndex) { if (SharedData->PhysicsAsset->ConstraintSetup[ConstraintIndex] == Constraint) { SelectedConstraintIndices.AddUnique(ConstraintIndex); } } } } SharedData->SetSelectedBodiesAnyPrim(SelectedBodyIndices, true); SharedData->SetSelectedConstraints(SelectedConstraintIndices, true); SkeletonTree->SelectItemsBy([&SelectedBodySetups, &SelectedConstraintTemplates](const TSharedRef& InItem, bool& bInOutExpand) { if(InItem->IsOfType()) { for (USkeletalBodySetup* SelectedBodySetup : SelectedBodySetups) { if (SelectedBodySetup == Cast(InItem->GetObject())) { bInOutExpand = true; return true; } } } else if(InItem->IsOfType()) { for (UPhysicsConstraintTemplate* SelectedConstraintTemplate : SelectedConstraintTemplates) { if (SelectedConstraintTemplate == Cast(InItem->GetObject())) { bInOutExpand = true; return true; } } } bInOutExpand = false; return false; }); } } void FPhysicsAssetEditor::HandleSelectionChanged(const TArrayView>& InSelectedItems, ESelectInfo::Type InSelectInfo) { if (!bSelecting) { TGuardValue RecursionGuard(bSelecting, true); // Always set the details customization object, regardless of selection type // We do this because the tree may have been rebuilt and objects invalidated TArray Objects; Algo::TransformIf(InSelectedItems, Objects, [](const TSharedPtr& InItem) { return InItem->GetObject() != nullptr; }, [](const TSharedPtr& InItem) { return InItem->GetObject(); }); if (PhysAssetProperties.IsValid()) { PhysAssetProperties->SetObjects(Objects); } // Only a user selection should change other view's selections if (InSelectInfo != ESelectInfo::Direct) { // Block selection broadcast until we have selected all, as this can be an expensive operation FScopedBulkSelection BulkSelection(SharedData); // clear selection SharedData->ClearSelectedBody(); SharedData->ClearSelectedConstraints(); bool bBoneSelected = false; TArray SelectedBodies; TArray SelectedBodiesAnyPrim; TArray SelectedConstraints; for (const TSharedPtr& Item : InSelectedItems) { if (Item->IsOfType()) { TSharedPtr SkeletonTreePhysicsBodyItem = StaticCastSharedPtr(Item); SelectedBodiesAnyPrim.Add(SkeletonTreePhysicsBodyItem->GetBodySetupIndex()); } else if (Item->IsOfType()) { TSharedPtr SkeletonTreePhysicsShapeItem = StaticCastSharedPtr(Item); FPhysicsAssetEditorSharedData::FSelection Selection(SkeletonTreePhysicsShapeItem->GetBodySetupIndex(), SkeletonTreePhysicsShapeItem->GetShapeType(), SkeletonTreePhysicsShapeItem->GetShapeIndex()); SelectedBodies.Add(Selection); } else if (Item->IsOfType()) { TSharedPtr SkeletonTreePhysicsConstraintItem = StaticCastSharedPtr(Item); SelectedConstraints.Add(SkeletonTreePhysicsConstraintItem->GetConstraintIndex()); } else if(Item->IsOfTypeByName(TEXT("FSkeletonTreeBoneItem"))) { bBoneSelected = true; } } SharedData->SetSelectedBodies(SelectedBodies, true); SharedData->SetSelectedBodiesAnyPrim(SelectedBodiesAnyPrim, true); SharedData->SetSelectedConstraints(SelectedConstraints, true); if(!bBoneSelected) { GetPersonaToolkit()->GetPreviewScene()->ClearSelectedBone(); } if (PhysicsAssetGraph.IsValid()) { TSet Bodies; TSet Constraints; Algo::TransformIf(InSelectedItems, Bodies, [](const TSharedPtr& InItem) { return InItem->GetObject() && InItem->GetObject()->IsA(); }, [](const TSharedPtr& InItem) { return Cast(InItem->GetObject()); }); Algo::TransformIf(InSelectedItems, Constraints, [](const TSharedPtr& InItem) { return InItem->GetObject() && InItem->GetObject()->IsA(); }, [](const TSharedPtr& InItem) { return Cast(InItem->GetObject()); }); PhysicsAssetGraph.Pin()->SelectObjects(Bodies.Array(), Constraints.Array()); } } } } void FPhysicsAssetEditor::HandlePreviewSceneCreated(const TSharedRef& InPersonaPreviewScene) { InPersonaPreviewScene->RegisterOnPreviewMeshChanged(FOnPreviewMeshChanged::CreateSP(this, &FPhysicsAssetEditor::OnChangeDefaultMesh)); SharedData->Initialize(InPersonaPreviewScene); AAnimationEditorPreviewActor* Actor = InPersonaPreviewScene->GetWorld()->SpawnActor(AAnimationEditorPreviewActor::StaticClass(), FTransform::Identity); InPersonaPreviewScene->SetActor(Actor); // Create the preview component SharedData->EditorSkelComp = NewObject(Actor); SharedData->EditorSkelComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); SharedData->EditorSkelComp->SharedData = SharedData.Get(); SharedData->EditorSkelComp->SetSkeletalMesh(SharedData->PhysicsAsset->GetPreviewMesh()); SharedData->EditorSkelComp->SetPhysicsAsset(SharedData->PhysicsAsset, true); SharedData->EditorSkelComp->SetDisablePostProcessBlueprint(true); InPersonaPreviewScene->SetPreviewMeshComponent(SharedData->EditorSkelComp); InPersonaPreviewScene->AddComponent(SharedData->EditorSkelComp, FTransform::Identity); InPersonaPreviewScene->SetAdditionalMeshesSelectable(false); // set root component, so we can attach to it. Actor->SetRootComponent(SharedData->EditorSkelComp); SharedData->EditorSkelComp->Stop(); SharedData->EditorSkelComp->PreviewInstance = NewObject(SharedData->EditorSkelComp, TEXT("PhatAnimScriptInstance")); SharedData->PhysicalAnimationComponent = NewObject(Actor); SharedData->PhysicalAnimationComponent->SetSkeletalMeshComponent(SharedData->EditorSkelComp); InPersonaPreviewScene->AddComponent(SharedData->PhysicalAnimationComponent, FTransform::Identity); SharedData->ResetTM = SharedData->EditorSkelComp->GetComponentToWorld(); // Register handle component SharedData->MouseHandle->RegisterComponentWithWorld(InPersonaPreviewScene->GetWorld()); SharedData->EnableSimulation(false); // we need to make sure we monitor any change to the PhysicsState being recreated, as this can happen from path that is external to this class // (example: setting a property on a body that is type "simulated" will recreate the state from USkeletalBodySetup::PostEditChangeProperty and let the body simulating (UE-107308) SharedData->EditorSkelComp->RegisterOnPhysicsCreatedDelegate(FOnSkelMeshPhysicsCreated::CreateLambda([this]() { // let's make sure nothing is simulating and that all necessary state are in proper order SharedData->EnableSimulation(false); })); // Make sure the floor mesh has collision (BlockAllDynamic may have been overriden) static FName CollisionProfileName(TEXT("PhysicsActor")); UStaticMeshComponent* FloorMeshComponent = const_cast(InPersonaPreviewScene->GetFloorMeshComponent()); FloorMeshComponent->SetCollisionProfileName(CollisionProfileName); FloorMeshComponent->RecreatePhysicsState(); } void FPhysicsAssetEditor::HandleExtendContextMenu(FMenuBuilder& InMenuBuilder) { TArray> SelectedItems = SkeletonTree->GetSelectedItems(); FSkeletonTreeSelection Selection(SelectedItems); TArray> SelectedBodies = Selection.GetSelectedItems(); TArray> SelectedConstraints = Selection.GetSelectedItems(); TArray> SelectedShapes = Selection.GetSelectedItems(); TArray> SelectedBones = Selection.GetSelectedItemsByTypeId("FSkeletonTreeBoneItem"); if (SelectedBodies.Num() > 0) { BuildMenuWidgetBody(InMenuBuilder); } else if (SelectedShapes.Num() > 0) { BuildMenuWidgetPrimitives(InMenuBuilder); } else if(SelectedConstraints.Num() > 0) { BuildMenuWidgetConstraint(InMenuBuilder); } else if(SelectedBones.Num() > 0) { BuildMenuWidgetBone(InMenuBuilder); } BuildMenuWidgetSelection(InMenuBuilder); } void FPhysicsAssetEditor::HandleExtendFilterMenu(FMenuBuilder& InMenuBuilder) { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); InMenuBuilder.PushCommandList(SkeletonTreeCommandList.ToSharedRef()); InMenuBuilder.BeginSection(TEXT("PhysicsAssetFilters"), LOCTEXT("PhysicsAssetFiltersHeader", "Physics Asset Filters")); { InMenuBuilder.AddMenuEntry(Commands.ShowBodies); InMenuBuilder.AddMenuEntry(Commands.ShowSimulatedBodies); InMenuBuilder.AddMenuEntry(Commands.ShowKinematicBodies); InMenuBuilder.AddMenuEntry(Commands.ShowConstraints); InMenuBuilder.AddMenuEntry(Commands.ShowPrimitives); InMenuBuilder.AddSeparator(); InMenuBuilder.AddMenuEntry(Commands.ShowConstraintsOnParentBodies); } InMenuBuilder.EndSection(); InMenuBuilder.PopCommandList(); } void FPhysicsAssetEditor::HandleToggleShowBodies() { SkeletonTreeBuilder->bShowBodies = !SkeletonTreeBuilder->bShowBodies; RefreshFilter(); } void FPhysicsAssetEditor::HandleToggleShowSimulatedBodies() { SkeletonTreeBuilder->bShowSimulatedBodies = !SkeletonTreeBuilder->bShowSimulatedBodies; RefreshFilter(); } void FPhysicsAssetEditor::HandleToggleShowKinematicBodies() { SkeletonTreeBuilder->bShowKinematicBodies = !SkeletonTreeBuilder->bShowKinematicBodies; RefreshFilter(); } void FPhysicsAssetEditor::HandleToggleShowConstraints() { SkeletonTreeBuilder->bShowConstraints = !SkeletonTreeBuilder->bShowConstraints; RefreshFilter(); } void FPhysicsAssetEditor::HandleToggleShowConstraintsOnParentBodies() { SkeletonTreeBuilder->bShowConstraintsOnParentBodies = !SkeletonTreeBuilder->bShowConstraintsOnParentBodies; RefreshFilter(); } void FPhysicsAssetEditor::HandleToggleShowPrimitives() { SkeletonTreeBuilder->bShowPrimitives = !SkeletonTreeBuilder->bShowPrimitives; RefreshFilter(); } ECheckBoxState FPhysicsAssetEditor::GetShowBodiesChecked() const { return SkeletonTreeBuilder->bShowBodies ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FPhysicsAssetEditor::GetShowSimulatedBodiesChecked() const { return SkeletonTreeBuilder->bShowSimulatedBodies ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FPhysicsAssetEditor::GetShowKinematicBodiesChecked() const { return SkeletonTreeBuilder->bShowKinematicBodies ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FPhysicsAssetEditor::GetShowConstraintsChecked() const { return SkeletonTreeBuilder->bShowConstraints ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } bool FPhysicsAssetEditor::IsShowConstraintsChecked() const { return SkeletonTreeBuilder->bShowConstraints; } ECheckBoxState FPhysicsAssetEditor::GetShowConstraintsOnParentBodiesChecked() const { return SkeletonTreeBuilder->bShowConstraintsOnParentBodies ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FPhysicsAssetEditor::GetShowPrimitivesChecked() const { return SkeletonTreeBuilder->bShowPrimitives ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FPhysicsAssetEditor::HandleGetFilterLabel(TArray& InOutItems) const { if(SkeletonTreeBuilder->bShowBodies) { InOutItems.Add(LOCTEXT("BodiesFilterLabel", "Bodies")); } if(SkeletonTreeBuilder->bShowConstraints) { InOutItems.Add(LOCTEXT("ConstraintsFilterLabel", "Constraints")); } if(SkeletonTreeBuilder->bShowPrimitives) { InOutItems.Add(LOCTEXT("PrimitivesFilterLabel", "Primitives")); } } void FPhysicsAssetEditor::RefreshFilter() { SkeletonTree->RefreshFilter(); // make sure we resynchronize the list HandleViewportSelectionChanged(SharedData->SelectedBodies, SharedData->SelectedConstraints); } void FPhysicsAssetEditor::HandleCreateNewConstraint(int32 BodyIndex0, int32 BodyIndex1) { if(BodyIndex0 != BodyIndex1) { SharedData->MakeNewConstraint(BodyIndex0, BodyIndex1); } } void FPhysicsAssetEditor::RecreatePhysicsState() { // Flush geometry cache inside the asset (don't want to use cached version of old geometry!) SharedData->PhysicsAsset->InvalidateAllPhysicsMeshes(); SharedData->EditorSkelComp->RecreatePhysicsState(); SharedData->EditorSkelComp->RecreateClothingActors(); // Reset simulation state of body instances so we dont actually simulate outside of 'simulation mode' SharedData->EnableSimulation(false); } TSharedRef FPhysicsAssetEditor::MakeConstraintScaleWidget() { return SNew(SBox) .HAlign(HAlign_Right) [ SNew(SBox) .Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f)) .WidthOverride(100.0f) [ SNew(SNumericEntryBox) .Font(FAppStyle::GetFontStyle(TEXT("MenuItem.Font"))) .AllowSpin(true) .MinSliderValue(0.0f) .MaxSliderValue(4.0f) .Value_Lambda([this]() { return SharedData->EditorOptions->ConstraintDrawSize; }) .OnValueChanged_Lambda([this](float InValue) { SharedData->EditorOptions->ConstraintDrawSize = InValue; }) .OnValueCommitted_Lambda([this](float InValue, ETextCommit::Type InCommitType) { SharedData->EditorOptions->ConstraintDrawSize = InValue; SharedData->EditorOptions->SaveConfig(); ViewportCommandList->WidgetInteraction(TEXT("ConstraintScaleWidget")); }) ] ]; } TSharedRef FPhysicsAssetEditor::MakeCollisionOpacityWidget() { return SNew(SBox) .HAlign(HAlign_Right) [ SNew(SBox) .Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f)) .WidthOverride(100.0f) [ SNew(SNumericEntryBox) .Font(FAppStyle::GetFontStyle(TEXT("MenuItem.Font"))) .AllowSpin(true) .MinValue(0.0f) .MaxValue(1.0f) .MinSliderValue(0.0f) .MaxSliderValue(1.0f) .Value_Lambda([this]() { return SharedData->EditorOptions->CollisionOpacity; }) .OnValueChanged_Lambda([this](float InValue) { SharedData->EditorOptions->CollisionOpacity = InValue; }) .OnValueCommitted_Lambda([this](float InValue, ETextCommit::Type InCommitType) { SharedData->EditorOptions->CollisionOpacity = InValue; SharedData->EditorOptions->SaveConfig(); ViewportCommandList->WidgetInteraction(TEXT("CollisionOpacityWidget")); }) ] ]; } #undef LOCTEXT_NAMESPACE