Add F key to frame the current selection (Maya/Blender style)
Pressing F computes the combined bounding box of every selected actor, then animates the camera over 0.25s (smoothstep ease) to a distance that fits the box in view while keeping the current viewing direction. The orbit focal point and distance are synced so a subsequent Alt+drag pivots around the new center. No-ops cleanly when nothing is selected. Falls back to actor pivots when no component reports valid bounds, and clamps the radius to a minimum so point-like actors don't pull the camera onto them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,26 @@ void APS_Editor_Pawn::Tick(float DeltaTime)
|
|||||||
{
|
{
|
||||||
Super::Tick(DeltaTime);
|
Super::Tick(DeltaTime);
|
||||||
|
|
||||||
|
// Focus animation: smoothly interpolate camera location/rotation toward the framed
|
||||||
|
// selection. Uses ease-in-out (smoothstep) so the camera doesn't snap at the boundaries.
|
||||||
|
if (bIsFocusing)
|
||||||
|
{
|
||||||
|
FocusElapsed = FMath::Min(FocusElapsed + DeltaTime, FocusDuration);
|
||||||
|
const float Linear = FocusElapsed / FocusDuration;
|
||||||
|
const float Alpha = Linear * Linear * (3.0f - 2.0f * Linear); // smoothstep
|
||||||
|
const FVector NewLoc = FMath::Lerp(FocusStartLocation, FocusTargetLocation, Alpha);
|
||||||
|
const FRotator NewRot = FMath::Lerp(FocusStartRotation, FocusTargetRotation, Alpha);
|
||||||
|
SetActorLocation(NewLoc);
|
||||||
|
SetActorRotation(NewRot);
|
||||||
|
if (FocusElapsed >= FocusDuration)
|
||||||
|
{
|
||||||
|
bIsFocusing = false;
|
||||||
|
// Sync orbit state so a subsequent Alt+drag pivots around the new focal point.
|
||||||
|
OrbitYaw = NewRot.Yaw;
|
||||||
|
OrbitPitch = NewRot.Pitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (APlayerController* PC = Cast<APlayerController>(GetController()))
|
if (APlayerController* PC = Cast<APlayerController>(GetController()))
|
||||||
{
|
{
|
||||||
bAltHeld = PC->IsInputKeyDown(EKeys::LeftAlt) || PC->IsInputKeyDown(EKeys::RightAlt);
|
bAltHeld = PC->IsInputKeyDown(EKeys::LeftAlt) || PC->IsInputKeyDown(EKeys::RightAlt);
|
||||||
@@ -229,6 +249,11 @@ void APS_Editor_Pawn::CreateInputActions()
|
|||||||
IA_Cancel = NewObject<UInputAction>(this, TEXT("IA_Cancel"));
|
IA_Cancel = NewObject<UInputAction>(this, TEXT("IA_Cancel"));
|
||||||
IA_Cancel->ValueType = EInputActionValueType::Boolean;
|
IA_Cancel->ValueType = EInputActionValueType::Boolean;
|
||||||
EditorMappingContext->MapKey(IA_Cancel, EKeys::Escape);
|
EditorMappingContext->MapKey(IA_Cancel, EKeys::Escape);
|
||||||
|
|
||||||
|
// Focus selection (Maya/Blender style)
|
||||||
|
IA_Focus = NewObject<UInputAction>(this, TEXT("IA_Focus"));
|
||||||
|
IA_Focus->ValueType = EInputActionValueType::Boolean;
|
||||||
|
EditorMappingContext->MapKey(IA_Focus, EKeys::F);
|
||||||
}
|
}
|
||||||
|
|
||||||
void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||||
@@ -275,6 +300,7 @@ void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComp
|
|||||||
EIC->BindAction(IA_SplineMode, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleSplineMode);
|
EIC->BindAction(IA_SplineMode, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleSplineMode);
|
||||||
EIC->BindAction(IA_Confirm, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleConfirm);
|
EIC->BindAction(IA_Confirm, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleConfirm);
|
||||||
EIC->BindAction(IA_Cancel, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleCancel);
|
EIC->BindAction(IA_Cancel, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleCancel);
|
||||||
|
EIC->BindAction(IA_Focus, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleFocus);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Input Handlers ----
|
// ---- Input Handlers ----
|
||||||
@@ -1362,6 +1388,78 @@ void APS_Editor_Pawn::HandleCancel(const FInputActionValue& Value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APS_Editor_Pawn::HandleFocus(const FInputActionValue& Value)
|
||||||
|
{
|
||||||
|
// Frame the current selection (Maya/Blender F-key behaviour). Computes the combined
|
||||||
|
// world-space bounding box of every selected actor, then animates the camera to a
|
||||||
|
// distance that fits the box in view while keeping the current viewing direction.
|
||||||
|
APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController());
|
||||||
|
if (!EditorPC) return;
|
||||||
|
|
||||||
|
UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager();
|
||||||
|
if (!SM || !SM->IsAnythingSelected()) return;
|
||||||
|
|
||||||
|
FBox CombinedBox(EForceInit::ForceInit);
|
||||||
|
for (const TWeakObjectPtr<AActor>& Weak : SM->GetSelectedActors())
|
||||||
|
{
|
||||||
|
if (AActor* Actor = Weak.Get())
|
||||||
|
{
|
||||||
|
const FBox ActorBox = Actor->GetComponentsBoundingBox(true);
|
||||||
|
if (ActorBox.IsValid)
|
||||||
|
{
|
||||||
|
CombinedBox += ActorBox;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!CombinedBox.IsValid)
|
||||||
|
{
|
||||||
|
// Fallback: use actor pivots if no component reports valid bounds (rare — empty actors)
|
||||||
|
for (const TWeakObjectPtr<AActor>& Weak : SM->GetSelectedActors())
|
||||||
|
{
|
||||||
|
if (AActor* Actor = Weak.Get())
|
||||||
|
{
|
||||||
|
CombinedBox += FBox(Actor->GetActorLocation(), Actor->GetActorLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!CombinedBox.IsValid) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FVector Center = CombinedBox.GetCenter();
|
||||||
|
const FVector Extent = CombinedBox.GetExtent();
|
||||||
|
// Sphere radius enclosing the box; clamp to a minimum so picking a point-like actor
|
||||||
|
// (zero-size bounds) still leaves the camera at a usable distance.
|
||||||
|
const float Radius = FMath::Max(Extent.Size(), 50.0f);
|
||||||
|
|
||||||
|
// Distance to fit a sphere of given radius given the camera's vertical FOV.
|
||||||
|
float FOV = 90.0f;
|
||||||
|
if (APlayerController* PC = Cast<APlayerController>(GetController()))
|
||||||
|
{
|
||||||
|
if (PC->PlayerCameraManager)
|
||||||
|
{
|
||||||
|
FOV = PC->PlayerCameraManager->GetFOVAngle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const float HalfFOVRad = FMath::DegreesToRadians(FOV * 0.5f);
|
||||||
|
const float FitDistance = Radius / FMath::Max(FMath::Tan(HalfFOVRad), 0.01f);
|
||||||
|
const float TargetDistance = FMath::Max(FitDistance * 1.4f, 100.0f); // 40% margin
|
||||||
|
|
||||||
|
// Keep current viewing direction; just slide the camera so Center sits TargetDistance
|
||||||
|
// in front along that direction. Then force the rotation to look squarely at Center —
|
||||||
|
// guards against accumulated rounding so the pivot for the next orbit is exact.
|
||||||
|
const FVector Forward = GetActorForwardVector();
|
||||||
|
FocusStartLocation = GetActorLocation();
|
||||||
|
FocusStartRotation = GetActorRotation();
|
||||||
|
FocusTargetLocation = Center - Forward * TargetDistance;
|
||||||
|
FocusTargetRotation = (Center - FocusTargetLocation).Rotation();
|
||||||
|
|
||||||
|
FocusElapsed = 0.0f;
|
||||||
|
bIsFocusing = true;
|
||||||
|
|
||||||
|
// Update orbit state so Alt+drag immediately after F pivots around the new center.
|
||||||
|
OrbitFocalPoint = Center;
|
||||||
|
OrbitDistance = TargetDistance;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Helpers ----
|
// ---- Helpers ----
|
||||||
|
|
||||||
void APS_Editor_Pawn::SetCameraMode(EPS_Editor_CameraMode NewMode)
|
void APS_Editor_Pawn::SetCameraMode(EPS_Editor_CameraMode NewMode)
|
||||||
|
|||||||
@@ -209,6 +209,9 @@ private:
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<UInputAction> IA_Cancel;
|
TObjectPtr<UInputAction> IA_Cancel;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UInputAction> IA_Focus;
|
||||||
|
|
||||||
// ---- Spline point / ChildMovable drag state ----
|
// ---- Spline point / ChildMovable drag state ----
|
||||||
TWeakObjectPtr<AActor> DraggedSplineActor;
|
TWeakObjectPtr<AActor> DraggedSplineActor;
|
||||||
TArray<FVector> SplinePointDragInitialState;
|
TArray<FVector> SplinePointDragInitialState;
|
||||||
@@ -256,6 +259,16 @@ private:
|
|||||||
void HandleSplineMode(const FInputActionValue& Value);
|
void HandleSplineMode(const FInputActionValue& Value);
|
||||||
void HandleConfirm(const FInputActionValue& Value);
|
void HandleConfirm(const FInputActionValue& Value);
|
||||||
void HandleCancel(const FInputActionValue& Value);
|
void HandleCancel(const FInputActionValue& Value);
|
||||||
|
void HandleFocus(const FInputActionValue& Value);
|
||||||
|
|
||||||
|
// ---- Focus animation state (F key, frame selection) ----
|
||||||
|
bool bIsFocusing = false;
|
||||||
|
float FocusElapsed = 0.0f;
|
||||||
|
float FocusDuration = 0.25f;
|
||||||
|
FVector FocusStartLocation = FVector::ZeroVector;
|
||||||
|
FVector FocusTargetLocation = FVector::ZeroVector;
|
||||||
|
FRotator FocusStartRotation = FRotator::ZeroRotator;
|
||||||
|
FRotator FocusTargetRotation = FRotator::ZeroRotator;
|
||||||
|
|
||||||
// ---- Helpers ----
|
// ---- Helpers ----
|
||||||
void CreateInputActions();
|
void CreateInputActions();
|
||||||
|
|||||||
Reference in New Issue
Block a user