Fixed memory leaks and the issue of it not working for locations behind the camera
.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "HUDBlueprintLibrary.generated.h"
UCLASS()
class YOURPROJECTNAME_API UHUDBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Converts a world location to screen position for HUD drawing. This differs from the results of FSceneView::WorldToScreen in that it returns a position along the edge of the screen for offscreen locations
*
* @param InLocation - The world space location to be converted to screen space
* @param EdgePercent - How close to the edge of the screen, 1.0 = at edge, 0.0 = at center of screen. .9 or .95 is usually desirable
* @param ViewportCenterLoc - for offsetting center of the screen, leave at (0.5, 0.5) for no offset
* @outparam OutScreenPosition - the screen coordinates for HUD drawing
* @outparam OutRotationAngleDegrees - The angle to rotate a hud element if you want it pointing toward the offscreen indicator, 0° if onscreen
* @outparam bIsOnScreen - True if the specified location is in the camera view (may be obstructed)
*/
UFUNCTION( BlueprintPure, meta=( WorldContext = "WorldContextObject", CallableWithoutWorldContext), Category = "HUD|Util" )
static void FindScreenEdgeLocationForWorldLocation( UObject* WorldContextObject, const FVector& InLocation, const float EdgePercent, const FVector2D ViewportCenterLoc, FVector2D& OutScreenPosition, float& OutRotationAngleDegrees, bool &bIsOnScreen );
};
.cpp
#include "HUDBlueprintLibrary.h"
#include <Kismet/GameplayStatics.h>
#include <GameFramework/Character.h>
#include <Engine/Engine.h>
void UHUDBlueprintLibrary::FindScreenEdgeLocationForWorldLocation( UObject* WorldContextObject, const FVector& InLocation, const float EdgePercent, const FVector2D ViewportCenterLoc, FVector2D& OutScreenPosition, float& OutRotationAngleDegrees, bool& bIsOnScreen )
{
bIsOnScreen = false;
OutRotationAngleDegrees = 0.f;
FVector2D ScreenPosition = FVector2D();
if( !GEngine )
{
return;
}
const FVector2D ViewportSize = FVector2D( GEngine->GameViewport->Viewport->GetSizeXY() );
const FVector2D ViewportCenter = FVector2D( ViewportSize.X * ViewportCenterLoc.X, ViewportSize.Y * ViewportCenterLoc.Y );
UWorld* World = GEngine->GetWorldFromContextObject( WorldContextObject );
if( !World )
{
return;
}
APlayerController* PlayerController = ( WorldContextObject ? UGameplayStatics::GetPlayerController( WorldContextObject, 0 ) : NULL );
if( !PlayerController )
{
return;
}
FVector CameraLoc;
FRotator CameraRot;
PlayerController->GetPlayerViewPoint( CameraLoc, CameraRot );
const FVector CameraToLoc = InLocation - CameraLoc;
FVector Forward = CameraRot.Vector();
FVector Offset = CameraToLoc.GetSafeNormal();
float DotProduct = FVector::DotProduct( Forward, Offset );
bool bLocationIsBehindCamera = ( DotProduct < 0 );
if( bLocationIsBehindCamera )
{
FVector Inverted = CameraToLoc * -1.f;
FVector NewInLocation = CameraLoc + Inverted;
PlayerController->ProjectWorldLocationToScreen( NewInLocation, ScreenPosition );
ScreenPosition.X = ViewportSize.X - ScreenPosition.X;
ScreenPosition.Y = ViewportSize.Y - ScreenPosition.Y;
}
else
{
PlayerController->ProjectWorldLocationToScreen( InLocation, ScreenPosition );
}
// Check to see if it's on screen. If it is, ProjectWorldLocationToScreen is all we need, return it.
if( ScreenPosition.X >= 0.f && ScreenPosition.X <= ViewportSize.X
&& ScreenPosition.Y >= 0.f && ScreenPosition.Y <= ViewportSize.Y && !bLocationIsBehindCamera )
{
OutScreenPosition = ScreenPosition;
bIsOnScreen = true;
return;
}
ScreenPosition -= ViewportCenter;
float AngleRadians = FMath::Atan2( ScreenPosition.Y, ScreenPosition.X );
AngleRadians -= FMath::DegreesToRadians( 90.f );
OutRotationAngleDegrees = FMath::RadiansToDegrees( AngleRadians ) + 180.f;
float Cos = cosf( AngleRadians );
float Sin = -sinf( AngleRadians );
ScreenPosition = FVector2D( ViewportCenter.X + ( Sin * 150.f ), ViewportCenter.Y + Cos * 150.f );
float m = Cos / Sin;
FVector2D ScreenBounds = ViewportCenter * EdgePercent;
if( Cos > 0 )
{
ScreenPosition = FVector2D( ScreenBounds.Y / m, ScreenBounds.Y );
}
else
{
ScreenPosition = FVector2D( -ScreenBounds.Y / m, -ScreenBounds.Y );
}
if( ScreenPosition.X > ScreenBounds.X )
{
ScreenPosition = FVector2D( ScreenBounds.X, ScreenBounds.X * m );
}
else if( ScreenPosition.X < -ScreenBounds.X )
{
ScreenPosition = FVector2D( -ScreenBounds.X, -ScreenBounds.X * m );
}
ScreenPosition += ViewportCenter;
OutScreenPosition = ScreenPosition;
}