From 30c0078a1d50bb5dc7e0894f583d888848e5dd50 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Fri, 10 Apr 2026 15:47:20 +0200 Subject: [PATCH] Add rotate/scale gizmo modes, hotkeys, and delete - E/R/T hotkeys to switch Translate/Rotate/Scale modes - Rotate: drag gizmo axis to rotate selection around that axis - Scale: drag gizmo axis to scale selection on that axis - Delete key to destroy selected actors - Movement keys: arrows only + A (up) / Q (down) - Removed ZQSD movement to free letter keys for hotkeys Co-Authored-By: Claude Opus 4.6 (1M context) --- .../PS_Editor/GameMode/PS_Editor_Pawn.cpp | 168 ++++++++++++++++-- .../PS_Editor/GameMode/PS_Editor_Pawn.h | 16 ++ 2 files changed, 168 insertions(+), 16 deletions(-) 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 813df5d..f92c6e1 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 @@ -122,29 +122,23 @@ void APS_Editor_Pawn::CreateInputActions() Mapping.Modifiers.Add(NewObject(this)); }; - // ZQSD (AZERTY) - MapForward(EKeys::Z); - MapBackward(EKeys::S); - MapRight(EKeys::D); - MapLeft(EKeys::Q); - - // Arrow keys + // Arrow keys only (letter keys reserved for hotkeys) MapForward(EKeys::Up); MapBackward(EKeys::Down); MapRight(EKeys::Right); MapLeft(EKeys::Left); - // E - Up (+Z) + // A - Up (+Z) { - FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::E); + FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::A); UInputModifierSwizzleAxis* Swizzle = NewObject(this); Swizzle->Order = EInputAxisSwizzle::ZYX; Mapping.Modifiers.Add(Swizzle); } - // A - Down (-Z) + // Q - Down (-Z) { - FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::A); + FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::Q); UInputModifierSwizzleAxis* Swizzle = NewObject(this); Swizzle->Order = EInputAxisSwizzle::ZYX; Mapping.Modifiers.Add(Swizzle); @@ -157,6 +151,23 @@ void APS_Editor_Pawn::CreateInputActions() EditorMappingContext->MapKey(IA_LeftClick, EKeys::LeftMouseButton); EditorMappingContext->MapKey(IA_RightClick, EKeys::RightMouseButton); EditorMappingContext->MapKey(IA_MiddleClick, EKeys::MiddleMouseButton); + + // Transform mode hotkeys (E/R/T) + IA_TranslateMode = NewObject(this, TEXT("IA_TranslateMode")); + IA_TranslateMode->ValueType = EInputActionValueType::Boolean; + EditorMappingContext->MapKey(IA_TranslateMode, EKeys::E); + + IA_RotateMode = NewObject(this, TEXT("IA_RotateMode")); + IA_RotateMode->ValueType = EInputActionValueType::Boolean; + EditorMappingContext->MapKey(IA_RotateMode, EKeys::R); + + IA_ScaleMode = NewObject(this, TEXT("IA_ScaleMode")); + IA_ScaleMode->ValueType = EInputActionValueType::Boolean; + EditorMappingContext->MapKey(IA_ScaleMode, EKeys::T); + + IA_Delete = NewObject(this, TEXT("IA_Delete")); + IA_Delete->ValueType = EInputActionValueType::Boolean; + EditorMappingContext->MapKey(IA_Delete, EKeys::Delete); } void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) @@ -188,6 +199,12 @@ void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComp EIC->BindAction(IA_MiddleClick, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleMiddleClickStarted); EIC->BindAction(IA_MiddleClick, ETriggerEvent::Completed, this, &APS_Editor_Pawn::HandleMiddleClickCompleted); + + // Transform mode hotkeys (only trigger on press, not hold) + EIC->BindAction(IA_TranslateMode, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleTranslateMode); + EIC->BindAction(IA_RotateMode, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleRotateMode); + EIC->BindAction(IA_ScaleMode, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleScaleMode); + EIC->BindAction(IA_Delete, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleDelete); } // ---- Input Handlers ---- @@ -295,15 +312,74 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value) const FVector AxisDir = Gizmo->GetAxisDirection(ActiveGizmoAxis); const FVector TotalDelta = CurrentIntersection - GizmoDragInitialIntersection; - const FVector ConstrainedDelta = FVector::DotProduct(TotalDelta, AxisDir) * AxisDir; + const EPS_Editor_TransformMode Mode = SM->GetTransformMode(); - // Apply delta to all selected actors from their initial transforms const TArray>& Selected = SM->GetSelectedActors(); - for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i) + + if (Mode == EPS_Editor_TransformMode::Translate) { - if (AActor* Actor = Selected[i].Get()) + const FVector ConstrainedDelta = FVector::DotProduct(TotalDelta, AxisDir) * AxisDir; + for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i) { - Actor->SetActorLocation(GizmoDragInitialTransforms[i].GetLocation() + ConstrainedDelta); + if (AActor* Actor = Selected[i].Get()) + { + Actor->SetActorLocation(GizmoDragInitialTransforms[i].GetLocation() + ConstrainedDelta); + } + } + } + else if (Mode == EPS_Editor_TransformMode::Rotate) + { + // Compute rotation angle from initial to current intersection around the axis + const FVector GizmoLoc = GizmoDragPlaneOrigin; + const FVector InitDir = (GizmoDragInitialIntersection - GizmoLoc).GetSafeNormal(); + const FVector CurrDir = (CurrentIntersection - GizmoLoc).GetSafeNormal(); + + // Signed angle via cross product + float Angle = FMath::Atan2( + FVector::DotProduct(FVector::CrossProduct(InitDir, CurrDir), AxisDir), + FVector::DotProduct(InitDir, CurrDir)); + Angle = FMath::RadiansToDegrees(Angle); + + const FQuat RotationQuat(AxisDir, FMath::DegreesToRadians(Angle)); + + for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i) + { + if (AActor* Actor = Selected[i].Get()) + { + // Rotate around gizmo pivot + const FVector Offset = GizmoDragInitialTransforms[i].GetLocation() - GizmoLoc; + const FVector RotatedOffset = RotationQuat.RotateVector(Offset); + Actor->SetActorLocation(GizmoLoc + RotatedOffset); + + // Also rotate the actor itself + const FQuat OrigRot = GizmoDragInitialTransforms[i].GetRotation(); + Actor->SetActorRotation((RotationQuat * OrigRot).Rotator()); + } + } + } + else if (Mode == EPS_Editor_TransformMode::Scale) + { + // Scale factor from mouse movement along the axis + const float InitialDist = FVector::DotProduct(GizmoDragInitialIntersection - GizmoDragPlaneOrigin, AxisDir); + const float CurrentDist = FVector::DotProduct(CurrentIntersection - GizmoDragPlaneOrigin, AxisDir); + + float ScaleFactor = 1.0f; + if (FMath::Abs(InitialDist) > KINDA_SMALL_NUMBER) + { + ScaleFactor = FMath::Clamp(CurrentDist / InitialDist, 0.01f, 100.0f); + } + + for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i) + { + if (AActor* Actor = Selected[i].Get()) + { + FVector NewScale = GizmoDragInitialTransforms[i].GetScale3D(); + // Scale only on the dragged axis + if (ActiveGizmoAxis == EPS_Editor_GizmoAxis::X) NewScale.X *= ScaleFactor; + else if (ActiveGizmoAxis == EPS_Editor_GizmoAxis::Y) NewScale.Y *= ScaleFactor; + else if (ActiveGizmoAxis == EPS_Editor_GizmoAxis::Z) NewScale.Z *= ScaleFactor; + Actor->SetActorScale3D(NewScale); + } } } @@ -412,6 +488,66 @@ void APS_Editor_Pawn::HandleMiddleClickCompleted(const FInputActionValue& Value) } } +void APS_Editor_Pawn::HandleTranslateMode(const FInputActionValue& Value) +{ + if (CameraMode != EPS_Editor_CameraMode::Idle) return; + if (APS_Editor_PlayerController* EditorPC = Cast(GetController())) + { + if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager()) + { + SM->SetTransformMode(EPS_Editor_TransformMode::Translate); + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Translate mode")); + } + } +} + +void APS_Editor_Pawn::HandleRotateMode(const FInputActionValue& Value) +{ + if (CameraMode != EPS_Editor_CameraMode::Idle) return; + if (APS_Editor_PlayerController* EditorPC = Cast(GetController())) + { + if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager()) + { + SM->SetTransformMode(EPS_Editor_TransformMode::Rotate); + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Rotate mode")); + } + } +} + +void APS_Editor_Pawn::HandleScaleMode(const FInputActionValue& Value) +{ + if (CameraMode != EPS_Editor_CameraMode::Idle) return; + if (APS_Editor_PlayerController* EditorPC = Cast(GetController())) + { + if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager()) + { + SM->SetTransformMode(EPS_Editor_TransformMode::Scale); + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Scale mode")); + } + } +} + +void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value) +{ + if (CameraMode != EPS_Editor_CameraMode::Idle) return; + if (APS_Editor_PlayerController* EditorPC = Cast(GetController())) + { + if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager()) + { + TArray> ToDelete = SM->GetSelectedActors(); + SM->ClearSelection(); + for (const TWeakObjectPtr& Weak : ToDelete) + { + if (AActor* Actor = Weak.Get()) + { + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Deleted %s"), *Actor->GetName()); + Actor->Destroy(); + } + } + } + } +} + // ---- 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 4d542a2..f010364 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 @@ -146,6 +146,18 @@ private: UPROPERTY() TObjectPtr IA_MiddleClick; + UPROPERTY() + TObjectPtr IA_TranslateMode; + + UPROPERTY() + TObjectPtr IA_RotateMode; + + UPROPERTY() + TObjectPtr IA_ScaleMode; + + UPROPERTY() + TObjectPtr IA_Delete; + // ---- Modifier key state ---- bool bAltHeld = false; bool bCtrlHeld = false; @@ -170,6 +182,10 @@ private: void HandleRightClickCompleted(const FInputActionValue& Value); void HandleMiddleClickStarted(const FInputActionValue& Value); void HandleMiddleClickCompleted(const FInputActionValue& Value); + void HandleTranslateMode(const FInputActionValue& Value); + void HandleRotateMode(const FInputActionValue& Value); + void HandleScaleMode(const FInputActionValue& Value); + void HandleDelete(const FInputActionValue& Value); // ---- Helpers ---- void CreateInputActions();