/* * Conic Section Toy * * Authors: * Marco Cecchetti * * 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 #include //#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 #include 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 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 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 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 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 :