/* radiance.c: merk op: radiance = 1/pi * radiosity */

#include "radiance.h"
#include "scene.h"
#include "render.h"
#include "debug.h"
#include "error.h"
#include "globals.h"

/**************** ambient radiance computation ********************/
static COLOR ambient_radiance;
static float totalarea;

static void AccumulateUnshotPower(PATCH *P)
{
	COLOR unshot_power = P->basis->GetUnshotPower(P);
	COLORADD(ambient_radiance, unshot_power, ambient_radiance);
	totalarea += P->area;
}

COLOR *ComputeAmbientRadiance(void)
{
	COLORCLEAR(ambient_radiance);

	if (render_ambient) {
		totalarea = 0.;
		PatchListIterate(Patches, AccumulateUnshotPower);
		COLORSCALEINVERSE(M_PI * totalarea, ambient_radiance, ambient_radiance);
	}

	RenderSetAmbientRadiance(&ambient_radiance);
	return &ambient_radiance;
}

void SetAmbientRadiance(COLOR *ambirad)
{
	ambient_radiance = *ambirad;
	RenderSetAmbientRadiance(&ambient_radiance);
}

COLOR *GetAmbientRadiance(void)
{
	return &ambient_radiance;
}

/**************** vertex color computation ********************/
void ComputeVertexColor(VERTEX *vertex)
{
	PATCHLIST *patches;
	PATCH *p;
	int patchcount;
	COLOR vertexradiance, vertextotalradiance;

	COLORCLEAR(vertextotalradiance);
	patches = vertex->patches;
	patchcount = 0;
	while ((p = PatchListNext(&patches))) {
		if (p->subpatches) continue;
		vertexradiance = p->basis->GetValueAtVertex(p->radiance, 
							    PatchWhichVertex(p, vertex));

		if (render_ambient) {
			COLOR a;

			COLORPROD(p->surface->material->Kd, ambient_radiance, a);
			COLORADD(vertexradiance, a, vertexradiance);
		}

		COLORADD(vertextotalradiance, vertexradiance, vertextotalradiance);
		
		patchcount++;
	}

	COLORSCALEINVERSE((float)patchcount, vertextotalradiance, vertextotalradiance);
	RadianceToRGB(vertextotalradiance, &vertex->color);
}

/* we could avoid computing the color of a vertex multiple times by keeping flags
 * that tell us wether we already computed the new color etc... */
void ElementComputeVertexColors(PATCH *element)
{
	int i;

	for (i=0; i<element->nrvertices; i++)
		ComputeVertexColor(element->vertex[i]);
}

void PatchComputeVertexColors(PATCH *patch)
{
	if (!patch->subpatches)
		ElementComputeVertexColors(patch);
	else
		PatchListIterate(patch->subpatches, PatchComputeVertexColors);
}

/* **************** initialisation of a freshly loaded scene *********************** */
static float maxradiance, totalpower;

static void InitPatchRadiance(PATCH *patch)
{
	float radiance, power;

/* if the patch is a light source, compute its initial radiance from it's given
 * selfemitted radiosity (Watts/square meter) material->Ed. The data are such that
 * R=100,G=100,B=100 corresponds to a white material emitting 100W/m^2, so divide
 * the given number by the nr of color channels (=3). Divide by M_PI since we store
 * radiances and not radiosities. Radiance can also be used for non diffuse 
 * environments and in diffuse environments it is radiosity divided by pi 
 * (= integral over the hemisphere of the cosine of the angle the direction being 
 * integrated over makes with the surface normal). */
	if (!COLORNULL(patch->surface->material->Ed)) {
		COLOR tmp;

		COLORSCALEINVERSE((float)COLORNRCHANNELS*M_PI, patch->surface->material->Ed, tmp);
		patch->basis->SetConstant(patch->radiance, &tmp);

/* for progressive refinement radiosity */
		patch->basis->SetConstant(patch->unshot_radiance, &tmp);

/* some other data needed to scale radiances to colors on the display and
 * to compute a relative error threshold */
		radiance = COLORSUMABSCOMPONENTS(tmp);
		if (radiance > maxradiance)
			maxradiance = radiance;

		power = M_PI * patch->area * COLORSUMABSCOMPONENTS(tmp);
		totalpower += power;
		if (power > maxpower)
			maxpower = power;
	}

/* the initial radiance of the other patches is zero */

	totalarea += patch->area;
}

/* called just after loading a scene */
void InitRadianceComputation(void)
{
	totalarea = 0.;		/* total surface area of the patches */
	totalpower = 0.;	/* total selfemitted power in the scene */
	maxradiance = 0.;	/* maximum radiance of a patch in the scene */
	maxpower = 0.;		/* maximum power of a patch in the scene */

	PatchListIterate(Patches, InitPatchRadiance);

/* average radiance per component of the patches in the scene - used for
 * radiance rescaling see RadianceToRGB() in color.c */
	radiance_rescale = totalpower/((float)COLORNRCHANNELS * M_PI * totalarea);

	SetRadianceRescaleFactor(radiance_rescale);
	PatchListIterate(Patches, PatchComputeVertexColors);

/* approximation error threshold */
	BFeps = maxpower * Feps; 

/* for a gradually decreasing error tolerance for gathering radiosity. The only
 * purpose of this is to improve feedback to the user -- see gauss_seidel.c */
	epsfac = 1.;	

	usecs_last_id_rendering = usecs_last_importance_propagation = 0;
}

