//////////////////////////////////////////////////////////////////////
//
//                             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				:	explosion.cpp
//  Classes				:	CVolume
//  Description			:	Models a volume
//
////////////////////////////////////////////////////////////////////////
#include "volume.h"
#include "memory.h"
#include "stats.h"
#include "hierarchy.h"
#include "random.h"


///////////////////////////////////////////////////////////////////////
// Class				:	CVolume::CIndirectNode
// Method				:	~CIndirectNode
// Description			:	Dtor
// Return Value			:	-
// Comments				:
// Date last edited		:	9/28/2002
CVolume::CIndirectNode::~CIndirectNode() {
	if (children[0] != NULL)	delete children[0];
	if (children[1] != NULL)	delete children[1];
	if (children[2] != NULL)	delete children[2];
	if (children[3] != NULL)	delete children[3];
	if (children[4] != NULL)	delete children[4];
	if (children[5] != NULL)	delete children[5];
	if (children[6] != NULL)	delete children[6];
	if (children[7] != NULL)	delete children[7];
}

///////////////////////////////////////////////////////////////////////
// Class				:	CVolume
// Method				:	CVolume
// Description			:	Ctor
// Return Value			:	-
// Comments				:
// Date last edited		:	9/28/2002
CVolume::CVolume(CAttributes *a,matrix *from,const char *n) : CFileResource(n) {
	int				i,j,k;
	const	float	*cP;
	float			maxSide;
	vector			D;
	int				np;
	float			*P;
	float			*mass;
	float			*density;
	float			*temperature;
	FILE			*in	=	fopen(n,"rb");

	memBegin();

	if (in != NULL) {
		int		version;
		float	*cP;

		fread(&version,sizeof(int),1,in);
		fread(&np,sizeof(int),1,in);

		P				=	(float *)	ralloc(np*3*sizeof(float));
		density			=	(float *)	ralloc(np*sizeof(float));
		mass			=	(float *)	ralloc(np*sizeof(float));
		temperature		=	(float *)	ralloc(np*sizeof(float));

		fread(P,			sizeof(float),3*np,in);
		fread(density,		sizeof(float),np,in);
		fread(mass,			sizeof(float),np,in);
		fread(temperature,	sizeof(float),np,in);

		fclose(in);

		for (cP=P,i=np;i>0;i--,cP+=3) {
			mulmp(cP,*from,cP);
		}
	} else {
		np	=	0;
	}

	side			=	(float) 0.01;
	stepSize		=	side*(float) 0.1;
	initv(bmin,INFINITY,INFINITY,INFINITY);
	initv(bmax,-INFINITY,-INFINITY,-INFINITY);

	for (cP=P,i=0;i<np;i++,cP+=3) {
		addBox(bmin,bmax,cP);
	}

	subvv(D,bmax,bmin);
	maxSide			=	max(max(D[0],D[1]),D[2]);
	numVoxels		=	(int) ceil(maxSide / side);

	addvv(D,bmax,bmin);
	mulvf(D,1/(float) 2);

	bmin[COMP_X]	=	D[COMP_X] - numVoxels*side/(float) 2;
	bmin[COMP_Y]	=	D[COMP_Y] - numVoxels*side/(float) 2;
	bmin[COMP_Z]	=	D[COMP_Z] - numVoxels*side/(float) 2;
	bmax[COMP_X]	=	bmin[COMP_X] + numVoxels*side;
	bmax[COMP_Y]	=	bmin[COMP_Y] + numVoxels*side;
	bmax[COMP_Z]	=	bmin[COMP_Z] + numVoxels*side;
	volume			=	side*side*side;
	initv(emittedEnergy,0,0,0);

	grids			=	new CVoxel**[numVoxels+2];
	for (i=0;i<numVoxels+2;i++) {
		CVoxel		**cVoxels;
		cVoxels	=	grids[i]	=	new	CVoxel*[numVoxels+2];

		for (j=0;j<numVoxels+2;j++) {
			CVoxel	*tVoxel;
			tVoxel	=	cVoxels[j]			=	new CVoxel[numVoxels+2];

			for (k=0;k<numVoxels+2;k++) {
				tVoxel->density				=	0;
				initv(tVoxel->emit,0,0,0);
				initv(tVoxel->indirect,0,0,0);

				tVoxel++;
			}
		}
	}

	maxIndirectDepth	=	(int) ceil(log(numVoxels) / log(2.0)) + 1;
	root				=	NULL;
	for (i=0;i<np;i++) {
		int		x,y,z;
		CVoxel	*cVoxel;
		vector	e;

		x	=	(int) ((P[COMP_X] - bmin[COMP_X]) / side);
		y	=	(int) ((P[COMP_Y] - bmin[COMP_Y]) / side);
		z	=	(int) ((P[COMP_Z] - bmin[COMP_Z]) / side);
		x++;
		y++;
		z++;

		assert(x >= 0);
		assert(y >= 0);
		assert(z >= 0);
		assert(x <= (numVoxels+1));
		assert(y <= (numVoxels+1));
		assert(z <= (numVoxels+1));

		cVoxel				=	&grids[x][y][z];

		mulvf(e,mass[i]/density[i]);	// Multiply with the volume of a particle

		insertIndirect(P,e);
		addvv(cVoxel->emit,e);
		addvv(emittedEnergy,e);
		cVoxel->density		+=	mass[i];
		P					+=	3;
	}

	if (root == NULL) {
		root	=	new CIndirectNode;
		addvv(root->middle,bmin,bmax);
		mulvf(root->middle,1 / (float) 2);
		initv(root->emit,0,0,0);
		root->side					=	side;
		root->children[0]			=	NULL;
		root->children[1]			=	NULL;
		root->children[2]			=	NULL;
		root->children[3]			=	NULL;
		root->children[4]			=	NULL;
		root->children[5]			=	NULL;
		root->children[6]			=	NULL;
		root->children[7]			=	NULL;
	}


	// Update the density
	for (i=0;i<numVoxels+2;i++) {
		CVoxel		**cVoxels;
		cVoxels	=	grids[i];

		for (j=0;j<numVoxels+2;j++) {
			CVoxel	*tVoxel;
			tVoxel	=	cVoxels[j];

			for (k=0;k<numVoxels+2;k++) {
				tVoxel->density				/=	volume;
				tVoxel++;
			}
		}
	}

	memEnd();
}

///////////////////////////////////////////////////////////////////////
// Class				:	CVolume
// Method				:	~CVolume
// Description			:	Dtor
// Return Value			:	-
// Comments				:
// Date last edited		:	9/28/2002
CVolume::~CVolume() {
	int	i,j;

	for (i=0;i<numVoxels+2;i++) {
		for (j=0;j<numVoxels+2;j++) {
			delete [] grids[i][j];
		}
		delete [] grids[i];
	}
	delete [] grids;

	if (root != NULL) {
		delete root;
	}
}



///////////////////////////////////////////////////////////////////////
// Class				:	CVolume
// Method				:	properties
// Description			:	Evaluate the volume properties
// Return Value			:	-
// Comments				:
// Date last edited		:	9/28/2002
void		CVolume::properties(float &density,const float *P) {
	int		x,y,z;

	x	=	(int) ((P[COMP_X] - bmin[COMP_X]) / side);
	y	=	(int) ((P[COMP_Y] - bmin[COMP_Y]) / side);
	z	=	(int) ((P[COMP_Z] - bmin[COMP_Z]) / side);
	x++;
	y++;
	z++;

	assert(x >= 0);
	assert(y >= 0);
	assert(z >= 0);
	assert(x <= (numVoxels+1));
	assert(y <= (numVoxels+1));
	assert(z <= (numVoxels+1));


	density					=	grids[x][y][z].density;
}

///////////////////////////////////////////////////////////////////////
// Class				:	CVolume
// Method				:	emit
// Description			:	Emit photons from the explosion
// Return Value			:	-
// Comments				:
// Date last edited		:	9/28/2002
int			CVolume::emit(int numPhotons,float *P,float *C,float *scale) {
	int		numEmitted	=	0;
	int		i,j,k;
	float	totalEnergy	=	0;
	float	volume		=	side*side*side;
	float	cx,cy,cz;
	float	remPoints;	// The remaining number of points
	vector	remEnergy;	// The remaining energy
	float	perEnergy;

	numPhotons--;

	totalEnergy	=	lengthv(emittedEnergy);
	perEnergy	=	totalEnergy / (float) numPhotons;

	if (totalEnergy < EPSILON)	return 0;

	initv(scale,perEnergy,perEnergy,perEnergy);

	remPoints			=	0;
	initv(remEnergy,0,0,0);
	for (cx=bmin[COMP_X]-side,i=0;i<numVoxels+2;i++,cx+=side) {
		CVoxel		**cVoxels;
		cVoxels	=	grids[i];

		for (cy=bmin[COMP_Y]-side,j=0;j<numVoxels+2;j++,cy+=side) {
			CVoxel	*tVoxel;
			tVoxel	=	cVoxels[j];

			for (cz=bmin[COMP_Z]-side,k=0;k<numVoxels+2;k++,cz+=side) {
				vector	eng;

				remPoints		+=	(float) sqrt(dotvv(tVoxel->emit,tVoxel->emit))*(float) numPhotons / totalEnergy;
				addvv(remEnergy,tVoxel->emit);

				mulvf(eng,remEnergy,1/remPoints);

				while ((remPoints > 1) && (numPhotons > 0)) {
					P[COMP_X]	=	urand() * side + cx;
					P[COMP_Y]	=	urand() * side + cy;
					P[COMP_Z]	=	urand() * side + cz;
					mulvf(C,eng,1/perEnergy);

					numEmitted++;
					numPhotons--;

					P			+=	3;
					C			+=	3;
					remPoints--;
					subvv(remEnergy,eng);
				}

				tVoxel++;
			}
		}
	}

	return	numEmitted;
}



///////////////////////////////////////////////////////////////////////
// Class				:	CVolume
// Method				:	insertIndirect
// Description			:	Compute something
// Return Value			:	-
// Comments				:
// Date last edited		:	9/28/2002
void	CVolume::insertIndirect(const float *P,const float *E) {
	float			cSide	=	side*numVoxels;
	CIndirectNode	*cNode	=	root;
	CIndirectNode	*pNode	=	NULL;
	int				pChild	=	0;
	vector			bmin,bmax;

	movvv(bmin,this->bmin);
	movvv(bmax,this->bmax);

	while(cSide > side) {
		if (cNode == NULL) {
			cNode						=	new CIndirectNode;
			addvv(cNode->middle,bmin,bmax);
			mulvf(cNode->middle,1 / (float) 2);
			initv(cNode->emit,0,0,0);
			cNode->side					=	cSide;
			cNode->children[0]			=	NULL;
			cNode->children[1]			=	NULL;
			cNode->children[2]			=	NULL;
			cNode->children[3]			=	NULL;
			cNode->children[4]			=	NULL;
			cNode->children[5]			=	NULL;
			cNode->children[6]			=	NULL;
			cNode->children[7]			=	NULL;
			if (pNode == NULL)	root	=	cNode;
			else {
				pNode->children[pChild]	=	cNode;
			}
		}

		addvv(cNode->emit,E);
		if (P[COMP_X] > cNode->middle[COMP_X]) {
			bmin[COMP_X]	=	cNode->middle[COMP_X];
			if (P[COMP_Y] > cNode->middle[COMP_Y]) {
				bmin[COMP_Y]	=	cNode->middle[COMP_Y];
				if (P[COMP_Z] > cNode->middle[COMP_Z]) {
					pChild			=	0;	
					bmin[COMP_Z]	=	cNode->middle[COMP_Z];
				} else {
					pChild			=	1;
					bmax[COMP_Z]	=	cNode->middle[COMP_Z];
				}
			} else {
				bmax[COMP_Y]	=	cNode->middle[COMP_Y];
				if (P[COMP_Z] > cNode->middle[COMP_Z]) {
					pChild			=	2;
					bmin[COMP_Z]	=	cNode->middle[COMP_Z];
				} else {
					pChild			=	3;
					bmax[COMP_Z]	=	cNode->middle[COMP_Z];
				}
			}
		} else {
			bmax[COMP_X]	=	cNode->middle[COMP_X];
			if (P[COMP_Y] > cNode->middle[COMP_Y]) {
				bmin[COMP_Y]	=	cNode->middle[COMP_Y];
				if (P[COMP_Z] > cNode->middle[COMP_Z]) {
					pChild			=	4;
					bmin[COMP_Z]	=	cNode->middle[COMP_Z];
				} else {
					pChild			=	5;
					bmax[COMP_Z]	=	cNode->middle[COMP_Z];
				}
			} else {
				bmax[COMP_Y]	=	cNode->middle[COMP_Y];
				if (P[COMP_Z] > cNode->middle[COMP_Z]) {
					pChild			=	6;
					bmin[COMP_Z]	=	cNode->middle[COMP_Z];
				} else {
					pChild			=	7;
					bmax[COMP_Z]	=	cNode->middle[COMP_Z];
				}
			}
		}

		pNode	=	cNode;
		cNode	=	pNode->children[pChild];
		cSide	*=	(float) 0.5;
	}
}

///////////////////////////////////////////////////////////////////////
// Class				:	CVolume
// Method				:	estimate
// Description			:	Estimate the indirect illumination at a point
// Return Value			:	-
// Comments				:
// Date last edited		:	9/28/2002
void	CVolume::estimate(float *dest,const float *P,const float *N,float scatter) {
	float			cSide	=	side;
	CIndirectNode	**stack,**stackBase;
	CIndirectNode	*cNode;
	vector			D;
	float			l;
	vector			from;
	vector			to;

	initv(dest,0,0,0);

	stack		=	stackBase	=	(CIndirectNode **) alloca(maxIndirectDepth*8*sizeof(CIndirectNode *));
	*stack++	=	root;
	while(stack > stackBase) {
		cNode	=	*(--stack);

		subvv(D,P,cNode->middle);
		l		=	lengthv(D);
		if (TRUE) {
			//if ((cNode->side / l) < 0.5) {
			// Estimate at this level
			float	t		=	l;
			float	tmin	=	0;
			float	density;
			float	coef	=	1 / l;

			mulvf(D,coef);
			movvv(from,cNode->middle);
			addvv(to,from,D);
			mulvf(D,stepSize);

			coef			*=	(float) 0.00001 / l;

			if (hierarchyIntersectBox(bmin,bmax,from,to,tmin,t)) {
				while(t > 0) {
					properties(density,from);
					coef	*=	(float) exp(-density*scatter*stepSize);
					addvv(from,D);
					t		-=	stepSize;
				}

				dest[0]	+=	coef*cNode->emit[0];
				dest[1]	+=	coef*cNode->emit[1];
				dest[2]	+=	coef*cNode->emit[2];
			}
		} else {
			if (cNode->children[0] != NULL)	*stack++	=	cNode->children[0];
			if (cNode->children[1] != NULL)	*stack++	=	cNode->children[1];
			if (cNode->children[2] != NULL)	*stack++	=	cNode->children[2];
			if (cNode->children[3] != NULL)	*stack++	=	cNode->children[3];
			if (cNode->children[4] != NULL)	*stack++	=	cNode->children[4];
			if (cNode->children[5] != NULL)	*stack++	=	cNode->children[5];
			if (cNode->children[6] != NULL)	*stack++	=	cNode->children[6];
			if (cNode->children[7] != NULL)	*stack++	=	cNode->children[7];
		}
	}

	dest[0]	=	max(dest[0],0);
	dest[1]	=	max(dest[1],0);
	dest[2]	=	max(dest[2],0);
	assert(dest[0] >= 0);
	assert(dest[1] >= 0);
	assert(dest[2] >= 0);
}

///////////////////////////////////////////////////////////////////////
// Class				:	CVolume
// Method				:	integrateIndirect
// Description			:	Compute something
// Return Value			:	-
// Comments				:
// Date last edited		:	9/28/2002
float	CVolume::integrateIndirect(const float *from,const float *to,float at) {
	return 0;
}


