// 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>; } UGizmoElementArrow::UGizmoElementArrow() { HeadType = EGizmoElementArrowHeadType::Cone; CylinderElement = NewObject(); ConeElement = NewObject(); 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& InHitMask) { HitMask = InHitMask; } void UGizmoElementArrow::SetHeadType(EGizmoElementArrowHeadType InHeadType) { if (InHeadType != HeadType) { HeadType = InHeadType; if (HeadType == EGizmoElementArrowHeadType::Cone) { ConeElement = NewObject(); BoxElement = nullptr; } else // (HeadType == EGizmoElementArrowHeadType::Cube) { BoxElement = NewObject(); 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& 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; }