// Copyright Epic Games, Inc. All Rights Reserved. #include "BaseGizmos/GizmoElementCylinder.h" #include "GizmoPrivateUtil.h" #include "BaseGizmos/GizmoRenderingUtil.h" #include "BaseGizmos/GizmoMath.h" #include "InputState.h" #include "Materials/MaterialInterface.h" #include "PrimitiveDrawingUtils.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(GizmoElementCylinder) namespace GizmoElementCylinderLocals { /** * Normalizes the given dash parameters to fit the input range. * @param OutScaleFactor the scale factor to apply to the dash length and gap length. * @return the number of dashes that fit within the range. */ template < typename RealType = float> int32 GetDashNumAndScale(const RealType& InLineLength, const RealType& InDashLength, const RealType& InDashGapLength, RealType& OutScaleFactor) { const RealType DashLengthAndGap = InDashLength + InDashGapLength; OutScaleFactor = static_cast(1); // Number based on sequence of dash, gap pairs, and last dash. This should always be an even number for fit alignment const int32 NumDashes = FMath::Max(2, FMath::CeilToInt32((InLineLength / DashLengthAndGap))); // Get a scale factor based on how many dashes with gaps (and an end dash) can actually fit on this line OutScaleFactor = InLineLength / (((NumDashes - 1) * DashLengthAndGap) + InDashLength); return NumDashes; } } void UGizmoElementCylinder::Render(IToolsContextRenderAPI* RenderAPI, const FRenderTraversalState& RenderState) { FRenderTraversalState CurrentRenderState(RenderState); bool bVisibleViewDependent = UpdateRenderState(RenderAPI, Base, CurrentRenderState); if (bVisibleViewDependent) { if (const UMaterialInterface* UseMaterial = CurrentRenderState.GetCurrentMaterial()) { const FQuat Rotation = FRotationMatrix::MakeFromZ(Direction).ToQuat(); const double HalfHeight = Height * 0.5; const FVector OriginOffset = Direction * HalfHeight; FTransform RenderLocalToWorldTransform = FTransform(Rotation, OriginOffset) * CurrentRenderState.LocalToWorldTransform; FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface(); if (bIsDashed) { // Get a scale factor based on how many dashes with gaps (and an end dash) can actually fit on this line float DashLengthScale = 0.0f; // Number based on sequence of dash, gap pairs, and last dash const int32 NumDashes = GizmoElementCylinderLocals::GetDashNumAndScale(FMath::Max(0.0f, Height - DashLength), DashLength, DashGapLength, DashLengthScale); const float AdjustedDashLength = DashLength * DashLengthScale; const float AdjustDashGapLength = DashGapLength * DashLengthScale; const float DashLengthAndGaps = (AdjustedDashLength + AdjustDashGapLength); const float HalfDashLength = AdjustedDashLength * 0.5f; for (int32 DashIndex = 0; DashIndex <= NumDashes; ++DashIndex) { DrawCylinder( PDI, RenderLocalToWorldTransform.ToMatrixWithScale(), (-FVector::UpVector * HalfHeight) + FVector::UpVector * (HalfDashLength + (DashIndex * DashLengthAndGaps)), FVector(1, 0, 0), FVector(0, 1, 0), FVector(0, 0, 1), Radius, HalfDashLength, NumSides, UseMaterial->GetRenderProxy(), SDPG_Foreground); } } else { DrawCylinder( PDI, RenderLocalToWorldTransform.ToMatrixWithScale(), FVector::ZeroVector, FVector(1, 0, 0), FVector(0, 1, 0), FVector(0, 0, 1), Radius, HalfHeight, NumSides, UseMaterial->GetRenderProxy(), SDPG_Foreground); } } } } FInputRayHit UGizmoElementCylinder::LineTrace(const UGizmoViewContext* ViewContext, const FLineTraceTraversalState& LineTraceState, const FVector& RayOrigin, const FVector& RayDirection) { FLineTraceTraversalState CurrentLineTraceState(LineTraceState); bool bHittableViewDependent = UpdateLineTraceState(ViewContext, Base, CurrentLineTraceState); if (bHittableViewDependent) { bool bIntersects = false; double RayParam = 0.0; const double PixelHitThresholdAdjust = CurrentLineTraceState.PixelToWorldScale * PixelHitDistanceThreshold; const double WorldHeight = Height * CurrentLineTraceState.LocalToWorldTransform.GetScale3D().X + PixelHitThresholdAdjust * 2.0; const double WorldRadius = Radius * CurrentLineTraceState.LocalToWorldTransform.GetScale3D().X + PixelHitThresholdAdjust; const FVector WorldDirection = CurrentLineTraceState.LocalToWorldTransform.TransformVectorNoScale(Direction); const FVector LocalCenter = Direction * Height * 0.5; const FVector WorldCenter = CurrentLineTraceState.LocalToWorldTransform.TransformPosition(LocalCenter); // due to numerical imprecision, the ray origin needs to be clamped in ortho views // (cf. UEditorInteractiveToolsContext::GetRayFromMousePos) FVector ClampedRayOrigin(RayOrigin); const double DepthBias = UE::GizmoUtil::ClampRayOrigin(ViewContext, ClampedRayOrigin, RayDirection); GizmoMath::RayCylinderIntersection( WorldCenter, WorldDirection, WorldRadius, WorldHeight, ClampedRayOrigin, RayDirection, bIntersects, RayParam); if (bIntersects) { // add the depth bias if any RayParam += DepthBias; FInputRayHit RayHit(static_cast(RayParam)); RayHit.SetHitObject(this); RayHit.HitIdentifier = PartIdentifier; return RayHit; } } return FInputRayHit(); } void UGizmoElementCylinder::SetBase(const FVector& InBase) { Base = InBase; } FVector UGizmoElementCylinder::GetBase() const { return Base; } void UGizmoElementCylinder::SetDirection(const FVector& InDirection) { Direction = InDirection; Direction.Normalize(); } FVector UGizmoElementCylinder::GetDirection() const { return Direction; } void UGizmoElementCylinder::SetHeight(float InHeight) { Height = InHeight; } float UGizmoElementCylinder::GetHeight() const { return Height; } void UGizmoElementCylinder::SetRadius(float InRadius) { Radius = InRadius; } float UGizmoElementCylinder::GetRadius() const { return Radius; } void UGizmoElementCylinder::SetNumSides(int32 InNumSides) { NumSides = InNumSides; } int32 UGizmoElementCylinder::GetNumSides() const { return NumSides; } void UGizmoElementCylinder::SetIsDashed(bool bInDashing) { bIsDashed = bInDashing; } bool UGizmoElementCylinder::GetIsDashed() const { return bIsDashed; } void UGizmoElementCylinder::SetDashParameters(const float InDashLength, const TOptional& InGapLength) { DashLength = InDashLength; DashGapLength = InGapLength.Get(InDashLength * 0.5f); } void UGizmoElementCylinder::GetDashParameters(float& OutDashLength, float& OutGapLength) const { OutDashLength = DashLength; OutGapLength = DashGapLength; }