snake.cc

Go to the documentation of this file.
00001 
00003 
00004 //
00005 // Copyright (c) 2002-2004 California Institute of Technology
00006 // Copyright (c) 2004-2007 University of Southern California
00007 // Rob Peters <rjpeters at usc dot edu>
00008 //
00009 // created: Mon May 12 11:15:20 2003
00010 // commit: $Id: snake.cc 10065 2007-04-12 05:54:56Z rjpeters $
00011 // $HeadURL: file:///lab/rjpeters/svnrepo/code/trunk/groovx/src/visx/snake.cc $
00012 //
00013 // --------------------------------------------------------------------
00014 //
00015 // This file is part of GroovX.
00016 //   [http://ilab.usc.edu/rjpeters/groovx/]
00017 //
00018 // GroovX is free software; you can redistribute it and/or modify it
00019 // under the terms of the GNU General Public License as published by
00020 // the Free Software Foundation; either version 2 of the License, or
00021 // (at your option) any later version.
00022 //
00023 // GroovX is distributed in the hope that it will be useful, but
00024 // WITHOUT ANY WARRANTY; without even the implied warranty of
00025 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00026 // General Public License for more details.
00027 //
00028 // You should have received a copy of the GNU General Public License
00029 // along with GroovX; if not, write to the Free Software Foundation,
00030 // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00031 //
00033 
00034 #ifndef GROOVX_VISX_SNAKE_CC_UTC20050626084015_DEFINED
00035 #define GROOVX_VISX_SNAKE_CC_UTC20050626084015_DEFINED
00036 
00037 #include "snake.h"
00038 
00039 #include "geom/geom.h"
00040 #include "geom/vec2.h"
00041 
00042 #include "rutz/rand.h"
00043 
00044 #include <algorithm>
00045 #include <cmath>
00046 
00047 #include "rutz/debug.h"
00048 GVX_DBG_REGISTER
00049 #include "rutz/trace.h"
00050 
00051 using geom::vec2d;
00052 
00053 namespace
00054 {
00055   const double MAX_DELTA         = M_PI/4.0; // == 45 degrees
00056   const double TEMPERATURE       = 0.05;
00057   const double INITIAL_INCREMENT = 0.1;
00058 
00059   struct Tuple4
00060   {
00061     Tuple4() {}
00062 
00063     Tuple4(double a0, double a1, double a2, double a3)
00064     { arr[0] = a0; arr[1] = a1; arr[2] = a2; arr[3] = a3; }
00065 
00066     double  operator[](int i) const { return arr[i]; }
00067     double& operator[](int i)       { return arr[i]; }
00068 
00069     double arr[4];
00070   };
00071 
00072   void pickRandom4(int length, int i[], rutz::urand& urand)
00073   {
00074     i[0] = i[1] = i[2] = i[3] = urand.idraw(length);
00075 
00076     while (i[1] == i[0])
00077     {
00078       i[1] = urand.idraw(length);
00079     }
00080 
00081     while (i[2] == i[0] || i[2] == i[1])
00082     {
00083       i[2] = urand.idraw(length);
00084     }
00085 
00086     while (i[3] == i[0] || i[3] == i[1] || i[3] == i[2])
00087     {
00088       i[3] = urand.idraw(length);
00089     }
00090 
00091     std::sort(i, i+4);
00092   }
00093 
00094   Tuple4 getEdgeLengths(const vec2d no[4])
00095   {
00096     return Tuple4(no[0].distance_to(no[1]),
00097                   no[1].distance_to(no[2]),
00098                   no[2].distance_to(no[3]),
00099                   no[3].distance_to(no[0]));
00100   }
00101 
00102   Tuple4 getThetas(const vec2d no[4])
00103   {
00104     return Tuple4(no[0].angle_to(no[1]),
00105                   no[1].angle_to(no[2]),
00106                   no[2].angle_to(no[3]),
00107                   no[3].angle_to(no[0]));
00108   }
00109 
00110   Tuple4 getAlphas(const Tuple4& theta)
00111   {
00112     return Tuple4(geom::rad_0_2pi(M_PI - theta[0] + theta[3]),
00113                   geom::rad_0_2pi(M_PI - theta[1] + theta[0]),
00114                   geom::rad_0_2pi(M_PI - theta[2] + theta[1]),
00115                   geom::rad_0_2pi(M_PI - theta[3] + theta[2]));
00116   }
00117 
00118   // Must return "true" in order to proceed with new nodes in jiggle().
00119   bool squashQuadrangle(const vec2d& old_0, const vec2d& old_1,
00120                         const vec2d& old_2, const vec2d& old_3,
00121                         vec2d* new_0, vec2d* new_1,
00122                         vec2d* new_2, vec2d* new_3,
00123                         double new_theta)
00124   {
00125   GVX_TRACE("squashQuadrangle");
00126     {
00127       // Here we adjust the position of node-1 by jiggling its angle with
00128       // respect to node-0:
00129 
00130       const double d = old_0.distance_to(old_1);
00131 
00132       new_1->x() = old_0.x() + d * cos(new_theta);
00133       new_1->y() = old_0.y() + d * sin(new_theta);
00134     }
00135 
00136     /*                                                     */
00137     /*                             . (2)                   */
00138     /*                           ./|                       */
00139     /*                  b      ./  `.                      */
00140     /*                       ./     |                      */
00141     /*                     ./       `.                     */
00142     /*                   ./          |                     */
00143     /*                 ./            `.  c                 */
00144     /*       (NEW 1) ./_________      |                    */
00145     /*                `--.  ang       `.                   */
00146     /*                    `--.         |                   */
00147     /*                        `--.     `.                  */
00148     /*                      a     `--.  |                  */
00149     /*                                `--. (3)             */
00150     /*                                                     */
00151 
00152 
00153     /*
00154 
00155     OK, we've picked a new node-1... now we want to hold node-3 fixed and
00156     try to find a node-2 that will keep the other distances constant. In
00157     trigonometric terms, we're trying to solve for the missing vertex of a
00158     triangle, given (1) the positions of the other two vertices, and (2)
00159     the lengths of all three sides.
00160 
00161     We do this in two steps:
00162 
00163       (1) assume node-1 is at (0,0), the origin
00164              and node-3 is at (a,0)
00165 
00166           then find node-2 in this unrotated coord system
00167 
00168       (2) now take the rotation angle (ang) required to put node-3 back in
00169           its proper position, and rotate node-2 by that angle as well
00170 
00171     From http://mathworld.wolfram.com/Triangle.html:
00172 
00173     A triangle with sides a, b, and c can be constructed by selecting
00174     vertices (0,0), (a,0), and (x,y), then solving:
00175 
00176         x^2   + y^2  =  b^2
00177       (x-a)^2 + y^2  =  c^2
00178 
00179     to obtain:
00180 
00181            a^2 + b^2 - c^2
00182        x = ---------------
00183                  2a
00184 
00185                     ,------------------------------
00186                    /          (a^2 + b^2 - c^2)^2
00187        y = +/- _  /  b^2  -  ---------------------
00188                 \/                   4a^2
00189 
00190                    ,-----------------------------------------------
00191                 -\/  2(a^2b^2 + b^2c^2 + a^2c^2) - a^4 - b^4 - c^4
00192          = +/- -----------------------------------------------------
00193                                        2a
00194 
00195     (note that the website has an incorrect formula for the solved y!)
00196 
00197     */
00198 
00199     const double a = old_3.distance_to(*new_1);
00200     const double b = old_1.distance_to(old_2);
00201     const double c = old_2.distance_to(old_3);
00202 
00203     const double cos_ang = (old_3.x() - new_1->x()) / a;  // adjac / hypot
00204     const double sin_ang = (old_3.y() - new_1->y()) / a;  // oppos / hypot
00205 
00206     const double a_sq = a*a;
00207     const double b_sq = b*b;
00208     const double c_sq = c*c;
00209 
00210     const double det =
00211       2*(a_sq*b_sq + b_sq*c_sq + a_sq*c_sq)
00212       - a_sq*a_sq - b_sq*b_sq - c_sq*c_sq;
00213 
00214     if (det < 0)
00215       return false;
00216 
00217     // OK, here's the unrotated coords of node-2:
00218 
00219     const double xp = (a*a + b*b - c*c) / (2.0*a);
00220     const double yp = sqrt(det) / (2.0*a);
00221 
00222     // Here's the two candidate re-rotated coords:
00223 
00224     const vec2d new_2a
00225       (new_1->x() + xp*cos_ang - yp*sin_ang,
00226        new_1->y() + xp*sin_ang + yp*cos_ang);
00227 
00228     const vec2d new_2b
00229       (new_1->x() + xp*cos_ang + yp*sin_ang,
00230        new_1->y() + xp*sin_ang - yp*cos_ang);
00231 
00232     const double d_2_2a = new_2a.distance_to(old_2);
00233     const double d_2_2b = new_2b.distance_to(old_2);
00234 
00235     *new_0 = old_0;
00236     *new_2 = (d_2_2a < d_2_2b) ? new_2a : new_2b;
00237     *new_3 = old_3;
00238 
00239     return true;
00240   }
00241 
00242   bool deltaTooBig(const Tuple4& delta)
00243   {
00244     return (fabs(delta[0]) > MAX_DELTA
00245             || fabs(delta[1]) > MAX_DELTA
00246             || fabs(delta[2]) > MAX_DELTA
00247             || fabs(delta[3]) > MAX_DELTA);
00248   }
00249 
00250   bool acceptNewDelta(const Tuple4& newdelta, const Tuple4& olddelta,
00251                       rutz::urand& urand)
00252   {
00253     const double energy_diff =
00254       newdelta[0]*newdelta[0] - olddelta[0]*olddelta[0]
00255       + newdelta[1]*newdelta[1] - olddelta[1]*olddelta[1]
00256       + newdelta[2]*newdelta[2] - olddelta[2]*olddelta[2]
00257       + newdelta[3]*newdelta[3] - olddelta[3]*olddelta[3];
00258 
00259     // Note, if energy_diff<0, then probability>1.
00260     const double probability = exp(-energy_diff/TEMPERATURE);
00261 
00262     return urand.fdraw() <= probability;
00263   }
00264 }
00265 
00266 Snake::Snake(int l, double spacing, rutz::urand& urand) :
00267   itsLength(l),
00268   itsElem(itsLength)
00269 {
00270 GVX_TRACE("Snake::Snake");
00271 
00272   const double radius = (itsLength * spacing) / (2*M_PI);
00273 
00274   const double alpha_start = 2 * M_PI * urand.fdraw();
00275 
00276   for (int n = 0; n < itsLength; ++n)
00277     {
00278       const double alpha = alpha_start + 2 * M_PI * n / itsLength;
00279 
00280       itsElem[n].set_polar_rad(radius, -alpha);
00281     }
00282 
00283   const int ITERS = 400;
00284 
00285   for (int count = 0; count < ITERS; ++count)
00286     {
00287       const bool convergeOk = this->jiggle(urand);
00288       if ( !convergeOk )
00289         {
00290           printf("warning: Snake::jiggle failed to converge\n");
00291           break;
00292         }
00293     }
00294 
00295   // Now reset so that the center of the ring is at the origin
00296   vec2d c;
00297 
00298   for (int n = 0; n < itsLength; ++n)
00299     c += itsElem[n];
00300 
00301   c /= itsLength;
00302 
00303   for (int n = 0; n < itsLength; ++n)
00304     itsElem[n] -= c;
00305 }
00306 
00307 Snake::~Snake()
00308 {
00309 GVX_TRACE("Snake::~Snake");
00310 }
00311 
00312 GaborArrayElement Snake::getElement(int n) const
00313 {
00314 GVX_TRACE("Snake::getElement");
00315 
00316   GVX_ASSERT(n < itsLength);
00317 
00318   GaborArrayElement result;
00319 
00320   result.type = GaborArrayElement::CONTOUR;
00321   result.pos.x() = 0.5 * (elem(n).x() + elem(n+1).x());
00322   result.pos.y() = 0.5 * (elem(n).y() + elem(n+1).y());
00323   result.theta = geom::rad_0_2pi(-elem(n).angle_to(elem(n+1)));
00324 
00325   return result;
00326 }
00327 
00328 /*        (pos.x[n],ypos[n])     position n                               */
00329 /*        (xpos[n+1],ypos[n+1]) position n+1                              */
00330 /*        (0.5*(xpos[n]+xpos[n+1]), 0.5*(ypos[n]+ypos[n+1]))              */
00331 /*                              location of patch n                       */
00332 /*        theta                 angle between pos n and pos n+1           */
00333 /*                              and orientation of patch n                */
00334 /*        delta                 angle difference between patch n and n+1  */
00335 /*                                                                        */
00336 /*                      theta[0]                                          */
00337 /*                             no[0]__________                            */
00338 /*                           ,-~ `-.                                      */
00339 /*                        ,-~       `-.                                   */
00340 /*                     ,-~   alpha[0]  `-.                                */
00341 /*             a[0] ,-~                   `-.  a[3]                       */
00342 /*               ,-~                         `-.                          */
00343 /*            ,-~                               `-.                       */
00344 /*         ,-~                                     `-.                    */
00345 /*      ,-~                                           `-.                 */
00346 /*no[1]~                                                 `-.   theta[3]   */
00347 /*    \   alpha[1]                                          `-.           */
00348 /*     \                                          alpha[3]    no[3]------ */
00349 /*      \                                                 _,-~            */
00350 /*       \                                            _,-~                */
00351 /*        \                                       _,-~                    */
00352 /*         \                                  _,-~                        */
00353 /*      a[1]\                             _,-~                            */
00354 /*           \                        _,-~                                */
00355 /*            \                   _,-~   a[2]                             */
00356 /*             \  alpha[2]    _,-~                                        */
00357 /*              \         _,-~                                            */
00358 /*               \    _,-~   theta[2]                                     */
00359 /*              no[2]~_________                                           */
00360 
00361 bool Snake::jiggle(rutz::urand& urand)
00362 {
00363 GVX_TRACE("Snake::jiggle");
00364 
00365   int i[4];
00366   pickRandom4(itsLength, i, urand);
00367 
00368   const vec2d old_pos[4] =
00369     {
00370       itsElem[i[0]], itsElem[i[1]], itsElem[i[2]], itsElem[i[3]]
00371     };
00372 
00373   const Tuple4 old_theta = getThetas(old_pos);
00374   const Tuple4 old_alpha = getAlphas(old_theta);
00375 
00376   const Tuple4 old_delta
00377     (geom::rad_npi_pi(elem(i[0]).angle_to(elem(i[0]+1)) - elem(i[0]-1).angle_to(elem(i[0]))),
00378      geom::rad_npi_pi(elem(i[1]).angle_to(elem(i[1]+1)) - elem(i[1]-1).angle_to(elem(i[1]))),
00379      geom::rad_npi_pi(elem(i[2]).angle_to(elem(i[2]+1)) - elem(i[2]-1).angle_to(elem(i[2]))),
00380      geom::rad_npi_pi(elem(i[3]).angle_to(elem(i[3]+1)) - elem(i[3]-1).angle_to(elem(i[3]))));
00381 
00382   vec2d new_pos[4];
00383 
00384   double increment = INITIAL_INCREMENT;
00385 
00386   const int MAX_ITERS = 10000;
00387 
00388   int k = 0;
00389 
00390   for (; k < MAX_ITERS; ++k)
00391     {
00392       const double incr = urand.booldraw() ? -increment : increment;
00393 
00394       const int r = urand.idraw(4);
00395 
00396       const double incr_theta = old_theta[r] - incr;
00397 
00398       const bool ok =
00399         squashQuadrangle(old_pos[(r+0)%4], old_pos[(r+1)%4],
00400                          old_pos[(r+2)%4], old_pos[(r+3)%4],
00401                          &new_pos[(r+0)%4], &new_pos[(r+1)%4],
00402                          &new_pos[(r+2)%4], &new_pos[(r+3)%4],
00403                          incr_theta);
00404 
00405       if (!ok)
00406         {
00407           increment *= 0.5;
00408           continue;
00409         }
00410 
00411       const Tuple4 new_theta = getThetas(new_pos);
00412       const Tuple4 new_alpha = getAlphas(new_theta);
00413       const Tuple4 new_delta
00414         (old_delta[0] + (old_alpha[0]-new_alpha[0]),
00415          old_delta[1] + (old_alpha[1]-new_alpha[1]),
00416          old_delta[2] + (old_alpha[2]-new_alpha[2]),
00417          old_delta[3] + (old_alpha[3]-new_alpha[3]));
00418 
00419       if (deltaTooBig(new_delta))
00420         {
00421           increment *= 0.8;
00422           continue;
00423         }
00424 
00425       if (acceptNewDelta(new_delta, old_delta, urand))
00426         break;
00427     }
00428 
00429   bool didConverge = (k < MAX_ITERS);
00430 
00431   for (int n = 0; n < 4; ++n)
00432     this->transformPath(i[n], new_pos[n],
00433                         i[(n+1)%4], new_pos[(n+1)%4]);
00434 
00435   for (int n = 0; n < 4; ++n)
00436     {
00437       itsElem[i[n]] = new_pos[n];
00438     }
00439 
00440   return didConverge;
00441 }
00442 
00443 void Snake::transformPath(int i1, const vec2d& new1,
00444                           int i2, const vec2d& new2)
00445 {
00446 GVX_TRACE("Snake::transformPath");
00447 
00448   const vec2d old1 = itsElem[i1];
00449   const vec2d old2 = itsElem[i2];
00450 
00451   // OK, our jiggling algorithm has determined new locations for node-1 and
00452   // node-2. Now, we want to adjust all the intermediate points
00453   // accordingly. This amounts to (1) a translation (move node-1 from its
00454   // old to new location), (2) a rotation (rotate node-2 around node-1 to
00455   // its new location), and (3) a scaling (contract or expand to put node-2
00456   // in the right spot). We can then apply these same transformations to
00457   // the intermediate points.
00458 
00459   /*                                                                   */
00460   /*               scale by      rotate by (a'-a)                      */
00461   /*               distance         degrees                            */
00462   /*                ratio              |                               */
00463   /*                  |                |                               */
00464   /*                  |                v                               */
00465   /*                  v                                                */
00466   /*    a11  a12              cos a'-a   -sin a'-a                     */
00467   /*  (          ) = d'/d * (                      )                   */
00468   /*    a21  a12              sin a'-a    cos a'-a                     */
00469   /*                                                                   */
00470 
00471   const double d = old1.distance_to(old2);
00472   const double a = old1.angle_to(old2);
00473 
00474   const double dp = new1.distance_to(new2);
00475   const double ap = new1.angle_to(new2);
00476 
00477   const double diff_a = ap - a;
00478 
00479   const double ratio_d = (dp/d);
00480 
00481   const double cos_diff_a = cos(diff_a);
00482   const double sin_diff_a = sin(diff_a);
00483 
00484   const double a11 =   ratio_d * cos_diff_a;
00485   const double a12 = - ratio_d * sin_diff_a;
00486   const double a21 =   ratio_d * sin_diff_a;
00487   const double a22 =   ratio_d * cos_diff_a;
00488 
00489   // Loop over all the nodes in between the nodes given by i1 and i2
00490   for (int n = (i1+1)%itsLength; n != i2; n = (n+1)%itsLength)
00491     {
00492       /*                                              */
00493       /*   x'      c1       a11   a12     x - b1      */
00494       /* (   ) = (    ) + (           ) (        )    */
00495       /*   y'      c2       a21   a12     y - b2      */
00496       /*                                              */
00497 
00498       const double diffx = itsElem[n].x() - old1.x();
00499       const double diffy = itsElem[n].y() - old1.y();
00500 
00501       itsElem[n].x() = new1.x() + a11*diffx + a12*diffy;
00502       itsElem[n].y() = new1.y() + a21*diffx + a22*diffy;
00503     }
00504 }
00505 
00506 static const char __attribute__((used)) vcid_groovx_visx_snake_cc_utc20050626084015[] = "$Id: snake.cc 10065 2007-04-12 05:54:56Z rjpeters $ $HeadURL: file:
00507 #endif // !GROOVX_VISX_SNAKE_CC_UTC20050626084015_DEFINED

The software described here is Copyright (c) 1998-2005, Rob Peters.
This page was generated Wed Dec 3 06:49:42 2008 by Doxygen version 1.5.5.