Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
geom.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Specific geometry functions for Inkscape, not provided my lib2geom.
4 *
5 * Author:
6 * Johan Engelen <goejendaagh@zonnet.nl>
7 *
8 * Copyright (C) 2008 Johan Engelen
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include <algorithm>
14#include <array>
15#include <cmath>
16#include "helper/geom.h"
17#include "helper/geom-curves.h"
18#include <glib.h>
19#include <2geom/curves.h>
22#include <2geom/convex-hull.h>
23
24using Geom::X;
25using Geom::Y;
26
27//#################################################################################
28// BOUNDING BOX CALCULATIONS
29
30/* Fast bbox calculation */
31/* Thanks to Nathan Hurst for suggesting it */
32static void
34{
35 Geom::Coord a, b, c, D;
36
37 bbox[0].expandTo(x111);
38 bbox[1].expandTo(y111);
39
40 // It already contains (x000,y000) and (x111,y111)
41 // All points of the Bezier lie in the convex hull of (x000,y000), (x001,y001), (x011,y011) and (x111,y111)
42 // So, if it also contains (x001,y001) and (x011,y011) we don't have to compute anything else!
43 // Note that we compute it for the X and Y range separately to make it easier to use them below
44 bool containsXrange = bbox[0].contains(x001) && bbox[0].contains(x011);
45 bool containsYrange = bbox[1].contains(y001) && bbox[1].contains(y011);
46
47 /*
48 * xttt = s * (s * (s * x000 + t * x001) + t * (s * x001 + t * x011)) + t * (s * (s * x001 + t * x011) + t * (s * x011 + t * x111))
49 * xttt = s * (s2 * x000 + s * t * x001 + t * s * x001 + t2 * x011) + t * (s2 * x001 + s * t * x011 + t * s * x011 + t2 * x111)
50 * xttt = s * (s2 * x000 + 2 * st * x001 + t2 * x011) + t * (s2 * x001 + 2 * st * x011 + t2 * x111)
51 * xttt = s3 * x000 + 2 * s2t * x001 + st2 * x011 + s2t * x001 + 2st2 * x011 + t3 * x111
52 * xttt = s3 * x000 + 3s2t * x001 + 3st2 * x011 + t3 * x111
53 * xttt = s3 * x000 + (1 - s) 3s2 * x001 + (1 - s) * (1 - s) * 3s * x011 + (1 - s) * (1 - s) * (1 - s) * x111
54 * xttt = s3 * x000 + (3s2 - 3s3) * x001 + (3s - 6s2 + 3s3) * x011 + (1 - 2s + s2 - s + 2s2 - s3) * x111
55 * xttt = (x000 - 3 * x001 + 3 * x011 - x111) * s3 +
56 * ( 3 * x001 - 6 * x011 + 3 * x111) * s2 +
57 * ( 3 * x011 - 3 * x111) * s +
58 * ( x111)
59 * xttt' = (3 * x000 - 9 * x001 + 9 * x011 - 3 * x111) * s2 +
60 * ( 6 * x001 - 12 * x011 + 6 * x111) * s +
61 * ( 3 * x011 - 3 * x111)
62 */
63
64 if (!containsXrange) {
65 a = 3 * x000 - 9 * x001 + 9 * x011 - 3 * x111;
66 b = 6 * x001 - 12 * x011 + 6 * x111;
67 c = 3 * x011 - 3 * x111;
68
69 /*
70 * s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a;
71 */
72 if (fabs (a) < Geom::EPSILON) {
73 /* s = -c / b */
74 if (fabs (b) > Geom::EPSILON) {
75 double s;
76 s = -c / b;
77 if ((s > 0.0) && (s < 1.0)) {
78 double t = 1.0 - s;
79 double xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111;
80 bbox[0].expandTo(xttt);
81 }
82 }
83 } else {
84 /* s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a; */
85 D = b * b - 4 * a * c;
86 if (D >= 0.0) {
87 Geom::Coord d, s, t, xttt;
88 /* Have solution */
89 d = sqrt (D);
90 s = (-b + d) / (2 * a);
91 if ((s > 0.0) && (s < 1.0)) {
92 t = 1.0 - s;
93 xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111;
94 bbox[0].expandTo(xttt);
95 }
96 s = (-b - d) / (2 * a);
97 if ((s > 0.0) && (s < 1.0)) {
98 t = 1.0 - s;
99 xttt = s * s * s * x000 + 3 * s * s * t * x001 + 3 * s * t * t * x011 + t * t * t * x111;
100 bbox[0].expandTo(xttt);
101 }
102 }
103 }
104 }
105
106 if (!containsYrange) {
107 a = 3 * y000 - 9 * y001 + 9 * y011 - 3 * y111;
108 b = 6 * y001 - 12 * y011 + 6 * y111;
109 c = 3 * y011 - 3 * y111;
110
111 if (fabs (a) < Geom::EPSILON) {
112 /* s = -c / b */
113 if (fabs (b) > Geom::EPSILON) {
114 double s;
115 s = -c / b;
116 if ((s > 0.0) && (s < 1.0)) {
117 double t = 1.0 - s;
118 double yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111;
119 bbox[1].expandTo(yttt);
120 }
121 }
122 } else {
123 /* s = (-b +/- sqrt (b * b - 4 * a * c)) / 2 * a; */
124 D = b * b - 4 * a * c;
125 if (D >= 0.0) {
126 Geom::Coord d, s, t, yttt;
127 /* Have solution */
128 d = sqrt (D);
129 s = (-b + d) / (2 * a);
130 if ((s > 0.0) && (s < 1.0)) {
131 t = 1.0 - s;
132 yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111;
133 bbox[1].expandTo(yttt);
134 }
135 s = (-b - d) / (2 * a);
136 if ((s > 0.0) && (s < 1.0)) {
137 t = 1.0 - s;
138 yttt = s * s * s * y000 + 3 * s * s * t * y001 + 3 * s * t * t * y011 + t * t * t * y111;
139 bbox[1].expandTo(yttt);
140 }
141 }
142 }
143 }
144}
145
148{
149 return bounds_exact_transformed(pv, t); //use this as it is faster for now! :)
150// return Geom::bounds_fast(pv * t);
151}
152
154{
155 if (pv.empty()) {
156 return {};
157 }
158
159 auto const initial = pv.initialPoint() * t;
160
161 // Obtain non-empty initial bbox to avoid having to deal with OptRect.
162 auto bbox = Geom::Rect(initial, initial);
163
164 for (auto const &path : pv) {
165 bbox.expandTo(path.initialPoint() * t);
166
167 // Don't loop including closing segment, since that segment can never increase the bbox.
168 for (auto curve = path.begin(); curve != path.end_open(); ++curve) {
169 curve->expandToTransformed(bbox, t);
170 }
171 }
172
173 return bbox;
174}
175
176bool pathv_similar(Geom::PathVector const &apv, Geom::PathVector const &bpv, double precision)
177{
178 if (apv == bpv) {
179 return true;
180 }
181 size_t totala = apv.curveCount();
182 if (totala != bpv.curveCount()) {
183 return false;
184 }
185 for (size_t i = 0; i < totala; i++) {
186 for (auto f : { 0.2, 0.4, 0.0 }) {
187 if (!Geom::are_near(apv.pointAt(i + f), bpv.pointAt(i + f), precision)) {
188 return false;
189 }
190 }
191 }
192 return true;
193}
194
195/*
196* Checks if the path describes a simple rectangle, returning true and populating rect if it does.
197*/
198Geom::OptRect check_simple_rect(Geom::PathVector const &pathv, double precision)
199{
200 // "Simple rectangle" is a single path with 4 line segments.
201 if (pathv.size() != 1) {
202 return {};
203 }
204
205 auto const &path = pathv.front();
206 // might be a better way to check if all the curves are line segments
207 if (!path.closed() || path.size() != 4 ||
208 std::any_of(path.begin(), path.end(), [](auto const &curve) { return !curve.isLineSegment(); }))
209 {
210 return {};
211 }
212
213 auto const p0 = path.initialPoint();
214 auto start = p0;
215 int prev_change = 0;
216 for (auto const &curve : path) {
217 auto end = curve.finalPoint();
218 // define a change in X as 1, change in Y as -1. Both is 0
219 int const change = !Geom::are_near(start.x(), end.x(), precision) - !Geom::are_near(start.y(), end.y(), precision);
220 if (change == 0 || change == prev_change) {
221 // Changing in both x and y, or continuing the same direction as the previous segment.
222 return {};
223 }
224 start = end;
225 prev_change = change;
226 }
227
228 // if we've made it this far, we can assume that the final point of the second segment is the opposite corner
229 auto const p1 = path[1].finalPoint();
230 return Geom::Rect(p0, p1);
231}
232
233static void
235{
236 Geom::Coord Ax, Ay, Bx, By, Dx, Dy, s;
237 Geom::Coord dist2;
238
239 /* Find distance */
240 Ax = x0;
241 Ay = y0;
242 Bx = x1;
243 By = y1;
244 Dx = x1 - x0;
245 Dy = y1 - y0;
246 const Geom::Coord Px = pt[X];
247 const Geom::Coord Py = pt[Y];
248
249 if (best) {
250 s = ((Px - Ax) * Dx + (Py - Ay) * Dy) / (Dx * Dx + Dy * Dy);
251 if (s <= 0.0) {
252 dist2 = (Px - Ax) * (Px - Ax) + (Py - Ay) * (Py - Ay);
253 } else if (s >= 1.0) {
254 dist2 = (Px - Bx) * (Px - Bx) + (Py - By) * (Py - By);
255 } else {
256 Geom::Coord Qx, Qy;
257 Qx = Ax + s * Dx;
258 Qy = Ay + s * Dy;
259 dist2 = (Px - Qx) * (Px - Qx) + (Py - Qy) * (Py - Qy);
260 }
261
262 if (dist2 < (*best * *best)) *best = sqrt (dist2);
263 }
264
265 if (wind) {
266 /* Find wind */
267 if ((Ax >= Px) && (Bx >= Px)) return;
268 if ((Ay >= Py) && (By >= Py)) return;
269 if ((Ay < Py) && (By < Py)) return;
270 if (Ay == By) return;
271 /* Ctach upper y bound */
272 if (Ay == Py) {
273 if (Ax < Px) *wind -= 1;
274 return;
275 } else if (By == Py) {
276 if (Bx < Px) *wind += 1;
277 return;
278 } else {
279 Geom::Coord Qx;
280 /* Have to calculate intersection */
281 Qx = Ax + Dx * (Py - Ay) / Dy;
282 if (Qx < Px) {
283 *wind += (Dy > 0.0) ? 1 : -1;
284 }
285 }
286 }
287}
288
289static void
291 Geom::Coord x001, Geom::Coord y001,
292 Geom::Coord x011, Geom::Coord y011,
293 Geom::Coord x111, Geom::Coord y111,
294 Geom::Point const &pt,
295 Geom::Rect *bbox, int *wind, Geom::Coord *best,
296 Geom::Coord tolerance)
297{
298 Geom::Coord x0, y0, x1, y1, len2;
299 int needdist, needwind;
300
301 const Geom::Coord Px = pt[X];
302 const Geom::Coord Py = pt[Y];
303
304 needdist = 0;
305 needwind = 0;
306
307 if (bbox) cubic_bbox (x000, y000, x001, y001, x011, y011, x111, y111, *bbox);
308
309 x0 = std::min (x000, x001);
310 x0 = std::min (x0, x011);
311 x0 = std::min (x0, x111);
312 y0 = std::min (y000, y001);
313 y0 = std::min (y0, y011);
314 y0 = std::min (y0, y111);
315 x1 = std::max (x000, x001);
316 x1 = std::max (x1, x011);
317 x1 = std::max (x1, x111);
318 y1 = std::max (y000, y001);
319 y1 = std::max (y1, y011);
320 y1 = std::max (y1, y111);
321
322 if (best) {
323 /* Quickly adjust to endpoints */
324 len2 = (x000 - Px) * (x000 - Px) + (y000 - Py) * (y000 - Py);
325 if (len2 < (*best * *best)) *best = (Geom::Coord) sqrt (len2);
326 len2 = (x111 - Px) * (x111 - Px) + (y111 - Py) * (y111 - Py);
327 if (len2 < (*best * *best)) *best = (Geom::Coord) sqrt (len2);
328
329 if (((x0 - Px) < *best) && ((y0 - Py) < *best) && ((Px - x1) < *best) && ((Py - y1) < *best)) {
330 /* Point is inside sloppy bbox */
331 /* Now we have to decide, whether subdivide */
332 /* fixme: (Lauris) */
333 if (((y1 - y0) > 5.0) || ((x1 - x0) > 5.0)) {
334 needdist = 1;
335 }
336 }
337 }
338 if (!needdist && wind) {
339 if ((y1 >= Py) && (y0 < Py) && (x0 < Px)) {
340 /* Possible intersection at the left */
341 /* Now we have to decide, whether subdivide */
342 /* fixme: (Lauris) */
343 if (((y1 - y0) > 5.0) || ((x1 - x0) > 5.0)) {
344 needwind = 1;
345 }
346 }
347 }
348
349 if (needdist || needwind) {
350 Geom::Coord x00t, x0tt, xttt, x1tt, x11t, x01t;
351 Geom::Coord y00t, y0tt, yttt, y1tt, y11t, y01t;
352 Geom::Coord s, t;
353
354 t = 0.5;
355 s = 1 - t;
356
357 x00t = s * x000 + t * x001;
358 x01t = s * x001 + t * x011;
359 x11t = s * x011 + t * x111;
360 x0tt = s * x00t + t * x01t;
361 x1tt = s * x01t + t * x11t;
362 xttt = s * x0tt + t * x1tt;
363
364 y00t = s * y000 + t * y001;
365 y01t = s * y001 + t * y011;
366 y11t = s * y011 + t * y111;
367 y0tt = s * y00t + t * y01t;
368 y1tt = s * y01t + t * y11t;
369 yttt = s * y0tt + t * y1tt;
370
371 geom_cubic_bbox_wind_distance (x000, y000, x00t, y00t, x0tt, y0tt, xttt, yttt, pt, nullptr, wind, best, tolerance);
372 geom_cubic_bbox_wind_distance (xttt, yttt, x1tt, y1tt, x11t, y11t, x111, y111, pt, nullptr, wind, best, tolerance);
373 } else {
374 geom_line_wind_distance (x000, y000, x111, y111, pt, wind, best);
375 }
376}
377
378static void
380 Geom::Point const &pt,
381 Geom::Rect *bbox, int *wind, Geom::Coord *dist,
382 Geom::Coord tolerance, Geom::Rect const *viewbox,
383 Geom::Point &p0) // pass p0 through as it represents the last endpoint added (the finalPoint of last curve)
384{
385 unsigned order = 0;
386 if (Geom::BezierCurve const* b = dynamic_cast<Geom::BezierCurve const*>(&c)) {
387 order = b->order();
388 }
389 if (order == 1) {
390 Geom::Point pe = c.finalPoint() * m;
391 if (bbox) {
392 bbox->expandTo(pe);
393 }
394 if (dist || wind) {
395 if (wind) { // we need to pick fill, so do what we're told
396 geom_line_wind_distance (p0[X], p0[Y], pe[X], pe[Y], pt, wind, dist);
397 } else { // only stroke is being picked; skip this segment if it's totally outside the viewbox
398 Geom::Rect swept(p0, pe);
399 if (!viewbox || swept.intersects(*viewbox))
400 geom_line_wind_distance (p0[X], p0[Y], pe[X], pe[Y], pt, wind, dist);
401 }
402 }
403 p0 = pe;
404 }
405 else if (order == 3) {
406 Geom::CubicBezier const& cubic_bezier = static_cast<Geom::CubicBezier const&>(c);
407 Geom::Point p1 = cubic_bezier[1] * m;
408 Geom::Point p2 = cubic_bezier[2] * m;
409 Geom::Point p3 = cubic_bezier[3] * m;
410
411 // get approximate bbox from handles (convex hull property of beziers):
412 Geom::Rect swept(p0, p3);
413 swept.expandTo(p1);
414 swept.expandTo(p2);
415
416 if (!viewbox || swept.intersects(*viewbox)) { // we see this segment, so do full processing
417 geom_cubic_bbox_wind_distance ( p0[X], p0[Y],
418 p1[X], p1[Y],
419 p2[X], p2[Y],
420 p3[X], p3[Y],
421 pt,
422 bbox, wind, dist, tolerance);
423 } else {
424 if (wind) { // if we need fill, we can just pretend it's a straight line
425 geom_line_wind_distance (p0[X], p0[Y], p3[X], p3[Y], pt, wind, dist);
426 } else { // otherwise, skip it completely
427 }
428 }
429 p0 = p3;
430 } else {
431 //this case handles sbasis as well as all other curve types
432 try {
433 Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
434 //recurse to convert the new path resulting from the sbasis to svgd
435 for (const auto & iter : sbasis_path) {
436 geom_curve_bbox_wind_distance(iter, m, pt, bbox, wind, dist, tolerance, viewbox, p0);
437 }
438 } catch (const Geom::Exception &e) {
439 // Curve isFinite failed.
440 g_warning("Error parsing curve: %s", e.what());
441 }
442 }
443}
444
445bool
446pointInTriangle(Geom::Point const &p, Geom::Point const &p1, Geom::Point const &p2, Geom::Point const &p3)
447{
448 //http://totologic.blogspot.com.es/2014/01/accurate-point-in-triangle-test.html
449 using Geom::X;
450 using Geom::Y;
451 double denominator = (p1[X]*(p2[Y] - p3[Y]) + p1[Y]*(p3[X] - p2[X]) + p2[X]*p3[Y] - p2[Y]*p3[X]);
452 double t1 = (p[X]*(p3[Y] - p1[Y]) + p[Y]*(p1[X] - p3[X]) - p1[X]*p3[Y] + p1[Y]*p3[X]) / denominator;
453 double t2 = (p[X]*(p2[Y] - p1[Y]) + p[Y]*(p1[X] - p2[X]) - p1[X]*p2[Y] + p1[Y]*p2[X]) / -denominator;
454 double s = t1 + t2;
455
456 return 0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1 && s <= 1;
457}
458
459
460/* Calculates...
461 and returns ... in *wind and the distance to ... in *dist.
462 Returns bounding box in *bbox if bbox!=NULL.
463 */
464void
466 Geom::Rect *bbox, int *wind, Geom::Coord *dist,
467 Geom::Coord tolerance, Geom::Rect const *viewbox)
468{
469 if (pathv.empty()) {
470 if (wind) *wind = 0;
471 if (dist) *dist = Geom::infinity();
472 return;
473 }
474
475 // remember last point of last curve
476 Geom::Point p0(0,0);
477
478 // remembering the start of subpath
479 Geom::Point p_start(0,0);
480 bool start_set = false;
481
482 for (const auto & it : pathv) {
483
484 if (start_set) { // this is a new subpath
485 if (wind && (p0 != p_start)) // for correct fill picking, each subpath must be closed
486 geom_line_wind_distance (p0[X], p0[Y], p_start[X], p_start[Y], pt, wind, dist);
487 }
488 p0 = it.initialPoint() * m;
489 p_start = p0;
490 start_set = true;
491 if (bbox) {
492 bbox->expandTo(p0);
493 }
494
495 // loop including closing segment if path is closed
496 for (Geom::Path::const_iterator cit = it.begin(); cit != it.end_default(); ++cit) {
497 geom_curve_bbox_wind_distance(*cit, m, pt, bbox, wind, dist, tolerance, viewbox, p0);
498 }
499 }
500
501 if (start_set) {
502 if (wind && (p0 != p_start)) // for correct picking, each subpath must be closed
503 geom_line_wind_distance (p0[X], p0[Y], p_start[X], p_start[Y], pt, wind, dist);
504 }
505}
506
507//#################################################################################
508
514{
515 // Fast negative check using bounds.
516 auto intersected_bounds = a.boundsFast() & b.boundsFast();
517 if (!intersected_bounds) {
518 return false;
519 }
520
521 // Slightly slower positive check using vertex containment.
522 for (auto &node : b.nodes()) {
523 if (a.winding(node)) {
524 return true;
525 }
526 }
527 for (auto &node : a.nodes()) {
528 if (b.winding(node)) {
529 return true;
530 }
531 }
532
533 // The winding method may not detect nodeless Bézier arcs in one pathvector glancing
534 // the edge of the other pathvector. We must deal with this possibility by also checking for
535 // intersections of boundaries.
536 auto crossings = Geom::SimpleCrosser().crossings(a, b);
537 if (crossings.empty()) {
538 return false;
539 }
540 auto is_empty = [](Geom::Crossings const &xings) -> bool { return xings.empty(); };
541 if (!std::all_of(crossings.begin(), crossings.end(), is_empty)) { // An intersection has been found
542 return true;
543 }
544 return false;
545}
546
551bool pathv_fully_contains(Geom::PathVector const &a, Geom::PathVector const &b, FillRule fill_rule, double precision)
552{
553 // Fast-path the case where a is a rectangle.
554 if (auto const a_rect = check_simple_rect(a, precision)) {
555 return a_rect->contains(b.boundsExact());
556 }
557
558 // At minimum, bbox of a must contain bbox of b
559 if (!a.boundsExact()->contains(b.boundsExact())) {
560 return false;
561 }
562
563 // There must be no intersections of edges.
564 // Fixme: This condition is too strict.
565 if (!a.intersect(b, precision).empty()) {
566 return false;
567 }
568
569 // Check winding number of first node of each subpath.
570 // Fixme: This condition is also too strict.
571 for (auto const &path : b) {
572 if (!is_point_inside(fill_rule, a.winding(path.initialPoint()))) {
573 return false;
574 }
575 }
576
577 // BBox is fully contained, no intersections, passed the winding test, the path must be contained
578 return true;
579}
580
581/*
582 * Converts all segments in all paths to Geom::LineSegment or Geom::HLineSegment or
583 * Geom::VLineSegment or Geom::CubicBezier.
584 */
587{
588 Geom::PathVector output;
589
590 for (const auto & pit : pathv) {
591 output.push_back( Geom::Path() );
592 output.back().setStitching(true);
593 output.back().start( pit.initialPoint() );
594
595 for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
596 if (is_straight_curve(*cit)) {
597 Geom::LineSegment l(cit->initialPoint(), cit->finalPoint());
598 output.back().append(l);
599 } else {
600 Geom::BezierCurve const *curve = dynamic_cast<Geom::BezierCurve const *>(&*cit);
601 if (curve && curve->order() == 3) {
602 Geom::CubicBezier b((*curve)[0], (*curve)[1], (*curve)[2], (*curve)[3]);
603 output.back().append(b);
604 } else {
605 // convert all other curve types to cubicbeziers
606 try {
607 Geom::Path cubicbezier_path = Geom::cubicbezierpath_from_sbasis(cit->toSBasis(), 0.1);
608 cubicbezier_path.close(false);
609 output.back().append(cubicbezier_path);
610 } catch (const Geom::Exception &e) {
611 // Curve isFinite failed.
612 g_warning("Error parsing curve: %s", e.what());
613 break;
614 }
615 }
616 }
617 }
618
619 output.back().close( pit.closed() );
620 }
621
622 return output;
623}
624
625/*
626 * Converts all segments in all paths to Geom::LineSegment. There is an intermediate
627 * stage where some may be converted to beziers. maxdisp is the maximum displacement from
628 * the line segment to the bezier curve; ** maxdisp is not used at this moment **.
629 *
630 * This is NOT a terribly fast method, but it should give a solution close to the one with the
631 * fewest points.
632 */
634pathv_to_linear( Geom::PathVector const &pathv, double /*maxdisp*/)
635{
636 Geom::PathVector output;
638
639 // Now all path segments are either already lines, or they are beziers.
640
641 for (const auto & pit : tmppath) {
642 output.push_back( Geom::Path() );
643 output.back().start( pit.initialPoint() );
644 output.back().close( pit.closed() );
645
646 for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
647 if (is_straight_curve(*cit)) {
648 Geom::LineSegment ls(cit->initialPoint(), cit->finalPoint());
649 output.back().append(ls);
650 }
651 else { /* all others must be Bezier curves */
652 Geom::BezierCurve const *curve = dynamic_cast<Geom::BezierCurve const *>(&*cit);
653 std::vector<Geom::Point> bzrpoints = curve->controlPoints();
654 Geom::Point A = bzrpoints[0];
655 Geom::Point B = bzrpoints[1];
656 Geom::Point C = bzrpoints[2];
657 Geom::Point D = bzrpoints[3];
658 std::vector<Geom::Point> pointlist;
659 pointlist.push_back(A);
661 A[X], A[Y],
662 B[X], B[Y],
663 C[X], C[Y],
664 D[X], D[Y],
665 pointlist,
666 0);
667 pointlist.push_back(D);
668 Geom::Point r1 = pointlist[0];
669 for (unsigned int i=1; i<pointlist.size();i++){
670 Geom::Point prev_r1 = r1;
671 r1 = pointlist[i];
672 Geom::LineSegment ls(prev_r1, r1);
673 output.back().append(ls);
674 }
675 pointlist.clear();
676 }
677 }
678 }
679
680 return output;
681}
682
683/*
684 * Converts all segments in all paths to Geom Cubic bezier.
685 * This is used in lattice2 LPE, maybe is better move the function to the effect
686 * But maybe could be usable by others, so i put here.
687 * The straight curve part is needed as is for the effect to work appropriately
688 */
690pathv_to_cubicbezier( Geom::PathVector const &pathv, bool nolines)
691{
692 Geom::PathVector output;
693 for (const auto & pit : pathv) {
694 if (pit.empty()) {
695 continue;
696 }
697 output.push_back( Geom::Path() );
698 output.back().start( pit.initialPoint() );
699 output.back().close( pit.closed() );
700 bool end_open = false;
701 if (pit.closed()) {
702 auto const &closingline = pit.back_closed();
703 if (!are_near(closingline.initialPoint(), closingline.finalPoint())) {
704 end_open = true;
705 }
706 }
707 Geom::Path pitCubic = (Geom::Path)pit;
708 if(end_open && pit.closed()){
709 pitCubic.close(false);
710 pitCubic.appendNew<Geom::LineSegment>( pitCubic.initialPoint() );
711 pitCubic.close(true);
712 }
713 for (Geom::Path::iterator cit = pitCubic.begin(); cit != pitCubic.end_open(); ++cit) {
714 Geom::BezierCurve const *curve = dynamic_cast<Geom::BezierCurve const *>(&*cit);
715 // is_straight curves dont work for bspline
716 if (nolines && is_straight_curve(*cit)) {
717 Geom::CubicBezier b(cit->initialPoint(), cit->pointAt(0.3334), cit->finalPoint(), cit->finalPoint());
718 output.back().append(b);
719 } else if (!curve || curve->order() != 3) {
720 // convert all other curve types to cubicbeziers
721 Geom::Path cubicbezier_path = Geom::cubicbezierpath_from_sbasis(cit->toSBasis(), 0.1);
722 output.back().append(cubicbezier_path);
723 } else if (Geom::are_near((*curve)[0],(*curve)[1]) && Geom::are_near((*curve)[2],(*curve)[3])){
724 Geom::LineSegment ls(cit->initialPoint(), cit->finalPoint());
725 output.back().append(ls);
726 } else {
727 Geom::CubicBezier b((*curve)[0], (*curve)[1], (*curve)[2], (*curve)[3]);
728 output.back().append(b);
729 }
730 }
731 }
732
733 return output;
734}
735
736//Study move to 2Geom
737size_t
739 size_t tot = 0;
740 for (auto const &subpath : pathv) {
741 tot += count_path_nodes(subpath);
742 }
743 return tot;
744}
745
746size_t
748 size_t tot = 0;
749 for (auto const &subpath : pathv) {
750 tot += count_path_curves(subpath);
751 }
752 return tot;
753}
754
755size_t
757 size_t tot = 0;
758 for (auto const &subpath : pathv) {
759 tot += count_path_degenerations(subpath);
760 }
761 return tot;
762}
763
765{
766 if (path.empty()) {
767 std::cerr << "count_path_degenerates: path is empty!" << std::endl;
768 // Hmm, a path always contains a closing segment which has two nodes which are degenerate if path is empty.
769 return 0;
770 }
771
772 size_t tot = 0;
773 Geom::Path::const_iterator curve_it = path.begin();
774 Geom::Path::const_iterator curve_endit = path.end_default();
775 if (path.closed()) {
776 auto const &closingline = path.back_closed();
777 // the closing line segment is always of type
778 // Geom::LineSegment.
779 if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
780 // closingline.isDegenerate() did not work, because it only checks for
781 // *exact* zero length, which goes wrong for relative coordinates and
782 // rounding errors...
783 // the closing line segment has zero-length. So stop before that one!
784 curve_endit = path.end_open();
785 }
786 }
787 while (curve_it != curve_endit) {
788 if (Geom::are_near((*curve_it).length(),0)) {
789 tot += 1;
790 }
791 ++curve_it;
792 }
793 return tot;
794}
795
796size_t count_path_nodes(Geom::Path const &path)
797{
798 if (path.empty()) {
799 std::cerr << "count_path_nodes: path is empty!" << std::endl;
800 // Hmm, a path always contains a closing segment which has two (degenerate) nodes...
801 return 0;
802 }
803
804 size_t tot = path.size_default() + 1; // if degenerate closing line one is erased no need to duple
805 if (path.closed()) {
806 tot -= 1;
807 auto const &closingline = path.back_closed();
808 // the closing line segment is always of type
809 // Geom::LineSegment.
810 if (!closingline.isDegenerate() && are_near(closingline.initialPoint(), closingline.finalPoint())) {
811 // closingline.isDegenerate() did not work, because it only checks for
812 // *exact* zero length, which goes wrong for relative coordinates and
813 // rounding errors...
814 // the closing line segment has zero-length. So stop before that one!
815 tot -= 1;
816 }
817 }
818 return tot;
819}
820
821size_t count_path_curves(Geom::Path const &path)
822{
823 if (path.empty()) {
824 std::cerr << "count_path_curves: path is empty!" << std::endl;
825 return 0;
826 }
827
828 size_t tot = path.size_default(); // if degenerate closing line one is erased no need to duple
829 if (path.closed()) {
830 auto const &closingline = path.back_closed();
831 // the closing line segment is always of type
832 // Geom::LineSegment.
833 if (!closingline.isDegenerate() && are_near(closingline.initialPoint(), closingline.finalPoint())) {
834 // closingline.isDegenerate() did not work, because it only checks for
835 // *exact* zero length, which goes wrong for relative coordinates and
836 // rounding errors...
837 // the closing line segment has zero-length. So stop before that one!
838 tot -= 1;
839 }
840 }
841 return tot;
842}
843
844// The next routine is modified from curv4_div::recursive_bezier from file agg_curves.cpp
845//----------------------------------------------------------------------------
846// Anti-Grain Geometry (AGG) - Version 2.5
847// A high quality rendering engine for C++
848// Copyright (C) 2002-2006 Maxim Shemanarev
849// Contact: mcseem@antigrain.com
850// mcseemagg@yahoo.com
851// http://antigrain.com
852//
853// AGG is free software; you can redistribute it and/or
854// modify it under the terms of the GNU General Public License
855// as published by the Free Software Foundation; either version 2
856// of the License, or (at your option) any later version.
857//
858// AGG is distributed in the hope that it will be useful,
859// but WITHOUT ANY WARRANTY; without even the implied warranty of
860// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
861// GNU General Public License for more details.
862//
863// You should have received a copy of the GNU General Public License
864// along with AGG; if not, write to the Free Software
865// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
866// MA 02110-1301, USA.
867//----------------------------------------------------------------------------
868void
869recursive_bezier4(const double x1, const double y1,
870 const double x2, const double y2,
871 const double x3, const double y3,
872 const double x4, const double y4,
873 std::vector<Geom::Point> &m_points,
874 int level)
875 {
876 // some of these should be parameters, but do it this way for now.
877 const double curve_collinearity_epsilon = 1e-30;
878 const double curve_angle_tolerance_epsilon = 0.01;
879 double m_cusp_limit = 0.0;
880 double m_angle_tolerance = 0.0;
881 double m_approximation_scale = 1.0;
882 double m_distance_tolerance_square = 0.5 / m_approximation_scale;
883 m_distance_tolerance_square *= m_distance_tolerance_square;
884 enum curve_recursion_limit_e { curve_recursion_limit = 32 };
885#define calc_sq_distance(A,B,C,D) ((A-C)*(A-C) + (B-D)*(B-D))
886
887 if(level > curve_recursion_limit)
888 {
889 return;
890 }
891
892
893 // Calculate all the mid-points of the line segments
894 //----------------------
895 double x12 = (x1 + x2) / 2;
896 double y12 = (y1 + y2) / 2;
897 double x23 = (x2 + x3) / 2;
898 double y23 = (y2 + y3) / 2;
899 double x34 = (x3 + x4) / 2;
900 double y34 = (y3 + y4) / 2;
901 double x123 = (x12 + x23) / 2;
902 double y123 = (y12 + y23) / 2;
903 double x234 = (x23 + x34) / 2;
904 double y234 = (y23 + y34) / 2;
905 double x1234 = (x123 + x234) / 2;
906 double y1234 = (y123 + y234) / 2;
907
908
909 // Try to approximate the full cubic curve by a single straight line
910 //------------------
911 double dx = x4-x1;
912 double dy = y4-y1;
913
914 double d2 = fabs(((x2 - x4) * dy - (y2 - y4) * dx));
915 double d3 = fabs(((x3 - x4) * dy - (y3 - y4) * dx));
916 double da1, da2, k;
917
918 switch((int(d2 > curve_collinearity_epsilon) << 1) +
919 int(d3 > curve_collinearity_epsilon))
920 {
921 case 0:
922 // All collinear OR p1==p4
923 //----------------------
924 k = dx*dx + dy*dy;
925 if(k == 0)
926 {
927 d2 = calc_sq_distance(x1, y1, x2, y2);
928 d3 = calc_sq_distance(x4, y4, x3, y3);
929 }
930 else
931 {
932 k = 1 / k;
933 da1 = x2 - x1;
934 da2 = y2 - y1;
935 d2 = k * (da1*dx + da2*dy);
936 da1 = x3 - x1;
937 da2 = y3 - y1;
938 d3 = k * (da1*dx + da2*dy);
939 if(d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1)
940 {
941 // Simple collinear case, 1---2---3---4
942 // We can leave just two endpoints
943 return;
944 }
945 if(d2 <= 0) d2 = calc_sq_distance(x2, y2, x1, y1);
946 else if(d2 >= 1) d2 = calc_sq_distance(x2, y2, x4, y4);
947 else d2 = calc_sq_distance(x2, y2, x1 + d2*dx, y1 + d2*dy);
948
949 if(d3 <= 0) d3 = calc_sq_distance(x3, y3, x1, y1);
950 else if(d3 >= 1) d3 = calc_sq_distance(x3, y3, x4, y4);
951 else d3 = calc_sq_distance(x3, y3, x1 + d3*dx, y1 + d3*dy);
952 }
953 if(d2 > d3)
954 {
955 if(d2 < m_distance_tolerance_square)
956 {
957 m_points.emplace_back(x2, y2);
958 return;
959 }
960 }
961 else
962 {
963 if(d3 < m_distance_tolerance_square)
964 {
965 m_points.emplace_back(x3, y3);
966 return;
967 }
968 }
969 break;
970
971 case 1:
972 // p1,p2,p4 are collinear, p3 is significant
973 //----------------------
974 if(d3 * d3 <= m_distance_tolerance_square * (dx*dx + dy*dy))
975 {
976 if(m_angle_tolerance < curve_angle_tolerance_epsilon)
977 {
978 m_points.emplace_back(x23, y23);
979 return;
980 }
981
982 // Angle Condition
983 //----------------------
984 da1 = fabs(atan2(y4 - y3, x4 - x3) - atan2(y3 - y2, x3 - x2));
985 if(da1 >= M_PI) da1 = 2*M_PI - da1;
986
987 if(da1 < m_angle_tolerance)
988 {
989 m_points.emplace_back(x2, y2);
990 m_points.emplace_back(x3, y3);
991 return;
992 }
993
994 if(m_cusp_limit != 0.0)
995 {
996 if(da1 > m_cusp_limit)
997 {
998 m_points.emplace_back(x3, y3);
999 return;
1000 }
1001 }
1002 }
1003 break;
1004
1005 case 2:
1006 // p1,p3,p4 are collinear, p2 is significant
1007 //----------------------
1008 if(d2 * d2 <= m_distance_tolerance_square * (dx*dx + dy*dy))
1009 {
1010 if(m_angle_tolerance < curve_angle_tolerance_epsilon)
1011 {
1012 m_points.emplace_back(x23, y23);
1013 return;
1014 }
1015
1016 // Angle Condition
1017 //----------------------
1018 da1 = fabs(atan2(y3 - y2, x3 - x2) - atan2(y2 - y1, x2 - x1));
1019 if(da1 >= M_PI) da1 = 2*M_PI - da1;
1020
1021 if(da1 < m_angle_tolerance)
1022 {
1023 m_points.emplace_back(x2, y2);
1024 m_points.emplace_back(x3, y3);
1025 return;
1026 }
1027
1028 if(m_cusp_limit != 0.0)
1029 {
1030 if(da1 > m_cusp_limit)
1031 {
1032 m_points.emplace_back(x2, y2);
1033 return;
1034 }
1035 }
1036 }
1037 break;
1038
1039 case 3:
1040 // Regular case
1041 //-----------------
1042 if((d2 + d3)*(d2 + d3) <= m_distance_tolerance_square * (dx*dx + dy*dy))
1043 {
1044 // If the curvature doesn't exceed the distance_tolerance value
1045 // we tend to finish subdivisions.
1046 //----------------------
1047 if(m_angle_tolerance < curve_angle_tolerance_epsilon)
1048 {
1049 m_points.emplace_back(x23, y23);
1050 return;
1051 }
1052
1053 // Angle & Cusp Condition
1054 //----------------------
1055 k = atan2(y3 - y2, x3 - x2);
1056 da1 = fabs(k - atan2(y2 - y1, x2 - x1));
1057 da2 = fabs(atan2(y4 - y3, x4 - x3) - k);
1058 if(da1 >= M_PI) da1 = 2*M_PI - da1;
1059 if(da2 >= M_PI) da2 = 2*M_PI - da2;
1060
1061 if(da1 + da2 < m_angle_tolerance)
1062 {
1063 // Finally we can stop the recursion
1064 //----------------------
1065 m_points.emplace_back(x23, y23);
1066 return;
1067 }
1068
1069 if(m_cusp_limit != 0.0)
1070 {
1071 if(da1 > m_cusp_limit)
1072 {
1073 m_points.emplace_back(x2, y2);
1074 return;
1075 }
1076
1077 if(da2 > m_cusp_limit)
1078 {
1079 m_points.emplace_back(x3, y3);
1080 return;
1081 }
1082 }
1083 }
1084 break;
1085 }
1086
1087 // Continue subdivision
1088 //----------------------
1089 recursive_bezier4(x1, y1, x12, y12, x123, y123, x1234, y1234, m_points, level + 1);
1090 recursive_bezier4(x1234, y1234, x234, y234, x34, y34, x4, y4, m_points, level + 1);
1091}
1092
1097bool approx_dihedral(Geom::Affine const &affine, double eps)
1098{
1099 // Ensure translation is zero.
1100 if (std::abs(affine[4]) > eps || std::abs(affine[5]) > eps) return false;
1101
1102 // Ensure matrix has integer components.
1103 std::array<int, 4> arr;
1104 for (int i = 0; i < 4; i++) {
1105 arr[i] = std::round(affine[i]);
1106 if (std::abs(affine[i] - arr[i]) > eps) return false;
1107 arr[i] = std::abs(arr[i]);
1108 }
1109
1110 // Ensure rounded matrix is correct.
1111 return arr == std::array {1, 0, 0, 1 } || arr == std::array{ 0, 1, 1, 0 };
1112}
1113
1114/*
1115 Local Variables:
1116 mode:c++
1117 c-file-style:"stroustrup"
1118 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1119 indent-tabs-mode:nil
1120 fill-column:99
1121 End:
1122*/
1123// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
bool is_point_inside(FillRule fill_rule, int winding)
Return whether a point is inside a shape, given the point's winding number and the shape's fill rule.
FillRule
Definition LivarotDefs.h:68
3x3 matrix representing an affine transformation.
Definition affine.h:70
Bezier curve with compile-time specified order.
Two-dimensional Bezier curve of arbitrary order.
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
Base exception class, all 2geom exceptions should be derived from this one.
Definition exception.h:53
const char * what() const noexcept override
Definition exception.h:63
bool contains(CRect const &r) const
Check whether the rectangle includes all points in the given rectangle.
bool contains(GenericRect< C > const &r) const
Check whether the rectangle includes all points in the given rectangle.
bool intersects(GenericRect< C > const &r) const
Check whether the rectangles have any common points.
void expandTo(CPoint const &p)
Enlarge the rectangle to contain the given point.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
size_type size() const
Get the number of paths in the vector.
Definition pathvector.h:147
std::vector< Point > nodes() const
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
OptRect boundsExact() const
int winding(Point const &p) const
Determine the winding number at the specified point.
Point pointAt(Coord t) const
Point initialPoint() const
Get the first point in the first path of the vector.
Definition pathvector.h:217
OptRect boundsFast() const
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
size_type curveCount() const
Get the total number of curves in the vector.
std::vector< PVIntersection > intersect(PathVector const &other, Coord precision=EPSILON) const
Sequence of contiguous curves, aka spline.
Definition path.h:353
bool closed() const
Check whether the path is closed.
Definition path.h:503
void setStitching(bool x)
Enable or disable the throwing of exceptions when stitching discontinuities.
Definition path.h:827
void close(bool closed=true)
Set whether the path is closed.
Definition path.cpp:322
bool empty() const
Check whether path is empty.
Definition path.h:500
Curve const & back_closed() const
Definition path.h:452
const_iterator end_open() const
Definition path.h:467
size_type size_default() const
Natural size of the path.
Definition path.h:486
void append(Curve *curve)
Add a new curve to the end of the path.
Definition path.h:750
Point initialPoint() const
Get the first point in the path.
Definition path.h:705
const_iterator begin() const
Definition path.h:464
void appendNew(Args &&... args)
Append a new curve to the path.
Definition path.h:804
const_iterator end_default() const
Definition path.h:466
void start(Point const &p)
Definition path.cpp:426
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
Axis aligned, non-empty rectangle.
Definition rect.h:92
Convex hull data structures.
Include all curve types.
double c[8][4]
const unsigned order
Specific curve type functions for Inkscape, not provided by lib2geom.
bool is_straight_curve(Geom::BezierCurve const &c)
Definition geom-curves.h:22
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
bool pathv_fully_contains(Geom::PathVector const &a, Geom::PathVector const &b, FillRule fill_rule, double precision)
Checks whether the filled region defined by pathvector a and fill rule fill_rule completely contains ...
Definition geom.cpp:551
size_t count_pathvector_nodes(Geom::PathVector const &pathv)
Definition geom.cpp:738
Geom::OptRect bounds_exact_transformed(Geom::PathVector const &pv, Geom::Affine const &t)
Definition geom.cpp:153
bool pointInTriangle(Geom::Point const &p, Geom::Point const &p1, Geom::Point const &p2, Geom::Point const &p3)
Definition geom.cpp:446
bool approx_dihedral(Geom::Affine const &affine, double eps)
Returns whether an affine transformation is approximately a dihedral transformation,...
Definition geom.cpp:1097
size_t count_path_nodes(Geom::Path const &path)
Definition geom.cpp:796
size_t count_path_curves(Geom::Path const &path)
Definition geom.cpp:821
static void geom_curve_bbox_wind_distance(Geom::Curve const &c, Geom::Affine const &m, Geom::Point const &pt, Geom::Rect *bbox, int *wind, Geom::Coord *dist, Geom::Coord tolerance, Geom::Rect const *viewbox, Geom::Point &p0)
Definition geom.cpp:379
bool pathv_similar(Geom::PathVector const &apv, Geom::PathVector const &bpv, double precision)
Definition geom.cpp:176
size_t count_path_degenerations(Geom::Path const &path)
Definition geom.cpp:764
bool pathvs_have_nonempty_overlap(Geom::PathVector const &a, Geom::PathVector const &b)
An exact check for whether the two pathvectors intersect or overlap, including the case of a line cro...
Definition geom.cpp:513
static void geom_cubic_bbox_wind_distance(Geom::Coord x000, Geom::Coord y000, Geom::Coord x001, Geom::Coord y001, Geom::Coord x011, Geom::Coord y011, Geom::Coord x111, Geom::Coord y111, Geom::Point const &pt, Geom::Rect *bbox, int *wind, Geom::Coord *best, Geom::Coord tolerance)
Definition geom.cpp:290
void recursive_bezier4(const double x1, const double y1, const double x2, const double y2, const double x3, const double y3, const double x4, const double y4, std::vector< Geom::Point > &m_points, int level)
Definition geom.cpp:869
static void geom_line_wind_distance(Geom::Coord x0, Geom::Coord y0, Geom::Coord x1, Geom::Coord y1, Geom::Point const &pt, int *wind, Geom::Coord *best)
Definition geom.cpp:234
Geom::PathVector pathv_to_cubicbezier(Geom::PathVector const &pathv, bool nolines)
Definition geom.cpp:690
Geom::PathVector pathv_to_linear_and_cubic_beziers(Geom::PathVector const &pathv)
Definition geom.cpp:586
size_t count_pathvector_curves(Geom::PathVector const &pathv)
Definition geom.cpp:747
Geom::PathVector pathv_to_linear(Geom::PathVector const &pathv, double)
Definition geom.cpp:634
static void cubic_bbox(Geom::Coord x000, Geom::Coord y000, Geom::Coord x001, Geom::Coord y001, Geom::Coord x011, Geom::Coord y011, Geom::Coord x111, Geom::Coord y111, Geom::Rect &bbox)
Definition geom.cpp:33
void pathv_matrix_point_bbox_wind_distance(Geom::PathVector const &pathv, Geom::Affine const &m, Geom::Point const &pt, Geom::Rect *bbox, int *wind, Geom::Coord *dist, Geom::Coord tolerance, Geom::Rect const *viewbox)
Definition geom.cpp:465
Geom::OptRect check_simple_rect(Geom::PathVector const &pathv, double precision)
Definition geom.cpp:198
Geom::OptRect bounds_fast_transformed(Geom::PathVector const &pv, Geom::Affine const &t)
Definition geom.cpp:147
size_t count_pathvector_degenerations(Geom::PathVector const &pathv)
Definition geom.cpp:756
Specific geometry functions for Inkscape, not provided my lib2geom.
Inkscape::XML::Node * node
Geom::Point start
Geom::Point end
Path cubicbezierpath_from_sbasis(D2< SBasis > const &B, double tol)
std::vector< Crossing > Crossings
Definition crossing.h:126
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Path intersection.
Conversion between SBasis and Bezier basis polynomials.
Crossings crossings(Curve const &a, Curve const &b)
A simple wrapper around pair_intersect.
Definition curve.h:24