diff options
Diffstat (limited to 'src/toys/conic-section-toy.cpp')
-rw-r--r-- | src/toys/conic-section-toy.cpp | 813 |
1 files changed, 813 insertions, 0 deletions
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 : |