GCC Code Coverage Report


Directory: ./
File: src/2geom/sbasis-geometric.cpp
Date: 2024-03-18 17:01:34
Exec Total Coverage
Lines: 51 375 13.6%
Functions: 4 32 12.5%
Branches: 44 646 6.8%

Line Branch Exec Source
1 /** Geometric operators on D2<SBasis> (1D->2D).
2 * Copyright 2012 JBC Engelen
3 * Copyright 2007 JF Barraud
4 * Copyright 2007 N Hurst
5 *
6 * The functions defined in this header related to 2d geometric operations such as arc length,
7 * unit_vector, curvature, and centroid. Most are built on top of unit_vector, which takes an
8 * arbitrary D2 and returns a D2 with unit length with the same direction.
9 *
10 * Todo/think about:
11 * arclength D2 -> sbasis (giving arclength function)
12 * does uniform_speed return natural parameterisation?
13 * integrate sb2d code from normal-bundle
14 * angle(md<2>) -> sbasis (gives angle from vector - discontinuous?)
15 * osculating circle center?
16 *
17 **/
18
19 #include <2geom/sbasis-geometric.h>
20 #include <2geom/sbasis.h>
21 #include <2geom/sbasis-math.h>
22 #include <2geom/sbasis-geometric.h>
23
24 //namespace Geom{
25 using namespace Geom;
26 using namespace std;
27
28 //Some utils first.
29 //TODO: remove this!!
30 /**
31 * Return a list of doubles that appear in both a and b to within error tol
32 * a, b, vector of double
33 * tol tolerance
34 */
35 static vector<double>
36 vect_intersect(vector<double> const &a, vector<double> const &b, double tol=0.){
37 vector<double> inter;
38 unsigned i=0,j=0;
39 while ( i<a.size() && j<b.size() ){
40 if (fabs(a[i]-b[j])<tol){
41 inter.push_back(a[i]);
42 i+=1;
43 j+=1;
44 }else if (a[i]<b[j]){
45 i+=1;
46 }else if (a[i]>b[j]){
47 j+=1;
48 }
49 }
50 return inter;
51 }
52
53 //------------------------------------------------------------------------------
54 static SBasis divide_by_sk(SBasis const &a, int k) {
55 if ( k>=(int)a.size()){
56 //make sure a is 0?
57 return SBasis();
58 }
59 if(k < 0) return shift(a,-k);
60 SBasis c;
61 c.insert(c.begin(), a.begin()+k, a.end());
62 return c;
63 }
64
65 static SBasis divide_by_t0k(SBasis const &a, int k) {
66 if(k < 0) {
67 SBasis c = Linear(0,1);
68 for (int i=2; i<=-k; i++){
69 c*=c;
70 }
71 c*=a;
72 return(c);
73 }else{
74 SBasis c = Linear(1,0);
75 for (int i=2; i<=k; i++){
76 c*=c;
77 }
78 c*=a;
79 return(divide_by_sk(c,k));
80 }
81 }
82
83 static SBasis divide_by_t1k(SBasis const &a, int k) {
84 if(k < 0) {
85 SBasis c = Linear(1,0);
86 for (int i=2; i<=-k; i++){
87 c*=c;
88 }
89 c*=a;
90 return(c);
91 }else{
92 SBasis c = Linear(0,1);
93 for (int i=2; i<=k; i++){
94 c*=c;
95 }
96 c*=a;
97 return(divide_by_sk(c,k));
98 }
99 }
100
101 static D2<SBasis> RescaleForNonVanishingEnds(D2<SBasis> const &MM, double ZERO=1.e-4){
102 D2<SBasis> M = MM;
103 //TODO: divide by all the s at once!!!
104 while ((M[0].size()>1||M[1].size()>1) &&
105 fabs(M[0].at0())<ZERO &&
106 fabs(M[1].at0())<ZERO &&
107 fabs(M[0].at1())<ZERO &&
108 fabs(M[1].at1())<ZERO){
109 M[0] = divide_by_sk(M[0],1);
110 M[1] = divide_by_sk(M[1],1);
111 }
112 while ((M[0].size()>1||M[1].size()>1) &&
113 fabs(M[0].at0())<ZERO && fabs(M[1].at0())<ZERO){
114 M[0] = divide_by_t0k(M[0],1);
115 M[1] = divide_by_t0k(M[1],1);
116 }
117 while ((M[0].size()>1||M[1].size()>1) &&
118 fabs(M[0].at1())<ZERO && fabs(M[1].at1())<ZERO){
119 M[0] = divide_by_t1k(M[0],1);
120 M[1] = divide_by_t1k(M[1],1);
121 }
122 return M;
123 }
124
125 /*static D2<SBasis> RescaleForNonVanishing(D2<SBasis> const &MM, double ZERO=1.e-4){
126 std::vector<double> levels;
127 levels.push_back(-ZERO);
128 levels.push_back(ZERO);
129 //std::vector<std::vector<double> > mr = multi_roots(MM, levels);
130 }*/
131
132
133 //=================================================================
134 //TODO: what's this for?!?!
135 Piecewise<D2<SBasis> >
136 Geom::cutAtRoots(Piecewise<D2<SBasis> > const &M, double ZERO){
137 vector<double> rts;
138 for (unsigned i=0; i<M.size(); i++){
139 vector<double> seg_rts = roots((M.segs[i])[0]);
140 seg_rts = vect_intersect(seg_rts, roots((M.segs[i])[1]), ZERO);
141 Linear mapToDom = Linear(M.cuts[i],M.cuts[i+1]);
142 for (double & seg_rt : seg_rts){
143 seg_rt= mapToDom(seg_rt);
144 }
145 rts.insert(rts.end(),seg_rts.begin(),seg_rts.end());
146 }
147 return partition(M,rts);
148 }
149
150 /** Return a function which gives the angle of vect at each point.
151 \param vect a piecewise parameteric curve.
152 \param tol the maximum error allowed.
153 \param order the maximum degree to use for approximation
154 \relates Piecewise
155 */
156 Piecewise<SBasis>
157 Geom::atan2(Piecewise<D2<SBasis> > const &vect, double tol, unsigned order){
158 Piecewise<SBasis> result;
159 Piecewise<D2<SBasis> > v = cutAtRoots(vect,tol);
160 result.cuts.push_back(v.cuts.front());
161 for (unsigned i=0; i<v.size(); i++){
162
163 D2<SBasis> vi = RescaleForNonVanishingEnds(v.segs[i]);
164 SBasis x=vi[0], y=vi[1];
165 Piecewise<SBasis> angle;
166 angle = divide (x*derivative(y)-y*derivative(x), x*x+y*y, tol, order);
167
168 //TODO: I don't understand this - sign.
169 angle = integral(-angle);
170 Point vi0 = vi.at0();
171 angle += -std::atan2(vi0[1],vi0[0]) - angle[0].at0();
172 //TODO: deal with 2*pi jumps form one seg to the other...
173 //TODO: not exact at t=1 because of the integral.
174 //TODO: force continuity?
175
176 angle.setDomain(Interval(v.cuts[i],v.cuts[i+1]));
177 result.concat(angle);
178 }
179 return result;
180 }
181 /** Return a function which gives the angle of vect at each point.
182 \param vect a piecewise parameteric curve.
183 \param tol the maximum error allowed.
184 \param order the maximum degree to use for approximation
185 \relates Piecewise, D2
186 */
187 Piecewise<SBasis>
188 Geom::atan2(D2<SBasis> const &vect, double tol, unsigned order){
189 return atan2(Piecewise<D2<SBasis> >(vect),tol,order);
190 }
191
192 /** tan2 is the pseudo-inverse of atan2. It takes an angle and returns a unit_vector that points in the direction of angle.
193 \param angle a piecewise function of angle wrt t.
194 \param tol the maximum error allowed.
195 \param order the maximum degree to use for approximation
196 \relates D2, SBasis
197 */
198 D2<Piecewise<SBasis> >
199 Geom::tan2(SBasis const &angle, double tol, unsigned order){
200 return tan2(Piecewise<SBasis>(angle), tol, order);
201 }
202
203 /** tan2 is the pseudo-inverse of atan2. It takes an angle and returns a unit_vector that points in the direction of angle.
204 \param angle a piecewise function of angle wrt t.
205 \param tol the maximum error allowed.
206 \param order the maximum degree to use for approximation
207 \relates Piecewise, D2
208 */
209 D2<Piecewise<SBasis> >
210 Geom::tan2(Piecewise<SBasis> const &angle, double tol, unsigned order){
211 return D2<Piecewise<SBasis> >(cos(angle, tol, order), sin(angle, tol, order));
212 }
213
214 /** Return a Piecewise<D2<SBasis> > which points in the same direction as V_in, but has unit_length.
215 \param V_in the original path.
216 \param tol the maximum error allowed.
217 \param order the maximum degree to use for approximation
218
219 unitVector(x,y) is computed as (b,-a) where a and b are solutions of:
220 ax+by=0 (eqn1) and a^2+b^2=1 (eqn2)
221
222 \relates Piecewise, D2
223 */
224 Piecewise<D2<SBasis> >
225 Geom::unitVector(D2<SBasis> const &V_in, double tol, unsigned order){
226 //TODO: Handle vanishing vectors...
227 // -This approach is numerically bad. Find a stable way to rescale V_in to have non vanishing ends.
228 // -This done, unitVector will have jumps at zeros: fill the gaps with arcs of circles.
229 D2<SBasis> V = RescaleForNonVanishingEnds(V_in);
230
231 if (V[0].isZero(tol) && V[1].isZero(tol))
232 return Piecewise<D2<SBasis> >(D2<SBasis>(Linear(1),SBasis()));
233 SBasis x = V[0], y = V[1];
234 SBasis r_eqn1, r_eqn2;
235
236 Point v0 = unit_vector(V.at0());
237 Point v1 = unit_vector(V.at1());
238 SBasis a = SBasis(order+1, Linear(0.));
239 a[0] = Linear(-v0[1],-v1[1]);
240 SBasis b = SBasis(order+1, Linear(0.));
241 b[0] = Linear( v0[0], v1[0]);
242
243 r_eqn1 = -(a*x+b*y);
244 r_eqn2 = Linear(1.)-(a*a+b*b);
245
246 for (unsigned k=1; k<=order; k++){
247 double r0 = (k<r_eqn1.size())? r_eqn1.at(k).at0() : 0;
248 double r1 = (k<r_eqn1.size())? r_eqn1.at(k).at1() : 0;
249 double rr0 = (k<r_eqn2.size())? r_eqn2.at(k).at0() : 0;
250 double rr1 = (k<r_eqn2.size())? r_eqn2.at(k).at1() : 0;
251 double a0,a1,b0,b1;// coeffs in a[k] and b[k]
252
253 //the equations to solve at this point are:
254 // a0*x(0)+b0*y(0)=r0 & 2*a0*a(0)+2*b0*b(0)=rr0
255 //and
256 // a1*x(1)+b1*y(1)=r1 & 2*a1*a(1)+2*b1*b(1)=rr1
257 a0 = r0/dot(v0,V.at0())*v0[0]-rr0/2*v0[1];
258 b0 = r0/dot(v0,V.at0())*v0[1]+rr0/2*v0[0];
259 a1 = r1/dot(v1,V.at1())*v1[0]-rr1/2*v1[1];
260 b1 = r1/dot(v1,V.at1())*v1[1]+rr1/2*v1[0];
261
262 a[k] = Linear(a0,a1);
263 b[k] = Linear(b0,b1);
264
265 //TODO: use "incremental" rather than explicit formulas.
266 r_eqn1 = -(a*x+b*y);
267 r_eqn2 = Linear(1)-(a*a+b*b);
268 }
269
270 //our candidate is:
271 D2<SBasis> unitV;
272 unitV[0] = b;
273 unitV[1] = -a;
274
275 //is it good?
276 double rel_tol = std::max(1.,std::max(V_in[0].tailError(0),V_in[1].tailError(0)))*tol;
277 if (r_eqn1.tailError(order)>rel_tol || r_eqn2.tailError(order)>tol){
278 //if not: subdivide and concat results.
279 Piecewise<D2<SBasis> > unitV0, unitV1;
280 unitV0 = unitVector(compose(V,Linear(0,.5)),tol,order);
281 unitV1 = unitVector(compose(V,Linear(.5,1)),tol,order);
282 unitV0.setDomain(Interval(0.,.5));
283 unitV1.setDomain(Interval(.5,1.));
284 unitV0.concat(unitV1);
285 return(unitV0);
286 }else{
287 //if yes: return it as pw.
288 Piecewise<D2<SBasis> > result;
289 result=(Piecewise<D2<SBasis> >)unitV;
290 return result;
291 }
292 }
293
294 /** Return a Piecewise<D2<SBasis> > which points in the same direction as V_in, but has unit_length.
295 \param V_in the original path.
296 \param tol the maximum error allowed.
297 \param order the maximum degree to use for approximation
298
299 unitVector(x,y) is computed as (b,-a) where a and b are solutions of:
300 ax+by=0 (eqn1) and a^2+b^2=1 (eqn2)
301
302 \relates Piecewise
303 */
304 Piecewise<D2<SBasis> >
305 Geom::unitVector(Piecewise<D2<SBasis> > const &V, double tol, unsigned order){
306 Piecewise<D2<SBasis> > result;
307 Piecewise<D2<SBasis> > VV = cutAtRoots(V);
308 result.cuts.push_back(VV.cuts.front());
309 for (unsigned i=0; i<VV.size(); i++){
310 Piecewise<D2<SBasis> > unit_seg;
311 unit_seg = unitVector(VV.segs[i],tol, order);
312 unit_seg.setDomain(Interval(VV.cuts[i],VV.cuts[i+1]));
313 result.concat(unit_seg);
314 }
315 return result;
316 }
317
318 /** returns a function giving the arclength at each point in M.
319 \param M the Element.
320 \param tol the maximum error allowed.
321 \relates Piecewise
322 */
323 Piecewise<SBasis>
324 9600 Geom::arcLengthSb(Piecewise<D2<SBasis> > const &M, double tol){
325
1/2
✓ Branch 2 taken 9600 times.
✗ Branch 3 not taken.
9600 Piecewise<D2<SBasis> > dM = derivative(M);
326
2/4
✓ Branch 3 taken 9600 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 9600 times.
✗ Branch 7 not taken.
9600 Piecewise<SBasis> dMlength = sqrt(dot(dM,dM),tol,3);
327
1/2
✓ Branch 1 taken 9600 times.
✗ Branch 2 not taken.
9600 Piecewise<SBasis> length = integral(dMlength);
328
2/4
✓ Branch 2 taken 9600 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9600 times.
✗ Branch 6 not taken.
9600 length-=length.segs.front().at0();
329 19200 return length;
330 9600 }
331
332 /** returns a function giving the arclength at each point in M.
333 \param M the Element.
334 \param tol the maximum error allowed.
335 \relates Piecewise, D2
336 */
337 Piecewise<SBasis>
338 Geom::arcLengthSb(D2<SBasis> const &M, double tol){
339 return arcLengthSb(Piecewise<D2<SBasis> >(M), tol);
340 }
341
342 #if 0
343 double
344 Geom::length(D2<SBasis> const &M,
345 double tol){
346 Piecewise<SBasis> length = arcLengthSb(M, tol);
347 return length.segs.back().at1();
348 }
349 double
350 Geom::length(Piecewise<D2<SBasis> > const &M,
351 double tol){
352 Piecewise<SBasis> length = arcLengthSb(M, tol);
353 return length.segs.back().at1();
354 }
355 #endif
356
357 /** returns a function giving the curvature at each point in M.
358 \param M the Element.
359 \param tol the maximum error allowed.
360 \relates Piecewise, D2
361 \todo claimed incomplete. Check.
362 */
363 Piecewise<SBasis>
364 Geom::curvature(D2<SBasis> const &M, double tol) {
365 D2<SBasis> dM=derivative(M);
366 Piecewise<D2<SBasis> > unitv = unitVector(dM,tol);
367 Piecewise<SBasis> dMlength = dot(Piecewise<D2<SBasis> >(dM),unitv);
368 Piecewise<SBasis> k = cross(derivative(unitv),unitv);
369 k = divide(k,dMlength,tol,3);
370 return(k);
371 }
372
373 /** returns a function giving the curvature at each point in M.
374 \param M the Element.
375 \param tol the maximum error allowed.
376 \relates Piecewise
377 \todo claimed incomplete. Check.
378 */
379 Piecewise<SBasis>
380 Geom::curvature(Piecewise<D2<SBasis> > const &V, double tol){
381 Piecewise<SBasis> result;
382 Piecewise<D2<SBasis> > VV = cutAtRoots(V);
383 result.cuts.push_back(VV.cuts.front());
384 for (unsigned i=0; i<VV.size(); i++){
385 Piecewise<SBasis> curv_seg;
386 curv_seg = curvature(VV.segs[i],tol);
387 curv_seg.setDomain(Interval(VV.cuts[i],VV.cuts[i+1]));
388 result.concat(curv_seg);
389 }
390 return result;
391 }
392
393 //=================================================================
394
395 /** Reparameterise M to have unit speed.
396 \param M the Element.
397 \param tol the maximum error allowed.
398 \param order the maximum degree to use for approximation
399 \relates Piecewise, D2
400 */
401 Piecewise<D2<SBasis> >
402 9600 Geom::arc_length_parametrization(D2<SBasis> const &M,
403 unsigned order,
404 double tol){
405 9600 Piecewise<D2<SBasis> > u;
406
1/2
✓ Branch 1 taken 9600 times.
✗ Branch 2 not taken.
9600 u.push_cut(0);
407
408
2/4
✓ Branch 3 taken 9600 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 9600 times.
✗ Branch 7 not taken.
9600 Piecewise<SBasis> s = arcLengthSb(Piecewise<D2<SBasis> >(M),tol);
409
2/2
✓ Branch 1 taken 11700 times.
✓ Branch 2 taken 9600 times.
21300 for (unsigned i=0; i < s.size();i++){
410 11700 double t0=s.cuts[i],t1=s.cuts[i+1];
411
3/6
✓ Branch 1 taken 11700 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 11700 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 11700 times.
11700 if ( are_near(s(t0),s(t1)) ) {
412 continue;
413 }
414
2/4
✓ Branch 5 taken 11700 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 11700 times.
✗ Branch 9 not taken.
11700 D2<SBasis> sub_M = compose(M,Linear(t0,t1));
415
1/2
✓ Branch 2 taken 11700 times.
✗ Branch 3 not taken.
11700 D2<SBasis> sub_u;
416
2/2
✓ Branch 0 taken 23400 times.
✓ Branch 1 taken 11700 times.
35100 for (unsigned dim=0;dim<2;dim++){
417
1/2
✓ Branch 3 taken 23400 times.
✗ Branch 4 not taken.
23400 SBasis sub_s = s.segs[i];
418
2/4
✓ Branch 1 taken 23400 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 23400 times.
✗ Branch 5 not taken.
23400 sub_s-=sub_s.at0();
419
2/4
✓ Branch 1 taken 23400 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 23400 times.
✗ Branch 5 not taken.
23400 sub_s/=sub_s.at1();
420
2/4
✓ Branch 3 taken 23400 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 23400 times.
✗ Branch 8 not taken.
23400 sub_u[dim]=compose_inverse(sub_M[dim],sub_s, order, tol);
421 23400 }
422
2/4
✓ Branch 1 taken 11700 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 11700 times.
✗ Branch 5 not taken.
11700 u.push(sub_u,s(t1));
423 11700 }
424 19200 return u;
425 9600 }
426
427 /** Reparameterise M to have unit speed.
428 \param M the Element.
429 \param tol the maximum error allowed.
430 \param order the maximum degree to use for approximation
431 \relates Piecewise
432 */
433 Piecewise<D2<SBasis> >
434 300 Geom::arc_length_parametrization(Piecewise<D2<SBasis> > const &M,
435 unsigned order,
436 double tol){
437 300 Piecewise<D2<SBasis> > result;
438
2/2
✓ Branch 1 taken 9600 times.
✓ Branch 2 taken 300 times.
9900 for (unsigned i=0; i<M.size(); i++) {
439
2/4
✓ Branch 3 taken 9600 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 9600 times.
✗ Branch 7 not taken.
9600 result.concat( arc_length_parametrization(M[i],order,tol) );
440 }
441 300 return result;
442 }
443
444 #include <gsl/gsl_integration.h>
445 static double sb_length_integrating(double t, void* param) {
446 SBasis* pc = (SBasis*)param;
447 return sqrt((*pc)(t));
448 }
449
450 /** Calculates the length of a D2<SBasis> through gsl integration.
451 \param B the Element.
452 \param tol the maximum error allowed.
453 \param result variable to be incremented with the length of the path
454 \param abs_error variable to be incremented with the estimated error
455 \relates D2
456 If you only want the length, this routine may be faster/more accurate.
457 */
458 void Geom::length_integrating(D2<SBasis> const &B, double &result, double &abs_error, double tol) {
459 D2<SBasis> dB = derivative(B);
460 SBasis dB2 = dot(dB, dB);
461
462 gsl_function F;
463 gsl_integration_workspace * w
464 = gsl_integration_workspace_alloc (20);
465 F.function = &sb_length_integrating;
466 F.params = (void*)&dB2;
467 double quad_result, err;
468 /* We could probably use the non adaptive code here if we removed any cusps first. */
469
470 gsl_integration_qag (&F, 0, 1, 0, tol, 20,
471 GSL_INTEG_GAUSS21, w, &quad_result, &err);
472
473 abs_error += err;
474 result += quad_result;
475 }
476
477 /** Calculates the length of a D2<SBasis> through gsl integration.
478 \param s the Element.
479 \param tol the maximum error allowed.
480 \relates D2
481 If you only want the total length, this routine faster and more accurate than constructing an arcLengthSb.
482 */
483 double
484 Geom::length(D2<SBasis> const &s,
485 double tol){
486 double result = 0;
487 double abs_error = 0;
488 length_integrating(s, result, abs_error, tol);
489 return result;
490 }
491 /** Calculates the length of a Piecewise<D2<SBasis> > through gsl integration.
492 \param s the Element.
493 \param tol the maximum error allowed.
494 \relates Piecewise
495 If you only want the total length, this routine faster and more accurate than constructing an arcLengthSb.
496 */
497 double
498 Geom::length(Piecewise<D2<SBasis> > const &s,
499 double tol){
500 double result = 0;
501 double abs_error = 0;
502 for (unsigned i=0; i < s.size();i++){
503 length_integrating(s[i], result, abs_error, tol);
504 }
505 return result;
506 }
507
508 /**
509 * Centroid using sbasis integration.
510 \param p the Element.
511 \param centroid on return contains the centroid of the shape
512 \param area on return contains the signed area of the shape.
513 \relates Piecewise
514 This approach uses green's theorem to compute the area and centroid using integrals. For curved shapes this is much faster than converting to polyline. Note that without an uncross operation the output is not the absolute area.
515
516 * Returned values:
517 0 for normal execution;
518 2 if area is zero, meaning centroid is meaningless.
519
520 */
521 21 unsigned Geom::centroid(Piecewise<D2<SBasis> > const &p, Point& centroid, double &area) {
522 21 Point centroid_tmp(0,0);
523 21 double atmp = 0;
524
2/2
✓ Branch 1 taken 66 times.
✓ Branch 2 taken 21 times.
87 for(unsigned i = 0; i < p.size(); i++) {
525
3/6
✓ Branch 5 taken 66 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 66 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 66 times.
✗ Branch 13 not taken.
66 SBasis curl = dot(p[i], rot90(derivative(p[i])));
526
1/2
✓ Branch 2 taken 66 times.
✗ Branch 3 not taken.
66 SBasis A = integral(curl);
527
2/4
✓ Branch 4 taken 66 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 66 times.
✗ Branch 8 not taken.
66 D2<SBasis> C = integral(multiply(curl, p[i]));
528
2/4
✓ Branch 1 taken 66 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 66 times.
✗ Branch 5 not taken.
66 atmp += A.at1() - A.at0();
529 66 centroid_tmp += C.at1()- C.at0(); // first moment.
530 66 }
531 // join ends
532 21 centroid_tmp *= 2;
533 21 Point final = p[p.size()-1].at1(), initial = p[0].at0();
534 21 const double ai = cross(final, initial);
535 21 atmp += ai;
536 21 centroid_tmp += (final + initial)*ai; // first moment.
537
538 21 area = atmp / 2;
539
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 2 times.
21 if (atmp != 0) {
540 19 centroid = centroid_tmp / (3 * atmp);
541 19 return 0;
542 }
543 2 return 2;
544 }
545
546 /**
547 * Find cubics with prescribed curvatures at both ends.
548 *
549 * this requires to solve a system of the form
550 *
551 * \f[
552 * \lambda_1 = a_0 \lambda_0^2 + c_0
553 * \lambda_0 = a_1 \lambda_1^2 + c_1
554 * \f]
555 *
556 * which is a deg 4 equation in lambda 0.
557 * Below are basic functions dedicated to solving this assuming a0 and a1 !=0.
558 */
559
560 static OptInterval
561 find_bounds_for_lambda0(double aa0,double aa1,double cc0,double cc1,
562 int insist_on_speeds_signs){
563
564 double a0=aa0,a1=aa1,c0=cc0,c1=cc1;
565 Interval result;
566 bool flip = a1<0;
567 if (a1<0){a1=-a1; c1=-c1;}
568 if (a0<0){a0=-a0; c0=-c0;}
569 double a = (a0<a1 ? a0 : a1);
570 double c = (c0<c1 ? c0 : c1);
571 double delta = 1-4*a*c;
572 if ( delta < 0 )
573 return OptInterval();//return empty interval
574 double lambda_max = (1+std::sqrt(delta))/2/a;
575
576 result = Interval(c,lambda_max);
577 if (flip)
578 result *= -1;
579 if (insist_on_speeds_signs == 1){
580 if (result.max() < 0)//Caution: setMin with max<new min...
581 return OptInterval();//return empty interval
582 result.setMin(0);
583 }
584 result = Interval(result.min()-.1,result.max()+.1);//just in case all our approx. were exact...
585 return result;
586 }
587
588 static
589 std::vector<double>
590 solve_lambda0(double a0,double a1,double c0,double c1,
591 int insist_on_speeds_signs){
592
593 SBasis p(3, Linear());
594 p[0] = Linear( a1*c0*c0+c1, a1*a0*(a0+ 2*c0) +a1*c0*c0 +c1 -1 );
595 p[1] = Linear( -a1*a0*(a0+2*c0), -a1*a0*(3*a0+2*c0) );
596 p[2] = Linear( a1*a0*a0 );
597
598 OptInterval domain = find_bounds_for_lambda0(a0,a1,c0,c1,insist_on_speeds_signs);
599 if ( !domain )
600 return std::vector<double>();
601 p = compose(p,Linear(domain->min(),domain->max()));
602 std::vector<double>rts = roots(p);
603 for (double & rt : rts){
604 rt = domain->min() + rt * domain->extent();
605 }
606 return rts;
607 }
608
609 /**
610 * \brief returns the cubics fitting direction and curvature of a given
611 * input curve at two points.
612 *
613 * The input can be the
614 * value, speed, and acceleration
615 * or
616 * value, speed, and cross(acceleration,speed)
617 * of the original curve at the both ends.
618 * (the second is often technically useful, as it avoids unnecessary division by |v|^2)
619 * Recall that K=1/R=cross(acceleration,speed)/|speed|^3.
620 *
621 * Moreover, a 7-th argument 'insist_on_speed_signs' can be supplied to select solutions:
622 * If insist_on_speed_signs == 1, only consider solutions where speeds at both ends are positively
623 * proportional to the given ones.
624 * If insist_on_speed_signs == 0, allow speeds to point in the opposite direction (both at the same time)
625 * If insist_on_speed_signs == -1, allow speeds to point in both direction independently.
626 *
627 * \relates D2
628 */
629 std::vector<D2<SBasis> >
630 Geom::cubics_fitting_curvature(Point const &M0, Point const &M1,
631 Point const &dM0, Point const &dM1,
632 double d2M0xdM0, double d2M1xdM1,
633 int insist_on_speed_signs,
634 double epsilon){
635 std::vector<D2<SBasis> > result;
636
637 //speed of cubic bezier will be lambda0*dM0 and lambda1*dM1,
638 //with lambda0 and lambda1 s.t. curvature at both ends is the same
639 //as the curvature of the given curve.
640 std::vector<double> lambda0,lambda1;
641 double dM1xdM0=cross(dM1,dM0);
642 if (fabs(dM1xdM0)<epsilon){
643 if (fabs(d2M0xdM0)<epsilon || fabs(d2M1xdM1)<epsilon){
644 return result;
645 }
646 double lbda02 = 6.*cross(M1-M0,dM0)/d2M0xdM0;
647 double lbda12 =-6.*cross(M1-M0,dM1)/d2M1xdM1;
648 if (lbda02<0 || lbda12<0){
649 return result;
650 }
651 lambda0.push_back(std::sqrt(lbda02) );
652 lambda1.push_back(std::sqrt(lbda12) );
653 }else{
654 //solve: lambda1 = a0 lambda0^2 + c0
655 // lambda0 = a1 lambda1^2 + c1
656 double a0,c0,a1,c1;
657 a0 = -d2M0xdM0/2/dM1xdM0;
658 c0 = 3*cross(M1-M0,dM0)/dM1xdM0;
659 a1 = -d2M1xdM1/2/dM1xdM0;
660 c1 = -3*cross(M1-M0,dM1)/dM1xdM0;
661
662 if (fabs(a0)<epsilon){
663 lambda1.push_back( c0 );
664 lambda0.push_back( a1*c0*c0 + c1 );
665 }else if (fabs(a1)<epsilon){
666 lambda0.push_back( c1 );
667 lambda1.push_back( a0*c1*c1 + c0 );
668 }else{
669 //find lamda0 by solving a deg 4 equation d0+d1*X+...+d4*X^4=0
670 vector<double> solns=solve_lambda0(a0,a1,c0,c1,insist_on_speed_signs);
671 for (double lbda0 : solns){
672 double lbda1=c0+a0*lbda0*lbda0;
673 //is this solution pointing in the + direction at both ends?
674 if (lbda0>=0. && lbda1>=0.){
675 lambda0.push_back( lbda0);
676 lambda1.push_back( lbda1);
677 }
678 //is this solution pointing in the - direction at both ends?
679 else if (lbda0<=0. && lbda1<=0. && insist_on_speed_signs<=0){
680 lambda0.push_back( lbda0);
681 lambda1.push_back( lbda1);
682 }
683 //ok,this solution is pointing in the + and - directions.
684 else if (insist_on_speed_signs<0){
685 lambda0.push_back( lbda0);
686 lambda1.push_back( lbda1);
687 }
688 }
689 }
690 }
691
692 for (unsigned i=0; i<lambda0.size(); i++){
693 Point V0 = lambda0[i]*dM0;
694 Point V1 = lambda1[i]*dM1;
695 D2<SBasis> cubic;
696 for(unsigned dim=0;dim<2;dim++){
697 SBasis c(2, Linear());
698 c[0] = Linear(M0[dim],M1[dim]);
699 c[1] = Linear( M0[dim]-M1[dim]+V0[dim],
700 -M0[dim]+M1[dim]-V1[dim]);
701 cubic[dim] = c;
702 }
703 #if 0
704 Piecewise<SBasis> k = curvature(result);
705 double dM0_l = dM0.length();
706 double dM1_l = dM1.length();
707 g_warning("Target radii: %f, %f", dM0_l*dM0_l*dM0_l/d2M0xdM0,dM1_l*dM1_l*dM1_l/d2M1xdM1);
708 g_warning("Obtained radii: %f, %f",1/k.valueAt(0),1/k.valueAt(1));
709 #endif
710 result.push_back(cubic);
711 }
712 return(result);
713 }
714
715 std::vector<D2<SBasis> >
716 Geom::cubics_fitting_curvature(Point const &M0, Point const &M1,
717 Point const &dM0, Point const &dM1,
718 Point const &d2M0, Point const &d2M1,
719 int insist_on_speed_signs,
720 double epsilon){
721 double d2M0xdM0 = cross(d2M0,dM0);
722 double d2M1xdM1 = cross(d2M1,dM1);
723 return cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0xdM0,d2M1xdM1,insist_on_speed_signs,epsilon);
724 }
725
726 std::vector<D2<SBasis> >
727 Geom::cubics_with_prescribed_curvature(Point const &M0, Point const &M1,
728 Point const &dM0, Point const &dM1,
729 double k0, double k1,
730 int insist_on_speed_signs,
731 double epsilon){
732 double length;
733 length = dM0.length();
734 double d2M0xdM0 = k0*length*length*length;
735 length = dM1.length();
736 double d2M1xdM1 = k1*length*length*length;
737 return cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0xdM0,d2M1xdM1,insist_on_speed_signs,epsilon);
738 }
739
740
741 namespace Geom {
742 /**
743 * \brief returns all the parameter values of A whose tangent passes through P.
744 * \relates D2
745 */
746 std::vector<double> find_tangents(Point P, D2<SBasis> const &A) {
747 SBasis crs (cross(A - P, derivative(A)));
748 return roots(crs);
749 }
750
751 /**
752 * \brief returns all the parameter values of A whose normal passes through P.
753 * \relates D2
754 */
755 std::vector<double> find_normals(Point P, D2<SBasis> const &A) {
756 SBasis crs (dot(A - P, derivative(A)));
757 return roots(crs);
758 }
759
760 /**
761 * \brief returns all the parameter values of A whose normal is parallel to vector V.
762 * \relates D2
763 */
764 std::vector<double> find_normals_by_vector(Point V, D2<SBasis> const &A) {
765 SBasis crs = dot(derivative(A), V);
766 return roots(crs);
767 }
768 /**
769 * \brief returns all the parameter values of A whose tangent is parallel to vector V.
770 * \relates D2
771 */
772 std::vector<double> find_tangents_by_vector(Point V, D2<SBasis> const &A) {
773 SBasis crs = dot(derivative(A), rot90(V));
774 return roots(crs);
775 }
776
777 }
778 //}; // namespace
779
780
781 /*
782 Local Variables:
783 mode:c++
784 c-file-style:"stroustrup"
785 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
786 indent-tabs-mode:nil
787 fill-column:99
788 End:
789 */
790 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
791