//##################################################################################################
//
//   Custom Visualization Core library
//   Copyright (C) 2011-2012 Ceetron AS
//    
//   This library is free software: you can redistribute it and/or modify 
//   it under the terms of the GNU General Public License as published by 
//   the Free Software Foundation, either version 3 of the License, or 
//   (at your option) any later version. 
//    
//   This library 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 General Public License at <<http://www.gnu.org/licenses/gpl.html>> 
//   for more details. 
//
//##################################################################################################

#include "cvfBase.h"
#include "cvfLocators.h"
#include "cvfCamera.h"
#include "cvfViewport.h"
#include "cvfRay.h"

namespace cvf {



//==================================================================================================
///
/// \class cvf::LocatorTranslateOnPlane
/// \ingroup Viewing
///
/// 
/// 
//==================================================================================================

//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
LocatorTranslateOnPlane::LocatorTranslateOnPlane(Camera* camera)
:   m_camera(camera),
    m_plane(0, 0, 1, 0),
    m_pos(0, 0, 0),
    m_lastPos(0, 0, 0)
{
    CVF_ASSERT(camera);
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
LocatorTranslateOnPlane::~LocatorTranslateOnPlane()
{
    // Empty destructor to avoid errors with undefined types when cvf::ref's destructor gets called
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void LocatorTranslateOnPlane::setPosition(const Vec3d& position, const Vec3d& planeNormal)
{
    m_pos = position;

    m_plane.setFromPointAndNormal(position, planeNormal);
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
Vec3d LocatorTranslateOnPlane::position() const
{
    return m_pos;
}


//--------------------------------------------------------------------------------------------------
/// The window coordinates are in OpenGL style coordinates, which means a right handed 
/// coordinate system with the origin in the lower left corner of the window.
//--------------------------------------------------------------------------------------------------
void LocatorTranslateOnPlane::start(int x, int y)
{
    CVF_ASSERT(m_camera.notNull());
    ref<Ray> ray = m_camera->rayFromWindowCoordinates(x, y);
    
    Vec3d isect(0, 0, 0);
    ray->planeIntersect(m_plane, &isect);

    m_lastPos = isect;
}


//--------------------------------------------------------------------------------------------------
/// The window coordinates are in OpenGL style coordinates, which means a right handed 
/// coordinate system with the origin in the lower left corner of the window.
//--------------------------------------------------------------------------------------------------
bool LocatorTranslateOnPlane::update(int x, int y)
{
    CVF_ASSERT(m_camera.notNull());

    Vec3d oldPos = m_pos;

    ref<Ray> ray = m_camera->rayFromWindowCoordinates(x, y);

    Vec3d isect(0, 0, 0);
    if (ray->planeIntersect(m_plane, &isect))
    {
        Vec3d delta = (isect - m_lastPos);
        m_plane.projectVector(delta, &delta);
        m_pos += delta;

        m_lastPos = isect;
    }

    if (m_pos == oldPos)
    {
        return false;
    }
    else
    {
        return true;
    }
}




//==================================================================================================
///
/// \class cvf::LocatorPanWalkRotate
/// \ingroup Viewing
///
/// Currently, rotate is not supported
/// 
//==================================================================================================
LocatorPanWalkRotate::LocatorPanWalkRotate(Camera* camera)
:   m_camera(camera),
    m_operation(PAN),
    m_pos(0, 0, 0),
    m_lastPosX(0),
    m_lastPosY(0)
{
    CVF_ASSERT(camera);
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
LocatorPanWalkRotate::~LocatorPanWalkRotate()
{
    // Empty destructor to avoid errors with undefined types when cvf::ref's destructor gets called
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void LocatorPanWalkRotate::setOperation(Operation op)
{
    m_operation = op;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void LocatorPanWalkRotate::setPosition(const Vec3d& position)
{
    m_pos = position;
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
Vec3d LocatorPanWalkRotate::position() const
{
    return m_pos;
}


//--------------------------------------------------------------------------------------------------
/// The window coordinates are in OpenGL style coordinates, which means a right handed 
/// coordinate system with the origin in the lower left corner of the window.
//--------------------------------------------------------------------------------------------------
void LocatorPanWalkRotate::start(int x, int y)
{
    m_lastPosX = x;
    m_lastPosY = y;
}


//--------------------------------------------------------------------------------------------------
/// The window coordinates are in OpenGL style coordinates, which means a right handed 
/// coordinate system with the origin in the lower left corner of the window.
//--------------------------------------------------------------------------------------------------
bool LocatorPanWalkRotate::update(int x, int y)
{
    CVF_ASSERT(m_camera.notNull());

    if (x == m_lastPosX && y == m_lastPosY) return false;

    const double vpPixSizeX = m_camera->viewport()->width();
    const double vpPixSizeY = m_camera->viewport()->height();
    if (vpPixSizeX <= 0 || vpPixSizeY <= 0) return false;

    // Normalized movement in screen plane 
    const double tx = (x - m_lastPosX)/vpPixSizeX;
    const double ty = (y - m_lastPosY)/vpPixSizeY;

    Vec3d oldPos = m_pos;

    if (m_operation == PAN)
    {
        updatePan(tx, ty);
    }
    else if (m_operation == WALK)
    {
        updateWalk(ty);
    }

    m_lastPosX = x;
    m_lastPosY = y;

    if (m_pos == oldPos)
    {
        return false;
    }
    else
    {
        return true;
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void LocatorPanWalkRotate::updatePan(double tx, double ty)
{
    CVF_ASSERT(m_camera.notNull());

    // Viewport size in world coordinates
    const double aspect = m_camera->aspectRatio();
    const double vpWorldSizeY = m_camera->frontPlaneFrustumHeight();
    const double vpWorldSizeX = vpWorldSizeY*aspect;

    const Vec3d camUp = m_camera->up();
    const Vec3d camRight = m_camera->right();

    Camera::ProjectionType projType = m_camera->projection();
 
    if (projType == Camera::PERSPECTIVE)
    {
        // Compute distance from camera to point projected onto camera forward direction
        const Vec3d camPos = m_camera->position();
        const Vec3d camDir = m_camera->direction();
        const Vec3d vDiff = m_pos - camPos;
        const double camPointDist = Math::abs(camDir*vDiff);

        const double nearPlane = m_camera->nearPlane();
        Vec3d vX =  camRight*((tx*vpWorldSizeX)/nearPlane)*camPointDist;
        Vec3d vY =  camUp*((ty*vpWorldSizeY)/nearPlane)*camPointDist;
        
        Vec3d translation = vX + vY;
        m_pos += translation;
    }

    else if (projType == Camera::ORTHO)
    {
        Vec3d vX =  camRight*tx*vpWorldSizeX;
        Vec3d vY =  camUp*ty*vpWorldSizeY;

        Vec3d translation = vX + vY;
        m_pos += translation;
    }
}


//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void LocatorPanWalkRotate::updateWalk(double ty)
{
    CVF_ASSERT(m_camera.notNull());

    const double vpWorldSizeY = m_camera->frontPlaneFrustumHeight();
    const Vec3d camDir = m_camera->direction();

    Camera::ProjectionType projType = m_camera->projection();

    // This is the distance that we will move the point in response to a full (whole viewport) movement of the mouse
    // Might need to revisit this to determine target distance
    // In that case it might be an idea to look at the trackball manipulator
    double targetDist = 0;
    if (projType == Camera::PERSPECTIVE)
    {
        // Compute distance from camera to point projected onto camera forward direction
        const Vec3d camPos = m_camera->position();
        const Vec3d vDiff = m_pos - camPos;
        const double camPointDist = Math::abs(camDir*vDiff);

        targetDist = camPointDist;
    }
    else if (projType == Camera::ORTHO)
    {
        targetDist = vpWorldSizeY;
    }

    double moveDist = ty*targetDist;
    Vec3d translation = camDir*moveDist;

    m_pos += translation;
}


} // namespace cvf

