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 f92c6e1..f45f923 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 @@ -168,6 +168,10 @@ void APS_Editor_Pawn::CreateInputActions() IA_Delete = NewObject(this, TEXT("IA_Delete")); IA_Delete->ValueType = EInputActionValueType::Boolean; EditorMappingContext->MapKey(IA_Delete, EKeys::Delete); + + IA_SnapToGround = NewObject(this, TEXT("IA_SnapToGround")); + IA_SnapToGround->ValueType = EInputActionValueType::Boolean; + EditorMappingContext->MapKey(IA_SnapToGround, EKeys::End); } void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) @@ -205,6 +209,7 @@ void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComp 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); + EIC->BindAction(IA_SnapToGround, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleSnapToGround); } // ---- Input Handlers ---- @@ -290,19 +295,6 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value) } else if (CameraMode == EPS_Editor_CameraMode::GizmoDrag) { - // Intersect mouse ray with drag plane, constrain delta to axis, apply to actors - FVector RayOrigin, RayDir; - if (!GetMouseWorldRay(RayOrigin, RayDir)) - { - return; - } - - FVector CurrentIntersection; - if (!RayPlaneIntersection(RayOrigin, RayDir, GizmoDragPlaneOrigin, GizmoDragPlaneNormal, CurrentIntersection)) - { - return; - } - APS_Editor_PlayerController* EditorPC = Cast(GetController()); if (!EditorPC) return; UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager(); @@ -311,14 +303,20 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value) if (!Gizmo) return; const FVector AxisDir = Gizmo->GetAxisDirection(ActiveGizmoAxis); - const FVector TotalDelta = CurrentIntersection - GizmoDragInitialIntersection; const EPS_Editor_TransformMode Mode = SM->GetTransformMode(); - const TArray>& Selected = SM->GetSelectedActors(); if (Mode == EPS_Editor_TransformMode::Translate) { + // Ray-plane intersection for precise axis-constrained translation + FVector RayOrigin, RayDir; + if (!GetMouseWorldRay(RayOrigin, RayDir)) return; + FVector CurrentIntersection; + if (!RayPlaneIntersection(RayOrigin, RayDir, GizmoDragPlaneOrigin, GizmoDragPlaneNormal, CurrentIntersection)) return; + + const FVector TotalDelta = CurrentIntersection - GizmoDragInitialIntersection; const FVector ConstrainedDelta = FVector::DotProduct(TotalDelta, AxisDir) * AxisDir; + for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i) { if (AActor* Actor = Selected[i].Get()) @@ -329,29 +327,23 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value) } else if (Mode == EPS_Editor_TransformMode::Rotate) { - // Compute rotation angle from initial to current intersection around the axis + // Use combined mouse X+Y screen movement to drive rotation angle + const float RotationSpeed = 1.0f; + const float AngleDelta = (LookInput.X + LookInput.Y) * RotationSpeed; + + // Accumulate total angle since drag start + GizmoDragAccumulator += AngleDelta; + + const FQuat RotationQuat(AxisDir, FMath::DegreesToRadians(GizmoDragAccumulator)); 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); + Actor->SetActorLocation(GizmoLoc + RotationQuat.RotateVector(Offset)); - // Also rotate the actor itself const FQuat OrigRot = GizmoDragInitialTransforms[i].GetRotation(); Actor->SetActorRotation((RotationQuat * OrigRot).Rotator()); } @@ -359,22 +351,17 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value) } 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); + // Use combined mouse X+Y screen movement to drive scale + const float ScaleSpeed = 0.01f; + GizmoDragAccumulator += (LookInput.X + LookInput.Y) * ScaleSpeed; - float ScaleFactor = 1.0f; - if (FMath::Abs(InitialDist) > KINDA_SMALL_NUMBER) - { - ScaleFactor = FMath::Clamp(CurrentDist / InitialDist, 0.01f, 100.0f); - } + const float ScaleFactor = FMath::Clamp(1.0f + GizmoDragAccumulator, 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; @@ -383,7 +370,6 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value) } } - // Update gizmo position Gizmo->SetActorLocation(SM->GetSelectionPivot()); } } @@ -548,6 +534,18 @@ void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value) } } +void APS_Editor_Pawn::HandleSnapToGround(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->SnapSelectionToGround(); + } + } +} + // ---- Helpers ---- void APS_Editor_Pawn::SetCameraMode(EPS_Editor_CameraMode NewMode) @@ -684,21 +682,30 @@ void APS_Editor_Pawn::BeginGizmoDrag(EPS_Editor_GizmoAxis Axis) ActiveGizmoAxis = Axis; - // Get the axis direction in world space const FVector AxisDir = Gizmo->GetAxisDirection(Axis); + const EPS_Editor_TransformMode Mode = SM->GetTransformMode(); - // Compute the drag plane: contains the axis and faces the camera as much as possible - const FVector CameraForward = GetActorForwardVector(); - FVector PlaneNormal = FVector::CrossProduct(AxisDir, CameraForward).GetSafeNormal(); - if (PlaneNormal.IsNearlyZero()) + FVector PlaneNormal; + if (Mode == EPS_Editor_TransformMode::Rotate) { - // Camera looking along the axis - use camera up instead - PlaneNormal = FVector::CrossProduct(AxisDir, FVector::UpVector).GetSafeNormal(); + // For rotation: plane perpendicular to the rotation axis + PlaneNormal = AxisDir; + } + else + { + // For translate/scale: plane containing the axis, facing the camera + const FVector CameraForward = GetActorForwardVector(); + PlaneNormal = FVector::CrossProduct(AxisDir, CameraForward).GetSafeNormal(); + if (PlaneNormal.IsNearlyZero()) + { + PlaneNormal = FVector::CrossProduct(AxisDir, FVector::UpVector).GetSafeNormal(); + } + PlaneNormal = FVector::CrossProduct(AxisDir, PlaneNormal).GetSafeNormal(); } - PlaneNormal = FVector::CrossProduct(AxisDir, PlaneNormal).GetSafeNormal(); GizmoDragPlaneNormal = PlaneNormal; GizmoDragPlaneOrigin = Gizmo->GetActorLocation(); + GizmoDragAccumulator = 0.0f; // Get initial mouse-plane intersection FVector RayOrigin, RayDir; 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 f010364..c011272 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 @@ -158,6 +158,9 @@ private: UPROPERTY() TObjectPtr IA_Delete; + UPROPERTY() + TObjectPtr IA_SnapToGround; + // ---- Modifier key state ---- bool bAltHeld = false; bool bCtrlHeld = false; @@ -171,6 +174,7 @@ private: FVector GizmoDragPlaneOrigin = FVector::ZeroVector; FVector GizmoDragInitialIntersection = FVector::ZeroVector; TArray GizmoDragInitialTransforms; + float GizmoDragAccumulator = 0.0f; // ---- Input Handlers ---- void HandleMove(const FInputActionValue& Value); @@ -186,6 +190,7 @@ private: void HandleRotateMode(const FInputActionValue& Value); void HandleScaleMode(const FInputActionValue& Value); void HandleDelete(const FInputActionValue& Value); + void HandleSnapToGround(const FInputActionValue& Value); // ---- Helpers ---- void CreateInputActions(); diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.cpp index 67f0a4f..d59cf18 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.cpp @@ -218,6 +218,45 @@ void UPS_Editor_SelectionManager::SetActorHighlight(AActor* Actor, bool bHighlig } } +void UPS_Editor_SelectionManager::SnapSelectionToGround() +{ + APlayerController* PC = OwnerPC.Get(); + if (!PC) return; + + for (const TWeakObjectPtr& Weak : SelectedActors) + { + AActor* Actor = Weak.Get(); + if (!Actor) continue; + + // Get actor bounds to compute the bottom of the actor + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + const float BottomOffset = Extent.Z; // Distance from actor origin to bottom + + // Trace downward from actor location + const FVector Start = Actor->GetActorLocation(); + const FVector End = Start - FVector::UpVector * 100000.0f; + + FHitResult Hit; + FCollisionQueryParams Params; + Params.AddIgnoredActor(Actor); + Params.AddIgnoredActor(PC->GetPawn()); + if (Gizmo) Params.AddIgnoredActor(Gizmo); + + if (Actor->GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params)) + { + // Place actor so its bottom sits on the ground + FVector NewLocation = Hit.ImpactPoint; + NewLocation.Z += BottomOffset; + Actor->SetActorLocation(NewLocation); + + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Snapped %s to ground"), *Actor->GetName()); + } + } + + UpdateGizmo(); +} + void UPS_Editor_SelectionManager::UpdateGizmo() { if (!Gizmo) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.h index 6adf866..3276ac5 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.h @@ -36,6 +36,9 @@ public: void ToggleActor(AActor* Actor); void ClearSelection(); + /** Snap all selected actors to the ground below them. */ + void SnapSelectionToGround(); + // ---- Queries ---- const TArray>& GetSelectedActors() const { return SelectedActors; }