Files
UnrealEngine/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseGizmos/FreeRotationSubGizmo.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

317 lines
8.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BaseGizmos/FreeRotationSubGizmo.h"
#include "InteractiveGizmoManager.h"
#include "BaseBehaviors/ClickDragBehavior.h"
#include "BaseBehaviors/MouseHoverBehavior.h"
#include "BaseGizmos/AxisSources.h"
#include "BaseGizmos/GizmoMath.h"
#include "BaseGizmos/GizmoPrivateUtil.h" // SetCommonSubGizmoProperties
#include "BaseGizmos/GizmoRenderingUtil.h"
#include "BaseGizmos/GizmoViewContext.h"
#include "BaseGizmos/HitTargets.h"
#include "BaseGizmos/StateTargets.h"
#include "BaseGizmos/TransformSources.h"
#include "BaseGizmos/TransformSubGizmoUtil.h" // FTransformSubGizmoCommonParams
#include "ToolDataVisualizer.h"
#include "Util/ColorConstants.h" // UE::Geometry::LinearColors
#include UE_INLINE_GENERATED_CPP_BY_NAME(FreeRotationSubGizmo)
namespace FreeRotationSubGizmoLocals
{
FLinearColor CircleColor = UE::Geometry::LinearColors::Gray3f();
int32 CircleNumSections = 32;
float CircleThickness = 2.0f;
}
void UFreeRotationSubGizmo::Setup()
{
UInteractiveGizmo::Setup();
// Add default mouse input behavior
MouseBehavior = NewObject<UClickDragInputBehavior>();
MouseBehavior->Initialize(this);
MouseBehavior->SetDefaultPriority(FInputCapturePriority(FInputCapturePriority::DEFAULT_GIZMO_PRIORITY));
AddInputBehavior(MouseBehavior);
UMouseHoverBehavior* HoverBehavior = NewObject<UMouseHoverBehavior>();
HoverBehavior->Initialize(this);
HoverBehavior->SetDefaultPriority(FInputCapturePriority(FInputCapturePriority::DEFAULT_GIZMO_PRIORITY));
AddInputBehavior(HoverBehavior);
AxisSource = NewObject<UGizmoConstantAxisSource>(this);
HitTarget = NewObject<UGizmoComponentHitTarget>(this);
StateTarget = NewObject<UGizmoNilStateTarget>(this);
bInInteraction = false;
}
bool UFreeRotationSubGizmo::InitializeAsRotationGizmo(
const UE::GizmoUtil::FTransformSubGizmoCommonParams& Params,
UGizmoViewContext* GizmoViewContextIn,
UE::GizmoUtil::FTransformSubGizmoSharedState* SharedState)
{
if (!Params.Component
|| !Params.TransformProxy)
{
return false;
}
UGizmoScaledAndUnscaledTransformSources* TransformSourcePtr;
// Make sure axis index is invalid so that the SetCommonSubGizmoProperties call below doesn't create
// an axis source for us.
if (!ensureMsgf(Params.Axis == EAxis::None, TEXT("UFreeRotationSubGizmo uses a camera axis source, so axis parameter should be None.")))
{
UE::GizmoUtil::FTransformSubGizmoCommonParams ParamsCopy = Params;
ParamsCopy.Axis = EAxis::None;
if (!UE::GizmoUtil::SetCommonSubGizmoProperties(this, ParamsCopy, SharedState, TransformSourcePtr))
{
return false;
}
}
else if (!UE::GizmoUtil::SetCommonSubGizmoProperties(this, Params, SharedState, TransformSourcePtr))
{
return false;
}
TransformSource = TransformSourcePtr;
// Set up the camera axis source
UGizmoConstantFrameAxisSource* CameraAxisSource = nullptr;
// See if we already have it in our shared state
if (SharedState && SharedState->CameraAxisSource)
{
CameraAxisSource = SharedState->CameraAxisSource;
}
else
{
// Create new and add to shared state.
UObject* Owner = Params.OuterForSubobjects ? Params.OuterForSubobjects : GetTransientPackage();
CameraAxisSource = NewObject<UGizmoConstantFrameAxisSource>(Owner);
TWeakObjectPtr<AActor> OwnerActor = Params.Component->GetOwner();
CustomTickFunction = [this, OwnerActor](float DeltaTime)
{
if (UGizmoConstantFrameAxisSource* FrameAxisSource = Cast<UGizmoConstantFrameAxisSource>(AxisSource.GetObject()))
{
UE::GizmoUtil::UpdateCameraAxisSource(
*FrameAxisSource,
GetGizmoManager(),
OwnerActor.IsValid() ? OwnerActor->GetTransform().GetLocation() : FVector3d::ZeroVector);
}
};
if (SharedState)
{
SharedState->CameraAxisSource = CameraAxisSource;
}
}
AxisSource = CameraAxisSource;
GizmoViewContext = GizmoViewContextIn;
return true;
}
// Called from OnClickPress if mode is IncrementalSphereBound
bool UFreeRotationSubGizmo::ClickPress_IncrementalSphereBound(const FInputDeviceRay& PressPos)
{
if (UnscaledSphereRadius == 0)
{
return false;
}
FVector SphereCenter = AxisSource->GetOrigin();
double LengthScale = UE::GizmoRenderingUtil::CalculateLocalPixelToWorldScale(GizmoViewContext, SphereCenter);
InteractionSphereRadius = UnscaledSphereRadius * LengthScale;
bool bIntersects;
GizmoMath::RaySphereIntersection(AxisSource->GetOrigin(),
InteractionSphereRadius, PressPos.WorldRay.Origin, PressPos.WorldRay.Direction,
bIntersects, LastSphereIntersectionPoint);
return bIntersects;
}
// Called from OnClickDrag if mode is IncrementalSphereBound
bool UFreeRotationSubGizmo::ClickDrag_IncrementalSphereBound(const FInputDeviceRay& DragPos)
{
if (InteractionSphereRadius == 0)
{
return false;
}
FVector SphereCenter = AxisSource->GetOrigin();
bool bIntersects = false;
FVector CurrentSphereIntersectionPoint;
GizmoMath::RaySphereIntersection(SphereCenter,
InteractionSphereRadius, DragPos.WorldRay.Origin, DragPos.WorldRay.Direction,
bIntersects, CurrentSphereIntersectionPoint);
if (!bIntersects)
{
// If we didn't hit the sphere itself, raycast the plane
FVector PlaneHitPoint;
GizmoMath::RayPlaneIntersectionPoint(
AxisSource->GetOrigin(), AxisSource->GetDirection(),
DragPos.WorldRay.Origin, DragPos.WorldRay.Direction,
bIntersects, PlaneHitPoint);
if (!bIntersects)
{
return false;
}
// Find the point on the sphere that is closest to the hit plane point
FVector TowardSphereVector = SphereCenter - PlaneHitPoint;
TowardSphereVector.Normalize();
GizmoMath::RaySphereIntersection(SphereCenter,
InteractionSphereRadius, PlaneHitPoint, TowardSphereVector,
bIntersects, CurrentSphereIntersectionPoint);
}
if (!ensure(bIntersects))
{
return false;
}
// Find the angle that we've rotated the sphere from LastSphereIntersectionPoint to current
FVector ToLastSpherePoint = (LastSphereIntersectionPoint - SphereCenter) / InteractionSphereRadius;
FVector ToCurrentSpherePoint = (CurrentSphereIntersectionPoint - SphereCenter) / InteractionSphereRadius;
FVector RotationAxis = ToLastSpherePoint.Cross(ToCurrentSpherePoint);
if (!RotationAxis.Normalize())
{
return false;
}
double RotationAngle = UE::Geometry::AngleR(ToLastSpherePoint, ToCurrentSpherePoint);
// Apply the new transform to the transform source
FTransform CurrentTransform = TransformSource->GetTransform();
FQuat RotationToApply(RotationAxis, RotationAngle);
CurrentTransform.SetRotation(RotationToApply * CurrentTransform.GetRotation());
TransformSource->SetTransform(CurrentTransform);
LastSphereIntersectionPoint = CurrentSphereIntersectionPoint;
return true;
}
void UFreeRotationSubGizmo::Render(IToolsContextRenderAPI* RenderAPI)
{
using namespace FreeRotationSubGizmoLocals;
if (bShowSphereBoundsDuringInteraction && bInInteraction)
{
FVector3d SphereCenter = AxisSource->GetOrigin();
double LengthScale = UE::GizmoRenderingUtil::CalculateLocalPixelToWorldScale(RenderAPI->GetSceneView(), SphereCenter);
double SphereRadius = UnscaledSphereRadius * LengthScale;
FToolDataVisualizer Drawer;
Drawer.BeginFrame(RenderAPI, RenderAPI->GetCameraState());
Drawer.DrawViewFacingCircle(SphereCenter, SphereRadius, CircleNumSections,
CircleColor, CircleThickness,/*bDepthTested*/ false);
Drawer.EndFrame();
}
}
void UFreeRotationSubGizmo::Tick(float DeltaTime)
{
if (CustomTickFunction)
{
CustomTickFunction(DeltaTime);
}
}
FInputRayHit UFreeRotationSubGizmo::CanBeginClickDragSequence(const FInputDeviceRay& PressPos)
{
FInputRayHit GizmoHit;
if (HitTarget && AxisSource && TransformSource)
{
GizmoHit = HitTarget->IsHit(PressPos);
}
return GizmoHit;
}
void UFreeRotationSubGizmo::OnClickPress(const FInputDeviceRay& PressPos)
{
bInInteraction = ClickPress_IncrementalSphereBound(PressPos);
if (!bInInteraction)
{
return;
}
if (StateTarget)
{
StateTarget->BeginUpdate();
}
if (ensure(HitTarget))
{
HitTarget->UpdateInteractingState(bInInteraction);
}
}
void UFreeRotationSubGizmo::OnClickDrag(const FInputDeviceRay& DragPos)
{
if (!bInInteraction)
{
return;
}
ClickDrag_IncrementalSphereBound(DragPos);
}
void UFreeRotationSubGizmo::OnClickRelease(const FInputDeviceRay& ReleasePos)
{
OnTerminateDragSequence();
}
void UFreeRotationSubGizmo::OnTerminateDragSequence()
{
if (!bInInteraction)
{
return;
}
if (StateTarget)
{
StateTarget->EndUpdate();
}
bInInteraction = false;
if (ensure(HitTarget))
{
HitTarget->UpdateInteractingState(bInInteraction);
}
}
FInputRayHit UFreeRotationSubGizmo::BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos)
{
FInputRayHit GizmoHit;
if (HitTarget)
{
GizmoHit = HitTarget->IsHit(PressPos);
}
return GizmoHit;
}
void UFreeRotationSubGizmo::OnBeginHover(const FInputDeviceRay& DevicePos)
{
HitTarget->UpdateHoverState(true);
}
bool UFreeRotationSubGizmo::OnUpdateHover(const FInputDeviceRay& DevicePos)
{
// not necessary...
HitTarget->UpdateHoverState(true);
return true;
}
void UFreeRotationSubGizmo::OnEndHover()
{
HitTarget->UpdateHoverState(false);
}