zoom = ({ controls, camera, speed = 1 }, zoomDelta = 0) => {
  let { scale } = controls

  if (controls.userControl.zoom && camera && zoomDelta !== undefined && zoomDelta !== 0 && !isNaN(zoomDelta)) {
    const sign = Math.sign(zoomDelta) === 0 ? 1 : Math.sign(zoomDelta)
    zoomDelta = (zoomDelta / zoomDelta) * sign * speed// controls.userControl.zoomSpeed
    // adjust zoom scaling based on distance : the closer to the target, the lesser zoom scaling we apply
    // zoomDelta *= Math.exp(Math.max(camera.scale * 0.05, 1))
    // updated scale after we will apply the new zoomDelta to the current scale
    const newScale = (zoomDelta + controls.scale)
    // updated distance after the scale has been updated, used to prevent going outside limits
    const newDistance = vec3.distance(camera.position, camera.target) * newScale

    if (newDistance > controls.limits.minDistance && newDistance < controls.limits.maxDistance) {
      scale += zoomDelta
    }
    // for ortho cameras
    if (camera.projectionType === 'orthographic') {
      const distance = vec3.length(vec3.subtract([], camera.position, camera.target)) * 0.3
      const width = Math.tan(camera.fov) * distance * camera.aspect
      const height = Math.tan(camera.fov) * distance

      const projection = require('../cameras/orthographicCamera').setProjection(camera, { width, height })
      camera = projection
    }

    /* if (controls.autoAdjustPlanes) {
      // these are empirical values , after a LOT of testing
      const distance = vec3.squaredDistance(camera.target, camera.position)
      camera.near = Math.min(Math.max(5, distance * 0.0015), 100)
    } */
  }
  return { controls: { scale }, camera }
}
compute camera state to zoom the camera
@param {Object} controls the controls data/state
@param {Object} camera the camera data/state
@param {Float} zoomDelta value of the zoom
@return {Object} the updated camera data/state