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 5d720db..813df5d 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 @@ -10,6 +10,7 @@ #include "GameFramework/PlayerController.h" #include "PS_Editor_SelectionManager.h" #include "PS_Editor_PlayerController.h" +#include "PS_Editor_Gizmo.h" #include "DrawDebugHelpers.h" APS_Editor_Pawn::APS_Editor_Pawn() @@ -44,6 +45,12 @@ void APS_Editor_Pawn::Tick(float DeltaTime) bAltHeld = PC->IsInputKeyDown(EKeys::LeftAlt) || PC->IsInputKeyDown(EKeys::RightAlt); bCtrlHeld = PC->IsInputKeyDown(EKeys::LeftControl) || PC->IsInputKeyDown(EKeys::RightControl); + // Gizmo hover highlighting in Idle mode + if (CameraMode == EPS_Editor_CameraMode::Idle) + { + UpdateGizmoHover(); + } + // Draw debug bounding box around selected actors if (APS_Editor_PlayerController* EditorPC = Cast(PC)) { @@ -264,6 +271,45 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value) SetActorLocation(OrbitFocalPoint + Offset); SetActorRotation((OrbitFocalPoint - GetActorLocation()).Rotation()); } + 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(); + if (!SM) return; + APS_Editor_Gizmo* Gizmo = SM->GetGizmo(); + if (!Gizmo) return; + + const FVector AxisDir = Gizmo->GetAxisDirection(ActiveGizmoAxis); + const FVector TotalDelta = CurrentIntersection - GizmoDragInitialIntersection; + const FVector ConstrainedDelta = FVector::DotProduct(TotalDelta, AxisDir) * AxisDir; + + // 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 (AActor* Actor = Selected[i].Get()) + { + Actor->SetActorLocation(GizmoDragInitialTransforms[i].GetLocation() + ConstrainedDelta); + } + } + + // Update gizmo position + Gizmo->SetActorLocation(SM->GetSelectionPivot()); + } } void APS_Editor_Pawn::HandleScroll(const FInputActionValue& Value) @@ -293,16 +339,35 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value) { if (bAltHeld) { - // Alt + left-click = orbit immediately (no pending) ComputeOrbitFocalPoint(); SetCameraMode(EPS_Editor_CameraMode::Orbit); + return; } - else + + // Check if clicking on a gizmo handle + FVector RayOrigin, RayDir; + if (GetMouseWorldRay(RayOrigin, RayDir)) { - // Enter PendingClick: wait to see if this is a click or a drag - PendingClickScreenPos = GetMouseScreenPosition(); - SetCameraMode(EPS_Editor_CameraMode::PendingClick); + if (APS_Editor_PlayerController* EditorPC = Cast(GetController())) + { + if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager()) + { + if (APS_Editor_Gizmo* Gizmo = SM->GetGizmo()) + { + EPS_Editor_GizmoAxis Axis = Gizmo->GetAxisUnderCursor(RayOrigin, RayDir); + if (Axis != EPS_Editor_GizmoAxis::None) + { + BeginGizmoDrag(Axis); + return; + } + } + } + } } + + // No gizmo hit: enter PendingClick + PendingClickScreenPos = GetMouseScreenPosition(); + SetCameraMode(EPS_Editor_CameraMode::PendingClick); } void APS_Editor_Pawn::HandleLeftClickCompleted(const FInputActionValue& Value) @@ -419,3 +484,106 @@ FVector2D APS_Editor_Pawn::GetMouseScreenPosition() const PC->GetMousePosition(MouseX, MouseY); return FVector2D(MouseX, MouseY); } + +bool APS_Editor_Pawn::GetMouseWorldRay(FVector& OutOrigin, FVector& OutDirection) const +{ + APlayerController* PC = Cast(GetController()); + if (!PC) + { + return false; + } + + const FVector2D ScreenPos = GetMouseScreenPosition(); + return PC->DeprojectScreenPositionToWorld(ScreenPos.X, ScreenPos.Y, OutOrigin, OutDirection); +} + +bool APS_Editor_Pawn::RayPlaneIntersection(const FVector& RayOrigin, const FVector& RayDir, + const FVector& PlaneOrigin, const FVector& PlaneNormal, FVector& OutIntersection) const +{ + const float Denom = FVector::DotProduct(RayDir, PlaneNormal); + if (FMath::Abs(Denom) < KINDA_SMALL_NUMBER) + { + return false; // Ray is parallel to plane + } + + const float T = FVector::DotProduct(PlaneOrigin - RayOrigin, PlaneNormal) / Denom; + if (T < 0.0f) + { + return false; // Intersection is behind the ray + } + + OutIntersection = RayOrigin + RayDir * T; + return true; +} + +void APS_Editor_Pawn::UpdateGizmoHover() +{ + APS_Editor_PlayerController* EditorPC = Cast(GetController()); + if (!EditorPC) return; + UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager(); + if (!SM) return; + APS_Editor_Gizmo* Gizmo = SM->GetGizmo(); + if (!Gizmo) return; + + FVector RayOrigin, RayDir; + if (GetMouseWorldRay(RayOrigin, RayDir)) + { + EPS_Editor_GizmoAxis Axis = Gizmo->GetAxisUnderCursor(RayOrigin, RayDir); + Gizmo->SetHighlightedAxis(Axis); + } + else + { + Gizmo->SetHighlightedAxis(EPS_Editor_GizmoAxis::None); + } +} + +void APS_Editor_Pawn::BeginGizmoDrag(EPS_Editor_GizmoAxis Axis) +{ + APS_Editor_PlayerController* EditorPC = Cast(GetController()); + if (!EditorPC) return; + UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager(); + if (!SM) return; + APS_Editor_Gizmo* Gizmo = SM->GetGizmo(); + if (!Gizmo) return; + + ActiveGizmoAxis = Axis; + + // Get the axis direction in world space + const FVector AxisDir = Gizmo->GetAxisDirection(Axis); + + // 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()) + { + // Camera looking along the axis - use camera up instead + PlaneNormal = FVector::CrossProduct(AxisDir, FVector::UpVector).GetSafeNormal(); + } + PlaneNormal = FVector::CrossProduct(AxisDir, PlaneNormal).GetSafeNormal(); + + GizmoDragPlaneNormal = PlaneNormal; + GizmoDragPlaneOrigin = Gizmo->GetActorLocation(); + + // Get initial mouse-plane intersection + FVector RayOrigin, RayDir; + if (GetMouseWorldRay(RayOrigin, RayDir)) + { + RayPlaneIntersection(RayOrigin, RayDir, GizmoDragPlaneOrigin, GizmoDragPlaneNormal, GizmoDragInitialIntersection); + } + + // Store initial transforms of all selected actors + GizmoDragInitialTransforms.Empty(); + for (const TWeakObjectPtr& Weak : SM->GetSelectedActors()) + { + if (AActor* Actor = Weak.Get()) + { + GizmoDragInitialTransforms.Add(Actor->GetActorTransform()); + } + else + { + GizmoDragInitialTransforms.Add(FTransform::Identity); + } + } + + SetCameraMode(EPS_Editor_CameraMode::GizmoDrag); +} 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 8666577..4d542a2 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 @@ -3,6 +3,7 @@ #include "CoreMinimal.h" #include "GameFramework/Pawn.h" #include "InputActionValue.h" +#include "PS_Editor_SelectionTypes.h" #include "PS_Editor_Pawn.generated.h" class UCameraComponent; @@ -152,6 +153,13 @@ private: // ---- Click vs drag state ---- FVector2D PendingClickScreenPos = FVector2D::ZeroVector; + // ---- Gizmo drag state ---- + EPS_Editor_GizmoAxis ActiveGizmoAxis = EPS_Editor_GizmoAxis::None; + FVector GizmoDragPlaneNormal = FVector::ZeroVector; + FVector GizmoDragPlaneOrigin = FVector::ZeroVector; + FVector GizmoDragInitialIntersection = FVector::ZeroVector; + TArray GizmoDragInitialTransforms; + // ---- Input Handlers ---- void HandleMove(const FInputActionValue& Value); void HandleLook(const FInputActionValue& Value); @@ -169,6 +177,10 @@ private: void UpdateCursorVisibility(); void ComputeOrbitFocalPoint(); FVector2D GetMouseScreenPosition() const; + bool GetMouseWorldRay(FVector& OutOrigin, FVector& OutDirection) const; + bool RayPlaneIntersection(const FVector& RayOrigin, const FVector& RayDir, const FVector& PlaneOrigin, const FVector& PlaneNormal, FVector& OutIntersection) const; + void UpdateGizmoHover(); + void BeginGizmoDrag(EPS_Editor_GizmoAxis Axis); float OrbitPitch = 0.0f; float OrbitYaw = 0.0f; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Gizmo/PS_Editor_Gizmo.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Gizmo/PS_Editor_Gizmo.cpp index 34407e1..fbce330 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Gizmo/PS_Editor_Gizmo.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Gizmo/PS_Editor_Gizmo.cpp @@ -53,9 +53,10 @@ void APS_Editor_Gizmo::BeginPlay() return MID; }; - Mat_X = MakeColorMat(FLinearColor::Red); - Mat_Y = MakeColorMat(FLinearColor::Green); - Mat_Z = MakeColorMat(FLinearColor(0.0f, 0.0f, 1.0f)); + // UE editor gizmo colors + Mat_X = MakeColorMat(FLinearColor(0.594f, 0.0197f, 0.0f)); // Dark red + Mat_Y = MakeColorMat(FLinearColor(0.1255f, 0.372f, 0.0f)); // Dark green + Mat_Z = MakeColorMat(FLinearColor(0.0392f, 0.0784f, 0.594f)); // Dark blue Mat_Highlight = MakeColorMat(FLinearColor::Yellow); // Apply materials to shafts and cones @@ -182,13 +183,18 @@ EPS_Editor_GizmoAxis APS_Editor_Gizmo::GetAxisUnderCursor(const FVector& RayOrig float BestDistance = TNumericLimits::Max(); EPS_Editor_GizmoAxis BestAxis = EPS_Editor_GizmoAxis::None; + // Use a sphere sweep with a generous radius for easier clicking + const float SweepRadius = 8.0f * GetActorScale3D().X; + auto TestComponent = [&](UStaticMeshComponent* Comp, EPS_Editor_GizmoAxis Axis) { - if (!Comp || !Comp->IsVisible()) return; + if (!Comp) return; FHitResult Hit; const FVector RayEnd = RayOrigin + RayDirection * 100000.0f; - if (Comp->LineTraceComponent(Hit, RayOrigin, RayEnd, FCollisionQueryParams::DefaultQueryParam)) + const FCollisionShape Sphere = FCollisionShape::MakeSphere(SweepRadius); + + if (Comp->SweepComponent(Hit, RayOrigin, RayEnd, FQuat::Identity, Sphere, false)) { if (Hit.Distance < BestDistance) {