// Copyright Epic Games, Inc. All Rights Reserved. #include "SCSEditorViewportClient.h" #include "Components/StaticMeshComponent.h" #include "Materials/Material.h" #include "CanvasItem.h" #include "Editor/EditorPerProjectUserSettings.h" #include "Settings/LevelEditorViewportSettings.h" #include "Editor/UnrealEdEngine.h" #include "ThumbnailRendering/SceneThumbnailInfo.h" #include "ThumbnailRendering/ThumbnailManager.h" #include "Engine/StaticMesh.h" #include "Components/InstancedStaticMeshComponent.h" #include "Kismet2/ComponentEditorUtils.h" #include "EngineUtils.h" #include "UnrealEdGlobals.h" #include "SEditorViewport.h" #include "EngineGlobals.h" #include "Editor.h" #include "SSubobjectEditor.h" #include "Kismet2/BlueprintEditorUtils.h" #include "SKismetInspector.h" #include "ScopedTransaction.h" #include "ISCSEditorCustomization.h" #include "CanvasTypes.h" #include "Engine/TextureCube.h" #include "SSCSEditorViewport.h" #include "PhysicsEngine/PhysicsConstraintComponent.h" #include "UnrealWidget.h" 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& 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& InBlueprintEditorPtr, FPreviewScene* InPreviewScene, const TSharedRef& InSCSEditorViewport) : FEditorViewportClient(nullptr, InPreviewScene, StaticCastSharedRef(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()->bUseSelectionOutline); // Set if the grid will be drawn DrawHelper.bDrawGrid = GetDefault()->bSCSEditorShowGrid; // now add floor EditorFloorComp = NewObject(GetTransientPackage(), TEXT("EditorFloorComp")); UStaticMesh* FloorMesh = LoadObject(NULL, TEXT("/Engine/EditorMeshes/PhAT_FloorBox.PhAT_FloorBox"), NULL, LOAD_None, NULL); if (ensure(FloorMesh)) { EditorFloorComp->SetStaticMesh(FloorMesh); } UMaterial* Material = LoadObject(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()->bSCSEditorShowFloor); EditorFloorComp->SetCollisionEnabled(GetDefault()->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()->bBegunPlay = 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 SubobjectEditor = BlueprintEditorPtr.Pin()->GetSubobjectEditor(); AActor* PreviewActor = GetPreviewActor(); if (PreviewActor != nullptr) { for (UActorComponent* Component : PreviewActor->GetComponents()) { if (UPrimitiveComponent* PrimComponent = Cast(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 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 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 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 Visualizer = GUnrealEd->FindComponentVisualizer(Comp->GetClass()); if (Visualizer.IsValid()) { Visualizer->DrawVisualizationHUD(Comp, &InViewport, &View, &Canvas); } } } } TGuardValue AutoRestore(GAllowActorScriptExecutionInEditor, true); const int32 HalfX = 0.5f * Viewport->GetSizeXY().X; const int32 HalfY = 0.5f * Viewport->GetSizeXY().Y; TArray SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes(); if(bIsManipulating && SelectedNodes.Num() > 0) { const FSubobjectData* Data = SelectedNodes[0]->GetDataSource(); const USceneComponent* SceneComp = Data ? Cast(Data->FindComponentInstanceInActor(PreviewActor)) : nullptr; if(SceneComp) { const FVector WidgetLocation = GetWidgetLocation(); const FPlane Proj = View.Project(WidgetLocation); if(Proj.W > 0.0f) { const int32 XPos = HalfX + (HalfX * Proj.X); const int32 YPos = HalfY + (HalfY * (Proj.Y * -1)); DrawAngles(&Canvas, XPos, YPos, GetCurrentWidgetAxis(), GetWidgetMode(), GetWidgetCoordSystem().Rotator(), WidgetLocation); } } } } } bool FSCSEditorViewportClient::InputKey(FViewport* InViewport, int32 ControllerId, FKey Key, EInputEvent Event, float AmountDepressed, bool bGamepad) { bool bHandled = GUnrealEd->ComponentVisManager.HandleInputKey(this, InViewport, Key, Event);; if( !bHandled ) { bHandled = FEditorViewportClient::InputKey(InViewport, ControllerId, Key, Event, AmountDepressed, bGamepad); } return bHandled; } 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 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(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 (HitProxy->IsA(HActor::StaticGetType())) { HActor* ActorProxy = (HActor*)HitProxy; AActor* PreviewActor = GetPreviewActor(); if (ActorProxy && ActorProxy->Actor && ActorProxy->PrimComponent) { USceneComponent* SelectedCompInstance = nullptr; if (ActorProxy->Actor == PreviewActor) { UPrimitiveComponent* TestComponent = const_cast(ActorProxy->PrimComponent); if (ActorProxy->Actor->GetComponents().Contains(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 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; } } GUnrealEd->ComponentVisManager.HandleClick(this, HitProxy, Click); } 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 BlueprintEditor = BlueprintEditorPtr.Pin(); if (PreviewActor && BlueprintEditor.IsValid()) { TArray 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; } for (const FSubobjectEditorTreeNodePtrType& SelectedNodePtr : SelectedNodes) { 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(Cast(Data->FindComponentInstanceInActor(PreviewActor))); USceneComponent* SelectedTemplate = const_cast(Cast(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 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) { PreviewActor->RerunConstructionScripts(); } SceneComp->PostEditComponentMove(true); // @TODO HACK passing 'finished' every frame... // If a constraint, copy back updated constraint frames to template UPhysicsConstraintComponent* ConstraintComp = Cast(SceneComp); UPhysicsConstraintComponent* TemplateComp = Cast(SelectedTemplate); if(ConstraintComp && TemplateComp) { TemplateComp->ConstraintInstance.CopyConstraintGeometryFrom(&ConstraintComp->ConstraintInstance); } // Iterate over all the active archetype instances and propagate the change(s) to the matching component instance TArray ArchetypeInstances; if(SelectedTemplate->HasAnyFlags(RF_ArchetypeObject)) { SelectedTemplate->GetArchetypeInstances(ArchetypeInstances); for(int32 InstanceIndex = 0; InstanceIndex < ArchetypeInstances.Num(); ++InstanceIndex) { SceneComp = Cast(ArchetypeInstances[InstanceIndex]); if(SceneComp != nullptr) { FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeRotation_DirectMutable(), OldRelativeRotation, SelectedTemplate->GetRelativeRotation()); FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeScale3D_DirectMutable(), OldRelativeScale3D, SelectedTemplate->GetRelativeScale3D()); } } } else if(UObject* Outer = SelectedTemplate->GetOuter()) { Outer->GetArchetypeInstances(ArchetypeInstances); for(int32 InstanceIndex = 0; InstanceIndex < ArchetypeInstances.Num(); ++InstanceIndex) { SceneComp = static_cast(FindObjectWithOuter(ArchetypeInstances[InstanceIndex], SelectedTemplate->GetClass(), SelectedTemplate->GetFName())); if(SceneComp) { FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeRotation_DirectMutable(), OldRelativeRotation, SelectedTemplate->GetRelativeRotation()); FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeScale3D_DirectMutable(), OldRelativeScale3D, SelectedTemplate->GetRelativeScale3D()); } } } } } } } GUnrealEd->RedrawLevelEditingViewports(); } } Invalidate(); } return bHandled; } void FSCSEditorViewportClient::TrackingStarted( const struct FInputEventState& InInputState, bool bIsDraggingWidget, bool bNudge ) { if( !bIsManipulating && bIsDraggingWidget ) { // 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; } } void FSCSEditorViewportClient::TrackingStopped() { 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); } } 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 BluePrintEditor = BlueprintEditorPtr.Pin(); if (BluePrintEditor.IsValid()) { TArray SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes(); if (BluePrintEditor->GetSubobjectEditor()->GetSceneRootNode().IsValid()) { TArray 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() && CastChecked(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 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(Data->FindComponentInstanceInActor(PreviewActor)) : nullptr; if(SceneComp) { TSharedPtr Customization = BlueprintEditorPtr.Pin()->CustomizeSubobjectEditor(SceneComp); FVector CustomLocation; if(Customization.IsValid() && Customization->HandleGetWidgetLocation(const_cast(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 BlueprintEditor = BlueprintEditorPtr.Pin(); if (PreviewActor && BlueprintEditor.IsValid()) { TArray SelectedNodes = BlueprintEditorPtr.Pin()->GetSelectedSubobjectEditorTreeNodes(); if(SelectedNodes.Num() > 0) { const FSubobjectEditorTreeNodePtrType SelectedNode = SelectedNodes.Last(); const FSubobjectData* Data = SelectedNode->GetDataSource(); const USceneComponent* SceneComp = Data ? Cast(Data->FindComponentInstanceInActor(PreviewActor)) : nullptr; if(SceneComp) { TSharedPtr 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()->SCSViewportCameraSpeed; } void FSCSEditorViewportClient::SetCameraSpeedSetting(int32 SpeedSetting) { GetMutableDefault()->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(Blueprint->ThumbnailInfo); if(ThumbnailInfo == nullptr) { ThumbnailInfo = USceneThumbnailInfo::StaticClass()->GetDefaultObject(); } // Clamp zoom to the actor's bounding sphere radius float OrbitZoom = ThumbnailInfo->OrbitZoom; if (PreviewActorBounds.SphereRadius + OrbitZoom < 0) { OrbitZoom = -PreviewActorBounds.SphereRadius; } ToggleOrbitCamera(true); { float 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 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(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()->bBegunPlay = bIsSimulateEnabled; PreviewScene->GetWorld()->bShouldSimulatePhysics = bIsSimulateEnabled; TSharedPtr SubobjectEditor = BlueprintEditorPtr.Pin()->GetSubobjectEditor(); TSharedRef 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()->bSCSEditorShowFloor; } void FSCSEditorViewportClient::ToggleShowFloor() { UEditorPerProjectUserSettings* Settings = GetMutableDefault(); 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()->bSCSEditorShowGrid; } void FSCSEditorViewportClient::ToggleShowGrid() { UEditorPerProjectUserSettings* Settings = GetMutableDefault(); 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 SelectedNodes = BlueprintEditorPtr.Pin()->GetSubobjectEditor()->GetSelectedHandles(); TSharedPtr 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 PreviewActorBounds = FBoxSphereBounds(ForceInitToZero); for (UActorComponent* Component : PreviewActor->GetComponents()) { // Aggregate primitive components that either have collision enabled or are otherwise visible components in-game if (UPrimitiveComponent* PrimComp = Cast(Component)) { if (PrimComp->IsRegistered() && (!PrimComp->bHiddenInGame || PrimComp->IsCollisionEnabled()) && PrimComp->Bounds.SphereRadius < HALF_WORLD_MAX) { PreviewActorBounds = PreviewActorBounds + PrimComp->Bounds; } } } } }