Files
UnrealEngineUWP/Engine/Source/Editor/PhAT/Private/PhATSharedData.cpp
PaulEremeeff 3d878d5a79 PR #996: Fixing PVS-Studio warnings (Contributed by PaulEremeeff)
I have reviewed each change carefully, but it is a large change and I could have missed something! Here is a summary of the types of changes in this CL:
 * Made nullptr checks consistent (the plurality of the changes are of this type)
 * Completed switch statements (IE, switch did not explicitly handle default case, but had unhandled enum entries - this is the second most popular type of fix)
 * Removed unused variables
 * Removed redundant initializations
 * WidgetNavigationCustomization.cpp was fixed by the owner
 * integers converted to floats where result was stored in a float
 * Removed redundent null checks (e.g. before delete statements)
 * Renamed variables to prevent non-obvious shadowing
 * Fixed use of bitwise & when checking for equality to an enum entry (which is often 0)
 * Fixes for some copy paste errors (e.g. FoliageEdMode.cpp)

[CL 2498053 by Dan Oconnor in Main branch]
2015-03-31 20:12:31 -04:00

1594 lines
50 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "PhATModule.h"
#include "PhysicsPublic.h"
#include "EditorSupportDelegates.h"
#include "ScopedTransaction.h"
#include "SPhATNewAssetDlg.h"
#include "PhATEdSkeletalMeshComponent.h"
#include "PhATSharedData.h"
#include "Developer/MeshUtilities/Public/MeshUtilities.h"
#include "PhysicsEngine/BodySetup.h"
#include "Engine/CollisionProfile.h"
#include "PhysicsEngine/PhysicsConstraintTemplate.h"
#include "PhysicsEngine/PhysicsHandleComponent.h"
#include "Vehicles/WheeledVehicleMovementComponent.h"
#include "Engine/StaticMesh.h"
#define LOCTEXT_NAMESPACE "PhATShared"
FPhATSharedData::FPhATSharedData()
: PreviewScene( FPreviewScene::ConstructionValues().ShouldSimulatePhysics(true) )
, COMRenderColor(255,255,100)
, CopiedBodySetup(NULL)
, CopiedConstraintTemplate(NULL)
, bInsideSelChange(false)
{
// Editor variables
BodyEdit_MeshViewMode = PRM_Solid;
BodyEdit_CollisionViewMode = PRM_Wireframe;
BodyEdit_ConstraintViewMode = PCV_AllPositions;
ConstraintEdit_MeshViewMode = PRM_None;
ConstraintEdit_CollisionViewMode = PRM_Wireframe;
ConstraintEdit_ConstraintViewMode = PCV_AllPositions;
Sim_MeshViewMode = PRM_Solid;
Sim_CollisionViewMode = PRM_Wireframe;
Sim_ConstraintViewMode = PCV_None;
EditingMode = PEM_BodyEdit;
bShowCOM = false;
bShowHierarchy = false;
bShowInfluences = false;
bDrawGround = true;
bShowFixedStatus = false;
bShowAnimSkel = false;
bSelectionLock = false;
bRunningSimulation = false;
bNoGravitySimulation = false;
bShowInstanceProps = false;
bManipulating = false;
// Construct mouse handle
MouseHandle = NewObject<UPhysicsHandleComponent>();
// Construct sim options.
EditorSimOptions = NewObject<UPhATSimOptions>(GetTransientPackage(), TEXT("EditorSimOptions"));
check(EditorSimOptions);
EditorSimOptions->HandleLinearDamping = MouseHandle->LinearDamping;
EditorSimOptions->HandleLinearStiffness = MouseHandle->LinearStiffness;
EditorSimOptions->HandleAngularDamping = MouseHandle->AngularDamping;
EditorSimOptions->HandleAngularStiffness = MouseHandle->AngularStiffness;
EditorSimOptions->InterpolationSpeed = MouseHandle->InterpolationSpeed;
// when record simulation, we'd like to record local to world change
Recorder.bRecordLocalToWorld = true;
}
FPhATSharedData::~FPhATSharedData()
{
}
void FPhATSharedData::Initialize()
{
EditorSkelComp = NULL;
USkeletalMesh * PreviewMesh = NULL;
FStringAssetReference PreviewMeshStringRef = PhysicsAsset->PreviewSkeletalMesh.ToStringReference();
// load it since now is the time to load
if (!PreviewMeshStringRef.ToString().IsEmpty())
{
PreviewMesh = Cast<USkeletalMesh>(StaticLoadObject(USkeletalMesh::StaticClass(), NULL, *PreviewMeshStringRef.ToString(), NULL, LOAD_None, NULL));
}
if ( PreviewMesh == NULL)
{
// 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);
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(
NSLOCTEXT("UnrealEd", "Error_PhysicsAssetHasNoSkelMesh",
"Warning: Physics Asset has no skeletal mesh assigned! For now, a simple default skeletal mesh ({0}) will be used. You can fix this by opening PhAT, selecting the appropriate skeletal mesh in the content browser, and using (Asset -> Change Mesh) before saving this asset."),
FText::FromString(PreviewMesh->GetFullName())));
}
EditorSkelMesh = PreviewMesh;
// Create SkeletalMeshComponent for rendering skeletal mesh
EditorSkelComp = NewObject<UPhATEdSkeletalMeshComponent>();
EditorSkelComp->SharedData = this;
// first disable collision first to avoid creating physics body
EditorSkelComp->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
EditorSkelComp->SetAnimationMode(EAnimationMode::Type::AnimationSingleNode);
// Create floor component
UStaticMesh* FloorMesh = LoadObject<UStaticMesh>(NULL, TEXT("/Engine/EditorMeshes/PhAT_FloorBox.PhAT_FloorBox"), NULL, LOAD_None, NULL);
check(FloorMesh);
EditorFloorComp = NewObject<UStaticMeshComponent>();
EditorFloorComp->StaticMesh = FloorMesh;
EditorFloorComp->SetRelativeScale3D(FVector(4.f));
PreviewScene.AddComponent(EditorSkelComp, FTransform::Identity);
PreviewScene.AddComponent(EditorFloorComp, FTransform::Identity);
// 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->BodySetup.Num(); ++i)
{
UBodySetup* BodySetup = PhysicsAsset->BodySetup[i];
if (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.
MeshUtilities.CalcBoneVertInfos(EditorSkelMesh, DominantWeightBoneInfos, true);
MeshUtilities.CalcBoneVertInfos(EditorSkelMesh, AnyWeightBoneInfos, false);
EditorSkelComp->SetSkeletalMesh(EditorSkelMesh);
EditorSkelComp->SetPhysicsAsset(PhysicsAsset);
// 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->BodySetup.Num(); ++i)
{
FName BoneName = PhysicsAsset->BodySetup[i]->BoneName;
int32 BoneIndex = EditorSkelMesh->RefSkeleton.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->BodySetup is a TArray and UE4 containers don't support remove_if()
for ( int32 i = MissingBodyIndices.Num() - 1; i >= 0; --i )
{
DeleteBody( MissingBodyIndices[i] );
}
}
}
// Register handle component
MouseHandle->RegisterComponentWithWorld(PreviewScene.GetWorld());
// Support undo/redo
PhysicsAsset->SetFlags(RF_Transactional);
EditorSkelComp->Stop();
SetSelectedBody(NULL);
SetSelectedConstraint(INDEX_NONE);
ResetTM = EditorSkelComp->GetComponentToWorld();
EnableSimulation(false);
}
void FPhATSharedData::CopyConstraintProperties(UPhysicsConstraintTemplate * FromConstraintSetup, UPhysicsConstraintTemplate * ToConstraintSetup)
{
//We want to copy frame2 relative to frame1. To do this we need to go from body2 into body1, compute relative.
//Then apply relative inside body1 of the destination constraint, and finally move it into body2 of destination constraint
//In total there are 4 bodies, body a and b are the frame1 frame2 bodies of the copied
// body c and d are frame 1 and frame 2 of the destination constraint
//we use the body letter to denote which space we're in, for example Frame1a would be frame1 inside BodyA
//Frame1b would be frame 1 inside body b. In this case BodyA * Frame1a == BodyB * Frame1b
FTransform Frame1A = FromConstraintSetup->DefaultInstance.GetRefFrame(EConstraintFrame::Frame1);
FTransform BodyA = GetConstraintBodyTM(FromConstraintSetup, EConstraintFrame::Frame1);
FTransform BodyB = GetConstraintBodyTM(FromConstraintSetup, EConstraintFrame::Frame2);
FTransform BodyAFrame1A = GetConstraintWorldTM(FromConstraintSetup, EConstraintFrame::Frame1);
FTransform BodyBFrame2B = GetConstraintWorldTM(FromConstraintSetup, EConstraintFrame::Frame2);
FTransform Frame1C = ToConstraintSetup->DefaultInstance.GetRefFrame(EConstraintFrame::Frame1);
FTransform BodyC = GetConstraintBodyTM(ToConstraintSetup, EConstraintFrame::Frame1);
FTransform BodyD = GetConstraintBodyTM(ToConstraintSetup, EConstraintFrame::Frame2);
FTransform FromF1AToF2A = BodyBFrame2B * BodyA.Inverse() * Frame1A.Inverse();
FTransform Frame2C = FromF1AToF2A * Frame1C;
FTransform Frame2D = Frame2C* BodyC * BodyD.Inverse();
ToConstraintSetup->Modify();
FConstraintInstance OldInstance = ToConstraintSetup->DefaultInstance;
ToConstraintSetup->DefaultInstance.CopyConstraintParamsFrom(&FromConstraintSetup->DefaultInstance);
// recover certain data that we'd like to keep - i.e. bone indices
// those still should stay
ToConstraintSetup->DefaultInstance.ConstraintIndex = OldInstance.ConstraintIndex;
ToConstraintSetup->DefaultInstance.ConstraintData = OldInstance.ConstraintData;
ToConstraintSetup->DefaultInstance.JointName = OldInstance.JointName;
ToConstraintSetup->DefaultInstance.ConstraintBone1 = OldInstance.ConstraintBone1;
ToConstraintSetup->DefaultInstance.ConstraintBone2 = OldInstance.ConstraintBone2;
ToConstraintSetup->DefaultInstance.Pos1 = OldInstance.Pos1;
ToConstraintSetup->DefaultInstance.Pos2 = OldInstance.Pos2;
//frame1 stays the same
ToConstraintSetup->DefaultInstance.PriAxis1 = OldInstance.PriAxis1;
ToConstraintSetup->DefaultInstance.SecAxis1 = OldInstance.SecAxis1;
//frame2 is copied but relative to frame1
ToConstraintSetup->DefaultInstance.PriAxis2 = Frame2D.GetUnitAxis(EAxis::X);
ToConstraintSetup->DefaultInstance.SecAxis2 = Frame2D.GetUnitAxis(EAxis::Y);
}
struct FMirrorInfo
{
FName BoneName;
int32 BoneIndex;
int32 BodyIndex;
int32 ConstraintIndex;
FMirrorInfo()
{
BoneIndex = INDEX_NONE;
BodyIndex = INDEX_NONE;
ConstraintIndex = INDEX_NONE;
BoneName = NAME_None;
}
};
void FPhATSharedData::Mirror()
{
TArray<FMirrorInfo> MirrorInfos;
if (EditingMode == PEM_BodyEdit) //grab all selected bodies
{
for (const FSelection& Selection : SelectedBodies)
{
MirrorInfos.AddUninitialized();
FMirrorInfo & MirrorInfo = MirrorInfos[MirrorInfos.Num() - 1];
MirrorInfo.BoneName = PhysicsAsset->BodySetup[Selection.Index]->BoneName;
MirrorInfo.BodyIndex = Selection.Index;
MirrorInfo.ConstraintIndex = PhysicsAsset->FindConstraintIndex(MirrorInfo.BoneName);
}
}
else if (EditingMode == PEM_ConstraintEdit) //grab all selected constraints
{
for (const FSelection& Selection : SelectedConstraints)
{
MirrorInfos.AddUninitialized();
FMirrorInfo & MirrorInfo = MirrorInfos[MirrorInfos.Num() - 1];
MirrorInfo.BoneName = PhysicsAsset->ConstraintSetup[Selection.Index]->DefaultInstance.ConstraintBone1;
MirrorInfo.BodyIndex = PhysicsAsset->FindBodyIndex(MirrorInfo.BoneName);
MirrorInfo.ConstraintIndex = Selection.Index;
}
}
for (FMirrorInfo & MirrorInfo : MirrorInfos) //mirror all selected bodies/constraints
{
int32 BoneIndex = EditorSkelMesh->RefSkeleton.FindBoneIndex(MirrorInfo.BoneName);
int32 MirrorBoneIndex = PhysicsAsset->FindMirroredBone(EditorSkelMesh, BoneIndex);
if (MirrorBoneIndex != INDEX_NONE)
{
UBodySetup * SrcBody = PhysicsAsset->BodySetup[MirrorInfo.BodyIndex];
const FScopedTransaction Transaction(NSLOCTEXT("PhAT", "MirrorBody", "MirrorBody"));
MakeNewBody(MirrorBoneIndex, false);
int32 MirrorBodyIndex = PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, MirrorBoneIndex);
UBodySetup * DestBody = PhysicsAsset->BodySetup[MirrorBodyIndex];
DestBody->Modify();
DestBody->CopyBodyPropertiesFrom(SrcBody);
int32 MirrorConstraintIndex = PhysicsAsset->FindConstraintIndex(DestBody->BoneName);
UPhysicsConstraintTemplate * FromConstraint = PhysicsAsset->ConstraintSetup[MirrorInfo.ConstraintIndex];
UPhysicsConstraintTemplate * ToConstraint = PhysicsAsset->ConstraintSetup[MirrorConstraintIndex];
CopyConstraintProperties(FromConstraint, ToConstraint);
}
}
}
FPhATSharedData::EPhATRenderMode FPhATSharedData::GetCurrentMeshViewMode()
{
if (bRunningSimulation)
{
return Sim_MeshViewMode;
}
else if (EditingMode == PEM_BodyEdit)
{
return BodyEdit_MeshViewMode;
}
else
{
return ConstraintEdit_MeshViewMode;
}
}
FPhATSharedData::EPhATRenderMode FPhATSharedData::GetCurrentCollisionViewMode()
{
if (bRunningSimulation)
{
return Sim_CollisionViewMode;
}
else if (EditingMode == PEM_BodyEdit)
{
return BodyEdit_CollisionViewMode;
}
else
{
return ConstraintEdit_CollisionViewMode;
}
}
FPhATSharedData::EPhATConstraintViewMode FPhATSharedData::GetCurrentConstraintViewMode()
{
if (bRunningSimulation)
{
return Sim_ConstraintViewMode;
}
else if (EditingMode == PEM_BodyEdit)
{
return BodyEdit_ConstraintViewMode;
}
else
{
return ConstraintEdit_ConstraintViewMode;
}
}
void FPhATSharedData::HitBone(int32 BodyIndex, EKCollisionPrimitiveType PrimType, int32 PrimIndex, bool bGroupSelect /* = false*/, bool bGroupSelectRemove /* = true */)
{
if (EditingMode == FPhATSharedData::PEM_BodyEdit && !bSelectionLock && !bRunningSimulation)
{
FPhATSharedData::FSelection Selection(BodyIndex, PrimType, PrimIndex);
SetSelectedBody( &Selection, bGroupSelect, bGroupSelectRemove);
}
}
void FPhATSharedData::HitConstraint(int32 ConstraintIndex, bool bGroupSelect)
{
if (EditingMode == FPhATSharedData::PEM_ConstraintEdit && !bSelectionLock && !bRunningSimulation)
{
SetSelectedConstraint(ConstraintIndex, bGroupSelect);
}
}
void FPhATSharedData::RefreshPhysicsAssetChange(const UPhysicsAsset* InPhysAsset)
{
if (InPhysAsset)
{
for (FObjectIterator Iter(USkeletalMeshComponent::StaticClass()); Iter; ++Iter)
{
USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(*Iter);
if (SkeletalMeshComponent->GetPhysicsAsset() == InPhysAsset)
{
// it needs to recreate IF it already has been created
if (SkeletalMeshComponent->IsPhysicsStateCreated())
{
SkeletalMeshComponent->RecreatePhysicsState();
}
}
}
for (FObjectIterator Iter(UWheeledVehicleMovementComponent::StaticClass()); Iter; ++Iter)
{
UWheeledVehicleMovementComponent * WheeledVehicleMovementComponent = Cast<UWheeledVehicleMovementComponent>(*Iter);
if (USkeletalMeshComponent * SkeltalMeshComponent = Cast<USkeletalMeshComponent>(WheeledVehicleMovementComponent->UpdatedComponent))
{
if (SkeltalMeshComponent->GetPhysicsAsset() == InPhysAsset)
{
//Need to recreate car data
WheeledVehicleMovementComponent->RecreatePhysicsState();
}
}
}
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
// since we recreate physicsstate, a lot of transient state data will be gone
// so have to turn simulation off again.
// ideally maybe in the future, we'll fix it by controlling tick?
EnableSimulation(false);
}
}
void FPhATSharedData::SetSelectedBodyAnyPrim(int32 BodyIndex, bool bGroupSelect /* = false */)
{
if (BodyIndex == INDEX_NONE)
{
SetSelectedBody(NULL);
return;
}
UBodySetup* BodySetup = PhysicsAsset->BodySetup[BodyIndex];
check(BodySetup);
if (BodySetup->AggGeom.SphereElems.Num() > 0)
{
FSelection Selection(BodyIndex, KPT_Sphere, 0);
SetSelectedBody(&Selection, bGroupSelect);
}
else if (BodySetup->AggGeom.BoxElems.Num() > 0)
{
FSelection Selection(BodyIndex, KPT_Box, 0);
SetSelectedBody(&Selection, bGroupSelect);
}
else if (BodySetup->AggGeom.SphylElems.Num() > 0)
{
FSelection Selection(BodyIndex, KPT_Sphyl, 0);
SetSelectedBody(&Selection, bGroupSelect);
}
else if (BodySetup->AggGeom.ConvexElems.Num() > 0)
{
FSelection Selection(BodyIndex, KPT_Convex, 0);
SetSelectedBody(&Selection, bGroupSelect);
}
else
{
UE_LOG(LogPhAT, Fatal, TEXT("Body Setup with No Primitives!"));
}
}
void FPhATSharedData::SetSelectedBody(const FSelection* Body, bool bGroupSelect /*= false*/, bool bGroupSelectRemove /* = true */)
{
if(bInsideSelChange)
{
return;
}
if(bGroupSelect == false)
{
SelectedBodies.Empty();
}
if(Body)
{
bool bAlreadySelected = false;
//unselect if already selected
for(int32 i=0; i<SelectedBodies.Num(); ++i)
{
if(SelectedBodies[i] == *Body)
{
if(bGroupSelectRemove)
{
SelectedBodies.RemoveAt(i);
}
bAlreadySelected = true;
break;
}
}
if(bAlreadySelected == false)
{
SelectedBodies.AddUnique(*Body);
}
}
if (SelectedBodies.Num() == 0)
{
// No bone selected
SelectionChangedEvent.Broadcast(EditorSimOptions, NULL);
}
else
{
check(GetSelectedBody() && GetSelectedBody()->Index >= 0 && GetSelectedBody()->Index < PhysicsAsset->BodySetup.Num());
// Set properties dialog to display selected bone (or bone instance) info.
TArray<UObject*> Objs;
for(int i=0; i<SelectedBodies.Num(); ++i)
{
Objs.Add(PhysicsAsset->BodySetup[SelectedBodies[i].Index]);
}
GroupSelectionChangedEvent.Broadcast(Objs);
}
//bInsideSelChange = true;
//HierarchySelectionChangedEvent.Broadcast(); //TODO: disable for now
bInsideSelChange = false;
ControlledBones.Empty();
if(!GetSelectedBody())
{
return;
}
for (int32 i = 0; i <EditorSkelMesh->RefSkeleton.GetNum(); ++i)
{
int32 ControllerBodyIndex = PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, i);
if (ControllerBodyIndex == GetSelectedBody()->Index)
{
ControlledBones.Add(i);
}
}
UpdateNoCollisionBodies();
PreviewChangedEvent.Broadcast();
}
void FPhATSharedData::UpdateNoCollisionBodies()
{
NoCollisionBodies.Empty();
// Query disable table with selected body and every other body.
for (int32 i = 0; i <PhysicsAsset->BodySetup.Num(); ++i)
{
// Add any bodies with bNoCollision
if (PhysicsAsset->BodySetup[i]->DefaultInstance.GetCollisionEnabled() == ECollisionEnabled::NoCollision)
{
NoCollisionBodies.Add(i);
}
else if (GetSelectedBody() && i != GetSelectedBody()->Index)
{
// Add this body if it has disabled collision with selected.
FRigidBodyIndexPair Key(i, GetSelectedBody()->Index);
if (PhysicsAsset->BodySetup[GetSelectedBody()->Index]->DefaultInstance.GetCollisionEnabled() == ECollisionEnabled::NoCollision ||
PhysicsAsset->CollisionDisableTable.Find(Key))
{
NoCollisionBodies.Add(i);
}
}
}
}
void FPhATSharedData::SetSelectedConstraint(int32 ConstraintIndex, bool bGroupSelect /*= false*/)
{
if(bGroupSelect == false)
{
SelectedConstraints.Empty();
}
if(ConstraintIndex != INDEX_NONE)
{
bool bAlreadySelected = false;
for(int32 i=0; i<SelectedConstraints.Num(); ++i)
{
if(SelectedConstraints[i].Index == ConstraintIndex)
{
bAlreadySelected = true;
SelectedConstraints.RemoveAt(i);
break;
}
}
if(bAlreadySelected == false)
{
FSelection Constraint(ConstraintIndex, KPT_Unknown, INDEX_NONE);
SelectedConstraints.AddUnique(Constraint);
}
}
if (!GetSelectedConstraint())
{
SelectionChangedEvent.Broadcast(EditorSimOptions, NULL);
}
else
{
check(GetSelectedConstraint()->Index >= 0 && GetSelectedConstraint()->Index < PhysicsAsset->ConstraintSetup.Num());
TArray<UObject*> Objs;
for(int i=0; i<SelectedConstraints.Num(); ++i)
{
Objs.Add(PhysicsAsset->ConstraintSetup[SelectedConstraints[i].Index]);
}
GroupSelectionChangedEvent.Broadcast(Objs);
}
PreviewChangedEvent.Broadcast();
}
void FPhATSharedData::SetCollisionBetweenSelected(bool bEnableCollision)
{
if (bRunningSimulation || SelectedBodies.Num() == 0)
{
return;
}
PhysicsAsset->Modify();
for(int32 i=0; i<SelectedBodies.Num(); ++i)
{
for(int32 j=i+1; j<SelectedBodies.Num(); ++j)
{
if(bEnableCollision)
{
PhysicsAsset->EnableCollision(SelectedBodies[i].Index, SelectedBodies[j].Index);
}else
{
PhysicsAsset->DisableCollision(SelectedBodies[i].Index, SelectedBodies[j].Index);
}
}
}
UpdateNoCollisionBodies();
PreviewChangedEvent.Broadcast();
}
void FPhATSharedData::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();
}
PreviewChangedEvent.Broadcast();
}
void FPhATSharedData::CopyBody()
{
check(SelectedBodies.Num() == 1);
CopiedBodySetup = PhysicsAsset->BodySetup[GetSelectedBody()->Index];
}
void FPhATSharedData::PasteBodyProperties()
{
// Can't do this while simulating!
if (bRunningSimulation)
{
return;
}
// Must have two valid bodies (which are different)
if(CopiedBodySetup == NULL)
{
return;
}
const FScopedTransaction Transaction( NSLOCTEXT("PhAT", "PasteBodyProperties", "Paste Body Properties") );
for(int32 i=0; i<SelectedBodies.Num(); ++i)
{
// Copy setup/instance properties - based on what we are viewing.
if (!bShowInstanceProps)
{
UBodySetup* ToBodySetup = PhysicsAsset->BodySetup[SelectedBodies[i].Index];
UBodySetup* FromBodySetup = CopiedBodySetup;
ToBodySetup->Modify();
ToBodySetup->CopyBodyPropertiesFrom(FromBodySetup);
}
else
{
FBodyInstance* ToBodyInstance = &PhysicsAsset->BodySetup[SelectedBodies[i].Index]->DefaultInstance;
FBodyInstance* FromBodyInstance = &CopiedBodySetup->DefaultInstance;
ToBodyInstance->CopyBodyInstancePropertiesFrom(FromBodyInstance);
}
}
SetSelectedBody(NULL); //paste can change the primitives on our selected bodies. There's probably a way to properly update this, but for now just deselect
PreviewChangedEvent.Broadcast();
}
bool FPhATSharedData::WeldSelectedBodies(bool bWeld /* = true */)
{
bool bCanWeld = false;
if (bRunningSimulation)
{
return false;
}
if(SelectedBodies.Num() <= 1)
{
return false;
}
//we only support two body weld
int BodyIndex0 = 0;
int BodyIndex1 = INDEX_NONE;
for(int32 i=1; i<SelectedBodies.Num(); ++i)
{
if(SelectedBodies[BodyIndex0].Index == SelectedBodies[i].Index)
{
continue;
}
if(BodyIndex1== INDEX_NONE)
{
BodyIndex1 = i;
}else
{
if(SelectedBodies[BodyIndex1].Index != SelectedBodies[i].Index)
{
return false;
}
}
}
//need to weld bodies not primitives
if(BodyIndex1 == INDEX_NONE)
{
return false;
}
const FSelection& Body0 = SelectedBodies[BodyIndex0];
const FSelection& Body1 = SelectedBodies[BodyIndex1];
FName Bone0Name = PhysicsAsset->BodySetup[Body0.Index]->BoneName;
int32 Bone0Index = EditorSkelMesh->RefSkeleton.FindBoneIndex(Bone0Name);
check(Bone0Index != INDEX_NONE);
FName Bone1Name = PhysicsAsset->BodySetup[Body1.Index]->BoneName;
int32 Bone1Index = EditorSkelMesh->RefSkeleton.FindBoneIndex(Bone1Name);
check(Bone1Index != INDEX_NONE);
int32 Bone0ParentIndex = EditorSkelMesh->RefSkeleton.GetParentIndex(Bone0Index);
int32 Bone1ParentIndex = EditorSkelMesh->RefSkeleton.GetParentIndex(Bone1Index);
int ParentBodyIndex = INDEX_NONE;
int ChildBodyIndex = INDEX_NONE;
FName ParentBoneName;
EKCollisionPrimitiveType ParentPrimitiveType = KPT_Unknown;
EKCollisionPrimitiveType ChildPrimitiveType = KPT_Unknown;
int32 ParentPrimitiveIndex = INDEX_NONE;
int32 ChildPrimitiveIndex = INDEX_NONE;
if (PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, Bone1ParentIndex) == Body0.Index)
{
ParentBodyIndex = Body0.Index;
ParentBoneName = Bone0Name;
ChildBodyIndex = Body1.Index;
ParentPrimitiveType = Body0.PrimitiveType;
ChildPrimitiveType = Body1.PrimitiveType;
ParentPrimitiveIndex = Body0.PrimitiveIndex;
//Child geoms get appended so just add it. This is kind of a hack but this whole indexing scheme needs to be rewritten anyway
ChildPrimitiveIndex = Body1.PrimitiveIndex + PhysicsAsset->BodySetup[Body0.Index]->AggGeom.GetElementCount(ChildPrimitiveType);
bCanWeld = true;
}else if(PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, Bone0ParentIndex) == Body1.Index)
{
ParentBodyIndex = Body1.Index;
ParentBoneName = Bone1Name;
ChildBodyIndex = Body0.Index;
ParentPrimitiveType = Body1.PrimitiveType;
ChildPrimitiveType = Body0.PrimitiveType;
ParentPrimitiveIndex = Body1.PrimitiveIndex;
//Child geoms get appended so just add it. This is kind of a hack but this whole indexing scheme needs to be rewritten anyway
ChildPrimitiveIndex = Body0.PrimitiveIndex + PhysicsAsset->BodySetup[Body1.Index]->AggGeom.GetElementCount(ChildPrimitiveType);
bCanWeld = true;
}
//function is used for the action and the check
if(bWeld == false)
{
return bCanWeld;
}
check(ParentBodyIndex != INDEX_NONE);
check(ChildBodyIndex != INDEX_NONE);
{
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "WeldBodies", "Weld Bodies") );
// .. the asset itself..
PhysicsAsset->Modify();
// .. the parent and child bodies..
PhysicsAsset->BodySetup[ParentBodyIndex]->Modify();
PhysicsAsset->BodySetup[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
HierarchyChangedEvent.Broadcast();
// Body index may have changed, so we re-find it.
int32 BodyIndex = PhysicsAsset->FindBodyIndex(ParentBoneName);
FSelection SelectionParent(BodyIndex, ParentPrimitiveType, ParentPrimitiveIndex);
SetSelectedBody(&SelectionParent); // This redraws the viewport as well...
FSelection SelectionChild(BodyIndex, ChildPrimitiveType, ChildPrimitiveIndex);
SetSelectedBody(&SelectionChild, true); // This redraws the viewport as well...
// Just to be safe - deselect any selected constraints
SetSelectedConstraint(INDEX_NONE);
RefreshPhysicsAssetChange(PhysicsAsset);
return true;
}
void FPhATSharedData::InitConstraintSetup(UPhysicsConstraintTemplate* ConstraintSetup, int32 ChildBodyIndex, int32 ParentBodyIndex)
{
check(ConstraintSetup);
ConstraintSetup->Modify(false);
UBodySetup* ChildBodySetup = PhysicsAsset->BodySetup[ ChildBodyIndex ];
UBodySetup* ParentBodySetup = PhysicsAsset->BodySetup[ ParentBodyIndex ];
check(ChildBodySetup && ParentBodySetup);
const int32 ChildBoneIndex = EditorSkelMesh->RefSkeleton.FindBoneIndex(ChildBodySetup->BoneName);
const int32 ParentBoneIndex = EditorSkelMesh->RefSkeleton.FindBoneIndex(ParentBodySetup->BoneName);
check(ChildBoneIndex != INDEX_NONE && ParentBoneIndex != INDEX_NONE);
// Transform of child from parent is just child ref-pose entry.
FMatrix ChildBoneTM = EditorSkelComp->GetBoneMatrix(ChildBoneIndex);
ChildBoneTM.RemoveScaling();
FMatrix ParentBoneTM = EditorSkelComp->GetBoneMatrix(ParentBoneIndex);
ParentBoneTM.RemoveScaling();
FMatrix RelTM = ChildBoneTM * ParentBoneTM.Inverse();
// Place joint at origin of child
ConstraintSetup->DefaultInstance.ConstraintBone1 = ChildBodySetup->BoneName;
ConstraintSetup->DefaultInstance.Pos1 = FVector::ZeroVector;
ConstraintSetup->DefaultInstance.PriAxis1 = FVector(1.f, 0.f, 0.f);
ConstraintSetup->DefaultInstance.SecAxis1 = FVector(0.f, 1.f, 0.f);
ConstraintSetup->DefaultInstance.ConstraintBone2 = ParentBodySetup->BoneName;
ConstraintSetup->DefaultInstance.Pos2 = RelTM.GetOrigin();
ConstraintSetup->DefaultInstance.PriAxis2 = RelTM.GetScaledAxis( EAxis::X );
ConstraintSetup->DefaultInstance.SecAxis2 = RelTM.GetScaledAxis( EAxis::Y );
// Disable collision between constrained bodies by default.
SetCollisionBetween(ChildBodyIndex, ParentBodyIndex, false);
}
void FPhATSharedData::MakeNewBody(int32 NewBoneIndex, bool bAutoSelect)
{
FName NewBoneName = EditorSkelMesh->RefSkeleton.GetBoneName(NewBoneIndex);
// If this body is already physical - do nothing.
int32 NewBodyIndex = PhysicsAsset->FindBodyIndex(NewBoneName);
if (NewBodyIndex != INDEX_NONE)
{
return;
}
// Find body that currently controls this bone.
int32 ParentBodyIndex = PhysicsAsset->FindControllingBodyIndex(EditorSkelMesh, NewBoneIndex);
PhysicsAsset->Modify();
// Create the physics body.
NewBodyIndex = FPhysicsAssetUtils::CreateNewBody(PhysicsAsset, NewBoneName);
UBodySetup* BodySetup = PhysicsAsset->BodySetup[ 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);
}
else
{
bCreatedBody = FPhysicsAssetUtils::CreateCollisionFromBone(BodySetup, EditorSkelMesh, NewBoneIndex, NewBodyData, AnyWeightBoneInfos);
}
if (bCreatedBody == false)
{
FPhysicsAssetUtils::DestroyBody(PhysicsAsset, NewBodyIndex);
return;
}
// Check if the bone of the new body has any physical children bones
for (int32 i = 0; i < EditorSkelMesh->RefSkeleton.GetNum(); ++i)
{
if (EditorSkelMesh->RefSkeleton.BoneIsChildOf(i, NewBoneIndex))
{
const int32 ChildBodyIndex = PhysicsAsset->FindBodyIndex(EditorSkelMesh->RefSkeleton.GetBoneName(i));
// If the child bone is physical, it may require fixing up in regards to constraints
if (ChildBodyIndex != INDEX_NONE)
{
UBodySetup* ChildBody = PhysicsAsset->BodySetup[ 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
if (ConstraintIndex == INDEX_NONE)
{
ConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(PhysicsAsset, ChildBody->BoneName);
check(ConstraintIndex != INDEX_NONE);
}
// If there's a pre-existing constraint, see if it needs to be fixed up
else
{
UPhysicsConstraintTemplate* ExistingConstraintSetup = PhysicsAsset->ConstraintSetup[ ConstraintIndex ];
check(ExistingConstraintSetup);
const int32 ExistingConstraintBoneIndex = EditorSkelMesh->RefSkeleton.FindBoneIndex(ExistingConstraintSetup->DefaultInstance.ConstraintBone2);
check(ExistingConstraintBoneIndex != INDEX_NONE);
// If the constraint exists between two child bones, then no fix up is required
if (EditorSkelMesh->RefSkeleton.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);
check(ExistingConstraintBodyIndex == ParentBodyIndex);
SetCollisionBetween(ChildBodyIndex, ExistingConstraintBodyIndex, true);
}
UPhysicsConstraintTemplate* ChildConstraintSetup = PhysicsAsset->ConstraintSetup[ ConstraintIndex ];
check(ChildConstraintSetup);
InitConstraintSetup(ChildConstraintSetup, ChildBodyIndex, NewBodyIndex);
}
}
}
// If we have a physics parent, create a joint to it.
if (ParentBodyIndex != INDEX_NONE)
{
const int32 NewConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(PhysicsAsset, NewBoneName);
UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[ NewConstraintIndex ];
check(ConstraintSetup);
InitConstraintSetup(ConstraintSetup, NewBodyIndex, ParentBodyIndex);
}
// update the tree
HierarchyChangedEvent.Broadcast();
if (bAutoSelect)
{
SetSelectedBodyAnyPrim(NewBodyIndex);
}
RefreshPhysicsAssetChange(PhysicsAsset);
}
void FPhATSharedData::SetSelectedConstraintRelTM(const FTransform& RelTM)
{
FTransform WParentFrame = GetConstraintWorldTM(GetSelectedConstraint(), EConstraintFrame::Frame2);
FTransform WNewChildFrame = RelTM * WParentFrame;
UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[GetSelectedConstraint()->Index];
ConstraintSetup->Modify();
// Get child bone transform
int32 BoneIndex = EditorSkelMesh->RefSkeleton.FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone1);
if (BoneIndex != INDEX_NONE)
{
FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex);
BoneTM.RemoveScaling();
ConstraintSetup->DefaultInstance.SetRefFrame(EConstraintFrame::Frame1, WNewChildFrame.GetRelativeTransform(BoneTM));
}
}
void FPhATSharedData::CopyConstraint()
{
check(SelectedConstraints.Num() == 1);
CopiedConstraintTemplate = PhysicsAsset->ConstraintSetup[GetSelectedConstraint()->Index];
}
void FPhATSharedData::PasteConstraintProperties()
{
if (CopiedConstraintTemplate == NULL)
{
return;
}
const FScopedTransaction Transaction( NSLOCTEXT("PhAT", "PasteConstraintProperties", "Paste Constraint Properties") );
UPhysicsConstraintTemplate* FromConstraintSetup = CopiedConstraintTemplate;
for(int32 i=0; i<SelectedConstraints.Num(); ++i)
{
// If we are showing instance properties - copy instance properties. If showing setup, just copy setup properties.
UPhysicsConstraintTemplate* ToConstraintSetup = PhysicsAsset->ConstraintSetup[SelectedConstraints[i].Index];
CopyConstraintProperties(FromConstraintSetup, ToConstraintSetup);
}
}
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 FPhATSharedData::CycleCurrentConstraintOrientation()
{
UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[GetSelectedConstraint()->Index];
FMatrix ConstraintTransform = ConstraintTemplate->DefaultInstance.GetRefFrame(EConstraintFrame::Frame2).ToMatrixWithScale();
FTransform WParentFrame = GetConstraintWorldTM(GetSelectedConstraint(), EConstraintFrame::Frame2);
FTransform WChildFrame = GetConstraintWorldTM(GetSelectedConstraint(), EConstraintFrame::Frame1);
FTransform RelativeTransform = WChildFrame * WParentFrame.Inverse();
CycleMatrixRows(&ConstraintTransform);
ConstraintTemplate->DefaultInstance.SetRefFrame(EConstraintFrame::Frame2, FTransform(ConstraintTransform));
SetSelectedConstraintRelTM(RelativeTransform);
}
void FPhATSharedData::CycleCurrentConstraintActive()
{
for(int32 i=0; i<SelectedConstraints.Num(); ++i)
{
UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[GetSelectedConstraint()->Index];
FConstraintInstance & DefaultInstance = ConstraintTemplate->DefaultInstance;
if(DefaultInstance.AngularSwing1Motion != ACM_Limited && DefaultInstance.AngularSwing2Motion != ACM_Limited)
{
DefaultInstance.AngularSwing1Motion = ACM_Limited;
DefaultInstance.AngularSwing2Motion = ACM_Locked;
DefaultInstance.AngularTwistMotion = ACM_Locked;
}else if(DefaultInstance.AngularSwing2Motion != ACM_Limited && DefaultInstance.AngularTwistMotion != ACM_Limited)
{
DefaultInstance.AngularSwing1Motion = ACM_Locked;
DefaultInstance.AngularSwing2Motion = ACM_Limited;
DefaultInstance.AngularTwistMotion = ACM_Locked;
}else
{
DefaultInstance.AngularSwing1Motion = ACM_Locked;
DefaultInstance.AngularSwing2Motion = ACM_Locked;
DefaultInstance.AngularTwistMotion = ACM_Limited;
}
}
}
void FPhATSharedData::ToggleConstraint(EPhATConstraintType Constraint)
{
for(int32 i=0; i<SelectedConstraints.Num(); ++i)
{
UPhysicsConstraintTemplate* ConstraintTemplate = PhysicsAsset->ConstraintSetup[GetSelectedConstraint()->Index];
FConstraintInstance & DefaultInstance = ConstraintTemplate->DefaultInstance;
if(Constraint == PCT_Swing1)
{
DefaultInstance.AngularSwing1Motion = DefaultInstance.AngularSwing1Motion == ACM_Limited ? ACM_Locked : ACM_Limited;
}else if(Constraint == PCT_Swing2)
{
DefaultInstance.AngularSwing2Motion = DefaultInstance.AngularSwing2Motion == ACM_Limited ? ACM_Locked : ACM_Limited;
}else
{
DefaultInstance.AngularTwistMotion = DefaultInstance.AngularTwistMotion == ACM_Limited ? ACM_Locked : ACM_Limited;
}
}
}
void FPhATSharedData::DeleteBody(int32 DelBodyIndex, bool bRefreshComponent)
{
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "DeleteBody", "Delete Body") );
// The physics asset and default instance..
PhysicsAsset->Modify();
// .. the body..
UBodySetup * BodySetup = PhysicsAsset->BodySetup[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->RefSkeleton.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 ? PhysicsAsset->BodySetup[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->BodySetup[BodyBelowIndex];
if (Constraint->DefaultInstance.ConstraintBone1 == BodyBelow->BoneName)
{
int32 NewConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(PhysicsAsset, BodyBelow->BoneName, Constraint);
UPhysicsConstraintTemplate * NewConstraint = PhysicsAsset->ConstraintSetup[NewConstraintIndex];
InitConstraintSetup(NewConstraint, BodyBelowIndex, ParentBodyIndex);
}
}
}
}
}
// Now actually destroy body. This will destroy any constraints associated with the body as well.
FPhysicsAssetUtils::DestroyBody(PhysicsAsset, DelBodyIndex);
// Select nothing.
SetSelectedBody(NULL);
SetSelectedConstraint(INDEX_NONE);
HierarchyChangedEvent.Broadcast();
if (bRefreshComponent)
{
RefreshPhysicsAssetChange(PhysicsAsset);
}
}
void FPhATSharedData::DeleteCurrentPrim()
{
if (bRunningSimulation)
{
return;
}
if (!GetSelectedBody())
{
return;
}
// Make sure rendering is done - so we are not changing data being used by collision drawing.
FlushRenderingCommands();
//We will first get all the bodysetups we're interested in. The number of duplicates each bodysetup has tells us how many geoms are being deleted
//We need to do this first because deleting will modify our selection
TMap<UBodySetup *, TArray<FSelection>> BodySelectionMap;
TArray<UBodySetup*> BodySetups;
for(int32 i=0; i<SelectedBodies.Num(); ++i)
{
UBodySetup* BodySetup = PhysicsAsset->BodySetup[SelectedBodies[i].Index];
BodySelectionMap.FindOrAdd(BodySetup).Add(SelectedBodies[i]);
}
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "DeletePrimitive", "Delete Primitive") );
for (TMap<UBodySetup*, TArray<FSelection> >::TConstIterator It(BodySelectionMap); It; ++It)
{
UBodySetup * BodySetup = It.Key();
const TArray<FSelection> & SelectedPrimitives = It.Value();
int32 SphereDeletedCount = 0;
int32 BoxDeletedCount = 0;
int32 SphylDeletedCount = 0;
int32 ConvexDeletedCount = 0;
for (int32 i = 0; i < SelectedPrimitives.Num(); ++i)
{
const FSelection& SelectedBody = SelectedPrimitives[i];
int32 BodyIndex = PhysicsAsset->FindBodyIndex(BodySetup->BoneName);
BodySetup->Modify();
if (SelectedBody.PrimitiveType == KPT_Sphere)
{
BodySetup->AggGeom.SphereElems.RemoveAt(SelectedBody.PrimitiveIndex - (SphereDeletedCount++));
}
else if (SelectedBody.PrimitiveType == KPT_Box)
{
BodySetup->AggGeom.BoxElems.RemoveAt(SelectedBody.PrimitiveIndex - (BoxDeletedCount++));
}
else if (SelectedBody.PrimitiveType == KPT_Sphyl)
{
BodySetup->AggGeom.SphylElems.RemoveAt(SelectedBody.PrimitiveIndex - (SphylDeletedCount++));
}
else if (SelectedBody.PrimitiveType == KPT_Convex)
{
BodySetup->AggGeom.ConvexElems.RemoveAt(SelectedBody.PrimitiveIndex - (ConvexDeletedCount++));
// Need to invalidate GUID in this case as cooked data must be updated
BodySetup->InvalidatePhysicsData();
}
// 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);
}
if (CopiedBodySetup == BodySetup)
{
CopiedBodySetup = NULL;
}
}
}
}
HierarchyChangedEvent.Broadcast();
SetSelectedBodyAnyPrim(INDEX_NONE); // Will call UpdateViewport
RefreshPhysicsAssetChange(PhysicsAsset);
}
FTransform FPhATSharedData::GetConstraintBodyTM(const UPhysicsConstraintTemplate* ConstraintSetup, EConstraintFrame::Type Frame) const
{
if (ConstraintSetup == NULL)
{
return FTransform::Identity;
}
int32 BoneIndex;
if (Frame == EConstraintFrame::Frame1)
{
BoneIndex = EditorSkelMesh->RefSkeleton.FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone1);
}
else
{
BoneIndex = EditorSkelMesh->RefSkeleton.FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone2);
}
// If we couldn't find the bone - fall back to identity.
if (BoneIndex == INDEX_NONE)
{
return FTransform::Identity;
}
else
{
FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex);
BoneTM.RemoveScaling();
return BoneTM;
}
}
FTransform FPhATSharedData::GetConstraintWorldTM(const UPhysicsConstraintTemplate* ConstraintSetup, EConstraintFrame::Type Frame, float Scale) const
{
if (ConstraintSetup == NULL)
{
return FTransform::Identity;
}
FVector Scale3D(Scale);
int32 BoneIndex;
FTransform LFrame = ConstraintSetup->DefaultInstance.GetRefFrame(Frame);
if (Frame == EConstraintFrame::Frame1)
{
BoneIndex = EditorSkelMesh->RefSkeleton.FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone1);
}
else
{
BoneIndex = EditorSkelMesh->RefSkeleton.FindBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone2);
}
// If we couldn't find the bone - fall back to identity.
if (BoneIndex == INDEX_NONE)
{
return FTransform::Identity;
}
else
{
FTransform BoneTM = EditorSkelComp->GetBoneTransform(BoneIndex);
BoneTM.RemoveScaling();
LFrame.ScaleTranslation(Scale3D);
return LFrame * BoneTM;
}
}
FTransform FPhATSharedData::GetConstraintMatrix(int32 ConstraintIndex, EConstraintFrame::Type Frame, float Scale) const
{
UPhysicsConstraintTemplate* ConstraintSetup = PhysicsAsset->ConstraintSetup[ConstraintIndex];
return GetConstraintWorldTM(ConstraintSetup, Frame, Scale);
}
FTransform FPhATSharedData::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 FPhATSharedData::DeleteCurrentConstraint()
{
if (EditingMode != PEM_ConstraintEdit || !GetSelectedConstraint())
{
return;
}
const FScopedTransaction Transaction( NSLOCTEXT("PhAT", "DeleteConstraint", "Delete Constraint") );
//Save indices before delete because delete modifies our Selected array
TArray<int32> Indices;
for(int32 i=0; i<SelectedConstraints.Num(); ++i)
{
Indices.Add(SelectedConstraints[i].Index);
}
Indices.Sort();
//These are indices into an array, we must remove it from greatest to smallest so that the indices don't shift
for(int32 i=Indices.Num() - 1; i>= 0; --i)
{
if(PhysicsAsset->ConstraintSetup[Indices[i]] == CopiedConstraintTemplate)
{
CopiedConstraintTemplate = NULL;
}
PhysicsAsset->Modify();
FPhysicsAssetUtils::DestroyConstraint(PhysicsAsset, Indices[i]);
}
SetSelectedConstraint(INDEX_NONE);
HierarchyChangedEvent.Broadcast();
PreviewChangedEvent.Broadcast();
}
void FPhATSharedData::ToggleInstanceProperties()
{
bShowInstanceProps = !bShowInstanceProps;
PreviewChangedEvent.Broadcast();
if (EditingMode == PEM_ConstraintEdit)
{
if (GetSelectedConstraint())
{
UPhysicsConstraintTemplate* ConSetup = PhysicsAsset->ConstraintSetup[GetSelectedConstraint()->Index];
FSelection Selection(GetSelectedConstraint()->Index, KPT_Unknown, INDEX_NONE);
SelectionChangedEvent.Broadcast(ConSetup, &Selection);
}
}
else if (EditingMode == PEM_BodyEdit)
{
if (GetSelectedBody())
{
UBodySetup* BodySetup = PhysicsAsset->BodySetup[GetSelectedBody()->Index];
// Set properties dialog to display selected bone (or bone instance) info.
SelectionChangedEvent.Broadcast(BodySetup, GetSelectedBody());
}
}
}
void FPhATSharedData::ToggleSimulation()
{
// don't start simulation if there are no bodies or if we are manipulating a body
if (PhysicsAsset->BodySetup.Num() == 0 || bManipulating)
{
return;
}
EnableSimulation(!bRunningSimulation);
bRunningSimulation = !bRunningSimulation;
}
void FPhATSharedData::EnableSimulation(bool bEnableSimulation)
{
if (bEnableSimulation)
{
// Flush geometry cache inside the asset (don't want to use cached version of old geometry!)
PhysicsAsset->InvalidateAllPhysicsMeshes();
// We should not already have an instance (destroyed when stopping sim).
EditorSkelComp->SetSimulatePhysics(true);
EditorSkelComp->SetPhysicsBlendWeight(EditorSimOptions->PhysicsBlend);
EditorSkelComp->InitArticulated(PreviewScene.GetWorld()->GetPhysicsScene());
// Make it start simulating
EditorSkelComp->WakeAllRigidBodies();
// Set the properties window to point at the simulation options object.
SelectionChangedEvent.Broadcast(EditorSimOptions, NULL);
}
else
{
// Stop any animation and clear node when stopping simulation.
EditorSkelComp->SetAnimation(NULL);
// Turn off/remove the physics instance for this thing, and move back to start location.
EditorSkelComp->TermArticulated();
EditorSkelComp->SetSimulatePhysics(false);
EditorSkelComp->SetPhysicsBlendWeight(0.f);
// 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();
PreviewChangedEvent.Broadcast();
// Put properties window back to selected.
if (EditingMode == FPhATSharedData::PEM_BodyEdit)
{
SetSelectedBody(NULL, true);
}
else
{
SetSelectedConstraint(INDEX_NONE, true);
}
}
}
void FPhATSharedData::OpenNewBodyDlg()
{
OpenNewBodyDlg(&NewBodyData, &NewBodyResponse);
}
void FPhATSharedData::OpenNewBodyDlg(FPhysAssetCreateParams* NewBodyData, EAppReturnType::Type* NewBodyResponse)
{
auto ModalWindow = SNew(SWindow)
.Title( NSLOCTEXT("PhAT", "NewAssetTitle", "New Asset") )
.SizingRule( ESizingRule::Autosized )
.SupportsMinimize(false) .SupportsMaximize(false);
auto MessageBox = SNew(SPhATNewAssetDlg)
.ParentWindow(ModalWindow)
.NewBodyData(NewBodyData)
.NewBodyResponse(NewBodyResponse);
ModalWindow->SetContent(MessageBox);
GEditor->EditorAddModalWindow(ModalWindow);
}
void FPhATSharedData::PostUndo()
{
if (bRunningSimulation)
{
return;
}
bool bInvalidSelection = false;
for (int32 BodyIndex = 0; BodyIndex < SelectedBodies.Num() && bInvalidSelection == false; ++BodyIndex)
{
const FSelection& Selection = SelectedBodies[BodyIndex];
if (PhysicsAsset->BodySetup.Num() <= Selection.Index)
{
bInvalidSelection = true;
}
else
{
if (UBodySetup * BodySetup = PhysicsAsset->BodySetup[Selection.Index])
{
switch (Selection.PrimitiveType)
{
case KPT_Box: bInvalidSelection = BodySetup->AggGeom.BoxElems.Num() <= Selection.PrimitiveIndex ? true : bInvalidSelection; break;
case KPT_Convex: bInvalidSelection = BodySetup->AggGeom.ConvexElems.Num() <= Selection.PrimitiveIndex ? true : bInvalidSelection; break;
case KPT_Sphere: bInvalidSelection = BodySetup->AggGeom.SphereElems.Num() <= Selection.PrimitiveIndex ? true : bInvalidSelection; break;
case KPT_Sphyl: bInvalidSelection = BodySetup->AggGeom.SphylElems.Num() <= Selection.PrimitiveIndex ? true : bInvalidSelection; break;
default: bInvalidSelection = true;
}
}
else
{
bInvalidSelection = true;
}
}
}
for (int32 ConstraintIndex = 0; ConstraintIndex < SelectedConstraints.Num() && bInvalidSelection == false; ++ConstraintIndex)
{
const FSelection& Selection = SelectedConstraints[ConstraintIndex];
if (PhysicsAsset->ConstraintSetup.Num() <= Selection.Index)
{
bInvalidSelection = true;
}
}
if (bInvalidSelection)
{
// Clear selection before we undo. We don't transact the editor itself - don't want to have something selected that is then removed.
SetSelectedBody(NULL);
SetSelectedConstraint(INDEX_NONE);
}
PreviewChangedEvent.Broadcast();
HierarchyChangedEvent.Broadcast();
}
void FPhATSharedData::Redo()
{
if (bRunningSimulation)
{
return;
}
SetSelectedBody(NULL);
SetSelectedConstraint(INDEX_NONE);
GEditor->RedoTransaction();
PhysicsAsset->UpdateBodySetupIndexMap();
PreviewChangedEvent.Broadcast();
HierarchyChangedEvent.Broadcast();
}
#undef LOCTEXT_NAMESPACE