Newer
Older
Import / projects / Gameloft / bne_lib / code / Camera / GameCameraController.cpp
#include "GameCameraController.h"

#include "Utils/Plane.h"
#include "Utils/CasualCoreCandidates.h"

#include <CasualCore/CasualCore.h>
#include <RKBoundsVolume.h>
#include <RKCamera.h>
#include <RKDevice.h>

using namespace bne;

SERIALIZE_BEGIN(GameCameraController)
SERIALIZE((float&)m_pars.minimumZoom, "Minimum zoom distance", "The minimum distance from the camera's pivot point", "Camera Zoom", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.maximumZoom, "Maximum zoom distance", "The maximum distance from the camera's pivot point", "Camera Zoom", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.zoomExtension, "Zoom distance extension", "The maximum distance the camera can be zoomed outside of it's bounds (during gestures)", "Camera Zoom", RKSERIAL_FLAG_NONE);

SERIALIZE((float&)m_pars.panDeceleration, "Pan Deceleration", "Deceleration rate when the camera is not actively panning", "Camera Pan", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.panMaxInertialSpeed, "Pan Max Inertial Speed", "Maximum Camera Inertial Speed", "Camera Pan", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.panExtension, "Pan extension", "The maximum distance the camera can be panned outside of it's bounds (during gestures)", "Camera Pan", RKSERIAL_FLAG_NONE);

SERIALIZE(m_pars.enableCameraExtents, "Enable Camera Bounds", "Enable the use of the camera viewport extents", "Camera Viewport Extents", RKSERIAL_FLAG_NONE);
SERIALIZE(m_pars.bOverrideViewportCentre, "Override Viewport Centre", "Should the centre of the viewport centre be specified manually?", "Camera Viewport Extents", RKSERIAL_FLAG_NONE);
SERIALIZE(m_pars.viewportCentreOverride, "Viewport Centre Override", "The centre of the viewport extents (overrides initial camera viewport)", "Camera Viewport Extents", RKSERIAL_FLAG_VECTOR_XYZ);
SERIALIZE((float&)m_pars.viewportExtentsNegativeX, "-x extent", "Negative x direction extent for the tile", "Camera Viewport Extents", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.viewportExtentsNegativeZ, "-z extent", "Negative z direction extent for the tile", "Camera Viewport Extents", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.viewportExtentsPositiveX, "+x extent", "Positive x direction extent for the tile", "Camera Viewport Extents", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.viewportExtentsPositiveZ, "+z extent", "Positive z direction extent for the tile", "Camera Viewport Extents", RKSERIAL_FLAG_NONE);

SERIALIZE(m_pars.adjustCameraFOVWithZoom, "Adjust Camera FOV with zoom", "Enable interpolation of camera FOV with change in zoom", "Zoom Adjustments", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.cameraYawAtMinZoom, "Yaw at Min Zoom", "Camera Yaw angle at closest zoom level", "Zoom Adjustments", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.cameraYawAtMaxZoom, "Yaw at Max Zoom", "Camera Yaw angle at furthest zoom level", "Zoom Adjustments", RKSERIAL_FLAG_NONE);
SERIALIZE(m_pars.adjustCameraAngleWithZoom, "Adjust Camera yaw with zoom", "Enable interpolation of camera yaw with change in zoom", "Zoom Adjustments", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.cameraFOVAtMinZoom, "FOV at Min Zoom", "Camera FOV at closest zoom level", "Zoom Adjustments", RKSERIAL_FLAG_NONE);
SERIALIZE((float&)m_pars.cameraFOVAtMaxZoom, "FOV at Max Zoom", "Camera FOV at furthest zoom level", "Zoom Adjustments", RKSERIAL_FLAG_NONE);

SERIALIZE((float&)m_pars.rubberBandRestoreTime, "Rubber Band Tween Time", "The time taken to ease the camera back within it's bounds", "Camera Control Settings", RKSERIAL_FLAG_NONE)
SERIALIZE(m_pars.disableInput, "Disable Input", "Disable user control of this camera", "Camera Control Settings", RKSERIAL_FLAG_NONE);
SERIALIZE_END()

namespace
{
  const Plane s_basePlane(RKVector(0, 0, 0, 0), RKVector(0, 1.f, 0, 0)); //!< RKVector::Zero, RKVector::Up - order of static initialisation issue

  const float s_panEasePercent = 0.7f;  //!< 

  RKVector GetFrustumIntercept(const RKFrustum& a_frustum, RKFrustum::RKFrustumCorner a_nearCorner, RKFrustum::RKFrustumCorner a_farCorner)
  {
    RKVector intercept;
    RKVector nearTopLeft = a_frustum.m_corners[a_nearCorner];
    RKVector topLeftFrustumDir = a_frustum.m_corners[a_farCorner] - nearTopLeft;
    topLeftFrustumDir.NormalizeXYZ();
    s_basePlane.Intersection(nearTopLeft, topLeftFrustumDir, &intercept);
    return intercept;
  }

  float EaseOutQuad(float a_elapsed, float a_duration)
  {
    float t = RKCLAMP(0.f, 1.f, a_elapsed / a_duration);
    return (0.f, 1.f, - t * (t - 2));
  }

  float EaseOutQuad(float a_elapsed, float a_duration, float a_start, float a_delta)
  {
    float t = RKCLAMP(0.f, 1.f, a_elapsed / a_duration);
    return -a_delta * t * (t - 2) + a_start;
  }
}

GameCameraController::GameCameraController()
: InteractiveCameraController()
{
  ClearFlag(NEEDS_UPDATE);
}

GameCameraController::~GameCameraController()
{
  RKCamera_SetDefault();
}

bool GameCameraController::PostInit()
{
  m_position.w = 1.f;

  // Update FOV to device aspect ratio
  SetFov(CalculateDeviceFOV(GetFov()));
  m_pars.cameraFOVAtMinZoom = CalculateDeviceFOV(m_pars.cameraFOVAtMinZoom);
  m_pars.cameraFOVAtMaxZoom = CalculateDeviceFOV(m_pars.cameraFOVAtMaxZoom);

  if (Parent::PostInit())
  {
    SetUpdatable(true);
    return true;
  }
  return false;
}

void GameCameraController::Reset()
{
  m_position.w = 1.f;

  m_ZoomMultiplier = 1.f;

  StopInertialMovement();

  m_bRestoreAfterZoomin = false;
  m_bRestoreAfterPan = false;

  if (m_camera)
  {
    m_camera->SetWorldPosition(m_RefPos);
    m_camera->SetWorldRotation(m_RefQuat);
  }
}

void GameCameraController::SetCameraControl(RKCamera* camera)
{
  Parent::SetCameraControl(camera);
  if (camera)
  {
    m_RefQuat = GetWorldRotation();
    m_RefPos = GetWorldPosition();
    camera->SetWorldPosition(m_RefPos);
    camera->SetWorldRotation(m_RefQuat);
    camera->SetFov(m_perspectiveFov);

    StopInertialMovement();

    // Get boundary info
    m_vBoundaryXAxis = camera->GetXAxis(); m_vBoundaryXAxis.y = 0.0f; m_vBoundaryXAxis.Normalize();
    m_vBoundaryZAxis = camera->GetYAxis(); m_vBoundaryZAxis.y = 0.0f; m_vBoundaryZAxis.Normalize();

    m_vBoundaryCenter = m_pars.bOverrideViewportCentre ? m_pars.viewportCentreOverride : ScreenToWorld(RKVector2{ 0, 0 });
    m_CameraDesiredTarget = m_vBoundaryCenter;

    if (m_pars.adjustCameraFOVWithZoom || m_pars.adjustCameraAngleWithZoom)
    {
      m_defaultZoom = m_pars.maximumZoom;
    }
    else
    {
      const float currentDistanceToGround = (GetLookAtPoint(m_camera) - m_camera->GetWorldPosition()).Length();
      m_defaultZoom = RKCLAMP(m_pars.minimumZoom + 3.f, m_pars.maximumZoom - 3.f, currentDistanceToGround);
    }
    SetZoomCam();
  }
}

void GameCameraController::UpdateFromCamera(RKCamera* camera)
{
  if (camera == nullptr || camera->IsOrtho() || !m_pars.enableCameraExtents)
    return;

  float currentZoom;
  RKVector cameraLookAt = GetLookAtPoint(camera);
  if (m_pars.adjustCameraFOVWithZoom || m_pars.adjustCameraAngleWithZoom)
  {
    currentZoom = m_pars.maximumZoom;
  }
  else
  {
    currentZoom = (cameraLookAt - camera->GetWorldPosition()).Length();
    currentZoom = RKCLAMP(m_pars.minimumZoom + 1.f, m_pars.maximumZoom - 1.f, currentZoom);
  }

  m_defaultZoom = currentZoom;
  RKVector controllerLookAt = GetLookAtPoint(this);
  RKVector controllerDirection = GetWorldTransform().GetZAxis();
  controllerDirection.w = 0;
  controllerDirection.Normalize();
  RKVector newControllerPos = controllerLookAt - (controllerDirection * currentZoom);
  SetWorldPosition(newControllerPos);
}

void GameCameraController::LockCameraInput(bool locked)
{
  m_pars.disableInput = locked;
  if (locked)
  {
    StopInertialMovement();

    m_bInPan = false;
    m_bInPinch = false;

    m_bRestoreAfterZoomin = false;
    m_bRestoreAfterPan = false;
  }
}

void GameCameraController::EnableCameraExtents(bool enabled)
{
  m_pars.enableCameraExtents = enabled;
}

bool GameCameraController::GetBoundedPosition(const RKCamera& a_camera, RKVector& a_retPos)
{
  a_retPos = a_camera.GetPosition();

  ViewportExtentData extentData;
  GetViewportWorldExtents(a_camera, extentData);
  if (extentData.bottomExcess - extentData.topExcess != 0.0f || extentData.leftExcess - extentData.rightExcess != 0.0f)
  {
    a_retPos += (extentData.bottomExcess - extentData.topExcess) * m_vBoundaryZAxis;
    a_retPos += (extentData.leftExcess - extentData.rightExcess) * m_vBoundaryXAxis;
    return true;
  }

  return false;
}

void GameCameraController::CopySettingFromCameraCtrl(CasualCore::CameraController* ctrl)
{
  Parent::CopySettingFromCameraCtrl(ctrl);
  if (ctrl && ctrl->ClassName() == ClassName())
  {
    m_pars = static_cast<GameCameraController *>(ctrl)->m_pars;
  }

  if (ctrl)
  {
    SetWorldPosition(ctrl->GetWorldPosition());
    SetWorldRotation(ctrl->GetWorldRotation());
    UpdateCamera(m_camera);
  }
}

void GameCameraController::Update(float time)
{
  if (m_camera)
  {
    RestoreFromZoomExtension(time);
    UpdateCameraInertia(time);

    // Update position of camera
    if (m_bInPan)
    {
      RKVector cameraCurrTarget = ScreenToWorld(RKVector2(0, 0));
      cameraCurrTarget = RKMath_Lerp(cameraCurrTarget, m_CameraDesiredTarget, s_panEasePercent);
      MoveCameraTowardDesiredTarget(cameraCurrTarget);
    }

    // Apply camera extents
    if (m_pars.enableCameraExtents)
    {
      ApplyExtents();
      RestoreFromPanExtension(time);
    }
    
    SetUserInput(false);
    UpdateTransform();
  }
}

void GameCameraController::RestoreFromZoomExtension(float a_fTime)
{
  if (m_bRestoreAfterZoomin)
  {
    m_zoomRestoreTime += a_fTime;
    if (m_zoomRestoreTime > m_pars.rubberBandRestoreTime)
    {
      m_bRestoreAfterZoomin = false;
    }

    m_defaultZoom = EaseOutQuad(m_zoomRestoreTime, m_pars.rubberBandRestoreTime, m_zoomRestoreStart, m_zoomRestoreDelta);
    SetZoomCam();
  }
}

void GameCameraController::RestoreFromPanExtension(float a_fTime)
{
  if (m_bRestoreAfterPan)
  {
    if (m_camera)
    {
      m_panRestoreTime += a_fTime;
      if (m_panRestoreTime > m_pars.rubberBandRestoreTime)
      {
        m_bRestoreAfterPan = false;
      }

      RKVector pos = m_camera->GetPosition();
      pos.Lerp(m_panRestoreStartPoint, m_panRestoreEndPoint, EaseOutQuad(m_panRestoreTime, m_pars.rubberBandRestoreTime));
      m_camera->SetWorldPosition(pos);
      m_CameraDesiredTarget = pos;
    }
    else
    {
      m_bRestoreAfterPan = false;
    }
  }
}

void GameCameraController::OnTap(float x, float y)
{
  StopInertialMovement();
}

void GameCameraController::PanStarted(float x, float y)
{
  if (m_pars.disableInput || !m_camera) return;

  SetUserInput(true);
  StopInertialMovement();

  m_bInPan = true;
  m_bRestoreAfterPan = false;
  m_CameraDesiredTarget = ScreenToWorld(RKVector2(0, 0));
  m_panCurrentScreenPoint = RKVector2(x, y);
  m_panPreviousScreenPoint = RKVector2(x, y);
}

void GameCameraController::PanChanged(float x, float y)
{
  if (!m_bInPan || m_pars.disableInput || !m_camera) return;

  SetUserInput(true);

  m_CameraDesiredTarget -= ScreenToWorld(RKVector2(x, y)) - ScreenToWorld(m_panCurrentScreenPoint);
  m_panPreviousScreenPoint = m_panCurrentScreenPoint;
  m_panCurrentScreenPoint = RKVector2(x, y);
}

void GameCameraController::PanEnded(float x, float y)
{
  if (!m_bInPan || m_pars.disableInput || !m_camera) return;
  
  SetUserInput(true);
  m_bInPan = false;

  ViewportExtentData extentData;
  GetViewportWorldExtents(*m_camera, extentData);

  if (extentData.bottomExcess - extentData.topExcess == 0.0f && extentData.leftExcess - extentData.rightExcess == 0.0f)
  {
    m_fPanSpeed = m_panDirection.Normalize();
    m_fPanSpeed = fminf(m_fPanSpeed, m_pars.panMaxInertialSpeed);
    m_bPanApplyInertia = true;
  }
  else
  {
    InitPanRestore();
  }
}

void GameCameraController::PinchStarted(float x, float y)
{
  if (m_pars.disableInput || !m_camera) return;

  StopInertialMovement();
  
  m_bInPinch = true;
  m_defaultZoom = ApplyZoomLimits(m_defaultZoom * m_ZoomMultiplier);

  ZoomBy(1.f);
}

void GameCameraController::PinchChanged(float scale, float velocity)
{
  if (m_pars.disableInput || !m_camera) return;
  if (fabs(scale) > RKEPSILON)
  {
    ZoomBy(1.f / scale);
  }
}

void GameCameraController::PinchEnded(float x, float y)
{
  if (m_pars.disableInput || !m_camera) return;

  m_bInPinch = false;
  m_defaultZoom = ApplyZoomLimits(m_defaultZoom * m_ZoomMultiplier);
  m_ZoomMultiplier = 1.f;

  m_zoomRestoreTime = 0;
  m_zoomRestoreStart = m_defaultZoom;
  m_zoomRestoreDelta = (m_defaultZoom < m_pars.minimumZoom) ? m_pars.minimumZoom - m_defaultZoom : m_pars.maximumZoom - m_defaultZoom;
  m_bRestoreAfterZoomin = m_defaultZoom < m_pars.minimumZoom || m_defaultZoom > m_pars.maximumZoom;
}

void GameCameraController::Pinch(float x, float y)
{
  if (m_pars.disableInput || !m_camera) return;
  if (x > 1.0f)
  {
    ZoomBy(0.98f);
  }
  else if (x < 1.0f)
  {
    ZoomBy(1.02f);
  }
  PinchEnded(x, y);
}

void GameCameraController::OnPinchGesture(int gestureId, float x, float y)
{
  if (!m_camera) return;
  SetUserInput(true);
  Parent::OnPinchGesture(gestureId, x, y);
}

void GameCameraController::OnPanGesture(int gestureId, float x, float y)
{
  if (!m_camera) return;
  SetUserInput(true);
  Parent::OnPanGesture(gestureId, x, y);
}

void GameCameraController::ZoomBy(float zoom_multiplier)
{
  m_ZoomMultiplier = zoom_multiplier;
  m_bRestoreAfterZoomin = false;
  SetZoomCam();
}

void GameCameraController::MoveCameraTowardDesiredTarget(const RKVector& target)
{
  if (m_camera)
  {
    m_camera->MoveToLookAt(target);
  }
}

void GameCameraController::GetViewportWorldExtents(const RKCamera& a_camera, ViewportExtentData& a_extentData)
{
  const RKFrustum& frustum = a_camera.GetFrustum();

  RKVector topLeftIntercept = GetFrustumIntercept(frustum, RKFrustum::RKFC_NearTopLeft, RKFrustum::RKFC_FarTopLeft);
  RKVector topRightIntercept = GetFrustumIntercept(frustum, RKFrustum::RKFC_NearTopRight, RKFrustum::RKFC_FarTopRight);
  RKVector bottomRightIntercept = GetFrustumIntercept(frustum, RKFrustum::RKFC_NearBottomRight, RKFrustum::RKFC_FarBottomRight);

  RKVector topLeftOffset = topLeftIntercept - m_vBoundaryCenter;

  a_extentData.topExcess = RKMAX((RKVector::Dot(topLeftOffset, m_vBoundaryZAxis) - m_pars.viewportExtentsNegativeX), 0.0f);
  a_extentData.leftExcess = RKMAX((RKVector::Dot(topLeftOffset, -m_vBoundaryXAxis) - m_pars.viewportExtentsNegativeZ), 0.0f);
  a_extentData.rightExcess = RKMAX((RKVector::Dot(topRightIntercept - m_vBoundaryCenter, m_vBoundaryXAxis) - m_pars.viewportExtentsPositiveZ), 0.0f);
  a_extentData.bottomExcess = RKMAX((RKVector::Dot(bottomRightIntercept - m_vBoundaryCenter, -m_vBoundaryZAxis) - m_pars.viewportExtentsPositiveX), 0.0f);
}

void GameCameraController::SetZoomCam()
{
  if (!m_camera) return;

  float targetZoom = ApplyZoomLimits(m_defaultZoom * m_ZoomMultiplier);

  if (m_pars.adjustCameraFOVWithZoom)
  {
    float zoomRatio = (targetZoom - m_pars.minimumZoom) / (m_pars.maximumZoom - m_pars.minimumZoom);
    m_camera->SetFov(m_pars.cameraFOVAtMinZoom + zoomRatio * (m_pars.cameraFOVAtMaxZoom - m_pars.cameraFOVAtMinZoom));
  }
  if (m_pars.adjustCameraAngleWithZoom)
  {
    float zoomRatio = (targetZoom - m_pars.minimumZoom) / (m_pars.maximumZoom - m_pars.minimumZoom);
    RKVector rotation;
    RKQuaternion rot = m_camera->GetWorldRotation();
    rot.ToEuler(&rotation.x, &rotation.y, &rotation.z);
    rotation.x = m_pars.cameraYawAtMinZoom + zoomRatio * (m_pars.cameraYawAtMaxZoom - m_pars.cameraYawAtMinZoom);
    rot.FromEuler(rotation.x * RKDEGREES_TO_RADIANS, rotation.y, rotation.z);
    m_camera->SetWorldRotation(rot);
  }

  if (!m_camera->IsOrtho())
  {
    RKVector newLookAtPoint = GetLookAtPoint(m_camera);

    RKVector cameraPos = newLookAtPoint - m_camera->GetDirection() * targetZoom;
    m_camera->SetPosition(cameraPos);
  }
  else {
    m_camera->SetZoom(targetZoom);
  }
}

bool GameCameraController::ApplyExtents()
{
  if (!m_camera) return false;

  ViewportExtentData extentData;
  GetViewportWorldExtents(*m_camera, extentData);

  extentData.bottomExcess = RKMAX(extentData.bottomExcess - m_pars.panExtension, 0.0f);
  extentData.topExcess = RKMAX(extentData.topExcess - m_pars.panExtension, 0.0f);
  extentData.leftExcess = RKMAX(extentData.leftExcess - m_pars.panExtension, 0.0f);
  extentData.rightExcess = RKMAX(extentData.rightExcess - m_pars.panExtension, 0.0f);

  if (extentData.bottomExcess - extentData.topExcess == 0.0f && extentData.leftExcess - extentData.rightExcess == 0.0f)
  {
    return false;
  }

  RKVector target = m_camera->GetPosition();
  target += (extentData.bottomExcess - extentData.topExcess) * m_vBoundaryZAxis;
  target += (extentData.leftExcess - extentData.rightExcess) * m_vBoundaryXAxis;

  m_camera->SetWorldPosition(target);
  m_CameraDesiredTarget = target;

  return true;
}

void GameCameraController::UpdateCameraInertia(float time)
{
  if (m_bUserInput && !m_bPanApplyInertia)
  {
    m_panDirection = (m_panCurrentScreenPoint - m_panPreviousScreenPoint) / (time > 0.001f ? time : 0.001f);
  }
  else
  {
    if (m_bPanApplyInertia)
    {
      // Determine the amount of time the camera actually spends panning
      bool finishedMove = false;
      float panTime = time;
      if (m_fPanSpeed - m_pars.panDeceleration * time <= 0.f)
      {
        panTime = m_fPanSpeed / m_pars.panDeceleration;
        finishedMove = true;
      }

      // Determine and apply the base displacement
      float displacementLenth = RKMAX(0.f, m_fPanSpeed * panTime - m_pars.panDeceleration * panTime * panTime / 2.0f);
      RKVector2 delta = m_panDirection * displacementLenth;
      
      m_CameraDesiredTarget -= ScreenToWorld(delta) - ScreenToWorld(RKVector2(0, 0));
      MoveCameraTowardDesiredTarget(m_CameraDesiredTarget);
      
      // Bound the camera to the soft boundary
      RKVector boundPos = RKVector::Zero;
      if (GetBoundedPosition(*m_camera, boundPos))
      {
        m_camera->SetPosition(boundPos);
      }

      // Update the pan speed
      m_fPanSpeed -= m_pars.panDeceleration * time;
      finishedMove = finishedMove || m_fPanSpeed <= 0.f || displacementLenth <= 0.f;

      // Check if the inertial move has finished
      if (finishedMove)
      {
        StopInertialMovement();
      }
    }
  }
}

void GameCameraController::StopInertialMovement()
{
  m_fPanSpeed = 0.f;
  m_bPanApplyInertia = false;
  m_panDirection.Set(0.f, 0.f);
}

float GameCameraController::ApplyZoomLimits(float zoom) const
{
  return RKCLAMP(m_pars.minimumZoom - m_pars.zoomExtension, m_pars.maximumZoom + m_pars.zoomExtension, zoom);
}

RKVector GameCameraController::ScreenToWorld(const RKVector2 & position)
{
  RKVector origin, direction, position_w;
  m_camera->GetWorldRay(position, origin, direction);
  if (!s_basePlane.Intersection(origin, direction, &position_w))
  {
    position_w = origin;
  }

  return position_w;
}

RKVector GameCameraController::GetLookAtPoint(RKTransformObject* camera)
{
  RKVector lookAtPoint(0, 0, 0, 1.f);
  RKVector direction = camera->GetWorldTransform().GetZAxis();
  direction.w = 0;
  direction.Normalize();
  s_basePlane.Intersection(camera->GetWorldPosition(), direction, &lookAtPoint);
  return lookAtPoint;
}

void GameCameraController::InitPanRestore()
{
  if (m_camera)
  {
    RKVector startPoint = m_camera->GetPosition();
    RKVector endPoint = RKVector::Zero;

    if (GetBoundedPosition(*m_camera, endPoint))
    {
      m_bRestoreAfterPan = true;
      m_panRestoreTime = 0.f;
      m_panRestoreStartPoint = startPoint;
      m_panRestoreEndPoint = endPoint;
    }
  }
}

float GameCameraController::CalculateDeviceFOV(float a_srcFOV, float a_srcAspectRatio)
{
  //NOTE: FOVs used in the game are vertical FOVs
  float t = a_srcAspectRatio * tan(a_srcFOV * RKDEGREES_TO_RADIANS * 0.5f);
  float newFov = atan(t / RKDevice_GetAspect()) *  2.0f * RKRADIANS_TO_DEGREES;
  return newFov;
}