| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** @file | ||
| 2 | * @brief Path - a sequence of contiguous curves | ||
| 3 | *//* | ||
| 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 | #ifndef LIB2GEOM_SEEN_PATH_H | ||
| 36 | #define LIB2GEOM_SEEN_PATH_H | ||
| 37 | |||
| 38 | #include <cstddef> | ||
| 39 | #include <iterator> | ||
| 40 | #include <algorithm> | ||
| 41 | #include <iostream> | ||
| 42 | #include <memory> | ||
| 43 | #include <optional> | ||
| 44 | #include <utility> | ||
| 45 | #include <vector> | ||
| 46 | |||
| 47 | #include <boost/operators.hpp> | ||
| 48 | #include <boost/ptr_container/ptr_vector.hpp> | ||
| 49 | |||
| 50 | #include <2geom/intersection.h> | ||
| 51 | #include <2geom/curve.h> | ||
| 52 | #include <2geom/bezier-curve.h> | ||
| 53 | #include <2geom/transforms.h> | ||
| 54 | |||
| 55 | namespace Geom { | ||
| 56 | |||
| 57 | class Path; | ||
| 58 | class ConvexHull; | ||
| 59 | |||
| 60 | namespace PathInternal { | ||
| 61 | |||
| 62 | typedef boost::ptr_vector<Curve> Sequence; | ||
| 63 | |||
| 64 | struct PathData { | ||
| 65 | Sequence curves; | ||
| 66 | OptRect fast_bounds; | ||
| 67 | }; | ||
| 68 | |||
| 69 | template <typename P> | ||
| 70 | class BaseIterator | ||
| 71 | : public boost::random_access_iterator_helper | ||
| 72 | < BaseIterator<P> | ||
| 73 | , Curve const | ||
| 74 | , std::ptrdiff_t | ||
| 75 | , Curve const * | ||
| 76 | , Curve const & | ||
| 77 | > | ||
| 78 | { | ||
| 79 | protected: | ||
| 80 | 313 | BaseIterator(P &p, unsigned i) : path(&p), index(i) {} | |
| 81 | // default copy, default assign | ||
| 82 | typedef BaseIterator<P> Self; | ||
| 83 | |||
| 84 | public: | ||
| 85 | 44 | BaseIterator() : path(NULL), index(0) {} | |
| 86 | |||
| 87 | bool operator<(BaseIterator const &other) const { | ||
| 88 | return path == other.path && index < other.index; | ||
| 89 | } | ||
| 90 | 480 | bool operator==(BaseIterator const &other) const { | |
| 91 |
3/4✓ Branch 0 taken 480 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 110 times.
✓ Branch 3 taken 370 times.
|
480 | return path == other.path && index == other.index; |
| 92 | } | ||
| 93 | 885 | Curve const &operator*() const { | |
| 94 | 885 | return (*path)[index]; | |
| 95 | } | ||
| 96 | |||
| 97 | 341 | Self &operator++() { | |
| 98 | 341 | ++index; | |
| 99 | 341 | return *this; | |
| 100 | } | ||
| 101 | ✗ | Self &operator--() { | |
| 102 | ✗ | --index; | |
| 103 | ✗ | return *this; | |
| 104 | } | ||
| 105 | ✗ | Self &operator+=(std::ptrdiff_t d) { | |
| 106 | ✗ | index += d; | |
| 107 | ✗ | return *this; | |
| 108 | } | ||
| 109 | Self &operator-=(std::ptrdiff_t d) { | ||
| 110 | index -= d; | ||
| 111 | return *this; | ||
| 112 | } | ||
| 113 | 222 | std::ptrdiff_t operator-(Self const &other) const { | |
| 114 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 222 times.
|
222 | assert(path == other.path); |
| 115 | 222 | return (std::ptrdiff_t)index - (std::ptrdiff_t)other.index; | |
| 116 | } | ||
| 117 | |||
| 118 | private: | ||
| 119 | P *path; | ||
| 120 | unsigned index; | ||
| 121 | |||
| 122 | friend class ::Geom::Path; | ||
| 123 | }; | ||
| 124 | |||
| 125 | } | ||
| 126 | |||
| 127 | /** @brief Generalized time value in the path. | ||
| 128 | * | ||
| 129 | * This class exists because when mapping the range of multiple curves onto the same interval | ||
| 130 | * as the curve index, we lose some precision. For instance, a path with 16 curves will | ||
| 131 | * have 4 bits less precision than a path with 1 curve. If you need high precision results | ||
| 132 | * in long paths, either use this class and related methods instead of the standard methods | ||
| 133 | * pointAt(), nearestTime() and so on, or use curveAt() to first obtain the curve, then | ||
| 134 | * call the method again to obtain a high precision result. | ||
| 135 | * | ||
| 136 | * @ingroup Paths */ | ||
| 137 | struct PathTime | ||
| 138 | : boost::totally_ordered<PathTime> | ||
| 139 | { | ||
| 140 | typedef PathInternal::Sequence::size_type size_type; | ||
| 141 | |||
| 142 | Coord t; ///< Time value in the curve | ||
| 143 | size_type curve_index; ///< Index of the curve in the path | ||
| 144 | |||
| 145 | 236 | PathTime() : t(0), curve_index(0) {} | |
| 146 | 404 | PathTime(size_type idx, Coord tval) : t(tval), curve_index(idx) {} | |
| 147 | |||
| 148 | 719 | bool operator<(PathTime const &other) const { | |
| 149 |
2/2✓ Branch 0 taken 174 times.
✓ Branch 1 taken 545 times.
|
719 | if (curve_index < other.curve_index) return true; |
| 150 |
2/2✓ Branch 0 taken 271 times.
✓ Branch 1 taken 274 times.
|
545 | if (curve_index == other.curve_index) { |
| 151 | 271 | return t < other.t; | |
| 152 | } | ||
| 153 | 274 | return false; | |
| 154 | } | ||
| 155 | 542 | bool operator==(PathTime const &other) const { | |
| 156 |
4/4✓ Branch 0 taken 221 times.
✓ Branch 1 taken 321 times.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 180 times.
|
542 | return curve_index == other.curve_index && t == other.t; |
| 157 | } | ||
| 158 | /// Convert times at or beyond 1 to 0 on the next curve. | ||
| 159 | 222 | void normalizeForward(size_type path_size) { | |
| 160 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 204 times.
|
222 | if (t >= 1) { |
| 161 | 18 | curve_index = (curve_index + 1) % path_size; | |
| 162 | 18 | t = 0; | |
| 163 | } | ||
| 164 | 222 | } | |
| 165 | /// Convert times at or before 0 to 1 on the previous curve. | ||
| 166 | 146 | void normalizeBackward(size_type path_size) { | |
| 167 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 125 times.
|
146 | if (t <= 0) { |
| 168 | 21 | curve_index = (curve_index - 1) % path_size; | |
| 169 | 21 | t = 1; | |
| 170 | } | ||
| 171 | 146 | } | |
| 172 | |||
| 173 | 16 | Coord asFlatTime() const { return curve_index + t; } | |
| 174 | }; | ||
| 175 | |||
| 176 | ✗ | inline std::ostream &operator<<(std::ostream &os, PathTime const &pos) { | |
| 177 | ✗ | os << pos.curve_index << ": " << format_coord_nice(pos.t); | |
| 178 | ✗ | return os; | |
| 179 | } | ||
| 180 | |||
| 181 | |||
| 182 | /** @brief Contiguous subset of the path's parameter domain. | ||
| 183 | * This is a directed interval, which allows one to specify any contiguous subset | ||
| 184 | * of the path's domain, including subsets that wrap around the initial point | ||
| 185 | * of the path. | ||
| 186 | * @ingroup Paths */ | ||
| 187 | class PathInterval { | ||
| 188 | public: | ||
| 189 | typedef PathInternal::Sequence::size_type size_type; | ||
| 190 | |||
| 191 | /** @brief Default interval. | ||
| 192 | * Default-constructed PathInterval includes only the initial point of the initial segment. */ | ||
| 193 | PathInterval(); | ||
| 194 | |||
| 195 | /** @brief Construct an interval in the path's parameter domain. | ||
| 196 | * @param from Initial time | ||
| 197 | * @param to Final time | ||
| 198 | * @param cross_start If true, the interval will proceed from the initial to final | ||
| 199 | * time through the initial point of the path, wrapping around the closing segment; | ||
| 200 | * otherwise it will not wrap around the closing segment. | ||
| 201 | * @param path_size Size of the path to which this interval applies, required | ||
| 202 | * to clean up degenerate cases */ | ||
| 203 | PathInterval(PathTime const &from, PathTime const &to, bool cross_start, size_type path_size); | ||
| 204 | |||
| 205 | /// Get the time value of the initial point. | ||
| 206 | PathTime const &initialTime() const { return _from; } | ||
| 207 | /// Get the time value of the final point. | ||
| 208 | PathTime const &finalTime() const { return _to; } | ||
| 209 | |||
| 210 | 82 | PathTime const &from() const { return _from; } | |
| 211 | 82 | PathTime const &to() const { return _to; } | |
| 212 | |||
| 213 | /// Check whether the interval has only one point. | ||
| 214 | 102 | bool isDegenerate() const { return _from == _to; } | |
| 215 | /// True if the interval goes in the direction of decreasing time values. | ||
| 216 | 59 | bool reverse() const { return _reverse; } | |
| 217 | /// True if the interior of the interval contains the initial point of the path. | ||
| 218 | 82 | bool crossesStart() const { return _cross_start; } | |
| 219 | |||
| 220 | /// Test a path time for inclusion. | ||
| 221 | bool contains(PathTime const &pos) const; | ||
| 222 | |||
| 223 | /// Get a time at least @a min_dist away in parameter space from the ends. | ||
| 224 | /// If no such time exists, the middle point is returned. | ||
| 225 | PathTime inside(Coord min_dist = EPSILON) const; | ||
| 226 | |||
| 227 | /// Select one of two intervals with given endpoints by parameter direction. | ||
| 228 | static PathInterval from_direction(PathTime const &from, PathTime const &to, | ||
| 229 | bool reversed, size_type path_size); | ||
| 230 | |||
| 231 | /// Select one of two intervals with given endpoints by whether it includes the initial point. | ||
| 232 | static PathInterval from_start_crossing(PathTime const &from, PathTime const &to, | ||
| 233 | bool cross_start, size_type path_size) { | ||
| 234 | PathInterval result(from, to, cross_start, path_size); | ||
| 235 | return result; | ||
| 236 | } | ||
| 237 | |||
| 238 | 82 | size_type pathSize() const { return _path_size; } | |
| 239 | size_type curveCount() const; | ||
| 240 | |||
| 241 | private: | ||
| 242 | PathTime _from, _to; | ||
| 243 | size_type _path_size; | ||
| 244 | bool _cross_start, _reverse; | ||
| 245 | }; | ||
| 246 | |||
| 247 | /// Create an interval in the direction of increasing time value. | ||
| 248 | /// @relates PathInterval | ||
| 249 | 44 | inline PathInterval forward_interval(PathTime const &from, PathTime const &to, | |
| 250 | PathInterval::size_type path_size) | ||
| 251 | { | ||
| 252 | 44 | PathInterval result = PathInterval::from_direction(from, to, false, path_size); | |
| 253 | 44 | return result; | |
| 254 | } | ||
| 255 | |||
| 256 | /// Create an interval in the direction of decreasing time value. | ||
| 257 | /// @relates PathInterval | ||
| 258 | inline PathInterval backward_interval(PathTime const &from, PathTime const &to, | ||
| 259 | PathInterval::size_type path_size) | ||
| 260 | { | ||
| 261 | PathInterval result = PathInterval::from_direction(from, to, true, path_size); | ||
| 262 | return result; | ||
| 263 | } | ||
| 264 | |||
| 265 | /// Output an interval in the path's domain. | ||
| 266 | /// @relates PathInterval | ||
| 267 | inline std::ostream &operator<<(std::ostream &os, PathInterval const &ival) { | ||
| 268 | os << "PathInterval["; | ||
| 269 | if (ival.crossesStart()) { | ||
| 270 | os << ival.from() << " -> 0: 0.0 -> " << ival.to(); | ||
| 271 | } else { | ||
| 272 | os << ival.from() << " -> " << ival.to(); | ||
| 273 | } | ||
| 274 | os << "]"; | ||
| 275 | return os; | ||
| 276 | } | ||
| 277 | |||
| 278 | typedef Intersection<PathTime> PathIntersection; | ||
| 279 | |||
| 280 | template <> | ||
| 281 | struct ShapeTraits<Path> { | ||
| 282 | typedef PathTime TimeType; | ||
| 283 | typedef PathInterval IntervalType; | ||
| 284 | typedef Path AffineClosureType; | ||
| 285 | typedef PathIntersection IntersectionType; | ||
| 286 | }; | ||
| 287 | |||
| 288 | /** @brief Stores information about the extremum points on a path, with respect | ||
| 289 | * to one of the coordinate axes. | ||
| 290 | * @relates Path::extrema() | ||
| 291 | */ | ||
| 292 | struct PathExtrema { | ||
| 293 | /** Points with the minimum and maximum value of a coordinate. */ | ||
| 294 | Point min_point, max_point; | ||
| 295 | |||
| 296 | /** Directions in which the OTHER coordinates change at the extremum points. | ||
| 297 | * | ||
| 298 | * - equals +1.0 if the other coordinate increases, | ||
| 299 | * - equals 0.0 if the other coordinate is constant (e.g., for an axis-aligned segment), | ||
| 300 | * - equals -1.0 if the other coordinate decreases. | ||
| 301 | */ | ||
| 302 | float glance_direction_at_min, glance_direction_at_max; | ||
| 303 | |||
| 304 | /** Path times corresponding to minimum and maximum points. */ | ||
| 305 | PathTime min_time, max_time; | ||
| 306 | }; | ||
| 307 | |||
| 308 | /** @brief Sequence of contiguous curves, aka spline. | ||
| 309 | * | ||
| 310 | * Path represents a sequence of contiguous curves, also known as a spline. | ||
| 311 | * It corresponds to a "subpath" in SVG terminology. It can represent both | ||
| 312 | * open and closed subpaths. The final point of each curve is exactly | ||
| 313 | * equal to the initial point of the next curve. | ||
| 314 | * | ||
| 315 | * The path always contains a linear closing segment that connects | ||
| 316 | * the final point of the last "real" curve to the initial point of the | ||
| 317 | * first curve. This way the curves form a closed loop even for open paths. | ||
| 318 | * If the closing segment has nonzero length and the path is closed, it is | ||
| 319 | * considered a normal part of the path data. There are three distinct sets | ||
| 320 | * of end iterators one can use to iterate over the segments: | ||
| 321 | * | ||
| 322 | * - Iterating between @a begin() and @a end() will iterate over segments | ||
| 323 | * which are part of the path. | ||
| 324 | * - Iterating between @a begin() and @a end_closed() | ||
| 325 | * will always iterate over a closed loop of segments. | ||
| 326 | * - Iterating between @a begin() and @a end_open() will always skip | ||
| 327 | * the final linear closing segment. | ||
| 328 | * | ||
| 329 | * If the final point of the last "real" segment coincides exactly with the initial | ||
| 330 | * point of the first segment, the closing segment will be absent from both | ||
| 331 | * [begin(), end_open()) and [begin(), end_closed()). | ||
| 332 | * | ||
| 333 | * Normally, an exception will be thrown when you try to insert a curve | ||
| 334 | * that makes the path non-continuous. If you are working with unsanitized | ||
| 335 | * curve data, you can call setStitching(true), which will insert line segments | ||
| 336 | * to make the path continuous. | ||
| 337 | * | ||
| 338 | * Internally, Path uses copy-on-write data. This is done for two reasons: first, | ||
| 339 | * copying a Curve requires calling a virtual function, so it's a little more expensive | ||
| 340 | * that normal copying; and second, it reduces the memory cost of copying the path. | ||
| 341 | * Therefore you can return Path and PathVector from functions without worrying | ||
| 342 | * about temporary copies. | ||
| 343 | * | ||
| 344 | * Note that this class cannot represent arbitrary shapes, which may contain holes. | ||
| 345 | * To do that, use PathVector, which is more generic. | ||
| 346 | * | ||
| 347 | * It's not very convenient to create a Path directly. To construct paths more easily, | ||
| 348 | * use PathBuilder. | ||
| 349 | * | ||
| 350 | * @ingroup Paths */ | ||
| 351 | class Path | ||
| 352 | : boost::equality_comparable< Path > | ||
| 353 | { | ||
| 354 | public: | ||
| 355 | typedef PathInternal::PathData PathData; | ||
| 356 | typedef PathInternal::Sequence Sequence; | ||
| 357 | typedef PathInternal::BaseIterator<Path> iterator; | ||
| 358 | typedef PathInternal::BaseIterator<Path const> const_iterator; | ||
| 359 | typedef Sequence::size_type size_type; | ||
| 360 | typedef Sequence::difference_type difference_type; | ||
| 361 | |||
| 362 | class ClosingSegment : public LineSegment { | ||
| 363 | public: | ||
| 364 | ClosingSegment() : LineSegment() {} | ||
| 365 | 341715 | ClosingSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {} | |
| 366 |
1/4✓ Branch 2 taken 861 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
861 | Curve *duplicate() const override { return new ClosingSegment(*this); } |
| 367 |
3/8✓ Branch 3 taken 164700 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 164700 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 164700 times.
✗ Branch 11 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
164700 | Curve *reverse() const override { return new ClosingSegment((*this)[1], (*this)[0]); } |
| 368 | }; | ||
| 369 | |||
| 370 | class StitchSegment : public LineSegment { | ||
| 371 | public: | ||
| 372 | StitchSegment() : LineSegment() {} | ||
| 373 | 16 | StitchSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {} | |
| 374 | ✗ | Curve *duplicate() const override { return new StitchSegment(*this); } | |
| 375 | ✗ | Curve *reverse() const override { return new StitchSegment((*this)[1], (*this)[0]); } | |
| 376 | }; | ||
| 377 | |||
| 378 | // Path(Path const &other) - use default copy constructor | ||
| 379 | |||
| 380 | /// Construct an empty path starting at the specified point. | ||
| 381 | 177015 | explicit Path(Point const &p = Point()) | |
| 382 |
2/6✓ Branch 1 taken 177015 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 177015 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
177015 | : _data(new PathData()) |
| 383 |
2/6✓ Branch 1 taken 177015 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 177015 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
177015 | , _closing_seg(new ClosingSegment(p, p)) |
| 384 | 177015 | , _closed(false) | |
| 385 | 177015 | , _exception_on_stitch(true) | |
| 386 | { | ||
| 387 |
1/2✓ Branch 2 taken 177015 times.
✗ Branch 3 not taken.
|
177015 | _data->curves.push_back(_closing_seg); |
| 388 | 177015 | } | |
| 389 | |||
| 390 | /// Construct a path containing a range of curves. | ||
| 391 | template <typename Iter> | ||
| 392 | Path(Iter first, Iter last, bool closed = false, bool stitch = false) | ||
| 393 | : _data(new PathData()) | ||
| 394 | , _closed(closed) | ||
| 395 | , _exception_on_stitch(!stitch) | ||
| 396 | { | ||
| 397 | for (Iter i = first; i != last; ++i) { | ||
| 398 | _data->curves.push_back(i->duplicate()); | ||
| 399 | } | ||
| 400 | if (!_data->curves.empty()) { | ||
| 401 | _closing_seg = new ClosingSegment(_data->curves.back().finalPoint(), | ||
| 402 | _data->curves.front().initialPoint()); | ||
| 403 | } else { | ||
| 404 | _closing_seg = new ClosingSegment(); | ||
| 405 | } | ||
| 406 | _data->curves.push_back(_closing_seg); | ||
| 407 | } | ||
| 408 | |||
| 409 | /// Create a path from a rectangle. | ||
| 410 | explicit Path(Rect const &r); | ||
| 411 | /// Create a path from a convex hull. | ||
| 412 | explicit Path(ConvexHull const &); | ||
| 413 | /// Create a path from a circle, using two elliptical arcs. | ||
| 414 | explicit Path(Circle const &c); | ||
| 415 | /// Create a path from an ellipse, using two elliptical arcs. | ||
| 416 | explicit Path(Ellipse const &e); | ||
| 417 | |||
| 418 | 1145682 | virtual ~Path() {} | |
| 419 | |||
| 420 | // Path &operator=(Path const &other) - use default assignment operator | ||
| 421 | |||
| 422 | /** @brief Swap contents with another path | ||
| 423 | * @todo Add noexcept specifiers for C++11 */ | ||
| 424 | 81900 | void swap(Path &other) noexcept { | |
| 425 | using std::swap; | ||
| 426 | 81900 | swap(other._data, _data); | |
| 427 | 81900 | swap(other._closing_seg, _closing_seg); | |
| 428 | 81900 | swap(other._closed, _closed); | |
| 429 | 81900 | swap(other._exception_on_stitch, _exception_on_stitch); | |
| 430 | 81900 | } | |
| 431 | /** @brief Swap contents of two paths. | ||
| 432 | * @relates Path */ | ||
| 433 | 81900 | friend inline void swap(Path &a, Path &b) noexcept { a.swap(b); } | |
| 434 | |||
| 435 | /** @brief Access a curve by index */ | ||
| 436 | 1039 | Curve const &operator[](size_type i) const { return _data->curves[i]; } | |
| 437 | /** @brief Access a curve by index */ | ||
| 438 | 669 | Curve const &at(size_type i) const { return _data->curves.at(i); } | |
| 439 | |||
| 440 | /** @brief Access the first curve in the path. | ||
| 441 | * Since the curve always contains at least a degenerate closing segment, | ||
| 442 | * it is always safe to use this method. */ | ||
| 443 | 157 | Curve const &front() const { return _data->curves.front(); } | |
| 444 | /// Alias for front(). | ||
| 445 | Curve const &initialCurve() const { return _data->curves.front(); } | ||
| 446 | /** @brief Access the last curve in the path. */ | ||
| 447 | Curve const &back() const { return back_default(); } | ||
| 448 | 12 | Curve const &back_open() const { | |
| 449 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
|
12 | if (empty()) return _data->curves.back(); |
| 450 | 12 | return _data->curves[_data->curves.size() - 2]; | |
| 451 | } | ||
| 452 | Curve const &back_closed() const { | ||
| 453 | return _closing_seg->isDegenerate() | ||
| 454 | ? _data->curves[_data->curves.size() - 2] | ||
| 455 | : _data->curves[_data->curves.size() - 1]; | ||
| 456 | } | ||
| 457 | Curve const &back_default() const { | ||
| 458 | return _includesClosingSegment() | ||
| 459 | ? back_closed() | ||
| 460 | : back_open(); | ||
| 461 | } | ||
| 462 | Curve const &finalCurve() const { return back_default(); } | ||
| 463 | |||
| 464 | 20 | const_iterator begin() const { return const_iterator(*this, 0); } | |
| 465 | 71 | const_iterator end() const { return end_default(); } | |
| 466 |
1/2✓ Branch 2 taken 293 times.
✗ Branch 3 not taken.
|
293 | const_iterator end_default() const { return const_iterator(*this, size_default()); } |
| 467 |
1/2✓ Branch 2 taken 59 times.
✗ Branch 3 not taken.
|
59 | const_iterator end_open() const { return const_iterator(*this, size_open()); } |
| 468 |
1/2✓ Branch 2 taken 1063527 times.
✗ Branch 3 not taken.
|
1063527 | const_iterator end_closed() const { return const_iterator(*this, size_closed()); } |
| 469 | 244 | iterator begin() { return iterator(*this, 0); } | |
| 470 | 44 | iterator end() { return end_default(); } | |
| 471 |
1/2✓ Branch 2 taken 44 times.
✗ Branch 3 not taken.
|
44 | iterator end_default() { return iterator(*this, size_default()); } |
| 472 | iterator end_open() { return iterator(*this, size_open()); } | ||
| 473 | iterator end_closed() { return iterator(*this, size_closed()); } | ||
| 474 | |||
| 475 | /// Size without the closing segment, even if the path is closed. | ||
| 476 | 12222 | size_type size_open() const { return _data->curves.size() - 1; } | |
| 477 | |||
| 478 | /** @brief Size with the closing segment, if it makes a difference. | ||
| 479 | * If the closing segment is degenerate, i.e. its initial and final points | ||
| 480 | * are exactly equal, then it is not included in this size. */ | ||
| 481 | ✗ | size_type size_closed() const { | |
| 482 | ✗ | return _closing_seg->isDegenerate() ? _data->curves.size() - 1 : _data->curves.size(); | |
| 483 | } | ||
| 484 | |||
| 485 | /// Natural size of the path. | ||
| 486 | 12222 | size_type size_default() const { | |
| 487 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 12222 times.
|
12222 | return _includesClosingSegment() ? size_closed() : size_open(); |
| 488 | } | ||
| 489 | /// Natural size of the path. | ||
| 490 | 12000 | size_type size() const { return size_default(); } | |
| 491 | |||
| 492 | size_type max_size() const { return _data->curves.max_size() - 1; } | ||
| 493 | |||
| 494 | /** @brief Check whether path is empty. | ||
| 495 | * The path is empty if it contains only the closing segment, which according | ||
| 496 | * to the continuity invariant must be degenerate. Note that unlike standard | ||
| 497 | * containers, two empty paths are not necessarily identical, because the | ||
| 498 | * degenerate closing segment may be at a different point, affecting the operation | ||
| 499 | * of methods such as appendNew(). */ | ||
| 500 | 450335 | bool empty() const { return (_data->curves.size() == 1); } | |
| 501 | |||
| 502 | /// Check whether the path is closed. | ||
| 503 | 86 | bool closed() const { return _closed; } | |
| 504 | |||
| 505 | /** @brief Set whether the path is closed. | ||
| 506 | * When closing a path where the last segment can be represented as a closing | ||
| 507 | * segment, the last segment will be removed. When opening a path, the closing | ||
| 508 | * segment will be erased. This means that closing and then opening a path | ||
| 509 | * will not always give back the original path. */ | ||
| 510 | void close(bool closed = true); | ||
| 511 | |||
| 512 | /** @brief Remove all curves from the path. | ||
| 513 | * The initial and final points of the closing segment are set to (0,0). | ||
| 514 | * The stitching flag remains unchanged. */ | ||
| 515 | void clear(); | ||
| 516 | |||
| 517 | /** @brief Get the approximate bounding box. | ||
| 518 | * The rectangle returned by this method will contain all the curves, but it's not | ||
| 519 | * guaranteed to be the smallest possible one */ | ||
| 520 | OptRect boundsFast() const; | ||
| 521 | |||
| 522 | /** @brief Get a tight-fitting bounding box. | ||
| 523 | * This will return the smallest possible axis-aligned rectangle containing | ||
| 524 | * all the curves in the path. */ | ||
| 525 | OptRect boundsExact() const; | ||
| 526 | |||
| 527 | Piecewise<D2<SBasis> > toPwSb() const; | ||
| 528 | |||
| 529 | /// Test paths for exact equality. | ||
| 530 | bool operator==(Path const &other) const; | ||
| 531 | |||
| 532 | /// Apply a transform to each curve. | ||
| 533 | template <typename T> | ||
| 534 | 36000 | Path &operator*=(T const &tr) { | |
| 535 | BOOST_CONCEPT_ASSERT((TransformConcept<T>)); | ||
| 536 | 36000 | _unshare(); | |
| 537 |
2/2✓ Branch 2 taken 1098000 times.
✓ Branch 3 taken 18000 times.
|
2232000 | for (std::size_t i = 0; i < _data->curves.size(); ++i) { |
| 538 | 2196000 | _data->curves[i] *= tr; | |
| 539 | } | ||
| 540 | 36000 | return *this; | |
| 541 | } | ||
| 542 | |||
| 543 | template <typename T> | ||
| 544 | ✗ | friend Path operator*(Path const &path, T const &tr) { | |
| 545 | BOOST_CONCEPT_ASSERT((TransformConcept<T>)); | ||
| 546 | ✗ | Path result(path); | |
| 547 | ✗ | result *= tr; | |
| 548 | ✗ | return result; | |
| 549 | ✗ | } | |
| 550 | |||
| 551 | /** @brief Get the allowed range of time values. | ||
| 552 | * @return Values for which pointAt() and valueAt() yield valid results. */ | ||
| 553 | Interval timeRange() const; | ||
| 554 | |||
| 555 | /** Get the curve at the specified time value. | ||
| 556 | * @param t Time value | ||
| 557 | * @param rest Optional storage for the corresponding time value in the curve */ | ||
| 558 | Curve const &curveAt(Coord t, Coord *rest = NULL) const; | ||
| 559 | |||
| 560 | /// Get the closing segment of the path. | ||
| 561 | LineSegment const &closingSegment() const { return *_closing_seg; } | ||
| 562 | |||
| 563 | /** @brief Get the point at the specified time value. | ||
| 564 | * Note that this method has reduced precision with respect to calling pointAt() | ||
| 565 | * directly on the curve. If you want high precision results, use the version | ||
| 566 | * that takes a PathTime parameter. | ||
| 567 | * | ||
| 568 | * Allowed time values range from zero to the number of curves; you can retrieve | ||
| 569 | * the allowed range of values with timeRange(). */ | ||
| 570 | Point pointAt(Coord t) const; | ||
| 571 | |||
| 572 | /// Get one coordinate (X or Y) at the specified time value. | ||
| 573 | Coord valueAt(Coord t, Dim2 d) const; | ||
| 574 | |||
| 575 | /// Get the curve at the specified path time. | ||
| 576 | Curve const &curveAt(PathTime const &pos) const; | ||
| 577 | /// Get the point at the specified path time. | ||
| 578 | Point pointAt(PathTime const &pos) const; | ||
| 579 | /// Get one coordinate at the specified path time. | ||
| 580 | Coord valueAt(PathTime const &pos, Dim2 d) const; | ||
| 581 | |||
| 582 | Point operator()(Coord t) const { return pointAt(t); } | ||
| 583 | |||
| 584 | /** @brief Find the extrema of the specified coordinate. | ||
| 585 | * | ||
| 586 | * Returns a PathExtrema struct describing "witness" points on the path | ||
| 587 | * where the specified coordinate attains its minimum and maximum values. | ||
| 588 | */ | ||
| 589 | PathExtrema extrema(Dim2 d) const; | ||
| 590 | |||
| 591 | /// Compute intersections with axis-aligned line. | ||
| 592 | std::vector<PathTime> roots(Coord v, Dim2 d) const; | ||
| 593 | |||
| 594 | /// Compute intersections with another path. | ||
| 595 | std::vector<PathIntersection> intersect(Path const &other, Coord precision = EPSILON) const; | ||
| 596 | |||
| 597 | /// Compute intersections of the path with itself. | ||
| 598 | std::vector<PathIntersection> intersectSelf(Coord precision = EPSILON) const; | ||
| 599 | |||
| 600 | /** @brief Determine the winding number at the specified point. | ||
| 601 | * | ||
| 602 | * The winding number is the number of full turns made by a ray that connects the passed | ||
| 603 | * point and the path's value (i.e. the result of the pointAt() method) as the time increases | ||
| 604 | * from 0 to the maximum valid value. Positive numbers indicate turns in the direction | ||
| 605 | * of increasing angles. | ||
| 606 | * | ||
| 607 | * Winding numbers are often used as the definition of what is considered "inside" | ||
| 608 | * the shape. Typically points with either nonzero winding or odd winding are | ||
| 609 | * considered to be inside the path. */ | ||
| 610 | int winding(Point const &p) const; | ||
| 611 | |||
| 612 | std::vector<Coord> allNearestTimes(Point const &p, Coord from, Coord to) const; | ||
| 613 | std::vector<Coord> allNearestTimes(Point const &p) const { | ||
| 614 | return allNearestTimes(p, 0, size_default()); | ||
| 615 | } | ||
| 616 | |||
| 617 | PathTime nearestTime(Point const &p, Coord *dist = NULL) const; | ||
| 618 | std::vector<Coord> nearestTimePerCurve(Point const &p) const; | ||
| 619 | |||
| 620 | std::vector<Point> nodes() const; | ||
| 621 | |||
| 622 | void appendPortionTo(Path &p, Coord f, Coord t) const; | ||
| 623 | |||
| 624 | /** @brief Append a subset of this path to another path. | ||
| 625 | * An extra stitching segment will be inserted if the start point of the portion | ||
| 626 | * and the final point of the target path do not match exactly. | ||
| 627 | * The closing segment of the target path will be modified. */ | ||
| 628 | void appendPortionTo(Path &p, PathTime const &from, PathTime const &to, bool cross_start = false) const { | ||
| 629 | PathInterval ival(from, to, cross_start, size_closed()); | ||
| 630 | appendPortionTo(p, ival, std::nullopt, std::nullopt); | ||
| 631 | } | ||
| 632 | |||
| 633 | /** @brief Append a subset of this path to another path. | ||
| 634 | * This version allows you to explicitly pass a PathInterval. */ | ||
| 635 | void appendPortionTo(Path &p, PathInterval const &ival) const { | ||
| 636 | appendPortionTo(p, ival, std::nullopt, std::nullopt); | ||
| 637 | } | ||
| 638 | |||
| 639 | /** @brief Append a subset of this path to another path, specifying endpoints. | ||
| 640 | * This method is for use in situations where endpoints of the portion segments | ||
| 641 | * have to be set exactly, for instance when computing Boolean operations. */ | ||
| 642 | void appendPortionTo(Path &p, PathInterval const &ival, | ||
| 643 | std::optional<Point> const &p_from, std::optional<Point> const &p_to) const; | ||
| 644 | |||
| 645 | Path portion(Coord f, Coord t) const { | ||
| 646 | Path ret; | ||
| 647 | ret.close(false); | ||
| 648 | appendPortionTo(ret, f, t); | ||
| 649 | return ret; | ||
| 650 | } | ||
| 651 | |||
| 652 | Path portion(Interval const &i) const { return portion(i.min(), i.max()); } | ||
| 653 | |||
| 654 | /** @brief Get a subset of the current path with full precision. | ||
| 655 | * When @a from is larger (later in the path) than @a to, the returned portion | ||
| 656 | * will be reversed. If @a cross_start is true, the portion will be reversed | ||
| 657 | * and will cross the initial point of the path. Therefore, when @a from is larger | ||
| 658 | * than @a to and @a cross_start is true, the returned portion will not be reversed, | ||
| 659 | * but will "wrap around" the end of the path. */ | ||
| 660 | Path portion(PathTime const &from, PathTime const &to, bool cross_start = false) const { | ||
| 661 | Path ret; | ||
| 662 | ret.close(false); | ||
| 663 | appendPortionTo(ret, from, to, cross_start); | ||
| 664 | return ret; | ||
| 665 | } | ||
| 666 | |||
| 667 | /** @brief Get a subset of the current path with full precision. | ||
| 668 | * This version allows you to explicitly pass a PathInterval. */ | ||
| 669 | Path portion(PathInterval const &ival) const { | ||
| 670 | Path ret; | ||
| 671 | ret.close(false); | ||
| 672 | appendPortionTo(ret, ival); | ||
| 673 | return ret; | ||
| 674 | } | ||
| 675 | |||
| 676 | /** @brief Obtain a reversed version of the current path. | ||
| 677 | * The final point of the current path will become the initial point | ||
| 678 | * of the reversed path, unless it is closed and has a non-degenerate | ||
| 679 | * closing segment. In that case, the new initial point will be the final point | ||
| 680 | * of the last "real" segment. */ | ||
| 681 | Path reversed() const; | ||
| 682 | |||
| 683 | void insert(iterator pos, Curve const &curve); | ||
| 684 | |||
| 685 | template <typename Iter> | ||
| 686 | ✗ | void insert(iterator pos, Iter first, Iter last) { | |
| 687 | ✗ | _unshare(); | |
| 688 | ✗ | Sequence::iterator seq_pos(seq_iter(pos)); | |
| 689 | ✗ | Sequence source; | |
| 690 | ✗ | for (; first != last; ++first) { | |
| 691 | ✗ | source.push_back(first->duplicate()); | |
| 692 | } | ||
| 693 | ✗ | do_update(seq_pos, seq_pos, source); | |
| 694 | ✗ | } | |
| 695 | |||
| 696 | void erase(iterator pos); | ||
| 697 | void erase(iterator first, iterator last); | ||
| 698 | |||
| 699 | // erase last segment of path | ||
| 700 | ✗ | void erase_last() { erase(iterator(*this, size() - 1)); } | |
| 701 | |||
| 702 | void start(Point const &p); | ||
| 703 | |||
| 704 | /** @brief Get the first point in the path. */ | ||
| 705 | 159 | Point initialPoint() const { return (*_closing_seg)[1]; } | |
| 706 | |||
| 707 | /** @brief Get the last point in the path. | ||
| 708 | * If the path is closed, this is always the same as the initial point. */ | ||
| 709 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 559974 times.
|
559974 | Point finalPoint() const { return (*_closing_seg)[_closed ? 1 : 0]; } |
| 710 | |||
| 711 | /** @brief Get the unit tangent vector at the start of the path, | ||
| 712 | * or the zero vector if undefined. */ | ||
| 713 | ✗ | Point initialUnitTangent() const { | |
| 714 | ✗ | for (auto const &curve : *this) { | |
| 715 | ✗ | if (!curve.isDegenerate()) { | |
| 716 | ✗ | return curve.unitTangentAt(0.0); | |
| 717 | } | ||
| 718 | } | ||
| 719 | ✗ | return Point(); | |
| 720 | } | ||
| 721 | |||
| 722 | /** @brief Get the unit tangent vector at the end of the path, | ||
| 723 | * or the zero vector if undefined. */ | ||
| 724 | ✗ | Point finalUnitTangent() const { | |
| 725 | ✗ | for (auto it = end(); it != begin();) { | |
| 726 | ✗ | --it; | |
| 727 | ✗ | if (!it->isDegenerate()) { | |
| 728 | ✗ | return it->unitTangentAt(1.0); | |
| 729 | } | ||
| 730 | } | ||
| 731 | ✗ | return Point(); | |
| 732 | } | ||
| 733 | |||
| 734 | void setInitial(Point const &p) { | ||
| 735 | _unshare(); | ||
| 736 | _closed = false; | ||
| 737 | _data->curves.front().setInitial(p); | ||
| 738 | _closing_seg->setFinal(p); | ||
| 739 | } | ||
| 740 | void setFinal(Point const &p) { | ||
| 741 | _unshare(); | ||
| 742 | _closed = false; | ||
| 743 | _data->curves[size_open() ? size_open() - 1 : 0].setFinal(p); | ||
| 744 | _closing_seg->setInitial(p); | ||
| 745 | } | ||
| 746 | |||
| 747 | /** @brief Add a new curve to the end of the path. | ||
| 748 | * This inserts the new curve right before the closing segment. | ||
| 749 | * The path takes ownership of the passed pointer, which should not be freed. */ | ||
| 750 | 116 | void append(Curve *curve) { | |
| 751 | 116 | _unshare(); | |
| 752 |
2/4✓ Branch 2 taken 116 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 116 times.
✗ Branch 6 not taken.
|
116 | stitchTo(curve->initialPoint()); |
| 753 | 116 | do_append(curve); | |
| 754 | 116 | } | |
| 755 | |||
| 756 | 68 | void append(Curve const &curve) { | |
| 757 | 68 | _unshare(); | |
| 758 |
2/4✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 68 times.
✗ Branch 6 not taken.
|
68 | stitchTo(curve.initialPoint()); |
| 759 | 68 | do_append(curve.duplicate()); | |
| 760 | 68 | } | |
| 761 | void append(D2<SBasis> const &curve) { | ||
| 762 | _unshare(); | ||
| 763 | stitchTo(Point(curve[X][0][0], curve[Y][0][0])); | ||
| 764 | do_append(new SBasisCurve(curve)); | ||
| 765 | } | ||
| 766 | void append(Path const &other) { | ||
| 767 | replace(end_open(), other.begin(), other.end()); | ||
| 768 | } | ||
| 769 | |||
| 770 | void replace(iterator replaced, Curve const &curve); | ||
| 771 | void replace(iterator first, iterator last, Curve const &curve); | ||
| 772 | void replace(iterator replaced, Path const &path); | ||
| 773 | void replace(iterator first, iterator last, Path const &path); | ||
| 774 | |||
| 775 | template <typename Iter> | ||
| 776 | ✗ | void replace(iterator replaced, Iter first, Iter last) { | |
| 777 | ✗ | replace(replaced, replaced + 1, first, last); | |
| 778 | ✗ | } | |
| 779 | |||
| 780 | template <typename Iter> | ||
| 781 | ✗ | void replace(iterator first_replaced, iterator last_replaced, Iter first, Iter last) { | |
| 782 | ✗ | _unshare(); | |
| 783 | ✗ | Sequence::iterator seq_first_replaced(seq_iter(first_replaced)); | |
| 784 | ✗ | Sequence::iterator seq_last_replaced(seq_iter(last_replaced)); | |
| 785 | ✗ | Sequence source; | |
| 786 | ✗ | for (; first != last; ++first) { | |
| 787 | ✗ | source.push_back(first->duplicate()); | |
| 788 | } | ||
| 789 | ✗ | do_update(seq_first_replaced, seq_last_replaced, source); | |
| 790 | ✗ | } | |
| 791 | |||
| 792 | /** @brief Append a new curve to the path. | ||
| 793 | * | ||
| 794 | * This family of methods will automatically use the current final point of the path | ||
| 795 | * as the first argument of the new curve's constructor. To call this method, | ||
| 796 | * you'll need to write e.g.: | ||
| 797 | * @code | ||
| 798 | path.template appendNew<CubicBezier>(control1, control2, end_point); | ||
| 799 | @endcode | ||
| 800 | * It is important to note that the coordinates passed to appendNew should be finite! | ||
| 801 | * If one of the coordinates is infinite, 2geom will throw a ContinuityError exception. | ||
| 802 | */ | ||
| 803 | template <typename CurveType, typename... Args> | ||
| 804 | 418548 | void appendNew(Args&&... args) { | |
| 805 | 418548 | _unshare(); | |
| 806 |
6/16✓ Branch 3 taken 549 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 394725 times.
✓ Branch 6 taken 549 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 394725 times.
✓ Branch 9 taken 549 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 394725 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
|
418548 | do_append(new CurveType(finalPoint(), std::forward<Args>(args)...)); |
| 807 | 418548 | } | |
| 808 | |||
| 809 | /** @brief Reduce the closing segment to a point if it's shorter than precision. | ||
| 810 | * Do this by moving the final point. */ | ||
| 811 | void snapEnds(Coord precision = EPSILON); | ||
| 812 | |||
| 813 | /// Append a stitching segment ending at the specified point. | ||
| 814 | void stitchTo(Point const &p); | ||
| 815 | |||
| 816 | /** @brief Return a copy of the path without degenerate curves, except possibly for a | ||
| 817 | * degenerate closing segment. */ | ||
| 818 | Path withoutDegenerateCurves() const; | ||
| 819 | |||
| 820 | /** @brief Verify the continuity invariant. | ||
| 821 | * If the path is not contiguous, this will throw a CountinuityError. */ | ||
| 822 | void checkContinuity() const; | ||
| 823 | |||
| 824 | /** @brief Enable or disable the throwing of exceptions when stitching discontinuities. | ||
| 825 | * Normally stitching will cause exceptions, but when you are working with unsanitized | ||
| 826 | * curve data, you can disable these exceptions. */ | ||
| 827 | 23 | void setStitching(bool x) { | |
| 828 | 23 | _exception_on_stitch = !x; | |
| 829 | 23 | } | |
| 830 | |||
| 831 | private: | ||
| 832 | ✗ | static Sequence::iterator seq_iter(iterator const &iter) { | |
| 833 | ✗ | return iter.path->_data->curves.begin() + iter.index; | |
| 834 | } | ||
| 835 | static Sequence::const_iterator seq_iter(const_iterator const &iter) { | ||
| 836 | return iter.path->_data->curves.begin() + iter.index; | ||
| 837 | } | ||
| 838 | |||
| 839 | // whether the closing segment is part of the path | ||
| 840 | 176922 | bool _includesClosingSegment() const { | |
| 841 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 176922 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
176922 | return _closed && !_closing_seg->isDegenerate(); |
| 842 | } | ||
| 843 | 414135 | void _unshare() { | |
| 844 | // Called before every mutation. | ||
| 845 | // Ensure we have our own copy of curve data and reset cached values | ||
| 846 |
2/2✓ Branch 1 taken 861 times.
✓ Branch 2 taken 413274 times.
|
414135 | if (!_data.unique()) { |
| 847 |
2/6✓ Branch 3 taken 861 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 861 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
861 | _data.reset(new PathData(*_data)); |
| 848 | 861 | _closing_seg = static_cast<ClosingSegment*>(&_data->curves.back()); | |
| 849 | } | ||
| 850 | 414135 | _data->fast_bounds = OptRect(); | |
| 851 | 414135 | } | |
| 852 | PathTime _factorTime(Coord t) const; | ||
| 853 | |||
| 854 | void stitch(Sequence::iterator first_replaced, Sequence::iterator last_replaced, Sequence &sequence); | ||
| 855 | void do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source); | ||
| 856 | |||
| 857 | // n.b. takes ownership of curve object | ||
| 858 | void do_append(Curve *curve); | ||
| 859 | |||
| 860 | std::shared_ptr<PathData> _data; | ||
| 861 | ClosingSegment *_closing_seg; | ||
| 862 | bool _closed; | ||
| 863 | bool _exception_on_stitch; | ||
| 864 | }; // end class Path | ||
| 865 | |||
| 866 | Piecewise<D2<SBasis> > paths_to_pw(PathVector const &paths); | ||
| 867 | |||
| 868 | inline Coord nearest_time(Point const &p, Path const &c) { | ||
| 869 | PathTime pt = c.nearestTime(p); | ||
| 870 | return pt.curve_index + pt.t; | ||
| 871 | } | ||
| 872 | |||
| 873 | bool are_near(Path const &a, Path const &b, Coord precision = EPSILON); | ||
| 874 | |||
| 875 | /** | ||
| 876 | * @brief Find the first point where two paths diverge away from one another. | ||
| 877 | * | ||
| 878 | * If the two paths have a common starting point, the algorithm follows them for as long as the | ||
| 879 | * images of the paths coincide and finds the first point where they stop coinciding. Note that | ||
| 880 | * only the images of paths in the plane are compared, and not their parametrizations, so this | ||
| 881 | * is not a functional (parametric) coincidence. If you want to test parametric coincidence, use | ||
| 882 | * bool are_near(Path const&, Path const&, Coord) instead. | ||
| 883 | * | ||
| 884 | * The function returns the point where the traces of the two paths finally diverge up to the | ||
| 885 | * specified precision. If the traces (images) of the paths are nearly identical until the end, | ||
| 886 | * the returned point is their (almost) common endpoint. If however the image of one of the paths | ||
| 887 | * is completely contained in the image of the other path, the returned point is the endpoint of | ||
| 888 | * the shorter path. | ||
| 889 | * | ||
| 890 | * If the paths have different starting points, then the returned intersection has the special | ||
| 891 | * time values of -1.0 on both paths and the returned intersection point is the midpoint of the | ||
| 892 | * line segment connecting the two starting points. | ||
| 893 | * | ||
| 894 | * @param first The first path to follow; corresponds to .first in the return value. | ||
| 895 | * @param second The second path to follow; corresponds to .second in the return value. | ||
| 896 | * @param precision How close the paths' images need to be in order to be considered as overlapping. | ||
| 897 | * @return A path intersection specifying the point and path times where the two paths part ways. | ||
| 898 | */ | ||
| 899 | PathIntersection parting_point(Path const &first, Path const &second, Coord precision = EPSILON); | ||
| 900 | |||
| 901 | std::ostream &operator<<(std::ostream &out, Path const &path); | ||
| 902 | |||
| 903 | } // end namespace Geom | ||
| 904 | |||
| 905 | |||
| 906 | #endif // LIB2GEOM_SEEN_PATH_H | ||
| 907 | |||
| 908 | /* | ||
| 909 | Local Variables: | ||
| 910 | mode:c++ | ||
| 911 | c-file-style:"stroustrup" | ||
| 912 | c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) | ||
| 913 | indent-tabs-mode:nil | ||
| 914 | fill-column:99 | ||
| 915 | End: | ||
| 916 | */ | ||
| 917 | // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : | ||
| 918 |