You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* Removed deprecated or dead code paths * Simplified build system setup for physics support * Deprecated build system flags and unsupported macros #jira none #rb Chris.Caulfield, Kriss.Gossart #preflight 62963ec0fe779f23c8ea0c5e [CL 20450744 by Benn Gallagher in ue5-main branch]
2894 lines
90 KiB
C++
2894 lines
90 KiB
C++
// 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<FPhysicsAssetEditorSharedData> 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<UPhysicsAssetEditorPhysicsHandleComponent>();
|
|
|
|
// Construct sim options.
|
|
EditorOptions = NewObject<UPhysicsAssetEditorOptions>(GetTransientPackage(), MakeUniqueObjectName(GetTransientPackage(), UPhysicsAssetEditorOptions::StaticClass(), FName(TEXT("EditorOptions"))), RF_Transactional);
|
|
check(EditorOptions);
|
|
|
|
EditorOptions->LoadConfig();
|
|
}
|
|
|
|
FPhysicsAssetEditorSharedData::~FPhysicsAssetEditorSharedData()
|
|
{
|
|
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::Initialize(const TSharedRef<IPersonaPreviewScene>& 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 <PhysicsAsset->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<IMeshUtilities>("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<int32> MissingBodyIndices;
|
|
FString BoneNames;
|
|
for (int32 i = 0; i <PhysicsAsset->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<FString> ParsedString;
|
|
ClipboardContent.ParseIntoArray(ParsedString, TEXT(";"), true);
|
|
|
|
if (ParsedString.Num() != 3)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FSoftObjectPath PhysicsAssetPath(ParsedString[0]);
|
|
OutAsset = Cast<UPhysicsAsset>(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<FMirrorInfo> 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<int32>& BodiesIndices, bool bSelected)
|
|
{
|
|
if (BodiesIndices.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (BodiesIndices.Num() == 1 && BodiesIndices[0] == INDEX_NONE)
|
|
{
|
|
ClearSelectedBody();
|
|
return;
|
|
}
|
|
|
|
TArray<FSelection> 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<int32>& BodiesIndices, bool bSelected)
|
|
{
|
|
if (BodiesIndices.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (BodiesIndices.Num() == 1 && BodiesIndices[0] == INDEX_NONE)
|
|
{
|
|
ClearSelectedBody();
|
|
return;
|
|
}
|
|
|
|
TArray<FSelection> 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<FSelection>& 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<int32> 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<int32> 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 <PhysicsAsset->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<int32> 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; i<SelectedBodies.Num(); ++i)
|
|
{
|
|
for(int32 j=i+1; j<SelectedBodies.Num(); ++j)
|
|
{
|
|
if(bEnableCollision)
|
|
{
|
|
PhysicsAsset->EnableCollision(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; i<SelectedBodies.Num(); ++i)
|
|
{
|
|
for(int32 j=i+1; j<SelectedBodies.Num(); ++j)
|
|
{
|
|
if(PhysicsAsset->IsCollisionEnabled(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; i<SelectedBodies.Num(); ++i)
|
|
{
|
|
for(int32 j = 0; j < PhysicsAsset->SkeletalBodySetups.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; i<SelectedBodies.Num(); ++i)
|
|
{
|
|
for(int32 j = 0; j < PhysicsAsset->SkeletalBodySetups.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<int32> 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<int32> 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<USkeletalBodySetup>() || InObjectClass->IsChildOf<UPhysicsConstraintTemplate>());
|
|
}
|
|
|
|
virtual void ProcessConstructedObject(UObject* NewObject) override
|
|
{
|
|
check(NewObject);
|
|
if (NewObject->IsA<USkeletalBodySetup>())
|
|
{
|
|
NewBodySetups.Add(Cast<USkeletalBodySetup>(NewObject));
|
|
}
|
|
else if (NewObject->IsA<UPhysicsConstraintTemplate>())
|
|
{
|
|
NewConstraintTemplates.Add(Cast<UPhysicsConstraintTemplate>(NewObject));
|
|
}
|
|
}
|
|
|
|
public:
|
|
TArray<USkeletalBodySetup*> NewBodySetups;
|
|
TArray<UPhysicsConstraintTemplate*> 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<UPackage>(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<UPhysicsAssetGenerationSettings>()->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<UBodySetup>(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; i<SelectedBodies.Num(); ++i)
|
|
{
|
|
UBodySetup* ToBodySetup = PhysicsAsset->SkeletalBodySetups[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; i<SelectedBodies.Num(); ++i)
|
|
{
|
|
if(SelectedBodies[BodyIndex0].Index == SelectedBodies[i].Index)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(BodyIndex1== INDEX_NONE)
|
|
{
|
|
BodyIndex1 = i;
|
|
}else
|
|
{
|
|
if(SelectedBodies[BodyIndex1].Index != SelectedBodies[i].Index)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//need to weld bodies not primitives
|
|
if(BodyIndex1 == INDEX_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FSelection& Body0 = SelectedBodies[BodyIndex0];
|
|
const FSelection& Body1 = SelectedBodies[BodyIndex1];
|
|
|
|
FName Bone0Name = PhysicsAsset->SkeletalBodySetups[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<int32> Constraints;
|
|
PhysicsAsset->BodyFindConstraints(ChildBodyIndex, Constraints);
|
|
|
|
for (int32 i = 0; i <Constraints.Num(); ++i)
|
|
{
|
|
int32 ConstraintIndex = Constraints[i];
|
|
PhysicsAsset->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<UPhysicsAssetGenerationSettings>()->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<int32>& ChildBodyIndices)
|
|
{
|
|
// check we have valid bodies
|
|
check(ParentBodyIndex < PhysicsAsset->SkeletalBodySetups.Num());
|
|
|
|
TArray<int32> 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<UPhysicsConstraintTemplate>(SourceConstraint);
|
|
|
|
if(FromConstraintSetup && SelectedConstraints.Num() > 0)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("PhysicsAssetEditor", "PasteConstraintProperties", "Paste Constraint Properties"));
|
|
|
|
for(int32 i=0; i<SelectedConstraints.Num(); ++i)
|
|
{
|
|
UPhysicsConstraintTemplate* ToConstraintSetup = PhysicsAsset->ConstraintSetup[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; i<SelectedConstraints.Num(); ++i)
|
|
{
|
|
UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[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; i<SelectedConstraints.Num(); ++i)
|
|
{
|
|
UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[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; i<SelectedConstraints.Num(); ++i)
|
|
{
|
|
UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[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<int32> Constraints;
|
|
PhysicsAsset->BodyFindConstraints(DelBodyIndex, Constraints);
|
|
|
|
//we want to fixup constraints so that nearest child bodies get constraint with parent body
|
|
TArray<int32> 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<UBodySetup *, TArray<FSelection>> BodySelectionMap;
|
|
TArray<UBodySetup*> BodySetups;
|
|
for(int32 i=0; i<SelectedBodies.Num(); ++i)
|
|
{
|
|
UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[SelectedBodies[i].Index];
|
|
BodySelectionMap.FindOrAdd(BodySetup).Add(SelectedBodies[i]);
|
|
}
|
|
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "DeletePrimitive", "Delete Primitive") );
|
|
|
|
for (TMap<UBodySetup*, TArray<FSelection> >::TConstIterator It(BodySelectionMap); It; ++It)
|
|
{
|
|
UBodySetup * BodySetup = It.Key();
|
|
const TArray<FSelection> & 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<int32> Indices;
|
|
for(int32 i=0; i<SelectedConstraints.Num(); ++i)
|
|
{
|
|
ConditionalClearClipboard(SharedDataConstants::ConstraintType, PhysicsAsset->ConstraintSetup[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<IPersonaPreviewScene> Scene = PreviewScene.Pin();
|
|
if (Scene != nullptr)
|
|
{
|
|
UStaticMeshComponent* FloorMeshComponent = const_cast<UStaticMeshComponent*>(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<SWindow> ModalWindow = SNew(SWindow)
|
|
.Title(LOCTEXT("NewAssetTitle", "New Physics Asset"))
|
|
.SizingRule(ESizingRule::FixedSize)
|
|
.ClientSize(FVector2D(400.0f, 400.0f))
|
|
.SupportsMinimize(false)
|
|
.SupportsMaximize(false);
|
|
|
|
TWeakPtr<SWindow> 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<SWidget> FPhysicsAssetEditorSharedData::CreateGenerateBodiesWidget(const FSimpleDelegate& InOnCreate, const FSimpleDelegate& InOnCancel, const TAttribute<bool>& InIsEnabled, const TAttribute<FText>& InCreateButtonText, bool bForNewAsset)
|
|
{
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
|
|
DetailsViewArgs.bHideSelectionTip = true;
|
|
DetailsViewArgs.bAllowSearch = false;
|
|
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
TSharedRef<IDetailsView> DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
|
|
|
|
GetMutableDefault<UPhysicsAssetGenerationSettings>()->LoadConfig();
|
|
DetailsView->SetObject(GetMutableDefault<UPhysicsAssetGenerationSettings>());
|
|
DetailsView->OnFinishedChangingProperties().AddLambda([](const FPropertyChangedEvent& InEvent){ GetMutableDefault<UPhysicsAssetGenerationSettings>()->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<UPhysicsAssetGenerationSettings>()->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
|