/*
	Computation of Form factor for Full Radiosity Solution.
*/
#include <stdio.h>
#include <math.h>
#ifdef __WATCOMC__
#include <string.h>
#else
#include <memory.h>
#endif
#include <malloc.h>

#include "GraphicsGems.h"
#include "data_structure.h"
#include "objects.h"
#include "vox.h"

#include "raddecl.h"

extern int hemicube_resolution;
extern int verbose_flag;
int ProjectionByRaytracing=0;

static double **TopFace_delta_form_factor;
static double **SideFace_delta_form_factor;
struct Hemicube{
        short int *topface;
        short int *rightface;
        short int *leftface;
        short int *frontface;
        short int *backface;
};
static double **F; /* Form Factors */
static double **A; /* N x N Matrix */
static double **I;  /* Computed Intensity */
static double **deltaI;  /* Delta Intensity for progressive radiosity */
static double *B;
static struct Hemicube hemicube;

#define Iter_Threshold 0.0001
#define VeryHigh 1.e5

static double **alloc_matrix(rows,cols)
int rows,cols;
{
	char *s="Cannot Allocate Matrix.";
	double **m;
	int i;
	m = (double **)malloc(rows*sizeof(double *));
	if (m==NULL) error(s);
	for (i=0;i<rows;i++) {
		m[i] = (double *)malloc(cols*sizeof(double));
		if (m[i] == NULL) error(s);
	}
	return(m);
}
static void allocate_hemicube()
{
	TopFace_delta_form_factor=
		alloc_matrix(hemicube_resolution,hemicube_resolution);
	SideFace_delta_form_factor=
		alloc_matrix(hemicube_resolution/2,hemicube_resolution);
	hemicube.topface=(short int *)malloc(
		hemicube_resolution*hemicube_resolution
		*sizeof(short int));
	hemicube.rightface=(short int *)malloc(
                hemicube_resolution/2*hemicube_resolution
                *sizeof(short int));
	hemicube.leftface=(short int *)malloc(
                hemicube_resolution/2*hemicube_resolution
                *sizeof(short int));
	hemicube.frontface=(short int *)malloc(
                hemicube_resolution/2*hemicube_resolution
                *sizeof(short int));
	hemicube.backface=(short int *)malloc(
                hemicube_resolution/2*hemicube_resolution
                *sizeof(short int));
}
static void free_matrix(m,rows,cols)
double **m;
int rows,cols;
{
	int i;
	for (i=0;i<rows;i++) free(m[i]);
	free(m);
}
static void deallocate_hemicube()
{
	free_matrix(TopFace_delta_form_factor,
		hemicube_resolution,hemicube_resolution);
	free_matrix(SideFace_delta_form_factor,
		hemicube_resolution/2,hemicube_resolution);
	free(hemicube.topface);
	free(hemicube.rightface);
	free(hemicube.leftface);
	free(hemicube.frontface);
	free(hemicube.backface);
}

/*
        compute_delta_form_factors : For the faces of the hemicube.
                Side faces are symmetric, so only one computation
                is enough.
*/
static compute_delta_form_factors()
{
        int i,j;
        double x, y, y2, delta;
        double factor;
        double z,zfactor,z2;
        double halfdelta;
        double delta_area_by_pi;

	delta  = 2.0/hemicube_resolution;
	halfdelta =delta/2.0;
	delta_area_by_pi=delta*delta/PI;

        for(i=0,y=(-1.0+halfdelta); i < hemicube_resolution; i++,y+=delta){
                y2 = y*y;
                for (j=0,x=(-1.0+halfdelta); j < hemicube_resolution; j++,x+=delta){
                        factor=1.0/(x*x+y2+1.0);
                        TopFace_delta_form_factor[i][j]=
                                delta_area_by_pi*factor*factor;
                }
        }
        for (i=0,z=halfdelta; i < hemicube_resolution/2; i++,z+=delta){
                z2 = z*z; zfactor = z*delta_area_by_pi;
                for(j=0,y=(-1.0+halfdelta); j < hemicube_resolution; j++,y+=delta){
                        factor=1.0/(y*y+z2+1.0);
                        SideFace_delta_form_factor[i][j]=zfactor*factor*factor;
                }
        }
}

static void increment_F(rows,raster,f,inc)
int rows;
short int *raster;
double f[];
double **inc;
{
	int i,j,k,patchnum;
	for(i=k=0;i<rows;i++)
		for(j=0;j<hemicube_resolution;j++,k++)
			if (patchnum=raster[k])
				f[patchnum-1] += inc[i][j];
}

static void extract_F_i(i)
int i;
{
	int nn;
       	for(nn=0;nn<total_surface_grid_points;nn++)F[i][nn]=0.;
	increment_F(hemicube_resolution,hemicube.topface,
		F[i],TopFace_delta_form_factor);
	increment_F(hemicube_resolution/2,hemicube.rightface,
		F[i],SideFace_delta_form_factor);
	increment_F(hemicube_resolution/2,hemicube.leftface,
		F[i],SideFace_delta_form_factor);
	increment_F(hemicube_resolution/2,hemicube.frontface,
		F[i],SideFace_delta_form_factor);
	increment_F(hemicube_resolution/2,hemicube.backface,
		F[i],SideFace_delta_form_factor);
}

static init_fullmatrix_radiosity()
{
	/* Allocate space for form factor */
	F = alloc_matrix(total_surface_grid_points,total_surface_grid_points);
	A = alloc_matrix(total_surface_grid_points,total_surface_grid_points);
	I = alloc_matrix(color_channels,total_surface_grid_points);
	B = (double *)malloc(total_surface_grid_points*sizeof(double));
	allocate_hemicube();
	compute_delta_form_factors();
}
static deinit_fullmatrix_radiosity()
{
	/* Free space allocated for FormFactor */
	free_matrix(F,total_surface_grid_points,total_surface_grid_points);
	free_matrix(A,total_surface_grid_points,total_surface_grid_points);
	free_matrix(I,color_channels,total_surface_grid_points);
	free(B);
	deallocate_hemicube();
}

static init_progressive_radiosity()
{
	/* Allocate space for form factor */
	F = alloc_matrix(1,total_surface_grid_points);
	I = alloc_matrix(color_channels,total_surface_grid_points);
	deltaI = alloc_matrix(color_channels,total_surface_grid_points);
	allocate_hemicube();
	compute_delta_form_factors();
}
static deinit_progressive_radiosity()
{
	free_matrix(F,1,total_surface_grid_points);
	free_matrix(I,color_channels,total_surface_grid_points);
	free_matrix(deltaI,color_channels,total_surface_grid_points);
	deallocate_hemicube();
}

static void from_specular_surface(ray,objectnum,t,u,v,n)
Ray *ray;
int objectnum;
double t,u,v;
int *n;
{
	Vector3 N;
        /* .... set the new ray */
        point_on_line(ray->start,ray->direction,t,ray->start);
        N = ofunc[object[objectnum].surface_geometry_type].get_surface_normal
                (object[objectnum].object_specific_structure,u,v);
        mirror_reflection(&N,&(ray->direction),&(object[objectnum]));
        (ray->number)++;
	*n=get_diffuse_surface(ray);
}
static void from_diffuse_surface(ray,objectnum,t,u,v,n)
Ray *ray;
int objectnum;
double t,u,v;
int *n;
{
	int uindex=grid_u(objectnum,u);
	int vindex=grid_v(objectnum,v);
	*n=(object[objectnum].start_grid_index+
		gridindex(objectnum,uindex,vindex)+1);
}
static void from_other_surface(ray,objectnum,t,u,v,n)
Ray *ray;
int objectnum;
double t,u,v;
int *n;
{
        error("Not Implemented.");
}
static void (*get_patchnum[MAX_REFLECTION_TYPE])()={
        from_diffuse_surface,
        from_specular_surface,
        from_other_surface
};

static int get_diffuse_surface(ray)
Ray *ray;
{
	double t_entry,t_exit;
	int HitBoundingBox();
        if (HitBoundingBox(&(volume_grid.extent.min),&(volume_grid.extent.max),
			&(ray->start),&(ray->direction),&t_entry,&t_exit)){
                int get_nearest_object_in_voxel();
                Vlist *create_vlist();
                Vlist *vlist,*templist;
                double t,u,v;
        	int nearest_object= UNDEFINED;
                vlist=templist=create_vlist(
                        &(ray->start),&(ray->direction),t_entry,t_exit
                );
                while (templist!=NULL){
                        Voxel *vox = volume_grid.voxels+templist->voxnum;
                        if(vox->nobjects)
				nearest_object=get_nearest_object_in_voxel
				(ray,templist->t_near,templist->t_far,vox,&t,&u,&v);
                        if (nearest_object!= UNDEFINED) break;
                        templist=templist->next;
                }
                purge_vlist(vlist);
                if (nearest_object!= UNDEFINED){
			int n;
			get_patchnum[object[nearest_object].surface_reflection_type]
			(ray,nearest_object,t,u,v,&n);
			return(n);
		}
	}
	return(0);
}
/*
int dump_buffer(rows,cols,b)
int rows,cols;
char *b;
{
	int i,j,k;
	fprintf(stderr,"\nDumping buffer\n");
	for(i=k=0;i<rows;i++){
		fprintf(stderr,"%02d.....",i);
		for(j=0;j<cols;j++,k++)
			fprintf(stderr,"%1d",b[k]);
		fprintf(stderr,"\n");
	}
}
int dump_raster(rows,cols,b)
int rows,cols;
short int *b;
{
	int i,j,k;
	fprintf(stderr,"\nDumping raster\n");
	for(i=k=0;i<rows;i++){
		fprintf(stderr,"%02d.....",i);
		for(j=0;j<cols;j++,k++)
			fprintf(stderr,"%1d",b[k]);
		fprintf(stderr,"\n");
	}
}
*/
static long project(C,N,U,V,rows,raster)
Point3 *C;
Vector3 *N,*U,*V;
int rows;
short int *raster;
{
	static long RayNumber=0;
	Ray ray;
	Vector3 Dir,delta_U,delta_V,half_delta_U,half_delta_V;
	int i,j,k;

	half_delta_U=delta_U = *U;
	V3Scale(&delta_U,2./hemicube_resolution);
	V3Scale(&half_delta_U,1./hemicube_resolution);
	half_delta_V=delta_V = *V;
	V3Scale(&delta_V,2./hemicube_resolution);
	V3Scale(&half_delta_V,1./hemicube_resolution);
	V3Add(N,V,&Dir); V3Sub(&Dir,U,&Dir);
	V3Add(&Dir,&half_delta_U,&Dir); V3Sub(&Dir,&half_delta_V,&Dir);
	ray.number=total_rays+RayNumber;
#if defined (DEBUG)
	ray.intersections=0;
#endif
	ray.path_length= -1;
	if (ProjectionByRaytracing)
	for(i=k=0;i<rows;i++){
		Vector3 dir;
		dir = Dir;
		for(j=0;j<hemicube_resolution;j++,k++){
			ray.start = *C;
			ray.direction=dir;
			V3Normalize(&(ray.direction));
			raster[k]=get_diffuse_surface(&ray);
			(ray.number)++;
			V3Add(&dir,&delta_U,&dir);
		}
		V3Sub(&Dir,&delta_V,&Dir);
	}
	else {
	double *zbuffer;
	char *surface_buffer;
	Matrix4 view_matrix;
	int n=rows*hemicube_resolution;
	extern void (*geometry_specific_scan_convert[MAX_GEOMETRY_TYPE])();
	/* Clean up the raster */
	memset((char *)raster,0,n*sizeof(short int));
	/* Set up a Z-buffer of equal dimension */
	zbuffer=(double *)malloc(n*sizeof(double));
	if (zbuffer == NULL)
	  error("Cannot allocate Z-buffer memory for scan conversion.");
	for(i=0;i<n;i++)zbuffer[i]=VeryHigh;
	surface_buffer=(char *)calloc(n,1);
	if (surface_buffer == NULL)
	  error("Cannot allocate surface-buffer memory for scan conversion.");
	/* Compute View transform matrix */
	view_transform(C,U,V,N,rows,hemicube_resolution,&view_matrix);
	for(i=0;i<number_objects;i++){
		/* Project each object on the raster */
		geometry_specific_scan_convert[
			object[i].surface_geometry_type
		](i,N,&view_matrix,rows,hemicube_resolution,
			zbuffer,surface_buffer,raster);
	}
	free((char *)zbuffer);
/*
dump_raster(rows,hemicube_resolution,raster);
dump_buffer(rows,hemicube_resolution,surface_buffer);
*/
	for(i=k=0;i<rows;i++)
	for(j=0;j<hemicube_resolution;j++,k++)
		if ((surface_buffer[k]-1)>DIFFUSE){
			ray.start = *C;
			ray.direction.x=Dir.x+j*delta_U.x-i*delta_V.x;
			ray.direction.y=Dir.y+j*delta_U.y-i*delta_V.y;
			ray.direction.z=Dir.z+j*delta_U.z-i*delta_V.z;
			V3Normalize(&(ray.direction));
			raster[k]=get_diffuse_surface(&ray);
			(ray.number)++;
		}
	free(surface_buffer);
	}
	return(RayNumber = (ray.number-total_rays));
}

static long project_on_hemicube(c,m)
Point3 *c;
Matrix4 *m;
{
	Vector3 X,Y,Z,Xneg,Yneg,Zneg;
	long n;

	X.x = 1.; X.y=X.z = 0.;	
	X = transform_vector(&X,m); V3Normalize(&X); Xneg=X; V3Negate(&Xneg);
	Y.y = 1.; Y.x=Y.z = 0.;	
	Y = transform_vector(&Y,m); V3Normalize(&Y); Yneg=Y; V3Negate(&Yneg);
	Z.z = 1.; Z.x=Z.y = 0.;	
	Z = transform_vector(&Z,m); V3Normalize(&Z); Zneg=Z; V3Negate(&Zneg);
	/* Project on Top Face */
	project(c,&Z,&X,&Y,hemicube_resolution,hemicube.topface);
	/* Project on Left Face */
	project(c,&Xneg,&Y,&Z,hemicube_resolution/2,hemicube.leftface);
	/* Project on Right Face */
	project(c,&X,&Yneg,&Z,hemicube_resolution/2,hemicube.rightface);
	/* Project on Front Face */
	project(c,&Yneg,&Xneg,&Z,hemicube_resolution/2,hemicube.frontface);
	/* Project on Back Face */
	n=project(c,&Y,&X,&Z,hemicube_resolution/2,hemicube.backface);
	return(n);
}

static hemicube_method(i,j,k,n,nrays)
int i;	/* Object num */
int j;  /* Vertical Grid */
int k;  /* Horizontal grid */
int n;  /* Patch num */
long *nrays;
{
	Matrix4 m;
	Vector3 N;
	Point3 centre;
	if (object[i].surface_reflection_type==MIRROR)return;
	centre = ofunc[object[i].surface_geometry_type].
		compute_center_point_in_the_grid_cell(i,j,k);
	N=ofunc[object[i].surface_geometry_type].
		get_surface_normal
		(object[i].object_specific_structure,
		(k+0.5)/object[i].grid_h_reso,
		(j+0.5)/object[i].grid_v_reso);
	m  = ofunc[object[i].surface_geometry_type].
		get_local_matrix(
		&N,object[i].object_specific_structure);
	*nrays=project_on_hemicube(&centre,&m);
}
static long compute_F_ij()
{
	int i,j,k,n;
	long nrays;
	if (verbose_flag)fprintf(stderr,"From Factor:");
	for (i=n=0;i<number_objects; i++){
		for(j=0;j<object[i].grid_v_reso;j++)
		for(k=0;k<object[i].grid_h_reso;k++,n++){
			hemicube_method(i,j,k,n,&nrays);
			extract_F_i(n);	
			if (verbose_flag)fprintf(stderr,".");
		}
	}
	if (verbose_flag)fprintf(stderr,"\n");
	return(nrays);
}

static double get_emission_color(onum,channel)
int onum,channel;
{
	int i;
	for(i=0;i<light_sources;i++)
		if (source[i].oindex == onum)
			return(source[i].strength[channel]);
	return(0.);
}

static void solve(channel,color)
int channel;
double color[];
{
	int i,j;
	int m,n;
	int diagonally_dominant();

        for (i=m=0; i < number_objects; i++){
		int grid_points = object[i].grid_v_reso*object[i].grid_h_reso;
		double rho = object[i].reflectance[channel];
		double c = get_emission_color(i,channel);
		for (j=0; j < grid_points; j++,m++){
			for(n=0;n<total_surface_grid_points;n++)
				A[m][n] = -F[m][n]*rho;
			A[m][m] += 1.;
			B[m] = color[m] = c/object[i].grid[j].area;
		}
	}
	if (diagonally_dominant(total_surface_grid_points,A)){
		fprintf(stderr,"Warning : Not Diagonally Dominant.\n");
		fprintf(stderr,
		      "Check for the reflectance of the surfaces.\n");
		fprintf(stderr,"Iteration may not converge.\n");
		fprintf(stderr,"So a predefined number of iterations will be tried.\n");
	}
	gauss_iter(total_surface_grid_points,A,color,B,Iter_Threshold);
}

static set_intensity(diff_flag,fl)
int diff_flag;
FILE *fl;
{
	int i,j,k,m;
	double max_intensity=0.,max_cum_intensity=0.;
        if((fl != NULL)&& verbose_output_flag){
                int rows,cols;
                for (i=m=0; i < number_objects; i++){
                        fprintf(fl,"intensity output for Surface : %d\n",i);
			for(rows=0;rows<object[i].grid_v_reso;rows++)
			for(cols=0;cols<object[i].grid_h_reso;cols++,m++){
                                Point3 centre;
                                centre = ofunc[object[i].
					surface_geometry_type].
				   compute_center_point_in_the_grid_cell
					 (i,rows,cols);
				fprintf(fl,"%g\t%g\t%g",centre.x,
					centre.y,centre.z);
                                for(k=0; k < color_channels; k++)
                                        fprintf(fl,"\t%g", I[k][m]);
                                fprintf(fl,"\n");
                        }
                }
        }
	/* Normalize */
	for(k=0;k<color_channels;k++)
	for(i=0;i<total_surface_grid_points;i++)
		if (I[k][i] > max_intensity) max_intensity=I[k][i];	
	for(k=0;k<color_channels;k++)
	for(i=0;i<total_surface_grid_points;i++)
		I[k][i] /= max_intensity;

	for (i=m=0; i < number_objects; i++){
		double cum_area=0.,cum_intensity[MAXCHANNELS];
		int grid_points = object[i].grid_v_reso*object[i].grid_h_reso;
		Surface_Grid_structure *g=object[i].grid;
		for(k=0; k<color_channels;k++) cum_intensity[k] = 0.;
		for (j=0; j < grid_points; j++,m++,g++){
			cum_area += g->area;
			for(k=0;k<color_channels;k++){
				cum_intensity[k] += I[k][m]*g->area;
				g->normalized_flux_density[k] = ((diff_flag)
					? fabs(I[k][m]-g->normalized_flux_density[k])
					:I[k][m]);
			}
		}
		for(k=0;k<color_channels;k++){
			cum_intensity[k]/=cum_area;
			object[i].normalized_flux_density[k] = cum_intensity[k];
			if (cum_intensity[k] > max_cum_intensity )
				max_cum_intensity = cum_intensity[k];
		}
	}
	for(i=0;i<number_objects; i++)
		for(k=0;k<color_channels;k++)
			object[i].normalized_flux_density[k]/=max_cum_intensity;
}


long fullmatrix_radiosity(diff_flag,fl)
int diff_flag;
FILE *fl;
{
	int i;
	long number=0;
	init_fullmatrix_radiosity();
	number=compute_F_ij();
	for(i=0;i<color_channels;i++)
		solve(i,I[i]);
	set_intensity(diff_flag,fl);
	write_out_intensity(fl);
	deinit_fullmatrix_radiosity();
	return(number);
}

long progressive_radiosity(maxpasses,diff_flag,fl)
int maxpasses;
int diff_flag;
FILE *fl;
{
	int i,j,k,npasses=0;
	long nrays;
	int max_h_grid,max_v_grid,maxpatch,maxobject= UNDEFINED;
	double maxdelta=0.;

	init_progressive_radiosity();
	for(k=0;k<color_channels;k++)
		for(i=0;i<total_surface_grid_points;i++)
			deltaI[k][i]=I[k][i]=0.;
	for(i=0;i<light_sources;i++){
		int index=source[i].oindex,n,nn;
		double cumarea=0.;
		for(j=nn=0;j<object[index].grid_v_reso;j++)
		for(k=0;k<object[index].grid_h_reso;k++,nn++)
			cumarea += object[index].grid[nn].area;
		n=object[index].start_grid_index;
		for(j=nn=0;j<object[index].grid_v_reso;j++)
		for(k=0;k<object[index].grid_h_reso;k++,nn++,n++){
			int l;
			double area=object[index].grid[nn].area;
			for(l=0;l<color_channels;l++){
			   I[l][n]=source[i].strength[l]/cumarea;
			   deltaI[l][n] = I[l][n]*area;
			   if (maxdelta < deltaI[l][n]){
				maxdelta=deltaI[l][n];
				max_h_grid=k;
				max_v_grid=j;
				maxpatch=n;
				maxobject=index;
			   }
			}
		}
	}
	if (maxpasses < 0) maxpasses=total_surface_grid_points;
	/* Distribute */
	while (maxpatch!= UNDEFINED){
		int l,n,nn;
		double area,increment[MAXCHANNELS];
		hemicube_method(maxobject,
			max_v_grid,max_h_grid,
			maxpatch,&nrays);
		extract_F_i(0);	
		area=object[maxobject].grid[max_v_grid*
				object[maxobject].grid_h_reso+
				max_h_grid].area;
		for(l=0;l<color_channels;l++){
		   increment[l]=deltaI[l][maxpatch];
		   deltaI[l][maxpatch]=0.;
		}
		maxobject= UNDEFINED; maxdelta=0.;
		for(i=n=0;i<number_objects;i++){
			for(j=nn=0;j<object[i].grid_v_reso;j++)
			for(k=0;k<object[i].grid_h_reso;k++,n++,nn++)
			if (object[i].surface_reflection_type !=DIFFUSE)
				continue;
			else{
				int l;
				for(l=0;l<color_channels;l++){
				   deltaI[l][n]+=
					object[i].reflectance[l]*
					increment[l]*F[0][n];
				   I[l][n]+=
					object[i].reflectance[l]*
					increment[l]*F[0][n]/
						object[i].grid[nn].area;
				   if(maxdelta < (deltaI[l][n])){
					maxdelta=deltaI[l][n];
					max_h_grid=k;
					max_v_grid=j;
					maxpatch=n;
					maxobject=i;
				   }
				}
			}
		}
		npasses++;
		if (verbose_flag)fprintf(stderr,".");
		if (npasses >= maxpasses) break;
	}
	if (verbose_flag)
		fprintf(stderr,"\n%d Distribution steps.\n",npasses);
	set_intensity(diff_flag,fl);
	write_out_intensity(fl);
	deinit_progressive_radiosity();
	return(nrays);
}
