1 /* 2 * curves.cc -- polygons, ellipses, circular arcs, splines 3 * 4 * This file is part of ePiX, a C++ library for creating high-quality 5 * figures in LaTeX 6 * 7 * Version 1.2.6 8 * Last Change: January 31, 2009 9 */ 10 11 /* 12 * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 13 * Andrew D. Hwang <rot 13 nujnat at zngupf dot ubylpebff dot rqh> 14 * Department of Mathematics and Computer Science 15 * College of the Holy Cross 16 * Worcester, MA, 01610-2395, USA 17 */ 18 19 /* 20 * ePiX is free software; you can redistribute it and/or modify it 21 * under the terms of the GNU General Public License as published by 22 * the Free Software Foundation; either version 2 of the License, or 23 * (at your option) any later version. 24 * 25 * ePiX is distributed in the hope that it will be useful, but WITHOUT 26 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 27 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 28 * License for more details. 29 * 30 * You should have received a copy of the GNU General Public License 31 * along with ePiX; if not, write to the Free Software Foundation, Inc., 32 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 33 */ 34 35 #include <cmath> 36 37 #include "constants.h" 38 #include "triples.h" 39 #include "errors.h" 40 41 #include "functions.h" 42 #include "pairs.h" 43 44 #include "camera.h" 45 46 #include "active_screen.h" 47 #include "screen.h" 48 49 #include "paint_style.h" 50 51 #include "state.h" 52 #include "domain.h" 53 54 #include "arrow_data.h" 55 #include "path.h" 56 #include "picture.h" 57 58 #include "spline.h" 59 #include "spline_data.h" 60 #include "curves.h" 61 62 namespace ePiX { 63 64 // Simple geometric objects 65 66 // Lines take a stretch factor, roughly in percent line(const P & tail,const P & head,double expand)67 void line(const P& tail, const P& head, double expand) 68 { 69 unsigned int num_pts(cam().is_linear() ? 1 : EPIX_NUM_PTS); 70 71 path data(tail, head, expand, num_pts); 72 data.draw(); 73 } 74 line(const P & tail,const P & head,double expand,unsigned int num_pts)75 void line(const P& tail, const P& head, double expand, 76 unsigned int num_pts) 77 { 78 if (!cam().is_linear()) 79 num_pts = (unsigned int) max(num_pts, EPIX_NUM_PTS); 80 81 path data(tail, head, expand, num_pts); 82 data.draw(); 83 } 84 85 // Line(p1, p2) -- draw uncropped portion of long line through p1, p2 Line(const P & arg1,const P & arg2)86 void Line(const P& arg1, const P& arg2) 87 { 88 P dir(arg2-arg1); 89 const double denom(norm(dir)); 90 91 if (EPIX_EPSILON < denom) 92 { 93 // CAUTION: Magic numbers 94 dir *= 1/denom; 95 line(arg1-100*dir, arg1+100*dir, 0, cam().is_linear() ? 1 : 400); 96 } 97 } // end of Line 98 99 100 // point-slope form Line(const P & tail,double slope)101 void Line(const P& tail, double slope) 102 { 103 Line(tail, tail+P(1, slope, 0)); 104 } 105 106 triangle(const P & p1,const P & p2,const P & p3)107 void triangle(const P& p1, const P& p2, const P& p3) 108 { 109 path data; 110 if (cam().is_linear()) 111 data.pt(p1).pt(p2).pt(p3); 112 else 113 { 114 // Magic number 60 115 const unsigned int N(60); 116 const double dt(1.0/N); 117 118 const P step12(dt*(p2-p1)); 119 const P step23(dt*(p3-p2)); 120 const P step31(dt*(p1-p3)); 121 122 for (unsigned int i=0; i<N; ++i) 123 data.pt(p1 + i*step12); 124 125 for (unsigned int i=0; i<N; ++i) 126 data.pt(p2 + i*step23); 127 128 for (unsigned int i=0; i<N; ++i) 129 data.pt(p3 + i*step31); 130 } 131 132 data.close().fill(the_paint_style().fill_flag()); 133 data.draw(); 134 } 135 quad(const P & p1,const P & p2,const P & p3,const P & p4)136 void quad(const P& p1, const P& p2, const P& p3, const P& p4) 137 { 138 path data; 139 if (cam().is_linear()) 140 data.pt(p1).pt(p2).pt(p3).pt(p4); 141 else 142 { 143 // Magic number 60 -> quad has 240 pts, is printed in one segment 144 const unsigned int N(60); 145 const double dt(1.0/N); 146 147 const P step12(dt*(p2-p1)); 148 const P step23(dt*(p3-p2)); 149 const P step34(dt*(p4-p3)); 150 const P step41(dt*(p1-p4)); 151 152 for (unsigned int i=0; i<N; ++i) 153 data.pt(p1 + i*step12); 154 155 for (unsigned int i=0; i<N; ++i) 156 data.pt(p2 + i*step23); 157 158 for (unsigned int i=0; i<N; ++i) 159 data.pt(p3 + i*step34); 160 161 for (unsigned int i=0; i<N; ++i) 162 data.pt(p4 + i*step41); 163 } 164 165 data.close().fill(the_paint_style().fill_flag()); 166 data.draw(); 167 } 168 169 // Draw coordinate rectangle with opposite corners as given. Arguments 170 // must lie is a plane parallel to a coordinate plane, but not on a 171 // line parallel to a coordinate axis. 172 rect(const P & p1,const P & p2)173 void rect(const P& p1, const P& p2) 174 { 175 P diagonal(p2 - p1); 176 P jump; 177 int perp_count(0); 178 179 // count coordinate axes perp to diagonal and flag normal 180 if (fabs(diagonal|E_1) < EPIX_EPSILON) 181 { 182 ++perp_count; 183 jump = E_2&(diagonal); 184 } 185 if (fabs(diagonal|E_2) < EPIX_EPSILON) 186 { 187 ++perp_count; 188 jump = E_3&(diagonal); 189 } 190 if (fabs(diagonal|E_3) < EPIX_EPSILON) 191 { 192 ++perp_count; 193 jump = E_1&(diagonal); 194 } 195 196 quad(p1, p1+jump, p2, p2-jump); 197 } // end rect 198 dart(const P & tail,const P & head)199 void dart(const P& tail, const P& head) 200 { 201 arrow(tail, head, 0.5); 202 } 203 aarrow(const P & tail,const P & head,double scale)204 void aarrow(const P& tail, const P& head, double scale) 205 { 206 P midpt(0.5*(tail+head)); 207 arrow(midpt, tail, scale); 208 arrow(midpt, head, scale); 209 } 210 ellipse(const P & center,const P & axis1,const P & axis2,double t_min,double t_max,unsigned int num_pts)211 void ellipse(const P& center, const P& axis1, const P& axis2, 212 double t_min, double t_max, unsigned int num_pts) 213 { 214 path data(center, axis1, axis2, t_min, t_max, num_pts); 215 216 217 if (min(fabs(t_max-t_min)/full_turn(), 1) == 1) 218 { 219 data.close(); 220 if (the_paint_style().fill_flag()) 221 data.fill(); 222 } 223 data.draw(); 224 } 225 ellipse(const P & center,const P & axis1,const P & axis2,double t_min,double t_max)226 void ellipse(const P& center, const P& axis1, const P& axis2, 227 double t_min, double t_max) 228 { 229 ellipse(center, axis1, axis2, t_min, t_max, EPIX_NUM_PTS); 230 } 231 232 ellipse(const P & center,const P & axis1,const P & axis2)233 void ellipse(const P& center, const P& axis1, const P& axis2) 234 { 235 ellipse(center, axis1, axis2, 0, full_turn()); 236 } 237 238 ellipse_arc(const P & center,const P & axis1,const P & axis2,double t_min,double t_max)239 void ellipse_arc(const P& center, const P& axis1, const P& axis2, 240 double t_min, double t_max) 241 { 242 ellipse(center, axis1, axis2, t_min, t_max); 243 } 244 245 arrow(const P & tail,const P & head,double scale)246 void arrow(const P& tail, const P& head, double scale) 247 { 248 std::vector<P> shaft(2); 249 shaft.at(0) = tail; 250 shaft.at(1) = head; 251 252 arrow_data data(shaft, tail, head, scale); 253 data.draw(); 254 } 255 ellipse(const P & center,const P & radius)256 void ellipse(const P& center, const P& radius) 257 { 258 ellipse(center, radius.x1()*E_1, radius.x2()*E_2); 259 } 260 261 // Standard half-ellipse functions ellipse_left(const P & center,const P & radius)262 void ellipse_left (const P& center, const P& radius) 263 { 264 ellipse(center, radius.x1()*E_1, radius.x2()*E_2, 265 0.25*full_turn(), 0.75*full_turn()); 266 } 267 ellipse_right(const P & center,const P & radius)268 void ellipse_right (const P& center, const P& radius) 269 { 270 ellipse(center, radius.x1()*E_1, radius.x2()*E_2, 271 -0.25*full_turn(), 0.25*full_turn()); 272 } 273 ellipse_top(const P & center,const P & radius)274 void ellipse_top (const P& center, const P& radius) 275 { 276 ellipse(center, radius.x1()*E_1, radius.x2()*E_2, 0, 0.5*full_turn()); 277 } 278 ellipse_bottom(const P & center,const P & radius)279 void ellipse_bottom (const P& center, const P& radius) 280 { 281 ellipse(center, radius.x1()*E_1, radius.x2()*E_2, -0.5*full_turn(), 0); 282 } 283 arc(const P & center,double r,double start,double finish)284 void arc(const P& center, double r, 285 double start, double finish) 286 { 287 ellipse(center, r*E_1, r*E_2, start, finish); 288 } 289 arrow(const P & center,const P & axis1,const P & axis2,double t_min,double t_max,double scale)290 void arrow(const P& center, const P& axis1, const P& axis2, 291 double t_min, double t_max, double scale) 292 { 293 // EPIX_NUM_PTS pts = one full turn; scale accordingly 294 double frac(fabs(t_max-t_min)/full_turn()); 295 unsigned int num_pts((unsigned int) max(2, ceil(frac*EPIX_NUM_PTS))); 296 297 const double dt((t_max - t_min)/num_pts); 298 299 std::vector<P> shaft(num_pts+1); 300 301 for (unsigned int i=0; i <= num_pts; ++i) 302 { 303 double t(t_min + i*dt); 304 shaft.at(i) = center + ((Cos(t)*axis1)+(Sin(t)*axis2)); 305 } 306 307 arrow_data data(shaft, shaft.at(num_pts-1), shaft.at(num_pts), scale); 308 data.draw(); 309 } 310 311 // circular arcs parallel to (x,y)-plane 312 arc_arrow(const P & center,double r,double start,double finish,double scale)313 void arc_arrow(const P& center, double r, 314 double start, double finish, double scale) 315 { 316 arrow(center, r*E_1, r*E_2, start, finish, scale); 317 } 318 319 320 // quadratic spline spline(const P & p1,const P & p2,const P & p3,unsigned int num_pts)321 void spline(const P& p1, const P& p2, const P& p3, unsigned int num_pts) 322 { 323 path data(p1, p2, p3, num_pts); 324 data.draw(); 325 } 326 spline(const P & p1,const P & p2,const P & p3)327 void spline(const P& p1, const P& p2, const P& p3) 328 { 329 spline(p1, p2, p3, EPIX_NUM_PTS); 330 } 331 arrow(const P & p1,const P & p2,const P & p3,double scale)332 void arrow(const P& p1, const P& p2, const P& p3, double scale) 333 { 334 const unsigned int num_pts(EPIX_NUM_PTS); 335 const double dt(1.0/num_pts); 336 std::vector<P> shaft(num_pts+1); 337 338 for (unsigned int i=0; i <= num_pts; ++i) 339 shaft.at(i) = spl_pt(p1, p2, p3, i*dt); 340 341 arrow_data data(shaft, shaft.at(num_pts-1), shaft.at(num_pts), scale); 342 data.draw(); 343 } 344 345 // cubic spline spline(const P & p1,const P & p2,const P & p3,const P & p4,unsigned int num_pts)346 void spline(const P& p1, const P& p2, 347 const P& p3, const P& p4, unsigned int num_pts) 348 { 349 path data(p1, p2, p3, p4, num_pts); 350 data.draw(); 351 } 352 spline(const P & p1,const P & p2,const P & p3,const P & p4)353 void spline(const P& p1, const P& p2, const P& p3, const P& p4) 354 { 355 spline(p1, p2, p3, p4, EPIX_NUM_PTS); 356 } 357 358 // natural spline through points spline(const std::vector<P> & data,unsigned int num_pts)359 void spline(const std::vector<P>& data, unsigned int num_pts) 360 { 361 n_spline tmp(data, data.at(0) == data.at(data.size()-1)); 362 path trace(tmp.data(num_pts)); 363 364 trace.draw(); 365 } 366 arrow(const P & p1,const P & p2,const P & p3,const P & p4,double scale)367 void arrow(const P& p1, const P& p2, const P& p3, const P& p4, double scale) 368 { 369 const unsigned int num_pts(EPIX_NUM_PTS); 370 const double dt(1.0/num_pts); 371 std::vector<P> shaft(num_pts+1); 372 373 for (unsigned int i=0; i <= num_pts; ++i) 374 shaft.at(i) = spl_pt(p1, p2, p3, p4, i*dt); 375 376 arrow_data data(shaft, shaft.at(num_pts-1), shaft.at(num_pts), scale); 377 data.draw(); 378 } 379 380 381 // n1 x n2 Cartesian grid, where coarse = (n1, n2) grid(const P & p1,const P & p2,mesh coarse,mesh fine)382 void grid(const P& p1, const P& p2, mesh coarse, mesh fine) 383 { 384 P diagonal(p2 - p1); 385 P jump1, jump2; // sides of grid 386 387 int perp_count(0); 388 389 int N1(coarse.n1()); 390 int N2(coarse.n2()); 391 392 // count coordinate axes diagonal is perp to and flag normal 393 if (fabs(diagonal|E_1) < EPIX_EPSILON) 394 { 395 ++perp_count; 396 jump1 = E_2&diagonal; 397 jump2 = E_3&diagonal; 398 399 } 400 if (fabs(diagonal|E_2) < EPIX_EPSILON) 401 { 402 ++perp_count; 403 jump1 = E_3&diagonal; 404 jump2 = E_1&diagonal; 405 } 406 if (fabs(diagonal|E_3) < EPIX_EPSILON) 407 { 408 ++perp_count; 409 jump1 = E_1&diagonal; 410 jump2 = E_2&diagonal; 411 } 412 413 if (perp_count != 1) 414 epix_warning("Ignoring degenerate coordinate grid"); 415 416 else 417 { 418 // grid line spacing 419 P grid_step1((1.0/N1)*jump1); 420 P grid_step2((1.0/N2)*jump2); 421 422 // makes grid subject to filling 423 rect(p1, p1 + jump1 + jump2); 424 425 for (int i=1; i < N1; ++i) 426 line(p1+i*grid_step1, p1+i*grid_step1+jump2, 0, fine.n2()); 427 428 for (int j=1; j < N2; ++j) 429 line(p1+j*grid_step2, p1+j*grid_step2+jump1, 0, fine.n1()); 430 } 431 } 432 433 // Grids that fill bounding_box with default camera grid(const P & p1,const P & p2,unsigned int n1,unsigned int n2)434 void grid(const P& p1, const P& p2, unsigned int n1, unsigned int n2) 435 { 436 grid(p1, p2, mesh(n1, n2), mesh(1,1)); 437 } 438 grid(unsigned int n1,unsigned int n2)439 void grid(unsigned int n1, unsigned int n2) 440 { 441 grid(active_screen()->bl(), active_screen()->tr(), n1, n2); 442 } 443 444 445 // polar grid with specified radius, mesh (rings and sectors), and resolution polar_grid(double radius,mesh coarse,mesh fine)446 void polar_grid(double radius, mesh coarse, mesh fine) 447 { 448 for (int i=1; i <= coarse.n1(); ++i) 449 ellipse(P(0,0,0), 450 (i*radius/coarse.n1())*E_1, (i*radius/coarse.n1())*E_2, 451 0, full_turn(), fine.n2()); 452 453 for (int j=0; j < coarse.n2(); ++j) 454 line(P(0,0,0), polar(radius, j*(full_turn())/coarse.n2()), 455 0, 2*fine.n1()); 456 } 457 polar_grid(double radius,unsigned int n1,unsigned int n2)458 void polar_grid(double radius, unsigned int n1, unsigned int n2) 459 { 460 polar_grid(radius, mesh(n1,n2), mesh(n1,EPIX_NUM_PTS)); 461 } 462 463 464 // logarithmic grids 465 466 // local helpers grid_lines1_log(double x_lo,double x_hi,double y_lo,double y_hi,unsigned int segs,unsigned int base)467 void grid_lines1_log(double x_lo, double x_hi, double y_lo, double y_hi, 468 unsigned int segs, unsigned int base) 469 { 470 if (segs == 0) 471 return; 472 473 const double dx((x_hi-x_lo)/segs); // "major grid" steps 474 const double denom(log(base)); // "minor"/log grid scale factor 475 476 for (unsigned int i=0; i < segs; ++i) 477 for (unsigned int j=1; j<base; ++j) 478 { 479 double x_tmp(x_lo + dx*(i+log(j)/denom)); 480 481 line(P(x_tmp, y_lo), P(x_tmp, y_hi)); 482 } 483 484 line(P(x_hi,y_lo), P(x_hi, y_hi)); // draw rightmost line manually 485 } 486 grid_lines2_log(double x_lo,double x_hi,double y_lo,double y_hi,unsigned int segs,unsigned int base)487 void grid_lines2_log(double x_lo, double x_hi, double y_lo, double y_hi, 488 unsigned int segs, unsigned int base) 489 { 490 if (segs == 0) 491 return; 492 493 const double dy((y_hi-y_lo)/segs); 494 const double denom(log(base)); 495 496 for (unsigned int i=0; i < segs; ++i) 497 for (unsigned int j=1; j<base; ++j) 498 { 499 double y_tmp(y_lo + dy*(i+log(j)/denom)); 500 501 line(P(x_lo, y_tmp), P(x_hi, y_tmp)); 502 } 503 504 line(P(x_hi,y_lo), P(x_hi, y_hi)); 505 } 506 507 // global functions log_grid(const P & p1,const P & p2,unsigned int segs1,unsigned int segs2,unsigned int base1,unsigned int base2)508 void log_grid(const P& p1, const P& p2, 509 unsigned int segs1, unsigned int segs2, 510 unsigned int base1, unsigned int base2) 511 { 512 grid_lines1_log(min(p1.x1(), p2.x1()), max(p1.x1(), p2.x1()), 513 min(p1.x2(), p2.x2()), max(p1.x2(), p2.x2()), 514 segs1, base1); 515 516 grid_lines2_log(min(p1.x1(), p2.x1()), max(p1.x1(), p2.x1()), 517 min(p1.x2(), p2.x2()), max(p1.x2(), p2.x2()), 518 segs2, base2); 519 } 520 log1_grid(const P & p1,const P & p2,unsigned int segs1,unsigned int segs2,unsigned int base1)521 void log1_grid(const P& p1, const P& p2, 522 unsigned int segs1, unsigned int segs2, 523 unsigned int base1) 524 { 525 grid_lines1_log(min(p1.x1(), p2.x1()), max(p1.x1(), p2.x1()), 526 min(p1.x2(), p2.x2()), max(p1.x2(), p2.x2()), 527 segs1, base1); 528 529 grid_lines2_log(min(p1.x1(), p2.x1()), max(p1.x1(), p2.x1()), 530 min(p1.x2(), p2.x2()), max(p1.x2(), p2.x2()), 531 segs2, 2); 532 } 533 log2_grid(const P & p1,const P & p2,unsigned int segs1,unsigned int segs2,unsigned int base2)534 void log2_grid(const P& p1, const P& p2, 535 unsigned int segs1, unsigned int segs2, 536 unsigned int base2) 537 { 538 grid_lines1_log(min(p1.x1(), p2.x1()), 539 max(p1.x1(), p2.x1()), 540 min(p1.x2(), p2.x2()), 541 max(p1.x2(), p2.x2()), 542 segs1, 2); 543 544 grid_lines2_log(min(p1.x1(), p2.x1()), 545 max(p1.x1(), p2.x1()), 546 min(p1.x2(), p2.x2()), 547 max(p1.x2(), p2.x2()), 548 segs2, base2); 549 } 550 551 552 // grids fill current bounding box log_grid(unsigned int segs1,unsigned int segs2,unsigned int base1,unsigned int base2)553 void log_grid(unsigned int segs1, unsigned int segs2, 554 unsigned int base1, unsigned int base2) 555 { 556 grid_lines1_log(active_screen()->h_min(), active_screen()->h_max(), 557 active_screen()->v_min(), active_screen()->v_max(), 558 segs1, base1); 559 560 grid_lines2_log(active_screen()->h_min(), active_screen()->h_max(), 561 active_screen()->v_min(), active_screen()->v_max(), 562 segs2, base2); 563 } 564 log1_grid(unsigned int segs1,unsigned int segs2,unsigned int base1)565 void log1_grid(unsigned int segs1, unsigned int segs2, 566 unsigned int base1) 567 { 568 grid_lines1_log(active_screen()->h_min(), active_screen()->h_max(), 569 active_screen()->v_min(), active_screen()->v_max(), 570 segs1, base1); 571 572 grid_lines2_log(active_screen()->h_min(), active_screen()->h_max(), 573 active_screen()->v_min(), active_screen()->v_max(), 574 segs2, 2); 575 } 576 log2_grid(unsigned int segs1,unsigned int segs2,unsigned int base2)577 void log2_grid(unsigned int segs1, unsigned int segs2, 578 unsigned int base2) 579 { 580 grid_lines1_log(active_screen()->h_min(), active_screen()->h_max(), 581 active_screen()->v_min(), active_screen()->v_max(), 582 segs1, 2); 583 584 grid_lines2_log(active_screen()->h_min(), active_screen()->h_max(), 585 active_screen()->v_min(), active_screen()->v_max(), 586 segs2, base2); 587 } 588 589 590 // fractal generation 591 // 592 // The basic recursion unit is a piecewise-linear path whose segments 593 // are parallel to spokes on a wheel, labelled modulo <spokes>. 594 // Recursively up to <depth>, each segment is replaced by a copy of the 595 // recursion unit, scaled and rotated in order to join p to q. 596 // 597 // Kludge: pre_seed[0] = spokes, pre_seed[1] = length of seed; 598 // 599 // Sample data for _/\_ standard Koch snowflake: 600 // const int seed[] = {6, 4, 0, 1, -1, 0}; 601 jump(int spokes,int length,const std::vector<int> & seed)602 P jump(int spokes, int length, const std::vector<int>& seed) 603 { 604 P sum(P(0,0)); 605 606 for (int i=0; i< length; ++i) 607 sum += cis(seed.at(i)*(full_turn())/spokes); 608 609 return sum; 610 } 611 fractal(const P & p,const P & q,const int depth,const int * pre_seed)612 void fractal(const P& p, const P& q, const int depth, const int *pre_seed) 613 { 614 int spokes(pre_seed[0]); 615 int seed_length(pre_seed[1]); 616 std::vector<int> seed(seed_length); 617 618 // extract seed from pre_seed 619 for (int i=0; i<seed_length; ++i) 620 seed.at(i) = pre_seed[i+2]; 621 622 // Unit-length steps in <seed> sequence add up to <scale> 623 P scale(jump(spokes, seed_length, seed)); 624 625 // Number of points in final fractal 626 int length(1+(int)pow(seed_length, depth)); 627 std::vector<int> dir(length); // stepping information 628 std::vector<P> data(length); // vertices 629 630 // dir[] starts out [0, 1, -1, 0, ..., 0] (seed_length^depth entries) 631 // then take repeated "Kronecker sum" with seed = [0, 1, -1, 0] 632 633 for(int i=0; i<seed_length; ++i) 634 dir.at(i) = seed.at(i); 635 636 for(int i=1; i<depth; ++i) // recursively fill dir array 637 for(int j=0; j < pow(seed_length,i); ++j) 638 for(int k=seed_length-1; 0 < k; --k) 639 dir.at(k*(int)pow(seed_length,i) + j) = dir.at(j) + seed.at(k); 640 641 P curr(p); 642 // 10/09/06: -depth -> 1-depth 643 double radius(pow(norm(scale), 1-depth)); 644 645 for(int i=0; i<length; ++i) 646 { 647 data.at(i) = curr; 648 // increment between successive points as a pair 649 pair temp((polar(radius, dir.at(i)*(full_turn())/spokes))*pair(q-p)); 650 651 // complex arithmetic 652 temp /= pair(scale); // homothety to join p to q 653 654 curr += P(temp.x1(), temp.x2()); 655 } 656 657 path fractal(data, false, false); 658 fractal.draw(); 659 } 660 661 } // end of namespace 662