Main Page | Modules | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

gesture.C

Go to the documentation of this file.
00001 #include "disp/colors.H"
00002 #include "geom/gl_view.H"
00003 #include "geom/world.H"         // for DEBUG_ELLIPSE
00004 #include "gest_int.H"
00005 #include "mlib/point2.H"        // XXX - Probably doesn't need to be included.
00006 #include "std/config.H"
00007 #include "std/run_avg.H"
00008 
00009 using namespace mlib;
00010 
00011 const double MIN_GESTURE_LENGTH=15;
00012 const double MIN_GESTURE_SPREAD=10;
00013 
00014 void
00015 GESTURE::init(CPIXEL& p, CEvent& down, double pressure)
00016 {
00017    _down = down;
00018    _start_frame = VIEW::stamp();
00019    _bbox.set(Wpt(XYpt(p)),Wpt(XYpt(p)));
00020    _pix_bbox.set(p, p);
00021    add(p, 1, pressure);
00022 }
00023 
00024 void   
00025 GESTURE::add(CPIXEL& p, double min_dist, double pressure) 
00026 {
00027    if (_pts.empty() || _pts.last().dist(p) >= min_dist) {
00028       _pts       += p;
00029       _pressures += pressure;
00030       _times     += stop_watch::sys_time();
00031 
00032       _bbox.update(Wpt(XYpt(p)));
00033       _pix_bbox.update(p);
00034 
00035       // XXX - should have incremental version of this:
00036       _pts.update_length();        
00037    }
00038    _end_frame = VIEW::stamp();
00039 }
00040 
00041 void
00042 GESTURE::smooth_points()
00043 {
00044    PIXEL_list s = _pts;
00045    for (int i=1; i<s.num()-1; i++) {
00046       // use 1-6-1 mask:
00047       s[i] = (_pts[i-1] + _pts[i]*6.0 + _pts[i+1])/8.0;
00048    }
00049    _pts = s;
00050 }
00051 
00052 void
00053 GESTURE::smooth_points(int n)
00054 {
00055    for (int i=0; i<n; i++)
00056       smooth_points();
00057 }
00058 
00059 void
00060 GESTURE::reflect_points(const PIXELline& l)
00061 {
00062    PIXEL_list s = _pts;
00063    for (int i=0; i<s.num(); i++) {
00064    s[i] = l.reflection(_pts[i]);
00065    }
00066    _pts = s;
00067 }
00068 
00069 void  
00070 GESTURE::complete(CPIXEL& p, CEvent& up) 
00071 {
00072    if (_pts.num() < 2)
00073       add(p,0,_pressures.last());
00074    _up = up;
00075 
00076    // get rid of jagged tips:
00077    trim();
00078 
00079    static int n = Config::get_var_int("GEST_SMOOTH_PASSES",1);
00080    smooth_points(n);
00081 
00082    _complete = true;
00083 }
00084 
00085 
00086 // XXX - move to ARRAY?
00087 template <class A>
00088 inline void
00089 clip_tip(A& array, int num_to_clip)
00090 {
00091    // remove first 'num_to_clip' elements from the array
00092    A tmp = array;
00093    tmp.reverse();                               // turn it around
00094    tmp.truncate(array.num() - num_to_clip);     // chop it off
00095    tmp.reverse();                               // turn it back
00096    array = tmp;
00097 }
00098 
00099 void  
00100 GESTURE::trim()
00101 {
00102    // Eliminate jagged ends at start and end of gesture.
00103 
00104    // However, skip short or tightly curled gestures
00105    if (spread() < 15)
00106       return;
00107 
00108    // Maximum length of gesture to trim at each end:
00109    static double trim_dist = Config::get_var_dbl("TRIM_DIST", 10,true);
00110 
00111    // Angle considered sharp (default 30 degrees):
00112    static double trim_angle = Config::get_var_dbl("TRIM_ANGLE", deg2rad(30),true);
00113 
00114    // Don't chop ends of short strokes
00115    if (length() <= trim_dist*2.5)
00116       return;
00117 
00118    int n = _pts.num(), i;
00119    int trim_i = 0;     // "Trim index" - index of new start of pixel trail
00120 
00121    static bool debug = Config::get_var_bool("GEST_DEBUG_TRIM",false);
00122 
00123    // Do the start of the stroke:
00124    for (i=1; (i < n-1) && (_pts[i].dist(_pts[0]) < trim_dist); i++) {
00125       if (angle(i) > trim_angle)
00126          trim_i = i;
00127    }
00128    if (trim_i > 0) {
00129       err_adv(debug, "GESTURE::trim: clipping at %d", trim_i);
00130       clip_tip(_pts,       trim_i);
00131       _pts.update_length();
00132       clip_tip(_times,     trim_i);
00133       clip_tip(_pressures, trim_i);
00134    }
00135       
00136    // Do the end of the stroke:
00137    n = _pts.num();
00138    trim_i = n-1;        // Index of last element after trimming
00139    for (i=n-2; (i > 0) && (_pts[i].dist(_pts.last()) < trim_dist); i--) {
00140       if (angle(i) > trim_angle)
00141          trim_i = i;
00142    }
00143    if (trim_i < n-1) {
00144       // The parameter to ARRAY::truncate() tells how many
00145       // elements the array should have after truncation. In this
00146       // case we want trim_i to be the index of the last element,
00147       // which means there should be trim_i + 1 elements in the
00148       // truncated array.
00149       _pts.      truncate(trim_i + 1);
00150       _pts.update_length();
00151       _times.    truncate(trim_i + 1);
00152       _pressures.truncate(trim_i + 1);
00153       err_adv(debug, "GESTURE::trim: trimming at %d", trim_i);
00154    }
00155 }
00156 
00157 GESTUREptr
00158 GESTURE::prev(int k) const
00159 {
00160    // k=0 returns this
00161    // k=1 returns the gesture immediately before this
00162    //     etc.
00163    return _gest_int->gesture(_index-k);
00164 }
00165 
00166 double
00167 GESTURE::speed() const
00168 {
00169    // length divided by elapsed time
00170 
00171    double et = elapsed_time();
00172    return (et > 1e-12) ? length()/et : 0;
00173 }
00174 
00175 double 
00176 GESTURE::startup_time(double dist_thresh) const
00177 {
00178    // Time spent within the given threshold distance of the 1st point.
00179    // Used to measure a gesture that begins w/ a pause.
00180 
00181    assert(dist_thresh >= 0);
00182 
00183    if (!is_stroke()) {
00184       err_msg("GESTURE::startup_time: Error: gesture is not complete");
00185       return 0;
00186    }
00187 
00188    if (dist_thresh > length())
00189       return elapsed_time();
00190 
00191    // Find last index i within the distance threshold:
00192    int i = -1;
00193    _pts.interpolate(dist_thresh/length(), 0, &i);
00194    if (!_times.valid_index(i)) {
00195       err_msg("GESTURE::startup_time: Error: PIXEL_list::interpolate failed");
00196       return 0;
00197    } else if (_times.valid_index(i+1)) {
00198       // Advance the index to the first slot outside the startup region:
00199       i++;
00200    }
00201 
00202    return elapsed_time(i);
00203 }
00204 
00205 double
00206 GESTURE::radius() const
00207 {
00208    // returns average distance to the "center" point
00209 
00210    if (_pts.empty())
00211       return 0;
00212 
00213    PIXEL c = center();
00214 
00215    double ret=0;
00216    for (int k=0; k<_pts.num(); k++)
00217       ret += _pts[k].dist(c);
00218 
00219    return (ret / _pts.num());
00220 }
00221 
00222 inline double
00223 angle(CPIXEL& a, CPIXEL& b, CPIXEL& c)
00224 {
00225    //                                        
00226    //                            c           
00227    //                           /            
00228    //                          /             
00229    //                         /              
00230    //                        /               
00231    //                       /                
00232    //                      /  angle           
00233    //   a - - - - - - - - b . . . . . .
00234    //
00235    //    Treating a, b, and c as ordered points in a
00236    //    polyline, calculate the angle of the change in
00237    //    direction, as shown.  In the diagram, the result is
00238    //    positive, since the forward direction swivels
00239    //    around b in the CCW direction. For clockwise
00240    //    rotations, the computed angle is negative.
00241    // 
00242 
00243    return (b - a).signed_angle(c - b);
00244 }
00245 
00246 double
00247 GESTURE::winding(bool do_trim, bool do_abs) const
00248 {
00249    static bool debug = Config::get_var_bool("GEST_DEBUG_WINDING",false);
00250 
00251    if (_pts.length() < 1.0)
00252       return 0;
00253 
00254    // beginning and ending indices of
00255    // pixel list vertices:
00256    int k1=0, k2=_pts.num()-1;
00257 
00258    err_adv(debug, "GESTURE::winding");
00259 
00260    if (do_trim) {
00261 
00262       // how many pixels to ignore at beginning 
00263       // and end of stroke, for purposes of computing
00264       // the winding number?
00265       double trim = min(.1 * _pts.length(), 12.0);
00266 
00267       // ignore the first and last "trim" pixels
00268       // because strokes often have jagged starts and
00269       // stops that throw off the value for the
00270       // winding number.
00271 
00272       // convert "trim" from pixel measure
00273       // to parameter along pixel list
00274       double t = clamp(trim/_pts.length(), 0.0, 1.0);
00275 
00276       err_adv(debug, "   t: %f, t*len: %f", t, t*_pts.length());
00277 
00278       double v=0;
00279       _pts.interpolate_length(t,   k1, v);
00280       _pts.interpolate_length(1-t, k2, v);
00281 
00282    }
00283 
00284    err_adv(debug, "   %d points, k1: %d, k2: %d", _pts.num(), k1, k2);
00285 
00286    // add up the angles
00287    double ret = 0;
00288    for (int k = max(k1,1); k < k2; k++) {
00289       double a = ::angle(_pts[k-1], _pts[k], _pts[k+1]);
00290       ret += (do_abs ? fabs(a) : a);
00291    }
00292    return ret/(M_PI*2);
00293 }
00294 
00295 bool
00296 GESTURE::is_corner(int i) const
00297 {
00298    // endpoints are considered "corners".
00299    // also internal points where exterior angle is large.
00300 
00301    // endpoints
00302    if (i == 0 || i ==_pts.num()-1)
00303       return true;
00304 
00305    // invalid points
00306    if (!_pts.valid_index(i))
00307       return false;
00308 
00309    // internal points
00310    static const double MIN_ANGLE = Config::get_var_int("JOT_CORNER_ANGLE",45);
00311    return rad2deg(fabs(angle(i))) > MIN_ANGLE;
00312 }
00313 
00314 ARRAY<int>
00315 GESTURE::corners() const
00316 {
00317    ARRAY<int> ret;
00318    for (int i=0;i < _pts.num(); i++)
00319       if (is_corner(i))
00320          ret += i;
00321    return ret;
00322 }
00323 
00324 bool 
00325 GESTURE::is_stroke() const
00326 {
00327    return (_down._d && _up._d && _pts.num() > 1);
00328 }
00329 
00330 bool 
00331 GESTURE::is_closed() const
00332 {
00333    // How close should endpoints be to consider it closed?
00334    // Say 7 percent of the total length?
00335    double thresh = max(10.0, length()*0.07);
00336    return (
00337       is_stroke()               &&
00338       (length() > 50)           &&
00339       (endpoint_dist() < thresh)
00340       );
00341 }
00342 
00343 bool 
00344 GESTURE::is_tap() const
00345 {
00346    return (
00347       is_stroke()               &&
00348       (elapsed_time() < .20)    &&
00349       (length() < 10)
00350       );
00351 }
00352 
00353 bool 
00354 GESTURE::is_press_hold() const
00355 {
00356    return (
00357       is_stroke()               &&
00358       (elapsed_time() > 0.5)    &&
00359       (length() < 10)
00360       );
00361 }
00362 
00363 bool 
00364 GESTURE::is_double_tap() const
00365 {
00366    GESTUREptr p = prev();
00367 
00368    return (is_tap() && p && p->is_tap() &&
00369            elapsed_time(p) < 1 && dist(p) < 10);
00370 }
00371 
00372 bool   
00373 GESTURE::is_dslash() const 
00374 {
00375    // Delayed slash: starts w/ a pause, then looks like a slash.
00376 
00377    return (
00378       is_stroke()               &&
00379       (length() > 10)           &&
00380       (straightness() > .9)     &&
00381       (length() < 100)          &&
00382       (startup_time()   > 0.25) &&
00383       (remaining_time() < 0.1)
00384       );
00385 }
00386 
00387 bool   
00388 GESTURE::is_tslash() const 
00389 {
00390     // tap-slash (tap, then slash)
00391 
00392    GESTUREptr p = prev();
00393 
00394    return (is_slash() && p && p->is_tap() &&
00395            elapsed_time(p) < 1 && dist(p) < 10);
00396 }
00397 
00398 bool   
00399 GESTURE::is_slash_tap() const 
00400 {
00401     // slash-tap
00402 
00403    GESTUREptr p = prev();
00404 
00405    return (is_tap() && p && p->is_slash() && elapsed_time(p) < 1);
00406 }
00407 
00408 bool   
00409 GESTURE::is_zip_zap() const 
00410 {
00411     // forward/back slash
00412 
00413    if (!is_stroke()             ||
00414        (length() <  20)         ||
00415        (length() > 200)         ||
00416        (straightness() > 0.5))
00417       return false;
00418 
00419    // should have 1 corner
00420    ARRAY<int> corn = corners();
00421    if (corn.num() != 3)
00422       return false;
00423 
00424    // stroke should be straight to corner and back:
00425    PIXEL c = _pts[corn[1]]; // interior corner location
00426    return (start().dist(c) + end().dist(c) > 0.95 * length());
00427 }
00428 
00429 bool   
00430 GESTURE::is_arc() const 
00431 {
00432     // slightly curving stroke
00433 
00434    double aw = fabs(winding());
00435    double wa = winding_abs();
00436    return (
00437       is_stroke()                       &&
00438       (length() >  12)                  &&
00439       (straightness() > 0.8)            &&
00440       (aw > 0.08)                       &&
00441       (aw < 0.35)                       &&
00442       (fabs(aw - wa) < 0.06)
00443       );
00444 }
00445 
00446 bool   
00447 GESTURE::is_small_arc() const 
00448 {
00449     // small arc (eg under 60 pixels long)
00450 
00451    return (is_arc() && length() < 60);
00452 }
00453 
00454 bool 
00455 GESTURE::is_dot() const
00456 {
00457    if (!is_stroke() || length() < 40 || straightness() > .2)
00458       return 0;
00459 
00460    PIXEL c = center();
00461 
00462    for (int k=0; k<_pts.num(); k++)
00463       if (_pts[k].dist(c) > 25)
00464          return 0;
00465 
00466    //return (winding() > 2.3);
00467    // XXX -- what's a reasonable value??
00468    return ( fabs(winding()) > 1.75);
00469 }
00470 
00471 bool 
00472 GESTURE::is_scribble() const
00473 {
00474    return (
00475       is_stroke()               &&
00476       (length() > 85)           &&
00477       (endpoint_dist() > 17)    &&
00478       (speed() > 250)           &&
00479       (straightness() < .45)    &&
00480       (winding(1,1) > 2.0)
00481       );
00482 }
00483 
00484 bool 
00485 GESTURE::is_loop() const
00486 {
00487    return (
00488       is_stroke()                       && // got a down and up
00489       (length() > 50)                   && // not too short
00490       (straightness() < .15)            && // ends are close relative to length
00491       (fabs(1 - fabs(winding())) < .3)  && // winding number is about 1
00492       (winding(1,1) < 1.5)                 // mostly convex
00493       );
00494 }
00495 
00496 bool 
00497 GESTURE::is_lasso() const
00498 {
00499    return (
00500       is_stroke()                       &&
00501       (speed() > 300)                   &&
00502       (length() > 50)                   &&
00503       (straightness() < .2)             &&
00504       (fabs(1 - fabs(winding())) < .5)  &&
00505       (winding(1,1) < 1.5)
00506       );
00507 }
00508 
00509 bool 
00510 GESTURE::is_circle(
00511    double max_ratio
00512    ) const
00513 {
00514    static bool debug = Config::get_var_bool("DEBUG_CIRCLE",false);
00515 
00516    PIXEL center;        // return values
00517    VEXEL axis;          //   for
00518    double r1=0, r2=0;   //     GESTURE::is_ellipse()
00519    if (!is_ellipse(center, axis, r1, r2)) {
00520       err_adv(debug, "GESTURE::is_circle: GESTURE is not an ellipse");
00521       return false;
00522    }
00523 
00524    // Check ratio of radii
00525    // (but avoid division by zero):
00526    const double MIN_R2 = 1e-8;
00527    bool success = (r2 >= MIN_R2 && r1/r2 < max_ratio);
00528    err_adv(debug && !success,
00529            "GESTURE::is_circle: too distorted (r1/r2 = %f > %f)",
00530            (r2 < MIN_R2) ? 0.0 : r1/r2, max_ratio);
00531    return success;
00532 }
00533 
00534 bool 
00535 GESTURE::is_small_circle(
00536    double max_ratio
00537    ) const
00538 {
00539    static bool debug = Config::get_var_bool("DEBUG_SMALL_CIRCLE",false);
00540 
00541    err_adv(debug, "GESTURE::is_small_circle:");
00542 
00543    const double SMALL_CIRCLE_SPREAD=10;
00544 
00545    double w  = winding();       // winding number: sum of angle_i
00546    double aw = fabs(w);         // |w|
00547    double diffw1 = fabs(1 - aw);// abs difference between w and 1
00548    double wa = winding(1,1);    // "winding abs", sum of |angle_i|
00549 
00550    err_adv(debug, "   spread: %f", spread());
00551    err_adv(debug, "   length: %f", length());
00552    err_adv(debug, "   straightness: %f", straightness());
00553    err_adv(debug, "   |1 - |w||: %f", diffw1);
00554    err_adv(debug, "   w abs: %f", wa);
00555 
00556    bool ret = (
00557       is_stroke()                       && // got a down and up
00558       spread() < SMALL_CIRCLE_SPREAD    && // Needs to be small
00559       (length() > 15)                   && // not too short
00560       (length() < 45)                   && // not too long 
00561       (straightness() < .20)            && // ends are close relative to length
00562       (diffw1 < .4)                     && // winding number is about 1
00563       (wa < 1.75)                          // mostly convex
00564       );
00565    if (ret) err_adv(debug, "  passed");
00566    else     err_adv(debug, "  failed");
00567    return ret;
00568 }
00569 
00570 bool  
00571 GESTURE::is_click_hold() const
00572 {
00573    return (
00574       _down._d                  &&
00575       !_up._d                   &&
00576       (length() < 10)           &&
00577       (elapsed_time() > 0.8)
00578       );
00579 }
00580 
00581 bool   
00582 GESTURE::is_line(double min_straightness, double min_len) const 
00583 {
00584    return (
00585       is_stroke()                         &&
00586       (straightness() > min_straightness) &&
00587       (length()       > min_len)
00588       );
00589 }
00590 
00591 bool   
00592 GESTURE::is_line() const 
00593 {
00594    static const double min_straightness =
00595       Config::get_var_dbl("JOT_LINE_MIN_STRAIGHTNESS",0.95);
00596    static const double min_len =
00597       Config::get_var_dbl("JOT_LINE_MIN_LEN",20);
00598    return is_line(min_straightness, min_len);
00599 }
00600 
00601 bool   
00602 GESTURE::is_slash() const 
00603 {
00604    return (
00605       is_stroke()               &&
00606       (length() > 8)           &&  //was 10
00607       (straightness() > .8)     &&  //was .9
00608       (length() < 80)           &&
00609       (speed()  > 120)
00610       );
00611 }
00612 
00613 bool 
00614 GESTURE::is_n_line() const 
00615 {
00616    if (!is_line())
00617       return 0;
00618 
00619    // check that angle w/ vector (0,1)
00620    // is less than 23 degrees
00621 
00622    VEXEL u = endpt_vec().normalized();
00623    return (u[1] > 0.92);
00624 }
00625 
00626 bool   
00627 GESTURE::is_e_line() const 
00628 {
00629    if (!is_line())
00630       return 0;
00631 
00632    // check that angle w/ vector (1,0)
00633    // is less than 23 degrees
00634 
00635    VEXEL u = endpt_vec().normalized();
00636    return (u[0] > 0.92);
00637 }
00638 
00639 bool   
00640 GESTURE::is_s_line() const 
00641 {
00642    if (!is_line())
00643       return 0;
00644 
00645    // check that angle w/ vector (0,-1)
00646    // is less than 23 degrees
00647 
00648    VEXEL u = endpt_vec().normalized();
00649    return (u[1] < -0.92);
00650 }
00651 
00652 bool   
00653 GESTURE::is_w_line() const 
00654 {
00655    if (!is_line())
00656       return 0;
00657 
00658    // check that angle w/ vector (-1,0)
00659    // is less than 30 degrees
00660 
00661    VEXEL u = endpt_vec().normalized();
00662    return (u[1] < -0.92);
00663 }
00664 
00665 bool   
00666 GESTURE::is_ne_line() const 
00667 {
00668    if (!is_line())
00669       return 0;
00670 
00671    // check that angle w/ vector (1,1)
00672    // is less than 30 degrees
00673 
00674    VEXEL u = endpt_vec().normalized();
00675    return ((u * VEXEL(1,1))/M_SQRT2 > 0.92);
00676 }
00677 
00678 bool   
00679 GESTURE::is_se_line() const 
00680 {
00681    if (!is_line())
00682       return 0;
00683 
00684    // check that angle w/ vector (1,-1)
00685    // is less than 30 degrees
00686 
00687    VEXEL u = endpt_vec().normalized();
00688    return ((u * VEXEL(1,-1))/M_SQRT2 > 0.92);
00689 }
00690 
00691 bool   
00692 GESTURE::is_sw_line() const 
00693 {
00694    if (!is_line())
00695       return 0;
00696 
00697    // check that angle w/ vector (-1,-1)
00698    // is less than 30 degrees
00699 
00700    VEXEL u = endpt_vec().normalized();
00701    return ((u * VEXEL(-1,-1))/M_SQRT2 > 0.92);
00702 }
00703 
00704 bool   
00705 GESTURE::is_nw_line() const 
00706 {
00707    if (!is_line())
00708       return 0;
00709 
00710    // check that angle w/ vector (-1,1)
00711    // is less than 30 degrees
00712 
00713    VEXEL u = endpt_vec().normalized();
00714    return ((u * VEXEL(-1,1))/M_SQRT2 > 0.92);
00715 }
00716 
00717 bool   
00718 GESTURE::is_x() const 
00719 {
00720    GESTUREptr p = prev();
00721 
00722    const double STRAIGHT_THRESH = 0.92;
00723    const double MAX_LENGTH = 100.0;
00724    const double MIN_LENGTH =  10.0;
00725 
00726    // ways to rule it out:
00727    if (!is_line(STRAIGHT_THRESH,MIN_LENGTH) ||
00728        length() > MAX_LENGTH ||
00729        length() < MIN_LENGTH ||
00730        !p ||
00731        !p->is_line(STRAIGHT_THRESH,MIN_LENGTH) ||
00732        p->length() > MAX_LENGTH ||
00733        p->length() < MIN_LENGTH ||
00734        (end_time() - p->start_time() > 2.0) ||
00735        center().dist(p->center())/max(length(),p->length()) > 0.5)
00736       return 0;
00737       
00738    // it comes down to the angle between the 2 lines
00739    // (should be greater than 60 degrees)
00740    double dot = fabs(endpt_vec().normalized() *
00741                      p->endpt_vec().normalized());
00742    return (dot < cos(M_PI/3));
00743 }
00744 
00745 inline double
00746 linear_interp(double x1, double y1, double x2, double y2, double x)
00747 {
00748    return ((x - x1)*((y2 - y1)/(x2 - x1))) + y1;
00749 }
00750 
00751 inline double
00752 ellipse_max_err(const GESTURE* gest)
00753 {
00754    // Return the allowable "error" for use when deciding
00755    // whether a GESTURE has the shape of an ellipse.
00756    //
00757    // Let the allowable error be proportional to stroke length.
00758    // A scaling factor of 0.008 seems to work okay.
00759    double err_factor = Config::get_var_dbl("ELLIPSE_ERR_FACTOR", 0.01,true);
00760    double max_err = err_factor * gest->length();
00761 
00762    // But tolerate more error for fast gestures:
00763    //   If speed <= LO_SPEED, multiply max_err by LO_FACTOR.
00764    //   If speed >= HI_SPEED, multiply max_err by HI_FACTOR.
00765    //   In between, linearly interpolate.
00766    double LO_SPEED = 200,
00767       LO_FACTOR = Config::get_var_dbl("ELLIPSE_LO_FACTOR", 0.9,true);
00768    double HI_SPEED = 500,
00769       HI_FACTOR = Config::get_var_dbl("ELLIPSE_HI_FACTOR", 1.4,true);
00770    double s = gest->speed();
00771    double r = linear_interp(LO_SPEED, LO_FACTOR, HI_SPEED, HI_FACTOR, s);
00772    max_err *= clamp(r, LO_FACTOR, HI_FACTOR);
00773 
00774    return max_err;
00775 }
00776 
00777 /*****************************************************************
00778  * ELLIPSE
00779  *****************************************************************/
00780 class ELLIPSE {
00781  protected:
00782    PIXEL        _center;        // center of ellipse
00783    VEXEL        _x;             // x direction
00784    VEXEL        _y;             // y direction = x.perp()
00785    double       _r1;            // "radius" in x
00786    double       _r2;            // "radius" in y
00787    PIXEL_list   _pts;           // points computed from above, for rendering
00788    
00789  public:
00790 
00791    //******** MANAGERS ********
00792 
00793    ELLIPSE(CPIXEL& c, CVEXEL& x, double r1, double r2, int res=32) {
00794       set(c,x,r1,r2,res);
00795    }
00796 
00797    virtual ~ELLIPSE() {}
00798 
00799    //******** BUILDING ********
00800 
00801    virtual void set(CPIXEL& c, CVEXEL& x, double r1, double r2, int res) {
00802       _center = c;
00803       _x = x.normalized();
00804       _y = _x.perpend();
00805       _r1 = fabs(r1);
00806       _r2 = fabs(r2);
00807       rebuild(res);
00808    }
00809 
00810    // Rebuild points with given resolution
00811    void rebuild(int res);
00812 
00813    // avg distance of each point in the given list to the ellipse
00814    double avg_dist(CPIXEL_list& pts) {
00815       RunningAvg<double> ret(0);
00816       for (int i=0; i<pts.num(); i++)
00817          ret.add(_pts.dist(pts[i]));
00818       return ret.val();
00819    }
00820 };
00821 
00822 void
00823 ELLIPSE::rebuild(int res) 
00824 {
00825    // Rebuild points with given resolution
00826 
00827    // Need good data:
00828    assert(_r1 > gEpsAbsMath && _r2 > gEpsAbsMath &&
00829           !_x.is_null() && !_y.is_null() && res > 4);
00830 
00831    // Prepare to rebuild pts:
00832    _pts.clear();
00833    _pts.realloc(res);
00834 
00835    // add points, including last point == first point
00836    for (int i=0; i<res; i++) {
00837       double t = (2*M_PI)*i/res;
00838       _pts += _center + _x*(_r1*cos(t)) + _y*(_r2*sin(t));
00839    }
00840 }
00841 
00842 /*****************************************************************
00843  * DEBUG_ELLIPSE
00844  *****************************************************************/
00845 class DEBUG_ELLIPSE : public GEL, public ELLIPSE {
00846  protected:
00847    egg_timer    _timer;         // used for timing out (to be undrawn)
00848    
00849  public:
00850 
00851    //******** MANAGERS ********
00852 
00853    DEBUG_ELLIPSE(CPIXEL& c, CVEXEL& x, double r1, double r2, int res=32) :
00854       ELLIPSE(c,x,r1,r2,res),
00855       _timer(10) {}
00856 
00857    //******** BUILDING ********
00858 
00859    virtual void set(CPIXEL& c, CVEXEL& x, double r1, double r2, int res) {
00860       ELLIPSE::set(c,x,r1,r2,res);
00861       _timer.reset(10); // show for 10 seconds, then fade away
00862    }
00863 
00864    //******** RUN-TIME TYPE ID ********
00865 
00866    DEFINE_RTTI_METHODS3("DEBUG_ELLIPSE", DEBUG_ELLIPSE*, GEL, CDATA_ITEM *);
00867 
00868    // Shortly before the timer expires, start fading away.
00869    // This computes the alpha to use for fading.
00870    double alpha() const {
00871       const double FADE_TIME = 0.5; // fade for half a second
00872       return min(_timer.remaining() / FADE_TIME, 1.0);
00873    }
00874 
00875    //******** GEL VIRTUAL METHODS ********
00876 
00877    virtual bool needs_blend()    const { return alpha() < 1; }
00878    virtual int  draw(CVIEWptr &);
00879 
00880    //******** DATA_ITEM VIRTUAL METHODS ********
00881 
00882    virtual DATA_ITEM* dup() const { return 0; }
00883 };
00884 
00885 int
00886 DEBUG_ELLIPSE::draw(CVIEWptr& v) 
00887 {
00888    // load identity for model matrix
00889    glMatrixMode(GL_MODELVIEW);
00890    glPushMatrix();
00891    glLoadIdentity();
00892       
00893    // set up to draw in PIXEL coords:
00894    glMatrixMode(GL_PROJECTION);
00895    glPushMatrix();
00896    glLoadMatrixd(v->pix_proj().transpose().matrix());
00897 
00898    // Set up line drawing attributes
00899    glPushAttrib(GL_ENABLE_BIT | GL_LINE_BIT | GL_CURRENT_BIT);
00900    glDisable(GL_LIGHTING);              // GL_ENABLE_BIT
00901 
00902    // Draw ellipse in blue, 2 pixels wide:
00903 
00904    // Turn on antialiasing for width-2 lines:
00905    GL_VIEW::init_line_smooth(GLfloat(v->line_scale()*2));
00906    GL_COL(COLOR::blue, alpha());
00907    glBegin(GL_LINE_STRIP);
00908    for (int k=0; k<_pts.num(); k++)
00909       glVertex2dv(_pts[k].data());
00910    glEnd();
00911    GL_VIEW::end_line_smooth();
00912 
00913    // Draw axes in dark grey, 1-pixel wide
00914    GL_VIEW::init_line_smooth(GLfloat(v->line_scale()*1));
00915    glColor4d(.8,.8,.8,alpha());
00916    glBegin(GL_LINES);
00917    glVertex2dv((_center - _x*_r1).data());
00918    glVertex2dv((_center + _x*_r1).data());
00919    glVertex2dv((_center - _y*_r2).data());
00920    glVertex2dv((_center + _y*_r2).data());
00921    glEnd();
00922    GL_VIEW::end_line_smooth();
00923 
00924    // Draw center as 3-pixel wide orange dot
00925    GL_VIEW::init_point_smooth(float(v->line_scale()*3));
00926    GL_COL(Color::orange, alpha());
00927    glBegin(GL_POINTS);
00928    glVertex2dv(_center.data());
00929    glEnd();
00930    GL_VIEW::end_point_smooth();
00931 
00932    glPopAttrib();
00933    glPopMatrix();
00934    glMatrixMode(GL_MODELVIEW);
00935    glPopMatrix();
00936    
00937    if (alpha() == 0)
00938       WORLD::undisplay(this, false);
00939 
00940    return 0;
00941 }
00942 
00943 inline PIXEL_list
00944 get_section(PIXEL_list& pts, double s0, double s1)
00945 {
00946    // Pull out the section of the given PIXEL_list corresponding
00947    // to the interval in parameter space from s0 to s1.
00948 
00949    // XXX - belongs in mlib/points.H
00950 
00951    pts.update_length();
00952 
00953    PIXEL_list ret;
00954    if (!(in_interval(s0, 0.0, 1.0) && in_interval(s1, 0.0, 1.0) && s0 < s1)) {
00955       err_msg("get_section: bad parameters: %f, %f", s0, s1);
00956       return ret;
00957    }
00958    if (pts.empty()) {
00959       err_msg("get_section: empty list");
00960       return ret;
00961    }
00962    VEXEL v0, v1; int k0, k1; double t0, t1;
00963    PIXEL p0 = pts.interpolate(s0, &v0, &k0, &t0);
00964    PIXEL p1 = pts.interpolate(s1, &v1, &k1, &t1);
00965    ret += p0;
00966    for (int k=k0+1; k<=k1; k++)
00967       ret += pts[k];
00968    if (t1 > 0)
00969       ret += p1;
00970    return ret;
00971 }
00972 
00973 inline PIXEL_list
00974 trim_endpt_overlap(CPIXEL_list& pts)
00975 {
00976    // HACK: for a PIXEL_list that basically forms a closed loop, trim
00977    // away the last bit of it if that bit overlaps with the start.
00978    // Used in the ellipse computation, below, since otherwise these
00979    // overlapping closed loops aren't recognized well, due to the way
00980    // GESTURE::is_ellipse() calculates the "center": by averaging all
00981    // the points. When there is an overlap, it skews the "center"
00982    // toward the side with the overlap.
00983 
00984    // Return value is the original list,
00985    // with some points removed from the end:
00986    PIXEL_list ret = pts;
00987 
00988    // Fraction of curve considered to be near either "end"
00989    double END_ZONE = 0.15; 
00990 
00991    // Extract the first 15% of the polyline as a separate polyline:
00992    PIXEL_list start = get_section(ret, 0, END_ZONE);
00993 
00994    // Find the index of the point near the start of the last 15%:
00995    int last_k;
00996    ret.interpolate(1 - END_ZONE, 0, &last_k, 0);
00997 
00998    // Pick off the last few points that are sufficiently close to the
00999    // start of the polyline.
01000    PIXEL foo; int fum;
01001    for (int k=pts.num()-1; k>last_k; k--) {
01002       if (start.closest(ret[k], foo, fum) < 10.0)
01003          ret.pop();
01004       else
01005          break;
01006    }
01007    return ret;
01008 }
01009 
01010 bool
01011 GESTURE::is_ellipse(
01012    PIXEL& center,       // returned: center of the ellipse
01013    VEXEL& axis,         // returned: principle axis (unit length)
01014    double& r1,          // returned: magnitude of main radius
01015    double& r2,          // returned: magnitude of smaller radius
01016    double err_mult      // multiplier for max_err
01017    ) const
01018 {
01019    ////////////////////////////////////////////////////////////
01020    // Find the "best-fit" ellipse for the 2D pixel trail of
01021    // the GESTURE. Return true if the ellipse satisfies the
01022    // given error tolerance, false otherwise.
01023    //
01024    // The error is computed by averaging the "distance" from
01025    // each point to the ellipse, measured along the line
01026    // thru its center.
01027    //
01028    // 5-step plan:
01029    //
01030    // (1) Compute the "center" of the ellipse, for now using
01031    //     a possibly erroneous method (see comment below).
01032    //
01033    // (2) Express the 2D points in the coordinate system with
01034    //     that center at its origin.
01035    //
01036    // (3) We seek 3 values a, b, and c such that:
01037    //
01038    //        ax^2 + 2bxy + cy^2 = 1
01039    //
01040    //     for each 2D point (x,y). For n points, this amounts to
01041    //     n equations with 3 unknowns, which can be expressed as:
01042    //
01043    //       (nx3)(3x1)  (nx1)
01044    //         X    A   =  J
01045    //
01046    //     where A[0] = a, A[1] = 2b, A[2] = c, J is the (nx1)
01047    //     matrix of 1's, and row i of the matrix X consists of:
01048    //
01049    //        x^2  xy  y^2
01050    //
01051    //     computed from the ith 2D point (x,y).
01052    //
01053    //     Let Xt denote the transpose of X. We compute a
01054    //     least-squares solution for A:
01055    //
01056    //      (3x1)   (3x3)  (3xn)(nx1)   
01057    //        A  = (Xt X)^-1 Xt   J
01058    //
01059    //  (4) Diagonalize the matrix:
01060    //       
01061    //         a  b
01062    //         b  c
01063    //
01064    //      to find the axes and radii of the ellipse.
01065    //
01066    //  (5) Check the goodness of the fit and reject anything
01067    //      that's not sufficiently ellipse-like.
01068    // 
01069    ////////////////////////////////////////////////////////////
01070 
01071    // XXX - Print debug messages if needed.
01072    // 
01073    // In case this method gets called more than once on the same
01074    // GESTURE, just print messages the first time.
01075    static const GESTURE* last_gesture = 0;
01076    static bool do_debug = Config::get_var_bool("DEBUG_ELLIPSE",false);
01077    bool debug = do_debug && last_gesture != this;
01078    last_gesture = this;
01079 
01080    // Step 0.
01081    //
01082    // Trivial reject.
01083    int n = _pts.num(), k;
01084    if (n < 8) {
01085       err_adv(debug, "GESTURE::is_ellipse: too few points", n);
01086       return false;
01087    }
01088 
01089    if (!is_loop()) {
01090       err_adv(debug, "GESTURE::is_ellipse: it's not a loop");
01091       return false;
01092    }
01093 
01094    // Step 1.
01095    //
01096    // XXX - In computing the "center," a small error can
01097    // have a large impact. The following requires regular
01098    // spacing of the input points. A more robust approach
01099    // would be to compute the "center of mass" of the
01100    // polyline, treating it as a thin wire with uniform
01101    // density. Or probably better, include the computation
01102    // of the "center" in the least-squares computation.
01103    //
01104    // XXX - because overlaps really skew the results, we're
01105    //       using this hack to trim away overlaps:
01106    center = trim_endpt_overlap(_pts).average();      
01107 
01108    // Step 2.
01109    //
01110    // Change of coordinates -- put origin at center
01111    ARRAY<VEXEL> P(n); // transformed points
01112    for (k=0; k<n; k++)
01113       P[k] += (_pts[k] - center);
01114 
01115    // Step 3.
01116    //
01117    // Set up (3 x n) matrix Xt, where column i contains
01118    // values x^2, xy, y^2 from point i:
01119    //
01120    // XXX - Could use a general matrix class about now
01121    double* Xt[3];
01122    Xt[0] = new double [ n ];
01123    Xt[1] = new double [ n ];
01124    Xt[2] = new double [ n ];
01125 
01126    for (k=0; k<n; k++) {
01127       Xt[0][k] = P[k][0] * P[k][0];     // x^2
01128       Xt[1][k] = P[k][0] * P[k][1];     // xy
01129       Xt[2][k] = P[k][1] * P[k][1];     // y^2
01130    }
01131    
01132    Wvec XtJ;      // Xt times (n x 1) vector of 1's
01133    Wtransf XtX;   // Xt times X, a (3x3) matrix
01134    XtX(0,0) = XtX(1,1) = XtX(2,2) = 0;  // start with all zeros
01135    for (k=0; k<n; k++) {
01136       // fill in entries of XtX and XtJ
01137       for (int i=0; i<3; i++) {
01138          XtJ[i] += Xt[i][k];       // XtJ[i] = row sum i
01139          for (int j=0; j<3; j++) {
01140             XtX(i,j) += Xt[i][k] * Xt[j][k];
01141          }
01142       }
01143    }
01144 
01145    // Thanks for the memories
01146    delete [] Xt[0];
01147    delete [] Xt[1];
01148    delete [] Xt[2];
01149 
01150    // Least-squares solve:
01151    Wvec A = XtX.inverse() * XtJ;
01152 
01153    // Coordinates of symmetric matrix describing the
01154    // quadratic form associated with the ellipse:
01155    //
01156    //    a  b
01157    //    b  c
01158    //
01159    double a = A[0], b = A[1]/2, c = A[2];
01160 
01161    // Step 4.
01162    //
01163    // Find eigenvalues of the matrix:
01164    double det = sqrt(sqr(a-c) + 4*sqr(b));
01165    double lambda_1 = (a + c + det)/2;
01166    double lambda_2 = (a + c - det)/2;
01167 
01168    if (lambda_1 <= 0 || lambda_2 <= 0) {
01169       err_adv(debug, "GESTURE::is_ellipse: non-positive eigenvalues in matrix");
01170       return false;
01171    }
01172 
01173    // Make sure lambda_1 has greatest magnitude
01174    if (lambda_2 > lambda_1) {
01175       // I think this never happens
01176       err_adv(debug, "GESTURE::is_ellipse: warning: a + c < 0");
01177       swap(lambda_1, lambda_2);
01178    }
01179 
01180    // Both must be non-negligible:
01181    if (lambda_2 < 1e-6) {
01182       err_adv(debug, "GESTURE::is_ellipse: vanishing eigenvalue (%f)", lambda_2);
01183       return false;
01184    }
01185 
01186    // Find the long axis of the ellipse, which is aligned
01187    // with the 2nd eigenvector (i.e. for lambda_2). The
01188    // following two rows must both be perpendicular to the
01189    // 1st eigenvector, and so both are parallel to the 2nd
01190    // eigenvector.
01191    VEXEL row1(a - lambda_1,      b      );
01192    VEXEL row2(     b,       c - lambda_1);
01193 
01194    // For robustness, take the longest one:
01195    axis = (row1.length() > row2.length()) ? row1 : row2;
01196 
01197    // Normalize it:
01198    axis = axis.normalized();
01199    if (axis.is_null()) {
01200       err_adv(debug, "GESTURE::is_ellipse: can't find principle axis");
01201       return false;
01202    }
01203 
01204    // Compute the radii of the ellipse
01205    r1 = 1 / sqrt(lambda_2);
01206    r2 = 1 / sqrt(lambda_1);
01207 
01208    // Step 5.
01209    //
01210    // Compute average distance of each gesture point to
01211    // the ellipse. If it is under the max allowable error,
01212    // the gesture is an ellipse.
01213 
01214    double max_err = err_mult * ellipse_max_err(this);
01215    double     err = ELLIPSE(center, axis, r1, r2).avg_dist(_pts);
01216    err_adv(debug, "GESTURE::is_ellipse: err %f, max: %f", err, max_err);
01217    bool success = (err <= max_err);
01218    if (debug)
01219       WORLD::create(new DEBUG_ELLIPSE(center, axis, r1, r2), false);
01220 
01221    return success;
01222 }
01223 
01224 void   
01225 GESTURE::print_stats() const
01226 {
01227    // Show various measurements of the gesture
01228 
01229    cerr << endl;
01230 //    err_msg("startup time:   %1.2f", startup_time());
01231 //    err_msg("remaining time: %1.2f", remaining_time());
01232    err_msg("speed:          %1.1f", speed());
01233    err_msg("winding:        %1.4f", winding());
01234    err_msg("winding_abs:    %1.4f", winding_abs());
01235 //    err_msg("endpoint_dist:  %1.1f", endpoint_dist());
01236    err_msg("length:         %1.1f", length());
01237    err_msg("straightness:   %1.3f", straightness());
01238 //    err_msg("Corners :       %d",    num_corners());
01239    cerr << endl;
01240 }
01241 
01242 void
01243 GESTURE::print_types() const
01244 {
01245    // get the error messages out of the way first:
01246    is_circle();
01247 
01248    cerr << "gesture is: ";
01249    if (is_stroke())     cerr << "stroke, ";
01250    if (is_tap())        cerr << "tap, ";
01251    if (is_double_tap()) cerr << "double tap, ";
01252    if (is_line())       cerr << "line, ";
01253    if (is_slash())      cerr << "slash, ";
01254    if (is_dslash())     cerr << "dslash, ";
01255    if (is_tslash())     cerr << "tslash, ";
01256    if (is_zip_zap())    cerr << "zip-zap, ";
01257    if (is_small_arc())  cerr << "small arc, ";
01258    if (is_dot())        cerr << "dot, ";
01259    if (is_scribble())   cerr << "scribble, ";
01260    if (is_closed())     cerr << "closed, ";
01261    if (is_loop())       cerr << "loop, ";
01262    if (is_lasso())      cerr << "lasso, ";
01263    if (is_circle())     cerr << "circle, ";
01264    if (is_small_circle())cerr << "small circle, ";
01265    if (is_ellipse())    cerr << "ellipse, ";
01266    if (is_n_line())     cerr << "n_line, ";
01267    if (is_e_line())     cerr << "e_line, ";
01268    if (is_s_line())     cerr << "s_line, ";
01269    if (is_w_line())     cerr << "w_line, ";
01270    if (is_ne_line())    cerr << "ne_line, ";
01271    if (is_se_line())    cerr << "se_line, ";
01272    if (is_sw_line())    cerr << "sw_line, ";
01273    if (is_nw_line())    cerr << "nw_line, ";
01274    if (is_x())          cerr << "x, ";
01275    if (is_click_hold()) cerr << "click_hold, ";
01276    if (is_press_hold()) cerr << "press_hold, ";
01277    cerr << endl;
01278 }
01279 
01280 int 
01281 GESTURE::draw(CVIEWptr &v) 
01282 {
01283    return 0;
01284 }
01285 
01286 int 
01287 GESTURE::draw_final(CVIEWptr &v) 
01288 {
01289    return _drawer ? _drawer->draw(this, v) : 0;
01290 }
01291 
01292 inline double
01293 pressure_to_grey(double p)
01294 {
01295    // map stylus pressure to greyscale non-linearly.
01296 
01297    // pressure is in range [0,1]
01298 
01299    // treat all pressures as being at least this:
01300    const double MIN_PRESSURE = 0.3; 
01301 
01302    // greyscale color at min pressure
01303    const double GREY0 = 0.6; 
01304 
01305    // this function increases in darkness from GREY0 at MIN_PRESSURE
01306    // to black at pressure 1. the square root makes darkness increase
01307    // rapidly for light pressures, then taper off to black as pressure
01308    // nears 1:
01309    return GREY0*(1 - sqrt(max(p, MIN_PRESSURE) - MIN_PRESSURE));
01310 }
01311 
01312 int
01313 GestureDrawer::draw(const GESTURE* gest, CVIEWptr& v)
01314 {
01315    // default drawing functionality
01316    // subclasses can handle fancy stroke drawing
01317 
01318    // load identity for model matrix
01319    glMatrixMode(GL_MODELVIEW);
01320    glPushMatrix();
01321    glLoadIdentity();
01322       
01323    // set up to draw in PIXEL coords:
01324    glMatrixMode(GL_PROJECTION);
01325    glPushMatrix();
01326    glLoadMatrixd(VIEW::peek()->pix_proj().transpose().matrix());
01327 
01328    // Set up line drawing attributes
01329    glPushAttrib(GL_ENABLE_BIT | GL_LINE_BIT | GL_CURRENT_BIT);
01330    glDisable(GL_LIGHTING);              // GL_ENABLE_BIT
01331 
01332    // turn on antialiasing for width-2 lines:
01333    GL_VIEW::init_line_smooth(GLfloat(v->line_scale()*2));
01334 
01335    // draw the line strip
01336    const PIXEL_list&    pts   = gest->pts();
01337    const ARRAY<double>& press = gest->pressures();
01338    glBegin(GL_LINE_STRIP);
01339    for (int k=0; k< pts.num(); k++) {
01340       double grey = pressure_to_grey(press[k]);
01341       glColor3d(grey, grey, grey);      // GL_CURRENT_BIT
01342       glVertex2dv(pts[k].data());
01343    }
01344    glEnd();
01345 
01346    // I tried to implement some code for corner getting highlighted
01347    // it is on the gesture.C version on Unnameable
01348 
01349    GL_VIEW::end_line_smooth();
01350 
01351    glPopAttrib();
01352    glPopMatrix();
01353    glMatrixMode(GL_MODELVIEW);
01354    glPopMatrix();
01355 
01356    return 0;
01357 }
01358 
01359 /* end of file gesture.C */

Generated on Mon Sep 18 11:39:30 2006 for jot by  doxygen 1.4.4