WorldToViewportPoint does not work as intended

Reproduction Steps
Using WorldToViewportPoint does not return correct offset values for use on screengui’s

This image shows that it should convert a 3D point in the world, to a 2D space, with the X and Y being the pixels on the screen

https://developer.roblox.com/en-us/api-reference/function/Camera/WorldToViewportPoint

local vector, onScreen = Camera:WorldToViewportPoint(WorldModel.BottomHalf.Position)
print(vector)
script.Parent.Card.Position = UDim2.fromOffset(vector.X, vector.Y)

Expected Behavior
The 3D space to return a 2D point, with offset

Actual Behavior
It returns seemingly scale degrees
0.5, 0.62929719686508, 6.0750036239624

If I do .fromScale, and plug it in, it works for this use case, but if the documentation states it should be pixels, then I want and expect to see pixels returned. Prime example being

local TopLeft = Vector3.new(
	WorldModel.BottomHalf.Position.X + (WorldModel.BottomHalf.Size.Z / 2),
	WorldModel.BottomHalf.Position.Y,
	WorldModel.BottomHalf.Position.Z - (WorldModel.BottomHalf.Size.X / 2)
)
local BottomRight = Vector3.new(
	WorldModel.BottomHalf.Position.X - (WorldModel.BottomHalf.Size.Z / 2),
	WorldModel.BottomHalf.Position.Y,
	WorldModel.BottomHalf.Position.Z + (WorldModel.BottomHalf.Size.X / 2)
)

TopLeftPart.Position = TopLeft -- The yellow part
BottomRightPart.Position = BottomRight -- The green part

local TopLeftVector = Camera:WorldToViewportPoint(TopLeft)
local BottomRightVector = Camera:WorldToViewportPoint(BottomRight)

print("Size", BottomRightVector.X, TopLeftVector.X)

prints
Size 1.0583291053772 -0.058329164981842 - Client - LocalScript:139

Even tho this is the vector3s


So no reason for either BottomRightVector or TopLeftVector to have positions that are off screen, when they are clearly on screen. Even adding the onScreen check, it says false, but I can clearly see the 2 points on screen.

Workaround
No

Issue Area: Engine
Issue Type: Other
Impact: Very High
Frequency: Constantly

1 Like

A workaround for now while you wait:

I found this a while back and it proved useful:

local function pointToViewport(camera, p)
    local vps = camera.ViewportSize
    local lp = camera.CFrame:pointToObjectSpace(p) -- make point relative to camera so easier to work with

    local r = vps.x/vps.y; -- aspect ratio
    local h = -lp.z*math.tan(math.rad(camera.FieldOfView/2)); -- calc height/2
    local w = r*h; -- calc width/2

    local corner = Vector3.new(-w, h, lp.z); -- find the top left corner of the far plane
    local relative = lp - corner; -- get the 3d point relative to the corner

    local sx = relative.x / (w*2); -- find the x percentage 
    local sy = -relative.y / (h*2); -- find the y percentage 

    local onscreen = -lp.z > 0 and sx >=0 and sx <= 1 and sy >=0 and sy <= 1

    -- returns in pixels as opposed to scale
    return Vector3.new(sx*vps.x, sy*vps.y, -lp.z), onscreen
end
1 Like

Could it be an extra boolean flag that governs whether to include/exclude an extra border area? This tripped me up on this function a while ago.

It returns the same values as my code example :confused:

The issue you’re experiencing can be solved in two different ways:

  • Use the WorldToScreenPoint function rather than the WorldToViewportPoint one. That will give you a position in Gui coordinates rather than one in viewport coordinates (Gui coordinates take into account the offset of the top bar, the “GuiInset”)

  • Set IgnoreGuiInset = true on the ScreenGui that the Gui content you’re positioning is inside of so that there is no GuiInset present and Gui coordinates will exactly equal viewport coordinates.

I tried both. Always had IgnoreGuiInset set to true

Can you post a place file?

The results you get back from the function will only be valid screen coordinates in the case where onScreen is true, so if it’s returning false it’s not unexpected that you will get back a garbage result.

Also, WorldToViewportPoint is used in many places in the builtin plugin code that ships with Studio, so it is heavily exercised and unlikely to be broken.

Changed a little bit, but general idea is there. Parts being created in correct spots, but frames not corresponding

Player_ID (1).rbxl (120.2 KB)

Oh I see now, this is a Camera in a ViewportFrame. It does indeed not work correctly in this case because the Camera object’s ViewportSize is treated as (1, 1) (The Camera doesn’t correctly pick up the ViewportFrame’s size)

The reason it kind of looks like it’s returning a Scale, is that for a ViewportSize of (1, 1) the offset returned effectively is a scale, but the aspect ratio is off because it’s using a 1:1 aspect ratio for the calculation rather than the one your viewport actually has.

You could correct it like this:

local camViewport = Camera.ViewportSize
			
local function fixAspect(xCoord)
	local viewport = script.Parent.AbsoluteSize
	local viewportAspect = viewport.Y / viewport.X
	local cameraAspect = camViewport.X / camViewport.Y
	local aspectModification = viewportAspect / cameraAspect
	xCoord -= 0.5
	xCoord *= aspectModification
	xCoord += 0.5
	return xCoord
end

local TopLeftVector, TopScreen = Camera:WorldToViewportPoint(TopLeft)
script.Parent.TopLeftFrame.Position = UDim2.fromScale(fixAspect(TopLeftVector.X) / camViewport.X, TopLeftVector.Y / camViewport.Y)

local BottomRightVector, BottomScreen = Camera:WorldToViewportPoint(BottomRight)
script.Parent.BottomRightFrame.Position = UDim2.fromScale(fixAspect(BottomRightVector.X) / camViewport.X, BottomRightVector.Y / camViewport.Y)

This code will not only fix the problem but continue to function correctly even if things get fixed and the Camera takes into account the correct viewport size.

This is currently the “expected behavior” because you could technically have multiple differently sized ViewportFrames pointed at the same Camera, but I reopened the discussion on that’s the right behavior.

10 Likes

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