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

503 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SnappingUtils.h"
#include "Model.h"
#include "Modules/ModuleManager.h"
#include "GameFramework/Actor.h"
#include "Settings/LevelEditorViewportSettings.h"
#include "Editor/GroupActor.h"
#include "GameFramework/PhysicsVolume.h"
#include "Engine/PostProcessVolume.h"
#include "GameFramework/WorldSettings.h"
#include "EngineUtils.h"
#include "LevelEditorViewport.h"
#include "Engine/Selection.h"
#include "EditorModeManager.h"
#include "ScopedTransaction.h"
#include "EdMode.h"
#include "LevelEditor.h"
#include "LevelEditorActions.h"
#include "VertexSnapping.h"
#include "ISnappingPolicy.h"
#include "ViewportSnappingModule.h"
#include "ActorGroupingUtils.h"
//////////////////////////////////////////////////////////////////////////
// FEditorViewportSnapping
class FEditorViewportSnapping : public ISnappingPolicy
{
public:
// FEditorViewportSnapping interface
virtual void SnapScale(FVector& Point, const FVector& GridBase) override;
virtual void SnapPointToGrid(FVector& Point, const FVector& GridBase) override;
virtual void SnapRotatorToGrid(FRotator& Rotation) override;
virtual void ClearSnappingHelpers(bool bClearImmediately = false) override;
virtual void DrawSnappingHelpers(const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
// End of FEditorViewportSnapping interface
bool IsSnapToGridEnabled();
bool IsSnapRotationEnabled();
bool IsSnapScaleEnabled();
/**
* @return true if snapping to vertices is enabled
*/
bool IsSnapToVertexEnabled(bool bIsPivot = false);
/**
* @return true if snapping actors to other actors is enabled
*/
bool IsSnapToActorEnabled();
/** Set user setting for actor snap. */
void EnableActorSnap(bool bEnable);
/** Access user setting for distance. Fractional 0.0->100.0 */
float GetActorSnapDistance(bool bScalar = false);
/** Set user setting for distance. Fractional 0.0->100.0 */
void SetActorSnapDistance(float Distance);
/**
* Attempts to snap the selected actors to the nearest other actor
*
* @param DragDelta The current world space drag amount
* @param ViewportClient The viewport client the user is dragging in
*/
bool SnapActorsToNearestActor( FVector& DragDelta, FLevelEditorViewportClient* ViewportClient );
/**
* Snaps actors to the nearest vertex on another actor
*
* @param DragDelta The current world space drag amount that will be modified to account for snapping to a vertex
* @param ViewportClient The viewport client the user is dragging in
* @return true if anything was snapped
*/
bool SnapDraggedActorsToNearestVertex( FVector& DragDelta, FLevelEditorViewportClient* ViewportClient );
/**
* Snaps a delta drag movement to the nearest vertex
*
* @param BaseLocation Location that should be snapped before any drag is applied
* @param DragDelta Delta drag movement that should be snapped. This value will be updated such that BaseLocation+DragDelta is the nearest snapped verted
* @param ViewportClient The viewport client being dragged in.
* @return true if anything was snapped
*/
bool SnapDragLocationToNearestVertex( const FVector& BaseLocation, FVector& DragDelta, FLevelEditorViewportClient* ViewportClient, bool bIsPivot = false );
/**
* Snaps a location to the nearest vertex
*
* @param Location The location to snap
* @param MouseLocation The current 2d mouse location. Vertices closer to the mouse are favored
* @param ViewportClient The viewport client being used
* @param OutVertexNormal The normal at the closest vertex
* @return true if anything was snapped
*/
bool SnapLocationToNearestVertex( FVector& Location, const FVector2D& MouseLocation, FLevelEditorViewportClient* ViewportClient, FVector& OutVertexNormal, bool bDrawVertHelpers );
bool SnapToBSPVertex( FVector& Location, FVector GridBase, FRotator& Rotation );
private:
/** Vertex snapping implementation */
FVertexSnappingImpl VertexSnappingImpl;
};
//////////////////////////////////////////////////////////////////////////
// FEditorViewportSnapping
bool FEditorViewportSnapping::IsSnapToGridEnabled()
{
return GetDefault<ULevelEditorViewportSettings>()->GridEnabled && !IsSnapToVertexEnabled();
}
bool FEditorViewportSnapping::IsSnapRotationEnabled()
{
// Ask Current Editor Mode if Rotation Snap is enabled
return GLevelEditorModeTools().IsSnapRotationEnabled();
}
bool FEditorViewportSnapping::IsSnapScaleEnabled()
{
return GetDefault<ULevelEditorViewportSettings>()->SnapScaleEnabled;
}
bool FEditorViewportSnapping::IsSnapToVertexEnabled(bool bIsPivot)
{
if( GetDefault<ULevelEditorViewportSettings>()->bSnapVertices )
{
return true;
}
else if( GCurrentLevelEditingViewportClient )
{
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>( TEXT("LevelEditor") );
TSharedPtr<FUICommandInfo> Command = bIsPivot ? LevelEditor.GetLevelEditorCommands().HoldToEnablePivotVertexSnapping : LevelEditor.GetLevelEditorCommands().HoldToEnableVertexSnapping;
return GCurrentLevelEditingViewportClient->IsCommandChordPressed(Command);
}
else
{
return false;
}
}
bool FEditorViewportSnapping::IsSnapToActorEnabled()
{
return GetDefault<ULevelEditorViewportSettings>()->bEnableActorSnap && !IsSnapToVertexEnabled();
}
void FEditorViewportSnapping::EnableActorSnap(bool bEnable)
{
ULevelEditorViewportSettings* ViewportSettings = GetMutableDefault<ULevelEditorViewportSettings>();
ViewportSettings->bEnableActorSnap = bEnable;
ViewportSettings->PostEditChange();
}
float FEditorViewportSnapping::GetActorSnapDistance(bool bScalar)
{
ULevelEditorViewportSettings* ViewportSettings = GetMutableDefault<ULevelEditorViewportSettings>();
// If the user has purposefully exceeded the maximum scale, convert this to the range so that it can be more easily adjusted in the editor
if (ViewportSettings->ActorSnapScale > 1.0f)
{
ViewportSettings->ActorSnapDistance *= ViewportSettings->ActorSnapScale;
ViewportSettings->ActorSnapScale = 1.0f;
ViewportSettings->PostEditChange();
}
if (bScalar)
{
// Clamp to within range (just so slider looks correct)
return FMath::Clamp(ViewportSettings->ActorSnapScale, 0.0f, 1.0f);
}
// Multiply by the max distance allowed to convert to range
return FMath::Max(0.0f, ViewportSettings->ActorSnapScale) * ViewportSettings->ActorSnapDistance;
}
void FEditorViewportSnapping::SetActorSnapDistance(float Distance)
{
ULevelEditorViewportSettings* ViewportSettings = GetMutableDefault<ULevelEditorViewportSettings>();
ViewportSettings->ActorSnapScale = Distance;
ViewportSettings->PostEditChange();
}
bool FEditorViewportSnapping::SnapActorsToNearestActor( FVector& Drag, FLevelEditorViewportClient* ViewportClient )
{
FEditorModeTools& Tools = GLevelEditorModeTools();
// Does the user have actor snapping enabled?
bool bSnapped = false;
if ( IsSnapToActorEnabled() )
{
// Are there selected actors?
USelection* Selection = GEditor->GetSelectedActors();
if ( Selection->Num() > 0 )
{
// Nearest results
const AActor* BestActor = NULL;
FVector BestPoint = FVector::ZeroVector;
double BestSqrdDist = 0.0f;
// Find the nearest actor to the pivot point that isn't part of the selection
const FVector PivotLocation = Tools.PivotLocation;
for (FActorIterator It(ViewportClient->GetWorld()); It; ++It) // Actor iterator :( [Note: Also, can't use BoxOverlapMulti to as the pivot may lie outside the bounds of the actor!]
{
AActor* Actor = static_cast<AActor*>( *It );
checkSlow( Actor->IsA(AActor::StaticClass()) );
// Make sure this isn't an invalid actor type or one of the selected actors
const FString tmp = Actor->GetActorLabel();
if ( !Actor->IsA(AWorldSettings::StaticClass())
&& !Actor->IsA(APhysicsVolume::StaticClass())
&& !Actor->IsA(APostProcessVolume::StaticClass())
&& !Selection->IsSelected( Actor ) )
{
// Group Actors don't appear in the selected actors list!
if (UActorGroupingUtils::IsGroupingActive())
{
// Valid snaps: locked groups (not self or actors within locked groups), actors within unlocked groups (not the group itself), other actors
const AGroupActor* GroupActor = Cast<AGroupActor>( Actor ); // AGroupActor::GetRootForActor( Actor );
if ( GroupActor && ( !GroupActor->IsLocked() || GroupActor->HasSelectedActors() ) )
{
continue;
}
}
// Is this the nearest actor to the pivot?
const FVector Point = Actor->GetActorLocation();
const double SqrdDist = FVector::DistSquared( PivotLocation, Point );
if ( BestActor == nullptr || SqrdDist < BestSqrdDist )
{
BestActor = Actor;
BestPoint = Point;
BestSqrdDist = SqrdDist;
}
}
}
// Did we find an actor?
const FString tmp = BestActor ? BestActor->GetActorLabel() : TEXT( "None" );
if ( BestActor )
{
// Are we within the threshold or exitting it?
const float Dist = GetActorSnapDistance();
if ( BestSqrdDist < FMath::Square( Dist ) )
{
bSnapped = true;
// Are we no already snapped, or is it different to our current location
if ( !Tools.SnappedActor || !Tools.CachedLocation.Equals( BestPoint ) )
{
// Calculate the delta between the snapped location and the current pivot and apply to all the selected actors
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "SnapActorsToActor", "Snap Actors To Actor") );
const FVector PivotDelta = ( BestPoint - PivotLocation );
ViewportClient->ApplyDeltaToActors( PivotDelta, FRotator::ZeroRotator, FVector::ZeroVector );
Tools.SetPivotLocation( BestPoint, false ); // Overwrite the location for next time we check
Drag = FVector::ZeroVector; // Reset the drag so the pivot doesn't jump
}
}
else if ( Tools.SnappedActor && !Tools.CachedLocation.Equals( PivotLocation ) )
{
const FVector PivotDelta = ( PivotLocation - BestPoint );
ViewportClient->ApplyDeltaToActors( PivotDelta, FRotator::ZeroRotator, FVector::ZeroVector );
//GUnrealEd->UpdatePivotLocationForSelection(); // Calling this ends up forcing the pivot back inside the threshold?!
Tools.SetPivotLocation( PivotLocation, false ); // Overwrite the location for next time we check
Drag = FVector::ZeroVector; // Reset the drag so the pivot doesn't jump
}
}
}
}
Tools.SnappedActor = bSnapped;
return bSnapped; // Whether or not the selection is snapped in place
}
void FEditorViewportSnapping::SnapPointToGrid(FVector& Point, const FVector& GridBase)
{
if( IsSnapToGridEnabled() )
{
Point = (Point - GridBase).GridSnap( GEditor->GetGridSize() ) + GridBase;
}
}
void FEditorViewportSnapping::SnapRotatorToGrid(FRotator& Rotation)
{
if( IsSnapRotationEnabled() )
{
if (GLevelEditorModeTools().SnapRotatorToGridOverride(Rotation))
{
return;
}
Rotation = Rotation.GridSnap( GEditor->GetRotGridSize() );
}
}
void FEditorViewportSnapping::SnapScale(FVector& Point, const FVector& GridBase)
{
if( IsSnapScaleEnabled() )
{
if( GEditor->UsePercentageBasedScaling() )
{
Point = (Point - GridBase).GridSnap( GEditor->GetGridSize() ) + GridBase;
}
else
{
if (GetDefault<ULevelEditorViewportSettings>()->PreserveNonUniformScale)
{
// when using 'auto-precision', we take the max component & snap its scale, then proportionally scale the other components
double MaxComponent = Point.GetAbsMax();
if(MaxComponent == 0.0f)
{
MaxComponent = 1.0f;
}
const double SnappedMaxComponent = FMath::GridSnap(MaxComponent, GEditor->GetScaleGridSize());
Point = Point * (SnappedMaxComponent / MaxComponent);
}
else
{
Point = Point.GridSnap( GEditor->GetScaleGridSize() );
}
}
}
}
bool FEditorViewportSnapping::SnapToBSPVertex(FVector& Location, FVector GridBase, FRotator& Rotation)
{
bool bSnapped = false;
SnapRotatorToGrid( Rotation );
if( IsSnapToVertexEnabled() )
{
FVector3f SrcPoint = (FVector3f)Location;
FVector3f DestPoint;
int32 Temp;
if( GWorld->GetModel()->FindNearestVertex(SrcPoint, DestPoint, GetDefault<ULevelEditorViewportSettings>()->SnapDistance, Temp ) >= 0.0)
{
Location = (FVector)DestPoint;
bSnapped = true;
}
}
if( !bSnapped )
{
SnapPointToGrid( Location, GridBase );
}
return bSnapped;
}
void FEditorViewportSnapping::ClearSnappingHelpers( bool bClearImmediately )
{
VertexSnappingImpl.ClearSnappingHelpers(bClearImmediately);
}
void FEditorViewportSnapping::DrawSnappingHelpers(const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
VertexSnappingImpl.DrawSnappingHelpers( View, PDI );
}
bool FEditorViewportSnapping::SnapLocationToNearestVertex( FVector& Location, const FVector2D& MouseLocation, FLevelEditorViewportClient* ViewportClient, FVector& OutVertexNormal, bool bDrawVertHelpers )
{
bool bSnapped = false;
if( IsSnapToVertexEnabled() )
{
bSnapped = VertexSnappingImpl.SnapLocationToNearestVertex( Location, MouseLocation, ViewportClient, OutVertexNormal, bDrawVertHelpers );
}
else
{
OutVertexNormal = FVector(ForceInitToZero);
}
return bSnapped;
}
bool FEditorViewportSnapping::SnapDraggedActorsToNearestVertex( FVector& DragDelta, FLevelEditorViewportClient* ViewportClient )
{
bool bSnapped = false;
if( IsSnapToVertexEnabled() && !DragDelta.IsNearlyZero() )
{
bSnapped = VertexSnappingImpl.SnapDraggedActorsToNearestVertex( DragDelta, ViewportClient );
}
return bSnapped;
}
bool FEditorViewportSnapping::SnapDragLocationToNearestVertex( const FVector& BaseLocation, FVector& DragDelta, FLevelEditorViewportClient* ViewportClient, bool bIsPivot )
{
bool bSnapped = false;
if( IsSnapToVertexEnabled(bIsPivot) && !DragDelta.IsNearlyZero() )
{
bSnapped = VertexSnappingImpl.SnapDragLocationToNearestVertex( BaseLocation, DragDelta, ViewportClient );
}
return bSnapped;
}
//////////////////////////////////////////////////////////////////////////
// FSnappingUtils
TSharedPtr<FEditorViewportSnapping> FSnappingUtils::EditorViewportSnapper;
bool FSnappingUtils::IsSnapToGridEnabled()
{
return EditorViewportSnapper.IsValid() && EditorViewportSnapper->IsSnapToGridEnabled();
}
bool FSnappingUtils::IsRotationSnapEnabled()
{
return EditorViewportSnapper.IsValid() && EditorViewportSnapper->IsSnapRotationEnabled();
}
bool FSnappingUtils::IsScaleSnapEnabled()
{
return EditorViewportSnapper.IsValid() && EditorViewportSnapper->IsSnapScaleEnabled();
}
bool FSnappingUtils::IsSnapToActorEnabled()
{
return EditorViewportSnapper.IsValid() && EditorViewportSnapper->IsSnapToActorEnabled();
}
void FSnappingUtils::EnableActorSnap(bool bEnable)
{
if (EditorViewportSnapper.IsValid())
{
EditorViewportSnapper->EnableActorSnap(bEnable);
}
}
float FSnappingUtils::GetActorSnapDistance(bool bScalar)
{
return EditorViewportSnapper.IsValid() ? EditorViewportSnapper->GetActorSnapDistance(bScalar) : 0.0f;
}
void FSnappingUtils::SetActorSnapDistance(float Distance)
{
if (EditorViewportSnapper.IsValid())
{
EditorViewportSnapper->SetActorSnapDistance(Distance);
}
}
bool FSnappingUtils::SnapActorsToNearestActor(FVector& DragDelta, FLevelEditorViewportClient* ViewportClient)
{
return EditorViewportSnapper.IsValid() ? EditorViewportSnapper->SnapActorsToNearestActor(DragDelta, ViewportClient) : false;
}
bool FSnappingUtils::SnapDraggedActorsToNearestVertex(FVector& DragDelta, FLevelEditorViewportClient* ViewportClient)
{
return EditorViewportSnapper.IsValid() ? EditorViewportSnapper->SnapDraggedActorsToNearestVertex(DragDelta, ViewportClient) : false;
}
bool FSnappingUtils::SnapDragLocationToNearestVertex(const FVector& BaseLocation, FVector& DragDelta, FLevelEditorViewportClient* ViewportClient, bool bIsPivot)
{
return EditorViewportSnapper.IsValid() ? EditorViewportSnapper->SnapDragLocationToNearestVertex(BaseLocation, DragDelta, ViewportClient, bIsPivot) : false;
}
bool FSnappingUtils::SnapLocationToNearestVertex(FVector& Location, const FVector2D& MouseLocation, FLevelEditorViewportClient* ViewportClient, FVector& OutVertexNormal, bool bDrawVertHelpers )
{
return EditorViewportSnapper.IsValid() ? EditorViewportSnapper->SnapLocationToNearestVertex(Location, MouseLocation, ViewportClient, OutVertexNormal, bDrawVertHelpers ) : false;
}
void FSnappingUtils::SnapScale(FVector& Point, const FVector& GridBase)
{
IViewportSnappingModule::GetSnapManager()->SnapScale(Point, GridBase);
}
void FSnappingUtils::SnapPointToGrid(FVector& Point, const FVector& GridBase)
{
IViewportSnappingModule::GetSnapManager()->SnapPointToGrid(Point, GridBase);
}
void FSnappingUtils::SnapRotatorToGrid(FRotator& Rotation)
{
IViewportSnappingModule::GetSnapManager()->SnapRotatorToGrid(Rotation);
}
bool FSnappingUtils::SnapToBSPVertex(FVector& Location, FVector GridBase, FRotator& Rotation)
{
return EditorViewportSnapper.IsValid() ? EditorViewportSnapper->SnapToBSPVertex(Location, GridBase, Rotation) : false;
}
void FSnappingUtils::ClearSnappingHelpers(bool bClearImmediately)
{
IViewportSnappingModule::GetSnapManager()->ClearSnappingHelpers(bClearImmediately);
}
void FSnappingUtils::DrawSnappingHelpers(const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
IViewportSnappingModule::GetSnapManager()->DrawSnappingHelpers(View, PDI);
}
void FSnappingUtils::InitEditorSnappingTools()
{
EditorViewportSnapper = MakeShareable(new FEditorViewportSnapping);
IViewportSnappingModule& Module = FModuleManager::LoadModuleChecked<IViewportSnappingModule>("ViewportSnapping");
Module.RegisterSnappingPolicy(EditorViewportSnapper);
}