:WorldToScreenPoint()

So I’m using :WorldToScreenPoint() to check which parts are in a players view, but there’s a problem with it as it only checks if the middle of the part is visible and not if any corners of the part are visible, and it also doesn’t count parts visible in the top bar area as visible. Any way to extend these boundaries and make it look at the corners of the parts too?

https://i.gyazo.com/82568b16a52a08083ec5e80f27aff785.mp4

game:GetService("RunService").RenderStepped:Connect(function()
	local children = workspace.Parts:GetChildren()
	for i = 1, #children do
		children[i].Material = "SmoothPlastic"
		local pos, vis = workspace.CurrentCamera:WorldToScreenPoint(children[i].Position)
		if vis then
		    children[i].Material = "Neon"
		end
	end
end)

I only need this for square or rectangle parts, not spheres or any other shapes.

4 Likes

Because the position is the exact center. I’m not sure of a great fix, you can try the position and if that doesn’t work try every corner using the Size of the part and doing maths with the Position. It has its caveats, but it’s more accurate.

If you use WorldToViewPort you can overcome to the top bar issues. You’ll want to “slide” the position of each center part to the outermost position of the parts bounding box that’s closest to the camera’s focus (See the diagram). This still doesn’t catch everything but it’s a lot better than flat out position checks.


There is probably a faster way to do this but this will make the most sense.

local function findNearestPointInPart(part, position)
	-- put the position into part space for clamping
	local toPart = part.CFrame:pointToObjectSpace(position)
	local halfSize = part.Size * 0.5
	
	-- return in world space, partPos +  toPart clamped to the part size
	return part.CFrame * Vector3.new(
		math.clamp(toPart.X, -halfSize.X, halfSize.X),
		math.clamp(toPart.Y, -halfSize.Y, halfSize.Y),
		math.clamp(toPart.Z, -halfSize.Z, halfSize.Z)
	)
end

-- how you would call it
while wait() do	
	somePart.CFrame = CFrame.new(findNearestPointInPart(somePart, camera.Focus.p))
end
3 Likes

You can get the positions of the corners of the part using the part’s CFrame and either Vector3s or CFrames of half the part’s size on each axis.


The * operator on CFrames will shift the left operand by the right operand according to the left operand’s orientation and position. In other words, it gets the world space of the right operand assuming it’s in the local space of the left operand.

An example: part.CFrame * Vector3.new(part.Size.x/2, 0, 0) will give you the point in the center of the right face of the part, regardless of how the part is rotated.

part.CFrame * Vector3.new(part.Size.x/2, part.Size.y/2, -part.Size.z/2) would give you the top right front corner. We can repeat this for all corners to get all corner positions.


The following function returns an array of Vector3s corresponding to every corner of the given CFrame and Size:

local function getCorners(cframe, size)
	return {
		cframe * Vector3.new(-size.x/2, -size.y/2, -size.z/2),
		cframe * Vector3.new(-size.x/2, -size.y/2,  size.z/2),
		cframe * Vector3.new( size.x/2, -size.y/2,  size.z/2),
		cframe * Vector3.new( size.x/2, -size.y/2, -size.z/2),
		cframe * Vector3.new(-size.x/2,  size.y/2, -size.z/2),
		cframe * Vector3.new(-size.x/2,  size.y/2,  size.z/2),
		cframe * Vector3.new( size.x/2,  size.y/2,  size.z/2),
		cframe * Vector3.new( size.x/2,  size.y/2, -size.z/2),
	}
end

The following lets you pass in a part:

local function getPartCorners(part)
	return getCorners(part.CFrame, part.Size)
end

The * operator also works for shifting a CFrame by another CFrame. part.CFrame * CFrame.new(part.Size.x/2, 0, 0) works just as well as part.CFrame * Vector3.new(part.Size.x/2, 0, 0), you just get a CFrame at the position with the part’s rotation instead of a Vector3.

CFrame * Operator


You can check if all of the corners of the part are (not) in the player’s view, but this requires 8 times as many checks. I feel like if you have to do too many of these, it’ll become a performance issue. You can probably get away with using some sort of buffer space around the screen if you are doing this with a lot of parts. You’ll only get an “estimate” of if it’s on the screen then, but it’s better than low performance.

What are you trying to check if parts are on the screen for?

7 Likes

I will be using these checks for a custom local player rendering system, the players are only 1 part on the server so to optimise I’d like to check if that part is visible on the screen and only then render the player.

Roblox already doesn’t render things that are off-screen.

(Members-only link to topic)

I highly suspect that showing/hiding the characters will have a greater cost than just letting Roblox handle it how they already do. If you’re using this check to enabled/disable animation updates or similar, then it may be reasonable.

2 Likes

Yes I’m using this to update animations and the position of the local character through tweening etc.

This. Don’t try to manage rendering in this way, similar to how cluster systems on Roblox are really inefficient.

That sounds alright then. You might want to make sure that you actually have performance issues before doing this, though. Optimizing something that runs fine is a waste of time you could spend completing features on your game. Even if you’re just updating animations and positions, it’s still possible that Roblox’s optimization is better than your own, so you should test without your optimizations first.

1 Like

I know that this topic has been going on for a long time but just to be able to transmit the knowledge I will show you the true solution, and it is the following

local function getCorners(part)
	local cframe,size = part.CFrame,part.Size
	return {
		cframe * Vector3.new(-size.x/2, -size.y/2, -size.z/2),
		cframe * Vector3.new(-size.x/2, -size.y/2,  size.z/2),
		cframe * Vector3.new( size.x/2, -size.y/2,  size.z/2),
		cframe * Vector3.new( size.x/2, -size.y/2, -size.z/2),
		cframe * Vector3.new(-size.x/2,  size.y/2, -size.z/2),
		cframe * Vector3.new(-size.x/2,  size.y/2,  size.z/2),
		cframe * Vector3.new( size.x/2,  size.y/2,  size.z/2),
		cframe * Vector3.new( size.x/2,  size.y/2, -size.z/2),
	}
end








local function isPointVisible(worldPoint, part)
	local vector, onScreen = workspace.CurrentCamera:WorldToViewportPoint(worldPoint)

	if onScreen then
		local origin = workspace.CurrentCamera.CFrame.p
		local ray = Ray.new(origin, worldPoint - origin)
		local hit = workspace:FindPartOnRay(ray)
		if hit and hit~=part then
			
		    return true
		end
	else
		return false
	end
	return true
end






game:GetService("RunService").RenderStepped:Connect(function()
	local children = workspace.Parts:GetChildren()
	for i = 1, #children do
		children[i].Material = "SmoothPlastic"
		
		local vis = false
		local corners = getCorners(children[i])
		
		for n,m in next, corners do
			if isPointVisible(m,children[i]) then
				vis = true
				break
			end
		end
		
		
		if vis or isPointVisible(children[i].Position,children[i]) then
			children[i].Material = "Neon"
			   
		end
		
		
    end
end)

Something I have to say is that the credit for this is not mine, but the credit belongs to TainaBog, the “getcorners” and “ispointvisible” functions i taken from one of their public models in the toolbox, their model was based on a viewport frame screen that showed in real time what is ONLY in the field of view of the viewport frame camera, I was looking for how to understand the programming of its model and that’s why I ended up here, I still don’t understand the mathematics it uses, but At least I know that the purpose of those 2 functions that are complicated for me is to detect any object that is within the limits of a camera.

If you are curious about the tainabog model, here is a link to the video in which I discovered their model: