/****************************************************************************/
/* This file is part of FreeFEM.                                            */
/*                                                                          */
/* FreeFEM is free software: you can redistribute it and/or modify          */
/* it under the terms of the GNU Lesser General Public License as           */
/* published by the Free Software Foundation, either version 3 of           */
/* the License, or (at your option) any later version.                      */
/*                                                                          */
/* FreeFEM 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 Lesser General Public License for more details.                      */
/*                                                                          */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with FreeFEM. If not, see <http://www.gnu.org/licenses/>.          */
/****************************************************************************/
/* SUMMARY : ...                                                            */
/* LICENSE : LGPLv3                                                         */
/* ORG     : LJLL Universite Pierre et Marie Curie, Paris, FRANCE           */
/* AUTHORS : Pascal Frey                                                    */
/* E-MAIL  : pascal.frey@sorbonne-universite.fr                             */

#ifdef __cplusplus
extern "C" {
#endif

#include "medit.h"
#include "extern.h"
#include "sproto.h"

extern GLboolean hasStereo;
extern int *pilmat, ipilmat, refmat, reftype, refitem;
extern short schw, schh;
extern ubyte quiet, fullscreen, tiling, stereoMode;

/* return current active scene */
int currentScene( ) {
  int k, idw;

  idw = glutGetWindow( );

  for (k = 0; k < MAX_SCENE; k++) {
    if (cv.scene[k] && idw == cv.scene[k]->idwin) return (k);
  }

  return (0);
}

/* check for OpenGL error */
void checkErrors(void) {
  GLenum error;

  while ((error = glGetError( )) != GL_NO_ERROR) {
    fprintf(stderr, "  ## ERROR: %d: %s\n", (int)error, (char *)gluErrorString(error));
    exit(1);
  }
}

void farclip(GLboolean b) {
  pScene sc;
  pPersp p;
  pCamera c;
  float look[3], ratio, units;
  int idw = currentScene( );
  static GLfloat up[3] = {0.0, 1.0, 0.0};

  /* default */
  sc = cv.scene[idw];
  p = sc->persp;
  c = sc->camera;
  ratio = (GLfloat)sc->par.xs / sc->par.ys;

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity( );

  switch (p->pmode) {
    case ORTHO:
      glOrtho(-1., 1., -1., 0.1, 0.01, 0.01);
      break;

    case PERSPECTIVE:
      if (b)
        gluPerspective(p->fovy, ratio, sc->dmax, 4.0 * sc->dmax);
      else
        gluPerspective(p->fovy, ratio, 0.01, 10000.0 * sc->dmax);

      units = 1.e-02;
      glPolygonOffset(1.0, units);
      break;

    case CAMERA:
      gluPerspective(p->fovy, ratio, 0.001 * sc->dmax, 4.0 * sc->dmax);
      look[0] = c->eye[0] + 0.001 * sc->dmax * c->speed[0];
      look[1] = c->eye[1] + 0.001 * sc->dmax * c->speed[1];
      look[2] = c->eye[2] + 0.001 * sc->dmax * c->speed[2];
      gluLookAt(c->eye[0], c->eye[1], c->eye[2], look[0], look[1], look[2], up[0], up[1], up[2]);
      break;
  }

  /* zoom transformation */
  if (p->rubber == 2) {
    glPushMatrix( );
    glLoadIdentity( );
    glRotatef(-p->gamma, 1., 0., 0.);
    glRotatef(p->alpha, 0., 1., 0.);
    glMultMatrixf(p->matrix);
    glGetFloatv(GL_PROJECTION_MATRIX, p->matrix);
    glPopMatrix( );
    p->rubber = 0;
  }

  /* apply transformation */
  glMultMatrixf(p->matrix);

  glMatrixMode(GL_MODELVIEW);
}

void reshapeScene(int width, int height) {
  pScene sc;

  if (ddebug) printf("reshape scene\n");

  sc = cv.scene[currentScene( )];
  sc->par.xs = width;
  sc->par.ys = height;

  glViewport(0, 0, width, height);
  farclip(GL_TRUE);
}

static void drawList(pScene sc, int clip, int map) {
  pMesh mesh = cv.mesh[sc->idmesh];
  ubyte elev = sc->mode & S_ALTITUDE;

  if (ddebug) printf("drawList %p %d %d\n", sc, clip, map);

  if (mesh->dim == 2 && !elev) glDisable(GL_DEPTH_TEST);

  glLineWidth(1.0);
  if (clip) {
    if (map) {
      if (sc->cmlist[LTets]) glCallList(sc->cmlist[LTets]);

      if (sc->cmlist[LHexa]) glCallList(sc->cmlist[LHexa]);
    } else {
      if (sc->clist[LTets]) glCallList(sc->clist[LTets]);

      if (sc->clist[LHexa]) glCallList(sc->clist[LHexa]);
    }
  } else if (map) {
    if (mesh->nt + mesh->nq) {
      if (sc->mlist[LTria]) glCallList(sc->mlist[LTria]);

      if (sc->mlist[LQuad]) glCallList(sc->mlist[LQuad]);
    } else {
      if (sc->mlist[LTets]) glCallList(sc->mlist[LTets]);

      if (sc->mlist[LHexa]) glCallList(sc->mlist[LHexa]);
    }
  } else {
    if (mesh->nt + mesh->nq) {
      if (sc->dlist[LTria]) glCallList(sc->dlist[LTria]);

      if (sc->dlist[LQuad]) glCallList(sc->dlist[LQuad]);
    } else {
      if (sc->dlist[LTets]) glCallList(sc->dlist[LTets]);

      if (sc->dlist[LHexa]) glCallList(sc->dlist[LHexa]);
    }
  }

  if (mesh->dim == 2 && !elev) glEnable(GL_DEPTH_TEST);
}

#ifdef ppc
void bogusQuad(pScene sc) {
  /* bogus polygon (nvidia) */
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  glLineWidth(1.0);
  glColor3fv(sc->par.back);
  glBegin(GL_QUADS);
  glVertex3f(0., 0., -sc->persp->depth);
  glVertex3f(0., 0., -sc->persp->depth);
  glVertex3f(0., 0., -sc->persp->depth);
  glVertex3f(0., 0., -sc->persp->depth);
  glEnd( );
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

#endif

static void displayScene(pScene sc, int mode, int clip) {
  int map;

  map = mode & S_MAP;

  switch (mode) {
    case FILL: /* solid fill */
      if (ddebug) printf("solid fill\n");

      glEnable(GL_LIGHTING);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glDisable(GL_POLYGON_OFFSET_FILL);
      drawList(sc, clip, 0);
      glDisable(GL_LIGHTING);
      break;

    case WIRE: /* basic wireframe */
    case WIRE + S_MATERIAL:
      if (ddebug) printf("wireframe\n");

#ifdef ppc
      bogusQuad(sc);
#endif
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      glColor4fv(sc->par.line);
      glDisable(GL_POLYGON_OFFSET_FILL);
      drawList(sc, clip, 0);
      break;

    case DEPTH: /* depth wireframe */
    case DEPTH + S_MATERIAL:
      if (ddebug) printf("depth wireframe\n");

      glEnable(GL_LIGHTING);
      glDisable(GL_COLOR_MATERIAL);
      glDisable(GL_POLYGON_OFFSET_FILL);
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      drawList(sc, clip, 0);
      glDisable(GL_LIGHTING);
      break;

    case HIDDEN: /* hidden lines removal */
    case HIDDEN + S_MATERIAL:
      if (ddebug) printf("hidden lines\n");

      glDisable(GL_LIGHTING);
      glDisable(GL_COLOR_MATERIAL);
      glEnable(GL_POLYGON_OFFSET_FILL);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glColor3fv(sc->par.back);
      drawList(sc, clip, 0);
      glDisable(GL_POLYGON_OFFSET_FILL);
#ifdef ppc
      bogusQuad(sc);
#endif
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      glColor4fv(sc->par.line);
      drawList(sc, clip, 0);
      break;

    case SHADED: /* shaded polygons */
    case SHADED + S_MATERIAL:
      if (ddebug) printf("shaded polygons\n");

      glEnable(GL_LIGHTING);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glEnable(GL_POLYGON_OFFSET_FILL);
      drawList(sc, clip, 0);
      glDisable(GL_LIGHTING);
      glDisable(GL_POLYGON_OFFSET_FILL);
#ifdef ppc
      bogusQuad(sc);
#endif
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      glColor4fv(sc->par.line);
      drawList(sc, clip, 0);
      break;

    case SIZEMAP: /* display metric map */
    case SIZEMAP + S_MATERIAL:
      if (ddebug) printf("display sizemap\n");

      glEnable(GL_LIGHTING);
      glEnable(GL_COLOR_MATERIAL);
      glEnable(GL_POLYGON_OFFSET_FILL);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      drawList(sc, clip, map);
      glDisable(GL_LIGHTING);
      glDisable(GL_COLOR_MATERIAL);
      glDisable(GL_POLYGON_OFFSET_FILL);
#ifdef ppc
      bogusQuad(sc);
#endif
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      glColor4fv(sc->par.line);
      glLineWidth(1.0);
      drawList(sc, clip, 0);
      if (sc->mode & S_ALTITUDE) {
        glColor4fv(sc->par.line);
        if (sc->mlist[LTets]) glCallList(sc->mlist[LTets]);

        if (sc->mlist[LHexa]) glCallList(sc->mlist[LHexa]);
      }

      break;

    default: /* other modes */
      if (ddebug) printf("rendering mode %d\n", sc->mode);

      /* interior */
      if (sc->mode & S_FILL) {
        if (sc->mode & S_COLOR) glEnable(GL_LIGHTING);

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glEnable(GL_POLYGON_OFFSET_FILL);
        if (sc->mode & S_MAP) {
          glEnable(GL_COLOR_MATERIAL);
          drawList(sc, clip, map);
          glDisable(GL_COLOR_MATERIAL);
        } else {
          glColor4fv(sc->par.back);
          drawList(sc, clip, 0);
        }
      }

      /* boundary */
      glDisable(GL_LIGHTING);
      glDisable(GL_COLOR_MATERIAL);
      glDisable(GL_POLYGON_OFFSET_FILL);
#ifdef ppc
      bogusQuad(sc);
#endif
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      if (!(sc->mode & S_BDRY)) break;

      if (sc->mode & S_COLOR && !(sc->mode & S_FILL)) {
        glEnable(GL_COLOR_MATERIAL);
        glEnable(GL_LIGHTING);
      }

      if (sc->mode & S_MAP) {
        if (sc->mode & S_FILL) {
          glColor4fv(sc->par.line);
          drawList(sc, clip, 0);
        } else {
          drawList(sc, clip, map);
        }
      } else if (sc->mode & S_ALTITUDE) {
        glColor4fv(sc->par.line);
        drawList(sc, clip, map);
        if (sc->mlist[LTets]) glCallList(sc->mlist[LTets]);

        if (sc->mlist[LHexa]) glCallList(sc->mlist[LHexa]);
      } else {
        glColor4fv(sc->par.line);
        drawList(sc, clip, 0);
      }
  }
}

static void displayData(pScene sc, pMesh mesh) {
  glDisable(GL_LIGHTING);
  glDisable(GL_COLOR_MATERIAL);

  /* iso-lines */
  if (sc->isotyp & S_ISOLINE) {
    glLineWidth(1.0);
    if (sc->ilist[LTria]) glCallList(sc->ilist[LTria]);

    if (sc->ilist[LQuad]) glCallList(sc->ilist[LQuad]);

    glDisable(GL_CLIP_PLANE0);
    if (sc->ilist[LTets] && sc->clip->active & C_ON) glCallList(sc->ilist[LTets]);
  }

  /* vector field */
  if (sc->isotyp & S_VECTOR) {
    if (mesh->dim == 2) {
      if (sc->vlist[LTria]) glCallList(sc->vlist[LTria]);

      if (sc->vlist[LQuad]) glCallList(sc->vlist[LQuad]);
    } else {
      if (sc->clip->active & C_ON) {
        glDisable(GL_CLIP_PLANE0);
        if (sc->vlist[LTets]) glCallList(sc->vlist[LTets]);

        if (sc->vlist[LHexa]) glCallList(sc->vlist[LHexa]);
      } else if (mesh->ntet + mesh->nhex == 0) {
        if (sc->vlist[LTria]) glCallList(sc->vlist[LTria]);
      }
    }
  }

  /* streamlines */
  if (sc->isotyp & S_CRITP && sc->cplist) glCallList(sc->cplist);

  if (sc->isotyp & S_STREAML) {
    int kk;

    for (kk = 0; kk < sc->stream->nbstl; kk++) {
      glCallList(sc->slist[kk]);
    }
  } else if (sc->isotyp & S_PARTICLE) {
    glEnable(GL_LIGHTING);
    glEnable(GL_COLOR_MATERIAL);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    displayParticle(sc, mesh);
    glDisable(GL_COLOR_MATERIAL);
  }

  /* iso-surfaces */
  if (sc->isotyp & S_ISOSURF) {
    glEnable(GL_LIGHTING);
    glEnable(GL_COLOR_MATERIAL);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    if (sc->ilist[LTets]) glCallList(sc->ilist[LTets]);

    if (sc->ilist[LHexa]) glCallList(sc->ilist[LHexa]);

    glDisable(GL_LIGHTING);
    glDisable(GL_COLOR_MATERIAL);
  }
}

void setupView(pScene sc) {
  pScene slave;
  pMesh mesh;
  pTransform view;
  pPersp p;
  pCamera c;

  /* default */
  if (ddebug) fprintf(stdout, "setupView\n");

  mesh = cv.mesh[sc->idmesh];
  view = sc->view;
  p = sc->persp;
  c = sc->camera;

  /* init transformation matrix */
  if (sc->type & S_RESET) {
    glPushMatrix( );
    glLoadIdentity( );
    if (p->pmode != CAMERA) {
      if (mesh->dim == 3 || sc->mode & S_ALTITUDE) {
        glRotatef(-60., 1., 0., 0.);
        glRotatef(-120., 0., 0., 1.);
      }
    } else {
      if (c->vecup == X_AXIS)
        glRotatef(90.0, 0.0, 0.0, 1.0);
      else if (c->vecup == Z_AXIS)
        glRotatef(-90.0, 1.0, 0.0, 0.0);
    }

    glGetFloatv(GL_MODELVIEW_MATRIX, view->matrix);
    glPopMatrix( );
    sc->type ^= S_RESET;
  }

  /* keep old transformation */
  memcpy(view->oldmat, view->matrix, 16 * sizeof(float));

  /* compute new transformation */
  glPushMatrix( );
  glLoadIdentity( );
  if (p->pmode != CAMERA) {
    glTranslatef(view->panx, view->pany, 0.0);
    if (mesh->dim == 3 || sc->mode & S_ALTITUDE)
      glRotatef(view->angle, view->axis[0], view->axis[1], view->axis[2]);

    glTranslatef(-view->opanx, -view->opany, 0.);
    glMultMatrixf(view->matrix);
    glGetFloatv(GL_MODELVIEW_MATRIX, view->matrix);
  } else if (animate) {
    c->eye[0] += c->spmod * c->speed[0];
    c->eye[1] += c->spmod * c->speed[1];
    c->eye[2] += c->spmod * c->speed[2];
    animateCamera( );
    reshapeScene(sc->par.xs, sc->par.ys);
  }

  glPopMatrix( );

  /* keep old translation */
  view->opanx = view->panx;
  view->opany = view->pany;

  /* copy views */
  if (!animate && sc->slave > -1) {
    int clvol;

    slave = cv.scene[sc->slave];
    memcpy(slave->view, sc->view, sizeof(struct transform));
    memcpy(slave->camera, sc->camera, sizeof(struct camera));
    slave->view->angle = 0.0f;
    clvol = slave->clip->active & C_VOL;
    memcpy(slave->clip, sc->clip, sizeof(struct clip));
    if (clvol) slave->clip->active |= C_VOL;
  }
}

void drawModel(pScene sc) {
  pMesh mesh;
  pTransform view;
  pClip clip;
  ubyte sstatic;

  /* default */
  mesh = cv.mesh[sc->idmesh];
  view = sc->view;
  clip = sc->clip;
  if (ddebug) printf("\n-- redraw scene %d, mesh %d\n", sc->idwin, sc->idmesh);

  glDisable(GL_LIGHTING);

  /* draw clipping plane */
  if (clip->active & C_ON) {
    drawClip(sc, clip, mesh, 0);
    glClipPlane(GL_CLIP_PLANE0, clip->eqn);
    glEnable(GL_CLIP_PLANE0);
  } else {
    glDisable(GL_CLIP_PLANE0);
  }

  /* draw object if static scene */
  sstatic = view->mstate > 0 && clip->cliptr->mstate > 0;
  if (sstatic || sc->type & S_FOLLOW) {
    displayScene(sc, sc->mode, 0);
    if (sc->item & S_NUMP || sc->item & S_NUMF) listNum(sc, mesh);

    /* draw normals */
    if (sc->type & S_NORMAL) {
      if (!sc->nlist) sc->nlist = drawNormals(mesh, sc);

      glCallList(sc->nlist);
    }

    /* draw data */
    if (sstatic) displayData(sc, mesh);
  } else if (!(sc->item & S_BOX)) {
    drawBox(sc, mesh, 0);
  }

  /* draw ridges, corners, etc. */
  if ((sc->item & S_GEOM) && sc->glist) {
    glDisable(GL_LIGHTING);
    if (!mesh->ne)
      glPointSize(1);
    else
      glPointSize(5);

    glDisable(GL_COLOR_MATERIAL);
    glCallList(sc->glist);
  }

  glDisable(GL_CLIP_PLANE0);
  if (clip->active & C_EDIT || sc->item & S_BOX) drawBox(sc, mesh, 0);

  if (sc->item & S_AXIS) drawAxis(sc, mesh->dim);

  if ((mesh->dim == 3 || sc->mode & S_ALTITUDE) && sc->item & S_GRID) drawBase(sc, mesh);

  if (sc->cube->active & C_ON) drawCube(sc, mesh);

  sstatic |= tiling;
  if (sstatic && clip->active & C_ON && clip->active & C_VOL) displayScene(sc, sc->mode, 1);

  if (sc->picklist && !(sc->isotyp & S_PARTICLE)) {
    glEnable(GL_LIGHTING);
    glEnable(GL_COLOR_MATERIAL);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_POLYGON_OFFSET_FILL);
    glCallList(sc->picklist);
    glEnable(GL_POLYGON_OFFSET_FILL);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glDisable(GL_COLOR_MATERIAL);
    glDisable(GL_LIGHTING);
  }

  /* show path, if any */
  if (sc->type & S_PATH && sc->path.tlist) glCallList(sc->path.tlist);
}

void redrawMorphing(pScene sc) {
  if (morphing) {
    pMesh mesh;
    mesh = cv.mesh[sc->idmesh];
    if (!morphMesh(sc, mesh)) return;
  }
}

/* animate */
void glutIdle(void) {
  static float timePassed = 0.0;
  static float timeRedraw = 0.02;
  float clk, timeElaps;

  clk = clock( );
  timeElaps = (clk - timePassed) / (float)CLOCKS_PER_SEC;
  if (timeElaps >= timeRedraw) {
    timePassed = clk;
    glutPostRedisplay( );
  }
}

void streamIdle(void) {
  pScene sc;
  pMesh mesh;
  static clock_t timbase = 0;
  static float maxtim = 0.;

  sc = cv.scene[currentScene( )];
  sc->par.cumtim += sc->par.dt;
  sc->par.advtim = 1;
  if (sc->par.cumtim > sc->par.maxtime) {
    sc->par.cumtim = 0.0;
    sc->par.cumpertime = 0.0;
    saveimg = 0;
    sc->isotyp &= ~S_PARTICLE;
    glutIdleFunc(0);
    printf("\nfin");
  } else if (sc->par.cumpertime >= sc->par.pertime) {
    mesh = cv.mesh[sc->idmesh];
    sc->par.cumpertime = 0.0;
    if (!animParticle(sc, mesh)) {
      sc->par.cumtim = 0.0;
      sc->par.cumpertime = 0.0;
      saveimg = 0;
      glutIdleFunc(0);
      printf("\nfin");
    } else {
      glutPostRedisplay( );
    }
  } else {
    glutPostRedisplay( );
  }

  return;

  if (timbase < 1) {
    timbase = clock( );
    maxtim = 0.0;
    sc->par.advtim = 0;
    glutPostRedisplay( );
  } else {
    float elp;
    clock_t tim;

    tim = clock( );
    elp = (tim - timbase) / (float)CLOCKS_PER_SEC;
    if (elp > sc->par.dt) {
      timbase = tim;
      maxtim += elp;
      sc->par.advtim = 1;
      if (maxtim > sc->par.maxtime) glutIdleFunc(0);
    }

    sc->par.cumtim += sc->par.dt;
    glutPostRedisplay( );
  }
}

/* OpenGL callbacks */
void redrawScene( ) {
  pScene sc;
  pTransform view;
  pPersp p;

  sc = cv.scene[currentScene( )];
  view = sc->view;
  p = sc->persp;

  if (stereoMode == MONO || !hasStereo) {
    glDrawBuffer(GL_BACK_LEFT);
    glClearColor(sc->par.back[0], sc->par.back[1], sc->par.back[2], sc->par.back[3]);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity( );
    gluLookAt(0., 0., -p->depth, 0., 0., 0., 0.0, 1.0, 0.0);

    setupView(sc);
    glMultMatrixf(view->matrix);
    glTranslatef(sc->cx, sc->cy, sc->cz);
    drawModel(sc);
    if (sc->type & S_DECO) redrawStatusBar(sc);
  } else {
    double ndfl, ratio, top, bottom;
    double left, right, nnear, ffar;

    nnear = -p->depth - 0.5 * sc->dmax;
    if (nnear < 0.1) nnear = 0.1;

    ffar = -p->depth + 0.5 * sc->dmax;
    ratio = sc->par.xs / (double)sc->par.ys;
    top = nnear * tan(DTOR * 0.5 * p->fovy);
    ndfl = nnear / p->depth;
    if (sc->par.eyesep < 0.0) sc->par.eyesep = fabs(p->depth / 20.0);

    /* left view */
    glDrawBuffer(GL_BACK_LEFT);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    left = -ratio * top + 0.5 * sc->par.eyesep * ndfl;
    right = ratio * top + 0.5 * sc->par.eyesep * ndfl;
    bottom = -top;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity( );
    glFrustum(left, right, top, bottom, nnear, ffar);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity( );
    gluLookAt(-sc->par.eyesep, 0., -p->depth, sc->par.eyesep / 3.0, 0., 0., 0.0, 1.0, 0.0);

    setupView(sc);
    glMultMatrixf(view->matrix);
    glTranslatef(sc->cx, sc->cy, sc->cz);
    drawModel(sc);
    if (sc->type & S_DECO) redrawStatusBar(sc);

    /* right view */
    glDrawBuffer(GL_BACK_RIGHT);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    left = -ratio * top - 0.5 * sc->par.eyesep * ndfl;
    right = ratio * top - 0.5 * sc->par.eyesep * ndfl;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity( );
    glFrustum(left, right, top, bottom, nnear, ffar);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity( );
    gluLookAt(sc->par.eyesep, 0., -p->depth, sc->par.eyesep / 3.0, 0., 0., 0.0, 1.0, 0.0);

    setupView(sc);
    glMultMatrixf(view->matrix);
    glTranslatef(sc->cx, sc->cy, sc->cz);
    drawModel(sc);
    if (sc->type & S_DECO) redrawStatusBar(sc);
  }

  /* refresh screen */
  if (saveimg && animate)
    glFlush( );
  else
    glutSwapBuffers( );

  if (ddebug) checkErrors( );

  if (saveimg && !(sc->type & S_SCISSOR)) keyFile('H', 0, 0);

  /* redraw linked scene */
  if (!animate && sc->slave > -1) {
    pScene slave;

    slave = cv.scene[sc->slave];
    glutSetWindow(slave->idwin);
    redrawScene( );
  }
}

/* OpenGL callbacks */
void redrawSchnauzer( ) {
  pScene sc = cv.scene[currentScene( )];
  pMesh mesh;
  char *ptr, data[256];

  mesh = cv.mesh[sc->idmesh];
  strcpy(data, mesh->name);
  ptr = (char *)strstr(data, ".mesh");
  if (ptr) *ptr = '\0';

  ptr = (char *)strstr(data, ".gis");
  if (ptr) *ptr = '\0';

  strcat(data, ".ppm");
  redrawScene( );
  imgHard(sc, data, 'H');

  exit(0);
}

void deleteScene(pScene sc) {
  /* default */
  if (ddebug) printf("deleteScene\n");

  M_free(sc->view);
  M_free(sc->clip);
  M_free(sc->persp);
  M_free(sc->camera);
  M_free(sc->material);
  M_free(sc->matsort);
  M_free(sc);
}

void initGrafix(pScene sc, pMesh mesh) {
  GLfloat lightamb[4] = {0.3, 0.3, 0.3, 1.0};
  GLfloat lightdif[4] = {1.0, 1.0, 1.0, 1.0};
  GLfloat lightpos[4] = {0.0, 0.0, 1.0, 0.0};

  if (ddebug) printf("initGrafix\n");

  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);
  glPolygonOffset(1.0, 1.0 / (float)0x10000);
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

  glShadeModel(GL_SMOOTH);
  glDisable(GL_NORMALIZE);
  glDisable(GL_LINE_SMOOTH);
  glDisable(GL_POINT_SMOOTH);
  glDisable(GL_DITHER);
  glDisable(GL_CULL_FACE);
  if (mesh->typ == 2) {
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
  }

  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

  /* lighting */
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
#if (!defined(GL_VERSION_1_1))
  glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
#endif

  glLightfv(GL_LIGHT0, GL_DIFFUSE, lightdif);
  glLightfv(GL_LIGHT0, GL_AMBIENT, lightamb);
  glEnable(GL_LIGHTING);
  if (stereoMode != MONO) {
    lightpos[2] = -1.0;
    sc->par.sunpos[2] = -fabs(sc->par.sunpos[2]);
  }

  if (sc->par.sunp)
    glLightfv(GL_LIGHT0, GL_POSITION, sc->par.sunpos);
  else
    glLightfv(GL_LIGHT0, GL_POSITION, lightpos);

  glEnable(GL_LIGHT0);
}

/* new scene */
int createScene(pScene sc, int idmesh) {
  pMesh mesh;
  char data[512];

  /* default */
  mesh = cv.mesh[idmesh];
  if (!quiet) fprintf(stdout, "   Computing 3D scene\n");

  /* set default mode */
  sc->idmesh = idmesh;
  sc->par.xi = sc->par.yi = 10;
  if (option == SCHNAUZER) {
    sc->par.xs = schw;
    sc->par.ys = schh;
  } else {
    if (sc->par.xs == 0) sc->par.xs = 600;

    if (sc->par.ys == 0) sc->par.ys = 600;
  }

  if (!sc->mode) sc->mode = HIDDEN;

  sc->item = 0;
  sc->shrink = 1.0;
  sc->slave = sc->master = -1;
  sc->picked = 0;
  if (mesh->nvn == 0) sc->type ^= S_FLAT;

  if (mesh->ne == 0) sc->item |= S_GEOM;

  /* compute scene depth */
  sc->dmax = sc->dmin = mesh->xmax - mesh->xmin;
  sc->dmax = max(sc->dmax, mesh->ymax - mesh->ymin);
  sc->dmin = min(sc->dmin, mesh->ymax - mesh->ymin);
  if (mesh->dim == 3) {
    sc->dmax = max(sc->dmax, mesh->zmax - mesh->zmin);
    sc->dmin = min(sc->dmin, mesh->zmax - mesh->zmin);
  }

  sc->dmax = fabs(sc->dmax);
  sc->dmin = fabs(sc->dmin);
  if (!sc->par.sunp) {
    sc->par.sunpos[0] *= 2.0 * sc->dmax;
    sc->par.sunpos[1] *= 2.0 * sc->dmax;
    sc->par.sunpos[2] *= 2.0 * sc->dmax;
  }

  sc->par.sunpos[3] = 1.0;

  /* create window */
  glutInitWindowSize(sc->par.xs, sc->par.ys);
  sc->idwin = glutCreateWindow("");
  assert(sc->idwin != 0);
  if (fullscreen) {
    glutFullScreen( );
    sc->par.xs = glutGet(GLUT_SCREEN_WIDTH);
    sc->par.ys = glutGet(GLUT_SCREEN_HEIGHT);
  }

  /* set window name */
  sprintf(data, "Medit - [%s] #%d", mesh->name, sc->idwin);
  glutSetWindowTitle(data);
  glutSetIconTitle(data);

  /* required! to change background color */
  glClearColor(sc->par.back[0], sc->par.back[1], sc->par.back[2], sc->par.back[3]);

  /* init perspective */
  sc->persp = initPersp(0, sc->dmax);
  sc->camera = (pCamera)initCamera(sc, Y_AXIS);
  if (mesh->typ == 2) {
    sc->persp->pmode = CAMERA;
    sc->persp->depth *= 0.5;
  }

  /* create default view */
  sc->view = (pTransform)createTransform( );
  if (!sc->view) return (0);

  sc->type |= S_RESET + S_DECO;
  sc->clip = (pClip)createClip(sc, mesh);
  if (!sc->clip) return (0);

  sc->cube = (pCube)createCube(sc, mesh);
  if (!sc->cube) return (0);

  /* create menus */
  if (!createMenus(sc, mesh)) return (0);

  /* assign callbacks */
  if (sc->type & S_SCISSOR) {
    glutDisplayFunc(scissorScene);
  } else if (option == SCHNAUZER) {
    glutDisplayFunc(redrawSchnauzer);
  } else if (sc->persp->pmode == CAMERA) {
    glutMouseFunc(mouseCamera);
    glutMotionFunc(motionCamera);
    glutDisplayFunc(redrawScene);
  } else {
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    glutDisplayFunc(redrawScene);
  }

  glutReshapeFunc(reshapeScene);
  glutKeyboardFunc(keyScene);
  glutSpecialFunc(special);
  glutAttachMenu(GLUT_RIGHT_BUTTON);

  /* create display lists by geom type */
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  doLists(sc, mesh);
  sc->glist = geomList(sc, mesh);
  sc->type |= S_FOLLOW;

  /* local stack */
  if (!pilmat) {
    pilmat = (int *)calloc(sc->par.nbmat + 2, sizeof(int));
    if (!pilmat) return (0);
  }

  /* color list */
  setupPalette(sc, mesh);
  sc->stream = NULL;

  initGrafix(sc, mesh);
  return (1);
}

#ifdef __cplusplus
}
#endif
