Files
2025-05-18 13:04:45 +08:00

371 lines
8.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BaseGizmos/GizmoElementArrow.h"
#include "BaseGizmos/GizmoElementBox.h"
#include "BaseGizmos/GizmoElementCone.h"
#include "BaseGizmos/GizmoElementCylinder.h"
#include "BaseGizmos/GizmoRenderingUtil.h"
#include "InputState.h"
#include "Materials/MaterialInterface.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GizmoElementArrow)
namespace UE::Private
{
void UpdatePixelThreshold(UGizmoElementBase* InGizmoElement, const float InPixelHitThreshold)
{
if (InGizmoElement)
{
InGizmoElement->SetPixelHitDistanceThreshold(InPixelHitThreshold);
}
}
using ThresholdGuard = TGuardValue_Bitfield_Cleanup<TFunction<void()>>;
}
UGizmoElementArrow::UGizmoElementArrow()
{
HeadType = EGizmoElementArrowHeadType::Cone;
CylinderElement = NewObject<UGizmoElementCylinder>();
ConeElement = NewObject<UGizmoElementCone>();
BoxElement = nullptr;
}
void UGizmoElementArrow::Render(IToolsContextRenderAPI* RenderAPI, const FRenderTraversalState& RenderState)
{
check(RenderAPI);
if (bUpdateArrowBody)
{
UpdateArrowBody();
}
if (bUpdateArrowHead)
{
UpdateArrowHead();
}
FRenderTraversalState CurrentRenderState(RenderState);
bool bVisibleViewDependent = UpdateRenderState(RenderAPI, Base, CurrentRenderState);
if (bVisibleViewDependent)
{
check(CylinderElement);
CylinderElement->Render(RenderAPI, CurrentRenderState);
if (HeadType == EGizmoElementArrowHeadType::Cone)
{
check(ConeElement);
ConeElement->Render(RenderAPI, CurrentRenderState);
}
else // (HeadType == EGizmoElementArrowHeadType::Cube)
{
check(BoxElement);
BoxElement->Render(RenderAPI, CurrentRenderState);
}
}
}
FInputRayHit UGizmoElementArrow::LineTrace(const UGizmoViewContext* ViewContext, const FLineTraceTraversalState& LineTraceState, const FVector& RayOrigin, const FVector& RayDirection)
{
using namespace UE::Private;
FLineTraceTraversalState CurrentLineTraceState(LineTraceState);
bool bHittableViewDependent = UpdateLineTraceState(ViewContext, Base, CurrentLineTraceState);
if (!bHittableViewDependent)
{
return FInputRayHit();
}
ThresholdGuard Guard([this]()
{
UpdatePixelThreshold(CylinderElement, PixelHitDistanceThreshold);
UpdatePixelThreshold(ConeElement, PixelHitDistanceThreshold);
UpdatePixelThreshold(BoxElement, PixelHitDistanceThreshold);
});
// update threshold if hitting the mask
if (UGizmoElementBase* HitMaskGizmo = HitMask.Get())
{
const FInputRayHit MaskHit = HitMaskGizmo->LineTrace(ViewContext, LineTraceState, RayOrigin, RayDirection);
if (MaskHit.bHit)
{
static constexpr float NoPixelHitThreshold = 0.f;
UpdatePixelThreshold(CylinderElement, NoPixelHitThreshold);
UpdatePixelThreshold(ConeElement, NoPixelHitThreshold);
UpdatePixelThreshold(BoxElement, NoPixelHitThreshold);
}
}
check(CylinderElement);
FInputRayHit Hit = CylinderElement->LineTrace(ViewContext, CurrentLineTraceState, RayOrigin, RayDirection);
if (!Hit.bHit)
{
if (HeadType == EGizmoElementArrowHeadType::Cone)
{
check(ConeElement);
Hit = ConeElement->LineTrace(ViewContext, CurrentLineTraceState, RayOrigin, RayDirection);
}
else // (HeadType == EGizmoElementArrowHeadType::Cube)
{
check(BoxElement);
Hit = BoxElement->LineTrace(ViewContext, CurrentLineTraceState, RayOrigin, RayDirection);
}
}
if (Hit.bHit)
{
Hit.SetHitObject(this);
Hit.HitIdentifier = PartIdentifier;
}
return Hit;
}
void UGizmoElementArrow::SetBase(const FVector& InBase)
{
if (Base != InBase)
{
Base = InBase;
bUpdateArrowBody = true;
bUpdateArrowHead = true;
}
}
FVector UGizmoElementArrow::GetBase() const
{
return Base;
}
void UGizmoElementArrow::SetDirection(const FVector& InDirection)
{
Direction = InDirection;
Direction.Normalize();
bUpdateArrowBody = true;
bUpdateArrowHead = true;
}
FVector UGizmoElementArrow::GetDirection() const
{
return Direction;
}
void UGizmoElementArrow::SetSideDirection(const FVector& InSideDirection)
{
SideDirection = InSideDirection;
SideDirection.Normalize();
bUpdateArrowHead = true;
}
FVector UGizmoElementArrow::GetSideDirection() const
{
return SideDirection;
}
void UGizmoElementArrow::SetBodyLength(float InBodyLength)
{
if (BodyLength != InBodyLength)
{
BodyLength = InBodyLength;
bUpdateArrowBody = true;
bUpdateArrowHead = true;
}
}
float UGizmoElementArrow::GetBodyLength() const
{
return BodyLength;
}
void UGizmoElementArrow::SetBodyRadius(float InBodyRadius)
{
if (BodyRadius != InBodyRadius)
{
BodyRadius = InBodyRadius;
bUpdateArrowBody = true;
bUpdateArrowHead = true;
}
}
float UGizmoElementArrow::GetBodyRadius() const
{
return BodyRadius;
}
void UGizmoElementArrow::SetHeadLength(float InHeadLength)
{
if (HeadLength != InHeadLength)
{
HeadLength = InHeadLength;
bUpdateArrowHead = true;
}
}
float UGizmoElementArrow::GetHeadLength() const
{
return HeadLength;
}
void UGizmoElementArrow::SetHeadRadius(float InHeadRadius)
{
if (HeadRadius != InHeadRadius)
{
HeadRadius = InHeadRadius;
bUpdateArrowHead = true;
}
}
float UGizmoElementArrow::GetHeadRadius() const
{
return HeadRadius;
}
void UGizmoElementArrow::SetNumSides(int32 InNumSides)
{
if (NumSides != InNumSides)
{
NumSides = InNumSides;
bUpdateArrowBody = true;
bUpdateArrowHead = true;
}
}
int32 UGizmoElementArrow::GetNumSides() const
{
return NumSides;
}
void UGizmoElementArrow::SetEndCaps(bool InEndCaps)
{
if (bEndCaps != InEndCaps)
{
bEndCaps = InEndCaps;
bUpdateArrowHead = true;
}
}
bool UGizmoElementArrow::GetEndCaps() const
{
return bEndCaps;
}
void UGizmoElementArrow::SetPixelHitDistanceThreshold(float InPixelHitDistanceThreshold)
{
if (PixelHitDistanceThreshold != InPixelHitDistanceThreshold)
{
PixelHitDistanceThreshold = InPixelHitDistanceThreshold;
bUpdateArrowBody = true;
bUpdateArrowHead = true;
}
}
void UGizmoElementArrow::SetHitMask(const TWeakObjectPtr<UGizmoElementBase>& InHitMask)
{
HitMask = InHitMask;
}
void UGizmoElementArrow::SetHeadType(EGizmoElementArrowHeadType InHeadType)
{
if (InHeadType != HeadType)
{
HeadType = InHeadType;
if (HeadType == EGizmoElementArrowHeadType::Cone)
{
ConeElement = NewObject<UGizmoElementCone>();
BoxElement = nullptr;
}
else // (HeadType == EGizmoElementArrowHeadType::Cube)
{
BoxElement = NewObject<UGizmoElementBox>();
ConeElement = nullptr;
}
UpdateArrowHead();
}
}
EGizmoElementArrowHeadType UGizmoElementArrow::GetHeadType() const
{
return HeadType;
}
void UGizmoElementArrow::SetIsDashed(bool bInDashing)
{
if (ensure(CylinderElement))
{
CylinderElement->SetIsDashed(bInDashing);
}
}
bool UGizmoElementArrow::IsDashed() const
{
if (ensure(CylinderElement))
{
return CylinderElement->GetIsDashed();
}
return false;
}
void UGizmoElementArrow::SetDashParameters(const float InDashLength, const TOptional<float>& InGapLength)
{
if (ensure(CylinderElement))
{
CylinderElement->SetDashParameters(InDashLength, InGapLength);
}
}
void UGizmoElementArrow::GetDashParameters(float& OutDashLength, float& OutGapLength) const
{
if (ensure(CylinderElement))
{
CylinderElement->GetDashParameters(OutDashLength, OutGapLength);
}
}
void UGizmoElementArrow::UpdateArrowBody()
{
CylinderElement->SetBase(FVector::ZeroVector);
CylinderElement->SetDirection(Direction);
CylinderElement->SetHeight(BodyLength);
CylinderElement->SetNumSides(NumSides);
CylinderElement->SetRadius(BodyRadius);
CylinderElement->SetPixelHitDistanceThreshold(PixelHitDistanceThreshold);
bUpdateArrowBody = false;
}
void UGizmoElementArrow::UpdateArrowHead()
{
if (HeadType == EGizmoElementArrowHeadType::Cone)
{
check(ConeElement);
// head length is multiplied by 0.9f to prevent gap between body cylinder and head cone
ConeElement->SetOrigin(Direction * (BodyLength + HeadLength * 0.9f));
ConeElement->SetDirection(-Direction);
ConeElement->SetHeight(HeadLength);
ConeElement->SetRadius(HeadRadius);
ConeElement->SetNumSides(NumSides);
ConeElement->SetElementInteractionState(ElementInteractionState);
ConeElement->SetPixelHitDistanceThreshold(PixelHitDistanceThreshold);
ConeElement->SetEndCaps(bEndCaps);
}
else // (HeadType == EGizmoElementArrowHeadType::Cube)
{
check(BoxElement);
BoxElement->SetCenter(Direction * (BodyLength + HeadLength * 0.5f));
BoxElement->SetUpDirection(Direction);
BoxElement->SetSideDirection(SideDirection);
BoxElement->SetDimensions(FVector(HeadLength, HeadLength, HeadLength));
BoxElement->SetElementInteractionState(ElementInteractionState);
BoxElement->SetPixelHitDistanceThreshold(PixelHitDistanceThreshold);
}
bUpdateArrowHead = false;
}