Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
ellipse.cpp
Go to the documentation of this file.
1/*
4 * Authors:
5 * Marco Cecchetti <mrcekets at gmail.com>
6 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
7 *
8 * Copyright 2008-2014 Authors
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it either under the terms of the GNU Lesser General Public
12 * License version 2.1 as published by the Free Software Foundation
13 * (the "LGPL") or, at your option, under the terms of the Mozilla
14 * Public License Version 1.1 (the "MPL"). If you do not alter this
15 * notice, a recipient may use your version of this file under either
16 * the MPL or the LGPL.
17 *
18 * You should have received a copy of the LGPL along with this library
19 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * You should have received a copy of the MPL along with this library
22 * in the file COPYING-MPL-1.1
23 *
24 * The contents of this file are subject to the Mozilla Public License
25 * Version 1.1 (the "License"); you may not use this file except in
26 * compliance with the License. You may obtain a copy of the License at
27 * http://www.mozilla.org/MPL/
28 *
29 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
30 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
31 * the specific language governing rights and limitations.
32 */
33
34#include <2geom/conicsec.h>
35#include <2geom/ellipse.h>
39
40namespace Geom {
41
43 : _center(c.center())
44 , _rays(c.radius(), c.radius())
45 , _angle(0)
46{}
47
48void Ellipse::setCoefficients(double A, double B, double C, double D, double E, double F)
49{
50 double den = 4*A*C - B*B;
51 if (den == 0) {
52 THROW_RANGEERROR("den == 0, while computing ellipse centre");
53 }
54 _center[X] = (B*E - 2*C*D) / den;
55 _center[Y] = (B*D - 2*A*E) / den;
56
57 // evaluate the a coefficient of the ellipse equation in normal form
58 // E(x,y) = a*(x-cx)^2 + b*(x-cx)*(y-cy) + c*(y-cy)^2 = 1
59 // where b = a*B , c = a*C, (cx,cy) == centre
60 double num = A * sqr(_center[X])
61 + B * _center[X] * _center[Y]
62 + C * sqr(_center[Y])
63 - F;
64
65
66 //evaluate ellipse rotation angle
67 _angle = std::atan2( -B, -(A - C) )/2;
68
69 // evaluate the length of the ellipse rays
70 double sinrot, cosrot;
71 sincos(_angle, sinrot, cosrot);
72 double cos2 = cosrot * cosrot;
73 double sin2 = sinrot * sinrot;
74 double cossin = cosrot * sinrot;
75
76 den = A * cos2 + B * cossin + C * sin2;
77 if (den == 0) {
78 THROW_RANGEERROR("den == 0, while computing 'rx' coefficient");
79 }
80 double rx2 = num / den;
81 if (rx2 < 0) {
82 THROW_RANGEERROR("rx2 < 0, while computing 'rx' coefficient");
83 }
84 _rays[X] = std::sqrt(rx2);
85
86 den = C * cos2 - B * cossin + A * sin2;
87 if (den == 0) {
88 THROW_RANGEERROR("den == 0, while computing 'ry' coefficient");
89 }
90 double ry2 = num / den;
91 if (ry2 < 0) {
92 THROW_RANGEERROR("ry2 < 0, while computing 'rx' coefficient");
93 }
94 _rays[Y] = std::sqrt(ry2);
95
96 // the solution is not unique so we choose always the ellipse
97 // with a rotation angle between 0 and PI/2
99}
100
102{
103 Coord sinrot, cosrot;
104 sincos(_angle, sinrot, cosrot);
105 Point p(ray(X) * cosrot + center(X), ray(X) * sinrot + center(Y));
106 return p;
107}
108
109
111{
112 Affine ret = Scale(ray(X), ray(Y)) * Rotate(_angle);
113 ret.setTranslation(center());
114 return ret;
115}
116
118{
119 if (ray(X) == 0 || ray(Y) == 0) {
120 THROW_RANGEERROR("a degenerate ellipse doesn't have an inverse unit circle transform");
121 }
122 Affine ret = Translate(-center()) * Rotate(-_angle) * Scale(1/ray(X), 1/ray(Y));
123 return ret;
124}
125
126
128{
129 Point a(0, 0), b(0, 0);
130 a[d] = -1;
131 b[d] = 1;
132 LineSegment ls(a, b);
134 return ls;
135}
136
138{
139 Point a(0, 0), b(0, 0);
140 b[d] = sgn(sign);
141 LineSegment ls(a, b);
143 return ls;
144}
145
147{
148 auto const trans = unitCircleTransform();
149
150 auto proj_bounds = [&] (Dim2 d) {
151 // The dth coordinate function pulls back to trans[d] * x + trans[d + 2] * y + trans[d + 4]
152 // in the coordinate system where the ellipse is a unit circle. We compute its range of
153 // values on the unit circle.
154 auto const r = std::hypot(trans[d], trans[d + 2]);
155 auto const mid = trans[d + 4];
156 return Interval(mid - r, mid + r);
157 };
158
159 return { proj_bounds(X), proj_bounds(Y) };
160}
161
163{
164 // Every ellipse is contained in the circle with the same center and radius
165 // equal to the larger of the two rays. We return the bounding square
166 // of this circle (this is really fast but only exact for circles).
167 auto const larger_ray = (ray(X) > ray(Y) ? ray(X) : ray(Y));
168 assert(larger_ray >= 0.0);
169 auto const rr = Point(larger_ray, larger_ray);
170 return Rect(_center - rr, _center + rr);
171}
172
173std::vector<double> Ellipse::coefficients() const
174{
175 std::vector<double> c(6);
176 coefficients(c[0], c[1], c[2], c[3], c[4], c[5]);
177 return c;
178}
179
180void Ellipse::coefficients(Coord &A, Coord &B, Coord &C, Coord &D, Coord &E, Coord &F) const
181{
182 if (ray(X) == 0 || ray(Y) == 0) {
183 THROW_RANGEERROR("a degenerate ellipse doesn't have an implicit form");
184 }
185
186 double cosrot, sinrot;
187 sincos(_angle, sinrot, cosrot);
188 double cos2 = cosrot * cosrot;
189 double sin2 = sinrot * sinrot;
190 double cossin = cosrot * sinrot;
191 double invrx2 = 1 / (ray(X) * ray(X));
192 double invry2 = 1 / (ray(Y) * ray(Y));
193
194 A = invrx2 * cos2 + invry2 * sin2;
195 B = 2 * (invrx2 - invry2) * cossin;
196 C = invrx2 * sin2 + invry2 * cos2;
197 D = -2 * A * center(X) - B * center(Y);
198 E = -2 * C * center(Y) - B * center(X);
199 F = A * center(X) * center(X)
200 + B * center(X) * center(Y)
201 + C * center(Y) * center(Y)
202 - 1;
203}
204
205
206void Ellipse::fit(std::vector<Point> const &points)
207{
208 size_t sz = points.size();
209 if (sz < 5) {
210 THROW_RANGEERROR("fitting error: too few points passed");
211 }
212 NL::LFMEllipse model;
214
215 for (size_t i = 0; i < sz; ++i) {
216 fitter.append(points[i]);
217 }
218 fitter.update();
219
220 NL::Vector z(sz, 0.0);
221 model.instance(*this, fitter.result(z));
222}
223
224
226Ellipse::arc(Point const &ip, Point const &inner, Point const &fp)
227{
228 // This is resistant to degenerate ellipses:
229 // both flags evaluate to false in that case.
230
231 bool large_arc_flag = false;
232 bool sweep_flag = false;
233
234 // Determination of large arc flag:
235 // large_arc is false when the inner point is on the same side
236 // of the center---initial point line as the final point, AND
237 // is on the same side of the center---final point line as the
238 // initial point.
239 // Additionally, large_arc is always false when we have exactly
240 // 1/2 of an arc, i.e. the cross product of the center -> initial point
241 // and center -> final point vectors is zero.
242 // Negating the above leads to the condition for large_arc being true.
243 Point fv = fp - _center;
244 Point iv = ip - _center;
245 Point innerv = inner - _center;
246 double ifcp = cross(fv, iv);
247
248 if (ifcp != 0 && (sgn(cross(fv, innerv)) != sgn(ifcp) ||
249 sgn(cross(iv, innerv)) != sgn(-ifcp)))
250 {
251 large_arc_flag = true;
252 }
253
254 // Determination of sweep flag:
255 // For clarity, let's assume that Y grows up. Then the cross product
256 // is positive for points on the left side of a vector and negative
257 // on the right side of a vector.
258 //
259 // cross(?, v) > 0
260 // o------------------->
261 // cross(?, v) < 0
262 //
263 // If the arc is small (large_arc_flag is false) and the final point
264 // is on the right side of the vector initial point -> center,
265 // we have to go in the direction of increasing angles
266 // (counter-clockwise) and the sweep flag is true.
267 // If the arc is large, the opposite is true, since we have to reach
268 // the final point going the long way - in the other direction.
269 // We can express this observation as:
270 // cross(_center - ip, fp - _center) < 0 xor large_arc flag
271 // This is equal to:
272 // cross(-iv, fv) < 0 xor large_arc flag
273 // But cross(-iv, fv) is equal to cross(fv, iv) due to antisymmetry
274 // of the cross product, so we end up with the condition below.
275 if ((ifcp < 0) ^ large_arc_flag) {
276 sweep_flag = true;
277 }
278
279 EllipticalArc *ret_arc = new EllipticalArc(ip, ray(X), ray(Y), rotationAngle(),
280 large_arc_flag, sweep_flag, fp);
281 return ret_arc;
282}
283
285{
286 _angle += r.angle();
287 _center *= r;
288 return *this;
289}
290
292{
293 Affine a = Scale(ray(X), ray(Y)) * Rotate(_angle);
294 Affine mwot = m.withoutTranslation();
295 Affine am = a * mwot;
296 Point new_center = _center * m;
297
298 if (are_near(am.descrim(), 0)) {
299 double angle;
300 if (am[0] != 0) {
301 angle = std::atan2(am[2], am[0]);
302 } else if (am[1] != 0) {
303 angle = std::atan2(am[3], am[1]);
304 } else {
305 angle = M_PI/2;
306 }
307 Point v = Point::polar(angle) * am;
308 _center = new_center;
309 _rays[X] = L2(v);
310 _rays[Y] = 0;
311 _angle = atan2(v);
312 return *this;
313 } else if (mwot.isScale(0) && _angle.radians() == 0) {
314 _rays[X] *= std::abs(mwot[0]);
315 _rays[Y] *= std::abs(mwot[3]);
316 _center = new_center;
317 return *this;
318 }
319
320 std::vector<double> coeff = coefficients();
321 Affine q( coeff[0], coeff[1]/2,
322 coeff[1]/2, coeff[2],
323 0, 0 );
324
325 Affine invm = mwot.inverse();
326 q = invm * q ;
327 std::swap(invm[1], invm[2]);
328 q *= invm;
329 setCoefficients(q[0], 2*q[1], q[3], 0, 0, -1);
330 _center = new_center;
331
332 return *this;
333}
334
336{
337 Ellipse result(*this);
338 result.makeCanonical();
339 return result;
340}
341
343{
344 if (_rays[X] == _rays[Y]) {
345 _angle = 0;
346 return;
347 }
348
349 if (_angle < 0) {
350 _angle += M_PI;
351 }
352 if (_angle >= M_PI/2) {
353 std::swap(_rays[X], _rays[Y]);
354 _angle -= M_PI/2;
355 }
356}
357
359{
360 Point p = Point::polar(t);
361 p *= unitCircleTransform();
362 return p;
363}
364
366{
367 Coord sinrot, cosrot, cost, sint;
368 sincos(rotationAngle(), sinrot, cosrot);
369 sincos(t, sint, cost);
370
371 if ( d == X ) {
372 return ray(X) * cosrot * cost
373 - ray(Y) * sinrot * sint
374 + center(X);
375 } else {
376 return ray(X) * sinrot * cost
377 + ray(Y) * cosrot * sint
378 + center(Y);
379 }
380}
381
383{
384 // degenerate ellipse is basically a reparametrized line segment
385 if (ray(X) == 0 || ray(Y) == 0) {
386 if (ray(X) != 0) {
387 return asin(Line(axis(X)).timeAt(p));
388 } else if (ray(Y) != 0) {
389 return acos(Line(axis(Y)).timeAt(p));
390 } else {
391 return 0;
392 }
393 }
395 return Angle(atan2(p * iuct)).radians0(); // return a value in [0, 2pi)
396}
397
399{
400 Point p = Point::polar(t + M_PI/2);
402 p.normalize();
403 return p;
404}
405
406bool Ellipse::contains(Point const &p) const
407{
409 return tp.length() <= 1;
410}
411
420static std::array<Coord, 2> axis_time_to_angles(Coord t, bool vertical)
421{
422 Coord const to_unit = std::clamp(2.0 * t - 1.0, -1.0, 1.0);
423 if (vertical) {
424 double const arcsin = std::asin(to_unit);
425 return {arcsin, M_PI - arcsin};
426 } else {
427 double const arccos = std::acos(to_unit);
428 return {arccos, -arccos};
429 }
430}
431
440static std::vector<ShapeIntersection> double_axis_intersections(std::vector<ShapeIntersection> &&axis_intersections,
441 bool vertical)
442{
443 if (axis_intersections.empty()) {
444 return {};
445 }
446 std::vector<ShapeIntersection> result;
447 result.reserve(2 * axis_intersections.size());
448
449 for (auto const &x : axis_intersections) {
450 for (auto a : axis_time_to_angles(x.second, vertical)) {
451 result.emplace_back(a, x.first, x.point()); // Swap first <-> converted second.
452 if (x.second == 0.0 || x.second == 1.0) {
453 break; // Do not double up endpoint intersections.
454 }
455 }
456 }
457 return result;
458}
459
460std::vector<ShapeIntersection> Ellipse::intersect(Line const &line) const
461{
462 std::vector<ShapeIntersection> result;
463
464 if (line.isDegenerate()) {
465 return result;
466 }
467 if (ray(X) == 0 || ray(Y) == 0) {
468 return double_axis_intersections(line.intersect(majorAxis()), ray(X) == 0);
469 }
470
471 // Ax^2 + Bxy + Cy^2 + Dx + Ey + F
472 std::array<Coord, 6> coeffs;
473 coefficients(coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4], coeffs[5]);
474 rescale_homogenous(coeffs);
475 auto [A, B, C, D, E, F] = coeffs;
477
478 // generic case
479 std::array<Coord, 3> line_coeffs;
480 line.coefficients(line_coeffs[0], line_coeffs[1], line_coeffs[2]);
481 rescale_homogenous(line_coeffs);
482 auto [a, b, c] = line_coeffs;
483 Point lv = line.vector();
484
485 if (fabs(lv[X]) > fabs(lv[Y])) {
486 // y = -a/b x - c/b
487 Coord q = -a/b;
488 Coord r = -c/b;
489
490 // substitute that into the ellipse equation, making it quadratic in x
491 Coord I = A + B*q + C*q*q; // x^2 terms
492 Coord J = B*r + C*2*q*r + D + E*q; // x^1 terms
493 Coord K = C*r*r + E*r + F; // x^0 terms
494 std::vector<Coord> xs = solve_quadratic(I, J, K);
495
496 for (double x : xs) {
497 Point p(x, q*x + r);
498 result.emplace_back(atan2(p * iuct), line.timeAt(p), p);
499 }
500 } else {
501 Coord q = -b/a;
502 Coord r = -c/a;
503
504 Coord I = A*q*q + B*q + C;
505 Coord J = A*2*q*r + B*r + D*q + E;
506 Coord K = A*r*r + D*r + F;
507 std::vector<Coord> xs = solve_quadratic(I, J, K);
508
509 for (double x : xs) {
510 Point p(q*x + r, x);
511 result.emplace_back(atan2(p * iuct), line.timeAt(p), p);
512 }
513 }
514 return result;
515}
516
517std::vector<ShapeIntersection> Ellipse::intersect(LineSegment const &seg) const
518{
519 if (!boundsFast().intersects(seg.boundsFast())) {
520 return {};
521 }
522
523 // We simply reuse the procedure for lines and filter out
524 // results where the line time value is outside of the unit interval,
525 // but we apply a small tolerance to account for numerical errors.
526 double const param_prec = EPSILON / seg.length(0.0);
527 // TODO: accept a precision setting instead of always using EPSILON
528 // (requires an ABI break).
529
530 auto xings = intersect(Line(seg));
531 if (xings.empty()) {
532 return xings;
533 }
534 decltype(xings) result;
535 result.reserve(xings.size());
536
537 for (auto const &x : xings) {
538 if (x.second < -param_prec || x.second > 1.0 + param_prec) {
539 continue;
540 }
541 result.emplace_back(x.first, std::clamp(x.second, 0.0, 1.0), x.point());
542 }
543 return result;
544}
545
546std::vector<ShapeIntersection> Ellipse::intersect(Ellipse const &other) const
547{
548 // Handle degenerate cases first.
549 if (ray(X) == 0 || ray(Y) == 0) { // Degenerate ellipse, collapsed to the major axis.
550 return double_axis_intersections(other.intersect(majorAxis()), ray(X) == 0);
551 }
552 if (*this == other) { // Two identical ellipses.
553 THROW_INFINITELY_MANY_SOLUTIONS("The two ellipses are identical.");
554 }
555 if (!boundsFast().intersects(other.boundsFast())) {
556 return {};
557 }
558
559 // Find coefficients of the implicit equations of the two ellipses and rescale
560 // them (losslessly) for better numerical conditioning.
561 std::array<double, 6> coeffs;
562 coefficients(coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4], coeffs[5]);
563 rescale_homogenous(coeffs);
564 auto [A, B, C, D, E, F] = coeffs;
565
566 std::array<double, 6> otheffs;
567 other.coefficients(otheffs[0], otheffs[1], otheffs[2], otheffs[3], otheffs[4], otheffs[5]);
568 rescale_homogenous(otheffs);
569 auto [a, b, c, d, e, f] = otheffs;
570
571 // Assume that Q(x, y) = 0 is the ellipse equation given by uppercase letters
572 // and R(x, y) = 0 is the equation given by lowercase ones.
573 // In other words, Q is the quadratic function describing this ellipse and
574 // R is the quadratic function for the other ellipse.
575 //
576 // A point (x, y) is common to both ellipses if and only if it solves the system
577 // { Q(x, y) = 0,
578 // { R(x, y) = 0.
579 //
580 // If ” is any real number, we can multiply the first equation by ” and add that
581 // to the first equation, obtaining the new system of equations:
582 // { Q(x, y) = 0,
583 // { ”Q(x, y) + R(x, y) = 0.
584 //
585 // The first equation still says that (x, y) is a point on this ellipse, but the
586 // second equation uses the new expression (”Q + R) instead of the original R.
587 //
588 // Why do we do this? The reason is that the set of functions {”Q + R : ” real}
589 // is a "real system of conics" and there's a theorem which guarantees that such a system
590 // always contains a "degenerate conic" [proof below].
591 // In practice, the degenerate conic will describe a line or a pair of lines, and intersecting
592 // a line with an ellipse is much easier than intersecting two ellipses directly.
593 //
594 // But in order to be able to do this, we must find a value of ” for which ”Q + R is degenerate.
595 // We can write the expression (”Q + R)(x, y) in the following way:
596 //
597 // | aa bb/2 dd/2 | |x|
598 // (”Q + R)(x, y) = [x y 1] | bb/2 cc ee/2 | |y|
599 // | dd/2 ee/2 ff | |1|
600 //
601 // where aa = ”A + a and so on. The determinant can be explicitly written out,
602 // giving an equation which is cubic in ” and can be solved analytically.
603 // The conic ”Q + R is degenerate if and only if this determinant is 0.
604 //
605 // Proof that there's always a degenerate conic: a cubic real polynomial always has a root,
606 // and if the polynomial in ” isn't cubic (coefficient of ”^3 is zero), then the starting
607 // conic is already degenerate.
608
609 Coord I, J, K, L; // Coefficients of ” in the expression for the determinant.
610 I = (-B*B*F + 4*A*C*F + D*E*B - A*E*E - C*D*D) / 4;
611 J = -((B*B - 4*A*C) * f + (2*B*F - D*E) * b + (2*A*E - D*B) * e +
612 (2*C*D - E*B) * d + (D*D - 4*A*F) * c + (E*E - 4*C*F) * a) / 4;
613 K = -((b*b - 4*a*c) * F + (2*b*f - d*e) * B + (2*a*e - d*b) * E +
614 (2*c*d - e*b) * D + (d*d - 4*a*f) * C + (e*e - 4*c*f) * A) / 4;
615 L = (-b*b*f + 4*a*c*f + d*e*b - a*e*e - c*d*d) / 4;
616
617 std::vector<Coord> mus = solve_cubic(I, J, K, L);
618 Coord mu = infinity();
619
620 // Now that we have solved for ”, we need to check whether the conic
621 // determined by ”Q + R is reducible to a product of two lines. If it's not,
622 // it means that there are no intersections. If it is, the intersections of these
623 // lines with the original ellipses (if there are any) give the coordinates
624 // of intersections.
625
626 // Prefer middle root if there are three.
627 // Out of three possible pairs of lines that go through four points of intersection
628 // of two ellipses, this corresponds to cross-lines. These intersect the ellipses
629 // at less shallow angles than the other two options.
630 if (mus.size() == 3) {
631 std::swap(mus[1], mus[0]);
632 }
634 static Coord const discriminant_precision = 1e-10;
635
636 for (Coord candidate_mu : mus) {
637 Coord const aa = std::fma(candidate_mu, A, a);
638 Coord const bb = std::fma(candidate_mu, B, b);
639 Coord const cc = std::fma(candidate_mu, C, c);
640 Coord const delta = sqr(bb) - 4*aa*cc;
641 if (delta < -discriminant_precision) {
642 continue;
643 }
644 mu = candidate_mu;
645 break;
646 }
647
648 // if no suitable mu was found, there are no intersections
649 if (mu == infinity()) {
650 return {};
651 }
652
653 // Create the degenerate conic and decompose it into lines.
654 std::array<double, 6> degen = {std::fma(mu, A, a), std::fma(mu, B, b), std::fma(mu, C, c),
655 std::fma(mu, D, d), std::fma(mu, E, e), std::fma(mu, F, f)};
657 auto const lines = xAx(degen[0], degen[1], degen[2],
658 degen[3], degen[4], degen[5]).decompose_df(discriminant_precision);
659
660 // intersect with the obtained lines and report intersections
661 std::vector<ShapeIntersection> result;
662 for (auto const &line : lines) {
663 if (line.isDegenerate()) {
664 continue;
665 }
666 auto as = intersect(line);
667 // NOTE: If we only cared about the intersection points, we could simply
668 // intersect this ellipse with the lines and ignore the other ellipse.
669 // But we need the time coordinates on the other ellipse as well.
670 auto bs = other.intersect(line);
671 if (as.empty() || bs.empty()) {
672 continue;
673 }
674 // Due to numerical errors, a tangency may sometimes be found as 1 intersection
675 // on one ellipse and 2 intersections on the other. If this happens, we average
676 // the points of the two intersections.
677 auto const intersection_average = [](ShapeIntersection const &i,
679 {
680 return ShapeIntersection(i.first, j.first, middle_point(i.point(), j.point()));
681 };
682 auto const synthesize_intersection = [&](ShapeIntersection const &i,
683 ShapeIntersection const &j) -> void
684 {
685 result.emplace_back(i.first, j.first, middle_point(i.point(), j.point()));
686 };
687 if (as.size() == 2) {
688 if (bs.size() == 2) {
689 synthesize_intersection(as[0], bs[0]);
690 synthesize_intersection(as[1], bs[1]);
691 } else if (bs.size() == 1) {
692 synthesize_intersection(intersection_average(as[0], as[1]), bs[0]);
693 }
694 } else if (as.size() == 1) {
695 if (bs.size() == 2) {
696 synthesize_intersection(as[0], intersection_average(bs[0], bs[1]));
697 } else if (bs.size() == 1) {
698 synthesize_intersection(as[0], bs[0]);
699 }
700 }
701 }
702 return result;
703}
704
705std::vector<ShapeIntersection> Ellipse::intersect(D2<Bezier> const &b) const
706{
707 Coord A, B, C, D, E, F;
708 coefficients(A, B, C, D, E, F);
709
710 // We plug the X and Y curves into the implicit equation and solve for t.
711 Bezier x = A*b[X]*b[X] + B*b[X]*b[Y] + C*b[Y]*b[Y] + D*b[X] + E*b[Y] + F;
712 std::vector<Coord> r = x.roots();
713
714 std::vector<ShapeIntersection> result;
715 for (double & i : r) {
716 Point p = b.valueAt(i);
717 result.emplace_back(timeAt(p), i, p);
718 }
719 return result;
720}
721
722bool Ellipse::operator==(Ellipse const &other) const
723{
724 if (_center != other._center) return false;
725
726 Ellipse a = this->canonicalForm();
727 Ellipse b = other.canonicalForm();
728
729 if (a._rays != b._rays) return false;
730 if (a._angle != b._angle) return false;
731
732 return true;
733}
734
735
736bool are_near(Ellipse const &a, Ellipse const &b, Coord precision)
737{
738 // We want to know whether no point on ellipse a is further than precision
739 // from the corresponding point on ellipse b. To check this, we compute
740 // the four extreme points at the end of each ray for each ellipse
741 // and check whether they are sufficiently close.
742
743 // First, we need to correct the angles on the ellipses, so that they are
744 // no further than M_PI/4 apart. This can always be done by rotating
745 // and exchanging axes.
746 Ellipse ac = a, bc = b;
747 if (distance(ac.rotationAngle(), bc.rotationAngle()).radians0() >= M_PI/2) {
748 ac.setRotationAngle(ac.rotationAngle() + M_PI);
749 }
750 if (distance(ac.rotationAngle(), bc.rotationAngle()) >= M_PI/4) {
751 Angle d1 = distance(ac.rotationAngle() + M_PI/2, bc.rotationAngle());
752 Angle d2 = distance(ac.rotationAngle() - M_PI/2, bc.rotationAngle());
753 Coord adj = d1.radians0() < d2.radians0() ? M_PI/2 : -M_PI/2;
754 ac.setRotationAngle(ac.rotationAngle() + adj);
755 ac.setRays(ac.ray(Y), ac.ray(X));
756 }
757
758 // Do the actual comparison by computing four points on each ellipse.
759 Point tps[] = {Point(1,0), Point(0,1), Point(-1,0), Point(0,-1)};
760 for (auto & tp : tps) {
761 if (!are_near(tp * ac.unitCircleTransform(),
762 tp * bc.unitCircleTransform(),
763 precision))
764 return false;
765 }
766 return true;
767}
768
769std::ostream &operator<<(std::ostream &out, Ellipse const &e)
770{
771 out << "Ellipse(" << e.center() << ", " << e.rays()
772 << ", " << format_coord_nice(e.rotationAngle()) << ")";
773 return out;
774}
775
776} // end namespace Geom
777
778
779/*
780 Local Variables:
781 mode:c++
782 c-file-style:"stroustrup"
783 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
784 indent-tabs-mode:nil
785 fill-column:99
786 End:
787*/
788// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
789
790
3x3 matrix representing an affine transformation.
Definition affine.h:70
void setTranslation(Point const &loc)
Sets the translation imparted by the Affine.
Definition affine.cpp:56
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
bool isScale(Coord eps=EPSILON) const
Check whether this matrix represents pure scaling.
Definition affine.cpp:147
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Affine withoutTranslation() const
Definition affine.h:169
Wrapper for angular values.
Definition angle.h:73
Coord radians0() const
Get the angle as positive radians.
Definition angle.h:112
Coord radians() const
Get the angle as radians.
Definition angle.h:107
Rect boundsFast() const override
Quickly compute the curve's approximate bounding box.
Coord length(Coord tolerance) const override
Compute the arc length of this curve.
Polynomial in Bernstein-Bezier basis.
Definition bezier.h:126
std::vector< Coord > roots() const
Definition bezier.cpp:105
Set of all points at a fixed distance from the center.
Definition circle.h:55
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
Point valueAt(double t) const
Definition d2.h:133
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 makeCanonical()
Definition ellipse.cpp:342
LineSegment semiaxis(Dim2 d, int sign=1) const
Definition ellipse.cpp:137
void setRays(Point const &p)
Set both rays of the ellipse.
Definition ellipse.h:111
LineSegment axis(Dim2 d) const
Definition ellipse.cpp:127
Point initialPoint() const
Get the point corresponding to the +X ray of the ellipse.
Definition ellipse.cpp:101
void setRotationAngle(Angle a)
Set the angle the X ray makes with the +X axis.
Definition ellipse.h:117
Point unitTangentAt(Coord t) const
Get the value of the derivative at time t normalized to unit length.
Definition ellipse.cpp:398
std::vector< double > coefficients() const
Get the coefficients of the ellipse's implicit equation.
Definition ellipse.cpp:173
bool contains(Point const &p) const
Check whether the ellipse contains the given point.
Definition ellipse.cpp:406
Ellipse canonicalForm() const
Return an ellipse with less degrees of freedom.
Definition ellipse.cpp:335
Rect boundsFast() const
Get a fast to compute bounding box which contains the ellipse.
Definition ellipse.cpp:162
Coord timeAt(Point const &p) const
Find the time value of a point on an ellipse.
Definition ellipse.cpp:382
Angle _angle
Definition ellipse.h:71
Angle rotationAngle() const
Get the angle the X ray makes with the +X axis.
Definition ellipse.h:126
std::vector< ShapeIntersection > intersect(Line const &line) const
Compute intersections with an infinite line.
Definition ellipse.cpp:460
Affine unitCircleTransform() const
Compute the transform that maps the unit circle to this ellipse.
Definition ellipse.cpp:110
void fit(std::vector< Point > const &points)
Create an ellipse passing through the specified points At least five points have to be specified.
Definition ellipse.cpp:206
EllipticalArc * arc(Point const &ip, Point const &inner, Point const &fp)
Create an elliptical arc from a section of the ellipse.
Definition ellipse.cpp:226
Affine inverseUnitCircleTransform() const
Compute the transform that maps this ellipse to the unit circle.
Definition ellipse.cpp:117
bool are_near(Ellipse const &a, Ellipse const &b, Coord precision=EPSILON)
Test whether two ellipses are approximately the same.
Definition ellipse.cpp:736
Point pointAt(Coord t) const
Evaluate a point on the ellipse.
Definition ellipse.cpp:358
Ellipse & operator*=(Translate const &t)
Definition ellipse.h:216
Coord ray(Dim2 d) const
Get one ray of the ellipse.
Definition ellipse.h:124
Rect boundsExact() const
Get the tight-fitting bounding box of the ellipse.
Definition ellipse.cpp:146
Point rays() const
Get both rays as a point.
Definition ellipse.h:122
Point _rays
Definition ellipse.h:70
Point center() const
Definition ellipse.h:119
Point _center
Definition ellipse.h:69
bool operator==(Ellipse const &other) const
Compare ellipses for exact equality.
Definition ellipse.cpp:722
LineSegment majorAxis() const
Definition ellipse.h:163
void setCoefficients(double A, double B, double C, double D, double E, double F)
Set an ellipse by solving its implicit equation.
Definition ellipse.cpp:48
Elliptical arc curve.
Intersection between two shapes.
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
Infinite line on a plane.
Definition line.h:53
std::vector< double > coefficients() const
Get the coefficients of the line equation as a vector.
Definition line.cpp:120
Coord timeAt(Point const &p) const
Get a time value corresponding to a point.
Definition line.cpp:223
bool isDegenerate() const
Check if the line has more than one point.
Definition line.h:190
Point vector() const
Get the line's raw direction vector.
Definition line.h:132
std::vector< ShapeIntersection > intersect(Line const &other) const
Definition line.cpp:253
void instance(Ellipse &e, ConstVectorView const &coeff) const
Two-dimensional point that doubles as a vector.
Definition point.h:66
void normalize()
Normalize the vector representing the point.
Definition point.cpp:96
Coord length() const
Compute the distance from origin.
Definition point.h:118
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotation around the origin.
Definition transforms.h:187
Coord angle() const
Definition transforms.h:204
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
std::array< Line, 2 > decompose_df(Coord epsilon=EPSILON) const
Division-free decomposition of a degenerate conic section, without degeneration test.
Conic Section.
double inner(valarray< double > const &x, valarray< double > const &y)
Css & result
double c[8][4]
Ellipse shape.
Elliptical arc curve.
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Various utility functions.
Definition affine.h:22
void sincos(double angle, double &sin_, double &cos_)
Simultaneously compute a sine and a cosine of the same angle.
Definition math-utils.h:89
static std::vector< ShapeIntersection > double_axis_intersections(std::vector< ShapeIntersection > &&axis_intersections, bool vertical)
For each intersection of some shape with the major axis of an ellipse, produce one or two intersectio...
Definition ellipse.cpp:440
std::ostream & operator<<(std::ostream &os, const Bezier &b)
Definition bezier.h:372
int sgn(const T &x)
Sign function - indicates the sign of a numeric type.
Definition math-utils.h:51
Intersection ShapeIntersection
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
xAx degen
Definition conic-6.cpp:70
static float sign(double number)
Returns +1 for positive numbers, -1 for negative numbers, and 0 otherwise.
double atan2(Point const &p)
std::vector< Coord > solve_quadratic(Coord a, Coord b, Coord c)
Analytically solve quadratic equation.
static std::array< Coord, 2 > axis_time_to_angles(Coord t, bool vertical)
Convert curve time on the major axis to the corresponding angle parameters on a degenerate ellipse co...
Definition ellipse.cpp:420
std::vector< Coord > solve_cubic(Coord a, Coord b, Coord c, Coord d)
Analytically solve cubic equation.
@ intersects
Definition geom.cpp:16
std::string format_coord_nice(Coord x)
Definition coord.cpp:89
int rescale_homogenous(std::array< double, N > &values)
Scale the doubles in the passed array to make them "reasonably large".
Definition math-utils.h:108
T sqr(const T &x)
Definition math-utils.h:57
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
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Point middle_point(LineSegment const &_segment)
unsigned long bs
Definition quantize.cpp:38
int num
Definition scribble.cpp:47
int delta