/* opengl.c: OpenGL graphics driver. */

#include <stdio.h>
#include <stdlib.h>

#include <Xm/Xm.h>
#include <Xm/DrawingA.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glu.h>
#include <unistd.h>

#include "render.h"
#include "canvas.h"
#include "camera.h"
#include "error.h"
#include "pools.h"
#include "scene.h"
#include "radiance.h"
#include "raytracing.h"
#include "raycasting.h"
#include "patch.h"
#include "renderhook.h"
#include "image.h"
#include "tonemapping.h"

#include <time.h>
#ifdef TIMINGS
static clock_t render_time=0;
static int render_frames=0;
#endif

#ifdef ALL_DOUBLE
/* Use double for everything : DIRTY !!!*/
#define glVertex3fv glVertex3dv
#define GLfloat GLdouble
#endif

static int dlistid = -1;	/* display list ID */

static XVisualInfo *glx_visual;
static GLXContext glx_context;
static Display *glx_display;
static Window glx_window;

static int attribute_list_db[] = {GLX_RGBA, 
			       GLX_RED_SIZE, 2,
			       GLX_GREEN_SIZE, 2,
			       GLX_BLUE_SIZE, 2,
			       GLX_DEPTH_SIZE, 16,
			       GLX_DOUBLEBUFFER,
			       None};

static int attribute_list_nodb[] = {GLX_RGBA, 
			       GLX_RED_SIZE, 2,
			       GLX_GREEN_SIZE, 2,
			       GLX_BLUE_SIZE, 2,
			       GLX_DEPTH_SIZE, 16,
			       None};

static int ogl_inited = FALSE, doublebuf = FALSE;

int do_render_normals;

static GLubyte *background_ptr = NULL;
static GLuint backgroundTex=0;

static XVisualInfo *OpenGLGetVisual(Display *dpy)
{
  if (!glXQueryExtension(dpy, NULL, NULL)) 
    Fatal(1, NULL, "No GLX extension available on the display");

  /* get an appropriate visual: first look for a visual with double 
     buffering */
  glx_visual = glXChooseVisual(dpy, DefaultScreen(dpy), attribute_list_db);
  if (glx_visual) {
    doublebuf = TRUE;
    return glx_visual;
  } else {	/* look for a visual without double buffering */
    doublebuf = FALSE;
    return glx_visual = glXChooseVisual(dpy, DefaultScreen(dpy), attribute_list_nodb);
  }
}

/* finds the visual and colormap to be used for OpenGL rendering on the specified 
 * screen */
Boolean RenderGetVisualInfoAndColormap(Screen *screen,
				       XVisualInfo *visual_info,
				       Colormap *colormap)
{
  XVisualInfo *vi;
  Display *dpy = DisplayOfScreen(screen);

  vi = OpenGLGetVisual(dpy);
  if (!vi) 
    Fatal(1, NULL, "Failed to get a suitable X visual");
  *visual_info = *vi;
  
  *colormap = XCreateColormap(dpy, RootWindow(dpy, visual_info->screen),
			      visual_info->visual, AllocNone);	

  return TRUE;
}

void RenderClearWindow(void)
{
  glClearColor(Camera.background.r, Camera.background.g, Camera.background.b, 0.);
  glClearDepth(1.);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void RenderSetCamera(void)
{
  RenderClearWindow();

  /* use full viewport */
  glViewport(0, 0, Camera.hres, Camera.vres);

  /* draw backgroudn when needed */
  if(renderopts.use_background && background_ptr) RenderBackground(&Camera);

  /* determine distance to front- and backclipping plane */
  RenderGetNearFar(&Camera.near, &Camera.far);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(Camera.vfov*2., (float)Camera.hres/(float)Camera.vres,
		 Camera.near, Camera.far);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(Camera.eyep.x, Camera.eyep.y ,Camera.eyep.z,
	    Camera.lookp.x, Camera.lookp.y, Camera.lookp.z,
	    Camera.updir.x, Camera.updir.y, Camera.updir.z);
}

static void OpenGLInitState(void)
{
  glDepthFunc(GL_LEQUAL);
  glEnable(GL_DEPTH_TEST); 

  glDrawBuffer(GL_FRONT_AND_BACK);
  
  RenderClearWindow();
  glFinish();

  ogl_inited = TRUE;
  dlistid = -1;

  do_render_normals = FALSE;
}

void RenderInitWindow(Display *display, Window window, XVisualInfo *visinfo)
{
  glx_display = display;
  glx_window = window;
  glx_visual = visinfo;

  /* create a GLX context */
  glx_context = glXCreateContext(glx_display, glx_visual, 0, GL_TRUE);
  if (!glx_context)
    Fatal(1, NULL, "Couldn't create GLX context. Is the window OpenGL capable??");
	
  /* connect the context to the window */
  if (!glXMakeCurrent(glx_display, glx_window, glx_context))
    Fatal(1, NULL, "Couldn't make the GLX context current.");

  doublebuf = TRUE;	/* assume it is true */

  OpenGLInitState();
}

static void OpenGLInitCallback(Widget canvas, XtPointer client_data, XtPointer call_data)
{
  RenderInitWindow(XtDisplay(canvas), XtWindow(canvas), glx_visual);
  XtRemoveCallback(canvas, XmNexposeCallback, OpenGLInitCallback, (XtPointer)NULL);
}

/* open window for rendering */
Widget RenderCreateWindow(Widget parent)
{
  Widget canvas;

  canvas = XtVaCreateManagedWidget("canvas",
				   xmDrawingAreaWidgetClass,
				   parent, 
				   NULL);

  /* initialize OpenGL on first expose */
  ogl_inited = FALSE;
  XtAddCallback(canvas, XmNexposeCallback, OpenGLInitCallback, (XtPointer)NULL);

  return canvas;
}

#ifdef OSMESA
/* Offscreen rendering using Mesa's offscreen rendering contexts */

#include "GL/osmesa.h"

/* creates an offscreen window for rendering */
void RenderCreateOffscreenWindow(int hres, int vres)
{
  GLubyte *image_buffer = (GLubyte *)Alloc(hres * vres * sizeof(GLubyte) * 4);

  OSMesaContext osctx = OSMesaCreateContext(OSMESA_RGBA, NULL);
  if (!osctx)
    Fatal(1, NULL, "Couldn't create Mesa offscreen rendering context");

  if (!OSMesaMakeCurrent(osctx, image_buffer, GL_UNSIGNED_BYTE, hres, vres))
    Fatal(1, NULL, "Couldn't bind Mesa offscreen rendering context to image buffer of size %d x %d", hres, vres);

  OpenGLInitState();
}
#else /*OSMESA*/
/* not Mesa, so no Mesa offscreen rendering context */

void RenderCreateOffscreenWindow(int hres, int vres)
{
  Fatal(1, NULL, "Offscreen rendering is only supported through the Mesa library.\nRe-link the program with the Mesa library if you want it");
}
#endif /*OSMESA*/

int RenderInitialized(void)
{
  return ogl_inited;
}

void RenderLine(VECTOR *x, VECTOR *y)
{
  POINT X, Y;
  VECTOR dir;

  glBegin(GL_LINES);

  /* move the line a bit closer to the eyepoint to avoid Z buffer
   * artefacts */
  VECTORSUBTRACT(Camera.eyep, *x, dir);
  VECTORSUMSCALED(*x, 0.01, dir, X);
  glVertex3fv((GLfloat *)&X);

  VECTORSUBTRACT(Camera.eyep, *y, dir);
  VECTORSUMSCALED(*y, 0.01, dir, Y);
  glVertex3fv((GLfloat *)&Y);

  glEnd();
}


void RenderAALine(VECTOR *x, VECTOR *y)
{
  glEnable(GL_LINE_SMOOTH);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);

  RenderLine(x,y);

  glDisable(GL_LINE_SMOOTH);
  glDisable(GL_BLEND);
}

/* sets the current color */
void RenderSetColor(RGB *rgb)
{
  RGB corrected_rgb;

  corrected_rgb = *rgb;
  RGBGAMMACORRECT(corrected_rgb);
  glColor3fv((GLfloat *)&corrected_rgb);
}

/* sets line width */
void RenderSetLineWidth(float width)
{
  glLineWidth(width);
}

/* renders a convex polygon flat shaded in the current color */
void RenderPolygonFlat(int nrverts, POINT *verts)
{
  int i;

  glBegin(GL_POLYGON);
  for (i=0; i<nrverts; i++) 
    glVertex3fv((GLfloat *)&verts[i]);
  glEnd();
}

/* renders a convex polygon with Gouraud shading */
void RenderPolygonGouraud(int nrverts, POINT *verts, RGB *vertcols)
{
  int i;

  glBegin(GL_POLYGON);
  for (i=0; i<nrverts; i++) {
    RenderSetColor(&vertcols[i]);
    glVertex3fv((GLfloat *)&verts[i]);
  }
  glEnd();
}

void RenderBeginTriangleStrip(void)
{
  glBegin(GL_TRIANGLE_STRIP);
}

void RenderNextTrianglePoint(POINT *point, RGB *col)
{
  if(col)
    RenderSetColor(col);
  glVertex3fv((float *)point);
}

void RenderTriangleStrip(int N,POINT *point, RGB *col)
{
  int i;

  if(N > 256)
  {
    Error("RenderTriangleStrip", "Too many vertices in strip (<256)");
    return;
  }

  for(i = 0; i < N; i++)
  {
    RenderSetColor(&col[i]);
    glVertex3fv((float *)(&point[i]));  
  }
}

void RenderEndTriangleStrip(void)
{
  glEnd();
}

void RenderPatchFlat(PATCH *patch)
{
  int i;

  RenderSetColor(&patch->color);
  switch (patch->nrvertices) {
  case 3:
    glBegin(GL_TRIANGLES);
    glVertex3fv((GLfloat *)patch->vertex[0]->point);
    glVertex3fv((GLfloat *)patch->vertex[1]->point);
    glVertex3fv((GLfloat *)patch->vertex[2]->point);
    glEnd();
    break;
  case 4:
    glBegin(GL_QUADS);
    glVertex3fv((GLfloat *)patch->vertex[0]->point);
    glVertex3fv((GLfloat *)patch->vertex[1]->point);
    glVertex3fv((GLfloat *)patch->vertex[2]->point);
    glVertex3fv((GLfloat *)patch->vertex[3]->point);
    glEnd();
    break;
  default:
    glBegin(GL_POLYGON);
    for (i=0; i<patch->nrvertices; i++)
      glVertex3fv((GLfloat *)patch->vertex[i]->point);
    glEnd();
  }
}

void RenderPatchSmooth(PATCH *patch)
{
  int i;

  switch (patch->nrvertices) {
  case 3:
    glBegin(GL_TRIANGLES);
    RenderSetColor(&patch->vertex[0]->color);
    glVertex3fv((GLfloat *)patch->vertex[0]->point);
    RenderSetColor(&patch->vertex[1]->color);
    glVertex3fv((GLfloat *)patch->vertex[1]->point);
    RenderSetColor(&patch->vertex[2]->color);
    glVertex3fv((GLfloat *)patch->vertex[2]->point);
    glEnd();
    break;
  case 4:
    glBegin(GL_QUADS);
    RenderSetColor(&patch->vertex[0]->color);
    glVertex3fv((GLfloat *)patch->vertex[0]->point);
    RenderSetColor(&patch->vertex[1]->color);
    glVertex3fv((GLfloat *)patch->vertex[1]->point);
    RenderSetColor(&patch->vertex[2]->color);
    glVertex3fv((GLfloat *)patch->vertex[2]->point);
    RenderSetColor(&patch->vertex[3]->color);
    glVertex3fv((GLfloat *)patch->vertex[3]->point);
    glEnd();
    break;
  default:
    glBegin(GL_POLYGON);
    for (i=0; i<patch->nrvertices; i++) {
      RenderSetColor(&patch->vertex[i]->color);
      glVertex3fv((GLfloat *)patch->vertex[i]->point);
    }
    glEnd();
  }
}

/* renders the patch outline in the current color */
void RenderPatchOutline(PATCH *patch)
{
  int i;
  POINT tmp; VECTOR dir;

  glBegin(GL_LINE_LOOP);
  for (i=0; i<patch->nrvertices; i++) {
    /* move the outlines a bit closer to the eyepoint to avoid Z buffer
     * artefacts */
    VECTORSUBTRACT(Camera.eyep, *patch->vertex[i]->point, dir);
    VECTORSUMSCALED(*patch->vertex[i]->point, 0.01, dir, tmp);
    glVertex3fv((GLfloat *)&tmp);
  }
  glEnd();
}

void RenderPatch(PATCH *patch)
{
  if(!renderopts.no_shading)
  {
    if (renderopts.smooth_shading)
      RenderPatchSmooth(patch);
    else
      RenderPatchFlat(patch);
  }

  if (renderopts.draw_outlines && 
      (VECTORDOTPRODUCT(patch->normal, Camera.eyep) + patch->plane_constant > EPSILON
       || renderopts.use_display_lists)) {
    RenderSetColor(&renderopts.outline_color);
    RenderPatchOutline(patch);
  }
}

static void ReallyRenderOctreeLeaf(GEOM *geom, void (*render_patch)(PATCH *))
{
  PATCHLIST *patchlist = GeomPatchList(geom);
  ForAllPatches(P, patchlist) {
    render_patch(P);
  } EndForAll;
}

static void RenderOctreeLeaf(GEOM *geom, void (*render_patch)(PATCH *))
{
  if (renderopts.use_display_lists) {
    if (geom->dlistid <= 0) {
      geom->dlistid = geom->id;
      glNewList(geom->dlistid, GL_COMPILE_AND_EXECUTE);
      ReallyRenderOctreeLeaf(geom, render_patch);
      glEndList();
    } else
      glCallList(geom->dlistid);
  } else
    ReallyRenderOctreeLeaf(geom, render_patch);
}

static int ViewCullBounds(float *bounds)
{
  int i;
  for (i=0; i<NR_VIEW_PLANES; i++) {
    if (BoundsBehindPlane(bounds, &Camera.viewplane[i].norm, Camera.viewplane[i].d))
      return TRUE;
  }
  return FALSE;
}

/* squared distance to midpoint (avoid taking square root) */
static float BoundsDistance2(VECTOR p, float *bounds)
{
  VECTOR mid, d;
  VECTORSET(mid,
	    0.5 * (bounds[MIN_X] + bounds[MAX_X]),
	    0.5 * (bounds[MIN_Y] + bounds[MAX_Y]),
	    0.5 * (bounds[MIN_Z] + bounds[MAX_Z]));
  VECTORSUBTRACT(mid, p, d);
  return VECTORNORM2(d);
}

/* geom is a surface or a compoint with 1 surface and up to 8 compound children
 * geoms, CLusetredWorldGeom is such a geom e.g. */
static void RenderOctreeNonLeaf(GEOM *geom, void (*render_patch)(PATCH *))
{
  int i, n, remaining;
  struct {
    GEOM *geom;
    float dist;
  } octree_children[8];
  GEOMLIST *children = GeomPrimList(geom);

  i = 0;
  ForAllGeoms(child, children) {
    if (GeomIsAggregate(child)) {
      if (i >= 8) {
	Error("RenderOctreeNonLeaf", "Invalid octree geom node (more than 8 compound children)");
	return;
      }
      octree_children[i++].geom = child;
    } else {
      /* render the patches associated with the octree node right away */
      RenderOctreeLeaf(child, render_patch);
    }
  } EndForAll;
  n = i;	/* nr of compound children */

  /* cull the non-leaf octree children geoms */
  for (i=0; i<n; i++) {
    if (ViewCullBounds(octree_children[i].geom->bounds)) {
      octree_children[i].geom = NULL;	/* culled */
      octree_children[i].dist = HUGE;
    } else {	/* not culled, compute distance from eye to midpoint of child */
      octree_children[i].dist = BoundsDistance2(Camera.eyep, octree_children[i].geom->bounds);
    }
  }

#ifdef RENDER_UNSORTED
  /* render children geoms */
  for (i=0; i<n; i++) {
    if (octree_children[i])
      RenderOctreeNonLeaf(octree_children[i].geom, render_patch);
  }
#else
  /* render children geoms in front to back order */
  remaining = n;
  while (remaining > 0) {
    /* find closest remaining child */
    int closest = 0;
    for (i=1; i<n; i++) {
      if (octree_children[i].dist < octree_children[closest].dist)
	closest = i;
    }

    if (!octree_children[closest].geom)
      break;	/* finished */

    /* render it */
    RenderOctreeNonLeaf(octree_children[closest].geom, render_patch);

    /* remove it from the list */
    octree_children[closest].geom = NULL;
    octree_children[closest].dist = HUGE;
    remaining--;
  }
#endif
}

void RenderWorldOctree(void (*render_patch)(PATCH *))
{
  if (!ClusteredWorldGeom)
    return;
  if (!render_patch) render_patch = RenderPatch;
  if (GeomIsAggregate(ClusteredWorldGeom))
    RenderOctreeNonLeaf(ClusteredWorldGeom, render_patch);
  else
    RenderOctreeLeaf(ClusteredWorldGeom, render_patch);
}

static void GeomDeleteDLists(GEOM *geom)
{
  if (geom->dlistid >= 0)
    glDeleteLists(geom->dlistid, 1);
  geom->dlistid = -1;

  if (GeomIsAggregate(ClusteredWorldGeom)) {
    GEOMLIST *children = GeomPrimList(geom);
    ForAllGeoms(child, children) {
      GeomDeleteDLists(child);
    } EndForAll;
  }
}

static void RenderNewOctreeDisplayLists(void)
{
  if (ClusteredWorldGeom)
    GeomDeleteDLists(ClusteredWorldGeom);
}

void RenderNewDisplayList(void)
{
  if (dlistid >= 0)
    glDeleteLists(dlistid, 1);
  dlistid = -1;

  if (renderopts.frustum_culling)
    RenderNewOctreeDisplayLists();

  /*  renderopts.render_raytraced_image = FALSE; */
}

void ReallyRender(void)
{
  if (Radiance && Radiance->RenderScene) {
    Radiance->RenderScene();
  } else if (renderopts.frustum_culling) {
    RenderWorldOctree(RenderPatch);
  } else {
    PatchListIterate(Patches, RenderPatch);
  }
}

void RenderRadiance(void)
{
  if (renderopts.smooth_shading)
    glShadeModel(GL_SMOOTH);
  else
    glShadeModel(GL_FLAT);

  RenderSetCamera();

  if (renderopts.backface_culling)
    glEnable(GL_CULL_FACE);
  else
    glDisable(GL_CULL_FACE);

  if (renderopts.use_display_lists && !renderopts.frustum_culling) {
    if (dlistid <= 0) {
      dlistid = 1;
      glNewList(dlistid, GL_COMPILE_AND_EXECUTE);
      /* render the scene */
      ReallyRender();
      glEndList();
    } else
      glCallList(1);
  } else {
    /* just render the scene */
    ReallyRender();
  }

  if (renderopts.draw_bounding_boxes)
    RenderBoundingBoxHierarchy();

  if (renderopts.draw_clusters)
    RenderClusterHierarchy();

  if (renderopts.draw_cameras)
    RenderCameras();

  /* RenderNormals(); */

}

void RenderScene(void)
{
#ifdef TIMINGS
  clock_t t = clock();
#endif

  if (!ogl_inited)
    return;

  RenderSetLineWidth(renderopts.linewidth);

  CanvasPushMode(CANVASMODE_RENDER);

  if (Camera.changed)
    renderopts.render_raytraced_image = FALSE;

  if (doublebuf)
    glDrawBuffer(GL_BACK);

  if (tmopts.display_test_image)
    RenderGammaTestImage(tmopts.testimg);
  else if (!renderopts.render_raytraced_image || !RenderRayTraced())
    RenderRadiance();

  /* Call installed render hooks, that want to render something
     in the scene */

  RenderHooks();

  glFinish();

  if (doublebuf)
    glXSwapBuffers(glx_display,glx_window);

  glDrawBuffer(GL_FRONT_AND_BACK);

  CanvasPullMode();

  if (do_render_normals)
    RenderNormals();

#ifdef TIMINGS
  render_time += clock() - t;
  render_frames++;
fprintf(stderr, "%d frames, %g sec: %g msec/frame\n", 
	render_frames, (float)render_time/(float)CLOCKS_PER_SEC, 
	(float)render_time/(float)CLOCKS_PER_SEC/(float)render_frames*1000.);
#endif
}

/* returns only after all issued graphics commands have been executed */
void RenderFinish(void)
{
  glFinish();
}

/* Renders an image of m lines of n pixels at column x on row y (= lower
 * left corner of image, relative to the lower left corner of the window) */
void RenderPixels(int x, int y, int width, int height, RGB *rgb)
{
  int j, rowlen;
  GLubyte *c, *c1;

  /* length of one row of RGBA image data rounded up to a multiple of 8 */
  rowlen = (4 * width * sizeof(GLubyte) + 7) & ~7;
  c1 = (GLubyte *)Alloc(height * rowlen + 8);
  c = c1 + ((((unsigned)c1 + 7) & (~7)) - (unsigned)c1);  /* align to 8-byte boundary */

  for (j=0; j<height; j++) {
    RGB *rgbp = &rgb[j*width];
    GLubyte *p = c + j*rowlen;	 	/* let each line start on a 8-byte boundary */
    int i;
    for (i=0; i<width; i++, rgbp++) {
      RGB corrected_rgb = *rgbp;
      RGBGAMMACORRECT(corrected_rgb);
      *p++ = (GLubyte)(corrected_rgb.r * 255.);
      *p++ = (GLubyte)(corrected_rgb.g * 255.);
      *p++ = (GLubyte)(corrected_rgb.b * 255.);
      *p++ = 255;			/* alpha = 1.0 */
    }
  }

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0, Camera.hres, 0, Camera.vres, -1.0, 1.0);

  glDisable(GL_DEPTH_TEST);

  glPixelStorei(GL_UNPACK_ALIGNMENT, 8);
  glRasterPos2i(x, y);
  glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_BYTE, c);

#ifdef OSLINUX
  /* Some linux drivers do not display immediately without the following lines */
  glFlush();
  glFinish();

  if (doublebuf)
    glXSwapBuffers(glx_display,glx_window);
#endif

  glEnable(GL_DEPTH_TEST);

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();


  Free((char *)c1, rowlen * height + 8);
}

void SaveScreen(char *fname, FILE *fp, int ispipe)
{
  ImageOutputHandle *img;
  long j, x=Camera.hres, y=Camera.vres;
  GLubyte *screen;
  unsigned char *buf;

  /* RayCast() saves the current picture in display-mapped (!) real
     values. (jp) */

  if (renderopts.trace) {
    RayCast(fname,fp,ispipe);
    return;
  }

  if (!fp || !(img = CreateImageOutputHandle(fname, fp, ispipe, x, y)))
    return;

  screen = (GLubyte *)Alloc((int)(x * y) * sizeof(GLubyte) * 4);
  buf = (unsigned char *)Alloc(3 * x);

  glReadBuffer(GL_FRONT);
  glReadPixels(0,  0, x, y, GL_RGBA, GL_UNSIGNED_BYTE,  screen);
  
  for (j=y-1; j>=0; j--) {
    long i;
    unsigned char *pbuf = buf;
    GLubyte *pixel = &screen[j*x*4];
    for (i=0; i<x; i++, pixel += 4) {
      *pbuf++ = pixel[0];
      *pbuf++ = pixel[1];
      *pbuf++ = pixel[2];
    }
    WriteDisplayRGB(img, buf);
  }

  Free((char *)buf, 3 * (int)x);
  Free((char *)screen, (int)(x * y) * 4 * sizeof(GLubyte));

  DeleteImageOutputHandle(img);
}

void SaveDepth(char *fname, FILE *fp, int ispipe)
{
  ImageOutputHandle *img;
  long j, x=Camera.hres, y=Camera.vres;
  GLfloat *screen;
  unsigned char *buf;

  if (!fp || !(img = CreateImageOutputHandle(fname, fp, ispipe, x, y)))
    return;

  screen = (GLfloat *)Alloc((int)(x * y) * sizeof(GLfloat));
  buf = (unsigned char *)Alloc(3 * x);

  glReadBuffer(GL_DEPTH);
  glReadPixels(0,  0, x, y, GL_DEPTH_COMPONENT, GL_FLOAT, screen);
  
  for (j=y-1; j>=0; j--) {
    long i;
    unsigned char *pbuf = buf;
    GLfloat *pixel = &screen[j*x];
    for (i=0; i<x; i++, pixel ++) {
      GLubyte val = (GLubyte)(*pixel * 255.);
      *pbuf++ = val;
      *pbuf++ = val;
      *pbuf++ = val;
    }
    WriteDisplayRGB(img, buf);
  }

  Free((char *)buf, 3 * x);
  Free((char *)screen, (int)(x * y) * sizeof(GLfloat));

  DeleteImageOutputHandle(img);
}

static void circ(float x, float y, float r)
{
  int i;
  float t;

  glBegin(GL_LINE_LOOP);
  for (i=0, t=0.; i<8; i++, t+=M_PI * 0.25)
    glVertex3f(x + r*cos(t), y + r*sin(t), 0.);
  glEnd();
}

void RenderPoint(VECTOR *p)
{
  VECTOR d;
  float x, y, z;

  VECTORSUBTRACT(*p, Camera.eyep, d);

  z = VECTORDOTPRODUCT(d, Camera.Z);
  x = (VECTORDOTPRODUCT(d, Camera.X)/z / tan(Camera.hfov * M_PI/180.) + 1.) * (float)(Camera.hres) * 0.5;
  y = (1. - VECTORDOTPRODUCT(d, Camera.Y)/z / tan(Camera.vfov * M_PI/180.)) * (float)(Camera.vres) * 0.5;

  if((z >= Camera.near) && (z <= Camera.far))
  {
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0.0, Camera.hres, 0.0, Camera.vres, -1.0, 1.0);
    
    glDisable(GL_DEPTH_TEST);
    
    circ(x, y, 3.);
    
    glEnable(GL_DEPTH_TEST);
    
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
  }
}

#ifdef SOFT_ID_RENDERING
#include "softids.h"

unsigned long *RenderIds(long *x, long *y)
{
  return SoftRenderIds(x, y);
}

#else /*SOFT_ID_RENDERING*/

/* Here's an attempt to make ID rendering work with <24 bit frame buffers,
 * but I believe it failed. To be investigated some time. */
static unsigned firstbit, nr, ng, nb, rmask, gmask, bmask, cr, cg, cb;
static int nrpasses, pass;

static void RenderPatchID(PATCH *patch)
{
  unsigned red, green, blue;
  unsigned long id = (unsigned long)(patch->id) >> firstbit;
  int i;

  /* SwitchBackfaceCulling(patch); */

  red = id & rmask; id >>= nr;
  green = id & gmask; id >>= ng;
  blue = id & bmask;
  glColor3ub(red<<cr, green<<cg, blue<<cb);
  /*
fprintf(stderr, "firstbit=%d, %lu -> (%u,%u,%u) -> %lu\n",
	firstbit, (unsigned long)(patch->id), red<<cr, green<<cg, blue<<cb,
        (unsigned long)((((blue << ng) | green) << nr) | red) << firstbit);
	*/

  switch (patch->nrvertices) {
  case 3:
    glBegin(GL_TRIANGLES);
    glVertex3fv((GLfloat *)patch->vertex[0]->point);
    glVertex3fv((GLfloat *)patch->vertex[1]->point);
    glVertex3fv((GLfloat *)patch->vertex[2]->point);
    glEnd();
    break;
  case 4:
    glBegin(GL_QUADS);
    glVertex3fv((GLfloat *)patch->vertex[0]->point);
    glVertex3fv((GLfloat *)patch->vertex[1]->point);
    glVertex3fv((GLfloat *)patch->vertex[2]->point);
    glVertex3fv((GLfloat *)patch->vertex[3]->point);
    glEnd();
    break;
  default:
    glBegin(GL_POLYGON);
    for (i=0; i<patch->nrvertices; i++)
      glVertex3fv((GLfloat *)patch->vertex[i]->point);
    glEnd();
  }
}

static void RenderIdPass(GLubyte *screen, unsigned long *ids,
			 int x, int y)
{
  GLubyte *pixel;
  unsigned long *id;
  long i, nrpixels = x*y;

  RenderSetCamera();

  glShadeModel(GL_FLAT);

  if (renderopts.backface_culling)
    glEnable(GL_CULL_FACE);
  else
    glDisable(GL_CULL_FACE);

  if (renderopts.frustum_culling) {
    int use_display_lists = renderopts.use_display_lists;
    renderopts.use_display_lists = FALSE;	/* temporarily switch it off */
    RenderWorldOctree(RenderPatchID);
    renderopts.use_display_lists = use_display_lists;    
  } else
    PatchListIterate(Patches, RenderPatchID);

  glFinish();

  if (doublebuf)
    glReadBuffer(GL_BACK);
  glReadPixels(0, 0, x, y, GL_RGBA, GL_UNSIGNED_BYTE, screen);

  for (i=0, pixel=screen, id=ids; i<nrpixels; i++, pixel+=4, id++) {
    unsigned long
      red=pixel[0] >> cr, 
      green=pixel[1] >> cg, 
      blue=pixel[2] >> cb;
    (*id) |= ((((blue << ng) | green) << nr) | red) << firstbit;    
  }
}

unsigned long *RenderIds(long *x, long *y)
{
  unsigned long *ids = (unsigned long *)NULL;
  GLubyte *screen;
  GLint r, g, b;
  long maxpatchid = PatchGetNextID()-1;
  GLint current_draw_buffer = GL_FRONT;

  if (doublebuf) {
    glGetIntegerv(GL_DRAW_BUFFER, &current_draw_buffer);
    glDrawBuffer(GL_BACK);
  }

  glDisable(GL_DITHER);

  *x = Camera.hres; *y = Camera.vres;
  ids = (unsigned long *)Alloc((int)(*x) * (int)(*y) * sizeof(unsigned long));
  memset(ids, 0, (int)(*x) * (int)(*y) * sizeof(unsigned long));
  screen = (GLubyte *)Alloc((int)(*x) * (int)(*y) * sizeof(GLubyte) * 4);

  glGetIntegerv(GL_RED_BITS, &r);
  glGetIntegerv(GL_GREEN_BITS, &g);
  glGetIntegerv(GL_BLUE_BITS, &b);
  nr = r>8?8:r;
  ng = g>8?8:g; 
  nb = b>8?8:b;
  cr = 8-nr, cg = 8-ng, cb = 8-nb; 
  rmask = (1<<nr)-1; gmask = (1<<ng)-1; bmask = (1<<nb)-1;

  nrpasses = maxpatchid / (1<<(nr+ng+nb)) + 1;
  for (pass=0; pass<nrpasses; pass++) {
    firstbit = pass * (nr+ng+nb);
    RenderIdPass(screen, ids, *x, *y);
  }

  Free((char *)screen, (int)(*x) * (int)(*y) * sizeof(GLubyte) * 4);

  glEnable(GL_DITHER);

  if (doublebuf)
    glDrawBuffer(current_draw_buffer);

  return ids;
}
#endif /*SOFT_ID_RENDERING*/

static void RenderFrustum(CAMERA *cam)
{
  POINT c, P, Q;
  float camlen = renderopts.camsize,
        hsiz = camlen * cam->viewdist * cam->tanhfov, 
        vsiz = camlen * cam->viewdist * cam->tanvfov;
  int i, j, maxi = 12, maxj = (int)((float)maxi * vsiz/hsiz);

  VECTORCOMB2(1., cam->eyep, camlen * cam->viewdist, cam->Z, c);

  glDisable(GL_DEPTH_TEST);

  RenderSetColor(&renderopts.camera_color);

  for (i=0; i<=maxi; i++) {
    VECTORCOMB3(c, (-1. + 2. * ((float)i/(float)maxi)) * hsiz, cam->X, -vsiz, cam->Y, P);
    VECTORCOMB3(c, (-1. + 2. * ((float)i/(float)maxi)) * hsiz, cam->X, +vsiz, cam->Y, Q);
    RenderLine(&P, &Q);
  }

  for (j=0; j<=maxj; j++) {
    VECTORCOMB3(c, -hsiz, cam->X, (-1. + 2. * ((float)j/(float)maxj)) * vsiz, cam->Y, P);
    VECTORCOMB3(c, +hsiz, cam->X, (-1. + 2. * ((float)j/(float)maxj)) * vsiz, cam->Y, Q);
    RenderLine(&P, &Q);
  }

  VECTORCOMB3(c,  hsiz, cam->X, -vsiz, cam->Y, Q);
  RenderLine(&cam->eyep, &Q);
  VECTORCOMB3(c, -hsiz, cam->X, -vsiz, cam->Y, Q);
  RenderLine(&cam->eyep, &Q);
  VECTORCOMB3(c, -hsiz, cam->X,  vsiz, cam->Y, Q);
  RenderLine(&cam->eyep, &Q);
  VECTORCOMB3(c,  hsiz, cam->X,  vsiz, cam->Y, Q);
  RenderLine(&cam->eyep, &Q);

  glLineWidth(1);
  glEnable(GL_DEPTH_TEST);
}

extern CAMERA AlternateCamera;
void RenderCameras(void)
{
  RenderFrustum(&AlternateCamera);
}

void RenderBackground(CAMERA *cam)
{
  /* turn of Z-buffer */
  glDisable(GL_DEPTH_TEST);

  /* push matrix and create bogus cam */
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
    
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluOrtho2D(0, cam->hres, 0, cam->vres);

  /* draw background (as texture) */
  glEnable(GL_TEXTURE_2D);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
  glBindTexture(GL_TEXTURE_2D, backgroundTex);
  glBegin(GL_QUADS);
  glTexCoord2f(0.0, 1.0); glVertex3f(0        , 0        , 0.0);
  glTexCoord2f(1.0, 1.0); glVertex3f(cam->hres, 0        , 0.0);
  glTexCoord2f(1.0, 0.0); glVertex3f(cam->hres, cam->vres, 0.0);
  glTexCoord2f(0.0, 0.0); glVertex3f(0        , cam->vres, 0.0);  
  glEnd();
  glDisable(GL_TEXTURE_2D);

  /* pop matrix */
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  
  /* enable Z-buffer back */
  glEnable(GL_DEPTH_TEST);
}


void skipComment(FILE *fp)
{
  char b = fgetc(fp);

  while(b == '#')
    {
      while (b != '\n')
        b = fgetc(fp);

      b = fgetc(fp);
    }
  ungetc(b, fp);
}

int InitBackground(FILE *fp)
{
  int width, height;
  int width2, height2;
  int colorsize, r;
  float pow1, pow2;
  long mem_needed;
  void *map;

  // load image (ppm)
  if (fp) {
    // read header (P6)
    fscanf(fp, "P6\n");
    
    // skip comment (#)
    skipComment(fp);
    
    // read dimensions
    r = fscanf(fp, "%d %d\n%d\n", &width, &height, &colorsize);
    if (r != 3) {
      Error(NULL, "Not a vallid PPM file");
      return FALSE;    
    }

    // alloc memory
    mem_needed = width * height * 3;
    map = malloc(mem_needed);

    // load map
    r = fread(map, 1, mem_needed, fp);
    if (r != mem_needed) {
      free(map);
      Error(NULL, "Not a vallid PPM file");
      return FALSE;
    }

    // convert to openGL convenient format
    pow1 = ceil(log((float)(width)) / log(2.));
    pow2 = ceil(log((float)(height)) / log(2.));

    if(pow1 > 10.) pow1 = 10.;
    if(pow2 > 10.) pow1 = 10.;

    width2 = (int)(pow(2, pow1));
    height2 = (int)(pow(2,pow2));

    mem_needed = width2 * height2 * 3;

    if(background_ptr) free(background_ptr);
    background_ptr = malloc(mem_needed);

    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
    glPixelStorei(GL_PACK_SKIP_ROWS, 0);
    glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
    gluScaleImage(GL_RGB, width , height , GL_UNSIGNED_BYTE, map,
		  width2, height2, GL_UNSIGNED_BYTE, (void *)(background_ptr));
    
    // free load map
    free(map);

    // register as texture
    glGenTextures(1, &backgroundTex);
    glBindTexture(GL_TEXTURE_2D, backgroundTex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width2, height2, 0, GL_RGB, GL_UNSIGNED_BYTE, background_ptr);

    return TRUE;
  }
  else return FALSE;
}
