Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
path.cpp
Go to the documentation of this file.
1/*
4 * Authors:
5 * MenTaLguY <mental@rydia.net>
6 * Marco Cecchetti <mrcekets at gmail.com>
7 * Krzysztof Kosiński <tweenk.pl@gmail.com>
8 *
9 * Copyright 2007-2014 authors
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it either under the terms of the GNU Lesser General Public
13 * License version 2.1 as published by the Free Software Foundation
14 * (the "LGPL") or, at your option, under the terms of the Mozilla
15 * Public License Version 1.1 (the "MPL"). If you do not alter this
16 * notice, a recipient may use your version of this file under either
17 * the MPL or the LGPL.
18 *
19 * You should have received a copy of the LGPL along with this library
20 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 * You should have received a copy of the MPL along with this library
23 * in the file COPYING-MPL-1.1
24 *
25 * The contents of this file are subject to the Mozilla Public License
26 * Version 1.1 (the "License"); you may not use this file except in
27 * compliance with the License. You may obtain a copy of the License at
28 * http://www.mozilla.org/MPL/
29 *
30 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
31 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
32 * the specific language governing rights and limitations.
33 */
34
35#include <2geom/path.h>
36#include <2geom/pathvector.h>
37#include <2geom/transforms.h>
38#include <2geom/circle.h>
39#include <2geom/ellipse.h>
40#include <2geom/convex-hull.h>
42#include <2geom/sweeper.h>
43#include <algorithm>
44#include <limits>
45#include <optional>
46
47using std::swap;
48using namespace Geom::PathInternal;
49
50namespace Geom {
51
52// this represents an empty interval
54 : _from(0, 0.0)
55 , _to(0, 0.0)
56 , _path_size(1)
57 , _cross_start(false)
58 , _reverse(false)
59{}
60
61PathInterval::PathInterval(PathTime const &from, PathTime const &to, bool cross_start, size_type path_size)
62 : _from(from)
63 , _to(to)
64 , _path_size(path_size)
65 , _cross_start(cross_start)
66 , _reverse((to < from) ^ cross_start)
67{
68 if (_reverse) {
70 if (cross_start && _to < to) {
71 // Normalization made us cross start (closed path),
72 // so we don't need to cross the start anymore.
73 _cross_start = false;
74 }
75 if (_from != _to) {
77 if (cross_start && _from > from) {
78 // Normalization backwards made us logically cross
79 // the start – we shouldn't cross the start again.
80 _cross_start = false;
81 }
82 }
83 } else {
85 if (cross_start && _from < from) {
86 _cross_start = false;
87 }
88 if (_from != _to) {
90 if (cross_start && _to > to) {
91 _cross_start = false;
92 }
93 }
94 }
95
96 if (_from == _to) {
97 _reverse = false;
98 _cross_start = false;
99 }
100}
101
102bool PathInterval::contains(PathTime const &pos) const {
103 if (_cross_start) {
104 if (_reverse) {
105 return pos >= _to || _from >= pos;
106 } else {
107 return pos >= _from || _to >= pos;
108 }
109 } else {
110 if (_reverse) {
111 return _to <= pos && pos <= _from;
112 } else {
113 return _from <= pos && pos <= _to;
114 }
115 }
116}
117
119{
120 if (isDegenerate()) return 0;
121 if (_cross_start) {
122 if (_reverse) {
124 } else {
126 }
127 } else {
128 if (_reverse) {
129 return _from.curve_index - _to.curve_index + 1;
130 } else {
131 return _to.curve_index - _from.curve_index + 1;
132 }
133 }
134}
135
137{
138 // If there is some node further than min_dist (in time coord) from the ends,
139 // return that node. Otherwise, return the middle.
140 PathTime result(0, 0.0);
141
144 return result;
145 }
146 // If _cross_start, then we can be sure that at least one node is in the domain.
147 // If dcurve == 0, it actually means that all curves are included in the domain
148
149 if (_reverse) {
151 bool from_close = _from.t < min_dist;
152 bool to_close = _to.t > 1 - min_dist;
153
154 if (dcurve == 0) {
155 dcurve = _path_size;
156 }
157
158 if (dcurve == 1) {
159 if (from_close || to_close) {
160 result.curve_index = _from.curve_index;
161 Coord tmid = _from.t - ((1 - _to.t) + _from.t) * 0.5;
162 if (tmid < 0) {
163 result.curve_index = (_path_size + result.curve_index - 1) % _path_size;
164 tmid += 1;
165 }
166 result.t = tmid;
167 return result;
168 }
169
170 result.curve_index = _from.curve_index;
171 return result;
172 }
173
174 result.curve_index = (_to.curve_index + 1) % _path_size;
175 if (to_close) {
176 if (dcurve == 2) {
177 result.t = 0.5;
178 } else {
179 result.curve_index = (result.curve_index + 1) % _path_size;
180 }
181 }
182 return result;
183 } else {
185 bool from_close = _from.t > 1 - min_dist;
186 bool to_close = _to.t < min_dist;
187
188 if (dcurve == 0) {
189 dcurve = _path_size;
190 }
191
192 if (dcurve == 1) {
193 if (from_close || to_close) {
194 result.curve_index = _from.curve_index;
195 Coord tmid = ((1 - _from.t) + _to.t) * 0.5 + _from.t;
196 if (tmid >= 1) {
197 result.curve_index = (result.curve_index + 1) % _path_size;
198 tmid -= 1;
199 }
200 result.t = tmid;
201 return result;
202 }
203
204 result.curve_index = _to.curve_index;
205 return result;
206 }
207
208 result.curve_index = (_from.curve_index + 1) % _path_size;
209 if (from_close) {
210 if (dcurve == 2) {
211 result.t = 0.5;
212 } else {
213 result.curve_index = (result.curve_index + 1) % _path_size;
214 }
215 }
216 return result;
217 }
218
220 return result;
221}
222
223PathInterval PathInterval::from_direction(PathTime const &from, PathTime const &to, bool reversed, size_type path_size)
224{
226 result._from = from;
227 result._to = to;
228 result._path_size = path_size;
229
230 if (reversed) {
231 result._to.normalizeForward(path_size);
232 if (result._from != result._to) {
233 result._from.normalizeBackward(path_size);
234 }
235 } else {
236 result._from.normalizeForward(path_size);
237 if (result._from != result._to) {
238 result._to.normalizeBackward(path_size);
239 }
240 }
241
242 if (result._from == result._to) {
243 result._reverse = false;
244 result._cross_start = false;
245 } else {
246 result._reverse = reversed;
247 if (reversed) {
248 result._cross_start = from < to;
249 } else {
250 result._cross_start = to < from;
251 }
252 }
253 return result;
254}
255
256
258 : _data(new PathData())
259 , _closing_seg(new ClosingSegment(r.corner(3), r.corner(0)))
260 , _closed(true)
261 , _exception_on_stitch(true)
262{
263 for (unsigned i = 0; i < 3; ++i) {
264 _data->curves.push_back(new LineSegment(r.corner(i), r.corner(i+1)));
265 }
266 _data->curves.push_back(_closing_seg);
267}
268
270 : _data(new PathData())
271 , _closing_seg(new ClosingSegment(Point(), Point()))
272 , _closed(true)
273 , _exception_on_stitch(true)
274{
275 if (ch.empty()) {
276 _data->curves.push_back(_closing_seg);
277 return;
278 }
279
282
283 Point last = ch.front();
284
285 for (std::size_t i = 1; i < ch.size(); ++i) {
286 _data->curves.push_back(new LineSegment(last, ch[i]));
287 last = ch[i];
288 }
289
290 _data->curves.push_back(_closing_seg);
291 _closed = true;
292}
293
295 : _data(new PathData())
296 , _closing_seg(NULL)
297 , _closed(true)
298 , _exception_on_stitch(true)
299{
300 Point p1 = c.pointAt(0);
301 Point p2 = c.pointAt(M_PI);
302 _data->curves.push_back(new EllipticalArc(p1, c.radius(), c.radius(), 0, false, true, p2));
303 _data->curves.push_back(new EllipticalArc(p2, c.radius(), c.radius(), 0, false, true, p1));
304 _closing_seg = new ClosingSegment(p1, p1);
305 _data->curves.push_back(_closing_seg);
306}
307
309 : _data(new PathData())
310 , _closing_seg(NULL)
311 , _closed(true)
312 , _exception_on_stitch(true)
313{
314 Point p1 = e.pointAt(0);
315 Point p2 = e.pointAt(M_PI);
316 _data->curves.push_back(new EllipticalArc(p1, e.rays(), e.rotationAngle(), false, true, p2));
317 _data->curves.push_back(new EllipticalArc(p2, e.rays(), e.rotationAngle(), false, true, p1));
318 _closing_seg = new ClosingSegment(p1, p1);
319 _data->curves.push_back(_closing_seg);
320}
321
322void Path::close(bool c)
323{
324 if (c == _closed) return;
325 if (c && _data->curves.size() >= 2) {
326 // when closing, if last segment is linear and ends at initial point,
327 // replace it with the closing segment
328 Sequence::iterator last = _data->curves.end() - 2;
329 if (last->isLineSegment() && last->finalPoint() == initialPoint()) {
330 _closing_seg->setInitial(last->initialPoint());
331 _data->curves.erase(last);
332 }
333 }
334 _closed = c;
335}
336
338{
339 _unshare();
340 _data->curves.pop_back().release();
341 _data->curves.clear();
344 _data->curves.push_back(_closing_seg);
345 _closed = false;
346}
347
349{
351 if (empty()) {
352 return bounds;
353 }
354 // if the path is not empty, we look for cached bounds
355 if (_data->fast_bounds) {
356 return _data->fast_bounds;
357 }
358
359 bounds = front().boundsFast();
360 const_iterator iter = begin();
361 // the closing path segment can be ignored, because it will always
362 // lie within the bbox of the rest of the path
363 if (iter != end()) {
364 for (++iter; iter != end(); ++iter) {
365 bounds.unionWith(iter->boundsFast());
366 }
367 }
368 _data->fast_bounds = bounds;
369 return bounds;
370}
371
373{
375 if (empty())
376 return bounds;
378 const_iterator iter = begin();
379 // the closing path segment can be ignored, because it will always lie within the bbox of the rest of the path
380 if (iter != end()) {
381 for (++iter; iter != end(); ++iter) {
382 bounds.unionWith(iter->boundsExact());
383 }
384 }
385 return bounds;
386}
387
389{
391 ret.push_cut(0);
392 unsigned i = 1;
393 bool degenerate = true;
394 // pw<d2<>> is always open. so if path is closed, add closing segment as well to pwd2.
395 for (const_iterator it = begin(); it != end_default(); ++it) {
396 if (!it->isDegenerate()) {
397 ret.push(it->toSBasis(), i++);
398 degenerate = false;
399 }
400 }
401 if (degenerate) {
402 // if path only contains degenerate curves, no second cut is added
403 // so we need to create at least one segment manually
405 }
406 return ret;
407}
408
409template <typename iter>
410iter inc(iter const &x, unsigned n) {
411 iter ret = x;
412 for (unsigned i = 0; i < n; i++)
413 ret++;
414 return ret;
415}
416
417bool Path::operator==(Path const &other) const
418{
419 if (this == &other)
420 return true;
421 if (_closed != other._closed)
422 return false;
423 return _data->curves == other._data->curves;
424}
425
426void Path::start(Point const &p) {
427 if (_data->curves.size() > 1) {
428 clear();
429 }
432}
433
435{
436 Interval ret(0, size_default());
437 return ret;
438}
439
440Curve const &Path::curveAt(Coord t, Coord *rest) const
441{
442 PathTime pos = _factorTime(t);
443 if (rest) {
444 *rest = pos.t;
445 }
446 return at(pos.curve_index);
447}
448
450{
451 return pointAt(_factorTime(t));
452}
453
455{
456 return valueAt(_factorTime(t), d);
457}
458
459Curve const &Path::curveAt(PathTime const &pos) const
460{
461 return at(pos.curve_index);
462}
463Point Path::pointAt(PathTime const &pos) const
464{
465 return at(pos.curve_index).pointAt(pos.t);
466}
467Coord Path::valueAt(PathTime const &pos, Dim2 d) const
468{
469 return at(pos.curve_index).valueAt(pos.t, d);
470}
471
472std::vector<PathTime> Path::roots(Coord v, Dim2 d) const
473{
474 std::vector<PathTime> res;
475 for (unsigned i = 0; i < size(); i++) {
476 std::vector<Coord> temp = (*this)[i].roots(v, d);
477 for (double j : temp)
478 res.emplace_back(i, j);
479 }
480 return res;
481}
482
483
484// The class below implements sweepline optimization for curve intersection in paths.
485// Instead of O(N^2), this takes O(N + X), where X is the number of overlaps
486// between the bounding boxes of curves.
487
488struct CurveIntersectionSweepSet
489{
490public:
491 struct CurveRecord {
492 boost::intrusive::list_member_hook<> _hook;
493 Curve const *curve;
494 Rect bounds;
495 std::size_t index;
496 unsigned which;
497
498 CurveRecord(Curve const *pc, std::size_t idx, unsigned w)
499 : curve(pc)
500 , bounds(curve->boundsFast())
501 , index(idx)
502 , which(w)
503 {}
504 };
505
506 typedef std::vector<CurveRecord>::const_iterator ItemIterator;
507
508 CurveIntersectionSweepSet(std::vector<PathIntersection> &result,
509 Path const &a, Path const &b, Coord precision)
510 : _result(result)
511 , _precision(precision)
512 , _sweep_dir(X)
513 {
514 std::size_t asz = a.size(), bsz = b.size();
515 _records.reserve(asz + bsz);
516
517 for (std::size_t i = 0; i < asz; ++i) {
518 _records.emplace_back(&a[i], i, 0);
519 }
520 for (std::size_t i = 0; i < bsz; ++i) {
521 _records.emplace_back(&b[i], i, 1);
522 }
523
524 OptRect abb = a.boundsFast() | b.boundsFast();
525 if (abb && abb->height() > abb->width()) {
526 _sweep_dir = Y;
527 }
528 }
529
530 std::vector<CurveRecord> const &items() { return _records; }
531 Interval itemBounds(ItemIterator ii) {
532 return ii->bounds[_sweep_dir];
533 }
534
535 void addActiveItem(ItemIterator ii) {
536 unsigned w = ii->which;
537 unsigned ow = (w+1) % 2;
538
539 _active[w].push_back(const_cast<CurveRecord&>(*ii));
540
541 for (auto & i : _active[ow]) {
542 if (!ii->bounds.intersects(i.bounds)) continue;
543 std::vector<CurveIntersection> cx = ii->curve->intersect(*i.curve, _precision);
544 for (auto & k : cx) {
545 PathTime tw(ii->index, k.first), tow(i.index, k.second);
546 _result.emplace_back(
547 w == 0 ? tw : tow,
548 w == 0 ? tow : tw,
549 k.point());
550 }
551 }
552 }
553 void removeActiveItem(ItemIterator ii) {
554 ActiveCurveList &acl = _active[ii->which];
555 acl.erase(acl.iterator_to(*ii));
556 }
557
558private:
559 typedef boost::intrusive::list
560 < CurveRecord
561 , boost::intrusive::member_hook
562 < CurveRecord
563 , boost::intrusive::list_member_hook<>
564 , &CurveRecord::_hook
565 >
566 > ActiveCurveList;
567
568 std::vector<CurveRecord> _records;
569 std::vector<PathIntersection> &_result;
570 ActiveCurveList _active[2];
572 Dim2 _sweep_dir;
573};
574
575std::vector<PathIntersection> Path::intersect(Path const &other, Coord precision) const
576{
577 std::vector<PathIntersection> result;
578
579 CurveIntersectionSweepSet cisset(result, *this, other, precision);
581 sweeper.process();
582
583 // preprocessing to remove duplicate intersections at endpoints
584 std::size_t asz = size(), bsz = other.size();
585 for (auto & i : result) {
586 i.first.normalizeForward(asz);
587 i.second.normalizeForward(bsz);
588 }
589 std::sort(result.begin(), result.end());
590 result.erase(std::unique(result.begin(), result.end()), result.end());
591
592 return result;
593}
594
595int Path::winding(Point const &p) const {
596 int wind = 0;
597
598 /* To handle all the edge cases, we consider the maximum Y edge of the bounding box
599 * as not included in box. This way paths that contain linear horizontal
600 * segments will be treated correctly. */
601 for (const_iterator i = begin(); i != end_closed(); ++i) {
602 Rect bounds = i->boundsFast();
603
604 if (bounds.height() == 0) continue;
605 if (p[X] > bounds.right() || !bounds[Y].lowerContains(p[Y])) {
606 // Ray doesn't intersect bbox, so we ignore this segment
607 continue;
608 }
609
610 if (p[X] < bounds.left()) {
611 /* Ray intersects the curve's bbox, but the point is outside it.
612 * The winding contribution is exactly the same as that
613 * of a linear segment with the same initial and final points. */
614 Point ip = i->initialPoint();
615 Point fp = i->finalPoint();
616 Rect eqbox(ip, fp);
617
618 if (eqbox[Y].lowerContains(p[Y])) {
619 /* The ray intersects the equivalent linear segment.
620 * Determine winding contribution based on its derivative. */
621 if (ip[Y] < fp[Y]) {
622 wind += 1;
623 } else if (ip[Y] > fp[Y]) {
624 wind -= 1;
625 } else {
626 // should never happen, because bounds.height() was not zero
627 assert(false);
628 }
629 }
630 } else {
631 // point is inside bbox
632 wind += i->winding(p);
633 }
634 }
635 return wind;
636}
637
638std::vector<double> Path::allNearestTimes(Point const &_point, double from, double to) const
639{
640 // TODO from and to are not used anywhere.
641 // rewrite this to simplify.
642 using std::swap;
643
644 if (from > to)
645 swap(from, to);
646 const Path &_path = *this;
647 unsigned int sz = _path.size();
648 if (_path.closed())
649 ++sz;
650 if (from < 0 || to > sz) {
651 THROW_RANGEERROR("[from,to] interval out of bounds");
652 }
653 double sif, st = modf(from, &sif);
654 double eif, et = modf(to, &eif);
655 unsigned int si = static_cast<unsigned int>(sif);
656 unsigned int ei = static_cast<unsigned int>(eif);
657 if (si == sz) {
658 --si;
659 st = 1;
660 }
661 if (ei == sz) {
662 --ei;
663 et = 1;
664 }
665 if (si == ei) {
666 std::vector<double> all_nearest = _path[si].allNearestTimes(_point, st, et);
667 for (double & i : all_nearest) {
668 i = si + i;
669 }
670 return all_nearest;
671 }
672 std::vector<double> all_t;
673 std::vector<std::vector<double> > all_np;
674 all_np.push_back(_path[si].allNearestTimes(_point, st));
675 std::vector<unsigned int> ni;
676 ni.push_back(si);
677 double dsq;
678 double mindistsq = distanceSq(_point, _path[si].pointAt(all_np.front().front()));
679 Rect bb(Geom::Point(0, 0), Geom::Point(0, 0));
680 for (unsigned int i = si + 1; i < ei; ++i) {
681 bb = (_path[i].boundsFast());
682 dsq = distanceSq(_point, bb);
683 if (mindistsq < dsq)
684 continue;
685 all_t = _path[i].allNearestTimes(_point);
686 dsq = distanceSq(_point, _path[i].pointAt(all_t.front()));
687 if (mindistsq > dsq) {
688 all_np.clear();
689 all_np.push_back(all_t);
690 ni.clear();
691 ni.push_back(i);
692 mindistsq = dsq;
693 } else if (mindistsq == dsq) {
694 all_np.push_back(all_t);
695 ni.push_back(i);
696 }
697 }
698 bb = (_path[ei].boundsFast());
699 dsq = distanceSq(_point, bb);
700 if (mindistsq >= dsq) {
701 all_t = _path[ei].allNearestTimes(_point, 0, et);
702 dsq = distanceSq(_point, _path[ei].pointAt(all_t.front()));
703 if (mindistsq > dsq) {
704 for (double & i : all_t) {
705 i = ei + i;
706 }
707 return all_t;
708 } else if (mindistsq == dsq) {
709 all_np.push_back(all_t);
710 ni.push_back(ei);
711 }
712 }
713 std::vector<double> all_nearest;
714 for (unsigned int i = 0; i < all_np.size(); ++i) {
715 for (unsigned int j = 0; j < all_np[i].size(); ++j) {
716 all_nearest.push_back(ni[i] + all_np[i][j]);
717 }
718 }
719 all_nearest.erase(std::unique(all_nearest.begin(), all_nearest.end()), all_nearest.end());
720 return all_nearest;
721}
722
723std::vector<Coord> Path::nearestTimePerCurve(Point const &p) const
724{
725 // return a single nearest time for each curve in this path
726 std::vector<Coord> np;
727 for (const_iterator it = begin(); it != end_default(); ++it) {
728 np.push_back(it->nearestTime(p));
729 }
730 return np;
731}
732
733PathTime Path::nearestTime(Point const &p, Coord *dist) const
734{
735 Coord mindist = std::numeric_limits<Coord>::max();
736 PathTime ret;
737
738 if (_data->curves.size() == 1) {
739 // naked moveto
740 ret.curve_index = 0;
741 ret.t = 0;
742 if (dist) {
743 *dist = distance(_closing_seg->initialPoint(), p);
744 }
745 return ret;
746 }
747
748 for (size_type i = 0; i < size_default(); ++i) {
749 Curve const &c = at(i);
750 if (distance(p, c.boundsFast()) >= mindist) continue;
751
752 Coord t = c.nearestTime(p);
753 Coord d = distance(c.pointAt(t), p);
754 if (d < mindist) {
755 mindist = d;
756 ret.curve_index = i;
757 ret.t = t;
758 }
759 }
760 if (dist) {
761 *dist = mindist;
762 }
763
764 return ret;
765}
766
767std::vector<Point> Path::nodes() const
768{
769 std::vector<Point> result;
770 size_type path_size = size_closed();
771 for (size_type i = 0; i < path_size; ++i) {
772 result.push_back(_data->curves[i].initialPoint());
773 }
774 return result;
775}
776
777void Path::appendPortionTo(Path &ret, double from, double to) const
778{
779 if (!(from >= 0 && to >= 0)) {
780 THROW_RANGEERROR("from and to must be >=0 in Path::appendPortionTo");
781 }
782 if (to == 0)
783 to = size() + 0.999999;
784 if (from == to) {
785 return;
786 }
787 double fi, ti;
788 double ff = modf(from, &fi), tf = modf(to, &ti);
789 if (tf == 0) {
790 ti--;
791 tf = 1;
792 }
793 const_iterator fromi = inc(begin(), (unsigned)fi);
794 if (fi == ti && from < to) {
795 ret.append(fromi->portion(ff, tf));
796 return;
797 }
798 const_iterator toi = inc(begin(), (unsigned)ti);
799 if (ff != 1.) {
800 // fromv->setInitial(ret.finalPoint());
801 ret.append(fromi->portion(ff, 1.));
802 }
803 if (from >= to) {
804 const_iterator ender = end();
805 if (ender->initialPoint() == ender->finalPoint())
806 ++ender;
807 ret.insert(ret.end(), ++fromi, ender);
808 ret.insert(ret.end(), begin(), toi);
809 } else {
810 ret.insert(ret.end(), ++fromi, toi);
811 }
812 ret.append(toi->portion(0., tf));
813}
814
815void Path::appendPortionTo(Path &target, PathInterval const &ival,
816 std::optional<Point> const &p_from, std::optional<Point> const &p_to) const
817{
818 assert(ival.pathSize() == size_closed());
819
820 if (ival.isDegenerate()) {
821 Point stitch_to = p_from ? *p_from : pointAt(ival.from());
822 target.stitchTo(stitch_to);
823 return;
824 }
825
826 PathTime const &from = ival.from(), &to = ival.to();
827
828 bool reverse = ival.reverse();
829 int di = reverse ? -1 : 1;
831
832 if (!ival.crossesStart() && from.curve_index == to.curve_index) {
833 Curve *c = (*this)[from.curve_index].portion(from.t, to.t);
834 if (p_from) {
835 c->setInitial(*p_from);
836 }
837 if (p_to) {
838 c->setFinal(*p_to);
839 }
840 target.append(c);
841 } else {
842 Curve *c_first = (*this)[from.curve_index].portion(from.t, reverse ? 0 : 1);
843 if (p_from) {
844 c_first->setInitial(*p_from);
845 }
846 target.append(c_first);
847
848 for (size_type i = (from.curve_index + s + di) % s; i != to.curve_index;
849 i = (i + s + di) % s)
850 {
851 if (reverse) {
852 target.append((*this)[i].reverse());
853 } else {
854 target.append((*this)[i].duplicate());
855 }
856 }
857
858 Curve *c_last = (*this)[to.curve_index].portion(reverse ? 1 : 0, to.t);
859 if (p_to) {
860 c_last->setFinal(*p_to);
861 }
862 target.append(c_last);
863 }
864}
865
867{
868 typedef std::reverse_iterator<Sequence::const_iterator> RIter;
869
870 Path ret(finalPoint());
871 if (empty()) return ret;
872
873 ret._data->curves.pop_back(); // this also deletes the closing segment from ret
874
875 RIter iter(_includesClosingSegment() ? _data->curves.end() : _data->curves.end() - 1);
876 RIter rend(_data->curves.begin());
877
878 if (_closed) {
879 // when the path is closed, there are two cases:
880 if (front().isLineSegment()) {
881 // 1. initial segment is linear: it becomes the new closing segment.
882 rend = RIter(_data->curves.begin() + 1);
884 } else {
885 // 2. initial segment is not linear: the closing segment becomes degenerate.
886 // However, skip it if it's already degenerate.
887 Point fp = finalPoint();
888 ret._closing_seg = new ClosingSegment(fp, fp);
889 }
890 } else {
891 // when the path is open, we reverse all real curves, and add a reversed closing segment.
892 ret._closing_seg = static_cast<ClosingSegment *>(_closing_seg->reverse());
893 }
894
895 for (; iter != rend; ++iter) {
896 ret._data->curves.push_back(iter->reverse());
897 }
898 ret._data->curves.push_back(ret._closing_seg);
899 ret._closed = _closed;
900 return ret;
901}
902
903
905{
906 _unshare();
907 Sequence::iterator seq_pos(seq_iter(pos));
908 Sequence source;
909 source.push_back(curve.duplicate());
910 do_update(seq_pos, seq_pos, source);
911}
912
914{
915 _unshare();
916 Sequence::iterator seq_pos(seq_iter(pos));
917
918 Sequence stitched;
919 do_update(seq_pos, seq_pos + 1, stitched);
920}
921
923{
924 _unshare();
925 Sequence::iterator seq_first = seq_iter(first);
926 Sequence::iterator seq_last = seq_iter(last);
927
928 Sequence stitched;
929 do_update(seq_first, seq_last, stitched);
930}
931
932void Path::stitchTo(Point const &p)
933{
934 if (!empty() && _closing_seg->initialPoint() != p) {
936 THROW_CONTINUITYERROR();
937 }
938 _unshare();
940 }
941}
942
943void Path::replace(iterator replaced, Curve const &curve)
944{
945 replace(replaced, replaced + 1, curve);
946}
947
948void Path::replace(iterator first_replaced, iterator last_replaced, Curve const &curve)
949{
950 _unshare();
951 Sequence::iterator seq_first_replaced(seq_iter(first_replaced));
952 Sequence::iterator seq_last_replaced(seq_iter(last_replaced));
953 Sequence source(1);
954 source.push_back(curve.duplicate());
955
956 do_update(seq_first_replaced, seq_last_replaced, source);
957}
958
959void Path::replace(iterator replaced, Path const &path)
960{
961 replace(replaced, path.begin(), path.end());
962}
963
964void Path::replace(iterator first, iterator last, Path const &path)
965{
966 replace(first, last, path.begin(), path.end());
967}
968
969void Path::snapEnds(Coord precision)
970{
971 if (!_closed) return;
972 if (_data->curves.size() > 1 && are_near(_closing_seg->length(precision), 0, precision)) {
973 _unshare();
975 (_data->curves.end() - 1)->setFinal(_closing_seg->finalPoint());
976 }
977}
978
980{
981 Sequence cleaned;
982 cleaned.reserve(size());
983
984 for (auto it = begin(); it != end_open(); ++it) {
985 if (!it->isDegenerate()) {
986 cleaned.push_back(it->duplicate());
987 }
988 }
989
990 Path result;
992 result.do_update(result._data->curves.begin(), result._data->curves.end(), cleaned);
993 return result;
994}
995
996// Replace curves between first and last with the contents of source.
997void Path::do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source)
998{
999 // TODO: handle cases where first > last in closed paths?
1000 bool last_beyond_closing_segment = (last == _data->curves.end());
1001
1002 // special case:
1003 // if do_update replaces the closing segment, we have to regenerate it
1004 if (source.empty()) {
1005 if (first == last) return; // nothing to do
1006
1007 // only removing some segments
1008 if ((!_closed && first == _data->curves.begin()) || (!_closed && last == _data->curves.end() - 1) || last_beyond_closing_segment) {
1009 // just adjust the closing segment
1010 // do nothing
1011 } else if (first->initialPoint() != (last - 1)->finalPoint()) {
1013 THROW_CONTINUITYERROR();
1014 }
1015 source.push_back(new StitchSegment(first->initialPoint(), (last - 1)->finalPoint()));
1016 }
1017 } else {
1018 // replacing
1019 if (first == _data->curves.begin() && last == _data->curves.end()) {
1020 // special case: replacing everything should work the same in open and closed curves
1021 _data->curves.erase(_data->curves.begin(), _data->curves.end() - 1);
1022 _closing_seg->setFinal(source.front().initialPoint());
1023 _closing_seg->setInitial(source.back().finalPoint());
1024 _data->curves.transfer(_data->curves.begin(), source.begin(), source.end(), source);
1025 return;
1026 }
1027
1028 // stitch in front
1029 if (!_closed && first == _data->curves.begin()) {
1030 // not necessary to stitch in front
1031 } else if (first->initialPoint() != source.front().initialPoint()) {
1033 THROW_CONTINUITYERROR();
1034 }
1035 source.insert(source.begin(), new StitchSegment(first->initialPoint(), source.front().initialPoint()));
1036 }
1037
1038 // stitch at the end
1039 if ((!_closed && last == _data->curves.end() - 1) || last_beyond_closing_segment) {
1040 // repurpose the closing segment as the stitch segment
1041 // do nothing
1042 } else if (source.back().finalPoint() != (last - 1)->finalPoint()) {
1044 THROW_CONTINUITYERROR();
1045 }
1046 source.push_back(new StitchSegment(source.back().finalPoint(), (last - 1)->finalPoint()));
1047 }
1048 }
1049
1050 // do not erase the closing segment, adjust it instead
1051 if (last_beyond_closing_segment) {
1052 --last;
1053 }
1054 _data->curves.erase(first, last);
1055 _data->curves.transfer(first, source.begin(), source.end(), source);
1056
1057 // adjust closing segment
1058 if (size_open() == 0) {
1060 } else {
1063 }
1064
1066}
1067
1069{
1070 if (&_data->curves.front() == _closing_seg) {
1071 _closing_seg->setFinal(c->initialPoint());
1072 } else {
1073 // if we can't freely move the closing segment, we check whether
1074 // the new curve connects with the last non-closing curve
1075 if (c->initialPoint() != _closing_seg->initialPoint()) {
1076 THROW_CONTINUITYERROR();
1077 }
1078 if (_closed && c->isLineSegment() &&
1079 c->finalPoint() == _closing_seg->finalPoint())
1080 {
1081 // appending a curve that matches the closing segment has no effect
1082 delete c;
1083 return;
1084 }
1085 }
1086 _data->curves.insert(_data->curves.end() - 1, c);
1087 _closing_seg->setInitial(c->finalPoint());
1088}
1089
1091{
1092 Sequence::const_iterator i = _data->curves.begin(), j = _data->curves.begin();
1093 ++j;
1094 for (; j != _data->curves.end(); ++i, ++j) {
1095 if (i->finalPoint() != j->initialPoint()) {
1096 THROW_CONTINUITYERROR();
1097 }
1098 }
1099 if (_data->curves.front().initialPoint() != _data->curves.back().finalPoint()) {
1100 THROW_CONTINUITYERROR();
1101 }
1102}
1103
1104// breaks time value into integral and fractional part
1106{
1107 size_type sz = size_default();
1108 if (t < 0 || t > sz) {
1109 THROW_RANGEERROR("parameter t out of bounds");
1110 }
1111
1112 PathTime ret;
1113 Coord k;
1114 ret.t = modf(t, &k);
1115 ret.curve_index = k;
1116 if (ret.curve_index == sz) {
1117 --ret.curve_index;
1118 ret.t = 1;
1119 }
1120 return ret;
1121}
1122
1124{
1125 Piecewise<D2<SBasis> > ret = paths[0].toPwSb();
1126 for (unsigned i = 1; i < paths.size(); i++) {
1127 ret.concat(paths[i].toPwSb());
1128 }
1129 return ret;
1130}
1131
1132bool are_near(Path const &a, Path const &b, Coord precision)
1133{
1134 if (a.size() != b.size()) return false;
1135
1136 for (unsigned i = 0; i < a.size(); ++i) {
1137 if (!a[i].isNear(b[i], precision)) return false;
1138 }
1139 return true;
1140}
1141
1142std::ostream &operator<<(std::ostream &out, Path const &path)
1143{
1144 SVGPathWriter pw;
1145 pw.feed(path);
1146 out << pw.str();
1147 return out;
1148}
1149
1150} // end namespace Geom
1151
1152/*
1153 Local Variables:
1154 mode:c++
1155 c-file-style:"stroustrup"
1156 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1157 indent-tabs-mode:nil
1158 fill-column:99
1159 End:
1160*/
1161// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Path - a sequence of contiguous curves.
Geom::IntRect bounds
Definition canvas.cpp:182
Circle shape.
void setInitial(Point const &v) override
Change the starting point of the curve.
Coord length(Coord tolerance) const override
Compute the arc length of this curve.
Point finalPoint() const override
Retrieve the end of the curve.
Point initialPoint() const override
Retrieve the start of the curve.
void setFinal(Point const &v) override
Change the ending point of the curve.
Set of all points at a fixed distance from the center.
Definition circle.h:55
Convex hull based on the Andrew's monotone chain algorithm.
bool empty() const
Check for emptiness.
Point const & back() const
Get the penultimate point of the lower hull.
size_t size() const
Get the number of points in the hull.
Point const & front() const
Get the first, leftmost point in the hull.
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
virtual Rect boundsFast() const =0
Quickly compute the curve's approximate bounding box.
virtual Rect boundsExact() const =0
Compute the curve's exact bounding box.
virtual Coord valueAt(Coord t, Dim2 d) const
Evaluate one of the coordinates at the specified time value.
Definition curve.h:116
virtual void setInitial(Point const &v)=0
Change the starting point of the curve.
virtual Curve * portion(Coord a, Coord b) const =0
Create a curve that corresponds to a part of this curve.
virtual void setFinal(Point const &v)=0
Change the ending point of the curve.
virtual Point pointAt(Coord t) const
Evaluate the curve at a specified time value.
Definition curve.h:110
Set of points with a constant sum of distances from two foci.
Definition ellipse.h:68
Angle rotationAngle() const
Get the angle the X ray makes with the +X axis.
Definition ellipse.h:126
Point pointAt(Coord t) const
Evaluate a point on the ellipse.
Definition ellipse.cpp:358
Point rays() const
Get both rays as a point.
Definition ellipse.h:122
Elliptical arc curve.
C right() const
Return rightmost coordinate of the rectangle (+X is to the right).
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C height() const
Get the vertical extent of the rectangle.
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
CPoint corner(unsigned i) const
Return the n-th corner of the rectangle.
Range of real numbers that is never empty.
Definition interval.h:59
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Contiguous subset of the path's parameter domain.
Definition path.h:187
PathInternal::Sequence::size_type size_type
Definition path.h:189
PathTime _to
Definition path.h:242
static PathInterval from_direction(PathTime const &from, PathTime const &to, bool reversed, size_type path_size)
Select one of two intervals with given endpoints by parameter direction.
Definition path.cpp:223
bool reverse() const
True if the interval goes in the direction of decreasing time values.
Definition path.h:216
size_type _path_size
Definition path.h:243
PathTime _from
Definition path.h:242
bool _cross_start
Definition path.h:244
PathTime inside(Coord min_dist=EPSILON) const
Get a time at least min_dist away in parameter space from the ends.
Definition path.cpp:136
size_type curveCount() const
Definition path.cpp:118
bool isDegenerate() const
Check whether the interval has only one point.
Definition path.h:214
PathTime const & to() const
Definition path.h:211
bool contains(PathTime const &pos) const
Test a path time for inclusion.
Definition path.cpp:102
bool crossesStart() const
True if the interior of the interval contains the initial point of the path.
Definition path.h:218
PathTime const & from() const
Definition path.h:210
size_type pathSize() const
Definition path.h:238
PathInterval()
Default interval.
Definition path.cpp:53
virtual void feed(Curve const &c, bool moveto_initial=true)
Definition path-sink.cpp:39
Sequence of subpaths.
Definition pathvector.h:122
Curve * reverse() const override
Create a reversed version of this curve.
Definition path.h:367
Sequence of contiguous curves, aka spline.
Definition path.h:353
bool closed() const
Check whether the path is closed.
Definition path.h:503
Point finalPoint() const
Get the last point in the path.
Definition path.h:709
bool operator==(Path const &other) const
Test paths for exact equality.
Definition path.cpp:417
void close(bool closed=true)
Set whether the path is closed.
Definition path.cpp:322
Interval timeRange() const
Get the allowed range of time values.
Definition path.cpp:434
std::shared_ptr< PathData > _data
Definition path.h:860
friend void swap(Path &a, Path &b) noexcept
Swap contents of two paths.
Definition path.h:433
bool empty() const
Check whether path is empty.
Definition path.h:500
OptRect boundsExact() const
Get a tight-fitting bounding box.
Definition path.cpp:372
Piecewise< D2< SBasis > > toPwSb() const
Definition path.cpp:388
bool _exception_on_stitch
Definition path.h:863
Path(Point const &p=Point())
Construct an empty path starting at the specified point.
Definition path.h:381
std::vector< PathTime > roots(Coord v, Dim2 d) const
Compute intersections with axis-aligned line.
Definition path.cpp:472
const_iterator end() const
Definition path.h:465
PathTime nearestTime(Point const &p, Coord *dist=NULL) const
Definition path.cpp:733
Point pointAt(Coord t) const
Get the point at the specified time value.
Definition path.cpp:449
OptRect boundsFast() const
Get the approximate bounding box.
Definition path.cpp:348
size_type size_open() const
Size without the closing segment, even if the path is closed.
Definition path.h:476
void clear()
Remove all curves from the path.
Definition path.cpp:337
Path withoutDegenerateCurves() const
Return a copy of the path without degenerate curves, except possibly for a degenerate closing segment...
Definition path.cpp:979
const_iterator end_open() const
Definition path.h:467
size_type size_default() const
Natural size of the path.
Definition path.h:486
void _unshare()
Definition path.h:843
ClosingSegment * _closing_seg
Definition path.h:861
Curve const & back_open() const
Definition path.h:448
std::vector< PathIntersection > intersect(Path const &other, Coord precision=EPSILON) const
Compute intersections with another path.
Definition path.cpp:575
int winding(Point const &p) const
Determine the winding number at the specified point.
Definition path.cpp:595
PathInternal::Sequence Sequence
Definition path.h:356
void appendPortionTo(Path &p, Coord f, Coord t) const
Definition path.cpp:777
Sequence::size_type size_type
Definition path.h:359
bool _includesClosingSegment() const
Definition path.h:840
void append(Curve *curve)
Add a new curve to the end of the path.
Definition path.h:750
static Sequence::iterator seq_iter(iterator const &iter)
Definition path.h:832
Point initialPoint() const
Get the first point in the path.
Definition path.h:705
Curve const & front() const
Access the first curve in the path.
Definition path.h:443
void do_append(Curve *curve)
Definition path.cpp:1068
Path reversed() const
Obtain a reversed version of the current path.
Definition path.cpp:866
bool _closed
Definition path.h:862
void setFinal(Point const &p)
Definition path.h:740
void snapEnds(Coord precision=EPSILON)
Reduce the closing segment to a point if it's shorter than precision.
Definition path.cpp:969
size_type size_closed() const
Size with the closing segment, if it makes a difference.
Definition path.h:481
size_type size() const
Natural size of the path.
Definition path.h:490
Coord valueAt(Coord t, Dim2 d) const
Get one coordinate (X or Y) at the specified time value.
Definition path.cpp:454
const_iterator begin() const
Definition path.h:464
void replace(iterator replaced, Curve const &curve)
Definition path.cpp:943
Curve const & at(size_type i) const
Access a curve by index.
Definition path.h:438
void checkContinuity() const
Verify the continuity invariant.
Definition path.cpp:1090
void do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source)
Definition path.cpp:997
void erase(iterator pos)
Definition path.cpp:913
std::vector< Coord > allNearestTimes(Point const &p, Coord from, Coord to) const
Definition path.cpp:638
PathTime _factorTime(Coord t) const
Definition path.cpp:1105
Curve const & curveAt(Coord t, Coord *rest=NULL) const
Get the curve at the specified time value.
Definition path.cpp:440
const_iterator end_closed() const
Definition path.h:468
std::vector< Coord > nearestTimePerCurve(Point const &p) const
Definition path.cpp:723
std::vector< Point > nodes() const
Definition path.cpp:767
const_iterator end_default() const
Definition path.h:466
void stitchTo(Point const &p)
Append a stitching segment ending at the specified point.
Definition path.cpp:932
void insert(iterator pos, Curve const &curve)
Definition path.cpp:904
void start(Point const &p)
Definition path.cpp:426
Function defined as discrete pieces.
Definition piecewise.h:71
void push(const T &s, double to)
Convenience/implementation hiding function to add segment/cut pairs.
Definition piecewise.h:141
void push_cut(double c)
Definition piecewise.h:152
void concat(const Piecewise< T > &other)
Definition piecewise.h:235
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
Serialize paths to SVG path data strings.
std::string str() const
Retrieve the generated path data string.
Generic sweepline algorithm.
Definition sweeper.h:93
void process()
Process entry and exit events.
Definition sweeper.h:109
Path and its polyline approximation.
Definition Path.h:93
const double w
Definition conic-4.cpp:19
Convex hull data structures.
Point const * _data
Css & result
double c[8][4]
Ellipse shape.
std::vector< ItemIterator > _active
double const _precision
std::vector< Geom::PathVector > _result
BezierCurveN< 1 > LineSegment
Line segment.
constexpr Coord lerp(Coord t, Coord a, Coord b)
Numerically stable linear interpolation.
Definition coord.h:97
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
vector< vector< Point > > paths
Definition metro.cpp:36
Various utility functions.
Definition affine.h:22
Bezier reverse(const Bezier &a)
Definition bezier.h:342
Piecewise< D2< SBasis > > paths_to_pw(PathVector const &paths)
Definition path.cpp:1123
std::ostream & operator<<(std::ostream &os, const Bezier &b)
Definition bezier.h:372
Coord distanceSq(Point const &p, Rect const &rect)
Definition rect.cpp:158
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
iter inc(iter const &x, unsigned n)
Definition path.cpp:410
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
PathVector - a sequence of subpaths.
GList * items
Generalized time value in the path.
Definition path.h:139
size_type curve_index
Index of the curve in the path.
Definition path.h:143
Coord t
Time value in the curve.
Definition path.h:142
void normalizeBackward(size_type path_size)
Convert times at or before 0 to 1 on the previous curve.
Definition path.h:166
void normalizeForward(size_type path_size)
Convert times at or beyond 1 to 0 on the next curve.
Definition path.h:159
Definition curve.h:24
Path sink which writes an SVG-compatible command string.
Class for implementing sweepline algorithms.
int index
Affine transformation classes.