Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
conicsec.cpp
Go to the documentation of this file.
1/*
2 * Authors:
3 * Nathan Hurst <njh@njhurst.com
4 *
5 * Copyright 2009 authors
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it either under the terms of the GNU Lesser General Public
9 * License version 2.1 as published by the Free Software Foundation
10 * (the "LGPL") or, at your option, under the terms of the Mozilla
11 * Public License Version 1.1 (the "MPL"). If you do not alter this
12 * notice, a recipient may use your version of this file under either
13 * the MPL or the LGPL.
14 *
15 * You should have received a copy of the LGPL along with this library
16 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * You should have received a copy of the MPL along with this library
19 * in the file COPYING-MPL-1.1
20 *
21 * The contents of this file are subject to the Mozilla Public License
22 * Version 1.1 (the "License"); you may not use this file except in
23 * compliance with the License. You may obtain a copy of the License at
24 * http://www.mozilla.org/MPL/
25 *
26 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
27 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
28 * the specific language governing rights and limitations.
29 */
30
31
32#include <2geom/conicsec.h>
36
37
38// File: convert.h
39#include <utility>
40#include <sstream>
41#include <stdexcept>
42#include <optional>
43
44namespace Geom
45{
46
48 std::optional<LineSegment> seg = l.clip(r);
49 if (seg) {
50 return *seg;
51 } else {
52 return LineSegment(Point(0,0), Point(0,0));
53 }
54}
55
56static double det(Point a, Point b) {
57 return a[0]*b[1] - a[1]*b[0];
58}
59
60template <typename T>
61static T det(T a, T b, T c, T d) {
62 return a*d - b*c;
63}
64
65template <typename T>
66static T det(T M[2][2]) {
67 return M[0][0]*M[1][1] - M[1][0]*M[0][1];
68}
69
70template <typename T>
71static T det3(T M[3][3]) {
72 return ( M[0][0] * det(M[1][1], M[1][2],
73 M[2][1], M[2][2])
74 -M[1][0] * det(M[0][1], M[0][2],
75 M[2][1], M[2][2])
76 +M[2][0] * det(M[0][1], M[0][2],
77 M[1][1], M[1][2]));
78}
79
80static double boxprod(Point a, Point b, Point c) {
81 return det(a,b) - det(a,c) + det(b,c);
82}
83
84class BadConversion : public std::runtime_error {
85public:
86 BadConversion(const std::string& s)
87 : std::runtime_error(s)
88 { }
89};
90
91template <typename T>
92inline std::string stringify(T x)
93{
94 std::ostringstream o;
95 if (!(o << x))
96 throw BadConversion("stringify(T)");
97 return o.str();
98}
99
100 /* A G4 continuous cubic parametric approximation for rational quadratics.
101 See
102 An analysis of cubic approximation schemes for conic sections
103 Michael Floater
104 SINTEF
105
106 This is less accurate overall than some of his other schemes, but
107 produces very smooth joins and is still optimally h^-6
108 convergent.
109 */
110
111double RatQuad::lambda() const {
112 return 2*(6*w*w +1 -std::sqrt(3*w*w+1))/(12*w*w+3);
113}
114
116 Point P,
117 Point P2, Point dP2) {
118 Line Line0 = Line::from_origin_and_vector(P0, dP0);
119 Line Line2 = Line::from_origin_and_vector(P2, dP2);
120 try {
121 OptCrossing oc = intersection(Line0, Line2);
122 if(!oc) // what to do?
123 return RatQuad(Point(), Point(), Point(), 0); // need opt really
124 //assert(0);
125 Point P1 = Line0.pointAt((*oc).ta);
126 double triarea = boxprod(P0, P1, P2);
127// std::cout << "RatQuad::fromPointsTangents: triarea = " << triarea << std::endl;
128 if (triarea == 0)
129 {
130 return RatQuad(P0, 0.5*(P0+P2), P2, 1);
131 }
132 double tau0 = boxprod(P, P1, P2)/triarea;
133 double tau1 = boxprod(P0, P, P2)/triarea;
134 double tau2 = boxprod(P0, P1, P)/triarea;
135 if (tau0 == 0 || tau1 == 0 || tau2 == 0)
136 {
137 return RatQuad(P0, 0.5*(P0+P2), P2, 1);
138 }
139 double w = tau1/(2*std::sqrt(tau0*tau2));
140// std::cout << "RatQuad::fromPointsTangents: tau0 = " << tau0 << std::endl;
141// std::cout << "RatQuad::fromPointsTangents: tau1 = " << tau1 << std::endl;
142// std::cout << "RatQuad::fromPointsTangents: tau2 = " << tau2 << std::endl;
143// std::cout << "RatQuad::fromPointsTangents: w = " << w << std::endl;
144 return RatQuad(P0, P1, P2, w);
145 } catch(Geom::InfiniteSolutions const&) {
146 return RatQuad(P0, 0.5*(P0+P2), P2, 1);
147 }
148 return RatQuad(Point(), Point(), Point(), 0); // need opt really
149}
150
152 return RatQuad(P0, P1, P2, dot(unit_vector(P0 - P1), unit_vector(P0 - P2)));
153}
154
155
157 return toCubic(lambda());
158}
159
160CubicBezier RatQuad::toCubic(double lamb) const {
161 return CubicBezier(P[0],
162 (1-lamb)*P[0] + lamb*P[1],
163 (1-lamb)*P[2] + lamb*P[1],
164 P[2]);
165}
166
167Point RatQuad::pointAt(double t) const {
168 Bezier xt(P[0][0], P[1][0]*w, P[2][0]);
169 Bezier yt(P[0][1], P[1][1]*w, P[2][1]);
170 double wt = Bezier(1, w, 1).valueAt(t);
171 return Point(xt.valueAt(t)/wt,
172 yt.valueAt(t)/wt);
173}
174
175void RatQuad::split(RatQuad &a, RatQuad &b) const {
176 a.P[0] = P[0];
177 b.P[2] = P[2];
178 a.P[1] = (P[0]+w*P[1])/(1+w);
179 b.P[1] = (w*P[1]+P[2])/(1+w);
180 a.w = b.w = std::sqrt((1+w)/2);
181 a.P[2] = b.P[0] = (0.5*a.P[1]+0.5*b.P[1]);
182}
183
184
186 SBasis t = Linear(0, 1);
187 SBasis omt = Linear(1, 0);
188
189 D2<SBasis> out(omt*omt*P[0][0]+2*omt*t*P[1][0]*w+t*t*P[2][0],
190 omt*omt*P[0][1]+2*omt*t*P[1][1]*w+t*t*P[2][1]);
191 for(int dim = 0; dim < 2; dim++) {
192 out[dim] = divide(out[dim], (omt*omt+2*omt*t*w+t*t), 2);
193 }
194 return out;
195}
196
197 std::vector<SBasis> RatQuad::homogeneous() const {
198 std::vector<SBasis> res(3, SBasis());
199 Bezier xt(P[0][0], P[1][0]*w, P[2][0]);
200 bezier_to_sbasis(res[0],xt);
201 Bezier yt(P[0][1], P[1][1]*w, P[2][1]);
202 bezier_to_sbasis(res[1],yt);
203 Bezier wt(1, w, 1);
204 bezier_to_sbasis(res[2],wt);
205 return res;
206}
207
208#if 0
209 std::string xAx::categorise() const {
210 double M[3][3] = {{c[0], c[1], c[3]},
211 {c[1], c[2], c[4]},
212 {c[3], c[4], c[5]}};
213 double D = det3(M);
214 if (c[0] == 0 && c[1] == 0 && c[2] == 0)
215 return "line";
216 std::string res = stringify(D);
217 double descr = c[1]*c[1] - c[0]*c[2];
218 if (descr < 0) {
219 if (c[0] == c[2] && c[1] == 0)
220 return res + "circle";
221 return res + "ellipse";
222 } else if (descr == 0) {
223 return res + "parabola";
224 } else if (descr > 0) {
225 if (c[0] + c[2] == 0) {
226 if (D == 0)
227 return res + "two lines";
228 return res + "rectangular hyperbola";
229 }
230 return res + "hyperbola";
231
232 }
233 return "no idea!";
234}
235#endif
236
237
238std::vector<Point> decompose_degenerate(xAx const & C1, xAx const & C2, xAx const & xC0) {
239 std::vector<Point> res;
240 double A[2][2] = {{2*xC0.c[0], xC0.c[1]},
241 {xC0.c[1], 2*xC0.c[2]}};
242//Point B0 = xC0.bottom();
243 double const determ = det(A);
244 //std::cout << determ << "\n";
245 if (fabs(determ) >= 1e-20) { // hopeful, I know
246 Geom::Coord const ideterm = 1.0 / determ;
247
248 double b[2] = {-xC0.c[3], -xC0.c[4]};
249 Point B0((A[1][1]*b[0] -A[0][1]*b[1]),
250 (-A[1][0]*b[0] + A[0][0]*b[1]));
251 B0 *= ideterm;
252 Point n0, n1;
253 // Are these just the eigenvectors of A11?
254 if(xC0.c[0] == xC0.c[2]) {
255 double b = 0.5*xC0.c[1]/xC0.c[0];
256 double c = xC0.c[2]/xC0.c[0];
257 //assert(fabs(b*b-c) > 1e-10);
258 double d = std::sqrt(b*b-c);
259 //assert(fabs(b-d) > 1e-10);
260 n0 = Point(1, b+d);
261 n1 = Point(1, b-d);
262 } else if(fabs(xC0.c[0]) > fabs(xC0.c[2])) {
263 double b = 0.5*xC0.c[1]/xC0.c[0];
264 double c = xC0.c[2]/xC0.c[0];
265 //assert(fabs(b*b-c) > 1e-10);
266 double d = std::sqrt(b*b-c);
267 //assert(fabs(b-d) > 1e-10);
268 n0 = Point(1, b+d);
269 n1 = Point(1, b-d);
270 } else {
271 double b = 0.5*xC0.c[1]/xC0.c[2];
272 double c = xC0.c[0]/xC0.c[2];
273 //assert(fabs(b*b-c) > 1e-10);
274 double d = std::sqrt(b*b-c);
275 //assert(fabs(b-d) > 1e-10);
276 n0 = Point(b+d, 1);
277 n1 = Point(b-d, 1);
278 }
279
282
283 std::vector<double> rts = C1.roots(L0);
284 for(double rt : rts) {
285 Point P = L0.pointAt(rt);
286 res.push_back(P);
287 }
288 rts = C1.roots(L1);
289 for(double rt : rts) {
290 Point P = L1.pointAt(rt);
291 res.push_back(P);
292 }
293 } else {
294 // single or double line
295 // check for completely zero case (what to do?)
296 assert(xC0.c[0] || xC0.c[1] ||
297 xC0.c[2] || xC0.c[3] ||
298 xC0.c[4] || xC0.c[5]);
299 Point trial_pt(0,0);
300 Point g = xC0.gradient(trial_pt);
301 if(L2sq(g) == 0) {
302 trial_pt[0] += 1;
303 g = xC0.gradient(trial_pt);
304 if(L2sq(g) == 0) {
305 trial_pt[1] += 1;
306 g = xC0.gradient(trial_pt);
307 if(L2sq(g) == 0) {
308 trial_pt[0] += 1;
309 g = xC0.gradient(trial_pt);
310 if(L2sq(g) == 0) {
311 trial_pt = Point(1.5,0.5);
312 g = xC0.gradient(trial_pt);
313 }
314 }
315 }
316 }
317 //std::cout << trial_pt << ", " << g << "\n";
329 assert(L2sq(g) != 0);
330
331 Line Lx = Line::from_origin_and_vector(trial_pt, g); // a line along the gradient
332 std::vector<double> rts = xC0.roots(Lx);
333 for(double rt : rts) {
334 Point P0 = Lx.pointAt(rt);
335 //std::cout << P0 << "\n";
337 std::vector<double> cnrts;
338 // It's very likely that at least one of the conics is degenerate, this will hopefully pick the more generate of the two.
339 if(fabs(C1.hessian().det()) > fabs(C2.hessian().det()))
340 cnrts = C1.roots(L);
341 else
342 cnrts = C2.roots(L);
343 for(double cnrt : cnrts) {
344 Point P = L.pointAt(cnrt);
345 res.push_back(P);
346 }
347 }
348 }
349 return res;
350}
351
352double xAx_descr(xAx const & C) {
353 double mC[3][3] = {{C.c[0], (C.c[1])/2, (C.c[3])/2},
354 {(C.c[1])/2, C.c[2], (C.c[4])/2},
355 {(C.c[3])/2, (C.c[4])/2, C.c[5]}};
356
357 return det3(mC);
358}
359
360
361std::vector<Point> intersect(xAx const & C1, xAx const & C2) {
362 // You know, if either of the inputs are degenerate we should use them first!
363 if(xAx_descr(C1) == 0) {
364 return decompose_degenerate(C1, C2, C1);
365 }
366 if(xAx_descr(C2) == 0) {
367 return decompose_degenerate(C1, C2, C2);
368 }
369 std::vector<Point> res;
370 SBasis T(Linear(-1,1));
371 SBasis S(Linear(1,1));
372 SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2},
373 {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2},
374 {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}};
375
376 SBasis D = det3(C);
377 std::vector<double> rts = Geom::roots(D);
378 if(rts.empty()) {
379 T = Linear(1,1);
380 S = Linear(-1,1);
381 SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2},
382 {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2},
383 {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}};
384
385 D = det3(C);
386 rts = Geom::roots(D);
387 }
388 // at this point we have a T and S and perhaps some roots that represent our degenerate conic
389 // Let's just pick one randomly (can we do better?)
390 //for(unsigned i = 0; i < rts.size(); i++) {
391 if(!rts.empty()) {
392 unsigned i = 0;
393 double t = T.valueAt(rts[i]);
394 double s = S.valueAt(rts[i]);
395 xAx xC0 = C1*t + C2*s;
396 //::draw(cr, xC0, screen_rect); // degen
397
398 return decompose_degenerate(C1, C2, xC0);
399
400
401 } else {
402 std::cout << "What?" << std::endl;
403 ;//std::cout << D << "\n";
404 }
405 return res;
406}
407
408
410 return xAx(1., 0, 1., -2*p[0], -2*p[1], dot(p,p));
411}
412
413xAx xAx::fromDistPoint(Point /*p*/, double /*d*/) {
414 return xAx();//1., 0, 1., -2*(1+d)*p[0], -2*(1+d)*p[1], dot(p,p)+d*d);
415}
416
417xAx xAx::fromLine(Point n, double d) {
418 return xAx(n[0]*n[0], 2*n[0]*n[1], n[1]*n[1], 2*d*n[0], 2*d*n[1], d*d);
419}
420
422 double dist;
423 Point norm = l.normalAndDist(dist);
424
425 return fromLine(norm, dist);
426}
427
428xAx xAx::fromPoints(std::vector<Geom::Point> const &pt) {
429 Geom::NL::Vector V(pt.size(), -1.0);
430 Geom::NL::Matrix M(pt.size(), 5);
431 for(unsigned i = 0; i < pt.size(); i++) {
432 Geom::Point P = pt[i];
434 vv[0] = P[0]*P[0];
435 vv[1] = P[0]*P[1];
436 vv[2] = P[1]*P[1];
437 vv[3] = P[0];
438 vv[4] = P[1];
439 }
440
441 Geom::NL::LinearSystem ls(M, V);
442
443 Geom::NL::Vector x = ls.SV_solve();
444 return Geom::xAx(x[0], x[1], x[2], x[3], x[4], 1);
445
446}
447
448
449
450double xAx::valueAt(Point P) const {
451 return evaluate_at(P[0], P[1]);
452}
453
454xAx xAx::scale(double sx, double sy) const {
455 return xAx(c[0]*sx*sx, c[1]*sx*sy, c[2]*sy*sy,
456 c[3]*sx, c[4]*sy, c[5]);
457}
458
460 double x = p[0];
461 double y = p[1];
462 return Point(2*c[0]*x + c[1]*y + c[3],
463 c[1]*x + 2*c[2]*y + c[4]);
464}
465
466xAx xAx::operator-(xAx const &b) const {
467 xAx res;
468 for(int i = 0; i < 6; i++) {
469 res.c[i] = c[i] - b.c[i];
470 }
471 return res;
472}
473xAx xAx::operator+(xAx const &b) const {
474 xAx res;
475 for(int i = 0; i < 6; i++) {
476 res.c[i] = c[i] + b.c[i];
477 }
478 return res;
479}
480xAx xAx::operator+(double const &b) const {
481 xAx res;
482 for(int i = 0; i < 5; i++) {
483 res.c[i] = c[i];
484 }
485 res.c[5] = c[5] + b;
486 return res;
487}
488
489xAx xAx::operator*(double const &b) const {
490 xAx res;
491 for(int i = 0; i < 6; i++) {
492 res.c[i] = c[i] * b;
493 }
494 return res;
495}
496
497 std::vector<Point> xAx::crossings(Rect r) const {
498 std::vector<Point> res;
499 for(int ei = 0; ei < 4; ei++) {
500 Geom::LineSegment ls(r.corner(ei), r.corner(ei+1));
501 D2<SBasis> lssb = ls.toSBasis();
502 SBasis edge_curve = evaluate_at(lssb[0], lssb[1]);
503 std::vector<double> rts = Geom::roots(edge_curve);
504 for(double rt : rts) {
505 res.push_back(lssb.valueAt(rt));
506 }
507 }
508 return res;
509}
510
511 std::optional<RatQuad> xAx::toCurve(Rect const & bnd) const {
512 std::vector<Point> crs = crossings(bnd);
513 if(crs.size() == 1) {
514 Point A = crs[0];
515 Point dA = rot90(gradient(A));
516 if(L2sq(dA) <= 1e-10) { // perhaps a single point?
517 return std::optional<RatQuad> ();
518 }
520 return RatQuad::fromPointsTangents(A, dA, ls.pointAt(0.5), ls[1], dA);
521 }
522 else if(crs.size() >= 2 && crs.size() < 4) {
523 Point A = crs[0];
524 Point C = crs[1];
525 if(crs.size() == 3) {
526 if(distance(A, crs[2]) > distance(A, C))
527 C = crs[2];
528 else if(distance(C, crs[2]) > distance(A, C))
529 A = crs[2];
530 }
531 Line bisector = make_bisector_line(LineSegment(A, C));
532 std::vector<double> bisect_rts = this->roots(bisector);
533 if(!bisect_rts.empty()) {
534 int besti = -1;
535 for(unsigned i =0; i < bisect_rts.size(); i++) {
536 Point p = bisector.pointAt(bisect_rts[i]);
537 if(bnd.contains(p)) {
538 besti = i;
539 }
540 }
541 if(besti >= 0) {
542 Point B = bisector.pointAt(bisect_rts[besti]);
543
544 Point dA = gradient(A);
545 Point dC = gradient(C);
546 if(L2sq(dA) <= 1e-10 || L2sq(dC) <= 1e-10) {
547 return RatQuad::fromPointsTangents(A, C-A, B, C, A-C);
548 }
549
551 B, C, rot90(dC));
552 return rq;
553 //std::vector<SBasis> hrq = rq.homogeneous();
554 /*SBasis vertex_poly = evaluate_at(hrq[0], hrq[1], hrq[2]);
555 std::vector<double> rts = roots(vertex_poly);
556 for(unsigned i = 0; i < rts.size(); i++) {
557 //draw_circ(cr, Point(rq.pointAt(rts[i])));
558 }*/
559 }
560 }
561 }
562 return std::optional<RatQuad>();
563}
564
565 std::vector<double> xAx::roots(Point d, Point o) const {
566 // Find the roots on line l
567 // form the quadratic Q(t) = 0 by composing l with xAx
568 double q2 = c[0]*d[0]*d[0] + c[1]*d[0]*d[1] + c[2]*d[1]*d[1];
569 double q1 = (2*c[0]*d[0]*o[0] +
570 c[1]*(d[0]*o[1]+d[1]*o[0]) +
571 2*c[2]*d[1]*o[1] +
572 c[3]*d[0] + c[4]*d[1]);
573 double q0 = c[0]*o[0]*o[0] + c[1]*o[0]*o[1] + c[2]*o[1]*o[1] + c[3]*o[0] + c[4]*o[1] + c[5];
574 std::vector<double> r;
575 if(q2 == 0) {
576 if(q1 == 0) {
577 return r;
578 }
579 r.push_back(-q0/q1);
580 } else {
581 double desc = q1*q1 - 4*q2*q0;
582 /*std::cout << q2 << ", "
583 << q1 << ", "
584 << q0 << "; "
585 << desc << "\n";*/
586 if (desc < 0)
587 return r;
588 else if (desc == 0)
589 r.push_back(-q1/(2*q2));
590 else {
591 desc = std::sqrt(desc);
592 double t;
593 if (q1 == 0)
594 {
595 t = -0.5 * desc;
596 }
597 else
598 {
599 t = -0.5 * (q1 + sgn(q1) * desc);
600 }
601 r.push_back(t/q2);
602 r.push_back(q0/t);
603 }
604 }
605 return r;
606}
607
608std::vector<double> xAx::roots(Line const &l) const {
609 return roots(l.versor(), l.origin());
610}
611
612Interval xAx::quad_ex(double a, double b, double c, Interval ivl) {
613 double cx = -b*0.5/a;
614 Interval bnds((a*ivl.min()+b)*ivl.min()+c, (a*ivl.max()+b)*ivl.max()+c);
615 if(ivl.contains(cx))
616 bnds.expandTo((a*cx+b)*cx+c);
617 return bnds;
618}
619
621 Geom::Affine m(2*c[0], c[1],
622 c[1], 2*c[2],
623 0, 0);
624 return m;
625}
626
627
628std::optional<Point> solve(double A[2][2], double b[2]) {
629 double const determ = det(A);
630 if (determ != 0.0) { // hopeful, I know
631 Geom::Coord const ideterm = 1.0 / determ;
632
633 return Point ((A[1][1]*b[0] -A[0][1]*b[1]),
634 (-A[1][0]*b[0] + A[0][0]*b[1]))* ideterm;
635 } else {
636 return std::optional<Point>();
637 }
638}
639
640std::optional<Point> xAx::bottom() const {
641 double A[2][2] = {{2*c[0], c[1]},
642 {c[1], 2*c[2]}};
643 double b[2] = {-c[3], -c[4]};
644 return solve(A, b);
645 //return Point(-c[3], -c[4])*hessian().inverse();
646}
647
649 if (c[0] == 0 && c[1] == 0 && c[2] == 0) {
650 Interval ext(valueAt(r.corner(0)));
651 for(int i = 1; i < 4; i++)
652 ext |= Interval(valueAt(r.corner(i)));
653 return ext;
654 }
655 double k = r[X].min();
656 Interval ext = quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[Y]);
657 k = r[X].max();
658 ext |= quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[Y]);
659 k = r[Y].min();
660 ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[X]);
661 k = r[Y].max();
662 ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[X]);
663 std::optional<Point> B0 = bottom();
664 if (B0 && r.contains(*B0))
665 ext.expandTo(0);
666 return ext;
667}
668
669
670
671
672
673
674
675
676
677/*
678 * helper functions
679 */
680
681bool at_infinity (Point const& p)
682{
683 if (p[X] == infinity() || p[X] == -infinity()
684 || p[Y] == infinity() || p[Y] == -infinity())
685 {
686 return true;
687 }
688 return false;
689}
690
691inline
692double signed_triangle_area (Point const& p1, Point const& p2, Point const& p3)
693{
694 return (cross(p2, p3) - cross(p1, p3) + cross(p1, p2));
695}
696
697
698
699/*
700 * Define a conic section by computing the one that fits better with
701 * N points.
702 *
703 * points: points to fit
704 *
705 * precondition: there must be at least 5 non-overlapping points
706 */
707void xAx::set(std::vector<Point> const& points)
708{
709 size_t sz = points.size();
710 if (sz < 5)
711 {
712 THROW_RANGEERROR("fitting error: too few points passed");
713 }
716
717 for (size_t i = 0; i < sz; ++i)
718 {
719 fitter.append(points[i]);
720 }
721 fitter.update();
722
723 NL::Vector z(sz, 0.0);
724 model.instance(*this, fitter.result(z));
725}
726
727/*
728 * Define a section conic by providing the coordinates of one of its vertex,
729 * the major axis inclination angle and the coordinates of its foci
730 * with respect to the unidimensional system defined by the major axis with
731 * origin set at the provided vertex.
732 *
733 * _vertex : section conic vertex V
734 * _angle : section conic major axis angle
735 * _dist1: +/-distance btw V and nearest focus
736 * _dist2: +/-distance btw V and farest focus
737 *
738 * prerequisite: _dist1 <= _dist2
739 */
740void xAx::set (const Point& _vertex, double _angle, double _dist1, double _dist2)
741{
742 using std::swap;
743
744 if (_dist2 == infinity() || _dist2 == -infinity()) // parabola
745 {
746 if (_dist1 == infinity()) // degenerate to a line
747 {
748 Line l(_vertex, _angle);
749 std::vector<double> lcoeff = l.coefficients();
750 coeff(3) = lcoeff[0];
751 coeff(4) = lcoeff[1];
752 coeff(5) = lcoeff[2];
753 return;
754 }
755
756 // y^2 - 4px == 0
757 double cD = -4 * _dist1;
758
759 double cosa = std::cos (_angle);
760 double sina = std::sin (_angle);
761 double cca = cosa * cosa;
762 double ssa = sina * sina;
763 double csa = cosa * sina;
764
765 coeff(0) = ssa;
766 coeff(1) = -2 * csa;
767 coeff(2) = cca;
768 coeff(3) = cD * cosa;
769 coeff(4) = cD * sina;
770
771 double VxVx = _vertex[X] * _vertex[X];
772 double VxVy = _vertex[X] * _vertex[Y];
773 double VyVy = _vertex[Y] * _vertex[Y];
774
775 coeff(5) = coeff(0) * VxVx + coeff(1) * VxVy + coeff(2) * VyVy
776 - coeff(3) * _vertex[X] - coeff(4) * _vertex[Y];
777 coeff(3) -= (2 * coeff(0) * _vertex[X] + coeff(1) * _vertex[Y]);
778 coeff(4) -= (2 * coeff(2) * _vertex[Y] + coeff(1) * _vertex[X]);
779
780 return;
781 }
782
783 if (std::fabs(_dist1) > std::fabs(_dist2))
784 {
785 swap (_dist1, _dist2);
786 }
787 if (_dist1 < 0)
788 {
789 _angle -= M_PI;
790 _dist1 = -_dist1;
791 _dist2 = -_dist2;
792 }
793
794 // ellipse and hyperbola
795 double lin_ecc = (_dist2 - _dist1) / 2;
796 double rx = (_dist2 + _dist1) / 2;
797
798 double cA = rx * rx - lin_ecc * lin_ecc;
799 double cC = rx * rx;
800 double cF = - cA * cC;
801// std::cout << "cA: " << cA << std::endl;
802// std::cout << "cC: " << cC << std::endl;
803// std::cout << "cF: " << cF << std::endl;
804
805 double cosa = std::cos (_angle);
806 double sina = std::sin (_angle);
807 double cca = cosa * cosa;
808 double ssa = sina * sina;
809 double csa = cosa * sina;
810
811 coeff(0) = cca * cA + ssa * cC;
812 coeff(2) = ssa * cA + cca * cC;
813 coeff(1) = 2 * csa * (cA - cC);
814
815 Point C (rx * cosa + _vertex[X], rx * sina + _vertex[Y]);
816 double CxCx = C[X] * C[X];
817 double CxCy = C[X] * C[Y];
818 double CyCy = C[Y] * C[Y];
819
820 coeff(3) = -2 * coeff(0) * C[X] - coeff(1) * C[Y];
821 coeff(4) = -2 * coeff(2) * C[Y] - coeff(1) * C[X];
822 coeff(5) = cF + coeff(0) * CxCx + coeff(1) * CxCy + coeff(2) * CyCy;
823}
824
825/*
826 * Define a conic section by providing one of its vertex and its foci.
827 *
828 * _vertex: section conic vertex
829 * _focus1: section conic focus
830 * _focus2: section conic focus
831 */
832void xAx::set (const Point& _vertex, const Point& _focus1, const Point& _focus2)
833{
834 if (at_infinity(_vertex))
835 {
836 THROW_RANGEERROR("case not handled: vertex at infinity");
837 }
838 if (at_infinity(_focus2))
839 {
840 if (at_infinity(_focus1))
841 {
842 THROW_RANGEERROR("case not handled: both focus at infinity");
843 }
844 Point VF = _focus1 - _vertex;
845 double dist1 = L2(VF);
846 double angle = atan2(VF);
847 set(_vertex, angle, dist1, infinity());
848 return;
849 }
850 else if (at_infinity(_focus1))
851 {
852 Point VF = _focus2 - _vertex;
853 double dist1 = L2(VF);
854 double angle = atan2(VF);
855 set(_vertex, angle, dist1, infinity());
856 return;
857 }
858 assert (are_collinear (_vertex, _focus1, _focus2));
859 if (!are_near(_vertex, _focus1))
860 {
861 Point VF = _focus1 - _vertex;
862 Line axis(_vertex, _focus1);
863 double angle = atan2(VF);
864 double dist1 = L2(VF);
865 double dist2 = distance (_vertex, _focus2);
866 double t = axis.timeAt(_focus2);
867 if (t < 0) dist2 = -dist2;
868// std::cout << "t = " << t << std::endl;
869// std::cout << "dist2 = " << dist2 << std::endl;
870 set (_vertex, angle, dist1, dist2);
871 }
872 else if (!are_near(_vertex, _focus2))
873 {
874 Point VF = _focus2 - _vertex;
875 double angle = atan2(VF);
876 double dist1 = 0;
877 double dist2 = L2(VF);
878 set (_vertex, angle, dist1, dist2);
879 }
880 else
881 {
882 coeff(0) = coeff(2) = 1;
883 coeff(1) = coeff(3) = coeff(4) = coeff(5) = 0;
884 }
885}
886
887/*
888 * Define a conic section by passing a focus, the related directrix,
889 * and the eccentricity (e)
890 * (e < 1 -> ellipse; e = 1 -> parabola; e > 1 -> hyperbola)
891 *
892 * _focus: a focus of the conic section
893 * _directrix: the directrix related to the given focus
894 * _eccentricity: the eccentricity parameter of the conic section
895 */
896void xAx::set (const Point & _focus, const Line & _directrix, double _eccentricity)
897{
898 Point O = _directrix.pointAt (_directrix.timeAtProjection (_focus));
899 //std::cout << "O = " << O << std::endl;
900 Point OF = _focus - O;
901 double p = L2(OF);
902
903 coeff(0) = 1 - _eccentricity * _eccentricity;
904 coeff(1) = 0;
905 coeff(2) = 1;
906 coeff(3) = -2 * p;
907 coeff(4) = 0;
908 coeff(5) = p * p;
909
910 double angle = atan2 (OF);
911
912 (*this) = rotate (angle);
913 //std::cout << "O = " << O << std::endl;
914 (*this) = translate (O);
915}
916
917/*
918 * Made up a degenerate conic section as a pair of lines
919 *
920 * l1, l2: lines that made up the conic section
921 */
922void xAx::set (const Line& l1, const Line& l2)
923{
924 std::vector<double> cl1 = l1.coefficients();
925 std::vector<double> cl2 = l2.coefficients();
926
927 coeff(0) = cl1[0] * cl2[0];
928 coeff(2) = cl1[1] * cl2[1];
929 coeff(5) = cl1[2] * cl2[2];
930 coeff(1) = cl1[0] * cl2[1] + cl1[1] * cl2[0];
931 coeff(3) = cl1[0] * cl2[2] + cl1[2] * cl2[0];
932 coeff(4) = cl1[1] * cl2[2] + cl1[2] * cl2[1];
933}
934
935
936
937/*
938 * Return the section conic kind
939 */
941{
942
943 xAx conic(*this);
946
947 double t1 = trace(A);
948 double t2 = det(A);
949 //double T3 = det(C);
950 int st1 = trace_sgn(A);
951 int st2 = det_sgn(A);
952 int sT3 = det_sgn(C);
953
954 //std::cout << "T3 = " << T3 << std::endl;
955 //std::cout << "sT3 = " << sT3 << std::endl;
956 //std::cout << "t2 = " << t2 << std::endl;
957 //std::cout << "t1 = " << t1 << std::endl;
958 //std::cout << "st2 = " << st2 << std::endl;
959
960 if (sT3 != 0)
961 {
962 if (st2 == 0)
963 {
964 return PARABOLA;
965 }
966 else if (st2 == 1)
967 {
968
969 if (sT3 * st1 < 0)
970 {
972 discr(0,0) = 4; discr(1,1) = t2; discr(1,0) = t1;
973 int discr_sgn = - det_sgn (discr);
974 //std::cout << "t1 * t1 - 4 * t2 = "
975 // << (t1 * t1 - 4 * t2) << std::endl;
976 //std::cout << "discr_sgn = " << discr_sgn << std::endl;
977 if (discr_sgn == 0)
978 {
979 return CIRCLE;
980 }
981 else
982 {
983 return REAL_ELLIPSE;
984 }
985 }
986 else // sT3 * st1 > 0
987 {
988 return IMAGINARY_ELLIPSE;
989 }
990 }
991 else // t2 < 0
992 {
993 if (st1 == 0)
994 {
996 }
997 else
998 {
999 return HYPERBOLA;
1000 }
1001 }
1002 }
1003 else // T3 == 0
1004 {
1005 if (st2 == 0)
1006 {
1007 //double T2 = NL::trace<2>(C);
1008 int sT2 = NL::trace_sgn<2>(C);
1009 //std::cout << "T2 = " << T2 << std::endl;
1010 //std::cout << "sT2 = " << sT2 << std::endl;
1011
1012 if (sT2 == 0)
1013 {
1014 return DOUBLE_LINE;
1015 }
1016 if (sT2 == -1)
1017 {
1019 }
1020 else // T2 > 0
1021 {
1023 }
1024 }
1025 else if (st2 == -1)
1026 {
1028 }
1029 else // t2 > 0
1030 {
1032 }
1033 }
1034 return UNKNOWN;
1035}
1036
1037/*
1038 * Return a string representing the conic section kind
1039 */
1040std::string xAx::categorise() const
1041{
1042 kind_t KIND = kind();
1043
1044 switch (KIND)
1045 {
1046 case PARABOLA :
1047 return "parabola";
1048 case CIRCLE :
1049 return "circle";
1050 case REAL_ELLIPSE :
1051 return "real ellispe";
1052 case IMAGINARY_ELLIPSE :
1053 return "imaginary ellispe";
1055 return "rectangular hyperbola";
1056 case HYPERBOLA :
1057 return "hyperbola";
1058 case DOUBLE_LINE :
1059 return "double line";
1061 return "two real parallel lines";
1063 return "two imaginary parallel lines";
1065 return "two real crossing lines";
1067 return "two imaginary crossing lines";
1068 default :
1069 return "unknown";
1070 }
1071}
1072
1073/*
1074 * Compute the solutions of the conic section algebraic equation with respect to
1075 * one coordinate after substituting to the other coordinate the passed value
1076 *
1077 * sol: the computed solutions
1078 * v: the provided value
1079 * d: the index of the coordinate the passed value have to be substituted to
1080 */
1081void xAx::roots (std::vector<double>& sol, Coord v, Dim2 d) const
1082{
1083 sol.clear();
1084 if (d < 0 || d > Y)
1085 {
1086 THROW_RANGEERROR("dimension parameter out of range");
1087 }
1088
1089 // p*t^2 + q*t + r = 0;
1090 double p, q, r;
1091
1092 if (d == X)
1093 {
1094 p = coeff(2);
1095 q = coeff(4) + coeff(1) * v;
1096 r = coeff(5) + (coeff(0) * v + coeff(3)) * v;
1097 }
1098 else
1099 {
1100 p = coeff(0);
1101 q = coeff(3) + coeff(1) * v;
1102 r = coeff(5) + (coeff(2) * v + coeff(4)) * v;
1103 }
1104
1105 if (p == 0)
1106 {
1107 if (q == 0) return;
1108 double t = -r/q;
1109 sol.push_back(t);
1110 return;
1111 }
1112
1113 if (q == 0)
1114 {
1115 if ((p > 0 && r > 0) || (p < 0 && r < 0)) return;
1116 double t = -r / p;
1117 t = std::sqrt (t);
1118 sol.push_back(-t);
1119 sol.push_back(t);
1120 return;
1121 }
1122
1123 if (r == 0)
1124 {
1125 double t = -q/p;
1126 sol.push_back(0);
1127 sol.push_back(t);
1128 return;
1129 }
1130
1131
1132 //std::cout << "p = " << p << ", q = " << q << ", r = " << r << std::endl;
1133 double delta = q * q - 4 * p * r;
1134 if (delta < 0) return;
1135 if (delta == 0)
1136 {
1137 double t = -q / (2 * p);
1138 sol.push_back(t);
1139 return;
1140 }
1141 // else
1142 double srd = std::sqrt(delta);
1143 double t = - (q + sgn(q) * srd) / 2;
1144 sol.push_back (t/p);
1145 sol.push_back (r/t);
1146
1147}
1148
1149/*
1150 * Return the inclination angle of the major axis of the conic section
1151 */
1152double xAx::axis_angle() const
1153{
1154 if (coeff(0) == 0 && coeff(1) == 0 && coeff(2) == 0)
1155 {
1156 Line l (coeff(3), coeff(4), coeff(5));
1157 return l.angle();
1158 }
1159 if (coeff(1) == 0 && (coeff(0) == coeff(2))) return 0;
1160
1161 double angle;
1162
1163 int sgn_discr = det_sgn (get_matrix().main_minor_const_view());
1164 if (sgn_discr == 0)
1165 {
1166 //std::cout << "rotation_angle: sgn_discr = "
1167 // << sgn_discr << std::endl;
1168 angle = std::atan2 (-coeff(1), 2 * coeff(2));
1169 if (angle < 0) angle += 2*M_PI;
1170 if (angle >= M_PI) angle -= M_PI;
1171
1172 }
1173 else
1174 {
1175 angle = std::atan2 (coeff(1), coeff(0) - coeff(2));
1176 if (angle < 0) angle += 2*M_PI;
1177 angle -= M_PI;
1178 if (angle < 0) angle += 2*M_PI;
1179 angle /= 2;
1180 if (angle >= M_PI) angle -= M_PI;
1181 }
1182 //std::cout << "rotation_angle : angle = " << angle << std::endl;
1183 return angle;
1184}
1185
1186/*
1187 * Translate the conic section by the given vector offset
1188 *
1189 * _offset: represent the vector offset
1190 */
1191xAx xAx::translate (const Point & _offset) const
1192{
1193 double B = coeff(1) / 2;
1194 double D = coeff(3) / 2;
1195 double E = coeff(4) / 2;
1196
1197 Point T = - _offset;
1198
1199 xAx cs;
1200 cs.coeff(0) = coeff(0);
1201 cs.coeff(1) = coeff(1);
1202 cs.coeff(2) = coeff(2);
1203
1204 Point DE;
1205 DE[0] = coeff(0) * T[0] + B * T[1];
1206 DE[1] = B * T[0] + coeff(2) * T[1];
1207
1208 cs.coeff(3) = (DE[0] + D) * 2;
1209 cs.coeff(4) = (DE[1] + E) * 2;
1210
1211 cs.coeff(5) = dot (T, DE) + 2 * (T[0] * D + T[1] * E) + coeff(5);
1212
1213 return cs;
1214}
1215
1216
1217/*
1218 * Rotate the conic section by the given angle wrt the point (0,0)
1219 *
1220 * angle: represent the rotation angle
1221 */
1222xAx xAx::rotate (double angle) const
1223{
1224 double c = std::cos(-angle);
1225 double s = std::sin(-angle);
1226 double cc = c * c;
1227 double ss = s * s;
1228 double cs = c * s;
1229
1230 xAx result;
1231 result.coeff(5) = coeff(5);
1232
1233 // quadratic terms
1234 double Bcs = coeff(1) * cs;
1235
1236 result.coeff(0) = coeff(0) * cc + Bcs + coeff(2) * ss;
1237 result.coeff(2) = coeff(0) * ss - Bcs + coeff(2) * cc;
1238 result.coeff(1) = coeff(1) * (cc - ss) + 2 * (coeff(2) - coeff(0)) * cs;
1239
1240 // linear terms
1241 result.coeff(3) = coeff(3) * c + coeff(4) * s;
1242 result.coeff(4) = coeff(4) * c - coeff(3) * s;
1243
1244 return result;
1245}
1246
1247
1248/*
1249 * Decompose a degenerate conic in two lines the conic section is made by.
1250 * Return true if the decomposition is successful, else if it fails.
1251 *
1252 * l1, l2: out parameters where the decomposed conic section is returned
1253 */
1254bool xAx::decompose (Line& l1, Line& l2) const
1255{
1257 if (!is_quadratic() || !isDegenerate())
1258 {
1259 return false;
1260 }
1261 NL::Matrix M(C);
1262 NL::SymmetricMatrix<3> D = -adj(C);
1263
1264 if (!D.is_zero()) // D == 0 <=> rank(C) < 2
1265 {
1266
1267 //if (D.get<0,0>() < 0 || D.get<1,1>() < 0 || D.get<2,2>() < 0)
1268 //{
1269 //std::cout << "C: \n" << C << std::endl;
1270 //std::cout << "D: \n" << D << std::endl;
1271
1272 /*
1273 * This case should be impossible because any diagonal element
1274 * of D is a square, but due to non exact aritmethic computation
1275 * it can actually happen; however the algorithm seems to work
1276 * correctly even if some diagonal term is negative, the only
1277 * difference is that we should compute the absolute value of
1278 * diagonal elements. So until we elaborate a better degenerate
1279 * test it's better not rising exception when we have a negative
1280 * diagonal element.
1281 */
1282 //}
1283
1284 NL::Vector d(3);
1285 d[0] = std::fabs (D.get<0,0>());
1286 d[1] = std::fabs (D.get<1,1>());
1287 d[2] = std::fabs (D.get<2,2>());
1288
1289 size_t idx = d.max_index();
1290 if (d[idx] == 0)
1291 {
1292 THROW_LOGICALERROR ("xAx::decompose: "
1293 "rank 2 but adjoint with null diagonal");
1294 }
1295 d[0] = D(idx,0); d[1] = D(idx,1); d[2] = D(idx,2);
1296 d.scale (1 / std::sqrt (std::fabs (D(idx,idx))));
1297 M(1,2) += d[0]; M(2,1) -= d[0];
1298 M(0,2) -= d[1]; M(2,0) += d[1];
1299 M(0,1) += d[2]; M(1,0) -= d[2];
1300
1301 //std::cout << "C: \n" << C << std::endl;
1302 //std::cout << "D: \n" << D << std::endl;
1303 //std::cout << "d = " << d << std::endl;
1304 //std::cout << "M = " << M << std::endl;
1305 }
1306
1307 std::pair<size_t, size_t> max_ij = M.max_index();
1308 std::pair<size_t, size_t> min_ij = M.min_index();
1309 double abs_max = std::fabs (M(max_ij.first, max_ij.second));
1310 double abs_min = std::fabs (M(min_ij.first, min_ij.second));
1311 size_t i_max, j_max;
1312 if (abs_max > abs_min)
1313 {
1314 i_max = max_ij.first;
1315 j_max = max_ij.second;
1316 }
1317 else
1318 {
1319 i_max = min_ij.first;
1320 j_max = min_ij.second;
1321 }
1322 l1.setCoefficients (M(i_max,0), M(i_max,1), M(i_max,2));
1323 l2.setCoefficients (M(0, j_max), M(1,j_max), M(2,j_max));
1324
1325 return true;
1326}
1327
1328std::array<Line, 2> xAx::decompose_df(Coord epsilon) const
1329{
1330 // For the classification of degenerate conics, see https://mathworld.wolfram.com/QuadraticCurve.html
1331 using std::sqrt, std::abs;
1332
1333 // Create 2 degenerate lines
1334 auto const origin = Point(0, 0);
1335 std::array<Line, 2> result = {Line(origin, origin), Line(origin, origin)};
1336
1337 double A = c[0];
1338 double B = c[1];
1339 double C = c[2];
1340 double D = c[3];
1341 double E = c[4];
1342 double F = c[5];
1343 Coord discriminant = sqr(B) - 4 * A * C;
1344 if (discriminant < -epsilon) {
1345 return result;
1346 }
1347
1348 bool single_line = false; // In the generic case, there will be 2 lines.
1349 bool parallel_lines = false;
1350 if (discriminant < epsilon) {
1351 discriminant = 0;
1352 parallel_lines = true;
1353 // Check the secondary discriminant
1354 Coord const secondary = sqr(D) + sqr(E) - 4 * F * (A + C);
1355 if (secondary < -epsilon) {
1356 return result;
1357 }
1358 single_line = (secondary < epsilon);
1359 }
1360
1361 if (abs(A) > epsilon || abs(C) > epsilon) {
1362 // This is the typical case: either x² or y² come with a nonzero coefficient.
1363 // To guard against numerical errors, we check which of the coefficients A, C has larger absolute value.
1364
1365 bool const swap_xy = abs(C) > abs(A);
1366 if (swap_xy) {
1367 std::swap(A, C);
1368 std::swap(D, E);
1369 }
1370
1371 // From now on, we may assume that A is "reasonably large".
1372 if (parallel_lines) {
1373 if (single_line) {
1374 // Special case: a single line.
1375 std::array<double, 3> coeffs = {sqrt(abs(A)), sqrt(abs(C)), sqrt(abs(F))};
1376 if (swap_xy) {
1377 std::swap(coeffs[0], coeffs[1]);
1378 }
1379 rescale_homogenous(coeffs);
1380 result[0].setCoefficients(coeffs[0], coeffs[1], coeffs[2]);
1381 return result;
1382 }
1383
1384 // Two parallel lines.
1385 Coord const quotient_discriminant = sqr(D) - 4 * A * F;
1386 if (quotient_discriminant < 0) {
1387 return result;
1388 }
1389 Coord const sqrt_disc = sqrt(quotient_discriminant);
1390 double const c1 = 0.5 * (D - sqrt_disc);
1391 double const c2 = c1 + sqrt_disc;
1392 std::array<double, 3> coeffs = {A, 0.5 * B, c1};
1393 if (swap_xy) {
1394 std::swap(coeffs[0], coeffs[1]);
1395 }
1396 rescale_homogenous(coeffs);
1397 result[0].setCoefficients(coeffs[0], coeffs[1], coeffs[2]);
1398
1399 coeffs = {A, 0.5 * B, c2};
1400 if (swap_xy) {
1401 std::swap(coeffs[0], coeffs[1]);
1402 }
1403 rescale_homogenous(coeffs);
1404 result[1].setCoefficients(coeffs[0], coeffs[1], coeffs[2]);
1405 return result;
1406 }
1407
1408 // Now for the typical case of 2 non-parallel lines.
1409
1410 // We know that A is further away from 0 than C is.
1411 // The mathematical derivation of the solution is as follows:
1412 // let Δ = B² - 4AC (the discriminant); we know Δ > 0.
1413 // Write δ = sqrt(Δ); we know that this is also positive.
1414 // Then the product AΔ is nonzero, so the equation
1415 // Ax² + Bxy + Cy² + Dx + Ey + F = 0
1416 // is equivalent to
1417 // AΔ (Ax² + Bxy + Cy² + Dx + Ey + F) = 0.
1418 // Consider the two factors
1419 // L_1 = Aδx + 0.5 (Bδ-Δ)y + EA - 0.5 D(B-δ)
1420 // L_2 = Aδx + 0.5 (Bδ+Δ)y - EA + 0.5 D(B+δ)
1421 // With a bit of algebra, you can show that L_1 * L_2 expands
1422 // to AΔ (Ax² + Bxy + Cy² + Dx + Ey + F) (in order to get the
1423 // correct value of F, you have to use the fact that the conic
1424 // is degenerate). Therefore, the factors L_1 and L_2 are in
1425 // fact equations of the two lines to be found.
1426 Coord const delta = sqrt(discriminant);
1427 std::array<double, 3> coeffs1 = {A * delta, 0.5 * (B * delta - discriminant), E * A - 0.5 * D * (B - delta)};
1428 std::array<double, 3> coeffs2 = {coeffs1[0], coeffs1[1] + discriminant, D * delta - coeffs1[2]};
1429 if (swap_xy) { // We must unswap the coefficients of x and y
1430 std::swap(coeffs1[0], coeffs1[1]);
1431 std::swap(coeffs2[0], coeffs2[1]);
1432 }
1433
1434 unsigned index = 0;
1435 if (coeffs1[0] != 0 || coeffs1[1] != 0) {
1436 rescale_homogenous(coeffs1);
1437 result[index++].setCoefficients(coeffs1[0], coeffs1[1], coeffs1[2]);
1438 }
1439 if (coeffs2[0] != 0 || coeffs2[1] != 0) {
1440 rescale_homogenous(coeffs2);
1441 result[index].setCoefficients(coeffs2[0], coeffs2[1], coeffs2[2]);
1442 }
1443 return result;
1444 }
1445
1446 // If we're here, then A==0 and C==0.
1447 if (abs(B) < epsilon) { // A == B == C == 0, so the conic reduces to Dx + Ey + F.
1448 if (D == 0 && E == 0) {
1449 return result;
1450 }
1451 std::array<double, 3> coeffs = {D, E, F};
1452 rescale_homogenous(coeffs);
1453 result[0].setCoefficients(coeffs[0], coeffs[1], coeffs[2]);
1454 return result;
1455 }
1456
1457 // OK, so A == C == 0 but B != 0. In other words, the conic has the form
1458 // Bxy + Dx + Ey + F. Since B != 0, the zero set stays the same if we multiply the
1459 // equation by B, which gives us this equation:
1460 // B²xy + BDx + BEy + BF = 0.
1461 // The above factors as (Bx + E)(By + D) = 0.
1462 std::array<double, 2> nonzero_coeffs = {B, E};
1463 rescale_homogenous(nonzero_coeffs);
1464 result[0].setCoefficients(nonzero_coeffs[0], 0, nonzero_coeffs[1]);
1465
1466 nonzero_coeffs = {B, D};
1467 rescale_homogenous(nonzero_coeffs);
1468 result[1].setCoefficients(0, nonzero_coeffs[0], nonzero_coeffs[1]);
1469 return result;
1470}
1471
1472/*
1473 * Return the rectangle that bound the conic section arc characterized by
1474 * the passed points.
1475 *
1476 * P1: the initial point of the arc
1477 * Q: the inner point of the arc
1478 * P2: the final point of the arc
1479 *
1480 * prerequisite: the passed points must lie on the conic
1481 */
1482Rect xAx::arc_bound (const Point & P1, const Point & Q, const Point & P2) const
1483{
1484 using std::swap;
1485 //std::cout << "BOUND: P1 = " << P1 << std::endl;
1486 //std::cout << "BOUND: Q = " << Q << std::endl;
1487 //std::cout << "BOUND: P2 = " << P2 << std::endl;
1488
1489 Rect B(P1, P2);
1490 double Qside = signed_triangle_area (P1, Q, P2);
1491 //std::cout << "BOUND: Qside = " << Qside << std::endl;
1492
1493 Line gl[2];
1494 bool empty[2] = {false, false};
1495
1496 try // if the passed coefficients lead to an equation 0x + 0y + c == 0,
1497 { // with c != 0 the setCoefficients rise an exception
1498 gl[0].setCoefficients (coeff(1), 2 * coeff(2), coeff(4));
1499 }
1500 catch(Geom::LogicalError const &e)
1501 {
1502 empty[0] = true;
1503 }
1504
1505 try
1506 {
1507 gl[1].setCoefficients (2 * coeff(0), coeff(1), coeff(3));
1508 }
1509 catch(Geom::LogicalError const &e)
1510 {
1511 empty[1] = true;
1512 }
1513
1514 std::vector<double> rts;
1515 std::vector<Point> M;
1516 for (size_t dim = 0; dim < 2; ++dim)
1517 {
1518 if (empty[dim]) continue;
1519 rts = roots (gl[dim]);
1520 M.clear();
1521 for (double rt : rts)
1522 M.push_back (gl[dim].pointAt (rt));
1523 if (M.size() == 1)
1524 {
1525 double Mside = signed_triangle_area (P1, M[0], P2);
1526 if (sgn(Mside) == sgn(Qside))
1527 {
1528 //std::cout << "BOUND: M.size() == 1" << std::endl;
1529 B[dim].expandTo(M[0][dim]);
1530 }
1531 }
1532 else if (M.size() == 2)
1533 {
1534 //std::cout << "BOUND: M.size() == 2" << std::endl;
1535 if (M[0][dim] > M[1][dim])
1536 swap (M[0], M[1]);
1537
1538 if (M[0][dim] > B[dim].max())
1539 {
1540 double Mside = signed_triangle_area (P1, M[0], P2);
1541 if (sgn(Mside) == sgn(Qside))
1542 B[dim].setMax(M[0][dim]);
1543 }
1544 else if (M[1][dim] < B[dim].min())
1545 {
1546 double Mside = signed_triangle_area (P1, M[1], P2);
1547 if (sgn(Mside) == sgn(Qside))
1548 B[dim].setMin(M[1][dim]);
1549 }
1550 else
1551 {
1552 double Mside = signed_triangle_area (P1, M[0], P2);
1553 if (sgn(Mside) == sgn(Qside))
1554 B[dim].setMin(M[0][dim]);
1555 Mside = signed_triangle_area (P1, M[1], P2);
1556 if (sgn(Mside) == sgn(Qside))
1557 B[dim].setMax(M[1][dim]);
1558 }
1559 }
1560 }
1561
1562 return B;
1563}
1564
1565/*
1566 * Return all points on the conic section nearest to the passed point "P".
1567 *
1568 * P: the point to compute the nearest one
1569 */
1570std::vector<Point> xAx::allNearestTimes (const Point &P) const
1571{
1572 // TODO: manage the circle - centre case
1573 std::vector<Point> points;
1574
1575 // named C the conic we look for points (x,y) on C such that
1576 // dot (grad (C(x,y)), rot90 (P -(x,y))) == 0; the set of points satisfying
1577 // this equation is still a conic G, so the wanted points can be found by
1578 // intersecting C with G
1579 xAx G (-coeff(1),
1580 2 * (coeff(0) - coeff(2)),
1581 coeff(1),
1582 -coeff(4) + coeff(1) * P[X] - 2 * coeff(0) * P[Y],
1583 coeff(3) - coeff(1) * P[Y] + 2 * coeff(2) * P[X],
1584 -coeff(3) * P[Y] + coeff(4) * P[X]);
1585
1586 std::vector<Point> crs = intersect (*this, G);
1587
1588 //std::cout << "NEAREST POINT: crs.size = " << crs.size() << std::endl;
1589 if (crs.empty()) return points;
1590
1591 size_t idx = 0;
1592 double mindist = distanceSq (crs[0], P);
1593 std::vector<double> dist;
1594 dist.push_back (mindist);
1595
1596 for (size_t i = 1; i < crs.size(); ++i)
1597 {
1598 dist.push_back (distanceSq (crs[i], P));
1599 if (mindist > dist.back())
1600 {
1601 idx = i;
1602 mindist = dist.back();
1603 }
1604 }
1605
1606 points.push_back (crs[idx]);
1607 for (size_t i = 0; i < crs.size(); ++i)
1608 {
1609 if (i == idx) continue;
1610 if (dist[i] == mindist)
1611 points.push_back (crs[i]);
1612 }
1613
1614 return points;
1615}
1616
1617
1618
1619bool clip (std::vector<RatQuad> & rq, const xAx & cs, const Rect & R)
1620{
1621 clipper aclipper (cs, R);
1622 return aclipper.clip (rq);
1623}
1624
1625
1626} // end namespace Geom
1627
1628
1629
1630
1631/*
1632 Local Variables:
1633 mode:c++
1634 c-file-style:"stroustrup"
1635 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1636 indent-tabs-mode:nil
1637 fill-column:99
1638 End:
1639*/
1640// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
pair< double, double > Point
Definition parser.cpp:7
Point origin
Definition aa.cpp:227
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord det() const
Calculate the determinant.
Definition affine.cpp:416
D2< SBasis > toSBasis() const override
Convert the curve to a symmetric power basis polynomial.
Point pointAt(Coord t) const override
Evaluate the curve at a specified time value.
Polynomial in Bernstein-Bezier basis.
Definition bezier.h:126
Coord valueAt(double t) const
Definition bezier.h:277
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
Point valueAt(double t) const
Definition d2.h:133
constexpr void expandTo(C val)
Extend the interval to include the given number.
constexpr C min() const
constexpr C max() const
constexpr bool contains(C val) const
Check whether the interval includes this number.
void setMin(CPoint const &p)
Set the upper left point of the rectangle.
bool contains(GenericRect< C > const &r) const
Check whether the rectangle includes all points in the given rectangle.
void setMax(CPoint const &p)
Set the lower right point of the rectangle.
void expandTo(CPoint const &p)
Enlarge the rectangle to contain the given point.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint corner(unsigned i) const
Return the n-th corner of the rectangle.
CPoint max() const
Get the corner of the rectangle with largest coordinate values.
Range of real numbers that is never empty.
Definition interval.h:59
Infinite line on a plane.
Definition line.h:53
void setCoefficients(double a, double b, double c)
Set the coefficients of the line equation.
Definition line.cpp:62
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
Point origin() const
Get the line's origin point.
Definition line.h:128
std::optional< LineSegment > clip(Rect const &r) const
Return the portion of the line that is inside the given rectangle.
Definition line.cpp:151
Coord timeAtProjection(Point const &p) const
Get a time value corresponding to a projection of a point on the line.
Definition line.h:244
Point normalAndDist(double &dist) const
Definition line.h:325
static Line from_origin_and_vector(Point const &o, Point const &v)
Create a line from origin and unit vector.
Definition line.h:114
Coord angle() const
Angle the line makes with the X axis, in mathematical convention.
Definition line.h:137
Point pointAt(Coord t) const
Definition line.h:231
Point versor() const
Get the line's normalized direction vector.
Definition line.h:135
Function that interpolates linearly between two values.
Definition linear.h:55
ConstSymmetricMatrixView< N-1 > main_minor_const_view() const
void instance(xAx &c, ConstVectorView const &coeff) const
const Vector & SV_solve()
Vector & scale(double x)
Definition vector.h:319
std::pair< size_t, size_t > max_index() const
Definition matrix.h:137
std::pair< size_t, size_t > min_index() const
Definition matrix.h:145
VectorView row_view(size_t i)
Definition matrix.h:253
Two-dimensional point that doubles as a vector.
Definition point.h:66
void split(RatQuad &a, RatQuad &b) const
Definition conicsec.cpp:175
D2< SBasis > hermite() const
Definition conicsec.cpp:185
static RatQuad circularArc(Point P0, Point P1, Point P2)
Definition conicsec.cpp:151
static RatQuad fromPointsTangents(Point P0, Point dP0, Point P, Point P2, Point dP2)
Definition conicsec.cpp:115
double lambda() const
Definition conicsec.cpp:111
CubicBezier toCubic() const
Definition conicsec.cpp:156
Point pointAt(double t) const
Definition conicsec.cpp:167
Point P[3]
A curve of the form B02*A + B12*B*w + B22*C/(B02 + B12*w + B22) These curves can exactly represent a ...
Definition conicsec.h:68
std::vector< SBasis > homogeneous() const
Definition conicsec.cpp:197
Axis aligned, non-empty rectangle.
Definition rect.h:92
Polynomial in symmetric power basis.
Definition sbasis.h:70
double valueAt(double t) const
Definition sbasis.h:219
std::vector< Point > allNearestTimes(const Point &P) const
xAx operator*(double const &b) const
Definition conicsec.cpp:489
static xAx fromDistPoint(Point p, double d)
Definition conicsec.cpp:413
bool decompose(Line &l1, Line &l2) const
void set(double c0, double c1, double c2, double c3, double c4, double c5)
Definition conicsec.h:212
std::string categorise() const
Definition conicsec.cpp:209
@ IMAGINARY_ELLIPSE
Definition conicsec.h:108
@ TWO_IMAGINARY_CROSSING_LINES
Definition conicsec.h:115
@ TWO_REAL_PARALLEL_LINES
Definition conicsec.h:112
@ REAL_ELLIPSE
Definition conicsec.h:107
@ TWO_REAL_CROSSING_LINES
Definition conicsec.h:114
@ TWO_IMAGINARY_PARALLEL_LINES
Definition conicsec.h:113
@ DOUBLE_LINE
Definition conicsec.h:111
@ RECTANGULAR_HYPERBOLA
Definition conicsec.h:109
static Interval quad_ex(double a, double b, double c, Interval ivl)
Definition conicsec.cpp:612
static xAx fromPoints(std::vector< Point > const &pts)
Definition conicsec.cpp:428
Rect arc_bound(const Point &P1, const Point &Q, const Point &P2) const
kind_t kind() const
Definition conicsec.cpp:940
static xAx fromPoint(Point p)
Definition conicsec.cpp:409
double axis_angle() const
std::array< Line, 2 > decompose_df(Coord epsilon=EPSILON) const
Division-free decomposition of a degenerate conic section, without degeneration test.
bool is_quadratic() const
Definition conicsec.h:323
xAx scale(double sx, double sy) const
Definition conicsec.cpp:454
xAx translate(const Point &_offset) const
double coeff(size_t i) const
Definition conicsec.h:300
Point gradient(Point p) const
Definition conicsec.cpp:459
double c[6]
Definition conicsec.h:101
std::vector< double > roots(Point d, Point o) const
Definition conicsec.cpp:565
static xAx fromLine(Point n, double d)
Definition conicsec.cpp:417
NL::SymmetricMatrix< 3 > get_matrix() const
Definition conicsec.h:289
double valueAt(Point P) const
Definition conicsec.cpp:450
xAx rotate(double angle) const
bool isDegenerate() const
Definition conicsec.h:332
Interval extrema(Rect r) const
Definition conicsec.cpp:648
T evaluate_at(T x, T y) const
Definition conicsec.h:246
std::optional< Point > bottom() const
Definition conicsec.cpp:640
std::optional< RatQuad > toCurve(Rect const &bnd) const
Definition conicsec.cpp:511
xAx operator-(xAx const &b) const
Definition conicsec.cpp:466
Geom::Affine hessian() const
Definition conicsec.cpp:620
xAx operator+(xAx const &b) const
Definition conicsec.cpp:473
std::vector< Point > crossings(Rect r) const
Definition conicsec.cpp:497
Conic section clipping with respect to a rectangle.
Conic Section.
Css & result
double c[8][4]
BezierCurveN< 3 > CubicBezier
Cubic (order 3) Bezier curve.
BezierCurveN< 1 > LineSegment
Line segment.
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
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Various utility functions.
Definition affine.h:22
Coord L1(Point const &p)
SBasisN< n > divide(SBasisN< n > const &a, SBasisN< n > const &b, int k)
int sgn(const T &x)
Sign function - indicates the sign of a numeric type.
Definition math-utils.h:51
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
Definition sbasisN.h:158
double signed_triangle_area(Point const &p1, Point const &p2, Point const &p3)
std::vector< Point > decompose_degenerate(xAx const &C1, xAx const &C2, xAx const &xC0)
Definition conicsec.cpp:238
bool at_infinity(Point const &p)
Definition conicsec.cpp:681
double xAx_descr(xAx const &C)
Definition conicsec.cpp:352
Coord distanceSq(Point const &p, Rect const &rect)
Definition rect.cpp:158
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
std::optional< Crossing > OptCrossing
Definition crossing.h:64
double atan2(Point const &p)
OptCrossing intersection(Ray const &r1, Line const &l2)
Definition line.h:545
SBasisN< n > sqrt(SBasisN< n > const &a, int k)
Line make_bisector_line(LineSegment const &_segment)
Definition line.h:497
static double boxprod(Point a, Point b, Point c)
Definition conicsec.cpp:80
static T det3(T M[3][3])
Definition conicsec.cpp:71
std::vector< double > roots(SBasis const &s)
std::vector< std::complex< double > > solve(const Poly &p)
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
std::string stringify(T x)
Definition conicsec.cpp:92
T sqr(const T &x)
Definition math-utils.h:57
bool clip(std::vector< RatQuad > &rq, const xAx &cs, const Rect &R)
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
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
T dot(D2< T > const &a, D2< T > const &b)
Definition d2.h:355
Point unit_vector(Point const &a)
static double det(Point a, Point b)
Definition conicsec.cpp:56
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
std::vector< Point > intersect(const xAx &C1, const xAx &C2)
Definition conicsec.cpp:361
Point abs(Point const &b)
SBasis bezier_to_sbasis(Coord const *handles, unsigned order)
STL namespace.
unsigned n1
int delta
int index