Hello,
this is probably not the most elegant solution, but it works and I thought someone might find it useful.
The function returns true when given WorldLocation is within viewport, false when it is offscreen, with nearest screen position.
Declaration:
UFUNCTION(BlueprintPure, meta = (Keywords = "world location to screen position"))
static bool ProjectWorldToScreen(const APlayerController* PlayerController, FVector WorldLocation, FVector2D& OutScreenPosition);
Implementation: [4.16]
bool YourClassName::ProjectWorldToScreen(const APlayerController* PlayerController, FVector WorldLocation, FVector2D& OutScreenPosition)
{
if (!PlayerController)
{
return false;
}
FVector2D ScreenPosTemp(-1.f, -1.f);
PlayerController->ProjectWorldLocationToScreen(WorldLocation, ScreenPosTemp);
const FVector2D ViewportSize = GEngine->GameViewport->Viewport->GetSizeXY();
bool bLocationIsInViewport = ScreenPosTemp.X >= 0.0f && ScreenPosTemp.X <= ViewportSize.X && ScreenPosTemp.Y >= 0.0f && ScreenPosTemp.Y <= ViewportSize.Y;
if (bLocationIsInViewport) /*It is on the screen*/
{
OutScreenPosition = ScreenPosTemp;
return true;
}
else /*It is not on the screen*/
{
const FVector CameraLocation = PlayerController->PlayerCameraManager->GetCameraLocation();
const FRotator CameraRotation = PlayerController->PlayerCameraManager->GetCameraRotation();
const FVector CameraForwardVector = UKismetMathLibrary::GetForwardVector(CameraRotation);
const FVector CameraRightVector = UKismetMathLibrary::GetRightVector(CameraRotation);
const FVector CameraUpVector = UKismetMathLibrary::GetUpVector(CameraRotation);
const FVector ProjectedVector = UKismetMathLibrary::ProjectVectorOnToPlane(WorldLocation - CameraLocation, CameraForwardVector).GetSafeNormal();
// Some random calculation to determine wheter the angle is in upper or lower half
const float DotProduct = FVector::DotProduct(ProjectedVector, CameraUpVector);
float Angle = 0.0f;
// Set angle to -180 .. 180 range
if (DotProduct > 0.0f)
{
const float Dot = FVector::DotProduct(ProjectedVector, CameraRightVector);
Angle = -UKismetMathLibrary::DegAcos(Dot); // negative angle
}
else
{
const float Dot = FVector::DotProduct(ProjectedVector, CameraRightVector);
Angle = UKismetMathLibrary::DegAcos(Dot); // positive angle
}
// Coordinates of 4 borders in viewport screen
const FVector UpperLeftCorner(0.f, 0.f, 0.f);
const FVector LowerLeftCorner(0.f, ViewportSize.Y, 0.f);
const FVector UpperRightCorner(ViewportSize.X, 0.f, 0.f);
const FVector LowerRightCorner(ViewportSize.X, ViewportSize.Y, 0.f);
const FVector ViewportCentre(ViewportSize.X / 2.0f, ViewportSize.Y / 2.0f, 0.f);
// Vector that points from centre of the screen to the WorldPosition
FVector DirectionVector(UKismetMathLibrary::DegCos(Angle), UKismetMathLibrary::DegSin(Angle), 0.f);
DirectionVector *= 10'000.f; // multiplying by some value to make sure end of this vector will always lie outside of the screen
DirectionVector += ViewportCentre;
FVector IntersectionPoint = FVector::ZeroVector;
if (Angle <= 90.f)
{
if (Angle <= 0.f)
{
if (Angle <= -90.f)
{
// 2. Quadrant
FMath::SegmentIntersection2D(UpperLeftCorner, UpperRightCorner, ViewportCentre, DirectionVector, IntersectionPoint);
FMath::SegmentIntersection2D(UpperLeftCorner, LowerLeftCorner, ViewportCentre, DirectionVector, IntersectionPoint);
}
else
{
// 1. Quadrant
FMath::SegmentIntersection2D(UpperLeftCorner, UpperRightCorner, ViewportCentre, DirectionVector, IntersectionPoint);
FMath::SegmentIntersection2D(UpperRightCorner, LowerRightCorner, ViewportCentre, DirectionVector, IntersectionPoint);
}
}
else
{
// 4. Quadrant
FMath::SegmentIntersection2D(LowerLeftCorner, LowerRightCorner, ViewportCentre, DirectionVector, IntersectionPoint);
FMath::SegmentIntersection2D(UpperRightCorner, LowerRightCorner, ViewportCentre, DirectionVector, IntersectionPoint);
}
}
else
{
// 3. Quadrant
FMath::SegmentIntersection2D(UpperLeftCorner, LowerLeftCorner, ViewportCentre, DirectionVector, IntersectionPoint);
FMath::SegmentIntersection2D(LowerLeftCorner, LowerRightCorner, ViewportCentre, DirectionVector, IntersectionPoint);
}
OutScreenPosition.X = IntersectionPoint.X;
OutScreenPosition.Y = IntersectionPoint.Y;
return false;
}
}