diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:57:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:57:42 +0000 |
commit | 61f3ab8f23f4c924d455757bf3e65f8487521b5a (patch) | |
tree | 885599a36a308f422af98616bc733a0494fe149a /src/toys | |
parent | Initial commit. (diff) | |
download | lib2geom-61f3ab8f23f4c924d455757bf3e65f8487521b5a.tar.xz lib2geom-61f3ab8f23f4c924d455757bf3e65f8487521b5a.zip |
Adding upstream version 1.3.upstream/1.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
161 files changed, 34606 insertions, 0 deletions
diff --git a/src/toys/2dsb2d.cpp b/src/toys/2dsb2d.cpp new file mode 100644 index 0000000..3effd1f --- /dev/null +++ b/src/toys/2dsb2d.cpp @@ -0,0 +1,128 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/transforms.h> +#include <2geom/pathvector.h> +#include <2geom/svg-path-parser.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +class Sb2d2: public Toy { + Path path_a; + D2<SBasis2d> sb2; + Piecewise<D2<SBasis> > path_a_pw; + PointSetHandle hand; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Geom::Point dir(1,-2); + for(unsigned dim = 0; dim < 2; dim++) { + Geom::Point dir(0,0); + dir[dim] = 1; + for(unsigned vi = 0; vi < sb2[dim].vs; vi++) + for(unsigned ui = 0; ui < sb2[dim].us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) { + unsigned corner = iu + 2*iv; + unsigned i = ui + vi*sb2[dim].us; + Geom::Point base((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + if(vi == 0 && ui == 0) { + base = Geom::Point(width/4., width/4.); + } + double dl = dot((hand.pts[corner+4*i] - base), dir)/dot(dir,dir); + sb2[dim][i][corner] = dl/(width/2)*pow(4.0,(double)ui+vi); + } + } + cairo_d2_sb2d(cr, sb2, dir*0.1, width); + cairo_set_source_rgba (cr, 0., 0., 0, 0.5); + cairo_stroke(cr); + for(unsigned i = 0; i < path_a_pw.size(); i++) { + D2<SBasis> B = path_a_pw[i]; + //const int depth = sb2[0].us*sb2[0].vs; + //const int surface_hand.pts = 4*depth; + //D2<SBasis> B = hand.pts_to_sbasis<3>(hand.pts.begin() + surface_hand.pts); + cairo_d2_sb(cr, B); + for(unsigned dim = 0; dim < 2; dim++) { + std::vector<double> r = roots(B[dim]); + for(double i : r) + draw_cross(cr, B(i)); + r = roots(Linear(width/4) - B[dim]); + for(double i : r) + draw_cross(cr, B(i)); + } + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + B *= (4./width); + D2<SBasis> tB = compose_each(sb2, B); + B = B*(width/2) + Geom::Point(width/4, width/4); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + + cairo_d2_sb(cr, tB); + } + + //*notify << "bo = " << sb2.index(0,0); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + PathVector paths_a = read_svgd(path_a_name); + assert(!paths_a.empty()); + path_a = paths_a[0]; + Rect bounds = path_a[0].boundsFast(); + std::cout << bounds.min() <<std::endl; + path_a = path_a * Affine(Translate(-bounds.min())); + double extreme = std::max(bounds.width(), bounds.height()); + path_a = path_a * Scale(40./extreme); + + path_a_pw = path_a.toPwSb(); + for(unsigned dim = 0; dim < 2; dim++) { + sb2[dim].us = 2; + sb2[dim].vs = 2; + const int depth = sb2[dim].us*sb2[dim].vs; + sb2[dim].resize(depth, Linear2d(0)); + } + + hand.pts.resize(sb2[0].vs*sb2[0].us*4); + handles.push_back(&hand); + + } + void resize_canvas(Geom::Rect const & s) override { + double width = s[0].extent(); + unsigned ii = 0; + for(unsigned vi = 0; vi < sb2[0].vs; vi++) + for(unsigned ui = 0; ui < sb2[0].us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) + hand.pts[ii++] = Geom::Point((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sb2d2); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/CMakeLists.txt b/src/toys/CMakeLists.txt new file mode 100644 index 0000000..c4929b7 --- /dev/null +++ b/src/toys/CMakeLists.txt @@ -0,0 +1,172 @@ +SET(2GEOM_TOY-FRAMEWORK-2_SRC +toy-framework-2.cpp +${2GEOM_INCLUDE_DIR}/toys/toy-framework-2.h +path-cairo.cpp +${2GEOM_INCLUDE_DIR}/toys/path-cairo.h +) +SET(2GEOM_LPE_TOY_FRAMEWORK_SRC +${2GEOM_TOY-FRAMEWORK-2_SRC} +lpe-framework.cpp +${2GEOM_INCLUDE_DIR}/toys/lpe-framework.h +) + +SET(2GEOM_TOYS-2_SRC +2dsb2d +aa +arc-bez +arc-length-param +auto-cross +boolops-toy +bound-path +bounds-test +box3d +center-warp +circle-fitting +circle-intersect +circle-line-intersect +circle-tangent-fitting +collinear-normal +conic-3 +conic-4 +conic-5 +conic-6 +conic-section-toy +convole +curvature-curve +curvature-test +curve-curve-distance +curve-curve-nearest-time +curve-intersection-by-bezier-clipping +curve-intersection-by-implicitization +cylinder3d +d2sbasis-fitting +d2sbasis-fitting-with-np +draw-toy +ellipse-area-minimizer +ellipse-bezier-intersect-toy +ellipse-fitting +ellipse-intersect-toy +ellipse-line-intersect-toy +elliptiarc-3point-center-fitting +elliptiarc-curve-fitting +elliptical-arc-toy +evolute +filet-minion +find-derivative +gear +#hatches +implicit-toy +ineaa +inner-product-clip +intersect-data +inverse-test +kinematic_templates +levelsets-test +line-toy +load-svgd +match-curve +mesh-grad +metro +minsb2d-solver +#normal-bundle +offset-toy +pair-intersect +paptest +parametrics +parser +path-along-path +path-effects +pencil +pencil-2 +plane3d +point-curve-nearest-time +portion-test +precise-flat +pw-compose-test +pw-funcs +pw-toy +rdm-area +rect_01 +rect_02 +rect_03 +rect-toy +root-finder-comparer +#rtree-toy +sanitize +#sb1d +sb2d +sb2d-solver +sbasisdim +sbasis-fitting +sb-math-test +sb-of-interval +sb-of-sb +sb-to-bez +sb-zeros +scribble +self-intersect +sketch-fitter +smash-intersector +squiggles +sweep +sweeper-toy +# these ones have only had a trivial rewrite to toy-2 +#uncross +winding-test +worms +) + +SET(2GEOM_LPE_TOYS_SRC +lpe-test +) + +OPTION(2GEOM_TOYS_LPE + "Build Inkscape Live Path Effect (LPE) Toy files" + ON) +IF(2GEOM_TOYS_LPE) + # make lib for lpetoy + add_library(lpetoy ${LIB_TYPE} ${2GEOM_LPE_TOY_FRAMEWORK_SRC}) + target_include_directories(lpetoy PUBLIC ${GTK3_INCLUDE_DIRS}) + target_link_libraries(lpetoy 2Geom::2geom ${GTK3_LIBRARIES}) + if(NOT WIN32 AND NOT APPLE) + target_link_libraries(lpetoy -lrt) + endif() + + FOREACH(source ${2GEOM_LPE_TOYS_SRC}) + add_executable(${source} ${source}.cpp) + target_link_libraries(${source} lpetoy 2Geom::2geom) + ENDFOREACH(source) + +ENDIF(2GEOM_TOYS_LPE) + +OPTION(2GEOM_TOYS + "Build the projects Toy files" + ON) +IF(2GEOM_TOYS) + # make lib for toy + ADD_LIBRARY(toy-2 ${LIB_TYPE} ${2GEOM_TOY-FRAMEWORK-2_SRC}) + target_include_directories(toy-2 PUBLIC ${GTK3_INCLUDE_DIRS}) + TARGET_LINK_LIBRARIES(toy-2 2Geom::2geom ${GTK3_LIBRARIES}) + if(NOT WIN32 AND NOT APPLE) + target_link_libraries(toy-2 -lrt) + endif() + + FOREACH(source ${2GEOM_TOYS-2_SRC}) + IF(${source} STREQUAL aa) + ADD_EXECUTABLE(${source} EXCLUDE_FROM_ALL ${source}.cpp) + TARGET_LINK_LIBRARIES(${source} affa) + ELSEIF(${source} STREQUAL ineaa) + ADD_EXECUTABLE(${source} EXCLUDE_FROM_ALL ${source}.cpp) + TARGET_LINK_LIBRARIES(${source} affa) + ELSEIF(${source} STREQUAL implicit-toy) + ADD_EXECUTABLE(${source} EXCLUDE_FROM_ALL ${source}.cpp) + TARGET_LINK_LIBRARIES(${source} affa) + ELSEIF(${source} STREQUAL boolops-cgal) + + ELSE(${source} STREQUAL aa) + ADD_EXECUTABLE(${source} ${source}.cpp) + ENDIF(${source} STREQUAL aa) + TARGET_LINK_LIBRARIES(${source} toy-2 2Geom::2geom ${GTK3_LIBRARIES} ) + ENDFOREACH(source) +ENDIF(2GEOM_TOYS) + diff --git a/src/toys/aa.cpp b/src/toys/aa.cpp new file mode 100644 index 0000000..8b852a2 --- /dev/null +++ b/src/toys/aa.cpp @@ -0,0 +1,520 @@ +#include <2geom/convex-hull.h> +#include <2geom/d2.h> +#include <2geom/geom.h> +#include <2geom/numeric/linear_system.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <aa.h> +#include <complex> +#include <algorithm> +#include <optional> + +using std::vector; +using namespace Geom; +using namespace std; + +//Geom::Rect zoom(Geom::Rect r, Geom::Point p, double s) { +// return p + (r - p)*s; +//} + +typedef std::complex<AAF> CAAF; + +struct PtLexCmp{ + bool operator()(const Point &a, const Point &b) { + return (a[0] < b[0]) || ((a[0] == b[0]) and (a[1] < b[1])); + } +}; + +void draw_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) { + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + cairo_move_to(cr, (*ls)[0]); + cairo_line_to(cr, (*ls)[1]); + cairo_stroke(cr); + + } +} + +OptRect tighten(Rect &r, Point n, Interval lu) { + vector<Geom::Point> result; + Point resultp; + for(int i = 0; i < 4; i++) { + Point cnr = r.corner(i); + double z = dot(cnr, n); + if((z > lu[0]) and (z < lu[1])) + result.push_back(cnr); + } + for(int i = 0; i < 2; i++) { + double c = lu[i]; + + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + result.push_back((*ls)[0]); + result.push_back((*ls)[1]); + } + } + if(result.size() < 2) + return OptRect(); + Rect nr(result[0], result[1]); + for(size_t i = 2; i < result.size(); i++) { + nr.expandTo(result[i]); + } + return intersect(nr, r); +} + +AAF ls_sample_based(AAF x, vector<Point> pts) { + NL::Matrix m(pts.size(), 2); + NL::Vector v(pts.size()); + NL::LinearSystem ls(m, v); + + m.set_all(0); + v.set_all(0); + for (unsigned int k = 0; k < pts.size(); ++k) + { + m(k,0) += pts[k][0]; + m(k,1) += 1; + //std::cout << pts[k] << " "; + + v[k] += pts[k][1]; + //v[1] += pts[k][1]; + //v[2] += y2; + } + + ls.SV_solve(); + + double A = ls.solution()[0]; + double B = ls.solution()[1]; + // Ax + B = y + Interval bnd(0,0); + for (unsigned int k = 0; k < pts.size(); ++k) + { + bnd.extendTo(A*pts[k][0]+B - pts[k][1]); + } + //std::cout << A << "," << B << std::endl; + return AAF(x, A, B, bnd.extent(), + x.special); +} + +AAF md_sample_based(AAF x, vector<Point> pts) { + Geom::ConvexHull ch1(pts); + Point a, b, c; + double dia = ch1.narrowest_diameter(a, b, c); + Point db = c-b; + double A = db[1]/db[0]; + Point aa = db*(dot(db, a-b)/dot(db,db))+b; + Point mid = (a+aa)/2; + double B = mid[1] - A*mid[0]; + double dB = (a[1] - A*a[0]) - B; + // Ax + B = y + std::cout << A << "," << B << std::endl; + return AAF(x, A, B, dB, + x.special); +} + +AAF atan_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + + const double ea = atan(a); + const double eb = atan(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + double xs = sqrt(1/alpha-1); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,atan(xs))); + xs = -xs; + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,atan(xs))); + + return md_sample_based(x, pts); +} + +AAF log_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + AAF_TYPE type; + if(a > 0) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + + const double ea = log(a); + const double eb = log(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // dlog(xs) = alpha + double xs = 1/(alpha); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,log(xs))); + + return md_sample_based(x, pts); +} + +AAF exp_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + + const double ea = exp(a); + const double eb = exp(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // dexp(xs) = alpha + double xs = log(alpha); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,exp(xs))); + + return md_sample_based(x, pts); +} + +AAF pow_sample_based(AAF x, double p) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + AAF_TYPE type; + if(a >= 0) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + + const double ea = pow(a, p); + const double eb = pow(b, p); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // d(xs^p) = alpha + // p xs^(p-1) = alpha + // xs = (alpha/p)^(1-p) + double xs = pow(alpha/p, 1./(p-1)); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,pow(xs, p))); + xs = -xs; + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,pow(xs, p))); + + return md_sample_based(x, pts); +} + +Point origin; +double scale=100; + +AAF trial_eval(AAF x, AAF y) { + x = x-origin[0]; + y = y-origin[1]; + + x = x/scale; + y = y/scale; + + return x*x -y*y + -6*x +10*y-16; + return -y + log(sqrt(x))/log(x); + return y*y - x*(x-1)*(x+1); + + //return x*x - 1; + //return y - pow(x,3); + //return y - pow_sample_based(x,2.5); + //return y - log_sample_based(x); + //return y - log(x); + //return y - exp_sample_based(x*log(x)); + //return y - sqrt(sin(x)); + //return sqrt(y)*x - sqrt(x) - y - 1; + //return y-1/x; + //return exp(x)-y; + //return sin(x)-y; + //return exp_sample_based(x)-y; + //return atan(x)-y; + //return atan_sample_based(x)-y; + //return atanh(x)-y; + //return x*y; + //return 4*x+3*y-1; + //return x*x + y*y - 1; + //return sin(x*y) + cos(pow(x, 3)) - atan(x); + //return pow((x*x + y*y), 2) - (x*x-y*y); + return 4*(2*y-4*x)*(2*y+4*x-16)-16*y*y; + return pow((x*x + y*y), 2) - (x*x-y*y); + //return pow(x,3) - 3*x*x - 3*y*y; + return (x*x + y*y-1)*((x-1)*(x-1)+y*y-1); + //return x*x-y; + //return (x*x*x-y*x)*sin(x) + (x-y*y)*cos(y)-0.5; +} + +AAF xaxis(AAF x, AAF y) { + y = y-origin[1]; + y = y/scale; + return y; +} + +AAF xaxis2(AAF x, AAF y) { + y = y-origin[1]; + y = y/scale; + return y-4; +} + +AAF yaxis(AAF x, AAF y) { + x = x-origin[0]; + x = x/scale; + return x; +} + +class ConvexTest: public Toy { +public: + PointSetHandle test_window; + PointSetHandle samples; + PointHandle orig_handle; + ConvexTest () { + toggles.push_back(Toggle("Show trials", false)); + handles.push_back(&test_window); + handles.push_back(&samples); + handles.push_back(&orig_handle); + orig_handle.pos = Point(300,300); + test_window.push_back(Point(100,100)); + test_window.push_back(Point(200,200)); + for(int i = 0; i < 0; i++) { + samples.push_back(Point(i*100, i*100+25)); + } + } + int iters; + int splits[4]; + bool show_splits; + std::vector<Toggle> toggles; + AAF (*eval)(AAF, AAF); + Geom::Rect view; + void recursive_implicit(Rect r, cairo_t*cr, double w) { + if(show_splits) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + /*if(f.is_partial()) + cairo_set_source_rgba(cr, 1, 0, 1, 0.25); + else*/ + cairo_set_source_rgba(cr, 0, 1, 0, 0.25); + cairo_rectangle(cr, r); + cairo_stroke(cr); + cairo_restore(cr); + } + iters++; + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //assert(x.rad() > 0); + //assert(y.rad() > 0); + AAF f = (*eval)(x, y); + // pivot + double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if(ivl.extent() < 0.5*L2(n)) { + draw_line_in_rect(cr, r, n, ivl.middle()); + return; + } + if(!f.is_partial() and f.is_indeterminate()) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + if(f.is_infinite()) { + cairo_set_source_rgb(cr, 1, 0.5, 0.5); + } else if(f.is_nan()) { + cairo_set_source_rgb(cr, 1, 1, 0); + } else { + cairo_set_source_rgb(cr, 1, 0, 0); + } + cairo_rectangle(cr, r); + if(show_splits) { + cairo_stroke(cr); + } else { + cairo_fill(cr); + } + cairo_restore(cr); + return; + } + + if((r.width() > w) or (r.height()>w)) { + if(f.straddles_zero()) { + // Three possibilities: + // 1) the trim operation buys us enough that we should just iterate + Point c = r.midpoint(); + Rect oldr = r; + if(out) + r = *out; + if(1 && out && (r.area() < oldr.area()*0.25)) { + splits[0] ++; + recursive_implicit(r, cr, w); + // 2) one dimension is significantly smaller + } else if(1 && (r[1].extent() < oldr[1].extent()*0.5)) { + splits[1]++; + recursive_implicit(Rect(Interval(r.left(), r.right()), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(r.left(), r.right()), + Interval(c[1], r.bottom())), cr,w); + } else if(1 && (r[0].extent() < oldr[0].extent()*0.5)) { + splits[2]++; + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(r.top(), r.bottom())), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(r.top(), r.bottom())), cr,w); + // 3) to ensure progress we must do a four way split + } else { + splits[3]++; + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(c[1], r.bottom())), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(c[1], r.bottom())), cr,w); + } + } + } else { + } + } + + void key_hit(GdkEventKey *e) override { + if(e->keyval == 'w') toggles[0].toggle(); else + if(e->keyval == 'a') toggles[1].toggle(); else + if(e->keyval == 'q') toggles[2].toggle(); else + if(e->keyval == 's') toggles[3].toggle(); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + void scroll(GdkEventScroll* e) override { + if (e->direction == GDK_SCROLL_UP) { + scale /= 1.2; + } else if (e->direction == GDK_SCROLL_DOWN) { + scale *= 1.2; + } + redraw(); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + origin = orig_handle.pos; + if(1) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgb(cr, 0.5, 0.5, 1); + eval = xaxis; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + eval = xaxis2; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + eval = yaxis; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + cairo_restore(cr); + iters = 0; + for(int & split : splits) + split = 0; + show_splits = toggles[0].on; + eval = trial_eval; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + for(int split : splits) + *notify << split << " + "; + *notify << " = " << iters; + } + if(1) { + Rect r(test_window.pts[0], test_window.pts[1]); + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //AAF f = md_sample_based(x, samples.pts)-y; + if(0) { + x = x-500; + y = y-300; + x = x/200; + y = y/200; + AAF f = atan_sample_based(x)-y; + cout << f << endl; + } + AAF f = (*eval)(x, y); + double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + //cout << d << endl; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if(out) + cairo_rectangle(cr, *out); + cairo_rectangle(cr, r); + draw_line_in_rect(cr, r, n, ivl.min()); + cairo_stroke(cr); + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgb(cr, 0.5, 0.5, 0); + draw_line_in_rect(cr, r, n, ivl.middle()); + cairo_restore(cr); + draw_line_in_rect(cr, r, n, ivl.max()); + cairo_stroke(cr); + } + if(0) { + Geom::ConvexHull gm(samples.pts); + cairo_convex_hull(cr, gm); + cairo_stroke(cr); + Point a, b, c; + double dia = gm.narrowest_diameter(a, b, c); + cairo_save(cr); + cairo_set_line_width(cr, 2); + cairo_set_source_rgba(cr, 1, 0, 0, 0.5); + cairo_move_to(cr, b); + cairo_line_to(cr, c); + cairo_move_to(cr, a); + cairo_line_to(cr, (c-b)*dot(a-b, c-b)/dot(c-b,c-b)+b); + cairo_stroke(cr); + //std::cout << a << ", " << b << ", " << c << ": " << dia << "\n"; + cairo_restore(cr); + } + Toy::draw(cr, notify, width, height, save,timer_stream); + Point d(25,25); + toggles[0].bounds = Rect(Point(10, height-80)+d, + Point(10+120, height-80+d[1])+d); + + draw_toggles(cr, toggles); + } + +}; + +int main(int argc, char **argv) { + init(argc, argv, new ConvexTest()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/arc-bez.cpp b/src/toys/arc-bez.cpp new file mode 100644 index 0000000..4e9dace --- /dev/null +++ b/src/toys/arc-bez.cpp @@ -0,0 +1,129 @@ +#include <2geom/d2.h> + +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +Piecewise<SBasis> +arcLengthSb2(Piecewise<D2<SBasis> > const &M, double /*tol*/){ + Piecewise<D2<SBasis> > dM = derivative(M); + Piecewise<SBasis> length = integral(dot(dM, unitVector(dM))); + length-=length.segs.front().at0(); + return length; +} + + + +class ArcBez: public Toy { + PointSetHandle bez_handle; +public: + ArcBez() { + for(int i = 0; i < 6; i++) + bez_handle.push_back(uniform()*400, uniform()*400); + handles.push_back(&bez_handle); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timing_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + D2<SBasis> B = bez_handle.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0.25, 0.5, 0, 0.8); + + double tol = 0.01; + bool time_operations = true; + if(time_operations) { + std::string units_string("us"); + Timer tm; + tm.ask_for_timeslice(); + tm.start(); + Piecewise<SBasis> als = arcLengthSb(B, tol); + Timer::Time als_time = tm.lap(); + *timing_stream << "arcLengthSb based " + << ", time = " << als_time + << units_string << std::endl; + + tm.start(); + Piecewise<SBasis> als2 = arcLengthSb2(Piecewise<D2<SBasis> >(B), 0.01); + Timer::Time als2_time = tm.lap(); + + *timing_stream << "arcLengthSb2 based " + << ", time = " << als2_time + << units_string << std::endl; + double abs_error = 0; + double integrating_arc_length = 0; + tm.start(); + length_integrating(B, integrating_arc_length, abs_error, 1e-10); + Timer::Time li_time = tm.lap(); + + *timing_stream << "gsl integrating " + << ", time = " << li_time + << units_string << std::endl; + } + Piecewise<SBasis> als = arcLengthSb(B, tol); + Piecewise<SBasis> als2 = arcLengthSb2(Piecewise<D2<SBasis> >(B), 0.01); + + cairo_d2_pw_sb(cr, D2<Piecewise<SBasis> >(Piecewise<SBasis>(SBasis(Linear(0, width))) , Piecewise<SBasis>(Linear(height-5)) - Piecewise<SBasis>(als)) ); + + double abs_error = 0; + double integrating_arc_length = 0; + length_integrating(B, integrating_arc_length, abs_error, 1e-10); + *notify << "arc length = " << integrating_arc_length << "; abs error = " << abs_error << std::endl; + double als_arc_length = als.segs.back().at1(); + *notify << "arc length = " << als_arc_length << "; error = " << als_arc_length - integrating_arc_length << std::endl; + double als_arc_length2 = als2.segs.back().at1(); + *notify << "arc length2 = " << als_arc_length2 << "; error = " << als_arc_length2 - integrating_arc_length << std::endl; + + { + double err = fabs(als_arc_length - integrating_arc_length); + double scale = 10./err; + Piecewise<D2<SBasis> > dM = derivative(Piecewise<D2<SBasis> >(B)); + Piecewise<SBasis> ddM = dot(dM,dM); + Piecewise<SBasis> dMlength = sqrt(ddM,tol,3); + double plot_width = (width - 200); + + Point org(100,height - 200); + cairo_move_to(cr, org); + for(double t = 0; t < 1; t += 0.01) { + cairo_line_to(cr, org + Point(t*plot_width, scale*(sqrt(ddM.valueAt(t)) - dMlength.valueAt(t)))); + } + cairo_move_to(cr, org); + cairo_line_to(cr, org+Point(plot_width, 0)); + cairo_stroke(cr); + + draw_number(cr, org, scale); + + } + + + Toy::draw(cr, notify, width, height, save,timing_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new ArcBez()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/arc-length-param.cpp b/src/toys/arc-length-param.cpp new file mode 100644 index 0000000..2c7f3c9 --- /dev/null +++ b/src/toys/arc-length-param.cpp @@ -0,0 +1,101 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double space=10){ + //double dt=(M[0].cuts.back()-M[0].cuts.front())/space; + Piecewise<D2<SBasis> > Mperp = rot90(derivative(M)) * 2; + for( double t = M.cuts.front(); t < M.cuts.back(); t += space) { + Point pos = M(t), perp = Mperp(t); + draw_line_seg(cr, pos + perp, pos - perp); + } + cairo_pw_d2_sb(cr, M); + cairo_stroke(cr); +} + +#define SIZE 4 + +class LengthTester: public Toy { +public: + PointSetHandle b1_handle; + PointSetHandle b2_handle; + void draw(cairo_t *cr, + std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override { + + D2<SBasis> B1 = b1_handle.asBezier(); + D2<SBasis> B2 = b2_handle.asBezier(); + Piecewise<D2<SBasis> >B; + B.concat(Piecewise<D2<SBasis> >(B1)); + B.concat(Piecewise<D2<SBasis> >(B2)); + +// testing fuse_nearby_ends + std::vector< Piecewise<D2<SBasis> > > pieces; + pieces = fuse_nearby_ends(split_at_discontinuities(B),9); + Piecewise<D2<SBasis> > C; + for (auto & piece : pieces){ + C.concat(piece); + } +// testing fuse_nearby_ends + + cairo_set_line_width (cr, .5); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + //cairo_d2_sb(cr, B1); + cairo_pw_d2_sb(cr, C); + //cairo_pw_d2_sb(cr, B); + cairo_stroke(cr); + + Timer tm; + tm.ask_for_timeslice(); + tm.start(); + + Piecewise<D2<SBasis> > uniform_B = arc_length_parametrization(B); + Timer::Time als_time = tm.lap(); + *timer_stream << "arc_length_parametrization, time = " << als_time << std::endl; + + cairo_set_source_rgba (cr, 0., 0., 0.9, 1); + dot_plot(cr,uniform_B); + cairo_stroke(cr); + *notify << "pieces = " << uniform_B.size() << ";\n"; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + LengthTester(){ + for(int i = 0; i < SIZE; i++) { + b1_handle.push_back(150+uniform()*300,150+uniform()*300); + b2_handle.push_back(150+uniform()*300,150+uniform()*300); + } + b1_handle.pts[0] = Geom::Point(150,150); + b1_handle.pts[1] = Geom::Point(150,150); + b1_handle.pts[2] = Geom::Point(150,450); + b1_handle.pts[3] = Geom::Point(450,150); + handles.push_back(&b1_handle); + handles.push_back(&b2_handle); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new LengthTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99: diff --git a/src/toys/auto-cross.cpp b/src/toys/auto-cross.cpp new file mode 100644 index 0000000..00b5b25 --- /dev/null +++ b/src/toys/auto-cross.cpp @@ -0,0 +1,321 @@ +/* @brief + * A toy for playing around with Path::intersectSelf(). + * + * Authors: + * Rafał Siejakowski <rs@rs-math.net> + * + * Copyright 2022 the Authors. + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <toys/toy-framework-2.h> +#include <2geom/path.h> +#include <2geom/elliptical-arc.h> +#include <2geom/svg-path-parser.h> + +using namespace Geom; +using Color = uint32_t; + +Color const RED = 0x80000000; +Color const GREEN = 0x00800000; +Color const BROWN = 0x90500000; +Color const BLUE = 0x0000ff00; +Color const BLACK = 0x00000000; + +static void set_cairo_rgb(cairo_t *c, Color rgb) +{ + cairo_set_source_rgba(c, (double)((rgb & 0xFF000000) >> 24) / 255.0, + (double)((rgb & 0x00FF0000) >> 16) / 255.0, + (double)((rgb & 0x0000FF00) >> 8) / 255.0, + 1.0); +} + +static void write_text(cairo_t *c, const char *text, Point const &position, Color color) +{ + cairo_move_to(c, position); + cairo_set_font_size(c, 12); + set_cairo_rgb(c, color); + cairo_show_text(c, text); +} + +static std::string format_point(Point const &pt) +{ + std::ostringstream ss; + ss.precision(4); + ss << pt; + return ss.str(); +} + +static EllipticalArc random_arc(Point from, Point to) +{ + double const dist = distance(from, to); + auto angle = atan2(to - from); + bool sweep = std::abs(angle) > M_PI_2; + angle *= 2; + angle = std::fmod(angle, 2.0 * M_PI); + return EllipticalArc(from, Point(0.5 * dist, 2.0 * dist), angle, false, sweep, to); +} + +class Item +{ +private: + Path _path; + Color _color; + std::string _d; + +public: + Item(Color color) + : _color{color} + {} + + void setPath(Path &&new_path) + { + _path = std::forward<Path>(new_path); + std::ostringstream oss; + oss << _path; + _d = oss.str(); + } + + void draw(cairo_t *cr) const + { + cairo_set_line_width(cr, 2); + set_cairo_rgb(cr, _color); + cairo_path(cr, _path); + cairo_stroke(cr); + _drawBezierTangents(cr); + _drawSelfIntersections(cr); + } + + void write(cairo_t *cr, Point const &pos) const + { + write_text(cr, _d.c_str(), pos, _color); + } + + std::string const& getSVGD() const { return _d; } + +private: + void _drawBezierTangents(cairo_t *c) const + { + cairo_set_line_width(c, 1); + set_cairo_rgb(c, 0x0000b000); + // Draw tangents for Beziers: + for (auto const &curve : _path) { + if (auto const *bezier = dynamic_cast<BezierCurve const *>(&curve)) { + if (bezier->order() > 1) { + auto points = bezier->controlPoints(); + cairo_move_to(c, points[0]); + cairo_line_to(c, points[1]); + cairo_stroke(c); + cairo_move_to(c, points.back()); + cairo_line_to(c, points[points.size() - 2]); + cairo_stroke(c); + } + } + } + } + + void _drawSelfIntersections(cairo_t *cr) const + { + set_cairo_rgb(cr, BLACK); + for (auto const &xing : _path.intersectSelf()) { + draw_cross(cr, xing.point()); + auto const coords = format_point(xing.point()); + write_text(cr, coords.c_str(), xing.point() + Point(8, -8), BLACK); + } + } +}; + +class AutoCross : public Toy +{ +public: + AutoCross() + : items{Item(RED), Item(GREEN), Item(BROWN)} + { + bezier_handles.pts = { {200, 400}, {100, 300}, {300, 400}, {300, 300}, {450, 300}, {500, 500}, {400, 400} }; + elliptical_handles.pts = { {500, 200}, {700, 400}, {600, 500} }; + mixed_handles.pts = { {100, 600}, {120, 690}, {300, 650}, {330, 600}, {500, 800} }; + handles.push_back(&bezier_handles); + handles.push_back(&elliptical_handles); + handles.push_back(&mixed_handles); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, + std::ostringstream *timer_stream) override + { + if (crashed) { + draw_error(cr, width, height); + } else { + try { + draw_impl(cr, width, height); + } catch (Exception &e) { + error = e.what(); + handles.clear(); + crashed = true; + } + } + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + void key_hit(GdkEventKey *ev) override + { + if (ev->keyval == GDK_KEY_space) { + print_path_d(); + } else if ((ev->keyval == GDK_KEY_V || ev->keyval == GDK_KEY_v) && (ev->state & GDK_CONTROL_MASK)) { + paste_d(); + } + + } + +private: + std::string error; + std::vector<Item> items; + PointSetHandle bezier_handles, elliptical_handles, mixed_handles; + bool crashed = false; + + void paste_d() + { + auto *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + auto *text = gtk_clipboard_wait_for_text(clipboard); + if (!text) { + return; + } + PathVector pv; + try { + pv = parse_svg_path(text); + } catch (SVGPathParseError &error) { + std::cerr << "Error pasting path d: " << error.what() << std::endl; + return; + } + if (pv.empty()) { + return; + } + Item paste_item{RED}; // TODO: cycle through a color palette. + paste_item.setPath(std::move(pv[0])); + items.push_back(paste_item); + redraw(); + } + + void print_path_d() + { + std::cout << "Path snapshots:\n"; + for (auto it = items.rbegin(); it != items.rend(); ++it) { + std::cout << it->getSVGD() << '\n'; + } + } + + void refresh_geometry() + { + // Construct the 2-segment Bézier path + auto const &cp = bezier_handles.pts; + Path bezier; + bezier.append(BezierCurveN<3>(cp[0].round(), cp[1].round(), cp[2].round(), cp[3].round())); + bezier.append(BezierCurveN<3>(cp[3].round(), cp[4].round(), cp[5].round(), cp[6].round())); + items[0].setPath(std::move(bezier)); + + // Construct the elliptical arcs + auto const &ae = elliptical_handles.pts; + Path elliptical; + elliptical.append(random_arc(ae[0], ae[1])); + elliptical.append(random_arc(ae[1], ae[2])); + items[1].setPath(std::move(elliptical)); + + // Construct a mixed path + auto const &mh = mixed_handles.pts; + Path mixed; + mixed.append(BezierCurveN<3>(mh[0], mh[1], mh[2], mh[3])); + mixed.append(random_arc(mh[3], mh[4])); + mixed.close(); + items[2].setPath(std::move(mixed)); + } + + void draw_impl(cairo_t *cr, int width, int height) + { + refresh_geometry(); + write_title(cr); + + auto text_pos = Point(20, height - 20); + for (auto const &item : items) { + item.draw(cr); + item.write(cr, text_pos); + text_pos -= Point(0, 20); + } + } + + void write_title(cairo_t *c) + { + cairo_move_to(c, 10, 40); + cairo_set_font_size(c, 30); + set_cairo_rgb(c, 0x0); + cairo_show_text(c, "Self-intersection of paths in lib2geom!"); + cairo_set_font_size(c, 14); + cairo_move_to(c, 10, 60); + cairo_show_text(c, "[Space]: Print SVG 'd' attributes to stdout"); + cairo_move_to(c, 10, 80); + cairo_show_text(c, "[Ctrl-V]: Paste a 'd' attribute from clipboard"); + } + + void draw_error(cairo_t *cr, int width, int height) + { + auto center = Point(0.5 * (double)width, 0.5 * (double)height); + cairo_move_to(cr, center + Point(-90, -100)); + cairo_line_to(cr, center + Point(100, 90)); + cairo_line_to(cr, center + Point(90, 100)); + cairo_line_to(cr, center + Point(-100, -90)); + cairo_close_path(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_fill(cr); + + cairo_move_to(cr, center + Point(90, -100)); + cairo_line_to(cr, center + Point(100, -90)); + cairo_line_to(cr, center + Point(-90, 100)); + cairo_line_to(cr, center + Point(-100, 90)); + cairo_close_path(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_fill(cr); + + cairo_move_to(cr, center + Point(-90, 120)); + cairo_show_text(cr, "Sorry, your toy has just broken :-/"); + cairo_move_to(cr, Point(10, center[Y] + 150)); + cairo_show_text(cr, error.c_str()); + } +}; + +int main(int argc, char **argv) +{ + auto toy = AutoCross(); + init(argc, argv, &toy, 800, 800); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
\ No newline at end of file diff --git a/src/toys/boolops-toy.cpp b/src/toys/boolops-toy.cpp new file mode 100644 index 0000000..389fdc3 --- /dev/null +++ b/src/toys/boolops-toy.cpp @@ -0,0 +1,242 @@ +#include <2geom/d2.h> +#include <2geom/intersection-graph.h> +#include <2geom/path.h> +#include <2geom/sbasis.h> +#include <2geom/svg-path-parser.h> +#include <2geom/transforms.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <algorithm> +#include <cstdlib> + +using namespace Geom; + +class BoolOps : public Toy { + PathVector as, bs; + Line ah, bh; + PointHandle path_handles[4]; + std::vector<Toggle> togs; + bool path_handles_inited; + +public: + BoolOps() + : path_handles_inited(false) + {} + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + if (!path_handles_inited) { + Rect vp(Point(10,10), Point(width-10, height-10)); + setup_path_handles(vp); + } + + Line aht(path_handles[0].pos, path_handles[1].pos); + Line bht(path_handles[2].pos, path_handles[3].pos); + + PathVector ast = as * ah.transformTo(aht); + PathVector bst = bs * bh.transformTo(bht); + + Timer tm; + tm.start(); + + PathIntersectionGraph pig(ast, bst); + std::vector<Point> dix, ix, wpoints; + ix = pig.intersectionPoints(); + dix = pig.intersectionPoints(true); + wpoints = pig.windingPoints(); + PathVector result, f_in, f_out; + + if (pig.valid()) { + if (togs[0].on && !togs[1].on && !togs[2].on) { + result = pig.getAminusB(); + } + if (!togs[0].on && togs[1].on && !togs[2].on) { + result = pig.getIntersection(); + } + if (!togs[0].on && !togs[1].on && togs[2].on) { + result = pig.getBminusA(); + } + if (togs[0].on && togs[1].on && !togs[2].on) { + result = ast; + } + if (togs[0].on && !togs[1].on && togs[2].on) { + result = pig.getXOR(); + } + if (!togs[0].on && togs[1].on && togs[2].on) { + result = bst; + } + if (togs[0].on && togs[1].on && togs[2].on) { + result = pig.getUnion(); + } + } + + if (togs[5].on || togs[6].on) { + pig.fragments(f_in, f_out); + } + Timer::Time boolop_time = tm.lap(); + + cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); + + cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); + cairo_path(cr, result); + cairo_fill(cr); + + cairo_set_line_width(cr, 1); + cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); + cairo_path(cr, ast); + + cairo_stroke(cr); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_path(cr, bst); + cairo_stroke(cr); + + if (togs[5].on) { + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_path(cr, f_in); + cairo_stroke(cr); + } + if (togs[6].on) { + cairo_set_source_rgb(cr, 0, 0, 1); + cairo_path(cr, f_out); + cairo_stroke(cr); + } + + //cairo_set_line_width(cr, 1); + + if (togs[7].on) { + cairo_set_source_rgb(cr, 0, 1, 1); + for (auto & wpoint : wpoints) { + draw_handle(cr, wpoint); + } + cairo_stroke(cr); + } + + if (togs[3].on) { + cairo_set_source_rgb(cr, 0, 1, 0); + for (auto & i : ix) { + draw_handle(cr, i); + } + cairo_stroke(cr); + } + + if (togs[4].on) { + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : dix) { + draw_handle(cr, i); + } + cairo_stroke(cr); + } + + + double x = width - 90, y = height - 40, y2 = height - 80; + Point p(x, y), p2(x, y2), dpoint(25,25), xo(25,0); + togs[0].bounds = Rect(p, p + dpoint); + togs[1].bounds = Rect(p + xo, p + xo + dpoint); + togs[2].bounds = Rect(p + 2*xo, p + 2*xo + dpoint); + + togs[3].bounds = Rect(p2 - 2*xo, p2 - 2*xo + dpoint); + togs[4].bounds = Rect(p2 - xo, p2 - xo + dpoint); + togs[5].bounds = Rect(p2, p2 + dpoint); + togs[6].bounds = Rect(p2 + xo, p2 + xo + dpoint); + togs[7].bounds = Rect(p2 + 2*xo, p2 + 2*xo + dpoint); + draw_toggles(cr, togs); + + *notify << ix.size() << " intersections"; + if (dix.size() != 0) { + *notify << " + " << dix.size() << " defective"; + } + if (pig.valid()) { + *notify << "\nboolop time: " << boolop_time << std::endl; + } else { + *notify << "\nboolop failed, time: " << boolop_time << std::endl; + } + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void mouse_pressed(GdkEventButton* e) override { + toggle_events(togs, e); + Toy::mouse_pressed(e); + } + + void first_time(int argc, char** argv) override { + const char *path_a_name="svgd/winding.svgd"; + const char *path_b_name="svgd/star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + if(argc > 2) + path_b_name = argv[2]; + + as = read_svgd(path_a_name); + bs = read_svgd(path_b_name); + + OptRect abox = as.boundsExact(); + OptRect bbox = bs.boundsExact(); + + if (!abox) { + std::clog << "Error: path A is empty" << std::endl; + } + if (!bbox) { + std::clog << "Error: path B is empty" << std::endl; + } + if (!abox || !bbox) { + std::exit(1); + } + + std::vector<Point> anodes = as.nodes(); + std::vector<Point> bnodes = bs.nodes(); + + typedef std::vector<Point>::iterator Iter; + std::pair<Iter, Iter> apts = + std::minmax_element(anodes.begin(), anodes.end(), Point::LexLess<Y>()); + std::pair<Iter, Iter> bpts = + std::minmax_element(bnodes.begin(), bnodes.end(), Point::LexLess<Y>()); + + ah = Line(*apts.first, *apts.second); + bh = Line(*bpts.first, *bpts.second); + + togs.emplace_back("R", true); + togs.emplace_back("&", false); + togs.emplace_back("B", false); + + togs.emplace_back("X", true); + togs.emplace_back("D", true); + togs.emplace_back("I", false); + togs.emplace_back("O", false); + togs.emplace_back("W", false); + } + + void setup_path_handles(Rect const &viewport) { + Line aht = ah * as.boundsExact()->transformTo(viewport, Aspect(ALIGN_XMID_YMID)); + Line bht = bh * bs.boundsExact()->transformTo(viewport, Aspect(ALIGN_XMID_YMID)); + + path_handles[0] = PointHandle(aht.initialPoint()); + path_handles[1] = PointHandle(aht.finalPoint()); + path_handles[2] = PointHandle(bht.initialPoint()); + path_handles[3] = PointHandle(bht.finalPoint()); + + for (auto & path_handle : path_handles) { + handles.push_back(&path_handle); + } + path_handles_inited = true; + } + //virtual bool should_draw_numbers() {return false;} +}; + +int main(int argc, char **argv) { + init(argc, argv, new BoolOps()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/bound-path.cpp b/src/toys/bound-path.cpp new file mode 100644 index 0000000..39ce2a1 --- /dev/null +++ b/src/toys/bound-path.cpp @@ -0,0 +1,289 @@ +/* + * Bounds Path and PathVector + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +std::string option_formatter(double x) +{ + if (x == 0.0) + return std::string("CURVE"); + if (x == 1.0) + return std::string("PATH"); + if (x == 2.0) + return std::string("PATHVECTOR"); + + return std::string(""); +} + +class BoundsPath : public Toy +{ + enum { CURVE = 0, PATH, PATHVECTOR }; + enum { FAST = 0, EXACT = 1 }; + + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + m_selection_kind = (unsigned int) (sliders[0].value()); + + for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i) + { + cairo_set_source_rgba(cr, 0.0, 0.4*(i+1), 0.8/(i+1), 1.0); + for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j) + { + m_pathvector_coll[i][j].clear(); + for (unsigned int k = 0; k < m_curves_per_path; ++k) + { + PointSetHandle psh; + psh.pts.resize(m_handles_per_curve); + for (unsigned int h = 0; h < m_handles_per_curve; ++h) + { + unsigned int kk = k * (m_handles_per_curve-1) + h; + psh.pts[h] = m_pathvector_coll_handles[i][j].pts[kk]; + } + m_pathvector_coll[i][j].append(psh.asBezier()); + } + cairo_path(cr, m_pathvector_coll[i][j]); + } + cairo_stroke(cr); + } + + + Rect bound; + if ( (m_selection_kind == CURVE) && (m_selected_curve != -1) ) + { + const Curve & curve = m_pathvector_coll[m_selected_pathvector][m_selected_path][m_selected_curve]; + bound = toggles[0].on ? curve.boundsExact() + : curve.boundsFast(); + } + else if ( (m_selection_kind == PATH) && (m_selected_path != -1) ) + { + const Path & path = m_pathvector_coll[m_selected_pathvector][m_selected_path]; + bound = toggles[0].on ? *path.boundsExact() + : *path.boundsFast(); + } + else if ( (m_selection_kind == PATHVECTOR) && (m_selected_pathvector != -1) ) + { + const PathVector & pathvector = m_pathvector_coll[m_selected_pathvector]; + bound = toggles[0].on ? *bounds_exact(pathvector) + : *bounds_fast(pathvector); + } + + cairo_set_source_rgba(cr, 0.5, 0.0, 0.0, 1.0); + cairo_set_line_width (cr, 0.4); + cairo_rectangle(cr, bound.left(), bound.top(), bound.width(), bound.height()); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + + void mouse_pressed(GdkEventButton* e) override + { + Point pos(e->x, e->y); + double d, t; + double dist = 1e10; + Rect bound; + m_selected_pathvector = -1; + m_selected_path = -1; + m_selected_curve = -1; + if (m_selection_kind == CURVE) + { + for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i) + { + for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j) + { + for ( unsigned int k = 0; k < m_pathvector_coll[i][j].size(); ++k) + { + const Curve & curve = m_pathvector_coll[i][j][k]; + bound = toggles[0].on ? curve.boundsExact() + : curve.boundsFast(); + d = distanceSq(pos, bound); + if ( are_near(d, 0) ) + { + t = curve.nearestTime(pos); + d = distanceSq(pos, curve.pointAt(t)); + if (d < dist) + { + dist = d; + m_selected_pathvector = i; + m_selected_path = j; + m_selected_curve = k; + } + } + } + } + } + //std::cerr << "m_selected_path = " << m_selected_path << std::endl; + //std::cerr << "m_selected_curve = " << m_selected_curve << std::endl; + } + else if (m_selection_kind == PATH) + { + for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i) + { + for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j) + { + const Path & path = m_pathvector_coll[i][j]; + bound = toggles[0].on ? *path.boundsExact() + : *path.boundsFast(); + d = distanceSq(pos, bound); + if ( are_near(d, 0) ) + { + t = path.nearestTime(pos).asFlatTime(); + d = distanceSq(pos, path.pointAt(t)); + if (d < dist) + { + dist = d; + m_selected_pathvector = i; + m_selected_path = j; + } + } + } + } + } + else if (m_selection_kind == PATHVECTOR) + { + for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i) + { + const PathVector & pathvector = m_pathvector_coll[i]; + bound = toggles[0].on ? *bounds_exact(pathvector) + : *bounds_fast(pathvector); + d = distanceSq(pos, bound); + if ( are_near(d, 0) ) + { + for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j) + { + const Path & path = m_pathvector_coll[i][j]; + t = path.nearestTime(pos).asFlatTime(); + d = distanceSq(pos, path.pointAt(t)); + if (d < dist) + { + dist = d; + m_selected_pathvector = i; + } + } + } + } + } + + Toy::mouse_pressed(e); + } + + public: + BoundsPath() + { + m_total_pathvectors = 2; + m_paths_per_vector = 2; + m_curves_per_path = 3; + m_handles_per_curve = 4; + + m_selection_kind = CURVE; + m_selected_pathvector = -1; + m_selected_path = -1; + m_selected_curve = -1; + m_handles_per_path = m_curves_per_path * (m_handles_per_curve-1) + 1; + + m_pathvector_coll_handles.resize(m_total_pathvectors); + m_pathvector_coll.resize(m_total_pathvectors); + for (unsigned int k = 0; k < m_total_pathvectors; ++k) + { + m_pathvector_coll_handles[k].resize(m_paths_per_vector); + m_pathvector_coll[k].resize(m_paths_per_vector); + for (unsigned int j = 0; j < m_paths_per_vector; ++j) + { + m_pathvector_coll_handles[k][j].pts.resize(m_handles_per_path); + handles.push_back(&(m_pathvector_coll_handles[k][j])); + for (unsigned int i = 0; i < m_handles_per_path; ++i) + { + m_pathvector_coll_handles[k][j].pts[i] + = Point(500*uniform() + 300*k, 300*uniform() + 80 + 200*j); + } + } + } + + sliders.emplace_back(0, 2, 1, 0, "selection type"); + sliders[0].geometry(Point(10, 20), 50, X); + sliders[0].formatter(&option_formatter); + + Rect toggle_bound(Point(300,20), Point(390, 45)); + toggles.emplace_back(toggle_bound, "fast/exact", EXACT); + + handles.push_back(&(sliders[0])); + handles.push_back(&(toggles[0])); + } + + + private: + unsigned int m_total_pathvectors; + unsigned int m_paths_per_vector; + unsigned int m_curves_per_path; + unsigned int m_handles_per_curve; + unsigned int m_handles_per_path; + std::vector<PathVector> m_pathvector_coll; + std::vector< std::vector<PointSetHandle> > m_pathvector_coll_handles; +// PathVector m_pathvector; +// std::vector<PointSetHandle> m_pathvector_handles; + int m_selected_curve; + int m_selected_path; + int m_selected_pathvector; + unsigned int m_selection_kind; + std::vector<Slider> sliders; + std::vector<Toggle> toggles; +}; + + +int main(int argc, char **argv) +{ + init( argc, argv, new BoundsPath(), 800, 600 ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/bounds-test.cpp b/src/toys/bounds-test.cpp new file mode 100644 index 0000000..f600162 --- /dev/null +++ b/src/toys/bounds-test.cpp @@ -0,0 +1,171 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){ + D2<SBasis> plot; + plot[0]=SBasis(Linear(150+a*300,150+b*300)); + plot[1]=B*(-vscale); + plot[1]+=300; + cairo_d2_sb(cr, plot); + cairo_stroke(cr); +} +static void plot_bar(cairo_t* cr, double height, double vscale=1,double a=0,double b=1){ + cairo_move_to(cr, Geom::Point(150+300*a,-height*vscale+300)); + cairo_line_to(cr, Geom::Point(150+300*b,-height*vscale+300)); + cairo_stroke(cr); +} + +class BoundsTester: public Toy { + unsigned size; + PointSetHandle hand; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + for (unsigned i=0;i<size;i++){ + hand.pts[i ][0]=150+15*(i-size); + hand.pts[i+size][0]=450+15*(i+1); + cairo_move_to(cr, Geom::Point(hand.pts[i ][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i ][0],450)); + cairo_move_to(cr, Geom::Point(hand.pts[i+size][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i+size][0],450)); + } + cairo_move_to(cr, Geom::Point(0,300)); + cairo_line_to(cr, Geom::Point(600,300)); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + + SBasis B(size, Linear()); + for (unsigned i=0;i<size;i++){ + B[i] = Linear(-(hand.pts[i ][1]-300)*std::pow(4.,(int)i), + -(hand.pts[i+size][1]-300)*std::pow(4.,(int)i) ); + } + B.normalize(); + plot(cr,B,1); + cairo_set_source_rgba (cr, 0., 0., 0.8, 1); + cairo_stroke(cr); + + Interval bnds = *bounds_local(B,Interval(0.,.5)); + plot_bar(cr,bnds.min(),1,.0,.5); + plot_bar(cr,bnds.max(),1,.0,.5); + cairo_set_source_rgba (cr, 0.4, 0., 0., 1); + cairo_stroke(cr); + bnds = *bounds_exact(B); + plot_bar(cr,bnds.min()); + plot_bar(cr,bnds.max()); + cairo_set_source_rgba (cr, 0.9, 0., 0., 1); + cairo_stroke(cr); + +/* +This is a multi-root test... +*/ + hand.pts[2*size ][0]=150; + hand.pts[2*size+1][0]=150; + hand.pts[2*size+2][0]=150; + hand.pts[2*size ][1]=std::max(hand.pts[2*size ][1],hand.pts[2*size+1][1]); + hand.pts[2*size+1][1]=std::max(hand.pts[2*size+1][1],hand.pts[2*size+2][1]); + vector<double> levels; + levels.push_back((300-hand.pts[2*size ][1])); + levels.push_back((300-hand.pts[2*size+1][1])); + levels.push_back((300-hand.pts[2*size+2][1])); + for (double level : levels) plot_bar(cr,level); + + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + + *notify<<"Use hand.pts to set the coefficients of the s-basis."<<std::endl; + + vector<double>my_roots; + +// cairo_set_source_rgba (cr, 0.9, 0., 0.8, 1); +// for (unsigned i=0;i<levels.size();i++){ +// my_roots.clear(); +// my_roots=roots(B-Linear(levels[i])); +// for(unsigned j=0;j<my_roots.size();j++){ +// draw_cross(cr,Point(150+300*my_roots[j],300-levels[i])); +// } +// } + +// cairo_set_source_rgba (cr, 0.9, 0., 0.8, 1); + + vector<vector<double> > sols=multi_roots(B,levels,.001,.001); + //map<double,unsigned> sols=multi_roots(B,levels); + //for(map<double,unsigned>::iterator sol=sols.begin();sol!=sols.end();sol++){ + // draw_handle(cr,Point(150+300*(*sol).first,300-levels[(*sol).second])); + //} + + for (unsigned i=0;i<sols.size();i++){ + for (unsigned j=0;j<sols[i].size();j++){ + draw_handle(cr,Point(150+300*sols[i][j],300-levels[i])); + } + } + cairo_set_source_rgba (cr, 0.9, 0., 0.8, 1); + +/* + + clock_t end_t; + unsigned iterations = 0; + + my_roots.clear(); + end_t = clock()+clock_t(0.1*CLOCKS_PER_SEC); + iterations = 0; + while(end_t > clock()) { + my_roots.clear(); + for (unsigned i=0;i<levels.size();i++){ + my_roots=roots(B-Linear(levels[i])); + } + iterations++; + } + *notify << 1000*0.1/iterations <<" ms = roots time"<< std::endl; + + sols.clear(); + end_t = clock()+clock_t(0.1*CLOCKS_PER_SEC); + iterations = 0; + while(end_t > clock()) { + sols.clear(); + sols=multi_roots(B,levels); + iterations++; + } + *notify << 1000*0.1/iterations <<" ms = multi roots time"<< std::endl; +*/ + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + BoundsTester(){ + size=5; + if(hand.pts.empty()) { + for(unsigned i = 0; i < 2*size; i++) + hand.pts.emplace_back(0,150+150+uniform()*300*0); + } + hand.pts.emplace_back(150,300+ 50+uniform()*100); + hand.pts.emplace_back(150,300- 50+uniform()*100); + hand.pts.emplace_back(150,300-150+uniform()*100); + handles.push_back(&hand); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new BoundsTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/box3d.cpp b/src/toys/box3d.cpp new file mode 100644 index 0000000..06d2e6a --- /dev/null +++ b/src/toys/box3d.cpp @@ -0,0 +1,153 @@ +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +class Box3d: public Toy { + Point orig; + + double tmat[3][4]; + double c[8][4]; + Point corners[8]; + + PointHandle origin_handle; + PointSetHandle vanishing_points_handles, axes_handles; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Geom::Point dir(1,-2); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + // draw vertical lines for the VP sliders and keep the sliders at their horizontal positions + draw_slider_lines (cr); + axes_handles.pts[0][0] = 30; + axes_handles.pts[1][0] = 45; + axes_handles.pts[2][0] = 60; + + /* create the transformation matrix for the map P^3 --> P^2 that has the following effect: + (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0) + (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1) + (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2) + (0 : 0 : 0 : 1) --> origin (= handle #3) + */ + for (int j = 0; j < 4; ++j) { + tmat[0][j] = vanishing_points_handles.pts[j][0]; + tmat[1][j] = vanishing_points_handles.pts[j][1]; + tmat[2][j] = 1; + } + + *notify << "Projection matrix:" << endl; + for (auto & i : tmat) { + for (double j : i) { + *notify << j << " "; + } + *notify << endl; + } + + // draw the projective images of the box's corners + for (int i = 0; i < 8; ++i) { + corners[i] = proj_image (cr, c[i]); + } + draw_box(cr, corners); + cairo_set_line_width (cr, 2); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int /*argc*/, char** /*argv*/) override { + // Finite images of the three vanishing points and the origin + handles.push_back(&origin_handle); + handles.push_back(&vanishing_points_handles); + handles.push_back(&axes_handles); + vanishing_points_handles.push_back(550,350); + vanishing_points_handles.push_back(150,300); + vanishing_points_handles.push_back(380,40); + vanishing_points_handles.push_back(340,450); + // plane origin + origin_handle.pos = Point(180,65); + + // Handles for moving in axes directions + axes_handles.push_back(30,300); + axes_handles.push_back(45,300); + axes_handles.push_back(60,300); + + // Box corners + for (int i = 0; i < 8; ++i) { + c[i][0] = ((i & 1) ? 1 : 0); + c[i][1] = ((i & 2) ? 1 : 0); + c[i][2] = ((i & 4) ? 1 : 0); + c[i][3] = 1; + } + + orig = origin_handle.pos; + } + Geom::Point proj_image (cairo_t *cr, const double pt[4]) { + double res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = + tmat[j][0] * (pt[0] - (axes_handles.pts[0][1]-300)/100) + + tmat[j][1] * (pt[1] - (axes_handles.pts[1][1]-300)/100) + + tmat[j][2] * (pt[2] - (axes_handles.pts[2][1]-300)/100) + + tmat[j][3] * pt[3]; + } + if (fabs (res[2]) > 0.000001) { + Geom::Point result = Geom::Point (res[0]/res[2], res[1]/res[2]); + draw_handle(cr, result); + return result; + } + assert(0); // unclipped point + return Geom::Point(0,0); + } + + void draw_box (cairo_t *cr, Geom::Point corners[8]) { + cairo_move_to(cr,corners[0]); + cairo_line_to(cr,corners[1]); + cairo_line_to(cr,corners[3]); + cairo_line_to(cr,corners[2]); + cairo_close_path(cr); + + cairo_move_to(cr,corners[4]); + cairo_line_to(cr,corners[5]); + cairo_line_to(cr,corners[7]); + cairo_line_to(cr,corners[6]); + cairo_close_path(cr); + + for(int i = 0 ; i < 4; i++) { + cairo_move_to(cr,corners[i]); + cairo_line_to(cr,corners[i+4]); + } + } + + void draw_slider_lines (cairo_t *cr) { + cairo_move_to(cr, Geom::Point(20,300)); + cairo_line_to(cr, Geom::Point(70,300)); + + for(int i = 0; i < 3; i++) { + cairo_move_to(cr, Geom::Point(30 + 15*i,00)); + cairo_line_to(cr, Geom::Point(30 + 15*i,450)); + } + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Box3d); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/center-warp.cpp b/src/toys/center-warp.cpp new file mode 100644 index 0000000..4c0f750 --- /dev/null +++ b/src/toys/center-warp.cpp @@ -0,0 +1,113 @@ +#include <2geom/bezier-to-sbasis.h> +#include <2geom/d2.h> +#include <2geom/path.h> +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/svg-path-parser.h> +#include <2geom/transforms.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = p[i]; + cairo_d2_sb(cr, B); + } +} + +class CentreWarp: public Toy { + Path path_a; + D2<SBasis2d> sb2; + Piecewise<D2<SBasis> > path_a_pw; + PointHandle brush_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Geom::Point dir(1,-2); + + D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + if(0) { + D2<Piecewise<SBasis> > tB(cos(B[0]*0.1)*(brush_handle.pos[0]/100) + B[0], + cos(B[1]*0.1)*(brush_handle.pos[1]/100) + B[1]); + + cairo_d2_pw_sb(cr, tB); + } else { + Piecewise<SBasis> r2 = (dot(path_a_pw - brush_handle.pos, path_a_pw - brush_handle.pos)); + Piecewise<SBasis> rc; + rc.push_cut(0); + rc.push(SBasis(Linear(1, 1)), 2); + rc.push(SBasis(Linear(1, 0)), 4); + rc.push(SBasis(Linear(0, 0)), 30); + rc *= 10; + rc.scaleDomain(1000); + Piecewise<SBasis> swr; + swr.push_cut(0); + swr.push(SBasis(Linear(0, 1)), 2); + swr.push(SBasis(Linear(1, 0)), 4); + swr.push(SBasis(Linear(0, 0)), 30); + swr *= 10; + swr.scaleDomain(1000); + cairo_pw(cr, swr);// + (height - 100)); + D2<Piecewise<SBasis> > uB = make_cuts_independent(unitVector(path_a_pw - brush_handle.pos)); + + D2<Piecewise<SBasis> > tB(compose(rc, (r2))*uB[0] + B[0], + compose(rc, (r2))*uB[1] + B[1]); + cairo_d2_pw_sb(cr, tB); + //path_a_pw = sectionize(tB); + } + cairo_stroke(cr); + + *notify << path_a_pw.size(); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + PathVector paths_a = read_svgd(path_a_name); + assert(!paths_a.empty()); + path_a = paths_a[0]; + + path_a.close(true); + path_a_pw = path_a.toPwSb(); + for(unsigned dim = 0; dim < 2; dim++) { + sb2[dim].us = 2; + sb2[dim].vs = 2; + const int depth = sb2[dim].us*sb2[dim].vs; + sb2[dim].resize(depth, Linear2d(0)); + } + + handles.push_back(&brush_handle); + brush_handle.pos = Point(100,100); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CentreWarp); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/circle-fitting.cpp b/src/toys/circle-fitting.cpp new file mode 100644 index 0000000..d51b1cf --- /dev/null +++ b/src/toys/circle-fitting.cpp @@ -0,0 +1,164 @@ +/* + * Circle and Elliptical Arc Fitting Example + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <memory> +#include <2geom/circle.h> +#include <2geom/elliptical-arc.h> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class CircleFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + if (first_time) + { + first_time = false; + Point toggle_sp( 300, height - 50); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(120,25) ); + sliders[0].geometry(Point(50, height - 50), 100); + } + + size_t n = (size_t)(sliders[0].value()) + 3; + if (n < psh.pts.size()) + { + psh.pts.resize(n); + } + else if (n > psh.pts.size()) + { + psh.push_back(400*uniform()+50, 300*uniform()+50); + } + + try + { + c.fit(psh.pts); + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + + if (toggles[0].on) + { + try + { + std::unique_ptr<EllipticalArc> eap( c.arc(psh.pts[0], psh.pts[1], psh.pts[2]) ); + ea = *eap; + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + + std::cerr << "center = " << c.center() << " ray = " << c.radius() << std::endl; + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + if (!toggles[0].on) + { + cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI); + } + else + { + draw_text(cr, psh.pts[0], "initial"); + draw_text(cr, psh.pts[1], "inner"); + draw_text(cr, psh.pts[2], "final"); + + D2<SBasis> easb = ea.toSBasis(); + cairo_d2_sb(cr, easb); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleFitting() + { + first_time = true; + + psh.pts.resize(3); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + + + toggles.emplace_back(" arc / circle ", false); + sliders.emplace_back(0, 5, 1, 0, "more handles"); + + handles.push_back(&psh); + handles.push_back(&(toggles[0])); + handles.push_back(&(sliders[0])); + } + + private: + Circle c; + EllipticalArc ea; + bool first_time; + PointSetHandle psh; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new CircleFitting(), 600, 600 ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/circle-intersect.cpp b/src/toys/circle-intersect.cpp new file mode 100644 index 0000000..d327a24 --- /dev/null +++ b/src/toys/circle-intersect.cpp @@ -0,0 +1,70 @@ +#include <toys/toy-framework-2.h> +#include <2geom/circle.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle psh[2]; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + double r1 = Geom::distance(psh[0].pts[0], psh[0].pts[1]); + double r2 = Geom::distance(psh[1].pts[0], psh[1].pts[1]); + + Circle c1(psh[0].pts[0], r1); + Circle c2(psh[1].pts[0], r2); + + std::vector<ShapeIntersection> result = c1.intersect(c2); + + cairo_set_line_width(cr, 1.0); + + // draw the circles + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_arc(cr, c1.center(X), c1.center(Y), c1.radius(), 0, 2*M_PI); + cairo_stroke(cr); + cairo_arc(cr, c2.center(X), c2.center(Y), c2.radius(), 0, 2*M_PI); + cairo_stroke(cr); + + // draw intersection points + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + + // show message + if (c1.contains(c2)) { + *notify << "Containment"; + } else if (!result.empty()) { + for (auto & i : result) { + *notify << i.point() << " "; + } + } else { + *notify << "No intersection"; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + psh[0].push_back(200,200); psh[0].push_back(250,200); + psh[1].push_back(150,150); psh[1].push_back(250,150); + handles.push_back(&psh[0]); + handles.push_back(&psh[1]); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/circle-line-intersect.cpp b/src/toys/circle-line-intersect.cpp new file mode 100644 index 0000000..3946ca1 --- /dev/null +++ b/src/toys/circle-line-intersect.cpp @@ -0,0 +1,67 @@ +#include <2geom/circle.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle psh[2]; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Rect all(Point(0, 0), Point(width, height)); + double r = Geom::distance(psh[0].pts[0], psh[0].pts[1]); + + Circle circ(psh[0].pts[0], r); + Line line(psh[1].pts[0], psh[1].pts[1]); + + std::vector<ShapeIntersection> result = circ.intersect(line); + + cairo_set_line_width(cr, 1.0); + + // draw the shapes + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_arc(cr, circ.center(X), circ.center(Y), circ.radius(), 0, 2*M_PI); + draw_line(cr, line, all); + cairo_stroke(cr); + + // draw intersection points + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + + // show message + if (!result.empty()) { + for (auto & i : result) { + *notify << i.point() << ", "; + } + } else { + *notify << "No intersection"; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + psh[0].push_back(200,200); psh[0].push_back(250,200); + psh[1].push_back(150,150); psh[1].push_back(250,150); + handles.push_back(&psh[0]); + handles.push_back(&psh[1]); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/circle-tangent-fitting.cpp b/src/toys/circle-tangent-fitting.cpp new file mode 100644 index 0000000..f0d74ee --- /dev/null +++ b/src/toys/circle-tangent-fitting.cpp @@ -0,0 +1,224 @@ +/* + * Circle and Elliptical Arc Fitting Example + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <memory> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/circle.h> +#include <2geom/elliptical-arc.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + + +class LFMCircleTangentEquation + : public NL::LinearFittingModelWithFixedTerms<Point, double, Circle> +{ + public: + mutable unsigned count; // sigh + mutable Point pb; + vector<Point> bases; + void feed( NL::VectorView & coeff, double & fixed_term, Point const& p ) const + { + if (count >= bases.size()) { + coeff[0] = p[X]; + coeff[1] = p[Y]; + coeff[2] = 1; + fixed_term = p[X] * p[X] + p[Y] * p[Y]; + pb = p; + } else { + Point pb = bases[count]; + coeff[0] = p[X]; + coeff[1] = p[Y]; + coeff[2] = 0; + fixed_term = 2*p[X]*pb[X] + 2*p[Y]*pb[Y]; + count ++; + } + } + + size_t size() const + { + return 3; + } + void instance(Circle & c, NL::Vector const& coeff) const + { + c.setCoefficients(1, coeff[0], coeff[1], coeff[2]); + } +}; + + +class CircleFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + if (first_time) + { + first_time = false; + Point toggle_sp( 300, height - 50); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(120,25) ); + sliders[0].geometry(Point(50, height - 50), 100); + } + + try + { + vector<Point> points; + points.push_back(psh.pts[1]-psh.pts[0]); // tangents + //points.push_back(psh.pts[2]-psh.pts[3]); + points.push_back(psh.pts[3]); + points.push_back(psh.pts[0]); + size_t sz = points.size(); + if (sz < 3) + { + THROW_RANGEERROR("fitting error: too few points passed"); + } + LFMCircleTangentEquation model; + model.count = 0; + model.bases.push_back(psh.pts[0]); + //model.bases.push_back(psh.pts[3]); + NL::least_squeares_fitter<LFMCircleTangentEquation> fitter(model, sz); + + for (size_t i = 0; i < sz; ++i) + { + fitter.append(points[i]); + } + fitter.update(); + + NL::Vector z(sz, 0.0); + model.instance(c, fitter.result(z)); + //c.set(psh.pts); + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + + if (toggles[0].on) + { + try + { + std::unique_ptr<EllipticalArc> eap( c.arc(psh.pts[0], psh.pts[1], psh.pts[3]) ); + ea = *eap; + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + cairo_move_to(cr, psh.pts[1]); + cairo_line_to(cr, 2*psh.pts[0] - psh.pts[1]); + cairo_move_to(cr, psh.pts[2]); + cairo_line_to(cr, 2*psh.pts[3] - psh.pts[2]); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + if (!toggles[0].on) + { + cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI); + } + else + { + draw_text(cr, psh.pts[0], "initial"); + draw_text(cr, psh.pts[1], "inner"); + draw_text(cr, psh.pts[2], "inner"); + draw_text(cr, psh.pts[3], "final"); + + D2<SBasis> easb = ea.toSBasis(); + cairo_d2_sb(cr, easb); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleFitting() + { + first_time = true; + + psh.pts.resize(4); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(450+100, 250+100); + psh.pts[2] = Point(250+100, 400+100); + psh.pts[3] = Point(250, 400); + + + + toggles.emplace_back(" arc / circle ", false); + sliders.emplace_back(0, 5, 1, 0, "more handles"); + + handles.push_back(&psh); + handles.push_back(&(toggles[0])); + handles.push_back(&(sliders[0])); + } + + private: + Circle c; + EllipticalArc ea; + bool first_time; + PointSetHandle psh; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new CircleFitting(), 600, 600 ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/collinear-normal.cpp b/src/toys/collinear-normal.cpp new file mode 100644 index 0000000..a5e1af5 --- /dev/null +++ b/src/toys/collinear-normal.cpp @@ -0,0 +1,204 @@ +/* + * Show off collinear normals between two Bezier curves. + * The intersection points are found by using Bezier clipping. + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <2geom/d2.h> +#include <2geom/basic-intersection.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/ray.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + + + +class CurveIntersect : public Toy +{ + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.8, 0., 0, 1); + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0.0, 0., 0, 1); + D2<SBasis> B = pshB.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + draw_text(cr, A.at0(), "A"); + draw_text(cr, B.at0(), "B"); + + Timer tm; + tm.ask_for_timeslice(); + tm.start(); + + find_collinear_normal(xs, pshA.pts, pshB.pts, m_precision); + Timer::Time als_time = tm.lap(); + *timer_stream << "find_collinear_normal " << als_time << std::endl; + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.7, 1); + for (auto & x : xs) + { + Point At = A(x.first); + Point Bu = B(x.second); + draw_axis(cr, At, Bu); + draw_handle(cr, At); + draw_handle(cr, Bu); + + } + cairo_stroke(cr); + + double h_a_t = 0, h_b_t = 0; + + double h_dist = hausdorfl( A, B, m_precision, &h_a_t, &h_b_t); + { + Point At = A(h_a_t); + Point Bu = B(h_b_t); + cairo_move_to(cr, At); + cairo_line_to(cr, Bu); + draw_handle(cr, At); + draw_handle(cr, Bu); + cairo_save(cr); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + cairo_stroke(cr); + cairo_restore(cr); + } + /*h_dist = hausdorf( A, B, m_precision, &h_a_t, &h_b_t); + { + Point At = A(h_a_t); + Point Bu = B(h_b_t); + draw_axis(cr, At, Bu); + draw_handle(cr, At); + draw_handle(cr, Bu); + cairo_save(cr); + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.0, 0.7, 0.0, 1); + cairo_stroke(cr); + cairo_restore(cr); + }*/ + *notify << "Hausdorf distance = " << h_dist + << " occurring at " << h_a_t + << " B=" << h_b_t << std::endl; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) + { + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); + } + + void draw_ray(cairo_t* cr, Ray const& r) + { + double angle = r.angle(); + draw_segment(cr, r.origin(), angle, m_length); + } + + void draw_axis(cairo_t* cr, Point const& p1, Point const& p2) + { + double d = Geom::distance(p1, p2); + d = d + d/4; + Point q1 = Ray(p1, p2).pointAt(d); + Point q2 = Ray(p2, p1).pointAt(d); + draw_segment(cr, q1, q2); + } + +public: + CurveIntersect(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + handles.push_back(&pshA); + for (unsigned int i = 0; i <= A_bez_ord; ++i) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200)); + handles.push_back(&pshB); + for (unsigned int i = 0; i <= B_bez_ord; ++i) + pshB.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200)); + + m_precision = 1e-6; + } + +private: + unsigned int A_bez_ord, B_bez_ord; + PointSetHandle pshA, pshB, pshC; + std::vector< std::pair<double, double> > xs; + double m_precision; + double m_width, m_height, m_length; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord = 4; + unsigned int B_bez_ord = 6; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + + + init( argc, argv, new CurveIntersect(A_bez_ord, B_bez_ord), 800, 800); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + + diff --git a/src/toys/conic-3.cpp b/src/toys/conic-3.cpp new file mode 100644 index 0000000..64b2c27 --- /dev/null +++ b/src/toys/conic-3.cpp @@ -0,0 +1,96 @@ +/** + * elliptics via C-curves. (njh) + * Limited to 180 degrees (by end point and tangent matching criteria) + * Also represents cycloids + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +Linear z0(0.5,1.); + +unsigned total_pieces; + +double sinC(double t) { return t - sin(t);} +double cosC(double t) { return 1 - cos(t);} +double tanC(double t) { return sinC(t) / cosC(t);} + +class Conic3: public Toy { + PointSetHandle psh; + public: + Conic3 () { + psh.push_back(100, 500); + psh.push_back(100, 500 - 200*M_PI/2); + psh.push_back(500, 500 - 200*M_PI/2); + psh.push_back(500, 500); + handles.push_back(&psh); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 0.5); + cairo_stroke(cr); + + Geom::Point a[2] = {psh.pts[0] - psh.pts[1], + psh.pts[2] - psh.pts[1]}; + double angle = Geom::angle_between(a[0], a[1]); + double len = std::max(Geom::L2(a[0]), + Geom::L2(a[1])); + for(auto & i : a) + i = len*unit_vector(i); + *notify << "angle = " << angle; + *notify << " sinC = " << sinC(angle); + *notify << " cosC = " << cosC(angle); + *notify << " tanC = " << tanC(angle); + vector<Geom::Point> e_a_h = psh.pts; + + double alpha = M_PI; + Piecewise<SBasis> pw(Linear(0, alpha)); + Piecewise<SBasis> C = cos(pw); + Piecewise<SBasis> S = sin(pw); + Piecewise<SBasis> sinC = pw - S; + Piecewise<SBasis> cosC = Piecewise<SBasis>(1) - C; + Piecewise<SBasis> Z3 = sinC/sinC(1); + Piecewise<SBasis> Z0 = reverse(Z3); + Piecewise<SBasis> Z2 = cosC/cosC(1) - Z3; + Piecewise<SBasis> Z1 = reverse(Z2); + + Piecewise<SBasis> Z[4] = {Z0, Z1, Z2, Z3}; + + D2<Piecewise<SBasis> > B; + for(unsigned dim = 0; dim < 2; dim++) { + B[dim] = Piecewise<SBasis>(0); + for(unsigned i = 0; i < 4; i++) { + B[dim] += Z[i]*e_a_h[i][dim]; + } + } + cairo_d2_pw_sb(cr, B); + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Conic3()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/conic-4.cpp b/src/toys/conic-4.cpp new file mode 100644 index 0000000..0dbecd6 --- /dev/null +++ b/src/toys/conic-4.cpp @@ -0,0 +1,129 @@ +/** + * elliptics via 5 point w-pi basis. (njh) + * Affine, endpoint, tangent, exact circle + * full circle. Convex containment implies small circle. + * Also represents lumpy polar type curves + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +const double w = 1./3; +const double cwp = cos(w*M_PI); +const double swp = sin(w*M_PI); +/*double phi(double t, double w) { return sin(w*t) - w*sin(t); } +double phih(double t, double w) { return sin(w*t) + w*sin(t); } +double b4(double t, double w) {return phi(t/2,w)*phih(t/2,w)/(swp*swp);} +double b3(double t, double w) {return cwp*phi(t,w)/(2*swp) - cwp*cwp*b4(t,w); } +double b2(double t, double w) {return 2*w*w*sin(t/2)*sin(t/2);} +double b1(double t, double w) {return b3(2*M_PI - t, w);} +double b0(double t, double w) {return b4(2*M_PI - t, w);}*/ + +class arc_basis{ +public: + Piecewise<SBasis> basis[5]; + double w; + + Piecewise<SBasis> phi(Piecewise<SBasis> const &d, double w) { + return sin(d*w) - sin(d)*w; + } + Piecewise<SBasis> phih(Piecewise<SBasis> const &d, double w) { + return sin(d*w) + sin(d)*w; + } + Piecewise<SBasis> b4(Piecewise<SBasis> const &d, double w) { + return phi(d*.5,w)/(swp*swp)*phih(d*.5,w); + } + Piecewise<SBasis> b3(Piecewise<SBasis> const &d, double w) { + return phi(d,w)*(cwp/(2*swp)) - b4(d,w)*(cwp*cwp); + } + + Piecewise<SBasis> b2(Piecewise<SBasis> const &d, double w) { + return sin(d*.5)*(2*w*w)*sin(d*.5); + } + Piecewise<SBasis> b1(Piecewise<SBasis> const &d, double w) { + return b3(reverse(d), w); + } + Piecewise<SBasis> b0(Piecewise<SBasis> const &d, double w) { + return b4(reverse(d), w); + } + + + arc_basis(double w) { + Piecewise<SBasis> dom(Linear(0, 2*M_PI)); + basis[0] = b4(dom, w); + basis[1] = b3(dom, w); + basis[2] = b2(dom, w); + basis[3] = b1(dom, w); + basis[4] = b0(dom, w); + } + +}; + +class Conic4: public Toy { + PointSetHandle psh; + public: + Conic4 () { + double sc = 30; + Geom::Point c(6*sc, 6*sc); + psh.push_back(sc*Geom::Point(0,0)+c); + psh.push_back(sc*Geom::Point(tan(w*M_PI)/w, 0)+c); + psh.push_back(sc*Geom::Point(0, 1/(w*w))+c); + psh.push_back(sc*Geom::Point(-tan(w*M_PI)/w, 0)+c); + psh.push_back(sc*Geom::Point(0,0)+c); + handles.push_back(&psh); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + std::vector<Geom::Point> e_h = psh.pts; + for(int i = 0; i < 5; i++) { + Geom::Point p = e_h[i]; + + if(i) + cairo_line_to(cr, p); + else + cairo_move_to(cr, p); + } + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + + arc_basis ab(1./3); + D2<Piecewise<SBasis> > B; + + for(unsigned dim = 0; dim < 2; dim++) + for(unsigned i = 0; i < 5; i++) + B[dim] += ab.basis[i]*e_h[i][dim]; + + cairo_d2_pw_sb(cr, B); + cairo_set_source_rgba (cr, 1., 0.5, 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Conic4()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/conic-5.cpp b/src/toys/conic-5.cpp new file mode 100644 index 0000000..51140c1 --- /dev/null +++ b/src/toys/conic-5.cpp @@ -0,0 +1,356 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/path-intersection.h> +#include <2geom/nearest-time.h> +#include <2geom/line.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> + +#include <cstdlib> +#include <map> +#include <vector> +#include <algorithm> +#include <optional> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> + + + +#include <2geom/conicsec.h> + +using namespace Geom; +using namespace std; + + +// File: convert.h +#include <sstream> +#include <stdexcept> + +class BadConversion : public std::runtime_error { +public: + BadConversion(const std::string& s) + : std::runtime_error(s) + { } +}; + +template <typename T> +inline std::string stringify(T x) +{ + std::ostringstream o; + if (!(o << x)) + throw BadConversion("stringify(T)"); + return o.str(); +} + +void draw_hull(cairo_t*cr, RatQuad rq) { + cairo_move_to(cr, rq.P[0]); + cairo_line_to(cr, rq.P[1]); + cairo_line_to(cr, rq.P[2]); + cairo_stroke(cr); +} + + + +void draw(cairo_t* cr, xAx C, Rect bnd) { + if(bnd[1].extent() < 5) return; + vector<double> prev_rts; + double py = bnd[Y].min(); + for(int i = 0; i < 100; i++) { + double t = i/100.; + double y = bnd[Y].valueAt(t); + vector<double> rts = C.roots(Point(1, 0), Point(0, y)); + int top = 0; + for(unsigned j = 0; j < rts.size(); j++) { + if(bnd[0].contains(rts[j])) { + rts[top] = rts[j]; + top++; + } + } + rts.erase(rts.begin()+top, rts.end()); + + if(rts.size() == prev_rts.size()) { + for(unsigned j = 0; j < rts.size(); j++) { + cairo_move_to(cr, prev_rts[j], py); + cairo_line_to(cr, rts[j], y); + cairo_stroke(cr); + } + } else { + draw(cr, C, Rect(bnd[X], Interval(py, y))); + } + prev_rts = rts; + py = y; + } +} + +template <typename T> +static T det(T a, T b, T c, T d) { + return a*d - b*c; +} + +template <typename T> +static T det(T M[2][2]) { + return M[0][0]*M[1][1] - M[1][0]*M[0][1]; +} + +template <typename T> +static T det3(T M[3][3]) { + return ( M[0][0] * det(M[1][1], M[1][2], + M[2][1], M[2][2]) + -M[1][0] * det(M[0][1], M[0][2], + M[2][1], M[2][2]) + +M[2][0] * det(M[0][1], M[0][2], + M[1][1], M[1][2])); +} + +double xAx_descr(xAx const & C) { + double mC[3][3] = {{C.c[0], (C.c[1])/2, (C.c[3])/2}, + {(C.c[1])/2, C.c[2], (C.c[4])/2}, + {(C.c[3])/2, (C.c[4])/2, C.c[5]}}; + + return det3(mC); +} + +void draw_ratquad(cairo_t*cr, RatQuad rq, double tol=1e-1) { + CubicBezier cb = rq.toCubic(); + // I tried using the nearest point to 0.5 for the error, but the improvement was negligible + if(L2(cb.pointAt(0.5) - rq.pointAt(0.5)) > tol) { + RatQuad a, b; + rq.split(a, b); + draw_ratquad(cr, a, tol); + draw_ratquad(cr, b, tol); + } else { + cairo_curve(cr, cb); + //draw_cross(cr, cb.pointAt(0)); + //draw_cross(cr, cb.pointAt(1)); + } +} + + +class Conic5: public Toy { + PointSetHandle path_handles; + PointHandle oncurve; + PointSetHandle cutting_plane; + std::vector<Slider> sliders; + RectHandle rh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + if(0) { + Path path; + path = Path(path_handles.pts[0]); + D2<SBasis> c = handles_to_sbasis(path_handles.pts.begin(), 2); + path.append(c); + + cairo_save(cr); + cairo_path(cr, path); + cairo_set_source_rgba (cr, 0., 1., 0, 0.3); + cairo_set_line_width (cr, 3); + cairo_stroke(cr); + cairo_restore(cr); + + //double w = exp(sliders[0].value()); + } + Point A = path_handles.pts[0]; + Point B = path_handles.pts[1]; + Point C = path_handles.pts[2]; + + if(1) { + QuadraticBezier qb(A, B, C); + //double abt = qb.nearestTime(oncurve.pos); + //oncurve.pos = qb.pointAt(abt); + + RatQuad rq = RatQuad::fromPointsTangents(A, B-A, oncurve.pos, C, B -C); //( A, B, C, w); + + cairo_save(cr); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + draw_ratquad(cr, rq); + //cairo_d2_sb(cr, rq.hermite()); + cairo_stroke(cr); + cairo_restore(cr); + } + + if(0) { + RatQuad rq = RatQuad::circularArc(A, B, C); + + cairo_save(cr); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + RatQuad a, b; + rq.split(a,b); + cairo_curve(cr, a.toCubic()); + cairo_curve(cr, b.toCubic()); + cairo_stroke(cr); + cairo_restore(cr); + } + + Rect screen_rect(Interval(10, width-10), Interval(10, height-10)); + Line cutLine(cutting_plane.pts[0], cutting_plane.pts[1]); + //double dist; + //Point norm = cutLine.normalAndDist(dist); + + const unsigned N = 3; + xAx sources[N] = { + xAx::fromPoint(A)*(exp(-sliders[0].value())), + xAx::fromPoint(B)*(exp(-sliders[1].value())), + xAx::fromPoint(C)*(exp(-sliders[2].value())) + //xAx::fromLine(Line(A, oncurve.pos)) + }; + for(unsigned i = 0; i < N; i++) { + //*notify << sources[i] << "\n"; + } + for(unsigned i = 0; i < N; i++) { + for(unsigned j = i+1; j < N; j++) { + xAx Q = sources[i]-sources[j]; + *notify << Q << " is a " << Q.categorise() << "\n"; + } + } + { + cairo_save(cr); + cairo_set_source_rgba(cr, 0, 0, 1, 0.5); + + ::draw(cr, (sources[0]-sources[1]), screen_rect); + ::draw(cr, (sources[0]-sources[2]), screen_rect); + ::draw(cr, (sources[1]-sources[2]), screen_rect); + cairo_restore(cr); + } + { + string os; + for(unsigned i = 0; i < N; i++) { + for(unsigned j = i+1; j < N; j++) { + xAx Q = sources[i]-sources[j]; + Interval iQ = Q.extrema(rh.pos); + if(iQ.contains(0)) { + os += stringify(iQ) + "\n"; + + Q.toCurve(rh.pos); + vector<Point> crs = Q.crossings(rh.pos); + for(auto & ei : crs) { + draw_cross(cr, ei); + } + + } + } + } + + draw_text(cr, rh.pos.midpoint(), + os); + } + if(1) { + xAx oxo=sources[0] - sources[2]; + Timer tm; + + tm.ask_for_timeslice(); + tm.start(); + + std::vector<Point> intrs = intersect(oxo, sources[0] - sources[1]); + Timer::Time als_time = tm.lap(); + *notify << "intersect time = " << als_time << std::endl; + for(auto & intr : intrs) { + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0,0); + draw_cross(cr, intr); + cairo_stroke(cr); + cairo_restore(cr); + } + + std::optional<RatQuad> orq = oxo.toCurve(rh.pos); + if(orq) { + RatQuad rq = *orq; + draw_hull(cr, rq); + vector<SBasis> hrq = rq.homogeneous(); + SBasis vertex_poly = (sources[0] - sources[1]).evaluate_at(hrq[0], hrq[1], hrq[2]); + //*notify << "\n0: " << hrq[0]; + //*notify << "\n1: " << hrq[1]; + //*notify << "\n2: " << hrq[2]; + vector<double> rts = roots(vertex_poly); + //*notify << "\nvertex poly:" << vertex_poly << '\n'; + for(unsigned i = 0; i < rts.size(); i++) { + draw_circ(cr, Point(rq.pointAt(rts[i]))); + *notify << "\nrq" << i << ":" << rts[i]; + } + + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + RatQuad a, b; + rq.split(a,b); + cairo_curve(cr, a.toCubic()); + cairo_curve(cr, b.toCubic()); + cairo_stroke(cr); + cairo_restore(cr); + } + } + if(0) { + RatQuad a, b; + //rq.split(a,b); + //cairo_move_to(cr, rq.toCubic().pointAt(0.5)); + cairo_line_to(cr, a.P[2]); + cairo_stroke(cr); + + cairo_curve(cr, a.toCubic()); + cairo_curve(cr, b.toCubic()); + + } + cairo_stroke(cr); + + //*notify << "w = " << w << "; lambda = " << rq.lambda() << "\n"; + Toy::draw(cr, notify, width, height, save, timer_stream); + } + +public: + Conic5() { + handles.push_back(&path_handles); + handles.push_back(&rh); + rh.pos = Rect(Point(100,100), Point(200,200)); + rh.show_center_handle = true; + handles.push_back(&oncurve); + for(int j = 0; j < 3; j++){ + path_handles.push_back(uniform()*400, 100+ uniform()*300); + } + oncurve.pos = ((path_handles.pts[0]+path_handles.pts[1]+path_handles.pts[2])/3); + handles.push_back(&cutting_plane); + for(int j = 0; j < 2; j++){ + cutting_plane.push_back(uniform()*400, 100+ uniform()*300); + } + sliders.emplace_back(0.0, 5.0, 0, 0.0, "a"); + sliders.emplace_back(0.0, 5.0, 0, 0.0, "b"); + sliders.emplace_back(0.0, 5.0, 0, 0.0, "c"); + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + } + + void first_time(int /*argc*/, char**/* argv*/) override { + + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Conic5()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/conic-6.cpp b/src/toys/conic-6.cpp new file mode 100644 index 0000000..90da671 --- /dev/null +++ b/src/toys/conic-6.cpp @@ -0,0 +1,304 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/path-intersection.h> +#include <2geom/nearest-time.h> +#include <2geom/line.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> + +#include <cstdlib> +#include <map> +#include <vector> +#include <algorithm> +#include <optional> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> + +#include <2geom/conicsec.h> + +std::vector<Geom::RatQuad> xAx_to_RatQuads(Geom::xAx const &/*C*/, Geom::Rect const &/*bnd*/) { + // find points on boundary + // if there are exactly 0 points return + // if there are exactly 2 points fit ratquad and return + // if there are an odd number, split bnd on the point with the smallest dot(unit_vector(grad), rect_edge) + // sort into clockwise order ABCD + // compute corresponding tangents + // test boundary points against the line through A + // if all on one side + // + // if A,X and Y,Z + // ratquad from A,X and Y,Z + return std::vector<Geom::RatQuad>(); +} + + + +using namespace Geom; +using namespace std; + + +// File: convert.h +#include <sstream> +#include <stdexcept> + +class BadConversion : public std::runtime_error { +public: + BadConversion(const std::string& s) + : std::runtime_error(s) + { } +}; + +template <typename T> +inline std::string stringify(T x) +{ + std::ostringstream o; + if (!(o << x)) + throw BadConversion("stringify(T)"); + return o.str(); +} + +namespace Geom{ +xAx degen; +}; + +void draw_hull(cairo_t*cr, RatQuad rq) { + cairo_move_to(cr, rq.P[0]); + cairo_line_to(cr, rq.P[1]); + cairo_line_to(cr, rq.P[2]); + cairo_stroke(cr); +} + + + +void draw(cairo_t* cr, xAx C, Rect bnd) { + if(bnd[1].extent() < 5) return; + vector<double> prev_rts; + double py = bnd[Y].min(); + for(int i = 0; i < 100; i++) { + double t = i/100.; + double y = bnd[Y].valueAt(t); + vector<double> rts = C.roots(Point(1, 0), Point(0, y)); + int top = 0; + for(unsigned j = 0; j < rts.size(); j++) { + if(bnd[0].contains(rts[j])) { + rts[top] = rts[j]; + top++; + } + } + rts.erase(rts.begin()+top, rts.end()); + + if(rts.size() == prev_rts.size()) { + for(unsigned j = 0; j < rts.size(); j++) { + cairo_move_to(cr, prev_rts[j], py); + cairo_line_to(cr, rts[j], y); + cairo_stroke(cr); + } +/* } else if(prev_rts.size() == 1) { + for(unsigned j = 0; j < rts.size(); j++) { + cairo_move_to(cr, prev_rts[0], py); + cairo_line_to(cr, rts[j], y); + cairo_stroke(cr); + } + } else if(rts.size() == 1) { + for(unsigned j = 0; j < prev_rts.size(); j++) { + cairo_move_to(cr, prev_rts[j], py); + cairo_line_to(cr, rts[0], y); + cairo_stroke(cr); + }*/ + } else { + draw(cr, C, Rect(bnd[0], Interval(py, y))); + /*for(unsigned j = 0; j < rts.size(); j++) { + cairo_move_to(cr, rts[j], y); + cairo_rel_line_to(cr, 1,1); + }*/ + } + prev_rts = rts; + py = y; + } +} + +template <typename T> +static T det(T a, T b, T c, T d) { + return a*d - b*c; +} + +template <typename T> +static T det(T M[2][2]) { + return M[0][0]*M[1][1] - M[1][0]*M[0][1]; +} + +template <typename T> +static T det3(T M[3][3]) { + return ( M[0][0] * det(M[1][1], M[1][2], + M[2][1], M[2][2]) + -M[1][0] * det(M[0][1], M[0][2], + M[2][1], M[2][2]) + +M[2][0] * det(M[0][1], M[0][2], + M[1][1], M[1][2])); +} + +class Conic6: public Toy { + PointSetHandle C1H, C2H; + std::vector<Slider> sliders; + Point mouse_sampler; + + void mouse_moved(GdkEventMotion* e) override { + mouse_sampler = Point(e->x, e->y); + Toy::mouse_moved(e); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + Rect screen_rect(Interval(10, width-10), Interval(10, height-10)); + + Geom::xAx C1 = xAx::fromPoints(C1H.pts); + ::draw(cr, C1, screen_rect); + *notify << C1; + + Geom::xAx C2 = xAx::fromPoints(C2H.pts); + ::draw(cr, C2, screen_rect); + *notify << C2; + + + SBasis T(Linear(-1,1)); + SBasis S(Linear(1,1)); + SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2}, + {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2}, + {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}}; + + SBasis D = det3(C); + std::vector<double> rts = Geom::roots(D); + if(rts.empty()) { + T = Linear(1,1); + S = Linear(-1,1); + SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2}, + {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2}, + {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}}; + + D = det3(C); + rts = Geom::roots(D); + } + // at this point we have a T and S and perhaps some roots that represent our degenerate conic + // Let's just pick one randomly (can we do better?) + //for(unsigned i = 0; i < rts.size(); i++) { + if(!rts.empty()) { + cairo_save(cr); + + unsigned i = 0; + double t = T.valueAt(rts[i]); + double s = S.valueAt(rts[i]); + *notify << t << "; " << s << std::endl; + /*double C0[3][3] = {{t*C1.c[0]+s*C2.c[0], (t*C1.c[1]+s*C2.c[1])/2, (t*C1.c[3]+s*C2.c[3])/2}, + {(t*C1.c[1]+s*C2.c[1])/2, t*C1.c[2]+s*C2.c[2], (t*C1.c[4]+s*C2.c[4])/2}, + {(t*C1.c[3]+s*C2.c[3])/2, (t*C1.c[4]+s*C2.c[4])/2, t*C1.c[5]+s*C2.c[5]}};*/ + xAx xC0 = C1*t + C2*s; + //::draw(cr, xC0, screen_rect); // degen + + std::optional<Point> oB0 = xC0.bottom(); + + Point B0 = *oB0; + //*notify << B0 << " = " << C1.gradient(B0); + draw_circ(cr, B0); + + Point n0, n1; + // Are these just the eigenvectors of A11? + if(fabs(xC0.c[0]) > fabs(xC0.c[2])) { + double b = 0.5*xC0.c[1]/xC0.c[0]; + double c = xC0.c[2]/xC0.c[0]; + double d = std::sqrt(b*b-c); + n0 = Point(1, b+d); + n1 = Point(1, b-d); + } else { + + double b = 0.5*xC0.c[1]/xC0.c[2]; + double c = xC0.c[0]/xC0.c[2]; + double d = std::sqrt(b*b-c); + n0 = Point(b+d, 1); + n1 = Point(b-d, 1); + } + cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); + + Line L0 = Line::from_origin_and_vector(B0, rot90(n0)); + draw_line(cr, L0, screen_rect); + Line L1 = Line::from_origin_and_vector(B0, rot90(n1)); + draw_line(cr, L1, screen_rect); + + cairo_set_source_rgb(cr, 1, 0., 0.); + rts = C1.roots(L0); + for(double rt : rts) { + Point P = L0.pointAt(rt); + draw_cross(cr, P); + *notify << C1.valueAt(P) << "; " << C2.valueAt(P) << "\n"; + } + rts = C1.roots(L1); + for(double rt : rts) { + Point P = L1.pointAt(rt); + draw_cross(cr, P); + *notify << C1.valueAt(P) << "; "<< C2.valueAt(P) << "\n"; + } + cairo_stroke(cr); + cairo_restore(cr); + } + + ::draw(cr, C1*sliders[0].value() + C2*sliders[1].value(), screen_rect); + + std::vector<Point> res = intersect(C1, C2); + for(auto & re : res) { + draw_circ(cr, re); + } + + cairo_stroke(cr); + + //*notify << "w = " << w << "; lambda = " << rq.lambda() << "\n"; + Toy::draw(cr, notify, width, height, save, timer_stream); + } + +public: + Conic6() { + for(int j = 0; j < 5; j++){ + C1H.push_back(uniform()*400, 100+ uniform()*300); + C2H.push_back(uniform()*400, 100+ uniform()*300); + } + handles.push_back(&C1H); + handles.push_back(&C2H); + sliders.emplace_back(-1.0, 1.0, 0, 0.0, "a"); + sliders.emplace_back(-1.0, 1.0, 0, 0.0, "b"); + sliders.emplace_back(0.0, 5.0, 0, 0.0, "c"); + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + } + + void first_time(int /*argc*/, char**/* argv*/) override { + + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Conic6()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/conic-section-toy.cpp b/src/toys/conic-section-toy.cpp new file mode 100644 index 0000000..31695be --- /dev/null +++ b/src/toys/conic-section-toy.cpp @@ -0,0 +1,813 @@ +/* + * Conic Section Toy + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2009 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +//#define CLIP_WITH_CAIRO_SUPPORT +#ifdef CLIP_WITH_CAIRO_SUPPORT + #include <2geom/conic_section_clipper_cr.h> +#endif + + +#include <2geom/conicsec.h> +#include <2geom/line.h> + +//#include <iomanip> +#include <optional> + + +using namespace Geom; + + +class ConicSectionToy : public Toy +{ + enum menu_item_t + { + SHOW_MENU = 0, + TEST_VERTEX_FOCI, + TEST_FITTING, + TEST_ECCENTRICITY, + TEST_DEGENERATE, + TEST_ROOTS, + TEST_NEAREST_POINT, + TEST_BOUND, + TEST_CLIP, + TEST_TANGENT, + TEST_DUAL, + TOTAL_ITEMS // this one must be the last item + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &ConicSectionToy::draw_menu; + } + + + void init_common() + { + init_vertex_foci(); + set_control_geometry = true; + } + + void draw_common (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_vertex_foci(cr, notify, width, height, save, timer_stream); + } + + +/* + * TEST VERTEX FOCI + */ + void init_vertex_foci() + { + set_common_control_geometry = true; + handles.clear(); + + focus1.pos = Point(300, 300); + focus2.pos = Point(500, 300); + vertex.pos = Point(200, 300); + + parabola_toggle = Toggle("set/unset F2 to infinity", false); + + handles.push_back (&vertex); + handles.push_back (&focus1); + handles.push_back (&focus2); + handles.push_back (¶bola_toggle); + } + + void draw_vertex_foci (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream * /*timer_stream*/) + { + init_vertex_foci_ctrl_geom(cr, notify, width, height); + + + if (!parabola_toggle.on ) + { + if (focus2.pos == Point(infinity(),infinity())) + focus2.pos = Point(m_width/2, m_height/2); + double df1f2 = distance (focus1.pos, focus2.pos); + if (df1f2 > 20 ) + { + Line axis(focus1.pos,focus2.pos); + vertex.pos = projection (vertex.pos, axis); + } + else if (df1f2 > 1) + { + Line axis(focus1.pos,vertex.pos); + focus2.pos = projection (focus2.pos, axis); + } + else + { + Line axis(focus1.pos,vertex.pos); + focus2.pos = focus1.pos; + } + } + else + { + focus2.pos = Point(infinity(),infinity()); + } + + cs.set (vertex.pos, focus1.pos, focus2.pos); + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.5); + draw (cr, cs, m_window); + cairo_stroke(cr); + + draw_label(cr, focus1, "F1"); + if (!parabola_toggle.on) draw_label(cr, focus2, "F2"); + draw_label(cr, vertex, "V"); + cairo_stroke(cr); + + *notify << cs.categorise() << ": " << cs << std::endl; + } + + void init_vertex_foci_ctrl_geom (cairo_t* /*cr*/, + std::ostringstream* /*notify*/, + int width, int /*height*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + + Point toggle_sp( width - 200, 10); + parabola_toggle.bounds = Rect (toggle_sp, toggle_sp + Point(190,30)); + } + } + + +/* + * TEST FITTING + */ + void init_fitting() + { + set_common_control_geometry = true; + handles.clear(); + + psh.pts.resize(5); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + psh.pts[3] = Point(400, 320); + psh.pts[4] = Point(50, 250); + + fitting_slider.set (0, 5, 1, 0, "more handles"); + + handles.push_back(&psh); + handles.push_back(&fitting_slider); + } + + void draw_fitting (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream * /*timer_stream*/) + { + init_fitting_ctrl_geom(cr, notify, width, height); + + size_t n = (size_t)(fitting_slider.value()) + 5; + if (n < psh.pts.size()) + { + psh.pts.resize(n); + } + else if (n > psh.pts.size()) + { + psh.push_back (std::fabs (width - 100) * uniform() + 50, + std::fabs (height - 100) * uniform() + 50); + } + + cs.set (psh.pts); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.5); + draw (cr, cs, m_window); + cairo_stroke(cr); + *notify << cs.categorise() << ": " << cs << std::endl; + } + + void init_fitting_ctrl_geom (cairo_t* /*cr*/, + std::ostringstream* /*notify*/, + int /*width*/, int height) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + fitting_slider.geometry (Point(50, height - 50), 100); + } + } + + +/* + * TEST ECCENTRICITY + */ + void init_eccentricity() + { + set_common_control_geometry = true; + handles.clear(); + + p1 = Point (100, 100); + p2 = Point (100, 400); + focus1 = Point (300, 250); + + eccentricity_slider.set (0, 3, 0, 1, "eccentricity"); + + handles.push_back (&p1); + handles.push_back (&p2); + handles.push_back (&focus1); + handles.push_back (&eccentricity_slider); + } + + void draw_eccentricity (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream * /*timer_stream*/) + { + init_eccentricity_ctrl_geom(cr, notify, width, height); + + Line directrix (p1.pos, p2.pos); + + cs.set (focus1.pos, directrix, eccentricity_slider.value()); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.5); + draw (cr, cs, m_window); + cairo_stroke(cr); + + draw_label (cr, focus1, "F"); + draw_line (cr, directrix, m_window); + draw_label(cr, p1, "directrix"); + cairo_stroke(cr); + + *notify << cs.categorise() << ": " << cs << std::endl; + } + + void init_eccentricity_ctrl_geom (cairo_t* /*cr*/, + std::ostringstream* /*notify*/, + int /*width*/, int height) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + eccentricity_slider.geometry (Point (10, height - 50), 300); + } + } + + + +/* + * TEST DEGENERATE + */ + void init_degenerate() + { + set_common_control_geometry = true; + handles.clear(); + + psh.pts.resize(4); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + psh.pts[3] = Point(400, 320); + + + + handles.push_back(&psh); + } + + void draw_degenerate (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream * /*timer_stream*/) + { + init_degenerate_ctrl_geom(cr, notify, width, height); + + Line l1 (psh.pts[0], psh.pts[1]); + Line l2 (psh.pts[2], psh.pts[3]); + cs.set (l1, l2); + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.5); + draw (cr, cs, m_window); + cairo_stroke(cr); + + *notify << cs.categorise() << ": " << cs << std::endl; + } + + void init_degenerate_ctrl_geom (cairo_t* /*cr*/, + std::ostringstream* /*notify*/, + int /*width*/, int /*height*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + } + } + + +/* + * TEST ROOTS + */ + void init_roots() + { + init_common(); + + p1.pos = Point(180, 50); + + x_y_toggle = Toggle("X/Y roots", true); + + handles.push_back(&p1); + handles.push_back(&x_y_toggle); + } + + void draw_roots (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + init_roots_ctrl_geom(cr, notify, width, height); + + + Dim2 DIM = x_y_toggle.on ? X : Y; + Line l(p1.pos, DIM * (-M_PI/2) + M_PI/2); + + cairo_set_line_width(cr, 0.2); + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + draw_line(cr, l, m_window); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + std::vector<double> values; + try + { + cs.roots(values, p1.pos[DIM], DIM); + } + catch(Geom::Exception e) + { + std::cerr << e.what() << std::endl; + } + for (double value : values) + { + Point p(value, value); + p[DIM] = p1.pos[DIM]; + draw_handle(cr, p); + } + cairo_stroke(cr); + + *notify << " "; + for ( unsigned int i = 0; i < values.size(); ++i ) + { + *notify << "v" << i << " = " << values[i] << " "; + } + } + + void init_roots_ctrl_geom (cairo_t* /*cr*/, std::ostringstream* /*notify*/, + int width, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + Point T(width - 120, height - 60); + x_y_toggle.bounds = Rect( T, T + Point(100,25) ); + } + } + +/* + * TEST NEAREST POINT + */ + + void init_nearest_time() + { + init_common(); + p1.pos = Point(180, 50); + handles.push_back(&p1); + } + + void draw_nearest_time (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + + Point P; + try + { + P = cs.nearestTime (p1.pos); + } + catch (LogicalError e) + { + std::cerr << e.what() << std::endl; + } + + + cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0); + draw_line_seg(cr, p1.pos, P); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.1, 0.1, 0.9, 1.0); + draw_handle(cr, P); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0); + draw_label(cr, p1, "Q"); + draw_text(cr, P + Point(5, 5), "P"); + cairo_stroke(cr); + } + +/* + * TEST BOUND + */ + void init_bound() + { + init_common(); + p1.pos = Point(50, 200); + p2.pos = Point(50, 400); + p3.pos = Point(50, 500); + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + } + + void draw_bound (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + + try + { + p1.pos = cs.nearestTime (p1.pos); + p2.pos = cs.nearestTime (p2.pos); + p3.pos = cs.nearestTime (p3.pos); + } + catch (LogicalError e) + { + std::cerr << e.what() << std::endl; + } + + Rect bound = cs.arc_bound (p1.pos, p2.pos, p3.pos); + cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0); + cairo_set_line_width (cr, 0.5); + cairo_rectangle (cr, bound); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0); + draw_label (cr, p1, "initial"); + draw_label (cr, p2, "inner"); + draw_label (cr, p3, "final"); + cairo_stroke(cr); + + } + +/* + * TEST CLIP + */ + void init_clip() + { + init_common(); + } + + void draw_clip (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + //init_clip_ctrl_geom(cr, notify, width, height); + + + Rect R(Point (100, 100),Point (width-100, height-100)); + std::vector<RatQuad> rq; +#ifdef CLIP_WITH_CAIRO_SUPPORT + clipper_cr aclipper(cr, cs, R); + aclipper.clip (rq); +#else + clip (rq, cs, R); +#endif + cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0); + cairo_set_line_width (cr, 0.5); + cairo_rectangle (cr, Rect (Point (100, 100),Point (width-100, height-100))); + for (auto & i : rq) + { + cairo_d2_sb (cr, i.toCubic().toSBasis()); + } + cairo_stroke(cr); + } + +/* + * TEST TANGENT + */ + void init_tangent() + { + init_common(); + + p1.pos = Point(180, 50); + + handles.push_back(&p1); + } + + void draw_tangent (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + + p1.pos = cs.nearestTime (p1.pos); + Line l = cs.tangent(p1.pos); + + draw_label (cr, p1, "P"); + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + draw_line(cr, l, m_window); + cairo_stroke(cr); + } + +/* + * TEST DUAL + */ + void init_dual() + { + init_common(); + } + + void draw_dual (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + xAx dc = cs.dual(); + // we need some trick to make the dual visible in the window + std::string dckind = dc.categorise(); + std::optional<Point> T = dc.centre(); + if (T) dc = dc.translate (-*T); + dc = dc.scale (1e-5, 1e-5); + dc = dc.translate (Point(width/2, height/2)); + draw (cr, dc, m_window); + cairo_stroke(cr); + *notify << "\n dual: " << dckind << ": " << dc; + } + + + void draw_segment (cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw (cairo_t* cr, xAx const& cs, const Rect& _window) + { + + // offset + //Point O(400, 300); + Point O(0, 0); + std::vector<double> r1, r2; + + size_t N = (size_t)(2 *_window.width()); + double dx = 0.5;//width / N; + //std::cout << "dx = " << dx << std::endl; + double x = _window.left() - O[X]; + for (size_t i = 0; i < N; ++i, x += dx) + { + if (r1.empty()) + { + cs.roots(r1, x, X); + if (r1.size() == 1) + { + r1.push_back(r1.front()); + } + if (i != 0 && r1.size() == 2) + { + Point p1(x-dx, r1[0]); + Point p2(x-dx, r1[1]); + p1 += O; p2 += O; + if (_window.contains(p1) && _window.contains(p2)) + draw_segment(cr, p1, p2); + } + continue; + } + cs.roots(r2, x, X); + if (r2.empty()) + { + Point p1(x-dx, r1[0]); + Point p2(x-dx, r1[1]); + p1 += O; p2 += O; + if (_window.contains(p1) && _window.contains(p2)) + draw_segment(cr, p1, p2); + r1.clear(); + continue; + } + if (r2.size() == 1) + { + r2.push_back(r2.front()); + } + + Point p1(x-dx, r1[0]); + Point p2(x, r2[0]); + p1 += O; p2 += O; + if (_window.contains(p1) && _window.contains(p2)) + draw_segment(cr, p1, p2); + + p1 = Point(x-dx, r1[1]) + O; + p2 = Point(x, r2[1]) + O; + if (_window.contains(p1) && _window.contains(p2)) + draw_segment(cr, p1, p2); + + using std::swap; + swap(r1, r2); + } + } + + void draw_label(cairo_t* cr, PointHandle const& ph, const char* label) + { + draw_text(cr, ph.pos+op, label); + } + +// void draw_label(cairo_t* cr, Line const& l, const char* label) +// { +// draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label); +// } + + void init_menu() + { + handles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/ ) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &ConicSectionToy::draw_menu; + break; + case 'B': + init_common(); + draw_f = &ConicSectionToy::draw_vertex_foci; + break; + case 'C': + init_fitting(); + draw_f = &ConicSectionToy::draw_fitting; + break; + case 'D': + init_eccentricity(); + draw_f = &ConicSectionToy::draw_eccentricity; + break; + case 'E': + init_degenerate(); + draw_f = &ConicSectionToy::draw_degenerate; + break; + case 'F': + init_roots(); + draw_f = &ConicSectionToy::draw_roots; + break; + case 'G': + init_nearest_time(); + draw_f = &ConicSectionToy::draw_nearest_time; + break; + case 'H': + init_bound(); + draw_f = &ConicSectionToy::draw_bound; + break; + case 'K': + init_clip(); + draw_f = &ConicSectionToy::draw_clip; + break; + case 'J': + init_tangent(); + draw_f = &ConicSectionToy::draw_tangent; + break; + case 'I': + init_dual(); + draw_f = &ConicSectionToy::draw_dual; + break; + + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream *timer_stream ) override + { + if (timer_stream == 0) timer_stream = notify; + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + m_window = Rect (Point (0, 0), Point (m_width, m_height)); + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + +public: + ConicSectionToy() + { + op = Point(5,5); + } + +private: + typedef void (ConicSectionToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + Point op; + double m_width, m_height, m_length; + Rect m_window; + + xAx cs; + PointHandle vertex, focus1, focus2; + Toggle parabola_toggle; + + PointSetHandle psh; + Slider fitting_slider; + + PointHandle p1, p2, p3; + Toggle x_y_toggle; + + Slider eccentricity_slider; +}; + + +const char* ConicSectionToy::menu_items[] = +{ + "show this menu", + "vertex and foci", + "fitting", + "eccentricity", + "degenerate", + "roots", + "nearest point", + "bound", + "clip", + "tangent", + "dual" +}; + +const char ConicSectionToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'K', 'J', 'I' +}; + + +int main(int argc, char **argv) +{ + //std::cout.precision(20); + init( argc, argv, new ConicSectionToy(), 800, 600 ); + return 0; +} + + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/convole.cpp b/src/toys/convole.cpp new file mode 100644 index 0000000..5f76cbc --- /dev/null +++ b/src/toys/convole.cpp @@ -0,0 +1,352 @@ +#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/orphan-code/sbasis-of.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+SBasis toSBasis(SBasisOf<double> const &f){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0],f[i][1]);
+ }
+ return result;
+}
+SBasisOf<double> toSBasisOfDouble(SBasis const &f){
+ SBasisOf<double> result;
+ for (auto i : f){
+ result.push_back(LinearOf<double>(i[0],i[1]));
+ }
+ return result;
+}
+
+
+
+SBasis integral(SBasis const &c) {
+ SBasis a;
+ a.resize(c.size() + 1, Linear(0,0));
+ a[0] = Linear(0,0);
+
+ for(unsigned k = 1; k < c.size() + 1; k++) {
+ double ahat = -c[k-1].tri()/(2*k);
+ a[k][0] = a[k][1] = ahat;
+ }
+ double aTri = 0;
+ for(int k = c.size()-1; k >= 0; k--) {
+ aTri = (c[k].hat() + (k+1)*aTri/2)/(2*k+1);
+ a[k][0] -= aTri/2;
+ a[k][1] += aTri/2;
+ }
+ a.normalize();
+ return a;
+}
+template<typename T>
+SBasisOf<T> integraaal(SBasisOf<T> const &c){
+ SBasisOf<T> a;
+ a.resize(c.size() + 1, LinearOf<T>(T(0.),T(0.)));
+
+ for(unsigned k = 1; k < c.size() + 1; k++) {
+ T ahat = (c[k-1][0]-c[k-1][1])/(2*k);
+ a[k] = LinearOf<T>(ahat);
+ }
+
+ T aTri = T(0.);
+ for(int k = c.size()-1; k >= 0; k--) {
+ aTri = (HatOf<T>(c[k]).d + (k+1)*aTri/2)/(2*k+1);
+ a[k][0] -= aTri/2;
+ a[k][1] += aTri/2;
+ }
+ //a.normalize();
+ return a;
+}
+
+SBasisOf<SBasisOf<double> > integral(SBasisOf<SBasisOf<double> > const &f, unsigned var){
+ //variable of f = 1, variable of f's coefficients = 0.
+ if (var == 1) return integraaal(f);
+ SBasisOf<SBasisOf<double> > result;
+ for(unsigned i = 0; i< f.size(); i++) {
+ //result.push_back(LinearOf<SBasisOf<double> >( integral(f[i][0]),integral(f[i][1])));
+ result.push_back(LinearOf<SBasisOf<double> >( integraaal(f[i][0]),integraaal(f[i][1])));
+ }
+ return result;
+}
+
+template <typename T>
+SBasisOf<T> multi_compose(SBasisOf<double> const &f, SBasisOf<T> const &g ){
+
+ //assert( f.input_dim() <= g.size() );
+
+ SBasisOf<T> u1 = g;
+ SBasisOf<T> u0 = -g + LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(1,1)));
+ SBasisOf<T> s = multiply(u0,u1);
+ SBasisOf<T> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + f[i][0]*u0 + f[i][1]*u1;
+ }
+ return r;
+}
+
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ SBasisOf<double> const &x,
+ SBasisOf<double> const &y){
+ SBasisOf<double> y0 = -y + LinearOf<double>(1,1);
+ SBasisOf<double> s = multiply(y0,y);
+ SBasisOf<double> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + compose(f[i][0],x)*y0 + compose(f[i][1],x)*y;
+ }
+ return r;
+}
+
+Piecewise<SBasis> convole(SBasisOf<double> const &f, Interval dom_f,
+ SBasisOf<double> const &g, Interval dom_g,
+ bool f_cst_ends = false){
+
+ if ( dom_f.extent() < dom_g.extent() ) return convole(g, dom_g, f, dom_f);
+
+ Piecewise<SBasis> result;
+
+ SBasisOf<SBasisOf<double> > u,v;
+ u.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,1))));
+ v.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,0)),
+ SBasisOf<double>(LinearOf<double>(1,1))));
+ SBasisOf<SBasisOf<double> > v_u = (v - u)*(dom_f.extent()/dom_g.extent());
+ v_u += SBasisOf<SBasisOf<double> >(SBasisOf<double>(-dom_g.min()/dom_g.extent()));
+ SBasisOf<SBasisOf<double> > gg = multi_compose(g,v_u);
+ SBasisOf<SBasisOf<double> > ff = SBasisOf<SBasisOf<double> >(f);
+ SBasisOf<SBasisOf<double> > hh = integral(ff*gg,0);
+
+ result.cuts.push_back(dom_f.min()+dom_g.min());
+ //Note: we know dom_f.extent() >= dom_g.extent()!!
+ //double rho = dom_f.extent()/dom_g.extent();
+ double t0 = dom_g.min()/dom_f.extent();
+ double t1 = dom_g.max()/dom_f.extent();
+ double t2 = t0+1;
+ double t3 = t1+1;
+ SBasisOf<double> a,b,t;
+ SBasis seg;
+ a = SBasisOf<double>(LinearOf<double>(0,0));
+ b = SBasisOf<double>(LinearOf<double>(0,t1-t0));
+ t = SBasisOf<double>(LinearOf<double>(t0,t1));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.min() + dom_g.max());
+ if (dom_f.extent() > dom_g.extent()){
+ a = SBasisOf<double>(LinearOf<double>(0,t2-t1));
+ b = SBasisOf<double>(LinearOf<double>(t1-t0,1));
+ t = SBasisOf<double>(LinearOf<double>(t1,t2));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.min());
+ }
+ a = SBasisOf<double>(LinearOf<double>(t2-t1,1.));
+ b = SBasisOf<double>(LinearOf<double>(1.,1.));
+ t = SBasisOf<double>(LinearOf<double>(t2,t3));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.max());
+ result*=dom_f.extent();
+
+ //--constant ends correction--------------
+ if (f_cst_ends){
+ SBasis ig = toSBasis(integraaal(g))*dom_g.extent();
+ ig -= ig.at0();
+ Piecewise<SBasis> cor;
+ cor.cuts.push_back(dom_f.min()+dom_g.min());
+ cor.push(reverse(ig)*f.at0(),dom_f.min()+dom_g.max());
+ cor.push(Linear(0),dom_f.max()+dom_g.min());
+ cor.push(ig*f.at1(),dom_f.max()+dom_g.max());
+ result+=cor;
+ }
+ //----------------------------------------
+ return result;
+}
+
+/*static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double space=10){
+ //double dt=(M[0].cuts.back()-M[0].cuts.front())/space;
+ Piecewise<D2<SBasis> > Mperp = rot90(derivative(M)) * 2;
+ for( double t = M.cuts.front(); t < M.cuts.back(); t += space) {
+ Point pos = M(t), perp = Mperp(t);
+ draw_line_seg(cr, pos + perp, pos - perp);
+ }
+ cairo_pw_d2_sb(cr, M);
+ cairo_stroke(cr);
+ }*/
+
+static void plot_graph(cairo_t *cr, Piecewise<SBasis> const &f,
+ double x_scale=300,
+ double y_scale=100){
+ //double dt=(M[0].cuts.back()-M[0].cuts.front())/space;
+ D2<Piecewise<SBasis> > g;
+ g[X] = Piecewise<SBasis>( SBasis(Linear(100+f.cuts.front()*x_scale,
+ 100+f.cuts.back()*x_scale)));
+ g[X].setDomain(f.domain());
+ g[Y] = -f*y_scale+400;
+ cairo_d2_pw_sb(cr, g);
+}
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+void
+plot3d(cairo_t *cr, SBasisOf<SBasisOf<double> > const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ SBasisOf<double> var = LinearOf<double>(0,1);
+ SBasisOf<double> cst = LinearOf<double>(i*1./NbRays,i*1./NbRays);
+ SBasis f_on_seg = toSBasis(compose(f,var,cst));
+ plot3d(cr,Linear(0,1),Linear(i*1./NbRays),f_on_seg,frame);
+ f_on_seg = toSBasis(compose(f,cst,var));
+ plot3d(cr,Linear(i*1./NbRays),Linear(0,1),f_on_seg,frame);
+ }
+}
+
+
+#define SIZE 2
+
+class ConvolutionToy: public Toy {
+
+ PointHandle adjuster, adjuster2, adjuster3;
+
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+ D2<Piecewise<SBasis> >B;
+ B[X].concat(Piecewise<SBasis>(B1[X]));
+ B[X].concat(Piecewise<SBasis>(B2[X]));
+ B[Y].concat(Piecewise<SBasis>(B1[Y]));
+ B[Y].concat(Piecewise<SBasis>(B2[Y]));
+
+//-----------------------------------------------------
+#if 0
+ Frame frame;
+ frame.O = Point(50,400);
+ frame.x = Point(300,0);
+ frame.y = Point(120,-75);
+ frame.z = Point(0,-300);
+ SBasisOf<SBasisOf<double> > u,v,cst;
+ cst.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(1,1))));
+ u.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,1))));
+ v.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,0)),
+ SBasisOf<double>(LinearOf<double>(1,1))));
+ plot3d(cr, integral(v,1) ,frame);
+ plot3d(cr, v ,frame);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_stroke(cr);
+ plot3d(cr, Linear(0,1), Linear(0), Linear(0), frame);
+ plot3d(cr, Linear(0), Linear(0,1), Linear(0), frame);
+ plot3d(cr, Linear(0), Linear(0), Linear(0,1), frame);
+ plot3d(cr, Linear(0,1), Linear(1), Linear(0), frame);
+ plot3d(cr, Linear(1), Linear(0,1), Linear(0), frame);
+ cairo_set_source_rgba (cr, 0., 0.0, 0.9, 1);
+ cairo_stroke(cr);
+#endif
+//-----------------------------------------------------
+
+ SBasisOf<double> smoother;
+ smoother.push_back(LinearOf<double>(0.));
+ smoother.push_back(LinearOf<double>(0.));
+ smoother.push_back(LinearOf<double>(30.));//could be less degree hungry!
+
+ adjuster.pos[X] = 400;
+ if(adjuster.pos[Y]>400) adjuster.pos[Y] = 400;
+ if(adjuster.pos[Y]<100) adjuster.pos[Y] = 100;
+ double scale=(400.-adjuster.pos[Y])/300+.01;
+ D2<Piecewise<SBasis> > smoothB1,smoothB2;
+ smoothB1[X] = convole(toSBasisOfDouble(B1[X]),Interval(0,4),smoother/scale,Interval(-scale/2,scale/2));
+ smoothB1[Y] = convole(toSBasisOfDouble(B1[Y]),Interval(0,4),smoother/scale,Interval(-scale/2,scale/2));
+ smoothB1[X].push(Linear(0.), 8+scale/2);
+ smoothB1[Y].push(Linear(0.), 8+scale/2);
+ smoothB2[X] = Piecewise<SBasis>(Linear(0));
+ smoothB2[X].setDomain(Interval(-scale/2,4-scale/2));
+ smoothB2[Y] = smoothB2[X];
+ smoothB2[X].concat(convole(toSBasisOfDouble(B2[X]),Interval(4,8),smoother/scale,Interval(-scale/2,scale/2)));
+ smoothB2[Y].concat(convole(toSBasisOfDouble(B2[Y]),Interval(4,8),smoother/scale,Interval(-scale/2,scale/2)));
+
+ Piecewise<D2<SBasis> > smoothB;
+ smoothB = sectionize(smoothB1)+sectionize(smoothB2);
+ //cairo_d2_sb(cr, B1);
+ //cairo_d2_sb(cr, B2);
+ cairo_pw_d2_sb(cr, smoothB);
+
+ cairo_move_to(cr,100,400);
+ cairo_line_to(cr,500,400);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+ Piecewise<SBasis>bx = Piecewise<SBasis>(B1[X]);
+ bx.setDomain(Interval(0,4));
+ Piecewise<SBasis>smth = Piecewise<SBasis>(toSBasis(smoother));
+ smth.setDomain(Interval(-scale/2,scale/2));
+ cairo_d2_sb(cr, B1);
+ plot_graph(cr, bx, 100, 1);
+ plot_graph(cr, smth/scale, 100, 100);
+ plot_graph(cr, smoothB1[X],100,1);
+
+ //cairo_pw_d2_sb(cr, Piecewise<D2<SBasis> >(B1) );
+ //cairo_pw_d2_sb(cr, sectionize(smoothB1));
+
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0.7, 0.2, 0., 1);
+ cairo_stroke(cr);
+
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ ConvolutionToy(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+
+ adjuster.pos = Geom::Point(400,100+300*uniform());
+ handles.push_back(&adjuster);
+
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new ConvolutionToy);
+ return 0;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+//vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99:
diff --git a/src/toys/curvature-curve.cpp b/src/toys/curvature-curve.cpp new file mode 100644 index 0000000..8f8f7bc --- /dev/null +++ b/src/toys/curvature-curve.cpp @@ -0,0 +1,142 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +using std::vector; +using namespace Geom; +using namespace std; + +//----------------------------------------------- + +class CurvatureTester: public Toy { + PointSetHandle curve_handle; + Path current_curve; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 1); + current_curve = Path(); + + for(int base_i = 0; base_i < int(curve_handle.pts.size()/2) - 1; base_i++) { + for(int i = 0; i < 2; i++) { + Geom::Point center=curve_handle.pts[1+2*i+base_i*2]; + Geom::Point normal=center- curve_handle.pts[2*i+base_i*2]; + double radius = Geom::L2(normal); + *notify<<"K="<<radius<<std::endl; + if (fabs(radius)>1e-4){ + + double ang = atan2(-normal); + cairo_arc(cr, center[0], center[1],fabs(radius), ang-0.3, ang+0.3); + cairo_set_source_rgba (cr, 0.75, 0.89, 1., 1); + cairo_stroke(cr); + + + //draw_handle(cr, center); + }else{ + } + { + cairo_save(cr); + double dashes[2] = {10, 10}; + cairo_set_dash(cr, dashes, 2, 0); + cairo_move_to(cr, center); + cairo_line_to(cr, center-normal); + cairo_stroke(cr); + cairo_restore(cr); + } + } + cairo_set_source_rgba (cr, 0., 0, 0., 1); + Geom::Point A = curve_handle.pts[0+base_i*2]; + Geom::Point B = curve_handle.pts[2+base_i*2]; + D2<SBasis> best_c = D2<SBasis>(SBasis(Linear(A[X],B[X])),SBasis(Linear(A[Y],B[Y]))); + double error = -1; + for(int i = 0; i < 16; i++) { + Geom::Point dA = curve_handle.pts[1+base_i*2]-A; + Geom::Point dB = curve_handle.pts[3+base_i*2]-B; + std::vector<D2<SBasis> > candidates = + cubics_fitting_curvature(curve_handle.pts[0+base_i*2],curve_handle.pts[2+base_i*2], + (i&2)?rot90(dA):-rot90(dA), + (i&1)?rot90(dB):-rot90(dB), + ((i&4)?-1:1)*L2sq(dA), ((i&8)?-1:1)*L2sq(dB)); + + if (candidates.empty()) { + } else { + //TODO: I'm sure std algorithm could do that for me... + unsigned best = 0; + for (unsigned i=0; i<candidates.size(); i++){ + Piecewise<SBasis> K = arcLengthSb(candidates[i]); + + double l = Geom::length(candidates[i]); + //double l = K.segs.back().at1();//Geom::length(candidates[i]); + //printf("l = %g\n", l); + if ( l < error || error < 0 ){ + error = l; + best = i; + best_c = candidates[best]; + } + } + } + } + if(error >= 0) { + //cairo_d2_sb(cr, best_c); + current_curve.append(best_c); + } + } + + cairo_path(cr, current_curve); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void canvas_click(Geom::Point at, int button) override { + std::cout << "clicked at:" << at << " with button " << button << std::endl; + if(button == 1) { + double dist; + double t = current_curve.nearestTime(at, &dist).asFlatTime(); + if(dist > 5) { + curve_handle.push_back(at); + curve_handle.push_back(at+Point(100,100)); + } else { + // split around t + Piecewise<D2<SBasis> > pw = current_curve.toPwSb(); + std::vector<Point > vnd = pw.valueAndDerivatives(t, 2); + Point on_curve = current_curve(t); + Point normal = rot90(vnd[1]); + Piecewise<SBasis > K = curvature(pw); + Point ps[2] = {on_curve, on_curve+unit_vector(normal)/K(t)}; + curve_handle.pts.insert(curve_handle.pts.begin()+2*(int(t)+1), ps, ps+2); + } + } + } + +public: + CurvatureTester(){ + if(handles.empty()) { + handles.push_back(&curve_handle); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + } + } +}; + +int main(int argc, char **argv) { + std::cout << "testing unit_normal(multidim_sbasis) based offset." << std::endl; + init(argc, argv, new CurvatureTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/curvature-test.cpp b/src/toys/curvature-test.cpp new file mode 100644 index 0000000..c209fe3 --- /dev/null +++ b/src/toys/curvature-test.cpp @@ -0,0 +1,110 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +using std::vector; +using namespace Geom; +using namespace std; + +// TODO: +// use path2 +// replace Ray stuff with path2 line segments. + +//----------------------------------------------- + +class CurvatureTester: public Toy { + PointSetHandle curve_handle; + PointHandle sample_point; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + D2<SBasis> B = curve_handle.asBezier(); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + sample_point.pos[1]=400; + sample_point.pos[0]=std::max(150.,sample_point.pos[0]); + sample_point.pos[0]=std::min(450.,sample_point.pos[0]); + cairo_move_to(cr, Geom::Point(150,400)); + cairo_line_to(cr, Geom::Point(450,400)); + cairo_set_source_rgba (cr, 0., 0., 0.5, 0.8); + cairo_stroke(cr); + + double t=std::max(0.,std::min(1.,(sample_point.pos[0]-150)/300.)); + Timer tm; + tm.ask_for_timeslice(); + tm.start(); + + Piecewise<SBasis> K = curvature(B); + Timer::Time als_time = tm.lap(); + *timer_stream << "curvature " << als_time << std::endl; + + for(unsigned ix = 0; ix < K.segs.size(); ix++) { + D2<SBasis> Kxy; + Kxy[1] = Linear(400) - K.segs[ix]*300; + Kxy[0] = Linear(300*K.cuts[ix] + 150, 300*K.cuts[ix+1] + 150); + cairo_d2_sb(cr, Kxy); + cairo_stroke(cr); + } + + double radius = K(t); + *notify<<"K="<<radius<<std::endl; + if (fabs(radius)>1e-4){ + radius=1./radius; + Geom::Point normal=unit_vector(derivative(B)(t)); + normal=rot90(normal); + Geom::Point center=B(t)-radius*normal; + + cairo_arc(cr, center[0], center[1],fabs(radius), 0, M_PI*2); + draw_handle(cr, center); + draw_handle(cr, B(t)); + }else{ + Geom::Point p=B(t); + Geom::Point v=derivative(B)(t); + draw_handle(cr, p); + cairo_move_to(cr, p-100*v); + cairo_line_to(cr, p+100*v); + } + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + CurvatureTester(){ + if(handles.empty()) { + handles.push_back(&curve_handle); + handles.push_back(&sample_point); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + sample_point.pos = Geom::Point(250,300); + } + } +}; + +int main(int argc, char **argv) { + std::cout << "testing unit_normal(multidim_sbasis) based offset." << std::endl; + init(argc, argv, new CurvatureTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/curve-curve-distance.cpp b/src/toys/curve-curve-distance.cpp new file mode 100644 index 0000000..bca0f27 --- /dev/null +++ b/src/toys/curve-curve-distance.cpp @@ -0,0 +1,1000 @@ +/* + * curve-curve distance + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/angle.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/piecewise.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/nearest-time.h> +#include <2geom/numeric/linear_system.h> + +#include <algorithm> + + + +namespace Geom +{ + +namespace detail +{ + +// this wrapper class is an helper to make up a curve portion and access it +// in an homogeneous way +template< typename Curve01T > +class CurvePortion +{ + public: + CurvePortion(const Curve & curve, double from, double to) + : m_curve_ptr(curve.portion(from, to)) + { + } + + Curve01T & get_curve() + { + return *( static_cast<Curve01T*>(m_curve_ptr) ); + } + + ~CurvePortion() + { + if (m_curve_ptr != NULL) + delete m_curve_ptr; + } + + private: + Curve* m_curve_ptr; +}; + +template<> +class CurvePortion< D2<SBasis> > +{ + public: + CurvePortion< D2<SBasis> >(const D2<SBasis> & curve, double from, double to) + : m_curve(portion(curve, from, to)) + { + } + + D2<SBasis> & get_curve() + { + return m_curve; + } + + private: + D2<SBasis> m_curve; +}; + + +template< typename Curve01T, typename CurveT > +class distance_impl +{ + typedef Curve01T curveA_type; + typedef CurveT curveB_type; + // determine how near a distance sample and the value computed through + // the interpolated function have to be + double accuracy; + // determine the recursion limit + double adaptive_limit; + // pieces of the initial subdivision + unsigned int piecees; + // degree of the polynomial used to interpolate a piece + unsigned int piece_degree; + // number of coefficients = piece_degree + 1 + unsigned int piece_size; + unsigned int samples_per_piece; + // total initial samples + unsigned int N; + // a junction is a part of the previous or of the next piece + unsigned int samples_per_junction; + unsigned int samples_per_2junctions; + // number of distance samples used in the interpolation (in the general case) + unsigned int samples_per_interpolation; + + // distance between two consecutive parameters at which samples are evaluated + double step; + double half_step; + // length of the initial domain interval of a piece + double piece_step; + // length of the interval related to a junction + double junction_step; + // index of the first sample related to a piece + unsigned int interval_si; + // index of the last sample related to a piece + unsigned int interval_ei; + // index of the first sample to be evaluated for the current piece + unsigned int evaluation_si; + // index of the last sample to be evaluated for the current piece + unsigned int evaluation_ei; + // index of the first sample to be used for interpolating the current piece + unsigned int interpolation_si; + // index of the last sample to be used for interpolating the current piece + unsigned int interpolation_ei; + // number of total samples to be used for interpolating the current piece + // this is equal to samples_per_interpolation except for the first and last + // piece + unsigned int interpolation_samples; + // parameter value for the first sample related to the current piece + double interval_st; + // interval_st + piece_step + double interval_et; + // curve piece start t + double portion_st; + // curve piece end t + double portion_et; + + unsigned int rec_pieces; + unsigned int rec_N; + unsigned int shared_si; + unsigned int shared_ei; + double rec_step; + double rec_half_step; + double rec_piece_step; + double rec_piece_2steps; + unsigned int rec_total_samples; + + + void init() + { + piece_degree = 3; + piece_size = piece_degree + 1; + samples_per_piece = 4; + N = piecees * samples_per_piece; + samples_per_junction = 2; + samples_per_2junctions = 2*samples_per_junction; + samples_per_interpolation + = samples_per_piece + samples_per_2junctions; + step = 1.0 / N; + half_step = step / 2; + piece_step = samples_per_piece * step; + junction_step = samples_per_junction * step; + interval_si = samples_per_junction; + interval_ei = interval_si + samples_per_piece; + portion_st = (double)(samples_per_junction) / samples_per_interpolation; + portion_et = portion_st + + (double)(samples_per_piece) / samples_per_interpolation; + + // recursive routine parameters + rec_pieces = 2; + rec_N = rec_pieces * samples_per_piece; + rec_total_samples = 2 * samples_per_piece + 1; + shared_si = samples_per_piece - samples_per_junction; + shared_ei = samples_per_piece + samples_per_junction; + rec_step = 1.0 / rec_N; + rec_half_step = rec_step / 2; + rec_piece_step = samples_per_piece * rec_step; + rec_piece_2steps = 2 * rec_piece_step; + } + + bool check_accuracy( SBasis const& piece, + NL::Vector const& sample_distances, + double step ) + { + double t = 0; + for (unsigned int i = 0; i < sample_distances.size(); ++i) + { + if ( !are_near(piece(t), sample_distances[i], accuracy) ) + { + return false; + } + t += step; + } + return true; + } + + + void append( Piecewise<SBasis> & pwc, + Piecewise<SBasis> const& spwc, + double interval_st, + double interval_length ) + { + double cut; + for (unsigned int i = 0; i < spwc.size(); ++i) + { + cut = interval_st + spwc.cuts[i+1] * interval_length; + pwc.push(spwc.segs[i], cut); + } + } + + void init_power_matrix(NL::Matrix & power_matrix) + { + double t = 0; + double u0, u1, s; + unsigned int half_rows = power_matrix.rows() / 2; + unsigned int n = power_matrix.rows() - 1; + for (unsigned int i0 = 0, i1 = n; i0 < half_rows; ++i0, --i1) + { + u0 = 1-t; + u1 = t; + s = u0 * u1; + for (unsigned int j = 0; j < piece_size; j+=2) + { + power_matrix(i0, j) = u0; + power_matrix(i0, j+1) = u1; + power_matrix(i1, j) = u1; + power_matrix(i1, j+1) = u0; + u0 *= s; + u1 *= s; + } + t += rec_step; + } + // t = 1/2 + assert( are_near(t, 0.5) ); + u1 = 1/2.0; + s = 1/4.0; + for (unsigned int j = 0; j < piece_size; j+=2) + { + power_matrix(half_rows, j) = u1; + power_matrix(half_rows, j+1) = u1; + u1 *= s; + } + } + + void interpolate( SBasis & piece, + NL::Matrix & psdinv_matrix, + NL::Vector & sample_distances, + double interpolation_si, double interpolation_samples, + double _portion_st, double _portion_et ) + { + piece.resize(2); + + NL::VectorView v( sample_distances, + interpolation_samples, + interpolation_si ); + NL::Vector coeff = psdinv_matrix * v; + for (unsigned int i = 0, k = 0; i < piece_size; i+=2, ++k) + { + piece[k][0] = coeff[i]; + piece[k][1] = coeff[i+1]; + } + piece = portion(piece, _portion_st, _portion_et); + } + + void evaluate_samples( curveA_type const& A, + curveB_type const& B, + NL::Vector & sample_distances, + double& t ) + { + Point At; + double nptime; + for (unsigned int i = evaluation_si; i < evaluation_ei; ++i) + { + At = A(t); + nptime = nearest_time(At, B); + sample_distances[i] = distance(At, B(nptime)); + t += step; + } + } + + void evaluate_piece_rec( Piecewise<SBasis> & pwc, + curveA_type const& A, + curveB_type const& B, + NL::Matrix & psdinv_matrix, + NL::Matrix & fpi_matrix, + NL::Matrix & lpi_matrix, + NL::Vector & curr_vector, + NL::Vector & sample_distances, + bool adaptive, + double _interpolation_si, + double _interpolation_ei, + double _interval_st, + double _interval_et, + double half_real_step ) + { + SBasis piece; + double _interpolation_samples = _interpolation_ei - _interpolation_si; + interpolate( piece, psdinv_matrix, curr_vector, + _interpolation_si, _interpolation_samples, + _interval_st, _interval_et ); + if (adaptive) + { + bool good + = check_accuracy( piece, sample_distances, rec_step ); + if (!good) + { + Piecewise<SBasis> spwc; + CurvePortion<curveA_type> cp(A, _interval_st, _interval_et); + evaluate_rec( spwc, + cp.get_curve(), + B, + fpi_matrix, + lpi_matrix, + sample_distances, + half_real_step ); + append(pwc, spwc, _interval_st, rec_piece_step); + } + else + { + pwc.push(piece, _interval_et); + } + } + else + { + pwc.push(piece, _interval_et); + } + } + + + // recursive routine: if the interpolated piece is accurate enough + // it's returned in the out-parameter pwc, otherwise the computation of + // two new piecees is performed using half of the current step so the + // number of samples per piece is always the same, while the interpolation + // of one piece is split into the computation of two new piecees when + // needed. + void evaluate_rec( Piecewise<SBasis> & pwc, + curveA_type const& A, + curveB_type const& B, + NL::Matrix & fpi_matrix, + NL::Matrix & lpi_matrix, + NL::Vector & sample_distances, + double real_step ) + { + const double half_real_step = real_step / 2; + const bool adaptive = !(real_step < adaptive_limit); + static const unsigned int middle_sample_index = samples_per_piece + 1; + + pwc.clear(); + pwc.push_cut(0); + // sample_distances used to check accuracy and for the interpolation + // of the two sub-pieces when needed + NL::Vector sample_distances_1(rec_total_samples); + NL::Vector sample_distances_2(rec_total_samples); + + // view of even indexes of sample_distances_1 + NL::VectorView + sd1_view_0(sample_distances_1, middle_sample_index, 0, 2); + // view of even indexes of sample_distances_2 + NL::VectorView + sd2_view_0(sample_distances_2, middle_sample_index, 0, 2); + // view of first half (+ 1) of sample_distances + NL::VectorView + sd_view_1(sample_distances, middle_sample_index, 0); + // view of second half of sample_distances + NL::VectorView + sd_view_2(sample_distances, middle_sample_index, samples_per_piece); + + sd1_view_0 = sd_view_1; + sd2_view_0 = sd_view_2; + + // if we have to check accuracy and go on with recursion + // we need to compute the distance samples of middle points + // of all current samples, because the new step is half of + // the current one + if (adaptive) + { + Point At; + double nptime; + double t = rec_half_step; + for (unsigned int i = 1; i < sample_distances.size(); i+=2) + { + At = A(t); + nptime = nearest_time(At, B); + sample_distances_1[i] = distance(At, B(nptime)); + At = A(t + rec_piece_step); + nptime = nearest_time(At, B); + sample_distances_2[i] = distance(At, B(nptime)); + t += rec_step; + } + } + + // first piece + evaluate_piece_rec( pwc, A, B, + fpi_matrix, + fpi_matrix, + lpi_matrix, + sample_distances, + sample_distances_1, + adaptive, + 0, // interpolation_si + shared_ei, // interpolation_ei + 0, // portion_st + rec_piece_step, // portion_et + half_real_step ); + + // copy back junction parts because + // the interpolate routine modifies them + for ( unsigned int i = 0, j = samples_per_piece - 1; + i < samples_per_junction; + ++i, --j ) + { + sd_view_1[j] = sd1_view_0[j]; + sd_view_2[i] = sd2_view_0[i]; + } + + + // last piece + evaluate_piece_rec( pwc, A, B, + lpi_matrix, + fpi_matrix, + lpi_matrix, + sample_distances, + sample_distances_2, + adaptive, + shared_si, // interpolation_si + rec_total_samples, // interpolation_ei + rec_piece_step, // portion_st + rec_piece_2steps, // portion_et + half_real_step ); + } + + + void evaluate_piece( Piecewise<SBasis> & pwc, + curveA_type const& A, + curveB_type const& B, + NL::Matrix & psdinv_matrix, + NL::Matrix & fpi_matrix, + NL::Matrix & lpi_matrix, + NL::Vector & curr_vector, + NL::Vector & sample_distances, + NL::Vector & end_junction, + NL::VectorView & start_junction_view, + NL::VectorView & end_junction_view, + double & t ) + { + //static size_t index = 0; + //std::cerr << "index = " << index++ << std::endl; + bool good; + SBasis piece; + Piecewise<SBasis> spwc; + interval_et += piece_step; + //std::cerr << "interval: " << interval_st << ", " << interval_et << std::endl; + //std::cerr << "interpolation range: " << interpolation_si << ", " << interpolation_ei << std::endl; + //std::cerr << "interpolation samples = " << interpolation_samples << std::endl; + evaluate_samples( A, B, curr_vector, t ); + //std::cerr << "current vector: " << curr_vector << std::endl; + for ( unsigned int i = 0, k = interval_si; + i < sample_distances.size(); + i+=2, ++k ) + { + sample_distances[i] = curr_vector[k]; + } + Point At; + double nptime; + double tt = interval_st + half_step; + for (unsigned int i = 1; i < sample_distances.size(); i+=2) + { + At = A(tt); + nptime = nearest_time(At, B); + sample_distances[i] = distance(At, B(nptime)); + tt += step; + } + //std::cerr << "sample_distances: " << sample_distances << std::endl; + end_junction = end_junction_view; + interpolate( piece, psdinv_matrix, curr_vector, + interpolation_si, interpolation_samples, + portion_st, portion_et ); + good = check_accuracy( piece, sample_distances, rec_step ); + //std::cerr << "good: " << good << std::endl; + //good = true; + if (!good) + { + CurvePortion<curveA_type> cp(A, interval_st, interval_et); + evaluate_rec( spwc, + cp.get_curve(), + B, + fpi_matrix, + lpi_matrix, + sample_distances, + half_step ); + append(pwc, spwc, interval_st, piece_step); + } + else + { + pwc.push(piece, interval_et); + } + interval_st = interval_et; + for (unsigned int i = 0; i < samples_per_junction; ++i) + { + curr_vector[i] = start_junction_view[i]; + curr_vector[samples_per_junction + i] = end_junction[i]; + } + } + + public: + void evaluate( Piecewise<SBasis> & pwc, + curveA_type const& A, + curveB_type const& B, + unsigned int _piecees ) + { + piecees = _piecees; + init(); + assert( !(piecees & 1) ); + assert( !(piece_size & 1) ); + assert( rec_total_samples & 1); + pwc.clear(); + pwc.push_cut(0); + NL::Matrix power_matrix(rec_total_samples, piece_size); + init_power_matrix(power_matrix); + + NL::MatrixView rec_fmv( power_matrix, + 0, 0, + shared_ei, piece_size ); + NL::Matrix rec_fpim = NL::pseudo_inverse(rec_fmv); + NL::MatrixView rec_lmv( power_matrix, + shared_si, 0, + rec_total_samples - shared_si, piece_size ); + NL::Matrix rec_lpim = NL::pseudo_inverse(rec_lmv); + + + + NL::Vector curr_vector(samples_per_interpolation); + NL::Vector sample_distances(rec_total_samples); + NL::Vector end_junction(samples_per_junction); + NL::VectorView start_junction_view( + sample_distances, + samples_per_junction, + rec_total_samples - 1 - samples_per_2junctions, + 2 ); + NL::VectorView end_junction_view( + curr_vector, + samples_per_junction, + samples_per_junction + samples_per_piece ); + + double t = 0; + + // first piece + evaluation_si = interval_si; + evaluation_ei = samples_per_interpolation; + interpolation_si = evaluation_si; + interpolation_ei = evaluation_ei; + interpolation_samples = interpolation_ei - interpolation_si; + interval_st = 0; + interval_et = 0; + NL::MatrixView fmv( power_matrix, + interpolation_si, 0, + interpolation_samples, piece_size ); + NL::Matrix fpim = NL::pseudo_inverse(fmv); + + evaluate_piece( pwc, A, B, fpim, + rec_fpim, rec_lpim, + curr_vector, sample_distances, end_junction, + start_junction_view, end_junction_view, + t ); + + // general case + evaluation_si = interval_si + samples_per_junction; + evaluation_ei = samples_per_interpolation; + interpolation_si = 0; + interpolation_ei = evaluation_ei; + interpolation_samples = interpolation_ei - interpolation_si; + NL::MatrixView gmv( power_matrix, + interpolation_si, 0, + interpolation_samples, piece_size ); + NL::Matrix gpim = NL::pseudo_inverse(gmv); + + for ( unsigned int piece_index = 1; + piece_index < piecees - 1; + ++piece_index ) + { + evaluate_piece( pwc, A, B, gpim, + rec_fpim, rec_lpim, + curr_vector, sample_distances, end_junction, + start_junction_view, end_junction_view, + t ); + } + + // last piece + evaluation_si = interval_si + samples_per_junction; + evaluation_ei = interval_ei + 1; + interpolation_si = 0; + interpolation_ei = evaluation_ei; + interpolation_samples = interpolation_ei -interpolation_si; + NL::MatrixView lmv( power_matrix, + interpolation_si, 0, + interpolation_samples, piece_size ); + NL::Matrix lpim = NL::pseudo_inverse(lmv); + + evaluate_piece( pwc, A, B, lpim, + rec_fpim, rec_lpim, + curr_vector, sample_distances, end_junction, + start_junction_view, end_junction_view, + t ); + } + + distance_impl() + : accuracy(1e-3), + adaptive_limit(1e-5) + {} + + void set_accuracy(double _accuracy) + { + accuracy = _accuracy; + } + + void set_adaptive_limit(double _adaptive_limit) + { + adaptive_limit = _adaptive_limit; + } + +}; // end class distance_impl + +} // end namespace detail + +template < typename Curve01T, typename CurveT > +inline +Piecewise<SBasis> +distance( Curve01T const& A, + CurveT const& B, + unsigned int pieces = 40, + double adaptive_limit = 1e-5, + double accuracy = 1e-3 ) +{ + + detail::distance_impl<Curve01T, CurveT> dist; + dist.set_accuracy(accuracy); + dist.set_adaptive_limit(adaptive_limit); + Piecewise<SBasis> pwc; + dist.evaluate(pwc, A, B, pieces); + return pwc; +} + +template < typename CurveT > +inline +Piecewise<SBasis> +distance( Piecewise< D2<SBasis> > const& A, + CurveT const& B, + unsigned int pieces = 40, + double adaptive_limit = 1e-5, + double accuracy = 1e-3 ) +{ + Piecewise<SBasis> result; + Piecewise<SBasis> pwc; + for (unsigned int i = 0; i < A.size(); ++i) + { + pwc = distance(A[i], B, pieces, adaptive_limit, accuracy); + pwc.scaleDomain(A.cuts[i+1] - A.cuts[i]); + pwc.offsetDomain(A.cuts[i]); + result.concat(pwc); + } + return result; +} + +template < typename CurveT > +inline +Piecewise<SBasis> +distance( Path const& A, + CurveT const& B, + unsigned int pieces = 40, + double adaptive_limit = 1e-5, + double accuracy = 1e-3 ) +{ + Piecewise<SBasis> result; + Piecewise<SBasis> pwc; + unsigned int sz = A.size(); + if (A.closed()) ++sz; + for (unsigned int i = 0; i < sz; ++i) + { + pwc = distance(A[i], B, pieces, adaptive_limit, accuracy); + pwc.offsetDomain(i); + result.concat(pwc); + } + return result; +} + + +template < typename Curve01T, typename CurveT > +unsigned int dist_test( Piecewise<SBasis> const& pwc, + Curve01T const& A, + CurveT const& B, + double step ) +{ + std::cerr << "======= inside dist test =======" << std::endl; + unsigned int total_checked_values = 0; + unsigned int total_error = 0; + double nptime, sample_distance; + Point At; + for (double t = 0; t <= 1; t += step) + { + At = A(t); + nptime = nearest_time(At, B); + sample_distance = distance(At, B(nptime)); + if ( !are_near(pwc(t), sample_distance, 0.001) ) + { + ++total_error; + std::cerr << "error at t: " << t << std::endl; + } + ++total_checked_values; + } + std::cerr << " total checked values : " << total_checked_values << std::endl; + std::cerr << " total error : " << total_error << std::endl; + return total_error; +} + +} // end namespace Geom + + +using namespace Geom; + +class DCCToy : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + Point ulc(width - 300, height - 60 ); + toggles[0].bounds = Rect(ulc, ulc + Point(160,25) ); + toggles[1].bounds = Rect(ulc + Point(0,30), ulc + Point(160,55) ); + sliders[0].geometry(ulc - Point(450,0), 400); + if (toggle0_status != toggles[0].on) + { + toggle0_status = toggles[0].on; + using std::swap; + swap(sliders[0], sliders[1]); + } + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + + if (choice == 0) + { + A = single_curve_psh.asBezier(); + cairo_d2_sb(cr, A); + } + else if (choice == 1) + { + pA.clear(); + for (unsigned int k = 0; k < path_curves; ++k) + { + PointSetHandle psh; + psh.pts.resize(path_handles_per_curve); + for (unsigned int h = 0; h < path_handles_per_curve; ++h) + { + unsigned int kk = k * (path_handles_per_curve-1) + h; + psh.pts[h] = path_psh.pts[kk]; + } + pA.append(psh.asBezier()); + } + cairo_path(cr, pA); + } + else if (choice == 2) + { + for (unsigned int i = 0; i < pwc_curves; ++i) + { + pwA.segs[i] = pwc_psh[i].asBezier(); + } + cairo_pw_d2_sb(cr, pwA); + } + + D2<SBasis> B = B_psh.asBezier(); + cairo_d2_sb(cr, B); + + double t = sliders[0].value(); + Piecewise<SBasis> d; + unsigned int total_error = 0; + Point cursor; + + if (!toggles[0].on) + { + if (choice == 0) + { + cursor = A(t); + d = distance(A, B, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, A, B, 0.0004); + } + else if (choice == 1) + { + cursor = pA(t); + d = distance(pA, B, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, pA, B, 0.0004); + } + else if (choice == 2) + { + cursor = pwA(t); + d = distance(pwA, B, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, pwA, B, 0.0004); + } + + double nptB = nearest_time(cursor, B); + draw_circ(cr, cursor); + cairo_move_to(cr, cursor); + cairo_line_to(cr, B(nptB)); + cairo_stroke(cr); + } + else + { + Point np(0,0); + cursor = B(t); + if (choice == 0) + { + double nptA = nearest_time(cursor, A); + np = A(nptA); + d = distance(B, A, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, B, A, 0.0004); + } + else if (choice == 1) + { + double nptA = nearest_time(cursor, pA); + np = pA(nptA); + d = distance(B, pA, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, B, pA, 0.0004); + } + draw_circ(cr, cursor); + cairo_move_to(cr, cursor); + cairo_line_to(cr, np); + cairo_stroke(cr); + } + + if (total_error != 0) + *notify << "total error: " << total_error << " "; + + + // draw distance function + Piecewise< D2<SBasis> > pwc; + pwc.cuts = d.cuts; + pwc.segs.resize(d.size()); + D2<SBasis> piece; + double domain_length = 800 / d.domain().extent(); + for ( unsigned int i = 0; i < d.size(); ++i ) + { + piece[X] = SBasis(Linear(20,20) + + domain_length * Linear(d.cuts[i], d.cuts[i+1])); + piece[Y] = 3 * d.segs[i]; + pwc.segs[i] = piece; + } + cairo_set_source_rgb(cr, 0.7,0,0); + cairo_pw_d2_sb(cr, pwc); + *notify << "total cuts: " << pwc.cuts.size(); + if (toggles[1].on) + { + for (unsigned int i = 0; i < pwc.cuts.size(); ++i) + { + draw_handle(cr, pwc(pwc.cuts[i])); + } + } + else + { + draw_handle(cr, pwc(0.0)); + draw_handle(cr, pwc(0.25)); + draw_handle(cr, pwc(0.5)); + draw_handle(cr, pwc(0.75)); + draw_handle(cr, pwc(1)); + } + draw_circ(cr, pwc(t)); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + + public: + DCCToy() + { + toggle0_status = false; + choice = 0; + + single_curve_handles = 6; + path_curves = 3; + path_handles_per_curve = 4; + path_total_handles = path_curves * (path_handles_per_curve - 1) + 1; + pwc_curves = 3; + pwc_handles_per_curve = 4; + pwc_total_handles = pwc_curves * pwc_handles_per_curve; + B_handles = 4; + + if (choice == 0) + { + for (unsigned int i = 0; i < single_curve_handles; ++i) + { + single_curve_psh.push_back(700*uniform(), 500*uniform()); + } + handles.push_back(&single_curve_psh); + sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t"); + } + else if (choice == 1) + { + for (unsigned int i = 0; i < path_total_handles; ++i) + { + path_psh.push_back(700*uniform(), 500*uniform()); + } + handles.push_back(&path_psh); + sliders.emplace_back(0.0, path_curves, 0.0, 0.0, "t"); + } + else if (choice == 2) + { + pwc_psh.resize(pwc_curves); + pwA.segs.resize(pwc_curves); + pwA.cuts.resize(pwc_curves+1); + pwA.cuts[0] = 0; + double length = 1.0 / pwc_curves; + for (unsigned int i = 0; i < pwc_curves; ++i) + { + for (unsigned int j = 0; j < pwc_handles_per_curve; ++j) + { + pwc_psh[i].push_back(700*uniform(), 500*uniform()); + } + handles.push_back(&(pwc_psh[i])); + pwA.cuts[i+1] = pwA.cuts[i] + length; + } + sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t"); + } + + for (unsigned int i = 0; i < B_handles; ++i) + { + B_psh.push_back(700*uniform(), 500*uniform()); + } + handles.push_back(&B_psh); + sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t"); + + toggles.emplace_back("d(A,B) <-> d(B,A)", false); + toggles.emplace_back("Show/Hide cuts", false); + + handles.push_back(&(toggles[0])); + handles.push_back(&(toggles[1])); + handles.push_back(&(sliders[0])); + + } + + private: + bool toggle0_status; + unsigned int choice; + unsigned int single_curve_handles, B_handles; + unsigned int path_curves, path_handles_per_curve, path_total_handles; + unsigned int pwc_curves, pwc_handles_per_curve, pwc_total_handles; + PointSetHandle single_curve_psh; + PointSetHandle path_psh; + std::vector<PointSetHandle> pwc_psh; + PointSetHandle B_psh; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + D2<SBasis> A; + Path pA; + Piecewise< D2<SBasis> > pwA; +}; + + + + +int main(int argc, char **argv) +{ + init( argc, argv, new DCCToy(), 840, 600 ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/curve-curve-nearest-time.cpp b/src/toys/curve-curve-nearest-time.cpp new file mode 100644 index 0000000..30fb327 --- /dev/null +++ b/src/toys/curve-curve-nearest-time.cpp @@ -0,0 +1,609 @@ +/* + * Nearest Points Toy 3 + * + * Authors: + * Nathan Hurst <njh at njhurst.com> + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/piecewise.h> +#include <2geom/path-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <algorithm> + + +using namespace Geom; + + +class np_finder +{ +public: + np_finder(cairo_t* _cr, D2<SBasis> const& _c1, D2<SBasis> const& _c2) + : cr(_cr), cc1(_c1), cc2(_c2), c1(_c1), c2(_c2) + { + + dc1 = derivative(_c1); + dc2 = derivative(_c2); + cd1 = dot(_c1,dc1); + cd2 = dot(_c2,dc2); + dsq = 10e30; + + Piecewise< D2<SBasis> > uv1 = unitVector(dc1, EPSILON); + Piecewise< D2<SBasis> > uv2 = unitVector(dc2, EPSILON); + + dcn1 = dot(Piecewise< D2<SBasis> >(dc1), uv1); + dcn2 = dot(Piecewise< D2<SBasis> >(dc2), uv2); + + r_dcn1 = cross(derivative(uv1), uv1); + r_dcn2 = cross(derivative(uv2), uv2); + + k1 = Geom::divide(r_dcn1, dcn1, EPSILON, 3); + k2 = Geom::divide(r_dcn2, dcn2, EPSILON, 3); + + + n1 = divide(rot90(uv1), k1, EPSILON, 3); + n2 = divide(rot90(uv2), k2, EPSILON, 3); + + std::vector<double> cuts1, cuts2; + + // add cuts at points where the curvature is discontinuos + for ( unsigned int i = 1; i < k1.size(); ++i ) + { + if( !are_near(k1[i-1].at1(), k1[i].at0()) ) + { + cuts1.push_back(k1.cuts[i]); + } + } + for ( unsigned int i = 1; i < k2.size(); ++i ) + { + if( !are_near(k2[i-1].at1(), k2[i].at0()) ) + { + cuts2.push_back(k2.cuts[i]); + } + } + + c1 = partition(c1, cuts1); + c2 = partition(c2, cuts2); + +// std::cerr << "# k1 discontinuitis" << std::endl; +// for( unsigned int i = 0; i < cuts1.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << cuts1[i] << std::endl; +// } +// std::cerr << "# k2 discontinuitis" << std::endl; +// for( unsigned int i = 0; i < cuts2.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << cuts2[i] << std::endl; +// } + + // add cuts at points were the curvature is zero + std::vector<double> k1_roots = roots(k1); + std::vector<double> k2_roots = roots(k2); + std::sort(k1_roots.begin(), k1_roots.end()); + std::sort(k2_roots.begin(), k2_roots.end()); + c1 = partition(c1, k1_roots); + c2 = partition(c2, k2_roots); + +// std::cerr << "# k1 zeros" << std::endl; +// for( unsigned int i = 0; i < k1_roots.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << k1_roots[i] << std::endl; +// } +// std::cerr << "# k2 zeros" << std::endl; +// for( unsigned int i = 0; i < k2_roots.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << k2_roots[i] << std::endl; +// } + + + cairo_set_line_width(cr, 0.2); +// cairo_set_source_rgba(cr, 0.0, 0.5, 0.0, 1.0); +// for( unsigned int i = 1; i < c1.size(); ++i ) +// { +// draw_circ(cr, c1[i].at0() ); +// } +// for( unsigned int i = 1; i < c2.size(); ++i ) +// { +// draw_circ(cr, c2[i].at0() ); +// } + + + // add cuts at nearest points to the other curve cuts points + cuts1.clear(); + cuts1.reserve(c1.size()+1); + for ( unsigned int i = 0; i < c1.size(); ++i ) + { + cuts1.push_back( nearest_time(c1[i].at0(), _c2, dc2, cd2) ); + } + cuts1.push_back( nearest_time(c1[c1.size()-1].at1(), _c2, dc2, cd2) ); + +// for ( unsigned int i = 0; i < c1.size(); ++i ) +// { +// cairo_move_to( cr, c1[i].at0() ); +// cairo_line_to(cr, c2(cuts1[i]) ); +// } +// cairo_move_to( cr, c1[c1.size()-1].at1() ); +// cairo_line_to(cr, c2(cuts1[c1.size()])); + + std::sort(cuts1.begin(), cuts1.end()); + + cuts2.clear(); + cuts2.reserve(c2.size()+1); + for ( unsigned int i = 0; i < c2.size(); ++i ) + { + cuts2.push_back( nearest_time(c2[i].at0(), _c1, dc1, cd1) ); + } + cuts2.push_back( nearest_time(c2[c2.size()-1].at1(), _c1, dc1, cd1) ); + +// for ( unsigned int i = 0; i < c2.size(); ++i ) +// { +// cairo_move_to( cr, c2[i].at0() ); +// cairo_line_to(cr, c1(cuts2[i]) ); +// } +// cairo_move_to( cr, c2[c2.size()-1].at1() ); +// cairo_line_to(cr, c1(cuts2[c2.size()])); +// cairo_stroke(cr); + + std::sort(cuts2.begin(), cuts2.end()); + + c1 = partition(c1, cuts2); + c2 = partition(c2, cuts1); + + + // copy curve to preserve cuts status + Piecewise< D2<SBasis> > pwc1 = c1; + n1 = partition(n1, pwc1.cuts); + pwc1 = partition(pwc1, n1.cuts); + r_dcn1 = partition(r_dcn1, n1.cuts); + Piecewise< D2<SBasis> > pwc2 = c2; + n2 = partition(n2, pwc2.cuts); + pwc2 = partition(pwc2, n2.cuts); + + assert( pwc1.size() == n1.size() ); + assert( pwc2.size() == n2.size() ); + assert( r_dcn1.size() == n1.size() ); + + // add cuts at curvature max and min points + Piecewise<SBasis> dk1 = derivative(k1); + Piecewise<SBasis> dk2 = derivative(k2); + std::vector<double> dk1_roots = roots(dk1); + std::vector<double> dk2_roots = roots(dk2); + std::sort(dk1_roots.begin(), dk1_roots.end()); + std::sort(dk2_roots.begin(), dk2_roots.end()); + + c1 = partition(c1, dk1_roots); + c2 = partition(c2, dk2_roots); + +// std::cerr << "# k1 min/max" << std::endl; +// for( unsigned int i = 0; i < dk1_roots.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << dk1_roots[i] << std::endl; +// } +// std::cerr << "# k2 min/max" << std::endl; +// for( unsigned int i = 0; i < dk2_roots.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << dk2_roots[i] << std::endl; +// } + +// cairo_set_source_rgba(cr, 0.0, 0.0, 0.6, 1.0); +// for( unsigned int i = 0; i < dk1_roots.size(); ++i ) +// { +// draw_handle(cr, c1(dk1_roots[i])); +// } +// for( unsigned int i = 0; i < dk2_roots.size(); ++i ) +// { +// draw_handle(cr, c2(dk2_roots[i])); +// } + + + // add cuts at nearest points to max and min curvature points + // of the other curve + cuts1.clear(); + cuts1.reserve(dk2_roots.size()); + for (double dk2_root : dk2_roots) + { + cuts1.push_back(nearest_time(_c2(dk2_root), _c1, dc1, cd1)); + } + +// for( unsigned int i = 0; i < dk2_roots.size(); ++i ) +// { +// cairo_move_to(cr, c2(dk2_roots[i])); +// cairo_line_to(cr, c1(cuts1[i])); +// } +// cairo_stroke(cr); + + std::sort(cuts1.begin(), cuts1.end()); + c1 = partition(c1, cuts1); + + + // swap normal vector direction and fill the skip list + skip_list.clear(); + skip_list.resize(c1.size(), false); + double npt; + Point p, nv; + unsigned int si; + for ( unsigned int i = 0; i < pwc1.size(); ++i ) + { + p = pwc1[i](0.5); + nv = n1[i](0.5); + npt = nearest_time(p, _c2, dc2, cd2); + if( dot( _c2(npt) - p, nv ) > 0 ) + { + if ( dot( nv, n2(npt) ) > 0 ) + { + n1[i] = -n1[i]; + r_dcn1[i] = -r_dcn1[i]; + } + else + { + si = c1.segN( n1.mapToDomain(0.5, i) ); + skip_list[si] = true; + } + } + } + + + for ( unsigned int i = 0; i < pwc2.size(); ++i ) + { + p = pwc2[i](0.5); + nv = n2[i](0.5); + npt = nearest_time(p, _c1, dc1, cd1); + if( dot( _c1(npt) - p, nv ) > 0 ) + { + if ( dot( nv, n1(npt) ) > 0 ) + { + n2[i] = -n2[i]; + } + } + } + + + evl1 = c1 + n1; + evl2 = c2 + n2; + +// cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); +// for ( unsigned int i = 0; i < c1.size(); ++i ) +// { +// double t = c1.mapToDomain(0.5, i); +// cairo_move_to(cr, c1(t)); +// cairo_line_to(cr, c1(t) + 30*unit_vector(n1(t))); +// } +// +// for ( unsigned int i = 0; i < c2.size(); ++i ) +// { +// double t = c2.mapToDomain(0.5, i); +// cairo_move_to(cr, c2(t)); +// cairo_line_to(cr, c2(t) + 30*unit_vector(n2(t))); +// } +// cairo_stroke(cr); + + std::cerr << "# skip list: "; + for( unsigned int i = 0; i < c1.cuts.size(); ++i ) + { + if ( skip_list[i] ) + std::cerr << i << " "; + } + std::cerr << std::endl; + + cairo_set_line_width(cr, 0.4); + cairo_set_source_rgba(cr, 0.6, 0.0, 0.0, 1.0); + for( unsigned int i = 0; i < c1.size(); ++i ) + { + if ( skip_list[i] ) + { + cairo_move_to(cr, c1[i].at0()); + cairo_line_to(cr, c1[i].at1()); + } + } + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0); + for( unsigned int i = 1; i < c1.size(); ++i ) + { + draw_circ(cr, c1[i].at0() ); + } + cairo_stroke(cr); + + std::cerr << "# c1 cuts: " << std::endl; + for( unsigned int i = 0; i < c1.cuts.size(); ++i ) + { + std::cerr << "c1.cuts[" << i << "]= " << c1.cuts[i] << std::endl; + } + + } + + void operator() () + { + nearest_times_impl(); + d = sqrt(dsq); + } + + Point firstPoint() const + { + return p1; + } + + Point secondPoint() const + { + return p2; + } + + double firstValue() const + { + return t1; + } + + double secondValue() const + { + return t2; + } + + double distance() const + { + return d; + } + +private: + void nearest_times_impl() + { + double t; + for ( unsigned int i = 0; i < c1.size(); ++i ) + { + if ( skip_list[i] ) continue; + std::cerr << i << " "; + t = c1.mapToDomain(0.5, i); + std::pair<double, double> npc = loc_nearest_times(t, c1.cuts[i], c1.cuts[i+1]); + if ( npc.second != -1 && dsq > L2sq(c1(npc.first) - c2(npc.second)) ) + { + t1 = npc.first; + t2 = npc.second; + p1 = c1(t1); + p2 = c2(t2); + dsq = L2sq(p1 - p2); + } + } + } + + std::pair<double, double> + loc_nearest_times( double t, double from = 0, double to = 1 ) + { + std::cerr << "[" << from << "," << to << "] t: " << t << std::endl; + unsigned int iter = 0, iter1 = 0, iter2 = 0; + std::pair<double, double> np(-1,-1); + std::pair<double, double> npf(from, -1); + std::pair<double, double> npt(to, -1); + double ct = t; + double pt = -1; + double s = nearest_time(c1(t), cc2, dc2, cd2); + cairo_set_source_rgba(cr, 1/(t+1), t*t, t, 1.0); + cairo_move_to(cr, c1(t)); + while( !are_near(ct, pt) && iter < 1000 ) + { + pt = ct; + double angle = angle_between( n1(ct), evl2(s) - evl1(ct) ); + assert( !std::isnan(angle) ); + angle = (angle > 0) ? angle - M_PI : angle + M_PI; + if ( std::fabs(angle) < M_PI/12 ) + { + ++iter2; +// cairo_move_to(cr, c1(ct)); +// cairo_line_to(cr, evl1(ct)); +// cairo_line_to(cr, evl2(s)); + //std::cerr << "s: " << s << std::endl; + //std::cerr << "t: " << ct << std::endl; + + ct = ct + angle / r_dcn1(ct); + s = nearest_time(c1(ct), cc2, dc2, cd2); +// angle = angle_between( n2(s), evl1(ct) - evl2(s) ); +// assert( !std::isnan(angle) ); +// angle = (angle > 0) ? angle - M_PI : angle + M_PI; +// s = s + angle / (dcn2(s) * k2(s)); + } + else + { + ++iter1; + ct = nearest_time(c2(s), cc1, dc1, cd1, from, to); + s = nearest_time(c1(ct), cc2, dc2, cd2); + } + iter = iter1 + iter2; + //std::cerr << "s: " << s << std::endl; + //std::cerr << "t: " << ct << std::endl; + //cairo_line_to(cr, c2(s)); + //cairo_line_to(cr, c1(ct)); + //std::cerr << "d(pt, ct) = " << std::fabs(ct - pt) << std::endl; + if ( ct < from ) + { + std::cerr << "break left" << std::endl; + np = npf; + break; + } + if ( ct > to ) + { + std::cerr << "break right" << std::endl; + np =npt; + break; + } + } + //std::cerr << "\n \n"; + std::cerr << "iterations: " << iter1 << " + " << iter2 << " = "<< iter << std::endl; + assert(iter < 3000); + //cairo_move_to(cr, c1(ct)); + //cairo_line_to(cr, c2(s)); + cairo_stroke(cr); + np.first = ct; + np.second = s; + return np; + } + + double nearest_time( Point const& p, D2<SBasis> const&c, D2<SBasis> const& dc, SBasis const& cd, double from = 0, double to = 1 ) + { + D2<SBasis> sbc = c - p; + SBasis dd = cd - dotp(p, dc); + std::vector<double> zeros = roots(dd); + double closest = from; + double distsq = L2sq(sbc(from)); + for (double zero : zeros) + { + if ( distsq > L2sq(sbc(zero)) ) + { + closest = zero; + distsq = L2sq(sbc(closest)); + } + } + if ( distsq > L2sq(sbc(to)) ) + closest = to; + return closest; + } + + SBasis dotp(Point const& p, D2<SBasis> const& c) + { + SBasis d; + d.resize(c[X].size()); + for ( unsigned int i = 0; i < c[0].size(); ++i ) + { + for( unsigned int j = 0; j < 2; ++j ) + d[i][j] = p[X] * c[X][i][j] + p[Y] * c[Y][i][j]; + } + return d; + } + + Piecewise< D2<SBasis> > + divide( Piecewise< D2<SBasis> > const& a, Piecewise<SBasis> const& b, double tol, unsigned int k, double zero=1.e-3) + { + D2< Piecewise<SBasis> > aa = make_cuts_independent(a); + D2< Piecewise<SBasis> > q(Geom::divide(aa[0], b, tol, k, zero), Geom::divide(aa[1], b, tol, k, zero)); + return sectionize(q); + } + + struct are_near_ + { + bool operator() (double x, double y, double eps = Geom::EPSILON ) + { + return are_near(x, y, eps); + } + }; + +private: + cairo_t* cr; + D2<SBasis> const& cc1, cc2; + Piecewise< D2<SBasis> > c1, c2; + D2<SBasis> dc1, dc2; + SBasis cd1, cd2; + Piecewise< D2<SBasis> > n1, n2, evl1, evl2; + Piecewise<SBasis> k1, k2, dcn1, dcn2, r_dcn1, r_dcn2; + double t1, t2, d, dsq; + Point p1, p2; + std::vector<bool> skip_list; +}; + + + + +class NearestPoints : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + D2<SBasis> B = pshB.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + np_finder np(cr, A, B); + Path AP, BP; + AP.append(A); BP.append(B); + Crossings ip_list = curve_sweep<SimpleCrosser>(AP, BP); + if( ip_list.empty() ) + { + np(); + cairo_set_line_width (cr, 0.4); + cairo_set_source_rgba(cr, 0.7, 0.0, 0.7, 1.0); + cairo_move_to(cr, np.firstPoint()); + cairo_line_to(cr, np.secondPoint()); + cairo_stroke(cr); + //std::cerr << "np: (" << np.firstValue() << "," << np.secondValue() << ")" << std::endl; + } + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + NearestPoints(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + handles.push_back(&pshA); + handles.push_back(&pshB); + for ( unsigned int i = 0; i < A_bez_ord; ++i ) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)); + for ( unsigned int i = 0; i < B_bez_ord; ++i ) + pshB.push_back(Geom::Point(uniform()*400, uniform()*400)); + + } + + private: + PointSetHandle pshA, pshB; + unsigned int A_bez_ord; + unsigned int B_bez_ord; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=8; + unsigned int B_bez_ord=5; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + init( argc, argv, new NearestPoints(A_bez_ord, B_bez_ord)); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/curve-intersection-by-bezier-clipping.cpp b/src/toys/curve-intersection-by-bezier-clipping.cpp new file mode 100644 index 0000000..dfd0a56 --- /dev/null +++ b/src/toys/curve-intersection-by-bezier-clipping.cpp @@ -0,0 +1,127 @@ +/* + * Show off crossings between two Bezier curves. + * The intersection points are found by using Bezier clipping. + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/d2.h> +#include <2geom/basic-intersection.h> +#include <2geom/sbasis-to-bezier.h> + + + + +using namespace Geom; + + +class CurveIntersect : public Toy +{ + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.8, 0., 0, 1); + //pshA.pts.back() = pshB.pts[0]; + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0.0, 0., 0, 1); + D2<SBasis> B = pshB.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + find_intersections_bezier_clipping(xs, pshA.pts, pshB.pts, m_precision); + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.7, 1); + for (auto & x : xs) + { + draw_handle(cr, A(x.first)); + draw_handle(cr, B(x.second)); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + +public: + CurveIntersect(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + handles.push_back(&pshA); + for (unsigned int i = 0; i <= A_bez_ord; ++i) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200)); + handles.push_back(&pshB); + for (unsigned int i = 0; i <= B_bez_ord; ++i) + pshB.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200)); + + m_precision = 1e-6; + } + +private: + unsigned int A_bez_ord, B_bez_ord; + PointSetHandle pshA, pshB, pshC; + std::vector< std::pair<double, double> > xs; + double m_precision; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord = 6; + unsigned int B_bez_ord = 8; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + + + init( argc, argv, new CurveIntersect(A_bez_ord, B_bez_ord), 800, 800); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + + diff --git a/src/toys/curve-intersection-by-implicitization.cpp b/src/toys/curve-intersection-by-implicitization.cpp new file mode 100644 index 0000000..e071911 --- /dev/null +++ b/src/toys/curve-intersection-by-implicitization.cpp @@ -0,0 +1,300 @@ +/* + * Show off crossings between two D2<SBasis> curves. + * The intersection points are found by using implicitization tecnique. + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/d2.h> +#include <2geom/sbasis-poly.h> +#include <2geom/numeric/linear_system.h> +#include <2geom/symbolic/implicit.h> + + +using namespace Geom; + +/* + * helper routines + */ +void poly_to_mvpoly1(SL::MVPoly1 & p, Geom::Poly const& q) +{ + for (size_t i = 0; i < q.size(); ++i) + { + p.coefficient(i, q[i]); + } + p.normalize(); +} + +void mvpoly1_to_poly(Geom::Poly & p, SL::MVPoly1 const& q) +{ + p.resize(q.get_poly().size()); + for (size_t i = 0; i < q.get_poly().size(); ++i) + { + p[i] = q[i]; + } +} + + +/* + * intersection_info + * structure utilized to store intersection info + * + * p - the intersection point + * t0 - the parameter t value at which the first curve pass through p + * t1 - the parameter t value at which the first curve pass through p + */ +struct intersection_info +{ + intersection_info() + {} + + intersection_info(Point const& _p, Coord _t0, Coord _t1) + : p(_p), t0(_t0), t1(_t1) + {} + + Point p; + Coord t0, t1; +}; + +typedef std::vector<intersection_info> intersections_info; + + + +/* + * intersection algorithm + */ +void intersect(intersections_info& xs, D2<SBasis> const& A, D2<SBasis> const& B) +{ + using std::swap; + + // supposing implicitization the most expensive step + // we perform a call to intersect with curve arguments swapped + if (A[0].size() > B[0].size()) + { + intersect(xs, B, A); + for (auto & x : xs) + swap(x.t0, x.t1); + + return; + } + + // convert A from symmetric power basis to power basis + Geom::Poly A0 = sbasis_to_poly(A[0]); + Geom::Poly A1 = sbasis_to_poly(A[1]); + + // convert to MultiPoly type + SL::MVPoly1 Af, Ag; + poly_to_mvpoly1(Af, A0); + poly_to_mvpoly1(Ag, A1); + + // compute a basis of the ideal related to the curve A + // in vector form + Geom::SL::basis_type b; + // if we compute the micro-basis the bezout matrix is made up + // by one only entry so we can't do the inversion step. + if (A0.size() == 3) + { + make_initial_basis(b, Af, Ag); + } + else + { + microbasis(b, Af, Ag); + } + + // we put the basis in of the form of two independent moving line + Geom::SL::MVPoly3 p, q; + basis_to_poly(p, b[0]); + basis_to_poly(q, b[1]); + + // compute the Bezout matrix and the implicit equation of the curve A + Geom::SL::Matrix<Geom::SL::MVPoly2> BZ = make_bezout_matrix(p, q); + SL::MVPoly2 ic = determinant_minor(BZ); + ic.normalize(); + + + // convert B from symmetric power basis to power basis + Geom::Poly B0 = sbasis_to_poly(B[0]); + Geom::Poly B1 = sbasis_to_poly(B[1]); + + // convert to MultiPoly type + SL::MVPoly1 Bf, Bg; + poly_to_mvpoly1(Bf, B0); + poly_to_mvpoly1(Bg, B1); + + // evaluate the implicit equation of A on B + // so we get an s(t) polynomial that give us + // the t values for B at which intersection happens + SL::MVPoly1 s = ic(Bf, Bg); + + // convert s(t) to Poly type, in order to use the real_solve function + Geom::Poly z; + mvpoly1_to_poly(z, s); + + // compute t values for the curve B at which intersection happens + std::vector<double> sol = solve_reals(z); + + // filter the found solutions wrt the domain interval [0,1] of B + // and compute the related point coordinates + std::vector<double> pt; + pt.reserve(sol.size()); + std::vector<Point> points; + points.reserve(sol.size()); + for (double & i : sol) + { + if (i >= 0 && i <= 1) + { + pt.push_back(i); + points.push_back(B(pt.back())); + } + } + + // case: A is parametrized by polynomial of degree 1 + // we compute the t values of A at the intersection points + // and filter the results wrt the domain interval [0,1] + double t; + xs.clear(); + xs.reserve(pt.size()); + if (A0.size() == 2) + { + for (size_t i = 0; i < points.size(); ++i) + { + t = (points[i][X] - A0[0]) / A0[1]; + if (t >= 0 && t <= 1) + { + xs.push_back(intersection_info(points[i], t, pt[i])); + } + } + return; + } + + // general case + // we compute the value of the parameter t of A at each intersection point + // and we filter the final result wrt the domain interval [0,1] + // the computation is performed by using the inversion formula for each point + // As reference see: + // Sederberger - Computer Aided Geometric Design + // par 16.5 - Implicitization and Inversion + size_t n = BZ.rows(); + Geom::NL::Matrix BZN(n, n); + Geom::NL::MatrixView BZV(BZN, 0, 0, n-1, n-1); + Geom::NL::VectorView cv = BZN.column_view(n-1); + Geom::NL::VectorView bv(cv, n-1); + Geom::NL::LinearSystem ls(BZV, bv); + for (size_t i = 0; i < points.size(); ++i) + { + // evaluate the first main minor of order n-1 at each intersection point + polynomial_matrix_evaluate(BZN, BZ, points[i]); + // solve the linear system with the powers of t as unknowns + ls.SV_solve(); + // the last element contains the t value + t = -ls.solution()[n-2]; + // filter with respect to the domain of A + if (t >= 0 && t <= 1) + { + xs.push_back(intersection_info(points[i], t, pt[i])); + } + } +} + + + +class IntersectImplicit : public Toy +{ + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.8, 0., 0, 1); + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0.0, 0., 0, 1); + D2<SBasis> B = pshB.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + intersect(xs, A, B); + for (auto & x : xs) + { + draw_handle(cr, x.p); + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + +public: + IntersectImplicit(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + handles.push_back(&pshA); + for (unsigned int i = 0; i <= A_bez_ord; ++i) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)); + handles.push_back(&pshB); + for (unsigned int i = 0; i <= B_bez_ord; ++i) + pshB.push_back(Geom::Point(uniform()*400, uniform()*400)); + + } + +private: + unsigned int A_bez_ord, B_bez_ord; + PointSetHandle pshA, pshB; + intersections_info xs; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord = 4; + unsigned int B_bez_ord = 6; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + + + init( argc, argv, new IntersectImplicit(A_bez_ord, B_bez_ord)); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/cylinder3d.cpp b/src/toys/cylinder3d.cpp new file mode 100644 index 0000000..19f3440 --- /dev/null +++ b/src/toys/cylinder3d.cpp @@ -0,0 +1,253 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> + +#include <gsl/gsl_matrix.h> + +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = p[i]; + cairo_d2_sb(cr, B); + } +} + +Geom::Point orig; + +static void draw_box (cairo_t *cr, Geom::Point corners[8]); +static void draw_slider_lines (cairo_t *cr); +static Geom::Point proj_image (cairo_t *cr, const double pt[4], const vector<Geom::Point> &handles); + +double tmat[3][4]; +double c[8][4]; +Geom::Point corners[8]; + +class Box3d: public Toy { + std::vector<Toggle> togs; + Path path_a; + Piecewise<D2<SBasis> > path_a_pw; + PointSetHandle hand; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + orig = hand.pts[7]; + + Geom::Point dir(1,-2); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + // draw vertical lines for the VP sliders and keep the sliders at their horizontal positions + draw_slider_lines (cr); + hand.pts[4][0] = 30; + hand.pts[5][0] = 45; + hand.pts[6][0] = 60; + + // draw the curve that is supposed to be projected on the box's front face + vector<Geom::Point>::iterator it = hand.pts.begin(); + for (int j = 0; j < 7; ++j) ++it; + + /* create the transformation matrix for the map P^3 --> P^2 that has the following effect: + (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0) + (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1) + (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2) + (0 : 0 : 0 : 1) --> origin (= handle #3) + */ + for (int j = 0; j < 4; ++j) { + tmat[0][j] = hand.pts[j][0]; + tmat[1][j] = hand.pts[j][1]; + tmat[2][j] = 1; + } + + *notify << "Projection matrix:" << endl; + for (auto & i : tmat) { + for (double j : i) { + *notify << j << " "; + } + *notify << endl; + } + + // draw the projective images of the box's corners + for (int i = 0; i < 8; ++i) { + corners[i] = proj_image (cr, c[i], hand.pts); + } + draw_box(cr, corners); + cairo_set_line_width (cr, 2); + cairo_stroke(cr); + + { + D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw); + Piecewise<SBasis> preimage[4]; + + if(togs[0].on) { + preimage[0] = sin((B[0] - orig[0]) / 100); + preimage[1] = -(B[1] - orig[1]) / 100; + preimage[2] = cos((B[0] - orig[0]) / 100); + } else { //if(togs[1].state) { + Piecewise<SBasis> sphi = sin((B[0] - orig[0]) / 200); + Piecewise<SBasis> cphi = cos((B[0] - orig[0]) / 200); + + preimage[0] = -sphi*sin((B[1] - orig[1]) / 200); + preimage[1] = -sphi*cos((B[1] - orig[1]) / 200); + preimage[2] = -cphi; + } + Piecewise<SBasis> res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = + (preimage[0]) * tmat[j][0] + + (preimage[1] - ((hand.pts[5][1]-300)/100)) * tmat[j][1] + + (preimage[2] - ((hand.pts[6][1]-00)/100)) * tmat[j][2] + +( - (hand.pts[4][1]-300)/100) * tmat[j][0] + tmat[j][3]; + } + //if (fabs (res[2]) > 0.000001) { + D2<Piecewise<SBasis> > result(divide(res[0],res[2], 4), + divide(res[1],res[2], 4)); + + cairo_d2_pw_sb(cr, result); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + } + draw_toggles(cr, togs); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + PathVector paths_a = read_svgd(path_a_name); + assert(!paths_a.empty()); + path_a = paths_a[0]; + + path_a.close(true); + path_a_pw = path_a.toPwSb(); + + // Finite images of the three vanishing points and the origin + hand.pts.emplace_back(150,300); + hand.pts.emplace_back(380,40); + hand.pts.emplace_back(550,350); + hand.pts.emplace_back(340,450); + + // Hand.Pts for moving in axes directions + hand.pts.emplace_back(30,300); + hand.pts.emplace_back(45,300); + hand.pts.emplace_back(60,300); + + // Box corners + for (int i = 0; i < 8; ++i) { + c[i][0] = ((i & 1) ? 1 : 0); + c[i][1] = ((i & 2) ? 1 : 0); + c[i][2] = ((i & 4) ? 1 : 0); + c[i][3] = 1; + } + + // Origin handle + hand.pts.emplace_back(180,70); + togs.emplace_back("S", true); + handles.push_back(&hand); + } + void key_hit(GdkEventKey *e) override { + if(e->keyval == 'c') togs[0].set(1); else + if(e->keyval == 's') togs[0].set(0); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(togs, e); + Toy::mouse_pressed(e); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Box3d); + return 0; +} + +void draw_box (cairo_t *cr, Geom::Point corners[8]) { + cairo_move_to(cr,corners[0]); + cairo_line_to(cr,corners[1]); + cairo_line_to(cr,corners[3]); + cairo_line_to(cr,corners[2]); + cairo_close_path(cr); + + cairo_move_to(cr,corners[4]); + cairo_line_to(cr,corners[5]); + cairo_line_to(cr,corners[7]); + cairo_line_to(cr,corners[6]); + cairo_close_path(cr); + + cairo_move_to(cr,corners[0]); + cairo_line_to(cr,corners[4]); + + cairo_move_to(cr,corners[1]); + cairo_line_to(cr,corners[5]); + + cairo_move_to(cr,corners[2]); + cairo_line_to(cr,corners[6]); + + cairo_move_to(cr,corners[3]); + cairo_line_to(cr,corners[7]); +} + +void draw_slider_lines (cairo_t *cr) { + cairo_move_to(cr, Geom::Point(20,300)); + cairo_line_to(cr, Geom::Point(70,300)); + + cairo_move_to(cr, Geom::Point(30,00)); + cairo_line_to(cr, Geom::Point(30,450)); + + cairo_move_to(cr, Geom::Point(45,00)); + cairo_line_to(cr, Geom::Point(45,450)); + + cairo_move_to(cr, Geom::Point(60,00)); + cairo_line_to(cr, Geom::Point(60,450)); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); +} + +static Geom::Point proj_image (cairo_t *cr, const double pt[4], const vector<Geom::Point> &handles) +{ + double res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = + tmat[j][0] * (pt[0] - (handles[4][1]-300)/100) + + tmat[j][1] * (pt[1] - (handles[5][1]-300)/100) + + tmat[j][2] * (pt[2] - (handles[6][1]-300)/100) + + tmat[j][3] * pt[3]; + } + if (fabs (res[2]) > 0.000001) { + Geom::Point result = Geom::Point (res[0]/res[2], res[1]/res[2]); + draw_handle(cr, result); + return result; + } + assert(0); // unclipped point + return Geom::Point(0,0); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/d2sbasis-fitting-with-np.cpp b/src/toys/d2sbasis-fitting-with-np.cpp new file mode 100644 index 0000000..05bcc6c --- /dev/null +++ b/src/toys/d2sbasis-fitting-with-np.cpp @@ -0,0 +1,147 @@ +/* + * D2<SBasis> Fitting Example + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class D2SBasisFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + bool changed = false; + for (size_t i = 0; i < total_handles; ++i) + { + if (psh.pts[i] != prev_pts[i]) + { + changed = true; + break; + } + } + if (changed) + { + for (size_t k = 0; k < 200; ++k) + { + lsf_2dsb.clear(); + lsf_2dsb.append(0); + lsf_2dsb.append(0.33); + double t = 0; + for (size_t i = 2; i < total_handles-2; ++i) + { + t = nearest_time(psh.pts[i], sb_curve); + lsf_2dsb.append(t); + } + lsf_2dsb.append(0.66); + lsf_2dsb.append(1); + lsf_2dsb.update(); + fmd2sb.instance(sb_curve, lsf_2dsb.result(psh.pts)); + } + prev_pts = psh.pts; + } + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + cairo_d2_sb(cr, sb_curve); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + D2SBasisFitting() + : sb_curve(), + total_handles(8), + order(total_handles / 2 - 1), + fmd2sb(order), + lsf_2dsb(fmd2sb, total_handles), + prev_pts() + { + step = 1.0 / (total_handles - 1); + for (size_t i = 0; i < total_handles; ++i) + { + psh.push_back(400*uniform() + 50, 300*uniform() + 50); + } + handles.push_back(&psh); + + double t = 0; + for (size_t i = 0; i < total_handles; ++i) + { + lsf_2dsb.append(t); + t += step; + } + prev_pts = psh.pts; + lsf_2dsb.update(); + fmd2sb.instance(sb_curve, lsf_2dsb.result(prev_pts)); + } + + private: + D2<SBasis> sb_curve; + unsigned int total_handles; + unsigned int order; + NL::LFMD2SBasis fmd2sb; + NL::least_squeares_fitter<NL::LFMD2SBasis> lsf_2dsb; + std::vector<Point> prev_pts; + double step; + PointSetHandle psh; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new D2SBasisFitting(), 600, 600 ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/d2sbasis-fitting.cpp b/src/toys/d2sbasis-fitting.cpp new file mode 100644 index 0000000..7e9b233 --- /dev/null +++ b/src/toys/d2sbasis-fitting.cpp @@ -0,0 +1,120 @@ +/* + * D2<SBasis> Fitting Example + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class D2SBasisFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + fmd2sb.instance(sb_curve, lsf_2dsb.result(prev_pts, psh.pts)); + prev_pts = psh.pts; + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + cairo_d2_sb(cr, sb_curve); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + D2SBasisFitting() + : sb_curve(), + total_handles(6), + order(total_handles / 2 - 1), + fmd2sb(order), + lsf_2dsb(fmd2sb, total_handles), + prev_pts() + { + step = 1.0 / (total_handles - 1); + for (size_t i = 0; i < total_handles; ++i) + { + psh.push_back(400*uniform() + 50, 300*uniform() + 50); + } + handles.push_back(&psh); + + double t = 0; + for (size_t i = 0; i < total_handles; ++i) + { + lsf_2dsb.append(t); + t += step; + } + prev_pts = psh.pts; + lsf_2dsb.update(); + fmd2sb.instance(sb_curve, lsf_2dsb.result(prev_pts)); + } + + private: + D2<SBasis> sb_curve; + unsigned int total_handles; + unsigned int order; + NL::LFMD2SBasis fmd2sb; + NL::least_squeares_fitter<NL::LFMD2SBasis> lsf_2dsb; + std::vector<Point> prev_pts; + double step; + PointSetHandle psh; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new D2SBasisFitting(), 600, 600 ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/data/london-locations.csv b/src/toys/data/london-locations.csv new file mode 100644 index 0000000..e7299c9 --- /dev/null +++ b/src/toys/data/london-locations.csv @@ -0,0 +1,298 @@ +Harrow & Wealdstone,75.2,163.5
+Kenton,86.1,153.6
+South Kenton,91,143.5
+North Wembley,94.2,136.8
+Wembley Central,99.1,128
+Stonebridge Park,110.7,122.8
+Harlesden,120.1,116.7
+Willesden Junction,128.3,112.4
+Kensal Green,137.7,111.6
+Queen's Park,148.6,113.5
+Kilburn Park,155.2,115.1
+Maida Vale,158.7,110
+Warwick Avenue,161.1,104.9
+Paddington,164.1,98.5
+Edgware Road,168.2,103.7
+Marylebone,171,105.1
+Baker Street,175.3,105.1
+Regent's Park,181.7,103.9
+Oxford Circus,184.3,97.5
+Piccadilly Circus,188.3,92.5
+Charing Cross,192.9,91.3
+Embankment,195.2,91
+Waterloo,199.6,87.6
+Lambeth North,201.3,84.9
+Elephant & Castle,208.3,81.1
+West Ruislip,20.7,139.9
+Ruislip Gardens,35.6,133.5
+South Ruislip,42.8,130.5
+Northolt,58.8,123.9
+Greenford,72.4,119.4
+Perivale,84.3,115.6
+Hanger Lane,103,109.7
+North Acton,121.2,104.4
+Ealing Broadway,98.3,93.7
+West Acton,108.6,98.8
+East Acton,127.1,99
+White City,138.5,95.1
+Shepherd's Bush,141.7,87.7
+Holland Park,148,90.3
+Notting Hill Gate,152.6,91.8
+Queensway,158.4,92.9
+Lancaster Gate,165,94
+Marble Arch,174.8,96
+Bond Street,179.8,96.8
+Tottenham Court Road,189.9,98.4
+Holborn,195.7,99.3
+Chancery Lane,199.9,100.1
+St Paul's,207.8,97.8
+Bank,213,96.1
+Liverpool Street,216.9,101
+Bethnal Green,234.3,109.1
+Mile End,243.4,107.7
+Stratford,259.5,121.2
+Leyton,259.1,136.9
+Leytonstone,266.9,145.2
+Snaresbrook,272.5,153.7
+South Woodford,276,164.6
+Woodford,279.7,180.2
+Wanstead,277.9,150.9
+Redbridge,286.9,152.9
+Gants Hill,296.3,152.4
+Newbury Park,310.1,152
+Barkingside,309.3,160.9
+Fairlop,310.4,170.7
+Hainault,311.4,175.2
+Grange Hill,310.4,185.8
+Chigwell,299.8,190.2
+Roding Valley,285.7,190.8
+Buckhurst Hill,285.6,195.5
+Loughton,290.1,213.7
+Debden,306.5,214.6
+Theydon Bois,315.1,241.9
+Epping,320.3,264.7
+Tower Hill,220.7,94.3
+Aldgate,220.4,98.8
+Moorgate,212.5,102.2
+Barbican,208.1,102.9
+Farringdon,203.2,105
+King's Cross St Pancras,193.1,112.1
+Euston Square,188.1,107.1
+Great Portland Street,183.3,105.4
+Edgware Road,169.6,102.2
+Bayswater,158,95.3
+High Street Kensington,155.2,84.6
+Gloucester Road,161.2,78.8
+South Kensington,166.5,78.5
+Sloane Square,176.5,77.4
+Victoria,183,79.3
+St James's Park,187.9,83.1
+Westminster,193.7,85.4
+Temple,199.2,94.2
+Blackfriars,205.3,94.7
+Mansion House,210.3,94.3
+Cannon Street,213,93.8
+Monument,215.5,93.1
+Ealing Common,105.3,91.1
+Acton Town,109.2,85.1
+Chiswick Park,114.9,80.3
+Turnham Green,123.5,79.5
+Richmond,99.7,49.4
+Kew Gardens,107.8,61.9
+Gunnersbury,111.3,75.6
+Stamford Brook,128.1,79.3
+Ravenscourt Park,133.3,79.2
+Hammersmith,139.1,77.3
+Barons Court,144.1,75.1
+West Kensington,148.9,75.7
+Earl's Court,154.8,76.8
+Kensington (Olympia),146.3,82
+Wimbledon,152,15.1
+Wimbledon Park,155.2,25.7
+Southfields,150.2,36.6
+East Putney,147.5,46.4
+Putney Bridge,149.1,54.9
+Parsons Green,152.3,62.1
+Fulham Broadway,155.2,67.3
+West Brompton,154.4,72.7
+Aldgate East,222.9,99.8
+Whitechapel,228.6,102.8
+Stepney Green,236.3,105.3
+Bow Road,249.5,107.1
+Bromley-by-Bow,256.1,106.2
+West Ham,264.2,110.7
+Plaistow,271.2,113.4
+Upton Park,280.7,116
+East Ham,291.9,120.2
+Barking,305.8,121.1
+Upney,316.3,118.7
+Becontree,330.3,121.8
+Dagenham Heathway,340.3,122.7
+Dagenham East,350.3,125.4
+Elm Park,369.8,130.4
+Hornchurch,378.4,133.8
+Upminster Bridge,386.5,137.2
+Upminster,397.5,138.6
+Shoreditch,223,103.3
+Shadwell,231.3,96
+Wapping,231.8,89.2
+Rotherhithe,233.1,85.5
+Canada Water,234.3,83.5
+Surrey Quays,237.1,78
+New Cross Gate,241.3,63.5
+New Cross,245,64.3
+Hammersmith,137.7,80.1
+Goldhawk Road,136.8,85.7
+Shepherd's Bush,137,89.2
+Latimer Road,142.5,95.9
+Ladbroke Grove,145.5,99.1
+Westbourne Park,150,101.9
+Royal Oak,158.1,100.2
+Stanmore,93.9,186.2
+Canons Park,97.2,177.9
+Queensbury,101.1,165.5
+Kingsbury,106.7,156.6
+Wembley Park,106.4,139.6
+Neasden,123.8,131.6
+Dollis Hill,129.7,129.9
+Willesden Green,139.1,128.9
+Kilburn,147.4,128.2
+West Hampstead,157.2,127.2
+Finchley Road,162.4,126.9
+Swiss Cottage,164.7,123.3
+St John's Wood,164.8,115.1
+Green Park,184.1,89.6
+Southwark,204.4,88.5
+London Bridge,214.4,89.7
+Bermondsey,225,84.5
+Canary Wharf,254,88.1
+North Greenwich,269.3,88
+Canning Town,267.7,96.2
+Watford,29.9,219.2
+Croxley,15.2,210.9
+Moor Park,22,194.9
+Northwood,27.7,177.9
+Northwood Hills,35.4,169.1
+Pinner,51.6,163.4
+North Harrow,60.4,157.3
+Harrow-on-the-Hill,76,149.6
+Uxbridge,2,121.7
+Hillingdon,14,126.4
+Ickenham,17.6,133.5
+Ruislip,28.9,142.2
+Ruislip Manor,34.2,144.6
+Eastcote,43.2,147.3
+Rayners Lane,56.3,146.9
+West Harrow,66.4,150
+Northwick Park,84.8,149.9
+Preston Road,97.8,146.4
+Edgware,107.3,182.3
+Burnt Oak,113.7,173.6
+Colindale,122.1,167.5
+Hendon Central,135.2,157.6
+Bent Cross,141.9,151.7
+Golders Green,154.3,146.9
+Hampstead,164.8,135.3
+Belsize Park,171.2,129.1
+Chalk Farm,176.2,124.3
+Camden Town,182,121.2
+High Barnet,150.8,219.3
+Totteridge & Whetstone,159.7,200.8
+Woodside Park,156.8,188.9
+West Finchley,154.5,181.9
+Finchley Central,154.5,172.7
+Mill Hill East,142.9,180.2
+East Finchley,168.9,161.8
+Highgate,180.5,152.8
+Archway,186.2,144.1
+Tufnell Park,186,136.6
+Kentish Town,184.4,129.9
+Mornington Crescent,184.6,114.9
+Euston,187.9,110.1
+Angel,203.9,113.6
+Old Street,212.2,108.4
+Borough,210.5,85.8
+Kennington,204.3,74.8
+Warren Street,185.4,104.9
+Goodge Street,187.9,101.9
+Leicester Square,191.4,94.2
+Oval,200,69.1
+Stockwell,195.3,60.4
+Clapham North,191.2,52.6
+Clapham Common,186,49.2
+Clapham South,181.6,41.9
+Balham,178.6,34.2
+Tooting Bec,176.2,28.2
+Tooting Broadway,171.2,17.6
+Colliers Wood,166.8,11.2
+South Wimbledon,159.4,7.9
+Morden,155.1,1.5
+Heathrow Terminals 1-2-3,20.5,53.7
+Heathrow Terminal 4,24.4,46.8
+Hatton Cross,35,51.4
+Hounslow West,57,57
+Hounslow Central,65.2,55.4
+Hounslow East,70.8,57.8
+Osterley,74,65
+Boston Manor,87.1,76.2
+Northfields,91.8,80.2
+South Ealing,95.6,81.7
+South Harrow,67.8,137.1
+Sudbury Hill,75.6,131.2
+Sudbury Town,88.1,127
+Alperton,97.1,119
+Park Royal,107,106
+North Ealing,105.2,98.1
+Knightsbridge,173.7,85.1
+Hyde Park Corner,178.1,86.5
+Covent Garden,193.7,95.6
+Russell Square,192.9,104.7
+Caledonian Road,196,128.8
+Holloway Road,200.2,132.5
+Arsenal,202.7,137.9
+Finsbury Park,203.8,143.9
+Manor House,208.6,149.6
+Turnpike Lane,204.2,165.8
+Wood Green,200.5,172.4
+Bounds Green,190.4,181.2
+Arnos Grove,186.3,189
+Southgate,188.2,201.3
+Oakwood,186.3,216.8
+Cockfosters,177.6,220.3
+Brixton,201.7,52.2
+Vauxhall,194.7,72.4
+Pimlico,189.8,75.5
+Highbury & Islington,204.1,126
+Seven Sisters,219.2,160.5
+Tottenham Hale,228.2,164.8
+Blackhorse Road,238.5,162.7
+Walthamstow Central,253.4,161.1
+Limehouse,239.2,97.1
+Westferry,246.8,95.3
+Poplar,254.6,93.8
+All Saints,256.2,96.4
+Devons Road,253.3,103.8
+Bow Church,250.9,108.9
+Pudding Mill Lane,255.8,115.7
+West India Quay,252.2,91.3
+Heron Quay,252,86.8
+South Quay,254.1,85.5
+Crossharbour and London Arena,256.1,80.3
+Mudchute,257.1,76.5
+Island Gardens,259,74.3
+Cutty Sark,259.2,69.3
+Greenwich,256.1,65.7
+Deptford Bridge,253.9,63.7
+Elverston Road,254.6,58.3
+Lewisham,256.5,54.5
+Blackwall,258.7,93.7
+East India,263.3,94.3
+Royal Victoria,272.5,93
+Custom House,277.9,93.2
+Prince Regent,281.4,93.3
+Royal Albert,288.9,93.2
+Beckton Park,292.7,93.3
+Cyprus,296.6,93.3
+Gallions Reach,301.5,94.2
+Beckton,296,99.3
+Tower Gateway,222.7,94.7
diff --git a/src/toys/data/london.txt b/src/toys/data/london.txt new file mode 100644 index 0000000..49e6f9c --- /dev/null +++ b/src/toys/data/london.txt @@ -0,0 +1,86 @@ +Bakerloo Line: +Harrow & Wealdstone,Kenton,South Kenton,North Wembley,Wembley Central,Stonebridge Park,Harlesden,Willesden Junction,Kensal Green,Queen's Park,Kilburn Park,Maida Vale,Warwick Avenue,Paddington,Edgware Road,Marylebone,Baker Street,Regent's Park,Oxford Circus,Piccadilly Circus,Charing Cross,Embankment,Waterloo,Lambeth North,Elephant & Castle + +Central Line 1: +West Ruislip,Ruislip Gardens,South Ruislip,Northolt,Greenford,Perivale,Hanger Lane,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Snaresbrook,South Woodford,Woodford,Buckhurst Hill,Loughton,Debden,Theydon Bois,Epping + +Central Line 2: +West Ruislip,Ruislip Gardens,South Ruislip,Northolt,Greenford,Perivale,Hanger Lane,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Wanstead,Redbridge,Gants Hill,Newbury Park,Barkingside,Fairlop,Hainault,Grange Hill,Chigwell,Roding Valley,Woodford + +Central Line 3: +Ealing Broadway,West Acton,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Snaresbrook,South Woodford,Woodford,Buckhurst Hill,Loughton,Debden,Theydon Bois,Epping + +Central Line 4: +Ealing Broadway,West Acton,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Wanstead,Redbridge,Gants Hill,Newbury Park,Barkingside,Fairlop,Hainault,Grange Hill,Chigwell,Roding Valley,Woodford + +Circle Line 1: +Westminster,St James's Park,Victoria,Sloane Square,South Kensington,Gloucester Road,High Street Kensington,Notting Hill Gate,Bayswater,Paddington,Edgware Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate + +Circle Line 2: +Tower Hill,Monument,Cannon Street,Mansion House,Blackfriars,Temple,Embankment,Westminster + +Hammersmith & City Line: +Hammersmith,Goldhawk Road,Shepherd's Bush,Latimer Road,Ladbroke Grove,Westbourne Park,Royal Oak,Paddington,Edgware Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking + +District Line 1: +Ealing Broadway,Ealing Common,Acton Town,Chiswick Park,Turnham Green,Stamford Brook,Ravenscourt Park,Hammersmith,Barons Court,West Kensington,Earl's Court,Gloucester Road,South Kensington,Sloane Square,Victoria,St James's Park,Westminster,Embankment,Temple,Blackfriars,Mansion House,Cannon Street,Monument,Tower Hill,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking,Upney,Becontree,Dagenham Heathway,Dagenham East,Elm Park,Hornchurch,Upminster Bridge,Upminster + +District Line 2: +Richmond,Kew Gardens,Gunnersbury,Turnham Green,Stamford Brook,Ravenscourt Park,Hammersmith,Barons Court,West Kensington,Earl's Court,Gloucester Road,South Kensington,Sloane Square,Victoria,St James's Park,Westminster,Embankment,Temple,Blackfriars,Mansion House,Cannon Street,Monument,Tower Hill,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking,Upney,Becontree,Dagenham Heathway,Dagenham East,Elm Park,Hornchurch,Upminster Bridge,Upminster + +District Line 3: +Wimbledon,Wimbledon Park,Southfields,East Putney,Putney Bridge,Parsons Green,Fulham Broadway,West Brompton,Earl's Court,Gloucester Road,South Kensington,Sloane Square,Victoria,St James's Park,Westminster,Embankment,Temple,Blackfriars,Mansion House,Cannon Street,Monument,Tower Hill,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking,Upney,Becontree,Dagenham Heathway,Dagenham East,Elm Park,Hornchurch,Upminster Bridge,Upminster + +District Line 4: +Kensington (Olympia),Earl's Court,High Street Kensington,Notting Hill Gate,Bayswater,Paddington,Edgware Road + +East London Line 1: +Shoreditch,Whitechapel,Shadwell,Wapping,Rotherhithe,Canada Water,Surrey Quays,New Cross + +East London Line 2: +Shoreditch,Whitechapel,Shadwell,Wapping,Rotherhithe,Canada Water,Surrey Quays,New Cross Gate + +Jubilee Line: +Stratford,West Ham,Canning Town,North Greenwich,Canary Wharf,Canada Water,Bermondsey,London Bridge,Southwark,Waterloo,Westminster,Green Park,Bond Street,Baker Street,St John's Wood,Swiss Cottage,Finchley Road,West Hampstead,Kilburn,Willesden Green,Dollis Hill,Neasden,Wembley Park,Kingsbury,Queensbury,Canons Park,Stanmore + +Metropolitan Line 1: +Moor Park,Northwood,Northwood Hills,Pinner,North Harrow,Harrow-on-the-Hill,Northwick Park,Preston Road,Wembley Park,Finchley Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate + +Metropolitan Line 2: +Watford,Croxley,Moor Park,Northwood,Northwood Hills,Pinner,North Harrow,Harrow-on-the-Hill,Northwick Park,Preston Road,Wembley Park,Finchley Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate + +Metropolitan Line 3: +Uxbridge,Hillingdon,Ickenham,Ruislip,Ruislip Manor,Eastcote,Rayners Lane,West Harrow,Harrow-on-the-Hill,Northwick Park,Preston Road,Wembley Park,Finchley Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate + +Northern Line 1: +High Barnet,Totteridge & Whetstone,Woodside Park,West Finchley,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Euston,King's Cross St Pancras,Angel,Old Street,Moorgate,Bank,London Bridge,Borough,Elephant & Castle,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 2: +High Barnet,Totteridge & Whetstone,Woodside Park,West Finchley,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Mornington Crescent,Euston,Warren Street,Goodge Street,Tottenham Court Road,Leicester Square,Charing Cross,Embankment,Waterloo,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 3: +Mill Hill East,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Euston,King's Cross St Pancras,Angel,Old Street,Moorgate,Bank,London Bridge,Borough,Elephant & Castle,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 4: +Mill Hill East,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Mornington Crescent,Euston,Warren Street,Goodge Street,Tottenham Court Road,Leicester Square,Charing Cross,Embankment,Waterloo,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 5: +Edgware,Burnt Oak,Colindale,Hendon Central,Bent Cross,Golders Green,Hampstead,Belsize Park,Chalk Farm,Camden Town,Euston,King's Cross St Pancras,Angel,Old Street,Moorgate,Bank,London Bridge,Borough,Elephant & Castle,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 6: +Edgware,Burnt Oak,Colindale,Hendon Central,Bent Cross,Golders Green,Hampstead,Belsize Park,Chalk Farm,Camden Town,Mornington Crescent,Euston,Warren Street,Goodge Street,Tottenham Court Road,Leicester Square,Charing Cross,Embankment,Waterloo,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Piccadilly Line 1: +Uxbridge,Hillingdon,Ickenham,Ruislip,Ruislip Manor,Eastcote,Rayners Lane,South Harrow,Sudbury Hill,Sudbury Town,Alperton,Park Royal,North Ealing,Ealing Common,Acton Town,Turnham Green,Hammersmith,Barons Court,Earl's Court,Gloucester Road,South Kensington,Knightsbridge,Hyde Park Corner,Green Park,Piccadilly Circus,Leicester Square,Covent Garden,Holborn,Russell Square,King's Cross St Pancras,Caledonian Road,Holloway Road,Arsenal,Finsbury Park,Manor House,Turnpike Lane,Wood Green,Bounds Green,Arnos Grove,Southgate,Oakwood,Cockfosters + +Piccadilly Line 2: +Heathrow Terminal 4,Hatton Cross,Hounslow West,Hounslow Central,Hounslow East,Osterley,Boston Manor,Northfields,South Ealing,Acton Town,Turnham Green,Hammersmith,Barons Court,Earl's Court,Gloucester Road,South Kensington,Knightsbridge,Hyde Park Corner,Green Park,Piccadilly Circus,Leicester Square,Covent Garden,Holborn,Russell Square,King's Cross St Pancras,Caledonian Road,Holloway Road,Arsenal,Finsbury Park,Manor House,Turnpike Lane,Wood Green,Bounds Green,Arnos Grove,Southgate,Oakwood,Cockfosters + +Piccadilly Line 3: +Hatton Cross,Heathrow Terminals 1-2-3,Heathrow Terminal 4 + +Victoria Line: +Walthamstow Central,Blackhorse Road,Tottenham Hale,Seven Sisters,Finsbury Park,Highbury & Islington,King's Cross St Pancras,Euston,Warren Street,Oxford Circus,Green Park,Victoria,Pimlico,Vauxhall,Stockwell,Brixton + +Waterloo & City Line: +Waterloo,Bank diff --git a/src/toys/data/nsw-centre.txt b/src/toys/data/nsw-centre.txt new file mode 100644 index 0000000..73b6817 --- /dev/null +++ b/src/toys/data/nsw-centre.txt @@ -0,0 +1,56 @@ +Olympic Park I: +Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Olympic Park + +Olympic Park II: +Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Olympic Park + +Eastern Suburbs & Illawarra 1: +Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +Eastern Suburbs & Illawarra 2: +Cronulla,Woolooware,Caringbah,Miranda,Gymea,Kirrawee,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +Bankstown Line a: +Town Hall,Wynyard,Circular Quay,St James,Museum,Central,Redfern,Erskineville,St Peters,Sydenham,Marrickville,Dulwich Hill,Hurlstone Park,Canterbury,Campsie,Belmore,Lakemba,Wiley Park,Punchbowl,Bankstown,Yagoona,Birrong,Regents Park,Berala,Lidcombe + +Bankstown Line b: +Lidcombe,Berala,Regents Park,Birrong,Sefton,Chester Hill,Leightonfield,Villawood,Carramar,Cabramatta,Warwick Farm,Liverpool + +Inner West 1a: +Liverpool,Warwick Farm,Cabramatta,Carramar,Villawood,Leightonfield,Chester Hill,Sefton,Regents Park,Berala,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Inner West 1b: +Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood + +Inner West 2a: +Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Inner West 2b: +Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood + +Airport & East Hills 1: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Wolli Creek,International,Domestic,Mascot,Green Square,Central,Museum,St James,Circular Quay,Wynyard,Town Hall + +Airport & East Hills 2: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Museum,St James,Circular Quay,Wynyard,Town Hall + +South: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Cumberland: +Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown,Doonside,Rooty Hill,Mt Druitt,St Marys + +Carlingford Line: +Carlingford,Telopea,Dundas,Rydalmere,Camellia,Rosehill,Clyde + +Western 1: +Richmond,East Richmond,Clarendon,Windsor,Mulgrave,Vineyard,Riverstone,Schofields,Quakers Hill,Marayong,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +Western 2: +Emu Plains,Penrith,Kingswood,Werrington,St Marys,Mt Druitt,Rooty Hill,Doonside,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +North Shore: +Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Waitara,Wahroonga,Warrawee,Turramurra,Pymble,Gordon,Killara,Lindfield,Roseville,Chatswood,Artarmon,St Leonards,Wollstonecraft,Waverton,North Sydney,Milsons Point,Wynyard,Town Hall,Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Lidcombe,Auburn,Clyde,Granville,Harris Park,Parramatta + +Northern: +Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney diff --git a/src/toys/data/nsw-locations.csv b/src/toys/data/nsw-locations.csv new file mode 100644 index 0000000..2cb6d13 --- /dev/null +++ b/src/toys/data/nsw-locations.csv @@ -0,0 +1,805 @@ +Aberdare Junction,151.513199,-32.773602
+Aberdeen,150.892593,-32.165401
+Adamstown,151.718307,-32.933899
+Albion Park,150.798203,-34.562698
+Albury,146.922302,-36.0863
+Alectown West,148.171906,-32.934101
+Allandale,151.416306,-32.720699
+Allawah,151.113098,-33.9692
+Alleena,147.134903,-34.072399
+Antiene,150.984695,-32.335999
+Ariah Park,147.2164,-34.344398
+Armidale,151.652206,-30.514
+Arncliffe,151.147095,-33.935299
+Artarmon,151.185593,-33.8069
+Ashfield,151.125504,-33.886799
+Ashley,149.800201,-29.297001
+Asquith,151.1082,-33.688202
+Athol,149.303604,-33.5452
+Auburn,151.032501,-33.8489
+Austinmer,150.928207,-34.305901
+Awaba,151.540604,-33.012501
+Baan Baa,149.951096,-30.599501
+Balldale,146.515106,-35.846199
+Ballina,153.554993,-28.860001
+Balmoral,150.521194,-34.305901
+Bangaroo,148.632599,-33.627602
+Banksia,151.138702,-33.944099
+Bankstown,151.034897,-33.917
+Barairmol,152.995102,-28.719601
+Barbigal,148.839905,-32.223202
+Bardwell Park,151.124405,-33.930901
+Bargo,150.579102,-34.291
+Bathurst,149.582199,-33.425999
+Beanbri,148.405701,-29.9536
+Beecroft,151.066803,-33.749199
+Beelbangera,146.099792,-34.254002
+Belford,151.274902,-32.6544
+Bell,150.278107,-33.506001
+Bellambi,150.909195,-34.362099
+Bellarwi,147.194702,-34.095001
+Bellata,149.786407,-29.918501
+Belmore,151.089203,-33.9161
+Bengerang,149.519608,-29.0714
+Beni,148.750595,-32.195099
+Berala,151.031906,-33.870998
+Beresfield,151.6586,-32.798901
+Berowra,151.153397,-33.623699
+Berry,150.695694,-34.780701
+Bethungra,147.860306,-34.763699
+Beverly Hills,151.082108,-33.9478
+Bexhill,153.346893,-28.761101
+Bexley North,151.111404,-33.937099
+Birrong,151.024399,-33.892601
+Black Springs,150.629395,-30.442801
+Blackheath,150.281998,-33.633301
+Blacktown,150.908707,-33.7668
+Blandford,150.898605,-31.788799
+Blaxland,150.608902,-33.742001
+Blayney,149.253799,-33.5261
+Bloomfield,149.105606,-33.319401
+Blue Cow,148.389801,-36.383701
+Boambee,153.1073,-30.348801
+Bogan Gate,147.801498,-33.108299
+Boggabri,150.038803,-30.702999
+Bomaderry,150.609695,-34.853298
+Bombo,150.852707,-34.658001
+Bomera,149.798798,-31.548
+Bon Accord,147.190903,-35.226501
+Bonalbo,152.623154,-28.73671
+Bondi Junction,151.246506,-33.893101
+Bonville,153.061996,-30.379
+Boomley,149.1035,-32.026798
+Booragul,151.604797,-32.970798
+Boothenba,148.689606,-32.198101
+Borenore,148.975693,-33.253101
+Bourke,145.933701,-30.094601
+Bowenfels,150.135406,-33.474998
+Bowral,150.416107,-34.4781
+Box Tank,142.2276,-32.201099
+Braemar,150.498596,-34.412498
+Branxton,151.345703,-32.6614
+Braunstone,152.961807,-29.8027
+Brightling,148.404999,-31.2185
+Broadmeadow,151.734406,-32.921902
+Broken Hill,141.466003,-31.9604
+Brolgan,148.063507,-33.142502
+Buddigower,147.008698,-34.0438
+Bullaburra,150.412796,-33.722301
+Bulli,150.9142,-34.333401
+Bulliac,152.035599,-31.914499
+Bullocks Flat,148.443207,-36.444698
+Bundanoon,150.300705,-34.655399
+Bundook,152.1259,-31.891199
+Bungabbee,153.146393,-28.780199
+Bungendore,149.444901,-35.254601
+Bunyan,149.158997,-36.1712
+Buralyang,146.760498,-33.974098
+Burgooney,146.574097,-33.385399
+Burradoo,150.397293,-34.493999
+Burrawang,150.529999,-34.581402
+Burren,148.966705,-30.1061
+Burringbar,153.471497,-28.4331
+Burwood,151.104706,-33.876301
+Buxton,150.532501,-34.2547
+Bygalorie,146.781693,-33.469002
+Byron,151.093994,-29.736401
+Cabramatta,150.938995,-33.8927
+Calleen,147.105804,-33.787701
+Calwalla,150.474304,-34.5625
+Camden,150.696899,-34.050701
+Camellia,151.025604,-33.817001
+Camira Creek,152.964203,-29.250401
+Campbelltown,150.812607,-34.063801
+Campsie,151.101303,-33.909901
+Canberra,149.150497,-35.3167
+Canberra (Civic),149.132904,-35.262299
+Canley Vale,150.944199,-33.886501
+Canterbury,151.117493,-33.91
+Capertee,149.986496,-33.149899
+Caragabal,147.740707,-33.839802
+Cardiff,151.662292,-32.941299
+Caringbah,151.121094,-34.0415
+Carlingford,151.046204,-33.780102
+Carlton,151.123795,-33.967701
+Carramar,150.960907,-33.884201
+Casino,153.037903,-28.860201
+Casula,150.910797,-33.9487
+Central,151.205704,-33.882999
+Cessnock,151.361694,-32.8419
+Chakola,149.184296,-36.099602
+Charity Creek,152.231003,-31.9016
+Chatswood,151.181,-33.796799
+Cheltenham,151.078796,-33.754601
+Chester Hill,150.996597,-33.883202
+Circular Quay,151.210907,-33.861198
+Civic,151.772903,-32.9249
+Clarendon,150.790497,-33.610001
+Clifton,150.967804,-34.2584
+Clyde,151.017197,-33.8349
+Coalcliff,150.976501,-34.241001
+Cobar,145.845398,-31.4939
+Cockle Creek,151.622406,-32.9426
+Coffs Harbour,153.138596,-30.3027
+Coledale,150.942596,-34.288799
+Collombatti,152.825699,-30.9755
+Colo Vale,150.4869,-34.3993
+Combara,148.370193,-31.1229
+Como,151.067902,-34.004799
+Compton Downs,146.571304,-30.4224
+Concord West,151.0858,-33.847599
+Condobolin,147.147995,-33.083302
+Coniston,150.884598,-34.437
+Conoble,144.707703,-32.9739
+Cooerwull,150.142105,-33.4818
+Cookamidgera,148.302902,-33.202801
+Coolalie,148.983704,-34.7854
+Coolamon,147.202194,-34.8167
+Cooma,149.133804,-36.2355
+Coombell,152.971893,-29.0159
+Coonong,146.122101,-35.1297
+Coopernook,152.600098,-31.8057
+Cootamundra,148.031006,-34.6399
+Coramba,153.019608,-30.2251
+Coreinbob,147.630905,-35.210999
+Corowa,146.384399,-35.996399
+Corrimal,150.904099,-34.374901
+Corringle,147.347397,-33.632801
+Couridjah,150.547897,-34.232601
+Cowan,151.170593,-33.591999
+Cowra,148.699005,-33.834702
+Cowra West,148.68248,-33.833015
+Crabbes Creek,153.501007,-28.457001
+Craboon,149.460297,-32.0294
+Craven,151.941498,-32.153999
+Cringila,150.877106,-34.465302
+Cronulla,151.150497,-34.056198
+Crooble,150.253998,-29.266199
+Croppa Creek,150.304703,-29.1269
+Crowther,148.502502,-34.0979
+Croydon,151.115799,-33.8829
+Cubbaroo,149.125198,-30.171499
+Culcairn,147.035797,-35.669899
+Culgoora,149.5849,-30.2798
+Cullen Bullen,150.006699,-33.3009
+Cullerin,149.407104,-34.783901
+Cullivel,146.379593,-35.259701
+Cullya,149.109695,-33.199699
+Curlewis,150.270401,-31.116699
+Currabubula,150.7341,-31.2626
+Dapto,150.790695,-34.492599
+Darnick,143.637894,-32.849602
+Daroobalgie,148.052002,-33.329899
+Deepwater,151.851593,-29.4387
+Denistone,151.087799,-33.798401
+Deringulla,149.326797,-31.3883
+Dingadee,151.7901,-32.368
+Domestic,151.179596,-33.930401
+Doonside,150.866196,-33.7631
+Dora Creek,151.500107,-33.080502
+Dorrigo,152.707199,-30.3319
+Douglas Park,150.709305,-34.182598
+Dripstone,148.990494,-32.644001
+Dubbo,148.605392,-32.245602
+Dulwich Hill,151.139801,-33.909302
+Dumaresq,151.580795,-30.459801
+Dunbible,153.401703,-28.380199
+Dundas,151.033096,-33.804001
+Dungog,151.759003,-32.398602
+Dunmore,150.839493,-34.605301
+East Hills,150.9841,-33.960602
+East Maitland,151.587494,-32.744499
+East Richmond,150.758194,-33.6017
+Eastwood,151.082794,-33.789501
+Edgecliff,151.239105,-33.879902
+Edgeroi,149.797394,-30.1171
+Elderslie,150.704895,-34.053299
+Eltham,153.3974,-28.753799
+Emerald Hill,150.104706,-30.8857
+Emu Plains,150.671799,-33.744202
+Engadine,151.013702,-34.067299
+Epping,151.082397,-33.770901
+Erigolia,146.356293,-33.8568
+Erskineville,151.184799,-33.9006
+Ettamogah,146.978806,-36.022499
+Euabalong West,146.391296,-33.054401
+Eulomogo,148.692795,-32.2887
+Eungai,152.900101,-30.848801
+Eurabba,147.8255,-34.055199
+Euratha,146.548401,-33.878799
+Eurie Eurie,148.252899,-29.964899
+Euston,142.764008,-34.569801
+Excelsior,149.974899,-33.0681
+Exeter,150.317307,-34.613499
+Fairfield,150.957703,-33.871101
+Fairview,148.159698,-32.378201
+Fairy Hill,153.008804,-28.770901
+Fairy Meadow,150.895203,-34.394699
+Farley,151.519501,-32.726398
+Fassifern,151.580902,-32.984798
+Faulconbridge,150.536194,-33.696499
+Finley,145.572998,-35.649799
+Fish River,149.3125,-34.7589
+Flemington,151.069901,-33.864101
+Forbes,148.010193,-33.374802
+Frampton,147.922501,-34.695301
+Gadara,148.158798,-35.320999
+Garah,149.634598,-29.0818
+Garema,147.937805,-33.551498
+Garoolgan,146.441101,-34.25
+Gerringong,150.8172,-34.744801
+Geurie,148.828995,-32.397301
+Gidginbung,147.4711,-34.3246
+Gidley,150.846893,-31.0065
+Girral,147.071106,-33.707699
+Glen Innes,151.726898,-29.7372
+Glenbrook,150.617905,-33.767601
+Glencoe,151.723694,-29.9231
+Glenfield,150.892105,-33.970699
+Glenlogan,148.653,-33.7691
+Glenroy,147.944107,-35.7495
+Gloucester,151.965897,-32.0042
+Gobondery,147.594193,-32.691002
+Gooloogong,148.449097,-33.567402
+Goondah,148.730499,-34.733601
+Goonumbla,148.1353,-32.9809
+Gooramma,148.622101,-34.493
+Gordon,151.155701,-33.754501
+Gosford,151.341003,-33.422699
+Gosford Racecourse,151.327194,-33.422401
+Goulburn,149.718002,-34.757801
+Grafton,152.925797,-29.683701
+Grafton City,152.941803,-29.7027
+Grahams Hill,150.730301,-34.040798
+Granville,151.012604,-33.831799
+Grasstree,150.965607,-32.285702
+Grawlin Plains,148.035995,-33.467701
+Green Square,151.1996,-33.9053
+Greenethorpe,148.401398,-33.997898
+Greenwood,151.008896,-29.731701
+Gresham,149.405197,-33.569199
+Greta,151.384201,-32.685799
+Griffith,146.045197,-34.285801
+Gubbata,146.5513,-33.637299
+Guildford,150.983795,-33.853699
+Gum Lake,143.177399,-32.679699
+Gunebang,146.682602,-33.014
+Gungal,150.500397,-32.266998
+Gunnedah,150.253098,-30.9821
+Gunning,149.262802,-34.776798
+Gunningbland,147.915207,-33.132801
+Gurley,149.799393,-29.7342
+Gurranang,152.996506,-29.459299
+Gwabegar,148.969101,-30.611
+Gymea,151.084305,-34.035
+Hadleigh,150.453598,-29.5877
+Hamilton,151.747696,-32.916901
+Hannan,146.417999,-33.6222
+Harden,148.370193,-34.5541
+Harris Park,151.007095,-33.822498
+Hartley Vale,150.261993,-33.5392
+Hawkesbury River,151.226196,-33.545399
+Hay,144.842102,-34.498299
+Hazelbrook,150.452606,-33.7229
+Heathcote,151.007706,-34.087502
+Helensburgh,150.993607,-34.1759
+Henty,147.032501,-35.516602
+Hermidale,146.723694,-31.548401
+Herons Creek,152.727295,-31.5891
+Hexham,151.684006,-32.8302
+High Street,151.561905,-32.740601
+Hilldale,151.648499,-32.504002
+Hillston,145.535507,-33.479099
+Hill Top,150.4936,-34.3540
+Holsworthy,150.955704,-33.961601
+Homebush,151.086594,-33.8661
+Hopefield,146.435806,-35.8918
+Hornsby,151.098495,-33.701302
+Horse Lake,142.065903,-32.115002
+Hurlstone Park,151.131897,-33.909698
+Hurstville,151.100601,-33.966099
+Illabo,147.741699,-34.814098
+Illalong Creek,148.656204,-34.715599
+Ingleburn,150.865494,-33.995701
+International,151.166107,-33.935001
+Ivanhoe,144.304092,-32.913502
+Jannali,151.064194,-34.015598
+Jaspers Brush,150.667007,-34.805401
+Jerilderie,145.723206,-35.361198
+Jindalee,148.088806,-34.577301
+Junee,147.582596,-34.870098
+Kadungle,147.621902,-32.758701
+Kahibah,151.718201,-32.9618
+Kamarah,146.7827,-34.324299
+Kamber,148.608795,-31.6383
+Kandos,149.968704,-32.859299
+Kankool,150.772705,-31.6975
+Kapooka,147.298492,-35.159302
+Karaak Flat,152.285004,-31.9237
+Katoomba,150.310593,-33.710701
+Kellys Plains,151.646393,-30.5667
+Kelso,149.604401,-33.422199
+Kembla Grange Racecourse,150.817795,-34.468899
+Kempsey,152.832993,-31.0765
+Kendall,152.706696,-31.6341
+Kenebri,149.0224,-30.7768
+Kentucky,151.449799,-30.7621
+Kiama,150.854095,-34.671902
+Kikoira,146.659698,-33.653198
+Kilgra,152.975906,-28.5497
+Killara,151.162598,-33.763901
+Killawarra,152.311707,-31.889999
+Kinalung,141.959396,-32.058701
+Kings Cross,151.219193,-33.8717
+Kingsgrove,151.098801,-33.940498
+Kingswood,150.719894,-33.756001
+Kirkham,150.715302,-34.046001
+Kirrawee,151.070801,-34.034599
+Kogarah,151.132095,-33.961498
+Kolodong,152.429092,-31.888399
+Komungla,149.645599,-34.864799
+Koolbury,150.902893,-32.216599
+Koolewong,151.315994,-33.464401
+Koolkhan,152.935303,-29.619699
+Koonadan,146.365097,-34.479801
+Koorakee,142.9245,-34.454498
+Kootingal,151.058502,-31.055799
+Korangi,153.045105,-30.256901
+Kotara,151.703094,-32.938599
+Kundabung,152.835495,-31.2082
+Kungala,153.008194,-29.948099
+Kurrajong,150.665604,-33.555
+Kyogle,153.002502,-28.6206
+Lake Cowal,147.357407,-33.688499
+Lakemba,151.076202,-33.919102
+Langtree,145.564499,-33.661598
+Langunyah,145.572205,-35.729198
+Lanitza,153.000305,-29.881399
+Lapstone,150.642807,-33.773499
+Larras Lee,148.859406,-33.000801
+Laureldale,153.414597,-28.7514
+Lawrance Road,153.009399,-29.3965
+Lawson,150.428101,-33.718102
+Leeton,146.395096,-34.553001
+Leeville,152.994202,-28.943399
+Leightonfield,150.9841,-33.881599
+Leniston,145.681503,-35.6506
+Lette,143.170593,-34.354198
+Leumeah,150.828903,-34.051399
+Leura,150.328201,-33.711201
+Lewisham,151.148193,-33.893101
+Leycester,153.196899,-28.7803
+Liamena,149.336304,-31.9685
+Lidcombe,151.044907,-33.862099
+Lilyvale,151.006195,-34.191002
+Limbri,151.156296,-31.033701
+Linden,150.504501,-33.713001
+Lindfield,151.169907,-33.773201
+Lisarow,151.369598,-33.3806
+Lithgow,150.156204,-33.479301
+Liverpool,150.9263,-33.923599
+Lochinvar,151.449707,-32.720299
+Loftus,151.0504,-34.044701
+Lucan,148.988998,-33.719002
+Lysaghts,150.875305,-34.453999
+Macarthur,150.795898,-34.071201
+Macdonaldtown,151.186203,-33.896301
+Macksville,152.912796,-30.7082
+Macquarie Fields,150.878098,-33.983799
+Maitland,151.550995,-32.737202
+Maldon,150.633301,-34.191299
+Marayong,150.8992,-33.7458
+Marrangaroo,150.112701,-33.449699
+Marrickville,151.155502,-33.9132
+Martin Place,151.211807,-33.8666
+Martins Creek,151.617599,-32.557899
+Marulan,150.006104,-34.7113
+Mary Vale,148.906799,-32.473999
+Mascot,151.1866,-33.921501
+Matakana,145.903595,-32.990002
+Meadowbank,151.090103,-33.815399
+Medlow Bath,150.281494,-33.674301
+Melinga,152.519104,-31.808901
+Menangle,150.743301,-34.124401
+Menangle Park,150.743805,-34.1021
+Mendooran,149.128693,-31.833401
+Menindee,142.425003,-32.3895
+Merah North,149.293793,-30.183901
+Merrylands,150.992203,-33.835602
+Merrywinebone,148.815399,-29.686001
+Metford,151.614807,-32.761501
+Michelago,149.165298,-35.710499
+Mickibri,148.194702,-32.8606
+Middlefield,147.575302,-32.4356
+Milguy,150.202805,-29.350901
+Milsons Point,151.211395,-33.8456
+Milsons Point (1st),151.209396,-33.850498
+Milsons Point (2nd),151.210297,-33.8475
+Mindaribba,151.5844,-32.6667
+Minemoorong,147.450195,-32.282299
+Minnamurra,150.8517,-34.625401
+Minto,150.8414,-34.026901
+Miowera,147.333298,-31.636499
+Miranda,151.102005,-34.036499
+Mittagong,150.447006,-34.452099
+Moleton,152.903503,-30.1607
+Molong,148.869904,-33.094299
+Molonglo,149.173996,-35.330601
+Mooball,153.492996,-28.440399
+Moombooldool,146.672897,-34.299999
+Mooren,149.336502,-31.6875
+Moppin,149.747299,-29.204901
+Moree,149.846405,-29.4736
+Morisset,151.486298,-33.110298
+Morpeth,151.626907,-32.724602
+Morpeth (1st),151.626877,-32.72459
+Morrisons Hill,148.109497,-34.533901
+Mortdale,151.0811,-33.9702
+Mortuary,151.202393,-33.885601
+Moss Vale,150.372803,-34.5452
+Moulamein,144.038895,-35.0882
+Mt Colah,151.115204,-33.6712
+Mt Druitt,150.818893,-33.768799
+Mount Gipps,141.598007,-31.898899
+Mt Kuring-gai,151.137497,-33.652302
+Mount Lion,152.9552,-28.4338
+Mount Rae,149.718201,-34.446301
+Mt Victoria,150.257599,-33.588299
+Mudgee,149.586594,-32.6012
+Mugincoble,148.225403,-33.190201
+Mulgrave,150.830597,-33.625198
+Mullion Creek,149.1185,-33.137699
+Mulwala,146.001495,-35.9809
+Mumbil,149.046295,-32.723
+Mungindi,148.973297,-28.968901
+Muronbung,148.960403,-32.174301
+Murrawal,149.336502,-31.4646
+Murrays Flats,149.787903,-34.723701
+Murrulla,150.919495,-31.8349
+Murrurundi,150.839294,-31.766399
+Museum,151.211304,-33.8755
+Muswellbrook,150.891006,-32.265202
+Myambat,150.617203,-32.3745
+Myocum,153.510803,-28.578199
+Myrtle Creek,152.945908,-29.110001
+Nambucca,152.977707,-30.628799
+Nammoona,153.022293,-28.8269
+Nana Glen,153.018097,-30.135599
+Nanardine,148.113297,-33.060799
+Napier,146.766998,-35.250401
+Narara,151.344193,-33.394001
+Narellan,150.735992,-34.039398
+Nargong,148.9366,-33.727699
+Narrabri,149.792206,-30.326
+Narrabri West,149.751205,-30.339399
+Narrandera,146.555496,-34.739799
+Narriah,146.725998,-33.948101
+Narwee,151.069595,-33.946602
+Narwonah,148.191498,-32.306599
+Neeworra,149.082504,-29.0266
+Neible,149.637207,-31.642401
+Nemingah,150.988098,-31.119101
+Nevertire,147.714005,-31.838301
+Newbridge,149.365799,-33.581799
+Newcastle,151.783905,-32.9249
+Newnes,150.237701,-33.176998
+Newtown,151.179703,-33.898201
+Niagara Park,151.352997,-33.382099
+No 1 Mortuary Station,151.047195,-33.868401
+No 2 Mortuary Station,151.048996,-33.875599
+No 3 Mortuary Station,151.054199,-33.877899
+No 4 Mortuary Station,151.058701,-33.872601
+Normanhurst,151.097504,-33.719898
+North Menangle,150.742401,-34.115601
+North Richmond,150.715805,-33.579899
+North Star,150.388702,-28.9307
+North Strathfield,151.087997,-33.858002
+North Sydney,151.205399,-33.841099
+North Wollongong,150.890594,-34.410999
+North Yathong,145.901001,-35.244301
+Norwood,149.731995,-34.680302
+Nubba,148.220398,-34.523399
+Nyngan,147.195007,-31.562901
+Nyrang Creek,148.556,-33.5467
+Oak Flats,150.815598,-34.57
+Oakey Creek,149.699493,-31.625799
+Oaklands,146.166,-35.5495
+Oatley,151.078705,-33.9799
+Oberon,149.852493,-33.701199
+Old Burren,148.905899,-29.9447
+Old Casino,153.046707,-28.854799
+Olympic Park,151.069,-33.847099
+Oolong,149.172501,-34.777
+Orange,149.1035,-33.286499
+Otford,151.005295,-34.210701
+Otford,151.005295,-34.210701
+Ourimbah,151.370102,-33.353901
+Padstow,151.031998,-33.950699
+Panania,150.996201,-33.953701
+Pangela,150.793503,-31.755199
+Parkes,148.174896,-33.141899
+Parkville,150.868393,-31.988701
+Parramatta,151.006195,-33.8167
+Paterson,151.612701,-32.602798
+Pendle Hill,150.953506,-33.800999
+Pennant Hills,151.073303,-33.735298
+Penrith,150.6922,-33.748199
+Penrose,150.210693,-34.673801
+Penshurst,151.089005,-33.965199
+Perisher,148.407501,-36.4025
+Petersham,151.155106,-33.893101
+Piambra,149.3741,-31.6329
+Picton,150.610992,-34.1782
+Pipers Flat,149.996704,-33.371201
+Pippita,151.063202,-33.8564
+Point Clare,151.328903,-33.445099
+Polona,149.205002,-33.489899
+Port Kembla,150.899597,-34.476898
+Port Kembla North,150.886505,-34.4715
+Portland,149.993393,-33.360298
+Premer,149.899597,-31.459
+Pucawan,147.349396,-34.396599
+Puggoon,149.470901,-32.250301
+Punchbowl,151.057098,-33.9245
+Pymble,151.144104,-33.7425
+Quakers Hill,150.886795,-33.726799
+Quandary,147.312607,-34.3857
+Quandialla,147.796906,-34.0117
+Quandong,148.176498,-33.9221
+Queanbeyan,149.225403,-35.3419
+Queens Wharf,151.620895,-32.7244
+Quipolly,150.655594,-31.4242
+Quirindi,150.680695,-31.504299
+Raglan,149.6510,-33.4344
+Raleigh,153.017807,-30.4541
+Ralvona,147.191101,-35.691101
+Rappville,152.953293,-29.086
+Ravensworth,151.057999,-32.439701
+Raworth,151.613998,-32.725101
+Red Bend,148.014404,-33.384102
+Redfern,151.1978,-33.8913
+Regents Park,151.025299,-33.881001
+Remep,149.845596,-31.495399
+Revesby,151.013794,-33.951401
+Rhodes,151.085403,-33.830101
+Richmond,150.753998,-33.5998
+Riverstone,150.857498,-33.678299
+Riverwood,151.050507,-33.951199
+Robertson,150.592194,-34.590599
+Rockdale,151.135406,-33.9519
+Rocky Ponds,148.503799,-34.583401
+Rookwood,151.055099,-33.863453
+Rooty Hill,150.843903,-33.770401
+Rosehill,151.022903,-33.822201
+Roseville,151.177795,-33.7817
+Rosewood,147.866196,-35.676399
+Roto,145.462402,-33.050804
+Rowena,148.908203,-29.814899
+Roxburgh,150.759003,-32.312698
+Royalla,149.149399,-35.510101
+Rutherford Racecourse,151.503006,-32.712101
+Ryan,146.910507,-35.5172
+Rydal,150.031006,-33.4846
+Rydalmere,151.0289,-33.8102
+Rylstone,149.973404,-32.7962
+Sandgate,151.702103,-32.870499
+Sandy Hollow,150.570007,-32.3382
+Sawtell,153.098007,-30.358101
+Sayers Lake,143.275299,-32.7225
+Scarborough,150.964905,-34.2635
+Schofields,150.867996,-33.6973
+Scone,150.866898,-32.045898
+Sefton,151.010895,-33.8848
+Seven Hills,150.936996,-33.7738
+Singleton,151.165695,-32.570499
+Somerset,152.164902,-31.884199
+South Gundagai,148.0979,-35.077202
+South Wyalong,147.2341,-33.934502
+Spring Ridge,150.253494,-31.396601
+Springdale,147.724701,-34.469299
+Springwood,150.563904,-33.7001
+Sproules Lagoon,147.500397,-34.387199
+St Helena,153.578903,-28.6658
+St James,151.211304,-33.870899
+St Leonards,151.195297,-33.823601
+St Marys,150.774902,-33.7612
+St Peters,151.178299,-33.906799
+Stanmore,151.163498,-33.893501
+Stanwell Park,150.978806,-34.2271
+Stokers,153.404404,-28.394501
+Stratford,151.9375,-32.118801
+Strathaird,149.746399,-34.423901
+Strathfield,151.093994,-33.871201
+Stuart Town,149.077393,-32.8008
+Summer Hill,151.139404,-33.889599
+Summervale,147.058899,-31.428499
+Sutherland,151.056396,-34.031502
+Sydenham,151.168503,-33.9133
+Tabbita,145.844803,-34.1022
+Table Top,147.001694,-35.9706
+Tahmoor,150.588501,-34.223999
+Taleeban,146.462097,-33.8699
+Tallawang,149.453293,-32.204201
+Tallimba,146.881897,-33.992298
+Tallong,150.085693,-34.7197
+Tamboolba,147.559692,-35.231499
+Tamworth,150.930603,-31.0839
+Tarago,149.649002,-35.069801
+Tarana,149.906494,-33.525101
+Tarcutta,147.710999,-35.2953
+Taree,152.458603,-31.905001
+Tarro,151.669998,-32.808201
+Tascott,151.316895,-33.448898
+Telarah,151.5392,-32.723202
+Telegraph Point,152.800507,-31.3214
+Telopea,151.042496,-33.793598
+Temora,147.5271,-34.444401
+Tempe,151.156906,-33.922901
+Tenterfield,152.005493,-29.053301
+Teralba,151.602997,-32.962299
+Tharbogang,145.992599,-34.254002
+The Gorge,141.675095,-31.9126
+The Risk,152.939407,-28.48
+The Rock,147.113907,-35.271999
+The Royal National Park,151.055603,-34.062199
+The Troffs,147.653702,-32.827599
+Thirlmere,150.570801,-34.204201
+Thirroul,150.917496,-34.318298
+Thornleigh,151.078003,-33.730598
+Thornton,151.640793,-32.783901
+Thulloo,146.768005,-33.659
+Tichborne,148.120605,-33.222099
+Tomingley West,148.152405,-32.5728
+Toolijooa,150.780807,-34.764702
+Toongabbie,150.949402,-33.786999
+Tootool,146.996994,-35.262001
+Torbane,149.989197,-33.112301
+Toronto,151.598007,-33.0154
+Tottenham,147.354797,-32.240501
+Towealgra,149.327698,-31.9305
+Town Hall,151.207108,-33.872398
+Towradgi,150.900406,-34.383801
+Trajere,148.387802,-33.492901
+Trangie,147.985001,-32.0299
+Trewilga,148.213303,-32.7883
+Trida,145.004501,-33.0182
+Trundle,147.708893,-32.916599
+Tubbul Road,147.936005,-34.255299
+Tuggerah,151.420898,-33.307201
+Tullamore,147.561996,-32.631401
+Tumblong,148.008194,-35.138802
+Tumulla,149.480606,-33.516899
+Turramurra,151.128403,-33.730099
+Turrawan,149.883804,-30.457399
+Turrella,151.139694,-33.929401
+Tweed Heads,153.5401,-28.1735
+Ulamambri,149.383194,-31.3298
+Ulinda,149.481705,-31.582199
+Umbango Creek,147.766205,-35.382702
+Unanderra,150.845795,-34.453701
+Upper Manilla,150.664001,-30.636101
+Uralla,151.505295,-30.6437
+Urana,146.273407,-35.328999
+Uranagong,146.235397,-35.4007
+Uranquinty,147.243698,-35.191399
+Urunga,153.017593,-30.4979
+Valley Heights,150.581497,-33.703602
+Victoria Street,151.592102,-32.7491
+Villawood,150.973907,-33.880402
+Vineyard,150.849792,-33.6497
+Wagga Wagga,147.366501,-35.119801
+Wahroonga,151.117401,-33.715698
+Waitara,151.104401,-33.707901
+Walcha Road,151.400497,-30.941799
+Wallangarra,151.931595,-28.9226
+Wallarobba,151.6946,-32.4951
+Wallerawang,150.0681,-33.4090
+Wallsend,151.667206,-32.902802
+Wambool,149.7789,-33.5158
+Wamboyne,147.279602,-33.5695
+Warabrook,151.709793,-32.886299
+Waratah,151.730896,-32.901001
+Wards River,151.938599,-32.217098
+Wargambeal,146.492294,-33.3606
+Wargin,147.253601,-34.115299
+Warnecliffe,149.084595,-32.987202
+Warnervale,151.447205,-33.247601
+Warragai Creek,152.988495,-29.547701
+Warral,150.860992,-31.1595
+Warrawee,151.121994,-33.722698
+Warrell Creek,152.892502,-30.7694
+Warren,147.826096,-31.697599
+Warrimoo,150.601196,-33.720001
+Warwick Farm,150.933807,-33.9147
+Waterfall,150.993393,-34.133999
+Wauchope,152.736694,-31.455099
+Waverton,151.197998,-33.8377
+Wee Elwah,145.197906,-33.045399
+Weedallion,147.936707,-34.193802
+Weemelah,149.255905,-29.0165
+Weetaliba,149.582504,-31.638901
+Weethalle,146.619904,-33.876499
+Weja,146.8228,-33.533401
+Wellington,148.945007,-32.5522
+Wentworth Falls,150.376801,-33.709099
+Wentworthville,150.971497,-33.806099
+Werai,150.345505,-34.590599
+Werrington,150.759705,-33.7589
+Werris Creek,150.646194,-31.3487
+West Ryde,151.091095,-33.805
+West Tamworth,150.912994,-31.0905
+West Wyalong,147.200195,-33.928398
+Westmead,150.986801,-33.8069
+Wickham,151.758194,-32.923
+Wiley Park,151.065994,-33.923
+Willie Ploma,148.075897,-35.093899
+Willow Tree,150.726593,-31.6476
+Wimborne,150.643494,-30.670799
+Windsor,150.809402,-33.612801
+Wingello,150.157593,-34.693298
+Wingen,150.880997,-31.8934
+Wingham,152.367004,-31.8659
+Winnunga,146.863907,-33.5965
+Wiragulla,151.742203,-32.4613
+Wirrinya,147.802994,-33.670898
+Wittenbra,149.085403,-31.044201
+Wolli Creek,151.154999,-33.925999
+Wollongong,150.887405,-34.425701
+Wollstonecraft,151.191498,-33.831299
+Wombarra,150.9524,-34.2752
+Wondabyne,151.255005,-33.491299
+Wongabinda,150.072098,-29.385401
+Wongarbon,148.759293,-32.336399
+Wongoni,149.278503,-31.871099
+Woodford,150.480698,-33.735802
+Woodlawn,153.319199,-28.7757
+Woodstock,148.8452,-33.743301
+Woolbrook,151.351898,-30.965099
+Woollahra,151.242401,-33.8848
+Woolooware,151.143494,-34.0476
+Woonona,150.913803,-34.3493
+Woy Woy,151.321899,-33.482601
+Wubbera,150.145004,-29.546301
+Wumbulgal,146.209198,-34.358601
+Wyanga,148.131104,-32.458302
+Wyangaree,152.963806,-28.5117
+Wybalena,148.163498,-35.4944
+Wyee,151.485001,-33.180401
+Wynyard,151.205795,-33.865101
+Wyong,151.426895,-33.284
+Wyrra,147.268005,-33.823002
+Wyuna Downs,146.488495,-30.5005
+Yagoona,151.023804,-33.904202
+Yallah,150.784195,-34.5364
+Yarra,149.630096,-34.786301
+Yass Junction,148.912903,-34.8088
+Yass Town,148.9086,-34.844002
+Yearinan,149.179199,-31.1819
+Yennora,150.969803,-33.8638
+Yerrinbool,150.542603,-34.3713
+Yethera,147.574799,-32.525398
+Yiddah,147.317795,-34.0364
+Yoogali,146.081497,-34.300499
+Youngareen,146.852203,-33.650398
+Yullundry,148.713898,-32.843201
+Zig Zag,150.2001,-33.4717
diff --git a/src/toys/data/nsw-london.zip b/src/toys/data/nsw-london.zip Binary files differnew file mode 100644 index 0000000..d0c0050 --- /dev/null +++ b/src/toys/data/nsw-london.zip diff --git a/src/toys/data/nsw.txt b/src/toys/data/nsw.txt new file mode 100644 index 0000000..6f33691 --- /dev/null +++ b/src/toys/data/nsw.txt @@ -0,0 +1,86 @@ +Olympic Park I: +Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Olympic Park + +Olympic Park II: +Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Olympic Park + +Eastern Suburbs & Illawarra 1: +Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +Eastern Suburbs & Illawarra 2: +Cronulla,Woolooware,Caringbah,Miranda,Gymea,Kirrawee,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +South Coast 1: +Bomaderry,Berry,Gerringong,Kiama,Bombo,Minnamurra,Dunmore,Oak Flats,Albion Park,Dapto,Kembla Grange Racecourse,Unanderra,Coniston,Wollongong,North Wollongong,Fairy Meadow,Towradgi,Corrimal,Bellambi,Woonona,Bulli,Thirroul,Austinmer,Coledale,Wombarra,Scarborough,Coalcliff,Stanwell Park,Otford,Helensburgh,Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +South Coast 2: +Port Kembla,Port Kembla North,Cringila,Lysaghts,Coniston,Wollongong,North Wollongong,Fairy Meadow,Towradgi,Corrimal,Bellambi,Woonona,Bulli,Thirroul,Austinmer,Coledale,Wombarra,Scarborough,Coalcliff,Stanwell Park,Otford,Helensburgh,Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +Bankstown Line a: +Town Hall,Wynyard,Circular Quay,St James,Museum,Central,Redfern,Erskineville,St Peters,Sydenham,Marrickville,Dulwich Hill,Hurlstone Park,Canterbury,Campsie,Belmore,Lakemba,Wiley Park,Punchbowl,Bankstown,Yagoona,Birrong,Regents Park,Berala,Lidcombe + +Bankstown Line b: +Lidcombe,Berala,Regents Park,Birrong,Sefton,Chester Hill,Leightonfield,Villawood,Carramar,Cabramatta,Warwick Farm,Liverpool + +Inner West 1a: +Liverpool,Warwick Farm,Cabramatta,Carramar,Villawood,Leightonfield,Chester Hill,Sefton,Regents Park,Berala,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Inner West 1b: +Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood + +Inner West 2a: +Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Inner West 2b: +Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood + +Airport & East Hills 1: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Wolli Creek,International,Domestic,Mascot,Green Square,Central,Museum,St James,Circular Quay,Wynyard,Town Hall + +Airport & East Hills 2: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Museum,St James,Circular Quay,Wynyard,Town Hall + +South: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Southern Highlands: +Goulburn,Marulan,Tallong,Wingello,Penrose,Bundanoon,Exeter,Moss Vale,Burradoo,Bowral,Mittagong,Yerrinbool,Bargo,Tahmoor,Picton,Douglas Park,Menangle,Menangle Park,Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown + +Southern Highlands ROAD COACH: +Bowral,Mittagong,Colo Vale,Hill Top,Balmoral,Buxton,Couridjah,Thirlmere,Picton + +Cumberland: +Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown,Doonside,Rooty Hill,Mt Druitt,St Marys + +Carlingford Line: +Carlingford,Telopea,Dundas,Rydalmere,Camellia,Rosehill,Clyde + +Western 1: +Richmond,East Richmond,Clarendon,Windsor,Mulgrave,Vineyard,Riverstone,Schofields,Quakers Hill,Marayong,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +Western 2: +Emu Plains,Penrith,Kingswood,Werrington,St Marys,Mt Druitt,Rooty Hill,Doonside,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +Blue Mountains: +Lithgow,Zig Zag,Bell,Mt Victoria,Blackheath,Medlow Bath,Katoomba,Leura,Wentworth Falls,Bullaburra,Lawson,Hazelbrook,Woodford,Linden,Faulconbridge,Springwood,Valley Heights,Warrimoo,Blaxland,Glenbrook,Lapstone,Emu Plains,Penrith,Kingswood,Werrington,St Marys,Mt Druitt,Rooty Hill,Doonside,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +North Shore: +Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Waitara,Wahroonga,Warrawee,Turramurra,Pymble,Gordon,Killara,Lindfield,Roseville,Chatswood,Artarmon,St Leonards,Wollstonecraft,Waverton,North Sydney,Milsons Point,Wynyard,Town Hall,Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Lidcombe,Auburn,Clyde,Granville,Harris Park,Parramatta + +Northern: +Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +Newcastle & Central Coast 1: +Newcastle,Civic,Wickham,Hamilton,Broadmeadow,Adamstown,Kotara,Cardiff,Cockle Creek,Teralba,Booragul,Fassifern,Awaba,Dora Creek,Morisset,Wyee,Warnervale,Wyong,Tuggerah,Ourimbah,Lisarow,Niagara Park,Narara,Gosford,Point Clare,Tascott,Koolewong,Woy Woy,Wondabyne,Hawkesbury River,Cowan,Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Waitara,Wahroonga,Warrawee,Turramurra,Pymble,Gordon,Killara,Lindfield,Roseville,Chatswood,Artarmon,St Leonards,Wollstonecraft,Waverton,North Sydney,Milsons Point,Wynyard,Town Hall,Central + +Newcastle & Central Coast 2: +Newcastle,Civic,Wickham,Hamilton,Broadmeadow,Adamstown,Kotara,Cardiff,Cockle Creek,Teralba,Booragul,Fassifern,Awaba,Dora Creek,Morisset,Wyee,Warnervale,Wyong,Tuggerah,Ourimbah,Lisarow,Niagara Park,Narara,Gosford,Point Clare,Tascott,Koolewong,Woy Woy,Wondabyne,Hawkesbury River,Cowan,Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central + +Wyong Line: +Wyong,Tuggerah,Ourimbah,Lisarow,Niagara Park,Narara,Gosford,Point Clare,Tascott,Koolewong,Woy Woy,Wondabyne,Hawkesbury River,Cowan,Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Homebush,Flemington,Lidcombe,Auburn,Clyde,Granville,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown,Doonside,Rooty Hill,Mt Druitt,St Marys + +Hunter 1: +Scone,Aberdeen,Muswellbrook,Singleton,Belford,Branxton,Greta,Allandale,Lochinvar,Maitland,High Street,East Maitland,Victoria Street,Metford,Thornton,Beresfield,Tarro,Hexham,Sandgate,Warabrook,Waratah,Hamilton,Wickham,Civic,Newcastle + +Hunter 2: +Dungog,Wiragulla,Wallarobba,Hilldale,Martins Creek,Paterson,Mindaribba,Telarah,Maitland,High Street,East Maitland,Victoria Street,Metford,Thornton,Beresfield,Tarro,Hexham,Sandgate,Warabrook,Waratah,Hamilton,Wickham,Civic,Newcastle diff --git a/src/toys/data/parser.cpp b/src/toys/data/parser.cpp new file mode 100644 index 0000000..cc5c6f0 --- /dev/null +++ b/src/toys/data/parser.cpp @@ -0,0 +1,47 @@ +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <map> +using namespace std; +typedef pair<double,double> Point; +int main(void) +{ + ifstream location_file("london-locations.csv"), path_file("london.txt"); + string id,sx,sy; + map<string,Point> idlookup; + while (getline(location_file,id,',')) + { + getline(location_file,sx,','); + getline(location_file,sy,'\n'); + char *e; + double x = strtod(sx.c_str(),&e), y = strtod(sy.c_str(),&e); + cout << id << " (" << x << "," << y << ")"<< endl; + idlookup[id]=make_pair(x,y); + } + string l; + vector<vector<Point> > paths; + while (getline(path_file,l,'\n')) { + vector<Point> ps; + if(l.size() < 2) continue; // skip blank lines + if(l.find(":",0)!=string::npos) continue; // skip labels + string::size_type p=0,q; + while((q=l.find(",",p))!=string::npos || p < l.size() && (q = l.size()-1)) { + id = l.substr(p,q-p); + cout << id << ","; + ps.push_back(idlookup[id]); + p=q+1; + } + paths.push_back(ps); + cout << "*******************************************" << endl; + } + for(unsigned i=0;i<paths.size();i++) { + vector<Point> ps=paths[i]; + for(unsigned j=0;j<ps.size();j++) { + double x=ps[j].first, y=ps[j].second; + cout << "(" << x << "," << y << ")" << ","; + } + cout << endl; + } + return(0); +} diff --git a/src/toys/differential-constraint.cpp b/src/toys/differential-constraint.cpp new file mode 100644 index 0000000..501eb76 --- /dev/null +++ b/src/toys/differential-constraint.cpp @@ -0,0 +1,132 @@ +/** Differential constraint solver hack + * based on idea from Michael Glissner + * (njh) + */ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework.h> + +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +#include <stdio.h> +#include <gsl/gsl_errno.h> +#include <gsl/gsl_matrix.h> +#include <gsl/gsl_odeiv.h> + +vector<Geom::Point> *handlesptr = NULL; + +const unsigned order = 6; + +class SBez: public Toy { + static int + func (double /*t*/, const double y[], double f[], + void */*params*/) + { + //double mu = *(double *)params; + D2<SBasis> B = handles_to_sbasis(handlesptr->begin(), order); + D2<SBasis> dB = derivative(B); + Geom::Point tan = dB(y[0]);//Geom::unit_vector(); + tan /= dot(tan,tan); + Geom::Point yp = B(y[0]); + double dtau = -dot(tan, yp - (*handlesptr)[order+1]); + f[0] = dtau; + + return GSL_SUCCESS; + } + + static int + jac (double /*t*/, const double y[], double *dfdy, + double dfdt[], void *params) + { + double mu = *(double *)params; + gsl_matrix_view dfdy_mat + = gsl_matrix_view_array (dfdy, 2, 2); + gsl_matrix * m = &dfdy_mat.matrix; + gsl_matrix_set (m, 0, 0, 0.0); + gsl_matrix_set (m, 0, 1, 1.0); + gsl_matrix_set (m, 1, 0, -2.0*mu*y[0]*y[1] - 1.0); + gsl_matrix_set (m, 1, 1, -mu*(y[0]*y[0] - 1.0)); + dfdt[0] = 0.0; + dfdt[1] = 0.0; + return GSL_SUCCESS; + } + + double y[2]; + + virtual void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + handlesptr = &handles; + cairo_set_line_width (cr, 0.5); + + D2<SBasis> B = handles_to_sbasis(handles.begin(), order); + cairo_d2_sb(cr, B); + + const gsl_odeiv_step_type * T + = gsl_odeiv_step_rk8pd; + + gsl_odeiv_step * s + = gsl_odeiv_step_alloc (T, 1); + gsl_odeiv_control * c + = gsl_odeiv_control_y_new (1e-6, 0.0); + gsl_odeiv_evolve * e + = gsl_odeiv_evolve_alloc (1); + + double mu = 10; + gsl_odeiv_system sys = {func, jac, 1, &mu}; + + static double t = 0.0; + double t1 = t + 1; + double h = 1e-6; + + while (t < t1) + { + int status = gsl_odeiv_evolve_apply (e, c, s, + &sys, + &t, t1, + &h, y); + + if (status != GSL_SUCCESS) + break; + + //printf ("%.5e %.5e %.5e\n", t, y[0], y[1]); + } + + draw_cross(cr, B(y[0])); + + gsl_odeiv_evolve_free (e); + gsl_odeiv_control_free (c); + gsl_odeiv_step_free (s); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } +public: + SBez() { + y[0] = 0; + for(unsigned i = 0; i <= order+1; i++) { + handles.push_back(Geom::Point(uniform()*400, uniform()*400)); + } + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SBez()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/draw-toy.cpp b/src/toys/draw-toy.cpp new file mode 100644 index 0000000..f5bd315 --- /dev/null +++ b/src/toys/draw-toy.cpp @@ -0,0 +1,116 @@ +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +class DrawToy: public Toy { + PointSetHandle hand; + //Knot : Och : Och : Knot : Och : Och : Knot : Och : Och : ... + void draw(cairo_t *cr, std::ostringstream */*notify*/, int /*width*/, int /*height*/, bool save, std::ostringstream*) override { + if(!save) { + cairo_set_source_rgba (cr, 0, 0.5, 0, 1); + cairo_set_line_width (cr, 1); + for(unsigned i = 0; i < hand.pts.size(); i+=3) { + draw_circ(cr, hand.pts[i]); + draw_number(cr, hand.pts[i], i/3); + } + cairo_set_source_rgba (cr, 0, 0, 0.5, 1); + for(unsigned i = 2; i < hand.pts.size(); i+=3) { + draw_circ(cr, hand.pts[i]); + draw_circ(cr, hand.pts[i-1]); + } + + cairo_set_source_rgba (cr, 0.5, 0, 0, 1); + for(unsigned i = 3; i < hand.pts.size(); i+=3) { + draw_line_seg(cr, hand.pts[i-2], hand.pts[i-3]); + draw_line_seg(cr, hand.pts[i], hand.pts[i-1]); + } + } + cairo_set_source_rgba (cr, 0, 0, 0, 1); + Geom::Path pb; + if(hand.pts.size() > 3) { + pb.start(hand.pts[0]); + for(unsigned i = 1; i < hand.pts.size() - 3; i+=3) { + pb.appendNew<Geom::CubicBezier>(hand.pts[i], hand.pts[i+1], hand.pts[i+2]); + } + } + cairo_path(cr, pb); + cairo_stroke(cr); + } + void mouse_pressed(GdkEventButton* e) override { + selected = NULL; + Geom::Point mouse(e->x, e->y); + int close_i = 0; + float close_d = 1000; + for(unsigned i = 0; i < hand.pts.size(); i+=1) { + if(Geom::distance(mouse, hand.pts[i]) < close_d) { + close_d = Geom::distance(mouse, hand.pts[i]); + close_i = i; + } + } + if(close_d < 5) { + if(e->button==3) + hand.pts.erase(hand.pts.begin() + close_i); + else { + selected = &hand; + hit_data = (void*)(intptr_t)close_i; + } + } else { + if(e->button==1) { + if(hand.pts.size() > 0) { + if(hand.pts.size() == 1) { + hand.pts.push_back((hand.pts[0] * 2 + mouse) / 3); + hand.pts.push_back((hand.pts[0] + mouse * 2) / 3); + } else { + Geom::Point prev = hand.pts[hand.pts.size() - 1]; + Geom::Point curve = prev - hand.pts[hand.pts.size() - 2]; + hand.pts.push_back(prev + curve); + hand.pts.push_back(mouse + curve); + } + } + hand.pts.push_back(mouse); + } else { + selected = &hand; + hit_data = (void*)(intptr_t)close_i; + } + } + } + + void mouse_moved(GdkEventMotion* e) override { + Geom::Point mouse(e->x, e->y); + + if(e->state & (GDK_BUTTON1_MASK) && selected != NULL) { + // NOTE this is completely broken. + int hd = 0; + if (hd % 3 == 0) { + Geom::Point diff = mouse - hand.pts[hd]; + if(int(hand.pts.size() - 1) > hd) hand.pts[hd + 1] += diff; + if(hd != 0) hand.pts[hd - 1] += diff; + } + Toy::mouse_moved(e); + } + } + + bool should_draw_numbers() override { return false; } +public: + DrawToy() { + + handles.push_back(&hand); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new DrawToy()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/ellipse-area-minimizer.cpp b/src/toys/ellipse-area-minimizer.cpp new file mode 100644 index 0000000..48df598 --- /dev/null +++ b/src/toys/ellipse-area-minimizer.cpp @@ -0,0 +1,352 @@ +/* + * Ellipse and Elliptical Arc Fitting Example + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/ellipse.h> +#include <2geom/elliptical-arc.h> +#include <2geom/utils.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <stdio.h> + +#include <gsl/gsl_errno.h> +#include <gsl/gsl_math.h> +#include <gsl/gsl_min.h> + + + + +using namespace Geom; + + +class LFMEllipseArea + : public NL::LinearFittingModelWithFixedTerms<Point, double, Ellipse> +{ + public: + LFMEllipseArea(double coeff) + : m_coeff(coeff*coeff) + { + } + void feed( NL::VectorView & coeff, double & fixed_term, Point const& p ) const + { + coeff[0] = p[X] * p[Y]; + coeff[1] = p[X]; + coeff[2] = p[Y]; + coeff[3] = 1; + fixed_term = p[X] * p[X] + m_coeff * p[Y] * p[Y]; + } + + size_t size() const + { + return 4; + } + + void instance(Ellipse & e, NL::ConstVectorView const& coeff) const + { +// std::cerr << " B = " << coeff[0] +// << " C = " << decimal_round(m_coeff,10) +// << " D = " << coeff[1] +// << " E = " << coeff[2] +// << " F = " << coeff[3] +// << std::endl; + e.setCoefficients(1, coeff[0], m_coeff, coeff[1], coeff[2], coeff[3]); + } + + private: + double m_coeff; +}; + +inline +Ellipse fitting(std::vector<Point> const& points, double coeff) +{ + size_t sz = points.size(); + if (sz != 4) + { + THROW_RANGEERROR("fitting error: too few points passed"); + } + LFMEllipseArea model(coeff); + NL::least_squeares_fitter<LFMEllipseArea> fitter(model, sz); + + for (size_t i = 0; i < sz; ++i) + { + fitter.append(points[i]); + } + fitter.update(); + + NL::Vector z(sz, 0.0); + Ellipse e; + model.instance(e, fitter.result(z)); + return e; +} + + +inline +double area_goal(double coeff, void* params) +{ + typedef std::vector<Point> point_set_t; + const point_set_t & points = *static_cast<point_set_t*>(params); + Ellipse e; + try + { + e = fitting(points, coeff); + } + catch(LogicalError exc) + { + //std::cerr << exc.what() << std::endl; + return 1e18; + } + return e.ray(X) * e.ray(Y); +} + + +inline +double perimeter_goal(double coeff, void* params) +{ + typedef std::vector<Point> point_set_t; + const point_set_t & points = *static_cast<point_set_t*>(params); + Ellipse e; + try + { + e = fitting(points, coeff); + } + catch(LogicalError exc) + { + //std::cerr << exc.what() << std::endl; + return 1e18; + } + return e.ray(X) + e.ray(Y); +} + +void no_minimum_error_handler (const char * reason, + const char * file, + int line, + int gsl_errno) +{ + if (gsl_errno == GSL_EINVAL) + { + std::cerr << "gsl: " << file << ":" << line << " ERROR: " << reason << std::endl; + } + else + { + gsl_error(reason, file, line, gsl_errno); + } +} + +typedef double goal_function_type(double coeff, void* params); + +double minimizer (std::vector<Point> & points, goal_function_type* gf) +{ + int status; + int iter = 0, max_iter = 1000; + const gsl_min_fminimizer_type *T; + gsl_min_fminimizer *s; + double m = 1.0; + double a = 1e-2, b = 1e2; + gsl_function F; + + F.function = gf; + F.params = static_cast<void*>(&points); + + //T = gsl_min_fminimizer_goldensection; + T = gsl_min_fminimizer_brent; + s = gsl_min_fminimizer_alloc (T); + gsl_min_fminimizer_set (s, &F, m, a, b); + +// printf ("using %s method\n", +// gsl_min_fminimizer_name (s)); +// +// printf ("%5s [%9s, %9s] %9s %10s %9s\n", +// "iter", "lower", "upper", "min", +// "err", "err(est)"); +// +// printf ("%5d [%.7f, %.7f] %.7f %+.7f %.7f\n", +// iter, a, b, +// m, m - m_expected, b - a); + + do + { + iter++; + status = gsl_min_fminimizer_iterate (s); + + m = gsl_min_fminimizer_x_minimum (s); + a = gsl_min_fminimizer_x_lower (s); + b = gsl_min_fminimizer_x_upper (s); + + status + = gsl_min_test_interval (a, b, 1e-3, 0.0); + +// if (status == GSL_SUCCESS) +// printf ("Converged:\n"); +// +// printf ("%5d [%.7f, %.7f] " +// "%.7f %+.7f %.7f\n", +// iter, a, b, +// m, m - m_expected, b - a); + } + while (status == GSL_CONTINUE && iter < max_iter); + + gsl_min_fminimizer_free (s); + + if (status != GSL_SUCCESS) return 0; + + return m; +} + + + +class EllipseAreaMinimizer : public Toy +{ + public: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + Point toggle_sp( 300, height - 50); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(135,25) ); + ConvexHull ch(psh.pts); + bool non_convex = false; + for(auto & pt : psh.pts) { + if (ch.contains(pt)) + non_convex = true; + } + + if(non_convex) { + Circle circ; + std::vector<Point> boundary(ch.begin(), ch.end()); + circ.fit(boundary); + e = Ellipse(circ); + } else { + goal_function_type* gf = &area_goal; + if (!toggles[0].on) gf = &perimeter_goal; + double coeff = minimizer(psh.pts, gf); + + try + { + e = fitting(psh.pts, coeff); + } + catch(LogicalError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + draw_elliptical_arc_with_cairo( cr, + e.center(X), e.center(Y), + e.ray(X), e.ray(Y), + 0, 2*M_PI, + e.rotationAngle() ); + if (toggles[0].on) + *notify << "Area: " << e.ray(X)*e.ray(Y); + else + *notify << "Perimeter: " << 2* (e.ray(X) + e.ray(Y)); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void draw_elliptical_arc_with_cairo( + cairo_t *cr, + double _cx, double _cy, + double _rx, double _ry, + double _sa, double _ea, + double _ra = 0 + ) const + { + double cos_rot_angle = std::cos(_ra); + double sin_rot_angle = std::sin(_ra); + cairo_matrix_t transform_matrix; + cairo_matrix_init( &transform_matrix, + _rx * cos_rot_angle, _rx * sin_rot_angle, + -_ry * sin_rot_angle, _ry * cos_rot_angle, + _cx, _cy + ); + cairo_save(cr); + cairo_transform(cr, &transform_matrix); + cairo_arc(cr, 0, 0, 1, _sa, _ea); + cairo_restore(cr); + } + + public: + EllipseAreaMinimizer() + { + gsl_set_error_handler(&no_minimum_error_handler); + + first_time = true; + + psh.pts.resize(4); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + psh.pts[3] = Point(400, 320); + + handles.push_back(&psh); + + toggles.emplace_back("Area/Perimeter", true); + handles.push_back(&(toggles[0])); + } + + public: + Ellipse e; + bool first_time; + PointSetHandle psh; + std::vector<Toggle> toggles; +}; + + + + +int main(int argc, char **argv) +{ + init( argc, argv, new EllipseAreaMinimizer(), 600, 600 ); + return 0; +} + + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/ellipse-bezier-intersect-toy.cpp b/src/toys/ellipse-bezier-intersect-toy.cpp new file mode 100644 index 0000000..5468d97 --- /dev/null +++ b/src/toys/ellipse-bezier-intersect-toy.cpp @@ -0,0 +1,74 @@ +#include <2geom/cairo-path-sink.h> +#include <2geom/ellipse.h> +#include <2geom/line.h> +#include <2geom/polynomial.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle eh, bh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Rect all(Point(0,0), Point(width, height)); + + double rx = Geom::distance(eh.pts[0], eh.pts[1]); + double ry = Geom::distance(eh.pts[0], eh.pts[2]); + double rot = Geom::atan2(eh.pts[1] - eh.pts[0]); + + Ellipse e(eh.pts[0], Point(rx, ry), rot); + D2<Bezier> b(bh.pts); + + cairo_set_line_width(cr, 1.0); + Geom::CairoPathSink cps(cr); + + // draw Bezier control polygon + cairo_set_source_rgba(cr, 0, 0, 1, 0.3); + cps.moveTo(bh.pts[0]); + for (unsigned i = 1; i < bh.pts.size(); ++i) { + cps.lineTo(bh.pts[i]); + } + cairo_stroke(cr); + + // draw Bezier curve and ellipse + cairo_set_source_rgb(cr, 0, 0, 0); + cps.feed(BezierCurve(b), true); + cps.feed(e); + cairo_stroke(cr); + + std::vector<ShapeIntersection> result = e.intersect(b); + + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + eh.push_back(300,300); eh.push_back(450,150); eh.push_back(250, 350); + bh.push_back(100,100); bh.push_back(500,100); bh.push_back(100,500); bh.push_back(500,500); + handles.push_back(&eh); + handles.push_back(&bh); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/ellipse-fitting.cpp b/src/toys/ellipse-fitting.cpp new file mode 100644 index 0000000..de8c81a --- /dev/null +++ b/src/toys/ellipse-fitting.cpp @@ -0,0 +1,191 @@ +/* + * Ellipse and Elliptical Arc Fitting Example + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/ellipse.h> +#include <2geom/elliptical-arc.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class EllipseFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + if (first_time) + { + first_time = false; + Point toggle_sp( 300, height - 50); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(120,25) ); + sliders[0].geometry(Point(50, height - 50), 100); + } + + size_t n = (size_t)(sliders[0].value()) + 5; + if (n < psh.pts.size()) + { + psh.pts.resize(n); + } + else if (n > psh.pts.size()) + { + psh.push_back(400*uniform()+50, 300*uniform()+50); + } + + try + { + e.fit(psh.pts); + } + catch(LogicalError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + + if (toggles[0].on) + { + try + { + std::unique_ptr<EllipticalArc> eap( e.arc(psh.pts[0], psh.pts[2], psh.pts[4]) ); + ea = *eap; + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + if (!toggles[0].on) + { + draw_elliptical_arc_with_cairo( cr, + e.center(X), e.center(Y), + e.ray(X), e.ray(Y), + 0, 2*M_PI, + e.rotationAngle() ); + } + else + { + draw_text(cr, psh.pts[0], "initial"); + draw_text(cr, psh.pts[2], "inner"); + draw_text(cr, psh.pts[4], "final"); + + D2<SBasis> easb = ea.toSBasis(); + cairo_d2_sb(cr, easb); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void draw_elliptical_arc_with_cairo( + cairo_t *cr, + double _cx, double _cy, + double _rx, double _ry, + double _sa, double _ea, + double _ra = 0 + ) const + { + double cos_rot_angle = std::cos(_ra); + double sin_rot_angle = std::sin(_ra); + cairo_matrix_t transform_matrix; + cairo_matrix_init( &transform_matrix, + _rx * cos_rot_angle, _rx * sin_rot_angle, + -_ry * sin_rot_angle, _ry * cos_rot_angle, + _cx, _cy + ); + cairo_save(cr); + cairo_transform(cr, &transform_matrix); + cairo_arc(cr, 0, 0, 1, _sa, _ea); + cairo_restore(cr); + } + + public: + EllipseFitting() + { + first_time = true; + + psh.pts.resize(5); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + psh.pts[3] = Point(400, 320); + psh.pts[4] = Point(50, 250); + + + toggles.emplace_back(" arc / ellipse ", false); + sliders.emplace_back(0, 5, 1, 0, "more handles"); + + handles.push_back(&psh); + handles.push_back(&(toggles[0])); + handles.push_back(&(sliders[0])); + } + + private: + Ellipse e; + EllipticalArc ea; + bool first_time; + PointSetHandle psh; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new EllipseFitting(), 600, 600 ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/ellipse-intersect-toy.cpp b/src/toys/ellipse-intersect-toy.cpp new file mode 100644 index 0000000..3d65baa --- /dev/null +++ b/src/toys/ellipse-intersect-toy.cpp @@ -0,0 +1,158 @@ +#include <2geom/cairo-path-sink.h> +#include <2geom/ellipse.h> +#include <2geom/line.h> +#include <2geom/polynomial.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle psh[2]; + Ellipse ea, eb; + Line l[6]; + + void intersect() { + // This is code is almost the same as in ellipse.cpp. + // We use it here to get the lines. + double A, B, C, D, E, F; + double a, b, c, d, e, f; + + ea.coefficients(A, E, B, C, D, F); + eb.coefficients(a, e, b, c, d, f); + + double I, J, K, L; + I = (-E*E*F + 4*A*B*F + C*D*E - A*D*D - B*C*C) / 4; + J = -((E*E - 4*A*B) * f + (2*E*F - C*D) * e + (2*A*D - C*E) * d + + (2*B*C - D*E) * c + (C*C - 4*A*F) * b + (D*D - 4*B*F) * a) / 4; + K = -((e*e - 4*a*b) * F + (2*e*f - c*d) * E + (2*a*d - c*e) * D + + (2*b*c - d*e) * C + (c*c - 4*a*f) * B + (d*d - 4*b*f) * A) / 4; + L = (-e*e*f + 4*a*b*f + c*d*e - a*d*d - b*c*c) / 4; + + std::vector<double> mu = solve_cubic(I, J, K, L); + + for (unsigned i = 0; i < mu.size(); ++i) { + double aa = mu[i] * A + a; + double bb = mu[i] * B + b; + double cc = mu[i] * C + c; + double dd = mu[i] * D + d; + double ee = mu[i] * E + e; + double ff = mu[i] * F + f; + double delta = ee*ee - 4*aa*bb; + if (delta < 0) { + continue; + } + + if (aa != 0) { + bb /= aa; cc /= aa; dd /= aa; ee /= aa; ff /= aa; + double s = (ee + std::sqrt(ee*ee - 4*bb)) / 2; + double q = ee - s; + double alpha = (dd - cc*q) / (s - q); + double beta = cc - alpha; + + l[i*2] = Line(1, q, alpha); + l[i*2+1] = Line(1, s, beta); + } else if (bb != 0) { + cc /= bb; dd /= bb; ee /= bb; ff /= bb; + double s = ee; + double q = 0; + double alpha = cc / ee; + double beta = ff * ee / cc; + + l[i*2] = Line(q, 1, alpha); + l[i*2+1] = Line(s, 1, beta); + } else { + // both aa and bb are zero + l[i*2] = Line(ee, 1, dd); + l[i*2+1] = Line(0, 1, cc/ee); + } + } + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Rect all(Point(0,0), Point(width, height)); + + double r1x = Geom::distance(psh[0].pts[0], psh[0].pts[1]); + double r1y = Geom::distance(psh[0].pts[0], psh[0].pts[2]); + double rot1 = Geom::atan2(psh[0].pts[1] - psh[0].pts[0]); + + double r2x = Geom::distance(psh[1].pts[0], psh[1].pts[1]); + double r2y = Geom::distance(psh[1].pts[0], psh[1].pts[2]); + double rot2 = Geom::atan2(psh[1].pts[1] - psh[1].pts[0]); + + ea = Ellipse(psh[0].pts[0], Point(r1x, r1y), rot1); + eb = Ellipse(psh[1].pts[0], Point(r2x, r2y), rot2); + + for (auto & i : l) { + i = Line(0, 0, 0); + } + + cairo_set_line_width(cr, 1.0); + + cairo_set_source_rgb(cr, 0, 0, 0); + Geom::CairoPathSink cps(cr); + cps.feed(ea); + cps.feed(eb); + cairo_stroke(cr); + + try { + intersect(); + std::vector<ShapeIntersection> result = ea.intersect(eb); + + if (!l[0].isDegenerate() && !l[1].isDegenerate()) { + cairo_set_source_rgba(cr, 1, 0, 0, 0.2); + draw_line(cr, l[0], all); + draw_line(cr, l[1], all); + cairo_stroke(cr); + } + if (!l[2].isDegenerate() && !l[3].isDegenerate()) { + cairo_set_source_rgba(cr, 0, 1, 0, 0.2); + draw_line(cr, l[2], all); + draw_line(cr, l[3], all); + cairo_stroke(cr); + } + if (!l[4].isDegenerate() && !l[5].isDegenerate()) { + cairo_set_source_rgba(cr, 0, 0, 1, 0.2); + draw_line(cr, l[4], all); + draw_line(cr, l[5], all); + cairo_stroke(cr); + } + + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + } catch(...) { + *notify << "Exception"; + } + + // TODO: draw_handle at intersections + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + psh[0].push_back(300,300); psh[0].push_back(450,150); psh[0].push_back(250, 250); + psh[1].push_back(350,300); psh[1].push_back(500,500); psh[1].push_back(300, 350); + handles.push_back(&psh[0]); + handles.push_back(&psh[1]); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/ellipse-line-intersect-toy.cpp b/src/toys/ellipse-line-intersect-toy.cpp new file mode 100644 index 0000000..4172852 --- /dev/null +++ b/src/toys/ellipse-line-intersect-toy.cpp @@ -0,0 +1,67 @@ +#include <2geom/cairo-path-sink.h> +#include <2geom/ellipse.h> +#include <2geom/line.h> +#include <2geom/polynomial.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle eh, lh; + Ellipse e; + Line l; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Rect all(Point(0,0), Point(width, height)); + + double rx = Geom::distance(eh.pts[0], eh.pts[1]); + double ry = Geom::distance(eh.pts[0], eh.pts[2]); + double rot = Geom::atan2(eh.pts[1] - eh.pts[0]); + + Ellipse e(eh.pts[0], Point(rx, ry), rot); + LineSegment l(lh.pts[0], lh.pts[1]); + + cairo_set_line_width(cr, 1.0); + + cairo_set_source_rgb(cr, 0, 0, 0); + draw_line_segment(cr, l, all); + Geom::CairoPathSink cps(cr); + cps.feed(e); + cairo_stroke(cr); + + std::vector<ShapeIntersection> result = e.intersect(l); + + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + eh.push_back(300,300); eh.push_back(450,150); eh.push_back(250, 350); + lh.push_back(280, 50); lh.push_back(320,550); + handles.push_back(&eh); + handles.push_back(&lh); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/elliptiarc-3point-center-fitting.cpp b/src/toys/elliptiarc-3point-center-fitting.cpp new file mode 100644 index 0000000..d2330a1 --- /dev/null +++ b/src/toys/elliptiarc-3point-center-fitting.cpp @@ -0,0 +1,266 @@ +/* + * make up an elliptical arc knowing 3 points lying on the arc + * and the ellipse centre + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/elliptical-arc.h> +#include <2geom/numeric/linear_system.h> + +namespace Geom +{ + +bool make_elliptical_arc( EllipticalArc & ea, + Point const& centre, + Point const& initial, + Point const& final, + Point const& inner ) +{ + + Point p[3] = { initial, inner, final }; + double x1, x2, x3, x4; + double y1, y2, y3, y4; + double x1y1, x2y2, x3y1, x1y3; + NL::Matrix m(3,3); + NL::Vector v(3); + NL::LinearSystem ls(m, v); + + m.set_all(0); + v.set_all(0); + for (auto & k : p) + { + // init_x_y + x1 = k[X] - centre[X]; x2 = x1 * x1; x3 = x2 * x1; x4 = x3 * x1; + y1 = k[Y] - centre[Y]; y2 = y1 * y1; y3 = y2 * y1; y4 = y3 * y1; + x1y1 = x1 * y1; + x2y2 = x2 * y2; + x3y1 = x3 * y1; x1y3 = x1 * y3; + + // init linear system + m(0,0) += x4; + m(0,1) += x3y1; + m(0,2) += x2y2; + + m(1,0) += x3y1; + m(1,1) += x2y2; + m(1,2) += x1y3; + + m(2,0) += x2y2; + m(2,1) += x1y3; + m(2,2) += y4; + + v[0] += x2; + v[1] += x1y1; + v[2] += y2; + } + + ls.SV_solve(); + + double A = ls.solution()[0]; + double B = ls.solution()[1]; + double C = ls.solution()[2]; + + + //evaluate ellipse rotation angle + double rot = std::atan2( -B, -(A - C) )/2; + std::cerr << "rot = " << rot << std::endl; + bool swap_axes = false; + if ( are_near(rot, 0) ) rot = 0; + if ( are_near(rot, M_PI/2) || rot < 0 ) + { + swap_axes = true; + } + + // evaluate the length of the ellipse rays + double cosrot = std::cos(rot); + double sinrot = std::sin(rot); + double cos2 = cosrot * cosrot; + double sin2 = sinrot * sinrot; + double cossin = cosrot * sinrot; + + double den = A * cos2 + B * cossin + C * sin2; + if ( den <= 0 ) + { + std::cerr << "!(den > 0) error" << std::endl; + std::cerr << "evaluating rx" << std::endl; + return false; + } + double rx = std::sqrt(1/den); + + den = C * cos2 - B * cossin + A * sin2; + if ( den <= 0 ) + { + std::cerr << "!(den > 0) error" << std::endl; + std::cerr << "evaluating ry" << std::endl; + return false; + } + double ry = std::sqrt(1/den); + + + // the solution is not unique so we choose always the ellipse + // with a rotation angle between 0 and PI/2 + if ( swap_axes ) std::swap(rx, ry); + if ( are_near(rot, M_PI/2) + || are_near(rot, -M_PI/2) + || are_near(rx, ry) ) + { + rot = 0; + } + else if ( rot < 0 ) + { + rot += M_PI/2; + } + + std::cerr << "swap axes: " << swap_axes << std::endl; + std::cerr << "rx = " << rx << " ry = " << ry << std::endl; + std::cerr << "rot = " << deg_from_rad(rot) << std::endl; + std::cerr << "centre: " << centre << std::endl; + + + // find out how we should set the large_arc_flag and sweep_flag + bool large_arc_flag = true; + bool sweep_flag = true; + + Point sp_cp = initial - centre; + Point ep_cp = final - centre; + Point ip_cp = inner - centre; + + double angle1 = angle_between(sp_cp, ep_cp); + double angle2 = angle_between(sp_cp, ip_cp); + double angle3 = angle_between(ip_cp, ep_cp); + + if ( angle1 > 0 ) + { + if ( angle2 > 0 && angle3 > 0 ) + { + large_arc_flag = false; + sweep_flag = true; + } + else + { + large_arc_flag = true; + sweep_flag = false; + } + } + else + { + if ( angle2 < 0 && angle3 < 0 ) + { + large_arc_flag = false; + sweep_flag = false; + } + else + { + large_arc_flag = true; + sweep_flag = true; + } + } + + // finally we're going to create the elliptical arc! + try + { + ea.set( initial, rx, ry, rot, + large_arc_flag, sweep_flag, final ); + } + catch( RangeError e ) + { + std::cerr << e.what() << std::endl; + return false; + } + + return true; +} + + +} + + + +using namespace Geom; + +class ElliptiArcMaker : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgb(cr, 0,0,0.3); + draw_text(cr, O.pos, "centre"); + draw_text(cr, A.pos, "initial"); + draw_text(cr, B.pos, "final"); + draw_text(cr, C.pos, "inner"); + cairo_stroke(cr); + cairo_set_source_rgb(cr, 0.7,0,0); + bool status + = make_elliptical_arc(ea, O.pos, A.pos, B.pos, C.pos); + if (status) + { + D2<Geom::SBasis> easb = ea.toSBasis(); + cairo_d2_sb(cr, easb); + } + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + ElliptiArcMaker() + : O(443, 441), + A(516, 275), + B(222, 455), + C(190, 234) + { + handles.push_back(&O); + handles.push_back(&A); + handles.push_back(&B); + handles.push_back(&C); + } + + private: + PointHandle O, A, B, C; + EllipticalArc ea; +}; + + + + + + + + +int main(int argc, char **argv) +{ + init( argc, argv, new ElliptiArcMaker() ); + return 0; +} + diff --git a/src/toys/elliptiarc-curve-fitting.cpp b/src/toys/elliptiarc-curve-fitting.cpp new file mode 100644 index 0000000..6c47f8d --- /dev/null +++ b/src/toys/elliptiarc-curve-fitting.cpp @@ -0,0 +1,127 @@ +/* + * Elliptical Arc Fitting Toy + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <gsl/gsl_linalg.h> + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/elliptical-arc.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + + +using namespace Geom; + + + +class EAFittingToy : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.2); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.); + //D2<SBasis> SB = handles_to_sbasis(handles.begin(), total_handles - 1); + D2<SBasis> SB = psh.asBezier(); + cairo_d2_sb(cr, SB); + cairo_stroke(cr); + + cairo_set_line_width (cr, 0.4); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.7, 1.0); + try + { + EllipticalArc EA; + if (!arc_from_sbasis(EA, SB, tolerance, 10)) { +// *notify << "distance error: " << convert.get_error() +// << " ( " << convert.get_bound() << " )" << std::endl +// << "angle error: " << convert.get_angle_error() +// << " ( " << convert.get_angle_tolerance() << " )"; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + D2<SBasis> easb = EA.toSBasis(); + cairo_d2_sb(cr, easb); + cairo_stroke(cr); + } + catch( RangeError e ) + { + std::cerr << e.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + EAFittingToy( double _tolerance ) + : tolerance(_tolerance) + { + handles.push_back(&psh); + total_handles = 6; + for ( unsigned int i = 0; i < total_handles; ++i ) + { + psh.push_back(uniform()*400, uniform()*400); + } + } + + PointSetHandle psh; + unsigned int total_handles; + double tolerance; +}; + + + +int main(int argc, char **argv) +{ + double tolerance = 8; + if(argc > 1) + sscanf(argv[1], "%lf", &tolerance); + init( argc, argv, new EAFittingToy(tolerance) ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ diff --git a/src/toys/elliptical-arc-toy.cpp b/src/toys/elliptical-arc-toy.cpp new file mode 100644 index 0000000..e2518f0 --- /dev/null +++ b/src/toys/elliptical-arc-toy.cpp @@ -0,0 +1,903 @@ +/** @file + * @brief Demonstration of elliptical arc functions + *//* + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof KosiÅ„ski <tweenk.pl@gmail.com> + * Copyright 2008-2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <2geom/elliptical-arc.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/cairo-path-sink.h> + +#include <vector> +#include <string> + + +using namespace Geom; + + + +std::string angle_formatter(double angle) +{ + return default_formatter(decimal_round(deg_from_rad(angle),2)); +} + + + +class EllipticalArcToy: public Toy +{ + + enum menu_item_t + { + SHOW_MENU = 0, + TEST_BASIC, + TEST_COMPARISON, + TEST_PORTION, + TEST_REVERSE, + TEST_NEAREST_POINTS, + TEST_DERIVATIVE, + TEST_ROOTS, + TEST_BOUNDS, + TEST_FITTING, + TEST_TRANSFORM, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + START_POINT = 0, + END_POINT, + POINT + }; + + enum toggle_label_t + { + LARGE_ARC_FLAG = 0, + SWEEP_FLAG, + X_Y_TOGGLE + }; + + enum slider_label_t + { + RX_SLIDER = 0, + RY_SLIDER, + ROT_ANGLE_SLIDER, + T_SLIDER, + FROM_SLIDER = T_SLIDER, + TO_SLIDER, + TM0_SLIDER = T_SLIDER, + TM1_SLIDER, + TM2_SLIDER, + TM3_SLIDER + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &EllipticalArcToy::draw_menu; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + double start_angle = (10.0/6.0) * M_PI; + double sweep_angle = (4.0/6.0) * M_PI; + double end_angle = start_angle + sweep_angle; + double rot_angle = (0.0/6.0) * M_PI; + double rx = 200; + double ry = 150; + double cx = 300; + double cy = 300; + + Point start_point( cx + rx * std::cos(start_angle), + cy + ry * std::sin(start_angle) ); + Point end_point( cx + rx * std::cos(end_angle), + cy + ry * std::sin(end_angle) ); + + bool large_arc = false; + bool sweep = true; + + + initial_point.pos = start_point; + final_point.pos = end_point; + + try + { + ea.set (initial_point.pos, + rx, ry, rot_angle, + large_arc, sweep, + final_point.pos); + } + catch (RangeError const &e) + { + no_solution = true; + std::cerr << e.what() << std::endl; + } + + sliders.clear(); + sliders.reserve(50); + sliders.emplace_back(0, 500, 0, ea.ray(X), "ray X"); + sliders.emplace_back(0, 500, 0, ea.ray(Y), "ray Y"); + sliders.emplace_back(0, 2*M_PI, 0, ea.rotationAngle(), "rot angle"); + sliders[ROT_ANGLE_SLIDER].formatter(&angle_formatter); + + toggles.clear(); + toggles.reserve(50); + toggles.emplace_back("Large Arc Flag", ea.largeArc()); + toggles.emplace_back("Sweep Flag", ea.sweep()); + + handles.clear(); + handles.push_back(&initial_point); + handles.push_back(&final_point); + handles.push_back(&(sliders[RX_SLIDER])); + handles.push_back(&(sliders[RY_SLIDER])); + handles.push_back(&(sliders[ROT_ANGLE_SLIDER])); + handles.push_back(&(toggles[LARGE_ARC_FLAG])); + handles.push_back(&(toggles[SWEEP_FLAG])); + } + + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream *timer_stream=0) + { + if(timer_stream == 0) + timer_stream = notify; + init_common_ctrl_geom(cr, width, height, notify); + + no_solution = false; + try + { + ea.set( initial_point.pos, + sliders[0].value(), + sliders[1].value(), + sliders[2].value(), + toggles[0].on, + toggles[1].on, + final_point.pos ); + } + catch (RangeError const &e) + { + no_solution = true; + std::cerr << e.what() << std::endl; + return; + } + + degenerate = ea.isDegenerate(); + + point_overlap = false; + if ( are_near(ea.initialPoint(), ea.finalPoint()) ) + { + point_overlap = true; + } + + // calculate the center of the two possible ellipse supporting the arc + std::pair<Point,Point> centers + = calculate_ellipse_centers( ea.initialPoint(), ea.finalPoint(), + ea.ray(X), ea.ray(Y), ea.rotationAngle(), + ea.largeArc(), ea.sweep() ); + + + // draw axes passing through the center of the ellipse supporting the arc + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.5); + draw_axes(cr); + + // draw the 2 ellipse with rays rx, ry passing through + // the 2 given point and with the x-axis inclined of rot_angle + if ( !(are_near(ea.ray(X), 0) || are_near(ea.ray(Y), 0)) ) + { + cairo_elliptiarc( cr, + centers.first[X], centers.first[Y], + ea.ray(X), ea.ray(Y), + 0, 2*M_PI, + ea.rotationAngle() ); + cairo_stroke(cr); + cairo_elliptiarc( cr, + centers.second[X], centers.second[Y], + ea.ray(X), ea.ray(Y), + 0, 2*M_PI, + ea.rotationAngle() ); + cairo_stroke(cr); + } + + // convert the elliptical arc to a sbasis path and draw it + D2<SBasis> easb = ea.toSBasis(); + cairo_set_line_width(cr, 0.5); + cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); + cairo_d2_sb(cr, easb); + cairo_stroke(cr); + + // draw initial and final point labels + draw_text(cr, ea.initialPoint() + Point(5, -15), "initial"); + draw_text(cr, ea.finalPoint() + Point(5, 0), "final"); + cairo_stroke(cr); + + // TODO re-enable this + //*notify << ea; + } + + + void draw_comparison(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + if ( no_solution || point_overlap ) return; + + // draw the arc with cairo in order to make a visual comparison + cairo_set_line_width(cr, 1); + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); + + if (ea.isDegenerate()) + { + cairo_move_to(cr, ea.initialPoint()); + cairo_line_to(cr, ea.finalPoint()); + } + else + { + if ( ea.sweep() ) + { + cairo_elliptiarc( cr, + ea.center(X), ea.center(Y), + ea.ray(X), ea.ray(Y), + ea.initialAngle(), ea.finalAngle(), + ea.rotationAngle() ); + } + else + { + cairo_elliptiarc( cr, + ea.center(X), ea.center(Y), + ea.ray(X), ea.ray(Y), + ea.finalAngle(), ea.initialAngle(), + ea.rotationAngle() ); + } + } + cairo_stroke(cr); + } + + + void init_portion() + { + init_common(); + + from_t = 0; + to_t = 1; + + sliders.emplace_back(0, 1, 0, from_t, "from"); + sliders.emplace_back(0, 1, 0, to_t, "to"); + + handles.push_back(&(sliders[FROM_SLIDER])); + handles.push_back(&(sliders[TO_SLIDER])); + } + + void draw_portion(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_portion_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + from_t = sliders[FROM_SLIDER].value(); + to_t = sliders[TO_SLIDER].value(); + + EllipticalArc* eapp + = static_cast<EllipticalArc*>(ea.portion(from_t, to_t)); + EllipticalArc& eap = *eapp; + + cairo_set_line_width(cr, 0.8); + cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 1.0); + cairo_move_to(cr, eap.center(X), eap.center(Y)); + cairo_line_to(cr, eap.initialPoint()[X], eap.initialPoint()[Y]); + cairo_move_to(cr, eap.center(X), eap.center(Y)); + cairo_line_to(cr, eap.finalPoint()[X], eap.finalPoint()[Y]); + cairo_stroke(cr); + D2<SBasis> sub_arc = eap.toSBasis(); + cairo_d2_sb(cr, sub_arc); + cairo_stroke(cr); + + delete eapp; + + } + + + void init_reverse() + { + init_common(); + time = 0; + + sliders.emplace_back(0, 1, 0, time, "t"); + handles.push_back(&(sliders[T_SLIDER])); + } + + void draw_reverse(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_reverse_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + + time = sliders[T_SLIDER].value(); + + EllipticalArc* eapp = static_cast<EllipticalArc*>(ea.reverse()); + EllipticalArc& eap = *eapp; + + cairo_set_line_width(cr, 0.8); + cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0); + + cairo_move_to(cr, eap.center(X), eap.center(Y)); + cairo_line_to(cr, eap.valueAt(time,X), eap.valueAt(time,Y)); + draw_circ(cr, eap.pointAt(time)); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 1.0); + D2<SBasis> sub_arc = eap.toSBasis(); + cairo_d2_sb(cr, sub_arc); + cairo_stroke(cr); + + delete eapp; + + } + + + void init_np() + { + init_common(); + nph.pos = Point(10,10); + handles.push_back(&nph); + } + + void draw_np(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + if ( no_solution || point_overlap ) return; + + std::vector<double> times = ea.allNearestTimes( nph.pos ); + for (double time : times) + { + cairo_move_to(cr,nph.pos); + cairo_line_to( cr, ea.pointAt(time) ); + } + cairo_stroke(cr); + } + + + void init_derivative() + { + init_common(); + time = 0; + + sliders.emplace_back(0, 1, 0, time, "t"); + handles.push_back(&(sliders[T_SLIDER])); + } + + void draw_derivative(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_reverse_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + time = sliders[T_SLIDER].value(); + + Curve* der = ea.derivative(); + Point p = ea.pointAt(time); + Point v = der->pointAt(time) + p; + delete der; +// std::vector<Point> points = ea.pointAndDerivatives(time, 8); +// Point p = points[0]; +// Point v = points[1] + p; + cairo_move_to(cr, p); + cairo_line_to(cr, v); + cairo_stroke(cr); + } + + + void init_roots() + { + init_common(); + ph.pos = Point(10,10); + toggles.emplace_back("X/Y roots", true ); + + handles.push_back(&ph); + handles.push_back(&(toggles[X_Y_TOGGLE])); + } + + void draw_roots(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_roots_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + Dim2 DIM = toggles[X_Y_TOGGLE].on ? X : Y; + + Point p1[2] = { Point(ph.pos[X], -1000), + Point(-1000, ph.pos[Y]) }; + Point p2[2] = { Point(ph.pos[X], 1000), + Point(1000, ph.pos[Y]) }; + cairo_set_line_width(cr, 0.5); + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_move_to(cr, p1[DIM]); + cairo_line_to(cr, p2[DIM]); + + std::vector<double> times; + try + { + times = ea.roots(ph.pos[DIM], DIM); + *notify << "winding: " << ea.winding(ph.pos); + } + catch(Geom::Exception e) + { + std::cerr << e.what() << std::endl; + } + for (double time : times) + { + draw_handle(cr, ea.pointAt(time)); + } + cairo_stroke(cr); + } + + + void init_bounds() + { + init_common(); + } + + void draw_bounds(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + if ( no_solution || point_overlap ) return; + +// const char* msg[] = { "xmax", "xmin", "ymax", "ymin" }; + + Rect bb = ea.boundsFast(); + +// for ( unsigned int i = 0; i < limits.size(); ++i ) +// { +// std::cerr << "angle[" << i << "] = " << deg_from_rad(limits[i]) << std::endl; +// Point extreme = ea.pointAtAngle(limits[i]); +// draw_handle(cr, extreme ); +// draw_text(cr, extreme, msg[i]); +// } + cairo_rectangle( cr, bb.left(), bb.top(), bb.width(), bb.height() ); + cairo_stroke(cr); + } + + + void init_fitting() + { + init_common(); + } + + void draw_fitting(cairo_t * cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + if ( no_solution || point_overlap ) return; + + D2<SBasis> easb = ea.toSBasis(); + try + { + EllipticalArc earc; + if (!arc_from_sbasis(earc, easb, 0.1, 5)) return; + + D2<SBasis> arc = earc.toSBasis(); + arc[0] += Linear(50, 50); + cairo_d2_sb(cr, arc); + cairo_stroke(cr); + } + catch (RangeError const &e) + { + std::cerr << "conversion failure" << std::endl; + std::cerr << e.what() << std::endl; + return; + } + } + + void init_transform() + { + init_common(); + + double max = 4; + double min = -max; + + sliders.emplace_back(min, max, 0, 1, "TM0"); + sliders.emplace_back(min, max, 0, 0, "TM1"); + sliders.emplace_back(min, max, 0, 0, "TM2"); + sliders.emplace_back(min, max, 0, 1, "TM3"); + + handles.push_back(&(sliders[TM0_SLIDER])); + handles.push_back(&(sliders[TM1_SLIDER])); + handles.push_back(&(sliders[TM2_SLIDER])); + handles.push_back(&(sliders[TM3_SLIDER])); + } + + void draw_transform(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_transform_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + Affine TM(sliders[TM0_SLIDER].value(), sliders[TM1_SLIDER].value(), + sliders[TM2_SLIDER].value(), sliders[TM3_SLIDER].value(), + ea.center(X), ea.center(Y)); + + Affine tm( 1, 0, + 0, 1, + -ea.center(X), -ea.center(Y) ); + + + EllipticalArc* tea = static_cast<EllipticalArc*>(ea.transformed(tm)); + EllipticalArc* eat = NULL; + eat = static_cast<EllipticalArc*>(tea->transformed(TM)); + delete tea; + if (eat == NULL) + { + std::cerr << "elliptiarc transformation failed" << std::endl; + return; + } + + CairoPathSink ps(cr); + + //D2<SBasis> sb = eat->toSBasis(); + cairo_set_line_width(cr, 0.8); + cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0); + //cairo_d2_sb(cr, sb); + ps.feed(*eat); + cairo_stroke(cr); + delete eat; + } + + void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int height, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + + sliders[RX_SLIDER].geometry(Point(50, height-120), 250); + sliders[RY_SLIDER].geometry(Point(50, height-85), 250); + sliders[ROT_ANGLE_SLIDER].geometry(Point(50, height-50), 180); + + toggles[LARGE_ARC_FLAG].bounds = Rect(Point(400, height-120), Point(540, height-95)); + toggles[SWEEP_FLAG].bounds = Rect(Point(400, height-70), Point(520, height-45)); + } + } + + void init_portion_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + Point from_sp = Point(600, height - 120); + Point to_sp = from_sp + Point(0,45); + double from_to_len = 100; + + sliders[FROM_SLIDER].geometry(from_sp, from_to_len); + sliders[TO_SLIDER].geometry(to_sp, from_to_len); + } + } + + void init_reverse_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + Point t_sp = Point(600, height - 120); + double t_len = 200; + + sliders[T_SLIDER].geometry(t_sp, t_len); + } + } + + void init_roots_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + Point T(600, height - 120); + toggles[X_Y_TOGGLE].bounds = Rect( T, T + Point(100,25) ); + } + } + + void init_transform_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + Point sp = Point(600, height - 140); + Point op = Point(0, 30); + double len = 200; + + sliders[TM0_SLIDER].geometry(sp, len); + sliders[TM1_SLIDER].geometry(sp += op, len); + sliders[TM2_SLIDER].geometry(sp += op, len); + sliders[TM3_SLIDER].geometry(sp += op, len); + } + } + + void init_menu() + { + handles.clear(); + sliders.clear(); + toggles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &EllipticalArcToy::draw_menu; + break; + case 'B': + init_common(); + draw_f = &EllipticalArcToy::draw_common; + break; + case 'C': + init_common(); + draw_f = &EllipticalArcToy::draw_comparison; + break; + case 'D': + draw_f = &EllipticalArcToy::draw_menu; + init_portion(); + draw_f = &EllipticalArcToy::draw_portion; + break; + case 'E': + init_reverse(); + draw_f = &EllipticalArcToy::draw_reverse; + break; + case 'F': + init_np(); + draw_f = &EllipticalArcToy::draw_np; + break; + case 'G': + init_derivative(); + draw_f = &EllipticalArcToy::draw_derivative; + break; + case 'H': + init_roots(); + draw_f = &EllipticalArcToy::draw_roots; + break; + case 'I': + init_bounds(); + draw_f = &EllipticalArcToy::draw_bounds; + break; + case 'J': + init_fitting(); + draw_f = &EllipticalArcToy::draw_fitting; + break; + case 'K': + init_transform(); + draw_f = &EllipticalArcToy::draw_transform; + break; + } + redraw(); + } + + + void draw_axes(cairo_t* cr) const + { + Point D(std::cos(ea.rotationAngle()), std::sin(ea.rotationAngle())); + Point Dx = (ea.ray(X) + 20) * D; + Point Dy = (ea.ray(Y) + 20) * D.cw(); + Point C(ea.center(X),ea.center(Y)); + Point LP = C - Dx; + Point RP = C + Dx; + Point UP = C - Dy; + Point DP = C + Dy; + + cairo_move_to(cr, LP[X], LP[Y]); + cairo_line_to(cr, RP[X], RP[Y]); + cairo_move_to(cr, UP[X], UP[Y]); + cairo_line_to(cr, DP[X], DP[Y]); + cairo_move_to(cr, 0, 0); + cairo_stroke(cr); + } + + void cairo_elliptiarc( cairo_t *cr, + double _cx, double _cy, + double _rx, double _ry, + double _sa, double _ea, + double _ra = 0 + ) const + { + double cos_rot_angle = std::cos(_ra); + double sin_rot_angle = std::sin(_ra); + cairo_matrix_t transform_matrix; + cairo_matrix_init( &transform_matrix, + _rx * cos_rot_angle, _rx * sin_rot_angle, + -_ry * sin_rot_angle, _ry * cos_rot_angle, + _cx, _cy + ); + cairo_save(cr); + cairo_transform(cr, &transform_matrix); + cairo_arc(cr, 0, 0, 1, _sa, _ea); + cairo_restore(cr); + } + + + std::pair<Point,Point> + calculate_ellipse_centers( Point _initial_point, Point _final_point, + double m_rx, double m_ry, + double m_rot_angle, + bool m_large_arc, bool m_sweep + ) + { + std::pair<Point,Point> result; + if ( _initial_point == _final_point ) + { + result.first = result.second = _initial_point; + return result; + } + + m_rx = std::fabs(m_rx); + m_ry = std::fabs(m_ry); + + Point d = _initial_point - _final_point; + + if ( are_near(m_rx, 0) || are_near(m_ry, 0) ) + { + result.first = result.second + = middle_point(_initial_point, _final_point); + return result; + } + + double sin_rot_angle = std::sin(m_rot_angle); + double cos_rot_angle = std::cos(m_rot_angle); + + + Affine m( cos_rot_angle, -sin_rot_angle, + sin_rot_angle, cos_rot_angle, + 0, 0 ); + + Point p = (d / 2) * m; + double rx2 = m_rx * m_rx; + double ry2 = m_ry * m_ry; + double rxpy = m_rx * p[Y]; + double rypx = m_ry * p[X]; + double rx2py2 = rxpy * rxpy; + double ry2px2 = rypx * rypx; + double num = rx2 * ry2; + double den = rx2py2 + ry2px2; + assert(den != 0); + double rad = num / den; + Point c(0,0); + if (rad > 1) + { + rad -= 1; + rad = std::sqrt(rad); + + if (m_large_arc == m_sweep) rad = -rad; + c = rad * Point(rxpy / m_ry, -rypx / m_rx); + + m[1] = -m[1]; + m[2] = -m[2]; + + c = c * m; + } + + d = middle_point(_initial_point, _final_point); + + result.first = c + d; + result.second = -c + d; + return result; + + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + EllipticalArcToy() {} + + private: + typedef void (EllipticalArcToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + bool no_solution, point_overlap; + bool degenerate; + PointHandle initial_point, final_point; + PointHandle nph, ph; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + EllipticalArc ea; + + double from_t; + double to_t; + double time; + +}; + + +const char* EllipticalArcToy::menu_items[] = +{ + "show this menu", + "basic", + "comparison", + "portion, pointAt", + "reverse, valueAt", + "nearest points", + "derivative", + "roots", + "bounding box", + "fitting", + "transformation" +}; + +const char EllipticalArcToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K' +}; + + +int main(int argc, char **argv) +{ + init( argc, argv, new EllipticalArcToy(), 850, 780 ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/evolute.cpp b/src/toys/evolute.cpp new file mode 100644 index 0000000..a5deb08 --- /dev/null +++ b/src/toys/evolute.cpp @@ -0,0 +1,93 @@ +#include <2geom/basic-intersection.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +/* +jfb: I think the evolute goes to infinity at inflection points, in which case you cannot "join" the pieces by hand. +jfb: for the evolute toy, you could not only cut at inflection points, but event remove the domains where cross(dda,da)<c*|da|^3, where c is a small constant, as these points will be off screen anyway. +*/ + +class Evolution: public Toy { + PointSetHandle psh; +void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 0.5); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + + D2<SBasis> A(psh.asBezier()); + + D2<SBasis> dA = derivative(A); + D2<SBasis> ddA = derivative(dA); + SBasis crs = cross(ddA, dA); + cairo_d2_sb(cr, D2<SBasis>(Linear(0,1000), crs*(500./bounds_exact(crs)->extent()))); + vector<double> rts = roots(crs); + for(double rt : rts) { + draw_handle(cr, A(rt)); + } + cairo_d2_sb(cr, A); + cairo_stroke(cr); + Interval r(0, 1); + if(!rts.empty()) + r.setMax(rts[0]); + //if(rts[0] == 0) + //rts.erase(rts.begin(), rts.begin()+1); + A = portion(A, r.min(), r.max()); + dA = portion(dA, r.min(), r.max()); + ddA = portion(ddA, r.min(), r.max()); + crs = portion(crs, r.min(), r.max()); + cairo_stroke(cr); + Piecewise<SBasis> s = divide(dA[0]*dot(dA,dA), crs, 100, 1); + D2<Piecewise<SBasis> > ev4(Piecewise<SBasis>(A[0]) + divide(-dA[1]*dot(dA,dA), crs, 100, 1), + Piecewise<SBasis>(A[1]) + divide(dA[0]*dot(dA,dA), crs, 100, 1)); + cairo_d2_pw_sb(cr, ev4); + cairo_stroke(cr); + if(1) { + std::cout << "bnd" << *bounds_exact(dot(ev4, ev4)) << std::endl; + cairo_d2_pw_sb(cr, D2<Piecewise<SBasis> >(Piecewise<SBasis>(SBasis(Linear(0,1000))), dot(ev4, ev4)*1000)); + cairo_stroke(cr); + vector<double> rts = roots(dot(ev4, ev4)-1); + for(double rt : rts) { + std::cout << rt << std::endl; + draw_handle(cr, ev4(rt)); + } + } + cairo_set_source_rgba (cr, 1., 0., 1, 1); + + Toy::draw(cr, notify, width, height, save,timer_stream); +} +public: +Evolution (unsigned bez_ord) { + handles.push_back(&psh); + for(unsigned i = 0; i < bez_ord; i++) + psh.push_back(uniform()*400, uniform()*400); +} +}; + +int main(int argc, char **argv) { + unsigned bez_ord=5; + if(argc > 1) + sscanf(argv[1], "%d", &bez_ord); + init(argc, argv, new Evolution(bez_ord)); + + return 0; +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/filet-minion.cpp b/src/toys/filet-minion.cpp new file mode 100644 index 0000000..e94e354 --- /dev/null +++ b/src/toys/filet-minion.cpp @@ -0,0 +1,159 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/path-intersection.h> +#include <2geom/nearest-time.h> +#include <2geom/circle.h> + +#include <cstdlib> +#include <map> +#include <vector> +#include <algorithm> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> +using namespace Geom; +using namespace std; + +class IntersectDataTester: public Toy { + int nb_paths; + int nb_curves_per_path; + int degree; + + std::vector<PointSetHandle> paths_handles; + std::vector<Slider> sliders; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + std::vector<D2<SBasis> > pieces; + PathVector paths; + for (int i = 0; i < nb_paths; i++){ + paths.push_back(Path(paths_handles[i].pts[0])); + for (unsigned j = 0; j+degree < paths_handles[i].size(); j+=degree){ + D2<SBasis> c = handles_to_sbasis(paths_handles[i].pts.begin()+j, degree); + paths[i].append(c); + pieces.push_back(c); + } + } + + cairo_path(cr, paths); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + double r = sliders[0].value(); + + D2<SBasis> B = pieces[0]; + Piecewise<D2<SBasis> > offset_curve0 = Piecewise<D2<SBasis> >(pieces[0])+rot90(unitVector(derivative(pieces[0])))*(-r); + Piecewise<D2<SBasis> > offset_curve1 = Piecewise<D2<SBasis> >(pieces[1])+rot90(unitVector(derivative(pieces[1])))*(-r); + + //cairo_pw_d2_sb(cr, offset_curve0); + //cairo_pw_d2_sb(cr, offset_curve1); + //cairo_stroke(cr); + + + Path p0 = path_from_piecewise(offset_curve0, 0.1)[0]; + Path p1 = path_from_piecewise(offset_curve1, 0.1)[0]; + Crossings cs = crossings(p0, p1); + + + for(auto & c : cs) { + *notify << c.ta << ", " << c.tb << '\n'; + Point cp =p0(c.ta); + //draw_circ(cr, cp); + //cairo_stroke(cr); + double p0pt = nearest_time(cp, pieces[0]); + double p1pt = nearest_time(cp, pieces[1]); + Circle circ(cp[0], cp[1], r); + //cairo_arc(cr, circ.center(X), circ.center(Y), circ.ray(), 0, 2*M_PI); + + std::unique_ptr<EllipticalArc> eap( + circ.arc(pieces[0](p0pt), pieces[0](1), pieces[1](p1pt)) ); + D2<SBasis> easb = eap->toSBasis(); + cairo_d2_sb(cr, easb); + cairo_stroke(cr); + } + + Point ends[2]; + if (0) + for(int endi = 0; endi < 2; endi++) { + D2<SBasis> dist = pieces[endi]-pieces[0].at1(); + *notify << dist << "\n"; + vector<double> locs = roots(dot(dist,dist) - SBasis(r*r)); + for(double loc : locs) { + //draw_circ(cr, pieces[endi](locs[i])); + *notify << loc << ' '; + } + if(locs.size()) { + std::sort(locs.begin(), locs.end()); + if (endi) + ends[endi] = pieces[endi](locs[0]); + else + ends[endi] = pieces[endi](locs.back()); + draw_circ(cr, ends[endi]); + } + } + + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + IntersectDataTester(int paths, int curves_in_path, int degree) : + nb_paths(paths), nb_curves_per_path(curves_in_path), degree(degree) { + for (int i = 0; i < nb_paths; i++){ + paths_handles.emplace_back(); + } + for(int i = 0; i < nb_paths; i++){ + for(int j = 0; j < (nb_curves_per_path*degree)+1; j++){ + paths_handles[i].push_back(uniform()*400, 100+ uniform()*300); + } + handles.push_back(&paths_handles[i]); + } + sliders.emplace_back(0.0, 100.0, 1, 30.0, "min radius"); + sliders.emplace_back(0.0, 100.0, 1, 0.0, "ray chooser"); + sliders.emplace_back(0.0, 100.0, 1, 0.0, "area chooser"); + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + } + + void first_time(int /*argc*/, char** /*argv*/) override { + + } +}; + +int main(int argc, char **argv) { + unsigned paths=1; + unsigned curves_in_path=2; + unsigned degree=3; + if(argc > 3) + sscanf(argv[3], "%d", °ree); + if(argc > 2) + sscanf(argv[2], "%d", &curves_in_path); + if(argc > 1) + sscanf(argv[1], "%d", &paths); + init(argc, argv, new IntersectDataTester(paths, curves_in_path, degree)); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/find-derivative.cpp b/src/toys/find-derivative.cpp new file mode 100644 index 0000000..e4c64f5 --- /dev/null +++ b/src/toys/find-derivative.cpp @@ -0,0 +1,500 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/angle.h> + +using std::vector; +using namespace Geom; +using namespace std; + +// Author: Johan Engelen, 2009 +// +// Shows how to find the locations on a path where the derivative is parallel to a certain vector. +//----------------------------------------------- + + +std::string angle_formatter(double angle) +{ + return default_formatter(decimal_round(deg_from_rad(angle),2)); +} + + + +class FindDerivatives : public Toy +{ + enum menu_item_t + { + SHOW_MENU = 0, + TEST_CREATE, + TEST_PROJECTION, + TEST_ORTHO, + TEST_DISTANCE, + TEST_POSITION, + TEST_SEG_BISEC, + TEST_ANGLE_BISEC, + TEST_COLLINEAR, + TEST_INTERSECTIONS, + TEST_COEFFICIENTS, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + }; + + enum toggle_label_t + { + }; + + enum slider_label_t + { + END_SHARED_SLIDERS = 0, + ANGLE_SLIDER = END_SHARED_SLIDERS, + A_COEFF_SLIDER = END_SHARED_SLIDERS, + B_COEFF_SLIDER, + C_COEFF_SLIDER + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + PointSetHandle curve_handle; + PointHandle sample_point; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &FindDerivatives::draw_menu; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + sliders.clear(); + toggles.clear(); + handles.clear(); + } + + + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/ ) + { + init_common_ctrl_geom(cr, width, height, notify); + } + + + void init_create() + { + init_common(); + + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + O.pos = Point(50, 400); + + sliders.emplace_back(0, 2*M_PI, 0, 0, "angle"); + sliders[ANGLE_SLIDER].formatter(&angle_formatter); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&O); + handles.push_back(&(sliders[ANGLE_SLIDER])); + } + + void draw_create(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_create_ctrl_geom(cr, notify, width, height); + + Line l1(p1.pos, p2.pos); + Line l2(O.pos, sliders[ANGLE_SLIDER].value()); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, O, "O"); + draw_label(cr, l1, "L(P1,P2)"); + draw_label(cr, l2, "L(O,angle)"); + } + + + void init_projection() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + O.pos = Point(50, 150); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&O); + } + + void draw_projection(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + LineSegment ls(p3.pos, p4.pos); + + Point np = projection(O.pos, l1); + LineSegment lsp = projection(ls, l1); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width(cr, 0.2); + draw_line(cr, l1); + draw_segment(cr, ls); + cairo_stroke(cr); + + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); + draw_segment(cr, lsp); + draw_handle(cr, lsp[0]); + draw_handle(cr, lsp[1]); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + draw_circ(cr, np); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0); + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, ls, "S"); + draw_label(cr, lsp, "prj(S)"); + draw_label(cr, O, "P"); + draw_text(cr, np, "prj(P)"); + + cairo_stroke(cr); + } + + void init_derivative() { + init_common(); + handles.push_back(&curve_handle); + handles.push_back(&sample_point); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + sample_point.pos = Geom::Point(250,300); + } + + void draw_derivative(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + + D2<SBasis> B = curve_handle.asBezier(); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + Point vector = sample_point.pos - Geom::Point(400,400); + cairo_move_to(cr, Geom::Point(400,400)); + cairo_line_to(cr, sample_point.pos); + cairo_set_source_rgba (cr, 0., 0., 0.5, 0.8); + cairo_stroke(cr); + + // How to find location of points with certain derivative along a path: + D2<SBasis> deriv = derivative(B); + SBasis dotp = dot(deriv, rot90(vector)); + std::vector<double> sol = roots(dotp); + for (double i : sol) { + draw_handle(cr, B.valueAt(i)); // the solutions are in vector 'sol' + } + + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void init_find_tangents() { + init_common(); + handles.push_back(&curve_handle); + handles.push_back(&sample_point); + + toggles.emplace_back(" tangent / normal ", false); + handles.push_back(&(toggles[0])); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + sample_point.pos = Geom::Point(250,300); + Point toggle_sp( 30, 30); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(200,25) ); + } + + void draw_find_tangents(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + + D2<SBasis> B = curve_handle.asBezier(); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + std::vector<double> sol = toggles[0].on ? + find_tangents(sample_point.pos, B) + : find_normals(sample_point.pos, B); + for (double i : sol) { + draw_handle(cr, B.valueAt(i)); // the solutions are in vector 'sol' + draw_segment(cr, B.valueAt(i), sample_point.pos); + } + + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void init_ortho() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 50); + p4.pos = Point(150, 450); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + } + + void draw_ortho(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + Line l2 = make_orthogonal_line(p3.pos, l1); + Line l3 = make_parallel_line(p4.pos, l1); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + draw_line(cr, l3); + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, p3, "O1"); + draw_label(cr, p4, "O2"); + + draw_label(cr, l1, "L"); + draw_label(cr, l2, "L1 _|_ L"); + draw_label(cr, l3, "L2 // L"); + + } + + + + + void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int /*height*/, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + } + } + + void init_create_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[ANGLE_SLIDER].geometry(Point(50, height - 50), 180); + } + } + + void init_coefficients_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[A_COEFF_SLIDER].geometry(Point(50, height - 160), 400); + sliders[B_COEFF_SLIDER].geometry(Point(50, height - 110), 400); + sliders[C_COEFF_SLIDER].geometry(Point(50, height - 60), 400); + } + } + + + void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) + { + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); + } + + void draw_segment(cairo_t* cr, LineSegment const& ls) + { + draw_segment(cr, ls[0], ls[1]); + } + + void draw_ray(cairo_t* cr, Ray const& r) + { + double angle = r.angle(); + draw_segment(cr, r.origin(), angle, m_length); + } + + void draw_line(cairo_t* cr, Line const& l) + { + double angle = l.angle(); + draw_segment(cr, l.origin(), angle, m_length); + draw_segment(cr, l.origin(), angle, -m_length); + } + + void draw_label(cairo_t* cr, PointHandle const& ph, const char* label) + { + draw_text(cr, ph.pos+op, label); + } + + void draw_label(cairo_t* cr, Line const& l, const char* label) + { + draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label); + } + + void draw_label(cairo_t* cr, LineSegment const& ls, const char* label) + { + draw_text(cr, middle_point(ls[0], ls[1])+op, label); + } + + void draw_label(cairo_t* cr, Ray const& r, const char* label) + { + Point prj = r.pointAt(r.nearestTime(Point(m_width/2-30, m_height/2-30))); + if (L2(r.origin() - prj) < 100) + { + prj = r.origin() + 100*r.vector(); + } + draw_text(cr, prj+op, label); + } + + void init_menu() + { + handles.clear(); + sliders.clear(); + toggles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/ ) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &FindDerivatives::draw_menu; + break; + case 'B': + init_derivative(); + draw_f = &FindDerivatives::draw_derivative; + break; + case 'C': + init_find_tangents(); + draw_f = &FindDerivatives::draw_find_tangents; + break; + case 'D': + init_ortho(); + draw_f = &FindDerivatives::draw_ortho; + break; + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + FindDerivatives() + { + op = Point(5,5); + } + + private: + typedef void (FindDerivatives::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + PointHandle p1, p2, p3, p4, p5, p6, O; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + Point op; + double m_width, m_height, m_length; + +}; // end class FindDerivatives + + +const char* FindDerivatives::menu_items[] = +{ + "show this menu", + "derivative matching on curve", + "find normals", + "find tangents" +}; + +const char FindDerivatives::keys[] = +{ + 'A', 'B', 'C', 'D' +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new FindDerivatives()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/gear.cpp b/src/toys/gear.cpp new file mode 100644 index 0000000..d2f2de2 --- /dev/null +++ b/src/toys/gear.cpp @@ -0,0 +1,317 @@ +/* + * GearToy - displays involute gears + * + * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com> + * Copyright 2006 Aaron Spike <aaron@ekips.org> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +class Gear { +public: + // pitch circles touch on two properly meshed gears + // all measurements are taken from the pitch circle + double pitch_diameter() {return (_number_of_teeth * _module) / M_PI;} + double pitch_radius() {return pitch_diameter() / 2.0;} + void pitch_radius(double R) {_module = (2 * M_PI * R) / _number_of_teeth;} + + // base circle serves as the basis for the involute toothe profile + double base_diameter() {return pitch_diameter() * cos(_pressure_angle);} + double base_radius() {return base_diameter() / 2.0;} + + // diametrical pitch + double diametrical_pitch() {return _number_of_teeth / pitch_diameter();} + + // height of the tooth above the pitch circle + double addendum() {return 1.0 / diametrical_pitch();} + // depth of the tooth below the pitch circle + double dedendum() {return addendum() + _clearance;} + + // root circle specifies the bottom of the fillet between teeth + double root_radius() {return pitch_radius() - dedendum();} + double root_diameter() {return root_radius() * 2.0;} + + // outer circle is the outside diameter of the gear + double outer_radius() {return pitch_radius() + addendum();} + double outer_diameter() {return outer_radius() * 2.0;} + + // angle covered by the tooth on the pitch circle + double tooth_thickness_angle() {return M_PI / _number_of_teeth;} + + Geom::Point centre() {return _centre;} + void centre(Geom::Point c) {_centre = c;} + + double angle() {return _angle;} + void angle(double a) {_angle = a;} + + int number_of_teeth() {return _number_of_teeth;} + + Geom::Path path(); + Gear spawn(int N, double a); + + Gear(int n, double m, double phi) { + _number_of_teeth = n; + _module = m; + _pressure_angle = phi; + _clearance = 0.0; + _angle = 0.0; + _centre = Geom::Point(0.0,0.0); + } +private: + int _number_of_teeth; + double _pressure_angle; + double _module; + double _clearance; + double _angle; + Geom::Point _centre; + D2<SBasis> _involute(double start, double stop) { + D2<SBasis> B; + D2<SBasis> I; + Linear bo = Linear(start,stop); + + B[0] = cos(bo,2); + B[1] = sin(bo,2); + + I = B - Linear(0,1) * derivative(B); + I = I*base_radius() + _centre; + return I; + } + D2<SBasis> _arc(double start, double stop, double R) { + D2<SBasis> B; + Linear bo = Linear(start,stop); + + B[0] = cos(bo,2); + B[1] = sin(bo,2); + + B = B*R + _centre; + return B; + } + // angle of the base circle used to create the involute to a certain radius + double involute_swath_angle(double R) { + if (R <= base_radius()) return 0.0; + return sqrt(R*R - base_radius()*base_radius())/base_radius(); + } + + // angle of the base circle between the origin of the involute and the intersection on another radius + double involute_intersect_angle(double R) { + if (R <= base_radius()) return 0.0; + return (sqrt(R*R - base_radius()*base_radius())/base_radius()) - acos(base_radius()/R); + } +}; + +void makeContinuous(D2<SBasis> &a, Point const b) { + for(unsigned d=0;d<2;d++) + a[d][0][0] = b[d]; +} + +Geom::Path Gear::path() { + Geom::Path pb; + + // angle covered by a full tooth and fillet + double tooth_rotation = 2.0 * tooth_thickness_angle(); + // angle covered by an involute + double involute_advance = involute_intersect_angle(outer_radius()) - involute_intersect_angle(root_radius()); + // angle covered by the tooth tip + double tip_advance = tooth_thickness_angle() - (2 * (involute_intersect_angle(outer_radius()) - involute_intersect_angle(pitch_radius()))); + // angle covered by the toothe root + double root_advance = (tooth_rotation - tip_advance) - (2.0 * involute_advance); + // begin drawing the involute at t if the root circle is larger than the base circle + double involute_t = involute_swath_angle(root_radius())/involute_swath_angle(outer_radius()); + + //rewind angle to start drawing from the leading edge of the tooth + double first_tooth_angle = _angle - ((0.5 * tip_advance) + involute_advance); + + Geom::Point prev; + for (int i=0; i < _number_of_teeth; i++) + { + double cursor = first_tooth_angle + (i * tooth_rotation); + + D2<SBasis> leading_I = compose(_involute(cursor, cursor + involute_swath_angle(outer_radius())), Linear(involute_t,1)); + if(i != 0) makeContinuous(leading_I, prev); + pb.append(SBasisCurve(leading_I)); + cursor += involute_advance; + prev = leading_I.at1(); + + D2<SBasis> tip = _arc(cursor, cursor+tip_advance, outer_radius()); + makeContinuous(tip, prev); + pb.append(SBasisCurve(tip)); + cursor += tip_advance; + prev = tip.at1(); + + cursor += involute_advance; + D2<SBasis> trailing_I = compose(_involute(cursor, cursor - involute_swath_angle(outer_radius())), Linear(1,involute_t)); + makeContinuous(trailing_I, prev); + pb.append(SBasisCurve(trailing_I)); + prev = trailing_I.at1(); + + if (base_radius() > root_radius()) { + Geom::Point leading_start = trailing_I.at1(); + Geom::Point leading_end = (root_radius() * unit_vector(leading_start - _centre)) + _centre; + prev = leading_end; + pb.appendNew<LineSegment>(leading_end); + } + + D2<SBasis> root = _arc(cursor, cursor+root_advance, root_radius()); + makeContinuous(root, prev); + pb.append(SBasisCurve(root)); + cursor += root_advance; + prev = root.at1(); + + if (base_radius() > root_radius()) { + Geom::Point trailing_start = root.at1(); + Geom::Point trailing_end = (base_radius() * unit_vector(trailing_start - _centre)) + _centre; + pb.appendNew<LineSegment>(trailing_end); + prev = trailing_end; + } + } + + return pb; +} +Gear Gear::spawn(int N, double a) { + Gear gear(N, _module, _pressure_angle); + double dist = gear.pitch_radius() + pitch_radius(); + gear.centre(Geom::Point::polar(a, dist) + _centre); + double new_angle = 0.0; + if (gear.number_of_teeth() % 2 == 0) + new_angle -= gear.tooth_thickness_angle(); + new_angle -= (_angle) * (pitch_radius() / gear.pitch_radius()); + new_angle += (a) * (pitch_radius() / gear.pitch_radius()); + gear.angle(new_angle + a); + return gear; +} + +class GearToy: public Toy { + public: + PointSetHandle hand; + GearToy () { + for(unsigned i = 0; i < 4; i++) + hand.pts.emplace_back(uniform()*400, uniform()*400); + handles.push_back(&hand); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 0.5); + + //Geom::Point centre = Geom::Point(width/2,height/2); + /* draw cross hairs + double dominant_dim = std::max(width,height); + double minor_dim = std::min(width,height); + for(unsigned i = 1; i < 2; i++) { + cairo_move_to(cr, centre[0]-minor_dim/4, centre[1]); + cairo_line_to(cr, centre[0]+minor_dim/4, centre[1]); + cairo_move_to(cr, centre[0], centre[1]-minor_dim/4); + cairo_line_to(cr, centre[0], centre[1]+minor_dim/4); + } + cairo_stroke(cr);*/ + + double pressure_angle = (hand.pts[3][0] / 10) * M_PI / 180; + Gear gear(int(hand.pts[2][0] / 10),200.0,pressure_angle); + Geom::Point gear_centre = hand.pts[1]; + gear.pitch_radius(Geom::distance(gear_centre, hand.pts[0])); + gear.angle(atan2(hand.pts[0] - gear_centre)); + gear.centre(gear_centre); + + // draw radii + cairo_new_sub_path(cr); + cairo_arc(cr, gear_centre[0], gear_centre[1], gear.base_radius(), 0, M_PI*2); + cairo_set_source_rgba (cr, 0., 0., 0.5, 1); + cairo_stroke(cr); + + cairo_new_sub_path(cr); + cairo_arc(cr, gear_centre[0], gear_centre[1], gear.pitch_radius(), 0, M_PI*2); + cairo_set_source_rgba (cr, 0.5, 0., 0., 1); + cairo_stroke(cr); + + cairo_new_sub_path(cr); + cairo_arc(cr, gear_centre[0], gear_centre[1], gear.outer_radius(), 0, M_PI*2); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_stroke(cr); + + cairo_new_sub_path(cr); + cairo_arc(cr, gear_centre[0], gear_centre[1], gear.root_radius(), 0, M_PI*2); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_stroke(cr); + + //draw gear + Geom::Path p = gear.path(); + cairo_path(cr, p); + cairo_set_source_rgba (cr, 0., 0., 0., 0.5); + cairo_set_line_width (cr, 2.0); + cairo_stroke(cr); + + Gear gear2 = gear.spawn(5, -2.0 * M_PI / 8.0); + Geom::Path p2 = gear2.path(); + cairo_path(cr, p2); + cairo_set_source_rgba (cr, 0., 0., 0., 0.5); + cairo_set_line_width (cr, 2.0); + cairo_stroke(cr); + + Gear gear3 = gear2.spawn(8, 0.0 * M_PI / 8.0); + Geom::Path p3 = gear3.path(); + cairo_path(cr, p3); + cairo_set_source_rgba (cr, 0., 0., 0., 0.5); + cairo_set_line_width (cr, 2.0); + cairo_stroke(cr); + + Gear gear4 = gear.spawn(6, 3.0 * M_PI / 4.0); + Geom::Path p4 = gear4.path(); + cairo_path(cr, p4); + cairo_set_source_rgba (cr, 0., 0., 0., 0.5); + cairo_set_line_width (cr, 2.0); + cairo_stroke(cr); + + *notify << "angle = " << gear.angle(); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new GearToy()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/hatches.cpp b/src/toys/hatches.cpp new file mode 100644 index 0000000..28fe440 --- /dev/null +++ b/src/toys/hatches.cpp @@ -0,0 +1,386 @@ +#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <cstdlib>
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+#define SIZE 4
+#define NB_SLIDER 8
+
+//------------------------------------------------
+// Some goodies to navigate through curve's levels.
+//------------------------------------------------
+struct LevelCrossing{
+ Point pt;
+ double t;
+ bool sign;
+ bool used;
+};
+struct LevelCrossingOrder {
+ bool operator()(LevelCrossing a, LevelCrossing b) {
+ return a.pt[Y] < b.pt[Y];
+ }
+};
+
+typedef std::vector<LevelCrossing> LevelCrossings;
+
+class LevelsCrossings: public std::vector<LevelCrossings>{
+public:
+ LevelsCrossings():std::vector<LevelCrossings>(){};
+ LevelsCrossings(std::vector<std::vector<double> > const ×,
+ Piecewise<D2<SBasis> > const &f,
+ Piecewise<SBasis> const &dx){
+ for (const auto & time : times){
+ LevelCrossings lcs;
+ for (double j : time){
+ LevelCrossing lc;
+ lc.pt = f.valueAt(j);
+ lc.t = j;
+ lc.sign = ( dx.valueAt(j)>0 );
+ lc.used = false;
+ lcs.push_back(lc);
+ }
+ std::sort(lcs.begin(), lcs.end(), LevelCrossingOrder());
+ //TODO: reverse all "in" flag if we had the wrong orientation!
+ push_back(lcs);
+ }
+ }
+ void flipInOut(){
+ for (unsigned i=0; i<size(); i++){
+ for (auto & j : (*this)){
+ j.sign = !j.sign;
+ }
+ }
+ }
+ void findFirstUnused(unsigned &level, unsigned &idx){
+ level = size();
+ idx = 0;
+ for (unsigned i=0; i<size(); i++){
+ for (unsigned j=0; j<(*this)[i].size(); j++){
+ if (!(*this)[i][j].used){
+ level = i;
+ idx = j;
+ return;
+ }
+ }
+ }
+ }
+ //set indexes to point to the next point in the "snake walk"
+ //follow_level's meaning:
+ // 0=yes upward
+ // 1=no, last move was upward,
+ // 2=yes downward
+ // 3=no, last move was downward.
+ void step(unsigned &level, unsigned &idx, int &direction){
+ std::cout << "Entering step: "<<level<<","<<idx<<", dir="<< direction<<"\n";
+
+ if ( direction % 2 == 0 ){
+ if (direction == 0) {
+ if ( idx >= (*this)[level].size()-1 || (*this)[level][idx+1].used ) {
+ level = size();
+ std::cout << "max end of level reached...\n";
+ return;
+ }
+ idx += 1;
+ }else{
+ if ( idx <= 0 || (*this)[level][idx-1].used ) {
+ level = size();
+ std::cout << "min end of level reached...\n";
+ return;
+ }
+ idx -= 1;
+ }
+ direction += 1;
+ std::cout << "exit with: "<<level<<","<<idx<<", dir="<< direction<<"\n";
+ return;
+ }
+ double t = (*this)[level][idx].t;
+ double sign = ((*this)[level][idx].sign ? 1 : -1);
+ double next_t = t;
+ level += 1;
+ direction = (direction + 1)%4;
+ if (level == size()){
+ std::cout << "max level reached\n";
+ return;
+ }
+ for (unsigned j=0; j<(*this)[level].size(); j++){
+ double tj = (*this)[level][j].t;
+ if ( sign*(tj-t) > 0 ){
+ if( next_t == t || sign*(tj-next_t)<0 ){
+ next_t = tj;
+ idx = j;
+ }
+ }
+ }
+ if ( next_t == t ){//not found.
+ level = size();
+ std::cout << "no next time found\n";
+ return;
+ }
+ //TODO: time is periodic!!!
+ //TODO: allow several components.
+ if ( (*this)[level][idx].used ) {
+ level = size();
+ std::cout << " reached a point already used\n";
+ return;
+ }
+ std::cout << "exit with: "<<level<<","<<idx<<"\n";
+ return;
+ }
+};
+
+
+//------------------------------------------------
+// Generate the levels with random, growth...
+//------------------------------------------------
+std::vector<double>generateLevels(Interval const &domain,
+ double const width,
+ double const growth,
+ double randomness){
+ std::vector<double> result;
+ std::srand(0);
+ double x = domain.min() + width/2;
+ double step = width;
+ while (x<domain.max()){
+ result.push_back(x);
+ double rdm = 1+ ( (rand() % 100) - 50) /100.*randomness;
+ x+= step*growth*rdm;
+ step*=growth;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------------
+// Walk through the intersections to create linear hatches
+//-------------------------------------------------------
+std::vector<Point> linearSnake(Piecewise<D2<SBasis> > const &f, double dy,double growth, double rdmness){
+
+ std::vector<Point> result;
+
+ Piecewise<SBasis> x = make_cuts_independent(f)[X];
+ //Rque: derivative is computed twice in the 2 lines below!!
+ Piecewise<SBasis> dx = derivative(x);
+ OptInterval range = bounds_exact(x);
+ //TODO: test range non emptyness!!
+ std::vector<double> levels = generateLevels((*range), dy, growth, rdmness);
+ std::vector<std::vector<double> > times;
+ times = multi_roots(x,levels);
+
+//TODO: fix multi_roots!!!*****************************************
+//remove doubles :-(
+ std::vector<std::vector<double> > cleaned_times(levels.size(),std::vector<double>());
+ for (unsigned i=0; i<times.size(); i++){
+ if ( times[i].size()>0 ){
+ double last_t = times[i][0]-1;//ugly hack!!
+ for (unsigned j=0; j<times[i].size(); j++){
+ if (times[i][j]-last_t >0.000001){
+ last_t = times[i][j];
+ cleaned_times[i].push_back(last_t);
+ }
+ }
+ }
+ }
+ times = cleaned_times;
+ for (unsigned i=0; i<times.size(); i++){
+ std::cout << "roots on level "<<i<<": ";
+ for (double j : times){
+ std::cout << j <<" ";
+ }
+ std::cout <<"\n";
+ }
+//*******************************************************************
+ LevelsCrossings lscs(times,f,dx);
+ unsigned i,j;
+ lscs.findFirstUnused(i,j);
+ while ( i < lscs.size() ){
+ int dir = 0;
+ while ( i < lscs.size() ){
+ result.push_back(lscs[i][j].pt);
+ lscs[i][j].used = true;
+ lscs.step(i,j, dir);
+ }
+ //TODO: handle "non convex cases" where hatches have to be restarted at some point.
+ //This needs some care in linearSnake->smoothSnake.
+ //
+ lscs.findFirstUnused(i,j);
+ }
+ return result;
+}
+
+//-------------------------------------------------------
+// Smooth the linear hatches according to params...
+//-------------------------------------------------------
+Piecewise<D2<SBasis> > smoothSnake(std::vector<Point> const &linearSnake,
+ double scale_bf = 1, double scale_bb = 1,
+ double scale_tf = 1, double scale_tb = 1){
+
+ if (linearSnake.size()<2) return Piecewise<D2<SBasis> >();
+ bool is_top = true;
+ Point last_pt = linearSnake[0];
+ Point last_hdle = linearSnake[0];
+ Path result(last_pt);
+ unsigned i=1;
+ while( i+1<linearSnake.size() ){
+ Point pt0 = linearSnake[i];
+ Point pt1 = linearSnake[i+1];
+ Point new_pt = (pt0+pt1)/2;
+ double scale = (is_top ? scale_tf : scale_bf );
+ Point new_hdle = new_pt+(pt0-new_pt)*scale;
+
+ result.appendNew<CubicBezier>(last_hdle,new_hdle,new_pt);
+
+ last_pt = new_pt;
+ scale = (is_top ? scale_tb : scale_bb );
+ last_hdle = new_pt+(pt1-new_pt)*scale;
+ i+=2;
+ is_top = !is_top;
+ }
+ if ( i<linearSnake.size() )
+ result.appendNew<CubicBezier>(last_hdle,linearSnake[i],linearSnake[i]);
+ return result.toPwSb();
+}
+
+//-------------------------------------------------------
+// Bend a path...
+//-------------------------------------------------------
+
+Piecewise<D2<SBasis> > bend(Piecewise<D2<SBasis> > const &f, Piecewise<SBasis> bending){
+ D2<Piecewise<SBasis> > ff = make_cuts_independent(f);
+ ff[X] += compose(bending, ff[Y]);
+ return sectionize(ff);
+}
+
+//-------------------------------------------------------
+// The toy!
+//-------------------------------------------------------
+class HatchesToy: public Toy {
+
+ PointHandle adjuster[NB_SLIDER];
+
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+ for(unsigned i=0; i<NB_SLIDER; i++){
+ adjuster[i].pos[X] = 30+i*20;
+ if (adjuster[i].pos[Y]<100) adjuster[i].pos[Y] = 100;
+ if (adjuster[i].pos[Y]>400) adjuster[i].pos[Y] = 400;
+ cairo_move_to(cr, Point(30+i*20,100));
+ cairo_line_to(cr, Point(30+i*20,400));
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ }
+ double hatch_width = (400-adjuster[0].pos[Y])/300.*50;
+ double scale_topfront = (250-adjuster[1].pos[Y])/150.*5;
+ double scale_topback = (250-adjuster[2].pos[Y])/150.*5;
+ double scale_botfront = (250-adjuster[3].pos[Y])/150.*5;
+ double scale_botback = (250-adjuster[4].pos[Y])/150.*5;
+ double growth = 1+(250-adjuster[5].pos[Y])/150.*.1;
+ double rdmness = 1+(400-adjuster[6].pos[Y])/300.*.9;
+ double bend_amount = (250-adjuster[7].pos[Y])/300.*100.;
+
+ b1_handle.pts.back() = b2_handle.pts.front();
+ b1_handle.pts.front() = b2_handle.pts.back();
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+
+ {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_d2_sb(cr, B1);
+ cairo_d2_sb(cr, B2);
+ cairo_restore(cr);
+ }
+
+ Piecewise<D2<SBasis> >B;
+ B.concat(Piecewise<D2<SBasis> >(B1));
+ B.continuousConcat(Piecewise<D2<SBasis> >(B2));
+
+ Piecewise<SBasis> bending = Piecewise<SBasis>(shift(Linear(bend_amount),1));
+ //TODO: test optrect non empty!!
+ bending.setDomain((*bounds_exact(B))[Y]);
+ Piecewise<D2<SBasis> >bentB = bend(B, bending);
+
+ std::vector<Point> snakePoints;
+ snakePoints = linearSnake(bentB, hatch_width, growth, rdmness);
+ Piecewise<D2<SBasis> >smthSnake = smoothSnake(snakePoints,
+ scale_topfront,
+ scale_topback,
+ scale_botfront,
+ scale_botback);
+
+ smthSnake = bend(smthSnake, -bending);
+ cairo_pw_d2_sb(cr, smthSnake);
+ cairo_set_line_width (cr, 1.5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+ if ( snakePoints.size() > 0 ){
+ Path snake(snakePoints.front());
+ for (unsigned i=1; i<snakePoints.size(); i++){
+ snake.appendNew<LineSegment>(snakePoints[i]);
+ }
+ //cairo_pw_d2_sb(cr, snake.toPwSb() );
+ }
+
+ //cairo_pw_d2_sb(cr, B);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0.7, 0.2, 0., 1);
+ cairo_stroke(cr);
+
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ HatchesToy(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ b1_handle.pts[0] = Geom::Point(400,300);
+ b1_handle.pts[1] = Geom::Point(400,400);
+ b1_handle.pts[2] = Geom::Point(100,400);
+ b1_handle.pts[3] = Geom::Point(100,300);
+
+ b2_handle.pts[0] = Geom::Point(100,300);
+ b2_handle.pts[1] = Geom::Point(100,200);
+ b2_handle.pts[2] = Geom::Point(400,200);
+ b2_handle.pts[3] = Geom::Point(400,300);
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+
+ for(unsigned i = 0; i < NB_SLIDER; i++) {
+ adjuster[i].pos = Geom::Point(30+i*20,250);
+ handles.push_back(&(adjuster[i]));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new HatchesToy);
+ return 0;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+//vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99:
diff --git a/src/toys/implicit-toy.cpp b/src/toys/implicit-toy.cpp new file mode 100644 index 0000000..c90c082 --- /dev/null +++ b/src/toys/implicit-toy.cpp @@ -0,0 +1,510 @@ +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/geom.h> +#include <2geom/d2.h> +#include <2geom/polynomial.h> +#include <2geom/sbasis-poly.h> +#include <2geom/transforms.h> + +#include <2geom/symbolic/implicit.h> + +#include <aa.h> + +#include <algorithm> +#include <ctime> +#include <functional> + + +using namespace Geom; + + + +struct PtLexCmp{ + bool operator()(const Point &a, const Point &b) { + return (a[0] < b[0]) || ((a[0] == b[0]) and (a[1] < b[1])); + } +}; + +//typedef AAF (*implicit_curve_t)(AAF, AAF); +typedef std::function<AAF (AAF const&, AAF const&)> implicit_curve_t; + +// draw ax + by + c = 0 +void draw_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) +{ + vector<Geom::Point> result; + Point resultp; + if(intersects == line_intersection(Point(1, 0), r.left(), + n, c, + resultp) && r[1].contains(resultp[1])) + result.push_back(resultp); + if(intersects == line_intersection(Point(1, 0), r.right(), + n, c, + resultp) && r[1].contains(resultp[1])) + result.push_back(resultp); + if(intersects == line_intersection(Point(0, 1), r.top(), + n, c, + resultp) && r[0].contains(resultp[0])) + result.push_back(resultp); + if(intersects == line_intersection(Point(0, 1), r.bottom(), + n, c, + resultp) && r[0].contains(resultp[0])) + result.push_back(resultp); + if(result.size() > 2) { + std::sort(result.begin(), result.end(), PtLexCmp()); + vector<Geom::Point>::iterator new_end = std::unique(result.begin(), result.end()); + result.resize(new_end-result.begin()); + } + if(result.size() == 2) + { + cairo_move_to(cr, result[0]); + cairo_line_to(cr, result[1]); + cairo_stroke(cr); + } +} + +OptRect tighten(Rect const&r, Point n, Interval lu) +{ + vector<Geom::Point> result; + Point resultp; + for(int i = 0; i < 4; i++) + { + Point cnr = r.corner(i); + double z = dot(cnr, n); + if ((z > lu[0]) && (z < lu[1])) + result.push_back(cnr); + } + for(int i = 0; i < 2; i++) + { + double c = lu[i]; + if(intersects == line_intersection(Point(1, 0), r.left(), + n, c, + resultp) && r[1].contains(resultp[1])) + result.push_back(resultp); + if(intersects == line_intersection(Point(1, 0), r.right(), + n, c, + resultp) && r[1].contains(resultp[1])) + result.push_back(resultp); + if(intersects == line_intersection(Point(0, 1), r.top(), + n, c, + resultp) && r[0].contains(resultp[0])) + result.push_back(resultp); + if(intersects == line_intersection(Point(0, 1), r.bottom(), + n, c, + resultp) && r[0].contains(resultp[0])) + result.push_back(resultp); + } + if(result.size() < 2) + return OptRect; + Rect nr(result[0], result[1]); + for(size_t i = 2; i < result.size(); i++) + { + nr.expandTo(result[i]); + } + return intersect(nr, r); +} + +static const unsigned int DEG = 5; +double bvp[DEG+1][DEG+1] + = {{-1, 0.00945115, -4.11799e-05, 1.01365e-07, -1.35037e-10, 7.7868e-14}, + {0.00837569, -6.24676e-05, 1.96093e-07, -3.09683e-10, 1.95681e-13, 0}, + {-2.39448e-05, 1.3331e-07, -2.65787e-10, 1.96698e-13, 0, 0}, + {2.76173e-08, -1.01069e-10, 9.88596e-14, 0, 0, 0}, + {-1.43584e-11, 2.48433e-14, 0, 0, 0, 0}, {2.49723e-15, 0, 0, 0, 0, 0}}; + + +AAF trial_eval(AAF const& x, AAF const& y) { +// AAF x = _x/100; +// AAF y = _y/100; + //return x*x - 1; + //return y - pow(x,3); + //return y - pow_sample_based(x,2.5); + //return y - log_sample_based(x); + //return y - log(x); + //return y - exp_sample_based(x*log(x)); + //return y - sqrt(sin(x)); + //return sqrt(y)*x - sqrt(x) - y - 1; + //return y-1/x; + //return exp(x)-y; + //return sin(x)-y; + //return exp_sample_based(x)-y; + //return atan(x)-y; + //return atan_sample_based(x)-y; + //return atanh(x)-y; + //return x*y; + //return 4*x+3*y-1; + //return x*x + y*y - 1; + //return sin(x*y) + cos(pow(x, 3)) - atan(x); + //return pow((x*x + y*y), 2) - (x*x-y*y); + //return x*x-y; + //return (x*x*x-y*x)*sin(x) + (x-y*y)*cos(y)-0.5; +// return -120.75 +(-64.4688 +(-16.6875 +(0.53125 -0.00390625*y)*y)*y)*y +// + (-15.9375 + ( 1.5 +( 4.375 -0.0625*y)*y)*y +// + (17 +( 9.5 -0.375*y)*y + (2 + -1*y -1*x)*x)*x)*x; + +// AAF v(0); +// for (size_t i = DEG; i > 0; --i) +// { +// AAF vy(0); +// for (size_t j = DEG - i; j > 0; --j) +// { +// vy += bvp[i][j]; +// vy *= y; +// } +// vy += bvp[i][0]; +// v += vy; +// v *= x; +// } +// AAF vy(0); +// for (size_t j = DEG; j > 0; --j) +// { +// vy += bvp[0][j]; +// vy *= y; +// } +// vy += bvp[0][0]; +// v += vy; +// return v; + + int i = DEG; + int j = DEG - i; + AAF vy(bvp[i][j]); + --j; + for (; j >= 0; --j) + { + vy *= y; + vy += bvp[DEG][j]; + } + AAF v(vy); + --i; + for (; i >= 0; --i) + { + int j = DEG - i; + AAF vy(bvp[i][j]); + --j; + for (; j >= 0; --j) + { + vy *= y; + vy += bvp[i][j]; + } + v *= x; + v += vy; + } + return v; + +// return +// -1 +( 0.00945115 +( -4.11799e-05 +( 1.01365e-07 +( -1.35037e-10 + 7.7868e-14*y)*y)*y)*y)*y +// + (0.00837569 +( -6.24676e-05 +( 1.96093e-07 +( -3.09683e-10 + 1.95681e-13*y)*y)*y)*y +// + (-2.39448e-05 +( 1.3331e-07 +( -2.65787e-10 + 1.96698e-13*y)*y)*y +// + (2.76173e-08 +( -1.01069e-10 + 9.88596e-14*y)*y +// + (-1.43584e-11 + 2.48433e-14*y + 2.49723e-15*x)*x)*x)*x)*x; +} + + + +double max_modulus (SL::MVPoly2 const& p) +{ + double a, m = 1; + + for (size_t i = 0; i < p.get_poly().size(); ++i) + for (double j : p) + { + a = std::abs(j); + if (m < a) m = a; + } + return m; +} + +void poly_to_mvpoly1(SL::MVPoly1& p, Geom::Poly const& q) +{ + for (size_t i = 0; i < q.size(); ++i) + { + p.coefficient(i, q[i]); + } + p.normalize(); +} + +void make_implicit_curve (SL::MVPoly2& ic, D2<SBasis> const& pc) +{ + Geom::Poly pc0 = sbasis_to_poly(pc[0]); + Geom::Poly pc1 = sbasis_to_poly(pc[1]); + +// std::cerr << "parametrization: \n"; +// std::cerr << "pc0 = " << pc0 << std::endl; +// std::cerr << "pc1 = " << pc1 << "\n\n"; + + SL::MVPoly1 f, g; + poly_to_mvpoly1(f, pc0); + poly_to_mvpoly1(g, pc1); + +// std::cerr << "parametrization: \n"; +// std::cerr << "f = " << f << std::endl; +// std::cerr << "g = " << g << "\n\n"; + + Geom::SL::basis_type b; + microbasis(b, f, g); + + Geom::SL::MVPoly3 p, q; + basis_to_poly(p, b[0]); + basis_to_poly(q, b[1]); + +// std::cerr << "generators as polynomial in R[t,x,y] : \n"; +// std::cerr << "p = " << p << std::endl; +// std::cerr << "q = " << q << "\n\n"; + + + Geom::SL::Matrix<Geom::SL::MVPoly2> B = make_bezout_matrix(p, q); + ic = determinant_minor(B); + ic.normalize(); + double m = max_modulus(ic); + ic /= m; + +// std::cerr << "Bezout matrix: (entries are bivariate polynomials) \n"; +// std::cerr << "B = " << B << "\n\n"; +// std::cerr << "determinant: \n"; +// std::cerr << "r(x, y) = " << ic << "\n\n"; + +} + +//namespace Geom{ namespace SL{ +// +//template<> +//struct zero<AAF, false> +//{ +// AAF operator() () const +// { +// return AAF(0); +// } +//}; +// +//} } + +class ImplicitToy : public Toy +{ + bool contains_zero (implicit_curve_t const& eval, + Rect r, double w=1e-5) + { + ++iters; + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + AAF f = eval(x, y); + double a = f.index_coeff(x.get_index(0)) / x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0)) / y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if (f.straddles_zero()) + { + if ((r.width() > w) || (r.height() > w)) + { + Point c = r.midpoint(); + Rect oldr = r; + if (out) r = *out; + // Three possibilities: + // 1) the trim operation buys us enough that we should just iterate + if (1 && (r.area() < oldr.area()*0.25)) + { + return contains_zero(eval, r, w); + } + // 2) one dimension is significantly smaller + else if (1 && (r[1].extent() < oldr[1].extent()*0.5)) + { + return contains_zero (eval, + Rect(Interval(r.left(), r.right()), + Interval(r.top(), c[1])), w) + || contains_zero (eval, + Rect(Interval(r.left(), r.right()), + Interval(c[1], r.bottom())), w); + } + else if (1 && (r[0].extent() < oldr[0].extent()*0.5)) + { + return contains_zero (eval, + Rect(Interval(r.left(), c[0]), + Interval(r.top(), r.bottom())), w) + || contains_zero (eval, + Rect(Interval(c[0], r.right()), + Interval(r.top(), r.bottom())), w); + } + // 3) to ensure progress we must do a four way split + else + { + return contains_zero (eval, + Rect(Interval(r.left(), c[0]), + Interval(r.top(), c[1])), w) + || contains_zero (eval, + Rect(Interval(c[0], r.right()), + Interval(r.top(), c[1])), w) + || contains_zero (eval, + Rect(Interval(r.left(), c[0]), + Interval(c[1], r.bottom())), w) + || contains_zero (eval, + Rect(Interval(c[0], r.right()), + Interval(c[1], r.bottom())), w); + } + } + //std::cout << w << " < " << r.width() << " , " << r.height() << std::endl; + //std::cout << r.min() << " - " << r.max() << std::endl; + return true; + } + return false; + } // end recursive_implicit + + + void draw_implicit_curve (cairo_t*cr, implicit_curve_t const& eval, + Point const& origin, Rect r, double w) + { + ++iters; + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //assert(x.rad() > 0); + //assert(y.rad() > 0); +// time(&t0); + AAF f = eval(x-origin[X], y-origin[Y]); +// time(&t1); +// d1 += std::difftime(t1, t0); + // pivot +// time(&t2); + double a = f.index_coeff(x.get_index(0)) / x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0)) / y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if (ivl.extent() < 0.5*L2(n)) + { + draw_line_in_rect(cr, r, n, ivl.middle()); + return; + } +// time(&t3); +// d2 += std::difftime(t3, t2); + if ((r.width() > w) || (r.height() > w)) + { + if (f.straddles_zero()) + { + Point c = r.midpoint(); + Rect oldr = r; + if (out) r = *out; + // Three possibilities: + // 1) the trim operation buys us enough that we should just iterate + if (1 && (r.area() < oldr.area()*0.25)) + { + draw_implicit_curve(cr, eval, origin, r, w); + } + // 2) one dimension is significantly smaller + else if (1 && (r[1].extent() < oldr[1].extent()*0.5)) + { + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), r.right()), + Interval(r.top(), c[1])), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), r.right()), + Interval(c[1], r.bottom())), w); + } + else if (1 && (r[0].extent() < oldr[0].extent()*0.5)) + { + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), c[0]), + Interval(r.top(), r.bottom())), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(c[0], r.right()), + Interval(r.top(), r.bottom())), w); + } + // 3) to ensure progress we must do a four way split + else + { + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), c[0]), + Interval(r.top(), c[1])), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(c[0], r.right()), + Interval(r.top(), c[1])), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), c[0]), + Interval(c[1], r.bottom())), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(c[0], r.right()), + Interval(c[1], r.bottom())), w); + } + } + } else { + if(contains_zero(eval, r*Geom::Translate(-origin))) { + cairo_save(cr); + cairo_set_source_rgb(cr, 0,0.5,0); + cairo_rectangle(cr, r); + cairo_fill(cr); + cairo_restore(cr); + } + } + } // end recursive_implicit + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + iters = 0; + d1 = d2 = 0; + cairo_set_line_width (cr, 0.3); + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + + SL::MVPoly2 ic; + make_implicit_curve(ic, A); + + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 0.8); + draw_implicit_curve (cr, ic, orig_handle.pos, + Rect(Interval(0,width), Interval(0, height)), 1); + cairo_stroke(cr); + +// std::cerr << "D1 = " << d1 << std::endl; +// std::cerr << "D2 = " << d2 << std::endl; + + *notify << "iter: " << iters; + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + +public: + ImplicitToy(unsigned int _A_bez_ord) + : A_bez_ord(_A_bez_ord) + { + handles.push_back(&orig_handle); + orig_handle.pos = Point(0,0); //Point(300,300); + + handles.push_back(&pshA); + for (unsigned int i = 0; i < A_bez_ord; ++i) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)); + } + +private: + unsigned int A_bez_ord; + PointHandle orig_handle; + PointSetHandle pshA; + time_t t0, t1, t2, t3; + double d1, d2; + unsigned int iters; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=5; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + + init( argc, argv, new ImplicitToy(A_bez_ord)); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/ineaa.cpp b/src/toys/ineaa.cpp new file mode 100644 index 0000000..75b9d41 --- /dev/null +++ b/src/toys/ineaa.cpp @@ -0,0 +1,655 @@ +#include <2geom/convex-hull.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/d2.h> +#include <2geom/geom.h> +#include <2geom/numeric/linear_system.h> + +#include <aa.h> +#include <complex> +#include <algorithm> +#include <optional> + +using std::vector; +using namespace Geom; +using namespace std; + +typedef std::complex<AAF> CAAF; + +struct PtLexCmp{ + bool operator()(const Point &a, const Point &b) { + return (a[0] < b[0]) || ((a[0] == b[0]) and (a[1] < b[1])); + } +}; + +// draw ax + by + c = 0 +void draw_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) { + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + cairo_move_to(cr, (*ls)[0]); + cairo_line_to(cr, (*ls)[1]); + cairo_stroke(cr); + + } +} + +void fill_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) { + ConvexHull ch; + + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + ch.boundary.push_back((*ls)[0]); + ch.boundary.push_back((*ls)[1]); + } + for(int i = 0; i < 4; i++) { + Point p = r.corner(i); + if(dot(n,p) < c) { + ch.boundary.push_back(p); + } + } + ch.graham(); + cairo_convex_hull(cr, ch.boundary); +} + +OptRect tighten(Rect &r, Point n, Interval lu) { + vector<Geom::Point> result; + Point resultp; + for(int i = 0; i < 4; i++) { + Point cnr = r.corner(i); + double z = dot(cnr, n); + if((z > lu[0]) and (z < lu[1])) + result.push_back(cnr); + } + for(int i = 0; i < 2; i++) { + double c = lu[i]; + + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + result.push_back((*ls)[0]); + result.push_back((*ls)[1]); + } + } + if(result.size() < 2) + return OptRect(); + Rect nr(result[0], result[1]); + for(size_t i = 2; i < result.size(); i++) { + nr.expandTo(result[i]); + } + return intersect(nr, r); +} + +AAF ls_sample_based(AAF x, vector<Point> pts) { + NL::Matrix m(pts.size(), 2); + NL::Vector v(pts.size()); + NL::LinearSystem ls(m, v); + + m.set_all(0); + v.set_all(0); + for (unsigned int k = 0; k < pts.size(); ++k) + { + m(k,0) += pts[k][0]; + m(k,1) += 1; + //std::cout << pts[k] << " "; + + v[k] += pts[k][1]; + //v[1] += pts[k][1]; + //v[2] += y2; + } + + ls.SV_solve(); + + double A = ls.solution()[0]; + double B = ls.solution()[1]; + // Ax + B = y + Interval bnd(0,0); + for (unsigned int k = 0; k < pts.size(); ++k) + { + bnd.extendTo(A*pts[k][0]+B - pts[k][1]); + } + //std::cout << A << "," << B << std::endl; + return AAF(x, A, B, bnd.extent(), + x.special); +} + +AAF md_sample_based(AAF x, vector<Point> pts) { + Geom::ConvexHull ch1(pts); + Point a, b, c; + ch1.narrowest_diameter(a, b, c); + Point db = c-b; + double A = db[1]/db[0]; + Point aa = db*(dot(db, a-b)/dot(db,db))+b; + Point mid = (a+aa)/2; + double B = mid[1] - A*mid[0]; + double dB = (a[1] - A*a[0]) - B; + // Ax + B = y + //std::cout << A << "," << B << std::endl; + return AAF(x, A, B, dB, + x.special); +} + +AAF atan_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + + const double ea = atan(a); + const double eb = atan(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + double xs = sqrt(1/alpha-1); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,atan(xs))); + xs = -xs; + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,atan(xs))); + + return md_sample_based(x, pts); +} + +AAF log_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + AAF_TYPE type; + if(a > 0) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + + const double ea = log(a); + const double eb = log(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // dlog(xs) = alpha + double xs = 1/(alpha); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,log(xs))); + + return md_sample_based(x, pts); +} + +AAF exp_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + + const double ea = exp(a); + const double eb = exp(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // dexp(xs) = alpha + double xs = log(alpha); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,exp(xs))); + + return md_sample_based(x, pts); +} + +double +desy_lambert_W(double x) { + if (x <= 500.0) { + double lx1 = log(x + 1.0); + return 0.665 * (1 + 0.0195 * lx1) * lx1 + 0.04; + } + return log(x - 4.0) - (1.0 - 1.0/log(x)) * log(log(x)); +} + +double lambertW(double x, double prec = 1E-12, int maxiters = 100) { + double w = desy_lambert_W(x); + const double e = exp(1); + for(int i = 0; i < maxiters; i++) { + double we = w * pow(e,w); + double w1e = (w + 1) * pow(e,w); + if( prec > abs((x - we) / w1e)) + return w; + w -= (we - x) / (w1e - (w+2) * (we-x) / (2*w+2)); + } + //raise ValueError("W doesn't converge fast enough for abs(z) = %f" % abs(x)) + return 0./0.; +} + +#include <gsl/gsl_errno.h> +#include <gsl/gsl_math.h> +#include <gsl/gsl_min.h> + +typedef struct{ + double a, b; +} param_W; + +double fn1 (double x, void * params) +{ + param_W *pw = (param_W*)params; + return (pw->a*x+pw->b) - lambertW(x); +} + +double optimise(void * params, double a, double b) { + int status; + //param_W *pw = (param_W*)params; + int iter = 0, max_iter = 100; + double m = (a+b)/2; + gsl_function F; + + F.function = &fn1; + F.params = params; + + const gsl_min_fminimizer_type *T = gsl_min_fminimizer_brent; + gsl_min_fminimizer *s = gsl_min_fminimizer_alloc (T); + if(a+1e-10 >= b) return m; +#if 0 + cout << a << " " << b << " " << m << endl; + cout << "fn:" << fn1(a, params) << " " << fn1(b, params) << " " << fn1(m, params) << endl; + cout << "fn:" << (pw->a*a+pw->b) << " " << (pw->a*b+pw->b) << " " << (pw->a*m+pw->b) << endl; +#endif + gsl_min_fminimizer_set (s, &F, m, a, b); + do + { + iter++; + status = gsl_min_fminimizer_iterate (s); + + m = gsl_min_fminimizer_x_minimum (s); + a = gsl_min_fminimizer_x_lower (s); + b = gsl_min_fminimizer_x_upper (s); + + status + = gsl_min_test_interval (a, b, 0.001, 0.0); + + } + while (status == GSL_CONTINUE && iter < max_iter); + + gsl_min_fminimizer_free (s); + + return m; +} + + +AAF W_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + const double e = exp(1); + AAF_TYPE type; + if(a >= -1./e) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + const double ea = lambertW(a); + const double eb = lambertW(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // d(W(xs)) = alpha + // W = + param_W pw; + pw.a = alpha; + pw.b = ea - alpha*a; + if(a < b) { + double xs = optimise(&pw, a, b); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,lambertW(xs))); + } + return md_sample_based(x, pts); +} + +AAF pow_sample_based(AAF x, double p) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + AAF_TYPE type; + if(floor(p) != p) { + if(a >= 0) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + } + const double ea = pow(a, p); + const double eb = pow(b, p); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // d(xs^p) = alpha + // p xs^(p-1) = alpha + // xs = (alpha/p)^(1-p) + double xs = pow(alpha/p, 1./(p-1)); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,pow(xs, p))); + xs = -xs; + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,pow(xs, p))); + + return md_sample_based(x, pts); +} + +Point origin; +AAF trial_eval(AAF x, AAF y) { + x = x-origin[0]; + y = y-origin[1]; + x = x/200; + y = y/200; + AAF x2 = pow_sample_based(x,2); + AAF y2 = pow_sample_based(y,2); + //return x2 + y2 - 1; + //return y - pow(x,3); + return y + W_sample_based(x); + //return y - pow_sample_based(x,2.5); + //return y - log_sample_based(x); + //return y - log(x); + //return -y + -exp_sample_based(x*log(x)); + return -x + -exp_sample_based(y*log(y)); + //return y - sqrt(sin(x)); + //return sqrt(y)*x - sqrt(x) - y - 1; + //return y-1/x; + //return exp(x)-y; + //return sin(x)-y; + //return exp_sample_based(x)-y; + //return atan(x)-y; + //return atan_sample_based(x)-y; + //return atanh(x)-y; + //return x*y; + //return 4*x+3*y-1; + //return x*x + y*y - 1; + return pow_sample_based((x2 + y2), 2) - (x2-y2); + //return x*x-y; + //return (x*x*x-y*x)*sin(x) + (x-y*y)*cos(y)-0.5; +} + +AAF xaxis(AAF x, AAF y) { + (void)x; + y = y-origin[1]; + y = y/200; + return y; +} + +AAF yaxis(AAF x, AAF y) { + (void)y; + x = x-origin[0]; + x = x/200; + return x; +} + +class ConvexTest: public Toy { +public: + PointSetHandle test_window; + PointSetHandle samples; + PointHandle orig_handle; + ConvexTest () { + toggles.push_back(Toggle("Show trials", false)); + handles.push_back(&test_window); + handles.push_back(&samples); + handles.push_back(&orig_handle); + orig_handle.pos = Point(00,300); + test_window.push_back(Point(100,100)); + test_window.push_back(Point(200,200)); + for(int i = 0; i < 0; i++) { + samples.push_back(Point(i*100, i*100+25)); + } + } + int iters; + int splits[4]; + bool show_splits; + std::vector<Toggle> toggles; + AAF (*eval)(AAF, AAF); + void recursive_implicit(Rect r, cairo_t*cr, double w) { + if(show_splits) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + /*if(f.is_partial()) + cairo_set_source_rgba(cr, 1, 0, 1, 0.25); + else*/ + cairo_set_source_rgba(cr, 0, 1, 0, 0.25); + cairo_rectangle(cr, r); + cairo_stroke(cr); + cairo_restore(cr); + } + iters++; + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //assert(x.rad() > 0); + //assert(y.rad() > 0); + AAF f = (*eval)(x, y); + double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if(ivl.extent() < 0.5*L2(n)) { + + cairo_save(cr); + cairo_set_source_rgba(cr, 0,1,0,0.125); + fill_line_in_rect(cr, r, n, ivl.middle()); + cairo_fill(cr); + cairo_restore(cr); + draw_line_in_rect(cr, r, n, ivl.middle()); + return; + } + if(f.strictly_neg()) { + cairo_save(cr); + cairo_set_source_rgba(cr, 0,1,0,0.125); + cairo_rectangle(cr, r); + cairo_fill(cr); + cairo_restore(cr); + return; + } + if(!f.is_partial() and f.is_indeterminate()) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + if(f.is_infinite()) { + cairo_set_source_rgb(cr, 1, 0.5, 0.5); + } else if(f.is_nan()) { + cairo_set_source_rgb(cr, 1, 1, 0); + } else { + cairo_set_source_rgb(cr, 1, 0, 0); + } + cairo_rectangle(cr, r); + if(show_splits) { + cairo_stroke(cr); + } else { + cairo_fill(cr); + } + cairo_restore(cr); + return; + } + + if((r.width() > w) or (r.height()>w)) { + if(f.strictly_neg() or f.straddles_zero()) { + // Three possibilities: + // 1) the trim operation buys us enough that we should just iterate + Point c = r.midpoint(); + Rect oldr = r; + if(1 && out) { + r = *out; + for(int i = 0; i < 4; i++) { + Point p = oldr.corner(i); + if(dot(n,p) < ivl.middle()) { + r.expandTo(p); + } + } + } + if(1 && out && (r.area() < oldr.area()*0.25)) { + splits[0] ++; + recursive_implicit(r, cr, w); + // 2) one dimension is significantly smaller + } else if(1 && (r[1].extent() < oldr[1].extent()*0.5)) { + splits[1]++; + recursive_implicit(Rect(Interval(r.left(), r.right()), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(r.left(), r.right()), + Interval(c[1], r.bottom())), cr,w); + } else if(1 && (r[0].extent() < oldr[0].extent()*0.5)) { + splits[2]++; + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(r.top(), r.bottom())), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(r.top(), r.bottom())), cr,w); + // 3) to ensure progress we must do a four way split + } else { + splits[3]++; + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(c[1], r.bottom())), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(c[1], r.bottom())), cr,w); + } + } + } else { + } + } + + void key_hit(GdkEventKey *e) override { + if(e->keyval == 'w') toggles[0].toggle(); else + if(e->keyval == 'a') toggles[1].toggle(); else + if(e->keyval == 'q') toggles[2].toggle(); else + if(e->keyval == 's') toggles[3].toggle(); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + origin = orig_handle.pos; + if(1) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgb(cr, 0.5, 0.5, 1); + eval = xaxis; + //recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + eval = yaxis; + //recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + cairo_restore(cr); + iters = 0; + for(int & split : splits) + split = 0; + show_splits = toggles[0].on; + eval = trial_eval; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + for(int split : splits) + *notify << split << " + "; + *notify << " = " << iters; + } + if(1) { + Rect r(test_window.pts[0], test_window.pts[1]); + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //AAF f = md_sample_based(x, samples.pts)-y; + if(0) { + x = x-500; + y = y-300; + x = x/200; + y = y/200; + AAF f = atan_sample_based(x)-y; + cout << f << endl; + } + AAF f = (*eval)(x, y); + double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + //cout << d << endl; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if(out) + cairo_rectangle(cr, *out); + cairo_rectangle(cr, r); + draw_line_in_rect(cr, r, n, ivl.min()); + if(f.strictly_neg()) { + cairo_save(cr); + cairo_set_source_rgba(cr, .5, 0.5, 0, 0.125); + cairo_fill(cr); + cairo_restore(cr); + } else + cairo_stroke(cr); + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgb(cr, 0.5, 0.5, 0); + draw_line_in_rect(cr, r, n, ivl.middle()); + cairo_stroke(cr); + cairo_restore(cr); + //fill_line_in_rect(cr, r, n, c); + draw_line_in_rect(cr, r, n, ivl.max()); + cairo_stroke(cr); + } + if(0) { + Geom::ConvexHull gm(samples.pts); + cairo_convex_hull(cr, gm); + cairo_stroke(cr); + Point a, b, c; + gm.narrowest_diameter(a, b, c); + cairo_save(cr); + cairo_set_line_width(cr, 2); + cairo_set_source_rgba(cr, 1, 0, 0, 0.5); + cairo_move_to(cr, b); + cairo_line_to(cr, c); + cairo_move_to(cr, a); + cairo_line_to(cr, (c-b)*dot(a-b, c-b)/dot(c-b,c-b)+b); + cairo_stroke(cr); + //std::cout << a << ", " << b << ", " << c << ": " << dia << "\n"; + cairo_restore(cr); + } + Toy::draw(cr, notify, width, height, save,timer_stream); + Point d(25,25); + toggles[0].bounds = Rect(Point(10, height-80)+d, + Point(10+120, height-80+d[1])+d); + + draw_toggles(cr, toggles); + } + +}; + +int main(int argc, char **argv) { + init(argc, argv, new ConvexTest()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/inner-product-clip.cpp b/src/toys/inner-product-clip.cpp new file mode 100644 index 0000000..9ada56b --- /dev/null +++ b/src/toys/inner-product-clip.cpp @@ -0,0 +1,174 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> + +#include <gsl/gsl_matrix.h> + +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = p[i]; + cairo_d2_sb(cr, B); + } +} + +void draw_line(cairo_t* cr, Geom::Point n, double d) { + cairo_move_to(cr, d*n + rot90(n)*1000); + cairo_line_to(cr, d*n - rot90(n)*1000); + cairo_move_to(cr, d*n); + cairo_line_to(cr, (d+10)*n); +} + +class InnerProductClip: public Toy { + Path path_a; + Piecewise<D2<SBasis> > path_a_pw; + std::vector<Toggle> togs; + PointHandle start_handle, end_handle; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw); + + Point n; + double d; + { + double x = width - 60, y = height - 60; + Point p(x, y), dpoint(25,25), xo(25,0), yo(0,25); + togs[0].bounds = Rect(p, p + dpoint); + togs[1].bounds = Rect(p + xo, p + xo + dpoint); + draw_toggles(cr, togs); + } + if(togs[0].on) { + d = L2(end_handle.pos - start_handle.pos); + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_new_sub_path(cr); + cairo_arc(cr, start_handle.pos[0], start_handle.pos[1], d, 0, M_PI*2); + cairo_stroke(cr); + cairo_restore(cr); + } else { + n = unit_vector(rot90(end_handle.pos - start_handle.pos)); + d = dot(n, start_handle.pos); + draw_line(cr, n, d); + } + //printf("%g\n", d); + + vector<double> all_roots; + for(unsigned i = 0; i <= path_a.size(); i++) { + //deriv = p[i].derivative(); + D2<SBasis> curpw = path_a[i].toSBasis(); + SBasis inner; + if(togs[0].on) { + D2<SBasis> test = curpw - start_handle.pos; + inner = test[0]*test[0] + test[0]*test[1] + 2*test[1]*test[1] - d*d; + } else { + inner = n[0]*curpw[0] + n[1]*curpw[1] - d; + } + vector<double> lr = roots(inner); + all_roots.insert(all_roots.end(), lr.begin(), lr.end()); + for(double i : lr) + draw_handle(cr, curpw(i)); + sort(lr.begin(), lr.end()); + lr.insert(lr.begin(), 0); + lr.insert(lr.end(), 1); + Path out; + for(unsigned j = 0; j < lr.size()-1; j++) { + //Point s = curpw(lr[j]); + Point m = curpw((lr[j] + lr[j+1])/2); + if(togs[0].on) + m -= start_handle.pos; + //Point e = curpw(lr[j+1]); + double dd; + if(togs[0].on) + //dd = dot(m, m) - d*d; + dd = m[0]*m[0] + m[0]*m[1] + 2*m[1]*m[1] - d*d; + else + dd = dot(n, m) - d; + if(togs[1].on) + dd = -dd; + //printf("%d [%g, %g] %g (%g, %g) (%g, %g)\n", + // i, lr[j], lr[j+1], dd, s[0], s[1], e[0], e[1]); + if(0 > dd) { + //Curve * cv = path_a[i].portion(lr[j], lr[j+1]); + cairo_d2_sb(cr, portion(curpw, lr[j], lr[j+1])); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + /*cairo_curve(cr, path_a[i]); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr);*/ + } + + } + } + + //cairo_pw_d2_sb(cr, path_a_pw); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void key_hit(GdkEventKey *e) override { + if(e->keyval == 's') togs[1].toggle(); else + if(e->keyval == 'c') togs[0].toggle(); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(togs, e); + Toy::mouse_pressed(e); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + PathVector paths_a = read_svgd(path_a_name); + assert(paths_a.size() > 0); + path_a = paths_a[0]; + + path_a.close(true); + path_a_pw = path_a.toPwSb(); + + // Finite images of the three vanishing points and the origin + handles.push_back(&start_handle); + handles.push_back(&end_handle); + togs.emplace_back("C", true); + togs.emplace_back("S", true); + } +public: + InnerProductClip() : start_handle(150,300), + end_handle(380,40) {} +}; + +int main(int argc, char **argv) { + init(argc, argv, new InnerProductClip); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/intersect-data.cpp b/src/toys/intersect-data.cpp new file mode 100644 index 0000000..d262684 --- /dev/null +++ b/src/toys/intersect-data.cpp @@ -0,0 +1,436 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> + +#include <cstdlib> +#include <cstdio> +#include <set> +#include <vector> +#include <algorithm> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> + +#include "topology.cpp" + + +static double exp_rescale(double x){ return pow(10, x);} +std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));} + + +class IntersectDataTester: public Toy { + unsigned nb_paths; + unsigned nb_curves_per_path; + unsigned degree; + double tol; + + PathVector cmd_line_paths; + + std::vector<PointSetHandle> paths_handles; + std::vector<Slider> sliders; + int nb_steps; + + Topology topo; + + //TODO conversions to path should be owned by the relevant classes. + Path edgeToPath(Topology::OrientedEdge o_edge){ + Topology::Edge e = topo.edges[o_edge.edge]; + D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + p = portion(p, dom); + if ( o_edge.reversed ){ + p = compose( p, Linear(1.,0.) ); + } + Path ret; + ret.setStitching(true); + Point center; + unsigned c_idx = topo.source(o_edge, true); + if ( c_idx == NULL_IDX ){ + ret.append(p); + }else{ + center = topo.vertices[c_idx].bounds.midpoint(); + ret = Path(center); + ret.append(p); + } + c_idx = topo.target(o_edge, true); + if ( c_idx == NULL_IDX ){ + return ret; + }else{ + center = topo.vertices[c_idx].bounds.midpoint(); + if ( center != p.at1() ) ret.appendNew<LineSegment>(center); + return ret; + } + } + + Path boundaryToPath(Topology::Boundary b){ + Point pt; + Path bndary; + bndary.setStitching(true); + + if (b.size()==0){ return Path(); } + + Topology::OrientedEdge o_edge = b.front(); + unsigned first_v = topo.source(o_edge, true); + if ( first_v != NULL_IDX ){ + pt = topo.vertices[first_v].bounds.midpoint(); + bndary = Path(pt); + } + + for (unsigned i = 0; i < b.size(); i++){ + bndary.append( edgeToPath(b[i])); + } + bndary.close(); + return bndary; + } + + //TODO:this should return a path vector, but we glue the components for easy drawing in the toy. + Path areaToPath(unsigned a){ + Path bndary; + bndary.setStitching(true); + if ( topo.areas[a].boundary.size()==0 ){//this is the unbounded component... + OptRect bbox = bounds_fast( topo.input_paths ); + if (!bbox ){return Path();}//??? + bbox->expandBy(50); + bndary = Path(bbox->corner(0)); + bndary.appendNew<LineSegment>(bbox->corner(1)); + bndary.appendNew<LineSegment>(bbox->corner(2)); + bndary.appendNew<LineSegment>(bbox->corner(3)); + bndary.appendNew<LineSegment>(bbox->corner(0)); + }else{ + bndary = boundaryToPath(topo.areas[a].boundary); + } + for (auto & inner_boundarie : topo.areas[a].inner_boundaries){ + bndary.append( boundaryToPath(inner_boundarie)); + bndary.appendNew<LineSegment>( bndary.initialPoint() ); + } + bndary.close(); + return bndary; + } + void drawAreas( cairo_t *cr, Topology const &topo, bool fill=true ){ + //don't draw the first one... + for (unsigned a=0; a<topo.areas.size(); a++){ + drawArea(cr, topo, a, fill); + } + } + void drawArea( cairo_t *cr, Topology const &topo, unsigned a, bool fill=true ){ + if (a>=topo.areas.size()) return; + Path bndary = areaToPath(a); + cairo_path(cr, bndary); + double r,g,b; + + int winding = 0; + for (int k : topo.areas[a].windings){ + winding += k; + } + + //convertHSVtoRGB(0, 1., .5 + winding/10, r,g,b); + //convertHSVtoRGB(360*a/topo.areas.size(), 1., .5, r,g,b); + convertHSVtoRGB(180+30*winding, 1., .5, r,g,b); + cairo_set_source_rgba (cr, r, g, b, 1); + //cairo_set_source_rgba (cr, 1., 0., 1., .3); + + if (fill){ + cairo_fill(cr); + }else{ + cairo_set_line_width (cr, 5); + cairo_stroke(cr); + } + } + + void highlightRay( cairo_t *cr, Topology &topo, unsigned b, unsigned r ){ + if (b>=topo.vertices.size()) return; + if (r>=topo.vertices[b].boundary.size()) return; + Rect box = topo.vertices[b].bounds; + //box.expandBy(2); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, 1.0); + cairo_set_line_width (cr, 1); + cairo_fill(cr); + unsigned eidx = topo.vertices[b].boundary[r].edge; + Topology::Edge e = topo.edges[eidx]; + D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + if (topo.vertices[b].boundary[r].reversed){ + //dom[0] += e.portion.extent()*2./3; + cairo_set_source_rgba (cr, 0., 1., 0., 1.0); + }else{ + //dom[1] -= e.portion.extent()*2./3; + cairo_set_source_rgba (cr, 0., 0., 1., 1.0); + } + p = portion(p, dom); + cairo_d2_sb(cr, p); + cairo_set_source_rgba (cr, 1., 0., 0, 1.0); + cairo_set_line_width (cr, 5); + cairo_stroke(cr); + } + + void drawEdge( cairo_t *cr, Topology const &topo, unsigned eidx ){ + if (eidx>=topo.edges.size()) return; + Topology::Edge e = topo.edges[eidx]; + D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + p = portion(p, dom); + cairo_d2_sb(cr, p); + if (e.start == NULL_IDX || e.end == NULL_IDX ) + cairo_set_source_rgba (cr, 0., 1., 0, 1.0); + else + cairo_set_source_rgba (cr, 0., 0., 0, 1.0); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + } + void drawEdges( cairo_t *cr, Topology const &topo ){ + for (unsigned e=0; e<topo.edges.size(); e++){ + drawEdge(cr, topo, e); + } + } + void drawKnownEdges( cairo_t *cr, Topology const &topo ){ + for (unsigned v=0; v<topo.vertices.size(); v++){ + for (unsigned e=0; e<topo.vertices[v].boundary.size(); e++){ + drawEdge(cr, topo, topo.vertices[v].boundary[e].edge); + } + } + } + + + void drawBox( cairo_t *cr, Topology const &topo, unsigned b ){ + if (b>=topo.vertices.size()) return; + Rect box = topo.vertices[b].bounds; + //box.expandBy(5); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, .5); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, .2); + cairo_fill(cr); + +// //std::cout<<"\nintersection boundary:\n"; +// for (unsigned i = 0; i < topo.vertices[b].boundary.size(); i++){ +// unsigned eidx = topo.vertices[b].boundary[i].edge; +// Topology::Edge e = topo.edges[eidx]; +// D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis(); +// Interval dom = e.portion; +// if (topo.vertices[b].boundary[i].reversed){ +// dom[0] += e.portion.extent()*2./3; +// cairo_set_source_rgba (cr, 0., 1., .5, 1); +// }else{ +// dom[1] -= e.portion.extent()*2./3; +// cairo_set_source_rgba (cr, 0., .5, 1., 1); +// } +// p = portion(p, dom); +// cairo_d2_sb(cr, p); +// cairo_set_line_width (cr, 2); +// cairo_stroke(cr); +// } + } + void drawBoxes( cairo_t *cr, Topology const &topo ){ + for (unsigned b=0; b<topo.vertices.size(); b++){ + drawBox(cr, topo, b); + } + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + *notify<<"line command args: svgd file or (nb paths, nb curves/path, degree of curves).\n"; + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + PathVector paths; + if (!cmd_line_paths.empty()){ + paths = cmd_line_paths; + for (unsigned i = 0; i < paths.size(); i++){ + paths[i] *= Translate( paths_handles[i].pts[0] - paths[i].initialPoint() ); + } + }else{ + for (unsigned i = 0; i < nb_paths; i++){ + paths_handles[i].pts.back()=paths_handles[i].pts.front(); + paths.push_back(Path(paths_handles[i].pts[0])); + for (unsigned j = 0; j+degree < paths_handles[i].size(); j+=degree){ + D2<SBasis> c = handles_to_sbasis(paths_handles[i].pts.begin()+j, degree); + if ( j + degree == paths_handles[i].size()-1 ){ + c[X].at(0)[1] = paths_handles[i].pts.front()[X]; + c[Y].at(0)[1] = paths_handles[i].pts.front()[Y]; + } + paths[i].append(c); + } + paths[i].close(); + } + } + *notify<<"Use '<' and '>' keys to move backward/forward in the sweep: (currently doing "<<nb_steps<<" steps)\n"; + *notify<<"nb_steps: "<<nb_steps<<"\n"; + + +#if 0 + cairo_path(cr, paths); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); +#endif + + tol = exp_rescale( sliders[3].value() ); + topo = Topology(paths, cr, tol, nb_steps ); + +#if 1 + unsigned v = (unsigned)(sliders[0].value()*(double(topo.vertices.size()))); + unsigned r = (unsigned)(sliders[1].value()*(double(topo.vertices[v].boundary.size()))); + unsigned a = (unsigned)(sliders[2].value()*(double(topo.areas.size()))); + if( v == topo.vertices.size() ) v--; + if( r == topo.vertices[v].boundary.size()) r--; + if( a == topo.areas.size()) a--; + drawAreas(cr, topo); + drawKnownEdges(cr, topo); + //drawArea(cr, topo, a, false); + //highlightRay(cr, topo, v, r ); + //*notify<<"highlighted edge: "<< topo.vertices[v].boundary[r].edge<<"\n"; + + //drawBox(cr,topo, unsigned(sliders[0].value())); + drawBoxes(cr,topo); +#endif + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + + void initSliders(){ + sliders.emplace_back(0.0, 1, 0, 0.0, "intersection chooser"); + sliders.emplace_back(0.0, 1, 0, 0.0, "ray chooser"); + sliders.emplace_back(0.0, 1, 0, 0.0, "area chooser"); + sliders.emplace_back(-5.0, 2, 0, 0.0, "tolerance chooser"); + + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + handles.push_back(&(sliders[3])); + + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + sliders[3].geometry(Point(50, 110), 250); + sliders[3].formatter( &exp_formatter ); + + } + + public: + IntersectDataTester(PathVector input_paths){ + cmd_line_paths = input_paths; + //nb_paths=0; nb_curves_per_path = 0; degree = 0;//meaningless + paths_handles = std::vector<PointSetHandle>( cmd_line_paths.size(), PointSetHandle() ); + for(unsigned i = 0; i < cmd_line_paths.size(); i++){ + //TODO: use path iterators to deal with closed/open paths!!! + //cmd_line_paths[i].close(); + if ( cmd_line_paths[i].closed() ){ + cmd_line_paths[i].appendNew<LineSegment>(cmd_line_paths[i].initialPoint() ); + } + Point p = cmd_line_paths[i].initialPoint(); + paths_handles.emplace_back(); + paths_handles[i].push_back(p); + handles.push_back( &paths_handles[i] ); + } + initSliders(); + } + + IntersectDataTester(unsigned paths, unsigned curves_in_path, unsigned degree) : + nb_paths(paths), nb_curves_per_path(curves_in_path), degree(degree) { + + paths_handles = std::vector<PointSetHandle>( nb_paths, PointSetHandle() ); + for(unsigned i = 0; i < nb_paths; i++){ + for(unsigned j = 0; j < (nb_curves_per_path*degree)+1; j++){ + paths_handles[i].push_back(uniform()*400, 100+ uniform()*300); + } + handles.push_back(&paths_handles[i]); + } + initSliders(); + } + + IntersectDataTester(){ + nb_paths=3; nb_curves_per_path = 5; degree = 1; + + paths_handles.emplace_back(); + paths_handles[0].push_back(100,100); + paths_handles[0].push_back(100,200); + paths_handles[0].push_back(300,200); + paths_handles[0].push_back(300,100); + paths_handles[0].push_back(100,100); + + paths_handles.emplace_back(); + paths_handles[1].push_back(120,190); + paths_handles[1].push_back(200,210); + paths_handles[1].push_back(280,190); + paths_handles[1].push_back(200,300); + paths_handles[1].push_back(120,190); + + paths_handles.emplace_back(); + paths_handles[2].push_back(180,150); + paths_handles[2].push_back(200,140); + paths_handles[2].push_back(220,150); + paths_handles[2].push_back(300,160); + paths_handles[2].push_back(180,150); + + handles.push_back(&paths_handles[0]); + handles.push_back(&paths_handles[1]); + handles.push_back(&paths_handles[2]); + + initSliders(); + } + + + void first_time(int /*argc*/, char** /*argv*/) override { + nb_steps = -1; + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case '>': + nb_steps++; + break; + case '<': + if ( nb_steps > -1 ) nb_steps--; + break; + } + redraw(); + } + +}; + +int main(int argc, char **argv) { + if(argc == 2){ + const char *path_name = argv[1]; + PathVector cmd_line_paths = read_svgd(path_name); //* Scale(3); + OptRect bounds = bounds_exact(cmd_line_paths); + if (bounds) { + cmd_line_paths *= Translate(Point(10,10) - bounds->min()); + } + init(argc, argv, new IntersectDataTester(cmd_line_paths)); + }else{ + unsigned nb_paths=3, nb_curves_per_path = 5, degree = 1; + if(argc > 3) + sscanf(argv[3], "%d", °ree); + if(argc > 2) + sscanf(argv[2], "%d", &nb_curves_per_path); + if(argc > 1){ + sscanf(argv[1], "%d", &nb_paths); + init(argc, argv, new IntersectDataTester( nb_paths, nb_curves_per_path, degree ) ); + }else{ + init(argc, argv, new IntersectDataTester()); + } + } + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/inverse-test.cpp b/src/toys/inverse-test.cpp new file mode 100644 index 0000000..c339419 --- /dev/null +++ b/src/toys/inverse-test.cpp @@ -0,0 +1,174 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +using std::vector; +using namespace Geom; +using namespace std; + +static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){ + D2<SBasis> plot; + plot[0]=SBasis(Linear(150+a*300,150+b*300)); + plot[1]=B*-vscale; + plot[1]+=450; + cairo_d2_sb(cr, plot); + cairo_stroke(cr); +} +static void plot(cairo_t* cr, Piecewise<SBasis> const &f,double vscale=1){ + for (unsigned i=0;i<f.size();i++){ + plot(cr,f.segs[i],vscale,f.cuts[i],f.cuts[i+1]); + draw_cross(cr,Geom::Point(f.cuts[i]*300 + 150, f.segs[i][0][0]*(-vscale) + 450)); + } +} + +static SBasis my_inverse(SBasis f, int order){ + double a0 = f[0][0]; + if(a0 != 0) { + f -= a0; + } + double a1 = f[0][1]; + if(a1 == 0) + THROW_NOTINVERTIBLE(); + //assert(a1 != 0);// not invertible. + if(a1 != 1) { + f /= a1; + } + + SBasis g=SBasis(order, Linear()); + g[0] = Linear(0,1); + double df0=derivative(f)(0); + double df1=derivative(f)(1); + SBasis r = Linear(0,1)-g(f); + + for(int i=1; i<order; i++){ + //std::cout<<"i: "<<i<<std::endl; + r=Linear(0,1)-g(f); + //std::cout<<"t-gof="<<r<<std::endl; + r.normalize(); + if (r.size()==0) return(g); + double a=r[i][0]/std::pow(df0,i); + double b=r[i][1]/std::pow(df1,i); + g[i] = Linear(a,b); + } + + return(g); +} + +static Piecewise<SBasis> pw_inverse(SBasis const &f, int order,double tol=.1,int depth=0){ + SBasis g=SBasis(Linear(0,1)),r; + Piecewise<SBasis> res,res1,res2; + + //std::cout<<"depth: "<<depth<<std::endl; + g=my_inverse(f,order); + r=g(f); + //std::cout<<"error: "<<g.tail_error(1)<<std::endl; + if (g.tailError(1)<tol){ + res.segs.push_back(g); + res.cuts.push_back(f[0][0]); + res.cuts.push_back(f[0][1]); + }else if (depth<200){ + SBasis ff; + ff=f(Linear(0,.5)); + res=pw_inverse(ff,order,tol,depth+1); + for (unsigned i=0;i<res.size();i++){ + res.segs[i]*=.5; + } + ff=f(Linear(.5,1)); + res1=pw_inverse(ff,order,tol,depth+1); + for (unsigned i=0;i<res1.size();i++){ + res1.segs[i]*=.5; + res1.segs[i]+=.5; + } + res.concat(res1); + } + return(res); +} + + + +class InverseTester: public Toy { + int size; + PointSetHandle hand; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + for (int i=0;i<size;i++){ + hand.pts[i ][0]=150+15*(i-size); + hand.pts[i+size][0]=450+15*(i+1); + cairo_move_to(cr, Geom::Point(hand.pts[i ][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i ][0],450)); + cairo_move_to(cr, Geom::Point(hand.pts[i+size][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i+size][0],450)); + } + cairo_move_to(cr, Geom::Point(0,300)); + cairo_line_to(cr, Geom::Point(600,300)); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + + SBasis f(size, Linear());//=SBasis(Linear(0,.5)); + for (int i=0;i<size;i++){ + f[i] = Linear(-(hand.pts[i ][1]-300)*std::pow(4.,i)/150, + -(hand.pts[i+size][1]-300)*std::pow(4.,i)/150 ); + } + plot(cr,Linear(0,1),300); + + f.normalize(); + cairo_set_line_width (cr, 2); + cairo_set_source_rgba (cr, 0., 0., 0.8, 1); + plot(cr,f,300); + + *notify<<"Use hand.pts to set the coefficients of the (blue) s-basis."<<std::endl; + *notify<<" (keep it monotonic!)"<<std::endl; + *notify<<"red=flipped inverse; should be the same as the blue one."<<std::endl; + + try { + Piecewise<SBasis> g=pw_inverse(f,3); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.8, 0., 0., 1); + plot(cr,g,300); + Piecewise<SBasis> h=compose(g,f); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.8, 0., 1); + plot(cr,h,300); + *notify<<g.size()<<" segments."; + } catch(NotInvertible) { + *notify << "function not invertible!" << std::endl; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + InverseTester() { + size=4; + if(hand.pts.empty()) { + for(int i = 0; i < 2*size; i++) + hand.pts.emplace_back(0,150+150+uniform()*300*0); + } + hand.pts[0][1]=300; + hand.pts[size][1]=150; + handles.push_back(&hand); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new InverseTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/kinematic_templates.cpp b/src/toys/kinematic_templates.cpp new file mode 100644 index 0000000..0089a1a --- /dev/null +++ b/src/toys/kinematic_templates.cpp @@ -0,0 +1,365 @@ +#include <toys/toy-framework-2.h> + +/* + * Copyright cilix42 + * Kinematic template toy. The aim is to manipulate the cursor movement + * so that it stays closer to a given shape (e.g., a circle or a line). + * For details, see http://hci.uwaterloo.ca/Publications/Papers/uist222-fung.pdf + * + * Each kinematic template has a radius of action outside of which it + * has no effect (this is indicated by a red circle). + */ + +#include <vector> +#include <2geom/point.h> +#include <2geom/transforms.h> + +using std::vector; +using namespace Geom; +using namespace std; + +// I feel a little uneasy using a Point for polar coords. +Point cartesian_to_polar(Point const &pt, Point const ¢er = Point(0,0)) { + Point rvec = pt - center; + // use atan2 unless you want to measure between two vectors + return Point(L2(rvec), atan2(rvec)); +} + +Point polar_to_cartesian(Point const &pt, Point const ¢er = Point(0,0)) { + return center + Point(pt[0],0) * Rotate(pt[1]); +} + +class KinematicTemplate { +public: + KinematicTemplate(double const sx = 0.0, double const sy = 0.0, double const cx = 0.0, double const cy = 0.0); + ~KinematicTemplate(); + + /* + * To facilitate the creation of templates, we can use different coordinates at each point + * (e.g., radial coordinates around a fixed center) + */ + virtual std::pair<Point, Point> local_coordinate_system(Point const &/*at*/) { + // Return standard cartesian coordinates + return std::make_pair(Point(1,0), Point(0,1)); + } + + virtual Point next_point(Point const &at, Point const &delta);// { return at; } + virtual void draw_visual_cue(cairo_t *cr); + + Point const get_center() { return center; } + void set_center(Point const &pos) { center = pos; } + + double get_radius_of_action() { return radius; } + void set_radius_of_action(double const r) { radius = r; } + void enlarge_radius_of_action(double const by) { + if (radius > -by) + radius += by; + else + radius = 0; + } + + +protected: + double sx, sy, cx, cy; + Point center; + double radius; +}; + +KinematicTemplate::KinematicTemplate(double const sx, double const sy, double const cx, double const cy) + : sx(sx), + sy(sy), + cx(cx), + cy(cy), + center(300,300), + radius(100) +{ +} + +KinematicTemplate::~KinematicTemplate() +{ +} + +Point +KinematicTemplate::next_point(Point const &last_pt, Point const &delta) +{ +// Point new_pt = last_pushed + kinematic_delta(last_pushed, delta, 0); + + /* Compute the "relative" coordinates w.r.t. the "local coordinate system" at the current point */ + Point v = local_coordinate_system(last_pt).first; + Point w = local_coordinate_system(last_pt).second; + double dotv = dot(v, delta); + double dotw = dot(w, delta); + + Point new_delta; + if (L2(last_pt + delta - center) < radius) { + /* + * We are within the radius of action of the kinematic template. + * Compute displacement w.r.t. the v/w-coordinate system. + */ + new_delta = (dotv*sx + cx)*v + (dotw*sy + cy)*w; + } else { + new_delta = delta; + } + + return last_pt + new_delta; +} + +void +KinematicTemplate::draw_visual_cue(cairo_t *cr) { + cairo_set_source_rgba (cr, 1, 0, 0, 1); + cairo_set_line_width (cr, 0.5); + cairo_new_sub_path(cr); + cairo_arc(cr, center[X], center[Y], radius, 0, M_PI*2); + cairo_stroke(cr); +} + +class RadialKinematicTemplate : public KinematicTemplate { +public: + RadialKinematicTemplate(Point const ¢er, double const sx, double const sy, double const cx, double const cy); + + std::pair<Point, Point> local_coordinate_system(Point const &at) override { + /* Return 'radial' coordinates around polar_center */ + Point v = unit_vector(at - center); + return std::make_pair(v, rot90(v)); + } + +private: + Point radial_center; +}; + +RadialKinematicTemplate::RadialKinematicTemplate(Point const ¢er, double const sx, double const sy, + double const cx = 0.0, double const cy = 0.0) + : KinematicTemplate(sx, sy, cx, cy) +{ + radial_center = center; +} + +class GridKinematicTemplate : public KinematicTemplate { +public: + GridKinematicTemplate(double const sx = 0.0, double const sy = 0.0, double const cx = 0.0, double const cy = 0.0) + : KinematicTemplate(sx, sy, cx, cy) {}; + Point next_point(Point const &at, Point const &delta) override;// { return at; } +}; + +Point +GridKinematicTemplate::next_point(Point const &at, Point const &delta) { + if (L2(at + delta - center) < radius) { + // we are within the radius of action + Point new_delta = delta; + + if (fabs(delta[0]) > fabs(delta[1])) + new_delta[1] *= sy; + else + new_delta[0] *= sx; + + return at + new_delta; + } else { + return at + delta; + } +} + + +// My idea was to compute the gradient of an arbitrary potential function as the transform. Probably the right way to do this is to use the hessian as the integrand -- njh +class ImplicitKinematicTemplate : public KinematicTemplate { +public: + ImplicitKinematicTemplate() {} + + Point next_point(Point const &at, Point const &delta) override { + if (L2(at + delta - center) < radius) { + // we are within the radius of action + + // the 0.7dx+1 includes a weakened version of the constraining force + // I can't help but think this is really a form of differential constraint solver, let's discuss. + return at + delta*Scale(0.7*sin(at[0]/10.0)+1, 0.7*cos(at[1]/10.0)+1); + } else { + return at + delta; + } + } +}; + + + +vector<KinematicTemplate*> kin; +KinematicTemplate *cur_kin; +std::string cur_choice = "A"; + +class KinematicTemplatesToy : public Toy { + + enum menu_item_t + { + KT_HORIZONTAL = 0, + KT_VERTICAL, + KT_GRID, + KT_CIRCLE, + KT_RADIAL, + KT_CONVEYOR, + KT_IMP, + TOTAL_ITEMS // this one must be the last item + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + Point cur, last_pushed; + vector<vector<Point>*> pts; + + bool dragging_center; // to prevent drawing while dragging the center + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/ ) + { + *notify << std::endl; + for (int i = KT_HORIZONTAL; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + *notify << "+/- - enlarge/shrink radius of action" << endl << endl << endl << endl; + *notify << "Current choice: " << cur_choice << endl; + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_set_line_width (cr, 1); + + //draw_handle(cr, cur_kin->get_center()); + draw_menu(cr, notify, width, height, save, timer_stream); + + // draw all points accumulated so far + for (auto & pt : pts) { + if (pt->size() > 0) { + cairo_move_to(cr, (*pt)[0]); + } + for (auto & j : *pt) { + //cout << " --> drawing line to point #" << j << endl; + cairo_line_to(cr, j); + } + } + + cairo_stroke(cr); + + cur_kin->draw_visual_cue(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void first_time(int /*argc*/, char** /*argv*/) override { + p1.pos = Point(200, 200); + handles.push_back(&p1); + + pts.clear(); + kin.push_back(new KinematicTemplate(1.0, 0.1)); // horizontal lines + kin.push_back(new KinematicTemplate(0.1, 1.0)); // horizontal lines + kin.push_back(new GridKinematicTemplate(0.1, 0.1)); + kin.push_back(new RadialKinematicTemplate(p1.pos, 0.1, 1.0)); + kin.push_back(new RadialKinematicTemplate(p1.pos, 1.0, 0.1)); + kin.push_back(new KinematicTemplate(1.0, 0.1, 1, 0)); // horiz conveyor + kin.push_back(new ImplicitKinematicTemplate()); + cur_kin = kin[0]; + cur_kin->set_center(p1.pos); + + dragging_center = false; + } + + void mouse_pressed(GdkEventButton *e) override { + Point at(e->x, e->y); + + if(L2(at - p1.pos) < 5) { + dragging_center = true; + } else { + if(e->button == 1) { + vector<Point> *vec = new vector<Point>; + vec->clear(); + vec->push_back(at); + last_pushed = at; + pts.push_back(vec); + } + } + + Toy::mouse_pressed(e); + } + + void mouse_released(GdkEventButton */*e*/) override { + dragging_center = false; + } + + void mouse_moved(GdkEventMotion* e) override { + if (!dragging_center) { + Point at(e->x, e->y); + + Point delta = at - cur; + //cout << "Mouse moved to: " << at << " (difference: " << delta << ")" << endl; + if(e->state & GDK_BUTTON1_MASK) { + Point new_pt = cur_kin->next_point(last_pushed, delta); + + pts.back()->push_back(new_pt); + last_pushed = new_pt; + } + cur = at; + } else { + cur_kin->set_center(p1.pos); + } + + Toy::mouse_moved(e); + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + // No need to copy and paste code + if(choice >= 'A' and choice < 'A' + TOTAL_ITEMS) { + cur_kin = kin[choice - 'A']; + cur_choice = choice; + + } else + switch (choice) + { + case '+': + cur_kin->enlarge_radius_of_action(5); + break; + case '-': + cur_kin->enlarge_radius_of_action(-5); + break; + default: + break; + } + p1.pos = cur_kin->get_center(); + redraw(); + } + +private: + PointHandle p1; +}; + +const char* KinematicTemplatesToy::menu_items[] = +{ + "horizontal", + "vertical", + "grid", + "circular", + "radial", + "conveyor", + "implicit" +}; + +const char KinematicTemplatesToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G' +}; + +int main(int argc, char **argv) { + init(argc, argv, new KinematicTemplatesToy); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/levelsets-test.cpp b/src/toys/levelsets-test.cpp new file mode 100644 index 0000000..cd5874e --- /dev/null +++ b/src/toys/levelsets-test.cpp @@ -0,0 +1,155 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace std; +using namespace Geom; + +static double exp_rescale(double x){ return std::pow(10, x);} +std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));} + + +static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){ + D2<SBasis> plot; + plot[0]=SBasis(Linear(150+a*300,150+b*300)); + plot[1]=B*(-vscale); + plot[1]+=300; + cairo_d2_sb(cr, plot); + cairo_stroke(cr); +} +static void plot_bar(cairo_t* cr, double height, double vscale=1,double a=0,double b=1){ + cairo_move_to(cr, Geom::Point(150+300*a,-height*vscale+300)); + cairo_line_to(cr, Geom::Point(150+300*b,-height*vscale+300)); +} +static void plot_bar(cairo_t* cr, Interval height, double vscale=1,double a=0,double b=1){ + Point A(150+300*a,-height.max()*vscale+300); + Point B(150+300*b,-height.min()*vscale+300); + cairo_rectangle(cr, Rect(A,B) ); +} + +class BoundsTester: public Toy { + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + for (unsigned i=0;i<size;i++){ + hand.pts[i ][0]=150+15*(i-size); + hand.pts[i+size][0]=450+15*(i+1); + cairo_move_to(cr, Geom::Point(hand.pts[i ][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i ][0],450)); + cairo_move_to(cr, Geom::Point(hand.pts[i+size][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i+size][0],450)); + } + cairo_move_to(cr, Geom::Point(0,300)); + cairo_line_to(cr, Geom::Point(600,300)); + + cairo_set_line_width (cr, .3); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + + SBasis B(size, Linear()); + for (unsigned i=0;i<size;i++){ + B[i] = Linear(-(hand.pts[i ][1]-300)*std::pow(4.,(int)i), + -(hand.pts[i+size][1]-300)*std::pow(4.,(int)i) ); + } + B.normalize(); + plot(cr,B,1); + cairo_set_source_rgba (cr, 0., 0., 0.8, 1); + cairo_stroke(cr); + + double vtol = exp_rescale(slider.value()); + if (vtol<1e-4) vtol=0; + + hand.pts[2*size ][X]=150; + hand.pts[2*size+1][X]=150; + hand.pts[2*size+2][X]=150; + hand.pts[2*size+1][Y]=std::max(hand.pts[2*size+1][Y],hand.pts[2*size+2][Y]+2*vtol); + hand.pts[2*size ][Y]=std::max(hand.pts[2*size ][Y],hand.pts[2*size+1][Y]+2*vtol); + + vector<Interval> levels; + levels.emplace_back(300-(hand.pts[2*size ][Y]-vtol), 300-(hand.pts[2*size ][Y]+vtol) ); + levels.emplace_back(300-(hand.pts[2*size+1][Y]-vtol), 300-(hand.pts[2*size+1][Y]+vtol) ); + levels.emplace_back(300-(hand.pts[2*size+2][Y]-vtol), 300-(hand.pts[2*size+2][Y]+vtol) ); + + for (auto & level : levels) plot_bar(cr,level.middle()); + cairo_set_source_rgba( cr, 1., 0., 0., 1); + cairo_stroke(cr); + for (auto & level : levels) plot_bar(cr,level); + cairo_set_source_rgba( cr, 1., 0., 0., .2); + cairo_fill(cr); + + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + + *notify<<"Use hand.pts to set the coefficients of the s-basis."<<std::endl; + + vector<vector<Interval> > sols=level_sets(B,levels,0,1); + for (unsigned i=0;i<sols.size();i++){ + for (unsigned j=0;j<sols[i].size();j++){ + Interval ys = levels[i]; + ys.expandTo(0.); + cairo_set_line_width (cr, .3); + plot_bar(cr,ys, 1., sols[i][j].min(), sols[i][j].max()); + cairo_set_source_rgba( cr, 0., 0., 1., .3); + cairo_fill(cr); + plot_bar(cr,ys, 1., sols[i][j].min(), sols[i][j].max()); + cairo_set_source_rgba( cr, 0., 0., 1., .3); + cairo_stroke(cr); + cairo_set_line_width (cr, 1.6); + cairo_set_source_rgba( cr, 0., 0., 1, 1); + plot_bar(cr,0., 1., sols[i][j].min(), sols[i][j].max()); + Point sol ( 150 + 300 * sols[i][j].middle(), 300); + draw_cross(cr, sol); + cairo_stroke(cr); + } + } + + cairo_set_source_rgba( cr, 0., 0., 1., .5); + cairo_fill(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + +public: + BoundsTester(){ + size=5; + if(hand.pts.empty()) { + for(unsigned i = 0; i < 2*size; i++) + hand.pts.emplace_back(0,150+150+uniform()*300*0); + } + hand.pts.emplace_back(150,300+ 50+uniform()*100); + hand.pts.emplace_back(150,300- 50+uniform()*100); + hand.pts.emplace_back(150,300-150+uniform()*100); + handles.push_back(&hand); + slider = Slider(-5, 2, 0, 0.5, "tolerance"); + slider.geometry(Point(50, 20), 250); + slider.formatter(&exp_formatter); + handles.push_back(&slider); + } + + +private: + unsigned size; + PointSetHandle hand; + Slider slider; + +}; + +int main(int argc, char **argv) { + init(argc, argv, new BoundsTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/line-toy.cpp b/src/toys/line-toy.cpp new file mode 100644 index 0000000..0090d3a --- /dev/null +++ b/src/toys/line-toy.cpp @@ -0,0 +1,916 @@ +/* + * Line Toy + * + * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/line.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/angle.h> + +#include <vector> +#include <string> +#include <optional> + +using namespace Geom; + + + +std::string angle_formatter(double angle) +{ + return default_formatter(decimal_round(deg_from_rad(angle),2)); +} + + + +class LineToy : public Toy +{ + enum menu_item_t + { + SHOW_MENU = 0, + TEST_CREATE, + TEST_PROJECTION, + TEST_ORTHO, + TEST_DISTANCE, + TEST_POSITION, + TEST_SEG_BISEC, + TEST_ANGLE_BISEC, + TEST_COLLINEAR, + TEST_INTERSECTIONS, + TEST_COEFFICIENTS, + TEST_SEGMENT_INSIDE, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + }; + + enum toggle_label_t + { + }; + + enum slider_label_t + { + END_SHARED_SLIDERS = 0, + ANGLE_SLIDER = END_SHARED_SLIDERS, + A_COEFF_SLIDER = END_SHARED_SLIDERS, + B_COEFF_SLIDER, + C_COEFF_SLIDER + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &LineToy::draw_menu; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + sliders.clear(); + toggles.clear(); + handles.clear(); + } + + + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/ ) + { + init_common_ctrl_geom(cr, width, height, notify); + } + + + void init_create() + { + init_common(); + + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + O.pos = Point(50, 400); + + sliders.emplace_back(0, 2*M_PI, 0, 0, "angle"); + sliders[ANGLE_SLIDER].formatter(&angle_formatter); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&O); + handles.push_back(&(sliders[ANGLE_SLIDER])); + } + + void draw_create(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_create_ctrl_geom(cr, notify, width, height); + + Line l1(p1.pos, p2.pos); + Line l2(O.pos, sliders[ANGLE_SLIDER].value()); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, O, "O"); + draw_label(cr, l1, "L(P1,P2)"); + draw_label(cr, l2, "L(O,angle)"); + } + + + void init_projection() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + O.pos = Point(50, 150); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&O); + } + + void draw_projection(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + LineSegment ls(p3.pos, p4.pos); + + Point np = projection(O.pos, l1); + LineSegment lsp = projection(ls, l1); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width(cr, 0.2); + draw_line(cr, l1); + draw_segment(cr, ls); + cairo_stroke(cr); + + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); + draw_segment(cr, lsp); + draw_handle(cr, lsp[0]); + draw_handle(cr, lsp[1]); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + draw_circ(cr, np); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0); + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, ls, "S"); + draw_label(cr, lsp, "prj(S)"); + draw_label(cr, O, "P"); + draw_text(cr, np, "prj(P)"); + + cairo_stroke(cr); + } + + + void init_ortho() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 50); + p4.pos = Point(150, 450); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + } + + void draw_ortho(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + Line l2 = make_orthogonal_line(p3.pos, l1); + Line l3 = make_parallel_line(p4.pos, l1); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + draw_line(cr, l3); + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, p3, "O1"); + draw_label(cr, p4, "O2"); + + draw_label(cr, l1, "L"); + draw_label(cr, l2, "L1 _|_ L"); + draw_label(cr, l3, "L2 // L"); + + } + + + void init_distance() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + p5.pos = Point(50, 150); + p6.pos = Point(480, 60); + O.pos = Point(300, 300); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&p5); + handles.push_back(&p6); + handles.push_back(&O); + } + + void draw_distance(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + LineSegment ls(p3.pos, p4.pos); + Ray r1(p5.pos, p6.pos); + + Point q1 = l1.pointAt(l1.nearestTime(O.pos)); + Point q2 = ls.pointAt(ls.nearestTime(O.pos)); + Point q3 = r1.pointAt(r1.nearestTime(O.pos)); + + double d1 = distance(O.pos, l1); + double d2 = distance(O.pos, ls); + double d3 = distance(O.pos, r1); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_segment(cr, ls); + draw_ray(cr, r1); + cairo_stroke(cr); + + + draw_label(cr, l1, "L"); + draw_label(cr, ls, "S"); + draw_label(cr, r1, "R"); + draw_label(cr, O, "P"); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.5, 0.5, 0.8, 1.0); + cairo_set_line_width(cr, 0.2); + draw_segment(cr, O.pos, q1); + draw_segment(cr, O.pos, q2); + draw_segment(cr, O.pos, q3); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_handle(cr, q1); + draw_handle(cr, q2); + draw_handle(cr, q3); + cairo_stroke(cr); + + *notify << " distance(P,L) = " << d1 << std::endl; + *notify << " distance(P,S) = " << d2 << std::endl; + *notify << " distance(P,R) = " << d3 << std::endl; + } + + + void init_position() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 50); + p4.pos = Point(150, 450); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + } + + void draw_position(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + Line l2(p3.pos, p4.pos); + + bool b1 = are_same(l1, l2, 0.01); + bool b2 = are_parallel(l1, l2, 0.01); + bool b3 = are_orthogonal(l1, l2, 0.01); + + double a = angle_between(l1,l2); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + cairo_stroke(cr); + + draw_label(cr, l1, "A"); + draw_label(cr, l2, "B"); + cairo_stroke(cr); + + if (b1) + { + *notify << " A is coincident with B" << std::endl; + } + else if (b2) + { + *notify << " A is parallel to B" << std::endl; + } + else if (b3) + { + *notify << " A is orthogonal to B" << std::endl; + } + else + { + *notify << " A is incident with B: angle(A,B) = " << angle_formatter(a) << std::endl; + } + } + + + void init_seg_bisec() + { + init_common(); + p1.pos = Point(100, 50); + p2.pos = Point(150, 450); + + handles.push_back(&p1); + handles.push_back(&p2); + } + + void draw_seg_bisec(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + LineSegment ls(p1.pos, p2.pos); + Line l = make_bisector_line(ls); + Point M = middle_point(p1.pos, p2.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_label(cr, p1, "P"); + draw_label(cr, p2, "Q"); + draw_label(cr, M, "M"); + draw_segment(cr, ls); + draw_line(cr, l); + cairo_stroke(cr); + + draw_label(cr, l, "axis"); + cairo_stroke(cr); + } + + + void init_angle_bisec() + { + init_common(); + p1.pos = Point(100, 50); + p2.pos = Point(150, 450); + O.pos = Point(50, 200); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&O); + } + + void draw_angle_bisec(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Ray r1(O.pos, p1.pos); + Ray r2(O.pos, p2.pos); + Ray rb = make_angle_bisector_ray(r1, r2); + + double a1 = angle_between(r1,rb); + double a2 = angle_between(rb,r2); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_label(cr, O, "O"); + draw_ray(cr, r1); + draw_ray(cr, r2); + draw_ray(cr, rb); + cairo_stroke(cr); + + draw_label(cr, r1, "R1"); + draw_label(cr, r2, "R2"); + draw_label(cr, rb, "bisector ray"); + + *notify << " angle(R1, bisector ray) = " << angle_formatter(a1) + << " angle(bisector ray, R2) = " << angle_formatter(a2) + << std::endl; + } + + + void init_collinear() + { + init_common(); + p1.pos = Point(100, 50); + p2.pos = Point(450, 450); + p3.pos = Point(400, 50); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + } + + void draw_collinear(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Point A = p1.pos; + Point B = p2.pos; + Point C = p3.pos; + + LineSegment AB(A, B); + LineSegment BC(B, C); + Line l(A, C); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_label(cr, p1, "A"); + draw_label(cr, p2, "B"); + draw_label(cr, p3, "C"); + cairo_stroke(cr); + + + double turn = cross(C, B) - cross(C, A) + cross(B, A); + //*notify << " turn: " << turn << std::endl; + + bool collinear = are_collinear(A, B, C, 200); + if (collinear) + { + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l); + cairo_stroke(cr); + *notify << " A,B,C are collinear!" << std::endl; + } + else + { + cairo_set_source_rgba(cr, 0.5, 0.5, 0.8, 1.0); + cairo_set_line_width(cr, 0.2); + draw_segment(cr, AB); + draw_segment(cr, BC); + cairo_stroke(cr); + if (turn < 0) + *notify << " A,B,C is a left turn: " << turn << std::endl; + else + *notify << " A,B,C is a right turn: " << turn << std::endl; + } + + } + + + void init_intersections() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + p5.pos = Point(50, 150); + p6.pos = Point(480, 60); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&p5); + handles.push_back(&p6); + } + + void draw_intersections(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + Ray r1(p3.pos, p4.pos); + LineSegment s1(p5.pos, p6.pos); + + std::vector<ShapeIntersection> cl1r1 = l1.intersect(r1); + std::vector<ShapeIntersection> cl1s1 = l1.intersect(s1); + std::vector<ShapeIntersection> cr1s1 = Line(r1).intersect(s1); + filter_ray_intersections(cr1s1, true, false); + + std::vector<Point> ip; + + if (!cl1r1.empty()) { + ip.push_back(l1.pointAt(cl1r1[0].first)); + } + if (!cl1s1.empty()) { + ip.push_back(l1.pointAt(cl1s1[0].first)); + } + if (!cr1s1.empty()) { + ip.push_back(r1.pointAt(cr1s1[0].first)); + } + + cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_ray(cr, r1); + draw_segment(cr, s1); + cairo_stroke(cr); + + + draw_label(cr, l1, "L"); + draw_label(cr, r1, "R"); + draw_label(cr, s1, "S"); + cairo_stroke(cr); + + std::string label("A"); + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.5); + for (auto & i : ip) + { + draw_handle(cr, i); + draw_text(cr, i+op, label.c_str()); + label[0] += 1; + } + cairo_stroke(cr); + + } + + + void init_coefficients() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + + Line l(p1.pos, p2.pos); + std::vector<double> coeff = l.coefficients(); + sliders.emplace_back(-1, 1, 0, coeff[0], "A"); + sliders.emplace_back(-1, 1, 0, coeff[1], "B"); + sliders.emplace_back(-500, 500, 0, coeff[2], "C"); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&(sliders[A_COEFF_SLIDER])); + handles.push_back(&(sliders[B_COEFF_SLIDER])); + handles.push_back(&(sliders[C_COEFF_SLIDER])); + } + + void draw_coefficients(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_coefficients_ctrl_geom(cr, notify, width, height); + + Line l1(p1.pos, p2.pos); + std::vector<double> coeff1 = l1.coefficients(); + Line l2(sliders[A_COEFF_SLIDER].value(), + sliders[B_COEFF_SLIDER].value(), + sliders[C_COEFF_SLIDER].value()); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.8); + draw_line(cr, l1); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.4); + draw_line(cr, l2); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + draw_label(cr, p1, "P"); + draw_label(cr, p2, "Q"); + //draw_label(cr, l1, "L(P,Q)"); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); + draw_label(cr, l2, "L(A, B, C)"); + cairo_stroke(cr); + + *notify << " L(P,Q): a = " << coeff1[0] + << ", b = " << coeff1[1] + << ", c = " << coeff1[2] << std::endl; + + } + + void init_segment_inside() + { + init_common(); + p1.pos = Point(200, 300); + p2.pos = Point(400, 200); + p3.pos = Point(250, 200); + p4.pos = Point(350, 300); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + } + + void draw_segment_inside(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + Line l(p1.pos, p2.pos); + Rect r(p3.pos, p4.pos); + + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_set_line_width(cr, 0.5); + draw_line(cr, l); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0, 0, 1, 1); + cairo_rectangle(cr, r); + cairo_stroke(cr); + + std::optional<LineSegment> seg = l.clip(r); + if (seg) { + cairo_set_source_rgba(cr, 1, 0, 0, 1); + draw_line_seg(cr, seg->initialPoint(), seg->finalPoint()); + cairo_stroke(cr); + } + } + + + void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int /*height*/, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + } + } + + void init_create_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[ANGLE_SLIDER].geometry(Point(50, height - 50), 180); + } + } + + void init_coefficients_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[A_COEFF_SLIDER].geometry(Point(50, height - 160), 400); + sliders[B_COEFF_SLIDER].geometry(Point(50, height - 110), 400); + sliders[C_COEFF_SLIDER].geometry(Point(50, height - 60), 400); + } + } + + + void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) + { + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); + } + + void draw_segment(cairo_t* cr, LineSegment const& ls) + { + draw_segment(cr, ls[0], ls[1]); + } + + void draw_ray(cairo_t* cr, Ray const& r) + { + double angle = r.angle(); + draw_segment(cr, r.origin(), angle, m_length); + } + + void draw_line(cairo_t* cr, Line const& l) + { + double angle = l.angle(); + draw_segment(cr, l.origin(), angle, m_length); + draw_segment(cr, l.origin(), angle, -m_length); + } + + void draw_label(cairo_t* cr, PointHandle const& ph, const char* label) + { + draw_text(cr, ph.pos+op, label); + } + + void draw_label(cairo_t* cr, Line const& l, const char* label) + { + draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label); + } + + void draw_label(cairo_t* cr, LineSegment const& ls, const char* label) + { + draw_text(cr, middle_point(ls[0], ls[1])+op, label); + } + + void draw_label(cairo_t* cr, Ray const& r, const char* label) + { + Point prj = r.pointAt(r.nearestTime(Point(m_width/2-30, m_height/2-30))); + if (L2(r.origin() - prj) < 100) + { + prj = r.origin() + 100*r.vector(); + } + draw_text(cr, prj+op, label); + } + + void init_menu() + { + handles.clear(); + sliders.clear(); + toggles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/ ) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &LineToy::draw_menu; + break; + case 'B': + init_create(); + draw_f = &LineToy::draw_create; + break; + case 'C': + init_projection(); + draw_f = &LineToy::draw_projection; + break; + case 'D': + init_ortho(); + draw_f = &LineToy::draw_ortho; + break; + case 'E': + init_distance(); + draw_f = &LineToy::draw_distance; + break; + case 'F': + init_position(); + draw_f = &LineToy::draw_position; + break; + case 'G': + init_seg_bisec(); + draw_f = &LineToy::draw_seg_bisec; + break; + case 'H': + init_angle_bisec(); + draw_f = &LineToy::draw_angle_bisec; + break; + case 'I': + init_collinear(); + draw_f = &LineToy::draw_collinear; + break; + case 'J': + init_intersections(); + draw_f = &LineToy::draw_intersections; + break; + case 'K': + init_coefficients(); + draw_f = &LineToy::draw_coefficients; + break; + case 'L': + init_segment_inside(); + draw_f = &LineToy::draw_segment_inside; + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + LineToy() + { + op = Point(5,5); + } + + private: + typedef void (LineToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + PointHandle p1, p2, p3, p4, p5, p6, O; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + Point op; + double m_width, m_height, m_length; + +}; // end class LineToy + + +const char* LineToy::menu_items[] = +{ + "show this menu", + "line generation", + "projection on a line", + "make orthogonal/parallel", + "distance", + "position", + "segment bisector", + "angle bisector", + "collinear", + "intersection", + "coefficients", + "segment inside" +}; + +const char LineToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L' +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new LineToy()); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/load-svgd.cpp b/src/toys/load-svgd.cpp new file mode 100644 index 0000000..68b1495 --- /dev/null +++ b/src/toys/load-svgd.cpp @@ -0,0 +1,76 @@ +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/svg-path-parser.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/cairo-path-sink.h> +#include <2geom/svg-path-writer.h> + +#include <cstdlib> + +using namespace Geom; + +/** + * @brief SVG path data loading toy. + * + * A very simple toy that loads a file containing raw SVG path data + * and displays it scaled so that it fits inside the window. + * + * Use this toy to see what the path data looks like without + * pasting it into the d= attribute of a path in Inkscape. + */ +class LoadSVGD: public Toy { + PathVector pv; + OptRect bounds; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Rect viewport(Point(10, 10), Point(width-10, height-10)); + PathVector res = pv * bounds->transformTo(viewport, Aspect(ALIGN_XMID_YMID)); + + CairoPathSink sink(cr); + sink.feed(res); + + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_fill_preserve(cr); + cairo_set_line_width(cr, 1); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_set_source_rgb(cr, 0,0,0); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + public: + LoadSVGD() {} + + void first_time(int argc, char** argv) override { + const char *path_b_name="star.svgd"; + if (argc > 1) + path_b_name = argv[1]; + pv = read_svgd(path_b_name); + bounds = pv.boundsExact(); + if (!bounds) { + std::cerr << "Empty path, aborting" << std::endl; + std::exit(1); + } + } +}; + +int main(int argc, char **argv) { + LoadSVGD x; + init(argc, argv, &x); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/load-svgd.py b/src/toys/load-svgd.py new file mode 100644 index 0000000..26bcef3 --- /dev/null +++ b/src/toys/load-svgd.py @@ -0,0 +1,68 @@ +#!/usr/bin/python + +import py2geom +import toyframework +import random,gtk +from py2geom_glue import * + +def cairo_region(cr, r): + cr.save() + cr.set_source_rgba(0, 0, 0, 1); + if not r.isFill(): + pass#cr.set_dash([]1, 0) + cairo_path(cr, r) + cr.fill() + cr.restore() + +def cairo_regions(cr, p): + for j in p: + cairo_region(cr, j) + +def cairo_shape(cr, s): + cairo_regions(cr, s.getContent()) + + + +def cleanup(ps): + pw = py2geom.paths_to_pw(ps) + centre, area = py2geom.centroid(pw) + if(area > 1): + return py2geom.sanitize(ps) * py2geom.Translate(-centre) + else: + return py2geom.sanitize(ps) + +class LoadSVGD(toyframework.Toy): + def __init__(self): + toyframework.Toy.__init__(self) + self.bs = [] + self.offset_handle = toyframework.PointHandle(0,0) + self.handles.append(self.offset_handle) + def draw(self, cr, pos, save): + t = py2geom.Translate(*self.offset_handle.pos) + #self.paths_b[0] * t + m = py2geom.Matrix() + m.setIdentity() + bst = self.bs * (m * t) + #bt = Region(b * t, b.isFill()) + + cr.set_line_width(1) + + cairo_shape(cr, bst) + + toyframework.Toy.draw(self, cr, pos, save) + + def first_time(self, argv): + path_b_name="star.svgd" + if len(argv) > 1: + path_b_name = argv[1] + self.paths_b = py2geom.read_svgd(path_b_name) + + bounds = py2geom.bounds_exact(self.paths_b) + self.offset_handle.pos = bounds.midpoint() - bounds.corner(0) + + self.bs = cleanup(self.paths_b) +t = LoadSVGD() +import sys + +toyframework.init(sys.argv, t, 500, 500) + diff --git a/src/toys/lpe-framework.cpp b/src/toys/lpe-framework.cpp new file mode 100644 index 0000000..19f7267 --- /dev/null +++ b/src/toys/lpe-framework.cpp @@ -0,0 +1,128 @@ +/* + * A framework for writing an Inkscape Live Path Effect (LPE) toy. + * + * Copyright 2009 Johan Engelen <goejendaagh@zonnet.nl> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#include <string.h> +#include <stdint.h> +#include <toys/lpe-framework.h> + +#include <2geom/sbasis-to-bezier.h> +#include <2geom/affine.h> +#include <2geom/pathvector.h> + +#define LPE_CONVERSION_TOLERANCE 0.01 + +/** + * When the input path handles are moved, this method is called to re-execute the LPE and draw the result. + */ +void LPEToy::draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) +{ + using namespace Geom; + + Piecewise<D2<SBasis> > pwd2(curve_handle.asBezier()); + PathVector A = Geom::path_from_piecewise( pwd2, LPE_CONVERSION_TOLERANCE); + cairo_set_line_width (cr, 2); + cairo_set_source_rgba (cr, 1., 0.0, 0., 1); + cairo_path(cr, A); + cairo_stroke(cr); + + // perform the effect: + PathVector B = doEffect_path(A); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.0, 0., 1); + cairo_path(cr, B); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); +} + +/** + * Initializes the LPE toy, and sets a simple default input path. + */ +LPEToy::LPEToy(){ + if(handles.empty()) { + handles.push_back(&curve_handle); + for(unsigned i = 0; i < 4; i++) { + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + } + } +} + +/* + * Here be the doEffect function chain: (this is copied code from Inkscape) + */ +Geom::PathVector +LPEToy::doEffect_path (Geom::PathVector const &path_in) +{ + Geom::PathVector path_out; + + if ( !concatenate_before_pwd2 ) { + // default behavior + for (const auto & i : path_in) { + Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = i.toPwSb(); + Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in); + Geom::PathVector path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); + // add the output path vector to the already accumulated vector: + for (const auto & j : path) { + path_out.push_back(j); + } + } + } else { + // concatenate the path into possibly discontinuous pwd2 + Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in; + for (const auto & i : path_in) { + pwd2_in.concat( i.toPwSb() ); + } + Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in); + path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); + } + + return path_out; +} + +Geom::Piecewise<Geom::D2<Geom::SBasis> > +LPEToy::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in) +{ + // default effect does nothing + return pwd2_in; +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/lpe-test.cpp b/src/toys/lpe-test.cpp new file mode 100644 index 0000000..ff397c0 --- /dev/null +++ b/src/toys/lpe-test.cpp @@ -0,0 +1,93 @@ +/** + * \file lpe-test.cpp + * \brief Example file showing how to write an Inkscape Live Path Effect toy. + */ +/* + * Copyright 2009 Johan Engelen <goejendaagh@zonnet.nl> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#include <toys/lpe-framework.h> + +// This is usually very bad practice: don't do it in your LPE +using std::vector; +using namespace Geom; +using namespace std; + +class LPETest: public LPEToy { +public: + LPETest() { + concatenate_before_pwd2 = false; + } + + Geom::Piecewise<Geom::D2<Geom::SBasis> > + doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in) override + { + using namespace Geom; + + Piecewise<D2<SBasis> > pwd2_out = pwd2_in; + + Point vector(50,100); + // generate extrusion bottom: (just a copy of original path, displaced a bit) + pwd2_out.concat( pwd2_in + vector ); + + // generate connecting lines (the 'sides' of the extrusion) + Path path(Point(0.,0.)); + path.appendNew<Geom::LineSegment>( vector ); + Piecewise<D2<SBasis> > connector = path.toPwSb(); + // connecting lines should be put at cusps + Piecewise<D2<SBasis> > deriv = derivative(pwd2_in); + std::vector<double> cusps; // = roots(deriv); + for (double cusp : cusps) { + pwd2_out.concat( connector + pwd2_in.valueAt(cusp) ); + } + // connecting lines should be put where the tangent of the path equals the extrude_vector in direction + std::vector<double> rts = roots(dot(deriv, rot90(vector))); + for (double rt : rts) { + pwd2_out.concat( connector + pwd2_in.valueAt(rt) ); + } + + return pwd2_out; + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new LPETest); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/match-curve.cpp b/src/toys/match-curve.cpp new file mode 100644 index 0000000..60e81a5 --- /dev/null +++ b/src/toys/match-curve.cpp @@ -0,0 +1,162 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZROOTS_TEST 0 +#if ZROOTS_TEST +#include <2geom/zroots.c> +#endif + +#include <vector> +using std::vector; +using namespace Geom; + +//#define HAVE_GSL + +template <typename T> +void shift(T &a, T &b, T const &c) { + a = b; + b = c; +} +template <typename T> +void shift(T &a, T &b, T &c, T const &d) { + a = b; + b = c; + c = d; +} + +extern unsigned total_steps, total_subs; + +class MatchCurve: public Toy { +public: + double timer_precision; + double units; + PointSetHandle psh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 1); + cairo_set_source_rgb(cr, 0,0,0); + std::vector<Geom::Point> trans; + trans.resize(psh.size()); + for(unsigned i = 0; i < psh.size(); i++) { + trans[i] = psh.pts[i] - Geom::Point(0, 3*width/4); + } + + std::vector<double> solutions; + + D2<SBasis> test_sb = psh.asBezier(); + + + D2<SBasis> B = psh.asBezier(); + Geom::Path pb; + pb.append(B); + pb.close(false); + cairo_path(cr, pb); + cairo_stroke(cr); + + D2<SBasis> m; + D2<SBasis> dB = derivative(B); + D2<SBasis> ddB = derivative(dB); + D2<SBasis> dddB = derivative(ddB); + + Geom::Point pt = B(0); + Geom::Point tang = dB(0); + Geom::Point dtang = ddB(0); + Geom::Point ddtang = dddB(0); + for(int dim = 0; dim < 2; dim++) { + m[dim] = Linear(pt[dim],pt[dim]+tang[dim]); + m[dim] += Linear(0, 1)*Linear(0, 1*dtang[dim])/2; + m[dim] += Linear(0, 1)*Linear(0, 1)*Linear(0, ddtang[dim])/6; + } + + double lo = 0, hi = 1; + double eps = 5; + while(hi - lo > 0.0001) { + double mid = (hi + lo)/2; + //double Bmid = (Bhi + Blo)/2; + + m = truncate(compose(B, Linear(0, mid)), 2); + // perform golden section search + double best_f = 0, best_x = 1; + for(int n = 2; n < 4; n++) { + Geom::Point m_pt = m(double(n)/6); + double x0 = 0, x3 = 1.; // just a guess! + const double R = 0.61803399; + const double C = 1 - R; + double x1 = C*x0 + R*x3; + double x2 = C*x1 + R*x3; + double f1 = Geom::distance(m_pt, B(x1)); + double f2 = Geom::distance(m_pt, B(x2)); + while(fabs(x3 - x0) > 1e-3*(fabs(x1) + fabs(x2))) { + if(f2 < f1) { + shift(x0, x1, x2, R*x1 + C*x3); + shift(f1, f2, Geom::distance(m_pt, B(x2))); + } else { + shift(x3, x2, x1, R*x2 + C*x0); + shift(f2, f1, Geom::distance(m_pt, B(x2))); + } + std::cout << x0 << "," + << x1 << "," + << x2 << "," + << x3 << "," + << std::endl; + } + if(f2 < f1) { + f1 = f2; + x1 = x2; + } + if(f1 > best_f) { + best_f = f1; + best_x = x1; + } + } + std::cout << mid << ":" << best_x << "->" << best_f << std::endl; + //draw_cross(cr, point_at(B, x1)); + + if(best_f > eps) { + hi = mid; + } else { + lo = mid; + } + } + std::cout << std::endl; + //draw_cross(cr, point_at(B, hi)); + draw_circ(cr, m(hi)); + { + Geom::Path pb; + pb.append(m); + pb.close(false); + cairo_path(cr, pb); + } + + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + MatchCurve() : timer_precision(0.1), units(1e6) // microseconds + { + handles.push_back(&psh); + for(int i = 0; i < 6; i++) + psh.push_back(uniform()*400, uniform()*400); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new MatchCurve()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/mesh-grad.cpp b/src/toys/mesh-grad.cpp new file mode 100644 index 0000000..c71f5f0 --- /dev/null +++ b/src/toys/mesh-grad.cpp @@ -0,0 +1,134 @@ +/** + * Generate approximate mesh gradients for blurring technique suggested by bbyak. + * (njh) + */ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +#include <vector> +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +const double u_subs = 5, + v_subs = 5, + fudge = .01; + +const double inv_u_subs = 1 / u_subs, + inv_v_subs = 1 / v_subs; + +class Sb2d2: public Toy { +public: + PointSetHandle hand; + Sb2d2() { + handles.push_back(&hand); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + D2<SBasis2d> sb2; + for(unsigned dim = 0; dim < 2; dim++) { + sb2[dim].us = 2; + sb2[dim].vs = 2; + const int depth = sb2[dim].us*sb2[dim].vs; + sb2[dim].resize(depth, Linear2d(0)); + } + Geom::Point dir(1,-2); + if(hand.pts.empty()) { + for(unsigned vi = 0; vi < sb2[0].vs; vi++) + for(unsigned ui = 0; ui < sb2[0].us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) + hand.pts.emplace_back((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + + } + + for(int dim = 0; dim < 2; dim++) { + Geom::Point dir(0,0); + dir[dim] = 1; + for(unsigned vi = 0; vi < sb2[dim].vs; vi++) + for(unsigned ui = 0; ui < sb2[dim].us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) { + unsigned corner = iu + 2*iv; + unsigned i = ui + vi*sb2[dim].us; + Geom::Point base((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + if(vi == 0 && ui == 0) { + base = Geom::Point(width/4., width/4.); + } + double dl = dot((hand.pts[corner+4*i] - base), dir)/dot(dir,dir); + sb2[dim][i][corner] = dl/(width/2)*pow(4.0,(double)ui+vi); + } + } + cairo_d2_sb2d(cr, sb2, dir*0.1, width); + cairo_set_source_rgba (cr, 0., 0., 0, 0.5); + cairo_stroke(cr); + for(unsigned vi = 0; vi < v_subs; vi++) { + double tv = vi * inv_v_subs; + for(unsigned ui = 0; ui < u_subs; ui++) { + double tu = ui * inv_u_subs; + + Geom::Path pb; + D2<SBasis> B; + D2<SBasis> tB; + + B[0] = Linear(tu-fudge, tu+fudge + inv_u_subs ); + B[1] = Linear(tv-fudge, tv-fudge); + tB = compose_each(sb2, B); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + pb.append(tB); + + B[0] = Linear(tu+fudge + inv_u_subs , tu+fudge + inv_u_subs); + B[1] = Linear(tv-fudge, tv+fudge + inv_v_subs); + tB = compose_each(sb2, B); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + pb.append(tB); + + B[0] = Linear(tu+fudge + inv_u_subs, tu-fudge); + B[1] = Linear(tv+fudge + inv_v_subs, tv+fudge + inv_v_subs); + tB = compose_each(sb2, B); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + pb.append(tB); + + B[0] = Linear(tu-fudge, tu-fudge); + B[1] = Linear(tv+fudge + inv_v_subs, tv-fudge); + tB = compose_each(sb2, B); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + pb.append(tB); + + cairo_path(cr, pb); + + //std::cout << pb.peek().end() - pb.peek().begin() << std::endl; + cairo_set_source_rgba (cr, tu, tv, 0, 1); + cairo_fill(cr); + } + } + //*notify << "bo = " << sb2.index(0,0); + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sb2d2); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/metro.cpp b/src/toys/metro.cpp new file mode 100644 index 0000000..5d2804c --- /dev/null +++ b/src/toys/metro.cpp @@ -0,0 +1,922 @@ +/** Generates approximate metromap lines + * Copyright njh + * Copyright Tim Dwyer + * Published in ISVC 2008, Las Vegas, Nevada, USA + */ + +#include <cstdio> +#include <cstring> +#include <cstdlib> +#include <cmath> + +#include <gtk/gtk.h> +#include <cassert> +#include <algorithm> +#include <sstream> +#include <iostream> +#include <fstream> +#include <vector> +#include <string> +#include <map> +#include <cairo-pdf.h> +#include <2geom/point.h> +#include <2geom/geom.h> +#include <toys/toy-framework-2.h> + +using std::string; +using std::vector; +using std::pair; +using std::make_pair; +using std::ifstream; +using std::map; +using std::cout; +using std::endl; +using namespace Geom; + +vector<vector<Point> > paths; + +class sufficient_stats{ +public: + double Sx, Sy, Sxx, Syy, Sxy; + double n; + + sufficient_stats() : Sx(0), Sy(0), Sxx(0), Syy(0), Sxy(0), n(0) {} + void + operator+=(Point p) { + Sx += p[0]; + Sy += p[1]; + Sxx += p[0]*p[0]; + Syy += p[1]*p[1]; + Sxy += p[0]*p[1]; + n += 1.0; + } + void + operator-=(Point p) { + Sx -= p[0]; + Sy -= p[1]; + Sxx -= p[0]*p[0]; + Syy -= p[1]*p[1]; + Sxy -= p[0]*p[1]; + n -= 1.0; + } + /*** What is the best regression we can do? . */ + Point best_normal() { + return rot90(unit_vector(Point(n*Sxx - Sx*Sx, + n*Sxy - Sx*Sy))); + } + /*** Compute the best line for the points, given normal. */ + double best_line(Point normal, const double dist, + double & mean) { + mean = (normal[0]*Sx + normal[1]*Sy); + return normal[0]*normal[0]*Sxx + + normal[1]*normal[1]*Syy + + 2*normal[0]*normal[1]*Sxy + - 2*dist*mean + n*dist*dist; + } + /*** Returns the index to the angle in angles that has the line of best fit + * passing through mean */ + unsigned best_schematised_line(vector<Point>& angles, Point p, + double & /*mean*/, double & cost) { + cost = DBL_MAX; + unsigned bestAngle = 0; + for(unsigned i=0;i<angles.size();i++) { + Point n = unit_vector(rot90(angles[i])); + double dist = dot(n, p); + double mean; + double bl = best_line(n, dist, mean); + if(bl < cost) { + cost = bl; + bestAngle = i; + } + } + return bestAngle; + } + /*** Compute the best line for the points, given normal. */ + double best_angled_line(Point normal, + double & mean) { + mean = (normal[0]*Sx + normal[1]*Sy); + double dist = mean/n; + return normal[0]*normal[0]*Sxx + + normal[1]*normal[1]*Syy + + 2*normal[0]*normal[1]*Sxy + - 2*dist*mean + n*dist*dist; + } +}; + +sufficient_stats +operator+(sufficient_stats const & a, sufficient_stats const &b) { + sufficient_stats ss; + ss.Sx = a.Sx + b.Sx; + ss.Sy = a.Sy + b.Sy; + ss.Sxx = a.Sxx + b.Sxx; + ss.Sxy = a.Sxy + b.Sxy; + ss.Syy = a.Syy + b.Syy; + ss.n = a.n + b.n; + return ss; +} + +sufficient_stats +operator-(sufficient_stats const & a, sufficient_stats const &b) { + sufficient_stats ss; + ss.Sx = a.Sx - b.Sx; + ss.Sy = a.Sy - b.Sy; + ss.Sxx = a.Sxx - b.Sxx; + ss.Sxy = a.Sxy - b.Sxy; + ss.Syy = a.Syy - b.Syy; + ss.n = a.n - b.n; + return ss; +} + +inline std::ostream &operator<< (std::ostream &out_file, const sufficient_stats &s) { + out_file << "Sx: " << s.Sx + << "Sy: " << s.Sy + << "Sxx: " << s.Sxx + << "Sxy: " << s.Sxy + << "Syy: " << s.Syy + << "n: " << s.n; + return out_file; +} + + + +class fit{ +public: + vector<Point> input; + vector<Point> solution; + + vector<pair<Point,Point> > lines; + vector<int> thickness; + + void + draw(cairo_t* cr) { + /* + if(solution.size() > 1) { + //cairo_set_line_width (cr, 1); + cairo_move_to(cr, solution[0]); + for(unsigned i = 1; i < solution.size(); i++) { + cairo_line_to(cr, solution[i]); + } + } + */ + //cairo_stroke(cr); + for(unsigned i = 0;i<lines.size();i++) { + if(thickness.size()>i) { + cairo_set_line_width (cr, thickness[i]); + } + cairo_move_to(cr, lines[i].first); + cairo_line_to(cr, lines[i].second); + cairo_stroke(cr); + } + } + + void endpoints() { + solution.push_back(input[0]); + solution.push_back(input.back()); + } + + void arbitrary(); + void linear_reg(); + + // sufficient stats from start to each point + vector<sufficient_stats> ac_ss; + + /*** Compute the least squares error for a line between two points on the line. */ + double measure(unsigned from, unsigned to, double & mean) { + sufficient_stats ss = ac_ss[to+1] - ac_ss[from]; + + Point n = unit_vector(rot90(input[to] - input[from])); + double dist = dot(n, input[from]); + return ss.best_line(n, dist, mean); + } + + /*** Compute the best line for the points, given normal. */ + double best_angled_line(unsigned from, unsigned to, + Point n, + double & mean) { + sufficient_stats ss = ac_ss[to+1] - ac_ss[from]; + return ss.best_angled_line(n, mean); + } + + double simple_measure(unsigned from, unsigned to, double & mean) { + Point n = unit_vector(rot90(input[to] - input[from])); + double dist = dot(n, input[from]); // distance from origin + double error = 0; + mean = 0; + for(unsigned l = from; l <= to; l++) { + double d = dot(input[l], n) - dist; + mean += dot(input[l], n); + error += d*d; + } + return error; + } + + void simple_dp(); + void C_simple_dp(); + + void C_endpoint(); + + vector<Geom::Point> angles; + fit(vector<Point> const & in) + :input(in) { + /* + Geom::Point as[] = {Point(0,1), Point(1,0), Point(1,1), Point(1,-1)}; + for(unsigned c = 0; c < 4; c++) { + angles.push_back(unit_vector(as[c])); + } + */ + sufficient_stats ss; + ss.Sx = ss.Sy = ss.Sxx = ss.Sxy = ss.Syy = 0; + ac_ss.push_back(ss); + for(auto & l : input) { + ss += l; + ac_ss.push_back(ss); + } + } + + class block{ + public: + unsigned next; + unsigned angle; + sufficient_stats ss; + double cost; + }; + vector<block> blocks; + void test(); + void merging_version(); + void schematised_merging(unsigned number_of_directions); + + double get_block_line(block& b, Point& d, Point& n, Point& c) { + n = unit_vector(rot90(d)); + c = Point(b.ss.Sx/b.ss.n,b.ss.Sy/b.ss.n); + return 0; + } +}; + +class build_bounds{ +public: + int total_n; + Point starting[2]; + Rect combined; + void add_point(Point const &P) { + if(total_n < 2) { + starting[total_n] = P; + total_n += 1; + if(total_n == 2) + combined = Rect(starting[0], starting[1]); + } else { + combined.expandTo(P); + total_n += 1; + } + } + OptRect result() const { + if(total_n > 1) + return combined; + return OptRect(); + } + build_bounds() : total_n(0) {} +}; + +/** + * returns a point which is portionally between topleft and bottomright in the same way that p was + * in src. + */ +Point map_point(Point p, Rect src, Point topleft, Point bottomright) { + p -= src.min(); + p[0] /= src[0].extent(); + p[1] /= src[1].extent(); + //cout << p << endl; + return Point(topleft[0]*(1-p[0]) + bottomright[0]*p[0], + topleft[1]*(1-p[1]) + bottomright[1]*p[1]); +} + +void parse_data(vector<vector<Point> >& paths, + string location_file_name, + string path_file_name) { + ifstream location_file(location_file_name.c_str()), + path_file(path_file_name.c_str()); + string id,sx,sy; + map<string,Point> idlookup; + build_bounds bld_bounds; + while (getline(location_file,id,',')) + { + getline(location_file,sx,','); + getline(location_file,sy,'\n'); + char *e; + // negative for coordinate system + Point p(strtod(sx.c_str(),&e), strtod(sy.c_str(),&e)); + //cout << id << p << endl; + idlookup[id] = p; + } + + + string l; + while (getline(path_file,l,'\n')) { + vector<Point> ps; + if(l.size() < 2) continue; // skip blank lines + if(l.find(":",0)!=string::npos) continue; // skip labels + string::size_type p=0,q; + while((q=l.find(",",p))!=string::npos || (p < l.size() && (q = l.size()))) { + id = l.substr(p,q-p); + //cout << id << endl; + Point pt = 2*idlookup[id]; + //cout << pt << endl; + bld_bounds.add_point(pt); + ps.push_back(pt); + p=q+1; + } + paths.push_back(ps); + //cout << "*******************************************" << endl; + } + Rect bounds = *bld_bounds.result(); + //cout << bounds.min() << " - " << bounds.max() << endl; + for(auto & path : paths) { + for(auto & j : path) { + j = map_point(j, bounds, + Point(0,512), Point(512*bounds[0].extent()/bounds[1].extent(),0)); + } + } + /* + for(map<string,Point>::iterator it = idlookup.begin(); + it != idlookup.end(); it++) { + (*it).second = map_point((*it).second, bounds, + Point(0,0), Point(100,100)); + //Point(0, 512), Point(512,0)); + cout << (*it).first << ":" << (*it).second << endl; + }*/ + /* + unsigned biggest = 0, biggest_i; + for(unsigned i=0;i<paths.size();i++) { + vector<Point> ps=paths[i]; + if(ps.size()>biggest) { + biggest_i=i; + biggest = ps.size(); + } + for(unsigned j=0;j<ps.size();j++) { + double x=ps[j][0], y=ps[j][1]; + cout << "(" << x << "," << y << ")" << ","; + } + cout << endl; + } + */ +} + +void extremePoints(vector<Point> const & pts, Point const & dir, + Point & min, Point & max) { + double minProj = DBL_MAX, maxProj = -DBL_MAX; + for(auto pt : pts) { + double p = dot(pt,dir); + if(p < minProj) { + minProj = p; + min = pt; + } + if(p > maxProj) { + maxProj = p; + max = pt; + } + } +} + +void fit::test() { + sufficient_stats ss; + const unsigned N = input.size(); + for(unsigned i = 0; i < N; i++) { + ss += input[i]; + } + double best_bl = DBL_MAX; + unsigned best = 0; + for(unsigned i=0;i<angles.size();i++) { + Point n = unit_vector(rot90(angles[i])); + double dist = dot(n, input[0]); + double mean; + double bl = ss.best_line(n, dist, mean); + if(bl < best_bl) { + best = i; + best_bl = bl; + } + mean/=N; + Point d = angles[i]; + Point a = mean*n; + Point min, max; + extremePoints(input,d,min,max); + Point b = dot(min,d)*d; + Point c = dot(max,d)*d; + Point start = a+b; + Point end = a+c; + lines.emplace_back(start,end); + thickness.push_back(1); + } + thickness[best] = 4; +} + +void fit::schematised_merging(unsigned number_of_directions) { + const double link_cost = 0; + const unsigned N = input.size()-1; + blocks.resize(N); + for(unsigned i = 0; i<number_of_directions ; i++) { + double t = M_PI*i/float(number_of_directions); + angles.emplace_back(cos(t),sin(t)); + } + // pairs + for(unsigned i = 0; i < N; i++) { + block b; + sufficient_stats ss; + ss += input[i]; + ss += input[i+1]; + b.ss = ss; + double mean, newcost; + b.angle = ss.best_schematised_line(angles, input[i], mean, newcost); + b.cost = link_cost + newcost; + b.next = i+1; + blocks[i] = b; + //std::cout << ss + //<< std::endl; + } + + // merge(a,b) + while(N>1) + { + block best_block; + unsigned best_idx = 0; + double best = 0; + unsigned beg = 0; + unsigned middle = blocks[beg].next; + unsigned end = blocks[middle].next; + while(middle < N) { + sufficient_stats ss = blocks[beg].ss + blocks[middle].ss; + //ss -= input[middle]; + double mean, newcost; + unsigned bestAngle = ss.best_schematised_line(angles, input[beg], mean, newcost); + double deltaCost = -link_cost - blocks[beg].cost - blocks[middle].cost + + newcost; + /*std::cout << beg << ", " + << middle << ", " + << end << ", " + << deltaCost <<"; " + << newcost <<"; " + << mean << ": " + << ss + << std::endl;*/ + //if(deltaCost < best) { + if(blocks[beg].angle==blocks[middle].angle) { + best = deltaCost; + best = -1; + best_idx = beg; + best_block.ss = ss; + best_block.cost = newcost; + best_block.next = end; + best_block.angle = bestAngle; + } + beg = middle; + middle = end; + end = blocks[end].next; + } + if(best < 0) + blocks[best_idx] = best_block; + else // no improvement possible + break; + } + { + solution.resize(0); // empty list + unsigned beg = 0; + unsigned prev = 0; + while(beg < N) { + block b = blocks[beg]; + { + Point n, c; + Point n1, c1; + Point d = angles[b.angle]; + get_block_line(b,d,n,c); + Point start = c, end = c+10*angles[b.angle]; + Line ln = Line::from_normal_distance(n, dot(c,n)); + if(beg==0) { + //start = intersection of b.line and + // line through input[0] orthogonal to b.line + OptCrossing c = intersection(ln, + Line::from_normal_distance(d, dot(d,input[0]))); + assert(c); + start = ln.pointAt(c->ta); + //line_intersection(n, dot(c,n), d, dot(d,input[0]), start); + } else { + //start = intersection of b.line and blocks[prev].line + block p = blocks[prev]; + if(b.angle!=p.angle) { + get_block_line(p,angles[p.angle],n1,c1); + //line_intersection(n, dot(c,n), n1, dot(c1,n1), start); + OptCrossing c = intersection(ln, + Line::from_normal_distance(n1, dot(c1,n1))); + assert(c); + start = ln.pointAt(c->ta); + } + } + + if (b.next < N) { + //end = intersection of b.line and blocks[b.next].line + block next = blocks[b.next]; + if(b.angle!=next.angle) { + get_block_line(next,angles[next.angle],n1,c1); + //line_intersection(n, dot(c,n), n1, dot(c1,n1), end); + OptCrossing c = intersection(ln, + Line::from_normal_distance(n1, dot(c1,n1))); + assert(c); + end = ln.pointAt(c->ta); + } + } else { + //end = intersection of b.line and + // line through input[N-1] orthogonal to b.line + //line_intersection(n, dot(c,n), d, dot(d,input[N]), end); + OptCrossing c = intersection(ln, + Line::from_normal_distance(d, dot(d,input[N]))); + assert(c); + end = ln.pointAt(c->ta); + } + lines.emplace_back(start,end); + } + prev = beg; + beg = b.next; + } + } +} +void fit::merging_version() { + const double link_cost = 100; + const unsigned N = input.size(); + blocks.resize(N); + // pairs + for(unsigned i = 0; i < N; i++) { + block b; + sufficient_stats ss; + ss.Sx = ss.Sy = ss.Sxx = ss.Sxy = ss.Syy = 0; + ss.n = 0; + ss += input[i]; + ss += input[i+1]; + b.ss = ss; + b.cost = link_cost; + b.next = i+1; + blocks[i] = b; + //std::cout << ss + //<< std::endl; + } + + // merge(a,b) + while(1) + { + block best_block; + unsigned best_idx = 0; + double best = 0; + unsigned beg = 0; + unsigned middle = blocks[beg].next; + unsigned end = blocks[middle].next; + while(end != N) { + sufficient_stats ss = blocks[beg].ss + blocks[middle].ss; + ss -= input[middle]; + double mean; + Point normal = unit_vector(rot90(input[end] - input[beg])); + double dist = dot(normal, input[beg]); + double newcost = ss.best_line(normal, dist, mean); + double deltaCost = -link_cost - blocks[beg].cost - blocks[middle].cost + + newcost; + /*std::cout << beg << ", " + << middle << ", " + << end << ", " + << deltaCost <<"; " + << newcost <<"; " + << mean << ": " + << ss + << std::endl;*/ + if(deltaCost < best) { + best = deltaCost; + best_idx = beg; + best_block.ss = ss; + best_block.cost = newcost; + best_block.next = end; + } + beg = middle; + middle = end; + end = blocks[end].next; + } + if(best < 0) + blocks[best_idx] = best_block; + else // no improvement possible + break; + } + { + solution.resize(0); // empty list + unsigned beg = 0; + while(beg != N) { + solution.push_back(input[beg]); + beg = blocks[beg].next; + } + } +} + + +void fit::arbitrary() { + /*solution.resize(input.size()); + copy(input.begin(), input.end(), solution.begin());*/ + // normals + + double best_error = INFINITY; + double best_mean = 0; + unsigned best_angle = 0; + for(unsigned i = 0; i < angles.size(); i++) { + Point angle = angles[i]; + double mean = 0; + double error = 0; + for(unsigned l = 0; l < input.size(); l++) { + mean += dot(input[i], angle); + } + mean /= input.size(); + for(unsigned l = 0; l < input.size(); l++) { + double d = dot(input[i], angle) - mean; + error += d*d; + } + if(error < best_error) { + best_mean = mean; + best_error = error; + best_angle = i; + } + } + Point angle = angles[best_angle]; + solution.push_back(angle*best_mean + dot(input[0], rot90(angle))*rot90(angle)); + solution.push_back(angle*best_mean + dot(input.back(), rot90(angle))*rot90(angle)); +} + +class reg_line{ +public: + Point parallel, centre, normal; + double Sr, Srr; + unsigned n; +}; + +template<class T> +reg_line +line_best_fit(T b, T e) { + double Sx = 0, + Sy = 0, + Sxx = 0, + Syy = 0, + Sxy = 0; + unsigned n = e - b; + reg_line rl; + rl.n = n; + for(T p = b; p != e; p++) { + Sx += (*p)[0]; + Sy += (*p)[1]; + Sxx += (*p)[0]*(*p)[0]; + Syy += (*p)[1]*(*p)[1]; + Sxy += (*p)[0]*(*p)[1]; + } + + rl.parallel = unit_vector(Point(n*Sxx - Sx*Sx, + n*Sxy - Sx*Sy)); + rl.normal = rot90(rl.parallel); + rl.centre = Point(Sx/n, Sy/n); + rl.Sr = rl.Srr = 0; + for(T p = b; p != e; p++) { + double r = dot(rl.parallel, (*p) - rl.centre); + rl.Sr += fabs(r); + rl.Srr += r*r; + } + return rl; +} + +void fit::linear_reg() { + reg_line rl = line_best_fit(input.begin(), + input.end()); + solution.push_back(rl.centre + dot(rl.parallel, input[0] - rl.centre)*rl.parallel); + solution.push_back(rl.centre + dot(rl.parallel, input.back() - rl.centre)*rl.parallel); +} + +void fit::simple_dp() { + const unsigned N = input.size(); + vector<unsigned> prev(N); + vector<double> penalty(N); + const double bend_pen = 100; + + for(unsigned i = 1; i < input.size(); i++) { + double mean; + double best = measure(0, i, mean); + unsigned best_prev = 0; + for(unsigned j = 1; j < i; j++) { + double err = penalty[j] + bend_pen + measure(j, i, mean); + if(err < best) { + best = err; + best_prev = j; + } + } + penalty[i] = best; + prev[i] = best_prev; + } + + unsigned i = prev.size()-1; + while(i > 0) { + solution.push_back(input[i]); + i = prev[i]; + } + solution.push_back(input[i]); + reverse(solution.begin(), solution.end()); +} + +void fit::C_endpoint() { + const unsigned N = input.size(); + + double best_mean; + double best = best_angled_line(0, N-1, angles[0], best_mean); + unsigned best_dir = 0; + for(unsigned c = 1; c < angles.size(); c++) { + double m; + double err = best_angled_line(0, N-1, angles[c], m); + if(err < best) { + best = err; + best_mean = m; + best_dir = c; + } + + } + Point dir = angles[best_dir]; + Point dirT = rot90(dir); + Point centre = dir*best_mean/N; + + solution.push_back(centre + dot(dirT, input[0] - centre)*dirT); + solution.push_back(centre + dot(dirT, input.back() - centre)*dirT); +} + +void fit::C_simple_dp() { + const unsigned N = input.size(); + + vector<int> prev(N); + vector<double> penalty(N); + vector<unsigned> dir(N); + vector<double> mean(N); + const double bend_pen = 0; + + for(unsigned i = 1; i < input.size(); i++) { + double best_mean; + double best = best_angled_line(0, i, angles[0], best_mean); + unsigned best_prev = 0; + unsigned best_dir = 0; + for(unsigned c = 1; c < angles.size(); c++) { + double m; + double err = best_angled_line(0, i, angles[c], m); + if(err < best) { + best = err; + best_mean = m; + best_dir = c; + best_prev = 0; + } + + } + for(unsigned j = 1; j < i; j++) { + for(unsigned c = 0; c < angles.size(); c++) { + double m; + if(c == dir[j]) + continue; + double err = penalty[j] + bend_pen + + best_angled_line(j, i, angles[c], m); + if(err < best) { + best = err; + best_mean = m; + best_dir = c; + best_prev = j; + } + + } + } + penalty[i] = best; + prev[i] = best_prev; + dir[i] = best_dir; + mean[i] = best_mean; + } + + prev[0] = -1; + unsigned i = prev.size()-1; + unsigned pi = i; + while(i > 0) { + Point bdir = angles[dir[i]]; + Point bdirT = rot90(bdir); + Point centre = bdir*mean[i]/N; + solution.push_back(centre + dot(bdirT, input[i] - centre)*bdirT); + solution.push_back(centre + dot(bdirT, input[pi] - centre)*bdirT); + pi = i; + i = prev[i]; + } + /*Point a = angles[dir[i]]; + Point aT = rot90(a); + solution.push_back(a*mean[i] + + dot(input[i], aT)*aT);*/ + reverse(solution.begin(), solution.end()); +} + + + + + + +class MetroMap: public Toy { +public: + vector<PointSetHandle> metro_lines; + PointHandle directions; + + bool should_draw_numbers() override { return false; } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + double slider_margin = 20; + double slider_top = 20; + double slider_bot = 200; + directions.pos[X] = slider_margin; + if (directions.pos[Y]<slider_top) directions.pos[Y] = slider_top; + if (directions.pos[Y]>slider_bot) directions.pos[Y] = slider_bot; + + unsigned num_directions = 2 + 15*(slider_bot-directions.pos[Y])/(slider_bot-slider_top); + + cairo_move_to(cr,Geom::Point(slider_margin,slider_bot)); + cairo_line_to(cr,Geom::Point(slider_margin,slider_top)); + cairo_set_line_width(cr,.5); + cairo_set_source_rgba (cr, 0., 0.3, 0., 1.); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0.5, 0, 1); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 1); + + unsigned N= paths.size(); + for(unsigned i=0;i<N;i++) { + double R,G,B; + convertHSVtoRGB(360.*double(i)/double(N),1,0.75,R,G,B); + metro_lines[i].rgb[0] = R; + metro_lines[i].rgb[1] = G; + metro_lines[i].rgb[2] = B; + cairo_set_source_rgba (cr, R, G, B, 0.8); + fit f(metro_lines[i].pts); + f.schematised_merging(num_directions); + f.draw(cr); + cairo_stroke(cr); + } + cairo_set_source_rgba (cr, 0., 0., 0, 1); + { + PangoLayout* layout = pango_cairo_create_layout (cr); + pango_layout_set_text(layout, + notify->str().c_str(), -1); + + PangoFontDescription *font_desc = pango_font_description_new(); + pango_font_description_set_family(font_desc, "Sans"); + const unsigned size_px = 10; + pango_font_description_set_absolute_size(font_desc, size_px * 1024.0); + pango_layout_set_font_description(layout, font_desc); + PangoRectangle logical_extent; + pango_layout_get_pixel_extents(layout, + NULL, + &logical_extent); + cairo_move_to(cr, 0, height-logical_extent.height); + pango_cairo_show_layout(cr, layout); + } + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void first_time(int argc, char** argv) override { + string location_file_name("data/london-locations.csv"); + string path_file_name("data/london.txt"); + if(argc > 2) { + location_file_name = argv[1]; + path_file_name = argv[2]; + } + cout << location_file_name << ", " << path_file_name << endl; + parse_data(paths, location_file_name, path_file_name); + for(auto & path : paths) { + metro_lines.emplace_back(); + for(auto & j : path) { + metro_lines.back().push_back(j); + } + } + for(auto & metro_line : metro_lines) { + handles.push_back(&metro_line); + } + handles.push_back(&directions); + } + + MetroMap() { + } + +}; + + + + + +int main(int argc, char **argv) { + init(argc, argv, new MetroMap()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + diff --git a/src/toys/minsb2d-solver.cpp b/src/toys/minsb2d-solver.cpp new file mode 100644 index 0000000..f2df9d9 --- /dev/null +++ b/src/toys/minsb2d-solver.cpp @@ -0,0 +1,376 @@ +#include <2geom/sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+
+//see a sb2d as an sb of u with coef in sbasis of v.
+void
+u_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.vs, Linear());
+ b = SBasis(f.vs, Linear());
+ for (unsigned v=0; v<f.vs; v++){
+ a[v] = Linear(f.index(deg,v)[0], f.index(deg,v)[2]);
+ b[v] = Linear(f.index(deg,v)[1], f.index(deg,v)[3]);
+ }
+}
+void
+v_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.us, Linear());
+ b = SBasis(f.us, Linear());
+ for (unsigned u=0; u<f.us; u++){
+ a[u] = Linear(f.index(deg,u)[0], f.index(deg,u)[1]);
+ b[u] = Linear(f.index(deg,u)[2], f.index(deg,u)[3]);
+ }
+}
+
+
+
+//TODO: implement sb2d algebra!!
+SBasis2d y_x2(){
+ SBasis2d result(Linear2d(0,-1,1,0));
+ result.push_back(Linear2d(1,1,1,1));
+ result.us = 2;
+ result.vs = 1;
+ return result;
+}
+
+SBasis2d x2_plus_y2_1(){
+/*TODO: implement sb2d algebra!!
+ SBasis2d one(Linear2d(1,1,1,1));
+ SBasis2d u(Linear2d(0,1,0,1));
+ SBasis2d v(Linear2d(0,0,1,1));
+ return(u*u+v*v-one);
+*/
+ SBasis2d result(Linear2d(-1,0,0,1));//x+y-1
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(-4,-1,-1,-1));
+ result.push_back(Linear2d(0,0,0,0));
+ result.us = 2;
+ result.vs = 2;
+ return result;
+}
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr,
+ Piecewise<SBasis> const &x,
+ Piecewise<SBasis> const &y,
+ Piecewise<SBasis> const &z, Frame frame){
+
+ Piecewise<SBasis> xx = partition(x,y.cuts);
+ Piecewise<SBasis> xxx = partition(xx,z.cuts);
+ Piecewise<SBasis> yyy = partition(y,xxx.cuts);
+ Piecewise<SBasis> zzz = partition(z,xxx.cuts);
+
+ for (unsigned i=0; i<xxx.size(); i++){
+ plot3d(cr, xxx[i], yyy[i], zzz[i], frame);
+ }
+}
+
+void
+plot3d(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ D2<SBasis> seg(SBasis(0.,1.), SBasis(i*1./NbRays, i*1./NbRays));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ for (int i=0; i<NbRays; i++){
+ D2<SBasis> seg(SBasis(i*1./NbRays, i*1./NbRays), SBasis(0.,1.));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+}
+
+void
+plot3d_top(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ for(int j=0; j<2; j++){
+ D2<SBasis> seg;
+ if (j==0){
+ seg = D2<SBasis>(SBasis(0.,1.), SBasis(i*1./NbRays, i*1./NbRays));
+ }else{
+ seg = D2<SBasis>(SBasis(i*1./NbRays,i*1./NbRays), SBasis(0.,1.));
+ }
+ SBasis f_on_seg = compose(f,seg);
+ std::vector<double> rts = roots(f_on_seg);
+ if (rts.size()==0||rts.back()<1) rts.push_back(1.);
+ double t1,t0 = 0;
+ for (unsigned i=(rts.front()<=0?1:0); i<rts.size(); i++){
+ t1 = rts[i];
+ if (f_on_seg((t0+t1)/2)>0)
+ plot3d(cr,seg[X](Linear(t0,t1)),seg[Y](Linear(t0,t1)),f_on_seg(Linear(t0,t1)),frame);
+ t0=t1;
+ }
+ //plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ }
+}
+#include <gsl/gsl_multimin.h>
+
+class Sb2dSolverToy: public Toy {
+public:
+ PointSetHandle hand;
+ Sb2dSolverToy() {
+ handles.push_back(&hand);
+ }
+
+ class bits_n_bobs{
+ public:
+ SBasis2d * ff;
+ Point A, B;
+ Point dA, dB;
+ };
+ static double
+ my_f (const gsl_vector *v, void *params)
+ {
+ double x, y;
+ bits_n_bobs* bnb = (bits_n_bobs *)params;
+
+ x = gsl_vector_get(v, 0);
+ y = gsl_vector_get(v, 1);
+ Bezier b0(bnb->B[0], bnb->B[0]+bnb->dB[0]*x, bnb->A[0]+bnb->dA[0]*y, bnb->A[0]);
+ Bezier b1(bnb->B[1], bnb->B[1]+bnb->dB[1]*x, bnb->A[1]+bnb->dA[1]*y, bnb->A[1]);
+
+ D2<SBasis> zeroset(b0.toSBasis(), b1.toSBasis());
+
+ SBasis comp = compose((*bnb->ff),zeroset);
+ Interval bounds = *bounds_fast(comp);
+ double error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+ //printf("error = %g %g %g\n", bounds.max(), bounds.min(), error);
+ return error*error;
+ }
+
+
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+/*
+ SBasis2d f = y_x2();
+ D2<SBasis> true_solution(Linear(0,1),Linear(0,1));
+ true_solution[Y].push_back(Linear(-1,-1));
+ SBasis zero = SBasis(Linear(0.));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+*/
+
+ SBasis2d f = x2_plus_y2_1();
+ D2<Piecewise<SBasis> > true_solution;
+ true_solution[X] = cos(SBasis(Linear(0,3.141592/2)));
+ true_solution[Y] = sin(SBasis(Linear(0,3.141592/2)));
+ Piecewise<SBasis> zero = Piecewise<SBasis>(SBasis(Linear(0.)));
+ //Geom::Point A(cos(tA*M_PI/2), sin(tA*M_PI/2));// = true_solution(tA);
+ //Geom::Point B(cos(tB*M_PI/2), sin(tB*M_PI/2));// = true_solution(tB);
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+ Point dA(-sin(tA*M_PI/2), cos(tA*M_PI/2));
+ Geom::Point dB(-sin(tB*M_PI/2), cos(tB*M_PI/2));
+ SBasis2d dfdu = partial_derivative(f, 0);
+ SBasis2d dfdv = partial_derivative(f, 1);
+ Geom::Point dfA(dfdu.apply(A[X],A[Y]),dfdv.apply(A[X],A[Y]));
+ Geom::Point dfB(dfdu.apply(B[X],B[Y]),dfdv.apply(B[X],B[Y]));
+ dA = rot90(dfA);
+ dB = rot90(dfB);
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+ plot3d_top(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+
+ plot3d(cr, true_solution[X], true_solution[Y], zero, frame);
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+ double error;
+ for(int degree = 2; degree < 2; degree++) {
+ D2<SBasis> zeroset = sb2dsolve(f,A,B,degree);
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasis comp = compose(f,zeroset);
+ plot3d(cr, zeroset[X], zeroset[Y], comp, frame);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, 1.);
+ cairo_stroke(cr);
+ //Fix Me: bounds_exact does not work here?!?!
+ Interval bounds = *bounds_fast(comp);
+ error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+ }
+ if (1) {
+
+ bits_n_bobs par = {&f, A, B, dA, dB};
+ bits_n_bobs* bnb = ∥
+ std::cout << f[0] << "= initial f \n";
+ const gsl_multimin_fminimizer_type *T =
+ gsl_multimin_fminimizer_nmsimplex;
+ gsl_multimin_fminimizer *s = NULL;
+ gsl_vector *ss, *x;
+ gsl_multimin_function minex_func;
+
+ size_t iter = 0;
+ int status;
+ double size;
+
+ /* Starting point */
+ x = gsl_vector_alloc (2);
+ gsl_vector_set (x, 0, 0.552); // magic number for quarter circle
+ gsl_vector_set (x, 1, 0.552);
+
+ /* Set initial step sizes to 1 */
+ ss = gsl_vector_alloc (2);
+ gsl_vector_set_all (ss, 0.1);
+
+ /* Initialize method and iterate */
+ minex_func.n = 2;
+ minex_func.f = &my_f;
+ minex_func.params = (void *)∥
+
+ s = gsl_multimin_fminimizer_alloc (T, 2);
+ gsl_multimin_fminimizer_set (s, &minex_func, x, ss);
+
+ do
+ {
+ iter++;
+ status = gsl_multimin_fminimizer_iterate(s);
+
+ if (status)
+ break;
+
+ size = gsl_multimin_fminimizer_size (s);
+ status = gsl_multimin_test_size (size, 1e-7);
+
+ if (status == GSL_SUCCESS)
+ {
+ printf ("converged to minimum at\n");
+ }
+
+ }
+ while (status == GSL_CONTINUE && iter < 100);
+ printf ("%5lu %g %gf f() = %g size = %g\n",
+ iter,
+ gsl_vector_get (s->x, 0),
+ gsl_vector_get (s->x, 1),
+ s->fval, size);
+ {
+ double x = gsl_vector_get(s->x, 0);
+ double y = gsl_vector_get(s->x, 1);
+ Bezier b0(bnb->B[0], bnb->B[0]+bnb->dB[0]*x, bnb->A[0]+bnb->dA[0]*y, bnb->A[0]);
+ Bezier b1(bnb->B[1], bnb->B[1]+bnb->dB[1]*x, bnb->A[1]+bnb->dA[1]*y, bnb->A[1]);
+
+ D2<SBasis> zeroset(b0.toSBasis(), b1.toSBasis());
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasis comp = compose(f,zeroset);
+ plot3d(cr, zeroset[X], zeroset[Y], comp, frame);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, 1.);
+ cairo_stroke(cr);
+ //Fix Me: bounds_exact does not work here?!?!
+ Interval bounds = *bounds_fast(comp);
+ error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+
+ }
+
+ gsl_vector_free(x);
+ gsl_vector_free(ss);
+ gsl_multimin_fminimizer_free (s);
+ }
+ *notify << "Gray: f-graph and true solution,\n";
+ *notify << "Red: solver solution,\n";
+ *notify << "Purple: value of f over solver solution.\n";
+ *notify << " error: "<< error <<".\n";
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb2dSolverToy());
+ return 0;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
+
diff --git a/src/toys/nasty.svg b/src/toys/nasty.svg new file mode 100644 index 0000000..4457044 --- /dev/null +++ b/src/toys/nasty.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.45" + sodipodi:modified="true"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35" + inkscape:cx="375" + inkscape:cy="481.62572" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="910" + inkscape:window-height="626" + inkscape:window-x="5" + inkscape:window-y="49" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 387.63906,267.9784 L 106.72661,469.82569 L 106.72661,267.9784 L 387.63906,469.82569 L 387.63906,267.9784 z " + id="path2160" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 540.4278,232.25903 C 440.92573,232.25903 646.18067,452.19982 540.4278,452.19982 C 437.36839,452.19982 639.73953,232.25903 540.4278,232.25903 z " + id="path2162" + sodipodi:nodetypes="csz" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 282.85714,578.07647 C 74.285714,832.36218 165.33193,1033.3172 288.57143,900.93361 C 410.47619,769.98377 73.333332,831.45467 282.85714,578.07647 z " + id="path2182" + sodipodi:nodetypes="czc" /> + <path + id="path2186" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 548.57143,662.36218 C 615.72948,662.36218 625.35777,906.64794 728.57143,906.64794 C 832.06154,906.64794 637.327,662.36218 548.57143,662.36218 C 459.95393,662.36218 291.30957,906.64794 428.57143,906.64794 C 534.44005,906.64794 481.41338,662.36218 548.57143,662.36218 z " + sodipodi:nodetypes="czzsz" /> + </g> +</svg> diff --git a/src/toys/nearest-times.cpp b/src/toys/nearest-times.cpp new file mode 100644 index 0000000..66803d6 --- /dev/null +++ b/src/toys/nearest-times.cpp @@ -0,0 +1,258 @@ +/* + * Nearest Points Toy + * + * Authors: + * Nathan Hurst <njh at njhurst.com> + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework.h> + + +using namespace Geom; + + +class np_finder +{ +public: + np_finder(cairo_t* _cr, D2<SBasis> const& _c1, D2<SBasis> const& _c2) + : cr(_cr), c1(_c1), c2(_c2) + { + dc1 = derivative(c1); + dc2 = derivative(c2); + cd1 = dot(c1,dc1); + cd2 = dot(c2,dc2); + dsq = 10e30; + } + + void operator() () + { + nearest_times_impl(0.5, 0, 1); + d = sqrt(dsq); + } + + Point firstPoint() const + { + return p1; + } + + Point secondPoint() const + { + return p2; + } + + double firstValue() const + { + return t1; + } + + double secondValue() const + { + return t2; + } + + double distance() const + { + return d; + } + +private: + void nearest_times_impl( double t, double from = 0, double to = 1 ) + { + //std::cerr << "[" << from << "," << to << "] t: " << t << std::endl; + + double st = t, et = t; + std::pair<double, double> npc = loc_nearest_times(t, from, to); + //std::cerr << "(" << npc.first << "," << npc.second << ")" << std::endl; + if ( npc.second != -1 && dsq > L2sq(c1(npc.first) - c2(npc.second)) ) + { + t1 = npc.first; + t2 = npc.second; + p1 = c1(t1); + p2 = c2(t2); + dsq = L2sq(p1 - p2); + } + if (npc.first < t) + st = npc.first; + else + et = npc.first; + //std::cerr << "[" << st << "," << et << "]" << std::endl; + double d1 = std::fabs(st - from); + double d2 = std::fabs(to - et); + if ( d1 > EPSILON ) + nearest_times_impl(from + d1/2, from, st); + if ( d2 > EPSILON ) + nearest_times_impl(et + d2/2, et, to); + } + + std::pair<double, double> + loc_nearest_times( double t, double from = 0, double to = 1 ) + { + unsigned int i = 0; + std::pair<double, double> np(-1,-1); + std::pair<double, double> npf(from, -1); + std::pair<double, double> npt(to, -1); + double ct = t; + double pt = -1; + double s; + //cairo_set_source_rgba(cr, 1/(t+1), t*t, t, 1.0); + //cairo_move_to(cr, c1(t)); + while( !are_near(ct, pt) ) + { + ++i; + pt = ct; + s = nearest_time(c1(ct), c2, dc2, cd2); + //std::cerr << "s: " << s << std::endl; + //cairo_line_to(cr, c2(s)); + ct = nearest_time(c2(s), c1, dc1, cd1, from, to); + //std::cerr << "t: " << t1 << std::endl; + //cairo_line_to(cr, c1(ct)); + if ( ct < from ) return npf; + if ( ct > to ) return npt; + } + //std::cerr << "\n \n"; + //std::cerr << "iterations: " << i << std::endl; + cairo_stroke(cr); + np.first = ct; + np.second = s; + return np; + } + + double nearest_time( Point const& p, D2<SBasis> const&c, D2<SBasis> const& dc, SBasis const& cd, double from = 0, double to = 1 ) + { + D2<SBasis> sbc = c - p; + SBasis dd = cd - dotp(p, dc); + std::vector<double> zeros = roots(dd); + double closest = from; + double distsq = L2sq(sbc(from)); + for ( unsigned int i = 0; i < zeros.size(); ++i ) + { + if ( distsq > L2sq(sbc(zeros[i])) ) + { + closest = zeros[i]; + distsq = L2sq(sbc(closest)); + } + } + if ( distsq > L2sq(sbc(to)) ) + closest = to; + return closest; + } + + SBasis dotp(Point const& p, D2<SBasis> const& c) + { + SBasis d; + d.resize(c[X].size()); + for ( unsigned int i = 0; i < c[0].size(); ++i ) + { + for( unsigned int j = 0; j < 2; ++j ) + d[i][j] = p[X] * c[X][i][j] + p[Y] * c[Y][i][j]; + } + return d; + } + +private: + static const Coord EPSILON = 10e-3; + cairo_t* cr; + D2<SBasis> const& c1, c2; + D2<SBasis> dc1, dc2; + SBasis cd1, cd2; + double t1, t2, d, dsq; + Point p1, p2; +}; + + + + +class NearestPoints : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) + { + cairo_set_line_width (cr, 0.2); + D2<SBasis> A = handles_to_sbasis(handles.begin(), A_bez_ord-1); + cairo_d2_sb(cr, A); + D2<SBasis> B = handles_to_sbasis(handles.begin()+A_bez_ord, B_bez_ord-1); + cairo_d2_sb(cr, B); + + + np_finder np(cr, A, B); + np(); + cairo_move_to(cr, np.firstPoint()); + cairo_line_to(cr, np.secondPoint()); + cairo_stroke(cr); + //std::cerr << "np: (" << np.firstValue() << "," << np.secondValue() << ")" << std::endl; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + NearestPoints(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + unsigned int total_handles = A_bez_ord + B_bez_ord; + for ( unsigned int i = 0; i < total_handles; ++i ) + handles.push_back(Geom::Point(uniform()*400, uniform()*400)); + } + + private: + unsigned int A_bez_ord; + unsigned int B_bez_ord; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=8; + unsigned int B_bez_ord=5; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + init( argc, argv, new NearestPoints(A_bez_ord, B_bez_ord)); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/nearest-times2.cpp b/src/toys/nearest-times2.cpp new file mode 100644 index 0000000..04cd9e6 --- /dev/null +++ b/src/toys/nearest-times2.cpp @@ -0,0 +1,313 @@ +/* + * Nearest Points Toy 2 + * + * Authors: + * Nathan Hurst <njh at njhurst.com> + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework.h> + +#include <algorithm> + + +using namespace Geom; + + +class np_finder +{ +public: + np_finder(cairo_t* _cr, D2<SBasis> const& _c1, D2<SBasis> const& _c2) + : cr(_cr), c1(_c1), c2(_c2) + { + dc1 = derivative(c1); + dc2 = derivative(c2); + cd1 = dot(c1,dc1); + cd2 = dot(c2,dc2); + dsq = 10e30; + + Piecewise<SBasis> k1 = curvature(c1, EPSILON); + Piecewise<SBasis> k2 = curvature(c2, EPSILON); + Piecewise<SBasis> dk1 = derivative(k1); + Piecewise<SBasis> dk2 = derivative(k2); + std::vector<double> k1_roots = roots(k1); + std::vector<double> k2_roots = roots(k2); + std::vector<double> dk1_roots = roots(dk1); + std::vector<double> dk2_roots = roots(dk2); + tlist.clear(); + tlist.resize(k1_roots.size() + k2_roots.size() + dk1_roots.size() + dk2_roots.size() + 4); + tlist.push_back(0); + tlist.insert(tlist.end(), dk1_roots.begin(), dk1_roots.end()); + tlist.insert(tlist.end(), k1_roots.begin(), k1_roots.end()); +// std::cerr << "dk1 roots: "; +// for ( unsigned int i = 0; i < dk1_roots.size(); ++i ) +// { +// std::cerr << dk1_roots[i] << " "; +// } +// std::cerr << "\n"; + for ( unsigned int i = 0; i < k2_roots.size(); ++i ) + { + tlist.push_back(nearest_time(c2(k2_roots[i]), c1, dc1, cd1)); + } + + for ( unsigned int i = 0; i < dk2_roots.size(); ++i ) + { + tlist.push_back(nearest_time(c2(dk2_roots[i]), c1, dc1, cd1)); + } + tlist.push_back(nearest_time(c2(0), c1, dc1, cd1)); + tlist.push_back(nearest_time(c2(1), c1, dc1, cd1)); + tlist.push_back(1); + std::sort(tlist.begin(), tlist.end()); + std::vector<double>::iterator pos + = std::unique(tlist.begin(), tlist.end(), are_near_() ); + if (pos != tlist.end()) + { + tlist.erase(pos, tlist.end()); + } + for( unsigned int i = 0; i < tlist.size(); ++i ) + { + draw_circ(cr, c1(tlist[i]) ); + } + } + + void operator() () + { + //nearest_times_impl( tlist.size() / 2, 0, tlist.size() - 1 ); + nearest_times_impl(); + d = sqrt(dsq); + } + + Point firstPoint() const + { + return p1; + } + + Point secondPoint() const + { + return p2; + } + + double firstValue() const + { + return t1; + } + + double secondValue() const + { + return t2; + } + + double distance() const + { + return d; + } + +private: + void nearest_times_impl() + { + double t; + double from = tlist[0]; + double to; + for ( unsigned int i = 1; i < tlist.size(); ++i ) + { + to = tlist[i]; + t = from + (to - from) / 2 ; + std::pair<double, double> npc = loc_nearest_times(t, from, to); + if ( npc.second != -1 && dsq > L2sq(c1(npc.first) - c2(npc.second)) ) + { + t1 = npc.first; + t2 = npc.second; + p1 = c1(t1); + p2 = c2(t2); + dsq = L2sq(p1 - p2); + } + from = tlist[i]; + } + } + + std::pair<double, double> + loc_nearest_times( double t, double from = 0, double to = 1 ) + { + //std::cerr << "[" << from << "," << to << "] t: " << t << std::endl; + unsigned int i = 0; + std::pair<double, double> np(-1,-1); + std::pair<double, double> npf(from, -1); + std::pair<double, double> npt(to, -1); + double ct = t; + double pt = -1; + double s; + //cairo_set_source_rgba(cr, 1/(t+1), t*t, t, 1.0); + cairo_move_to(cr, c1(t)); + while( !are_near(ct, pt) ) + { + ++i; + pt = ct; + s = nearest_time(c1(ct), c2, dc2, cd2); + //std::cerr << "s: " << s << std::endl; + //cairo_line_to(cr, c2(s)); + ct = nearest_time(c2(s), c1, dc1, cd1, from, to); + //std::cerr << "t: " << ct << std::endl; + //cairo_line_to(cr, c1(ct)); + //std::cerr << "d(pt, ct) = " << std::fabs(ct - pt) << std::endl; + if ( ct < from ) return npf; + if ( ct > to ) return npt; + } + //std::cerr << "\n \n"; + std::cerr << "iterations: " << i << std::endl; + //cairo_move_to(cr, c1(ct)); + //cairo_line_to(cr, c2(s)); + cairo_stroke(cr); + np.first = ct; + np.second = s; + return np; + } + + double nearest_time( Point const& p, D2<SBasis> const&c, D2<SBasis> const& dc, SBasis const& cd, double from = 0, double to = 1 ) + { + D2<SBasis> sbc = c - p; + SBasis dd = cd - dotp(p, dc); + std::vector<double> zeros = roots(dd); + double closest = from; + double distsq = L2sq(sbc(from)); + for ( unsigned int i = 0; i < zeros.size(); ++i ) + { + if ( distsq > L2sq(sbc(zeros[i])) ) + { + closest = zeros[i]; + distsq = L2sq(sbc(closest)); + } + } + if ( distsq > L2sq(sbc(to)) ) + closest = to; + return closest; + } + + SBasis dotp(Point const& p, D2<SBasis> const& c) + { + SBasis d; + d.resize(c[X].size()); + for ( unsigned int i = 0; i < c[0].size(); ++i ) + { + for( unsigned int j = 0; j < 2; ++j ) + d[i][j] = p[X] * c[X][i][j] + p[Y] * c[Y][i][j]; + } + return d; + } + + struct are_near_ + { + bool operator() (double x, double y, double eps = Geom::EPSILON ) + { + return are_near(x, y, eps); + } + }; + +private: + static const Coord EPSILON = 10e-5; + cairo_t* cr; + D2<SBasis> const& c1, c2; + D2<SBasis> dc1, dc2; + SBasis cd1, cd2; + double t1, t2, d, dsq; + Point p1, p2; + std::vector<double> tlist; +}; + + + + +class NearestPoints : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) + { + cairo_set_line_width (cr, 0.2); + D2<SBasis> A = handles_to_sbasis(handles.begin(), A_bez_ord-1); + cairo_d2_sb(cr, A); + D2<SBasis> B = handles_to_sbasis(handles.begin()+A_bez_ord, B_bez_ord-1); + cairo_d2_sb(cr, B); + + + np_finder np(cr, A, B); + np(); + cairo_move_to(cr, np.firstPoint()); + cairo_line_to(cr, np.secondPoint()); + cairo_stroke(cr); + //std::cerr << "np: (" << np.firstValue() << "," << np.secondValue() << ")" << std::endl; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + NearestPoints(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + unsigned int total_handles = A_bez_ord + B_bez_ord; + for ( unsigned int i = 0; i < total_handles; ++i ) + handles.push_back(Geom::Point(uniform()*400, uniform()*400)); + } + + private: + unsigned int A_bez_ord; + unsigned int B_bez_ord; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=8; + unsigned int B_bez_ord=5; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + init( argc, argv, new NearestPoints(A_bez_ord, B_bez_ord)); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/normal-bundle.cpp b/src/toys/normal-bundle.cpp new file mode 100644 index 0000000..efe7536 --- /dev/null +++ b/src/toys/normal-bundle.cpp @@ -0,0 +1,230 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +class NormalBundle : public std::vector<D2<SBasis2d> >{ +public: + vector<double> lengths; + NormalBundle(){lengths.push_back(0.);} + void setBase(D2<SBasis> const &B, double tol); + void draw(cairo_t* cr, unsigned NbSections =5,unsigned NbFibre=5); +}; +vector<D2<SBasis> > compose(NormalBundle const &NB, + D2<SBasis> const &Binit, + Geom::Point Origin=Geom::Point(0,0)); + +//-------------------------------------------- + +void SBasis1d_to_2d(D2<SBasis> C0, + D2<SBasis> C1, + D2<SBasis2d> &S){ + for(unsigned dim = 0; dim < 2; dim++) { +//**** C0 and C1 should have the same size... + for (unsigned i=C0[dim].size();i<C1[dim].size(); i++) + C0[dim].push_back(Linear(0)); + for (unsigned i=C1[dim].size();i<C0[dim].size(); i++) + C1[dim].push_back(Linear(0)); + S[dim].clear(); + S[dim].us = C0[dim].size(); + S[dim].vs = 1; + for(unsigned v = 0; v < S[dim].vs; v++) + for(unsigned u = 0; u < S[dim].us; u++) + S[dim].push_back(Linear2d(C0[dim][u][0],C0[dim][u][1], + C1[dim][u][0],C1[dim][u][1])); + } +} + +void NormalBundle::setBase(D2<SBasis> const &B, double tol=0.01) { + + D2<SBasis> dB = derivative(B); + vector<double> cuts; + Piecewise<D2<SBasis> > unitV=unitVector(dB); + + //TODO: clean this up, use arc_length_parametrization... + cuts=unitV.cuts; + double t0=0,t1,L=0; + for(unsigned i=1;i<cuts.size();i++){ + t1=cuts[i]; + D2<SBasis> subB=compose(B,Linear(t0,t1)); + D2<SBasis2d> S; + SBasis1d_to_2d(subB, subB+rot90(unitV[i]), S); + push_back(S); + + SBasis s=integral(dot(compose(dB,Linear(t0,t1)),unitV[i])); + L+=(s(1)-s(0))*(t1-t0); + lengths.push_back(L); + + t0=t1; + } +} + +void NormalBundle::draw(cairo_t *cr, unsigned NbLi, unsigned NbCol) { + D2<SBasis> B; + vector<D2<SBasis> > tB; + //Geom::Point Seg[2]; + B[1]=Linear(-100,100); + double width=*(lengths.rbegin()); + if (NbCol>0) + for(unsigned ui = 0; ui <= NbCol; ui++) { + B[0]=Linear(ui*width/NbCol); + tB = compose(*this,B); + if (tB.size()>0) cairo_d2_sb(cr, tB[0]); + } + + B[0]=SBasis(Linear(0,1)); + for(unsigned ui = 0; ui <= NbLi; ui++) { + B[1]=Linear(-100+ui*200/NbLi); + for(unsigned i = 0; i <size(); i++) { + D2<SBasis> section=composeEach((*this)[i],B); + cairo_d2_sb(cr, section); + } + } +} + + +vector<D2<SBasis> > compose(NormalBundle const &NB, + D2<SBasis> const &Binit, + Geom::Point Origin){ + vector<D2<SBasis> > result; + D2<SBasis> B=Binit; + D2<SBasis> Bcut; + vector<double> Roots; + std::map<double,unsigned> Cuts; + unsigned idx; + + B = B + (-Origin); + + //--Find intersections with fibers over segment ends. + for(unsigned i=0; i<=NB.size();i++){ + Roots=roots(B[0]); + for (vector<double>::iterator root=Roots.begin(); + root!=Roots.end();root++) + Cuts[*root]=i; + if((Cuts.count(0.)==0) and + ((B[0].valueAt(0.)<=0) or i==NB.size())) + Cuts[0.]=i; + if((Cuts.count(1.)==0) and + ((B[0].valueAt(1.)<=0) or i==NB.size())) + Cuts[1.]=i; + if (i<NB.size()) + B[0]-=(NB.lengths[i+1]-NB.lengths[i]); + } + B[0]+=*(NB.lengths.rbegin()); + + //-- Compose each piece with the relevant sbasis2d. + // TODO: use a uniform parametrization of the base. + std::map<double,unsigned>::iterator cut=Cuts.begin(); + std::map<double,unsigned>::iterator next=cut; next++; + while(next!=Cuts.end()){ + double t0=(*cut).first; + unsigned idx0=(*cut).second; + double t1=(*next).first; + unsigned idx1=(*next).second; + if (idx0 != idx1){ + idx=std::min(idx0,idx1); + } else if(B[0]((t0+t1)/2) < NB.lengths[idx0]) { // we have a left 'bump', + idx=idx0-1; + } else if(B[0]((t0+t1)/2) == NB.lengths[idx0]) { //we have a vertical segment!... + idx= (idx0==NB.size())? idx0-1:idx0; + } else //we have a right 'bump'. + idx=idx0; + + //--trim version... + if (idx>=0 and idx<NB.size()) { + for (unsigned dim=0;dim<2;dim++) + Bcut[dim]=compose(B[dim], Linear(t0,t1)); + double width=NB.lengths[idx+1]-NB.lengths[idx]; + Bcut[0]=compose(Linear(-NB.lengths[idx]/width, + (1-NB.lengths[idx])/width),Bcut[0]); + Bcut = composeEach(NB[idx], Bcut); + result.push_back(Bcut); + } + cut++; + next++; + } + return(result); +} + + + +class NormalBundleToy: public Toy { + PointSetHandle B_handle; + PointSetHandle P_handle; + PointHandle O_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + D2<SBasis> B = B_handle.asBezier(); + D2<SBasis> P = P_handle.asBezier(); + Geom::Point O = O_handle.pos; + + NormalBundle NBdle; + NBdle.setBase(B); + Geom::Point Oo(O[0]+*(NBdle.lengths.rbegin()),O[1]); + + vector<D2<SBasis> > Q=compose(NBdle,P,O); + + cairo_set_line_width (cr, 0.5); + //Base lines + cairo_set_source_rgba (cr, 0.9, 0., 0., 1); + cairo_d2_sb(cr, B); + draw_line_seg(cr, O, Oo); + cairo_stroke(cr); + + //Sections + cairo_set_source_rgba (cr, 0, 0, 0.9, 1); + cairo_d2_sb(cr, P); + for (unsigned i=0;i<Q.size();i++){ + cairo_d2_sb(cr, Q[i]); + } + cairo_stroke(cr); + + //Normal bundle + cairo_set_source_rgba (cr, 0., 0., 0., 1); + NBdle.draw(cr,3,5); + cairo_stroke(cr); + + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + NormalBundleToy(){ + if(handles.empty()) { + handles.push_back(&B_handle); + handles.push_back(&P_handle); + handles.push_back(&O_handle); + for(unsigned i = 0; i < 4; i++) + B_handle.push_back(200+50*i,400); + for(unsigned i = 0; i < 4; i++) + P_handle.push_back(100+uniform()*400, + 150+uniform()*100); + O_handle.pos = Geom::Point(200,200); + } + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new NormalBundleToy); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + + diff --git a/src/toys/offset-toy.cpp b/src/toys/offset-toy.cpp new file mode 100644 index 0000000..4b3e617 --- /dev/null +++ b/src/toys/offset-toy.cpp @@ -0,0 +1,156 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-math.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/path-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <sstream> + +using std::vector; +using namespace Geom; +using namespace std; + +// TODO: +// use path2 +// replace Ray stuff with path2 line segments. + +//----------------------------------------------- + +static void +plot_offset(cairo_t* cr, D2<SBasis> const &M, + Coord offset = 10, + unsigned NbPts = 10){ + D2<SBasis> dM = derivative(M); + for (unsigned i = 0;i < NbPts;i++){ + double t = i*1./NbPts; + Point V = dM(t); + V = offset*rot90(unit_vector(V)); + draw_handle(cr, M(t)+V); + } +} + +static void plot(cairo_t* cr, Piecewise<SBasis> const &f,double vscale=1){ + D2<Piecewise<SBasis> > plot; + plot[1]=-f*vscale; + plot[1]+=450; + + plot[0].cuts.push_back(f.cuts.front()); + plot[0].cuts.push_back(f.cuts.back()); + plot[0].segs.emplace_back(Linear(150,450)); + + for (unsigned i=1; i<f.size(); i++){ + double t=f.cuts[i],ft=f.segs[i].at0(); + cairo_move_to(cr, Point(150+t*300, 450)); + cairo_line_to(cr, Point(150+t*300, 450-ft*vscale)); + } + cairo_d2_pw_sb(cr, plot); +} + + + +class OffsetTester: public Toy { + PointSetHandle psh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + D2<SBasis> B = psh.asBezier(); + *notify << "Curve offset:" << endl; + *notify << " -blue: pointwise plotted offset," << endl; + *notify << " -red: rot90(unitVector(derivative(.)))+rays at cut" << endl; + *notify << " -gray: cos(atan2),sin(atan2)" << endl; + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + Coord offset = -100; + plot_offset(cr,B,offset,11); + cairo_set_source_rgba (cr, 0, 0, 1, 1); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + Piecewise<D2<SBasis> > n = rot90(unitVector(derivative(B))); + Piecewise<D2<SBasis> > offset_curve = Piecewise<D2<SBasis> >(B)+n*offset; + PathVector offset_path = path_from_piecewise(offset_curve, 0.1); + + cairo_path(cr, offset_path); + cairo_stroke(cr); + for(const auto & pi : offset_path) { + Crossings cs = self_crossings(pi); + for(auto & c : cs) { + draw_cross(cr, pi.pointAt(c.ta)); + std::stringstream s; + Point Pa = pi.pointAt(c.ta); + Point Pb = pi.pointAt(c.tb); + s << L1(Pa - Pb) << std::endl; + std::string ss = s.str(); + draw_text(cr, Pa+Point(3,3), ss.c_str(), false, "Serif 6"); + + } + } + + for(unsigned i = 0; i < n.size()+1;i++){ + Point ptA=B(n.cuts[i]), ptB; + if (i==n.size()) + ptB=ptA+n.segs[i-1].at1()*offset; + else + ptB=ptA+n.segs[i].at0()*offset; + cairo_move_to(cr,ptA); + cairo_line_to(cr,ptB); + cairo_set_source_rgba (cr, 1, 0, 0, 1); + cairo_stroke(cr); + } + + Piecewise<SBasis> alpha = atan2(derivative(B),1e-2,3); + plot(cr,alpha,75/M_PI); + + Piecewise<D2<SBasis> >n2 = sectionize(D2<Piecewise<SBasis> >(sin(alpha),cos(alpha))); + cairo_pw_d2_sb(cr,Piecewise<D2<SBasis> >(B)+n2*offset*.9); + cairo_set_source_rgba (cr, 0.5, 0.2, 0.5, 0.8); + cairo_stroke(cr); + + Piecewise<SBasis> k = curvature(B); + cairo_pw_d2_sb(cr,Piecewise<D2<SBasis> >(B)+k*n*100); + cairo_set_source_rgba (cr, 0.5, 0.2, 0.5, 0.8); + cairo_stroke(cr); + + *notify << "Total length: " << length(B) << endl; + *notify << "(nb of cuts of unitVector: " << n.size()-1 << ")" << endl; + *notify << "(nb of cuts of cos,sin(atan2): " << n2.size()-1 << ")" << endl; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + OffsetTester(int order) { + handles.push_back(&psh); + for(int i = 0; i < order; i++) + psh.push_back(200+50*i,300+70*uniform()); + } +}; + +int main(int argc, char **argv) { + int A_bez_ord = 6; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + init(argc, argv, new OffsetTester(A_bez_ord)); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/pair-intersect.cpp b/src/toys/pair-intersect.cpp new file mode 100644 index 0000000..9cc01ca --- /dev/null +++ b/src/toys/pair-intersect.cpp @@ -0,0 +1,147 @@ +#include <2geom/basic-intersection.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/path-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +class PairIntersect: public Toy { + PointSetHandle A_handles; + PointSetHandle B_handles; + std::vector<Toggle> toggles; + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + draw_toggles(cr, toggles); + cairo_save(cr); + cairo_set_source_rgba(cr, 0, 0, 0, 1.0); + cairo_set_line_width (cr, 0.5); + D2<SBasis> A = A_handles.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.0, 0, 0.8, 1.0); + cairo_set_line_width (cr, 0.5); + D2<SBasis> B = B_handles.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + cairo_save(cr); + cairo_set_source_rgba(cr, 0, 0, 0, 1.0); + cairo_set_line_width (cr, 0.5); + SBasis crs (cross(A - A(0), derivative(A))); + crs = shift(crs*Linear(-1, 0)*Linear(-1, 0), -2); + crs = crs * (300/(*bounds_exact(crs)).extent()); + vector<double> rts = roots(crs); + for(double t : rts) { + cairo_move_to(cr, A(0)); + cairo_line_to(cr, A(t)); + cairo_stroke(cr); + } + cairo_restore(cr); + cairo_move_to(cr, 0, 300); + cairo_line_to(cr, width, 300); + crs += 300; + D2<SBasis > are_graph(SBasis(Linear(0, width)), crs ); + cairo_save(cr); + cairo_d2_sb(cr, are_graph); + cairo_set_line_width (cr, .5); + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_stroke(cr); + cairo_restore(cr); + + Path PB; + PB.append(B); + Path PA; + PA.append(A); + + if (toggles[0].on) { + PathVector ps; + ps.push_back(PA); + ps.push_back(PB); + CrossingSet cs = crossings_among(ps); + *notify << "total intersections: " << cs.size() << '\n'; + cairo_stroke(cr); + cairo_set_source_rgba (cr, 1., 0., 0, 0.8); + for(unsigned i = 0; i < cs.size(); i++) { + Crossings section = cs[i]; + *notify << "section " << i << ": " << section.size() << '\n'; + for(auto & j : section) { + draw_handle(cr, A(j.ta)); + *notify << Geom::distance(A(j.ta), B(j.tb)) + << std::endl; + } + } + + cairo_stroke(cr); + } else { + vector<Geom::Point> Ab = A_handles.pts, Bb = B_handles.pts; + std::vector<std::pair<double, double> > section; + find_intersections( section, A, B); + std::vector<std::pair<double, double> > polished_section = section; + *notify << "total intersections: " << section.size(); + polish_intersections( polished_section, A, B); + cairo_stroke(cr); + cairo_set_source_rgba (cr, 1., 0., 0, 0.8); + for(unsigned i = 0; i < section.size(); i++) { + draw_handle(cr, A(section[i].first)); + *notify << Geom::distance(A(section[i].first), B(section[i].second)) + << " polished " + << Geom::distance(A(polished_section[i].first), B(polished_section[i].second)) + << std::endl; + } + + cairo_stroke(cr); + } + cairo_restore(cr); + + + Toy::draw(cr, notify, width, height, save,timer_stream); +} +public: + PairIntersect (unsigned A_bez_ord, unsigned B_bez_ord) { + toggles.emplace_back("Path", true); + toggles[0].bounds = Rect(Point(10,100), Point(100, 130)); + //toggles.push_back(Toggle("Curve", true)); + //toggles[1].bounds = Rect(Point(10,130), Point(100, 160)); + handles.push_back(&A_handles); + handles.push_back(&B_handles); + A_handles.name = "A"; + B_handles.name = "B"; + for(unsigned i = 0; i < A_bez_ord; i++) + A_handles.push_back(uniform()*400, uniform()*400); + for(unsigned i = 0; i < B_bez_ord; i++) + B_handles.push_back(uniform()*400, uniform()*400); +} +}; + +int main(int argc, char **argv) { +unsigned A_bez_ord=10; +unsigned B_bez_ord=3; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + init(argc, argv, new PairIntersect(A_bez_ord, B_bez_ord)); + + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/paptest.cpp b/src/toys/paptest.cpp new file mode 100644 index 0000000..4ea2cd2 --- /dev/null +++ b/src/toys/paptest.cpp @@ -0,0 +1,107 @@ +/*
+ * A simple toy to test the path along path
+ *
+ * Copyright 2007 Johan Engelen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+
+#include <iostream>
+#include <2geom/path-sink.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> >
+doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in, Geom::Piecewise<Geom::D2<Geom::SBasis> > & pattern)
+{
+ using namespace Geom;
+
+ Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(pwd2_in, 2, .1);
+ uskeleton = remove_short_cuts(uskeleton,.01);
+ Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
+ n = force_continuity(remove_short_cuts(n,.1));
+
+ D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pattern);
+ Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
+ Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
+ Interval pattBnds = *bounds_exact(x);
+ x -= pattBnds.min();
+ Interval pattBndsY = *bounds_exact(y);
+ y -= (pattBndsY.max()+pattBndsY.min())/2;
+
+ int nbCopies = int(uskeleton.cuts.back()/pattBnds.extent());
+ double scaling = 1;
+
+ double pattWidth = pattBnds.extent() * scaling;
+
+ if (scaling != 1.0) {
+ x*=scaling;
+ }
+
+ double offs = 0;
+ Piecewise<D2<SBasis> > output;
+ for (int i=0; i<nbCopies; i++){
+ output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
+ offs+=pattWidth;
+ }
+
+ return output;
+}
+
+int main(int argc, char **argv) {
+ if (argc > 1) {
+ Geom::PathVector originald = Geom::parse_svg_path(&*argv[1]);
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > originaldpwd2;
+ for (const auto & i : originald) {
+ originaldpwd2.concat( i.toPwSb() );
+ }
+
+ Geom::PathVector pattern = Geom::parse_svg_path(&*argv[2]);
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > patternpwd2;
+ for (const auto & i : pattern) {
+ patternpwd2.concat( i.toPwSb() );
+ }
+
+ doEffect_pwd2(originaldpwd2, patternpwd2);
+ }
+ return 0;
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/parametrics.cpp b/src/toys/parametrics.cpp new file mode 100644 index 0000000..2d2538c --- /dev/null +++ b/src/toys/parametrics.cpp @@ -0,0 +1,229 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/affine.h> + +#include <glib.h> +#include <vector> +#include <iostream> +using std::vector; +using namespace Geom; + +int mode; + +static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double max, double space=10){ + Piecewise<D2<SBasis> > Mperp = rot90(derivative(M)) * 3; + for( double t = M.cuts.front(); t < max; t += space) { + Point pos = M(t), perp = Mperp(t); + draw_line_seg(cr, pos + perp, pos - perp); + } + cairo_stroke(cr); +} + +static void draw_axis(cairo_t *cr, Piecewise<D2<SBasis> > const &pw, unsigned d, Affine m) { + double mult; + if(abs(mode)==1) mult = 20; + if(abs(mode)==2) mult = 1; + if(abs(mode)==3) mult = 100; + if(abs(mode)==4) mult = 20; + if(abs(mode)==5) mult = 20; + if(abs(mode)==6) mult = 100; + for(unsigned i = 0; i < pw.size(); i++) { + cairo_d2_sb(cr, D2<SBasis>(SBasis(pw.cuts[i]-pw.cuts[0], pw.cuts[i+1]-pw.cuts[0])*mult, SBasis(pw[i][d]))*m); + } +} +/* +void dump_latex(PathVector ps) { + for(unsigned d = 0; d < 2; d++) { + std::cout << "$$\n" << (d?"y":"x") << "(t) = \\left\\{\n\\begin{array}{ll}\n"; + int seg = 0; + for(unsigned i = 0; i < ps.size(); i++) + for(unsigned j = 0; j < ps[i].size(); j++) { + Bezier<3> &b = dynamic_cast<Bezier<3>& >(const_cast<Curve&>(ps[i][j])); + std::cout << b[0][d] << "(" << seg+1 << "-t)^3 + " + << 3*b[1][d] << "t(" << seg+1 << "-t)^2 + " + << 3*b[2][d] << "t^2(" << seg+1 << "-t) + " + << b[3][d] << "t^3,& " << seg << "\\leq t < " << seg+1 << "\\\\\n"; + seg++; + } + std::cout << "\\end{array}\n$$\n"; + } +} +*/ +class Parametrics: public Toy { + Piecewise<D2<SBasis> > cat, alcat, box, arc, monk, traj; +#ifdef USE_TIME + GTimer* time; + bool st; +#endif + double waitt; + double t; + int count; + void draw(cairo_t *cr, + std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override { + //double t = handles[0][0] / 20.; + +#ifdef USE_TIME + gulong* foo = 0; + t = g_timer_elapsed(time, foo) * 100; +#else + double inc; + if(mode==1) inc = .1; + if(mode==2) inc = 5; + if(mode==3) inc = .01; + if(mode==4) inc = .04; + if(mode==5) inc = .1; + if(mode==6) inc = .01; + if(mode<0) inc = .01*M_PI; + if(!save && !waitt) { + t += inc; + } + if(waitt) waitt += 1; + if(waitt>20) waitt = 0; +#endif + Piecewise<D2<SBasis> > obj; + if(abs(mode)==1) obj = cat; + if(abs(mode)==2) obj = alcat; + if(abs(mode)==3) obj = arc; + if(abs(mode)==4) obj = box; + if(abs(mode)==5) obj = monk; + if(abs(mode)==6) obj = traj; + if(t==obj.cuts.back()) t += inc/2; + cairo_set_source_rgb(cr, 1,1,1); + if(save) { + cairo_rectangle(cr, 0, 0, width, height); + cairo_fill(cr); + } + Piecewise<D2<SBasis> > port, rport; + if(mode>0) { + port = portion(obj, 0, t); + rport = mode>0? portion(obj, t, obj.cuts[obj.size()]) : obj; + cairo_set_source_rgba (cr, 0., 0., 0., 1); + Point curpt = rport[0].at0(); + if(t<obj.cuts.back()) { + draw_line_seg(cr, curpt, Point(curpt[0], 350)); + draw_line_seg(cr, curpt, Point(350, curpt[1])); + cairo_stroke(cr); + } + + char tlab[64]; + sprintf(tlab, "t=%.02f", t); + draw_text(cr, curpt, tlab , true); + + cairo_set_line_width (cr, 2); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_pw_d2_sb(cr, port); + cairo_stroke(cr); + } + if(mode>=0 && t>=obj.cuts.back()+inc) t = 0; + cairo_set_source_rgba (cr, 0.9, 0., 0., 1); + if(mode<0) { + draw_axis(cr, obj, 0, from_basis(Point(cos(t),sin(t)),Point(sin(t),-cos(t)),Point(0, 350))); + if(cos(t) <= 0) { + mode = -mode; + t = 0; + waitt = 1; + } + } else + draw_axis(cr, rport, 0, from_basis(Point(0,1),Point(1,0),Point(0, 350))); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0., 0.9, 1); + if(mode<0) + draw_axis(cr, obj, 1, from_basis(Point(1,0),Point(0,1),Point(350*t/M_PI*2, 0))); + else + draw_axis(cr, rport, 1, from_basis(Point(1,0),Point(0,1),Point(350, 0))); + cairo_stroke(cr); + + if(mode==2 && t>0) { + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0., 0.9, 1); + dot_plot(cr, port, t); + cairo_stroke(cr); + } + + if(!save) { + char file[100]; + sprintf(file, "output/%04d.png", count); + //take_screenshot(file); + count++; + } + // *notify << "pieces = " << alcat.size() << ";\n"; + + Toy::draw(cr, notify, width, height, save,timer_stream); + redraw(); + } + +#ifdef USE_TIME + virtual void mouse_moved(GdkEventMotion* e) { + if(st) { + g_timer_start(time); + st = false; + } + Toy::mouse_moved(e); + } +#endif + + public: + Parametrics(){ + mode = 2; + PathVector cp = read_svgd("cat.svgd"); + //dump_latex(cp); + cat = paths_to_pw(cp); + cat *= .3; + cat += Point(50, 50); + alcat = arc_length_parametrization(cat); + + monk = paths_to_pw(read_svgd("monkey.svgd")); + //monk *= .3; + monk += Point(50,50); + + arc = sectionize(D2<Piecewise<SBasis> >(cos(Linear(0,M_PI))*120, sin(Linear(0,M_PI))*-120)); + arc += Point(200, 200); + + box = Piecewise<D2<SBasis> >(); + box.push_cut(0); + box.push(D2<SBasis>(SBasis(100.,300.), SBasis(100.)), 1); + box.push(D2<SBasis>(SBasis(300.), SBasis(100.,300.)), 2); + box.push(D2<SBasis>(SBasis(300.,100.), SBasis(300.)), 3); + box.push(D2<SBasis>(SBasis(100.), SBasis(300.,100.)), 4); + //handles.push_back(Point(100, 100)); + traj = Piecewise<D2<SBasis> >(); + SBasis quad = Linear(0,1)*Linear(0,1)*256-Linear(0,256)+200; + traj.push_cut(0); + traj.push(D2<SBasis>(SBasis(100.,300.),SBasis(quad)), 1); +#ifdef USE_TIME + time = g_timer_new(); + g_timer_reset(time); + st = true; +#endif + waitt = 0; + count = 0; + t = 0; + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Parametrics, 720, 480); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99: diff --git a/src/toys/parser.cpp b/src/toys/parser.cpp new file mode 100644 index 0000000..c7950e5 --- /dev/null +++ b/src/toys/parser.cpp @@ -0,0 +1,108 @@ +/* + * A simple toy to test the parser + * + * Copyright 2007 Aaron Spike <aaron@ekips.org> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + + +#include <iostream> +#include <2geom/path-sink.h> +#include <2geom/svg-path-parser.h> + +class SVGPathTestPrinter : public Geom::PathSink { +public: + void moveTo(Geom::Point const &p) override { + std::cout << "M " << p << std::endl; + } + + void hlineTo(Geom::Coord v) { + std::cout << "H " << v << std::endl; + } + + void vlineTo(Geom::Coord v) { + std::cout << "V " << v << std::endl; + } + + void lineTo(Geom::Point const &p) override { + std::cout << "L " << p << std::endl; + } + + void curveTo(Geom::Point const &c0, Geom::Point const &c1, Geom::Point const &p) override { + std::cout << "C " << c0 << " " << c1 << " " << p << std::endl; + } + + void quadTo(Geom::Point const &c, Geom::Point const &p) override { + std::cout << "Q " << c << " " << p << std::endl; + } + + void arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Geom::Point const &p) override + { + std::cout << "A " << rx << " " << ry << " " << angle << " " << large_arc << " " << sweep << " " << p << std::endl; + } + + bool backspace() override + { + //std::cout << "[remove last segment]" << std::endl; + return false; + } + + void closePath() override { + std::cout << "Z" << std::endl; + } + + void flush() override { + ; + } + +}; + + +int main(int argc, char **argv) { + if (argc > 1) { + SVGPathTestPrinter sink; + Geom::parse_svg_path(&*argv[1], sink); + std::cout << "Try real pathsink:" << std::endl; + Geom::PathVector testpath = Geom::parse_svg_path(&*argv[1]); + std::cout << "Geom::PathVector length: " << testpath.size() << std::endl; + if ( !testpath.empty() ) + std::cout << "Path curves: " << testpath.front().size() << std::endl; + std::cout << "success!" << std::endl; + } + return 0; +}; + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/path-along-path.cpp b/src/toys/path-along-path.cpp new file mode 100644 index 0000000..a2a6a1b --- /dev/null +++ b/src/toys/path-along-path.cpp @@ -0,0 +1,112 @@ +#include <2geom/d2.h> +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <algorithm> +using std::vector; +using namespace Geom; + +class PathAlongPathToy: public Toy { + PointSetHandle skel_handles, pat_handles; + PointHandle origin_handle; + bool should_draw_numbers() override{return false;} + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + D2<SBasis> skeleton = skel_handles.asBezier(); + D2<SBasis> pattern = pat_handles.asBezier(); + + + cairo_set_line_width(cr,1.); + cairo_pw_d2_sb(cr, Piecewise<D2<SBasis> >(skeleton)); + cairo_set_source_rgba(cr,0.0,0.0,1.0,1.0); + cairo_stroke(cr); + + cairo_pw_d2_sb(cr, Piecewise<D2<SBasis> >(pattern)); + cairo_set_source_rgba(cr,1.0,0.0,1.0,1.0); + cairo_stroke(cr); + + origin_handle.pos[0]=150; + Geom::Point origin = origin_handle.pos; + + Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(Piecewise<D2<SBasis> >(skeleton),2,.1); + uskeleton = remove_short_cuts(uskeleton,.01); + Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton)); + n = force_continuity(remove_short_cuts(n,.1)); + + Piecewise<SBasis> x=Piecewise<SBasis>(pattern[0]-origin[0]); + Piecewise<SBasis> y=Piecewise<SBasis>(pattern[1]-origin[1]); + Interval pattBnds = *bounds_exact(x); + int nbCopies = int(uskeleton.cuts.back()/pattBnds.extent()); + + //double pattWidth = uskeleton.cuts.back()/nbCopies; + double pattWidth = pattBnds.extent(); + + double offs = 0; + x-=pattBnds.min(); + //x*=pattWidth/pattBnds.extent(); + + Piecewise<D2<SBasis> >output; + for (int i=0; i<nbCopies; i++){ + output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs)); + offs+=pattWidth; + } + + //Perform cut for last segment + double tt = uskeleton.cuts.back() - offs; + if(tt > 0.) { + vector<double> rs = roots(x - tt); + rs.push_back(0); rs.push_back(1); //regard endpoints + std::sort(rs.begin(), rs.end()); + std::unique(rs.begin(), rs.end()); + //enumerate indices of sections to the left of the line + for(unsigned i = (x[0].at0()>tt ? 1 : 0); i < rs.size()-1; i+=2) { + Piecewise<SBasis> port = portion(x+offs, rs[i], rs[i+1]); + output.concat(compose(uskeleton,port)+portion(y, rs[i], rs[i+1])*compose(n,port)); + } + } + + cairo_pw_d2_sb(cr, output); + cairo_set_source_rgba(cr,1.0,0.0,1.0,1.0); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + PathAlongPathToy() : origin_handle(150,150) { + if(handles.empty()) { + handles.push_back(&skel_handles); + handles.push_back(&pat_handles); + for(int i = 0; i < 8; i++) + skel_handles.push_back(200+50*i,400); + for(int i = 0; i < 4; i++) + pat_handles.push_back(100+uniform()*400, + 150+uniform()*100); + + handles.push_back(&origin_handle); + } + } +}; + + +int main(int argc, char **argv) { + init(argc, argv, new PathAlongPathToy); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + diff --git a/src/toys/path-cairo.cpp b/src/toys/path-cairo.cpp new file mode 100644 index 0000000..73c2a59 --- /dev/null +++ b/src/toys/path-cairo.cpp @@ -0,0 +1,342 @@ +#include <cairo.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/utils.h> +#include <sstream> +#include <optional> + +using namespace Geom; + +void cairo_rectangle(cairo_t *cr, Rect const& r) { + cairo_rectangle(cr, r.left(), r.top(), r.width(), r.height()); +} + +void cairo_convex_hull(cairo_t *cr, ConvexHull const& ch) { + if(ch.empty()) return; + cairo_move_to(cr, ch[ch.size()-1]); + for(auto i : ch) { + cairo_line_to(cr, i); + } +} + +void cairo_curve(cairo_t *cr, Curve const& c) { + if(!cairo_has_current_point(cr)) + cairo_move_to(cr, c.initialPoint()); + + if(LineSegment const* line_segment = dynamic_cast<LineSegment const*>(&c)) { + cairo_line_to(cr, (*line_segment)[1][0], (*line_segment)[1][1]); + } + else if(QuadraticBezier const *quadratic_bezier = dynamic_cast<QuadraticBezier const*>(&c)) { + std::vector<Point> points = quadratic_bezier->controlPoints(); + Point b1 = points[0] + (2./3) * (points[1] - points[0]); + Point b2 = b1 + (1./3) * (points[2] - points[0]); + cairo_curve_to(cr, b1[0], b1[1], + b2[0], b2[1], + points[2][0], points[2][1]); + } + else if(CubicBezier const *cubic_bezier = dynamic_cast<CubicBezier const*>(&c)) { + std::vector<Point> points = cubic_bezier->controlPoints(); + cairo_curve_to(cr, points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1]); + } +// else if(EllipticalArc const *svg_elliptical_arc = dynamic_cast<EllipticalArc *>(c)) { +// //TODO: get at the innards and spit them out to cairo +// } + else { + //this case handles sbasis as well as all other curve types + Path sbasis_path = cubicbezierpath_from_sbasis(c.toSBasis(), 0.1); + + //recurse to convert the new path resulting from the sbasis to svgd + for(const auto & iter : sbasis_path) { + cairo_curve(cr, iter); + } + } +} + +void cairo_path(cairo_t *cr, Path const &p) { + cairo_move_to(cr, p.initialPoint()[0], p.initialPoint()[1]); + if(p.size() == 0) { // naked moveto + cairo_move_to(cr, p.finalPoint()+Point(8,0)); + cairo_line_to(cr, p.finalPoint()+Point(-8,0)); + cairo_move_to(cr, p.finalPoint()+Point(0,8)); + cairo_line_to(cr, p.finalPoint()+Point(0,-8)); + return; + } + + for(const auto & iter : p) { + cairo_curve(cr, iter); + } + if(p.closed()) + cairo_close_path(cr); +} + +void cairo_path_stitches(cairo_t *cr, Path const &p) { + Path::const_iterator iter; + for ( iter = p.begin() ; iter != p.end() ; ++iter ) { + Curve const &c=*iter; + if (dynamic_cast<Path::StitchSegment const *>(&c)) { + cairo_move_to(cr, c.initialPoint()[X], c.initialPoint()[Y]); + cairo_line_to(cr, c.finalPoint()[X], c.finalPoint()[Y]); + + std::stringstream s; + s << L1(c.finalPoint() - c.initialPoint()); + std::string ss = s.str(); + draw_text(cr, c.initialPoint()+Point(5,5), ss.c_str(), false, "Serif 6"); + + //std::cout << c.finalPoint() - c.initialPoint() << std::endl; + } + } +} + +void cairo_path_handles(cairo_t */*cr*/, Path const &/*p*/) { + //TODO +} + +void cairo_path(cairo_t *cr, PathVector const &p) { + PathVector::const_iterator it; + for(it = p.begin(); it != p.end(); ++it) { + cairo_path(cr, *it); + } +} + +void cairo_path_stitches(cairo_t *cr, PathVector const &p) { + PathVector::const_iterator it; + for ( it = p.begin() ; it != p.end() ; ++it ) { + cairo_path_stitches(cr, *it); + } +} + +void cairo_d2_sb(cairo_t *cr, D2<SBasis> const &B) { + cairo_path(cr, path_from_sbasis(B, 0.1)); +} + +void cairo_d2_sb2d(cairo_t* cr, D2<SBasis2d> const &sb2, Point /*dir*/, double width) { + D2<SBasis> B; + for(int ui = 0; ui <= 10; ui++) { + double u = ui/10.; + B[0] = extract_u(sb2[0], u);// + Linear(u); + B[1] = extract_u(sb2[1], u); + for(unsigned i = 0; i < 2; i ++) { + B[i] = B[i]*(width/2) + Linear(width/4); + } + cairo_d2_sb(cr, B); + } + for(int vi = 0; vi <= 10; vi++) { + double v = vi/10.; + B[1] = extract_v(sb2[1], v);// + Linear(v); + B[0] = extract_v(sb2[0], v); + for(unsigned i = 0; i < 2; i ++) { + B[i] = B[i]*(width/2) + Linear(width/4); + } + cairo_d2_sb(cr, B); + } +} + +void cairo_sb2d(cairo_t* cr, SBasis2d const &sb2, Point dir, double width) { + D2<SBasis> B; + for(int ui = 0; ui <= 10; ui++) { + double u = ui/10.; + B[0] = extract_u(sb2, u)*dir[0] + Linear(u); + B[1] = SBasis(Linear(0,1)) + extract_u(sb2, u)*dir[1]; + for(unsigned i = 0; i < 2; i ++) { + B[i] = B[i]*(width/2) + Linear(width/4); + } + cairo_d2_sb(cr, B); + } + for(int vi = 0; vi <= 10; vi++) { + double v = vi/10.; + B[1] = extract_v(sb2, v)*dir[1] + Linear(v); + B[0] = SBasis(Linear(0,1)) + extract_v(sb2, v)*dir[0]; + for(unsigned i = 0; i < 2; i ++) { + B[i] = B[i]*(width/2) + Linear(width/4); + } + cairo_d2_sb(cr, B); + } +} + +void cairo_d2_pw_sb(cairo_t *cr, D2<Piecewise<SBasis> > const &p) { + cairo_pw_d2_sb(cr, sectionize(p)); +} + +void cairo_pw_d2_sb(cairo_t *cr, Piecewise<D2<SBasis> > const &p) { + for(unsigned i = 0; i < p.size(); i++) + cairo_d2_sb(cr, p[i]); +} + + +void draw_line_seg(cairo_t *cr, Geom::Point a, Geom::Point b) { + cairo_move_to(cr, a[0], a[1]); + cairo_line_to(cr, b[0], b[1]); + cairo_stroke(cr); +} + +void draw_spot(cairo_t *cr, Geom::Point h) { + draw_line_seg(cr, h, h); +} + +void draw_handle(cairo_t *cr, Geom::Point h) { + double x = h[Geom::X]; + double y = h[Geom::Y]; + cairo_move_to(cr, x-3, y); + cairo_line_to(cr, x+3, y); + cairo_move_to(cr, x, y-3); + cairo_line_to(cr, x, y+3); +} + +void draw_cross(cairo_t *cr, Geom::Point h) { + double x = h[Geom::X]; + double y = h[Geom::Y]; + cairo_move_to(cr, x-3, y-3); + cairo_line_to(cr, x+3, y+3); + cairo_move_to(cr, x+3, y-3); + cairo_line_to(cr, x-3, y+3); +} + +void draw_circ(cairo_t *cr, Geom::Point h) { + int x = int(h[Geom::X]); + int y = int(h[Geom::Y]); + cairo_new_sub_path(cr); + cairo_arc(cr, x, y, 3, 0, M_PI*2); + cairo_stroke(cr); +} + +void draw_ray(cairo_t *cr, Geom::Point h, Geom::Point dir) { + draw_line_seg(cr, h, h+dir); + Point unit = 3*unit_vector(dir), + rot = rot90(unit); + draw_line_seg(cr, h+dir, h + dir - unit + rot); + draw_line_seg(cr, h+dir, h + dir - unit - rot); +} + + +void +cairo_move_to (cairo_t *cr, Geom::Point p1) { + cairo_move_to(cr, p1[0], p1[1]); +} + +void +cairo_line_to (cairo_t *cr, Geom::Point p1) { + cairo_line_to(cr, p1[0], p1[1]); +} + +void +cairo_curve_to (cairo_t *cr, Geom::Point p1, + Geom::Point p2, Geom::Point p3) { + cairo_curve_to(cr, p1[0], p1[1], + p2[0], p2[1], + p3[0], p3[1]); +} +/* + void draw_string(GtkWidget *widget, string s, int x, int y) { + PangoLayout *layout = gtk_widget_create_pango_layout(widget, s.c_str()); + cairo_t* cr = gdk_cairo_create (widget->window); + cairo_move_to(cr, x, y); + pango_cairo_show_layout(cr, layout); + cairo_destroy (cr); + }*/ + +// H in [0,360) +// S, V, R, G, B in [0,1] +void convertHSVtoRGB(const double H, const double S, const double V, + double& R, double& G, double& B) { + int Hi = int(floor(H/60.)) % 6; + double f = H/60. - Hi; + double p = V*(1-S); + double q = V*(1-f*S); + double t = V*(1-(1-f)*S); + switch(Hi) { + case 0: R=V, G=t, B=p; break; + case 1: R=q, G=V, B=p; break; + case 2: R=p, G=V, B=t; break; + case 3: R=p, G=q, B=V; break; + case 4: R=t, G=p, B=V; break; + case 5: R=V, G=p, B=q; break; + } +} + +void draw_line(cairo_t *cr, double a, double b, double c, const Geom::Rect& r) { + Geom::Line l(a, b, c); + std::optional<Geom::LineSegment> seg = l.clip(r); + if (seg) { + cairo_move_to(cr, seg->initialPoint()); + cairo_line_to(cr, seg->finalPoint()); + cairo_stroke(cr); + } +} + + +void draw_line(cairo_t* cr, const Geom::Line& l, const Geom::Rect& r) +{ + std::vector<double> coeff = l.coefficients(); + draw_line (cr, coeff[0], coeff[1], coeff[2], r); +} + + +void draw_line(cairo_t *cr, Geom::Point n, double dist, Geom::Rect r) { + draw_line(cr, n[0], n[1], dist, r); +} + + +void draw_ray(cairo_t *cr, const Geom::Ray& ray, const Geom::Rect& r) +{ + LineSegment ls; + + for (size_t i = 0; i < 4; ++i) + { + ls.setInitial (r.corner(i)); + ls.setFinal (r.corner(i+1)); + OptCrossing cx = intersection (ls, ray); + if (cx) + { + Point P = ray.pointAt ((*cx).tb); + draw_line_seg (cr, ray.origin(), P); + break; + } + } +} + +void draw_line_segment(cairo_t *cr, const Geom::LineSegment& ls, const Geom::Rect& r) +{ + if(r.contains(ls[0])) { + if(r.contains(ls[1])) { + draw_line_seg(cr, ls[0], ls[1]); + } else { + draw_ray(cr, Geom::Ray(ls[0], ls[1]), r); + } + + } else { + if(r.contains(ls[1])) { + draw_ray(cr, Geom::Ray(ls[1], ls[0]), r); + } else { + draw_line(cr, Geom::Line(ls[0], ls[1]), r); + } + + } +} + +void draw_line_seg_with_arrow(cairo_t *cr, Geom::Point a, Geom::Point b, double dangle, double radius) { + double angle = atan2(a-b); + cairo_move_to(cr, a); + cairo_line_to(cr, b); + + cairo_move_to(cr, b); + cairo_line_to(cr, Point::polar(angle + dangle, radius) + b); + cairo_move_to(cr, b); + cairo_line_to(cr, Point::polar(angle - dangle, radius) + b); + cairo_stroke(cr); +} + + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(substatement-open . 0)) + indent-tabs-mode:nil + c-brace-offset:0 + fill-column:99 + End: + vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : +*/ diff --git a/src/toys/path-effects.cpp b/src/toys/path-effects.cpp new file mode 100644 index 0000000..fdd7ef0 --- /dev/null +++ b/src/toys/path-effects.cpp @@ -0,0 +1,140 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/svg-path-parser.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/sbasis-math.h> + +#include <cstdlib> + +using namespace Geom; + +Piecewise<SBasis > sore_tooth(Interval intv) { + Piecewise<SBasis > out; + double t = intv.min(); + Point p(0,0); + out.push_cut(0); + double r = 20; + double dir = 0.5; + while(t < intv.max()) { + double nt = t + 10; + if(nt > intv.max()) + nt = intv.max(); + SBasis zag(r*Linear(dir,-dir)); + out.push(zag, nt); + t = nt; + dir = -dir; + } + return out; +} + +Piecewise< D2<SBasis> > zaggy(Interval intv, double dt, double radius) { + Piecewise<D2<SBasis> > out; + double t = intv.min(); + Point p(0,0); + out.push_cut(0); + while(t < intv.max()) { + double nt = t + uniform()*dt; + if(nt > intv.max()) + nt = intv.max(); + Point np = Point((uniform()-0.5)*2*radius, (uniform()-0.5)*2*radius); + D2<SBasis> zag(SBasis(p[0],np[0]), SBasis(p[1],np[1])); + p = np; + //std::cout << t <<","<< nt << p << np << std::endl; + out.push(zag, nt); + t = nt; + } + return out; +} + + +class BoolOps: public Toy { + PathVector pv; + PointHandle offset_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Geom::Translate t(offset_handle.pos); + + cairo_set_line_width(cr, 1); + cairo_set_source_rgb(cr, 0.75,0.75,1); + + //cairo_shape(cr, bst); + cairo_path(cr, pv*t); + cairo_stroke(cr); + + cairo_set_source_rgb(cr, 0,0,0); + for(const auto & i : pv) { + Piecewise<D2<SBasis> > B = i.toPwSb(); + Piecewise<D2<SBasis> > n = rot90(unitVector(derivative(B))); + Piecewise<SBasis > al = arcLengthSb(B); + +#if 0 + Piecewise<D2<SBasis> > offset_curve = Piecewise<D2<SBasis> >(B)+n*offset; + PathVector offset_path = path_from_piecewise(offset_curve, 0.1); + + cairo_path(cr, offset_path*t); + cairo_stroke(cr); +#endif + //Piecewise<D2<SBasis> > zz_curve = B+zaggy(B.domain(), 0.1, 20);//al*n; + //Piecewise<D2<SBasis> > zz_curve = Piecewise<D2<SBasis> >(B)+ + // compose(sore_tooth(Interval(al.firstValue(),al.lastValue())), al)*n; + Piecewise<D2<SBasis> > zz_curve = Piecewise<D2<SBasis> >(B)+ + sin(al*0.1)*10*n; + PathVector zz_path = path_from_piecewise(zz_curve, 0.1); + + cairo_path(cr, zz_path*t); + cairo_stroke(cr); + } + for(const auto & i : pv) { + if(i.size() == 0) { + *notify << "naked moveto;"; + } else + for(const auto & j : i) { + const Curve* c = &j; + *notify << typeid(*c).name() << ';' ; + } + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + public: + BoolOps () {} + + void first_time(int argc, char** argv) override { + const char *path_b_name="star.svgd"; + if(argc > 1) + path_b_name = argv[1]; + pv = read_svgd(path_b_name); + std::cout << pv.size() << "\n"; + std::cout << pv[0].size() << "\n"; + pv *= Translate(-pv[0].initialPoint()); + + Rect bounds = *pv[0].boundsExact(); + handles.push_back(&offset_handle); + offset_handle.pos = bounds.midpoint() - bounds.corner(0); + + //bs = cleanup(pv); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new BoolOps()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/path-toy.py b/src/toys/path-toy.py new file mode 100644 index 0000000..631482f --- /dev/null +++ b/src/toys/path-toy.py @@ -0,0 +1,41 @@ +#!/usr/bin/python + +import py2geom +import toyframework +import random,gtk +from py2geom_glue import * + +class PathToy(toyframework.Toy): + def __init__(self): + toyframework.Toy.__init__(self) + self.handles.append(toyframework.PointHandle(200, 200)) + self.path_b_name="star.svgd" + self.pv = py2geom.read_svgd(self.path_b_name); + centr = py2geom.Point() + for p in self.pv: + c,area = py2geom.centroid(p.toPwSb()) + centr += c + self.pv = self.pv*py2geom.Matrix(py2geom.Translate(-centr)) + def draw(self, cr, pos, save): + cr.set_source_rgba (0., 0., 0., 1) + cr.set_line_width (1) + + + B = (self.pv[0]*py2geom.Matrix(py2geom.Translate(*self.handles[0].pos))).toPwSb(); + n = py2geom.rot90(py2geom.unit_vector(py2geom.derivative(B), 0.01, 3)); + al = py2geom.arcLengthSb(B, 0.1); + offset = 10. + + offset_curve = B+py2geom.sin(al*0.1, 0.01, 2)*n*10. + offset_path = py2geom.path_from_piecewise(offset_curve, 0.1, True) + + py2geom.cairo_path(cr, offset_path) + cr.stroke() + + self.notify = '' + toyframework.Toy.draw(self, cr, pos, save) + +t = PathToy() +import sys + +toyframework.init(sys.argv, t, 500, 500) diff --git a/src/toys/pencil-2.cpp b/src/toys/pencil-2.cpp new file mode 100644 index 0000000..a312083 --- /dev/null +++ b/src/toys/pencil-2.cpp @@ -0,0 +1,1133 @@ +/* + * pencil-2 Toy - point fitting. + * + * 2009 njh + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/basic-intersection.h> +#include <2geom/math-utils.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define SP_HUGE 1e5 +#define noBEZIER_DEBUG + +#ifdef HAVE_IEEEFP_H +# include <ieeefp.h> +#endif + +namespace Geom{ + +namespace BezierFitter{ + +typedef Point BezierCurve[]; + +/* Forward declarations */ +static void generate_bezier(Point b[], Point const d[], double const u[], unsigned len, + Point const &tHat1, Point const &tHat2, double tolerance_sq); +static void estimate_lengths(Point bezier[], + Point const data[], double const u[], unsigned len, + Point const &tHat1, Point const &tHat2); +static void estimate_bi(Point b[4], unsigned ei, + Point const data[], double const u[], unsigned len); +static void reparameterize(Point const d[], unsigned len, double u[], CubicBezier const & bezCurve); +static double NewtonRaphsonRootFind(CubicBezier const & Q, Point const &P, double u); +static Point darray_center_tangent(Point const d[], unsigned center, unsigned length); +static Point darray_right_tangent(Point const d[], unsigned const len); +static unsigned copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[]); +static void chord_length_parameterize(Point const d[], double u[], unsigned len); +static double compute_max_error_ratio(Point const d[], double const u[], unsigned len, + BezierCurve const bezCurve, double tolerance, + unsigned *splitPoint); +static double compute_hook(Point const &a, Point const &b, double const u, + CubicBezier const & bezCurve, + double const tolerance); +static double compute_hook(Point const &a, Point const &b, double const u, + BezierCurve const bezCurve, + double const tolerance) { + CubicBezier cb(bezCurve[0], bezCurve[1], bezCurve[2], bezCurve[3]); + return compute_hook(a, b, u, cb, tolerance); + +} + + +static void reparameterize_pts(Point const d[], unsigned len, double u[], BezierCurve const bezCurve) { + CubicBezier cb(bezCurve[0], bezCurve[1], bezCurve[2], bezCurve[3]); + reparameterize(d, len, u, cb); +} + + + +static Point const unconstrained_tangent(0, 0); + + +/* + * B0, B1, B2, B3 : Bezier multipliers + */ + +#define B0(u) ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) ) +#define B1(u) ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) ) +#define B2(u) ( 3 * u * u * ( 1.0 - u ) ) +#define B3(u) ( u * u * u ) + +#ifdef BEZIER_DEBUG +# define DOUBLE_ASSERT(x) assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) ) +# define BEZIER_ASSERT(b) do { \ + DOUBLE_ASSERT((b)[0][X]); DOUBLE_ASSERT((b)[0][Y]); \ + DOUBLE_ASSERT((b)[1][X]); DOUBLE_ASSERT((b)[1][Y]); \ + DOUBLE_ASSERT((b)[2][X]); DOUBLE_ASSERT((b)[2][Y]); \ + DOUBLE_ASSERT((b)[3][X]); DOUBLE_ASSERT((b)[3][Y]); \ + } while(0) +#else +# define DOUBLE_ASSERT(x) do { } while(0) +# define BEZIER_ASSERT(b) do { } while(0) +#endif + + +Point +bezier_pt(unsigned const degree, Point const V[], double const t) +{ + return bernstein_value_at(t, V, degree); + +} + +/* + * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent : + * Approximate unit tangents at endpoints and "center" of digitized curve + */ + +/** + * Estimate the (forward) tangent at point d[first + 0.5]. + * + * Unlike the center and right versions, this calculates the tangent in + * the way one might expect, i.e., wrt increasing index into d. + * \pre (2 \<= len) and (d[0] != d[1]). + **/ +Point +darray_left_tangent(Point const d[], unsigned const len) +{ + assert( len >= 2 ); + assert( d[0] != d[1] ); + return unit_vector( d[1] - d[0] ); +} + +/** + * Estimates the (backward) tangent at d[last - 0.5]. + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre 2 \<= len. + * \pre d[len - 1] != d[len - 2]. + * \pre all[p in d] in_svg_plane(p). + */ +static Point +darray_right_tangent(Point const d[], unsigned const len) +{ + assert( 2 <= len ); + unsigned const last = len - 1; + unsigned const prev = last - 1; + assert( d[last] != d[prev] ); + return unit_vector( d[prev] - d[last] ); +} + +/** + * Estimate the (forward) tangent at point d[0]. + * + * Unlike the center and right versions, this calculates the tangent in + * the way one might expect, i.e., wrt increasing index into d. + * + * \pre 2 \<= len. + * \pre d[0] != d[1]. + * \pre all[p in d] in_svg_plane(p). + * \post is_unit_vector(ret). + **/ +Point +darray_left_tangent(Point const d[], unsigned const len, double const tolerance_sq) +{ + assert( 2 <= len ); + assert( 0 <= tolerance_sq ); + for (unsigned i = 1;;) { + Point const pi(d[i]); + Point const t(pi - d[0]); + double const distsq = dot(t, t); + if ( tolerance_sq < distsq ) { + return unit_vector(t); + } + ++i; + if (i == len) { + return ( distsq == 0 + ? darray_left_tangent(d, len) + : unit_vector(t) ); + } + } +} + +/** + * Estimates the (backward) tangent at d[last]. + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre 2 \<= len. + * \pre d[len - 1] != d[len - 2]. + * \pre all[p in d] in_svg_plane(p). + */ +Point +darray_right_tangent(Point const d[], unsigned const len, double const tolerance_sq) +{ + assert( 2 <= len ); + assert( 0 <= tolerance_sq ); + unsigned const last = len - 1; + for (unsigned i = last - 1;; i--) { + Point const pi(d[i]); + Point const t(pi - d[last]); + double const distsq = dot(t, t); + if ( tolerance_sq < distsq ) { + return unit_vector(t); + } + if (i == 0) { + return ( distsq == 0 + ? darray_right_tangent(d, len) + : unit_vector(t) ); + } + } +} + +/** + * Estimates the (backward) tangent at d[center], by averaging the two + * segments connected to d[center] (and then normalizing the result). + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre (0 \< center \< len - 1) and d is uniqued (at least in + * the immediate vicinity of \a center). + */ +static Point +darray_center_tangent(Point const d[], + unsigned const center, + unsigned const len) +{ + assert( center != 0 ); + assert( center < len - 1 ); + + Point ret; + if ( d[center + 1] == d[center - 1] ) { + /* Rotate 90 degrees in an arbitrary direction. */ + Point const diff = d[center] - d[center - 1]; + ret = rot90(diff); + } else { + ret = d[center - 1] - d[center + 1]; + } + ret.normalize(); + return ret; +} + + +int +bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers); + +int +bezier_fit_cubic_full(Point bezier[], int split_points[], + Point const data[], int const len, + Point const &tHat1, Point const &tHat2, + double const error, unsigned const max_beziers); + + +/** + * Fit a single-segment Bezier curve to a set of digitized points. + * + * \return Number of segments generated, or -1 on error. + */ +int +bezier_fit_cubic(Point *bezier, Point const *data, int len, double error) +{ + return bezier_fit_cubic_r(bezier, data, len, error, 1); +} + +/** + * Fit a multi-segment Bezier curve to a set of digitized points, with + * possible weedout of identical points and NaNs. + * + * \param max_beziers Maximum number of generated segments + * \param Result array, must be large enough for n. segments * 4 elements. + * + * \return Number of segments generated, or -1 on error. + */ +int +bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers) +{ + if(bezier == NULL || + data == NULL || + len <= 0 || + max_beziers >= (1ul << (31 - 2 - 1 - 3))) + return -1; + + Point *uniqued_data = new Point[len]; + unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data); + + if ( uniqued_len < 2 ) { + delete[] uniqued_data; + return 0; + } + + /* Call fit-cubic function with recursion. */ + int const ret = bezier_fit_cubic_full(bezier, NULL, uniqued_data, uniqued_len, + unconstrained_tangent, unconstrained_tangent, + error, max_beziers); + delete[] uniqued_data; + return ret; +} + + + +/** + * Copy points from src to dest, filter out points containing NaN and + * adjacent points with equal x and y. + * \return length of dest + */ +static unsigned +copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[]) +{ + unsigned si = 0; + for (;;) { + if ( si == src_len ) { + return 0; + } + if (!std::isnan(src[si][X]) && + !std::isnan(src[si][Y])) { + dest[0] = Point(src[si]); + ++si; + break; + } + si++; + } + unsigned di = 0; + for (; si < src_len; ++si) { + Point const src_pt = Point(src[si]); + if ( src_pt != dest[di] + && !std::isnan(src_pt[X]) + && !std::isnan(src_pt[Y])) { + dest[++di] = src_pt; + } + } + unsigned dest_len = di + 1; + assert( dest_len <= src_len ); + return dest_len; +} + +/** + * Fit a multi-segment Bezier curve to a set of digitized points, without + * possible weedout of identical points and NaNs. + * + * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1]. + * \param max_beziers Maximum number of generated segments + * \param Result array, must be large enough for n. segments * 4 elements. + */ +int +bezier_fit_cubic_full(Point bezier[], int split_points[], + Point const data[], int const len, + Point const &tHat1, Point const &tHat2, + double const error, unsigned const max_beziers) +{ + int const maxIterations = 4; /* std::max times to try iterating */ + + if(!(bezier != NULL) || + !(data != NULL) || + !(len > 0) || + !(max_beziers >= 1) || + !(error >= 0.0)) + return -1; + + if ( len < 2 ) return 0; + + if ( len == 2 ) { + /* We have 2 points, which can be fitted trivially. */ + bezier[0] = data[0]; + bezier[3] = data[len - 1]; + double const dist = distance(bezier[0], bezier[3]) / 3.0; + if (std::isnan(dist)) { + /* Numerical problem, fall back to straight line segment. */ + bezier[1] = bezier[0]; + bezier[2] = bezier[3]; + } else { + bezier[1] = ( is_zero(tHat1) + ? ( 2 * bezier[0] + bezier[3] ) / 3. + : bezier[0] + dist * tHat1 ); + bezier[2] = ( is_zero(tHat2) + ? ( bezier[0] + 2 * bezier[3] ) / 3. + : bezier[3] + dist * tHat2 ); + } + BEZIER_ASSERT(bezier); + return 1; + } + + /* Parameterize points, and attempt to fit curve */ + unsigned splitPoint; /* Point to split point set at. */ + bool is_corner; + { + double *u = new double[len]; + chord_length_parameterize(data, u, len); + if ( u[len - 1] == 0.0 ) { + /* Zero-length path: every point in data[] is the same. + * + * (Clients aren't allowed to pass such data; handling the case is defensive + * programming.) + */ + delete[] u; + return 0; + } + + generate_bezier(bezier, data, u, len, tHat1, tHat2, error); + reparameterize_pts(data, len, u, bezier); + + /* Find max deviation of points to fitted curve. */ + double const tolerance = std::sqrt(error + 1e-9); + double maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint); + + if ( fabs(maxErrorRatio) <= 1.0 ) { + BEZIER_ASSERT(bezier); + delete[] u; + return 1; + } + + /* If error not too large, then try some reparameterization and iteration. */ + if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) { + for (int i = 0; i < maxIterations; i++) { + generate_bezier(bezier, data, u, len, tHat1, tHat2, error); + reparameterize_pts(data, len, u, bezier); + maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint); + if ( fabs(maxErrorRatio) <= 1.0 ) { + BEZIER_ASSERT(bezier); + delete[] u; + return 1; + } + } + } + delete[] u; + is_corner = (maxErrorRatio < 0); + } + + if (is_corner) { + assert(splitPoint < unsigned(len)); + if (splitPoint == 0) { + if (is_zero(tHat1)) { + /* Got spike even with unconstrained initial tangent. */ + ++splitPoint; + } else { + return bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2, + error, max_beziers); + } + } else if (splitPoint == unsigned(len - 1)) { + if (is_zero(tHat2)) { + /* Got spike even with unconstrained final tangent. */ + --splitPoint; + } else { + return bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent, + error, max_beziers); + } + } + } + + if ( 1 < max_beziers ) { + /* + * Fitting failed -- split at max error point and fit recursively + */ + unsigned const rec_max_beziers1 = max_beziers - 1; + + Point recTHat2, recTHat1; + if (is_corner) { + if(!(0 < splitPoint && splitPoint < unsigned(len - 1))) + return -1; + recTHat1 = recTHat2 = unconstrained_tangent; + } else { + /* Unit tangent vector at splitPoint. */ + recTHat2 = darray_center_tangent(data, splitPoint, len); + recTHat1 = -recTHat2; + } + int const nsegs1 = bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1, + tHat1, recTHat2, error, rec_max_beziers1); + if ( nsegs1 < 0 ) { +#ifdef BEZIER_DEBUG + g_print("fit_cubic[1]: recursive call failed\n"); +#endif + return -1; + } + assert( nsegs1 != 0 ); + if (split_points != NULL) { + split_points[nsegs1 - 1] = splitPoint; + } + unsigned const rec_max_beziers2 = max_beziers - nsegs1; + int const nsegs2 = bezier_fit_cubic_full(bezier + nsegs1*4, + ( split_points == NULL + ? NULL + : split_points + nsegs1 ), + data + splitPoint, len - splitPoint, + recTHat1, tHat2, error, rec_max_beziers2); + if ( nsegs2 < 0 ) { +#ifdef BEZIER_DEBUG + g_print("fit_cubic[2]: recursive call failed\n"); +#endif + return -1; + } + +#ifdef BEZIER_DEBUG + g_print("fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n", + nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers); +#endif + return nsegs1 + nsegs2; + } else { + return -1; + } +} + + +/** + * Fill in \a bezier[] based on the given data and tangent requirements, using + * a least-squares fit. + * + * Each of tHat1 and tHat2 should be either a zero vector or a unit vector. + * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise, + * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3]. + * + * \param tolerance_sq Used only for an initial guess as to tangent directions + * when \a tHat1 or \a tHat2 is zero. + */ +static void +generate_bezier(Point bezier[], + Point const data[], double const u[], unsigned const len, + Point const &tHat1, Point const &tHat2, + double const tolerance_sq) +{ + bool const est1 = is_zero(tHat1); + bool const est2 = is_zero(tHat2); + Point est_tHat1( est1 + ? darray_left_tangent(data, len, tolerance_sq) + : tHat1 ); + Point est_tHat2( est2 + ? darray_right_tangent(data, len, tolerance_sq) + : tHat2 ); + estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); + /* We find that darray_right_tangent tends to produce better results + for our current freehand tool than full estimation. */ + if (est1) { + estimate_bi(bezier, 1, data, u, len); + if (bezier[1] != bezier[0]) { + est_tHat1 = unit_vector(bezier[1] - bezier[0]); + } + estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); + } +} + + +static void +estimate_lengths(Point bezier[], + Point const data[], double const uPrime[], unsigned const len, + Point const &tHat1, Point const &tHat2) +{ + double C[2][2]; /* Matrix C. */ + double X[2]; /* Matrix X. */ + + /* Create the C and X matrices. */ + C[0][0] = 0.0; + C[0][1] = 0.0; + C[1][0] = 0.0; + C[1][1] = 0.0; + X[0] = 0.0; + X[1] = 0.0; + + /* First and last control points of the Bezier curve are positioned exactly at the first and + last data points. */ + bezier[0] = data[0]; + bezier[3] = data[len - 1]; + + for (unsigned i = 0; i < len; i++) { + /* Bezier control point coefficients. */ + double const b0 = B0(uPrime[i]); + double const b1 = B1(uPrime[i]); + double const b2 = B2(uPrime[i]); + double const b3 = B3(uPrime[i]); + + /* rhs for eqn */ + Point const a1 = b1 * tHat1; + Point const a2 = b2 * tHat2; + + C[0][0] += dot(a1, a1); + C[0][1] += dot(a1, a2); + C[1][0] = C[0][1]; + C[1][1] += dot(a2, a2); + + /* Additional offset to the data point from the predicted point if we were to set bezier[1] + to bezier[0] and bezier[2] to bezier[3]. */ + Point const shortfall + = ( data[i] + - ( ( b0 + b1 ) * bezier[0] ) + - ( ( b2 + b3 ) * bezier[3] ) ); + X[0] += dot(a1, shortfall); + X[1] += dot(a2, shortfall); + } + + /* We've constructed a pair of equations in the form of a matrix product C * alpha = X. + Now solve for alpha. */ + double alpha_l, alpha_r; + + /* Compute the determinants of C and X. */ + double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]; + if ( det_C0_C1 != 0 ) { + /* Apparently Kramer's rule. */ + double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0]; + double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1]; + alpha_l = det_X_C1 / det_C0_C1; + alpha_r = det_C0_X / det_C0_C1; + } else { + /* The matrix is under-determined. Try requiring alpha_l == alpha_r. + * + * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same + * variable in the equations. We can do this by adding the columns of C to form a single + * column, to be multiplied by alpha to give the column vector X. + * + * We try each row in turn. + */ + double const c0 = C[0][0] + C[0][1]; + if (c0 != 0) { + alpha_l = alpha_r = X[0] / c0; + } else { + double const c1 = C[1][0] + C[1][1]; + if (c1 != 0) { + alpha_l = alpha_r = X[1] / c1; + } else { + /* Let the below code handle this. */ + alpha_l = alpha_r = 0.; + } + } + } + + /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get + coincident control points that lead to divide by zero in any subsequent + NewtonRaphsonRootFind() call.) */ + /// \todo Check whether this special-casing is necessary now that + /// NewtonRaphsonRootFind handles non-positive denominator. + if ( alpha_l < 1.0e-6 || + alpha_r < 1.0e-6 ) + { + alpha_l = alpha_r = distance(data[0], data[len-1]) / 3.0; + } + + /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and + right, respectively. */ + bezier[1] = alpha_l * tHat1 + bezier[0]; + bezier[2] = alpha_r * tHat2 + bezier[3]; + + return; +} + +static double lensq(Point const p) { + return dot(p, p); +} + +static void +estimate_bi(Point bezier[4], unsigned const ei, + Point const data[], double const u[], unsigned const len) +{ + if(!(1 <= ei && ei <= 2)) + return; + unsigned const oi = 3 - ei; + double num[2] = {0., 0.}; + double den = 0.; + for (unsigned i = 0; i < len; ++i) { + double const ui = u[i]; + double const b[4] = { + B0(ui), + B1(ui), + B2(ui), + B3(ui) + }; + + for (unsigned d = 0; d < 2; ++d) { + num[d] += b[ei] * (b[0] * bezier[0][d] + + b[oi] * bezier[oi][d] + + b[3] * bezier[3][d] + + - data[i][d]); + } + den -= b[ei] * b[ei]; + } + + if (den != 0.) { + for (unsigned d = 0; d < 2; ++d) { + bezier[ei][d] = num[d] / den; + } + } else { + bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) / 3.; + } +} + +/** + * Given set of points and their parameterization, try to find a better assignment of parameter + * values for the points. + * + * \param d Array of digitized points. + * \param u Current parameter values. + * \param bezCurve Current fitted curve. + * \param len Number of values in both d and u arrays. + * Also the size of the array that is allocated for return. + */ +static void +reparameterize(Point const d[], + unsigned const len, + double u[], + CubicBezier const &bezCurve) +{ + assert( 2 <= len ); + + unsigned const last = len - 1; + assert( bezCurve[0] == d[0] ); + assert( bezCurve[3] == d[last] ); + assert( u[0] == 0.0 ); + assert( u[last] == 1.0 ); + /* Otherwise, consider including 0 and last in the below loop. */ + + for (unsigned i = 1; i < last; i++) { + u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]); + } +} + +/** + * Use Newton-Raphson iteration to find better root. + * + * \param Q Current fitted curve + * \param P Digitized point + * \param u Parameter value for "P" + * + * \return Improved u + */ +static double +NewtonRaphsonRootFind(CubicBezier const &Q, Point const &P, double const u) +{ + assert( 0.0 <= u ); + assert( u <= 1.0 ); + + std::vector<Point> Q_u = Q.pointAndDerivatives(u, 2); + + /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the + distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the + distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum + distance from P to Q(u)). */ + Point const diff = Q_u[0] - P; + double numerator = dot(diff, Q_u[1]); + double denominator = dot(Q_u[1], Q_u[1]) + dot(diff, Q_u[2]); + + double improved_u; + if ( denominator > 0. ) { + /* One iteration of Newton-Raphson: + improved_u = u - f(u)/f'(u) */ + improved_u = u - ( numerator / denominator ); + } else { + /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather + than local minimum), so we move an arbitrary amount in the right direction. */ + if ( numerator > 0. ) { + improved_u = u * .98 - .01; + } else if ( numerator < 0. ) { + /* Deliberately asymmetrical, to reduce the chance of cycling. */ + improved_u = .031 + u * .98; + } else { + improved_u = u; + } + } + + if (!std::isfinite(improved_u)) { + improved_u = u; + } else if ( improved_u < 0.0 ) { + improved_u = 0.0; + } else if ( improved_u > 1.0 ) { + improved_u = 1.0; + } + + /* Ensure that improved_u isn't actually worse. */ + { + double const diff_lensq = lensq(diff); + for (double proportion = .125; ; proportion += .125) { + if ( lensq( Q.pointAt(improved_u) - P ) > diff_lensq ) { + if ( proportion > 1.0 ) { + //g_warning("found proportion %g", proportion); + improved_u = u; + break; + } + improved_u = ( ( 1 - proportion ) * improved_u + + proportion * u ); + } else { + break; + } + } + } + + DOUBLE_ASSERT(improved_u); + return improved_u; +} + + +/** + * Assign parameter values to digitized points using relative distances between points. + * + * \pre Parameter array u must have space for \a len items. + */ +static void +chord_length_parameterize(Point const d[], double u[], unsigned const len) +{ + if(!( 2 <= len )) + return; + + /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */ + u[0] = 0.0; + for (unsigned i = 1; i < len; i++) { + double const dist = distance(d[i], d[i-1]); + u[i] = u[i-1] + dist; + } + + /* Then scale to [0.0 .. 1.0]. */ + double tot_len = u[len - 1]; + if(!( tot_len != 0 )) + return; + if (std::isfinite(tot_len)) { + for (unsigned i = 1; i < len; ++i) { + u[i] /= tot_len; + } + } else { + /* We could do better, but this probably never happens anyway. */ + for (unsigned i = 1; i < len; ++i) { + u[i] = i / (double) ( len - 1 ); + } + } + + /** \todo + * It's been reported that u[len - 1] can differ from 1.0 on some + * systems (amd64), despite it having been calculated as x / x where x + * is isFinite and non-zero. + */ + if (u[len - 1] != 1) { + double const diff = u[len - 1] - 1; + if (fabs(diff) > 1e-13) { + assert(0); // No warnings in 2geom + //g_warning("u[len - 1] = %19g (= 1 + %19g), expecting exactly 1", + // u[len - 1], diff); + } + u[len - 1] = 1; + } + +#ifdef BEZIER_DEBUG + assert( u[0] == 0.0 && u[len - 1] == 1.0 ); + for (unsigned i = 1; i < len; i++) { + assert( u[i] >= u[i-1] ); + } +#endif +} + + + + +/** + * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum + * error is non-zero) set \a *splitPoint to the corresponding index. + * + * \pre 2 \<= len. + * \pre u[0] == 0. + * \pre u[len - 1] == 1.0. + * \post ((ret == 0.0) + * || ((*splitPoint \< len - 1) + * \&\& (*splitPoint != 0 || ret \< 0.0))). + */ +static double +compute_max_error_ratio(Point const d[], double const u[], unsigned const len, + BezierCurve const bezCurve, double const tolerance, + unsigned *const splitPoint) +{ + assert( 2 <= len ); + unsigned const last = len - 1; + assert( bezCurve[0] == d[0] ); + assert( bezCurve[3] == d[last] ); + assert( u[0] == 0.0 ); + assert( u[last] == 1.0 ); + /* I.e. assert that the error for the first & last points is zero. + * Otherwise we should include those points in the below loop. + * The assertion is also necessary to ensure 0 < splitPoint < last. + */ + + double maxDistsq = 0.0; /* Maximum error */ + double max_hook_ratio = 0.0; + unsigned snap_end = 0; + Point prev = bezCurve[0]; + for (unsigned i = 1; i <= last; i++) { + Point const curr = bezier_pt(3, bezCurve, u[i]); + double const distsq = lensq( curr - d[i] ); + if ( distsq > maxDistsq ) { + maxDistsq = distsq; + *splitPoint = i; + } + double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance); + if (max_hook_ratio < hook_ratio) { + max_hook_ratio = hook_ratio; + snap_end = i; + } + prev = curr; + } + + double const dist_ratio = std::sqrt(maxDistsq) / tolerance; + double ret; + if (max_hook_ratio <= dist_ratio) { + ret = dist_ratio; + } else { + assert(0 < snap_end); + ret = -max_hook_ratio; + *splitPoint = snap_end - 1; + } + assert( ret == 0.0 + || ( ( *splitPoint < last ) + && ( *splitPoint != 0 || ret < 0. ) ) ); + return ret; +} + +/** + * Whereas compute_max_error_ratio() checks for itself that each data point + * is near some point on the curve, this function checks that each point on + * the curve is near some data point (or near some point on the polyline + * defined by the data points, or something like that: we allow for a + * "reasonable curviness" from such a polyline). "Reasonable curviness" + * means we draw a circle centred at the midpoint of a..b, of radius + * proportional to the length |a - b|, and require that each point on the + * segment of bezCurve between the parameters of a and b be within that circle. + * If any point P on the bezCurve segment is outside of that allowable + * region (circle), then we return some metric that increases with the + * distance from P to the circle. + * + * Given that this is a fairly arbitrary criterion for finding appropriate + * places for sharp corners, we test only one point on bezCurve, namely + * the point on bezCurve with parameter halfway between our estimated + * parameters for a and b. (Alternatives are taking the farthest of a + * few parameters between those of a and b, or even using a variant of + * NewtonRaphsonFindRoot() for finding the maximum rather than minimum + * distance.) + */ +static double +compute_hook(Point const &a, Point const &b, double const u, CubicBezier const & bezCurve, + double const tolerance) +{ + Point const P = bezCurve.pointAt(u); + double const dist = distance((a+b)*.5, P); + if (dist < tolerance) { + return 0; + } + double const allowed = distance(a, b) + tolerance; + return dist / allowed; + /** \todo + * effic: Hooks are very rare. We could start by comparing + * distsq, only resorting to the more expensive L2 in cases of + * uncertainty. + */ +} + +} + +} + +#include <2geom/bezier-utils.h> + + +using std::vector; +using namespace Geom; +using namespace std; + +class PointToBezierTester: public Toy { + //std::vector<Slider> sliders; + PointHandle adjuster, adjuster2, adjuster3; + std::vector<Toggle> toggles; + Piecewise<D2<SBasis > > stroke; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_save(cr); + + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 0.5); + adjuster2.pos[0]=150; + adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.); + cairo_move_to(cr, 150, 150); + cairo_line_to(cr, 150, 450); + cairo_stroke(cr); + ostringstream val_s; + double scale0=(450-adjuster2.pos[1])/300; + double curve_precision = pow(10, scale0*5-2); + val_s << curve_precision; + draw_text(cr, adjuster2.pos, val_s.str().c_str()); + cairo_restore(cr); + + cairo_save(cr); + + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 0.5); + + if(!mouses.empty()) { + cairo_move_to(cr, mouses[0]); + for(auto & mouse : mouses) { + cairo_line_to(cr, mouse); + } + cairo_stroke(cr); + } + + if(!mouses.empty()) { + Point bezier[1000]; + int segsgenerated; + { + Timer tm; + + tm.ask_for_timeslice(); + tm.start(); + segsgenerated = bezier_fit_cubic_r(bezier, &mouses[0], + mouses.size(), curve_precision, 240); + + Timer::Time als_time = tm.lap(); + *notify << "original time = " << als_time << std::endl; + } + + if(1) { + cairo_save(cr); + cairo_set_source_rgba (cr, 0., 1., 0., 1); + cairo_move_to(cr, bezier[0]); + int bezi=1; + for(int i = 0; i < segsgenerated; i ++) { + cairo_curve_to(cr, bezier[bezi], bezier[bezi+1], bezier[bezi+2]); + bezi += 4; + } + cairo_stroke(cr); + cairo_restore(cr); + + } + { + Timer tm; + + tm.ask_for_timeslice(); + tm.start(); + segsgenerated = Geom::BezierFitter::bezier_fit_cubic_r(bezier, &mouses[0], + mouses.size(), curve_precision, 240); + + Timer::Time als_time = tm.lap(); + *notify << "experimental version time = " << als_time << std::endl; + } + + if (1) { + cairo_save(cr); + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_move_to(cr, bezier[0]); + int bezi=1; + for(int i = 0; i < segsgenerated; i ++) { + cairo_curve_to(cr, bezier[bezi], bezier[bezi+1], bezier[bezi+2]); + bezi += 4; + } + cairo_stroke(cr); + cairo_restore(cr); + } + *notify << "segments : "<< segsgenerated <<"\n"; + } + cairo_restore(cr); + + /* + Point p(25, height - 50), d(50,25); + toggles[0].bounds = Rect(p, p + d); + p+= Point(75, 0); + toggles[1].bounds = Rect(p, p + d); + draw_toggles(cr, toggles); + */ + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + void key_hit(GdkEventKey *e) override { + if(e->keyval == 's') toggles[0].toggle(); + redraw(); + } + vector<Point> mouses; + int mouse_drag; + + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + if(!selected) { + mouse_drag = 1; + mouses.clear(); + } + } + + + void mouse_moved(GdkEventMotion* e) override { + if(mouse_drag) { + mouses.emplace_back(e->x, e->y); + redraw(); + } else { + Toy::mouse_moved(e); + } + } + + void mouse_released(GdkEventButton* e) override { + mouse_drag = 0; + stroke.clear(); + stroke.push_cut(0); + Path pth; + for(unsigned i = 2; i < mouses.size(); i+=2) { + pth.append(QuadraticBezier(mouses[i-2], mouses[i-1], mouses[i])); + } + stroke = pth.toPwSb(); + Toy::mouse_released(e); + } + + PointToBezierTester() { + adjuster2.pos = Geom::Point(150,300); + handles.push_back(&adjuster2); + toggles.emplace_back("Seq", false); + toggles.emplace_back("Linfty", true); + //} + //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t")); + //handles.push_back(&(sliders[0])); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new PointToBezierTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/pencil.cpp b/src/toys/pencil.cpp new file mode 100644 index 0000000..1ac3587 --- /dev/null +++ b/src/toys/pencil.cpp @@ -0,0 +1,374 @@ +/* + * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier. + * + * Copyright 2007 jf barraud. + * 2008 njh + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +// mainly experimental atm... +// do not expect to find anything understandable here atm. + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/basic-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZERO 1e-7 + +using std::vector; +using namespace Geom; +using namespace std; + +#include <stdio.h> +#include <gsl/gsl_poly.h> + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p, double hscale=1., double vscale=1.) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(150+p.cuts[i]*hscale, 150+p.cuts[i+1]*hscale); + B[1] = Linear(450) - p[i]*vscale; + cairo_d2_sb(cr, B); + } +} + +//=================================================================================== + +D2<SBasis> +naive_sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){ + + Piecewise<D2<SBasis> > dM = derivative(M); + Point M0 = M(t0); + Point dM0 = dM(t0)*(t1-t0); + Point M1 = M(t1); + Point dM1 = dM(t1)*(t1-t0); + D2<SBasis> result; + for (unsigned dim=0; dim<2; dim++){ + SBasis r(2, Linear()); + r[0] = Linear(M0[dim],M1[dim]); + r[1] = Linear(M0[dim]-M1[dim]+dM0[dim],-(M0[dim]-M1[dim]+dM1[dim])); + result[dim] = r; + } + return result; +} + +D2<SBasis> +sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){ + Point M0,dM0,d2M0,M1,dM1,d2M1,A0,V0,A1,V1; + Piecewise<D2<SBasis> > dM,d2M; + dM=derivative(M); + d2M=derivative(dM); + M0 =M(t0); + M1 =M(t1); + dM0 =dM(t0); + dM1 =dM(t1); + d2M0=d2M(t0); + d2M1=d2M(t1); + A0=M(t0); + A1=M(t1); + + std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0,d2M1); + if (candidates.size()==0){ + return D2<SBasis>(SBasis(M0[X],M1[X]),SBasis(M0[Y],M1[Y])) ; + } + double maxlength = -1; + unsigned best = 0; + for (unsigned i=0; i<candidates.size(); i++){ + double l = length(candidates[i]); + if ( l < maxlength || maxlength < 0 ){ + maxlength = l; + best = i; + } + } + return candidates[best]; +} +#include <2geom/sbasis-to-bezier.h> + +int goal_function_type = 0; + +double goal_function(Piecewise<D2<SBasis> >const &A, + Piecewise<D2<SBasis> >const&B) { + if(goal_function_type) { + OptInterval bnds = bounds_fast(dot(derivative(A), rot90(derivative(B)))); + //double h_dist = bnds.dimensions().length(); +//0 is in the rect!, TODO:gain factor ~2 for free. +// njh: not really, the benefit is actually rather small. + double h_dist = 0; + if(bnds) + h_dist = bnds->extent(); + return h_dist ; + } else { + Rect bnds = *bounds_fast(A - B); + return max(bnds.min().length(), bnds.max().length()); + } +} + +int recursive_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) { + if (t0>=t1) return 0;//TODO: fix me... + if (t0+0.001>=t1) return 0;//TODO: fix me... + + //TODO: don't re-compute derivative(f) at each try!! + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1); + + if(k_bez[0].size() > 1 and k_bez[1].size() > 1) { + Piecewise<SBasis> s = arcLengthSb(k_bez); + s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1(); + s += t0; + double h_dist = goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez)); + if(h_dist < precision) { + cairo_save(cr); + cairo_set_line_width (cr, 0.93); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + draw_handle(cr, k_bez.at0()); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + cairo_restore(cr); + return 1; + } + } + //TODO: find a better place where to cut (at the worst fit?). + return recursive_curvature_fitter(cr, f, t0, (t0+t1)/2, precision) + + recursive_curvature_fitter(cr, f, (t0+t1)/2, t1, precision); +} + +double single_curvature_fitter(Piecewise<D2<SBasis> > const &f, double t0, double t1) { + if (t0>=t1) return 0;//TODO: fix me... + if (t0+0.001>=t1) return 0;//TODO: fix me... + + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1); + + if(k_bez[0].size() > 1 and k_bez[1].size() > 1) { + Piecewise<SBasis> s = arcLengthSb(k_bez); + s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1(); + s += t0; + return goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez)); + } + return 1e100; +} + +struct quadratic_params +{ + Piecewise<D2<SBasis> > const *f; + double t0, precision; +}; + + +double quadratic (double x, void *params) { + struct quadratic_params *p + = (struct quadratic_params *) params; + + return single_curvature_fitter(*p->f, p->t0, x) - p->precision; +} + +#include <stdio.h> +#include <gsl/gsl_errno.h> +#include <gsl/gsl_math.h> +#include <gsl/gsl_roots.h> + + +int sequential_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) { + if(t0 >= t1) return 0; + + double r = t1; + if(single_curvature_fitter(f, t0, t1) > precision) { + int status; + int iter = 0, max_iter = 100; + const gsl_root_fsolver_type *T; + gsl_root_fsolver *s; + gsl_function F; + struct quadratic_params params = {&f, t0, precision}; + + F.function = &quadratic; + F.params = ¶ms; + + T = gsl_root_fsolver_brent; + s = gsl_root_fsolver_alloc (T); + gsl_root_fsolver_set (s, &F, t0, t1); + + do + { + iter++; + status = gsl_root_fsolver_iterate (s); + r = gsl_root_fsolver_root (s); + double x_lo = gsl_root_fsolver_x_lower (s); + double x_hi = gsl_root_fsolver_x_upper (s); + status = gsl_root_test_interval (x_lo, x_hi, + 0, 0.001); + + + } + while (status == GSL_CONTINUE && iter < max_iter); + + double x_lo = gsl_root_fsolver_x_lower (s); + double x_hi = gsl_root_fsolver_x_upper (s); + printf ("%5d [%.7f, %.7f] %.7f %.7f\n", + iter, x_lo, x_hi, + r, + x_hi - x_lo); + gsl_root_fsolver_free (s); + } + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,r); + + cairo_save(cr); + cairo_set_line_width (cr, 0.93); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + draw_handle(cr, k_bez.at0()); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + cairo_restore(cr); + + if(r < t1) + return sequential_curvature_fitter(cr, f, r, t1, precision) + 1; + return 1; +} + + +class SbToBezierTester: public Toy { + //std::vector<Slider> sliders; + PointHandle adjuster, adjuster2, adjuster3; + std::vector<Toggle> toggles; + Piecewise<D2<SBasis > > stroke; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_save(cr); + + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 0.5); + + if(!mouses.empty()) { + cairo_move_to(cr, mouses[0]); + for(auto & mouse : mouses) { + cairo_line_to(cr, mouse); + } + cairo_stroke(cr); + } + adjuster2.pos[0]=150; + adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.); + cairo_move_to(cr, 150, 150); + cairo_line_to(cr, 150, 450); + cairo_stroke(cr); + ostringstream val_s; + double scale0=(450-adjuster2.pos[1])/300; + double curve_precision = pow(10, scale0*5-2); + val_s << curve_precision; + draw_text(cr, adjuster2.pos, val_s.str().c_str()); + + + Piecewise<D2<SBasis> > f_as_pw = stroke; + cairo_pw_d2_sb(cr, f_as_pw); + cairo_stroke(cr); + if(!stroke.empty()) { + f_as_pw = arc_length_parametrization(f_as_pw); + int segs = 0; + goal_function_type = toggles[1].on; + if(toggles[0].on) + segs = sequential_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(), curve_precision); + else { + segs = recursive_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(),curve_precision); + } + *notify << " total segments: "<< segs <<"\n"; + } + cairo_restore(cr); + Point p(25, height - 50), d(50,25); + toggles[0].bounds = Rect(p, p + d); + p+= Point(75, 0); + toggles[1].bounds = Rect(p, p + d); + draw_toggles(cr, toggles); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + void key_hit(GdkEventKey *e) override { + if(e->keyval == 's') toggles[0].toggle(); + redraw(); + } + vector<Point> mouses; + int mouse_drag; + + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + if(!selected) { + mouse_drag = 1; + mouses.clear(); + } + } + + + void mouse_moved(GdkEventMotion* e) override { + if(mouse_drag) { + mouses.emplace_back(e->x, e->y); + redraw(); + } else { + Toy::mouse_moved(e); + } + } + + void mouse_released(GdkEventButton* e) override { + mouse_drag = 0; + stroke.clear(); + stroke.push_cut(0); + Path pth; + for(unsigned i = 2; i < mouses.size(); i+=2) { + pth.append(QuadraticBezier(mouses[i-2], mouses[i-1], mouses[i])); + } + stroke = pth.toPwSb(); + Toy::mouse_released(e); + } + + SbToBezierTester() { + adjuster.pos = Geom::Point(150+300*uniform(),150+300*uniform()); + handles.push_back(&adjuster); + adjuster2.pos = Geom::Point(150,300); + handles.push_back(&adjuster2); + adjuster3.pos = Geom::Point(450,300); + handles.push_back(&adjuster3); + toggles.emplace_back("Seq", false); + toggles.emplace_back("Linfty", true); + //} + //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t")); + //handles.push_back(&(sliders[0])); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SbToBezierTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/plane3d.cpp b/src/toys/plane3d.cpp new file mode 100644 index 0000000..8710869 --- /dev/null +++ b/src/toys/plane3d.cpp @@ -0,0 +1,130 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> + +#include <gsl/gsl_matrix.h> + +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +class Box3d: public Toy { + double tmat[3][4]; + PointHandle origin_handle; + PointSetHandle vanishing_points_handles; + PathVector paths_a; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Geom::Point orig = origin_handle.pos; + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + /* create the transformation matrix for the map P^3 --> P^2 that has the following effect: + (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0) + (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1) + (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2) + (0 : 0 : 0 : 1) --> origin (= handle #3) + */ + for (int j = 0; j < 4; ++j) { + tmat[0][j] = vanishing_points_handles.pts[j][0]; + tmat[1][j] = vanishing_points_handles.pts[j][1]; + tmat[2][j] = 1; + } + + *notify << "Projection matrix:" << endl; + for (auto & i : tmat) { + for (double j : i) { + *notify << j << " "; + } + *notify << endl; + } + + for(const auto & i : paths_a) { + Piecewise<D2<SBasis> > path_a_pw = i.toPwSb(); + + D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw); + Piecewise<SBasis> preimage[4]; + + preimage[0] = (B[0] - orig[0]) / 100; + preimage[1] = -(B[1] - orig[1]) / 100; + Piecewise<SBasis> res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = preimage[0] * tmat[j][0] + + preimage[1] * tmat[j][1] + + tmat[j][3]; + } + + //if (fabs (res[2]) > 0.000001) { + D2<Piecewise<SBasis> > result(divide(res[0],res[2], 2), + divide(res[1],res[2], 2)); + + cairo_d2_pw_sb(cr, result); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="ptitle.svgd"; + if(argc > 1) + path_a_name = argv[1]; + paths_a = read_svgd(path_a_name); + assert(paths_a.size() > 0); + + // Finite images of the three vanishing points and the origin + handles.push_back(&origin_handle); + handles.push_back(&vanishing_points_handles); + vanishing_points_handles.push_back(550,350); + vanishing_points_handles.push_back(150,300); + vanishing_points_handles.push_back(380,40); + vanishing_points_handles.push_back(340,450); + // plane origin + origin_handle.pos = Point(180,65); + } + //int should_draw_bounds() {return 1;} + + Geom::Point proj_image (cairo_t *cr, const double pt[4], const vector<Geom::Point> &/*handles*/) + { + double res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = 0; + for (int i = 0; i < 3; ++i) + res[j] += tmat[j][i] * pt[i]; + } + if (fabs (res[2]) > 0.000001) { + Geom::Point result = Geom::Point (res[0]/res[2], res[1]/res[2]); + draw_handle(cr, result); + return result; + } + assert(0); // unclipped point + return Geom::Point(0,0); + } + +}; + +int main(int argc, char **argv) { + init(argc, argv, new Box3d); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/plane3d.py b/src/toys/plane3d.py new file mode 100644 index 0000000..0ffcb51 --- /dev/null +++ b/src/toys/plane3d.py @@ -0,0 +1,78 @@ +#!/usr/bin/python + +import py2geom +import toyframework +import random,gtk +import numpy +from py2geom_glue import * + +class Box3d(toyframework.Toy): + def __init__(self): + toyframework.Toy.__init__(self) + self.tmat = numpy.zeros([3,4]) + # plane origin + self.origin_handle = toyframework.PointHandle(180,65) + self.handles.append(self.origin_handle) + self.vanishing_points_handles = toyframework.PointSetHandle() + path_a_name="ptitle.svgd" + import sys + if len(sys.argv) > 1: + path_a_name = sys.argv[1] + self.paths_a = py2geom.read_svgd(path_a_name) + + + # Finite images of the three vanishing points and the origin + self.handles.append(self.vanishing_points_handles) + self.vanishing_points_handles.append(550,350) + self.vanishing_points_handles.append(150,300) + self.vanishing_points_handles.append(380,40) + self.vanishing_points_handles.append(340,450) + def draw(self, cr, pos, save): + orig = self.origin_handle.pos; + cr.set_source_rgba (0., 0.125, 0, 1) + + # create the transformation matrix for the map P^3 --> P^2 that has the following effect: + # (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0) + # (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1) + # (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2) + # (0 : 0 : 0 : 1) --> origin (= handle #3) + + tmat = numpy.zeros([3,4]) + for j in range(4): + tmat[0][j] = self.vanishing_points_handles.pts[j][0] + tmat[1][j] = self.vanishing_points_handles.pts[j][1] + tmat[2][j] = 1 + + self.notify = "Projection matrix:\n" + for i in range(3): + for j in range(4): + self.notify += str(tmat[i][j]) + " " + self.notify += '\n' + + for p in self.paths_a: + B = py2geom.make_cuts_independant(p.toPwSb()) + preimage = [None]*4 + + preimage[0] = (B[0] - orig[0]) / 100; + preimage[1] = -(B[1] - orig[1]) / 100; + Piecewise<SBasis> res[3]; + for j in range(3): + res[j] = (preimage[0] * tmat[j][0] + + preimage[1] * tmat[j][1] + + + tmat[j][3]) + + result = D2PiecewiseSBasis(divide(res[0],res[2], 2), + divide(res[1],res[2], 2)) + + toyframework.cairo_d2_pw(cr, result) + cr.set_source_rgba (0., 0.125, 0, 1) + cr.stroke() + + toyframework.Toy.draw(self, cr, pos, save) + +t = Box3d() +import sys + +toyframework.init(sys.argv, t, 500, 500) + + diff --git a/src/toys/point-curve-nearest-time.cpp b/src/toys/point-curve-nearest-time.cpp new file mode 100644 index 0000000..0067920 --- /dev/null +++ b/src/toys/point-curve-nearest-time.cpp @@ -0,0 +1,397 @@ +/* + * point-curve nearest point routines testing + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/curves.h> +#include <2geom/angle.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/piecewise.h> + +#include <2geom/svg-path-parser.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/transforms.h> +#include <2geom/pathvector.h> + + +#include <algorithm> + +using namespace Geom; + +std::ostream& +operator<< (std::ostream &out, PathVectorTime const &pvp) +{ + return out << pvp.path_index << "." << pvp.curve_index << "." << pvp.t; +} + +class NearestPoints : public Toy +{ + enum menu_item_t + { + FIRST_ITEM = 1, + LINE_SEGMENT = FIRST_ITEM, + ELLIPTICAL_ARC, + SBASIS_CURVE, + PIECEWISE, + PATH, + PATH_SVGD, + TOTAL_ITEMS + }; + + static const char* menu_items[TOTAL_ITEMS]; + +private: + PathVector paths_b; + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + + Point p = ph.pos; + Point np = p; + std::vector<Point> nps; + + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgb(cr, 0,0,0); + switch ( choice ) + { + case '1': + { + LineSegment seg(psh.pts[0], psh.pts[1]); + cairo_move_to(cr, psh.pts[0]); + cairo_curve(cr, seg); + double t = seg.nearestTime(p); + np = seg.pointAt(t); + if ( toggles[0].on ) + { + nps.push_back(np); + } + break; + } + case '2': + { + EllipticalArc earc; + bool earc_constraints_satisfied = true; + try + { + earc.set(psh.pts[0], 200, 150, 0, true, true, psh.pts[1]); + } + catch( RangeError e ) + { + earc_constraints_satisfied = false; + } + if ( earc_constraints_satisfied ) + { + cairo_d2_sb(cr, earc.toSBasis()); + if ( toggles[0].on ) + { + std::vector<double> t = earc.allNearestTimes(p); + for (double i : t) + nps.push_back(earc.pointAt(i)); + } + else + { + double t = earc.nearestTime(p); + np = earc.pointAt(t); + } + } + break; + } + case '3': + { + D2<SBasis> A = psh.asBezier(); + cairo_d2_sb(cr, A); + if ( toggles[0].on ) + { + std::vector<double> t = Geom::all_nearest_times(p, A); + for (double i : t) + nps.push_back(A(i)); + } + else + { + double t = nearest_time(p, A); + np = A(t); + } + break; + } + case '4': + { + D2<SBasis> A = handles_to_sbasis(psh.pts.begin(), 3); + D2<SBasis> B = handles_to_sbasis(psh.pts.begin() + 3, 3); + D2<SBasis> C = handles_to_sbasis(psh.pts.begin() + 6, 3); + D2<SBasis> D = handles_to_sbasis(psh.pts.begin() + 9, 3); + cairo_d2_sb(cr, A); + cairo_d2_sb(cr, B); + cairo_d2_sb(cr, C); + cairo_d2_sb(cr, D); + Piecewise< D2<SBasis> > pwc; + pwc.push_cut(0); + pwc.push_seg(A); + pwc.push_cut(0.25); + pwc.push_seg(B); + pwc.push_cut(0.50); + pwc.push_seg(C); + pwc.push_cut(0.75); + pwc.push_seg(D); + pwc.push_cut(1); + if ( toggles[0].on ) + { + std::vector<double> t = Geom::all_nearest_times(p, pwc); + for (double i : t) + nps.push_back(pwc(i)); + } + else + { + double t = Geom::nearest_time(p, pwc); + np = pwc(t); + } + break; + } + case '5': + { + closed_toggle = true; + BezierCurveN<3> A(psh.pts[0], psh.pts[1], psh.pts[2], psh.pts[3]); + BezierCurveN<2> B(psh.pts[3], psh.pts[4], psh.pts[5]); + BezierCurveN<3> C(psh.pts[5], psh.pts[6], psh.pts[7], psh.pts[8]); + Path path; + path.append(A); + path.append(B); + path.append(C); + EllipticalArc D; + bool earc_constraints_satisfied = true; + try + { + D.set(psh.pts[8], 160, 80, 0, true, true, psh.pts[9]); + } + catch( RangeError e ) + { + earc_constraints_satisfied = false; + } + if ( earc_constraints_satisfied ) path.append(D); + if ( toggles[1].on ) path.close(true); + + cairo_path(cr, path); + + if ( toggles[0].on ) + { + std::vector<double> t = path.allNearestTimes(p); + for (double i : t) + nps.push_back(path.pointAt(i)); + } + else + { + PathTime pt = path.nearestTime(p); + np = path.pointAt(pt); + } + break; + } + case '6': + { + closed_toggle = true; + PathVector pathv = paths_b*Translate(psh.pts[0]-paths_b[0][0].initialPoint()); + //std::cout << pathv.size() << std::endl; + OptRect optRect = bounds_fast(pathv); + + cairo_rectangle(cr, *optRect); + cairo_stroke(cr); + + cairo_path(cr, pathv); + + if ( toggles[0].on ) + { + std::vector<PathVectorTime> t = pathv.allNearestTimes(p); + for (auto & i : t) + nps.push_back(pathv.pointAt(i)); + } + else + { + //std::optional<PathVectorTime> + double s = 0, e = 1; + draw_cross(cr, pathv[0].pointAt(s)); + draw_cross(cr, pathv[0].pointAt(e)); + double t = pathv[0][0].nearestTime(p, 0, 1); + if(t) { + *notify << p+psh.pts[0] << std::endl; + *notify << t << std::endl; + np = pathv[0].pointAt(t); + } + } + break; + } + default: + { + *notify << std::endl; + for (int i = FIRST_ITEM; i < TOTAL_ITEMS; ++i) + { + *notify << " " << i << " - " << menu_items[i] << std::endl; + } + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + + if ( toggles[0].on ) + { + for (auto & np : nps) + { + cairo_move_to(cr, p); + cairo_line_to(cr, np); + } + } + else + { + cairo_move_to(cr, p); + cairo_line_to(cr, np); + } + cairo_stroke(cr); + + toggles[0].bounds = Rect( Point(10, height - 50), Point(10, height - 50) + Point(80,25) ); + toggles[0].draw(cr); + if ( closed_toggle ) + { + toggles[1].bounds = Rect( Point(100, height - 50), Point(100, height - 50) + Point(80,25) ); + toggles[1].draw(cr); + closed_toggle = false; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void key_hit(GdkEventKey *e) override + { + choice = e->keyval; + switch ( choice ) + { + case '1': + total_handles = 2; + break; + case '2': + total_handles = 2; + break; + case '3': + total_handles = 6; + break; + case '4': + total_handles = 13; + break; + case '5': + total_handles = 10; + break; + case '6': + total_handles = 1; + break; + default: + total_handles = 0; + } + psh.pts.clear(); + psh.push_back( 262.6037,35.824151); + psh.pts[0] += Point(300,300); + psh.push_back(0,0); + psh.pts[1] += psh.pts[0]; + psh.push_back(-92.64892,-187.405851); + psh.pts[2] += psh.pts[0]; + psh.push_back(30,-149.999981); + psh.pts[3] += psh.pts[0]; + for ( unsigned int i = 0; i < total_handles; ++i ) + { + psh.push_back(uniform()*400, uniform()*400); + } + ph.pos = Point(uniform()*400, uniform()*400); + redraw(); + } + + void mouse_pressed(GdkEventButton* e) override + { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + +public: + void first_time(int argc, char** argv) override { + const char *path_b_name="star.svgd"; + if(argc > 1) + path_b_name = argv[1]; + paths_b = read_svgd(path_b_name); + } + NearestPoints() + : total_handles(0), choice('0'), closed_toggle(false) + { + handles.push_back(&psh); + handles.push_back(&ph); + ph.pos = Point(uniform()*400, uniform()*400); + toggles.emplace_back("ALL NP", false ); + toggles.emplace_back("CLOSED", false ); + } + +private: + PointSetHandle psh; + PointHandle ph; + std::vector<Toggle> toggles; + unsigned int total_handles; + char choice; + bool closed_toggle; +}; + +const char* NearestPoints::menu_items[] = +{ + "", + "LineSegment", + "EllipticalArc", + "SBasisCurve", + "Piecewise", + "Path", + "Path from SVGD" +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new NearestPoints() ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/portion-test.cpp b/src/toys/portion-test.cpp new file mode 100644 index 0000000..bc64db3 --- /dev/null +++ b/src/toys/portion-test.cpp @@ -0,0 +1,105 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +using std::vector; +using namespace Geom; +using namespace std; + +// TODO: +// use path2 +// replace Ray stuff with path2 line segments. + +//----------------------------------------------- + +class PortionTester: public Toy { + PointSetHandle curve_handle; + PointHandle sample_point1, sample_point2; + std::vector<Toggle> toggles; + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + draw_toggles(cr, toggles); + D2<SBasis> B = curve_handle.asBezier(); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + sample_point1.pos[1]=400; + sample_point1.pos[0]=std::max(150.,sample_point1.pos[0]); + sample_point1.pos[0]=std::min(450.,sample_point1.pos[0]); + sample_point2.pos[1]=400; + sample_point2.pos[0]=std::max(150.,sample_point2.pos[0]); + sample_point2.pos[0]=std::min(450.,sample_point2.pos[0]); + cairo_move_to(cr, Geom::Point(150,400)); + cairo_line_to(cr, Geom::Point(450,400)); + cairo_set_source_rgba (cr, 0., 0., 0.5, 0.8); + cairo_stroke(cr); + + double t0=std::max(0.,std::min(1.,(sample_point1.pos[0]-150)/300.)); + double t1=std::max(0.,std::min(1.,(sample_point2.pos[0]-150)/300.)); + + Path P; + P.append(B); + + if (toggles[0].on) { + if (toggles[1].on) + cairo_curve(cr, P.portion(t0,t1)[0]); + else + cairo_path(cr, P.portion(t0,t1)); + } else + cairo_d2_sb(cr, portion(B,t0,t1)); + + + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + PortionTester(){ + toggles.emplace_back("Path", true); + toggles[0].bounds = Rect(Point(10,100), Point(100, 130)); + toggles.emplace_back("Curve", true); + toggles[1].bounds = Rect(Point(10,130), Point(100, 160)); + if(handles.empty()) { + handles.push_back(&curve_handle); + handles.push_back(&sample_point1); + handles.push_back(&sample_point2); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + sample_point1.pos = Geom::Point(250,300); + sample_point2.pos = Geom::Point(350,300); + } + } +}; + +int main(int argc, char **argv) { + std::cout << "testing unit_normal(multidim_sbasis) based offset." << std::endl; + init(argc, argv, new PortionTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/precise-flat.cpp b/src/toys/precise-flat.cpp new file mode 100644 index 0000000..0113619 --- /dev/null +++ b/src/toys/precise-flat.cpp @@ -0,0 +1,86 @@ +/** + * efficient and precise flattening of curves. + * incomplete rewrite (njh) + */ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +class PreciseFlat: public Toy { + PointSetHandle hand; +void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 0.5); + + D2<SBasis> B = hand.asBezier(); + D2<SBasis> dB = derivative(B); + D2<SBasis> ddB = derivative(dB); + cairo_set_source_rgb(cr, 0,0,0); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + // draw the longest chord that is no worse than tol from the curve. + + Geom::Point st = unit_vector(dB(0)); + double s3 = fabs(dot(hand.pts[2] - hand.pts[0], rot90(st))); + + SBasis inflect = dot(dB, rot90(ddB)); + std::vector<double> rts = roots(inflect); + double f = 3; + for(double rt : rts) { + draw_handle(cr, B(rt)); + + double tp = rt; + Geom::Point st = unit_vector(dB(tp)); + Geom::Point O = B(tp); + double s4 = fabs(dot(hand.pts[3] - O, rot90(st))); + double tf = pow(f/s4, 1./3); + Geom::Point t1p = B(tp + tf*(1-tp)); + Geom::Point t1m = B(tp - tf*(1-tp)); + cairo_move_to(cr, t1m); + cairo_line_to(cr, t1p); + cairo_stroke(cr); + //std::cout << tp << ", " << t1m << ", " << t1p << std::endl; + } + + cairo_move_to(cr, B(0)); + double t0 = 2*sqrt(f/(3*s3)); + //std::cout << t0 << std::endl; + cairo_line_to(cr, B(t0)); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); +} + +public: +PreciseFlat () { + for(unsigned i = 0; i < 4; i++) + hand.pts.emplace_back(uniform()*400, uniform()*400); + handles.push_back(&hand); +} + +}; + +int main(int argc, char **argv) { + init(argc, argv, new PreciseFlat()); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/pw-compose-test.cpp b/src/toys/pw-compose-test.cpp new file mode 100644 index 0000000..a7e9438 --- /dev/null +++ b/src/toys/pw-compose-test.cpp @@ -0,0 +1,98 @@ +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class PwToy: public Toy { +public: + vector<PointSetHandle*> pw_handles; + PointSetHandle slids; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0.5, 0, 1); + cairo_set_line_width (cr, 1); + + D2<Piecewise<SBasis> > pws; + for(unsigned i = 0; i < pw_handles.size(); i++) { + D2<SBasis> foo = pw_handles[i]->asBezier(); + cairo_d2_sb(cr, foo); + for(unsigned d = 0; d < 2; d++) { + pws[d].cuts.push_back(150*i); + pws[d].segs.push_back(foo[d]); + } + } + for(unsigned d = 0; d < 2; d++) + pws[d].cuts.push_back(150*pw_handles.size()); + + slids.pts[0][1]=450; + slids.pts[1][1]=450; + slids.pts[2][1]=450; + slids.pts[3][1]=450; + + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + D2<SBasis> foo = slids.asBezier(); + SBasis g = foo[0] - Linear(150); + cairo_d2_sb(cr, foo); + for(unsigned i=0;i<20;i++){ + double t=i/20.; + draw_handle(cr, foo(t)); + } + cairo_stroke(cr); + foo[1]=foo[0]; + foo[0]=Linear(150,450); + cairo_d2_sb(cr, foo); + + cairo_d2_pw_sb(cr, pws); + + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0.9, 0., 0., 1); + D2<Piecewise<SBasis> > res = compose(pws, Piecewise<SBasis>(g)); + cairo_d2_pw_sb(cr, res); + for(unsigned i=0;i<20;i++){ + double t=(res[0].cuts.back()-res[0].cuts.front())*i/20.; + draw_handle(cr, Point(res[0](t),res[1](t))); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + bool should_draw_numbers() override { return false; } + + public: + PwToy () { + unsigned segs = 5; + unsigned handles_per_seg = 4; + double x = 150; + for(unsigned a = 0; a < segs; a++) { + PointSetHandle* psh = new PointSetHandle; + + for(unsigned i = 0; i < handles_per_seg; i++, x+= 25) + psh->push_back(Point(x, uniform() * 150)); + pw_handles.push_back(psh); + handles.push_back(psh); + } + for(unsigned i = 0; i < 4; i++) + slids.push_back(Point(150 + segs*50*i,100)); + handles.push_back(&slids); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new PwToy()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/pw-funcs.cpp b/src/toys/pw-funcs.cpp new file mode 100644 index 0000000..f4c6471 --- /dev/null +++ b/src/toys/pw-funcs.cpp @@ -0,0 +1,100 @@ +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> + +using namespace Geom; +using namespace std; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = p[i]; + cairo_d2_sb(cr, B); + } +} + +void cairo_horiz(cairo_t *cr, double y, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, i, y); + cairo_rel_line_to(cr, 0, 10); + } +} + +void cairo_vert(cairo_t *cr, double x, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, x, i); + cairo_rel_line_to(cr, 10, 0); + } +} + +Piecewise<SBasis> log(Interval in) { + Piecewise<SBasis> I = integral(Geom::reciprocal(Linear(in.min(), in.max()))); + return I + Piecewise<SBasis> (-I.segs[0][0] + log(in.min())); +} + +Piecewise<SBasis> xlogx(Interval in) { + Piecewise<SBasis> I = integral(log(in) + Piecewise<SBasis>(1)); + return I + Piecewise<SBasis> (-I.segs[0][0] + in.min()*log(in.min())); +} + +class PwToy: public Toy { + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 1); + + Piecewise<SBasis> pws; + + //pws = Geom::cos(Linear(0,100)) + 3; + pws = Geom::sqrt(Linear(0,100)); + //pws = log(Interval(1,8)); + //Piecewise<SBasis> l(Linear(-100,100)); + //Piecewise<SBasis> one(Linear(1,1)); + //pws = Geom::reciprocal(l*l + one)*l + l; + //pws = xlogx(Interval(0.5,3)); + //pws = Geom::reciprocal(pws); + //pws = -integral(Geom::reciprocal(Linear(1,2)))*Piecewise<SBasis>(Linear(1,2)); + + pws = -pws*width/4 + width/2; + pws.scaleDomain(width/2); + pws.offsetDomain(width/4); + + cairo_pw(cr, pws); + + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0., 0., .5, 1.); + cairo_horiz(cr, 500, pws.cuts); + cairo_stroke(cr); + + *notify << "total pieces: " << pws.size(); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + bool should_draw_numbers() override { return false; } + int should_draw_bounds() override { return 2; } + public: + PwToy () {} +}; + +int main(int argc, char **argv) { + init(argc, argv, new PwToy()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/pw-toy.cpp b/src/toys/pw-toy.cpp new file mode 100644 index 0000000..93ca8ea --- /dev/null +++ b/src/toys/pw-toy.cpp @@ -0,0 +1,117 @@ +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> + +using namespace Geom; +using namespace std; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = Linear(150) + p[i]; + cairo_d2_sb(cr, B); + } +} + +void cairo_horiz(cairo_t *cr, double y, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, i, y); + cairo_rel_line_to(cr, 0, 10); + } +} + +void cairo_vert(cairo_t *cr, double x, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, x, i); + cairo_rel_line_to(cr, 10, 0); + } +} + +#include "pwsbhandle.cpp" // FIXME: This looks like it may give problems later, (including a .cpp file) + +class PwToy: public Toy { + unsigned segs, handles_per_curve, curves; + PWSBHandle pwsbh[2]; + PointHandle interval_test[2]; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 1); + + std::vector<Piecewise<SBasis> > pws(curves); + for(unsigned a = 0; a < curves; a++) { + pws[a] = pwsbh[a].value(); + cairo_pw(cr, pws[a]); + } + cairo_stroke(cr); + + Piecewise<SBasis> pw_out = pws[0] + pws[1]; + + cairo_set_source_rgba (cr, 0., 0., .5, 1.); + cairo_horiz(cr, 500, pw_out.cuts); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0., .5, 1.); + cairo_pw(cr, pws[0] + pws[1]); + cairo_stroke(cr); + + Interval bs = *bounds_local(pw_out, Interval(interval_test[0].pos[0], + interval_test[1].pos[0])); + vector<double> vec; + vec.push_back(bs.min() + 150); vec.push_back(bs.max() + 150); + cairo_set_source_rgba (cr, .5, 0., 0., 1.); + cairo_vert(cr, 100, vec); + cairo_stroke(cr); + + /* Portion demonstration + Piecewise<SBasis> pw_out = portion(pws[0], handles[handles.size() - 2][0], handles[handles.size() - 1][0]); + cairo_set_source_rgba (cr, 0, .5, 0, .25); + cairo_set_line_width(cr, 3); + cairo_pw(cr, pw_out); + cairo_stroke(cr); + */ + + *notify << pws[0].segN(interval_test[0].pos[0]) << "; " << pws[0].segT(interval_test[0].pos[0]); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + bool should_draw_numbers() override { return false; } + + public: + PwToy () { + segs = 3; + handles_per_curve = 4 * segs; + curves = 2; + for(unsigned a = 0; a < curves; a++) { + pwsbh[a] = PWSBHandle(4, 3); + handles.push_back(&pwsbh[a]); + for(unsigned i = 0; i < handles_per_curve; i++) + pwsbh[a].push_back(150 + 300*i/(4*segs), uniform() * 150 + 150 - 50 * a); + } + interval_test[0].pos = Point(150, 400); + interval_test[1].pos = Point(300, 400); + handles.push_back(&interval_test[0]); + handles.push_back(&interval_test[1]); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new PwToy()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/pw-toy.py b/src/toys/pw-toy.py new file mode 100644 index 0000000..011c917 --- /dev/null +++ b/src/toys/pw-toy.py @@ -0,0 +1,180 @@ +#!/usr/bin/python + +import py2geom +import toyframework +import random,gtk +from py2geom_glue import * + +def cairo_pw(cr, p): + for i in range(p.size()): + a,b = p.cuts[i], p.cuts[i+1] + bez = sbasis_to_bezier(p[i], 0) + cr.move_to(a, bez[0]) + cr.curve_to(lerp(a, b, 1./3), bez[1], + lerp(a,b, 2./3), bez[2], + b, bez[3]) + +def cairo_horiz(cr, y, ps): + for p in ps: + cr.move_to(p, y); + cr.rel_line_to(0, 10) + +def cairo_vert(cr, x, ps): + for p in ps: + cr.move_to(x, p) + cr.rel_line_to(10, 0) + +def l2s(l): + sb = py2geom.SBasis(l) + return sb + +def constant(l): + pws = py2geom.PiecewiseSBasis() + + pws.push_cut(0) + pws.push_seg(l2s(py2geom.Linear(l,l)) ) + pws.push_cut(1000) + return pws + +X = l2s(py2geom.Linear(0, 1)) +OmX = l2s(py2geom.Linear(1, 0)) +def bezier_to_sbasis(handles, order): + if(order == 0): + return l2s(py2geom.Linear(handles[0])) + elif(order == 1): + return l2s(py2geom.Linear(handles[0], handles[1])) + else: + return (py2geom.multiply(OmX, bezier_to_sbasis(handles[:-1], order-1)) + + py2geom.multiply(X, bezier_to_sbasis(handles[1:], order-1))) + + +def bez_to_sbasis(handles, order): + return bezier_to_sbasis([h[1] for h in handles], order) + +class PWSBHandle(toyframework.Handle): + def __init__(self, cs, segs): + self.handles_per_curve = cs*segs + self.curve_size = cs + self.segs = segs + self.pts = [] + def append(self, x, y): + self.pts.append(py2geom.Point(x,y)) + def value(self, y_0=0): + pws = py2geom.PiecewiseSBasis() + for i in range(0, self.handles_per_curve, self.curve_size): + pws.push_cut(self.pts[i][0]); + for j in range(i, i + self.curve_size): + self.pts[j] = py2geom.Point(self.pts[j][0], self.pts[j][1] - y_0) + hnd = self.pts[i:i+self.curve_size] + pws.push_seg( bez_to_sbasis(hnd, self.curve_size-1)); + for j in range(i, i + self.curve_size): + self.pts[j] = py2geom.Point(self.pts[j][0], self.pts[j][1] + y_0); + + pws.push_cut(self.pts[self.handles_per_curve - 1][0]); + assert(pws.invariants()); + return pws + + def draw(self, cr, annotes): + for p in self.pts: + toyframework.draw_circ(cr, p) + + def hit(self, mouse): + for i,p in enumerate(self.pts): + if(py2geom.distance(py2geom.Point(*mouse), p) < 5): + return i + return None + + def move_to(self, hit, om, m): + om = py2geom.Point(*om) + m = py2geom.Point(*m) + if hit != None: + i,hand = hit + self.pts[hand] = m + for i in range(self.curve_size, self.handles_per_curve, self.curve_size): + self.pts[i-1] = py2geom.Point(self.pts[i][0],self.pts[i-1][1]) + + for i in range(0, self.handles_per_curve, self.curve_size): + for j in range(1, (self.curve_size-1)): + t = float(j)/(self.curve_size-1) + x = lerp(self.pts[i][0], self.pts[i+self.curve_size-1][0],t) + self.pts[i+j] = py2geom.Point(x, self.pts[i+j][1]) + + + +class PwToy(toyframework.Toy): + def __init__(self): + toyframework.Toy.__init__(self) + self.segs = 2 + self.handles_per_curve = 4 * self.segs + self.curves = 2 + self.pwsbh = [] + self.interval_test = [] + for a in range(self.curves): + self.pwsbh.append(PWSBHandle(4, self.curves)) + self.handles.append(self.pwsbh[a]) + for i in range(self.handles_per_curve): + t = 150 + 300*i/(4*self.segs) + self.pwsbh[a].append(t, random.uniform(0,1) * 150 + 150 - 50 * a) + self.interval_test.append(toyframework.PointHandle(150, 400)) + self.interval_test.append(toyframework.PointHandle(300, 400)) + self.handles.append(self.interval_test[0]) + self.handles.append(self.interval_test[1]) + self.func = "pws[0] + pws[1]" + def gtk_ready(self): + import gtk + self.sb_entry = gtk.Entry() + self.sb_entry.connect("changed", self.sb_changed) + toyframework.get_vbox().add(self.sb_entry) + toyframework.get_vbox().show_all() + self.sb_entry.set_text(self.func) + def sb_changed(self, sb): + self.func = sb.get_text() + self.redraw() + def draw(self, cr, pos, save): + cr.set_source_rgba (0., 0., 0., 1) + cr.set_line_width (1) + + pws = [self.pwsbh[i].value() for i in range(self.curves)] + for p in pws: + cairo_pw(cr, p) + cr.stroke() + + d = locals().copy() + for i in dir(py2geom): + d[i] = py2geom.__dict__[i] + d['l2s'] = l2s + d['constant'] = constant + pw_out = eval(self.func, d) + + bs = py2geom.bounds_local(pw_out, py2geom.OptInterval( + py2geom.Interval(self.interval_test[0].pos[0], + self.interval_test[1].pos[0]))); + if not bs.isEmpty(): + bs = bs.toInterval() + for ph in self.interval_test: + ph.pos= py2geom.Point(ph.pos[0], bs.middle()) + cr.save() + cr.set_source_rgba (.0, 0.25, 0.5, 1.) + cr.rectangle(self.interval_test[0].pos[0], bs.min(), + self.interval_test[1].pos[0]-self.interval_test[0].pos[0], bs.extent()) + cr.stroke() + bs = py2geom.bounds_exact(pw_out); + cr.set_source_rgba (0.25, 0.25, .5, 1.); + if not bs.isEmpty(): + bs = bs.toInterval() + cairo_horiz(cr, bs.middle(), pw_out.cuts); + cr.stroke() + cr.restore() + + cr.set_source_rgba (0., 0., .5, 1.); + cairo_pw(cr, pw_out) + cr.stroke() + + + self.notify = str(bs) + toyframework.Toy.draw(self, cr, pos, save) + +t = PwToy() +import sys + +toyframework.init(sys.argv, t, 500, 500) diff --git a/src/toys/pwsbhandle.cpp b/src/toys/pwsbhandle.cpp new file mode 100644 index 0000000..ce4f21e --- /dev/null +++ b/src/toys/pwsbhandle.cpp @@ -0,0 +1,77 @@ +class PWSBHandle : public Handle{ +public: + unsigned handles_per_curve, curve_size, segs; + PWSBHandle() {} + PWSBHandle(unsigned cs, unsigned segs) :handles_per_curve(cs*segs),curve_size(cs), segs(segs) {} + std::vector<Geom::Point> pts; + virtual void draw(cairo_t *cr, bool annotes = false); + + virtual void* hit(Geom::Point mouse); + virtual void move_to(void* hit, Geom::Point om, Geom::Point m); + void push_back(double x, double y) {pts.push_back(Geom::Point(x,y));} + Piecewise<SBasis> value(double y_0=150) { + Piecewise<SBasis> pws; + Point* base = &pts[0]; + for(unsigned i = 0; i < handles_per_curve; i+=curve_size) { + pws.push_cut(base[i][0]); + //Bad hack to move 0 to 150 + for(unsigned j = i; j < i + curve_size; j++) + base[j] = Point(base[j][0], base[j][1] - y_0); + pws.push_seg( Geom::handles_to_sbasis(base+i, curve_size-1)[1]); + for(unsigned j = i; j < i + curve_size; j++) + base[j] = Point(base[j][0], base[j][1] + y_0); + } + pws.push_cut(base[handles_per_curve - 1][0]); + assert(pws.invariants()); + return pws; + } + virtual void load(FILE* f); + virtual void save(FILE* f); +}; + +void PWSBHandle::draw(cairo_t *cr, bool /*annotes*/) { + for(auto & pt : pts) { + draw_circ(cr, pt); + } +} + +void* PWSBHandle::hit(Geom::Point mouse) { + for(auto & pt : pts) { + if(Geom::distance(mouse, pt) < 5) + return (void*)(&pt); + } + return 0; +} + +void PWSBHandle::move_to(void* hit, Geom::Point /*om*/, Geom::Point m) { + if(hit) { + *(Geom::Point*)hit = m; + Point* base = &pts[0]; + for(unsigned i = curve_size; i < handles_per_curve; i+=curve_size) { + base[i-1][0] = base[i][0]; + } + for(unsigned i = 0; i < handles_per_curve; i+=curve_size) { + for(unsigned j = 1; j < (curve_size-1); j++) { + double t = float(j)/(curve_size-1); + base[i+j][0] = (1 - t)*base[i][0] + t*base[i+curve_size-1][0]; + } + } + } +} + +void PWSBHandle::load(FILE* f) { + unsigned n = 0; + assert(3 == fscanf(f, "%d %d %d\n", &curve_size, &segs, &n)); + assert(n == curve_size*segs); + pts.clear(); + for(unsigned i = 0; i < n; i++) { + pts.push_back(read_point(f)); + } +} + +void PWSBHandle::save(FILE* f) { + fprintf(f, "%d %d %lu\n", curve_size, segs, pts.size()); + for(auto & pt : pts) { + fprintf(f, "%lf %lf\n", pt[0], pt[1]); + } +} diff --git a/src/toys/py2geom_glue.py b/src/toys/py2geom_glue.py new file mode 100644 index 0000000..fa2b5ae --- /dev/null +++ b/src/toys/py2geom_glue.py @@ -0,0 +1,42 @@ +#!/usr/bin/python + +def choose(n, k): + r = 1 + for i in range(1, k+1): + r = (r*(n-k+i))/i + return r + +def W(n, j, k): + q = (n+1)/2. + if((n & 1) == 0 and j == q and k == q): + return 1 + if(k > n-k): + return W(n, n-j, n-k) + assert((k <= q)) + if(k >= q): + return 0 + # assert(!(j >= n-k)); + if(j >= n-k): + return 0 + # assert(!(j < k)); + if(j < k): + return 0 + return float(choose(n-2*k-1, j-k)) / choose(n,j); + +# this produces a degree 2q bezier from a degree k sbasis +def sbasis_to_bezier(B, q): + if(q == 0): + q = len(B) + n = q*2 + result = [0.0 for i in range(n)] + if(q > len(B)): + q = len(B) + n -= 1 + for k in range(q): + for j in range(n-k+1): + result[j] += (W(n, j, k)*B[k][0] + + W(n, n-j, k)*B[k][1]) + return result + +def lerp(a, b, t): + return (1-t)*a + t*b diff --git a/src/toys/ray_test.py b/src/toys/ray_test.py new file mode 100644 index 0000000..6ec8b01 --- /dev/null +++ b/src/toys/ray_test.py @@ -0,0 +1,20 @@ +# test of 2geom ray bindings + +import py2geom as g + +# find one point along a ray +a = g.Point(0,0) +b = g.Point(2,2) + +r = g.Ray(a,b) +from math import sqrt +print r.pointAt(sqrt(2)) + +# measure the angle between two rays +c = g.Point(2,-2) +r2 = g.Ray(a,c) +from math import degrees +# FIXME: the third argument (clockwise) ought to be optional, but has to be supplied +print degrees(g.angle_between(r, r2, True)) +print degrees(g.angle_between(r, r2)) + diff --git a/src/toys/rdm-area.cpp b/src/toys/rdm-area.cpp new file mode 100644 index 0000000..22cc104 --- /dev/null +++ b/src/toys/rdm-area.cpp @@ -0,0 +1,479 @@ +#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <cstdlib>
+#include <vector>
+#include <list>
+#include <algorithm>
+using std::vector;
+using namespace Geom;
+
+#define SIZE 4
+#define NB_SLIDER 2
+
+struct Triangle{
+ Point a,b,c;
+ double area;
+};
+
+//TODO: this would work only for C1 pw<d2<sb>> input. Split the input at corners to work with pwd2sb...
+//TODO: for more general purpose, return a path...
+void toPoly(D2<SBasis> const &f, std::list<Point> &p, double tol, bool include_first=true){
+ D2<SBasis> df = derivative(f);
+ D2<SBasis> d2f = derivative(df);
+ double t=0;
+ if ( include_first ){ p.push_back( f.at0() );}
+ while (t<1){
+ Point v = unit_vector(df.valueAt(t));
+ //OptInterval bounds = bounds_local(df[X]*v[Y]-df[Y]*v[X], Interval(t,1));
+ OptInterval bounds = bounds_local(d2f[X]*v[Y]-d2f[Y]*v[X], Interval(t,1));
+ if (bounds) {
+ double bds_max = (-bounds->min()>bounds->max() ? -bounds->min() : bounds->max());
+ double dt;
+ //if (bds_max<tol) dt = 1;
+ //else dt = tol/bds_max;
+ if (bds_max<tol/4) dt = 1;
+ else dt = 2*std::sqrt( tol / bds_max );
+ t+=dt*5;
+ if (t>1) t = 1;
+ }else{
+ t = 1;
+ }
+ p.push_back( f.valueAt(t) );
+ }
+ return;
+}
+
+std::list<Point> toPoly(std::vector<Piecewise<D2<SBasis> > > f, double tol){
+ assert ( f.size() >0 && f[0].size() >0 );
+ std::list<Point> res;
+
+ for (unsigned i = 0; i<f.size(); i++){
+ for (unsigned j = 0; j<f[i].size(); j++){
+ toPoly(f[i][j],res,tol, j==0);
+ }
+ if ( f[i].segs.front().at0() != f[i].segs.back().at1() ){
+ res.push_back( f[i].segs.front().at0() );
+ }
+ if ( i>0 ) res.push_back( f[0][0].at0() );
+ }
+ return res;
+}
+
+//TODO: this is an ugly hack, use path intersection instead!!
+bool intersect(Point const &a0, Point const &b0, Point const &a1, Point const &b1, Point &c, double tol=.0001){
+ double abaa1 = cross( b0-a0, a1-a0);
+ double abab1 = cross( b0-a0, b1-a0);
+ double abaa0 = cross( b1-a1, a0-a1);
+ double abab0 = cross( b1-a1, b0-a1);
+ if ( abaa1 * abab1 < -tol && abaa0 * abab0 < -tol ){
+ c = a1 - (b1-a1) * abaa1/(abab1-abaa1);
+ return true;
+ }
+#if 1
+ return false;//TODO: handle limit cases!!
+#else
+ if ( abaa1 == 0 && dot( a0-a1, b0-a1 ) < 0 ) {
+ c = a1;
+ return true;
+ }
+ if ( abab1 == 0 && dot( a0-b1, b0-b1 ) < 0 ) {
+ c = b1;
+ return true;
+ }
+ if ( abaa0 == 0 && dot( a1-a0, b1-a0 ) < 0 ) {
+ c = a0;
+ return true;
+ }
+ if ( abab0 == 0 && dot( a1-b0, b1-b0 ) < 0 ) {
+ c = b0;
+ return true;
+ }
+ return false;
+#endif
+}
+
+//TODO: use path intersection stuff!
+void uncross(std::list<Point> &loop){
+ std::list<Point>::iterator b0 = loop.begin(),a0,b1,a1;
+ if ( b0 == loop.end() ) return;
+ a0 = b0;
+ ++b0;
+ if ( b0 == loop.end() ) return;
+ //now a0,b0 are 2 consecutive points.
+ while ( b0 != loop.end() ){
+ b1 = b0;
+ ++b1;
+ if ( b1 != loop.end() ) {
+ a1 = b1;
+ ++b1;
+ if ( b1 != loop.end() ) {
+ //now a0,b0,a1,b1 are 4 consecutive points.
+ Point c;
+ while ( b1 != loop.end() ){
+ if ( intersect(*a0,*b0,*a1,*b1,c) ){
+ if ( c != (*a0) && c != (*b0) ){
+ loop.insert(b1,c);
+ loop.insert(b0,c);
+ ++a1;
+ std::list<Point> loop_piece;
+ loop_piece.insert(loop_piece.begin(), b0, a1 );
+ loop_piece.reverse();
+ loop.erase( b0, a1 );
+ loop.splice( a1, loop_piece );
+ b0 = a0;
+ ++b0;
+ //a1 = b1; a1--;//useless
+ }else{
+ //TODO: handle degenerated crossings...
+ }
+ }else{
+ a1=b1;
+ ++b1;
+ }
+ }
+ }
+ }
+ a0 = b0;
+ ++b0;
+ }
+ return;//We should never reach this point.
+}
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+void triangulate(std::list<Point> &pts, std::vector<Triangle> &tri, bool clockwise = false, double tol=.001){
+ pts.unique();
+ while ( !pts.empty() && pts.front() == pts.back() ){ pts.pop_back(); }
+ if ( pts.size() < 3 ) return;
+ //cycle by 1 to have a better looking output...
+ pts.push_back(pts.front()); pts.pop_front();
+ std::list<Point>::iterator a,b,c,m;
+ int sign = (clockwise ? -1 : 1 );
+ a = pts.end(); --a;
+ b = pts.begin();
+ c = b; ++c;
+ //now a,b,c are 3 consecutive points.
+ if ( pts.size() == 3 ) {
+ Triangle abc;
+ abc.a = (*a);
+ abc.b = (*b);
+ abc.c = (*c);
+ abc.area = sign *( cross((*b) - (*a),(*c) - (*b))/2) ;
+ if ( abc.area >0 ){
+ tri.push_back(abc);
+ pts.clear();
+ }
+ return;
+ }
+ bool found = false;
+ while( c != pts.end() ){
+ double abac = cross((*b)-(*a),(*c)-(*a));
+ if ( fabs(abac)<tol && dot( *b-*a, *c-*b ) <= 0) {
+ //this is a degenerated triangle. Remove it and continue.
+ pts.erase(b);
+ triangulate(pts,tri,clockwise);
+ return;
+ }
+ m = c;
+ ++m;
+ while ( m != pts.end() && !found && m!=a){
+ bool pointing_inside;
+ double abam = cross((*b)-(*a),(*m)-(*a));
+ double bcbm = cross((*c)-(*b),(*m)-(*b));
+ if ( sign * abac > 0 ){
+ pointing_inside = ( sign * abam >= 0 ) && ( sign * bcbm >= 0 );
+ }else {
+ pointing_inside = ( sign * abam >=0 ) || ( sign * bcbm >=0);
+ }
+ if ( pointing_inside ){
+ std::list<Point>::iterator p=c,q=++p;
+ Point inter;
+ while ( q != pts.end() && !intersect(*b,*m,*p,*q,inter) ){
+ p=q;
+ ++q;
+ }
+ if ( q == pts.end() ){
+ found = true;
+ }else{
+ ++m;
+ }
+ }else{
+ ++m;
+ }
+ }
+ if ( found ){
+ std::list<Point>pts_beg;
+ pts.insert(b,*b);
+ pts.insert(m,*m);
+ pts_beg.splice(pts_beg.begin(), pts, b, m);
+ triangulate(pts_beg,tri,clockwise);
+ triangulate(pts,tri,clockwise);
+ return;
+ }else{
+ a = b;
+ b = c;
+ ++c;
+ }
+ }
+ //we should never reach this point.
+}
+
+double
+my_rand_generator(){
+ double x = std::rand();
+ return x/RAND_MAX;
+}
+
+class RandomGenerator {
+public:
+ RandomGenerator();
+ RandomGenerator(Piecewise<D2<SBasis> >f_in, double tol=.1);
+ ~RandomGenerator(){};
+ void setDomain(Piecewise<D2<SBasis> >f_in, double tol=.1);
+ void set_generator(double (*rand_func)());
+ void resetRandomizer();
+ Point pt();
+ double area();
+
+protected:
+ double (*rand)();//set this to your favorite generator of numbers in [0,1] (an inkscape param for instance!)
+ long start_seed;
+ long seed;
+ std::vector<Triangle> triangles;
+ std::vector<double> areas;
+};
+
+RandomGenerator::RandomGenerator(){
+ seed = start_seed = 10;
+ rand = &my_rand_generator;//set this to your favorite generator of numbers in [0,1]!
+}
+RandomGenerator::RandomGenerator(Piecewise<D2<SBasis> >f_in, double tol){
+ seed = start_seed = 10;
+ rand = &my_rand_generator;//set this to your favorite generator of numbers in [0,1]!
+ setDomain(f_in, tol);
+}
+void RandomGenerator::setDomain(Piecewise<D2<SBasis> >f_in, double tol){
+ std::vector<Piecewise<D2<SBasis> > >f = split_at_discontinuities(f_in);
+ std::list<Point> p = toPoly( f, tol);
+ uncross(p);
+ if ( p.size()<3) return;
+ double tot_area = 0;
+ std::list<Point>::iterator a = p.begin(), b=a;
+ ++b;
+ while(b!=p.end()){
+ tot_area += ((*b)[X]-(*a)[X]) * ((*b)[Y]+(*a)[Y])/2;
+ ++a;++b;
+ }
+ bool clockwise = tot_area < 0;
+ triangles = std::vector<Triangle>();
+ triangulate(p,triangles,clockwise);
+ areas = std::vector<double>(triangles.size(),0.);
+ double cumul = 0;
+ for (unsigned i = 0; i<triangles.size(); i++){
+ cumul += triangles[i].area;
+ areas[i] = cumul;
+ }
+}
+
+void RandomGenerator::resetRandomizer(){
+ seed = start_seed;
+}
+Point RandomGenerator::pt(){
+ if (areas.empty()) return Point(0,0);
+ double pick_area = rand()*areas.back();
+ std::vector<double>::iterator picked = std::lower_bound( areas.begin(), areas.end(), pick_area);
+ unsigned i = picked - areas.begin();
+ double x = (*rand)();
+ double y = (*rand)();
+ if ( x+y > 1) {
+ x = 1-x;
+ y = 1-y;
+ }
+ //x=.3; y=.3;
+ Point res;
+ res = triangles[i].a;
+ res += x * ( triangles[i].b - triangles[i].a );
+ res += y * ( triangles[i].c - triangles[i].a );
+ return res;
+}
+double RandomGenerator::area(){
+ if (areas.empty()) return 0;
+ return areas.back();
+}
+void RandomGenerator::set_generator(double (*f)()){
+ rand = f;//set this to your favorite generator of numbers in [0,1]!
+}
+
+
+
+
+
+
+//-------------------------------------------------------
+// The toy!
+//-------------------------------------------------------
+class RandomToy: public Toy {
+
+ PointHandle adjuster[NB_SLIDER];
+
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+ srand(10);
+ for(unsigned i=0; i<NB_SLIDER; i++){
+ adjuster[i].pos[X] = 30+i*20;
+ if (adjuster[i].pos[Y]<100) adjuster[i].pos[Y] = 100;
+ if (adjuster[i].pos[Y]>400) adjuster[i].pos[Y] = 400;
+ cairo_move_to(cr, Point(30+i*20,100));
+ cairo_line_to(cr, Point(30+i*20,400));
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ }
+ double tol = (400-adjuster[0].pos[Y])/300.*5+0.05;
+ double tau = (400-adjuster[1].pos[Y])/300.;
+// double scale_topback = (250-adjuster[2].pos[Y])/150.*5;
+// double scale_botfront = (250-adjuster[3].pos[Y])/150.*5;
+// double scale_botback = (250-adjuster[4].pos[Y])/150.*5;
+// double growth = 1+(250-adjuster[5].pos[Y])/150.*.1;
+// double rdmness = 1+(400-adjuster[6].pos[Y])/300.*.9;
+// double bend_amount = (250-adjuster[7].pos[Y])/300.*100.;
+
+ b1_handle.pts.back() = b2_handle.pts.front();
+ b1_handle.pts.front() = b2_handle.pts.back();
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgba(cr, 0, 0, 0, 1);
+ cairo_d2_sb(cr, B1);
+ cairo_d2_sb(cr, B2);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+
+ Piecewise<D2<SBasis> >B;
+ B.concat(Piecewise<D2<SBasis> >(B1));
+ B.continuousConcat(Piecewise<D2<SBasis> >(B2));
+
+ Piecewise<SBasis> are;
+
+ Point centroid_tmp(0,0);
+ are = integral(dot(B, rot90(derivative(B))))*0.5;
+ are = (are - are.firstValue())*(height/10) / (are.lastValue() - are.firstValue());
+
+ D2<Piecewise<SBasis> > are_graph(Piecewise<SBasis>(Linear(0, width)), are );
+ std::cout << are.firstValue() << "," << are.lastValue() << std::endl;
+ cairo_save(cr);
+ cairo_d2_pw_sb(cr, are_graph);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+
+#if 0
+ std::vector<Piecewise<D2<SBasis> > >f = split_at_discontinuities(B);
+ std::list<Point> p = toPoly( f, tol);
+ uncross(p);
+ cairo_move_to(cr, p.front());
+ for (std::list<Point>::iterator pt = p.begin(); pt!=p.end(); ++pt){
+ cairo_line_to(cr, *pt);
+ //if (i++>p.size()*tau) break;
+ }
+ cairo_set_line_width (cr, 3);
+ cairo_set_source_rgba (cr, 1., 0., 0., .5);
+ cairo_stroke(cr);
+
+ if ( p.size()<3) return;
+ double tot_area = 0;
+ std::list<Point>::iterator a = p.begin(), b=a;
+ b++;
+ while(b!=p.end()){
+ tot_area += ((*b)[X]-(*a)[X]) * ((*b)[Y]+(*a)[Y])/2;
+ a++;b++;
+ }
+ bool clockwise = tot_area < 0;
+
+ std::vector<Triangle> tri;
+ int nbiter =0;
+ triangulate(p,tri,clockwise);
+ cairo_set_source_rgba (cr, 1., 1., 0., 1);
+ cairo_stroke(cr);
+ for (unsigned i=0; i<tri.size(); i++){
+ cairo_move_to(cr, tri[i].a);
+ cairo_line_to(cr, tri[i].b);
+ cairo_line_to(cr, tri[i].c);
+ cairo_line_to(cr, tri[i].a);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., .9, .5);
+ cairo_stroke(cr);
+ cairo_move_to(cr, tri[i].a);
+ cairo_line_to(cr, tri[i].b);
+ cairo_line_to(cr, tri[i].c);
+ cairo_line_to(cr, tri[i].a);
+ cairo_set_source_rgba (cr, 0.5, 0., .9, .1);
+ cairo_fill(cr);
+ }
+#endif
+
+ RandomGenerator rdm = RandomGenerator(B, tol);
+ for(int i = 0; i < rdm.area()/5*tau; i++) {
+ draw_handle(cr, rdm.pt());
+ }
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ RandomToy(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ b1_handle.pts[0] = Geom::Point(400,300);
+ b1_handle.pts[1] = Geom::Point(400,400);
+ b1_handle.pts[2] = Geom::Point(100,400);
+ b1_handle.pts[3] = Geom::Point(100,300);
+
+ b2_handle.pts[0] = Geom::Point(100,300);
+ b2_handle.pts[1] = Geom::Point(100,200);
+ b2_handle.pts[2] = Geom::Point(400,200);
+ b2_handle.pts[3] = Geom::Point(400,300);
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+
+ for(unsigned i = 0; i < NB_SLIDER; i++) {
+ adjuster[i].pos = Geom::Point(30+i*20,250);
+ handles.push_back(&(adjuster[i]));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new RandomToy);
+ return 0;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+//vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99:
diff --git a/src/toys/rect-toy.cpp b/src/toys/rect-toy.cpp new file mode 100644 index 0000000..929afb7 --- /dev/null +++ b/src/toys/rect-toy.cpp @@ -0,0 +1,395 @@ +/* + * Rect Toy + * + * Copyright 2008 njh <> + * multitoy approach + * Copyright 2008 Marco <> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/line.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/transforms.h> + +#include <2geom/angle.h> + +#include <vector> +#include <string> +#include <optional> + +using namespace Geom; + + + +std::string angle_formatter(double angle) +{ + return default_formatter(decimal_round(deg_from_rad(angle),2)); +} + +class LineToy : public Toy +{ + enum menu_item_t + { + SHOW_MENU = 0, + TEST_CREATE, + TEST_PROJECTION, + TEST_ORTHO, + TEST_DISTANCE, + TEST_POSITION, + TEST_SEG_BISEC, + TEST_ANGLE_BISEC, + TEST_COLLINEAR, + TEST_INTERSECTIONS, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + }; + + enum toggle_label_t + { + }; + + enum slider_label_t + { + ANGLE_SLIDER = 0, + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &LineToy::draw_menu; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + sliders.clear(); + toggles.clear(); + handles.clear(); + } + + + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/ ) + { + init_common_ctrl_geom(cr, width, height, notify); + } + + + void init_create() + { + init_common(); + + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + + sliders.emplace_back(0, 2*M_PI, 0, 0, "angle"); + sliders[ANGLE_SLIDER].formatter(&angle_formatter); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&(sliders[ANGLE_SLIDER])); + } + + void draw_create(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_create_ctrl_geom(cr, notify, width, height); + + Rect r1(p1.pos, p2.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + + Affine rot = Translate(-r1.midpoint())*Rotate(sliders[ANGLE_SLIDER].value())*Translate(r1.midpoint()); + cairo_rectangle(cr, r1*rot); + cairo_move_to(cr, r1.corner(3)*rot); + for(int i = 0; i < 4; i++) { + cairo_line_to(cr, r1.corner(i)*rot); + } + + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + } + + + + void init_intersections() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + p5.pos = Point(50, 150); + p6.pos = Point(480, 60); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&p5); + handles.push_back(&p6); + } + + void draw_intersections(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Rect r1(p1.pos, p2.pos); + Rect r2(p3.pos, p4.pos); + Line l1(p5.pos, p6.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.0, 0.3, 0.0, 1.0); + cairo_rectangle(cr, r2); + cairo_stroke(cr); + + OptRect r1xr2 = intersect(r1, r2); + if(r1xr2) { + cairo_set_source_rgba(cr, 1.0, 0.7, 0.7, 1.0); + cairo_rectangle(cr, *r1xr2); + cairo_fill(cr); + } + + cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + cairo_stroke(cr); + + + std::optional<LineSegment> ls = rect_line_intersect(r1, LineSegment(p5.pos, p6.pos)); + *notify << "intersects: " << ((ls)?"true":"false") << std::endl; + if(ls) { + draw_handle(cr, (*ls)[0]); + draw_handle(cr, (*ls)[1]); + cairo_save(cr); + cairo_set_line_width(cr, 2); + cairo_move_to(cr, (*ls)[0]); + cairo_line_to(cr, (*ls)[1]); + cairo_stroke(cr); + cairo_restore(cr); + } + + + } + + void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int /*height*/, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + } + } + + void init_create_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[ANGLE_SLIDER].geometry(Point(50, height - 50), 180); + } + } + + + + void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) + { + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); + } + + void draw_segment(cairo_t* cr, LineSegment const& ls) + { + draw_segment(cr, ls[0], ls[1]); + } + + void draw_ray(cairo_t* cr, Ray const& r) + { + double angle = r.angle(); + draw_segment(cr, r.origin(), angle, m_length); + } + + void draw_line(cairo_t* cr, Line const& l) + { + double angle = l.angle(); + draw_segment(cr, l.origin(), angle, m_length); + draw_segment(cr, l.origin(), angle, -m_length); + } + + void draw_label(cairo_t* cr, PointHandle const& ph, const char* label) + { + draw_text(cr, ph.pos+op, label); + } + + void draw_label(cairo_t* cr, Line const& l, const char* label) + { + draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label); + } + + void draw_label(cairo_t* cr, LineSegment const& ls, const char* label) + { + draw_text(cr, middle_point(ls[0], ls[1])+op, label); + } + + void draw_label(cairo_t* cr, Ray const& r, const char* label) + { + Point prj = r.pointAt(r.nearestTime(Point(m_width/2-30, m_height/2-30))); + if (L2(r.origin() - prj) < 100) + { + prj = r.origin() + 100*r.vector(); + } + draw_text(cr, prj+op, label); + } + + void init_menu() + { + handles.clear(); + sliders.clear(); + toggles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &LineToy::draw_menu; + break; + case 'B': + init_create(); + draw_f = &LineToy::draw_create; + break; + case 'C': + init_intersections(); + draw_f = &LineToy::draw_intersections; + break; + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + LineToy() + { + op = Point(5,5); + } + + private: + typedef void (LineToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + PointHandle p1, p2, p3, p4, p5, p6, O; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + Point op; + double m_width, m_height, m_length; + +}; // end class LineToy + + +const char* LineToy::menu_items[] = +{ + "show this menu", + "rect generation", + "intersection with a line" +}; + +const char LineToy::keys[] = +{ + 'A', 'B', 'C' +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new LineToy()); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/rect_01.cpp b/src/toys/rect_01.cpp new file mode 100644 index 0000000..333843b --- /dev/null +++ b/src/toys/rect_01.cpp @@ -0,0 +1,98 @@ +/* + * SimpleRect examples + * + * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +/* +Very Simple example of a toy that creates a rectangle on screen + +I am still very inexperienced with lib2geom +so don't use these examples as a reference :) +*/ + + + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +#include <2geom/transforms.h> + +using std::vector; +using namespace Geom; + + + + +class SimpleRect: public Toy { + PointSetHandle psh; + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + { + cairo_save(cr); + Path p1; + p1.appendNew<LineSegment>(Point(0, 0)); + p1.appendNew<LineSegment>(Point(100, 0)); + p1.appendNew<LineSegment>(Point(100, 100)); + p1.appendNew<LineSegment>(Point(0, 100)); + p1.appendNew<LineSegment>(Point(0, 0)); + p1.close(); + + Path p2 = p1 * Rotate::from_degrees(45); // + + cairo_set_source_rgb(cr, 0,0,0); + cairo_path(cr, p1); + cairo_path(cr, p2); + cairo_stroke(cr); + cairo_restore(cr); + } + PointHandle p1, p2; + p1.pos = Point(300, 50); + p2.pos = Point(450, 450); + + Rect r1(p1.pos, p2.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + SimpleRect(){ + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SimpleRect()); + + return 0; +} + diff --git a/src/toys/rect_02.cpp b/src/toys/rect_02.cpp new file mode 100644 index 0000000..a903e2d --- /dev/null +++ b/src/toys/rect_02.cpp @@ -0,0 +1,81 @@ +/* + * SimpleRect examples + * + * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +/* +Simple example of a toy that creates a rectangle on screen. +We also add two handles (that we don't use in our program) + +I am still very inexperienced with lib2geom +so don't use these examples as a reference :) +*/ + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + + +class SimpleRect: public Toy { + PointSetHandle psh; + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + + PointHandle p1, p2; + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + + Rect r1(p1.pos, p2.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save, timer_stream); + } + public: + SimpleRect (unsigned no_of_handles) { + handles.push_back(&psh); + for(unsigned i = 0; i < no_of_handles; i++) + psh.push_back( 200 + ( i * 10 ), 300 + ( i * 10 ) ); + } +}; + +int main(int argc, char **argv) { + unsigned no_of_handles=2; + if(argc > 1) + sscanf(argv[1], "%d", &no_of_handles); + init(argc, argv, new SimpleRect(no_of_handles)); + + return 0; +} + diff --git a/src/toys/rect_03.cpp b/src/toys/rect_03.cpp new file mode 100644 index 0000000..89be320 --- /dev/null +++ b/src/toys/rect_03.cpp @@ -0,0 +1,78 @@ +/* + * SimpleRect examples + * + * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +/* +Simple example of a toy that creates a rectangle on screen. +Now we use the 2 handles to define the rect + +I am still very inexperienced with lib2geom +so don't use these examples as a reference :) +*/ + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + + + +class SimpleRect: public Toy { + PointSetHandle psh; + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + Rect r1(psh.pts[0], psh.pts[1]); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + + + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save, timer_stream); +} + public: + SimpleRect (unsigned no_of_handles) { + handles.push_back(&psh); + for(unsigned i = 0; i < no_of_handles; i++) + psh.push_back( 200 + ( i * 20 ), 300 + ( i * 20 ) ); + } +}; + +int main(int argc, char **argv) { + unsigned no_of_handles=2; + if(argc > 1) + sscanf(argv[1], "%d", &no_of_handles); + init(argc, argv, new SimpleRect(no_of_handles)); + + return 0; +} + diff --git a/src/toys/root-finder-comparer.cpp b/src/toys/root-finder-comparer.cpp new file mode 100644 index 0000000..3952d7c --- /dev/null +++ b/src/toys/root-finder-comparer.cpp @@ -0,0 +1,247 @@ +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/solver.h> +#include <2geom/sbasis-poly.h> +#include "../2geom/orphan-code/nearestpoint.cpp" // FIXME: This looks like it may give problems later, (including a .cpp file) +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZROOTS_TEST 0 +#if ZROOTS_TEST +#include <2geom/zroots.c> +#endif + +using std::swap; + +namespace Geom{ +extern void subdiv_sbasis(SBasis const & s, + std::vector<double> & roots, + double left, double right); +}; + +double eval_bernstein(double* w, double t, unsigned N) { + double Vtemp[2*N]; + //const int degree = N-1; + for (unsigned i = 0; i < N; i++) + Vtemp[i] = w[i]; + + /* Triangle computation */ + const double omt = (1-t); + //Left[0] = Vtemp[0]; + //Right[degree] = Vtemp[degree]; + double *prev_row = Vtemp; + double *row = Vtemp + N; + for (unsigned i = 1; i < N; i++) { + for (unsigned j = 0; j < N - i; j++) { + row[j] = omt*prev_row[j] + t*prev_row[j+1]; + } + //Left[i] = row[0]; + //Right[degree-i] = row[degree-i]; + swap(prev_row, row); + } + return prev_row[0]; +} + +#include <vector> +using std::vector; +using namespace Geom; + +#ifdef HAVE_GSL +#include <complex> +using std::complex; +#endif + +class RootFinderComparer: public Toy { +public: + PointSetHandle psh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + std::vector<Geom::Point> trans; + trans.resize(psh.size()); + for(unsigned i = 0; i < handles.size(); i++) { + trans[i] = psh.pts[i] - Geom::Point(0, height/2); + } + + Timer tm; + + + std::vector<double> solutions; + solutions.resize(6); + + tm.ask_for_timeslice(); + tm.start(); + FindRoots(&trans[0], 5, &solutions[0], 0); + Timer::Time als_time = tm.lap(); + *notify << "original time = " << als_time << std::endl; + + D2<SBasis> test_sb = psh.asBezier();//handles_to_sbasis(handles.begin(),5); + Interval bs = *bounds_exact(test_sb[1]); + cairo_move_to(cr, test_sb[0](0), bs.min()); + cairo_line_to(cr, test_sb[0](1), bs.min()); + cairo_move_to(cr, test_sb[0](0), bs.max()); + cairo_line_to(cr, test_sb[0](1), bs.max()); + cairo_stroke(cr); + *notify << "sb bounds = "<<bs.min()<< ", " <<bs.max()<<std::endl; + Poly ply = sbasis_to_poly(test_sb[1]); + ply = Poly(height/2) - ply; + cairo_move_to(cr, 0, height/2); + cairo_line_to(cr, width, height/2); + cairo_stroke(cr); +#ifdef HAVE_GSL + vector<complex<double> > complex_solutions; + complex_solutions = solve(ply); +#if 1 + *notify << "gsl: "; + std::copy(complex_solutions.begin(), complex_solutions.end(), std::ostream_iterator<std::complex<double> >(*notify, ",\t")); + *notify << std::endl; +#endif +#endif + +#if ZROOTS_TEST + fcomplex a[ply.size()]; + for(unsigned i = 0; i < ply.size(); i++) { + a[i] = ply[i]; + } + //copy(a, a+ply.size(), ply.begin()); + fcomplex rts[ply.size()]; + + zroots(a, ply.size(), rts, true); + for(unsigned i = 0; i < ply.size(); i++) { + if(! a[i].imag()) + solutions[i] = a[i].real(); + } +#endif + +#ifdef HAVE_GSL + + tm.ask_for_timeslice(); + tm.start(); + solve(ply); + als_time = tm.lap(); + *timer_stream << "gsl poly = " << als_time << std::endl; +#endif + + #if ZROOTS_TEST + tm.ask_for_timeslice(); + tm.start(); + zroots(a, ply.size(), rts, false); + als_time = tm.lap(); + *timer_stream << "zroots poly = " << als_time << std::endl; + #endif + + #if LAGUERRE_TEST + tm.ask_for_timeslice(); + tm.start(); + Laguerre(ply); + als_time = tm.lap(); + *timer_stream << "Laguerre poly = " << als_time << std::endl; + complex_solutions = Laguerre(ply); + + #endif + + #define SBASIS_SUBDIV_TEST 0 + #if SBASIS_SUBDIV_TEST + tm.ask_for_timeslice(); + tm.start(); + subdiv_sbasis(-test_sb[1] + Linear(3*width/4), + rts, 0, 1); + als_time = tm.lap(); + *timer_stream << "sbasis subdivision = " << als_time << std::endl; + #endif + #if 0 + tm.ask_for_timeslice(); + tm.start(); + solutions.resize(0); + find_parametric_bezier_roots(&trans[0], 5, solutions, 0); + als_time = tm.lap(); + *timer_stream << "solver parametric = " << als_time << std::endl; + #endif + double ys[trans.size()]; + for(unsigned i = 0; i < trans.size(); i++) { + ys[i] = trans[i][1]; + double x = double(i)/(trans.size()-1); + x = (1-x)*height/4 + x*height*3/4; + draw_handle(cr, Geom::Point(x, height/2 + ys[i])); + } + + solutions.resize(0); + tm.ask_for_timeslice(); + tm.start(); + find_bernstein_roots(ys, 5, solutions, 0, 0, 1, false); + als_time = tm.lap(); + *notify << "found sub solutions at:\n"; + std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double >(*notify, ",")); + *notify << "solver 1d bernstein subdivision n_slns = " << solutions.size() + << ", time = " << als_time << std::endl; + + solutions.resize(0); + tm.ask_for_timeslice(); + tm.start(); + find_bernstein_roots(ys, 5, solutions, 0, 0, 1, true); + als_time = tm.lap(); + + *notify << "solver 1d bernstein secant subdivision slns" << solutions.size() + << ", time = " << als_time << std::endl; + *notify << "found secant solutions at:\n"; + std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double >(*notify, ",")); + *notify << "solver 1d bernstein subdivision accuracy:" + << std::endl; + for(double solution : solutions) { + *notify << solution << ":" << eval_bernstein(ys, solution, trans.size()) << ","; + } + tm.ask_for_timeslice(); + tm.start(); + solutions = roots( -test_sb[1] + Linear(height/2)); + als_time = tm.lap(); +#if 1 + *notify << "sbasis roots: "; + std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double>(*notify, ",\t")); + *notify << "\n time = " << als_time << std::endl; +#endif + for(double solution : solutions) { + double x = test_sb[0](solution); + draw_cross(cr, Geom::Point(x, height/2)); + + } + + *notify << "found " << solutions.size() << "solutions at:\n"; + std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double >(*notify, ",")); + + D2<SBasis> B = psh.asBezier();//handles_to_sbasis(handles.begin(), 5); + Geom::Path pb; + pb.append(B); + pb.close(false); + cairo_path(cr, pb); + + B[0] = Linear(width/4, 3*width/4); + cairo_d2_sb(cr, B); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + RootFinderComparer(unsigned degree) + { + for(unsigned i = 0; i < degree; i++) psh.push_back(Geom::Point(uniform()*400, uniform()*400)); + handles.push_back(&psh); + } +}; + +int main(int argc, char **argv) { + unsigned bez_ord = 6; + if(argc > 1) + sscanf(argv[1], "%d", &bez_ord); + init(argc, argv, new RootFinderComparer(bez_ord)); + + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/rtree-toy.cpp b/src/toys/rtree-toy.cpp new file mode 100644 index 0000000..b7872fa --- /dev/null +++ b/src/toys/rtree-toy.cpp @@ -0,0 +1,573 @@ +/* + * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +/* + initial toy for redblack trees +*/ + + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +#include <vector> +#include <sstream> +#include <getopt.h> + +#include <2geom/orphan-code/rtree.h> +#include "../2geom/orphan-code/rtree.cpp" + + +//using std::vector; +using namespace Geom; +using namespace std; + +// make sure that in RTreeToy() constructor you assign the same number of colors +// otherwise, they extra will be black :P +const int no_of_colors = 8; +const string search_str = "Mode: Search (Area: Click whitespace and Drag)"; +const string update_str = "Mode: Update (Click Bounding Box (dark gray) and Drag) NOT implemented"; +const string insert_str = "Mode: Insert (Click whitespace and Drag)" ; +const string erase_str = "Mode: Delete (Click on Bounding Box (dark gray))"; +const string help_str = "'I': Insert, 'U': Update, 'S': Search, 'D': Delete"; + + +const char* program_name; + +class RTreeToy: public Toy +{ + +// PointSetHandle handle_set; +// std::vector< Handle > rectangle_handles; + std::vector< RectHandle > rectangles; + + Geom::Point starting_point; // during click and drag: start point of click + Geom::Point ending_point; // during click and drag: end point of click (release) + Geom::Point highlight_point; // not used + + // colors we are going to use for different purposes + colour color_shape, color_shape_guide; + colour color_select_area, color_select_area_guide; // red(a=0.6), red + + bool add_new_rect; + bool delete_rect; + + Rect rect_chosen; // the rectangle of the search area + Rect dummy_draw; // the "helper" rectangle that is shown during the click and drag (before the mouse release) + + // save the bounding boxes of the tree in here + std::vector< std::vector< Rect > > rects_level; + std::vector<colour> color_rtree_level; + unsigned drawBB_color; + bool drawBB_color_all; + + enum the_modes { + INSERT_MODE = 0, + UPDATE_MODE, + DELETE_MODE, + SEARCH_MODE, + } mode; // insert/alter, search, delete modes + bool drawBB; // draw bounding boxes of RTree + string out_str, drawBB_str, drawBB_color_str; + + // printing of the tree + //int help_counter; // the "x" of the label of each node + static const int label_size = 15 ; // size the label of each node + + Geom::RTree rtree; + + void * hit; + unsigned rect_id; + + + // used for the keys that switch between modes + enum menu_item_t + { + INSERT_I = 0, + UPDATE_U, + DELETE_D, + SEARCH_S, + BB_TOGGLE_T, + BB_DRAW_0, + BB_DRAW_1, + BB_DRAW_2, + BB_DRAW_3, + BB_DRAW_4, + BB_DRAW_5, + BB_DRAW_ALL_O, + TOTAL_ITEMS // this one must be the last item + }; + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + + + void draw( cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream ) override { + cairo_set_line_width( cr, 1 ); + cairo_set_source_rgba( cr, color_shape ); + cairo_stroke( cr ); + + // draw the shapes we save in the rtree + for(auto & rectangle : rectangles){ + rectangle.draw( cr, true ); + } + cairo_set_source_rgba( cr, color_shape ); + cairo_stroke( cr ); + + // draw a rect if we did click & drag (so that we know what we are going to create) + if( add_new_rect ){ + dummy_draw = Rect( starting_point, ending_point ); + cairo_rectangle( cr, dummy_draw ); + if( mode == INSERT_MODE ){ + cairo_set_source_rgba( cr, color_shape_guide ); + } + else if( mode == SEARCH_MODE ){ + cairo_set_source_rgba( cr, color_select_area_guide ); + } + cairo_stroke( cr ); + } + + // draw a rect for the search area + cairo_rectangle( cr, rect_chosen ); + cairo_set_source_rgba( cr, color_select_area ); + cairo_stroke( cr ); + + *notify << help_str << "\n" + << "'T': Bounding Boxes: " << drawBB_str << ", '0'-'" << no_of_colors << "', 'P': Show Layer: " << drawBB_color_str << "\n" + << out_str; + + if( drawBB ){ + for(unsigned color = 0 ; color < rects_level.size() ; color++ ){ + if( drawBB_color == color || drawBB_color_all ){ + for(auto & j : rects_level){ + cairo_rectangle( cr, j ); + } + cairo_set_source_rgba( cr, color_rtree_level[color] ); + cairo_stroke( cr ); + } + } + } + + Toy::draw( cr, notify, width, height, save,timer_stream ); + } + + void mouse_moved( GdkEventMotion* e ) override{ + if(add_new_rect && + ( mode == INSERT_MODE || mode == SEARCH_MODE ) ) + { + Toy::mouse_moved( e ); + ending_point = Point( e->x, e->y ); + } + } + + void mouse_pressed( GdkEventButton* e ) override { + Toy::mouse_pressed( e ); + if(e->button == 1){ // left mouse button + if( mode == INSERT_MODE ){ + starting_point = Point( e->x, e->y ); + ending_point = starting_point; + add_new_rect = true; + } + else if( mode == SEARCH_MODE ){ + starting_point = Point( e->x, e->y ); + ending_point = starting_point; + add_new_rect = true; + } + else if( mode == DELETE_MODE ) { + Geom::Point mouse(e->x, e->y); + unsigned i = 0; + for( i = 0; i < rectangles.size(); i++) { + hit = rectangles[i].hit(mouse); + if( hit ) { + break; + } + } + if( hit ){ + // erase specific element + stringstream shape_id( rectangles[i].name ); + unsigned shape_id_int; + shape_id >> shape_id_int; + + rtree.erase( rectangles[i].pos, shape_id_int ); + rectangles.erase( rectangles.begin() + i ); +// check_if_deleted( ); +// check_if_duplicates( ); + delete_rect = true; + } + hit = NULL; + } + } + else if( e->button == 2 ){ //middle button + } + else if( e->button == 3 ){ //right button + } + } + + void mouse_released( GdkEventButton* e ) override { + Toy::mouse_released( e ); + if( e->button == 1 ) { //left mouse button + if( mode == INSERT_MODE ) { + if( add_new_rect ){ + ending_point = Point( e->x, e->y ); + RectHandle t( Rect(starting_point, ending_point), false ); + + std::stringstream out; + out << rect_id;; + t.name = out.str(); + rectangles.push_back( t ); + rect_id++; + + insert_in_tree_the_last_rect(); + find_rtree_subtrees_bounding_boxes( rtree ); + add_new_rect = false; + } + } + else if( mode == SEARCH_MODE ){ + if( add_new_rect ){ + ending_point = Point( e->x, e->y ); + rect_chosen = Rect( starting_point, ending_point ); + + std::vector< int > result(0); + + if( rtree.root ){ + rtree.search( rect_chosen, &result, rtree.root ); + } + std::cout << "Search results: " << result.size() << std::endl; + for(int i : result){ + std::cout << i << ", " ; + } + std::cout << std::endl; + + add_new_rect = false; + } + } + else if( mode == DELETE_MODE ) { // mode: delete + if( delete_rect ){ + delete_rect = false; + if( rtree.root ){ + find_rtree_subtrees_bounding_boxes( rtree ); + } + std::cout << " \nTree:\n" << std::endl; + rtree.print_tree( rtree.root, 0 ); + std::cout << "...done\n" << std::endl; + } + } + } + else if( e->button == 2 ){ //middle button + } + else if( e->button == 3 ){ //right button + } + } + + + void key_hit( GdkEventKey *e ) override + { + char choice = std::toupper( e->keyval ); + switch ( choice ) + { + case 'I': + mode = INSERT_MODE; + out_str = insert_str; + break; + case 'S': + mode = SEARCH_MODE; + out_str = search_str; + break; + case 'D': + mode = DELETE_MODE; + out_str = erase_str; + break; + case 'U': + mode = UPDATE_MODE; + out_str = update_str; + break; + case 'T': + if( drawBB ){ + drawBB = false; + drawBB_str = "OFF"; + } + else{ + drawBB = true; + drawBB_str = "ON"; + } + break; + case 'P': + drawBB_color_all = true; + drawBB_color = 9; + drawBB_color_str = "all"; + break; + case '0': + drawBB_color_all = false; + drawBB_color = 0; + drawBB_color_str = "0"; + break; + case '1': + drawBB_color_all = false; + drawBB_color = 1; + drawBB_color_str = "1"; + break; + case '2': + drawBB_color_all = false; + drawBB_color = 2; + drawBB_color_str = "2"; + break; + case '3': + drawBB_color_all = false; + drawBB_color = 3; + drawBB_color_str = "3"; + break; + case '4': + drawBB_color_all = false; + drawBB_color = 4; + drawBB_color_str = "4"; + break; + case '5': + drawBB_color_all = false; + drawBB_color = 5; + drawBB_color_str = "5"; + break; + case '6': + drawBB_color_all = false; + drawBB_color = 6; + drawBB_color_str = "6"; + break; + case '7': + drawBB_color_all = false; + drawBB_color = 7; + drawBB_color_str = "7"; + break; + } + redraw(); + } + + + void insert_in_tree_the_last_rect(){ + unsigned i = rectangles.size() - 1; + Rect r1 = rectangles[i].pos; + + stringstream shape_id( rectangles[i].name ); + unsigned shape_id_int; + shape_id >> shape_id_int; + + // insert in R tree + rtree.insert( r1, shape_id_int ); + std::cout << " \nTree:\n" << std::endl; + rtree.print_tree( rtree.root, 0 ); + std::cout << "...done\n" << std::endl; + }; + + + void find_rtree_subtrees_bounding_boxes( Geom::RTree tree ){ + if( tree.root ){ + // clear existing bounding boxes + for(auto & color : rects_level){ + color.clear(); + } + save_bb( tree.root, 0); + } + }; + + // TODO fix this. + void save_bb( Geom::RTreeNode* subtree_root, int depth ) + { + if( subtree_root->children_nodes.size() > 0 ){ + + // descend in each one of the elements and call print_tree + for(auto & children_node : subtree_root->children_nodes){ + Rect r1( children_node.bounding_box ); + rects_level[ depth ].push_back( r1 ); + + if( depth == no_of_colors - 1 ){ // if we reached Nth levels of colors, roll back to color 0 + save_bb( children_node.data, 0); + } + else{ + save_bb( children_node.data, depth+1); + } + } + } + // else do nothing, entries are the rects themselves... + }; + + + +public: + RTreeToy(unsigned rmin, unsigned rmax, char /*handlefile*/): + rectangles(0), + color_shape(0, 0, 0, 0.9), color_shape_guide(1, 0, 0, 1), + color_select_area(1, 0, 0, 0.6 ), color_select_area_guide(1, 0, 0, 1 ), //1, 0, 0, 1 + add_new_rect( false ), delete_rect( false ), + rect_chosen(), dummy_draw(), + rects_level( no_of_colors ), + color_rtree_level( no_of_colors, colour(0, 0, 0, 0) ), + drawBB_color(9), drawBB_color_all(true), + mode( INSERT_MODE ), drawBB(true), + out_str( insert_str ), + drawBB_str("ON"), drawBB_color_str("all"), + rtree( rmin, rmax, QUADRATIC_SPIT ), + hit( 0 ), rect_id( 0 ) + { + // only "bright" colors + color_rtree_level[0] = colour(0, 0.80, 1, 1); // cyan + color_rtree_level[1] = colour(0, 0.85, 0, 1); // green + color_rtree_level[2] = colour(0.75, 0, 0.75, 1); // purple + color_rtree_level[3] = colour(0, 0, 1, 1); // blue + color_rtree_level[4] = colour(1, 0.62, 0, 1); // orange + color_rtree_level[5] = colour(1, 0, 0.8, 1); // pink + color_rtree_level[6] = colour(0.47, 0.26, 0.12, 1); + color_rtree_level[7] = colour(1, 0.90, 0, 1); // yellow + } + +}; + + + +int main(int argc, char **argv) { + + char* min_arg = NULL; + char* max_arg = NULL; + + int set_min_max = 0; + + int c; + + while (1) + { + static struct option long_options[] = + { + /* These options set a flag. */ + /* These options don't set a flag. + We distinguish them by their indices. */ + {"min-nodes", required_argument, 0, 'n'}, + {"max-nodes", required_argument, 0, 'm'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long (argc, argv, "n:m:h", + long_options, &option_index); + + /* Detect the end of the options. */ + if (c == -1){ + break; + } + + switch (c) + { + case 'n': + min_arg = optarg; + set_min_max += 1; + break; + + + case 'm': + max_arg = optarg; + set_min_max += 2; + break; + + + case 'h': + std::cerr << "Usage: " << argv[0] << " options\n" << std::endl ; + std::cerr << + " -n --min-nodes=NUMBER minimum number in node.\n" << + " -m --max-nodes=NUMBER maximum number in node.\n" << + " -h --help Print this help.\n" << std::endl; + exit(1); + break; + + + case '?': + /* getopt_long already printed an error message. */ + break; + + default: + abort (); + } + } + + unsigned rmin = 0; + unsigned rmax = 0; + + if( set_min_max == 3 ){ + stringstream s1( min_arg ); + s1 >> rmin; + + stringstream s2( max_arg ); + s2 >> rmax; + if( rmax <= rmin || rmax < 2 || rmin < 1 ){ + std::cerr << "Rtree set to 2, 3" << std::endl ; + rmin = 2; + rmax = 3; + } + } + else{ + std::cerr << "Rtree set to 2, 3 ." << std::endl ; + rmin = 2; + rmax = 3; + } + + + char handlefile = 'T'; + std::cout << "rmin: " << rmin << " rmax:" << rmax << std::endl; + init(argc, argv, new RTreeToy( rmin, rmax, handlefile) ); + + return 0; +} + + + +const char* RTreeToy::menu_items[] = +{ + "Insert / Alter Rectangle", + "Search Rectangle", + "Delete Reactangle", + "Toggle" +}; + +const char RTreeToy::keys[] = +{ + 'I', 'U', 'S', 'D', 'T', + '0', '1', '2', '3', '4', '5', 'P' +}; + + + + + + +/* +intersection test + Rect r1( Point(100, 100), Point(150, 150)), + r2( Point(200, 200), Point(250, 250)), + r3( Point(50, 50), Point(100, 100)); + OptRect a_intersection_b; + a_intersection_b = intersect( r1, r2 ); + std::cout << "r1, r2 " << a_intersection_b.empty() << std::endl; + a_intersection_b = intersect( r1, r3 ); + std::cout << "r1, r3 " << a_intersection_b.empty() << std::endl; +*/ diff --git a/src/toys/sanitize.cpp b/src/toys/sanitize.cpp new file mode 100644 index 0000000..1d9a81f --- /dev/null +++ b/src/toys/sanitize.cpp @@ -0,0 +1,204 @@ +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/svg-path-parser.h> +#include <2geom/utils.h> +#include <cstdlib> +#include <2geom/crossing.h> +#include <2geom/path-intersection.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/pathvector.h> + +using namespace Geom; + +struct EndPoint { + public: + Point point, norm; + double time; + EndPoint() : time(0) { } + EndPoint(Point p, Point n, double t) : point(p), norm(n), time(t) { } +}; + +struct Edge { + public: + EndPoint from, to; + int ix; + bool cw; + Edge() { } + Edge(EndPoint f, EndPoint t, int i, bool c) : from(f), to(t), ix(i), cw(c) { } + bool operator==(Edge const &other) { return from.time == other.from.time && to.time == other.to.time; } +}; + +typedef std::vector<Edge> Edges; + +Edges edges(Path const &p, Crossings const &cr, unsigned ix) { + Edges ret = Edges(); + EndPoint prev; + for(unsigned i = 0; i <= cr.size(); i++) { + double t = cr[i == cr.size() ? 0 : i].getTime(ix); + Point pnt = p.pointAt(t); + Point normal = p.pointAt(t+0.01) - pnt; + normal.normalize(); + std::cout << pnt << "\n"; + EndPoint cur(pnt, normal, t); + if(i == 0) { prev = cur; continue; } + ret.push_back(Edge(prev, cur, ix, false)); + ret.push_back(Edge(prev, cur, ix, true)); + prev = cur; + } + return ret; +} + +template<class T> +void append(std::vector<T> &vec, std::vector<T> const &other) { + vec.insert(vec.end(),other.begin(), other.end()); +} + +Edges edges(PathVector const &ps, CrossingSet const &crs) { + Edges ret = Edges(); + for(unsigned i = 0; i < crs.size(); i++) { + Edges temp = edges(ps[i], crs[i], i); + append(ret, temp); + } + return ret; +} + +PathVector edges_to_paths(Edges const &es, PathVector const &ps) { + PathVector ret; + for(const auto & e : es) { + ret.push_back(ps[e.ix].portion(e.from.time, e.to.time)); + } + return ret; +} + +void draw_cell(cairo_t *cr, Edges const &es, PathVector const &ps) { + cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), 0.5); + cairo_set_line_width(cr, uniform() * 10); + PathVector paths = edges_to_paths(es, ps); + Piecewise<D2<SBasis> > pw = paths_to_pw(paths); + double area; + Point centre; + Geom::centroid(pw, centre, area); + cairo_path(cr, paths); //* (Translate(-centre) * Scale(0.2) * Translate(centre*2))); + cairo_stroke(cr); +} + +//Only works for normal +double ang(Point n1, Point n2) { + return (dot(n1, n2)+1) * (cross(n1, n2) < 0 ? -1 : 1); +} + +template<class T> +void remove(std::vector<T> &vec, T const &val) { + for (typename std::vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) { + if(*it == val) { + vec.erase(it); + return; + } + } +} + +std::vector<Edges> cells(cairo_t */*cr*/, PathVector const &ps) { + CrossingSet crs = crossings_among(ps); + Edges es = edges(ps, crs); + std::vector<Edges> ret = std::vector<Edges>(); + + while(!es.empty()) { + std::cout << "hello!\n"; + Edge start = es.front(); + Path p = Path(); + Edge cur = start; + bool rev = false; + Edges cell = Edges(); + do { + std::cout << rev << " " << cur.from.time << ", " << cur.to.time << "\n"; + double a = 0; + Edge was = cur; + EndPoint curpnt = rev ? cur.from : cur.to; + Point norm = rev ? -curpnt.norm : curpnt.norm; + //Point to = curpnt.point + norm *20; + + //std::cout << norm; + for(auto & e : es) { + if(e == was || e.cw != start.cw) continue; + if((!are_near(curpnt.time, e.from.time)) && + are_near(curpnt.point, e.from.point, 0.1)) { + double v = ang(norm, e.from.norm); + //draw_line_seg(cr, curpnt.point, to); + //draw_line_seg(cr, to, es[i].from.point + es[i].from.norm*30); + //std::cout << v << "\n"; + if(start.cw ? v < a : v > a ) { + a = v; + cur = e; + rev = false; + } + } + if((!are_near(curpnt.time, e.to.time)) && + are_near(curpnt.point, e.to.point, 0.1)) { + double v = ang(norm, -e.to.norm); + if(start.cw ? v < a : v > a) { + a = v; + cur = e; + rev = true; + } + } + } + cell.push_back(cur); + remove(es, cur); + if(cur == was) break; + } while(!(cur == start)); + if(are_near(start.to.point, rev ? cur.from.point : cur.to.point)) { + ret.push_back(cell); + } + } + return ret; +} + +int cellWinding(Edges const &/*es*/, PathVector const &/*ps*/) { + return 0; +} + +class Sanitize: public Toy { + PathVector paths; + std::vector<Edges> es; + PointSetHandle angh; + PointSetHandle pathix; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, + std::ostringstream *timer_stream) override + { + int ix = pathix.pts[0][X] / 10; + es = cells(cr, paths); + draw_cell(cr, es[ix], paths); + + cairo_set_source_rgba(cr, 0, 0, 0, 1); + //cairo_path(cr, paths); + //cairo_stroke(cr); + + Point ap = angh.pts[1] - angh.pts[0], bp = angh.pts[2] - angh.pts[0]; + ap.normalize(); bp.normalize(); + *notify << ang(ap, bp); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + Sanitize () {} + void first_time(int argc, char** argv) override { + const char *path_name="sanitize_examples.svgd"; + if(argc > 1) + path_name = argv[1]; + paths = read_svgd(path_name); + + handles.push_back(&angh); handles.push_back(&pathix); + angh.push_back(100, 100); + angh.push_back(80, 100); + angh.push_back(100, 80); + pathix.push_back(30, 200); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sanitize()); + return 0; +} diff --git a/src/toys/sb-math-test.cpp b/src/toys/sb-math-test.cpp new file mode 100644 index 0000000..9964e32 --- /dev/null +++ b/src/toys/sb-math-test.cpp @@ -0,0 +1,164 @@ +/* + * sb-math-test.cpp - some std functions to work with (pw)s-basis + * + * Authors: + * Jean-Francois Barraud + * + * Copyright (C) 2006-2007 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <2geom/sbasis-math.h> +#include <2geom/piecewise.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <2geom/path.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +#define ZERO 1e-3 + +using std::vector; +using namespace Geom; +using namespace std; + +#include <stdio.h> +#include <math.h> + +#include "pwsbhandle.cpp" // FIXME: This looks like it may give problems later, (including a .cpp file) + +//-Plot--------------------------------------------------------------- +static void plot(cairo_t* cr, double (*f)(double), Piecewise<SBasis> const &x, double vscale=1){ + int NbPts=40; + for(int i=0; i<NbPts; i++){ + double t=double(i)/NbPts; + t=x.cuts.front()*(1-t) + x.cuts.back()*t; + draw_handle(cr, Point(150+i*300./NbPts,300-(*f)(x(t))*vscale)); + cairo_stroke(cr); + } +} + +static void plot(cairo_t* cr, Piecewise<SBasis> const &f,double vscale=1){ + D2<Piecewise<SBasis> > plot; + plot[1]=-f; + plot[1]*=vscale; + plot[1]+=300; + + plot[0].cuts.push_back(f.cuts.front()); + plot[0].cuts.push_back(f.cuts.back()); + plot[0].segs.emplace_back(Linear(150,450)); + + cairo_d2_pw_sb(cr, plot); + + for (unsigned i=1; i<f.size(); i++){ + cairo_move_to(cr, Point(150+f.cuts[i]*300,300)); + cairo_line_to(cr, Point(150+f.cuts[i]*300,300-vscale*f.segs[i].at0())); + } +} + +double my_inv(double x){return (1./x);} + +#define SIZE 4 + +class SbCalculusToy: public Toy { + PWSBHandle pwsbh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + //Let the user input sbasis coefs. + cairo_move_to(cr, Geom::Point(0,300)); + cairo_line_to(cr, Geom::Point(600,300)); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + + Piecewise<SBasis> f = pwsbh.value(300); + + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .8); + plot(cr,f,1); + cairo_stroke(cr); + +/* cairo_set_source_rgba (cr, 0.3, 0.3, 0.3,.8); + plot(cr,abs(f),1); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0.3, 0.3, 0.3,.8); + plot(cr,signSb(f),75); + cairo_stroke(cr);*/ + +// cairo_set_source_rgba (cr, 0.3, 0.3, 0.3,.8); +// plot(cr,maxSb(f, -f+50),1); +// cairo_stroke(cr); + +// cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1); +// plot(cr,sqrt(abs(f),.01,3),10); +// plot(cr,&sqrt,abs(f),10); +// cairo_stroke(cr); + +// cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 1); +// plot(cr,cos(f,.01,3),75); +// plot(cr,&cos,f,75); +// cairo_stroke(cr); + +// cairo_set_source_rgba (cr, 0.9, 0.0, 0.7, 1); +// plot(cr,sin(f,.01,3),75); +// plot(cr,&sin,f,75); +// cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0.9, 0.0, 0.7, 1); + plot(cr,divide(Linear(1),f,.01,3),2); + plot(cr,&my_inv,f,10); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + SbCalculusToy() : pwsbh(4,1){ + for(int i = 0; i < 2*SIZE; i++) + pwsbh.push_back(i*100,150+150+uniform()*300*0); + handles.push_back(&pwsbh); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SbCalculusToy); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/sb-of-interval.cpp b/src/toys/sb-of-interval.cpp new file mode 100644 index 0000000..3044620 --- /dev/null +++ b/src/toys/sb-of-interval.cpp @@ -0,0 +1,187 @@ +#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+#include <vector>
+
+#include <2geom/orphan-code/sbasis-of.h>
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+#define SIZE 4
+
+static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){
+ D2<SBasis> plot;
+ plot[0]=SBasis(Linear(150+a*300,150+b*300));
+ plot[1]=B*(-vscale);
+ plot[1]+=300;
+ cairo_d2_sb(cr, plot);
+// cairo_stroke(cr);
+}
+static void plot(cairo_t* cr, SBasisOf<Interval> const &f, double vscale=1,double /*dx*/=.05, double a=0,double b=1){
+ cairo_save(cr);
+#if 0
+ double t = a;
+ while (t<=b){
+ Interval i = f.valueAt(t);
+ std::cout<<i.min()<<","<<i.max()<<"\n";
+ draw_cross(cr, Geom::Point( 150+t*300, 300-i.min()*vscale ) );
+ draw_cross(cr, Geom::Point( 150+t*300, 300-i.max()*vscale ) );
+ t+=dx;
+ }
+#endif
+ D2<SBasis> plot;
+ Path pth;
+ pth.setStitching(true);
+ SBasis fmin(f.size(), Linear());
+ SBasis fmax(f.size(), Linear());
+ for(unsigned i = 0; i < f.size(); i++) {
+ for(unsigned j = 0; j < 2; j++) {
+ fmin[i][j] = f[i][j].min();
+ fmax[i][j] = f[i][j].max();
+ }
+ }
+ plot[0]=SBasis(Linear(150+a*300,150+b*300));
+ plot[1]=fmin*(-vscale);
+ plot[1]+=300;
+ pth.append(plot);
+ plot[1]=fmax*(-vscale);
+ plot[1]+=300;
+ pth.append(reverse(plot));
+ cairo_path(cr, pth);
+
+ cairo_set_source_rgba(cr, 0, 0, 0, 0.1);
+ cairo_fill(cr);
+ cairo_restore(cr);
+}
+
+
+
+class SbOfInterval: public Toy {
+
+ unsigned size;
+ PointHandle adjuster_a[3*SIZE];
+ PointHandle adjuster_b[3*SIZE];
+
+ void drawSliders(cairo_t *cr, PointHandle adjuster[], double y_min, double y_max){
+ for (unsigned i=0; i < size; i++){
+ cairo_move_to(cr, Geom::Point(adjuster[3*i].pos[X],y_min));
+ cairo_line_to(cr, Geom::Point(adjuster[3*i].pos[X],y_max));
+ }
+ }
+ void drawIntervals(cairo_t *cr, PointHandle adjuster[], double /*x*/, double /*dx*/, double /*y_min*/, double /*y_max*/){
+ for (unsigned i=0; i < size; i++){
+ cairo_move_to(cr, adjuster[3*i+1].pos);
+ cairo_line_to(cr, adjuster[3*i+2].pos);
+ }
+ }
+ void setupSliders(PointHandle adjuster[], double x, double dx, double y_min, double y_max){
+ for (unsigned i=0; i < size; i++){
+ for (unsigned j=0; j < 3; j++){
+ adjuster[3*i+j].pos[X] = x + dx*i;
+ if (adjuster[3*i+j].pos[Y] < y_min) adjuster[3*i+j].pos[Y] = y_min;
+ if (adjuster[3*i+j].pos[Y] > y_max) adjuster[3*i+j].pos[Y] = y_max;
+ }
+ if (adjuster[3*i+1].pos[Y] < adjuster[3*i+2].pos[Y]) adjuster[3*i+1].pos[Y] = adjuster[3*i+2].pos[Y];
+ if (adjuster[3*i ].pos[Y] < adjuster[3*i+2].pos[Y]) adjuster[3*i ].pos[Y] = adjuster[3*i+2].pos[Y];
+ if (adjuster[3*i ].pos[Y] > adjuster[3*i+1].pos[Y]) adjuster[3*i ].pos[Y] = adjuster[3*i+1].pos[Y];
+ }
+ }
+
+ PointSetHandle hand;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ SBasisOf<Interval> f;
+ double min=150, max=450;
+ setupSliders(adjuster_a, 100, 15, min, max);
+ setupSliders(adjuster_b, 500, 15, min, max);
+ drawSliders(cr, adjuster_a, min, max);
+ drawSliders(cr, adjuster_b, min, max);
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+ drawIntervals(cr, adjuster_a, 100, 15, min, max);
+ drawIntervals(cr, adjuster_b, 500, 15, min, max);
+ cairo_set_line_width (cr, 3);
+ cairo_set_source_rgba (cr, 0.8, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ cairo_move_to(cr, Geom::Point(150,300));
+ cairo_line_to(cr, Geom::Point(450,300));
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ for (unsigned i=0; i < size; i++){
+ double amin = (max+min)/2 - adjuster_a[3*i+1].pos[Y];
+ double amax = (max+min)/2 - adjuster_a[3*i+2].pos[Y];
+ Interval ai(amin*std::pow(4.,(int)i), amax*std::pow(4.,(int)i));
+ double bmin = (max+min)/2 - adjuster_b[3*i+1].pos[Y];
+ double bmax = (max+min)/2 - adjuster_b[3*i+2].pos[Y];
+ Interval bi(bmin*std::pow(4.,(int)i), bmax*std::pow(4.,(int)i));
+ f.push_back(LinearOf<Interval>(ai,bi));
+ }
+ SBasis f_dble(size, Linear());
+ for (unsigned i=0; i < size; i++){
+ double ai = (max+min)/2 - adjuster_a[3*i].pos[Y];
+ double bi = (max+min)/2 - adjuster_b[3*i].pos[Y];
+ f_dble[i] = Linear(ai*std::pow(4.,(int)i),bi*std::pow(4.,(int)i));
+ }
+
+ plot(cr,f_dble);
+ plot(cr,f);
+ cairo_set_source_rgba (cr, 0., 0., 0.8, 1);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SbOfInterval(){
+ size=SIZE;
+ for(unsigned i=0; i < size; i++){
+ adjuster_a[3*i].pos[X] = 0;
+ adjuster_a[3*i].pos[Y] = 300;
+ adjuster_a[3*i+1].pos[X] = 0;
+ adjuster_a[3*i+1].pos[Y] = 350;
+ adjuster_a[3*i+2].pos[X] = 0;
+ adjuster_a[3*i+2].pos[Y] = 250;
+ adjuster_b[3*i].pos[X] = 0;
+ adjuster_b[3*i].pos[Y] = 300;
+ adjuster_b[3*i+1].pos[X] = 0;
+ adjuster_b[3*i+1].pos[Y] = 350;
+ adjuster_b[3*i+2].pos[X] = 0;
+ adjuster_b[3*i+2].pos[Y] = 250;
+ }
+
+ for(unsigned i = 0; i < size; i++) {
+ handles.push_back(&(adjuster_a[3*i]));
+ handles.push_back(&(adjuster_a[3*i+1]));
+ handles.push_back(&(adjuster_a[3*i+2]));
+ handles.push_back(&(adjuster_b[3*i]));
+ handles.push_back(&(adjuster_b[3*i+1]));
+ handles.push_back(&(adjuster_b[3*i+2]));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SbOfInterval);
+ return 0;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/sb-of-sb.cpp b/src/toys/sb-of-sb.cpp new file mode 100644 index 0000000..d2ee2e6 --- /dev/null +++ b/src/toys/sb-of-sb.cpp @@ -0,0 +1,478 @@ +#include <time.h>
+#include <vector>
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <2geom/orphan-code/sbasis-of.h>
+
+using namespace Geom;
+using namespace std;
+
+SBasis toSBasis(SBasisOf<double> const &f){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0],f[i][1]);
+ }
+ return result;
+}
+SBasisOf<double> toSBasisOfDouble(SBasis const &f){
+ SBasisOf<double> result;
+ for (auto i : f){
+ result.push_back(LinearOf<double>(i[0],i[1]));
+ }
+ return result;
+}
+
+
+#if 0
+template<unsigned dim>
+class LinearDim;
+template<unsigned dim>
+class SBasisDim;
+
+template<unsigned dim>
+class LinearDim : public LinearOf<SBasisDim<dim-1> >{
+public:
+ LinearDim() : LinearOf<SBasisDim<dim-1> >() {};
+ LinearDim(SBasisDim <dim-1> const &a, SBasisDim<dim-1> const &b ) : LinearOf<SBasisDim<dim-1> >(a,b) {};
+ LinearDim(LinearDim <dim-1> const &a, LinearDim<dim-1> const &b ) :
+ LinearOf<SBasisDim<dim-1> >(SBasisDim<dim-1>(a),SBasisDim<dim-1>(b)) {};
+};
+
+template<>
+class LinearDim<1> : public LinearOf<double>{
+public:
+ LinearDim() : LinearOf<double>() {};
+ LinearDim(double const &a, double const &b ) : LinearOf<double>(a,b) {};
+};
+
+
+template<unsigned dim>
+class SBasisDim : public SBasisOf<SBasisDim<dim-1> >{
+public:
+ SBasisDim() : SBasisOf<SBasisDim<dim-1> >() {};
+ SBasisDim(LinearDim<dim> const &lin) :
+ SBasisOf<SBasisDim<dim-1> >(LinearOf<SBasisDim<dim-1> >(lin[0],lin[1])) {};
+};
+
+template<>
+class SBasisDim<1> : public SBasisOf<double>{
+/*
+public:
+ SBasisDim<1>() : SBasisOf<double>() {};
+ SBasisDim(SBasisOf<double> other) : SBasisOf<double>(other) {};
+ SBasisDim(LinearOf<double> other) : SBasisOf<double>(other) {};
+*/
+};
+
+SBasis toSBasis(SBasisDim<1> f){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0],f[i][1]);
+ }
+ return result;
+}
+
+template<unsigned dim_f,unsigned dim_g>
+SBasisDim<dim_g> compose(SBasisDim<dim_f> const &f, std::vector<SBasisDim<dim_g> > const &g ){
+
+ assert( dim_f <= g.size() );
+
+ SBasisDim<dim_g> u0 = g[dim_f-1];
+ SBasisDim<dim_g> u1 = -u0 + 1;
+ SBasisDim<dim_g> s = multiply(u0,u1);
+ SBasisDim<dim_g> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ if ( dim_f>1 ){
+ r = s*r + compose(f[i][0],g)*u0 + compose(f[i][1],g)*u1;
+ }else{
+ r = s*r + f[i][0]*u0 + f[i][1]*u1;
+ }
+ }
+ return r;
+}
+
+#endif
+
+template <typename T>
+SBasisOf<T> multi_compose(SBasisOf<double> const &f, SBasisOf<T> const &g ){
+
+ //assert( f.input_dim() <= g.size() );
+
+ SBasisOf<T> u0 = g;
+ SBasisOf<T> u1 = -u0 + LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(1,1)));
+ SBasisOf<T> s = multiply(u0,u1);
+ SBasisOf<T> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + f[i][0]*u0 + f[i][1]*u1;
+ }
+ return r;
+}
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ SBasisOf<double> const &x,
+ SBasisOf<double> const &y){
+ SBasisOf<double> y0 = -y + LinearOf<double>(1,1);
+ SBasisOf<double> s = multiply(y0,y);
+ SBasisOf<double> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + compose(f[i][0],x)*y0 + compose(f[i][1],x)*y;
+ }
+ return r;
+}
+
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ D2<SBasisOf<double> > const &X){
+ return compose(f, X[0], X[1]);
+}
+
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ D2<SBasis> const &X){
+ return compose(f, toSBasisOfDouble(X[0]), toSBasisOfDouble(X[1]));
+}
+
+/*
+static
+SBasis eval_v(SBasisOf<SBasis> const &f, double v){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0].valueAt(v),f[i][1].valueAt(v));
+ }
+ return result;
+}
+static
+SBasis eval_v(SBasisOf<SBasisOf<double> > const &f, double v){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0].valueAt(v),f[i][1].valueAt(v));
+ }
+ return result;
+}*/
+static
+SBasisOf<double> eval_dim(SBasisOf<SBasisOf<double> > const &f, double t, unsigned dim){
+ if (dim == 1) return f.valueAt(t);
+ SBasisOf<double> result;
+ for (unsigned i=0; i<f.size(); i++){
+ result.push_back(LinearOf<double>(f[i][0].valueAt(t),f[i][1].valueAt(t)));
+ }
+ return result;
+}
+
+/*
+static
+SBasis eval_v(SBasisDim<2> const &f, double v){
+ SBasis result;
+ for (unsigned i=0; i<f.size(); i++){
+ result.push_back(Linear(f[i][0].valueAt(v),f[i][1].valueAt(v)));
+ }
+ return result;
+}
+*/
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+ // find the point on the x,y plane that projects to P
+ Point unproject(Point P) {
+ return P * from_basis(x, y, O).inverse();
+ }
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasisOf<double> const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + toSBasis(z)*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr,
+ Piecewise<SBasis> const &x,
+ Piecewise<SBasis> const &y,
+ Piecewise<SBasis> const &z, Frame frame){
+
+ Piecewise<SBasis> xx = partition(x,y.cuts);
+ Piecewise<SBasis> xxx = partition(xx,z.cuts);
+ Piecewise<SBasis> yyy = partition(y,xxx.cuts);
+ Piecewise<SBasis> zzz = partition(z,xxx.cuts);
+
+ for (unsigned i=0; i<xxx.size(); i++){
+ plot3d(cr, xxx[i], yyy[i], zzz[i], frame);
+ }
+}
+
+void
+plot3d(cairo_t *cr, SBasisOf<SBasisOf<double> > const &f, Frame frame){
+ int iMax = 5;
+ for (int i=0; i<iMax; i++){
+ plot3d(cr, Linear(0,1), Linear(i/(iMax-1.)), eval_dim(f, i/(iMax-1.), 0), frame);
+ plot3d(cr, Linear(i/(iMax-1.)), Linear(0,1), eval_dim(f, i/(iMax-1.), 1), frame);
+ }
+}
+
+SBasisOf<SBasisOf<double> > integral(SBasisOf<SBasisOf<double> > const &f, unsigned var){
+ //variable of f = 1, variable of f's coefficients = 0.
+ if (var == 1) return integral(f);
+ SBasisOf<SBasisOf<double> > result;
+ for(unsigned i = 0; i< f.size(); i++) {
+ result.push_back(LinearOf<SBasisOf<double> >( integral(f[i][0]),integral(f[i][1])));
+ }
+ return result;
+}
+
+Piecewise<SBasis> convole(SBasisOf<double> const &f, Interval dom_f,
+ SBasisOf<double> const &g, Interval dom_g){
+
+ if ( dom_f.extent() < dom_g.extent() ) return convole(g, dom_g, f, dom_f);
+
+ SBasisOf<SBasisOf<double> > u,v;
+ u.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(0,1),
+ LinearOf<double>(0,1)));
+ v.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(0,0),
+ LinearOf<double>(1,1)));
+ SBasisOf<SBasisOf<double> > v_u = v - u*(dom_f.extent()/dom_g.extent());
+ v_u += SBasisOf<SBasisOf<double> >(SBasisOf<double>(dom_f.min()/dom_g.extent()));
+ SBasisOf<SBasisOf<double> > gg = multi_compose(g,(v - u*(dom_f.extent()/dom_g.extent())));
+ SBasisOf<SBasisOf<double> > ff = SBasisOf<SBasisOf<double> >(f);
+ SBasisOf<SBasisOf<double> > hh = integral(ff*gg,0);
+
+ Piecewise<SBasis> result;
+ result.cuts.push_back(dom_f.min()+dom_g.min());
+ //Note: we know dom_f.extent() >= dom_g.extent()!!
+ double rho = dom_f.extent()/dom_g.extent();
+ SBasisOf<double> a,b,t;
+ SBasis seg;
+ a = SBasisOf<double>(LinearOf<double>(0.,0.));
+ b = SBasisOf<double>(LinearOf<double>(0.,1/rho));
+ t = SBasisOf<double>(LinearOf<double>(dom_f.min()/dom_g.extent(),dom_f.min()/dom_g.extent()+1));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.min() + dom_g.max());
+ if (dom_f.extent() > dom_g.extent()){
+ a = SBasisOf<double>(LinearOf<double>(0.,1-1/rho));
+ b = SBasisOf<double>(LinearOf<double>(1/rho,1.));
+ t = SBasisOf<double>(LinearOf<double>(dom_f.min()/dom_g.extent()+1, dom_f.max()/dom_g.extent() ));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.min());
+ }
+ a = SBasisOf<double>(LinearOf<double>(1.-1/rho,1.));
+ b = SBasisOf<double>(LinearOf<double>(1.,1.));
+ t = SBasisOf<double>(LinearOf<double>(dom_f.max()/dom_g.extent(), dom_f.max()/dom_g.extent()+1 ));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.max());
+ return result;
+}
+
+template <typename T>
+SBasisOf<T> subderivative(SBasisOf<T> const& f) {
+ SBasisOf<T> res;
+ for(unsigned i = 0; i < f.size(); i++) {
+ res.push_back(LinearOf<T>(derivative(f[i][0]), derivative(f[i][1])));
+ }
+ return res;
+}
+
+OptInterval bounds_fast(SBasisOf<double> const &f) {
+ return bounds_fast(toSBasis(f));
+}
+
+/**
+ * Finds a path which traces the 0 contour of f, traversing from A to B as a single cubic d2<sbasis>.
+ * The algorithm is based on matching direction and curvature at each end point.
+ */
+//TODO: handle the case when B is "behind" A for the natural orientation of the level set.
+//TODO: more generally, there might be up to 4 solutions. Choose the best one!
+D2<SBasis>
+sbofsb_cubic_solve(SBasisOf<SBasisOf<double> > const &f, Geom::Point const &A, Geom::Point const &B){
+ D2<SBasis>result;//(Linear(A[X],B[X]),Linear(A[Y],B[Y]));
+ //g_warning("check 0 = %f = %f!", f.apply(A[X],A[Y]), f.apply(B[X],B[Y]));
+
+ SBasisOf<SBasisOf<double> > f_u = derivative(f);
+ SBasisOf<SBasisOf<double> > f_v = subderivative(f);
+ SBasisOf<SBasisOf<double> > f_uu = derivative(f_u);
+ SBasisOf<SBasisOf<double> > f_uv = derivative(f_v);
+ SBasisOf<SBasisOf<double> > f_vv = subderivative(f_v);
+
+ Geom::Point dfA(f_u.valueAt(A[X]).valueAt(A[Y]),f_v.valueAt(A[X]).valueAt(A[Y]));
+ Geom::Point dfB(f_u.valueAt(B[X]).valueAt(B[Y]),f_v.valueAt(B[X]).valueAt(B[Y]));
+
+ Geom::Point V0 = rot90(dfA);
+ Geom::Point V1 = rot90(dfB);
+
+ double D2fVV0 = f_uu.valueAt(A[X]).valueAt(A[Y])*V0[X]*V0[X]+
+ 2*f_uv.valueAt(A[X]).valueAt(A[Y])*V0[X]*V0[Y]+
+ f_vv.valueAt(A[X]).valueAt(A[Y])*V0[Y]*V0[Y];
+ double D2fVV1 = f_uu.valueAt(B[X]).valueAt(B[Y])*V1[X]*V1[X]+
+ 2*f_uv.valueAt(B[X]).valueAt(B[Y])*V1[X]*V1[Y]+
+ f_vv.valueAt(B[X]).valueAt(B[Y])*V1[Y]*V1[Y];
+
+ std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(A,B,V0,V1,D2fVV0,D2fVV1);
+ if (candidates.size()==0) {
+ return D2<SBasis>(SBasis(A[X],B[X]),SBasis(A[Y],B[Y]));
+ }
+ //TODO: I'm sure std algorithm could do that for me...
+ double error = -1;
+ unsigned best = 0;
+ for (unsigned i=0; i<candidates.size(); i++){
+ Interval bounds = *bounds_fast(compose(f,candidates[i]));
+ double new_error = (fabs(bounds.max())>fabs(bounds.min()) ? fabs(bounds.max()) : fabs(bounds.min()) );
+ if ( new_error < error || error < 0 ){
+ error = new_error;
+ best = i;
+ }
+ }
+ return candidates[best];
+}
+
+class SBasis0fSBasisToy: public Toy {
+ PointSetHandle hand;
+ PointSetHandle cut_hand;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ //double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ //double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+
+
+ SBasisOf<SBasisOf<double> > f,u,v;
+ u.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(-1,-1),LinearOf<double>(1,1)));
+ v.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(-1,1),LinearOf<double>(-1,1)));
+#if 1
+ f = u*u + v*v - LinearOf<SBasisOf<double> >(LinearOf<double>(1,1),LinearOf<double>(1,1));
+ //*notify << "input dim = " << f.input_dim() <<"\n";
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+
+ LineSegment ls(frame.unproject(cut_hand.pts[0]),
+ frame.unproject(cut_hand.pts[1]));
+ SBasis cutting = toSBasis(compose(f, ls.toSBasis()));
+ //cairo_sb(cr, cutting);
+ //cairo_stroke(cr);
+ plot3d(cr, ls.toSBasis()[0], ls.toSBasis()[1], SBasis(0.0), frame);
+ vector<double> rts = roots(cutting);
+ if(rts.size() >= 2) {
+ Geom::Point A = ls.pointAt(rts[0]);
+ Geom::Point B = ls.pointAt(rts[1]);
+
+ //Geom::Point A(1,0.5);
+ //Geom::Point B(0.5,1);
+ D2<SBasis> zeroset = sbofsb_cubic_solve(f,A,B);
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+ }
+#else
+
+ SBasisOf<SBasisOf<double> > g = u - v ;
+ g += LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(.5,.5)));
+ SBasisOf<double> h;
+ h.push_back(LinearOf<double>(0,0));
+ h.push_back(LinearOf<double>(0,0));
+ h.push_back(LinearOf<double>(1,1));
+
+ f = multi_compose(h,g);
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .75, 0.25, 0.25, 1.);
+ cairo_stroke(cr);
+/*
+ SBasisDim<1> g = SBasisOf<double>(LinearOf<double>(0,1));
+ g.push_back(LinearOf<double>(-1,-1));
+ std::vector<SBasisDim<2> > vars;
+ vars.push_back(ff);
+ plot3d(cr,compose(g,vars),frame);
+ cairo_set_source_rgba (cr, .5, 0.9, 0.5, 1.);
+ cairo_stroke(cr);
+*/
+#endif
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SBasis0fSBasisToy(){
+ handles.push_back(&hand);
+ handles.push_back(&cut_hand);
+ cut_hand.push_back(100,100);
+ cut_hand.push_back(500,500);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SBasis0fSBasisToy);
+ return 0;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/sb-to-bez.cpp b/src/toys/sb-to-bez.cpp new file mode 100644 index 0000000..337180f --- /dev/null +++ b/src/toys/sb-to-bez.cpp @@ -0,0 +1,404 @@ +/* + * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier. + * + * Copyright 2007 jf barraud. + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +// mainly experimental atm... +// do not expect to find anything understandable here atm. + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/basic-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZERO 1e-7 + +using std::vector; +using namespace Geom; +using namespace std; + +#include <stdio.h> +#include <gsl/gsl_poly.h> + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p, double hscale=1., double vscale=1.) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(150+p.cuts[i]*hscale, 150+p.cuts[i+1]*hscale); + B[1] = Linear(450) - p[i]*vscale; + cairo_d2_sb(cr, B); + } +} + +//=================================================================================== + +D2<SBasis> +naive_sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){ + + Piecewise<D2<SBasis> > dM = derivative(M); + Point M0 = M(t0); + Point dM0 = dM(t0)*(t1-t0); + Point M1 = M(t1); + Point dM1 = dM(t1)*(t1-t0); + D2<SBasis> result; + for (unsigned dim=0; dim<2; dim++){ + SBasis r(2, Linear()); + r[0] = Linear(M0[dim],M1[dim]); + r[1] = Linear(M0[dim]-M1[dim]+dM0[dim],-(M0[dim]-M1[dim]+dM1[dim])); + result[dim] = r; + } + return result; +} + +D2<SBasis> +sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){ + Point M0,dM0,d2M0,M1,dM1,d2M1,A0,V0,A1,V1; + Piecewise<D2<SBasis> > dM,d2M; + dM=derivative(M); + d2M=derivative(dM); + M0 =M(t0); + M1 =M(t1); + dM0 =dM(t0); + dM1 =dM(t1); + d2M0=d2M(t0); + d2M1=d2M(t1); + A0=M(t0); + A1=M(t1); + + std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0,d2M1); + if (candidates.empty()){ + return D2<SBasis>(SBasis(M0[X],M1[X]),SBasis(M0[Y],M1[Y])) ; + } + double maxlength = -1; + unsigned best = 0; + for (unsigned i=0; i<candidates.size(); i++){ + double l = length(candidates[i]); + if ( l < maxlength || maxlength < 0 ){ + maxlength = l; + best = i; + } + } + return candidates[best]; +} +#include <2geom/sbasis-to-bezier.h> + +int goal_function_type = 0; + +double goal_function(Piecewise<D2<SBasis> >const &A, + Piecewise<D2<SBasis> >const&B) { + if(goal_function_type) { + OptInterval bnds = bounds_fast(dot(derivative(A), rot90(derivative(B)))); + //double h_dist = bnds.dimensions().length(); +//0 is in the rect!, TODO:gain factor ~2 for free. +// njh: not really, the benefit is actually rather small. + double h_dist = 0; + if(bnds) + h_dist = bnds->extent(); + return h_dist ; + } else { + Rect bnds = *bounds_fast(A - B); + return max(bnds.min().length(), bnds.max().length()); + } +} + +int recursive_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) { + if (t0>=t1) return 0;//TODO: fix me... + if (t0+0.001>=t1) return 0;//TODO: fix me... + + //TODO: don't re-compute derivative(f) at each try!! + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1); + + if(k_bez[0].size() > 1 and k_bez[1].size() > 1) { + Piecewise<SBasis> s = arcLengthSb(k_bez); + s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1(); + s += t0; + double h_dist = goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez)); + if(h_dist < precision) { + cairo_save(cr); + cairo_set_line_width (cr, 0.93); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + draw_handle(cr, k_bez.at0()); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + cairo_restore(cr); + return 1; + } + } + //TODO: find a better place where to cut (at the worst fit?). + return recursive_curvature_fitter(cr, f, t0, (t0+t1)/2, precision) + + recursive_curvature_fitter(cr, f, (t0+t1)/2, t1, precision); +} + +double single_curvature_fitter(Piecewise<D2<SBasis> > const &f, double t0, double t1) { + if (t0>=t1) return 0;//TODO: fix me... + if (t0+0.001>=t1) return 0;//TODO: fix me... + + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1); + + if(k_bez[0].size() > 1 and k_bez[1].size() > 1) { + Piecewise<SBasis> s = arcLengthSb(k_bez); + s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1(); + s += t0; + return goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez)); + } + return 1e100; +} + +struct quadratic_params +{ + Piecewise<D2<SBasis> > const *f; + double t0, precision; +}; + + +double quadratic (double x, void *params) { + struct quadratic_params *p + = (struct quadratic_params *) params; + + return single_curvature_fitter(*p->f, p->t0, x) - p->precision; +} + +#include <stdio.h> +#include <gsl/gsl_errno.h> +#include <gsl/gsl_math.h> +#include <gsl/gsl_roots.h> + + +int sequential_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) { + if(t0 >= t1) return 0; + + double r = t1; + if(single_curvature_fitter(f, t0, t1) > precision) { + int status; + int iter = 0, max_iter = 100; + const gsl_root_fsolver_type *T; + gsl_root_fsolver *s; + gsl_function F; + struct quadratic_params params = {&f, t0, precision}; + + F.function = &quadratic; + F.params = ¶ms; + + T = gsl_root_fsolver_brent; + s = gsl_root_fsolver_alloc (T); + gsl_root_fsolver_set (s, &F, t0, t1); + + do + { + iter++; + status = gsl_root_fsolver_iterate (s); + r = gsl_root_fsolver_root (s); + double x_lo = gsl_root_fsolver_x_lower (s); + double x_hi = gsl_root_fsolver_x_upper (s); + status = gsl_root_test_interval (x_lo, x_hi, + 0, 0.001); + + + } + while (status == GSL_CONTINUE && iter < max_iter); + + double x_lo = gsl_root_fsolver_x_lower (s); + double x_hi = gsl_root_fsolver_x_upper (s); + printf ("%5d [%.7f, %.7f] %.7f %.7f\n", + iter, x_lo, x_hi, + r, + x_hi - x_lo); + gsl_root_fsolver_free (s); + } + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,r); + + cairo_save(cr); + cairo_set_line_width (cr, 0.93); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + draw_handle(cr, k_bez.at0()); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + cairo_restore(cr); + + if(r < t1) + return sequential_curvature_fitter(cr, f, r, t1, precision) + 1; + return 1; +} + + +class SbToBezierTester: public Toy { + //std::vector<Slider> sliders; + std::vector<PointSetHandle*> path_psh; + PointHandle adjuster, adjuster2; + std::vector<Toggle> toggles; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_save(cr); + for(unsigned i = 1; i < path_psh.size(); i++) + path_psh[i-1]->pts.back() = path_psh[i]->pts[0]; + Piecewise<D2<SBasis> > f_as_pw(path_psh[0]->asBezier()); + for(unsigned i = 1; i < path_psh.size(); i++) { + f_as_pw.push_seg(path_psh[i]->asBezier()); + } + //f=handles_to_sbasis(handles.begin(), SIZE-1); + adjuster.pos[1]=450; + adjuster.pos[0]=std::max(adjuster.pos[0],150.); + adjuster.pos[0]=std::min(adjuster.pos[0],450.); + double t0=0;//(adjuster.pos[0]-150)/300; + double t1=(adjuster.pos[0]-150)/300; + //if (t0>t1) {double temp=t0;t0=t1;t1=temp;} + + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 0.5); + cairo_pw_d2_sb(cr, f_as_pw); + cairo_stroke(cr); + if (t0==t1) return;//TODO: fix me... +#if 0 + if(0) { + Piecewise<D2<SBasis> > g = f_as_pw; + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0., 0.9, .7); + double error=0; + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.9, 0., 0., .7); + D2<SBasis> naive_bez = naive_sb_seg_to_bez(g,0,t1); + cairo_d2_sb(cr, naive_bez); + cairo_stroke(cr); + + adjuster2.pos[0]=150; + adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.); + + double scale0=(450-adjuster2.pos[1])/150; + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.7, 0., 0.7, .7); + D2<SBasis> k_bez = sb_seg_to_bez(g,t0,t1); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + double h_a_t = 0, h_b_t = 0; + + double h_dist = hausdorfl( k_bez, f, 1e-6, &h_a_t, &h_b_t); + { + Point At = k_bez(h_a_t); + Point Bu = f(h_b_t); + cairo_move_to(cr, At); + cairo_line_to(cr, Bu); + draw_handle(cr, At); + draw_handle(cr, Bu); + cairo_save(cr); + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + cairo_stroke(cr); + cairo_restore(cr); + } + *notify << "Move handle 6 to set the segment to be approximated by cubic bezier.\n"; + *notify << " -red: bezier approx derived from parametrization.\n"; + *notify << " -blue: bezier approx derived from curvature.\n"; + *notify << " max distance (to original): "<<h_dist<<"\n"; + } +#endif + + + f_as_pw = arc_length_parametrization(f_as_pw); + adjuster2.pos[0]=150; + adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.); + cairo_move_to(cr, 150, 150); + cairo_line_to(cr, 150, 450); + cairo_stroke(cr); + ostringstream val_s; + double scale0=(450-adjuster2.pos[1])/300; + double curve_precision = pow(10, scale0*5-2); + val_s << curve_precision; + draw_text(cr, adjuster2.pos, val_s.str().c_str()); + + int segs = 0; + goal_function_type = toggles[1].on; + if(toggles[0].on) + segs = sequential_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(), curve_precision); + else { + segs = recursive_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(),curve_precision); + } + Geom::PathVector vpt = path_from_piecewise(f_as_pw, curve_precision, true); + unsigned default_number_curves = 0; + for(const auto & i : vpt) { + default_number_curves += i.size(); + } + + *notify << " segments from default algorithm: "<< default_number_curves <<"\n"; + *notify << " total segments: "<< segs <<"\n"; + cairo_restore(cr); + Point p(25, height - 100), d(50,25); + toggles[0].bounds = Rect(p, p + d); + p+= Point(75, 0); + toggles[1].bounds = Rect(p, p + d); + draw_toggles(cr, toggles); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + void key_hit(GdkEventKey *e) override { + if(e->keyval == 's') toggles[0].toggle(); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + SbToBezierTester() { + //if(handles.empty()) { + for(int j = 0; j < 3; j++) { + path_psh.push_back(new PointSetHandle()); + for(unsigned i = 0; i < 6; i++) + path_psh.back()->push_back(150+300*uniform(),150+300*uniform()); + handles.push_back(path_psh.back()); + } + adjuster.pos = Geom::Point(150+300*uniform(),150+300*uniform()); + handles.push_back(&adjuster); + adjuster2.pos = Geom::Point(150,300); + handles.push_back(&adjuster2); + toggles.emplace_back("Seq", true); + toggles.emplace_back("Linfty", true); + //} + //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t")); + //handles.push_back(&(sliders[0])); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SbToBezierTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/sb-zeros.cpp b/src/toys/sb-zeros.cpp new file mode 100644 index 0000000..ebe3aea --- /dev/null +++ b/src/toys/sb-zeros.cpp @@ -0,0 +1,63 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +#define SIZE 4 + +class SBZeros: public Toy { + PointSetHandle pB1, pB2; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + D2<SBasis> B1 = pB1.asBezier(); + D2<SBasis> B2 = pB2.asBezier(); + Piecewise<D2<SBasis> >B; + B.concat(Piecewise<D2<SBasis> >(B1)); + B.concat(Piecewise<D2<SBasis> >(B2)); + std::vector<Point> e; + std::vector<Piecewise<D2<SBasis> > > s; + s.push_back(derivative(B)); + for(int j = 0; j < 5; j++) s.push_back(derivative(s.back())); + for(int j = 0; j <= 5; j++) { + for(unsigned d = 0; d < 2; d++) { + std::vector<double> r = roots(make_cuts_independent(s[j])[d]); + for(double k : r) e.push_back(B.valueAt(k)); + } + } + for(auto & i : e) draw_cross(cr, i); + + cairo_set_line_width (cr, .5); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_pw_d2_sb(cr, B); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + SBZeros () { + for(unsigned i = 0; i < SIZE; i++) + pB1.push_back(150+uniform()*300,150+uniform()*300); + for(unsigned i = 0; i < SIZE; i++) + pB2.push_back(150+uniform()*300,150+uniform()*300); + handles.push_back(&pB1); + handles.push_back(&pB2); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SBZeros()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sb1d.cpp b/src/toys/sb1d.cpp new file mode 100644 index 0000000..dabbebb --- /dev/null +++ b/src/toys/sb1d.cpp @@ -0,0 +1,125 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/choose.h> +#include <2geom/convex-hull.h> + +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +extern unsigned total_steps, total_subs; + +double& handle_to_sb(unsigned i, unsigned n, SBasis &sb) { + assert(i < n); + assert(n <= sb.size()*2); + unsigned k = i; + if(k >= n/2) { + k = n - k - 1; + return sb[k][1]; + } else + return sb[k][0]; +} + +double handle_to_sb_t(unsigned i, unsigned n) { + double k = i; + if(i >= n/2) + k = n - k - 1; + double t = k/(2*k+1); + if(i >= n/2) + return 1 - t; + else + return t; +} + +class Sb1d: public Toy { +public: + PointSetHandle hand; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0.5, 0, 1); + cairo_set_line_width (cr, 1); + + if(!save) { + for(unsigned i = 0; i < hand.pts.size(); i++) { + hand.pts[i][0] = width*handle_to_sb_t(i, hand.pts.size())/2 + width/4; + if(i) + cairo_line_to(cr, hand.pts[i]); + else + cairo_move_to(cr, hand.pts[i]); + } + } + + D2<SBasis> B; + B[0] = Linear(width/4, 3*width/4); + B[1].resize(hand.pts.size()/2); + for(auto & i : B) { + i = Linear(0); + } + for(unsigned i = 0; i < hand.pts.size(); i++) { + handle_to_sb(i, hand.pts.size(), B[1]) = 3*width/4 - hand.pts[i][1]; + } + for(unsigned i = 1; i < B[1].size(); i++) { + B[1][i] = B[1][i]*choose<double>(2*i+1, i); + } + + Interval bs = *bounds_fast(B[1]); + double lo, hi; + lo = 3*width/4 - bs.min(); + hi = 3*width/4 - bs.max(); + cairo_move_to(cr, B[0](0), lo); + cairo_line_to(cr, B[0](1), lo); + cairo_move_to(cr, B[0](0), hi); + cairo_line_to(cr, B[0](1), hi); + cairo_stroke(cr); + *notify << "sb bounds = "<<lo << ", " <<hi<<std::endl; + //B[1] = SBasis(Linear(3*width/4)) - B[1]; + *notify << B[0] << ", "; + *notify << B[1]; + Geom::Path pb; + B[1] = SBasis(Linear(3*width/4)) - B[1]; + pb.append(B); + pb.close(false); + cairo_path(cr, pb); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + + Geom::ConvexHull ch(hand.pts); + + cairo_move_to(cr, ch.back()); + for(auto i : ch) { + cairo_line_to(cr, i); + } + Toy::draw(cr, notify, width, height, save,timer_stream); + } +public: +Sb1d () { + hand.pts.emplace_back(0,450); + for(unsigned i = 0; i < 4; i++) + hand.pts.emplace_back(uniform()*400, uniform()*400); + hand.pts.emplace_back(0,450); + handles.push_back(&hand); +} +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sb1d()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sb2d-solver.cpp b/src/toys/sb2d-solver.cpp new file mode 100644 index 0000000..a60f8cd --- /dev/null +++ b/src/toys/sb2d-solver.cpp @@ -0,0 +1,282 @@ +#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <gsl/gsl_poly.h>
+
+using std::vector;
+using namespace Geom;
+
+//see a sb2d as an sb of u with coef in sbasis of v.
+void
+u_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.vs, Linear());
+ b = SBasis(f.vs, Linear());
+ for (unsigned v=0; v<f.vs; v++){
+ a[v] = Linear(f.index(deg,v)[0], f.index(deg,v)[2]);
+ b[v] = Linear(f.index(deg,v)[1], f.index(deg,v)[3]);
+ }
+}
+void
+v_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.us, Linear());
+ b = SBasis(f.us, Linear());
+ for (unsigned u=0; u<f.us; u++){
+ a[u] = Linear(f.index(deg,u)[0], f.index(deg,u)[1]);
+ b[u] = Linear(f.index(deg,u)[2], f.index(deg,u)[3]);
+ }
+}
+
+
+
+//TODO: implement sb2d algebra!!
+SBasis2d y_x2(){
+ SBasis2d result(Linear2d(0,-1,1,0));
+ result.push_back(Linear2d(1,1,1,1));
+ result.us = 2;
+ result.vs = 1;
+ return result;
+}
+
+SBasis2d x2_plus_y2_1(){
+/*TODO: implement sb2d algebra!!
+ SBasis2d one(Linear2d(1,1,1,1));
+ SBasis2d u(Linear2d(0,1,0,1));
+ SBasis2d v(Linear2d(0,0,1,1));
+ return(u*u+v*v-one);
+*/
+ SBasis2d result(Linear2d(-1,0,0,1));//x+y-1
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(0,0,0,0));
+ result.us = 2;
+ result.vs = 2;
+ return result;
+}
+
+SBasis2d conic_sb2d(vector<double> /*coeff*/) {
+/*TODO: implement sb2d algebra!!
+ SBasis2d one(Linear2d(1,1,1,1));
+ SBasis2d u(Linear2d(0,1,0,1));
+ SBasis2d v(Linear2d(0,0,1,1));
+ return(u*u+v*v-one);
+*/
+ SBasis2d result(Linear2d(-1,0,0,1));//x+y-1
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(0,0,0,0));
+ result.us = 2;
+ result.vs = 2;
+ return result;
+}
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr,
+ Piecewise<SBasis> const &x,
+ Piecewise<SBasis> const &y,
+ Piecewise<SBasis> const &z, Frame frame){
+
+ Piecewise<SBasis> xx = partition(x,y.cuts);
+ Piecewise<SBasis> xxx = partition(xx,z.cuts);
+ Piecewise<SBasis> yyy = partition(y,xxx.cuts);
+ Piecewise<SBasis> zzz = partition(z,xxx.cuts);
+
+ for (unsigned i=0; i<xxx.size(); i++){
+ plot3d(cr, xxx[i], yyy[i], zzz[i], frame);
+ }
+}
+
+void
+plot3d(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ D2<SBasis> seg(SBasis(0.,1.), SBasis(i*1./NbRays,i*1./NbRays));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ for (int i=0; i<NbRays; i++){
+ D2<SBasis> seg(SBasis(i*1./NbRays, i*1./NbRays), SBasis(0.,1.));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+}
+
+void
+plot3d_top(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ for(int j=0; j<2; j++){
+ D2<SBasis> seg;
+ if (j==0){
+ seg = D2<SBasis>(SBasis(0.,1.), SBasis(i*1./NbRays,i*1./NbRays));
+ }else{
+ seg = D2<SBasis>(SBasis(i*1./NbRays,i*1./NbRays), SBasis(0.,1.));
+ }
+ SBasis f_on_seg = compose(f,seg);
+ std::vector<double> rts = roots(f_on_seg);
+ if (rts.empty()||rts.back()<1) rts.push_back(1.);
+ double t1,t0 = 0;
+ for (unsigned i=(rts.front()<=0?1:0); i<rts.size(); i++){
+ t1 = rts[i];
+ if (f_on_seg((t0+t1)/2)>0)
+ plot3d(cr,seg[X](Linear(t0,t1)),seg[Y](Linear(t0,t1)),f_on_seg(Linear(t0,t1)),frame);
+ t0=t1;
+ }
+ //plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ }
+}
+
+class Sb2dSolverToy: public Toy {
+public:
+ PointSetHandle hand;
+ Sb2dSolverToy() {
+ handles.push_back(&hand);
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+#if 0
+ SBasis2d f = y_x2();
+ D2<SBasis> true_solution(Linear(0,1),Linear(0,1));
+ true_solution[Y].push_back(Linear(-1,-1));
+ SBasis zero = SBasis(Linear(0.));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+
+#elif 0
+ SBasis2d f = x2_plus_y2_1();
+ D2<Piecewise<SBasis> > true_solution;
+ true_solution[X] = cos(SBasis(Linear(0,3.14/2)));
+ true_solution[Y] = sin(SBasis(Linear(0,3.14/2)));
+ Piecewise<SBasis> zero = Piecewise<SBasis>(SBasis(Linear(0.)));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+#else
+ SBasis2d f = conic_sb2d(vector<double>());
+ D2<Piecewise<SBasis> > true_solution;
+ true_solution[X] = cos(SBasis(Linear(0,3.14/2)));
+ true_solution[Y] = sin(SBasis(Linear(0,3.14/2)));
+ Piecewise<SBasis> zero = Piecewise<SBasis>(SBasis(Linear(0.)));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+#endif
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+ plot3d_top(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+
+ plot3d(cr, true_solution[X], true_solution[Y], zero, frame);
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+ double error;
+ for(int degree = 1; degree < 4; degree++) {
+ //D2<SBasis> zeroset = sb2dsolve(f,A,B,degree);
+ D2<SBasis> zeroset = sb2d_cubic_solve(f,A,B);
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasis comp = compose(f,zeroset);
+ plot3d(cr, zeroset[X], zeroset[Y], comp, frame);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, 1.);
+ cairo_stroke(cr);
+ //Fix Me: bounds_exact does not work here?!?!
+ Interval bounds = *bounds_fast(comp);
+ error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+ }
+ *notify << "Gray: f-graph and true solution,\n";
+ *notify << "Red: solver solution,\n";
+ *notify << "Purple: value of f over solver solution.\n";
+ *notify << " error: "<< error <<".\n";
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb2dSolverToy());
+ return 0;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/toys/sb2d.cpp b/src/toys/sb2d.cpp new file mode 100644 index 0000000..b449d01 --- /dev/null +++ b/src/toys/sb2d.cpp @@ -0,0 +1,83 @@ +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +class Sb2d: public Toy { +public: + PointSetHandle hand; + Sb2d() { + handles.push_back(&hand); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + SBasis2d sb2; + sb2.us = 2; + sb2.vs = 2; + const int depth = sb2.us*sb2.vs; + const int surface_handles = 4*depth; + sb2.resize(depth, Linear2d(0)); + vector<Geom::Point> display_handles(surface_handles); + Geom::Point dir(1,-2); + if(hand.pts.empty()) { + for(unsigned vi = 0; vi < sb2.vs; vi++) + for(unsigned ui = 0; ui < sb2.us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) + hand.pts.emplace_back((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + + hand.pts.push_back(Geom::Point(3*width/4., width/4.) + 30*dir); + } + dir = (hand.pts[surface_handles] - Geom::Point(3*width/4., width/4.)) / 30; + if(!save) { + cairo_move_to(cr, 3*width/4., width/4.); + cairo_line_to(cr, hand.pts[surface_handles]); + } + for(unsigned vi = 0; vi < sb2.vs; vi++) + for(unsigned ui = 0; ui < sb2.us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) { + unsigned corner = iu + 2*iv; + unsigned i = ui + vi*sb2.us; + Geom::Point base((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + double dl = dot((hand.pts[corner+4*i] - base), dir)/dot(dir,dir); + display_handles[corner+4*i] = dl*dir + base; + sb2[i][corner] = dl*10/(width/2)*pow(4.,(double)ui+vi); + } + cairo_sb2d(cr, sb2, dir*0.1, width); + + *notify << "bo = " << sb2.index(0,0); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + if(!save) + for(auto & display_handle : display_handles) + draw_circ(cr, display_handle); + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sb2d()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sbasis-fitting.cpp b/src/toys/sbasis-fitting.cpp new file mode 100644 index 0000000..9963790 --- /dev/null +++ b/src/toys/sbasis-fitting.cpp @@ -0,0 +1,214 @@ +/* + * SBasis Fitting Example + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class SBasisFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + sliders[0].geometry(Point(50, 30), 100); + + size_t value = (size_t)(sliders[0].value()); + + if (degree != value) + { + degree = value; + total_handles = degree + 1; + order = total_handles/2 - 1; + dstep = interval_length/degree; + step = 1.0/degree; + + double x = sx; + psh.pts.clear(); + for (size_t i = 0; i < total_handles; ++i) + { + psh.push_back(x, 300*uniform()+50); + x += dstep; + } + + handles[0] = &psh; + + if (fmsb != NULL) delete fmsb; + fmsb = new NL::LFMSBasis(order); + assert(fmsb != NULL); + if (lsf_sb != NULL) delete lsf_sb; + lsf_sb = new NL::least_squeares_fitter<NL::LFMSBasis>(*fmsb, 25); + assert(lsf_sb != NULL); + + double t = 0; + for (size_t i = 0; i < total_handles; ++i) + { + lsf_sb->append(t); + t += step; + } + lsf_sb->update(); + + curr_ys.clear(); + curr_ys.resize(total_handles); + for (size_t i = 0; i < total_handles; ++i) + { + curr_ys[i] = psh.pts[i][Y]; + } + prev_ys = curr_ys; + + fmsb->instance(sb_curve, lsf_sb->result(curr_ys)); + } + else + { + double x = sx; + for (size_t i = 0; i < total_handles; ++i) + { + psh.pts[i][X] = x; + curr_ys[i] = psh.pts[i][Y]; + x += dstep; + } + fmsb->instance(sb_curve, lsf_sb->result(prev_ys, curr_ys)); + prev_ys = curr_ys; + } + + + D2<SBasis> curve; + curve[X] = SBasis(Linear(sx,sx) + interval_length * Linear(0, 1)); + curve[Y] = sb_curve; + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + cairo_d2_sb(cr, curve); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + SBasisFitting() + : degree(3), + total_handles(degree+1), + order(total_handles/2 - 1), + interval_length(400), + dstep(interval_length/degree), + step(1.0/degree) + { + sx = 50; + double x = sx; + for (size_t i = 0; i < total_handles; ++i) + { + psh.push_back(x, 300*uniform()+50); + x += dstep; + } + + handles.push_back(&psh); + + fmsb = new NL::LFMSBasis(order); + assert(fmsb != NULL); + lsf_sb = new NL::least_squeares_fitter<NL::LFMSBasis>(*fmsb, 25); + assert(lsf_sb != NULL); + + double t = 0; + for (size_t i = 0; i < total_handles; ++i) + { + lsf_sb->append(t); + t += step; + } + lsf_sb->update(); + + curr_ys.clear(); + curr_ys.resize(total_handles); + for (size_t i = 0; i < total_handles; ++i) + { + curr_ys[i] = psh.pts[i][Y]; + } + prev_ys = curr_ys; + + fmsb->instance(sb_curve, lsf_sb->result(curr_ys)); + + + sliders.emplace_back(1, 11, 2, 3, "degree"); + handles.push_back(&(sliders[0])); + } + + ~SBasisFitting() override + { + if (fmsb != NULL) delete fmsb; + if (lsf_sb != NULL) delete lsf_sb; + } + + private: + size_t degree; + size_t total_handles; + size_t order; + double interval_length; + double dstep; + double step; + double sx; + std::vector<double> curr_ys, prev_ys; + SBasis sb_curve; + NL::LFMSBasis* fmsb; + NL::least_squeares_fitter<NL::LFMSBasis>* lsf_sb; + PointSetHandle psh; + std::vector<Slider> sliders; +}; + + + + +int main(int argc, char **argv) +{ + init( argc, argv, new SBasisFitting(), 600, 600 ); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + diff --git a/src/toys/sbasisdim.cpp b/src/toys/sbasisdim.cpp new file mode 100644 index 0000000..3804090 --- /dev/null +++ b/src/toys/sbasisdim.cpp @@ -0,0 +1,262 @@ +#include <iostream>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+#include <vector>
+
+#include <2geom/orphan-code/linearN.h>
+#include <2geom/orphan-code/sbasisN.h>
+
+using namespace Geom;
+using namespace std;
+
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, double x, double y, double z, Frame frame){
+ Point p;
+ for (unsigned dim=0; dim<2; dim++){
+ p[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ p[dim] += frame.O[dim];
+ }
+ draw_cross(cr, p);
+}
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr, LinearN<2> const &f, Frame frame){
+ int iMax = 5;
+ for (int i=0; i<iMax; i++){
+ double t = i/(iMax-1.);
+ plot3d(cr, Linear(0,1), Linear(t), toLinear(f.partialEval(t, 1)), frame);
+ plot3d(cr, Linear(t), Linear(0,1), toLinear(f.partialEval(t, 0)), frame);
+ }
+}
+void
+plot3d(cairo_t *cr, SBasisN<2> const &f, Frame frame){
+ int iMax = 5;
+ for (int i=0; i<iMax; i++){
+ double t = i/(iMax-1.);
+ plot3d(cr, Linear(0,1), Linear(t), toSBasis(f.partialEval(t, 1)), frame);
+ plot3d(cr, Linear(t), Linear(0,1), toSBasis(f.partialEval(t, 0)), frame);
+ }
+}
+void
+dot_plot3d(cairo_t *cr, SBasisN<2> const &f, Frame frame){
+ int iMax = 15;
+ double t[2];
+ for (int i=0; i<iMax; i++){
+ t[0] = i/(iMax-1.);
+ for (int j=0; j<iMax; j++){
+ t[1] = j/(iMax-1.);
+ plot3d(cr, t[0], t[1], f.valueAt(t), frame);
+ }
+ }
+}
+
+
+class SBasisDimToy: public Toy {
+ PointSetHandle hand;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasisN<1> t = LinearN<1>(0,1);
+
+ LinearN<2> u,v;
+ setToVariable(u,0);
+ setToVariable(v,1);
+ SBasisN<2> f, x = u, y = v; //x,y are used for conversion :-(
+
+
+//--------------------
+//Basic MultiDegree<2> tests...
+//--------------------
+#if 0
+ unsigned sizes[2];
+ sizes[0] = 4;
+ sizes[1] = 3;
+ MultiDegree<2> d0;
+ d0.p[0]=3;
+ d0.p[1]=2;
+ std::cout<<"(3,2)->"<< d0.asIdx(sizes) <<"\n";
+ MultiDegree<2> d1(11,sizes);
+ std::cout<<"11->"<< d1.p[0] <<","<<d1.p[1] <<"\n";
+#endif
+
+//--------------------
+//Basic LinearN tests
+//--------------------
+#if 0
+ plot3d(cr, u, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .75, 0., 0., 1.);
+ cairo_stroke(cr);
+ plot3d(cr, v, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0., 0., 0.75, 1.);
+ cairo_stroke(cr);
+#endif
+
+//--------------------
+//Basic SBasisN tests
+//--------------------
+#if 1
+ f = x*x + y*y;//(x-one*.5)*(x-one*.5);
+ std::cout<<"\nf: "<<f<<"\n";
+ std::cout<<"Degrees:\n";
+ std::cout<<"quick_deg: "<< f.quick_degree(0)<<", "<<f.quick_degree(1)<<"\n";
+ std::cout<<"real s_deg: "<< f.degree(0)<<", "<<f.degree(1)<<"\n";
+ std::cout<<"real t_deg: "<< f.real_t_degree(0)<<", "<<f.real_t_degree(1)<<"\n";
+ plot3d(cr, f, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0., 0.75, 0., 1.);
+ cairo_stroke(cr);
+#endif
+
+//--------------------
+// SBasisOf<SBasisOf<double> > simulation tests
+//--------------------
+#if 1
+ SBasisN<1> y1d = LinearN<1>(0,1);
+ SBasisN<2> g,g1;
+ g.appendCoef(LinearN<1>(0.), y1d*y1d , 0);
+ g.appendCoef(y1d + 1, y1d, 0);
+ g1 = x*y*y + x*(-x+1)*( (-x+1)*(y+1) + x*y );
+ plot3d(cr, g-g1, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0., 0.75, 0., 1.);
+ cairo_stroke(cr);
+#endif
+//--------------------
+// SBasisN composition tests
+//--------------------
+#if 1
+ SBasisN<1> z;
+ std::vector<SBasisN<1> > var;
+ t -=.5;
+ var.push_back( t*t + tA);
+ var.push_back( (t+.3)*t*(t-.3) + tB);
+ z = compose(f,var);
+ cairo_set_line_width(cr,1);
+ plot3d(cr, toSBasis(var[0]), toSBasis(var[1]), Linear(0.), frame);
+ cairo_set_source_rgba (cr, 0., 0., 0.75, 1.);
+ cairo_stroke(cr);
+ plot3d(cr, toSBasis(var[0]), toSBasis(var[1]), toSBasis(z), frame);
+ cairo_set_source_rgba (cr, 0.75, 0., 0., 1.);
+ cairo_stroke(cr);
+#endif
+
+//--------------------
+//Some timing. TODO: Compare to SBasisOf<SBasisOf<double> >
+//--------------------
+#if 0
+ double units = 1e6;
+ std::string units_string("us");
+ double timer_precision = 0.1;
+ clock_t end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ // Base loop to remove overhead
+ end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ long iterations = 0;
+ while(end_t > clock()) {
+ iterations++;
+ }
+ double overhead = timer_precision*units/iterations;
+
+ end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ iterations = 0;
+ while(end_t > clock()) {
+ f.valueAt(t);
+ iterations++;
+ }
+ *notify << "recursive eval: "
+ << ", time = " << timer_precision*units/iterations-overhead
+ << units_string << std::endl;
+#endif
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SBasisDimToy(){
+ handles.push_back(&hand);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SBasisDimToy);
+ return 0;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/scribble.cpp b/src/toys/scribble.cpp new file mode 100644 index 0000000..afaff65 --- /dev/null +++ b/src/toys/scribble.cpp @@ -0,0 +1,366 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-math.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/path-intersection.h> +#include <2geom/bezier-curve.h> +#include <2geom/transforms.h> +#include <2geom/angle.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <sstream> + +using std::vector; +using namespace Geom; +using namespace std; + +// TODO: +// use path2 +// replace Ray stuff with path2 line segments. + +//----------------------------------------------- + +void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) +{ + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); +} + +void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) +{ + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); +} + +void draw_segment(cairo_t* cr, LineSegment const& ls) +{ + draw_segment(cr, ls[0], ls[1]); +} + +int num = 30; +vector<double> c1_bot; +vector<double> c1_top; +vector<double> c2_bot; +vector<double> c2_top; +vector<double> c3_bot; +vector<double> c3_top; +vector<double> c4_bot; +vector<double> c4_top; + +CubicBezier create_bezier(Point const &anchor, double angle /* in degrees */, + double length, double dx1, double dx2, cairo_t *cr = NULL) { + Point A = anchor; + Point dir = Point(1.0, 0) * Rotate(-angle) * length; + Point B = anchor + dir; + + Point C = A - Point(1.0, 0) * dx1; + Point D = B + Point(1.0, 0) * dx1; + Point E = A + Point(1.0, 0) * dx2; + Point F = B - Point(1.0, 0) * dx2; + + if (cr) { + draw_cross(cr, A); + draw_cross(cr, B); + draw_cross(cr, C); + draw_cross(cr, D); + draw_cross(cr, E); + draw_cross(cr, F); + } + + return CubicBezier(C, E, F, D); +} + +/* + * Draws a single "scribble segment" (we use many of these to cover the whole curve). + * + * Let I1, I2 be two adjacent intervals (bounded by the points A1, A2, A3) on the lower and J1, J2 + * two adjacent intervals (bounded by B1, B2, B3) on the upper parallel. Then we specify: + * + * - The point in I1 where the scribble line starts (given by a value in [0,1]) + * - The point in J2 where the scribble line ends (given by a value in [0,1]) + * - A point in I2 (1st intermediate point of the Bezier curve) + * - A point in J1 (2nd intermediate point of the Bezier curve) + * + */ +CubicBezier +create_bezier_again(Point const &anchor1, Point const &anchor2, Point const &dir1, Point const &dir2, + double /*c1*/, double /*c2*/, double c3, double c4, double mu, cairo_t *cr = NULL) { + Point A = anchor1;// - dir * c1; + Point B = anchor1 + dir1 * (c3 + mu); + Point C = anchor2 - dir2 * (c4 + mu); + Point D = anchor2;// + dir * c2; + + if (cr) { + draw_cross(cr, A); + //draw_cross(cr, B); + //draw_cross(cr, C); + //draw_cross(cr, D); + } + + return CubicBezier(A, B, C, D); +} + +CubicBezier +create_bezier_along_curve(Piecewise<D2<SBasis> > const &curve1, + Piecewise<D2<SBasis> > const &curve2, + double segdist, + Coord const t1, Coord const t2, Point const &n, + double c1, double c2, double /*c3*/, double /*c4*/, double /*mu*/, cairo_t *cr = NULL) { + cout << "create_bezier_along_curve -- start" << endl; + /* + Point A = curve1.valueAt(t1 - c1); + Point B = curve1.valueAt(t1) + n * (c3 + mu); + Point C = curve2.valueAt(t2) - n * (c4 + mu); + Point D = curve2.valueAt(t2 + c2); + */ + Point A = curve1.valueAt(t1 - c1 * segdist); + Point B = curve1.valueAt(t1) + n * 0.1; + Point C = curve2.valueAt(t2) - n * 0.1; + Point D = curve2.valueAt(t2 + c2 * segdist); + + if (cr) { + draw_cross(cr, A); + //draw_cross(cr, B); + //draw_cross(cr, C); + //draw_cross(cr, D); + } + + cout << "create_bezier_along_curve -- end" << endl; + return CubicBezier(A, B, C, D); +} + +class OffsetTester: public Toy { + PointSetHandle psh; + PointSetHandle psh_rand; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + double w = 600; + double slider_top = w/4.; + double slider_bot = w*3./4.; + double slider_margin = 40; + double slider_middle = (slider_top + slider_bot) / 2; + + if(psh.pts.empty()) { + psh.pts.emplace_back(200,300); + psh.pts.emplace_back(350,250); + psh.pts.emplace_back(500,280); + psh.pts.emplace_back(700,300); + + psh.pts.emplace_back(400,300); + psh.pts.emplace_back(550,250); + psh.pts.emplace_back(700,280); + psh.pts.emplace_back(900,300); + + psh.pts.emplace_back(900,500); + psh.pts.emplace_back(700,480); + psh.pts.emplace_back(550,450); + psh.pts.emplace_back(400,500); + + psh_rand.pts.emplace_back(slider_margin,slider_bot); + psh_rand.pts.emplace_back(slider_margin,slider_top); + psh_rand.pts.emplace_back(slider_margin,slider_top); + psh_rand.pts.emplace_back(slider_margin,slider_top); + psh_rand.pts.emplace_back(slider_margin,slider_top); + psh_rand.pts.emplace_back(slider_margin,slider_bot); + psh_rand.pts.emplace_back(slider_margin,slider_middle); + } + + psh_rand.pts[0][X] = slider_margin; + if (psh_rand.pts[0][Y]<slider_top) psh_rand.pts[0][Y] = slider_top; + if (psh_rand.pts[0][Y]>slider_bot) psh_rand.pts[0][Y] = slider_bot; + psh_rand.pts[1][X] = slider_margin + 15; + if (psh_rand.pts[1][Y]<slider_top) psh_rand.pts[1][Y] = slider_top; + if (psh_rand.pts[1][Y]>slider_bot) psh_rand.pts[1][Y] = slider_bot; + psh_rand.pts[2][X] = slider_margin + 30; + if (psh_rand.pts[2][Y]<slider_top) psh_rand.pts[2][Y] = slider_top; + if (psh_rand.pts[2][Y]>slider_bot) psh_rand.pts[2][Y] = slider_bot; + psh_rand.pts[3][X] = slider_margin + 45; + if (psh_rand.pts[3][Y]<slider_top) psh_rand.pts[3][Y] = slider_top; + if (psh_rand.pts[3][Y]>slider_bot) psh_rand.pts[3][Y] = slider_bot; + psh_rand.pts[4][X] = slider_margin + 60; + if (psh_rand.pts[4][Y]<slider_top) psh_rand.pts[4][Y] = slider_top; + if (psh_rand.pts[4][Y]>slider_bot) psh_rand.pts[4][Y] = slider_bot; + psh_rand.pts[5][X] = slider_margin + 75; + if (psh_rand.pts[5][Y]<slider_top) psh_rand.pts[5][Y] = slider_top; + if (psh_rand.pts[5][Y]>slider_bot) psh_rand.pts[5][Y] = slider_bot; + psh_rand.pts[6][X] = slider_margin + 90; + if (psh_rand.pts[6][Y]<slider_top) psh_rand.pts[6][Y] = slider_top; + if (psh_rand.pts[6][Y]>slider_bot) psh_rand.pts[6][Y] = slider_bot; + + *notify << "Sliders:" << endl << endl << endl << endl; + *notify << "0 - segment distance" << endl; + *notify << "1 - start anchor randomization" << endl; + *notify << "2 - end anchor randomization" << endl; + *notify << "3 - start rounding randomization" << endl; + *notify << "4 - end rounding randomization" << endl; + *notify << "5 - start/end rounding increase randomization" << endl; + *notify << "6 - additional offset of the upper anchors (to modify the segment angle)" << endl; + + for(unsigned i = 0; i < psh_rand.size(); ++i) { + cairo_move_to(cr,Geom::Point(slider_margin + 15.0 * i, slider_bot)); + cairo_line_to(cr,Geom::Point(slider_margin + 15.0 * i, slider_top)); + } + cairo_set_line_width(cr,.5); + cairo_set_source_rgba (cr, 0., 0.3, 0., 1.); + cairo_stroke(cr); + + cairo_set_line_width (cr, 2); + cairo_set_source_rgba (cr, 0., 0., 0.8, 1); + + // Draw the curve and its offsets + D2<SBasis> B = psh.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + Coord offset = 30; + Piecewise<D2<SBasis> > n = rot90(unitVector(derivative(B))); + Piecewise<D2<SBasis> > offset_curve1 = Piecewise<D2<SBasis> >(B)+n*offset; + Piecewise<D2<SBasis> > offset_curve2 = Piecewise<D2<SBasis> >(B)-n*offset; + PathVector offset_path1 = path_from_piecewise(offset_curve1, 0.1); + PathVector offset_path2 = path_from_piecewise(offset_curve2, 0.1); + Piecewise<D2<SBasis> > tangent1 = unitVector(derivative(offset_curve1)); + Piecewise<D2<SBasis> > tangent2 = unitVector(derivative(offset_curve2)); + cairo_set_line_width (cr, 1); + cairo_path(cr, offset_path1); + cairo_path(cr, offset_path2); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + + double lambda1 = 1.0 - (psh_rand.pts[1][Y] - slider_top) * 2.0/w; + double lambda2 = 1.0 - (psh_rand.pts[2][Y] - slider_top) * 2.0/w; + double lambda3 = 1.0 - (psh_rand.pts[3][Y] - slider_top) * 2.0/w; + double lambda4 = 1.0 - (psh_rand.pts[4][Y] - slider_top) * 2.0/w; + double mu = 1.0 - (psh_rand.pts[5][Y] - slider_top) * 2.0/w; + //Point dir = Point(1,0) * (slider_bot - psh_rand.pts[0][Y]) / 2.5; + double off = 0.5 - (psh_rand.pts[6][Y] - slider_top) * 2.0/w; + + double segdist = (slider_bot - psh_rand.pts[0][Y]) / (slider_bot - slider_top) * 0.1; + if (segdist < 0.01) { + segdist = 0.01; + } + + vector<Point> pts_bot; + vector<Point> pts_top; + vector<Point> dirs_bot; + vector<Point> dirs_top; + int counter = 0; + for(double i = 0.0; i < 1.0; i += segdist) { + draw_cross(cr, offset_curve1.valueAt(i)); + pts_bot.push_back(offset_curve1.valueAt(i + segdist * c1_bot[counter] * lambda1)); + pts_top.push_back(offset_curve2.valueAt(i + segdist * (c2_top[counter] * lambda2 + 1/2.0) + off)); + dirs_bot.push_back(tangent1.valueAt(i) * 20); + dirs_top.push_back(tangent2.valueAt(i) * 20); + ++counter; + } + + for(int i = 0; i < num; ++i) { + cout << "c1_bot[" << i << "]: " << c1_bot[i] << endl; + } + + for (int i = 0; i < num-1; ++i) { + Path path1; + //cout << "dirs_bot[" << i << "]: " << dirs_bot[i] << endl; + cout << "c3_bot[" << i << "]: " << c3_bot[i] << endl; + CubicBezier bc = create_bezier_again(pts_bot[i], pts_top[i], + dirs_bot[i], dirs_top[i], + 0, 0, c3_bot[i] * lambda3, c4_top[i] * lambda4, mu, cr); + //c1_bot[i] * lambda1, + //c2_top[i] * lambda2, + //c3_bot[i] * lambda3, + //c4_top[i] * lambda4, mu, cr); + + path1.append(bc); + cairo_path(cr, path1); + + Path path2; + bc = create_bezier_again(pts_top[i], pts_bot[i+1], + dirs_top[i], dirs_bot[i+1], + 0, 0, c4_bot[i] * lambda4, c3_top[i] * lambda3, mu, cr); + /* + bc = create_bezier_again(pts_top[i+1], pts_bot[i], dir, + 1.0 - c2_top[i] * lambda2, + 1.0 - c1_bot[i+1] * lambda1, + c3_top[i] * lambda3, + c4_bot[i] * lambda4, mu, cr); + */ + path2.append(bc); + cairo_path(cr, path2); + } + + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + OffsetTester() { + handles.push_back(&psh); + handles.push_back(&psh_rand); + /* + psh.pts.clear(); + Point A(100,300); + //Point B(100,200); + //Point C(200,330); + Point D(200,100); + psh.push_back(A); + //psh.push_back(B); + //psh.push_back(C); + psh.push_back(D); + psh.push_back(Geom::Point(slider_margin,slider_bot)); + */ + + for (int i = 0; i < num; ++i) { + c1_bot.push_back(uniform() - 0.5); + c1_top.push_back(uniform() - 0.5); + //c1_bot.push_back(1.0); + //c1_top.push_back(1.0); + c2_bot.push_back(uniform() - 0.5); + c2_top.push_back(uniform() - 0.5); + + c3_bot.push_back(uniform()); + c3_top.push_back(uniform()); + c4_bot.push_back(uniform()); + c4_top.push_back(uniform()); + } + + /* + for (int i = 0; i < num; ++i) { + c1_bot[i] = c1_bot[i] / 10.0; + c2_bot[i] = c2_bot[i] / 10.0; + c3_bot[i] = c3_bot[i] / 10.0; + c4_bot[i] = c4_bot[i] / 10.0; + + c1_top[i] = c1_top[i] / 10.0; + c2_top[i] = c2_top[i] / 10.0; + c3_top[i] = c3_top[i] / 10.0; + c4_top[i] = c4_top[i] / 10.0; + } + */ + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new OffsetTester); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +//vim:filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/self-intersect.cpp b/src/toys/self-intersect.cpp new file mode 100644 index 0000000..840e058 --- /dev/null +++ b/src/toys/self-intersect.cpp @@ -0,0 +1,67 @@ +#include <2geom/basic-intersection.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +class SelfIntersect: public Toy { + PointSetHandle psh; +void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 0.5); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + + D2<SBasis> A = psh.asBezier(); + //Rect Ar = *bounds_fast(A); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + + std::vector<std::pair<double, double> > all_si; + + find_self_intersections(all_si, A); + + cairo_stroke(cr); + cairo_set_source_rgba (cr, 1., 0., 1, 1); + for(auto & i : all_si) { + draw_handle(cr, A(i.first)); + } + cairo_stroke(cr); + + *notify << "total intersections: " << all_si.size(); + + Toy::draw(cr, notify, width, height, save,timer_stream); +} +public: +SelfIntersect (unsigned bez_ord) { + handles.push_back(&psh); + for(unsigned i = 0; i < bez_ord; i++) + psh.push_back(uniform()*400, uniform()*400); +} +}; + +int main(int argc, char **argv) { + unsigned bez_ord=5; + if(argc > 1) + sscanf(argv[1], "%d", &bez_ord); + init(argc, argv, new SelfIntersect(bez_ord)); + + return 0; +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sketch-fitter.cpp b/src/toys/sketch-fitter.cpp new file mode 100644 index 0000000..2a74924 --- /dev/null +++ b/src/toys/sketch-fitter.cpp @@ -0,0 +1,923 @@ +/* + * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier. + * + * Copyright 2007 jf barraud. + * 2008 njh + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +// mainly experimental atm... +// do not expect to find anything understandable here atm. + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-math.h> +#include <2geom/basic-intersection.h> +#include <2geom/bezier-utils.h> + +#include <2geom/circle.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZERO 1e-7 + +using std::vector; +using namespace Geom; +using namespace std; + +#include <stdio.h> +#include <gsl/gsl_poly.h> + +std::vector<Point> neighbors(std::vector<Point> const &pts, unsigned idx, double radius){ + std::vector<Point> res; + Point p0 = pts[idx]; + for (auto p : pts){ + if ( L2(p-p0) < radius ) res.push_back(p); + } + return res; +} + +double curvature(Point const &a, Point const &b, Point const &c){ + Line med_ab = Line( (a+b)/2, (a+b)/2+rot90(b-a) ); + Line med_bc = Line( (b+c)/2, (b+c)/2+rot90(c-b) ); + OptCrossing o = intersection(med_ab, med_bc); + if (o){ + Point oo = med_ab.pointAt(o->ta); + return(1./L2(oo-a)); + } + else + return 0; +} + +double avarageCurvature(std::vector<Point> const &pts, unsigned idx, double radius){ + std::vector<Point> ngbrs = neighbors(pts, idx, radius); + if (ngbrs.size()<3) return 0; + double k=0; + double mass = 0; + for (unsigned i=0; i<5; i++){ + unsigned ia = 0, ib = 0, ic = 0; + ia = rand()%ngbrs.size(); + while (ib == ia) + ib = rand()%ngbrs.size(); + while (ic == ia || ic == ib) + ic = rand()%ngbrs.size(); + k += curvature(pts[ia],pts[ib],pts[ic]); + mass += 1; //smaller mass to closer triplets? + } + k /= mass; + return k; +} + +Point massCenter(std::vector<Point> const &pts){ + Point g = Point(0,0); + for (unsigned i=0; i<pts.size(); i++){ + g += pts[i]/pts.size(); + } + return g; +} + +Line meanSquareLine(std::vector<Point> const &pts){ + Point g = massCenter(pts); + double a = 0, b = 0, c = 0; + for (auto pt : pts){ + a += (pt[Y]-g[Y])*(pt[Y]-g[Y]); + b +=-(pt[X]-g[X])*(pt[Y]-g[Y]); + c += (pt[X]-g[X])*(pt[X]-g[X]); + } + double eigen = ( (a+c) - sqrt((a-c)*(a-c)+4*b*b) )/2; + Point u(-b,a-eigen); + return Line(g, g+u); +} + +void tighten(std::vector<Point> &pts, double radius, bool linear){ + for (unsigned i=0; i<pts.size(); i++){ + std::vector<Point> ngbrs = neighbors(pts,i,radius); + if (linear){ + Line d = meanSquareLine(ngbrs); + Point proj = projection( pts[i], d ); + double t = 2./3.; + pts[i] = pts[i]*(1-t) + proj*t; + }else if (ngbrs.size()>=3) { + Circle c; + c.fit(ngbrs); + Point o = c.center(); + double r = c.radius(); + pts[i] = o + unit_vector(pts[i]-o)*r; + } + } +} + +double dist_to(std::vector<Point> const &pts, Point const &p, unsigned *idx=NULL){ + double d,d_min = std::numeric_limits<float>::infinity(); + if (idx) *idx = pts.size(); + for (unsigned i = 0; i<pts.size(); i++){ + d = L2(pts[i]-p); + if ( d < d_min ){ + d_min = d; + if (idx) *idx = i; + } + } + return d_min; +} + +void fuse_close_points(std::vector<Point> &pts, double dist_min){ + if (pts.size()==0) return; + std::vector<Point> reduced_pts; + reduced_pts.push_back(pts[0]); + for (auto & pt : pts){ + double d = dist_to(reduced_pts, pt); + if ( d > dist_min ) reduced_pts.push_back(pt); + } + pts = reduced_pts; + return; +} + + +unsigned nearest_after(std::vector<Point>const &pts, unsigned idx, double *dist = NULL){ + if ( idx >= pts.size()-1 ) return pts.size(); + Point p = pts[idx]; + unsigned res = idx+1; + double d_min = L2(p-pts[res]); + for (unsigned i=idx+2; i<pts.size(); i++){ + double d = L2(p-pts[i]); + if (d < d_min) { + d_min = d; + res = i; + } + } + if (dist) *dist = d_min; + return res; +} + +//TEST ME: use direction information to separate exaeco? +void sort_nearest(std::vector<Point> &pts, double no_longer_than = 0){ + double d; + Point p; + for (unsigned i=0; i<pts.size()-1; i++){ + unsigned j = nearest_after(pts,i,&d); + if (no_longer_than >0.1 && d > no_longer_than){ + pts.erase(pts.begin()+i+1, pts.end()); + return; + } + p = pts[i+1]; + pts[i+1] = pts[j]; + pts[j] = p; + } +} + +//FIXME: optimize me if further used... +void sort_nearest_bis(std::vector<Point> &pts, double radius){ + double d; + Point p; + for (unsigned i=0; i<pts.size()-1; i++){ + bool already_visited = true; + unsigned next = 0; // silence warning + while ( i < pts.size()-1 && already_visited ){ + next = nearest_after(pts,i,&d); + already_visited = false; + for (unsigned k=0; k<i; k++){ + double d_k_next = L2( pts[next] - pts[k]); + if ( d_k_next < d && d_k_next < radius ){ + already_visited = true; + pts.erase(pts.begin()+next); + break; + } + } + } + if (!already_visited){ + p = pts[i+1]; + pts[i+1] = pts[next]; + pts[next] = p; + } + } +} + +Path ordered_fit(std::vector<Point> &pts, double tol){ + unsigned n_points = pts.size(); + Geom::Point * b = g_new(Geom::Point, 4*n_points); + Geom::Point * points = g_new(Geom::Point, 4*n_points); + for (unsigned int i = 0; i < pts.size(); i++) { + points[i] = pts[i]; + } + int max_segs = 4*n_points; + int const n_segs = bezier_fit_cubic_r(b, points, n_points, + tol*tol, max_segs); + Path res; + if ( n_segs > 0){ + res = Path(b[0]); + for (int i=0; i<n_segs; i++){ + res.appendNew<CubicBezier>(b[4*i+1],b[4*i+2],b[4*i+3]); + } + } + g_free(b); + g_free(points); + return res; +} + +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- + + +std::vector<Point> eat(std::vector<Point> const &pts, double sampling){ + std::vector<bool> visited(pts.size(),false); + std::vector<Point> res; + Point p = pts.front(); + //Point q = p; + res.push_back(p); + while(true){ + double num_nghbrs = 0; + Point next(0,0); + for(unsigned i = 0; i < pts.size(); i++) { + if (!visited[i] && L2(pts[i]-p)<sampling){ + //TODO: rotate pts[i] so that last step was in dir -pi... + //dir += atan2(pts[i]-p); + visited[i] = true; + next+= pts[i]-p; + num_nghbrs += 1; + } + } + if (num_nghbrs == 0) break; + //q=p; + next *= 1./num_nghbrs; + p += next; + res.push_back(p); + } + return res; +} + + + + + +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- + +double exp_rescale(double x) +{ + return pow(10, x*5-2); +} +std::string exp_formatter(double x) +{ + return default_formatter(exp_rescale(x)); +} + +class SketchFitterToy: public Toy { + + enum menu_item_t + { + SHOW_MENU = 0, + TEST_TIGHTEN, + TEST_EAT_BY_STEP, + TEST_TIGHTEN_EAT, + TEST_CURVATURE, + TEST_SORT, + TEST_NUMERICAL, + SHOW_HELP, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + }; + + enum toggle_label_t + { + DRAW_MOUSES = 0, + DRAW_IMPROVED_MOUSES, + DRAW_STROKE, + TIGHTEN_USE_CIRCLE, + SORT_BIS, + TOTAL_TOGGLES // this one must be the last item + }; + + enum slider_label_t + { + TIGHTEN_NBHD_SIZE = 0, + TIGHTEN_ITERRATIONS, + EAT_NBHD_SIZE, + SORT_RADIUS, + FUSE_RADIUS, + INTERPOLATE_RADIUS, + CURVATURE_NBHD_SIZE, + POINT_CHOOSER, + TOTAL_SLIDERS // this one must be the last item + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void fit_empty(){} + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &SketchFitterToy::draw_menu; + fit_f = &SketchFitterToy::fit_empty; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + handles.clear(); + handles.push_back(&(toggles[DRAW_MOUSES])); + handles.push_back(&(toggles[DRAW_IMPROVED_MOUSES])); + handles.push_back(&(toggles[DRAW_STROKE])); + + //sliders.clear(); + //toggles.clear(); + //handles.clear(); + } + void init_common_ctrl_geom(cairo_t* /*cr*/, int width, int /*height*/, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + Point p(10, 20), d(width/3-20,25); + toggles[DRAW_MOUSES].bounds = Rect(p, p + d); + p += Point ((width)/3, 0); + toggles[DRAW_IMPROVED_MOUSES].bounds = Rect(p, p + d); + p += Point ((width)/3, 0); + toggles[DRAW_STROKE].bounds = Rect(p, p + d); + } + } + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, std::ostringstream */*timer_stream*/) + { + init_common_ctrl_geom(cr, width, height, notify); + if(!mouses.empty() && toggles[DRAW_MOUSES].on ) { + //cairo_move_to(cr, mouses[0]); + //for(unsigned i = 0; i < mouses.size(); i++) { + // cairo_line_to(cr, mouses[i]); + //} + for(auto & mouse : mouses) { + draw_cross(cr, mouse); + } + cairo_set_source_rgba (cr, 0., 0., 0., .25); + cairo_set_line_width (cr, 0.5); + cairo_stroke(cr); + } + + if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) { + cairo_move_to(cr, improved_mouses[0]); + for(auto & improved_mouse : improved_mouses) { + draw_cross(cr, improved_mouse); + } + cairo_set_source_rgba (cr, 1., 0., 0., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + + if(!stroke.empty() && toggles[DRAW_STROKE].on) { + cairo_pw_d2_sb(cr, stroke); + cairo_set_source_rgba (cr, 0., 0., 1., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + + *notify << "Press SHIFT to continue sketching. 'Z' to apply changes"; + } + + +//----------------------------------------------------------------------------------------- +// Tighten: tries to move the points toward the common curve +//----------------------------------------------------------------------------------------- + void init_tighten() + { + init_common(); + handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE])); + handles.push_back(&(sliders[TIGHTEN_ITERRATIONS])); + handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE])); + } + void init_tighten_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[TIGHTEN_NBHD_SIZE ].geometry(Point(50, height - 35*2), 180); + sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180); + + Point p(width-250, height - 50), d(225,25); + toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p, p + d); + } + } + void fit_tighten(){ + improved_mouses = mouses; + double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value()); + for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){ + tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on); + } + } + void draw_tighten(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_tighten_ctrl_geom(cr, notify, width, height); + } + +//----------------------------------------------------------------------------------------- +// Eat by step: eats the curve moving at each step in the average direction of the neighbors. +//----------------------------------------------------------------------------------------- + void init_eat() + { + init_common(); + handles.push_back(&(sliders[EAT_NBHD_SIZE])); + } + void init_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[EAT_NBHD_SIZE].geometry(Point(50, height - 35*(0+2)), 180); + } + } + void fit_eat(){ + double radius = exp_rescale(sliders[EAT_NBHD_SIZE].value()); + improved_mouses = mouses; + + tighten(improved_mouses, 20, true); + + stroke = Piecewise<D2<SBasis> >(); + improved_mouses = eat(improved_mouses, radius); + Path p(improved_mouses[0]); + for(unsigned i = 1; i < improved_mouses.size(); i++) { + p.appendNew<LineSegment>(improved_mouses[i]); + } + stroke = p.toPwSb(); + } + void draw_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_eat_ctrl_geom(cr, notify, width, height); + } + +//----------------------------------------------------------------------------------------- +// Tighten + Eat +//----------------------------------------------------------------------------------------- + void init_tighten_eat() + { + init_common(); + handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE])); + handles.push_back(&(sliders[TIGHTEN_ITERRATIONS])); + handles.push_back(&(sliders[EAT_NBHD_SIZE])); + } + void init_tighten_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[TIGHTEN_NBHD_SIZE ].geometry(Point(50, height - 35*2), 180); + sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180); + sliders[EAT_NBHD_SIZE ].geometry(Point(50, height - 35*4), 180); + } + } + void fit_tighten_eat(){ + improved_mouses = mouses; + double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value()); + for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){ + tighten(improved_mouses, radius, toggles[0].on); + } + stroke = Piecewise<D2<SBasis> >(); + radius = exp_rescale(sliders[EAT_NBHD_SIZE].value()); + improved_mouses = eat(improved_mouses, radius); + Path p(improved_mouses[0]); + for(unsigned i = 1; i < improved_mouses.size(); i++) { + p.appendNew<LineSegment>(improved_mouses[i]); + } + stroke = p.toPwSb(); + } + void draw_tighten_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_tighten_eat_ctrl_geom(cr, notify, width, height); + } + +//----------------------------------------------------------------------------------------- +// Sort: tighten, then sort and eventually fuse. +//----------------------------------------------------------------------------------------- + void init_sort() + { + init_common(); + handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE])); + handles.push_back(&(sliders[TIGHTEN_ITERRATIONS])); + handles.push_back(&(sliders[SORT_RADIUS])); + handles.push_back(&(sliders[FUSE_RADIUS])); + handles.push_back(&(sliders[INTERPOLATE_RADIUS])); + handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE])); + handles.push_back(&(toggles[SORT_BIS])); + } + void init_sort_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[TIGHTEN_NBHD_SIZE].geometry(Point(50, height - 35*2), 180); + sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180); + sliders[SORT_RADIUS].geometry(Point(50, height - 35*4), 180); + sliders[FUSE_RADIUS].geometry(Point(50, height - 35*5), 180); + sliders[INTERPOLATE_RADIUS].geometry(Point(50, height - 35*6), 180); + + Point p(width-250, height - 50), d(225,25); + toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p, p + d); + p += Point(0,-30); + toggles[SORT_BIS].bounds = Rect(p, p + d); + } + } + void fit_sort(){ + improved_mouses = mouses; + double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value()); + for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){ + tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on); + } + double max_jump = exp_rescale(sliders[SORT_RADIUS].value()); + if (toggles[SORT_BIS].on){ + sort_nearest_bis(improved_mouses, max_jump); + }else{ + sort_nearest(improved_mouses, max_jump); + } + radius = exp_rescale(sliders[FUSE_RADIUS].value()); + fuse_close_points(improved_mouses, radius); + + radius = exp_rescale(sliders[INTERPOLATE_RADIUS].value()); + Path p = ordered_fit(improved_mouses, radius/5); + stroke = p.toPwSb(); + } + void draw_sort(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_sort_ctrl_geom(cr, notify, width, height); + + if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) { + cairo_move_to(cr, improved_mouses[0]); + for(unsigned i = 1; i < improved_mouses.size(); i++) { + cairo_line_to(cr, improved_mouses[i]); + } + cairo_set_source_rgba (cr, 1., 0., 0., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + } + +//----------------------------------------------------------------------------------------- +// Average curvature. +//----------------------------------------------------------------------------------------- + void init_curvature() + { + init_common(); + handles.push_back(&(sliders[CURVATURE_NBHD_SIZE])); + handles.push_back(&(sliders[POINT_CHOOSER])); + } + void init_curvature_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[CURVATURE_NBHD_SIZE].geometry(Point(50, height - 60), 180); + sliders[POINT_CHOOSER ].geometry(Point(50, height - 90), 180); + } + } + //just for fun! + void fit_curvature(){ + std::vector<double> curvatures(mouses.size(),0); + std::vector<double> lengths(mouses.size(),0); + for (unsigned i=0; i<mouses.size(); i++){ + double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value()); + std::vector<Point> ngbrs = neighbors(mouses,i,radius); + if ( ngbrs.size()>2 ){ + Circle c; + c.fit(ngbrs); + curvatures[i] = 1./c.radius(); + Point v = (i<mouses.size()-1) ? mouses[i+1]-mouses[i] : mouses[i]-mouses[i-1]; + if (cross(v, c.center()-mouses[i]) > 0 ) + curvatures[i] *= -1; + }else{ + curvatures[i] = 0; + } + if (i>0){ + lengths[i] = lengths[i-1] + L2(mouses[i]-mouses[i-1]); + } + } + Piecewise<SBasis> k = interpolate( lengths, curvatures , 1); + Piecewise<SBasis> alpha = integral(k); + Piecewise<D2<SBasis> > v = sectionize(tan2(alpha)); + stroke = integral(v) + mouses[0]; + + Point sp = stroke.lastValue()-stroke.firstValue(); + Point mp = mouses.back()-mouses.front(); + Affine mat1 = Affine(sp[X], sp[Y], -sp[Y], sp[X], stroke.firstValue()[X], stroke.firstValue()[Y]); + Affine mat2 = Affine(mp[X], mp[Y], -mp[Y], mp[X], mouses[0][X], mouses[0][Y]); + mat1 = mat1.inverse()*mat2; + stroke = stroke*mat1; + + } + void draw_curvature(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_curvature_ctrl_geom(cr, notify, width, height); + if(!mouses.empty()) { + double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value()); + unsigned i = unsigned( (mouses.size()-1)*sliders[POINT_CHOOSER].value()/100. ); + std::vector<Point> ngbrs = neighbors(mouses,i,radius); + if ( ngbrs.size()>2 ){ + draw_cross(cr, mouses[i]); + Circle c; + c.fit(ngbrs); + cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI); + cairo_set_source_rgba (cr, 1., 0., 0., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + cairo_pw_d2_sb(cr, stroke); + } + } + +//----------------------------------------------------------------------------------------- +// Brutal optimization, number of segment fixed. +//----------------------------------------------------------------------------------------- + void init_numerical() + { + init_common(); + //sliders.push_back(Slider(0, 10, 1, 1, "Number of curves")); + //handles.push_back(&(sliders[0])); + } + void init_numerical_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int /*height*/) + { + if ( set_control_geometry ){ + set_control_geometry = false; + //sliders[0].geometry(Point(50, height - 35*(0+2)), 180); + } + } + void fit_numerical(){ + //Not implemented + } + void draw_numerical(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_numerical_ctrl_geom(cr, notify, width, height); + if(!mouses.empty()) { + cairo_pw_d2_sb(cr, stroke); + cairo_set_source_rgba (cr, 1., 0., 0., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + } +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- + + void init_help() + { + handles.clear(); + //sliders.clear(); + //toggles.clear(); + } + void draw_help( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/) + { + *notify << "Tighten:\n"; + *notify << " move points toward local\n"; + *notify << " mean square line (or circle).\n"; + *notify << "Eat:\n"; + *notify << " eat points like a pacman; at each step, move to the\n"; + *notify << " average of the not already visited neighbor points.\n"; + *notify << "Sort:\n"; + *notify << " move from one point to the nearest one.\n"; + *notify << " Stop at the first jump longer than sort-radius\n"; + *notify << "Sort-bis:\n"; + *notify << " move from one point to the nearest one,\n"; + *notify << " unless it was 'already visited' (i.e. it is closer to\n"; + *notify << " an already sorted point with distance < sort-radius.\n"; + *notify << "Fuse: \n"; + *notify << " start from first point, remove all points closer to it\n"; + *notify << " than fuse-radius, move to the first one that is not, and repeat.\n"; + *notify << "Curvature: \n"; + *notify << " Compute the curvature at a given point from the circle fitting the\n"; + *notify << " nearby points (just for fun: the stroke is the 'integral' of this\n"; + *notify << " average curvature)\n"; + *notify << "Numerical: \n"; + *notify << " still waiting for someone to implement me ;-)\n\n"; + *notify << std::endl; + } + +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- + +public: + vector<Point> mouses; + int mouse_drag; + vector<Point> improved_mouses; + Piecewise<D2<SBasis > > stroke; + + void mouse_pressed(GdkEventButton* e) override { + //toggle_events(toggles, e); + Toy::mouse_pressed(e); + if(!selected) { + mouse_drag = 1; + if (!(e->state & (GDK_SHIFT_MASK))){ + mouses.clear(); + } + } + } + + void mouse_moved(GdkEventMotion* e) override { + if(mouse_drag) { + mouses.emplace_back(e->x, e->y); + redraw(); + } else { + Toy::mouse_moved(e); + } + } + + void mouse_released(GdkEventButton* e) override { + mouse_drag = 0; + if(!mouses.empty()) { + (this->*fit_f)(); + } + Toy::mouse_released(e); + } + + void init_menu() + { + handles.clear(); + //sliders.clear(); + //toggles.clear(); + } + void draw_menu( cairo_t * cr, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/) + { + *notify << "Sketch some shape on canvas (press SHIFT to use several 'strokes')\n"; + *notify << "Each menu below will transform your input.\n"; + *notify << "Press 'Z' to make the result the new input\n"; + *notify << " \n \n \n"; + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + if(!mouses.empty()) { + cairo_move_to(cr, mouses[0]); + for(auto & mouse : mouses) { + cairo_line_to(cr, mouse); + } + for(auto & mouse : mouses) { + draw_cross(cr, mouse); + } + cairo_set_source_rgba (cr, 0., 0., 0., .25); + cairo_set_line_width (cr, 0.5); + cairo_stroke(cr); + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &SketchFitterToy::draw_menu; + break; + case 'B': + init_tighten(); + fit_f = &SketchFitterToy::fit_tighten; + draw_f = &SketchFitterToy::draw_tighten; + break; + case 'C': + init_eat(); + fit_f = &SketchFitterToy::fit_eat; + draw_f = &SketchFitterToy::draw_eat; + break; + case 'D': + init_tighten_eat(); + fit_f = &SketchFitterToy::fit_tighten_eat; + draw_f = &SketchFitterToy::draw_tighten_eat; + break; + case 'E': + init_sort(); + fit_f = &SketchFitterToy::fit_sort; + draw_f = &SketchFitterToy::draw_sort; + break; + case 'F': + init_curvature(); + fit_f = &SketchFitterToy::fit_curvature; + draw_f = &SketchFitterToy::draw_curvature; + break; + case 'G': + init_numerical(); + fit_f = &SketchFitterToy::fit_numerical; + draw_f = &SketchFitterToy::draw_numerical; + break; + case 'H': + init_help(); + draw_f = &SketchFitterToy::draw_help; + break; + case 'Z': + mouses = improved_mouses; + break; + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream ) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + + public: + SketchFitterToy() + { + srand ( time(NULL) ); + sliders = std::vector<Slider>(TOTAL_SLIDERS, Slider(0., 1., 0, 0., "")); + + sliders[TIGHTEN_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "neighborhood size"); + sliders[TIGHTEN_NBHD_SIZE ].formatter(&exp_formatter); + sliders[TIGHTEN_ITERRATIONS] = Slider(0, 10, 1, 3, "iterrations"); + sliders[EAT_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "eating neighborhood size"); + sliders[EAT_NBHD_SIZE ].formatter(&exp_formatter); + sliders[SORT_RADIUS ] = Slider(0., 1., 0, 0.65, "sort radius"); + sliders[SORT_RADIUS ].formatter(&exp_formatter); + sliders[FUSE_RADIUS ] = Slider(0., 1., 0, 0.65, "fuse radius"); + sliders[FUSE_RADIUS ].formatter(&exp_formatter); + sliders[INTERPOLATE_RADIUS ] = Slider(0., 1., 0, 0.65, "intrepolate precision"); + sliders[INTERPOLATE_RADIUS ].formatter(&exp_formatter); + sliders[CURVATURE_NBHD_SIZE] = Slider(0., 1., 0, 0.65, "curvature nbhd size"); + sliders[CURVATURE_NBHD_SIZE].formatter(&exp_formatter); + sliders[POINT_CHOOSER ] = Slider(0, 100, 0, 50, "Point chooser(%)"); + + toggles = std::vector<Toggle>(TOTAL_TOGGLES, Toggle("",true)); + toggles[DRAW_MOUSES] = Toggle("Draw mouses",true); + toggles[DRAW_IMPROVED_MOUSES] = Toggle("Draw new mouses",true); + toggles[DRAW_STROKE] = Toggle("Draw stroke",true); + toggles[TIGHTEN_USE_CIRCLE] = Toggle("Tighten: use circle",false); + toggles[SORT_BIS ] = Toggle("Sort: bis",false); + } + + private: + typedef void (SketchFitterToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + typedef void (SketchFitterToy::* fit_func_t) (); + fit_func_t fit_f; + bool set_common_control_geometry; + bool set_control_geometry; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + double m_width, m_height, m_length; + +}; // end class SketchFitterToy + + +const char* SketchFitterToy::menu_items[] = +{ + "show this menu", + "tighten", + "eat points step by step", + "tighten + eat", + "tighten + sort + fuse", + "curvature", + "numerical", + "help", +}; + +const char SketchFitterToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' +}; + +int main(int argc, char **argv) { + init(argc, argv, new SketchFitterToy); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/smash-intersector.cpp b/src/toys/smash-intersector.cpp new file mode 100644 index 0000000..b01acfb --- /dev/null +++ b/src/toys/smash-intersector.cpp @@ -0,0 +1,583 @@ +/* + * Diffeomorphism-based intersector: given two curves + * M(t)=(x(t),y(t)) and N(u)=(X(u),Y(u)) + * and supposing M is a graph over the x-axis, we compute y(x) and solve + * Y(u) - y(X(u)) = 0 + * to get the intersections of the two curves... + * + * Notice the result can be far from intuitive because of the choice we have + * to make to consider a curve as a graph over x or y. For instance the two + * branches of xy=eps are never close from this point of view (!)... + * + * Authors: + * J.-F. Barraud <jfbarraud at gmail.com> + * Copyright 2010 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <cstdlib> +#include <cstdio> +#include <set> +#include <vector> +#include <algorithm> + +#include <2geom/orphan-code/intersection-by-smashing.h> +#include "../2geom/orphan-code/intersection-by-smashing.cpp" + +using namespace Geom; + +#define VERBOSE 0 + +static double exp_rescale(double x){ return ::pow(10, x);} +std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));} + + + +#if 0 +//useless here; +Piecewise<D2<SBasis> > linearizeCusps( D2<SBasis> f, double tol){ + D2<SBasis> df = derivative( f ); + std::vector<Interval> xdoms = level_set( df[X], 0., tol); + std::vector<Interval> ydoms = level_set( df[Y], 0., tol); + std::vector<Interval> doms; + //TODO: use order!! + for ( unsigned i=0; i<xdoms.size(); i++ ){ + OptInterval inter = xdoms[i]; + for ( unsigned j=0; j<ydoms.size(); j++ ){ + inter &= ydoms[j]; + } + if (inter) { + doms.push_back( *inter ); + } + } + Piecewise<D2<SBasis> > result; + if (doms.size() == 0 ) return Piecewise<D2<SBasis> >(f); + if (doms[0].min() > 0 ){ + result.cuts.push_back( 0 ); + result.cuts.push_back( doms[0].min() ); + result.segs.push_back( portion( f, Interval( 0, doms[0].min() ) ) ); + } + for ( unsigned i=0; i<doms.size(); i++ ){ + Point a = result.segs.back().at1(); + Point b = f.valueAt( doms[i].middle() ); + Point c = f.valueAt( doms[i].max() ); + result.cuts.push_back( doms[i].middle() ); + result.segs.push_back( D2<SBasis>( Linear( a[X], b[X] ), Linear( a[Y], b[Y] ) ) ); + result.cuts.push_back( doms[i].max() ); + result.segs.push_back( D2<SBasis>( Linear( b[X], c[X] ), Linear( b[Y], c[Y] ) ) ); + double t = ( i+1 == doms.size() )? 1 : doms[i+1].min(); + result.cuts.push_back( t ); + result.segs.push_back( portion( f, Interval( doms[i].max(), t ) ) ); + } + return result; +} +#endif + +#if 0 +/* Computes the intersection of two sets given as (ordered) union intervals. + */ +std::vector<Interval> intersect( std::vector<Interval> const &a, std::vector<Interval> const &b){ + std::vector<Interval> result; + //TODO: use order! + for (unsigned i=0; i < a.size(); i++){ + for (unsigned j=0; j < b.size(); j++){ + OptInterval c( a[i] ); + c &= b[j]; + if ( c ) { + result.push_back( *c ); + } + } + } + return result; +} + +/* Computes the top and bottom boundaries of the L_\infty neighborhood + * of a curve. The curve is supposed to be a graph over the x-axis. + */ +void computeLinfinityNeighborhood( D2<SBasis > const &f, double tol, D2<Piecewise<SBasis> > &topside, D2<Piecewise<SBasis> > &botside ){ + double signx = ( f[X].at0() > f[X].at1() )? -1 : 1; + double signy = ( f[Y].at0() > f[Y].at1() )? -1 : 1; + + Piecewise<D2<SBasis> > top, bot; + top = Piecewise<D2<SBasis> > (f); + top.cuts.insert( top.cuts.end(), 2); + top.segs.insert( top.segs.end(), D2<SBasis>(Linear( f[X].at1(), f[X].at1()+2*tol*signx), + Linear( f[Y].at1() )) ); + bot = Piecewise<D2<SBasis> >(f); + bot.cuts.insert( bot.cuts.begin(), - 1 ); + bot.segs.insert( bot.segs.begin(), D2<SBasis>(Linear( f[X].at0()-2*tol*signx, f[X].at0()), + Linear( f[Y].at0() )) ); + top += Point(-tol*signx, tol); + bot += Point( tol*signx, -tol); + + if ( signy < 0 ){ + swap( top, bot ); + top += Point( 0, 2*tol); + bot += Point( 0, -2*tol); + } + topside = make_cuts_independent(top); + botside = make_cuts_independent(bot); +} + + +/*Compute top and bottom boundaries of the L^infty nbhd of the graph of a *monotonic* function f. + * if f is increasing, it is given by [f(t-tol)-tol, f(t+tol)+tol]. + * if not, it is [f(t+tol)-tol, f(t-tol)+tol]. + */ +void computeLinfinityNeighborhood( Piecewise<SBasis> const &f, double tol, Piecewise<SBasis> &top, Piecewise<SBasis> &bot){ + top = f + tol; + top.offsetDomain( - tol ); + top.cuts.insert( top.cuts.end(), f.domain().max() + tol); + top.segs.insert( top.segs.end(), SBasis(Linear( f.lastValue() + tol )) ); + + bot = f - tol; + bot.offsetDomain( tol ); + bot.cuts.insert( bot.cuts.begin(), f.domain().min() - tol); + bot.segs.insert( bot.segs.begin(), SBasis(Linear( f.firstValue() - tol )) ); + + if ( f.firstValue() > f.lastValue() ){ + swap( top, bot ); + top += 2*tol; + bot -= 2*tol; + } +} + +std::vector<Interval> level_set( D2<SBasis> const &f, Rect region){ + std::vector<Interval> x_in_reg = level_set( f[X], region[X] ); + std::vector<Interval> y_in_reg = level_set( f[Y], region[Y] ); + std::vector<Interval> result = intersect ( x_in_reg, y_in_reg ); + return result; +} + +void prolongateByConstants( Piecewise<SBasis> &f, double paddle_width ){ + if ( f.size() == 0 ) return; //do we have a covention about the domain of empty pwsb? + f.cuts.insert( f.cuts.begin(), f.cuts.front() - paddle_width ); + f.segs.insert( f.segs.begin(), SBasis( f.segs.front().at0() ) ); + f.cuts.insert( f.cuts.end(), f.cuts.back() + paddle_width ); + f.segs.insert( f.segs.end(), SBasis( f.segs.back().at1() ) ); +} + + + +/* Returns the intervals over which the curve keeps its slope + * in one of the 8 sectors delimited by x=0, y=0, y=x, y=-x. + * WARNING: both curves are supposed to be a graphs over x or y axis, + * and the smaller the slopes the better (typically <=45°). + */ +std::vector<std::pair<Interval, Interval> > smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, + double tol, cairo_t *cr , bool draw_more_stuff=false ){ + + std::vector<std::pair<Interval, Interval> > res; + + // a and b or X and Y may have to be exchanged, so make local copies. + D2<SBasis> aa = a; + D2<SBasis> bb = b; + bool swapresult = false; + bool swapcoord = false;//debug only! + + if ( draw_more_stuff ){ + cairo_set_line_width (cr, 3); + cairo_set_source_rgba(cr, .5, .9, .7, 1 ); + cairo_d2_sb(cr, aa); + cairo_d2_sb(cr, bb); + cairo_stroke(cr); + } + +#if 1 + //if the (enlarged) bounding boxes don't intersect, stop. + if ( !draw_more_stuff ){ + OptRect abounds = bounds_fast( a ); + OptRect bbounds = bounds_fast( b ); + if ( !abounds || !bbounds ) return res; + abounds->expandBy(tol); + if ( !(abounds->intersects(*bbounds))){ + return res; + } + } +#endif + + //Choose the best curve to be re-parametrized by x or y values. + OptRect dabounds = bounds_exact(derivative(a)); + OptRect dbbounds = bounds_exact(derivative(b)); + if ( dbbounds->min().length() > dabounds->min().length() ){ + aa=b; + bb=a; + swap( dabounds, dbbounds ); + swapresult = true; + } + + //Choose the best coordinate to use as new parameter + double dxmin = std::min( abs((*dabounds)[X].max()), abs((*dabounds)[X].min()) ); + double dymin = std::min( abs((*dabounds)[Y].max()), abs((*dabounds)[Y].min()) ); + if ( (*dabounds)[X].max()*(*dabounds)[X].min() < 0 ) dxmin=0; + if ( (*dabounds)[Y].max()*(*dabounds)[Y].min() < 0 ) dymin=0; + assert (dxmin>=0 && dymin>=0); + + if (dxmin < dymin) { + aa = D2<SBasis>( aa[Y], aa[X] ); + bb = D2<SBasis>( bb[Y], bb[X] ); + swapcoord = true; + } + + //re-parametrize aa by the value of x. + Interval x_range_strict( aa[X].at0(), aa[X].at1() ); + Piecewise<SBasis> y_of_x = pw_compose_inverse(aa[Y],aa[X], 2, 1e-5); + + //Compute top and bottom boundaries of the L^infty nbhd of aa. + Piecewise<SBasis> top_ay, bot_ay; + computeLinfinityNeighborhood( y_of_x, tol, top_ay, bot_ay); + + Interval ax_range = top_ay.domain();//i.e. aa[X] domain ewpanded by tol. + + if ( draw_more_stuff ){ + Piecewise<SBasis> dbg_x( SBasis( Linear( top_ay.domain().min(), top_ay.domain().max() ) ) ); + dbg_x.setDomain( top_ay.domain() ); + D2<Piecewise<SBasis> > dbg_side ( Piecewise<SBasis>( SBasis( Linear( 0 ) ) ), + Piecewise<SBasis>( SBasis( Linear( 0, 2*tol) ) ) ); + + D2<Piecewise<SBasis> > dbg_rgn; + unsigned h = ( swapcoord ) ? Y : X; + dbg_rgn[h].concat ( dbg_x ); + dbg_rgn[h].concat ( dbg_side[X] + dbg_x.lastValue() ); + dbg_rgn[h].concat ( reverse(dbg_x) ); + dbg_rgn[h].concat ( dbg_side[X] + dbg_x.firstValue() ); + + dbg_rgn[1-h].concat ( bot_ay ); + dbg_rgn[1-h].concat ( dbg_side[Y] + bot_ay.lastValue() ); + dbg_rgn[1-h].concat ( reverse(top_ay) ); + dbg_rgn[1-h].concat ( reverse( dbg_side[Y] ) + bot_ay.firstValue() ); + + cairo_set_line_width (cr, 1.); + cairo_set_source_rgba(cr, 0., 1., 0., .75 ); + cairo_d2_pw_sb(cr, dbg_rgn ); + cairo_stroke(cr); + + D2<SBasis> bbb = bb; + if ( swapcoord ) swap( bbb[X], bbb[Y] ); + //Piecewise<D2<SBasis> > dbg_rgnB = neighborhood( bbb, tol ); + D2<Piecewise<SBasis> > dbg_topB, dbg_botB; + computeLinfinityNeighborhood( bbb, tol, dbg_topB, dbg_botB ); + cairo_set_line_width (cr, 1.); + cairo_set_source_rgba(cr, .2, 8., .2, .4 ); +// cairo_pw_d2_sb(cr, dbg_rgnB ); + cairo_d2_pw_sb(cr, dbg_topB ); + cairo_d2_pw_sb(cr, dbg_botB ); + cairo_stroke(cr); + } + + std::vector<Interval> bx_in_ax_range = level_set(bb[X], ax_range ); + + // find times when bb is in the neighborhood of aa. + std::vector<Interval> tbs; + for (unsigned i=0; i<bx_in_ax_range.size(); i++){ + D2<Piecewise<SBasis> > bb_in; + bb_in[X] = Piecewise<SBasis> ( portion( bb[X], bx_in_ax_range[i] ) ); + bb_in[Y] = Piecewise<SBasis> ( portion( bb[Y], bx_in_ax_range[i]) ); + bb_in[X].setDomain( bx_in_ax_range[i] ); + bb_in[Y].setDomain( bx_in_ax_range[i] ); + + Piecewise<SBasis> h; + Interval level; + h = bb_in[Y] - compose( top_ay, bb_in[X] ); + level = Interval( -infinity(), 0 ); + std::vector<Interval> rts_lo = level_set( h, level); + h = bb_in[Y] - compose( bot_ay, bb_in[X] ); + level = Interval( 0, infinity()); + std::vector<Interval> rts_hi = level_set( h, level); + + std::vector<Interval> rts = intersect( rts_lo, rts_hi ); + tbs.insert(tbs.end(), rts.begin(), rts.end() ); + } + + std::vector<std::pair<Interval, Interval> > result(tbs.size(),std::pair<Interval,Interval>()); + + /* for each solution I, find times when aa is in the neighborhood of bb(I). + * (Note: the preimage of bb[X](I) by aa[X], enlarged by tol, is a good approximation of this: + * it would give points in the 2*tol neighborhood of bb (if the slope of aa is never more than 1). + * + faster computation. + * - implies little jumps depending on the subdivision of the input curve into monotonic pieces + * and on the choice of preferred axis. If noticeable, these jumps would feel random to the user :-( + */ + for (unsigned j=0; j<tbs.size(); j++){ + result[j].second = tbs[j]; + std::vector<Interval> tas; + Piecewise<SBasis> fat_y_of_x = y_of_x; + prolongateByConstants( fat_y_of_x, 100*(1+tol) ); + + D2<Piecewise<SBasis> > top_b, bot_b; + D2<SBasis> bbj = portion( bb, tbs[j] ); + computeLinfinityNeighborhood( bbj, tol, top_b, bot_b ); + + Piecewise<SBasis> h; + Interval level; + h = top_b[Y] - compose( fat_y_of_x, top_b[X] ); + level = Interval( +infinity(), 0 ); + std::vector<Interval> rts_top = level_set( h, level); + for (unsigned idx=0; idx < rts_top.size(); idx++){ + rts_top[idx] = Interval( top_b[X].valueAt( rts_top[idx].min() ), + top_b[X].valueAt( rts_top[idx].max() ) ); + } + assert( rts_top.size() == 1 ); + + h = bot_b[Y] - compose( fat_y_of_x, bot_b[X] ); + level = Interval( 0, -infinity()); + std::vector<Interval> rts_bot = level_set( h, level); + for (unsigned idx=0; idx < rts_bot.size(); idx++){ + rts_bot[idx] = Interval( bot_b[X].valueAt( rts_bot[idx].min() ), + bot_b[X].valueAt( rts_bot[idx].max() ) ); + } + assert( rts_bot.size() == 1 ); + +#if VERBOSE + printf("range(aa[X]) = [%f, %f];\n", y_of_x.domain().min(), y_of_x.domain().max()); + printf("range(bbj[X]) = [%f, %f]; tol= %f\n", bbj[X].at0(), bbj[X].at1(), tol); + + printf("rts_top = "); + for (unsigned dbgi=0; dbgi<rts_top.size(); dbgi++){ + printf("[%f,%f]U", rts_top[dbgi].min(), rts_top[dbgi].max() ); + } + printf("\n"); + printf("rts_bot = "); + for (unsigned dbgi=0; dbgi<rts_bot.size(); dbgi++){ + printf("[%f,%f]U", rts_bot[dbgi].min(), rts_bot[dbgi].max() ); + } + printf("\n"); +#endif + rts_top = intersect( rts_top, rts_bot ); +#if VERBOSE + printf("intersection = "); + for (unsigned dbgi=0; dbgi<rts_top.size(); dbgi++){ + printf("[%f,%f]U", rts_top[dbgi].min(), rts_top[dbgi].max() ); + } + printf("\n\n"); + + if (rts_top.size() != 1){ + printf("!!!!!!!!!!!!!!!!!!!!!!\n!!!!!!!!!!!!!!!!!!!!!!\n"); + rts_top[0].unionWith( rts_top[1] ); + assert( false ); + } +#endif + assert (rts_top.size() == 1); + Interval x_dom = rts_top[0]; + + if ( x_dom.max() <= x_range_strict.min() ){ + tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 0 : 1 ) ); + }else if ( x_dom.min() >= x_range_strict.max() ){ + tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 1 : 0 ) ); + }else{ + tas = level_set(aa[X], x_dom ); + } + +#if VERBOSE + if ( tas.size() != 1 ){ + printf("Error: preimage of [%f, %f] by x:[0,1]->[%f, %f] is ", + x_dom.min(), x_dom.max(), x_range_strict.min(), x_range_strict.max()); + if ( tas.size() == 0 ){ + printf( "empty.\n"); + }else{ + printf("\n [%f,%f]", tas[0].min(), tas[0].max() ); + for (unsigned toto=1; toto<tas.size(); toto++){ + printf(" U [%f,%f]", tas[toto].min(), tas[toto].max() ); + } + } + } +#endif + assert( tas.size()==1 ); + result[j].first = tas.front(); + } + + if (swapresult) { + for ( unsigned i=0; i<result.size(); i++){ + Interval temp = result[i].first; + result[i].first = result[i].second; + result[i].second = temp; + } + } + return result; +} + +#endif + +class Intersector : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + double tol = exp_rescale(slider.value()); + D2<SBasis> A = handles_to_sbasis(psh.pts.begin(), A_bez_ord-1); + D2<SBasis> B = handles_to_sbasis(psh.pts.begin()+A_bez_ord, B_bez_ord-1); + cairo_set_line_width (cr, .8); + cairo_set_source_rgba(cr,0.,0.,0.,.6); + cairo_d2_sb(cr, A); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + Rect tolbytol( anchor.pos, anchor.pos ); + tolbytol.expandBy( tol ); + cairo_rectangle(cr, tolbytol); + cairo_stroke(cr); +/* + Piecewise<D2<SBasis> > smthA = linearizeCusps(A+Point(0,10), tol); + cairo_set_line_width (cr, 1.); + cairo_set_source_rgba(cr, 1., 0., 1., 1. ); + cairo_pw_d2_sb(cr, smthA); + cairo_stroke(cr); +*/ + + std::vector<Interval> Acuts = monotonicSplit(A); + std::vector<Interval> Bcuts = monotonicSplit(B); + +#if 0 + for (unsigned i=0; i<Acuts.size(); i++){ + D2<SBasis> Ai = portion( A, Acuts[i]); + cairo_set_line_width (cr, .2); + cairo_set_source_rgba(cr, 0., 0., 0., 1. ); + draw_cross(cr, Ai.at0()); + cairo_stroke(cr); + for (unsigned j=0; j<Bcuts.size(); j++){ + std::vector<std::pair<Interval, Interval> > my_intersections; + D2<SBasis> Bj = portion( B, Bcuts[j]); + cairo_set_line_width (cr, .2); + cairo_set_source_rgba(cr, 0., 0., 0., 1. ); + draw_cross(cr, Bj.at0()); + cairo_stroke(cr); + } + } +#endif + + std::vector<SmashIntersection> my_intersections; + my_intersections = smash_intersect( A, B, tol ); + + for (auto & my_intersection : my_intersections){ + cairo_set_line_width (cr, 2.5); + cairo_set_source_rgba(cr, 1., 0., 0., .8 ); + cairo_d2_sb(cr, portion( A, my_intersection.times[X])); + cairo_stroke(cr); + cairo_set_line_width (cr, 2.5); + cairo_set_source_rgba(cr, 0., 0., 1., .8 ); + cairo_d2_sb(cr, portion( B, my_intersection.times[Y])); + cairo_stroke(cr); + } +#if 0 + + unsigned apiece( slidera.value()/100. * Acuts.size() ); + unsigned bpiece( sliderb.value()/100. * Bcuts.size() ); + + + for (unsigned i=0; i<Acuts.size(); i++){ + D2<SBasis> Ai = portion( A, Acuts[i]); + for (unsigned j=0; j<Bcuts.size(); j++){ + if ( toggle.on && (i != apiece || j != bpiece) ) continue; + + std::vector<SmashIntersection> my_intersections; + D2<SBasis> Bj = portion( B, Bcuts[j]); + bool draw_more = toggle.on && i == apiece && j == bpiece; +// my_intersections = smash_intersect( Ai, Bj, tol, cr, draw_more ); + my_intersections = monotonic_smash_intersect( Ai, Bj, tol ); + + for (unsigned k=0; k<my_intersections.size(); k++){ + cairo_set_line_width (cr, 2.5); + cairo_set_source_rgba(cr, 1., 0., 0., .8 ); + cairo_d2_sb(cr, portion( Ai, my_intersections[k].times[X])); + cairo_stroke(cr); + cairo_set_line_width (cr, 2.5); + cairo_set_source_rgba(cr, 0., 0., 1., .8 ); + cairo_d2_sb(cr, portion( Bj, my_intersections[k].times[Y])); + cairo_stroke(cr); + } + } + } +#endif + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + Intersector(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + unsigned int total_handles = A_bez_ord + B_bez_ord; + for ( unsigned int i = 0; i < total_handles; ++i ) + psh.push_back(Geom::Point(uniform()*400, uniform()*400)); + handles.push_back(&psh); + slider = Slider(-4, 2, 0, 1.2, "tolerance"); + slider.geometry(Point(30, 20), 250); + slider.formatter(&exp_formatter); + handles.push_back(&slider); + slidera = Slider(0, 100, 1, 0., "piece on A"); + slidera.geometry(Point(300, 50), 250); + handles.push_back(&slidera); + sliderb = Slider(0, 100, 1, 0., "piece on B"); + sliderb.geometry(Point(300, 80), 250); + handles.push_back(&sliderb); + toggle = Toggle( Rect(Point(300,10), Point(440,30)), "Piece by piece", false ); + handles.push_back(&toggle); + anchor = PointHandle ( Point(100, 100 ) ); + handles.push_back(&anchor); + } + + private: + unsigned int A_bez_ord; + unsigned int B_bez_ord; + PointSetHandle psh; + PointHandle anchor; + Slider slider,slidera,sliderb; + Toggle toggle; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=4; + unsigned int B_bez_ord=4; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + init( argc, argv, new Intersector(A_bez_ord, B_bez_ord)); + return 0; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/squiggles.cpp b/src/toys/squiggles.cpp new file mode 100644 index 0000000..eef33ad --- /dev/null +++ b/src/toys/squiggles.cpp @@ -0,0 +1,216 @@ +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> + +#define NB_CTL_PTS 6 +#define K_SCALE .002 + +using namespace Geom; +using namespace std; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = Linear(150) + p[i]; + cairo_d2_sb(cr, B); + } +} + +void cairo_horiz(cairo_t *cr, double y, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, i, y); + cairo_rel_line_to(cr, 0, 10); + } +} + +void cairo_vert(cairo_t *cr, double x, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, x, i); + cairo_rel_line_to(cr, 10, 0); + } +} + +/* +Piecewise<SBasis> interpolate(std::vector<double> values, std::vector<double> times){ + assert ( values.size() == times.size() ); + if ( values.size() == 0 ) return Piecewise<SBasis>(); + if ( values.size() == 1 ) return Piecewise<SBasis>(values[0]);//what about time?? + + SBasis bump_in = Linear(0,1);//Enough for piecewise linear interpolation. + //bump_in.push_back(Linear(-1,1));//uncomment for C^1 interpolation + SBasis bump_out = Linear(1,0); + //bump_out.push_back(Linear(1,-1)); + + Piecewise<SBasis> result; + result.cuts.push_back(times[0]); + for (unsigned i = 0; i<values.size()-1; i++){ + result.push(bump_out*values[i]+bump_in*values[i+1],times[i+1]); + } + return result; +} +*/ + +//#include <toys/pwsbhandle.cpp // FIXME: This looks like it may give problems later, (including a .cpp file) + +class Squiggles: public Toy { + PointSetHandle hand; + unsigned current_ctl_pt; + Point current_pos; + Point current_dir; + std::vector<double> curvatures; + std::vector<double> times; + Piecewise<D2<SBasis> > curve; + double tot_length; + int mode; //0=set curvature, 1=set curv.+rotation, 2=translate, 3=slide time. + + void mouse_moved(GdkEventMotion* e) override{ + mode = 0; + if((e->state & (GDK_SHIFT_MASK)) && + (e->state & (GDK_CONTROL_MASK))) { + mode = 3; + }else if(e->state & (GDK_CONTROL_MASK)) { + mode = 1; + }else if(e->state & (GDK_SHIFT_MASK)) { + mode = 2; + } + Toy::mouse_moved(e); + } + + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 1); + + *notify << "Drag to set curvature,\n"; + *notify << "SHIFT-Drag to move curve,\n"; + *notify << "CTRL-Drag to rotate,\n"; + *notify << "SHIFT-CTRL-Drag to slide handles."; + //Get user input + if (mouse_down && selected) { + for(unsigned i = 0; i < handles.size(); i++) { + if(selected == handles[i]){ + current_ctl_pt = i; + break; + } + } + double time = times[current_ctl_pt]; + current_pos = curve.valueAt(time); + current_dir = derivative(curve).valueAt(time);//*This should be a unit vector!* + Point hdle = dynamic_cast<PointHandle*>(handles[current_ctl_pt])->pos; + if (mode == 0){ + curvatures[current_ctl_pt] = cross(hdle - current_pos,current_dir)*K_SCALE; + }else if (mode == 1){//Rotate + double sign = ( curvatures[current_ctl_pt]>=0 ? 1 : -1 ); + //curvatures[current_ctl_pt] = sign*L2(hdle - current_pos)*K_SCALE; + current_dir = -sign*unit_vector(rot90(hdle - current_pos)); + }else if (mode == 2){//Translate + Point old_pos = current_pos + curvatures[current_ctl_pt]/K_SCALE*rot90(current_dir); + current_pos += hdle - old_pos; + curve += hdle - old_pos; + }else if (mode == 3){//Slide time + Point old_pos = current_pos + curvatures[current_ctl_pt]/K_SCALE*rot90(current_dir); + double delta = dot(hdle - old_pos,current_dir); + double epsilon = 2; + if (current_ctl_pt>0 && times[current_ctl_pt]+delta < times[current_ctl_pt-1]+epsilon){ + delta = times[current_ctl_pt-1] + epsilon - times[current_ctl_pt]; + } + if (current_ctl_pt<times.size()-1 && times[current_ctl_pt]+delta > times[current_ctl_pt+1]-epsilon){ + delta = times[current_ctl_pt+1] - epsilon - times[current_ctl_pt]; + } + times[current_ctl_pt] += delta; + current_pos += delta*current_dir; + } + } + + //Compute new curve + + Piecewise<SBasis> curvature = interpolate( times, curvatures , 1); + Piecewise<SBasis> alpha = integral(curvature); + Piecewise<D2<SBasis> > v = sectionize(tan2(alpha)); + curve = integral(v)+Point(100,100); + + //transform to keep current point in place + double time = times[current_ctl_pt]; + Point new_pos = curve.valueAt(time); + Point new_dir = v.valueAt(time); + Affine mat1 = Affine( new_dir[X], new_dir[Y], -new_dir[Y], new_dir[X], new_pos[X], new_pos[Y]); + Affine mat2 = Affine(current_dir[X],current_dir[Y],-current_dir[Y],current_dir[X],current_pos[X],current_pos[Y]); + mat1 = mat1.inverse()*mat2; + curve = curve*mat1; + v = v*mat1.withoutTranslation(); + + //update handles + cairo_save(cr); + double dashes[2] = {3, 2}; + cairo_set_dash(cr, dashes, 2, 0); + cairo_set_line_width(cr, .5); + cairo_set_source_rgba (cr, 0., 0., 0.5, 1); + for(unsigned i = 0; i < NB_CTL_PTS; i++) { + Point m = curve.valueAt(times[i]); + dynamic_cast<PointHandle*>(handles[i])->pos = m + + curvatures[i]/K_SCALE*rot90(v.valueAt(times[i])); + draw_handle(cr, m); + cairo_move_to(cr, m); + cairo_line_to(cr, dynamic_cast<PointHandle*>(handles[i])->pos); + } + +#if 0 + D2<Piecewise<SBasis> > graphe; + graphe[X] = Piecewise<SBasis>(Linear(100,300)); + graphe[Y] = -curvature/K_SCALE+400; + graphe[X].setDomain(graphe[Y].domain()); + cairo_d2_pw_sb(cr, graphe); +#endif + + cairo_stroke(cr); + cairo_restore(cr); + + cairo_pw_d2_sb(cr, curve); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + bool should_draw_numbers() override { return false; } + +public: + Squiggles () { + current_ctl_pt = 0; + current_dir = Point(1,0); + current_pos = Point(100,100); + tot_length = 300; + + curve = Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(100.,300.),SBasis(100.,100.))); + for(unsigned i = 0; i < NB_CTL_PTS; i++) { + curvatures.push_back(0); + times.push_back(i*tot_length/(NB_CTL_PTS-1)); + PointHandle *pt_hdle = new PointHandle(Geom::Point(100+i*tot_length/(NB_CTL_PTS-1), 100.)); + handles.push_back(pt_hdle); + } + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Squiggles()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/star-gap.hand b/src/toys/star-gap.hand new file mode 100644 index 0000000..6506970 --- /dev/null +++ b/src/toys/star-gap.hand @@ -0,0 +1 @@ +300.000000 300.000000 diff --git a/src/toys/svgd/2rect.svgd b/src/toys/svgd/2rect.svgd new file mode 100644 index 0000000..2d8cdc2 --- /dev/null +++ b/src/toys/svgd/2rect.svgd @@ -0,0 +1 @@ +M 111.42857,92.362183 L 494.28572,92.362183 L 494.28572,275.21933 L 111.42857,275.21933 L 111.42857,92.362183 zM 217.14285,192.36218 L 660,192.36218 L 660,389.50504 L 217.14285,389.50504 L 217.14285,192.36218 z
\ No newline at end of file diff --git a/src/toys/svgd/4rect.svgd b/src/toys/svgd/4rect.svgd new file mode 100644 index 0000000..af15296 --- /dev/null +++ b/src/toys/svgd/4rect.svgd @@ -0,0 +1 @@ +M 92.73692,69.488149 L 412.73692,69.488149 L 412.73692,266.63103 L 92.73692,266.63103 L 92.73692,69.488149 z M 430.12021,178.5218 L 564.40593,178.5218 L 564.40593,275.66466 L 430.12021,275.66466 L 430.12021,178.5218 z M 430.12021,286.66478 L 564.40593,286.66478 L 564.40593,383.80764 L 430.12021,383.80764 L 430.12021,286.66478 z M 430.12021,69.488149 L 564.40593,69.488149 L 564.40593,166.63101 L 430.12021,166.63101 L 430.12021,69.488149 z
\ No newline at end of file diff --git a/src/toys/svgd/ant.svgd b/src/toys/svgd/ant.svgd new file mode 100644 index 0000000..a92d01a --- /dev/null +++ b/src/toys/svgd/ant.svgd @@ -0,0 +1 @@ +M 60.03,2.56 C 58.71,2.43 57.16,15.65 57.09,16.44 C 56.11,27.39 60.84,32.53 60.56,34.03 C 60.29,35.53 60.27,41.43 64.63,47.53 C 63.29,48.91 62.08,50.27 61.0,51.63 C 58.67,47.53 55.81,43.15 53.78,41.13 C 51.65,38.99 42.4,33.78 40.5,33.09 C 39.83,32.85 31.78,24.38 30.88,25.28 C 29.97,26.19 37.86,34.75 38.66,34.63 C 41.17,38.09 51.17,45.0 51.91,46.06 C 52.64,47.13 57.55,56.07 57.59,56.13 C 55.72,59.04 55.17,61.1 54.47,63.59 C 51.13,60.05 41.55,57.28 34.66,57.28 C 28.94,57.28 19.55,57.14 17.81,57.44 C 11.32,58.55 0.64,71.64 2.13,73.13 C 3.3,74.3 11.14,60.86 17.63,59.75 C 19.5,59.43 34.08,62.18 36.25,62.03 C 38.42,61.88 45.45,65.59 49.38,66.16 C 45.65,65.36 41.81,66.1 38.66,69.25 C 26.96,80.95 28.35,91.77 32.88,96.41 C 32.88,96.41 32.87,96.43 32.88,96.44 C 32.89,96.45 32.9,96.46 32.91,96.47 C 32.92,96.48 32.93,96.49 32.94,96.5 C 32.95,96.51 32.96,96.52 32.97,96.53 C 37.61,101.05 48.41,102.4 60.09,90.72 C 63.03,87.79 63.88,84.23 63.34,80.75 C 64.22,84.76 67.48,91.09 67.34,93.13 C 67.2,95.3 69.91,109.84 69.59,111.72 C 68.49,118.21 55.04,126.07 56.22,127.25 C 57.7,128.73 70.8,118.05 71.91,111.56 C 72.2,109.82 72.06,100.44 72.06,94.72 C 72.06,87.82 69.3,78.25 65.75,74.91 C 68.24,74.21 70.33,73.62 73.25,71.75 C 73.3,71.79 82.21,76.73 83.28,77.47 C 84.35,78.21 91.25,88.21 94.72,90.72 C 94.59,91.52 103.16,99.41 104.06,98.5 C 104.97,97.6 96.49,89.51 96.25,88.84 C 95.57,86.94 90.39,77.73 88.25,75.59 C 86.22,73.57 81.81,70.68 77.72,68.34 C 79.07,67.27 80.46,66.09 81.84,64.75 C 87.95,69.1 93.85,69.09 95.34,68.81 C 96.84,68.54 101.98,73.27 112.94,72.28 C 113.72,72.21 126.95,70.66 126.81,69.34 C 126.65,67.79 114.41,69.33 113.59,69.72 C 111.42,68.99 99.94,66.69 98.78,66.19 C 97.62,65.68 91.92,62.98 84.78,61.19 C 86.71,57.89 86.76,54.39 85.59,51.34 C 88.51,54.25 91.2,55.8 94.41,52.59 C 97.92,49.08 100.48,45.17 101.94,41.44 C 106.66,42.93 110.64,43.45 111.56,43.88 C 112.49,44.3 113.68,43.63 114.75,40.0 C 116.5,34.03 117.56,28.4 116.88,28.09 C 116.19,27.79 110.98,37.62 111.34,41.22 C 108.58,39.29 106.28,38.51 102.97,37.91 C 103.76,34.04 103.18,30.66 101.03,28.44 C 101.03,28.43 101.04,28.41 101.03,28.41 C 101.01,28.39 100.99,28.36 100.97,28.34 C 98.75,26.18 95.31,25.58 91.44,26.38 C 90.83,23.07 90.08,20.76 88.16,18.0 C 91.75,18.37 101.59,13.18 101.28,12.5 C 100.97,11.82 95.35,12.84 89.38,14.59 C 85.74,15.66 85.07,16.89 85.5,17.81 C 85.93,18.74 86.45,22.72 87.94,27.44 C 84.21,28.89 80.29,31.46 76.78,34.97 C 73.58,38.17 75.12,40.87 78.03,43.78 C 74.98,42.62 71.48,42.67 68.19,44.59 C 66.4,37.45 63.66,31.75 63.16,30.59 C 62.65,29.43 60.39,17.92 59.66,15.75 C 60.05,14.94 61.59,2.72 60.03,2.56 z
\ No newline at end of file diff --git a/src/toys/svgd/arcs.svgd b/src/toys/svgd/arcs.svgd new file mode 100644 index 0000000..8a69290 --- /dev/null +++ b/src/toys/svgd/arcs.svgd @@ -0,0 +1 @@ +M50,50 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25 diff --git a/src/toys/svgd/banana.svgd b/src/toys/svgd/banana.svgd new file mode 100644 index 0000000..37a385e --- /dev/null +++ b/src/toys/svgd/banana.svgd @@ -0,0 +1 @@ +M 265.71429,158.07648 C 190.6809,177.20365 122.07669,234.71591 104.63056,312.25563 C 77.156999,399.83651 70.794763,500.27985 114.77866,583.69036 C 144.53063,634.44937 199.36676,669.20393 257.74302,675.53709 C 270.81001,702.04856 314.06907,671.3079 286.33697,654.18295 C 255.1688,600.99542 216.46623,548.3017 211.13947,484.83476 C 202.31209,421.03768 205.02251,351.85571 239.06833,295.30186 C 257.00587,263.44474 279.73092,234.67917 301.98406,205.79097 C 300.02661,180.33683 358.06043,134.08979 326.58199,129.37189 C 312.92978,127.95692 277.30454,151.73686 265.71429,158.07648 z diff --git a/src/toys/svgd/cat.svgd b/src/toys/svgd/cat.svgd new file mode 100644 index 0000000..557f13d --- /dev/null +++ b/src/toys/svgd/cat.svgd @@ -0,0 +1 @@ +M 153.04,308.88 C 67.07,342.82 57.97,453.92 130.83,506.41 C 245.3,553.77 426.19,542.77 445.18,392.24 C 446.78,351.09 426.76,300.40 382.24,291.24 M 338.24,282.24 C 285.72,273.19442 231.95477,285.06235 181.03,297.88016 M 308.58,391.49 C 327.09,380.94 348.61,376.39128 369.74,375.64253 M 118.31907,386.96814 C 134.17,392.253 150.03,397.53 165.88,402.82 M 206.65,418.65109 C 211.53912,410.76 255.11,411.00542 258.75668,418.65109 C 262.66,426.86 238.25,466.21 229.31,466.21866 C 220.88,466.21866 201.72358,426.62348 206.65,418.65109 z M 270.08,465.68 C 345.58,465.84 421.09,466.04 496.59,466.21 M 276.87,445.83 C 346.34,439.03 415.80,432.24 485.26,425.44 M 202.12,459.95 C 159.09,469.59 116.05,479.23 73.01,488.86 M 196.66,443.29 C 148.65,444.89 100.64,446.49 52.63,448.09769 M 205.04,480.06 C 221.65,480.065 238.26,480.06 254.87,480.06 M 95.66,350.69 C 91.13,315.21 86.60,279.72 82.07,244.23 C 133.11,252.96 156.26,279.88 190.80,312.19 M 338.03,289.53 C 360.08,241.80 427.37,231.27 452.87,221.21 C 450.44,257.57 440.48,289.67 428.64,323.51 M 196.76,290.19 C 201.26,252.13 242.44,234.63 272.70,219.28 C 417.48,158.60 583.94,152.87 733.23,201.03 C 853.85,234.84 966.06,304.90 931.20,445.90 C 850.68,533.28 734.22,618.52 613.28,632.83 C 572.66,633.17 536.29,602.83 578.98,592.96 C 664.47,600.17 743.16,553.80 807.85,502.83 C 831.31,480.70 855.70,455.39 863.54,423.18 M 742.13,302.52 C 696.70,300.78 645.69,308.71 616,347.22 C 578.46,386.94 583.35,447.38 587.45,497.88 C 584.24,518.69 614.07,549.65 579.61,545.01 C 533.59,551.10 487.13,554.76 441.40,562.48 C 397.18,588.94 462.84,613.68 489.95,609.31 C 514.10,610.80 538.52,610.13 562.38,605.92 M 211.18,531.90 C 167.07,548.50 234.68,593.43 260.49,576.39 C 326.21,562.34 388.19,535.46 447.53,504.56 C 489.62,482.86 548.71,461.63 557.51,412.45 C 563.23,380.53 543.26,346.99 536.94,314.20 C 532.43,276.36 513.09,264.81 475.14,265.25 M 450.71,504.40 C 494.88,523 543.39,525.09 590.45,519.27 M 100.78,485.00 C 87.77,505.40 71.18,555.90 114.73,539.59 C 131.70,538.38 137.48,525.86 139.17,510.47 diff --git a/src/toys/svgd/circle.svgd b/src/toys/svgd/circle.svgd new file mode 100644 index 0000000..fb276fa --- /dev/null +++ b/src/toys/svgd/circle.svgd @@ -0,0 +1 @@ +M 0 0 A 100 100 0 0 0 200 0 A 100 100 0 0 0 0 0 z
\ No newline at end of file diff --git a/src/toys/svgd/degenerate-line.svgd b/src/toys/svgd/degenerate-line.svgd new file mode 100644 index 0000000..832bb17 --- /dev/null +++ b/src/toys/svgd/degenerate-line.svgd @@ -0,0 +1 @@ +m 23,42 0,0 diff --git a/src/toys/svgd/diederik.svgd b/src/toys/svgd/diederik.svgd new file mode 100644 index 0000000..7e422ab --- /dev/null +++ b/src/toys/svgd/diederik.svgd @@ -0,0 +1 @@ +m 262.6037,35.824151 c 0,0 -92.64892,-187.405851 30,-149.999981 104.06976,31.739531 170,109.9999815 170,109.9999815 l -10,-59.9999905 c 0,0 40,79.99999 -40,79.99999 -80,0 -70,-129.999981 -70,-129.999981 l 50,0 C 435.13571,-131.5667 652.76275,126.44872 505.74322,108.05672 358.73876,89.666591 292.6037,-14.175849 292.6037,15.824151 c 0,30 -30,20 -30,20 z
\ No newline at end of file diff --git a/src/toys/svgd/diederik1.svgd b/src/toys/svgd/diederik1.svgd new file mode 100644 index 0000000..be181f5 --- /dev/null +++ b/src/toys/svgd/diederik1.svgd @@ -0,0 +1 @@ +m 262.6037,35.824151 c 0,0 -92.64892,-187.405851 30,-149.999981
\ No newline at end of file diff --git a/src/toys/svgd/double-move.svgd b/src/toys/svgd/double-move.svgd new file mode 100644 index 0000000..d2be8c3 --- /dev/null +++ b/src/toys/svgd/double-move.svgd @@ -0,0 +1 @@ +m 23,42 m 10,0 diff --git a/src/toys/svgd/ellipses.svgd b/src/toys/svgd/ellipses.svgd new file mode 100644 index 0000000..c0f3a2c --- /dev/null +++ b/src/toys/svgd/ellipses.svgd @@ -0,0 +1 @@ +M 310 640 A 290 320 0 0 1 310 0 A 290 320 0 0 1 310 640 z M 310 635 A 285 315 0 0 0 310 5 A 285 315 0 0 0 310 635 z M 310 630 A 280 310 0 0 1 310 10 A 280 310 0 0 1 310 630 z M 310 625 A 275 305 0 0 0 310 15 A 275 305 0 0 0 310 625 z M 310 620 A 270 300 0 0 1 310 20 A 270 300 0 0 1 310 620 z M 310 615 A 265 295 0 0 0 310 25 A 265 295 0 0 0 310 615 z M 310 610 A 260 290 0 0 1 310 30 A 260 290 0 0 1 310 610 z M 310 605 A 255 285 0 0 0 310 35 A 255 285 0 0 0 310 605 z M 310 600 A 250 280 0 0 1 310 40 A 250 280 0 0 1 310 600 z M 310 595 A 245 275 0 0 0 310 45 A 245 275 0 0 0 310 595 z M 310 590 A 240 270 0 0 1 310 50 A 240 270 0 0 1 310 590 z M 310 585 A 235 265 0 0 0 310 55 A 235 265 0 0 0 310 585 z M 310 580 A 230 260 0 0 1 310 60 A 230 260 0 0 1 310 580 z M 310 575 A 225 255 0 0 0 310 65 A 225 255 0 0 0 310 575 z M 310 570 A 220 250 0 0 1 310 70 A 220 250 0 0 1 310 570 z M 310 565 A 215 245 0 0 0 310 75 A 215 245 0 0 0 310 565 z M 310 560 A 210 240 0 0 1 310 80 A 210 240 0 0 1 310 560 z M 310 555 A 205 235 0 0 0 310 85 A 205 235 0 0 0 310 555 z M 310 550 A 200 230 0 0 1 310 90 A 200 230 0 0 1 310 550 z M 310 545 A 195 225 0 0 0 310 95 A 195 225 0 0 0 310 545 z M 310 540 A 190 220 0 0 1 310 100 A 190 220 0 0 1 310 540 z M 310 535 A 185 215 0 0 0 310 105 A 185 215 0 0 0 310 535 z M 310 530 A 180 210 0 0 1 310 110 A 180 210 0 0 1 310 530 z M 310 525 A 175 205 0 0 0 310 115 A 175 205 0 0 0 310 525 z M 310 520 A 170 200 0 0 1 310 120 A 170 200 0 0 1 310 520 z M 310 515 A 165 195 0 0 0 310 125 A 165 195 0 0 0 310 515 z M 310 510 A 160 190 0 0 1 310 130 A 160 190 0 0 1 310 510 z M 310 505 A 155 185 0 0 0 310 135 A 155 185 0 0 0 310 505 z M 310 500 A 150 180 0 0 1 310 140 A 150 180 0 0 1 310 500 z M 310 495 A 145 175 0 0 0 310 145 A 145 175 0 0 0 310 495 z M 310 490 A 140 170 0 0 1 310 150 A 140 170 0 0 1 310 490 z M 310 485 A 135 165 0 0 0 310 155 A 135 165 0 0 0 310 485 z M 310 480 A 130 160 0 0 1 310 160 A 130 160 0 0 1 310 480 z M 310 475 A 125 155 0 0 0 310 165 A 125 155 0 0 0 310 475 z M 310 470 A 120 150 0 0 1 310 170 A 120 150 0 0 1 310 470 z M 310 465 A 115 145 0 0 0 310 175 A 115 145 0 0 0 310 465 z M 310 460 A 110 140 0 0 1 310 180 A 110 140 0 0 1 310 460 z M 310 455 A 105 135 0 0 0 310 185 A 105 135 0 0 0 310 455 z M 310 450 A 100 130 0 0 1 310 190 A 100 130 0 0 1 310 450 z M 310 445 A 95 125 0 0 0 310 195 A 95 125 0 0 0 310 445 z M 310 440 A 90 120 0 0 1 310 200 A 90 120 0 0 1 310 440 z M 310 435 A 85 115 0 0 0 310 205 A 85 115 0 0 0 310 435 z M 310 430 A 80 110 0 0 1 310 210 A 80 110 0 0 1 310 430 z M 310 425 A 75 105 0 0 0 310 215 A 75 105 0 0 0 310 425 z M 310 420 A 70 100 0 0 1 310 220 A 70 100 0 0 1 310 420 z M 310 415 A 65 95 0 0 0 310 225 A 65 95 0 0 0 310 415 z M 310 410 A 60 90 0 0 1 310 230 A 60 90 0 0 1 310 410 z M 310 405 A 55 85 0 0 0 310 235 A 55 85 0 0 0 310 405 z M 310 400 A 50 80 0 0 1 310 240 A 50 80 0 0 1 310 400 z M 310 395 A 45 75 0 0 0 310 245 A 45 75 0 0 0 310 395 z M 310 390 A 40 70 0 0 1 310 250 A 40 70 0 0 1 310 390 z M 310 385 A 35 65 0 0 0 310 255 A 35 65 0 0 0 310 385 z M 310 380 A 30 60 0 0 1 310 260 A 30 60 0 0 1 310 380 z M 310 375 A 25 55 0 0 0 310 265 A 25 55 0 0 0 310 375 z M 310 370 A 20 50 0 0 1 310 270 A 20 50 0 0 1 310 370 z M 310 365 A 15 45 0 0 0 310 275 A 15 45 0 0 0 310 365 z M 310 360 A 10 40 0 0 1 310 280 A 10 40 0 0 1 310 360 z M 310 355 A 5 35 0 0 0 310 285 A 5 35 0 0 0 310 355 z
\ No newline at end of file diff --git a/src/toys/svgd/emptyset.svgd b/src/toys/svgd/emptyset.svgd new file mode 100644 index 0000000..7e91eeb --- /dev/null +++ b/src/toys/svgd/emptyset.svgd @@ -0,0 +1 @@ +M 551.42857,252.36218 52.857143,750.93361 M 440,495.21933 c 0,77.31986 -62.68014,140 -140,140 -77.31986,0 -140,-62.68014 -140,-140 0,-77.31987 62.68014,-140 140,-140 77.31986,0 140,62.68013 140,140 z
\ No newline at end of file diff --git a/src/toys/svgd/fan.svgd b/src/toys/svgd/fan.svgd new file mode 100644 index 0000000..940fb71 --- /dev/null +++ b/src/toys/svgd/fan.svgd @@ -0,0 +1 @@ + M 470 350 L 750 350 L 749.5904551097586 364.7203022982254 L 469.92772737231036 352.9440604596451 L 469.7110836003318 355.88102841977366 L 748.362807068547 379.4051420988682 L 746.3200133880255 394.01914233660864 L 469.3505905978869 358.80382846732175 L 468.84711682419385 361.70541932096774 L 743.4669953370983 408.5270966048386 L 739.810626086145 422.8940539709793 L 468.2018751916726 364.5788107941959 L 467.41642014393256 367.4170806352678 L 735.3597141489511 437.0854031763389 L 730.1249821622271 451.06695601766626 L 466.49264391098126 370.21339120353326 L 465.4327719506772 372.96100594190546 L 724.1190410538375 464.80502970952716 L 717.3563596619706 478.2665280290849 L 464.2393575874066 375.65330560581697 L 462.91527586090126 378.28380420955995 L 709.8532298784406 491.4190210477996 L 701.6277274000925 504.2308232579669 L 461.4637166000163 380.84616465159337 L 459.88817673815265 383.3342139811762 L 692.6996681828653 516.671069905881 L 683.090560703419 528.7097913477304 L 458.19245188883866 385.7419582695461 L 456.3806272017642 388.0635970498188 L 672.8235541433303 540.317985249094 L 661.9233826206857 551.4676864541059 L 454.4570675212975 390.2935372908212 L 452.4264068711928 392.4264068711929 L 650.4163056034258 562.1320343559646 L 638.3300446479859 572.2853376064882 L 450.29353729082106 394.45706752129763 L 448.06359704981867 396.3806272017643 L 625.693716615639 581.9031360088215 L 612.5377635274268 590.9622594441939 L 445.74195826954593 398.1924518888388 L 443.33421398117605 399.8881767381528 L 598.8938792266642 599.4408836907639 L 584.7949330256947 607.3185830000821 L 440.8461646515932 401.4637166000164 L 438.2838042095597 402.9152758609014 L 570.2748905208385 614.5763793045069 L 555.3687317662951 621.1967879370334 L 435.6533056058168 404.2393575874067 L 432.9610059419052 405.4327719506773 L 540.1123670041297 627.1638597533864 L 524.5425501533539 632.4632195549066 L 430.21339120353304 406.4926439109813 L 427.41708063526755 407.4164201439326 L 508.69679026651625 637.082100719663 L 492.6132611671087 641.0093759583635 L 424.57881079419565 408.2018751916727 L 421.7054193209675 408.8471168241939 L 476.33070948548254 644.2355841209694 L 459.8883613148219 646.7529529894346 L 418.8038284673215 409.35059059788694 L 415.88102841977343 409.71108360033185 L 443.3258277120495 648.5554180016593 L 426.68300927132094 649.638636861552 L 412.94406045964485 409.92772737231036 L 409.9999999999998 410 L 409.99999999999875 650.0000000000001 L 393.3169907286766 649.6386368615517 L 407.0559395403547 409.92772737231036 L 404.1189715802261 409.7110836003318 L 376.67417228794807 648.5554180016591 L 360.11163868517565 646.7529529894343 L 401.1961715326781 409.3505905978869 L 398.29458067903204 408.8471168241938 L 343.669290514515 644.235584120969 L 327.38673883288885 641.0093759583631 L 395.4211892058039 408.2018751916726 L 392.582919364732 407.4164201439325 L 311.30320973348137 637.0821007196624 L 295.4574498466437 632.4632195549059 L 389.78660879646657 406.4926439109812 L 387.0389940580944 405.43277195067714 L 279.887632995868 627.1638597533856 L 264.6312682337026 621.1967879370326 L 384.3466943941828 404.2393575874065 L 381.7161957904399 402.9152758609012 L 249.72510947915927 614.5763793045061 L 235.2050669743031 607.3185830000812 L 379.1538353484064 401.4637166000162 L 376.6657860188236 399.8881767381526 L 221.1061207733337 599.440883690763 L 207.4622364725711 590.9622594441928 L 374.2580417304537 398.19245188883855 L 371.936402950181 396.38062720176407 L 194.30628338435898 581.9031360088203 L 181.6699553520122 572.2853376064868 L 369.7064627091786 394.45706752129735 L 367.57359312880686 392.4264068711926 L 169.58369439657233 562.1320343559632 L 158.07661737931238 551.4676864541045 L 365.5429324787022 390.2935372908209 L 363.61937279823553 388.0635970498185 L 147.17644585666795 540.3179852490925 L 136.9094392965793 528.7097913477288 L 361.80754811116105 385.74195826954576 L 360.111823261847 383.3342139811759 L 127.30033181713321 516.6710699058793 L 118.37227259990618 504.2308232579651 L 358.53628339998346 380.846164651593 L 357.08472413909845 378.28380420955955 L 110.14677012155806 491.4190210477978 L 102.64364033802809 478.266528029083 L 355.7606424125932 375.6533056058166 L 354.5672280493226 372.96100594190506 L 95.8809589461614 464.8050297095253 L 89.87501783777185 451.0669560176643 L 353.5073560890186 370.21339120353286 L 352.5835798560673 367.4170806352674 L 84.64028585104796 437.0854031763369 L 80.18937391385413 422.8940539709773 L 351.7981248083272 364.5788107941955 L 351.15288317580604 361.70541932096734 L 76.53300466290085 408.5270966048366 L 73.67998661197373 394.01914233660654 L 350.649409402113 358.8038284673213 L 350.2889163996681 355.8810284197732 L 71.63719293145243 379.4051420988661 L 70.40954489024085 364.7203022982233 L 350.0722726276896 352.9440604596447 L 349.99999999999994 349.99999999999955 L 69.99999999999955 349.99999999999784 L 70.40954489024102 335.27969770177236 L 350.0722726276896 347.0559395403545 L 350.28891639966815 344.1189715802259 L 71.63719293145283 320.59485790112956 L 73.67998661197436 305.98085766338914 L 350.6494094021131 341.19617153267785 L 351.1528831758062 338.2945806790318 L 76.5330046629017 291.47290339515916 L 80.1893739138552 277.10594602901847 L 351.7981248083274 335.42118920580367 L 352.5835798560675 332.58291936473177 L 84.64028585104933 262.9145968236589 L 89.87501783777338 248.93304398233158 L 353.50735608901886 329.78660879646634 L 354.5672280493229 327.03899405809415 L 95.88095894616305 235.19497029047068 L 102.64364033802997 221.73347197091297 L 355.76064241259354 324.3466943941826 L 357.08472413909885 321.71619579043966 L 110.14677012156017 208.58097895219828 L 118.37227259990851 195.76917674203108 L 358.53628339998386 319.15383534840623 L 360.11182326184746 316.6657860188234 L 127.30033181713577 183.32893009411694 L 136.90943929658204 171.29020865226764 L 361.8075481111615 314.25804173045356 L 363.61937279823604 311.9364029501808 L 147.1764458566709 159.68201475090402 L 158.07661737931554 148.5323135458922 L 365.5429324787027 309.7064627091784 L 367.5735931288075 307.5735931288067 L 169.5836943965756 137.8679656440335 L 181.66995535201565 127.71466239351008 L 369.7064627091792 305.542932478702 L 371.9364029501816 303.61937279823536 L 194.3062833843626 118.09686399117678 L 207.46223647257486 109.03774055580445 L 374.2580417304544 301.8075481111609 L 376.6657860188243 300.1118232618469 L 221.10612077333755 100.55911630923444 L 235.20506697430704 92.68141699991645 L 379.15383534840714 298.5362833999833 L 381.71619579044057 297.08472413909834 L 249.72510947916334 85.4236206954917 L 264.6312682337068 78.80321206296531 L 384.34669439418354 295.76064241259303 L 387.0389940580951 294.56722804932247 L 279.8876329958723 72.83614024661233 L 295.4574498466481 67.53678044509223 L 389.7866087964673 293.50735608901846 L 392.5829193647328 292.58357985606716 L 311.3032097334858 62.91789928033586 L 327.3867388328934 58.99062404163544 L 395.4211892058047 291.7981248083271 L 398.2945806790329 291.1528831758059 L 343.6692905145196 55.76441587902963 L 360.11163868518037 53.247047010564586 L 401.1961715326789 290.64940940211295 L 404.11897158022697 290.288916399668 L 376.67417228795284 51.44458199833997 L 393.3169907286814 50.36136313844747 L 407.05593954035555 290.0722726276895 L 410.0000000000006 289.9999999999999 L 410.0000000000036 49.99999999999932 L 426.6830092713258 50.36136313844776 L 412.94406045964575 290.0722726276896 L 415.8810284197743 290.2889163996681 L 443.3258277120543 51.444581998340595 L 459.8883613148268 53.24704701056555 L 418.8038284673224 290.6494094021131 L 421.70541932096836 291.15288317580615 L 476.33070948548743 55.76441587903088 L 492.6132611671136 58.99062404163698 L 424.5788107941965 291.7981248083274 L 427.4170806352684 292.58357985606756 L 508.69679026652113 62.91789928033768 L 524.5425501533587 67.53678044509428 L 430.2133912035339 293.50735608901886 L 432.9610059419061 294.5672280493229 L 540.1123670041344 72.83614024661466 L 555.3687317662998 78.80321206296787 L 435.6533056058176 295.7606424125936 L 438.28380420956057 297.0847241390989 L 570.2748905208431 85.42362069549449 L 584.7949330256993 92.68141699991958 L 440.846164651594 298.5362833999839 L 443.3342139811768 300.1118232618476 L 598.8938792266686 100.5591163092378 L 612.5377635274311 109.03774055580809 L 445.74195826954667 301.8075481111616 L 448.0635970498194 303.61937279823616 L 625.6937166156432 118.09686399118067 L 638.3300446479899 127.7146623935142 L 450.29353729082175 305.5429324787028 L 452.4264068711935 307.57359312880754 L 650.4163056034297 137.86796564403784 L 661.9233826206896 148.53231354589673 L 454.45706752129814 309.70646270917933 L 456.3806272017648 311.9364029501818 L 672.8235541433339 159.6820147509088 L 683.0905607034226 171.2902086522726 L 458.1924518888393 314.2580417304545 L 459.8881767381533 316.6657860188244 L 692.6996681828685 183.32893009412211 L 701.6277274000955 195.76917674203642 L 461.4637166000169 319.1538353484073 L 462.91527586090183 321.71619579044074 L 709.8532298784436 208.5809789522038 L 717.3563596619734 221.7334719709186 L 464.2393575874071 324.3466943941837 L 465.43277195067765 327.0389940580953 L 724.1190410538401 235.19497029047645 L 730.1249821622295 248.9330439823375 L 466.49264391098166 329.7866087964675 L 467.4164201439329 332.582919364733 L 735.3597141489532 262.91459682366497 L 739.8106260861471 277.1059460290246 L 468.201875191673 335.4211892058049 L 468.84711682419413 338.29458067903306 L 743.4669953371001 291.4729033951654 L 746.3200133880272 305.98085766339545 L 469.35059059788716 341.1961715326791 L 469.7110836003321 344.1189715802272 L 748.3628070685484 320.5948579011359 L 749.5904551097599 335.2796977017788 L 469.9277273723105 347.0559395403558 z diff --git a/src/toys/svgd/lotsarect.svgd b/src/toys/svgd/lotsarect.svgd new file mode 100644 index 0000000..ab2d186 --- /dev/null +++ b/src/toys/svgd/lotsarect.svgd @@ -0,0 +1 @@ +M 39.75993,80.899849 L 359.75993,80.899849 L 359.75993,278.04273 L 39.75993,278.04273 L 39.75993,80.899849 zM 377.14322,406.21942 L 511.42894,406.21942 L 511.42894,503.36228 L 377.14322,503.36228 L 377.14322,406.21942 zM 377.14322,189.9335 L 511.42894,189.9335 L 511.42894,287.07636 L 377.14322,287.07636 L 377.14322,189.9335 zM 377.14322,298.07648 L 511.42894,298.07648 L 511.42894,395.21934 L 377.14322,395.21934 L 377.14322,298.07648 zM 377.14322,80.899849 L 511.42894,80.899849 L 511.42894,178.04271 L 377.14322,178.04271 L 377.14322,80.899849 zM 377.14322,514.36237 L 511.42894,514.36237 L 511.42894,611.50523 L 377.14322,611.50523 L 377.14322,514.36237 zM 49.495197,612.79767 L 359.4294,612.79767 L 359.4294,622.22533 L 49.495197,622.22533 L 49.495197,612.79767 zM 374.74936,625.76074 L 512.62885,625.76074 L 512.62885,757.74794 L 374.74936,757.74794 L 374.74936,625.76074 z
\ No newline at end of file diff --git a/src/toys/svgd/monkey.svgd b/src/toys/svgd/monkey.svgd new file mode 100644 index 0000000..9bdbbc3 --- /dev/null +++ b/src/toys/svgd/monkey.svgd @@ -0,0 +1 @@ +M 5.9562726,54.79058 C 13.541683,37.50935 29.441883,27.76099 41.447803,33.03085 c 12.00591,5.26987 15.59357,23.57221 8.00816,40.85344 -7.58541,17.28124 -9.90029,25.53083 -32.70294,16.30215 C 4.5991026,85.26753 -1.6291374,72.07182 5.9562726,54.79058 z M 59.77575,139.78799 c 17.6347,24.11274 67.29975,30.95068 91.77238,0.71978 m -34.6436,-37.40404 c 0,3.47102 -5.75618,6.2881 -12.84862,6.2881 -7.09242,0 -12.848597,-2.81708 -12.848597,-6.2881 0,-3.471028 5.756177,-6.288105 12.848597,-6.288105 7.09244,0 12.84862,2.817077 12.84862,6.288105 z m 8.28521,-17.104633 c -2.97718,0.127204 -5.59649,-4.52155 -5.84666,-10.376686 -0.25017,-5.855125 1.96307,-10.710355 4.94026,-10.837559 2.97718,-0.127204 5.59649,4.52155 5.84665,10.376676 0.25017,5.855135 -1.96307,10.710365 -4.94025,10.837569 z M 83.28913,64.783523 c 2.977573,-0.11776 5.582124,4.539282 5.813719,10.395185 0.231594,5.855893 -1.997037,10.704081 -4.97461,10.821841 -2.977572,0.11776 -5.582124,-4.539282 -5.813718,-10.395175 -0.231594,-5.855903 1.997036,-10.704092 4.974609,-10.821851 z M 45.217471,85.556076 c 0.822591,11.141413 3.633402,22.142554 10.599469,31.583504 0,0 -15.4694,5.45785 -13.31599,26.99188 2.87914,28.79134 33.63174,40.93745 66.22007,40.66777 27.38883,-0.21003 58.61221,-13.31662 59.02225,-39.2282 0.29666,-23.0823 -6.83795,-24.11275 -12.95611,-28.79134 6.43867,-10.04933 9.12075,-19.882931 9.80868,-31.216258 m -7.09398,-26.793307 c -5.29439,-5.661365 -13.31177,-9.59154 -23.94831,-11.089645 -10.28695,-1.43893 -26.99188,5.03849 -29.15123,5.03849 -1.79946,0 -23.47674,-7.30799 -34.5496,-4.6786 -6.377516,1.507416 -11.596875,4.060909 -15.573817,7.756776 M 47.116071,35.239853 C 60.692058,16.061922 81.318474,3.8164724 104.40235,3.8164724 c 24.26701,0 45.81819,13.5328816 59.31755,34.4261516 m 14.55081,56.509427 c -0.76518,14.403499 -4.61365,27.847529 -10.79081,39.459189 M 42.237336,135.88313 C 35.362956,123.6046 31.132611,109.16372 30.481189,93.641902 M 204.76611,54.79058 C 197.1807,37.50935 181.2805,27.76099 169.27458,33.03085 c -12.00591,5.26987 -15.59357,23.57221 -8.00816,40.85344 7.58541,17.28124 9.90029,25.53083 32.70294,16.30215 12.15392,-4.91891 18.38216,-18.11462 10.79675,-35.39586 z diff --git a/src/toys/svgd/nasty.svgd b/src/toys/svgd/nasty.svgd new file mode 100644 index 0000000..2a26bd2 --- /dev/null +++ b/src/toys/svgd/nasty.svgd @@ -0,0 +1,3 @@ +M 387.63906,267.9784 L 106.72661,469.82569 L 106.72661,267.9784 L 387.63906,469.82569 L 387.63906,267.9784 z M 540.4278,232.25903 C 440.92573,232.25903 646.18067,452.19982 540.4278,452.19982 C 437.36839,452.19982 639.73953,232.25903 540.4278,232.25903 z +M 282.85714,578.07647 C 74.285714,832.36218 165.33193,1033.3172 288.57143,900.93361 C 410.47619,769.98377 73.333332,831.45467 282.85714,578.07647 z +M 548.57143,662.36218 C 615.72948,662.36218 625.35777,906.64794 728.57143,906.64794 C 832.06154,906.64794 637.327,662.36218 548.57143,662.36218 C 459.95393,662.36218 291.30957,906.64794 428.57143,906.64794 C 534.44005,906.64794 481.41338,662.36218 548.57143,662.36218 z diff --git a/src/toys/svgd/onlyarcs.svgd b/src/toys/svgd/onlyarcs.svgd new file mode 100644 index 0000000..b8fe8d1 --- /dev/null +++ b/src/toys/svgd/onlyarcs.svgd @@ -0,0 +1,10 @@ +M 0,0 +A 40,20 0 0 0 100,0 + 40,20 0 0 0 200,0 + 40,20 90 0 0 200,100 + 40,20 90 0 0 200,200 + 40,20 0 0 0 100,200 + 40,20 0 0 0 0,200 + 40,20 90 0 0 0,100 + 40,20 90 0 0 0,0 + z diff --git a/src/toys/svgd/ptitle.svgd b/src/toys/svgd/ptitle.svgd new file mode 100644 index 0000000..2df8860 --- /dev/null +++ b/src/toys/svgd/ptitle.svgd @@ -0,0 +1 @@ +M 115.1808,25.588188 C 127.80826,26.557245 141.79964,22.715822 153.00893,30.053032 C 167.1951,42.822758 151.75887,64.216819 134.75853,59.865532 C 126.06809,57.038779 129.18533,66.019697 128.71596,71.414009 C 131.4913,80.259661 123.09665,77.935329 117.16748,78.076469 C 112.56276,76.886302 116.47422,67.179968 115.1808,62.6785 C 115.1808,50.315063 115.1808,37.951625 115.1808,25.588188 z M 131.60308,50.056938 C 148.57642,53.813235 146.23212,30.658565 130.43,35.396782 C 127.29211,38.94083 127.23081,46.355022 131.60308,50.056938 z M 183.06752,60.357719 C 165.63447,60.295503 183.24033,80.518401 189.00011,65.31476 C 192.06883,58.337714 187.11171,60.778459 183.06752,60.357719 z M 202.29799,55.611626 C 202.29799,63.099907 202.29799,70.588188 202.29799,78.076469 C 197.62085,76.91918 187.20205,81.503563 189.60658,73.264604 C 182.8671,85.729177 152.16107,75.907493 165.13563,57.951724 C 168.48007,47.659856 195.18552,59.239016 187.25111,47.525688 C 177.86756,43.881758 158.53169,53.222176 167.93494,39.294753 C 181.3394,36.315116 203.97321,35.500521 202.29799,55.611626 z M 243.32533,49.424126 C 229.50261,42.165491 224.74932,58.12986 226.66127,68.597202 C 228.09365,76.076455 226.04486,80.074546 217.99991,78.076469 C 210.48863,79.788485 215.52906,68.948786 214.07533,64.603246 C 214.07533,55.969321 214.07533,47.335395 214.07533,38.701469 C 218.71947,39.856601 229.10169,35.273643 226.66127,43.513335 C 227.95656,41.232134 248.09079,31.257919 243.31691,46.694818 C 243.31972,47.604587 243.32252,48.514356 243.32533,49.424126 z M 267.30191,60.357719 C 249.86884,60.295482 267.47469,80.518412 273.23449,65.31476 C 276.30322,58.33771 271.34611,60.778461 267.30191,60.357719 z M 286.53235,55.611626 C 286.53235,63.099907 286.53235,70.588188 286.53235,78.076469 C 281.85522,76.919181 271.43643,81.503563 273.84097,73.264604 C 267.10148,85.729174 236.39544,75.907495 249.37001,57.951724 C 252.71445,47.659856 279.41992,59.239015 271.48549,47.525688 C 262.10194,43.88176 242.76606,53.222171 252.16932,39.294753 C 265.57378,36.315114 288.20758,35.500525 286.53236,55.611626 M 334.80191,45.240532 C 345.49046,28.171278 366.87108,43.167474 361.59096,59.874146 C 361.59097,65.941587 361.59097,72.009028 361.59097,78.076469 C 356.92475,76.919987 346.51835,81.503682 348.93472,73.264604 C 348.50839,65.231031 349.97235,56.898734 347.77455,49.142876 C 335.14645,42.504341 335.807,61.093114 336.24329,69.326636 C 337.9621,76.849177 335.16475,79.855557 327.58193,78.076469 C 320.00816,79.827535 325.02155,68.991435 323.58704,64.603246 C 326.31279,55.038882 318.58105,38.272161 311.38782,53.827464 C 310.41918,61.855715 311.09896,69.997063 310.89566,78.076469 C 306.22944,76.919987 295.82304,81.503682 298.23941,73.264604 C 298.23941,61.743559 298.23941,50.222514 298.23941,38.701469 C 302.90563,39.857951 313.31203,35.274257 310.89566,43.513335 C 314.6641,37.643381 330.44834,33.922886 334.8019,45.240532 M 412.70816,58.283501 C 413.74716,65.329673 402.8183,60.407999 398.27256,61.869438 C 392.57307,62.588118 377.07559,58.69943 386.48158,68.513969 C 394.57691,78.279659 414.21137,57.534567 410.84485,73.681704 C 404.09284,81.288741 385.15052,81.142742 376.35658,73.681938 C 363.9865,62.168354 371.67984,37.148572 389.80164,37.838507 C 401.97647,35.889384 413.6668,45.751205 412.70815,58.283501 M 399.77066,54.099907 C 393.61653,30.04231 370.10862,61.928589 399.77066,54.099907 z M 435.94644,27.521782 C 435.96201,33.478312 433.93472,41.4777 442.68305,38.701469 C 449.91823,34.769304 451.1939,46.589839 446.99432,47.701469 C 441.05173,47.687042 433.16506,45.728538 435.94644,54.438081 C 435.39823,61.49483 434.91359,71.865095 445.20998,69.076469 C 450.19974,68.732922 449.24732,82.168413 442.05502,78.076469 C 430.09932,80.727323 420.36946,71.930596 423.36049,59.588823 C 422.71262,54.349502 426.24292,44.932013 417.58627,47.701469 C 415.00348,41.807039 418.08141,37.377201 423.3605,37.739096 C 422.6103,31.016795 422.60176,25.036149 431.05949,27.521782 C 432.68847,27.521782 434.31746,27.521782 435.94644,27.521782 z M 485.90347,49.424126 C 472.08074,42.165489 467.32744,58.129851 469.2394,68.597202 C 470.67179,76.076455 468.62301,80.074546 460.57805,78.076469 C 453.06677,79.788485 458.1072,68.948786 456.65347,64.603246 C 456.65347,55.969321 456.65347,47.335395 456.65347,38.701469 C 461.29761,39.856601 471.67983,35.273643 469.23941,43.513335 C 470.53469,41.232124 490.6689,31.257926 485.89505,46.694818 C 485.89785,47.604587 485.90066,48.514356 485.90347,49.424126 z M 492.23157,38.701469 C 496.8757,39.856601 507.25793,35.273643 504.8175,43.513335 C 504.8175,55.03438 504.8175,66.555425 504.8175,78.076469 C 500.17337,76.921338 489.79114,81.504296 492.23157,73.264604 C 492.23157,61.743559 492.23157,50.222514 492.23157,38.701469 z M 492.23157,23.373344 C 496.8757,24.528476 507.25793,19.945518 504.8175,28.18521 C 507.42558,36.781485 497.41711,32.761224 492.30665,33.638969 C 492.13675,30.373397 492.27768,26.760041 492.23157,23.373344 z M 548.79797,39.931938 C 552.97763,56.979712 538.66501,41.438745 530.05971,49.846001 C 518.41065,63.70638 539.40292,76.166974 548.79799,67.577905 C 553.09286,81.766505 534.23374,80.517459 525.20155,76.9674 C 507.36245,69.798238 511.84153,38.785564 531.6681,38.095047 C 537.4159,37.270461 543.34813,37.942021 548.79799,39.931938 M 590.49329,39.931938 C 594.79689,57.039848 577.52986,40.882708 568.9513,48.158523 C 572.15538,57.881524 601.33708,52.315812 591.96984,71.985653 C 585.95746,81.25109 561.1725,82.610066 558.00892,72.785571 C 554.84332,59.391839 574.62439,78.16019 582.36692,66.985848 C 570.73978,64.945587 547.5903,55.865125 561.70033,40.775688 C 570.39079,35.821962 581.23811,37.826772 590.4933,39.931938 diff --git a/src/toys/svgd/rect.svgd b/src/toys/svgd/rect.svgd new file mode 100644 index 0000000..29ee442 --- /dev/null +++ b/src/toys/svgd/rect.svgd @@ -0,0 +1 @@ +M 120,98.076469 L 551.42856,98.076469 L 551.42856,323.79075 L 120,323.79075 L 120,98.076469 z
\ No newline at end of file diff --git a/src/toys/svgd/sanitize-examples.svgd b/src/toys/svgd/sanitize-examples.svgd new file mode 100644 index 0000000..486188d --- /dev/null +++ b/src/toys/svgd/sanitize-examples.svgd @@ -0,0 +1 @@ +m 103.3207,92.186815 c 61.67475,0 77.09344,123.349515 138.76821,123.349515 92.51214,0 92.51214,-123.349515 0,-123.349515 -61.67476,0 -77.09345,123.349515 -138.76821,123.349515 -92.512144,0 -92.512144,-123.349515 0,-123.349515 m 172.70652,592.36216 c -116.436115,0 -206.29787,-170 -79.999995,-170 119.740085,0 239.999995,110 79.999995,110 -159.999995,0 -39.74009,-110 80,-110 126.29788,0 36.43613,170 -80,170
\ No newline at end of file diff --git a/src/toys/svgd/scribble.svgd b/src/toys/svgd/scribble.svgd new file mode 100644 index 0000000..206563d --- /dev/null +++ b/src/toys/svgd/scribble.svgd @@ -0,0 +1 @@ +M 205.71429,509.50504 C 191.27639,435.33396 124.96983,282.9985 237.14286,240.93361 C 298.33002,217.98843 410.42812,416.03951 425.71429,458.07647 C 456.36209,542.35792 211.91703,513.49252 165.71429,483.79075 C 20.478436,390.42485 89.745454,432.24443 157.14286,589.50504 C 207.33723,706.62525 264.31061,792.65288 391.42857,818.07647 C 466.24819,833.04039 419.17914,844.21704 428.57143,806.6479 C 455.45597,699.10972 205.78614,535.50673 188.57143,466.6479 C 186.58441,458.69982 179.04762,453.31456 174.28571,446.6479 C 173.27225,445.22905 206.50053,444.8262 242.85714,426.6479 C 302.27468,396.93913 311.42857,331.66273 311.42857,272.36218 C 311.42857,232.18181 300.5556,209.69024 257.14286,195.21933 C 217.28192,181.93235 106.94618,129.21667 94.285714,123.79075 C 79.18517,117.31909 126.94244,129.50504 168.57143,129.50504 C 301.00757,129.50504 430.66079,118.07647 562.85714,118.07647 C 597.38432,118.07647 452.97541,72.362183 425.71429,72.362183 C 289.53604,72.362183 290.63424,45.059235 377.14286,218.07647 C 439.49432,342.7794 589.94279,380.17165 680,455.21933 C 715.67553,484.94893 625.63444,427.43 577.14286,406.6479 C 390.81914,326.79487 363.97733,373.48237 502.85714,512.36218 C 519.79024,529.29528 656.23138,719.67741 540,680.93361 C 493.67741,665.49275 225.40147,535.28189 382.85714,503.79075 C 399.28655,500.50487 561.71346,472.36218 517.14286,472.36218 C 410.68431,472.36218 181.50374,350.75815 111.42857,292.36218 C 21.960157,217.80517 195.28155,363.95164 220,378.07647 C 486.98782,530.64094 333.26177,540.93361 142.85714,540.93361 C 33.897473,540.93361 -105.50257,492.78561 -34.285714,635.21933 C 4.462011,712.71478 133.2726,753.67122 208.57143,783.79075 C 234.8422,794.29906 219.78391,839.52186 208.57143,855.21933 C 168.50791,911.30825 225.30435,836.44417 274.28571,826.6479 C 322.06141,817.09276 466.28439,799.2233 485.71429,740.93361 C 493.89344,716.39615 516.95233,712.23517 474.28571,683.79075 C 439.34382,660.49616 441.2221,580.11609 397.14286,558.07647 C 373.83188,546.42098 339.15563,564.21294 322.85714,572.36218 C 305.88053,580.85049 256.57431,543.50648 240,535.21933 C 222.14649,526.29257 195.2496,535.66674 205.71429,509.50504 C 206.5716,507.36174 165.95426,541.31306 205.71429,509.50504 C 209.43271,506.5303 205.71429,519.02885 205.71429,523.79075 C 205.71429,528.55266 205.71429,514.26694 205.71429,509.50504 z
\ No newline at end of file diff --git a/src/toys/svgd/spiral.svgd b/src/toys/svgd/spiral.svgd new file mode 100644 index 0000000..cf096f9 --- /dev/null +++ b/src/toys/svgd/spiral.svgd @@ -0,0 +1 @@ +M 425.71429,452.36218 C 428.61778,445.53492 436.34979,452.9077 437.06166,457.18797 C 438.99077,468.78726 426.04452,475.99734 416.06271,475.05691 C 398.20761,473.37469 388.47706,454.23374 391.67221,437.88481 C 396.36121,413.89211 422.40756,401.33715 445.01745,406.97273 C 475.15289,414.48407 490.6018,447.65442 482.45111,476.49113 C 472.1943,512.7792 431.80767,531.14987 396.75955,520.44636 C 354.31159,507.48297 333.00474,459.83266 346.28275,418.58165 C 361.93043,369.96877 416.872,345.71719 464.32061,361.58328 C 519.10192,379.9013 546.30379,442.1516 527.84056,495.79429 C 506.86147,556.74653 437.29069,586.90252 377.45639,565.83581 C 310.33136,542.20215 277.21849,465.30257 300.8933,399.27849 C 327.17683,325.97925 411.41129,289.90746 483.62377,316.19383
\ No newline at end of file diff --git a/src/toys/svgd/star.svg b/src/toys/svgd/star.svg new file mode 100644 index 0000000..113f523 --- /dev/null +++ b/src/toys/svgd/star.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.45.1" + sodipodi:docbase="/home/njh/svn/lib2geom/src/toys" + sodipodi:docname="star.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35" + inkscape:cx="375" + inkscape:cy="520" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="910" + inkscape:window-height="631" + inkscape:window-x="0" + inkscape:window-y="25" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="fill:#ffff00;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 268.57143,258.07648 L 377.39465,340.01528 L 414.5031,208.94498 L 454.38051,339.19955 L 561.44306,254.97326 L 517.14286,383.79077 L 653.26525,378.58005 L 541.7086,456.7566 L 654.89671,532.55178 L 518.69446,530.22658 L 565.71428,658.07648 L 456.89106,576.13768 L 419.78261,707.20797 L 379.9052,576.95341 L 272.84264,661.1797 L 317.14285,532.36219 L 181.02045,537.5729 L 292.57711,459.39635 L 179.38899,383.60117 L 315.59124,385.92637 L 268.57143,258.07648 z " + id="path3134" /> + </g> +</svg> diff --git a/src/toys/svgd/star.svgd b/src/toys/svgd/star.svgd new file mode 100644 index 0000000..c6eb2d4 --- /dev/null +++ b/src/toys/svgd/star.svgd @@ -0,0 +1 @@ +M 268.57143,258.07648 L 377.39465,340.01528 L 414.5031,208.94498 L 454.38051,339.19955 L 561.44306,254.97326 L 517.14286,383.79077 L 653.26525,378.58005 L 541.7086,456.7566 L 654.89671,532.55178 L 518.69446,530.22658 L 565.71428,658.07648 L 456.89106,576.13768 L 419.78261,707.20797 L 379.9052,576.95341 L 272.84264,661.1797 L 317.14285,532.36219 L 181.02045,537.5729 L 292.57711,459.39635 L 179.38899,383.60117 L 315.59124,385.92637 L 268.57143,258.07648 z
\ No newline at end of file diff --git a/src/toys/svgd/tadpole.svgd b/src/toys/svgd/tadpole.svgd new file mode 100644 index 0000000..8895582 --- /dev/null +++ b/src/toys/svgd/tadpole.svgd @@ -0,0 +1 @@ +m 371.42857,212.36218 c -32.48101,-26.68142 -91.58939,10.54383 -64.64475,49.96244 5.57459,42.57277 70.53374,68.98077 78.10803,11.90685 14.41443,-52.55004 -40.97633,-99.82411 -91.27953,-91.66162 -45.76637,3.77115 -95.66566,43.4207 -88.25336,93.06866 8.10715,43.84559 57.30139,51.48387 83.49912,79.21169 62.5378,56.33364 174.00162,56.411 226.54697,-13.71412 33.15536,-44.32089 27.37475,-103.8585 15.40989,-154.44966 C 518.94598,147.80633 481.97543,121.49829 442.21462,117.80826 360.77206,103.76604 275.59142,128.77168 196.14437,100.04722 138.26682,85.91059 75.980997,126.30734 62.339274,183.76981 43.90764,241.6785 65.723336,304.74593 104.98132,348.74653 179.31517,452.93484 311.47528,524.69632 441.07626,499.66492 521.15261,482.71575 593.45793,431.55112 633.57498,359.85355 666.74394,302.92229 686.82287,232.82246 669.17746,167.67963 652.45446,117.60598 601.61006,86.174529 550.15809,84.370797 388.03362,61.260938 223.67779,65.532229 60.679408,54.420286 c -85.3275,5.078677 -174.754978,36.378483 -230.507428,103.801754 -26.33214,35.75102 -22.70039,88.23391 8.40942,120.02678 31.2681,37.67952 76.41934,59.44396 112.847171,91.25622
\ No newline at end of file diff --git a/src/toys/svgd/touchboxes.svgd b/src/toys/svgd/touchboxes.svgd new file mode 100644 index 0000000..55e6f9d --- /dev/null +++ b/src/toys/svgd/touchboxes.svgd @@ -0,0 +1 @@ +m 350,380 200,0 0,300 -200,0 0,-300 z M 80,180 l 270,0 0,400 -270,0 0,-400 z
\ No newline at end of file diff --git a/src/toys/svgd/toy.svgd b/src/toys/svgd/toy.svgd new file mode 100644 index 0000000..9983796 --- /dev/null +++ b/src/toys/svgd/toy.svgd @@ -0,0 +1 @@ +M 685.71429 132.36218 L 337.14286+238.07647 608.57143,298.07647 C 608.57143,298.07647 685.71429,543.79075 517.14286,552.36218 348.57143,560.93361 280,858.07647 422.85714,852.36218 565.71429,846.6479 691.42857,612.36218 691.42857,612.36218 Q 348.57143,560.93361 685.71429,132.36218 Z diff --git a/src/toys/svgd/triarrange.svgd b/src/toys/svgd/triarrange.svgd new file mode 100644 index 0000000..f5d82ee --- /dev/null +++ b/src/toys/svgd/triarrange.svgd @@ -0,0 +1 @@ +M 495,400 45,380 M 210,230 450,460 M 100,440 370,230
\ No newline at end of file diff --git a/src/toys/svgd/tricky.svgd b/src/toys/svgd/tricky.svgd new file mode 100644 index 0000000..1b2fa9a --- /dev/null +++ b/src/toys/svgd/tricky.svgd @@ -0,0 +1 @@ +M 458.65625,31.34375 L 455.0625,31.4375 L 451.6875,32.65625 C 434.07714,39.013007 420.84501,52.305976 414.03125,67.25 C 407.21749,82.194024 405.96292,98.410997 408.28125,113.8125 C 412.4771,141.68706 428.51783,169.60215 456.40625,181.6875 C 538.70194,312.80904 554.38602,481.52856 510.34375,629.90625 L 509.8125,631.71875 L 509.5625,633.59375 C 506.87733,655.34401 495.49324,679.1849 479.46875,695.25 C 463.70165,711.05705 445.14595,719.37248 423.84375,716.625 C 403.50709,711.46674 391.72366,697.93145 383.4375,677.25 C 375.01895,656.2381 372.48837,628.78961 374.375,606.03125 C 385.44095,485.85838 388.01837,349.21084 313,239 C 270.11084,152.28462 127.55134,177.04972 109.25,269.96875 L 108.5,273.6875 L 109.03125,277.4375 C 117.41998,339.02101 116.06431,402.35901 112.375,466.09375 L 112.21875,469.125 L 112.84375,472.09375 C 117.1301,492.47365 108.37169,516.28486 92.03125,532.75 C 78.58177,546.30212 61.816491,553.83892 44.78125,552.625 C 26.079739,485.94082 42.403549,411.80134 67.125,342.90625 L 24.3125,327.53125 C -3.3082192,404.50618 -24.482689,494.297 5.71875,580.03125 L 9.5625,590.90625 L 20.5625,594.25 C 59.671156,606.15028 98.586445,590.76637 124.34375,564.8125 C 149.33808,539.62742 164.42386,503.26605 157.9375,466.125 C 161.49046,403.72415 162.85377,340.00411 154.65625,276.1875 C 166.8006,229.69869 250.73728,213.68098 272.65625,260.03125 L 273.46875,261.6875 L 274.5,263.21875 C 340.18519,358.54848 339.90385,483.98933 329.03125,602.0625 L 329.03125,602.15625 L 329.03125,602.25 C 326.66071,630.84572 329.10103,663.98958 341.1875,694.15625 C 353.27397,724.32292 377.30928,752.4189 414.28125,761.1875 L 415.28125,761.40625 L 416.25,761.5625 C 453.7973,767.02344 487.87231,751.21921 511.65625,727.375 C 534.86042,704.11203 549.85821,673.25525 554.3125,641.53125 C 602.10825,479.2135 584.5466,295.0552 490.8125,150.71875 L 486.125,143.53125 L 477.90625,141.21875 C 466.97267,138.13618 455.72384,123.25825 453.28125,107.03125 C 452.05996,98.91775 453.07904,91.297618 455.4375,86.125 C 457.36175,81.904709 459.73336,79.094036 464.03125,76.90625 C 495.13555,76.980202 529.29685,90.275009 566.03125,101.59375 C 610.4676,115.59228 646.8459,148.41525 668.6875,190.40625 L 668.8125,190.65625 L 668.9375,190.875 C 697.92762,243.68554 717.84744,301.48146 706.25,356.90625 L 706.21875,357.15625 L 706.15625,357.40625 C 694.18886,421.72169 679.2601,488.02392 680.625,557.96875 C 678.68079,606.53403 686.86775,651.99143 686.21875,693.84375 C 670.05571,787.71759 590.84823,878.44142 494,879.59375 L 493.96875,879.59375 L 493.9375,879.59375 C 395.82741,881.09756 293.40089,802.00173 289.84375,701.78125 L 289.8125,700.875 L 289.71875,700 C 282.30077,635.83434 290.72304,569.30623 297.15625,501.21875 L 298.0625,491.5625 L 291.75,484.21875 C 284.80041,476.14619 277.49477,469.76476 268.21875,465.84375 C 258.94273,461.92274 247.2182,461.69521 238.125,465.375 C 219.9386,472.73458 212.83021,487.30247 206.46875,501.0625 C 200.10729,514.82253 195.45919,529.80289 191.34375,543.03125 C 187.22831,556.25961 183.18481,568.24935 181.34375,571.875 L 181.28125,572.03125 L 181.21875,572.1875 C 142.65707,651.07426 94.549143,727.5865 58.8125,812.3125 C 49.294957,829.15506 36.457563,832.16994 22.90625,829.09375 C 9.2095614,825.98456 -3.2515511,815.31431 -6,797.40625 L -6.28125,795.53125 L -6.875,793.71875 C -20.296536,753.1447 -21.015532,704.32135 -39.625,654.96875 C -73.90942,524.31702 -90.602855,380.79943 -28.71875,262.375 C 1.9598227,205.26365 67.457357,164.07188 103.28125,90.9375 L 62.4375,70.9375 C 33.995201,129.00238 -30.775413,170.01981 -68.9375,241.0625 L -69,241.1875 L -69.0625,241.28125 C -139.47511,376.02623 -119.00391,531.98526 -83.5,667.0625 L -83.1875,668.28125 L -82.71875,669.46875 C -67.458776,708.96795 -66.587036,756.94282 -50.5625,806.375 C -44.27864,841.61077 -17.523667,866.57525 12.84375,873.46875 C 43.80863,880.49788 80.448846,867.38803 98.96875,833.78125 L 99.5625,832.71875 L 100.03125,831.59375 C 133.90634,750.79708 181.84088,674.53399 222.09375,592.1875 C 227.62217,581.30025 230.72079,569.58288 234.78125,556.53125 C 238.84171,543.47962 243.26444,529.92624 247.78125,520.15625 C 248.49865,518.60449 249.23407,517.44847 249.96875,516.1875 C 243.90866,577.21519 237.04535,640.29773 244.46875,704.90625 C 244.48076,705.01077 244.48792,705.11422 244.5,705.21875 C 250.176,833.46768 372.77047,926.96151 494.625,925.09375 C 619.20181,923.6115 712.56579,813.78448 731.40625,699.875 L 731.65625,698.28125 L 731.6875,696.65625 C 732.75618,647.53405 724.20107,601.36335 726.09375,558.53125 L 726.125,557.8125 L 726.125,557.09375 C 724.89958,494.29702 738.71436,431.07277 750.875,365.71875 C 765.84238,294.18884 740.23516,226.21058 708.8125,168.96875 C 681.8812,117.19267 636.59507,76.122901 579.5625,58.15625 L 579.5,58.125 L 579.4375,58.125 C 544.17995,47.261312 504.09579,29.800274 458.65625,31.34375 z
\ No newline at end of file diff --git a/src/toys/svgd/winding.svgd b/src/toys/svgd/winding.svgd new file mode 100644 index 0000000..e0d9b59 --- /dev/null +++ b/src/toys/svgd/winding.svgd @@ -0,0 +1 @@ +M 237.8317,-0.00079806585 C 209.05129,2.0908553 194.49466,33.03178 168.59187,39.198673 C 144.83493,42.756195 133.04439,64.090923 112.23618,72.228008 C 96.390481,93.25412 79.059139,109.36615 55.144754,121.38967 C 28.073728,135.55225 28.749612,175.16363 61.142614,182.29572 C 59.032188,207.27758 51.935234,245.41728 19.327194,232.92932 C -15.949549,243.07701 3.1745321,287.497 13.519564,310.39525 C 17.791104,331.61525 13.405542,357.01005 26.625846,375.92214 C 24.121233,402.01179 53.313158,415.34704 52.967142,440.29059 C 53.481143,471.00236 85.579755,514.66901 114.61929,479.98667 C 130.54404,473.62405 176.35717,503.20612 155.39995,524.08371 C 144.17379,562.84736 195.91996,565.77913 221.73596,560.74671 C 247.29609,564.51298 273.27675,571.81621 298.92308,570.02889 C 324.04728,573.62413 345.47989,554.14368 370.64594,564.03304 C 402.54533,573.02054 442.7131,544.20356 415.3454,511.64988 C 431.35565,493.15457 459.52837,465.86203 478.13411,494.82269 C 512.36196,508.17908 524.21953,461.82677 528.84872,436.94541 C 539.13223,415.75136 555.80382,396.04476 559.09515,372.30135 C 573.74637,351.25358 558.73087,324.13051 575.26348,304.02273 C 593.5935,277.76628 586.3979,225.87087 544.09643,239.90933 C 527.05985,225.53034 511.89014,182.31837 545.28987,174.97401 C 567.85671,145.99111 526.28917,121.32068 504.46509,108.82388 C 488.46914,94.238944 477.21301,71.049919 455.38968,63.601767 C 442.2435,41.019814 410.90027,47.427299 396.59221,27.25649 C 378.5237,4.3455996 329.19512,-15.054231 323.43731,28.290685 C 321.44896,48.974453 272.63938,43.051302 266.79567,31.393533 C 265.68977,16.25095 255.66504,-2.8664064 237.8317,-0.00079806585 z M 214.63232,41.285294 C 229.03834,39.461472 281.82707,44.654146 243.16774,50.095915 C 215.41861,55.13544 186.824,66.887416 160.08963,70.064158 C 171.40031,49.377676 189.54369,36.977634 214.63232,41.285294 z M 364.21881,43.044488 C 389.3526,38.086584 423.75203,50.784111 426.68647,74.129598 C 394.45708,65.080185 361.92396,54.41391 328.76005,47.772323 C 335.0792,39.285706 353.90158,45.472989 364.21881,43.044488 z M 299.89827,49.421568 C 334.38633,45.796396 318.92415,87.04827 327.35648,107.9555 C 332.43473,138.01064 340.0154,167.56295 347.72637,197.02897 C 326.17931,210.45393 292.14883,198.94677 265.91081,202.13508 C 245.36668,206.92129 230.45935,196.7415 243.8515,176.32177 C 254.2743,135.80305 265.45003,94.976539 267.46313,52.939957 C 278.24641,51.687903 288.9237,47.465671 299.89827,49.421568 z M 137.22777,69.707278 C 166.23618,69.494992 120.49304,93.04016 137.22777,69.707278 z M 451.29893,73.445566 C 466.93112,101.11163 425.11973,69.858477 451.29893,73.445566 z M 161.69157,80.207469 C 195.12998,110.69979 212.21109,153.72778 229.83786,194.32162 C 218.52396,208.95034 195.22647,168.57531 181.29522,159.91955 C 163.37572,141.33152 146.8742,120.6359 137.28274,96.425042 C 142.33212,87.653771 151.47682,79.604025 161.69157,80.207469 z M 432.38759,84.605455 C 469.44406,97.732695 430.28001,132.80731 416.73442,150.68472 C 399.36069,169.02554 380.60198,186.72201 360.97525,202.252 C 349.31821,186.47906 377.05703,160.7322 382.09772,141.74406 C 393.9733,119.90955 408.28746,98.903859 427.49484,82.84626 L 432.38759,84.605455 z M 128.54175,91.752182 C 115.85675,118.87612 92.85066,143.11942 78.60652,170.73021 C 62.000206,188.85762 85.067031,127.73706 91.166453,114.95867 C 96.261463,99.592362 115.43004,97.567564 128.54175,91.752182 z M 461.79912,96.095193 C 494.61461,101.4137 503.85761,132.95303 510.27958,161.07074 C 516.23377,173.44499 515.53354,194.75452 505.81343,172.70604 C 492.0604,146.83354 474.56757,123.01553 458.88546,98.239211 C 458.10505,96.914376 460.38765,93.356115 461.79912,96.095193 z M 79.944004,189.00264 C 125.91433,201.82926 173.80854,204.82394 221.2293,207.58413 C 227.40504,232.22763 205.75574,261.03157 200.77991,287.0056 C 198.98453,308.21225 184.56547,318.80777 169.17926,299.94722 C 133.95987,277.46442 98.659981,254.15242 59.27347,239.46953 C 61.480079,223.18569 64.663812,195.41065 79.944004,189.00264 z M 511.49636,197.57872 C 525.19335,217.70705 535.43047,253.2069 500.46557,254.90752 C 464.15836,271.3807 430.27042,292.57043 396.81888,314.12534 C 377.50066,297.83942 377.83971,261.83829 366.73344,237.87322 C 355.70058,219.68486 361.03872,202.51102 384.63974,208.83172 C 426.3772,206.34293 468.63407,204.30758 509.18742,193.18073 L 511.49636,197.57872 z M 254.26917,235.40139 C 267.88776,248.222 300.90986,260.92788 269.02803,272.58167 C 260.2717,268.87591 255.10883,237.44794 254.26917,235.40139 z M 331.50879,237.05064 C 324.01248,255.18663 320.23797,289.128 300.05044,261.46924 C 304.69039,252.09852 327.86234,239.96464 331.50879,237.05064 z M 50.312574,243.81254 C 48.701291,276.28009 44.929165,308.91572 44.438642,341.59724 C 38.920345,352.62589 18.235924,313.83416 27.328216,302.38831 C 33.148841,283.15159 39.24851,257.94992 50.312574,243.81254 z M 536.45493,250.2446 C 543.88366,277.15913 566.74879,308.63556 551.43159,335.47436 C 546.44721,350.66062 532.20173,359.53588 537.73084,335.75981 C 539.32919,307.71959 531.27597,272.91081 536.06286,249.15412 L 536.45493,250.2446 z M 263.28504,288.34215 C 282.51337,317.65756 244.52942,307.7959 226.78176,309.61741 C 238.1088,304.60141 254.71447,286.66664 263.28504,288.34215 z M 326.06629,289.99139 C 335.50507,298.75069 371.96349,315.98071 340.11655,310.44928 C 318.61751,314.37824 306.58789,306.07219 322.5447,287.44802 L 326.06629,289.99139 z M 281.9215,316.10443 C 315.80881,306.99659 294.53894,340.10829 290.93737,357.61043 C 289.75062,345.2197 277.62158,323.68751 281.9215,316.10443 z M 184.01134,318.7982 C 187.74469,337.09704 148.67935,342.5761 134.89818,354.17134 C 111.56567,365.55352 86.689349,375.07418 60.537891,376.68669 C 34.857135,351.83888 71.020358,335.11628 93.620682,331.96846 C 123.12694,324.08505 153.65422,321.18138 184.01134,318.7982 z M 384.50452,320.1176 C 389.61697,323.73995 377.26894,318.84237 384.50452,320.1176 z M 398.90793,321.38202 C 443.1239,326.51593 489.45667,330.51222 528.63386,353.62958 C 538.96391,391.76884 496.17809,381.49967 476.30611,371.10904 C 448.0456,360.17677 421.89729,344.64344 396.10421,328.91357 C 393.51601,325.98521 395.11468,321.97061 398.90793,321.38202 z M 203.30751,332.26703 C 227.72727,353.07009 264.71284,370.58598 280.20641,394.90874 C 262.93503,412.3019 241.56903,425.95006 223.50819,442.94849 C 205.20907,458.95886 187.67677,475.97038 172.41166,494.93753 C 148.89686,486.37063 113.76917,464.1279 143.2293,439.75157 C 162.91673,405.20393 177.76059,368.20734 192.14762,331.22251 C 195.29635,327.04196 200.53018,328.93547 203.30751,332.26703 z M 386.48362,331.16754 C 400.18586,350.95872 404.22928,376.64387 415.28715,398.36989 C 425.20431,421.90616 436.33007,445.05076 450.41934,466.4056 C 434.12186,488.50845 401.66036,511.25603 387.04573,475.11066 C 360.70616,446.5202 330.78928,421.55644 300.72289,397.02737 C 310.13036,373.45503 344.5899,362.82508 363.91108,344.81642 C 371.25297,341.05885 379.09963,331.94719 386.48362,331.16754 z M 42.781023,362.72308 C 57.078154,387.91587 19.840772,365.01162 42.781023,362.72308 z M 542.33724,366.4064 C 556.06367,380.56137 523.55694,385.37945 539.8395,367.13493 C 539.57763,366.48977 542.65437,362.78508 542.33724,366.4064 z M 53.336189,383.50357 C 75.268719,404.32092 91.325113,433.37386 113.18698,455.63259 C 124.54299,476.44851 74.161701,435.968 63.852016,426.25618 C 50.708583,416.70395 54.964638,397.77227 53.336189,383.50357 z M 528.09876,391.41994 C 533.1255,424.20402 506.16623,442.7593 481.40591,457.52287 C 471.49835,466.88801 451.01521,473.12226 468.89427,456.9389 C 489.1958,435.80533 506.44563,411.83156 525.1851,389.27592 C 526.23268,388.092 530.2919,389.29239 528.09876,391.41994 z M 294.45576,401.04054 C 302.97032,444.77201 314.18465,490.50238 303.45686,534.78351 C 269.8116,555.70792 267.45319,510.26453 271.11797,487.87885 C 272.91786,458.43851 279.8073,429.65527 286.31949,400.98556 C 288.31716,397.74595 292.52433,397.64886 294.45576,401.04054 z M 180.49295,506.64717 C 207.67798,522.26301 246.52699,528.98157 268.55252,545.52857 C 242.42325,558.17886 217.83828,546.5235 198.29377,527.97033 C 194.69189,524.69972 159.29181,498.82287 180.49295,506.64717 z M 404.51536,507.63672 C 390.01309,525.27528 368.18947,538.74082 347.86304,550.23549 C 335.91524,557.74156 287.21939,542.66729 323.67227,537.66117 C 350.78566,529.02491 379.06974,516.04027 404.51536,507.63672 z M 292.69657,548.86784 C 304.93678,562.57073 267.95665,553.48912 288.01486,547.90629 L 290.34982,548.11682 L 292.69657,548.86784 z diff --git a/src/toys/sweep.cpp b/src/toys/sweep.cpp new file mode 100644 index 0000000..4f26b81 --- /dev/null +++ b/src/toys/sweep.cpp @@ -0,0 +1,89 @@ +#include <2geom/sweep-bounds.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class Sweep: public Toy { +public: + PointSetHandle hand; + unsigned count_a, count_b; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + std::vector<Rect> rects_a, rects_b; + cairo_set_source_rgb(cr, 0,0,0); + + for(unsigned i = 0; i < count_a; i++) + rects_a.emplace_back(hand.pts[i*2], hand.pts[i*2+1]); + + for(unsigned i = 0; i < count_b; i++) + rects_b.emplace_back(hand.pts[i*2 + count_a*2], hand.pts[i*2+1 + count_a*2]); + + { + std::vector<std::vector<unsigned> > res = sweep_bounds(rects_a); + cairo_set_line_width(cr,0.5); + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + for(unsigned i = 0; i < res.size(); i++) { + for(unsigned j = 0; j < res[i].size(); j++) { + draw_line_seg(cr, rects_a[i].midpoint(), rects_a[res[i][j]].midpoint()); + cairo_stroke(cr); + } + } + cairo_restore(cr); + }{ + std::vector<std::vector<unsigned> > res = sweep_bounds(rects_a, rects_b); + cairo_set_line_width(cr,0.5); + cairo_save(cr); + cairo_set_source_rgb(cr, 0.5, 0, 0.5); + for(unsigned i = 0; i < res.size(); i++) { + for(unsigned j = 0; j < res[i].size(); j++) { + draw_line_seg(cr, rects_a[i].midpoint(), rects_b[res[i][j]].midpoint()); + cairo_stroke(cr); + } + } + cairo_restore(cr); + } + cairo_set_line_width(cr,3); + cairo_set_source_rgba(cr,1,0,0,1); + for(unsigned i = 0; i < count_a; i++) + cairo_rectangle(cr, rects_a[i].left(), rects_a[i].top(), rects_a[i].width(), rects_a[i].height()); + cairo_stroke(cr); + + cairo_set_source_rgba(cr,0,0,1,1); + for(unsigned i = 0; i < count_b; i++) + cairo_rectangle(cr, rects_b[i].left(), rects_b[i].top(), rects_b[i].width(), rects_b[i].height()); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + bool should_draw_numbers() override { return false; } + public: + Sweep () { + count_a = 20; + count_b = 10; + for(unsigned i = 0; i < (count_a + count_b); i++) { + Point dim(uniform() * 90 + 10, uniform() * 90 + 10), + pos(uniform() * 500 + 50, uniform() * 500 + 50); + hand.pts.push_back(pos - dim/2); + hand.pts.push_back(pos + dim/2); + } + handles.push_back(&hand); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sweep()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sweeper-toy.cpp b/src/toys/sweeper-toy.cpp new file mode 100644 index 0000000..5ca28db --- /dev/null +++ b/src/toys/sweeper-toy.cpp @@ -0,0 +1,170 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> + + +#include <cstdlib> +#include <set> +#include <vector> +#include <algorithm> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> + +using namespace Geom; +using namespace std; + +#include "sweeper.cpp" + +double exp_rescale(double x){ return pow(10, x);} +std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));} + + +class SweeperToy: public Toy { + int nb_paths; + int nb_curves_per_path; + int degree; + + std::vector<PointSetHandle> paths_handles; + std::vector<Slider> sliders; + Sweeper sweeper; + + void drawTile( cairo_t *cr, unsigned idx , unsigned line_width=1){ + if (idx>=sweeper.tiles_data.size()) return; + Rect box; + box = sweeper.tiles_data[idx].fbox; + box[X].expandBy(1); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0., .5); + cairo_set_line_width (cr, line_width); + cairo_stroke(cr); + box = sweeper.tiles_data[idx].tbox; + box[Y].expandBy(1); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 0., 0., 1., .5); + cairo_set_line_width (cr, line_width); + cairo_stroke(cr); + + Sweeper::Tile tile = sweeper.tiles_data[idx]; + D2<SBasis> p = sweeper.paths[tile.path][tile.curve].toSBasis(); + Interval dom = Interval(tile.f,tile.t); + cairo_set_source_rgba (cr, 0., 1., .5, .8); + p = portion(p, dom); + cairo_d2_sb(cr, p); + cairo_set_line_width (cr, line_width); + cairo_stroke(cr); + } + + void drawTiles( cairo_t *cr ){ + for (unsigned i=0; i<sweeper.tiles_data.size(); i++){ + drawTile( cr, i ); + } + +// for (unsigned i=0; i<sweeper.vtxboxes.size(); i++){ +// cairo_rectangle(cr, sweeper.vtxboxes[i]); +// cairo_set_source_rgba (cr, 0., 0., 0, 1); +// cairo_set_line_width (cr, 1); +// cairo_stroke(cr); +// } + } + + void enlightTile( cairo_t *cr, unsigned idx){ + drawTile(cr, idx, 4); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + PathVector paths; + for (int i = 0; i < nb_paths; i++){ + paths_handles[i].pts.back()=paths_handles[i].pts.front(); + paths.push_back(Path(paths_handles[i].pts[0])); + for (unsigned j = 0; j+degree < paths_handles[i].size(); j+=degree){ + D2<SBasis> c = handles_to_sbasis(paths_handles[i].pts.begin()+j, degree); + paths[i].append(c); + } + paths[i].close(); + } + + //cairo_path(cr, paths); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + + double tol = exp_rescale(sliders[3].value()); + Rect tolbytol( Point(50,110), Point(50,110) ); + tolbytol.expandBy( tol ); + cairo_rectangle(cr, tolbytol); + cairo_stroke(cr); + + sweeper = Sweeper(paths,X, tol); + unsigned idx = (unsigned)(sliders[0].value()*(sweeper.tiles_data.size()-1)); + drawTiles(cr); + enlightTile(cr, idx); + + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + SweeperToy(int paths, int curves_in_path, int degree) : + nb_paths(paths), nb_curves_per_path(curves_in_path), degree(degree) { + for (int i = 0; i < nb_paths; i++){ + paths_handles.emplace_back(); + } + for(int i = 0; i < nb_paths; i++){ + for(int j = 0; j < (nb_curves_per_path*degree)+1; j++){ + paths_handles[i].push_back(uniform()*400, 100+ uniform()*300); + } + handles.push_back(&paths_handles[i]); + } + sliders.emplace_back(0.0, 1, 0, 0.0, "intersection chooser"); + sliders.emplace_back(0.0, 1, 0, 0.0, "ray chooser"); + sliders.emplace_back(0.0, 1, 0, 0.0, "area chooser"); + sliders.emplace_back(-5.0, 2, 0, 0.0, "tolerance chooser"); + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + handles.push_back(&(sliders[3])); + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + sliders[3].geometry(Point(50, 110), 250); + sliders[3].formatter(&exp_formatter); + } + + void first_time(int /*argc*/, char** /*argv*/) override { + + } +}; + +int main(int argc, char **argv) { + unsigned paths=10; + unsigned curves_in_path=3; + unsigned degree=1; + if(argc > 3) + sscanf(argv[3], "%d", °ree); + if(argc > 2) + sscanf(argv[2], "%d", &curves_in_path); + if(argc > 1) + sscanf(argv[1], "%d", &paths); + init(argc, argv, new SweeperToy(paths, curves_in_path, degree)); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sweeper.cpp b/src/toys/sweeper.cpp new file mode 100644 index 0000000..7dae586 --- /dev/null +++ b/src/toys/sweeper.cpp @@ -0,0 +1,1135 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> + +#include <cstdlib> +#include <cstdio> +#include <set> +#include <vector> +#include <algorithm> + +#include <limits.h> +#define NULL_IDX UINT_MAX + +#include <2geom/orphan-code/intersection-by-smashing.h> +#include "../2geom/orphan-code/intersection-by-smashing.cpp" + +using namespace Geom; +using namespace std; + + +/* +The sweeper class takes a PathVector as input and generates "events" to let clients construct the relevant graph. + +The basic strategy is the following: +The path is split into "tiles": a tile consists in 2 boxes related by a (monotonic) curve. + +The tiles are created at the very beginning, using a sweep, but *no care* is taken to topology +information at this step! All the boxes of all the tiles are then enlarged so that they are +either equal or disjoint. +[TODO: we should look for curves traversing boxes, split them and repeat the process...] + +The sweeper maintains a virtual sweepline, that is the limit of the "known area". The tiles can have 2 states: +open if they have one end in the known area, and one in the unknown, closed otherwise. +[TODO: open/close should belong to tiles pointers, not tiles...] + +The sorted list of open tiles intersecting the sweep line is called the "context". +*!*WARNING*!*: because the sweep line is not straight, closed tiles can still be in the context!! +they can only be removed once the end of the last box is reached. + +The events are changes in the context when the sweep line crosses boxes. +They are obtained by sorting the tiles according to one or the other of theire end boxes depending +on the open/close state. + +A "big" event happens when the sweep line reaches a new 'box'. After such a "big" event, the sweep +line goes round the new box along it's 3 other sides. +N.B.: in an ideal world, all tiles ending at one box would be on one side, all the tiles starting +there on the other. Unfortunately, because we have boxes as vertices, things are not that nice: +open/closed tiles can appear in any order around a vertex, even in the monotonic case(!). Morover, +our fat vertices have a non zero "duration", during which many things can happen: this is why we +have to keep closed edges in the context until both ends of theire boxes are reached... + + +To keep things uniform, such "big" events are split into elementary ones: opening/closing of a single +edge. One such event is generated for each tile around the current 'box', in CCW order (geometrically, +the sweepline is deformed in a neighborhood of the box to go round it for a certain amount, enter the +box and come back inside the box; the piece inside the box is a "virtual edge" that is not added for +good but that we keep track of). The event knows if it's the last one in such a sequence, so that the +client knows when to do the additional work required to "close" the vertex construction. Hmmm. It's +hard to explain the moves without a drawing here...(see sweep.svg in the doc dir). There are + +*Closings: insert a new the relevant tile in the context with a "exit" flag. + +*Openings: insert a new the relevant tile in the context with a "entry" flag. + +At the end of a box, the relevant exit/entries are purged from the context. + + +N.B. I doubt we can do boolops without building the full graph, i.e. having different clients to obtain +different outputs. So splitting sweeper/grpah builder is maybe not so relevant w/r to functionality +(only code organization). +*/ + + +//TODO: decline intersections algorithms for each kind of curves... +//TODO: write an intersector that can work on sub domains. +//TODO: factor computation of derivative and the like out. +std::vector<SmashIntersection> monotonic_smash_intersect( Curve const &a, Interval a_dom, + Curve const &b, Interval b_dom, double tol){ + std::vector<SmashIntersection> result; + D2<SBasis> asb = a.toSBasis(); + asb = portion( asb, a_dom ); + D2<SBasis> bsb = b.toSBasis(); + bsb = portion( bsb, b_dom ); + result = monotonic_smash_intersect(asb, bsb, tol ); + for (auto & i : result){ + i.times[X] *= a_dom.extent(); + i.times[X] += a_dom.min(); + i.times[Y] *= b_dom.extent(); + i.times[Y] += b_dom.min(); + } + return result; +} + + + +class Sweeper{ +public: + + //--------------------------- + // utils... + //--------------------------- + + //near predicate utilized in process_splits + template<typename T> + struct NearPredicate { + double tol; + NearPredicate(double eps):tol(eps){} + NearPredicate(){tol = EPSILON;}//??? + bool operator()(T x, T y) { return are_near(x, y, tol); } }; + + // ensures that f and t are elements of a vector, sorts and uniqueifies + // also asserts that no values fall outside of f and t + // if f is greater than t, the sort is in reverse + void process_splits(std::vector<double> &splits, double f, double t, double tol=EPSILON) { + //splits.push_back(f); + //splits.push_back(t); + std::sort(splits.begin(), splits.end()); + std::vector<double>::iterator end = std::unique(splits.begin(), splits.end(), NearPredicate<double>(tol)); + splits.resize(end - splits.begin()); + + //remove any splits which fall outside t / f + while(!splits.empty() && splits.front() < f+tol) splits.erase(splits.begin()); + splits.insert(splits.begin(), f); + //splits[0] = f; + while(!splits.empty() && splits.back() > t-tol) splits.erase(splits.end() - 1); + splits.push_back(t); + //splits.back() = t; + } + + struct IntersectionMinTimeOrder { + unsigned which; + IntersectionMinTimeOrder (unsigned idx) : which(idx) {} + bool operator()(SmashIntersection const &a, SmashIntersection const &b) const { + return a.times[which].min() < b.times[which].min(); + } + }; + + // ensures that f and t are elements of a vector, sorts and uniqueifies + // also asserts that no values fall outside of f and t + // if f is greater than t, the sort is in reverse + std::vector<std::pair<Interval, Rect> > + process_intersections(std::vector<SmashIntersection> &inters, unsigned which, unsigned tileidx) { + std::vector<std::pair<Interval, Rect> > result; + std::pair<Interval, Rect> apair; + Interval dom ( tiles_data[tileidx].f, tiles_data[tileidx].t ); + apair.first = Interval( dom.min() ); + apair.second = tiles_data[tileidx].fbox; + result.push_back( apair ); + + std::sort(inters.begin(), inters.end(), IntersectionMinTimeOrder(which) ); + for (auto & inter : inters){ + if ( !inter.times[which].intersects( dom ) )//this should never happen. + continue; + if ( result.back().first.intersects( inter.times[which] ) ){ + result.back().first.unionWith( inter.times[which] ); + result.back().second.unionWith( inter.bbox ); + }else{ + apair.first = inter.times[which]; + apair.second = inter.bbox; + result.push_back( apair ); + } + } + apair.first = Interval( dom.max() ); + apair.second = tiles_data[tileidx].tbox; + if ( result.size() > 1 && result.back().first.intersects( apair.first ) ){ + result.back().first.unionWith( apair.first ); + result.back().second.unionWith( apair.second ); + }else{ + result.push_back( apair ); + } + return result; + } + + + //--------------------------- + // Tiles. + //--------------------------- + + //A tile is a "light edge": just two boxes, joint by a curve. + //it is open iff intersected by the sweepline. + class Tile{ + public: + unsigned path; + unsigned curve; + double f; + double t; + Rect fbox, tbox; + bool reversed;//with respect to sweep direction. Flip f/t instead? + bool open;//means sweepline currently cuts it (i.e. one end in the known area, the other in the unknown). + int state;//-1: both ends in unknown area, 0:one end in each, 1: both in known area. + //Warning: we can not delete a tile immediately when it's past(=closed again), only when the end of it's tbox is!. + Rect bbox(){Rect b = fbox; b.unionWith(tbox); return b;} + Point min(){return ( bbox().min() ); } + Point max(){return ( bbox().max() ); } +// Rect cur_box() const {return ((open)^(reversed) ) ? tbox : fbox; } + Rect cur_box() const { return ((state>=0)^(reversed) ) ? tbox : fbox; } + Rect first_box() const {return ( reversed ) ? tbox : fbox; } + Rect last_box() const {return ( reversed ) ? fbox : tbox; } + }; + + D2<SBasis> tileToSB(Tile const &tile){ + //TODO: don't convert each time!!!!!! + assert( tile.path < paths.size() ); + assert( tile.curve < paths[tile.path].size() ); + D2<SBasis> c = paths[tile.path][tile.curve].toSBasis(); + c = portion( c, Interval( tile.f, tile.t ) ); + return c; + } + + //SweepOrder for Rects or Tiles. + class SweepOrder{ + public: + Dim2 dim; + SweepOrder(Dim2 d) : dim(d) {} + bool operator()(const Rect &a, const Rect &b) const { + return Point::LexLessRt(dim)(a.min(), b.min()); + } + bool operator()(const Tile &a, const Tile &b) const { + return Point::LexLessRt(dim)(a.cur_box().min(), b.cur_box().min()); + } + }; + + class PtrSweepOrder{ + public: + Dim2 dim; + std::vector<Tile>::iterator const begin; + PtrSweepOrder(std::vector<Tile>::iterator const beg, Dim2 d) : dim(d), begin(beg){} + bool operator()(const unsigned a, const unsigned b) const { + return Point::LexLessRt(dim)((begin+a)->cur_box().min(), (begin+b)->cur_box().min()); + } + }; + + + //--------------------------- + // Vertices. + //--------------------------- + + //A ray is nothing but an edge ending or starting at a given vertex, + some info about when/where it exited a "separating" box; + struct Ray{ + public: + unsigned tile; + bool centrifuge;//true if the intrinsic orientation of curve points away from the vertex. + //exit info: + unsigned exit_side;//0:y=min; 1:x=max; 2:y=max; 3:x=min. + double exit_place; //x or y value on the exit line. + double exit_time; //exit time on curve. + Ray(){tile = NULL_IDX; exit_side = 4;} + Ray(unsigned tile_idx, unsigned s, double p, double t){ + tile = tile_idx; + exit_side =s; + exit_place = p; + exit_time = t; + } + Ray(unsigned tile_idx, bool outward){ + tile = tile_idx; + exit_side = 4; + centrifuge = outward; + exit_time = (centrifuge) ? 2 : -1 ; + } + void setExitInfo( unsigned side, double place, double time){ + exit_side = side; + exit_place = place; + exit_time = time; + } + }; + + class FatVertex : public Rect{ + public: + std::vector<Ray> rays; + FatVertex(const Rect &r, unsigned const tile, bool centrifuge) : Rect(r){ + rays.push_back( Ray(tile, centrifuge) ); + } + FatVertex(Rect r) : Rect(r){} + FatVertex() : Rect(){} + void erase(unsigned from, unsigned to){ + unsigned size = to-from; + from = from % rays.size(); + to = from + size; + + if (to >= rays.size() ){ + to = to % rays.size(); + rays.erase( rays.begin()+from, rays.end() ); + rays.erase( rays.begin(), rays.begin()+to ); + }else{ + rays.erase( rays.begin()+from, rays.begin()+to ); + } + + } + }; + + //--------------------------- + // Context related stuff. + //--------------------------- + + class Event{ + public: + bool opening;//true means an edge is added, otherwise an edge is removed from context. + unsigned tile;//which tile to open/close. + unsigned insert_at;//where to insert the next tile in the context. + //unsigned erase_at;//idx of the tile to close in the context. = context.find(tile). + bool to_be_continued; + bool empty(){ + return tile==NULL_IDX; + } + Event(){ + opening = false; + insert_at = 0; + //erase_at = 0; + tile = NULL_IDX; + to_be_continued = false; + } + }; + + void printEvent(Event const &e){ + std::printf("Event: "); + std::printf("%s, ", e.opening?"opening":"closing"); + std::printf("insert_at:%u, ", e.insert_at); + //std::printf("erase_at:%u, ", e.erase_at); + std::printf("tile:%u.\n", e.tile); + } + + class Context : public std::vector<std::pair<unsigned,bool> >{//first = tile, second = true if it's a birth (+). + public: + Point last_pos; + FatVertex pending_vertex; + Event pending_event; + + unsigned find(unsigned const tile, bool positive_only=false){ + for (unsigned i=0; i<size(); i++){ + if ( (*this)[i].first == tile ){ + if ( (*this)[i].second || !positive_only ) return i; + } + } + return (*this).size(); + } + }; + void printContext(){ + std::printf("context:["); + for (unsigned i=0; i<context.size(); i++){ + unsigned tile = context[i].first; + assert( tile<tiles_data.size() ); + std::printf(" %s%u%s", (tiles_data[ tile ].reversed)?"-":"+", tile, (context[i].second)?"o":"c"); +// assert( context[i].second || !tiles_data[ tile ].open); + assert( context[i].second || tiles_data[ tile ].state==1); + } + std::printf("]\n"); + } + + + + //---- + //This is the heart of it all!! Take particular care to non linear sweep line... + //---- + //Given a point on the sweep line, (supposed to be the min() of a vertex not yet connected to the already known part), + //find the first edge "after" it in the context. Pretty tricky atm :-( + //TODO: implement this as a lower_bound (?). + + unsigned contextRanking(Point const &pt){ + +// std::printf("contextRanking:------------------------------------\n"); + + unsigned rank = context.size(); + std::vector<unsigned> unmatched_closed_tiles = std::vector<unsigned>(); + +// std::printf("Scan context.\n"); + + for (unsigned i=0; i<context.size(); i++){ + + unsigned tile_idx = context[i].first; + assert( tile_idx < tiles_data.size() ); +// std::printf("testing %u (e=%u),", i, tile_idx); + + Tile tile = tiles_data[tile_idx]; + assert( tile.state >= 0 ); + + //if the tile is open (i.e. not both ends in the known area) and point is below/above the tile's bbox: + if ( tile.state == 0 ){ +// std::printf("opened tile, "); + if (pt[1-dim] < tile.min()[1-dim] ) { +// printContext(); +// std::printf("below bbox %u!\n", i); + rank = i; + break; + } + if (pt[1-dim] > tile.max()[1-dim] ){ +// std::printf("above bbox %u!\n", i); + continue; + } + + //TODO: don't convert each time!!!!!! + D2<SBasis> c = tileToSB( tile ); + + std::vector<double> times = roots(c[dim]-pt[dim]); + if (times.size()==0){ + assert( tile.first_box()[dim].contains(pt[dim]) ); + if ( pt[1-dim] < tile.first_box()[1-dim].min() ){ +// std::printf("open+hit box %u!\n", i); + rank = i; + break; + }else{ + continue; + } + } + if ( pt[1-dim] < c[1-dim](times.front()) ){ +// std::printf("open+hit curve %u!\n", i); + rank = i; + break; + } + } + +// std::printf("closed tile, "); + + + //At this point, the tile is closed (i.e. both ends are in the known area) + //Such tiles do 'nested parens' like travels in the unknown area. + //We are interested in the second occurrence only (to give a chance to open tiles to exist in between). + if ( unmatched_closed_tiles.size()==0 || tile_idx != unmatched_closed_tiles.back() ){ + unmatched_closed_tiles.push_back( tile_idx ); +// std::printf("open paren %u\n",tile_idx); + continue; + } + unmatched_closed_tiles.pop_back(); + +// std::printf("close paren, "); + + if ( !tile.bbox().contains( pt ) ){ + continue; + } + +// std::printf("in bbox, "); + + //At least one of fbox[dim], tbox[dim] has to contain the pt[dim]: assert it? + + //Find intersection with the hline(vline if dim=Y) through the point + double hit_place; + //TODO: don't convert each time!!!!!! + D2<SBasis> c = tileToSB( tile ); + std::vector<double> times = roots(c[1-dim]-pt[1-dim]); + if ( times.size()>0 ){ +// std::printf("hit curve,"); + hit_place = c[dim](times.front()); + }else{ +// std::printf("hit box, "); + //if there was no intersection, the line went through the first_box + assert( tile.first_box()[1-dim].contains(pt[1-dim]) ); + continue; + } + + if ( pt[dim] > hit_place ){ +// std::printf("wrong side, "); + continue; + } +// std::printf("good side, "); + rank = i; + break; + } + +// std::printf("rank %u.\n", rank); +// printContext(); + assert( rank<=tiles_data.size() ); + return rank; + } + + //TODO: optimize this. + //it's done the slow way for debugging purpose... + void purgeDeadTiles(){ + //std::printf("purge "); + //printContext(); + for (unsigned i=0; i<context.size(); i++){ + assert( context[i].first<tiles_data.size() ); + Tile tile = tiles_data[context[i].first]; + if (tile.state==1 && Point::LexLessRt(dim)( tile.fbox.max(), context.last_pos ) && Point::LexLessRt(dim)( tile.tbox.max(), context.last_pos ) ){ +// if (!tile.open && Point::LexLessRt(dim)( tile.fbox.max(), context.last_pos ) && Point::LexLessRt(dim)( tile.tbox.max(), context.last_pos ) ){ + unsigned j; + for (j=i+1; j<context.size() && context[j].first != context[i].first; j++){} + assert ( j < context.size() ); + if ( context[j].first == context[i].first){ + assert ( context[j].second == !context[i].second ); + context.erase(context.begin()+j); + context.erase(context.begin()+i); +// printContext(); + i--; + } + } + } + return; + } + + void applyEvent(Event event){ +// std::printf("Apply event : "); + if(event.empty()){ +// std::printf("empty event!\n"); + return; + } + +// printEvent(event); +// std::printf(" old "); +// printContext(); + + assert ( context.begin() + event.insert_at <= context.end() ); + + if (!event.opening){ +// unsigned idx = event.erase_at; +// assert( idx == context.find(event.tile) ); +// assert( context[idx].first == event.tile); + tiles_data[event.tile].open = false; + tiles_data[event.tile].state = 1; + //context.erase(context.begin()+idx); + unsigned idx = event.insert_at; + context.insert(context.begin()+idx, std::pair<unsigned, bool>(event.tile, false) ); + }else{ + unsigned idx = event.insert_at; + tiles_data[event.tile].open = true; + tiles_data[event.tile].state = 0; + context.insert(context.begin()+idx, std::pair<unsigned, bool>(event.tile, true) ); + sortTiles(); + } + context.last_pos = context.pending_vertex.min(); + context.last_pos[1-dim] = context.pending_vertex.max()[1-dim]; + +// std::printf(" new "); +// printContext(); +// std::printf("\n"); + //context.pending_event = Event();is this a good idea? + } + + + + //--------------------------- + // Sweeper. + //--------------------------- + + PathVector paths; + std::vector<Tile> tiles_data; + std::vector<unsigned> tiles; + std::vector<Rect> vtxboxes; + Context context; + double tol; + Dim2 dim; + + + //------------------------------- + //-- Tiles preparation. + //------------------------------- + + //split input paths into monotonic pieces... + void createMonotonicTiles(){ + for ( unsigned i=0; i<paths.size(); i++){ + for ( unsigned j=0; j<paths[i].size(); j++){ + //find the points where slope is 0°, 45°, 90°, 135°... + D2<SBasis> deriv = derivative( paths[i][j].toSBasis() ); + std::vector<double> splits0 = roots( deriv[X] ); + std::vector<double> splits90 = roots( deriv[Y] ); + std::vector<double> splits45 = roots( deriv[X]- deriv[Y] ); + std::vector<double> splits135 = roots( deriv[X] + deriv[Y] ); + std::vector<double> splits; + splits.insert(splits.begin(), splits0.begin(), splits0.end() ); + splits.insert(splits.begin(), splits90.begin(), splits90.end() ); + splits.insert(splits.begin(), splits45.begin(), splits45.end() ); + splits.insert(splits.begin(), splits135.begin(), splits135.end() ); + process_splits(splits,0,1); + + for(unsigned k = 1; k < splits.size(); k++){ + Tile tile; + tile.path = i; + tile.curve = j; + tile.f = splits[k-1]; + tile.t = splits[k]; + //TODO: use meaningful tolerance here!! + Point fp = paths[i][j].pointAt(tile.f); + Point tp = paths[i][j].pointAt(tile.t); + tile.fbox = Rect(fp, fp ); + tile.tbox = Rect(tp, tp ); + tile.open = false; + tile.state = -1; + tile.reversed = Point::LexLessRt(dim)(tp, fp); + + tiles_data.push_back(tile); + } + } + } + std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) ); + } + + void splitTile(unsigned i, double t, double tolerance=0, bool sort = true){ + assert( i<tiles_data.size() ); + Tile newtile = tiles_data[i]; + assert( newtile.f < t && t < newtile.t ); + newtile.f = t; + //newtile.fbox = fatPoint(paths[newtile.path][newtile.curve].pointAt(t), tolerance ); + Point p = paths[newtile.path][newtile.curve].pointAt(t); + newtile.fbox = Rect(p, p); + newtile.fbox.expandBy( tolerance ); + tiles_data[i].tbox = newtile.fbox; + tiles_data[i].t = t; + tiles_data.insert(tiles_data.begin()+i+1, newtile); + if (sort) + std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); + } + void splitTile(unsigned i, SmashIntersection inter, unsigned which,bool sort = true){ + double t = inter.times[which].middle(); + assert( i<tiles_data.size() ); + Tile newtile = tiles_data[i]; + assert( newtile.f < t && t < newtile.t ); + newtile.f = t; + newtile.fbox = inter.bbox; + tiles_data[i].tbox = newtile.fbox; + tiles_data[i].t = t; + tiles_data.insert(tiles_data.begin()+i+1, newtile); + if (sort) + std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); + } +#if 0 + void splitTile(unsigned i, std::vector<double> const ×, double tolerance=0, bool sort = true){ + if ( times.size()<3 ) return; + assert( i<tiles_data.size() ); + std::vector<Tile> pieces ( times.size()-2, tiles_data[i] ); + Rect prevbox = tiles_data[i].fbox; + for (unsigned k=0; k < times.size()-2; k++){ + pieces[k].f = times[k]; + pieces[k].t = times[k+1]; + pieces[k].fbox = prevbox; + //TODO: use relevant precision here. + prevbox = fatPoint(paths[tiles_data[i].path][tiles_data[i].curve].pointAt(times[k+1]), tolerance ); + pieces[k].tbox = prevbox; + } + tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end() ); + unsigned newi = i + times.size()-2; + assert( newi<tiles_data.size() ); + assert( newi>=1 ); + tiles_data[newi].f = tiles_data[newi-1].t; + tiles_data[newi].fbox = tiles_data[newi-1].tbox; + + if (sort) + std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) ); + } +#else + void splitTile(unsigned i, std::vector<std::pair<Interval,Rect> > const &cuts, bool sort = true){ + assert ( cuts.size() >= 2 ); + assert( i<tiles_data.size() ); + std::vector<Tile> pieces ( cuts.size()-1, tiles_data[i] ); + for (unsigned k=1; k+1 < cuts.size(); k++){ + pieces[k-1].t = cuts[k].first.middle(); + pieces[k ].f = cuts[k].first.middle(); + pieces[k-1].tbox = cuts[k].second; + pieces[k ].fbox = cuts[k].second; + } + pieces.front().fbox.unionWith( cuts[0].second ); + pieces.back().tbox.unionWith( cuts.back().second ); + + tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end()-1 ); + unsigned newi = i + cuts.size()-2; + assert( newi < tiles_data.size() ); + tiles_data[newi] = pieces.back(); + + if (sort) + std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) ); + } +#endif + + //TODO: maybe not optimal. For a fully optimized sweep, it would be nice to have + //an efficient way to way find *only the first* intersection (in sweep direction)... + void splitIntersectingTiles(){ + //make sure it is sorted, but should be ok. (remove sorting at the end of monotonic tiles creation? + std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) ); + +// std::printf("\nFind intersections: tiles_data.size():%u\n", tiles_data.size() ); + + for (unsigned i=0; i+1<tiles_data.size(); i++){ + //std::printf("\ni=%u (%u([%f,%f]))\n", i, tiles_data[i].curve, tiles_data[i].f, tiles_data[i].t ); + std::vector<SmashIntersection> inters_on_i; + for (unsigned j=i+1; j<tiles_data.size(); j++){ + //std::printf(" j=%u (%u)\n", j,tiles_data[j].curve ); + if ( Point::LexLessRt(dim)(tiles_data[i].max(), tiles_data[j].min()) ) break; + + unsigned pi = tiles_data[i].path; + unsigned ci = tiles_data[i].curve; + unsigned pj = tiles_data[j].path; + unsigned cj = tiles_data[j].curve; + std::vector<SmashIntersection> intersections; + + intersections = monotonic_smash_intersect(paths[pi][ci], Interval(tiles_data[i].f, tiles_data[i].t), + paths[pj][cj], Interval(tiles_data[j].f, tiles_data[j].t), tol ); + inters_on_i.insert( inters_on_i.end(), intersections.begin(), intersections.end() ); + std::vector<std::pair<Interval, Rect> > cuts = process_intersections(intersections, 1, j); + +// std::printf(" >|%u/%u|=%u. times_j:%u", i, j, crossings.size(), times_j.size() ); + + splitTile(j, cuts, false); + j += cuts.size()-2; + } + + //process_splits(times_i, tiles_data[i].f, tiles_data[i].t); + //assert(times_i.size()>=2); + //splitTile(i, times_i, tol, false); + //i+=times_i.size()-2; + std::vector<std::pair<Interval, Rect> > cuts_on_i = process_intersections(inters_on_i, 0, i); + splitTile(i, cuts_on_i, false); + i += cuts_on_i.size()-2; + //std::printf("new i:%u, tiles_data: %u\n",i ,tiles_data.size()); + //std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); + std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); + } + //this last sorting should be useless!! + std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) ); + } + + void sortTiles(){ + std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) ); + } + + + //------------------------------- + //-- Vertices boxes cookup. + //------------------------------- + + void fuseInsert(const Rect &b, std::vector<Rect> &boxes, Dim2 dim){ + //TODO: this can be optimized... + for (unsigned i=0; i<boxes.size(); i++){ + if ( Point::LexLessRt(dim)( b.max(), boxes[i].min() ) ) break; + if ( b.intersects( boxes[i] ) ){ + Rect bigb = b; + bigb.unionWith( boxes[i] ); + boxes.erase( boxes.begin()+i ); + fuseInsert( bigb, boxes, dim); + return; + } + } + std::vector<Rect>::iterator pos = std::lower_bound(boxes.begin(), boxes.end(), b, SweepOrder(dim) ); + boxes.insert( pos, b ); + } + + //debug only!! + bool isContained(Rect b, std::vector<Rect> const &boxes ){ + for (const auto & boxe : boxes){ + if ( boxe.contains(b) ) return true; + } + return false; + } + + //Collect vertex boxes. Fuse overlapping ones. + //NB: enlarging a vertex may create intersection with already scanned ones... + std::vector<Rect> collectBoxes(){ + std::vector<Rect> ret; + for (auto & i : tiles_data){ + fuseInsert(i.fbox, ret, dim); + fuseInsert(i.tbox, ret, dim); + } + return ret; + } + + //enlarge tiles ends to match the vertices bounding boxes. + //remove edges fully contained in one vertex bbox. + void enlargeTilesEnds(const std::vector<Rect> &boxes ){ + for (unsigned i=0; i<tiles_data.size(); i++){ + std::vector<Rect>::const_iterator f_it; + f_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].fbox, SweepOrder(dim) ); + if ( f_it==boxes.end() ) f_it--; + while (!(*f_it).contains(tiles_data[i].fbox) && f_it != boxes.begin()){ + f_it--; + } + assert( (*f_it).contains(tiles_data[i].fbox) ); + tiles_data[i].fbox = *f_it; + + std::vector<Rect>::const_iterator t_it; + t_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].tbox, SweepOrder(dim) ); + if ( t_it==boxes.end() ) t_it--; + while (!(*t_it).contains(tiles_data[i].tbox) && t_it != boxes.begin()){ + t_it--; + } + assert( (*t_it).contains(tiles_data[i].tbox) ); + tiles_data[i].tbox = *t_it; + + //NB: enlarging the ends may swapp their sweep order!!! + tiles_data[i].reversed = Point::LexLessRt(dim)( tiles_data[i].tbox.min(), tiles_data[i].fbox.min()); + + if ( f_it==t_it ){ + tiles_data.erase(tiles_data.begin()+i); + i-=1; + } + } + } + + //Make sure tiles stop at vertices. Split them if needed. + //Returns true if at least one tile was split. + bool splitTilesThroughFatPoints(std::vector<Rect> &boxes ){ + + std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) ); + std::sort(boxes.begin(), boxes.end(), SweepOrder(dim) ); + + bool result = false; + for (unsigned i=0; i<tiles_data.size(); i++){ + for (auto & boxe : boxes){ + if ( Point::LexLessRt(dim)( tiles_data[i].max(), boxe.min()) ) break; + if ( Point::LexLessRt(dim)( boxe.max(), tiles_data[i].min()) ) continue; + if ( !boxe.intersects( tiles_data[i].bbox() ) ) continue; + if ( tiles_data[i].fbox.intersects( boxe ) ) continue; + if ( tiles_data[i].tbox.intersects( boxe ) ) continue; + + //at this point box[k] intersects the curve bbox away from the fbox and tbox. + + D2<SBasis> c = tileToSB( tiles_data[i] ); +//----------> use level-set!! + for (unsigned corner=0; corner<4; corner++){ + unsigned D = corner % 2; + double val = boxe.corner(corner)[D]; + std::vector<double> times = roots( c[D] - val ); + if ( times.size()>0 ){ + double t = lerp( times.front(), tiles_data[i].f, tiles_data[i].t ); + double hit_place = c[1-D](times.front()); + if ( boxe[1-D].contains(hit_place) ){ + result = true; + //taking a point on the boundary is dangerous!! + //Either use >0 tolerance here, or find 2 intersection points and split in between. + splitTile( i, t, tol, false); + break; + } + } + } + } + } + return result; + } + + + + + + //TODO: rewrite all this!... + //------------------------------------------------------------------------------------------- + //------------------------------------------------------------------------------------------- + //------------------------------------------------------------------------------------------- + //------------------------------- + //-- ccw Sorting of rays around a vertex. + //------------------------------- + //------------------------------------------------------------------------------------------- + //------------------------------------------------------------------------------------------- + //------------------------------------------------------------------------------------------- + //returns an (infinite) rect around "a" separating it from "b". Nota: 3 sides are infinite! + //TODO: place the cut where there is most space... + OptRect separate(Rect const &a, Rect const &b){ + Rect ret ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) ); + double gap = 0; + unsigned dir = 4; + if (b[X].min() - a[X].max() > gap){ + gap = b[X].min() - a[X].max(); + dir = 0; + } + if (a[X].min() - b[X].max() > gap){ + gap = a[X].min() - b[X].max(); + dir = 1; + } + if (b[Y].min() - a[Y].max() > gap){ + gap = b[Y].min() - a[Y].max(); + dir = 2; + } + if (a[Y].min() - b[Y].max() > gap){ + gap = a[Y].min() - b[Y].max(); + dir = 3; + } + switch (dir) { + case 0: ret[X].setMax(( a.max()[X] + b.min()[X] )/ 2); break; + case 1: ret[X].setMin(( b.max()[X] + a.min()[X] )/ 2); break; + case 2: ret[Y].setMax(( a.max()[Y] + b.min()[Y] )/ 2); break; + case 3: ret[Y].setMin(( b.max()[Y] + a.min()[Y] )/ 2); break; + case 4: return OptRect(); + } + return OptRect(ret); + } + + //Find 4 lines (returned as a Rect sides) that cut all the rays (=edges). *!* some side might be infinite. + OptRect isolateVertex(Rect const &box){ + OptRect sep ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) ); + //separate this vertex from the others. Find a better way. + for (auto & vtxboxe : vtxboxes){ + if ( Point::LexLessRt(dim)( sep->max(), vtxboxe.min() ) ){ + break; + } + if ( vtxboxe!=box ){//&& !vtxboxes[i].intersects(box) ){ + OptRect sepi = separate(box, vtxboxe); + if ( sep && sepi ){ + sep = intersect(*sep, *sepi); + }else{ + std::cout<<"box="<<box<<"\n"; + std::cout<<"vtxboxes[i]="<<vtxboxe<<"\n"; + assert(sepi); + } + } + } + if (!sep) THROW_EXCEPTION("Invalid intersection data."); + return sep; + } + + //TODO: argh... rewrite to have "dim"=min first place. + struct ExitPoint{ + public: + unsigned side; //0:y=min; 1:x=max; 2:y=max; 3:x=min. + double place; //x or y value on the exit line. + unsigned ray_idx; + double time; //exit time on curve. + ExitPoint(){} + ExitPoint(unsigned s, double p, unsigned r, double t){ + side =s; + place = p; + ray_idx = r; + time = t; + } + }; + + + class ExitOrder{ + public: + bool operator()(Ray a, Ray b) const { + if ( a.exit_side < b.exit_side ) return true; + if ( a.exit_side > b.exit_side ) return false; + if ( a.exit_side <= 1) { + return ( a.exit_place < b.exit_place ); + } + return ( a.exit_place > b.exit_place ); + } + }; + + void printRay(Ray const &r){ + std::printf("Ray: tile=%u, centrifuge=%u, side=%u, place=%f\n", + r.tile, r.centrifuge, r.exit_side, r.exit_place); + } + void printVertex(FatVertex const &v){ + std::printf("Vertex: [%f,%f]x[%f,%f]\n", v[X].min(),v[X].max(),v[Y].min(),v[Y].max() ); + for (const auto & ray : v.rays){ + printRay(ray); + } + } + + //TODO: use a partial order on input coming from the context + Try quadrant order just in case it's enough. + //TODO: use monotonic assumption. + void sortRays( FatVertex &v ){ + OptRect sep = isolateVertex(v); + + for (unsigned i=0; i < v.rays.size(); i++){ + v.rays[i].centrifuge = (tiles_data[ v.rays[i].tile ].fbox == v); + v.rays[i].exit_time = v.rays[i].centrifuge ? 1 : 0 ; + } + + for (unsigned i=0; i < v.rays.size(); i++){ + //TODO: don't convert each time!!! + assert( v.rays[i].tile < tiles_data.size() ); + D2<SBasis> c = tileToSB( tiles_data[ v.rays[i].tile ] ); + + for (unsigned side=0; side<4; side++){//scan X or Y direction, on level min or max... + double level = sep->corner( side )[1-side%2]; + if (level != infinity() && level != -infinity() ){ + std::vector<double> times = roots(c[1-side%2]-level); + if ( times.size() > 0 ) { + double t; + assert( v.rays[i].tile < tiles_data.size() ); + if (tiles_data[ v.rays[i].tile ].fbox == v){ + t = times.front(); + if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time > t ){ + v.rays[i].setExitInfo( side, c[side%2](t), t); + } + }else{ + t = times.back(); + if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time < t ){ + v.rays[i].setExitInfo( side, c[side%2](t), t); + } + } + } + } + } + } + + //Rk: at this point, side == 4 means the edge is contained in the intersection box (?)...; + std::sort( v.rays.begin(), v.rays.end(), ExitOrder() ); + } + + + //------------------------------- + //-- initialize all data. + //------------------------------- + + Sweeper(){} + Sweeper(PathVector const &input_paths, Dim2 sweep_dir, double tolerance=1e-5){ + paths = input_paths;//use a ptr... + dim = sweep_dir; + tol = tolerance; + + //split paths into monotonic tiles + createMonotonicTiles(); + + //split at tiles intersections + splitIntersectingTiles(); + + //handle overlapping end boxes/and tiles traversing boxes. + do{ + vtxboxes = collectBoxes(); + }while ( splitTilesThroughFatPoints(vtxboxes) ); + + enlargeTilesEnds(vtxboxes); + + //now create the pointers to the tiles. + tiles = std::vector<unsigned>(tiles_data.size(), 0); + for (unsigned i=0; i<tiles_data.size(); i++){ + tiles[i] = i; + } + sortTiles(); + + //initialize the context. + if (tiles_data.size()>0){ + context.clear(); + context.last_pos = tiles_data[tiles.front()].min(); + context.last_pos[dim] -= 1; + context.pending_vertex = FatVertex(); + context.pending_event = Event(); + } + +// std::printf("Sweeper initialized (%u tiles)\n", tiles_data.size()); + } + + + //------------------------------- + //-- Event walk. + //------------------------------- + + + Event getNextEvent(){ +// std::printf("getNextEvent():\n"); + +// std::printf("initial contex:\n"); +// printContext(); + Event old_event = context.pending_event, event; +// std::printf("apply old event\n"); + applyEvent(context.pending_event); +// printContext(); + + if (context.pending_vertex.rays.size()== 0){ +// std::printf("cook up a new vertex\n"); + + //find the edges at the next vertex. + //TODO: implement this as a lower bound!! + std::vector<unsigned>::iterator low, high; + //Warning: bad looking test, but make sure we advance even in case of 0 width boxes... + for ( low = tiles.begin(); low != tiles.end() && + ( tiles_data[*low].state==1 || Point::LexLessRt(dim)(tiles_data[*low].cur_box().min(), context.last_pos) ); low++){} + + if ( low == tiles.end() ){ +// std::printf("no more event found\n"); + return(Event()); + } + Rect pos = tiles_data[ *low ].cur_box(); + context.last_pos = pos.min(); + context.last_pos[1-dim] = pos.max()[1-dim]; + +// printContext(); +// std::printf("purgeDeadTiles\n"); + purgeDeadTiles(); +// printContext(); + + FatVertex v(pos); + high = low; + do{ +// v.rays.push_back( Ray(*high, !tiles_data[*high].open) ); + v.rays.push_back( Ray(*high, tiles_data[*high].state!=0) ); + high++; + }while( high != tiles.end() && tiles_data[ *high ].cur_box()==pos ); + +// std::printf("sortRays\n"); + sortRays(v); + + //Look for an opened tile + unsigned i=0; + + for( i=0; i<v.rays.size(); ++i){assert( v.rays[i].tile<tiles_data.size() );} +// for( i=0; i<v.rays.size() && !tiles_data[ v.rays[i].tile ].open; ++i){} + for( i=0; i<v.rays.size() && tiles_data[ v.rays[i].tile ].state!=0; ++i){} + + //if there are only openings: + if (i == v.rays.size() ){ +// std::printf("only openings!\n"); + event.insert_at = contextRanking(pos.min()); + } + //if there is at least one closing: make it first, and catch 'insert_at'. + else{ +// std::printf("not only openings\n"); + if( i > 0 ){ + std::vector<Ray> head; + head.assign ( v.rays.begin(), v.rays.begin()+i ); + v.rays.erase ( v.rays.begin(), v.rays.begin()+i ); + v.rays.insert( v.rays.end(), head.begin(), head.end()); + } + //assert( tiles_data[ v.rays[0].tile ].open ); + assert( tiles_data[ v.rays[0].tile ].state==0 ); + event.insert_at = context.find( v.rays.front().tile )+1; +// event.erase_at = context.find( v.rays.front().tile ); +// std::printf("at least one closing!\n"); + } + context.pending_vertex = v; + }else{ +// std::printf("continue biting exiting vertex\n"); + event.tile = context.pending_vertex.rays.front().tile; + event.insert_at = old_event.insert_at; +// if (old_event.opening){ +// event.insert_at++; +// }else{ +// if (old_event.erase_at < old_event.insert_at){ +// event.insert_at--; +// } +// } + event.insert_at++; + } + event.tile = context.pending_vertex.rays.front().tile; +// event.opening = !tiles_data[ event.tile ].open; + event.opening = tiles_data[ event.tile ].state!=0; + event.to_be_continued = context.pending_vertex.rays.size()>1; +// if ( !event.opening ) event.erase_at = context.find(event.tile); + + context.pending_vertex.rays.erase(context.pending_vertex.rays.begin()); + context.pending_event = event; +// printEvent(event); + return event; + } +}; + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/topology.cpp b/src/toys/topology.cpp new file mode 100644 index 0000000..25830ce --- /dev/null +++ b/src/toys/topology.cpp @@ -0,0 +1,668 @@ +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> + +#include <vector> +#include <algorithm> +#include "sweeper.cpp" + +/* +Topology Class: +This class mainly consists in 3 vectors: vertices, edges, and areas. +-edges: have start/end, left/right pointing to vertices or areas. +-vertices: have a "boundary"= the sequence of edges sorted in CCW order. +-areas: have one outer "boundary" + a vector of inner boundaries, which are + sequence of edges. + +To build this data, the strategy is to let a line sweep the plane (from left +to right, say) and consider the topology of what is on the left of the sweep line. +Topology changes are called events, and we call an external "sweeper" to generate +them for us. + +So we start with an empty data, and respond to events to always describe the +topology of what is on the left of the sweep line. [more precisely, we start with +one region that has empty boundary, and since the external sweeper knows how many +edges we'll have at the end, so we create them from scratch, leaving their ends +as "unknown"] + +Note: see the sweeper for more info about events; they are essentially generated +when the sweep line crosses a vertex (which is in fact a box), but are in fact split +into smaller events, one for each edge around the vertex... + + +The code is using a lot of vectors: unsing pointers instead of vectors could speed +things up (?), but vector indices are easier to debug than memory addresses.:P +*/ + +using namespace Geom; +using namespace std; + +class Topology { +public: + + // -!- convention: + // In a boundary, reversed edges point away from the vertex or CW around the area. + struct OrientedEdge{ + unsigned edge; //edge index. + bool reversed; //true if the intrinsic edge orientation points away (from vertex) or backward (along area boundary) + OrientedEdge(unsigned edge_idx, bool o){ + edge = edge_idx; + reversed = o; + } + OrientedEdge(){ + edge = NULL_IDX; + reversed = false; + } + bool operator == ( OrientedEdge const &other) const { + return (edge == other.edge && edge!=NULL_IDX && reversed == other.reversed); + } + }; + + class Boundary : public std::vector<OrientedEdge>{ + public: + bool of_area;//true if this is the boundary of an area. Fix this with templates? + Boundary(bool area_type): of_area(area_type){} + }; + + class Vertex{ + public: + Boundary boundary; // list of edges in CCW order around the vertex + Geom::Rect bounds; + Vertex():boundary(false){} + }; + + class Area {//an area is a connected comp of the complement of the graph. . + public: + Boundary boundary; // outermost boundary component, CCW oriented (i.e. area is on the left of the boundary). + std::vector<Boundary> inner_boundaries;//same conventions, area on the left, so this gives the CW orientation for inner components. + std::vector<int> windings;//one winding number for each input path. + Area(unsigned size): boundary(true), windings(size, 0){} + }; + + class Edge { + public: + unsigned left, right;// the indices of the areas on the left and on the right this edge. + unsigned start, end; // the indices of vertices at start and at end of this edge. + Geom::Interval portion; + unsigned path; + unsigned curve; + Edge(){ + left = NULL_IDX; + right =NULL_IDX; + start = NULL_IDX; + end = NULL_IDX; + portion = Interval(); + path = NULL_IDX; + curve = NULL_IDX; + } + }; + + vector<Area> areas; + vector<Edge> edges; + vector<Vertex> vertices; + + PathVector input_paths;//we don't need our own copy... + cairo_t* cr; + + //debug only!! + int steps_max; + //---------- + + + //---------------------------------------------------- + //-- utils... + //---------------------------------------------------- + + void printIdx(unsigned idx){ (idx == NULL_IDX)? std::printf("?") : std::printf("%u", idx); } + void printVertex(unsigned i){ + std::printf("vertex %u: ", i); + printBoundary(vertices[i].boundary); + std::printf("\n"); + } + void printEdge(unsigned i){ + std::printf("edge %u: ", i); + printIdx(edges[i].start); + std::printf(" -> "); + printIdx(edges[i].end); + std::printf(" ^"); + printIdx(edges[i].left); + std::printf(" _"); + printIdx(edges[i].right); + std::printf("\n"); + } + void printArea(unsigned i){ + std::printf("area %u: ", i); + printBoundary(areas[i].boundary); + for (auto & inner_boundarie : areas[i].inner_boundaries){ + std::printf(", "); + printBoundary(inner_boundarie); + } + std::printf("\n"); + } + + void printOrientedEdge(OrientedEdge const &f){ + ( f.reversed ) ? std::printf("-") : std::printf("+"); + printIdx(f.edge); + std::printf(" "); + } + void printBoundary(Boundary const &bndry){ + (bndry.of_area) ? std::printf("[") : std::printf("<"); + for (unsigned i=0; i<bndry.size(); i++){ + printOrientedEdge(bndry[i]); + } + (bndry.of_area) ? std::printf("]") : std::printf(">"); + } + + void print(){ + std::cout<<"\nCrossing Data:\n"; + for (unsigned i=0; i<vertices.size(); i++){ + printVertex(i); + } + std::cout<<"\n"; + for (unsigned i=0; i<edges.size(); i++){ + printEdge(i); + } + std::cout<<"\n"; + for (unsigned i=0; i<areas.size(); i++){ + printArea(i); + } + } + + D2<SBasis> edgeAsSBasis(unsigned e){ + //beurk! optimize me. + D2<SBasis> c = input_paths[edges[e].path][edges[e].curve].toSBasis(); + return portion(c, edges[e].portion); + } + + Path edgeToPath(Topology::OrientedEdge o_edge){ + Topology::Edge e = edges[o_edge.edge]; + D2<SBasis> p = input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + p = portion(p, dom); + if ( o_edge.reversed ){ + p = compose( p, Linear(1.,0.) ); + } + Path ret; + ret.setStitching(true); + Point center; + unsigned c_idx = source(o_edge, true); + if ( c_idx == NULL_IDX ){ + ret.append(p); + }else{ + center = vertices[c_idx].bounds.midpoint(); + ret = Path(center); + ret.append(p); + } + c_idx = target(o_edge, true); + if ( c_idx == NULL_IDX ){ + return ret; + }else{ + center = vertices[c_idx].bounds.midpoint(); + if ( center != p.at1() ) ret.appendNew<LineSegment>(center); + return ret; + } + } + + Path boundaryToPath(Topology::Boundary b){ + Point pt; + Path bndary; + bndary.setStitching(true); + + if (b.size()==0){ return Path(); } + + Topology::OrientedEdge o_edge = b.front(); + unsigned first_v = source(o_edge, true); + if ( first_v != NULL_IDX ){ + pt = vertices[first_v].bounds.midpoint(); + bndary = Path(pt); + } + + for (unsigned i = 0; i < b.size(); i++){ + bndary.append( edgeToPath(b[i])); + } + bndary.close(); + return bndary; + } + + + + //---------------------------------------------------- + //-- Boundary Navigation/Modification + //---------------------------------------------------- + + //TODO: this should be an OrientedEdge method, be requires access to the edges. + unsigned source(OrientedEdge const &f, bool as_area_bndry){ + unsigned prev; + if (f.reversed ) + prev = (as_area_bndry)? edges[f.edge].end : edges[f.edge].right; + else + prev = (as_area_bndry)? edges[f.edge].start : edges[f.edge].left; + return prev; + } + unsigned target(OrientedEdge const &f, bool as_area_bndry){ + unsigned prev; + if (f.reversed ) + prev = (as_area_bndry)? edges[f.edge].start : edges[f.edge].left; + else + prev = (as_area_bndry)? edges[f.edge].end : edges[f.edge].right; + return prev; + } + + //TODO: this should be a Boundary method, but access to the full data is required... + bool prolongate( Boundary &bndry, OrientedEdge const &f){ + if ( bndry.empty() ){ + bndry.push_back(f); + return true; + } + unsigned src = source(f, bndry.of_area); + if ( src == target( bndry.back(), bndry.of_area ) && src != NULL_IDX ){ + bndry.push_back(f); + return true; + } + unsigned tgt = target( f, bndry.of_area ); + if ( tgt == source( bndry.front(), bndry.of_area ) && tgt != NULL_IDX ){ + bndry.insert( bndry.begin(), f); + return true; + } + return false; + } + + bool prolongate(Boundary &a, Boundary &b){ + if (a.size()==0 || b.size()==0 || (a.of_area != b.of_area) ) return false; + unsigned src; + src = source(a.front(), a.of_area); + +// unsigned af = a.front().edge, as=source(a.front(), a.of_area), ab=a.back().edge, at=target(a.back(), a.of_area); +// unsigned bf = b.front().edge, bs=source(b.front(), b.of_area), bb=b.back().edge, bt=target(b.back(), b.of_area); +// std::printf("a=%u(%u)...(%u)%u\n", as, af,ab,at); +// std::printf("b=%u(%u)...(%u)%u\n", bs, bf,bb,bt); + +// std::printf("%u == %u?\n", src, target( b.back(), b.of_area )); + if ( src == target( b.back(), b.of_area ) && src != NULL_IDX ){ + a.insert( a.begin(), b.begin(), b.end() ); +// std::printf("boundaries fused!!\n"); + return true; + } + src = source(b.front(), b.of_area); + if ( src == target( a.back(), a.of_area ) && src != NULL_IDX ){ + a.insert( a.end(), b.begin(), b.end() ); + return true; + } + return false; + } + + //TODO: this should be a Boundary or Area method, but requires access to the full data... + //TODO: systematically check for connected boundaries before returning? + void addAreaBoundaryPiece(unsigned a, OrientedEdge const &f){ + if ( areas[a].boundary.size()>0 && prolongate( areas[a].boundary, f ) ) return; + for (auto & inner_boundarie : areas[a].inner_boundaries){ +// printBoundary(areas[a].inner_boundaries[i]); +// printf(" matches "); +// printOrientedEdge(f); +// printf("?"); + if ( inner_boundarie.size()>0 && prolongate( inner_boundarie, f) ) return; +// printf("no. (%u vs %u)", target(areas[a].inner_boundaries[i].back(), true), source(f, true)); + } + Boundary new_comp(true); + new_comp.push_back(f); + areas[a].inner_boundaries.push_back(new_comp); + } + + + bool fuseConnectedBoundaries(unsigned a){ +// std::printf(" fuseConnectedBoundaries %u\n",a); + + bool ret = false; + if ( areas[a].boundary.size()>0 ){ + for ( unsigned i=0; i<areas[a].inner_boundaries.size(); i++){ + if ( prolongate( areas[a].boundary, areas[a].inner_boundaries[i] ) ){ + areas[a].inner_boundaries.erase(areas[a].inner_boundaries.begin()+i); + i--; + ret = true; + } + } + } + for ( unsigned i=0; i<areas[a].inner_boundaries.size(); i++){ + for ( unsigned j=i+1; j<areas[a].inner_boundaries.size(); j++){ + if ( prolongate( areas[a].inner_boundaries[i], areas[a].inner_boundaries[j] ) ){ + areas[a].inner_boundaries.erase(areas[a].inner_boundaries.begin()+j); + j--; + ret = true; + } + } + } + return ret; + } + + //------------------------------- + //-- Some basic area manipulation. + //------------------------------- + + void renameArea(unsigned oldi, unsigned newi){ + for (auto & edge : edges){ + if ( edge.left == oldi ) edge.left = newi; + if ( edge.right == oldi ) edge.right = newi; + } + } + void deleteArea(unsigned a0){//ptrs would definitely be helpful here... + assert(a0<areas.size()); + for (unsigned a=a0+1; a<areas.size(); a++){ + renameArea(a,a-1); + } + areas.erase(areas.begin()+a0); + } + + //fuse open(=not finished!) areas. The boundaries are supposed to match. true on success. + void fuseAreas(unsigned a, unsigned b){ +// std::printf("fuse Areas %u and %u\n", a, b); + if (a==b) return; + if (a>b) swap(a,b);//this is important to keep track of the outermost component!! + + areas[a].inner_boundaries.push_back(areas[b].boundary); + for (unsigned i=0; i<areas[b].inner_boundaries.size(); i++){ + areas[a].inner_boundaries.push_back(areas[b].inner_boundaries[i]); + } + renameArea(b,a); + deleteArea(b); + assert( fuseConnectedBoundaries(a) ); + return; + } + + PathVector areaToPath(unsigned a){ + PathVector bndary; + if ( areas[a].boundary.size()!=0 ){//this is not the unbounded component... + bndary.push_back( boundaryToPath(areas[a].boundary ) ); + } + for (auto & inner_boundarie : areas[a].inner_boundaries){ + bndary.push_back( boundaryToPath(inner_boundarie) ); + } + return bndary; + } + + //DEBUG ONLY: we add a rect round the unbounded comp, and glue the bndries + //for easy drawing in the toys... + Path glued_areaToPath(unsigned a){ + Path bndary; + if ( areas[a].boundary.size()==0 ){//this is the unbounded component... + OptRect bbox = bounds_fast( input_paths ); + if (!bbox ){return Path();}//??? + bbox->expandBy(50); + bndary = Path(bbox->corner(0)); + bndary.appendNew<LineSegment>(bbox->corner(1)); + bndary.appendNew<LineSegment>(bbox->corner(2)); + bndary.appendNew<LineSegment>(bbox->corner(3)); + bndary.appendNew<LineSegment>(bbox->corner(0)); + }else{ + bndary = boundaryToPath(areas[a].boundary); + } + for (auto & inner_boundarie : areas[a].inner_boundaries){ + bndary.append( boundaryToPath(inner_boundarie)); + bndary.appendNew<LineSegment>( bndary.initialPoint() ); + } + bndary.close(); + return bndary; + } + + void drawAreas( cairo_t *cr, bool fill=true ){ + //don't draw the first one... + for (unsigned a=0; a<areas.size(); a++){ + drawArea(cr, a, fill); + } + } + void drawArea( cairo_t *cr, unsigned a, bool fill=true ){ + if (a>=areas.size()) return; + Path bndary = glued_areaToPath(a); + cairo_path(cr, bndary); + if (fill){ + cairo_fill(cr); + }else{ + cairo_stroke(cr); + } + } + void highlightRay( cairo_t *cr, unsigned b, unsigned r ){ + if (b>=vertices.size()) return; + if (r>=vertices[b].boundary.size()) return; + Rect box = vertices[b].bounds; + //box.expandBy(2); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, 1.0); + cairo_set_line_width (cr, 1); + cairo_fill(cr); + unsigned eidx = vertices[b].boundary[r].edge; + Topology::Edge e = edges[eidx]; + D2<SBasis> p = input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + if (vertices[b].boundary[r].reversed){ + //dom[0] += e.portion.extent()*2./3; + cairo_set_source_rgba (cr, 0., 1., 0., 1.0); + }else{ + //dom[1] -= e.portion.extent()*2./3; + cairo_set_source_rgba (cr, 0., 0., 1., 1.0); + } + p = portion(p, dom); + cairo_d2_sb(cr, p); + cairo_set_source_rgba (cr, 1., 0., 0, 1.0); + cairo_set_line_width (cr, 5); + cairo_stroke(cr); + } + + void drawEdge( cairo_t *cr, unsigned eidx ){ + if (eidx>=edges.size()) return; + Topology::Edge e = edges[eidx]; + D2<SBasis> p = input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + p = portion(p, dom); + cairo_d2_sb(cr, p); + if (e.start == NULL_IDX || e.end == NULL_IDX ) + cairo_set_source_rgba (cr, 0., 1., 0, 1.0); + else + cairo_set_source_rgba (cr, 0., 0., 0, 1.0); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + } + void drawEdges( cairo_t *cr){ + for (unsigned e=0; e<edges.size(); e++){ + drawEdge(cr, e); + } + } + void drawKnownEdges( cairo_t *cr){ + for (auto & vertice : vertices){ + for (unsigned e=0; e<vertice.boundary.size(); e++){ + drawEdge(cr, vertice.boundary[e].edge); + } + } + } + + + void drawBox( cairo_t *cr, unsigned b ){ + if (b>=vertices.size()) return; + Rect box = vertices[b].bounds; + //box.expandBy(5); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, .5); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, .2); + cairo_fill(cr); + } + + void drawBoxes( cairo_t *cr){ + for (unsigned b=0; b<vertices.size(); b++){ + drawBox(cr, b); + } + } + + + + + + + + + + + //---------------------------------------------------- + //-- Fill data using a sweeper... + //---------------------------------------------------- + + Topology(){} + ~Topology(){} + Topology(PathVector const &paths, cairo_t* cairo, double tol=EPSILON, int stepsmax=-1){ +// std::printf("\n---------------------\n---------------------\n---------------------\n"); +// std::printf("Topology creation\n"); + cr = cairo; + + //debug only: + steps_max = stepsmax; + //------------- + + input_paths = paths; + + vertices.clear(); + edges.clear(); + areas.clear(); + Area empty( input_paths.size() ); + areas.push_back(empty); + + Sweeper sweeper( paths, X, tol ); + + edges = std::vector<Edge>( sweeper.tiles_data.size(), Edge() ); + for (unsigned i=0; i<edges.size(); i++){ + edges[i].path = sweeper.tiles_data[i].path; + edges[i].curve = sweeper.tiles_data[i].curve; + edges[i].portion = Interval(sweeper.tiles_data[i].f, sweeper.tiles_data[i].t); + } + + //std::printf("entering event loop:\n"); + int step=0; + for(Sweeper::Event event = sweeper.getNextEvent(); ; event = sweeper.getNextEvent() ){ +// std::printf(" new event received: "); + //print(); + //debug only!!! + if ( steps_max >= 0 && step > steps_max ){ + break; + }else{ + step++; + } + //--------- + + if (event.empty()){ + //std::printf(" empty event received\n"); + break; + } + + //std::printf(" non empty event received:"); + //sweeper.printEvent(event); + + //is this a new event or the continuation of an old one? + unsigned v; + Rect r = sweeper.context.pending_vertex; + if (vertices.empty() || !r.intersects( vertices.back().bounds ) ){ + v = vertices.size(); + vertices.push_back(Vertex()); + vertices[v].bounds = r; +// std::printf(" new intersection created (%u).\n",v); + }else{ + v = vertices.size()-1; +// std::printf(" continue last intersection (%u).\n",v); + } + + //--Closing an edge:------------- + if( !event.opening ){ + unsigned e = event.tile, a, b; +// std::printf(" closing edge %u\n", e); + bool reversed = sweeper.tiles_data[e].reversed;//Warning: true means v==e.start + if (reversed){ + edges[e].start = v; + a = edges[e].right; + b = edges[e].left; + }else{ + edges[e].end = v; + a = edges[e].left; + b = edges[e].right; + } + OrientedEdge vert_edge(e, reversed); + if (vertices[v].boundary.size()>0){//Make sure areas are compatible (only relevant if the last event was an opening). + fuseAreas ( a, target( vertices[v].boundary.back(), false ) ); + } + assert( prolongate( vertices[v].boundary, vert_edge) ); + fuseConnectedBoundaries(a);//there is no doing both: tests are performed twice but for 2 areas. + fuseConnectedBoundaries(b);// + }else{ + //--Opening an edge:------------- + unsigned e = event.tile; +// std::printf(" opening edge %u\n", e); + bool reversed = !sweeper.tiles_data[e].reversed;//Warning: true means v==start. + + //--Find first and last area around this vertex:------------- + unsigned cur_a; + if ( vertices[v].boundary.size() > 0 ){ + cur_a = target( vertices[v].boundary.back(), false ); + }else{//this vertex is empty + if ( event.insert_at < sweeper.context.size() ){ + unsigned upper_tile = sweeper.context[event.insert_at].first; + cur_a = (sweeper.tiles_data[upper_tile].reversed) ? edges[upper_tile].left : edges[upper_tile].right; + }else{ + cur_a = 0; + } + } + + unsigned new_a = areas.size(); + + Area new_area(paths.size()); + new_area.boundary.push_back( OrientedEdge(e, !reversed ) ); + new_area.windings = areas[cur_a].windings;//FIXME: escape boundary cases!!! + if ( input_paths[edges[e].path].closed() ){ + new_area.windings[edges[e].path] += (reversed) ? +1 : -1; + } + areas.push_back(new_area); + + //update edge + if (reversed){ + edges[e].start = v; + edges[e].left = new_a; + edges[e].right = cur_a; + }else{ + edges[e].end = v; + edges[e].left = cur_a; + edges[e].right = new_a; + } + //update vertex + OrientedEdge f(e, reversed); + assert( prolongate( vertices[v].boundary, f) ); + addAreaBoundaryPiece(cur_a, OrientedEdge(e, reversed) ); + } + if (!event.to_be_continued && vertices[v].boundary.size()>0){ + unsigned first_a = source( vertices[v].boundary.front(), false ); + unsigned last_a = target( vertices[v].boundary.back(), false ); + fuseAreas(first_a, last_a); + } + +// this->print(); +// std::printf("----------------\n"); + //std::printf("\n"); + } + } + + + + //---------------------------------------------------- + //-- done. + //---------------------------------------------------- +}; + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/toy-framework-2.cpp b/src/toys/toy-framework-2.cpp new file mode 100644 index 0000000..54166cf --- /dev/null +++ b/src/toys/toy-framework-2.cpp @@ -0,0 +1,972 @@ +#include <cstring> +#include <cstdint> +#include <typeinfo> +#include <cairo.h> +#include <gtk/gtk.h> +#include <toys/toy-framework-2.h> + +#include <cairo-features.h> +#if CAIRO_HAS_PDF_SURFACE +#include <cairo-pdf.h> +#endif +#if CAIRO_HAS_SVG_SURFACE +#include <cairo-svg.h> +#endif + +GtkApplicationWindow* the_window = nullptr; +static GtkWidget *the_canvas = nullptr; +Toy* the_toy = nullptr; +int the_requested_height = 0; +int the_requested_width = 0; +gchar **the_emulated_argv = nullptr; + +gchar *arg_spool_filename = nullptr; +gchar *arg_handles_filename = nullptr; +gchar *arg_screenshot_filename = nullptr; +gchar **arg_extra_files = nullptr; + +//Utility functions + +double uniform() { + return double(rand()) / RAND_MAX; +} + +colour colour::from_hsv( float H, // hue shift (in degrees) + float S, // saturation shift (scalar) + float V, // value multiplier (scalar) + float A + ) +{ + double inr = 1; + double ing = 0; + double inb = 0; + float k = V/3; + float a = V*S*cos(H)/3; + float b = V*S*sin(H)/3; + + return colour( + (k+2*a)*inr - 2*b*ing + (k-a-b)*inb, + (-k+a+3*b)*inr + (3*a-b)*ing + (-k+a+2*b)*inb, + (2*k-2*a)*inr + 2*b*ing + (2*k+a+b)*inb, + A); +} + + // Given H,S,L in range of 0-1 + + // Returns a Color (RGB struct) in range of 0-255 + +colour colour::from_hsl(float h, float sl, float l, float a) { + h /= M_PI*2; + colour rgba(l,l,l,a); // default to gray + + double v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl); + + if (v > 0) { + double m; + double sv; + int sextant; + double fract, vsf, mid1, mid2; + + m = l + l - v; + sv = (v - m ) / v; + h *= 6.0; + sextant = (int)h; + fract = h - sextant; + vsf = v * sv * fract; + mid1 = m + vsf; + mid2 = v - vsf; + switch (sextant%6) { + case 0: + rgba.r = v; + rgba.g = mid1; + rgba.b = m; + break; + + case 1: + rgba.r = mid2; + rgba.g = v; + rgba.b = m; + break; + + case 2: + rgba.r = m; + rgba.g = v; + rgba.b = mid1; + break; + + case 3: + rgba.r = m; + rgba.g = mid2; + rgba.b = v; + break; + + case 4: + rgba.r = mid1; + rgba.g = m; + rgba.b = v; + break; + + case 5: + rgba.r = v; + rgba.g = m; + rgba.b = mid2; + break; + } + } + return rgba; +} + +void cairo_set_source_rgba(cairo_t* cr, colour c) { + cairo_set_source_rgba(cr, c.r, c.g, c.b, c.a); +} + +void draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom, const char* fontdesc) { + PangoLayout* layout = pango_cairo_create_layout (cr); + pango_layout_set_text(layout, txt, -1); + PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc); + pango_layout_set_font_description(layout, font_desc); + pango_font_description_free (font_desc); + PangoRectangle logical_extent; + pango_layout_get_pixel_extents(layout, NULL, &logical_extent); + cairo_move_to(cr, loc - Geom::Point(0, bottom ? logical_extent.height : 0)); + pango_cairo_show_layout(cr, layout); +} + +void draw_text(cairo_t *cr, Geom::Point loc, const std::string& txt, bool bottom, const std::string& fontdesc) { + draw_text(cr, loc, txt.c_str(), bottom, fontdesc.c_str()); +} + +void draw_number(cairo_t *cr, Geom::Point pos, int num, std::string name, bool bottom) { + std::ostringstream number; + if (name.size()) + number << name; + number << num; + draw_text(cr, pos, number.str().c_str(), bottom); +} + +void draw_number(cairo_t *cr, Geom::Point pos, unsigned num, std::string name, bool bottom) { + std::ostringstream number; + if (name.size()) + number << name; + number << num; + draw_text(cr, pos, number.str().c_str(), bottom); +} + +void draw_number(cairo_t *cr, Geom::Point pos, double num, std::string name, bool bottom) { + std::ostringstream number; + if (name.size()) + number << name; + number << num; + draw_text(cr, pos, number.str().c_str(), bottom); +} + +//Framework Accessors +void redraw() { gtk_widget_queue_draw(GTK_WIDGET(the_window)); } + +void Toy::draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool /*save*/, std::ostringstream *timer_stream) +{ + if(should_draw_bounds() == 1) { + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 0.5); + for(unsigned i = 1; i < 4; i+=2) { + cairo_move_to(cr, 0, i*width/4); + cairo_line_to(cr, width, i*width/4); + cairo_move_to(cr, i*width/4, 0); + cairo_line_to(cr, i*width/4, height); + } + } + else if(should_draw_bounds() == 2) { + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 0.5); + cairo_move_to(cr, 0, width/2); + cairo_line_to(cr, width, width/2); + cairo_move_to(cr, width/2, 0); + cairo_line_to(cr, width/2, height); + } + + cairo_set_line_width (cr, 1); + for(auto & handle : handles) { + cairo_set_source_rgb (cr, handle->rgb[0], handle->rgb[1], handle->rgb[2]); + handle->draw(cr, should_draw_numbers()); + } + + cairo_set_source_rgba (cr, 0.5, 0, 0, 1); + if(selected && mouse_down == true) + selected->draw(cr, should_draw_numbers()); + + cairo_set_source_rgba (cr, 0.5, 0.25, 0, 1); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0.5, 0, 0.8); + { + *notify << std::ends; + draw_text(cr, Geom::Point(0, height-notify_offset), notify->str().c_str(), true); + } + if(show_timings) { + *timer_stream << std::ends; + draw_text(cr, Geom::Point(0, notify_offset), timer_stream->str().c_str(), false); + } +} + +void Toy::mouse_moved(GdkEventMotion* e) +{ + Geom::Point mouse(e->x, e->y); + + if(e->state & (GDK_BUTTON1_MASK | GDK_BUTTON3_MASK)) { + if(selected) + selected->move_to(hit_data, old_mouse_point, mouse); + } + old_mouse_point = mouse; + redraw(); +} + +void Toy::mouse_pressed(GdkEventButton* e) { + Geom::Point mouse(e->x, e->y); + selected = NULL; + hit_data = NULL; + canvas_click_button = e->button; + if(e->button == 1) { + for(auto & handle : handles) { + void * hit = handle->hit(mouse); + if(hit) { + selected = handle; + hit_data = hit; + } + } + mouse_down = true; + } + old_mouse_point = mouse; + redraw(); +} + +void Toy::scroll(GdkEventScroll* /*e*/) { +} + +void Toy::canvas_click(Geom::Point at, int button) { + (void)at; + (void)button; +} + +void Toy::mouse_released(GdkEventButton* e) { + if(selected == NULL) { + Geom::Point mouse(e->x, e->y); + canvas_click(mouse, canvas_click_button); + canvas_click_button = 0; + } + selected = NULL; + hit_data = NULL; + if(e->button == 1) + mouse_down = false; + redraw(); +} + +void Toy::load(FILE* f) { + char data[1024]; + if (fscanf(f, "%1024s", data)) { + name = data; + } + for(auto & handle : handles) { + handle->load(f); + } +} + +void Toy::save(FILE* f) { + fprintf(f, "%s\n", name.c_str()); + for(auto & handle : handles) + handle->save(f); +} + +//Gui Event Callbacks + +void show_about_dialog(GSimpleAction *, GVariant *, gpointer) { + GtkWidget* about_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(about_window), "About"); + gtk_window_set_resizable(GTK_WINDOW(about_window), FALSE); + + GtkWidget* about_text = gtk_text_view_new(); + GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(about_text)); + gtk_text_buffer_set_text(buf, "Toy lib2geom application", -1); + gtk_container_add(GTK_CONTAINER(about_window), about_text); + + gtk_widget_show_all(about_window); +} + +void quit(GSimpleAction *, GVariant *, gpointer) { + g_application_quit(g_application_get_default()); +} + +Geom::Point read_point(FILE* f) { + Geom::Point p; + for(unsigned i = 0; i < 2; i++) + assert(fscanf(f, " %lf ", &p[i])); + return p; +} + +Geom::Interval read_interval(FILE* f) { + Geom::Interval p; + Geom::Coord a, b; + assert(fscanf(f, " %lf ", &a)); + assert(fscanf(f, " %lf ", &b)); + p.setEnds(a, b); + return p; +} + +void open_handles(GSimpleAction *, GVariant *, gpointer) { + if (!the_toy) return; + GtkWidget* d = gtk_file_chooser_dialog_new( + "Open handle configuration", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_OPEN, + "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL); + if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) { + const char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d)); + FILE* f = fopen(filename, "r"); + the_toy->load(f); + fclose(f); + } + gtk_widget_destroy(d); +} + +void save_handles(GSimpleAction *, GVariant *, gpointer) { + if (!the_toy) return; + GtkWidget* d = gtk_file_chooser_dialog_new( + "Save handle configuration", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_SAVE, + "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL); + if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) { + const char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d)); + FILE* f = fopen(filename, "w"); + the_toy->save(f); + fclose(f); + } + gtk_widget_destroy(d); +} + +void write_image(const char* filename) { + cairo_surface_t* cr_s; + unsigned l = strlen(filename); + int width = gdk_window_get_width(gtk_widget_get_window(the_canvas)); + int height = gdk_window_get_height(gtk_widget_get_window(the_canvas)); + bool save_png = false; + + if (l >= 4 && strcmp(filename + l - 4, ".png") == 0) { + cr_s = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32, width, height ); + save_png = true; + } + +#if CAIRO_HAS_PDF_SURFACE + else if (l >= 4 && strcmp(filename + l - 4, ".pdf") == 0) + cr_s = cairo_pdf_surface_create(filename, width, height); +#endif +#if CAIRO_HAS_SVG_SURFACE +#if CAIRO_HAS_PDF_SURFACE + else +#endif + cr_s = cairo_svg_surface_create(filename, width, height); +#endif + cairo_t* cr = cairo_create(cr_s); + + if(save_png) { + cairo_save(cr); + cairo_set_source_rgb(cr, 1,1,1); + cairo_paint(cr); + cairo_restore(cr); + } + if(the_toy != NULL) { + std::ostringstream * notify = new std::ostringstream; + std::ostringstream * timer_stream = new std::ostringstream; + the_toy->draw(cr, notify, width, height, true, timer_stream); + delete notify; + delete timer_stream; + } + + cairo_show_page(cr); + if(save_png) + cairo_surface_write_to_png(cr_s, filename); + cairo_destroy (cr); + cairo_surface_destroy (cr_s); +} + +void save_cairo(GSimpleAction *, GVariant *, gpointer) { + GtkWidget* d = gtk_file_chooser_dialog_new( + "Save file as svg, pdf or png", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_SAVE, + "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL); + if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) { + const gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d)); + write_image(filename); + } + gtk_widget_destroy(d); +} + +static gint delete_event(GtkWidget*, GdkEventAny*, gpointer) { + quit(nullptr, nullptr, nullptr); + return FALSE; +} + + +static void toggle_action(GSimpleAction *action, GVariant *, gpointer) { + GVariant *state = g_action_get_state(G_ACTION(action)); + g_action_change_state(G_ACTION(action), g_variant_new_boolean(!g_variant_get_boolean(state))); + g_variant_unref(state); +} + +static void set_show_timings(GSimpleAction *action, GVariant *variant, gpointer) { + the_toy->show_timings = g_variant_get_boolean(variant); + g_simple_action_set_state(action, variant); +} + +static gboolean draw_callback(GtkWidget *widget, cairo_t *cr) +{ + int width = gdk_window_get_width(gtk_widget_get_window(widget)); + int height = gdk_window_get_height(gtk_widget_get_window(widget)); + + std::ostringstream notify; + + static bool resized = false; + if(!resized) { + Geom::Rect alloc_size(Geom::Interval(0, width), + Geom::Interval(0, height)); + if(the_toy != NULL) + the_toy->resize_canvas(alloc_size); + resized = true; + } + cairo_rectangle(cr, 0, 0, width, height); + cairo_set_source_rgba(cr,1,1,1,1); + cairo_fill(cr); + if (the_toy != NULL) { + std::ostringstream * timer_stream = new std::ostringstream; + + if (the_toy->spool_file) { + the_toy->save(the_toy->spool_file); + } + + the_toy->draw(cr, ¬ify, width, height, false, timer_stream); + delete timer_stream; + } + + return TRUE; +} + +static gint mouse_motion_event(GtkWidget* widget, GdkEventMotion* e, gpointer data) { + (void)(data); + (void)(widget); + + if(the_toy != NULL) the_toy->mouse_moved(e); + + return FALSE; +} + +static gint mouse_event(GtkWidget* widget, GdkEventButton* e, gpointer data) { + (void)(data); + (void)(widget); + + if(the_toy != NULL) the_toy->mouse_pressed(e); + + return FALSE; +} + +static gint scroll_event(GtkWidget* widget, GdkEventScroll* e, gpointer data) { + (void)(data); + (void)(widget); + if(the_toy != NULL) the_toy->scroll(e); + + return FALSE; +} + +static gint mouse_release_event(GtkWidget* widget, GdkEventButton* e, gpointer data) { + (void)(data); + (void)(widget); + + if(the_toy != NULL) the_toy->mouse_released(e); + + return FALSE; +} + +static gint key_press_event(GtkWidget *widget, GdkEventKey *e, gpointer data) { + (void)(data); + (void)(widget); + + if(the_toy != NULL) the_toy->key_hit(e); + + return FALSE; +} + +static gint size_allocate_event(GtkWidget* widget, GtkAllocation *allocation, gpointer data) { + (void)(data); + (void)(widget); + + Geom::Rect alloc_size(Geom::Interval(allocation->x, allocation->x+ allocation->width), + Geom::Interval(allocation->y, allocation->y+allocation->height)); + if(the_toy != NULL) the_toy->resize_canvas(alloc_size); + + return FALSE; +} + + +const char *the_builder_xml = R"xml( +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <menu id="menu"> + <submenu> + <attribute name="label">File</attribute> + <section> + <item> + <attribute name="label">Open Handles...</attribute> + <attribute name="action">app.open-handles</attribute> + </item> + <item> + <attribute name="label">Save Handles...</attribute> + <attribute name="action">app.save-handles</attribute> + </item> + </section> + <section> + <item> + <attribute name="label">Save as SVG of PDF...</attribute> + <attribute name="action">app.save-image</attribute> + </item> + </section> + <section> + <item> + <attribute name="label">Show Timings</attribute> + <attribute name="action">app.show-timings</attribute> + </item> + <item> + <attribute name="label">Quit</attribute> + <attribute name="action">app.quit</attribute> + </item> + </section> + </submenu> + <submenu> + <attribute name="label">Help</attribute> + <item> + <attribute name="label">About...</attribute> + <attribute name="action">app.about</attribute> + </item> + </submenu> + </menu> +</interface> +)xml"; + +static GActionEntry the_actions[] = +{ + {"open-handles", open_handles, nullptr, nullptr, nullptr}, + {"save-handles", save_handles, nullptr, nullptr, nullptr}, + {"save-image", save_cairo, nullptr, nullptr, nullptr}, + {"show-timings", toggle_action, nullptr, "false", set_show_timings}, + {"quit", quit, nullptr, nullptr, nullptr}, + {"about", show_about_dialog, nullptr, nullptr, nullptr}, +}; + +static GOptionEntry const the_options[] = { + {"handles", 'h', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_handles_filename, + "Load handle positions from given file", "FILE"}, + {"spool", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_spool_filename, + "Record all interaction to the given file", "FILE"}, + {"screenshot", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_screenshot_filename, + "Take screenshot and exit", nullptr}, + {G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &arg_extra_files, + "Additional data files", "FILES..."}, + {nullptr, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr}, +}; + +static void activate(GApplication *app, gpointer); +static void startup(GApplication *app, gpointer); + +void init(int argc, char **argv, Toy* t, int width, int height) { + the_toy = t; + the_requested_width = width; + the_requested_height = height; + + std::string app_name = "org.inkscape.lib2geom.toy."; + char const *dir_pos = strrchr(argv[0], G_DIR_SEPARATOR); + std::string argv_name = dir_pos ? dir_pos + 1 : argv[0]; + + // Erase extension for Windows + size_t dot_pos = argv_name.rfind('.'); + if (dot_pos != std::string::npos) { + argv_name.erase(dot_pos); + } + the_toy->name = argv_name; + app_name += argv_name; + + GtkApplication* app = gtk_application_new(app_name.c_str(), G_APPLICATION_FLAGS_NONE); + g_application_add_main_option_entries(G_APPLICATION(app), the_options); + g_action_map_add_action_entries(G_ACTION_MAP(app), the_actions, G_N_ELEMENTS(the_actions), nullptr); + g_signal_connect(G_OBJECT(app), "startup", G_CALLBACK(startup), nullptr); + g_signal_connect(G_OBJECT(app), "activate", G_CALLBACK(activate), nullptr); + + g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); +} + +static void startup(GApplication *app, gpointer) { + GtkBuilder *builder = gtk_builder_new_from_string(the_builder_xml, -1); + GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu")); + gtk_application_set_menubar(GTK_APPLICATION(app), menu); + g_object_unref(builder); +} + +static void activate(GApplication *app, gpointer) { + if (arg_spool_filename) { + the_toy->spool_file = fopen(arg_spool_filename, "w"); + } + + int const emulated_argc = arg_extra_files ? g_strv_length(arg_extra_files) + 1 : 1; + gchar const **emulated_argv = new gchar const*[emulated_argc]; + emulated_argv[0] = the_toy->name.c_str(); + for (int i = 1; i < emulated_argc; ++i) { + emulated_argv[i] = arg_extra_files[i-1]; + } + the_toy->first_time(emulated_argc, const_cast<char**>(emulated_argv)); + delete[] emulated_argv; + + if (arg_handles_filename) { + FILE *handles_file = fopen(arg_handles_filename, "r"); + the_toy->load(handles_file); + fclose(handles_file); + } + + if (arg_screenshot_filename) { + write_image(arg_screenshot_filename); + g_application_quit(app); + return; + } + + the_window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(g_application_get_default()))); + gtk_window_set_title(GTK_WINDOW(the_window), the_toy->name.c_str()); + g_signal_connect(G_OBJECT(the_window), "delete_event", G_CALLBACK(delete_event), NULL); + + the_canvas = gtk_drawing_area_new(); + gtk_widget_add_events(the_canvas, (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK)); + g_signal_connect(G_OBJECT(the_canvas), "draw", G_CALLBACK(draw_callback), 0); + g_signal_connect(G_OBJECT(the_canvas), "scroll-event", G_CALLBACK(scroll_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "button-press-event", G_CALLBACK(mouse_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "button-release-event", G_CALLBACK(mouse_release_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "motion-notify-event", G_CALLBACK(mouse_motion_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "key-press-event", G_CALLBACK(key_press_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "size-allocate", G_CALLBACK(size_allocate_event), 0); + + gtk_container_add(GTK_CONTAINER(the_window), the_canvas); + gtk_window_set_default_size(GTK_WINDOW(the_window), the_requested_width, the_requested_height); + gtk_widget_show_all(GTK_WIDGET(the_window)); + + // Make sure the canvas can receive key press events. + gtk_widget_set_can_focus(the_canvas, TRUE); + gtk_widget_grab_focus(the_canvas); + assert(gtk_widget_is_focus(the_canvas)); +} + + +void Toggle::draw(cairo_t *cr, bool /*annotes*/) { + cairo_pattern_t* source = cairo_get_source(cr); + double rc, gc, bc, aa; + cairo_pattern_get_rgba(source, &rc, &gc, &bc, &aa); + cairo_set_source_rgba(cr,0,0,0,1); + cairo_rectangle(cr, bounds.left(), bounds.top(), + bounds.width(), bounds.height()); + if(on) { + cairo_fill(cr); + cairo_set_source_rgba(cr,1,1,1,1); + } //else cairo_stroke(cr); + cairo_stroke(cr); + draw_text(cr, bounds.corner(0) + Geom::Point(5,2), text); + cairo_set_source_rgba(cr, rc, gc, bc, aa); +} + +void Toggle::toggle() { + on = !on; +} +void Toggle::set(bool state) { + on = state; +} + + +void Toggle::handle_click(GdkEventButton* e) { + if(bounds.contains(Geom::Point(e->x, e->y)) && e->button == 1) toggle(); +} + +void* Toggle::hit(Geom::Point mouse) +{ + if (bounds.contains(mouse)) + { + toggle(); + return this; + } + return 0; +} + +void toggle_events(std::vector<Toggle> &ts, GdkEventButton* e) { + for(auto & t : ts) t.handle_click(e); +} + +void draw_toggles(cairo_t *cr, std::vector<Toggle> &ts) { + for(auto & t : ts) t.draw(cr); +} + + + +Slider::value_type Slider::value() const +{ + Slider::value_type v = m_handle.pos[m_dir] - m_pos[m_dir]; + v = ((m_max - m_min) / m_length) * v; + //std::cerr << "v : " << v << std::endl; + if (m_step != 0) + { + int k = std::floor(v / m_step); + v = k * m_step; + } + v = v + m_min; + //std::cerr << "v : " << v << std::endl; + return v; +} + +void Slider::value(Slider::value_type _value) +{ + if ( _value < m_min ) _value = m_min; + if ( _value > m_max ) _value = m_max; + if (m_step != 0) + { + _value = _value - m_min; + int k = std::floor(_value / m_step); + _value = k * m_step + m_min; + } + m_handle.pos[m_dir] + = (m_length / (m_max - m_min)) * (_value - m_min) + m_pos[m_dir]; +} + +void Slider::max_value(Slider::value_type _value) +{ + Slider::value_type v = value(); + m_max = _value; + value(v); +} + +void Slider::min_value(Slider::value_type _value) +{ + Slider::value_type v = value(); + m_min = _value; + value(v); +} + +// dir = X horizontal slider dir = Y vertical slider +void Slider::geometry( Geom::Point _pos, + Slider::value_type _length, + Geom::Dim2 _dir ) +{ + Slider::value_type v = value(); + m_pos = _pos; + m_length = _length; + m_dir = _dir; + Geom::Dim2 fix_dir = static_cast<Geom::Dim2>( (m_dir + 1) % 2 ); + m_handle.pos[fix_dir] = m_pos[fix_dir]; + value(v); +} + +void Slider::draw(cairo_t* cr, bool annotate) +{ + cairo_pattern_t* source = cairo_get_source(cr); + double rc, gc, bc, aa; + cairo_pattern_get_rgba(source, &rc, &gc, &bc, &aa); + double lw = cairo_get_line_width(cr); + std::ostringstream os; + os << m_label << ": " << (*m_formatter)(value()); + cairo_set_source_rgba(cr, 0.1, 0.1, 0.7, 1.0); + cairo_set_line_width(cr, 0.7); + m_handle.draw(cr, annotate); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0); + cairo_set_line_width(cr, 0.4); + m_handle.draw(cr, annotate); + cairo_move_to(cr, m_pos[Geom::X], m_pos[Geom::Y]); + Geom::Point offset; + if ( m_dir == Geom::X ) + { + cairo_rel_line_to(cr, m_length, 0); + offset = Geom::Point(0,5); + } + else + { + cairo_rel_line_to(cr, 0, m_length); + offset = Geom::Point(5,0); + } + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + draw_text(cr, m_pos + offset, os.str().c_str()); + cairo_set_source_rgba(cr, rc, gc, bc, aa); + cairo_set_line_width(cr, lw); +} + +void Slider::move_to(void* hit, Geom::Point om, Geom::Point m) +{ + // fix_dir == ! m_dir + Geom::Dim2 fix_dir = static_cast<Geom::Dim2>( (m_dir + 1) % 2 ); + m[fix_dir] = m_pos[fix_dir]; + double diff = m[m_dir] - m_pos[m_dir]; +// if (m_step != 0) +// { +// double step = (m_step * m_length) / (m_max - m_min) ; +// int k = std::floor(diff / step); +// double v = k * step; +// m[m_dir] = v + m_pos[m_dir]; +// } + if ( diff < 0 ) m[m_dir] = m_pos[m_dir]; + if ( diff > m_length ) m[m_dir] = m_pos[m_dir] + m_length; + m_handle.move_to(hit, om, m); +} + + + +void PointHandle::draw(cairo_t *cr, bool /*annotes*/) { + draw_circ(cr, pos); +} + +void* PointHandle::hit(Geom::Point mouse) { + if(Geom::distance(mouse, pos) < 5) + return this; + return 0; +} + +void PointHandle::move_to(void* /*hit*/, Geom::Point /*om*/, Geom::Point m) { + pos = m; +} + +void PointHandle::load(FILE* f) { + pos = read_point(f); +} + +void PointHandle::save(FILE* f) { + fprintf(f, "%lf %lf\n", pos[0], pos[1]); +} + +void PointSetHandle::draw(cairo_t *cr, bool annotes) { + for(unsigned i = 0; i < pts.size(); i++) { + draw_circ(cr, pts[i]); + if(annotes) draw_number(cr, pts[i], i, name); + } +} + +void* PointSetHandle::hit(Geom::Point mouse) { + for(auto & pt : pts) { + if(Geom::distance(mouse, pt) < 5) + return (void*)(&pt); + } + return 0; +} + +void PointSetHandle::move_to(void* hit, Geom::Point /*om*/, Geom::Point m) { + if(hit) { + *(Geom::Point*)hit = m; + } +} + +void PointSetHandle::load(FILE* f) { + int n = 0; + assert(1 == fscanf(f, "%d\n", &n)); + pts.clear(); + for(int i = 0; i < n; i++) { + pts.push_back(read_point(f)); + } +} + +void PointSetHandle::save(FILE* f) { + fprintf(f, "%d\n", (int)pts.size()); + for(auto & pt : pts) { + fprintf(f, "%lf %lf\n", pt[0], pt[1]); + } +} + +#include <2geom/bezier-to-sbasis.h> + +Geom::D2<Geom::SBasis> PointSetHandle::asBezier() { + return handles_to_sbasis(pts.begin(), size()-1); +} + +void RectHandle::draw(cairo_t *cr, bool /*annotes*/) { + cairo_rectangle(cr, pos); + cairo_stroke(cr); + if(show_center_handle) { + draw_circ(cr, pos.midpoint()); + } + draw_text(cr, pos.corner(0), name); +} + +void* RectHandle::hit(Geom::Point mouse) { + if(show_center_handle) { + if(Geom::distance(mouse, pos.midpoint()) < 5) + return (void*)(intptr_t)1; + } + for(int i = 0; i < 4; i++) { + if(Geom::distance(mouse, pos.corner(i)) < 5) + return (void*)(intptr_t)(2+i); + } + for(int i = 0; i < 4; i++) { + Geom::LineSegment ls(pos.corner(i), pos.corner(i+1)); + if(Geom::distance(ls.pointAt(ls.nearestTime(mouse)),mouse) < 5) + return (void*)(intptr_t)(6+i); + } + return 0; + +} + +void RectHandle::move_to(void* hit, Geom::Point om, Geom::Point m) { + using Geom::X; + using Geom::Y; + + unsigned h = (unsigned)(uintptr_t)(hit); + if(h == 1) + pos += (m-om); + else if(h >= 2 and h <= 5) {// corners + int xi = (h-2)& 1; + int yi = (h-2)&2; + if(yi) + xi = 1-xi; // clockwise + if (xi) { + pos[X].setMax(m[0]); + } else { + pos[X].setMin(m[0]); + } + if (yi/2) { + pos[Y].setMax(m[1]); + } else { + pos[Y].setMax(m[1]); + } + } else if(h >= 6 and h <= 9) {// edges + int side, d; + switch(h-6) { + case 0: d = 1; side = 0; break; + case 1: d = 0; side = 1; break; + case 2: d = 1; side = 1; break; + case 3: d = 0; side = 0; break; + } + if (side) { + pos[d].setMax(m[d]); + } else { + pos[d].setMin(m[d]); + } + } +} + +void RectHandle::load(FILE* f) { + assert(0 == fscanf(f, "r\n")); + for(int i = 0; i < 2; i++) { + pos[i] = read_interval(f); + } + +} + +void RectHandle::save(FILE* f) { + fprintf(f, "r\n"); + for(unsigned i = 0; i < 2; i++) { + fprintf(f, "%lf %lf\n", pos[i].min(), pos[i].max()); + } +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/toy-template.cpp b/src/toys/toy-template.cpp new file mode 100644 index 0000000..f7e5090 --- /dev/null +++ b/src/toys/toy-template.cpp @@ -0,0 +1,35 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework.h> + +using namespace Geom; + +class MyToy: public Toy { + virtual void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + //draw code here + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + MyToy () { + //Initialization here + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new MyToy()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/toyframework.py b/src/toys/toyframework.py new file mode 100644 index 0000000..c580c1e --- /dev/null +++ b/src/toys/toyframework.py @@ -0,0 +1,435 @@ +#!/usr/bin/python + +import gtk,math +import pangocairo,cairo +import gobject + +# def draw_text(cr, pos, txt, bottom = False): +# def draw_number(cr, pos, num): + +def draw_circ(cr, (x, y)): + cr.new_sub_path() + cr.arc(x, y, 3, 0, math.pi*2) + cr.stroke() +def draw_cross(cr, (x, y)): + cr.move_to(x-3, y-3) + cr.line_to(x+3, y+3) + cr.move_to(x-3, y+3) + cr.line_to(x+3, y-3) + cr.stroke() + +class Handle: + def __init__(self): + pass + def draw(self, cr, annotes): + pass + def hit(self, (x, y)): + return None + def move_to(self, hit, om, m): + pass + def scroll(self, pos, dir): + pass + +class PointHandle(Handle): + def __init__(self, x, y, name=""): + Handle.__init__(self) + self.pos = (x,y) + self.name = name + def draw(self, cr, annotes): + draw_circ(cr, self.pos) + if annotes: + draw_text(cr, self.pos, str(self.name)) + def hit(self, mouse): + if math.hypot(mouse[0] - self.pos[0], mouse[1] - self.pos[1]) < 5: + return 0 + return None + def move_to(self, hit, om, m): + self.pos = m + +class PointSetHandle(Handle): + def __init__(self, pts=None, name=""): + Handle.__init__(self) + self.pts = pts or [] + self.name = name + def draw(self, cr, annotes): + for p in self.pts: + draw_circ(cr, p) + if annotes: + draw_text(cr, p, str(self.name)) + def hit(self, mouse): + for i,p in enumerate(self.pts): + if math.hypot(mouse[0] - p[0], mouse[1] - p[1]) < 5: + return i + return None + def move_to(self, hit, om, m): + self.pts[hit[1]] = m + def append(self, x,y): + self.pts.append((x,y)) + +class Toy: + def __init__(self): + self.handles = [] + self.mouse_down = False + self.old_mouse = None + self.selected = None + self.notify = "notify" + self.origin = [0,0] + self.interactive_level = 0 + self.transform = None + + def draw(self, cr, (width, height), save): + bounds = self.should_draw_bounds() + self.transform = cr.get_matrix() + self.transform.invert() + if bounds == 1: + cr.set_source_rgba(0., 0., 0, 0.8) + cr.set_line_width (0.5) + for i in [1,3]: + cr.move_to(0, i*width/4) + cr.line_to(width, i*width/4) + cr.move_to(i*width/4, 0) + cr.line_to(i*width/4, height) + elif bounds == 2: + cr.set_source_rgba (0., 0., 0, 0.8) + cr.set_line_width (0.5) + cr.move_to(0, width/2) + cr.line_to(width, width/2) + cr.move_to(width/2, 0) + cr.line_to(width/2, height) + + cr.set_source_rgba (0., 0.5, 0, 1) + cr.set_line_width (1) + annotes = self.should_draw_numbers() + for i,h in enumerate(self.handles): + cr.save() + if self.selected and i == self.selected[0]: + cr.set_source_rgba (0.5, 0, 0, 1) + h.draw(cr, annotes) + cr.restore() + + cr.set_source_rgba (0., 0.5, 0, 0.8) + if self.notify: + cr.save() + cr.identity_matrix() + bnds = draw_text(cr, (0, height), self.notify, True) + l,t,w,h = bnds[1] + cr.set_source_rgba(1,1,1,0.9) + cr.rectangle(l,t+height-h, w, h) + cr.fill() + cr.set_source_rgba(0,0,0,1) + draw_text(cr, (0, height), self.notify, True) + cr.restore() + + def mouse_moved(self, e): + mouse = (e.x, e.y) + if self.transform != None: + mouse = self.transform.transform_point(e.x, e.y) + + if e.state & (gtk.gdk.BUTTON1_MASK | gtk.gdk.BUTTON3_MASK): + self.interactive_level = 2 + if self.selected: + self.handles[self.selected[0]].move_to(self.selected, self.old_mouse, mouse) + self.old_mouse = mouse + self.canvas.queue_draw() + #self.redraw() + def mouse_pressed(self, e): + self.interactive_level = 1 + mouse = (e.x, e.y) + if self.transform != None: + mouse = self.transform.transform_point(e.x, e.y) + if e.button == 1: + for i,h in enumerate(self.handles): + hit = h.hit(mouse) + if hit != None: + self.selected = (i, hit) + self.mouse_down = True + self.old_mouse = mouse + self.canvas.queue_draw() + #self.redraw() + + def mouse_released(self, e): + self.interactive_level = 1 + self.selected = None + if e.button == 1: + self.mouse_down = False + self.canvas.queue_draw() + #self.redraw() + + def scroll_event(self, da, ev): + self.interactive_level = 1 + #print 'scroll:'+'\n'.join([str((x, getattr(ev, x))) for x in dir(ev)]) + #print 'end' + da.queue_draw() + hit = None + for i,h in enumerate(self.handles): + hit = h.scroll((ev.x,ev.y), ev.direction) + if hit != None: + break + return hit != None + + def key_hit(self, e): + pass + + def should_draw_numbers(self): + return True + def should_draw_bounds(self): + return 0 + + def first_time(self, argv): + pass + + def gtk_ready(self): + pass + + def resize_canvas(self, s): + pass + def redraw(self): + self.window.queue_draw() + def get_save_size(self): + return (0,0)+self.window.window.get_size() + def delete_event(self, window, e): + gtk.main_quit() + return False + + def expose_event(self, widget, event): + cr = widget.window.cairo_create() + + width, height = widget.window.get_size() + global resized + + if not resized: + alloc_size = ((0, width), (0, height)) + self.resize_canvas(alloc_size) + resized = True + cr.translate(self.origin[0], self.origin[1]) + self.draw(cr, (width, height), False) + + return True + + def mouse_motion_event(self, widget, e): + e.x -= self.origin[0] + e.y -= self.origin[1] + self.mouse_moved(e) + + return False + + def mouse_event(self, widget, e): + e.x -= self.origin[0] + e.y -= self.origin[1] + self.mouse_pressed(e) + + return False + + def mouse_release_event(self, widget, e): + e.x -= self.origin[0] + e.y -= self.origin[1] + self.mouse_released(e) + + return False + + def key_release_event(self, widget, e): + self.key_hit(e) + + return False + + def size_allocate_event(self, widget, allocation): + alloc_size = ((allocation.x, allocation.x + allocation.width), + (allocation.y, allocation.y+allocation.height)) + self.resize_canvas(alloc_size) + + return False + + def relax_interaction_timeout(self): + if self.interactive_level > 0: + self.interactive_level -= 1 + self.canvas.queue_draw() + return True + + +class Toggle: + def __init__(self): + self.bounds = (0,0,0,0) + self.text = "" + self.on = False + def draw(self, cr): + cr.set_source_rgba(0,0,0,1) + cr.rectangle(bounds.left(), bounds.top(), + bounds.width(), bounds.height()) + if(on): + cr.fill() + cr.set_source_rgba(1,1,1,1) + else: + cr.stroke() + draw_text(cr, bounds.corner(0) + (5,2), text) + + def toggle(self): + self.on = not self.on + def set(self, state): + self.on = state + def handle_click(self, e): + if bounds.contains((e.x, e,y)) and e.button == 1: + toggle() + +def toggle_events(toggles, e): + for t in toggles: + t.handle_click(e) + +def draw_toggles(cr, toggles): + for t in toggles: + t.draw(cr) + + +current_toys = [] + +def draw_text(cr, loc, txt, bottom = False, font="Sans 12"): + import pango + layout = pangocairo.CairoContext.create_layout (cr) + layout.set_font_description (pango.FontDescription(font)) + + layout.set_text(txt) + bounds = layout.get_pixel_extents() + cr.move_to(loc[0], loc[1]) + if bottom: + if bottom == "topright": + cr.move_to(loc[0] - bounds[1][2], + loc[1]) + elif bottom == "bottomright": + cr.move_to(loc[0] - bounds[1][2], + loc[1] - bounds[1][3]) + elif bottom == "topleft": + cr.move_to(loc[0], + loc[1]) + else: + cr.move_to(loc[0], loc[1] - bounds[1][3]) + cr.show_layout(layout) + return bounds + +# Framework Accessors + +# Gui Event Callbacks + +def make_about(evt): + about_window = gtk.Window(gtk.WINDOW_TOPLEVEL) + about_window.set_title("About") + about_window.set_resizable(True) + + about_text = gtk.TextView() + buf = about_text.get_buffer() + buf.set_text("Toy lib2geom application", -1) + about_window.add(about_text) + + about_window.show_all() + +def save_cairo(evt): + d = gtk.FileChooserDialog("Save file as svg, png or pdf", + None, + gtk.FILE_CHOOSER_ACTION_SAVE, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) + + if(d.run() == gtk.RESPONSE_ACCEPT): + filename = d.get_filename() + cr_s = None + left, top, width, height = current_toys[0].get_save_size() + + if filename[-4:] == ".pdf": + cr_s = cairo.PDFSurface(filename, width, height) + elif filename[-4:] == ".png": + cr_s = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height ) + else: + cr_s = cairo.SVGSurface(filename, width, height) + cr = cairo.Context(cr_s) + cr = pangocairo.CairoContext(cairo.Context(cr_s)) + cr.translate(-left, -top) + current_toys[0].draw(cr, (width, height), True) + + cr.show_page() + del cr + del cr_s + d.destroy() + +resized = False + + +def FileMenuAction(): + pass + +ui = """ +<ui> + <menubar> + <menu name="FileMenu" action="FileMenuAction"> + <menuitem name="Save" action="save_cairo" /> + <menuitem name="Quit" action="gtk.main_quit" /> + <placeholder name="FileMenuAdditions" /> + </menu> + <menu name="HelpMenu" action="HelpMenuAction"> + <menuitem name="About" action="make_about" /> + </menu> + </menubar> +</ui> +""" + +def init(argv, t, width, height): + global current_toys + current_toys.append(t) + + t.first_time(argv) + + t.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + t.window.set_title("title") + +# Creates the menu from the menu data above + uim = gtk.UIManager() + ag = gtk.ActionGroup('menu_actiongroup') + ag.add_actions([('FileMenuAction', None, 'File', None, None, None), + ('save_cairo', None, 'Save screenshot', None, None, save_cairo), + ('gtk.main_quit', None, 'Quit', None, None, gtk.main_quit), + ('HelpMenuAction', None, 'Help', None, None, None), + ('make_about', None, 'About', None, None, make_about) + ]) + uim.insert_action_group(ag, 0) + uim.add_ui_from_string(ui) + menu = uim.get_widget("/ui/menubar") + # Creates the menu from the menu data above + #uim = gtk.UIManager() + #uim.add_ui_from_string(ui) + #menu = uim.get_widget("ui/menubar") + + t.window.connect("delete_event", t.delete_event) + + t.canvas = gtk.DrawingArea() + + t.canvas.add_events((gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.KEY_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.SCROLL_MASK)) + + t.canvas.connect("expose_event", t.expose_event) + t.canvas.connect("button_press_event", t.mouse_event) + t.canvas.connect("button_release_event", t.mouse_release_event) + t.canvas.connect("motion_notify_event", t.mouse_motion_event) + t.canvas.connect("key_press_event", t.key_release_event) + t.canvas.connect("size-allocate", t.size_allocate_event) + t.canvas.connect("scroll_event", t.scroll_event) + + t.vbox = gtk.VBox(False, 0) + t.window.add(t.vbox) + + t.vbox.pack_start (menu, False, False, 0) + + pain = gtk.VPaned() + t.vbox.pack_start(pain, True, True, 0) + pain.add1(t.canvas) + + t.canvas.set_size_request(width, height) + t.window.show_all() + + # Make sure the canvas can receive key press events. + t.canvas.set_flags(gtk.CAN_FOCUS) + t.canvas.grab_focus() + assert(t.canvas.is_focus()) + + t.gtk_ready() + gobject.timeout_add(1000, t.relax_interaction_timeout) + + gtk.main() + +def get_vbox(): + return current_toys[0].vbox diff --git a/src/toys/uncross.cpp b/src/toys/uncross.cpp new file mode 100644 index 0000000..aea5438 --- /dev/null +++ b/src/toys/uncross.cpp @@ -0,0 +1,500 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> + +#include <cstdlib> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/ord.h> +using namespace Geom; +using namespace std; + +void draw_rect(cairo_t *cr, Point tl, Point br) { + cairo_move_to(cr, tl[X], tl[Y]); + cairo_line_to(cr, br[X], tl[Y]); + cairo_line_to(cr, br[X], br[Y]); + cairo_line_to(cr, tl[X], br[Y]); + cairo_close_path(cr); +} + +void draw_bounds(cairo_t *cr, PathVector ps) { + srand(0); + vector<Rect> bnds; + for(auto & p : ps) { + for(const auto & it : p) { + Rect bounds = (it.boundsFast()); + bnds.push_back(bounds); + cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5); + //draw_rect(cr, bounds.min(), bounds.max()); + cairo_stroke(cr); + } + } + { + std::vector<std::vector<unsigned> > res = sweep_bounds(bnds); + cairo_set_line_width(cr,0.5); + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + for(unsigned i = 0; i < res.size(); i++) { + for(unsigned j = 0; j < res[i].size(); j++) { + draw_line_seg(cr, bnds[i].midpoint(), bnds[res[i][j]].midpoint()); + cairo_stroke(cr); + } + } + cairo_restore(cr); + } +} + +void mark_verts(cairo_t *cr, PathVector ps) { + for(auto & p : ps) + for(const auto & it : p) + draw_cross(cr, it.initialPoint()); +} + +int winding(PathVector ps, Point p) { + int wind = 0; + for(const auto & i : ps) + wind += winding(i,p); + return wind; +} + +template<typename T> +//std::vector<T>::iterator +T* insort(std::vector<T> &v, const T& val) +{ + T* iter = upper_bound(v.begin(), v.end(), val); + + unsigned offset = iter - v.begin(); + v.insert(iter, val); + + return offset + v.begin(); +} + + +class Uncross{ +public: + class Piece{ + public: + Rect bounds; + Interval parameters; + Curve const* curve; + D2<SBasis> sb; + int mark; + int id; + }; + class Crossing{ + public: + vector<int> joins; + //double crossing_product; // first cross(d^nA, d^nB) for n that is non-zero, or 0 if the two curves are the same + }; + vector<Rect> rs; + PathVector* pths; + cairo_t* cr; + std::vector<Piece> pieces; + + Uncross(PathVector &pt, cairo_t* cr):pths(&pt), cr(cr) {} + + void build() { + cairo_save(cr); + PathVector &ps(*pths); + for(auto & p : ps) { + for(const auto & it : p) { + Rect bounds = (it.boundsExact()); + rs.push_back(bounds); + //cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5); + //draw_rect(cr, bounds.min(), bounds.max()); + cairo_stroke(cr); + pieces.emplace_back(); + pieces.back().bounds = bounds; + pieces.back().curve = ⁢ + pieces.back().parameters = Interval(0,1); + pieces.back().sb = it.toSBasis(); + } + } + cairo_restore(cr); + } + + struct Event { + double x; + unsigned ix; + bool closing; + Event(double pos, unsigned i, bool c) : x(pos), ix(i), closing(c) {} +// Lexicographic ordering by x then closing + bool operator<(Event const &other) const { + if(x < other.x) return true; + if(x > other.x) return false; + return closing < other.closing; + } + + }; + + std::vector<Event> events; + std::vector<unsigned> open; + + int cmpy(Piece* a, Piece* b, Interval X) { + if(a->bounds[Y].max() < b->bounds[Y].min()) { // bounds are strictly ordered + return -1; + } + if(a->bounds[Y].min() > b->bounds[Y].max()) { // bounds are strictly ordered + return 1; + } + std::vector<std::pair<double, double> > xs; + find_intersections(xs, a->sb, b->sb); + if(!xs.empty()) { + polish_intersections( xs, a->sb, b->sb); + // must split around these points to make new Pieces + for(auto & x : xs) { + std::cout << "cross:" << x.first << " , " << x.second << "\n"; + + cairo_save(cr); + draw_circ(cr, a->sb(x.first)); + cairo_stroke(cr); + cairo_restore(cr); + int ix = events[0].ix; + if(0){ + pop_heap(events.begin(), events.end()); + events.pop_back(); + std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix); + open.erase(iter); + std::cout << "kill\n"; + } + + } + } else { // any point gives an order + vector<double> ar = roots(a->sb[0] - X.middle()); + vector<double> br = roots(b->sb[0] - X.middle()); + if ((ar.size() == 1) and (br.size() == 1)) + + return a->sb[1](ar[0]) < b->sb[1](br[0]); + } + return 0; // FIXME + } + + + static void + draw_interval(cairo_t* cr, Interval I, Point origin, Point /*dir*/) { + cairo_save(cr); + cairo_set_line_width(cr, 0.5); + + cairo_move_to(cr, Point(I.min(), -3) + origin); + cairo_line_to(cr, Point(I.min(), +3) + origin); + cairo_move_to(cr, Point(I.max(), -3) + origin); + cairo_line_to(cr, Point(I.max(), +3) + origin); + + cairo_move_to(cr, Point(I.min(), 0) + origin); + cairo_line_to(cr, Point(I.min(), 0) + origin); + cairo_stroke(cr); + cairo_restore(cr); + } + void broke_sweep_bounds() { + cairo_save(cr); + + cairo_set_source_rgb(cr, 1, 0, 0); + events.reserve(rs.size()*2); + std::vector<std::vector<unsigned> > pairs(rs.size()); + + for(unsigned i = 0; i < rs.size(); i++) { + events.emplace_back(rs[i].left(), i, false); + events.emplace_back(rs[i].right(), i, true); + } + //std::sort(events.begin(), events.end()); + std::make_heap(events.begin(), events.end()); + + //for(unsigned i = 0; i < events.size(); i++) { + + int i = 0; + while(!events.empty()) { + unsigned ix = events[0].ix; + if(events[0].closing) { + std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix); + if(iter != open.end()) { + cairo_save(cr); + cairo_set_source_rgb(cr, 0, 1, 0); + cairo_set_line_width(cr, 0.25); + cairo_rectangle(cr, rs[*iter]); + cairo_stroke(cr); + cairo_restore(cr); + open.erase(iter);} + } else { + draw_interval(cr, rs[ix][0], Point(0,5*i+10), Point(0, 1)); + for(unsigned int jx : open) { + OptInterval oiy = intersect(rs[ix][Y], rs[jx][Y]); + if(oiy) { + pairs[jx].push_back(ix); + std::cout << "oiy:" << *oiy << std::endl; + OptInterval oix = intersect(rs[ix][X], rs[jx][X]); + if(oix) { + std::cout << *oix; + std::cout << cmpy(&pieces[ix], &pieces[jx], *oix); + continue; + } + //draw_line_seg(cr, rs[ix].midpoint(), rs[jx].midpoint()); + cairo_stroke(cr); + } + } + open.push_back(ix); + } + pop_heap(events.begin(), events.end()); + events.pop_back(); + i++; + } + //return pairs; + cairo_restore(cr); + } + + void sweep_bounds() { + cairo_save(cr); + + cairo_set_source_rgb(cr, 1, 0, 0); + events.reserve(rs.size()*2); + std::vector<std::vector<unsigned> > pairs(rs.size()); + + for(unsigned i = 0; i < rs.size(); i++) { + events.emplace_back(rs[i].left(), i, false); + events.emplace_back(rs[i].right(), i, true); + } + std::sort(events.begin(), events.end()); + + for(unsigned i = 0; i < events.size(); i++) { + unsigned ix = events[i].ix; + if(events[i].closing) { + std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix); + if(iter != open.end()) { + cairo_save(cr); + cairo_set_source_rgb(cr, 0, 1, 0); + cairo_set_line_width(cr, 0.25); + cairo_rectangle(cr, rs[*iter]); + cairo_stroke(cr); + cairo_restore(cr); + open.erase(iter); + } + } else { + draw_interval(cr, rs[ix][0], Point(0,5*i+10), Point(0, 1)); + for(unsigned int jx : open) { + OptInterval oiy = intersect(rs[ix][Y], rs[jx][Y]); + if(oiy) { + pairs[jx].push_back(ix); + std::cout << "oiy:" << *oiy << std::endl; + OptInterval oix = intersect(rs[ix][X], rs[jx][X]); + if(oix) { + std::cout << *oix; + std::cout << cmpy(&pieces[ix], &pieces[jx], *oix); + continue; + } + //draw_line_seg(cr, rs[ix].midpoint(), rs[jx].midpoint()); + cairo_stroke(cr); + } + } + open.push_back(ix); + } + } + + cairo_restore(cr); + } + + +}; + +class WindingTest: public Toy { + PathVector path; + PointHandle test_pt_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_set_line_width(cr, 0.5); + cairo_save(cr); + cairo_path(cr, path); + cairo_set_source_rgb(cr, 1, 1, 0); + cairo_stroke(cr); + cairo_restore(cr); + //mark_verts(cr, path); + cairo_stroke(cr); + cairo_set_line_width(cr, 1); + //draw_bounds(cr, path); + if(0) { + Uncross uc(path, cr); + uc.build(); + + uc.sweep_bounds(); + + //draw_bounds(cr, path); mark_verts(cr, path); + } + + cairo_save(cr); + PathVector &ps(path); + vector<Rect> rs; + std::vector<Uncross::Piece> pieces; + std::vector<Uncross::Crossing> crosses; + int id_counter = 0; + for(auto & p : ps) { + int piece_start = pieces.size(); + for(Path::iterator it = p.begin(); it != p.end(); ++it) { + Rect bounds = (it->boundsExact()); + rs.push_back(bounds); + /*cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5); + draw_rect(cr, bounds.min(), bounds.max()); + cairo_stroke(cr);*/ + pieces.emplace_back(); + pieces.back().bounds = bounds; + pieces.back().curve = &*it; + pieces.back().parameters = Interval(0,1); + pieces.back().sb = it->toSBasis(); + pieces.back().mark = 0; + pieces.back().id = id_counter++; + if(it != p.begin() and !crosses.empty()) + crosses.back().joins.push_back(pieces.back().id); + crosses.emplace_back(); + crosses.back().joins.push_back(pieces.back().id); + } + crosses.back().joins.push_back(pieces[piece_start].id); + //crosses[cross_start].joins.push_back(pieces.back().id); + } + cairo_restore(cr); + std::vector<std::vector<unsigned> > prs = sweep_bounds(rs); + + cairo_save(cr); + std::vector<Uncross::Piece> new_pieces; + for(unsigned i = 0; i < prs.size(); i++) { + int ix = i; + Uncross::Piece& A = pieces[ix]; + for(int jx : prs) { + Uncross::Piece& B = pieces[jx]; + cairo_set_source_rgb(cr, 0, 1, 0); + draw_line_seg(cr, rs[ix].midpoint(), rs[jx].midpoint()); + cairo_stroke(cr); + cout << ix << ", " << jx << endl; + std::vector<std::pair<double, double> > xs; + find_intersections(xs, A.sb, B.sb); + if(not xs.empty()) { + polish_intersections( xs, A.sb, B.sb); + // must split around these points to make new Pieces + double A_t_prev = 0; + double B_t_prev = 0; + int A_prec_id = A.id; + int B_prec_id = B.id; + for(unsigned cv_idx = 0; cv_idx <= xs.size(); cv_idx++) { + double A_t = 1; + double B_t = 1; + if(cv_idx < xs.size()) { + A_t = xs[cv_idx].first; + B_t = xs[cv_idx].second; + } + + cairo_save(cr); + draw_circ(cr, A.sb(xs[cv_idx].first)); + cairo_stroke(cr); + cairo_restore(cr); + + Interval A_slice(A_t_prev, A_t); + Interval B_slice(B_t_prev, B_t); + if((A_slice.extent() > 0) or (B_slice.extent() > 0)) { + cout << "Aslice" <<A_slice << endl; + D2<SBasis> Asb = portion(A.sb, A_slice); + OptRect Abnds = bounds_exact(Asb); + if(Abnds) { + new_pieces.emplace_back(); + new_pieces.back().bounds = *Abnds; + new_pieces.back().curve = A.curve; + new_pieces.back().parameters = A_slice; + new_pieces.back().sb = Asb; + new_pieces.back().id = id_counter++; + crosses.emplace_back(); + crosses.back().joins.push_back(A_prec_id); + crosses.back().joins.push_back(new_pieces.back().id); + A.mark = 1; + A_prec_id = new_pieces.back().id; + } + + cout << "Bslice" <<B_slice << endl; + D2<SBasis> Bsb = portion(B.sb, B_slice); + OptRect Bbnds = bounds_exact(Bsb); + if(Bbnds) { + new_pieces.emplace_back(); + new_pieces.back().bounds = *Bbnds; + new_pieces.back().curve = B.curve; + new_pieces.back().parameters = B_slice; + new_pieces.back().sb = Bsb; + new_pieces.back().id = id_counter++; + crosses.emplace_back(); + crosses.back().joins.push_back(B_prec_id); + crosses.back().joins.push_back(new_pieces.back().id); + B.mark = 1; + B_prec_id = new_pieces.back().id; + } + } + A_t_prev = A_t; + B_t_prev = B_t; + } + } + } + } + if(1)for(unsigned i = 0; i < prs.size(); i++) { + if(not pieces[i].mark) + new_pieces.push_back(pieces[i]); + } + cairo_restore(cr); + + for(auto & new_piece : new_pieces) { + cout << new_piece.parameters << ", " <<new_piece.id <<endl; + cairo_save(cr); + cairo_rectangle(cr, new_piece.bounds); + cairo_set_source_rgba(cr, 0,1,0,0.1); + cairo_fill(cr); + cairo_set_source_rgba(cr, 0.3,0.3,0,0.1); + cairo_rectangle(cr, new_piece.bounds); + cairo_stroke(cr); + cairo_restore(cr); + cairo_d2_sb(cr, new_piece.sb); + cairo_stroke(cr); + } + + + cout << "crossings:"; + for(auto & cr : crosses) { + for(int join : cr.joins) { + cout << join << ", "; + } + cout << endl; + } + + std::streambuf* cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(notify->rdbuf()); + *notify << "\nwinding:" << winding(path, test_pt_handle.pos) << "\n"; + std::cout.rdbuf(cout_buffer); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + WindingTest () : test_pt_handle(300,300) {} + void first_time(int argc, char** argv) override { + const char *path_name="winding.svgd"; + if(argc > 1) + path_name = argv[1]; + path = read_svgd(path_name); + OptRect bounds = bounds_exact(path); + if (bounds) { + path *= Translate(Point(10,10) - bounds->min()); + } + + handles.push_back(&test_pt_handle); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new WindingTest()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/winding-test.cpp b/src/toys/winding-test.cpp new file mode 100644 index 0000000..a0f7608 --- /dev/null +++ b/src/toys/winding-test.cpp @@ -0,0 +1,107 @@ +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> + +#include <iostream> +#include <cstdlib> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/ord.h> +using namespace Geom; + +void draw_rect(cairo_t *cr, Point tl, Point br) { + cairo_move_to(cr, tl[X], tl[Y]); + cairo_line_to(cr, br[X], tl[Y]); + cairo_line_to(cr, br[X], br[Y]); + cairo_line_to(cr, tl[X], br[Y]); + cairo_close_path(cr); +} + +void draw_bounds(cairo_t *cr, PathVector ps) { + srand(0); + vector<Rect> bnds; + for(auto & p : ps) { + for(const auto & it : p) { + Rect bounds = (it.boundsFast()); + bnds.push_back(bounds); + cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5); + //draw_rect(cr, bounds.min(), bounds.max()); + cairo_stroke(cr); + } + } + { + std::vector<std::vector<unsigned> > res = sweep_bounds(bnds); + cairo_set_line_width(cr,0.5); + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + for(unsigned i = 0; i < res.size(); i++) { + for(unsigned j = 0; j < res[i].size(); j++) { + draw_line_seg(cr, bnds[i].midpoint(), bnds[res[i][j]].midpoint()); + cairo_stroke(cr); + } + } + cairo_restore(cr); + } +} + +void mark_verts(cairo_t *cr, PathVector ps) { + for(auto & p : ps) + for(const auto & it : p) + draw_cross(cr, it.initialPoint()); +} + +int winding(PathVector ps, Point p) { + int wind = 0; + for(const auto & i : ps) + wind += winding(i,p); + return wind; +} + +class WindingTest: public Toy { + PathVector path; + PointHandle test_pt_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_path(cr, path); + cairo_stroke(cr); + mark_verts(cr, path); + draw_bounds(cr, path); + + //draw_bounds(cr, path); mark_verts(cr, path); + + std::streambuf* cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(notify->rdbuf()); + *notify << "\nwinding:" << winding(path, test_pt_handle.pos) << "\n"; + std::cout.rdbuf(cout_buffer); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + WindingTest () : test_pt_handle(300,300) {} + void first_time(int argc, char** argv) override { + const char *path_name="winding.svgd"; + if(argc > 1) + path_name = argv[1]; + path = read_svgd(path_name); + + handles.push_back(&test_pt_handle); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new WindingTest()); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/worms.cpp b/src/toys/worms.cpp new file mode 100644 index 0000000..81e7558 --- /dev/null +++ b/src/toys/worms.cpp @@ -0,0 +1,138 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/svg-path-parser.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +//Random walkers toy, written by mgsloan, initially for a school video proj. + +using namespace Geom; + +static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double min, double max, double space=10){ + for( double t = min; t < max; t += space) { + Point pos = M(t), perp = M.valueAndDerivatives(t, 2)[1].cw() * 3; + draw_line_seg(cr, pos + perp, pos - perp); + } + cairo_stroke(cr); +} + +D2<SBasis> random_d2() { + D2<SBasis> ret(SBasis(6, Linear()), + SBasis(6, Linear())); + ret[0][0] = Linear(uniform()*720, uniform()*720); + ret[1][0] = Linear(uniform()*480, uniform()*480); + + int mul = 1; + for(int i = 1; i < 6; i++) { + ret[0][i] = Linear(uniform()*2000*mul - 1000, uniform()*2000*mul - 1000); + ret[1][i] = Linear(uniform()*2000*mul - 1000, uniform()*2000*mul - 1000); + mul*=2; + } + return ret; +} + +class Worm { + Piecewise<D2<SBasis> > path; + int spawn_time, last_time; + double red, green, blue, length; + public: + void tele(int t) { + Piecewise<D2<SBasis> > new_path(portion(path, 0, t - last_time)); + new_path.push(random_d2(), path.domain().max()+1); + path = arc_length_parametrization(new_path); + } + void add_section(const D2<SBasis> x) { + Piecewise<D2<SBasis> > new_path(path); + D2<SBasis> seg(x); + seg[0][0][0] = path.segs.back()[0][0][1]; + seg[1][0][0] = path.segs.back()[1][0][1]; + new_path.push(seg, path.domain().max()+1); + path = arc_length_parametrization(new_path); + } + Worm (int t, double r, double g, double b, double l) : spawn_time(t), last_time(t), red(r), green(g), blue(b), length(l) { + path = Piecewise<D2<SBasis> >(random_d2()); + add_section(random_d2()); + } + void draw(cairo_t *cr, int t) { + if(t - last_time > path.domain().max()) add_section(random_d2()); + if(t - last_time - length > path.cuts[1]) { + Piecewise<D2<SBasis> > new_path; + new_path.push_cut(0); + for(unsigned i = 1; i < path.size(); i++) { + new_path.push(path[i], path.cuts[i+1] - path.cuts[1]); + } + last_time = t - length; + path = new_path; + } + cairo_set_source_rgb(cr, red, green, blue); + Piecewise<D2<SBasis> > port = portion(path, std::max(t - last_time - length, 0.), t - last_time); + cairo_pw_d2_sb(cr, port); + cairo_stroke(cr); + + double d = 4; + cairo_set_dash(cr, &d, 1, 0); + for(unsigned i = 1; i < path.size(); i++) { + if(path[i].at0() != path[i-1].at1()) { + draw_line_seg(cr, path[i].at0(), path[i-1].at1()); + } + } + cairo_stroke(cr); + cairo_set_dash(cr, &d, 0, 0); + + cairo_set_source_rgb(cr, 0., 0., 1.); + dot_plot(cr, path, std::max(t - last_time - length, 0.), t - last_time); + } + void reverse_direction(int t) { + path = portion(path, 0, t - last_time); + D2<SBasis> seg = random_d2(), last = path[path.size()-1]; + for(unsigned c = 0; c < 2; c++) + for(unsigned d = 1; d < seg[c].size() && d < last[c].size(); d++) + seg[c][d][0] = -last[c][d][1]; + add_section(seg); + } +}; + +class Intro: public Toy { + int t; + vector<Worm> worms; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + t++; + if(t < 40 && t % 2 == 0) { + worms.emplace_back(t, uniform(), uniform(), uniform(), uniform() * 200 + 50); + } + + for(auto & worm : worms) { + worm.draw(cr, t); + if(uniform() > .999) worm.tele(t); + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + redraw(); + } + + int should_draw_bounds() override { return 0; } + + public: + Intro () { + t = 0; + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Intro(), 720, 480); + return 0; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : |