• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Classes
  • Files
  • File List

misc.hh

00001 // Copyright (C) 2008, 2009 EPITA Research and Development Laboratory
00002 // (LRDE)
00003 //
00004 // This file is part of the Milena Library.  This library is free
00005 // software; you can redistribute it and/or modify it under the terms
00006 // of the GNU General Public License version 2 as published by the
00007 // Free Software Foundation.
00008 //
00009 // This library is distributed in the hope that it will be useful,
00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012 // General Public License for more details.
00013 //
00014 // You should have received a copy of the GNU General Public License
00015 // along with this library; see the file COPYING.  If not, write to
00016 // the Free Software Foundation, 51 Franklin Street, Fifth Floor,
00017 // Boston, MA 02111-1307, USA.
00018 //
00019 // As a special exception, you may use this file as part of a free
00020 // software library without restriction.  Specifically, if other files
00021 // instantiate templates or use macros or inline functions from this
00022 // file, or you compile this file and link it with other files to
00023 // produce an executable, this file does not by itself cause the
00024 // resulting executable to be covered by the GNU General Public
00025 // License.  This exception does not however invalidate any other
00026 // reasons why the executable file might be covered by the GNU General
00027 // Public License.
00028 
00029 // FIXME: Split this file.
00030 // FIXME: Address license-related issues?
00031 
00032 #ifndef MILENA_APPS_STATUES_MISC_HH
00033 # define MILENA_APPS_STATUES_MISC_HH
00034 
00035 # include <algorithm>  // For std::swap.
00036 # include <utility>    // For std::pair.
00037 
00038 # include <mln/algebra/vec.hh>
00039 # include <mln/algebra/mat.hh>
00040 
00041 # include <mln/norm/l2.hh>
00042 
00043 # include <mln/data/fill.hh>
00044 
00045 
00052 // FIXME: We should turn `likely' and `unlikely' into functions.
00053 #ifndef likely
00054 #  if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
00055 #    define likely(x) (x)
00056 #    define unlikely(x) (x)
00057 #  else
00058 #    define likely(x)   (__builtin_expect((x), 1))
00059 #    define unlikely(x) (__builtin_expect((x), 0))
00060 #  endif
00061 #endif
00062 
00063 namespace mln
00064 {
00065 
00066   namespace algebra
00067   {
00068 
00069     // From Trimesh.
00070     // FIXME: Doc (input, return value, etc.).
00071     // FIXME: Idea: Add reference to Numerical Recipes.
00072 
00078     template <unsigned N, typename T>
00079     inline
00080     bool
00081     ldlt_decomp(mat<N, N, T>& A, vec<N, T>& rdiag)
00082     {
00083       vec<N - 1, T> v;
00084       for (unsigned i = 0; i < N; ++i)
00085         {
00086           for (unsigned k = 0; k < i; ++k)
00087             v[k] = A(i, k) * rdiag[k];
00088           for (unsigned j = i; j < N; ++j)
00089             {
00090               T sum = A(i, j);
00091               for (unsigned k = 0; k < i; k++)
00092                 sum -= v[k] * A(j, k);
00093               if (i == j)
00094                 {
00095                   if (unlikely(sum <= T(0)))
00096                     return false;
00097                   rdiag[i] = T(1) / sum;
00098                 }
00099               else
00100                 A(j, i) = sum;
00101             }
00102         }
00103       return true;
00104     }
00105 
00106     // From Trimesh.
00107     // FIXME: Doc (input, return value, etc.).
00108     // FIXME: Idea: Add reference to Numerical Recipes.
00109 
00111     template <unsigned N, typename T>
00112     inline
00113     void
00114     ldlt_solve(const mat<N, N, T>& A, const vec<N, T>& rdiag,
00115                const vec<N, T>& B, vec<N, T>& x)
00116     {
00117       for (unsigned i = 0; i < N; ++i)
00118         {
00119           T sum = B[i];
00120           for (unsigned k = 0; k < i; ++k)
00121             sum -= A(i, k) * x[k];
00122           x[i] = sum * rdiag[i];
00123         }
00124       for (int i = N - 1; i >= 0; --i)
00125         {
00126           T sum = 0;
00127           for (unsigned k = i + 1; k < N; ++k)
00128             sum += A(k, i) * x[k];
00129           x[i] -= sum * rdiag[i];
00130         }
00131     }
00132 
00133   } // end of namespace mln::algebra
00134 
00135 } // end of namespace mln
00136 
00137 
00138 // FIXME: To be moved elsewhere (in mln/geom)?
00139 
00140 // Inspired from Trimesh.
00141 
00142 namespace mln
00143 {
00144 
00145   namespace geom
00146   {
00147 
00148     // FIXME: More doc (see Trimesh's).
00149 
00150     /* FIXME: We should restrict attached data to vertices in return
00151        value.  */
00161     inline
00162     complex_image< 2, mln::space_2complex_geometry, algebra::vec<3, float> >
00163     mesh_normal(const p_complex<2, space_2complex_geometry>& mesh)
00164     {
00165       // Shortcuts.
00166       static const unsigned D = 2;
00167       typedef space_2complex_geometry G;
00168       typedef algebra::vec<3, float> vec3f;
00169       typedef complex_image< D, G, vec3f > normal_t;
00170 
00171       normal_t normal(mesh);
00172       data::fill(normal, literal::zero);
00173 
00174       mln::p_n_faces_fwd_piter<D, G> f(mesh, 2);
00175       // A neighborhood where neighbors are the set of 0-faces
00176       // transitively adjacent to the reference point.
00177       typedef mln::complex_m_face_neighborhood<D, G> adj_vertices_nbh_t;
00178       adj_vertices_nbh_t adj_vertices_nbh;
00179       mln_niter_(adj_vertices_nbh_t) adj_v(adj_vertices_nbh, f);
00180       /* FIXME: We should be able to pas this value (m) either at the
00181          construction of the neighborhood or at the construction of
00182          the iterator.  Alas, we can't inherit ctors (yet), so we
00183          can't rely on a simple mixin approach.  */
00184       adj_v.iter().set_m(0);
00185 
00186       // Iterate on 2-faces (triangles).
00187       for_all(f)
00188       {
00190         std::vector<mln_psite_(normal_t)> p;
00191         p.reserve(3);
00192         for_all(adj_v)
00193           p.push_back(adj_v);
00195         mln_assertion(p.size() == 3);
00196 
00197         /* FIXME: Is the explicit conversion to_site() really needed?
00198            If not, we could provide a local shortcut like
00199 
00200              vec(p[0])   // Instead of p[0].to_site().front().to_vec()
00201 
00202            to shorten these lines.  */
00203         // FIXME: Factor as well?
00204         vec3f a =
00205           p[0].to_site().front().to_vec() - p[1].to_site().front().to_vec();
00206         vec3f b =
00207           p[1].to_site().front().to_vec() - p[2].to_site().front().to_vec();
00208         vec3f c =
00209           p[2].to_site().front().to_vec() - p[0].to_site().front().to_vec();
00210 
00211         // FIXME: Factor as well?
00212         float l2a = norm::sqr_l2(a);
00213         float l2b = norm::sqr_l2(b);
00214         float l2c = norm::sqr_l2(c);
00215         vec3f face_normal = algebra::vprod(a, b);
00216 
00217         normal(p[0]) += face_normal * (1.0f / (l2a * l2c));
00218         normal(p[1]) += face_normal * (1.0f / (l2b * l2a));
00219         normal(p[2]) += face_normal * (1.0f / (l2c * l2b));
00220       }
00221 
00222       // Normalize normals.  We don't need to iterate on all faces, just
00223       // 0-faces.
00224       mln::p_n_faces_fwd_piter<D, G> v(mesh, 0);
00225       for_all(v)
00226         normal(v).normalize();
00227 
00228       return normal;
00229     }
00230 
00231 
00232     /* FIXME: We should restrict attached data to vertices in return
00233        value.  */
00246     inline
00247     std::pair<
00248       complex_image< 2, mln::space_2complex_geometry, algebra::vec<3, float> >,
00249       complex_image< 2, mln::space_2complex_geometry, float >
00250       >
00251     mesh_corner_point_area(const p_complex<2, space_2complex_geometry>& mesh)
00252     {
00253       // Shortcuts.
00254       static const unsigned D = 2;
00255       typedef space_2complex_geometry G;
00256       typedef algebra::vec<3, float> vec3f;
00257 
00258       // Hold data for 2-faces only.
00259       typedef complex_image< D, G, vec3f > corner_area_t;
00260       // Hold data for 0-faces only.
00261       typedef complex_image< D, G, float > point_area_t;
00262       // Packed output.
00263       typedef std::pair<corner_area_t, point_area_t> output_t;
00264 
00265       // Initialize output.
00266       output_t output(mesh, mesh);
00267       corner_area_t& corner_area = output.first;
00268       point_area_t& point_area = output.second;
00269       data::fill(corner_area, literal::zero);
00270       data::fill(point_area, literal::zero);
00271 
00272       mln::p_n_faces_fwd_piter<D, G> f(mesh, 2);
00273       // A neighborhood where neighbors are the set of 0-faces
00274       // transitively adjacent to the reference point.
00275       typedef mln::complex_m_face_neighborhood<D, G> adj_vertices_nbh_t;
00276       adj_vertices_nbh_t adj_vertices_nbh;
00277       mln_niter_(adj_vertices_nbh_t) adj_v(adj_vertices_nbh, f);
00278       /* FIXME: We should be able to pas this value (m) either at the
00279          construction of the neighborhood or at the construction of
00280          the iterator.  Alas, we can't inherit ctors (yet), so we
00281          can't rely on a simple mixin approach.  */
00282       adj_v.iter().set_m(0);
00283 
00284       for_all(f)
00285       {
00287         std::vector<mln_psite_(corner_area_t)> p;
00288         p.reserve(3);
00289         for_all(adj_v)
00290           p.push_back(adj_v);
00292         mln_assertion(p.size() == 3);
00293 
00294         // (Opposite) edge vectors.
00295         algebra::vec<3, vec3f> e;
00296         // FIXME: See above.
00297         e.set
00298           (p[2].to_site().front().to_vec() - p[1].to_site().front().to_vec(),
00299            p[0].to_site().front().to_vec() - p[2].to_site().front().to_vec(),
00300            p[1].to_site().front().to_vec() - p[0].to_site().front().to_vec());
00301 
00302         // Compute corner weights.
00303         float area = 0.5f * norm::l2(algebra::vprod(e[0], e[1]));
00304         vec3f sqr_norm;
00305         sqr_norm.set(norm::sqr_l2(e[0]),
00306                      norm::sqr_l2(e[1]),
00307                      norm::sqr_l2(e[2]));
00308         vec3f edge_weight;
00309         edge_weight.set
00310           (sqr_norm[0] * (sqr_norm[1] + sqr_norm[2] - sqr_norm[0]),
00311            sqr_norm[1] * (sqr_norm[2] + sqr_norm[0] - sqr_norm[1]),
00312            sqr_norm[2] * (sqr_norm[0] + sqr_norm[1] - sqr_norm[2]));
00313 
00314         if (edge_weight[0] <= 0.0f)
00315           {
00316             corner_area(f)[1] = -0.25f * sqr_norm[2] * area / (e[0] * e[2]);
00317             corner_area(f)[2] = -0.25f * sqr_norm[1] * area / (e[0] * e[1]);
00318             corner_area(f)[0] = area - corner_area(f)[1] - corner_area(f)[2];
00319           }
00320         else if (edge_weight[1] <= 0.0f)
00321           {
00322             corner_area(f)[2] = -0.25f * sqr_norm[0] * area / (e[1] * e[0]);
00323             corner_area(f)[0] = -0.25f * sqr_norm[2] * area / (e[1] * e[2]);
00324             corner_area(f)[1] = area - corner_area(f)[2] - corner_area(f)[0];
00325           }
00326         else if (edge_weight[2] <= 0.0f)
00327           {
00328             corner_area(f)[0] = -0.25f * sqr_norm[1] * area / (e[2] * e[1]);
00329             corner_area(f)[1] = -0.25f * sqr_norm[0] * area / (e[2] * e[0]);
00330             corner_area(f)[2] = area - corner_area(f)[0] - corner_area(f)[1];
00331           }
00332         else
00333           {
00334             float ewscale =
00335               0.5f * area / (edge_weight[0] + edge_weight[1] + edge_weight[2]);
00336             for (int i = 0; i < 3; ++i)
00337               corner_area(f)[i] =
00338                 ewscale * (edge_weight[(i+1)%3] + edge_weight[(i+2)%3]);
00339           }
00340 
00341         for (int i = 0; i < 3; ++i)
00342           point_area(p[i]) += corner_area(f)[i];
00343       }
00344 
00345       return output;
00346     }
00347 
00348 
00349     namespace internal
00350     {
00351 
00367       static inline unsigned next(unsigned i) { return (i + 1) % 3; }
00368       static inline unsigned prev(unsigned i) { return (i - 1) % 3; }
00372 
00373       inline
00374       void
00375       rot_coord_sys(const algebra::vec<3, float> &old_u,
00376                     const algebra::vec<3, float> &old_v,
00377                     const algebra::vec<3, float> &new_norm,
00378                     algebra::vec<3, float> &new_u,
00379                     algebra::vec<3, float> &new_v)
00380       {
00381         new_u = old_u;
00382         new_v = old_v;
00383         algebra::vec<3, float> old_norm = vprod(old_u, old_v);
00384         float ndot = old_norm * new_norm;
00385         if (unlikely(ndot <= -1.0f))
00386           {
00387             new_u = -new_u;
00388             new_v = -new_v;
00389             return;
00390           }
00391         algebra::vec<3, float> perp_old = new_norm - ndot * old_norm;
00392         algebra::vec<3, float> dperp =
00393           1.0f / (1 + ndot) * (old_norm + new_norm);
00394         new_u -= dperp * (new_u * perp_old);
00395         new_v -= dperp * (new_v * perp_old);
00396       }
00397 
00398 
00399       // FIXME: Add const to formals whenever we can.
00400 
00401       // Reproject a curvature tensor from the basis spanned by old_u and old_v
00402       // (which are assumed to be unit-length and perpendicular) to the
00403       // new_u, new_v basis.
00404       inline
00405       void
00406       proj_curv(const algebra::vec<3, float>& old_u,
00407                 const algebra::vec<3, float>& old_v,
00408                 float old_ku, float old_kuv, float old_kv,
00409                 const algebra::vec<3, float>& new_u,
00410                 const algebra::vec<3, float>& new_v,
00411                 float& new_ku, float& new_kuv, float& new_kv)
00412       {
00413         algebra::vec<3, float> r_new_u, r_new_v;
00414         rot_coord_sys(new_u, new_v, vprod(old_u, old_v), r_new_u, r_new_v);
00415 
00416         float u1 = r_new_u * old_u;
00417         float v1 = r_new_u * old_v;
00418         float u2 = r_new_v * old_u;
00419         float v2 = r_new_v * old_v;
00420         new_ku  = old_ku * u1*u1 + old_kuv * (2.0f  * u1*v1) + old_kv * v1*v1;
00421         new_kuv = old_ku * u1*u2 + old_kuv * (u1*v2 + u2*v1) + old_kv * v1*v2;
00422         new_kv  = old_ku * u2*u2 + old_kuv * (2.0f  * u2*v2) + old_kv * v2*v2;
00423       }
00424 
00428       inline
00429       void
00430       diagonalize_curv(const algebra::vec<3, float>& old_u,
00431                        const algebra::vec<3, float>& old_v,
00432                        float ku, float kuv, float kv,
00433                        const algebra::vec<3, float>& new_norm,
00434                        algebra::vec<3, float>& pdir1,
00435                        algebra::vec<3, float>& pdir2,
00436                        float& k1, float& k2)
00437       {
00438         algebra::vec<3, float> r_old_u, r_old_v;
00439         rot_coord_sys(old_u, old_v, new_norm, r_old_u, r_old_v);
00440 
00441         float c = 1, s = 0, tt = 0;
00442         if (likely(kuv != 0.0f))
00443           {
00444             // Jacobi rotation to diagonalize.
00445             float h = 0.5f * (kv - ku) / kuv;
00446             tt = (h < 0.0f) ?
00447               1.0f / (h - sqrt(1.0f + h*h)) :
00448               1.0f / (h + sqrt(1.0f + h*h));
00449             c = 1.0f / sqrt(1.0f + tt*tt);
00450             s = tt * c;
00451           }
00452 
00453         k1 = ku - tt * kuv;
00454         k2 = kv + tt * kuv;
00455 
00456         if (fabs(k1) >= fabs(k2))
00457           pdir1 = c*r_old_u - s*r_old_v;
00458         else
00459           {
00460             std::swap(k1, k2);
00461             pdir1 = s*r_old_u + c*r_old_v;
00462           }
00463         pdir2 = vprod(new_norm, pdir1);
00464       }
00465 
00466     } // end of namespace mln::geom::internal
00467 
00468 
00480     /* FIXME: We should restrict attached data to vertices in return
00481        value.  */
00482     /* FIXME: Have a typedef for this return type?  */
00483     inline
00484     std::pair<
00485       complex_image< 2, mln::space_2complex_geometry, float >,
00486       complex_image< 2, mln::space_2complex_geometry, float >
00487       >
00488     mesh_curvature(const p_complex<2, space_2complex_geometry>& mesh)
00489     {
00490       // Shortcuts.
00491       static const unsigned D = 2;
00492       typedef space_2complex_geometry G;
00493       typedef algebra::vec<3, float> vec3f;
00494       typedef algebra::mat<3, 3, float> mat3f;
00495 
00496       typedef complex_image< D, G, vec3f > corner_area_t;
00497       typedef complex_image< D, G, float > point_area_t;
00498 
00499       // Normals at vertices.
00500       typedef complex_image< 2, mln::space_2complex_geometry, vec3f > normal_t;
00501       normal_t normal = mesh_normal(mesh);
00502 
00503       // Areas ``belonging'' to a normal.
00504       typedef complex_image< D, G, vec3f > corner_area_t;
00505       typedef complex_image< D, G, float > point_area_t;
00506       typedef std::pair<corner_area_t, point_area_t> corner_point_area_t;
00507       corner_point_area_t corner_point_area = mesh_corner_point_area(mesh);
00508       // Shortcuts.
00509       corner_area_t& corner_area = corner_point_area.first;
00510       point_area_t& point_area = corner_point_area.second;
00511 
00512       // Curvatures at each vertex.
00513       typedef complex_image< 2, mln::space_2complex_geometry, float > curv_t;
00514       typedef std::pair<curv_t, curv_t> output_t;
00515       output_t output(mesh, mesh);
00516       curv_t& curv1 = output.first;
00517       curv_t& curv2 = output.second;
00518       // ??
00519       complex_image< 2, mln::space_2complex_geometry, float > curv12(mesh);
00520       // Principal directions at each vertex.
00521       complex_image< 2, mln::space_2complex_geometry, vec3f > pdir1(mesh);
00522       complex_image< 2, mln::space_2complex_geometry, vec3f > pdir2(mesh);
00523 
00524       /*-------------------------------------------------.
00525       | Set up an initial coordinate system per vertex.  |
00526       `-------------------------------------------------*/
00527 
00528       mln::p_n_faces_fwd_piter<D, G> f(mesh, 2);
00529       // A neighborhood where neighbors are the set of 0-faces
00530       // transitively adjacent to the reference point.
00531       typedef mln::complex_m_face_neighborhood<D, G> adj_vertices_nbh_t;
00532       adj_vertices_nbh_t adj_vertices_nbh;
00533       mln_niter_(adj_vertices_nbh_t) adj_v(adj_vertices_nbh, f);
00534       /* FIXME: We should be able to pas this value (m) either at the
00535          construction of the neighborhood or at the construction of
00536          the iterator.  Alas, we can't inherit ctors (yet), so we
00537          can't rely on a simple mixin approach.  */
00538       adj_v.iter().set_m(0);
00539 
00540       for_all(f)
00541       {
00543         std::vector<mln_psite_(curv_t)> p;
00544         p.reserve(3);
00545         for_all(adj_v)
00546           p.push_back(adj_v);
00548         mln_assertion(p.size() == 3);
00549 
00550         // FIXME: See above.
00551         pdir1(p[0]) =
00552           p[1].to_site().front().to_vec() - p[0].to_site().front().to_vec();
00553         pdir1(p[1]) =
00554           p[2].to_site().front().to_vec() - p[1].to_site().front().to_vec();
00555         pdir1(p[2]) =
00556           p[0].to_site().front().to_vec() - p[2].to_site().front().to_vec();
00557       }
00558 
00559       mln::p_n_faces_fwd_piter<D, G> v(mesh, 0);
00560       for_all(v)
00561       {
00562         pdir1(v) = algebra::vprod(pdir1(v), normal(v));
00563         pdir1(v).normalize();
00564         pdir2(v) = algebra::vprod(normal(v), pdir1(v));
00565       }
00566 
00567       /*-----------------------------.
00568       | Compute curvature per-face.  |
00569       `-----------------------------*/
00570 
00571       for_all(f)
00572       {
00573         std::vector<mln_psite_(curv_t)> p;
00574         p.reserve(3);
00575         for_all(adj_v)
00576           p.push_back(adj_v);
00577         mln_assertion(p.size() == 3);
00578 
00579         // (Opposite) edge vectors.
00580         algebra::vec<3, vec3f> e;
00581         // FIXME: See above.
00582         e.set
00583           (p[2].to_site().front().to_vec() - p[1].to_site().front().to_vec(),
00584            p[0].to_site().front().to_vec() - p[2].to_site().front().to_vec(),
00585            p[1].to_site().front().to_vec() - p[0].to_site().front().to_vec());
00586 
00587         // N-T-B coordinate system per face.
00588         vec3f t = e[0];
00589         t.normalize();
00590         vec3f n = algebra::vprod(e[0], e[1]);
00591         /* FIXME: Why don't we normalize N?  Is it a normal vector by
00592            construction?  Or maybe we don't need it to be normal?  */
00593         vec3f b = algebra::vprod(n, t);
00594         b.normalize();
00595 
00596         // Estimate curvature based on variation of normals along edges.
00597         vec3f m = literal::zero;
00598         mat3f w = literal::zero;
00599         for (int j = 0; j < 3; ++j)
00600           {
00601             float u = e[j] * t;
00602             float v = e[j] * b;
00603             w(0, 0) += u * u;
00604             w(0, 1) += u * v;
00605             /* FIXME: Those two lines were commented in Trimesh's
00606                original code.
00607 
00608                  //w(1, 1) += v*v + u*u;
00609                  //w(1, 2) += u*v;
00610 
00611             */
00612             w(2, 2) += v * v; 
00613             vec3f dn =
00614               normal(p[internal::prev(j)]) - normal(p[internal::next(j)]);
00615             float dnu = dn * t;
00616             float dnv = dn * b;
00617             m[0] += dnu*u;
00618             m[1] += dnu*v + dnv*u;
00619             m[2] += dnv*v;
00620           }
00621           w(1, 1) = w(0, 0) + w(2, 2);
00622           w(1, 2) = w(0, 1);
00623 
00624           // Least squares solution.
00625           vec3f diag;
00626 #ifndef NDEBUG
00627           bool ldlt_decomp_sucess_p = algebra::ldlt_decomp(w, diag);
00628 #endif // ! NDEBUG
00629           mln_assertion(ldlt_decomp_sucess_p);
00630           algebra::ldlt_solve(w, diag, m, m);
00631 
00632           // Push it back out to the vertices
00633           for (int j = 0; j < 3; ++j)
00634             {
00635               float c1, c12, c2;
00636               internal::proj_curv(t, b, m[0], m[1], m[2],
00637                                   pdir1(p[j]), pdir2(p[j]), c1, c12, c2);
00638               float wt = corner_area(f)[j] / point_area(p[j]);
00639               curv1(p[j])  += wt * c1;
00640               curv12(p[j]) += wt * c12;
00641               curv2(p[j])  += wt * c2;
00642             }
00643       }
00644 
00645       /*-------------------------------------------------------------.
00646       | Compute principal directions and curvatures at each vertex.  |
00647       `-------------------------------------------------------------*/
00648 
00649       for_all(v)
00650         internal::diagonalize_curv(pdir1(v), pdir2(v),
00651                                    curv1(v), curv12(v), curv2(v),
00652                                    normal(v), pdir1(v), pdir2(v),
00653                                    curv1(v), curv2(v));
00654 
00655       return output;
00656     }
00657 
00658   } // end of namespace mln::geom
00659 
00660 } // end of namespace mln
00661 
00662 #endif // ! MILENA_APPS_STATUES_MISC_HH

Generated on Thu Sep 8 2011 18:32:07 for Milena (Olena) by  doxygen 1.7.1