// 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& 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()->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 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 = Viewport->GetSizeXY().X / 2; const int32 HalfY = Viewport->GetSizeXY().Y / 2; 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 = static_cast(HalfX + (HalfX * Proj.X)); const int32 YPos = static_cast(HalfY + (HalfY * Proj.Y * -1.0)); DrawAngles(&Canvas, XPos, YPos, GetCurrentWidgetAxis(), GetWidgetMode(), GetWidgetCoordSystem().Rotator(), WidgetLocation); } } } } // If enabled, draw viewport selection limited text const TSharedPtr 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 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(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(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; } } } 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 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; } // 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 TemplateComponentsMoved; TArray 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(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) { 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(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(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(Cast(Data->FindComponentInstanceInActor(PreviewActor))); } } // Array corresponds to TemplateComponentsMoved, with objects we need to search for archetypes TArray 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(Moved.SceneComp); UPhysicsConstraintComponent* TemplateComp = Cast(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> 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& 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(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(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 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 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 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()->SetBegunPlay(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 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(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 BlueprintEditor = BlueprintEditorPtr.Pin()) { if (FEditorViewportSelectabilityBridge* SelectabilityBridge = BlueprintEditor->GetViewportSelectabilityBridge()) { return SelectabilityBridge->IsViewportSelectionLimited(); } } return false; } bool FSCSEditorViewportClient::IsObjectSelectableInViewport(UObject* const InObject) const { if (const TSharedPtr 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(InHitProxy); if (ActorHitProxy && IsValid(ActorHitProxy->Actor)) { if (bIsViewportSelectionLimited) { bIsActorHit = true; } PrimitiveComponent = const_cast(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(); } }