/* scratch.c: scratch renderer routines. Used for handling intra-cluster visibility
 * with a Z-buffer visibility algorithm in software. */

#include "galerkinP.h"
#include "scratch.h"
#include "sgl.h"
#include "geom.h"
#include "cluster.h"

#ifdef DEBUG
#include "render.h"
#endif

/* create a scratch software renderer for various operations on clusters. */
void ScratchInit(void)
{
  gal.scratch = sglOpen(gal.scratch_fb_size, gal.scratch_fb_size);
  sglDepthTesting(TRUE);
}

/* terminates scratch rendering */
void ScratchTerminate(void)
{
  sglClose(gal.scratch);
}

/* src is a toplevel surface element. Render the corrsponding patch 
 * with pixel value a pointer to the element. Uses global variable
 * eyep for backface culling. */
static POINT eyep;
static void ScratchRenderElementPtr(ELEMENT *elem)
{
  PATCH *patch = elem->pog.patch;
  VECTOR v[4]; int i;

  /* Backface culling test: only render the element if it is turned towards
   * the current eye point */
  if (VECTORDOTPRODUCT(patch->normal, eyep) + patch->plane_constant < EPSILON)
    return;

  for (i=0; i<patch->nrvertices; i++)
    v[i] = *patch->vertex[i]->point;

  sglSetColor((SGL_PIXEL)elem);
  sglPolygon(patch->nrvertices, v);
}

/* Sets up an orthographic projection of the cluster as
 * seen from the eye. Renders the element pointers to the elements
 * in clus in the scratch frame buffer and returns pointer to a boundingbox 
 * containing the size of the virtual screen. The cluster clus nicely fits
 * into the virtual screen. */
float *ScratchRenderElementPtrs(ELEMENT *clus, POINT eye)
{
  POINT centre = ElementMidpoint(clus);
  VECTOR up = {0., 0., 1.}, viewdir;
  static BOUNDINGBOX bbx;
  TRANSFORM lookat;
  SGL_CONTEXT *prev_sgl_context;
  int vp_size;

  if (clus->id == gal.lastclusid && VECTOREQUAL(eye, gal.lasteye, EPSILON)) 
    return bbx;
  else {
    /* cache previously rendered cluster and eye point in order to
     * avoid rerendering the same situation next time. */
    gal.lastclusid = clus->id;
    gal.lasteye = eye;
  }

  VECTORSUBTRACT(centre, eye, viewdir);
  VECTORNORMALIZE(viewdir);
  if (fabs(VECTORDOTPRODUCT(up, viewdir)) > 1.-EPSILON)
    VECTORSET(up, 0., 1., 0.);
  lookat = LookAt(eye, centre, up);

  BoundsTransform(GeomBounds(clus->pog.geom), &lookat, bbx);

  prev_sgl_context = sglMakeCurrent(gal.scratch);
  sglLoadMatrix(Ortho(bbx[MIN_X], bbx[MAX_X], bbx[MIN_Y], bbx[MAX_Y], -bbx[MAX_Z], -bbx[MIN_Z]));
  sglMultMatrix(lookat);

  /* choose a viewport depending on the relative size of the smallest
   * surface element in the cluster to be rendered. */
  vp_size = (int)((bbx[MAX_X] - bbx[MIN_X]) * (bbx[MAX_Y] - bbx[MIN_Y]) / clus->minarea);
  if (vp_size > gal.scratch->width) vp_size = gal.scratch->width;
  if (vp_size < 32) vp_size = 32;
  sglViewport(0, 0, vp_size, vp_size);

  /* Render element pointers in the scratch frame buffer. */
  eyep = eye;	/* needed for backface culling test */
  sglClear((SGL_PIXEL)NULL, SGL_ZMAX);
  IterateOverSurfaceElementsInCluster(clus, ScratchRenderElementPtr);

  sglMakeCurrent(prev_sgl_context);
  return bbx;
}

#ifdef DEBUG
void ShowScratchBuffer(RGB bkgcol)
{
  int i, j;
  RGB rgb[gal.scratch->width];
  SGL_PIXEL *pix;

  for (j=0; j<gal.scratch->vp_height; j++) {
    pix = gal.scratch->fbuf + j * gal.scratch->width;
    for (i=0; i<gal.scratch->vp_width; i++, pix++) {
      ELEMENT *elem = (ELEMENT *)(*pix);
      if (elem) 
	RadianceToRGB(elem->radiance[0], &rgb[i]);
      else
	rgb[i] = bkgcol;
    }
    RenderPixels(0, gal.scratch->height-j-1, gal.scratch->vp_width, rgb);
  }
}
#endif /*DEBUG*/

/* After rendering element pointers in the scratch frame buffer, this routine 
 * computes the average radiance of the virtual screen. */
COLOR ScratchRadiance(void)
{
  int nonbkgrnd; 
  SGL_PIXEL *pix;
  COLOR rad;
  int i,j;

#ifdef DEBUG
  ShowScratchBuffer(Blue);
#endif

  COLORCLEAR(rad);
  nonbkgrnd = 0;
  for (j=0; j<gal.scratch->vp_height; j++) {
    pix = gal.scratch->fbuf + j * gal.scratch->width;
    for (i=0; i<gal.scratch->vp_width; i++, pix++) {
      ELEMENT *elem = (ELEMENT *)(*pix);
      if (elem) {
	if (gal.iteration_method == GAUSS_SEIDEL ||
	    gal.iteration_method == JACOBI) {
	  COLORADD(rad, elem->radiance[0], rad);
	} else {
	  COLORADD(rad, elem->unshot_radiance[0], rad);
	}
	nonbkgrnd ++;
      }
    }
  }
  if (nonbkgrnd > 0) {
    COLORSCALE(1./(double)(gal.scratch->vp_width*gal.scratch->vp_height), rad, rad);
  }
  return rad;
}

/* Computes the number of non background pixels. */
int ScratchNonBackgroundPixels(void)
{
  int nonbkgrnd; 
  SGL_PIXEL *pix;
  int i, j;

#ifdef DEBUG
  ShowScratchBuffer(Green);
#endif

  nonbkgrnd = 0;
  for (j=0; j<gal.scratch->vp_height; j++) {
    pix = gal.scratch->fbuf + j * gal.scratch->width;
    for (i=0; i<gal.scratch->vp_width; i++, pix++) {
      ELEMENT *elem = (ELEMENT *)(*pix);
      if (elem) 
	nonbkgrnd ++;
    }
  }
  return nonbkgrnd;
}

/* Counts the number of pixels occupied by each element. The result is
 * accumulated in the tmp field of the elements. This field should be
 * initialized to zero before. */
void ScratchPixelsPerElement(void)
{
  SGL_PIXEL *pix;
  int i, j;

#ifdef DEBUG
  ShowScratchBuffer(Yellow);
#endif
  for (j=0; j<gal.scratch->vp_height; j++) {
    pix = gal.scratch->fbuf + j * gal.scratch->width;
    for (i=0; i<gal.scratch->vp_width; i++, pix++) {
      ELEMENT *elem = (ELEMENT *)(*pix);
      if (elem)
	elem->tmp ++;
    }
  }
}

