Functions to calculate the visible width / height at a given z-depth from a perspective camera

In case it helps anyone, here’s how to fit an object (most likely a flat plane) on the screen so that it “fits” depending on aspect ratio. For example, if we have an object taller than it is wide we want the Y dimension (the tall dimension) to fit within the view, but if the object is wider than it is tall we want the X dimension (the width dimension) to fit within view.

The following distanceToFitObjectToView function will tell you at what distance to place the object so that it fits within view regardless which dimension is bigger:

(The code has TypeScript type annotations, but you can remove them, f.e. the : number stuff)

(Note, angles are in radians!)

/**
 * Convert vertical field of view to horizontal field of view, given an aspect
 * ratio. See https://arstechnica.com/civis/viewtopic.php?f=6&t=37447
 *
 * @param vfov - The vertical field of view.
 * @param aspect - The camera aspect ratio, which is generally width/height of the viewport.
 * @returns - The horizontal field of view.
 */
function vfovToHfov(vfov: number, aspect: number): number {
  const {tan, atan} = Math
  return atan(aspect * tan(vfov / 2)) * 2
}

/**
 * Get the distance from the camera to fit an object in view by either its
 * horizontal or its vertical dimension.
 *
 * @param size - This should be the width or height of the object to fit.
 * @param fov - If `size` is the object's width, `fov` should be the horizontal
 * field of view of the view camera. If `size` is the object's height, then
 * `fov` should be the view camera's vertical field of view.
 * @returns - The distance from the camera so that the object will fit from
 * edge to edge of the viewport.
 */
function _distanceToFitObjectInView(size: number, fov: number): number {
  const {tan} = Math
  return size / (2 * tan(fov / 2))
}

function distanceToFitObjectToView(
  cameraAspect: number,
  cameraVFov: number,
  objWidth: number,
  objHeight: number
): number {
  const objAspect = objWidth / objHeight
  const cameraHFov = vfovToHfov(cameraVFov, cameraAspect)

  let distance: number = 0

  if (objAspect > cameraAspect) {
    distance = _distanceToFitObjectInView(objHeight, cameraVFov)
  } else if (objAspect <= cameraAspect) {
    distance = _distanceToFitObjectInView(objWidth, cameraHFov)
  }

  return distance
}

Demo

Here’s a live example showing the concepts put together. Open it in new tab, try resizing vertically and horizontally, and try both “contain” and “cover” modes in the code (like CSS object-fit):

5 Likes