// Copyright Epic Games, Inc. All Rights Reserved. #include "PhysicsAssetEditorMode.h" #include "PhysicsAssetEditor.h" #include "ISkeletonTree.h" #include "IPersonaPreviewScene.h" #include "PersonaModule.h" #include "ISkeletonEditorModule.h" #include "PhysicsAssetGraphSummoner.h" #include "PhysicsEngine/PhysicsAsset.h" #include "PhysicsEngine/PhysicsConstraintTemplate.h" #include "PhysicsAssetEditorActions.h" #include "SEditorViewportToolBarMenu.h" #include "PhysicsAssetEditorProfilesSummoner.h" #include "PropertyEditorModule.h" #include "Preferences/PhysicsAssetEditorOptions.h" #include "Widgets/Input/SSpinBox.h" #include "Modules/ModuleManager.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "PhysicsAssetEditorToolsSummoner.h" #include "UICommandList_Pinnable.h" #include "IPersonaViewport.h" #include "IPinnedCommandList.h" #define LOCTEXT_NAMESPACE "PhysicsAssetEditorMode" static const FName PhysicsAssetEditorPreviewViewportName("Viewport"); static const FName PhysicsAssetEditorPropertiesName("DetailsTab"); static const FName PhysicsAssetEditorHierarchyName("SkeletonTreeView"); static const FName PhysicsAssetEditorGraphName("PhysicsAssetGraphView"); static const FName PhysicsAssetEditorProfilesName("PhysicsAssetProfilesView"); static const FName PhysicsAssetEditorToolsName("PhysicsAssetTools"); static const FName PhysicsAssetEditorAdvancedPreviewName(TEXT("AdvancedPreviewTab")); FText BuildPhysicsAssetShapeTypeCountText(const UPhysicsAsset* const PhysicsAsset) { // Find the number of each type of shape in the asset and record them in a string. if (PhysicsAsset) { // Find the number of each type of primitive in the physics asset. TMap ShapeCount; ShapeCount.Add(EAggCollisionShape::Sphere, 0); ShapeCount.Add(EAggCollisionShape::Box, 0); ShapeCount.Add(EAggCollisionShape::Sphyl, 0); ShapeCount.Add(EAggCollisionShape::Convex, 0); ShapeCount.Add(EAggCollisionShape::TaperedCapsule, 0); int32 TotalShapeCount = 0; for (const TObjectPtr & SkeletalBodySetup : PhysicsAsset->SkeletalBodySetups) { if (SkeletalBodySetup) { for (auto& Element : ShapeCount) { const int32 Count = SkeletalBodySetup->AggGeom.GetElementCount(Element.Key); Element.Value += Count; TotalShapeCount += Count; } } } // Create a text element for each value that will be included in the output. FFormatOrderedArguments FormatArgs; auto AddToFormatArgs = [&ShapeCount, &FormatArgs](const EAggCollisionShape::Type InShapeType, const FTextFormat& InTextFormat) { if (const int32 CurrentShapeTypeCount = ShapeCount[InShapeType]) { FormatArgs.Add(FText::Format(InTextFormat, CurrentShapeTypeCount)); } }; FormatArgs.Add(FText::Format(LOCTEXT("PrimitiveShapeCount", "{0} {0}|plural(one=Primitive,other=Primitives)"), TotalShapeCount)); AddToFormatArgs(EAggCollisionShape::Sphere, LOCTEXT("PrimitiveSphereCount", "{0} {0}|plural(one=Sphere,other=Spheres)")); AddToFormatArgs(EAggCollisionShape::Box, LOCTEXT("PrimitiveBoxCount", "{0} {0}|plural(one=Box,other=Boxes)")); AddToFormatArgs(EAggCollisionShape::Sphyl, LOCTEXT("PrimitiveSphylCount", "{0} {0}|plural(one=Capsule,other=Capsules)")); AddToFormatArgs(EAggCollisionShape::Convex, LOCTEXT("PrimitiveConvexCount", "{0} {0}|plural(one=Convex Element,other=Convex Elements)")); AddToFormatArgs(EAggCollisionShape::TaperedCapsule, LOCTEXT("PrimitiveTaperedCapsuleCount", "{0} {0}|plural(one=Tapered Capsule,other=Tapered Capsules)")); // Build format string for the number of accumulated elements. FString FormatStr = "{0}"; if (FormatArgs.Num() > 1) { FormatStr += ": ("; for (uint32 Index = 1, Max = FormatArgs.Num(); Index < Max; ++Index) { FormatStr += "{" + FString::FromInt(Index) + "}, "; } FormatStr.RemoveFromEnd(" "); FormatStr.RemoveFromEnd(","); FormatStr += ")"; } return FText::Format(FText::FromString(FormatStr), FormatArgs); } return FText(); } FText BuildCrossConstraintCountText(const UPhysicsAsset* const PhysicsAsset) { // Find the number of cross constraints in the asset and record them in a string. if (PhysicsAsset) { uint32 CrossConstraintCount = 0; const USkeletalMesh* const SkeletalMesh = PhysicsAsset->GetPreviewMesh(); if (SkeletalMesh) { const FReferenceSkeleton& RefSkeleton = SkeletalMesh->GetRefSkeleton(); for (TObjectPtr CurrentConstraintSetup : PhysicsAsset->ConstraintSetup) { if (CurrentConstraintSetup) { const int32 ChildBoneIndex = RefSkeleton.FindBoneIndex(CurrentConstraintSetup->DefaultInstance.GetChildBoneName()); const int32 ParentBoneIndex = RefSkeleton.FindBoneIndex(CurrentConstraintSetup->DefaultInstance.GetParentBoneName()); if (ParentBoneIndex != RefSkeleton.GetParentIndex(ChildBoneIndex)) { ++CrossConstraintCount; } } } } if (CrossConstraintCount > 0) { return FText::Format(LOCTEXT("CrossConstraintCount", "({0} {0}|plural(one=Cross Constraint,other=Cross Constraints))"), CrossConstraintCount); } } return FText(); } uint32 CalculateTotalNumberOfCollisionIteractions(const UPhysicsAsset* const PhysicsAsset) { uint32 Result = 0; // Find total number of collision iteractions. if (PhysicsAsset) { const uint32 BodyCount = PhysicsAsset->SkeletalBodySetups.Num(); const uint32 PotentialCollisionCount = (BodyCount * (BodyCount - 1)) / 2; const uint32 IgnoredCollisionCount = PhysicsAsset->CollisionDisableTable.Num(); Result = PotentialCollisionCount - IgnoredCollisionCount; } return Result; } FPhysicsAssetEditorMode::FPhysicsAssetEditorMode(TSharedRef InHostingApp, TSharedRef InSkeletonTree, TSharedRef InPreviewScene) : FApplicationMode(PhysicsAssetEditorModes::PhysicsAssetEditorMode) { PhysicsAssetEditorPtr = StaticCastSharedRef(InHostingApp); TSharedRef PhysicsAssetEditor = StaticCastSharedRef(InHostingApp); ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::LoadModuleChecked("SkeletonEditor"); TabFactories.RegisterFactory(SkeletonEditorModule.CreateSkeletonTreeTabFactory(InHostingApp, InSkeletonTree)); FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked("Persona"); TabFactories.RegisterFactory(PersonaModule.CreateDetailsTabFactory(InHostingApp, FOnDetailsCreated::CreateSP(&PhysicsAssetEditor.Get(), &FPhysicsAssetEditor::HandleDetailsCreated))); auto ExtendShowMenu = [this](FMenuBuilder& InMenuBuilder) { const FPhysicsAssetEditorCommands& Actions = FPhysicsAssetEditorCommands::Get(); InMenuBuilder.PushCommandList(PhysicsAssetEditorPtr.Pin()->GetViewportCommandList().ToSharedRef()); InMenuBuilder.BeginSection("PhysicsAssetShowCommands", LOCTEXT("PhysicsShowCommands", "Physics Rendering")); { InMenuBuilder.AddMenuEntry(Actions.ToggleMassProperties); // Mesh, collision and constraint rendering modes struct Local { static void BuildMeshRenderModeMenu(FMenuBuilder& InSubMenuBuilder) { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); InSubMenuBuilder.BeginSection("PhysicsAssetEditorRenderingMode", LOCTEXT("MeshRenderModeHeader", "Mesh Drawing (Edit)")); { InSubMenuBuilder.AddMenuEntry(Commands.MeshRenderingMode_Solid); InSubMenuBuilder.AddMenuEntry(Commands.MeshRenderingMode_Wireframe); InSubMenuBuilder.AddMenuEntry(Commands.MeshRenderingMode_None); } InSubMenuBuilder.EndSection(); InSubMenuBuilder.BeginSection("PhysicsAssetEditorRenderingModeSim", LOCTEXT("MeshRenderModeSimHeader", "Mesh Drawing (Simulation)")); { InSubMenuBuilder.AddMenuEntry(Commands.MeshRenderingMode_Simulation_Solid); InSubMenuBuilder.AddMenuEntry(Commands.MeshRenderingMode_Simulation_Wireframe); InSubMenuBuilder.AddMenuEntry(Commands.MeshRenderingMode_Simulation_None); } InSubMenuBuilder.EndSection(); } static void BuildCollisionRenderModeMenu(FMenuBuilder& InSubMenuBuilder, TWeakPtr PhysicsAssetEditorPtr) { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); InSubMenuBuilder.BeginSection("PhysicsAssetEditorCollisionRenderSettings", LOCTEXT("CollisionRenderSettingsHeader", "Body Drawing")); { InSubMenuBuilder.AddMenuEntry(Commands.RenderOnlySelectedSolid); InSubMenuBuilder.AddMenuEntry(Commands.HideSimulatedBodies); InSubMenuBuilder.AddMenuEntry(Commands.HideKinematicBodies); InSubMenuBuilder.AddWidget(PhysicsAssetEditorPtr.Pin()->MakeCollisionOpacityWidget(), LOCTEXT("CollisionOpacityLabel", "Collision Opacity")); } InSubMenuBuilder.EndSection(); InSubMenuBuilder.BeginSection("PhysicsAssetEditorCollisionMode", LOCTEXT("CollisionRenderModeHeader", "Body Drawing (Edit)")); { InSubMenuBuilder.AddMenuEntry(Commands.CollisionRenderingMode_Solid); InSubMenuBuilder.AddMenuEntry(Commands.CollisionRenderingMode_Wireframe); InSubMenuBuilder.AddMenuEntry(Commands.CollisionRenderingMode_SolidWireframe); InSubMenuBuilder.AddMenuEntry(Commands.CollisionRenderingMode_None); } InSubMenuBuilder.EndSection(); InSubMenuBuilder.BeginSection("PhysicsAssetEditorCollisionModeSim", LOCTEXT("CollisionRenderModeSimHeader", "Body Drawing (Simulation)")); { InSubMenuBuilder.AddMenuEntry(Commands.CollisionRenderingMode_Simulation_Solid); InSubMenuBuilder.AddMenuEntry(Commands.CollisionRenderingMode_Simulation_Wireframe); InSubMenuBuilder.AddMenuEntry(Commands.CollisionRenderingMode_Simulation_SolidWireframe); InSubMenuBuilder.AddMenuEntry(Commands.CollisionRenderingMode_Simulation_None); } InSubMenuBuilder.EndSection(); } static void BuildConstraintRenderModeMenu(FMenuBuilder& InSubMenuBuilder, TWeakPtr PhysicsAssetEditorPtr) { const FPhysicsAssetEditorCommands& Commands = FPhysicsAssetEditorCommands::Get(); InSubMenuBuilder.BeginSection("PhysicsAssetEditorConstraints", LOCTEXT("ConstraintHeader", "Constraints")); { InSubMenuBuilder.AddMenuEntry(Commands.DrawConstraintsAsPoints); InSubMenuBuilder.AddMenuEntry(Commands.RenderOnlySelectedConstraints); InSubMenuBuilder.AddWidget(PhysicsAssetEditorPtr.Pin()->MakeConstraintScaleWidget(), LOCTEXT("ConstraintScaleLabel", "Constraint Scale")); } InSubMenuBuilder.EndSection(); InSubMenuBuilder.BeginSection("PhysicsAssetEditorConstraintMode", LOCTEXT("ConstraintRenderModeHeader", "Constraint Drawing (Edit)")); { InSubMenuBuilder.AddMenuEntry(Commands.ConstraintRenderingMode_None); InSubMenuBuilder.AddMenuEntry(Commands.ConstraintRenderingMode_AllPositions); InSubMenuBuilder.AddMenuEntry(Commands.ConstraintRenderingMode_AllLimits); } InSubMenuBuilder.EndSection(); InSubMenuBuilder.BeginSection("PhysicsAssetEditorConstraintModeSim", LOCTEXT("ConstraintRenderModeSimHeader", "Constraint Drawing (Simulation)")); { InSubMenuBuilder.AddMenuEntry(Commands.ConstraintRenderingMode_Simulation_None); InSubMenuBuilder.AddMenuEntry(Commands.ConstraintRenderingMode_Simulation_AllPositions); InSubMenuBuilder.AddMenuEntry(Commands.ConstraintRenderingMode_Simulation_AllLimits); } InSubMenuBuilder.EndSection(); } }; InMenuBuilder.AddSubMenu(LOCTEXT("MeshRenderModeSubMenu", "Mesh"), FText::GetEmpty(), FNewMenuDelegate::CreateStatic(&Local::BuildMeshRenderModeMenu)); InMenuBuilder.AddSubMenu(LOCTEXT("CollisionRenderModeSubMenu", "Bodies"), FText::GetEmpty(), FNewMenuDelegate::CreateStatic(&Local::BuildCollisionRenderModeMenu, PhysicsAssetEditorPtr)); InMenuBuilder.AddSubMenu(LOCTEXT("ConstraintRenderModeSubMenu", "Constraints"), FText::GetEmpty(), FNewMenuDelegate::CreateStatic(&Local::BuildConstraintRenderModeMenu, PhysicsAssetEditorPtr)); } InMenuBuilder.EndSection(); InMenuBuilder.PopCommandList(); }; auto ExtendMenuBar = [this](FMenuBuilder& InMenuBuilder) { FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bAllowSearch = false; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; TSharedPtr DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs); DetailsView->SetObject(PhysicsAssetEditorPtr.Pin()->GetSharedData()->EditorOptions); DetailsView->OnFinishedChangingProperties().AddLambda([this](const FPropertyChangedEvent& InEvent) { PhysicsAssetEditorPtr.Pin()->GetSharedData()->EditorOptions->SaveConfig(); }); InMenuBuilder.AddWidget(DetailsView.ToSharedRef(), FText(), true); }; TArray> ViewportExtenders; ViewportExtenders.Add(MakeShared()); ViewportExtenders[0]->AddMenuExtension("AnimViewportSceneElements", EExtensionHook::Before, PhysicsAssetEditor->GetToolkitCommands(), FMenuExtensionDelegate::CreateLambda(ExtendShowMenu)); ViewportExtenders[0]->AddMenuExtension("AnimViewportPhysicsMenu", EExtensionHook::After, PhysicsAssetEditor->GetToolkitCommands(), FMenuExtensionDelegate::CreateLambda(ExtendMenuBar)); FPersonaViewportArgs ViewportArgs(InPreviewScene); ViewportArgs.bAlwaysShowTransformToolbar = true; ViewportArgs.bShowStats = false; ViewportArgs.bShowTurnTable = false; ViewportArgs.bShowPhysicsMenu = true; ViewportArgs.Extenders = ViewportExtenders; ViewportArgs.OnViewportCreated = FOnViewportCreated::CreateLambda([this](const TSharedRef& InViewport) { // Setup bindings with the recent commands bar TWeakPtr WeakViewport = InViewport; InViewport->GetPinnedCommandList()->BindCommandList(PhysicsAssetEditorPtr.Pin()->GetViewportCommandList().ToSharedRef()); InViewport->GetPinnedCommandList()->RegisterCustomWidget(IPinnedCommandList::FOnGenerateCustomWidget::CreateSP(PhysicsAssetEditorPtr.Pin().Get(), &FPhysicsAssetEditor::MakeConstraintScaleWidget), TEXT("ConstraintScaleWidget"), LOCTEXT("ConstraintScaleLabel", "Constraint Scale")); InViewport->GetPinnedCommandList()->RegisterCustomWidget(IPinnedCommandList::FOnGenerateCustomWidget::CreateSP(PhysicsAssetEditorPtr.Pin().Get(), &FPhysicsAssetEditor::MakeCollisionOpacityWidget), TEXT("CollisionOpacityWidget"), LOCTEXT("CollisionOpacityLabel", "Collision Opacity")); }); ViewportArgs.OnGetViewportText = FOnGetViewportText::CreateLambda([this](EViewportCorner InViewportCorner) { if(InViewportCorner == EViewportCorner::TopLeft) { TSharedPtr SharedData = PhysicsAssetEditorPtr.Pin()->GetSharedData(); // Write physics asset summary at the top of the view port. return FText::Format( NSLOCTEXT("UnrealEd", "BodiesConstraints_F", "{0} Bodies ({1} Considered for bounds, {2}%)\n{3}\n{4} Constraints{5}\n{6} Collision Interactions"), FText::AsNumber(SharedData->PhysicsAsset->SkeletalBodySetups.Num()), FText::AsNumber(SharedData->PhysicsAsset->BoundsBodies.Num()), FText::AsNumber(static_cast(SharedData->PhysicsAsset->BoundsBodies.Num()) / static_cast(SharedData->PhysicsAsset->SkeletalBodySetups.Num()) * 100.0f), BuildPhysicsAssetShapeTypeCountText(SharedData->PhysicsAsset), FText::AsNumber(SharedData->PhysicsAsset->ConstraintSetup.Num()), BuildCrossConstraintCountText(SharedData->PhysicsAsset), FText::AsNumber(CalculateTotalNumberOfCollisionIteractions(SharedData->PhysicsAsset))); } return FText(); }); ViewportArgs.ContextName = TEXT("PhysicsAssetEditor.Viewport"); TabFactories.RegisterFactory(PersonaModule.CreatePersonaViewportTabFactory(InHostingApp, ViewportArgs)); TabFactories.RegisterFactory(PersonaModule.CreateAdvancedPreviewSceneTabFactory(InHostingApp, InPreviewScene)); TabFactories.RegisterFactory(MakeShared(InHostingApp, CastChecked((*PhysicsAssetEditor->GetObjectsCurrentlyBeingEdited())[0]), InSkeletonTree->GetEditableSkeleton(), FOnPhysicsAssetGraphCreated::CreateSP(&PhysicsAssetEditor.Get(), &FPhysicsAssetEditor::HandlePhysicsAssetGraphCreated), FOnGraphObjectsSelected::CreateSP(&PhysicsAssetEditor.Get(), &FPhysicsAssetEditor::HandleGraphObjectsSelected))); TabFactories.RegisterFactory(MakeShared(InHostingApp, CastChecked((*PhysicsAssetEditor->GetObjectsCurrentlyBeingEdited())[0]))); TabFactories.RegisterFactory(MakeShared(InHostingApp)); TabLayout = FTabManager::NewLayout("Standalone_PhysicsAssetEditor_Layout_v5.3") ->AddArea ( FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewSplitter() ->SetSizeCoefficient(0.9f) ->SetOrientation(Orient_Horizontal) ->Split ( FTabManager::NewSplitter() ->SetSizeCoefficient(0.2f) ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.6f) ->AddTab(PhysicsAssetEditorHierarchyName, ETabState::OpenedTab) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.4f) ->AddTab(PhysicsAssetEditorGraphName, ETabState::OpenedTab) ) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.6f) ->SetHideTabWell(true) ->AddTab(PhysicsAssetEditorPreviewViewportName, ETabState::OpenedTab) ) ->Split ( FTabManager::NewSplitter() ->SetSizeCoefficient(0.2f) ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.6f) ->AddTab(PhysicsAssetEditorPropertiesName, ETabState::OpenedTab) ->AddTab(PhysicsAssetEditorAdvancedPreviewName, ETabState::OpenedTab) ->SetForegroundTab(PhysicsAssetEditorPropertiesName) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.4f) ->AddTab(PhysicsAssetEditorToolsName, ETabState::OpenedTab) ->AddTab(PhysicsAssetEditorProfilesName, ETabState::OpenedTab) ->SetForegroundTab(PhysicsAssetEditorToolsName) ) ) ) ); PersonaModule.OnRegisterTabs().Broadcast(TabFactories, InHostingApp); LayoutExtender = MakeShared(); PersonaModule.OnRegisterLayoutExtensions().Broadcast(*LayoutExtender.Get()); TabLayout->ProcessExtensions(*LayoutExtender.Get()); } void FPhysicsAssetEditorMode::RegisterTabFactories(TSharedPtr InTabManager) { TSharedPtr PhysicsAssetEditor = PhysicsAssetEditorPtr.Pin(); PhysicsAssetEditor->RegisterTabSpawners(InTabManager.ToSharedRef()); PhysicsAssetEditor->PushTabFactories(TabFactories); FApplicationMode::RegisterTabFactories(InTabManager); } #undef LOCTEXT_NAMESPACE