/*
 * raytrace.c
 *
 * 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".
 *
 * $Id$
 *
 * $Log$
 */

/*
 * This module could use some work.  In particular, a better antialiasing
 * scheme would be nice.
 */
#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"

static Pixel		*pixel_buf[2];	/* Point buffer, background color */
static Pixel		*out_buf;	/* Output pixel buffer */
static pixel_square	*SquareStack;
static Float		Minsquare;
static int		*SampleNumbers;

static void	distributed_trace(), trace_jit_line(), trace_jit_pixel(),
		adaptive_trace(), trace_line(), subdivide_line(),
		subdivide_square(), trace_point();

static int	pixel_ok();

static Ray	TopRay;				/* Top-level ray. */

/*
 * "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()
{
	/*
	 * 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 *)0;
	TopRay.depth = 0;

	out_buf = (Pixel *)Malloc(Screen.xsize * sizeof(Pixel));

	if (Options.jittered)
		distributed_trace();
	else
		adaptive_trace();
}

/*
 * Raytrace an image using "distributed" raytracing.
 */
static void
distributed_trace()
{
	int y;
	Float usertime, systime, lasttime;

	switch (Options.jit_samples) {
		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",
				Options.jit_samples*Options.jit_samples);
	}

	/*
	 * Trace each scanline, writing results to output file.
	 */
	lasttime = 0;
	for (y = Screen.miny; y <= Screen.maxy; y++) {
		trace_jit_line(y, out_buf);
		PictureWriteLine(out_buf);
		if (y % Options.report_freq == 0) {
			fprintf(Stats.fstats,"Finished line %d (%lu rays",y,
							Stats.EyeRays);
			if (Options.verbose) {
				/*
				 * Report total CPU and split times.
				 */
				get_cpu_time(&usertime, &systime);
				fprintf(Stats.fstats,", %2.2f sec,",
						usertime+systime);
				fprintf(Stats.fstats," %2.2f split",
						usertime+systime-lasttime);
				lasttime = usertime+systime;
			}
			fprintf(Stats.fstats,")\n");
			(void)fflush(Stats.fstats);
		}
	}
}

static void
trace_jit_line(line, buf)
int line;
Pixel *buf;
{
	register int x;

	for (x = Screen.minx; x <= Screen.maxx; x++)
		trace_jit_pixel(x, line, buf++);
}

static void
trace_jit_pixel(xp, yp, color)
int xp, yp;
Pixel *color;
{
	Pixel tmp;
	Float x, y, xpos, ypos;
	int i, j, index;

	ypos = (Float)yp - 0.5;
	color->r = color->g = color->b = color->alpha = 0.;
	index = 0;
	for (i = 0; i < Options.jit_samples; i++, ypos += Sampling.spacing) {
		xpos = (Float)xp - 0.5;
		for (j=0; j < Options.jit_samples; j++,
		     xpos += Sampling.spacing) {
			x = xpos + nrand() * Sampling.spacing;
			y = ypos + nrand() * Sampling.spacing;
			trace_point(x, y, &tmp, SampleNumbers[index++]);
			color->r += tmp.r;
			color->g += tmp.g;
			color->b += tmp.b;
			color->alpha += tmp.alpha;
		}
	}
	color->r *= Sampling.weight;
	color->g *= Sampling.weight;
	color->b *= Sampling.weight;
	color->alpha *= Sampling.weight;
}

/*
 * Raytrace an image using adaptive supersampling to perform antialising.
 */
static void
adaptive_trace()
{
	int line;
	Float usertime, systime, lasttime;

	pixel_buf[0] = (Pixel *)Malloc(sizeof(Pixel) *
				(unsigned)(Screen.xsize + 1));
	pixel_buf[1] = (Pixel *)Malloc(sizeof(Pixel) *
				(unsigned)(Screen.xsize + 1));
	/*
	 * Minimum pixel square size.
	 */
	Minsquare = 1./pow(2.,(Float)Options.pixel_div);
	/*
	 * At any time, there can be a maximum of 3 * pixel_div + 1
	 * pixel squares on the stack.
	 */
	SquareStack = (pixel_square *)Malloc((unsigned)(3*Options.pixel_div+1)*
				sizeof(pixel_square));
	/*
	 * A pixel is treated as a square through whose corners rays
	 * are traced.  If the color values at the corners are
	 * "too different" (as measured by pixel_ok()), the square is
	 * divided into four squares (tracing 5 additional rays)
	 * and the process is repeated on the four new squares.
	 * The color assigned to a square is the average of the 4 corners.
	 * Note that this means that adjacent super-sampled pixels
	 * cause the same ray to be traced more than once.
	 * This scheme is adequate but far from wonderful.
	 */
	line = Screen.miny;
	trace_line(line, &pixel_buf[line & 01][0]);
	lasttime = 0;

	for(line++; line <= Screen.maxy + 1; line++) {
		trace_line(line, &pixel_buf[line & 01][0]);
		subdivide_line(line -1, pixel_buf[line & 01],
				     pixel_buf[(line+1) & 01],
				     out_buf);
		PictureWriteLine(out_buf);
		if (line % Options.report_freq == 0) {
			fprintf(Stats.fstats,"Finished line %d (%lu rays",line,
								Stats.EyeRays);
			if (Options.verbose) {
				/*
				 * Report total CPU and split times.
				 */
				get_cpu_time(&usertime, &systime);
				fprintf(Stats.fstats,", %2.2f sec,",
						usertime+systime);
				fprintf(Stats.fstats," %2.2f split",
						usertime+systime-lasttime);
				lasttime = usertime+systime;
			}
			fprintf(Stats.fstats,")\n");
			(void)fflush(Stats.fstats);
		}
	}
}

/*
 * Trace a line of sample points along "line".
 */
static void
trace_line(line, buf)
int line;
Pixel *buf;
{
	register int x;
	Float y, xoff;

	/*
	 * Line # 'line' has a 'y' value of line - 0.5.
	 */
	y = line - 0.5;
	/*
	 * Since we're samping pixel corners, we need to subtract
	 * 0.5 from the value of each x.
	 */
	xoff = (Float)Screen.minx - 0.5;
	/*
	 * We need to trace xsize + 1 rays.
	 */
	for (x = 0; x <= Screen.xsize; x++)
		trace_point((Float)x + xoff, y, buf + x, -1);
}

/*
 * Given the two arrays of sample points that define the upper and
 * lower edges of all the pixel squares in line "y," push each
 * square in turn on the pixelsquare stack and determine a color value
 * for the pixel by calling subdivide_square().
 */
static void
subdivide_line(y, upper, lower, buf)
int y;
Pixel *upper, *lower, *buf;
{
	register int x;

	/*
	 * Upper is the array of sample values that define the "upper"
	 * part (corners) of this row, while lower are the "lower" corners.
	 * For the next (lower) row, the current "upper" becomes "lower".
	 */
	for(x = 0; x < Screen.xsize; x++) {
		SquareStack[0].x = (Float)(x + Screen.minx);
		SquareStack[0].y = (Float)y;
		SquareStack[0].size = 1.0;
		SquareStack[0].ul = upper[x];
		SquareStack[0].ur = upper[x+1];
		SquareStack[0].ll = lower[x];
		SquareStack[0].lr = lower[x+1];
		subdivide_square(&buf[x]);
	}
}

/*
 * Bleck, but it saves us a function call and keeps the code much cleaner.
 */
#define push_square(u,v,s,a,b,c,d) {\
			Stackp->x = u; \
			Stackp->y = v; \
			Stackp->size = s; \
			Stackp->ul = a; \
			Stackp->ur = b; \
			Stackp->ll = c; \
			Stackp->lr = d; \
			Stackp++;}

/*
 * Subdivide a pixel square.
 */
static void
subdivide_square(color)
Pixel *color;
{
	register pixel_square *Stackp;
	Float x, y, size, halfsize;
	Float avfact, xdelta, ydelta;
	Pixel ul, ur, ll, lr, u, d, l, r, c;

	color->r = color->g = color->b = color->alpha = 0.;
	Stackp = SquareStack + 1;

	do {
		Stackp--;
		size = Stackp->size;
		ul = Stackp->ul;
		ur = Stackp->ur;
		ll = Stackp->ll;
		lr = Stackp->lr;

		if (size <= Minsquare || pixel_ok(&ul,&ur,&ll,&lr)) {
			/*
			 * The square is either the smallest allowed, or
			 * the four corners of the square are similar.
			 * Average the four corners (weighted by the
			 * size of the square) to get this square's
			 * contribution to the whole pixel's color.
			 */
			avfact = (size * size) * 0.25;
			color->r += (ul.r + ur.r + ll.r + lr.r) * avfact;
			color->g += (ul.g + ur.g + ll.g + lr.g) * avfact;
			color->b += (ul.b + ur.b + ll.b + lr.b) * avfact;
			color->alpha += (ul.alpha+ur.alpha+ll.alpha+lr.alpha) *
					avfact;
			continue;
		}
		/*
		 * Subdivide into four squares -- trace 5 additional
		 * rays and push the appropriate squares on the pixelsquare
		 * stack.
		 */
		x = Stackp->x;
		y = Stackp->y;
		halfsize = size * 0.5;
		xdelta = x + halfsize;
		ydelta = y + halfsize;
		trace_point(xdelta, y, &u, -1);
		trace_point(x, ydelta, &l, -1);
		trace_point(xdelta, ydelta, &c, -1);
		trace_point(x + size, ydelta, &r, -1);
		trace_point(xdelta, y + size, &d, -1);
		if(size == 1.)
			Stats.SuperSampled++;
		push_square(x, y, halfsize, ul, u, l, c);
		push_square(xdelta, y, halfsize, u, ur, c, r);
		push_square(x, ydelta, halfsize, l, c, ll, d);
		push_square(xdelta, ydelta, halfsize,
					c, r, d, lr);
	} while (Stackp != SquareStack);
}

/*
 * Trace a ray through x, y on the screen, placing the result in "color."
 */
static void
trace_point(x, y, color, sample)
Float x, y;		/* Screen position to sample */
Pixel *color;		/* resulting color */
int sample;		/* sample number, < 0 indicates single sample */
{
	Float dist;
	HitList hitlist;
	Color ctmp;
	extern void focus_blur_ray(), ShadeRay();

	/*
	 * Calculate ray direction.
	 */
	Stats.EyeRays++;
	TopRay.dir.x = Screen.firstray.x + x*Screen.scrnx.x + y*Screen.scrny.x;
	TopRay.dir.y = Screen.firstray.y + x*Screen.scrnx.y + y*Screen.scrny.y;
	TopRay.dir.z = Screen.firstray.z + x*Screen.scrnx.z + y*Screen.scrny.z;

	(void)VecNormalize(&TopRay.dir);

	TopRay.sample = sample;

	if (Camera.aperture > 0.0) {
		/*
		 * If the aperture is open, adjust the initial ray
		 * to account for depth of field.  
		 */
		focus_blur_ray(&TopRay);
	}

	/*
	 * Do the actual ray trace.
	 */
	dist = FAR_AWAY;
	hitlist.nodes = 0;
	(void)TraceRay(&TopRay, &hitlist, EPSILON, &dist);
	ShadeRay(&hitlist, &TopRay, dist, &Screen.background, &ctmp, 1.0);
	color->r = ctmp.r;
	color->g = ctmp.g;
	color->b = ctmp.b;
	if (hitlist.nodes != 0) {
		color->alpha = 1.;
	} else {
		color->alpha = 0.;
	}
}

/*
 * Return TRUE if this pixel is okay and doesn't need to be supersampled,
 * FALSE otherwise.
 */
static int
pixel_ok(w,x,y,z)
Pixel *w, *x, *y, *z;
{
	Float rmax, rmin, gmax, gmin, bmax, bmin;
	Float rsum, gsum, bsum;

	/*
	 * Find min & max R, G, & B.
	 */
	rmax = max(w->r, max(x->r, max(y->r, z->r)));
	rmin = min(w->r, min(x->r, min(y->r, z->r)));
	gmax = max(w->g, max(x->g, max(y->g, z->g)));
	gmin = min(w->g, min(x->g, min(y->g, z->g)));
	bmax = max(w->b, max(x->b, max(y->b, z->b)));
	bmin = min(w->b, min(x->b, min(y->b, z->b)));

	/*
	 * Contrast is defined as (Max - Min) / (Max + Min) for each
	 * of RG&B.  If any of these values is greater than the maximum
	 * allowed, we have to supersample.
	 */
	rsum = rmax + rmin;
	gsum = gmax + gmin;
	bsum = bmax + bmin;
	if ((rsum == 0. || (rmax - rmin) / rsum < Options.contrast.r) &&
	    (gsum == 0. || (bmax - bmin) / gsum < Options.contrast.g) &&
	    (bsum == 0. || (gmax - gmin) / bsum < Options.contrast.b))
		return TRUE;

	return FALSE;
}
