/****************************************************************************
 * file:      vb-02-viewport.c
 * status:    20-sep-2005, pfk.
 *            03-oct-2007, pfk: correct display after reshape.
 * function:  Example: Interaction in viewport.
 *
 *            OpenGL:
 *            A viewport maps the drawing area on (part of) the output area.
 *            Cursor positions are given in window coordinates for the whole
 *            output area, so you must check if a position is in the area
 *            indicated by the viewport and you must convert to the proper
 *            coordinates in the drawing area (user coordinates). 
 *
 *            Program:
 *            A square is drawn and mapped three times using different 
 *            viewports. The square is slightly smaller than the drawing area.
 *            The last viewport is used to draw lines from low left corner 
 *            to the cursor position indicated by a left mouse button click
 *            within that last viewport. 
 ****************************************************************************/

#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// Size and position of the OpenGL output window.
#define WINSIZE_X    600   // size in X in pixels
#define WINSIZE_Y    400   // size in Y in pixels
#define WINPOS_X     100   // position top
#define WINPOS_Y     100   //              left corner

// Boundaries of user coordinates in OpenGL output window.
#define XMIN       -10.0
#define XMAX        10.0
#define YMIN       -10.0
#define YMAX        10.0

#define OFFSET       1.0    /* to draw slightly within the drawing area */

#define FRAME_TITLE  "Sample program: Viewport"

static GLsizei frameWidth,  /* size of window in pixels */
               frameHeight;

static int   vpXLo, vpXHi,  /* viewport boundaries in window coord. */
             vpYLo, vpYHi;

static float g_shift_x,     /* shift due to window resize */
             g_shift_y;     /* used to adjust viewports   */


/****************************************************************************
 * Initialise.
 ****************************************************************************/
void progInit (void) 
{
  /* Set background (black). */
  glClearColor (0.0, 0.0, 0.0, 0.0);

  /* Instructions for user. */
  printf
    ("\n%s\nType 'e', 'q', 's' or ESC in the output window to stop.\n",
     FRAME_TITLE);

  frameWidth  = WINSIZE_X;
  frameHeight = WINSIZE_Y;
}


/****************************************************************************
 * Print text variable "s" starting at position (x,y).
 ****************************************************************************/
void printText (float x, float y, char s[])
{
  int  i,       // loop variabele
       length;  // number of characters, length of "s"
  char c;       // single character

  glRasterPos2f (x, y);

  length = strlen (s);
  for(i=0; i<length; i++)
  { c = s[i];
    glutBitmapCharacter (GLUT_BITMAP_8_BY_13, c);
  }
}


/****************************************************************************
 * Draw line from (x1,y1) to (x2,y2) in user coordinates.
 ****************************************************************************/
void drawLine (float x1, float y1, float x2, float y2)
{
  glBegin (GL_LINES);
    glVertex2f (x1, y1);
    glVertex2f (x2, y2);
  glEnd ();
}


/****************************************************************************
 * Draw objects.
 ****************************************************************************/
void drawObjects (void)
{
  glLineWidth (4.0);
  drawLine (XMIN+OFFSET, YMIN+OFFSET,
            XMAX-OFFSET, YMIN+OFFSET);
  drawLine (XMAX-OFFSET, YMIN+OFFSET,
            XMAX-OFFSET, YMAX-OFFSET);
  drawLine (XMAX-OFFSET, YMAX-OFFSET,
            XMIN+OFFSET, YMAX-OFFSET);
  drawLine (XMIN+OFFSET, YMAX-OFFSET,
            XMIN+OFFSET, YMIN+OFFSET);
  glLineWidth (1.0);

  printText (XMIN+2.0*OFFSET, YMAX-2.0*OFFSET, "Colour");
}


/****************************************************************************
 * Display function.
 ****************************************************************************/
void progDisplay (void)
{
  glClear (GL_COLOR_BUFFER_BIT);

  /* setup for viewport of full window */
  vpXLo = 0;
  vpXHi = WINSIZE_X;
  vpYLo = 0;
  vpYHi = WINSIZE_Y;
  glViewport (vpXLo, vpYLo, vpXHi-vpXLo, vpYHi-vpYLo);  /* full window */
  glColor3f (1.0, 1.0, 1.0);
  printText (XMIN+0.5, YMIN+0.4,
    "Type 'e', 'q', 's' or ESC in this output window to stop.");

  /* setup for viewport of smaller window:
   * after reshape, the viewport is adjusted to have the original size
   * by using g_shift_x and g_shift_y */
  vpXLo = 0;
  vpXHi = WINSIZE_X/2;
  vpYLo = 0;
  vpYHi = WINSIZE_Y;
  glViewport (vpXLo, vpYLo,
              vpXHi-vpXLo + 0.5*WINSIZE_X*g_shift_x,
              vpYHi-vpYLo + 1.0*WINSIZE_Y*g_shift_y);   /* left half */

  glColor3f (1.0, 0.0, 0.0);
  drawObjects ();

  vpXLo = WINSIZE_X/2;
  vpXHi = WINSIZE_X;
  vpYLo = 0;
  vpYHi = WINSIZE_Y;
  glViewport (vpXLo, vpYLo,
              vpXHi-vpXLo + 0.5*WINSIZE_X*g_shift_x,
              vpYHi-vpYLo + 1.0*WINSIZE_Y*g_shift_y);   /* right half */

  glColor3f (0.0, 1.0, 0.0);
  drawObjects ();

  vpXLo = WINSIZE_X * 0.25;
  vpXHi = WINSIZE_X * 0.75;
  vpYLo = WINSIZE_Y * 0.25;
  vpYHi = WINSIZE_Y * 0.75;
  //printf ("shift = %10.3f, %10.3f\n", g_shift_x, g_shift_y);
  glViewport (vpXLo, vpYLo,
              vpXHi-vpXLo + 0.5*WINSIZE_X*g_shift_x,
              vpYHi-vpYLo + 0.5*WINSIZE_Y*g_shift_y);   /* central */

  glColor3f (0.0, 1.0, 1.0);
  drawObjects ();

  glFlush ();
}


/****************************************************************************
 * Processing of change in size of output window:
 *   w and h are the new width and height of the output window in
 *      window coordinates,
 *   shift_x and shift_y are the shifts in X- and Y-direction in
 *      user coordinates.
 ****************************************************************************/
void progReshape (GLsizei w, GLsizei h)
{
  float shift_x, shift_y;

  shift_x = ( (float)(w-WINSIZE_X) / (float)WINSIZE_X ) * (XMAX-XMIN);
  shift_y = ( (float)(h-WINSIZE_Y) / (float)WINSIZE_Y ) * (YMAX-YMIN);

  g_shift_x = shift_x/(XMAX-XMIN);
  g_shift_y = shift_y/(YMAX-YMIN);

  glViewport (0, 0, w, h);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();

  /* Original size of objects is preserved. */
  gluOrtho2D (XMIN, XMAX + shift_x, YMIN, YMAX + shift_y);
  g_shift_x = shift_x/(XMAX-XMIN);
  g_shift_y = shift_y/(YMAX-YMIN);

  /* Size of objects is adapted to output window size.
  gluOrtho2D (XMIN, XMAX, YMIN, YMAX);
  g_shift_x = 0.0;
  g_shift_y = 0.0; */

  glFlush ();
}


/****************************************************************************
 * Processing of key interrupt:
 *   program stops if e, q, s or <ESC> is typed with cursor in output area. 
 ****************************************************************************/
void progKeyboard (unsigned char key, int x, int y)
{
  switch (key)
  { case 'e': exit (0);
              break;
    case 'q': exit (0);
              break;
    case 's': exit (0);
              break;
    case 27:  exit (0);
              break;
  }
}

/****************************************************************************
 * Check if position (x,y) is within current viewport
 *   0 = false = not within viewport.
 ****************************************************************************/
int posInViewport (int x, int y)
{
  if ( (x>=vpXLo) && (x<=vpXHi) && (y>=vpYLo) && (y<=vpYHi) )
    return (1);
  else
    return (0);
}


/****************************************************************************
 * Processing of mouse button interrupt: 
 *   "button" indicates which button,
 *   "state" indicates the state of the button (pressed or released),
 *   "x" and "y" is cursor position in window coordinates
 *     (pixel position with origin top left),
 *   "xx" and "yy" is cursor position in user coordinates.
 ****************************************************************************/
void progMouse (int button, int state, int x, int y)
{
  float xx, yy;
  int   xloc, yloc;  /* local copy of cursor position */

  xloc = x;  yloc = y;

  /* Check if left mouse button has been released with cursor
   * in current viewport. */
  if ( (button == GLUT_LEFT_BUTTON) && (state == GLUT_UP) &&
       posInViewport (xloc, yloc) )
  { /* Yes, convert cursor position from window coordinates (x,y)
     * to user coordinates (xx,yy) with respect to viewport
     * (viewport boundaries are vpXLo, vpXHi, vpYLo, vpYHi). */
    xx = ( (float)x - (float)vpXLo ) /
         ( (float)vpXHi - (float)vpXLo ) *
         (XMAX - XMIN) + XMIN;

    yy = ( (float)(frameHeight - y) - (float)vpYLo ) /
         ( (float)vpYHi - (float)vpYLo ) *
         (YMAX - YMIN) + YMIN;

    /* Random line colour. */
    glColor3f (((float)rand()/(float)RAND_MAX),   /* rood  */
               ((float)rand()/(float)RAND_MAX),   /* groen */
               ((float)rand()/(float)RAND_MAX));  /* blauw */

    /* Random line thickness between 1 and 4. */
    glLineWidth (((float)rand()/(float)RAND_MAX+1.0)*4.0);

    glViewport (vpXLo, vpYLo, vpXHi-vpXLo, vpYHi-vpYLo);
    drawLine (XMIN+OFFSET, YMIN+OFFSET, xx, yy);

    /* Back to default values. */
    glColor3f (1.0, 1.0, 1.0);
    glLineWidth (1.0);

    glFlush ();
  }
}


/****************************************************************************
 * Main program.
 ****************************************************************************/
int main (int argc, char** argv)
{
  int windowID;

  /* Initialize OpenGL Utility Toolkit. */
  glutInit (&argc, argv);
  glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
  glutInitWindowSize (WINSIZE_X, WINSIZE_Y); 
  glutInitWindowPosition (WINPOS_X, WINPOS_Y);
  windowID = glutCreateWindow (FRAME_TITLE);

  /* Register event handling functions. */
  glutDisplayFunc (progDisplay); 
  glutReshapeFunc (progReshape);
  glutKeyboardFunc (progKeyboard);
  glutMouseFunc (progMouse);

  progInit ();

  /* Event handling loop. */
  glutMainLoop ();

  return 0;
}
