Search code examples
maththree.jswebglaframequaternions

Aframe/Three fit to screen - calculate zoom


I want to zoom the camera in Three/Aframe so an image fits to screen.

This is the code I'm using:

    this._camera = document.getElementById('camera').getAttribute('camera')
    this._ratio = this._assetWidth/this._assetHeight

    this._vFOV = window.THREE.Math.degToRad( this._camera?.fov || 80 )

    this._height = 2 * Math.tan( this._vFOV / 2 ) * this.data.distance
    this._width = this._height * this._ratio

    this._zoom = this._ratio > 1 ? this._width/window.innerWidth : this._height/window.innerHeight

    console.log(this._zoom,  this._ratio, this._width, window.innerWidth)

I've got to the part where I need to calculate Zoom so that the object fits to the screen, i.e. if it's landscape fits to width, if it's portrait fit to height.

I thought this was the answer but it's not. that's to calculate the camera position rather than zoom value.

I'm stuck on how you work out the zoom value.

Any clues?


Solution

  • Fitting an object to the screen by:

    • changing the camera FoV
    • zooming the camera
    • repositioning the camera / object

    is quite similar once you understand where the formulas came from.
    We'll use this neat image (from this SO thread) as it covers all three topics:

    enter image description here

    0. What do we want to achieve

    We want the object (the longer side of either its width or height) to cover the filmHeight - so it fits the screen.

    1. Recalculating the FoV

    In this case we do know the focalLength (camera distance from the object) and filmHeight (object width or height). We can calculate fov / 2 thanks to our friend trigonometry:

    Tan (fov / 2) = (filmHeight / 2) / focalLength
    => fov = 2 * ATan ((filmHeight / 2)) / focalLength * 180 / PI

    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
    <script>
      AFRAME.registerComponent("fit", {
        init: function() {
          const plane = document.querySelector("a-plane")
          const distance = this.el.object3D.position.distanceTo(plane.object3D.position)
          var height = plane.getAttribute("geometry").height
          var newFov = 2 * Math.atan((height / 2) / distance) * (180 / Math.PI); // in degrees
          this.el.sceneEl.camera.fov = newFov
        }
      })
    </script>
    <a-scene>
      <a-plane position="0 1.6 -2" material="src: https://i.imgur.com/wjobVTN.jpg"></a-plane>
      <a-camera position="0 1.6 0" fit></a-camera>
    </a-scene>

    2. Repositioning the object / camera

    Same triangle different variables. Now we want to know the focalLength:
    Tan (fov / 2) = (filmHeight / 2) / focalLength
    => focalLength = (filmHeight / 2) / Tan (fov / 2)

    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
    <script>
      AFRAME.registerComponent("fit", {
        init: function() {
          const plane = document.querySelector("a-plane")
          const height = plane.getAttribute("geometry").height
          const fov = this.el.sceneEl.camera.fov * (Math.PI / 180);
          const newDistance = Math.abs((height / 2) / Math.tan(fov / 2))
          plane.object3D.position.z = -1 * newDistance;
        }
      })
    </script>
    <a-scene>
      <a-plane position="0 1.6 -2" material="src: https://i.imgur.com/wjobVTN.jpg"></a-plane>
      <a-camera position="0 1.6 0" fit></a-camera>
    </a-scene>

    3. Zooming the camera

    If we know what distance should the camera be from the object for it to fill the screen - we know what is the relation between the current distance, and the new one:

    zoom = currentDistance / necessaryDistance

    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
    <script>
      AFRAME.registerComponent("fit", {
        init: function() {
          const plane = document.querySelector("a-plane");
          const distance = this.el.object3D.position.distanceTo(plane.object3D.position);
          const height = plane.getAttribute("geometry").height;
          const fov = this.el.sceneEl.camera.fov * (Math.PI / 180);
          const newDistance = Math.abs((height / 2) / Math.tan(fov / 2));
          this.el.sceneEl.camera.zoom = distance / newDistance;
        }
      })
    </script>
    <a-scene>
      <a-plane position="0 1.6 -2" material="src: https://i.imgur.com/wjobVTN.jpg"></a-plane>
      <a-camera position="0 1.6 0" fit></a-camera>
    </a-scene>