1 package org.djutils.draw.curve;
2
3 import org.djutils.draw.function.ContinuousPiecewiseLinearFunction;
4 import org.djutils.draw.line.PolyLine2d;
5 import org.djutils.draw.point.DirectedPoint2d;
6 import org.djutils.draw.point.Point2d;
7 import org.djutils.exceptions.Throw;
8 import org.djutils.exceptions.Try;
9 import org.djutils.math.AngleUtil;
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 public class Clothoid2d implements Curve2d, OffsetCurve2d
38 {
39
40
41 private static final double ANGLE_TOLERANCE = 2.0 * Math.PI / 3600.0;
42
43
44 private static final double SECANT_TOLERANCE = 1e-8;
45
46
47 private final DirectedPoint2d startPoint;
48
49
50 private final DirectedPoint2d endPoint;
51
52
53 private final double startCurvature;
54
55
56 private final double endCurvature;
57
58
59 private final double length;
60
61
62
63
64
65 private final double a;
66
67
68 private final double alphaMin;
69
70
71 private final double alphaMax;
72
73
74 private final double[] t0;
75
76
77 private final double[] n0;
78
79
80 private final boolean opposite;
81
82
83 private final boolean reflected;
84
85
86 private final Straight2d straight;
87
88
89 private final Arc2d arc;
90
91
92 private boolean shiftDetermined;
93
94
95 private double shiftX;
96
97
98 private double shiftY;
99
100
101 private double dShiftX;
102
103
104 private double dShiftY;
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 public Clothoid2d(final DirectedPoint2d startPoint, final DirectedPoint2d endPoint)
124 {
125 Throw.whenNull(startPoint, "startPoint");
126 Throw.whenNull(endPoint, "endPoint");
127 this.startPoint = startPoint;
128 this.endPoint = endPoint;
129
130 double dx = endPoint.x - startPoint.x;
131 double dy = endPoint.y - startPoint.y;
132 double d2 = Math.hypot(dx, dy);
133 double d = Math.atan2(dy, dx);
134
135 double phi1 = AngleUtil.normalizeAroundZero(d - startPoint.dirZ);
136 double phi2 = AngleUtil.normalizeAroundZero(endPoint.dirZ - d);
137 double phi1Abs = Math.abs(phi1);
138 double phi2Abs = Math.abs(phi2);
139
140 if (phi1Abs < ANGLE_TOLERANCE && phi2Abs < ANGLE_TOLERANCE)
141 {
142
143 this.length = Math.hypot(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
144 this.a = Double.POSITIVE_INFINITY;
145 this.startCurvature = 0.0;
146 this.endCurvature = 0.0;
147 this.straight = new Straight2d(startPoint, this.length);
148 this.arc = null;
149 this.alphaMin = 0.0;
150 this.alphaMax = 0.0;
151 this.t0 = null;
152 this.n0 = null;
153 this.opposite = false;
154 this.reflected = false;
155 return;
156 }
157 else if (Math.abs(phi2 - phi1) < ANGLE_TOLERANCE)
158 {
159
160 double r = .5 * d2 / Math.sin(phi1);
161 double cosStartDirection = Math.cos(startPoint.dirZ);
162 double sinStartDirection = Math.sin(startPoint.dirZ);
163 double ang = Math.PI / 2.0;
164 double cosAng = Math.cos(ang);
165 double sinAng = Math.sin(ang);
166 double x0 = startPoint.x - r * (cosStartDirection * cosAng + sinStartDirection * sinAng);
167 double y0 = startPoint.y - r * (cosStartDirection * -sinAng + sinStartDirection * cosAng);
168 double from = Math.atan2(startPoint.y - y0, startPoint.x - x0);
169 double to = Math.atan2(endPoint.y - y0, endPoint.x - x0);
170 if (r < 0 && to > from)
171 {
172 to = to - 2.0 * Math.PI;
173 }
174 else if (r > 0 && to < from)
175 {
176 to = to + 2.0 * Math.PI;
177 }
178 double angle = Math.abs(to - from);
179 this.length = angle * Math.abs(r);
180 this.a = 0.0;
181 this.startCurvature = 1.0 / r;
182 this.endCurvature = 1.0 / r;
183 this.straight = null;
184 this.arc = new Arc2d(startPoint, Math.abs(r), r > 0.0, angle);
185 this.alphaMin = 0.0;
186 this.alphaMax = 0.0;
187 this.t0 = null;
188 this.n0 = null;
189 this.opposite = false;
190 this.reflected = false;
191 return;
192 }
193 this.straight = null;
194 this.arc = null;
195
196
197
198 if (phi2Abs < phi1Abs)
199 {
200 this.opposite = true;
201 double phi3 = phi1;
202 phi1 = -phi2;
203 phi2 = -phi3;
204 dx = -dx;
205 dy = -dy;
206 }
207 else
208 {
209 this.opposite = false;
210 }
211
212
213 this.reflected = phi2 < 0 || phi2 > Math.PI;
214 if (this.reflected)
215 {
216 phi1 = -phi1;
217 phi2 = -phi2;
218 }
219
220
221 double[] cs = Fresnel.fresnel(alphaToT(phi1 + phi2));
222 double h = cs[1] * Math.cos(phi1) - cs[0] * Math.sin(phi1);
223 boolean cShape = 0 < phi1 && phi1 < phi2 && phi2 < Math.PI && h < 0;
224 double theta = getTheta(phi1, phi2, cShape);
225 double aSign = cShape ? -1.0 : 1.0;
226 double thetaSign = -aSign;
227
228 double v1 = theta + phi1 + phi2;
229 double v2 = theta + phi1;
230 double[] cs0 = Fresnel.fresnel(alphaToT(theta));
231 double[] cs1 = Fresnel.fresnel(alphaToT(v1));
232 this.a = d2 / ((cs1[1] + aSign * cs0[1]) * Math.sin(v2) + (cs1[0] + aSign * cs0[0]) * Math.cos(v2));
233
234 dx /= d2;
235 dy /= d2;
236 if (this.reflected)
237 {
238
239 this.t0 = new double[] {Math.cos(-v2) * dx + Math.sin(-v2) * dy, -Math.sin(-v2) * dx + Math.cos(-v2) * dy};
240 this.n0 = new double[] {-this.t0[1], this.t0[0]};
241 }
242 else
243 {
244 this.t0 = new double[] {Math.cos(v2) * dx + Math.sin(v2) * dy, -Math.sin(v2) * dx + Math.cos(v2) * dy};
245 this.n0 = new double[] {this.t0[1], -this.t0[0]};
246 }
247
248 this.alphaMin = thetaSign * theta;
249 this.alphaMax = v1;
250 double sign = (this.reflected ? -1.0 : 1.0);
251 double curveMin = Math.PI * alphaToT(this.alphaMin) / this.a;
252 double curveMax = Math.PI * alphaToT(v1) / this.a;
253 this.startCurvature = sign * (this.opposite ? -curveMax : curveMin);
254 this.endCurvature = sign * (this.opposite ? -curveMin : curveMax);
255 this.length = this.a * (alphaToT(v1) - alphaToT(this.alphaMin));
256 }
257
258
259
260
261
262
263
264
265
266
267 public Clothoid2d(final DirectedPoint2d startPoint, final double a, final double startCurvature, final double endCurvature)
268 {
269 Throw.whenNull(startPoint, "startPoint");
270 Throw.when(a <= 0.0, IllegalArgumentException.class, "A value must be above 0.");
271 this.startPoint = startPoint;
272
273 this.a = a * Math.sqrt(Math.PI);
274 this.length = a * a * Math.abs(endCurvature - startCurvature);
275 this.startCurvature = startCurvature;
276 this.endCurvature = endCurvature;
277
278 double l1 = a * a * startCurvature;
279 double l2 = a * a * endCurvature;
280 this.alphaMin = Math.abs(l1) * startCurvature / 2.0;
281 this.alphaMax = Math.abs(l2) * endCurvature / 2.0;
282
283 double ang = AngleUtil.normalizeAroundZero(startPoint.dirZ) - Math.abs(this.alphaMin);
284 this.t0 = new double[] {Math.cos(ang), Math.sin(ang)};
285 this.n0 = new double[] {this.t0[1], -this.t0[0]};
286 double endDirection = ang + Math.abs(this.alphaMax);
287 if (startCurvature > endCurvature)
288 {
289
290
291 double m = Math.tan(startPoint.dirZ + Math.PI / 2.0);
292
293
294 double onePlusMm = 1.0 + m * m;
295 double oneMinusMm = 1.0 - m * m;
296 double mmMinusOne = m * m - 1.0;
297 double twoM = 2.0 * m;
298 double t00 = this.t0[0];
299 double t01 = this.t0[1];
300 double n00 = this.n0[0];
301 double n01 = this.n0[1];
302 this.t0[0] = (oneMinusMm * t00 + 2 * m * t01) / onePlusMm;
303 this.t0[1] = (twoM * t00 + mmMinusOne * t01) / onePlusMm;
304 this.n0[0] = (oneMinusMm * n00 + 2 * m * n01) / onePlusMm;
305 this.n0[1] = (twoM * n00 + mmMinusOne * n01) / onePlusMm;
306
307 double ang2 = Math.atan2(this.t0[1], this.t0[0]);
308 endDirection = ang2 - Math.abs(this.alphaMax) + Math.PI;
309 }
310 PolyLine2d line = toPolyLine(new Flattener2d.NumSegments(1));
311 Point2d end = Try.assign(() -> line.get(line.size() - 1), "Line does not have an end point.");
312 this.endPoint = new DirectedPoint2d(end.x, end.y, endDirection);
313
314
315 this.straight = null;
316 this.arc = null;
317 this.opposite = false;
318 this.reflected = false;
319 }
320
321
322
323
324
325
326
327
328
329
330
331
332
333 public static Clothoid2d withLength(final DirectedPoint2d startPoint, final double length, final double startCurvature,
334 final double endCurvature)
335 {
336 Throw.when(length <= 0.0, IllegalArgumentException.class, "Length must be above 0.");
337 double a = Math.sqrt(length / Math.abs(endCurvature - startCurvature));
338 return new Clothoid2d(startPoint, a, startCurvature, endCurvature);
339 }
340
341
342
343
344
345
346 private static double alphaToT(final double alpha)
347 {
348 return alpha >= 0 ? Math.sqrt(alpha * 2.0 / Math.PI) : -Math.sqrt(-alpha * 2.0 / Math.PI);
349 }
350
351
352
353
354
355
356
357
358 private static double getTheta(final double phi1, final double phi2, final boolean cShape)
359 {
360 double sign, phiMin, phiMax;
361 if (cShape)
362 {
363 double lambda = (1 - Math.cos(phi1)) / (1 - Math.cos(phi2));
364 phiMin = 0.0;
365 phiMax = (lambda * lambda * (phi1 + phi2)) / (1 - (lambda * lambda));
366 sign = -1.0;
367 }
368 else
369 {
370 phiMin = Math.max(0, -phi1);
371 phiMax = Math.PI / 2 - phi1;
372 sign = 1;
373 }
374
375 double fMin = fTheta(phiMin, phi1, phi2, sign);
376 double fMax = fTheta(phiMax, phi1, phi2, sign);
377 if (fMin * fMax > 0)
378 {
379 throw new IllegalArgumentException(
380 "f(phiMin) and f(phiMax) have the same sign, we cant find f(theta) = 0 between them.");
381 }
382
383
384 double x0 = phiMin;
385 double x1 = phiMax;
386 double x2 = 0;
387 for (int i = 0; i < 100; i++)
388 {
389 double f1 = fTheta(x1, phi1, phi2, sign);
390 x2 = x1 - f1 * (x1 - x0) / (f1 - fTheta(x0, phi1, phi2, sign));
391 x2 = Math.max(Math.min(x2, phiMax), phiMin);
392 x0 = x1;
393 x1 = x2;
394 if (Math.abs(x0 - x1) < SECANT_TOLERANCE || Math.abs(x0 / x1 - 1) < SECANT_TOLERANCE
395 || Math.abs(f1) < SECANT_TOLERANCE)
396 {
397 return x2;
398 }
399 }
400
401 return x2;
402 }
403
404
405
406
407
408
409
410
411
412
413
414 private static double fTheta(final double theta, final double phi1, final double phi2, final double sign)
415 {
416 double thetaPhi1 = theta + phi1;
417 double[] cs0 = Fresnel.fresnel(alphaToT(theta));
418 double[] cs1 = Fresnel.fresnel(alphaToT(thetaPhi1 + phi2));
419 return (cs1[1] + sign * cs0[1]) * Math.cos(thetaPhi1) - (cs1[0] + sign * cs0[0]) * Math.sin(thetaPhi1);
420 }
421
422 @Override
423 public DirectedPoint2d getStartPoint()
424 {
425 return this.startPoint;
426 }
427
428 @Override
429 public DirectedPoint2d getEndPoint()
430 {
431 return this.endPoint;
432 }
433
434
435
436
437
438 public double getStartCurvature()
439 {
440 return this.startCurvature;
441 }
442
443
444
445
446
447 public double getEndCurvature()
448 {
449 return this.endCurvature;
450 }
451
452
453
454
455
456 public double getStartRadius()
457 {
458 return 1.0 / this.startCurvature;
459 }
460
461
462
463
464
465 public double getEndRadius()
466 {
467 return 1.0 / this.endCurvature;
468 }
469
470
471
472
473
474 public double getA()
475 {
476
477
478 return this.a / Math.sqrt(Math.PI);
479 }
480
481
482
483
484 private void assureShift()
485 {
486 if (this.shiftDetermined)
487 {
488 return;
489 }
490
491 DirectedPoint2d p1 = this.opposite ? this.endPoint : this.startPoint;
492 DirectedPoint2d p2 = this.opposite ? this.startPoint : this.endPoint;
493
494
495 double[] csMin = Fresnel.fresnel(alphaToT(this.alphaMin));
496 double xMin = this.a * (csMin[0] * this.t0[0] - csMin[1] * this.n0[0]);
497 double yMin = this.a * (csMin[0] * this.t0[1] - csMin[1] * this.n0[1]);
498 this.shiftX = p1.x - xMin;
499 this.shiftY = p1.y - yMin;
500
501
502 if (p2 != null)
503 {
504 double[] csMax = Fresnel.fresnel(alphaToT(this.alphaMax));
505 double xMax = this.a * (csMax[0] * this.t0[0] - csMax[1] * this.n0[0]);
506 double yMax = this.a * (csMax[0] * this.t0[1] - csMax[1] * this.n0[1]);
507 this.dShiftX = p2.x - (xMax + this.shiftX);
508 this.dShiftY = p2.y - (yMax + this.shiftY);
509 }
510 else
511 {
512 this.dShiftX = 0.0;
513 this.dShiftY = 0.0;
514 }
515
516 this.shiftDetermined = true;
517 }
518
519
520
521
522
523
524
525 private Point2d getPoint(final double fraction, final double offset)
526 {
527 double f = this.opposite ? 1.0 - fraction : fraction;
528 double alpha = this.alphaMin + f * (this.alphaMax - this.alphaMin);
529 double[] cs = Fresnel.fresnel(alphaToT(alpha));
530 double x = this.shiftX + this.a * (cs[0] * this.t0[0] - cs[1] * this.n0[0]) + f * this.dShiftX;
531 double y = this.shiftY + this.a * (cs[0] * this.t0[1] - cs[1] * this.n0[1]) + f * this.dShiftY;
532 double d = getDirectionForAlpha(alpha) + Math.PI / 2;
533 return new Point2d(x + Math.cos(d) * offset, y + Math.sin(d) * offset);
534 }
535
536 @Override
537 public Point2d getPoint(final double fraction)
538 {
539 if (this.arc != null)
540 {
541 return this.arc.getPoint(fraction);
542 }
543 else if (this.straight != null)
544 {
545 return this.straight.getPoint(fraction);
546 }
547 return getPoint(fraction, 0);
548 }
549
550 @Override
551 public Point2d getPoint(final double fraction, final ContinuousPiecewiseLinearFunction of)
552 {
553 if (this.arc != null)
554 {
555 return this.arc.getPoint(fraction, of);
556 }
557 else if (this.straight != null)
558 {
559 return this.straight.getPoint(fraction, of);
560 }
561 return getPoint(fraction, of.get(fraction));
562 }
563
564 @Override
565 public Double getDirection(final double fraction)
566 {
567 if (this.arc != null)
568 {
569 return this.arc.getDirection(fraction);
570 }
571 else if (this.straight != null)
572 {
573 return this.straight.getDirection(fraction);
574 }
575 return getDirectionForAlpha(this.alphaMin + fraction * (this.alphaMax - this.alphaMin));
576 }
577
578
579
580
581
582
583 private double getDirectionForAlpha(final double alpha)
584 {
585 double rot = Math.atan2(this.t0[1], this.t0[0]);
586
587 rot += this.reflected ? -Math.abs(alpha) : Math.abs(alpha);
588 if (this.opposite)
589 {
590 rot += Math.PI;
591 }
592 return AngleUtil.normalizeAroundZero(rot);
593 }
594
595 @Override
596 public PolyLine2d toPolyLine(final Flattener2d flattener)
597 {
598 if (this.straight != null)
599 {
600 return this.straight.toPolyLine(flattener);
601 }
602 if (this.arc != null)
603 {
604 return this.arc.toPolyLine(flattener);
605 }
606 assureShift();
607 return flattener.flatten(this);
608 }
609
610 @Override
611 public PolyLine2d toPolyLine(final OffsetFlattener2d flattener, final ContinuousPiecewiseLinearFunction offsets)
612 {
613 Throw.whenNull(offsets, "offsets");
614 if (this.straight != null)
615 {
616 return this.straight.toPolyLine(flattener, offsets);
617 }
618 if (this.arc != null)
619 {
620 return this.arc.toPolyLine(flattener, offsets);
621 }
622 assureShift();
623 return flattener.flatten(this, offsets);
624 }
625
626 @Override
627 public double getLength()
628 {
629 return this.length;
630 }
631
632
633
634
635
636
637 public String getAppliedShape()
638 {
639 return this.straight == null ? (this.arc == null ? "Clothoid" : "Arc") : "Straight";
640 }
641
642 @Override
643 public String toString()
644 {
645 return "Clothoid [startPoint=" + this.startPoint + ", endPoint=" + this.endPoint + ", startCurvature="
646 + this.startCurvature + ", endCurvature=" + this.endCurvature + ", length=" + this.length + "]";
647 }
648
649 }
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667 final class Fresnel
668 {
669
670
671
672 private static final double[] CN1 = new double[] {
673 9.999999999999999421E-01,
674 -1.994608988261842706E-01,
675 1.761939525434914045E-02,
676 -5.280796513726226960E-04,
677 5.477113856826871660E-06
678 };
679
680
681 private static final double[] CD1 = new double[] {
682 1.000000000000000000E+00,
683 4.727921120104532689E-02,
684 1.099572150256418851E-03,
685 1.552378852769941331E-05,
686 1.189389014228757184E-07
687 };
688
689
690 private static final double[] CN2 = new double[] {
691 1.00000000000111043640E+00,
692 -2.07073360335323894245E-01,
693 1.91870279431746926505E-02,
694 -6.71376034694922109230E-04,
695 1.02365435056105864908E-05,
696 -5.68293310121870728343E-08
697 };
698
699
700 private static final double[] CD2 = new double[] {
701 1.00000000000000000000E+00,
702 3.96667496952323433510E-02,
703 7.88905245052359907842E-04,
704 1.01344630866749406081E-05,
705 8.77945377892369265356E-08,
706 4.41701374065009620393E-10
707 };
708
709
710 private static final double[] SN1 = new double[] {
711 5.2359877559829887021E-01,
712 -7.0748991514452302596E-02,
713 3.8778212346368287939E-03,
714 -8.4555728435277680591E-05,
715 6.7174846662514086196E-07
716 };
717
718
719 private static final double[] SD1 = new double[] {
720 1.0000000000000000000E+00,
721 4.1122315114238422205E-02,
722 8.1709194215213447204E-04,
723 9.6269087593903403370E-06,
724 5.9528122767840998345E-08
725 };
726
727
728 private static final double[] SN2 = new double[] {
729 5.23598775598344165913E-01,
730 -7.37766914010191323867E-02,
731 4.30730526504366510217E-03,
732 -1.09540023911434994566E-04,
733 1.28531043742724820610E-06,
734 -5.76765815593088804567E-09
735 };
736
737
738 private static final double[] SD2 = new double[] {
739 1.00000000000000000000E+00,
740 3.53398342167472162540E-02,
741 6.18224620195473216538E-04,
742 6.87086265718620117905E-06,
743 5.03090581246612375866E-08,
744 2.05539124458579596075E-10
745 };
746
747
748 private static final double[] FN3 = new double[] {
749 3.1830975293580985290E-01,
750 1.2226000551672961219E+01,
751 1.2924886131901657025E+02,
752 4.3886367156695547655E+02,
753 4.1466722177958961672E+02,
754 5.6771463664185116454E+01
755 };
756
757
758 private static final double[] FD3 = new double[] {
759 1.0000000000000000000E+00,
760 3.8713003365583442831E+01,
761 4.1674359830705629745E+02,
762 1.4740030733966610568E+03,
763 1.5371675584895759916E+03,
764 2.9113088788847831515E+02
765 };
766
767
768 private static final double[] FN4 = new double[] {
769 3.183098818220169217E-01,
770 1.958839410219691002E+01,
771 3.398371349269842400E+02,
772 1.930076407867157531E+03,
773 3.091451615744296552E+03,
774 7.177032493651399590E+02
775 };
776
777
778 private static final double[] FD4 = new double[] {
779 1.000000000000000000E+00,
780 6.184271381728873709E+01,
781 1.085350675006501251E+03,
782 6.337471558511437898E+03,
783 1.093342489888087888E+04,
784 3.361216991805511494E+03
785 };
786
787
788 private static final double[] FN5 = new double[] {
789 -9.675460329952532343E-02,
790 -2.431275407194161683E+01,
791 -1.947621998306889176E+03,
792 -6.059852197160773639E+04,
793 -7.076806952837779823E+05,
794 -2.417656749061154155E+06,
795 -7.834914590078311336E+05
796 };
797
798
799 private static final double[] FD5 = new double[] {
800 1.000000000000000000E+00,
801 2.548289012949732752E+02,
802 2.099761536857815105E+04,
803 6.924122509827708985E+05,
804 9.178823229918143780E+06,
805 4.292733255630186679E+07,
806 4.803294184260528342E+07
807 };
808
809
810 private static final double[] GN3 = new double[] {
811 1.013206188102747985E-01,
812 4.445338275505123778E+00,
813 5.311228134809894481E+01,
814 1.991828186789025318E+02,
815 1.962320379716626191E+02,
816 2.054214324985006303E+01
817 };
818
819
820 private static final double[] GD3 = new double[] {
821 1.000000000000000000E+00,
822 4.539250196736893605E+01,
823 5.835905757164290666E+02,
824 2.544731331818221034E+03,
825 3.481121478565452837E+03,
826 1.013794833960028555E+03
827 };
828
829
830 private static final double[] GN4 = new double[] {
831 1.01321161761804586E-01,
832 7.11205001789782823E+00,
833 1.40959617911315524E+02,
834 9.08311749529593938E+02,
835 1.59268006085353864E+03,
836 3.13330163068755950E+02
837 };
838
839
840 private static final double[] GD4 = new double[] {
841 1.00000000000000000E+00,
842 7.17128596939302198E+01,
843 1.49051922797329229E+03,
844 1.06729678030583897E+04,
845 2.41315567213369742E+04,
846 1.15149832376260604E+04
847 };
848
849
850 private static final double[] GN5 = new double[] {
851 -1.53989733819769316E-01,
852 -4.31710157823357568E+01,
853 -3.87754141746378493E+03,
854 -1.35678867813756347E+05,
855 -1.77758950838029676E+06,
856 -6.66907061668636416E+06,
857 -1.72590224654836845E+06
858 };
859
860
861 private static final double[] GD5 = new double[] {
862 1.00000000000000000E+00,
863 2.86733194975899483E+02,
864 2.69183180396242536E+04,
865 1.02878693056687506E+06,
866 1.62095600500231646E+07,
867 9.38695862531635179E+07,
868 1.40622441123580005E+08
869 };
870
871
872
873 private Fresnel()
874 {
875
876 }
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891 public static double[] fresnel(final double x)
892 {
893 final double t = Math.abs(x);
894 double cc, ss;
895 if (t < 1.2)
896 {
897 cc = t * ratioEval(t, CN1, +1) / ratioEval(t, CD1, +1);
898 ss = t * t * t * ratioEval(t, SN1, +1) / ratioEval(t, SD1, +1);
899 }
900 else if (t < 1.6)
901 {
902 cc = t * ratioEval(t, CN2, +1) / ratioEval(t, CD2, +1);
903 ss = t * t * t * ratioEval(t, SN2, +1) / ratioEval(t, SD2, +1);
904 }
905 else if (t < 1.9)
906 {
907 double pitt2 = Math.PI * t * t / 2;
908 double sinpitt2 = Math.sin(pitt2);
909 double cospitt2 = Math.cos(pitt2);
910 double ft = (1 / t) * ratioEval(t, FN3, -1) / ratioEval(t, FD3, -1);
911 double gt = (1 / (t * t * t)) * ratioEval(t, GN3, -1) / ratioEval(t, GD3, -1);
912 cc = .5 + ft * sinpitt2 - gt * cospitt2;
913 ss = .5 - ft * cospitt2 - gt * sinpitt2;
914 }
915 else if (t < 2.4)
916 {
917 double pitt2 = Math.PI * t * t / 2;
918 double sinpitt2 = Math.sin(pitt2);
919 double cospitt2 = Math.cos(pitt2);
920 double tinv = 1 / t;
921 double tttinv = tinv * tinv * tinv;
922 double ft = tinv * ratioEval(t, FN4, -1) / ratioEval(t, FD4, -1);
923 double gt = tttinv * ratioEval(t, GN4, -1) / ratioEval(t, GD4, -1);
924 cc = .5 + ft * sinpitt2 - gt * cospitt2;
925 ss = .5 - ft * cospitt2 - gt * sinpitt2;
926 }
927 else
928 {
929 double pitt2 = Math.PI * t * t / 2;
930 double sinpitt2 = Math.sin(pitt2);
931 double cospitt2 = Math.cos(pitt2);
932 double piinv = 1 / Math.PI;
933 double tinv = 1 / t;
934 double tttinv = tinv * tinv * tinv;
935 double ttttinv = tttinv * tinv;
936 double ft = tinv * (piinv + (ttttinv * ratioEval(t, FN5, -1) / ratioEval(t, FD5, -1)));
937 double gt = tttinv * ((piinv * piinv) + (ttttinv * ratioEval(t, GN5, -1) / ratioEval(t, GD5, -1)));
938 cc = .5 + ft * sinpitt2 - gt * cospitt2;
939 ss = .5 - ft * cospitt2 - gt * sinpitt2;
940 }
941 if (x < 0)
942 {
943 cc = -cc;
944 ss = -ss;
945 }
946
947 return new double[] {cc, ss};
948 }
949
950
951
952
953
954
955
956
957 private static double ratioEval(final double t, final double[] coef, final double sign)
958 {
959 double value = 0;
960 for (int s = 0; s < coef.length; s++)
961 {
962 value += coef[s] * Math.pow(t, sign * 4 * s);
963 }
964 return value;
965 }
966
967 }