/* * curves.cc -- polygons, ellipses, circular arcs, splines * * This file is part of ePiX, a C++ library for creating high-quality * figures in LaTeX * * Version 1.2.0-2 * Last Change: September 26, 2007 */ /* * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007 * Andrew D. Hwang * Department of Mathematics and Computer Science * College of the Holy Cross * Worcester, MA, 01610-2395, USA */ /* * ePiX is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ePiX is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public * License for more details. * * You should have received a copy of the GNU General Public License * along with ePiX; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include "constants.h" #include "triples.h" #include "errors.h" #include "functions.h" #include "pairs.h" #include "camera.h" #include "active_screen.h" #include "screen.h" #include "paint_style.h" #include "state.h" #include "domain.h" #include "arrow_data.h" #include "path.h" #include "picture.h" #include "spline.h" #include "spline_data.h" #include "curves.h" namespace ePiX { // Simple geometric objects // Lines take a stretch factor, roughly in percent void line(const P& tail, const P& head, double expand) { unsigned int num_pts(cam().is_linear() ? 2 : EPIX_NUM_PTS); path data(tail, head, expand, num_pts); data.draw(); } void line(const P& tail, const P& head, double expand, unsigned int num_pts) { if (!cam().is_linear()) num_pts = (unsigned int) max(num_pts, EPIX_NUM_PTS); path data(tail, head, expand, num_pts); data.draw(); } // Line(p1, p2) -- draw uncropped portion of long line through p1, p2 void Line(const P& arg1, const P& arg2) { P dir(arg2-arg1); const double denom(norm(dir)); if (EPIX_EPSILON < denom) { // TO DO: Not as robust as could be: // 1. Assumes endpoint(s) are fairly close to origin // 2. May not handle non-linear lenses well dir *= 1/denom; line(arg1-EPIX_INFTY*dir, arg1+EPIX_INFTY*dir); } } // end of Line // point-slope form void Line(const P& tail, double slope) { Line(tail, tail+P(1, slope, 0)); } void triangle(const P& p1, const P& p2, const P& p3) { path data; if (cam().is_linear()) data.pt(p1).pt(p2).pt(p3); else { // Magic number 60 const unsigned int N(60); const double dt(1.0/N); const P step12(dt*(p2-p1)); const P step23(dt*(p3-p2)); const P step31(dt*(p1-p3)); for (unsigned int i=0; i quad has 240 pts, is printed in one segment const unsigned int N(60); const double dt(1.0/N); const P step12(dt*(p2-p1)); const P step23(dt*(p3-p2)); const P step34(dt*(p4-p3)); const P step41(dt*(p1-p4)); for (unsigned int i=0; i shaft(2); shaft.at(0) = tail; shaft.at(1) = head; arrow_data data(shaft, tail, head, scale); data.draw(); } void ellipse(const P& center, const P& radius) { ellipse(center, radius.x1()*E_1, radius.x2()*E_2); } // Standard half-ellipse functions void ellipse_left (const P& center, const P& radius) { ellipse(center, radius.x1()*E_1, radius.x2()*E_2, 0.25*full_turn(), 0.75*full_turn()); } void ellipse_right (const P& center, const P& radius) { ellipse(center, radius.x1()*E_1, radius.x2()*E_2, -0.25*full_turn(), 0.25*full_turn()); } void ellipse_top (const P& center, const P& radius) { ellipse(center, radius.x1()*E_1, radius.x2()*E_2, 0, 0.5*full_turn()); } void ellipse_bottom (const P& center, const P& radius) { ellipse(center, radius.x1()*E_1, radius.x2()*E_2, -0.5*full_turn(), 0); } void arc(const P& center, double r, double start, double finish) { ellipse(center, r*E_1, r*E_2, start, finish); } void arrow(const P& center, const P& axis1, const P& axis2, double t_min, double t_max, double scale) { // EPIX_NUM_PTS pts = one full turn; scale accordingly double frac(fabs(t_max-t_min)/full_turn()); unsigned int num_pts((unsigned int) max(2, ceil(frac*EPIX_NUM_PTS))); const double dt((t_max - t_min)/num_pts); std::vector

shaft(num_pts+1); for (unsigned int i=0; i <= num_pts; ++i) { double t(t_min + i*dt); shaft.at(i) = center + ((Cos(t)*axis1)+(Sin(t)*axis2)); } arrow_data data(shaft, shaft.at(num_pts-1), shaft.at(num_pts), scale); data.draw(); } // circular arcs parallel to (x,y)-plane void arc_arrow(const P& center, double r, double start, double finish, double scale) { arrow(center, r*E_1, r*E_2, start, finish, scale); } // quadratic spline void spline(const P& p1, const P& p2, const P& p3, unsigned int num_pts) { path data(p1, p2, p3, num_pts); data.draw(); } void spline(const P& p1, const P& p2, const P& p3) { spline(p1, p2, p3, EPIX_NUM_PTS); } void arrow(const P& p1, const P& p2, const P& p3, double scale) { const unsigned int num_pts(EPIX_NUM_PTS); const double dt(1.0/num_pts); std::vector

shaft(num_pts+1); for (unsigned int i=0; i <= num_pts; ++i) shaft.at(i) = spl_pt(p1, p2, p3, i*dt); arrow_data data(shaft, shaft.at(num_pts-1), shaft.at(num_pts), scale); data.draw(); } // cubic spline void spline(const P& p1, const P& p2, const P& p3, const P& p4, unsigned int num_pts) { path data(p1, p2, p3, p4, num_pts); data.draw(); } void spline(const P& p1, const P& p2, const P& p3, const P& p4) { spline(p1, p2, p3, p4, EPIX_NUM_PTS); } // natural spline through points void spline(const std::vector

& data, unsigned int num_pts) { n_spline tmp(data, data.at(0) == data.at(data.size()-1)); path trace(tmp.data(num_pts)); trace.draw(); } void arrow(const P& p1, const P& p2, const P& p3, const P& p4, double scale) { const unsigned int num_pts(EPIX_NUM_PTS); const double dt(1.0/num_pts); std::vector

shaft(num_pts+1); for (unsigned int i=0; i <= num_pts; ++i) shaft.at(i) = spl_pt(p1, p2, p3, p4, i*dt); arrow_data data(shaft, shaft.at(num_pts-1), shaft.at(num_pts), scale); data.draw(); } // n1 x n2 Cartesian grid, where coarse = (n1, n2) void grid(const P& p1, const P& p2, mesh coarse, mesh fine) { P diagonal(p2 - p1); P jump1, jump2; // sides of grid int perp_count(0); int N1(coarse.n1()); int N2(coarse.n2()); // count coordinate axes diagonal is perp to and flag normal if (fabs(diagonal|E_1) < EPIX_EPSILON) { ++perp_count; jump1 = E_2&diagonal; jump2 = E_3&diagonal; } if (fabs(diagonal|E_2) < EPIX_EPSILON) { ++perp_count; jump1 = E_3&diagonal; jump2 = E_1&diagonal; } if (fabs(diagonal|E_3) < EPIX_EPSILON) { ++perp_count; jump1 = E_1&diagonal; jump2 = E_2&diagonal; } if (perp_count != 1) epix_warning("Ignoring degenerate coordinate grid"); else { // grid line spacing P grid_step1((1.0/N1)*jump1); P grid_step2((1.0/N2)*jump2); // makes grid subject to filling rect(p1, p1 + jump1 + jump2); for (int i=1; i < N1; ++i) line(p1+i*grid_step1, p1+i*grid_step1+jump2, 0, fine.n2()); for (int j=1; j < N2; ++j) line(p1+j*grid_step2, p1+j*grid_step2+jump1, 0, fine.n1()); } } // Grids that fill bounding_box with default camera void grid(const P& p1, const P& p2, unsigned int n1, unsigned int n2) { grid(p1, p2, mesh(n1, n2), mesh(1,1)); } void grid(unsigned int n1, unsigned int n2) { grid(active_screen()->bl(), active_screen()->tr(), n1, n2); } // polar grid with specified radius, mesh (rings and sectors), and resolution void polar_grid(double radius, mesh coarse, mesh fine) { for (int i=1; i <= coarse.n1(); ++i) ellipse(P(0,0,0), (i*radius/coarse.n1())*E_1, (i*radius/coarse.n1())*E_2, 0, full_turn(), fine.n2()); for (int j=0; j < coarse.n2(); ++j) line(P(0,0,0), polar(radius, j*(full_turn())/coarse.n2()), 0, 2*fine.n1()); } void polar_grid(double radius, unsigned int n1, unsigned int n2) { polar_grid(radius, mesh(n1,n2), mesh(n1,EPIX_NUM_PTS)); } // logarithmic grids // local helpers void grid_lines1_log(double x_lo, double x_hi, double y_lo, double y_hi, unsigned int segs, unsigned int base) { if (segs == 0) return; const double dx((x_hi-x_lo)/segs); // "major grid" steps const double denom(log(base)); // "minor"/log grid scale factor for (unsigned int i=0; i < segs; ++i) for (unsigned int j=1; jh_min(), active_screen()->h_max(), active_screen()->v_min(), active_screen()->v_max(), segs1, base1); grid_lines2_log(active_screen()->h_min(), active_screen()->h_max(), active_screen()->v_min(), active_screen()->v_max(), segs2, base2); } void log1_grid(unsigned int segs1, unsigned int segs2, unsigned int base1) { grid_lines1_log(active_screen()->h_min(), active_screen()->h_max(), active_screen()->v_min(), active_screen()->v_max(), segs1, base1); grid_lines2_log(active_screen()->h_min(), active_screen()->h_max(), active_screen()->v_min(), active_screen()->v_max(), segs2, 2); } void log2_grid(unsigned int segs1, unsigned int segs2, unsigned int base2) { grid_lines1_log(active_screen()->h_min(), active_screen()->h_max(), active_screen()->v_min(), active_screen()->v_max(), segs1, 2); grid_lines2_log(active_screen()->h_min(), active_screen()->h_max(), active_screen()->v_min(), active_screen()->v_max(), segs2, base2); } // fractal generation // // The basic recursion unit is a piecewise-linear path whose segments // are parallel to spokes on a wheel, labelled modulo . // Recursively up to , each segment is replaced by a copy of the // recursion unit, scaled and rotated in order to join p to q. // // Kludge: pre_seed[0] = spokes, pre_seed[1] = length of seed; // // Sample data for _/\_ standard Koch snowflake: // const int seed[] = {6, 4, 0, 1, -1, 0}; P jump(int spokes, int length, const std::vector& seed) { P sum(P(0,0)); for (int i=0; i< length; ++i) sum += cis(seed.at(i)*(full_turn())/spokes); return sum; } void fractal(const P& p, const P& q, const int depth, const int *pre_seed) { int spokes(pre_seed[0]); int seed_length(pre_seed[1]); std::vector seed(seed_length); // extract seed from pre_seed for (int i=0; i sequence add up to P scale(jump(spokes, seed_length, seed)); // Number of points in final fractal int length(1+(int)pow(seed_length, depth)); std::vector dir(length); // stepping information std::vector

data(length); // vertices // dir[] starts out [0, 1, -1, 0, ..., 0] (seed_length^depth entries) // then take repeated "Kronecker sum" with seed = [0, 1, -1, 0] for(int i=0; i 1-depth double radius(pow(norm(scale), 1-depth)); for(int i=0; i