• 3D
  • Frame a 3D object so that it fills the viewport

Hi All,

I've been using Godot for a while, on and off over the last couple of years, I "think" this is my first post on the forums, so hello everybody :)

Anyway, I have a question if you wouldn't mind helping.

I want the ability to frame a 3D object in the viewport so that it fills the view. The camera angle can change, the focal object is also not fixed in scale or shape, however, a simple AABB will suffice for the shape approximation. The effect I'm going for is similar to what you would get in the editor itself when you 'Focus Selection' on a node in the scene.

I've looked through the docs and Googled furiously but cannot find anything.

Any help would be greatly appreciated and thank you in advance.

I'm not sure I understand what you mean. Do you want a mesh to fill the entire screen, maybe for post-processing?

No he means moving the camera around so that the view frustum is centered on the selected objects so that they are within the view bounds and zoomed in without anything being left outside.

@Dschoonmaker said: I'm not sure I understand what you mean. Do you want a mesh to fill the entire screen, maybe for post-processing?

@Megalomaniak said: No he means moving the camera around so that the view frustum is centered on the selected objects so that they are within the view bounds and zoomed in without anything being left outside.

Hi @Dschoonmaker no, sorry I didn't explain myself properly. Basically, what @Megalomaniak said, I would liek to focus the camera onto a spurious object within the world when the user selects it.

If you create a 3D scene in the Godot editor, add a node, select the node then press 'F' (not to pay respects) the editor camera will pan, zoom and rotate to make the selection fill the view. I'm looking for pretty much exactly this behaviour.

I can handle the panning and rotating, the bit that I'm stuck on is knowing how far to zoom without clipping parts of the focal item from view.

So you want to zoom in just enough to have the whole object in view, but not so far parts of it will not be shown. Is it a specific object you want to do this on, or can it be any object? What are you trying to do?

@Dschoonmaker said: So you want to zoom in just enough to have the whole object in view, but not so far parts of it will not be shown. Is it a specific object you want to do this on, or can it be any object? What are you trying to do?

As per my original post, it can be any object, any shape and any scale. The specific shape can be safely approximated by an AABB, I don't require this to be exact. The camera can be anywhere in the world too, that is when the transition starts.

I was hoping that as this feature is in the editor that we would have a command in Godot to 'just do it' but I can't see one.

I have a very naive approach in mind, I could point the camera at the focal object and calculate the 2D screen coordinates of each corner vertex then step the camera towards the object, stopping once the screen coordinates get within a certain threshold of the viewport boundaries. But this seems like a messy and inefficient method of achieving this goal.

I wonder if the camera object might have a function for checking that. Haven't looked into it before though.

I guess the interpolated camera might be able to do something about it.

perhaps just creating a vector position to move the camera to might be enough, simply make sure its the distance of the target objects AABB or more away from the target object?

I don't think the editor actually zooms automatically to fit the object inside the screen, it looks like it stays at whatever zoom level you were at previously. Could you perhaps do the same?

Maybe you could zoom out, then for each corner of the AABB, raycast to the camera, and check if it needs to zoom in. The problem is, I don't know how you could calculate how much you'd need to zoom in.

This also seems like a messy and inefficient method of achieving this goal.

@Dschoonmaker said: I don't think the editor actually zooms automatically to fit the object inside the screen, it looks like it stays at whatever zoom level you were at previously. Could you perhaps do the same?

Maybe you could zoom out, then for each corner of the AABB, raycast to the camera, and check if it needs to zoom in. The problem is, I don't know how you could calculate how much you'd need to zoom in.

This also seems like a messy and inefficient method of achieving this goal.

It should do, well it does in my editor? If the object has a visible component, the camera points at the object and zooms in. I'm running 3.2.stable.

@Megalomaniak said: I wonder if the camera object might have a function for checking that. Haven't looked into it before though.

I guess the interpolated camera might be able to do something about it.

perhaps just creating a vector position to move the camera to might be enough, simply make sure its the distance of the target objects AABB or more away from the target object?

I've had a look through the documentation of the Camera and can't find anything.

Hmmm, this might actually work! The focusable objects won't have extreme ratios in their size, maybe 4:1 at a maximum. I could as you say, take the largest size across all coordinates, then align the camera at a fixed distance from the object based on the extremity and a coefficient based on the ratio of the viewport rect. I know the FOV is fixed to what I assign, so there won't be any variables to contend with.

Unless somebody comes along with a better way of doing it, I will put something simple together and try cranking the window size around like a crazed window resizer and see if I can trip it up.

@Dschoonmaker Excellent! That looks like the wizardry that I'm after.

Thank you very much.

Somewhat. It works very well if I position the camera along a cardinal axis. I just need to tweak the code slightly to make it work along any arbitrary axis. To do this I need to get a normalised vector that points to the camera's current location from the origin of the focal object, then use that to project the distance and thus position the camera.

Once I have this working, I will post the code here for people to use.

@monotonic said: Somewhat. It works very well if I position the camera along a cardinal axis. I just need to tweak the code slightly to make it work along any arbitrary axis. To do this I need to get a normalised vector that points to the camera's current location from the origin of the focal object, then use that to project the distance and thus position the camera.

Once I have this working, I will post the code here for people to use.

I have some code for a 3rd person camera that can set the camera position based on X&Y angles, and distance. Could this help with that?

Okay, I think this function should do it, but I haven't tested it very much:

func zoom_to_fit(mesh):
	var objectSize = mesh.get_aabb().get_longest_axis_size()
	var distance = abs(objectSize / sin(fov / 2))

And set the position based on distance. This doesn't factor in the mesh's position.

Also, this script uses the longest axis of the AABB, so it has the effect of a cube AABB. It might look bad on a very long AABB.

OK, I've implemented a very basic function which takes into account the cameras original position, then focuses and moves to the target node. Essentially it calculates the new position of the camera along the line between the camera and the target node, adjusted by the calculated distance.

The code to follow will immediately move the camera to the target position and angle. For my purposes, I will be adding a smooth rotation and translation which will be fairly simple to implement.

func focus_camera_on_node(camera: Camera, node: MeshInstance, margin = 1.1) -> void:
	var fov = camera.fov
	var max_extent =  node.get_aabb().get_longest_axis_size()
	var min_distance = (max_extent * margin) / sin(deg2rad(fov / 2.0))

	camera.look_at(node.translation, Vector3.UP)
	
	var offset = (camera.translation - node.translation).normalized()
	
	camera.translation = node.translation + (offset * min_distance)

So it works? I'm not entirely sure the function I mentioned is reliable. I don't actually know how it works, I just took it off StackOverflow and converted it to GDScript.

Hi @Dschoonmaker

I used the algorithm that you linked to from the Unity forums and converted over to GDscript with some tweaks to make it function the way I needed it. The snippet above is rough and needs a lot of attention but should it be enough to get going should anybody else need it.

The bare essentials are there, but it does require the following to make it game ready:

  1. DON'T use the get_longest_axis_size() method it is crude and will only work for objects that are evenly sized in all directions. This would work if the longest axis is perpendicular to the angle that the camera is looking down. A better method would be to get the max extent of the object along the plane that is perpendicular to the cameras viewing angle.
  2. This code snaps the camera to the position and rotation, this would be horrific in-game, use interpolation instead.

I have these two issues resolved in my game code, but again, the above should serve as a good start for anybody else. In regard to how it actually works, the original poster on the Unity thread you linked to made quite a good explanation and accompanying sketch. It definitely taught me something new.

Thanks for the help @Dschoonmaker and @Megalomaniak, it is appreciated.

Glad I could help! I did know my code was kind of crude, but if you've managed to improve it, good!

@monotonic said: A better method would be to get the max extent of the object along the plane that is perpendicular to the cameras viewing angle. I also thought of this, it's a good idea, I just don't know how to calculate it.