| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** @file | ||
| 2 | * @brief Path - a sequence of contiguous curves (implementation file) | ||
| 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 | #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> | ||
| 41 | #include <2geom/svg-path-writer.h> | ||
| 42 | #include <2geom/sweeper.h> | ||
| 43 | #include <algorithm> | ||
| 44 | #include <limits> | ||
| 45 | #include <optional> | ||
| 46 | |||
| 47 | using std::swap; | ||
| 48 | using namespace Geom::PathInternal; | ||
| 49 | |||
| 50 | namespace Geom { | ||
| 51 | |||
| 52 | // this represents an empty interval | ||
| 53 | 116 | PathInterval::PathInterval() | |
| 54 | 116 | : _from(0, 0.0) | |
| 55 | 116 | , _to(0, 0.0) | |
| 56 | 116 | , _path_size(1) | |
| 57 | 116 | , _cross_start(false) | |
| 58 | 116 | , _reverse(false) | |
| 59 | 116 | {} | |
| 60 | |||
| 61 | 50 | PathInterval::PathInterval(PathTime const &from, PathTime const &to, bool cross_start, size_type path_size) | |
| 62 | 50 | : _from(from) | |
| 63 | 50 | , _to(to) | |
| 64 | 50 | , _path_size(path_size) | |
| 65 | 50 | , _cross_start(cross_start) | |
| 66 | 50 | , _reverse((to < from) ^ cross_start) | |
| 67 | { | ||
| 68 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 31 times.
|
50 | if (_reverse) { |
| 69 | 19 | _to.normalizeForward(_path_size); | |
| 70 |
6/6✓ Branch 0 taken 9 times.
✓ Branch 1 taken 10 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 18 times.
|
19 | 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 | 1 | _cross_start = false; | |
| 74 | } | ||
| 75 |
1/2✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
|
19 | if (_from != _to) { |
| 76 | 19 | _from.normalizeBackward(_path_size); | |
| 77 |
6/6✓ Branch 0 taken 9 times.
✓ Branch 1 taken 10 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 18 times.
|
19 | if (cross_start && _from > from) { |
| 78 | // Normalization backwards made us logically cross | ||
| 79 | // the start – we shouldn't cross the start again. | ||
| 80 | 1 | _cross_start = false; | |
| 81 | } | ||
| 82 | } | ||
| 83 | } else { | ||
| 84 | 31 | _from.normalizeForward(_path_size); | |
| 85 |
6/6✓ Branch 0 taken 9 times.
✓ Branch 1 taken 22 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 30 times.
|
31 | if (cross_start && _from < from) { |
| 86 | 1 | _cross_start = false; | |
| 87 | } | ||
| 88 |
1/2✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
|
31 | if (_from != _to) { |
| 89 | 31 | _to.normalizeBackward(_path_size); | |
| 90 |
6/6✓ Branch 0 taken 9 times.
✓ Branch 1 taken 22 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 30 times.
|
31 | if (cross_start && _to > to) { |
| 91 | 1 | _cross_start = false; | |
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
|
50 | if (_from == _to) { |
| 97 | ✗ | _reverse = false; | |
| 98 | ✗ | _cross_start = false; | |
| 99 | } | ||
| 100 | 50 | } | |
| 101 | |||
| 102 | 184 | bool PathInterval::contains(PathTime const &pos) const { | |
| 103 |
2/2✓ Branch 0 taken 90 times.
✓ Branch 1 taken 94 times.
|
184 | if (_cross_start) { |
| 104 |
2/2✓ Branch 0 taken 45 times.
✓ Branch 1 taken 45 times.
|
90 | if (_reverse) { |
| 105 |
4/4✓ Branch 1 taken 30 times.
✓ Branch 2 taken 15 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 12 times.
|
45 | return pos >= _to || _from >= pos; |
| 106 | } else { | ||
| 107 |
4/4✓ Branch 1 taken 30 times.
✓ Branch 2 taken 15 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 12 times.
|
45 | return pos >= _from || _to >= pos; |
| 108 | } | ||
| 109 | } else { | ||
| 110 |
2/2✓ Branch 0 taken 47 times.
✓ Branch 1 taken 47 times.
|
94 | if (_reverse) { |
| 111 |
4/4✓ Branch 1 taken 33 times.
✓ Branch 2 taken 14 times.
✓ Branch 4 taken 22 times.
✓ Branch 5 taken 11 times.
|
47 | return _to <= pos && pos <= _from; |
| 112 | } else { | ||
| 113 |
4/4✓ Branch 1 taken 33 times.
✓ Branch 2 taken 14 times.
✓ Branch 4 taken 22 times.
✓ Branch 5 taken 11 times.
|
47 | return _from <= pos && pos <= _to; |
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | 20 | PathInterval::size_type PathInterval::curveCount() const | |
| 119 | { | ||
| 120 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
|
20 | if (isDegenerate()) return 0; |
| 121 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
|
20 | if (_cross_start) { |
| 122 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
|
10 | if (_reverse) { |
| 123 | 5 | return _path_size - _to.curve_index + _from.curve_index + 1; | |
| 124 | } else { | ||
| 125 | 5 | return _path_size - _from.curve_index + _to.curve_index + 1; | |
| 126 | } | ||
| 127 | } else { | ||
| 128 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
|
10 | if (_reverse) { |
| 129 | 5 | return _from.curve_index - _to.curve_index + 1; | |
| 130 | } else { | ||
| 131 | 5 | return _to.curve_index - _from.curve_index + 1; | |
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | 64 | PathTime PathInterval::inside(Coord min_dist) const | |
| 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 | 64 | PathTime result(0, 0.0); | |
| 141 | |||
| 142 |
4/4✓ Branch 0 taken 46 times.
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 28 times.
|
64 | if (!_cross_start && _from.curve_index == _to.curve_index) { |
| 143 | 18 | PathTime result(_from.curve_index, lerp(0.5, _from.t, _to.t)); | |
| 144 | 18 | 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 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 37 times.
|
46 | if (_reverse) { |
| 150 | 9 | size_type dcurve = (_path_size + _from.curve_index - _to.curve_index) % _path_size; | |
| 151 | 9 | bool from_close = _from.t < min_dist; | |
| 152 | 9 | bool to_close = _to.t > 1 - min_dist; | |
| 153 | |||
| 154 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
|
9 | if (dcurve == 0) { |
| 155 | 1 | dcurve = _path_size; | |
| 156 | } | ||
| 157 | |||
| 158 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
|
9 | if (dcurve == 1) { |
| 159 |
4/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
|
3 | if (from_close || to_close) { |
| 160 | 2 | result.curve_index = _from.curve_index; | |
| 161 | 2 | Coord tmid = _from.t - ((1 - _to.t) + _from.t) * 0.5; | |
| 162 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (tmid < 0) { |
| 163 | ✗ | result.curve_index = (_path_size + result.curve_index - 1) % _path_size; | |
| 164 | ✗ | tmid += 1; | |
| 165 | } | ||
| 166 | 2 | result.t = tmid; | |
| 167 | 2 | return result; | |
| 168 | } | ||
| 169 | |||
| 170 | 1 | result.curve_index = _from.curve_index; | |
| 171 | 1 | return result; | |
| 172 | } | ||
| 173 | |||
| 174 | 6 | result.curve_index = (_to.curve_index + 1) % _path_size; | |
| 175 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
|
6 | if (to_close) { |
| 176 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (dcurve == 2) { |
| 177 | 1 | result.t = 0.5; | |
| 178 | } else { | ||
| 179 | 1 | result.curve_index = (result.curve_index + 1) % _path_size; | |
| 180 | } | ||
| 181 | } | ||
| 182 | 6 | return result; | |
| 183 | } else { | ||
| 184 | 37 | size_type dcurve = (_path_size + _to.curve_index - _from.curve_index) % _path_size; | |
| 185 | 37 | bool from_close = _from.t > 1 - min_dist; | |
| 186 | 37 | bool to_close = _to.t < min_dist; | |
| 187 | |||
| 188 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 34 times.
|
37 | if (dcurve == 0) { |
| 189 | 3 | dcurve = _path_size; | |
| 190 | } | ||
| 191 | |||
| 192 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 19 times.
|
37 | if (dcurve == 1) { |
| 193 |
3/4✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
|
18 | if (from_close || to_close) { |
| 194 | 2 | result.curve_index = _from.curve_index; | |
| 195 | 2 | Coord tmid = ((1 - _from.t) + _to.t) * 0.5 + _from.t; | |
| 196 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (tmid >= 1) { |
| 197 | 2 | result.curve_index = (result.curve_index + 1) % _path_size; | |
| 198 | 2 | tmid -= 1; | |
| 199 | } | ||
| 200 | 2 | result.t = tmid; | |
| 201 | 2 | return result; | |
| 202 | } | ||
| 203 | |||
| 204 | 16 | result.curve_index = _to.curve_index; | |
| 205 | 16 | return result; | |
| 206 | } | ||
| 207 | |||
| 208 | 19 | result.curve_index = (_from.curve_index + 1) % _path_size; | |
| 209 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 17 times.
|
19 | if (from_close) { |
| 210 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (dcurve == 2) { |
| 211 | 1 | result.t = 0.5; | |
| 212 | } else { | ||
| 213 | 1 | result.curve_index = (result.curve_index + 1) % _path_size; | |
| 214 | } | ||
| 215 | } | ||
| 216 | 19 | return result; | |
| 217 | } | ||
| 218 | |||
| 219 | result.curve_index = _reverse ? _from.curve_index : _to.curve_index; | ||
| 220 | return result; | ||
| 221 | } | ||
| 222 | |||
| 223 | 96 | PathInterval PathInterval::from_direction(PathTime const &from, PathTime const &to, bool reversed, size_type path_size) | |
| 224 | { | ||
| 225 | 96 | PathInterval result; | |
| 226 | 96 | result._from = from; | |
| 227 | 96 | result._to = to; | |
| 228 | 96 | result._path_size = path_size; | |
| 229 | |||
| 230 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 66 times.
|
96 | if (reversed) { |
| 231 | 30 | result._to.normalizeForward(path_size); | |
| 232 |
1/2✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
|
30 | if (result._from != result._to) { |
| 233 | 30 | result._from.normalizeBackward(path_size); | |
| 234 | } | ||
| 235 | } else { | ||
| 236 | 66 | result._from.normalizeForward(path_size); | |
| 237 |
1/2✓ Branch 1 taken 66 times.
✗ Branch 2 not taken.
|
66 | if (result._from != result._to) { |
| 238 | 66 | result._to.normalizeBackward(path_size); | |
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 96 times.
|
96 | if (result._from == result._to) { |
| 243 | ✗ | result._reverse = false; | |
| 244 | ✗ | result._cross_start = false; | |
| 245 | } else { | ||
| 246 | 96 | result._reverse = reversed; | |
| 247 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 66 times.
|
96 | if (reversed) { |
| 248 | 30 | result._cross_start = from < to; | |
| 249 | } else { | ||
| 250 | 66 | result._cross_start = to < from; | |
| 251 | } | ||
| 252 | } | ||
| 253 | 96 | return result; | |
| 254 | } | ||
| 255 | |||
| 256 | |||
| 257 | 1 | Path::Path(Rect const &r) | |
| 258 |
2/6✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
1 | : _data(new PathData()) |
| 259 |
2/6✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
|
1 | , _closing_seg(new ClosingSegment(r.corner(3), r.corner(0))) |
| 260 | 1 | , _closed(true) | |
| 261 | 1 | , _exception_on_stitch(true) | |
| 262 | { | ||
| 263 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
|
4 | for (unsigned i = 0; i < 3; ++i) { |
| 264 |
3/8✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
3 | _data->curves.push_back(new LineSegment(r.corner(i), r.corner(i+1))); |
| 265 | } | ||
| 266 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | _data->curves.push_back(_closing_seg); |
| 267 | 1 | } | |
| 268 | |||
| 269 | ✗ | Path::Path(ConvexHull const &ch) | |
| 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 | |||
| 280 | ✗ | _closing_seg->setInitial(ch.back()); | |
| 281 | ✗ | _closing_seg->setFinal(ch.front()); | |
| 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 | |||
| 294 | ✗ | Path::Path(Circle const &c) | |
| 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 | |||
| 308 | ✗ | Path::Path(Ellipse const &e) | |
| 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 | |||
| 322 | 416 | void Path::close(bool c) | |
| 323 | { | ||
| 324 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 370 times.
|
416 | if (c == _closed) return; |
| 325 |
5/6✓ Branch 0 taken 370 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 369 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 369 times.
✓ Branch 7 taken 1 times.
|
370 | 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 |
2/4✓ Branch 3 taken 369 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 369 times.
✗ Branch 7 not taken.
|
369 | Sequence::iterator last = _data->curves.end() - 2; |
| 329 |
13/20✓ Branch 2 taken 369 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 212 times.
✓ Branch 5 taken 157 times.
✓ Branch 8 taken 212 times.
✗ Branch 9 not taken.
✓ Branch 13 taken 212 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 54 times.
✓ Branch 17 taken 158 times.
✓ Branch 18 taken 212 times.
✓ Branch 19 taken 157 times.
✓ Branch 21 taken 212 times.
✓ Branch 22 taken 157 times.
✓ Branch 24 taken 54 times.
✓ Branch 25 taken 315 times.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
|
369 | if (last->isLineSegment() && last->finalPoint() == initialPoint()) { |
| 330 |
2/4✓ Branch 3 taken 54 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 54 times.
✗ Branch 7 not taken.
|
54 | _closing_seg->setInitial(last->initialPoint()); |
| 331 |
1/2✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
|
54 | _data->curves.erase(last); |
| 332 | } | ||
| 333 | } | ||
| 334 | 370 | _closed = c; | |
| 335 | } | ||
| 336 | |||
| 337 | 1406 | void Path::clear() | |
| 338 | { | ||
| 339 | 1406 | _unshare(); | |
| 340 |
2/4✓ Branch 3 taken 1406 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1406 times.
✗ Branch 7 not taken.
|
1406 | _data->curves.pop_back().release(); |
| 341 | 1406 | _data->curves.clear(); | |
| 342 |
1/2✓ Branch 3 taken 1406 times.
✗ Branch 4 not taken.
|
1406 | _closing_seg->setInitial(Point(0, 0)); |
| 343 |
1/2✓ Branch 3 taken 1406 times.
✗ Branch 4 not taken.
|
1406 | _closing_seg->setFinal(Point(0, 0)); |
| 344 | 1406 | _data->curves.push_back(_closing_seg); | |
| 345 | 1406 | _closed = false; | |
| 346 | 1406 | } | |
| 347 | |||
| 348 | 285130 | OptRect Path::boundsFast() const | |
| 349 | { | ||
| 350 | 285130 | OptRect bounds; | |
| 351 |
2/4✓ Branch 1 taken 285130 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 285130 times.
|
285130 | if (empty()) { |
| 352 | ✗ | return bounds; | |
| 353 | } | ||
| 354 | // if the path is not empty, we look for cached bounds | ||
| 355 |
2/2✓ Branch 2 taken 285081 times.
✓ Branch 3 taken 49 times.
|
285130 | if (_data->fast_bounds) { |
| 356 | 285081 | return _data->fast_bounds; | |
| 357 | } | ||
| 358 | |||
| 359 |
3/6✓ Branch 3 taken 49 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 49 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 49 times.
✗ Branch 10 not taken.
|
49 | bounds = front().boundsFast(); |
| 360 |
1/2✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
|
49 | 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 |
3/6✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 49 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 49 times.
✗ Branch 9 not taken.
|
49 | if (iter != end()) { |
| 364 |
4/6✓ Branch 4 taken 246 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 246 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 197 times.
✓ Branch 11 taken 49 times.
|
246 | for (++iter; iter != end(); ++iter) { |
| 365 |
3/6✓ Branch 2 taken 197 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 197 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 197 times.
✗ Branch 9 not taken.
|
197 | bounds.unionWith(iter->boundsFast()); |
| 366 | } | ||
| 367 | } | ||
| 368 | 49 | _data->fast_bounds = bounds; | |
| 369 | 49 | return bounds; | |
| 370 | } | ||
| 371 | |||
| 372 | ✗ | OptRect Path::boundsExact() const | |
| 373 | { | ||
| 374 | ✗ | OptRect bounds; | |
| 375 | ✗ | if (empty()) | |
| 376 | ✗ | return bounds; | |
| 377 | ✗ | bounds = front().boundsExact(); | |
| 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 | |||
| 388 | 27 | Piecewise<D2<SBasis> > Path::toPwSb() const | |
| 389 | { | ||
| 390 | 27 | Piecewise<D2<SBasis> > ret; | |
| 391 |
1/2✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
|
27 | ret.push_cut(0); |
| 392 | 27 | unsigned i = 1; | |
| 393 | 27 | bool degenerate = true; | |
| 394 | // pw<d2<>> is always open. so if path is closed, add closing segment as well to pwd2. | ||
| 395 |
5/8✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 309 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 309 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 282 times.
✓ Branch 14 taken 27 times.
|
309 | for (const_iterator it = begin(); it != end_default(); ++it) { |
| 396 |
3/6✓ Branch 1 taken 282 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 282 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 282 times.
✗ Branch 7 not taken.
|
282 | if (!it->isDegenerate()) { |
| 397 |
3/6✓ Branch 2 taken 282 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 282 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 282 times.
✗ Branch 9 not taken.
|
282 | ret.push(it->toSBasis(), i++); |
| 398 | 282 | degenerate = false; | |
| 399 | } | ||
| 400 | } | ||
| 401 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | 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 | ||
| 404 | ✗ | ret = Piecewise<D2<SBasis> >(initialPoint()); | |
| 405 | } | ||
| 406 | 27 | return ret; | |
| 407 | ✗ | } | |
| 408 | |||
| 409 | template <typename iter> | ||
| 410 | ✗ | iter 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 | |||
| 417 | 72 | bool Path::operator==(Path const &other) const | |
| 418 | { | ||
| 419 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 72 times.
|
72 | if (this == &other) |
| 420 | ✗ | return true; | |
| 421 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 72 times.
|
72 | if (_closed != other._closed) |
| 422 | ✗ | return false; | |
| 423 | 72 | return _data->curves == other._data->curves; | |
| 424 | } | ||
| 425 | |||
| 426 | 13407 | void Path::start(Point const &p) { | |
| 427 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 13407 times.
|
13407 | if (_data->curves.size() > 1) { |
| 428 | ✗ | clear(); | |
| 429 | } | ||
| 430 | 13407 | _closing_seg->setInitial(p); | |
| 431 | 13407 | _closing_seg->setFinal(p); | |
| 432 | 13407 | } | |
| 433 | |||
| 434 | 60 | Interval Path::timeRange() const | |
| 435 | { | ||
| 436 |
2/4✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 60 times.
✗ Branch 6 not taken.
|
60 | Interval ret(0, size_default()); |
| 437 | 60 | return ret; | |
| 438 | } | ||
| 439 | |||
| 440 | 21 | Curve const &Path::curveAt(Coord t, Coord *rest) const | |
| 441 | { | ||
| 442 | 21 | PathTime pos = _factorTime(t); | |
| 443 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
|
21 | if (rest) { |
| 444 | ✗ | *rest = pos.t; | |
| 445 | } | ||
| 446 | 21 | return at(pos.curve_index); | |
| 447 | } | ||
| 448 | |||
| 449 | 148 | Point Path::pointAt(Coord t) const | |
| 450 | { | ||
| 451 |
2/4✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 148 times.
✗ Branch 6 not taken.
|
296 | return pointAt(_factorTime(t)); |
| 452 | } | ||
| 453 | |||
| 454 | ✗ | Coord Path::valueAt(Coord t, Dim2 d) const | |
| 455 | { | ||
| 456 | ✗ | return valueAt(_factorTime(t), d); | |
| 457 | } | ||
| 458 | |||
| 459 | ✗ | Curve const &Path::curveAt(PathTime const &pos) const | |
| 460 | { | ||
| 461 | ✗ | return at(pos.curve_index); | |
| 462 | } | ||
| 463 | 226 | Point Path::pointAt(PathTime const &pos) const | |
| 464 | { | ||
| 465 | 226 | return at(pos.curve_index).pointAt(pos.t); | |
| 466 | } | ||
| 467 | 1 | Coord Path::valueAt(PathTime const &pos, Dim2 d) const | |
| 468 | { | ||
| 469 | 1 | return at(pos.curve_index).valueAt(pos.t, d); | |
| 470 | } | ||
| 471 | |||
| 472 | 2 | std::vector<PathTime> Path::roots(Coord v, Dim2 d) const | |
| 473 | { | ||
| 474 | 2 | std::vector<PathTime> res; | |
| 475 |
3/4✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 2 times.
|
7 | for (unsigned i = 0; i < size(); i++) { |
| 476 |
2/4✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
|
5 | std::vector<Coord> temp = (*this)[i].roots(v, d); |
| 477 |
2/2✓ Branch 8 taken 3 times.
✓ Branch 9 taken 5 times.
|
8 | for (double j : temp) |
| 478 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | res.emplace_back(i, j); |
| 479 | 5 | } | |
| 480 | 2 | 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 | |||
| 488 | struct CurveIntersectionSweepSet | ||
| 489 | { | ||
| 490 | public: | ||
| 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 | 76 | CurveRecord(Curve const *pc, std::size_t idx, unsigned w) | |
| 499 | 152 | : curve(pc) | |
| 500 |
1/2✓ Branch 1 taken 76 times.
✗ Branch 2 not taken.
|
76 | , bounds(curve->boundsFast()) |
| 501 | 76 | , index(idx) | |
| 502 | 76 | , which(w) | |
| 503 | 76 | {} | |
| 504 | }; | ||
| 505 | |||
| 506 | typedef std::vector<CurveRecord>::const_iterator ItemIterator; | ||
| 507 | |||
| 508 | 7 | CurveIntersectionSweepSet(std::vector<PathIntersection> &result, | |
| 509 | Path const &a, Path const &b, Coord precision) | ||
| 510 | 14 | : _result(result) | |
| 511 | 7 | , _precision(precision) | |
| 512 |
2/8✓ Branch 1 taken 14 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
21 | , _sweep_dir(X) |
| 513 | { | ||
| 514 |
2/4✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
|
7 | std::size_t asz = a.size(), bsz = b.size(); |
| 515 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | _records.reserve(asz + bsz); |
| 516 | |||
| 517 |
2/2✓ Branch 1 taken 37 times.
✓ Branch 2 taken 7 times.
|
44 | for (std::size_t i = 0; i < asz; ++i) { |
| 518 |
2/4✓ Branch 3 taken 37 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 37 times.
✗ Branch 7 not taken.
|
37 | _records.emplace_back(&a[i], i, 0); |
| 519 | } | ||
| 520 |
2/2✓ Branch 1 taken 39 times.
✓ Branch 2 taken 7 times.
|
46 | for (std::size_t i = 0; i < bsz; ++i) { |
| 521 |
2/4✓ Branch 3 taken 39 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 39 times.
✗ Branch 7 not taken.
|
39 | _records.emplace_back(&b[i], i, 1); |
| 522 | } | ||
| 523 | |||
| 524 |
3/6✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 7 times.
✗ Branch 11 not taken.
|
7 | OptRect abb = a.boundsFast() | b.boundsFast(); |
| 525 |
5/6✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 7 taken 5 times.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 5 times.
✓ Branch 10 taken 2 times.
|
7 | if (abb && abb->height() > abb->width()) { |
| 526 | 5 | _sweep_dir = Y; | |
| 527 | } | ||
| 528 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
7 | } |
| 529 | |||
| 530 | 35 | std::vector<CurveRecord> const &items() { return _records; } | |
| 531 | 76 | Interval itemBounds(ItemIterator ii) { | |
| 532 | 76 | return ii->bounds[_sweep_dir]; | |
| 533 | } | ||
| 534 | |||
| 535 | 76 | void addActiveItem(ItemIterator ii) { | |
| 536 | 76 | unsigned w = ii->which; | |
| 537 | 76 | unsigned ow = (w+1) % 2; | |
| 538 | |||
| 539 | 76 | _active[w].push_back(const_cast<CurveRecord&>(*ii)); | |
| 540 | |||
| 541 |
4/6✓ Branch 2 taken 76 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 76 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 106 times.
✓ Branch 9 taken 76 times.
|
258 | for (auto & i : _active[ow]) { |
| 542 |
3/4✓ Branch 2 taken 106 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 68 times.
✓ Branch 5 taken 38 times.
|
106 | if (!ii->bounds.intersects(i.bounds)) continue; |
| 543 |
1/2✓ Branch 3 taken 38 times.
✗ Branch 4 not taken.
|
38 | std::vector<CurveIntersection> cx = ii->curve->intersect(*i.curve, _precision); |
| 544 |
2/2✓ Branch 7 taken 38 times.
✓ Branch 8 taken 38 times.
|
76 | for (auto & k : cx) { |
| 545 | 38 | PathTime tw(ii->index, k.first), tow(i.index, k.second); | |
| 546 |
5/6✓ Branch 1 taken 19 times.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 19 times.
✓ Branch 4 taken 19 times.
✓ Branch 6 taken 38 times.
✗ Branch 7 not taken.
|
76 | _result.emplace_back( |
| 547 | w == 0 ? tw : tow, | ||
| 548 | w == 0 ? tow : tw, | ||
| 549 | 76 | k.point()); | |
| 550 | } | ||
| 551 | 38 | } | |
| 552 | 76 | } | |
| 553 | 76 | void removeActiveItem(ItemIterator ii) { | |
| 554 | 76 | ActiveCurveList &acl = _active[ii->which]; | |
| 555 |
2/4✓ Branch 4 taken 76 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 76 times.
✗ Branch 8 not taken.
|
76 | acl.erase(acl.iterator_to(*ii)); |
| 556 | 76 | } | |
| 557 | |||
| 558 | private: | ||
| 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]; | ||
| 571 | Coord _precision; | ||
| 572 | Dim2 _sweep_dir; | ||
| 573 | }; | ||
| 574 | |||
| 575 | 7 | std::vector<PathIntersection> Path::intersect(Path const &other, Coord precision) const | |
| 576 | { | ||
| 577 | 7 | std::vector<PathIntersection> result; | |
| 578 | |||
| 579 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | CurveIntersectionSweepSet cisset(result, *this, other, precision); |
| 580 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | Sweeper<CurveIntersectionSweepSet> sweeper(cisset); |
| 581 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | sweeper.process(); |
| 582 | |||
| 583 | // preprocessing to remove duplicate intersections at endpoints | ||
| 584 |
2/4✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
|
7 | std::size_t asz = size(), bsz = other.size(); |
| 585 |
2/2✓ Branch 7 taken 38 times.
✓ Branch 8 taken 7 times.
|
45 | for (auto & i : result) { |
| 586 | 38 | i.first.normalizeForward(asz); | |
| 587 | 38 | i.second.normalizeForward(bsz); | |
| 588 | } | ||
| 589 |
1/2✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
|
7 | std::sort(result.begin(), result.end()); |
| 590 |
2/4✓ Branch 9 taken 7 times.
✗ Branch 10 not taken.
✓ Branch 13 taken 7 times.
✗ Branch 14 not taken.
|
7 | result.erase(std::unique(result.begin(), result.end()), result.end()); |
| 591 | |||
| 592 | 14 | return result; | |
| 593 | 7 | } | |
| 594 | |||
| 595 | 155304 | int Path::winding(Point const &p) const { | |
| 596 | 155304 | 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 |
5/8✓ Branch 2 taken 155304 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 1063527 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 1063527 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 908223 times.
✓ Branch 14 taken 155304 times.
|
1063527 | for (const_iterator i = begin(); i != end_closed(); ++i) { |
| 602 |
2/4✓ Branch 2 taken 908223 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 908223 times.
✗ Branch 6 not taken.
|
908223 | Rect bounds = i->boundsFast(); |
| 603 | |||
| 604 |
2/2✓ Branch 1 taken 195015 times.
✓ Branch 2 taken 713208 times.
|
908223 | if (bounds.height() == 0) continue; |
| 605 |
7/8✓ Branch 2 taken 427278 times.
✓ Branch 3 taken 285930 times.
✓ Branch 7 taken 427278 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 232525 times.
✓ Branch 10 taken 194753 times.
✓ Branch 11 taken 518455 times.
✓ Branch 12 taken 194753 times.
|
713208 | if (p[X] > bounds.right() || !bounds[Y].lowerContains(p[Y])) { |
| 606 | // Ray doesn't intersect bbox, so we ignore this segment | ||
| 607 | 518455 | continue; | |
| 608 | } | ||
| 609 | |||
| 610 |
2/2✓ Branch 2 taken 123555 times.
✓ Branch 3 taken 71198 times.
|
194753 | 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 |
2/4✓ Branch 2 taken 123555 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 123555 times.
✗ Branch 6 not taken.
|
123555 | Point ip = i->initialPoint(); |
| 615 |
2/4✓ Branch 2 taken 123555 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 123555 times.
✗ Branch 6 not taken.
|
123555 | Point fp = i->finalPoint(); |
| 616 |
1/2✓ Branch 2 taken 123555 times.
✗ Branch 3 not taken.
|
123555 | Rect eqbox(ip, fp); |
| 617 | |||
| 618 |
3/4✓ Branch 3 taken 123555 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 123554 times.
✓ Branch 6 taken 1 times.
|
123555 | if (eqbox[Y].lowerContains(p[Y])) { |
| 619 | /* The ray intersects the equivalent linear segment. | ||
| 620 | * Determine winding contribution based on its derivative. */ | ||
| 621 |
2/2✓ Branch 2 taken 108881 times.
✓ Branch 3 taken 14673 times.
|
123554 | if (ip[Y] < fp[Y]) { |
| 622 | 108881 | wind += 1; | |
| 623 |
1/2✓ Branch 2 taken 14673 times.
✗ Branch 3 not taken.
|
14673 | } else if (ip[Y] > fp[Y]) { |
| 624 | 14673 | 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 |
2/4✓ Branch 1 taken 71198 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 71198 times.
✗ Branch 5 not taken.
|
71198 | wind += i->winding(p); |
| 633 | } | ||
| 634 | } | ||
| 635 | 155304 | return wind; | |
| 636 | } | ||
| 637 | |||
| 638 | ✗ | std::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 | |||
| 723 | ✗ | std::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 | |||
| 733 | 9 | PathTime Path::nearestTime(Point const &p, Coord *dist) const | |
| 734 | { | ||
| 735 | 9 | Coord mindist = std::numeric_limits<Coord>::max(); | |
| 736 | 9 | PathTime ret; | |
| 737 | |||
| 738 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
|
9 | 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 |
3/4✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 9 times.
|
35 | for (size_type i = 0; i < size_default(); ++i) { |
| 749 |
1/2✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
|
26 | Curve const &c = at(i); |
| 750 |
4/6✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 26 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 12 times.
✓ Branch 9 taken 14 times.
|
26 | if (distance(p, c.boundsFast()) >= mindist) continue; |
| 751 | |||
| 752 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | Coord t = c.nearestTime(p); |
| 753 |
2/4✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 14 times.
✗ Branch 6 not taken.
|
14 | Coord d = distance(c.pointAt(t), p); |
| 754 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
|
14 | if (d < mindist) { |
| 755 | 12 | mindist = d; | |
| 756 | 12 | ret.curve_index = i; | |
| 757 | 12 | ret.t = t; | |
| 758 | } | ||
| 759 | } | ||
| 760 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (dist) { |
| 761 | ✗ | *dist = mindist; | |
| 762 | } | ||
| 763 | |||
| 764 | 9 | return ret; | |
| 765 | } | ||
| 766 | |||
| 767 | ✗ | std::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 | |||
| 777 | ✗ | void 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 | |||
| 815 | 82 | void Path::appendPortionTo(Path &target, PathInterval const &ival, | |
| 816 | std::optional<Point> const &p_from, std::optional<Point> const &p_to) const | ||
| 817 | { | ||
| 818 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 82 times.
|
82 | assert(ival.pathSize() == size_closed()); |
| 819 | |||
| 820 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 82 times.
|
82 | if (ival.isDegenerate()) { |
| 821 | ✗ | Point stitch_to = p_from ? *p_from : pointAt(ival.from()); | |
| 822 | ✗ | target.stitchTo(stitch_to); | |
| 823 | ✗ | return; | |
| 824 | } | ||
| 825 | |||
| 826 | 82 | PathTime const &from = ival.from(), &to = ival.to(); | |
| 827 | |||
| 828 | 82 | bool reverse = ival.reverse(); | |
| 829 |
2/2✓ Branch 0 taken 39 times.
✓ Branch 1 taken 43 times.
|
82 | int di = reverse ? -1 : 1; |
| 830 | 82 | size_type s = size_closed(); | |
| 831 | |||
| 832 |
6/6✓ Branch 1 taken 66 times.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 29 times.
✓ Branch 4 taken 37 times.
✓ Branch 5 taken 29 times.
✓ Branch 6 taken 53 times.
|
82 | if (!ival.crossesStart() && from.curve_index == to.curve_index) { |
| 833 | 29 | Curve *c = (*this)[from.curve_index].portion(from.t, to.t); | |
| 834 |
2/2✓ Branch 1 taken 15 times.
✓ Branch 2 taken 14 times.
|
29 | if (p_from) { |
| 835 | 15 | c->setInitial(*p_from); | |
| 836 | } | ||
| 837 |
2/2✓ Branch 1 taken 15 times.
✓ Branch 2 taken 14 times.
|
29 | if (p_to) { |
| 838 | 15 | c->setFinal(*p_to); | |
| 839 | } | ||
| 840 | 29 | target.append(c); | |
| 841 | } else { | ||
| 842 |
2/2✓ Branch 1 taken 33 times.
✓ Branch 2 taken 20 times.
|
53 | Curve *c_first = (*this)[from.curve_index].portion(from.t, reverse ? 0 : 1); |
| 843 |
2/2✓ Branch 1 taken 37 times.
✓ Branch 2 taken 16 times.
|
53 | if (p_from) { |
| 844 | 37 | c_first->setInitial(*p_from); | |
| 845 | } | ||
| 846 | 53 | target.append(c_first); | |
| 847 | |||
| 848 |
2/2✓ Branch 0 taken 37 times.
✓ Branch 1 taken 53 times.
|
90 | for (size_type i = (from.curve_index + s + di) % s; i != to.curve_index; |
| 849 | 37 | i = (i + s + di) % s) | |
| 850 | { | ||
| 851 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 5 times.
|
37 | if (reverse) { |
| 852 | 32 | target.append((*this)[i].reverse()); | |
| 853 | } else { | ||
| 854 | 5 | target.append((*this)[i].duplicate()); | |
| 855 | } | ||
| 856 | } | ||
| 857 | |||
| 858 |
2/2✓ Branch 1 taken 33 times.
✓ Branch 2 taken 20 times.
|
53 | Curve *c_last = (*this)[to.curve_index].portion(reverse ? 1 : 0, to.t); |
| 859 |
2/2✓ Branch 1 taken 37 times.
✓ Branch 2 taken 16 times.
|
53 | if (p_to) { |
| 860 | 37 | c_last->setFinal(*p_to); | |
| 861 | } | ||
| 862 | 53 | target.append(c_last); | |
| 863 | } | ||
| 864 | } | ||
| 865 | |||
| 866 | 164750 | Path Path::reversed() const | |
| 867 | { | ||
| 868 | typedef std::reverse_iterator<Sequence::const_iterator> RIter; | ||
| 869 | |||
| 870 |
2/4✓ Branch 2 taken 164750 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 164750 times.
✗ Branch 6 not taken.
|
164750 | Path ret(finalPoint()); |
| 871 |
2/4✓ Branch 1 taken 164750 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 164750 times.
|
164750 | if (empty()) return ret; |
| 872 | |||
| 873 |
1/2✓ Branch 3 taken 164750 times.
✗ Branch 4 not taken.
|
164750 | ret._data->curves.pop_back(); // this also deletes the closing segment from ret |
| 874 | |||
| 875 |
7/12✓ Branch 4 taken 164750 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 22 times.
✓ Branch 7 taken 164728 times.
✓ Branch 10 taken 22 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 164728 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 164728 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 164750 times.
✗ Branch 21 not taken.
|
164750 | RIter iter(_includesClosingSegment() ? _data->curves.end() : _data->curves.end() - 1); |
| 876 |
2/4✓ Branch 5 taken 164750 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 164750 times.
✗ Branch 9 not taken.
|
164750 | RIter rend(_data->curves.begin()); |
| 877 | |||
| 878 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 164724 times.
|
164750 | if (_closed) { |
| 879 | // when the path is closed, there are two cases: | ||
| 880 |
4/6✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 21 times.
✓ Branch 7 taken 5 times.
|
26 | if (front().isLineSegment()) { |
| 881 | // 1. initial segment is linear: it becomes the new closing segment. | ||
| 882 |
3/6✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 21 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 21 times.
✗ Branch 12 not taken.
|
21 | rend = RIter(_data->curves.begin() + 1); |
| 883 |
6/14✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 21 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 21 times.
✗ Branch 13 not taken.
✓ Branch 15 taken 21 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 21 times.
✗ Branch 19 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
|
21 | ret._closing_seg = new ClosingSegment(front().finalPoint(), front().initialPoint()); |
| 884 | } else { | ||
| 885 | // 2. initial segment is not linear: the closing segment becomes degenerate. | ||
| 886 | // However, skip it if it's already degenerate. | ||
| 887 |
1/2✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | Point fp = finalPoint(); |
| 888 |
2/6✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
|
5 | 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 |
1/2✓ Branch 1 taken 164724 times.
✗ Branch 2 not taken.
|
164724 | ret._closing_seg = static_cast<ClosingSegment *>(_closing_seg->reverse()); |
| 893 | } | ||
| 894 | |||
| 895 |
4/6✓ Branch 1 taken 329559 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 494309 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 329559 times.
✓ Branch 7 taken 164750 times.
|
823868 | for (; iter != rend; ++iter) { |
| 896 |
3/6✓ Branch 2 taken 329559 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 329559 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 329559 times.
✗ Branch 9 not taken.
|
329559 | ret._data->curves.push_back(iter->reverse()); |
| 897 | } | ||
| 898 |
1/2✓ Branch 2 taken 164750 times.
✗ Branch 3 not taken.
|
164750 | ret._data->curves.push_back(ret._closing_seg); |
| 899 | 164750 | ret._closed = _closed; | |
| 900 | 164750 | return ret; | |
| 901 | ✗ | } | |
| 902 | |||
| 903 | |||
| 904 | ✗ | void Path::insert(iterator pos, Curve const &curve) | |
| 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 | |||
| 913 | 1 | void Path::erase(iterator pos) | |
| 914 | { | ||
| 915 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | _unshare(); |
| 916 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | Sequence::iterator seq_pos(seq_iter(pos)); |
| 917 | |||
| 918 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | Sequence stitched; |
| 919 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | do_update(seq_pos, seq_pos + 1, stitched); |
| 920 | 2 | } | |
| 921 | |||
| 922 | ✗ | void Path::erase(iterator first, iterator last) | |
| 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 | |||
| 932 | 299 | void Path::stitchTo(Point const &p) | |
| 933 | { | ||
| 934 |
10/14✓ Branch 1 taken 299 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 181 times.
✓ Branch 4 taken 118 times.
✓ Branch 7 taken 181 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 179 times.
✓ Branch 12 taken 181 times.
✓ Branch 13 taken 118 times.
✓ Branch 15 taken 2 times.
✓ Branch 16 taken 297 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
299 | if (!empty() && _closing_seg->initialPoint() != p) { |
| 935 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (_exception_on_stitch) { |
| 936 | ✗ | THROW_CONTINUITYERROR(); | |
| 937 | } | ||
| 938 | 2 | _unshare(); | |
| 939 |
3/8✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
|
2 | do_append(new StitchSegment(_closing_seg->initialPoint(), p)); |
| 940 | } | ||
| 941 | 299 | } | |
| 942 | |||
| 943 | ✗ | void Path::replace(iterator replaced, Curve const &curve) | |
| 944 | { | ||
| 945 | ✗ | replace(replaced, replaced + 1, curve); | |
| 946 | ✗ | } | |
| 947 | |||
| 948 | ✗ | void 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 | |||
| 959 | ✗ | void Path::replace(iterator replaced, Path const &path) | |
| 960 | { | ||
| 961 | ✗ | replace(replaced, path.begin(), path.end()); | |
| 962 | ✗ | } | |
| 963 | |||
| 964 | 10 | void Path::replace(iterator first, iterator last, Path const &path) | |
| 965 | { | ||
| 966 | 10 | replace(first, last, path.begin(), path.end()); | |
| 967 | 10 | } | |
| 968 | |||
| 969 | ✗ | void 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(); | |
| 974 | ✗ | _closing_seg->setInitial(_closing_seg->finalPoint()); | |
| 975 | ✗ | (_data->curves.end() - 1)->setFinal(_closing_seg->finalPoint()); | |
| 976 | } | ||
| 977 | } | ||
| 978 | |||
| 979 | 9 | Path Path::withoutDegenerateCurves() const | |
| 980 | { | ||
| 981 |
1/2✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
9 | Sequence cleaned; |
| 982 |
2/4✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
|
9 | cleaned.reserve(size()); |
| 983 | |||
| 984 |
5/8✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 31 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 31 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 22 times.
✓ Branch 14 taken 9 times.
|
31 | for (auto it = begin(); it != end_open(); ++it) { |
| 985 |
4/6✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 11 times.
|
22 | if (!it->isDegenerate()) { |
| 986 |
3/6✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 11 times.
✗ Branch 8 not taken.
|
11 | cleaned.push_back(it->duplicate()); |
| 987 | } | ||
| 988 | } | ||
| 989 | |||
| 990 |
1/2✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
|
9 | Path result; |
| 991 | 9 | result._closed = _closed; | |
| 992 |
3/6✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 9 times.
✗ Branch 10 not taken.
|
9 | result.do_update(result._data->curves.begin(), result._data->curves.end(), cleaned); |
| 993 | 18 | return result; | |
| 994 | 9 | } | |
| 995 | |||
| 996 | // Replace curves between first and last with the contents of source. | ||
| 997 | 22 | void Path::do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source) | |
| 998 | { | ||
| 999 | // TODO: handle cases where first > last in closed paths? | ||
| 1000 |
2/4✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 22 times.
✗ Branch 7 not taken.
|
22 | 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 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 19 times.
|
22 | if (source.empty()) { |
| 1005 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | if (first == last) return; // nothing to do |
| 1006 | |||
| 1007 | // only removing some segments | ||
| 1008 |
18/30✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 1 times.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 1 times.
✗ Branch 21 not taken.
✓ Branch 23 taken 1 times.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✓ Branch 26 taken 1 times.
✓ Branch 27 taken 1 times.
✗ Branch 28 not taken.
✓ Branch 29 taken 1 times.
✓ Branch 30 taken 2 times.
✓ Branch 32 taken 2 times.
✓ Branch 33 taken 1 times.
✗ Branch 35 not taken.
✓ Branch 36 taken 3 times.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
|
3 | 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()) { | |
| 1012 | ✗ | if (_exception_on_stitch) { | |
| 1013 | ✗ | THROW_CONTINUITYERROR(); | |
| 1014 | } | ||
| 1015 | ✗ | source.push_back(new StitchSegment(first->initialPoint(), (last - 1)->finalPoint())); | |
| 1016 | } | ||
| 1017 | } else { | ||
| 1018 | // replacing | ||
| 1019 |
13/22✓ Branch 3 taken 19 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 19 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 11 times.
✓ Branch 9 taken 8 times.
✓ Branch 13 taken 11 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 11 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 8 times.
✓ Branch 19 taken 3 times.
✓ Branch 20 taken 11 times.
✓ Branch 21 taken 8 times.
✓ Branch 23 taken 19 times.
✗ Branch 24 not taken.
✓ Branch 26 taken 8 times.
✓ Branch 27 taken 11 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
|
19 | if (first == _data->curves.begin() && last == _data->curves.end()) { |
| 1020 | // special case: replacing everything should work the same in open and closed curves | ||
| 1021 | 8 | _data->curves.erase(_data->curves.begin(), _data->curves.end() - 1); | |
| 1022 |
3/6✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
|
8 | _closing_seg->setFinal(source.front().initialPoint()); |
| 1023 |
3/6✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
|
8 | _closing_seg->setInitial(source.back().finalPoint()); |
| 1024 | 8 | _data->curves.transfer(_data->curves.begin(), source.begin(), source.end(), source); | |
| 1025 | 8 | return; | |
| 1026 | } | ||
| 1027 | |||
| 1028 | // stitch in front | ||
| 1029 |
10/14✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 4 times.
✓ Branch 12 taken 6 times.
✓ Branch 13 taken 5 times.
✓ Branch 15 taken 9 times.
✓ Branch 16 taken 2 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
11 | if (!_closed && first == _data->curves.begin()) { |
| 1030 | // not necessary to stitch in front | ||
| 1031 |
4/8✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
✓ Branch 10 taken 9 times.
✗ Branch 11 not taken.
✓ Branch 15 taken 9 times.
✗ Branch 16 not taken.
|
9 | } else if (first->initialPoint() != source.front().initialPoint()) { |
| 1032 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (_exception_on_stitch) { |
| 1033 | ✗ | THROW_CONTINUITYERROR(); | |
| 1034 | } | ||
| 1035 |
6/14✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✓ Branch 11 taken 9 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 9 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 9 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 9 times.
✗ Branch 21 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
|
9 | source.insert(source.begin(), new StitchSegment(first->initialPoint(), source.front().initialPoint())); |
| 1036 | } | ||
| 1037 | |||
| 1038 | // stitch at the end | ||
| 1039 |
13/18✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 6 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 4 times.
✓ Branch 14 taken 2 times.
✓ Branch 15 taken 4 times.
✓ Branch 16 taken 5 times.
✓ Branch 17 taken 6 times.
✓ Branch 18 taken 5 times.
✓ Branch 20 taken 5 times.
✓ Branch 21 taken 6 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
|
11 | if ((!_closed && last == _data->curves.end() - 1) || last_beyond_closing_segment) { |
| 1040 | // repurpose the closing segment as the stitch segment | ||
| 1041 | // do nothing | ||
| 1042 |
5/10✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 5 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 5 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 5 times.
✗ Branch 15 not taken.
✓ Branch 20 taken 5 times.
✗ Branch 21 not taken.
|
5 | } else if (source.back().finalPoint() != (last - 1)->finalPoint()) { |
| 1043 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (_exception_on_stitch) { |
| 1044 | ✗ | THROW_CONTINUITYERROR(); | |
| 1045 | } | ||
| 1046 |
6/14✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 5 times.
✗ Branch 13 not taken.
✓ Branch 15 taken 5 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 5 times.
✗ Branch 19 not taken.
✓ Branch 21 taken 5 times.
✗ Branch 22 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
|
5 | 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 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 8 times.
|
14 | if (last_beyond_closing_segment) { |
| 1052 | 6 | --last; | |
| 1053 | } | ||
| 1054 | 14 | _data->curves.erase(first, last); | |
| 1055 | 14 | _data->curves.transfer(first, source.begin(), source.end(), source); | |
| 1056 | |||
| 1057 | // adjust closing segment | ||
| 1058 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 12 times.
|
14 | if (size_open() == 0) { |
| 1059 |
2/4✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
|
2 | _closing_seg->setFinal(_closing_seg->initialPoint()); |
| 1060 | } else { | ||
| 1061 |
3/6✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
|
12 | _closing_seg->setInitial(back_open().finalPoint()); |
| 1062 |
3/6✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
|
12 | _closing_seg->setFinal(front().initialPoint()); |
| 1063 | } | ||
| 1064 | |||
| 1065 | 14 | checkContinuity(); | |
| 1066 | } | ||
| 1067 | |||
| 1068 | 397309 | void Path::do_append(Curve *c) | |
| 1069 | { | ||
| 1070 |
2/2✓ Branch 2 taken 13494 times.
✓ Branch 3 taken 383815 times.
|
397309 | if (&_data->curves.front() == _closing_seg) { |
| 1071 |
2/4✓ Branch 2 taken 13494 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13494 times.
✗ Branch 6 not taken.
|
13494 | _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 |
3/6✓ Branch 2 taken 383815 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 383815 times.
✗ Branch 7 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 383815 times.
|
383815 | if (c->initialPoint() != _closing_seg->initialPoint()) { |
| 1076 | ✗ | THROW_CONTINUITYERROR(); | |
| 1077 | } | ||
| 1078 |
6/10✓ Branch 0 taken 2 times.
✓ Branch 1 taken 383813 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 383815 times.
|
767636 | if (_closed && c->isLineSegment() && |
| 1079 |
6/12✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 383813 times.
✓ Branch 10 taken 383813 times.
✓ Branch 11 taken 2 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
383821 | 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 | 397309 | _data->curves.insert(_data->curves.end() - 1, c); | |
| 1087 |
2/4✓ Branch 2 taken 397309 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 397309 times.
✗ Branch 6 not taken.
|
397309 | _closing_seg->setInitial(c->finalPoint()); |
| 1088 | } | ||
| 1089 | |||
| 1090 | 39 | void Path::checkContinuity() const | |
| 1091 | { | ||
| 1092 |
4/8✓ Branch 4 taken 39 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 39 times.
✗ Branch 8 not taken.
✓ Branch 14 taken 39 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 39 times.
✗ Branch 18 not taken.
|
39 | Sequence::const_iterator i = _data->curves.begin(), j = _data->curves.begin(); |
| 1093 | 39 | ++j; | |
| 1094 |
4/6✓ Branch 5 taken 176 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 176 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 137 times.
✓ Branch 12 taken 39 times.
|
176 | for (; j != _data->curves.end(); ++i, ++j) { |
| 1095 |
3/6✓ Branch 3 taken 137 times.
✗ Branch 4 not taken.
✓ Branch 8 taken 137 times.
✗ Branch 9 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 137 times.
|
137 | if (i->finalPoint() != j->initialPoint()) { |
| 1096 | ✗ | THROW_CONTINUITYERROR(); | |
| 1097 | } | ||
| 1098 | } | ||
| 1099 |
5/10✓ Branch 3 taken 39 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 39 times.
✗ Branch 7 not taken.
✓ Branch 11 taken 39 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 39 times.
✗ Branch 15 not taken.
✗ Branch 19 not taken.
✓ Branch 20 taken 39 times.
|
39 | if (_data->curves.front().initialPoint() != _data->curves.back().finalPoint()) { |
| 1100 | ✗ | THROW_CONTINUITYERROR(); | |
| 1101 | } | ||
| 1102 | 39 | } | |
| 1103 | |||
| 1104 | // breaks time value into integral and fractional part | ||
| 1105 | 169 | PathTime Path::_factorTime(Coord t) const | |
| 1106 | { | ||
| 1107 |
1/2✓ Branch 1 taken 169 times.
✗ Branch 2 not taken.
|
169 | size_type sz = size_default(); |
| 1108 |
2/4✓ Branch 0 taken 169 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 169 times.
|
169 | if (t < 0 || t > sz) { |
| 1109 | ✗ | THROW_RANGEERROR("parameter t out of bounds"); | |
| 1110 | } | ||
| 1111 | |||
| 1112 | 169 | PathTime ret; | |
| 1113 | 169 | Coord k; | |
| 1114 | 169 | ret.t = modf(t, &k); | |
| 1115 | 169 | ret.curve_index = k; | |
| 1116 |
2/2✓ Branch 0 taken 59 times.
✓ Branch 1 taken 110 times.
|
169 | if (ret.curve_index == sz) { |
| 1117 | 59 | --ret.curve_index; | |
| 1118 | 59 | ret.t = 1; | |
| 1119 | } | ||
| 1120 | 338 | return ret; | |
| 1121 | } | ||
| 1122 | |||
| 1123 | ✗ | Piecewise<D2<SBasis> > paths_to_pw(PathVector const &paths) | |
| 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 | |||
| 1132 | 4 | bool are_near(Path const &a, Path const &b, Coord precision) | |
| 1133 | { | ||
| 1134 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
|
4 | if (a.size() != b.size()) return false; |
| 1135 | |||
| 1136 |
2/2✓ Branch 1 taken 12 times.
✓ Branch 2 taken 2 times.
|
14 | for (unsigned i = 0; i < a.size(); ++i) { |
| 1137 |
2/2✓ Branch 3 taken 1 times.
✓ Branch 4 taken 11 times.
|
12 | if (!a[i].isNear(b[i], precision)) return false; |
| 1138 | } | ||
| 1139 | 2 | return true; | |
| 1140 | } | ||
| 1141 | |||
| 1142 | ✗ | std::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 : | ||
| 1162 |