/**
 * @file VCRA-SZCZERBACKI-PointBasedRendering.c
 * @brief Point Based Rendering Tool.
 *                                                                    
 * Point Based Rendering Tool (using OpenGL and GLUT).                 \n
 *                                                                     \n
 * Using partially the splatting code implemented by Jussi Rasanen for \n
 * his Master Thesis "Surface Splatting: Theory, Extensions and        \n
 * Implementation", 2002, at Helsinki University of Technology         \n
 *                                                                     \n
 * Course:      Visao Computacional e Realidade Aumentada (PUC-RIO).   \n
 * Instructor:  Marcelo Gattass.                                       \n
 * Student:     Ricardo Szczerbacki.                                   \n
 * Version:     1.00.                                                  \n
 * 2006.2                                                              \n
 */

#include "stopwatch.h"
#include "splatting.h"

extern void analyticPipeline( const std::vector<Splat> &splats, ABuffer *abuffer, const Matrix4x4 &objectToCamera, const Matrix4x4 &projection, const Vector4 &viewport, float front, float back );

const float PI = 3.14159265358979323846f;

Mesh    *lwo     = NULL;
Color   *buffer  = NULL;
ABuffer *abuffer = NULL;

std::vector<Splat>	splats;


#include <fstream>
#include <windows.h>

#include <glut.h>   /* includes glut and OpenGL */

#include <stdlib.h>  /* libs ANSI C */
#include <stdio.h>



/* Global variables */
bool inputFile = false;
char inputFileName[200];

float xRotation = 0.0f, yRotation = 0.0f, zRotation = 0.0f;
float yRotationAcc = 0.0f;
float rotationStep = 10.0f;
float xEye, yEye, zEye, zEyeStep,
      xCenter, yCenter, zCenter, 
      xUp, yUp, zUp, 
      xLight, yLight, zLight,
      nearValue, farValue,
      normalSizeFactor;

float xAxis[3] = {1.0f, 0.0f, 0.0f}, 
      yAxis[3] = {0.0f, 1.0f, 0.0f}, 
      zAxis[3] = {0.0f, 0.0f, 1.0f};

GLdouble modelview[16], modelviewSplatting[16];

int width = 800-50;      /**< Canvas width */
int height = 770-50;     /**< Canvas height */
int textHeight = 160;    /**< Text area  height */
int currentStep = 0;     /**< Current process step */

int LEFT_COL_SIZE = 130;
int TOP_ROW_SIZE = 100;

int widthImage3D = width - 3 - LEFT_COL_SIZE - 3 - 1 - 1 - 3;
int heightImage3D = height - 3 - 1 - 1 - 3 - TOP_ROW_SIZE - 3;

float xMin, yMin, zMin, xMax, yMax, zMax, maxDimension;


void __cdecl svPrint(const char *format, ...)
{
	static char	text[1000];
	ULONG i;

	va_list	argptr;
	va_start(argptr, format);
	vsprintf(text, format, argptr);
	va_end(argptr);

	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),text,strlen(text),&i,0);
}

void __cdecl svError(const char *format, ...)
{
	static char	text[1000];
	ULONG i;

	va_list	argptr;
	va_start(argptr, format);
	vsprintf(text, format, argptr);
	va_end(argptr);

	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),text,strlen(text),&i,0);
    exit( -1 );
}

const char *__cdecl STR( const char *format, ... )
{
	static char string[1024];

	va_list arg;

    va_start( arg, format );
    vsprintf( string, format, arg );
    va_end( arg );

	return string;
}


void initCamera()
{
  lwo->calculateBoundingBox();
  xMin = lwo->getBoundingBoxMin().x;
  yMin = lwo->getBoundingBoxMin().y;
  zMin = lwo->getBoundingBoxMin().z;
  xMax = lwo->getBoundingBoxMax().x;
  yMax = lwo->getBoundingBoxMax().y;
  zMax = lwo->getBoundingBoxMax().z;
  if ((xMax - xMin) > (yMax - yMin)) 
    if ((xMax - xMin) > (zMax - zMin))
      maxDimension = xMax - xMin;
    else
      maxDimension = zMax - zMin;
  else if ((yMax - yMin) > (zMax - zMin))
      maxDimension = yMax - yMin;
    else
      maxDimension = zMax - zMin;

  xEye     = (xMax + xMin)/2;
  yEye     = (yMax + yMin)/2, 
  zEye     = maxDimension*2;
  zEyeStep = zEye/10;

  xCenter = (xMax + xMin)/2;
  yCenter = (yMax + yMin)/2;
  zCenter = (zMax + zMin)/2;

  xUp     = 0.0;
  yUp     = 1.0;
  zUp     = 0.0;

  nearValue = maxDimension/10;
  farValue  = 10*maxDimension;

  normalSizeFactor = maxDimension/50;

  xLight    = xMax + (xMax - xMin);
  yLight    = yMax + (yMax - yMin);
  zLight    = -2*maxDimension;

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
}

/**
 * Change state variable for step processing
 */
void executeNextStep()
{
  if (currentStep == 0) {
    loadLwoFile(lwoFileName);
    initCamera();
  }

  if (++currentStep == 7)
    exit(0);
}

/**
 * Change state variable for step processing
 */
void executePreviousStep()
{
  if (currentStep > 0)
    currentStep--;
}

/**
 * Draw text using openGL
 */
void drawText(int x, int y, char *string) {
  int len, i;

  glRasterPos2f(x, y);
  len = (int) strlen(string);
  for (i = 0; i < len; i++)
	  glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12 , string[i]);
}


/* ************************
 * Application callbacks  *
 **************************/

void display_cb(void)   /**< Callback for canvas display */
{	
  glutSetCursor(GLUT_CURSOR_WAIT);
  
  Stopwatch watch;
  double renderingTime;
  char output[200];
  int yStep, yDetails, yCommands,
      xStep, xDetails, xCommands;

  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  /* black */
  glClear(GL_COLOR_BUFFER_BIT);          /* set background color to black*/

  

  /* -------------------------------------------------------------------------- */
  /* Draw 3D polygon based rendering (if step 1 is the current one)             */
  /* -------------------------------------------------------------------------- */
  if (currentStep == 1) {
    watch.start();

    glViewport(3 + LEFT_COL_SIZE + 3 + 1, 3 + 1, widthImage3D, heightImage3D);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(90,(double)widthImage3D/(double)heightImage3D, nearValue, farValue);
    gluLookAt(xEye, yEye, -zEye, xCenter, yCenter, zCenter, xUp, yUp, zUp);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    float light_ambient[]=  { 0.02f, 0.02f, 0.02f, 1.0f }; 
    float light_diffuse[]=  { 1.0f, 1.0f, 1.0f }; 
    float light_position0[]= { 1.0, 1.0, zLight, 1.0f };

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_CULL_FACE);

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position0);

    glLoadMatrixd(modelview);

    if (yRotation) {
      glRotatef(yRotation, yAxis[0], yAxis[1], yAxis[2]);
      yRotation = 0.0f;
    }

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);

    glPushMatrix();
    glScalef(2.0, 2.0, 2.0);
    glBegin(GL_TRIANGLES);
      glColor3f(0.55,0.72,0.85);
      int t;
      Mesh::Triangle *triangle;
      for (t = 0; t < lwo->getTriangleCount(); t++) {
        triangle = &lwo->getTriangle(t);
        glNormal3f(triangle->m_normal.x, triangle->m_normal.y, triangle->m_normal.z);
        glVertex3f(lwo->getVertex(triangle->m_v[0]).m_position.x, lwo->getVertex(triangle->m_v[0]).m_position.y, lwo->getVertex(triangle->m_v[0]).m_position.z);
        glVertex3f(lwo->getVertex(triangle->m_v[1]).m_position.x, lwo->getVertex(triangle->m_v[1]).m_position.y, lwo->getVertex(triangle->m_v[1]).m_position.z);
        glVertex3f(lwo->getVertex(triangle->m_v[2]).m_position.x, lwo->getVertex(triangle->m_v[2]).m_position.y, lwo->getVertex(triangle->m_v[2]).m_position.z);
      }
      
    glEnd();
    glPopMatrix();
    renderingTime = watch.stop();
  }

  /* -------------------------------------------------------------------------- */
  /* Draw 3D vertex representation (if step 2 is the current one)               */
  /* -------------------------------------------------------------------------- */

  if (currentStep == 2) {
    watch.start();

    glViewport(3 + LEFT_COL_SIZE + 3 + 1, 3 + 1, widthImage3D, heightImage3D);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(90,(double)widthImage3D/(double)heightImage3D, nearValue, farValue);
    gluLookAt(xEye, yEye, -zEye, xCenter, yCenter, zCenter, xUp, yUp, zUp);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixd(modelview);


    if (yRotation) {
      glRotatef(yRotation, yAxis[0], yAxis[1], yAxis[2]);
      yRotation = 0.0f;
    }

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);

    float light_ambient[]=  { 0.15f, 0.15f, 0.15f, 1.0f }; 
    float light_diffuse[]=  { 0.4f, 0.4f, 0.4f }; 
    float light_position0[]= { xLight, yLight, zLight, 1.0f };

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_CULL_FACE);
    glPointSize(2.0f);

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
    
    glPushMatrix();
    glScalef(2.0, 2.0, 2.0);
    glBegin(GL_POINTS);
      glColor3f(0.55,0.72,0.85);
      int v;
      Mesh::Vertex *vertex;
      for (v = 0; v < lwo->getVertexCount(); v++) {
        vertex = &lwo->getVertex(v);
        glNormal3f(vertex->m_normal.x, vertex->m_normal.y, vertex->m_normal.z);
        glVertex3f(vertex->m_position.x, vertex->m_position.y, vertex->m_position.z);
      }
      
    glEnd();
    glPopMatrix();
    renderingTime = watch.stop();
  }

  /* -------------------------------------------------------------------------- */
  /* Draw 3D normal representation (if step 3 is the current one)                */
  /* -------------------------------------------------------------------------- */

  if (currentStep == 3) {
    watch.start();

    glViewport(3 + LEFT_COL_SIZE + 3 + 1, 3 + 1, widthImage3D, heightImage3D);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(90,(double)widthImage3D/(double)heightImage3D, nearValue, farValue);
    gluLookAt(xEye, yEye, -zEye, xCenter, yCenter, zCenter, xUp, yUp, zUp);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixd(modelview);


    if (yRotation) {
      glRotatef(yRotation, yAxis[0], yAxis[1], yAxis[2]);
      yRotation = 0.0f;
    }

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);

    float light_ambient[]=  { 0.15f, 0.15f, 0.15f, 1.0f }; 
    float light_diffuse[]=  { 0.4f, 0.4f, 0.4f }; 
    float light_position0[]= { xLight, yLight, zLight, 1.0f };

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_CULL_FACE);
    glEnable(GL_NORMALIZE);
    glPointSize(2.0f);
    glLineWidth(0.5f);

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
    
    glPushMatrix();
    glScalef(2.0, 2.0, 2.0);
    glBegin(GL_POINTS);
      glColor3f(0.55,0.72,0.85);
      int v;
      Mesh::Vertex *vertex;
      for (v = 0; v < lwo->getVertexCount(); v++) {
        vertex = &lwo->getVertex(v);
        glNormal3f(vertex->m_normal.x, vertex->m_normal.y, vertex->m_normal.z);
        glVertex3f(vertex->m_position.x, vertex->m_position.y, vertex->m_position.z);
      }

      glEnd();
      
    glBegin(GL_LINES);
      float resizedNormal[3];
      glColor3f(1.0,0.15,0.15);
      for (v = 0; v < lwo->getVertexCount(); v++) {
        vertex = &lwo->getVertex(v);
        resizedNormal[0] = vertex->m_normal.x * normalSizeFactor;
        resizedNormal[1] = vertex->m_normal.y * normalSizeFactor;
        resizedNormal[2] = vertex->m_normal.z * normalSizeFactor;
        glNormal3f(vertex->m_normal.x, vertex->m_normal.y, vertex->m_normal.z);
        glVertex3f(vertex->m_position.x, vertex->m_position.y, vertex->m_position.z);
        glVertex3f(vertex->m_position.x + resizedNormal[0], vertex->m_position.y + resizedNormal[1], vertex->m_position.z + resizedNormal[2]);
      }
      
    glEnd();
    glPopMatrix();
    renderingTime = watch.stop();
  }



  /* -------------------------------------------------------------------------- */
  /* Draw 3D splat representation (if step 4 is the current one)                */
  /* -------------------------------------------------------------------------- */

  if ((currentStep == 4) || (currentStep == 5)) {
    watch.start();

    glViewport(3 + LEFT_COL_SIZE + 3 + 1, 3 + 1, widthImage3D, heightImage3D);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(90,(double)widthImage3D/(double)heightImage3D, nearValue, farValue);
    gluLookAt(xEye, yEye, -zEye, xCenter, yCenter, zCenter, xUp, yUp, zUp);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixd(modelview);


    if (yRotation) {
      glRotatef(yRotation, yAxis[0], yAxis[1], yAxis[2]);
      yRotation = 0.0f;
    }

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);

    float light_ambient[]=  { 0.15f, 0.15f, 0.15f, 1.0f }; 
    float light_diffuse[]=  { 0.4f, 0.4f, 0.4f }; 
    float light_position0[]= { xLight, yLight, zLight, 1.0f };

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_CULL_FACE);
    glEnable(GL_NORMALIZE);
    glPointSize(2.0f);
    glLineWidth(0.5f);

    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
    
    glColor3f(0.55,0.72,0.85);

    GLUquadric *quadric = gluNewQuadric(); 

    int s;
    float angle;
    float rotationVector[3];
    glPushMatrix();
    glScalef(2.0, 2.0, 2.0);
    for (s = 0; s < splats.size(); s++) {
      glPushMatrix();

      angle = acos(splats[s].m_normal.z/sqrt(splats[s].m_normal.x*splats[s].m_normal.x +
                                             splats[s].m_normal.y*splats[s].m_normal.y +
                                             splats[s].m_normal.z*splats[s].m_normal.z)) * 360/(2*PI);
      
      rotationVector[0] = -splats[s].m_normal.y;
      rotationVector[1] = splats[s].m_normal.x;
      rotationVector[2] = 0;
      
      glTranslatef(splats[s].m_position.x, splats[s].m_position.y, splats[s].m_position.z);
      glRotatef(angle, rotationVector[0], rotationVector[1], rotationVector[2]);
      if (currentStep == 4)
        gluDisk(quadric, 0.0f, splats[s].m_size/3, 30, 1);
      if (currentStep == 5)
        gluDisk(quadric, splats[s].m_size*0.95, splats[s].m_size, 30, 1);

      glPopMatrix();
    }
    glPopMatrix();


    renderingTime = watch.stop();
  }

  /* -------------------------------------------------------------------------- */
  /* Draw 3D splat based rendering (if step 6 is the current one)               */
  /* -------------------------------------------------------------------------- */

  if (currentStep == 6) 
  {
    watch.start();

    glViewport(3 + LEFT_COL_SIZE + 3 + 1, 3 + 1, widthImage3D, heightImage3D);

    glEnable(GL_TEXTURE_2D);

    glClearColor( 0, 0, 0, 0 );
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0f,1.0f,1.0f);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE,GL_ONE);

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective(90,(float)widthImage3D/(float)heightImage3D,0.01,1000);
    gluLookAt(xEye, yEye, -zEye, xCenter, yCenter, zCenter, xUp, yUp, zUp);
    glMatrixMode( GL_MODELVIEW );
    glPushMatrix();
    glLoadIdentity();

    float sx = pixelScale * (float)sizex / defaultWidth;
    float sy = pixelScale * (float)sizey / defaultWidth;

    figsx = sx*2.0f/(float)sizex;
    figsy = sy*2.0f/(float)sizey;

    static Timer t;
    t.sample();

    static CameraControls cameraControls(1.0f,1.0f,0.f,false);//rotationScale,translationScale,inertia,framerateDependent);
    Matrix4x4 cameraToWorld = cameraControls.getCameraMatrix();
    Matrix4x4 worldToCamera;
    worldToCamera = cameraToWorld;
    worldToCamera.invert();

    static float a = 0.0f;
    Matrix4x4 objectToCamera;
    objectToCamera.ident();
    objectToCamera *= MatrixFactory::rotateY( -yRotationAcc * (2*PI)/360 );
    objectToCamera *= 3.0f;
    objectToCamera.setColumn(3,Vector4(0,0,10,1.0f));
    objectToCamera.transpose();
    worldToCamera.transpose();
    objectToCamera *= worldToCamera;

    const float	hfov = 90.0f;
    const float	vfov = 90.0f;
    const float front = nearValue; /*0.1f*/
    const float	back = farValue; /*100.0f*/

    float hx = 1.0f / float(tan(degreeToRad(hfov)*0.5f));
    float hy = 1.0f / float(tan(degreeToRad(vfov)*0.5f));
    float Q = back / (back - front);

    Matrix4x4 projection(
      hx, 0,   0,        0,
      0,  -hy, 0,        0,
      0,  0,   Q,		   1,
      0,  0,   -Q*front, 0);

    Vector4 viewport(0,0,(float)sizex,(float)sizey);

    static bool zrange = true;

    static float zthreshold = 0.1f;

    glScalef(4.0 + maxDimension/2.0, 4.0 + maxDimension/2.0,1.0);

    abuffer->clear();

    analyticPipeline(splats,abuffer,objectToCamera,projection,viewport,front,back);

    abuffer->collapseToFrameBuffer(buffer, Vector4(0.0f,0.0f,0.0f,1.0f),zrange,zthreshold);

    //render texture mapped quad
    glTexImage2D(GL_TEXTURE_2D, 0, 4, sizex, sizey, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    glBegin(GL_TRIANGLE_FAN);
      glTexCoord2f(0,0);
      glVertex3f(-sx,sy,-1);
      glTexCoord2f(1,0);
      glVertex3f(sx,sy,-1);
      glTexCoord2f(1,1);
      glVertex3f(sx,-sy,-1);
      glTexCoord2f(0,1);
      glVertex3f(-sx,-sy,-1);
	  glEnd();

	  glPopMatrix();

    renderingTime = watch.stop();

    glDisable(GL_TEXTURE_2D);
  }

  /* -------------------------------------------------------------------------- */
  /* Draw user interface, external to 3D rendering (always)                     */
  /* -------------------------------------------------------------------------- */

  glViewport(0, 0, width, height);

  glDisable(GL_LIGHTING);
  glDisable(GL_LIGHT0);
  glDisable(GL_CULL_FACE);
  glDisable(GL_NORMALIZE);
  glDisable(GL_BLEND);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D (0.0, width, 0.0, height);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  // Draw text backgrounds
  glBegin(GL_QUADS);
    // Step title display (upper left)
    glColor3f(0.15,0.2,0.2);
    glVertex2i(3, height - 3);
    glVertex2i(LEFT_COL_SIZE, height - 3);
    glVertex2i(LEFT_COL_SIZE, height - 3 - TOP_ROW_SIZE);
    glVertex2i(3, height - 3 - TOP_ROW_SIZE);

    // Step details display (upper right)
    glColor3f(0.22,0.30,0.30);
    glVertex2i(3 + LEFT_COL_SIZE, height - 3);
    glVertex2i(width - 3, height - 3);
    glVertex2i(width - 3, height - 3 - TOP_ROW_SIZE);
    glVertex2i(3 + LEFT_COL_SIZE, height - 3 - TOP_ROW_SIZE);

    // Commands display (buttom left)
    glVertex2i(3, height - 3 - TOP_ROW_SIZE - 3);
    glVertex2i(LEFT_COL_SIZE, height - 3 - TOP_ROW_SIZE - 3);
    glVertex2i(LEFT_COL_SIZE, 3);
    glVertex2i(3, 3);
  glEnd();

  // Draw 3D image border
  glBegin(GL_LINES);
    glColor3f(0.5,0.5,0.5);
    glVertex2i(3 + LEFT_COL_SIZE, height - 3 - TOP_ROW_SIZE - 3);
    glVertex2i(width - 3, height - 3 - TOP_ROW_SIZE - 3);

    glVertex2i(width - 3, height - 3 - TOP_ROW_SIZE - 3);
    glVertex2i(width - 3, 3);

    glVertex2i(width - 3, 3);
    glVertex2i(3 + LEFT_COL_SIZE, 3);

    glVertex2i(3 + LEFT_COL_SIZE, 3);
    glVertex2i(3 + LEFT_COL_SIZE, height - 3 - TOP_ROW_SIZE - 3);
  glEnd();

  // Draw all texts
  // fixed texts first 
  xStep     = 3 + 7;
  xDetails  = 3 + LEFT_COL_SIZE + 3 + 7;
  xCommands = 3 + 7;

  yStep = height - 3 - 13;
  yDetails = height - 3 - 13;
  glColor3f(0.0,1.0,1.0);
  drawText(xDetails, yDetails, "DETAILS");

  yCommands = height - 3 - TOP_ROW_SIZE - 3 - 13;
  drawText(xCommands, yCommands, "COMMANDS");

  // ENTER command
  if (currentStep < 6) {
    yCommands -= 25;
    glColor3f(1.0,1.0,1.0);
    drawText(xCommands, yCommands, "ENTER");
    yCommands -= 15;
    glColor3f(0.7,0.9,0.8);
    drawText(xCommands, yCommands, "Go to next step");
  }


  // BACKSPACE command
  yCommands -= 25;
  glColor3f(1.0,1.0,1.0);
  drawText(xCommands, yCommands, "BACKSPACE");
  yCommands -= 15;
  glColor3f(0.7,0.9,0.8);
  drawText(xCommands, yCommands, "Go to previous step");

  // ESC command
  yCommands -= 25;
  glColor3f(1.0,1.0,1.0);
  drawText(xCommands, yCommands, "ESC");
  yCommands -= 15;
  glColor3f(0.7,0.9,0.8);
  drawText(xCommands, yCommands, "Exit program");
  yCommands -= 25;
  yCommands -= 25;
  

  // dynamic (step dependent) texts
  switch (currentStep)
  {
    case 0:
      // Step title
      glColor3f(0.0,1.0,1.0);
      drawText(xStep, yStep, "SELECTED FILE");
      yStep -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xStep, yStep, lwoFileName);

      // Details
      yDetails -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xDetails, yDetails, "Choose the model to be rendered. The sphere model contains near 5,000 triangles (press 's' to choose).");
      yDetails -= 13;
      drawText(xDetails, yDetails, "The venus model conains almost 100,000 triangles (press 'v' to choose).");
      yDetails -= 13;
      yDetails -= 13;
      drawText(xDetails, yDetails, "This application will first render the mesh using the OpenGL pipeline and then convert the mesh to splats ");
      yDetails -= 13;
      drawText(xDetails, yDetails, "to apply a splatting rendering algorithm.");

      //Commands
      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "S");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Choose SPHERE file");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "V");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Choose VENUS file");

      if (inputFile) {
        yCommands -= 25;
        glColor3f(1.0,1.0,1.0);
        drawText(xCommands, yCommands, "P");
        yCommands -= 15;
        glColor3f(0.7,0.9,0.8);
        drawText(xCommands, yCommands, "Choose file passed");
        yCommands -= 13;
        drawText(xCommands, yCommands, "as parameter");
      }
    break;

    case 1:
      // Step title
      glColor3f(0.0,1.0,1.0);
      drawText(xStep, yStep, "STEP 1");

      yStep -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xStep, yStep, "Obtain the");
      yStep -= 13;
      drawText(xStep, yStep, "original mesh.");

      // Details
      yDetails -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xDetails, yDetails, "Read a lwo file (LightWave format) containing the input mesh.");
      yDetails -= 13;
      yDetails -= 13;
      sprintf (output, "Showing below the model, using OpenGL pipeline to render the mesh. Renderig time %.4fs (%.2f fps).", renderingTime, 1/renderingTime);
      drawText(xDetails, yDetails, output);

      //Commands
      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "LEFT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate left");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "RIGHT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate right");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "UP ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom in");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "DOWN ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom out");
    break;

    case 2:
      // Step title
      glColor3f(0.0,1.0,1.0);
      drawText(xStep, yStep, "STEP 2a");

      yStep -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xStep, yStep, "CREATE SPLATS:");
      yStep -= 13;
      yStep -= 13;
      drawText(xStep, yStep, "Define splats");
      yStep -= 13;
      drawText(xStep, yStep, "positions.");

      // Details
      yDetails -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xDetails, yDetails, "Create the splats using the vertex positions of original mesh as center for generated splats.");
      yDetails -= 13;
      yDetails -= 13;
      drawText(xDetails, yDetails, "Showing below the splats positions (as points).");

      //Commands
      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "LEFT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate left");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "RIGHT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate right");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "UP ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom in");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "DOWN ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom out");
    break;

    case 3:
      // Step title
      glColor3f(0.0,1.0,1.0);
      drawText(xStep, yStep, "STEP 2b");

      yStep -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xStep, yStep, "CREATE SPLATS:");
      yStep -= 13;
      yStep -= 13;
      drawText(xStep, yStep, "Define splats");
      yStep -= 13;
      drawText(xStep, yStep, "normals.");

      // Details
      yDetails -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xDetails, yDetails, "Define splats normals as the original mesh vertex normals.");
      yDetails -= 13;
      yDetails -= 13;
      drawText(xDetails, yDetails, "Showing below the generated splats positions with normals.");

      //Commands
      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "LEFT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate left");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "RIGHT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate right");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "UP ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom in");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "DOWN ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom out");
    break;

    case 4:
      // Step title
      glColor3f(0.0,1.0,1.0);
      drawText(xStep, yStep, "STEP 2c");

      yStep -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xStep, yStep, "CREATE SPLATS:");
      yStep -= 13;
      yStep -= 13;
      drawText(xStep, yStep, "Define splats");
      yStep -= 13;
      drawText(xStep, yStep, "sizes.");

      // Details
      yDetails -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xDetails, yDetails, "Calculate each splat size evaluating the maximum distance to it's neighbors.");
      yDetails -= 13;
      yDetails -= 13;
      drawText(xDetails, yDetails, "Showing below, just to illustrate, the generated splats as solid disks with a smaller size ");
      yDetails -= 13;
      drawText(xDetails, yDetails, "(1/3 of calculated splat size). Next step will show the real size.");

      //Commands
      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "LEFT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate left");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "RIGHT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate right");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "UP ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom in");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "DOWN ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom out");
    break;

    case 5:
      // Step title
      glColor3f(0.0,1.0,1.0);
      drawText(xStep, yStep, "STEP 2c");

      yStep -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xStep, yStep, "CREATE SPLATS:");
      yStep -= 13;
      yStep -= 13;
      drawText(xStep, yStep, "Define splats");
      yStep -= 13;
      drawText(xStep, yStep, "sizes.");

      // Details
      yDetails -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xDetails, yDetails, "Calculate each splat size evaluating the maximum distance to it's neighbors.");
      yDetails -= 13;
      yDetails -= 13;
      drawText(xDetails, yDetails, "Showing below the generated splats as circles, with the calculated size.");

      //Commands
      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "LEFT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate left");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "RIGHT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate right");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "UP ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom in");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "DOWN ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom out");
    break;

    case 6:
      // Step title
      glColor3f(0.0,1.0,1.0);
      drawText(xStep, yStep, "STEP 3");

      yStep -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xStep, yStep, "Render model using");
      yStep -= 13;
      drawText(xStep, yStep, "splatting algorithm");
      yStep -= 13;
      yStep -= 13;
      drawText(xStep, yStep, "(last step)");

      // Details
      yDetails -= 20;
      glColor3f(0.5,0.8,0.7);
      drawText(xDetails, yDetails, "Generate the final image from obtained splats, using a approximation for a resampling filter,");
      yDetails -= 13;
      drawText(xDetails, yDetails, "that represents the convolution of a prefilter and a reconstruction filter, both Gaussians.");
      yDetails -= 13;
      yDetails -= 13;
      sprintf (output, "Showing below the final image rendered using splatting. Renderig time %.4fs (%.2f fps).", renderingTime, 1/renderingTime);
      drawText(xDetails, yDetails, output);
      yDetails -= 13;
      drawText(xDetails, yDetails, "Using different camera settings.");

      //Commands
      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "LEFT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate left");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "RIGHT ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Rotate right");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "UP ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom in");

      yCommands -= 25;
      glColor3f(1.0,1.0,1.0);
      drawText(xCommands, yCommands, "DOWN ARROW");
      yCommands -= 15;
      glColor3f(0.7,0.9,0.8);
      drawText(xCommands, yCommands, "Zoom out");
    break;

    default:
    break;
  };



  glutSwapBuffers();

  glutSetCursor(GLUT_CURSOR_INHERIT);
} /* REDISPLAY callback  */


void resizeABuffer(int width, int height, int subpixels)
{
	if( width < 1 || width > maxResolution || height < 1 || height > maxResolution || subpixels < 1 || subpixels > 64 ) return;
	if( abuffer )
	{
		if( width != abuffer->getWidth() || height != abuffer->getHeight() || subpixels != abuffer->getSubpixels() )
		{
			delete abuffer;
			delete buffer;
		}
		else return;
	}
	if( subpixels != abuffer->getSubpixels() ) makeJitter(subpixels);
	abuffer = new ABuffer(width, height, subpixels );
	buffer = new Color[width * height];
}


void reshape_cb(int new_width, int new_height)  /**< Callback for canvas resize */
{
  glutReshapeWindow(width, height);

  glutPostRedisplay(); /* force redisplay */
} 

void keyboard_cb(unsigned char key, int x, int y) {
  if ((currentStep < 6) && (key == 13)) {
    executeNextStep();
    glutPostRedisplay();
  }

  if (key == 8) {
    executePreviousStep();
    glutPostRedisplay();
  }

  if (key == 27)
    exit(0);

  if (currentStep == 0) {
    if ((key == 's') || (key == 'S'))
      strcpy(lwoFileName, "sphere.lwo");

    if ((key == 'v') || (key == 'V'))
      strcpy(lwoFileName, "venus.lwo");

    if (inputFile && ((key == 'p') || (key == 'P')))
      strcpy(lwoFileName, inputFileName);

    glutPostRedisplay();
  }

}


void special_cb(int key, int x, int y)  /**< Callback for special keys handling */
{
  if ((currentStep >= 1) && (currentStep <= 6)) {
    switch (key) {
      case GLUT_KEY_LEFT: 
        yRotation    -= rotationStep;
        yRotationAcc -= rotationStep;
      break;

      case GLUT_KEY_RIGHT: 
        yRotation    += rotationStep;
        yRotationAcc += rotationStep;
      break;

      case GLUT_KEY_UP: 
        if ((zEye - zEyeStep) > 0)
          zEye -= zEyeStep;
      break;

      case GLUT_KEY_DOWN: 
        if ((zEye + zEyeStep) < 20*zEye)
          zEye += zEyeStep;
      break;

    }
    
    glutPostRedisplay(); /* force window redraw */
  }
  
}


int main(int argc, char **argv)
{
  int i;

  sizex = widthImage3D;
  sizey = heightImage3D;

  abuffer = new ABuffer(sizex,sizey,subpixels);
	buffer = new Color[sizex * sizey];
	makeJitter(subpixels);

	int c = 1;

  /* Initializing GLUT */
  glutInit(&argc, argv);                     
  glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE);
  glutInitWindowSize(width, height);         
  glutCreateWindow("VCRA: Point Based Rendering Steps");      
  
  /* Registering callbacks */
  glutDisplayFunc(display_cb);      
  glutReshapeFunc(reshape_cb);
  glutKeyboardFunc(keyboard_cb);
  glutSpecialFunc(special_cb);      
  

  if (argc > 1) {
    strcpy(lwoFileName , (char *)argv[1]);
    strcpy(inputFileName, lwoFileName);
    inputFile = true;
  } else
    strcpy(lwoFileName , "sphere.lwo");


  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);

  /* GLUT main loop */
  glutMainLoop();             /* wait for user events*/

  return 0;
}
