Camera Zoom To Fit Object

Great post. Thanks :+1:

Great post !

It didnā€™t work for me at first though, so I changed it a bit.
Also make sure that the offset is at the right scale for youā€¦ If your object is 500 wide, you will have no great result with an offset of 1.5. Try 50.

function fitCameraToObject( camera, object, offset ) {

offset = offset || 1.5;

const boundingBox = new THREE.Box3();

boundingBox.setFromObject( object );

const center = boundingBox.getCenter( new THREE.Vector3() );
const size = boundingBox.getSize( new THREE.Vector3() );

const startDistance = center.distanceTo(camera.position);
// here we must check if the screen is horizontal or vertical, because camera.fov is
// based on the vertical direction.
const endDistance = camera.aspect > 1 ?
					((size.y/2)+offset) / Math.abs(Math.tan(camera.fov/2)) :
					((size.y/2)+offset) / Math.abs(Math.tan(camera.fov/2)) / camera.aspect ;


camera.position.set(
	camera.position.x * endDistance / startDistance,
	camera.position.y * endDistance / startDistance,
	camera.position.z * endDistance / startDistance,
	);
camera.lookAt(center);

};

I wrote an updated version of this function that takes an array of objects and fits the camera to them. Should fit well to any amount of objects of any size. Aside from that, this maintains the direction of the camera and controls.

Codepen

const size = new THREE.Vector3();
const center = new THREE.Vector3();
const box = new THREE.Box3();

function fitCameraToSelection(camera, controls, selection, fitOffset = 1.2) {
  box.makeEmpty();
  for(const object of selection) {
    box.expandByObject(object);
  }
  
  box.getSize(size);
  box.getCenter(center );
  
  const maxSize = Math.max(size.x, size.y, size.z);
  const fitHeightDistance = maxSize / (2 * Math.atan(Math.PI * camera.fov / 360));
  const fitWidthDistance = fitHeightDistance / camera.aspect;
  const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
  
  const direction = controls.target.clone()
    .sub(camera.position)
    .normalize()
    .multiplyScalar(distance);

  controls.maxDistance = distance * 10;
  controls.target.copy(center);
  
  camera.near = distance / 100;
  camera.far = distance * 100;
  camera.updateProjectionMatrix();

  camera.position.copy(controls.target).sub(direction);
  
  controls.update();
}
21 Likes

Related:

1 Like

This works fantastically on PerspectiveCamera, but not on OrthographicCamera since OrthographicCamera doesnā€™t have fov. I tried calculating an fov using what is described at http://forums.cgsociety.org/archive/index.php/t-725538.html , but failed miserably. haha

Any tips for adapting this to the Ortho camera?

Works great, thank you!

In my case Iā€™m using a CSS2DRenderer too to render labels on top of some objects; the camera is correctly facing the objects, but somehow the labels seem not to be updating their positions. I add a label like this (extracted from one of the threejs examples), where points is an array of Three.Points that are correctly rendered.-

var earthDiv = document.createElement( 'div' );
earthDiv.className = 'label';
earthDiv.textContent = 'Nube de puntos';
earthDiv.style.marginTop = '-1em';
var earthLabel = new CSS2DObject( earthDiv );
earthLabel.position.set( 0, 1, 0 );
points[0].add( earthLabel );

Any idea of how to fix this?

OrthoCamera is easier, because the width of the frustum is constant along the length of the frustum. Just set the width/height of the ortho cam to match the width/height of the object you want to fit, and done!

1 Like

If I was straight on, sure, but at an isometric angle, with multiple objects, it isnā€™t that clear cut. I will put together a demo in the morning.

Yeah, when not looking at the object straight on, you could use objectā€™s bounding sphere (not perfect, might have empty space around the edges of the view), or do some trig on the object to get the diagonal size that you need based on the bounding box (a little better), or something more complicated depending on the shape of the geometry (but this one applies for PerspectiveCamera too if you want it to fit perfectly).

1 Like

I second that, we know the canvas size so is there a way to calculate it? In my project by try and error I ended up using 6.50. no idea why this works. magic number?

Maybe this helps: Here are some functions to fit objects within a view using a few lines of trigonometry math:

That code does not work. Box3.getSize() needs a Vector3.
This is a better solution:
Cracking the three.js object fitting (to camera) nut | Wejn.org

1 Like

Oh good catch, the code was outdated. Iā€™ve updated it to use modern three.js style.

Ortho camera solution that worked for me.

export const fitCameraToCenteredObject = (
  camera: THREE.OrthographicCamera,
  object: any
) => {
  const boundingBox = new THREE.Box3();

  boundingBox.setFromObject(object);

  const center = boundingBox.getCenter(new THREE.Vector3());
  const size = boundingBox.getSize(new THREE.Vector3());
  const maxSize = Math.max(size.x, size.y, size.z);
  let newPositionCamera = new THREE.Vector3(maxSize, maxSize, maxSize);
  camera.zoom = 1;
  camera.left = -(2 * maxSize);
  camera.bottom = -(2 * maxSize);
  camera.top = 2 * maxSize;
  camera.right = 2 * maxSize;
  camera.near = -maxSize * 4;
  camera.far = maxSize * 4;
  // camera;
  camera.position.set(
    newPositionCamera.x,
    newPositionCamera.y,
    newPositionCamera.z
  );
  camera.lookAt(0, 0, 0);
  camera.updateProjectionMatrix();
};

1 Like

Since the question was referred to on discord, and itā€™s quite a popular topic involving way more math than any reasonable person would like to be implementing on their own :point_right: if someone is looking for a quick solution for fitting an object / set of objects within the viewport (with any arbitrary zoom or padding), yomotsu/camera-controls may offer just what you need (fitToBox example.)

5 Likes