/* starbase.c: graphics driver for HP/starbase. Tested only with a CRX48Z
 * graphics adapter.  */

/* define the DLISTS symbol if you want to compile in display list support.
 * I had problems with it: delete_segment and refresh_segment were not 
 * found during linking. Strange, isn't it ??? [PhB, 21 Aug 98] */
/* #define DLISTS */

#include <Xm/Xm.h>
#include <Xg/Starbase.h>

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

/* starbase file descriptor and nr of passes needed to render something */
static int fildes=-1, passes, buf;
static int sbinited = FALSE;
static int dlistid = -1;

/* 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)
{
  return False;		/* no non-default visual required */
}

/* starbase initialisation */
void RenderInitWindow(Display *display, Window window, XVisualInfo *visinfo)
{
  if (fildes < 0) {
    /* This will e.g. be the case for external canvas windows. 
     * Try to initialize starbase on it. */
    char *path = make_X11_gopen_string(display, window);
    fildes = gopen(path, OUTDEV, NULL, INIT|THREE_D|MODEL_XFORM|ACCELERATED);
    if (fildes < 0) {
      /* really can't open starbase,  sorry! */
      Error(NULL, "Error initializing starbase:\n%s", strerror(errno));
      return;
    }
  }

  clip_indicator(fildes,CLIP_TO_VIEWPORT);
  clear_control(fildes,CLEAR_VIEWPORT);

  /* 24-bit colors with shading */
  shade_mode(fildes, CMAP_FULL|INIT, FALSE); 
  clear_control(fildes,CLEAR_DISPLAY_SURFACE|CLEAR_ZBUFFER);
  background_color(fildes, Camera.background.r, Camera.background.g, Camera.background.b);

  /* enable double buffering */
  double_buffer(fildes, TRUE|INIT, 24 /*bitplanes for each buffer*/);
  buf = 0;

  /* Use the hardware Z-buffer and backface culling if wanted. */
  passes = hidden_surface(fildes, TRUE, renderopts.backface_culling);

  sbinited = TRUE;
  dlistid = -1;
}

static void StarbaseInitCallback(Widget canvas, XtPointer client_data, XtPointer call_data)
{
  XtVaGetValues(canvas, XgNfildes, &fildes, NULL);
  RenderInitWindow(XtDisplay(canvas), XtWindow(canvas), NULL);
  XtRemoveCallback(canvas, XmNexposeCallback, StarbaseInitCallback, (XtPointer)NULL);
}

/* invoked when the canvas window is destroyed */
static void StarbaseTerminateCallback(Widget canvas, XtPointer client_data, XtPointer call_data)
{
  gclose(fildes);
}

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

  canvas = XtVaCreateManagedWidget("canvas",
				   xgStarbaseWidgetClass,
				   parent,
				   XgNshadeMode, XgCMAP_FULL,
				   XgNopenMode, XgTHREE_D|XgMODEL_XFORM,
				   NULL);

  /* initialize starbase on first expose event */
  sbinited = FALSE;
  fildes = -1;
  XtAddCallback(canvas, XmNexposeCallback, StarbaseInitCallback, (XtPointer)NULL);

  /* window destroy event */
  XtAddCallback(canvas, XmNdestroyCallback, StarbaseTerminateCallback, (XtPointer)NULL);

  return canvas;
}

/* creates an offscreen window for rendering */
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");
}

int RenderInitialized(void)
{
  return sbinited;
}

/* clear the canvas window and Z buffer */
void RenderClearWindow(void)
{
  hidden_surface(fildes, FALSE, renderopts.backface_culling);	
  vdc_extent(fildes, -0.5, -0.5, 0.0, (float)Camera.hres+0.5, (float)Camera.vres+0.5, 1.0);

  background_color(fildes, Camera.background.r, Camera.background.g, Camera.background.b);
  clear_view_surface(fildes);
  hidden_surface(fildes, TRUE, renderopts.backface_culling);
}

/* to make a right handed coordinate system */
static float right_handed_coord_matrix[4][4] = {
	{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} };

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

  RenderClearWindow();

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

  camera.camx = Camera.eyep.x;		
  camera.camy = Camera.eyep.y;
  camera.camz = -Camera.eyep.z;
  camera.refx = Camera.lookp.x;		
  camera.refy = Camera.lookp.y;
  camera.refz = -Camera.lookp.z;
  camera.upx =  Camera.updir.x;		
  camera.upy =  Camera.updir.y;
  camera.upz =  -Camera.updir.z;
  camera.front = Camera.near - Camera.viewdist;		
  camera.back = Camera.far - Camera.viewdist;
  camera.projection = CAM_PERSPECTIVE;	
  camera.field_of_view = Camera.vfov * 2;
  view_camera(fildes, &camera);		
 
  view_matrix3d(fildes, right_handed_coord_matrix, PRE_CONCAT_VW);
}

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

  corrected_rgb = *rgb;
  RGBGAMMACORRECT(corrected_rgb, renderopts.gamma);
  fill_color(fildes, corrected_rgb.r, corrected_rgb.g, corrected_rgb.b);
  perimeter_color(fildes, corrected_rgb.r, corrected_rgb.g, corrected_rgb.b);
}

/* Sets starbase vertex format according to whether flat or gouraud shading is
 * desired. */
typedef enum {UNDEFINED, FLAT, GOURAUD} SHADEMODEL;

static void ShadeModel(SHADEMODEL model)
{
  static int current_shademodel = UNDEFINED;
  if (model != current_shademodel) {
    switch (model) {
    case FLAT:
      vertex_format(fildes,
		    0,	/* no extra coordinates */
		    0,	/* no extra coordinates to be used*/
		    0,	/* rgb */
		    FALSE,	
		    CLOCKWISE);
      break;
    case GOURAUD:
      vertex_format(fildes,
		    3,	/* 3 extra coordinates */
		    3,	/* use 3 extra coordinates */
		    1,	/* rgb */
		    FALSE,	/* normal is determined automatically */
		    CLOCKWISE); /* vertex order - see manpage */
      break;
    default:
      Fatal(-1, "RenderSwicthMode", "Something dirty wrong here!");
    }
    current_shademodel = model;
  }
}

/* sets starbase interior style and draw perimeter flag if different than
previous time */
static void InteriorStyle(int style, int peri)
{
  static int current_style=-1, current_peri=-1;
  if (style!=current_style || peri!=current_peri) {
    interior_style(fildes, style, peri);
    current_style = style;
    current_peri = peri;
  }
}

void RenderBeginTriangleStrip(void)
{
  Error("RenderBeginTriangleStrip", "to be implemented");
}

void RenderNextTrianglePoint(POINT *point, RGB *col)
{
}

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

void RenderEndTriangleStrip(void)
{
}

/* renders a convex polygon flat shaded in the current color */
void RenderPolygonFlat(int nrverts, POINT *verts)
{
  InteriorStyle(INT_SOLID, FALSE);
  ShadeModel(FLAT);
  polygon3d(fildes, (float *)verts, nrverts, FALSE);
}

/* renders a convex polygon with Gouraud shading */
void RenderPolygonGouraud(int nrverts, POINT *verts, RGB *vertcols)
{
  int i;
  float clist[256][6];
  RGB corrected_rgb;

  InteriorStyle(INT_SOLID, FALSE);
  ShadeModel(GOURAUD);
  for (i=0; i<nrverts; i++) {
    clist[i][0] = verts[i].x;
    clist[i][1] = verts[i].y;
    clist[i][2] = verts[i].z;

    corrected_rgb = vertcols[i];
    RGBGAMMACORRECT(corrected_rgb, renderopts.gamma);
    clist[i][3] = corrected_rgb.r;
    clist[i][4] = corrected_rgb.g;
    clist[i][5] = corrected_rgb.b;
  }
  polygon3d(fildes, clist, nrverts, FALSE);
}

/* renders the patch as a solid polygon, flat shaded with its computed color */
static void DoRenderPatchFlat(PATCH *patch)
{
  int i;
  float clist[PATCHMAXVERTICES][3];
  RGB rgb;

  ShadeModel(FLAT);
  for (i=0; i<patch->nrvertices; i++) {
    clist[i][0] = patch->vertex[i]->point->x;
    clist[i][1] = patch->vertex[i]->point->y;
    clist[i][2] = patch->vertex[i]->point->z;
  }
  rgb = patch->color;
  RGBGAMMACORRECT(rgb, renderopts.gamma);
  fill_color(fildes, rgb.r, rgb.g, rgb.b);

  polygon3d(fildes, clist, patch->nrvertices, FALSE);
}

void RenderPatchFlat(PATCH *patch)
{
  InteriorStyle(INT_SOLID, FALSE);
  DoRenderPatchFlat(patch);
}

/* renders the patch as a solid polygon, smooth shaded using the colors computed
 * for its vertices */
static void DoRenderPatchSmooth(PATCH *patch)
{
  int i;
  float clist[PATCHMAXVERTICES][6];
  RGB corrected_rgb;

  ShadeModel(GOURAUD);
  for (i=0; i<patch->nrvertices; i++) {
    clist[i][0] = patch->vertex[i]->point->x;
    clist[i][1] = patch->vertex[i]->point->y;
    clist[i][2] = patch->vertex[i]->point->z;

    corrected_rgb = patch->vertex[i]->color;
    RGBGAMMACORRECT(corrected_rgb, renderopts.gamma);
    clist[i][3] = corrected_rgb.r;
    clist[i][4] = corrected_rgb.g;
    clist[i][5] = corrected_rgb.b;
  }
  polygon3d(fildes, clist, patch->nrvertices, FALSE);
}

void RenderPatchSmooth(PATCH *patch)
{
  InteriorStyle(INT_SOLID, FALSE);
  DoRenderPatchSmooth(patch);
}
  
/* renders the patch outline in the current color */
void RenderPatchOutline(PATCH *patch)
{
  InteriorStyle(INT_HOLLOW, TRUE);
  DoRenderPatchFlat(patch);
}

/* renders a patch */
void RenderPatch(PATCH *patch)
{
  if (renderopts.draw_outlines) {
    RenderSetColor(&renderopts.outline_color);
    InteriorStyle(INT_SOLID, TRUE);
  } else
    InteriorStyle(INT_SOLID, FALSE);

  if (renderopts.smooth_shading)
    DoRenderPatchSmooth(patch);
  else
    DoRenderPatchFlat(patch);
}

void RenderNewDisplayList(void)
{
#ifdef DLISTS
/* refresh_segment and delete_segment not found during linking !!! */
  if (dlistid >= 0)
    delete_segment(fildes, dlistid);
  dlistid = -1;
#else
  static int wgiv = 0;
  if (!wgiv) {
    Warning(NULL, "The starbase driver has been compiled without display list support");
    wgiv = 1;
  }
#endif

  renderopts.render_raytraced_image = FALSE;  
}

static void DoRenderScene(void)
{
  if (!Radiance || !Radiance->RenderScene) {
    if (renderopts.smooth_shading) {
      PatchListIterate(Patches, DoRenderPatchSmooth);
    } else {
      PatchListIterate(Patches, DoRenderPatchFlat);
    }
  } else
    Radiance->RenderScene();
}

static void RenderRadiance(void)
{
  hidden_surface(fildes, FALSE, renderopts.backface_culling);	
  double_buffer(fildes, TRUE, 24);
  passes = hidden_surface(fildes, TRUE, renderopts.backface_culling);

  RenderSetCamera();

  ShadeModel(renderopts.smooth_shading ? GOURAUD : FLAT);
  if (renderopts.draw_outlines) {
    RenderSetColor(&renderopts.outline_color);
    InteriorStyle(INT_SOLID, TRUE);
  } else
    InteriorStyle(INT_SOLID, FALSE);

#ifdef DLISTS
/* refresh_segment and delete_segment not found during linking !!! */
  if (renderopts.use_display_lists) {
    if (dlistid < 0) {
      dlistid = 1;
      open_segment(fildes, dlistid, FALSE, TRUE);
      DoRenderScene();
      close_segment(fildes);
    } else
      execute_segment(fildes, dlistid);	
  } else
#endif
    DoRenderScene();
    
  if (renderopts.draw_bounding_boxes)
    RenderBoundingBoxHierarchy();
  
  if (renderopts.draw_clusters)
    RenderClusterHierarchy();

  dbuffer_switch(fildes,buf++);

  hidden_surface(fildes, FALSE, renderopts.backface_culling);	
  double_buffer(fildes, TRUE|DFRONT, 24);
  passes = hidden_surface(fildes, TRUE, renderopts.backface_culling);
}

/* renders the whole scene */
void RenderScene(void)
{
  /* don't draw if starbase has not yet been initialized */
  if (!sbinited)
    return;

  CanvasPushMode(CANVASMODE_RENDER);

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

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

  CanvasPullMode();
}

void RenderFinish(void)
{
  /* to be implemented */
}

/* renders a line of n pixels starting at column x on row y, used eg while 
 * raytracing */
void RenderPixels(int x, int y, int n, int m, RGB *rgb)
{
  unsigned char *scan[3];
  int k, j;
  RGB corrected_rgb;

  scan[0] = (unsigned char *)Alloc(n*m);
  scan[1] = (unsigned char *)Alloc(n*m);
  scan[2] = (unsigned char *)Alloc(n*m);
  for (j=0; j<m; j++) {
    for (k=0; k<n; k++) {
      /* starbase wants the image down to up */
      corrected_rgb = rgb[(m-j-1)*n+k];
      RGBGAMMACORRECT(corrected_rgb, renderopts.gamma);
      scan[0][j*n+k] = corrected_rgb.r * 255.;
      scan[1][j*n+k] = corrected_rgb.g * 255.;
      scan[2][j*n+k] = corrected_rgb.b * 255.;
    }
  }

  for (j=0; j<3; j++) {
    /* 2-j because starbase stores pixels in blue,green,red order */
    bank_switch(fildes, 2-j, 0);
    dcblock_write(fildes, x, Camera.vres-y-m, n, m, scan[j], 0);
  }
  flush_buffer(fildes);

  Free((char *)scan[0], n*m); 
  Free((char *)scan[1], n*m); 
  Free((char *)scan[2], n*m);
}

/* renders a line from point p to point q, used for eg debugging */
void RenderLine(POINT *p, POINT *q)
{
  move3d(fildes, p->x, p->y, p->z);
  draw3d(fildes, q->x, q->y, q->z);
}

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

void SaveScreen(char *fname, FILE *fp, int ispipe)
{
  ImageOutputHandle *img;
  int i,j,k;
  unsigned char *scan[3], *buf;

  if (!fp || !(img = CreateImageOutputHandle(fname, fp, ispipe, Camera.hres, Camera.vres)))
    return;

  scan[0] = (unsigned char *)Alloc(Camera.hres);
  scan[1] = (unsigned char *)Alloc(Camera.hres);
  scan[2] = (unsigned char *)Alloc(Camera.hres);
  buf = (unsigned char *)Alloc(3 * Camera.hres);  

  for (i=0; i<Camera.vres; i++) {
    unsigned char *fbuf = buf;

    for (j=0; j<3; j++) {
      /* 2-j because starbase stores pixels in blue,green,red order */
      bank_switch(fildes, 2-j, 0);
      dcblock_read(fildes, 0, i, Camera.hres, 1, scan[j], 0);
    }

    for (k=0; k<Camera.hres; k++) {
      *fbuf++ = scan[0][k];
      *fbuf++ = scan[1][k];
      *fbuf++ = scan[2][k];
    }

    WriteDisplayRGB(img, buf);
  }

  Free((char *)buf, 3 * Camera.hres);
  Free((char *)scan[0], Camera.hres); 
  Free((char *)scan[1], Camera.hres); 
  Free((char *)scan[2], Camera.hres); 

  DeleteImageOutputHandle(img);
}

static unsigned long *GetIds(long *x, long *y)
{
  int i,j,k;
  unsigned char *scan[3];
  unsigned long *ids, *p;

  *x = Camera.hres;
  *y = Camera.vres;
  ids = (unsigned long *)Alloc((*x) * (*y) * sizeof(unsigned long));

  scan[0] = (unsigned char *)Alloc(Camera.hres);
  scan[1] = (unsigned char *)Alloc(Camera.hres);
  scan[2] = (unsigned char *)Alloc(Camera.hres);

  for (i=0, p=ids; i<Camera.vres; i++) {
    for (j=0; j<3; j++) {
      /* 2-j because starbase stores pixels in blue,green,red order */
      bank_switch(fildes, 2-j, 0);
      dcblock_read(fildes, 0, i, Camera.hres, 1, scan[j], 0);
    }
    for (k=0; k<Camera.hres; k++) 
      *p++ = (((scan[2][k] << 8) | scan[1][k]) << 8) | scan[0][k];
  }

  Free((char *)scan[0], Camera.hres); 
  Free((char *)scan[1], Camera.hres); 
  Free((char *)scan[2], Camera.hres); 

  return ids;
}

static void RenderPatchId(PATCH *patch)
{
  int i;
  float clist[PATCHMAXVERTICES][3];
  RGB rgb;

  rgb.r = (float)(((unsigned)patch->id      )&0xff) / 255.;
  rgb.g = (float)(((unsigned)patch->id >> 8 )&0xff) / 255.;
  rgb.b = (float)(((unsigned)patch->id >> 16)&0xff) / 255.;
  fill_color(fildes, rgb.r ,rgb.g, rgb.b);

  for (i=0; i<patch->nrvertices; i++) {
    clist[i][0] = patch->vertex[i]->point->x;
    clist[i][1] = patch->vertex[i]->point->y;
    clist[i][2] = patch->vertex[i]->point->z;
  }
  polygon3d(fildes, clist, patch->nrvertices, FALSE);
}

/* 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;

  /* don't draw if starbase has not yet been initialized */
  if (!sbinited)
    return (unsigned long *)NULL;

  CanvasPushMode(CANVASMODE_RENDER);

  hidden_surface(fildes, FALSE, renderopts.backface_culling);	
  double_buffer(fildes, TRUE, 24);
  passes = hidden_surface(fildes, TRUE, renderopts.backface_culling);

  RenderSetCamera();

  ShadeModel(FLAT);
  InteriorStyle(INT_SOLID, FALSE);

  PatchListIterate(Patches, RenderPatchId);

  dbuffer_switch(fildes,buf++);

  hidden_surface(fildes, FALSE, renderopts.backface_culling);	
  double_buffer(fildes, TRUE|DFRONT, 24);
  passes = hidden_surface(fildes, TRUE, renderopts.backface_culling);

  CanvasPullMode();

  ids = GetIds(x, y);

  RenderScene();

  return ids;
}


