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