Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move camera to fit 3D scene

I'm looking for an algorithm to fit a bounding box inside a viewport (in my case a DirectX scene). I know about algorithms for centering a bounding sphere in a orthographic camera but would need the same for a bounding box and a perspective camera. I can not just change the FOV because this app has FOV as a user editable variable, so it must move the camera.

I have most of the data:

  • I have the up-vector for the camera
  • I have the center point of the bounding box
  • I have the look-at vector (direction and distance) from the camera point to the box center
  • I have projected the points on a plane perpendicular to the camera and retrieved the coefficients describing how much the max/min X and Y coords are within or outside the viewing plane.

Problems I have:

  • Center of the bounding box isn't necessarily in the center of the viewport (that is, it's bounding rectangle after projection).
  • Since the field of view "skew" the projection (see http://en.wikipedia.org/wiki/File:Perspective-foreshortening.svg) I cannot simply use the coefficients as a scale factor to move the camera because it will overshoot/undershoot the desired camera position

How do I find the camera position so that it fills the viewport as pixel perfect as possible (exception being if the aspect ratio is far from 1.0, it only needs to fill one of the screen axis)?

I've tried some other things:

  • Using a bounding sphere and Tangent to find a scale factor to move the camera. This doesn't work well, because, it doesn't take into account the perspective projection, and secondly spheres are bad bounding volumes for my use because I have a lot of flat and long geometries.
  • Iterating calls to the function to get a smaller and smaller error in the camera position. This has worked somewhat, but I can sometimes run into weird edge cases where the camera position overshoots too much and the error factor increases. Also, when doing this I didn't recenter the model based on the position of the bounding rectangle. I couldn't find a solid, robust way to do that reliably.

Help please!

like image 307
Burre Avatar asked May 19 '10 14:05

Burre


2 Answers

There are many possible camera positions + orientations where the bounding box would fit inside the view frustum. But any procedure would select one specific camera position and orientation.

If you would consider bounding spheres, one solution could be to

  • first change orientation to look at bounding sphere center
  • then move back sufficiently (negative look direction) for bounding sphere to fit inside frustum

With bounding boxes you could consider an earlier step of first positioning the camera at perpendicular to the center of the largest (or smallest, whatever you prefer) cube face.

I have no experience with DirectX, but moving and changing the looking direction of the camera to center a certain point should be easy. The hard part is to do the math of deciding how far to move to view the object.

Math

If you know the bounding size s of the object in world coordinates (we are not interested in pixels or camera coordinates, since those are dependent on your distance) from the orientation of the camera, you can compute the required distance d of the camera to the bounding shape if you know the x and y Field-Of-View angle a of the perspective projection.

     frustum      ------                         ------    *****          -          -----          *   *          |    -===     ) FOV a   *bounding box  | BB size s camera -----          *   *          |             ------    *****          -                   ------      |-------------------|         distance d 

So, the math is tan(a/2) = (s/2) / d => d = (s/2) / tan(a/2) Which will give you the distance the camera should be placed from the closest bounding surface.

like image 173
catchmeifyoutry Avatar answered Sep 19 '22 14:09

catchmeifyoutry


I know there are some excellent answers above, but I wanted to add a rediculously simple solution to fit the bounding sphere inside the camera frustrum. It makes the assumption that you want to keep the camera Target and Forward vector the same, and simply adjust camera distance to target.

Note, this won't give you the best fit but it will give you an approximate fit, showing all geometry, and only in a few lines of code, and without screen to world transformations

enter image description here

// Compute camera radius to fit bounding sphere // Implementation in C# //   // Given a bounding box around your scene BoundingBox bounds = new BoundingBox();  // Compute the centre point of the bounding box // NOTE: The implementation for this is to take the mid-way point between  // two opposing corners of the bounding box Vector3 center = bounds.Center;  // Find the corner of the bounding box which is maximum distance from the  // centre of the bounding box. Vector3.Distance computes the distance between  // two vectors. Select is just nice syntactic sugar to loop  // over Corners and find the max distance. double boundSphereRadius = bounds.Corners.Select(x => Vector3.Distance(x, bounds.Center)).Max();  // Given the camera Field of View in radians double fov = Math3D.DegToRad(FieldOfView);  // Compute the distance the camera should be to fit the entire bounding sphere double camDistance = (boundSphereRadius * 2.0) / Math.Tan(fov / 2.0);  // Now, set camera.Target to bounds.Center // set camera.Radius to camDistance // Keep current forward vector the same 

The implementation of BoundingBox in C# is found below. The important points are the Centre and Corners properties. Vector3 is a pretty standard implementation of a 3 component (X,Y,Z) vector

public struct BoundingBox {             public Vector3 Vec0;     public Vector3 Vec1;      public BoundingBox(Vector3 vec0, Vector3 vec1)     {         Vec0 = vec0;         Vec1 = vec1;     }      public Vector3 Center     {         get { return (Vec0 + Vec1)*0.5; }     }      public IList<Vector3> Corners     {         get         {             Vector3[] corners = new[]             {                 new Vector3( Vec0.X, Vec0.Y, Vec0.Z ),                  new Vector3( Vec1.X, Vec0.Y, Vec0.Z ),                  new Vector3( Vec0.X, Vec1.Y, Vec0.Z ),                  new Vector3( Vec0.X, Vec0.Y, Vec1.Z ),                  new Vector3( Vec1.X, Vec1.Y, Vec0.Z ),                  new Vector3( Vec1.X, Vec0.Y, Vec1.Z ),                  new Vector3( Vec0.X, Vec1.Y, Vec1.Z ),                  new Vector3( Vec1.X, Vec1.Y, Vec1.Z ),              };              return corners;         }     }  } 
like image 36
Dr. Andrew Burnett-Thompson Avatar answered Sep 19 '22 14:09

Dr. Andrew Burnett-Thompson