1 /*
2  * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier.
3  *
4  * Copyright 2007 jf barraud.
5  * 2008 njh
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it either under the terms of the GNU Lesser General Public
9  * License version 2.1 as published by the Free Software Foundation
10  * (the "LGPL") or, at your option, under the terms of the Mozilla
11  * Public License Version 1.1 (the "MPL"). If you do not alter this
12  * notice, a recipient may use your version of this file under either
13  * the MPL or the LGPL.
14  *
15  * You should have received a copy of the LGPL along with this library
16  * in the file COPYING-LGPL-2.1; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  * You should have received a copy of the MPL along with this library
19  * in the file COPYING-MPL-1.1
20  *
21  * The contents of this file are subject to the Mozilla Public License
22  * Version 1.1 (the "License"); you may not use this file except in
23  * compliance with the License. You may obtain a copy of the License at
24  * http://www.mozilla.org/MPL/
25  *
26  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
27  * OF ANY KIND, either express or implied. See the LGPL or the MPL for
28  * the specific language governing rights and limitations.
29  *
30  */
31 
32 // mainly experimental atm...
33 // do not expect to find anything understandable here atm.
34 
35 #include <2geom/d2.h>
36 #include <2geom/sbasis.h>
37 #include <2geom/sbasis-geometric.h>
38 #include <2geom/sbasis-math.h>
39 #include <2geom/basic-intersection.h>
40 #include <2geom/bezier-utils.h>
41 
42 #include <2geom/circle.h>
43 
44 #include <toys/path-cairo.h>
45 #include <toys/toy-framework-2.h>
46 
47 #define ZERO 1e-7
48 
49 using std::vector;
50 using namespace Geom;
51 using namespace std;
52 
53 #include <stdio.h>
54 #include <gsl/gsl_poly.h>
55 
neighbors(std::vector<Point> const & pts,unsigned idx,double radius)56 std::vector<Point> neighbors(std::vector<Point> const &pts, unsigned idx, double radius){
57     std::vector<Point> res;
58     Point p0 = pts[idx];
59     for (unsigned i = 0; i<pts.size(); i++){
60         Point p = pts[i];
61         if ( L2(p-p0) < radius ) res.push_back(p);
62     }
63     return res;
64 }
65 
curvature(Point const & a,Point const & b,Point const & c)66 double curvature(Point const &a, Point const &b, Point const &c){
67     Line med_ab = Line( (a+b)/2, (a+b)/2+rot90(b-a) );
68     Line med_bc = Line( (b+c)/2, (b+c)/2+rot90(c-b) );
69     OptCrossing o = intersection(med_ab, med_bc);
70     if (o){
71         Point oo = med_ab.pointAt(o->ta);
72         return(1./L2(oo-a));
73     }
74     else
75         return 0;
76 }
77 
avarageCurvature(std::vector<Point> const & pts,unsigned idx,double radius)78 double avarageCurvature(std::vector<Point> const &pts, unsigned idx, double radius){
79     std::vector<Point> ngbrs = neighbors(pts, idx, radius);
80     if (ngbrs.size()<3) return 0;
81     double k=0;
82     double mass = 0;
83     for (unsigned i=0; i<5; i++){
84         unsigned ia = 0, ib = 0, ic = 0;
85         ia = rand()%ngbrs.size();
86         while (ib == ia)
87             ib = rand()%ngbrs.size();
88         while (ic == ia || ic == ib)
89             ic = rand()%ngbrs.size();
90         k += curvature(pts[ia],pts[ib],pts[ic]);
91         mass += 1; //smaller mass to closer triplets?
92     }
93     k /= mass;
94     return k;
95 }
96 
massCenter(std::vector<Point> const & pts)97 Point massCenter(std::vector<Point> const &pts){
98     Point g = Point(0,0);
99     for (unsigned i=0; i<pts.size(); i++){
100         g += pts[i]/pts.size();
101     }
102     return g;
103 }
104 
meanSquareLine(std::vector<Point> const & pts)105 Line meanSquareLine(std::vector<Point> const &pts){
106     Point g = massCenter(pts);
107     double a = 0, b = 0, c = 0;
108     for (unsigned i=0; i<pts.size(); i++){
109         a += (pts[i][Y]-g[Y])*(pts[i][Y]-g[Y]);
110         b +=-(pts[i][X]-g[X])*(pts[i][Y]-g[Y]);
111         c += (pts[i][X]-g[X])*(pts[i][X]-g[X]);
112     }
113     double eigen = ( (a+c) - sqrt((a-c)*(a-c)+4*b*b) )/2;
114     Point u(-b,a-eigen);
115     return Line(g, g+u);
116 }
117 
tighten(std::vector<Point> & pts,double radius,bool linear)118 void tighten(std::vector<Point> &pts, double radius, bool linear){
119     for (unsigned i=0; i<pts.size(); i++){
120         std::vector<Point> ngbrs = neighbors(pts,i,radius);
121         if (linear){
122             Line d = meanSquareLine(ngbrs);
123             Point proj = projection( pts[i], d );
124             double t = 2./3.;
125             pts[i] = pts[i]*(1-t) + proj*t;
126         }else if (ngbrs.size()>=3) {
127             Circle c;
128             c.fit(ngbrs);
129             Point o = c.center();
130             double r = c.radius();
131             pts[i] = o + unit_vector(pts[i]-o)*r;
132         }
133     }
134 }
135 
dist_to(std::vector<Point> const & pts,Point const & p,unsigned * idx=NULL)136 double dist_to(std::vector<Point> const &pts, Point const &p, unsigned *idx=NULL){
137     double d,d_min = std::numeric_limits<float>::infinity();
138     if (idx) *idx = pts.size();
139     for (unsigned i = 0; i<pts.size(); i++){
140         d = L2(pts[i]-p);
141         if ( d < d_min ){
142             d_min = d;
143             if (idx) *idx = i;
144         }
145     }
146     return d_min;
147 }
148 
fuse_close_points(std::vector<Point> & pts,double dist_min)149 void fuse_close_points(std::vector<Point> &pts, double dist_min){
150     if (pts.size()==0) return;
151     std::vector<Point> reduced_pts;
152     reduced_pts.push_back(pts[0]);
153     for (unsigned i = 0; i<pts.size(); i++){
154         double d = dist_to(reduced_pts, pts[i]);
155         if ( d > dist_min ) reduced_pts.push_back(pts[i]);
156     }
157     pts = reduced_pts;
158     return;
159 }
160 
161 
nearest_after(std::vector<Point> const & pts,unsigned idx,double * dist=NULL)162 unsigned nearest_after(std::vector<Point>const &pts, unsigned idx, double *dist = NULL){
163     if ( idx >= pts.size()-1 ) return pts.size();
164     Point p = pts[idx];
165     unsigned res = idx+1;
166     double d_min = L2(p-pts[res]);
167     for (unsigned i=idx+2; i<pts.size(); i++){
168         double d = L2(p-pts[i]);
169         if (d < d_min) {
170             d_min = d;
171             res = i;
172         }
173     }
174     if (dist) *dist = d_min;
175     return res;
176 }
177 
178 //TEST ME: use direction information to separate exaeco?
sort_nearest(std::vector<Point> & pts,double no_longer_than=0)179 void sort_nearest(std::vector<Point> &pts, double no_longer_than = 0){
180     double d;
181     Point p;
182     for (unsigned i=0; i<pts.size()-1; i++){
183         unsigned j = nearest_after(pts,i,&d);
184         if (no_longer_than >0.1 && d > no_longer_than){
185             pts.erase(pts.begin()+i+1, pts.end());
186             return;
187         }
188         p = pts[i+1];
189         pts[i+1] = pts[j];
190         pts[j] = p;
191     }
192 }
193 
194 //FIXME: optimize me if further used...
sort_nearest_bis(std::vector<Point> & pts,double radius)195 void sort_nearest_bis(std::vector<Point> &pts, double radius){
196     double d;
197     Point p;
198     for (unsigned i=0; i<pts.size()-1; i++){
199         bool already_visited = true;
200         unsigned next = 0; // silence warning
201         while ( i < pts.size()-1 && already_visited ){
202             next = nearest_after(pts,i,&d);
203             already_visited = false;
204             for (unsigned k=0; k<i; k++){
205                 double d_k_next = L2( pts[next] - pts[k]);
206                 if ( d_k_next < d && d_k_next < radius ){
207                     already_visited = true;
208                     pts.erase(pts.begin()+next);
209                     break;
210                 }
211             }
212         }
213         if (!already_visited){
214             p = pts[i+1];
215             pts[i+1] = pts[next];
216             pts[next] = p;
217         }
218     }
219 }
220 
ordered_fit(std::vector<Point> & pts,double tol)221 Path ordered_fit(std::vector<Point> &pts, double tol){
222     unsigned n_points = pts.size();
223     Geom::Point * b = g_new(Geom::Point, 4*n_points);
224     Geom::Point * points = g_new(Geom::Point, 4*n_points);
225     for (unsigned int i = 0; i < pts.size(); i++) {
226         points[i] = pts[i];
227     }
228     int max_segs = 4*n_points;
229     int const n_segs = bezier_fit_cubic_r(b, points, n_points,
230                                           tol*tol, max_segs);
231     Path res;
232     if ( n_segs > 0){
233         res = Path(b[0]);
234         for (int i=0; i<n_segs; i++){
235             res.appendNew<CubicBezier>(b[4*i+1],b[4*i+2],b[4*i+3]);
236         }
237     }
238     g_free(b);
239     g_free(points);
240     return res;
241 }
242 
243 //-----------------------------------------------------------------------------------------
244 //-----------------------------------------------------------------------------------------
245 //-----------------------------------------------------------------------------------------
246 
247 
eat(std::vector<Point> const & pts,double sampling)248 std::vector<Point> eat(std::vector<Point> const &pts, double sampling){
249     std::vector<bool> visited(pts.size(),false);
250     std::vector<Point> res;
251     Point p = pts.front();
252     //Point q = p;
253     res.push_back(p);
254     while(true){
255         double num_nghbrs = 0;
256         Point next(0,0);
257         for(unsigned i = 0; i < pts.size(); i++) {
258             if (!visited[i] && L2(pts[i]-p)<sampling){
259                 //TODO: rotate pts[i] so that last step was in dir -pi...
260                 //dir += atan2(pts[i]-p);
261                 visited[i] = true;
262                 next+= pts[i]-p;
263                 num_nghbrs += 1;
264             }
265         }
266         if (num_nghbrs == 0) break;
267         //q=p;
268         next *= 1./num_nghbrs;
269         p += next;
270         res.push_back(p);
271     }
272     return res;
273 }
274 
275 
276 
277 
278 
279 //-----------------------------------------------------------------------------------------
280 //-----------------------------------------------------------------------------------------
281 //-----------------------------------------------------------------------------------------
282 //-----------------------------------------------------------------------------------------
283 //-----------------------------------------------------------------------------------------
284 //-----------------------------------------------------------------------------------------
285 
exp_rescale(double x)286 double exp_rescale(double x)
287 {
288     return pow(10, x*5-2);
289 }
exp_formatter(double x)290 std::string exp_formatter(double x)
291 {
292     return default_formatter(exp_rescale(x));
293 }
294 
295 class SketchFitterToy: public Toy {
296 
297     enum menu_item_t
298     {
299         SHOW_MENU = 0,
300         TEST_TIGHTEN,
301         TEST_EAT_BY_STEP,
302         TEST_TIGHTEN_EAT,
303         TEST_CURVATURE,
304         TEST_SORT,
305         TEST_NUMERICAL,
306         SHOW_HELP,
307         TOTAL_ITEMS // this one must be the last item
308     };
309 
310     enum handle_label_t
311     {
312     };
313 
314     enum toggle_label_t
315     {
316         DRAW_MOUSES = 0,
317         DRAW_IMPROVED_MOUSES,
318         DRAW_STROKE,
319         TIGHTEN_USE_CIRCLE,
320         SORT_BIS,
321         TOTAL_TOGGLES // this one must be the last item
322     };
323 
324     enum slider_label_t
325     {
326         TIGHTEN_NBHD_SIZE = 0,
327         TIGHTEN_ITERRATIONS,
328         EAT_NBHD_SIZE,
329         SORT_RADIUS,
330         FUSE_RADIUS,
331         INTERPOLATE_RADIUS,
332         CURVATURE_NBHD_SIZE,
333         POINT_CHOOSER,
334         TOTAL_SLIDERS // this one must be the last item
335     };
336 
337     static const char* menu_items[TOTAL_ITEMS];
338     static const char keys[TOTAL_ITEMS];
339 
fit_empty()340     void fit_empty(){}
first_time(int,char **)341     virtual void first_time(int /*argc*/, char** /*argv*/)
342     {
343         draw_f = &SketchFitterToy::draw_menu;
344         fit_f =  &SketchFitterToy::fit_empty;
345     }
346 
init_common()347     void init_common()
348     {
349         set_common_control_geometry = true;
350         set_control_geometry = true;
351 
352         handles.clear();
353         handles.push_back(&(toggles[DRAW_MOUSES]));
354         handles.push_back(&(toggles[DRAW_IMPROVED_MOUSES]));
355         handles.push_back(&(toggles[DRAW_STROKE]));
356 
357         //sliders.clear();
358         //toggles.clear();
359         //handles.clear();
360     }
init_common_ctrl_geom(cairo_t *,int width,int,std::ostringstream *)361     void init_common_ctrl_geom(cairo_t* /*cr*/, int width, int /*height*/, std::ostringstream* /*notify*/)
362     {
363         if ( set_common_control_geometry )
364         {
365             set_common_control_geometry = false;
366             Point p(10, 20), d(width/3-20,25);
367             toggles[DRAW_MOUSES].bounds = Rect(p, p + d);
368             p += Point ((width)/3, 0);
369             toggles[DRAW_IMPROVED_MOUSES].bounds = Rect(p, p + d);
370             p += Point ((width)/3, 0);
371             toggles[DRAW_STROKE].bounds = Rect(p, p + d);
372         }
373     }
draw_common(cairo_t * cr,std::ostringstream * notify,int width,int height,bool,std::ostringstream *)374     virtual void draw_common( cairo_t *cr, std::ostringstream *notify,
375                               int width, int height, bool /*save*/, std::ostringstream */*timer_stream*/)
376     {
377         init_common_ctrl_geom(cr, width, height, notify);
378         if(!mouses.empty() && toggles[DRAW_MOUSES].on ) {
379             //cairo_move_to(cr, mouses[0]);
380             //for(unsigned i = 0; i < mouses.size(); i++) {
381             //    cairo_line_to(cr, mouses[i]);
382             //}
383             for(unsigned i = 0; i < mouses.size(); i++) {
384                 draw_cross(cr, mouses[i]);
385             }
386             cairo_set_source_rgba (cr, 0., 0., 0., .25);
387             cairo_set_line_width (cr, 0.5);
388             cairo_stroke(cr);
389         }
390 
391         if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) {
392             cairo_move_to(cr, improved_mouses[0]);
393             for(unsigned i = 0; i < improved_mouses.size(); i++) {
394                 draw_cross(cr, improved_mouses[i]);
395             }
396             cairo_set_source_rgba (cr, 1., 0., 0., 1);
397             cairo_set_line_width (cr, .75);
398             cairo_stroke(cr);
399         }
400 
401         if(!stroke.empty() && toggles[DRAW_STROKE].on) {
402             cairo_pw_d2_sb(cr, stroke);
403             cairo_set_source_rgba (cr, 0., 0., 1., 1);
404             cairo_set_line_width (cr, .75);
405             cairo_stroke(cr);
406         }
407 
408         *notify << "Press SHIFT to continue sketching. 'Z' to apply changes";
409     }
410 
411 
412 //-----------------------------------------------------------------------------------------
413 // Tighten: tries to move the points toward the common curve
414 //-----------------------------------------------------------------------------------------
init_tighten()415     void init_tighten()
416     {
417         init_common();
418         handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
419         handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
420         handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE]));
421     }
init_tighten_ctrl_geom(cairo_t *,std::ostringstream *,int width,int height)422     void init_tighten_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height)
423     {
424         if ( set_control_geometry ){
425             set_control_geometry = false;
426             sliders[TIGHTEN_NBHD_SIZE  ].geometry(Point(50, height - 35*2), 180);
427             sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180);
428 
429             Point p(width-250, height - 50), d(225,25);
430             toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p,     p + d);
431         }
432     }
fit_tighten()433     void fit_tighten(){
434         improved_mouses = mouses;
435         double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value());
436         for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){
437             tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on);
438         }
439     }
draw_tighten(cairo_t * cr,std::ostringstream * notify,int width,int height,bool save,std::ostringstream * timer_stream)440     void draw_tighten(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
441         draw_common(cr, notify, width, height, save, timer_stream);
442         init_tighten_ctrl_geom(cr, notify, width, height);
443     }
444 
445 //-----------------------------------------------------------------------------------------
446 // Eat by step: eats the curve moving at each step in the average direction of the neighbors.
447 //-----------------------------------------------------------------------------------------
init_eat()448     void init_eat()
449     {
450         init_common();
451         handles.push_back(&(sliders[EAT_NBHD_SIZE]));
452     }
init_eat_ctrl_geom(cairo_t *,std::ostringstream *,int,int height)453     void init_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
454     {
455         if ( set_control_geometry ){
456             set_control_geometry = false;
457             sliders[EAT_NBHD_SIZE].geometry(Point(50, height - 35*(0+2)), 180);
458         }
459     }
fit_eat()460     void fit_eat(){
461         double radius = exp_rescale(sliders[EAT_NBHD_SIZE].value());
462         improved_mouses = mouses;
463 
464         tighten(improved_mouses, 20, true);
465 
466         stroke = Piecewise<D2<SBasis> >();
467         improved_mouses = eat(improved_mouses, radius);
468         Path p(improved_mouses[0]);
469         for(unsigned i = 1; i < improved_mouses.size(); i++) {
470             p.appendNew<LineSegment>(improved_mouses[i]);
471         }
472         stroke = p.toPwSb();
473     }
draw_eat(cairo_t * cr,std::ostringstream * notify,int width,int height,bool save,std::ostringstream * timer_stream)474     void draw_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
475         draw_common(cr, notify, width, height, save, timer_stream);
476         init_eat_ctrl_geom(cr, notify, width, height);
477     }
478 
479 //-----------------------------------------------------------------------------------------
480 // Tighten + Eat
481 //-----------------------------------------------------------------------------------------
init_tighten_eat()482     void init_tighten_eat()
483     {
484         init_common();
485         handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
486         handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
487         handles.push_back(&(sliders[EAT_NBHD_SIZE]));
488     }
init_tighten_eat_ctrl_geom(cairo_t *,std::ostringstream *,int,int height)489     void init_tighten_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
490     {
491         if ( set_control_geometry ){
492             set_control_geometry = false;
493             sliders[TIGHTEN_NBHD_SIZE  ].geometry(Point(50, height - 35*2), 180);
494             sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180);
495             sliders[EAT_NBHD_SIZE      ].geometry(Point(50, height - 35*4), 180);
496         }
497     }
fit_tighten_eat()498     void fit_tighten_eat(){
499         improved_mouses = mouses;
500         double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value());
501         for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){
502             tighten(improved_mouses, radius, toggles[0].on);
503         }
504         stroke = Piecewise<D2<SBasis> >();
505         radius = exp_rescale(sliders[EAT_NBHD_SIZE].value());
506         improved_mouses = eat(improved_mouses, radius);
507         Path p(improved_mouses[0]);
508         for(unsigned i = 1; i < improved_mouses.size(); i++) {
509             p.appendNew<LineSegment>(improved_mouses[i]);
510         }
511         stroke = p.toPwSb();
512     }
draw_tighten_eat(cairo_t * cr,std::ostringstream * notify,int width,int height,bool save,std::ostringstream * timer_stream)513     void draw_tighten_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
514         draw_common(cr, notify, width, height, save, timer_stream);
515         init_tighten_eat_ctrl_geom(cr, notify, width, height);
516     }
517 
518 //-----------------------------------------------------------------------------------------
519 // Sort: tighten, then sort and eventually fuse.
520 //-----------------------------------------------------------------------------------------
init_sort()521     void init_sort()
522     {
523         init_common();
524         handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
525         handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
526         handles.push_back(&(sliders[SORT_RADIUS]));
527         handles.push_back(&(sliders[FUSE_RADIUS]));
528         handles.push_back(&(sliders[INTERPOLATE_RADIUS]));
529         handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE]));
530         handles.push_back(&(toggles[SORT_BIS]));
531     }
init_sort_ctrl_geom(cairo_t *,std::ostringstream *,int width,int height)532     void init_sort_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height)
533     {
534         if ( set_control_geometry ){
535             set_control_geometry = false;
536             sliders[TIGHTEN_NBHD_SIZE].geometry(Point(50, height - 35*2), 180);
537             sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180);
538             sliders[SORT_RADIUS].geometry(Point(50, height - 35*4), 180);
539             sliders[FUSE_RADIUS].geometry(Point(50, height - 35*5), 180);
540             sliders[INTERPOLATE_RADIUS].geometry(Point(50, height - 35*6), 180);
541 
542             Point p(width-250, height - 50), d(225,25);
543             toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p,     p + d);
544             p += Point(0,-30);
545             toggles[SORT_BIS].bounds = Rect(p,     p + d);
546         }
547     }
fit_sort()548     void fit_sort(){
549         improved_mouses = mouses;
550         double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value());
551         for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){
552             tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on);
553         }
554         double max_jump = exp_rescale(sliders[SORT_RADIUS].value());
555         if (toggles[SORT_BIS].on){
556             sort_nearest_bis(improved_mouses, max_jump);
557         }else{
558             sort_nearest(improved_mouses, max_jump);
559         }
560         radius = exp_rescale(sliders[FUSE_RADIUS].value());
561         fuse_close_points(improved_mouses, radius);
562 
563         radius = exp_rescale(sliders[INTERPOLATE_RADIUS].value());
564         Path p = ordered_fit(improved_mouses, radius/5);
565         stroke = p.toPwSb();
566     }
draw_sort(cairo_t * cr,std::ostringstream * notify,int width,int height,bool save,std::ostringstream * timer_stream)567     void draw_sort(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
568         draw_common(cr, notify, width, height, save, timer_stream);
569         init_sort_ctrl_geom(cr, notify, width, height);
570 
571         if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) {
572             cairo_move_to(cr, improved_mouses[0]);
573             for(unsigned i = 1; i < improved_mouses.size(); i++) {
574                 cairo_line_to(cr, improved_mouses[i]);
575             }
576             cairo_set_source_rgba (cr, 1., 0., 0., 1);
577             cairo_set_line_width (cr, .75);
578             cairo_stroke(cr);
579         }
580     }
581 
582 //-----------------------------------------------------------------------------------------
583 // Average curvature.
584 //-----------------------------------------------------------------------------------------
init_curvature()585     void init_curvature()
586     {
587         init_common();
588         handles.push_back(&(sliders[CURVATURE_NBHD_SIZE]));
589         handles.push_back(&(sliders[POINT_CHOOSER]));
590     }
init_curvature_ctrl_geom(cairo_t *,std::ostringstream *,int,int height)591     void init_curvature_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
592     {
593         if ( set_control_geometry ){
594             set_control_geometry = false;
595             sliders[CURVATURE_NBHD_SIZE].geometry(Point(50, height - 60), 180);
596             sliders[POINT_CHOOSER      ].geometry(Point(50, height - 90), 180);
597         }
598     }
599     //just for fun!
fit_curvature()600     void fit_curvature(){
601         std::vector<double> curvatures(mouses.size(),0);
602         std::vector<double> lengths(mouses.size(),0);
603         for (unsigned i=0; i<mouses.size(); i++){
604             double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value());
605             std::vector<Point> ngbrs = neighbors(mouses,i,radius);
606             if ( ngbrs.size()>2 ){
607                 Circle c;
608                 c.fit(ngbrs);
609                 curvatures[i] = 1./c.radius();
610                 Point v = (i<mouses.size()-1) ? mouses[i+1]-mouses[i] : mouses[i]-mouses[i-1];
611                 if (cross(v, c.center()-mouses[i]) > 0 )
612                     curvatures[i] *= -1;
613             }else{
614                 curvatures[i] = 0;
615             }
616             if (i>0){
617                 lengths[i] = lengths[i-1] + L2(mouses[i]-mouses[i-1]);
618             }
619         }
620         Piecewise<SBasis> k = interpolate( lengths, curvatures , 1);
621         Piecewise<SBasis> alpha = integral(k);
622         Piecewise<D2<SBasis> > v = sectionize(tan2(alpha));
623         stroke = integral(v) + mouses[0];
624 
625         Point sp = stroke.lastValue()-stroke.firstValue();
626         Point mp = mouses.back()-mouses.front();
627         Affine mat1 = Affine(sp[X], sp[Y], -sp[Y], sp[X], stroke.firstValue()[X], stroke.firstValue()[Y]);
628         Affine mat2 = Affine(mp[X], mp[Y], -mp[Y], mp[X], mouses[0][X], mouses[0][Y]);
629         mat1 = mat1.inverse()*mat2;
630         stroke = stroke*mat1;
631 
632     }
draw_curvature(cairo_t * cr,std::ostringstream * notify,int width,int height,bool save,std::ostringstream * timer_stream)633     void draw_curvature(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
634         draw_common(cr, notify, width, height, save, timer_stream);
635         init_curvature_ctrl_geom(cr, notify, width, height);
636         if(!mouses.empty()) {
637             double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value());
638             unsigned i = unsigned( (mouses.size()-1)*sliders[POINT_CHOOSER].value()/100. );
639             std::vector<Point> ngbrs = neighbors(mouses,i,radius);
640             if ( ngbrs.size()>2 ){
641                 draw_cross(cr, mouses[i]);
642                 Circle c;
643                 c.fit(ngbrs);
644                 cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI);
645                 cairo_set_source_rgba (cr, 1., 0., 0., 1);
646                 cairo_set_line_width (cr, .75);
647                 cairo_stroke(cr);
648             }
649             cairo_pw_d2_sb(cr, stroke);
650         }
651     }
652 
653 //-----------------------------------------------------------------------------------------
654 // Brutal optimization, number of segment fixed.
655 //-----------------------------------------------------------------------------------------
init_numerical()656     void init_numerical()
657     {
658         init_common();
659         //sliders.push_back(Slider(0, 10, 1, 1, "Number of curves"));
660         //handles.push_back(&(sliders[0]));
661     }
init_numerical_ctrl_geom(cairo_t *,std::ostringstream *,int,int)662     void init_numerical_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int /*height*/)
663     {
664         if ( set_control_geometry ){
665             set_control_geometry = false;
666             //sliders[0].geometry(Point(50, height - 35*(0+2)), 180);
667         }
668     }
fit_numerical()669     void fit_numerical(){
670         //Not implemented
671     }
draw_numerical(cairo_t * cr,std::ostringstream * notify,int width,int height,bool save,std::ostringstream * timer_stream)672     void draw_numerical(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
673         draw_common(cr, notify, width, height, save, timer_stream);
674         init_numerical_ctrl_geom(cr, notify, width, height);
675         if(!mouses.empty()) {
676             cairo_pw_d2_sb(cr, stroke);
677             cairo_set_source_rgba (cr, 1., 0., 0., 1);
678             cairo_set_line_width (cr, .75);
679             cairo_stroke(cr);
680         }
681     }
682 //-----------------------------------------------------------------------------------------
683 //-----------------------------------------------------------------------------------------
684 
init_help()685     void init_help()
686     {
687         handles.clear();
688         //sliders.clear();
689         //toggles.clear();
690     }
draw_help(cairo_t *,std::ostringstream * notify,int,int,bool,std::ostringstream *)691     void draw_help( cairo_t * /*cr*/, std::ostringstream *notify,
692                     int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/)
693     {
694         *notify << "Tighten:\n";
695         *notify << "    move points toward local\n";
696         *notify << "    mean square line (or circle).\n";
697         *notify << "Eat:\n";
698         *notify << "    eat points like a pacman; at each step, move to the\n";
699         *notify << "    average of the not already visited neighbor points.\n";
700         *notify << "Sort:\n";
701         *notify << "     move from one point to the nearest one.\n";
702         *notify << "    Stop at the first jump longer than sort-radius\n";
703         *notify << "Sort-bis:\n";
704         *notify << "    move from one point to the nearest one,\n";
705         *notify << "    unless it was 'already visited' (i.e. it is closer to\n";
706         *notify << "    an already sorted point with distance < sort-radius.\n";
707         *notify << "Fuse: \n";
708         *notify << "    start from first point, remove all points closer to it\n";
709         *notify << "    than fuse-radius, move to the first one that is not, and repeat.\n";
710         *notify << "Curvature: \n";
711         *notify << "    Compute the curvature at a given point from the circle fitting the\n";
712         *notify << "    nearby points (just for fun: the stroke is the 'integral' of this\n";
713         *notify << "    average curvature)\n";
714         *notify << "Numerical: \n";
715         *notify << "    still waiting for someone to implement me ;-)\n\n";
716         *notify << std::endl;
717     }
718 
719 //-----------------------------------------------------------------------------------------
720 //-----------------------------------------------------------------------------------------
721 
722 public:
723     vector<Point> mouses;
724     int mouse_drag;
725     vector<Point> improved_mouses;
726     Piecewise<D2<SBasis > > stroke;
727 
mouse_pressed(GdkEventButton * e)728     void mouse_pressed(GdkEventButton* e) {
729         //toggle_events(toggles, e);
730 	Toy::mouse_pressed(e);
731 	if(!selected) {
732 	    mouse_drag = 1;
733             if (!(e->state & (GDK_SHIFT_MASK))){
734                 mouses.clear();
735             }
736 	}
737     }
738 
mouse_moved(GdkEventMotion * e)739     virtual void mouse_moved(GdkEventMotion* e) {
740 	if(mouse_drag) {
741 	    mouses.push_back(Point(e->x, e->y));
742 	    redraw();
743 	} else {
744 	    Toy::mouse_moved(e);
745 	}
746     }
747 
mouse_released(GdkEventButton * e)748     virtual void mouse_released(GdkEventButton* e) {
749         mouse_drag = 0;
750         if(!mouses.empty()) {
751             (this->*fit_f)();
752         }
753         Toy::mouse_released(e);
754     }
755 
init_menu()756     void init_menu()
757     {
758         handles.clear();
759         //sliders.clear();
760         //toggles.clear();
761     }
draw_menu(cairo_t * cr,std::ostringstream * notify,int,int,bool,std::ostringstream *)762     void draw_menu( cairo_t * cr, std::ostringstream *notify,
763                     int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/)
764     {
765         *notify << "Sketch some shape on canvas (press SHIFT to use several 'strokes')\n";
766         *notify << "Each menu below will transform your input.\n";
767         *notify << "Press 'Z' to make the result the new input\n";
768         *notify << " \n \n \n";
769         *notify << std::endl;
770         for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i)
771         {
772             *notify << "   " << keys[i] << " -  " <<  menu_items[i] << std::endl;
773         }
774         if(!mouses.empty()) {
775             cairo_move_to(cr, mouses[0]);
776             for(unsigned i = 0; i < mouses.size(); i++) {
777                 cairo_line_to(cr, mouses[i]);
778             }
779             for(unsigned i = 0; i < mouses.size(); i++) {
780                 draw_cross(cr, mouses[i]);
781             }
782             cairo_set_source_rgba (cr, 0., 0., 0., .25);
783             cairo_set_line_width (cr, 0.5);
784             cairo_stroke(cr);
785         }
786     }
787 
key_hit(GdkEventKey * e)788     void key_hit(GdkEventKey *e)
789     {
790         char choice = std::toupper(e->keyval);
791         switch ( choice )
792         {
793             case 'A':
794                 init_menu();
795                 draw_f = &SketchFitterToy::draw_menu;
796                 break;
797             case 'B':
798                 init_tighten();
799                 fit_f = &SketchFitterToy::fit_tighten;
800                 draw_f = &SketchFitterToy::draw_tighten;
801                 break;
802             case 'C':
803                 init_eat();
804                 fit_f = &SketchFitterToy::fit_eat;
805                 draw_f = &SketchFitterToy::draw_eat;
806                 break;
807             case 'D':
808                 init_tighten_eat();
809                 fit_f = &SketchFitterToy::fit_tighten_eat;
810                 draw_f = &SketchFitterToy::draw_tighten_eat;
811                 break;
812             case 'E':
813                 init_sort();
814                 fit_f = &SketchFitterToy::fit_sort;
815                 draw_f = &SketchFitterToy::draw_sort;
816                 break;
817             case 'F':
818                 init_curvature();
819                 fit_f = &SketchFitterToy::fit_curvature;
820                 draw_f = &SketchFitterToy::draw_curvature;
821                 break;
822             case 'G':
823                 init_numerical();
824                 fit_f = &SketchFitterToy::fit_numerical;
825                 draw_f = &SketchFitterToy::draw_numerical;
826                 break;
827             case 'H':
828                 init_help();
829                 draw_f = &SketchFitterToy::draw_help;
830                 break;
831             case 'Z':
832                 mouses = improved_mouses;
833                 break;
834         }
835         redraw();
836     }
837 
draw(cairo_t * cr,std::ostringstream * notify,int width,int height,bool save,std::ostringstream * timer_stream)838     virtual void draw( cairo_t *cr, std::ostringstream *notify,
839                        int width, int height, bool save, std::ostringstream *timer_stream )
840     {
841         m_width = width;
842         m_height = height;
843         m_length = (m_width > m_height) ? m_width : m_height;
844         m_length *= 2;
845         (this->*draw_f)(cr, notify, width, height, save, timer_stream);
846         Toy::draw(cr, notify, width, height, save, timer_stream);
847     }
848 
849 
850   public:
SketchFitterToy()851     SketchFitterToy()
852     {
853         srand ( time(NULL) );
854         sliders = std::vector<Slider>(TOTAL_SLIDERS, Slider(0., 1., 0, 0., ""));
855 
856         sliders[TIGHTEN_NBHD_SIZE  ] = Slider(0., 1., 0, 0.65, "neighborhood size");
857         sliders[TIGHTEN_NBHD_SIZE  ].formatter(&exp_formatter);
858         sliders[TIGHTEN_ITERRATIONS] = Slider(0, 10, 1, 3, "iterrations");
859         sliders[EAT_NBHD_SIZE      ] = Slider(0., 1., 0, 0.65, "eating neighborhood size");
860         sliders[EAT_NBHD_SIZE      ].formatter(&exp_formatter);
861         sliders[SORT_RADIUS        ] = Slider(0., 1., 0, 0.65, "sort radius");
862         sliders[SORT_RADIUS        ].formatter(&exp_formatter);
863         sliders[FUSE_RADIUS        ] = Slider(0., 1., 0, 0.65, "fuse radius");
864         sliders[FUSE_RADIUS        ].formatter(&exp_formatter);
865         sliders[INTERPOLATE_RADIUS ] = Slider(0., 1., 0, 0.65, "intrepolate precision");
866         sliders[INTERPOLATE_RADIUS ].formatter(&exp_formatter);
867         sliders[CURVATURE_NBHD_SIZE] = Slider(0., 1., 0, 0.65, "curvature nbhd size");
868         sliders[CURVATURE_NBHD_SIZE].formatter(&exp_formatter);
869         sliders[POINT_CHOOSER      ] = Slider(0, 100, 0, 50, "Point chooser(%)");
870 
871         toggles = std::vector<Toggle>(TOTAL_TOGGLES, Toggle("",true));
872         toggles[DRAW_MOUSES] = Toggle("Draw mouses",true);
873         toggles[DRAW_IMPROVED_MOUSES] = Toggle("Draw new mouses",true);
874         toggles[DRAW_STROKE] = Toggle("Draw stroke",true);
875         toggles[TIGHTEN_USE_CIRCLE] = Toggle("Tighten: use circle",false);
876         toggles[SORT_BIS      ] = Toggle("Sort: bis",false);
877     }
878 
879   private:
880     typedef void (SketchFitterToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*);
881     draw_func_t draw_f;
882     typedef void (SketchFitterToy::* fit_func_t) ();
883     fit_func_t fit_f;
884     bool set_common_control_geometry;
885     bool set_control_geometry;
886     std::vector<Toggle> toggles;
887     std::vector<Slider> sliders;
888     double m_width, m_height, m_length;
889 
890 }; // end class SketchFitterToy
891 
892 
893 const char* SketchFitterToy::menu_items[] =
894 {
895     "show this menu",
896     "tighten",
897     "eat points step by step",
898     "tighten + eat",
899     "tighten + sort + fuse",
900     "curvature",
901     "numerical",
902     "help",
903 };
904 
905 const char SketchFitterToy::keys[] =
906 {
907     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'
908 };
909 
main(int argc,char ** argv)910 int main(int argc, char **argv) {
911     init(argc, argv, new SketchFitterToy);
912     return 0;
913 }
914 
915 /*
916   Local Variables:
917   mode:c++
918   c-file-style:"stroustrup"
919   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
920   indent-tabs-mode:nil
921   fill-column:99
922   End:
923 */
924 // vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
925