/******************************************************************************
 *{@C
 *      Copyright:      2010-2018 Paul Obermeier (obermeier@tcl3d.org)
 *
 *                      See the file "Tcl3D_License.txt" for information on
 *                      usage and redistribution of this file, and for a
 *                      DISCLAIMER OF ALL WARRANTIES.
 *
 *      Module:         Tcl3D -> tcl3dOgl
 *      Filename:       tcl3dViewMath.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    C functions for handling model and viewing matrices in
 *                      OpenGL compatible style.
 *                      These are needed as a replacement for the deprecated
 *                      funtionality of the fixed-function pipeline introduced
 *                      in OpenGL 3.
 *                      This module is intended to be wrapped with Swig for the
 *                      Tcl3D package. Matrices are represented
 *                      as standard Tcl lists.
 *
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <float.h>

#include "tcl3dVecMath.h"
#include "tcl3dViewMath.h"

const double PI = 3.1415926535897932384626433832795;

/**
  * Layout of matrices.
  * m[0+4*0]  m[0+4*1]  m[0+4*2]  m[0+4*3]
  * m[1+4*0]  m[1+4*1]  m[1+4*2]  m[1+4*3]
  * m[2+4*0]  m[2+4*1]  m[2+4*2]  m[2+4*3]
  * m[3+4*0]  m[3+4*1]  m[3+4*2]  m[3+4*3]
  */

/**
  * Replacement function for glOrtho.
  * It has the same signature except the last parameter, where the calculated
  * matrix is stored.
  */
void tcl3dOrtho (double left, double right,
                 double bottom, double top,
                 double zNear, double zFar,
                 float *res)
{
    res[0] = (float)(2.0/(right-left));
    res[1] = 0.0f;
    res[2] = 0.0f;
    res[3] = 0.0f;
    res[4] = 0.0f;
    res[5] = (float)(2.0/(top-bottom));
    res[6] = 0.0f;
    res[7] = 0.0f;
    res[8] = 0.0f;
    res[9] = 0.0f;
    res[10] = (float)(-2.0/(zFar-zNear));
    res[11] = 0.0f;
    res[12] = (float)(-(right+left)/(right-left));
    res[13] = (float)(-(top+bottom)/(top-bottom));
    res[14] = (float)(-(zFar+zNear)/(zFar-zNear));
    res[15] = 1.0f;
}

/** Replacement function for glFrustum.
  * It has the same signature except the last parameter, where the calculated
  * matrix is stored.
  */
void tcl3dFrustum (double left, double right,
                   double bottom, double top,
                   double zNear, double zFar,
                   float *res)
{
    res[0] = (float)(2.0*zNear/(right-left));
    res[1] = 0.0f;
    res[2] = 0.0f;
    res[3] = 0.0f;
    res[4] = 0.0f;
    res[5] = (float)(2.0*zNear/(top-bottom));
    res[6] = 0.0f;
    res[7] = 0.0f;
    res[8] = (float)((right+left)/(right-left));
    res[9] = (float)((top+bottom)/(top-bottom));
    res[10] = (float)(-(zFar+zNear)/(zFar-zNear));
    res[11] = -1.0f;
    res[12] =  0.0f;
    res[13] =  0.0f;
    res[14] = (float)(-(2.0*zFar*zNear)/(zFar-zNear));
    res[15] =  0.0f;
}

/** Replacement function for gluPerspective.
  * It has the same signature except the last parameter, where the calculated
  * matrix is stored.
  */
void tcl3dPerspective (double fovy, double aspect,
                       double zNear, double zFar,
                       float *res)
{
    double left, right, bottom, top;
    double fovx;

    top    = zNear * tan(fovy * PI / 360.0);
    bottom = -top;
    right  = aspect * top;
    left   = -right;

    tcl3dFrustum (left, right, bottom, top, zNear, zFar, res);
}

/** Enhanced perspective function for rendering just a subregion of the images.
  */
void tcl3dPerspectiveWithOffset ( double fovX, double fovY,
                                  double zNear, double zFar,
                                  int winWidth, int winHeight,
                                  int subWidth, int subHeight,
                                  int offX, int offY, 
                                  float *res)
{
    double left, right, bottom, top;
    double nTanFovX, nTanFovY;
    double tanFovY, aspect;

    aspect = fovX / fovY;
    tanFovY  = tan (fovY * PI / 360.0);
    nTanFovX = zNear * tanFovY * aspect;
    nTanFovY = zNear * tanFovY;

    left  = -1.0 * nTanFovX * (1.0 - (2.0 * (double)offX / winWidth));
    right = nTanFovX * (((2.0 * (offX + subWidth)) / winWidth) - 1.0);

    bottom = -1.0 * nTanFovY * (1.0 - (2.0 * (double)offY / winHeight));
    top    = nTanFovY * (((2.0 * (offY + subHeight)) / winHeight) - 1.0);

    tcl3dFrustum (left, right, bottom, top, zNear, zFar, res);
}

/** Replacement function for gluLookAt.
  * It has the same signature except the last parameter, where the calculated
  * matrix is stored.
  */
void tcl3dLookAt (double eyeX, double eyeY, double eyeZ, 
                  double centerX, double centerY, double centerZ,
                  double upX, double upY, double upZ,
                  float *res)
{
    float forward[3], side[3], up[3];
    float matrix[16];
    float trans[16];

    forward[0] = centerX - eyeX;
    forward[1] = centerY - eyeY;
    forward[2] = centerZ - eyeZ;
    tcl3dVec3fNormalize (forward);

    up[0] = upX;
    up[1] = upY;
    up[2] = upZ;
    
    tcl3dVec3fCrossProduct (forward, up, side);
    tcl3dVec3fNormalize (side);

    tcl3dVec3fCrossProduct (side, forward, up);

    matrix[0] = side[0];
    matrix[1] = up[0];
    matrix[2] = -forward[0];
    matrix[3] = 0.0f;
    matrix[4] = side[1];
    matrix[5] = up[1];
    matrix[6] = -forward[1];
    matrix[7] = 0.0f;
    matrix[8] = side[2];
    matrix[9] = up[2];
    matrix[10] = -forward[2];
    matrix[11] = 0.0f;
    matrix[12] = 0.0f;
    matrix[13] = 0.0f;
    matrix[14] = 0.0f;
    matrix[15] = 1.0f;

    tcl3dMatfTranslate (-eyeX, -eyeY, -eyeZ, trans);

    tcl3dMatfMult (matrix, trans, res);
}
