| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * SVG Elliptical Arc Class | ||
| 3 | * | ||
| 4 | * Authors: | ||
| 5 | * Marco Cecchetti <mrcekets at gmail.com> | ||
| 6 | * Krzysztof KosiĆski <tweenk.pl@gmail.com> | ||
| 7 | * Copyright 2008-2009 Authors | ||
| 8 | * | ||
| 9 | * This library is free software; you can redistribute it and/or | ||
| 10 | * modify it either under the terms of the GNU Lesser General Public | ||
| 11 | * License version 2.1 as published by the Free Software Foundation | ||
| 12 | * (the "LGPL") or, at your option, under the terms of the Mozilla | ||
| 13 | * Public License Version 1.1 (the "MPL"). If you do not alter this | ||
| 14 | * notice, a recipient may use your version of this file under either | ||
| 15 | * the MPL or the LGPL. | ||
| 16 | * | ||
| 17 | * You should have received a copy of the LGPL along with this library | ||
| 18 | * in the file COPYING-LGPL-2.1; if not, write to the Free Software | ||
| 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 20 | * You should have received a copy of the MPL along with this library | ||
| 21 | * in the file COPYING-MPL-1.1 | ||
| 22 | * | ||
| 23 | * The contents of this file are subject to the Mozilla Public License | ||
| 24 | * Version 1.1 (the "License"); you may not use this file except in | ||
| 25 | * compliance with the License. You may obtain a copy of the License at | ||
| 26 | * http://www.mozilla.org/MPL/ | ||
| 27 | * | ||
| 28 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY | ||
| 29 | * OF ANY KIND, either express or implied. See the LGPL or the MPL for | ||
| 30 | * the specific language governing rights and limitations. | ||
| 31 | */ | ||
| 32 | |||
| 33 | #include <cfloat> | ||
| 34 | #include <limits> | ||
| 35 | #include <memory> | ||
| 36 | |||
| 37 | #include <2geom/bezier-curve.h> | ||
| 38 | #include <2geom/ellipse.h> | ||
| 39 | #include <2geom/elliptical-arc.h> | ||
| 40 | #include <2geom/path-sink.h> | ||
| 41 | #include <2geom/sbasis-geometric.h> | ||
| 42 | #include <2geom/transforms.h> | ||
| 43 | #include <2geom/utils.h> | ||
| 44 | |||
| 45 | #include <2geom/numeric/vector.h> | ||
| 46 | #include <2geom/numeric/fitting-tool.h> | ||
| 47 | #include <2geom/numeric/fitting-model.h> | ||
| 48 | |||
| 49 | namespace Geom | ||
| 50 | { | ||
| 51 | |||
| 52 | /** | ||
| 53 | * @class EllipticalArc | ||
| 54 | * @brief Elliptical arc curve | ||
| 55 | * | ||
| 56 | * Elliptical arc is a curve taking the shape of a section of an ellipse. | ||
| 57 | * | ||
| 58 | * The arc function has two forms: the regular one, mapping the unit interval to points | ||
| 59 | * on 2D plane (the linear domain), and a second form that maps some interval | ||
| 60 | * \f$A \subseteq [0,2\pi)\f$ to the same points (the angular domain). The interval \f$A\f$ | ||
| 61 | * determines which part of the ellipse forms the arc. The arc is said to contain an angle | ||
| 62 | * if its angular domain includes that angle (and therefore it is defined for that angle). | ||
| 63 | * | ||
| 64 | * The angular domain considers each ellipse to be | ||
| 65 | * a rotated, scaled and translated unit circle: 0 corresponds to \f$(1,0)\f$ on the unit circle, | ||
| 66 | * \f$\pi/2\f$ corresponds to \f$(0,1)\f$, \f$\pi\f$ to \f$(-1,0)\f$ and \f$3\pi/2\f$ | ||
| 67 | * to \f$(0,-1)\f$. After the angle is mapped to a point from a unit circle, the point is | ||
| 68 | * transformed using a matrix of this form | ||
| 69 | * \f[ M = \left[ \begin{array}{ccc} | ||
| 70 | r_X \cos(\theta) & -r_Y \sin(\theta) & 0 \\ | ||
| 71 | r_X \sin(\theta) & r_Y \cos(\theta) & 0 \\ | ||
| 72 | c_X & c_Y & 1 \end{array} \right] \f] | ||
| 73 | * where \f$r_X, r_Y\f$ are the X and Y rays of the ellipse, \f$\theta\f$ is its angle of rotation, | ||
| 74 | * and \f$c_X, c_Y\f$ the coordinates of the ellipse's center - thus mapping the angle | ||
| 75 | * to some point on the ellipse. Note that for example the point at angluar coordinate 0, | ||
| 76 | * the center and the point at angular coordinate \f$\pi/4\f$ do not necessarily | ||
| 77 | * create an angle of \f$\pi/4\f$ radians; it is only the case if both axes of the ellipse | ||
| 78 | * are of the same length (i.e. it is a circle). | ||
| 79 | * | ||
| 80 | * @image html ellipse-angular-coordinates.png "An illustration of the angular domain" | ||
| 81 | * | ||
| 82 | * Each arc is defined by five variables: The initial and final point, the ellipse's rays, | ||
| 83 | * and the ellipse's rotation. Each set of those parameters corresponds to four different arcs, | ||
| 84 | * with two of them larger than half an ellipse and two of them turning clockwise while traveling | ||
| 85 | * from initial to final point. The two flags disambiguate between them: "large arc flag" selects | ||
| 86 | * the bigger arc, while the "sweep flag" selects the arc going in the direction of positive | ||
| 87 | * angles. Angles always increase when going from the +X axis in the direction of the +Y axis, | ||
| 88 | * so if Y grows downwards, this means clockwise. | ||
| 89 | * | ||
| 90 | * @image html elliptical-arc-flags.png "Meaning of arc flags (Y grows downwards)" | ||
| 91 | * | ||
| 92 | * @ingroup Curves | ||
| 93 | */ | ||
| 94 | |||
| 95 | |||
| 96 | /** @brief Compute bounds of an elliptical arc. | ||
| 97 | * The bounds computation works as follows. The extreme X and Y points | ||
| 98 | * are either the endpoints or local minima / maxima of the ellipse. | ||
| 99 | * We already have endpoints, and we compute the local extremes. | ||
| 100 | * The local extremes correspond to two angles separated by \f$\pi\f$. | ||
| 101 | * Once we compute these angles, we check whether they belong to the arc, | ||
| 102 | * and if they do, we evaluate the ellipse at these angles. | ||
| 103 | * The bounding box of the arc is equal to the bounding box of the endpoints | ||
| 104 | * and the local extrema that belong to the arc. | ||
| 105 | */ | ||
| 106 | 42468 | Rect EllipticalArc::boundsExact() const | |
| 107 | { | ||
| 108 |
2/2✓ Branch 1 taken 1200 times.
✓ Branch 2 taken 41268 times.
|
42468 | if (isChord()) { |
| 109 |
1/2✓ Branch 1 taken 1200 times.
✗ Branch 2 not taken.
|
1200 | return { _initial_point, _final_point }; |
| 110 | } | ||
| 111 | |||
| 112 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 41268 times.
|
41268 | if (_angles.isFull()) { |
| 113 | ✗ | return _ellipse.boundsExact(); | |
| 114 | } | ||
| 115 | |||
| 116 |
1/2✓ Branch 2 taken 41268 times.
✗ Branch 3 not taken.
|
41268 | auto const trans = unitCircleTransform(); |
| 117 | |||
| 118 | 41268 | auto proj_bounds = [&] (Dim2 d) { | |
| 119 | // The dth coordinate function pulls back to trans[d] * x + trans[d + 2] * y + trans[d + 4] | ||
| 120 | // in the coordinate system where the ellipse is a unit circle. We compute its range of | ||
| 121 | // values on the unit circle arc. | ||
| 122 |
1/2✓ Branch 4 taken 82536 times.
✗ Branch 5 not taken.
|
82536 | auto result = Interval(_initial_point[d], _final_point[d]); |
| 123 | |||
| 124 | 82536 | auto const v = Point(trans[d], trans[d + 2]); | |
| 125 | 82536 | auto const r = v.length(); | |
| 126 | 82536 | auto const mid = trans[d + 4]; | |
| 127 |
1/2✓ Branch 2 taken 82536 times.
✗ Branch 3 not taken.
|
82536 | auto const angle = Angle(v); |
| 128 | |||
| 129 |
2/2✓ Branch 1 taken 30878 times.
✓ Branch 2 taken 51658 times.
|
82536 | if (_angles.contains(angle)) { |
| 130 | 30878 | result.expandTo(mid + r); | |
| 131 | } | ||
| 132 |
3/4✓ Branch 2 taken 82536 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 40868 times.
✓ Branch 7 taken 41668 times.
|
82536 | if (_angles.contains(angle + M_PI)) { |
| 133 | 40868 | result.expandTo(mid - r); | |
| 134 | } | ||
| 135 | |||
| 136 | 165072 | return result; | |
| 137 | 41268 | }; | |
| 138 | |||
| 139 |
3/6✓ Branch 2 taken 41268 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 41268 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 41268 times.
✗ Branch 10 not taken.
|
82536 | return { proj_bounds(X), proj_bounds(Y) }; |
| 140 | } | ||
| 141 | |||
| 142 | 2400 | void EllipticalArc::expandToTransformed(Rect &bbox, Affine const &transform) const | |
| 143 | { | ||
| 144 |
1/2✓ Branch 2 taken 2400 times.
✗ Branch 3 not taken.
|
2400 | bbox.expandTo(_final_point * transform); |
| 145 | |||
| 146 |
2/2✓ Branch 1 taken 1200 times.
✓ Branch 2 taken 1200 times.
|
2400 | if (isChord()) { |
| 147 | 1200 | return; | |
| 148 | } | ||
| 149 | |||
| 150 |
2/4✓ Branch 3 taken 1200 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1200 times.
✗ Branch 7 not taken.
|
1200 | auto const trans = unitCircleTransform() * transform; |
| 151 | |||
| 152 |
2/2✓ Branch 4 taken 2400 times.
✓ Branch 5 taken 1200 times.
|
3600 | for (auto d : { X, Y }) { |
| 153 | // See boundsExact() for explanation. | ||
| 154 | 2400 | auto const v = Point(trans[d], trans[d + 2]); | |
| 155 | 2400 | auto const r = v.length(); | |
| 156 | 2400 | auto const mid = trans[d + 4]; | |
| 157 |
1/2✓ Branch 2 taken 2400 times.
✗ Branch 3 not taken.
|
2400 | auto const interval = Interval(mid - r, mid + r); |
| 158 | |||
| 159 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2400 times.
|
2400 | if (_angles.isFull()) { |
| 160 | ✗ | bbox[d].unionWith(interval); | |
| 161 |
2/2✓ Branch 2 taken 2349 times.
✓ Branch 3 taken 51 times.
|
2400 | } else if (!bbox[d].contains(interval)) { |
| 162 |
1/2✓ Branch 2 taken 2349 times.
✗ Branch 3 not taken.
|
2349 | auto const angle = Angle(v); |
| 163 |
2/2✓ Branch 1 taken 748 times.
✓ Branch 2 taken 1601 times.
|
2349 | if (_angles.contains(angle)) { |
| 164 | 748 | bbox[d].expandTo(mid + r); | |
| 165 | } | ||
| 166 |
3/4✓ Branch 2 taken 2349 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 751 times.
✓ Branch 7 taken 1598 times.
|
2349 | if (_angles.contains(angle + M_PI)) { |
| 167 | 751 | bbox[d].expandTo(mid - r); | |
| 168 | } | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | 20000 | Point EllipticalArc::pointAtAngle(Coord t) const | |
| 174 | { | ||
| 175 |
1/2✓ Branch 2 taken 20000 times.
✗ Branch 3 not taken.
|
20000 | Point ret = _ellipse.pointAt(t); |
| 176 | 20000 | return ret; | |
| 177 | } | ||
| 178 | |||
| 179 | ✗ | Coord EllipticalArc::valueAtAngle(Coord t, Dim2 d) const | |
| 180 | { | ||
| 181 | ✗ | return _ellipse.valueAt(t, d); | |
| 182 | } | ||
| 183 | |||
| 184 | 16 | std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const | |
| 185 | { | ||
| 186 | 16 | std::vector<Coord> sol; | |
| 187 | |||
| 188 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
|
16 | if (isChord()) { |
| 189 | ✗ | sol = chord().roots(v, d); | |
| 190 | ✗ | return sol; | |
| 191 | } | ||
| 192 | |||
| 193 | 16 | Interval unit_interval(0, 1); | |
| 194 | |||
| 195 | 16 | double rotx, roty; | |
| 196 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
|
16 | if (d == X) { |
| 197 |
1/2✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
|
6 | sincos(rotationAngle(), roty, rotx); |
| 198 | 6 | roty = -roty; | |
| 199 | } else { | ||
| 200 |
1/2✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
|
10 | sincos(rotationAngle(), rotx, roty); |
| 201 | } | ||
| 202 | |||
| 203 | 16 | double rxrotx = ray(X) * rotx; | |
| 204 | 16 | double c_v = center(d) - v; | |
| 205 | |||
| 206 | 16 | double a = -rxrotx + c_v; | |
| 207 | 16 | double b = ray(Y) * roty; | |
| 208 | 16 | double c = rxrotx + c_v; | |
| 209 | //std::cerr << "a = " << a << std::endl; | ||
| 210 | //std::cerr << "b = " << b << std::endl; | ||
| 211 | //std::cerr << "c = " << c << std::endl; | ||
| 212 | |||
| 213 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
|
16 | if (a == 0) |
| 214 | { | ||
| 215 |
1/2✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | sol.push_back(M_PI); |
| 216 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (b != 0) |
| 217 | { | ||
| 218 | 8 | double s = 2 * std::atan(-c/(2*b)); | |
| 219 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
|
8 | if ( s < 0 ) s += 2*M_PI; |
| 220 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | sol.push_back(s); |
| 221 | } | ||
| 222 | } | ||
| 223 | else | ||
| 224 | { | ||
| 225 | 8 | double delta = b * b - a * c; | |
| 226 | //std::cerr << "delta = " << delta << std::endl; | ||
| 227 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (delta == 0) { |
| 228 | ✗ | double s = 2 * std::atan(-b/a); | |
| 229 | ✗ | if ( s < 0 ) s += 2*M_PI; | |
| 230 | ✗ | sol.push_back(s); | |
| 231 | } | ||
| 232 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | else if ( delta > 0 ) |
| 233 | { | ||
| 234 | 8 | double sq = std::sqrt(delta); | |
| 235 | 8 | double s = 2 * std::atan( (-b - sq) / a ); | |
| 236 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if ( s < 0 ) s += 2*M_PI; |
| 237 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | sol.push_back(s); |
| 238 | 8 | s = 2 * std::atan( (-b + sq) / a ); | |
| 239 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
8 | if ( s < 0 ) s += 2*M_PI; |
| 240 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | sol.push_back(s); |
| 241 | } | ||
| 242 | } | ||
| 243 | |||
| 244 | 16 | std::vector<double> arc_sol; | |
| 245 |
2/2✓ Branch 7 taken 32 times.
✓ Branch 8 taken 16 times.
|
48 | for (double & i : sol) { |
| 246 | //std::cerr << "s = " << deg_from_rad(sol[i]); | ||
| 247 |
2/4✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 32 times.
✗ Branch 6 not taken.
|
32 | i = timeAtAngle(i); |
| 248 | //std::cerr << " -> t: " << sol[i] << std::endl; | ||
| 249 |
2/2✓ Branch 1 taken 25 times.
✓ Branch 2 taken 7 times.
|
32 | if (unit_interval.contains(i)) { |
| 250 |
1/2✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
|
25 | arc_sol.push_back(i); |
| 251 | } | ||
| 252 | } | ||
| 253 | 16 | return arc_sol; | |
| 254 | 16 | } | |
| 255 | |||
| 256 | |||
| 257 | // D(E(t,C),t) = E(t+PI/2,O), where C is the ellipse center | ||
| 258 | // the derivative doesn't rotate the ellipse but there is a translation | ||
| 259 | // of the parameter t by an angle of PI/2 so the ellipse points are shifted | ||
| 260 | // of such an angle in the cw direction | ||
| 261 | 8 | Curve *EllipticalArc::derivative() const | |
| 262 | { | ||
| 263 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (isChord()) { |
| 264 | ✗ | return chord().derivative(); | |
| 265 | } | ||
| 266 | |||
| 267 | 8 | EllipticalArc *result = static_cast<EllipticalArc*>(duplicate()); | |
| 268 | 8 | result->_ellipse.setCenter(0, 0); | |
| 269 |
1/2✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
|
8 | result->_angles.setInitial(result->_angles.initialAngle() + M_PI/2); |
| 270 |
1/2✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
|
8 | result->_angles.setFinal(result->_angles.finalAngle() + M_PI/2); |
| 271 |
2/4✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
|
8 | result->_initial_point = result->pointAtAngle( result->initialAngle() ); |
| 272 |
2/4✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
|
8 | result->_final_point = result->pointAtAngle( result->finalAngle() ); |
| 273 | 8 | return result; | |
| 274 | } | ||
| 275 | |||
| 276 | |||
| 277 | std::vector<Point> | ||
| 278 | 34 | EllipticalArc::pointAndDerivatives(Coord t, unsigned int n) const | |
| 279 | { | ||
| 280 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
|
34 | if (isChord()) { |
| 281 | ✗ | return chord().pointAndDerivatives(t, n); | |
| 282 | } | ||
| 283 | |||
| 284 | 34 | unsigned int nn = n+1; // nn represents the size of the result vector. | |
| 285 | 34 | std::vector<Point> result; | |
| 286 |
1/2✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
|
34 | result.reserve(nn); |
| 287 |
2/4✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 34 times.
✗ Branch 6 not taken.
|
34 | double angle = angleAt(t); |
| 288 |
1/2✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
|
34 | std::unique_ptr<EllipticalArc> ea( static_cast<EllipticalArc*>(duplicate()) ); |
| 289 | 34 | ea->_ellipse.setCenter(0, 0); | |
| 290 | 34 | unsigned int m = std::min(nn, 4u); | |
| 291 |
2/2✓ Branch 0 taken 136 times.
✓ Branch 1 taken 34 times.
|
170 | for ( unsigned int i = 0; i < m; ++i ) |
| 292 | { | ||
| 293 |
2/4✓ Branch 3 taken 136 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 136 times.
✗ Branch 7 not taken.
|
136 | result.push_back( ea->pointAtAngle(angle) ); |
| 294 |
2/2✓ Branch 1 taken 76 times.
✓ Branch 2 taken 60 times.
|
136 | angle += (sweep() ? M_PI/2 : -M_PI/2); |
| 295 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 122 times.
|
136 | if ( !(angle < 2*M_PI) ) angle -= 2*M_PI; |
| 296 | } | ||
| 297 | 34 | m = nn / 4; | |
| 298 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
|
34 | for ( unsigned int i = 1; i < m; ++i ) |
| 299 | { | ||
| 300 | ✗ | for ( unsigned int j = 0; j < 4; ++j ) | |
| 301 | ✗ | result.push_back( result[j] ); | |
| 302 | } | ||
| 303 | 34 | m = nn - 4 * m; | |
| 304 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
|
34 | for ( unsigned int i = 0; i < m; ++i ) |
| 305 | { | ||
| 306 | ✗ | result.push_back( result[i] ); | |
| 307 | } | ||
| 308 |
1/2✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
|
34 | if ( !result.empty() ) // nn != 0 |
| 309 |
1/2✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
|
34 | result[0] = pointAtAngle(angle); |
| 310 | 34 | return result; | |
| 311 | 34 | } | |
| 312 | |||
| 313 | 100664 | Point EllipticalArc::pointAt(Coord t) const | |
| 314 | { | ||
| 315 |
2/2✓ Branch 0 taken 93 times.
✓ Branch 1 taken 100571 times.
|
100664 | if (t == 0.0) { |
| 316 | 93 | return initialPoint(); | |
| 317 | } | ||
| 318 |
2/2✓ Branch 0 taken 78 times.
✓ Branch 1 taken 100493 times.
|
100571 | if (t == 1.0) { |
| 319 | 78 | return finalPoint(); | |
| 320 | } | ||
| 321 |
2/2✓ Branch 1 taken 80000 times.
✓ Branch 2 taken 20493 times.
|
100493 | if (isChord()) { |
| 322 |
2/4✓ Branch 2 taken 80000 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 80000 times.
✗ Branch 6 not taken.
|
160000 | return chord().pointAt(t); |
| 323 | } | ||
| 324 |
3/6✓ Branch 2 taken 20493 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 20493 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 20493 times.
✗ Branch 9 not taken.
|
40986 | return _ellipse.pointAt(angleAt(t)); |
| 325 | } | ||
| 326 | |||
| 327 | ✗ | Coord EllipticalArc::valueAt(Coord t, Dim2 d) const | |
| 328 | { | ||
| 329 | ✗ | if (isChord()) return chord().valueAt(t, d); | |
| 330 | ✗ | return valueAtAngle(angleAt(t), d); | |
| 331 | } | ||
| 332 | |||
| 333 | 63 | Curve* EllipticalArc::portion(double f, double t) const | |
| 334 | { | ||
| 335 | // fix input arguments | ||
| 336 | 63 | f = std::clamp(f, 0.0, 1.0); | |
| 337 | 63 | t = std::clamp(t, 0.0, 1.0); | |
| 338 | |||
| 339 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 63 times.
|
63 | if (f == t) { |
| 340 | ✗ | EllipticalArc *arc = new EllipticalArc(); | |
| 341 | ✗ | arc->_initial_point = arc->_final_point = pointAt(f); | |
| 342 | ✗ | return arc; | |
| 343 | } | ||
| 344 |
4/4✓ Branch 0 taken 51 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 32 times.
|
63 | if (f == 0.0 && t == 1.0) { |
| 345 | 19 | return duplicate(); | |
| 346 | } | ||
| 347 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 43 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
44 | if (f == 1.0 && t == 0.0) { |
| 348 | ✗ | return reverse(); | |
| 349 | } | ||
| 350 | |||
| 351 | 44 | EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate()); | |
| 352 | 44 | arc->_initial_point = pointAt(f); | |
| 353 | 44 | arc->_final_point = pointAt(t); | |
| 354 | 44 | arc->_angles.setAngles(angleAt(f), angleAt(t)); | |
| 355 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 41 times.
|
44 | if (f > t) arc->_angles.setSweep(!sweep()); |
| 356 |
5/6✓ Branch 0 taken 23 times.
✓ Branch 1 taken 21 times.
✓ Branch 3 taken 23 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 23 times.
✓ Branch 6 taken 21 times.
|
44 | if ( _large_arc && fabs(angularExtent() * (t-f)) <= M_PI) { |
| 357 | 23 | arc->_large_arc = false; | |
| 358 | } | ||
| 359 | 44 | return arc; | |
| 360 | } | ||
| 361 | |||
| 362 | // the arc is the same but traversed in the opposite direction | ||
| 363 | 40016 | Curve *EllipticalArc::reverse() const | |
| 364 | { | ||
| 365 | using std::swap; | ||
| 366 | 40016 | EllipticalArc *rarc = static_cast<EllipticalArc*>(duplicate()); | |
| 367 | 40016 | rarc->_angles.reverse(); | |
| 368 | 40016 | swap(rarc->_initial_point, rarc->_final_point); | |
| 369 | 40016 | return rarc; | |
| 370 | } | ||
| 371 | |||
| 372 | #ifdef HAVE_GSL // GSL is required for function "solve_reals" | ||
| 373 | 37 | std::vector<double> EllipticalArc::allNearestTimes( Point const& p, double from, double to ) const | |
| 374 | { | ||
| 375 | 37 | std::vector<double> result; | |
| 376 | |||
| 377 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
|
37 | if ( from > to ) std::swap(from, to); |
| 378 |
2/4✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
|
37 | if ( from < 0 || to > 1 ) |
| 379 | { | ||
| 380 | ✗ | THROW_RANGEERROR("[from,to] interval out of range"); | |
| 381 | } | ||
| 382 | |||
| 383 |
3/8✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 37 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 37 times.
|
37 | if ( ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) || are_near(from, to) ) |
| 384 | { | ||
| 385 | ✗ | result.push_back(from); | |
| 386 | ✗ | return result; | |
| 387 | } | ||
| 388 |
3/6✓ Branch 2 taken 37 times.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 37 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 37 times.
|
37 | else if ( are_near(ray(X), 0) || are_near(ray(Y), 0) ) |
| 389 | { | ||
| 390 | ✗ | LineSegment seg(pointAt(from), pointAt(to)); | |
| 391 | ✗ | Point np = seg.pointAt( seg.nearestTime(p) ); | |
| 392 | ✗ | if ( are_near(ray(Y), 0) ) | |
| 393 | { | ||
| 394 | ✗ | if ( are_near(rotationAngle(), M_PI/2) | |
| 395 | ✗ | || are_near(rotationAngle(), 3*M_PI/2) ) | |
| 396 | { | ||
| 397 | ✗ | result = roots(np[Y], Y); | |
| 398 | } | ||
| 399 | else | ||
| 400 | { | ||
| 401 | ✗ | result = roots(np[X], X); | |
| 402 | } | ||
| 403 | } | ||
| 404 | else | ||
| 405 | { | ||
| 406 | ✗ | if ( are_near(rotationAngle(), M_PI/2) | |
| 407 | ✗ | || are_near(rotationAngle(), 3*M_PI/2) ) | |
| 408 | { | ||
| 409 | ✗ | result = roots(np[X], X); | |
| 410 | } | ||
| 411 | else | ||
| 412 | { | ||
| 413 | ✗ | result = roots(np[Y], Y); | |
| 414 | } | ||
| 415 | } | ||
| 416 | ✗ | return result; | |
| 417 | ✗ | } | |
| 418 |
2/2✓ Branch 3 taken 25 times.
✓ Branch 4 taken 12 times.
|
37 | else if ( are_near(ray(X), ray(Y)) ) |
| 419 | { | ||
| 420 | 25 | Point r = p - center(); | |
| 421 |
2/4✓ Branch 3 taken 25 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 25 times.
|
25 | if ( are_near(r, Point(0,0)) ) |
| 422 | { | ||
| 423 | ✗ | THROW_INFINITESOLUTIONS(0); | |
| 424 | } | ||
| 425 | // TODO: implement case r != 0 | ||
| 426 | // Point np = ray(X) * unit_vector(r); | ||
| 427 | // std::vector<double> solX = roots(np[X],X); | ||
| 428 | // std::vector<double> solY = roots(np[Y],Y); | ||
| 429 | // double t; | ||
| 430 | // if ( are_near(solX[0], solY[0]) || are_near(solX[0], solY[1])) | ||
| 431 | // { | ||
| 432 | // t = solX[0]; | ||
| 433 | // } | ||
| 434 | // else | ||
| 435 | // { | ||
| 436 | // t = solX[1]; | ||
| 437 | // } | ||
| 438 | // if ( !(t < from || t > to) ) | ||
| 439 | // { | ||
| 440 | // result.push_back(t); | ||
| 441 | // } | ||
| 442 | // else | ||
| 443 | // { | ||
| 444 | // | ||
| 445 | // } | ||
| 446 | } | ||
| 447 | |||
| 448 | // solve the equation <D(E(t),t)|E(t)-p> == 0 | ||
| 449 | // that provides min and max distance points | ||
| 450 | // on the ellipse E wrt the point p | ||
| 451 | // after the substitutions: | ||
| 452 | // cos(t) = (1 - s^2) / (1 + s^2) | ||
| 453 | // sin(t) = 2t / (1 + s^2) | ||
| 454 | // where s = tan(t/2) | ||
| 455 | // we get a 4th degree equation in s | ||
| 456 | /* | ||
| 457 | * ry s^4 ((-cy + py) Cos[Phi] + (cx - px) Sin[Phi]) + | ||
| 458 | * ry ((cy - py) Cos[Phi] + (-cx + px) Sin[Phi]) + | ||
| 459 | * 2 s^3 (rx^2 - ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi]) + | ||
| 460 | * 2 s (-rx^2 + ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi]) | ||
| 461 | */ | ||
| 462 | |||
| 463 | 37 | Point p_c = p - center(); | |
| 464 | 37 | double rx2_ry2 = (ray(X) - ray(Y)) * (ray(X) + ray(Y)); | |
| 465 | 37 | double sinrot, cosrot; | |
| 466 |
1/2✓ Branch 3 taken 37 times.
✗ Branch 4 not taken.
|
37 | sincos(rotationAngle(), sinrot, cosrot); |
| 467 | 37 | double expr1 = ray(X) * (p_c[X] * cosrot + p_c[Y] * sinrot); | |
| 468 | 37 | Poly coeff; | |
| 469 |
1/2✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
|
37 | coeff.resize(5); |
| 470 | 37 | coeff[4] = ray(Y) * ( p_c[Y] * cosrot - p_c[X] * sinrot ); | |
| 471 | 37 | coeff[3] = 2 * ( rx2_ry2 + expr1 ); | |
| 472 | 37 | coeff[2] = 0; | |
| 473 | 37 | coeff[1] = 2 * ( -rx2_ry2 + expr1 ); | |
| 474 | 37 | coeff[0] = -coeff[4]; | |
| 475 | |||
| 476 | // for ( unsigned int i = 0; i < 5; ++i ) | ||
| 477 | // std::cerr << "c[" << i << "] = " << coeff[i] << std::endl; | ||
| 478 | |||
| 479 | 37 | std::vector<double> real_sol; | |
| 480 | // gsl_poly_complex_solve raises an error | ||
| 481 | // if the leading coefficient is zero | ||
| 482 |
2/2✓ Branch 2 taken 7 times.
✓ Branch 3 taken 30 times.
|
37 | if ( are_near(coeff[4], 0) ) |
| 483 | { | ||
| 484 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | real_sol.push_back(0); |
| 485 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | if ( !are_near(coeff[3], 0) ) |
| 486 | { | ||
| 487 | 7 | double sq = -coeff[1] / coeff[3]; | |
| 488 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if ( sq > 0 ) |
| 489 | { | ||
| 490 | ✗ | double s = std::sqrt(sq); | |
| 491 | ✗ | real_sol.push_back(s); | |
| 492 | ✗ | real_sol.push_back(-s); | |
| 493 | } | ||
| 494 | } | ||
| 495 | } | ||
| 496 | else | ||
| 497 | { | ||
| 498 |
1/2✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
|
30 | real_sol = solve_reals(coeff); |
| 499 | } | ||
| 500 | |||
| 501 |
2/2✓ Branch 7 taken 77 times.
✓ Branch 8 taken 37 times.
|
114 | for (double & i : real_sol) |
| 502 | { | ||
| 503 | 77 | i = 2 * std::atan(i); | |
| 504 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 37 times.
|
77 | if ( i < 0 ) i += 2*M_PI; |
| 505 | } | ||
| 506 | // when s -> Infinity then <D(E)|E-p> -> 0 iff coeff[4] == 0 | ||
| 507 | // so we add M_PI to the solutions being lim arctan(s) = PI when s->Infinity | ||
| 508 |
2/2✓ Branch 1 taken 7 times.
✓ Branch 2 taken 30 times.
|
37 | if ( (real_sol.size() % 2) != 0 ) |
| 509 | { | ||
| 510 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | real_sol.push_back(M_PI); |
| 511 | } | ||
| 512 | |||
| 513 | 37 | double mindistsq1 = std::numeric_limits<double>::max(); | |
| 514 | 37 | double mindistsq2 = std::numeric_limits<double>::max(); | |
| 515 | 37 | double dsq = 0; | |
| 516 | 37 | unsigned int mi1 = 0, mi2 = 0; | |
| 517 |
2/2✓ Branch 1 taken 84 times.
✓ Branch 2 taken 37 times.
|
121 | for ( unsigned int i = 0; i < real_sol.size(); ++i ) |
| 518 | { | ||
| 519 |
2/4✓ Branch 3 taken 84 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 84 times.
✗ Branch 7 not taken.
|
84 | dsq = distanceSq(p, pointAtAngle(real_sol[i])); |
| 520 |
2/2✓ Branch 0 taken 51 times.
✓ Branch 1 taken 33 times.
|
84 | if ( mindistsq1 > dsq ) |
| 521 | { | ||
| 522 | 51 | mindistsq2 = mindistsq1; | |
| 523 | 51 | mi2 = mi1; | |
| 524 | 51 | mindistsq1 = dsq; | |
| 525 | 51 | mi1 = i; | |
| 526 | } | ||
| 527 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 10 times.
|
33 | else if ( mindistsq2 > dsq ) |
| 528 | { | ||
| 529 | 23 | mindistsq2 = dsq; | |
| 530 | 23 | mi2 = i; | |
| 531 | } | ||
| 532 | } | ||
| 533 | |||
| 534 |
2/4✓ Branch 4 taken 37 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 37 times.
✗ Branch 8 not taken.
|
37 | double t = timeAtAngle(real_sol[mi1]); |
| 535 |
3/4✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 4 times.
|
37 | if ( !(t < from || t > to) ) |
| 536 | { | ||
| 537 |
1/2✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
|
33 | result.push_back(t); |
| 538 | } | ||
| 539 | |||
| 540 | 37 | bool second_sol = false; | |
| 541 |
2/4✓ Branch 3 taken 37 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 37 times.
✗ Branch 7 not taken.
|
37 | t = timeAtAngle(real_sol[mi2]); |
| 542 |
5/8✓ Branch 1 taken 5 times.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 37 times.
|
37 | if ( real_sol.size() == 4 && !(t < from || t > to) ) |
| 543 | { | ||
| 544 | ✗ | if ( result.empty() || are_near(mindistsq1, mindistsq2) ) | |
| 545 | { | ||
| 546 | ✗ | result.push_back(t); | |
| 547 | ✗ | second_sol = true; | |
| 548 | } | ||
| 549 | } | ||
| 550 | |||
| 551 | // we need to test extreme points too | ||
| 552 |
2/4✓ Branch 2 taken 37 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 37 times.
✗ Branch 6 not taken.
|
37 | double dsq1 = distanceSq(p, pointAt(from)); |
| 553 |
2/4✓ Branch 2 taken 37 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 37 times.
✗ Branch 6 not taken.
|
37 | double dsq2 = distanceSq(p, pointAt(to)); |
| 554 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
|
37 | if ( second_sol ) |
| 555 | { | ||
| 556 | ✗ | if ( mindistsq2 > dsq1 ) | |
| 557 | { | ||
| 558 | ✗ | result.clear(); | |
| 559 | ✗ | result.push_back(from); | |
| 560 | ✗ | mindistsq2 = dsq1; | |
| 561 | } | ||
| 562 | ✗ | else if ( are_near(mindistsq2, dsq) ) | |
| 563 | { | ||
| 564 | ✗ | result.push_back(from); | |
| 565 | } | ||
| 566 | ✗ | if ( mindistsq2 > dsq2 ) | |
| 567 | { | ||
| 568 | ✗ | result.clear(); | |
| 569 | ✗ | result.push_back(to); | |
| 570 | } | ||
| 571 | ✗ | else if ( are_near(mindistsq2, dsq2) ) | |
| 572 | { | ||
| 573 | ✗ | result.push_back(to); | |
| 574 | } | ||
| 575 | |||
| 576 | } | ||
| 577 | else | ||
| 578 | { | ||
| 579 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 33 times.
|
37 | if ( result.empty() ) |
| 580 | { | ||
| 581 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if ( are_near(dsq1, dsq2) ) |
| 582 | { | ||
| 583 | ✗ | result.push_back(from); | |
| 584 | ✗ | result.push_back(to); | |
| 585 | } | ||
| 586 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | else if ( dsq2 > dsq1 ) |
| 587 | { | ||
| 588 | ✗ | result.push_back(from); | |
| 589 | } | ||
| 590 | else | ||
| 591 | { | ||
| 592 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | result.push_back(to); |
| 593 | } | ||
| 594 | } | ||
| 595 | } | ||
| 596 | |||
| 597 | 37 | return result; | |
| 598 | 37 | } | |
| 599 | #endif | ||
| 600 | |||
| 601 | /** @brief Convert the passed intersections to curve time parametrization | ||
| 602 | * and filter out any invalid intersections. | ||
| 603 | */ | ||
| 604 | 10021 | std::vector<ShapeIntersection> EllipticalArc::_filterIntersections(std::vector<ShapeIntersection> &&xs, | |
| 605 | bool is_first) const | ||
| 606 | { | ||
| 607 | 10021 | std::vector<ShapeIntersection> result; | |
| 608 |
1/2✓ Branch 2 taken 10021 times.
✗ Branch 3 not taken.
|
10021 | result.reserve(xs.size()); |
| 609 |
2/2✓ Branch 7 taken 20038 times.
✓ Branch 8 taken 10021 times.
|
30059 | for (auto &xing : xs) { |
| 610 |
3/4✓ Branch 1 taken 20038 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10036 times.
✓ Branch 4 taken 10002 times.
|
20038 | if (_validateIntersection(xing, is_first)) { |
| 611 |
1/2✓ Branch 2 taken 10036 times.
✗ Branch 3 not taken.
|
10036 | result.emplace_back(std::move(xing)); |
| 612 | } | ||
| 613 | } | ||
| 614 | 10021 | return result; | |
| 615 | ✗ | } | |
| 616 | |||
| 617 | /** @brief Convert the passed intersection to curve time and check whether the intersection | ||
| 618 | * is numerically sane. | ||
| 619 | * | ||
| 620 | * @param xing The intersection to convert to curve time and to validate. | ||
| 621 | * @param is_first If true, this arc is the first of the intersected curves; if false, it's second. | ||
| 622 | * @return Whether the intersection is valid. | ||
| 623 | * | ||
| 624 | * Note that the intersection is guaranteed to be converted only if the return value is true. | ||
| 625 | */ | ||
| 626 | 20038 | bool EllipticalArc::_validateIntersection(ShapeIntersection &xing, bool is_first) const | |
| 627 | { | ||
| 628 | static auto const UNIT_INTERVAL = Interval(0, 1); | ||
| 629 | 20038 | constexpr auto EPS = 1e-4; | |
| 630 | |||
| 631 |
2/2✓ Branch 0 taken 20030 times.
✓ Branch 1 taken 8 times.
|
20038 | Coord &t = is_first ? xing.first : xing.second; |
| 632 |
4/6✓ Branch 4 taken 20038 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 20038 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 20037 times.
|
20038 | if (!are_near_rel(_ellipse.pointAt(t), xing.point(), EPS)) { |
| 633 | 1 | return false; | |
| 634 | } | ||
| 635 | |||
| 636 |
2/4✓ Branch 2 taken 20037 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 20037 times.
✗ Branch 6 not taken.
|
20037 | t = timeAtAngle(t); |
| 637 |
2/2✓ Branch 1 taken 10001 times.
✓ Branch 2 taken 10036 times.
|
20037 | if (!UNIT_INTERVAL.contains(t)) { |
| 638 | 10001 | return false; | |
| 639 | } | ||
| 640 |
3/6✓ Branch 4 taken 10036 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 10036 times.
✗ Branch 8 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 10036 times.
|
10036 | if (!are_near_rel(pointAt(t), xing.point(), EPS)) { |
| 641 | ✗ | return false; | |
| 642 | } | ||
| 643 | 10036 | return true; | |
| 644 | } | ||
| 645 | |||
| 646 | 90026 | std::vector<CurveIntersection> EllipticalArc::intersect(Curve const &other, Coord eps) const | |
| 647 | { | ||
| 648 |
3/4✓ Branch 1 taken 90026 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 80000 times.
✓ Branch 4 taken 10026 times.
|
90026 | if (isLineSegment()) { |
| 649 |
1/2✓ Branch 2 taken 80000 times.
✗ Branch 3 not taken.
|
80000 | LineSegment ls(_initial_point, _final_point); |
| 650 |
1/2✓ Branch 1 taken 80000 times.
✗ Branch 2 not taken.
|
80000 | return ls.intersect(other, eps); |
| 651 | 80000 | } | |
| 652 | |||
| 653 |
3/4✓ Branch 1 taken 10026 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10010 times.
✓ Branch 4 taken 16 times.
|
10026 | if (other.isLineSegment()) { |
| 654 |
3/6✓ Branch 2 taken 10010 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10010 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 10010 times.
✗ Branch 9 not taken.
|
10010 | LineSegment ls(other.initialPoint(), other.finalPoint()); |
| 655 |
2/4✓ Branch 2 taken 10010 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10010 times.
✗ Branch 6 not taken.
|
20020 | return _filterIntersections(_ellipse.intersect(ls), true); |
| 656 | 10010 | } | |
| 657 | |||
| 658 |
3/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 13 times.
|
16 | if (auto bez = dynamic_cast<BezierCurve const *>(&other)) { |
| 659 |
2/4✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
|
6 | return _filterIntersections(_ellipse.intersect(bez->fragment()), true); |
| 660 | } | ||
| 661 | |||
| 662 |
2/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
|
13 | if (auto arc = dynamic_cast<EllipticalArc const *>(&other)) { |
| 663 | 13 | std::vector<CurveIntersection> crossings; | |
| 664 | try { | ||
| 665 |
2/2✓ Branch 2 taken 4 times.
✓ Branch 3 taken 9 times.
|
13 | crossings = _ellipse.intersect(arc->_ellipse); |
| 666 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | } catch (InfinitelyManySolutions &) { |
| 667 | // This could happen if the two arcs come from the same ellipse. | ||
| 668 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | return _intersectSameEllipse(arc); |
| 669 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | } |
| 670 |
2/4✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
|
8 | return arc->_filterIntersections(_filterIntersections(std::move(crossings), true), false); |
| 671 | 13 | } | |
| 672 | |||
| 673 | // in case someone wants to make a custom curve type | ||
| 674 | ✗ | auto result = other.intersect(*this, eps); | |
| 675 | ✗ | transpose_in_place(result); | |
| 676 | ✗ | return result; | |
| 677 | ✗ | } | |
| 678 | |||
| 679 | /** @brief Check if two arcs on the same ellipse intersect/overlap. | ||
| 680 | * | ||
| 681 | * @param other Another elliptical arc on the same ellipse as this one. | ||
| 682 | * @return If the arcs overlap, the returned vector contains synthesized intersections | ||
| 683 | * at the start and end of the overlap. | ||
| 684 | * If the arcs do not overlap, an empty vector is returned. | ||
| 685 | */ | ||
| 686 | 9 | std::vector<ShapeIntersection> EllipticalArc::_intersectSameEllipse(EllipticalArc const *other) const | |
| 687 | { | ||
| 688 |
2/4✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
|
9 | assert(_ellipse == other->_ellipse); |
| 689 | 9 | auto const &other_angles = other->angularInterval(); | |
| 690 | 9 | std::vector<ShapeIntersection> result; | |
| 691 | |||
| 692 | /// A closure to create an "intersection" at the prescribed angle. | ||
| 693 | 9 | auto const synthesize_intersection = [&](Angle angle) { | |
| 694 |
1/2✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
|
22 | auto const time = timeAtAngle(angle); |
| 695 |
3/4✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
✓ Branch 11 taken 13 times.
✓ Branch 12 taken 9 times.
|
22 | if (result.end() == std::find_if(result.begin(), result.end(), |
| 696 | 15 | [=](ShapeIntersection const &xing) -> bool { | |
| 697 | 15 | return xing.first == time; | |
| 698 | })) | ||
| 699 | { | ||
| 700 |
4/8✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 13 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 13 times.
✗ Branch 13 not taken.
|
13 | result.emplace_back(time, other->timeAtAngle(angle), _ellipse.pointAt(angle)); |
| 701 | } | ||
| 702 | 22 | }; | |
| 703 | |||
| 704 |
2/2✓ Branch 7 taken 18 times.
✓ Branch 8 taken 9 times.
|
27 | for (auto a : {_angles.initialAngle(), _angles.finalAngle()}) { |
| 705 |
2/2✓ Branch 1 taken 11 times.
✓ Branch 2 taken 7 times.
|
18 | if (other_angles.contains(a)) { |
| 706 |
1/2✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
|
11 | synthesize_intersection(a); |
| 707 | } | ||
| 708 | } | ||
| 709 |
2/2✓ Branch 7 taken 18 times.
✓ Branch 8 taken 9 times.
|
27 | for (auto a : {other_angles.initialAngle(), other_angles.finalAngle()}) { |
| 710 |
2/2✓ Branch 1 taken 11 times.
✓ Branch 2 taken 7 times.
|
18 | if (_angles.contains(a)) { |
| 711 |
1/2✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
|
11 | synthesize_intersection(a); |
| 712 | } | ||
| 713 | } | ||
| 714 | 9 | return result; | |
| 715 | ✗ | } | |
| 716 | |||
| 717 | 10389 | void EllipticalArc::_updateCenterAndAngles() | |
| 718 | { | ||
| 719 | // See: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes | ||
| 720 |
2/4✓ Branch 3 taken 10389 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 10389 times.
✗ Branch 8 not taken.
|
10389 | Point d = initialPoint() - finalPoint(); |
| 721 |
3/6✓ Branch 3 taken 10389 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 10389 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 10389 times.
✗ Branch 11 not taken.
|
10389 | Point mid = middle_point(initialPoint(), finalPoint()); |
| 722 | |||
| 723 | 10389 | auto degenerate_ellipse = [&] { | |
| 724 | 2 | _ellipse = Ellipse(); | |
| 725 |
1/2✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | _ellipse.setCenter(initialPoint()); |
| 726 | 2 | _angles = AngleInterval(); | |
| 727 | 2 | _large_arc = false; | |
| 728 | 2 | }; | |
| 729 | |||
| 730 | // if ip = fp, the arc contains no other points | ||
| 731 |
4/6✓ Branch 2 taken 10389 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 10389 times.
✗ Branch 7 not taken.
✓ Branch 11 taken 2 times.
✓ Branch 12 taken 10387 times.
|
10389 | if (initialPoint() == finalPoint()) { |
| 732 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | degenerate_ellipse(); |
| 733 | 2 | return; | |
| 734 | } | ||
| 735 | |||
| 736 | // rays should be positive | ||
| 737 | 10387 | _ellipse.setRays(std::fabs(ray(X)), std::fabs(ray(Y))); | |
| 738 | |||
| 739 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 10383 times.
|
10387 | if (isChord()) { |
| 740 | 4 | _ellipse.setRays(L2(d) / 2, 0); | |
| 741 |
2/4✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
|
4 | _ellipse.setRotationAngle(atan2(d)); |
| 742 | 4 | _ellipse.setCenter(mid); | |
| 743 |
2/4✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
|
4 | _angles.setAngles(0, M_PI); |
| 744 | 4 | _angles.setSweep(false); | |
| 745 | 4 | _large_arc = false; | |
| 746 | 4 | return; | |
| 747 | } | ||
| 748 | |||
| 749 |
2/4✓ Branch 4 taken 10383 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 10383 times.
✗ Branch 8 not taken.
|
10383 | Rotate rot(rotationAngle()); // the matrix in F.6.5.3 |
| 750 |
1/2✓ Branch 2 taken 10383 times.
✗ Branch 3 not taken.
|
10383 | Rotate invrot = rot.inverse(); // the matrix in F.6.5.1 |
| 751 | |||
| 752 | 10383 | Point r = rays(); | |
| 753 |
1/2✓ Branch 5 taken 10383 times.
✗ Branch 6 not taken.
|
10383 | Point p = d / 2 * invrot; // x', y' in F.6.5.1 |
| 754 | 10383 | Point c(0,0); // cx', cy' in F.6.5.2 | |
| 755 | |||
| 756 | // Correct out-of-range radii | ||
| 757 | 10383 | Coord lambda = hypot(p[X]/r[X], p[Y]/r[Y]); | |
| 758 |
2/2✓ Branch 0 taken 52 times.
✓ Branch 1 taken 10331 times.
|
10383 | if (lambda > 1) { |
| 759 | 52 | r *= lambda; | |
| 760 | 52 | _ellipse.setRays(r); | |
| 761 | 52 | _ellipse.setCenter(mid); | |
| 762 | } else { | ||
| 763 | // evaluate F.6.5.2 | ||
| 764 | 10331 | Coord rxry = r[X]*r[X] * r[Y]*r[Y]; | |
| 765 | 10331 | Coord pxry = p[X]*p[X] * r[Y]*r[Y]; | |
| 766 | 10331 | Coord rxpy = r[X]*r[X] * p[Y]*p[Y]; | |
| 767 | 10331 | Coord const denominator = rxpy + pxry; | |
| 768 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10331 times.
|
10331 | if (denominator == 0.0) { |
| 769 | ✗ | degenerate_ellipse(); | |
| 770 | ✗ | return; | |
| 771 | } | ||
| 772 | 10331 | Coord rad = (rxry - pxry - rxpy) / denominator; | |
| 773 | // normally rad should never be negative, but numerical inaccuracy may cause this | ||
| 774 |
2/2✓ Branch 0 taken 10044 times.
✓ Branch 1 taken 287 times.
|
10331 | if (rad > 0) { |
| 775 | 10044 | rad = std::sqrt(rad); | |
| 776 |
2/2✓ Branch 1 taken 4998 times.
✓ Branch 2 taken 5046 times.
|
10044 | if (sweep() == _large_arc) { |
| 777 | 4998 | rad = -rad; | |
| 778 | } | ||
| 779 | 10044 | c = rad * Point(r[X]*p[Y]/r[Y], -r[Y]*p[X]/r[X]); | |
| 780 |
1/2✓ Branch 3 taken 10044 times.
✗ Branch 4 not taken.
|
10044 | _ellipse.setCenter(c * rot + mid); |
| 781 | } else { | ||
| 782 | 287 | _ellipse.setCenter(mid); | |
| 783 | } | ||
| 784 | } | ||
| 785 | |||
| 786 | // Compute start and end angles. | ||
| 787 | // If the ellipse was enlarged, c will be zero - this is correct. | ||
| 788 | 10383 | Point sp((p[X] - c[X]) / r[X], (p[Y] - c[Y]) / r[Y]); | |
| 789 | 10383 | Point ep((-p[X] - c[X]) / r[X], (-p[Y] - c[Y]) / r[Y]); | |
| 790 | 10383 | Point v(1, 0); | |
| 791 | |||
| 792 |
2/4✓ Branch 2 taken 10383 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10383 times.
✗ Branch 6 not taken.
|
10383 | _angles.setInitial(angle_between(v, sp)); |
| 793 |
2/4✓ Branch 2 taken 10383 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10383 times.
✗ Branch 6 not taken.
|
10383 | _angles.setFinal(angle_between(v, ep)); |
| 794 | |||
| 795 | /*double sweep_angle = angle_between(sp, ep); | ||
| 796 | if (!sweep() && sweep_angle > 0) sweep_angle -= 2*M_PI; | ||
| 797 | if (sweep() && sweep_angle < 0) sweep_angle += 2*M_PI;*/ | ||
| 798 | } | ||
| 799 | |||
| 800 | 23 | D2<SBasis> EllipticalArc::toSBasis() const | |
| 801 | { | ||
| 802 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
|
23 | if (isChord()) { |
| 803 | ✗ | return chord().toSBasis(); | |
| 804 | } | ||
| 805 | |||
| 806 |
1/2✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
|
23 | D2<SBasis> arc; |
| 807 | // the interval of parametrization has to be [0,1] | ||
| 808 | 23 | Coord et = initialAngle().radians() + sweepAngle(); | |
| 809 | 23 | Linear param(initialAngle().radians(), et); | |
| 810 | 23 | Coord cosrot, sinrot; | |
| 811 |
1/2✓ Branch 3 taken 23 times.
✗ Branch 4 not taken.
|
23 | sincos(rotationAngle(), sinrot, cosrot); |
| 812 | |||
| 813 | // order = 4 seems to be enough to get a perfect looking elliptical arc | ||
| 814 |
2/4✓ Branch 3 taken 23 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 23 times.
✗ Branch 8 not taken.
|
23 | SBasis arc_x = ray(X) * cos(param,4); |
| 815 |
2/4✓ Branch 3 taken 23 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 23 times.
✗ Branch 8 not taken.
|
23 | SBasis arc_y = ray(Y) * sin(param,4); |
| 816 |
6/12✓ Branch 7 taken 23 times.
✗ Branch 8 not taken.
✓ Branch 12 taken 23 times.
✗ Branch 13 not taken.
✓ Branch 16 taken 23 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 23 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 23 times.
✗ Branch 23 not taken.
✓ Branch 26 taken 23 times.
✗ Branch 27 not taken.
|
23 | arc[0] = arc_x * cosrot - arc_y * sinrot + Linear(center(X), center(X)); |
| 817 |
6/12✓ Branch 7 taken 23 times.
✗ Branch 8 not taken.
✓ Branch 12 taken 23 times.
✗ Branch 13 not taken.
✓ Branch 16 taken 23 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 23 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 23 times.
✗ Branch 23 not taken.
✓ Branch 26 taken 23 times.
✗ Branch 27 not taken.
|
23 | arc[1] = arc_x * sinrot + arc_y * cosrot + Linear(center(Y), center(Y)); |
| 818 | |||
| 819 | // ensure that endpoints remain exact | ||
| 820 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 23 times.
|
69 | for ( int d = 0 ; d < 2 ; d++ ) { |
| 821 |
2/4✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 46 times.
✗ Branch 8 not taken.
|
46 | arc[d][0][0] = initialPoint()[d]; |
| 822 |
2/4✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 46 times.
✗ Branch 8 not taken.
|
46 | arc[d][0][1] = finalPoint()[d]; |
| 823 | } | ||
| 824 | |||
| 825 |
1/2✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
|
23 | return arc; |
| 826 | 23 | } | |
| 827 | |||
| 828 | // All operations that do not contain skew can be evaluated | ||
| 829 | // without passing through the implicit form of the ellipse, | ||
| 830 | // which preserves precision. | ||
| 831 | |||
| 832 | ✗ | void EllipticalArc::operator*=(Translate const &tr) | |
| 833 | { | ||
| 834 | ✗ | _initial_point *= tr; | |
| 835 | ✗ | _final_point *= tr; | |
| 836 | ✗ | _ellipse *= tr; | |
| 837 | ✗ | } | |
| 838 | |||
| 839 | 4 | void EllipticalArc::operator*=(Scale const &s) | |
| 840 | { | ||
| 841 | 4 | _initial_point *= s; | |
| 842 | 4 | _final_point *= s; | |
| 843 | 4 | _ellipse *= s; | |
| 844 | 4 | } | |
| 845 | |||
| 846 | ✗ | void EllipticalArc::operator*=(Rotate const &r) | |
| 847 | { | ||
| 848 | ✗ | _initial_point *= r; | |
| 849 | ✗ | _final_point *= r; | |
| 850 | ✗ | _ellipse *= r; | |
| 851 | ✗ | } | |
| 852 | |||
| 853 | ✗ | void EllipticalArc::operator*=(Zoom const &z) | |
| 854 | { | ||
| 855 | ✗ | _initial_point *= z; | |
| 856 | ✗ | _final_point *= z; | |
| 857 | ✗ | _ellipse *= z; | |
| 858 | ✗ | } | |
| 859 | |||
| 860 | 2401 | void EllipticalArc::operator*=(Affine const& m) | |
| 861 | { | ||
| 862 |
2/2✓ Branch 1 taken 1200 times.
✓ Branch 2 taken 1201 times.
|
2401 | if (isChord()) { |
| 863 | 1200 | _initial_point *= m; | |
| 864 | 1200 | _final_point *= m; | |
| 865 |
1/2✓ Branch 2 taken 1200 times.
✗ Branch 3 not taken.
|
1200 | _ellipse.setCenter(middle_point(_initial_point, _final_point)); |
| 866 | 1200 | _ellipse.setRays(0, 0); | |
| 867 |
1/2✓ Branch 2 taken 1200 times.
✗ Branch 3 not taken.
|
1200 | _ellipse.setRotationAngle(0); |
| 868 | 1200 | return; | |
| 869 | } | ||
| 870 | |||
| 871 | 1201 | _initial_point *= m; | |
| 872 | 1201 | _final_point *= m; | |
| 873 | 1201 | _ellipse *= m; | |
| 874 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1201 times.
|
1201 | if (m.det() < 0) { |
| 875 | ✗ | _angles.setSweep(!sweep()); | |
| 876 | } | ||
| 877 | |||
| 878 | // ellipse transformation does not preserve its functional form, | ||
| 879 | // i.e. e.pointAt(0.5)*m and (e*m).pointAt(0.5) can be different. | ||
| 880 | // We need to recompute start / end angles. | ||
| 881 |
2/4✓ Branch 2 taken 1201 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1201 times.
✗ Branch 6 not taken.
|
1201 | _angles.setInitial(_ellipse.timeAt(_initial_point)); |
| 882 |
2/4✓ Branch 2 taken 1201 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1201 times.
✗ Branch 6 not taken.
|
1201 | _angles.setFinal(_ellipse.timeAt(_final_point)); |
| 883 | } | ||
| 884 | |||
| 885 | 15 | bool EllipticalArc::_equalTo(Curve const &c) const | |
| 886 | { | ||
| 887 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | if (this == &c) return true; |
| 888 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | auto other = dynamic_cast<EllipticalArc const *>(&c); |
| 889 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | if (!other) return false; |
| 890 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
|
15 | if (_initial_point != other->_initial_point) return false; |
| 891 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
|
15 | if (_final_point != other->_final_point) return false; |
| 892 | // TODO: all arcs with ellipse rays which are too small | ||
| 893 | // and fall back to a line should probably be equal | ||
| 894 |
1/2✗ Branch 7 not taken.
✓ Branch 8 taken 15 times.
|
15 | if (rays() != other->rays()) return false; |
| 895 |
1/2✗ Branch 7 not taken.
✓ Branch 8 taken 15 times.
|
15 | if (rotationAngle() != other->rotationAngle()) return false; |
| 896 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | if (_large_arc != other->_large_arc) return false; |
| 897 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
|
15 | if (sweep() != other->sweep()) return false; |
| 898 | 15 | return true; | |
| 899 | } | ||
| 900 | |||
| 901 | 13 | bool EllipticalArc::isNear(Curve const &c, Coord precision) const | |
| 902 | { | ||
| 903 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | EllipticalArc const *other = dynamic_cast<EllipticalArc const *>(&c); |
| 904 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 11 times.
|
13 | if (!other) { |
| 905 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
|
2 | if (isChord()) { |
| 906 | ✗ | return c.isNear(chord(), precision); | |
| 907 | } | ||
| 908 | 2 | return false; | |
| 909 | } | ||
| 910 | |||
| 911 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
|
11 | if (!are_near(_initial_point, other->_initial_point, precision)) return false; |
| 912 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 10 times.
|
11 | if (!are_near(_final_point, other->_final_point, precision)) return false; |
| 913 |
2/6✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 10 times.
|
10 | if (isChord() && other->isChord()) return true; |
| 914 | |||
| 915 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
10 | if (sweep() != other->sweep()) return false; |
| 916 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
|
10 | if (!are_near(_ellipse, other->_ellipse, precision)) return false; |
| 917 | 10 | return true; | |
| 918 | } | ||
| 919 | |||
| 920 | 169 | void EllipticalArc::feed(PathSink &sink, bool moveto_initial) const | |
| 921 | { | ||
| 922 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 169 times.
|
169 | if (moveto_initial) { |
| 923 | ✗ | sink.moveTo(_initial_point); | |
| 924 | } | ||
| 925 |
2/4✓ Branch 4 taken 169 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 169 times.
✗ Branch 10 not taken.
|
169 | sink.arcTo(ray(X), ray(Y), rotationAngle(), _large_arc, sweep(), _final_point); |
| 926 | 169 | } | |
| 927 | |||
| 928 | 9863 | int EllipticalArc::winding(Point const &p) const | |
| 929 | { | ||
| 930 | using std::swap; | ||
| 931 | |||
| 932 | 9863 | double sinrot, cosrot; | |
| 933 |
1/2✓ Branch 3 taken 9863 times.
✗ Branch 4 not taken.
|
9863 | sincos(rotationAngle(), sinrot, cosrot); |
| 934 | |||
| 935 |
1/2✓ Branch 4 taken 9863 times.
✗ Branch 5 not taken.
|
9863 | Angle ymin_a = std::atan2( ray(Y) * cosrot, ray(X) * sinrot ); |
| 936 |
1/2✓ Branch 3 taken 9863 times.
✗ Branch 4 not taken.
|
9863 | Angle ymax_a = ymin_a + M_PI; |
| 937 | |||
| 938 |
2/4✓ Branch 2 taken 9863 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9863 times.
✗ Branch 6 not taken.
|
9863 | Point ymin = pointAtAngle(ymin_a); |
| 939 |
2/4✓ Branch 2 taken 9863 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9863 times.
✗ Branch 6 not taken.
|
9863 | Point ymax = pointAtAngle(ymax_a); |
| 940 |
1/2✓ Branch 2 taken 9863 times.
✗ Branch 3 not taken.
|
9863 | if (ymin[Y] > ymax[Y]) { |
| 941 | 9863 | swap(ymin, ymax); | |
| 942 | 9863 | swap(ymin_a, ymax_a); | |
| 943 | } | ||
| 944 | |||
| 945 |
3/6✓ Branch 4 taken 9863 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 9863 times.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 9863 times.
|
9863 | if (!Interval(ymin[Y], ymax[Y]).lowerContains(p[Y])) { |
| 946 | ✗ | return 0; | |
| 947 | } | ||
| 948 | |||
| 949 | 9863 | bool const left = cross(ymax - ymin, p - ymin) > 0; | |
| 950 |
1/2✓ Branch 1 taken 9863 times.
✗ Branch 2 not taken.
|
9863 | bool const inside = _ellipse.contains(p); |
| 951 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 9863 times.
|
9863 | if (_angles.isFull()) { |
| 952 | ✗ | if (inside) { | |
| 953 | ✗ | return sweep() ? 1 : -1; | |
| 954 | } | ||
| 955 | ✗ | return 0; | |
| 956 | } | ||
| 957 | 9863 | bool const includes_ymin = _angles.contains(ymin_a); | |
| 958 | 9863 | bool const includes_ymax = _angles.contains(ymax_a); | |
| 959 | |||
| 960 | 9863 | AngleInterval rarc(ymin_a, ymax_a, true), | |
| 961 | 9863 | larc(ymax_a, ymin_a, true); | |
| 962 | |||
| 963 | // we'll compute the result for an arc in the direction of increasing angles | ||
| 964 | // and then negate if necessary | ||
| 965 | 9863 | Angle ia = initialAngle(), fa = finalAngle(); | |
| 966 | 9863 | Point ip = _initial_point, fp = _final_point; | |
| 967 |
2/2✓ Branch 1 taken 6 times.
✓ Branch 2 taken 9857 times.
|
9863 | if (!sweep()) { |
| 968 | 6 | swap(ia, fa); | |
| 969 | 6 | swap(ip, fp); | |
| 970 | } | ||
| 971 | |||
| 972 | 9863 | bool const initial_left = larc.contains(ia); | |
| 973 | 9863 | bool const final_left = larc.contains(fa); | |
| 974 | |||
| 975 | 9863 | bool intersects_left = false, intersects_right = false; | |
| 976 |
4/4✓ Branch 0 taken 2153 times.
✓ Branch 1 taken 7710 times.
✓ Branch 2 taken 1060 times.
✓ Branch 3 taken 1093 times.
|
9863 | if (inside || left) { |
| 977 | // The point is inside the ellipse or to the left of it, so the rightwards horizontal ray | ||
| 978 | // may intersect the part of the arc contained in the right half of the ellipse. | ||
| 979 | // There are four ways in which this can happen. | ||
| 980 | |||
| 981 | 1060 | intersects_right = | |
| 982 | // Possiblity 1: the arc extends into the right half through the min-Y point | ||
| 983 | // and the ray intersects this extension: | ||
| 984 |
7/12✓ Branch 0 taken 1967 times.
✓ Branch 1 taken 2435 times.
✓ Branch 6 taken 1967 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 1967 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 1967 times.
✓ Branch 14 taken 6836 times.
✓ Branch 15 taken 1967 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
15172 | (includes_ymin && !final_left && Interval(ymin[Y], fp[Y]).lowerContains(p[Y])) |
| 985 | 6836 | || | |
| 986 | // Possibility 2: the arc starts and ends within the right half (hence, it cannot be the | ||
| 987 | // "large arc") and the ray's Y-coordinate is within the Y-coordinate range of the arc: | ||
| 988 |
4/16✓ Branch 0 taken 1938 times.
✓ Branch 1 taken 4898 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1938 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✓ Branch 20 taken 8803 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
|
15639 | (!initial_left && !final_left && !largeArc() && Interval(ip[Y], fp[Y]).lowerContains(p[Y])) |
| 989 | 6836 | || | |
| 990 | // Possibility 3: the arc starts in the right half and continues through the max-Y | ||
| 991 | // point into the left half: | ||
| 992 |
9/14✓ Branch 0 taken 1938 times.
✓ Branch 1 taken 4898 times.
✓ Branch 2 taken 1938 times.
✗ Branch 3 not taken.
✓ Branch 8 taken 1938 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 1938 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 2 times.
✓ Branch 15 taken 1936 times.
✓ Branch 16 taken 1938 times.
✓ Branch 17 taken 6865 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
|
15639 | (!initial_left && includes_ymax && Interval(ip[Y], ymax[Y]).lowerContains(p[Y])) |
| 993 |
4/4✓ Branch 0 taken 4402 times.
✓ Branch 1 taken 4401 times.
✓ Branch 2 taken 4898 times.
✓ Branch 3 taken 2 times.
|
22504 | || |
| 994 | // Possibility 4: the entire right half of the ellipse is contained in the arc. | ||
| 995 |
4/6✓ Branch 0 taken 4898 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2433 times.
✓ Branch 3 taken 2465 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2433 times.
|
4898 | (initial_left && final_left && includes_ymin && includes_ymax); |
| 996 | } | ||
| 997 |
4/4✓ Branch 0 taken 4899 times.
✓ Branch 1 taken 4964 times.
✓ Branch 2 taken 1093 times.
✓ Branch 3 taken 3806 times.
|
9863 | if (left && !inside) { |
| 998 | // The point is to the left of the ellipse, so the rightwards horizontal ray | ||
| 999 | // may intersect the part of the arc contained in the left half of the ellipse. | ||
| 1000 | // There are four ways in which this can happen. | ||
| 1001 | |||
| 1002 | 1093 | intersects_left = | |
| 1003 | // Possibility 1: the arc starts in the left half and continues through the min-Y | ||
| 1004 | // point into the right half: | ||
| 1005 |
7/12✓ Branch 0 taken 527 times.
✓ Branch 1 taken 1 times.
✓ Branch 6 taken 527 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 527 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 527 times.
✓ Branch 14 taken 566 times.
✓ Branch 15 taken 527 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
2148 | (includes_ymin && initial_left && Interval(ymin[Y], ip[Y]).lowerContains(p[Y])) |
| 1006 |
2/2✓ Branch 0 taken 565 times.
✓ Branch 1 taken 1 times.
|
566 | || |
| 1007 | // Possibility 2: the arc starts and ends within the left half (hence, it cannot be the | ||
| 1008 | // "large arc") and the ray's Y-coordinate is within the Y-coordinate range of the arc: | ||
| 1009 |
7/14✓ Branch 0 taken 565 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 565 times.
✗ Branch 4 not taken.
✓ Branch 9 taken 565 times.
✗ Branch 10 not taken.
✓ Branch 13 taken 565 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✓ Branch 16 taken 565 times.
✓ Branch 17 taken 565 times.
✓ Branch 18 taken 528 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
1658 | (initial_left && final_left && !largeArc() && Interval(ip[Y], fp[Y]).lowerContains(p[Y])) |
| 1010 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | || |
| 1011 | // Possibility 3: the arc extends into the left half through the max-Y point | ||
| 1012 | // and the ray intersects this extension: | ||
| 1013 |
6/12✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 1 times.
✓ Branch 15 taken 1092 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
|
1094 | (final_left && includes_ymax && Interval(fp[Y], ymax[Y]).lowerContains(p[Y])) |
| 1014 |
2/2✓ Branch 0 taken 528 times.
✓ Branch 1 taken 565 times.
|
2186 | || |
| 1015 | // Possibility 4: the entire left half of the ellipse is contained in the arc. | ||
| 1016 | ✗ | (!initial_left && !final_left && includes_ymin && includes_ymax); | |
| 1017 | |||
| 1018 | } | ||
| 1019 | 9863 | int const winding_assuming_increasing_angles = (int)intersects_right - (int)intersects_left; | |
| 1020 |
2/2✓ Branch 1 taken 9857 times.
✓ Branch 2 taken 6 times.
|
9863 | return sweep() ? winding_assuming_increasing_angles : -winding_assuming_increasing_angles; |
| 1021 | } | ||
| 1022 | |||
| 1023 | ✗ | std::ostream &operator<<(std::ostream &out, EllipticalArc const &ea) | |
| 1024 | { | ||
| 1025 | ✗ | out << "EllipticalArc(" | |
| 1026 | ✗ | << ea.initialPoint() << ", " | |
| 1027 | ✗ | << format_coord_nice(ea.ray(X)) << ", " << format_coord_nice(ea.ray(Y)) << ", " | |
| 1028 | ✗ | << format_coord_nice(ea.rotationAngle()) << ", " | |
| 1029 | ✗ | << "large_arc=" << (ea.largeArc() ? "true" : "false") << ", " | |
| 1030 | ✗ | << "sweep=" << (ea.sweep() ? "true" : "false") << ", " | |
| 1031 | ✗ | << ea.finalPoint() << ")"; | |
| 1032 | ✗ | return out; | |
| 1033 | } | ||
| 1034 | |||
| 1035 | } // end namespace Geom | ||
| 1036 | |||
| 1037 | /* | ||
| 1038 | Local Variables: | ||
| 1039 | mode:c++ | ||
| 1040 | c-file-style:"stroustrup" | ||
| 1041 | c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) | ||
| 1042 | indent-tabs-mode:nil | ||
| 1043 | fill-column:99 | ||
| 1044 | End: | ||
| 1045 | */ | ||
| 1046 | // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : | ||
| 1047 | |||
| 1048 |