Announcement

Collapse
No announcement yet.

Project World to Screen Location stops returning a value

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Project World to Screen Location stops returning a value

    Hi,

    I'm currently working on an blueprint implementation of https://forums.unrealengine.com/show...Blueprint-Node

    I've managed to create a working script, however I'm trying to get my head around the 'Project World To Screen' node. It takes in a Player controller and a World Position (Vector 3) of the object you're projecting and returns a Screen Position (Vector 2D). The object being projected is a ball. Whenever the ball is behind the player the Screen Position returns a zero vector. I'm not 100% sure why, but the node stops returning a value. I was thinking that it may be due to the fact that the ball at some point is no longer being rendered and thus the function returns a zero vector but this doesn't make much sense since the world location of the ball is passed through continuously.



    Is there an alternative way of calculating the Screen Position for when the object is behind the player?

    Here you can see the ball is slightly of screen:


    Here once the player has turned so the ball is almost directly behind it the indicator disappears (because the Project World To Screen stops returning a Screen position):
    Attached Files

    #2
    Code:
    bool UGameplayStatics::ProjectWorldToScreen(APlayerController const* Player, const FVector& WorldPosition, FVector2D& ScreenPosition)
    {
    	ULocalPlayer* const LP = Player ? Player->GetLocalPlayer() : nullptr;
    	if (LP && LP->ViewportClient)
    	{
    		// get the projection data
    		FSceneViewProjectionData ProjectionData;
    		if (LP->GetProjectionData(LP->ViewportClient->Viewport, eSSP_FULL, /*out*/ ProjectionData))
    		{
    			FMatrix const ViewProjectionMatrix = ProjectionData.ComputeViewProjectionMatrix();
    			return FSceneView::ProjectWorldToScreen(WorldPosition, ProjectionData.GetConstrainedViewRect(), ViewProjectionMatrix, ScreenPosition);
    		}
    	}
    
    	ScreenPosition = FVector2D::ZeroVector;
    	return false;
    }

    This is the UE4 4.9.2 Build 'Project World To Screen' function in C++
    Last edited by TheJamsh; 03-03-2016, 09:32 AM. Reason: added [code] tags

    Comment


      #3
      The default projection only works for objects that are in front of the camera's direction vector, and unfortunately there's no way to alter that in Blueprint.

      I wrote a snippet of C++ which can handle reverse projections, which I use for screen-edge arrows, here's the code. I'm going to put a Wiki tutorial up soon. 'TargetWorldLocation' is the position you want to transform.

      Code:
      		FVector Projected;
      		bool bTargetBehindCamera = false;
      
      		// Custom Projection Function
      		ULocalPlayer* const LP = OwningGESPlayer->GetLocalPlayer();
      		if (LP && LP->ViewportClient)
      		{
      			FSceneViewProjectionData NewProjectionData;
      			if (LP->GetProjectionData(LP->ViewportClient->Viewport, EStereoscopicPass::eSSP_FULL, NewProjectionData))
      			{
      				const FMatrix ViewProjectionMatrix = NewProjectionData.ComputeViewProjectionMatrix();
      				const FIntRect ViewRectangle = NewProjectionData.GetConstrainedViewRect();
      
      				FPlane Result = ViewProjectionMatrix.TransformFVector4(FVector4(TargetWorldLocation, 1.f));
      				if (Result.W < 0.f) { bTargetBehindCamera = true; }
      				if (Result.W == 0.f) { Result.W = 1.f; } // Prevent Divide By Zero
      
      				const float RHW = 1.f / FMath::Abs(Result.W);
      				Projected = FVector(Result.X, Result.Y, Result.Z) * RHW;
      	
      				// Normalize to 0..1 UI Space
       				const float NormX = (Projected.X / 2.f) + 0.5f;
       				const float NormY = 1.f - (Projected.Y / 2.f) - 0.5f;
       
       				Projected.X = (float)ViewRectangle.Min.X + (NormX * (float)ViewRectangle.Width());
       				Projected.Y = (float)ViewRectangle.Min.Y + (NormY * (float)ViewRectangle.Height());
      			}
      		}
      
      		FVector2D ScreenPos = FVector2D(Projected.X, Projected.Y);

      Comment


        #4
        Thank you for firstly fixing my post and also thank you very much for sharing your solution. I wonder if it will be addressed down the line. I will happily merge your function and test it.

        Comment


          #5
          Originally posted by TheJamsh View Post
          The default projection only works for objects that are in front of the camera's direction vector, and unfortunately there's no way to alter that in Blueprint.

          I wrote a snippet of C++ which can handle reverse projections, which I use for screen-edge arrows, here's the code. I'm going to put a Wiki tutorial up soon. 'TargetWorldLocation' is the position you want to transform.

          Code:
          		FVector Projected;
          		bool bTargetBehindCamera = false;
          
          		// Custom Projection Function
          		ULocalPlayer* const LP = OwningGESPlayer->GetLocalPlayer();
          		if (LP && LP->ViewportClient)
          		{
          			FSceneViewProjectionData NewProjectionData;
          			if (LP->GetProjectionData(LP->ViewportClient->Viewport, EStereoscopicPass::eSSP_FULL, NewProjectionData))
          			{
          				const FMatrix ViewProjectionMatrix = NewProjectionData.ComputeViewProjectionMatrix();
          				const FIntRect ViewRectangle = NewProjectionData.GetConstrainedViewRect();
          
          				FPlane Result = ViewProjectionMatrix.TransformFVector4(FVector4(TargetWorldLocation, 1.f));
          				if (Result.W < 0.f) { bTargetBehindCamera = true; }
          				if (Result.W == 0.f) { Result.W = 1.f; } // Prevent Divide By Zero
          
          				const float RHW = 1.f / FMath::Abs(Result.W);
          				Projected = FVector(Result.X, Result.Y, Result.Z) * RHW;
          	
          				// Normalize to 0..1 UI Space
           				const float NormX = (Projected.X / 2.f) + 0.5f;
           				const float NormY = 1.f - (Projected.Y / 2.f) - 0.5f;
           
           				Projected.X = (float)ViewRectangle.Min.X + (NormX * (float)ViewRectangle.Width());
           				Projected.Y = (float)ViewRectangle.Min.Y + (NormY * (float)ViewRectangle.Height());
          			}
          		}
          
          		FVector2D ScreenPos = FVector2D(Projected.X, Projected.Y);
          I wish I know how to use this =(
          Enigma Prison - Facebook - Twitter - IndieDB

          Comment


            #6
            I wanted projections from behind the player also, so I dropped more or less what TheJamsh above has into a function that closely matches UE 4.14 ProjectWorldToScreen.

            Declared:

            Code:
            UFUNCTION(BlueprintPure, Category = "UI Math")
            static bool ProjectWorldToScreenBidirectional(APlayerController const* Player, const FVector& WorldPosition, FVector2D& ScreenPosition, bool& bTargetBehindCamera, bool bPlayerViewportRelative = false);
            Defined:

            Code:
            bool UIBlueprintFunctionLibrary::ProjectWorldToScreenBidirectional(APlayerController const* Player, const FVector& WorldPosition, FVector2D& ScreenPosition, bool& bTargetBehindCamera, bool bPlayerViewportRelative)
            {
            	FVector Projected;
            	bool bSuccess = false;
            
            	ULocalPlayer* const LP = Player ? Player->GetLocalPlayer() : nullptr;
            	if (LP && LP->ViewportClient)
            	{
            		// get the projection data
            		FSceneViewProjectionData ProjectionData;
            		if (LP->GetProjectionData(LP->ViewportClient->Viewport, eSSP_FULL, /*out*/ ProjectionData))
            		{
            			const FMatrix ViewProjectionMatrix = ProjectionData.ComputeViewProjectionMatrix();
            			const FIntRect ViewRectangle = ProjectionData.GetConstrainedViewRect();
            
            			FPlane Result = ViewProjectionMatrix.TransformFVector4(FVector4(WorldPosition, 1.f));
            			if (Result.W < 0.f) { bTargetBehindCamera = true; }
            			if (Result.W == 0.f) { Result.W = 1.f; } // Prevent Divide By Zero
            
            			const float RHW = 1.f / FMath::Abs(Result.W);
            			Projected = FVector(Result.X, Result.Y, Result.Z) * RHW;
            
            			// Normalize to 0..1 UI Space
            			const float NormX = (Projected.X / 2.f) + 0.5f;
            			const float NormY = 1.f - (Projected.Y / 2.f) - 0.5f;
            
            			Projected.X = (float)ViewRectangle.Min.X + (NormX * (float)ViewRectangle.Width());
            			Projected.Y = (float)ViewRectangle.Min.Y + (NormY * (float)ViewRectangle.Height());
            
            			bSuccess = true;
            			ScreenPosition = FVector2D(Projected.X, Projected.Y);
            
            			if (bPlayerViewportRelative)
            			{
            				ScreenPosition -= FVector2D(ProjectionData.GetConstrainedViewRect().Min);
            			}
            		}
            		else
            		{
            			ScreenPosition = FVector2D(1234, 5678);
            		}
            	}
            
            	return bSuccess;
            }

            Looks like:

            Click image for larger version

Name:	projection_node.png
Views:	1
Size:	18.5 KB
ID:	1123170

            Comment


              #7
              Can someone please explain, how do I add this to my editor?

              Comment


                #8
                Originally posted by Generica View Post
                Can someone please explain, how do I add this to my editor?
                You need to create a new C++ class on Unreal Editor as Blueprint Function Library and save,
                then you add the first piece of code on the [name of your class].h and the second part on [name of your class].cpp

                Save everything and compile on the editor.

                Comment


                  #9
                  Originally posted by Paul Schultz View Post
                  I wanted projections from behind the player also, so I dropped more or less what TheJamsh above has into a function that closely matches UE 4.14 ProjectWorldToScreen.

                  Declared:

                  Code:
                  UFUNCTION(BlueprintPure, Category = "UI Math")
                  static bool ProjectWorldToScreenBidirectional(APlayerController const* Player, const FVector& WorldPosition, FVector2D& ScreenPosition, bool& bTargetBehindCamera, bool bPlayerViewportRelative = false);
                  Defined:

                  Code:
                  bool UIBlueprintFunctionLibrary::ProjectWorldToScreenBidirectional(APlayerController const* Player, const FVector& WorldPosition, FVector2D& ScreenPosition, bool& bTargetBehindCamera, bool bPlayerViewportRelative)
                  {
                  FVector Projected;
                  bool bSuccess = false;
                  
                  ULocalPlayer* const LP = Player ? Player->GetLocalPlayer() : nullptr;
                  if (LP && LP->ViewportClient)
                  {
                  // get the projection data
                  FSceneViewProjectionData ProjectionData;
                  if (LP->GetProjectionData(LP->ViewportClient->Viewport, eSSP_FULL, /*out*/ ProjectionData))
                  {
                  const FMatrix ViewProjectionMatrix = ProjectionData.ComputeViewProjectionMatrix();
                  const FIntRect ViewRectangle = ProjectionData.GetConstrainedViewRect();
                  
                  FPlane Result = ViewProjectionMatrix.TransformFVector4(FVector4(WorldPosition, 1.f));
                  if (Result.W < 0.f) { bTargetBehindCamera = true; }
                  if (Result.W == 0.f) { Result.W = 1.f; } // Prevent Divide By Zero
                  
                  const float RHW = 1.f / FMath::Abs(Result.W);
                  Projected = FVector(Result.X, Result.Y, Result.Z) * RHW;
                  
                  // Normalize to 0..1 UI Space
                  const float NormX = (Projected.X / 2.f) + 0.5f;
                  const float NormY = 1.f - (Projected.Y / 2.f) - 0.5f;
                  
                  Projected.X = (float)ViewRectangle.Min.X + (NormX * (float)ViewRectangle.Width());
                  Projected.Y = (float)ViewRectangle.Min.Y + (NormY * (float)ViewRectangle.Height());
                  
                  bSuccess = true;
                  ScreenPosition = FVector2D(Projected.X, Projected.Y);
                  
                  if (bPlayerViewportRelative)
                  {
                  ScreenPosition -= FVector2D(ProjectionData.GetConstrainedViewRect().Min);
                  }
                  }
                  else
                  {
                  ScreenPosition = FVector2D(1234, 5678);
                  }
                  }
                  
                  return bSuccess;
                  }

                  Looks like:

                  [ATTACH=CONFIG]129684[/ATTACH]
                  I suggest you to add na ELSE parameter to set bTargetBehindCamera back to false when look to the tag point again.

                  if (Result.W < 0.f) { bTargetBehindCamera = true; } else {bTargetBehindCamera = false;}

                  Comment

                  Working...
                  X