/************************************************************************
 Correction for projecting on spherical surfaces                      
                                                                      
 created:  Willem de jonge, willem@argcargv.com, 03/2003
 modified: Bob Kuehne, rpk@blue-newt.com, 09/2005

 under construction! doesn't work with ut2003 yet                     
************************************************************************/

#ifndef PROJECTIONCORRECTION_H
#define PROJECTIONCORRECTION_H

#include "stdafx.h"
#include "CaveUTGL.h"

#include <sys/types.h>
#include <sys/stat.h>

#include <string>
#include <fstream>
#include <math.h>

// disable double to float conversion warnings
#pragma warning(disable: 4244)

class Vector3
{
public:
    Vector3() {}
    Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
    float x,y,z;
};

//-------------------------------------------------------------------
// vector addition
inline Vector3 operator+(const Vector3 &a, const Vector3 &b)
{
    Vector3 r;
    r.x = a.x + b.x;
    r.y = a.y + b.y;
    r.z = a.z + b.z;

    return r;
};

//-------------------------------------------------------------------
// vector substraction
inline Vector3 operator-(const Vector3 &a, const Vector3 &b)
{
    Vector3 r;
    r.x = a.x - b.x;
    r.y = a.y - b.y;
    r.z = a.z - b.z;

    return r;
};

//-------------------------------------------------------------------
// dot product
inline float operator*(const Vector3 &a, const Vector3 &b)
{
    return a.x*b.x + a.y*b.y + a.z*b.z;
};

//-------------------------------------------------------------------
// scalar vector multiplication
inline Vector3 operator*(const float s, const Vector3 &a)
{
    Vector3 r;
    r.x = s*a.x;
    r.y = s*a.y;
    r.z = s*a.z;
    return r;
};

inline Vector3 operator/( const Vector3 vv, const float ll)
{
    Vector3 rr;
    rr.x = vv.x/ll;
    rr.y = vv.y/ll;
    rr.z = vv.z/ll;
    return rr;
};

//-------------------------------------------------------------------
// vector length
inline float vectorLength( const Vector3 &vv )
{
    return( sqrt( vv * vv ) );
}

//-------------------------------------------------------------------
// vector normalization
inline Vector3 vectorNormalize( const Vector3 &vv )
{
    Vector3 rr;
    rr = vv / vectorLength( vv );
    return( rr );
}

struct Sphere
{
    Vector3 c;
    float r;
};

struct Plane
{
    Vector3 n;
    float   d;
};

struct Ray
{
    Ray() {}
    Ray(Vector3 start, Vector3 direction) { p = start; d = direction; }

    void normalize()
    {
        d = vectorNormalize( d );
    }

    Vector3 p;
    Vector3 d;
};

class ProjectionDistortion
{
public:
    ProjectionDistortion();
    ~ProjectionDistortion();

    /*
        Cleans up gl state (texture & display list)
    */
    void fini();

    /*!
        Perform distortion correction.
        \pre Scene to be corrected completely rendered into framebuffer.
        \post Scene is rendered on warped mesh with distortion correction applied.
        
        \param[in] debug  Write image file out as debug information. Default: false
        \param[in] grid   Enable/disable grid. Default: false
        \param[in] path   Write image file to this path. Default: ""
        \param[in] name   Write image file to this name. If specified, img is overwritten. Default: ""
        \param[in] nframe Write image file every nth frame. Default: 6000
    */
    void distortionCorrect( bool debug = false, bool grid = false, bool usegl = false,
        std::string path = "", std::string name = "", unsigned int nframe=6000 );

    /*!
        Set the width/height for the various correction needs. This is
        a WORKAROUND for an as yet undiscovered bug which makes the glGet
        Viewport and Scissor information bogus during reads in a valid context.
        Not sure why, so we just added a config parameter to the CaveUT.ini file
        and then pass that information along to here.

        \param[in] ww Viewport/texture width.
        \param[in] hh Viewport/texture height.
    */
    void setViewDimensions( float ww, float hh );
    void getViewDimensions( float &ww, float &hh );

    /*!
        Sets view model. This correlates to the actual projection area within the
        game engine. The view is automatically rotated to be centered between the
        top and bottom ((top-bottom)/2.0).
        \param[in] FOVleft    FOVleft.   Units: degrees.
        \param[in] FOVright   FOVright.  Units: degrees.
        \param[in] FOVtop     FOVtop.    Units: degrees.
        \param[in] FOVbottom  FOVbottom. Units: degrees.
    */
    void setViewModel(float FOVleft, float FOVright, float FOVtop, float FOVbottom , float FOVrotation);
    void getViewModel(float& FOVleft, float& FOVright, float& FOVtop, float& FOVbottom, float &FOVrotation );


    /*!
        Sets projector position and area.
        \param[in] x  X-position of projector. Units: meters.
        \param[in] y  Y-position of projector. Units: meters.
        \param[in] z  Z-position of projector. Units: meters.
        \param[in] FOVleft    FOVleft.   Units: degrees.
        \param[in] FOVright   FOVright.  Units: degrees.
        \param[in] FOVtop     FOVtop.    Units: degrees.
        \param[in] FOVbottom  FOVbottom. Units: degrees.
        \param[in] FOPpitch Projector rotation (around horizontal axis). Units: degrees.
        \param[in] FOPpitch Projector rotation (around vertical axis). Units: degrees.
    */
    void setProjector(float x, float y, float z, 
            float FOVleft, float FOVright,
            float FOVtop, float FOVbottom,
            float FOPpitch,
            float FOPyaw );
    void getProjector(float& x, float& y, float& z,
            float& FOVleft, float& FOVright,
            float& FOVtop, float& FOVbottom,
            float& FOVpitch, float& FOVyaw );

    /*!
        Sets eyepoint.
        \param[in] xx  X-position of eye. Units: meters.
        \param[in] yy  Y-position of eye. Units: meters.
        \param[in] zz  Z-position of eye. Units: meters.
    */
    void setEye(float xx, float yy, float zz);
    /*!
        Retrieves eyepoint.
        \param[out] xx  X-position of eye. Units: meters.
        \param[out] yy  Y-position of eye. Units: meters.
        \param[out] zz  Z-position of eye. Units: meters.
    */
    void getEye(float& xx, float& yy, float& zz);

    /*!
        Sets color manipulation parameters.
        
        \param[in] redScale Red multiplier.
        \param[in] greenScale Green multiplier.
        \param[in] blueScale Blue multiplier.
        \param[in] redBias Red offset.
        \param[in] greenBias Green offset.
        \param[in] blueBias Blue offset.
    */
    void setColorScaleBias( float redScale, float greenScale, float blueScale,
            float redBias, float greenBias, float blueBias );
    /*!
        Sets sphere radius.
        
        \param[in] radius  Radius of physical sphere. Units: meters
        \param[in] overlap Overlap (bonus) when drawing sphere. If the projected sphere
                           segment is to cover 30 degrees, it's possible that the projector
                           may radiate off the edges of this surface, so this parameter will build
                           extra sphere in the model on which to project. This parameter does not
                           affect the fov used to calculate the eye view. Units: degres 
    */
    void setSphere(float radius, float overlap );
    /*!
        Retrieves sphere radius.
        \param[out] radius  Radius of physical sphere. Units: meters
    */
    void getSphere(float& radius);

    void setBlendGamma( float all, float red, float green, float blue )
    {
        if ( all > 0.0 )
        {
            _gammaInv[ 0 ] = _gammaInv[ 1 ] = _gammaInv[ 2 ] = 1.0/all;
        }
        else
        {
            _gammaInv[ 0 ] = 1.0/red;
            _gammaInv[ 1 ] = 1.0/green;
            _gammaInv[ 2 ] = 1.0/blue;
        }
    }

    void setBlendParams( bool enabled, bool blendDebug, float leftEdgeBegin, float leftEdgeEnd,
                        float rightEdgeBegin, float rightEdgeEnd, float blendExp )
    {
        _blendEnabled    = enabled;
        _blendDebug      = blendDebug;
        _leftBlend[ 0 ]  = leftEdgeBegin;
        _leftBlend[ 1 ]  = leftEdgeEnd;
        _rightBlend[ 0 ] = rightEdgeBegin;
        _rightBlend[ 1 ] = rightEdgeEnd;
        _blendExp = blendExp;
    }

private:
    Ray  v(float x, float y);
    void p_inv(Ray &ray, float *x, float *y);

    void initializeBuffer();
    void correct(float src_x, float src_y, float *dst_x, float *dst_y);

    /*!
        Builds the display list for a particular mapping, applying distorted texture coordinates
        to a 2d mesh. Currently hardcoded to 20x20. Probably better to encode something that more
        precisely represents the area being drawn to. Try to make the quads/primitives relatively
        square in aspect ratio.
        \param[ in ] debug Turn on debugging output and visual elements
        \param[ in ] usegl Use OpenGL-projected-texture distortion correction
    */
    void recalculate( bool debug = false, bool usegl = false );
    bool intersect(float radius, Ray &ray, Vector3 *i);
    bool intersect(const Plane &plane, const Ray &ray, Vector3 *i);
    /*!
        Issues an OpenGL texcoord and vertex call based on the x/y and texcoords specified.
        Only functional for non-projected-texture corrections.
    */
    void distortedVertex(float old_x, float old_y, float new_x, float new_y);
    void surface2Texcoords(float x, float y, float *u, float *v);
    void surface2ScreenCoords(float x, float y, float *sx, float *sy);

    GLuint m_buffer;
    GLint  m_buffer_width;
    GLint  m_buffer_height;

    GLint  m_vp_width;
    GLint  m_vp_height;

    float  m_hack_width;
    float  m_hack_height;

    float  m_width;                 // width and height of viewport
    float  m_height;

    bool m_paramsChanged;
    bool m_bufferInitialized;

    GLuint m_displayList;

    GLJumpTable::GLhandleARB  blendProgram;
    bool _blendEnabled;
    bool _blendDebug;
    float _leftBlend[ 4 ];
    float _rightBlend[ 4 ];
    float _blendExp;

    // view model used to create planar perspective projection
    float  m_viewFOVleft;
    float  m_viewFOVright;
    float  m_viewFOVtop;
    float  m_viewFOVbottom;
    float  m_viewFOVrotation;

    // projection model to use for spherical projection
    float   m_projFOVleft;
    float   m_projFOVright;
    float   m_projFOVtop;
    float   m_projFOVbottom;
    float   m_projFOVpitch;
    float   m_projFOVyaw;
    Vector3 m_projPos;
    Vector3 m_eyePos;
    float   m_radius;
    float   m_sphereOverlap;

    // color parameters
    float _colorScale[ 4 ];
    float _colorBias[ 4 ];
    float _gammaInv[ 3 ];

    // reload file info
    struct stat m_lastupdate;

    std::ostream *oo;
};

#endif
