Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
elliptical-arc.cpp
Go to the documentation of this file.
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>
40#include <2geom/path-sink.h>
42#include <2geom/transforms.h>
43#include <2geom/utils.h>
44
48
49namespace Geom
50{
51
107{
108 if (isChord()) {
109 return { _initial_point, _final_point };
110 }
111
112 if (_angles.isFull()) {
113 return _ellipse.boundsExact();
114 }
115
116 auto const trans = unitCircleTransform();
117
118 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.
123
124 auto const v = Point(trans[d], trans[d + 2]);
125 auto const r = v.length();
126 auto const mid = trans[d + 4];
127 auto const angle = Angle(v);
128
129 if (_angles.contains(angle)) {
130 result.expandTo(mid + r);
131 }
132 if (_angles.contains(angle + M_PI)) {
133 result.expandTo(mid - r);
134 }
135
136 return result;
137 };
138
139 return { proj_bounds(X), proj_bounds(Y) };
140}
141
142void EllipticalArc::expandToTransformed(Rect &bbox, Affine const &transform) const
143{
145
146 if (isChord()) {
147 return;
148 }
149
150 auto const trans = unitCircleTransform() * transform;
151
152 for (auto d : { X, Y }) {
153 // See boundsExact() for explanation.
154 auto const v = Point(trans[d], trans[d + 2]);
155 auto const r = v.length();
156 auto const mid = trans[d + 4];
157 auto const interval = Interval(mid - r, mid + r);
158
159 if (_angles.isFull()) {
160 bbox[d].unionWith(interval);
161 } else if (!bbox[d].contains(interval)) {
162 auto const angle = Angle(v);
163 if (_angles.contains(angle)) {
164 bbox[d].expandTo(mid + r);
165 }
166 if (_angles.contains(angle + M_PI)) {
167 bbox[d].expandTo(mid - r);
168 }
169 }
170 }
171}
172
174{
175 Point ret = _ellipse.pointAt(t);
176 return ret;
177}
178
180{
181 return _ellipse.valueAt(t, d);
182}
183
184std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const
185{
186 std::vector<Coord> sol;
187
188 if (isChord()) {
189 sol = chord().roots(v, d);
190 return sol;
191 }
192
193 Interval unit_interval(0, 1);
194
195 double rotx, roty;
196 if (d == X) {
197 sincos(rotationAngle(), roty, rotx);
198 roty = -roty;
199 } else {
200 sincos(rotationAngle(), rotx, roty);
201 }
202
203 double rxrotx = ray(X) * rotx;
204 double c_v = center(d) - v;
205
206 double a = -rxrotx + c_v;
207 double b = ray(Y) * roty;
208 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 if (a == 0)
214 {
215 sol.push_back(M_PI);
216 if (b != 0)
217 {
218 double s = 2 * std::atan(-c/(2*b));
219 if ( s < 0 ) s += 2*M_PI;
220 sol.push_back(s);
221 }
222 }
223 else
224 {
225 double delta = b * b - a * c;
226 //std::cerr << "delta = " << delta << std::endl;
227 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 else if ( delta > 0 )
233 {
234 double sq = std::sqrt(delta);
235 double s = 2 * std::atan( (-b - sq) / a );
236 if ( s < 0 ) s += 2*M_PI;
237 sol.push_back(s);
238 s = 2 * std::atan( (-b + sq) / a );
239 if ( s < 0 ) s += 2*M_PI;
240 sol.push_back(s);
241 }
242 }
243
244 std::vector<double> arc_sol;
245 for (double & i : sol) {
246 //std::cerr << "s = " << deg_from_rad(sol[i]);
247 i = timeAtAngle(i);
248 //std::cerr << " -> t: " << sol[i] << std::endl;
249 if (unit_interval.contains(i)) {
250 arc_sol.push_back(i);
251 }
252 }
253 return arc_sol;
254}
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
262{
263 if (isChord()) {
264 return chord().derivative();
265 }
266
267 EllipticalArc *result = static_cast<EllipticalArc*>(duplicate());
269 result->_angles.setInitial(result->_angles.initialAngle() + M_PI/2);
270 result->_angles.setFinal(result->_angles.finalAngle() + M_PI/2);
271 result->_initial_point = result->pointAtAngle( result->initialAngle() );
272 result->_final_point = result->pointAtAngle( result->finalAngle() );
273 return result;
274}
275
276
277std::vector<Point>
279{
280 if (isChord()) {
281 return chord().pointAndDerivatives(t, n);
282 }
283
284 unsigned int nn = n+1; // nn represents the size of the result vector.
285 std::vector<Point> result;
286 result.reserve(nn);
287 double angle = angleAt(t);
288 std::unique_ptr<EllipticalArc> ea( static_cast<EllipticalArc*>(duplicate()) );
289 ea->_ellipse.setCenter(0, 0);
290 unsigned int m = std::min(nn, 4u);
291 for ( unsigned int i = 0; i < m; ++i )
292 {
293 result.push_back( ea->pointAtAngle(angle) );
294 angle += (sweep() ? M_PI/2 : -M_PI/2);
295 if ( !(angle < 2*M_PI) ) angle -= 2*M_PI;
296 }
297 m = nn / 4;
298 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 m = nn - 4 * m;
304 for ( unsigned int i = 0; i < m; ++i )
305 {
306 result.push_back( result[i] );
307 }
308 if ( !result.empty() ) // nn != 0
309 result[0] = pointAtAngle(angle);
310 return result;
311}
312
314{
315 if (t == 0.0) {
316 return initialPoint();
317 }
318 if (t == 1.0) {
319 return finalPoint();
320 }
321 if (isChord()) {
322 return chord().pointAt(t);
323 }
324 return _ellipse.pointAt(angleAt(t));
325}
326
328{
329 if (isChord()) return chord().valueAt(t, d);
330 return valueAtAngle(angleAt(t), d);
331}
332
333Curve* EllipticalArc::portion(double f, double t) const
334{
335 // fix input arguments
336 f = std::clamp(f, 0.0, 1.0);
337 t = std::clamp(t, 0.0, 1.0);
338
339 if (f == t) {
340 EllipticalArc *arc = new EllipticalArc();
341 arc->_initial_point = arc->_final_point = pointAt(f);
342 return arc;
343 }
344 if (f == 0.0 && t == 1.0) {
345 return duplicate();
346 }
347 if (f == 1.0 && t == 0.0) {
348 return reverse();
349 }
350
351 EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate());
352 arc->_initial_point = pointAt(f);
353 arc->_final_point = pointAt(t);
354 arc->_angles.setAngles(angleAt(f), angleAt(t));
355 if (f > t) arc->_angles.setSweep(!sweep());
356 if ( _large_arc && fabs(angularExtent() * (t-f)) <= M_PI) {
357 arc->_large_arc = false;
358 }
359 return arc;
360}
361
362// the arc is the same but traversed in the opposite direction
364{
365 using std::swap;
366 EllipticalArc *rarc = static_cast<EllipticalArc*>(duplicate());
367 rarc->_angles.reverse();
368 swap(rarc->_initial_point, rarc->_final_point);
369 return rarc;
370}
371
372#ifdef HAVE_GSL // GSL is required for function "solve_reals"
373std::vector<double> EllipticalArc::allNearestTimes( Point const& p, double from, double to ) const
374{
375 std::vector<double> result;
376
377 if ( from > to ) std::swap(from, to);
378 if ( from < 0 || to > 1 )
379 {
380 THROW_RANGEERROR("[from,to] interval out of range");
381 }
382
383 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 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 else if ( are_near(ray(X), ray(Y)) )
419 {
420 Point r = p - center();
421 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 Point p_c = p - center();
464 double rx2_ry2 = (ray(X) - ray(Y)) * (ray(X) + ray(Y));
465 double sinrot, cosrot;
466 sincos(rotationAngle(), sinrot, cosrot);
467 double expr1 = ray(X) * (p_c[X] * cosrot + p_c[Y] * sinrot);
468 Poly coeff;
469 coeff.resize(5);
470 coeff[4] = ray(Y) * ( p_c[Y] * cosrot - p_c[X] * sinrot );
471 coeff[3] = 2 * ( rx2_ry2 + expr1 );
472 coeff[2] = 0;
473 coeff[1] = 2 * ( -rx2_ry2 + expr1 );
474 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 std::vector<double> real_sol;
480 // gsl_poly_complex_solve raises an error
481 // if the leading coefficient is zero
482 if ( are_near(coeff[4], 0) )
483 {
484 real_sol.push_back(0);
485 if ( !are_near(coeff[3], 0) )
486 {
487 double sq = -coeff[1] / coeff[3];
488 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 real_sol = solve_reals(coeff);
499 }
500
501 for (double & i : real_sol)
502 {
503 i = 2 * std::atan(i);
504 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 if ( (real_sol.size() % 2) != 0 )
509 {
510 real_sol.push_back(M_PI);
511 }
512
513 double mindistsq1 = std::numeric_limits<double>::max();
514 double mindistsq2 = std::numeric_limits<double>::max();
515 double dsq = 0;
516 unsigned int mi1 = 0, mi2 = 0;
517 for ( unsigned int i = 0; i < real_sol.size(); ++i )
518 {
519 dsq = distanceSq(p, pointAtAngle(real_sol[i]));
520 if ( mindistsq1 > dsq )
521 {
522 mindistsq2 = mindistsq1;
523 mi2 = mi1;
524 mindistsq1 = dsq;
525 mi1 = i;
526 }
527 else if ( mindistsq2 > dsq )
528 {
529 mindistsq2 = dsq;
530 mi2 = i;
531 }
532 }
533
534 double t = timeAtAngle(real_sol[mi1]);
535 if ( !(t < from || t > to) )
536 {
537 result.push_back(t);
538 }
539
540 bool second_sol = false;
541 t = timeAtAngle(real_sol[mi2]);
542 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 double dsq1 = distanceSq(p, pointAt(from));
553 double dsq2 = distanceSq(p, pointAt(to));
554 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 if ( result.empty() )
580 {
581 if ( are_near(dsq1, dsq2) )
582 {
583 result.push_back(from);
584 result.push_back(to);
585 }
586 else if ( dsq2 > dsq1 )
587 {
588 result.push_back(from);
589 }
590 else
591 {
592 result.push_back(to);
593 }
594 }
595 }
596
597 return result;
598}
599#endif
600
604std::vector<ShapeIntersection> EllipticalArc::_filterIntersections(std::vector<ShapeIntersection> &&xs,
605 bool is_first) const
606{
607 std::vector<ShapeIntersection> result;
608 result.reserve(xs.size());
609 for (auto &xing : xs) {
610 if (_validateIntersection(xing, is_first)) {
611 result.emplace_back(std::move(xing));
612 }
613 }
614 return result;
615}
616
627{
628 static auto const UNIT_INTERVAL = Interval(0, 1);
629 constexpr auto EPS = 1e-4;
630
631 Coord &t = is_first ? xing.first : xing.second;
632 if (!are_near_rel(_ellipse.pointAt(t), xing.point(), EPS)) {
633 return false;
634 }
635
636 t = timeAtAngle(t);
637 if (!UNIT_INTERVAL.contains(t)) {
638 return false;
639 }
640 if (!are_near_rel(pointAt(t), xing.point(), EPS)) {
641 return false;
642 }
643 return true;
644}
645
646std::vector<CurveIntersection> EllipticalArc::intersect(Curve const &other, Coord eps) const
647{
648 if (isLineSegment()) {
650 return ls.intersect(other, eps);
651 }
652
653 if (other.isLineSegment()) {
654 LineSegment ls(other.initialPoint(), other.finalPoint());
655 return _filterIntersections(_ellipse.intersect(ls), true);
656 }
657
658 if (auto bez = dynamic_cast<BezierCurve const *>(&other)) {
659 return _filterIntersections(_ellipse.intersect(bez->fragment()), true);
660 }
661
662 if (auto arc = dynamic_cast<EllipticalArc const *>(&other)) {
663 std::vector<CurveIntersection> crossings;
664 try {
665 crossings = _ellipse.intersect(arc->_ellipse);
666 } catch (InfinitelyManySolutions &) {
667 // This could happen if the two arcs come from the same ellipse.
668 return _intersectSameEllipse(arc);
669 }
670 return arc->_filterIntersections(_filterIntersections(std::move(crossings), true), false);
671 }
672
673 // in case someone wants to make a custom curve type
674 auto result = other.intersect(*this, eps);
676 return result;
677}
678
686std::vector<ShapeIntersection> EllipticalArc::_intersectSameEllipse(EllipticalArc const *other) const
687{
688 assert(_ellipse == other->_ellipse);
689 auto const &other_angles = other->angularInterval();
690 std::vector<ShapeIntersection> result;
691
693 auto const synthesize_intersection = [&](Angle angle) {
694 auto const time = timeAtAngle(angle);
695 if (result.end() == std::find_if(result.begin(), result.end(),
696 [=](ShapeIntersection const &xing) -> bool {
697 return xing.first == time;
698 }))
699 {
700 result.emplace_back(time, other->timeAtAngle(angle), _ellipse.pointAt(angle));
701 }
702 };
703
704 for (auto a : {_angles.initialAngle(), _angles.finalAngle()}) {
705 if (other_angles.contains(a)) {
706 synthesize_intersection(a);
707 }
708 }
709 for (auto a : {other_angles.initialAngle(), other_angles.finalAngle()}) {
710 if (_angles.contains(a)) {
711 synthesize_intersection(a);
712 }
713 }
714 return result;
715}
716
718{
719 // See: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
720 Point d = initialPoint() - finalPoint();
722
723 auto degenerate_ellipse = [&] {
724 _ellipse = Ellipse();
727 _large_arc = false;
728 };
729
730 // if ip = fp, the arc contains no other points
731 if (initialPoint() == finalPoint()) {
732 degenerate_ellipse();
733 return;
734 }
735
736 // rays should be positive
737 _ellipse.setRays(std::fabs(ray(X)), std::fabs(ray(Y)));
738
739 if (isChord()) {
740 _ellipse.setRays(L2(d) / 2, 0);
742 _ellipse.setCenter(mid);
743 _angles.setAngles(0, M_PI);
744 _angles.setSweep(false);
745 _large_arc = false;
746 return;
747 }
748
749 Rotate rot(rotationAngle()); // the matrix in F.6.5.3
750 Rotate invrot = rot.inverse(); // the matrix in F.6.5.1
751
752 Point r = rays();
753 Point p = d / 2 * invrot; // x', y' in F.6.5.1
754 Point c(0,0); // cx', cy' in F.6.5.2
755
756 // Correct out-of-range radii
757 Coord lambda = hypot(p[X]/r[X], p[Y]/r[Y]);
758 if (lambda > 1) {
759 r *= lambda;
760 _ellipse.setRays(r);
761 _ellipse.setCenter(mid);
762 } else {
763 // evaluate F.6.5.2
764 Coord rxry = r[X]*r[X] * r[Y]*r[Y];
765 Coord pxry = p[X]*p[X] * r[Y]*r[Y];
766 Coord rxpy = r[X]*r[X] * p[Y]*p[Y];
767 Coord const denominator = rxpy + pxry;
768 if (denominator == 0.0) {
769 degenerate_ellipse();
770 return;
771 }
772 Coord rad = (rxry - pxry - rxpy) / denominator;
773 // normally rad should never be negative, but numerical inaccuracy may cause this
774 if (rad > 0) {
775 rad = std::sqrt(rad);
776 if (sweep() == _large_arc) {
777 rad = -rad;
778 }
779 c = rad * Point(r[X]*p[Y]/r[Y], -r[Y]*p[X]/r[X]);
780 _ellipse.setCenter(c * rot + mid);
781 } else {
782 _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 Point sp((p[X] - c[X]) / r[X], (p[Y] - c[Y]) / r[Y]);
789 Point ep((-p[X] - c[X]) / r[X], (-p[Y] - c[Y]) / r[Y]);
790 Point v(1, 0);
791
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
801{
802 if (isChord()) {
803 return chord().toSBasis();
804 }
805
806 D2<SBasis> arc;
807 // the interval of parametrization has to be [0,1]
809 Linear param(initialAngle().radians(), et);
810 Coord cosrot, sinrot;
811 sincos(rotationAngle(), sinrot, cosrot);
812
813 // order = 4 seems to be enough to get a perfect looking elliptical arc
814 SBasis arc_x = ray(X) * cos(param,4);
815 SBasis arc_y = ray(Y) * sin(param,4);
816 arc[0] = arc_x * cosrot - arc_y * sinrot + Linear(center(X), center(X));
817 arc[1] = arc_x * sinrot + arc_y * cosrot + Linear(center(Y), center(Y));
818
819 // ensure that endpoints remain exact
820 for ( int d = 0 ; d < 2 ; d++ ) {
821 arc[d][0][0] = initialPoint()[d];
822 arc[d][0][1] = finalPoint()[d];
823 }
824
825 return arc;
826}
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
833{
834 _initial_point *= tr;
835 _final_point *= tr;
836 _ellipse *= tr;
837}
838
840{
841 _initial_point *= s;
842 _final_point *= s;
843 _ellipse *= s;
844}
845
847{
848 _initial_point *= r;
849 _final_point *= r;
850 _ellipse *= r;
851}
852
854{
855 _initial_point *= z;
856 _final_point *= z;
857 _ellipse *= z;
858}
859
861{
862 if (isChord()) {
863 _initial_point *= m;
864 _final_point *= m;
866 _ellipse.setRays(0, 0);
868 return;
869 }
870
871 _initial_point *= m;
872 _final_point *= m;
873 _ellipse *= m;
874 if (m.det() < 0) {
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.
883}
884
886{
887 if (this == &c) return true;
888 auto other = dynamic_cast<EllipticalArc const *>(&c);
889 if (!other) return false;
890 if (_initial_point != other->_initial_point) return false;
891 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 if (rays() != other->rays()) return false;
895 if (rotationAngle() != other->rotationAngle()) return false;
896 if (_large_arc != other->_large_arc) return false;
897 if (sweep() != other->sweep()) return false;
898 return true;
899}
900
901bool EllipticalArc::isNear(Curve const &c, Coord precision) const
902{
903 EllipticalArc const *other = dynamic_cast<EllipticalArc const *>(&c);
904 if (!other) {
905 if (isChord()) {
906 return c.isNear(chord(), precision);
907 }
908 return false;
909 }
910
911 if (!are_near(_initial_point, other->_initial_point, precision)) return false;
912 if (!are_near(_final_point, other->_final_point, precision)) return false;
913 if (isChord() && other->isChord()) return true;
914
915 if (sweep() != other->sweep()) return false;
916 if (!are_near(_ellipse, other->_ellipse, precision)) return false;
917 return true;
918}
919
920void EllipticalArc::feed(PathSink &sink, bool moveto_initial) const
921{
922 if (moveto_initial) {
924 }
926}
927
928int EllipticalArc::winding(Point const &p) const
929{
930 using std::swap;
931
932 double sinrot, cosrot;
933 sincos(rotationAngle(), sinrot, cosrot);
934
935 Angle ymin_a = std::atan2( ray(Y) * cosrot, ray(X) * sinrot );
936 Angle ymax_a = ymin_a + M_PI;
937
938 Point ymin = pointAtAngle(ymin_a);
939 Point ymax = pointAtAngle(ymax_a);
940 if (ymin[Y] > ymax[Y]) {
941 swap(ymin, ymax);
942 swap(ymin_a, ymax_a);
943 }
944
945 if (!Interval(ymin[Y], ymax[Y]).lowerContains(p[Y])) {
946 return 0;
947 }
948
949 bool const left = cross(ymax - ymin, p - ymin) > 0;
950 bool const inside = _ellipse.contains(p);
951 if (_angles.isFull()) {
952 if (inside) {
953 return sweep() ? 1 : -1;
954 }
955 return 0;
956 }
957 bool const includes_ymin = _angles.contains(ymin_a);
958 bool const includes_ymax = _angles.contains(ymax_a);
959
960 AngleInterval rarc(ymin_a, ymax_a, true),
961 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 Angle ia = initialAngle(), fa = finalAngle();
967 if (!sweep()) {
968 swap(ia, fa);
969 swap(ip, fp);
970 }
971
972 bool const initial_left = larc.contains(ia);
973 bool const final_left = larc.contains(fa);
974
975 bool intersects_left = false, intersects_right = false;
976 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 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 (includes_ymin && !final_left && Interval(ymin[Y], fp[Y]).lowerContains(p[Y]))
985 ||
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 (!initial_left && !final_left && !largeArc() && Interval(ip[Y], fp[Y]).lowerContains(p[Y]))
989 ||
990 // Possibility 3: the arc starts in the right half and continues through the max-Y
991 // point into the left half:
992 (!initial_left && includes_ymax && Interval(ip[Y], ymax[Y]).lowerContains(p[Y]))
993 ||
994 // Possibility 4: the entire right half of the ellipse is contained in the arc.
995 (initial_left && final_left && includes_ymin && includes_ymax);
996 }
997 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 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 (includes_ymin && initial_left && Interval(ymin[Y], ip[Y]).lowerContains(p[Y]))
1006 ||
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 (initial_left && final_left && !largeArc() && Interval(ip[Y], fp[Y]).lowerContains(p[Y]))
1010 ||
1011 // Possibility 3: the arc extends into the left half through the max-Y point
1012 // and the ray intersects this extension:
1013 (final_left && includes_ymax && Interval(fp[Y], ymax[Y]).lowerContains(p[Y]))
1014 ||
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 int const winding_assuming_increasing_angles = (int)intersects_right - (int)intersects_left;
1020 return sweep() ? winding_assuming_increasing_angles : -winding_assuming_increasing_angles;
1021}
1022
1023std::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
Various utility functions.
Bezier curve.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord det() const
Calculate the determinant.
Definition affine.cpp:416
Directed angular interval.
Definition angle.h:188
void setSweep(bool s)
Set whether the interval goes in the direction of increasing angles.
Definition angle.h:272
void setAngles(Angle s, Angle e, bool prefer_full=false)
Set both angles at once.
Definition angle.h:266
Angle finalAngle() const
Get the end angle.
Definition angle.h:231
void setInitial(Angle a, bool prefer_full=false)
Set the initial angle.
Definition angle.h:247
bool isFull() const
Check whether the interval contains all angles.
Definition angle.h:239
void reverse()
Reverse the direction of the interval while keeping contained values the same.
Definition angle.h:275
Angle initialAngle() const
Get the start angle.
Definition angle.h:229
bool contains(Angle a) const
Check whether the interval includes the given angle.
Definition angle.h:326
void setFinal(Angle a, bool prefer_full=false)
Set the final angle.
Definition angle.h:256
Wrapper for angular values.
Definition angle.h:73
Coord radians() const
Get the angle as radians.
Definition angle.h:107
std::vector< CurveIntersection > intersect(Curve const &other, Coord eps=EPSILON) const override
Compute intersections with another curve.
Coord nearestTime(Point const &p, Coord from=0, Coord to=1) const override
Compute a time value at which the curve comes closest to a specified point.
Curve * derivative() const override
Create a derivative of this curve.
Two-dimensional Bezier curve of arbitrary order.
D2< SBasis > toSBasis() const override
Convert the curve to a symmetric power basis polynomial.
Coord valueAt(Coord t, Dim2 d) const override
Evaluate one of the coordinates at the specified time value.
Point pointAt(Coord t) const override
Evaluate the curve at a specified time value.
std::vector< Coord > roots(Coord v, Dim2 d) const override
Computes time values at which the curve intersects an axis-aligned line.
std::vector< Point > pointAndDerivatives(Coord t, unsigned n) const override
Evaluate the curve and its derivatives.
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
virtual Point initialPoint() const =0
Retrieve the start of the curve.
virtual Point finalPoint() const =0
Retrieve the end of the curve.
virtual std::vector< CurveIntersection > intersect(Curve const &other, Coord eps=EPSILON) const
Compute intersections with another curve.
Definition curve.cpp:97
virtual bool isLineSegment() const
Check whether the curve is a line segment.
Definition curve.h:98
void transform(Affine const &m)
Transform this curve by an affine transformation.
Definition curve.h:193
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
Set of points with a constant sum of distances from two foci.
Definition ellipse.h:68
Coord valueAt(Coord t, Dim2 d) const
Evaluate a single coordinate of a point on the ellipse.
Definition ellipse.cpp:365
void setRays(Point const &p)
Set both rays of the ellipse.
Definition ellipse.h:111
void setRotationAngle(Angle a)
Set the angle the X ray makes with the +X axis.
Definition ellipse.h:117
bool contains(Point const &p) const
Check whether the ellipse contains the given point.
Definition ellipse.cpp:406
Coord timeAt(Point const &p) const
Find the time value of a point on an ellipse.
Definition ellipse.cpp:382
std::vector< ShapeIntersection > intersect(Line const &line) const
Compute intersections with an infinite line.
Definition ellipse.cpp:460
void setCenter(Point const &p)
Set the center.
Definition ellipse.h:107
Point pointAt(Coord t) const
Evaluate a point on the ellipse.
Definition ellipse.cpp:358
Rect boundsExact() const
Get the tight-fitting bounding box of the ellipse.
Definition ellipse.cpp:146
Elliptical arc curve.
Coord timeAtAngle(Angle a) const
Compute the curve time value corresponding to the given angular value.
Angle initialAngle() const
std::vector< double > roots(double v, Dim2 d) const override
Computes time values at which the curve intersects an axis-aligned line.
D2< SBasis > toSBasis() const override
Convert the curve to a symmetric power basis polynomial.
bool isLineSegment() const override
Check whether the curve is a line segment.
Point pointAt(Coord t) const override
Evaluate the arc in the curve domain, i.e. .
AngleInterval _angles
AngleInterval angularInterval() const
Get the angular interval of the arc.
Point finalPoint() const override
Retrieve the end of the curve.
Coord valueAt(Coord t, Dim2 d) const override
Evaluate a single coordinate on the arc in the curve domain.
Curve * reverse() const override
Create a reversed version of this curve.
std::vector< Point > pointAndDerivatives(Coord t, unsigned int n) const override
int winding(Point const &p) const override
Compute the partial winding number of this curve.
Rect boundsExact() const override
Compute bounds of an elliptical arc.
Curve * duplicate() const override
Create an exact copy of this curve.
void feed(PathSink &sink, bool moveto_initial) const override
Feed the curve to a PathSink.
bool largeArc() const
Whether the arc is larger than half an ellipse.
Point initialPoint() const override
Retrieve the start of the curve.
Coord angularExtent() const
Get the elliptical angle spanned by the arc.
LineSegment chord() const
Get the line segment connecting the arc's endpoints.
Angle rotationAngle() const
Get the defining ellipse's rotation.
bool _validateIntersection(ShapeIntersection &xing, bool is_first) const
Convert the passed intersection to curve time and check whether the intersection is numerically sane.
std::vector< ShapeIntersection > _intersectSameEllipse(EllipticalArc const *other) const
Check if two arcs on the same ellipse intersect/overlap.
bool isChord() const
Check whether both rays are nonzero.
void expandToTransformed(Rect &bbox, Affine const &transform) const override
Expand the given rectangle to include the transformed curve, assuming it already contains its initial...
Point rays() const
Get both rays as a point.
Affine unitCircleTransform() const
Compute a transform that maps the unit circle to the arc's ellipse.
std::vector< CurveIntersection > intersect(Curve const &other, Coord eps=EPSILON) const override
Compute intersections with another curve.
Curve * derivative() const override
Create a derivative of this curve.
Angle angleAt(Coord t) const
Compute the angular domain value corresponding to the given time value.
void operator*=(Translate const &tr) override
Point pointAtAngle(Coord t) const
Evaluate the arc at the specified angular coordinate.
EllipticalArc()
Creates an arc with all variables set to zero.
Coord ray(Dim2 d) const
Get one of the ellipse's rays.
Coord sweepAngle() const
Compute the amount by which the angle parameter changes going from start to end.
bool _equalTo(Curve const &c) const override
bool sweep() const
Whether the arc turns clockwise.
Point center() const
Get the arc's center.
Coord valueAtAngle(Coord t, Dim2 d) const
Evaluate one of the arc's coordinates at the specified angle.
Angle finalAngle() const
std::vector< ShapeIntersection > _filterIntersections(std::vector< ShapeIntersection > &&xs, bool is_first) const
Convert the passed intersections to curve time parametrization and filter out any invalid intersectio...
Curve * portion(double f, double t) const override
Create a curve that corresponds to a part of this curve.
std::vector< double > allNearestTimes(Point const &p, double from=0, double to=1) const override
Compute time values at which the curve comes closest to a specified point.
bool isNear(Curve const &other, Coord precision) const override
Test whether two curves are approximately the same.
constexpr bool contains(C val) const
Check whether the interval includes this number.
void expandTo(CPoint const &p)
Enlarge the rectangle to contain the given point.
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
Intersection between two shapes.
TimeB second
Second shape and time value.
Point point() const
Intersection point, as calculated by the intersection algorithm.
TimeA first
First shape and time value.
Range of real numbers that is never empty.
Definition interval.h:59
constexpr bool lowerContains(Coord val) const
Check whether the number is contained in the union of the interior and the lower boundary.
Definition interval.h:124
Function that interpolates linearly between two values.
Definition linear.h:55
Callback interface for processing path data.
Definition path-sink.h:56
virtual void arcTo(Coord rx, Coord ry, Coord angle, bool large_arc, bool sweep, Point const &p)=0
Output an elliptical arc segment.
virtual void moveTo(Point const &p)=0
Move to a different point without creating a segment.
Two-dimensional point that doubles as a vector.
Definition point.h:66
Polynomial in canonical (monomial) basis.
Definition polynomial.h:50
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotation around the origin.
Definition transforms.h:187
Rotate inverse() const
Definition transforms.h:209
Polynomial in symmetric power basis.
Definition sbasis.h:70
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
Combination of a translation and uniform scale.
Definition transforms.h:292
Css & result
double c[8][4]
Ellipse shape.
Elliptical arc curve.
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Various utility functions.
Definition affine.h:22
SBasisN< n > cos(LinearN< n > bo, int k)
void sincos(double angle, double &sin_, double &cos_)
Simultaneously compute a sine and a cosine of the same angle.
Definition math-utils.h:89
std::ostream & operator<<(std::ostream &os, const Bezier &b)
Definition bezier.h:372
bool are_near_rel(Point const &a, Point const &b, double eps=EPSILON)
Test whether the relative distance between two points is less than some threshold.
Definition point.h:409
void transpose_in_place(std::vector< Intersection< T, T > > &xs)
Coord distanceSq(Point const &p, Rect const &rect)
Definition rect.cpp:158
double atan2(Point const &p)
double angle_between(Line const &l1, Line const &l2)
Definition line.h:456
bool contains(Path const &p, Point const &i, bool evenodd=true)
std::vector< double > solve_reals(const Poly &p)
std::string format_coord_nice(Coord x)
Definition coord.cpp:89
Crossings crossings(Curve const &a, Curve const &b)
Piecewise< SBasis > cross(Piecewise< D2< SBasis > > const &a, Piecewise< D2< SBasis > > const &b)
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
SBasisN< n > sin(LinearN< n > bo, int k)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Point middle_point(LineSegment const &_segment)
callback interface for SVG path data
two-dimensional geometric operators.
int delta
Affine transformation classes.