PreviewLayoutLinesTool.cpp

Go to the documentation of this file.
00001 // -*- c-basic-offset: 4 -*-
00022 #include "panoinc_WX.h"
00023 #include "panoinc.h"
00024 
00025 #include "PreviewLayoutLinesTool.h"
00026 #include <config.h>
00027 
00028 #include "base_wx/platform.h"
00029 #include "MainFrame.h"
00030 #include "GreatCircles.h"
00031 
00032 #include <wx/platform.h>
00033 #ifdef __WXMAC__
00034 #include <OpenGL/gl.h>
00035 #include <OpenGL/glu.h>
00036 #else
00037 #include <GL/gl.h>
00038 #include <GL/glu.h>
00039 #endif
00040 
00041 #include <cfloat>
00042 #include <cmath>
00043 
00044 // the size of the rectangular texture. Must be a power of two, and at least 8.
00045 #define rect_ts 64
00046 
00047 // The resolution to use to sample overlaps.
00048 /* The time it takes to sample it is a multiple of this squared + a constant.
00049  * So keep it low to make the redraws faster, set it higher to make the grey
00050  * lines' appearence more accurate. Make sure to adjust MIN_SAMPLE_OVERLAPS
00051  * below to keep the proportion that need to overlap the same.
00052  */
00053 #define SAMPLE_FREQUENCY 12
00054 
00055 /* The amount of samples that are required to be within the other image for it
00056  * to count as a large enough overlap to warrent a grey line. It is affected by
00057  * the SAMPLE_FREQUENCY. An image with SAMPLE_FREQUENCY squared overlaps is 
00058  * entirely within the other image. Must be > 0.
00059  * 
00060  * 12*12 = 144 samples. 16 / 144 = 1/9, so a 9th of the image must overlap for
00061  * the grey line to appear. If just the corners overlap, the overlap must be a
00062  * third in both directions:
00063  * |||
00064  * |||
00065  * ||$SS
00066  *   SSS
00067  *   SSS
00068  */
00069 #define MIN_SAMPLE_OVERLAPS 16
00070 
00072 class PosMap
00073 {
00074 public:
00075     hugin_utils::FDiff2D data[SAMPLE_FREQUENCY][SAMPLE_FREQUENCY];
00076     hugin_utils::FDiff2D * operator[](std::size_t index)
00077     {
00078         return data[index];
00079     }
00080 };
00081 
00082 PreviewLayoutLinesTool::PreviewLayoutLinesTool(ToolHelper *helper)
00083     : Tool(helper),
00084       m_updateStatistics(true),
00085       m_nearestLine(-1),
00086       m_useNearestLine(false),
00087       m_holdOnNear(false)
00088 {
00089     helper->GetPanoramaPtr()->addObserver(this);
00090     // make the textures. We have a circle border and a square one.
00091     // the textures are white with a the alpha chanel forming a border.
00092     glGenTextures(1, (GLuint*) &m_rectangleBorderTex);
00093     // we only want to specify alpha, but using just alpha in opengl attaches 0
00094     // for the luminosity. I tried biasing the red green and blue values to get
00095     // them to 1.0, but it didn't work under OS X for some reason. Instead we
00096     // use a luminance alpha pair, and use 1.0 for luminance all the time.
00097 
00098     // In the rectangle texture, the middle is 1/8 opaque, the outer pixels
00099     // are completely transparent, and one pixel in from the edges is
00100     // a completly opaque line.
00101     unsigned char rect_tex_data[rect_ts][rect_ts][2];
00102     // make everything white
00103     for (unsigned int x = 0; x < rect_ts; x++)
00104     {
00105         for (unsigned int y = 0; y < rect_ts; y++)
00106         {
00107             rect_tex_data[x][y][0] = 255;
00108         }
00109     }
00110     // now set the middle of the mask semi transparent
00111     for (unsigned int x = 2; x < rect_ts - 2; x++)
00112     {
00113         for (unsigned int y = 2; y < rect_ts - 2; y++)
00114         {
00115             rect_tex_data[x][y][1] = 31;
00116         }
00117     }
00118     // make an opaque border
00119     for (unsigned int d = 1; d < rect_ts - 1; d++)
00120     {
00121         rect_tex_data[d][1][1] = 255;
00122         rect_tex_data[d][rect_ts - 2][1] = 255;
00123         rect_tex_data[1][d][1] = 255;
00124         rect_tex_data[rect_ts - 2][d][1] = 255;
00125     }
00126     // make a transparent border around that
00127     for (unsigned int d = 0; d < rect_ts; d++)
00128     {
00129         rect_tex_data[d][0][1] = 0;
00130         rect_tex_data[d][rect_ts - 1][1] = 0;
00131         rect_tex_data[0][d][1] = 0;
00132         rect_tex_data[rect_ts - 1][d][1] = 0;
00133     }
00134     glBindTexture(GL_TEXTURE_2D, m_rectangleBorderTex);
00135     gluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, rect_ts, rect_ts,
00136                       GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, rect_tex_data);
00137     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
00138                     GL_LINEAR_MIPMAP_LINEAR);
00139     // clamp texture so it won't wrap over the border of the cropped region.
00140     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
00141     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
00142 }
00143 PreviewLayoutLinesTool::~PreviewLayoutLinesTool()
00144 {
00145     // free the textures
00146     glDeleteTextures(1, (GLuint*) &m_rectangleBorderTex);
00147     
00148     // delete all the transforms.
00149     while (!m_transforms.empty())
00150     {
00151         delete m_transforms.back();
00152         m_transforms.pop_back();
00153     }
00154     
00155     // stop observing the panorama
00156     helper->GetPanoramaPtr()->removeObserver(this);
00157 }
00158 
00159 void PreviewLayoutLinesTool::panoramaChanged(HuginBase::PanoramaData &pano)
00160 {
00161     m_updateStatistics = true;
00162 }
00163 void PreviewLayoutLinesTool::panoramaImagesChanged(HuginBase::PanoramaData&,
00164                                                    const HuginBase::UIntSet&)
00165 {
00166     m_updateStatistics = true;
00167 }
00168 
00169 void PreviewLayoutLinesTool::Activate()
00170 {
00171     // register notifications
00172     helper->NotifyMe(PreviewToolHelper::MOUSE_MOVE, this);
00173     helper->NotifyMe(PreviewToolHelper::DRAW_UNDER_IMAGES, this);
00174     helper->NotifyMe(PreviewToolHelper::DRAW_OVER_IMAGES, this);
00175     helper->NotifyMe(PreviewToolHelper::MOUSE_PRESS, this);
00176     
00177     helper->SetStatusMessage(_("Click a connection to edit control points."));
00178 }
00179 
00180 void PreviewLayoutLinesTool::MouseMoveEvent(double x, double y, wxMouseEvent & e)
00181 {
00182 
00183     // Try to find the nearest line to the mouse pointer.
00184     // ...Unless there are no lines.
00185     if (m_lines.empty())
00186     {
00187         return;
00188     }
00189 
00190     if (!(helper->IsMouseOverPano())) {
00191         if (m_useNearestLine) {
00192             m_useNearestLine = false;
00193             helper->GetVisualizationStatePtr()->ForceRequireRedraw();
00194             helper->GetVisualizationStatePtr()->Redraw();
00195         }
00196         return;
00197     }
00198 
00199     if (e.Dragging() && !m_holdOnNear) {
00200         return;
00201     }
00202     
00203     // Check each line in turn.
00204     double minDistance = DBL_MAX;
00205     unsigned int nearestLineOld = m_nearestLine;
00206     for (unsigned int i = 0; i < m_lines.size(); i++)
00207     {
00208         if (m_lines[i].dud) continue;
00209         double lineDistance = m_lines[i].getDistance(helper->GetMousePanoPosition());
00210         if (lineDistance < minDistance)
00211         {
00212             // found a new minimum.
00213             minDistance = lineDistance;
00214             m_nearestLine = i;
00215         }
00216     }
00217     
00218     // Work out if it is close enough to highlight it.
00219     bool oldUseNearestLine = m_useNearestLine;
00220     // The limit is 70 pixels from the line.
00221     // Coordinates are panorama pixels squared, so we'll need to scale it.
00222     double scale = helper->GetVisualizationStatePtr()->GetScale();
00223     scale *= scale;
00224     m_useNearestLine = minDistance < 4900.0 / scale;
00225     
00226     if (oldUseNearestLine != m_useNearestLine || m_nearestLine != nearestLineOld)
00227     {
00228         LineDetails & line = m_lines[m_nearestLine];
00229         // get notification of when the connected images are drawn so we can
00230         // draw them on top with a highlight.
00231         helper->NotifyMeBeforeDrawing(line.image1, this);
00232         helper->NotifyMeBeforeDrawing(line.image2, this);
00233         
00234         // Redraw with new indicators. Since the indicators aren't part of the
00235         // panorama, we have to persuade the viewstate that a redraw is required.
00236         helper->GetVisualizationStatePtr()->ForceRequireRedraw();
00237         helper->GetVisualizationStatePtr()->Redraw();
00238     }
00239 }
00240 
00241 void PreviewLayoutLinesTool::BeforeDrawImagesEvent()
00242 {
00243     // We should check if the lines or image centers have changed.
00244     if (m_updateStatistics)
00245     {
00246         m_updateStatistics = false;
00247         // Must be done in this order since the transforms updateImageCentres
00248         // uses are also used by updateLineInformation.
00249         updateImageCentres();
00250         updateLineInformation();
00251         
00252         if (m_nearestLine >= m_lines.size())
00253         {
00254             // The line we had selected no longer exists. Use the first line
00255             // until the mouse moves again.
00256             m_nearestLine = 0;
00257         }
00258     }
00259     
00260     // now draw each line.
00261     glEnable(GL_LINE_SMOOTH);
00262     helper->GetViewStatePtr()->GetTextureManager()->DisableTexture();//    for (unsigned int i = 0; i < m_lines.size(); i++)
00263     for (unsigned int i = 0; i < m_lines.size(); i++)
00264     {
00265         m_lines[i].draw(m_useNearestLine && i == m_nearestLine);
00266     }
00267     glDisable(GL_LINE_SMOOTH);
00268     glEnable(GL_TEXTURE_2D);
00269     // reset some openGL state back to its normal value.
00270     glColor3ub(255, 255, 255);
00271     glLineWidth(1.0);
00272 }
00273 
00274 void PreviewLayoutLinesTool::AfterDrawImagesEvent()
00275 {
00276     // we draw the partly transparent identification boxes over the top of the
00277     // two images connecting the nearest line to the mouse pointer.
00278     
00279     // If there are no lines, there isn't a nearest one. Also check if we use it
00280     if (m_lines.empty() || !m_useNearestLine) return;
00281     
00282     // draw the actual images
00283     unsigned int image1 = m_lines[m_nearestLine].image1;
00284     unsigned int image2 = m_lines[m_nearestLine].image2;
00285     helper->GetViewStatePtr()->GetTextureManager()->DrawImage(image1,
00286                         helper->GetVisualizationStatePtr()->GetMeshDisplayList(image1));
00287     helper->GetViewStatePtr()->GetTextureManager()->DrawImage(image2,
00288                         helper->GetVisualizationStatePtr()->GetMeshDisplayList(image2));
00289                         
00290     // Setup OpenGL blending state for identification borders.
00291     glEnable(GL_BLEND);
00292     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
00293     // use the border texture.
00294     helper->GetViewStatePtr()->GetTextureManager()->DisableTexture(true);
00295     glBindTexture(GL_TEXTURE_2D, m_rectangleBorderTex);
00296     // We use the texture matrix to align the texture with the cropping region.
00297     glMatrixMode(GL_TEXTURE);
00298     
00299     // now draw the identification boxes
00300     drawIdentificationBorder(image1);
00301     drawIdentificationBorder(image2);
00302     
00303     // set OpenGL state back how we found it.
00304     glMatrixMode(GL_MODELVIEW);
00305     glDisable(GL_BLEND);
00306 }
00307 
00308 void PreviewLayoutLinesTool::drawIdentificationBorder(unsigned int image)
00309 {
00310     // we want to shift the texture so it lines up with the cropped region.
00311     glPushMatrix();
00312     const HuginBase::SrcPanoImage & src = *(helper->GetViewStatePtr()->
00313                                                             GetSrcImage(image));
00314     int width = src.getSize().width(), height = src.getSize().height();
00315     vigra::Rect2D crop_region = src.getCropRect();
00316     // pick a texture depending on crop mode and move it to the cropped area
00317     switch (src.getCropMode())
00318     {
00319         case HuginBase::SrcPanoImage::CROP_CIRCLE:
00320         case HuginBase::SrcPanoImage::CROP_RECTANGLE:
00321             // get the biggest rectangle contained by both the image  and the
00322             // cropped area.
00323             crop_region &= vigra::Rect2D(src.getSize());
00324             glScalef((float) width / (float) crop_region.width(),
00325                      (float) height / (float) crop_region.height(),
00326                      1.0);
00327             glTranslatef(-(float) crop_region.left() / (float) width,
00328                          -(float) crop_region.top() / (float) height,
00329                          0.0);
00330             break;
00331         case HuginBase::SrcPanoImage::NO_CROP:
00332             break;
00333     }
00334     // draw the image with the border texture.
00335     glMatrixMode(GL_MODELVIEW);
00336     glCallList(helper->GetVisualizationStatePtr()->GetMeshDisplayList(image));
00337     glMatrixMode(GL_TEXTURE);
00338     // reset the texture matrix.
00339     glPopMatrix();
00340 }
00341 
00342 bool PreviewLayoutLinesTool::BeforeDrawImageEvent(unsigned int image)
00343 {
00344     if (m_lines.empty() || !m_useNearestLine)
00345     {
00346         // there are no lines, so none should be highlighted, therefore every
00347         // image must be drawn. Don't notify us any longer and draw the image.
00348         helper->DoNotNotifyMeBeforeDrawing(image, this);
00349         return true;
00350     }
00351     // Delay drawing of images, so we can show them on top of the others.
00352     LineDetails & line = m_lines[m_nearestLine];
00353     if (image == line.image1 || image == line.image2) return false;
00354     // We must be done with this event, so don't notify us any longer:
00355     helper->DoNotNotifyMeBeforeDrawing(image, this);
00356     return true;
00357 }
00358 
00359 void PreviewLayoutLinesTool::MouseButtonEvent(wxMouseEvent & e)
00360 {
00361     // If it was a left click and we have at least one line, bring up the images
00362     // in that line in the control point editor.
00363     if ( e.LeftDown() && !m_lines.empty() && m_useNearestLine)
00364     {
00365         m_holdOnNear = true;
00366     } 
00367 
00368     if (m_holdOnNear && e.LeftUp() && m_useNearestLine) {
00369         m_holdOnNear = false;
00370         LineDetails & line = m_lines[m_nearestLine];
00371         MainFrame::Get()->ShowCtrlPointEditor(line.image1, line.image2);
00372         MainFrame::Get()->Raise();
00373     }
00374 
00375     if (m_useNearestLine && e.LeftUp()) {
00376         m_useNearestLine = false;
00377     }
00378 }
00379 
00380 void PreviewLayoutLinesTool::updateLineInformation()
00381 {
00382     m_lines.clear();
00383     const PT::Panorama & pano = *(helper->GetPanoramaPtr());
00384     unsigned int numberOfImages = pano.getNrOfImages();
00385     HuginBase::UIntSet active_images = pano.getActiveImages();
00386     // make a line for every image pair, but set the unneeded ones as dud.
00387     // This is for constant look up times when we scan control points.
00388     m_lines.resize(numberOfImages * numberOfImages);
00389     unsigned int numberOfControlPoints = pano.getNrOfCtrlPoints();
00390     // loop over all control points to count them and get error statistics.
00391     for (unsigned int cpi = 0 ; cpi < numberOfControlPoints ; cpi++)
00392     {
00393         const ControlPoint & cp = pano.getCtrlPoint(cpi);
00394         unsigned int low_index, high_index;
00395         if (cp.image1Nr < cp.image2Nr)
00396         {
00397             low_index = cp.image1Nr;
00398             high_index = cp.image2Nr;
00399         } else {
00400             low_index = cp.image2Nr;
00401             high_index = cp.image1Nr;
00402         }
00403         // find the line.
00404         // We use the formula in the line below to record image numbers to each
00405         // line later.
00406         LineDetails & line = m_lines[low_index * numberOfImages + high_index];
00407         // update control point count.
00408         line.numberOfControlPoints++;
00409         // update error statistics
00410         line.totalError += cp.error;
00411         if (cp.error > line.worstError)
00412         {
00413             line.worstError = cp.error;
00414         }
00415     }
00416     
00417     /* Find some locations of the images. We will test if they overlap using
00418      *  these locations. We don't need the last image as we can always take the
00419      * smallest numbered image as the source of points.
00420      */
00427     std::vector<PosMap>  positions(pano.getNrOfImages() - 1);
00428     for (unsigned int i = 0; i < numberOfImages - 1; i++)
00429     {
00430         const HuginBase::SrcPanoImage & img = pano.getImage(i);
00431         for (unsigned int x = 0; x < SAMPLE_FREQUENCY; x++)
00432         {
00433             for (unsigned int y = 0; y < SAMPLE_FREQUENCY; y++)
00434             {
00435                 // scale (x, y) so it is always within the cropped region of the
00436                 // image.
00437                 vigra::Rect2D c = img.getCropRect();
00441                 double xc = double (x) / double (SAMPLE_FREQUENCY)
00442                                         * double(c.width()) + c.left();
00443                 double yc = double (y) / double (SAMPLE_FREQUENCY)
00444                                         * double(c.height()) + c.top();
00445                 // now look up (xc, yc) in the image, find where in the panorama
00446                 // it ends up.
00447                 m_transforms[i]->transformImgCoord  (
00448                             positions[i][x][y].x, positions[i][x][y].y,
00449                             xc, yc                  );
00450             }
00451         }
00452     }
00453     
00454     // write other line data.
00455     for (unsigned int i = 0; i < numberOfImages; i++)
00456     {
00457         for (unsigned int j = 0; j < numberOfImages; j++)
00458         {
00459             LineDetails & line = m_lines[i * numberOfImages + j];
00460             line.image1 = i;
00461             line.image2 = j;
00463             if (!(set_contains(active_images, i) &&
00464                   set_contains(active_images, j)))
00465             {
00466                 // At least one of the images is hidden, so don't show the line.
00467                 line.dud = true;
00468             }
00469             else if (line.numberOfControlPoints > 0)
00470             {
00471                 line.dud = false;
00472             } else if (i >= j) {
00473                 // We only use lines where image1 is the lowest numbered image.
00474                 // We don't bother with lines from one image to the same one.
00475                 line.dud = true;
00476             } else {
00477                 // test overlapping regions.
00478                 HuginBase::PTools::Transform transform;
00479                 ViewState & viewState = *helper->GetViewStatePtr();
00480                 HuginBase::SrcPanoImage & src = *viewState.GetSrcImage(j);
00481                 transform.createTransform(src, *(viewState.GetOptions()));
00482                 unsigned int overlapingSamples = 0;
00483                 for (unsigned int x = 0; x < SAMPLE_FREQUENCY; x++)
00484                 {
00485                     for (unsigned int y = 0; y < SAMPLE_FREQUENCY; y++)
00486                     {
00487                         // check if mapping a point that was found earilier to
00488                         // be inside an image in panorama space is inside the
00489                         // other image when transformed from panorama to image.
00490                         double dx, dy;
00491                         transform.transformImgCoord (
00492                                         dx, dy,
00493                                         positions[i][x][y].x,
00494                                         positions[i][x][y].y
00495                                                     );
00496                         if (src.isInside(vigra::Point2D((int) dx, (int) dy)))
00497                         {
00498                             // they overlap
00499                             overlapingSamples++;
00500                         }
00501                     }
00502                 }
00503                 // If the overlap isn't big enough, the line isn't used.
00504                 line.dud = (overlapingSamples < MIN_SAMPLE_OVERLAPS);
00505             }
00506             
00507             if (!line.dud)
00508             {
00509                 line.arc = GreatCircleArc(m_imageCentresSpherical[i].x,
00510                                           m_imageCentresSpherical[i].y,
00511                                           m_imageCentresSpherical[j].x,
00512                                           m_imageCentresSpherical[j].y,
00513                                           *(helper->GetVisualizationStatePtr()));
00514             }
00515         }
00516     }
00517 }
00518 
00519 void PreviewLayoutLinesTool::updateImageCentres()
00520 {
00521     unsigned int numberOfImages = helper->GetPanoramaPtr()->getNrOfImages();
00522     // The transforms have no copy constructor, so we can't have a direct vector
00523     // and use resize. Instead we have a vector of pointers and we create and
00524     // delete transforms as they are needed.
00525     while (m_transforms.size() > numberOfImages)
00526     {
00527         delete m_transforms.back();
00528         m_transforms.pop_back();
00529     }
00530     m_transforms.reserve(numberOfImages);
00531     while (m_transforms.size() < numberOfImages)
00532     {
00533         m_transforms.insert(m_transforms.end(), new HuginBase::PTools::Transform);
00534     }
00535     m_imageCentres.resize(helper->GetPanoramaPtr()->getNrOfImages());
00536     m_imageCentresSpherical.resize(m_imageCentres.size());
00537     
00538     HuginBase::PanoramaOptions spherical_pano_opts;
00539     spherical_pano_opts.setProjection(HuginBase::PanoramaOptions::EQUIRECTANGULAR);
00540     spherical_pano_opts.setWidth(360);
00541     spherical_pano_opts.setHeight(180);
00542     spherical_pano_opts.setHFOV(360);
00543     
00544     for (unsigned int image_number = 0;
00545          image_number < helper->GetPanoramaPtr()->getNrOfImages();
00546          image_number++)
00547     {
00548         // transforming image coordinates to panorama coordinates.
00549         m_transforms[image_number]->createInvTransform  (
00550             *(helper->GetViewStatePtr()->GetSrcImage(image_number)),
00551             *(helper->GetViewStatePtr()->GetOptions())
00552                                                         );
00553         HuginBase::PTools::Transform to_spherical;
00554         to_spherical.createInvTransform (
00555             *(helper->GetViewStatePtr()->GetSrcImage(image_number)),
00556             spherical_pano_opts
00557                                          );
00558         const vigra::Size2D & s = helper->GetViewStatePtr()->GetSrcImage(image_number)->getSize();
00559         // find where the middle of the image maps to.
00560         m_transforms[image_number]->transformImgCoord   (
00561                                     m_imageCentres[image_number].x,
00562                                     m_imageCentres[image_number].y,
00563                                     double(s.x) / 2.0, double(s.y) / 2.0
00564                                                         );
00565         to_spherical.transformImgCoord(
00566             m_imageCentresSpherical[image_number].x,
00567             m_imageCentresSpherical[image_number].y,
00568             double(s.x) / 2.0, double(s.y) / 2.0
00569                                       );
00570     }
00571 }
00572 
00573 void PreviewLayoutLinesTool::LineDetails::draw(bool highlight)
00574 {
00575     if (dud)
00576     {
00577         // The line isn't real, don't draw it.
00578         return;
00579     }
00580     // work out the colour
00581     if (highlight)
00582     {
00583         glColor3ub(255, 255, 255);
00584     } else if (numberOfControlPoints == 0) {
00585         // no control points, use a grey line
00586         glColor3ub(170, 170, 170);
00587     }else {
00588         double red, green, blue;
00589         hugin_utils::ControlPointErrorColour(worstError,red,green,blue);
00590         glColor3d(red, green, blue);
00591     }
00592     
00593     double lineWidth = numberOfControlPoints / 5.0 + 1.0;
00594     if (lineWidth > 5.0) lineWidth = 5.0;
00595 //    glLineWidth(lineWidth);
00596     
00597     arc.draw(false, lineWidth);
00598 }
00599 
00600 PreviewLayoutLinesTool::LineDetails::LineDetails()
00601     :   numberOfControlPoints(0),
00602         worstError(0),
00603         totalError(0)
00604 {
00605     
00606 }
00607 
00608 float PreviewLayoutLinesTool::LineDetails::getDistance(hugin_utils::FDiff2D point)
00609 {
00610     if (dud)
00611     {
00612         // This isn't a real line, so return the maximum distance to it possible.
00613         return FLT_MAX;
00614     }
00615     else
00616     {
00617         return  arc.squareDistance(point);
00618     }
00619 }
00620 

Generated on Mon Sep 1 01:25:37 2014 for Hugintrunk by  doxygen 1.3.9.1