Camera:ZoomToExtents

As a developer, I want to be able to add a model to a ViewportFrame and get a reasonable camera crop without too much effort.

It would be really great to be able to do something like

Camera:ZoomToExtents(model)

Which would make the camera get as close as possible to the object while still keeping it uncropped in the frame.

I think there’s even a C++ function that already does this in the engine, making this a really high !/$ feature.

44 Likes

I agree that we should add this as a built in method. But for now if anyone needs this functionality in Lua here is a script which replicates the Zoom Extents behavior in Studio (pressing the F key with a model/part selected).

local function getCameraOffset(fov, extentsSize)
	local halfSize = extentsSize.Magnitude / 2
	local fovDivisor = math.tan(math.rad(fov / 2))
	return halfSize / fovDivisor
end

local function zoomToExtents(camera, instance)
	local isModel = instance:IsA("Model")

	local instanceCFrame = isModel and instance:GetModelCFrame() or instance.CFrame
	local extentsSize = isModel and instance:GetExtentsSize() or instance.Size

	local cameraOffset = getCameraOffset(camera.FieldOfView, extentsSize)
	local cameraRotation = camera.CFrame - camera.CFrame.p

	local instancePosition = instanceCFrame.p
	camera.CFrame = cameraRotation + instancePosition + (-cameraRotation.LookVector * cameraOffset)
	camera.Focus = cameraRotation + instancePosition
end

zoomToExtents(workspace.CurrentCamera, workspace.Baseplate)
59 Likes

Thanks for sharing. Added you to my credits list.

I’m not good enough at linear algebra to figure this out myself.

8 Likes

This should still be a built-in function call tho.

8 Likes

I know this is bumping an already answered post, but I’m making a comment b/c there are few things worth noting about TheGamer101’s solution that may cause people some issues (especially in the context of model fitting for a viewport frame)

First off let’s explain the TheGamer101’s method:

Camera’s in Roblox use perspective projection which means you can judge depth. For example, things far away are small and things close are big.

That means your camera’s view bounds look something like this (from a Y & Z axis side profile):

image

Say you know the height of an object you want the camera to zoomToExtents to. You can use basic trigonometry to solve for the distance that your camera needs to be away from the object to fully encapsulate it.

javaw_2021-10-24_10-51-28

-- SOH CAH TOA
-- tan = opposite / adjacent
tan(FOV / 2) = (height / 2) / distance
distance = (height / 2) / (tan(FOV / 2))

So in short, what TheGamer101 is doing is finding the bounding sphere of the model and then using its diameter as the height. The issue here is that when we do this we end up with the following scenario.

javaw_2021-10-24_10-55-35

As you can see some of the circle is cut off in the process so you don’t actually get a perfectly encapsulated model. Instead, you can still do this with trigonometry, but instead use sin instead of tan.

2021-07-11_12-17-37

sin = opposite / hypotenuse
sin(fov2) = radius / distance
distance = radius / sin(fov2)

Now you might think “That’s it, it’s completely solved!”. Unfortunately no, all we’ve focused on so far is the distance for the vertical height of our viewport frame size. We also have to take into account the horizontal width. Otherwise, we may end up in a situation like so:

2021-07-11_12-24-34
It fit’s vertically, but not horizontally!

Luckily, the fix is easy we do the same calculation as above, but instead figure out the horizontal field of view and then we pick the minimum between the two.

local function getModelFitDistance(model, vpf, camera)
	local modelCFrame, modelSize = model:GetBoundingBox()
	local vpfSize = vpf.AbsoluteSize
	
	-- clamped b/c we only want to scale the xfov2 if width < height
	-- otherwise if width > height then xfov2 == yfov2
	local wh = math.min(1, vpfSize.X / vpfSize.Y)

	local yfov2 = math.rad(camera.FieldOfView / 2)
	local xfov2 = math.atan(math.tan(yfov2) * wh)
	local radius = modelSize.Magnitude / 2

	return radius / math.sin(xfov2)
end

RobloxStudioBeta_2021-07-11_14-57-24 2021-07-11_14-57-31

Checkout this post if you want a nice module that does this and a few other things for you!

48 Likes

Thanks for the rundown. I think this kind of complexity is the perfect kind of thing to hide behind an API call that just magically works :stuck_out_tongue:

6 Likes

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