/*****************************************************************************
*  rad.c
*
*  This program contains three functions that should be called in sequence to
*  perform radiosity rendering:
*  InitRad(): Initialize radiosity.
*  DoRad(): Perform the main radiosity iteration loop.
*  CleanUpRad(): Clean up.
*
*  The following routines are assumed to be provided by the user:
*  BeginDraw()
*  DrawPolygon()
*  EndDraw()
*  Refer to rad.h for details
*
*  Copyright (C) 1990-1991 Apple Computer, Inc.
*  All rights reserved.
*
*  12/1990 S. Eric Chen	
******************************************************************************/

#include "rad.h"
#include <math.h>
#include <stdlib.h>

#define kMaxPolyPoints	255
#define PI	3.1415926
#define AddVector(c,a,b) (c).x=(a).x+(b).x, (c).y=(a).y+(b).y, (c).z=(a).z+(b).z
#define SubVector(c,a,b) (c).x=(a).x-(b).x, (c).y=(a).y-(b).y, (c).z=(a).z-(b).z
#define CrossVector(c,a,b)	(c).x = (a).y*(b).z - (a).z*(b).y, \
                				(c).y = (a).z*(b).x - (a).x*(b).z, \
                				(c).z = (a).x*(b).y - (a).y*(b).x
#define DotVector(a,b) (a).x*(b).x + (a).y*(b).y + (a).z*(b).z
#define ScaleVector(c,s) (c).x*=(s), (c).y*=(s), (c).z*=(s)
#define NormalizeVector(n,a) 	((n)=sqrt(DotVector(a,a)), \
								(n)?((a).x/=n, (a).y/=n, (a).z/=n):0)

typedef struct {
	TView	view;		/* we only need to store one face of the hemi-cube */
	double*	topFactors;	/* delta form-factors(weight for each pixel) of the top face */
	double*	sideFactors; /* delta form-factors of the side faces */
} THemicube;

static TRadParams *params;	/* input parameters */
static THemicube hemicube;	/* one hemi-cube */
static double *formfactors;	/* a form-factor array which has the same length as the number of elements */
static double totalEnergy;	/* total emitted energy; used for convergence checking */

static const TSpectra black = { 0, 0, 0 };	/* for initialization */
static int FindShootPatch(unsigned long *shootPatch);
static void SumFactors(double* formfactors, int xRes, int yRes, 
	unsigned long* buf, double* deltaFactors);
static void MakeTopFactors(int hres, double* deltaFactors);
static void MakeSideFactors(int hres, double* deltaFactors);
static void ComputeFormfactors(unsigned long shootPatch);
static void DistributeRad(unsigned long shootPatch);
static void DisplayResults(TView* view);
static void DrawElement(TElement* ep, unsigned long color);
static TColor32b SpectraToRGB(TSpectra* spectra);


/* Initialize radiosity based on the input parameters p */
void InitRad(TRadParams *p)
{
	int n;
	int hRes;
	unsigned long i;
	int j;
	TPatch*	pp;
	TElement* ep;
	
	params = p;
	
	/* initialize hemi-cube */
	hemicube.view.fovx = 90;
	hemicube.view.fovy = 90;
	/* make sure hemicube resolution is an even number */
	hRes = ((int)(params->hemicubeRes/2.0+0.5))*2;
	hemicube.view.xRes = hemicube.view.yRes = hRes;
	n = hRes*hRes;
	hemicube.view.buffer = calloc(n, sizeof(unsigned long));
	hemicube.view.wid=0;
	hemicube.view.near = params->worldSize*0.001;
	hemicube.view.far = params->worldSize;
	
	/* take advantage of the symmetry in the delta form-factors */
	hemicube.topFactors= calloc(n/4, sizeof(double));
	hemicube.sideFactors= calloc(n/4, sizeof(double));
	MakeTopFactors(hRes/2, hemicube.topFactors);
	MakeSideFactors(hRes/2, hemicube.sideFactors);
	
	formfactors = calloc(params->nElements, sizeof(double));
	
	/* initialize radiosity */
	pp = params->patches;
	for (i=params->nPatches; i--; pp++)
		pp->unshotRad = *(pp->emission);
	ep = params->elements;
	for (i=params->nElements; i--; ep++)
		ep->rad = *(ep->patch->emission);

	/* compute total energy */
	totalEnergy = 0;
	pp = params->patches;
	for (i=params->nPatches; i--; pp++)
		for (j=0; j<kNumberOfRadSamples; j++)
			totalEnergy += pp->emission->samples[j] * pp->area;

	DisplayResults(&params->displayView); 

}

/* Main iterative loop */
void DoRad()
{
	unsigned long shootPatch;
	
	while (FindShootPatch(&shootPatch)) 
	{
		ComputeFormfactors(shootPatch);
		DistributeRad(shootPatch);
		DisplayResults(&params->displayView);
	}
	
}

/* Clean up */
void CleanUpRad()
{
	free(hemicube.topFactors);
	free(hemicube.sideFactors);
	free(hemicube.view.buffer);
	free(formfactors);

}

/* Find the next shooting patch based on the unshot energy of each patch */
/* Return 0 if convergence is reached; otherwise, return 1 */
static int FindShootPatch(unsigned long *shootPatch)
{
	int i, j;
	double energySum, error, maxEnergySum=0;
	TPatch* ep;

	ep = params->patches;
	for (i=0; i< params->nPatches; i++, ep++)
	{
		energySum =0;
		for (j=0; j<kNumberOfRadSamples; j++)
			energySum += ep->unshotRad.samples[j] * ep->area;
		
		if (energySum > maxEnergySum) 
		{
			*shootPatch = i;
			maxEnergySum = energySum;
		}
	}

	error = maxEnergySum / totalEnergy;
	/* check convergence */
	if (error < params->threshold)
		return (0);		/* converged */
	else
		return (1);
	

}

/* Find out the index to the delta form-factors arrary */
#define Index(i)	((i)<hres? i: (hres-1- ((i)%hres)))

/* Use the largest 32bit unsigned long for background */
#define kBackgroundItem 0xffffffff

/* Convert a hemi-cube face to form-factors */
static void SumFactors(
double* formfactors, /* output */
int xRes, int yRes, /* resolution of the hemi-cube face */
unsigned long* buf, /* we only need the storage of the top hemi-cube face */
double* deltaFactors /* delta form-factors for each hemi-cube pixel */
)
{
	int i, j;
	int ii, jj;
	unsigned long *ip=buf;
	int hres = xRes/2;
	for (i=0; i<yRes; i++) 
	{
		ii= Index(i)*hres;
	  	for (j=0; j<xRes; j++, ip++) 
			if ((*ip) != kBackgroundItem) 
			{
				jj = Index(j);
				formfactors[*ip] += deltaFactors[ii+jj];
			}
	}
}

/* Create the delta form-factors for the top face of hemi-cube */
/* Only need to compute 1/4 of the form-factors because of the 4-way symmetry */
static void MakeTopFactors(
int hres, /* half resolution of the face */
double* deltaFactors /* output */
)
{
    int j,k;
    double xSq , ySq, xy1Sq;
	double n= hres;
	double* wp;
	double dj, dk;
	
	wp = deltaFactors;
	for (j=0; j<hres; j++)
	{
		dj = (double)j;
		ySq = (n - (j+0.5)) / n;
       	ySq *= ySq;
       	for ( k=0 ; k<hres ; k++ )
       	{
			dk = (double)k;
       		xSq = ( n - (k + 0.5) ) / n;
			xSq *= xSq;
			xy1Sq =  xSq + ySq + 1.0 ;
			xy1Sq *= xy1Sq;
        	*wp++ = 1.0 / (xy1Sq * PI * n * n);
       	}
    }
}

/* Create the delta form-factors for the side face of hemi-cube */
/* Only need to compute 1/4 of the form-factors because of the 4-way symmetry */
static void MakeSideFactors(
int hres, /* half resolution of the face */
double* deltaFactors /* output */
)
{
    int j,k;
    double x, xSq , y, ySq, xy1, xy1Sq;
	double n= hres;
	double* wp;
	double dj, dk;
	
	wp = deltaFactors;
	for (j=0; j<hres; j++)
	{
		dj = (double)j;
		y = (n - (dj+0.5)) / n;
       	ySq = y*y;
       	for ( k=0 ; k<hres ; k++ )
       	{
			dk = (double)k;
       		x = ( n - (dk + 0.5) ) / n;
			xSq = x*x;
			xy1 =  xSq + ySq + 1.0 ;
			xy1Sq = xy1*xy1;
        	*wp++ = y / (xy1Sq * PI * n * n);
       	}
    }
}

/* Use drand48 instead if it is supported */
#define RandomFloat ((float)(rand())/(float)RAND_MAX)

/* Compute form-factors from the shooting patch to every elements */
static void ComputeFormfactors(unsigned long shootPatch)
{
	unsigned long i;
	TVector3f	up[5]; 
	TPoint3f	lookat[5];
	TPoint3f	center;
	TVector3f	normal, tangentU, tangentV, vec;
	int face;
	double		norm;
	TPatch*		sp;
	double*		fp;
	TElement*	ep;

	/* get the center of shootPatch */
	sp = &(params->patches[shootPatch]);
	center = sp->center;
	normal = sp->normal;
	
	/* rotate the hemi-cube along the normal axis of the patch randomly */
	/* this will reduce the hemi-cube aliasing artifacts */
	do {
		vec.x = RandomFloat;
		vec.y = RandomFloat;
		vec.z = RandomFloat;
		/* get a tangent vector */
		CrossVector(tangentU, normal, vec);
		NormalizeVector(norm, tangentU);
	} while (norm==0);	/* bad choice of the radom vector */
	
	/* compute tangentV */
	CrossVector(tangentV, normal, tangentU);
	
	/* assign the lookats and ups for each hemicube face */
	AddVector(lookat[0], center, normal);
	up[0] = tangentU;
	AddVector(lookat[1], center, tangentU);
	up[1] = normal;
	AddVector(lookat[2], center, tangentV);
	up[2] = normal;
	SubVector(lookat[3], center, tangentU);
	up[3] = normal;
	SubVector(lookat[4], center, tangentV);
	up[4] = normal;
	
	/* position the hemicube slightly above the center of the shooting patch */
	ScaleVector(normal, params->worldSize*0.0001);
	AddVector(hemicube.view.camera, center, normal);
	
	/* clear the formfactors */
	fp = formfactors;
	for (i=params->nElements; i--; fp++)
		*fp = 0.0;
		
	for (face=0; face < 5; face++)
	{
		hemicube.view.lookat = lookat[face];
		hemicube.view.up = up[face];

		/* draw elements */
		BeginDraw(&(hemicube.view), kBackgroundItem);
		for (i=0; i< params->nElements; i++)
			DrawElement(&params->elements[i], i);	
			/* color element i with its index */
		EndDraw();
		
		/* get formfactors */
		if (face==0)
			SumFactors(formfactors, hemicube.view.xRes, hemicube.view.yRes, 
				hemicube.view.buffer, hemicube.topFactors);
		else
			SumFactors(formfactors, hemicube.view.xRes, hemicube.view.yRes/2, 
				hemicube.view.buffer, hemicube.sideFactors);
	}
	
	/* compute reciprocal form-factors */
	ep = params->elements;
	fp = formfactors;
	for (i=params->nElements; i--; ep++, fp++)
	{
		*fp *= sp->area / ep->area;

		/* This is a potential source of hemi-cube aliasing */
		/* To do this right, we need to subdivide the shooting patch
		and reshoot. For now we just clip it to unity */
		if ((*fp) > 1.0) 	*fp ==1.0;	
	}

}

/* Distribute radiosity form shootPatch to every element */
/* Reset the shooter's unshot radiosity to 0 */
static void DistributeRad(unsigned long shootPatch)
{
	unsigned long i;
	int j;
	TPatch* sp;
	TElement* ep;
	double* fp;
	TSpectra deltaRad;
	double w;

	sp = &(params->patches[shootPatch]);
	
	/* distribute unshotRad to every element */
	ep = params->elements;
	fp = formfactors;
	for (i=params->nElements; i--; ep++, fp++)
	{
		if ((*fp) != 0.0) 
		{
			for (j=0; j<kNumberOfRadSamples; j++)
				 deltaRad.samples[j] = 	sp->unshotRad.samples[j] * (*fp) * 
										ep->patch->reflectance->samples[j];

			/* incremental element's radiosity and patch's unshot radiosity */
			w = ep->area/ep->patch->area;
			for (j=0; j<kNumberOfRadSamples; j++) 
			{
				ep->rad.samples[j] += deltaRad.samples[j];
				ep->patch->unshotRad.samples[j] += deltaRad.samples[j] * w;
			}
		}
	}
	
	/* reset shooting patch's unshot radiosity */
	sp->unshotRad = black;
}

/* Convert a TSpectra (radiosity) to a TColor32b (rgb color) */
/* Assume the first three samples of the spectra are the r, g, b colors */
/* More elaborated color space transformation could be performed here */
static TColor32b
SpectraToRGB(TSpectra* spectra)
{
	TColor32b	c;
	TSpectra	r;
	double 	max=1.0;
	int k;

	for (k=kNumberOfRadSamples; k--;) {
		if (spectra->samples[k] > max)
			max = spectra->samples[k];
	}
	/* Clip the intensity*/
	r = *spectra;
	if (max>1.0) {
		for (k=kNumberOfRadSamples; k--; )
			r.samples[k] /= max;
	}
	
	/* Convert to a 32-bit color; Assume the first 3 samples in TSpectra 
	are the r, g, b colors we want. Otherwise, do color conversion here */
	c.a= 0;
	c.r= (unsigned char) (r.samples[0] * 255.0 + 0.5);
	c.g= (unsigned char) (r.samples[1] * 255.0 + 0.5);
	c.b= (unsigned char) (r.samples[2] * 255.0 + 0.5);
	
	return c;
}

static void
GetAmbient(TSpectra* ambient)
{
	TPatch* p;
	unsigned long i;
	int k;
	static int first = 1;
	static TSpectra baseSum; 
	TSpectra uSum;

	uSum=black;
	if (first) {
		double areaSum;
		TSpectra rSum;
		areaSum=0;
		rSum=black;
		/* sum area and (area*reflectivity) */
		p= params->patches;
		for (i=params->nPatches; i--; p++) {
			areaSum += p->area;
			for (k=kNumberOfRadSamples; k--; )
				rSum.samples[k] += p->reflectance->samples[k]* p->area;
		}
		for (k=kNumberOfRadSamples; k--; )
			baseSum.samples[k] = areaSum - rSum.samples[k];
		first = 0;
	}

	/* sum (unshot radiosity * area) */
	p= params->patches;
	for (i=params->nPatches; i--; p++) {
		for (k=kNumberOfRadSamples; k--; )
			uSum.samples[k] += p->unshotRad.samples[k] * p->area;
	}
	
	/* compute ambient */
	for (k=kNumberOfRadSamples; k--; )
		ambient->samples[k] = uSum.samples[k] / baseSum.samples[k];

}

static void
DisplayResults(TView* view)
{
	unsigned long i;
	register TElement* ep;
	TSpectra ambient;
	GetAmbient(&ambient);
	
	BeginDraw(view, 0);
	ep = params->elements;
	for (i=0; i< params->nElements; i++, ep++) {
		TColor32b	c;
		TSpectra  s;
		int k;
		/* add ambient approximation */
		if (params->addAmbient) {
			for (k=kNumberOfRadSamples; k--;)
				s.samples[k] = (ep->rad.samples[k] + (ambient.samples[k]*
					ep->patch->reflectance->samples[k]))*params->intensityScale;
		} else {
			for (k=kNumberOfRadSamples; k--; )
				s.samples[k] = ep->rad.samples[k]*params->intensityScale;
		}
		/* quantize color */
		c = SpectraToRGB(&s);
		DrawElement(ep, *(unsigned long*)&c);
	}
			
	EndDraw();

}

static void
DrawElement(TElement* ep, unsigned long color)
{
	static TPoint3f pts[kMaxPolyPoints];
	int nPts = ep->nVerts;
	int j;
	for (j=0; j<nPts; j++)
		pts[j] = params->points[ep->verts[j]];
	
	DrawPolygon(nPts, pts, &ep->normal, color);

}




