3d adjust camera position in front of some objects on window resize (solved)

Hello there,
It is embarrassing :flushed:, but I need some maths help, please.
When my app starts, the player is moved to the counter that hold some books that serve as a menu (don’t mind the two white books, I am not done yet):


Now, on window resize or different aspect ratios of the device used, there is a problem if the scale of the window height is larger than the scale of the window width.
Screenshot 2022-06-27 at 10.52.38
So, I want to move the camera further away, roughly here:
Screenshot 2022-06-27 at 10.55.41
2d, no probs, but for 3d, I am unable to find the factor by which to alter the position:
Screenshot 2022-06-27 at 10.53.56
If someone could help me with this, I would be very grateful.

Forgot to add: it’s a perspective camera, the projection is: “use_camera_projection”.

The built-in perspective camera keeps the same vertical FOV when the window is resized. So if you change the width of the window, it just crops what you see horizontally, while the vertical view size stays constant. But what you’re looking for is to keep the horizontal view size constant, so you can always see your books. You need to scale the distance between your camera and the books (In other words: it’s not a fixed offset, it depends on how far away the books are).

I fiddled around and essentially found a solution by educated trial and error, but to sum up:

  • If window is scaled up vertically, offset the camera backwards so objects stay the same size on screen (counteract the usual “zooming” effect).
  • If the window is scaled up horizontally, offset the camera forwards so objects fill the same proportion of the window width.

(And vice versa of course if the window is scaled down.)

Demo project: perspective-cam-resize.zip (9.9 KB)

The relevant code:

Click to show
local origW = tonumber(sys.get_config("display.width"))
local origH = tonumber(sys.get_config("display.height"))

local distFromPlayerToTarget = 10

local function onResize(self, event, data)
	if event == window.WINDOW_EVENT_RESIZED then
		local newW, newH = data.width, data.height

		local widthScale = newW / origW
		local heightScale = newH / origH

		local scaledDist = distFromPlayerToTarget * heightScale
		-- Player stays at the same position.
		-- Offset camera forward & back by difference in distance.
		local camOffset = scaledDist - distFromPlayerToTarget
		go.set("/camera", "position.z", camOffset)
	end
end

I used a “player” object that is a certain distance away from the target that I want to look at, with the “camera” on a child object with a default position of (0, 0, 0), that I offset forward and back on the Z axis.

3 Likes

:grinning: @ross.grams, thank you, Ross for your reply and for the fiddling to find a solution to my problem!

Ah, great, I wanted to ask anyway which kind of FOV I am dealing with. I did find some formulae in C# and JS that use vertical and horizontal FOV, distance between object and camera and size of the object, couldn’t get them to work though - yet.
I will put your project and your code into practice tomorrow, too tired now. Will report back to let you know.
Thanks a lot again for your help!

Cheers

Brigitte

2 Likes

I had to look up the FOV myself too, but it does actually say it in the Camera Manual and in the API doc for vmath.matrix4_perspective() (which can be used in lieu of the regular camera to calculate your own perspective projection).

2 Likes

You are right, I should have spotted this as well, just can’t beat my old habit of skim reading :flushed:
Will send you a pm about some camera stuff I’d like to discuss with you if you agree. Once everything works like I have in mind, I’ll present it here.

1 Like

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 :grinning_face_with_smiling_eyes:)


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
5 Likes

Yep, we had a very fruitful discussion about maths - the maths I failed to grasp :laughing:, and Ross came up with this beautiful solution on how to keep 3d objects in the view of the camera when a window is resized or the device has a different aspect ratio.
Ross, thank you very much for it and for actually writing the solution to my problem, this was much more than I could have expected. Now, my close-ups work in any scenario, very happy. :smiley:

3 Likes