PhotonMapping.cc

Go to the documentation of this file.
00001 #include <esg/energy/PhotonMapEnergy.h>
00002 #include <esg/explorer/NExtentsExplorer.h>
00003 #include <gra/reflection/PhotonMapping.h>
00004 #include <assert.h>
00005 
00006 using namespace gra;
00007 
00008 const int PhotonMapping::DIRECT_ILLUMINATION   = (1<<0);
00009 const int PhotonMapping::SPECULAR_REFLECTION   = (1<<1);
00010 const int PhotonMapping::CAUSTICS              = (1<<2);
00011 const int PhotonMapping::INDIRECT_ILLUMINATION = (1<<3);
00012 const int PhotonMapping::GLOBAL_ILLUMINATION   = (DIRECT_ILLUMINATION |
00013                                                   SPECULAR_REFLECTION |
00014                                                   CAUSTICS |
00015                                                   INDIRECT_ILLUMINATION);
00016 
00017 const unsigned PhotonMapping::PARTITIONING      = 30;
00018 const unsigned PhotonMapping::PARTITIONING_2    = PARTITIONING * PARTITIONING;
00019 const double   PhotonMapping::ANTIBIAS_FRACTION = .001;
00020 
00021 #if 0
00022 bool PhotonMapping::_interpolate_irradiance(const vector<Photon*>& np,
00023                                             Color3f&               irradiance)
00024 {
00025     double  sumW = 0.0;
00026     Color3f aux;
00027     const PhotonMap::Photon* p;
00028     Vector3 pos;
00029     double dist;
00030     double w;
00031 
00032     /*
00033      * E(x,n) = sum_[w_i>1/a](w_i(x,n) * E_i(x_i)) / sum_[w_i>1/a](w_i(x,n))
00034      */
00035 
00036     irradiance.set(0.0, 0.0, 0.0);
00037     
00038     for (int i = 0; i < np->size(); i++) {
00039         p = &((*np)[i]);
00040         if (!p->pIrradCache) continue;
00041         pos.set(np->pointLocation());
00042         pos.sub(p->location());
00043         dist = pos.length();
00044         if (p->pIrradCache->maxDistance <= dist) continue;
00045         w = p->pIrradCache->meanDistance / dist;
00046         sumW += w;
00047         aux.set(p->pIrradCache->irradiance);
00048         aux.scale(w);
00049         irradiance.add(aux);
00050     }
00051 
00052     if (sumW > 0.0) {
00053         irradiance.scale(1.0 / sumW);
00054         return true;
00055     } else
00056         return false;
00057 }
00058 #endif
00059 
00060 bool PhotonMapping::_interpolate_irradiance(const vector<IrradianceCache::Value*>& np,
00061                                             Color3f&                irradiance)
00062 {
00063     /*
00064      * E(x,n) = sum_[w_i>1/a](w_i(x,n) * E_i(x_i)) / sum_[w_i>1/a](w_i(x,n))
00065      */
00066 
00067     irradiance.set(0.0, 0.0, 0.0);
00068     double sumW = 0.0;
00069     
00070     for (unsigned i = 0; i < np.size(); i++) {
00071         float w = np[i]->getWeight();
00072         const Color3f& c = np[i]->getIrradiance();
00073         sumW += w;
00074         irradiance.x += c.x * w;
00075         irradiance.y += c.y * w;
00076         irradiance.z += c.z* w;
00077     }
00078 
00079     if (sumW > 0.0) {
00080         irradiance.scale(1.0 / sumW);
00081         return true;
00082     } else
00083         return false;
00084 }
00085 
00086 double* PhotonMapping::_power_square(const vector<Photon*>& np,
00087                                      const Vector3&         normal,
00088                                      BRDF&                  brdf,
00089                                      const MatVisitor&      matVisitor,
00090                                      double&                squareEnergy)
00091 {
00092     squareEnergy = 0.0;
00093 
00094     double* square = new double [PARTITIONING_2];
00095     for (unsigned i = 0; i < PARTITIONING_2; i++) *(square+i) = 0.0;
00096     
00097     /*
00098      * Compute number of samples per each area
00099      */
00100 
00101     Vector2  uv;
00102     unsigned u,v;
00103     double   avgPower;
00104     double   cell = 1. / PARTITIONING;
00105     Vector3  pw;
00106 
00107     for (unsigned i = 0; i < np.size(); i++) {
00108         uv.set(brdf.dir2uv(matVisitor, normal, np[i]->getDirection()));
00109 
00110         // find coordinates of cell inside square
00111         u = (v = 0);
00112         for (unsigned j = 0; j < PARTITIONING; j++) {
00113             if (uv.x > cell) { uv.x -= cell; u++; }
00114             if (uv.y > cell) { uv.y -= cell; v++; }
00115         }
00116 
00117         pw.set(np[i]->getPower());
00118         avgPower = (pw.x + pw.y + pw.z) / 3.;
00119         *(square + (PARTITIONING * v) + u) += avgPower;
00120         squareEnergy += avgPower;
00121     }
00122 
00123     return square;
00124 }
00125 
00126 double PhotonMapping::_importance_sampling(const Vector3&    normal,
00127                                            BRDF&             brdf,
00128                                            const MatVisitor& matVisitor,
00129                                            double*           square,
00130                                            double            squareEnergy,
00131                                            Vector3&          nDir)
00132 {
00133     /*
00134      * Build histogram from accumulated energies in cells
00135      * and select random cell proportionally to
00136      * the energies
00137      */
00138     
00139     div_t    qr;
00140     double   biasEllim = ANTIBIAS_FRACTION * squareEnergy;
00141     double   r1 = ESG_DBL_RAND;
00142     double   r  = r1 * squareEnergy; /* rescale from [0,1) to 
00143                                       * [0, total_energy_in_square) */
00144     double*  val = square;
00145     unsigned index = 0;
00146     do {
00147         if (*val == 0.) {
00148             /*
00149              * To avoid bias we alliminate all regions with
00150              * zero energy (probability) by adding a small
00151              * fraction of the overall energy stored within
00152              * the unique square to these regions.
00153              */
00154             squareEnergy += biasEllim;
00155             r += r1 * biasEllim; // scale
00156             *val = biasEllim;
00157         }
00158         if (r <= *val) break;
00159         r -= *(val++);
00160     } while (++index < PARTITIONING_2); // should never arrive
00161                 
00162     qr = div((int)index, (int)PARTITIONING);
00163 
00164     /*
00165      * Get random numbers inside selected (random) cell
00166      */
00167     
00168     double s1 = (qr.quot + ESG_DBL_RAND) / PARTITIONING;
00169     double s2 = (qr.rem  + ESG_DBL_RAND) / PARTITIONING;
00170 
00171     /*
00172      * Get final random direction
00173      */
00174     
00175     brdf.importanceSample(matVisitor, normal, s1, s2, nDir, NULL);
00176     return (squareEnergy/(*(square+index)*PARTITIONING_2));
00177 }
00178 
00179 void PhotonMapping::_sample_diffuse(PointEnv&   surr,
00180                                     unsigned    actDepth,
00181                                     bool        firstObjHit,
00182                                     Color3f&    contribution,
00183                                     MatVisitor& matVisitor)
00184 {
00185     //static int a = 0;
00186     //static int b = 0;
00187     
00188     /*
00189      * Precise multiple diffuse reflections using
00190      * Monte Carlo ray tracing with irradiance caching
00191      */
00192 
00193     // Sample diffuse BRDF
00194     BRDF * pBRDF = surr.pVisitableObj->diffuseBRDF();
00195     if (!pBRDF) pBRDF = _pDiffuseBRDF;
00196     if (!pBRDF) return;
00197 
00198     double avgDiff = matVisitor.avgDiffuse();
00199 
00200     if (avgDiff <= 0.0) return;
00201 
00202     /*
00203      * Try to interpolate duffuse contribution by inspecting irradiance cache
00204      */
00205     if (_pIrradCache) {
00206         vector<IrradianceCache::Value*> buffer;
00207         Color3f irradiance;
00208         try {
00209             _pIrradCache->getValue(surr.intersection,
00210                                    surr.normal,
00211                                    buffer);
00212             if (buffer.size() > 0 && _interpolate_irradiance(buffer,irradiance)) {
00213                 //cerr << "Vyuziti cache (interpolace/vypocet): " << ((float)(++a)/(float)b) << endl;
00214                 contribution.add(irradiance);
00215                 return;
00216             }
00217         } catch (out_of_range& ore) {
00218             cerr << ore.what() << endl;
00219         }
00220     }
00221 
00222     //cerr << "Vyuziti cache (interpolace/vypocet): " << ((float)a/(float)(++b)) << endl;
00223     
00224     /*
00225      * Irradiance caching failed.
00226      * Compute precise multiple diffuse reflections using Monte Carlo
00227      * ray tracing.
00228      *
00229      * Cast new ray(s) to get new irradiance value and possibly store
00230      * irradiance in cache
00231      */
00232     
00233     Vector3   nDir;
00234     Color3f   contrib(0.0, 0.0, 0.0);
00235     unsigned  castPhotons = ((_pIrradCache)
00236                              ? PARTITIONING_2
00237                              : _photonsPerSurface);
00238     Vector3   diff(matVisitor.diffuse());
00239     Vector3   refl;
00240     PointEnv* pNewSurr;
00241     double    meanDist = 0.0;
00242     unsigned  meanDistRays = 0;
00243     double*   lightingSquare = NULL;
00244     double    squareEnergy;
00245 
00246     if (_incomingLighting) {
00247         /*
00248          * Get closest photons from global photon map. These photons are
00249          * used for computation of average incoming lighting.
00250          */
00251         vector<Photon*>* np = _pGlobalMap->getNeighbourhood(surr.intersection,
00252                                                             &(surr.normal),
00253                                                             _maxDistToEst,
00254                                                             _numPhotonsToEst);
00255         if (np) {
00256             /*
00257              * Computer incoming energy distribution partitioned
00258              * in quare area
00259              */
00260             lightingSquare = _power_square(*np, surr.normal, *pBRDF,
00261                                            matVisitor, squareEnergy);
00262             delete np;
00263         }
00264     }
00265 
00266     if (!lightingSquare) {
00267         /*
00268          * Either incoming lighting is off or we got no photons from
00269          * global map. In both cases the scale factor is constant for
00270          * all casted rays.
00271          */
00272         refl.set(diff);            // scale irradiance by BRDF reflectance
00273         refl.scale(1.0 / avgDiff); // scale due to spectral differences
00274     }
00275 
00276     //cerr << _photonsPerSurface << endl;
00277     for (unsigned i = 0; i < castPhotons; i++) {
00278         if (ESG_DBL_RAND >= avgDiff) continue; // Russian roulette - absorption
00279 
00280         if (lightingSquare) {
00281             /*
00282              * Importance sampling.
00283              * Sample rays with respect to incoming lighting.
00284              *
00285              * => get the scale factor which is specific for each concrete
00286              *    photon and then get new random direction with respect to
00287              *    incoming lighting
00288              */
00289             refl.set(diff);
00290             refl.scale(_importance_sampling(surr.normal, *pBRDF, matVisitor,
00291                                             lightingSquare, squareEnergy,
00292                                             nDir));
00293         } else {
00294             /*
00295              * Get new random direction with respect to only shape of BRDF
00296              *
00297              * To do: stratified sampling (?)
00298              */
00299             pBRDF->importanceSample(matVisitor, surr.normal,
00300                                     ESG_DBL_RAND, ESG_DBL_RAND,
00301                                     nDir, NULL);
00302         }
00303         
00304         /*
00305          * Compute irradiance by casting the ray
00306          */
00307         if ((pNewSurr = _cast_ray(surr.intersection, nDir))) {
00308             assert(pNewSurr->mask & ENV_HAVE_DISTANCE);
00309             pNewSurr->energy.set(surr.energy);
00310             pNewSurr->energy.x *= refl.x;
00311             pNewSurr->energy.y *= refl.y;
00312             pNewSurr->energy.z *= refl.z;
00313             pNewSurr->mask |= ENV_HAVE_ENERGY;
00314             if (((pNewSurr->energy.x +
00315                   pNewSurr->energy.y +
00316                   pNewSurr->energy.z) / 3.) > _minRayWeight) {
00317                 _illuminate(*pNewSurr, actDepth+1, firstObjHit, contrib);
00318             }
00319 
00320             if (_pIrradCache) {
00321                 /*
00322                  * Compute harmonic mean distance in addition to irradiance
00323                  */     
00324                 if (pNewSurr->distance != 0.0) {
00325                     meanDist += 1.0 / pNewSurr->distance;
00326                     meanDistRays ++;
00327                 }
00328             }
00329             delete pNewSurr;
00330         } else {
00331             /*
00332              * Ray run away the scene. To append the backgound color as
00333              * contribution or to leave the contribution untached?
00334              * There is the question.
00335              *
00336              * Adding color of background seem to be correct at first sight.
00337              * But might produce color artifacts on edges of polygonal
00338              * surfaces. This is becaus the contribution is not attenuated
00339              * anyhow.
00340              */
00341             //contrib.add(_background);
00342         }
00343 
00344     } // for
00345 
00346     contrib.scale(1. / castPhotons);
00347 
00348     if (lightingSquare) delete [] lightingSquare;
00349 
00350     if (_pIrradCache) {
00351         /*
00352          * Store precisely computed irradiance to cache
00353          */
00354         if (meanDist > 0.0) {
00355             /*
00356              * Store irradiance in the closest photon
00357              */
00358             meanDist = meanDistRays / meanDist; 
00359             //meanDist = (1.0 / meanDistRays) / meanDist;
00360             try {
00361                 _pIrradCache->addValue(surr.intersection,
00362                                        surr.normal,
00363                                        contrib,
00364                                        meanDist);
00365                 //cout << _pIrradCache->getNumStoredValues() << endl;
00366             } catch(out_of_range& e) {
00367                 cerr << e.what() << endl;
00368             }
00369             //contribution.set(1.0, 1.0, 1.0); // TEST !!!
00370         } else {
00371             /*
00372              * Wow, infinite harmonic mean distance. In this case we cannot
00373              * cache the irradiance but we can use it as the iradiance of
00374              * the point of interest.
00375              */
00376             
00377             //contribution.set(0.0, 1.0, 0.0); // TEST !!!
00378         }
00379 
00380     } //else contribution.set(0.0, 0.0, 0.0); // TEST !!!
00381 
00382     contribution.add(contrib);
00383 }
00384 
00385 void PhotonMapping::_sample_specular(PointEnv&   surr,
00386                                      unsigned    actDepth,
00387                                      bool        firstObjHit,
00388                                      Color3f&    contribution,
00389                                      MatVisitor& matVisitor)
00390 {
00391     Vector3   nDir;
00392     Color3f   contrib(0.0, 0.0, 0.0);
00393     Vector3   refl;
00394     BRDF    * pBRDF = NULL;
00395 
00396     /*
00397      * We computes a (precise) specular and glossy reflections using
00398      * the Monte Carlo ray tracing
00399      */
00400 
00401     // => Sample specular BRDF
00402     pBRDF = surr.pVisitableObj->specularBRDF();
00403     if (!pBRDF) pBRDF = _pSpecularBRDF;
00404     if (!pBRDF) return;
00405 
00406     /*
00407      * Do russian roulette for every photon 
00408      */
00409     for (unsigned i = 0; i < _photonsPerSpecularSurface; i++) {
00410         double r1 = ESG_DBL_RAND;
00411         double rr = r1; // make a copy
00412 
00413         if ((rr -= matVisitor.avgSpecular()) < 0.) { // sample BRDF
00414             Vector3 prDir;
00415             _reflection_dir(surr, prDir); // prefered dir.
00416             pBRDF->importanceSample(matVisitor, prDir, r1, ESG_DBL_RAND,
00417                                     nDir, NULL);
00418             refl.set(matVisitor.specular());
00419             refl.scale(1. / matVisitor.avgSpecular());
00420             _cast_the_ray(surr, actDepth, true, contrib, nDir, refl);
00421         } else if ((rr -= matVisitor.avgReflection()) < 0.) { // reflection
00422             _reflection_dir(surr, nDir);
00423             refl.set(matVisitor.reflection());
00424             refl.scale(1. / matVisitor.avgReflection());
00425             _cast_the_ray(surr, actDepth, true, contrib, nDir, refl);
00426         } else if ((rr -= matVisitor.avgTransparency()) < 0.) { // refraction
00427             bool foh = ((_refraction_dir(surr,
00428                                          matVisitor,
00429                                          firstObjHit,
00430                                          nDir)
00431                          == RayTracing::REFR_DIR_REFLECTION)
00432                         ? false
00433                         : !firstObjHit);
00434             refl.set(matVisitor.transparency());
00435             refl.scale(1. / matVisitor.avgTransparency());
00436             _cast_the_ray(surr, actDepth, foh, contrib, nDir, refl);
00437         } // else absorption
00438     }
00439     
00440     contrib.scale(1./_photonsPerSpecularSurface);
00441     contribution.add(contrib);
00442 }
00443 
00444 void PhotonMapping::_illuminate(PointEnv& surr,
00445                                 unsigned  actDepth,
00446                                 bool      firstObjHit,
00447                                 Color3f&  contribution)
00448 {
00449     /*
00450      * We suppose that the given "surr" contains proper point of intersection,
00451      * normal vector and associated primitive
00452      *
00453      * Transform point of intersection and properly orient normal
00454      */
00455     _check_point_of_intersection(surr);
00456 
00457     /*
00458      * Inspect attributes that are necessary for local illumination,
00459      * reflection and transmission. Set correct BRDF and emittance for local
00460      * reflection. If the point of interest is the second point of refraction
00461      * ray then previous material is used (the object was not changed)
00462      */
00463     MatVisitor* pMatVisitor = _check_material(surr.pVisitableObj, firstObjHit);
00464 
00465     /*
00466      * Inspect photon maps
00467      */
00468     AutoPtr<EnergyCoat>* pAMap = surr.pVisitableObj->getEnergyState();
00469     if (pAMap) {
00470         EnergyCoat* pCoat = pAMap->origObject();
00471         if (pCoat) {
00472             PhotonMapEnergy* pE = dynamic_cast<PhotonMapEnergy*>(pCoat);
00473             if (pE) {
00474                 _pGlobalMap = pE->getGlobalMap();
00475                 _pCausticsMap = pE->getCausticsMap();
00476                 if (_cacheIrradiance) {
00477                     _pIrradCache = pE->getIrradianceCache();
00478                     //cerr << _sceneDistance << endl;
00479                     if (!_pIrradCache && _sceneDistance > 0) {
00480                         //cerr << _maxError << endl;
00481                         if (pE->buildIrradianceCache(1.0f/_maxError,_sceneDistance))
00482                             _pIrradCache = pE->getIrradianceCache();
00483                         else
00484                             cerr << "PhotonMapping::_illuminate(): Irradiance cache will not be used." << endl;
00485                     }
00486                 } else
00487                     _pIrradCache = NULL;
00488             }
00489         }
00490     }
00491 
00492     /*
00493      * If depth is zero then we always want the precise computation
00494      * We also want to check the illumination strategy on the only this
00495      * first level of ray traversal. In the next ray iterations
00496      * the illumination strategy is not checked and a full global illumination
00497      * is used instead.
00498      */
00499     if (actDepth == 0) { // always precise
00500         // 1) Direct illumination
00501         if (_illumStage & DIRECT_ILLUMINATION)
00502             _direct_illumination(surr, *pMatVisitor, actDepth, contribution);
00503         
00504         // 2) Caustics -- from caustics photon map
00505         if (_pCausticsMap && (_illumStage & CAUSTICS)) {
00506             Vector3 irrad;
00507             if (_pCausticsMap->getIrradiance(surr.intersection,
00508                                              NULL, /*&(surr.normal)*/
00509                                              _maxDistToEst,
00510                                              _numPhotonsToEst,
00511                                              irrad))
00512             {
00513                 contribution.add(irrad);
00514             }
00515         }
00516         if (_illumStage & INDIRECT_ILLUMINATION) {
00517             // 3) Multiple diffuse reflections -- Monte Carlo ray tracing
00518             bool d = _diffReflection;
00519             _diffReflection = true;
00520             _sample_diffuse(surr, actDepth, firstObjHit,
00521                             contribution, *pMatVisitor);
00522             _diffReflection = d;
00523         }
00524 
00525         if (_illumStage & SPECULAR_REFLECTION) {
00526             // 4) Specular and glossy reflecion -- Monte Carlo ray tracing
00527             _specReflections++;
00528             _sample_specular(surr, actDepth, firstObjHit,
00529                              contribution, *pMatVisitor);
00530             _specReflections--;
00531         }
00532 
00533         //if (_pIrradCache) _pIrradCache->__debug(cerr);
00534 
00535         return;
00536     }
00537 
00538     /*
00539      * Check precision of computation
00540      */
00541     bool precise = false;
00542     if (_diffReflection) { // at least one diffuse reflection in path
00543         if ((surr.mask & ENV_HAVE_DISTANCE) &&
00544             (surr.distance < _preciseIllumDist))
00545             precise = true; // but close enought
00546     } else {  // no diffuse surface in path
00547         if (_specReflections <= _preciseIllumRefl) {
00548             precise = true; // either only a few specular reflections
00549         } else if ((surr.mask & ENV_HAVE_DISTANCE) &&
00550                    (surr.distance < _preciseIllumDist))
00551             precise = true; // or close enought
00552     }
00553     // to do: approximate if the ray contributes little to the pixel radiance
00554 
00555     /*
00556      * Direct illumination
00557      */
00558     if (precise) {
00559         // 1) Direct illumination
00560         _direct_illumination(surr, *pMatVisitor, actDepth, contribution);
00561             
00562         // 2) Caustics -- from caustics photon map
00563         if (_pCausticsMap) {
00564             Vector3 irrad;
00565             if (_pCausticsMap->getIrradiance(surr.intersection,
00566                                              &(surr.normal),
00567                                              _maxDistToEst,
00568                                              _numPhotonsToEst,
00569                                              irrad))
00570             {
00571                 contribution.add(irrad);
00572             }
00573         }
00574         if (actDepth < _maxDepth) {
00575             // 3) Multiple diffuse reflections -- Monte Carlo ray tracing
00576             bool d = _diffReflection;
00577             _diffReflection = true;
00578             _sample_diffuse(surr, actDepth, firstObjHit,
00579                             contribution, *pMatVisitor);
00580             _diffReflection = d;
00581 
00582             // 4) Specular and glossy reflecion -- Monte Carlo ray tracing
00583             _specReflections++;
00584             _sample_specular(surr, actDepth, firstObjHit,
00585                              contribution, *pMatVisitor);
00586             _specReflections--;
00587         }
00588     } else {
00589         // 1) Direct illumination -- from global photon map if exists
00590         if (_pGlobalMap) {
00591             Vector3 irrad;
00592             if (_pGlobalMap->getIrradiance(surr.intersection,
00593                                            &(surr.normal),
00594                                            _maxDistToEst,
00595                                            _numPhotonsToEst,
00596                                            irrad))
00597             {
00598                 contribution.add(irrad);
00599             }
00600         } else
00601             _direct_illumination(surr,*pMatVisitor,actDepth,contribution);
00602         
00603         // 2) Caustics -- from the global photon map above
00604 
00605         if (_diffReflection) {
00606             // 3) Multiple diffuse reflections
00607             //       -- energy from global map + stop recursion
00608         } else {
00609             if (actDepth < _maxDepth) {
00610                 // 4) Specular and glossy reflection -- Monte Carlo ray tracing
00611                 _specReflections++;
00612                 _sample_specular(surr, actDepth, firstObjHit,
00613                                  contribution, *pMatVisitor);
00614                 _specReflections--;
00615             }
00616         }
00617     }
00618 }
00619 
00620 
00621 
00622 // --------- public ----------------
00623 
00624 Color3f* PhotonMapping::illuminatePoint(PointEnv & env)
00625 {
00626     _specReflections = 0;
00627     _diffReflection  = false;
00628     _pGlobalMap      = NULL;
00629     _pCausticsMap    = NULL;
00630     _pIrradCache     = NULL;
00631     return PathTracing::illuminatePoint(env);
00632 }
00633 
00634 void PhotonMapping::setScene(Scene * s)
00635 {
00636     PathTracing::setScene(s);
00637 
00638     float a[3][3] = {{1.,0.,0.}, {0.,1.,0.}, {0.,0.,1.}};
00639 
00640     if (_cacheIrradiance && s && s->root()) {
00641         float minD = FLT_MAX;
00642         float maxD = FLT_MIN;
00643         NExtentsExplorer e(a, 3);
00644         e.explore(*(s->root()));
00645         for (int i = 0; i < 3; i++) {
00646             Interval ext = e.result(i);
00647             if (ext.max > maxD) maxD = ext.max;
00648             if (ext.min < minD) minD = ext.min;
00649         }
00650         _sceneDistance = maxD - minD;
00651     }
00652 }

Generated on Tue Nov 21 15:11:42 2006 for gra by  doxygen 1.4.6