// Copyright Epic Games, Inc. All Rights Reserved. #include "BaseGizmos/GizmoArrowComponent.h" #include "BaseGizmos/GizmoRenderingUtil.h" #include "BaseGizmos/GizmoViewContext.h" #include "MeshElementCollector.h" #include "PrimitiveSceneProxy.h" #include "Materials/Material.h" #include "MaterialShared.h" #include "PrimitiveDrawingUtils.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(GizmoArrowComponent) namespace GizmoArrowComponentLocals { const float ARROW_RENDERVISIBILITY_DOT_THRESHOLD = 0.965f; //~15 degrees template bool GetWorldEndpoints(bool bIsViewDependent, const SceneViewOrGizmoViewContext* View, const FVector& WorldOrigin, const FVector& Direction, float Gap, float Length, bool bUseWorldAxes, TFunctionRef VectorTransform, FVector& StartPointOut, FVector& EndPointOut, float& PixelToWorldScaleOut) { FVector ArrowDirection = bUseWorldAxes ? Direction : VectorTransform(Direction); float LengthScale = 1; if (bIsViewDependent) { // direction to origin of gizmo FVector ViewDirection = View->IsPerspectiveProjection() ? WorldOrigin - View->ViewLocation : View->GetViewDirection(); ViewDirection.Normalize(); bool bFlipped = (FVector::DotProduct(ViewDirection, ArrowDirection) > 0); ArrowDirection = (bFlipped) ? -ArrowDirection : ArrowDirection; //bRenderVisibilityOut = FMath::Abs(FVector::DotProduct(ArrowDirection, ViewDirection)) < 0.985f; // ~10 degrees bool bRenderVisibility = FMath::Abs(FVector::DotProduct(ArrowDirection, ViewDirection)) < ARROW_RENDERVISIBILITY_DOT_THRESHOLD; // ~15 degrees if (!bRenderVisibility) { return false; } LengthScale = UE::GizmoRenderingUtil::CalculateLocalPixelToWorldScale(View, WorldOrigin); } PixelToWorldScaleOut = LengthScale; double StartDist = LengthScale * Gap; double EndDist = LengthScale * (Gap + Length); StartPointOut = WorldOrigin + StartDist * ArrowDirection; EndPointOut = WorldOrigin + EndDist * ArrowDirection; return true; } } class FGizmoArrowComponentSceneProxy final : public FPrimitiveSceneProxy { public: SIZE_T GetTypeHash() const override { static size_t UniquePointer; return reinterpret_cast(&UniquePointer); } FGizmoArrowComponentSceneProxy(const UGizmoArrowComponent* InComponent) : FPrimitiveSceneProxy(InComponent), Color(InComponent->Color), Direction(InComponent->Direction), Gap(InComponent->Gap), Length(InComponent->Length), Thickness(InComponent->Thickness), HoverThicknessMultiplier(InComponent->HoverSizeMultiplier) { } virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override { using namespace GizmoArrowComponentLocals; const FMatrix& LocalToWorldMatrix = GetLocalToWorld(); FVector Origin = LocalToWorldMatrix.TransformPosition(FVector::ZeroVector); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { if (VisibilityMap & (1 << ViewIndex)) { const FSceneView* View = Views[ViewIndex]; FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); bool bWorldAxis = (bExternalWorldLocalState) ? (*bExternalWorldLocalState) : false; FVector StartPoint, EndPoint; float PixelToWorldScale = 0; bool bIsViewDependent = (bExternalIsViewDependent) ? (*bExternalIsViewDependent) : false; bool bRenderVisibility = GetWorldEndpoints(bIsViewDependent, View, Origin, Direction, Gap, Length, bWorldAxis, [&LocalToWorldMatrix](const FVector& VectorIn) { return FVector{ LocalToWorldMatrix.TransformVector(VectorIn) }; }, StartPoint, EndPoint, PixelToWorldScale); if (!bRenderVisibility) { continue; } float UseThickness = (bExternalHoverState != nullptr && *bExternalHoverState == true) ? (HoverThicknessMultiplier * Thickness) : (Thickness); if (View->IsPerspectiveProjection()) { UseThickness *= (View->FOV / 90.0f); // compensate for FOV scaling in Gizmos... } PDI->DrawLine(StartPoint, EndPoint, Color, SDPG_Foreground, UseThickness, 0.0f, true); } } } virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override { FPrimitiveViewRelevance Result; Result.bDrawRelevance = IsShown(View); Result.bDynamicRelevance = true; Result.bShadowRelevance = false; Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); Result.bRenderCustomDepth = ShouldRenderCustomDepth(); return Result; } virtual bool CanBeOccluded() const override { return false; } virtual uint32 GetMemoryFootprint(void) const override { return IntCastChecked(sizeof *this + GetAllocatedSize()); } SIZE_T GetAllocatedSize(void) const { return FPrimitiveSceneProxy::GetAllocatedSize(); } void SetExternalHoverState(bool* HoverState) { bExternalHoverState = HoverState; } void SetExternalWorldLocalState(bool* bWorldLocalState) { bExternalWorldLocalState = bWorldLocalState; } void SetExternalIsViewDependent(bool* bExternalIsViewDependentIn) { bExternalIsViewDependent = bExternalIsViewDependentIn; } private: FLinearColor Color; FVector Direction; float Gap; float Length; float Thickness; float HoverThicknessMultiplier; // set on Component for use in ::GetDynamicMeshElements() bool* bExternalHoverState = nullptr; bool* bExternalWorldLocalState = nullptr; bool* bExternalIsViewDependent = nullptr; }; FPrimitiveSceneProxy* UGizmoArrowComponent::CreateSceneProxy() { FGizmoArrowComponentSceneProxy* NewProxy = new FGizmoArrowComponentSceneProxy(this); NewProxy->SetExternalHoverState(&bHovering); NewProxy->SetExternalWorldLocalState(&bWorld); NewProxy->SetExternalIsViewDependent(&bIsViewDependent); return NewProxy; } FBoxSphereBounds UGizmoArrowComponent::CalcBounds(const FTransform& LocalToWorld) const { return FBoxSphereBounds(FSphere(FVector::ZeroVector, Gap+Length).TransformBy(LocalToWorld)); } bool UGizmoArrowComponent::LineTraceComponent(FHitResult& OutHit, const FVector Start, const FVector End, const FCollisionQueryParams& Params) { using namespace GizmoArrowComponentLocals; const FTransform& Transform = this->GetComponentToWorld(); FVector UseOrigin = Transform.TransformPosition(FVector::ZeroVector); // Copy what is done in the proxy object, but get data from the gizmo view context. FVector StartPoint = FVector::ZeroVector, EndPoint = FVector::ZeroVector; // initialized to appease CIS float PixelToWorldScale = 0; bool bRenderVisibility = GetWorldEndpoints(bIsViewDependent, ToRawPtr(GizmoViewContext), UseOrigin, Direction, Gap, Length, bWorld, [&Transform](const FVector& VectorIn) { return Transform.TransformVector(VectorIn); }, StartPoint, EndPoint, PixelToWorldScale); if (!bRenderVisibility) { return false; } FVector NearestArrow, NearestLine; FMath::SegmentDistToSegmentSafe(StartPoint, EndPoint, Start, End, NearestArrow, NearestLine); double Distance = FVector::Distance(NearestArrow, NearestLine); if (Distance > PixelHitDistanceThreshold * PixelToWorldScale) { return false; } OutHit.Component = this; OutHit.Distance = static_cast(FVector::Distance(Start, NearestLine)); OutHit.ImpactPoint = NearestLine; return true; }