//////////////////////////////////////////////////////////////////////
//
//                             Pixie
//
// Copyright © 1999 - 2003, Okan Arikan
//
// Contact: okan@cs.berkeley.edu
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
//  File				:	gatherCache.cpp
//  Classes				:	CGatherCache
//  Description			:	This class holds a gather cache information
//
////////////////////////////////////////////////////////////////////////
#include "gatherCache.h"
#include "memory.h"
#include "random.h"
#include "object.h"
#include "photonMap.h"
#include "stats.h"
#include "linsys.h"
#include "harmonics.h"

#define GATHER_STACKLENGTH		10000
#define GATHER_LOOKUP_ENTRIES	1000	// The number of entries in the gather lookup table
#define GATHER_OCCLUSION



//#define	GATHER_PRINT
#define	GATHER_SMOOTH_FF

const	float	PLANARITY_NORMAL_THRESHOLD		=	(float) 0.9;
const	float	PLANARITY_DISTANCE_THRESHOLD	=	(float) 0.01;


// These two arrays are used to hold plusOne[i] = (i+1) % 3 and minusOne[i] = (i-1) % 3
static	int		plusOne[3]				=	{1,2,0};
static	int		plusOnes[6][5]			=	{
												{0,0,0,0,0},
												{0,0,0,0,0},
												{0,0,0,0,0},
												{1,2,0,0,0},
												{1,2,3,0,0},
												{1,2,3,4,0}	};

// The lookup parameters used during the finalization
static	const	CGlobalIllumLookup				*lookupParam;
static			CGatherCache::CGatherHarmonic	*allHarmonics;



// FIXME: Debugging junk - REMOVE
#ifdef GATHER_PRINT
static	FILE		*out;
static	float		printScale;

static	void	printTriangle(const float *P0,const float *P1,const float *P2) {
	fprintf(out,"%f %f %f %f %f %f %f %f %f\n",
		P0[0]*printScale,P0[1]*printScale,P0[2]*printScale,
		P1[0]*printScale,P1[1]*printScale,P1[2]*printScale,
		P2[0]*printScale,P2[1]*printScale,P2[2]*printScale);
}

static	void	printPoint(const float *P,const float *C=NULL) {
	if (C == NULL) {
		fprintf(out,"%f %f %f\n",
			P[0]*printScale,P[1]*printScale,P[2]*printScale);
	} else {
		fprintf(out,"%f %f %f - %f %f %f\n",
			P[0]*printScale,P[1]*printScale,P[2]*printScale,
			C[0],C[1],C[2]);
	}
}

static	void	printSphere(const float *P,float dP) {
	fprintf(out,"%f %f %f %f\n",P[0]*printScale,P[1]*printScale,P[2]*printScale,dP*printScale);
}



static	void	printLine(const float *P0,const float *P1) {
	fprintf(out,"%f %f %f %f %f %f\n",
		P0[0]*printScale,P0[1]*printScale,P0[2]*printScale,
		P1[0]*printScale,P1[1]*printScale,P1[2]*printScale);
}
#endif
// FIXME: Debugging junk - REMOVE










































































///////////////////////////////////////////////////////////////////////









///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	CGatherCache
// Description			:	Ctor
// Return Value			:	-
// Comments				:	This form is used for writing
// Date last edited		:	10/6/2003
CGatherCache::CGatherCache(const char *n,unsigned int m,const float *bmi,const float *bma,CXform *w,CHierarchy *h,FILE *in,CArray<CTriangle *> *at) : CCache(n,m) {
	vector			center;
	float			side;

	// Compute the bounding box
	addvv(center,bmi,bma);
	mulvf(center,(float) 0.5);
	side						=	max(max(bma[0] - bmi[0],bma[1] - bmi[1]),bma[2] - bmi[2]);

#ifdef GATHER_PRINT
	// Compute how much we want to scale
	printScale					=	1 / (side*10);
	out							=	fopen("test.txt","w");
#endif

	memory						=	new CMemStack;

	freeTriangles				=	NULL;
	freeClusters				=	NULL;
	hierarchy					=	h;					// Raytracing hierarchy
	microTriangles				=	NULL;

	samplePoints				=	NULL;				// No samples yet
	numSamplePoints				=	0;

	gatherTable					=	NULL;

	// Try to read the cache
	if (in != NULL) {
		int	i;

		// Read the nodes
		root			=	readNode(in);
		fread(&maxDepth,1,sizeof(int),in);
		nodeStack		=	(CGatherNode **) memory->alloc(sizeof(CGatherNode*)*maxDepth*8);

		// Read the hierarchy
		rootCluster		=	readCluster(in);
		fread(&maxClusterDepth,1,sizeof(int),in);
		clusterStack	=	(CGatherCluster **) memory->alloc(sizeof(CGatherCluster*)*maxClusterDepth*8);

		// Close the file
		fclose(in);

		// We don't need the geometry
		delete at;

		// This is a precomputed table of angles
		gatherTable								=	(float *) memory->alloc(sizeof(float)*(GATHER_LOOKUP_ENTRIES+2));

		for (i=0;i<GATHER_LOOKUP_ENTRIES;i++) {
			double	angle		=	2*(i / (double) (GATHER_LOOKUP_ENTRIES-1)) - 1;
			gatherTable[i+1]	=	(float) (acos(angle) / sin(acos(angle)));
		}

		gatherTable[0]							=	gatherTable[1];
		gatherTable[GATHER_LOOKUP_ENTRIES+1]	=	1;
		gatherTable[GATHER_LOOKUP_ENTRIES]		=	1;
	} else {
		int				i;
		CTriangle		**triangles;
		int				numTriangles;
		CGatherTriangle	**allTriangles;
		int				numGatherTriangles;

		// Allocate the root node
		root				=	(CGatherNode *) memory->alloc(sizeof(CGatherNode));
		root->samples		=	NULL;
		movvv(root->center,center);
		root->side			=	side;
		for (i=0;i<8;i++) root->children[i]	=	NULL;
		maxDepth			=	0;
		nodeStack			=	NULL;

		triangles			=	at->array;
		numTriangles		=	at->numItems;

		memBegin();

		// Allocate the temp memory
		allTriangles		=	(CGatherTriangle **) ralloc(numTriangles*sizeof(CGatherTriangle *));
		numGatherTriangles	=	0;

		// Process the triangles and convert them
		for (i=0;i<numTriangles;i++) {
			const	CTriangle	*cTriangle	=	triangles[i];
			const	CVertex		*v0			=	(const CVertex *) getPointer(cTriangle->v[0]);
			const	CVertex		*v1			=	(const CVertex *) getPointer(cTriangle->v[1]);
			const	CVertex		*v2			=	(const CVertex *) getPointer(cTriangle->v[2]);
			CGatherTriangle		*nTriangle	=	newTriangle();
			vector				D1,D2,N;

			// Compute the normal vector of the triangle
			subvv(D1,v1->P,v0->P);
			subvv(D2,v2->P,v0->P);
			crossvv(N,D1,D2);
			if (dotvv(N,N) == 0) {
				// Skip the degenerate triangles
				continue;
			}

			// Swap the vertices if necessary
			addvv(D1,v0->N,v1->N);
			addvv(D1,v2->N);
			if (dotvv(D1,N) < 0) {
				const CVertex	*tmp	=	v1;
				v1						=	v2;
				v2						=	tmp;
				mulvf(N,-1);
			}

			// Record the plane of the triangle and its area
			nTriangle->dA	=	lengthv(N) * (float) 0.5;

			// Record the normal of the triangle
			normalizev(N);
			movvv(nTriangle->N,N);
	
			// Record the vertices
			movvv(nTriangle->corners+0,v0->P);
			movvv(nTriangle->corners+3,v1->P);
			movvv(nTriangle->corners+6,v2->P);
			nTriangle->attributes	=	cTriangle->object->attributes;

			// Record the center of the triangle
			addvv(D1,v0->P,v1->P);
			addvv(D1,v2->P);
			mulvf(D1,1 / (float) 3);
			movvv(nTriangle->P,D1);

			allTriangles[numGatherTriangles++]	=	nTriangle;
		}

		// Hierarchycally cluster the triangles
		rootCluster			=	cluster(numGatherTriangles,allTriangles);
		rootCluster->parent	=	NULL;
		maxClusterDepth		=	0;
		clusterStack		=	NULL;			// We should not be doing cluster accesses anyway

		memEnd();

		// Ditch the geometry
		delete at;

		// Create the array of microtriangles we'll need
		microTriangles		=	new CArray<CGatherTriangle *>;
	}
}

///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	~CGatherCache
// Description			:	Dtor
// Return Value			:	-
// Comments				:
// Date last edited		:	10/6/2003
CGatherCache::~CGatherCache() {
	// Are we writing into a file ?
	if (flags & CACHE_WRITE) {
		FILE	*out	=	ropen(name,"wb",fileGatherCache);

		if (out != NULL) {
			// Write the nodes
			writeNode(out,root);
			fwrite(&maxDepth,1,sizeof(int),out);

			// Write the cluster hierarchy
			writeCluster(out,rootCluster);
			fwrite(&maxClusterDepth,1,sizeof(int),out);

			// Close the file
			fclose(out);
		}
	}

	// Ditch the memory we allocated
	if (microTriangles != NULL)	delete microTriangles;

	delete memory;

#ifdef GATHER_PRINT
	fclose(out);
#endif
}





///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	finalize
// Description			:	Finalize before delete
// Return Value			:	-
// Comments				:
// Date last edited		:	10/6/2003
void	CGatherCache::finalize(const CGlobalIllumLookup *l) {

	// Do we have samples waiting to be processed ?
	if (numSamplePoints > 0) {
		CGatherHarmonic	*cHarmonic;

		memBegin();

		// Create an array of shading points
		CShadingPoint	**samples	=	(CShadingPoint **) ralloc(numSamplePoints*sizeof(CShadingPoint *));
		int				i;
		CShadingPoint	*cSample;

		for (i=0,cSample=samplePoints;cSample!=NULL;cSample=cSample->next) {
			samples[i++]	=	cSample;
		}

		assert(i == numSamplePoints);

		lookupParam			=	l;
		allHarmonics		=	NULL;
		compute(numSamplePoints,samples);

		memEnd();

		numSamplePoints	=	0;

		// Insert the harmonics we computed
		while((cHarmonic=allHarmonics) != NULL) {
			CGatherNode			*cNode		=	root;
			int					depth		=	0;
			int					i,j;
			float				R			=	4*cHarmonic->dPSmooth;

			allHarmonics					=	cHarmonic->next;

			while(cNode->side > R) {
				depth++;

				for (j=0,i=0;i<3;i++) {
					if (cHarmonic->P[i] > cNode->center[i]) {
						j			|=	1 << i;
					}
				}

				if (cNode->children[j] == NULL)	{
					CGatherNode	*nNode	=	(CGatherNode *) memory->alloc(sizeof(CGatherNode));

					for (i=0;i<3;i++) {
						if (cHarmonic->P[i] > cNode->center[i]) {
							nNode->center[i]	=	cNode->center[i] + cNode->side * (float) 0.25;
						} else {
							nNode->center[i]	=	cNode->center[i] - cNode->side * (float) 0.25;
						}
					}

					cNode->children[j]	=	nNode;
					nNode->side			=	cNode->side / (float) 2;
					nNode->samples		=	NULL;
					for (i=0;i<8;i++)	nNode->children[i]	=	NULL;
				}

				cNode			=	cNode->children[j];
			}

			cHarmonic->next		=	cNode->samples;
			cNode->samples		=	cHarmonic;
			maxDepth			=	max(maxDepth,depth);
		}
	}

	if (microTriangles != NULL) {
		if (microTriangles->numItems > 0) {
			// Create the new root cluster
			rootCluster		=	cluster(microTriangles->numItems,microTriangles->array);
			maxClusterDepth	=	depth(rootCluster);
		}

		delete microTriangles;
		microTriangles	=	NULL;
	}
}




















///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	indirectdiffuse
// Description			:	Compute the indirect diffuse
// Return Value			:	-
// Comments				:
// Date last edited		:	10/6/2003
void	CGatherCache::lookup(float *C,const float *P,const float *N,const CGlobalIllumLookup *lookup) {

	// Are we creating the cache ?
	if (flags & CACHE_WRITE) {

		// Yes, just save the shading point
		CShadingPoint	*cSample	=	(CShadingPoint *) memory->alloc(sizeof(CShadingPoint));

		movvv(cSample->P,P);
		movvv(cSample->N,N);
		cSample->next				=	samplePoints;
		samplePoints				=	cSample;
		numSamplePoints++;

		// Return a false color
		C[0]						=	-N[COMP_Z];
		C[1]						=	-N[COMP_Z];
		C[2]						=	-N[COMP_Z];
		C[3]						=	-N[COMP_Z];
	} else {
		// We're reading the cache
		float				H[27];
		CGatherNode			*cNode;
		CGatherNode			**stack;
		CGatherHarmonic		*cHarmonic;
		float				totalWeight;
		int					i;
		vector				global,local;

		// First interpolate the harmonics
		for (i=0;i<27;i++)	H[i]	=	0;

		// Do we want to perform a global lookup ?
		if (lookup->gatherGlobal) {
			float	slack	=	1;

retry:;
			totalWeight		=	0;
			stack			=	nodeStack;
			*stack++		=	root;
			while(stack > nodeStack) {
				cNode	=	*(--stack);

				// Iterate over the samples
				for (cHarmonic=cNode->samples;cHarmonic!=NULL;cHarmonic=cHarmonic->next) {
					vector	D;
					float	t,n;
					float	nominator;

					subvv(D,cHarmonic->P,P);

					// Are we in the range ?
					nominator	=	dotvv(D,D);
					n			=	dotvv(N,cHarmonic->N);
					t			=	cHarmonic->dPSmooth*max(n,(float) 0.01)*slack;;

					// Are we close enough for blending ?
					if (nominator < t*t) {

						// Compute the blending weights
						t	=	(float) sqrt(nominator / (t*t));

						// Put a hermite bump
						const float	w	=	1 + 2*t*t*t - 3*t*t;

						assert(t <= 1);
						assert(t >= 0);
						assert(w <= 1);
						assert(w >= 0);

						// Blend the harmonic coefficients
						for (i=0;i<27;i++)	H[i]	+=	cHarmonic->Y[i]*w;
						totalWeight					+=	w;
					}
				}

				// Check the children
				for (i=0;i<8;i++) {
					CGatherNode	*tNode	=	cNode->children[i];

					if (tNode != NULL) {
						const	float	tSide	=	tNode->side;

						if (	((tNode->center[0] + tSide) > P[0])	&&
								((tNode->center[1] + tSide) > P[1])	&&
								((tNode->center[2] + tSide) > P[2])	&&
								((tNode->center[0] - tSide) < P[0])	&&
								((tNode->center[1] - tSide) < P[1])	&&
								((tNode->center[2] - tSide) < P[2])) {
							*stack++	=	tNode;
						}
					}
				}
			}

			// Normalize the harmonics
			if (totalWeight > EPSILON) {
				const float	invWeight		=	1 / totalWeight;

				for (i=0;i<27;i++)	H[i]	*=	invWeight;
			} else {
				// We could not find a good harmonics
				slack	*=	2;
				goto retry;
			}
		}

		// Evaluate the far field
		harmonicDiffuse(global,H,N);
		mulvf(global,1 / PI);

		// Do the local correction
		initv(local,0,0,0);
		if ((lookup->gatherLocal) && (flags & CACHE_READ)) {
			float			cornerDist[3];
			int				cornerStat[3];
			float			corners[6*3];
			const float		*src;
			int				numCorners;
			float			*cCorner;
			float			ff;
			vector			D;
			CGatherCluster	**cstack,*cCluster;
			const float		d				=	-dotvv(P,N);
			const float		dP				=	(P[COMP_Z]*lookup->minA + lookup->minB)*lookup->cacheSmoothing;

			// Go over the microtriangles and perform the correction as necessary
			cstack			=	clusterStack;
			*cstack++		=	rootCluster;
			while(cstack > clusterStack) {
				cCluster	=	*(--cstack);

				subvv(D,cCluster->P,P);

				// Is the cluster too far away ?
				const float	t	=	cCluster->dP + dP;
				if (dotvv(D,D) < t*t) {
					float	dist;
					float	tist;

					// Trivially reject clusters that are planar
					if (cCluster->planar) {
						dist	=	dotvv(P,cCluster->N) - dotvv(cCluster->P,cCluster->N);
						tist	=	cCluster->dP*PLANARITY_DISTANCE_THRESHOLD;

						// Are we behind the cluster ?
						if (dist < -tist) {
							// Yes
							continue;
						}

						// Can the cluster have a significant form factor ?
						if (dotvv(cCluster->N,N) > PLANARITY_NORMAL_THRESHOLD) {
							// No
							continue;
						}
					}

					// Are we too close to the cluster ?
					if (dotvv(D,D) < (cCluster->dP*cCluster->dP*lookup->closenessFactor)) {

						// Yes, is the cluster a triangle ?
						if (cCluster->triangle != NULL) {
							const		CGatherTriangle	*cTriangle	=	cCluster->triangle;
							vector		gatherP;

							// Compute the distances of the triangle corners to the query plane
							cornerDist[0] = dotvv(cTriangle->corners+0,N) + d;
							cornerDist[1] = dotvv(cTriangle->corners+3,N) + d;
							cornerDist[2] = dotvv(cTriangle->corners+6,N) + d;

							// Is the triangle completely behind the query plane ?
							if (	(cornerDist[0] <= tist) &&
									(cornerDist[1] <= tist) &&
									(cornerDist[2] <= tist)	) {
								// Yes, keep going
								continue;
							}

							// If we're slight behind the triangle, push us forward a little bit
							if (dist < tist) {
								mulvf(gatherP,cCluster->N,(tist-dist));
								addvv(gatherP,P);
							} else {
								movvv(gatherP,P);
							}

							// Compute the corners that are behind the query plane
							cornerStat[0]	=	cornerDist[0] >= 0;
							cornerStat[1]	=	cornerDist[1] >= 0;
							cornerStat[2]	=	cornerDist[2] >= 0;

							// Check if we need to clip the triangle
							if (cornerStat[0] & cornerStat[1] & cornerStat[2]) {
								// No
								numCorners	=	3;
								subvv(corners+0,cTriangle->corners+0,gatherP);	normalizev(corners+0);
								subvv(corners+3,cTriangle->corners+3,gatherP);	normalizev(corners+3);
								subvv(corners+6,cTriangle->corners+6,gatherP);	normalizev(corners+6);
							} else {
								// Yes							
								numCorners	=	0;
								cCorner		=	corners;
								src			=	cTriangle->corners;

								// Perform the actual clipping
								for (i=0;i<3;i++) {
									if (cornerStat[i]) {
										subvv(cCorner,src+i*3,gatherP);
										normalizev(cCorner);
										cCorner	+=	3;
										numCorners++;

										if (cornerStat[plusOne[i]]) {
											// Nothing to output
										} else {
											const float	*in		=	src + i*3;
											const float	*out	=	src + plusOne[i]*3;
											const float	t		=	cornerDist[i] / (cornerDist[i] - cornerDist[plusOne[i]]);

											interpolatev(cCorner,in,out,t);
											subvv(cCorner,gatherP);
											normalizev(cCorner);
											cCorner	+=3;
											numCorners++;
										}
									} else {
										if (cornerStat[plusOne[i]]) {
											const float	*in		=	src + plusOne[i]*3;
											const float	*out	=	src + i*3;
											const float	t		=	cornerDist[plusOne[i]] / (cornerDist[plusOne[i]] - cornerDist[i]);

											interpolatev(cCorner,in,out,t);
											subvv(cCorner,gatherP);
											normalizev(cCorner);
											cCorner	+=3;
											numCorners++;
										} else {
											// Nothing to output
										}
									}
								}

								// Sanity check
								assert(cornerStat[0] | cornerStat[1] | cornerStat[2]);
							}

							// Compute the form factor
							ff	=	0;
							assert(numCorners <= 5);

							const int	*modTable	=	plusOnes[numCorners];
							for (i=0;i<numCorners;i++) {
								const float	*c0		=	corners + i*3;
								const float	*c1		=	corners + modTable[i]*3;
								const float	alpha	=	dotvv(c0,c1);
								const int	index	=	(int) (GATHER_LOOKUP_ENTRIES*(alpha + 1)*0.5);

								assert(dotvv(c0,c0) >= 0.9);
								assert(dotvv(c0,c0) <= 2);

								if ((index < 10) || (index >= (GATHER_LOOKUP_ENTRIES - 10))) {
									// The function is not smooth in this range, compute it the brute force way
									const float	alphat	=	(float) acos(max(min(alpha,1),-1));
									vector		R;

									crossvv(R,c1,c0);
									if (dotvv(R,R) > EPSILON)	{
										normalizev(R);
									} else {
										if (alphat > 2) {
											movvv(R,N);
										} else {
											initv(R,0,0,0);
										}
									}

									mulvf(R,alphat);
									ff					+=	dotvv(R,N);
								} else {
									// The function is smooth, lookup the table
									const float	alphat	=	gatherTable[index];
									vector		R;

									crossvv(R,c1,c0);
									mulvf(R,alphat);
									ff					+=	dotvv(R,N);
								}
							}

							ff	*=	1 / (float) (2*PI);

							// ff = the fraction of the hemisphere that the triangle covers

							// The final sanity check
							if (ff < 0) {
								ff	=	-ff;
								//continue;
							}						

							// At his point we should evaluate the integral of the spherical harmonics over the triangle
#ifdef GATHER_SMOOTH_FF
							{
								const float	ff2	=	-dotvv(D,N)*dotvv(D,cCluster->NdA) / (PI*dotvv(D,D)*dotvv(D,D));
								const float	w	=	lengthv(D) / (4*cCluster->dP);
								const float	t	=	1 + 2*w*w*w - 3*w*w;

								ff				=	t*ff + (1-t)*ff2;
							}
#endif

							assert(cTriangle->C[0] >= 0);
							assert(cTriangle->C[1] >= 0);
							assert(cTriangle->C[2] >= 0);

							// Do the local correction
							local[0]			+=	ff*(cTriangle->C[0] - global[0]);
							local[1]			+=	ff*(cTriangle->C[1] - global[1]);
							local[2]			+=	ff*(cTriangle->C[2] - global[2]);
						} else {
							// Traverse the children
							assert(cCluster->children[0] != NULL);
							assert(cCluster->children[1] != NULL);

							*cstack++	=	cCluster->children[0];
							*cstack++	=	cCluster->children[1];
						}
					} else {
						// If the cluster is too far away, ignore
						if (dotvv(D,D) >= (dP*dP))		continue;

						// Trivial rejects
						if (dotvv(N,D) <= 0)			continue;
						if (dotvv(cCluster->N,D) >= 0)	continue;

						float	ff		=	-dotvv(D,N)*dotvv(D,cCluster->NdA) / (PI*dotvv(D,D)*dotvv(D,D));

						if (ff > 0) {
							if (ff < (float) 0.01) {
								// FIXME: 1 sqruare root + 1 division = potential bottleneck
								const float	w	=	lengthv(D) / dP;
								float		t;

								t				=	1 + 2*w*w*w - 3*w*w;

								assert(t >= 0);
								assert(t <= 1);

								ff				*=	t;

								local[0]		+=	ff*(cCluster->C[0] - global[0]);
								local[1]		+=	ff*(cCluster->C[1] - global[1]);
								local[2]		+=	ff*(cCluster->C[2] - global[2]);
							} else {
								// Traverse the children

								if (cCluster->children[0] != NULL)	*cstack++	=	cCluster->children[0];
								if (cCluster->children[1] != NULL)	*cstack++	=	cCluster->children[1];
							}
						}
					}
				}
			}
		}

		// Combine the global and local estimates
		addvv(C,global,local);

		// Normalize with the BRDF
		C[3]			=	(C[0] + C[1] + C[2]) * (1 / (float) 3);
	}
}






















///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	cachesample
// Description			:	Check if there's a nearby sample
// Return Value			:	-
// Comments				:
// Date last edited		:	10/6/2003
void	CGatherCache::cachesample(float *C,const float *P,const float *N,float dP) {
	CGatherNode			*cNode;
	CGatherNode			**stack;
	CGatherHarmonic		*cHarmonic;
	int					i;

	if (flags & CACHE_WRITE)	{
		C[0]	=	0;
		return;
	}

	// First interpolate the harmonics
	stack		=	nodeStack;
	*stack++	=	root;
	while(stack > nodeStack) {
		cNode	=	*(--stack);

		// Iterate over the samples
		for (cHarmonic=cNode->samples;cHarmonic!=NULL;cHarmonic=cHarmonic->next) {
			vector	D;

			subvv(D,cHarmonic->P,P);

			if (dotvv(D,D) < dP*dP)	{
				C[0]	=	1;
				return;
			}
		}

		// Check the children
		for (i=0;i<8;i++) {
			CGatherNode	*tNode	=	cNode->children[i];

			if (tNode != NULL) {
				const	float	tSide	=	tNode->side;

				if (	((tNode->center[0] + tSide) > P[0])	&&
						((tNode->center[1] + tSide) > P[1])	&&
						((tNode->center[2] + tSide) > P[2])	&&
						((tNode->center[0] - tSide) < P[0])	&&
						((tNode->center[1] - tSide) < P[1])	&&
						((tNode->center[2] - tSide) < P[2])) {
					*stack++	=	tNode;
				}
			}
		}
	}

	C[0]	=	0;
}

















///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	writeNode
// Description			:	Write a cache node onto the disk
// Return Value			:	-
// Comments				:
// Date last edited		:	10/6/2003
void					CGatherCache::writeNode(FILE *out,CGatherNode *node) {
	int				i;
	CGatherHarmonic	*cSample;

	fwrite(node->center,1,sizeof(vector),out);
	fwrite(&node->side,1,sizeof(float),out);

	for (i=0,cSample=node->samples;cSample!=NULL;cSample=cSample->next,i++);

	fwrite(&i,1,sizeof(int),out);

	for (cSample=node->samples;cSample!=NULL;cSample=cSample->next) {
		fwrite(cSample,1,sizeof(CGatherHarmonic),out);
	}

	fwrite(node->children,8,sizeof(CGatherNode *),out);

	for (i=0;i<8;i++) {
		if (node->children[i] != NULL)	writeNode(out,node->children[i]);
	}
}




///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	readNode
// Description			:	Read a cache node from disk
// Return Value			:	-
// Comments				:
// Date last edited		:	10/6/2003
CGatherCache::CGatherNode				*CGatherCache::readNode(FILE *in) {
	int				i;
	CGatherHarmonic	*cSample;
	CGatherNode		*cNode	=	(CGatherNode *) memory->alloc(sizeof(CGatherNode));

	fread(cNode->center,1,sizeof(vector),in);
	fread(&cNode->side,1,sizeof(float),in);

	fread(&i,1,sizeof(int),in);

	cNode->samples			=	NULL;
	for (;i>0;i--) {
		cSample				=	(CGatherHarmonic *) memory->alloc(sizeof(CGatherHarmonic));
		fread(cSample,1,sizeof(CGatherHarmonic),in);

		cSample->next		=	cNode->samples;
		cNode->samples		=	cSample;
	}

	fread(cNode->children,8,sizeof(CGatherNode *),in);

	for (i=0;i<8;i++) {
		if (cNode->children[i] != NULL)	cNode->children[i]	=	readNode(in);
	}

	return cNode;
}





///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	writeCluster
// Description			:	Write a cluster to disk
// Return Value			:	-
// Comments				:
// Date last edited		:	10/6/2003
void							CGatherCache::writeCluster(FILE *out,CGatherCluster *cCluster) {
	fwrite(cCluster,1,sizeof(CGatherCluster),out);

	if (cCluster->triangle != NULL)		fwrite(cCluster->triangle,1,sizeof(CGatherTriangle),out);
	if (cCluster->children[0] != NULL)	writeCluster(out,cCluster->children[0]);
	if (cCluster->children[1] != NULL)	writeCluster(out,cCluster->children[1]);
}

///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	readCluster
// Description			:	Read a cluster from disk
// Return Value			:	-
// Comments				:
// Date last edited		:	10/6/2003
CGatherCache::CGatherCluster	*CGatherCache::readCluster(FILE *in) {
	CGatherCluster	*cCluster	=	newCluster();

	fread(cCluster,1,sizeof(CGatherCluster),in);

	if (cCluster->triangle	  != NULL)	{
		cCluster->triangle	=	newTriangle();
		fread(cCluster->triangle,1,sizeof(CGatherTriangle),in);
	}

	if (cCluster->children[0] != NULL)	cCluster->children[0]	=	readCluster(in);
	if (cCluster->children[1] != NULL)	cCluster->children[1]	=	readCluster(in);

	return cCluster;
}












































///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	compute
// Description			:	Compute the harmonic samples
// Return Value			:	-
// Comments				:
// Date last edited		:	10/12/2003
void			CGatherCache::compute(int numSamples,CShadingPoint **points) {
	vector				P,N;
	float				dP,dPMin,dPMax,dN;
	int					i;
	const CShadingPoint	*cSample;

	// If we have way too many points, forget about clustering
	if (numSamples > 10000) {
		dP		=	INFINITY;
		dN		=	-1;
		dPMin	=	0;
		dPMax	=	0;
	} else {
		// Compute the cluster center and the radius
		initv(P,0,0,0);
		initv(N,0,0,0);
		dP	=	0;
		dN	=	1;

		for (i=0;i<numSamples;i++) {
			cSample	=	points[i];

			addvv(P,cSample->P);
			addvv(N,cSample->N);
		}

		mulvf(P,1 / (float) numSamples);
		normalizev(N);

		for (i=0;i<numSamples;i++) {
			vector	D;

			cSample	=	points[i];

			subvv(D,P,cSample->P);
			dP		=	max(dP,dotvv(D,D));
			dN		=	min(dN,dotvv(N,cSample->N));
		}

		dP		=	(float) sqrt(dP);				// This is the radius of the cluster

		dPMin	=	(lookupParam->minA*P[COMP_Z] + lookupParam->minB);			// This is the minimum acceptable radius
		dPMax	=	(lookupParam->maxA*P[COMP_Z] + lookupParam->maxB);			// This is the maximum acceptable radius
	}

	// Are we too big ?
	if (dP > dPMax) {
		// We're too big, split
		//i	=	partition(numSamples,points,dPMin);
		i	=	partition(numSamples,points,0);
		compute(i,points);
		compute(numSamples-i,points+i);
	} else {
		// Shoot bunch of rays 
		int		numRays;
		vector	C,D;
		float	X[81],Y[27];
		CRay	ray;

		// Clear the matrices
		for (i=0;i<27;i++) {
			X[i]	=	0;
			Y[i]	=	0;
		}
		for (;i<81;i++) {
			X[i]	=	0;
		}

		// Collect the samples
		for (numRays=0;numRays<lookupParam->numSamples;numRays++) {

			// Pick a random shading point
			cSample					=	points[irand() % numSamples];

			// Sample this shading point
			sampleCosineHemisphere(D,cSample->N,(float) (PI/2.0),generator);

			normalizev(D);

			movvv(ray.dir,D);
			movvv(ray.from,cSample->P);
			addvv(ray.to,ray.from,ray.dir);

			ray.invDir[COMP_X]		=	1/ray.dir[COMP_X];
			ray.invDir[COMP_Y]		=	1/ray.dir[COMP_Y];
			ray.invDir[COMP_Z]		=	1/ray.dir[COMP_Z];
			ray.t					=	INFINITY;
			ray.time				=	urand();
			ray.flags				=	ATTRIBUTES_FLAGS_TRACE_VISIBLE;
			ray.tmin				=	lookupParam->bias;
			ray.lastXform			=	NULL;
			ray.object				=	NULL;

			// Trace the ray
			hierarchy->intersect(&ray);
			stats.numIndirectDiffuseRays++;

			// Do we have an intersection that's too close ?
			if (dP > dPMin) {
				if (ray.t < dP) {
					break;
				}
			}

			//if (ray.t < dP) continue;

			// Accumulate the intersection
			if (ray.object != NULL) {
				vector		P,N;
				CAttributes	*attributes	=	ray.object->attributes;
				CPhotonMap	*globalMap;

				if ((globalMap = attributes->globalMap) == NULL) {
					initv(C,1,1,1);
				} else {
					normalizev(N,ray.N);
					mulvf(P,ray.dir,ray.t);
					addvv(P,ray.from);

					stats.numIndirectDiffusePhotonmapLookups++;

					globalMap->lookup(C,P,N,attributes->photonEstimator);
					mulvv(C,attributes->surfaceColor);
				}
			} else {
				initv(C,0,0,0);
			}

			harmonicAccumulate(X,Y,D,C);
		}

		// Did we abort because there was nearby junk
		if (numRays < lookupParam->numSamples) {
			// We should keep on splitting
			//i	=	partition(numSamples,points,dPMin);
			i	=	partition(numSamples,points,0);
			compute(i,points);
			compute(numSamples-i,points+i);
		} else {
			stats.numIndirectDiffuseSamples++;

			int	planar;

			// Save the sample
			CGatherHarmonic	*cHarmonic	=	(CGatherHarmonic *) memory->alloc(sizeof(CGatherHarmonic));
			movvv(cHarmonic->P,P);
			movvv(cHarmonic->N,N);
			cHarmonic->dP				=	dP;
			cHarmonic->dPSmooth			=	dP*lookupParam->cacheSmoothing;
			cHarmonic->dN				=	dN;

			linSolve(X,Y,9,3);
			memcpy(cHarmonic->Y,Y,27*sizeof(float));

			stats.numIndirectDiffuseSamples++;

			// Save the harmonic
			cHarmonic->next		=	allHarmonics;
			allHarmonics		=	cHarmonic;

			// Before we process the geometry, check if the cluster is planar
			if (dN > PLANARITY_NORMAL_THRESHOLD) {
				const float	d	=	dotvv(P,N);

				planar	=	TRUE;
				for (i=0;i<numSamples;i++) {
					const CShadingPoint	*cPoint	=	points[i];
					const float			d2		=	dotvv(cPoint->P,N) - d;
					const float			t		=	dP*PLANARITY_DISTANCE_THRESHOLD;

					// Are we planar ?
					if (d2*d2 > t*t) {
						planar	=	FALSE;
						break;
					}
				}
			} else {
				planar	=	FALSE;
			}

			// We are a leaf node, process the geometry around us
			processGeometry(P,dPMin*lookupParam->cacheSmoothing,planar,N);

#ifdef GATHER_PRINT
			if (FALSE) {
				vector	C;
				int		i;

				initv(C,urand(),urand(),urand());
				normalizev(C);

				for (i=0;i<numSamples;i+=4) {
					cSample					=	points[i];
					printPoint(cSample->P,C);

				}
			}
#endif

		}
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	partition
// Description			:	Partition the shading points into two clusters
// Return Value			:	-
// Comments				:
// Date last edited		:	10/12/2003
int				CGatherCache::partition(int numRays,CShadingPoint **rays,float dP) {
	int						moved;
	CShadingPoint			*s1,*s2;
	vector					P1,P2,N1,N2;
	vector					nP1,nP2,nN1,nN2;
	int						i,j,last;
	vector					D;
	int						iteration;

	// Are we too small ?
	if (numRays == 2)	return 1;

	// Are we too big ?
	if (numRays > 10000) {
		// For large number of samples, don't wory about a very accurate split
		iteration	=	1;
	} else {
		// For small number of samples, worry about an accurate split
		iteration	=	20;
	}

	// Select two random cluster centers that are not the same
	s1	=	rays[irand() % numRays];
	do {
		s2	=	rays[irand() % numRays];

		subvv(D,s1->P,s2->P);
	} while(dotvv(D,D) == 0);

	// Save the cluster centers
	movvv(P1,s1->P);
	movvv(P2,s2->P);
	movvv(N1,s1->N);
	movvv(N2,s2->N);

	// Init the cluster membership
	for (i=0;i<numRays;i++) {
		rays[i]->next	=	(CShadingPoint *) (-1);
	}

	// Perform 2-means clustering
	for (moved=TRUE;(iteration > 0) && (moved==TRUE);iteration--) {
		float			d1,d2;
		CShadingPoint	*cPoint;

		moved		=	FALSE;

		initv(nP1,0,0,0);
		initv(nP2,0,0,0);
		initv(nN1,0,0,0);
		initv(nN2,0,0,0);

		// Partition the points into two clusters
		for (j=0,i=0;i<numRays;i++) {
			cPoint	=	rays[i];

			subvv(D,P1,cPoint->P);
			d1	=	lengthv(D) + dP*(1-dotvv(cPoint->N,N1));
			subvv(D,P2,cPoint->P);
			d2	=	lengthv(D) + dP*(1-dotvv(cPoint->N,N2));

			if (d1 < d2) {
				if (cPoint->next != (CShadingPoint *) 1) {
					moved				=	TRUE;
					cPoint->next		=	(CShadingPoint *) 1;
				}

				addvv(nP1,rays[i]->P);
				addvv(nN1,rays[i]->N);
				j++;
			} else {
				if (cPoint->next != (CShadingPoint *) 2) {
					moved				=	TRUE;
					cPoint->next		=	(CShadingPoint *) 2;
				}

				addvv(nP2,rays[i]->P);
				addvv(nN2,rays[i]->N);
			}
		}

		mulvf(P1,nP1,1/(float) j);
		mulvf(P2,nP2,1/(float) (numRays-j));
		normalizev(N1,nN1);
		normalizev(N2,nN2);

	}

	// Separate the two clusters
	for (last=0,i=0;i<numRays;i++) {
		if (rays[i]->next == (CShadingPoint *) 1) {
			CShadingPoint	*t	=	rays[last];
			rays[last++]		=	rays[i];
			rays[i]				=	t;
		}
	}

	if (rays[last]->next == (CShadingPoint *) 1)	last++;

#ifdef _DEBUG
	// Sanity check here
	for (i=0;i<last;i++)	assert(rays[i]->next == (CShadingPoint *) 1);
	for (;i<numRays;i++)	assert(rays[i]->next == (CShadingPoint *) 2);
#endif

	if ((last == 0) || (numRays == last)) {
		return partition(numRays,rays,dP);
	} else {
		return last;
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	processGeometry
// Description			:	Process the geometry and split it as necessary
// Return Value			:	-
// Comments				:
// Date last edited		:	10/12/2003
void			CGatherCache::processGeometry(const float *P,float dP,int planar,const float *N) {
	// Locate the nearby uncomputed triangles
	CGatherCluster		*stackBase[GATHER_STACKLENGTH];	// FIXME: The 1000 here looks very bad
	CGatherCluster		**stack;
	CGatherCluster		*cCluster;
	int					numCounted=0;

	if (rootCluster == NULL)	return;

	stack		=	stackBase;
	*stack++	=	rootCluster;
	while(stack > stackBase) {
		cCluster	=	*(--stack);

		// Trivial reject for planar clusters
		if (planar) {
			if (cCluster->planar) {
				if (dotvv(N,cCluster->N) > PLANARITY_NORMAL_THRESHOLD) continue;
			}
		}

		// Is the cluster an individual triangle ?
		if (cCluster->triangle != NULL) {
			CGatherTriangle *cTriangle	=	cCluster->triangle;
			const float		dPMin		=	(lookupParam->minA*cCluster->P[COMP_Z] + lookupParam->minB)*lookupParam->geometryFactor;

			// Are we small enough ?
			if (cCluster->dP < dPMin) {
				CGatherCluster	*pCluster,*ppCluster;
				CPhotonMap		*globalMap;

				// Record this triangle into the list of microtriangles we have
				microTriangles->push(cTriangle);

				// Lookup the global photon map on the triangle
				if ((globalMap = cTriangle->attributes->globalMap) != NULL) {
					stats.numIndirectDiffusePhotonmapLookups++;

					globalMap->lookup(cTriangle->C,cTriangle->P,cTriangle->N,cTriangle->attributes->photonEstimator);
					mulvv(cTriangle->C,cTriangle->attributes->surfaceColor);
				} else {
#ifdef GATHER_OCCLUSION
					initv(cTriangle->C,1,1,1);
#else
					initv(cTriangle->C,1,1,1);
#endif
				}


#ifdef GATHER_PRINT
				// Print the triangle out
				printPoint(cTriangle->P,cTriangle->C);
				printTriangle(cTriangle->corners+0,cTriangle->corners+3,cTriangle->corners+6);
#endif

				// Delete the cluster
				if ((pCluster=cCluster->parent) != NULL) {
					CGatherCluster	*oCluster;

					if (pCluster->children[0] == cCluster) {
						oCluster	=	pCluster->children[1];
					} else {
						assert(pCluster->children[1] == cCluster);
						oCluster	=	pCluster->children[0];
					}

					if ((ppCluster=pCluster->parent) != NULL) {
						if (ppCluster->children[0] == pCluster) {
							ppCluster->children[0]	=	oCluster;
						} else {
							assert(ppCluster->children[1] == pCluster);
							ppCluster->children[1]	=	oCluster;
						}

						oCluster->parent	=	ppCluster;
						deleteCluster(pCluster);
					} else {
						rootCluster			=	oCluster;
						oCluster->parent	=	NULL;
					}
				} else {
					rootCluster	=	NULL;
				}

				// Ditch the cluster
				deleteCluster(cCluster);
			} else {
				// No we're too big add this cluster into the to-process pile
				CGatherTriangle	*children[4];
				int				numChildren;
				CGatherCluster	*nCluster;
				
				assert(cTriangle != NULL);

				numChildren	=	split(children,cTriangle,dPMin);
				nCluster	=	cluster(numChildren,children);

				// Swap cCluster with nCluster
				if (cCluster->parent) {
					if (cCluster->parent->children[0] == cCluster) {
						cCluster->parent->children[0]	=	nCluster;
					} else {
						assert(cCluster->parent->children[1] == cCluster);
						cCluster->parent->children[1]	=	nCluster;
					}

					nCluster->parent	=	cCluster->parent;
				} else {
					rootCluster			=	nCluster;
					nCluster->parent	=	NULL;
				}

				// Ditch the triangle and the cluster
				deleteTriangle(cTriangle);
				deleteCluster(cCluster);

				// Keep going
				*stack++				=	nCluster;
			}
		} else {
			CGatherCluster	*nCluster;

			// No descent into the children
			if ((nCluster = cCluster->children[0]) != NULL) {
				vector	D;
				float	t;

				subvv(D,P,nCluster->P);
				t	=	nCluster->dP + dP;
				if (dotvv(D,D) < (t*t)) {
					*stack++	=	nCluster;
				}
			}

			if ((nCluster = cCluster->children[1]) != NULL) {
				vector	D;
				float	t;

				subvv(D,P,nCluster->P);
				t	=	nCluster->dP + dP;
				if (dotvv(D,D) < (t*t)) {
					*stack++	=	nCluster;
				}
			}

			assert((stack - stackBase) < GATHER_STACKLENGTH);
		}
	}
}






///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	cluster
// Description			:	Cluster the triangles into two clusters
// Return Value			:	-
// Comments				:
// Date last edited		:	10/12/2003
CGatherCache::CGatherCluster *CGatherCache::cluster(int numTriangles,CGatherTriangle **triangles) {
	CGatherCluster	*cCluster	=	NULL;
	int				i;
	vector			P,N,C,NdA;
	float			dA,dP,dN;

	initv(C,0,0,0);
	initv(P,0,0,0);
	initv(N,0,0,0);
	initv(NdA,0,0,0);
	dP		=	0;
	dN		=	1;
	dA		=	0;

	// Compute the area weighted center of the cluster
	if (numTriangles == 1) {
		const CGatherTriangle	*cTriangle	=	triangles[0];

		movvv(P,cTriangle->P);
		movvv(C,cTriangle->C);
		mulvf(NdA,cTriangle->N,cTriangle->dA);
		movvv(N,cTriangle->N);
		dA		=	cTriangle->dA;
	} else {
		for (i=0;i<numTriangles;i++) {
			const CGatherTriangle	*cTriangle	=	triangles[i];

			P[0]	+=	cTriangle->corners[0+0]*cTriangle->dA;
			P[1]	+=	cTriangle->corners[0+1]*cTriangle->dA;
			P[2]	+=	cTriangle->corners[0+2]*cTriangle->dA;

			P[0]	+=	cTriangle->corners[3+0]*cTriangle->dA;
			P[1]	+=	cTriangle->corners[3+1]*cTriangle->dA;
			P[2]	+=	cTriangle->corners[3+2]*cTriangle->dA;

			P[0]	+=	cTriangle->corners[6+0]*cTriangle->dA;
			P[1]	+=	cTriangle->corners[6+1]*cTriangle->dA;
			P[2]	+=	cTriangle->corners[6+2]*cTriangle->dA;

			NdA[0]	+=	cTriangle->N[0]*cTriangle->dA;
			NdA[1]	+=	cTriangle->N[1]*cTriangle->dA;
			NdA[2]	+=	cTriangle->N[2]*cTriangle->dA;

			C[0]	+=	cTriangle->C[0]*cTriangle->dA;
			C[1]	+=	cTriangle->C[1]*cTriangle->dA;
			C[2]	+=	cTriangle->C[2]*cTriangle->dA;

			dA		+=	cTriangle->dA;
		}

		if (dA < EPSILON) {
			initv(C,0,0,0);
			initv(P,0,0,0);
			initv(N,0,0,0);

			for (i=0;i<numTriangles;i++) {
				const CGatherTriangle	*cTriangle	=	triangles[i];

				P[0]	+=	cTriangle->corners[0+0];
				P[1]	+=	cTriangle->corners[0+1];
				P[2]	+=	cTriangle->corners[0+2];

				P[0]	+=	cTriangle->corners[3+0];
				P[1]	+=	cTriangle->corners[3+1];
				P[2]	+=	cTriangle->corners[3+2];

				P[0]	+=	cTriangle->corners[6+0];
				P[1]	+=	cTriangle->corners[6+1];
				P[2]	+=	cTriangle->corners[6+2];

				N[0]	+=	cTriangle->N[0];
				N[1]	+=	cTriangle->N[1];
				N[2]	+=	cTriangle->N[2];

				C[0]	+=	cTriangle->C[0];
				C[1]	+=	cTriangle->C[1];
				C[2]	+=	cTriangle->C[2];
			}

			mulvf(P,1 / (float) (3*3));
			mulvf(C,1 / (float) 3);
			normalizev(N);
		} else {
			mulvf(P,1 / (3*dA));
			mulvf(C,1 / dA);
			normalizev(N,NdA);
		}
	}

	// Compute the maximum deviation from the cluster
	for (i=0;i<numTriangles;i++) {
		const CGatherTriangle	*cTriangle	=	triangles[i];
		vector			D;

		subvv(D,P,cTriangle->corners+0);
		dP	=	max(dP,dotvv(D,D));

		subvv(D,P,cTriangle->corners+3);
		dP	=	max(dP,dotvv(D,D));

		subvv(D,P,cTriangle->corners+6);
		dP	=	max(dP,dotvv(D,D));

		dN	=	min(dN,dotvv(N,cTriangle->N));
	}
	dP				=	(float) sqrt(dP);

	// Save the data
	cCluster		=	newCluster();
	movvv(cCluster->P,P);
	movvv(cCluster->N,N);
	movvv(cCluster->C,C);
	movvv(cCluster->NdA,NdA);
	cCluster->dP	=	dP;
	cCluster->dN	=	dN;

	// Are we close to being planar ?
	if (dN > PLANARITY_NORMAL_THRESHOLD) {
		float	d	=	dotvv(P,N);

		cCluster->planar	=	TRUE;

		for (i=0;i<numTriangles;i++) {
			CGatherTriangle	*cTriangle	=	triangles[i];
			float			d2			=	dotvv(cTriangle->P,N) - d;

			if (d2*d2 > dP*dP*(float) 0.00001) {
				cCluster->planar		=	FALSE;
				break;
			}
		}
	} else {
		cCluster->planar	=	FALSE;
	}

	if (numTriangles == 1) {
		cCluster->triangle		=	triangles[0];
		cCluster->children[0]	=	NULL;
		cCluster->children[1]	=	NULL;
	} else {
		CGatherCluster	*child0,*child1;

		i						=	partition(numTriangles,triangles);
		child0					=	cluster(i,triangles);
		child1					=	cluster(numTriangles-i,triangles+i);

		cCluster->triangle		=	NULL;
		cCluster->children[0]	=	child0;
		cCluster->children[1]	=	child1;
		child0->parent			=	cCluster;
		child1->parent			=	cCluster;
	}

	return cCluster;
}

///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	partition
// Description			:	Partition bunch of triangles into two groups
// Return Value			:	-
// Comments				:
// Date last edited		:	10/12/2003
int							CGatherCache::partition(int numTriangles,CGatherTriangle **triangles) {
	// Perform the 2-means clustering
	int						moved;
	CGatherTriangle			*s1,*s2;
	vector					P1,P2,N1,N2;
	vector					nP1,nP2,nN1,nN2;
	int						i,j,last;
	vector					D;
	int						iteration;

	// Do we have too few triangles ?
	if (numTriangles == 2)	return 1;

	// Choose two random triangles
	for (iteration = 20;iteration > 0;iteration--) {
		s1	=	triangles[irand() % numTriangles];
		s2	=	triangles[irand() % numTriangles];

		subvv(D,s1->P,s2->P);
		if (dotvv(D,D) > 0)	break;
	}
	
	if (iteration == 0)	return numTriangles / 2;

	// Do we have too many triangles ?
	if (numTriangles > 10000) {
		iteration	=	1;
	} else {
		iteration	=	20;
	}

	movvv(P1,s1->P);
	movvv(N1,s1->N);
	movvv(P2,s2->P);
	movvv(N2,s2->N);

	// Init the cluster membership
	for (i=0;i<numTriangles;i++) {
		triangles[i]->cluster	=	-1;
	}

	// Perform 2-means clustering
	for (moved=TRUE;(iteration > 0) && (moved==TRUE);iteration--) {
	//for (moved=TRUE;moved==TRUE;) {
		float			d1,d2;
		CGatherTriangle	*cPoint;

		moved		=	FALSE;

		initv(nP1,0,0,0);
		initv(nP2,0,0,0);
		initv(nN1,0,0,0);
		initv(nN2,0,0,0);

		// Partition the points into two
		for (j=0,i=0;i<numTriangles;i++) {
			cPoint	=	triangles[i];

			subvv(D,P1,cPoint->P);
			d1	=	lengthv(D);
			subvv(D,P2,cPoint->P);
			d2	=	lengthv(D);

			if (d1 < d2) {
				if (cPoint->cluster != 1) {
					moved				=	TRUE;
					cPoint->cluster		=	1;
				}

				addvv(nP1,triangles[i]->P);
				addvv(nN1,triangles[i]->N);
				j++;
			} else {
				if (cPoint->cluster != 2) {
					moved				=	TRUE;
					cPoint->cluster		=	2;
				}

				addvv(nP2,triangles[i]->P);
				addvv(nN2,triangles[i]->N);
			}


			assert(nN1[0] > -100000);
			assert(nN2[0] > -100000);
		}

		mulvf(P1,nP1,1/(float) j);
		mulvf(P2,nP2,1/(float) (numTriangles-j));
		normalizev(N1,nN1);
		normalizev(N2,nN2);
	}

	// Separate the two clusters
	for (last=0,i=0;i<numTriangles;i++) {
		if (triangles[i]->cluster == 1) {
			CGatherTriangle		*t	=	triangles[last];
			triangles[last++]		=	triangles[i];
			triangles[i]			=	t;
		}
	}

	if (triangles[last]->cluster == 1)	last++;

#ifdef _DEBUG
	for (i=0;i<last;i++)		assert(triangles[i]->cluster == 1);
	for (;i<numTriangles;i++)	assert(triangles[i]->cluster == 2);
#endif

	if ((last == 0) || (numTriangles == last)) {
		return partition(numTriangles,triangles);
	} else {
		return last;
	}
}


///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	split
// Description			:	Split a triangle
// Return Value			:	-
// Comments				:
// Date last edited		:	10/12/2003
int					CGatherCache::split(CGatherTriangle **children,const CGatherTriangle *cTriangle,float dP) {
	vector	D;
	float	L0,L1,L2;
	float	M0,M1,M2;
	int		i0,i1,i2;

	// Compute the triangle edge lengths
	subvv(D,cTriangle->corners+3,cTriangle->corners+0);
	L0	=	dotvv(D,D);
	subvv(D,cTriangle->corners+6,cTriangle->corners+3);
	L1	=	dotvv(D,D);
	subvv(D,cTriangle->corners+0,cTriangle->corners+6);
	L2	=	dotvv(D,D);

	// Sort the edge lengths
	if (L0 > L1) {
		if (L2 > L0) {
			// L2 > L0 > L1
			i0	=	2;	M0	=	L2;
			i1	=	0;	M1	=	L0;
			i2	=	1;	M2	=	L1;
		} else {
			if (L2 > L1) {
				// L0 > L2 > L1
				i0	=	0;	M0	=	L0;
				i1	=	2;	M1	=	L2;
				i2	=	1;	M2	=	L1;
			} else {
				// L0 > L1 > L2
				i0	=	0;	M0	=	L0;
				i1	=	1;	M1	=	L1;
				i2	=	2;	M2	=	L2;
			}
		}
	} else {
		if (L2 > L1) {
			// L2 > L1 > L0
			i0	=	2;	M0	=	L2;
			i1	=	1;	M1	=	L1;
			i2	=	0;	M2	=	L0;
		} else {
			if (L2 > L0) {
				// L1 > L2 > L0
				i0	=	1;	M0	=	L1;
				i1	=	2;	M1	=	L2;
				i2	=	0;	M2	=	L0;
			} else {
				// L1 > L0 > L2
				i0	=	1;	M0	=	L1;
				i1	=	0;	M1	=	L0;
				i2	=	2;	M2	=	L2;
			}
		}
	}

	// Do some sanity check
	assert(M0 >= M1);
	assert(M1 >= M2);
	assert(M2 >= 0);

	// This macro is used to create a triangle
#define processTriangle(__P0,__P1,__P2,__index)				\
	nTriangle				=	newTriangle();				\
	nTriangle->attributes	=	cTriangle->attributes;		\
	nTriangle->dA			=	cTriangle->dA / (float) 4;	\
	movvv(nTriangle->C,cTriangle->C);						\
	movvv(nTriangle->N,cTriangle->N);						\
	movvv(nTriangle->corners+0,__P0);						\
	movvv(nTriangle->corners+3,__P1);						\
	movvv(nTriangle->corners+6,__P2);						\
	addvv(nTriangle->P,__P0,__P1);							\
	addvv(nTriangle->P,__P2);								\
	mulvf(nTriangle->P,1 / (float) 3);						\
	children[__index]		=	nTriangle;

	// Check the edge lengths and figure out what king of a split we'd like to make
	if (M0 > (float) (1.5*1.5)*M1) {
		// 2 Way split
		const int		c0	=	i0;
		const int		c1	=	(c0+1) % 3;
		const int		c2	=	(c1+1) % 3;
		const float		*P0	=	cTriangle->corners + c0*3;
		const float		*P1	=	cTriangle->corners + c1*3;
		const float		*P2	=	cTriangle->corners + c2*3;
		CGatherTriangle	*nTriangle;
		vector			P01;

		// Split the triangle
		interpolatev(P01,P0,P1,(float) 0.5);

		processTriangle(P0,P01,P2,0);
		processTriangle(P01,P1,P2,1);

		return 2;
	} else if (M2 < M1 / (float) (2*2)) {
		// 3 Way split
		const int		c0	=	i2;
		const int		c1	=	(c0+1) % 3;
		const int		c2	=	(c1+1) % 3;
		const float		*P0	=	cTriangle->corners + c0*3;
		const float		*P1	=	cTriangle->corners + c1*3;
		const float		*P2	=	cTriangle->corners + c2*3;
		CGatherTriangle	*nTriangle;
		vector			P12,P20;
		const float		ml	=	min(max(M2,dP*dP),M1 / (float) (2*2));
		const float		t	=	(float) sqrt(ml / M1);

		// Split the triangle
		interpolatev(P12,P1,P2,(float) t);
		interpolatev(P20,P0,P2,(float) t);

		processTriangle(P0,P12,P20,0);
		processTriangle(P0,P1,P12,1);
		processTriangle(P12,P2,P20,2);

		return 3;
	} else {
		// 4 Way split
		const float		*P0	=	cTriangle->corners + 0;
		const float		*P1	=	cTriangle->corners + 3;
		const float		*P2	=	cTriangle->corners + 6;
		CGatherTriangle	*nTriangle;
		vector			P01,P12,P20;

		// Split the triangle
		interpolatev(P01,P0,P1,(float) 0.5);
		interpolatev(P12,P1,P2,(float) 0.5);
		interpolatev(P20,P2,P0,(float) 0.5);

		processTriangle(P0,P01,P20,0);
		processTriangle(P20,P01,P12,1);
		processTriangle(P01,P1,P12,2);
		processTriangle(P20,P12,P2,3);

		return 4;
	}

#undef processTriangle
}



///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	depth
// Description			:	Compute the depth the cluster tree
// Return Value			:	-
// Comments				:
// Date last edited		:	12/10/2003
int				CGatherCache::depth(const CGatherCluster *cCluster) {
	int	d;

	if (cCluster->children[0] != NULL)	d	=	depth(cCluster->children[0]);
	else								d	=	0;

	if (cCluster->children[1] != NULL)	d	=	max(d,depth(cCluster->children[1]));

	return d+1;
}





///////////////////////////////////////////////////////////////////////
// Class				:	CGatherCache
// Method				:	split
// Description			:	Split a triangle
// Return Value			:	-
// Comments				:
// Date last edited		:	10/12/2003
void			CGatherCache::printCluster(CGatherCluster *cCluster,int depth) {

#ifdef GATHER_PRINT
	if (cCluster->triangle != NULL) {
		CGatherTriangle	*cTriangle	=	cCluster->triangle;

		printTriangle(cTriangle->corners+0,cTriangle->corners+3,cTriangle->corners+6);
	} else {
		if (depth == 0) {
			vector	C;

			initv(C,urand(),urand(),urand());
			printPoint(cCluster->P,C);
		}

		if (cCluster->children[0] != NULL) printCluster(cCluster->children[0],depth-1);
		if (cCluster->children[1] != NULL) printCluster(cCluster->children[1],depth-1);
	}
#endif

}


