Files
UnrealEngineUWP/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseGizmos/ScalableSphereGizmo.cpp
michael balzer 2b10993563 Move InteractiveToolsFramework and GeometryFramework out of Experimental
#jira UETOOL-3823
#rb brooke.hubert
#preflight 6109d1e9b4288d0001acb7ef

[CL 17055606 by michael balzer in ue5-main branch]
2021-08-04 13:58:55 -04:00

360 lines
9.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BaseGizmos/ScalableSphereGizmo.h"
#include "InteractiveGizmoManager.h"
#include "SceneManagement.h"
#include "BaseGizmos/GizmoBoxComponent.h"
#include "Components/SphereComponent.h"
#include "Engine/CollisionProfile.h"
#include "Kismet/KismetMathLibrary.h"
#include "BaseGizmos/GizmoMath.h"
#include "BaseBehaviors/MouseHoverBehavior.h"
#include "BaseGizmos/GizmoRenderingUtil.h"
#define LOCTEXT_NAMESPACE "UScalableSphereGizmo"
// UScalableSphereGizmoBuilder
UInteractiveGizmo* UScalableSphereGizmoBuilder::BuildGizmo(const FToolBuilderState& SceneState) const
{
UScalableSphereGizmo* NewGizmo = NewObject<UScalableSphereGizmo>(SceneState.GizmoManager);
return NewGizmo;
}
// UScalableSphereGizmo
void UScalableSphereGizmo::Setup()
{
UInteractiveGizmo::Setup();
Radius = 1000.0f;
TransactionDescription = LOCTEXT("ScalableSphereGizmo", "Scale Sphere Gizmo");
UScalableSphereGizmoInputBehavior* ScalableSphereBehavior = NewObject<UScalableSphereGizmoInputBehavior>(this);
ScalableSphereBehavior->Initialize(this);
AddInputBehavior(ScalableSphereBehavior);
UMouseHoverBehavior* HoverBehavior = NewObject<UMouseHoverBehavior>(this);
HoverBehavior->Initialize(this);
AddInputBehavior(HoverBehavior);
}
void UScalableSphereGizmo::Render(IToolsContextRenderAPI* RenderAPI)
{
if (ActiveTarget)
{
FColor UseColor = FColor(200, 255, 255);
// Draw the gizmo in yellow and draw lines showing drag direction if hovering or dragging
if (bIsHovering || bIsDragging)
{
UseColor = FColor::Yellow;
// Parameters for the line that shows the drag direction
FVector LineStart = DragCurrentPositionProjected;
float LineLength = 30.f;
FVector LineEnd = LineStart + ActiveAxis * LineLength;
// Get the Pixel to World scale of the line
const FSceneView* View = RenderAPI->GetSceneView();
float PixelToWorld = GizmoRenderingUtil::CalculateLocalPixelToWorldScale(View, LineEnd);
// Draw the lines in both directions
RenderAPI->GetPrimitiveDrawInterface()->DrawLine(LineStart, LineStart + ActiveAxis * PixelToWorld * LineLength, FLinearColor::Red, SDPG_Foreground);
RenderAPI->GetPrimitiveDrawInterface()->DrawLine(LineStart, LineStart - ActiveAxis * PixelToWorld * LineLength, FLinearColor::Red, SDPG_Foreground);
}
DrawWireSphereAutoSides(RenderAPI->GetPrimitiveDrawInterface(), ActiveTarget->GetTransform(), UseColor, Radius, SDPG_Foreground);
}
}
FInputRayHit UScalableSphereGizmo::BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos)
{
FHitResult HitResult;
FVector HitAxis;
FTransform DragTransform;
if (HitTest(PressPos.WorldRay, HitResult, HitAxis, DragTransform))
{
bIsHovering = true;
DragStartWorldPosition = DragTransform.GetLocation();
DragCurrentPositionProjected = DragStartWorldPosition;
ActiveAxis = HitAxis;
return FInputRayHit(HitResult.Distance);
}
bIsHovering = false;
// Return invalid ray hit to say we don't want to listen to hover input
return FInputRayHit();
}
bool UScalableSphereGizmo::OnUpdateHover(const FInputDeviceRay& DevicePos)
{
if (!ActiveTarget)
{
bIsHovering = false;
return false;
}
FVector Start = DevicePos.WorldRay.Origin;
const float MaxRaycastDistance = 1e6f;
FVector End = DevicePos.WorldRay.Origin + DevicePos.WorldRay.Direction * MaxRaycastDistance;
FRay HitCheckRay(Start, End - Start);
FHitResult HitResult;
FVector HitAxis;
FTransform DragTransform;
if (HitTest(HitCheckRay, HitResult, HitAxis, DragTransform))
{
bIsHovering = true;
DragStartWorldPosition = DragTransform.GetLocation();
DragCurrentPositionProjected = DragStartWorldPosition;
ActiveAxis = HitAxis;
return true;
}
bIsHovering = false;
return false;
}
void UScalableSphereGizmo::OnEndHover()
{
bIsHovering = false;
}
void UScalableSphereGizmo::SetTarget(UTransformProxy* InTarget)
{
ActiveTarget = InTarget;
}
bool UScalableSphereGizmo::CheckCircleIntersection(const FRay& Ray, FVector CircleNormal, FVector& OutHitLocation, FVector& OutHitAxis)
{
// Find the intresection with the circle plane. Note that unlike the FMath version, GizmoMath::RayPlaneIntersectionPoint()
// checks that the ray isn't parallel to the plane.
FVector WorldOrigin = ActiveTarget->GetTransform().GetLocation();
FVector Start = Ray.Origin;
const float MaxRaycastDistance = 1e6f;
FVector End = Ray.Origin + Ray.Direction * MaxRaycastDistance;
FVector HitPos;
bool bIntersects = false;
// Figure out if the ray intersects the circle plane
GizmoMath::RayPlaneIntersectionPoint(WorldOrigin, CircleNormal, Ray.Origin, Ray.Direction, bIntersects, HitPos);
if (!bIntersects || Ray.GetParameter(HitPos) > Ray.GetParameter(End))
{
return false;
}
// Find the point on the circle closest to the intersection
FVector NearestCircle;
GizmoMath::ClosetPointOnCircle(HitPos, WorldOrigin, CircleNormal, Radius, NearestCircle);
FVector NearestRay = Ray.ClosestPoint(NearestCircle);
// Make sure the point is within a certain distance of the ray
double Distance = FVector::Distance(NearestCircle, NearestRay);
if (Distance > HitErrorThreshold)
{
return false;
}
OutHitAxis = NearestCircle - WorldOrigin;
OutHitAxis.Normalize();
OutHitLocation = NearestCircle;
return true;
}
bool UScalableSphereGizmo::HitTest(const FRay& Ray, FHitResult& OutHit, FVector& OutAxis, FTransform &OutTransform)
{
if (!ActiveTarget)
{
return false;
}
FVector OutHitLocation;
FVector OutHitAxis;
if (CheckCircleIntersection(Ray, FVector::XAxisVector, OutHitLocation, OutHitAxis))
{
OutAxis = OutHitAxis;
OutTransform.SetIdentity();
OutTransform.SetTranslation(OutHitLocation);
return true;
}
if (CheckCircleIntersection(Ray, FVector::YAxisVector, OutHitLocation, OutHitAxis))
{
OutAxis = OutHitAxis;
OutTransform.SetIdentity();
OutTransform.SetTranslation(OutHitLocation);
return true;
}
if (CheckCircleIntersection(Ray, FVector::ZAxisVector, OutHitLocation, OutHitAxis))
{
OutAxis = OutHitAxis;
OutTransform.SetIdentity();
OutTransform.SetTranslation(OutHitLocation);
return true;
}
return false;
}
void UScalableSphereGizmo::SetRadius(float InRadius)
{
// Negative Radius not allowed
if (InRadius < 0)
{
InRadius = 0;
}
Radius = InRadius;
if (UpdateRadiusFunc)
{
UpdateRadiusFunc(Radius);
}
}
void UScalableSphereGizmo::OnBeginDrag(const FInputDeviceRay& Ray)
{
FVector Start = Ray.WorldRay.Origin;
const float MaxRaycastDistance = 1e6f;
FVector End = Ray.WorldRay.Origin + Ray.WorldRay.Direction * MaxRaycastDistance;
FRay HitCheckRay(Start, End - Start);
FHitResult HitResult;
FVector HitAxis;
FTransform DragTransform;
// Check if the Ray hit any of the handles
if (HitTest(HitCheckRay, HitResult, HitAxis, DragTransform))
{
ActiveAxis = HitAxis;
FVector RayNearestPt;
float RayNearestParam;
FVector InteractionStartPoint;
// Find the initial parameters along the hit axis
GizmoMath::NearestPointOnLineToRay(DragTransform.GetLocation(), ActiveAxis,
Ray.WorldRay.Origin, Ray.WorldRay.Direction,
InteractionStartPoint, InteractionStartParameter,
RayNearestPt, RayNearestParam);
DragStartWorldPosition = DragTransform.GetLocation();
DragCurrentPositionProjected = DragStartWorldPosition;
bIsDragging = true;
GetGizmoManager()->BeginUndoTransaction(TransactionDescription);
}
}
void UScalableSphereGizmo::OnUpdateDrag(const FInputDeviceRay& Ray)
{
FVector AxisNearestPt;
float AxisNearestParam;
FVector RayNearestPt;
float RayNearestParam;
// Find the parameters along the hit axis
GizmoMath::NearestPointOnLineToRay(DragStartWorldPosition, ActiveAxis,
Ray.WorldRay.Origin, Ray.WorldRay.Direction,
AxisNearestPt, AxisNearestParam,
RayNearestPt, RayNearestParam);
float InteractionCurrentParameter = AxisNearestParam;
float DeltaParam = InteractionCurrentParameter - InteractionStartParameter;
InteractionStartParameter = InteractionCurrentParameter;
DragCurrentPositionProjected = AxisNearestPt;
// Update the radius
SetRadius(Radius + DeltaParam);
}
void UScalableSphereGizmo::OnEndDrag(const FInputDeviceRay& Ray)
{
GetGizmoManager()->EndUndoTransaction();
bIsDragging = false;
}
// UScalableSphereGizmoInputBehavior
void UScalableSphereGizmoInputBehavior::Initialize(UScalableSphereGizmo* InGizmo)
{
Gizmo = InGizmo;
}
FInputCaptureRequest UScalableSphereGizmoInputBehavior::WantsCapture(const FInputDeviceState& input)
{
if (IsPressed(input))
{
FHitResult HitResult;
FVector HitAxis;
FTransform DragTransform;
if (Gizmo->HitTest(input.Mouse.WorldRay, HitResult, HitAxis, DragTransform))
{
return FInputCaptureRequest::Begin(this, EInputCaptureSide::Any, HitResult.Distance);
}
}
return FInputCaptureRequest::Ignore();
}
FInputCaptureUpdate UScalableSphereGizmoInputBehavior::BeginCapture(const FInputDeviceState& input, EInputCaptureSide eSide)
{
FInputDeviceRay DeviceRay(input.Mouse.WorldRay, input.Mouse.Position2D);
LastWorldRay = DeviceRay.WorldRay;
LastScreenPosition = DeviceRay.ScreenPosition;
// Forward behavior to the Gizmo
Gizmo->OnBeginDrag(DeviceRay);
bInputDragCaptured = true;
return FInputCaptureUpdate::Begin(this, EInputCaptureSide::Any);
}
FInputCaptureUpdate UScalableSphereGizmoInputBehavior::UpdateCapture(const FInputDeviceState& input, const FInputCaptureData& data)
{
FInputDeviceRay DeviceRay(input.Mouse.WorldRay, input.Mouse.Position2D);
LastWorldRay = DeviceRay.WorldRay;
LastScreenPosition = DeviceRay.ScreenPosition;
if (IsReleased(input))
{
bInputDragCaptured = false;
Gizmo->OnEndDrag(FInputDeviceRay(LastWorldRay));
return FInputCaptureUpdate::End();
}
// Forward behavior to the Gizmo
Gizmo->OnUpdateDrag(FInputDeviceRay(LastWorldRay));
return FInputCaptureUpdate::Continue();
}
void UScalableSphereGizmoInputBehavior::ForceEndCapture(const FInputCaptureData& data)
{
if (bInputDragCaptured)
{
bInputDragCaptured = false;
Gizmo->OnEndDrag(FInputDeviceRay(LastWorldRay));
}
}
#undef LOCTEXT_NAMESPACE