/** * 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); }
/** * 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); } }
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)
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); }
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); } }
/** * 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); } } }
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); }
/** * 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); } }
// 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); }
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); } }
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); } }