Files
UnrealEngineUWP/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp

2336 lines
74 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "PersonaPrivatePCH.h"
#include "AnimationEditorPreviewScene.h"
#include "SAnimationEditorViewport.h"
#include "Runtime/Engine/Public/Slate/SceneViewport.h"
#include "SAnimViewportToolBar.h"
#include "AnimViewportShowCommands.h"
#include "AnimGraphDefinitions.h"
#include "AnimPreviewInstance.h"
#include "AnimationEditorViewportClient.h"
#include "AnimGraphNode_SkeletalControlBase.h"
#include "Runtime/Engine/Classes/Components/ReflectionCaptureComponent.h"
#include "Runtime/Engine/Classes/Components/SphereReflectionCaptureComponent.h"
#include "UnrealWidget.h"
#include "MouseDeltaTracker.h"
#include "ScopedTransaction.h"
#include "Components/DirectionalLightComponent.h"
#include "Components/ExponentialHeightFogComponent.h"
#include "CanvasTypes.h"
#include "Engine/TextureCube.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "Engine/CollisionProfile.h"
#include "Engine/SkeletalMeshSocket.h"
#include "EngineUtils.h"
#include "GameFramework/WorldSettings.h"
#include "PhysicsEngine/PhysicsSettings.h"
#include "Components/WindDirectionalSourceComponent.h"
#include "Engine/StaticMesh.h"
#include "SAnimationEditorViewport.h"
namespace {
// Value from UE3
static const float AnimationEditorViewport_RotateSpeed = 0.02f;
// Value from UE3
static const float AnimationEditorViewport_TranslateSpeed = 0.25f;
// follow camera feature
static const float FollowCamera_InterpSpeed = 4.f;
static const float FollowCamera_InterpSpeed_Z = 1.f;
// @todo double define - fix it
const float FOVMin = 5.f;
const float FOVMax = 170.f;
}
/**
* A hit proxy class for sockets in the Persona viewport.
*/
struct HPersonaSocketProxy : public HHitProxy
{
DECLARE_HIT_PROXY();
FSelectedSocketInfo SocketInfo;
explicit HPersonaSocketProxy( FSelectedSocketInfo InSocketInfo )
: SocketInfo( InSocketInfo )
{}
};
IMPLEMENT_HIT_PROXY( HPersonaSocketProxy, HHitProxy );
/**
* A hit proxy class for sockets in the Persona viewport.
*/
struct HPersonaBoneProxy : public HHitProxy
{
DECLARE_HIT_PROXY();
FName BoneName;
explicit HPersonaBoneProxy(const FName& InBoneName)
: BoneName( InBoneName )
{}
};
IMPLEMENT_HIT_PROXY( HPersonaBoneProxy, HHitProxy );
#define LOCTEXT_NAMESPACE "FAnimationViewportClient"
/////////////////////////////////////////////////////////////////////////
// FAnimationViewportClient
FAnimationViewportClient::FAnimationViewportClient(FAnimationEditorPreviewScene& InPreviewScene, TWeakPtr<FPersona> InPersonaPtr, const TSharedRef<SAnimationEditorViewport>& InAnimationEditorViewport)
: FEditorViewportClient(nullptr, &InPreviewScene, StaticCastSharedRef<SEditorViewport>(InAnimationEditorViewport))
, PersonaPtr( InPersonaPtr )
, bManipulating(false)
, bInTransaction(false)
, AnimationPlaybackScale(1.0f)
, GravityScaleSliderValue(0.25f)
, PrevWindStrength(0.0f)
, SelectedWindActor(NULL)
, bFocusOnDraw(false)
, BodyTraceDistance(100000.0f)
, bShouldUpdateDefaultValues(false)
{
// load config
ConfigOption = UPersonaOptions::StaticClass()->GetDefaultObject<UPersonaOptions>();
check (ConfigOption);
// DrawHelper set up
DrawHelper.PerspectiveGridSize = HALF_WORLD_MAX1;
DrawHelper.AxesLineThickness = ConfigOption->bHighlightOrigin ? 1.0f : 0.0f;
DrawHelper.bDrawGrid = ConfigOption->bShowGrid;
LocalAxesMode = static_cast<ELocalAxesMode::Type>(ConfigOption->DefaultLocalAxesSelection);
WidgetMode = FWidget::WM_Rotate;
SetSelectedBackgroundColor(ConfigOption->ViewportBackgroundColor, false);
EngineShowFlags.Game = 0;
EngineShowFlags.ScreenSpaceReflections = 1;
EngineShowFlags.AmbientOcclusion = 1;
EngineShowFlags.SetSnap(0);
SetRealtime(true);
if(GEditor->PlayWorld)
{
SetRealtime(false,true); // We are PIE, don't start in realtime mode
}
// set visibility
GetAnimPreviewScene()->EditorSkyComp->SetVisibility(ConfigOption->bShowSky);
GetAnimPreviewScene()->EditorHeightFogComponent->SetVisibility(ConfigOption->bShowSky);
GetAnimPreviewScene()->EditorFloorComp->SetVisibility(ConfigOption->bShowFloor);
ViewFOV = FMath::Clamp<float>(ConfigOption->ViewFOV, FOVMin, FOVMax);
EngineShowFlags.DisableAdvancedFeatures();
EngineShowFlags.CompositeEditorPrimitives = true;
// set camera mode
bCameraFollow = false;
bDrawUVs = false;
UVChannelToDraw = 0;
bAutoAlignFloor = true;
// Set audio mute option
UWorld* World = PreviewScene->GetWorld();
if(World)
{
World->bAllowAudioPlayback = !ConfigOption->bMuteAudio;
}
// Grab a wind actor if it exists (could have been placed in the scene before a mode transition)
TActorIterator<AWindDirectionalSource> WindIter(World);
if(WindIter)
{
AWindDirectionalSource* WindActor = *WindIter;
WindSourceActor = WindActor;
PrevWindLocation = WindActor->GetActorLocation();
PrevWindRotation = WindActor->GetActorRotation();
PrevWindStrength = WindActor->GetComponent()->Strength;
}
else
{
//wind actor's initial position, rotation and strength
PrevWindLocation = FVector(100, 100, 100);
PrevWindRotation = FRotator(0, 0, 0); // roll, yaw, pitch
PrevWindStrength = 0.2f;
}
}
FAnimationViewportClient::~FAnimationViewportClient()
{
}
FLinearColor FAnimationViewportClient::GetBackgroundColor() const
{
return SelectedHSVColor.HSVToLinearRGB();
}
FSceneView* FAnimationViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily)
{
float AmbientCubemapIntensity = 0.4f;
FSceneView* SceneView = FEditorViewportClient::CalcSceneView(ViewFamily);
FFinalPostProcessSettings::FCubemapEntry& CubemapEntry = *new(SceneView->FinalPostProcessSettings.ContributingCubemaps) FFinalPostProcessSettings::FCubemapEntry;
CubemapEntry.AmbientCubemap = GUnrealEd->GetThumbnailManager()->AmbientCubemap;
CubemapEntry.AmbientCubemapTintMulScaleValue = FLinearColor::White * AmbientCubemapIntensity;
return SceneView;
}
void FAnimationViewportClient::SetSelectedBackgroundColor(const FLinearColor& RGBColor, bool bSave/*=true*/)
{
SelectedHSVColor = RGBColor.LinearRGBToHSV();
// save to config
if (bSave)
{
ConfigOption->SetViewportBackgroundColor(RGBColor);
}
// only consider V from HSV
// just inversing works against for mid range brightness
// V being from 0-0.3 (almost white), 0.31-1 (almost black)
if (SelectedHSVColor.B < 0.3f)
{
DrawHelper.GridColorAxis = FColor(230,230,230);
DrawHelper.GridColorMajor = FColor(180,180,180);
DrawHelper.GridColorMinor = FColor(128,128,128);
}
else
{
DrawHelper.GridColorAxis = FColor(20,20,20);
DrawHelper.GridColorMajor = FColor(60,60,60);
DrawHelper.GridColorMinor = FColor(128,128,128);
}
Invalidate();
}
void FAnimationViewportClient::SetBackgroundColor( FLinearColor InColor )
{
SetSelectedBackgroundColor(InColor);
}
float FAnimationViewportClient::GetBrightnessValue() const
{
return SelectedHSVColor.B;
}
void FAnimationViewportClient::SetBrightnessValue( float Value )
{
SelectedHSVColor.B = Value;
SetSelectedBackgroundColor(SelectedHSVColor.HSVToLinearRGB());
}
void FAnimationViewportClient::OnToggleShowGrid()
{
FEditorViewportClient::SetShowGrid();
ConfigOption->SetShowGrid(DrawHelper.bDrawGrid);
}
bool FAnimationViewportClient::IsShowingGrid() const
{
return FEditorViewportClient::IsSetShowGridChecked();
}
void FAnimationViewportClient::OnToggleShowFloor()
{
if (GetAnimPreviewScene()->EditorFloorComp)
{
GetAnimPreviewScene()->EditorFloorComp->SetVisibility(!GetAnimPreviewScene()->EditorFloorComp->bVisible);
Invalidate();
}
ConfigOption->SetShowFloor(GetAnimPreviewScene()->EditorFloorComp->bVisible);
}
bool FAnimationViewportClient::IsShowingFloor() const
{
return (GetAnimPreviewScene()->EditorFloorComp) ? GetAnimPreviewScene()->EditorFloorComp->bVisible : false;
}
void FAnimationViewportClient::OnToggleAutoAlignFloor()
{
bAutoAlignFloor = !bAutoAlignFloor;
UpdateCameraSetup();
}
bool FAnimationViewportClient::IsAutoAlignFloor() const
{
return bAutoAlignFloor;
}
void FAnimationViewportClient::OnToggleShowSky()
{
if (GetAnimPreviewScene()->EditorSkyComp)
{
bool bNewVisibility = !GetAnimPreviewScene()->EditorSkyComp->bVisible;
GetAnimPreviewScene()->EditorSkyComp->SetVisibility(bNewVisibility);
GetAnimPreviewScene()->EditorHeightFogComponent->SetVisibility(bNewVisibility);
Invalidate();
}
ConfigOption->SetShowSky(GetAnimPreviewScene()->EditorSkyComp->bVisible);
}
bool FAnimationViewportClient::IsShowingSky() const
{
return (GetAnimPreviewScene()->EditorSkyComp) ? GetAnimPreviewScene()->EditorSkyComp->bVisible : false;
}
void FAnimationViewportClient::OnToggleMuteAudio()
{
UWorld* World = PreviewScene->GetWorld();
if(World)
{
bool bNewAllowAudioPlayback = !World->AllowAudioPlayback();
World->bAllowAudioPlayback = bNewAllowAudioPlayback;
ConfigOption->SetMuteAudio(!bNewAllowAudioPlayback);
}
}
bool FAnimationViewportClient::IsAudioMuted() const
{
UWorld* World = PreviewScene->GetWorld();
return (World) ? !World->AllowAudioPlayback() : false;
}
void FAnimationViewportClient::SetCameraFollow()
{
bCameraFollow = !bCameraFollow;
if( bCameraFollow )
{
EnableCameraLock(false);
if (PreviewSkelMeshComp.IsValid())
{
FBoxSphereBounds Bound = PreviewSkelMeshComp.Get()->CalcBounds(FTransform::Identity);
SetViewLocationForOrbiting(Bound.Origin);
}
}
else
{
FocusViewportOnPreviewMesh();
Invalidate();
}
}
bool FAnimationViewportClient::IsSetCameraFollowChecked() const
{
return bCameraFollow;
}
void FAnimationViewportClient::SetPreviewMeshComponent(UDebugSkelMeshComponent* InPreviewSkelMeshComp)
{
PreviewSkelMeshComp = InPreviewSkelMeshComp;
PreviewSkelMeshComp->BonesOfInterest.Empty();
UpdateCameraSetup();
// Setup physics data from physics assets if available, clearing any physics setup on the component
UPhysicsAsset* PhysAsset = PreviewSkelMeshComp->GetPhysicsAsset();
if(PhysAsset)
{
PhysAsset->InvalidateAllPhysicsMeshes();
PreviewSkelMeshComp->TermArticulated();
PreviewSkelMeshComp->InitArticulated(GetWorld()->GetPhysicsScene());
// Set to block all to enable tracing.
PreviewSkelMeshComp->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
PreviewSkelMeshComp->RefreshBoneTransforms();
}
}
void FAnimationViewportClient::Draw(const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
FEditorViewportClient::Draw(View, PDI);
if (PreviewSkelMeshComp.IsValid() && PreviewSkelMeshComp->SkeletalMesh)
{
// Can't have both bones of interest and sockets of interest set
check( !(PreviewSkelMeshComp->BonesOfInterest.Num() && PreviewSkelMeshComp->SocketsOfInterest.Num() ) )
// if we have BonesOfInterest, draw sub set of the bones only
if ( PreviewSkelMeshComp->BonesOfInterest.Num() > 0 )
{
DrawMeshSubsetBones(PreviewSkelMeshComp.Get(), PreviewSkelMeshComp->BonesOfInterest, PDI);
}
// otherwise, if we display bones, display
if ( PreviewSkelMeshComp->bDisplayBones )
{
DrawMeshBones(PreviewSkelMeshComp.Get(), PDI);
}
if ( PreviewSkelMeshComp->bDisplayRawAnimation )
{
DrawMeshBonesUncompressedAnimation(PreviewSkelMeshComp.Get(), PDI);
}
if ( PreviewSkelMeshComp->NonRetargetedSpaceBases.Num() > 0 )
{
DrawMeshBonesNonRetargetedAnimation(PreviewSkelMeshComp.Get(), PDI);
}
if( PreviewSkelMeshComp->bDisplayAdditiveBasePose )
{
DrawMeshBonesAdditiveBasePose(PreviewSkelMeshComp.Get(), PDI);
}
if(PreviewSkelMeshComp->bDisplayBakedAnimation)
{
DrawMeshBonesBakedAnimation(PreviewSkelMeshComp.Get(), PDI);
}
if(PreviewSkelMeshComp->bDisplaySourceAnimation)
{
DrawMeshBonesSourceRawAnimation(PreviewSkelMeshComp.Get(), PDI);
}
// Display normal vectors of each simulation vertex
if ( PreviewSkelMeshComp->bDisplayClothingNormals )
{
PreviewSkelMeshComp->DrawClothingNormals(PDI);
}
// Display tangent spaces of each graphical vertex
if ( PreviewSkelMeshComp->bDisplayClothingTangents )
{
PreviewSkelMeshComp->DrawClothingTangents(PDI);
}
// Display collision volumes of current selected cloth
if ( PreviewSkelMeshComp->bDisplayClothingCollisionVolumes )
{
PreviewSkelMeshComp->DrawClothingCollisionVolumes(PDI);
}
// Display collision volumes of current selected cloth
if ( PreviewSkelMeshComp->bDisplayClothPhysicalMeshWire )
{
PreviewSkelMeshComp->DrawClothingPhysicalMeshWire(PDI);
}
// Display collision volumes of current selected cloth
if ( PreviewSkelMeshComp->bDisplayClothMaxDistances )
{
PreviewSkelMeshComp->DrawClothingMaxDistances(PDI);
}
// Display collision volumes of current selected cloth
if ( PreviewSkelMeshComp->bDisplayClothBackstops )
{
PreviewSkelMeshComp->DrawClothingBackstops(PDI);
}
if( PreviewSkelMeshComp->bDisplayClothFixedVertices )
{
PreviewSkelMeshComp->DrawClothingFixedVertices(PDI);
}
// Display socket hit points
if ( PreviewSkelMeshComp->bDrawSockets )
{
if ( PreviewSkelMeshComp->bSkeletonSocketsVisible && PreviewSkelMeshComp->SkeletalMesh->Skeleton )
{
DrawSockets( PreviewSkelMeshComp.Get()->SkeletalMesh->Skeleton->Sockets, PDI, true );
}
if ( PreviewSkelMeshComp->bMeshSocketsVisible )
{
DrawSockets( PreviewSkelMeshComp.Get()->SkeletalMesh->GetMeshOnlySocketList(), PDI, false );
}
}
// If we have a socket of interest, draw the widget
if ( PreviewSkelMeshComp->SocketsOfInterest.Num() == 1 )
{
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
bool bSocketIsOnSkeleton = PreviewSkelMeshComp->SocketsOfInterest[0].bSocketIsOnSkeleton;
TArray<USkeletalMeshSocket*> SocketAsArray;
SocketAsArray.Add( Socket );
DrawSockets( SocketAsArray, PDI, false );
}
}
if ( PersonaPtr.IsValid() && PreviewSkelMeshComp.IsValid() )
{
// Allow selected nodes to draw debug rendering if they support it
const FGraphPanelSelectionSet SelectedNodes = PersonaPtr.Pin()->GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UAnimGraphNode_SkeletalControlBase* Node = Cast<UAnimGraphNode_SkeletalControlBase>(*NodeIt);
if (Node)
{
Node->Draw(PDI, PreviewSkelMeshComp.Get());
}
}
if(bFocusOnDraw)
{
bFocusOnDraw = false;
FocusViewportOnPreviewMesh();
}
}
}
void FAnimationViewportClient::DrawCanvas( FViewport& InViewport, FSceneView& View, FCanvas& Canvas )
{
if (PreviewSkelMeshComp.IsValid())
{
// Display bone names
if (PreviewSkelMeshComp->bShowBoneNames)
{
ShowBoneNames(&Canvas, &View);
}
// Display info
if (IsShowingMeshStats())
{
DisplayInfo(&Canvas, &View, IsDetailedMeshStats());
}
// Draw name of selected bone
if (PreviewSkelMeshComp->BonesOfInterest.Num() == 1)
{
const FIntPoint ViewPortSize = Viewport->GetSizeXY();
const int32 HalfX = ViewPortSize.X/2;
const int32 HalfY = ViewPortSize.Y/2;
int32 BoneIndex = PreviewSkelMeshComp->BonesOfInterest[0];
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
FMatrix BoneMatrix = PreviewSkelMeshComp->GetBoneMatrix(BoneIndex);
const FPlane proj = View.Project(BoneMatrix.GetOrigin());
if (proj.W > 0.f)
{
const int32 XPos = HalfX + ( HalfX * proj.X );
const int32 YPos = HalfY + ( HalfY * (proj.Y * -1) );
FQuat BoneQuat = PreviewSkelMeshComp->GetBoneQuaternion(BoneName);
FVector Loc = PreviewSkelMeshComp->GetBoneLocation(BoneName);
FCanvasTextItem TextItem( FVector2D( XPos, YPos), FText::FromString( BoneName.ToString() ), GEngine->GetSmallFont(), FLinearColor::White );
Canvas.DrawItem( TextItem );
}
}
// Draw name of selected socket
if (PreviewSkelMeshComp->SocketsOfInterest.Num() == 1)
{
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
FMatrix SocketMatrix;
Socket->GetSocketMatrix( SocketMatrix, PreviewSkelMeshComp.Get() );
const FVector SocketPos = SocketMatrix.GetOrigin();
const FPlane proj = View.Project( SocketPos );
if(proj.W > 0.f)
{
const FIntPoint ViewPortSize = Viewport->GetSizeXY();
const int32 HalfX = ViewPortSize.X/2;
const int32 HalfY = ViewPortSize.Y/2;
const int32 XPos = HalfX + ( HalfX * proj.X );
const int32 YPos = HalfY + ( HalfY * (proj.Y * -1) );
FCanvasTextItem TextItem( FVector2D( XPos, YPos), FText::FromString( Socket->SocketName.ToString() ), GEngine->GetSmallFont(), FLinearColor::White );
Canvas.DrawItem( TextItem );
}
}
if (bDrawUVs)
{
DrawUVsForMesh(Viewport, &Canvas, 1.0f);
}
}
}
void FAnimationViewportClient::DrawUVsForMesh(FViewport* InViewport, FCanvas* InCanvas, int32 InTextYPos)
{
//use the overriden LOD level
const uint32 LODLevel = FMath::Clamp(PreviewSkelMeshComp->ForcedLodModel - 1, 0, PreviewSkelMeshComp->SkeletalMesh->LODInfo.Num() - 1);
TArray<FVector2D> SelectedEdgeTexCoords; //No functionality in Persona for this (yet?)
DrawUVs(InViewport, InCanvas, InTextYPos, LODLevel, UVChannelToDraw, SelectedEdgeTexCoords, NULL, &PreviewSkelMeshComp->GetSkeletalMeshResource()->LODModels[LODLevel] );
}
FAnimNode_SkeletalControlBase* FAnimationViewportClient::FindSkeletalControlAnimNode(TWeakObjectPtr<UAnimGraphNode_SkeletalControlBase> AnimGraphNode) const
{
FAnimNode_SkeletalControlBase* AnimNode = NULL;
if (!PreviewSkelMeshComp.IsValid() || !PreviewSkelMeshComp->GetAnimInstance())
{
return NULL;
}
if (AnimGraphNode.IsValid())
{
// find an anim node index from debug data
UAnimBlueprintGeneratedClass* AnimBlueprintClass = Cast<UAnimBlueprintGeneratedClass>(PreviewSkelMeshComp->GetAnimInstance()->GetClass());
if (AnimBlueprintClass)
{
FAnimBlueprintDebugData& DebugData = AnimBlueprintClass->GetAnimBlueprintDebugData();
int32* IndexPtr = DebugData.NodePropertyToIndexMap.Find(AnimGraphNode);
if (IndexPtr)
{
int32 AnimNodeIndex = *IndexPtr;
// reverse node index temporarily because of a bug in NodeGuidToIndexMap
AnimNodeIndex = AnimBlueprintClass->AnimNodeProperties.Num() - AnimNodeIndex - 1;
AnimNode = AnimBlueprintClass->AnimNodeProperties[AnimNodeIndex]->ContainerPtrToValuePtr<FAnimNode_SkeletalControlBase>(PreviewSkelMeshComp->GetAnimInstance());
}
}
}
return AnimNode;
}
void FAnimationViewportClient::FindSelectedAnimGraphNode()
{
bool bSelected = false;
// finding a selected node
if (PersonaPtr.IsValid() && PreviewSkelMeshComp.IsValid())
{
USkeletalMeshComponent* PreviewComponent = PreviewSkelMeshComp.Get();
check(PreviewComponent);
const FGraphPanelSelectionSet SelectedNodes = PersonaPtr.Pin()->GetSelectedNodes();
// don't support multi-selection
if (SelectedNodes.Num() == 1)
{
FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes);
// first element
UAnimGraphNode_SkeletalControlBase* AnimGraphNode = Cast<UAnimGraphNode_SkeletalControlBase>(*NodeIt);
if (AnimGraphNode)
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(AnimGraphNode);
if (AnimNode)
{
bSelected = true;
// when selected first after AnimGraph is opened, assign previous data to current node
if (SelectedSkelControlAnimGraph != AnimGraphNode)
{
if (SelectedSkelControlAnimGraph.IsValid())
{
SelectedSkelControlAnimGraph->DeselectActor(PreviewComponent);
}
// make same values to ensure data consistency
AnimGraphNode->CopyNodeDataTo(AnimNode);
WidgetMode = (FWidget::EWidgetMode)AnimGraphNode->GetWidgetMode(PreviewComponent);
ECoordSystem DesiredCoordSystem = (ECoordSystem)AnimGraphNode->GetWidgetCoordinateSystem(PreviewComponent);
SetWidgetCoordSystemSpace(DesiredCoordSystem);
}
else
{
if (bShouldUpdateDefaultValues)
{
// copy updated values into internal node to ensure data consistency
AnimGraphNode->CopyNodeDataFrom(AnimNode);
bShouldUpdateDefaultValues = false;
}
}
AnimGraphNode->MoveSelectActorLocation(PreviewComponent, AnimNode);
SelectedSkelControlAnimGraph = AnimGraphNode;
}
}
}
if (!bSelected)
{
if (SelectedSkelControlAnimGraph.IsValid())
{
SelectedSkelControlAnimGraph->DeselectActor(PreviewComponent);
}
SelectedSkelControlAnimGraph.Reset();
}
}
}
void FAnimationViewportClient::ClearSelectedAnimGraphNode()
{
if (SelectedSkelControlAnimGraph.IsValid())
{
SelectedSkelControlAnimGraph->DeselectActor(PreviewSkelMeshComp.Get());
}
}
void FAnimationViewportClient::PostUndo()
{
// undo for skeletal controllers
// search all UAnimGraphNode_SkeletalControlBase nodes and apply data
UEdGraph* FocusedGraph = PersonaPtr.Pin()->GetFocusedGraph();
// @fixme : fix this to better way than looping all nodes
if (FocusedGraph)
{
// find UAnimGraphNode_SkeletalControlBase
for (UEdGraphNode* Node : FocusedGraph->Nodes)
{
UAnimGraphNode_SkeletalControlBase* AnimGraphNode = Cast<UAnimGraphNode_SkeletalControlBase>(Node);
if (AnimGraphNode)
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(AnimGraphNode);
if (AnimNode)
{
// copy undo data
AnimGraphNode->CopyNodeDataTo(AnimNode);
}
}
}
}
}
void FAnimationViewportClient::PostCompile()
{
// reset the selected anim graph to copy
SelectedSkelControlAnimGraph.Reset();
// if the user manipulated Pin values directly from the node, then should copy updated values to the internal node to retain data consistency
UEdGraph* FocusedGraph = PersonaPtr.Pin()->GetFocusedGraph();
// @fixme : fix this to better way than looping all nodes
if (FocusedGraph)
{
// find UAnimGraphNode_SkeletalControlBase
for (UEdGraphNode* Node : FocusedGraph->Nodes)
{
UAnimGraphNode_SkeletalControlBase* AnimGraphNode = Cast<UAnimGraphNode_SkeletalControlBase>(Node);
if (AnimGraphNode)
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(AnimGraphNode);
if (AnimNode)
{
AnimGraphNode->CopyNodeDataFrom(AnimNode);
}
}
}
}
}
void FAnimationViewportClient::Tick(float DeltaSeconds)
{
FEditorViewportClient::Tick(DeltaSeconds);
// @todo fixme
if (bCameraFollow && PreviewSkelMeshComp.IsValid())
{
// if camera isn't lock, make the mesh bounds to be center
FSphere BoundSphere = GetCameraTarget();
// need to interpolate from ViewLocation to Origin
SetCameraTargetLocation(BoundSphere, DeltaSeconds);
}
if (!GIntraFrameDebuggingGameThread)
{
PreviewScene->GetWorld()->Tick(LEVELTICK_All, DeltaSeconds*AnimationPlaybackScale);
}
UDebugSkelMeshComponent* PreviewComp = PreviewSkelMeshComp.Get();
if (PreviewComp)
{
// Handle updating the preview component to represent the effects of root motion
FBoxSphereBounds Bounds = GetAnimPreviewScene()->EditorFloorComp->CalcBounds(GetAnimPreviewScene()->EditorFloorComp->GetRelativeTransform());
PreviewComp->ConsumeRootMotion(Bounds.GetBox().Min, Bounds.GetBox().Max);
}
FindSelectedAnimGraphNode();
}
void FAnimationViewportClient::SetCameraTargetLocation(const FSphere &BoundSphere, float DeltaSeconds)
{
FVector OldViewLoc = GetViewLocation();
FMatrix EpicMat = FTranslationMatrix(-GetViewLocation());
EpicMat = EpicMat * FInverseRotationMatrix(GetViewRotation());
FMatrix CamRotMat = EpicMat.InverseFast();
FVector CamDir = FVector(CamRotMat.M[0][0],CamRotMat.M[0][1],CamRotMat.M[0][2]);
FVector NewViewLocation = BoundSphere.Center - BoundSphere.W * 2 * CamDir;
NewViewLocation.X = FMath::FInterpTo(OldViewLoc.X, NewViewLocation.X, DeltaSeconds, FollowCamera_InterpSpeed);
NewViewLocation.Y = FMath::FInterpTo(OldViewLoc.Y, NewViewLocation.Y, DeltaSeconds, FollowCamera_InterpSpeed);
NewViewLocation.Z = FMath::FInterpTo(OldViewLoc.Z, NewViewLocation.Z, DeltaSeconds, FollowCamera_InterpSpeed_Z);
SetViewLocation( NewViewLocation );
}
void FAnimationViewportClient::ShowBoneNames( FCanvas* Canvas, FSceneView* View )
{
if (!PreviewSkelMeshComp.IsValid() || !PreviewSkelMeshComp->MeshObject)
{
return;
}
//Most of the code taken from FASVViewportClient::Draw() in AnimSetViewerMain.cpp
FSkeletalMeshResource* SkelMeshResource = PreviewSkelMeshComp->GetSkeletalMeshResource();
check(SkelMeshResource);
const int32 LODIndex = FMath::Clamp(PreviewSkelMeshComp->PredictedLODLevel, 0, SkelMeshResource->LODModels.Num()-1);
FStaticLODModel& LODModel = SkelMeshResource->LODModels[ LODIndex ];
const int32 HalfX = Viewport->GetSizeXY().X/2;
const int32 HalfY = Viewport->GetSizeXY().Y/2;
for (int32 i=0; i< LODModel.RequiredBones.Num(); i++)
{
const int32 BoneIndex = LODModel.RequiredBones[i];
// If previewing a specific chunk, only show the bone names that belong to it
if ((PreviewSkelMeshComp->ChunkIndexPreview >= 0) && !LODModel.Chunks[PreviewSkelMeshComp->ChunkIndexPreview].BoneMap.Contains(BoneIndex))
{
continue;
}
const FColor BoneColor = FColor::White;
if (BoneColor.A != 0)
{
const FVector BonePos = PreviewSkelMeshComp->ComponentToWorld.TransformPosition(PreviewSkelMeshComp->GetSpaceBases()[BoneIndex].GetLocation());
const FPlane proj = View->Project(BonePos);
if (proj.W > 0.f)
{
const int32 XPos = HalfX + ( HalfX * proj.X );
const int32 YPos = HalfY + ( HalfY * (proj.Y * -1) );
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
const FString BoneString = FString::Printf( TEXT("%d: %s"), BoneIndex, *BoneName.ToString() );
FCanvasTextItem TextItem( FVector2D( XPos, YPos), FText::FromString( BoneString ), GEngine->GetSmallFont(), BoneColor );
TextItem.EnableShadow(FLinearColor::Black);
Canvas->DrawItem( TextItem );
}
}
}
}
void FAnimationViewportClient::DisplayInfo(FCanvas* Canvas, FSceneView* View, bool bDisplayAllInfo)
{
int32 CurXOffset = 5;
int32 CurYOffset = 60;
int32 XL, YL;
StringSize( GEngine->GetSmallFont(), XL, YL, TEXT("L") );
FString InfoString;
// it is weird, but unless it's completely black, it's too bright, so just making it white if only black
const FLinearColor TextColor = (SelectedHSVColor.B < 0.3f) ? FLinearColor::White : FLinearColor::Black;
// if not valid skeletalmesh
if (!PreviewSkelMeshComp.IsValid() || !PreviewSkelMeshComp->SkeletalMesh)
{
return;
}
if (PreviewSkelMeshComp->SkeletalMesh->MorphTargets.Num() > 0)
{
FColor HeadlineColour(255,83,0);
FColor SubHeadlineColour(202,66,0);
int32 SubHeadingIndent = CurXOffset + 10;
TArray<UMaterial*> ProcessedMaterials;
TArray<UMaterial*> MaterialsThatNeedMorphFlagOn;
TArray<UMaterial*> MaterialsThatNeedSaving;
for (int i = 0; i < PreviewSkelMeshComp->GetNumMaterials(); ++i)
{
if (UMaterialInterface* MaterialInterface = PreviewSkelMeshComp->GetMaterial(i))
{
UMaterial* Material = MaterialInterface->GetMaterial();
if ((Material != nullptr) && !ProcessedMaterials.Contains(Material))
{
ProcessedMaterials.Add(Material);
if (!Material->GetUsageByFlag(MATUSAGE_MorphTargets))
{
MaterialsThatNeedMorphFlagOn.Add(Material);
}
else if (Material->IsUsageFlagDirty(MATUSAGE_MorphTargets))
{
MaterialsThatNeedSaving.Add(Material);
}
}
}
}
if (MaterialsThatNeedMorphFlagOn.Num() > 0)
{
InfoString = FString::Printf( *LOCTEXT("MorphSupportNeeded", "The following materials need morph support ('Used with Morph Targets' in material editor):").ToString() );
Canvas->DrawShadowedString( CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), HeadlineColour );
CurYOffset += YL + 2;
for(auto Iter = MaterialsThatNeedMorphFlagOn.CreateIterator(); Iter; ++Iter)
{
UMaterial* Material = (*Iter);
InfoString = FString::Printf(TEXT("%s"), *Material->GetPathName());
Canvas->DrawShadowedString( SubHeadingIndent, CurYOffset, *InfoString, GEngine->GetSmallFont(), SubHeadlineColour );
CurYOffset += YL + 2;
}
CurYOffset += 2;
}
if (MaterialsThatNeedSaving.Num() > 0)
{
InfoString = FString::Printf( *LOCTEXT("MaterialsNeedSaving", "The following materials need saving to fully support morph targets:").ToString() );
Canvas->DrawShadowedString( CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), HeadlineColour );
CurYOffset += YL + 2;
for(auto Iter = MaterialsThatNeedSaving.CreateIterator(); Iter; ++Iter)
{
UMaterial* Material = (*Iter);
InfoString = FString::Printf(TEXT("%s"), *Material->GetPathName());
Canvas->DrawShadowedString( SubHeadingIndent, CurYOffset, *InfoString, GEngine->GetSmallFont(), SubHeadlineColour );
CurYOffset += YL + 2;
}
CurYOffset += 2;
}
}
UAnimPreviewInstance* PreviewInstance = PreviewSkelMeshComp->PreviewInstance;
if( PreviewInstance )
{
// see if you have anim sequence that has transform curves
UAnimSequence* Sequence = Cast<UAnimSequence>(PreviewInstance->CurrentAsset);
if ( Sequence && Sequence->DoesNeedRebake() )
{
FColor SubHeadlineColour(202, 66, 0);
InfoString = TEXT("Animation is being edited. To apply to raw animation data, click \"Apply\"");
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), SubHeadlineColour);
CurYOffset += YL + 2;
}
}
if (PreviewSkelMeshComp->IsUsingInGameBounds())
{
if (!PreviewSkelMeshComp->CheckIfBoundsAreCorrrect())
{
if( PreviewSkelMeshComp->GetPhysicsAsset() == NULL )
{
InfoString = FString::Printf( *LOCTEXT("NeedToSetupPhysicsAssetForAccurateBounds", "You may need to setup Physics Asset to use more accurate bounds").ToString() );
}
else
{
InfoString = FString::Printf( *LOCTEXT("NeedToSetupBoundsInPhysicsAsset", "You need to setup bounds in Physics Asset to include whole mesh").ToString() );
}
Canvas->DrawShadowedString( CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor );
CurYOffset += YL + 2;
}
}
if (PreviewSkelMeshComp != NULL && PreviewSkelMeshComp->MeshObject != NULL)
{
if (bDisplayAllInfo)
{
FSkeletalMeshResource* SkelMeshResource = PreviewSkelMeshComp->GetSkeletalMeshResource();
check(SkelMeshResource);
// Draw stats about the mesh
const FBoxSphereBounds& SkelBounds = PreviewSkelMeshComp->Bounds;
const FPlane ScreenPosition = View->Project(SkelBounds.Origin);
const int32 HalfX = Viewport->GetSizeXY().X / 2;
const int32 HalfY = Viewport->GetSizeXY().Y / 2;
const float ScreenRadius = FMath::Max((float)HalfX * View->ViewMatrices.ProjMatrix.M[0][0], (float)HalfY * View->ViewMatrices.ProjMatrix.M[1][1]) * SkelBounds.SphereRadius / FMath::Max(ScreenPosition.W, 1.0f);
const float LODFactor = ScreenRadius / 320.0f;
int32 NumBonesInUse;
int32 NumBonesMappedToVerts;
int32 NumChunksInUse;
int32 NumSectionsInUse;
FString WeightUsage;
const int32 LODIndex = FMath::Clamp(PreviewSkelMeshComp->PredictedLODLevel, 0, SkelMeshResource->LODModels.Num() - 1);
FStaticLODModel& LODModel = SkelMeshResource->LODModels[LODIndex];
NumBonesInUse = LODModel.RequiredBones.Num();
NumBonesMappedToVerts = LODModel.ActiveBoneIndices.Num();
NumChunksInUse = LODModel.Chunks.Num();
NumSectionsInUse = LODModel.Sections.Num();
InfoString = FString::Printf(TEXT("LOD: %d, Bones: %d (Mapped to Vertices: %d), Polys: %d"),
LODIndex,
NumBonesInUse,
NumBonesMappedToVerts,
LODModel.GetTotalFaces());
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
InfoString = FString::Printf(TEXT("Current Screen Size: %3.2f, FOV:%3.0f"), LODFactor, ViewFOV);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
CurYOffset += 1; // --
uint32 TotalRigidVertices = 0;
uint32 TotalSoftVertices = 0;
for (int32 ChunkIndex = 0; ChunkIndex < LODModel.Chunks.Num(); ChunkIndex++)
{
int32 ChunkRigidVerts = LODModel.Chunks[ChunkIndex].GetNumRigidVertices();
int32 ChunkSoftVerts = LODModel.Chunks[ChunkIndex].GetNumSoftVertices();
InfoString = FString::Printf(TEXT(" [Chunk %d] Verts:%d (Rigid:%d Soft:%d), Bones:%d"),
ChunkIndex,
ChunkRigidVerts + ChunkSoftVerts,
ChunkRigidVerts,
ChunkSoftVerts,
LODModel.Chunks[ChunkIndex].BoneMap.Num()
);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor*0.8f);
TotalRigidVertices += ChunkRigidVerts;
TotalSoftVertices += ChunkSoftVerts;
}
InfoString = FString::Printf(TEXT("TOTAL Verts:%d (Rigid:%d Soft:%d)"),
LODModel.NumVertices,
TotalRigidVertices,
TotalSoftVertices);
CurYOffset += 1; // --
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
InfoString = FString::Printf(TEXT("Chunks:%d, Sections:%d %s"),
NumChunksInUse,
NumSectionsInUse,
*WeightUsage
);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
if (PreviewSkelMeshComp->BonesOfInterest.Num() > 0)
{
int32 BoneIndex = PreviewSkelMeshComp->BonesOfInterest[0];
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
FTransform ReferenceTransform = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetRefBonePose()[BoneIndex];
FTransform LocalTransform = PreviewSkelMeshComp->LocalAtoms[BoneIndex];
FTransform ComponentTransform = PreviewSkelMeshComp->GetSpaceBases()[BoneIndex];
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Local :%s"), *LocalTransform.ToString());
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Component :%s"), *ComponentTransform.ToString());
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Reference :%s"), *ReferenceTransform.ToString());
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
}
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Approximate Size: %ix%ix%i"),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.X * 2.0f),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.Y * 2.0f),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.Z * 2.0f));
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
uint32 NumNotiesWithErrors = PreviewSkelMeshComp->AnimNotifyErrors.Num();
for (uint32 i = 0; i < NumNotiesWithErrors; ++i)
{
uint32 NumErrors = PreviewSkelMeshComp->AnimNotifyErrors[i].Errors.Num();
for (uint32 ErrorIdx = 0; ErrorIdx < NumErrors; ++ErrorIdx)
{
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *PreviewSkelMeshComp->AnimNotifyErrors[i].Errors[ErrorIdx], GEngine->GetSmallFont(), FLinearColor(1.0f, 0.0f, 0.0f, 1.0f));
}
}
}
else // simplified default display info to be same as static mesh editor
{
FSkeletalMeshResource* SkelMeshResource = PreviewSkelMeshComp->GetSkeletalMeshResource();
check(SkelMeshResource);
const int32 LODIndex = FMath::Clamp(PreviewSkelMeshComp->PredictedLODLevel, 0, SkelMeshResource->LODModels.Num() - 1);
FStaticLODModel& LODModel = SkelMeshResource->LODModels[LODIndex];
// Current LOD
InfoString = FString::Printf(TEXT("LOD: %d"), LODIndex);
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// current screen size
const FBoxSphereBounds& SkelBounds = PreviewSkelMeshComp->Bounds;
const FPlane ScreenPosition = View->Project(SkelBounds.Origin);
const int32 HalfX = Viewport->GetSizeXY().X / 2;
const int32 HalfY = Viewport->GetSizeXY().Y / 2;
const float ScreenRadius = FMath::Max((float)HalfX * View->ViewMatrices.ProjMatrix.M[0][0], (float)HalfY * View->ViewMatrices.ProjMatrix.M[1][1]) * SkelBounds.SphereRadius / FMath::Max(ScreenPosition.W, 1.0f);
const float LODFactor = ScreenRadius / 320.0f;
float ScreenSize = ComputeBoundsScreenSize(ScreenPosition, SkelBounds.SphereRadius, *View);
InfoString = FString::Printf(TEXT("Current Screen Size: %3.2f"), LODFactor);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// Triangles
uint32 NumTotalTriangles = 0;
int32 NumSections = LODModel.NumNonClothingSections();
for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
{
NumTotalTriangles += LODModel.Sections[SectionIndex].NumTriangles;
}
InfoString = FString::Printf(TEXT("Triangles: %d"), NumTotalTriangles);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// Vertices
InfoString = FString::Printf(TEXT("Vertices: %d"), LODModel.NumVertices);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// UV Channels
InfoString = FString::Printf(TEXT("UV Channels: %d"), LODModel.NumTexCoords);
CurYOffset += YL + 2;
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
// Approx Size
CurYOffset += YL + 2;
InfoString = FString::Printf(TEXT("Approx Size: %ix%ix%i"),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.X * 2.0f),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.Y * 2.0f),
FMath::RoundToInt(PreviewSkelMeshComp->Bounds.BoxExtent.Z * 2.0f));
Canvas->DrawShadowedString(CurXOffset, CurYOffset, *InfoString, GEngine->GetSmallFont(), TextColor);
}
}
}
int32 FAnimationViewportClient::FindSelectedBone() const
{
int32 SelectedBone = INDEX_NONE;
if (SelectedSkelControlAnimGraph.IsValid())
{
FName BoneName = SelectedSkelControlAnimGraph->FindSelectedBone();
SelectedBone = PreviewSkelMeshComp->GetBoneIndex(BoneName);
}
if (SelectedBone == INDEX_NONE && PreviewSkelMeshComp.IsValid() && (PreviewSkelMeshComp->BonesOfInterest.Num() == 1) )
{
SelectedBone = PreviewSkelMeshComp->BonesOfInterest[0];
}
return SelectedBone;
}
USkeletalMeshSocket* FAnimationViewportClient::FindSelectedSocket() const
{
USkeletalMeshSocket* SelectedSocket = NULL;
if (PreviewSkelMeshComp.IsValid() && (PreviewSkelMeshComp->SocketsOfInterest.Num() == 1) )
{
SelectedSocket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
}
return SelectedSocket;
}
TWeakObjectPtr<AWindDirectionalSource> FAnimationViewportClient::FindSelectedWindActor() const
{
return SelectedWindActor;
}
void FAnimationViewportClient::ProcessClick(class FSceneView& View, class HHitProxy* HitProxy, FKey Key, EInputEvent Event, uint32 HitX, uint32 HitY)
{
TSharedPtr<FPersona> SharedPersona = PersonaPtr.Pin();
if(!SharedPersona.IsValid())
{
// No reason to continue, cannot select elements
return;
}
if ( HitProxy )
{
if ( HitProxy->IsA( HPersonaSocketProxy::StaticGetType() ) )
{
HPersonaSocketProxy* SocketProxy = ( HPersonaSocketProxy* )HitProxy;
// Tell Persona that the socket has been selected - this will sort out the skeleton tree, etc.
SharedPersona->SetSelectedSocket( SocketProxy->SocketInfo );
}
else if ( HitProxy->IsA( HPersonaBoneProxy::StaticGetType() ) )
{
HPersonaBoneProxy* BoneProxy = ( HPersonaBoneProxy* )HitProxy;
// Tell Persona that the bone has been selected - this will sort out the skeleton tree, etc.
SharedPersona->SetSelectedBone( PreviewSkelMeshComp.Get()->SkeletalMesh->Skeleton, BoneProxy->BoneName );
}
else if ( HitProxy->IsA( HActor::StaticGetType() ) )
{
HActor* ActorHitProxy = static_cast<HActor*>(HitProxy);
AWindDirectionalSource* WindActor = Cast<AWindDirectionalSource>(ActorHitProxy->Actor);
if( WindActor )
{
//clear previously selected things
SharedPersona->DeselectAll();
SelectedWindActor = WindActor;
}
else if (SelectedSkelControlAnimGraph.IsValid() && SelectedSkelControlAnimGraph->IsActorClicked(ActorHitProxy))
{
// do some actions when hit
SelectedSkelControlAnimGraph->ProcessActorClick(ActorHitProxy);
}
}
}
else
{
// Cast for phys bodies if we didn't get any hit proxies
FHitResult Result(1.0f);
const FViewportClick Click(&View, this, EKeys::Invalid, IE_Released, Viewport->GetMouseX(), Viewport->GetMouseY());
bool bHit = PreviewSkelMeshComp.Get()->LineTraceComponent(Result, Click.GetOrigin(), Click.GetOrigin() + Click.GetDirection() * BodyTraceDistance, FCollisionQueryParams(true));
if(bHit)
{
SharedPersona->SetSelectedBone(PreviewSkelMeshComp->SkeletalMesh->Skeleton, Result.BoneName);
}
else
{
// We didn't hit a proxy or a physics object, so deselect all objects
SharedPersona->DeselectAll();
}
}
}
bool FAnimationViewportClient::InputWidgetDelta( FViewport* Viewport, EAxisList::Type CurrentAxis, FVector& Drag, FRotator& Rot, FVector& Scale )
{
// Get some useful info about buttons being held down
const bool bCtrlDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl);
const bool bShiftDown = Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift);
const bool bMouseButtonDown = Viewport->KeyState( EKeys::LeftMouseButton ) || Viewport->KeyState( EKeys::MiddleMouseButton ) || Viewport->KeyState( EKeys::RightMouseButton );
bool bHandled = false;
if ( bManipulating && CurrentAxis != EAxisList::None )
{
bHandled = true;
int32 BoneIndex = FindSelectedBone();
USkeletalMeshSocket* SelectedSocket = FindSelectedSocket();
TWeakObjectPtr<AWindDirectionalSource> WindActor = FindSelectedWindActor();
FAnimNode_ModifyBone* SkelControl = NULL;
if ( BoneIndex >= 0 )
{
//Get the skeleton control manipulating this bone
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
SkelControl = &(PreviewSkelMeshComp->PreviewInstance->ModifyBone(BoneName));
}
if ( SkelControl || SelectedSocket )
{
FTransform CurrentSkelControlTM(
SelectedSocket ? SelectedSocket->RelativeRotation : SkelControl->Rotation,
SelectedSocket ? SelectedSocket->RelativeLocation : SkelControl->Translation,
SelectedSocket ? SelectedSocket->RelativeScale : SkelControl->Scale);
FTransform BaseTM;
if ( SelectedSocket )
{
BaseTM = SelectedSocket->GetSocketTransform( PreviewSkelMeshComp.Get() );
}
else
{
BaseTM = PreviewSkelMeshComp->GetBoneTransform( BoneIndex );
}
// Remove SkelControl's orientation from BoneMatrix, as we need to translate/rotate in the non-SkelControlled space
BaseTM = BaseTM.GetRelativeTransformReverse(CurrentSkelControlTM);
const bool bDoRotation = WidgetMode == FWidget::WM_Rotate || WidgetMode == FWidget::WM_TranslateRotateZ;
const bool bDoTranslation = WidgetMode == FWidget::WM_Translate || WidgetMode == FWidget::WM_TranslateRotateZ;
const bool bDoScale = WidgetMode == FWidget::WM_Scale;
if (bDoRotation)
{
FVector RotAxis;
float RotAngle;
Rot.Quaternion().ToAxisAndAngle( RotAxis, RotAngle );
FVector4 BoneSpaceAxis = BaseTM.TransformVector( RotAxis );
//Calculate the new delta rotation
FQuat DeltaQuat( BoneSpaceAxis, RotAngle );
DeltaQuat.Normalize();
FRotator NewRotation = ( CurrentSkelControlTM * FTransform( DeltaQuat )).Rotator();
if (SelectedSkelControlAnimGraph.IsValid())
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(SelectedSkelControlAnimGraph);
if (AnimNode)
{
SelectedSkelControlAnimGraph->DoRotation(PreviewSkelMeshComp.Get(), Rot, AnimNode);
bShouldUpdateDefaultValues = true;
}
}
else if ( SelectedSocket )
{
SelectedSocket->RelativeRotation = NewRotation;
}
else
{
SkelControl->Rotation = NewRotation;
}
}
if (bDoTranslation)
{
FVector4 BoneSpaceOffset = BaseTM.TransformVector(Drag);
if (SelectedSkelControlAnimGraph.IsValid())
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(SelectedSkelControlAnimGraph);
if (AnimNode)
{
SelectedSkelControlAnimGraph->DoTranslation(PreviewSkelMeshComp.Get(), Drag, AnimNode);
bShouldUpdateDefaultValues = true;
}
}
else if (SelectedSocket)
{
SelectedSocket->RelativeLocation += BoneSpaceOffset;
}
else
{
SkelControl->Translation += BoneSpaceOffset;
}
}
if(bDoScale)
{
FVector4 BoneSpaceScaleOffset;
if (ModeTools->GetCoordSystem() == COORD_World)
{
BoneSpaceScaleOffset = BaseTM.TransformVector(Scale);
}
else
{
BoneSpaceScaleOffset = Scale;
}
if (SelectedSkelControlAnimGraph.IsValid())
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(SelectedSkelControlAnimGraph);
if (AnimNode)
{
SelectedSkelControlAnimGraph->DoScale(PreviewSkelMeshComp.Get(), Scale, AnimNode);
bShouldUpdateDefaultValues = true;
}
}
else if(SelectedSocket)
{
SelectedSocket->RelativeScale += BoneSpaceScaleOffset;
}
else
{
SkelControl->Scale += BoneSpaceScaleOffset;
}
}
}
else if( WindActor.IsValid() )
{
if (WidgetMode == FWidget::WM_Rotate)
{
FTransform WindTransform = WindActor->GetTransform();
FRotator NewRotation = ( WindTransform * FTransform( Rot ) ).Rotator();
WindActor->SetActorRotation( NewRotation );
}
else
{
FVector Location = WindActor->GetActorLocation();
Location += Drag;
WindActor->SetActorLocation(Location);
}
}
Viewport->Invalidate();
}
return bHandled;
}
void FAnimationViewportClient::TrackingStarted( const struct FInputEventState& InInputState, bool bIsDraggingWidget, bool bNudge )
{
int32 BoneIndex = FindSelectedBone();
USkeletalMeshSocket* SelectedSocket = FindSelectedSocket();
TWeakObjectPtr<AWindDirectionalSource> WindActor = FindSelectedWindActor();
if( (BoneIndex >= 0 || SelectedSocket || WindActor.IsValid()) && bIsDraggingWidget )
{
bool bValidAxis = false;
FVector WorldAxisDir;
if(InInputState.IsLeftMouseButtonPressed() && (Widget->GetCurrentAxis() & EAxisList::XYZ) != 0)
{
if ( PreviewSkelMeshComp->SocketsOfInterest.Num() == 1 )
{
const bool bAltDown = InInputState.IsAltButtonPressed();
if ( bAltDown && PersonaPtr.IsValid() )
{
// Rather than moving/rotating the selected socket, copy it and move the copy instead
PersonaPtr.Pin()->DuplicateAndSelectSocket( PreviewSkelMeshComp->SocketsOfInterest[0] );
}
// Socket movement is transactional - we want undo/redo and saving of it
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
if ( Socket && bInTransaction == false )
{
if (WidgetMode == FWidget::WM_Rotate )
{
GEditor->BeginTransaction( LOCTEXT("AnimationEditorViewport_RotateSocket", "Rotate Socket" ) );
}
else
{
GEditor->BeginTransaction( LOCTEXT("AnimationEditorViewport_TranslateSocket", "Translate Socket" ) );
}
Socket->SetFlags( RF_Transactional ); // Undo doesn't work without this!
Socket->Modify();
bInTransaction = true;
}
}
else if( BoneIndex >= 0 )
{
if ( bInTransaction == false )
{
// we also allow undo/redo of bone manipulations
if (WidgetMode == FWidget::WM_Rotate )
{
GEditor->BeginTransaction( LOCTEXT("AnimationEditorViewport_RotateBone", "Rotate Bone" ) );
}
else
{
GEditor->BeginTransaction( LOCTEXT("AnimationEditorViewport_TranslateBone", "Translate Bone" ) );
}
PreviewSkelMeshComp->PreviewInstance->SetFlags( RF_Transactional ); // Undo doesn't work without this!
PreviewSkelMeshComp->PreviewInstance->Modify();
bInTransaction = true;
// now modify the bone array
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
PreviewSkelMeshComp->PreviewInstance->ModifyBone(BoneName);
}
}
}
bManipulating = true;
}
}
void FAnimationViewportClient::TrackingStopped()
{
if (bManipulating)
{
// Socket movement is transactional - we want undo/redo and saving of it
if ( bInTransaction )
{
GEditor->EndTransaction();
bInTransaction = false;
}
bManipulating = false;
}
Invalidate();
}
FWidget::EWidgetMode FAnimationViewportClient::GetWidgetMode() const
{
FWidget::EWidgetMode Mode = FWidget::WM_None;
if (!PreviewSkelMeshComp->IsAnimBlueprintInstanced())
{
if ((PreviewSkelMeshComp != NULL) && ((PreviewSkelMeshComp->BonesOfInterest.Num() == 1) || (PreviewSkelMeshComp->SocketsOfInterest.Num() == 1)))
{
Mode = WidgetMode;
}
else if (SelectedWindActor.IsValid())
{
Mode = WidgetMode;
}
}
if (SelectedSkelControlAnimGraph.IsValid())
{
Mode = (FWidget::EWidgetMode)SelectedSkelControlAnimGraph->GetWidgetMode(PreviewSkelMeshComp.Get());
}
return Mode;
}
FVector FAnimationViewportClient::GetWidgetLocation() const
{
if (SelectedSkelControlAnimGraph.IsValid())
{
FAnimNode_SkeletalControlBase* AnimNode = FindSkeletalControlAnimNode(SelectedSkelControlAnimGraph.Get());
if (AnimNode)
{
return SelectedSkelControlAnimGraph->GetWidgetLocation(PreviewSkelMeshComp.Get(), AnimNode);
}
}
else if( PreviewSkelMeshComp->BonesOfInterest.Num() > 0 )
{
int32 BoneIndex = PreviewSkelMeshComp->BonesOfInterest.Last();
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex);
FMatrix BoneMatrix = PreviewSkelMeshComp->GetBoneMatrix(BoneIndex);
return BoneMatrix.GetOrigin();
}
else if( PreviewSkelMeshComp->SocketsOfInterest.Num() > 0 )
{
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest.Last().Socket;
FMatrix SocketMatrix;
Socket->GetSocketMatrix( SocketMatrix, PreviewSkelMeshComp.Get() );
return SocketMatrix.GetOrigin();
}
else if( SelectedWindActor.IsValid() )
{
return SelectedWindActor.Get()->GetActorLocation();
}
return FVector::ZeroVector;
}
FMatrix FAnimationViewportClient::GetWidgetCoordSystem() const
{
const bool bIsLocal = GetWidgetCoordSystemSpace() == COORD_Local;
if( bIsLocal )
{
if (SelectedSkelControlAnimGraph.IsValid())
{
FName BoneName = SelectedSkelControlAnimGraph->FindSelectedBone();
int32 BoneIndex = PreviewSkelMeshComp->GetBoneIndex(BoneName);
if (BoneIndex != INDEX_NONE)
{
FTransform BoneMatrix = PreviewSkelMeshComp->GetBoneTransform(BoneIndex);
return BoneMatrix.ToMatrixNoScale().RemoveTranslation();
}
}
else if ( PreviewSkelMeshComp->BonesOfInterest.Num() > 0 )
{
int32 BoneIndex = PreviewSkelMeshComp->BonesOfInterest.Last();
FTransform BoneMatrix = PreviewSkelMeshComp->GetBoneTransform(BoneIndex);
return BoneMatrix.ToMatrixNoScale().RemoveTranslation();
}
else if( PreviewSkelMeshComp->SocketsOfInterest.Num() > 0 )
{
USkeletalMeshSocket* Socket = PreviewSkelMeshComp->SocketsOfInterest.Last().Socket;
FMatrix SocketMatrix;
Socket->GetSocketMatrix( SocketMatrix, PreviewSkelMeshComp.Get() );
return SocketMatrix.RemoveTranslation();
}
else if ( SelectedWindActor.IsValid() )
{
return SelectedWindActor->GetTransform().ToMatrixNoScale().RemoveTranslation();
}
}
return FMatrix::Identity;
}
ECoordSystem FAnimationViewportClient::GetWidgetCoordSystemSpace() const
{
return ModeTools->GetCoordSystem();
}
void FAnimationViewportClient::SetWidgetCoordSystemSpace(ECoordSystem NewCoordSystem)
{
ModeTools->SetCoordSystem(NewCoordSystem);
Invalidate();
}
void FAnimationViewportClient::SetViewMode(EViewModeIndex InViewModeIndex)
{
FEditorViewportClient::SetViewMode(InViewModeIndex);
ConfigOption->SetViewModeIndex(InViewModeIndex);
}
void FAnimationViewportClient::SetViewportType(ELevelViewportType InViewportType)
{
FEditorViewportClient::SetViewportType(InViewportType);
FocusViewportOnPreviewMesh();
}
bool FAnimationViewportClient::InputKey( FViewport* Viewport, int32 ControllerId, FKey Key, EInputEvent Event, float AmountDepressed, bool bGamepad )
{
const int32 HitX = Viewport->GetMouseX();
const int32 HitY = Viewport->GetMouseY();
bool bHandled = false;
// Handle switching modes - only allowed when not already manipulating
if ( (Event == IE_Pressed) && (Key == EKeys::SpaceBar) && !bManipulating )
{
int32 SelectedBone = FindSelectedBone();
USkeletalMeshSocket* SelectedSocket = FindSelectedSocket();
TWeakObjectPtr<AWindDirectionalSource> SelectedWind = FindSelectedWindActor();
if (SelectedBone >= 0 || SelectedSocket || SelectedWind.IsValid())
{
if (SelectedSkelControlAnimGraph.IsValid())
{
WidgetMode = (FWidget::EWidgetMode)SelectedSkelControlAnimGraph->ChangeToNextWidgetMode(PreviewSkelMeshComp.Get(), WidgetMode);
if (WidgetMode == FWidget::WM_Scale)
{
SetWidgetCoordSystemSpace(COORD_Local);
}
else
{
SetWidgetCoordSystemSpace(COORD_World);
}
}
else
if (WidgetMode == FWidget::WM_Rotate)
{
WidgetMode = FWidget::WM_Scale;
}
else if (WidgetMode == FWidget::WM_Scale)
{
WidgetMode = FWidget::WM_Translate;
}
else
{
WidgetMode = FWidget::WM_Rotate;
}
//@TODO: ANIMATION: Add scaling support
}
bHandled = true;
Invalidate();
}
if( (Event == IE_Pressed) && (Key == EKeys::F) )
{
bHandled = true;
FocusViewportOnPreviewMesh();
}
// Pass keys to standard controls, if we didn't consume input
return (bHandled)
? true
: FEditorViewportClient::InputKey(Viewport, ControllerId, Key, Event, AmountDepressed, bGamepad);
}
void FAnimationViewportClient::SetWidgetMode(FWidget::EWidgetMode InMode)
{
bool bAnimBPMode = PersonaPtr.Pin()->IsModeCurrent(FPersonaModes::AnimBlueprintEditMode);
// support WER keys to change gizmo mode for Bone Controller preivew in anim blueprint
if (bAnimBPMode)
{
if (!bManipulating)
{
int32 SelectedBone = FindSelectedBone();
if (SelectedBone >= 0 && SelectedSkelControlAnimGraph.IsValid())
{
if (SelectedSkelControlAnimGraph->SetWidgetMode(PreviewSkelMeshComp.Get(), InMode))
{
WidgetMode = InMode;
}
}
}
}
else
{
WidgetMode = InMode;
}
// Force viewport to redraw
Viewport->Invalidate();
}
bool FAnimationViewportClient::CanSetWidgetMode(FWidget::EWidgetMode NewMode) const
{
return true;
}
void FAnimationViewportClient::SetLocalAxesMode(ELocalAxesMode::Type AxesMode)
{
LocalAxesMode = AxesMode;
ConfigOption->SetDefaultLocalAxesSelection( AxesMode );
}
bool FAnimationViewportClient::IsLocalAxesModeSet( ELocalAxesMode::Type AxesMode ) const
{
return LocalAxesMode == AxesMode;
}
void FAnimationViewportClient::DrawBonesFromTransforms(TArray<FTransform>& Transforms, UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI, FLinearColor BoneColour, FLinearColor RootBoneColour) const
{
if ( Transforms.Num() > 0 )
{
TArray<FTransform> WorldTransforms;
WorldTransforms.AddUninitialized(Transforms.Num());
TArray<FLinearColor> BoneColours;
BoneColours.AddUninitialized(Transforms.Num());
// we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this
for ( int32 Index=0; Index < MeshComponent->RequiredBones.Num(); ++Index )
{
const int32 BoneIndex = MeshComponent->RequiredBones[Index];
const int32 ParentIndex = MeshComponent->SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex);
WorldTransforms[BoneIndex] = Transforms[BoneIndex] * MeshComponent->ComponentToWorld;
BoneColours[BoneIndex] = (ParentIndex >= 0) ? BoneColour : RootBoneColour;
}
DrawBones(MeshComponent, MeshComponent->RequiredBones, WorldTransforms, PDI, BoneColours);
}
}
void FAnimationViewportClient::DrawMeshBonesUncompressedAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if ( MeshComponent && MeshComponent->SkeletalMesh )
{
DrawBonesFromTransforms(MeshComponent->UncompressedSpaceBases, MeshComponent, PDI, FColor(255, 127, 39, 255), FColor(255, 127, 39, 255));
}
}
void FAnimationViewportClient::DrawMeshBonesNonRetargetedAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if ( MeshComponent && MeshComponent->SkeletalMesh )
{
DrawBonesFromTransforms(MeshComponent->NonRetargetedSpaceBases, MeshComponent, PDI, FColor(159, 159, 39, 255), FColor(159, 159, 39, 255));
}
}
void FAnimationViewportClient::DrawMeshBonesAdditiveBasePose(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if ( MeshComponent && MeshComponent->SkeletalMesh )
{
DrawBonesFromTransforms(MeshComponent->AdditiveBasePoses, MeshComponent, PDI, FColor(0, 159, 0, 255), FColor(0, 159, 0, 255));
}
}
void FAnimationViewportClient::DrawMeshBonesSourceRawAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if(MeshComponent && MeshComponent->SkeletalMesh)
{
DrawBonesFromTransforms(MeshComponent->SourceAnimationPoses, MeshComponent, PDI, FColor(195, 195, 195, 255), FColor(195, 159, 195, 255));
}
}
void FAnimationViewportClient::DrawMeshBonesBakedAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if(MeshComponent && MeshComponent->SkeletalMesh)
{
DrawBonesFromTransforms(MeshComponent->BakedAnimationPoses, MeshComponent, PDI, FColor(0, 128, 192, 255), FColor(0, 128, 192, 255));
}
}
void FAnimationViewportClient::DrawMeshBones(USkeletalMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const
{
if ( MeshComponent && MeshComponent->SkeletalMesh )
{
TArray<FTransform> WorldTransforms;
WorldTransforms.AddUninitialized(MeshComponent->GetNumSpaceBases());
TArray<FLinearColor> BoneColours;
BoneColours.AddUninitialized(MeshComponent->GetNumSpaceBases());
TArray<int32> SelectedBones;
if(UDebugSkelMeshComponent* DebugMeshComponent = Cast<UDebugSkelMeshComponent>(MeshComponent))
{
SelectedBones = DebugMeshComponent->BonesOfInterest;
}
// we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this
for ( int32 Index=0; Index<MeshComponent->RequiredBones.Num(); ++Index )
{
const int32 BoneIndex = MeshComponent->RequiredBones[Index];
const int32 ParentIndex = MeshComponent->SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex);
WorldTransforms[BoneIndex] = MeshComponent->GetSpaceBases()[BoneIndex]*MeshComponent->ComponentToWorld;
if(SelectedBones.Contains(BoneIndex))
{
BoneColours[BoneIndex] = FLinearColor(1.0f, 0.34f, 0.0f, 1.0f);
}
else
{
BoneColours[BoneIndex] = (ParentIndex >= 0) ? FLinearColor::White : FLinearColor::Red;
}
}
DrawBones(MeshComponent, MeshComponent->RequiredBones, WorldTransforms, PDI, BoneColours);
}
}
void FAnimationViewportClient::DrawBones(const USkeletalMeshComponent* MeshComponent, const TArray<FBoneIndexType> & RequiredBones, const TArray<FTransform> & WorldTransforms, FPrimitiveDrawInterface* PDI, const TArray<FLinearColor> BoneColours, float LineThickness/*=0.f*/) const
{
check ( MeshComponent && MeshComponent->SkeletalMesh );
TArray<int32> SelectedBones;
if(const UDebugSkelMeshComponent* DebugMeshComponent = Cast<const UDebugSkelMeshComponent>(MeshComponent))
{
SelectedBones = DebugMeshComponent->BonesOfInterest;
// we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this
for ( int32 Index=0; Index<RequiredBones.Num(); ++Index )
{
const int32 BoneIndex = RequiredBones[Index];
const int32 ParentIndex = MeshComponent->SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex);
FVector Start, End;
FLinearColor LineColor = BoneColours[BoneIndex];
if (ParentIndex >=0)
{
Start = WorldTransforms[ParentIndex].GetLocation();
End = WorldTransforms[BoneIndex].GetLocation();
}
else
{
Start = FVector::ZeroVector;
End = WorldTransforms[BoneIndex].GetLocation();
}
static const float SphereRadius = 1.0f;
TArray<FVector> Verts;
//Calc cone size
FVector EndToStart = (Start-End);
float ConeLength = EndToStart.Size();
float Angle = FMath::RadiansToDegrees(FMath::Atan(SphereRadius / ConeLength));
//Render Sphere for bone end point and a cone between it and its parent.
PDI->SetHitProxy( new HPersonaBoneProxy( MeshComponent->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex)) );
DrawWireSphere(PDI, End, LineColor, SphereRadius, 10, SDPG_Foreground);
DrawWireCone(PDI, Verts, FRotationMatrix::MakeFromX(EndToStart)*FTranslationMatrix(End), ConeLength, Angle, 4, LineColor, SDPG_Foreground);
PDI->SetHitProxy( NULL );
// draw gizmo
if( (LocalAxesMode == ELocalAxesMode::All) ||
((LocalAxesMode == ELocalAxesMode::Selected) && SelectedBones.Contains(BoneIndex))
)
{
RenderGizmo(WorldTransforms[BoneIndex], PDI);
}
}
}
}
void FAnimationViewportClient::RenderGizmo(const FTransform& Transform, FPrimitiveDrawInterface* PDI) const
{
// Display colored coordinate system axes for this joint.
const float AxisLength = 3.75f;
const float LineThickness = 0.3f;
const FVector Origin = Transform.GetLocation();
// Red = X
FVector XAxis = Transform.TransformVector( FVector(1.0f,0.0f,0.0f) );
XAxis.Normalize();
PDI->DrawLine( Origin, Origin + XAxis * AxisLength, FColor( 255, 80, 80),SDPG_Foreground, LineThickness);
// Green = Y
FVector YAxis = Transform.TransformVector( FVector(0.0f,1.0f,0.0f) );
YAxis.Normalize();
PDI->DrawLine( Origin, Origin + YAxis * AxisLength, FColor( 80, 255, 80),SDPG_Foreground, LineThickness);
// Blue = Z
FVector ZAxis = Transform.TransformVector( FVector(0.0f,0.0f,1.0f) );
ZAxis.Normalize();
PDI->DrawLine( Origin, Origin + ZAxis * AxisLength, FColor( 80, 80, 255),SDPG_Foreground, LineThickness);
}
void FAnimationViewportClient::DrawMeshSubsetBones(const USkeletalMeshComponent* MeshComponent, const TArray<int32>& BonesOfInterest, FPrimitiveDrawInterface* PDI) const
{
// this BonesOfInterest has to be in MeshComponent base, not Skeleton
if ( MeshComponent && MeshComponent->SkeletalMesh && BonesOfInterest.Num() > 0 )
{
TArray<FTransform> WorldTransforms;
WorldTransforms.AddUninitialized(MeshComponent->GetNumSpaceBases());
TArray<FLinearColor> BoneColours;
BoneColours.AddUninitialized(MeshComponent->GetNumSpaceBases());
TArray<FBoneIndexType> RequiredBones;
const FReferenceSkeleton& RefSkeleton = MeshComponent->SkeletalMesh->RefSkeleton;
static const FName SelectionColorName("SelectionColor");
const FSlateColor SelectionColor = FEditorStyle::GetSlateColor(SelectionColorName);
const FLinearColor LinearSelectionColor( SelectionColor.IsColorSpecified() ? SelectionColor.GetSpecifiedColor() : FLinearColor::White );
// we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this
for ( auto Iter = MeshComponent->RequiredBones.CreateConstIterator(); Iter; ++Iter)
{
const int32 BoneIndex = *Iter;
bool bDrawBone = false;
const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
// need to see if it's child of any of Bones of interest
for (auto SubIter=BonesOfInterest.CreateConstIterator(); SubIter; ++SubIter )
{
const int32 SubBoneIndex = *SubIter;
// if I'm child of the BonesOfInterest
if(BoneIndex == SubBoneIndex)
{
//found a bone we are interested in
if(ParentIndex >= 0)
{
WorldTransforms[ParentIndex] = MeshComponent->GetSpaceBases()[ParentIndex]*MeshComponent->ComponentToWorld;
}
BoneColours[BoneIndex] = LinearSelectionColor;
bDrawBone = true;
break;
}
else if ( RefSkeleton.BoneIsChildOf(BoneIndex, SubBoneIndex) )
{
BoneColours[BoneIndex] = FLinearColor::White;
bDrawBone = true;
break;
}
}
if (bDrawBone)
{
//add to the list
RequiredBones.AddUnique(BoneIndex);
WorldTransforms[BoneIndex] = MeshComponent->GetSpaceBases()[BoneIndex]*MeshComponent->ComponentToWorld;
}
}
DrawBones(MeshComponent, RequiredBones, WorldTransforms, PDI, BoneColours, 0.3f);
}
}
void FAnimationViewportClient::DrawSockets( TArray<USkeletalMeshSocket*>& Sockets, FPrimitiveDrawInterface* PDI, bool bUseSkeletonSocketColor ) const
{
if ( const UDebugSkelMeshComponent* DebugMeshComponent = PreviewSkelMeshComp.Get() )
{
TArray<FSelectedSocketInfo> SelectedSockets;
SelectedSockets = DebugMeshComponent->SocketsOfInterest;
for ( auto SocketIt = Sockets.CreateConstIterator(); SocketIt; ++SocketIt )
{
USkeletalMeshSocket* Socket = *(SocketIt);
FReferenceSkeleton& RefSkeleton = DebugMeshComponent->SkeletalMesh->RefSkeleton;
const int32 ParentIndex = RefSkeleton.FindBoneIndex(Socket->BoneName);
FTransform WorldTransformSocket = Socket->GetSocketTransform(DebugMeshComponent);
FLinearColor SocketColor;
FVector Start, End;
if (ParentIndex >=0)
{
FTransform WorldTransformParent = DebugMeshComponent->GetSpaceBases()[ParentIndex]*DebugMeshComponent->ComponentToWorld;
Start = WorldTransformParent.GetLocation();
End = WorldTransformSocket.GetLocation();
}
else
{
Start = FVector::ZeroVector;
End = WorldTransformSocket.GetLocation();
}
const bool bSelectedSocket = SelectedSockets.ContainsByPredicate([Socket](const FSelectedSocketInfo& Info){ return Info.Socket == Socket; });
if( bSelectedSocket )
{
SocketColor = FLinearColor(1.0f, 0.34f, 0.0f, 1.0f);
}
else
{
SocketColor = (ParentIndex >= 0) ? FLinearColor::White : FLinearColor::Red;
}
static const float SphereRadius = 1.0f;
TArray<FVector> Verts;
//Calc cone size
FVector EndToStart = (Start-End);
float ConeLength = EndToStart.Size();
float Angle = FMath::RadiansToDegrees(FMath::Atan(SphereRadius / ConeLength));
//Render Sphere for bone end point and a cone between it and its parent.
PDI->SetHitProxy( new HPersonaBoneProxy( Socket->BoneName ) );
PDI->DrawLine( Start, End, SocketColor, SDPG_Foreground );
PDI->SetHitProxy( NULL );
// draw gizmo
if( (LocalAxesMode == ELocalAxesMode::All) || bSelectedSocket )
{
FMatrix SocketMatrix;
Socket->GetSocketMatrix( SocketMatrix, PreviewSkelMeshComp.Get() );
PDI->SetHitProxy( new HPersonaSocketProxy( FSelectedSocketInfo( Socket, bUseSkeletonSocketColor ) ) );
DrawWireDiamond( PDI, SocketMatrix, 2.f, SocketColor, SDPG_Foreground );
PDI->SetHitProxy( NULL );
RenderGizmo(FTransform(SocketMatrix), PDI);
}
}
}
}
FSphere FAnimationViewportClient::GetCameraTarget()
{
bool bFoundTarget = false;
FSphere Sphere(FVector(0,0,0), 100.0f); // default
if( PreviewSkelMeshComp.IsValid() )
{
FTransform ComponentToWorld = PreviewSkelMeshComp->ComponentToWorld;
PreviewSkelMeshComp.Get()->CalcBounds(ComponentToWorld);
if( PreviewSkelMeshComp->BonesOfInterest.Num() > 0 )
{
const int32 FocusBoneIndex = PreviewSkelMeshComp->BonesOfInterest[0];
if( FocusBoneIndex != INDEX_NONE )
{
const FName BoneName = PreviewSkelMeshComp->SkeletalMesh->RefSkeleton.GetBoneName(FocusBoneIndex);
Sphere.Center = PreviewSkelMeshComp->GetBoneLocation(BoneName);
Sphere.W = 30.0f;
bFoundTarget = true;
}
}
if( !bFoundTarget && (PreviewSkelMeshComp->SocketsOfInterest.Num() > 0) )
{
USkeletalMeshSocket * Socket = PreviewSkelMeshComp->SocketsOfInterest[0].Socket;
if( Socket )
{
Sphere.Center = Socket->GetSocketLocation( PreviewSkelMeshComp.Get() );
Sphere.W = 30.0f;
bFoundTarget = true;
}
}
if( !bFoundTarget )
{
FBoxSphereBounds Bounds = PreviewSkelMeshComp.Get()->CalcBounds(FTransform::Identity);
Sphere = Bounds.GetSphere();
}
}
return Sphere;
}
void FAnimationViewportClient::UpdateCameraSetup()
{
static FRotator CustomOrbitRotation(-33.75, -135, 0);
if (PreviewSkelMeshComp.IsValid() && PreviewSkelMeshComp->SkeletalMesh)
{
FSphere BoundSphere = GetCameraTarget();
FVector CustomOrbitZoom(0, BoundSphere.W / (75.0f * (float)PI / 360.0f), 0);
FVector CustomOrbitLookAt = BoundSphere.Center;
SetCameraSetup(CustomOrbitLookAt, CustomOrbitRotation, CustomOrbitZoom, CustomOrbitLookAt, GetViewLocation(), GetViewRotation() );
// Move the floor to the bottom of the bounding box of the mesh, rather than on the origin
FVector Bottom = PreviewSkelMeshComp->Bounds.GetBoxExtrema(0);
if (GetAnimPreviewScene()->EditorFloorComp)
{
FVector FloorPos(0.f, 0.f, 0.f);
if (bAutoAlignFloor)
{
FloorPos.Z = GetFloorOffset() + Bottom.Z;
}
GetAnimPreviewScene()->EditorFloorComp->SetWorldTransform(FTransform(FQuat::Identity, FloorPos, FVector(3.0f, 3.0f, 1.0f)));
}
}
}
void FAnimationViewportClient::FocusViewportOnSphere( FSphere& Sphere )
{
FBox Box( Sphere.Center - FVector(Sphere.W, 0.0f, 0.0f), Sphere.Center + FVector(Sphere.W, 0.0f, 0.0f) );
bool bInstant = false;
FocusViewportOnBox( Box, bInstant );
Invalidate();
}
void FAnimationViewportClient::FocusViewportOnPreviewMesh()
{
FIntPoint ViewportSize = Viewport->GetSizeXY();
if(!(ViewportSize.SizeSquared() > 0))
{
// We cannot focus fully right now as the viewport does not know its size
// and we must have the aspect to correctly focus on the component,
bFocusOnDraw = true;
}
FSphere Sphere = GetCameraTarget();
FocusViewportOnSphere(Sphere);
}
float FAnimationViewportClient::GetFloorOffset() const
{
if ( PersonaPtr.IsValid() )
{
USkeletalMesh* Mesh = PersonaPtr.Pin()->GetMesh();
if ( Mesh )
{
return Mesh->FloorOffset;
}
}
return 0.0f;
}
void FAnimationViewportClient::SetFloorOffset( float NewValue )
{
if ( PersonaPtr.IsValid() )
{
USkeletalMesh* Mesh = PersonaPtr.Pin()->GetMesh();
if ( Mesh )
{
// This value is saved in a UPROPERTY for the mesh, so changes are transactional
FScopedTransaction Transaction( LOCTEXT( "SetFloorOffset", "Set Floor Offset" ) );
Mesh->Modify();
Mesh->FloorOffset = NewValue;
UpdateCameraSetup(); // This does the actual moving of the floor mesh
Invalidate();
}
}
}
TWeakObjectPtr<AWindDirectionalSource> FAnimationViewportClient::CreateWindActor(UWorld* World)
{
TWeakObjectPtr<AWindDirectionalSource> Wind = World->SpawnActor<AWindDirectionalSource>(PrevWindLocation, PrevWindRotation);
check(Wind.IsValid());
//initial wind strength value
Wind->GetComponent()->Strength = PrevWindStrength;
return Wind;
}
void FAnimationViewportClient::EnableWindActor(bool bEnableWind)
{
UWorld* World = PersonaPtr.Pin()->PreviewComponent->GetWorld();
check(World);
if(bEnableWind)
{
if(!WindSourceActor.IsValid())
{
WindSourceActor = CreateWindActor(World);
}
}
else if(WindSourceActor.IsValid())
{
PrevWindLocation = WindSourceActor->GetActorLocation();
PrevWindRotation = WindSourceActor->GetActorRotation();
PrevWindStrength = WindSourceActor->GetComponent()->Strength;
if(World->DestroyActor(WindSourceActor.Get()))
{
//clear the gizmo (translation or rotation)
PersonaPtr.Pin()->DeselectAll();
}
}
}
void FAnimationViewportClient::SetWindStrength( float SliderPos )
{
if(WindSourceActor.IsValid())
{
//Clamp grid size slider value between 0 - 1
WindSourceActor->GetComponent()->Strength = FMath::Clamp<float>(SliderPos, 0.0f, 1.0f);
//to apply this new wind strength
WindSourceActor->UpdateComponentTransforms();
}
}
float FAnimationViewportClient::GetWindStrengthSliderValue() const
{
if(WindSourceActor.IsValid())
{
return WindSourceActor->GetComponent()->Strength;
}
return 0;
}
FText FAnimationViewportClient::GetWindStrengthLabel() const
{
//Clamp slide value so that minimum value displayed is 0.00 and maximum is 1.0
float SliderValue = FMath::Clamp<float>(GetWindStrengthSliderValue(), 0.0f, 1.0f);
static const FNumberFormattingOptions FormatOptions = FNumberFormattingOptions()
.SetMinimumFractionalDigits(2)
.SetMaximumFractionalDigits(2);
return FText::AsNumber(SliderValue, &FormatOptions);
}
void FAnimationViewportClient::SetGravityScale( float SliderPos )
{
GravityScaleSliderValue = SliderPos;
float RealGravityScale = SliderPos * 4;
check(PersonaPtr.Pin()->PreviewComponent);
UWorld* World = PersonaPtr.Pin()->PreviewComponent->GetWorld();
AWorldSettings* Setting = World->GetWorldSettings();
Setting->WorldGravityZ = UPhysicsSettings::Get()->DefaultGravityZ*RealGravityScale;
Setting->bWorldGravitySet = true;
}
float FAnimationViewportClient::GetGravityScaleSliderValue() const
{
return GravityScaleSliderValue;
}
FText FAnimationViewportClient::GetGravityScaleLabel() const
{
//Clamp slide value so that minimum value displayed is 0.00 and maximum is 4.0
float SliderValue = FMath::Clamp<float>(GetGravityScaleSliderValue()*4, 0.0f, 4.0f);
static const FNumberFormattingOptions FormatOptions = FNumberFormattingOptions()
.SetMinimumFractionalDigits(2)
.SetMaximumFractionalDigits(2);
return FText::AsNumber(SliderValue, &FormatOptions);
}
void FAnimationViewportClient::ToggleShowNormals()
{
if(PreviewSkelMeshComp.IsValid())
{
PreviewSkelMeshComp->bDrawNormals = !PreviewSkelMeshComp->bDrawNormals;
PreviewSkelMeshComp->MarkRenderStateDirty();
Invalidate();
}
}
bool FAnimationViewportClient::IsSetShowNormalsChecked() const
{
if (PreviewSkelMeshComp.IsValid())
{
return PreviewSkelMeshComp->bDrawNormals;
}
return false;
}
void FAnimationViewportClient::ToggleShowTangents()
{
if(PreviewSkelMeshComp.IsValid())
{
PreviewSkelMeshComp->bDrawTangents = !PreviewSkelMeshComp->bDrawTangents;
PreviewSkelMeshComp->MarkRenderStateDirty();
Invalidate();
}
}
bool FAnimationViewportClient::IsSetShowTangentsChecked() const
{
if (PreviewSkelMeshComp.IsValid())
{
return PreviewSkelMeshComp->bDrawTangents;
}
return false;
}
void FAnimationViewportClient::ToggleShowBinormals()
{
if(PreviewSkelMeshComp.IsValid())
{
PreviewSkelMeshComp->bDrawBinormals = !PreviewSkelMeshComp->bDrawBinormals;
PreviewSkelMeshComp->MarkRenderStateDirty();
Invalidate();
}
}
bool FAnimationViewportClient::IsSetShowBinormalsChecked() const
{
if (PreviewSkelMeshComp.IsValid())
{
return PreviewSkelMeshComp->bDrawBinormals;
}
return false;
}
void FAnimationViewportClient::ToggleDrawUVOverlay()
{
bDrawUVs = !bDrawUVs;
Invalidate();
}
bool FAnimationViewportClient::IsSetDrawUVOverlayChecked() const
{
return bDrawUVs;
}
void FAnimationViewportClient::OnSetShowMeshStats(int32 ShowMode)
{
ConfigOption->SetShowMeshStats(ShowMode);
}
bool FAnimationViewportClient::IsShowingMeshStats() const
{
const bool bShouldBeEnabled = ConfigOption->ShowMeshStats != EDisplayInfoMode::None;
const bool bCanBeEnabled = !PersonaPtr.Pin()->IsModeCurrent(FPersonaModes::AnimBlueprintEditMode);
return bShouldBeEnabled && bCanBeEnabled;
}
bool FAnimationViewportClient::IsDetailedMeshStats() const
{
return ConfigOption->ShowMeshStats == EDisplayInfoMode::Detailed;
}
int32 FAnimationViewportClient::GetShowMeshStats() const
{
return ConfigOption->ShowMeshStats;
}
FAnimationEditorPreviewScene* FAnimationViewportClient::GetAnimPreviewScene() const
{
return static_cast<FAnimationEditorPreviewScene*>(PreviewScene);
}
#undef LOCTEXT_NAMESPACE