/* raytrace.c,v 1.1.1.1 1995/02/27 07:38:51 explorer Exp */

/*
 * Copyright (C) 1989, 1991, Craig E. Kolb
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 *
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 */

#include "libcommon/common.h"
#include "rayshade.h"
#include "libsurf/atmosphere.h"
#include "libsurf/surface.h"
#include "libcommon/sampling.h"
#include "options.h"
#include "stats.h"
#include "raytrace.h"
#include "viewing.h"

#define UNSAMPLED	-1
#define SUPERSAMPLED	-2

typedef struct {
  Pixel *pix;			/* Pixel values */
  int *samp;			/* Sample number */
} Scanline;

static int have_initialized = 0; /* flag to only initialize once */
static Scanline scan0, scan1, scan2;
static Ray TopRay;		/* Top-level ray. */

static int  *SampleNumbers;
static void  RaytraceInit _PROTO((void));
static int   ExcessiveContrast _PROTO((int, Pixel *, Pixel*, Pixel *));

void         AdaptiveRefineScanline _PROTO((int y, Scanline *, Scanline *,
					    Scanline*));
void         FullySamplePixel _PROTO((int, int, Pixel *, int *));
void         FullySampleScanline _PROTO((int, Scanline *));
void         SingleSampleScanline _PROTO((int, Scanline *));
Float        SampleTime _PROTO((int));

Pixel WhitePix = {1., 1., 1., 1.};
Pixel BlackPix = {0., 0., 0., 0.};

/*
 * "Dither matrices" used to encode the 'number' of a ray that samples a
 * particular portion of a pixel.  Hand-coding is ugly, but...
 */
static int OneSample[1] = {0};
static int TwoSamples[4] = {
  0, 2,
  3, 1
};
static int ThreeSamples[9] = {
  0, 2, 7,
  6, 5, 1,
  3, 8, 4
};
static int FourSamples[16] = {
   0,  8,  2, 10,
  12,  4, 14,  6,
   3, 11,  1,  9,
  15,  7, 13,  5
};
static int FiveSamples[25] = {
   0,  8, 23, 17,  2,
  19, 12,  4, 20, 15,
   3, 21, 16,  9,  6,
  14, 10, 24,  1, 13,
  22,  7, 18, 11,  5
};
static int SixSamples[36] = {
   6, 32,  3, 34, 35,  1,
   7, 11, 27, 28,  8, 30,
  24, 14, 16, 15, 23, 19,
  13, 20, 22, 21, 17, 18,
  25, 29, 10,  9, 26, 12,
  36,  5, 33,  4,  2, 31
};
static int SevenSamples[49] = {
  22, 47, 16, 41, 10, 35,  4,
   5, 23, 48, 17, 42, 11, 29,
  30,  6, 24, 49, 18, 36, 12,
  13, 31,  7, 25, 43, 19, 37,
  38, 14, 32,  1, 26, 44, 20,
  21, 39,  8, 33,  2, 27, 45,
  46, 15, 40,  9, 34,  3, 28
};
static int EightSamples[64] = {
   8, 58, 59,  5,  4, 62, 63,  1,
  49, 15, 14, 52, 53, 11, 10, 56,
  41, 23, 22, 44, 45, 19, 18, 48,
  32, 34, 35, 29, 28, 38, 39, 25,
  40, 26, 27, 37, 36, 30, 31, 33,
  17, 47, 46, 20, 21, 43, 42, 24,
   9, 55, 54, 12, 13, 51, 50, 16,
  64,  2,  3, 61, 60,  6,  7, 57
};

void
raytrace()
{
  int y, *tmpsamp;
  Pixel *tmppix;
  Float usertime, systime, lasttime;

  /*
   * If this is the first frame, allocate scanlines, etc.
   */
  if (!have_initialized) {
    RaytraceInit();
    have_initialized = 1;
  }
  /*
   * The top-level ray TopRay always has as its origin the
   * eye position and as its medium NULL, indicating that it
   * is passing through a medium with index of refraction
   * equal to DefIndex.
   */
  TopRay.pos = Camera.pos;
  TopRay.media = (Medium *) NULL;
  TopRay.depth = 0;

  /*
   * Always fully sample the bottom and top rows and the left
   * and right column of pixels.  This minimizes artifacts that
   * may arise when piecing together images.
   */
  FullySampleScanline(0, &scan0);

  SingleSampleScanline(1, &scan1);
  FullySamplePixel(0, 1, &scan1.pix[0], &scan1.samp[0]);
  FullySamplePixel(Screen.xsize - 1, 1, &scan1.pix[Screen.xsize - 1],
		   &scan1.samp[Screen.xsize - 1]);

  lasttime = 0;
  for (y = 1; y < Screen.ysize; y++) {
    SingleSampleScanline(y + 1, &scan2);
    FullySamplePixel(0, y + 1, &scan2.pix[0], &scan2.samp[0]);
    FullySamplePixel(Screen.xsize - 1, y + 1,
		     &scan2.pix[Screen.xsize - 1],
		     &scan2.samp[Screen.xsize - 1]);

    if (Sampling.sidesamples > 1)
      AdaptiveRefineScanline(y, &scan0, &scan1, &scan2);

#ifndef NSPARALLEL
    PictureWriteLine(scan0.pix);
#else
    if (Options.mode == MODE_STANDALONE)
      PictureWriteLine(scan0.pix);
    else
      PictureBufferWrite(scan0.pix);
#endif

    tmppix = scan0.pix;
    tmpsamp = scan0.samp;
    scan0.pix = scan1.pix;
    scan0.samp = scan1.samp;
    scan1.pix = scan2.pix;
    scan1.samp = scan2.samp;
    scan2.pix = tmppix;
    scan2.samp = tmpsamp;

    if ((y + Screen.miny - 1) % Options.report_freq == 0) {
      fprintf(Stats.fstats, "Finished line %d (%lu rays",
	      y + Screen.miny - 1,
	      Stats.EyeRays);
      if (Options.verbose) {
	/*
	 * Report total CPU and split times.
	 */
	RSGetCpuTime(&usertime, &systime);
	fprintf(Stats.fstats, ", %2.2f sec,",
		usertime + systime);
	fprintf(Stats.fstats, " %2.2f split,",
		usertime + systime - lasttime);
	fprintf(Stats.fstats, " %6.2f ray/sec",
		Stats.EyeRays / (usertime + systime));
	lasttime = usertime + systime;
      }
      fprintf(Stats.fstats, ")\n");
      (void) fflush(Stats.fstats);
    }
  }
  /*
   * Supersample last scanline.
   */
  for (y = 1; y < Screen.xsize - 1; y++) {
    if (scan0.samp[y] != SUPERSAMPLED)
      FullySamplePixel(y, Screen.ysize - 1,
		       &scan0.pix[y],
		       &scan0.samp[y]);
  }
  
#ifndef NSPARALLEL
  PictureWriteLine(scan0.pix);
#else
  if (Options.mode == MODE_STANDALONE)
    PictureWriteLine(scan0.pix);
  else
    PictureBufferWrite(scan0.pix);
#endif
}

void SingleSampleScanline(line, data)
     int line;
     Scanline *data;
{
  Float upos, vpos, yp;
  int x, usamp, vsamp;

  yp = line + Screen.miny - 0.5 * Sampling.filterwidth;
  for (x = 0; x < Screen.xsize; x++) {
    /*
     * Pick a sample number...
     */
    data->samp[x] = nrand() * Sampling.totsamples;
    /*
     * Take sample corresponding to sample #.
     */
    usamp = data->samp[x] % Sampling.sidesamples;
    vsamp = data->samp[x] / Sampling.sidesamples;

    vpos = yp + vsamp * Sampling.filterdelta;
    upos = x + Screen.minx - 0.5 * Sampling.filterwidth +
      usamp * Sampling.filterdelta;
    if (Options.jitter) {
      vpos += nrand() * Sampling.filterdelta;
      upos += nrand() * Sampling.filterdelta;
    }
    TopRay.time = SampleTime(SampleNumbers[data->samp[x]]);
    SampleScreen(upos, vpos, &TopRay,
		 &data->pix[x], SampleNumbers[data->samp[x]]);
    if (Options.samplemap)
      data->pix[x].alpha = 0;
  }
}

void FullySampleScanline(line, data)
  int line;
  Scanline *data;
{
  int x;

  for (x = 0; x < Screen.xsize; x++) {
    data->samp[x] = UNSAMPLED;
    FullySamplePixel(x, line, &data->pix[x], &data->samp[x]);
  }
}

void FullySamplePixel(xp, yp, pix, prevsamp)
  int xp, yp;
  Pixel *pix;
  int *prevsamp;
{
  Float upos, vpos, u, v;
  int x, y, sampnum;
  Pixel ctmp;

#ifdef AMIGA
#ifdef LATTICE
  chkabort();
#else
#ifndef __GNUC__
  Chk_Abort();
#endif
#endif
#endif
  if (*prevsamp == SUPERSAMPLED)
    return;			/* already done */

  Stats.SuperSampled++;
  if (*prevsamp == UNSAMPLED) {
    /*
     * No previous sample; initialize to black.
     */
    pix->r = pix->g = pix->b = pix->alpha = 0.;
  } else {
    if (Sampling.sidesamples == 1) {
      *prevsamp = SUPERSAMPLED;
      return;
    }
    x = *prevsamp % Sampling.sidesamples;
    y = *prevsamp / Sampling.sidesamples;
    pix->r *= Sampling.filter[x][y];
    pix->g *= Sampling.filter[x][y];
    pix->b *= Sampling.filter[x][y];
    pix->alpha *= Sampling.filter[x][y];
  }

  sampnum = 0;
  xp += Screen.minx;
  vpos = Screen.miny + yp - 0.5 * Sampling.filterwidth;
  for (y = 0; y < Sampling.sidesamples; y++,
       vpos += Sampling.filterdelta) {
    upos = xp - 0.5 * Sampling.filterwidth;
    for (x = 0; x < Sampling.sidesamples; x++,
	 upos += Sampling.filterdelta) {
      if (sampnum != *prevsamp) {
	if (Options.jitter) {
	  u = upos + nrand() * Sampling.filterdelta;
	  v = vpos + nrand() * Sampling.filterdelta;
	} else {
	  u = upos;
	  v = vpos;
	}
	TopRay.time = SampleTime(SampleNumbers[sampnum]);
	SampleScreen(u, v, &TopRay, &ctmp,
		     SampleNumbers[sampnum]);
	pix->r += ctmp.r * Sampling.filter[x][y];
	pix->g += ctmp.g * Sampling.filter[x][y];
	pix->b += ctmp.b * Sampling.filter[x][y];
	pix->alpha += ctmp.alpha * Sampling.filter[x][y];
      }
      if (++sampnum == Sampling.totsamples)
	sampnum = 0;
    }
  }

  if (Options.samplemap)
    pix->alpha = 255;

  *prevsamp = SUPERSAMPLED;
}

void AdaptiveRefineScanline(y, scan0, scan1, scan2)
  int y;
  Scanline *scan0, *scan1, *scan2;
{
  int x, done;

  /*
   * Walk down scan1, looking at 4-neighbors for excessive contrast.
   * If found, supersample *all* neighbors not already supersampled.
   * The process is repeated until either there are no
   * high-contrast regions or all such regions are already supersampled.
   */

  do {
    done = TRUE;
    for (x = 1; x < Screen.xsize - 1; x++) {
      /*
       * Find min and max RGB for area we care about
       */
      if (ExcessiveContrast(x, scan0->pix, scan1->pix,
			    scan2->pix)) {
	if (scan1->samp[x - 1] != SUPERSAMPLED) {
	  done = FALSE;
	  FullySamplePixel(x - 1, y,
			   &scan1->pix[x - 1],
			   &scan1->samp[x - 1]);
	}
	if (scan0->samp[x] != SUPERSAMPLED) {
	  done = FALSE;
	  FullySamplePixel(x, y - 1,
			   &scan0->pix[x],
			   &scan0->samp[x]);
	}
	if (scan1->samp[x + 1] != SUPERSAMPLED) {
	  done = FALSE;
	  FullySamplePixel(x + 1, y,
			   &scan1->pix[x + 1],
			   &scan1->samp[x + 1]);
	}
	if (scan2->samp[x] != SUPERSAMPLED) {
	  done = FALSE;
	  FullySamplePixel(x, y + 1,
			   &scan2->pix[x],
			   &scan2->samp[x]);
	}
	if (scan1->samp[x] != SUPERSAMPLED) {
	  done = FALSE;
	  FullySamplePixel(x, y,
			   &scan1->pix[x],
			   &scan1->samp[x]);
	}
      }
    }
  } while (!done);
}

static int ExcessiveContrast(x, pix0, pix1, pix2)
  int x;
  Pixel *pix0, *pix1, *pix2;
{
  Float mini, maxi, sum, diff;

  maxi = max(pix0[x].r, pix1[x - 1].r);
  if (pix1[x].r > maxi)
    maxi = pix1[x].r;
  if (pix1[x + 1].r > maxi)
    maxi = pix1[x + 1].r;
  if (pix2[x].r > maxi)
    maxi = pix2[x].r;

  mini = min(pix0[x].r, pix1[x - 1].r);
  if (pix1[x].r < mini)
    mini = pix1[x].r;
  if (pix1[x + 1].r < mini)
    mini = pix1[x + 1].r;
  if (pix2[x].r < mini)
    mini = pix2[x].r;

  diff = maxi - mini;
  sum = maxi + mini;
  if (sum > EPSILON && diff / sum > Options.contrast.r)
    return TRUE;

  maxi = max(pix0[x].g, pix1[x - 1].g);
  if (pix1[x].g > maxi)
    maxi = pix1[x].g;
  if (pix1[x + 1].g > maxi)
    maxi = pix1[x + 1].g;
  if (pix2[x].g > maxi)
    maxi = pix2[x].g;

  mini = min(pix0[x].g, pix1[x - 1].g);
  if (pix1[x].g < mini)
    mini = pix1[x].g;
  if (pix1[x + 1].g < mini)
    mini = pix1[x + 1].g;
  if (pix2[x].g < mini)
    mini = pix2[x].g;

  diff = maxi - mini;
  sum = maxi + mini;

  if (sum > EPSILON && diff / sum > Options.contrast.g)
    return TRUE;

  maxi = max(pix0[x].b, pix1[x - 1].b);
  if (pix1[x].b > maxi)
    maxi = pix1[x].b;
  if (pix1[x + 1].b > maxi)
    maxi = pix1[x + 1].b;
  if (pix2[x].b > maxi)
    maxi = pix2[x].b;

  mini = min(pix0[x].b, pix1[x - 1].b);
  if (pix1[x].b < mini)
    mini = pix1[x].b;
  if (pix1[x + 1].b < mini)
    mini = pix1[x + 1].b;
  if (pix2[x].b < mini)
    mini = pix2[x].b;

  diff = maxi - mini;
  sum = maxi + mini;
  if (sum > EPSILON && diff / sum > Options.contrast.b)
    return TRUE;

  return FALSE;
}

Float
SampleTime(sampnum)
  int sampnum;
{
  Float window, jitter = 0.0, res;

  if (Options.shutterspeed <= 0.)
    return Options.framestart;
  if (Options.jitter)
    jitter = nrand();
  window = Options.shutterspeed / Sampling.totsamples;
  res = Options.framestart + window * (sampnum + jitter);
  TimeSet(res);
  return res;
}

static void
RaytraceInit()
{
  switch (Sampling.sidesamples) {
  case 1:
    SampleNumbers = OneSample;
    break;
  case 2:
    SampleNumbers = TwoSamples;
    break;
  case 3:
    SampleNumbers = ThreeSamples;
    break;
  case 4:
    SampleNumbers = FourSamples;
    break;
  case 5:
    SampleNumbers = FiveSamples;
    break;
  case 6:
    SampleNumbers = SixSamples;
    break;
  case 7:
    SampleNumbers = SevenSamples;
    break;
  case 8:
    SampleNumbers = EightSamples;
    break;
  default:
    RLerror(RL_PANIC, "Sorry, %d rays/pixel not supported.\n",
	    Sampling.totsamples);
  }

  /*
   * Allocate pixel arrays and arrays to store sampling info.
   */
  scan0.pix = (Pixel *) Malloc(Screen.xsize * sizeof(Pixel));
  scan1.pix = (Pixel *) Malloc(Screen.xsize * sizeof(Pixel));
  scan2.pix = (Pixel *) Malloc(Screen.xsize * sizeof(Pixel));

  scan0.samp = (int *) Malloc(Screen.xsize * sizeof(int));
  scan1.samp = (int *) Malloc(Screen.xsize * sizeof(int));
  scan2.samp = (int *) Malloc(Screen.xsize * sizeof(int));
}
