54#include <gsl/gsl_poly.h>
56std::vector<Point>
neighbors(std::vector<Point>
const &pts,
unsigned idx,
double radius){
57 std::vector<Point> res;
60 if (
L2(p-p0) < radius ) res.push_back(p);
78 std::vector<Point> ngbrs =
neighbors(pts, idx, radius);
79 if (ngbrs.size()<3)
return 0;
82 for (
unsigned i=0; i<5; i++){
83 unsigned ia = 0, ib = 0, ic = 0;
84 ia = rand()%ngbrs.size();
86 ib = rand()%ngbrs.size();
87 while (ic == ia || ic == ib)
88 ic = rand()%ngbrs.size();
98 for (
unsigned i=0; i<pts.size(); i++){
99 g += pts[i]/pts.size();
106 double a = 0, b = 0,
c = 0;
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]);
112 double eigen = ( (a+
c) -
sqrt((a-
c)*(a-
c)+4*b*b) )/2;
118 for (
unsigned i=0; i<pts.size(); i++){
119 std::vector<Point> ngbrs =
neighbors(pts,i,radius);
124 pts[i] = pts[i]*(1-t) +
proj*t;
125 }
else if (ngbrs.size()>=3) {
129 double r =
c.radius();
135double 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++){
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);
161unsigned nearest_after(std::vector<Point>
const &pts,
unsigned idx,
double *dist = NULL){
162 if ( idx >= pts.size()-1 )
return pts.size();
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]);
173 if (dist) *dist = d_min;
181 for (
unsigned i=0; i<pts.size()-1; i++){
183 if (no_longer_than >0.1 && d > no_longer_than){
184 pts.erase(pts.begin()+i+1, pts.end());
197 for (
unsigned i=0; i<pts.size()-1; i++){
198 bool already_visited =
true;
200 while ( i < pts.size()-1 && already_visited ){
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);
212 if (!already_visited){
214 pts[i+1] = pts[next];
221 unsigned n_points = pts.
size();
224 for (
unsigned int i = 0; i < pts.size(); i++) {
227 int max_segs = 4*n_points;
233 for (
int i=0; i<n_segs; i++){
247std::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();
254 double num_nghbrs = 0;
256 for(
unsigned i = 0; i < pts.size(); i++) {
257 if (!visited[i] &&
L2(pts[i]-p)<sampling){
265 if (num_nghbrs == 0)
break;
267 next *= 1./num_nghbrs;
287 return pow(10, x*5-2);
294class SketchFitterToy:
public Toy {
316 DRAW_IMPROVED_MOUSES,
325 TIGHTEN_NBHD_SIZE = 0,
336 static const char* menu_items[TOTAL_ITEMS];
337 static const char keys[TOTAL_ITEMS];
342 draw_f = &SketchFitterToy::draw_menu;
343 fit_f = &SketchFitterToy::fit_empty;
348 set_common_control_geometry =
true;
349 set_control_geometry =
true;
352 handles.push_back(&(toggles[DRAW_MOUSES]));
353 handles.push_back(&(toggles[DRAW_IMPROVED_MOUSES]));
354 handles.push_back(&(toggles[DRAW_STROKE]));
360 void init_common_ctrl_geom(
cairo_t* ,
int width,
int , std::ostringstream* )
362 if ( set_common_control_geometry )
364 set_common_control_geometry =
false;
366 toggles[DRAW_MOUSES].bounds =
Rect(p, p + d);
368 toggles[DRAW_IMPROVED_MOUSES].bounds =
Rect(p, p + d);
370 toggles[DRAW_STROKE].bounds =
Rect(p, p + d);
373 virtual void draw_common(
cairo_t *cr, std::ostringstream *notify,
374 int width,
int height,
bool , std::ostringstream *)
377 if(!mouses.empty() && toggles[DRAW_MOUSES].on ) {
382 for(
auto & mouse : mouses) {
386 cairo_set_line_width (cr, 0.5);
390 if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) {
392 for(
auto & improved_mouse : improved_mouses) {
396 cairo_set_line_width (cr, .75);
400 if(!
stroke.empty() && toggles[DRAW_STROKE].on) {
403 cairo_set_line_width (cr, .75);
407 *notify <<
"Press SHIFT to continue sketching. 'Z' to apply changes";
417 handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
418 handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
419 handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE]));
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);
429 toggles[TIGHTEN_USE_CIRCLE].bounds =
Rect(p, p + d);
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);
439 void draw_tighten(
cairo_t *cr, std::ostringstream *notify,
int width,
int height,
bool save, std::ostringstream *timer_stream) {
450 handles.push_back(&(sliders[EAT_NBHD_SIZE]));
452 void init_eat_ctrl_geom(
cairo_t* , std::ostringstream* ,
int ,
int height)
454 if ( set_control_geometry ){
455 set_control_geometry =
false;
456 sliders[EAT_NBHD_SIZE].geometry(
Point(50,
height - 35*(0+2)), 180);
460 double radius =
exp_rescale(sliders[EAT_NBHD_SIZE].value());
461 improved_mouses = mouses;
463 tighten(improved_mouses, 20,
true);
466 improved_mouses =
eat(improved_mouses, radius);
467 Path p(improved_mouses[0]);
468 for(
unsigned i = 1; i < improved_mouses.
size(); i++) {
473 void draw_eat(
cairo_t *cr, std::ostringstream *notify,
int width,
int height,
bool save, std::ostringstream *timer_stream) {
481 void init_tighten_eat()
484 handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
485 handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
486 handles.push_back(&(sliders[EAT_NBHD_SIZE]));
488 void init_tighten_eat_ctrl_geom(
cairo_t* , std::ostringstream* ,
int ,
int height)
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);
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);
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++) {
512 void draw_tighten_eat(
cairo_t *cr, std::ostringstream *notify,
int width,
int height,
bool save, std::ostringstream *timer_stream) {
514 init_tighten_eat_ctrl_geom(cr, notify,
width,
height);
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]));
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);
542 toggles[TIGHTEN_USE_CIRCLE].bounds =
Rect(p, p + d);
544 toggles[SORT_BIS].bounds =
Rect(p, p + d);
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);
553 double max_jump =
exp_rescale(sliders[SORT_RADIUS].value());
554 if (toggles[SORT_BIS].on){
559 radius =
exp_rescale(sliders[FUSE_RADIUS].value());
562 radius =
exp_rescale(sliders[INTERPOLATE_RADIUS].value());
566 void draw_sort(
cairo_t *cr, std::ostringstream *notify,
int width,
int height,
bool save, std::ostringstream *timer_stream) {
570 if(!improved_mouses.
empty() && toggles[DRAW_IMPROVED_MOUSES].on ) {
572 for(
unsigned i = 1; i < improved_mouses.
size(); i++) {
576 cairo_set_line_width (cr, .75);
584 void init_curvature()
587 handles.push_back(&(sliders[CURVATURE_NBHD_SIZE]));
588 handles.push_back(&(sliders[POINT_CHOOSER]));
590 void init_curvature_ctrl_geom(
cairo_t* , std::ostringstream* ,
int ,
int height)
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);
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 ){
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 )
616 lengths[i] = lengths[i-1] +
L2(mouses[i]-mouses[i-1]);
625 Point mp = mouses.back()-mouses.front();
632 void draw_curvature(
cairo_t *cr, std::ostringstream *notify,
int width,
int height,
bool save, std::ostringstream *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 ){
643 cairo_arc(cr,
c.center(
X),
c.center(
Y),
c.radius(), 0, 2*M_PI);
645 cairo_set_line_width (cr, .75);
655 void init_numerical()
661 void init_numerical_ctrl_geom(
cairo_t* , std::ostringstream* ,
int ,
int )
663 if ( set_control_geometry ){
664 set_control_geometry =
false;
668 void fit_numerical(){
671 void draw_numerical(
cairo_t *cr, std::ostringstream *notify,
int width,
int height,
bool save, std::ostringstream *timer_stream) {
673 init_numerical_ctrl_geom(cr, notify,
width,
height);
674 if(!mouses.empty()) {
677 cairo_set_line_width (cr, .75);
690 void draw_help(
cairo_t * , std::ostringstream *notify,
691 int ,
int ,
bool , std::ostringstream *)
693 *notify <<
"Tighten:\n";
694 *notify <<
" move points toward local\n";
695 *notify <<
" mean square line (or circle).\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;
722 vector<Point> mouses;
724 vector<Point> improved_mouses;
732 if (!(modifiers & (GDK_SHIFT_MASK))){
741 mouses.emplace_back(pos);
750 if(!mouses.empty()) {
762 void draw_menu(
cairo_t * cr, std::ostringstream *notify,
763 int ,
int ,
bool , std::ostringstream *)
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)
772 *notify <<
" " << keys[i] <<
" - " << menu_items[i] << std::endl;
774 if(!mouses.empty()) {
776 for(
auto & mouse : mouses) {
779 for(
auto & mouse : mouses) {
783 cairo_set_line_width (cr, 0.5);
788 void key_hit(
unsigned keyval,
unsigned modifiers)
override
790 char choice = std::toupper(keyval);
795 draw_f = &SketchFitterToy::draw_menu;
799 fit_f = &SketchFitterToy::fit_tighten;
800 draw_f = &SketchFitterToy::draw_tighten;
804 fit_f = &SketchFitterToy::fit_eat;
805 draw_f = &SketchFitterToy::draw_eat;
809 fit_f = &SketchFitterToy::fit_tighten_eat;
810 draw_f = &SketchFitterToy::draw_tighten_eat;
814 fit_f = &SketchFitterToy::fit_sort;
815 draw_f = &SketchFitterToy::draw_sort;
819 fit_f = &SketchFitterToy::fit_curvature;
820 draw_f = &SketchFitterToy::draw_curvature;
824 fit_f = &SketchFitterToy::fit_numerical;
825 draw_f = &SketchFitterToy::draw_numerical;
829 draw_f = &SketchFitterToy::draw_help;
832 mouses = improved_mouses;
838 void draw(
cairo_t *cr, std::ostringstream *notify,
839 int width,
int height,
bool save, std::ostringstream *timer_stream )
override
843 m_length = (m_width > m_height) ? m_width : m_height;
853 srand ( time(NULL) );
854 sliders = std::vector<Slider>(TOTAL_SLIDERS,
Slider(0., 1., 0, 0.,
""));
856 sliders[TIGHTEN_NBHD_SIZE ] =
Slider(0., 1., 0, 0.65,
"neighborhood size");
858 sliders[TIGHTEN_ITERRATIONS] =
Slider(0, 10, 1, 3,
"iterrations");
859 sliders[EAT_NBHD_SIZE ] =
Slider(0., 1., 0, 0.65,
"eating neighborhood size");
861 sliders[SORT_RADIUS ] =
Slider(0., 1., 0, 0.65,
"sort radius");
863 sliders[FUSE_RADIUS ] =
Slider(0., 1., 0, 0.65,
"fuse radius");
865 sliders[INTERPOLATE_RADIUS ] =
Slider(0., 1., 0, 0.65,
"intrepolate precision");
867 sliders[CURVATURE_NBHD_SIZE] =
Slider(0., 1., 0, 0.65,
"curvature nbhd size");
869 sliders[POINT_CHOOSER ] =
Slider(0, 100, 0, 50,
"Point chooser(%)");
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);
880 typedef void (SketchFitterToy::* draw_func_t) (
cairo_t*,
std::ostringstream*, int, int, bool,
std::ostringstream*);
882 typedef void (SketchFitterToy::* fit_func_t) ();
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;
893const char* SketchFitterToy::menu_items[] =
897 "eat points step by step",
899 "tighten + sort + fuse",
905const char SketchFitterToy::keys[] =
907 'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H'
910int main(
int argc,
char **argv) {
911 init(argc, argv,
new SketchFitterToy);
OptRect tighten(Rect &r, Point n, Interval lu)
Basic intersection routines.
Bezier fitting algorithms.
3x3 matrix representing an affine transformation.
Affine inverse() const
Compute the inverse matrix.
Bezier curve with compile-time specified order.
Set of all points at a fixed distance from the center.
void fit(std::vector< Point > const &points)
Fit the circle to the passed points using the least squares method.
Infinite line on a plane.
Point pointAt(Coord t) const
Sequence of contiguous curves, aka spline.
Piecewise< D2< SBasis > > toPwSb() const
size_type size() const
Natural size of the path.
void appendNew(Args &&... args)
Append a new curve to the path.
Function defined as discrete pieces.
Two-dimensional point that doubles as a vector.
Axis aligned, non-empty rectangle.
virtual void first_time(int, char **)
virtual void mouse_pressed(Geom::Point const &pos, unsigned button, unsigned modifiers)
virtual void mouse_moved(Geom::Point const &pos, unsigned modifiers)
vector< Handle * > handles
virtual void mouse_released(Geom::Point const &pos, unsigned button, unsigned modifiers)
virtual void save(FILE *f)
virtual void key_hit(unsigned keyval, unsigned modifiers)
virtual void draw(cairo_t *cr, std::ostringstream *notify, int w, int h, bool save, std::ostringstream *timing_stream)
Lifts one dimensional objects into 2D.
static auto proj(Geom::Point const &p, int dim)
static double exp_rescale(double x)
Various utility functions.
Piecewise< SBasis > curvature(D2< SBasis > const &M, double tol=.01)
Point projection(Point const &p, Line const &line)
std::optional< Crossing > OptCrossing
OptCrossing intersection(Ray const &r1, Line const &l2)
Piecewise< D2< SBasis > > sectionize(D2< Piecewise< SBasis > > const &a)
SBasisN< n > sqrt(SBasisN< n > const &a, int k)
Piecewise< SBasis > interpolate(std::vector< double > times, std::vector< double > values, unsigned smoothness=1)
Returns a Piecewise SBasis with prescribed values at prescribed times.
int bezier_fit_cubic_r(Point bezier[], Point const data[], int len, double error, unsigned max_beziers)
Fit a multi-segment Bezier curve to a set of digitized points, with possible weedout of identical poi...
D2< Piecewise< SBasis > > tan2(SBasis const &angle, double tol=.01, unsigned order=3)
Bezier integral(Bezier const &a)
Piecewise< SBasis > cross(Piecewise< D2< SBasis > > const &a, Piecewise< D2< SBasis > > const &b)
SBasis L2(D2< SBasis > const &a, unsigned k)
Point unit_vector(Point const &a)
D2< T > rot90(D2< T > const &a)
void cairo_line_to(cairo_t *cr, Geom::Point p1)
void draw_cross(cairo_t *cr, Geom::Point h)
void cairo_move_to(cairo_t *cr, Geom::Point p1)
void cairo_pw_d2_sb(cairo_t *cr, Geom::Piecewise< Geom::D2< Geom::SBasis > > const &p)
Linear linear(double ax, double b)
two-dimensional geometric operators.
some std functions to work with (pw)s-basis
Polynomial in symmetric power basis (S-basis)
std::vector< Point > eat(std::vector< Point > const &pts, double sampling)
Point massCenter(std::vector< Point > const &pts)
void sort_nearest_bis(std::vector< Point > &pts, double radius)
void sort_nearest(std::vector< Point > &pts, double no_longer_than=0)
std::string exp_formatter(double x)
Path ordered_fit(std::vector< Point > &pts, double tol)
void tighten(std::vector< Point > &pts, double radius, bool linear)
void fuse_close_points(std::vector< Point > &pts, double dist_min)
double dist_to(std::vector< Point > const &pts, Point const &p, unsigned *idx=NULL)
double exp_rescale(double x)
std::vector< Point > neighbors(std::vector< Point > const &pts, unsigned idx, double radius)
unsigned nearest_after(std::vector< Point >const &pts, unsigned idx, double *dist=NULL)
double avarageCurvature(std::vector< Point > const &pts, unsigned idx, double radius)
Line meanSquareLine(std::vector< Point > const &pts)
void cairo_set_source_rgba(cairo_t *cr, colour c)
std::string default_formatter(double x)
void init(int argc, char **argv, Toy *t, int width=600, int height=600)