3849 lines
130 KiB
C++
3849 lines
130 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PhysicsAssetEditorSharedData.h"
|
|
#include "Animation/MirrorDataTable.h"
|
|
#include "Chaos/GeometryQueries.h"
|
|
#include "Chaos/Box.h"
|
|
#include "Chaos/Capsule.h"
|
|
#include "Chaos/Sphere.h"
|
|
#include "PhysicsAssetEditorPhysicsHandleComponent.h"
|
|
#include "PhysicsAssetRenderUtils.h"
|
|
#include "PhysicsAssetEditorSelection.h"
|
|
#include "PhysicsEngine/RigidBodyIndexPair.h"
|
|
#include "Math/Axis.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 "PhysicsEngine/SkeletalBodySetup.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 "Widgets/Notifications/SNotificationList.h"
|
|
#include "Framework/Notifications/NotificationManager.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"
|
|
|
|
namespace SharedDataConstants
|
|
{
|
|
const FString ConstraintType = TEXT("Constraint");
|
|
const FString BodyType = TEXT("SkeletalBodySetup");
|
|
}
|
|
|
|
// File Scope Utility Functions //
|
|
|
|
/** Returns the Editor Body Flag bit mask that indicates if the supplied axis has been fixed in component space */
|
|
int32 FindCoMAxisEditorBodyFlag(const EAxis::Type InAxis)
|
|
{
|
|
return int32(1) << (InAxis - EAxis::X); // Ensure that X axis is represented by bit 0.
|
|
}
|
|
|
|
namespace
|
|
{
|
|
template <typename TShapeElem>
|
|
void SetSelectedBodiesPrimitivesHelper(const int32 BodyIndex, const TArray<TShapeElem>& ShapeElems, TArray<FPhysicsAssetEditorSharedData::FSelection>& SelectedElems, const TFunction<bool(const TArray<FPhysicsAssetEditorSharedData::FSelection>&, const int32 BodyIndex, const FKShapeElem&)>& Predicate)
|
|
{
|
|
for (int32 PrimitiveIndex = 0; PrimitiveIndex < ShapeElems.Num(); ++PrimitiveIndex)
|
|
{
|
|
const TShapeElem& ShapeElem = ShapeElems[PrimitiveIndex];
|
|
if (Predicate(SelectedElems, BodyIndex, ShapeElem))
|
|
{
|
|
SelectedElems.Add(MakePrimitiveSelection(BodyIndex, ShapeElem.GetShapeType(), PrimitiveIndex));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FPhysicsAssetEditorSharedData::FSelection> CreateBodyPrimitivesSelection(TObjectPtr<UPhysicsAsset> PhysicsAsset, const TArray<int32>& BodiesIndices, const TFunction<bool(const TArray<FPhysicsAssetEditorSharedData::FSelection>&, const int32 BodyIndex, const FKShapeElem&)>& Predicate)
|
|
{
|
|
TArray<FPhysicsAssetEditorSharedData::FSelection> NewSelection;
|
|
for (const int32 BodyIndex : BodiesIndices)
|
|
{
|
|
UBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[BodyIndex];
|
|
check(BodySetup);
|
|
|
|
const FKAggregateGeom& AggGeom = BodySetup->AggGeom;
|
|
SetSelectedBodiesPrimitivesHelper(BodyIndex, AggGeom.SphereElems, NewSelection, Predicate);
|
|
SetSelectedBodiesPrimitivesHelper(BodyIndex, AggGeom.BoxElems, NewSelection, Predicate);
|
|
SetSelectedBodiesPrimitivesHelper(BodyIndex, AggGeom.SphylElems, NewSelection, Predicate);
|
|
SetSelectedBodiesPrimitivesHelper(BodyIndex, AggGeom.ConvexElems, NewSelection, Predicate);
|
|
SetSelectedBodiesPrimitivesHelper(BodyIndex, AggGeom.TaperedCapsuleElems, NewSelection, Predicate);
|
|
SetSelectedBodiesPrimitivesHelper(BodyIndex, AggGeom.LevelSetElems, NewSelection, Predicate);
|
|
SetSelectedBodiesPrimitivesHelper(BodyIndex, AggGeom.SkinnedLevelSetElems, NewSelection, Predicate);
|
|
SetSelectedBodiesPrimitivesHelper(BodyIndex, AggGeom.MLLevelSetElems, NewSelection, Predicate);
|
|
SetSelectedBodiesPrimitivesHelper(BodyIndex, AggGeom.SkinnedTriangleMeshElems, NewSelection, Predicate);
|
|
}
|
|
|
|
return NewSelection;
|
|
}
|
|
|
|
// Pass each unique pair of values (excluding those containing the same value twice) in the supplied collection to the supplied function object.
|
|
template<typename CollectionType, typename FunctionObjectType>
|
|
void ForEachUniquePair(CollectionType&& Collection, const FunctionObjectType & FunctionObject)
|
|
{
|
|
using IteratorType = typename std::remove_reference<CollectionType>::type::TConstIterator;
|
|
|
|
for (IteratorType OuterItr = Collection.CreateConstIterator(); OuterItr; ++OuterItr)
|
|
{
|
|
IteratorType InnerItr = OuterItr;
|
|
|
|
++InnerItr;
|
|
|
|
for (; InnerItr; ++InnerItr)
|
|
{
|
|
FunctionObject(*OuterItr, *InnerItr);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename CollectionType>
|
|
bool SelectionContainsIndex(CollectionType&& Collection, const int32 InIndex)
|
|
{
|
|
return Algo::FindByPredicate(Collection, [InIndex](const FPhysicsAssetEditorSharedData::FSelection& InSelection) // Predicate returns true if the index is already in the selection.
|
|
{
|
|
return InSelection.Index == InIndex;
|
|
}
|
|
) != nullptr;
|
|
}
|
|
|
|
Chaos::TSphere<Chaos::FReal, 3> ConvertPrimitiveToImplicitObject(const FKSphereElem& Elem)
|
|
{
|
|
return Chaos::TSphere<Chaos::FReal, 3>(FVector::Zero(), Elem.Radius);
|
|
}
|
|
|
|
Chaos::FCapsule ConvertPrimitiveToImplicitObject(const FKSphylElem& Elem)
|
|
{
|
|
// FKSphylElem : Axis of Capsule is along the z-axis of the transform.
|
|
// FCapsule : Requires two end points for construction.
|
|
|
|
const FVector HalfAxis = FVector::ZAxisVector * Elem.Length * 0.5f;
|
|
return Chaos::FCapsule(-HalfAxis, HalfAxis, Elem.Radius);
|
|
}
|
|
|
|
Chaos::TBox<Chaos::FReal, 3> ConvertPrimitiveToImplicitObject(const FKBoxElem& Elem)
|
|
{
|
|
const FVector HalfExtents = FVector(Elem.X, Elem.Y, Elem.Z) * 0.5f;
|
|
return Chaos::TBox<Chaos::FReal, 3>(-HalfExtents, HalfExtents);
|
|
}
|
|
|
|
// Returns true if the two supplied primitive shapes overlap.
|
|
template< typename PrimitiveAType, typename PrimitiveBType > bool DoPrimitivesOverlap(const PrimitiveAType& PrimitiveA, const Chaos::FRigidTransform3& BoneTMA, const PrimitiveBType& PrimitiveB, const Chaos::FRigidTransform3& BoneTMB)
|
|
{
|
|
auto ImplicitObjectA = ConvertPrimitiveToImplicitObject(PrimitiveA);
|
|
auto ImplicitObjectB = ConvertPrimitiveToImplicitObject(PrimitiveB);
|
|
|
|
const FTransform PrimitiveTMA = PrimitiveA.GetTransform() * BoneTMA;
|
|
const FTransform PrimitiveTMB = PrimitiveB.GetTransform() * BoneTMB;
|
|
|
|
return Chaos::Utilities::CastHelper(ImplicitObjectA, PrimitiveTMA, [ImplicitObjectB, PrimitiveTMB](const auto& Downcast, const auto& FullGeomTransform)
|
|
{
|
|
return Chaos::OverlapQuery(ImplicitObjectB, PrimitiveTMB, Downcast, FullGeomTransform, /*Thickness=*/0);
|
|
});
|
|
|
|
// TODO: Add support for FKTaperedCapsuleElem - currently unsupported by Chaos::OverlapQuery / CastHelper
|
|
}
|
|
|
|
// Applies an operator to all primitives in the supplied geometry that could be included in an RBAN simulation.
|
|
template< typename OperationType > void ForEachRBANPrimitive(FKAggregateGeom& AggregateGeometry, OperationType& Operation)
|
|
{
|
|
for (FKSphereElem& Elem : AggregateGeometry.SphereElems)
|
|
{
|
|
Operation(Elem);
|
|
}
|
|
|
|
for (FKBoxElem& Elem : AggregateGeometry.BoxElems)
|
|
{
|
|
Operation(Elem);
|
|
}
|
|
|
|
for (FKSphylElem& Elem : AggregateGeometry.SphylElems)
|
|
{
|
|
Operation(Elem);
|
|
}
|
|
|
|
// TODO: Add support for FKTaperedCapsuleElem
|
|
}
|
|
|
|
// Returns true if any of the primitives in either supplied body overlap.
|
|
bool DoBodiesOverlap(TObjectPtr<USkeletalBodySetup> BodyA, TObjectPtr<USkeletalBodySetup> BodyB, TObjectPtr<UPhysicsAsset> PhysicsAsset, TObjectPtr<UPhysicsAssetEditorSkeletalMeshComponent> EditorSkelComp)
|
|
{
|
|
bool bIsOverlapping = false;
|
|
|
|
if (EditorSkelComp)
|
|
{
|
|
if (const USkeletalMesh* const EditorSkelMesh = PhysicsAsset->GetPreviewMesh())
|
|
{
|
|
// Test each geometry object in Body A against each geometry object in Body B - return true if any overlap.
|
|
|
|
const FName BoneNameA = BodyA->BoneName;
|
|
const int32 BoneIndexA = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(BoneNameA);
|
|
const Chaos::FRigidTransform3 BoneTMA(EditorSkelComp->GetBoneTransform(BoneIndexA).ToMatrixWithScale());
|
|
|
|
const FName BoneNameB = BodyB->BoneName;
|
|
const int32 BoneIndexB = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(BoneNameB);
|
|
const Chaos::FRigidTransform3 BoneTMB(EditorSkelComp->GetBoneTransform(BoneIndexB).ToMatrixWithScale());
|
|
|
|
auto PrimativeOverlapOuter = [&BoneTMA, &BodyB, &BoneTMB, &bIsOverlapping](const auto PrimitiveA)
|
|
{
|
|
auto PrimativeOverlapInner = [&PrimitiveA, &BoneTMA, &BoneTMB, &bIsOverlapping](const auto PrimitiveB)
|
|
{
|
|
bIsOverlapping |= DoPrimitivesOverlap(PrimitiveA, BoneTMA, PrimitiveB, BoneTMB);
|
|
};
|
|
|
|
ForEachRBANPrimitive(BodyB->AggGeom, PrimativeOverlapInner); // For each geometry object in Body B
|
|
};
|
|
|
|
ForEachRBANPrimitive(BodyA->AggGeom, PrimativeOverlapOuter); // For each geometry object in Body A
|
|
}
|
|
}
|
|
|
|
return bIsOverlapping;
|
|
}
|
|
|
|
bool IsBodyPairCollisionEnabled(TObjectPtr<UPhysicsAsset> PhysicsAsset, const int32 BodyAIndex, const int32 BodyBIndex)
|
|
{
|
|
return !PhysicsAsset->CollisionDisableTable.Find(FRigidBodyIndexPair(BodyAIndex, BodyBIndex));
|
|
}
|
|
|
|
// class FScopedBulkSelection //
|
|
|
|
FScopedBulkSelection::FScopedBulkSelection(TSharedPtr<FPhysicsAssetEditorSharedData> InSharedData)
|
|
: SharedData(InSharedData)
|
|
{
|
|
SharedData->bSuspendSelectionBroadcast = true;
|
|
}
|
|
|
|
FScopedBulkSelection::~FScopedBulkSelection()
|
|
{
|
|
SharedData->bSuspendSelectionBroadcast = false;
|
|
SharedData->BroadcastSelectionChanged();
|
|
}
|
|
|
|
// class FPhysicsAssetEditorSharedData //
|
|
|
|
FPhysicsAssetEditorSharedData::FPhysicsAssetEditorSharedData()
|
|
: COMRenderColor(255,255,100)
|
|
, bSuspendSelectionBroadcast(false)
|
|
, InsideSelChange(0)
|
|
{
|
|
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();
|
|
|
|
// Construct selection manager.
|
|
SelectedObjects = NewObject<UPhysicsAssetEditorSelection>(GetTransientPackage(), MakeUniqueObjectName(GetTransientPackage(), UPhysicsAssetEditorSelection::StaticClass(), FName(TEXT("PhysicsAssetEditorSelectedObjects"))), RF_Transactional);
|
|
check(SelectedObjects);
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PhysicsAsset->EditorBodyFlags.SetNum(PhysicsAsset->SkeletalBodySetups.Num(), EAllowShrinking::Yes);
|
|
|
|
// Support undo/redo
|
|
PhysicsAsset->SetFlags(RF_Transactional);
|
|
|
|
ClearSelectedBody();
|
|
ClearSelectedCoMs();
|
|
ClearSelectedConstraints();
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::BroadcastSelectionChanged()
|
|
{
|
|
if (!bSuspendSelectionBroadcast)
|
|
{
|
|
SelectionChangedEvent.Broadcast(SelectedObjects->SelectedElements());
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ToggleShowCom()
|
|
{
|
|
SetShowCom(!GetShowCom());
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetShowCom(bool InValue)
|
|
{
|
|
if(FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings())
|
|
{
|
|
PhysicsAssetRenderSettings->CenterOfMassViewMode = (InValue) ? EPhysicsAssetEditorCenterOfMassViewMode::All : EPhysicsAssetEditorCenterOfMassViewMode::None;
|
|
}
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::GetShowCom() const
|
|
{
|
|
if(FPhysicsAssetRenderSettings* const PhysicsAssetRenderSettings = GetRenderSettings())
|
|
{
|
|
return PhysicsAssetRenderSettings->CenterOfMassViewMode == EPhysicsAssetEditorCenterOfMassViewMode::All;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FVector FPhysicsAssetEditorSharedData::GetCOMRenderPosition(const int32 BodyIndex) const
|
|
{
|
|
if (IsManipulating())
|
|
{
|
|
if (SelectionContainsIndex(SelectedCoMs(), BodyIndex))
|
|
{
|
|
if (const FVector* const ManipulatedCoMPosition = FindManipulatedBodyCoMPosition(BodyIndex))
|
|
{
|
|
// Return the Selection objects CoM position when manipulating as that is the one we're actually updating with the
|
|
// manipulator widget (as updating the CoM in the physics body proper is complicated).
|
|
return *ManipulatedCoMPosition;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (EditorSkelComp && EditorSkelComp->Bodies.IsValidIndex(BodyIndex))
|
|
{
|
|
if (const FBodyInstance* const EditorBodyInstance = EditorSkelComp->Bodies[BodyIndex])
|
|
{
|
|
return EditorBodyInstance->GetCOMPosition();
|
|
}
|
|
}
|
|
|
|
return FVector::ZeroVector;
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::IsCoMAxisFixedInComponentSpace(const int32 BodyIndex, const EAxis::Type InAxis) const
|
|
{
|
|
if (PhysicsAsset && PhysicsAsset->EditorBodyFlags.IsValidIndex(BodyIndex))
|
|
{
|
|
return PhysicsAsset->EditorBodyFlags[BodyIndex] & FindCoMAxisEditorBodyFlag(InAxis);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetCoMAxisFixedInComponentSpace(const int32 BodyIndex, const EAxis::Type InAxis, const bool bValue)
|
|
{
|
|
if (PhysicsAsset && PhysicsAsset->EditorBodyFlags.IsValidIndex(BodyIndex))
|
|
{
|
|
int32& BodyFlags = PhysicsAsset->EditorBodyFlags[BodyIndex];
|
|
BodyFlags = (bValue) ? BodyFlags | FindCoMAxisEditorBodyFlag(InAxis) : BodyFlags & ~FindCoMAxisEditorBodyFlag(InAxis);
|
|
}
|
|
}
|
|
|
|
FVector FPhysicsAssetEditorSharedData::CalculateCoMNudgeForWorldSpacePosition(const int32 BodyIndex, const FVector& CoMPositionWorldSpace) const
|
|
{
|
|
FVector CalculatedCoMOffset = FVector::ZeroVector;
|
|
|
|
if (EditorSkelComp && EditorSkelComp->Bodies.IsValidIndex(BodyIndex))
|
|
{
|
|
if (FBodyInstance* const EditorBodyInstance = EditorSkelComp->Bodies[BodyIndex])
|
|
{
|
|
const int32 BoneIndex = EditorSkelComp->GetBoneIndex(PhysicsAsset->SkeletalBodySetups[BodyIndex]->BoneName);
|
|
const FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex);
|
|
const FVector CoMWithoutNudge = EditorBodyInstance->GetMassSpaceLocal().GetTranslation() - EditorBodyInstance->COMNudge;
|
|
CalculatedCoMOffset = BoneTM.InverseTransformPosition(CoMPositionWorldSpace) - CoMWithoutNudge;
|
|
}
|
|
}
|
|
|
|
return CalculatedCoMOffset;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::RecordSelectedCoM()
|
|
{
|
|
if (EditorSkelComp)
|
|
{
|
|
ManipulatedBodyCoMPositionMap.Reset();
|
|
|
|
for (const FSelection& SelectedObject : SelectedObjects->UniqueSelectedElementsOfType(FSelection::Body | FSelection::Primitive | FSelection::CenterOfMass))
|
|
{
|
|
ManipulatedBodyCoMPositionMap.FindOrAdd(SelectedObject.GetIndex()) = EditorSkelComp->Bodies[SelectedObject.GetIndex()]->GetCOMPosition();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::PostManipulationUpdateCoM()
|
|
{
|
|
// Update CoM nudge to compensate for any change in body transform on any axis that is fixed in component space.
|
|
for (const FSelection& SelectedObject : SelectedObjects->UniqueSelectedElementsOfType(FSelection::Body | FSelection::Primitive | FSelection::CenterOfMass))
|
|
{
|
|
const int32 BodyIndex = SelectedObject.GetIndex();
|
|
const FBodyInstance* const EditorBodyInstance = EditorSkelComp->Bodies[BodyIndex];
|
|
const FVector* const ManipulationCoMPosition = FindManipulatedBodyCoMPosition(BodyIndex);
|
|
|
|
if (ManipulationCoMPosition) // Expect to find a valid cached CoM position for any selected CoM marker or primitive undergoing manipulation.
|
|
{
|
|
if (SelectedObject.HasType(FSelection::CenterOfMass)) // Directly selected CoM markers have priority over their owning bodies for determining CoM manipulation behavior.
|
|
{
|
|
const FVector CalculatedCoMOffset = CalculateCoMNudgeForWorldSpacePosition(BodyIndex, *ManipulationCoMPosition);
|
|
PhysicsAsset->SkeletalBodySetups[BodyIndex]->DefaultInstance.COMNudge = CalculatedCoMOffset;
|
|
}
|
|
else if (IsCoMAxisFixedInComponentSpace(BodyIndex, EAxis::X) || IsCoMAxisFixedInComponentSpace(BodyIndex, EAxis::Y) || IsCoMAxisFixedInComponentSpace(BodyIndex, EAxis::Z))
|
|
{
|
|
const FVector CoMOffset = EditorBodyInstance->COMNudge;
|
|
FVector CalculatedCoMOffset = CalculateCoMNudgeForWorldSpacePosition(BodyIndex, *ManipulationCoMPosition);
|
|
|
|
// Only apply lock to the specified Axis in bone space.
|
|
if (!IsCoMAxisFixedInComponentSpace(BodyIndex, EAxis::X)) { CalculatedCoMOffset.X = CoMOffset.X; }
|
|
if (!IsCoMAxisFixedInComponentSpace(BodyIndex, EAxis::Y)) { CalculatedCoMOffset.Y = CoMOffset.Y; }
|
|
if (!IsCoMAxisFixedInComponentSpace(BodyIndex, EAxis::Z)) { CalculatedCoMOffset.Z = CoMOffset.Z; }
|
|
|
|
PhysicsAsset->SkeletalBodySetups[BodyIndex]->DefaultInstance.COMNudge = CalculatedCoMOffset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::UpdateCoM()
|
|
{
|
|
if (bShouldUpdatedSelectedCoMs) // < This calculation must be delayed by a frame s.t. changes to the physics state have been propagated to the physics bodies.
|
|
{
|
|
PostManipulationUpdateCoM();
|
|
RefreshPhysicsAssetChange(PhysicsAsset, false);
|
|
ManipulatedBodyCoMPositionMap.Reset();
|
|
bShouldUpdatedSelectedCoMs = false;
|
|
}
|
|
}
|
|
|
|
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;
|
|
TArray<FName> CollidingBodyBoneNames; // Names of the controlling bones of all bodies that this body can collide with.
|
|
FMirrorInfo()
|
|
{
|
|
BoneIndex = INDEX_NONE;
|
|
BodyIndex = INDEX_NONE;
|
|
ConstraintIndex = INDEX_NONE;
|
|
BoneName = NAME_None;
|
|
}
|
|
};
|
|
|
|
template<typename GeometryElementType> void MirrorPrimitives(TArray< GeometryElementType >& PrimitiveCollection)
|
|
{
|
|
static const 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 (GeometryElementType& Primitive : PrimitiveCollection)
|
|
{
|
|
Primitive.Rotation = (Primitive.Rotation.Quaternion() * ArtistMirrorConvention).Rotator();
|
|
Primitive.Center = -Primitive.Center;
|
|
Primitive.SetName(UMirrorDataTable::GetSettingsMirrorName(Primitive.GetName()));
|
|
}
|
|
}
|
|
|
|
template<> void MirrorPrimitives<FKSphereElem>(TArray< FKSphereElem >& PrimitiveCollection)
|
|
{
|
|
for (FKSphereElem& Primitive : PrimitiveCollection)
|
|
{
|
|
Primitive.Center = -Primitive.Center;
|
|
Primitive.SetName(UMirrorDataTable::GetSettingsMirrorName(Primitive.GetName()));
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::Mirror()
|
|
{
|
|
USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh();
|
|
if(EditorSkelMesh)
|
|
{
|
|
// Build list of all bodies and constraints to be mirrored
|
|
TArray<FMirrorInfo> MirrorInfos;
|
|
MirrorInfos.Reserve(UniqueSelectionReferencingBodies().Num() + SelectedConstraints().Num());
|
|
|
|
for (const FSelection& Selection : UniqueSelectionReferencingBodies())
|
|
{
|
|
MirrorInfos.AddDefaulted();
|
|
FMirrorInfo & MirrorInfo = MirrorInfos[MirrorInfos.Num() - 1];
|
|
MirrorInfo.BoneName = PhysicsAsset->SkeletalBodySetups[Selection.Index]->BoneName;
|
|
MirrorInfo.BodyIndex = Selection.Index;
|
|
MirrorInfo.ConstraintIndex = PhysicsAsset->FindConstraintIndex(MirrorInfo.BoneName);
|
|
|
|
// Record all the colliding body bone names
|
|
// - This must be done before the bodies are mirrored because information may be lost in that process (for example, a user
|
|
// could select a mirrored pair of bodies. Both would be destroyed and recreated before collision interactions were mirrored).
|
|
// - Need to store bone names as body indexs can change during mirroring.
|
|
for (int32 CollidingBodyIndex = 0; CollidingBodyIndex < PhysicsAsset->SkeletalBodySetups.Num(); ++CollidingBodyIndex)
|
|
{
|
|
if (PhysicsAsset->IsCollisionEnabled(CollidingBodyIndex, MirrorInfo.BodyIndex))
|
|
{
|
|
const FName CollidingBoneName = PhysicsAsset->SkeletalBodySetups[CollidingBodyIndex]->BoneName;
|
|
MirrorInfo.CollidingBodyBoneNames.Add(CollidingBoneName);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const FSelection& Selection : SelectedConstraints())
|
|
{
|
|
MirrorInfos.AddDefaulted();
|
|
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"));
|
|
MakeOrRecreateBody(MirrorBoneIndex, false);
|
|
|
|
const int MirrorBodyIndex = PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, MirrorBoneIndex);
|
|
check(MirrorBodyIndex != INDEX_NONE);
|
|
|
|
UBodySetup* const DestBody = PhysicsAsset->SkeletalBodySetups[MirrorBodyIndex];
|
|
DestBody->Modify();
|
|
DestBody->CopyBodyPropertiesFrom(SrcBody);
|
|
|
|
MirrorPrimitives(DestBody->AggGeom.SphylElems);
|
|
MirrorPrimitives(DestBody->AggGeom.BoxElems);
|
|
MirrorPrimitives(DestBody->AggGeom.SphereElems);
|
|
MirrorPrimitives(DestBody->AggGeom.TaperedCapsuleElems);
|
|
|
|
const 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);
|
|
}
|
|
|
|
UpdateOverlappingBodyPairs(MirrorBodyIndex);
|
|
}
|
|
}
|
|
|
|
// Mirror collision interactions - Do this after all mirrored bodies have been created as there may be collision interactions between the new bodies.
|
|
{
|
|
FString MirrorCollisionsMissingBones;
|
|
FString MirrorCollisionsMissingBodies;
|
|
uint32 MissingBodyCount = 0;
|
|
uint32 MissingBoneCount = 0;
|
|
|
|
for (FMirrorInfo& MirrorInfo : MirrorInfos)
|
|
{
|
|
const int32 SourceBoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(MirrorInfo.BoneName);
|
|
const int32 MirrorBoneIndex = PhysicsAsset->FindMirroredBone(EditorSkelMesh, SourceBoneIndex);
|
|
|
|
if (MirrorBoneIndex != INDEX_NONE)
|
|
{
|
|
const int32 SourceBodyIndex = MirrorInfo.BodyIndex;
|
|
|
|
for(FName SourceCollidingBoneName : MirrorInfo.CollidingBodyBoneNames)
|
|
{
|
|
const int32 SourceCollidingBoneIndex = EditorSkelMesh->GetRefSkeleton().FindBoneIndex(SourceCollidingBoneName); // Find Index of the bone associated with the body that the source body was allowed to collide with.
|
|
|
|
int32 MirrorCollidingBoneIndex = INDEX_NONE;
|
|
if (EditorSkelMesh->GetRefSkeleton().IsValidIndex(SourceCollidingBoneIndex))
|
|
{
|
|
MirrorCollidingBoneIndex = PhysicsAsset->FindMirroredBone(EditorSkelMesh, SourceCollidingBoneIndex); // Find the index of the bone that mirrors the colliding body's bone.
|
|
}
|
|
|
|
FName MirrorCollidingBoneName = NAME_None;
|
|
if (EditorSkelMesh->GetRefSkeleton().IsValidIndex(MirrorCollidingBoneIndex))
|
|
{
|
|
MirrorCollidingBoneName = EditorSkelMesh->GetRefSkeleton().GetBoneName(MirrorCollidingBoneIndex); // Find the name of the bone that mirrors the colliding body's bone.
|
|
}
|
|
|
|
const int32 MirrorCollidingBodyIndex = PhysicsAsset->FindBodyIndex(MirrorCollidingBoneName); // Find the index of the colliding body.;
|
|
|
|
if (MirrorCollidingBodyIndex != INDEX_NONE)
|
|
{
|
|
const FName MirrorBoneName = EditorSkelMesh->GetRefSkeleton().GetBoneName(MirrorBoneIndex);
|
|
const int32 MirrorBodyIndex = PhysicsAsset->FindBodyIndex(MirrorBoneName);
|
|
|
|
PhysicsAsset->EnableCollision(MirrorCollidingBodyIndex, MirrorBodyIndex); // Enable collisions with the body associated with that bone.
|
|
}
|
|
else // Error reporting
|
|
{
|
|
if (MirrorCollidingBoneIndex != INDEX_NONE) // Found the mirrored bone but failed to find an associated physics body
|
|
{
|
|
MirrorCollisionsMissingBodies += MirrorCollidingBoneName.ToString() + "\n";
|
|
++MissingBodyCount;
|
|
}
|
|
else // Failed to find the mirrored bone.
|
|
{
|
|
MirrorCollisionsMissingBones += SourceCollidingBoneName.ToString() + "\n";
|
|
++MissingBoneCount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Display an error notification if necessary.
|
|
if (!(MirrorCollisionsMissingBones.IsEmpty() && MirrorCollisionsMissingBodies.IsEmpty()))
|
|
{
|
|
// Construct error message for failed collision mirroring.
|
|
FText MissingMirrorBodiesErrorText;
|
|
FText MissingMirrorBonesErrorText;
|
|
|
|
if (MissingBodyCount > 0)
|
|
{
|
|
MissingMirrorBodiesErrorText = FText::Format(LOCTEXT("MissingMirrorBody", "Missing {0}|plural(one=body,other=bodies) for {0}|plural(one=bone,other=bones):\n{1}"), MissingBodyCount, FText::FromString(MirrorCollisionsMissingBodies));
|
|
}
|
|
|
|
if (MissingBoneCount > 0)
|
|
{
|
|
MissingMirrorBonesErrorText = FText::Format(LOCTEXT("MissingMirrorBone", "Missing {0}|plural(one=mirror,other=mirrors) for {0}|plural(one=bone,other=bones):\n{1}Note: Mirroring is based entirely on bone name matching."), MissingBoneCount, FText::FromString(MirrorCollisionsMissingBones));
|
|
}
|
|
|
|
const FText ErrorMsg = FText::Format(LOCTEXT("FailedToMirrorCollisions", "Failed to mirror all collisions\n{0}{1}"), MissingMirrorBodiesErrorText, MissingMirrorBonesErrorText);
|
|
|
|
// Display notification.
|
|
FNotificationInfo Info(ErrorMsg);
|
|
Info.ExpireDuration = 4.0f;
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if (Notification)
|
|
{
|
|
Notification->SetCompletionState(SNotificationItem::CS_Fail);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EPhysicsAssetEditorMeshViewMode FPhysicsAssetEditorSharedData::GetCurrentMeshViewMode(bool bSimulation)
|
|
{
|
|
if (bSimulation)
|
|
{
|
|
return EditorOptions->SimulationMeshViewMode;
|
|
}
|
|
else
|
|
{
|
|
return EditorOptions->MeshViewMode;
|
|
}
|
|
}
|
|
|
|
EPhysicsAssetEditorCenterOfMassViewMode FPhysicsAssetEditorSharedData::GetCurrentCenterOfMassViewMode(const bool bSimulation) const
|
|
{
|
|
if (bSimulation)
|
|
{
|
|
return EditorOptions->SimulationCenterOfMassViewMode;
|
|
}
|
|
else
|
|
{
|
|
return EditorOptions->CenterOfMassViewMode;
|
|
}
|
|
}
|
|
|
|
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 = MakePrimitiveSelection(BodyIndex, PrimType, PrimIndex);
|
|
|
|
if(bGroupSelect)
|
|
{
|
|
if(IsBodySelected(Selection))
|
|
{
|
|
ModifySelectedPrimitives(Selection, false);
|
|
}
|
|
else
|
|
{
|
|
ModifySelectedPrimitives(Selection, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetSelectedPrimitives(Selection);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::HitCoM(const int32 BodyIndex, const bool bGroupSelect)
|
|
{
|
|
if (!bRunningSimulation)
|
|
{
|
|
const FPhysicsAssetEditorSharedData::FSelection Selection = MakeCoMSelection(BodyIndex);
|
|
|
|
if (bGroupSelect)
|
|
{
|
|
if (IsCoMSelected(BodyIndex))
|
|
{
|
|
ModifySelectedCoMs(Selection, false);
|
|
}
|
|
else
|
|
{
|
|
ModifySelectedCoMs(Selection, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetSelectedCoMs(Selection);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::HitConstraint(int32 ConstraintIndex, bool bGroupSelect)
|
|
{
|
|
if (!bRunningSimulation)
|
|
{
|
|
if(bGroupSelect)
|
|
{
|
|
if(IsConstraintSelected(ConstraintIndex))
|
|
{
|
|
ModifySelectedConstraints(ConstraintIndex, false);
|
|
}
|
|
else
|
|
{
|
|
ModifySelectedConstraints(ConstraintIndex, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ClearSelectedConstraints();
|
|
ModifySelectedConstraints(ConstraintIndex, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::RefreshPhysicsAssetChange(const UPhysicsAsset* InPhysAsset, bool bFullClothRefresh)
|
|
{
|
|
if (InPhysAsset)
|
|
{
|
|
InPhysAsset->RefreshPhysicsAssetChange();
|
|
|
|
// Broadcast delegate
|
|
FPhysicsDelegates::OnPhysicsAssetChanged.Broadcast(InPhysAsset);
|
|
|
|
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
|
|
// since we recreate physics state, 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();
|
|
|
|
for (int32 BodyIndex = 0, BodyCount = EditorSkelComp->Bodies.Num(); BodyIndex < BodyCount; ++BodyIndex)
|
|
{
|
|
EditorSkelComp->Bodies[BodyIndex]->BodySetup = InPhysAsset->SkeletalBodySetups[BodyIndex];
|
|
}
|
|
|
|
if(bFullClothRefresh)
|
|
{
|
|
EditorSkelComp->RecreateClothingActors();
|
|
}
|
|
else
|
|
{
|
|
UpdateClothPhysics();
|
|
}
|
|
EnableSimulation(false);
|
|
|
|
InitializeOverlappingBodyPairs();
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedBodiesAllPrimitive(const TArray<int32>& BodiesIndices, bool bSelected)
|
|
{
|
|
SetSelectedBodiesPrimitives(BodiesIndices, bSelected, [](const TArray<FSelection>& CurrentSelection, const int32 BodyIndex, const FKShapeElem& Primitive)
|
|
{
|
|
// Select all primitives
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedBodiesPrimitivesWithCollisionType(const TArray<int32>& BodiesIndices, const ECollisionEnabled::Type CollisionType, bool bSelected)
|
|
{
|
|
SetSelectedBodiesPrimitives(BodiesIndices, bSelected, [CollisionType](const TArray<FSelection>& CurrentSelection, const int32 BodyIndex, const FKShapeElem& Primitive)
|
|
{
|
|
// Select primitives which match the collision type
|
|
return Primitive.GetCollisionEnabled() == CollisionType;
|
|
});
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedBodiesPrimitives(const TArray<int32>& BodiesIndices, bool bSelected, const TFunction<bool(const TArray<FSelection>&, const int32 BodyIndex, const FKShapeElem&)>& Predicate)
|
|
{
|
|
if (BodiesIndices.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (BodiesIndices.Num() == 1 && BodiesIndices[0] == INDEX_NONE)
|
|
{
|
|
ClearSelectedBody();
|
|
return;
|
|
}
|
|
|
|
ModifySelectedBodies(CreateBodyPrimitivesSelection(PhysicsAsset, BodiesIndices, Predicate), bSelected);
|
|
}
|
|
|
|
FPhysicsAssetEditorSharedData::SelectionFilterRange FPhysicsAssetEditorSharedData::SelectedBodies() const
|
|
{
|
|
return SelectedObjects->SelectedElementsOfType(FSelection::Body);
|
|
}
|
|
|
|
FPhysicsAssetEditorSharedData::SelectionFilterRange FPhysicsAssetEditorSharedData::SelectedCoMs() const
|
|
{
|
|
return SelectedObjects->SelectedElementsOfType(FSelection::CenterOfMass);
|
|
}
|
|
|
|
FPhysicsAssetEditorSharedData::SelectionFilterRange FPhysicsAssetEditorSharedData::SelectedConstraints() const
|
|
{
|
|
return SelectedObjects->SelectedElementsOfType(FSelection::Constraint);
|
|
}
|
|
|
|
FPhysicsAssetEditorSharedData::SelectionFilterRange FPhysicsAssetEditorSharedData::SelectedPrimitives() const
|
|
{
|
|
return SelectedObjects->SelectedElementsOfType(FSelection::Primitive);
|
|
}
|
|
|
|
FPhysicsAssetEditorSharedData::SelectionFilterRange FPhysicsAssetEditorSharedData::SelectedBodiesAndPrimitives() const
|
|
{
|
|
return SelectedObjects->SelectedElementsOfType(FSelection::Body | FSelection::Primitive);
|
|
}
|
|
|
|
|
|
|
|
FPhysicsAssetEditorSharedData::SelectionUniqueRange FPhysicsAssetEditorSharedData::UniqueSelectionReferencingBodies() const
|
|
{
|
|
return SelectedObjects->UniqueSelectedElementsOfType(FSelection::Body | FSelection::Primitive);
|
|
}
|
|
|
|
const UPhysicsAssetEditorSelection* FPhysicsAssetEditorSharedData::GetSelectedObjects() const
|
|
{
|
|
return SelectedObjects;
|
|
}
|
|
|
|
const FPhysicsAssetEditorSharedData::FSelection* FPhysicsAssetEditorSharedData::GetSelectedBodyOrPrimitive() const
|
|
{
|
|
return SelectedObjects->GetLastSelectedOfType(FSelection::Body | FSelection::Primitive);
|
|
}
|
|
|
|
const FPhysicsAssetEditorSharedData::FSelection* FPhysicsAssetEditorSharedData::GetSelectedBody() const
|
|
{
|
|
return SelectedObjects->GetLastSelectedOfType(FSelection::Body);
|
|
}
|
|
|
|
const FPhysicsAssetEditorSharedData::FSelection* FPhysicsAssetEditorSharedData::GetSelectedCoM() const
|
|
{
|
|
return SelectedObjects->GetLastSelectedOfType(FSelection::CenterOfMass);
|
|
}
|
|
|
|
const FPhysicsAssetEditorSharedData::FSelection* FPhysicsAssetEditorSharedData::GetSelectedConstraint() const
|
|
{
|
|
return SelectedObjects->GetLastSelectedOfType(FSelection::Constraint);
|
|
}
|
|
|
|
const FPhysicsAssetEditorSharedData::FSelection* FPhysicsAssetEditorSharedData::GetSelectedPrimitive() const
|
|
{
|
|
return SelectedObjects->GetLastSelectedOfType(FSelection::Primitive);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetGroupSelectionActive(const bool bIsActive)
|
|
{
|
|
bIsGroupSelectionActive = bIsActive;
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::IsGroupSelectionActive() const
|
|
{
|
|
return bIsGroupSelectionActive;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelected(const TArray<FSelection>& InSelectedElements, const bool bSelected)
|
|
{
|
|
ModifySelectionInternal([this, InSelectedElements, bSelected]() -> bool { return SelectedObjects->ModifySelected(InSelectedElements, bSelected); });
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelected(const TArray<FSelection>& InSelectedElements)
|
|
{
|
|
ModifySelectionInternal([this, InSelectedElements]() -> bool { return SelectedObjects->SetSelected(InSelectedElements); });
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::IsSelected(const FSelection& InSelection) const
|
|
{
|
|
return SelectedObjects->SelectedElements().Contains(InSelection);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ClearSelected()
|
|
{
|
|
SelectedObjects->ClearSelection();
|
|
|
|
BroadcastSelectionChanged();
|
|
UpdateNoCollisionBodies();
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ClearSelectedPrimitives()
|
|
{
|
|
if (InsideSelChange)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ClearSelected();
|
|
|
|
++InsideSelChange;
|
|
BroadcastPreviewChanged();
|
|
--InsideSelChange;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedPrimitives(const FSelection& InSelectedElement, const bool bSelected)
|
|
{
|
|
ModifySelected(TArray<FSelection>{ InSelectedElement }, bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedPrimitives(const TArray<FSelection>& InSelectedElements, const bool bSelected)
|
|
{
|
|
ModifySelected(InSelectedElements, bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedPrimitives(const FSelection& InSelectedElement)
|
|
{
|
|
SetSelected(TArray<FSelection>{ InSelectedElement });
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedPrimitives(const TArray<FSelection>& InSelectedElements)
|
|
{
|
|
SetSelected(InSelectedElements); // TODO - should only clear and set selected primitives, not bodies etc
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ClearSelectedCoMs()
|
|
{
|
|
if (InsideSelChange)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ClearSelected();
|
|
|
|
++InsideSelChange;
|
|
BroadcastPreviewChanged();
|
|
--InsideSelChange;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedCoMs(const FSelection& InSelectedElement, const bool bSelected)
|
|
{
|
|
ModifySelected(TArray<FSelection>{ InSelectedElement }, bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedCoMs(const TArray<FSelection>& InSelectedElements, const bool bSelected)
|
|
{
|
|
ModifySelected(InSelectedElements, bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedCoMs(const FSelection& InSelectedElement)
|
|
{
|
|
SetSelected(TArray<FSelection>{ InSelectedElement });
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedCoMs(const TArray<FSelection>& InSelectedElements)
|
|
{
|
|
SetSelected(InSelectedElements);
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::IsCoMSelected(const int32 BodyIndex) const
|
|
{
|
|
return SelectionContainsIndex(SelectedCoMs(), BodyIndex);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ClearSelectedBody()
|
|
{
|
|
ClearSelected();
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedBodies(const FSelection& Body, bool bSelected)
|
|
{
|
|
ModifySelected(TArray<FSelection>{ Body }, bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedBodies(const TArray<FSelection>& InSelectedElements, bool bSelected)
|
|
{
|
|
ModifySelected(InSelectedElements, bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedBodies(const FSelection& InSelectedElement)
|
|
{
|
|
SetSelected(TArray<FSelection>{ InSelectedElement });
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedBodies(const TArray<FSelection>& InSelectedElements)
|
|
{
|
|
SetSelected(InSelectedElements);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedBodies(const int32 BodyIndex, const bool bSelected)
|
|
{
|
|
ModifySelected(TArray<FSelection>{ MakeBodySelection(PhysicsAsset, BodyIndex) }, bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedBodies(const TArray<int32>& BodiesIndices, const bool bSelected)
|
|
{
|
|
ModifySelected(MakeBodySelection(PhysicsAsset, BodiesIndices), bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedBodies(const int32 BodyIndex)
|
|
{
|
|
SetSelected(TArray<FSelection>{ MakeBodySelection(PhysicsAsset, BodyIndex) });
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedBodies(const TArray<int32>& BodiesIndices)
|
|
{
|
|
SetSelected(MakeBodySelection(PhysicsAsset, BodiesIndices));
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::IsBodySelected(const FSelection& Body) const
|
|
{
|
|
return Body.HasType(FPhysicsAssetEditorSelectedElement::Body) && SelectedObjects->SelectedElements().Contains(Body); // TODO - should this be implemented with the following fn ?
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::IsBodySelected(const int32 BodyIndex) const
|
|
{
|
|
return Algo::FindByPredicate(SelectedObjects->SelectedElements(), [BodyIndex](const FSelection& Element)
|
|
{
|
|
return Element.HasType(FPhysicsAssetEditorSelectedElement::Body | FPhysicsAssetEditorSelectedElement::Primitive) && (Element.Index == BodyIndex);
|
|
}) != nullptr;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ToggleSelectionType(bool bIgnoreUserConstraints)
|
|
{
|
|
TSet<int32> NewSelectedBodies;
|
|
for (const FSelection& Selection : SelectedConstraints())
|
|
{
|
|
const UPhysicsConstraintTemplate* const ConstraintTemplate = PhysicsAsset->ConstraintSetup[Selection.Index];
|
|
|
|
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; // Use a set here because we could have multiple shapes selected which would cause us to add and remove the same constraint.
|
|
for (const FSelection& Selection : UniqueSelectionReferencingBodies())
|
|
{
|
|
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();
|
|
|
|
SetSelectedBodiesAllPrimitive(NewSelectedBodies.Array(), true);
|
|
ModifySelectedConstraints(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 : UniqueSelectionReferencingBodies())
|
|
{
|
|
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 : UniqueSelectionReferencingBodies())
|
|
{
|
|
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 (!SelectionContainsIndex(SelectedConstraints(), 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 (!IsBodySelected(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 : UniqueSelectionReferencingBodies())
|
|
{
|
|
ShowBody(Selection.Index);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::HideSelected()
|
|
{
|
|
for (const FSelection& Selection : SelectedConstraints())
|
|
{
|
|
HideConstraint(Selection.Index);
|
|
}
|
|
for (const FSelection& Selection : UniqueSelectionReferencingBodies())
|
|
{
|
|
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 && (UniqueSelectionReferencingBodies().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& Selection : UniqueSelectionReferencingBodies())
|
|
{
|
|
ShowBody(Selection.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 : UniqueSelectionReferencingBodies())
|
|
{
|
|
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();
|
|
|
|
int32 SelectedBodyIndex = INDEX_NONE;
|
|
|
|
if (const FPhysicsAssetEditorSharedData::FSelection* const SelectedBodyOrPrimitive = GetSelectedBodyOrPrimitive())
|
|
{
|
|
SelectedBodyIndex = SelectedBodyOrPrimitive->Index;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
if ((SelectedBodyIndex == INDEX_NONE) || (PhysicsAsset->SkeletalBodySetups[i]->DefaultInstance.GetCollisionEnabled() == ECollisionEnabled::NoCollision))
|
|
{
|
|
// Add all bodies if non are selected.
|
|
// Add any bodies with bNoCollision
|
|
NoCollisionBodies.Add(i);
|
|
}
|
|
else if (i != SelectedBodyIndex)
|
|
{
|
|
if (!ensure(PhysicsAsset->SkeletalBodySetups[SelectedBodyIndex]))
|
|
{
|
|
continue;
|
|
}
|
|
// Add this body if it has disabled collision with selected.
|
|
FRigidBodyIndexPair Key(i, SelectedBodyIndex);
|
|
|
|
if (PhysicsAsset->SkeletalBodySetups[SelectedBodyIndex]->DefaultInstance.GetCollisionEnabled() == ECollisionEnabled::NoCollision ||
|
|
PhysicsAsset->CollisionDisableTable.Find(Key))
|
|
{
|
|
NoCollisionBodies.Add(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ClearSelectedConstraints()
|
|
{
|
|
if(InsideSelChange)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ClearSelected();
|
|
|
|
++InsideSelChange;
|
|
BroadcastPreviewChanged();
|
|
--InsideSelChange;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedConstraints(const int32 ConstraintIndex, const bool bSelected)
|
|
{
|
|
ModifySelectedConstraints(TArray<int32>{ ConstraintIndex }, bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ModifySelectedConstraints(const TArray<int32>& ConstraintsIndices, const bool bSelected)
|
|
{
|
|
ModifySelected(MakeConstraintSelection(ConstraintsIndices), bSelected);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetSelectedConstraints(const TArray<int32>& ConstraintsIndices)
|
|
{
|
|
SetSelected(MakeConstraintSelection(ConstraintsIndices));
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::IsConstraintSelected(const int32 ConstraintIndex) const
|
|
{
|
|
return SelectedObjects->SelectedElements().Contains(MakeConstraintSelection(ConstraintIndex));
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetCollisionBetweenSelected(const bool bEnableCollision)
|
|
{
|
|
if (bRunningSimulation || UniqueSelectionReferencingBodies().IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
PhysicsAsset->Modify();
|
|
|
|
ForEachUniquePair(UniqueSelectionReferencingBodies(), [PhysicsAsset = PhysicsAsset, bEnableCollision](const FSelection& Lhs, const FSelection& Rhs)
|
|
{
|
|
if (bEnableCollision)
|
|
{
|
|
PhysicsAsset->EnableCollision(Lhs.Index, Rhs.Index);
|
|
}
|
|
else
|
|
{
|
|
PhysicsAsset->DisableCollision(Lhs.Index, Rhs.Index);
|
|
}
|
|
});
|
|
|
|
UpdateNoCollisionBodies();
|
|
RefreshPhysicsAssetChange(PhysicsAsset);
|
|
InitializeOverlappingBodyPairs();
|
|
BroadcastPreviewChanged();
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::CanSetCollisionBetweenSelected(bool bEnableCollision) const
|
|
{
|
|
if (bRunningSimulation || UniqueSelectionReferencingBodies().IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bResult = false;
|
|
|
|
ForEachUniquePair(UniqueSelectionReferencingBodies(), [PhysicsAsset = PhysicsAsset, bEnableCollision, &bResult](const FSelection& Lhs, const FSelection& Rhs)
|
|
{
|
|
if (PhysicsAsset->IsCollisionEnabled(Lhs.Index, Rhs.Index) != bEnableCollision)
|
|
{
|
|
bResult = true;
|
|
}
|
|
});
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetCollisionBetweenSelectedAndAll(bool bEnableCollision)
|
|
{
|
|
FPhysicsAssetEditorSharedData::SelectionUniqueRange SelectedRange = UniqueSelectionReferencingBodies();
|
|
|
|
if (bRunningSimulation || SelectedRange.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
PhysicsAsset->Modify();
|
|
|
|
for(const FSelection& Selection : SelectedRange)
|
|
{
|
|
for(int32 j = 0; j < PhysicsAsset->SkeletalBodySetups.Num(); ++j)
|
|
{
|
|
if(bEnableCollision)
|
|
{
|
|
PhysicsAsset->EnableCollision(Selection.Index, j);
|
|
}
|
|
else
|
|
{
|
|
PhysicsAsset->DisableCollision(Selection.Index, j);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
UpdateNoCollisionBodies();
|
|
RefreshPhysicsAssetChange(PhysicsAsset);
|
|
InitializeOverlappingBodyPairs();
|
|
BroadcastPreviewChanged();
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::CanSetCollisionBetweenSelectedAndAll(bool bEnableCollision) const
|
|
{
|
|
if (!bRunningSimulation)
|
|
{
|
|
for (const FSelection& SelectedBody : UniqueSelectionReferencingBodies())
|
|
{
|
|
for (int32 j = 0; j < PhysicsAsset->SkeletalBodySetups.Num(); ++j)
|
|
{
|
|
if (PhysicsAsset->IsCollisionEnabled(SelectedBody.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();
|
|
RefreshPhysicsAssetChange(PhysicsAsset);
|
|
UpdateOverlappingBodyPairs(Body1Index);
|
|
UpdateOverlappingBodyPairs(Body2Index);
|
|
}
|
|
|
|
BroadcastPreviewChanged();
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetPrimitiveCollision(ECollisionEnabled::Type CollisionEnabled)
|
|
{
|
|
if (bRunningSimulation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PhysicsAsset->Modify();
|
|
|
|
for (const FSelection& SelectedBody : UniqueSelectionReferencingBodies())
|
|
{
|
|
PhysicsAsset->SetPrimitiveCollision(SelectedBody.GetIndex(), SelectedBody.GetPrimitiveType(), SelectedBody.GetPrimitiveIndex(), CollisionEnabled);
|
|
}
|
|
|
|
BroadcastPreviewChanged();
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::CanSetPrimitiveCollision(ECollisionEnabled::Type CollisionEnabled) const
|
|
{
|
|
if (bRunningSimulation || UniqueSelectionReferencingBodies().IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::GetIsPrimitiveCollisionEnabled(ECollisionEnabled::Type CollisionEnabled) const
|
|
{
|
|
for (const FSelection& Selection : SelectedPrimitives())
|
|
{
|
|
if (PhysicsAsset->GetPrimitiveCollision(Selection.GetIndex(), Selection.GetPrimitiveType(), Selection.GetPrimitiveIndex()) == CollisionEnabled)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SetPrimitiveContributeToMass(bool bContributeToMass)
|
|
{
|
|
for (const FSelection& Selection : SelectedPrimitives())
|
|
{
|
|
PhysicsAsset->SetPrimitiveContributeToMass(Selection.Index, Selection.GetPrimitiveType(), Selection.PrimitiveIndex, bContributeToMass);
|
|
}
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::CanSetPrimitiveContributeToMass() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::GetPrimitiveContributeToMass() const
|
|
{
|
|
for (const FSelection& Selection : SelectedPrimitives())
|
|
{
|
|
if (PhysicsAsset->GetPrimitiveContributeToMass(Selection.Index, Selection.GetPrimitiveType(), Selection.PrimitiveIndex))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
case EPhysAssetFitGeomType::EFG_LevelSet: return EAggCollisionShape::Type::LevelSet;
|
|
case EPhysAssetFitGeomType::EFG_SkinnedLevelSet: return EAggCollisionShape::Type::SkinnedLevelSet;
|
|
case EPhysAssetFitGeomType::EFG_MLLevelSet: return EAggCollisionShape::Type::MLLevelSet;
|
|
case EPhysAssetFitGeomType::EFG_SkinnedTriangleMesh:return EAggCollisionShape::Type::SkinnedTriangleMesh;
|
|
default: return EAggCollisionShape::Type::Unknown;
|
|
}
|
|
}
|
|
|
|
EPhysAssetFitGeomType ConvertAggCollisionShapeTypeToPhysicsAssetGeomType(const EAggCollisionShape::Type AggCollisionShapeType)
|
|
{
|
|
switch (AggCollisionShapeType)
|
|
{
|
|
case EAggCollisionShape::Type::Box: return EPhysAssetFitGeomType::EFG_Box;
|
|
case EAggCollisionShape::Type::Sphyl: return EPhysAssetFitGeomType::EFG_Sphyl;
|
|
case EAggCollisionShape::Type::Sphere: return EPhysAssetFitGeomType::EFG_Sphere;
|
|
case EAggCollisionShape::Type::TaperedCapsule: return EPhysAssetFitGeomType::EFG_TaperedCapsule;
|
|
case EAggCollisionShape::Type::Convex: return EPhysAssetFitGeomType::EFG_SingleConvexHull;
|
|
case EAggCollisionShape::Type::LevelSet: return EPhysAssetFitGeomType::EFG_LevelSet;
|
|
case EAggCollisionShape::Type::SkinnedLevelSet: return EPhysAssetFitGeomType::EFG_SkinnedLevelSet;
|
|
case EAggCollisionShape::Type::MLLevelSet: return EPhysAssetFitGeomType::EFG_MLLevelSet;
|
|
case EAggCollisionShape::Type::SkinnedTriangleMesh: return EPhysAssetFitGeomType::EFG_SkinnedTriangleMesh;
|
|
default: return EPhysAssetFitGeomType(INDEX_NONE);
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
case EAggCollisionShape::LevelSet:
|
|
PrimitiveCount = BodySetup->AggGeom.LevelSetElems.Num();
|
|
break;
|
|
case EAggCollisionShape::SkinnedLevelSet:
|
|
PrimitiveCount = BodySetup->AggGeom.SkinnedLevelSetElems.Num();
|
|
break;
|
|
case EAggCollisionShape::MLLevelSet:
|
|
PrimitiveCount = BodySetup->AggGeom.MLLevelSetElems.Num();
|
|
break;
|
|
case EAggCollisionShape::SkinnedTriangleMesh:
|
|
PrimitiveCount = BodySetup->AggGeom.SkinnedTriangleMeshElems.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])
|
|
{
|
|
auto SetElementName = [BodySetup, PrimitiveType, &PrimitiveIndex](auto& GeometryCollection, const TCHAR* NamePostfixText)
|
|
{
|
|
if (PrimitiveIndex == INDEX_NONE)
|
|
{
|
|
PrimitiveIndex = GeometryCollection.Num() - 1;
|
|
}
|
|
if (GeometryCollection.IsValidIndex(PrimitiveIndex))
|
|
{
|
|
FName PrimitiveName(FString::Printf(TEXT("%s_%s"), *BodySetup->BoneName.ToString(), NamePostfixText));
|
|
GeometryCollection[PrimitiveIndex].SetName(PrimitiveName);
|
|
}
|
|
};
|
|
|
|
if (PrimitiveType == EAggCollisionShape::Sphere)
|
|
{
|
|
SetElementName(BodySetup->AggGeom.SphereElems, TEXT("sphere"));
|
|
}
|
|
else if (PrimitiveType == EAggCollisionShape::Box)
|
|
{
|
|
SetElementName(BodySetup->AggGeom.BoxElems, TEXT("box"));
|
|
}
|
|
else if (PrimitiveType == EAggCollisionShape::Sphyl)
|
|
{
|
|
SetElementName(BodySetup->AggGeom.SphylElems, TEXT("capsule"));
|
|
}
|
|
else if (PrimitiveType == EAggCollisionShape::Convex)
|
|
{
|
|
SetElementName(BodySetup->AggGeom.ConvexElems, TEXT("convex"));
|
|
}
|
|
else if (PrimitiveType == EAggCollisionShape::TaperedCapsule)
|
|
{
|
|
SetElementName(BodySetup->AggGeom.TaperedCapsuleElems, TEXT("tapered_capsule"));
|
|
}
|
|
else if (PrimitiveType == EAggCollisionShape::LevelSet)
|
|
{
|
|
SetElementName(BodySetup->AggGeom.LevelSetElems, TEXT("level_set"));
|
|
}
|
|
else if (PrimitiveType == EAggCollisionShape::SkinnedLevelSet)
|
|
{
|
|
SetElementName(BodySetup->AggGeom.SkinnedLevelSetElems, TEXT("skinned_level_set"));
|
|
}
|
|
else if (PrimitiveType == EAggCollisionShape::MLLevelSet)
|
|
{
|
|
SetElementName(BodySetup->AggGeom.MLLevelSetElems, TEXT("ml_level_set"));
|
|
}
|
|
else if (PrimitiveType == EAggCollisionShape::SkinnedTriangleMesh)
|
|
{
|
|
SetElementName(BodySetup->AggGeom.SkinnedTriangleMeshElems, TEXT("skinned_triangle_mesh"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::CopySelectedBodiesAndConstraintsToClipboard(int32& OutNumCopiedBodies, int32& OutNumCopiedConstraints, int32& OutNumCopiedDisabledCollisionPairs)
|
|
{
|
|
OutNumCopiedBodies = 0;
|
|
OutNumCopiedConstraints = 0;
|
|
OutNumCopiedDisabledCollisionPairs = 0;
|
|
|
|
if (PhysicsAsset)
|
|
{
|
|
// Clear the mark state for saving.
|
|
UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp));
|
|
|
|
FStringOutputDevice Archive;
|
|
const FExportObjectInnerContext Context;
|
|
|
|
TSet<int32> ExportedBodyIndices;
|
|
|
|
// export bodies first
|
|
{
|
|
OutNumCopiedBodies = 0;
|
|
|
|
// Export each of the selected nodes
|
|
for (const FSelection& Selection : UniqueSelectionReferencingBodies())
|
|
{
|
|
// 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(Selection.Index))
|
|
{
|
|
ExportedBodyIndices.Add(Selection.Index);
|
|
|
|
if (USkeletalBodySetup* BodySetup = PhysicsAsset->SkeletalBodySetups[Selection.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 a body 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// export collision relationships
|
|
{
|
|
auto ExportCollisionRelationships = [this, &Context, &Archive, &OutNumCopiedDisabledCollisionPairs](const int32 BodyIndexA, const int32 BodyIndexB)
|
|
{
|
|
if (!PhysicsAsset->IsCollisionEnabled(BodyIndexA, BodyIndexB))
|
|
{
|
|
const USkeletalBodySetup* const BodySetupA = PhysicsAsset->SkeletalBodySetups[BodyIndexA];
|
|
const USkeletalBodySetup* const BodySetupB = PhysicsAsset->SkeletalBodySetups[BodyIndexB];
|
|
|
|
check(BodySetupA);
|
|
check(BodySetupB);
|
|
|
|
if (BodySetupA && BodySetupB)
|
|
{
|
|
UPhysicsAssetCollisionPair* const CollisionPair = NewObject<UPhysicsAssetCollisionPair>();
|
|
|
|
CollisionPair->Set(BodySetupA->BoneName, BodySetupB->BoneName);
|
|
UExporter::ExportToOutputDevice(&Context, CollisionPair, NULL, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false);
|
|
++OutNumCopiedDisabledCollisionPairs;
|
|
}
|
|
}
|
|
};
|
|
|
|
ForEachUniquePair(ExportedBodyIndices, ExportCollisionRelationships);
|
|
}
|
|
|
|
// 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>() || InObjectClass->IsChildOf<UPhysicsAssetCollisionPair>());
|
|
}
|
|
|
|
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));
|
|
}
|
|
else if (NewObject->IsA<UPhysicsAssetCollisionPair>())
|
|
{
|
|
NewDisabledCollisionPairs.Add(Cast<UPhysicsAssetCollisionPair>(NewObject));
|
|
}
|
|
}
|
|
|
|
public:
|
|
TArray<USkeletalBodySetup*> NewBodySetups;
|
|
TArray<UPhysicsConstraintTemplate*> NewConstraintTemplates;
|
|
TArray<UPhysicsAssetCollisionPair*> NewDisabledCollisionPairs;
|
|
};
|
|
|
|
bool FPhysicsAssetEditorSharedData::CanPasteBodiesAndConstraintsFromClipboard() const
|
|
{
|
|
FString TextToImport;
|
|
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
|
|
FSkeletalBodyAndConstraintSetupObjectTextFactory Factory;
|
|
return Factory.CanCreateObjectsFromText(TextToImport);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::PasteBodiesAndConstraintsFromClipboard(int32& OutNumPastedBodies, int32& OutNumPastedConstraints, int32& OutNumPastedDisabledCollisionPairs)
|
|
{
|
|
OutNumPastedBodies = 0;
|
|
OutNumPastedConstraints = 0;
|
|
OutNumPastedDisabledCollisionPairs = 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();
|
|
{
|
|
TArray<int32> PastedBodyIndices;
|
|
|
|
// 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 || Factory.NewDisabledCollisionPairs.Num() > 0)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("PhysicsAssetEditor", "PasteBodiesAndConstraintsFromClipboard", "Paste Bodies, Constraints And Disabled Collision Pairs From Clipboard"));
|
|
|
|
PhysicsAsset->Modify();
|
|
|
|
// let's first process the bodies
|
|
OutNumPastedBodies = 0;
|
|
for (USkeletalBodySetup* PastedBodySetup : Factory.NewBodySetups)
|
|
{
|
|
// does this 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;
|
|
|
|
PastedBodyIndices.Add(BodyIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enable collisions between all pasted bodies.
|
|
ForEachUniquePair(PastedBodyIndices, [this](const int32 BodyIndexA, const int32 BodyIndexB)
|
|
{
|
|
PhysicsAsset->EnableCollision(BodyIndexA, BodyIndexB);
|
|
});
|
|
|
|
// Disable collisions between pasted bodies as specified by pasted disabled collision pairs.
|
|
for (UPhysicsAssetCollisionPair* const CollisionPair : Factory.NewDisabledCollisionPairs)
|
|
{
|
|
const int32 BodyIndexA = PhysicsAsset->FindBodyIndex(CollisionPair->BoneNameA);
|
|
const int32 BodyIndexB = PhysicsAsset->FindBodyIndex(CollisionPair->BoneNameB);
|
|
|
|
if ((BodyIndexA != INDEX_NONE) && (BodyIndexB != INDEX_NONE))
|
|
{
|
|
PhysicsAsset->DisableCollision(BodyIndexA, BodyIndexB);
|
|
++OutNumPastedDisabledCollisionPairs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 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::CopySelectedShapesToClipboard(int32& OutNumCopiedShapes, int32& OutNumBodiesCopiedFrom)
|
|
{
|
|
OutNumCopiedShapes = 0;
|
|
OutNumBodiesCopiedFrom = 0;
|
|
if (PhysicsAsset)
|
|
{
|
|
// Clear the mark state for saving.
|
|
UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp));
|
|
|
|
// Make a temp bodysetup to house all the selected shapes
|
|
USkeletalBodySetup* NewBodySetup = NewObject<USkeletalBodySetup>();
|
|
NewBodySetup->AddToRoot();
|
|
{
|
|
TSet<int32> SelectedBodyIndices;
|
|
for (const FSelection& Selection : SelectedPrimitives())
|
|
{
|
|
if (const USkeletalBodySetup* OldBodySetup = PhysicsAsset->SkeletalBodySetups[Selection.Index])
|
|
{
|
|
if (NewBodySetup->AddCollisionElemFrom(OldBodySetup->AggGeom, Selection.GetPrimitiveType(), Selection.GetPrimitiveIndex()))
|
|
{
|
|
SelectedBodyIndices.Add(Selection.Index);
|
|
++OutNumCopiedShapes;
|
|
}
|
|
}
|
|
}
|
|
OutNumBodiesCopiedFrom = SelectedBodyIndices.Num();
|
|
}
|
|
|
|
// Export the new bodysetup to the clipboard as text
|
|
if (OutNumCopiedShapes > 0)
|
|
{
|
|
FStringOutputDevice Archive;
|
|
const FExportObjectInnerContext Context;
|
|
UExporter::ExportToOutputDevice(&Context, NewBodySetup, NULL, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false);
|
|
FString ExportedText = Archive;
|
|
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
|
|
}
|
|
|
|
// Allow the temp bodysetup to get deleted by garbage collection
|
|
NewBodySetup->RemoveFromRoot();
|
|
}
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::CanPasteShapesFromClipboard() const
|
|
{
|
|
FString TextToImport;
|
|
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
|
|
FBodySetupObjectTextFactory Factory;
|
|
return Factory.CanCreateObjectsFromText(TextToImport);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::PasteShapesFromClipboard(int32& OutNumPastedShapes, int32& OutNumBodiesPastedInto)
|
|
{
|
|
OutNumPastedShapes = 0;
|
|
OutNumBodiesPastedInto = 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
|
|
FBodySetupObjectTextFactory Factory;
|
|
Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport);
|
|
|
|
// Paste copied shapes into each of the selected bodies
|
|
if (Factory.NewBodySetups.Num() > 0 && !UniqueSelectionReferencingBodies().IsEmpty())
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("PhysicsAssetEditor", "PasteShapesFromClipboard", "Paste Shapes From Clipboard"));
|
|
|
|
// We have to track which bodies we've pasted into, because they might appear multiple times
|
|
// (for separate primitive shapes) in the SelectedObjects->SelectedBodies() list.
|
|
TSet<int32> PastedBodyIndices;
|
|
for (const UBodySetup* NewBodySetup : Factory.NewBodySetups)
|
|
{
|
|
OutNumPastedShapes += NewBodySetup->AggGeom.GetElementCount();
|
|
for (const FSelection& SelectedBody : UniqueSelectionReferencingBodies())
|
|
{
|
|
if (!PastedBodyIndices.Contains(SelectedBody.Index))
|
|
{
|
|
PastedBodyIndices.Add(SelectedBody.Index);
|
|
if (USkeletalBodySetup* TargetBodySetup = PhysicsAsset->SkeletalBodySetups[SelectedBody.Index])
|
|
{
|
|
TargetBodySetup->Modify();
|
|
TargetBodySetup->AddCollisionFrom(NewBodySetup->AggGeom);
|
|
++OutNumBodiesPastedInto;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the temp package from the root now that it has served its purpose
|
|
TempPackage->RemoveFromRoot();
|
|
RefreshPhysicsAssetChange(PhysicsAsset);
|
|
BroadcastPreviewChanged();
|
|
BroadcastHierarchyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::CopyBodyProperties()
|
|
{
|
|
check(UniqueSelectionReferencingBodies().Num() == 1);
|
|
CopyToClipboard(SharedDataConstants::BodyType, PhysicsAsset->SkeletalBodySetups[GetSelectedBodyOrPrimitive()->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(!UniqueSelectionReferencingBodies().IsEmpty())
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("PhysicsAssetEditor", "PasteBodyProperties", "Paste Body Properties") );
|
|
|
|
PhysicsAsset->Modify();
|
|
|
|
for (const FSelection& Selection : UniqueSelectionReferencingBodies())
|
|
{
|
|
UBodySetup* const ToBodySetup = PhysicsAsset->SkeletalBodySetups[Selection.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();
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::CopyBodyName()
|
|
{
|
|
check(UniqueSelectionReferencingBodies().Num() == 1);
|
|
FPlatformApplicationMisc::ClipboardCopy(*PhysicsAsset->SkeletalBodySetups[GetSelectedBodyOrPrimitive()->Index]->BoneName.ToString());
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::WeldSelectedBodies(bool bWeld /* = true */)
|
|
{
|
|
bool bCanWeld = false;
|
|
if (bRunningSimulation)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(UniqueSelectionReferencingBodies().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(int SelectedIndex = 0, SelectedCount = SelectedObjects->Num(); SelectedIndex < SelectedCount; ++SelectedIndex)
|
|
{
|
|
const FSelection& SelectedElement = SelectedObjects->GetSelectedAt(SelectedIndex);
|
|
|
|
if (SelectedElement.HasType(FPhysicsAssetEditorSelectedElement::Body))
|
|
{
|
|
if (SelectedObjects->GetSelectedAt(BodyIndex0).Index == SelectedElement.Index)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (BodyIndex1 == INDEX_NONE)
|
|
{
|
|
BodyIndex1 = SelectedIndex;
|
|
}
|
|
else
|
|
{
|
|
if (SelectedObjects->GetSelectedAt(BodyIndex1).Index != SelectedElement.Index)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//need to weld bodies not primitives
|
|
if(BodyIndex1 == INDEX_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
check(SelectedObjects->IsValidIndex(BodyIndex0));
|
|
check(SelectedObjects->IsValidIndex(BodyIndex1));
|
|
|
|
const FSelection& Body0 = SelectedObjects->GetSelectedAt(BodyIndex0);
|
|
const FSelection& Body1 = SelectedObjects->GetSelectedAt(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.GetIndex())
|
|
{
|
|
ParentBodyIndex = Body0.GetIndex();
|
|
ParentBoneName = Bone0Name;
|
|
ChildBodyIndex = Body1.GetIndex();
|
|
ParentPrimitiveType = Body0.GetPrimitiveType();
|
|
ChildPrimitiveType = Body1.GetPrimitiveType();
|
|
ParentPrimitiveIndex = Body0.GetPrimitiveIndex();
|
|
//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.GetPrimitiveIndex() + PhysicsAsset->SkeletalBodySetups[Body0.Index]->AggGeom.GetElementCount(ChildPrimitiveType);
|
|
|
|
bCanWeld = true;
|
|
}else if(PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, Bone0ParentIndex) == Body1.GetIndex())
|
|
{
|
|
ParentBodyIndex = Body1.GetIndex();
|
|
ParentBoneName = Bone1Name;
|
|
ChildBodyIndex = Body0.GetIndex();
|
|
ParentPrimitiveType = Body1.GetPrimitiveType();
|
|
ChildPrimitiveType = Body0.GetPrimitiveType();
|
|
ParentPrimitiveIndex = Body1.GetPrimitiveIndex();
|
|
//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.GetPrimitiveIndex() + PhysicsAsset->SkeletalBodySetups[Body1.GetIndex()]->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();
|
|
|
|
const int32 BodyIndex = PhysicsAsset->FindBodyIndex(ParentBoneName);
|
|
|
|
// Previous selection is invalid because child no longer has same index. Just to be safe - deselect any selected bodies or constraints
|
|
SetSelectedPrimitives({ MakePrimitiveSelection(BodyIndex, ParentPrimitiveType, ParentPrimitiveIndex), MakePrimitiveSelection(BodyIndex, ChildPrimitiveType, ChildPrimitiveIndex) }); // This redraws the viewport as well...
|
|
|
|
RefreshPhysicsAssetChange(PhysicsAsset);
|
|
return true;
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::ModifySelectionInternal(TFunctionRef<bool(void)> SelectionOperation)
|
|
{
|
|
if (!InsideSelChange && SelectionOperation())
|
|
{
|
|
BroadcastSelectionChanged();
|
|
UpdateNoCollisionBodies();
|
|
|
|
++InsideSelChange;
|
|
BroadcastPreviewChanged();
|
|
--InsideSelChange;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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::RecreateBody(const int32 NewBoneIndex, const bool bAutoSelect)
|
|
{
|
|
RecreateBody(GetDefault<UPhysicsAssetGenerationSettings>()->CreateParams, NewBoneIndex, bAutoSelect);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::RecreateBody(const FPhysAssetCreateParams& BodyData, const int32 BoneIndex, const bool bAutoSelect)
|
|
{
|
|
USkeletalMesh* EditorSkelMesh = PhysicsAsset->GetPreviewMesh();
|
|
if (EditorSkelMesh == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PhysicsAsset->Modify();
|
|
|
|
const FName BoneName = EditorSkelMesh->GetRefSkeleton().GetBoneName(BoneIndex);
|
|
const int32 BodyIndex = PhysicsAsset->FindBodyIndex(BoneName);
|
|
|
|
check(BodyIndex != INDEX_NONE);
|
|
if (BodyIndex != INDEX_NONE)
|
|
{
|
|
FPhysicsAssetUtils::RecreateBody(PhysicsAsset, BoneName, BodyData, BodyIndex); // Create a new physics body setup at the same index as the original body setup.
|
|
|
|
BroadcastHierarchyChanged();
|
|
|
|
if (bAutoSelect)
|
|
{
|
|
ModifySelectedBodies(BodyIndex, true);
|
|
}
|
|
|
|
RefreshPhysicsAssetChange(PhysicsAsset);
|
|
}
|
|
|
|
RefreshPhysicsAssetChange(PhysicsAsset);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::MakeNewBody(int32 NewBoneIndex, bool bAutoSelect)
|
|
{
|
|
MakeNewBody(GetDefault<UPhysicsAssetGenerationSettings>()->CreateParams, NewBoneIndex, bAutoSelect);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::MakeNewBody(const FPhysAssetCreateParams& NewBodyData, const int32 NewBoneIndex, const 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);
|
|
|
|
// 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);
|
|
|
|
const bool bCreateConstraints = NewBodyData.bCreateConstraints && FPhysicsAssetUtils::CanCreateConstraints();
|
|
|
|
// 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)
|
|
{
|
|
if (bCreateConstraints)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (PhysicsAsset->ConstraintSetup.IsValidIndex(ConstraintIndex))
|
|
{
|
|
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 && bCreateConstraints)
|
|
{
|
|
const int32 NewConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(PhysicsAsset, NewBoneName);
|
|
UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[ NewConstraintIndex ];
|
|
check(ConstraintSetup);
|
|
|
|
InitConstraintSetup(ConstraintSetup, NewBodyIndex, ParentBodyIndex);
|
|
}
|
|
|
|
// update the tree
|
|
BroadcastHierarchyChanged();
|
|
|
|
if (bAutoSelect)
|
|
{
|
|
ModifySelectedBodies(NewBodyIndex, true);
|
|
}
|
|
|
|
|
|
RefreshPhysicsAssetChange(PhysicsAsset);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::MakeOrRecreateBody(const int32 NewBoneIndex, const bool bAutoSelect)
|
|
{
|
|
if (const USkeletalMesh* const EditorSkelMesh = PhysicsAsset->GetPreviewMesh())
|
|
{
|
|
const FName NewBoneName = EditorSkelMesh->GetRefSkeleton().GetBoneName(NewBoneIndex);
|
|
|
|
if (PhysicsAsset->FindBodyIndex(NewBoneName) != INDEX_NONE)
|
|
{
|
|
RecreateBody(NewBoneIndex, bAutoSelect); // Create a new body at the same index as the one being replaced. This ensures that all references to this body via index remain valid.
|
|
}
|
|
else
|
|
{
|
|
MakeNewBody(NewBoneIndex, bAutoSelect); // Create a new body.
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (ensure(FPhysicsAssetUtils::CanCreateConstraints()))
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
SetSelectedConstraints(NewlyCreatedConstraints);
|
|
|
|
// 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
|
|
const int32 BoneIndex = EditorSkelComp->GetBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone1);
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
const FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex);
|
|
ConstraintSetup->DefaultInstance.SetRefFrame(EConstraintFrame::Frame1, WNewChildFrame.GetRelativeTransform(BoneTM));
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SnapConstraintToBone(const int32 ConstraintIndex, const EConstraintTransformComponentFlags ComponentFlags /* = EConstraintTransformComponentFlags::All */)
|
|
{
|
|
UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[ConstraintIndex];
|
|
ConstraintSetup->Modify();
|
|
SnapConstraintToBone(ConstraintSetup->DefaultInstance, ComponentFlags);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::SnapConstraintToBone(FConstraintInstance& ConstraintInstance, const EConstraintTransformComponentFlags ComponentFlags /* = EConstraintTransformComponentFlags::All */)
|
|
{
|
|
ConstraintInstance.SnapTransformsToDefault(ComponentFlags, PhysicsAsset);
|
|
}
|
|
|
|
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* const FromConstraintSetup = Cast<UPhysicsConstraintTemplate>(SourceConstraint);
|
|
|
|
FPhysicsAssetEditorSharedData::SelectionFilterRange SelectedConstraintRange = SelectedConstraints();
|
|
|
|
if(FromConstraintSetup && !SelectedConstraintRange.IsEmpty())
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("PhysicsAssetEditor", "PasteConstraintProperties", "Paste Constraint Properties"));
|
|
|
|
for (const FSelection& SelectedConstraint : SelectedConstraintRange)
|
|
{
|
|
UPhysicsConstraintTemplate* const ToConstraintSetup = PhysicsAsset->ConstraintSetup[SelectedConstraint.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 (const FSelection& SelectedConstraint : SelectedConstraints())
|
|
{
|
|
UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[SelectedConstraint.Index];
|
|
ConstraintTemplate->Modify();
|
|
FMatrix ConstraintTransform = ConstraintTemplate->DefaultInstance.GetRefFrame(EConstraintFrame::Frame2).ToMatrixWithScale();
|
|
FTransform WParentFrame = GetConstraintWorldTM(&SelectedConstraint, EConstraintFrame::Frame2);
|
|
FTransform WChildFrame = GetConstraintWorldTM(&SelectedConstraint, 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 (const FSelection& SelectedConstraint : SelectedConstraints())
|
|
{
|
|
UPhysicsConstraintTemplate* const ConstraintTemplate = PhysicsAsset->ConstraintSetup[SelectedConstraint.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 (const FSelection& SelectedConstraint : SelectedConstraints())
|
|
{
|
|
UPhysicsConstraintTemplate* const ConstraintTemplate = PhysicsAsset->ConstraintSetup[SelectedConstraint.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 (const FSelection& SelectedConstraint : SelectedConstraints())
|
|
{
|
|
const UPhysicsConstraintTemplate* const ConstraintTemplate = PhysicsAsset->ConstraintSetup[SelectedConstraint.Index];
|
|
const 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);
|
|
if (ensure(PhysicsAsset->ConstraintSetup.IsValidIndex(NewConstraintIndex)))
|
|
{
|
|
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::DeleteCurrentSelection()
|
|
{
|
|
DeleteCurrentBody();
|
|
DeleteCurrentPrim();
|
|
DeleteCurrentConstraint();
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::DeleteCurrentBody()
|
|
{
|
|
// Delete any directly selected bodies and all their primitives.
|
|
TArray<FSelection> DirectSelectedBodies = SelectedBodies().ToArray();
|
|
|
|
if (!DirectSelectedBodies.IsEmpty())
|
|
{
|
|
// Remove target body indexes from the selection.
|
|
ModifySelectedBodies(DirectSelectedBodies, false);
|
|
|
|
// Sort by body index - highest first - as body indexes greater than the deleted index in the physics asset will be modified by each deletion.
|
|
Algo::SortBy(DirectSelectedBodies, &FSelection::Index, TGreater<>());
|
|
|
|
// Delete target bodies.
|
|
for (const FSelection& Selection : DirectSelectedBodies)
|
|
{
|
|
DeleteBody(Selection.Index, false);
|
|
}
|
|
|
|
RefreshPhysicsAssetChange(PhysicsAsset);
|
|
BroadcastHierarchyChanged();
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::DeleteCurrentPrim()
|
|
{
|
|
if (bRunningSimulation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!GetSelectedBodyOrPrimitive())
|
|
{
|
|
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 (const FSelection& SelectedPrimitive : SelectedPrimitives())
|
|
{
|
|
UBodySetup* const BodySetup = PhysicsAsset->SkeletalBodySetups[SelectedPrimitive.Index];
|
|
BodySelectionMap.FindOrAdd(BodySetup).Add(SelectedPrimitive);
|
|
}
|
|
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "DeletePrimitive", "Delete Primitive") );
|
|
|
|
for (TMap<UBodySetup*, TArray<FSelection> >::TIterator It(BodySelectionMap); It; ++It)
|
|
{
|
|
UBodySetup * BodySetup = It.Key();
|
|
TArray<FSelection>& SelectedPrimitives = It.Value();
|
|
|
|
// Sort selected primitives by primitive index to ensure we update element indexes correctly as we modify the geometry arrays.
|
|
SelectedPrimitives.Sort([](const FSelection& LhsElement, const FSelection& RhsElement) -> bool { return LhsElement.GetPrimitiveIndex() < RhsElement.GetPrimitiveIndex(); });
|
|
|
|
int32 SphereDeletedCount = 0;
|
|
int32 BoxDeletedCount = 0;
|
|
int32 SphylDeletedCount = 0;
|
|
int32 ConvexDeletedCount = 0;
|
|
int32 TaperedCapsuleDeletedCount = 0;
|
|
int32 LevelSetDeletedCount = 0;
|
|
int32 SkinnedLevelSetDeletedCount = 0;
|
|
int32 MLLevelSetDeletedCount = 0;
|
|
int32 SkinnedTriangleMeshDeletedCount = 0;
|
|
|
|
for (int32 i = 0; i < SelectedPrimitives.Num(); ++i)
|
|
{
|
|
const FSelection& SelectedBody = SelectedPrimitives[i];
|
|
int32 BodyIndex = PhysicsAsset->FindBodyIndex(BodySetup->BoneName);
|
|
|
|
BodySetup->Modify();
|
|
|
|
if (SelectedBody.GetPrimitiveType() == EAggCollisionShape::Sphere)
|
|
{
|
|
BodySetup->AggGeom.SphereElems.RemoveAt(SelectedBody.PrimitiveIndex - (SphereDeletedCount++));
|
|
}
|
|
else if (SelectedBody.GetPrimitiveType() == EAggCollisionShape::Box)
|
|
{
|
|
BodySetup->AggGeom.BoxElems.RemoveAt(SelectedBody.PrimitiveIndex - (BoxDeletedCount++));
|
|
}
|
|
else if (SelectedBody.GetPrimitiveType() == EAggCollisionShape::Sphyl)
|
|
{
|
|
BodySetup->AggGeom.SphylElems.RemoveAt(SelectedBody.PrimitiveIndex - (SphylDeletedCount++));
|
|
}
|
|
else if (SelectedBody.GetPrimitiveType() == 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.GetPrimitiveType() == EAggCollisionShape::TaperedCapsule)
|
|
{
|
|
BodySetup->AggGeom.TaperedCapsuleElems.RemoveAt(SelectedBody.PrimitiveIndex - (TaperedCapsuleDeletedCount++));
|
|
}
|
|
else if (SelectedBody.GetPrimitiveType() == EAggCollisionShape::LevelSet)
|
|
{
|
|
BodySetup->AggGeom.LevelSetElems.RemoveAt(SelectedBody.PrimitiveIndex - (LevelSetDeletedCount++));
|
|
}
|
|
else if (SelectedBody.GetPrimitiveType() == EAggCollisionShape::SkinnedLevelSet)
|
|
{
|
|
BodySetup->AggGeom.SkinnedLevelSetElems.RemoveAt(SelectedBody.PrimitiveIndex - (SkinnedLevelSetDeletedCount++));
|
|
}
|
|
else if (SelectedBody.GetPrimitiveType() == EAggCollisionShape::MLLevelSet)
|
|
{
|
|
BodySetup->AggGeom.MLLevelSetElems.RemoveAt(SelectedBody.PrimitiveIndex - (MLLevelSetDeletedCount++));
|
|
}
|
|
else if (SelectedBody.GetPrimitiveType() == EAggCollisionShape::SkinnedTriangleMesh)
|
|
{
|
|
BodySetup->AggGeom.SkinnedTriangleMeshElems.RemoveAt(SelectedBody.PrimitiveIndex - (SkinnedTriangleMeshDeletedCount++));
|
|
}
|
|
|
|
// 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 != nullptr) && (EditorSkelComp != nullptr))
|
|
{
|
|
const FName BoneName = (Frame == EConstraintFrame::Frame1) ? ConstraintSetup->DefaultInstance.ConstraintBone1 : ConstraintSetup->DefaultInstance.ConstraintBone2;
|
|
const int32 BoneIndex = EditorSkelComp->GetBoneIndex(BoneName);
|
|
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex);
|
|
BoneTM.RemoveScaling();
|
|
return BoneTM;
|
|
}
|
|
}
|
|
|
|
return FTransform::Identity; // If we couldn't find the bone - fall back to identity.
|
|
}
|
|
|
|
FTransform FPhysicsAssetEditorSharedData::GetConstraintWorldTM(const UPhysicsConstraintTemplate* const ConstraintSetup, const EConstraintFrame::Type Frame, const float Scale) const
|
|
{
|
|
if ((ConstraintSetup != nullptr) && (EditorSkelComp != nullptr))
|
|
{
|
|
const FName BoneName = (Frame == EConstraintFrame::Frame1) ? ConstraintSetup->DefaultInstance.ConstraintBone1 : ConstraintSetup->DefaultInstance.ConstraintBone2;
|
|
const int32 BoneIndex = EditorSkelComp->GetBoneIndex(BoneName);
|
|
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
FTransform LFrame = ConstraintSetup->DefaultInstance.GetRefFrame(Frame);
|
|
LFrame.ScaleTranslation(FVector(Scale));
|
|
const FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex);
|
|
return LFrame * BoneTM;
|
|
}
|
|
}
|
|
|
|
return FTransform::Identity;
|
|
}
|
|
|
|
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 (const FSelection& SelectedConstraint : SelectedConstraints())
|
|
{
|
|
ConditionalClearClipboard(SharedDataConstants::ConstraintType, PhysicsAsset->ConstraintSetup[SelectedConstraint.Index]);
|
|
Indices.Add(SelectedConstraint.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 || IsManipulating())
|
|
{
|
|
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->GetSkeletalMeshAsset() && PreviewAnimationAsset->GetSkeleton() == EditorSkelComp->GetSkeletalMeshAsset()->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 simulation 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);
|
|
}
|
|
|
|
BroadcastHierarchyChanged();
|
|
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()
|
|
{
|
|
// The selection can become invalid if the creation of an object that is selected is undone etc - try to detect that here and clear selection if it is the case.
|
|
|
|
bool bInvalidSelection = false;
|
|
|
|
for(UPhysicsAssetEditorSelection::Iterator Itr = SelectedBodiesAndPrimitives().CreateConstIterator(); Itr && !bInvalidSelection; ++Itr)
|
|
{
|
|
const FSelection& Selection = *Itr;
|
|
|
|
if (PhysicsAsset->SkeletalBodySetups.Num() <= Selection.GetIndex())
|
|
{
|
|
bInvalidSelection = true;
|
|
}
|
|
else
|
|
{
|
|
if (UBodySetup * BodySetup = PhysicsAsset->SkeletalBodySetups[Selection.GetIndex()])
|
|
{
|
|
switch (Selection.GetPrimitiveType())
|
|
{
|
|
case EAggCollisionShape::Box: bInvalidSelection = BodySetup->AggGeom.BoxElems.Num() <= Selection.GetPrimitiveIndex() ? true : bInvalidSelection; break;
|
|
case EAggCollisionShape::Convex: bInvalidSelection = BodySetup->AggGeom.ConvexElems.Num() <= Selection.GetPrimitiveIndex() ? true : bInvalidSelection; break;
|
|
case EAggCollisionShape::Sphere: bInvalidSelection = BodySetup->AggGeom.SphereElems.Num() <= Selection.GetPrimitiveIndex() ? true : bInvalidSelection; break;
|
|
case EAggCollisionShape::Sphyl: bInvalidSelection = BodySetup->AggGeom.SphylElems.Num() <= Selection.GetPrimitiveIndex() ? true : bInvalidSelection; break;
|
|
case EAggCollisionShape::TaperedCapsule: bInvalidSelection = BodySetup->AggGeom.TaperedCapsuleElems.Num() <= Selection.GetPrimitiveIndex() ? true : bInvalidSelection; break;
|
|
default: bInvalidSelection = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bInvalidSelection = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(UPhysicsAssetEditorSelection::Iterator Itr = SelectedConstraints().CreateConstIterator(); Itr && (bInvalidSelection == false); ++Itr)
|
|
{
|
|
const FSelection& Selection = *Itr;
|
|
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.
|
|
SelectedObjects->ClearSelectionWithoutTransaction(FSelection::Body | FSelection::Constraint);
|
|
}
|
|
|
|
BroadcastPreviewChanged();
|
|
BroadcastHierarchyChanged();
|
|
BroadcastSelectionChanged();
|
|
InitializeOverlappingBodyPairs();
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::Redo()
|
|
{
|
|
if (bRunningSimulation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ClearSelectedBody();
|
|
ClearSelectedConstraints();
|
|
|
|
GEditor->RedoTransaction();
|
|
PhysicsAsset->UpdateBodySetupIndexMap();
|
|
|
|
BroadcastPreviewChanged();
|
|
BroadcastHierarchyChanged();
|
|
BroadcastSelectionChanged();
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::AddReferencedObjects(FReferenceCollector& Collector)
|
|
{
|
|
Collector.AddReferencedObject(PhysicsAsset);
|
|
Collector.AddReferencedObject(EditorSkelComp);
|
|
Collector.AddReferencedObject(PhysicalAnimationComponent);
|
|
Collector.AddReferencedObject(EditorOptions);
|
|
Collector.AddReferencedObject(MouseHandle);
|
|
Collector.AddReferencedObject(SelectedObjects);
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
FVector FPhysicsAssetEditorSharedData::GetSelectedCoMPosition()
|
|
{
|
|
if (const FSelection* const SelectedCoM = GetSelectedCoM())
|
|
{
|
|
if (const FVector* const ManipulatedBodyCoMPosition = FindManipulatedBodyCoMPosition(SelectedCoM->Index))
|
|
{
|
|
// return the CoM position from the FSelection object because the physics body's CoM position will only be updated at the end of manipulation.
|
|
return *ManipulatedBodyCoMPosition;
|
|
}
|
|
else
|
|
{
|
|
return EditorSkelComp->Bodies[SelectedCoM->Index]->GetCOMPosition();
|
|
}
|
|
}
|
|
|
|
return FVector::ZeroVector;
|
|
}
|
|
|
|
|
|
FPhysicsAssetRenderSettings* FPhysicsAssetEditorSharedData::GetRenderSettings() const
|
|
{
|
|
return UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::BeginManipulation()
|
|
{
|
|
RecordSelectedCoM();
|
|
|
|
bManipulating = true;
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::EndManipulation()
|
|
{
|
|
bManipulating = false;
|
|
bShouldUpdatedSelectedCoMs = true;
|
|
|
|
RefreshPhysicsAssetChange(PhysicsAsset, false);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::FindOverlappingBodyPairs(const int32 InBodyIndex, TArray<TPair<int32, int32>>& OutCollidingBodyPairs)
|
|
{
|
|
if (PhysicsAsset->SkeletalBodySetups.IsValidIndex(InBodyIndex) && (PhysicsAsset->SkeletalBodySetups[InBodyIndex]->DefaultInstance.GetCollisionEnabled() != ECollisionEnabled::NoCollision))
|
|
{
|
|
auto CreateCollisionPair = [](const int32 IndexA, const int32 IndexB)
|
|
{
|
|
return (IndexA < IndexB) ? TPair<int32, int32>(IndexA, IndexB) : TPair<int32, int32>(IndexB, IndexA);
|
|
};
|
|
|
|
for (int32 BodyIndex = 0, BodyCount = PhysicsAsset->SkeletalBodySetups.Num(); BodyIndex < BodyCount; ++BodyIndex)
|
|
{
|
|
if (BodyIndex != InBodyIndex)
|
|
{
|
|
if (IsBodyPairCollisionEnabled(PhysicsAsset, InBodyIndex, BodyIndex) && DoBodiesOverlap(PhysicsAsset->SkeletalBodySetups[InBodyIndex], PhysicsAsset->SkeletalBodySetups[BodyIndex], PhysicsAsset, EditorSkelComp))
|
|
{
|
|
OutCollidingBodyPairs.AddUnique(CreateCollisionPair(BodyIndex, InBodyIndex));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::RemoveOverlappingBodyPairs(const int32 InBodyIndex, TArray<TPair<int32, int32>>& OutCollidingBodyPairs)
|
|
{
|
|
auto PairContainsIndex = [InBodyIndex](const TPair<int32, int32>& Element)
|
|
{
|
|
return (Element.Key == InBodyIndex) || (Element.Value == InBodyIndex);
|
|
};
|
|
|
|
OutCollidingBodyPairs.RemoveAll(PairContainsIndex);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::InitializeOverlappingBodyPairs()
|
|
{
|
|
OverlappingCollidingBodyPairs.Reset();
|
|
|
|
for (int32 BodyIndex = 0, BodyCount = PhysicsAsset->SkeletalBodySetups.Num(); BodyIndex < BodyCount; ++BodyIndex)
|
|
{
|
|
FindOverlappingBodyPairs(BodyIndex, OverlappingCollidingBodyPairs);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::UpdateOverlappingBodyPairs(const int32 InBodyIndex)
|
|
{
|
|
RemoveOverlappingBodyPairs(InBodyIndex, OverlappingCollidingBodyPairs);
|
|
FindOverlappingBodyPairs(InBodyIndex, OverlappingCollidingBodyPairs);
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::IsBodyOverlapping(const int32 InBodyIndex) const
|
|
{
|
|
auto PairContainsIndex = [InBodyIndex](const TPair<int32, int32>& Element) // TODO - avoid duplication
|
|
{
|
|
return (Element.Key == InBodyIndex) || (Element.Value == InBodyIndex);
|
|
};
|
|
|
|
return Algo::FindByPredicate(OverlappingCollidingBodyPairs, PairContainsIndex) != nullptr;
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::ShouldShowBodyOverlappingHighlight(const int32 InBodyIndex) const
|
|
{
|
|
return IsHighlightingOverlapingBodies() && IsBodyOverlapping(InBodyIndex);
|
|
}
|
|
|
|
void FPhysicsAssetEditorSharedData::ToggleHighlightOverlapingBodies()
|
|
{
|
|
EditorOptions->bHighlightOverlapingBodies = ~EditorOptions->bHighlightOverlapingBodies;
|
|
EditorOptions->SaveConfig();
|
|
}
|
|
|
|
bool FPhysicsAssetEditorSharedData::IsHighlightingOverlapingBodies() const
|
|
{
|
|
return EditorOptions->bHighlightOverlapingBodies;
|
|
}
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|