/* gauss_seidel.c: gathering radiosity */

#include "gauss_seidel.h"
#include "scene.h"
#include "radiance.h"
#include "importance.h"
#include "globals.h"
#include "hrefine.h"
#include "render.h"
#include "camera.h"
#include "coefficients.h"
#include "initiallinking.h"

/* define SLIDING_ERROR if you want an error threshold that decreases a bit each
 * iteration --- if you do higher order basisfunctions, the stability of the computations
 * is not guaranteed, so I left it out. */
#undef SLIDING_ERROR

/* #define DEBUG */
#include "monitor.h"

static int still_refined;
static float BFeps2, BFCeps2, maxdirectimportance;

/* Returns TRUE if the approximation error estimate for the total radiance emitted by
 * PQ->Q received by PQ->P is tolerable. */
static int GatheringAccurateEnough(INTERACTION *PQ)
{
	PATCH *P = PQ->P, *Q = PQ->Q;
	COLOR deltaB;
	extern int TotalElements;
	
	/* sanity check --- will be left out once in future ... */
	if (P->id < 1 || P->id > TotalElements+1 ||
	    Q->id < 1 || Q->id > TotalElements+1) {
		fprintf(stderr, "GatheringAccurateEnoguh() got garbage: P->id = %d, Q->id = %d\n",
			P->id, Q->id);
		return TRUE;
	}

	if (P->area < Aeps && Q->area < Aeps) {
		return TRUE;
	} else if (!ImportanceDriven) {
		float deltaBF;
		COLOR maxQrad;

		MAXCOEFFICIENT(Q->radiance, maxQrad, Q->basis->size);
		COLORPROD(P->surface->material->Kd, maxQrad, deltaB);
		deltaBF = PQ->deltaK * COLORSUMABSCOMPONENTS(deltaB);
		deltaBF *= M_PI * P->area;

		if (deltaBF >= BFeps2)
		{
			still_refined = TRUE;	/* still refining */
			return FALSE;
		} else {
			return TRUE;
		}
	} else /* if (ImportanceDriven) */ {	
		float deltaBFC;
		COLOR maxQrad, Pimp;

		MAXCOEFFICIENT(Q->radiance, maxQrad, Q->basis->size);
		COLORPROD(P->surface->material->Kd, maxQrad, deltaB);
		Pimp = P->basis->GetImportance(P);
		deltaBFC = M_PI * COLORMAXCOMPONENT(Pimp) * PQ->deltaK * COLORSUMABSCOMPONENTS(deltaB);

		if (deltaBFC >= BFCeps2) {
			still_refined = TRUE;	/* we are still refining */
			return FALSE;
		} else
			return TRUE;
	}
}

static void InteractionGatherRadShootImp(INTERACTION *PQ)
{
	PATCH *P = PQ->P, *Q = PQ->Q;
	Float *Kab;
	COLOR delta_imp, delta_rad;
	int a, b;

	for (a=0, Kab=PQ->K; a < P->basis->size; a++) {
		COLORCLEAR(delta_rad);
		for (b=0; b < Q->basis->size; b++, Kab++) 
			COLORADDSCALED(delta_rad, *Kab, Q->radiance[b], delta_rad);
		COLORPROD(P->surface->material->Kd, delta_rad, delta_rad);
		COLORADD(P->received_radiance[a], delta_rad, P->received_radiance[a]);
	}

	if (ImportanceDriven) {
		for (b=0; b < Q->basis->size; b++) {
			COLORCLEAR(delta_imp);
			for (a=0, Kab=PQ->K+b; a < P->basis->size; a++, Kab+=Q->basis->size) 
				COLORADDSCALED(delta_imp, *Kab, P->importance[a], delta_imp);
			COLORPROD(P->surface->material->Kd, delta_imp, delta_imp);
			COLORADD(Q->received_importance[b], delta_imp, Q->received_importance[b]);
		}
	}
}

static void PatchGatherRadShootImp(PATCH *P)
{
	CLEARCOEFFICIENTS(P->received_radiance, P->basis->size);
	InteractionListIterate(P->interactions, InteractionGatherRadShootImp);

	PatchListIterate(P->subpatches, PatchGatherRadShootImp);
}

static void UpdateRadiance(PATCH *P)
{
/* ambient radiance is computed from unshot radiance, make sure that the ambient
 * radiance will be zero next time */
	CLEARCOEFFICIENTS(P->unshot_radiance, P->basis->size);

	P->basis->GatherPushPullRadiance(P);
	SetRadianceRescaleFactor(radiance_rescale);
	PatchComputeVertexColors(P);
/* slows down the computations too much ...
	if (demo)
		RenderScene();
*/
}

static void ToplevelPatchGatherRadShootImp(PATCH *P)
{
	COLOR imp = P->basis->GetImportance(P);

	if (!ImportanceDriven || COLORSUMABSCOMPONENTS(imp) > EPSILON) {
		if (!P->interactionscreated)
			CreateToplevelInteractions(P); 
	}

	if (HierarchicalRefinement) 
		PatchRefineInteractions(P);

	PatchGatherRadShootImp(P);

/* by commenting out this line, we are doing Jacobi iterations and no Gauss-Seidel
 * iterations ...  */
/*
        UpdateRadiance(P);   
*/
	iterationr ++;
}

static void UpdateImportance(PATCH *P)
{
	P->basis->GatherPushPullImportance(P);
}

/* see importance.c */
static void PatchUpdateDirectImportance(PATCH *P, float *newimp)
{
	PatchListIterate1A(P->subpatches, PatchUpdateDirectImportance, newimp);

	P->direct_importance = newimp[P->id]/P->area;

/* for computing the BFC error threshold */
	if (P->direct_importance > maxdirectimportance)
		maxdirectimportance = P->direct_importance;
}

int DoGaussSeidelOneStep(void)
{
	clock_t t;
	COLOR dark;

/* no visualisation with ambient term for gathering radiosity */
	COLORCLEAR(dark);
	SetAmbientRadiance(&dark);

	usecs_last_radiance_propagation = 0;
	usecs_last_importance_propagation = 0;

	SetOracle(GatheringAccurateEnough);

	t = clock() - usecs_ff_computation - usecs_shaftculling;
	if (ImportanceDriven && Camera.changed) {
		char rendermode = RenderGetMode();

		Camera.changed = FALSE;

		maxdirectimportance = 0.;
		SetPatchDirectImportanceUpdateRoutine(PatchUpdateDirectImportance);
		UpdateDirectImportance();
		PatchListIterate(Patches, UpdateImportance);
		usecs_last_importance_propagation = clock() - t;

		if (rendermode == RENDER_IMPORTANCE || 
		    rendermode == RENDER_UNSHOT_IMPORTANCE)
			RenderScene();

		epsfac = 1.;
	}

#ifdef SLIDING_ERROR
	BFeps2 = epsfac;
	if (BFeps2 < BFeps) 
		BFeps2 = BFeps;
#else
	BFeps2 = BFeps;
#endif
	BFCeps2 = maxdirectimportance * BFeps2;

	still_refined = FALSE;

	PatchListIterate(Patches, ToplevelPatchGatherRadShootImp);
	PatchListIterate(Patches, UpdateRadiance); 
	if (ImportanceDriven) 
		PatchListIterate(Patches, UpdateImportance);

	usecs_last_radiance_propagation = clock() - t 
		              - usecs_ff_computation - usecs_shaftculling;
	if (ImportanceDriven) {
		usecs_last_radiance_propagation /= 2;
		usecs_last_importance_propagation = usecs_last_radiance_propagation;
	}

#ifdef SLIDING_ERROR
/* make the error threshold a bit smaller for each iteration until finally
 * BFeps2 <= BFeps. see eg Cohen & Wallace, "Radiosity and Realistic Image 
 * Synthesis p 205 */
	epsfac /= 1.4;

	return (BFeps2 <= BFeps && !still_refined);
#else
	return FALSE;	/* never done */
#endif
}






