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