// Copyright Epic Games, Inc. All Rights Reserved. #include "PhysicsAssetEditorSharedData.h" #include "PhysicsAssetEditorPhysicsHandleComponent.h" #include "PhysicsAssetRenderUtils.h" #include "PhysicsEngine/RigidBodyIndexPair.h" #include "Misc/MessageDialog.h" #include "Modules/ModuleManager.h" #include "UObject/UObjectIterator.h" #include "Widgets/SWindow.h" #include "Components/StaticMeshComponent.h" #include "Engine/SkeletalMesh.h" #include "Components/SkeletalMeshComponent.h" #include "Preferences/PhysicsAssetEditorOptions.h" #include "Engine/StaticMesh.h" #include "Engine/CollisionProfile.h" #include "Editor.h" #include "PhysicsAssetEditorModule.h" #include "EditorSupportDelegates.h" #include "ScopedTransaction.h" #include "PhysicsAssetEditorSkeletalMeshComponent.h" #include "MeshUtilities.h" #include "MeshUtilitiesCommon.h" #include "PhysicsEngine/BoxElem.h" #include "PhysicsEngine/ConstraintInstance.h" #include "PhysicsEngine/PhysicsConstraintTemplate.h" #include "PhysicsEngine/PhysicalAnimationComponent.h" #include "PhysicsEngine/PhysicsAsset.h" #include "PhysicsAssetEditorAnimInstance.h" #include "IPersonaPreviewScene.h" #include "PhysicsPublic.h" #include "PhysicsAssetGenerationSettings.h" #include "IDetailsView.h" #include "PropertyEditorModule.h" #include "Widgets/Input/SButton.h" #include "Widgets/Text/STextBlock.h" #include "ClothingSimulationInteractor.h" #include "UnrealExporter.h" #include "Exporters/Exporter.h" #include "Factories.h" #include "HAL/PlatformApplicationMisc.h" #include "SPrimaryButton.h" #define LOCTEXT_NAMESPACE "PhysicsAssetEditorShared" //PRAGMA_DISABLE_OPTIMIZATION namespace SharedDataConstants { const FString ConstraintType = TEXT("Constraint"); const FString BodyType = TEXT("SkeletalBodySetup"); } FScopedBulkSelection::FScopedBulkSelection(TSharedPtr InSharedData) : SharedData(InSharedData) { SharedData->bSuspendSelectionBroadcast = true; } FScopedBulkSelection::~FScopedBulkSelection() { SharedData->bSuspendSelectionBroadcast = false; SharedData->BroadcastSelectionChanged(); } FPhysicsAssetEditorSharedData::FPhysicsAssetEditorSharedData() : COMRenderColor(255,255,100) , bSuspendSelectionBroadcast(false) , InsideSelChange(0) { // Editor variables bShowCOM = false; bRunningSimulation = false; bNoGravitySimulation = false; bManipulating = false; LastClickPos = FIntPoint::ZeroValue; LastClickOrigin = FVector::ZeroVector; LastClickDirection = FVector::UpVector; LastClickHitPos = FVector::ZeroVector; LastClickHitNormal = FVector::UpVector; bLastClickHit = false; // Construct mouse handle MouseHandle = NewObject(); // Construct sim options. EditorOptions = NewObject(GetTransientPackage(), MakeUniqueObjectName(GetTransientPackage(), UPhysicsAssetEditorOptions::StaticClass(), FName(TEXT("EditorOptions"))), RF_Transactional); check(EditorOptions); EditorOptions->LoadConfig(); } FPhysicsAssetEditorSharedData::~FPhysicsAssetEditorSharedData() { } void FPhysicsAssetEditorSharedData::Initialize(const TSharedRef& InPreviewScene) { PreviewScene = InPreviewScene; EditorSkelComp = nullptr; PhysicalAnimationComponent = nullptr; FSoftObjectPath PreviewMeshStringRef = PhysicsAsset->PreviewSkeletalMesh.ToSoftObjectPath(); // Look for body setups with no shapes (how does this happen?). // If we find one- just bang on a default box. bool bFoundEmptyShape = false; for (int32 i = 0; i SkeletalBodySetups.Num(); ++i) { UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[i]; if (BodySetup && BodySetup->AggGeom.GetElementCount() == 0) { FKBoxElem BoxElem; BoxElem.SetTransform(FTransform::Identity); BoxElem.X = 15.f; BoxElem.Y = 15.f; BoxElem.Z = 15.f; BodySetup->AggGeom.BoxElems.Add(BoxElem); check(BodySetup->AggGeom.BoxElems.Num() == 1); bFoundEmptyShape = true; } } // Pop up a warning about what we did. if (bFoundEmptyShape) { FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "EmptyBodyFound", "Bodies was found with no primitives!\nThey have been reset to have a box.")); } IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked("MeshUtilities"); // Used for viewing bone influences, resetting bone geometry etc. USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh) { MeshUtilities.CalcBoneVertInfos(EditorSkelMesh, DominantWeightBoneInfos, true); MeshUtilities.CalcBoneVertInfos(EditorSkelMesh, AnyWeightBoneInfos, false); // Ensure PhysicsAsset mass properties are up to date. PhysicsAsset->UpdateBoundsBodiesArray(); // Check if there are any bodies in the Asset which do not have bones in the skeletal mesh. // If so, put up a warning. TArray MissingBodyIndices; FString BoneNames; for (int32 i = 0; i SkeletalBodySetups.Num(); ++i) { if (!ensure(PhysicsAsset->SkeletalBodySetups[i])) { continue; } FName BoneName = PhysicsAsset->SkeletalBodySetups[i]->BoneName; int32 BoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(BoneName); if (BoneIndex == INDEX_NONE) { MissingBodyIndices.Add( i ); BoneNames += FString::Printf(TEXT("\t%s\n"), *BoneName.ToString()); } } const FText MissingBodyMsg = FText::Format( LOCTEXT( "MissingBones", "The following Bodies are in the PhysicsAsset, but have no corresponding bones in the SkeletalMesh.\nClick OK to delete them, or Cancel to ignore.\n\n{0}" ), FText::FromString( BoneNames ) ); if ( MissingBodyIndices.Num() ) { if ( FMessageDialog::Open( EAppMsgType::OkCancel, MissingBodyMsg ) == EAppReturnType::Ok ) { // Delete the bodies with no associated bones const FScopedTransaction Transaction( LOCTEXT( "DeleteUnusedPhysicsBodies", "Delete Physics Bodies With No Bones" ) ); PhysicsAsset->SetFlags(RF_Transactional); PhysicsAsset->Modify(); // Iterate backwards, as PhysicsAsset->SkeletalBodySetups is a TArray and Unreal containers don't support remove_if() for ( int32 i = MissingBodyIndices.Num() - 1; i >= 0; --i ) { DeleteBody( MissingBodyIndices[i], false ); } } } } // Support undo/redo PhysicsAsset->SetFlags(RF_Transactional); ClearSelectedBody(); ClearSelectedConstraints(); } void FPhysicsAssetEditorSharedData::BroadcastSelectionChanged() { if (!bSuspendSelectionBroadcast) { SelectionChangedEvent.Broadcast(SelectedBodies, SelectedConstraints); } } void FPhysicsAssetEditorSharedData::BroadcastHierarchyChanged() { HierarchyChangedEvent.Broadcast(); } void FPhysicsAssetEditorSharedData::BroadcastPreviewChanged() { PreviewChangedEvent.Broadcast(); } void FPhysicsAssetEditorSharedData::CachePreviewMesh() { USkeletalMesh* PreviewMesh = PhysicsAsset->PreviewSkeletalMesh.LoadSynchronous(); if (PreviewMesh == nullptr) { // Fall back to the default skeletal mesh in the EngineMeshes package. // This is statically loaded as the package is likely not fully loaded // (otherwise, it would have been found in the above iteration). PreviewMesh = (USkeletalMesh*)StaticLoadObject(USkeletalMesh::StaticClass(), NULL, TEXT("/Engine/EngineMeshes/SkeletalCube.SkeletalCube"), NULL, LOAD_None, NULL); check(PreviewMesh); PhysicsAsset->PreviewSkeletalMesh = PreviewMesh; FMessageDialog::Open(EAppMsgType::Ok, FText::Format( LOCTEXT("Error_PhysicsAssetHasNoSkelMesh", "Warning: Physics Asset has no skeletal mesh assigned.\nFor now, a simple default skeletal mesh ({0}) will be used.\nYou can fix this by opening the asset and choosing another skeletal mesh from the toolbar."), FText::FromString(PreviewMesh->GetFullName()))); } else if(PreviewMesh->GetSkeleton() == nullptr) { // Fall back in the case of a deleted skeleton PreviewMesh = (USkeletalMesh*)StaticLoadObject(USkeletalMesh::StaticClass(), NULL, TEXT("/Engine/EngineMeshes/SkeletalCube.SkeletalCube"), NULL, LOAD_None, NULL); check(PreviewMesh); PhysicsAsset->PreviewSkeletalMesh = PreviewMesh; FMessageDialog::Open(EAppMsgType::Ok, FText::Format( LOCTEXT("Error_PhysicsAssetHasNoSkelMeshSkeleton", "Warning: Physics Asset has a skeletal mesh with no skeleton assigned.\nFor now, a simple default skeletal mesh ({0}) will be used.\nYou can fix this by opening the asset and choosing another skeletal mesh from the toolbar, or repairing the skeleton."), FText::FromString(PreviewMesh->GetFullName()))); } } void FPhysicsAssetEditorSharedData::CopyConstraintProperties(const UPhysicsConstraintTemplate * FromConstraintSetup, UPhysicsConstraintTemplate * ToConstraintSetup, bool bKeepOldRotation) { ToConstraintSetup->Modify(); FConstraintInstance OldInstance = ToConstraintSetup->DefaultInstance; ToConstraintSetup->DefaultInstance.CopyConstraintPhysicalPropertiesFrom(&FromConstraintSetup->DefaultInstance, /*bKeepPosition=*/true, bKeepOldRotation); ToConstraintSetup->UpdateProfileInstance(); } void FPhysicsAssetEditorSharedData::CopyToClipboard(const FString& ObjectType, UObject* Object) { FSoftObjectPath PhysicsAssetPath(PhysicsAsset); FSoftObjectPath ObjectAssetPath(Object); FString ClipboardContent = FString::Format(TEXT("{0};{1};{2}"), { PhysicsAssetPath.ToString(), *ObjectType, ObjectAssetPath.ToString() }); FPlatformApplicationMisc::ClipboardCopy(*ClipboardContent); } bool FPhysicsAssetEditorSharedData::PasteFromClipboard(const FString& InObjectType, UPhysicsAsset*& OutAsset, UObject*& OutObject) { FString SourceObjectType; return ParseClipboard(OutAsset, SourceObjectType, OutObject) && SourceObjectType == InObjectType; } void FPhysicsAssetEditorSharedData::ConditionalClearClipboard(const FString& ObjectType, UObject* Object) { UPhysicsAsset* SourceAsset = nullptr; FString SourceObjectType; UObject* SourceObject = nullptr; if(ParseClipboard(SourceAsset, SourceObjectType, SourceObject)) { // Clear the clipboard if it matches the parameters we're given if (SourceAsset == PhysicsAsset && SourceObjectType == ObjectType && SourceObject == Object) { FString EmptyString; FPlatformApplicationMisc::ClipboardCopy(*EmptyString); } } } bool FPhysicsAssetEditorSharedData::ClipboardHasCompatibleData() { UPhysicsAsset* DummyAsset = nullptr; FString DummyObjectType; UObject* DummyObject = nullptr; return ParseClipboard(DummyAsset, DummyObjectType, DummyObject); } bool FPhysicsAssetEditorSharedData::ParseClipboard(UPhysicsAsset*& OutAsset, FString& OutObjectType, UObject*& OutObject) { FString ClipboardContent; FPlatformApplicationMisc::ClipboardPaste(ClipboardContent); TArray ParsedString; ClipboardContent.ParseIntoArray(ParsedString, TEXT(";"), true); if (ParsedString.Num() != 3) { return false; } FSoftObjectPath PhysicsAssetPath(ParsedString[0]); OutAsset = Cast(PhysicsAssetPath.ResolveObject()); if (!OutAsset) { return false; } OutObjectType = ParsedString[1]; FSoftObjectPath ObjectAssetPath(ParsedString[2]); OutObject = ObjectAssetPath.ResolveObject(); return OutObject != nullptr; } struct FMirrorInfo { FName BoneName; int32 BoneIndex; int32 BodyIndex; int32 ConstraintIndex; FMirrorInfo() { BoneIndex = INDEX_NONE; BodyIndex = INDEX_NONE; ConstraintIndex = INDEX_NONE; BoneName = NAME_None; } }; void FPhysicsAssetEditorSharedData::Mirror() { USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh) { TArray MirrorInfos; for (const FSelection& Selection : SelectedBodies) { MirrorInfos.AddUninitialized(); FMirrorInfo & MirrorInfo = MirrorInfos[MirrorInfos.Num() - 1]; MirrorInfo.BoneName = PhysicsAsset->SkeletalBodySetups[Selection.Index]->BoneName; MirrorInfo.BodyIndex = Selection.Index; MirrorInfo.ConstraintIndex = PhysicsAsset->FindConstraintIndex(MirrorInfo.BoneName); } for (const FSelection& Selection : SelectedConstraints) { MirrorInfos.AddUninitialized(); FMirrorInfo & MirrorInfo = MirrorInfos[MirrorInfos.Num() - 1]; MirrorInfo.BoneName = PhysicsAsset->ConstraintSetup[Selection.Index]->DefaultInstance.ConstraintBone1; MirrorInfo.BodyIndex = PhysicsAsset->FindBodyIndex(MirrorInfo.BoneName); MirrorInfo.ConstraintIndex = Selection.Index; } for (FMirrorInfo & MirrorInfo : MirrorInfos) //mirror all selected bodies/constraints { int32 BoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(MirrorInfo.BoneName); int32 MirrorBoneIndex = PhysicsAsset->FindMirroredBone(EditorSkelMesh, BoneIndex); if (MirrorBoneIndex != INDEX_NONE) { UBodySetup * SrcBody = PhysicsAsset->SkeletalBodySetups[MirrorInfo.BodyIndex]; const FScopedTransaction Transaction(NSLOCTEXT("PhysicsAssetEditor", "MirrorBody", "MirrorBody")); MakeNewBody(MirrorBoneIndex, false); int32 MirrorBodyIndex = PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, MirrorBoneIndex); UBodySetup * DestBody = PhysicsAsset->SkeletalBodySetups[MirrorBodyIndex]; DestBody->Modify(); DestBody->CopyBodyPropertiesFrom(SrcBody); FQuat ArtistMirrorConvention(1,0,0,0); //used to be (0 0 1 0) // how Epic Maya artists rig the right and left orientation differently. todo: perhaps move to cvar W for (FKSphylElem& Sphyl : DestBody->AggGeom.SphylElems) { Sphyl.Rotation = (Sphyl.Rotation.Quaternion()*ArtistMirrorConvention).Rotator(); Sphyl.Center = -Sphyl.Center; } for (FKBoxElem& Box : DestBody->AggGeom.BoxElems) { Box.Rotation = (Box.Rotation.Quaternion()*ArtistMirrorConvention).Rotator(); Box.Center = -Box.Center; } for (FKSphereElem& Sphere : DestBody->AggGeom.SphereElems) { Sphere.Center = -Sphere.Center; } for (FKTaperedCapsuleElem& TaperedCapsule : DestBody->AggGeom.TaperedCapsuleElems) { TaperedCapsule.Rotation = (TaperedCapsule.Rotation.Quaternion()*ArtistMirrorConvention).Rotator(); TaperedCapsule.Center = -TaperedCapsule.Center; } int32 MirrorConstraintIndex = PhysicsAsset->FindConstraintIndex(DestBody->BoneName); if(PhysicsAsset->ConstraintSetup.IsValidIndex(MirrorConstraintIndex) && PhysicsAsset->ConstraintSetup.IsValidIndex(MirrorInfo.ConstraintIndex)) { UPhysicsConstraintTemplate * FromConstraint = PhysicsAsset->ConstraintSetup[MirrorInfo.ConstraintIndex]; UPhysicsConstraintTemplate * ToConstraint = PhysicsAsset->ConstraintSetup[MirrorConstraintIndex]; CopyConstraintProperties(FromConstraint, ToConstraint); } } } } } EPhysicsAssetEditorMeshViewMode FPhysicsAssetEditorSharedData::GetCurrentMeshViewMode(bool bSimulation) { if (bSimulation) { return EditorOptions->SimulationMeshViewMode; } else { return EditorOptions->MeshViewMode; } } EPhysicsAssetEditorCollisionViewMode FPhysicsAssetEditorSharedData::GetCurrentCollisionViewMode(bool bSimulation) { if (bSimulation) { return EditorOptions->SimulationCollisionViewMode; } else { return EditorOptions->CollisionViewMode; } } EPhysicsAssetEditorConstraintViewMode FPhysicsAssetEditorSharedData::GetCurrentConstraintViewMode(bool bSimulation) { if (bSimulation) { return EditorOptions->SimulationConstraintViewMode; } else { return EditorOptions->ConstraintViewMode; } } void FPhysicsAssetEditorSharedData::HitBone(int32 BodyIndex, EAggCollisionShape::Type PrimType, int32 PrimIndex, bool bGroupSelect) { if (!bRunningSimulation) { FPhysicsAssetEditorSharedData::FSelection Selection(BodyIndex, PrimType, PrimIndex); if(bGroupSelect) { if(IsBodySelected(Selection)) { SetSelectedBody(Selection, false); } else { SetSelectedBody(Selection, true); } } else { ClearSelectedBody(); SetSelectedBody(Selection, true); } } } void FPhysicsAssetEditorSharedData::HitConstraint(int32 ConstraintIndex, bool bGroupSelect) { if (!bRunningSimulation) { if(bGroupSelect) { if(IsConstraintSelected(ConstraintIndex)) { SetSelectedConstraint(ConstraintIndex, false); } else { SetSelectedConstraint(ConstraintIndex, true); } } else { ClearSelectedConstraints(); SetSelectedConstraint(ConstraintIndex, true); } } } void FPhysicsAssetEditorSharedData::RefreshPhysicsAssetChange(const UPhysicsAsset* InPhysAsset, bool bFullClothRefresh) { if (InPhysAsset) { InPhysAsset->RefreshPhysicsAssetChange(); // Broadbcast delegate FPhysicsDelegates::OnPhysicsAssetChanged.Broadcast(InPhysAsset); FEditorSupportDelegates::RedrawAllViewports.Broadcast(); // since we recreate physicsstate, a lot of transient state data will be gone // so have to turn simulation off again. // ideally maybe in the future, we'll fix it by controlling tick? EditorSkelComp->RecreatePhysicsState(); if(bFullClothRefresh) { EditorSkelComp->RecreateClothingActors(); } else { UpdateClothPhysics(); } EnableSimulation(false); } } void FPhysicsAssetEditorSharedData::SetSelectedBodyAnyPrim(int32 BodyIndex, bool bSelected) { SetSelectedBodiesAnyPrim({ BodyIndex }, bSelected); } void FPhysicsAssetEditorSharedData::SetSelectedBodiesAnyPrim(const TArray& BodiesIndices, bool bSelected) { if (BodiesIndices.Num() == 0) { return; } if (BodiesIndices.Num() == 1 && BodiesIndices[0] == INDEX_NONE) { ClearSelectedBody(); return; } TArray NewSelection; for (const int32 BodyIndex : BodiesIndices) { UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[BodyIndex]; check(BodySetup); if (BodySetup->AggGeom.SphereElems.Num() > 0) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::Sphere, 0)); } else if (BodySetup->AggGeom.BoxElems.Num() > 0) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::Box, 0)); } else if (BodySetup->AggGeom.SphylElems.Num() > 0) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::Sphyl, 0)); } else if (BodySetup->AggGeom.ConvexElems.Num() > 0) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::Convex, 0)); } else if (BodySetup->AggGeom.TaperedCapsuleElems.Num() > 0) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::TaperedCapsule, 0)); } else { UE_LOG(LogPhysicsAssetEditor, Fatal, TEXT("Body Setup with index %d has No Primitives!"), BodyIndex); } } if (NewSelection.Num() > 0) { SetSelectedBodies(NewSelection, bSelected); } } void FPhysicsAssetEditorSharedData::SetSelectedBodiesAllPrim(const TArray& BodiesIndices, bool bSelected) { if (BodiesIndices.Num() == 0) { return; } if (BodiesIndices.Num() == 1 && BodiesIndices[0] == INDEX_NONE) { ClearSelectedBody(); return; } TArray NewSelection; for (const int32 BodyIndex : BodiesIndices) { UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[BodyIndex]; check(BodySetup); for (int32 PrimitiveIndex = 0; PrimitiveIndex < BodySetup->AggGeom.SphereElems.Num(); ++PrimitiveIndex) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::Sphere, PrimitiveIndex)); } for (int32 PrimitiveIndex = 0; PrimitiveIndex < BodySetup->AggGeom.BoxElems.Num(); ++PrimitiveIndex) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::Box, PrimitiveIndex)); } for (int32 PrimitiveIndex = 0; PrimitiveIndex < BodySetup->AggGeom.SphylElems.Num(); ++PrimitiveIndex) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::Sphyl, PrimitiveIndex)); } for (int32 PrimitiveIndex = 0; PrimitiveIndex < BodySetup->AggGeom.ConvexElems.Num(); ++PrimitiveIndex) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::Convex, PrimitiveIndex)); } for (int32 PrimitiveIndex = 0; PrimitiveIndex < BodySetup->AggGeom.TaperedCapsuleElems.Num(); ++PrimitiveIndex) { NewSelection.Add(FSelection(BodyIndex, EAggCollisionShape::TaperedCapsule, PrimitiveIndex)); } } if (NewSelection.Num() > 0) { SetSelectedBodies(NewSelection, bSelected); } } void FPhysicsAssetEditorSharedData::ClearSelectedBody() { SelectedBodies.Empty(); SelectedConstraints.Empty(); BroadcastSelectionChanged(); } void FPhysicsAssetEditorSharedData::SetSelectedBody(const FSelection& Body, bool bSelected) { SetSelectedBodies({ Body }, bSelected); } void FPhysicsAssetEditorSharedData::SetSelectedBodies(const TArray& Bodies, bool bSelected) { if (InsideSelChange || Bodies.Num() == 0) { return; } if (bSelected) { for (const FSelection& Body : Bodies) { SelectedBodies.AddUnique(Body); } } else { for (const FSelection& Body : Bodies) { SelectedBodies.Remove(Body); } } BroadcastSelectionChanged(); if (!GetSelectedBody()) { return; } UpdateNoCollisionBodies(); ++InsideSelChange; BroadcastPreviewChanged(); --InsideSelChange; } bool FPhysicsAssetEditorSharedData::IsBodySelected(const FSelection& Body) const { return SelectedBodies.Contains(Body); } void FPhysicsAssetEditorSharedData::ToggleSelectionType(bool bIgnoreUserConstraints) { TSet NewSelectedBodies; for (const FSelection& Selection : SelectedConstraints) { UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[Selection.Index]; FConstraintInstance & DefaultInstance = ConstraintTemplate->DefaultInstance; for (int32 BodyIdx = 0; BodyIdx < PhysicsAsset->SkeletalBodySetups.Num(); ++BodyIdx) { UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[BodyIdx]; // no need to account for bIgnoreUserConstraints when selecting from constraints to bodies if (ConstraintTemplate->DefaultInstance.ConstraintBone1 == BodySetup->BoneName) { if (BodySetup->AggGeom.GetElementCount() > 0 && !NewSelectedBodies.Contains(BodyIdx)) { NewSelectedBodies.Add(BodyIdx); } } } } TSet NewSelectedConstraints; //We could have multiple shapes selected which would cause us to add and remove the same constraint. for (const FSelection& Selection : SelectedBodies) { UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[Selection.Index]; for(int32 ConstraintIdx = 0; ConstraintIdx < PhysicsAsset->ConstraintSetup.Num(); ++ConstraintIdx) { const UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[ConstraintIdx]; bool bConstraintIsConnectedToBone = (ConstraintTemplate->DefaultInstance.JointName == BodySetup->BoneName); if (!bIgnoreUserConstraints) { bConstraintIsConnectedToBone |= (ConstraintTemplate->DefaultInstance.ConstraintBone1 == BodySetup->BoneName); } if (bConstraintIsConnectedToBone) { if (!NewSelectedConstraints.Contains(ConstraintIdx)) { NewSelectedConstraints.Add(ConstraintIdx); } } } } ClearSelectedBody(); ClearSelectedConstraints(); SetSelectedBodiesAllPrim(NewSelectedBodies.Array(), true); SetSelectedConstraints(NewSelectedConstraints.Array(), true); } void FPhysicsAssetEditorSharedData::ToggleShowSelected() { bool bAllSelectedVisible = true; if (bAllSelectedVisible) { for (const FSelection& Selection : SelectedConstraints) { if (IsConstraintHidden(Selection.Index)) { bAllSelectedVisible = false; break; } } } if (bAllSelectedVisible) { for (const FSelection& Selection : SelectedBodies) { if (IsBodyHidden(Selection.Index)) { bAllSelectedVisible = false; } } } if (bAllSelectedVisible) { HideSelected(); } else { ShowSelected(); } } void FPhysicsAssetEditorSharedData::ToggleShowOnlySelected() { // Show only selected: make selected items visible and all others invisible. // If we are already in the ShowOnlySelected state, make all visible. bool bAllSelectedVisible = true; if (bAllSelectedVisible) { for (const FSelection& Selection : SelectedConstraints) { if (IsConstraintHidden(Selection.Index)) { bAllSelectedVisible = false; break; } } } if (bAllSelectedVisible) { for (const FSelection& Selection : SelectedBodies) { if (IsBodyHidden(Selection.Index)) { bAllSelectedVisible = false; } } } bool bAllNotSelectedHidden = true; if (bAllNotSelectedHidden) { for (int32 ConstraintIndex = 0; ConstraintIndex < PhysicsAsset->ConstraintSetup.Num(); ++ConstraintIndex) { // Look at unselected constraints if (!SelectedConstraints.ContainsByPredicate([ConstraintIndex](FSelection& V) { return V.Index == ConstraintIndex; } )) { // Is it hidden? if (!IsConstraintHidden(ConstraintIndex)) { bAllNotSelectedHidden = false; break; } } } } if (bAllNotSelectedHidden) { for (int32 BodyIndex = 0; BodyIndex < PhysicsAsset->SkeletalBodySetups.Num(); ++BodyIndex) { // Look at unselected bodies if (!SelectedBodies.ContainsByPredicate([BodyIndex](FSelection& V) { return V.Index == BodyIndex; })) { // Is it hidden? if (!IsBodyHidden(BodyIndex)) { bAllNotSelectedHidden = false; break; } } } } if (bAllSelectedVisible && bAllNotSelectedHidden) { ShowAll(); } else { HideAll(); ShowSelected(); } } bool FPhysicsAssetEditorSharedData::IsBodyHidden(const int32 BodyIndex) const { if (FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings()) { return PhysicsAssetRenderSettings->IsBodyHidden(BodyIndex); } return false; } bool FPhysicsAssetEditorSharedData::IsConstraintHidden(const int32 ConstraintIndex) const { if (FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings()) { return PhysicsAssetRenderSettings->IsConstraintHidden(ConstraintIndex); } return false; } void FPhysicsAssetEditorSharedData::HideBody(const int32 BodyIndex) { if (FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings()) { PhysicsAssetRenderSettings->HideBody(BodyIndex); } } void FPhysicsAssetEditorSharedData::ShowBody(const int32 BodyIndex) { if (FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings()) { PhysicsAssetRenderSettings->ShowBody(BodyIndex); } } void FPhysicsAssetEditorSharedData::HideConstraint(const int32 ConstraintIndex) { if (FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings()) { PhysicsAssetRenderSettings->HideConstraint(ConstraintIndex); } } void FPhysicsAssetEditorSharedData::ShowConstraint(const int32 ConstraintIndex) { if (FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings()) { PhysicsAssetRenderSettings->ShowConstraint(ConstraintIndex); } } void FPhysicsAssetEditorSharedData::ShowAll() { if (FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings()) { PhysicsAssetRenderSettings->ShowAll(); } } void FPhysicsAssetEditorSharedData::HideAllBodies() { if (FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings()) { PhysicsAssetRenderSettings->HideAllBodies(PhysicsAsset); } } void FPhysicsAssetEditorSharedData::HideAllConstraints() { if (FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings()) { PhysicsAssetRenderSettings->HideAllConstraints(PhysicsAsset); } } void FPhysicsAssetEditorSharedData::HideAll() { HideAllBodies(); HideAllConstraints(); } void FPhysicsAssetEditorSharedData::ShowSelected() { for (const FSelection& Selection : SelectedConstraints) { ShowConstraint(Selection.Index); } for (const FSelection& Selection : SelectedBodies) { ShowBody(Selection.Index); } } void FPhysicsAssetEditorSharedData::HideSelected() { for (const FSelection& Selection : SelectedConstraints) { HideConstraint(Selection.Index); } for (const FSelection& Selection : SelectedBodies) { HideBody(Selection.Index); } } void FPhysicsAssetEditorSharedData::ToggleShowOnlyColliding() { // important that we check this before calling ShowAll bool bIsShowingColliding = true; for (const int32 BodyIndex : NoCollisionBodies) { bIsShowingColliding &= IsBodyHidden(BodyIndex); if (!bIsShowingColliding) { break; } } // in any case first show all ShowAll(); FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings(); // only works if one only body is selected if (!bIsShowingColliding && PhysicsAssetRenderSettings && (SelectedBodies.Num() == 1)) { // NoCollisionBodies already contains the non colliding bodies from the one selection PhysicsAssetRenderSettings->SetHiddenBodies(NoCollisionBodies); } } void FPhysicsAssetEditorSharedData::ToggleShowOnlyConstrained() { if (PhysicsAsset == nullptr) { return; } // important that we check this before calling ShowAll { FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings(); if (PhysicsAssetRenderSettings && PhysicsAssetRenderSettings->AreAnyBodiesHidden()) { PhysicsAssetRenderSettings->ShowAllBodies(); return; } } // first Hide all bodies and then show only the ones that needs to be HideAllBodies(); // add the current selection of bodies for (const FSelection& SelectedBody : SelectedBodies) { ShowBody(SelectedBody.Index); } // collect connected bodies from the selected constraints for (const FSelection& Selection : SelectedConstraints) { UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[Selection.Index]; FConstraintInstance& DefaultInstance = ConstraintTemplate->DefaultInstance; // Add both connected bodies int32 Body1IndexToAdd = PhysicsAsset->FindBodyIndex(DefaultInstance.ConstraintBone1); if (Body1IndexToAdd != INDEX_NONE) { ShowBody(Body1IndexToAdd); } int32 Body2IndexToAdd = PhysicsAsset->FindBodyIndex(DefaultInstance.ConstraintBone2); if (Body2IndexToAdd != INDEX_NONE) { ShowBody(Body2IndexToAdd); } } // collect connected bodies from the selected bodies for (const FSelection& Selection : SelectedBodies) { UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[Selection.Index]; for (int32 ConstraintIdx = 0; ConstraintIdx < PhysicsAsset->ConstraintSetup.Num(); ++ConstraintIdx) { const UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[ConstraintIdx]; FName OtherConnectedBody; if (ConstraintTemplate->DefaultInstance.ConstraintBone1 == BodySetup->BoneName) { OtherConnectedBody = ConstraintTemplate->DefaultInstance.ConstraintBone2; } else if (ConstraintTemplate->DefaultInstance.ConstraintBone2 == BodySetup->BoneName) { OtherConnectedBody = ConstraintTemplate->DefaultInstance.ConstraintBone1; } if (!OtherConnectedBody.IsNone()) { int32 BodyIndexToAdd = PhysicsAsset->FindBodyIndex(OtherConnectedBody); if (BodyIndexToAdd != INDEX_NONE) { ShowBody(BodyIndexToAdd); } } } } } void FPhysicsAssetEditorSharedData::UpdateNoCollisionBodies() { NoCollisionBodies.Empty(); // Query disable table with selected body and every other body. for (int32 i = 0; i SkeletalBodySetups.Num(); ++i) { if (!ensure(PhysicsAsset->SkeletalBodySetups[i])) { continue; } // Add any bodies with bNoCollision if (PhysicsAsset->SkeletalBodySetups[i]->DefaultInstance.GetCollisionEnabled() == ECollisionEnabled::NoCollision) { NoCollisionBodies.Add(i); } else if (GetSelectedBody() && i != GetSelectedBody()->Index) { if (!ensure(PhysicsAsset->SkeletalBodySetups[GetSelectedBody()->Index])) { continue; } // Add this body if it has disabled collision with selected. FRigidBodyIndexPair Key(i, GetSelectedBody()->Index); if (PhysicsAsset->SkeletalBodySetups[GetSelectedBody()->Index]->DefaultInstance.GetCollisionEnabled() == ECollisionEnabled::NoCollision || PhysicsAsset->CollisionDisableTable.Find(Key)) { NoCollisionBodies.Add(i); } } } } void FPhysicsAssetEditorSharedData::ClearSelectedConstraints() { if(InsideSelChange) { return; } SelectedBodies.Empty(); SelectedConstraints.Empty(); BroadcastSelectionChanged(); ++InsideSelChange; BroadcastPreviewChanged(); --InsideSelChange; } void FPhysicsAssetEditorSharedData::SetSelectedConstraint(int32 ConstraintIndex, bool bSelected) { SetSelectedConstraints({ ConstraintIndex }, bSelected); } void FPhysicsAssetEditorSharedData::SetSelectedConstraints(const TArray ConstraintsIndices, bool bSelected) { if (ConstraintsIndices.Num() == 0) { return; } if (InsideSelChange) { return; } bool bSelectionchanged = false; for (int32 ConstraintIndex : ConstraintsIndices) { if (ConstraintIndex != INDEX_NONE) { FSelection Constraint(ConstraintIndex, EAggCollisionShape::Unknown, INDEX_NONE); if (bSelected) { SelectedConstraints.AddUnique(Constraint); } else { SelectedConstraints.Remove(Constraint); } bSelectionchanged = true; } } if (bSelectionchanged) { BroadcastSelectionChanged(); ++InsideSelChange; BroadcastPreviewChanged(); --InsideSelChange; } } bool FPhysicsAssetEditorSharedData::IsConstraintSelected(int32 ConstraintIndex) const { FSelection Constraint(ConstraintIndex, EAggCollisionShape::Unknown, INDEX_NONE); return SelectedConstraints.Contains(Constraint); } void FPhysicsAssetEditorSharedData::SetCollisionBetweenSelected(bool bEnableCollision) { if (bRunningSimulation || SelectedBodies.Num() == 0) { return; } PhysicsAsset->Modify(); for(int32 i=0; iEnableCollision(SelectedBodies[i].Index, SelectedBodies[j].Index); }else { PhysicsAsset->DisableCollision(SelectedBodies[i].Index, SelectedBodies[j].Index); } } } UpdateNoCollisionBodies(); BroadcastPreviewChanged(); } bool FPhysicsAssetEditorSharedData::CanSetCollisionBetweenSelected(bool bEnableCollision) const { if (bRunningSimulation || SelectedBodies.Num() == 0) { return false; } for(int32 i=0; iIsCollisionEnabled(SelectedBodies[i].Index, SelectedBodies[j].Index) != bEnableCollision) { return true; } } } return false; } void FPhysicsAssetEditorSharedData::SetCollisionBetweenSelectedAndAll(bool bEnableCollision) { if (bRunningSimulation || SelectedBodies.Num() == 0) { return; } PhysicsAsset->Modify(); for(int32 i=0; iSkeletalBodySetups.Num(); ++j) { if(bEnableCollision) { PhysicsAsset->EnableCollision(SelectedBodies[i].Index, j); } else { PhysicsAsset->DisableCollision(SelectedBodies[i].Index, j); } } } UpdateNoCollisionBodies(); BroadcastPreviewChanged(); } bool FPhysicsAssetEditorSharedData::CanSetCollisionBetweenSelectedAndAll(bool bEnableCollision) const { if (bRunningSimulation || SelectedBodies.Num() == 0) { return false; } for(int32 i=0; iSkeletalBodySetups.Num(); ++j) { if(PhysicsAsset->IsCollisionEnabled(SelectedBodies[i].Index, j) != bEnableCollision) { return true; } } } return false; } void FPhysicsAssetEditorSharedData::SetCollisionBetween(int32 Body1Index, int32 Body2Index, bool bEnableCollision) { if (bRunningSimulation) { return; } PhysicsAsset->Modify(); if (Body1Index != INDEX_NONE && Body2Index != INDEX_NONE && Body1Index != Body2Index) { if (bEnableCollision) { PhysicsAsset->EnableCollision(Body1Index, Body2Index); } else { PhysicsAsset->DisableCollision(Body1Index, Body2Index); } UpdateNoCollisionBodies(); } BroadcastPreviewChanged(); } void FPhysicsAssetEditorSharedData::SetPrimitiveCollision(ECollisionEnabled::Type CollisionEnabled) { if (bRunningSimulation) { return; } PhysicsAsset->Modify(); for (FSelection SelectedBody : SelectedBodies) { PhysicsAsset->SetPrimitiveCollision(SelectedBody.Index, SelectedBody.PrimitiveType, SelectedBody.PrimitiveIndex, CollisionEnabled); } BroadcastPreviewChanged(); } bool FPhysicsAssetEditorSharedData::CanSetPrimitiveCollision(ECollisionEnabled::Type CollisionEnabled) const { if (bRunningSimulation || SelectedBodies.Num() == 0) { return false; } return true; } bool FPhysicsAssetEditorSharedData::GetIsPrimitiveCollisionEnabled(ECollisionEnabled::Type CollisionEnabled) const { for (const FSelection& SelectedBody : SelectedBodies) { if (PhysicsAsset->GetPrimitiveCollision(SelectedBody.Index, SelectedBody.PrimitiveType, SelectedBody.PrimitiveIndex) == CollisionEnabled) { return true; } } return false; } void FPhysicsAssetEditorSharedData::SetPrimitiveContributeToMass(bool bContributeToMass) { for (const FSelection& SelectedBody : SelectedBodies) { PhysicsAsset->SetPrimitiveContributeToMass(SelectedBody.Index, SelectedBody.PrimitiveType, SelectedBody.PrimitiveIndex, bContributeToMass); } } bool FPhysicsAssetEditorSharedData::CanSetPrimitiveContributeToMass() const { return true; } bool FPhysicsAssetEditorSharedData::GetPrimitiveContributeToMass() const { for (const FSelection& SelectedBody : SelectedBodies) { if (PhysicsAsset->GetPrimitiveContributeToMass(SelectedBody.Index, SelectedBody.PrimitiveType, SelectedBody.PrimitiveIndex)) { return true; } } return false; } static EAggCollisionShape::Type ConvertPhysicsAssetGeomTypeToAggCollisionShapeType(EPhysAssetFitGeomType PhysicsAssetGeomType) { switch (PhysicsAssetGeomType) { case EPhysAssetFitGeomType::EFG_Box: return EAggCollisionShape::Type::Box; case EPhysAssetFitGeomType::EFG_Sphyl: return EAggCollisionShape::Type::Sphyl; case EPhysAssetFitGeomType::EFG_Sphere: return EAggCollisionShape::Type::Sphere; case EPhysAssetFitGeomType::EFG_TaperedCapsule: return EAggCollisionShape::Type::TaperedCapsule; case EPhysAssetFitGeomType::EFG_SingleConvexHull: return EAggCollisionShape::Type::Convex; case EPhysAssetFitGeomType::EFG_MultiConvexHull: return EAggCollisionShape::Type::Convex; default: return EAggCollisionShape::Type::Unknown; } } void FPhysicsAssetEditorSharedData::AutoNameAllPrimitives(int32 BodyIndex, EPhysAssetFitGeomType PrimitiveType) { AutoNameAllPrimitives(BodyIndex, ConvertPhysicsAssetGeomTypeToAggCollisionShapeType(PrimitiveType)); } void FPhysicsAssetEditorSharedData::AutoNameAllPrimitives(int32 BodyIndex, EAggCollisionShape::Type PrimitiveType) { if (!PhysicsAsset || !PhysicsAsset->SkeletalBodySetups.IsValidIndex(BodyIndex)) { return; } if (UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[BodyIndex]) { int32 PrimitiveCount = 0; switch (PrimitiveType) { case EAggCollisionShape::Sphere: PrimitiveCount = BodySetup->AggGeom.SphereElems.Num(); break; case EAggCollisionShape::Box: PrimitiveCount = BodySetup->AggGeom.BoxElems.Num(); break; case EAggCollisionShape::Sphyl: PrimitiveCount = BodySetup->AggGeom.SphylElems.Num(); break; case EAggCollisionShape::Convex: PrimitiveCount = BodySetup->AggGeom.ConvexElems.Num(); break; case EAggCollisionShape::TaperedCapsule: PrimitiveCount = BodySetup->AggGeom.TaperedCapsuleElems.Num(); break; } for (int32 PrimitiveIndex = 0; PrimitiveIndex < PrimitiveCount; PrimitiveIndex++) { AutoNamePrimitive(BodyIndex, PrimitiveType, PrimitiveIndex); } } } void FPhysicsAssetEditorSharedData::AutoNamePrimitive(int32 BodyIndex, EAggCollisionShape::Type PrimitiveType, int32 PrimitiveIndex) { if (!PhysicsAsset || !PhysicsAsset->SkeletalBodySetups.IsValidIndex(BodyIndex)) { return; } if (UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[BodyIndex]) { if (PrimitiveType == EAggCollisionShape::Sphere) { if (PrimitiveIndex == INDEX_NONE) { PrimitiveIndex = BodySetup->AggGeom.SphereElems.Num() - 1; } if (BodySetup->AggGeom.SphereElems.IsValidIndex(PrimitiveIndex)) { FName PrimitiveName(FString::Printf(TEXT("%s_sphere"), *BodySetup->BoneName.ToString())); BodySetup->AggGeom.SphereElems[PrimitiveIndex].SetName(PrimitiveName); } } else if (PrimitiveType == EAggCollisionShape::Box) { if (PrimitiveIndex == INDEX_NONE) { PrimitiveIndex = BodySetup->AggGeom.BoxElems.Num() - 1; } if (BodySetup->AggGeom.BoxElems.IsValidIndex(PrimitiveIndex)) { FName PrimitiveName(FString::Printf(TEXT("%s_box"), *BodySetup->BoneName.ToString())); BodySetup->AggGeom.BoxElems[PrimitiveIndex].SetName(PrimitiveName); } } else if (PrimitiveType == EAggCollisionShape::Sphyl) { if (PrimitiveIndex == INDEX_NONE) { PrimitiveIndex = BodySetup->AggGeom.SphylElems.Num() - 1; } if (BodySetup->AggGeom.SphylElems.IsValidIndex(PrimitiveIndex)) { FName PrimitiveName(FString::Printf(TEXT("%s_capsule"), *BodySetup->BoneName.ToString())); BodySetup->AggGeom.SphylElems[PrimitiveIndex].SetName(PrimitiveName); } } else if (PrimitiveType == EAggCollisionShape::Convex) { if (PrimitiveIndex == INDEX_NONE) { PrimitiveIndex = BodySetup->AggGeom.ConvexElems.Num() - 1; } if (BodySetup->AggGeom.ConvexElems.IsValidIndex(PrimitiveIndex)) { FName PrimitiveName(FString::Printf(TEXT("%s_convex"), *BodySetup->BoneName.ToString())); BodySetup->AggGeom.ConvexElems[PrimitiveIndex].SetName(PrimitiveName); } } else if (PrimitiveType == EAggCollisionShape::TaperedCapsule) { if (PrimitiveIndex == INDEX_NONE) { PrimitiveIndex = BodySetup->AggGeom.TaperedCapsuleElems.Num() - 1; } if (BodySetup->AggGeom.TaperedCapsuleElems.IsValidIndex(PrimitiveIndex)) { FName PrimitiveName(FString::Printf(TEXT("%s_tapered_capsule"), *BodySetup->BoneName.ToString())); BodySetup->AggGeom.TaperedCapsuleElems[PrimitiveIndex].SetName(PrimitiveName); } } } } void FPhysicsAssetEditorSharedData::CopySelectedBodiesAndConstraintsToClipboard(int32& OutNumCopiedBodies, int32& OutNumCopiedConstraints) { OutNumCopiedBodies = 0; OutNumCopiedConstraints = 0; if (PhysicsAsset) { // Clear the mark state for saving. UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp)); FStringOutputDevice Archive; const FExportObjectInnerContext Context; // export bodies first { OutNumCopiedBodies = 0; TSet ExportedBodyIndices; // Export each of the selected nodes for (const FSelection& SelectedBody : SelectedBodies) { // selected bodies contain the primitives, so abody can be stored multiple time for each of its primitive // we need to make sure we process it only once if (!ExportedBodyIndices.Contains(SelectedBody.Index)) { ExportedBodyIndices.Add(SelectedBody.Index); if (USkeletalBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[SelectedBody.Index]) { UExporter::ExportToOutputDevice(&Context, BodySetup, NULL, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false); ++OutNumCopiedBodies; } } } } // export constraint next { OutNumCopiedConstraints = 0; TSet ExportedConstraintIndices; // Export each of the selected nodes for (const FSelection& SelectedConstraint : SelectedConstraints) { // selected bodies contain the primitives, so abody can be stored multiple time for each of its primitive // we need to make sure we process it only once if (!ExportedConstraintIndices.Contains(SelectedConstraint.Index)) { ExportedConstraintIndices.Add(SelectedConstraint.Index); if (UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[SelectedConstraint.Index]) { UExporter::ExportToOutputDevice(&Context, ConstraintSetup, NULL, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false); ++OutNumCopiedConstraints; } } } } // save to clipboard as text FString ExportedText = Archive; FPlatformApplicationMisc::ClipboardCopy(*ExportedText); } } class FSkeletalBodyAndConstraintSetupObjectTextFactory : public FCustomizableTextObjectFactory { public: FSkeletalBodyAndConstraintSetupObjectTextFactory() : FCustomizableTextObjectFactory(GWarn) { } // FCustomizableTextObjectFactory implementation virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override { return (InObjectClass->IsChildOf() || InObjectClass->IsChildOf()); } virtual void ProcessConstructedObject(UObject* NewObject) override { check(NewObject); if (NewObject->IsA()) { NewBodySetups.Add(Cast(NewObject)); } else if (NewObject->IsA()) { NewConstraintTemplates.Add(Cast(NewObject)); } } public: TArray NewBodySetups; TArray NewConstraintTemplates; }; void FPhysicsAssetEditorSharedData::PasteBodiesAndConstraintsFromClipboard(int32& OutNumPastedBodies, int32& OutNumPastedConstraints) { OutNumPastedBodies = 0; OutNumPastedConstraints = 0; if (PhysicsAsset) { FString TextToImport; FPlatformApplicationMisc::ClipboardPaste(TextToImport); if (!TextToImport.IsEmpty()) { UPackage* TempPackage = NewObject(nullptr, TEXT("/Engine/Editor/PhysicsAssetEditor/Transient"), RF_Transient); TempPackage->AddToRoot(); { // Turn the text buffer into objects FSkeletalBodyAndConstraintSetupObjectTextFactory Factory; Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport); // transaction block if (Factory.NewBodySetups.Num() > 0 || Factory.NewConstraintTemplates.Num() > 0) { const FScopedTransaction Transaction(NSLOCTEXT("PhysicsAssetEditor", "PasteBodiesAndConstraintsFromClipboard", "Paste Bodies And Constraints From Clipboard")); PhysicsAsset->Modify(); // let's first process the bodies OutNumPastedBodies = 0; for (USkeletalBodySetup* PastedBodySetup : Factory.NewBodySetups) { // doe sthis bone exist in the target physics asset? int32 BodyIndex = PhysicsAsset->FindBodyIndex(PastedBodySetup->BoneName); if (BodyIndex == INDEX_NONE) { // none found, create a brand new one const FPhysAssetCreateParams& NewBodyData = GetDefault()->CreateParams; BodyIndex = FPhysicsAssetUtils::CreateNewBody(PhysicsAsset, PastedBodySetup->BoneName, NewBodyData); } if (PhysicsAsset->SkeletalBodySetups.IsValidIndex(BodyIndex)) { if (UBodySetup* TargetBodySetup = PhysicsAsset->SkeletalBodySetups[BodyIndex]) { check(TargetBodySetup->BoneName == PastedBodySetup->BoneName); TargetBodySetup->Modify(); TargetBodySetup->CopyBodyPropertiesFrom(PastedBodySetup); ++OutNumPastedBodies; } } } // now let's process the constraints OutNumPastedConstraints = 0; for (const UPhysicsConstraintTemplate* PastedConstraintTemplate : Factory.NewConstraintTemplates) { FName ConstraintUniqueName = PastedConstraintTemplate->DefaultInstance.JointName; // search for a matching constraint by bone names const int32 ConstraintIndexByBones = PhysicsAsset->FindConstraintIndex(PastedConstraintTemplate->DefaultInstance.ConstraintBone1, PastedConstraintTemplate->DefaultInstance.ConstraintBone2); const int32 ConstraintIndexByJointName = PhysicsAsset->FindConstraintIndex(ConstraintUniqueName); // If the indices are not matching we need to generate a new unique name for the constraint if (ConstraintIndexByBones != ConstraintIndexByJointName) { ConstraintUniqueName = *MakeUniqueNewConstraintName(); } int32 ConstraintIndex = ConstraintIndexByBones; if (ConstraintIndex == INDEX_NONE) { // none found, create a brand new one ConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(PhysicsAsset, ConstraintUniqueName); check(ConstraintIndex != INDEX_NONE); } if (PhysicsAsset->ConstraintSetup.IsValidIndex(ConstraintIndex)) { if (UPhysicsConstraintTemplate* TargetConstraintTemplate = PhysicsAsset->ConstraintSetup[ConstraintIndex]) { TargetConstraintTemplate->Modify(); // keep the existing instance as we want to keep some of its data FConstraintInstance ExistingInstance = TargetConstraintTemplate->DefaultInstance; TargetConstraintTemplate->DefaultInstance.CopyConstraintParamsFrom(&PastedConstraintTemplate->DefaultInstance); TargetConstraintTemplate->DefaultInstance.JointName = ConstraintUniqueName; TargetConstraintTemplate->DefaultInstance.ConstraintIndex = ConstraintIndex; TargetConstraintTemplate->DefaultInstance.ConstraintHandle = ExistingInstance.ConstraintHandle; TargetConstraintTemplate->UpdateProfileInstance(); ++OutNumPastedConstraints; } } } } } // Remove the temp package from the root now that it has served its purpose TempPackage->RemoveFromRoot(); RefreshPhysicsAssetChange(PhysicsAsset); ClearSelectedBody(); //paste can change the primitives on our selected bodies. There's probably a way to properly update this, but for now just deselect ClearSelectedConstraints(); //paste can change the primitives on our selected bodies. There's probably a way to properly update this, but for now just deselect BroadcastPreviewChanged(); BroadcastHierarchyChanged(); } } } void FPhysicsAssetEditorSharedData::CopyBodyProperties() { check(SelectedBodies.Num() == 1); CopyToClipboard(SharedDataConstants::BodyType, PhysicsAsset->SkeletalBodySetups[GetSelectedBody()->Index]); } void FPhysicsAssetEditorSharedData::PasteBodyProperties() { // Can't do this while simulating! if (bRunningSimulation) { return; } UPhysicsAsset* SourceAsset = nullptr; UObject* SourceBodySetup = nullptr; int32 SourceBodyIndex = 0; if(!PasteFromClipboard(SharedDataConstants::BodyType, SourceAsset, SourceBodySetup)) { return; } const UBodySetup* CopiedBodySetup = Cast(SourceBodySetup); // Must have two valid bodies (which are different) if(CopiedBodySetup == NULL) { return; } if(SelectedBodies.Num() > 0) { const FScopedTransaction Transaction( NSLOCTEXT("PhysicsAssetEditor", "PasteBodyProperties", "Paste Body Properties") ); PhysicsAsset->Modify(); for(int32 i=0; iSkeletalBodySetups[SelectedBodies[i].Index]; ToBodySetup->Modify(); ToBodySetup->CopyBodyPropertiesFrom(CopiedBodySetup); } ClearSelectedBody(); //paste can change the primitives on our selected bodies. There's probably a way to properly update this, but for now just deselect BroadcastPreviewChanged(); } } bool FPhysicsAssetEditorSharedData::WeldSelectedBodies(bool bWeld /* = true */) { bool bCanWeld = false; if (bRunningSimulation) { return false; } if(SelectedBodies.Num() <= 1) { return false; } USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh == nullptr) { return false; } //we only support two body weld int BodyIndex0 = 0; int BodyIndex1 = INDEX_NONE; for(int32 i=1; iSkeletalBodySetups[Body0.Index]->BoneName; int32 Bone0Index = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(Bone0Name); check(Bone0Index != INDEX_NONE); FName Bone1Name = PhysicsAsset->SkeletalBodySetups[Body1.Index]->BoneName; int32 Bone1Index = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(Bone1Name); check(Bone1Index != INDEX_NONE); int32 Bone0ParentIndex = EditorSkelMesh->GetRefSkeleton().GetParentIndex(Bone0Index); int32 Bone1ParentIndex = EditorSkelMesh->GetRefSkeleton().GetParentIndex(Bone1Index); int ParentBodyIndex = INDEX_NONE; int ChildBodyIndex = INDEX_NONE; FName ParentBoneName; EAggCollisionShape::Type ParentPrimitiveType = EAggCollisionShape::Unknown; EAggCollisionShape::Type ChildPrimitiveType = EAggCollisionShape::Unknown; int32 ParentPrimitiveIndex = INDEX_NONE; int32 ChildPrimitiveIndex = INDEX_NONE; if (PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, Bone1ParentIndex) == Body0.Index) { ParentBodyIndex = Body0.Index; ParentBoneName = Bone0Name; ChildBodyIndex = Body1.Index; ParentPrimitiveType = Body0.PrimitiveType; ChildPrimitiveType = Body1.PrimitiveType; ParentPrimitiveIndex = Body0.PrimitiveIndex; //Child geoms get appended so just add it. This is kind of a hack but this whole indexing scheme needs to be rewritten anyway ChildPrimitiveIndex = Body1.PrimitiveIndex + PhysicsAsset->SkeletalBodySetups[Body0.Index]->AggGeom.GetElementCount(ChildPrimitiveType); bCanWeld = true; }else if(PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, Bone0ParentIndex) == Body1.Index) { ParentBodyIndex = Body1.Index; ParentBoneName = Bone1Name; ChildBodyIndex = Body0.Index; ParentPrimitiveType = Body1.PrimitiveType; ChildPrimitiveType = Body0.PrimitiveType; ParentPrimitiveIndex = Body1.PrimitiveIndex; //Child geoms get appended so just add it. This is kind of a hack but this whole indexing scheme needs to be rewritten anyway ChildPrimitiveIndex = Body0.PrimitiveIndex + PhysicsAsset->SkeletalBodySetups[Body1.Index]->AggGeom.GetElementCount(ChildPrimitiveType); bCanWeld = true; } //function is used for the action and the check if(bWeld == false) { return bCanWeld; } check(ParentBodyIndex != INDEX_NONE); check(ChildBodyIndex != INDEX_NONE); { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "WeldBodies", "Weld Bodies") ); // .. the asset itself.. PhysicsAsset->Modify(); // .. the parent and child bodies.. PhysicsAsset->SkeletalBodySetups[ParentBodyIndex]->Modify(); PhysicsAsset->SkeletalBodySetups[ChildBodyIndex]->Modify(); // .. and any constraints of the 'child' body.. TArray Constraints; PhysicsAsset->BodyFindConstraints(ChildBodyIndex, Constraints); for (int32 i = 0; i ConstraintSetup[ConstraintIndex]->Modify(); } // Do the actual welding FPhysicsAssetUtils::WeldBodies(PhysicsAsset, ParentBodyIndex, ChildBodyIndex, EditorSkelComp); } // update the tree BroadcastHierarchyChanged(); // Just to be safe - deselect any selected constraints ClearSelectedConstraints(); ClearSelectedBody(); // Previous selection is invalid because child no longer has same index. int32 BodyIndex = PhysicsAsset->FindBodyIndex(ParentBoneName); FSelection SelectionParent(BodyIndex, ParentPrimitiveType, ParentPrimitiveIndex); SetSelectedBody(SelectionParent, true); // This redraws the viewport as well... FSelection SelectionChild(BodyIndex, ChildPrimitiveType, ChildPrimitiveIndex); SetSelectedBody(SelectionChild, true); // This redraws the viewport as well... RefreshPhysicsAssetChange(PhysicsAsset); return true; } void FPhysicsAssetEditorSharedData::InitConstraintSetup(UPhysicsConstraintTemplate* ConstraintSetup, int32 ChildBodyIndex, int32 ParentBodyIndex) { check(ConstraintSetup); ConstraintSetup->Modify(false); UBodySetup* ChildBodySetup = PhysicsAsset->SkeletalBodySetups[ ChildBodyIndex ]; UBodySetup* ParentBodySetup = PhysicsAsset->SkeletalBodySetups[ ParentBodyIndex ]; check(ChildBodySetup && ParentBodySetup); // Place joint at origin of child ConstraintSetup->DefaultInstance.ConstraintBone1 = ChildBodySetup->BoneName; ConstraintSetup->DefaultInstance.ConstraintBone2 = ParentBodySetup->BoneName; SnapConstraintToBone(ConstraintSetup->DefaultInstance); ConstraintSetup->SetDefaultProfile(ConstraintSetup->DefaultInstance); // Disable collision between constrained bodies by default. SetCollisionBetween(ChildBodyIndex, ParentBodyIndex, false); } void FPhysicsAssetEditorSharedData::MakeNewBody(int32 NewBoneIndex, bool bAutoSelect) { USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh == nullptr) { return; } PhysicsAsset->Modify(); FName NewBoneName = EditorSkelMesh->GetRefSkeleton().GetBoneName(NewBoneIndex); // If this body is already physical, remove the current body int32 NewBodyIndex = PhysicsAsset->FindBodyIndex(NewBoneName); if (NewBodyIndex != INDEX_NONE) { DeleteBody(NewBodyIndex, false); } // Find body that currently controls this bone. int32 ParentBodyIndex = PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, NewBoneIndex); const FPhysAssetCreateParams& NewBodyData = GetDefault()->CreateParams; // Create the physics body. NewBodyIndex = FPhysicsAssetUtils::CreateNewBody(PhysicsAsset, NewBoneName, NewBodyData); UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[ NewBodyIndex ]; check(BodySetup->BoneName == NewBoneName); BodySetup->Modify(); bool bCreatedBody = false; // Create a new physics body for this bone. if (NewBodyData.VertWeight == EVW_DominantWeight) { bCreatedBody = FPhysicsAssetUtils::CreateCollisionFromBone(BodySetup, EditorSkelMesh, NewBoneIndex, NewBodyData, DominantWeightBoneInfos[NewBoneIndex]); } else { bCreatedBody = FPhysicsAssetUtils::CreateCollisionFromBone(BodySetup, EditorSkelMesh, NewBoneIndex, NewBodyData, AnyWeightBoneInfos[NewBoneIndex]); } if (bCreatedBody == false) { FPhysicsAssetUtils::DestroyBody(PhysicsAsset, NewBodyIndex); return; } // name the new created primitives AutoNameAllPrimitives(NewBodyIndex, NewBodyData.GeomType); // Check if the bone of the new body has any physical children bones for (int32 i = 0; i < EditorSkelMesh->GetRefSkeleton().GetRawBoneNum(); ++i) { if (EditorSkelMesh->GetRefSkeleton().BoneIsChildOf(i, NewBoneIndex)) { const int32 ChildBodyIndex = PhysicsAsset->FindBodyIndex(EditorSkelMesh->GetRefSkeleton().GetBoneName(i)); // If the child bone is physical, it may require fixing up in regards to constraints if (ChildBodyIndex != INDEX_NONE) { UBodySetup* ChildBody = PhysicsAsset->SkeletalBodySetups[ ChildBodyIndex ]; check(ChildBody); int32 ConstraintIndex = PhysicsAsset->FindConstraintIndex(ChildBody->BoneName); // If the child body is not constrained already, create a new constraint between // the child body and the new body // @todo: This isn't quite right. It is possible that the child constraint's parent body is not our parent body. // This can happen in a couple ways: // - the user altered the child constraint to attach to a different parent bond // - a new bone was added. E.g., add bone at root of hierarchy. Import mesh with new bone. Add body to root bone. // So, if this happens we need to decide if we should leave the old constraint there and add a new one, or commandeer the // constraint. If the former, we should probably change a constraint to a "User" constraint when they change its bones. // We are currently doing the latter... if (ConstraintIndex == INDEX_NONE) { ConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(PhysicsAsset, ChildBody->BoneName); check(ConstraintIndex != INDEX_NONE); } // If there's a pre-existing constraint, see if it needs to be fixed up else { UPhysicsConstraintTemplate* ExistingConstraintSetup = PhysicsAsset->ConstraintSetup[ ConstraintIndex ]; check(ExistingConstraintSetup); const int32 ExistingConstraintBoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(ExistingConstraintSetup->DefaultInstance.ConstraintBone2); check(ExistingConstraintBoneIndex != INDEX_NONE); // If the constraint exists between two child bones, then no fix up is required if (EditorSkelMesh->GetRefSkeleton().BoneIsChildOf(ExistingConstraintBoneIndex, NewBoneIndex)) { continue; } // If the constraint isn't between two child bones, then it is between a physical bone higher in the bone // hierarchy than the new bone, so it needs to be fixed up by setting the constraint to point to the new bone // instead. Additionally, collision needs to be re-enabled between the child bone and the identified "grandparent" // bone. const int32 ExistingConstraintBodyIndex = PhysicsAsset->FindBodyIndex(ExistingConstraintSetup->DefaultInstance.ConstraintBone2); check(ExistingConstraintBodyIndex != INDEX_NONE); // See above comments about the child constraint's parent not necessarily being our parent... if (ExistingConstraintBodyIndex == ParentBodyIndex) { SetCollisionBetween(ChildBodyIndex, ExistingConstraintBodyIndex, true); } } UPhysicsConstraintTemplate* ChildConstraintSetup = PhysicsAsset->ConstraintSetup[ ConstraintIndex ]; check(ChildConstraintSetup); InitConstraintSetup(ChildConstraintSetup, ChildBodyIndex, NewBodyIndex); } } } // If we have a physics parent, create a joint to it. if (ParentBodyIndex != INDEX_NONE) { const int32 NewConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(PhysicsAsset, NewBoneName); UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[ NewConstraintIndex ]; check(ConstraintSetup); InitConstraintSetup(ConstraintSetup, NewBodyIndex, ParentBodyIndex); } // update the tree BroadcastHierarchyChanged(); if (bAutoSelect) { SetSelectedBodyAnyPrim(NewBodyIndex, true); } RefreshPhysicsAssetChange(PhysicsAsset); } FString FPhysicsAssetEditorSharedData::MakeUniqueNewConstraintName() { // Make a new unique name for this constraint int32 Index = 0; FString BaseConstraintName(TEXT("UserConstraint")); FString ConstraintName = BaseConstraintName; while (PhysicsAsset->FindConstraintIndex(*ConstraintName) != INDEX_NONE) { ConstraintName = FString::Printf(TEXT("%s_%d"), *BaseConstraintName, Index++); } return ConstraintName; } void FPhysicsAssetEditorSharedData::MakeNewConstraints(int32 ParentBodyIndex, const TArray& ChildBodyIndices) { // check we have valid bodies check(ParentBodyIndex < PhysicsAsset->SkeletalBodySetups.Num()); TArray NewlyCreatedConstraints; for (const int32 ChildBodyIndex : ChildBodyIndices) { check(ChildBodyIndex < PhysicsAsset->SkeletalBodySetups.Num()); // Make a new unique name for this constraint FString ConstraintName = MakeUniqueNewConstraintName(); // Create new constraint with a name not related to a bone, so it wont get auto managed in code that creates new bodies const int32 NewConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(PhysicsAsset, *ConstraintName); UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[NewConstraintIndex]; check(ConstraintSetup); NewlyCreatedConstraints.Add(NewConstraintIndex); InitConstraintSetup(ConstraintSetup, ChildBodyIndex, ParentBodyIndex); } ClearSelectedConstraints(); SetSelectedConstraints(NewlyCreatedConstraints, true); // update the tree BroadcastHierarchyChanged(); RefreshPhysicsAssetChange(PhysicsAsset); BroadcastSelectionChanged(); } void FPhysicsAssetEditorSharedData::MakeNewConstraint(int32 ParentBodyIndex, int32 ChildBodyIndex) { MakeNewConstraints(ParentBodyIndex, { ChildBodyIndex }); } void FPhysicsAssetEditorSharedData::SetConstraintRelTM(const FPhysicsAssetEditorSharedData::FSelection* Constraint, const FTransform& RelTM) { USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh == nullptr) { return; } FTransform WParentFrame = GetConstraintWorldTM(Constraint, EConstraintFrame::Frame2); FTransform WNewChildFrame = RelTM * WParentFrame; UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[Constraint->Index]; ConstraintSetup->Modify(); // Get child bone transform int32 BoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone1); if (BoneIndex != INDEX_NONE) { FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex); BoneTM.RemoveScaling(); ConstraintSetup->DefaultInstance.SetRefFrame(EConstraintFrame::Frame1, WNewChildFrame.GetRelativeTransform(BoneTM)); } } void FPhysicsAssetEditorSharedData::SnapConstraintToBone(int32 ConstraintIndex) { UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[ConstraintIndex]; ConstraintSetup->Modify(); SnapConstraintToBone(ConstraintSetup->DefaultInstance); } void FPhysicsAssetEditorSharedData::SnapConstraintToBone(FConstraintInstance& ConstraintInstance) { USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh == nullptr) { return; } const int32 BoneIndex1 = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(ConstraintInstance.ConstraintBone1); const int32 BoneIndex2 = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(ConstraintInstance.ConstraintBone2); check(BoneIndex1 != INDEX_NONE); check(BoneIndex2 != INDEX_NONE); const FTransform BoneTransform1 = EditorSkelComp->GetBoneTransform(BoneIndex1); const FTransform BoneTransform2 = EditorSkelComp->GetBoneTransform(BoneIndex2); // Bone transforms are world space, and frame transforms are local space (local to bones). // Frame 1 is the child frame, and set to identity. // Frame 2 is the parent frame, and needs to be set relative to Frame1. ConstraintInstance.SetRefFrame(EConstraintFrame::Frame2, BoneTransform1.GetRelativeTransform(BoneTransform2)); ConstraintInstance.SetRefFrame(EConstraintFrame::Frame1, FTransform::Identity); } void FPhysicsAssetEditorSharedData::CopyConstraintProperties() { check(SelectedConstraints.Num() == 1); CopyToClipboard(SharedDataConstants::ConstraintType, PhysicsAsset->ConstraintSetup[GetSelectedConstraint()->Index]); } void FPhysicsAssetEditorSharedData::PasteConstraintProperties() { UPhysicsAsset* SourceAsset = nullptr; UObject* SourceConstraint; if(!PasteFromClipboard(SharedDataConstants::ConstraintType, SourceAsset, SourceConstraint)) { return; } const UPhysicsConstraintTemplate* FromConstraintSetup = Cast(SourceConstraint); if(FromConstraintSetup && SelectedConstraints.Num() > 0) { const FScopedTransaction Transaction(NSLOCTEXT("PhysicsAssetEditor", "PasteConstraintProperties", "Paste Constraint Properties")); for(int32 i=0; iConstraintSetup[SelectedConstraints[i].Index]; CopyConstraintProperties(FromConstraintSetup, ToConstraintSetup, /*bKeepOriginalRotation=*/true); } } } void CycleMatrixRows(FMatrix* TM) { float Tmp[3]; Tmp[0] = TM->M[0][0]; Tmp[1] = TM->M[0][1]; Tmp[2] = TM->M[0][2]; TM->M[0][0] = TM->M[1][0]; TM->M[0][1] = TM->M[1][1]; TM->M[0][2] = TM->M[1][2]; TM->M[1][0] = TM->M[2][0]; TM->M[1][1] = TM->M[2][1]; TM->M[1][2] = TM->M[2][2]; TM->M[2][0] = Tmp[0]; TM->M[2][1] = Tmp[1]; TM->M[2][2] = Tmp[2]; } void FPhysicsAssetEditorSharedData::CycleCurrentConstraintOrientation() { const FScopedTransaction Transaction( LOCTEXT("CycleCurrentConstraintOrientation", "Cycle Current Constraint Orientation") ); for(int32 i=0; iConstraintSetup[SelectedConstraints[i].Index]; ConstraintTemplate->Modify(); FMatrix ConstraintTransform = ConstraintTemplate->DefaultInstance.GetRefFrame(EConstraintFrame::Frame2).ToMatrixWithScale(); FTransform WParentFrame = GetConstraintWorldTM(&SelectedConstraints[i], EConstraintFrame::Frame2); FTransform WChildFrame = GetConstraintWorldTM(&SelectedConstraints[i], EConstraintFrame::Frame1); FTransform RelativeTransform = WChildFrame * WParentFrame.Inverse(); CycleMatrixRows(&ConstraintTransform); ConstraintTemplate->DefaultInstance.SetRefFrame(EConstraintFrame::Frame2, FTransform(ConstraintTransform)); SetSelectedConstraintRelTM(RelativeTransform); } } void FPhysicsAssetEditorSharedData::CycleCurrentConstraintActive() { const FScopedTransaction Transaction( LOCTEXT("CycleCurrentConstraintActive", "Cycle Current Constraint Active") ); for(int32 i=0; iConstraintSetup[SelectedConstraints[i].Index]; ConstraintTemplate->Modify(); FConstraintInstance & DefaultInstance = ConstraintTemplate->DefaultInstance; if(DefaultInstance.GetAngularSwing1Motion() != ACM_Limited && DefaultInstance.GetAngularSwing2Motion() != ACM_Limited) { DefaultInstance.SetAngularSwing1Motion(ACM_Limited); DefaultInstance.SetAngularSwing2Motion(ACM_Locked); DefaultInstance.SetAngularTwistMotion(ACM_Locked); }else if(DefaultInstance.GetAngularSwing2Motion() != ACM_Limited && DefaultInstance.GetAngularTwistMotion() != ACM_Limited) { DefaultInstance.SetAngularSwing1Motion(ACM_Locked); DefaultInstance.SetAngularSwing2Motion(ACM_Limited); DefaultInstance.SetAngularTwistMotion(ACM_Locked); }else { DefaultInstance.SetAngularSwing1Motion(ACM_Locked); DefaultInstance.SetAngularSwing2Motion(ACM_Locked); DefaultInstance.SetAngularTwistMotion(ACM_Limited); } ConstraintTemplate->UpdateProfileInstance(); } } void FPhysicsAssetEditorSharedData::ToggleConstraint(EPhysicsAssetEditorConstraintType Constraint) { const FScopedTransaction Transaction( LOCTEXT("ToggleConstraintTypeLock", "Toggle Constraint Type Lock") ); for(int32 i=0; iConstraintSetup[GetSelectedConstraint()->Index]; ConstraintTemplate->Modify(); FConstraintInstance & DefaultInstance = ConstraintTemplate->DefaultInstance; if(Constraint == PCT_Swing1) { DefaultInstance.SetAngularSwing1Motion(DefaultInstance.GetAngularSwing1Motion() == ACM_Limited ? ACM_Locked : ACM_Limited); }else if(Constraint == PCT_Swing2) { DefaultInstance.SetAngularSwing2Motion(DefaultInstance.GetAngularSwing2Motion() == ACM_Limited ? ACM_Locked : ACM_Limited); }else { DefaultInstance.SetAngularTwistMotion(DefaultInstance.GetAngularTwistMotion() == ACM_Limited ? ACM_Locked : ACM_Limited); } ConstraintTemplate->UpdateProfileInstance(); } } bool FPhysicsAssetEditorSharedData::IsAngularConstraintLocked(EPhysicsAssetEditorConstraintType Constraint) const { bool bLocked = false; bool bSame = false; for(int32 i = 0; i < SelectedConstraints.Num(); ++i) { UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[GetSelectedConstraint()->Index]; FConstraintInstance & DefaultInstance = ConstraintTemplate->DefaultInstance; if(Constraint == PCT_Swing1) { bLocked |= DefaultInstance.GetAngularSwing1Motion() == ACM_Locked; } else if(Constraint == PCT_Swing2) { bLocked |= DefaultInstance.GetAngularSwing2Motion() == ACM_Locked; } else { bLocked |= DefaultInstance.GetAngularTwistMotion() == ACM_Locked; } } return bLocked; } void FPhysicsAssetEditorSharedData::DeleteBody(int32 DelBodyIndex, bool bRefreshComponent) { USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh == nullptr) { return; } const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "DeleteBody", "Delete Body") ); // The physics asset and default instance.. PhysicsAsset->Modify(); // .. the body.. UBodySetup * BodySetup = PhysicsAsset->SkeletalBodySetups[DelBodyIndex]; BodySetup->Modify(); // .. and any constraints to the body. TArray Constraints; PhysicsAsset->BodyFindConstraints(DelBodyIndex, Constraints); //we want to fixup constraints so that nearest child bodies get constraint with parent body TArray NearestBodiesBelow; PhysicsAsset->GetNearestBodyIndicesBelow(NearestBodiesBelow, BodySetup->BoneName, EditorSkelMesh); int32 BoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(BodySetup->BoneName); if (BoneIndex != INDEX_NONE) //it's possible to delete bodies that have no bones. In this case just ignore all of this fixup code { int32 ParentBodyIndex = PhysicsAsset->FindParentBodyIndex(EditorSkelMesh, BoneIndex); UBodySetup * ParentBody = ParentBodyIndex != INDEX_NONE ? ToRawPtr(PhysicsAsset->SkeletalBodySetups[ParentBodyIndex]) : NULL; for (const int32 ConstraintIndex : Constraints) { UPhysicsConstraintTemplate * Constraint = PhysicsAsset->ConstraintSetup[ConstraintIndex]; Constraint->Modify(); if (ParentBody) { //for all constraints that contain a nearest child of this body, create a copy of the constraint between the child and parent for (const int32 BodyBelowIndex : NearestBodiesBelow) { UBodySetup * BodyBelow = PhysicsAsset->SkeletalBodySetups[BodyBelowIndex]; if (Constraint->DefaultInstance.ConstraintBone1 == BodyBelow->BoneName) { int32 NewConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(PhysicsAsset, BodyBelow->BoneName, Constraint); UPhysicsConstraintTemplate * NewConstraint = PhysicsAsset->ConstraintSetup[NewConstraintIndex]; InitConstraintSetup(NewConstraint, BodyBelowIndex, ParentBodyIndex); } } } } } // Clear clipboard if it was pointing to this body ConditionalClearClipboard(SharedDataConstants::BodyType, BodySetup); // Now actually destroy body. This will destroy any constraints associated with the body as well. FPhysicsAssetUtils::DestroyBody(PhysicsAsset, DelBodyIndex); // Select nothing. ClearSelectedBody(); ClearSelectedConstraints(); BroadcastHierarchyChanged(); if (bRefreshComponent) { RefreshPhysicsAssetChange(PhysicsAsset); } } void FPhysicsAssetEditorSharedData::DeleteCurrentPrim() { if (bRunningSimulation) { return; } if (!GetSelectedBody()) { return; } // Make sure rendering is done - so we are not changing data being used by collision drawing. FlushRenderingCommands(); //We will first get all the bodysetups we're interested in. The number of duplicates each bodysetup has tells us how many geoms are being deleted //We need to do this first because deleting will modify our selection TMap> BodySelectionMap; TArray BodySetups; for(int32 i=0; iSkeletalBodySetups[SelectedBodies[i].Index]; BodySelectionMap.FindOrAdd(BodySetup).Add(SelectedBodies[i]); } const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "DeletePrimitive", "Delete Primitive") ); for (TMap >::TConstIterator It(BodySelectionMap); It; ++It) { UBodySetup * BodySetup = It.Key(); const TArray & SelectedPrimitives = It.Value(); int32 SphereDeletedCount = 0; int32 BoxDeletedCount = 0; int32 SphylDeletedCount = 0; int32 ConvexDeletedCount = 0; int32 TaperedCapsuleDeletedCount = 0; for (int32 i = 0; i < SelectedPrimitives.Num(); ++i) { const FSelection& SelectedBody = SelectedPrimitives[i]; int32 BodyIndex = PhysicsAsset->FindBodyIndex(BodySetup->BoneName); BodySetup->Modify(); if (SelectedBody.PrimitiveType == EAggCollisionShape::Sphere) { BodySetup->AggGeom.SphereElems.RemoveAt(SelectedBody.PrimitiveIndex - (SphereDeletedCount++)); } else if (SelectedBody.PrimitiveType == EAggCollisionShape::Box) { BodySetup->AggGeom.BoxElems.RemoveAt(SelectedBody.PrimitiveIndex - (BoxDeletedCount++)); } else if (SelectedBody.PrimitiveType == EAggCollisionShape::Sphyl) { BodySetup->AggGeom.SphylElems.RemoveAt(SelectedBody.PrimitiveIndex - (SphylDeletedCount++)); } else if (SelectedBody.PrimitiveType == EAggCollisionShape::Convex) { BodySetup->AggGeom.ConvexElems.RemoveAt(SelectedBody.PrimitiveIndex - (ConvexDeletedCount++)); // Need to invalidate GUID in this case as cooked data must be updated BodySetup->InvalidatePhysicsData(); } else if (SelectedBody.PrimitiveType == EAggCollisionShape::TaperedCapsule) { BodySetup->AggGeom.TaperedCapsuleElems.RemoveAt(SelectedBody.PrimitiveIndex - (TaperedCapsuleDeletedCount++)); } // If this bone has no more geometry - remove it totally. if (BodySetup->AggGeom.GetElementCount() == 0) { check(i == SelectedPrimitives.Num() - 1); //we should really only delete on last prim - only reason this is even in for loop is because of API needing body index if (BodyIndex != INDEX_NONE) { DeleteBody(BodyIndex, false); } } } } ClearSelectedBody(); // Will call UpdateViewport RefreshPhysicsAssetChange(PhysicsAsset); BroadcastHierarchyChanged(); } FTransform FPhysicsAssetEditorSharedData::GetConstraintBodyTM(const UPhysicsConstraintTemplate* ConstraintSetup, EConstraintFrame::Type Frame) const { if (ConstraintSetup == NULL) { return FTransform::Identity; } USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh == nullptr) { return FTransform::Identity; } int32 BoneIndex; if (Frame == EConstraintFrame::Frame1) { BoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone1); } else { BoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone2); } // If we couldn't find the bone - fall back to identity. if (BoneIndex == INDEX_NONE) { return FTransform::Identity; } else { FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex); BoneTM.RemoveScaling(); return BoneTM; } } FTransform FPhysicsAssetEditorSharedData::GetConstraintWorldTM(const UPhysicsConstraintTemplate* ConstraintSetup, EConstraintFrame::Type Frame, float Scale) const { if (ConstraintSetup == NULL) { return FTransform::Identity; } USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh(); if(EditorSkelMesh == nullptr) { return FTransform::Identity; } FVector Scale3D(Scale); int32 BoneIndex; FTransform LFrame = ConstraintSetup->DefaultInstance.GetRefFrame(Frame); if (Frame == EConstraintFrame::Frame1) { BoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone1); } else { BoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone2); } // If we couldn't find the bone - fall back to identity. if (BoneIndex == INDEX_NONE) { return FTransform::Identity; } else { FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex); BoneTM.RemoveScaling(); LFrame.ScaleTranslation(Scale3D); return LFrame * BoneTM; } } FTransform FPhysicsAssetEditorSharedData::GetConstraintMatrix(int32 ConstraintIndex, EConstraintFrame::Type Frame, float Scale) const { UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[ConstraintIndex]; return GetConstraintWorldTM(ConstraintSetup, Frame, Scale); } FTransform FPhysicsAssetEditorSharedData::GetConstraintWorldTM(const FSelection* Constraint, EConstraintFrame::Type Frame) const { int32 ConstraintIndex = Constraint ? Constraint->Index : INDEX_NONE; if (ConstraintIndex == INDEX_NONE) { return FTransform::Identity; } UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[ConstraintIndex]; return GetConstraintWorldTM(ConstraintSetup, Frame, 1.f); } void FPhysicsAssetEditorSharedData::DeleteCurrentConstraint() { if (!GetSelectedConstraint()) { return; } const FScopedTransaction Transaction( NSLOCTEXT("PhysicsAssetEditor", "DeleteConstraint", "Delete Constraint") ); //Save indices before delete because delete modifies our Selected array TArray Indices; for(int32 i=0; iConstraintSetup[SelectedConstraints[i].Index]); Indices.Add(SelectedConstraints[i].Index); } Indices.Sort(); //These are indices into an array, we must remove it from greatest to smallest so that the indices don't shift for(int32 i=Indices.Num() - 1; i>= 0; --i) { PhysicsAsset->Modify(); FPhysicsAssetUtils::DestroyConstraint(PhysicsAsset, Indices[i]); } ClearSelectedConstraints(); BroadcastHierarchyChanged(); BroadcastPreviewChanged(); } void FPhysicsAssetEditorSharedData::ToggleSimulation() { // don't start simulation if there are no bodies or if we are manipulating a body if (PhysicsAsset->SkeletalBodySetups.Num() == 0 || bManipulating) { return; } EnableSimulation(!bRunningSimulation); } void FPhysicsAssetEditorSharedData::EnableSimulation(bool bEnableSimulation) { // keep the EditorSkelComp animation asset if any set UAnimationAsset* PreviewAnimationAsset = nullptr; if (EditorSkelComp->PreviewInstance) { PreviewAnimationAsset = EditorSkelComp->PreviewInstance->CurrentAsset; } if (bEnableSimulation) { // in Chaos, we have to manipulate the RBAN node in the Anim Instance (at least until we get SkelMeshComp implemented) const bool bUseRBANSolver = (PhysicsAsset->SolverType == EPhysicsAssetSolverType::RBAN); MouseHandle->SetAnimInstanceMode(bUseRBANSolver); if (!bUseRBANSolver) { // We should not already have an instance (destroyed when stopping sim). EditorSkelComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); EditorSkelComp->SetSimulatePhysics(true); EditorSkelComp->ResetAllBodiesSimulatePhysics(); EditorSkelComp->SetPhysicsBlendWeight(EditorOptions->PhysicsBlend); PhysicalAnimationComponent->SetSkeletalMeshComponent(EditorSkelComp); // Make it start simulating EditorSkelComp->WakeAllRigidBodies(); } else { // Enable the PreviewInstance (containing the AnimNode_RigidBody) EditorSkelComp->SetAnimationMode(EAnimationMode::AnimationCustomMode); EditorSkelComp->InitAnim(true); // Disable main solver physics EditorSkelComp->SetAllBodiesSimulatePhysics(false); // make sure we enable the preview animation is any compatible with the skeleton if (PreviewAnimationAsset && EditorSkelComp->SkeletalMesh && PreviewAnimationAsset->GetSkeleton() == EditorSkelComp->SkeletalMesh->GetSkeleton()) { EditorSkelComp->EnablePreview(true, PreviewAnimationAsset); EditorSkelComp->Play(true); } // Add the floor TSharedPtr Scene = PreviewScene.Pin(); if (Scene != nullptr) { UStaticMeshComponent* FloorMeshComponent = const_cast(Scene->GetFloorMeshComponent()); if ((FloorMeshComponent != nullptr) && (FloorMeshComponent->GetBodyInstance() != nullptr)) { EditorSkelComp->CreateSimulationFloor(FloorMeshComponent->GetBodyInstance(), FloorMeshComponent->GetBodyInstance()->GetUnrealWorldTransform()); } } } if(EditorOptions->bResetClothWhenSimulating) { EditorSkelComp->RecreateClothingActors(); } } else { // Disable the PreviewInstance //EditorSkelComp->AnimScriptInstance = nullptr; //if(EditorSkelComp->GetAnimationMode() != EAnimationMode::AnimationSingleNode) { EditorSkelComp->SetAnimationMode(EAnimationMode::AnimationSingleNode); } // Stop any animation and clear node when stopping simulation. PhysicalAnimationComponent->SetSkeletalMeshComponent(nullptr); // Undo ends up recreating the anim script instance, so we need to remove it here (otherwise the AnimNode_RigidBody similation starts when we undo) EditorSkelComp->ClearAnimScriptInstance(); EditorSkelComp->SetPhysicsBlendWeight(0.f); EditorSkelComp->ResetAllBodiesSimulatePhysics(); EditorSkelComp->SetSimulatePhysics(false); ForceDisableSimulation(); // Since simulation, actor location changes. Reset to identity EditorSkelComp->SetWorldTransform(ResetTM); // Force an update of the skeletal mesh to get it back to ref pose EditorSkelComp->RefreshBoneTransforms(); // restore the EditorSkelComp animation asset if (PreviewAnimationAsset) { EditorSkelComp->EnablePreview(true, PreviewAnimationAsset); } BroadcastPreviewChanged(); } bRunningSimulation = bEnableSimulation; } void FPhysicsAssetEditorSharedData::OpenNewBodyDlg() { OpenNewBodyDlg(&NewBodyResponse); } void FPhysicsAssetEditorSharedData::OpenNewBodyDlg(EAppReturnType::Type* NewBodyResponse) { TSharedRef ModalWindow = SNew(SWindow) .Title(LOCTEXT("NewAssetTitle", "New Physics Asset")) .SizingRule(ESizingRule::FixedSize) .ClientSize(FVector2D(400.0f, 400.0f)) .SupportsMinimize(false) .SupportsMaximize(false); TWeakPtr ModalWindowPtr = ModalWindow; ModalWindow->SetContent( CreateGenerateBodiesWidget( FSimpleDelegate::CreateLambda([ModalWindowPtr, NewBodyResponse]() { *NewBodyResponse = EAppReturnType::Ok; ModalWindowPtr.Pin()->RequestDestroyWindow(); }), FSimpleDelegate::CreateLambda([ModalWindowPtr, NewBodyResponse]() { *NewBodyResponse = EAppReturnType::Cancel; ModalWindowPtr.Pin()->RequestDestroyWindow(); }), true, LOCTEXT("CreateAsset", "Create Asset"), true )); GEditor->EditorAddModalWindow(ModalWindow); } TSharedRef FPhysicsAssetEditorSharedData::CreateGenerateBodiesWidget(const FSimpleDelegate& InOnCreate, const FSimpleDelegate& InOnCancel, const TAttribute& InIsEnabled, const TAttribute& InCreateButtonText, bool bForNewAsset) { FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; DetailsViewArgs.bHideSelectionTip = true; DetailsViewArgs.bAllowSearch = false; FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked("PropertyEditor"); TSharedRef DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs); GetMutableDefault()->LoadConfig(); DetailsView->SetObject(GetMutableDefault()); DetailsView->OnFinishedChangingProperties().AddLambda([](const FPropertyChangedEvent& InEvent){ GetMutableDefault()->SaveConfig(); }); return SNew(SVerticalBox) .IsEnabled(InIsEnabled) +SVerticalBox::Slot() .FillHeight(1.0f) [ DetailsView ] +SVerticalBox::Slot() .AutoHeight() [ SNew(SBorder) .BorderImage(FAppStyle::Get().GetBrush("Brushes.Panel")) .VAlign(VAlign_Center) .HAlign(HAlign_Right) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(2.0f) .AutoWidth() [ SNew(SPrimaryButton) .Text(InCreateButtonText) .OnClicked_Lambda([InOnCreate]() { GetMutableDefault()->SaveConfig(); InOnCreate.ExecuteIfBound(); return FReply::Handled(); }) .ToolTipText(bForNewAsset ? LOCTEXT("CreateAsset_Tooltip", "Create a new physics asset using these settings.") : LOCTEXT("GenerateBodies_Tooltip", "Generate new bodies and constraints. If bodies are selected then they will be replaced along with their constraints using the new settings, otherwise all bodies and constraints will be re-created")) ] +SHorizontalBox::Slot() .Padding(2.0f) .AutoWidth() [ SNew(SButton) .Visibility_Lambda([bForNewAsset](){ return bForNewAsset ? EVisibility::Visible : EVisibility::Collapsed; }) .ButtonStyle(FAppStyle::Get(), "FlatButton") .ForegroundColor(FLinearColor::White) .ContentPadding(FMargin(6, 2)) .OnClicked_Lambda([InOnCancel](){ InOnCancel.ExecuteIfBound(); return FReply::Handled(); }) [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "PhysicsAssetEditor.Tools.Font") .Text(LOCTEXT("Cancel", "Cancel")) ] ] ] ]; } void FPhysicsAssetEditorSharedData::PostUndo() { bool bInvalidSelection = false; for (int32 BodyIndex = 0; BodyIndex < SelectedBodies.Num() && bInvalidSelection == false; ++BodyIndex) { const FSelection& Selection = SelectedBodies[BodyIndex]; if (PhysicsAsset->SkeletalBodySetups.Num() <= Selection.Index) { bInvalidSelection = true; } else { if (UBodySetup * BodySetup = PhysicsAsset->SkeletalBodySetups[Selection.Index]) { switch (Selection.PrimitiveType) { case EAggCollisionShape::Box: bInvalidSelection = BodySetup->AggGeom.BoxElems.Num() <= Selection.PrimitiveIndex ? true : bInvalidSelection; break; case EAggCollisionShape::Convex: bInvalidSelection = BodySetup->AggGeom.ConvexElems.Num() <= Selection.PrimitiveIndex ? true : bInvalidSelection; break; case EAggCollisionShape::Sphere: bInvalidSelection = BodySetup->AggGeom.SphereElems.Num() <= Selection.PrimitiveIndex ? true : bInvalidSelection; break; case EAggCollisionShape::Sphyl: bInvalidSelection = BodySetup->AggGeom.SphylElems.Num() <= Selection.PrimitiveIndex ? true : bInvalidSelection; break; case EAggCollisionShape::TaperedCapsule: bInvalidSelection = BodySetup->AggGeom.TaperedCapsuleElems.Num() <= Selection.PrimitiveIndex ? true : bInvalidSelection; break; default: bInvalidSelection = true; } } else { bInvalidSelection = true; } } } for (int32 ConstraintIndex = 0; ConstraintIndex < SelectedConstraints.Num() && bInvalidSelection == false; ++ConstraintIndex) { const FSelection& Selection = SelectedConstraints[ConstraintIndex]; if (PhysicsAsset->ConstraintSetup.Num() <= Selection.Index) { bInvalidSelection = true; } } if (bInvalidSelection) { // Clear selection before we undo. We don't transact the editor itself - don't want to have something selected that is then removed. ClearSelectedBody(); ClearSelectedConstraints(); } BroadcastPreviewChanged(); BroadcastHierarchyChanged(); } void FPhysicsAssetEditorSharedData::Redo() { if (bRunningSimulation) { return; } ClearSelectedBody(); ClearSelectedConstraints(); GEditor->RedoTransaction(); PhysicsAsset->UpdateBodySetupIndexMap(); BroadcastPreviewChanged(); BroadcastHierarchyChanged(); } void FPhysicsAssetEditorSharedData::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddReferencedObject(PhysicsAsset); Collector.AddReferencedObject(EditorSkelComp); Collector.AddReferencedObject(PhysicalAnimationComponent); Collector.AddReferencedObject(EditorOptions); Collector.AddReferencedObject(MouseHandle); if (PreviewScene != nullptr) { PreviewScene.Pin()->AddReferencedObjects(Collector); } } void FPhysicsAssetEditorSharedData::ForceDisableSimulation() { // Reset simulation state of body instances so we dont actually simulate outside of 'simulation mode' for (int32 BodyIdx = 0; BodyIdx < EditorSkelComp->Bodies.Num(); ++BodyIdx) { if (FBodyInstance* BodyInst = EditorSkelComp->Bodies[BodyIdx]) { if (UBodySetup* PhysAssetBodySetup = PhysicsAsset->SkeletalBodySetups[BodyIdx]) { BodyInst->SetInstanceSimulatePhysics(false); } } } } void FPhysicsAssetEditorSharedData::UpdateClothPhysics() { if(EditorSkelComp && EditorSkelComp->GetClothingSimulationInteractor()) { EditorSkelComp->GetClothingSimulationInteractor()->PhysicsAssetUpdated(); } } FPhysicsAssetRenderSettings* FPhysicsAssetEditorSharedData::GetRenderSettings() const { return UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset); } #undef LOCTEXT_NAMESPACE