/* changed 07/11/00 - get correct text width */
/*
 GNU Maverik - a system for managing display and interaction in 
               Virtual Environment applications.
 Copyright (C) 1999-2000 Advanced Interfaces Group

 This program 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 2
 of the License, or (at your option) any later version.

 This program 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 for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA


 The authors can be contacted via:
 www   - http://aig.cs.man.ac.uk
 email - maverik@aig.cs.man.ac.uk
 mail  - Advanced Interfaces Group, Room 2.94, Computer Science Building, 
         University of Manchester, Manchester, M13 9PL, UK
*/


#include "mavlib_kernel.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

MAV_list *mavlib_frame0_list;
MAV_list *mavlib_frame1_list;
MAV_list *mavlib_frame2_list;
MAV_list *mavlib_frame3_list;
MAV_list *mavlib_frame4_list;
MAV_list *mavlib_duringFrame_addList;
MAV_list *mavlib_duringFrame_rmvList;
int mavlib_doingFrame0=MAV_FALSE;
int mavlib_doingFrame1=MAV_FALSE;
int mavlib_doingFrame2=MAV_FALSE;
int mavlib_doingFrame3=MAV_FALSE;
int mavlib_doingFrame4=MAV_FALSE;
int mav_firstFrame=MAV_TRUE;
int mav_frameCount=0;
int mav_opt_finish=MAV_FALSE;
int mav_opt_flush=MAV_FALSE;

float mavlib_culTime=0;
int mavlib_culFrame=0;
MAV_timer mavlib_frameTimer;


/* Routines to set and remove frame0 (navigator) functions */

void mav_frameFn0Add(MAV_frameFn fn)
{
  if (mavlib_doingFrame0)
  {
    /* Cant add during frame0 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_addList, (void *) fn);
  }
  else
  {
    if (!mav_listItemContains(mavlib_frame0_list, (void *) fn)) mav_listItemAdd(mavlib_frame0_list, (void *) fn);
  }
}

void mav_frameFn0Rmv(MAV_frameFn fn) 
{
  if (mavlib_doingFrame0)
  {
    /* Cant remove during frame0 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_rmvList, (void *) fn);
  }
  else
  {
    mav_listItemRmv(mavlib_frame0_list, (void *) fn);
  }
}



/* Routines to set and remove frame1 functions */

void mav_frameFn1Add(MAV_frameFn fn)
{
  if (mavlib_doingFrame1)
  {
    /* Cant add during frame1 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_addList, (void *) fn);
  }
  else
  {
    if (!mav_listItemContains(mavlib_frame1_list, (void *) fn)) mav_listItemAdd(mavlib_frame1_list, (void *) fn);
  }
}

void mav_frameFn1Rmv(MAV_frameFn fn) 
{
  if (mavlib_doingFrame1)
  {
    /* Cant remove during frame1 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_rmvList, (void *) fn);
  }
  else
  {
    mav_listItemRmv(mavlib_frame1_list, (void *) fn);
  }
}



/* As above but for frame2 */

void mav_frameFn2Add(MAV_frameFn fn)
{
  if (mavlib_doingFrame2)
  {
    /* Cant add during frame2 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_addList, (void *) fn);
  }
  else
  {
    if (!mav_listItemContains(mavlib_frame2_list, (void *) fn)) mav_listItemAdd(mavlib_frame2_list, (void *) fn);
  }
}

void mav_frameFn2Rmv(MAV_frameFn fn) 
{
  if (mavlib_doingFrame2)
  {
    /* Cant remove during frame2 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_rmvList, (void *) fn);
  }
  else
  {
    mav_listItemRmv(mavlib_frame2_list, (void *) fn);
  }
}



/* As above but for frame3 */

void mav_frameFn3Add(MAV_frameFn fn)
{
  if (mavlib_doingFrame3)
  {
    /* Cant add during frame3 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_addList, (void *) fn);
  }
  else
  {
    if (!mav_listItemContains(mavlib_frame3_list, (void *) fn)) mav_listItemAdd(mavlib_frame3_list, (void *) fn);
  }
}

void mav_frameFn3Rmv(MAV_frameFn fn) 
{
  if (mavlib_doingFrame3)
  {
    /* Cant remove during frame3 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_rmvList, (void *) fn);
  }
  else
  {
    mav_listItemRmv(mavlib_frame3_list, (void *) fn);
  }
}



/* As above but for frame4 */

void mav_frameFn4Add(MAV_frameFn fn)
{
  if (mavlib_doingFrame4)
  {
    /* Cant add during frame4 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_addList, (void *) fn);
  }
  else
  {
    if (!mav_listItemContains(mavlib_frame4_list, (void *) fn)) mav_listItemAdd(mavlib_frame4_list, (void *) fn);
  }
}

void mav_frameFn4Rmv(MAV_frameFn fn) 
{
  if (mavlib_doingFrame4)
  {
    /* Cant remove during frame4 as messes up the list */
    mav_listItemAdd(mavlib_duringFrame_rmvList, (void *) fn);
  }
  else
  {
    mav_listItemRmv(mavlib_frame4_list, (void *) fn);
  }
}



/* Routine to define the start of a frame */

void mav_frameBegin(void) 
{
  MAV_window *w, *orig_win= mav_win_current;
  MAV_matrix view_matrix;
  MAV_viewParams *vp;
  MAV_frameFn fn;
  MAV_vector view_shift;
  float hdist, vdist;

  /* start framerate timer */

  mav_timerStart(&mavlib_frameTimer);

  /* Poll devices */

  mav_devicePoll();

  /* Do frame 0 functions */

  mavlib_doingFrame0= MAV_TRUE;
  mav_listEmpty(mavlib_duringFrame_addList);
  mav_listEmpty(mavlib_duringFrame_rmvList);

  mav_listPointerReset(mavlib_frame0_list);  
  while (mav_listItemNext(mavlib_frame0_list, (void **) &fn)) (*fn)();

  mavlib_doingFrame0= MAV_FALSE;
  mav_listPointerReset(mavlib_duringFrame_addList);
  while (mav_listItemNext(mavlib_duringFrame_addList, (void **) &fn)) mav_frameFn0Add(fn);

  mav_listPointerReset(mavlib_duringFrame_rmvList);  
  while (mav_listItemNext(mavlib_duringFrame_rmvList, (void **) &fn)) mav_frameFn0Rmv(fn);

  /* Do frame 1 functions */

  mavlib_doingFrame1= MAV_TRUE;
  mav_listEmpty(mavlib_duringFrame_addList);
  mav_listEmpty(mavlib_duringFrame_rmvList);

  mav_listPointerReset(mavlib_frame1_list);  
  while (mav_listItemNext(mavlib_frame1_list, (void **) &fn)) (*fn)();

  mavlib_doingFrame1= MAV_FALSE;
  mav_listPointerReset(mavlib_duringFrame_addList);
  while (mav_listItemNext(mavlib_duringFrame_addList, (void **) &fn)) mav_frameFn1Add(fn);

  mav_listPointerReset(mavlib_duringFrame_rmvList);  
  while (mav_listItemNext(mavlib_duringFrame_rmvList, (void **) &fn)) mav_frameFn1Rmv(fn);

  /* Clear buffers and set matrices for each window */

  mav_listPointerReset(mav_win_list);

  while (mav_listItemNext(mav_win_list, (void **) &w)) {
    if (w!=mav_win_current) mav_windowSet(w);

    /* clear colour, depth and, if enabled, accumulation buffers */
    mav_gfxClearCZ();
    if (mav_opt_accumBuf) mav_gfxClearA();
    mav_surfaceParamsUndefine();
    
    /* calculate view matrix orientation */
    vp= mav_win_current->vp;
    
    /* dont reply on view up being exact */
    vp->right= mav_vectorNormalize(mav_vectorCrossProduct(vp->view, vp->up));
    vp->up= mav_vectorNormalize(mav_vectorCrossProduct(vp->right, vp->view));

    /* calc translated view parameters */
    if (vp->mod)
    {
      (*(vp->mod))(w);
    }
    else
    {
      mav_viewParamsFixed(w);
    }

    /* calc stereo view parameters */
    if (w->mod)
    {
      (*(w->mod))(w);
    }
    else
    {
      mav_eyeMono(w);
    }

    /* define and load the orientation part of the matrix */

    view_matrix.mat[0][0]= w->right.x;
    view_matrix.mat[0][1]= w->right.y;
    view_matrix.mat[0][2]= w->right.z;
    view_matrix.mat[0][3]= 0;

    view_matrix.mat[1][0]= w->up.x;
    view_matrix.mat[1][1]= w->up.y;
    view_matrix.mat[1][2]= w->up.z;
    view_matrix.mat[1][3]= 0;

    view_matrix.mat[2][0]= -w->view.x;
    view_matrix.mat[2][1]= -w->view.y;
    view_matrix.mat[2][2]= -w->view.z;
    view_matrix.mat[2][3]= 0;

    view_matrix.mat[3][0]= 0;
    view_matrix.mat[3][1]= 0;
    view_matrix.mat[3][2]= 0;
    view_matrix.mat[3][3]= 1.0;

    mav_gfxMatrixLoad(view_matrix);

    /* account for eye position */ 

    view_shift= w->eye;
    mav_gfxMatrixTranslate(mav_vectorScalar(view_shift, -1.0));

    /* store resulting view and proj.view matrices  */

    w->viewMat= mav_gfxMatrixGet();
    w->pdvMat= mav_matrixMult(w->projMat, w->viewMat);

    /* store the 5 ncp vertices */
    vdist= tan(w->fov*0.5*MAV_PI_OVER_180)*w->ncp;
    hdist= vdist*w->aspect;

    w->ncpv[4]= mav_vectorAdd(w->eye, mav_vectorScalar(w->view, w->ncp));
    w->ncpv[0]= mav_vectorAdd(w->ncpv[4], mav_vectorAdd(mav_vectorScalar(w->right, -hdist), mav_vectorScalar(w->up, -vdist)));
    w->ncpv[1]= mav_vectorAdd(w->ncpv[4], mav_vectorAdd(mav_vectorScalar(w->right, +hdist), mav_vectorScalar(w->up, -vdist)));
    w->ncpv[2]= mav_vectorAdd(w->ncpv[4], mav_vectorAdd(mav_vectorScalar(w->right, +hdist), mav_vectorScalar(w->up, +vdist)));
    w->ncpv[3]= mav_vectorAdd(w->ncpv[4], mav_vectorAdd(mav_vectorScalar(w->right, -hdist), mav_vectorScalar(w->up, +vdist)));

    /* store the 5 fcp vertices */
    vdist= tan(w->fov*0.5*MAV_PI_OVER_180)*w->fcp;
    hdist= vdist*w->aspect;

    w->fcpv[4]= mav_vectorAdd(w->eye, mav_vectorScalar(w->view, w->fcp));
    w->fcpv[0]= mav_vectorAdd(w->fcpv[4], mav_vectorAdd(mav_vectorScalar(w->right, -hdist), mav_vectorScalar(w->up, -vdist)));
    w->fcpv[1]= mav_vectorAdd(w->fcpv[4], mav_vectorAdd(mav_vectorScalar(w->right, +hdist), mav_vectorScalar(w->up, -vdist)));
    w->fcpv[2]= mav_vectorAdd(w->fcpv[4], mav_vectorAdd(mav_vectorScalar(w->right, +hdist), mav_vectorScalar(w->up, +vdist)));
    w->fcpv[3]= mav_vectorAdd(w->fcpv[4], mav_vectorAdd(mav_vectorScalar(w->right, -hdist), mav_vectorScalar(w->up, +vdist)));
  }

  if (mav_win_current!=orig_win) mav_windowSet(orig_win);  

  /* Calc world pos of devices */

  mav_deviceCalc();

  /* Update positions for absolute lights */

  mavlib_lightPosFix();

  /* Do frame 2 functions */

  mavlib_doingFrame2= MAV_TRUE;
  mav_listEmpty(mavlib_duringFrame_addList);
  mav_listEmpty(mavlib_duringFrame_rmvList);

  mav_listPointerReset(mavlib_frame2_list);
  while (mav_listItemNext(mavlib_frame2_list, (void **) &fn)) (*fn)();

  mavlib_doingFrame2= MAV_FALSE;
  mav_listPointerReset(mavlib_duringFrame_addList);
  while (mav_listItemNext(mavlib_duringFrame_addList, (void **) &fn)) mav_frameFn2Add(fn);

  mav_listPointerReset(mavlib_duringFrame_rmvList);  
  while (mav_listItemNext(mavlib_duringFrame_rmvList, (void **) &fn)) mav_frameFn2Rmv(fn);
}



/* Routine to define the end of the frame */

void mav_frameEnd(void)
{
  MAV_window *w, *orig_win= mav_win_current;
  MAV_frameFn fn;

  /* Do frame 3 functions */

  mavlib_doingFrame3= MAV_TRUE;
  mav_listEmpty(mavlib_duringFrame_addList);
  mav_listEmpty(mavlib_duringFrame_rmvList);

  mav_listPointerReset(mavlib_frame3_list);  
  while (mav_listItemNext(mavlib_frame3_list, (void **) &fn)) (*fn)();

  mavlib_doingFrame3= MAV_FALSE;
  mav_listPointerReset(mavlib_duringFrame_addList);
  while (mav_listItemNext(mavlib_duringFrame_addList, (void **) &fn)) mav_frameFn3Add(fn);

  mav_listPointerReset(mavlib_duringFrame_rmvList);  
  while (mav_listItemNext(mavlib_duringFrame_rmvList, (void **) &fn)) mav_frameFn3Rmv(fn);

  /* swap the buffers for each window */

  mav_listPointerReset(mav_win_list);

  while (mav_listItemNext(mav_win_list, (void **) &w)) {
    if (w!=mav_win_current) mav_windowSet(w);    
    if (mav_opt_finish) mav_gfxFinish();
    if (mav_opt_flush) mav_gfxFlush();
    mav_gfxWindowBuffersSwap();
  }  

  if (mav_win_current!=orig_win) mav_windowSet(orig_win);

  /* calculate instant frame rate */
  
  mav_timerStop(&mavlib_frameTimer);  
  mav_fps= 1.0/mavlib_frameTimer.wall;

  /* calculate averaged frame rate */

  mavlib_culTime+=mavlib_frameTimer.wall;
  mavlib_culFrame++;

  if (mavlib_culTime>1.0) {
    mav_fps_avg= mavlib_culFrame/mavlib_culTime;
    mavlib_culTime=0.0;
    mavlib_culFrame=0;
  }

  mav_firstFrame=MAV_FALSE;

  /* Do frame 4 functions */

  mavlib_doingFrame4= MAV_TRUE;
  mav_listEmpty(mavlib_duringFrame_addList);
  mav_listEmpty(mavlib_duringFrame_rmvList);

  mav_listPointerReset(mavlib_frame4_list);  
  while (mav_listItemNext(mavlib_frame4_list, (void **) &fn)) (*fn)();

  mavlib_doingFrame4= MAV_FALSE;
  mav_listPointerReset(mavlib_duringFrame_addList);
  while (mav_listItemNext(mavlib_duringFrame_addList, (void **) &fn)) mav_frameFn4Add(fn);

  mav_listPointerReset(mavlib_duringFrame_rmvList);  
  while (mav_listItemNext(mavlib_duringFrame_rmvList, (void **) &fn)) mav_frameFn4Rmv(fn);

  /* Increment frame count */

  mav_frameCount++;
}



/* Routine to apply a null translation to the eye parameters */

void mav_viewParamsFixed(MAV_window *w)
{
  /* simply copy provided data into translated part */
  
  w->vp->trans_view= w->vp->view;
  w->vp->trans_up= w->vp->up;
  w->vp->trans_right= w->vp->right;
  w->vp->trans_eye= w->vp->eye;
}

#ifdef WIN32
int mav_gfxStringLength (int, char *, int);
#endif

/* Routine to calculate the length of a string */

int mav_stringLength(MAV_window *w, char *s, int font)
{
  int i, tot=0;

#ifdef WIN32
/* note - adding the individual widths together doesn't work */
/* because text characters overlap */
  tot= mav_gfxStringLength (w->id, s, font);
#else
  if (w->palette->fontlist[font].defined) 
  {
    for (i=0; i<strlen(s); i++) tot+= w->palette->fontlist[font].width[(int)s[i]];
  }
  else
  {
    if (mav_opt_output) fprintf(stderr, "Warning: font %i not defined\n", font);
  }
#endif

  return tot;
}



/* Routines to display a string on screen */

int mavlib_justify=0;

void mavlib_displayStringToAll(char *s, int col, int font, float x, float y)
{
  MAV_window *w;

  mav_listPointerReset(mav_win_list);
  
  while (mav_listItemNext(mav_win_list, (void **) &w)) mav_stringDisplay(w,s,col,font,x,y);
}

void mav_stringDisplay(MAV_window *w, char *s, int col, int font, float x, float y)
{
  MAV_window *orig= mav_win_current;
  MAV_surfaceParams sp;
  int c=0;

  if (w==mav_win_all) 
  {
    mavlib_displayStringToAll(s, col, font, x, y);
  }
  else
  {
    if (mav_win_current!=w) mav_windowSet(w);

    /* check font has been defined */
    if (!mav_win_current->palette->fontlist[font].defined) {
      if (mav_opt_output) fprintf(stderr, "Warning: font %i not defined\n", font);
    }

    /* set the correct colouring */
    sp.mode= MAV_COLOUR;
    sp.colour= col;
    sp.material=0;
    sp.texture=0;
    mav_surfaceParamsUse(&sp);

    /* use a projection matrix such that we talk in term of pixels */
    mav_gfxMatrixMode(MAV_PROJECTION);
    mav_gfxMatrixLoad(MAV_ID_MATRIX);    
    mav_gfxOrthogonalSet(0, mav_win_current->width, 0, mav_win_current->height, -1, 1);
    
    /* use an identity view matrix */
    mav_gfxMatrixMode(MAV_MODELVIEW);
    mav_gfxMatrixPush();
    mav_gfxMatrixLoad(MAV_ID_MATRIX);

    /* convert position (in range [-1:1]) into pixel values */
    x= (x+1)/2.0 * mav_win_current->width;
    y= (y+1)/2.0 * mav_win_current->height;

    /* account for justification */
    switch (mavlib_justify) {
    case 1: /* Center justify */
      x-= mav_stringLength(mav_win_current, s, font)/2;
      break;

    case 2: /* Right justify */
      x-= mav_stringLength(mav_win_current, s, font);
      break;
    }

    /* if position is invalid (x<0), ignore a chacter and retry */
#ifdef WIN32
/* don't try culling as character widths are invalid */
    mav_gfxRasterPos2DSet (x,y);
    mav_gfxWindowStringDisplay (s, font);
#else
    while (x<0 && c<strlen(s)) {
      x+= mav_win_current->palette->fontlist[font].width[(int)s[c]];
      c++;
    }

    /* set raster position and display text (unless its all culled out) */
    if (x>=0) {
      mav_gfxRasterPos2DSet(x, y);
      mav_gfxWindowStringDisplay(&s[c], font);
    }
#endif

    /* restore original projection and view matrices */
    mav_gfxMatrixMode(MAV_PROJECTION);
    mav_gfxMatrixLoad(mav_win_current->projMat);
    
    mav_gfxMatrixMode(MAV_MODELVIEW);
    mav_gfxMatrixPop();

    if (mav_win_current!=orig) mav_windowSet(orig);
  }
}



/* Routines to display justified text on screen */

void mav_stringDisplayLeft(MAV_window *w, char *s, int col, int font, float x, float y)
{
  mav_stringDisplay(w, s, col, font, x, y);
}

void mav_stringDisplayCentre(MAV_window *w, char *s, int col, int font, float x, float y)
{
  mavlib_justify= 1;
  mav_stringDisplay(w, s, col, font, x, y);
  mavlib_justify= 0;
}

void mav_stringDisplayRight(MAV_window *w, char *s, int col, int font, float x, float y)
{
  mavlib_justify= 2;
  mav_stringDisplay(w, s, col, font, x, y);
  mavlib_justify= 0;
}



/* Routine to print a set of view parameters */

void mav_viewParamsPrint(char *s, MAV_viewParams f)
{
  printf("%s", s);
  mav_vectorPrint("eye ", f.eye);
  mav_vectorPrint("view ", f.view);
  mav_vectorPrint("up ", f.up);
}



/* Routine to display in one window another window's frustum */

void mavlib_frustumDisplayToAll(MAV_window *f)
{
  MAV_window *w;

  mav_listPointerReset(mav_win_list);
  
  while (mav_listItemNext(mav_win_list, (void **) &w)) mav_frustumDisplay(w,f);
}

void mav_frustumDisplay(MAV_window *w, MAV_window *f)
{
  MAV_surfaceParams sp;
  MAV_window *orig=mav_win_current;
  int i;
 
  if (w==mav_win_all) 
  {
    mavlib_frustumDisplayToAll(f);
  }
  else
  {
    if (w!=orig) mav_windowSet(w);
  
    sp.mode= MAV_COLOUR;
    sp.colour= MAV_COLOUR_BLACK;
    sp.material= 0;
    sp.texture= 0;
    mav_surfaceParamsUse(&sp);
    
    mav_gfxLineClosedBegin();
    mav_gfxVertex(f->ncpv[0]);
    mav_gfxVertex(f->ncpv[1]);
    mav_gfxVertex(f->ncpv[2]);
    mav_gfxVertex(f->ncpv[3]);
    mav_gfxLineClosedEnd();
    
    mav_gfxLineClosedBegin();
    mav_gfxVertex(f->fcpv[0]);
    mav_gfxVertex(f->fcpv[1]);
    mav_gfxVertex(f->fcpv[2]);
    mav_gfxVertex(f->fcpv[3]);
    mav_gfxLineClosedEnd();
    
    for (i=0; i<5; i++) {
      mav_gfxLineClosedBegin();
      mav_gfxVertex(f->ncpv[i]);
      mav_gfxVertex(f->fcpv[i]);
      mav_gfxLineClosedEnd();
    }
    
    if (w!=orig) mav_windowSet(orig);
  }
}
