1280 lines
43 KiB
C++
1280 lines
43 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SCSEditorViewportClient.h"
|
|
|
|
#include "BlueprintEditor.h"
|
|
#include "CanvasItem.h"
|
|
#include "CanvasTypes.h"
|
|
#include "ComponentVisualizer.h"
|
|
#include "ComponentVisualizerManager.h"
|
|
#include "Components/ActorComponent.h"
|
|
#include "Components/ChildActorComponent.h"
|
|
#include "Components/InstancedStaticMeshComponent.h"
|
|
#include "Components/PrimitiveComponent.h"
|
|
#include "Components/SceneComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Containers/Array.h"
|
|
#include "Containers/BitArray.h"
|
|
#include "Containers/Set.h"
|
|
#include "Containers/UnrealString.h"
|
|
#include "CoreGlobals.h"
|
|
#include "Editor.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "Editor/EditorPerProjectUserSettings.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "EditorViewportSelectability.h"
|
|
#include "EditorComponents.h"
|
|
#include "EditorViewportSelectabilityBridge.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Engine/EngineTypes.h"
|
|
#include "Engine/SimpleConstructionScript.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/World.h"
|
|
#include "EngineDefines.h"
|
|
#include "EngineUtils.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "HitProxies.h"
|
|
#include "ISCSEditorCustomization.h"
|
|
#include "Internationalization/Text.h"
|
|
#include "Kismet2/ComponentEditorUtils.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "Materials/Material.h"
|
|
#include "Math/Color.h"
|
|
#include "Math/IntPoint.h"
|
|
#include "Math/Plane.h"
|
|
#include "Math/QuatRotationTranslationMatrix.h"
|
|
#include "Math/Transform.h"
|
|
#include "Math/Vector.h"
|
|
#include "Math/Vector2D.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "ObjectTools.h"
|
|
#include "PhysicsEngine/ConstraintInstance.h"
|
|
#include "PhysicsEngine/PhysicsConstraintComponent.h"
|
|
#include "PreviewScene.h"
|
|
#include "SEditorViewport.h"
|
|
#include "SSCSEditorViewport.h"
|
|
#include "SSubobjectEditor.h"
|
|
#include "PrimitiveDrawingUtils.h"
|
|
#include "SceneView.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Settings/LevelEditorViewportSettings.h"
|
|
#include "ShowFlags.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "SubobjectData.h"
|
|
#include "SubobjectDataSubsystem.h"
|
|
#include "Templates/Casts.h"
|
|
#include "Templates/UnrealTemplate.h"
|
|
#include "ThumbnailRendering/SceneThumbnailInfo.h"
|
|
#include "ThumbnailRendering/ThumbnailManager.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
#include "UObject/ObjectPtr.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/UObjectGlobals.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UnrealClient.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "UnrealWidget.h"
|
|
#include "Widgets/SWidget.h"
|
|
|
|
struct FSubobjectDataHandle;
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogSCSEditorViewport, Log, All);
|
|
|
|
namespace
|
|
{
|
|
/** Automatic translation applied to the camera in the default editor viewport logic when orbit mode is enabled. */
|
|
const float AutoViewportOrbitCameraTranslate = 256.0f;
|
|
|
|
void DrawAngles(FCanvas* Canvas, int32 XPos, int32 YPos, EAxisList::Type ManipAxis, UE::Widget::EWidgetMode MoveMode, const FRotator& Rotation, const FVector& Translation)
|
|
{
|
|
FString OutputString(TEXT(""));
|
|
if(MoveMode == UE::Widget::WM_Rotate && Rotation.IsZero() == false)
|
|
{
|
|
//Only one value moves at a time
|
|
const FVector EulerAngles = Rotation.Euler();
|
|
if(ManipAxis == EAxisList::X)
|
|
{
|
|
OutputString += FString::Printf(TEXT("Roll: %0.2f"), EulerAngles.X);
|
|
}
|
|
else if(ManipAxis == EAxisList::Y)
|
|
{
|
|
OutputString += FString::Printf(TEXT("Pitch: %0.2f"), EulerAngles.Y);
|
|
}
|
|
else if(ManipAxis == EAxisList::Z)
|
|
{
|
|
OutputString += FString::Printf(TEXT("Yaw: %0.2f"), EulerAngles.Z);
|
|
}
|
|
}
|
|
else if(MoveMode == UE::Widget::WM_Translate && Translation.IsZero() == false)
|
|
{
|
|
//Only one value moves at a time
|
|
if(ManipAxis == EAxisList::X)
|
|
{
|
|
OutputString += FString::Printf(TEXT(" %0.2f"), Translation.X);
|
|
}
|
|
else if(ManipAxis == EAxisList::Y)
|
|
{
|
|
OutputString += FString::Printf(TEXT(" %0.2f"), Translation.Y);
|
|
}
|
|
else if(ManipAxis == EAxisList::Z)
|
|
{
|
|
OutputString += FString::Printf(TEXT(" %0.2f"), Translation.Z);
|
|
}
|
|
}
|
|
|
|
if(OutputString.Len() > 0)
|
|
{
|
|
FCanvasTextItem TextItem( FVector2D(XPos, YPos), FText::FromString( OutputString ), GEngine->GetSmallFont(), FLinearColor::White );
|
|
Canvas->DrawItem( TextItem );
|
|
}
|
|
}
|
|
|
|
// Determine whether or not the given node has a parent node that is not the root node, is movable and is selected
|
|
bool IsMovableParentNodeSelected(const FSubobjectEditorTreeNodePtrType& NodePtr, const TArray<FSubobjectEditorTreeNodePtrType>& SelectedNodes)
|
|
{
|
|
if(NodePtr.IsValid())
|
|
{
|
|
// Check for a valid parent node
|
|
FSubobjectEditorTreeNodePtrType ParentNodePtr = NodePtr->GetParent();
|
|
const FSubobjectData* ParentData = ParentNodePtr.IsValid() ? ParentNodePtr->GetDataSource() : nullptr;
|
|
if(ParentData && !ParentData->IsRootComponent())
|
|
{
|
|
if(SelectedNodes.Contains(ParentNodePtr))
|
|
{
|
|
// The parent node is not the root node and is also selected; success
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Recursively search for any other parent nodes farther up the tree that might be selected
|
|
return IsMovableParentNodeSelected(ParentNodePtr, SelectedNodes);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// FSCSEditorViewportClient
|
|
|
|
FSCSEditorViewportClient::FSCSEditorViewportClient(TWeakPtr<FBlueprintEditor>& InBlueprintEditorPtr, FPreviewScene* InPreviewScene, const TSharedRef<SSCSEditorViewport>& InSCSEditorViewport)
|
|
: FEditorViewportClient(nullptr, InPreviewScene, StaticCastSharedRef<SEditorViewport>(InSCSEditorViewport))
|
|
, BlueprintEditorPtr(InBlueprintEditorPtr)
|
|
, PreviewActorBounds(ForceInitToZero)
|
|
, bIsManipulating(false)
|
|
, ScopedTransaction(NULL)
|
|
, bIsSimulateEnabled(false)
|
|
{
|
|
WidgetMode = UE::Widget::WM_Translate;
|
|
WidgetCoordSystem = COORD_Local;
|
|
EngineShowFlags.DisableAdvancedFeatures();
|
|
|
|
check(Widget);
|
|
Widget->SetSnapEnabled(true);
|
|
|
|
// Selectively set particular show flags that we need
|
|
EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
|
|
|
|
// Set if the grid will be drawn
|
|
DrawHelper.bDrawGrid = GetDefault<UEditorPerProjectUserSettings>()->bSCSEditorShowGrid;
|
|
|
|
// now add floor
|
|
EditorFloorComp = NewObject<UStaticMeshComponent>(GetTransientPackage(), TEXT("EditorFloorComp"));
|
|
|
|
UStaticMesh* FloorMesh = LoadObject<UStaticMesh>(NULL, TEXT("/Engine/EditorMeshes/PhAT_FloorBox.PhAT_FloorBox"), NULL, LOAD_None, NULL);
|
|
if (ensure(FloorMesh))
|
|
{
|
|
EditorFloorComp->SetStaticMesh(FloorMesh);
|
|
}
|
|
|
|
UMaterial* Material = LoadObject<UMaterial>(NULL, TEXT("/Engine/EditorMaterials/PersonaFloorMat.PersonaFloorMat"), NULL, LOAD_None, NULL);
|
|
if (ensure(Material))
|
|
{
|
|
EditorFloorComp->SetMaterial(0, Material);
|
|
}
|
|
|
|
EditorFloorComp->SetRelativeScale3D(FVector(3.f, 3.f, 1.f));
|
|
EditorFloorComp->SetVisibility(GetDefault<UEditorPerProjectUserSettings>()->bSCSEditorShowFloor);
|
|
EditorFloorComp->SetCollisionEnabled(GetDefault<UEditorPerProjectUserSettings>()->bSCSEditorShowFloor? ECollisionEnabled::QueryAndPhysics : ECollisionEnabled::NoCollision);
|
|
PreviewScene->AddComponent(EditorFloorComp, FTransform::Identity);
|
|
|
|
// Turn off so that actors added to the world do not have a lifespan (so they will not auto-destroy themselves).
|
|
PreviewScene->GetWorld()->SetBegunPlay(false);
|
|
|
|
PreviewScene->SetSkyCubemap(GUnrealEd->GetThumbnailManager()->AmbientCubemap);
|
|
}
|
|
|
|
FSCSEditorViewportClient::~FSCSEditorViewportClient()
|
|
{
|
|
// Ensure that an in-progress transaction is ended
|
|
EndTransaction();
|
|
}
|
|
|
|
void FSCSEditorViewportClient::Tick(float DeltaSeconds)
|
|
{
|
|
FEditorViewportClient::Tick(DeltaSeconds);
|
|
|
|
// Register the selection override delegate for the preview actor's components
|
|
TSharedPtr<SSubobjectEditor> SubobjectEditor = BlueprintEditorPtr.Pin()->GetSubobjectEditor();
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
if (PreviewActor != nullptr)
|
|
{
|
|
for (UActorComponent* Component : PreviewActor->GetComponents())
|
|
{
|
|
if (UPrimitiveComponent* PrimComponent = Cast<UPrimitiveComponent>(Component))
|
|
{
|
|
if (!PrimComponent->SelectionOverrideDelegate.IsBound())
|
|
{
|
|
SubobjectEditor->SetSelectionOverride(PrimComponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InvalidatePreview(false);
|
|
}
|
|
|
|
// Tick the preview scene world.
|
|
if (!GIntraFrameDebuggingGameThread)
|
|
{
|
|
// Ensure that the preview actor instance is up-to-date for component editing (e.g. after compiling the Blueprint, the actor may be reinstanced outside of this class)
|
|
if(PreviewActor != BlueprintEditorPtr.Pin()->GetBlueprintObj()->SimpleConstructionScript->GetComponentEditorActorInstance())
|
|
{
|
|
BlueprintEditorPtr.Pin()->GetBlueprintObj()->SimpleConstructionScript->SetComponentEditorActorInstance(PreviewActor);
|
|
}
|
|
|
|
// Allow full tick only if preview simulation is enabled and we're not currently in an active SIE or PIE session
|
|
if(bIsSimulateEnabled && GEditor->PlayWorld == NULL && !GEditor->bIsSimulatingInEditor)
|
|
{
|
|
PreviewScene->GetWorld()->Tick(IsRealtime() ? LEVELTICK_All : LEVELTICK_TimeOnly, DeltaSeconds);
|
|
}
|
|
else
|
|
{
|
|
PreviewScene->GetWorld()->Tick(IsRealtime() ? LEVELTICK_ViewportsOnly : LEVELTICK_TimeOnly, DeltaSeconds);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSCSEditorViewportClient::Draw(const FSceneView* View, FPrimitiveDrawInterface* PDI)
|
|
{
|
|
FEditorViewportClient::Draw(View, PDI);
|
|
|
|
bool bHitTesting = PDI->IsHitTesting();
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
if(PreviewActor)
|
|
{
|
|
if(GUnrealEd != nullptr)
|
|
{
|
|
TArray<FSubobjectEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes();
|
|
for(FSubobjectEditorTreeNodePtrType SelectedNode : SelectedNodes)
|
|
{
|
|
const FSubobjectData* Data = SelectedNode->GetDataSource();
|
|
const UActorComponent* Comp = Data ? Data->FindComponentInstanceInActor(PreviewActor) : nullptr;
|
|
if(Comp != nullptr && Comp->IsRegistered())
|
|
{
|
|
// Try and find a visualizer
|
|
TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(Comp->GetClass());
|
|
if (Visualizer.IsValid())
|
|
{
|
|
Visualizer->DrawVisualization(Comp, View, PDI);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSCSEditorViewportClient::DrawCanvas( FViewport& InViewport, FSceneView& View, FCanvas& Canvas )
|
|
{
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
if(PreviewActor)
|
|
{
|
|
if (GUnrealEd != nullptr)
|
|
{
|
|
TArray<FSubobjectEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes();
|
|
for(FSubobjectEditorTreeNodePtrType SelectedNode : SelectedNodes)
|
|
{
|
|
const FSubobjectData* Data = SelectedNode->GetDataSource();
|
|
const UActorComponent* Comp = Data ? Data->FindComponentInstanceInActor(PreviewActor) : nullptr;
|
|
|
|
if (Comp != nullptr && Comp->IsRegistered())
|
|
{
|
|
// Try and find a visualizer
|
|
TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(Comp->GetClass());
|
|
if (Visualizer.IsValid())
|
|
{
|
|
Visualizer->DrawVisualizationHUD(Comp, &InViewport, &View, &Canvas);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TGuardValue<bool> AutoRestore(GAllowActorScriptExecutionInEditor, true);
|
|
|
|
const int32 HalfX = Viewport->GetSizeXY().X / 2;
|
|
const int32 HalfY = Viewport->GetSizeXY().Y / 2;
|
|
|
|
TArray<FSubobjectEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes();
|
|
|
|
if(bIsManipulating && SelectedNodes.Num() > 0)
|
|
{
|
|
const FSubobjectData* Data = SelectedNodes[0]->GetDataSource();
|
|
const USceneComponent* SceneComp = Data ? Cast<USceneComponent>(Data->FindComponentInstanceInActor(PreviewActor)) : nullptr;
|
|
if(SceneComp)
|
|
{
|
|
const FVector WidgetLocation = GetWidgetLocation();
|
|
const FPlane Proj = View.Project(WidgetLocation);
|
|
if(Proj.W > 0.0f)
|
|
{
|
|
const int32 XPos = static_cast<int32>(HalfX + (HalfX * Proj.X));
|
|
const int32 YPos = static_cast<int32>(HalfY + (HalfY * Proj.Y * -1.0));
|
|
DrawAngles(&Canvas, XPos, YPos, GetCurrentWidgetAxis(), GetWidgetMode(), GetWidgetCoordSystem().Rotator(), WidgetLocation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If enabled, draw viewport selection limited text
|
|
const TSharedPtr<FBlueprintEditor> BlueprintEditor = BlueprintEditorPtr.Pin();
|
|
if (BlueprintEditor.IsValid())
|
|
{
|
|
if (FEditorViewportSelectabilityBridge* SelectabilityBridge = BlueprintEditor->GetViewportSelectabilityBridge())
|
|
{
|
|
if (SelectabilityBridge->IsViewportSelectionLimited())
|
|
{
|
|
const FText SelectionLimitedText = SelectabilityBridge->GetViewportSelectionLimitedText();
|
|
FEditorViewportSelectability::DrawEnabledTextNotice(&Canvas, SelectionLimitedText);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::InputKey(const FInputKeyEventArgs& EventArgs)
|
|
{
|
|
bool bHandled = GUnrealEd->ComponentVisManager.HandleInputKey(this, EventArgs.Viewport, EventArgs.Key, EventArgs.Event);
|
|
|
|
if(!bHandled)
|
|
{
|
|
bHandled = FEditorViewportClient::InputKey(EventArgs);
|
|
}
|
|
|
|
return bHandled;
|
|
}
|
|
|
|
void FSCSEditorViewportClient::MouseMove(FViewport* InViewport, int32 InX, int32 InY)
|
|
{
|
|
HHitProxy* const HitResult = InViewport ? InViewport->GetHitProxy(InX, InY) : nullptr;
|
|
UpdateHoverFromHitProxy(HitResult);
|
|
|
|
return FEditorViewportClient::MouseMove(InViewport, InX, InY);
|
|
}
|
|
|
|
EMouseCursor::Type FSCSEditorViewportClient::GetCursor(FViewport* InViewport, int32 InX, int32 InY)
|
|
{
|
|
if (MouseCursor.IsSet())
|
|
{
|
|
return MouseCursor.GetValue();
|
|
}
|
|
|
|
return FEditorViewportClient::GetCursor(InViewport, InX, InY);
|
|
}
|
|
|
|
void FSCSEditorViewportClient::ProcessClick(class FSceneView& View, class HHitProxy* HitProxy, FKey Key, EInputEvent Event, uint32 HitX, uint32 HitY)
|
|
{
|
|
const FViewportClick Click(&View, this, Key, Event, HitX, HitY);
|
|
|
|
if (HitProxy)
|
|
{
|
|
if (HitProxy->IsA(HInstancedStaticMeshInstance::StaticGetType()))
|
|
{
|
|
HInstancedStaticMeshInstance* InstancedStaticMeshInstanceProxy = ((HInstancedStaticMeshInstance*)HitProxy);
|
|
|
|
TSharedPtr<ISCSEditorCustomization> Customization = BlueprintEditorPtr.Pin()->CustomizeSubobjectEditor(InstancedStaticMeshInstanceProxy->Component);
|
|
if (Customization.IsValid() && Customization->HandleViewportClick(AsShared(), View, HitProxy, Key, Event, HitX, HitY))
|
|
{
|
|
Invalidate();
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (HitProxy->IsA(HWidgetAxis::StaticGetType()))
|
|
{
|
|
const bool bOldModeWidgets1 = EngineShowFlags.ModeWidgets;
|
|
const bool bOldModeWidgets2 = View.Family->EngineShowFlags.ModeWidgets;
|
|
|
|
EngineShowFlags.SetModeWidgets(false);
|
|
FSceneViewFamily* SceneViewFamily = const_cast<FSceneViewFamily*>(View.Family);
|
|
SceneViewFamily->EngineShowFlags.SetModeWidgets(false);
|
|
bool bWasWidgetDragging = Widget->IsDragging();
|
|
Widget->SetDragging(false);
|
|
|
|
// Invalidate the hit proxy map so it will be rendered out again when GetHitProxy
|
|
// is called
|
|
Viewport->InvalidateHitProxy();
|
|
|
|
// This will actually re-render the viewport's hit proxies!
|
|
HHitProxy* HitProxyWithoutAxisWidgets = Viewport->GetHitProxy(HitX, HitY);
|
|
if (HitProxyWithoutAxisWidgets != NULL && !HitProxyWithoutAxisWidgets->IsA(HWidgetAxis::StaticGetType()))
|
|
{
|
|
// Try this again, but without the widget this time!
|
|
ProcessClick(View, HitProxyWithoutAxisWidgets, Key, Event, HitX, HitY);
|
|
}
|
|
|
|
// Undo the evil
|
|
EngineShowFlags.SetModeWidgets(bOldModeWidgets1);
|
|
SceneViewFamily->EngineShowFlags.SetModeWidgets(bOldModeWidgets2);
|
|
|
|
Widget->SetDragging(bWasWidgetDragging);
|
|
|
|
// Invalidate the hit proxy map again so that it'll be refreshed with the original
|
|
// scene contents if we need it again later.
|
|
Viewport->InvalidateHitProxy();
|
|
return;
|
|
}
|
|
else if(GUnrealEd->ComponentVisManager.HandleClick(this, HitProxy, Click))
|
|
{
|
|
// Component Vis Manager handled this click, no need to do anything
|
|
}
|
|
else if (HitProxy->IsA(HActor::StaticGetType()))
|
|
{
|
|
HActor* ActorProxy = (HActor*)HitProxy;
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
if (ActorProxy && ActorProxy->Actor && ActorProxy->PrimComponent)
|
|
{
|
|
const USceneComponent* SelectedCompInstance = nullptr;
|
|
|
|
if (ActorProxy->Actor == PreviewActor)
|
|
{
|
|
const UPrimitiveComponent* TestComponent = ActorProxy->PrimComponent;
|
|
if (ActorProxy->Actor->GetComponents().Contains(TestComponent)
|
|
&& IsObjectSelectableInViewport(const_cast<UPrimitiveComponent*>(TestComponent)))
|
|
{
|
|
SelectedCompInstance = TestComponent;
|
|
}
|
|
}
|
|
else if (ActorProxy->Actor->IsChildActor())
|
|
{
|
|
AActor* TestActor = ActorProxy->Actor;
|
|
while (TestActor->GetParentActor()->IsChildActor())
|
|
{
|
|
TestActor = TestActor->GetParentActor();
|
|
}
|
|
|
|
if (TestActor->GetParentActor() == PreviewActor)
|
|
{
|
|
SelectedCompInstance = TestActor->GetParentComponent();
|
|
}
|
|
}
|
|
|
|
if (SelectedCompInstance)
|
|
{
|
|
TSharedPtr<ISCSEditorCustomization> Customization = BlueprintEditorPtr.Pin()->CustomizeSubobjectEditor(SelectedCompInstance);
|
|
if (!(Customization.IsValid() && Customization->HandleViewportClick(AsShared(), View, HitProxy, Key, Event, HitX, HitY)))
|
|
{
|
|
const bool bIsCtrlKeyDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl);
|
|
if (BlueprintEditorPtr.IsValid())
|
|
{
|
|
// Note: This will find and select any node associated with the component instance that's attached to the proxy (including visualizers)
|
|
BlueprintEditorPtr.Pin()->FindAndSelectSubobjectEditorTreeNode(SelectedCompInstance, bIsCtrlKeyDown);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Invalidate();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct FTemplateComponentMoved
|
|
{
|
|
int32 SelectedNodeIndex;
|
|
USceneComponent* SceneComp;
|
|
USceneComponent* SelectedTemplate;
|
|
FVector OldRelativeLocation;
|
|
FRotator OldRelativeRotation;
|
|
FVector OldRelativeScale3D;
|
|
};
|
|
|
|
bool FSCSEditorViewportClient::InputWidgetDelta( FViewport* InViewport, EAxisList::Type CurrentAxis, FVector& Drag, FRotator& Rot, FVector& Scale )
|
|
{
|
|
bool bHandled = false;
|
|
if(bIsManipulating && CurrentAxis != EAxisList::None)
|
|
{
|
|
bHandled = true;
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
TSharedPtr<FBlueprintEditor> BlueprintEditor = BlueprintEditorPtr.Pin();
|
|
if (PreviewActor && BlueprintEditor.IsValid())
|
|
{
|
|
TArray<FSubobjectEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes();
|
|
if(SelectedNodes.Num() > 0)
|
|
{
|
|
FVector ModifiedScale = Scale;
|
|
|
|
// (mirrored from Level Editor VPC) - we don't scale components when we only have a very small scale change
|
|
if (!Scale.IsNearlyZero())
|
|
{
|
|
if (GEditor->UsePercentageBasedScaling())
|
|
{
|
|
ModifiedScale = Scale * ((GEditor->GetScaleGridSize() / 100.0f) / GEditor->GetGridSize());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ModifiedScale = FVector::ZeroVector;
|
|
}
|
|
|
|
// RerunConstructionScripts only needs to be called once, after all selected components have been moved (it's potentially quite
|
|
// expensive). After that, some post-move work needs to happen per component, so we keep track of the components that were moved.
|
|
bool bNeedsRerunConstructionScripts = false;
|
|
|
|
TArray<FTemplateComponentMoved> TemplateComponentsMoved;
|
|
TArray<UActorComponent*> ActorComponentsMoved;
|
|
TemplateComponentsMoved.Reserve(SelectedNodes.Num());
|
|
|
|
for (int32 SelectedNodeIndex = 0; SelectedNodeIndex < SelectedNodes.Num(); SelectedNodeIndex++)
|
|
{
|
|
const FSubobjectEditorTreeNodePtrType& SelectedNodePtr = SelectedNodes[SelectedNodeIndex];
|
|
const FSubobjectData* Data = SelectedNodePtr->GetDataSource();
|
|
// Don't allow editing of a root node, inherited SCS node or child node that also has a movable (non-root) parent node selected
|
|
const bool bCanEdit = GUnrealEd->ComponentVisManager.IsActive() ||
|
|
(Data && !Data->IsRootComponent() && !IsMovableParentNodeSelected(SelectedNodePtr, SelectedNodes));
|
|
|
|
if(bCanEdit)
|
|
{
|
|
if (GUnrealEd->ComponentVisManager.HandleInputDelta(this, InViewport, Drag, Rot, Scale))
|
|
{
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
Invalidate();
|
|
return true;
|
|
}
|
|
|
|
// #TODO_BH Clean up const casts
|
|
USceneComponent* SceneComp = const_cast<USceneComponent*>(Cast<USceneComponent>(Data->FindComponentInstanceInActor(PreviewActor)));
|
|
USceneComponent* SelectedTemplate = const_cast<USceneComponent*>(Cast<USceneComponent>(Data->GetObjectForBlueprint(BlueprintEditor->GetBlueprintObj())));
|
|
if(SceneComp && SelectedTemplate)
|
|
{
|
|
// Cache the current default values for propagation
|
|
FVector OldRelativeLocation = SelectedTemplate->GetRelativeLocation();
|
|
FRotator OldRelativeRotation = SelectedTemplate->GetRelativeRotation();
|
|
FVector OldRelativeScale3D = SelectedTemplate->GetRelativeScale3D();
|
|
|
|
// Adjust the deltas as necessary
|
|
FComponentEditorUtils::AdjustComponentDelta(SceneComp, Drag, Rot);
|
|
|
|
TSharedPtr<ISCSEditorCustomization> Customization = BlueprintEditor->CustomizeSubobjectEditor(SceneComp);
|
|
if(Customization.IsValid() && Customization->HandleViewportDrag(SceneComp, SelectedTemplate, Drag, Rot, ModifiedScale, GetWidgetLocation()))
|
|
{
|
|
// Handled by SCS Editor customization
|
|
}
|
|
else
|
|
{
|
|
// Apply delta to the template component object
|
|
// (the preview scene component will be set in one of the ArchetypeInstances loops below... to keep the two in sync)
|
|
GEditor->ApplyDeltaToComponent(
|
|
SelectedTemplate,
|
|
true,
|
|
&Drag,
|
|
&Rot,
|
|
&ModifiedScale,
|
|
SelectedTemplate->GetRelativeLocation());
|
|
}
|
|
|
|
UBlueprint* PreviewBlueprint = UBlueprint::GetBlueprintFromClass(PreviewActor->GetClass());
|
|
if(PreviewBlueprint != nullptr)
|
|
{
|
|
// Like PostEditMove(), but we only need to re-run construction scripts
|
|
if(PreviewBlueprint && PreviewBlueprint->bRunConstructionScriptOnDrag)
|
|
{
|
|
bNeedsRerunConstructionScripts = true;
|
|
}
|
|
|
|
FTemplateComponentMoved& ComponentMoved = TemplateComponentsMoved[TemplateComponentsMoved.AddUninitialized()];
|
|
ComponentMoved.SelectedNodeIndex = SelectedNodeIndex;
|
|
ComponentMoved.SceneComp = SceneComp;
|
|
ComponentMoved.SelectedTemplate = SelectedTemplate;
|
|
ComponentMoved.OldRelativeLocation = OldRelativeLocation;
|
|
ComponentMoved.OldRelativeRotation = OldRelativeRotation;
|
|
ComponentMoved.OldRelativeScale3D = OldRelativeScale3D;
|
|
|
|
// Track static meshes for bulk re-registration
|
|
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(SceneComp);
|
|
if (StaticMeshComponent)
|
|
{
|
|
ActorComponentsMoved.Add(StaticMeshComponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
// This bulk re-register context forces Add/RemovePrimitive and debug physics update commands to be sent to the
|
|
// render thread in batches, significantly improving performance.
|
|
FStaticMeshComponentBulkReregisterContext ReregisterContext(GetScene(), ActorComponentsMoved);
|
|
|
|
// Get the SCS if present
|
|
UBlueprintGeneratedClass* PreviewBlueprint = Cast<UBlueprintGeneratedClass>(PreviewActor->GetClass());
|
|
USimpleConstructionScript* SCS = nullptr;
|
|
if (PreviewBlueprint && PreviewBlueprint->SimpleConstructionScript)
|
|
{
|
|
SCS = PreviewBlueprint->SimpleConstructionScript;
|
|
|
|
// Tell the reregister context about the simple construction script, which allows the SCS to batch render commands
|
|
// for newly created components generated during construction.
|
|
ReregisterContext.AddSimpleConstructionScript(SCS);
|
|
|
|
// Optimize calls to GetArchetypeInstances by generating the SCS node map for the blueprint class.
|
|
SCS->CreateNameToSCSNodeMap();
|
|
}
|
|
|
|
if (bNeedsRerunConstructionScripts)
|
|
{
|
|
PreviewActor->RerunConstructionScripts();
|
|
|
|
// The construction scripts will have recreated the selected components, and the ones in the TemplateComponentsMoved
|
|
// array now point to the deleted version. Update to the newly created version.
|
|
for (FTemplateComponentMoved& Moved : TemplateComponentsMoved)
|
|
{
|
|
const FSubobjectEditorTreeNodePtrType& SelectedNodePtr = SelectedNodes[Moved.SelectedNodeIndex];
|
|
const FSubobjectData* Data = SelectedNodePtr->GetDataSource();
|
|
|
|
Moved.SceneComp = const_cast<USceneComponent*>(Cast<USceneComponent>(Data->FindComponentInstanceInActor(PreviewActor)));
|
|
}
|
|
}
|
|
|
|
// Array corresponds to TemplateComponentsMoved, with objects we need to search for archetypes
|
|
TArray<UObject*> ArchetypeSearchObjects;
|
|
ArchetypeSearchObjects.Reserve(TemplateComponentsMoved.Num());
|
|
|
|
for (FTemplateComponentMoved& Moved : TemplateComponentsMoved)
|
|
{
|
|
Moved.SceneComp->PostEditComponentMove(true); // @TODO HACK passing 'finished' every frame...
|
|
|
|
// If a constraint, copy back updated constraint frames to template
|
|
UPhysicsConstraintComponent* ConstraintComp = Cast<UPhysicsConstraintComponent>(Moved.SceneComp);
|
|
UPhysicsConstraintComponent* TemplateComp = Cast<UPhysicsConstraintComponent>(Moved.SelectedTemplate);
|
|
if (ConstraintComp && TemplateComp)
|
|
{
|
|
TemplateComp->ConstraintInstance.CopyConstraintGeometryFrom(&ConstraintComp->ConstraintInstance);
|
|
}
|
|
|
|
// Add to the list of objects we need to search for archetypes
|
|
if (Moved.SelectedTemplate->HasAnyFlags(RF_ArchetypeObject))
|
|
{
|
|
// Searching the item itself
|
|
ArchetypeSearchObjects.Add(Moved.SelectedTemplate);
|
|
}
|
|
else if (UObject* Outer = Moved.SelectedTemplate->GetOuter())
|
|
{
|
|
// Searching the outer
|
|
ArchetypeSearchObjects.Add(Outer);
|
|
}
|
|
else
|
|
{
|
|
// Searching nothing -- place a dummy item to preserve array ordering
|
|
ArchetypeSearchObjects.Add(nullptr);
|
|
}
|
|
}
|
|
|
|
// Get the list of active archetype instances for each moved object, in bulk for efficiency
|
|
TArray<TArray<UObject*>> ArchetypeInstancesList;
|
|
ObjectTools::BatchGetArchetypeInstances(ArchetypeSearchObjects, ArchetypeInstancesList);
|
|
|
|
// Propagate the change(s) to the matching component instance
|
|
for (int32 ObjectIndex = 0; ObjectIndex < TemplateComponentsMoved.Num(); ObjectIndex++)
|
|
{
|
|
// Did the search find any instances?
|
|
TArray<UObject*>& ArchetypeInstances = ArchetypeInstancesList[ObjectIndex];
|
|
if (ArchetypeInstances.Num())
|
|
{
|
|
FTemplateComponentMoved& Moved = TemplateComponentsMoved[ObjectIndex];
|
|
if (Moved.SelectedTemplate->HasAnyFlags(RF_ArchetypeObject))
|
|
{
|
|
// Object itself was archetype
|
|
for (int32 InstanceIndex = 0; InstanceIndex < ArchetypeInstances.Num(); ++InstanceIndex)
|
|
{
|
|
USceneComponent* SceneComp = Cast<USceneComponent>(ArchetypeInstances[InstanceIndex]);
|
|
if (SceneComp != nullptr)
|
|
{
|
|
FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), Moved.OldRelativeLocation, Moved.SelectedTemplate->GetRelativeLocation());
|
|
FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeRotation_DirectMutable(), Moved.OldRelativeRotation, Moved.SelectedTemplate->GetRelativeRotation());
|
|
FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeScale3D_DirectMutable(), Moved.OldRelativeScale3D, Moved.SelectedTemplate->GetRelativeScale3D());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Outer was archetype
|
|
for (int32 InstanceIndex = 0; InstanceIndex < ArchetypeInstances.Num(); ++InstanceIndex)
|
|
{
|
|
USceneComponent* SceneComp = static_cast<USceneComponent*>(FindObjectWithOuter(ArchetypeInstances[InstanceIndex], Moved.SelectedTemplate->GetClass(), Moved.SelectedTemplate->GetFName()));
|
|
if (SceneComp)
|
|
{
|
|
FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), Moved.OldRelativeLocation, Moved.SelectedTemplate->GetRelativeLocation());
|
|
FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeRotation_DirectMutable(), Moved.OldRelativeRotation, Moved.SelectedTemplate->GetRelativeRotation());
|
|
FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeScale3D_DirectMutable(), Moved.OldRelativeScale3D, Moved.SelectedTemplate->GetRelativeScale3D());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SCS)
|
|
{
|
|
SCS->RemoveNameToSCSNodeMap();
|
|
}
|
|
}
|
|
|
|
GUnrealEd->RedrawLevelEditingViewports();
|
|
}
|
|
}
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
return bHandled;
|
|
}
|
|
|
|
void FSCSEditorViewportClient::TrackingStarted( const struct FInputEventState& InInputState, bool bIsDraggingWidget, bool bNudge )
|
|
{
|
|
if( !bIsManipulating && bIsDraggingWidget )
|
|
{
|
|
HandleBeginTransform();
|
|
}
|
|
}
|
|
|
|
void FSCSEditorViewportClient::TrackingStopped()
|
|
{
|
|
if( bIsManipulating )
|
|
{
|
|
HandleEndTransform();
|
|
}
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::BeginTransform(const FGizmoState& InState)
|
|
{
|
|
return HandleBeginTransform();
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::EndTransform(const FGizmoState& InState)
|
|
{
|
|
return HandleEndTransform();
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::HandleBeginTransform()
|
|
{
|
|
if (!bIsManipulating)
|
|
{
|
|
// Suspend component modification during each delta step to avoid recording unnecessary overhead into the transaction buffer
|
|
GEditor->DisableDeltaModification(true);
|
|
|
|
// Begin transaction
|
|
BeginTransaction( NSLOCTEXT("UnrealEd", "ModifyComponents", "Modify Component(s)") );
|
|
bIsManipulating = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::HandleEndTransform()
|
|
{
|
|
if (bIsManipulating)
|
|
{
|
|
// Re-run construction scripts if we haven't done so yet (so that the components in the preview actor can update their transforms)
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
if(PreviewActor != nullptr)
|
|
{
|
|
UBlueprint* PreviewBlueprint = UBlueprint::GetBlueprintFromClass(PreviewActor->GetClass());
|
|
if(PreviewBlueprint != nullptr && !PreviewBlueprint->bRunConstructionScriptOnDrag)
|
|
{
|
|
PreviewActor->RerunConstructionScripts();
|
|
}
|
|
}
|
|
|
|
// End transaction
|
|
bIsManipulating = false;
|
|
EndTransaction();
|
|
|
|
// Restore component delta modification
|
|
GEditor->DisableDeltaModification(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
UE::Widget::EWidgetMode FSCSEditorViewportClient::GetWidgetMode() const
|
|
{
|
|
// Default to not drawing the widget
|
|
UE::Widget::EWidgetMode ReturnWidgetMode = UE::Widget::WM_None;
|
|
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
if(!bIsSimulateEnabled && PreviewActor)
|
|
{
|
|
const TSharedPtr<FBlueprintEditor> BluePrintEditor = BlueprintEditorPtr.Pin();
|
|
if (BluePrintEditor.IsValid())
|
|
{
|
|
TArray<FSubobjectEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes();
|
|
|
|
if (BluePrintEditor->GetSubobjectEditor()->GetSceneRootNode().IsValid())
|
|
{
|
|
TArray<FSubobjectEditorTreeNodePtrType> RootNodes = BluePrintEditor->GetSubobjectEditor()->GetRootNodes();
|
|
|
|
if (GUnrealEd->ComponentVisManager.IsActive() &&
|
|
GUnrealEd->ComponentVisManager.IsVisualizingArchetype())
|
|
{
|
|
// Component visualizer is active and editing the archetype
|
|
ReturnWidgetMode = WidgetMode;
|
|
}
|
|
else
|
|
{
|
|
// if the selected nodes array is empty, or only contains entries from the
|
|
// root nodes array, or isn't visible in the preview actor, then don't display a transform widget
|
|
for(FSubobjectEditorTreeNodePtrType CurrentNodePtr : SelectedNodes)
|
|
{
|
|
if (CurrentNodePtr.IsValid())
|
|
{
|
|
FSubobjectData* Data = CurrentNodePtr->GetDataSource();
|
|
if (Data && Data->CanEdit())
|
|
{
|
|
const bool bIsNotRootComponent = !RootNodes.Contains(CurrentNodePtr) && !Data->IsRootComponent();
|
|
const bool bIsISM =
|
|
Data->GetObject<UInstancedStaticMeshComponent>() &&
|
|
CastChecked<UInstancedStaticMeshComponent>(Data->FindComponentInstanceInActor(GetPreviewActor()))->SelectedInstances.Contains(true);
|
|
const bool bHasInstanceInActor = Data->FindComponentInstanceInActor(PreviewActor) != nullptr;
|
|
|
|
if ((bIsNotRootComponent || bIsISM) && bHasInstanceInActor)
|
|
{
|
|
// a non-NULL, non-root item is selected, draw the widget
|
|
ReturnWidgetMode = WidgetMode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ReturnWidgetMode;
|
|
}
|
|
|
|
|
|
void FSCSEditorViewportClient::SetWidgetMode( UE::Widget::EWidgetMode NewMode )
|
|
{
|
|
WidgetMode = NewMode;
|
|
}
|
|
|
|
void FSCSEditorViewportClient::SetWidgetCoordSystemSpace( ECoordSystem NewCoordSystem )
|
|
{
|
|
WidgetCoordSystem = NewCoordSystem;
|
|
}
|
|
|
|
FVector FSCSEditorViewportClient::GetWidgetLocation() const
|
|
{
|
|
FVector ComponentVisWidgetLocation;
|
|
if (GUnrealEd->ComponentVisManager.IsVisualizingArchetype() &&
|
|
GUnrealEd->ComponentVisManager.GetWidgetLocation(this, ComponentVisWidgetLocation))
|
|
{
|
|
return ComponentVisWidgetLocation;
|
|
}
|
|
|
|
FVector Location = FVector::ZeroVector;
|
|
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
if(PreviewActor)
|
|
{
|
|
TArray<FSubobjectEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes();
|
|
if(SelectedNodes.Num() > 0)
|
|
{
|
|
// Use the last selected item for the widget location
|
|
const FSubobjectData* Data = SelectedNodes[0]->GetDataSource();
|
|
|
|
const USceneComponent* SceneComp = Data ? Cast<USceneComponent>(Data->FindComponentInstanceInActor(PreviewActor)) : nullptr;
|
|
if(SceneComp)
|
|
{
|
|
TSharedPtr<ISCSEditorCustomization> Customization = BlueprintEditorPtr.Pin()->CustomizeSubobjectEditor(SceneComp);
|
|
FVector CustomLocation;
|
|
if(Customization.IsValid() && Customization->HandleGetWidgetLocation(const_cast<USceneComponent*>(SceneComp), CustomLocation))
|
|
{
|
|
Location = CustomLocation;
|
|
}
|
|
else
|
|
{
|
|
Location = SceneComp->GetComponentLocation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Location;
|
|
}
|
|
|
|
FMatrix FSCSEditorViewportClient::GetWidgetCoordSystem() const
|
|
{
|
|
FMatrix ComponentVisWidgetCoordSystem;
|
|
if (GUnrealEd->ComponentVisManager.IsVisualizingArchetype() &&
|
|
GUnrealEd->ComponentVisManager.GetCustomInputCoordinateSystem(this, ComponentVisWidgetCoordSystem))
|
|
{
|
|
return ComponentVisWidgetCoordSystem;
|
|
}
|
|
|
|
FMatrix Matrix = FMatrix::Identity;
|
|
if( GetWidgetCoordSystemSpace() == COORD_Local )
|
|
{
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
TSharedPtr<FBlueprintEditor> BlueprintEditor = BlueprintEditorPtr.Pin();
|
|
if (PreviewActor && BlueprintEditor.IsValid())
|
|
{
|
|
TArray<FSubobjectEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes();
|
|
if(SelectedNodes.Num() > 0)
|
|
{
|
|
const FSubobjectEditorTreeNodePtrType SelectedNode = SelectedNodes.Last();
|
|
const FSubobjectData* Data = SelectedNode->GetDataSource();
|
|
const USceneComponent* SceneComp = Data ? Cast<USceneComponent>(Data->FindComponentInstanceInActor(PreviewActor)) : nullptr;
|
|
if(SceneComp)
|
|
{
|
|
TSharedPtr<ISCSEditorCustomization> Customization = BlueprintEditor->CustomizeSubobjectEditor(SceneComp);
|
|
FMatrix CustomTransform;
|
|
if(Customization.IsValid() && Customization->HandleGetWidgetTransform(SceneComp, CustomTransform))
|
|
{
|
|
Matrix = CustomTransform;
|
|
}
|
|
else
|
|
{
|
|
Matrix = FQuatRotationMatrix( SceneComp->GetComponentQuat() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!Matrix.Equals(FMatrix::Identity))
|
|
{
|
|
Matrix.RemoveScaling();
|
|
}
|
|
|
|
return Matrix;
|
|
}
|
|
|
|
int32 FSCSEditorViewportClient::GetCameraSpeedSetting() const
|
|
{
|
|
return GetDefault<UEditorPerProjectUserSettings>()->SCSViewportCameraSpeed;
|
|
}
|
|
|
|
void FSCSEditorViewportClient::SetCameraSpeedSetting(int32 SpeedSetting)
|
|
{
|
|
GetMutableDefault<UEditorPerProjectUserSettings>()->SCSViewportCameraSpeed = SpeedSetting;
|
|
}
|
|
|
|
void FSCSEditorViewportClient::InvalidatePreview(bool bResetCamera)
|
|
{
|
|
// Ensure that the editor is valid before continuing
|
|
if(!BlueprintEditorPtr.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UBlueprint* Blueprint = BlueprintEditorPtr.Pin()->GetBlueprintObj();
|
|
check(Blueprint);
|
|
|
|
const bool bIsPreviewActorValid = GetPreviewActor() != nullptr;
|
|
|
|
// Create or update the Blueprint actor instance in the preview scene
|
|
BlueprintEditorPtr.Pin()->UpdatePreviewActor(Blueprint, !bIsPreviewActorValid);
|
|
|
|
Invalidate();
|
|
RefreshPreviewBounds();
|
|
|
|
if( bResetCamera )
|
|
{
|
|
ResetCamera();
|
|
}
|
|
}
|
|
|
|
void FSCSEditorViewportClient::ResetCamera()
|
|
{
|
|
UBlueprint* Blueprint = BlueprintEditorPtr.Pin()->GetBlueprintObj();
|
|
|
|
// For now, loosely base default camera positioning on thumbnail preview settings
|
|
USceneThumbnailInfo* ThumbnailInfo = Cast<USceneThumbnailInfo>(Blueprint->ThumbnailInfo);
|
|
if(ThumbnailInfo == nullptr)
|
|
{
|
|
ThumbnailInfo = USceneThumbnailInfo::StaticClass()->GetDefaultObject<USceneThumbnailInfo>();
|
|
}
|
|
|
|
// Clamp zoom to the actor's bounding sphere radius
|
|
double OrbitZoom = ThumbnailInfo->OrbitZoom;
|
|
if (PreviewActorBounds.SphereRadius + OrbitZoom < 0.0)
|
|
{
|
|
OrbitZoom = -PreviewActorBounds.SphereRadius;
|
|
}
|
|
|
|
ToggleOrbitCamera(true);
|
|
{
|
|
double TargetDistance = PreviewActorBounds.SphereRadius;
|
|
if(TargetDistance <= 0.0f)
|
|
{
|
|
TargetDistance = AutoViewportOrbitCameraTranslate;
|
|
}
|
|
|
|
FRotator ThumbnailAngle(ThumbnailInfo->OrbitPitch, ThumbnailInfo->OrbitYaw, 0.0f);
|
|
|
|
SetViewLocationForOrbiting(PreviewActorBounds.Origin);
|
|
SetViewLocation( GetViewLocation() + FVector(0.0f, TargetDistance * 1.5f + OrbitZoom - AutoViewportOrbitCameraTranslate, 0.0f) );
|
|
SetViewRotation( ThumbnailAngle );
|
|
}
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
void FSCSEditorViewportClient::ToggleRealtimePreview()
|
|
{
|
|
SetRealtime(!IsRealtime());
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
AActor* FSCSEditorViewportClient::GetPreviewActor() const
|
|
{
|
|
return BlueprintEditorPtr.Pin()->GetPreviewActor();
|
|
}
|
|
|
|
void FSCSEditorViewportClient::FocusViewportToSelection()
|
|
{
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
if(PreviewActor)
|
|
{
|
|
TArray<FSubobjectEditorTreeNodePtrType> SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes();
|
|
if(SelectedNodes.Num() > 0)
|
|
{
|
|
const FSubobjectData* Data = SelectedNodes[0]->GetDataSource();
|
|
// Use the last selected item for the widget location
|
|
const USceneComponent* SceneComp = Data ? Cast<USceneComponent>(Data->FindComponentInstanceInActor(PreviewActor)) : nullptr;
|
|
if(SceneComp)
|
|
{
|
|
FocusViewportOnBox(SceneComp->Bounds.GetBox());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FocusViewportOnBox(PreviewActor->GetComponentsBoundingBox(true));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::GetIsSimulateEnabled()
|
|
{
|
|
return bIsSimulateEnabled;
|
|
}
|
|
|
|
void FSCSEditorViewportClient::ToggleIsSimulateEnabled()
|
|
{
|
|
// Must destroy existing actors before we toggle the world state
|
|
BlueprintEditorPtr.Pin()->DestroyPreview();
|
|
|
|
bIsSimulateEnabled = !bIsSimulateEnabled;
|
|
PreviewScene->GetWorld()->SetBegunPlay(bIsSimulateEnabled);
|
|
PreviewScene->GetWorld()->bShouldSimulatePhysics = bIsSimulateEnabled;
|
|
|
|
TSharedPtr<SWidget> SubobjectEditor = BlueprintEditorPtr.Pin()->GetSubobjectEditor();
|
|
TSharedRef<SWidget> Inspector = BlueprintEditorPtr.Pin()->GetInspector();
|
|
|
|
// When simulate is enabled, we don't want to allow the user to modify the components
|
|
BlueprintEditorPtr.Pin()->UpdatePreviewActor(BlueprintEditorPtr.Pin()->GetBlueprintObj(), true);
|
|
|
|
SubobjectEditor->SetEnabled(!bIsSimulateEnabled);
|
|
Inspector->SetEnabled(!bIsSimulateEnabled);
|
|
|
|
if(!IsRealtime())
|
|
{
|
|
ToggleRealtimePreview();
|
|
}
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::GetShowFloor()
|
|
{
|
|
return GetDefault<UEditorPerProjectUserSettings>()->bSCSEditorShowFloor;
|
|
}
|
|
|
|
void FSCSEditorViewportClient::ToggleShowFloor()
|
|
{
|
|
UEditorPerProjectUserSettings* Settings = GetMutableDefault<UEditorPerProjectUserSettings>();
|
|
|
|
bool bShowFloor = Settings->bSCSEditorShowFloor;
|
|
bShowFloor = !bShowFloor;
|
|
|
|
EditorFloorComp->SetVisibility(bShowFloor);
|
|
EditorFloorComp->SetCollisionEnabled(bShowFloor? ECollisionEnabled::QueryAndPhysics : ECollisionEnabled::NoCollision);
|
|
|
|
Settings->bSCSEditorShowFloor = bShowFloor;
|
|
Settings->PostEditChange();
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::GetShowGrid()
|
|
{
|
|
return GetDefault<UEditorPerProjectUserSettings>()->bSCSEditorShowGrid;
|
|
}
|
|
|
|
void FSCSEditorViewportClient::ToggleShowGrid()
|
|
{
|
|
UEditorPerProjectUserSettings* Settings = GetMutableDefault<UEditorPerProjectUserSettings>();
|
|
|
|
bool bShowGrid = Settings->bSCSEditorShowGrid;
|
|
bShowGrid = !bShowGrid;
|
|
|
|
DrawHelper.bDrawGrid = bShowGrid;
|
|
|
|
Settings->bSCSEditorShowGrid = bShowGrid;
|
|
Settings->PostEditChange();
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
void FSCSEditorViewportClient::BeginTransaction(const FText& Description)
|
|
{
|
|
//UE_LOG(LogSCSEditorViewport, Log, TEXT("FSCSEditorViewportClient::BeginTransaction() pre: %s %08x"), SessionName, *((uint32*)&ScopedTransaction));
|
|
|
|
if(!ScopedTransaction)
|
|
|
|
{
|
|
if(USubobjectDataSubsystem* System = USubobjectDataSubsystem::Get())
|
|
{
|
|
TArray<FSubobjectDataHandle> SelectedNodes = BlueprintEditorPtr.Pin()->GetSubobjectEditor()->GetSelectedHandles();
|
|
TSharedPtr<FBlueprintEditor> BlueprintEditor = BlueprintEditorPtr.Pin();
|
|
ScopedTransaction = System->BeginTransaction(SelectedNodes, Description, BlueprintEditor.IsValid() ? BlueprintEditor->GetBlueprintObj() : nullptr);
|
|
}
|
|
}
|
|
|
|
//UE_LOG(LogSCSEditorViewport, Log, TEXT("FSCSEditorViewportClient::BeginTransaction() post: %s %08x"), SessionName, *((uint32*)&ScopedTransaction));
|
|
}
|
|
|
|
void FSCSEditorViewportClient::EndTransaction()
|
|
{
|
|
//UE_LOG(LogSCSEditorViewport, Log, TEXT("FSCSEditorViewportClient::EndTransaction(): %08x"), *((uint32*)&ScopedTransaction));
|
|
|
|
if(ScopedTransaction)
|
|
{
|
|
delete ScopedTransaction;
|
|
ScopedTransaction = nullptr;
|
|
}
|
|
}
|
|
|
|
void FSCSEditorViewportClient::RefreshPreviewBounds()
|
|
{
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
|
|
if(PreviewActor)
|
|
{
|
|
// Compute actor bounds as the sum of its visible parts
|
|
FBoxSphereBounds::Builder BoundsBuilder;
|
|
for (UActorComponent* Component : PreviewActor->GetComponents())
|
|
{
|
|
// Aggregate primitive components that either have collision enabled or are otherwise visible components in-game
|
|
if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(Component))
|
|
{
|
|
if (PrimComp->IsRegistered() && (!PrimComp->bHiddenInGame || PrimComp->IsCollisionEnabled()) && PrimComp->Bounds.SphereRadius < HALF_WORLD_MAX)
|
|
{
|
|
BoundsBuilder += PrimComp->Bounds;
|
|
}
|
|
}
|
|
}
|
|
PreviewActorBounds = BoundsBuilder;
|
|
}
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::IsViewportSelectionLimited() const
|
|
{
|
|
if (const TSharedPtr<FBlueprintEditor> BlueprintEditor = BlueprintEditorPtr.Pin())
|
|
{
|
|
if (FEditorViewportSelectabilityBridge* SelectabilityBridge = BlueprintEditor->GetViewportSelectabilityBridge())
|
|
{
|
|
return SelectabilityBridge->IsViewportSelectionLimited();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSCSEditorViewportClient::IsObjectSelectableInViewport(UObject* const InObject) const
|
|
{
|
|
if (const TSharedPtr<FBlueprintEditor> BlueprintEditor = BlueprintEditorPtr.Pin())
|
|
{
|
|
if (FEditorViewportSelectabilityBridge* SelectabilityBridge = BlueprintEditor->GetViewportSelectabilityBridge())
|
|
{
|
|
return SelectabilityBridge->IsObjectSelectableInViewport(InObject);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void FSCSEditorViewportClient::UpdateHoverFromHitProxy(HHitProxy* const InHitProxy)
|
|
{
|
|
const bool bIsViewportSelectionLimited = IsViewportSelectionLimited();
|
|
|
|
UPrimitiveComponent* PrimitiveComponent = nullptr;
|
|
bool bIsGizmoHit = false;
|
|
bool bIsActorHit = false;
|
|
|
|
if (InHitProxy)
|
|
{
|
|
if (InHitProxy->IsA(HWidgetAxis::StaticGetType()))
|
|
{
|
|
if (bIsViewportSelectionLimited)
|
|
{
|
|
bIsGizmoHit = true;
|
|
}
|
|
}
|
|
else if (InHitProxy->IsA(HActor::StaticGetType()))
|
|
{
|
|
const HActor* const ActorHitProxy = static_cast<HActor*>(InHitProxy);
|
|
if (ActorHitProxy && IsValid(ActorHitProxy->Actor))
|
|
{
|
|
if (bIsViewportSelectionLimited)
|
|
{
|
|
bIsActorHit = true;
|
|
}
|
|
PrimitiveComponent = const_cast<UPrimitiveComponent*>(ActorHitProxy->PrimComponent.Get());
|
|
}
|
|
}
|
|
}
|
|
|
|
FEditorViewportSelectability::UpdateHoveredPrimitive(bIsViewportSelectionLimited
|
|
, PrimitiveComponent
|
|
, HoveredPrimitiveComponents
|
|
, [this](UObject* const InObject) -> bool
|
|
{
|
|
return IsObjectSelectableInViewport(InObject);
|
|
});
|
|
|
|
// Set mouse cursor after hovered primitive component list has been updated
|
|
if (bIsGizmoHit)
|
|
{
|
|
MouseCursor = EMouseCursor::CardinalCross;
|
|
}
|
|
else if (bIsActorHit)
|
|
{
|
|
MouseCursor = HoveredPrimitiveComponents.IsEmpty() ? EMouseCursor::SlashedCircle : EMouseCursor::Crosshairs;
|
|
}
|
|
else if (bIsViewportSelectionLimited)
|
|
{
|
|
MouseCursor = EMouseCursor::SlashedCircle;
|
|
}
|
|
else
|
|
{
|
|
MouseCursor.Reset();
|
|
}
|
|
}
|