383 lines
11 KiB
C++
383 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ScalableConeGizmo.h"
|
|
#include "BaseGizmos/GizmoMath.h"
|
|
#include "BaseGizmos/GizmoRenderingUtil.h"
|
|
#include "BaseBehaviors/MouseHoverBehavior.h"
|
|
#include "BaseGizmos/TransformProxy.h"
|
|
#include "PrimitiveDrawingUtils.h"
|
|
#include "Engine/HitResult.h"
|
|
#include "InteractiveGizmoManager.h"
|
|
#include "ToolContextInterfaces.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(ScalableConeGizmo)
|
|
|
|
#define LOCTEXT_NAMESPACE "UScalableSphereGizmo"
|
|
|
|
// UScalableConeGizmoBuilder
|
|
|
|
UInteractiveGizmo* UScalableConeGizmoBuilder::BuildGizmo(const FToolBuilderState& SceneState) const
|
|
{
|
|
UScalableConeGizmo* NewGizmo = NewObject<UScalableConeGizmo>(SceneState.GizmoManager);
|
|
return NewGizmo;
|
|
}
|
|
// UScalableConeGizmo
|
|
|
|
void UScalableConeGizmo::Setup()
|
|
{
|
|
UInteractiveGizmo::Setup();
|
|
|
|
Length = 1000.0f;
|
|
Angle = 45.f;
|
|
MaxAngle = 90.f;
|
|
MinAngle = 0.f;
|
|
ConeColor = FColor(200, 255, 255);
|
|
|
|
TransactionDescription = LOCTEXT("ScalableConeGizmo", "Scale Cone Gizmo");
|
|
|
|
UScalableConeGizmoInputBehavior* ScalableConeBehavior = NewObject<UScalableConeGizmoInputBehavior>(this);
|
|
ScalableConeBehavior->Initialize(this);
|
|
AddInputBehavior(ScalableConeBehavior);
|
|
|
|
UMouseHoverBehavior* HoverBehavior = NewObject<UMouseHoverBehavior>(this);
|
|
HoverBehavior->Initialize(this);
|
|
AddInputBehavior(HoverBehavior);
|
|
}
|
|
|
|
void UScalableConeGizmo::Render(IToolsContextRenderAPI* RenderAPI)
|
|
{
|
|
if (ActiveTarget)
|
|
{
|
|
DrawWireSphereCappedCone(RenderAPI->GetPrimitiveDrawInterface(), ActiveTarget->GetTransform(), Length, Angle, 32, 8, 10, ConeColor, SDPG_Foreground);
|
|
|
|
// Draw the yellow circle as a hightlight when hovering or dragging
|
|
if (bIsHovering || bIsDragging)
|
|
{
|
|
FVector WorldOrigin = ActiveTarget->GetTransform().GetLocation();
|
|
FVector CircleNormal = ActiveTarget->GetTransform().GetRotation().Vector();
|
|
|
|
float ConeHeight = Length * FMath::Cos(FMath::DegreesToRadians(Angle));
|
|
float ConeRadius = Length * FMath::Sin(FMath::DegreesToRadians(Angle));
|
|
|
|
// The center of the circle at the base of the cone
|
|
FVector CircleOrigin = WorldOrigin + CircleNormal * ConeHeight;
|
|
|
|
FVector CircleX;
|
|
FVector CircleY;
|
|
|
|
// Direction vectors to represent the circle
|
|
GizmoMath::MakeNormalPlaneBasis(CircleNormal, CircleX, CircleY);
|
|
|
|
DrawCircle(RenderAPI->GetPrimitiveDrawInterface(), CircleOrigin, CircleX, CircleY, FLinearColor::Yellow, ConeRadius, 32, SDPG_Foreground, 0, 0.1f);
|
|
}
|
|
|
|
// Draw the lines to show which direction to drag in while hovering
|
|
if (bIsHovering)
|
|
{
|
|
// Parameters for the line that shows the drag direction
|
|
FVector LineStart = DragCurrentPositionProjected;
|
|
float LineLength = 30.f;
|
|
FVector LineEnd = LineStart + HitAxis * LineLength;
|
|
|
|
// Get the Pixel to World scale of the line
|
|
const FSceneView* View = RenderAPI->GetSceneView();
|
|
float PixelToWorld = UE::GizmoRenderingUtil::CalculateLocalPixelToWorldScale(View, LineEnd);
|
|
|
|
// Draw the lines in both directions
|
|
RenderAPI->GetPrimitiveDrawInterface()->DrawLine(LineStart, LineStart + HitAxis * PixelToWorld * LineLength, FLinearColor::Red, SDPG_Foreground);
|
|
RenderAPI->GetPrimitiveDrawInterface()->DrawLine(LineStart, LineStart - HitAxis * PixelToWorld * LineLength, FLinearColor::Red, SDPG_Foreground);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UScalableConeGizmo::SetTarget(UTransformProxy* InTarget)
|
|
{
|
|
ActiveTarget = InTarget;
|
|
}
|
|
|
|
void UScalableConeGizmo::SetAngleDegrees(float InAngle)
|
|
{
|
|
Angle = InAngle;
|
|
|
|
Angle = FMath::Clamp<float>(Angle, MinAngle, MaxAngle);
|
|
|
|
if (UpdateAngleFunc)
|
|
{
|
|
UpdateAngleFunc(InAngle);
|
|
}
|
|
}
|
|
|
|
void UScalableConeGizmo::SetLength(float InLength)
|
|
{
|
|
Length = InLength;
|
|
}
|
|
|
|
float UScalableConeGizmo::GetLength()
|
|
{
|
|
return Length;
|
|
}
|
|
|
|
float UScalableConeGizmo::GetAngleDegrees()
|
|
{
|
|
return Angle;
|
|
}
|
|
|
|
FInputRayHit UScalableConeGizmo::BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos)
|
|
{
|
|
FHitResult HitResult;
|
|
FVector TestHitAxis;
|
|
FTransform DragTransform;
|
|
|
|
if (HitTest(PressPos.WorldRay, HitResult, TestHitAxis, DragTransform))
|
|
{
|
|
bIsHovering = true;
|
|
DragStartWorldPosition = DragTransform.GetLocation();
|
|
DragCurrentPositionProjected = DragStartWorldPosition;
|
|
HitAxis = TestHitAxis;
|
|
|
|
return FInputRayHit(HitResult.Distance);
|
|
}
|
|
|
|
bIsHovering = false;
|
|
// Return invalid ray hit to say we don't want to listen to hover input
|
|
return FInputRayHit();
|
|
}
|
|
|
|
bool UScalableConeGizmo::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 TestHitAxis;
|
|
FTransform DragTransform;
|
|
|
|
if (HitTest(HitCheckRay, HitResult, TestHitAxis, DragTransform))
|
|
{
|
|
bIsHovering = true;
|
|
DragStartWorldPosition = DragTransform.GetLocation();
|
|
DragCurrentPositionProjected = DragStartWorldPosition;
|
|
HitAxis = TestHitAxis;
|
|
return true;
|
|
}
|
|
|
|
bIsHovering = false;
|
|
return false;
|
|
}
|
|
|
|
void UScalableConeGizmo::OnEndHover()
|
|
{
|
|
bIsHovering = false;
|
|
}
|
|
|
|
bool UScalableConeGizmo::HitTest(const FRay& Ray, FHitResult& OutHit, FVector& OutAxis, FTransform& OutTransform)
|
|
{
|
|
if (!ActiveTarget)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FVector Start = Ray.Origin;
|
|
const float MaxRaycastDistance = 1e6f;
|
|
FVector End = Ray.Origin + Ray.Direction * MaxRaycastDistance;
|
|
|
|
// 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 CircleNormal = ActiveTarget->GetTransform().GetRotation().Vector();
|
|
|
|
float ConeHeight = Length * FMath::Cos(FMath::DegreesToRadians(Angle));
|
|
float ConeRadius = Length * FMath::Sin(FMath::DegreesToRadians(Angle));
|
|
|
|
// The center of the base of the cone
|
|
FVector CircleOrigin = WorldOrigin + CircleNormal * ConeHeight;
|
|
|
|
FVector ConeBottom = WorldOrigin + CircleNormal * Length;
|
|
|
|
FVector HitPos;
|
|
bool bIntersects = false;
|
|
|
|
// Figure out if the ray interesects with the plane at the base of the cone
|
|
GizmoMath::RayPlaneIntersectionPoint(CircleOrigin, CircleNormal, Ray.Origin, Ray.Direction, bIntersects, HitPos);
|
|
|
|
if (!bIntersects || Ray.GetParameter(HitPos) > Ray.GetParameter(End))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Find the closest point on the circle of the intersection
|
|
FVector NearestCircle;
|
|
GizmoMath::ClosetPointOnCircle(HitPos, CircleOrigin, CircleNormal, ConeRadius, NearestCircle);
|
|
|
|
FVector NearestRay = Ray.ClosestPoint(NearestCircle);
|
|
|
|
// Make sure the distance to the ray is within a certain threshold
|
|
double Distance = FVector::Distance(NearestCircle, NearestRay);
|
|
|
|
if (Distance > HitErrorThreshold)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OutAxis = NearestCircle - ConeBottom;
|
|
OutAxis.Normalize();
|
|
|
|
OutTransform.SetIdentity();
|
|
OutTransform.SetTranslation(NearestCircle);
|
|
return true;
|
|
}
|
|
|
|
void UScalableConeGizmo::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;
|
|
FTransform DragTransform;
|
|
FVector HitTestAxis;
|
|
|
|
// Check if any component was hit
|
|
if (HitTest(HitCheckRay, HitResult, HitTestAxis, DragTransform))
|
|
{
|
|
HitAxis = HitTestAxis;
|
|
|
|
FVector RayNearestPt; float RayNearestParam;
|
|
|
|
// Get the initial interaction parameters
|
|
GizmoMath::NearestPointOnLineToRay(DragTransform.GetLocation(), HitAxis,
|
|
Ray.WorldRay.Origin, Ray.WorldRay.Direction,
|
|
InteractionStartPoint, InteractionStartParameter,
|
|
RayNearestPt, RayNearestParam);
|
|
|
|
DragStartWorldPosition = DragTransform.GetLocation();
|
|
DragCurrentPositionProjected = DragStartWorldPosition;
|
|
|
|
bIsDragging = true;
|
|
|
|
GetGizmoManager()->BeginUndoTransaction(TransactionDescription);
|
|
}
|
|
}
|
|
|
|
void UScalableConeGizmo::OnUpdateDrag(const FInputDeviceRay& Ray)
|
|
{
|
|
FVector AxisNearestPt; float AxisNearestParam;
|
|
FVector RayNearestPt; float RayNearestParam;
|
|
|
|
// Get the current interaction parameters
|
|
GizmoMath::NearestPointOnLineToRay(DragStartWorldPosition, HitAxis,
|
|
Ray.WorldRay.Origin, Ray.WorldRay.Direction,
|
|
AxisNearestPt, AxisNearestParam,
|
|
RayNearestPt, RayNearestParam);
|
|
|
|
FVector GizmoLocation = ActiveTarget->GetTransform().GetLocation();
|
|
|
|
// Vector to the starting position of the interaction
|
|
FVector Start = InteractionStartPoint - GizmoLocation;
|
|
Start.Normalize();
|
|
|
|
// Vector to the ending position of the interaction
|
|
FVector End = AxisNearestPt - GizmoLocation;
|
|
End.Normalize();
|
|
|
|
float DotP = static_cast<float>(FVector::DotProduct(Start, End));
|
|
|
|
float DeltaAngle = FMath::Acos(DotP);
|
|
|
|
// Get the angle between the start and end vectors and the forward vector to check if the drag direction should be +ve or -ve
|
|
|
|
FVector Forward = ActiveTarget->GetTransform().GetRotation().Vector();
|
|
|
|
float StartAngle = FMath::Acos(static_cast<float>(FVector::DotProduct(Start, Forward)));
|
|
float EndAngle = FMath::Acos(static_cast<float>(FVector::DotProduct(End, Forward)));
|
|
|
|
if (StartAngle > EndAngle)
|
|
{
|
|
DeltaAngle = -DeltaAngle;
|
|
}
|
|
|
|
SetAngleDegrees(Angle + FMath::RadiansToDegrees(DeltaAngle));
|
|
|
|
InteractionStartPoint = AxisNearestPt;
|
|
DragCurrentPositionProjected = AxisNearestPt;
|
|
InteractionStartParameter = AxisNearestParam;
|
|
}
|
|
|
|
void UScalableConeGizmo::OnEndDrag(const FInputDeviceRay& Ray)
|
|
{
|
|
GetGizmoManager()->EndUndoTransaction();
|
|
bIsDragging = false;
|
|
}
|
|
|
|
// UScalableConeGizmoInputBehavior
|
|
|
|
void UScalableConeGizmoInputBehavior::Initialize(UScalableConeGizmo* InGizmo)
|
|
{
|
|
Gizmo = InGizmo;
|
|
}
|
|
|
|
FInputCaptureRequest UScalableConeGizmoInputBehavior::WantsCapture(const FInputDeviceState& input)
|
|
{
|
|
if (IsPressed(input))
|
|
{
|
|
FHitResult HitResult;
|
|
FVector HitTestAxis;
|
|
FTransform DragTransform;
|
|
if (Gizmo->HitTest(input.Mouse.WorldRay, HitResult, HitTestAxis, DragTransform))
|
|
{
|
|
return FInputCaptureRequest::Begin(this, EInputCaptureSide::Any, HitResult.Distance);
|
|
}
|
|
}
|
|
|
|
return FInputCaptureRequest::Ignore();
|
|
}
|
|
|
|
FInputCaptureUpdate UScalableConeGizmoInputBehavior::BeginCapture(const FInputDeviceState& input, EInputCaptureSide eSide)
|
|
{
|
|
FInputDeviceRay DeviceRay(input.Mouse.WorldRay, input.Mouse.Position2D);
|
|
LastWorldRay = DeviceRay.WorldRay;
|
|
LastScreenPosition = DeviceRay.ScreenPosition;
|
|
|
|
Gizmo->OnBeginDrag(DeviceRay);
|
|
bInputDragCaptured = true;
|
|
return FInputCaptureUpdate::Begin(this, EInputCaptureSide::Any);
|
|
}
|
|
|
|
FInputCaptureUpdate UScalableConeGizmoInputBehavior::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();
|
|
}
|
|
|
|
Gizmo->OnUpdateDrag(FInputDeviceRay(LastWorldRay));
|
|
|
|
return FInputCaptureUpdate::Continue();
|
|
}
|
|
|
|
void UScalableConeGizmoInputBehavior::ForceEndCapture(const FInputCaptureData& data)
|
|
{
|
|
if (bInputDragCaptured)
|
|
{
|
|
bInputDragCaptured = false;
|
|
Gizmo->OnEndDrag(FInputDeviceRay(LastWorldRay));
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|