WorldToViewportPoint undesired behavior?

Hi!

I’m working on a simple system which gets two points on screen (center of screen and a point transformed from 3D onto the viewport) and then creating a line connecting the two. I use simple trigonometry to create the line.

Desired behavior:

I’m able to achieve the desired behavior with one exception. At some point, the line seems to flip and connect to the opposite of the point where the actual object is.

Undesired behavior:

I’ve also noticed that at some point the screen position of the object randomly jumps from [num] to [num].

Undesired behavior output:
output

Here’s my code for creating the line:

--called every RenderStepped
function updateLine(part)
	local pos,on=workspace.CurrentCamera:WorldToViewportPoint(part.Position)
	
	local p1=Vector2.new(gui.AbsoluteSize.X*0.5,gui.AbsoluteSize.Y*0.5)
	local p2=Vector2.new(pos.X,pos.Y)
	local p3=Vector2.new(p2.X,p1.Y)

	local hyp=(p2-p1).Magnitude
	local opp=math.abs(p2.Y-p1.Y)

	local alpha=math.deg(math.asin(opp/hyp))

	gui.p1.Position=UDim2.fromOffset(p1.X,p1.Y) --used for visualization only
	gui.p2.Position=UDim2.fromOffset(p2.X,p2.Y) --used for visualization only
	gui.p3.Position=UDim2.fromOffset(p3.X,p3.Y) --used for visualization only

	gui.Line.Size=UDim2.new(0,hyp,0,1)
	gui.Line.Position=UDim2.new(0,(p2.X+p1.X)/2,0,(p2.Y+p1.Y)/2)

	if p2.Y>p1.Y then alpha=-alpha end
	if p2.X>p1.X then alpha=90+(90-alpha) end

	gui.Line.Rotation=alpha
end

Have you tried to if the value is set to negative to then times it by -1 to flip it back to a positive value?

Unfortunately no since I can’t tell when the part position is doing this undesired behavior or when it’s actually at a negative screen position. (e.g. x:-500 y:-500)

Just do

if x < 0 or if y < 0

This will indicate if the values are negative

That doesn’t work. If the part is actually to the left of the player then the values will be negative. However, in this case the part is to the right and yet the values are negative when they should be positive. The computer only knows where the part is based on WorldToViewportPoint which is what returns large negative values in the first case.

local camera = workspace.CurrentCamera
local part = workspace.SpawnLocation
local frame = game.Players.LocalPlayer.PlayerGui.ScreenGui.Frame

while true do
	-- get the ViewportPoint
	local vector, inViewport = camera:WorldToViewportPoint(part.Position)
	
	-- if vector.z is negative then mirror the x and y
	if vector.z < 0 then
		local vx = camera.ViewportSize.X / 2
		local vy = camera.ViewportSize.Y / 2
		local deltaX = vx - vector.X
		local deltaY = vy - vector.Y
		vector = Vector3.new(vx + deltaX, vy + deltaY, -vector.Z)
	end
	
	-- clamp so the frame stays ın view
	local x = math.clamp(vector.X, 0, camera.ViewportSize.X)
	local y = math.clamp(vector.Y, 0, camera.ViewportSize.Y)
	
	-- position the frame
	frame.Position = UDim2.new(0, x, 0, y)
	
	task.wait()
end

here is a demo you can test
WorldToViewportPoint.rbxl (35.3 KB)

Hey there!

I’m guessing you’ve probably either solved or moved on from this problem at this point. However for anyone in the future trying to get this to work, here’s what’s going on.

Understanding the Issue

I’ve sketched up my understanding. Basically in rendering a 3D scene onto a 2D screen, the camera has to project points on a flat plane (called the clipping plane). The blue region of the clip plane represents the viewport, however, as it is a plane it extends infinitely beyond that. The frustum is the cone of vision that is formed by the clip plane and the position of the camera, it defines everything you can see.

Thus, you end up with 3 outcomes for any object in a scene.

  1. When the geometry is in the frustum, it projects onto the viewport as normal
  2. When the geometry is out of the frustum but in front of the camera, it projects on the plane but not the viewport
  3. When an object is out of the frustum and behind the camera, the projection line appears to flip the image. This might not be super useful, but it’s actually fully expected behavior for the camera. That spike in numbers you noticed in the output are the dimensions approaching infinite as the point moves from case #2 to case #3, with infinity being fully parallel to the clip plane.

Working Around Case #3

As you’re just dealing with a single point, @5uphi 's solution likely works fine. If you’re tired of these edge cases though, in all honesty, I’d recommend skipping all this headache-inducing math and just drawing a line with a part in a ViewportFrame that you update each frame. Then you can stick to global coordinates while still staying within the HUD.

To anyone who may have a requirement to solve this beyond a single point, mirroring it for case #3 doesn’t maintain the 3D perspective distancing between points. When I tried to project a 3D surface into 2D from behind, the mirrored image was I believe actually what you’d see if you turned the camera around, rather than the image being projected as though it was in-front.

In my case, since it was geometry rather than a single point, I had in-frustum geometry that was connected via a line to the troublesome vertex. I solved my issue by finding the intersection between that line and the clip plane, then just moving the behind vertex to be right in front of the camera but behind the clipping plane.

As to more universal solutions, admittedly I go back and forth between “it just feels like a solution exists” and “the way it currently behaves is the proper behavior, you’re trying to solve the wrong problem”. I tried inverting the Z-offset from the camera for objects behind me, which got them to not invert but the perspective still skewed in weird ways. If anyone reading this has a better and tested solution please do post.

Anyways, hope this helps someone! Happy scripting!

4 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.