From 49cb480e15bc497ad8b2f96f45a34e5b70d6253f Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Wed, 29 Apr 2026 18:00:54 +0200 Subject: [PATCH] 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) --- .../PS_Editor/GameMode/PS_Editor_Pawn.cpp | 98 +++++++++++++++++++ .../PS_Editor/GameMode/PS_Editor_Pawn.h | 13 +++ 2 files changed, 111 insertions(+) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.cpp index 369895e..6f7ba4f 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.cpp @@ -77,6 +77,26 @@ void APS_Editor_Pawn::Tick(float 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(GetController())) { bAltHeld = PC->IsInputKeyDown(EKeys::LeftAlt) || PC->IsInputKeyDown(EKeys::RightAlt); @@ -229,6 +249,11 @@ void APS_Editor_Pawn::CreateInputActions() IA_Cancel = NewObject(this, TEXT("IA_Cancel")); IA_Cancel->ValueType = EInputActionValueType::Boolean; EditorMappingContext->MapKey(IA_Cancel, EKeys::Escape); + + // Focus selection (Maya/Blender style) + IA_Focus = NewObject(this, TEXT("IA_Focus")); + IA_Focus->ValueType = EInputActionValueType::Boolean; + EditorMappingContext->MapKey(IA_Focus, EKeys::F); } 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_Confirm, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleConfirm); EIC->BindAction(IA_Cancel, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleCancel); + EIC->BindAction(IA_Focus, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleFocus); } // ---- 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(GetController()); + if (!EditorPC) return; + + UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager(); + if (!SM || !SM->IsAnythingSelected()) return; + + FBox CombinedBox(EForceInit::ForceInit); + for (const TWeakObjectPtr& 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& 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(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 ---- void APS_Editor_Pawn::SetCameraMode(EPS_Editor_CameraMode NewMode) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.h index b9bd685..fb7065d 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.h @@ -209,6 +209,9 @@ private: UPROPERTY() TObjectPtr IA_Cancel; + UPROPERTY() + TObjectPtr IA_Focus; + // ---- Spline point / ChildMovable drag state ---- TWeakObjectPtr DraggedSplineActor; TArray SplinePointDragInitialState; @@ -256,6 +259,16 @@ private: void HandleSplineMode(const FInputActionValue& Value); void HandleConfirm(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 ---- void CreateInputActions();