/* gl.c: rendering interface for Iris GL and clones */

#include <Xm/Xm.h>
#include <Xm/DrawingA.h>

#include <gl/glws.h>

/* Iris GL defines a constant named MATERIAL while we have a type called MATERIAL */
#define GL_MATERIAL MATERIAL
#undef MATERIAL

/* idem for our function called Scale() in transform.h */
#define GL_Scale Scale
#undef Scale

#include "render.h"
#include "canvas.h"
#include "camera.h"
#include "error.h"
#include "pools.h"
#include "scene.h"
#include "radiance.h"
#include "renderhook.h"
#include "image.h"

static XVisualInfo *gl_vinfo;	/* describes visual to be used for rendering */

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

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

static int glinited = False;	/* set to True when GL is initialized on the window. 
				 * No drawing into the window should be allowed before 
				 * this variable is set to True in GLinitCallback(). */

static long zmax;	/* Z value to clear Z buffer with */

/* identity matrix */
static const Matrix idmat = {{1.0, 0.0, 0.0, 0.0},
			     {0.0, 1.0, 0.0, 0.0},
			     {0.0, 0.0, 1.0, 0.0},
			     {0.0, 0.0, 0.0, 1.0}};

/* GLX configuration info. See GLXgetconfig() manual page. */
static GLXconfig sdescin[] = {	/* GLXconfig given to XGLgetconfig as input parm */
#ifndef NO_DOUBLE_BUFFERING
    {GLX_NORMAL, GLX_DOUBLE	, True},	/* window with double buffering */
#else
    {GLX_NORMAL, GLX_DOUBLE	, False},	/* window without double buffering */
#endif
    {GLX_NORMAL, GLX_RGB	, True},	/* window in RGB colormode */
    {GLX_NORMAL, GLX_ZSIZE	, GLX_NOCONFIG},	/* max. nr of Z planes */
    {0         , 0		, 0}
}, *sdescout=NULL;			/* GLXconfig returned by GLXgetconfig */

/* finds the visual and colormap to be used for Iris GL rendering on the specified 
 * screen */
Boolean RenderGetVisualInfoAndColormap(Screen *screen,
				       XVisualInfo *visual_info,
				       Colormap *colormap)
{
  int scrno, count;
  XVisualInfo vinfo_template;
  GLXconfig *psdesc;
  Display *dpy;

  dpy = DisplayOfScreen(screen);
  /* find screen number of screen on display */
  for (scrno=0; scrno < ScreenCount(dpy); scrno++) {
    if (ScreenOfDisplay(dpy, scrno) == screen)
      break;
  }
  if (scrno >= ScreenCount(dpy)) {
    Fatal(1, "RenderGetVisualInfoAndColormap", "screen not found on its display - what a nonsense!");
  }

  /* returns information about the visual and colormaps to be used for rendering */
  sdescout = GLXgetconfig(dpy, scrno, sdescin);
  if (!sdescout) {
    Fatal(1, "RenderGetVisualInfoAndColormap", "Could not obtain GLX config info\n");
  }

  psdesc = sdescout;
  while (psdesc->buffer != 0) {
    switch (psdesc->buffer) {
    case GLX_NORMAL:
      switch (psdesc->mode) {
      case GLX_COLORMAP:
	*colormap = (Colormap)psdesc->arg;
	break;
      case GLX_VISUAL:
	vinfo_template.visualid = (VisualID)psdesc->arg;
	vinfo_template.screen = scrno;
	vinfo_template.class = TrueColor;
	/* find the XVisualInfo for this visual ID */
	gl_vinfo = XGetVisualInfo(dpy, VisualIDMask|VisualScreenMask|VisualClassMask, &vinfo_template, &count);
	if (!gl_vinfo) 
	  Fatal(1, "RenderGetVisualInfoAndColormap", "couldn't find visual info for visual id %d\n", vinfo_template.visualid);
	*visual_info = *gl_vinfo;
	break;
      default:
	break;
      }
      break;
    default:
      break;
    }
    psdesc++;
  }

  return True;		/* non-default visual required */
}

/* Prepare for rendering in the given window. */
void RenderInitWindow(Display *display, Window window, XVisualInfo *visinfo)
{
  float lmprops[4];
  GLXconfig *psdesc;

  if (!sdescout) {
    /* external canvas window, check window visual and set colormap. */
    XWindowAttributes wattr;
    XVisualInfo gl_vinfo;
    Colormap cmap;

    XGetWindowAttributes(display, window, &wattr);
    RenderGetVisualInfoAndColormap(wattr.screen, &gl_vinfo, &cmap);

    if (gl_vinfo.visualid != visinfo->visualid)
      Fatal(1, NULL, "The external canvas has wrong visualID (0x%02x, should be 0x%02)",
	    visinfo->visualid, gl_vinfo.visualid);

    XSetWindowColormap(display, window, cmap);
  }

  /* fill in the window IDs in the arg field of description items with
   * mode GLX_WINDOW */
  for (psdesc = sdescout; psdesc->buffer != 0; psdesc++) {
    if (psdesc->mode == GLX_WINDOW)
      psdesc->arg = window;
  }

  /* prepare for drawing onto the display */
  if (GLXlink(display, sdescout) < 0)
    Fatal(1, "GLinitCallback", "Graphics initialisation failed: nonzero return code from GLXlink()");

  /* all subsequent GL drawing will happen in the canvas window */
  if (GLXwinset(display, window) < 0)
    Fatal(1, "RenderCreateWindow", "Graphics initialisation failed: nonzero return code from GLXwinset()");

  zfunction(ZF_LEQUAL);
  zmax = getgdesc(GD_ZMAX);  	/* value to clear the Z buffer with */

  zbuffer(1);			/* switch Z buffering on */
  frontbuffer(1);		/* everything that is drawn outside RenderScene()
				 * should be visible immediately, so enable rendering
				 * in the frontbuffer. */

  /* shading model is very simple: there is only an ambient light source with
   * intensity (1,1,1) */
  lmprops[0] = AMBIENT;
  lmprops[1] = 1.0;
  lmprops[2] = 1.0;
  lmprops[3] = 1.0;
  lmdef(DEFLMODEL, 1, 4, lmprops);
  lmbind(LMODEL, 1);

  /* the glinited variable indicates whether or not GL has been initialized */
  glinited = True;
}

/* this expose callback function is invoked the first time the canvas widget is
 * exposed. It initializes Iris Gl for rendering into the canvas window. */
static void GLinitCallback(Widget canvas, XtPointer client_data, XtPointer call_data)
{
  RenderInitWindow(XtDisplay(canvas), XtWindow(canvas), gl_vinfo);

  /* remove the GLinitCallback: it needs to be executed just once: when the widget
   * is exposed for the first time. */
  XtRemoveCallback(canvas, XmNexposeCallback, GLinitCallback, (XtPointer)NULL);
}

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

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

  /* initialize GL on first expose event */
  glinited = False;
  XtAddCallback(canvas, XmNexposeCallback, GLinitCallback, (XtPointer)NULL);

  return canvas;
}

void RenderCreateOffscreenWindow(int hres, int vres)
{
  Fatal(1, NULL, "Offscreen rendering is only supported through the Mesa library.\nRe-compile the program using the Mesa library if you want it");
}

int RenderInitialized(void)
{
  return glinited;
}

/* clear the canvas window and Z buffer */
void RenderClearWindow(void)
{
  long bkgcolor;

  bkgcolor =  (unsigned char)(Camera.background.r * 255.) |
             ((unsigned char)(Camera.background.g * 255.) << 8 ) |
             ((unsigned char)(Camera.background.b * 255.) << 16);

  czclear(bkgcolor, zmax);
}

/* up-direction can be anything, not just (0,1,0) */
static void RenderFixUpTransform(void)
{
  float d, t;
  VECTOR up=Camera.updir;
  VectorNormalize(&up);

/* push the transforms in reverse order */
  t = acos(up.y) * 180. / M_PI;
  rot(t, 'z');

  d = 1. - up.y*up.y;
  if (d > EPSILON) {
    t = acos(up.x/sqrt(d)) * 180. / M_PI;
    if (up.z < 0.)
      t = -t;
    rot(t, 'y');
  }
}

static void RenderFixUpPoint(VECTOR *src, VECTOR *dest)
{
  VECTOR tmp;
  float d, t;
  VECTOR up=Camera.updir;
  VectorNormalize(&up);

  d = 1. - up.y*up.y;
  if (d > EPSILON) {
    t = acos(up.x/sqrt(d));
    if (up.z < 0.)
      t = -t;

/*	rot(t, 'y'); */
    tmp.z = cos(t) * src->z - sin(t) * src->x;
    tmp.x = sin(t) * src->z + cos(t) * src->x;
    tmp.y = src->y;
  } else
    tmp = *src;

  t = acos(up.y);
/*	rot(t, 'z'); */
  dest->x = cos(t) * tmp.x - sin(t) * tmp.y;
  dest->y = sin(t) * tmp.x + cos(t) * tmp.y;
  dest->z = tmp.z;
}

/* passes the current virtual camera position, focus point, fov etc...
 * to the graphics hardware */
void RenderSetCamera(void)
{
  VECTOR eyep, lookp;

  RenderClearWindow();

  loadmatrix(idmat);	/* something valid on the matrix stack at least */

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

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

  /* x2 because Iris GL has another opinion about what fov is and x 10 because
   * perspective wants angles in tents of degrees: fov=450 -> 45 degrees */
  perspective(Camera.vfov*20., (float)Camera.hres/(float)Camera.vres,
	      Camera.near, Camera.far); 

  /* GL assumes that up is along the positive Y axis, convert our idea of up */
  RenderFixUpPoint(&Camera.eyep, &eyep);
  RenderFixUpPoint(&Camera.lookp, &lookp);

  lookat(eyep.x, eyep.y ,eyep.z,
	 lookp.x, lookp.y, lookp.z,
	 0.0);

  RenderFixUpTransform();
}

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

  corrected_rgb = *rgb;
  RGBGAMMACORRECT(corrected_rgb, renderopts.gamma);
  c3f((float *)&corrected_rgb);
}

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

  bgnpolygon();
  for (i=0; i<nrverts; i++) 
    v3f((float *)&verts[i]);
  endpolygon();
}

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

  bgnpolygon();
  for (i=0; i<nrverts; i++) {
    RenderSetColor(&vertcols[i]);
    v3f((float *)&verts[i]);
  }
  endpolygon();
}

void RenderBeginTriangleStrip(void)
{
  bgntmesh();
}

void RenderNextTrianglePoint(POINT *point, RGB *col)
{
  if(col)
    RenderSetColor(col);
  v3f((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]);
    v3f((float *)(&point[i]));  
  }
}

void RenderEndTriangleStrip(void)
{
  endtmesh();
}

/* renders the patch as a solid polygon, flat shaded with its computed color */
void RenderPatchFlat(PATCH *patch)
{
  int i;

  RenderSetColor(&patch->color);
  bgnpolygon();
  for (i=0; i<patch->nrvertices; i++) 
    v3f((float *)patch->vertex[i]->point);
  endpolygon();
}

/* renders the patch as a solid polygon, smooth shaded using the colors computed
 * for its vertices */
void RenderPatchSmooth(PATCH *patch)
{
  int i;

  bgnpolygon();
  for (i=0; i<patch->nrvertices; i++) {
    RenderSetColor(&patch->vertex[i]->color);
    v3f((float *)patch->vertex[i]->point);
  }
  endpolygon();
}

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

  if (renderopts.backface_culling) {
    /* don't draw outlines of backfacing polygons. */
    VECTORSUBTRACT(Camera.eyep, patch->midpoint, dir);
    if (VECTORDOTPRODUCT(dir, patch->normal) < 0.)
      return;
  }

  bgnclosedline();
  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);
    v3f((float *)&tmp);
  }
  endclosedline();
}

/* renders a patch */
void RenderPatch(PATCH *patch)
{
  if (renderopts.smooth_shading)
    RenderPatchSmooth(patch);
  else
    RenderPatchFlat(patch);

  if (renderopts.draw_outlines) {
    RenderSetColor(&renderopts.outline_color);
    RenderPatchOutline(patch);
  }
}

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

  renderopts.render_raytracing_image = FALSE;
}

static void RenderRadiance(void)
{
  RenderSetCamera();

  backface(renderopts.backface_culling);

  if (renderopts.smooth_shading)
    shademodel(GOURAUD);
  else
    shademodel(FLAT);

  if (renderopts.use_display_lists) {
    if (dlistid < 0) {
      dlistid = 1;
      makeobj(dlistid);

      if (!Radiance || !Radiance->RenderScene) {
	PatchListIterate(Patches, RenderPatch);
      } else
	Radiance->RenderScene();

      closeobj();
    }
    callobj(dlistid);
  } else {
    if (!Radiance || !Radiance->RenderScene) {
      PatchListIterate(Patches, RenderPatch);
    } else
      Radiance->RenderScene();
  }

  if (renderopts.draw_bounding_boxes)
    RenderBoundingBoxHierarchy();

  if (renderopts.draw_clusters)
    RenderClusterHierarchy();

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

  RenderHooks();
}

/* renders the whole scene */
void RenderScene(void)
{
#ifdef NEVER
  clock_t t = clock();
#endif

  /* don't draw if GL has not yet been initialized for drawing into the canvas window */
  if (!glinited)
    return;

  CanvasPushMode(CANVASMODE_RENDER);

  backbuffer(1);
  frontbuffer(0);	/* render in the backbuffer only */

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

  if (!renderopts.render_raytraced_image || !RenderRayTraced())
    RenderRadiance();

  swapbuffers(); 

  backbuffer(0);
  frontbuffer(1);	/* everything that is rendered elsewhere is expected to
			 * be visible immediately */

  CanvasPullMode();
#ifdef NEVER
  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
}

void RenderFinish(void)
{
  finish();
}

/* 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 n, int m, RGB *rgb)
{
  int i;
  unsigned long *c;
  RGB corrected_rgb;

  c = (unsigned long *)Alloc(n * m * sizeof(unsigned long));

  for (i=0; i<n * m; i++) {
    corrected_rgb = rgb[i];
    RGBGAMMACORRECT(corrected_rgb, renderopts.gamma);
    c[i] =  ((unsigned char)(corrected_rgb.r * 255.)) |
           (((unsigned char)(corrected_rgb.g * 255.)) << 8) |
           (((unsigned char)(corrected_rgb.b * 255.)) << 16);
  }

  lrectwrite(x, y, x+n-1, y+m-1, c);

  Free((char *)c, n * m * sizeof(unsigned long));
}

/* renders a line from point p to point q, used for eg debugging */
void RenderLine(POINT *p, POINT *q)
{
  VECTOR dir, tmp;

  VECTORSUBTRACT(Camera.eyep, *p, dir);
  VECTORSUMSCALED(*p, 0.01, dir, tmp);
  move(tmp.x, tmp.y, tmp.z);

  VECTORSUBTRACT(Camera.eyep, *q, dir);
  VECTORSUMSCALED(*q, 0.01, dir, tmp);
  draw(tmp.x, tmp.y, tmp.z);
}

void RenderAALine(POINT *p, POINT *q)
{
  /* To implement */
  RenderLine(p,q);
}

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))
  {  
    zbuffer(0);
    mmode(MPROJECTION);
    pushmatrix();
    ortho2(-0.5, (float)Camera.hres+0.5, -0.5, (float)Camera.vres+0.5);
    mmode(MVIEWING);
    pushmatrix();
    loadmatrix(idmat);

    circ(x, y, 3);
    
    mmode(MVIEWING);
    popmatrix();
    mmode(MPROJECTION);
    popmatrix();
    zbuffer(1);
  }
}

/* reads a GL drawing buffer */
unsigned long *GetScreenBuffer(long *x, long *y)
{
  unsigned long *screen;
  
  getsize(x, y);
  screen = (unsigned long *)Alloc((*x) * (*y) * sizeof(unsigned long));
  lrectread(0, 0, *x-1, *y-1, screen);
  return screen;
}

/* saves the image in the front buffer to a ppm file, viewable with e.g. xv */
void SaveScreen(char *fname, FILE *fp, int ispipe)
{
  ImageOutputHandle *img;
  unsigned long *screen;
  long i, j, x, y;
  unsigned char *buf;

  readsource(SRC_FRONT);
  screen = GetScreenBuffer(&x, &y);

  if (!fp || !(img = CreateImageOutputHandle(fname, fp, ispipe, x, y)))
    return;
  buf = (unsigned char *)Alloc(3 * x);
  
  for (j=y-1; j>=0; j--) {
    long *pixel = screen+j*x;
    unsigned char *fbuf = buf;
    for (i=0; i<x; i++, pixel++) {
      *fbuf++ = (unsigned char)((*pixel)&0xff);
      *fbuf++ = (unsigned char)(((*pixel)&0xff00)>>8);
      *fbuf++ = (unsigned char)(((*pixel)&0xff0000)>>16);
    }
    WriteDisplayRGB(img, buf);
  }

  Free((char *)buf, 3 * x);
  Free((char *)screen, x * y * sizeof(unsigned long));

  DeleteImageOutputHandle(img);
}

/* for determining directly received importance etc... */
static void RenderPatchID(PATCH *patch)
{
  int i;

  cpack(((unsigned long)patch->id)&0xffffff); 
  bgnpolygon();
  for (i=0; i<patch->nrvertices; i++) 
    v3f((float *)patch->vertex[i]->point);
  endpolygon();
}

/* Patch ID rendering. Returns an array of size (*x)*(*y) containing the IDs of
 * the patches visible through each pixel or 0 if the background is visible through 
 * the pixel. x and y, determining the size of the array are returned. */
unsigned long *RenderIds(long *x, long *y)
{
  unsigned long *ids;

  *x = *y = 0;

  backbuffer(1);
  frontbuffer(0);	/* the user shouldn't notice it */

  RenderSetCamera();

  backface(renderopts.backface_culling);
  shademodel(FLAT);

  PatchListIterate(Patches, RenderPatchID);

  /* indicate that we want to read from the back buffer */
  readsource(SRC_BACK); 
  ids = GetScreenBuffer(x, y);

  backbuffer(0);
  frontbuffer(1);	/* render in the frontbuffer only again */

  return ids;
}







