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
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
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):
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.
So in short, what TheGamer101 is doing is finding the bounding sphere of the model and then using its circumfrence as the height. The issue here is that when we do this we end up with the following scenario.
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.
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:
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)
Checkout this post if you want a nice module that does this and a few other things for you!