// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimationEditorViewportClient.h" #include "Modules/ModuleManager.h" #include "EngineGlobals.h" #include "Animation/AnimSequence.h" #include "SceneView.h" #include "Styling/AppStyle.h" #include "AnimationEditorPreviewScene.h" #include "Materials/Material.h" #include "CanvasItem.h" #include "Editor/EditorPerProjectUserSettings.h" #include "Animation/AnimBlueprint.h" #include "Preferences/PersonaOptions.h" #include "Engine/CollisionProfile.h" #include "Animation/AnimBlueprintGeneratedClass.h" #include "Animation/BuiltInAttributeTypes.h" #include "Animation/MirrorDataTable.h" #include "GameFramework/WorldSettings.h" #include "PersonaModule.h" #include "Rendering/SkeletalMeshRenderData.h" #include "GenericPlatform/GenericPlatformInputDeviceMapper.h" #include "DynamicMeshBuilder.h" #include "Materials/MaterialInstanceDynamic.h" #include "Materials/MaterialRenderProxy.h" #include "SEditorViewport.h" #include "CanvasTypes.h" #include "AnimPreviewInstance.h" #include "ScopedTransaction.h" #include "PhysicsEngine/PhysicsAsset.h" #include "Engine/SkeletalMeshSocket.h" #include "Engine/SkinnedAssetCommon.h" #include "SAnimationEditorViewport.h" #include "AssetViewerSettings.h" #include "IPersonaEditorModeManager.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "SkeletalDebugRendering.h" #include "SkeletalRenderPublic.h" #include "AudioDevice.h" #include "RawIndexBuffer.h" #include "CameraController.h" #include "ContextObjectStore.h" #include "Tools/EdModeInteractiveToolsContext.h" #include "IPersonaToolkit.h" #include "Animation/MorphTarget.h" #include "Rendering/SkeletalMeshModel.h" #include "UnrealWidget.h" #include "Engine/PoseWatchRenderData.h" #include "UObject/UE5MainStreamObjectVersion.h" #include "Animation/AnimCompositeBase.h" #include "AudioEditorSettings.h" #include "Animation/AnimNotifies/AnimNotifyState.h" #include "Animation/AnimMontage.h" #include "Animation/AnimSequenceHelpers.h" #include "AnimationBlueprintLibrary.h" TAutoConsoleVariable CVarShowEngineDebugMessageOnAnimViewport(TEXT("Persona.AnimationEditorViewportClient.ShowEngineDebugMessageOnAnimViewport"), true, TEXT("When true show engine onscreen debug messages on animation editor viewport clients.")); namespace { static const float AnimationEditorViewport_RotateSpeed = 0.02f; static const float AnimationEditorViewport_TranslateSpeed = 0.25f; // follow camera feature static const FVector::FReal FollowCamera_InterpSpeed = 4.f; static const FVector::FReal FollowCamera_InterpSpeed_Z = 1.f; } namespace EAnimationPlaybackSpeeds { // Speed scales for animation playback, must match EAnimationPlaybackSpeeds::Type float Values[EAnimationPlaybackSpeeds::NumPlaybackSpeeds] = { 0.1f, 0.25f, 0.5f, 0.75f, 1.0f, 2.0f, 5.0f, 10.0f, 0.f }; } namespace UE::Private { bool CanDrawPreviewComponents(TArray PreviewComponents) { //Avoid drawing if any of the component reference a compiling asset for (UDebugSkelMeshComponent* PreviewMeshComponent : PreviewComponents) { if (PreviewMeshComponent && PreviewMeshComponent->GetSkinnedAsset() && PreviewMeshComponent->GetSkinnedAsset()->IsCompiling()) { return false; } } return true; } void DrawCoordinateSystem(FPrimitiveDrawInterface* PDI, const FTransform& Transform, const float Thickness, const float Length, const float DepthBias, const bool bScreenSpace, uint8 Alpha) { const FVector Location = Transform.GetLocation(); const FVector AxisX = Transform.GetUnitAxis(EAxis::X) * Length; const FVector AxisY = Transform.GetUnitAxis(EAxis::Y) * Length; const FVector AxisZ = Transform.GetUnitAxis(EAxis::Z) * Length; PDI->DrawTranslucentLine(Location, Location + AxisX, FColor::Red.WithAlpha(Alpha), SDPG_World, 1.0f, DepthBias, bScreenSpace); PDI->DrawTranslucentLine(Location, Location + AxisY, FColor::Green.WithAlpha(Alpha), SDPG_World, 1.0f, DepthBias, bScreenSpace); PDI->DrawTranslucentLine(Location, Location + AxisZ, FColor::Blue.WithAlpha(Alpha), SDPG_World, 1.0f, DepthBias, bScreenSpace); } FColor GetColorForAxis(EAxis::Type InAxis) { // Just draw all forward-axis versions as black for now. return FColor::Black; } void DrawFlatArrow(class FPrimitiveDrawInterface* PDI,const FVector& Base,const FVector& XAxis,const FVector& YAxis,FColor Color,float Length,int32 Width, const FMaterialRenderProxy* MaterialRenderProxy, uint8 DepthPriority, float Thickness = 0.0f) { float DistanceFromBaseToHead = Length/3.0f; float DistanceFromBaseToTip = DistanceFromBaseToHead*2.0f; float WidthOfBase = Width; float WidthOfHead = 2*Width; FVector ArrowPoints[7]; //base points ArrowPoints[0] = Base - YAxis*(WidthOfBase*.5f); ArrowPoints[1] = Base + YAxis*(WidthOfBase*.5f); //inner head ArrowPoints[2] = ArrowPoints[0] + XAxis*DistanceFromBaseToHead; ArrowPoints[3] = ArrowPoints[1] + XAxis*DistanceFromBaseToHead; //outer head ArrowPoints[4] = ArrowPoints[2] - YAxis*(WidthOfBase*.5f); ArrowPoints[5] = ArrowPoints[3] + YAxis*(WidthOfBase*.5f); //tip ArrowPoints[6] = Base + XAxis*Length; //Draw lines { //base PDI->DrawTranslucentLine(ArrowPoints[0], ArrowPoints[1], Color, DepthPriority, Thickness); //base sides PDI->DrawTranslucentLine(ArrowPoints[0], ArrowPoints[2], Color, DepthPriority, Thickness); PDI->DrawTranslucentLine(ArrowPoints[1], ArrowPoints[3], Color, DepthPriority, Thickness); //head base PDI->DrawTranslucentLine(ArrowPoints[2], ArrowPoints[4], Color, DepthPriority, Thickness); PDI->DrawTranslucentLine(ArrowPoints[3], ArrowPoints[5], Color, DepthPriority, Thickness); //head sides PDI->DrawTranslucentLine(ArrowPoints[4], ArrowPoints[6], Color, DepthPriority, Thickness); PDI->DrawTranslucentLine(ArrowPoints[5], ArrowPoints[6], Color, DepthPriority, Thickness); } if (MaterialRenderProxy != nullptr) { FDynamicMeshBuilder MeshBuilder(PDI->View->GetFeatureLevel()); //Compute vertices for base circle. for(int32 i = 0; i< 7; ++i) { FDynamicMeshVertex MeshVertex; MeshVertex.Position = (FVector3f)ArrowPoints[i]; MeshVertex.Color = Color; MeshVertex.TextureCoordinate[0] = FVector2f(0.0f, 0.0f);; MeshVertex.SetTangents(FVector3f(XAxis^YAxis), (FVector3f)YAxis, (FVector3f)XAxis); MeshBuilder.AddVertex(MeshVertex); //Add bottom vertex } //Add triangles / double sided { MeshBuilder.AddTriangle(0, 2, 1); //base MeshBuilder.AddTriangle(0, 1, 2); //base MeshBuilder.AddTriangle(1, 2, 3); //base MeshBuilder.AddTriangle(1, 3, 2); //base MeshBuilder.AddTriangle(4, 5, 6); //head MeshBuilder.AddTriangle(4, 6, 5); //head } MeshBuilder.Draw(PDI, FMatrix::Identity, MaterialRenderProxy, DepthPriority, 0.0f); } } } #define LOCTEXT_NAMESPACE "FAnimationViewportClient" ///////////////////////////////////////////////////////////////////////// // FAnimationViewportClient FAnimationViewportClient::FAnimationViewportClient(const TSharedRef& InPreviewScene, const TSharedRef& InAnimationEditorViewport, const TSharedRef& InAssetEditorToolkit, int32 InViewportIndex, bool bInShowStats) : FEditorViewportClient(&InAssetEditorToolkit->GetEditorModeManager(), &InPreviewScene.Get(), StaticCastSharedRef(InAnimationEditorViewport)) , PreviewScenePtr(InPreviewScene) , AssetEditorToolkitPtr(InAssetEditorToolkit) , bRotateCameraToFollowBone(false) , bFocusOnDraw(false) , bFocusUsingCustomCamera(false) , CachedScreenSize(0.0f) , bShowMeshStats(bInShowStats) , bInitiallyFocused(false) , OrbitRotation(FQuat::Identity) , ViewportIndex(InViewportIndex) , LastLookAtLocation(FVector::ZeroVector) , bResumeAfterTracking(false) { CachedDefaultCameraController = CameraController; OnCameraControllerChanged(); Widget->SetUsesEditorModeTools(ModeTools.Get()); ((FAssetEditorModeManager*)ModeTools.Get())->SetPreviewScene(&InPreviewScene.Get()); ModeTools->SetDefaultMode(FPersonaEditModes::SkeletonSelection); // Default to local space SetWidgetCoordSystemSpace(COORD_Local); // load config ConfigOption = UPersonaOptions::StaticClass()->GetDefaultObject(); check (ConfigOption); // DrawHelper set up DrawHelper.PerspectiveGridSize = UE_OLD_HALF_WORLD_MAX1; DrawHelper.AxesLineThickness = ConfigOption->bHighlightOrigin ? 1.0f : 0.0f; DrawHelper.bDrawGrid = true; // Toggling grid now relies on the show flag WidgetMode = UE::Widget::WM_Rotate; ModeTools->SetWidgetMode(WidgetMode); EngineShowFlags.Game = 0; EngineShowFlags.ScreenSpaceReflections = 1; EngineShowFlags.AmbientOcclusion = 1; EngineShowFlags.SetSnap(0); EngineShowFlags.Grid = ConfigOption->bShowGrid; SetRealtime(true); if(GEditor->PlayWorld) { const bool bShouldBeRealtime = false; AddRealtimeOverride(bShouldBeRealtime, LOCTEXT("RealtimeOverride_PIE", "Play in Editor")); } // @todo double define - fix it const float FOVMin = 5.f; const float FOVMax = 170.f; ViewFOV = FMath::Clamp(ConfigOption->GetAssetEditorOptions(InAssetEditorToolkit->GetEditorName()).ViewportConfigs[ViewportIndex].ViewFOV, FOVMin, FOVMax); CameraSpeedSetting = ConfigOption->GetAssetEditorOptions(InAssetEditorToolkit->GetEditorName()).ViewportConfigs[ViewportIndex].CameraSpeedSetting; CameraSpeedScalar = ConfigOption->GetAssetEditorOptions(InAssetEditorToolkit->GetEditorName()).ViewportConfigs[ViewportIndex].CameraSpeedScalar; EngineShowFlags.SetSeparateTranslucency(true); EngineShowFlags.SetCompositeEditorPrimitives(true); EngineShowFlags.SetSelectionOutline(true); bDrawUVs = false; UVChannelToDraw = 0; bAutoAlignFloor = ConfigOption->bAutoAlignFloorToMesh; // Set audio mute option UWorld* World = PreviewScene->GetWorld(); if(World) { World->bAllowAudioPlayback = !ConfigOption->bMuteAudio; UAudioEditorSettings* AudioConfigOption = GetMutableDefault(); check(AudioConfigOption); AudioConfigOption->SetUseAudioAttenuation(true); } } FAnimationViewportClient::~FAnimationViewportClient() { CameraController = CachedDefaultCameraController; // Unregistering the callbacks is mandatory, else we get random crashes if (FAnimationEditorPreviewScene* AnimationEditorPreviewScene = GetAnimPreviewScenePtr().Get()) { AnimationEditorPreviewScene->UnregisterOnPreviewMeshChanged(this); AnimationEditorPreviewScene->UnregisterOnInvalidateViews(this); AnimationEditorPreviewScene->UnregisterOnCameraOverrideChanged(this); AnimationEditorPreviewScene->UnregisterOnPreTick(this); AnimationEditorPreviewScene->UnregisterOnPostTick(this); if (UDebugSkelMeshComponent* PreviewMeshComponent = AnimationEditorPreviewScene->GetPreviewMeshComponent()) { if (OnPhysicsCreatedDelegateHandle.IsValid()) { PreviewMeshComponent->UnregisterOnPhysicsCreatedDelegate(OnPhysicsCreatedDelegateHandle); } if (OnMeshChangedDelegateHandle.IsValid()) { if (USkeletalMesh* SkelMesh = PreviewMeshComponent->GetSkeletalMeshAsset()) { SkelMesh->GetOnMeshChanged().Remove(OnMeshChangedDelegateHandle); } } } AnimationEditorPreviewScene->UnregisterOnSelectedBonesChanged(OnSelectedBonesChangedHandle); } OnPhysicsCreatedDelegateHandle.Reset(); OnMeshChangedDelegateHandle.Reset(); } void FAnimationViewportClient::Initialize() { if (FAnimationEditorPreviewScene* AnimationEditorPreviewScene = GetAnimPreviewScenePtr().Get()) { AnimationEditorPreviewScene->RegisterOnCameraOverrideChanged(FSimpleDelegate::CreateSP(this, &FAnimationViewportClient::OnCameraControllerChanged)); AnimationEditorPreviewScene->RegisterOnPreviewMeshChanged(FOnPreviewMeshChanged::CreateSP(this, &FAnimationViewportClient::HandleSkeletalMeshChanged)); if (AnimationEditorPreviewScene->GetPreviewMeshComponent()) { HandleSkeletalMeshChanged(nullptr, AnimationEditorPreviewScene->GetPreviewMeshComponent()->GetSkeletalMeshAsset()); } AnimationEditorPreviewScene->RegisterOnInvalidateViews(FSimpleDelegate::CreateSP(this, &FAnimationViewportClient::HandleInvalidateViews)); AnimationEditorPreviewScene->RegisterOnFocusViews(FSimpleDelegate::CreateSP(this, &FAnimationViewportClient::HandleFocusViews)); AnimationEditorPreviewScene->RegisterOnPreTick(FSimpleDelegate::CreateSP(this, &FAnimationViewportClient::HandlePreviewScenePreTick)); AnimationEditorPreviewScene->RegisterOnPostTick(FSimpleDelegate::CreateSP(this, &FAnimationViewportClient::HandlePreviewScenePostTick)); OnSelectedBonesChangedHandle = AnimationEditorPreviewScene->RegisterOnSelectedBonesChanged(FOnSelectedBonesChanged::CreateLambda([this](const TArray&, ESelectInfo::Type) { UpdateBonesToDraw(); })); } // Setup bones to draw on initialise UpdateBonesToDraw(); } void FAnimationViewportClient::OnToggleAutoAlignFloor() { bAutoAlignFloor = !bAutoAlignFloor; UpdateCameraSetup(); ConfigOption->SetAutoAlignFloorToMesh(bAutoAlignFloor); } bool FAnimationViewportClient::IsAutoAlignFloor() const { return bAutoAlignFloor; } 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::OnToggleUseAudioAttenuation() { UAudioEditorSettings* AudioConfigOption = GetMutableDefault(); check(AudioConfigOption); AudioConfigOption->SetUseAudioAttenuation(!AudioConfigOption->IsUsingAudioAttenuation()); } bool FAnimationViewportClient::IsUsingAudioAttenuation() const { const UAudioEditorSettings* AudioConfigOption = GetDefault(); check(AudioConfigOption); return AudioConfigOption->IsUsingAudioAttenuation(); } void FAnimationViewportClient::ToggleRotateCameraToFollowBone() { bRotateCameraToFollowBone = !bRotateCameraToFollowBone; if (!bRotateCameraToFollowBone && GetCameraFollowMode() == EAnimationViewportCameraFollowMode::Bone) { OrbitRotation = FQuat::Identity; } } bool FAnimationViewportClient::GetShouldRotateCameraToFollowBone() const { return bRotateCameraToFollowBone; } void FAnimationViewportClient::SetCameraFollowMode(EAnimationViewportCameraFollowMode InCameraFollowMode, FName InBoneName) { bool bCanFollow = true; UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if(InCameraFollowMode == EAnimationViewportCameraFollowMode::Bone && PreviewMeshComponent) { bCanFollow = PreviewMeshComponent->GetBoneIndex(InBoneName) != INDEX_NONE; } if(bCanFollow && InCameraFollowMode != EAnimationViewportCameraFollowMode::None) { ConfigOption->SetViewCameraFollow(AssetEditorToolkitPtr.Pin()->GetEditorName(), InCameraFollowMode, InBoneName, ViewportIndex); CameraFollowMode = InCameraFollowMode; CameraFollowBoneName = InBoneName; bCameraLock = true; bUsingOrbitCamera = true; if (PreviewMeshComponent != nullptr) { FVector LookAtLocation = LastLookAtLocation; switch(CameraFollowMode) { case EAnimationViewportCameraFollowMode::Bounds: { const FBoxSphereBounds Bounds = PreviewMeshComponent->CalcGameBounds(PreviewMeshComponent->GetComponentTransform()); LookAtLocation = Bounds.Origin; } break; case EAnimationViewportCameraFollowMode::Root: { LookAtLocation = PreviewMeshComponent->GetBoneTransform(0).GetLocation(); LookAtLocation.Z = PreviewMeshComponent->CalcGameBounds(PreviewMeshComponent->GetComponentTransform()).Origin.Z; } break; case EAnimationViewportCameraFollowMode::Bone: { LookAtLocation = PreviewMeshComponent->GetBoneLocation(InBoneName); } break; } OrbitRotation = FQuat::Identity; SetLookAtLocation(LookAtLocation, true); LastLookAtLocation = LookAtLocation; bUsingOrbitCamera = true; } } else { ConfigOption->SetViewCameraFollow(AssetEditorToolkitPtr.Pin()->GetEditorName(), EAnimationViewportCameraFollowMode::None, NAME_None, ViewportIndex); CameraFollowMode = EAnimationViewportCameraFollowMode::None; CameraFollowBoneName = NAME_None; OrbitRotation = FQuat::Identity; EnableCameraLock(false); FocusViewportOnPreviewMesh(false); Invalidate(); } } void FAnimationViewportClient::OnFocusViewportToSelection() { // If focusing on a bone and using a Camera Follow Mode that orbits a bone, update the bone to follow to the selected bone if (CameraFollowMode == EAnimationViewportCameraFollowMode::Root || CameraFollowMode == EAnimationViewportCameraFollowMode::Bone || CameraFollowMode == EAnimationViewportCameraFollowMode::Bounds ) { int32 SelectedBoneIndex = GetAnimPreviewScene()->GetSelectedBoneIndex(); if (SelectedBoneIndex != INDEX_NONE) { const FReferenceSkeleton& ReferenceSkeleton = GetAnimPreviewScene()->GetPreviewMeshComponent()->GetReferenceSkeleton(); const FName SelectedBoneName = ReferenceSkeleton.GetBoneName(SelectedBoneIndex); check(SelectedBoneName != NAME_None); bRotateCameraToFollowBone = false; SetCameraFollowMode(EAnimationViewportCameraFollowMode::Bone, SelectedBoneName); } else { SetCameraFollowMode(EAnimationViewportCameraFollowMode::Root, FName()); } } else { SetCameraFollowMode(EAnimationViewportCameraFollowMode::None); FocusViewportOnPreviewMesh(false); } } EAnimationViewportCameraFollowMode FAnimationViewportClient::GetCameraFollowMode() const { return CameraFollowMode; } FName FAnimationViewportClient::GetCameraFollowBoneName() const { return CameraFollowBoneName; } void FAnimationViewportClient::JumpToDefaultCamera() { FocusViewportOnPreviewMesh(true); } void FAnimationViewportClient::SaveCameraAsDefault() { USkeletalMesh* SkelMesh = GetAnimPreviewScene()->GetPreviewMeshComponent()->GetSkeletalMeshAsset(); if (SkelMesh) { FScopedTransaction Transaction(LOCTEXT("SaveCameraAsDefault", "Save Camera As Default")); FViewportCameraTransform& ViewTransform = GetViewTransform(); SkelMesh->Modify(); SkelMesh->SetDefaultEditorCameraLocation(ViewTransform.GetLocation()); SkelMesh->SetDefaultEditorCameraRotation(ViewTransform.GetRotation()); SkelMesh->SetDefaultEditorCameraLookAt(ViewTransform.GetLookAt()); SkelMesh->SetDefaultEditorCameraOrthoZoom(ViewTransform.GetOrthoZoom()); SkelMesh->SetHasCustomDefaultEditorCamera(true); // Create and display a notification const FText NotificationText = FText::Format(LOCTEXT("SavedDefaultCamera", "Saved default camera for {0}"), FText::AsCultureInvariant(SkelMesh->GetName())); FNotificationInfo Info(NotificationText); Info.ExpireDuration = 2.0f; FSlateNotificationManager::Get().AddNotification(Info); } } bool FAnimationViewportClient::CanSaveCameraAsDefault() const { return CameraFollowMode == EAnimationViewportCameraFollowMode::None; } void FAnimationViewportClient::ClearDefaultCamera() { USkeletalMesh* SkelMesh = GetAnimPreviewScene()->GetPreviewMeshComponent()->GetSkeletalMeshAsset(); if (SkelMesh) { FScopedTransaction Transaction(LOCTEXT("ClearDefaultCamera", "Clear Default Camera")); SkelMesh->Modify(); SkelMesh->SetHasCustomDefaultEditorCamera(false); // Create and display a notification const FText NotificationText = FText::Format(LOCTEXT("ClearedDefaultCamera", "Cleared default camera for {0}"), FText::AsCultureInvariant(SkelMesh->GetName())); FNotificationInfo Info(NotificationText); Info.ExpireDuration = 2.0f; FSlateNotificationManager::Get().AddNotification(Info); } } bool FAnimationViewportClient::HasDefaultCameraSet() const { USkeletalMesh* SkelMesh = GetAnimPreviewScene()->GetPreviewMeshComponent()->GetSkeletalMeshAsset(); return (SkelMesh && SkelMesh->GetHasCustomDefaultEditorCamera()); } static void DisableAllBodiesSimulatePhysics(UDebugSkelMeshComponent* PreviewMeshComponent) { // Reset simulation state of body instances so we dont actually simulate after recreating the physics state for (int32 BodyIdx = 0; BodyIdx < PreviewMeshComponent->Bodies.Num(); ++BodyIdx) { if (FBodyInstance* BodyInst = PreviewMeshComponent->Bodies[BodyIdx]) { BodyInst->SetInstanceSimulatePhysics(false); } } } void FAnimationViewportClient::HandleSkeletalMeshChanged(USkeletalMesh* OldSkeletalMesh, USkeletalMesh* NewSkeletalMesh) { // Set up our notifications that the mesh we're watching has changed from some external source (like undo/redo) if (OldSkeletalMesh) { OldSkeletalMesh->GetOnMeshChanged().Remove(OnMeshChangedDelegateHandle); } if (NewSkeletalMesh) { OnMeshChangedDelegateHandle = NewSkeletalMesh->GetOnMeshChanged().AddSP(this, &FAnimationViewportClient::HandleOnMeshChanged); } if (OldSkeletalMesh != NewSkeletalMesh || NewSkeletalMesh == nullptr) { if (!bInitiallyFocused) { FocusViewportOnPreviewMesh(true); bInitiallyFocused = true; } UpdateCameraSetup(); } // Setup physics data from physics assets if available, clearing any physics setup on the component UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); UPhysicsAsset* PhysAsset = PreviewMeshComponent->GetPhysicsAsset(); if(PhysAsset) { PhysAsset->InvalidateAllPhysicsMeshes(); if (OnPhysicsCreatedDelegateHandle.IsValid()) { PreviewMeshComponent->UnregisterOnPhysicsCreatedDelegate(OnPhysicsCreatedDelegateHandle); OnPhysicsCreatedDelegateHandle.Reset(); } // we need to make sure we monitor any change to the PhysicsState being recreated, as this can happen from path that is external to this class // (example: setting a property on a body that is type "simulated" will recreate the state from USkeletalBodySetup::PostEditChangeProperty and let the body simulating (UE-107308) OnPhysicsCreatedDelegateHandle = PreviewMeshComponent->RegisterOnPhysicsCreatedDelegate(FOnSkelMeshPhysicsCreated::CreateSP(this, &FAnimationViewportClient::HandleOnSkelMeshPhysicsCreated)); PreviewMeshComponent->TermArticulated(); PreviewMeshComponent->InitArticulated(GetWorld()->GetPhysicsScene()); if (PreviewMeshComponent->CanOverrideCollisionProfile()) { // Set to PhysicsActor to enable tracing regardless of project overrides static FName CollisionProfileName(TEXT("PhysicsActor")); PreviewMeshComponent->SetCollisionProfileName(CollisionProfileName); } } UpdateBonesToDraw(); Invalidate(); } void FAnimationViewportClient::HandleOnMeshChanged() { UpdateCameraSetup(); UpdateBonesToDraw(); Invalidate(); } void FAnimationViewportClient::HandleOnSkelMeshPhysicsCreated() { UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); // let's make sure nothing is simulating and that all necessary state are in proper order PreviewMeshComponent->SetPhysicsBlendWeight(0.f); PreviewMeshComponent->SetSimulatePhysics(false); DisableAllBodiesSimulatePhysics(PreviewMeshComponent); } void FAnimationViewportClient::Draw(const FSceneView* View, FPrimitiveDrawInterface* PDI) { TArray PreviewMeshComponents = GetPreviewScene()->GetAllPreviewMeshComponents(); if (!UE::Private::CanDrawPreviewComponents(PreviewMeshComponents)) { return; } UpdateBonesToDraw(); FEditorViewportClient::Draw(View, PDI); // draw bones for all debug skeletal meshes for (UDebugSkelMeshComponent* PreviewMeshComponent : PreviewMeshComponents) { const bool bValidComponent = PreviewMeshComponent != nullptr; const bool bValidSkeletalMesh = bValidComponent && PreviewMeshComponent->GetSkeletalMeshAsset() != nullptr; if (bValidComponent && bValidSkeletalMesh && !PreviewMeshComponent->GetSkeletalMeshAsset()->IsCompiling()) { // Can't have both bones of interest and sockets of interest set check( !(GetAnimPreviewScene()->GetSelectedBoneIndex() != INDEX_NONE && GetAnimPreviewScene()->GetSelectedSocket().IsValid() ) ) const FReferenceSkeleton& RefSkeleton = PreviewMeshComponent->GetReferenceSkeleton(); const TArray& DrawBoneIndices = PreviewMeshComponent->GetDrawBoneIndices(); // draw the skeleton normally if ( GetBoneDrawMode() != EBoneDrawMode::None ) { DrawMeshBones(PreviewMeshComponent, PDI); } // special draw modes for debugging various transforms... if (PreviewMeshComponent->bDisplayRawAnimation ) { DrawMeshBonesUncompressedAnimation(PreviewMeshComponent, PDI); } if (PreviewMeshComponent->NonRetargetedSpaceBases.Num() > 0 ) { DrawMeshBonesNonRetargetedAnimation(PreviewMeshComponent, PDI); } if(PreviewMeshComponent->bDisplayAdditiveBasePose ) { DrawMeshBonesAdditiveBasePose(PreviewMeshComponent, PDI); } if(PreviewMeshComponent->bDisplayBakedAnimation) { DrawMeshBonesBakedAnimation(PreviewMeshComponent, PDI); } if(PreviewMeshComponent->bDisplaySourceAnimation) { DrawMeshBonesSourceRawAnimation(PreviewMeshComponent, PDI); } DrawWatchedPoses(PreviewMeshComponent, PDI); PreviewMeshComponent->DebugDrawClothing(PDI); // Display socket hit points if (PreviewMeshComponent->bDrawSockets ) { if (PreviewMeshComponent->bSkeletonSocketsVisible && PreviewMeshComponent->GetSkeletalMeshAsset()->GetSkeleton() ) { DrawSockets(PreviewMeshComponent, MutableView(PreviewMeshComponent->GetSkeletalMeshAsset()->GetSkeleton()->Sockets), GetAnimPreviewScene()->GetSelectedSocket(), PDI, true); } if ( PreviewMeshComponent->bMeshSocketsVisible ) { DrawSockets(PreviewMeshComponent, MutableView(PreviewMeshComponent->GetSkeletalMeshAsset()->GetMeshOnlySocketList()), GetAnimPreviewScene()->GetSelectedSocket(), PDI, false); } } if (PreviewMeshComponent->bDrawAttributes) { DrawAttributes(PreviewMeshComponent, PDI); } DrawNotifies(PreviewMeshComponent, PDI); DrawRootMotionTrajectory(PreviewMeshComponent, PDI); DrawAssetUserData(PDI); } else if (bValidComponent && !bValidSkeletalMesh) { if (const USkeleton* Skeleton = GetPreviewScene()->GetPersonaToolkit()->GetSkeleton()) { DrawBonesFromSkeleton(PreviewMeshComponent, Skeleton, PreviewMeshComponent->BonesOfInterest, PDI); } } } if (bFocusOnDraw) { bFocusOnDraw = false; FocusViewportOnPreviewMesh(bFocusUsingCustomCamera); } // set camera mode if need be (we need to do this here as focus on draw can take us out of orbit mode) FAssetEditorOptions& Options = ConfigOption->GetAssetEditorOptions(AssetEditorToolkitPtr.Pin()->GetEditorName()); if(Options.ViewportConfigs[ViewportIndex].CameraFollowMode != CameraFollowMode) { SetCameraFollowMode(Options.ViewportConfigs[ViewportIndex].CameraFollowMode, Options.ViewportConfigs[ViewportIndex].CameraFollowBoneName); } } void FAnimationViewportClient::DrawCanvas( FViewport& InViewport, FSceneView& View, FCanvas& Canvas ) { TArray PreviewMeshComponents = GetPreviewScene()->GetAllPreviewMeshComponents(); if (!UE::Private::CanDrawPreviewComponents(PreviewMeshComponents)) { return; } FEditorViewportClient::DrawCanvas(InViewport, View, Canvas); UWorld* World = nullptr; for (UDebugSkelMeshComponent* PreviewMeshComponent : PreviewMeshComponents) { if (!PreviewMeshComponent) { continue; } // Display bone names if (PreviewMeshComponent->bShowBoneNames) { ShowBoneNames(&Canvas, &View, PreviewMeshComponent); } // Display attribute names if (PreviewMeshComponent->bDrawAttributes) { ShowAttributeNames(&Canvas, &View, PreviewMeshComponent); } DrawCanvasNotifies(PreviewMeshComponent, Canvas, View); DrawCanvasAssetUserData(Canvas, View); if (bDrawUVs) { DrawUVsForMesh(Viewport, &Canvas, 1, PreviewMeshComponent); } // Debug draw clothing texts PreviewMeshComponent->DebugDrawClothingTexts(&Canvas, &View); if(World == nullptr) { World = PreviewMeshComponent->GetWorld(); } } #if !(UE_BUILD_TEST) if (World && GEngine->bEnableOnScreenDebugMessagesDisplay && GEngine->bEnableOnScreenDebugMessages && CVarShowEngineDebugMessageOnAnimViewport->GetBool()) { constexpr int32 MessageX = 20; constexpr int32 MessageY = 65; GEngine->DrawOnscreenDebugMessages(World, Viewport, &Canvas, nullptr, MessageX, MessageY); } #endif } void FAnimationViewportClient::DrawUVsForMesh(FViewport* InViewport, FCanvas* InCanvas, int32 InTextYPos, UDebugSkelMeshComponent* PreviewMeshComponent) { if (!PreviewMeshComponent || !PreviewMeshComponent->GetSkeletalMeshAsset() || PreviewMeshComponent->GetSkeletalMeshAsset()->IsCompiling()) { return; } //use the overridden LOD level const uint32 LODLevel = FMath::Clamp(PreviewMeshComponent->GetForcedLOD() - 1, 0, PreviewMeshComponent->GetSkeletalMeshAsset()->GetLODNum() - 1); TArray SelectedEdgeTexCoords; //No functionality in Persona for this (yet?) DrawUVs(InViewport, InCanvas, InTextYPos, LODLevel, UVChannelToDraw, SelectedEdgeTexCoords, nullptr, &PreviewMeshComponent->GetSkeletalMeshRenderData()->LODRenderData[LODLevel] ); } void FAnimationViewportClient::Tick(float DeltaSeconds) { //Avoid ticking the animation viewport if the skeletalmesh is compiling if (GetAnimPreviewScene()->GetPreviewMeshComponent() && GetAnimPreviewScene()->GetPreviewMeshComponent()->GetSkeletalMeshAsset() && GetAnimPreviewScene()->GetPreviewMeshComponent()->GetSkeletalMeshAsset()->IsCompiling()) { return; } FEditorViewportClient::Tick(DeltaSeconds); GetAnimPreviewScene()->FlagTickable(); TimecodeDisplay.Reset(); if (GetAnimPreviewScene()->IsShowTimecode()) { UAnimationAsset* AnimationAsset = GetAnimPreviewScene()->GetPreviewAnimationAsset(); if (UAnimSequence* AnimSequence = Cast(AnimationAsset)) { FName BoneName = UAnimationBlueprintLibrary::FindBoneNameWithTimecodeAttributes(AnimSequence); FString SlateName; FQualifiedFrameTime QualifiedFrameTime; TOptional PlayPosition = GetAnimPreviewScene()->GetCurrentTime(); if (PlayPosition && UAnimationBlueprintLibrary::EvaluateBoneTimecodeAndSlateAttributesAtTime(BoneName, AnimSequence, *PlayPosition, QualifiedFrameTime, SlateName)) { TimecodeDisplay = {QualifiedFrameTime, SlateName}; } } } } void FAnimationViewportClient::HandlePreviewScenePreTick() { } void FAnimationViewportClient::HandlePreviewScenePostTick() { if (UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent()) { const bool bInBoneOrbitMode = CameraFollowMode != EAnimationViewportCameraFollowMode::None; if (IsTracking()) { if (const UAnimSingleNodeInstance* SingleNodeInstance = PreviewMeshComponent->GetSingleNodeInstance()) { if (SingleNodeInstance->IsPlaying() && GetDefault()->bPauseAnimationOnCameraMove) { PreviewMeshComponent->Stop(); bResumeAfterTracking = true; return; } } } else if (bResumeAfterTracking) { PreviewMeshComponent->Play(true); bResumeAfterTracking = false; } if (CameraFollowMode != EAnimationViewportCameraFollowMode::None) { FVector LookAtLocation = LastLookAtLocation; switch(CameraFollowMode) { case EAnimationViewportCameraFollowMode::Bounds: { const FBoxSphereBounds Bounds = PreviewMeshComponent->CalcGameBounds(PreviewMeshComponent->GetComponentTransform()); LookAtLocation = Bounds.Origin; } break; case EAnimationViewportCameraFollowMode::Root: { LookAtLocation = PreviewMeshComponent->GetBoneTransform(0).GetLocation(); LookAtLocation.Z = PreviewMeshComponent->CalcGameBounds(PreviewMeshComponent->GetComponentTransform()).Origin.Z; } break; case EAnimationViewportCameraFollowMode::Bone: { const int32 BoneIndex = PreviewMeshComponent->GetBoneIndex(CameraFollowBoneName); if (BoneIndex != INDEX_NONE) { LookAtLocation = PreviewMeshComponent->GetBoneTransform(BoneIndex).GetLocation(); if (GetShouldRotateCameraToFollowBone()) { OrbitRotation = PreviewMeshComponent->GetBoneQuaternion(CameraFollowBoneName) * FQuat(FVector(0.0f, 1.0f, 0.0f), PI * 0.5f); } } else { SetCameraFollowMode(EAnimationViewportCameraFollowMode::None); return; } } break; } const FVector Offset = LookAtLocation - LastLookAtLocation; SetLookAtLocation(GetLookAtLocation() + Offset); LastLookAtLocation = LookAtLocation; } } } 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, (FVector::FReal)DeltaSeconds, FollowCamera_InterpSpeed); NewViewLocation.Y = FMath::FInterpTo(OldViewLoc.Y, NewViewLocation.Y, (FVector::FReal)DeltaSeconds, FollowCamera_InterpSpeed); NewViewLocation.Z = FMath::FInterpTo(OldViewLoc.Z, NewViewLocation.Z, (FVector::FReal)DeltaSeconds, FollowCamera_InterpSpeed_Z); SetViewLocation( NewViewLocation ); } void FAnimationViewportClient::ShowBoneNames( FCanvas* Canvas, FSceneView* View, UDebugSkelMeshComponent* PreviewMeshComponent ) { if (!(PreviewMeshComponent && PreviewMeshComponent->MeshObject)) { return; } //Most of the code taken from FASVViewportClient::Draw() in AnimSetViewerMain.cpp FSkeletalMeshRenderData* SkelMeshRenderData = PreviewMeshComponent->GetSkeletalMeshRenderData(); check(SkelMeshRenderData); const int32 LODIndex = FMath::Clamp(PreviewMeshComponent->GetPredictedLODLevel(), 0, SkelMeshRenderData->LODRenderData.Num()-1); FSkeletalMeshLODRenderData& LODData = SkelMeshRenderData->LODRenderData[ LODIndex ]; // Check if our reference skeleton is out of synch with the one on the loddata const FReferenceSkeleton& ReferenceSkeleton = PreviewMeshComponent->GetReferenceSkeleton(); if (ReferenceSkeleton.GetNum() < LODData.RequiredBones.Num()) { return; } const int32 HalfX = static_cast(Viewport->GetSizeXY().X / 2 / GetDPIScale()); const int32 HalfY = static_cast(Viewport->GetSizeXY().Y / 2 / GetDPIScale()); for (int32 i=0; i< LODData.RequiredBones.Num(); i++) { const int32 BoneIndex = LODData.RequiredBones[i]; if (!BonesToDraw.IsValidIndex(BoneIndex) || !BonesToDraw[BoneIndex]) { continue; } // Skip drawing bone name of selected bone, already drawn in SkeletonSelectionEditMode if (PreviewMeshComponent == GetAnimPreviewScene()->GetPreviewMeshComponent() && GetAnimPreviewScene()->GetSelectedBoneIndex() == BoneIndex) { continue; } // If previewing a specific section, only show the bone names that belong to it if ((PreviewMeshComponent->GetSectionPreview() >= 0) && !LODData.RenderSections[PreviewMeshComponent->GetSectionPreview()].BoneMap.Contains(BoneIndex)) { continue; } if ((PreviewMeshComponent->GetMaterialPreview() >= 0)) { TArray FoundSectionIndex; for (int32 SectionIndex = 0; SectionIndex < LODData.RenderSections.Num(); ++SectionIndex) { if (LODData.RenderSections[SectionIndex].MaterialIndex == PreviewMeshComponent->GetMaterialPreview()) { FoundSectionIndex.Add(SectionIndex); break; } } if (FoundSectionIndex.Num() > 0) { bool PreviewSectionContainBoneIndex = false; for (int32 SectionIndex : FoundSectionIndex) { if (LODData.RenderSections[SectionIndex].BoneMap.Contains(BoneIndex)) { PreviewSectionContainBoneIndex = true; break; } } if (!PreviewSectionContainBoneIndex) { continue; } } } const FColor BoneColor = FColor::White; if (BoneColor.A != 0) { const FVector BonePos = PreviewMeshComponent->GetComponentTransform().TransformPosition(PreviewMeshComponent->GetDrawTransform(BoneIndex).GetLocation()); const FPlane proj = View->Project(BonePos); if (proj.W > 0.f) { const int32 XPos = static_cast(HalfX + ( HalfX * proj.X )); const int32 YPos = static_cast(HalfY + ( HalfY * (proj.Y * -1) )); const FName BoneName = ReferenceSkeleton.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::ShowAttributeNames(FCanvas* Canvas, FSceneView* View, UDebugSkelMeshComponent* MeshComponent) const { if (!MeshComponent || !MeshComponent->GetSkeletalMeshAsset()) { return; } const int32 HalfX = static_cast(Viewport->GetSizeXY().X / 2 / GetDPIScale()); const int32 HalfY = static_cast(Viewport->GetSizeXY().Y / 2 / GetDPIScale()); const UE::Anim::FMeshAttributeContainer& Attributes = MeshComponent->GetCustomAttributes(); const int32 TransformAnimationAttributeTypeIndex = Attributes.FindTypeIndex(FTransformAnimationAttribute::StaticStruct()); if (TransformAnimationAttributeTypeIndex != INDEX_NONE) { const TArray& AttributeIdentifiers = Attributes.GetKeys(TransformAnimationAttributeTypeIndex); const TArray>& AttributeValues = Attributes.GetValues(TransformAnimationAttributeTypeIndex); check(AttributeIdentifiers.Num() == AttributeValues.Num()); for (int32 AttributeIndex = 0; AttributeIndex < AttributeValues.Num(); ++AttributeIndex) { if (const FTransformAnimationAttribute* AttributeValue = AttributeValues[AttributeIndex].GetPtr()) { const UE::Anim::FAttributeId& AttributeIdentifier = AttributeIdentifiers[AttributeIndex]; const FTransform AttributeParentTransform = MeshComponent->GetDrawTransform(AttributeIdentifier.GetIndex()) * MeshComponent->GetComponentTransform(); const FTransform AttributeTransform = AttributeValue->Value * AttributeParentTransform; const FPlane proj = View->Project(AttributeTransform.GetLocation()); if (proj.W > 0.f) { const int32 XPos = HalfX + static_cast(HalfX * proj.X); const int32 YPos = HalfY + static_cast(HalfY * (proj.Y * -1)); FCanvasTextItem TextItem(FVector2D(XPos, YPos), FText::FromName(AttributeIdentifier.GetName()), GEngine->GetSmallFont(), FLinearColor(0.0f, 1.0f, 1.0f)); TextItem.EnableShadow(FLinearColor::Black); Canvas->DrawItem(TextItem); } } } } } bool FAnimationViewportClient::ShouldDisplayAdditiveScaleErrorMessage() const { UAnimSequence* AnimSequence = Cast(GetAnimPreviewScene()->GetPreviewAnimationAsset()); if (AnimSequence) { if (AnimSequence->IsValidAdditive() && AnimSequence->RefPoseSeq) { FGuid AnimSeqGuid = AnimSequence->RefPoseSeq->GetDataModel()->GenerateGuid(); if (RefPoseGuid != AnimSeqGuid) { RefPoseGuid = AnimSeqGuid; bDoesAdditiveRefPoseHaveZeroScale = AnimSequence->DoesSequenceContainZeroScale(); } return bDoesAdditiveRefPoseHaveZeroScale; } } RefPoseGuid.Invalidate(); return false; } extern FText ConcatenateLine(const FText& InText, const FText& InNewLine); FText FAnimationViewportClient::GetDisplayInfo(bool bDisplayAllInfo) const { FText TextValue; const UAssetViewerSettings* Settings = UAssetViewerSettings::Get(); const UEditorPerProjectUserSettings* PerProjectUserSettings = GetDefault(); const int32 ProfileIndex = Settings->Profiles.IsValidIndex(PerProjectUserSettings->AssetViewerProfileIndex) ? PerProjectUserSettings->AssetViewerProfileIndex : 0; // if not valid skeletalmesh UDebugSkelMeshComponent* PreviewMeshComponent = GetPreviewScene()->GetPreviewMeshComponent(); if (PreviewMeshComponent == nullptr || PreviewMeshComponent->GetSkeletalMeshAsset() == nullptr || PreviewMeshComponent->GetSkeletalMeshAsset()->IsCompiling()) { return FText(); } if (ShouldDisplayAdditiveScaleErrorMessage()) { TextValue = ConcatenateLine(TextValue, LOCTEXT("AdditiveRefPoseWarning", "Additive ref pose contains scales of 0.0, this can cause additive animations to not give the desired results")); } if (PreviewMeshComponent->GetSkeletalMeshAsset()->GetMorphTargets().Num() > 0) { TArray ProcessedMaterials; TArray MaterialsThatNeedMorphFlagOn; TArray MaterialsThatNeedSaving; const TIndirectArray& LODModels = PreviewMeshComponent->GetSkeletalMeshAsset()->GetImportedModel()->LODModels; const TArray& SkeletalMeshMaterials = PreviewMeshComponent->GetSkeletalMeshAsset()->GetMaterials(); int32 LodNumber = LODModels.Num(); TArray MaterialUsingMorphTarget; for (UMorphTarget *MorphTarget : PreviewMeshComponent->GetSkeletalMeshAsset()->GetMorphTargets()) { if (MorphTarget == nullptr) { continue; } for (const FMorphTargetLODModel& MorphTargetLODModel : MorphTarget->GetMorphLODModels()) { for (int32 SectionIndex : MorphTargetLODModel.SectionIndices) { for (int32 LodIdx = 0; LodIdx < LodNumber; LodIdx++) { const TArray& LODMaterialMap = PreviewMeshComponent->GetSkeletalMeshAsset()->GetLODInfo(LodIdx)->LODMaterialMap; const FSkeletalMeshLODModel& LODModel = LODModels[LodIdx]; if (LODModel.Sections.IsValidIndex(SectionIndex)) { int32 SectionMaterialIndex = LODModel.Sections[SectionIndex].MaterialIndex; if (LODMaterialMap.IsValidIndex(SectionIndex) && LODMaterialMap[SectionIndex] != INDEX_NONE) { SectionMaterialIndex = LODMaterialMap[SectionIndex]; } if (SkeletalMeshMaterials.IsValidIndex(SectionMaterialIndex)) { MaterialUsingMorphTarget.AddUnique(SkeletalMeshMaterials[SectionMaterialIndex].MaterialInterface); } } } } } } for (int i = 0; i < PreviewMeshComponent->GetNumMaterials(); ++i) { if (UMaterialInterface* MaterialInterface = PreviewMeshComponent->GetMaterial(i)) { UMaterial* Material = MaterialInterface->GetMaterial(); if ((Material != nullptr) && !ProcessedMaterials.Contains(Material)) { ProcessedMaterials.Add(Material); if (MaterialUsingMorphTarget.Contains(MaterialInterface) && !Material->GetUsageByFlag(MATUSAGE_MorphTargets)) { MaterialsThatNeedMorphFlagOn.Add(Material); } else if (Material->IsUsageFlagDirty(MATUSAGE_MorphTargets)) { MaterialsThatNeedSaving.Add(Material); } } } } if (MaterialsThatNeedMorphFlagOn.Num() > 0) { TextValue = ConcatenateLine(TextValue, LOCTEXT("MorphSupportNeeded", "The following materials need morph support ('Used with Morph Targets' in material editor):")); for(auto Iter = MaterialsThatNeedMorphFlagOn.CreateIterator(); Iter; ++Iter) { TextValue = ConcatenateLine(TextValue,FText::FromString((*Iter)->GetPathName())); } } if (MaterialsThatNeedSaving.Num() > 0) { TextValue = ConcatenateLine(TextValue, LOCTEXT("MaterialsNeedSaving", "The following materials need saving to fully support morph targets:")); for(auto Iter = MaterialsThatNeedSaving.CreateIterator(); Iter; ++Iter) { TextValue = ConcatenateLine(TextValue, FText::FromString((*Iter)->GetPathName())); } } } UAnimPreviewInstance* PreviewInstance = PreviewMeshComponent->PreviewInstance; if( PreviewInstance ) { // see if you have anim sequence that has transform curves if (UAnimSequence* Sequence = Cast(PreviewInstance->GetCurrentAsset())) { if (Sequence->IsCompressedDataOutOfDate()) { TextValue = ConcatenateLine(TextValue, LOCTEXT("ApplyToCompressedDataWarning", "Animation is being edited. To apply to compressed data (and recalculate baked additives), click \"Apply\"")); } } UAnimCompositeBase* CompositeBase = Cast(PreviewInstance->GetCurrentAsset()); if (CompositeBase && !CompositeBase->GetCommonTargetFrameRate().IsValid()) { FString AssetString; TArray Assets; if(CompositeBase->GetAllAnimationSequencesReferred(Assets, false)) { for (const UAnimationAsset* AnimAsset : Assets) { if (const UAnimSequenceBase* AnimSequenceBase = Cast(AnimAsset)) { AssetString.Append(FString::Printf(TEXT("\n\t%s - %s"), *AnimSequenceBase->GetName(), *AnimSequenceBase->GetSamplingFrameRate().ToPrettyText().ToString())); } } } TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("IncompatibleFrameRatesCompositeWarning", "{0} is composed of assets with incompatible framerates:{1}"), CompositeBase->GetClass()->GetDisplayNameText(), FText::FromString(AssetString))); } } if (PreviewMeshComponent->IsUsingInGameBounds()) { if (!PreviewMeshComponent->CheckIfBoundsAreCorrrect()) { if( PreviewMeshComponent->GetPhysicsAsset() == nullptr ) { TextValue = ConcatenateLine(TextValue, LOCTEXT("NeedToSetupPhysicsAssetForAccurateBounds", "You may need to setup Physics Asset to use more accurate bounds")); } else { TextValue = ConcatenateLine(TextValue, LOCTEXT("NeedToSetupBoundsInPhysicsAsset", "You need to setup bounds in Physics Asset to include whole mesh")); } } } if (PreviewMeshComponent != nullptr && PreviewMeshComponent->MeshObject != nullptr) { if (bDisplayAllInfo) { FSkeletalMeshRenderData* SkelMeshResource = PreviewMeshComponent->GetSkeletalMeshRenderData(); check(SkelMeshResource); // Draw stats about the mesh int32 NumBonesInUse; int32 NumBonesMappedToVerts; int32 NumSectionsInUse; const int32 LODIndex = FMath::Clamp(PreviewMeshComponent->GetPredictedLODLevel(), 0, SkelMeshResource->LODRenderData.Num() - 1); FSkeletalMeshLODRenderData& LODData = SkelMeshResource->LODRenderData[LODIndex]; NumBonesInUse = LODData.RequiredBones.Num(); NumBonesMappedToVerts = LODData.ActiveBoneIndices.Num(); NumSectionsInUse = LODData.RenderSections.Num(); // Calculate polys based on non clothing sections so we don't duplicate the counts. uint32 NumTotalTriangles = 0; int32 NumSections = LODData.RenderSections.Num(); for(int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) { NumTotalTriangles += LODData.RenderSections[SectionIndex].NumTriangles; } TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("MeshInfoFormat", "LOD: {0}, Bones: {1} (Mapped to Vertices: {2}), Polys: {3}"), FText::AsNumber(LODIndex), FText::AsNumber(NumBonesInUse), FText::AsNumber(NumBonesMappedToVerts), FText::AsNumber(NumTotalTriangles))); TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("ScreenSizeFOVFormat", "Current Screen Size: {0}, FOV: {1}"), FText::AsNumber(CachedScreenSize), FText::AsNumber(ViewFOV))); for (int32 SectionIndex = 0; SectionIndex < LODData.RenderSections.Num(); SectionIndex++) { int32 SectionVerts = LODData.RenderSections[SectionIndex].GetNumVertices(); FText SectionDisabledText = LODData.RenderSections[SectionIndex].bDisabled ? LOCTEXT("SectionIsDisbable", " Disabled") : FText::GetEmpty(); TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("SectionFormat", " [Section {0}]{1} Verts: {2}, Bones: {3}, Max Influences: {4}"), FText::AsNumber(SectionIndex), SectionDisabledText, FText::AsNumber(SectionVerts), FText::AsNumber(LODData.RenderSections[SectionIndex].BoneMap.Num()), FText::AsNumber(LODData.RenderSections[SectionIndex].MaxBoneInfluences) )); } TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("TotalVerts", "TOTAL Verts: {0}"), FText::AsNumber(LODData.GetNumVertices()))); TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("Sections", "Sections: {0}"), NumSectionsInUse )); TArray LocalBoneTransforms = PreviewMeshComponent->GetBoneSpaceTransforms(); if (PreviewMeshComponent->BonesOfInterest.Num() > 0) { int32 BoneIndex = PreviewMeshComponent->BonesOfInterest[0]; FTransform ReferenceTransform = PreviewMeshComponent->GetReferenceSkeleton().GetRefBonePose()[BoneIndex]; FTransform LocalTransform = LocalBoneTransforms[BoneIndex]; FTransform ComponentTransform = PreviewMeshComponent->GetDrawTransform(BoneIndex); auto GetDisplayTransform = [](const FTransform& InTransform) -> FText { FRotator R(InTransform.GetRotation()); FVector T(InTransform.GetTranslation()); FVector S(InTransform.GetScale3D()); FString Output = FString::Printf(TEXT("Rotation: X(Roll) %f Y(Pitch) %f Z(Yaw) %f\r\n"), R.Roll, R.Pitch, R.Yaw); Output += FString::Printf(TEXT("Translation: %f %f %f\r\n"), T.X, T.Y, T.Z); Output += FString::Printf(TEXT("Scale3D: %f %f %f\r\n"), S.X, S.Y, S.Z); return FText::FromString(Output); }; TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("LocalTransform", "Local: {0}"), GetDisplayTransform(LocalTransform))); TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("ComponentTransform", "Component: {0}"), GetDisplayTransform(ComponentTransform))); TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("ReferenceTransform", "Reference: {0}"), GetDisplayTransform(ReferenceTransform))); } TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("ApproximateSize", "Approximate Size: {0}x{1}x{2}"), FText::AsNumber(FMath::RoundToInt(PreviewMeshComponent->Bounds.BoxExtent.X * 2.0f)), FText::AsNumber(FMath::RoundToInt(PreviewMeshComponent->Bounds.BoxExtent.Y * 2.0f)), FText::AsNumber(FMath::RoundToInt(PreviewMeshComponent->Bounds.BoxExtent.Z * 2.0f)))); uint32 NumNotiesWithErrors = PreviewMeshComponent->AnimNotifyErrors.Num(); for (uint32 i = 0; i < NumNotiesWithErrors; ++i) { uint32 NumErrors = PreviewMeshComponent->AnimNotifyErrors[i].Errors.Num(); for (uint32 ErrorIdx = 0; ErrorIdx < NumErrors; ++ErrorIdx) { TextValue = ConcatenateLine(TextValue, FText::FromString(PreviewMeshComponent->AnimNotifyErrors[i].Errors[ErrorIdx])); } } } else // simplified default display info to be same as static mesh editor { FSkeletalMeshRenderData* SkelMeshResource = PreviewMeshComponent->GetSkeletalMeshRenderData(); check(SkelMeshResource); const int32 LODIndex = FMath::Clamp(PreviewMeshComponent->GetPredictedLODLevel(), 0, SkelMeshResource->LODRenderData.Num() - 1); FSkeletalMeshLODRenderData& LODData = SkelMeshResource->LODRenderData[LODIndex]; // Current LOD TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("LODFormat", "LOD: {0}"), FText::AsNumber(LODIndex))); // current screen size TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("ScreenSizeFormat", "Current Screen Size: {0}"), FText::AsNumber(CachedScreenSize))); // Triangles uint32 NumTotalTriangles = 0; int32 NumSections = LODData.RenderSections.Num(); for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) { NumTotalTriangles += LODData.RenderSections[SectionIndex].NumTriangles; } TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("TrianglesFormat", "Triangles: {0}"), FText::AsNumber(NumTotalTriangles))); // Vertices TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("VerticesFormat", "Vertices: {0}"), FText::AsNumber(LODData.GetNumVertices()))); // UV Channels TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("UVChannelsFormat", "UV Channels: {0}"), FText::AsNumber(LODData.GetNumTexCoords()))); // Approx Size TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("ApproxSize", "Approx Size: {0}x{1}x{2}"), FText::AsNumber(FMath::RoundToInt(PreviewMeshComponent->Bounds.BoxExtent.X * 2.0f)), FText::AsNumber(FMath::RoundToInt(PreviewMeshComponent->Bounds.BoxExtent.Y * 2.0f)), FText::AsNumber(FMath::RoundToInt(PreviewMeshComponent->Bounds.BoxExtent.Z * 2.0f)))); } // In case a skin weight profile is currently being previewed show the number of override skin weights it stores if (PreviewMeshComponent->IsUsingSkinWeightProfile()) { const FSkeletalMeshRenderData* SkelMeshResource = PreviewMeshComponent->GetSkeletalMeshRenderData(); check(SkelMeshResource); const int32 LODIndex = FMath::Clamp(PreviewMeshComponent->GetPredictedLODLevel(), 0, SkelMeshResource->LODRenderData.Num() - 1); const FSkeletalMeshLODRenderData& LODData = SkelMeshResource->LODRenderData[LODIndex]; const FName ProfileName = PreviewMeshComponent->GetCurrentSkinWeightProfileName(); const FRuntimeSkinWeightProfileData* OverrideData = LODData.SkinWeightProfilesData.GetOverrideData(ProfileName); TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("NumSkinWeightOverrides", "Skin Weight Profile Weights: {0}"), (OverrideData && OverrideData->NumWeightsPerVertex > 0) ? FText::AsNumber(OverrideData->BoneWeights.Num() / OverrideData->NumWeightsPerVertex) : LOCTEXT("NoSkinWeightsOverridesForLOD", "no data for LOD"))); } const bool bMirroring = PreviewMeshComponent->PreviewInstance && PreviewMeshComponent->PreviewInstance->GetMirrorDataTable(); if (bMirroring) { TextValue = ConcatenateLine(TextValue,FText::Format(LOCTEXT("Preview_mirrored", "Mirrored with {0} "), FText::FromString(PreviewMeshComponent->PreviewInstance->GetMirrorDataTable()->GetName()))); } } if (const IClothingSimulation* const ClothingSimulation = PreviewMeshComponent->GetClothingSimulation()) { // Cloth stats if (const int32 NumActiveCloths = ClothingSimulation->GetNumCloths()) { TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("NumActiveCloths", "Active Cloths: {0}"), NumActiveCloths)); } if (const int32 NumKinematicParticles = ClothingSimulation->GetNumKinematicParticles()) { TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("NumKinematicParticles", "Kinematic Particles: {0}"), NumKinematicParticles)); } if (const int32 NumDynamicParticles = ClothingSimulation->GetNumDynamicParticles()) { TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("NumDynamicParticles", "Dynamic Particles: {0}"), NumDynamicParticles)); } if (const int32 NumIterations = ClothingSimulation->GetNumIterations()) { TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("NumIterations", "Iterations: {0}"), NumIterations)); } if (const float SimulationTime = ClothingSimulation->GetSimulationTime()) { FNumberFormattingOptions NumberFormatOptions; NumberFormatOptions.AlwaysSign = false; NumberFormatOptions.UseGrouping = false; NumberFormatOptions.RoundingMode = ERoundingMode::HalfFromZero; NumberFormatOptions.MinimumIntegralDigits = 1; NumberFormatOptions.MaximumIntegralDigits = 6; NumberFormatOptions.MinimumFractionalDigits = 2; NumberFormatOptions.MaximumFractionalDigits = 2; TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("SimulationTime", "Simulation Time: {0}ms"), FText::AsNumber(SimulationTime, &NumberFormatOptions))); } if (ClothingSimulation->IsTeleported()) { TextValue = ConcatenateLine(TextValue, LOCTEXT("IsTeleported", "Simulation Teleport Activated")); } } if (PreviewMeshComponent->GetSectionPreview() != INDEX_NONE) { // Notify the user if they are isolating a mesh section. TextValue = ConcatenateLine(TextValue, LOCTEXT("MeshSectionsHiddenWarning", "Mesh Sections Hidden")); } if (PreviewMeshComponent->GetMaterialPreview() != INDEX_NONE) { // Notify the user if they are isolating a mesh section. TextValue = ConcatenateLine(TextValue, LOCTEXT("MeshMaterialHiddenWarning", "Mesh Materials Hidden")); } if (const UAnimSequenceBase* AnimSequenceBase = Cast(GetAnimPreviewScene()->GetPreviewAnimationAsset())) { TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("FramerateFormat", "Framerate: {0}"), AnimSequenceBase->GetSamplingFrameRate().ToPrettyText())); } if (const UPoseAsset* PoseAsset = Cast(GetAnimPreviewScene()->GetPreviewAnimationAsset())) { if (PoseAsset->GetLinkerCustomVersion(FUE5MainStreamObjectVersion::GUID) >= FUE5MainStreamObjectVersion::PoseAssetRawDataGUIDUpdate && PoseAsset->SourceAnimation && PoseAsset->SourceAnimationRawDataGUID.IsValid() && PoseAsset->GetSourceAnimationGuid() != PoseAsset->SourceAnimationRawDataGUID) { TextValue = ConcatenateLine(TextValue, LOCTEXT("PoseAssetOutOfDateWarning", "Poses are out-of-sync with the source animation. To update them click \"Update Source\"")); } } if (TimecodeDisplay) { TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("TimecodeInfo", "Timecode: {0}"), FText::FromString(TimecodeDisplay->QualifiedTime.ToTimecode().ToString()))); TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("SlateName", "Slate: {0}"), FText::FromString(TimecodeDisplay->Slate))); TextValue = ConcatenateLine(TextValue, FText::Format(LOCTEXT("Rate", "Rate: {0}"), FText::AsNumber(TimecodeDisplay->QualifiedTime.Rate.AsDecimal()))); } return TextValue; } void FAnimationViewportClient::DrawNodeDebugLines(TArray& Lines, FCanvas* Canvas, FSceneView* View) { if(Lines.Num() > 0) { int32 CurrentXOffset = 5; int32 CurrentYOffset = 60; int32 CharWidth; int32 CharHeight; StringSize(GEngine->GetSmallFont(), CharWidth, CharHeight, TEXT("0")); const int32 LineHeight = CharHeight + 2; for(FText& Line : Lines) { FCanvasTextItem TextItem(FVector2D(CurrentXOffset, CurrentYOffset), Line, GEngine->GetSmallFont(), FLinearColor::White); TextItem.EnableShadow(FLinearColor::Black); Canvas->DrawItem(TextItem); CurrentYOffset += LineHeight; } } } void FAnimationViewportClient::TrackingStarted( const struct FInputEventState& InInputState, bool bIsDraggingWidget, bool bNudge ) { ModeTools->StartTracking(this, Viewport); } void FAnimationViewportClient::TrackingStopped() { ModeTools->EndTracking(this, Viewport); Invalidate(); } FVector FAnimationViewportClient::GetWidgetLocation() const { return ModeTools->GetWidgetLocation(); } FMatrix FAnimationViewportClient::GetWidgetCoordSystem() const { const ECoordSystem Space = GetWidgetCoordSystemSpace(); const bool bComputeOrientation = Space == COORD_Local || Space == COORD_Parent || Space == COORD_Explicit; if (bComputeOrientation) { return ModeTools->GetCustomInputCoordinateSystem(); } 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(AssetEditorToolkitPtr.Pin()->GetEditorName(), InViewModeIndex, ViewportIndex); } void FAnimationViewportClient::SetViewportType(ELevelViewportType InViewportType) { FEditorViewportClient::SetViewportType(InViewportType); FocusViewportOnPreviewMesh(true); if(CameraFollowMode != EAnimationViewportCameraFollowMode::None) { bUsingOrbitCamera = true; } if (InViewportType != ELevelViewportType::LVT_Perspective) { SetCameraFollowMode(EAnimationViewportCameraFollowMode::None); } } void FAnimationViewportClient::RotateViewportType() { FEditorViewportClient::RotateViewportType(); FocusViewportOnPreviewMesh(true); if(CameraFollowMode != EAnimationViewportCameraFollowMode::None) { bUsingOrbitCamera = true; } } bool FAnimationViewportClient::InputKey(const FInputKeyEventArgs& EventArgs) { bool bHandled = false; FAdvancedPreviewScene* AdvancedScene = static_cast(PreviewScene); bHandled |= AdvancedScene->HandleInputKey(EventArgs); // Pass keys to standard controls, if we didn't consume input return (bHandled) ? true : FEditorViewportClient::InputKey(EventArgs); } bool FAnimationViewportClient::InputAxis(const FInputKeyEventArgs& EventArgs) { bool bResult = true; if (!bDisableInput) { FAdvancedPreviewScene* AdvancedScene = (FAdvancedPreviewScene*)PreviewScene; bResult = AdvancedScene->HandleViewportInput(EventArgs.Viewport, EventArgs.InputDevice, EventArgs.Key, EventArgs.AmountDepressed, EventArgs.DeltaTime, EventArgs.NumSamples, EventArgs.IsGamepad()); if (bResult) { Invalidate(); } else { bResult = FEditorViewportClient::InputAxis(EventArgs); } } return bResult; } void FAnimationViewportClient::SetLocalAxesMode(ELocalAxesMode::Type AxesMode) { ConfigOption->SetDefaultLocalAxesSelection( AxesMode ); } bool FAnimationViewportClient::IsLocalAxesModeSet( ELocalAxesMode::Type AxesMode ) const { return (ELocalAxesMode::Type)ConfigOption->DefaultLocalAxesSelection == AxesMode; } ELocalAxesMode::Type FAnimationViewportClient::GetLocalAxesMode() const { return (ELocalAxesMode::Type)ConfigOption->DefaultLocalAxesSelection; } void FAnimationViewportClient::SetBoneDrawSize(const float InBoneDrawSize) { BoneDrawSize = FMath::Max(0.001f,InBoneDrawSize); // optionally inform editors that may want to maintain bone size between sessions if (OnSetBoneSize.IsBound()) { OnSetBoneSize.Execute(BoneDrawSize); } RedrawRequested(nullptr); } float FAnimationViewportClient::GetBoneDrawSize() const { // optionally get bone size from editors that may be storing this between sessions if (OnGetBoneSize.IsBound()) { return OnGetBoneSize.Execute(); } return BoneDrawSize; } void FAnimationViewportClient::SetBoneDrawMode(EBoneDrawMode::Type AxesMode) { ConfigOption->SetDefaultBoneDrawSelection(AxesMode); RedrawRequested(Viewport); UpdateBonesToDraw(); } bool FAnimationViewportClient::IsBoneDrawModeSet(EBoneDrawMode::Type AxesMode) const { return (EBoneDrawMode::Type)ConfigOption->DefaultBoneDrawSelection == AxesMode; } EBoneDrawMode::Type FAnimationViewportClient::GetBoneDrawMode() const { return (EBoneDrawMode::Type)ConfigOption->DefaultBoneDrawSelection; } void FAnimationViewportClient::DrawBonesFromTransforms( TArray& Transforms, UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI, FLinearColor BoneColour, FLinearColor RootBoneColour) const { if (Transforms.IsEmpty() || !MeshComponent->GetSkeletalMeshAsset() || MeshComponent->SkeletonDrawMode == ESkeletonDrawMode::Hidden) { return; } if (MeshComponent->SkeletonDrawMode == ESkeletonDrawMode::GreyedOut) { BoneColour = GetDefault()->DisabledBoneColor; } TArray WorldTransforms; WorldTransforms.AddUninitialized(Transforms.Num()); TArray 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 const TArray& DrawBoneIndices = MeshComponent->GetDrawBoneIndices(); for ( int32 Index=0; Index < MeshComponent->GetDrawBoneIndices().Num(); ++Index ) { const int32 BoneIndex = DrawBoneIndices[Index]; const int32 ParentIndex = MeshComponent->GetReferenceSkeleton().GetParentIndex(BoneIndex); WorldTransforms[BoneIndex] = Transforms[BoneIndex] * MeshComponent->GetComponentTransform(); BoneColours[BoneIndex] = (ParentIndex >= 0) ? BoneColour : RootBoneColour; } constexpr bool bForceDraw = false; const bool bAddHitProxy = MeshComponent->SkeletonDrawMode != ESkeletonDrawMode::GreyedOut; const bool bUseMuliColors = GetDefault()->bShowBoneColors; DrawBones( MeshComponent->GetComponentLocation(), DrawBoneIndices, MeshComponent->GetReferenceSkeleton(), WorldTransforms, MeshComponent->BonesOfInterest, BoneColours, PDI, bForceDraw, bAddHitProxy, bUseMuliColors); } void FAnimationViewportClient::DrawBonesFromCompactPose( const FCompactHeapPose& Pose, UDebugSkelMeshComponent* MeshComponent, FPrimitiveDrawInterface* PDI, const FLinearColor& DrawColor) const { if (Pose.GetNumBones() == 0 || !MeshComponent || !MeshComponent->GetSkeletalMeshAsset() || MeshComponent->SkeletonDrawMode == ESkeletonDrawMode::Hidden) { return; } TArray WorldTransforms; WorldTransforms.AddUninitialized(Pose.GetBoneContainer().GetNumBones()); // we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this for (FCompactPoseBoneIndex BoneIndex : Pose.ForEachBoneIndex()) { FMeshPoseBoneIndex MeshBoneIndex = Pose.GetBoneContainer().MakeMeshPoseIndex(BoneIndex); int32 ParentIndex = Pose.GetBoneContainer().GetParentBoneIndex(MeshBoneIndex.GetInt()); if (ParentIndex == INDEX_NONE) { WorldTransforms[MeshBoneIndex.GetInt()] = Pose[BoneIndex] * MeshComponent->GetComponentTransform(); } else { WorldTransforms[MeshBoneIndex.GetInt()] = Pose[BoneIndex] * WorldTransforms[ParentIndex]; } } constexpr bool bForceDraw = true; const bool bAddHitProxy = MeshComponent->SkeletonDrawMode != ESkeletonDrawMode::GreyedOut; const bool bUseMultiColor = GetDefault()->bShowBoneColors; DrawBones( MeshComponent->GetComponentLocation(), MeshComponent->GetDrawBoneIndices(), MeshComponent->GetReferenceSkeleton(), WorldTransforms, MeshComponent->BonesOfInterest, TArray(), PDI, bForceDraw, bAddHitProxy, bUseMultiColor); } void FAnimationViewportClient::DrawMeshBonesUncompressedAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const { if ( MeshComponent && MeshComponent->GetSkeletalMeshAsset()) { 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->GetSkeletalMeshAsset()) { 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->GetSkeletalMeshAsset()) { 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->GetSkeletalMeshAsset()) { DrawBonesFromTransforms(MeshComponent->SourceAnimationPoses, MeshComponent, PDI, FColor(195, 195, 195, 255), FColor(195, 159, 195, 255)); } } void FAnimationViewportClient::DrawWatchedPoses(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) { if (UAnimBlueprintGeneratedClass* AnimBPGenClass = Cast(MeshComponent->AnimClass)) { if (UAnimBlueprint* Blueprint = Cast(AnimBPGenClass->ClassGeneratedBy)) { if (const UAnimInstance* DebuggedAnimInstance = Cast(Blueprint->GetObjectBeingDebugged())) { if (const USkeletalMeshComponent* DebuggedSkeletalMeshComponent = DebuggedAnimInstance->GetSkelMeshComponent()) { if (const USkeletalMesh* SkeletalMesh = DebuggedSkeletalMeshComponent->GetSkeletalMeshAsset()) { FAnimBlueprintDebugData& DebugData = AnimBPGenClass->GetAnimBlueprintDebugData(); DebugData.ForEachActiveVisiblePoseWatchPoseElement([PDI, SkeletalMesh](FAnimNodePoseWatch& PoseWatch) { PoseWatch.CopyPoseWatchData(SkeletalMesh->GetRefSkeleton()); SkeletalDebugRendering::DrawBonesFromPoseWatch(PDI, PoseWatch, /*bUseWorldTransform*/false); }); } } } } } } void FAnimationViewportClient::DrawMeshBonesBakedAnimation(UDebugSkelMeshComponent * MeshComponent, FPrimitiveDrawInterface* PDI) const { if(MeshComponent && MeshComponent->GetSkeletalMeshAsset()) { DrawBonesFromTransforms(MeshComponent->BakedAnimationPoses, MeshComponent, PDI, FColor(0, 128, 192, 255), FColor(0, 128, 192, 255)); } } void FAnimationViewportClient::DrawBonesFromSkeleton(UDebugSkelMeshComponent * MeshComponent, const USkeleton* Skeleton, const TArray& InSelectedBones,FPrimitiveDrawInterface* PDI) const { check(Skeleton); // Draw from skeleton ref pose const TArray& SkeletonRefPose = Skeleton->GetRefLocalPoses(NAME_None); TArray WorldTransforms; WorldTransforms.AddUninitialized(SkeletonRefPose.Num()); TArray BoneColours; BoneColours.AddUninitialized(SkeletonRefPose.Num()); TArray RequiredBones; const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton(); for (FBoneIndexType BoneIndex = 0; BoneIndex < SkeletonRefPose.Num(); ++BoneIndex) { const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex); //add to the list RequiredBones.AddUnique(BoneIndex); if (ParentIndex >= 0) { WorldTransforms[BoneIndex] = SkeletonRefPose[BoneIndex] * WorldTransforms[ParentIndex]; } else { WorldTransforms[BoneIndex] = SkeletonRefPose[BoneIndex]; } BoneColours[BoneIndex] = MeshComponent->GetBoneColor(BoneIndex); } // color virtual bones const FLinearColor VirtualBoneColor = GetDefault()->VirtualBoneColor; for (const int16 VirtualBoneIndex : RefSkeleton.GetRequiredVirtualBones()) { BoneColours[VirtualBoneIndex] = VirtualBoneColor; } constexpr bool bForceDraw = false; constexpr bool bAddHitProxy = true; const bool bUseMultiColor = GetDefault()->bShowBoneColors; DrawBones( FVector::ZeroVector, RequiredBones, RefSkeleton, WorldTransforms, InSelectedBones, BoneColours, PDI, bForceDraw, bAddHitProxy, bUseMultiColor); } void FAnimationViewportClient::UpdateBonesToDraw() { if (UDebugSkelMeshComponent* MeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent()) { const FReferenceSkeleton& RefSkeleton = MeshComponent->GetReferenceSkeleton(); TArray ParentIndices; ParentIndices.AddUninitialized(RefSkeleton.GetNum()); for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex) { ParentIndices[BoneIndex] = RefSkeleton.GetParentIndex(BoneIndex); } SkeletalDebugRendering::CalculateBonesToDraw( ParentIndices, MeshComponent->BonesOfInterest, GetBoneDrawMode(), BonesToDraw); } } void FAnimationViewportClient::DrawMeshBones(UDebugSkelMeshComponent* MeshComponent, FPrimitiveDrawInterface* PDI) const { if (!MeshComponent || !MeshComponent->GetSkeletalMeshAsset() || MeshComponent->GetNumDrawTransform() == 0 || MeshComponent->SkeletonDrawMode == ESkeletonDrawMode::Hidden) { return; } TArray WorldTransforms; WorldTransforms.AddUninitialized(MeshComponent->GetNumDrawTransform()); TArray BoneColours; BoneColours.AddUninitialized(MeshComponent->GetNumDrawTransform()); // factor skeleton draw mode into color selection const FLinearColor BoneColor = MeshComponent->SkeletonDrawMode == ESkeletonDrawMode::GreyedOut ? GetDefault()->DisabledBoneColor : GetDefault()->DefaultBoneColor; const FLinearColor VirtualBoneColor = MeshComponent->SkeletonDrawMode == ESkeletonDrawMode::GreyedOut ? GetDefault()->DisabledBoneColor : GetDefault()->VirtualBoneColor; // we could cache parent bones as we calculate, but right now I'm not worried about perf issue of this const TArray& DrawBoneIndices = MeshComponent->GetDrawBoneIndices(); for ( int32 Index=0; IndexGetDrawTransform(BoneIndex) * MeshComponent->GetComponentTransform(); BoneColours[BoneIndex] = MeshComponent->GetBoneColor(BoneIndex); } // color virtual bones for (int16 VirtualBoneIndex : MeshComponent->GetReferenceSkeleton().GetRequiredVirtualBones()) { BoneColours[VirtualBoneIndex] = VirtualBoneColor; } constexpr bool bForceDraw = false; // don't allow selection if the skeleton draw mode is greyed out const bool bAddHitProxy = MeshComponent->SkeletonDrawMode != ESkeletonDrawMode::GreyedOut; const bool bUseMultiColors = GetDefault()->bShowBoneColors; DrawBones( MeshComponent->GetComponentLocation(), DrawBoneIndices, MeshComponent->GetReferenceSkeleton(), WorldTransforms, MeshComponent->BonesOfInterest, BoneColours, PDI, bForceDraw, bAddHitProxy, bUseMultiColors); } void FAnimationViewportClient::DrawBones( const FVector& ComponentOrigin, const TArray& RequiredBones, const FReferenceSkeleton& RefSkeleton, const TArray& WorldTransforms, const TArray& InSelectedBones, const TArray& BoneColors, FPrimitiveDrawInterface* PDI, bool bForceDraw, bool bAddHitProxy, bool bUseMultiColors) const { FSkelDebugDrawConfig DrawConfig; DrawConfig.BoneDrawMode = GetBoneDrawMode(); DrawConfig.BoneDrawSize = GetBoneDrawSize(); DrawConfig.bAddHitProxy = bAddHitProxy; DrawConfig.bForceDraw = bForceDraw; DrawConfig.bUseMultiColorAsDefaultColor = bUseMultiColors; DrawConfig.DefaultBoneColor = GetMutableDefault()->DefaultBoneColor; DrawConfig.AffectedBoneColor = GetMutableDefault()->AffectedBoneColor; DrawConfig.SelectedBoneColor = GetMutableDefault()->SelectedBoneColor; DrawConfig.ParentOfSelectedBoneColor = GetMutableDefault()->ParentOfSelectedBoneColor; TArray> HitProxies; if (bAddHitProxy) { HitProxies.Reserve(RefSkeleton.GetNum()); for (int32 Index = 0; Index < RefSkeleton.GetNum(); ++Index) { HitProxies.Add(new HPersonaBoneHitProxy(Index, RefSkeleton.GetBoneName(Index))); } } SkeletalDebugRendering::DrawBones( PDI, ComponentOrigin, RequiredBones, RefSkeleton, WorldTransforms, InSelectedBones, BoneColors, HitProxies, DrawConfig, BonesToDraw ); } void FAnimationViewportClient::DrawAttributes(UDebugSkelMeshComponent* MeshComponent, FPrimitiveDrawInterface* PDI) const { if (MeshComponent && MeshComponent->GetSkeletalMeshAsset()) { const UE::Anim::FMeshAttributeContainer& Attributes = MeshComponent->GetCustomAttributes(); const int32 TransformAnimationAttributeTypeIndex = Attributes.FindTypeIndex(FTransformAnimationAttribute::StaticStruct()); if (TransformAnimationAttributeTypeIndex != INDEX_NONE) { const TArray& AttributeIdentifiers = Attributes.GetKeys(TransformAnimationAttributeTypeIndex); const TArray>& AttributeValues = Attributes.GetValues(TransformAnimationAttributeTypeIndex); check(AttributeIdentifiers.Num() == AttributeValues.Num()); for (int32 AttributeIndex = 0; AttributeIndex < AttributeValues.Num(); ++AttributeIndex) { if (const FTransformAnimationAttribute* AttributeValue = AttributeValues[AttributeIndex].GetPtr()) { const UE::Anim::FAttributeId& AttributeIdentifier = AttributeIdentifiers[AttributeIndex]; const FTransform AttributeParentTransform = MeshComponent->GetDrawTransform(AttributeIdentifier.GetIndex()) * MeshComponent->GetComponentTransform(); const FTransform AttributeTransform = AttributeValue->Value * AttributeParentTransform; DrawWireDiamond(PDI, AttributeTransform.ToMatrixNoScale(), 2.0f, FLinearColor(0.0f, 1.0f, 1.0f), SDPG_Foreground); SkeletalDebugRendering::DrawAxes(PDI, AttributeTransform, SDPG_Foreground, 0.0f, 10.0f); //DrawDashedLine(PDI, AttributeTransform.GetLocation(), AttributeParentTransform.GetLocation(), FLinearColor(0.0f, 1.0f, 1.0f), 2.0f, SDPG_World); } } } } } void FAnimationViewportClient::DrawNotifies(UDebugSkelMeshComponent* MeshComponent, FPrimitiveDrawInterface* PDI) const { if (MeshComponent && MeshComponent->IsNotificationVisualizationsEnabled() && MeshComponent->GetSkeletalMeshAsset()) { if (const UAnimSequenceBase* AnimSequenceBase = Cast(GetAnimPreviewScene()->GetPreviewAnimationAsset())) { for (const FAnimNotifyEvent& Notify : AnimSequenceBase->Notifies) { if (Notify.Notify) { Notify.Notify->DrawInEditor(PDI, MeshComponent, AnimSequenceBase, Notify); } if (Notify.NotifyStateClass) { Notify.NotifyStateClass->DrawInEditor(PDI, MeshComponent, AnimSequenceBase, Notify); } } } } } void FAnimationViewportClient::DrawCanvasNotifies(UDebugSkelMeshComponent* MeshComponent, FCanvas& Canvas, FSceneView& View) const { if (MeshComponent && MeshComponent->IsNotificationVisualizationsEnabled() && MeshComponent->GetSkeletalMeshAsset()) { if (const UAnimSequenceBase* AnimSequenceBase = Cast(GetAnimPreviewScene()->GetPreviewAnimationAsset())) { for (const FAnimNotifyEvent& Notify : AnimSequenceBase->Notifies) { if (Notify.Notify) { Notify.Notify->DrawCanvasInEditor(Canvas, View, MeshComponent, AnimSequenceBase, Notify); } if (Notify.NotifyStateClass) { Notify.NotifyStateClass->DrawCanvasInEditor(Canvas, View, MeshComponent, AnimSequenceBase, Notify); } } } } } TArray FAnimationViewportClient::GetEditedObjectsWithAssetUserData() const { TArray Result; if (const TSharedPtr AssetEditorToolkit = AssetEditorToolkitPtr.Pin()) { if (const TArray* ObjectsCurrentlyBeingEdited = AssetEditorToolkit->GetObjectsCurrentlyBeingEdited()) { for (UObject* Object : *ObjectsCurrentlyBeingEdited) { if (IInterface_AssetUserData* AssetUserDataInterface = Cast(Object)) { Result.Add(AssetUserDataInterface); } } } } return Result; } void FAnimationViewportClient::DrawAssetUserData(FPrimitiveDrawInterface* PDI) const { TArray AssetsWithUserData = GetEditedObjectsWithAssetUserData(); for (const IInterface_AssetUserData* AssetUserDataInterface : AssetsWithUserData) { if (const TArray* AssetUserDataArray = AssetUserDataInterface->GetAssetUserDataArray()) { for (const UAssetUserData* AssetUserData : *AssetUserDataArray) { if (AssetUserData) { AssetUserData->Draw(PDI, PDI->View); } } } } } void FAnimationViewportClient::DrawCanvasAssetUserData(FCanvas& Canvas, FSceneView& View) const { TArray AssetsWithUserData = GetEditedObjectsWithAssetUserData(); for (const IInterface_AssetUserData* AssetUserDataInterface : AssetsWithUserData) { if (const TArray* AssetUserDataArray = AssetUserDataInterface->GetAssetUserDataArray()) { for (const UAssetUserData* AssetUserData : *AssetUserDataArray) { if (AssetUserData) { AssetUserData->DrawCanvas(Canvas, View); } } } } } void FAnimationViewportClient::DrawRootMotionTrajectory(UDebugSkelMeshComponent* MeshComponent, FPrimitiveDrawInterface* PDI) const { constexpr float DepthBias = 2.0f; constexpr bool bScreenSpace = true; if (MeshComponent && !MeshComponent->IsVisualizeRootMotionMode(EVisualizeRootMotionMode::None) && MeshComponent->GetSkeletalMeshAsset() && MeshComponent->DoesCurrentAssetHaveRootMotion()) { const EVisualizeRootMotionMode VisMode = MeshComponent->GetVisualizeRootMotionMode(); const FTransform& ReferenceTransform = MeshComponent->RootMotionReferenceTransform; const UMirrorDataTable* MirrorTable = MeshComponent->PreviewInstance ? MeshComponent->PreviewInstance->GetMirrorDataTable() : nullptr; if (const UAnimSequenceBase* AnimSequenceBase = Cast(GetAnimPreviewScene()->GetPreviewAnimationAsset())) { // Draw root motion trajectory const int32 NumFrames = AnimSequenceBase->GetNumberOfSampledKeys(); const FFrameRate FrameRate = AnimSequenceBase->GetSamplingFrameRate(); const float CurrentTime = MeshComponent->GetPosition(); const USkeleton* Skeleton = AnimSequenceBase->GetSkeleton(); check(Skeleton); EAxis::Type SkeletonForwardAxis = Skeleton->GetPreviewForwardAxis(); const FColor TrajectoryColor = FColor::Black.WithAlpha(64); FVector PrevLocation; float PlayLength = AnimSequenceBase->GetPlayLength(); PlayLength = AnimSequenceBase->GetPlayLength(); for (int32 Frame = 0; Frame <= NumFrames; Frame++) { const double Time = FMath::Clamp(FrameRate.AsSeconds(Frame), 0., (double)PlayLength); const FTransform Transform = UE::Anim::ExtractRootMotionFromAnimationAsset(AnimSequenceBase, MirrorTable, 0.0, Time) * ReferenceTransform; const FVector Location = Transform.GetLocation(); const bool bFirstOrLastPoint = Frame == 0 || Frame == NumFrames; PDI->DrawPoint(Location, TrajectoryColor, bFirstOrLastPoint ? 2.5f : 1.25f, SDPG_World); if (VisMode == EVisualizeRootMotionMode::TrajectoryAndOrientation) { if (bFirstOrLastPoint || (Frame % 3 == 0)) { const FVector XAxis = Transform.GetUnitAxis(SkeletonForwardAxis); const FColor AxisColor = UE::Private::GetColorForAxis(SkeletonForwardAxis); FVector YAxis, ZAxis; XAxis.FindBestAxisVectors(YAxis,ZAxis); UE::Private::DrawFlatArrow(PDI, Transform.GetLocation(), XAxis, ZAxis, AxisColor.WithAlpha(64), 15.0f, 8, nullptr, SDPG_World, 1.0f); } } if (Frame > 0) { PDI->DrawTranslucentLine(PrevLocation, Location, TrajectoryColor, SDPG_World, 1.0f, DepthBias, bScreenSpace); } PrevLocation = Location; } // Draw current location on the root motion. { const FTransform Transform = UE::Anim::ExtractRootMotionFromAnimationAsset(AnimSequenceBase, MirrorTable, 0.0, CurrentTime) * ReferenceTransform; const FVector XAxis = Transform.GetUnitAxis(SkeletonForwardAxis); const FColor AxisColor = UE::Private::GetColorForAxis(SkeletonForwardAxis); FVector YAxis, ZAxis; XAxis.FindBestAxisVectors(YAxis,ZAxis); if (VisMode == EVisualizeRootMotionMode::TrajectoryAndOrientation) { UE::Private::DrawFlatArrow(PDI, Transform.GetLocation(), XAxis, ZAxis, AxisColor, 30.0f, 15, GEngine->ArrowMaterialYellow->GetRenderProxy(), SDPG_Foreground, 1.0f); } UE::Private::DrawCoordinateSystem(PDI, Transform, 10.0f, 20.0f, DepthBias, bScreenSpace, 200); } } } } void FAnimationViewportClient::DrawSockets(const UDebugSkelMeshComponent* InPreviewMeshComponent, TArray& InSockets, FSelectedSocketInfo InSelectedSocket, FPrimitiveDrawInterface* PDI, bool bUseSkeletonSocketColor) { if (InPreviewMeshComponent && InPreviewMeshComponent->GetSkeletalMeshAsset()) { ELocalAxesMode::Type LocalAxesMode = (ELocalAxesMode::Type)GetDefault()->DefaultLocalAxesSelection; for ( auto SocketIt = InSockets.CreateConstIterator(); SocketIt; ++SocketIt ) { USkeletalMeshSocket* Socket = *(SocketIt); const FReferenceSkeleton& RefSkeleton = InPreviewMeshComponent->GetReferenceSkeleton(); const int32 ParentIndex = RefSkeleton.FindBoneIndex(Socket->BoneName); FTransform WorldTransformSocket = Socket->GetSocketTransform(InPreviewMeshComponent); FLinearColor SocketColor; FVector Start, End; if (ParentIndex >=0) { FTransform WorldTransformParent = InPreviewMeshComponent->GetDrawTransform(ParentIndex) * InPreviewMeshComponent->GetComponentTransform(); Start = WorldTransformParent.GetLocation(); End = WorldTransformSocket.GetLocation(); } else { Start = FVector::ZeroVector; End = WorldTransformSocket.GetLocation(); } const bool bSelectedSocket = (InSelectedSocket.Socket == Socket); if( bSelectedSocket ) { SocketColor = FLinearColor(1.0f, 0.34f, 0.0f, 1.0f); } else { SocketColor = (bUseSkeletonSocketColor) ? FLinearColor::White : FLinearColor::Red; } //Render Sphere for bone end point and a cone between it and its parent. PDI->DrawLine( Start, End, SocketColor, SDPG_Foreground ); // draw gizmo if( (LocalAxesMode == ELocalAxesMode::All) || bSelectedSocket ) { FMatrix SocketMatrix; Socket->GetSocketMatrix( SocketMatrix, InPreviewMeshComponent); PDI->SetHitProxy(new HPersonaSocketHitProxy(Socket)); DrawWireDiamond( PDI, SocketMatrix, 2.f, SocketColor, SDPG_Foreground ); PDI->SetHitProxy(nullptr); SkeletalDebugRendering::DrawAxes(PDI, FTransform(SocketMatrix), SDPG_Foreground); } } } } FSphere FAnimationViewportClient::GetCameraTarget() { // give the editor mode a chance to give us a camera target if (IPersonaManagerContext* PersonaModeManagerContext = GetModeTools()->GetInteractiveToolsContext()->ContextObjectStore->FindContext()) { FSphere Target; if (PersonaModeManagerContext->GetCameraTarget(Target)) { return Target; } } const FSphere DefaultSphere(FVector(0,0,0), 100.0f); UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if( !PreviewMeshComponent ) { return DefaultSphere; } AActor* Actor = PreviewMeshComponent->GetOwner(); FBox LocalBox(ForceInit); if (GetModeTools()->ComputeBoundingBoxForViewportFocus(Actor, PreviewMeshComponent, LocalBox)) { return FBoxSphereBounds(LocalBox).GetSphere(); } FBoxSphereBounds Bounds = PreviewMeshComponent->CalcGameBounds(FTransform::Identity); return Bounds.GetSphere(); } void FAnimationViewportClient::UpdateCameraSetup() { static FRotator CustomOrbitRotation(-33.75, -135, 0); UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if (PreviewMeshComponent != nullptr && PreviewMeshComponent->GetSkeletalMeshAsset()) { 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 = PreviewMeshComponent->Bounds.GetBoxExtrema(0); FVector FloorPos(0.f, 0.f, GetFloorOffset()); if (bAutoAlignFloor) { FloorPos.Z += Bottom.Z; } GetAnimPreviewScene()->SetFloorLocation(FloorPos); } } void FAnimationViewportClient::FocusViewportOnSphere( FSphere& Sphere, bool bInstant /*= true*/ ) { FBox Box( Sphere.Center - FVector(Sphere.W, 0.0f, 0.0f), Sphere.Center + FVector(Sphere.W, 0.0f, 0.0f) ); FocusViewportOnBox( Box, bInstant ); Invalidate(); } void FAnimationViewportClient::TransformVertexPositionsToWorld(TArray& LocalVertices) const { const UDebugSkelMeshComponent* const PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if ( !PreviewMeshComponent ) { return; } const FTransform& LocalToWorldTransform = PreviewMeshComponent->GetComponentTransform(); for ( int32 VertexIndex = 0; VertexIndex < LocalVertices.Num(); ++VertexIndex ) { FVector3f& VertexPosition = LocalVertices[VertexIndex].Position; VertexPosition = (FVector3f)LocalToWorldTransform.TransformPosition((FVector)VertexPosition); } } void FAnimationViewportClient::GetAllVertexIndicesUsedInSection(const FRawStaticIndexBuffer16or32Interface& IndexBuffer, const FSkelMeshRenderSection& SkelMeshSection, TArray& OutIndices) const { const uint32 BaseIndex = SkelMeshSection.BaseIndex; const int32 NumWedges = SkelMeshSection.NumTriangles * 3; for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex) { const int32 VertexIndexForWedge = IndexBuffer.Get(SkelMeshSection.BaseIndex + WedgeIndex); OutIndices.Add(VertexIndexForWedge); } } FBox FAnimationViewportClient::ComputeBoundingBoxForSelectedEditorSection() const { UDebugSkelMeshComponent* const PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if ( !PreviewMeshComponent ) { return FBox(ForceInitToZero); } USkeletalMesh* const SkeletalMesh = PreviewMeshComponent->GetSkeletalMeshAsset(); FSkeletalMeshObject* const MeshObject = PreviewMeshComponent->MeshObject; if ( !SkeletalMesh || !MeshObject ) { return FBox(ForceInitToZero); } const int32 LODLevel = PreviewMeshComponent->GetPredictedLODLevel(); const int32 SelectedEditorSection = PreviewMeshComponent->GetSelectedEditorSection(); const FSkeletalMeshRenderData& SkelMeshRenderData = MeshObject->GetSkeletalMeshRenderData(); const FSkeletalMeshLODRenderData& LODData = SkelMeshRenderData.LODRenderData[LODLevel]; const FSkelMeshRenderSection& SelectedSectionSkelMesh = LODData.RenderSections[SelectedEditorSection]; // Get us vertices from the entire LOD model. TArray SkinnedVertices; PreviewMeshComponent->GetCPUSkinnedVertices(SkinnedVertices, LODLevel); TransformVertexPositionsToWorld(SkinnedVertices); // Find out which of these the selected section actually uses. TArray VertexIndices; GetAllVertexIndicesUsedInSection(*LODData.MultiSizeIndexContainer.GetIndexBuffer(), SelectedSectionSkelMesh, VertexIndices); // Get their bounds. FBox BoundingBox(ForceInitToZero); for ( int32 Index = 0; Index < VertexIndices.Num(); ++Index ) { const int32 VertexIndex = VertexIndices[Index]; BoundingBox += (FVector)SkinnedVertices[VertexIndex].Position; } return BoundingBox; } void FAnimationViewportClient::FocusViewportOnPreviewMesh(bool bUseCustomCamera) { FIntPoint ViewportSize(FIntPoint::ZeroValue); if (Viewport != nullptr) { 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; bFocusUsingCustomCamera = bUseCustomCamera; return; } if (UDebugSkelMeshComponent* const PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent()) { if (USkeletalMesh* const SkelMesh = PreviewMeshComponent->GetSkeletalMeshAsset()) { if (bUseCustomCamera && SkelMesh->GetHasCustomDefaultEditorCamera()) { FViewportCameraTransform& ViewTransform = GetViewTransform(); ViewTransform.SetLocation(SkelMesh->GetDefaultEditorCameraLocation()); ViewTransform.SetRotation(SkelMesh->GetDefaultEditorCameraRotation()); ViewTransform.SetLookAt(SkelMesh->GetDefaultEditorCameraLookAt()); ViewTransform.SetOrthoZoom(SkelMesh->GetDefaultEditorCameraOrthoZoom()); Invalidate(); return; } if (PreviewMeshComponent->GetSelectedEditorSection() != INDEX_NONE) { const FBox SelectedSectionBounds = ComputeBoundingBoxForSelectedEditorSection(); if (SelectedSectionBounds.IsValid) { FocusViewportOnBox(SelectedSectionBounds); } return; } } } FSphere Sphere = GetCameraTarget(); FocusViewportOnSphere(Sphere); } float FAnimationViewportClient::GetFloorOffset() const { const USkeletalMeshComponent* SkelMeshComponent = GetPreviewScene()->GetPreviewMeshComponent(); USkeletalMesh* Mesh = SkelMeshComponent ? SkelMeshComponent->GetSkeletalMeshAsset() : nullptr; if ( Mesh ) { return Mesh->GetFloorOffset(); } return 0.0f; } void FAnimationViewportClient::SetFloorOffset( float NewValue ) { const USkeletalMeshComponent* SkelMeshComponent = GetPreviewScene()->GetPreviewMeshComponent(); USkeletalMesh* Mesh = SkelMeshComponent ? SkelMeshComponent->GetSkeletalMeshAsset() : nullptr; if ( Mesh ) { Mesh->Modify(); Mesh->SetFloorOffset(NewValue); UpdateCameraSetup(); // This does the actual moving of the floor mesh Invalidate(); } } void FAnimationViewportClient::ToggleCPUSkinning() { GetPreviewScene()->ForEachPreviewMesh([this](UDebugSkelMeshComponent* PreviewMeshComponent) { const bool bCurVal = PreviewMeshComponent->GetCPUSkinningEnabled(); PreviewMeshComponent->SetCPUSkinningEnabled(!bCurVal); }); Invalidate(); } bool FAnimationViewportClient::IsSetCPUSkinningChecked() const { UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if (PreviewMeshComponent != nullptr) { return PreviewMeshComponent->GetCPUSkinningEnabled(); } return false; } void FAnimationViewportClient::ToggleShowNormals() { GetPreviewScene()->ForEachPreviewMesh([this](UDebugSkelMeshComponent* PreviewMeshComponent) { PreviewMeshComponent->bDrawNormals = !PreviewMeshComponent->bDrawNormals; PreviewMeshComponent->MarkRenderStateDirty(); }); Invalidate(); } bool FAnimationViewportClient::IsSetShowNormalsChecked() const { UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if (PreviewMeshComponent != nullptr) { return PreviewMeshComponent->bDrawNormals; } return false; } void FAnimationViewportClient::ToggleShowTangents() { GetPreviewScene()->ForEachPreviewMesh([this](UDebugSkelMeshComponent* PreviewMeshComponent) { PreviewMeshComponent->bDrawTangents = !PreviewMeshComponent->bDrawTangents; PreviewMeshComponent->MarkRenderStateDirty(); }); Invalidate(); } bool FAnimationViewportClient::IsSetShowTangentsChecked() const { UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if (PreviewMeshComponent != nullptr) { return PreviewMeshComponent->bDrawTangents; } return false; } void FAnimationViewportClient::ToggleShowBinormals() { GetPreviewScene()->ForEachPreviewMesh([this](UDebugSkelMeshComponent* PreviewMeshComponent) { PreviewMeshComponent->bDrawBinormals = !PreviewMeshComponent->bDrawBinormals; PreviewMeshComponent->MarkRenderStateDirty(); }); Invalidate(); } bool FAnimationViewportClient::IsSetShowBinormalsChecked() const { UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if (PreviewMeshComponent != nullptr) { return PreviewMeshComponent->bDrawBinormals; } return false; } void FAnimationViewportClient::SetDrawUVOverlay(bool bInDrawUVs) { bDrawUVs = bInDrawUVs; 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; return bShouldBeEnabled && bShowMeshStats; } bool FAnimationViewportClient::IsShowingSelectedNodeStats() const { return ConfigOption->ShowMeshStats == EDisplayInfoMode::SkeletalControls; } bool FAnimationViewportClient::IsDetailedMeshStats() const { return ConfigOption->ShowMeshStats == EDisplayInfoMode::Detailed; } int32 FAnimationViewportClient::GetShowMeshStats() const { return ConfigOption->ShowMeshStats; } void FAnimationViewportClient::SetPlaybackSpeedMode(EAnimationPlaybackSpeeds::Type InMode) { if (FAnimationEditorPreviewScene* AnimationEditorPreviewScene = GetAnimPreviewScenePtr().Get()) { AnimationEditorPreviewScene->SetAnimationPlaybackSpeedMode(InMode); } } EAnimationPlaybackSpeeds::Type FAnimationViewportClient::GetPlaybackSpeedMode() const { if (FAnimationEditorPreviewScene* AnimationEditorPreviewScene = GetAnimPreviewScenePtr().Get()) { return AnimationEditorPreviewScene->GetAnimationPlaybackSpeedMode(); } return EAnimationPlaybackSpeeds::Type::Normal; } void FAnimationViewportClient::SetCustomAnimationSpeed(const float InCustomAnimationSpeed) { if (FAnimationEditorPreviewScene* AnimationEditorPreviewScene = GetAnimPreviewScenePtr().Get()) { AnimationEditorPreviewScene->SetCustomAnimationSpeed(InCustomAnimationSpeed); } } float FAnimationViewportClient::GetCustomAnimationSpeed() const { if (FAnimationEditorPreviewScene* AnimationEditorPreviewScene = GetAnimPreviewScenePtr().Get()) { return AnimationEditorPreviewScene->GetCustomAnimationSpeed(); } return 0.0f; } TSharedPtr FAnimationViewportClient::GetAnimPreviewScenePtr() const { return StaticCastSharedPtr(PreviewScenePtr); } TSharedRef FAnimationViewportClient::GetAnimPreviewScene() const { return StaticCastSharedRef(GetPreviewScene()); } IPersonaEditorModeManager* FAnimationViewportClient::GetPersonaModeManager() const { return ModeTools->GetInteractiveToolsContext()->ContextObjectStore->FindContext()->GetPersonaEditorModeManager(); } void FAnimationViewportClient::HandleInvalidateViews() { Invalidate(); } void FAnimationViewportClient::HandleFocusViews() { SetCameraFollowMode(EAnimationViewportCameraFollowMode::None); FocusViewportOnPreviewMesh(false); } bool FAnimationViewportClient::CanCycleWidgetMode() const { return ModeTools ? ModeTools->CanCycleWidgetMode() : false; } void FAnimationViewportClient::UpdateAudioListener(const FSceneView& View) { UWorld* ViewportWorld = GetWorld(); if (ViewportWorld) { if (FAudioDevice* AudioDevice = ViewportWorld->GetAudioDeviceRaw()) { const FVector& ViewLocation = GetViewLocation(); const FRotator& ViewRotation = GetViewRotation(); FTransform ListenerTransform(ViewRotation); ListenerTransform.SetLocation(ViewLocation); AudioDevice->SetListener(ViewportWorld, 0, ListenerTransform, 0.0f); } } } void FAnimationViewportClient::SetupViewForRendering( FSceneViewFamily& ViewFamily, FSceneView& View ) { FEditorViewportClient::SetupViewForRendering( ViewFamily, View ); if(bHasAudioFocus) { UpdateAudioListener(View); } // Cache screen size const UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene()->GetPreviewMeshComponent(); if (PreviewMeshComponent != nullptr && PreviewMeshComponent->MeshObject != nullptr) { const FBoxSphereBounds& SkelBounds = PreviewMeshComponent->Bounds; CachedScreenSize = ComputeBoundsScreenSize(SkelBounds.Origin, static_cast(SkelBounds.SphereRadius), View); } } void FAnimationViewportClient::HandleToggleShowFlag(FEngineShowFlags::EShowFlag EngineShowFlagIndex) { FEditorViewportClient::HandleToggleShowFlag(EngineShowFlagIndex); GetPreviewScene()->ForEachPreviewMesh([this](UDebugSkelMeshComponent* InMesh) { InMesh->bDisplayVertexColors = EngineShowFlags.VertexColors; InMesh->MarkRenderStateDirty(); }); ConfigOption->SetShowGrid(EngineShowFlags.Grid); } void FAnimationViewportClient::OnCameraControllerChanged() { TSharedPtr Override = GetAnimPreviewScene()->GetCurrentCameraOverride(); CameraController = Override.IsValid() ? Override.Get() : CachedDefaultCameraController; } FMatrix FAnimationViewportClient::CalcViewRotationMatrix(const FRotator& InViewRotation) const { auto ComputeOrbitMatrix = [this](const FViewportCameraTransform& InViewTransform) { FTransform Transform = FTransform( -InViewTransform.GetLookAt() ) * FTransform( OrbitRotation.Inverse() ) * FTransform( FRotator(0, InViewTransform.GetRotation().Yaw,0) ) * FTransform( FRotator(0, 0, InViewTransform.GetRotation().Pitch) ) * FTransform( FVector(0, (InViewTransform.GetLocation() - InViewTransform.GetLookAt()).Size(), 0) ); return Transform.ToMatrixNoScale() * FInverseRotationMatrix( FRotator(0,90.f,0) ); }; const FViewportCameraTransform& ViewTransform = GetViewTransform(); if (bUsingOrbitCamera) { // @todo vreditor: Not stereo friendly yet return FTranslationMatrix(ViewTransform.GetLocation()) * ComputeOrbitMatrix(ViewTransform); } else { // Create the view matrix return FInverseRotationMatrix(InViewRotation); } } #undef LOCTEXT_NAMESPACE