/*
* The Simple Earth Project
* Copyright (C) 2016 HueSoft LLC
* Author: Hector Mendoza, hector.mendoza@huesoftllc.com
*
* This file is part of the Simple Earth Project.
*
* The Simple Earth Project is free software: you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version
* 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see
* .
*/
#include "Constants.h"
#include "Camera.h"
#include "math.h"
#include "Utilities.h"
#include "ElevationManager.h"
//Singleton implementation
Camera* Camera::mInstance = NULL;
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Constructor. Initializes attributes.
*/
Camera::Camera()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mPosition.latitude = mDesiredPosition.latitude = 0.0;
mPosition.longitude = mDesiredPosition.longitude = 0.0;
mPosition.altitude = mDesiredPosition.altitude = Constants::CAMERA_INITIAL_ALTITUDE;
mLookAt.x = mDesiredLookAt.x = 0.0;
mLookAt.y = mDesiredLookAt.y = 0.0;
mLookAt.z = mDesiredLookAt.z = 0.0;
mUpVector.x = mDesiredUpVector.x = 0.0;
mUpVector.y = mDesiredUpVector.y = 0.0;
mUpVector.z = mDesiredUpVector.z = 1.0;
mYaw = mDesiredYaw = Constants::PI/2.0;
mLookAtAltitude = mDesiredLookAtAltitude = 0.0f;
mCameraOrbitMove = true;
mScreenSize.x = 1600;
mScreenSize.y = 1200;
mAltitudeGranularity = 300.0;
mShiftAngleGranularity = 1.0;
mYawAngleGranularity = 0.02;
mPitchAltitudeGranulatity = 0.5f;
mMaximumRadius = 40000.0f;
mSyncMoveByScreen = false;
mMoveByScreenInitialPoint.x = mMoveByScreenFinalPoint.x = 0;
mMoveByScreenInitialPoint.y = mMoveByScreenFinalPoint.y = 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Destructor.
*/
Camera::~Camera()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mInstance = NULL;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Singleton pattern implementation. Returns the single instance of this class.
*
* @return The single instance of this object.
*/
Camera* Camera::getInstance()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
if (mInstance == NULL)
{
mInstance = new Camera();
}
return mInstance;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* This is one of the main camera movement function. It moves the camera
* according to the given discrete movement option.
*
* @param movement See discrete movement options in header file for valid values
*/
void Camera::moveByDiscrete(int movement)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
double overShoot;
float groundElevation = ElevationManager::getInstance()->getElevation(mDesiredPosition.latitude, mDesiredPosition.longitude);
//compute radius increment step based on
//distance from the surface of the Earth
mAltitudeGranularity = (mDesiredPosition.altitude - groundElevation)/10.0;
//clamp altitude increment to 1m
if (mAltitudeGranularity < 0.001)
{
mAltitudeGranularity = 0.001;
}
mShiftAngleGranularity = (mDesiredPosition.altitude - (double)groundElevation)/10000.0;
//clamp angle granularity to 1m at the equator
if (mShiftAngleGranularity < 0.000009)
{
mShiftAngleGranularity = 0.000009;
}
if (movement == ZOOM_IN || movement == ZOOM_OUT)
{
if (movement == ZOOM_IN)
{
//prevent terrain getting too close to the camera
//that it would dissapear due to near clipping
overShoot = mDesiredPosition.altitude - mAltitudeGranularity;
if (overShoot > (groundElevation+0.01))
{
mDesiredPosition.altitude -= mAltitudeGranularity;
}
else
{
//clamp at the ground
mDesiredPosition.altitude = groundElevation + 0.01;
}
}
else if (movement == ZOOM_OUT && mDesiredPosition.altitude 180.0)
{
mDesiredPosition.longitude -= 360.0;
}
}
else if (movement == SHIFT_RIGHT)
{
mDesiredPosition.longitude -= mShiftAngleGranularity;
if (mDesiredPosition.longitude < -180.0)
{
mDesiredPosition.longitude += 360.0;
}
}
else if (movement == SHIFT_UP)
{
//prevent gimbal lock at north pole
overShoot = mDesiredPosition.latitude + mShiftAngleGranularity;
if (overShoot < 85.0)
{
mDesiredPosition.latitude = overShoot;
}
}
else if (movement == SHIFT_DOWN)
{
//prevent gimbal lock at south pole
overShoot = mDesiredPosition.latitude - mShiftAngleGranularity;
if (overShoot > -85.0)
{
mDesiredPosition.latitude = overShoot;
}
}
}
else
{
bool setLatitude = false;
//if camera is in perspective view mode, the
//movements need to reflect the forward vector
if (movement == SHIFT_LEFT)
{
overShoot = mDesiredPosition.latitude - mShiftAngleGranularity * cos(mDesiredYaw);
mDesiredPosition.longitude += mShiftAngleGranularity * sin(mDesiredYaw);
setLatitude = true;
}
else if (movement == SHIFT_RIGHT)
{
overShoot = mDesiredPosition.latitude + mShiftAngleGranularity * cos(mDesiredYaw);
mDesiredPosition.longitude -= mShiftAngleGranularity * sin(mDesiredYaw);
setLatitude = true;
}
else if (movement == SHIFT_UP)
{
overShoot = mDesiredPosition.latitude + mShiftAngleGranularity * sin(mDesiredYaw);
mDesiredPosition.longitude += mShiftAngleGranularity * cos(mDesiredYaw);
setLatitude = true;
}
else if (movement == SHIFT_DOWN)
{
overShoot = mDesiredPosition.latitude - mShiftAngleGranularity * sin(mDesiredYaw);
mDesiredPosition.longitude -= mShiftAngleGranularity * cos(mDesiredYaw);
setLatitude = true;
}
else if (movement == YAW_LEFT)
{
mDesiredYaw += mYawAngleGranularity;
}
else if (movement == YAW_RIGHT)
{
mDesiredYaw -= mYawAngleGranularity;
}
else if (movement == PITCH_UP)
{
mDesiredLookAtAltitude += mPitchAltitudeGranulatity;
}
else if (movement == PITCH_DOWN)
{
mDesiredLookAtAltitude -= mPitchAltitudeGranulatity;
}
//protect from gimbal lock
if (setLatitude && overShoot < 85.0 && overShoot > -85.0)
{
mDesiredPosition.latitude = overShoot;
}
//after the new camera position has been determined we need to
//compute up vector and look at since we are in perspective view
computeUpVectorAndLookAt();
}//end of else of if (mCameraOrbitMove)
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Sets the desired navigation end point and starts the navigation on a separate
* thread.
*
* @param desiredPosition The desired end point for the navigation.
*/
void Camera::moveByDestinationPoint(const GeodeticPosition& desiredPosition)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mDesiredEndPoint = desiredPosition;
start();
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* OVERRIDEN FROM QThread. Executes on a separate thread. Gets called once by
* moveByDestinationPoint to navigate to the desired location.
*/
void Camera::run()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
//determine if sling shot effect needs to be
//shown depending on desired camera altitude
int slingshotAltitude;
if (mDesiredEndPoint.altitude < 4000.0f)
{
slingshotAltitude = 4000.0f;
}
else
{
slingshotAltitude = mDesiredEndPoint.altitude;
}
//interpolate between current and desired position
int numberOfSteps = 100;
float latitudeStepSize = (mDesiredEndPoint.latitude - mDesiredPosition.latitude)/(float)numberOfSteps;
float longitudeStepSize = (mDesiredEndPoint.longitude - mDesiredPosition.longitude)/(float)numberOfSteps;
float altitudeStepSize1 = (slingshotAltitude - mDesiredPosition.altitude)/((float)numberOfSteps/2.0f);
float altitudeStepSize2 = (mDesiredEndPoint.altitude - slingshotAltitude)/((float)numberOfSteps/2.0f);
GeodeticPosition newPosition;
for (int i = 0; i < numberOfSteps; i++)
{
newPosition.latitude = mDesiredPosition.latitude + latitudeStepSize;
newPosition.longitude = mDesiredPosition.longitude + longitudeStepSize;
if (i < (numberOfSteps/2))
{
newPosition.altitude = mDesiredPosition.altitude + altitudeStepSize1;
}
else
{
newPosition.altitude = mDesiredPosition.altitude + altitudeStepSize2;
}
setGeodeticPosition(newPosition);
msleep(33);
}
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* This method takes two pixel coordinates and stores the values in order to
* later compute the world coordinates of both locations and move the camera
* according to the difference between the two points.
*
* @param initialLocation Initial pixel location
* @param finalLocation Final pixel location
*/
void Camera::moveByScreen(const ScreenCoordinates& initialPoint, const ScreenCoordinates& finalPoint)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mMoveByScreenInitialPoint = initialPoint;
mMoveByScreenFinalPoint = finalPoint;
mSyncMoveByScreen = true;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* This method gets called under the OpenGL rendering context in order to
* perform projections and set the desired position based on previosuly given
* screen coordinates.
*/
void Camera::syncMoveByScreen()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
if (mSyncMoveByScreen)
{
mSyncMoveByScreen = false;
//compute geodetic position of screen locations
GeodeticPosition initialPosGeo = Utilities::xyzToGeodetic(Utilities::screenToWorld(mMoveByScreenInitialPoint));
GeodeticPosition finalPosGeo = Utilities::xyzToGeodetic(Utilities::screenToWorld(mMoveByScreenFinalPoint));
//ensure both locations are on the surface of the earth. Note, Mt. Everest is
//a little less than 9Km in altitude and is the highest point on the earth
if (initialPosGeo.altitude < 9.0 && finalPosGeo.altitude < 9.0)
{
//get angle difference between both points and move camera by angle difference
GeodeticPosition newPosition = mDesiredPosition;
newPosition.latitude += initialPosGeo.latitude - finalPosGeo.latitude;
newPosition.longitude += initialPosGeo.longitude - finalPosGeo.longitude;
//also make sure we don't go near the poles to avoid gimbal lock
if (newPosition.latitude < 85.0 && newPosition.latitude > -85.0)
{
setGeodeticPosition(newPosition);
}
//wrap longitude around
if (mDesiredPosition.longitude > 180.0)
{
mDesiredPosition.longitude -= 360.0;
}
else if (mDesiredPosition.longitude < -180.0)
{
mDesiredPosition.longitude += 360.0;
}
}
}
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* This method ensures that the camera only moves under the OpenGL rendering
* thread to achieve smooth camera motion.
*/
void Camera::sync()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mPosition = mDesiredPosition;
mLookAt = mDesiredLookAt;
mUpVector = mDesiredUpVector;
mYaw = mDesiredYaw;
mLookAtAltitude = mDesiredLookAtAltitude;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Returns the current position of the camera in OpenGL(XYZ) coordinates.
*
* @return Current XYZ position of the camera
*/
SimpleVector Camera::getPosition()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
return Utilities::geodeticToXYZ(mPosition);
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Returns the camera's current geodetic position.
*
* @return Current geodetic position for the camera
*/
const GeodeticPosition& Camera::getGeodeticPosition() const
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
return mPosition;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Returns the current look-at position of the camera.
*
* @return Current look-at position of the camera
*/
const SimpleVector& Camera::getLookAt() const
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
return mLookAt;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Returns the current up vector of the camera.
*
* @return Current camera up vector
*/
const SimpleVector& Camera::getUpVector() const
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
return mUpVector;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Returns the camera yaw angle. This is only meaningful when the user is in
* perspective view mode.
*
* @return The yaw angle in radians
*/
double Camera::getYaw()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
return mYaw;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Returns the altitude for the camera's look at point.
*
* @return The altitude of current look at point
*/
float Camera::getLookAtAltitude()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
return mLookAtAltitude;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Returns true if the camera is set to orbit movement. False if camera is set
* to pespective movement.
*
* @return True if camera is set to orbit movement, false if it is set to
* perspective movement
*/
bool Camera::getCameraOrbitMove()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
return mCameraOrbitMove;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Returns the current screen size.
*
* @return Screen size
*/
const ScreenCoordinates& Camera::getScreenSize() const
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
return mScreenSize;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Sets the desired camera position in XYZ coordinates.
*
* @param position Desired camera position in XYZ
*/
void Camera::setPosition(const SimpleVector& position)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
setGeodeticPosition(Utilities::xyzToGeodetic(position));
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Sets the desired geodetic position for the camera.
*
* @param position Desired geodetic position
*/
void Camera::setGeodeticPosition(const GeodeticPosition& position)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mDesiredPosition = position;
//after the new camera position has been determined we need to
//compute up vector and look at if we are in perspective view
if (!mCameraOrbitMove)
{
computeUpVectorAndLookAt();
}
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Sets the current look at position of the camera.
*
* @param lookAt New look at position
*/
void Camera::setLookAt(const SimpleVector& lookAt)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mDesiredLookAt = lookAt;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Sets the current up vector of the camera.
*
* @param upVector New camera up vector
*/
void Camera::setUpVector(const SimpleVector& upVector)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mDesiredUpVector = upVector;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Sets the camera yaw angle. This is only meaningful when the user is in
* perspective view mode.
*
* @param angle Yaw angle in radians
*/
void Camera::setYaw(double angle)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mDesiredYaw = angle;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Sets the altitude of the camera's look at point.
*
* @param altitude New altitude of look at point
*/
void Camera::setLookAtAltitude(float altitude)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mDesiredLookAtAltitude = altitude;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Set whether the camera movement is orbit or perspective mode.
*
* @param value New camera movement mode
*/
void Camera::setCameraOrbitMove(bool value)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
if (value)
{
//orbit up vector is the z axis
mDesiredUpVector.x = 0.0;
mDesiredUpVector.y = 0.0;
mDesiredUpVector.z = 1.0;
//look at center of the Earth
mDesiredLookAt.x = 0.0;
mDesiredLookAt.y = 0.0;
mDesiredLookAt.z = 0.0;
}
else
{
mDesiredYaw = Constants::PI / 2.0;//reset yaw
computeUpVectorAndLookAt();
}
mCameraOrbitMove = value;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Set method for the current screen size.
*
* @param screenSize New value for screen size
*/
void Camera::setScreenSize(const ScreenCoordinates& screenSize)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
mScreenSize = screenSize;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/**
* Computes up vector and look at position based on current position and yaw.
*/
void Camera::computeUpVectorAndLookAt()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
SimpleVector desiredPosition = Utilities::geodeticToXYZ(mDesiredPosition);
float cameraRadius = mDesiredPosition.altitude + Constants::EARTH_MEAN_RADIUS;
//compute up vector
mDesiredUpVector.x = desiredPosition.x / cameraRadius;
mDesiredUpVector.y = desiredPosition.y / cameraRadius;
mDesiredUpVector.z = desiredPosition.z / cameraRadius;
//look at is half a degree up towards the forward vector
GeodeticPosition lookAtPosition = mDesiredPosition;
lookAtPosition.latitude += sin(mDesiredYaw) / 2.0;
lookAtPosition.longitude += cos(mDesiredYaw) / 2.0;
lookAtPosition.altitude = mDesiredLookAtAltitude;
mDesiredLookAt = Utilities::geodeticToXYZ(lookAtPosition);
}