/**
 * Compute the diffusely reemited light data and place the result into reemited.
 * localBasis : the object localBasis at the computation point.
 * surfaceCoordinate : the surface coordinate (texture coordinate) of the computation
 *   point on the object.
 * incident : the incident ray (light ray)
 * view : the view ray (from the camera or bounced)
 * incidentLight : the incident light comming from the incident ray.
 * reemitedLight : the light reemited into the view direction (result will be placed 
 *   here).
 */
void RoughLambertianBRDF::getDiffuseReemited(const Basis& localBasis, const Point2D& surfaceCoordinate, const LightVector& incidentLight, LightVector& reemitedLight)
{
  Vector view     = reemitedLight.getRay().v;
  Vector incident = incidentLight.getRay().v;

  Real cosOi = -incident.dot(localBasis.k);
  Real cosOv = -view.dot(localBasis.k);

  if(cosOi <= 0)
  {
    reemitedLight.clear();
    return;
  }

  if(cosOi>=1.0)
    cosOi=1.0;
  if(cosOv>=1.0)
    cosOv=1.0;

  Real Oi = std::acos(cosOi);
  Real Ov = std::acos(cosOv);
  Real Pi = std::atan2(incident.dot(localBasis.j), incident.dot(localBasis.i));
  Real Pv = std::atan2(view.dot(localBasis.j), view.dot(localBasis.i));

  Real factor = OrenNayarFormula::orenNayarReflectance(Oi, Pi, Ov, Pv, _roughness);

  for(unsigned int i=0; i<reemitedLight.size(); i++)
    reemitedLight[i].setRadiance(incidentLight[i].getRadiance()*_spectrum[incidentLight[i].getIndex()]*factor);

  reemitedLight.changeReemitedPolarisationFramework(localBasis.k);
}
void RoughLambertianBRDF::getDiffuseReemitedFromAmbiant(const Basis& localBasis, const Point2D& surfaceCoordinate, LightVector& reemitedLight, const Spectrum& incident)
{
  reemitedLight.clear();
  reemitedLight.changeReemitedPolarisationFramework(localBasis.k);
  //// the same in all directions
  //for(unsigned int i=0; i<reemitedLight.size(); i++) {
  //  reemitedLight[i].setRadiance(incident[i] * _spectrum[i] );
  //}

  //reemitedLight.changeReemitedPolarisationFramework(localBasis.k);
}
Beispiel #3
0
/**
 * Compute the diffusely reemited light data and place the result into reemited.
 * localBasis : the object localBasis at the computation point.
 * surfaceCoordinate : the surface coordinate (texture coordinate) of the computation
 *   point on the object.
 * incident : the incident ray (light ray)
 * view : the view ray (from the camera or bounced)
 * incidentLight : the incident light comming from the incident ray.
 * reemitedLight : the light reemited into the view direction (result will be placed 
 *   here).
 */
void BeckmannBRDF::getDiffuseReemited(const Basis& localBasis, const Point2D& surfaceCoordinate, const LightVector& incidentLight, LightVector& reemitedLight)
{
  const Vector& incident = incidentLight.getRay().v;
  const Vector& view     = reemitedLight.getRay().v;

  //Computing the micro normal
  Vector microNormal;
  microNormal.setsum(incident, view);
  microNormal.mul(-1.0);
  microNormal.normalize();

  //Computing some cosinuses and sinuses
  Real cosLN = -localBasis.k.dot(incident);   // incident and normal
  Real cosVN = -localBasis.k.dot(view);       // view and normal 
  Real cosHN = localBasis.k.dot(microNormal); // micro normal and normal
  Real cosLH = -microNormal.dot(incident);    // incident and micro normal
  Real cosVH = -microNormal.dot(view);        // view and micro normal

  //Compute Beckmann and Shadowing&masking coeficients
  Real beckmann = BeckmannRoughnessFormula::BeckmannDistribution(cosHN, 
                                                                 _roughness);
  Real shadmask = BeckmannRoughnessFormula::BeckmannShadowMasking(cosLN, 
                                                                  cosVN, 
                                                                  cosVH, 
                                                                  cosHN);
  
  //Setting the polarisation framework
  LightVector localIncidentLight(incidentLight);
  localIncidentLight.changeIncidentPolarisationFramework(microNormal);
  localIncidentLight.flip();
  reemitedLight.changeReemitedPolarisationFramework(microNormal);

  if(cosVN <=0.001 || cosLN <=0.001)
  {
    reemitedLight.clear();
    return;
  }

  //Computing reflectances for each wavelength
  for(unsigned int i=0; i<localIncidentLight.size(); i++)
  {
    Real ROrth, RPara;
    getReflectance(cosLH, localIncidentLight[i].getIndex(), ROrth, RPara);
    reemitedLight[i].applyReflectance(localIncidentLight[i], RPara*beckmann*shadmask, ROrth*beckmann*shadmask);
  }
}
Beispiel #4
0
LightVector<TriMesh::VertexHandle> ring::getRing(TriMesh::VertexHandle _vh, int k)
// returns all vertices that are within the k-ring of the vertex vh
{
	if (!initialised) init();

	int aux;
	int epoch_base = epoch;
	epoch++;
	//std::list<TriMesh::VertexHandle> lik;
	TriMesh::VertexHandle vh;
	TriMesh::VertexVertexIter vvi, vvstart;
	mesh.property(epochs, _vh) = epoch+1;
	LightVector<TriMesh::VertexHandle> vect;
	vect.push_back(_vh);
	unsigned int i = 0;

	while (i < vect.size())
	{
		vh = vect[i];
		aux = mesh.property(epochs, vh);
		if (aux > epoch_base + k+1) break; // we have finished exploring all the vertices from the k-1 ring.
		if (aux == epoch + 1) ++epoch; // finished all vertices in the current epoch, go to next ring

		vvstart = vvi = mesh.vv_iter(vh);
		do
		{
			vh = vvi.handle();
			aux = mesh.property(epochs, vh);
			if (aux <= epoch_base) // node has not been visited yet
			{
				mesh.property(epochs, vh) = epoch+1;
				vect.push_back(vh);
			} else 
			{
				// the node has been visted already and has been added to vect, continue
			}
			++vvi;
		} while (vvi);
		++i; // move to next vertex
	} // i < vect.size()
	return vect;
} // void getRing(TriMesh mesh, TriMesh::VertexHandle _vh, int k)
Beispiel #5
0
void BeckmannBRDF::getDiffuseReemitedFromAmbiant(const Basis& localBasis, const Point2D& surfaceCoordinate, LightVector& reemitedLight, const Spectrum& incident)
{
  const Vector& light = localBasis.k;
  const Vector& view     = reemitedLight.getRay().v;

  //Computing the micro normal
  Vector microNormal;
  microNormal.setsum(light, view);
  microNormal.mul(-1.0);
  microNormal.normalize();

  //Computing some cosinuses and sinuses
  Real cosLN = -localBasis.k.dot(light);   // light and normal
  Real cosVN = -localBasis.k.dot(view);       // view and normal 
  Real cosHN = localBasis.k.dot(microNormal); // micro normal and normal
  Real cosLH = -microNormal.dot(light);    // light and micro normal
  Real cosVH = -microNormal.dot(view);        // view and micro normal

  //Compute Beckmann and Shadowing&masking coeficients
  Real beckmann = BeckmannRoughnessFormula::BeckmannDistribution(cosHN, 
                                                                 _roughness);
  Real shadmask = BeckmannRoughnessFormula::BeckmannShadowMasking(cosLN, 
                                                                  cosVN, 
                                                                  cosVH, 
                                                                  cosHN);

  if(cosVN <=0.001 )
  {
    reemitedLight.clear();
    return;
  }
  
  for(unsigned int wl=0; wl < reemitedLight.size(); wl++) {
    Real ROrth, RPara;
    getReflectance(cosLH, wl, ROrth, RPara);
    ROrth *= beckmann * shadmask;
    RPara *= beckmann * shadmask;
    reemitedLight[wl].setRadiance(incident[wl] /** cosVN*/ * 0.5 * (ROrth + RPara));
  }

 reemitedLight.changeReemitedPolarisationFramework(microNormal);
}
Beispiel #6
0
void outputGNUPlot(LightVector<XY>& points, char* filename)
{
	ofstream of;
	of.open(filename);
	unsigned int n = points.size();
	for (unsigned int i = 0; i<n; ++i)
	{
		of<<points[i].x()<<"    "<<points[i].y()<<"   "<<points[(i+1)%n].x()<<"    "<<points[(i+1)%n].y()<<endl;
	}
	of.close();
}
/**
 * Compute the secondary rays for diffuse reflexion and place them into the subrays
 * vector.
 * localBasis : the local base on the object at the computation point.
 * surfaceCoordinate : the surface  coordinate (texture coordinate) of the computation 
 *   point on the object.
 * view : the view ray (from the camera or bounced)
 * nbRays : the number of wanted ray. It is an indicative information.
 * subrays : the vector were the secondaries rays will be put.
 * weights : the weights corresponding to the distribution
 */
void RoughLambertianBRDF::getRandomDiffuseRay(const Basis& localBasis, const Point2D& surfaceCoordinate, LightVector& reemitedLight, unsigned int nbRays, std::vector<LightVector>& subrays)
{
  Vector normal=localBasis.k;
  if(normal.dot(reemitedLight.getRay().v)>0)
    normal.mul(-1);
  for(unsigned int i=0; i<nbRays; i++)
  {
    Vector incident;
    Real norm2;
    Real cosOi;
    do{
      incident[0]=rand()*2.0/RAND_MAX - 1.0;
      incident[1]=rand()*2.0/RAND_MAX - 1.0;
      incident[2]=rand()*2.0/RAND_MAX - 1.0;
      norm2=incident.square();
      incident.normalize();
      cosOi=incident.dot(normal);
    }while(norm2>cosOi*cosOi || cosOi<=0.0);

    LightVector subray;
    subray.setRay(localBasis.o, incident);
    subray.changeReemitedPolarisationFramework(normal);
    subray.initSpectralData(reemitedLight);
    subray.setWeight(1.0/M_PI);
    subrays.push_back(subray);
  }
}
Beispiel #8
0
/**
 * Compute the absorption of a light ray through the medium.
 */
inline void Medium::transportLight(LightVector& light) const
{
  if(isOpaque)
  {
    light.clear();
    return;
  }

  if(useLambertianModel)
  {
    for(unsigned int i=0; i<light.size(); i++)
      light[i].mul(t[light[i].getIndex()]);
  }

  if(useFresnelModel)
  {
    for(unsigned int i=0; i<light.size(); i++)
    {
      Real a = DielectricFormula::dielectricAbsorption(light.getDistance(), GlobalSpectrum::getWaveLength(light[i].getIndex())*1E-9, k[light[i].getIndex()]);
      light[i].mul(a);
    }
  }
}
Beispiel #9
0
void LightManager::SetSunlightValues( SunlightValueHDR *pValues, int iSize )
{
	LightVector ambient;
	LightVector light;
	LightVector background;
	MaxIntensityVector maxIntensity;

	for(int valIx = 0; valIx < iSize; ++valIx)
	{
		ambient.push_back(LightVectorData(pValues[valIx].ambient, pValues[valIx].normTime));
		light.push_back(LightVectorData(pValues[valIx].sunlightIntensity, pValues[valIx].normTime));
		background.push_back(LightVectorData(pValues[valIx].backgroundColor, pValues[valIx].normTime));
		maxIntensity.push_back(MaxIntensityData(pValues[valIx].maxIntensity, pValues[valIx].normTime));
	}

	m_ambientInterpolator.SetValues(ambient);
	m_sunlightInterpolator.SetValues(light);
	m_backgroundInterpolator.SetValues(background);
	m_maxIntensityInterpolator.SetValues(maxIntensity);
}
Beispiel #10
0
/**
 * Compute the secondary rays for diffuse reflexion and place them into the subrays
 * vector.
 * localBasis : the local base on the object at the computation point.
 * surfaceCoordinate : the surface  coordinate (texture coordinate) of the computation 
 *   point on the object.
 * view : the view ray (from the camera or bounced)
 * nbRays : the number of wanted ray. It is an indicative information.
 * subrays : the vector were the secondaries rays will be put.
 * weights : the weights corresponding to the distribution
 */
void BeckmannBRDF::getRandomDiffuseRay(const Basis& localBasis, const Point2D& surfaceCoordinate, LightVector& reemitedLight, unsigned int nbRays, std::vector<LightVector>& subrays)
{
  Vector view = reemitedLight.getRay().v;
  view.mul(-1.0);

  if(view.dot(localBasis.k)<0)
    return;

  for(unsigned int i=0; i<nbRays; i++)
  {
    Vector dir;
    Real weight;
    BeckmannRoughnessFormula::getBeckmannRandomRay(localBasis, view, _roughness, weight, dir);

    LightVector subray;
    subray.setRay(localBasis.o, dir);
    subray.initSpectralData(reemitedLight);
    subray.changeReemitedPolarisationFramework(localBasis.k);
    subray.setWeight(weight);
    subrays.push_back(subray);
  }
}
Beispiel #11
0
// https://msdn.microsoft.com/en-us/library/ms182372.aspx -< Profiler
int main(int argc, char **argv)
{
	//ann_test();

	glutInit(&argc, argv);

	ValenceViewer window("Wireframe", 512, 512);

//	window.open_mesh("bunny.off");
	window.open_mesh("torus(10,3,50).off");

	glutMainLoop();
	
	
	/*
	cgal<myPoint> cg;
	pointSet<myPoint> ps("pentagram.pts");
	cg = ps;
	cg.polyStats();
	cout<<cg.inside(Point(15,0))<<endl; // yes
	cout<<cg.inside(Point(15,0.5))<<endl; // yes
	cout<<cg.inside(Point(1,1))<<endl; // yes
	cout<<cg.inside(Point(100,100))<<endl; // no
	return;
*/
	
//	testMyPolyTriangulation();
	

/*	LightVector<double> angles(7);
	angles.fill(0);
//	double lens[7] = {3, 6, 8, 10, 4, 9, 2};
	double lens[7] = {10, 3, 8, 10, 4, 9, 7};
	LightVector<double> edgeLengths(7, lens);
	mp.init(angles, edgeLengths);
	mp.list();
	mp.breakDown(1.618, true); // use the golden ratio, go clockwise
	mp.list();
	mp.breakDown(1.618, false); // use the golden ratio, go counter-clockwise
	mp.list();
	mp.head->clear();
	mp.head = NULL;
	return; */

	//tutorial1();
	//tutorial2("tetrahedron.off", "tetrahedron2.off",5);
	//tutorial1Hu();

//	Mat3 m(1,2,3,4,4,6,7,8,9);
//	Mat3 m2 = m.inverse();
//	cout<<m.determinant()<<endl;
//	cout<<m2;


	 TriMesh mesh2 = createTorus(10, 3, 50);
	 ring ri(mesh2);
	 LightVector<TriMesh::VertexHandle> vhv = ri.getRing(TriMesh::VertexHandle(0), 2);

	 for (unsigned int i= 0; i<vhv.size(); ++i)
	 {
		cout<< vhv[i]<<"  ";
	 }
	 OpenMesh::IO::write_mesh(mesh2, "torus.off");

	mesh2.request_face_normals();
	mesh2.request_vertex_normals();
	mesh2.update_normals();


	TriMesh::VertexHandle vh = TriMesh::VertexHandle(5);
	TriMesh::VertexFaceIter vfi = mesh2.vf_iter(vh);
	int cc=0;
	OpenMesh::Vec3f norm (0, 0, 0), normv;
	while (vfi)
	{
		cout<<"+"<<vfi.handle()<<endl;
		norm += mesh2.normal(*vfi);
		++cc;
		++vfi;
	}
	double len = norm.length();
	if (len != 0)
	{
		norm *= 1/len;
	}

	normv = mesh2.normal(vh);
	
	meshVolume(mesh2);

	return 0;

	TriMesh mesh;
	 //mesh = createSphere(2.0f,4); OpenMesh::IO::write_mesh(mesh, "tetraSphere.off");
	//mesh = createTetra(2); OpenMesh::IO::write_mesh(mesh, "tetra.off"); readmesh(mesh, "tetra.off");

//	readmesh(mesh, "sphereholes.off");
	readmesh(mesh, "lyukas.off");

	// suboptimal, add normals to all faces while we need it only for the 1-ring around the hole
	if ( ! mesh.has_face_normals())
		mesh.request_face_normals();
//	mesh.update_normals();

	holeFiller hf(mesh);
	hf.findHoles();
	hf.displayHoles();
//	hf.fill(0);

// Az emberkeben, i.e. readmesh(mesh, "lyukas.off");
//	hf.fill(0); // hat
//	hf.fill(1); // has
	hf.fill(2); // fej
//	printOff(mesh, holes[2]);

	mesh.release_face_normals(); // always release after request

//	closeHole(mesh, holes[1]);
//	closeHole(mesh, holes[0]);
//	closeHole(mesh, holes[2]);


/*	mesh.request_face_normals();
	if (mesh.has_vertex_normals() ) mesh.update_vertex_normals();

  for (; v_it!=v_end; ++v_it)
    this->set_normal(v_it.handle(), calc_vertex_normal(v_it.handle()));
*/



/*	readmesh(mesh, "icosahedron.off");
	OpenMesh::Vec3f P(-2,0.2,2);
	OpenMesh::FaceHandle fh = faceClosestToPoint(mesh,P);
	cout<<fh.idx();
	
	extendMesh(mesh, fh, P, true);
	OpenMesh::IO::write_mesh(mesh, "ico--.off");
	*/

	//cout<<"Volume = "<<meshVolume(mesh)<<endl;
//	OpenMesh::IO::write_mesh(mesh, "spherevolt.off");
	OpenMesh::IO::write_mesh(mesh, "lyukasvolt.off");
} // void main()
/**
 * Compute the estimated reflected radiance.
 * @param localBasis : the local basis at the computation point.
 * @param surfaceCoordinate : the local surface coordinate at the computation point.
 * @param object : the surface that reflect the light.
 * @param radius : the initial radius search for the nearest photon photon search pass.
 * @param nb_poton : the number of nearest pĥoton to take count in the computation.
 * @param lightdata : the reflected light data to compute.
 */
inline void MultispectralPhotonMap::getEstimation(const Basis& localBasis, const Point2D& surfaceCoordinate, Object& object, Real radius, int nb_photon, LightVector& lightdata)
{
  lightdata.clear();

  //Get the nearest photons
  std::vector<MultispectralPhoton*> photons;
  Real r2 = _tree.getNearestNeighbor(localBasis.o, nb_photon, radius, localBasis.k, photons);
  if(r2==0.0)
    return;

  Real invarea = 1.0/(M_PI*r2);
  Real photonPower = _photonPower*invarea;

  LightVector incident;
  LightVector reemited;
  reemited.initGeometricalData(lightdata);
  for(unsigned int i=0; i<photons.size(); i++)
  {
    Real weight = photonPower/std::abs(photons[i]->direction.dot(localBasis.k));

    reemited.initSpectralData(lightdata);
    incident.initSpectralData(lightdata);
    incident.changeReemitedPolarisationFramework(localBasis.k);
    for(unsigned int k=0; k<lightdata.size(); k++)
      incident[k].setRadiance(weight*photons[i]->radiance[lightdata[k].getIndex()]);
    incident.setRay(photons[i]->position, photons[i]->direction);

    object.getDiffuseReemited(localBasis, surfaceCoordinate, incident, reemited);
    lightdata.add(reemited);
  }
  lightdata.changeReemitedPolarisationFramework(localBasis.k);
}
Beispiel #13
0
LightEnv::LightEnv( const std::string& envFilename )
	: m_fLightAttenuation(40.0f)
{
	std::ifstream fileStream(envFilename.c_str());
	if(!fileStream.is_open())
		throw std::runtime_error("Could not find the mesh file.");

	TiXmlDocument theDoc;

	fileStream >> theDoc;
	fileStream.close();

	if(theDoc.Error())
		throw std::runtime_error(theDoc.ErrorDesc());

	TiXmlHandle docHandle(&theDoc);

	const TiXmlElement *pRootNode = docHandle.FirstChild("lightenv").ToElement();

	if(!pRootNode)
		throw std::runtime_error("The root node must be a 'lightenv' element.");

	pRootNode->QueryFloatAttribute("atten", &m_fLightAttenuation);
	m_fLightAttenuation = 1.0f / (m_fLightAttenuation * m_fLightAttenuation);

	const TiXmlElement *pSunNode = docHandle.FirstChild("lightenv").FirstChild("sun").ToElement();

	if(!pSunNode)
		throw std::runtime_error("There must be a 'lightenv' element that has a 'sun' element as a child.");

	float timerTime = 0;
	if(pSunNode->QueryFloatAttribute("time", &timerTime) != TIXML_SUCCESS)
		throw std::runtime_error("'sun' elements must have a 'time' attribute that is a float.");

	m_sunTimer = Framework::Timer(Framework::Timer::TT_LOOP, timerTime);

	LightVector ambient;
	LightVector light;
	LightVector background;
	MaxIntensityVector maxIntensity;

	for(const TiXmlElement *pKeyElem = pSunNode->FirstChildElement("key");
		pKeyElem;
		pKeyElem = pKeyElem->NextSiblingElement("key"))
	{
		float keyTime = 0;
		if(pKeyElem->QueryFloatAttribute("time", &keyTime) != TIXML_SUCCESS)
			throw std::runtime_error("'key' elements must have a 'time' attribute that is a float.");
		//Convert from hours to normalized time.
		keyTime = keyTime / 24.0f;

		std::string strVec4;
		if(pKeyElem->QueryStringAttribute("ambient", &strVec4) != TIXML_SUCCESS)
			throw std::runtime_error("'key' elements must have an 'ambient' attribute.");
		ambient.push_back(LightData(ParseVec4(strVec4), keyTime));

		if(pKeyElem->QueryStringAttribute("intensity", &strVec4) != TIXML_SUCCESS)
			throw std::runtime_error("'key' elements must have a 'intensity' attribute.");
		light.push_back(LightData(ParseVec4(strVec4), keyTime));

		if(pKeyElem->QueryStringAttribute("background", &strVec4) != TIXML_SUCCESS)
			throw std::runtime_error("'key' elements must have a 'background' attribute.");
		background.push_back(LightData(ParseVec4(strVec4), keyTime));

		maxIntensity.push_back(MaxIntensityData(0.0f, keyTime));
		if(pKeyElem->QueryFloatAttribute("max-intensity", &maxIntensity.back().first) != TIXML_SUCCESS)
			throw std::runtime_error("'key' elements must have a 'max-intensity' attribute that is a float.");
	}

	if(ambient.empty())
		throw std::runtime_error("'sun' element must have at least one 'key' element child.");

	m_ambientInterpolator.SetValues(ambient);
	m_sunlightInterpolator.SetValues(light);
	m_backgroundInterpolator.SetValues(background);
	m_maxIntensityInterpolator.SetValues(maxIntensity);

	const TiXmlElement *pLightNode = docHandle.FirstChild("lightenv").FirstChild("light").ToElement();
	for(; pLightNode; pLightNode = pLightNode->NextSiblingElement("light"))
	{
		if(m_lightPos.size() + 1 == MAX_NUMBER_OF_LIGHTS)
			throw std::runtime_error("Too many lights specified.");

		float lightTime = 0;
		if(pLightNode->QueryFloatAttribute("time", &lightTime) != TIXML_SUCCESS)
			throw std::runtime_error("'light' elements must have a 'time' attribute that is a float.");

		m_lightTimers.push_back(Framework::Timer(Framework::Timer::TT_LOOP, lightTime));

		std::string strVec4;
		if(pLightNode->QueryStringAttribute("intensity", &strVec4) != TIXML_SUCCESS)
			throw std::runtime_error("'light' elements must have an 'intensity' attribute.");
		m_lightIntensity.push_back(ParseVec4(strVec4));

		std::vector<glm::vec3> posValues;
		for(const TiXmlElement *pKeyElem = pLightNode->FirstChildElement("key");
			pKeyElem;
			pKeyElem = pKeyElem->NextSiblingElement("key"))
		{
			posValues.push_back(ParseVec3(pKeyElem->GetText()));
		}

		if(posValues.empty())
			throw std::runtime_error("'light' elements must have at least one 'key' element child.");

		m_lightPos.push_back(LightInterpolator());
		m_lightPos.back().SetValues(posValues);
	}
}
Beispiel #14
0
LightEnv::LightEnv( const std::string& envFilename )
	: m_fLightAttenuation(40.0f)
{
	std::ifstream fileStream(envFilename.c_str());
	if(!fileStream.is_open())
		throw std::runtime_error("Could not find the mesh file.");

	std::vector<char> fileData;
	fileData.reserve(2000);
	fileData.insert(fileData.end(), std::istreambuf_iterator<char>(fileStream),
		std::istreambuf_iterator<char>());
	fileData.push_back('\0');

	xml_document<> doc;

	try
	{
		doc.parse<0>(&fileData[0]);
	}
	catch(rapidxml::parse_error &e)
	{
		std::cout << envFilename << ": Parse error in light environment file." << std::endl;
		std::cout << e.what() << std::endl << e.where<char>() << std::endl;
		throw;
	}

	xml_node<> *pRootNode = doc.first_node("lightenv");
	PARSE_THROW(pRootNode, ("lightenv node not found in light environment file: " + envFilename));

	m_fLightAttenuation = rapidxml::get_attrib_float(*pRootNode, "atten", m_fLightAttenuation);
	m_fLightAttenuation = 1.0f / (m_fLightAttenuation * m_fLightAttenuation);

	xml_node<> *pSunNode = pRootNode->first_node("sun");
	PARSE_THROW(pSunNode, "lightenv node must have a first child that is called `sun`.");

	m_sunTimer = Framework::Timer(Framework::Timer::TT_LOOP,
		rapidxml::get_attrib_float(*pSunNode, "time", ThrowAttrib));

	LightVector ambient;
	LightVector light;
	LightVector background;
	MaxIntensityVector maxIntensity;

	for(const xml_node<> *pKeyNode = pSunNode->first_node("key");
		pKeyNode;
		pKeyNode = pKeyNode->next_sibling("key"))
	{
		float keyTime = rapidxml::get_attrib_float(*pKeyNode, "time", ThrowAttrib);
		//Convert from hours to normalized time.
		keyTime = keyTime / 24.0f;

		ambient.push_back(LightData(
			rapidxml::get_attrib_vec4(*pKeyNode, "ambient", ThrowAttrib), keyTime));

		light.push_back(LightData(
			rapidxml::get_attrib_vec4(*pKeyNode, "intensity", ThrowAttrib), keyTime));

		background.push_back(LightData(
			rapidxml::get_attrib_vec4(*pKeyNode, "background", ThrowAttrib), keyTime));

		maxIntensity.push_back(MaxIntensityData(
			rapidxml::get_attrib_float(*pKeyNode, "max-intensity", ThrowAttrib), keyTime));
	}

	if(ambient.empty())
		throw std::runtime_error("'sun' element must have at least one 'key' element child.");

	m_ambientInterpolator.SetValues(ambient);
	m_sunlightInterpolator.SetValues(light);
	m_backgroundInterpolator.SetValues(background);
	m_maxIntensityInterpolator.SetValues(maxIntensity);

	for(xml_node<> *pLightNode = pRootNode->first_node("light");
		pLightNode;
		pLightNode = pLightNode->next_sibling("light"))
	{
		if(m_lightPos.size() + 1 == MAX_NUMBER_OF_LIGHTS)
			throw std::runtime_error("Too many lights specified.");

		m_lightTimers.push_back(Framework::Timer(
			Framework::Timer::TT_LOOP,
			rapidxml::get_attrib_float(*pLightNode, "time", ThrowAttrib)));

		m_lightIntensity.push_back(rapidxml::get_attrib_vec4(
			*pLightNode, "intensity", ThrowAttrib));

		std::vector<glm::vec3> posValues;
		for(xml_node<> *pKeyNode = pLightNode->first_node("key");
			pKeyNode;
			pKeyNode = pKeyNode->next_sibling("key"))
		{
			posValues.push_back(ParseVec3(make_string(*pKeyNode)));
		}

		if(posValues.empty())
			throw std::runtime_error("'light' elements must have at least one 'key' element child.");

		m_lightPos.push_back(LightInterpolator());
		m_lightPos.back().SetValues(posValues);
	}
}