EM_Task/UnrealEd/Private/DragTool_BoxSelect.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

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);
}
}