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