396 lines
15 KiB
C++
396 lines
15 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DragTool_BoxSelect.h"
|
|
#include "Components/PrimitiveComponent.h"
|
|
#include "CanvasItem.h"
|
|
#include "Settings/LevelEditorViewportSettings.h"
|
|
#include "GameFramework/Volume.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "EngineUtils.h"
|
|
#include "EditorModeManager.h"
|
|
#include "EditorModes.h"
|
|
#include "ActorEditorUtils.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Engine/LevelStreaming.h"
|
|
#include "CanvasTypes.h"
|
|
#include "Subsystems/BrushEditingSubsystem.h"
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FDragTool_BoxSelect
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Starts a mouse drag behavior. The start location is snapped to the editor constraints if bUseSnapping is true.
|
|
*
|
|
* @param InViewportClient The viewport client in which the drag event occurred.
|
|
* @param InStart Where the mouse was when the drag started.
|
|
*/
|
|
void FDragTool_ActorBoxSelect::StartDrag(FEditorViewportClient* InViewportClient, const FVector& InStart, const FVector2D& InStartScreen)
|
|
{
|
|
FDragTool::StartDrag(InViewportClient, InStart, InStartScreen);
|
|
|
|
FIntPoint MousePos;
|
|
InViewportClient->Viewport->GetMousePos(MousePos);
|
|
|
|
Start = FVector(MousePos);
|
|
End = EndWk = Start;
|
|
|
|
FLevelEditorViewportClient::ClearHoverFromObjects();
|
|
|
|
// Create a list of bsp models to check for intersection with the box
|
|
ModelsToCheck.Reset();
|
|
// Do not select BSP if its not visible
|
|
if (InViewportClient->EngineShowFlags.BSP)
|
|
{
|
|
UWorld* World = InViewportClient->GetWorld();
|
|
check(World);
|
|
// Add the persistent level always
|
|
ModelsToCheck.Add(World->PersistentLevel->Model);
|
|
// Add all streaming level models
|
|
for (ULevelStreaming* StreamingLevel: World->GetStreamingLevels())
|
|
{
|
|
// Only add streaming level models if the level is visible
|
|
if (StreamingLevel && StreamingLevel->GetShouldBeVisibleInEditor())
|
|
{
|
|
if (ULevel* Level = StreamingLevel->GetLoadedLevel())
|
|
{
|
|
ModelsToCheck.Add(Level->Model);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDragTool_ActorBoxSelect::AddDelta(const FVector& InDelta)
|
|
{
|
|
FDragTool::AddDelta(InDelta);
|
|
|
|
FIntPoint MousePos;
|
|
LevelViewportClient->Viewport->GetMousePos(MousePos);
|
|
|
|
End = FVector(MousePos);
|
|
EndWk = End;
|
|
|
|
const bool bUseHoverFeedback = GEditor != NULL && GetDefault<ULevelEditorViewportSettings>()->bEnableViewportHoverFeedback;
|
|
|
|
if (bUseHoverFeedback)
|
|
{
|
|
const bool bStrictDragSelection = GetDefault<ULevelEditorViewportSettings>()->bStrictBoxSelection;
|
|
|
|
// If we are using over feedback calculate a new box from the one being dragged
|
|
FBox SelBBox;
|
|
CalculateBox(SelBBox);
|
|
|
|
// Check every actor to see if it intersects the frustum created by the box
|
|
// If it does, the actor will be selected and should be given a hover cue
|
|
bool bSelectionChanged = false;
|
|
UWorld* IteratorWorld = GWorld;
|
|
for (FActorIterator It(IteratorWorld); It; ++It)
|
|
{
|
|
AActor& Actor = **It;
|
|
const bool bActorHitByBox = IntersectsBox(Actor, SelBBox, bStrictDragSelection);
|
|
|
|
if (bActorHitByBox)
|
|
{
|
|
// Apply a hover effect to any actor that will be selected
|
|
AddHoverEffect(Actor);
|
|
}
|
|
else
|
|
{
|
|
// Remove any hover effect on this actor as it no longer will be selected by the current box
|
|
RemoveHoverEffect(Actor);
|
|
}
|
|
}
|
|
|
|
// Check each model to see if it will be selected
|
|
for (int32 ModelIndex = 0; ModelIndex < ModelsToCheck.Num(); ++ModelIndex)
|
|
{
|
|
UModel& Model = *ModelsToCheck[ModelIndex];
|
|
for (int32 NodeIndex = 0; NodeIndex < Model.Nodes.Num(); NodeIndex++)
|
|
{
|
|
if (IntersectsBox(Model, NodeIndex, SelBBox, bStrictDragSelection))
|
|
{
|
|
// Apply a hover effect to any bsp surface that will be selected
|
|
AddHoverEffect(Model, Model.Nodes[NodeIndex].iSurf);
|
|
}
|
|
else
|
|
{
|
|
// Remove any hover effect on this bsp surface as it no longer will be selected by the current box
|
|
RemoveHoverEffect(Model, Model.Nodes[NodeIndex].iSurf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Ends a mouse drag behavior (the user has let go of the mouse button).
|
|
*/
|
|
void FDragTool_ActorBoxSelect::EndDrag()
|
|
{
|
|
UBrushEditingSubsystem* BrushSubsystem = GEditor->GetEditorSubsystem<UBrushEditingSubsystem>();
|
|
const bool bGeometryMode = BrushSubsystem ? BrushSubsystem->IsGeometryEditorModeActive() : false;
|
|
|
|
FScopedTransaction Transaction(NSLOCTEXT("ActorFrustumSelect", "MarqueeSelectTransation", "Marquee Select"));
|
|
|
|
bool bShouldSelect = true;
|
|
FBox SelBBox;
|
|
CalculateBox(SelBBox);
|
|
|
|
if (bControlDown)
|
|
{
|
|
// If control is down remove from selection
|
|
bShouldSelect = false;
|
|
}
|
|
else if (!bShiftDown)
|
|
{
|
|
// If the user is selecting, but isn't hold down SHIFT, remove all current selections.
|
|
ModeTools->SelectNone();
|
|
}
|
|
|
|
// Let the editor mode try to handle the box selection.
|
|
const bool bEditorModeHandledBoxSelection = ModeTools->BoxSelect(SelBBox, bLeftMouseButtonDown);
|
|
|
|
// Let the component visualizers try to handle the selection.
|
|
const bool bComponentVisHandledSelection = !bEditorModeHandledBoxSelection && GUnrealEd->ComponentVisManager.HandleBoxSelect(SelBBox, LevelViewportClient, LevelViewportClient->Viewport);
|
|
|
|
// If the edit mode didn't handle the selection, try normal actor box selection.
|
|
if (!bEditorModeHandledBoxSelection && !bComponentVisHandledSelection)
|
|
{
|
|
const bool bStrictDragSelection = GetDefault<ULevelEditorViewportSettings>()->bStrictBoxSelection;
|
|
|
|
if (bControlDown)
|
|
{
|
|
// If control is down remove from selection
|
|
bShouldSelect = false;
|
|
}
|
|
else if (!bShiftDown)
|
|
{
|
|
// If the user is selecting, but isn't hold down SHIFT, remove all current selections.
|
|
GEditor->SelectNone(true, true);
|
|
}
|
|
|
|
// Select all actors that are within the selection box area. Be aware that certain modes do special processing below.
|
|
bool bSelectionChanged = false;
|
|
UWorld* IteratorWorld = GWorld;
|
|
const TArray<FName>& HiddenLayers = LevelViewportClient->ViewHiddenLayers;
|
|
for (FActorIterator It(IteratorWorld); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
|
|
bool bActorIsVisible = true;
|
|
for (auto Layer: Actor->Layers)
|
|
{
|
|
// Check the actor isn't in one of the layers hidden from this viewport.
|
|
if (HiddenLayers.Contains(Layer))
|
|
{
|
|
bActorIsVisible = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Select the actor if we need to
|
|
if (bActorIsVisible && IntersectsBox(*Actor, SelBBox, bStrictDragSelection))
|
|
{
|
|
GEditor->SelectActor(Actor, bShouldSelect, false);
|
|
bSelectionChanged = true;
|
|
}
|
|
}
|
|
|
|
// Check every model to see if its BSP surfaces should be selected
|
|
for (int32 ModelIndex = 0; ModelIndex < ModelsToCheck.Num(); ++ModelIndex)
|
|
{
|
|
UModel& Model = *ModelsToCheck[ModelIndex];
|
|
// Check every node in the model
|
|
for (int32 NodeIndex = 0; NodeIndex < Model.Nodes.Num(); NodeIndex++)
|
|
{
|
|
if (IntersectsBox(Model, NodeIndex, SelBBox, bStrictDragSelection))
|
|
{
|
|
// If the node intersected the frustum select the corresponding surface
|
|
GEditor->SelectBSPSurf(&Model, Model.Nodes[NodeIndex].iSurf, bShouldSelect, false);
|
|
bSelectionChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bSelectionChanged)
|
|
{
|
|
// If any selections were made. Notify that now.
|
|
GEditor->NoteSelectionChange();
|
|
}
|
|
}
|
|
|
|
// Clear any hovered objects that might have been created while dragging
|
|
FLevelEditorViewportClient::ClearHoverFromObjects();
|
|
|
|
// Clean up.
|
|
FDragTool::EndDrag();
|
|
}
|
|
|
|
void FDragTool_ActorBoxSelect::Render(const FSceneView* View, FCanvas* Canvas)
|
|
{
|
|
FCanvasBoxItem BoxItem(FVector2D(Start.X, Start.Y) / Canvas->GetDPIScale(), FVector2D(End.X - Start.X, End.Y - Start.Y) / Canvas->GetDPIScale());
|
|
BoxItem.SetColor(FLinearColor::White);
|
|
Canvas->DrawItem(BoxItem);
|
|
}
|
|
|
|
void FDragTool_ActorBoxSelect::CalculateBox(FBox& OutBox)
|
|
{
|
|
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
|
|
LevelViewportClient->Viewport,
|
|
LevelViewportClient->GetScene(),
|
|
LevelViewportClient->EngineShowFlags)
|
|
.SetRealtimeUpdate(LevelViewportClient->IsRealtime()));
|
|
|
|
FSceneView* View = LevelViewportClient->CalcSceneView(&ViewFamily);
|
|
|
|
FVector4 StartScreenPos = View->PixelToScreen(Start.X, Start.Y, 0);
|
|
FVector4 EndScreenPos = View->PixelToScreen(End.X, End.Y, 0);
|
|
|
|
FVector TransformedStart = View->ScreenToWorld(View->PixelToScreen(Start.X, Start.Y, 0.5f));
|
|
FVector TransformedEnd = View->ScreenToWorld(View->PixelToScreen(End.X, End.Y, 0.5f));
|
|
|
|
// Create a bounding box based on the start/end points (normalizes the points).
|
|
OutBox.Init();
|
|
OutBox += TransformedStart;
|
|
OutBox += TransformedEnd;
|
|
|
|
switch (LevelViewportClient->ViewportType)
|
|
{
|
|
case LVT_OrthoXY:
|
|
case LVT_OrthoNegativeXY:
|
|
OutBox.Min.Z = -WORLD_MAX;
|
|
OutBox.Max.Z = WORLD_MAX;
|
|
break;
|
|
case LVT_OrthoXZ:
|
|
case LVT_OrthoNegativeXZ:
|
|
OutBox.Min.Y = -WORLD_MAX;
|
|
OutBox.Max.Y = WORLD_MAX;
|
|
break;
|
|
case LVT_OrthoYZ:
|
|
case LVT_OrthoNegativeYZ:
|
|
OutBox.Min.X = -WORLD_MAX;
|
|
OutBox.Max.X = WORLD_MAX;
|
|
break;
|
|
case LVT_OrthoFreelook:
|
|
case LVT_Perspective:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the passed in Actor intersects with the provided box
|
|
*
|
|
* @param InActor The actor to check
|
|
* @param InBox The box to check against
|
|
* @param bUseStrictSelection true if the actor must be entirely within the frustum
|
|
*/
|
|
bool FDragTool_ActorBoxSelect::IntersectsBox(AActor& InActor, const FBox& InBox, bool bUseStrictSelection)
|
|
{
|
|
bool bActorHitByBox = false;
|
|
|
|
UBrushEditingSubsystem* BrushSubsystem = GEditor->GetEditorSubsystem<UBrushEditingSubsystem>();
|
|
const bool bGeometryMode = BrushSubsystem ? BrushSubsystem->IsGeometryEditorModeActive() : false;
|
|
|
|
// Check for special cases (like certain show flags that might hide an actor)
|
|
bool bActorIsHiddenByShowFlags = false;
|
|
|
|
// Check to see that volume actors are visible in the viewport
|
|
if (InActor.IsA(AVolume::StaticClass()) && (!LevelViewportClient->EngineShowFlags.Volumes || !LevelViewportClient->IsVolumeVisibleInViewport(InActor)))
|
|
{
|
|
bActorIsHiddenByShowFlags = true;
|
|
}
|
|
|
|
// Never drag-select hidden actors or builder brushes. Also, don't consider actors which haven't been recently rendered.
|
|
//@TODO - replace with proper check for if this object was visible last frame. This is viewport dependent and viewports can use different concepts of time
|
|
// depending on if they are in "realtime" mode or not. See FLevelEditorViewportClient::Draw for the differing concepts of time.
|
|
const bool bActorRecentlyRendered = true; // Actor->LastRenderTime > ( GWorld->GetTimeSeconds() - 1.0f );
|
|
if (!bActorIsHiddenByShowFlags && !InActor.IsHiddenEd() && !FActorEditorUtils::IsABuilderBrush(&InActor) && bActorRecentlyRendered)
|
|
{
|
|
// Iterate over all actor components, selecting out primitive components
|
|
for (UActorComponent* Component: InActor.GetComponents())
|
|
{
|
|
UPrimitiveComponent* PrimitiveComponent = Cast<UPrimitiveComponent>(Component);
|
|
if (PrimitiveComponent && PrimitiveComponent->IsRegistered() && PrimitiveComponent->IsVisibleInEditor())
|
|
{
|
|
if (PrimitiveComponent->ComponentIsTouchingSelectionBox(InBox, LevelViewportClient->EngineShowFlags, bGeometryMode, bUseStrictSelection))
|
|
{
|
|
bActorHitByBox = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bActorHitByBox;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the provided BSP node intersects with the provided frustum
|
|
*
|
|
* @param InModel The model containing BSP nodes to check
|
|
* @param NodeIndex The index to a BSP node in the model. This node is used for the bounds check.
|
|
* @param InFrustum The frustum to check against.
|
|
* @param bUseStrictSelection true if the node must be entirely within the frustum
|
|
*/
|
|
bool FDragTool_ActorBoxSelect::IntersectsBox(const UModel& InModel, int32 NodeIndex, const FBox& InBox, bool bUseStrictSelection) const
|
|
{
|
|
FBox NodeBB;
|
|
InModel.GetNodeBoundingBox(InModel.Nodes[NodeIndex], NodeBB);
|
|
|
|
bool bFullyContained = false;
|
|
bool bIntersects = false;
|
|
if (!bUseStrictSelection)
|
|
{
|
|
bIntersects = InBox.Intersect(NodeBB);
|
|
}
|
|
else
|
|
{
|
|
bIntersects = InBox.IsInside(NodeBB.Max) && InBox.IsInside(NodeBB.Min);
|
|
}
|
|
|
|
return bIntersects;
|
|
}
|
|
|
|
/** Adds a hover effect to the passed in actor */
|
|
void FDragTool_ActorBoxSelect::AddHoverEffect(AActor& InActor)
|
|
{
|
|
FViewportHoverTarget HoverTarget(&InActor);
|
|
FLevelEditorViewportClient::AddHoverEffect(HoverTarget);
|
|
FLevelEditorViewportClient::HoveredObjects.Add(HoverTarget);
|
|
}
|
|
|
|
/** Removes a hover effect from the passed in actor */
|
|
void FDragTool_ActorBoxSelect::RemoveHoverEffect(AActor& InActor)
|
|
{
|
|
FViewportHoverTarget HoverTarget(&InActor);
|
|
FSetElementId Id = FLevelEditorViewportClient::HoveredObjects.FindId(HoverTarget);
|
|
if (Id.IsValidId())
|
|
{
|
|
FLevelEditorViewportClient::RemoveHoverEffect(HoverTarget);
|
|
FLevelEditorViewportClient::HoveredObjects.Remove(Id);
|
|
}
|
|
}
|
|
|
|
/** Adds a hover effect to the passed in bsp surface */
|
|
void FDragTool_ActorBoxSelect::AddHoverEffect(UModel& InModel, int32 SurfIndex)
|
|
{
|
|
FViewportHoverTarget HoverTarget(&InModel, SurfIndex);
|
|
FLevelEditorViewportClient::AddHoverEffect(HoverTarget);
|
|
FLevelEditorViewportClient::HoveredObjects.Add(HoverTarget);
|
|
}
|
|
|
|
/** Removes a hover effect from the passed in bsp surface */
|
|
void FDragTool_ActorBoxSelect::RemoveHoverEffect(UModel& InModel, int32 SurfIndex)
|
|
{
|
|
FViewportHoverTarget HoverTarget(&InModel, SurfIndex);
|
|
FSetElementId Id = FLevelEditorViewportClient::HoveredObjects.FindId(HoverTarget);
|
|
if (Id.IsValidId())
|
|
{
|
|
FLevelEditorViewportClient::RemoveHoverEffect(HoverTarget);
|
|
FLevelEditorViewportClient::HoveredObjects.Remove(Id);
|
|
}
|
|
}
|