OK, after some “internal discussion”, here is a new demo project that works as intended—to reposition a default perspective camera to fit an object, of a certain size, in view, regardless of the window size and proportion. In other words: “zoom to fit” with a fixed-FOV perspective camera.
perspective-zoom-to-fit.zip (11.7 KB)
The essential code is pretty simple. Assuming you’re looking straight at the target, you can just take it’s width and height, the camera FOV, and the window size, and calculate the required distance between the target and the camera.
local function getCamDistToSeeSize(FOV, size)
return (size/2) / math.tan(FOV/2)
end
local function getHorizFOV(vertFOV, aspect)
return 2 * math.atan(math.tan(vertFOV/2) * aspect)
end
local function getCamDistToSeeTarget(targetWidth, targetHeight, vertFOV, winAspect)
local horizFOV = getHorizFOV(vertFOV, winAspect)
local distForWidth = getCamDistToSeeSize(horizFOV, targetWidth)
local distForHeight = getCamDistToSeeSize(vertFOV, targetHeight)
return math.max(distForWidth, distForHeight)
end
Longer Explanation
Considering one axis at a time, if you know the FOV angle and you know the size of the painting/target object, then it’s pretty simple to find the distance that the camera needs to be at:
tan(angle) = opposite / adjacent
— The definition of tan
. We want to solve for “adjacent”.
adjacent * tan(angle) = opposite
— Multiplied both sides by adjacent
.
adjacent = opposite / tan(angle)
— Divided both sides by tan(angle)
.
camDist = (size/2) / math.tan(FOV/2)
— Swap in real values/functions.
So that’s the 'getCamDistToSeeSize'
function (the best name I could think of )
Next:
local function getHorizFOV(vertFOV, aspect)
return 2 * math.atan(math.tan(vertFOV/2) * aspect)
end
Honestly I didn’t fully think this one through myself, but that’s the formula for getting the horizontal FOV from the vertical FOV and the aspect ratio of the window or viewport. You get the tangent of the vertical FOV, then multiply by the aspect ratio, then invert—do the arctangent to get the horizontal FOV angle.
So we use those functions together to get what we need:
local function getCamDistToSeeTarget(targetWidth, targetHeight, vertFOV, winAspect)
-- The horizontal FOV changes with the window proportion, so calculate that first:
local horizFOV = getHorizFOV(vertFOV, winAspect)
-- Check each dimension (vertical and horizontal) to see how far away
-- the camera needs to be to fit the target in view (see above diagram again).
local distForWidth = getCamDistToSeeSize(horizFOV, targetWidth)
local distForHeight = getCamDistToSeeSize(vertFOV, targetHeight)
-- We need the target to fit on screen in both dimensions, so use the larger distance.
return math.max(distForWidth, distForHeight)
end