#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;
}