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/kinematic_templates.cpp | |
parent | Initial commit. (diff) | |
download | lib2geom-upstream.tar.xz lib2geom-upstream.zip |
Adding upstream version 1.3.upstream/1.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/toys/kinematic_templates.cpp')
-rw-r--r-- | src/toys/kinematic_templates.cpp | 365 |
1 files changed, 365 insertions, 0 deletions
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 : |