[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/MAPSWorks/SimpleEarth/master/src/Camera.cpp [Back]  [Original]

/*
 *  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);
}

Web Proxy Viewer  |  New URL  |  Original Page