Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/LevelEditorViewport.cpp
2025-05-18 13:04:45 +08:00

5885 lines
205 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LevelEditorViewport.h"
#include "Animation/Skeleton.h"
#include "Materials/MaterialInterface.h"
#include "Modules/ModuleManager.h"
#include "Misc/PackageName.h"
#include "Misc/ITransaction.h"
#include "Framework/Application/SlateApplication.h"
#include "Styling/AppStyle.h"
#include "Components/MeshComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Animation/AnimSequenceBase.h"
#include "CanvasItem.h"
#include "Engine/BrushBuilder.h"
#include "Engine/SkeletalMesh.h"
#include "Settings/LevelEditorViewportSettings.h"
#include "Engine/Brush.h"
#include "AI/NavigationSystemBase.h"
#include "AssetRegistry/AssetData.h"
#include "Editor/UnrealEdEngine.h"
#include "Animation/AnimBlueprint.h"
#include "Exporters/ExportTextContainer.h"
#include "Factories/MaterialFactoryNew.h"
#include "Factories/MaterialInstanceConstantFactoryNew.h"
#include "Editor/GroupActor.h"
#include "Components/DecalComponent.h"
#include "Components/DirectionalLightComponent.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "Components/ModelComponent.h"
#include "Kismet2/ComponentEditorUtils.h"
#include "Engine/Selection.h"
#include "UObject/GCObjectScopeGuard.h"
#include "UObject/UObjectIterator.h"
#include "EngineUtils.h"
#include "Editor.h"
#include "EditorDirectories.h"
#include "EditorModeRegistry.h"
#include "EditorModes.h"
#include "PhysicsManipulationMode.h"
#include "UnrealEdGlobals.h"
#include "Materials/MaterialExpressionTextureSample.h"
#include "EditorSupportDelegates.h"
#include "Elements/Common/EditorDataStorageFeatures.h"
#include "Elements/Framework/TypedElementRegistry.h"
#include "Elements/Framework/EngineElementsLibrary.h"
#include "Elements/Framework/TypedElementCommonActions.h"
#include "Elements/Framework/TypedElementListObjectUtil.h"
#include "Elements/Framework/TypedElementViewportInteraction.h"
#include "Elements/Interfaces/TypedElementObjectInterface.h"
#include "Elements/Interfaces/TypedElementDataStorageCompatibilityInterface.h"
#include "Elements/Actor/ActorElementLevelEditorViewportInteractionCustomization.h"
#include "Elements/Component/ComponentElementLevelEditorViewportInteractionCustomization.h"
#include "AudioDevice.h"
#include "MouseDeltaTracker.h"
#include "ScopedTransaction.h"
#include "HModel.h"
#include "Layers/LayersSubsystem.h"
#include "StaticLightingSystem/StaticLightingPrivate.h"
#include "SEditorViewport.h"
#include "LevelEditor.h"
#include "LevelViewportActions.h"
#include "SLevelViewport.h"
#include "AssetSelection.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetToolsModule.h"
#include "IPlacementModeModule.h"
#include "Engine/Polys.h"
#include "ActorEditorUtils.h"
#include "ObjectTools.h"
#include "PackageTools.h"
#include "SnappingUtils.h"
#include "LevelViewportClickHandlers.h"
#include "DragTool_BoxSelect.h"
#include "DragTool_FrustumSelect.h"
#include "DragTool_Measure.h"
#include "DragTool_ViewportChange.h"
#include "DragAndDrop/BrushBuilderDragDropOp.h"
#include "DynamicMeshBuilder.h"
#include "Editor/ActorPositioning.h"
#include "Editor/ObjectPositioning.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Settings/EditorProjectSettings.h"
#include "ContentBrowserModule.h"
#include "IContentBrowserSingleton.h"
#include "ContentStreaming.h"
#include "IHeadMountedDisplay.h"
#include "IXRTrackingSystem.h"
#include "ActorGroupingUtils.h"
#include "EditorInteractiveGizmoManager.h"
#include "EditorWorldExtension.h"
#include "VREditorMode.h"
#include "EditorWorldExtension.h"
#include "ViewportWorldInteraction.h"
#include "Subsystems/BrushEditingSubsystem.h"
#include "Engine/VolumeTexture.h"
#include "Engine/TextureCube.h"
#include "Materials/MaterialExpressionDivide.h"
#include "Materials/MaterialExpressionSubtract.h"
#include "Materials/MaterialExpressionTransformPosition.h"
#include "Materials/MaterialExpressionCustom.h"
#include "Materials/MaterialExpressionWorldPosition.h"
#include "Materials/MaterialExpressionReflectionVectorWS.h"
#include "Materials/MaterialExpressionBounds.h"
#include "LevelEditorDragDropHandler.h"
#include "UnrealWidget.h"
#include "Tools/EdModeInteractiveToolsContext.h"
#include "GenericPlatform/GenericPlatformInputDeviceMapper.h"
#include "Rendering/StaticLightingSystemInterface.h"
#include "TextureResource.h"
#include "EditorDragTools/EditorDragToolBehaviorTarget.h"
#include "Subsystems/PlacementSubsystem.h"
#include "Subsystems/EditorElementSubsystem.h"
DEFINE_LOG_CATEGORY(LogEditorViewport);
#define LOCTEXT_NAMESPACE "LevelEditorViewportClient"
const FLevelViewportActorLock FLevelViewportActorLock::None(nullptr);
TWeakPtr<FTypedElementList> FLevelEditorViewportClient::StaticDropPreviewElements;
TArray< TWeakObjectPtr< AActor > > FLevelEditorViewportClient::DropPreviewActors;
TMap< TObjectKey< AActor >, TWeakObjectPtr< UActorComponent > > FLevelEditorViewportClient::ViewComponentForActorCache;
bool FLevelEditorViewportClient::bIsDroppingPreviewActor;
/** Static: List of objects we're hovering over */
TSet<FViewportHoverTarget> FLevelEditorViewportClient::HoveredObjects;
IMPLEMENT_HIT_PROXY( HLevelSocketProxy, HHitProxy );
namespace LevelEditorViewportLocals
{
const FText DropObjectsTransactionName = LOCTEXT("PlaceObjects", "Place Objects");
/**
* Calls Func on each element in ElementArray that represents an actor.
*/
void ForEachActorInElementArray(TArray<FTypedElementHandle> ElementArray, TFunctionRef<void(AActor&)> Func)
{
UTypedElementRegistry* Registry = UTypedElementRegistry::GetInstance();
if (!ensure(Registry))
{
return;
}
for (const FTypedElementHandle& Element : ElementArray)
{
ITypedElementObjectInterface* ObjectInterface = Registry->GetElementInterface<ITypedElementObjectInterface>(Element);
if (!ObjectInterface)
{
continue;
}
AActor* Actor = ObjectInterface->GetObjectAs<AActor>(Element);
if (!Actor)
{
continue;
}
Func(*Actor);
}
}
}
/** Helper function to compute a new location that is snapped to the origin plane given the users cursor location and camera angle */
static FVector4 AttemptToSnapLocationToOriginPlane( const FViewportCursorLocation& Cursor, FVector4 Location )
{
ELevelViewportType ViewportType = Cursor.GetViewportType();
if ( ViewportType == LVT_Perspective )
{
FVector CamPos = Cursor.GetViewportClient()->GetViewLocation();
FVector NewLocFloor( Location.X, Location.Y, 0 );
bool CamBelowOrigin = CamPos.Z < 0;
FPlane CamPlane( CamPos, FVector::UpVector );
// If the camera is looking at the floor place the brush on the floor
if ( !CamBelowOrigin && CamPlane.PlaneDot( Location ) < 0 )
{
Location = NewLocFloor;
}
else if ( CamBelowOrigin && CamPlane.PlaneDot( Location ) > 0 )
{
Location = NewLocFloor;
}
}
else if ( ViewportType == LVT_OrthoXY || ViewportType == LVT_OrthoNegativeXY )
{
// In ortho place the brush at the origin of the hidden axis
Location.Z = 0;
}
else if ( ViewportType == LVT_OrthoXZ || ViewportType == LVT_OrthoNegativeXZ )
{
// In ortho place the brush at the origin of the hidden axis
Location.Y = 0;
}
else if ( ViewportType == LVT_OrthoYZ || ViewportType == LVT_OrthoNegativeYZ )
{
// In ortho place the brush at the origin of the hidden axis
Location.X = 0;
}
return Location;
}
/** Helper function to get an atmosphere light from an index*/
static UDirectionalLightComponent* GetAtmosphericLight(const uint8 DesiredLightIndex, UWorld* ViewportWorld)
{
UDirectionalLightComponent* SelectedAtmosphericLight = nullptr;
float SelectedLightLuminance = 0.0f;
for (TObjectIterator<UDirectionalLightComponent> ComponentIt; ComponentIt; ++ComponentIt)
{
if (ComponentIt->GetWorld() == ViewportWorld)
{
UDirectionalLightComponent* AtmosphericLight = *ComponentIt;
if (!AtmosphericLight->IsUsedAsAtmosphereSunLight() || AtmosphericLight->GetAtmosphereSunLightIndex() != DesiredLightIndex || !AtmosphericLight->GetVisibleFlag())
continue;
float LightLuminance = AtmosphericLight->GetColoredLightBrightness().GetLuminance();
if (!SelectedAtmosphericLight || // Set it if null
SelectedLightLuminance < LightLuminance) // Or choose the brightest atmospheric light
{
SelectedAtmosphericLight = AtmosphericLight;
}
}
}
return SelectedAtmosphericLight;
}
static void NotifyAtmosphericLightHasMoved(UDirectionalLightComponent& SelectedAtmosphericLight, bool bFinished)
{
AActor* LightOwner = SelectedAtmosphericLight.GetOwner();
if (LightOwner)
{
// Now notify the owner about the transform update, e.g. construction script on instance.
LightOwner->PostEditMove(bFinished);
// No PostEditChangeProperty because not paired with a PreEditChange
if (bFinished)
{
SelectedAtmosphericLight.InvalidateLightingCache();
}
FStaticLightingSystemInterface::OnSkyAtmosphereModified.Broadcast();
}
}
TArray<FTypedElementHandle> FLevelEditorViewportClient::TryPlacingAssetObject(ULevel* InLevel, UObject* ObjToUse,
const UE::AssetPlacementUtil::FExtraPlaceAssetOptions& PlacementOptions,
const FViewportCursorLocation* CursorInformation)
{
using namespace LevelEditorViewportLocals;
// This is used for issuing a legacy notification.
TArray<AActor*> PlacedActors;
// Our actual output.
TArray<FTypedElementHandle> PlacedItems;
// Helper to update PlacedActors and PlacedItems. Can be called with null actor.
auto AddActorToPlaced = [&PlacedItems, &PlacedActors](AActor* Actor)
{
if (!Actor)
{
return;
}
PlacedActors.Add(Actor);
FTypedElementHandle Handle = UEngineElementsLibrary::AcquireEditorActorElementHandle(Actor);
if (ensure(Handle))
{
PlacedItems.Add(Handle);
}
};
if (!ensure(ObjToUse))
{
return PlacedItems;
}
UClass* ObjectClass = Cast<UClass>(ObjToUse);
if ( ObjectClass == NULL )
{
ObjectClass = ObjToUse->GetClass();
}
if (!ensure(ObjectClass))
{
return PlacedItems;
}
bool bPlaced = false;
if ( ObjectClass->IsChildOf( AActor::StaticClass() ) )
{
//Attempting to drop a UClass object
UActorFactory* ActorFactory = Cast<UActorFactory>(PlacementOptions.FactoryToUse.GetObject());
if ( ActorFactory == NULL )
{
ActorFactory = GEditor->FindActorFactoryForActorClass( ObjectClass );
}
if (ActorFactory != NULL )
{
UE::AssetPlacementUtil::FExtraPlaceAssetOptions OverridenPlacementOptions = PlacementOptions;
OverridenPlacementOptions.FactoryToUse = TScriptInterface<IAssetFactoryInterface>(ActorFactory);
PlacedItems = UE::AssetPlacementUtil::PlaceAssetInCurrentLevel(ObjToUse, OverridenPlacementOptions);
ForEachActorInElementArray(PlacedItems, [&PlacedActors](AActor& Actor)
{
PlacedActors.Add(&Actor);
});
bPlaced = PlacedItems.Num() > 0;
}
if (!bPlaced && !ObjectClass->HasAnyClassFlags(CLASS_NotPlaceable | CLASS_Abstract) )
{
// If no actor factory was found or failed, add the actor directly.
// TODO: We might want to investigate using the above PlaceAssetInCurrentLevel path even when we
// don't have an actor factory, and only use this legacy one if that fails for whatever reason.
// Note that this path acts a bit differently. For instance it doesn't use IsDroppingPreviewActor()
// to set bIsEditorPrieviewActor on the output if we are dropping a preview. We fix this in
// DropObjectsAtCoordinates where we directly know whether we are adding to previews (instead of using
// the static IsDroppingPreviewActor).
const FTransform ActorTransform = FActorPositioning::GetCurrentViewportPlacementTransform(
*ObjectClass->GetDefaultObject<AActor>(), /*bSnap=*/true, CursorInformation);
AActor* Actor = GEditor->AddActor( InLevel, ObjectClass, ActorTransform,
/*bSilent=*/false, PlacementOptions.ObjectFlags, PlacementOptions.bSelectOutput);
AddActorToPlaced(Actor);
bPlaced = (Actor != nullptr);
}
}
if (!bPlaced && ObjToUse->IsA( UExportTextContainer::StaticClass() ) )
{
// TODO: This path probably needs fixing for non-actor placement
UExportTextContainer* ExportContainer = CastChecked<UExportTextContainer>(ObjToUse);
const TArray<AActor*> NewActors = GEditor->AddExportTextActors( ExportContainer->ExportText,
/*bSilent*/false, PlacementOptions.ObjectFlags);
for (AActor* Actor : NewActors)
{
AddActorToPlaced(Actor);
}
}
else if (!bPlaced && ObjToUse->IsA( UBrushBuilder::StaticClass() ) )
{
UBrushBuilder* BrushBuilder = CastChecked<UBrushBuilder>(ObjToUse);
UWorld* World = InLevel->OwningWorld;
BrushBuilder->Build(World);
ABrush* DefaultBrush = World->GetDefaultBrush();
if (DefaultBrush != NULL)
{
FVector ActorLoc = GEditor->ClickLocation + GEditor->ClickPlane * (FVector::BoxPushOut(GEditor->ClickPlane, DefaultBrush->GetPlacementExtent()));
FSnappingUtils::SnapPointToGrid(ActorLoc, FVector::ZeroVector);
DefaultBrush->SetActorLocation(ActorLoc);
AddActorToPlaced(DefaultBrush);
}
}
else if (!bPlaced)
{
bool bShouldPlace = true;
if (ObjectClass->IsChildOf(UBlueprint::StaticClass()))
{
UBlueprint* BlueprintObj = StaticCast<UBlueprint*>(ObjToUse);
bShouldPlace = BlueprintObj->GeneratedClass != NULL;
if (bShouldPlace)
{
check(BlueprintObj->ParentClass == BlueprintObj->GeneratedClass->GetSuperClass());
if (BlueprintObj->GeneratedClass->HasAnyClassFlags(CLASS_NotPlaceable | CLASS_Abstract))
{
bShouldPlace = false;
}
}
}
if (bShouldPlace)
{
PlacedItems = UE::AssetPlacementUtil::PlaceAssetInCurrentLevel(ObjToUse, PlacementOptions);
LevelEditorViewportLocals::ForEachActorInElementArray(PlacedItems, [&PlacedActors](AActor& Actor)
{
PlacedActors.Add(&Actor);
// Not clear why this would be necessary, but kept for legacy behavior.
Actor.PostEditMove(true);
});
}
}
if (PlacedActors.Num() > 0)
{
FEditorDelegates::OnNewActorsPlaced.Broadcast(ObjToUse, PlacedActors);
}
return PlacedItems;
}
TArray<AActor*> FLevelEditorViewportClient::TryPlacingActorFromObject(ULevel* InLevel, UObject* ObjToUse,
bool bSelectActors, EObjectFlags ObjectFlags, UActorFactory* FactoryToUse,
const FName Name, const FViewportCursorLocation* Cursor)
{
TArray<AActor*> OutputActors;
// Forward the call to TryPlacingAssetObject
UE::AssetPlacementUtil::FExtraPlaceAssetOptions PlacementOptions;
PlacementOptions.bSelectOutput = bSelectActors;
PlacementOptions.ObjectFlags = ObjectFlags;
PlacementOptions.FactoryToUse = FactoryToUse;
PlacementOptions.Name = Name;
TArray<FTypedElementHandle> PlacedItems = TryPlacingAssetObject(InLevel, ObjToUse, PlacementOptions, Cursor);
// Filter out actors into the actors array
LevelEditorViewportLocals::ForEachActorInElementArray(PlacedItems, [&OutputActors](AActor& Actor)
{
OutputActors.Add(&Actor);
});
return OutputActors;
}
static FString GetSharedTextureNameAndKind( FString TextureName, EMaterialKind& Kind)
{
// Try and strip the suffix from the texture name, if we're successful it must be of that type.
bool hasBaseSuffix = TextureName.RemoveFromEnd( "_D" ) || TextureName.RemoveFromEnd( "_Diff" ) || TextureName.RemoveFromEnd( "_Diffuse" ) || TextureName.RemoveFromEnd( "_Detail" ) || TextureName.RemoveFromEnd( "_Base" );
if ( hasBaseSuffix )
{
Kind = EMaterialKind::Base;
return TextureName;
}
bool hasNormalSuffix = TextureName.RemoveFromEnd( "_N" ) || TextureName.RemoveFromEnd( "_Norm" ) || TextureName.RemoveFromEnd( "_Normal" );
if ( hasNormalSuffix )
{
Kind = EMaterialKind::Normal;
return TextureName;
}
bool hasSpecularSuffix = TextureName.RemoveFromEnd( "_S" ) || TextureName.RemoveFromEnd( "_Spec" ) || TextureName.RemoveFromEnd( "_Specular" );
if ( hasSpecularSuffix )
{
Kind = EMaterialKind::Specular;
return TextureName;
}
bool hasEmissiveSuffix = TextureName.RemoveFromEnd( "_E" ) || TextureName.RemoveFromEnd( "_Emissive" );
if ( hasEmissiveSuffix )
{
Kind = EMaterialKind::Emissive;
return TextureName;
}
Kind = EMaterialKind::Unknown;
return TextureName;
}
static UTexture* GetTextureWithNameVariations( const FString& BasePackageName, const TArray<FString>& Suffixes )
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>( TEXT( "AssetRegistry" ) );
// Try all the variations of suffixes, if we find a package matching the suffix, return it.
const FTopLevelAssetPath Texture2DClassPath(TEXT("/Script/Engine"), TEXT("Texture2D"));
for ( int i = 0; i < Suffixes.Num(); i++ )
{
TArray<FAssetData> OutAssetData;
if ( AssetRegistryModule.Get().GetAssetsByPackageName( *( BasePackageName + Suffixes[i] ), OutAssetData ) && OutAssetData.Num() > 0 )
{
if ( OutAssetData[0].AssetClassPath == Texture2DClassPath )
{
return Cast<UTexture>(OutAssetData[0].GetAsset());
}
}
}
return nullptr;
}
static bool TryAndCreateMaterialInput( UMaterial* UnrealMaterial, EMaterialKind TextureKind, UTexture* UnrealTexture, FExpressionInput& MaterialInput, int X, int Y )
{
// Ignore null textures.
if ( UnrealTexture == nullptr )
{
return false;
}
bool bTextureHasAlpha = !UnrealTexture->CompressionNoAlpha;
if ( bTextureHasAlpha )
{
if ( const UTexture2D* Texture2D = Cast<UTexture2D>(UnrealTexture) )
{
const EPixelFormatChannelFlags ValidTextureChannels = GetPixelFormatValidChannels(Texture2D->GetPixelFormat());
bTextureHasAlpha = EnumHasAnyFlags(ValidTextureChannels, EPixelFormatChannelFlags::A);
}
}
const bool bSetupAsNormalMap = UnrealTexture->IsNormalMap();
UMaterialEditorOnlyData* UnrealMaterialEditorOnly = UnrealMaterial->GetEditorOnlyData();
UnrealMaterial->BlendMode = bTextureHasAlpha ? EBlendMode::BLEND_Masked : EBlendMode::BLEND_Opaque;
// Create a new texture sample expression, this is our texture input node into the material output.
UMaterialExpressionTextureSample* UnrealTextureExpression = NewObject<UMaterialExpressionTextureSample>(UnrealMaterial);
UnrealMaterial->GetExpressionCollection().AddExpression( UnrealTextureExpression );
MaterialInput.Expression = UnrealTextureExpression;
UnrealTextureExpression->Texture = UnrealTexture;
UnrealTextureExpression->AutoSetSampleType();
UnrealTextureExpression->MaterialExpressionEditorX += X;
UnrealTextureExpression->MaterialExpressionEditorY += Y;
if ( UnrealTexture->IsA<UVolumeTexture>() )
{
// If it's a volume texture, build an expression which computes UVW coordinates from bounds-relative pixel position.
UMaterialExpressionDivide* DivideExpression = NewObject<UMaterialExpressionDivide>(UnrealMaterial);
UMaterialExpressionSubtract* BoundsRelativePosExpression = NewObject<UMaterialExpressionSubtract>(UnrealMaterial);
UMaterialExpressionSubtract* BoundsSizeExpression = NewObject<UMaterialExpressionSubtract>(UnrealMaterial);
UMaterialExpressionBounds* LocalBoundsExpression = NewObject<UMaterialExpressionBounds>(UnrealMaterial);
UMaterialExpressionTransformPosition* TransformPositionExpression = NewObject<UMaterialExpressionTransformPosition>(UnrealMaterial);
UMaterialExpressionWorldPosition* WorldPosExpression = NewObject<UMaterialExpressionWorldPosition>(UnrealMaterial);
UnrealMaterial->GetExpressionCollection().AddExpression( DivideExpression );
UnrealMaterial->GetExpressionCollection().AddExpression( BoundsRelativePosExpression );
UnrealMaterial->GetExpressionCollection().AddExpression( BoundsSizeExpression );
UnrealMaterial->GetExpressionCollection().AddExpression( LocalBoundsExpression );
UnrealMaterial->GetExpressionCollection().AddExpression( TransformPositionExpression );
UnrealMaterial->GetExpressionCollection().AddExpression( WorldPosExpression );
int32 EditorPosX = UnrealTextureExpression->MaterialExpressionEditorX;
int32 EditorPosY = UnrealTextureExpression->MaterialExpressionEditorY;
UnrealTextureExpression->Coordinates.Expression = DivideExpression;
EditorPosX -= 150;
DivideExpression->A.Expression = BoundsRelativePosExpression;
DivideExpression->B.Expression = BoundsSizeExpression;
DivideExpression->MaterialExpressionEditorX = EditorPosX;
DivideExpression->MaterialExpressionEditorY = EditorPosY;
EditorPosX -= 150;
BoundsRelativePosExpression->A.Expression = TransformPositionExpression;
BoundsRelativePosExpression->B.Expression = LocalBoundsExpression;
BoundsRelativePosExpression->B.OutputIndex = 2;
BoundsRelativePosExpression->MaterialExpressionEditorX = EditorPosX;
BoundsRelativePosExpression->MaterialExpressionEditorY = EditorPosY;
BoundsSizeExpression->A.Expression = LocalBoundsExpression;
BoundsSizeExpression->A.OutputIndex = UMaterialExpressionBounds::BoundsMaxOutputIndex;
BoundsSizeExpression->B.Expression = LocalBoundsExpression;
BoundsSizeExpression->B.OutputIndex = UMaterialExpressionBounds::BoundsMinOutputIndex;
BoundsSizeExpression->MaterialExpressionEditorX = EditorPosX;
BoundsSizeExpression->MaterialExpressionEditorY = EditorPosY + 100;
EditorPosX -= 300;
TransformPositionExpression->Input.Expression = WorldPosExpression;
TransformPositionExpression->TransformSourceType = TRANSFORMPOSSOURCE_World;
TransformPositionExpression->TransformType = TRANSFORMPOSSOURCE_Local;
TransformPositionExpression->MaterialExpressionEditorX = EditorPosX;
TransformPositionExpression->MaterialExpressionEditorY = EditorPosY;
LocalBoundsExpression->Type = MEILB_ObjectLocal;
LocalBoundsExpression->MaterialExpressionEditorX = EditorPosX;
LocalBoundsExpression->MaterialExpressionEditorY = EditorPosY + 100;
EditorPosX -= 250;
WorldPosExpression->WorldPositionShaderOffset = WPT_Default;
WorldPosExpression->MaterialExpressionEditorX = EditorPosX;
WorldPosExpression->MaterialExpressionEditorY = EditorPosY;
}
else if (UnrealTexture->IsA<UTextureCube>())
{
// If it's a cube texture, add a reflection vector expression to the UVW input to satisfy the expression input requirement
UMaterialExpressionReflectionVectorWS* ReflectionVectorExpression = NewObject<UMaterialExpressionReflectionVectorWS>(UnrealMaterial);
UnrealMaterial->GetExpressionCollection().AddExpression(ReflectionVectorExpression);
ReflectionVectorExpression->MaterialExpressionEditorX = UnrealTextureExpression->MaterialExpressionEditorX - 250; // Place node to the left to avoid overlap
ReflectionVectorExpression->MaterialExpressionEditorY = UnrealTextureExpression->MaterialExpressionEditorY;
UnrealTextureExpression->Coordinates.Expression = ReflectionVectorExpression;
}
// If we know for a fact this is a normal map, it can only legally be placed in the normal map slot.
// Ignore the Material kind for, but for everything else try and match it to the right slot, fallback
// to the BaseColor if we don't know.
if ( !bSetupAsNormalMap )
{
if ( TextureKind == EMaterialKind::Base )
{
UnrealMaterialEditorOnly->BaseColor.Expression = UnrealTextureExpression;
if ( bTextureHasAlpha && UnrealTextureExpression->Outputs.IsValidIndex(4) )
{
UnrealMaterialEditorOnly->Opacity.Connect(4, UnrealTextureExpression);
UnrealMaterialEditorOnly->OpacityMask.Connect(4, UnrealTextureExpression);
}
}
else if ( TextureKind == EMaterialKind::Specular )
{
UnrealMaterialEditorOnly->Specular.Expression = UnrealTextureExpression;
}
else if ( TextureKind == EMaterialKind::Emissive )
{
UnrealMaterialEditorOnly->EmissiveColor.Expression = UnrealTextureExpression;
}
else
{
UnrealMaterialEditorOnly->BaseColor.Expression = UnrealTextureExpression;
}
}
else
{
UnrealMaterialEditorOnly->Normal.Expression = UnrealTextureExpression;
}
return true;
}
UObject* FLevelEditorViewportClient::GetOrCreateMaterialFromTexture(UTexture* UnrealTexture)
{
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
// Check if a base material and corresponding params are set in the Settings to determine whether to create a Material or MIC
const ULevelEditorViewportSettings* ViewportSettings = GetDefault<ULevelEditorViewportSettings>();
UMaterialInterface* BaseMaterial = ViewportSettings->MaterialForDroppedTextures.LoadSynchronous();
const bool bCreateMaterialInstance = BaseMaterial && ViewportSettings->MaterialParamsForDroppedTextures.Num() > 0;
const FString TexturePackageName = UnrealTexture->GetPackage()->GetName();
FString TextureShortName = FPackageName::GetShortName(TexturePackageName);
// See if we can figure out what kind of material it is, based on a suffix, like _S for Specular, _D for Base/Detail/Diffuse.
// if it can determine which type of texture it was, it will return the base name of the texture minus the suffix.
EMaterialKind MaterialKind;
TextureShortName = GetSharedTextureNameAndKind( TextureShortName, MaterialKind );
FString NewPackageFolder;
if (AssetTools.GetWritableFolderPermissionList()->PassesStartsWithFilter(TexturePackageName))
{
// Create the material in the source texture folder
NewPackageFolder = FPackageName::GetLongPackagePath(TexturePackageName);
}
if (NewPackageFolder.IsEmpty())
{
// Create the material in the current world folder?
if (UWorld* EditorWorld = GEditor->GetEditorWorldContext().World())
{
const FString EditorWorldPackageName = EditorWorld->GetPackage()->GetName();
if (!FPackageName::IsTempPackage(EditorWorldPackageName))
{
NewPackageFolder = FPackageName::GetLongPackagePath(EditorWorldPackageName);
if (!AssetTools.GetWritableFolderPermissionList()->PassesStartsWithFilter(NewPackageFolder))
{
NewPackageFolder.Reset();
}
}
}
}
if (NewPackageFolder.IsEmpty())
{
// Create the material in the last directory an asset was created in?
if (FPackageName::TryConvertFilenameToLongPackageName(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::NEW_ASSET), NewPackageFolder))
{
if (!AssetTools.GetWritableFolderPermissionList()->PassesStartsWithFilter(NewPackageFolder))
{
NewPackageFolder.Reset();
}
}
else
{
NewPackageFolder.Reset();
}
}
if (NewPackageFolder.IsEmpty())
{
// Create the material in the game root folder
NewPackageFolder = TEXT("/Game");
}
const FString MaterialFullName = bCreateMaterialInstance ? (TEXT("MI_") + TextureShortName) : (TextureShortName + TEXT("_Mat"));
FString NewPackageName = FPaths::Combine(NewPackageFolder, MaterialFullName);
NewPackageName = UPackageTools::SanitizePackageName(NewPackageName);
// See if the material asset already exists with the expected name, if it does, just return
// an instance of it.
TArray<FAssetData> OutAssetData;
if (AssetRegistry.GetAssetsByPackageName(*NewPackageName, OutAssetData) && (OutAssetData.Num() > 0))
{
UObject* FoundAsset = OutAssetData[0].GetAsset();
if (FoundAsset != nullptr && // Due to redirects we might have an asset by this name that actually doesn't exist under that name anymore.
FoundAsset->IsA(UMaterialInterface::StaticClass()))
{
return FoundAsset;
}
UE_LOG(LogEditorViewport, Warning, TEXT("Failed to create material %s from texture because a non-material asset already exists with that name"), *NewPackageName);
return nullptr;
}
UPackage* Package = CreatePackage(*NewPackageName);
// Variations for Base Maps.
TArray<FString> BaseSuffixes;
BaseSuffixes.Add("_D");
BaseSuffixes.Add("_Diff");
BaseSuffixes.Add("_Diffuse");
BaseSuffixes.Add("_Detail");
BaseSuffixes.Add("_Base");
// Variations for Normal Maps.
TArray<FString> NormalSuffixes;
NormalSuffixes.Add("_N");
NormalSuffixes.Add("_Norm");
NormalSuffixes.Add("_Normal");
// Variations for Specular Maps.
TArray<FString> SpecularSuffixes;
SpecularSuffixes.Add("_S");
SpecularSuffixes.Add("_Spec");
SpecularSuffixes.Add("_Specular");
// Variations for Emissive Maps.
TArray<FString> EmissiveSuffixes;
EmissiveSuffixes.Add("_E");
EmissiveSuffixes.Add("_Emissive");
// The asset path for the base texture, we need this to try and append different suffixes to to find other textures we can use.
const FString BaseTexturePackage = FPackageName::GetLongPackagePath(TexturePackageName) + TEXT("/") + TextureShortName;
UMaterialInterface* CreatedMaterialInterface = nullptr;
if (bCreateMaterialInstance)
{
UMaterialInstanceConstantFactoryNew* Factory = NewObject<UMaterialInstanceConstantFactoryNew>();
Factory->InitialParent = BaseMaterial;
UMaterialInstanceConstant* CreatedMIC = Cast<UMaterialInstanceConstant>(Factory->FactoryCreateNew(UMaterialInstanceConstant::StaticClass(), Package, *MaterialFullName, RF_Standalone | RF_Public, NULL, GWarn));
if (!CreatedMIC)
{
return nullptr;
}
CreatedMaterialInterface = CreatedMIC;
if (MaterialKind == EMaterialKind::Unknown)
{
// If the texture type cannot be determined, treat it as a base texture and don't look for any matching textures
if (const FName* UnknownParam = ViewportSettings->MaterialParamsForDroppedTextures.Find(EMaterialKind::Unknown))
{
CreatedMIC->SetTextureParameterValueEditorOnly(*UnknownParam, UnrealTexture);
}
else if (const FName* BaseParam = ViewportSettings->MaterialParamsForDroppedTextures.Find(EMaterialKind::Base))
{
CreatedMIC->SetTextureParameterValueEditorOnly(*BaseParam, UnrealTexture);
}
else
{
UE_LOG(LogEditorViewport, Warning, TEXT("Dropped texture not assigned to material instance %s because no material parameter name was defined for Unknown or Base in LevelEditorViewportSettings"), *MaterialFullName);
}
}
else
{
auto AssignParam = [ViewportSettings, CreatedMIC, BaseTexturePackage](EMaterialKind ParamKind, TArray<FString>& Suffixes)->bool
{
if (const FName* Param = ViewportSettings->MaterialParamsForDroppedTextures.Find(ParamKind))
{
if (UTexture* Texture = GetTextureWithNameVariations(BaseTexturePackage, Suffixes))
{
CreatedMIC->SetTextureParameterValueEditorOnly(*Param, Texture);
return true;
}
}
return false;
};
// The passed-in texture should be found and assigned in one of the below functions, however it's possible this fails because
// the user hasn't set up their Settings properly. If another type of texture is found but not defined in the settings, then
// those can just silently fail - only warn the user if the specific texture they chose failed to assign.
bool bAssignedInputTexture = false;
bAssignedInputTexture |= AssignParam(EMaterialKind::Base, BaseSuffixes) && MaterialKind == EMaterialKind::Base;
bAssignedInputTexture |= AssignParam(EMaterialKind::Normal, NormalSuffixes) && MaterialKind == EMaterialKind::Normal;
bAssignedInputTexture |= AssignParam(EMaterialKind::Specular, SpecularSuffixes) && MaterialKind == EMaterialKind::Specular;
bAssignedInputTexture |= AssignParam(EMaterialKind::Emissive, EmissiveSuffixes) && MaterialKind == EMaterialKind::Emissive;
if (!bAssignedInputTexture)
{
UE_LOG(LogEditorViewport, Warning, TEXT("Dropped texture not assigned to material instance %s because no material parameter name was defined in LevelEditorViewportSettings"), *MaterialFullName);
}
}
}
else
{
// create an unreal material asset
UMaterialFactoryNew* MaterialFactory = NewObject<UMaterialFactoryNew>();
UMaterial* UnrealMaterial = (UMaterial*)MaterialFactory->FactoryCreateNew(
UMaterial::StaticClass(), Package, *MaterialFullName, RF_Standalone | RF_Public | RF_Transactional, NULL, GWarn);
if (UnrealMaterial == nullptr)
{
return nullptr;
}
CreatedMaterialInterface = UnrealMaterial;
UMaterialEditorOnlyData* UnrealMaterialEditorOnly = UnrealMaterial->GetEditorOnlyData();
const int HSpace = -300;
// If we were able to figure out the material kind, we need to try and build a complex material
// involving multiple textures. If not, just try and connect what we found to the base map.
if (MaterialKind == EMaterialKind::Unknown)
{
TryAndCreateMaterialInput(UnrealMaterial, EMaterialKind::Base, UnrealTexture, UnrealMaterialEditorOnly->BaseColor, HSpace, 0);
}
else
{
// Try and find different variations
UTexture* BaseTexture = GetTextureWithNameVariations(BaseTexturePackage, BaseSuffixes);
UTexture* NormalTexture = GetTextureWithNameVariations(BaseTexturePackage, NormalSuffixes);
UTexture* SpecularTexture = GetTextureWithNameVariations(BaseTexturePackage, SpecularSuffixes);
UTexture* EmissiveTexture = GetTextureWithNameVariations(BaseTexturePackage, EmissiveSuffixes);
// Connect and layout any textures we find into their respective inputs in the material.
const int VSpace = 170;
TryAndCreateMaterialInput(UnrealMaterial, EMaterialKind::Base, BaseTexture, UnrealMaterialEditorOnly->BaseColor, HSpace, VSpace * -1);
TryAndCreateMaterialInput(UnrealMaterial, EMaterialKind::Specular, SpecularTexture, UnrealMaterialEditorOnly->Specular, HSpace, VSpace * 0);
TryAndCreateMaterialInput(UnrealMaterial, EMaterialKind::Emissive, EmissiveTexture, UnrealMaterialEditorOnly->EmissiveColor, HSpace, VSpace * 1);
TryAndCreateMaterialInput(UnrealMaterial, EMaterialKind::Normal, NormalTexture, UnrealMaterialEditorOnly->Normal, HSpace, VSpace * 2);
}
}
CreatedMaterialInterface->PreEditChange(nullptr);
CreatedMaterialInterface->PostEditChange();
// Notify the asset registry
FAssetRegistryModule::AssetCreated(CreatedMaterialInterface);
// Set the dirty flag so this package will get saved later
Package->SetDirtyFlag( true );
CreatedMaterialInterface->ForceRecompileForRendering();
// Warn users that a new material has been created
FNotificationInfo Info( FText::Format( LOCTEXT( "DropTextureMaterialCreated", "{0} '{1}' Created" ), CreatedMaterialInterface->GetClass()->GetDisplayNameText(), FText::FromString(MaterialFullName)));
Info.ExpireDuration = 4.0f;
Info.bUseLargeFont = true;
Info.bUseSuccessFailIcons = false;
Info.Image = FAppStyle::GetBrush( "ClassThumbnail.Material" );
FSlateNotificationManager::Get().AddNotification( Info );
return CreatedMaterialInterface;
}
/**
* Helper function that attempts to apply the supplied object to the supplied actor.
*
* @param ObjToUse Object to attempt to apply as specific asset
* @param ComponentToApplyTo Component to whom the asset should be applied
* @param TargetMaterialSlot When dealing with submeshes this will represent the target section/slot to apply materials to.
* @param bTest Whether to test if the object would be successfully applied without actually doing it.
*
* @return true if the provided object was successfully applied to the provided actor
*/
static bool AttemptApplyObjToComponent(UObject* ObjToUse, USceneComponent* ComponentToApplyTo, int32 TargetMaterialSlot = -1, bool bTest = false)
{
bool bResult = false;
if (ComponentToApplyTo && !ComponentToApplyTo->IsCreatedByConstructionScript())
{
// MESH/DECAL
UMeshComponent* MeshComponent = Cast<UMeshComponent>(ComponentToApplyTo);
UDecalComponent* DecalComponent = Cast<UDecalComponent>(ComponentToApplyTo);
if (MeshComponent || DecalComponent)
{
// Dropping a texture?
UTexture* DroppedObjAsTexture = Cast<UTexture>(ObjToUse);
if (DroppedObjAsTexture != NULL)
{
if (bTest)
{
bResult = false;
}
else
{
// Turn dropped textures into materials
ObjToUse = FLevelEditorViewportClient::GetOrCreateMaterialFromTexture(DroppedObjAsTexture);
}
}
// Dropping a material?
UMaterialInterface* DroppedObjAsMaterial = Cast<UMaterialInterface>(ObjToUse);
if (DroppedObjAsMaterial)
{
if (bTest)
{
bResult = false;
}
else
{
bResult = FComponentEditorUtils::AttemptApplyMaterialToComponent(ComponentToApplyTo, DroppedObjAsMaterial, TargetMaterialSlot);
if (bResult)
{
GEditor->OnSceneMaterialsModified();
}
}
}
}
// SKELETAL MESH COMPONENT
USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(ComponentToApplyTo);
if (SkeletalMeshComponent)
{
// Dropping an Anim Blueprint?
UAnimBlueprint* DroppedObjAsAnimBlueprint = Cast<UAnimBlueprint>(ObjToUse);
if (DroppedObjAsAnimBlueprint)
{
USkeleton* AnimBPSkeleton = DroppedObjAsAnimBlueprint->TargetSkeleton;
if (AnimBPSkeleton)
{
if (bTest)
{
bResult = true;
}
else
{
const FScopedTransaction Transaction(LOCTEXT("DropAnimBlueprintOnObject", "Drop Anim Blueprint On Object"));
SkeletalMeshComponent->Modify();
// If the component doesn't have a mesh, the mesh should change
const bool bShouldChangeMesh = !SkeletalMeshComponent->GetSkeletalMeshAsset();
if (bShouldChangeMesh)
{
SkeletalMeshComponent->SetSkeletalMesh(AnimBPSkeleton->GetPreviewMesh(true));
}
SkeletalMeshComponent->SetAnimInstanceClass(DroppedObjAsAnimBlueprint->GeneratedClass);
bResult = true;
}
}
}
// Dropping an Anim Sequence or Vertex Animation?
UAnimSequenceBase* DroppedObjAsAnimSequence = Cast<UAnimSequenceBase>(ObjToUse);
if (DroppedObjAsAnimSequence)
{
USkeleton* AnimSkeleton = DroppedObjAsAnimSequence->GetSkeleton();
if (AnimSkeleton)
{
if (bTest)
{
bResult = true;
}
else
{
const FScopedTransaction Transaction(LOCTEXT("DropAnimationOnObject", "Drop Animation On Object"));
SkeletalMeshComponent->Modify();
// If the component doesn't have a mesh, the mesh should change
const bool bShouldChangeMesh = !SkeletalMeshComponent->GetSkeletalMeshAsset();
if (bShouldChangeMesh)
{
SkeletalMeshComponent->SetSkeletalMesh(AnimSkeleton->GetAssetPreviewMesh(DroppedObjAsAnimSequence));
}
SkeletalMeshComponent->SetAnimationMode(EAnimationMode::Type::AnimationSingleNode);
SkeletalMeshComponent->AnimationData.AnimToPlay = DroppedObjAsAnimSequence;
// set runtime data
SkeletalMeshComponent->SetAnimation(DroppedObjAsAnimSequence);
if (SkeletalMeshComponent->GetSkeletalMeshAsset())
{
bResult = true;
SkeletalMeshComponent->InitAnim(true);
}
}
}
}
}
}
return bResult;
}
/**
* Helper function that attempts to apply the supplied object to the supplied actor.
*
* @param ObjToUse Object to attempt to apply as specific asset
* @param ActorToApplyTo Actor to whom the asset should be applied
* @param TargetMaterialSlot When dealing with submeshes this will represent the target section/slot to apply materials to.
* @param bTest Whether to test if the object would be successfully applied without actually doing it.
*
* @return true if the provided object was successfully applied to the provided actor
*/
static bool AttemptApplyObjToActor( UObject* ObjToUse, AActor* ActorToApplyTo, int32 TargetMaterialSlot = -1, bool bTest = false )
{
bool bResult = false;
if ( ActorToApplyTo )
{
bResult = false;
TInlineComponentArray<USceneComponent*> SceneComponents;
ActorToApplyTo->GetComponents(SceneComponents);
for (USceneComponent* SceneComp : SceneComponents)
{
bResult |= AttemptApplyObjToComponent(ObjToUse, SceneComp, TargetMaterialSlot, bTest);
}
// Notification hook for dropping asset onto actor
if(!bTest)
{
FEditorDelegates::OnApplyObjectToActor.Broadcast(ObjToUse, ActorToApplyTo);
}
}
return bResult;
}
/**
* Helper function that attempts to apply the supplied object as a material to the BSP surface specified by the
* provided model and index.
*
* @param ObjToUse Object to attempt to apply as a material
* @param ModelHitProxy Hit proxy containing the relevant model
* @param SurfaceIndex The index in the model's surface array of the relevant
*
* @return true if the supplied object was successfully applied to the specified BSP surface
*/
bool FLevelEditorViewportClient::AttemptApplyObjAsMaterialToSurface( UObject* ObjToUse, HModel* ModelHitProxy, FViewportCursorLocation& Cursor )
{
bool bResult = false;
UTexture* DroppedObjAsTexture = Cast<UTexture>( ObjToUse );
if ( DroppedObjAsTexture != NULL )
{
ObjToUse = GetOrCreateMaterialFromTexture( DroppedObjAsTexture );
}
// Ensure the dropped object is a material
UMaterialInterface* DroppedObjAsMaterial = Cast<UMaterialInterface>( ObjToUse );
if ( DroppedObjAsMaterial && ModelHitProxy )
{
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
Viewport,
GetScene(),
EngineShowFlags)
.SetRealtimeUpdate( IsRealtime() ));
FSceneView* View = CalcSceneView( &ViewFamily );
UModel* Model = ModelHitProxy->GetModel();
// If our model doesn't exist
if ( !Model )
{
return false;
}
// or is part of an outer that is being destroyed
TWeakObjectPtr<UObject> ModelOuter(Model->GetOuter());
if ( !ModelOuter.IsValid() )
{
return false;
}
TArray<uint32> SelectedSurfaces;
bool bDropedOntoSelectedSurface = false;
const int32 DropX = Cursor.GetCursorPos().X;
const int32 DropY = Cursor.GetCursorPos().Y;
{
uint32 SurfaceIndex;
ModelHitProxy->ResolveSurface(View, DropX, DropY, SurfaceIndex);
if (SurfaceIndex != INDEX_NONE)
{
if ((Model->Surfs[SurfaceIndex].PolyFlags & PF_Selected) == 0)
{
// Surface was not selected so only apply to this surface
SelectedSurfaces.Add(SurfaceIndex);
}
else
{
bDropedOntoSelectedSurface = true;
}
}
}
if( bDropedOntoSelectedSurface )
{
for (int32 SurfaceIndex = 0; SurfaceIndex < Model->Surfs.Num(); ++SurfaceIndex)
{
FBspSurf& Surf = Model->Surfs[SurfaceIndex];
if (Surf.PolyFlags & PF_Selected)
{
SelectedSurfaces.Add(SurfaceIndex);
}
}
}
if( SelectedSurfaces.Num() )
{
// Apply the material to the specified surface
FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "DragDrop_Transaction_ApplyMaterialToSurface", "Apply Material to Surface"));
if (ModelHitProxy->GetModelComponent())
{
// Modify the component so that PostEditUndo can reregister the model after undo
ModelHitProxy->GetModelComponent()->Modify();
}
for( int32 SurfListIndex = 0; SurfListIndex < SelectedSurfaces.Num(); ++SurfListIndex )
{
uint32 SelectedSurfIndex = SelectedSurfaces[SurfListIndex];
check(Model->Surfs.IsValidIndex(SelectedSurfIndex));
Model->ModifySurf(SelectedSurfIndex, true);
Model->Surfs[SelectedSurfIndex].Material = DroppedObjAsMaterial;
const bool bUpdateTexCoords = false;
const bool bOnlyRefreshSurfaceMaterials = true;
GEditor->polyUpdateBrush(Model, SelectedSurfIndex, bUpdateTexCoords, bOnlyRefreshSurfaceMaterials);
}
bResult = true;
}
}
return bResult;
}
static bool AreAllDroppedObjectsBrushBuilders(const TArray<UObject*>& DroppedObjects)
{
for (UObject* DroppedObject : DroppedObjects)
{
if (!DroppedObject->IsA(UBrushBuilder::StaticClass()))
{
return false;
}
}
return true;
}
static bool AreAnyDroppedObjectsBrushBuilders(const TArray<UObject*>& DroppedObjects)
{
for (UObject* DroppedObject : DroppedObjects)
{
if (DroppedObject->IsA(UBrushBuilder::StaticClass()))
{
return true;
}
}
return false;
}
bool FLevelEditorViewportClient::DropObjectsOnBackground(FViewportCursorLocation& Cursor,
const TArray<UObject*>& DroppedObjects, EObjectFlags ObjectFlags, TArray<FTypedElementHandle>& OutNewItems,
const FDropObjectOptions& Options)
{
if (DroppedObjects.Num() == 0)
{
return false;
}
bool bSuccess = false;
const bool bTransacted = !Options.bCreateDropPreview && !AreAllDroppedObjectsBrushBuilders(DroppedObjects);
// Create a transaction if not a preview drop
if (bTransacted)
{
GEditor->BeginTransaction(LevelEditorViewportLocals::DropObjectsTransactionName);
}
for ( int32 DroppedObjectsIdx = 0; DroppedObjectsIdx < DroppedObjects.Num(); ++DroppedObjectsIdx )
{
UObject* AssetObj = DroppedObjects[DroppedObjectsIdx];
ensure( AssetObj );
// Attempt to create actors from the dropped object
UE::AssetPlacementUtil::FExtraPlaceAssetOptions PlacementOptions;
PlacementOptions.bSelectOutput = Options.bSelectOutput;
PlacementOptions.ObjectFlags = ObjectFlags;
PlacementOptions.FactoryToUse = Options.FactoryToUse;
PlacementOptions.Name = NAME_None;
TArray<FTypedElementHandle> PlacedItems = TryPlacingAssetObject(GetWorld()->GetCurrentLevel(), AssetObj,
PlacementOptions, &Cursor);
if (PlacedItems.Num() > 0)
{
OutNewItems.Append(PlacedItems);
bSuccess = true;
}
}
if (bTransacted)
{
GEditor->EndTransaction();
}
return bSuccess;
}
bool FLevelEditorViewportClient::DropObjectsOnBackground(FViewportCursorLocation& Cursor, const TArray<UObject*>& DroppedObjects, EObjectFlags ObjectFlags,
TArray<AActor*>& OutNewActors, bool bCreateDropPreview, bool bSelectOutput, UActorFactory* FactoryToUse)
{
FDropObjectOptions Options;
Options.bCreateDropPreview = bCreateDropPreview;
Options.bSelectOutput = bSelectOutput;
Options.FactoryToUse = FactoryToUse;
TArray<FTypedElementHandle> NewItems;
bool bSuccess = DropObjectsOnBackground(Cursor, DroppedObjects, ObjectFlags, NewItems, Options);
LevelEditorViewportLocals::ForEachActorInElementArray(NewItems, [&OutNewActors](AActor& Actor)
{
OutNewActors.Add(&Actor);
});
return bSuccess;
}
bool FLevelEditorViewportClient::DropObjectsOnActor(FViewportCursorLocation& Cursor,
const TArray<UObject*>& DroppedObjects, AActor* DroppedUponActor,
int32 DroppedUponSlot, EObjectFlags ObjectFlags,
TArray<FTypedElementHandle>& OutNewItems, const FDropObjectOptions& Options)
{
if ( !DroppedUponActor || DroppedObjects.Num() == 0 )
{
return false;
}
bool bSuccess = false;
const bool bTransacted = !Options.bCreateDropPreview && !AreAllDroppedObjectsBrushBuilders(DroppedObjects);
// Create a transaction if not a preview drop
if (bTransacted)
{
GEditor->BeginTransaction(LevelEditorViewportLocals::DropObjectsTransactionName);
}
for ( UObject* DroppedObject : DroppedObjects )
{
const bool bIsTestApplication = Options.bCreateDropPreview;
const bool bAppliedToActor = (Options.FactoryToUse == nullptr ) ? AttemptApplyObjToActor( DroppedObject, DroppedUponActor, DroppedUponSlot, bIsTestApplication ) : false;
if (!bAppliedToActor)
{
// Attempt to create actors from the dropped object
UE::AssetPlacementUtil::FExtraPlaceAssetOptions PlacementOptions;
PlacementOptions.bSelectOutput = Options.bSelectOutput;
PlacementOptions.ObjectFlags = ObjectFlags;
PlacementOptions.FactoryToUse = Options.FactoryToUse;
PlacementOptions.Name = NAME_None;
TArray<FTypedElementHandle> NewItems = TryPlacingAssetObject(GetWorld()->GetCurrentLevel(),
DroppedObject, PlacementOptions, &Cursor);
if (NewItems.Num() > 0 )
{
OutNewItems.Append(NewItems);
bSuccess = true;
}
}
else
{
bSuccess = true;
}
}
if (bTransacted)
{
GEditor->EndTransaction();
}
return bSuccess;
}
bool FLevelEditorViewportClient::DropObjectsOnActor(FViewportCursorLocation& Cursor, const TArray<UObject*>& DroppedObjects,
AActor* DroppedUponActor, int32 DroppedUponSlot, EObjectFlags ObjectFlags,
TArray<AActor*>& OutNewActors, bool bCreateDropPreview, bool bSelectOutput, UActorFactory* FactoryToUse)
{
FDropObjectOptions Options;
Options.bCreateDropPreview = bCreateDropPreview;
Options.bSelectOutput = bSelectOutput;
Options.FactoryToUse = FactoryToUse;
TArray<FTypedElementHandle> NewItems;
bool bSuccess = DropObjectsOnActor(Cursor, DroppedObjects, DroppedUponActor, DroppedUponSlot, ObjectFlags, NewItems, Options);
LevelEditorViewportLocals::ForEachActorInElementArray(NewItems, [&OutNewActors](AActor& Actor)
{
OutNewActors.Add(&Actor);
});
return bSuccess;
}
bool FLevelEditorViewportClient::DropObjectsOnBSPSurface(FSceneView* View, FViewportCursorLocation& Cursor,
const TArray<UObject*>& DroppedObjects, HModel* TargetProxy, EObjectFlags ObjectFlags,
TArray<FTypedElementHandle>& OutNewItems, const FDropObjectOptions& Options)
{
if (DroppedObjects.Num() == 0)
{
return false;
}
bool bSuccess = false;
const bool bTransacted = !Options.bCreateDropPreview && !AreAllDroppedObjectsBrushBuilders(DroppedObjects);
// Create a transaction if not a preview drop
if (bTransacted)
{
GEditor->BeginTransaction(LevelEditorViewportLocals::DropObjectsTransactionName);
}
for (UObject* DroppedObject : DroppedObjects)
{
const bool bAppliedToActor = (!Options.bCreateDropPreview && Options.FactoryToUse == NULL) ? AttemptApplyObjAsMaterialToSurface(DroppedObject, TargetProxy, Cursor) : false;
if (!bAppliedToActor)
{
// Attempt to create actors from the dropped object
UE::AssetPlacementUtil::FExtraPlaceAssetOptions PlacementOptions;
PlacementOptions.bSelectOutput = Options.bSelectOutput;
PlacementOptions.ObjectFlags = ObjectFlags;
PlacementOptions.FactoryToUse = Options.FactoryToUse;
PlacementOptions.Name = NAME_None;
TArray<FTypedElementHandle> NewItems = TryPlacingAssetObject(GetWorld()->GetCurrentLevel(),
DroppedObject, PlacementOptions, &Cursor);
if (NewItems.Num() > 0)
{
OutNewItems.Append(NewItems);
bSuccess = true;
}
}
else
{
bSuccess = true;
}
}
if (bTransacted)
{
GEditor->EndTransaction();
}
return bSuccess;
}
bool FLevelEditorViewportClient::DropObjectsOnBSPSurface(FSceneView* View, FViewportCursorLocation& Cursor,
const TArray<UObject*>& DroppedObjects, HModel* TargetProxy, EObjectFlags ObjectFlags,
TArray<AActor*>& OutNewActors, bool bCreateDropPreview, bool bSelectOutput, UActorFactory* FactoryToUse)
{
FDropObjectOptions Options;
Options.bCreateDropPreview = bCreateDropPreview;
Options.bSelectOutput = bSelectOutput;
Options.FactoryToUse = FactoryToUse;
TArray<FTypedElementHandle> NewItems;
bool bSuccess = DropObjectsOnBSPSurface(View, Cursor, DroppedObjects, TargetProxy, ObjectFlags, NewItems, Options);
LevelEditorViewportLocals::ForEachActorInElementArray(NewItems, [&OutNewActors](AActor& Actor)
{
OutNewActors.Add(&Actor);
});
return bSuccess;
}
/**
* Called when an asset is dropped upon a manipulation widget.
*
* @param View The SceneView for the dropped-in viewport
* @param Cursor Mouse cursor location
* @param DroppedObjects Array of objects dropped into the viewport
*
* @return true if the drop operation was successfully handled; false otherwise
*/
bool FLevelEditorViewportClient::DropObjectsOnWidget(FSceneView* View, FViewportCursorLocation& Cursor, const TArray<UObject*>& DroppedObjects, bool bCreateDropPreview)
{
bool bResult = false;
// Axis translation/rotation/scale widget - find out what's underneath the axis widget
// Modify the ShowFlags for the scene so we can re-render the hit proxies without any axis widgets.
// Store original ShowFlags and assign them back when we're done
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);
// 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!
FIntPoint DropPos = Cursor.GetCursorPos();
HHitProxy* HitProxy = Viewport->GetHitProxy(DropPos.X, DropPos.Y);
// We should never encounter a widget axis. If we do, then something's wrong
// with our ShowFlags (or the widget drawing code)
check( !HitProxy || ( HitProxy && !HitProxy->IsA( HWidgetAxis::StaticGetType() ) ) );
// Try this again, but without the widgets this time!
TArray<FTypedElementHandle> NewObjects;
const FIntPoint& CursorPos = Cursor.GetCursorPos();
FDropObjectOptions DropOptions;
DropOptions.bOnlyDropOnTarget = false;
DropOptions.bCreateDropPreview = bCreateDropPreview;
bResult = DropObjectsAtCoordinates(CursorPos.X, CursorPos.Y, DroppedObjects, NewObjects, DropOptions);
// Restore the original flags
EngineShowFlags.SetModeWidgets(bOldModeWidgets1);
SceneViewFamily->EngineShowFlags.SetModeWidgets(bOldModeWidgets2);
return bResult;
}
bool FLevelEditorViewportClient::HasDropPreviewActors() const
{
return HasDropPreviewElements();
}
/* Helper functions to find a dropped position on a 2D layer */
static bool IsDroppingOn2DLayer()
{
const ULevelEditorViewportSettings* ViewportSettings = GetDefault<ULevelEditorViewportSettings>();
const ULevelEditor2DSettings* Settings2D = GetDefault<ULevelEditor2DSettings>();
return ViewportSettings->bEnableLayerSnap && Settings2D->SnapLayers.IsValidIndex(ViewportSettings->ActiveSnapLayerIndex);
}
static UE::Positioning::FObjectPositioningTraceResult TraceForPositionOn2DLayer(const FViewportCursorLocation& Cursor)
{
const ULevelEditorViewportSettings* ViewportSettings = GetDefault<ULevelEditorViewportSettings>();
const ULevelEditor2DSettings* Settings2D = GetDefault<ULevelEditor2DSettings>();
check(Settings2D->SnapLayers.IsValidIndex(ViewportSettings->ActiveSnapLayerIndex));
const float Offset = Settings2D->SnapLayers[ViewportSettings->ActiveSnapLayerIndex].Depth;
FVector PlaneCenter(0, 0, 0);
FVector PlaneNormal(0, 0, 0);
switch (Settings2D->SnapAxis)
{
case ELevelEditor2DAxis::X: PlaneCenter.X = Offset; PlaneNormal.X = -1; break;
case ELevelEditor2DAxis::Y: PlaneCenter.Y = Offset; PlaneNormal.Y = -1; break;
case ELevelEditor2DAxis::Z: PlaneCenter.Z = Offset; PlaneNormal.Z = -1; break;
}
UE::Positioning::FObjectPositioningTraceResult Result;
const double Numerator = FVector::DotProduct(PlaneCenter - Cursor.GetOrigin(), PlaneNormal);
const double Denominator = FVector::DotProduct(PlaneNormal, Cursor.GetDirection());
if (FMath::Abs(Denominator) < SMALL_NUMBER)
{
Result.State = UE::Positioning::FObjectPositioningTraceResult::Failed;
}
else
{
Result.State = UE::Positioning::FObjectPositioningTraceResult::HitSuccess;
Result.SurfaceNormal = PlaneNormal;
double D = Numerator / Denominator;
Result.Location = Cursor.GetOrigin() + D * Cursor.GetDirection();
}
return Result;
}
bool FLevelEditorViewportClient::UpdateDropPreviewActors(int32 MouseX, int32 MouseY, const TArray<UObject*>& DroppedObjects, bool& out_bDroppedObjectsVisible, UActorFactory* FactoryToUse)
{
return UpdateDropPreviewElements(MouseX, MouseY, DroppedObjects, out_bDroppedObjectsVisible, FactoryToUse);
}
void FLevelEditorViewportClient::DestroyDropPreviewActors()
{
DestroyDropPreviewElements();
}
bool FLevelEditorViewportClient::HasDropPreviewElements() const
{
return ensure(DropPreviewElements) && DropPreviewElements->Num() > 0;
}
bool FLevelEditorViewportClient::UpdateDropPreviewElements(int32 MouseX, int32 MouseY,
const TArray<UObject*>& DroppedObjects, bool& out_bDroppedObjectsVisible,
TScriptInterface<IAssetFactoryInterface> Factory)
{
out_bDroppedObjectsVisible = false;
if (!HasDropPreviewElements())
{
return false;
}
// While dragging actors, allow viewport updates
bNeedsRedraw = true;
// If the mouse did not move, there is no need to update anything
if (MouseX == DropPreviewMouseX && MouseY == DropPreviewMouseY)
{
return false;
}
// Update the cached mouse position
DropPreviewMouseX = MouseX;
DropPreviewMouseY = MouseY;
// Get the center point between all the drop preview objects for use in calculations below
// Also, build a list of valid AActor* pointers
FVector CombinedOrigin = FVector::ZeroVector;
int32 Count = 0;
DropPreviewElements->ForEachElement<ITypedElementWorldInterface>([&CombinedOrigin, &Count](const TTypedElement<ITypedElementWorldInterface>& InElement)
{
FTransform Transform;
if (InElement.GetWorldTransform(Transform))
{
CombinedOrigin += Transform.GetTranslation();
++Count;
}
// true means don't stop
return true;
});
if (Count == 0)
{
return false;
}
// Finish the calculation of the actors origin now that we know we are not dividing by zero
CombinedOrigin /= Count;
FCollisionQueryParams CollisionQueryParams;
DropPreviewElements->ForEachElement<ITypedElementWorldInterface>(
[&CollisionQueryParams](const TTypedElement<ITypedElementWorldInterface>& InElement)
{
InElement.AddIgnoredElementToCollisionQueryParams(InElement, CollisionQueryParams);
// true means continue
return true;
});
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
Viewport,
GetScene(),
EngineShowFlags)
.SetRealtimeUpdate(IsRealtime()));
FSceneView* View = CalcSceneView(&ViewFamily);
FViewportCursorLocation Cursor(View, this, MouseX, MouseY);
const UE::Positioning::FObjectPositioningTraceResult TraceResult = IsDroppingOn2DLayer()
? TraceForPositionOn2DLayer(Cursor)
: UE::Positioning::TraceWorldForPositionWithDefault(Cursor, *View, &CollisionQueryParams);
GEditor->UnsnappedClickLocation = TraceResult.Location;
GEditor->ClickLocation = TraceResult.Location;
GEditor->ClickPlane = FPlane(TraceResult.Location, TraceResult.SurfaceNormal);
// Snap the new location if snapping is enabled
FSnappingUtils::SnapPointToGrid(GEditor->ClickLocation, FVector::ZeroVector);
AActor* DroppedOnActor = Cast<AActor>(TraceResult.HitObject.Get());
if (DroppedOnActor)
{
// We indicate that the dropped objects are visible if *any* of them are not applicable to other actors
out_bDroppedObjectsVisible = DroppedObjects.ContainsByPredicate([&](UObject* AssetObj) {
return !AttemptApplyObjToActor(AssetObj, DroppedOnActor, -1, true);
});
}
else
{
// All dropped objects are visible if we're not dropping on an actor
out_bDroppedObjectsVisible = true;
}
// Perform the actual updates
DropPreviewElements->ForEachElementHandle(
[this, &TraceResult, ActorFactory = Cast<UActorFactory>(Factory.GetObject())](const FTypedElementHandle& Element)
{
UTypedElementRegistry* Registry = UTypedElementRegistry::GetInstance();
if (!ensure(Registry))
{
return false;
}
// Used for the actual location update
ITypedElementWorldInterface* WorldInterface = Registry->GetElementInterface<ITypedElementWorldInterface>(Element);
// Used for some legacy updates if we're dealing with actors
ITypedElementObjectInterface* ObjectInterface = Registry->GetElementInterface<ITypedElementObjectInterface>(Element);
AActor* Actor = ObjectInterface ? ObjectInterface->GetObjectAs<AActor>(Element) : nullptr;
if (WorldInterface)
{
FTransform3d PreviousTransform = PreDragElementTransforms.FindRef(Element);
UActorFactory* ActorFactoryToUse = (!ActorFactory && Actor) ? GEditor->FindActorFactoryForActorClass(Actor->GetClass())
: ActorFactory;
FSnappedPositioningData PositioningData = FSnappedPositioningData(this, TraceResult.Location, TraceResult.SurfaceNormal)
.DrawSnapHelpers(true)
.UseStartTransform(PreviousTransform)
// TODO: Need to support non-actor factories here
.UseFactory(ActorFactoryToUse)
.UsePlacementExtent(Actor ? Actor->GetPlacementExtent() : FVector3d::Zero());
FTransform DestinationTransform = FActorPositioning::GetSnappedSurfaceAlignedTransform(PositioningData);
DestinationTransform.SetScale3D(PreviousTransform.GetScale3D()); // preserve scaling
WorldInterface->SetWorldTransform(Element, DestinationTransform);
}
if (Actor)
{
Actor->SetIsTemporarilyHiddenInEditor(false);
Actor->PostEditMove(false);
}
// true means don't stop
return true;
});
return true;
}
void FLevelEditorViewportClient::DestroyDropPreviewElements()
{
using namespace LevelEditorViewportLocals;
using namespace UE::Editor::DataStorage;
if (!HasDropPreviewElements())
{
return;
}
// TODO: This code to remove the object from TEDS should not be necessary, because the element
// deletion code further below should include TEDS deregistration. However, the code path for
// deleting preview actors in UUnrealEdEngine::DeleteActors skips explicit handle deregistration,
// and although it does still happen in the immediately triggered garbage cleanup, that feels
// potentially brittle.
if (ICompatibilityProvider* TedsCompat = GetMutableDataStorageFeature<ICompatibilityProvider>(CompatibilityFeatureName))
{
DropPreviewElements->ForEachElement<ITypedElementObjectInterface>(
[this, TedsCompat](const TTypedElement<ITypedElementObjectInterface>& InElement)
{
UObject* PreviewObject = InElement.GetObject();
if (PreviewObject && PreviewObject != GetWorld()->GetDefaultBrush())
{
TedsCompat->RemoveCompatibleObject(PreviewObject);
}
return true; // true means continue
});
}
// Used for special casing BSP backwards compatibility: the builder brush is used as a preview object, and
// we don't want to delete it.
const UObject* DefaultBrush = GetWorld() ? GetWorld()->GetDefaultBrush() : nullptr;
DropPreviewElements->ForEachElement<ITypedElementWorldInterface>([this, DefaultBrush](const TTypedElement<ITypedElementWorldInterface>& InElement)
{
// Make sure to remove any items keyed by this handle, otherwise we'll get complaints about
// remaining references to the handle after destroying the item
PreDragElementTransforms.Remove(InElement);
// Don't delete the BSP builder brush, which legacy code uses for previews and expects to always exist.
if (DefaultBrush)
{
ITypedElementObjectInterface* ObjectInterface = UTypedElementRegistry::GetInstance()->GetElementInterface<ITypedElementObjectInterface>(InElement);
const UObject* Object = ObjectInterface ? ObjectInterface->GetObject(InElement) : nullptr;
if (Object && Object == DefaultBrush)
{
// true means continue - don't fall through to the deletion.
return true;
}
}
FTypedElementDeletionOptions Options;
Options
.SetWarnAboutReferences(false)
.SetWarnAboutSoftReferences(false);
if (!ensure(InElement.DeleteElement(InElement.GetOwnerWorld(), GetMutableSelectionSet(), Options)))
{
// We don't expect to fail, but try a legacy actor deletion path if we do.
// TODO: This might not help that much because for actors, the DeleteElements will frequently still
// return true even if deletion does not go through because it calls UUnrealEdEngine::DeleteActors,
// and that function returns true for many failed deletions. It would be nice to make edits to make
// the check more reliable.
ITypedElementObjectInterface* ObjectInterface = UTypedElementRegistry::GetInstance()->GetElementInterface<ITypedElementObjectInterface>(InElement);
AActor* Actor = ObjectInterface ? ObjectInterface->GetObjectAs<AActor>(InElement) : nullptr;
if (Actor)
{
GetWorld()->DestroyActor(Actor);
}
}
return true; // true means continue
});
DropPreviewElements->Empty();
}
/**
* Checks the viewport to see if the given object can be dropped using the given mouse coordinates local to this viewport
*
* @param MouseX The position of the mouse's X coordinate
* @param MouseY The position of the mouse's Y coordinate
* @param AssetData Asset in question to be dropped
*/
FDropQuery FLevelEditorViewportClient::CanDropObjectsAtCoordinates(int32 MouseX, int32 MouseY, const FAssetData& AssetData)
{
FDropQuery Result;
ULevelEditorDragDropHandler* DragDrop = GEditor->GetLevelEditorDragDropHandler();
if (!DragDrop->PreviewDropObjectsAtCoordinates(MouseX, MouseY, GetWorld(), Viewport, AssetData))
{
Result.bCanDrop = DragDrop->GetCanDrop();
Result.HintText = DragDrop->GetPreviewDropHintText();
return Result;
}
UObject* AssetObj = AssetData.GetAsset();
UClass* ClassObj = Cast<UClass>(AssetObj);
UPlacementSubsystem* PlacementSubsystem = GEditor->GetEditorSubsystem<UPlacementSubsystem>();
// Check if the asset has an actor factory
bool bHasActorFactory = PlacementSubsystem && PlacementSubsystem->FindAssetFactoryFromAssetData(AssetData) != nullptr;
bHasActorFactory = bHasActorFactory || FActorFactoryAssetProxy::GetFactoryForAsset(AssetData) != nullptr;
if (ClassObj)
{
if (!bHasActorFactory && !ObjectTools::IsClassValidForPlacing(ClassObj))
{
Result.bCanDrop = false;
Result.HintText = FText::Format(LOCTEXT("DragAndDrop_CannotDropAssetClassFmt", "The class '{0}' cannot be placed in a level"), FText::FromString(ClassObj->GetName()));
return Result;
}
AssetObj = ClassObj->GetDefaultObject();
}
if (ensureMsgf(AssetObj != NULL, TEXT("AssetObj was null (%s)"), *AssetData.GetFullName()))
{
if (AssetObj->IsA(AActor::StaticClass()) || bHasActorFactory)
{
Result.bCanDrop = true;
GUnrealEd->SetPivotMovedIndependently(false);
}
else if (AssetObj->IsA(UBrushBuilder::StaticClass()))
{
Result.bCanDrop = true;
GUnrealEd->SetPivotMovedIndependently(false);
}
else
{
HHitProxy* HitProxy = Viewport->GetHitProxy(MouseX, MouseY);
if (HitProxy != nullptr && CanApplyMaterialToHitProxy(HitProxy))
{
if (AssetObj->IsA(UMaterialInterface::StaticClass()) || AssetObj->IsA(UTexture::StaticClass()))
{
// If our asset is a material and the target is a valid recipient
Result.bCanDrop = true;
GUnrealEd->SetPivotMovedIndependently(false);
}
}
}
}
return Result;
}
bool FLevelEditorViewportClient::DropObjectsAtCoordinates(int32 MouseX, int32 MouseY, const TArray<UObject*>& DroppedObjects,
TArray<FTypedElementHandle>& OutNewElements, const FDropObjectOptions& Options)
{
bool bResult = false;
// Allow the drag drop handler to do anything pre-drop.
ULevelEditorDragDropHandler* DragDropHandler = GEditor->GetLevelEditorDragDropHandler();
if (!Options.bCreateDropPreview)
{
// TODO: Deprecate PreDropObjectsAtCoordinates because it does not currently get used anywhere.
TArray<AActor*> NewActors;
if (!DragDropHandler->PreDropObjectsAtCoordinates(MouseX, MouseY, GetWorld(), Viewport, DroppedObjects, NewActors))
{
return bResult;
}
for (AActor* Actor : NewActors)
{
if (FTypedElementHandle Handle = UEngineElementsLibrary::AcquireEditorActorElementHandle(Actor))
{
OutNewElements.Add(Handle);
}
}
}
// Make sure the placement dragging actor is cleaned up.
DestroyDropPreviewActors();
if(DroppedObjects.Num() > 0)
{
bIsDroppingPreviewActor = Options.bCreateDropPreview;
Viewport->InvalidateHitProxy();
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
Viewport,
GetScene(),
EngineShowFlags)
.SetRealtimeUpdate( IsRealtime() ));
FSceneView* View = CalcSceneView( &ViewFamily );
FViewportCursorLocation Cursor(View, this, MouseX, MouseY);
HHitProxy* HitProxy = Viewport->GetHitProxy(Cursor.GetCursorPos().X, Cursor.GetCursorPos().Y);
const UE::Positioning::FObjectPositioningTraceResult TraceResult = IsDroppingOn2DLayer()
? TraceForPositionOn2DLayer(Cursor)
: UE::Positioning::TraceWorldForPositionWithDefault(Cursor, *View, nullptr);
GEditor->UnsnappedClickLocation = TraceResult.Location;
GEditor->ClickLocation = TraceResult.Location;
GEditor->ClickPlane = FPlane(TraceResult.Location, TraceResult.SurfaceNormal);
// Snap the new location if snapping is enabled
FSnappingUtils::SnapPointToGrid(GEditor->ClickLocation, FVector::ZeroVector);
EObjectFlags ObjectFlags = Options.bCreateDropPreview ? RF_Transient : RF_Transactional;
if (HitProxy == nullptr || HitProxy->IsA(HInstancedStaticMeshInstance::StaticGetType()))
{
bResult = DropObjectsOnBackground(Cursor, DroppedObjects, ObjectFlags, OutNewElements, Options);
}
else if (HitProxy->IsA(HActor::StaticGetType()) || HitProxy->IsA(HBSPBrushVert::StaticGetType()))
{
AActor* TargetActor = NULL;
UPrimitiveComponent* TargetComponent = nullptr;
int32 TargetMaterialSlot = -1;
if (HitProxy->IsA(HActor::StaticGetType()))
{
HActor* TargetProxy = static_cast<HActor*>(HitProxy);
TargetActor = TargetProxy->Actor;
TargetComponent = ConstCast(TargetProxy->PrimComponent);
TargetMaterialSlot = TargetProxy->MaterialIndex;
}
else if (HitProxy->IsA(HBSPBrushVert::StaticGetType()))
{
HBSPBrushVert* TargetProxy = static_cast<HBSPBrushVert*>(HitProxy);
TargetActor = TargetProxy->Brush.Get();
}
// If shift is pressed set the material slot to -1, so that it's applied to every slot.
// We have to request it from the platform application directly because IsShiftPressed gets
// the cached state, when the viewport had focus
if ( FSlateApplication::Get().GetPlatformApplication()->GetModifierKeys().IsShiftDown() )
{
TargetMaterialSlot = -1;
}
if (TargetActor != nullptr && IsValidChecked(TargetActor->GetWorld()) && !TargetActor->GetWorld()->IsUnreachable())
{
FNavigationLockContext LockNavigationUpdates(TargetActor->GetWorld(), ENavigationLockReason::SpawnOnDragEnter, Options.bCreateDropPreview);
// If the target actor is selected, we should drop onto all selected actors
// otherwise, we should drop only onto the target object
const bool bDropOntoSelectedActors = TargetActor->IsSelected();
const bool bCanApplyToComponent = AttemptApplyObjToComponent(DroppedObjects[0], TargetComponent, TargetMaterialSlot, true);
if (Options.bOnlyDropOnTarget || !bDropOntoSelectedActors || !bCanApplyToComponent)
{
if (bCanApplyToComponent)
{
const bool bIsTestAttempt = Options.bCreateDropPreview;
bResult = AttemptApplyObjToComponent(DroppedObjects[0], TargetComponent, TargetMaterialSlot, bIsTestAttempt);
}
else
{
// Couldn't apply to a component, so try dropping the objects on the hit actor
bResult = DropObjectsOnActor(Cursor, DroppedObjects, TargetActor, TargetMaterialSlot,
ObjectFlags, OutNewElements, Options);
}
}
else
{
// Are any components selected?
if (GEditor->GetSelectedComponentCount() > 0)
{
// Is the target component selected?
USelection* ComponentSelection = GEditor->GetSelectedComponents();
if (ComponentSelection->IsSelected(TargetComponent))
{
// The target component is selected, so try applying the object to every selected component
for (FSelectedEditableComponentIterator It(GEditor->GetSelectedEditableComponentIterator()); It; ++It)
{
USceneComponent* SceneComponent = Cast<USceneComponent>(*It);
AttemptApplyObjToComponent(DroppedObjects[0], SceneComponent, TargetMaterialSlot, Options.bCreateDropPreview);
bResult = true;
}
}
else
{
// The target component is not selected, so apply the object exclusively to it
bResult = AttemptApplyObjToComponent(DroppedObjects[0], TargetComponent, TargetMaterialSlot, Options.bCreateDropPreview);
}
}
if (!bResult)
{
const FScopedTransaction Transaction(LOCTEXT("DropObjectsOnSelectedActors", "Drop Objects on Selected Actors"));
for (FSelectionIterator It(*GEditor->GetSelectedActors()); It; ++It)
{
TargetActor = static_cast<AActor*>( *It );
if (TargetActor)
{
DropObjectsOnActor(Cursor, DroppedObjects, TargetActor,
TargetMaterialSlot, ObjectFlags, OutNewElements, Options);
bResult = true;
}
}
}
}
}
}
else if (HitProxy->IsA(HModel::StaticGetType()))
{
// BSP surface
bResult = DropObjectsOnBSPSurface(View, Cursor, DroppedObjects, static_cast<HModel*>(HitProxy), ObjectFlags, OutNewElements, Options);
}
else if( HitProxy->IsA( HWidgetAxis::StaticGetType() ) )
{
// Axis translation/rotation/scale widget - find out what's underneath the axis widget
bResult = DropObjectsOnWidget(View, Cursor, DroppedObjects);
}
// If the hit proxy was not one of the above types, we probably still don't want to cancel the drop.
else
{
// Ideally we would probably use some interface to get the information we need to confirm the drop. However, the fallback
// drop we use in the case of actors doesn't really need extra information. Instead, we'll just use the presence of a
// ITypedElementWorldInterface interface to mean that the hitproxy corresponds to something real in the world.
auto DoesElementHaveWorldInterface = [](const FTypedElementHandle& Handle) -> bool
{
UTypedElementRegistry* Registry = UTypedElementRegistry::GetInstance();
if (!Handle || !Registry)
{
return false;
}
return !!Registry->GetElementInterface<ITypedElementWorldInterface>(Handle);
};
if (DoesElementHaveWorldInterface(HitProxy->GetElementHandle()))
{
// This is the same thing that we do in other "drop objects" functions like FLevelEditorViewportClient::DropObjectsOnBackground
// and FLevelEditorViewportClient::DropObjectsOnActor (when not doing some kind of special "apply to actor" operation)
for (UObject* DroppedObject : DroppedObjects)
{
UE::AssetPlacementUtil::FExtraPlaceAssetOptions PlacementOptions;
PlacementOptions.bSelectOutput = Options.bSelectOutput;
PlacementOptions.ObjectFlags = ObjectFlags;
PlacementOptions.FactoryToUse = Options.FactoryToUse;
PlacementOptions.Name = NAME_None;
TArray<FTypedElementHandle> NewElements = TryPlacingAssetObject(GetWorld()->GetCurrentLevel(), DroppedObject, PlacementOptions, &Cursor);
if (NewElements.Num() > 0)
{
OutNewElements.Append(NewElements);
bResult = true;
}
}
}
}
if ( bResult )
{
// If we are creating a drop preview actor instead of a normal actor, we need to disable collision, selection, and make sure it is never saved.
if (Options.bCreateDropPreview && OutNewElements.Num() > 0 )
{
// Theoretically we shouldn't have selected preview items to begin with, but we've had this safety deselection
// code forever, and might as well keep it.
UTypedElementSelectionSet* SelectionSet = GetMutableSelectionSet();
if (ensure(SelectionSet))
{
FTypedElementSelectionOptions SelectionOptions;
SelectionSet->DeselectElements(OutNewElements, SelectionOptions);
}
DestroyDropPreviewElements();
// Helper to add elements to DropPreviewElements
auto StorePreviewElement = [this](const FTypedElementHandle& Element)
{
UTypedElementRegistry* Registry = UTypedElementRegistry::GetInstance();
if (!ensure(Registry))
{
return;
}
ITypedElementWorldInterface* WorldInterface = Registry->GetElementInterface<ITypedElementWorldInterface>(Element);
// Don't store preview element if it doesn't have a world interface, because we would need it
// to delete the preview element or to move it around
if (!WorldInterface)
{
return;
}
DropPreviewElements->Add(Element);
};
// Collect the preview elements and save pre drag transforms.
for (const FTypedElementHandle& Element : OutNewElements)
{
StorePreviewElement(Element);
}
// Store pre drag transforms
DropPreviewElements->ForEachElement<ITypedElementWorldInterface>(
[this](const TTypedElement<ITypedElementWorldInterface>& InElement)
{
FTransform Transform;
if (InElement.GetWorldTransform(Transform))
{
PreDragElementTransforms.Add(InElement, Transform);
}
return true; // true means continue
});
// Do some legacy actor handling
DropPreviewElements->ForEachElement<ITypedElementObjectInterface>(
[this](const TTypedElement<ITypedElementObjectInterface>& InElement)
{
AActor* NewActor = InElement.GetObjectAs<AActor>();
if (!NewActor)
{
return true; // true means continue
}
PreDragActorTransforms.Add(NewActor, NewActor->GetTransform());
NewActor->SetActorEnableCollision(false);
// This boolean already gets set via UActorFactory::PlaceAsset in code paths that go through
// PlaceAssetUsingFactory (PlacementOptions.bIsCreatingPreviewElements = FLevelEditorViewportClient::IsDroppingPreviewActor();)
// But bIsEditorPrieviewActor does not get set if we go through GEditor->AddActor, which can
// prevent preview actors from being properly destroyed later. If we fix/eliminate paths that
// do not set this bool properly, we can do an ensure here.
NewActor->bIsEditorPreviewActor = FLevelEditorViewportClient::IsDroppingPreviewActor();
// Prevent future selection. This also prevents the hit proxy from interfering with placement logic.
for (UActorComponent* Component : NewActor->GetComponents())
{
if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(Component))
{
PrimComp->bSelectable = false;
}
}
// true means continue
return true;
});
// Set the current MouseX/Y to prime the preview update
DropPreviewMouseX = MouseX;
DropPreviewMouseY = MouseY;
// Invalidate the hit proxy now so the drop preview will be accurate.
// We don't invalidate the hit proxy in the drop preview update itself because it is slow.
//Viewport->InvalidateHitProxy();
}
// Dropping the actors rather than a preview? Probably want to select them all then.
else if(!Options.bCreateDropPreview && Options.bSelectOutput && DroppedObjects.Num() > 0
// If we're dropping bsp brushes, that geometry actually gets created later, in
// FBrushBuilderDragDropOp::OnDrop(), so we don't want to select the (preview) builder brush.
// (for reference, this function gets called via CurWidget.Widget->OnDrop() in FSlateApplication::RoutePointerUpEvent,
// whereas the FBrushBuilderDragDropOp::OnDrop() gets called via SlateUser->NotifyPointerReleased).
&& !AreAnyDroppedObjectsBrushBuilders(DroppedObjects))
{
UTypedElementSelectionSet* SelectionSet = GetMutableSelectionSet();
if (ensure(SelectionSet))
{
FTypedElementSelectionOptions SelectionOptions;
SelectionSet->SelectElements(OutNewElements, SelectionOptions);
}
}
// Give the viewport focus
//SetFocus( static_cast<HWND>( Viewport->GetWindow() ) );
SetCurrentViewport();
}
}
if (bResult)
{
if ( !Options.bCreateDropPreview && IPlacementModeModule::IsAvailable() )
{
IPlacementModeModule::Get().AddToRecentlyPlaced(DroppedObjects, Options.FactoryToUse);
}
if (!Options.bCreateDropPreview)
{
TArray<AActor*> Actors;
LevelEditorViewportLocals::ForEachActorInElementArray(OutNewElements, [&Actors](AActor& Actor)
{
Actors.Add(&Actor);
});
FEditorDelegates::OnNewActorsDropped.Broadcast(DroppedObjects, Actors);
}
}
if (!Options.bCreateDropPreview)
{
DragDropHandler->PostDropObjectsAtCoordinates(MouseX, MouseX, World, Viewport, DroppedObjects);
}
// Reset if creating a preview actor.
bIsDroppingPreviewActor = false;
return bResult;
}
bool FLevelEditorViewportClient::DropObjectsAtCoordinates(int32 MouseX, int32 MouseY, const TArray<UObject*>& DroppedObjects, TArray<AActor*>& OutNewActors, bool bOnlyDropOnTarget, bool bCreateDropPreview, bool bSelectOutput, UActorFactory* FactoryToUse)
{
FDropObjectOptions Options;
Options.bOnlyDropOnTarget = bOnlyDropOnTarget;
Options.bCreateDropPreview = bCreateDropPreview;
Options.bSelectOutput = bSelectOutput;
Options.FactoryToUse = FactoryToUse;
TArray<FTypedElementHandle> NewElements;
bool bSuccess = DropObjectsAtCoordinates(MouseX, MouseY, DroppedObjects, NewElements, Options);
LevelEditorViewportLocals::ForEachActorInElementArray(NewElements, [&OutNewActors](AActor& Actor)
{
OutNewActors.Add(&Actor);
});
return bSuccess;
}
/**
* Called to check if a material can be applied to an object, given the hit proxy
*/
bool FLevelEditorViewportClient::CanApplyMaterialToHitProxy( const HHitProxy* HitProxy ) const
{
// The check for HWidgetAxis is made to prevent the transform widget from blocking an attempt at applying a material to a mesh.
return ( HitProxy->IsA(HModel::StaticGetType()) || HitProxy->IsA(HActor::StaticGetType()) || HitProxy->IsA(HWidgetAxis::StaticGetType()) );
}
FTrackingTransaction::FTrackingTransaction()
{
}
FTrackingTransaction::~FTrackingTransaction()
{
End();
USelection::SelectionChangedEvent.RemoveAll(this);
}
void FTrackingTransaction::Begin(const FText& Description, AActor* AdditionalActor)
{
End();
ScopedTransaction = new FScopedTransaction( Description );
TrackingTransactionState = ETransactionState::Active;
if (UTypedElementSelectionSet* SelectionSet = GetMutableSelectionSet())
{
// TODO: Ideally "Begin" would *not* speculatively call Modify at all (nor need to track/restore dirty state),
// as Modify should be called on-demand prior to changes within the transaction, however various existing
// code makes assumptions that tracking transactions will call Modify on the active selection.
// Call modify the currently selected objects, gathering up any groups for processing later
TSet<AGroupActor*> GroupsPendingModify;
auto ModifyObject = [this, &GroupsPendingModify](UObject* InObject)
{
// Track the dirty state of the packages so that it can be restored if the selection changes mid-transaction (eg, drag duplication)
UPackage* Package = InObject->GetOutermost();
if (!InitialPackageDirtyStates.Contains(Package))
{
InitialPackageDirtyStates.Add(Package, Package->IsDirty());
}
AActor* Actor = Cast<AActor>(InObject);
if (Actor && UActorGroupingUtils::IsGroupingActive())
{
if (AGroupActor* GroupActor = AGroupActor::GetRootForActor(Actor, true))
{
GroupsPendingModify.Add(GroupActor);
}
}
InObject->Modify();
return true;
};
SelectionSet->ForEachSelectedObject(ModifyObject);
if (AdditionalActor)
{
ModifyObject(AdditionalActor);
}
// Recursively call modify on any groups, as viewport manipulation of an actor within a group tends to affect the entire group
for (AGroupActor* GroupPendingModify : GroupsPendingModify)
{
GroupPendingModify->ForEachActorInGroup([](AActor* InGroupedActor, AGroupActor* InGroupActor)
{
InGroupedActor->Modify();
});
}
SelectionSet->OnChanged().AddRaw(this, &FTrackingTransaction::OnEditorSelectionChanged);
}
}
void FTrackingTransaction::End()
{
if( ScopedTransaction )
{
delete ScopedTransaction;
ScopedTransaction = NULL;
}
TrackingTransactionState = ETransactionState::Inactive;
InitialPackageDirtyStates.Empty();
if (UTypedElementSelectionSet* SelectionSet = GetMutableSelectionSet())
{
SelectionSet->OnChanged().RemoveAll(this);
}
}
void FTrackingTransaction::Cancel()
{
if(ScopedTransaction)
{
ScopedTransaction->Cancel();
}
// If the transaction is cancelled, reset the package dirty states to reflect their original state
for (const TTuple<UPackage*, bool>& InitialPackageDirtyState : InitialPackageDirtyStates)
{
UPackage* Package = InitialPackageDirtyState.Key;
const bool bInitialDirtyState = InitialPackageDirtyState.Value;
if (!bInitialDirtyState && Package->IsDirty())
{
Package->SetDirtyFlag(false);
}
}
End();
}
void FTrackingTransaction::BeginPending(const FText& Description)
{
End();
PendingDescription = Description;
TrackingTransactionState = ETransactionState::Pending;
}
void FTrackingTransaction::PromotePendingToActive()
{
if(IsPending())
{
Begin(PendingDescription);
PendingDescription = FText();
}
}
// TODO: Ideally this should be given the selection set for the level editor, rather than rely on the global state
const UTypedElementSelectionSet* FTrackingTransaction::GetSelectionSet() const
{
return GetMutableSelectionSet();
}
UTypedElementSelectionSet* FTrackingTransaction::GetMutableSelectionSet() const
{
return (GEditor && GEditor->GetSelectedActors())
? GEditor->GetSelectedActors()->GetElementSelectionSet()
: nullptr;
}
void FTrackingTransaction::OnEditorSelectionChanged(const UTypedElementSelectionSet* InSelectionSet)
{
if (!InitialPackageDirtyStates.Num())
{
return;
}
checkf(TrackingTransactionState == ETransactionState::Active, TEXT("Inactive tracking transaction contains package states"));
// The selection state has changed mid-transaction (eg, drag duplication)
// Restore the dirty state on any packages which no longer have selected objects
TSet<UPackage*> SelectedPackages;
InSelectionSet->ForEachSelectedObject([&SelectedPackages](UObject* InObject)
{
SelectedPackages.Add(InObject->GetOutermost());
return true;
});
for (const TTuple<UPackage*, bool>& InitialPackageDirtyState : InitialPackageDirtyStates)
{
UPackage* Package = InitialPackageDirtyState.Key;
const bool bInitialDirtyState = InitialPackageDirtyState.Value;
if (!SelectedPackages.Contains(Package) && !bInitialDirtyState && Package->IsDirty())
{
Package->SetDirtyFlag(false);
}
}
InitialPackageDirtyStates.Empty();
}
FLevelEditorViewportClient::FLevelEditorViewportClient(const TSharedPtr<SLevelViewport>& InLevelViewport)
: FEditorViewportClient(&GLevelEditorModeTools(), nullptr, StaticCastSharedPtr<SEditorViewport>(InLevelViewport))
, ViewHiddenLayers()
, VolumeActorVisibility()
, LastEditorViewLocation( FVector::ZeroVector )
, LastEditorViewRotation( FRotator::ZeroRotator )
, ColorScale( FVector(1,1,1) )
, FadeColor( FColor(0,0,0) )
, FadeAmount(0.f)
, bEnableFading(false)
, bEnableColorScaling(false)
, bDrawBaseInfo(false)
, bDuplicateOnNextDrag( false )
, bDuplicateActorsInProgress( false )
, bIsTrackingBrushModification( false )
, bOnlyMovedPivot(false)
, bLockedCameraView(true)
, bNeedToRestoreComponentBeingMovedFlag(false)
, bHasBegunGizmoManipulation(false)
, bReceivedFocusRecently(false)
, bAlwaysShowModeWidgetAfterSelectionChanges(true)
, CachedElementsToManipulate(UTypedElementRegistry::GetInstance()->CreateElementList())
, SpriteCategoryVisibility()
, World(nullptr)
, TrackingTransaction()
, CachedPilotTransform(TOptional<FTransform>())
, DropPreviewMouseX(0)
, DropPreviewMouseY(0)
, bWasControlledByOtherViewport(false)
, bCurrentlyEditingThroughMovementWidget(false)
, bEditorCameraCut(false)
, bWasEditorCameraCut(false)
, bApplyCameraSpeedScaleByDistance(true)
{
// By default a level editor viewport is pointed to the editor world
SetReferenceToWorldContext(GEditor->GetEditorWorldContext());
GEditor->AddLevelViewportClients(this);
// The level editor fully supports mode tools and isn't doing any incompatible stuff with the Widget
ModeTools->SetWidgetMode(UE::Widget::WM_Translate);
Widget->SetUsesEditorModeTools(ModeTools.Get());
ModeTools->OnWidgetModeChanged().AddRaw(this, &FLevelEditorViewportClient::OnWidgetModeChanged);
// Register for editor cleanse events so we can release references to hovered actors
FEditorSupportDelegates::CleanseEditor.AddRaw(this, &FLevelEditorViewportClient::OnEditorCleanse);
// Register for editor PIE event that allows you to clean up states that might block PIE
FEditorDelegates::PreBeginPIE.AddRaw(this, &FLevelEditorViewportClient::OnPreBeginPIE);
// Add a delegate so we get informed when an actor has moved.
GEngine->OnActorMoved().AddRaw(this, &FLevelEditorViewportClient::OnActorMoved);
// Set up defaults for the draw helper.
DrawHelper.bDrawGrid = true;
DrawHelper.bDrawPivot = false;
DrawHelper.bDrawBaseInfo = false;
DrawHelper.bDrawWorldBox = false;
DrawHelper.bDrawKillZ = false;
InitializeVisibilityFlags();
// Sign up for notifications about users changing settings.
GetMutableDefault<ULevelEditorViewportSettings>()->OnSettingChanged().AddRaw(this, &FLevelEditorViewportClient::HandleViewportSettingChanged);
FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor").OnMapChanged().AddRaw(this, &FLevelEditorViewportClient::OnMapChanged);
DropPreviewElements = StaticDropPreviewElements.Pin();
if (!StaticDropPreviewElements.IsValid() && UTypedElementRegistry::GetInstance())
{
DropPreviewElements = UTypedElementRegistry::GetInstance()->CreateElementList();
StaticDropPreviewElements = DropPreviewElements;
}
}
//
// FLevelEditorViewportClient::~FLevelEditorViewportClient
//
FLevelEditorViewportClient::~FLevelEditorViewportClient()
{
if (UTypedElementSelectionSet* SelectionSet = GetMutableSelectionSet())
{
SelectionSet->OnPreChange().RemoveAll(this);
}
if (UTypedElementRegistry* Registry = UTypedElementRegistry::GetInstance())
{
Registry->OnProcessingDeferredElementsToDestroy().RemoveAll(this);
}
ResetElementsToManipulate();
ModeTools->OnWidgetModeChanged().RemoveAll(this);
// Unregister for all global callbacks to this object
FEditorSupportDelegates::CleanseEditor.RemoveAll(this);
FEditorDelegates::PreBeginPIE.RemoveAll(this);
FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor").OnMapChanged().RemoveAll(this);
if(GEngine)
{
// Remove our move delegate
GEngine->OnActorMoved().RemoveAll(this);
// make sure all actors have this view removed from their visibility bits
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
Layers->RemoveViewFromActorViewVisibility(this);
GEditor->RemoveLevelViewportClients(this);
if (FocusTimerHandle.IsValid())
{
GEditor->GetTimerManager()->ClearTimer(FocusTimerHandle);
}
GetMutableDefault<ULevelEditorViewportSettings>()->OnSettingChanged().RemoveAll(this);
RemoveReferenceToWorldContext(GEditor->GetEditorWorldContext());
}
//make to clean up the global "current" & "last" clients when we delete the active one.
if (GCurrentLevelEditingViewportClient == this)
{
GCurrentLevelEditingViewportClient = NULL;
}
if (GLastKeyLevelEditingViewportClient == this)
{
GLastKeyLevelEditingViewportClient = NULL;
}
}
void FLevelEditorViewportClient::InitializeVisibilityFlags()
{
// make sure all actors know about this view for per-view layer vis
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
Layers->UpdatePerViewVisibility(this);
// Get the number of volume classes so we can initialize our bit array
TArray<UClass*> VolumeClasses;
UUnrealEdEngine::GetSortedVolumeClasses(&VolumeClasses);
VolumeActorVisibility.Init(true, VolumeClasses.Num());
// Initialize all sprite categories to visible
SpriteCategoryVisibility.Init(true, GUnrealEd->SpriteIDToIndexMap.Num());
}
void FLevelEditorViewportClient::InitializeViewportInteraction()
{
{
UTypedElementSelectionSet* SelectionSet = GetMutableSelectionSet();
SelectionSet->OnPreChange().AddRaw(this, &FLevelEditorViewportClient::ResetElementsToManipulateFromSelectionChange);
UTypedElementRegistry* Registry = UTypedElementRegistry::GetInstance();
Registry->OnProcessingDeferredElementsToDestroy().AddRaw(this, &FLevelEditorViewportClient::ResetElementsToManipulateFromProcessingDeferredElementsToDestroy);
}
{
ViewportInteraction->GetDefaultInterfaceCustomization()->SetToolkitHost(ParentLevelEditor.Pin().Get());
}
{
TUniquePtr<FActorElementLevelEditorViewportInteractionCustomization> ActorCustomization = MakeUnique<FActorElementLevelEditorViewportInteractionCustomization>();
ActorCustomization->SetLevelEditorViewportClient(this);
ActorCustomization->SetToolkitHost(ParentLevelEditor.Pin().Get());
ViewportInteraction->RegisterInterfaceCustomizationByTypeName(NAME_Actor, MoveTemp(ActorCustomization));
}
{
TUniquePtr<FComponentElementLevelEditorViewportInteractionCustomization> ComponentCustomization = MakeUnique<FComponentElementLevelEditorViewportInteractionCustomization>();
ComponentCustomization->SetLevelEditorViewportClient(this);
ComponentCustomization->SetToolkitHost(ParentLevelEditor.Pin().Get());
ViewportInteraction->RegisterInterfaceCustomizationByTypeName(NAME_Components, MoveTemp(ComponentCustomization));
}
}
FSceneView* FLevelEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
bWasControlledByOtherViewport = false;
// set all other matching viewports to my location, if the LOD locking is enabled,
// unless another viewport already set me this frame (otherwise they fight)
if (GetDefault<ULevelEditorViewportSettings>()->bUseLODViewLocking)
{
for (FLevelEditorViewportClient* ViewportClient : GEditor->GetLevelViewportClients())
{
//only change camera for a viewport that is looking at the same scene
if (ViewportClient == NULL || GetScene() != ViewportClient->GetScene())
{
continue;
}
// go over all other level viewports
if (ViewportClient->Viewport && ViewportClient != this)
{
// force camera of same-typed viewports
if (ViewportClient->GetViewportType() == GetViewportType())
{
ViewportClient->SetViewLocation( GetViewLocation() );
ViewportClient->SetViewRotation( GetViewRotation() );
ViewportClient->SetOrthoZoom( GetOrthoZoom() );
// don't let this other viewport update itself in its own CalcSceneView
ViewportClient->bWasControlledByOtherViewport = true;
}
// when we are LOD locking, ortho views get their camera position from this view, so make sure it redraws
else if (IsPerspective() && !ViewportClient->IsPerspective())
{
// don't let this other viewport update itself in its own CalcSceneView
ViewportClient->bWasControlledByOtherViewport = true;
}
}
// if the above code determined that this viewport has changed, delay the update unless
// an update is already in the pipe
if (ViewportClient->bWasControlledByOtherViewport && ViewportClient->TimeForForceRedraw == 0.0)
{
ViewportClient->TimeForForceRedraw = FPlatformTime::Seconds() + 0.9 + FMath::FRand() * 0.2;
}
}
}
FSceneView* View = FEditorViewportClient::CalcSceneView(ViewFamily, StereoViewIndex);
View->ViewActor = ActorLocks.GetLock().GetLockedActor();
View->SpriteCategoryVisibility = SpriteCategoryVisibility;
View->bCameraCut = bEditorCameraCut;
View->bHasSelectedComponents = GEditor->GetSelectedComponentCount() > 0;
return View;
}
ELevelViewportType FLevelEditorViewportClient::GetViewportType() const
{
const UCameraComponent* ActiveCameraComponent = GetCameraComponentForView();
if (ActiveCameraComponent != NULL)
{
return (ActiveCameraComponent->ProjectionMode == ECameraProjectionMode::Perspective) ? LVT_Perspective : LVT_OrthoFreelook;
}
else
{
return FEditorViewportClient::GetViewportType();
}
}
void FLevelEditorViewportClient::SetViewportTypeFromTool( ELevelViewportType InViewportType )
{
SetViewportType(InViewportType);
}
void FLevelEditorViewportClient::SetViewportType( ELevelViewportType InViewportType )
{
if (InViewportType != LVT_Perspective)
{
SetActorLock(nullptr);
UpdateViewForLockedActor();
}
FEditorViewportClient::SetViewportType(InViewportType);
}
void FLevelEditorViewportClient::RotateViewportType()
{
SetActorLock(nullptr);
UpdateViewForLockedActor();
FEditorViewportClient::RotateViewportType();
}
void FLevelEditorViewportClient::OverridePostProcessSettings( FSceneView& View )
{
const UCameraComponent* CameraComponent = GetCameraComponentForView();
if (CameraComponent)
{
View.OverridePostProcessSettings(CameraComponent->PostProcessSettings, CameraComponent->PostProcessBlendWeight);
}
}
bool FLevelEditorViewportClient::ShouldLockPitch() const
{
// If we have somehow gotten out of the locked rotation
if ((GetViewRotation().Pitch < -90.f + KINDA_SMALL_NUMBER || GetViewRotation().Pitch > 90.f - KINDA_SMALL_NUMBER)
&& FMath::Abs(GetViewRotation().Roll) > (90.f - KINDA_SMALL_NUMBER))
{
return false;
}
// Else use the standard rules
return FEditorViewportClient::ShouldLockPitch();
}
void FLevelEditorViewportClient::BeginCameraMovement(bool bHasMovement)
{
const bool bIsUsingLegacyMovementNotify = GetDefault<ULevelEditorViewportSettings>()->bUseLegacyCameraMovementNotifications;
// If there's new movement broadcast it
if (bHasMovement)
{
if (!bIsCameraMoving)
{
AActor* ActorLock = GetActiveActorLock().Get();
if (!bIsCameraMovingOnTick && ActorLock)
{
GEditor->BroadcastBeginCameraMovement(*ActorLock);
// consider modification from piloting as relative location changes
FProperty* TransformProperty = FComponentElementLevelEditorViewportInteractionCustomization::GetEditTransformProperty(UE::Widget::WM_Translate);
if (TransformProperty)
{
// Create edit property event
FEditPropertyChain PropertyChain;
PropertyChain.AddHead(TransformProperty);
// Broadcast Pre Edit change notification, we can't call PreEditChange directly on Actor or ActorComponent from here since it will unregister the components until PostEditChange
FCoreUObjectDelegates::OnPreObjectPropertyChanged.Broadcast(ActorLock, PropertyChain);
}
}
if (!bIsUsingLegacyMovementNotify)
{
TrackingTransaction.Cancel();
}
bIsCameraMoving = true;
}
}
else if (bIsUsingLegacyMovementNotify || !bIsTracking)
{
bIsCameraMoving = false;
}
}
void FLevelEditorViewportClient::EndCameraMovement()
{
// If there was movement and it has now stopped, broadcast it
if (bIsCameraMovingOnTick && !bIsCameraMoving)
{
if (AActor* ActorLock = GetActiveActorLock().Get())
{
GEditor->BroadcastEndCameraMovement(*ActorLock);
// Create post edit property change event, consider modification from piloting as relative location changes
FProperty* TransformProperty = FComponentElementLevelEditorViewportInteractionCustomization::GetEditTransformProperty(UE::Widget::WM_Translate);
FPropertyChangedEvent PropertyChangedEvent(TransformProperty, EPropertyChangeType::ValueSet);
// Broadcast Post Edit change notification, we can't call PostEditChangeProperty directly on Actor or ActorComponent from here since it wasn't pair with a proper PreEditChange
FCoreUObjectDelegates::OnObjectPropertyChanged.Broadcast(ActorLock, PropertyChangedEvent);
}
}
}
void FLevelEditorViewportClient::PerspectiveCameraMoved()
{
// Update the locked actor (if any) from the camera
MoveLockedActorToCamera();
// If any other viewports have this actor locked too, we need to update them
if( GetActiveActorLock().IsValid() )
{
UpdateLockedActorViewports(GetActiveActorLock().Get(), false);
}
// Broadcast 'camera moved' delegate
FEditorDelegates::OnEditorCameraMoved.Broadcast(GetViewLocation(), GetViewRotation(), ViewportType, ViewIndex);
}
/**
* Reset the camera position and rotation. Used when creating a new level.
*/
void FLevelEditorViewportClient::ResetCamera()
{
// Initialize perspective view transform
ViewTransformPerspective.SetLocation(EditorViewportDefs::DefaultPerspectiveViewLocation);
ViewTransformPerspective.SetRotation(EditorViewportDefs::DefaultPerspectiveViewRotation);
ViewTransformPerspective.SetLookAt(FVector::ZeroVector);
FMatrix OrbitMatrix = ViewTransformPerspective.ComputeOrbitMatrix();
OrbitMatrix = OrbitMatrix.InverseFast();
ViewTransformPerspective.SetRotation(OrbitMatrix.Rotator());
ViewTransformPerspective.SetLocation(OrbitMatrix.GetOrigin());
ViewTransformPerspective.SetOrthoZoom(DEFAULT_ORTHOZOOM);
// Initialize orthographic view transform
ViewTransformOrthographic.SetLocation(FVector::ZeroVector);
ViewTransformOrthographic.SetRotation(FRotator::ZeroRotator);
ViewTransformOrthographic.SetOrthoZoom(DEFAULT_ORTHOZOOM);
ViewFOV = FOVAngle;
SetIsCameraCut();
// Broadcast 'camera moved' delegate
FEditorDelegates::OnEditorCameraMoved.Broadcast(GetViewLocation(), GetViewRotation(), ViewportType, ViewIndex);
}
void FLevelEditorViewportClient::ResetViewForNewMap()
{
ResetCamera();
bForcingUnlitForNewMap = true;
}
void FLevelEditorViewportClient::PrepareCameraForPIE()
{
LastEditorViewLocation = GetViewLocation();
LastEditorViewRotation = GetViewRotation();
}
void FLevelEditorViewportClient::RestoreCameraFromPIE()
{
const bool bRestoreEditorCamera = GEditor != NULL && !GetDefault<ULevelEditorViewportSettings>()->bEnableViewportCameraToUpdateFromPIV;
//restore the camera position if this is an ortho viewport OR if PIV camera dropping is undesired
if ( IsOrtho() || bRestoreEditorCamera )
{
SetViewLocation( LastEditorViewLocation );
SetViewRotation( LastEditorViewRotation );
}
if( IsPerspective() )
{
ViewFOV = FOVAngle;
RemoveCameraRoll();
}
}
void FLevelEditorViewportClient::ReceivedFocus(FViewport* InViewport)
{
if (!bReceivedFocusRecently)
{
bReceivedFocusRecently = true;
// A few frames can pass between receiving focus and processing a click, so we use a timer to track whether we have recently received focus.
FTimerDelegate ResetFocusReceivedTimer;
ResetFocusReceivedTimer.BindLambda([&] ()
{
bReceivedFocusRecently = false;
FocusTimerHandle.Invalidate(); // The timer will only execute once, so we can invalidate now.
});
GEditor->GetTimerManager()->SetTimer(FocusTimerHandle, ResetFocusReceivedTimer, 0.1f, false);
}
FEditorViewportClient::ReceivedFocus(InViewport);
}
void FLevelEditorViewportClient::LostFocus(FViewport* InViewport)
{
FEditorViewportClient::LostFocus(InViewport);
GEditor->SetPreviewMeshMode(false);
}
//
// FLevelEditorViewportClient::ProcessClick
//
void FLevelEditorViewportClient::ProcessClick(FSceneView& View, HHitProxy* HitProxy, FKey Key, EInputEvent Event, uint32 HitX, uint32 HitY)
{
UBrushEditingSubsystem* BrushSubsystem = GEditor->GetEditorSubsystem<UBrushEditingSubsystem>();
// We may have started gizmo manipulation if hot-keys were pressed when we started this click
// If so, we need to end that now before we potentially update the selection below,
// otherwise the usual call in TrackingStopped would include the newly selected element
if (bHasBegunGizmoManipulation)
{
FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
ViewportInteraction->EndGizmoManipulation(ElementsToManipulate, GetWidgetMode(), ETypedElementViewportInteractionGizmoManipulationType::Click);
bHasBegunGizmoManipulation = false;
}
const FViewportClick Click(&View,this,Key,Event,HitX,HitY);
if (Click.GetKey() == EKeys::MiddleMouseButton && !Click.IsAltDown() && !Click.IsShiftDown())
{
LevelViewportClickHandlers::ClickViewport(this, Click);
return;
}
if (!ModeTools->HandleClick(this, HitProxy,Click))
{
const FTypedElementHandle HitElement = HitProxy ? HitProxy->GetElementHandle() : FTypedElementHandle();
if (HitProxy == NULL)
{
LevelViewportClickHandlers::ClickBackdrop(this,Click);
}
else if (HitProxy->IsA(HWidgetAxis::StaticGetType()))
{
// The user clicked on an axis translation/rotation hit proxy. However, we want
// to find out what's underneath the axis widget. To do this, we'll need to render
// the viewport's hit proxies again, this time *without* the axis widgets!
// OK, we need to be a bit evil right here. Basically we want to hijack the ShowFlags
// for the scene so we can re-render the hit proxies without any axis widgets. We'll
// store the original ShowFlags and modify them appropriately
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();
}
else if (GUnrealEd->ComponentVisManager.HandleClick(this, HitProxy, Click))
{
// Component vis manager handled the click
}
else if (HitElement && LevelViewportClickHandlers::ClickElement(this, HitElement, Click))
{
// Element handled the click
}
else if (HitProxy->IsA(HActor::StaticGetType()))
{
HActor* ActorHitProxy = (HActor*)HitProxy;
AActor* ConsideredActor = ActorHitProxy->Actor;
if (ConsideredActor) // It is possible to be clicking something during level transition if you spam click, and it might not be valid by this point
{
while (ConsideredActor->IsChildActor())
{
ConsideredActor = ConsideredActor->GetParentActor();
}
// We want to process the click on the component only if:
// 1. The actor clicked is already selected
// 2. The actor selected is the only actor selected
// 3. The actor selected is blueprintable
// 4. No components are already selected and the click was a double click
// 5. OR, a component is already selected and the click was NOT a double click
const bool bActorAlreadySelectedExclusively = GEditor->GetSelectedActors()->IsSelected(ConsideredActor) && (GEditor->GetSelectedActorCount() == 1);
const bool bActorIsBlueprintable = FKismetEditorUtilities::CanCreateBlueprintOfClass(ConsideredActor->GetClass());
const bool bComponentAlreadySelected = GEditor->GetSelectedComponentCount() > 0;
const bool bWasDoubleClick = (Click.GetEvent() == IE_DoubleClick);
const bool bSelectComponent = bActorAlreadySelectedExclusively && bActorIsBlueprintable && (bComponentAlreadySelected != bWasDoubleClick);
bool bComponentSelected = false;
if (bSelectComponent)
{
bComponentSelected = LevelViewportClickHandlers::ClickComponent(this, ActorHitProxy, Click);
}
if (!bComponentSelected)
{
LevelViewportClickHandlers::ClickActor(this, ConsideredActor, Click, true);
}
// We clicked an actor, allow the pivot to reposition itself.
// GUnrealEd->SetPivotMovedIndependently(false);
}
}
else if (HitProxy->IsA(HInstancedStaticMeshInstance::StaticGetType()))
{
LevelViewportClickHandlers::ClickActor(this, ((HInstancedStaticMeshInstance*)HitProxy)->Component->GetOwner(), Click, true);
}
else if (HitProxy->IsA(HBSPBrushVert::StaticGetType()) && ((HBSPBrushVert*)HitProxy)->Brush.IsValid())
{
FVector Vertex = FVector(*((HBSPBrushVert*)HitProxy)->Vertex);
LevelViewportClickHandlers::ClickBrushVertex(this,((HBSPBrushVert*)HitProxy)->Brush.Get(),&Vertex,Click);
}
else if (HitProxy->IsA(HStaticMeshVert::StaticGetType()))
{
LevelViewportClickHandlers::ClickStaticMeshVertex(this,((HStaticMeshVert*)HitProxy)->Actor,((HStaticMeshVert*)HitProxy)->Vertex,Click);
}
else if (BrushSubsystem && BrushSubsystem->ProcessClickOnBrushGeometry(this, HitProxy, Click))
{
// Handled by the brush subsystem
}
else if (HitProxy->IsA(HModel::StaticGetType()))
{
HModel* ModelHit = (HModel*)HitProxy;
// Compute the viewport's current view family.
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues( Viewport, GetScene(), EngineShowFlags ));
FSceneView* SceneView = CalcSceneView( &ViewFamily );
uint32 SurfaceIndex = INDEX_NONE;
if(ModelHit->ResolveSurface(SceneView,HitX,HitY,SurfaceIndex))
{
LevelViewportClickHandlers::ClickSurface(this,ModelHit->GetModel(),SurfaceIndex,Click);
}
}
else if (HitProxy->IsA(HLevelSocketProxy::StaticGetType()))
{
LevelViewportClickHandlers::ClickLevelSocket(this, HitProxy, Click);
}
}
}
// Frustum parameters for the perspective view.
static float GPerspFrustumAngle=90.f;
static float GPerspFrustumAspectRatio=1.77777f;
static float GPerspFrustumStartDist=GNearClippingPlane;
static float GPerspFrustumEndDist=UE_FLOAT_HUGE_DISTANCE;
static FMatrix GPerspViewMatrix;
void FLevelEditorViewportClient::Tick(float DeltaTime)
{
if (bWasEditorCameraCut && bEditorCameraCut)
{
bEditorCameraCut = false;
}
bWasEditorCameraCut = bEditorCameraCut;
bCurrentlyEditingThroughMovementWidget = false;
// Gives FindViewComponentForActor a chance to refresh once every Tick.
ViewComponentForActorCache.Reset();
FEditorViewportClient::Tick(DeltaTime);
// Update the preview mesh for the preview mesh mode.
GEditor->UpdatePreviewMesh();
// Copy perspective views to the global if this viewport has streaming volume previs enabled
if ( (IsPerspective() && GetDefault<ULevelEditorViewportSettings>()->bLevelStreamingVolumePrevis && Viewport->GetSizeXY().X > 0) )
{
GPerspFrustumAngle=ViewFOV;
GPerspFrustumAspectRatio=AspectRatio;
GPerspFrustumStartDist=GetNearClipPlane();
GPerspFrustumEndDist= UE_FLOAT_HUGE_DISTANCE;
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
Viewport,
GetScene(),
EngineShowFlags)
.SetRealtimeUpdate( IsRealtime() ) );
FSceneView* View = CalcSceneView(&ViewFamily);
GPerspViewMatrix = View->ViewMatrices.GetViewMatrix();
}
UpdateViewForLockedActor(DeltaTime);
UserIsControllingAtmosphericLightTimer = FMath::Max(UserIsControllingAtmosphericLightTimer - DeltaTime, 0.0f);
}
void FLevelEditorViewportClient::UpdateViewForLockedActor(float DeltaTime)
{
// We can't be locked to a cinematic actor if this viewport doesn't allow cinematic control
if (!bAllowCinematicControl && ActorLocks.CinematicActorLock.HasValidLockedActor())
{
ActorLocks.CinematicActorLock = FLevelViewportActorLock::None;
}
bUseControllingActorViewInfo = false;
ControllingActorViewInfo = FMinimalViewInfo();
ControllingActorAspectRatioAxisConstraint.Reset();
ControllingActorExtraPostProcessBlends.Empty();
ControllingActorExtraPostProcessBlendWeights.Empty();
const FLevelViewportActorLock& ActiveLock = ActorLocks.GetLock();
AActor* Actor = ActiveLock.GetLockedActor();
if( Actor != NULL )
{
// Check if the viewport is transitioning
FViewportCameraTransform& ViewTransform = GetViewTransform();
if (!ViewTransform.IsPlaying())
{
// Update transform
bool bGotCameraView = false;
if (bLockedCameraView)
{
// If this is a camera actor, then inherit some other settings
UActorComponent* const ViewComponent = FindViewComponentForActor(Actor);
if (ViewComponent != nullptr)
{
if ( ensure(ViewComponent->GetEditorPreviewInfo(DeltaTime, ControllingActorViewInfo)) )
{
bUseControllingActorViewInfo = true;
if (UCameraComponent* CameraComponent = Cast<UCameraComponent>(ViewComponent))
{
CameraComponent->GetExtraPostProcessBlends(ControllingActorExtraPostProcessBlends, ControllingActorExtraPostProcessBlendWeights);
}
// Axis constraint for aspect ratio
ControllingActorAspectRatioAxisConstraint = ActiveLock.AspectRatioAxisConstraint;
// Post processing is handled by OverridePostProcessingSettings
ViewFOV = ControllingActorViewInfo.FOV;
AspectRatio = ControllingActorViewInfo.AspectRatio;
SetViewLocation(ControllingActorViewInfo.Location);
SetViewRotation(ControllingActorViewInfo.Rotation);
bGotCameraView = true;
}
}
}
if (!bGotCameraView)
{
if (Actor->GetAttachParentActor() != NULL)
{
// Actor is parented, so use the actor to world matrix for translation and rotation information.
SetViewLocation(Actor->GetActorLocation());
SetViewRotation(Actor->GetActorRotation());
}
else if (Actor->GetRootComponent() != NULL)
{
// No attachment, so just use the relative location, so that we don't need to
// convert from a quaternion, which loses winding information.
SetViewLocation(Actor->GetRootComponent()->GetRelativeLocation());
SetViewRotation(Actor->GetRootComponent()->GetRelativeRotation());
}
}
const double DistanceToCurrentLookAt = FVector::Dist( GetViewLocation() , GetLookAtLocation() );
const FQuat CameraOrientation = FQuat::MakeFromEuler( GetViewRotation().Euler() );
FVector Direction = CameraOrientation.RotateVector( FVector(1,0,0) );
SetLookAtLocation( GetViewLocation() + Direction * DistanceToCurrentLookAt );
}
}
}
/*namespace ViewportDeadZoneConstants
{
enum
{
NO_DEAD_ZONE,
STANDARD_DEAD_ZONE
};
};*/
/** Trim the specified line to the planes of the frustum */
void TrimLineToFrustum(const FConvexVolume& Frustum, FVector& Start, FVector& End)
{
FVector Intersection;
for (const FPlane& Plane : Frustum.Planes)
{
if (FMath::SegmentPlaneIntersection(Start, End, Plane, Intersection))
{
// Chop the line inside the frustum
if ((static_cast<const FVector&>(Plane) | (Intersection - End)) > 0.0f)
{
Start = Intersection;
}
else
{
End = Intersection;
}
}
}
}
static void GetAttachedActorsRecursive(const AActor* InActor, TArray<AActor*>& OutActors)
{
TArray<AActor*> AttachedActors;
InActor->GetAttachedActors(AttachedActors);
for (AActor* AttachedActor : AttachedActors)
{
GetAttachedActorsRecursive(AttachedActor, OutActors);
}
OutActors.Append(AttachedActors);
};
void FLevelEditorViewportClient::ProjectActorsIntoWorld(const TArray<AActor*>& Actors, FViewport* InViewport, const FVector& Drag, const FRotator& Rot)
{
// Compile an array of selected actors
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
InViewport,
GetScene(),
EngineShowFlags)
.SetRealtimeUpdate( IsRealtime() ));
// SceneView is deleted with the ViewFamily
FSceneView* SceneView = CalcSceneView( &ViewFamily );
// Calculate the frustum so we can trim rays to it
const FConvexVolume Frustum;
GetViewFrustumBounds(const_cast<FConvexVolume&>(Frustum), SceneView->ViewMatrices.GetViewProjectionMatrix(), true);
const FMatrix InputCoordSystem = GetWidgetCoordSystem();
const EAxisList::Type CurrentAxis = GetCurrentWidgetAxis();
const FVector DeltaTranslation = (ModeTools->PivotLocation - ModeTools->CachedLocation) + Drag;
// Loop over all the actors and attempt to snap them along the drag axis normal
for (AActor* Actor : Actors)
{
// Use the Delta of the Mode tool with the actor pre drag location to avoid accumulating snapping offsets
FVector NewActorPosition;
if (const FTransform* PreDragTransform = PreDragActorTransforms.Find(Actor))
{
NewActorPosition = PreDragTransform->GetLocation() + DeltaTranslation;
}
else
{
const FTransform& ActorTransform = PreDragActorTransforms.Add(Actor, Actor->GetTransform());
NewActorPosition = ActorTransform.GetLocation() + DeltaTranslation;
}
FViewportCursorLocation Cursor(SceneView, this, 0, 0);
FActorPositionTraceResult TraceResult;
bool bSnapped = false;
bool bIsOnScreen = false;
{
// We only snap things that are on screen
FVector2D ScreenPos;
FIntPoint ViewportSize = InViewport->GetSizeXY();
if (SceneView->WorldToPixel(NewActorPosition, ScreenPos) && FMath::IsWithin(ScreenPos.X, 0, ViewportSize.X) && FMath::IsWithin(ScreenPos.Y, 0, ViewportSize.Y))
{
bIsOnScreen = true;
Cursor = FViewportCursorLocation(SceneView, this, static_cast<int32>(ScreenPos.X), static_cast<int32>(ScreenPos.Y));
}
}
if (bIsOnScreen)
{
TArray<AActor*> IgnoreActors;
IgnoreActors.Append(Actors); // Add the whole list of actors so you can't hit the moving set with the ray
GetAttachedActorsRecursive(Actor, IgnoreActors);
// Determine how we're going to attempt to project the object onto the world
if (CurrentAxis == EAxisList::XY || CurrentAxis == EAxisList::XZ || CurrentAxis == EAxisList::YZ)
{
// Snap along the perpendicular axis
const FVector PlaneNormal = CurrentAxis == EAxisList::XY ? FVector(0, 0, 1) : CurrentAxis == EAxisList::XZ ? FVector(0, 1, 0) : FVector(1, 0, 0);
FVector TraceDirection = InputCoordSystem.TransformVector(PlaneNormal);
// Make sure the trace normal points along the view direction
if (FVector::DotProduct(SceneView->GetViewDirection(), TraceDirection) < 0.f)
{
TraceDirection = -TraceDirection;
}
FVector RayStart = NewActorPosition - (TraceDirection * HALF_WORLD_MAX/2);
FVector RayEnd = NewActorPosition + (TraceDirection * HALF_WORLD_MAX/2);
TrimLineToFrustum(Frustum, RayStart, RayEnd);
TraceResult = FActorPositioning::TraceWorldForPosition(*GetWorld(), *SceneView, RayStart, RayEnd, &IgnoreActors);
}
else
{
TraceResult = FActorPositioning::TraceWorldForPosition(Cursor, *SceneView, &IgnoreActors);
}
}
if (TraceResult.State == FActorPositionTraceResult::HitSuccess)
{
// Move the actor to the position of the trace hit using the spawn offset rules
// We only do this if we found a valid hit (we don't want to move the actor in front of the camera by default)
const UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(Actor->GetClass());
const FTransform* PreDragActorTransform = PreDragActorTransforms.Find(Actor);
check(PreDragActorTransform);
// Compute the surface aligned transform. Note we do not use the snapped version here as our DragDelta is already snapped
const FPositioningData PositioningData = FPositioningData(TraceResult.Location, TraceResult.SurfaceNormal)
.UseStartTransform(*PreDragActorTransform)
.UsePlacementExtent(Actor->GetPlacementExtent())
.UseFactory(Factory);
FTransform ActorTransform = FActorPositioning::GetSurfaceAlignedTransform(PositioningData);
ActorTransform.SetScale3D(Actor->GetActorScale3D());
if (USceneComponent* RootComponent = Actor->GetRootComponent())
{
RootComponent->SetWorldTransform(ActorTransform);
}
}
else
{
// Didn't find a valid surface snapping candidate, just apply the deltas directly
ApplyDeltaToActor(Actor, NewActorPosition - Actor->GetActorLocation(), Rot, FVector(0.f, 0.f, 0.f));
}
}
}
bool FLevelEditorViewportClient::InputWidgetDelta(FViewport* InViewport, EAxisList::Type InCurrentAxis, FVector& Drag, FRotator& Rot, FVector& Scale)
{
if (GUnrealEd->ComponentVisManager.HandleInputDelta(this, InViewport, Drag, Rot, Scale))
{
return true;
}
bool bHandled = false;
// Give the current editor mode a chance to use the input first. If it does, don't apply it to anything else.
if (FEditorViewportClient::InputWidgetDelta(InViewport, InCurrentAxis, Drag, Rot, Scale))
{
bHandled = true;
}
else
{
//@TODO: MODETOOLS: Much of this needs to get pushed to Super, but not all of it can be...
if( InCurrentAxis != EAxisList::None )
{
// Skip actors transformation routine in case if any of the selected actors locked
// but still pretend that we have handled the input
if (!GEditor->HasLockedActors())
{
const bool LeftMouseButtonDown = InViewport->KeyState(EKeys::LeftMouseButton);
const bool RightMouseButtonDown = InViewport->KeyState(EKeys::RightMouseButton);
const bool MiddleMouseButtonDown = InViewport->KeyState(EKeys::MiddleMouseButton);
// Some functionality can be skipped if new gizmos and ITF drag tools are active
const bool bUseLegacyGizmoDragTools = !(UEditorInteractiveGizmoManager::UsesNewTRSGizmos() && UE::Editor::DragTools::UseEditorDragTools());
// If duplicate dragging . . .
if (IsAltPressed() && (LeftMouseButtonDown || RightMouseButtonDown) && bUseLegacyGizmoDragTools)
{
// The widget has been offset, so check if we should duplicate the selection.
if ( bDuplicateOnNextDrag )
{
// Only duplicate if we're translating or rotating.
if ( !Drag.IsNearlyZero() || !Rot.IsZero() )
{
bDuplicateOnNextDrag = false;
TSharedPtr<ILevelEditor> LevelEditor = ParentLevelEditor.Pin();
UTypedElementCommonActions* CommonActions = LevelEditor ? LevelEditor->GetCommonActions() : nullptr;
if (CommonActions)
{
FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
TArray<FTypedElementHandle> DuplicatedElements;
{
// Do not used the cached manipulation list here here, as it will have removed attachments, and we do want to duplicate those
DuplicatedElements = CommonActions->DuplicateSelectedElements(GetSelectionSet(), GetWorld(), FVector::ZeroVector);
}
// Exclusively select the new elements, so that future gizmo interaction manipulates those items instead
if (DuplicatedElements.Num() > 0)
{
// We need to end the gizmo manipulation on the current selection, as they won't be moved again this drag...
if (bHasBegunGizmoManipulation)
{
ViewportInteraction->EndGizmoManipulation(ElementsToManipulate, GetWidgetMode(), ETypedElementViewportInteractionGizmoManipulationType::Click);
// Note: We don't reset bHasBegunGizmoManipulation here, as we test it again below to begin manipulating the duplicated selection
}
UTypedElementSelectionSet* SelectionSet = GetMutableSelectionSet();
const FTypedElementSelectionOptions SelectionOptions = FTypedElementSelectionOptions()
.SetAllowLegacyNotifications(false); // Old drag duplicate code didn't used to notify about this selection change
SelectionSet->SetSelection(DuplicatedElements, SelectionOptions);
// Selected actors are the same as the selection set at this point, send the legacy mode tools update
{
TArray<AActor*> SelectedActors = SelectionSet->GetSelectedObjects<AActor>();
constexpr bool bDidOffsetDuplicate = false;
ModeTools->ActorsDuplicatedNotify(SelectedActors, SelectedActors, bDidOffsetDuplicate);
}
// Force the cached manipulation list to update, as we don't notify about the change above
ResetElementsToManipulate();
// We need to start gizmo manipulation on the duplicated selection, as they may continue to move as part of this drag duplicate
if (bHasBegunGizmoManipulation)
{
ElementsToManipulate = GetElementsToManipulate();
ViewportInteraction->BeginGizmoManipulation(ElementsToManipulate, GetWidgetMode());
}
}
RedrawAllViewportsIntoThisScene();
}
}
}
}
// We do not want actors updated if we are holding down the middle mouse button.
// enable MMB for New TRS Gizmos
const bool bEnableMMB = UEditorInteractiveGizmoManager::UsesNewTRSGizmos() ? !bDraggingByHandle : false;
if(!MiddleMouseButtonDown || bEnableMMB)
{
bool bSnapped = FSnappingUtils::SnapActorsToNearestActor( Drag, this );
bSnapped = bSnapped || FSnappingUtils::SnapDraggedActorsToNearestVertex( Drag, this );
// If we are only changing position, project the actors onto the world
const bool bOnlyTranslation = !Drag.IsZero() && Rot.IsZero() && Scale.IsZero();
const EAxisList::Type CurrentAxis = GetCurrentWidgetAxis();
const bool bSingleAxisDrag = CurrentAxis == EAxisList::X || CurrentAxis == EAxisList::Y || CurrentAxis == EAxisList::Z;
if (!bSnapped && !bSingleAxisDrag && GetDefault<ULevelEditorViewportSettings>()->SnapToSurface.bEnabled && bOnlyTranslation)
{
TArray<AActor*> SelectedActors;
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
if (AActor* Actor = Cast<AActor>(*It))
{
SelectedActors.Add(Actor);
}
}
ProjectActorsIntoWorld(SelectedActors, InViewport, Drag, Rot);
}
else
{
ApplyDeltaToSelectedElements(FTransform(Rot, Drag, Scale));
}
ApplyDeltaToRotateWidget( Rot );
bCurrentlyEditingThroughMovementWidget = true;
}
else
{
FSnappingUtils::SnapDragLocationToNearestVertex( ModeTools->PivotLocation, Drag, this, true );
GUnrealEd->SetPivotMovedIndependently(true);
bOnlyMovedPivot = true;
}
ModeTools->PivotLocation += Drag;
ModeTools->SnappedLocation += Drag;
if (IsShiftPressed() && !UEditorInteractiveGizmoManager::UsesNewTRSGizmos())
{
bApplyCameraSpeedScaleByDistance = false;
FVector CameraDelta( Drag );
MoveViewportCamera( CameraDelta, FRotator::ZeroRotator );
bApplyCameraSpeedScaleByDistance = true;
}
ModeTools->UpdateInternalData();
}
bHandled = true;
}
}
return bHandled;
}
bool FLevelEditorViewportClient::ShouldScaleCameraSpeedByDistance() const
{
return bApplyCameraSpeedScaleByDistance && FEditorViewportClient::ShouldScaleCameraSpeedByDistance();
}
TSharedPtr<FDragTool> FLevelEditorViewportClient::MakeDragTool( EDragTool::Type DragToolType )
{
// Let the drag tool handle the transaction
TrackingTransaction.Cancel();
TSharedPtr<FDragTool> DragTool;
switch( DragToolType )
{
case EDragTool::BoxSelect:
DragTool = MakeShareable( new FDragTool_ActorBoxSelect(this) );
break;
case EDragTool::FrustumSelect:
DragTool = MakeShareable( new FDragTool_ActorFrustumSelect(this) );
break;
case EDragTool::Measure:
DragTool = MakeShareable( new FDragTool_Measure(this) );
break;
case EDragTool::ViewportChange:
DragTool = MakeShareable( new FDragTool_ViewportChange(this) );
break;
};
return DragTool;
}
static const FLevelViewportCommands& GetLevelViewportCommands()
{
static FName LevelEditorName("LevelEditor");
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>( LevelEditorName );
return LevelEditor.GetLevelViewportCommands();
}
void FLevelEditorViewportClient::SetCurrentViewport()
{
// Set the current level editing viewport client to the dropped-in viewport client
if (GCurrentLevelEditingViewportClient != this)
{
// Invalidate the old vp client to remove its special selection box
if (GCurrentLevelEditingViewportClient)
{
GCurrentLevelEditingViewportClient->Invalidate();
}
GCurrentLevelEditingViewportClient = this;
}
Invalidate();
}
void FLevelEditorViewportClient::SetLastKeyViewport()
{
// Store a reference to the last viewport that received a key press.
GLastKeyLevelEditingViewportClient = this;
if (GCurrentLevelEditingViewportClient != this)
{
if (GCurrentLevelEditingViewportClient)
{
//redraw without yellow selection box
GCurrentLevelEditingViewportClient->Invalidate();
}
//cause this viewport to redraw WITH yellow selection box
Invalidate();
GCurrentLevelEditingViewportClient = this;
}
}
bool FLevelEditorViewportClient::InputKey(const FInputKeyEventArgs& InEventArgs)
{
if (bDisableInput)
{
return true;
}
const int32 HitX = InEventArgs.Viewport->GetMouseX();
const int32 HitY = InEventArgs.Viewport->GetMouseY();
FInputEventState InputState( InEventArgs.Viewport, InEventArgs.Key, InEventArgs.Event );
SetLastKeyViewport();
// Compute a view.
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
InEventArgs.Viewport,
GetScene(),
EngineShowFlags )
.SetRealtimeUpdate( IsRealtime() ) );
FSceneView* View = CalcSceneView( &ViewFamily );
// Compute the click location.
if ( InputState.IsMouseButtonEvent() && InputState.IsAnyMouseButtonDown() )
{
const FViewportCursorLocation Cursor(View, this, HitX, HitY);
const FActorPositionTraceResult TraceResult = FActorPositioning::TraceWorldForPositionWithDefault(Cursor, *View);
GEditor->UnsnappedClickLocation = TraceResult.Location;
GEditor->ClickLocation = TraceResult.Location;
GEditor->ClickPlane = FPlane(TraceResult.Location, TraceResult.SurfaceNormal);
// Snap the new location if snapping is enabled
FSnappingUtils::SnapPointToGrid(GEditor->ClickLocation, FVector::ZeroVector);
}
if (GUnrealEd->ComponentVisManager.HandleInputKey(this, InEventArgs.Viewport, InEventArgs.Key, InEventArgs.Event))
{
return true;
}
UWorld* ViewportWorld = GetWorld();
auto ProcessAtmosphericLightShortcut = [&](const uint8 LightIndex, bool& bCurrentUserControl)
{
UDirectionalLightComponent* SelectedSunLight = GetAtmosphericLight(LightIndex, ViewportWorld);
if (SelectedSunLight)
{
if (!bCurrentUserControl)
{
FText TrackingDescription = FText::Format(LOCTEXT("RotatationShortcut", "Rotate Atmosphere Light {0}"), LightIndex);
TrackingTransaction.Begin(TrackingDescription, SelectedSunLight->GetOwner());
AddRealtimeOverride(true, LOCTEXT("RealtimeOverrideMessage_AtmospherelLight", "Atmosphere Light Control"));// The first time, save that setting for RestoreRealtime
}
bCurrentUserControl = true;
UserIsControllingAtmosphericLightTimer = 3.0f; // Keep the widget open for a few seconds even when not tweaking the sun light
}
};
bool bCmdCtrlLPressed = (InputState.IsCommandButtonPressed() || InputState.IsCtrlButtonPressed()) && InEventArgs.Key == EKeys::L;
if (bCmdCtrlLPressed && InputState.IsShiftButtonPressed())
{
ProcessAtmosphericLightShortcut(1, bUserIsControllingAtmosphericLight1);
return true;
}
if (bCmdCtrlLPressed)
{
ProcessAtmosphericLightShortcut(0, bUserIsControllingAtmosphericLight0);
return true;
}
if (bUserIsControllingAtmosphericLight0 || bUserIsControllingAtmosphericLight1)
{
UDirectionalLightComponent* SelectedSunLight = GetAtmosphericLight(bUserIsControllingAtmosphericLight0 ? 0 : 1, ViewportWorld);
if (SelectedSunLight)
{
NotifyAtmosphericLightHasMoved(*SelectedSunLight, true);
}
TrackingTransaction.End(); // End undo/redo translation
RemoveRealtimeOverride(LOCTEXT("RealtimeOverrideMessage_AtmospherelLight", "Atmosphere Light Control")); // Restore previous real-time state
}
bUserIsControllingAtmosphericLight0 = false; // Disable all atmospheric light controls
bUserIsControllingAtmosphericLight1 = false;
UEditorWorldExtensionCollection& EditorWorldExtensionCollection = *GEditor->GetEditorWorldExtensionsManager()->GetEditorWorldExtensions(GetWorld());
if (EditorWorldExtensionCollection.InputKey(this, Viewport, InEventArgs.Key, InEventArgs.Event))
{
return true;
}
bool bHandled = FEditorViewportClient::InputKey(InEventArgs);
// Handle input for the height preview mode.
bool bEnablePreviewMeshChordPressed = IsCommandChordPressed(GetLevelViewportCommands().EnablePreviewMesh);
bool bCyclePreviewMeshChordPressed = IsCommandChordPressed(GetLevelViewportCommands().CyclePreviewMesh);
if (!InputState.IsMouseButtonEvent() && (bEnablePreviewMeshChordPressed || bCyclePreviewMeshChordPressed))
{
GEditor->SetPreviewMeshMode(true);
if (bCyclePreviewMeshChordPressed && (InEventArgs.Event == IE_Pressed))
{
GEditor->CyclePreviewMesh();
}
bHandled = true;
}
else
{
GEditor->SetPreviewMeshMode(false);
}
const bool bAltIsBeingPressed = (InEventArgs.Event == IE_Pressed) && (InEventArgs.Key == EKeys::LeftAlt || InEventArgs.Key == EKeys::RightAlt);
// Clear Duplicate Actors mode when mouse buttons are released
if ( !InputState.IsAnyMouseButtonDown() )
{
bDuplicateActorsInProgress = false;
}
// Stamp a duplicate at the current position during a drag when ALT is pressed and we're dragging with a transaction open, and there is no component visualizer
else if ( bAltIsBeingPressed && bDraggingByHandle && TrackingTransaction.IsActive() && GUnrealEd->ComponentVisManager.GetActiveComponentVis() == nullptr )
{
TSharedPtr<ILevelEditor> LevelEditor = ParentLevelEditor.Pin();
UTypedElementCommonActions* CommonActions = LevelEditor ? LevelEditor->GetCommonActions() : nullptr;
if (CommonActions)
{
FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
TArray<FTypedElementHandle> DuplicatedElements;
{
// Do not used the cached manipulation list here here, as it will have removed attachments, and we do want to duplicate those
DuplicatedElements = CommonActions->DuplicateSelectedElements(GetSelectionSet(), GetWorld(), FVector::ZeroVector);
}
// Do not select the duplicate actors so that the drag operation can continue freely
if (DuplicatedElements.Num() > 0)
{
// Although we did not change the selection, we still need to notify legacy mode tools of the duplication
TArray<AActor*> SelectedActors = GetSelectionSet()->GetSelectedObjects<AActor>();
constexpr bool bDidOffsetDuplicate = false;
ModeTools->ActorsDuplicatedNotify(SelectedActors, SelectedActors, bDidOffsetDuplicate);
}
RedrawAllViewportsIntoThisScene();
}
}
return bHandled;
}
void SetActorBeingMovedByEditor(AActor* Actor, bool bIsBeingMoved)
{
TInlineComponentArray<UPrimitiveComponent*> Components;
Actor->GetComponents(Components);
for (UPrimitiveComponent* PrimitiveComponent : Components)
{
PrimitiveComponent->SetIsBeingMovedByEditor(bIsBeingMoved);
}
}
void FLevelEditorViewportClient::TrackingStarted( const FInputEventState& InInputState, bool bIsDraggingWidget, bool bNudge )
{
// Begin transacting. Give the current editor mode an opportunity to do the transacting.
const bool bTrackingHandledExternally = ModeTools->StartTracking(this, Viewport);
TrackingTransaction.End();
// Re-initialize new tracking only if a new button was pressed, otherwise we continue the previous one.
if ( InInputState.GetInputEvent() == IE_Pressed )
{
EInputEvent Event = InInputState.GetInputEvent();
FKey Key = InInputState.GetKey();
if ( InInputState.IsAltButtonPressed() && bDraggingByHandle )
{
if(Event == IE_Pressed && (Key == EKeys::LeftMouseButton || Key == EKeys::RightMouseButton) && !bDuplicateActorsInProgress)
{
// Set the flag so that the actors actors will be duplicated as soon as the widget is displaced.
bDuplicateOnNextDrag = true;
bDuplicateActorsInProgress = true;
}
}
else
{
bDuplicateOnNextDrag = false;
}
}
bOnlyMovedPivot = false;
bNeedToRestoreComponentBeingMovedFlag = false;
bHasBegunGizmoManipulation = false;
PreDragActorTransforms.Empty();
PreDragElementTransforms.Empty();
// Track BSP changes
{
bIsTrackingBrushModification = false;
FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
TypedElementListObjectUtil::ForEachObject<ABrush>(ElementsToManipulate, [this](ABrush* InBrush)
{
bIsTrackingBrushModification = InBrush && InBrush->CanEverAffectBSP();
return !bIsTrackingBrushModification;
});
}
if (bIsDraggingWidget)
{
Widget->SetSnapEnabled(true);
FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
ViewportInteraction->BeginGizmoManipulation(ElementsToManipulate, GetWidgetMode());
bHasBegunGizmoManipulation = true;
if (!bDuplicateActorsInProgress)
{
bNeedToRestoreComponentBeingMovedFlag = true;
TypedElementListObjectUtil::ForEachObject<AActor>(ElementsToManipulate, [this](AActor* InActor)
{
SetActorBeingMovedByEditor(InActor, true);
return true;
});
}
}
// Start a transformation transaction if required
if( !bTrackingHandledExternally )
{
if( bIsDraggingWidget )
{
TrackingTransaction.TransCount++;
FText TrackingDescription;
switch( GetWidgetMode() )
{
case UE::Widget::WM_Translate:
TrackingDescription = LOCTEXT("MoveTransaction", "Move Elements");
break;
case UE::Widget::WM_Rotate:
TrackingDescription = LOCTEXT("RotateTransaction", "Rotate Elements");
break;
case UE::Widget::WM_Scale:
TrackingDescription = LOCTEXT("ScaleTransaction", "Scale Elements");
break;
case UE::Widget::WM_TranslateRotateZ:
TrackingDescription = LOCTEXT("TranslateRotateZTransaction", "Translate/RotateZ Elements");
break;
case UE::Widget::WM_2D:
TrackingDescription = LOCTEXT("TranslateRotate2D", "Translate/Rotate2D Elements");
break;
default:
if( bNudge )
{
TrackingDescription = LOCTEXT("NudgeTransaction", "Nudge Elements");
}
}
if(!TrackingDescription.IsEmpty())
{
if(bNudge)
{
TrackingTransaction.Begin(TrackingDescription);
}
else
{
// If this hasn't begun due to a nudge, start it as a pending transaction so that it only really begins when the mouse is moved
TrackingTransaction.BeginPending(TrackingDescription);
}
}
}
else
{
AActor* ActiveActorLock = GetActiveActorLock().Get();
if (ActiveActorLock && !ActiveActorLock->IsLockLocation() && TrackingTransaction.TransCount == 0 && !CachedPilotTransform.IsSet())
{
// Cache Pilot Actor transform before piloting ends
CachedPilotTransform = ActiveActorLock->GetTransform();
}
}
if (TrackingTransaction.IsActive() || TrackingTransaction.IsPending())
{
// Suspend actor/component modification during each delta step to avoid recording unnecessary overhead into the transaction buffer
GEditor->DisableDeltaModification(true);
}
GUnrealEd->ComponentVisManager.TrackingStarted(this);
}
}
void FLevelEditorViewportClient::TrackingStopped()
{
// Only disable the duplicate on next drag flag if we actually dragged the mouse.
bDuplicateOnNextDrag = false;
// here we check to see if anything of worth actually changed when ending our MouseMovement
// If the TransCount > 0 (we changed something of value) so we need to call PostEditMove() on stuff
// if we didn't change anything then don't call PostEditMove()
bool bDidAnythingActuallyChange = false;
// Stop transacting. Give the current editor mode an opportunity to do the transacting.
const bool bTransactingHandledByEditorMode = ModeTools->EndTracking(this, Viewport);
if( !bTransactingHandledByEditorMode )
{
if( TrackingTransaction.TransCount > 0 )
{
bDidAnythingActuallyChange = true;
TrackingTransaction.TransCount--;
}
}
// Finish tracking a brush transform and update the Bsp
if (bIsTrackingBrushModification)
{
bDidAnythingActuallyChange = HaveSelectedObjectsBeenChanged() && !bOnlyMovedPivot;
bIsTrackingBrushModification = false;
if ( bDidAnythingActuallyChange && bWidgetAxisControlledByDrag )
{
GEditor->RebuildAlteredBSP();
}
}
// Notify the selected actors that they have been moved.
// Don't do this if AddDelta was never called.
{
const bool bDidMove = bDidAnythingActuallyChange && MouseDeltaTracker->HasReceivedDelta();
if (bHasBegunGizmoManipulation)
{
FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
ViewportInteraction->EndGizmoManipulation(ElementsToManipulate, GetWidgetMode(), bDidMove ? ETypedElementViewportInteractionGizmoManipulationType::Drag : ETypedElementViewportInteractionGizmoManipulationType::Click);
bHasBegunGizmoManipulation = false;
}
if (bDidMove && !GUnrealEd->IsPivotMovedIndependently())
{
GUnrealEd->UpdatePivotLocationForSelection();
}
GUnrealEd->ComponentVisManager.TrackingStopped(this, bDidMove);
}
if (bNeedToRestoreComponentBeingMovedFlag)
{
FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
TypedElementListObjectUtil::ForEachObject<AActor>(ElementsToManipulate, [this](AActor* InActor)
{
SetActorBeingMovedByEditor(InActor, false);
return true;
});
bNeedToRestoreComponentBeingMovedFlag = false;
}
// End the transaction here if one was started in StartTransaction()
if( TrackingTransaction.IsActive() || TrackingTransaction.IsPending() )
{
if( !HaveSelectedObjectsBeenChanged())
{
TrackingTransaction.Cancel();
}
else
{
TrackingTransaction.End();
}
// Restore actor/component delta modification
GEditor->DisableDeltaModification(false);
}
ModeTools->ActorMoveNotify();
if( bDidAnythingActuallyChange )
{
FScopedLevelDirtied LevelDirtyCallback;
LevelDirtyCallback.Request();
RedrawAllViewportsIntoThisScene();
}
PreDragActorTransforms.Empty();
PreDragElementTransforms.Empty();
}
void FLevelEditorViewportClient::AbortTracking()
{
if (TrackingTransaction.IsActive())
{
// Applying the global undo here will reset the drag operation
if (GUndo)
{
GUndo->Apply();
}
TrackingTransaction.Cancel();
StopTracking();
}
}
void FLevelEditorViewportClient::HandleViewportSettingChanged(FName PropertyName)
{
if (PropertyName == GET_MEMBER_NAME_CHECKED(ULevelEditorViewportSettings, bUseSelectionOutline))
{
EngineShowFlags.SetSelectionOutline(GetDefault<ULevelEditorViewportSettings>()->bUseSelectionOutline);
}
}
void FLevelEditorViewportClient::OnMapChanged(UWorld* InWorld, EMapChangeType MapChangeType)
{
if (InWorld != GetWorld())
{
return;
}
bDisableInput = (MapChangeType == EMapChangeType::TearDownWorld);
}
void FLevelEditorViewportClient::OnActorMoved(AActor* InActor)
{
// Update the cameras from their locked actor (if any)
UpdateLockedActorViewport(InActor, false);
}
void FLevelEditorViewportClient::NudgeSelectedObjects( const struct FInputEventState& InputState )
{
FViewport* InViewport = InputState.GetViewport();
EInputEvent Event = InputState.GetInputEvent();
FKey Key = InputState.GetKey();
const int32 MouseX = InViewport->GetMouseX();
const int32 MouseY = InViewport->GetMouseY();
if( Event == IE_Pressed || Event == IE_Repeat )
{
// If this is a pressed event, start tracking.
if ( !bIsTracking && Event == IE_Pressed )
{
// without the check for !bIsTracking, the following code would cause a new transaction to be created
// for each "nudge" that occurred while the key was held down. Disabling this code prevents the transaction
// from being constantly recreated while as long as the key is held, so that the entire move is considered an atomic action (and
// doing undo reverts the entire movement, as opposed to just the last nudge that occurred while the key was held down)
MouseDeltaTracker->StartTracking( this, MouseX, MouseY, InputState, true );
bIsTracking = true;
}
FIntPoint StartMousePos;
InViewport->GetMousePos( StartMousePos );
FKey VirtualKey = EKeys::MouseX;
EAxisList::Type VirtualAxis = GetHorizAxis();
float VirtualDelta = GEditor->GetGridSize() * (Key == EKeys::Left?-1:1);
if( Key == EKeys::Up || Key == EKeys::Down )
{
VirtualKey = EKeys::MouseY;
VirtualAxis = GetVertAxis();
VirtualDelta = GEditor->GetGridSize() * (Key == EKeys::Up?1:-1);
}
bWidgetAxisControlledByDrag = false;
Widget->SetCurrentAxis( VirtualAxis );
MouseDeltaTracker->AddDelta( this, VirtualKey, static_cast<int32>(VirtualDelta), 1 );
Widget->SetCurrentAxis( VirtualAxis );
UpdateMouseDelta();
InViewport->SetMouse( StartMousePos.X , StartMousePos.Y );
}
else if( bIsTracking && Event == IE_Released )
{
bWidgetAxisControlledByDrag = false;
MouseDeltaTracker->EndTracking( this );
bIsTracking = false;
Widget->SetCurrentAxis( EAxisList::None );
}
RedrawAllViewportsIntoThisScene();
}
/**
* Returns the horizontal axis for this viewport.
*/
EAxisList::Type FLevelEditorViewportClient::GetHorizAxis() const
{
switch( GetViewportType() )
{
case LVT_OrthoXY:
case LVT_OrthoNegativeXY:
return EAxisList::X;
case LVT_OrthoXZ:
case LVT_OrthoNegativeXZ:
return EAxisList::X;
case LVT_OrthoYZ:
case LVT_OrthoNegativeYZ:
return EAxisList::Y;
case LVT_OrthoFreelook:
case LVT_Perspective:
break;
}
return EAxisList::X;
}
/**
* Returns the vertical axis for this viewport.
*/
EAxisList::Type FLevelEditorViewportClient::GetVertAxis() const
{
switch( GetViewportType() )
{
case LVT_OrthoXY:
case LVT_OrthoNegativeXY:
return EAxisList::Y;
case LVT_OrthoXZ:
case LVT_OrthoNegativeXZ:
return EAxisList::Z;
case LVT_OrthoYZ:
case LVT_OrthoNegativeYZ:
return EAxisList::Z;
case LVT_OrthoFreelook:
case LVT_Perspective:
break;
}
return EAxisList::Y;
}
//
// FLevelEditorViewportClient::InputAxis
//
/**
* Sets the current level editing viewport client when created and stores the previous one
* When destroyed it sets the current viewport client back to the previous one.
*/
struct FScopedSetCurrentViewportClient
{
FScopedSetCurrentViewportClient( FLevelEditorViewportClient* NewCurrentViewport )
{
PrevCurrentLevelEditingViewportClient = GCurrentLevelEditingViewportClient;
GCurrentLevelEditingViewportClient = NewCurrentViewport;
}
~FScopedSetCurrentViewportClient()
{
GCurrentLevelEditingViewportClient = PrevCurrentLevelEditingViewportClient;
}
private:
FLevelEditorViewportClient* PrevCurrentLevelEditingViewportClient;
};
bool FLevelEditorViewportClient::InputAxis(const FInputKeyEventArgs& Args)
{
if (bDisableInput)
{
return true;
}
// Forward input axis events to the Editor World extension collection and give extensions an opportunity to consume input.
IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get();
FPlatformUserId UserId = DeviceMapper.GetUserForInputDevice(Args.InputDevice);
int32 ControllerId;
if (DeviceMapper.RemapUserAndDeviceToControllerId(UserId, ControllerId, Args.InputDevice))
{
UEditorWorldExtensionCollection& ExtensionCollection = *GEditor->GetEditorWorldExtensionsManager()->GetEditorWorldExtensions(GetWorld());
if (ExtensionCollection.InputAxis(this, Args.Viewport, ControllerId, Args.Key, Args.AmountDepressed, Args.DeltaTime))
{
return true;
}
}
// @todo Slate: GCurrentLevelEditingViewportClient is switched multiple times per frame and since we draw the border in slate this effectively causes border to always draw on the last viewport
FScopedSetCurrentViewportClient( this );
return FEditorViewportClient::InputAxis(Args);
}
static uint32 GetVolumeActorVisibilityId( const AActor& InActor )
{
UClass* Class = InActor.GetClass();
static TMap<UClass*, uint32 > ActorToIdMap;
if( ActorToIdMap.Num() == 0 )
{
// Build a mapping of volume classes to ID's. Do this only once
TArray< UClass *> VolumeClasses;
UUnrealEdEngine::GetSortedVolumeClasses(&VolumeClasses);
for( int32 VolumeIdx = 0; VolumeIdx < VolumeClasses.Num(); ++VolumeIdx )
{
// An actors flag is just the index of the actor in the stored volume array shifted left to represent a unique bit.
ActorToIdMap.Add( VolumeClasses[VolumeIdx], VolumeIdx );
}
}
uint32* ActorID = ActorToIdMap.Find( Class );
// return 0 if the actor flag was not found, otherwise return the actual flag.
return ActorID ? *ActorID : 0;
}
/**
* Returns true if the passed in volume is visible in the viewport (due to volume actor visibility flags)
*
* @param Volume The volume to check
*/
bool FLevelEditorViewportClient::IsVolumeVisibleInViewport( const AActor& VolumeActor ) const
{
// We pass in the actor class for compatibility but we should make sure
// the function is only given volume actors
//check( VolumeActor.IsA(AVolume::StaticClass()) );
uint32 VolumeId = GetVolumeActorVisibilityId( VolumeActor );
return VolumeActorVisibility[ VolumeId ];
}
void FLevelEditorViewportClient::RedrawAllViewportsIntoThisScene()
{
// Invalidate all viewports, so the new gizmo is rendered in each one
GEditor->RedrawLevelEditingViewports();
}
void FLevelEditorViewportClient::SetVREditView(bool bGameViewEnable)
{
UEditorWorldExtensionCollection* ExtensionCollection = GEditor->GetEditorWorldExtensionsManager()->GetEditorWorldExtensions(GetWorld());
check(ExtensionCollection != nullptr);
UVREditorMode* VREditorMode = Cast<UVREditorMode>(ExtensionCollection->FindExtension(UVREditorMode::StaticClass()));
if (VREditorMode && VREditorMode->IsFullyInitialized())
{
if (bGameViewEnable)
{
VREditorMode->OnPlacePreviewActor().AddStatic(&FLevelEditorViewportClient::SetIsDroppingPreviewActor);
}
else
{
VREditorMode->OnPlacePreviewActor().RemoveAll(this);
}
}
FEditorViewportClient::SetVREditView(bGameViewEnable);
}
UE::Widget::EWidgetMode FLevelEditorViewportClient::GetWidgetMode() const
{
if (GUnrealEd->ComponentVisManager.IsActive() && GUnrealEd->ComponentVisManager.IsVisualizingArchetype())
{
return UE::Widget::WM_None;
}
return FEditorViewportClient::GetWidgetMode();
}
FVector FLevelEditorViewportClient::GetWidgetLocation() const
{
FVector ComponentVisWidgetLocation;
if (GUnrealEd->ComponentVisManager.GetWidgetLocation(this, ComponentVisWidgetLocation))
{
return ComponentVisWidgetLocation;
}
return FEditorViewportClient::GetWidgetLocation();
}
FMatrix FLevelEditorViewportClient::GetWidgetCoordSystem() const
{
FMatrix ComponentVisWidgetCoordSystem;
if (GUnrealEd->ComponentVisManager.GetCustomInputCoordinateSystem(this, ComponentVisWidgetCoordSystem))
{
return ComponentVisWidgetCoordSystem;
}
return FEditorViewportClient::GetWidgetCoordSystem();
}
void FLevelEditorViewportClient::MoveLockedActorToCamera()
{
// If turned on, move any selected actors to the cameras location/rotation
AActor* ActiveActorLock = GetActiveActorLock().Get();
if (ActiveActorLock)
{
if (!ActiveActorLock->IsLockLocation())
{
if (TrackingTransaction.TransCount > 0)
{
SnapshotTransactionBuffer(ActiveActorLock);
USceneComponent* ActiveActorLockComponent = ActiveActorLock->GetRootComponent();
if (ActiveActorLockComponent && !ActiveActorLockComponent->IsCreatedByConstructionScript())
{
SnapshotTransactionBuffer(ActiveActorLockComponent);
}
}
// Need to disable orbit camera before setting actor position so that the viewport camera location is converted back
GCurrentLevelEditingViewportClient->ToggleOrbitCamera(false);
USceneComponent* ActiveActorLockComponent = ActiveActorLock->GetRootComponent();
TOptional<FRotator> PreviousRotator;
if (ActiveActorLockComponent)
{
PreviousRotator = ActiveActorLockComponent->GetRelativeRotation();
}
// If we're locked to a camera then we're reflecting the camera view and not the actor position. We need to reflect that delta when we reposition the piloted actor
if (bUseControllingActorViewInfo)
{
const UActorComponent* ViewComponent = FindViewComponentForActor(ActiveActorLock);
if (const UCameraComponent* CameraViewComponent = Cast<UCameraComponent>(ViewComponent))
{
FTransform AdditiveOffset;
float AdditiveFOV;
CameraViewComponent->GetAdditiveOffset(AdditiveOffset, AdditiveFOV);
const FTransform RelativeTransform = (AdditiveOffset * CameraViewComponent->GetComponentTransform()).Inverse();
const FTransform DesiredTransform = FTransform(GCurrentLevelEditingViewportClient->GetViewRotation(), GCurrentLevelEditingViewportClient->GetViewLocation());
ActiveActorLock->SetActorTransform(ActiveActorLock->GetActorTransform() * RelativeTransform * DesiredTransform);
}
else if (const USceneComponent* SceneViewComponent = Cast<USceneComponent>(ViewComponent))
{
const FTransform RelativeTransform = SceneViewComponent->GetComponentTransform().Inverse();
const FTransform DesiredTransform = FTransform(GCurrentLevelEditingViewportClient->GetViewRotation(), GCurrentLevelEditingViewportClient->GetViewLocation());
ActiveActorLock->SetActorTransform(ActiveActorLock->GetActorTransform() * RelativeTransform * DesiredTransform);
}
}
else
{
ActiveActorLock->SetActorLocation(GCurrentLevelEditingViewportClient->GetViewLocation(), false);
ActiveActorLock->SetActorRotation(GCurrentLevelEditingViewportClient->GetViewRotation());
}
if (ActiveActorLockComponent)
{
const FRotator Rot = PreviousRotator.GetValue();
FRotator ActorRotWind, ActorRotRem;
Rot.GetWindingAndRemainder(ActorRotWind, ActorRotRem);
const FQuat ActorQ = ActorRotRem.Quaternion();
const FQuat ResultQ = ActiveActorLockComponent->GetRelativeRotation().Quaternion();
FRotator NewActorRotRem = FRotator(ResultQ);
ActorRotRem.SetClosestToMe(NewActorRotRem);
FRotator DeltaRot = NewActorRotRem - ActorRotRem;
DeltaRot.Normalize();
ActiveActorLockComponent->SetRelativeRotationExact(Rot + DeltaRot);
}
}
if (ABrush* Brush = Cast<ABrush>(ActiveActorLock))
{
Brush->SetNeedRebuild(Brush->GetLevel());
}
FScopedLevelDirtied LevelDirtyCallback;
LevelDirtyCallback.Request();
RedrawAllViewportsIntoThisScene();
}
}
bool FLevelEditorViewportClient::HaveSelectedObjectsBeenChanged() const
{
return (TrackingTransaction.TransCount > 0 || TrackingTransaction.IsActive()) && (MouseDeltaTracker->HasReceivedDelta() || MouseDeltaTracker->WasExternalMovement());
}
FTypedElementListConstRef FLevelEditorViewportClient::GetElementsToManipulate(const bool bForceRefresh)
{
CacheElementsToManipulate(bForceRefresh);
return CachedElementsToManipulate;
}
void FLevelEditorViewportClient::CacheElementsToManipulate(const bool bForceRefresh)
{
if (bForceRefresh)
{
ResetElementsToManipulate();
}
if (!bHasCachedElementsToManipulate)
{
CachedElementsToManipulate = UEditorElementSubsystem::GetEditorNormalizedSelectionSet(*GetSelectionSet());
UEditorElementSubsystem::GetEditorManipulableElements(CachedElementsToManipulate, GetWidgetMode(), bIsSimulateInEditorViewport ? GEditor->PlayWorld : GEditor->EditorWorld);
bHasCachedElementsToManipulate = true;
}
}
void FLevelEditorViewportClient::ResetElementsToManipulate(const bool bClearList)
{
if (bClearList)
{
CachedElementsToManipulate->Reset();
}
bHasCachedElementsToManipulate = false;
}
void FLevelEditorViewportClient::ResetElementsToManipulateFromSelectionChange(const UTypedElementSelectionSet* InSelectionSet)
{
check(InSelectionSet == GetSelectionSet());
// Don't clear the list immediately, as the selection may change from a construction script running (while we're still iterating the list!)
// We'll process the clear on the next cache request, or when the typed element registry actually processes its pending deletion
ResetElementsToManipulate(/*bClearList*/false);
}
void FLevelEditorViewportClient::ResetElementsToManipulateFromProcessingDeferredElementsToDestroy()
{
if (!bHasCachedElementsToManipulate)
{
// If we have no cache, make sure the cached list is definitely empty now to ensure it doesn't contain any lingering references to things that are about to be deleted
CachedElementsToManipulate->Reset();
}
}
const UTypedElementSelectionSet* FLevelEditorViewportClient::GetSelectionSet() const
{
TSharedPtr<ILevelEditor> LevelEditor = ParentLevelEditor.Pin();
return LevelEditor ? LevelEditor->GetElementSelectionSet() : GEditor->GetSelectedActors()->GetElementSelectionSet();
}
UTypedElementSelectionSet* FLevelEditorViewportClient::GetMutableSelectionSet() const
{
TSharedPtr<ILevelEditor> LevelEditor = ParentLevelEditor.Pin();
return LevelEditor ? LevelEditor->GetMutableElementSelectionSet() : GEditor->GetSelectedActors()->GetElementSelectionSet();
}
void FLevelEditorViewportClient::MoveCameraToLockedActor()
{
// Do a slightly shorter version of the code in UpdateViewForLockedActor where we don't care about
// post-process settings.
const FLevelViewportActorLock& ActiveLock = ActorLocks.GetLock();
AActor* Actor = ActiveLock.GetLockedActor();
if (Actor)
{
bool bGotCameraView = false;
if (bLockedCameraView)
{
// If this is a camera actor, then inherit some other settings
UActorComponent* const ViewComponent = FindViewComponentForActor(Actor);
if (ViewComponent != nullptr)
{
FMinimalViewInfo ViewInfo;
if (ensure(ViewComponent->GetEditorPreviewInfo(0.f, ViewInfo)))
{
ViewFOV = ControllingActorViewInfo.FOV;
AspectRatio = ControllingActorViewInfo.AspectRatio;
SetViewLocation(ControllingActorViewInfo.Location);
SetViewRotation(ControllingActorViewInfo.Rotation);
bGotCameraView = true;
}
}
}
if (!bGotCameraView)
{
if (Actor->GetAttachParentActor() != NULL)
{
// Actor is parented, so use the actor to world matrix for translation and rotation information.
SetViewLocation(Actor->GetActorLocation());
SetViewRotation(Actor->GetActorRotation());
}
else if (Actor->GetRootComponent() != NULL)
{
// No attachment, so just use the relative location, so that we don't need to
// convert from a quaternion, which loses winding information.
SetViewLocation(Actor->GetRootComponent()->GetRelativeLocation());
SetViewRotation(Actor->GetRootComponent()->GetRelativeRotation());
}
}
Invalidate();
}
}
UActorComponent* FLevelEditorViewportClient::FindViewComponentForActor(AActor const* Actor)
{
UActorComponent* PreviewComponent = nullptr;
if (Actor)
{
const TWeakObjectPtr<UActorComponent> * CachedComponent = ViewComponentForActorCache.Find(Actor);
if (CachedComponent != nullptr)
{
PreviewComponent = CachedComponent->Get();
}
else
{
TSet<AActor const*> CheckedActors;
PreviewComponent = FindViewComponentForActor(Actor, CheckedActors);
ViewComponentForActorCache.Add(Actor, PreviewComponent);
}
}
return PreviewComponent;
}
UActorComponent* FLevelEditorViewportClient::FindViewComponentForActor(AActor const* Actor, TSet<AActor const*>& CheckedActors)
{
UActorComponent* PreviewComponent = nullptr;
if (Actor && !CheckedActors.Contains(Actor))
{
CheckedActors.Add(Actor);
// see if actor has a component with preview capabilities (prioritize camera components)
const TSet<UActorComponent*>& Comps = Actor->GetComponents();
// We need to know if any child component with preview info is selected
bool bFoundSelectedComp = false;
for (UActorComponent* Comp : Comps)
{
FMinimalViewInfo DummyViewInfo;
if (Comp && Comp->IsActive() && Comp->GetEditorPreviewInfo(/*DeltaTime =*/0.0f, DummyViewInfo))
{
if (Comp->IsSelected())
{
PreviewComponent = Comp;
bFoundSelectedComp = true;
break;
}
else if (PreviewComponent)
{
UCameraComponent* AsCamComp = Cast<UCameraComponent>(Comp);
if (AsCamComp != nullptr)
{
PreviewComponent = AsCamComp;
}
continue;
}
PreviewComponent = Comp;
}
}
// No preview if default preview is forbidden and no children selection found
if (!Actor->IsDefaultPreviewEnabled() && !bFoundSelectedComp)
{
return nullptr;
}
// now see if any actors are attached to us, directly or indirectly, that have an active camera component we might want to use
// we will just return the first one.
if (PreviewComponent == nullptr)
{
Actor->ForEachAttachedActors(
[&](AActor * AttachedActor) -> bool
{
UActorComponent* const Comp = FindViewComponentForActor(AttachedActor, CheckedActors);
if (Comp)
{
PreviewComponent = Comp;
return false; /* stops iteration */
}
return true; /* continue iteration */
}
);
}
}
return PreviewComponent;
}
void FLevelEditorViewportClient::SetActorLock(AActor* Actor)
{
// If we had an active lock and are clearing it, also end the transaction for that lock
if (!Actor)
{
AActor* ActiveActorLock = GetActiveActorLock().Get();
if (ActiveActorLock && !ActiveActorLock->IsLockLocation() && CachedPilotTransform.IsSet())
{
FTransform EndPosition = ActiveActorLock->GetTransform();
ActiveActorLock->SetActorTransform(CachedPilotTransform.GetValue());
{
const FScopedTransaction Transaction(LOCTEXT("PilotTransaction", "Pilot Actor"));
ActiveActorLock->Modify();
ActiveActorLock->SetActorTransform(EndPosition);
}
CachedPilotTransform = TOptional<FTransform>();
}
}
SetActorLock(FLevelViewportActorLock(Actor));
}
void FLevelEditorViewportClient::SetActorLock(const FLevelViewportActorLock& InActorLock)
{
if (ActorLocks.ActorLock.LockedActor != InActorLock.LockedActor)
{
SetIsCameraCut();
}
if (ActorLocks.ActorLock.LockedActor.IsValid())
{
PreviousActorLocks.ActorLock = ActorLocks.ActorLock;
}
ActorLocks.ActorLock = InActorLock;
}
void FLevelEditorViewportClient::SetCinematicActorLock(AActor* Actor)
{
SetCinematicActorLock(FLevelViewportActorLock(Actor));
}
void FLevelEditorViewportClient::SetCinematicActorLock(const FLevelViewportActorLock& InActorLock)
{
if (ActorLocks.CinematicActorLock.LockedActor != InActorLock.LockedActor)
{
SetIsCameraCut();
}
if (ActorLocks.CinematicActorLock.LockedActor.IsValid())
{
PreviousActorLocks.CinematicActorLock = ActorLocks.CinematicActorLock;
}
ActorLocks.CinematicActorLock = InActorLock;
}
bool FLevelEditorViewportClient::IsActorLocked(const TWeakObjectPtr<const AActor> InActor) const
{
return (InActor.IsValid() && GetActiveActorLock() == InActor);
}
bool FLevelEditorViewportClient::IsAnyActorLocked() const
{
return GetActiveActorLock().IsValid();
}
void FLevelEditorViewportClient::UpdateLockedActorViewports(const AActor* InActor, const bool bCheckRealtime)
{
// Loop through all the other viewports, checking to see if the camera needs updating based on the locked actor
for (FLevelEditorViewportClient* Client : GEditor->GetLevelViewportClients())
{
if( Client && Client != this )
{
Client->UpdateLockedActorViewport(InActor, bCheckRealtime);
}
}
}
void FLevelEditorViewportClient::UpdateLockedActorViewport(const AActor* InActor, const bool bCheckRealtime)
{
// If this viewport has the actor locked and we need to update the camera, then do so
if( IsActorLocked(InActor) && ( !bCheckRealtime || IsRealtime() ) )
{
MoveCameraToLockedActor();
}
}
void FLevelEditorViewportClient::ApplyDeltaToActors(const FVector& InDrag, const FRotator& InRot, const FVector& InScale)
{
ApplyDeltaToSelectedElements(FTransform(InRot, InDrag, InScale));
}
void FLevelEditorViewportClient::ApplyDeltaToActor(AActor* InActor, const FVector& InDeltaDrag, const FRotator& InDeltaRot, const FVector& InDeltaScale)
{
if (FTypedElementHandle ActorElementHandle = UEngineElementsLibrary::AcquireEditorActorElementHandle(InActor))
{
ApplyDeltaToElement(ActorElementHandle, FTransform(InDeltaRot, InDeltaDrag, InDeltaScale));
}
}
void FLevelEditorViewportClient::ApplyDeltaToComponent(USceneComponent* InComponent, const FVector& InDeltaDrag, const FRotator& InDeltaRot, const FVector& InDeltaScale)
{
if (FTypedElementHandle ComponentElementHandle = UEngineElementsLibrary::AcquireEditorComponentElementHandle(InComponent))
{
ApplyDeltaToElement(ComponentElementHandle, FTransform(InDeltaRot, InDeltaDrag, InDeltaScale));
}
}
void FLevelEditorViewportClient::ApplyDeltaToSelectedElements(const FTransform& InDeltaTransform)
{
if (InDeltaTransform.GetTranslation().IsZero() && InDeltaTransform.Rotator().IsZero() && InDeltaTransform.GetScale3D().IsZero())
{
return;
}
FTransform ModifiedDeltaTransform = InDeltaTransform;
{
FVector AdjustedScale = ModifiedDeltaTransform.GetScale3D();
// If we are scaling, we need to change the scaling factor a bit to properly align to grid
if (GEditor->UsePercentageBasedScaling() && !AdjustedScale.IsNearlyZero())
{
AdjustedScale *= ((GEditor->GetScaleGridSize() / 100.0f) / GEditor->GetGridSize());
}
ModifiedDeltaTransform.SetScale3D(AdjustedScale);
}
FInputDeviceState InputState;
InputState.SetModifierKeyStates(IsShiftPressed(), IsAltPressed(), IsCtrlPressed(), IsCmdPressed());
FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
ViewportInteraction->UpdateGizmoManipulation(ElementsToManipulate, GetWidgetMode(), Widget ? Widget->GetCurrentAxis() : EAxisList::None, InputState, ModifiedDeltaTransform);
}
void FLevelEditorViewportClient::ApplyDeltaToElement(const FTypedElementHandle& InElementHandle, const FTransform& InDeltaTransform)
{
FInputDeviceState InputState;
InputState.SetModifierKeyStates(IsShiftPressed(), IsAltPressed(), IsCtrlPressed(), IsCmdPressed());
ViewportInteraction->ApplyDeltaToElement(InElementHandle, GetWidgetMode(), Widget ? Widget->GetCurrentAxis() : EAxisList::None, InputState, InDeltaTransform);
}
void FLevelEditorViewportClient::MirrorSelectedActors(const FVector& InMirrorScale)
{
FScopedLevelDirtied LevelDirtyCallback;
const UTypedElementSelectionSet* SelectionSet = GetSelectionSet();
SelectionSet->ForEachSelectedObject<AActor>([this, &InMirrorScale, &LevelDirtyCallback](AActor* InActor)
{
ViewportInteraction->MirrorElement(UEngineElementsLibrary::AcquireEditorActorElementHandle(InActor), InMirrorScale);
LevelDirtyCallback.Request();
return true;
});
if (UBrushEditingSubsystem* BrushSubsystem = GEditor->GetEditorSubsystem<UBrushEditingSubsystem>())
{
BrushSubsystem->UpdateGeometryFromSelectedBrushes();
}
RedrawAllViewportsIntoThisScene();
}
void FLevelEditorViewportClient::MirrorSelectedElements(const FVector& InMirrorScale)
{
FScopedLevelDirtied LevelDirtyCallback;
const UTypedElementSelectionSet* SelectionSet = GetSelectionSet();
SelectionSet->ForEachSelectedElementHandle([this, &InMirrorScale, &LevelDirtyCallback](const FTypedElementHandle& InElementHandle)
{
ViewportInteraction->MirrorElement(InElementHandle, InMirrorScale);
LevelDirtyCallback.Request();
return true;
});
if (UBrushEditingSubsystem* BrushSubsystem = GEditor->GetEditorSubsystem<UBrushEditingSubsystem>())
{
BrushSubsystem->UpdateGeometryFromSelectedBrushes();
}
RedrawAllViewportsIntoThisScene();
}
bool FLevelEditorViewportClient::GetFocusBounds(FTypedElementListConstRef InElements, FBoxSphereBounds& OutBounds)
{
return ViewportInteraction->GetFocusBounds(InElements, OutBounds);
}
FTransform FLevelEditorViewportClient::CachePreDragActorTransform(const AActor* InActor)
{
if (const FTransform* PreDragTransform = PreDragActorTransforms.Find(InActor))
{
return *PreDragTransform;
}
return PreDragActorTransforms.Add(InActor, InActor->GetTransform());
}
EMouseCursor::Type FLevelEditorViewportClient::GetCursor(FViewport* InViewport,int32 X,int32 Y)
{
EMouseCursor::Type CursorType = FEditorViewportClient::GetCursor(InViewport,X,Y);
UWorld* ViewportWorld = GetWorld();
if (ViewportWorld != nullptr)
{
// Allow the viewport interaction to override any previously set mouse cursor
UViewportWorldInteraction* WorldInteraction = Cast<UViewportWorldInteraction>(GEditor->GetEditorWorldExtensionsManager()->GetEditorWorldExtensions(GetWorld())->FindExtension(UViewportWorldInteraction::StaticClass()));
if (WorldInteraction != nullptr)
{
if (WorldInteraction->ShouldForceCursor())
{
CursorType = EMouseCursor::Crosshairs;
SetRequiredCursor(false, true);
UpdateRequiredCursorVisibility();
}
else if (WorldInteraction->ShouldSuppressExistingCursor())
{
CursorType = EMouseCursor::None;
SetRequiredCursor(false, false);
UpdateRequiredCursorVisibility();
}
}
}
// Don't select widget axes by mouse over while they're being controlled by a mouse drag.
if( InViewport->IsCursorVisible() && !bWidgetAxisControlledByDrag && !ModeTools->HasOngoingTransform())
{
HHitProxy* HitProxy = InViewport->GetHitProxy(X, Y);
if( !HitProxy && HoveredObjects.Num() > 0 )
{
ClearHoverFromObjects();
Invalidate( false, false );
}
}
return CursorType;
}
void FLevelEditorViewportClient::MouseMove(FViewport* InViewport, int32 x, int32 y)
{
if (bUserIsControllingAtmosphericLight0 || bUserIsControllingAtmosphericLight1)
{
UWorld* ViewportWorld = GetWorld();
const uint8 DesiredLightIndex = bUserIsControllingAtmosphericLight0 ? 0 : 1;
UDirectionalLightComponent* SelectedAtmosphericLight = GetAtmosphericLight(DesiredLightIndex, ViewportWorld);
if (SelectedAtmosphericLight)
{
int32 mouseDeltaX = x - CachedLastMouseX;
int32 mouseDeltaY = y - CachedLastMouseY;
FTransform ComponentTransform = SelectedAtmosphericLight->GetComponentTransform();
FQuat LightRotation = ComponentTransform.GetRotation();
// Rotate around up axis (yaw)
FVector UpVector = FVector(0, 0, 1);
LightRotation = FQuat(UpVector, float(mouseDeltaX)*0.01f) * LightRotation;
// Light Zenith rotation (pitch)
FVector PitchRotationAxis = FVector::CrossProduct(LightRotation.GetForwardVector(), UpVector);
if (FMath::Abs(FVector::DotProduct(LightRotation.GetForwardVector(), UpVector)) > (1.0f - KINDA_SMALL_NUMBER))
{
PitchRotationAxis = FVector::CrossProduct(LightRotation.GetForwardVector(), FVector(1, 0, 0));
}
PitchRotationAxis.Normalize();
LightRotation = FQuat(PitchRotationAxis, float(mouseDeltaY)*0.01f) * LightRotation;
ComponentTransform.SetRotation(LightRotation);
SelectedAtmosphericLight->SetWorldTransform(ComponentTransform);
NotifyAtmosphericLightHasMoved(*SelectedAtmosphericLight, false);
UserControlledAtmosphericLightMatrix = ComponentTransform;
UserControlledAtmosphericLightMatrix.NormalizeRotation();
}
}
FEditorViewportClient::MouseMove(InViewport, x, y);
}
/**
* Called when the mouse is moved while a window input capture is in effect
*
* @param InViewport Viewport that captured the mouse input
* @param InMouseX New mouse cursor X coordinate
* @param InMouseY New mouse cursor Y coordinate
*/
void FLevelEditorViewportClient::CapturedMouseMove( FViewport* InViewport, int32 InMouseX, int32 InMouseY )
{
// Commit to any pending transactions now
TrackingTransaction.PromotePendingToActive();
FEditorViewportClient::CapturedMouseMove(InViewport, InMouseX, InMouseY);
}
/**
* Checks if the mouse is hovered over a hit proxy and decides what to do.
*/
void FLevelEditorViewportClient::CheckHoveredHitProxy( HHitProxy* HoveredHitProxy )
{
FEditorViewportClient::CheckHoveredHitProxy(HoveredHitProxy);
// We'll keep track of changes to hovered objects as the cursor moves
const bool bUseHoverFeedback = GEditor != NULL && GetDefault<ULevelEditorViewportSettings>()->bEnableViewportHoverFeedback;
TSet< FViewportHoverTarget > NewHoveredObjects;
// If the cursor is visible over level viewports, then we'll check for new objects to be hovered over
if( bUseHoverFeedback && HoveredHitProxy )
{
// Set mouse hover cue for objects under the cursor
if (HoveredHitProxy->IsA(HActor::StaticGetType()) || HoveredHitProxy->IsA(HBSPBrushVert::StaticGetType()))
{
// Hovered over an actor
AActor* ActorUnderCursor = NULL;
if (HoveredHitProxy->IsA(HActor::StaticGetType()))
{
HActor* ActorHitProxy = static_cast<HActor*>(HoveredHitProxy);
ActorUnderCursor = ActorHitProxy->Actor;
}
else if (HoveredHitProxy->IsA(HBSPBrushVert::StaticGetType()))
{
HBSPBrushVert* ActorHitProxy = static_cast<HBSPBrushVert*>(HoveredHitProxy);
ActorUnderCursor = ActorHitProxy->Brush.Get();
}
if( ActorUnderCursor != NULL )
{
// Check to see if the actor under the cursor is part of a group. If so, we will how a hover cue the whole group
AGroupActor* GroupActor = AGroupActor::GetRootForActor( ActorUnderCursor, true, false );
if(GroupActor && UActorGroupingUtils::IsGroupingActive())
{
// Get all the actors in the group and add them to the list of objects to show a hover cue for.
TArray<AActor*> ActorsInGroup;
GroupActor->GetGroupActors( ActorsInGroup, true );
for( int32 ActorIndex = 0; ActorIndex < ActorsInGroup.Num(); ++ActorIndex )
{
NewHoveredObjects.Add( FViewportHoverTarget( ActorsInGroup[ActorIndex] ) );
}
}
else
{
NewHoveredObjects.Add( FViewportHoverTarget( ActorUnderCursor ) );
}
}
}
else if( HoveredHitProxy->IsA( HModel::StaticGetType() ) )
{
// Hovered over a model (BSP surface)
HModel* ModelHitProxy = static_cast< HModel* >( HoveredHitProxy );
UModel* ModelUnderCursor = ModelHitProxy->GetModel();
if( ModelUnderCursor != NULL )
{
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
Viewport,
GetScene(),
EngineShowFlags)
.SetRealtimeUpdate( IsRealtime() ));
FSceneView* SceneView = CalcSceneView( &ViewFamily );
uint32 SurfaceIndex = INDEX_NONE;
if( ModelHitProxy->ResolveSurface( SceneView, CachedMouseX, CachedMouseY, SurfaceIndex ) )
{
FBspSurf& Surf = ModelUnderCursor->Surfs[ SurfaceIndex ];
Surf.PolyFlags |= PF_Hovered;
NewHoveredObjects.Add( FViewportHoverTarget( ModelUnderCursor, SurfaceIndex ) );
}
}
}
}
UpdateHoveredObjects( NewHoveredObjects );
}
void FLevelEditorViewportClient::UpdateHoveredObjects( const TSet<FViewportHoverTarget>& NewHoveredObjects )
{
// Check to see if there are any hovered objects that need to be updated
{
bool bAnyHoverChanges = false;
if( NewHoveredObjects.Num() > 0 )
{
for( TSet<FViewportHoverTarget>::TIterator It( HoveredObjects ); It; ++It )
{
FViewportHoverTarget& OldHoverTarget = *It;
if( !NewHoveredObjects.Contains( OldHoverTarget ) )
{
// Remove hover effect from object that no longer needs it
RemoveHoverEffect( OldHoverTarget );
HoveredObjects.Remove( OldHoverTarget );
bAnyHoverChanges = true;
}
}
}
for( TSet<FViewportHoverTarget>::TConstIterator It( NewHoveredObjects ); It; ++It )
{
const FViewportHoverTarget& NewHoverTarget = *It;
if( !HoveredObjects.Contains( NewHoverTarget ) )
{
// Add hover effect to this object
AddHoverEffect( NewHoverTarget );
HoveredObjects.Add( NewHoverTarget );
bAnyHoverChanges = true;
}
}
// Redraw the viewport if we need to
if( bAnyHoverChanges )
{
// NOTE: We're only redrawing the viewport that the mouse is over. We *could* redraw all viewports
// so the hover effect could be seen in all potential views, but it will be slower.
RedrawRequested( Viewport );
}
}
}
bool FLevelEditorViewportClient::GetActiveSafeFrame(float& OutAspectRatio) const
{
if (!IsOrtho())
{
const UCameraComponent* CameraComponent = GetCameraComponentForView();
if (CameraComponent && CameraComponent->bConstrainAspectRatio)
{
OutAspectRatio = CameraComponent->AspectRatio;
return true;
}
}
return false;
}
/**
* Renders a view frustum specified by the provided frustum parameters
*
* @param PDI PrimitiveDrawInterface to use to draw the view frustum
* @param FrustumColor Color to draw the view frustum in
* @param FrustumAngle Angle of the frustum
* @param FrustumAspectRatio Aspect ratio of the frustum
* @param FrustumStartDist Start distance of the frustum
* @param FrustumEndDist End distance of the frustum
* @param InViewMatrix View matrix to use to draw the frustum
*/
static void RenderViewFrustum( FPrimitiveDrawInterface* PDI,
const FLinearColor& FrustumColor,
float FrustumAngle,
float FrustumAspectRatio,
float FrustumStartDist,
float FrustumEndDist,
const FMatrix& InViewMatrix)
{
FVector Direction(0,0,1);
FVector LeftVector(1,0,0);
FVector UpVector(0,1,0);
FVector Verts[8];
// FOVAngle controls the horizontal angle.
float HozHalfAngle = (FrustumAngle) * ((float)PI/360.f);
float HozLength = FrustumStartDist * FMath::Tan(HozHalfAngle);
float VertLength = HozLength/FrustumAspectRatio;
// near plane verts
Verts[0] = (Direction * FrustumStartDist) + (UpVector * VertLength) + (LeftVector * HozLength);
Verts[1] = (Direction * FrustumStartDist) + (UpVector * VertLength) - (LeftVector * HozLength);
Verts[2] = (Direction * FrustumStartDist) - (UpVector * VertLength) - (LeftVector * HozLength);
Verts[3] = (Direction * FrustumStartDist) - (UpVector * VertLength) + (LeftVector * HozLength);
HozLength = FrustumEndDist * FMath::Tan(HozHalfAngle);
VertLength = HozLength/FrustumAspectRatio;
// far plane verts
Verts[4] = (Direction * FrustumEndDist) + (UpVector * VertLength) + (LeftVector * HozLength);
Verts[5] = (Direction * FrustumEndDist) + (UpVector * VertLength) - (LeftVector * HozLength);
Verts[6] = (Direction * FrustumEndDist) - (UpVector * VertLength) - (LeftVector * HozLength);
Verts[7] = (Direction * FrustumEndDist) - (UpVector * VertLength) + (LeftVector * HozLength);
for( int32 x = 0 ; x < 8 ; ++x )
{
Verts[x] = InViewMatrix.InverseFast().TransformPosition( Verts[x] );
}
const uint8 PrimitiveDPG = SDPG_Foreground;
PDI->DrawLine( Verts[0], Verts[1], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[1], Verts[2], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[2], Verts[3], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[3], Verts[0], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[4], Verts[5], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[5], Verts[6], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[6], Verts[7], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[7], Verts[4], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[0], Verts[4], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[1], Verts[5], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[2], Verts[6], FrustumColor, PrimitiveDPG );
PDI->DrawLine( Verts[3], Verts[7], FrustumColor, PrimitiveDPG );
}
void FLevelEditorViewportClient::Draw(const FSceneView* View,FPrimitiveDrawInterface* PDI)
{
FMemMark Mark(FMemStack::Get());
FEditorViewportClient::Draw(View,PDI);
DrawBrushDetails(View, PDI);
AGroupActor::DrawBracketsForGroups(PDI, Viewport);
if (EngineShowFlags.StreamingBounds)
{
DrawTextureStreamingBounds(View, PDI);
}
// A frustum should be drawn if the viewport is ortho and level streaming volume previs is enabled in some viewport
if ( IsOrtho() )
{
for (FLevelEditorViewportClient* CurViewportClient : GEditor->GetLevelViewportClients())
{
if ( CurViewportClient && IsPerspective() && GetDefault<ULevelEditorViewportSettings>()->bLevelStreamingVolumePrevis )
{
// Draw the view frustum of the level streaming volume previs viewport.
RenderViewFrustum(PDI, FLinearColor(1.0, 0.0, 1.0, 1.0),
GPerspFrustumAngle,
GPerspFrustumAspectRatio,
GPerspFrustumStartDist,
GPerspFrustumEndDist,
GPerspViewMatrix);
break;
}
}
}
if (IsPerspective())
{
DrawStaticLightingDebugInfo( View, PDI );
}
if ( GEditor->bEnableSocketSnapping )
{
const bool bGameViewMode = View->Family->EngineShowFlags.Game && !GEditor->bDrawSocketsInGMode;
for( FActorIterator It(GetWorld()); It; ++It )
{
AActor* Actor = *It;
if (bGameViewMode || Actor->IsHiddenEd())
{
// Don't display sockets on hidden actors...
continue;
}
for (UActorComponent* Component : Actor->GetComponents())
{
USceneComponent* SceneComponent = Cast<USceneComponent>(Component);
if (SceneComponent && SceneComponent->HasAnySockets())
{
TArray<FComponentSocketDescription> Sockets;
SceneComponent->QuerySupportedSockets(Sockets);
for (int32 SocketIndex = 0; SocketIndex < Sockets.Num() ; ++SocketIndex)
{
FComponentSocketDescription& Socket = Sockets[SocketIndex];
if (Socket.Type == EComponentSocketType::Socket)
{
const FTransform SocketTransform = SceneComponent->GetSocketTransform(Socket.Name);
const float DiamondSize = 2.0f;
const FColor DiamondColor(255,128,128);
PDI->SetHitProxy( new HLevelSocketProxy( *It, SceneComponent, Socket.Name ) );
DrawWireDiamond( PDI, SocketTransform.ToMatrixWithScale(), DiamondSize, DiamondColor, SDPG_Foreground );
PDI->SetHitProxy( NULL );
}
}
}
}
}
}
if( this == GCurrentLevelEditingViewportClient )
{
FSnappingUtils::DrawSnappingHelpers( View, PDI );
}
if(GUnrealEd != NULL && !IsInGameView())
{
GUnrealEd->DrawComponentVisualizers(View, PDI);
}
if (GEditor->bDrawParticleHelpers == true)
{
if (View->Family->EngineShowFlags.Game)
{
extern ENGINE_API void DrawParticleSystemHelpers(const FSceneView* View,FPrimitiveDrawInterface* PDI);
DrawParticleSystemHelpers(View, PDI);
}
}
if (UserIsControllingAtmosphericLightTimer > 0.0f)
{
// Draw a gizmo helping to figure out where is the light when moving it using a shortcut.
FQuat ViewRotation = FQuat(GetViewRotation());
FVector ViewPosition = GetViewLocation();
const float GizmoDistance = 50.0f;
const float GizmoSideOffset = 15.0f;
const float GizmoRadius = 10.0f;
const float ThicknessLight = 0.05f;
const float ThicknessBold = 0.2f;
// Always draw the gizmo right in in front of the camera with a little side shift.
const FVector X(1.0f, 0.0f, 0.0f);
const FVector Y(0.0f, 1.0f, 0.0f);
const FVector Z(0.0f, 0.0f, 1.0f);
const FVector Base = ViewPosition + GizmoDistance * ViewRotation.GetForwardVector() + GizmoSideOffset * (-ViewRotation.GetUpVector() + ViewRotation.GetRightVector());
// Draw world main axis
FRotator IdentityX(0.0f, 0.0f, 0.0f);
FRotator IdentityY(0.0f, 90.0f, 0.0f);
FRotator IdentityZ(90.0f, 0.0f, 0.0f);
DrawDirectionalArrow(PDI, FQuatRotationTranslationMatrix(FQuat(IdentityX), Base), FColor(255, 0, 0, 127), GizmoRadius, 0.3f, SDPG_World, ThicknessBold);
DrawDirectionalArrow(PDI, FQuatRotationTranslationMatrix(FQuat(IdentityY), Base), FColor(0, 255, 0, 127), GizmoRadius, 0.3f, SDPG_World, ThicknessBold);
DrawDirectionalArrow(PDI, FQuatRotationTranslationMatrix(FQuat(IdentityZ), Base), FColor(0, 0, 255, 127), GizmoRadius, 0.3f, SDPG_World, ThicknessBold);
// Render polar coordinate circles
DrawCircle(PDI, Base, X, Y, FLinearColor(0.2f, 0.2f, 1.0f), GizmoRadius, 32, SDPG_World, ThicknessBold);
DrawCircle(PDI, Base, X, Y, FLinearColor(0.2f, 0.2f, 0.75f), GizmoRadius*0.75f, 32, SDPG_World, ThicknessLight);
DrawCircle(PDI, Base, X, Y, FLinearColor(0.2f, 0.2f, 0.50f), GizmoRadius*0.50f, 32, SDPG_World, ThicknessLight);
DrawCircle(PDI, Base, X, Y, FLinearColor(0.2f, 0.2f, 0.25f), GizmoRadius*0.25f, 32, SDPG_World, ThicknessLight);
DrawArc(PDI, Base, Z, Y, -90.0f, 90.0f, GizmoRadius, 32, FLinearColor(1.0f, 0.2f, 0.2f), SDPG_World);
DrawArc(PDI, Base, Z, X, -90.0f, 90.0f, GizmoRadius, 32, FLinearColor(0.2f, 1.0f, 0.2f), SDPG_World);
// Draw the light incoming light direction. The arrow is offset outward to help depth perception when it intersects with other gizmo elements.
const FLinearColor ArrowColor = FLinearColor(-UserControlledAtmosphericLightMatrix.GetRotation().GetForwardVector() * 0.5f + 0.5f);
const FVector ArrowOrigin = Base - UserControlledAtmosphericLightMatrix.GetRotation().GetForwardVector()*GizmoRadius*1.25;
const FQuatRotationTranslationMatrix ArrowToWorld(UserControlledAtmosphericLightMatrix.GetRotation(), ArrowOrigin);
DrawDirectionalArrow(PDI, ArrowToWorld, ArrowColor, GizmoRadius, 0.3f, SDPG_World, ThicknessBold);
// Now draw x, y and z axis to help getting a sense of depth when look at the vectors on screen.
FVector LightArrowTip = -UserControlledAtmosphericLightMatrix.GetRotation().GetForwardVector()*GizmoRadius;
FVector P0 = Base + LightArrowTip * FVector(1.0f, 0.0f, 0.0f);
FVector P1 = Base + LightArrowTip * FVector(1.0f, 1.0f, 0.0f);
FVector P2 = Base + LightArrowTip * FVector(1.0f, 1.0f, 1.0f);
PDI->DrawLine(Base, P0, FLinearColor(1.0f, 0.0f, 0.0f), SDPG_World, ThicknessLight);
PDI->DrawLine(P0, P1, FLinearColor(0.0f, 1.0f, 0.0f), SDPG_World, ThicknessLight);
PDI->DrawLine(P1, P2, FLinearColor(0.0f, 0.0f, 1.0f), SDPG_World, ThicknessLight);
}
Mark.Pop();
}
void FLevelEditorViewportClient::DrawBrushDetails(const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
// Draw translucent polygons on brushes and volumes
TRACE_CPUPROFILER_EVENT_SCOPE(FLevelEditorViewportClient::DrawBrushDetails);
for (TActorIterator<ABrush> It(GetWorld()); It; ++It)
{
ABrush* Brush = *It;
// Brush->Brush is checked to safe from brushes that were created without having their brush members attached.
// Check whether it satisfies the ShowBrushMarkersPoly condition of being selected or whether bDisplayShadedVolume is true
if (Brush->Brush && Brush->Brush->Polys && (FActorEditorUtils::IsABuilderBrush(Brush) || Brush->IsVolumeBrush()) &&
(Brush->bDisplayShadedVolume || (GetDefault<ULevelEditorViewportSettings>()->bShowBrushMarkerPolys && ModeTools->GetSelectedActors()->IsSelected(Brush))))
{
// Build a mesh by basically drawing the triangles of each
FDynamicMeshBuilder MeshBuilder(View->GetFeatureLevel());
int32 VertexOffset = 0;
for (int32 PolyIdx = 0; PolyIdx < Brush->Brush->Polys->Element.Num(); ++PolyIdx)
{
const FPoly* Poly = &Brush->Brush->Polys->Element[PolyIdx];
if (Poly->Vertices.Num() > 2)
{
const FVector3f Vertex0 = Poly->Vertices[0];
FVector3f Vertex1 = Poly->Vertices[1];
MeshBuilder.AddVertex(Vertex0, FVector2f::ZeroVector, FVector3f(1, 0, 0), FVector3f(0, 1, 0), FVector3f(0, 0, 1), FColor::White);
MeshBuilder.AddVertex(Vertex1, FVector2f::ZeroVector, FVector3f(1, 0, 0), FVector3f(0, 1, 0), FVector3f(0, 0, 1), FColor::White);
for (int32 VertexIdx = 2; VertexIdx < Poly->Vertices.Num(); ++VertexIdx)
{
const FVector3f Vertex2 = Poly->Vertices[VertexIdx];
MeshBuilder.AddVertex(Vertex2, FVector2f::ZeroVector, FVector3f(1, 0, 0), FVector3f(0, 1, 0), FVector3f(0, 0, 1), FColor::White);
MeshBuilder.AddTriangle(VertexOffset, VertexOffset + VertexIdx, VertexOffset + VertexIdx - 1);
Vertex1 = Vertex2;
}
// Increment the vertex offset so the next polygon uses the correct vertex indices.
VertexOffset += Poly->Vertices.Num();
}
}
// Use the material below when bDisplayShadedVolume is true
if (Brush->bDisplayShadedVolume)
{
// Allocate the material proxy and register it so it can be deleted properly once the rendering is done with it.
FColor VolumeColor = Brush->GetWireColor();
VolumeColor.A = static_cast<uint8>(Brush->ShadedVolumeOpacityValue * 255.0f);
FDynamicColoredMaterialRenderProxy* MaterialProxy = new FDynamicColoredMaterialRenderProxy(GEngine->GeomMaterial->GetRenderProxy(), VolumeColor);
PDI->RegisterDynamicResource(MaterialProxy);
// Flush the mesh triangles.
// This will get the default behavior of receiving decals but not having a hitproxy ID
MeshBuilder.Draw(PDI, Brush->ActorToWorld().ToMatrixWithScale(), MaterialProxy, SDPG_World, false);
}
else
{
// Allocate the material proxy and register it so it can be deleted properly once the rendering is done with it.
FDynamicColoredMaterialRenderProxy* MaterialProxy = new FDynamicColoredMaterialRenderProxy(GEngine->EditorBrushMaterial->GetRenderProxy(), Brush->GetWireColor());
PDI->RegisterDynamicResource(MaterialProxy);
// Flush the mesh triangles.
MeshBuilder.Draw(PDI, Brush->ActorToWorld().ToMatrixWithScale(), MaterialProxy, SDPG_World, false, true, FHitProxyId::InvisibleHitProxyId);
}
}
}
if (ModeTools->ShouldDrawBrushVertices() && !IsInGameView())
{
UTexture2D* VertexTexture = GEngine->DefaultBSPVertexTexture;
const float TextureSizeX = VertexTexture->GetSizeX() * 0.170f;
const float TextureSizeY = VertexTexture->GetSizeY() * 0.170f;
USelection* Selection = ModeTools->GetSelectedActors();
if(Selection->IsClassSelected(ABrush::StaticClass()))
{
for (FSelectionIterator It(*ModeTools->GetSelectedActors()); It; ++It)
{
ABrush* Brush = Cast<ABrush>(*It);
if(Brush && Brush->Brush && !FActorEditorUtils::IsABuilderBrush(Brush))
{
for(int32 p = 0; p < Brush->Brush->Polys->Element.Num(); ++p)
{
FTransform BrushTransform = Brush->ActorToWorld();
FPoly* poly = &Brush->Brush->Polys->Element[p];
for(int32 VertexIndex = 0; VertexIndex < poly->Vertices.Num(); ++VertexIndex)
{
const FVector3f& PolyVertex = poly->Vertices[VertexIndex];
const FVector WorldLocation = FVector(BrushTransform.TransformPosition((FVector)PolyVertex));
const float Scale =
static_cast<float>(View->WorldToScreen(WorldLocation).W * (4.0f / View->UnscaledViewRect.Width() / View->ViewMatrices.GetProjectionMatrix().M[0][0]));
const FColor Color(Brush->GetWireColor());
PDI->SetHitProxy(new HBSPBrushVert(Brush, &poly->Vertices[VertexIndex]));
PDI->DrawSprite(WorldLocation, TextureSizeX * Scale, TextureSizeY * Scale, VertexTexture->GetResource(), Color, SDPG_World, 0.0f, 0.0f, 0.0f, 0.0f, SE_BLEND_Masked);
PDI->SetHitProxy(NULL);
}
}
}
}
}
}
}
void FLevelEditorViewportClient::UpdateAudioListener(const FSceneView& View)
{
UWorld* ViewportWorld = GetWorld();
if (ViewportWorld)
{
if (FAudioDevice* AudioDevice = ViewportWorld->GetAudioDeviceRaw())
{
FVector ViewLocation = GetViewLocation();
FRotator ViewRotation = GetViewRotation();
const bool bStereoRendering = GEngine->XRSystem.IsValid() && GEngine->IsStereoscopic3D( Viewport );
if( bStereoRendering && GEngine->XRSystem->IsHeadTrackingAllowed() )
{
FQuat RoomSpaceHeadOrientation;
FVector RoomSpaceHeadLocation;
GEngine->XRSystem->GetCurrentPose( IXRTrackingSystem::HMDDeviceId, /* Out */ RoomSpaceHeadOrientation, /* Out */ RoomSpaceHeadLocation );
// NOTE: The RoomSpaceHeadLocation has already been adjusted for WorldToMetersScale
const FVector WorldSpaceHeadLocation = GetViewLocation() + GetViewRotation().RotateVector( RoomSpaceHeadLocation );
ViewLocation = WorldSpaceHeadLocation;
ViewRotation = (ViewRotation.Quaternion() * RoomSpaceHeadOrientation).Rotator();
}
FTransform ListenerTransform(ViewRotation);
ListenerTransform.SetLocation(ViewLocation);
AudioDevice->SetListener(ViewportWorld, 0, ListenerTransform, 0.f);
}
}
}
/** Determines if the new MoveCanvas movement should be used
* @return - true if we should use the new drag canvas movement. Returns false for combined object-camera movement and marquee selection
*/
bool FLevelEditorViewportClient::ShouldUseMoveCanvasMovement()
{
const bool LeftMouseButtonDown = Viewport->KeyState(EKeys::LeftMouseButton) ? true : false;
const bool MiddleMouseButtonDown = Viewport->KeyState(EKeys::MiddleMouseButton) ? true : false;
const bool RightMouseButtonDown = Viewport->KeyState(EKeys::RightMouseButton) ? true : false;
const bool bMouseButtonDown = (LeftMouseButtonDown || MiddleMouseButtonDown || RightMouseButtonDown );
const bool AltDown = IsAltPressed();
const bool ShiftDown = IsShiftPressed();
const bool ControlDown = IsCtrlPressed();
//if we're using the new move canvas mode, we're in an ortho viewport, and the mouse is down
if (GetDefault<ULevelEditorViewportSettings>()->bPanMovesCanvas && IsOrtho() && bMouseButtonDown)
{
//MOVING CAMERA
if ( !MouseDeltaTracker->UsingDragTool() && AltDown == false && ShiftDown == false && ControlDown == false && (Widget->GetCurrentAxis() == EAxisList::None) && (LeftMouseButtonDown ^ RightMouseButtonDown))
{
return true;
}
//OBJECT MOVEMENT CODE
if ( ( AltDown == false && ShiftDown == false && ( LeftMouseButtonDown ^ RightMouseButtonDown ) ) &&
( ( GetWidgetMode() == UE::Widget::WM_Translate && Widget->GetCurrentAxis() != EAxisList::None ) ||
( GetWidgetMode() == UE::Widget::WM_TranslateRotateZ && Widget->GetCurrentAxis() != EAxisList::ZRotation && Widget->GetCurrentAxis() != EAxisList::None ) ||
( GetWidgetMode() == UE::Widget::WM_2D && Widget->GetCurrentAxis() != EAxisList::Rotate2D && Widget->GetCurrentAxis() != EAxisList::None ) ) )
{
return true;
}
//ALL other cases hide the mouse
return false;
}
else
{
//current system - do not show cursor when mouse is down
return false;
}
}
void FLevelEditorViewportClient::SetupViewForRendering( FSceneViewFamily& ViewFamily, FSceneView& View )
{
FEditorViewportClient::SetupViewForRendering( ViewFamily, View );
ViewFamily.bDrawBaseInfo = bDrawBaseInfo;
ViewFamily.bCurrentlyBeingEdited = bCurrentlyEditingThroughMovementWidget;
// Don't use fading or color scaling while we're in light complexity mode, since it may change the colors!
if(!ViewFamily.EngineShowFlags.LightComplexity)
{
if(bEnableFading)
{
View.OverlayColor = FadeColor;
View.OverlayColor.A = FMath::Clamp(FadeAmount, 0.f, 1.f);
}
if(bEnableColorScaling)
{
View.ColorScale = FLinearColor(FVector3f{ ColorScale });
}
}
TSharedPtr<FDragDropOperation> DragOperation = FSlateApplication::Get().GetDragDroppingContent();
if (!(DragOperation.IsValid() && DragOperation->IsOfType<FBrushBuilderDragDropOp>()))
{
// Hide the builder brush when not in geometry mode
ViewFamily.EngineShowFlags.SetBuilderBrush(false);
}
// Update the listener.
if (bHasAudioFocus)
{
UpdateAudioListener(View);
}
}
void FLevelEditorViewportClient::DrawCanvas( FViewport& InViewport, FSceneView& View, FCanvas& Canvas )
{
// HUD for components visualizers
if (GUnrealEd != NULL && !IsInGameView())
{
GUnrealEd->DrawComponentVisualizersHUD(&InViewport, &View, &Canvas);
}
FEditorViewportClient::DrawCanvas(InViewport, View, Canvas);
// Testbed
FCanvasItemTestbed TestBed;
TestBed.Draw( Viewport, &Canvas );
DrawStaticLightingDebugInfo(&View, &Canvas);
}
/**
* Draw the texture streaming bounds.
*/
void FLevelEditorViewportClient::DrawTextureStreamingBounds(const FSceneView* View,FPrimitiveDrawInterface* PDI)
{
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
TArray<FAssetData> SelectedAssetData;
ContentBrowserModule.Get().GetSelectedAssets(SelectedAssetData);
TArray<const UTexture2D*> SelectedTextures;
for (auto AssetIt = SelectedAssetData.CreateConstIterator(); AssetIt; ++AssetIt)
{
if (AssetIt->IsAssetLoaded())
{
const UTexture2D* Texture = Cast<UTexture2D>(AssetIt->GetAsset());
if (Texture)
{
SelectedTextures.Add(Texture);
}
}
}
TArray<FBox> AssetBoxes;
if (IStreamingManager::Get().IsTextureStreamingEnabled())
{
for (const UTexture2D* Texture : SelectedTextures)
{
IStreamingManager::Get().GetTextureStreamingManager().GetObjectReferenceBounds(Texture, AssetBoxes);
}
}
for (const FBox& Box : AssetBoxes)
{
DrawWireBox(PDI, Box, FColorList::Yellow, SDPG_World);
}
}
/** Serialization. */
void FLevelEditorViewportClient::AddReferencedObjects( FReferenceCollector& Collector )
{
FEditorViewportClient::AddReferencedObjects( Collector );
for( TSet<FViewportHoverTarget>::TIterator It( FLevelEditorViewportClient::HoveredObjects ); It; ++It )
{
FViewportHoverTarget& CurHoverTarget = *It;
Collector.AddReferencedObject( CurHoverTarget.HoveredActor );
Collector.AddReferencedObject( CurHoverTarget.HoveredModel );
}
{
FSceneViewStateInterface* Ref = ViewState.GetReference();
if(Ref)
{
Ref->AddReferencedObjects(Collector);
}
}
}
/**
* Copies layout and camera settings from the specified viewport
*
* @param InViewport The viewport to copy settings from
*/
void FLevelEditorViewportClient::CopyLayoutFromViewport( const FLevelEditorViewportClient& InViewport )
{
SetViewLocation( InViewport.GetViewLocation() );
SetViewRotation( InViewport.GetViewRotation() );
ViewFOV = InViewport.ViewFOV;
ViewportType = InViewport.ViewportType;
SetOrthoZoom( InViewport.GetOrthoZoom() );
ActorLocks = InViewport.ActorLocks;
bAllowCinematicControl = InViewport.bAllowCinematicControl;
}
UWorld* FLevelEditorViewportClient::ConditionalSetWorld()
{
// Should set GWorld to the play world if we are simulating in the editor and not already in the play world (reentrant calls to this would cause the world to be the same)
if( bIsSimulateInEditorViewport && GEditor->PlayWorld && GEditor->PlayWorld != GWorld )
{
return SetPlayInEditorWorld( GEditor->PlayWorld );
}
// Returned world doesn't matter for this case
return NULL;
}
void FLevelEditorViewportClient::ConditionalRestoreWorld( UWorld* InWorld )
{
if( bIsSimulateInEditorViewport && InWorld )
{
// We should not already be in the world about to switch to an we should not be switching to the play world
check( GWorld != InWorld && InWorld != GEditor->PlayWorld );
RestoreEditorWorld( InWorld );
}
}
/** Updates any orthographic viewport movement to use the same location as this viewport */
void FLevelEditorViewportClient::UpdateLinkedOrthoViewports( bool bInvalidate )
{
// Only update if linked ortho movement is on, this viewport is orthographic, and is the current viewport being used.
if (GetDefault<ULevelEditorViewportSettings>()->bUseLinkedOrthographicViewports && IsOrtho() && GCurrentLevelEditingViewportClient == this)
{
int32 MaxFrames = -1;
int32 NextViewportIndexToDraw = INDEX_NONE;
// Search through all viewports for orthographic ones
for( int32 ViewportIndex = 0; ViewportIndex < GEditor->GetLevelViewportClients().Num(); ++ViewportIndex )
{
FLevelEditorViewportClient* Client = GEditor->GetLevelViewportClients()[ViewportIndex];
check(Client);
// Only update other orthographic viewports viewing the same scene
if( (Client != this) && Client->IsOrtho() && (Client->GetScene() == this->GetScene()) )
{
int32 Frames = Client->FramesSinceLastDraw;
Client->bNeedsLinkedRedraw = false;
Client->SetOrthoZoom( GetOrthoZoom() );
Client->SetViewLocation( GetViewLocation() );
if( Client->IsVisible() )
{
// Find the viewport which has the most number of frames since it was last rendered. We will render that next.
if( Frames > MaxFrames )
{
MaxFrames = Frames;
NextViewportIndexToDraw = ViewportIndex;
}
if( bInvalidate )
{
Client->Invalidate();
}
}
}
}
if( bInvalidate )
{
Invalidate();
}
if( NextViewportIndexToDraw != INDEX_NONE )
{
// Force this viewport to redraw.
GEditor->GetLevelViewportClients()[NextViewportIndexToDraw]->bNeedsLinkedRedraw = true;
}
}
}
//
// FLevelEditorViewportClient::GetScene
//
FLinearColor FLevelEditorViewportClient::GetBackgroundColor() const
{
return IsPerspective() ? GEditor->C_WireBackground : GEditor->C_OrthoBackground;
}
int32 FLevelEditorViewportClient::GetCameraSpeedSetting() const
{
return GetDefault<ULevelEditorViewportSettings>()->CameraSpeed;
}
void FLevelEditorViewportClient::SetCameraSpeedSetting(int32 SpeedSetting)
{
GetMutableDefault<ULevelEditorViewportSettings>()->CameraSpeed = SpeedSetting;
}
float FLevelEditorViewportClient::GetCameraSpeedScalar() const
{
return GetDefault<ULevelEditorViewportSettings>()->CameraSpeedScalar;
}
void FLevelEditorViewportClient::SetCameraSpeedScalar(float SpeedScalar)
{
GetMutableDefault<ULevelEditorViewportSettings>()->CameraSpeedScalar = SpeedScalar;
}
bool FLevelEditorViewportClient::OverrideHighResScreenshotCaptureRegion(FIntRect& OutCaptureRegion)
{
FSlateRect Rect;
if (CalculateEditorConstrainedViewRect(Rect, Viewport, GetDPIScale()))
{
FSlateRect InnerRect = Rect.InsetBy(FMargin(0.5f * SafePadding * Rect.GetSize().Size()));
OutCaptureRegion = FIntRect((int32)InnerRect.Left, (int32)InnerRect.Top, (int32)(InnerRect.Left + InnerRect.GetSize().X), (int32)(InnerRect.Top + InnerRect.GetSize().Y));
return true;
}
return false;
}
bool FLevelEditorViewportClient::BeginTransform(const FGizmoState& InState)
{
TrackingTransaction.End();
bDuplicateOnNextDrag = false;
bOnlyMovedPivot = false;
bNeedToRestoreComponentBeingMovedFlag = false;
MouseDeltaTracker->SetExternalMovement(true);
PreDragActorTransforms.Empty();
Widget->SetSnapEnabled(true);
const FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
ViewportInteraction->BeginGizmoManipulation(ElementsToManipulate, GetWidgetMode());
bHasBegunGizmoManipulation = true;
if (!bDuplicateActorsInProgress)
{
bNeedToRestoreComponentBeingMovedFlag = true;
TypedElementListObjectUtil::ForEachObject<AActor>(ElementsToManipulate, [this](AActor* InActor)
{
SetActorBeingMovedByEditor(InActor, true);
return true;
});
}
TrackingTransaction.TransCount++;
const FText Description = LOCTEXT("TransformTransaction", "Transform Elements");
TrackingTransaction.BeginPending(Description);
if (TrackingTransaction.IsActive() || TrackingTransaction.IsPending())
{
// Suspend actor/component modification during each delta step to avoid recording unnecessary overhead into the transaction buffer
GEditor->DisableDeltaModification(true);
}
GUnrealEd->ComponentVisManager.TrackingStarted(this);
return true;
}
bool FLevelEditorViewportClient::EndTransform(const FGizmoState& InState)
{
if (!bHasBegunGizmoManipulation)
{
return false;
}
bDuplicateOnNextDrag = false;
// here we check to see if anything of worth actually changed when ending our MouseMovement
// If the TransCount > 0 (we changed something of value) so we need to call PostEditMove() on stuff
// if we didn't change anything then don't call PostEditMove()
bool bDidAnythingActuallyChange = false;
if( TrackingTransaction.TransCount > 0 )
{
bDidAnythingActuallyChange = true;
TrackingTransaction.TransCount--;
}
// TODO ensure that the gizmo actually moved
const bool bDidMove = bDidAnythingActuallyChange;
const FTypedElementListConstRef ElementsToManipulate = GetElementsToManipulate();
if (bHasBegunGizmoManipulation)
{
auto GetManipType = [bDidMove]()
{
if (bDidMove)
{
return ETypedElementViewportInteractionGizmoManipulationType::Drag;
}
return ETypedElementViewportInteractionGizmoManipulationType::Click;
};
ViewportInteraction->EndGizmoManipulation(ElementsToManipulate, GetWidgetMode(), GetManipType());
bHasBegunGizmoManipulation = false;
}
if (bDidMove && !GUnrealEd->IsPivotMovedIndependently())
{
GUnrealEd->UpdatePivotLocationForSelection();
}
GUnrealEd->ComponentVisManager.TrackingStopped(this, bDidMove);
if (bNeedToRestoreComponentBeingMovedFlag)
{
TypedElementListObjectUtil::ForEachObject<AActor>(ElementsToManipulate, [this](AActor* InActor)
{
SetActorBeingMovedByEditor(InActor, false);
return true;
});
bNeedToRestoreComponentBeingMovedFlag = false;
}
// End the transaction here if one was started in StartTransaction()
if( TrackingTransaction.IsActive() || TrackingTransaction.IsPending() )
{
if (!HaveSelectedObjectsBeenChanged())
{
TrackingTransaction.Cancel();
}
else
{
TrackingTransaction.End();
}
// Restore actor/component delta modification
GEditor->DisableDeltaModification(false);
}
ModeTools->ActorMoveNotify();
if (bDidAnythingActuallyChange)
{
FScopedLevelDirtied LevelDirtyCallback;
LevelDirtyCallback.Request();
RedrawAllViewportsIntoThisScene();
}
PreDragActorTransforms.Empty();
MouseDeltaTracker->SetExternalMovement(false);
return true;
}
/**
* Static: Adds a hover effect to the specified object
*
* @param InHoverTarget The hoverable object to add the effect to
*/
void FLevelEditorViewportClient::AddHoverEffect( const FViewportHoverTarget& InHoverTarget )
{
AActor* ActorUnderCursor = InHoverTarget.HoveredActor;
UModel* ModelUnderCursor = InHoverTarget.HoveredModel;
if( ActorUnderCursor != nullptr )
{
for (UActorComponent* Component : ActorUnderCursor->GetComponents())
{
UPrimitiveComponent* PrimitiveComponent = Cast<UPrimitiveComponent>(Component);
if (PrimitiveComponent && PrimitiveComponent->IsRegistered())
{
PrimitiveComponent->PushHoveredToProxy( true );
}
}
}
else if (ModelUnderCursor != nullptr)
{
check( InHoverTarget.ModelSurfaceIndex != INDEX_NONE );
check( InHoverTarget.ModelSurfaceIndex < (uint32)ModelUnderCursor->Surfs.Num() );
FBspSurf& Surf = ModelUnderCursor->Surfs[ InHoverTarget.ModelSurfaceIndex ];
Surf.PolyFlags |= PF_Hovered;
}
}
/**
* Static: Removes a hover effect from the specified object
*
* @param InHoverTarget The hoverable object to remove the effect from
*/
void FLevelEditorViewportClient::RemoveHoverEffect( const FViewportHoverTarget& InHoverTarget )
{
AActor* CurHoveredActor = InHoverTarget.HoveredActor;
if( CurHoveredActor != nullptr )
{
for (UActorComponent* Component : CurHoveredActor->GetComponents())
{
UPrimitiveComponent* PrimitiveComponent = Cast<UPrimitiveComponent>(Component);
if (PrimitiveComponent && PrimitiveComponent->IsRegistered())
{
check(PrimitiveComponent->IsRegistered());
PrimitiveComponent->PushHoveredToProxy( false );
}
}
}
UModel* CurHoveredModel = InHoverTarget.HoveredModel;
if( CurHoveredModel != nullptr )
{
if( InHoverTarget.ModelSurfaceIndex != INDEX_NONE &&
(uint32)CurHoveredModel->Surfs.Num() >= InHoverTarget.ModelSurfaceIndex )
{
FBspSurf& Surf = CurHoveredModel->Surfs[ InHoverTarget.ModelSurfaceIndex ];
Surf.PolyFlags &= ~PF_Hovered;
}
}
}
/**
* Static: Clears viewport hover effects from any objects that currently have that
*/
void FLevelEditorViewportClient::ClearHoverFromObjects()
{
// Clear hover feedback for any actor's that were previously drawing a hover cue
if( HoveredObjects.Num() > 0 )
{
for( TSet<FViewportHoverTarget>::TIterator It( HoveredObjects ); It; ++It )
{
FViewportHoverTarget& CurHoverTarget = *It;
RemoveHoverEffect( CurHoverTarget );
}
HoveredObjects.Empty();
}
}
void FLevelEditorViewportClient::OnWidgetModeChanged(UE::Widget::EWidgetMode NewMode)
{
// We need this in case the current selection allowed the previous widget mode but not the new one or vice versa.
ResetElementsToManipulate();
}
void FLevelEditorViewportClient::OnEditorCleanse()
{
ClearHoverFromObjects();
FSceneViewStateInterface* ViewStateInterface = ViewState.GetReference();
if (ViewStateInterface)
{
// The view state can reference materials from the world being cleaned up. (Example post process materials)
ViewStateInterface->ClearMIDPool();
}
}
void FLevelEditorViewportClient::OnPreBeginPIE(const bool bIsSimulating)
{
// Called before PIE attempts to start, allowing the viewport to cancel processes, like dragging, that will block PIE from beginning
AbortTracking();
}
bool FLevelEditorViewportClient::GetSpriteCategoryVisibility( const FName& InSpriteCategory ) const
{
const int32 CategoryIndex = GEngine->GetSpriteCategoryIndex( InSpriteCategory );
check( CategoryIndex != INDEX_NONE && CategoryIndex < SpriteCategoryVisibility.Num() );
return SpriteCategoryVisibility[ CategoryIndex ];
}
bool FLevelEditorViewportClient::GetSpriteCategoryVisibility( int32 Index ) const
{
check( Index >= 0 && Index < SpriteCategoryVisibility.Num() );
return SpriteCategoryVisibility[ Index ];
}
void FLevelEditorViewportClient::SetSpriteCategoryVisibility( const FName& InSpriteCategory, bool bVisible )
{
const int32 CategoryIndex = GEngine->GetSpriteCategoryIndex( InSpriteCategory );
check( CategoryIndex != INDEX_NONE && CategoryIndex < SpriteCategoryVisibility.Num() );
SpriteCategoryVisibility[ CategoryIndex ] = bVisible;
}
void FLevelEditorViewportClient::SetSpriteCategoryVisibility( int32 Index, bool bVisible )
{
check( Index >= 0 && Index < SpriteCategoryVisibility.Num() );
SpriteCategoryVisibility[ Index ] = bVisible;
}
void FLevelEditorViewportClient::SetAllSpriteCategoryVisibility( bool bVisible )
{
SpriteCategoryVisibility.Init( bVisible, SpriteCategoryVisibility.Num() );
}
UWorld* FLevelEditorViewportClient::GetWorld() const
{
if (bIsSimulateInEditorViewport)
{
return GEditor->PlayWorld;
}
else if (World)
{
return World;
}
return FEditorViewportClient::GetWorld();
}
void FLevelEditorViewportClient::SetReferenceToWorldContext(FWorldContext& WorldContext)
{
WorldContext.AddRef(World);
}
void FLevelEditorViewportClient::RemoveReferenceToWorldContext(FWorldContext& WorldContext)
{
WorldContext.RemoveRef(World);
}
void FLevelEditorViewportClient::SetIsSimulateInEditorViewport( bool bInIsSimulateInEditorViewport )
{
bIsSimulateInEditorViewport = bInIsSimulateInEditorViewport;
}
bool FLevelEditorViewportClient::GetPivotForOrbit(FVector& Pivot) const
{
if (FEditorViewportClient::GetPivotForOrbit(Pivot))
{
return true;
}
FBox BoundingBox(ForceInit);
int32 NumValidComponents = 0;
for (FSelectionIterator It(GEditor->GetSelectedComponentIterator()); It; ++It)
{
// Allow orbiting on selected SceneComponents
USceneComponent* Component = Cast<USceneComponent>(*It);
if (Component && Component->IsRegistered())
{
TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(Component->GetClass());
FBox FocusOnSelectionBBox;
if (Visualizer && Visualizer->HasFocusOnSelectionBoundingBox(FocusOnSelectionBBox))
{
BoundingBox += FocusOnSelectionBBox;
}
else
{
// It's possible that it doesn't have a bounding box, so just take its position in that case
FBox ComponentBBox = Component->Bounds.GetBox();
if (ComponentBBox.GetVolume() != 0)
{
BoundingBox += ComponentBBox;
}
else
{
BoundingBox += Component->GetComponentLocation();
}
}
++NumValidComponents;
}
}
if (NumValidComponents > 0)
{
Pivot = BoundingBox.GetCenter();
return true;
}
// Use the center of the bounding box of the current selected actors, if any, as the pivot point for orbiting the camera
int32 NumSelectedActors = 0;
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
if (AActor* Actor = Cast<AActor>(*It))
{
TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents(Actor);
for(int32 ComponentIndex = 0; ComponentIndex < PrimitiveComponents.Num(); ++ComponentIndex)
{
UPrimitiveComponent* PrimitiveComponent = PrimitiveComponents[ComponentIndex];
if (PrimitiveComponent->IsRegistered() && !PrimitiveComponent->GetIgnoreBoundsForEditorFocus())
{
TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(PrimitiveComponent->GetClass());
FBox FocusOnSelectionBBox;
if (Visualizer && Visualizer->HasFocusOnSelectionBoundingBox(FocusOnSelectionBBox))
{
BoundingBox += FocusOnSelectionBBox;
}
else
{
BoundingBox += PrimitiveComponent->Bounds.GetBox();
}
++NumSelectedActors;
}
}
}
}
if (NumSelectedActors > 0)
{
Pivot = BoundingBox.GetCenter();
return true;
}
return false;
}
void FLevelEditorViewportClient::SetEditingThroughMovementWidget()
{
bCurrentlyEditingThroughMovementWidget = true;
}
#undef LOCTEXT_NAMESPACE