1 package org.djutils.draw.curve;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertFalse;
5 import static org.junit.jupiter.api.Assertions.assertNotEquals;
6 import static org.junit.jupiter.api.Assertions.assertTrue;
7 import static org.junit.jupiter.api.Assertions.fail;
8
9 import java.util.Iterator;
10 import java.util.NavigableMap;
11 import java.util.TreeMap;
12
13 import org.djutils.draw.Direction3d;
14 import org.djutils.draw.Export;
15 import org.djutils.draw.function.ContinuousPiecewiseLinearFunction;
16 import org.djutils.draw.line.LineSegment2d;
17 import org.djutils.draw.line.LineSegment3d;
18 import org.djutils.draw.line.PolyLine2d;
19 import org.djutils.draw.line.PolyLine3d;
20 import org.djutils.draw.line.Ray2d;
21 import org.djutils.draw.line.Ray3d;
22 import org.djutils.draw.point.DirectedPoint2d;
23 import org.djutils.draw.point.Point2d;
24 import org.djutils.draw.point.Point3d;
25 import org.djutils.math.AngleUtil;
26 import org.junit.jupiter.api.Test;
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public class TestCurves
50 {
51
52
53
54
55 @Test
56 public void testStraight()
57 {
58 NavigableMap<Double, Double> transition = new TreeMap<>();
59 transition.put(0.0, 0.0);
60 transition.put(0.2, 1.0);
61 transition.put(1.0, 2.0);
62 int steps = 100;
63 for (double x : new double[] {-10, -1, -0.1, 0, 0.1, 1, 10})
64 {
65 for (double y : new double[] {-30, -3, -0.3, 0, 0.3, 3, 30})
66 {
67 for (double dirZ : new double[] {-3, -2, -1, 0, 1, 2, 3})
68 {
69 DirectedPoint2d dp = new DirectedPoint2d(x, y, dirZ);
70 for (double length : new double[] {0.3, 3, 30})
71 {
72 Straight2d cs = new Straight2d(dp, length);
73 assertEquals(x, cs.getStartPoint().x, 0.0, "start x");
74 assertEquals(y, cs.getStartPoint().y, 0.0, "start y");
75 assertEquals(dirZ, cs.getStartPoint().dirZ, 0.00001, "start dirZ");
76 assertEquals(cs.getStartCurvature(), 0, 0, "start curvature");
77 assertEquals(x + Math.cos(dirZ) * length, cs.getEndPoint().x, 0.00001, "end x");
78 assertEquals(y + Math.sin(dirZ) * length, cs.getEndPoint().y, 0.00001, "end y");
79 assertEquals(dirZ, cs.getEndPoint().dirZ, 0.00001, "end dirZ");
80 assertEquals(cs.getEndCurvature(), 0, 0, "end curvature");
81 assertEquals(length, cs.getLength(), 0.00001, "length");
82 PolyLine2d flattened = cs.toPolyLine(null);
83 assertEquals(2, flattened.size(), "size of flattened is 2 points");
84 assertEquals(x, flattened.get(0).x, 0, "start of flattened x");
85 assertEquals(y, flattened.get(0).y, 0, "start of flattened y");
86 assertEquals(x + Math.cos(dirZ) * length, flattened.get(1).x, 0.00001, "end of flattened x");
87 assertEquals(y + Math.sin(dirZ) * length, flattened.get(1).y, 0.00001, "end of flattened y");
88 for (int step = 0; step <= steps; step++)
89 {
90 double fraction = 1.0 * step / steps;
91 Point2d position = cs.getPoint(fraction);
92 double direction = cs.getDirection(fraction);
93 Point2d closest = flattened.closestPointOnPolyLine(position);
94 assertEquals(0, position.distance(closest), 0.01, "Point at fraction lies on flattened line");
95 assertEquals(flattened.get(0).directionTo(flattened.get(1)), direction, 0.00001,
96 "Direction matches");
97 }
98 ContinuousPiecewiseLinearFunction of = new ContinuousPiecewiseLinearFunction(transition);
99 flattened = cs.toPolyLine(null, of);
100 assertEquals(3, flattened.size(),
101 "size of flattened line with one offset knot along the way is 3 points");
102 assertEquals(x, flattened.get(0).x, 0, "start of flattened x");
103 assertEquals(y, flattened.get(0).y, 0, "start of flattened y");
104 assertEquals(x + length * 0.2 * Math.cos(dirZ) - of.get(0.2) * Math.sin(dirZ), flattened.getX(1),
105 0.0001, "x of intermediate point");
106 assertEquals(y + length * 0.2 * Math.sin(dirZ) + of.get(0.2) * Math.cos(dirZ), flattened.getY(1),
107 0.0001, "x of intermediate point");
108 assertEquals(x + length * 1.0 * Math.cos(dirZ) - of.get(1.0) * Math.sin(dirZ), flattened.getX(2),
109 0.0001, "x of intermediate point");
110 assertEquals(y + length * 1.0 * Math.sin(dirZ) + of.get(1.0) * Math.cos(dirZ), flattened.getY(2),
111 0.0001, "x of intermediate point");
112 for (int step = 0; step <= steps; step++)
113 {
114 double fraction = 1.0 * step / steps;
115 Point2d position = cs.getPoint(fraction, of);
116 double direction = cs.getDirection(fraction, of);
117 Point2d closest = flattened.closestPointOnPolyLine(position);
118 assertEquals(0, position.distance(closest), 0.01, "Point at fraction lies on flattened line");
119 if (fraction < 0.2)
120 {
121 assertEquals(flattened.get(0).directionTo(flattened.get(1)), direction, 0.00001,
122 "Direction matches");
123 }
124 if (fraction > 0.2)
125 {
126 assertEquals(flattened.get(1).directionTo(flattened.get(2)), direction, 0.00001,
127 "Direction matches");
128 }
129 }
130 }
131 }
132 }
133 }
134 try
135 {
136 new Straight2d(new DirectedPoint2d(1, 2, 3), -0.2);
137 fail("negative length should have thrown an IllegalArgumentException");
138 }
139 catch (IllegalArgumentException iae)
140 {
141
142 }
143 try
144 {
145 new Straight2d(new DirectedPoint2d(1, 2, 3), 0.0);
146 fail("zero length should have thrown an IllegalArgumentException");
147 }
148 catch (IllegalArgumentException iae)
149 {
150
151 }
152 assertTrue(new Straight2d(new DirectedPoint2d(2, 5, 1), 3).toString().startsWith("Straight ["),
153 "toString returns something descriptive");
154 }
155
156
157
158
159 @Test
160 public void testArc()
161 {
162 NavigableMap<Double, Double> transition = new TreeMap<>();
163 transition.put(0.0, 0.0);
164 transition.put(0.2, 1.0);
165 transition.put(1.0, 2.0);
166 for (double x : new double[] {0, -10, -1, -0.1, 0.1, 1, 10})
167 {
168 for (double y : new double[] {0, -30, -3, -0.3, 0.3, 3, 30})
169 {
170 for (double dirZ : new double[] {-3, -2, -1, 0, Math.PI / 2, 1, 2, 3})
171 {
172 DirectedPoint2d dp = new DirectedPoint2d(x, y, dirZ);
173 for (double radius : new double[] {3.0, 0.3, 30})
174 {
175 for (boolean left : new Boolean[] {false, true})
176 {
177 for (double a : new double[] {1, 0.1, 2, 5})
178 {
179 Arc2d ca = new Arc2d(dp, radius, left, a);
180 assertEquals(x, ca.getStartPoint().x, 0.00001, "start x");
181 assertEquals(y, ca.getStartPoint().y, 0.00001, "start y");
182 assertEquals(dirZ, ca.getStartPoint().dirZ, 0, "start dirZ");
183 assertEquals(radius, ca.getStartRadius(), 0.000001, "start radius");
184 assertEquals(radius, ca.getEndRadius(), 0.000001, "end radius");
185 assertEquals(1 / radius, ca.getStartCurvature(), 0.00001, "start curvature");
186 assertEquals(1 / radius, ca.getEndCurvature(), 0.00001, "end curvature");
187 assertEquals(dirZ, ca.getStartDirection(), 0, "start direction");
188 assertEquals(AngleUtil.normalizeAroundZero(dirZ + (left ? a : -a)), ca.getEndDirection(),
189 0.00001, "end direction");
190 int sign = left ? 1 : -1;
191 Point2d center =
192 new Point2d(x - Math.sin(dirZ) * radius * sign, y + Math.cos(dirZ) * radius * sign);
193 DirectedPoint2d expectedEnd =
194 new DirectedPoint2d(center.x + Math.sin(dirZ + a * sign) * radius * sign,
195 center.y - Math.cos(dirZ + a * sign) * radius * sign,
196 AngleUtil.normalizeAroundZero(dirZ + a * sign));
197 assertTrue(expectedEnd.epsilonEquals(ca.getEndPoint(), 0.001, 0.00001), " end point");
198 assertEquals(radius * a, ca.getLength(), 0.00001, "length");
199
200 PolyLine2d flattened = ca.toPolyLine(new Flattener2d.NumSegments(20));
201 verifyNumSegments(ca, flattened, 20);
202
203 double precision = 0.1;
204 flattened = ca.toPolyLine(new Flattener2d.MaxDeviation(precision));
205 verifyMaxDeviation(ca, flattened, precision);
206
207 double anglePrecision = 0.01;
208 flattened = ca.toPolyLine(new Flattener2d.MaxAngle(anglePrecision));
209 verifyMaxAngleDeviation(flattened, ca, anglePrecision);
210
211 flattened = ca.toPolyLine(new Flattener2d.MaxDeviationAndAngle(precision, anglePrecision));
212 verifyMaxDeviation(ca, flattened, precision);
213 verifyMaxAngleDeviation(flattened, ca, anglePrecision);
214
215 if (radius > 2 && ca.getLength() > 2)
216 {
217 ContinuousPiecewiseLinearFunction of = new ContinuousPiecewiseLinearFunction(transition);
218
219 flattened = ca.toPolyLine(new OffsetFlattener2d.NumSegments(30), of);
220 verifyNumSegments(ca, of, flattened, 30);
221
222 flattened = ca.toPolyLine(new OffsetFlattener2d.MaxDeviation(precision), of);
223 verifyMaxDeviation(ca, of, flattened, precision);
224
225 flattened = ca.toPolyLine(new OffsetFlattener2d.MaxAngle(anglePrecision), of);
226 verifyMaxAngleDeviation(flattened, ca, of, anglePrecision);
227
228 flattened = ca.toPolyLine(
229 new OffsetFlattener2d.MaxDeviationAndAngle(precision, anglePrecision), of);
230 verifyMaxDeviation(ca, of, flattened, precision);
231 verifyMaxAngleDeviation(flattened, ca, of, anglePrecision);
232 }
233 }
234 }
235 }
236 }
237 }
238 }
239 try
240 {
241 new Arc2d(new DirectedPoint2d(1, 2, 3), -0.01, true, 1);
242 fail("negative radius should have thrown an IllegalArgumentException");
243 }
244 catch (IllegalArgumentException iae)
245 {
246
247 }
248 new Arc2d(new DirectedPoint2d(1, 2, 3), 0, true, 1);
249 try
250 {
251 new Arc2d(new DirectedPoint2d(1, 2, 3), 10, true, -0.1);
252 fail("negative angle should have thrown an IllegalArgumentException");
253 }
254 catch (IllegalArgumentException iae)
255 {
256
257 }
258 new Arc2d(new DirectedPoint2d(1, 2, 3), 10, true, 0);
259 assertTrue(new Arc2d(new DirectedPoint2d(1, 2, 3), 10, true, 1).toString().startsWith("Arc ["),
260 "toString returns something descriptive");
261
262 Arc2d arc2d = new Arc2d(new DirectedPoint2d(1, 2, 3), 10, true, 1.5);
263 assertEquals(1.5, arc2d.getAngle(), 0.00001, "Angle is returned");
264 assertTrue(arc2d.isLeft(), "arc is left");
265 arc2d = new Arc2d(new DirectedPoint2d(1, 2, 3), 10, false, 1.5);
266 assertFalse(arc2d.isLeft(), "arc is right");
267 }
268
269
270
271
272
273
274
275 private static void verifyNumSegments(final Curve2d curve, final PolyLine2d flattened, final int numSegments)
276 {
277 assertEquals(numSegments, flattened.size() - 1, "Number of segments");
278 for (int i = 0; i <= numSegments; i++)
279 {
280 double fraction = i * 1.0 / numSegments;
281 Point2d expectedPoint = curve.getPoint(fraction);
282 Point2d actualPoint = flattened.get(i);
283 assertEquals(expectedPoint, actualPoint, "Point in flattened line matches point generated by the continuous arc");
284 }
285 }
286
287
288
289
290
291
292
293
294 private static void verifyNumSegments(final OffsetCurve2d curve, final ContinuousPiecewiseLinearFunction of,
295 final PolyLine2d flattened, final int numSegments)
296 {
297 assertEquals(numSegments, flattened.size() - 1, "Number of segments");
298 for (int i = 0; i <= numSegments; i++)
299 {
300 double fraction = i * 1.0 / numSegments;
301 Point2d expectedPoint = curve.getPoint(fraction, of);
302 Point2d actualPoint = flattened.get(i);
303 assertEquals(expectedPoint, actualPoint, "Point in flattened line matches point generated by the continuous arc");
304 }
305 }
306
307
308
309
310
311
312
313 private static void verifyNumSegments(final Curve3d curve, final PolyLine3d flattened, final int numSegments)
314 {
315 assertEquals(numSegments, flattened.size() - 1, "Number of segments");
316 for (int i = 0; i <= numSegments; i++)
317 {
318 double fraction = i * 1.0 / numSegments;
319 Point3d expectedPoint = curve.getPoint(fraction);
320 Point3d actualPoint = flattened.get(i);
321 assertEquals(expectedPoint, actualPoint, "Point in flattened line matches point generated by the continuous arc");
322 }
323 }
324
325
326 public static final double FUDGE_FACTOR = 1.4;
327
328
329
330
331
332
333
334 private static void verifyMaxDeviation(final Curve2d curve, final PolyLine2d flattened, final double precision)
335 {
336 int steps = 100;
337 for (int step = 0; step <= steps; step++)
338 {
339 double fraction = 1.0 * step / steps;
340 Point2d curvePoint = curve.getPoint(fraction);
341 Point2d polyLinePoint = flattened.closestPointOnPolyLine(curvePoint);
342 if (curvePoint.distance(polyLinePoint) > precision * FUDGE_FACTOR)
343 {
344 printSituation(-1, 0.5, flattened, curve, fraction, null);
345 curve.toPolyLine(new Flattener2d.MaxDeviation(precision));
346 }
347 assertEquals(0, curvePoint.distance(polyLinePoint), precision * FUDGE_FACTOR,
348 "point on Curve2d is close to PolyLine2d");
349 }
350 }
351
352
353
354
355
356
357
358
359 private static void verifyMaxDeviation(final OffsetCurve2d curve, final ContinuousPiecewiseLinearFunction of,
360 final PolyLine2d flattened, final double precision)
361 {
362 double fraction = 0.0;
363 int steps = flattened.size() - 1;
364 for (int step = 0; step < steps; step++)
365 {
366 double fractionAtStartOfSegment = Double.NaN;
367 double fractionAtEndOfSegment = Double.NaN;
368 LineSegment2d lineSegment = flattened.getSegment(step);
369
370 for (double positionOnSegment : new double[] {0.01, 0.99})
371 {
372 Point2d pointOnSegment = lineSegment.getLocation(positionOnSegment * lineSegment.getLength());
373 double flattenedDir = lineSegment.getStartPoint().directionTo(lineSegment.getEndPoint());
374
375 double veryClose = 0.1 / flattened.size() / 5;
376
377 double highFraction = Math.min(1.0, fraction + Math.min(20.0 / steps, 0.5));
378 while (highFraction - fraction > veryClose)
379 {
380 double midFraction = (fraction + highFraction) / 2;
381 Point2d midPoint = curve.getPoint(midFraction, of);
382 double dir = midPoint.directionTo(pointOnSegment);
383 double dirDifference = Math.abs(AngleUtil.normalizeAroundZero(flattenedDir - dir));
384 if (dirDifference < Math.PI / 4)
385 {
386 fraction = midFraction;
387 }
388 else
389 {
390 highFraction = midFraction;
391 }
392 }
393 if (positionOnSegment < 0.5)
394 {
395 fractionAtStartOfSegment = fraction;
396 }
397 else
398 {
399 fractionAtEndOfSegment = fraction;
400 }
401 }
402 fraction = (fractionAtStartOfSegment + fractionAtEndOfSegment) / 2;
403 Point2d curvePoint = curve.getPoint(fraction, of);
404 double actualDistance = curvePoint.distance(lineSegment.closestPointOnSegment(curvePoint));
405 if (actualDistance > precision * FUDGE_FACTOR)
406 {
407 printSituation(step, 0.5, flattened, curve, fraction, of);
408 curve.toPolyLine(new OffsetFlattener2d.MaxDeviation(precision), of);
409 }
410 assertEquals(0, actualDistance, precision * FUDGE_FACTOR, "point on OffsetCurve2d is close to PolyLine2d");
411 fraction = fractionAtEndOfSegment;
412 }
413 }
414
415
416
417
418
419
420
421 private static void verifyMaxDeviation(final Curve3d curve, final PolyLine3d flattened, final double precision)
422 {
423 int steps = 100;
424 for (int step = 0; step <= steps; step++)
425 {
426 double fraction = 1.0 * step / steps;
427 Point3d curvePoint = curve.getPoint(fraction);
428 Point3d polyLinePoint = flattened.closestPointOnPolyLine(curvePoint);
429 if (curvePoint.distance(polyLinePoint) > precision * FUDGE_FACTOR)
430 {
431 printSituation(-1, 0.5, flattened, curve, fraction);
432 curve.toPolyLine(new Flattener3d.MaxDeviation(precision));
433 }
434 assertEquals(0, curvePoint.distance(polyLinePoint), precision * FUDGE_FACTOR,
435 "point on Curve3d is close to PolyLine2d");
436 }
437 }
438
439
440
441
442
443
444
445
446
447
448 public static void printSituation(final int segment, final double positionOnSegment, final PolyLine2d flattened,
449 final Object curve, final double fraction, final ContinuousPiecewiseLinearFunction of)
450 {
451 System.out.println("# " + curve);
452 System.out.print(Export.toPlot(flattened));
453 if (null != of)
454 {
455 System.out.print("c0,1,0 "
456 + Export.toPlot(((OffsetCurve2d) curve).toPolyLine(new OffsetFlattener2d.MaxDeviation(0.01), of)));
457 }
458 System.out.print("c0,1,1 " + Export.toPlot(((Curve2d) curve).toPolyLine(new Flattener2d.MaxDeviation(0.01))));
459 Point2d pointAtFraction =
460 null != of ? ((OffsetCurve2d) curve).getPoint(fraction, of) : ((Curve2d) curve).getPoint(fraction);
461 double faDir =
462 null != of ? ((OffsetCurve2d) curve).getDirection(fraction, of) : ((Curve2d) curve).getDirection(fraction);
463 System.out.print("# curveDirection=" + faDir);
464 if (segment >= 0)
465 {
466 double flattenedDir = flattened.get(segment).directionTo(flattened.get(segment + 1));
467 System.out.println(", segment direction=" + flattenedDir + " directionDifference=" + (flattenedDir - faDir));
468 System.out.println("# segment=" + segment + ", positionOnSegment=" + positionOnSegment);
469 System.out.println("sw0.1c1,0.6,0.6 " + Export.toPlot(flattened.getSegment(segment)) + " r");
470 LineSegment2d lineSegment = flattened.getSegment(segment);
471 Point2d closestPointOnSegment = lineSegment.closestPointOnSegment(pointAtFraction);
472 System.out.println("# closestPointOnSegment=" + closestPointOnSegment + " distance from pointAtFraction to segment="
473 + pointAtFraction.distance(closestPointOnSegment));
474 }
475 else
476 {
477 System.out.println();
478 }
479 Point2d closestPointOnFlattened = flattened.closestPointOnPolyLine(pointAtFraction);
480 System.out.println("# fraction=" + fraction + " pointAtFraction=" + pointAtFraction + ", closestPointOnFlattened="
481 + closestPointOnFlattened + ", distance=" + pointAtFraction.distance(closestPointOnFlattened));
482 System.out.print("# segments: ");
483 for (int i = 0; i < flattened.size() - 1; i++)
484 {
485 System.out.print(String.format("%s%3d%s ", i == segment ? "##" : " ", i, i == segment ? "##" : " "));
486 }
487 System.out.print("\n# angles: ");
488 for (int i = 0; i < flattened.size() - 1; i++)
489 {
490 System.out.print(String.format(" %7.4f", flattened.get(i).directionTo(flattened.get(i + 1))));
491 }
492 System.out.print("\n# lengths: ");
493 for (int i = 0; i < flattened.size() - 1; i++)
494 {
495 System.out.print(String.format(" %7.4f", flattened.get(i).distance(flattened.get(i + 1))));
496 }
497 System.out.print("\n# x: ");
498 for (int i = 0; i < flattened.size(); i++)
499 {
500 System.out.print(String.format(" %7.4f", flattened.get(i).x));
501 }
502 System.out.print("\n# y: ");
503 for (int i = 0; i < flattened.size(); i++)
504 {
505 System.out.print(String.format(" %7.4f", flattened.get(i).y));
506 }
507 System.out.println("\nc0,0,1 M0,0L " + pointAtFraction.x + "," + pointAtFraction.y);
508 if (null != of)
509 {
510 System.out.print("# Knots in ofl2d domain:");
511 OffsetCurve2d ofl2d = (OffsetCurve2d) curve;
512 for (Iterator<ContinuousPiecewiseLinearFunction.TupleSt> iterator = of.iterator(); iterator.hasNext();)
513 {
514 double knot = iterator.next().s();
515 if (knot != 0.0 && knot != 1.0)
516 {
517 double t = ofl2d.getT(knot * ofl2d.getLength());
518 System.out.println("\tknot at " + knot + " -> fraction " + t + " point " + ofl2d.getPoint(t, of));
519 }
520 }
521 }
522
523 System.out.println("break here");
524 }
525
526
527
528
529
530
531
532
533
534 public static void printSituation(final int segment, final double positionOnSegment, final PolyLine3d flattened,
535 final Curve3d curve, final double fraction)
536 {
537 System.out.println("# " + curve);
538 System.out.print(Export.toPlot(flattened.project()));
539 System.out.print("c0,1,1 " + Export.toPlot(curve.toPolyLine(new Flattener3d.NumSegments(500)).project()));
540 Point3d pointAtFraction = curve.getPoint(fraction);
541 Direction3d faDir = curve.getDirection(fraction);
542 System.out.print("# curveDirection=" + faDir);
543 if (segment >= 0)
544 {
545 Direction3d flattenedDir = flattened.get(segment).directionTo(flattened.get(segment + 1));
546 System.out.println(
547 ", segment direction=" + flattenedDir + " directionDifference=" + flattenedDir.directionDifference(faDir));
548 System.out.println("# segment=" + segment + ", positionOnSegment=" + positionOnSegment);
549 System.out.println("sw0.1c1,0.6,0.6 " + Export.toPlot(flattened.getSegment(segment).project()) + " r");
550 LineSegment3d lineSegment = flattened.getSegment(segment);
551 Point3d closestPointOnSegment = lineSegment.closestPointOnSegment(pointAtFraction);
552 System.out.println("# closestPointOnSegment=" + closestPointOnSegment + " distance from pointAtFraction to segment="
553 + pointAtFraction.distance(closestPointOnSegment));
554 }
555 else
556 {
557 System.out.println();
558 }
559 Point3d closestPointOnFlattened = flattened.closestPointOnPolyLine(pointAtFraction);
560 System.out.println("# fraction=" + fraction + " pointAtFraction=" + pointAtFraction + "\n# closestPointOnFlattened="
561 + closestPointOnFlattened + ", distance=" + pointAtFraction.distance(closestPointOnFlattened));
562 System.out.print("# segments: ");
563 for (int i = 0; i < flattened.size() - 1; i++)
564 {
565 System.out.print(String.format("%s%3d%s ", i == segment ? "##" : " ", i, i == segment ? "##" : " "));
566 }
567 System.out.print("\n# dirY: ");
568 for (int i = 0; i < flattened.size() - 1; i++)
569 {
570 Direction3d segmentDirection = flattened.get(i).directionTo(flattened.get(i + 1));
571 System.out.print(String.format(" %7.4f", segmentDirection.dirY));
572 }
573 System.out.print("\n# dirZ: ");
574 for (int i = 0; i < flattened.size() - 1; i++)
575 {
576 Direction3d segmentDirection = flattened.get(i).directionTo(flattened.get(i + 1));
577 System.out.print(String.format(" %7.4f", segmentDirection.dirZ));
578 }
579 System.out.print("\n# lengths: ");
580 for (int i = 0; i < flattened.size() - 1; i++)
581 {
582 System.out.print(String.format(" %7.4f", flattened.get(i).distance(flattened.get(i + 1))));
583 }
584 System.out.print("\n# x: ");
585 for (int i = 0; i < flattened.size(); i++)
586 {
587 System.out.print(String.format(" %7.4f", flattened.get(i).x));
588 }
589 System.out.print("\n# y: ");
590 for (int i = 0; i < flattened.size(); i++)
591 {
592 System.out.print(String.format(" %7.4f", flattened.get(i).y));
593 }
594 System.out.print("\n# z: ");
595 for (int i = 0; i < flattened.size(); i++)
596 {
597 System.out.print(String.format(" %7.4f", flattened.get(i).z));
598 }
599 System.out.println("\nc0,0,1 M0,0L " + pointAtFraction.x + "," + pointAtFraction.y);
600
601 System.out.println("break here");
602 }
603
604
605
606
607
608
609
610 public static void verifyMaxAngleDeviation(final PolyLine2d flattened, final Curve2d curve, final double anglePrecision)
611 {
612 double fraction = 0.0;
613 for (int step = 0; step < flattened.size() - 1; step++)
614 {
615 double flattenedDir = flattened.get(step).directionTo(flattened.get(step + 1));
616 for (double positionOnSegment : new double[] {0.1, 0.9})
617 {
618 Point2d flattenPoint = flattened.get(step).interpolate(flattened.get(step + 1), positionOnSegment);
619
620 double veryClose = 0.1 / flattened.size() / 5;
621
622 double highFraction = Math.min(1.0, fraction + Math.min(20.0 / flattened.size(), 0.5));
623 while (highFraction - fraction > veryClose)
624 {
625 double midFraction = (fraction + highFraction) / 2;
626 Point2d midPoint = curve.getPoint(midFraction);
627 double dir = flattenPoint.directionTo(midPoint);
628 double dirDifference = Math.abs(AngleUtil.normalizeAroundZero(flattenedDir - dir));
629 if (dirDifference < Math.PI / 2)
630 {
631 highFraction = midFraction;
632 }
633 else
634 {
635 fraction = midFraction;
636 }
637 }
638 double faDir = curve.getDirection(fraction);
639 if (Math.abs(AngleUtil.normalizeAroundZero(flattenedDir - faDir)) > anglePrecision * FUDGE_FACTOR)
640 {
641 printSituation(step, positionOnSegment, flattened, curve, fraction, null);
642 curve.toPolyLine(new Flattener2d.MaxAngle(anglePrecision));
643 }
644 assertEquals(0, AngleUtil.normalizeAroundZero(flattenedDir - faDir), anglePrecision * FUDGE_FACTOR,
645 "direction difference should be less than anglePrecision");
646 }
647 }
648 }
649
650
651
652
653
654
655
656
657
658 public static void verifyMaxAngleDeviation(final PolyLine2d flattened, final OffsetCurve2d curve,
659 final ContinuousPiecewiseLinearFunction of, final double anglePrecision)
660 {
661 double fraction = 0.0;
662 for (int step = 0; step < flattened.size() - 1; step++)
663 {
664 double flattenedDir = flattened.get(step).directionTo(flattened.get(step + 1));
665 for (double positionOnSegment : new double[] {0.1, 0.9})
666 {
667 Point2d flattenPoint = flattened.get(step).interpolate(flattened.get(step + 1), positionOnSegment);
668
669 double veryClose = 0.1 / flattened.size() / 5;
670
671 double highFraction = Math.min(1.0, fraction + 0.1);
672 while (highFraction - fraction > veryClose)
673 {
674 double midFraction = (fraction + highFraction) / 2;
675 Point2d midPoint = curve.getPoint(midFraction, of);
676 double dir = flattenPoint.directionTo(midPoint);
677 double dirDifference = Math.abs(AngleUtil.normalizeAroundZero(flattenedDir - dir));
678 if (dirDifference < Math.PI / 2)
679 {
680 highFraction = midFraction;
681 }
682 else
683 {
684 fraction = midFraction;
685 }
686 }
687
688 Double knot = null;
689 for (Iterator<ContinuousPiecewiseLinearFunction.TupleSt> iterator = of.iterator(); iterator.hasNext();)
690 {
691 knot = iterator.next().s();
692 if (knot != 0.0 && knot != 1.0 && Math.abs(curve.getT(knot * curve.getLength()) - fraction) <= veryClose)
693 {
694 break;
695 }
696 knot = null;
697 }
698 if (knot == null)
699 {
700 double faDir = curve.getDirection(fraction, of);
701 if (Math.abs(AngleUtil.normalizeAroundZero(flattenedDir - faDir)) > anglePrecision * FUDGE_FACTOR)
702 {
703 printSituation(step, positionOnSegment, flattened, curve, fraction, of);
704 curve.getDirection(fraction, of);
705 curve.toPolyLine(new OffsetFlattener2d.MaxAngle(anglePrecision), of);
706 }
707 assertEquals(0, AngleUtil.normalizeAroundZero(flattenedDir - faDir), anglePrecision * FUDGE_FACTOR,
708 "direction difference should be less than anglePrecision");
709 }
710 }
711 }
712 }
713
714
715
716
717
718
719
720 public static void verifyMaxAngleDeviation(final PolyLine3d flattened, final Curve3d curve, final double anglePrecision)
721 {
722 double fraction = 0.0;
723 for (int step = 0; step < flattened.size() - 1; step++)
724 {
725 Direction3d flattenedDir = flattened.get(step).directionTo(flattened.get(step + 1));
726 for (double positionOnSegment : new double[] {0.1, 0.9})
727 {
728 Point3d flattenPoint = flattened.get(step).interpolate(flattened.get(step + 1), positionOnSegment);
729
730 double veryClose = 0.1 / flattened.size() / 5;
731
732 double highFraction = Math.min(1.0, fraction + Math.min(20.0 / flattened.size(), 0.5));
733 while (highFraction - fraction > veryClose)
734 {
735 double midFraction = (fraction + highFraction) / 2;
736 Point3d midPoint = curve.getPoint(midFraction);
737 Direction3d dir = flattenPoint.directionTo(midPoint);
738 double dirDifference = flattenedDir.directionDifference(dir);
739 if (dirDifference < Math.PI / 2)
740 {
741 highFraction = midFraction;
742 }
743 else
744 {
745 fraction = midFraction;
746 }
747 }
748 Direction3d faDir = curve.getDirection(fraction);
749 if (flattenedDir.directionDifference(faDir) > anglePrecision * FUDGE_FACTOR)
750 {
751 printSituation(step, positionOnSegment, flattened, curve, fraction);
752 }
753 assertEquals(0, flattenedDir.directionDifference(faDir), anglePrecision * FUDGE_FACTOR,
754 "direction difference should be less than anglePrecision");
755 }
756 }
757 }
758
759
760
761
762 @Test
763 public void testBezier2d()
764 {
765 NavigableMap<Double, Double> transition = new TreeMap<>();
766 transition.put(0.0, 0.0);
767 transition.put(0.2, 1.0);
768 transition.put(1.0, 2.0);
769 for (double x : new double[] {0, -1, -0.1, 10})
770 {
771 for (double y : new double[] {0, -3, -0.3, 30})
772 {
773 for (double dirZ : new double[] {-3, -2, -1, 0, Math.PI / 2, 1, 2, 3})
774 {
775 Ray2d dp = new Ray2d(x, y, dirZ);
776 for (double x2 : new double[] {10.5, 30})
777 {
778 for (double y2 : new double[] {30.5, 70})
779 {
780 for (double dirZ2 : new double[] {1, 0.1, 2.5, 5})
781 {
782 Ray2d dp2 = new Ray2d(x2, y2, dirZ2);
783 BezierCubic2d cbc = new BezierCubic2d(dp, dp2);
784 assertEquals(x, cbc.getStartPoint().x, 0, "start x");
785 assertEquals(y, cbc.getStartPoint().y, 0, "start y");
786 assertEquals(dirZ, cbc.getStartPoint().dirZ, 0.00001, "start dirZ");
787 assertEquals(dirZ, cbc.getStartDirection(), 0.00001, "start direction");
788 assertEquals(x2, cbc.getEndPoint().x, 0.000001, "end x");
789 assertEquals(y2, cbc.getEndPoint().y, 0.000001, "end y");
790 assertEquals(AngleUtil.normalizeAroundZero(dirZ2), cbc.getEndPoint().dirZ, 0.00001, "end dirZ");
791 assertEquals(AngleUtil.normalizeAroundZero(dirZ2), cbc.getEndDirection(), 0.00001,
792 "end direction");
793
794 PolyLine2d flattened = cbc.toPolyLine(new Flattener2d.NumSegments(20));
795 verifyNumSegments(cbc, flattened, 20);
796
797 double precision = 0.1;
798 flattened = cbc.toPolyLine(new Flattener2d.MaxDeviation(precision));
799 verifyMaxDeviation(cbc, flattened, precision);
800 double anglePrecision = 0.01;
801 double meanDir = dp.directionTo(dp2);
802 if (Math.abs(AngleUtil.normalizeAroundZero(meanDir - dirZ)) < 2.5
803 && Math.abs(AngleUtil.normalizeAroundZero(meanDir - dirZ2)) < 2.5)
804 {
805
806 flattened = cbc.toPolyLine(new Flattener2d.MaxAngle(anglePrecision));
807 verifyMaxAngleDeviation(flattened, cbc, anglePrecision);
808
809 flattened = cbc.toPolyLine(new Flattener2d.MaxDeviationAndAngle(precision, anglePrecision));
810 verifyMaxDeviation(cbc, flattened, precision);
811 verifyMaxAngleDeviation(flattened, cbc, anglePrecision);
812 }
813
814 if (cbc.getStartRadius() > 2 && cbc.getLength() > 2)
815 {
816 ContinuousPiecewiseLinearFunction of = new ContinuousPiecewiseLinearFunction(transition);
817
818 flattened = cbc.toPolyLine(new OffsetFlattener2d.NumSegments(30), of);
819 verifyNumSegments(cbc, of, flattened, 30);
820
821 flattened = cbc.toPolyLine(new OffsetFlattener2d.MaxDeviation(precision), of);
822 if (Math.abs(AngleUtil.normalizeAroundZero(meanDir - dirZ)) < 2
823 && Math.abs(AngleUtil.normalizeAroundZero(meanDir - dirZ2)) < 2)
824 {
825
826 verifyMaxDeviation(cbc, of, flattened, precision);
827 flattened = cbc.toPolyLine(new OffsetFlattener2d.MaxAngle(anglePrecision), of);
828 verifyMaxAngleDeviation(flattened, cbc, of, anglePrecision);
829
830 flattened = cbc.toPolyLine(
831 new OffsetFlattener2d.MaxDeviationAndAngle(precision, anglePrecision), of);
832 verifyMaxDeviation(cbc, of, flattened, precision);
833 verifyMaxAngleDeviation(flattened, cbc, of, anglePrecision);
834 }
835 }
836 }
837 }
838 }
839 }
840 }
841 }
842 }
843
844
845
846
847 @Test
848 public void testCubicbezierRadiusAndSome()
849 {
850
851
852 double controlDistance = (4.0 / 3) * Math.tan(Math.PI / 8);
853 BezierCubic2d bcb = new BezierCubic2d(new Point2d(1, 0), new Point2d(1, controlDistance),
854 new Point2d(controlDistance, 1), new Point2d(0, 1));
855 assertEquals(1.0, bcb.getStartRadius(), 0.03, "start radius of cubic bezier approximation of unit circle");
856 assertEquals(1.0, bcb.getEndRadius(), 0.03, "end radius of cubic bezier approximation of unit circle");
857 bcb = new BezierCubic2d(new Point2d(1, 0), new Point2d(1, -controlDistance), new Point2d(controlDistance, -1),
858 new Point2d(0, -1));
859 assertEquals(-1.0, bcb.getStartRadius(), 0.03, "start radius of cubic bezier approximation of unit circle");
860 assertEquals(-1.0, bcb.getEndRadius(), 0.03, "end radius of cubic bezier approximation of unit circle");
861 assertEquals(0.0, bcb.getT(0.0), 0.0, "getT is exact at 0.0");
862 assertEquals(0.0, bcb.getT(0.001 * bcb.getLength()), 0.1, "getT is close to 0.0 for small input");
863 assertEquals(0.5, bcb.getT(0.5 * bcb.getLength()), 0.01, "getT is close to 0.5 halfway on symmetrical Bezier");
864 assertEquals(1.0, bcb.getT(0.999 * bcb.getLength()), 0.1, "getT is close to 1.0 for input close to 1.0");
865 assertEquals(1.0, bcb.getT(bcb.getLength()), 0.0, "getT is exact at 1.0");
866
867 try
868 {
869 bcb.split(-0.0001);
870 fail("Negative split point should have thrown IllegalArgumentException");
871 }
872 catch (IllegalArgumentException iae)
873 {
874
875 }
876
877 try
878 {
879 bcb.split(1.0001);
880 fail("Split point beyond 1.0 should have thrown IllegalArgumentException");
881 }
882 catch (IllegalArgumentException iae)
883 {
884
885 }
886
887 }
888
889
890
891
892 @Test
893 public void testBezier3d()
894 {
895 NavigableMap<Double, Double> transition = new TreeMap<>();
896 transition.put(0.0, 0.0);
897 transition.put(0.2, 1.0);
898 transition.put(1.0, 2.0);
899 for (double x : new double[] {0, 10})
900 {
901 for (double y : new double[] {0, 30})
902 {
903 for (double z : new double[] {0, 5})
904 {
905 for (double dirY : new double[] {Math.PI / 2, 0.3, 3})
906 {
907 for (double dirZ : new double[] {0, -2, Math.PI / 2, 2})
908 {
909 Ray3d dp = new Ray3d(x, y, z, dirY, dirZ);
910 for (double x2 : new double[] {10.5, 30})
911 {
912 for (double y2 : new double[] {30.5, 70})
913 {
914 for (double z2 : new double[] {3, 6})
915 {
916 for (double dirY2 : new double[] {Math.PI / 2, 1, 2.5})
917 {
918 for (double dirZ2 : new double[] {0, 0.1, 2.5, 5})
919 {
920 Ray3d dp2 = new Ray3d(x2, y2, z2, dirY2, dirZ2);
921 BezierCubic3d cbc = new BezierCubic3d(dp, dp2);
922 assertEquals(x, cbc.getStartPoint().x, 0, "start x");
923 assertEquals(y, cbc.getStartPoint().y, 0, "start y");
924 assertEquals(z, cbc.getStartPoint().z, 0, "start y");
925 assertEquals(dirZ, cbc.getStartPoint().dirZ, 0.0001, "start dirZ");
926 assertEquals(dirY, cbc.getStartDirection().dirY, 0.0001, "start direction");
927 assertEquals(dirZ, cbc.getStartDirection().dirZ, 0.0001, "start direction");
928 assertEquals(x2, cbc.getEndPoint().x, 0.000001, "end x");
929 assertEquals(y2, cbc.getEndPoint().y, 0.000001, "end y");
930 assertEquals(z2, cbc.getEndPoint().z, 0.000001, "end y");
931 assertEquals(AngleUtil.normalizeAroundZero(dirY2), cbc.getEndPoint().dirY,
932 0.00001, "end dirY");
933 assertEquals(AngleUtil.normalizeAroundZero(dirY2), cbc.getEndDirection().dirY,
934 0.00001, "end dirY");
935 assertEquals(AngleUtil.normalizeAroundZero(dirZ2), cbc.getEndPoint().dirZ,
936 0.00001, "end dirZ");
937 assertEquals(AngleUtil.normalizeAroundZero(dirZ2), cbc.getEndDirection().dirZ,
938 0.00001, "end dirZ");
939
940 PolyLine3d flattened = cbc.toPolyLine(new Flattener3d.NumSegments(20));
941 verifyNumSegments(cbc, flattened, 20);
942
943 double precision = 0.1;
944 flattened = cbc.toPolyLine(new Flattener3d.MaxDeviation(precision));
945 verifyMaxDeviation(cbc, flattened, precision);
946
947 Direction3d meanDir = dp.directionTo(dp2);
948 if (dp.getDir().directionDifference(dp2.getDir()) < 2
949 && dp.getDir().directionDifference(meanDir) < 2
950 && dp2.getDir().directionDifference(meanDir) < 2)
951 {
952
953
954 double anglePrecision = 0.01;
955
956 flattened = cbc.toPolyLine(new Flattener3d.MaxAngle(anglePrecision));
957 verifyMaxAngleDeviation(flattened, cbc, anglePrecision);
958
959 flattened = cbc.toPolyLine(
960 new Flattener3d.MaxDeviationAndAngle(precision, anglePrecision));
961 verifyMaxDeviation(cbc, flattened, precision);
962 verifyMaxAngleDeviation(flattened, cbc, anglePrecision);
963 }
964 }
965 }
966 }
967 }
968 }
969 }
970 }
971 }
972 }
973 }
974 }
975
976
977
978
979 @Test
980 public void testFlattenerExceptions()
981 {
982 for (int badAmount : new int[] {0, -1})
983 {
984 try
985 {
986 new Flattener2d.NumSegments(badAmount);
987 fail("fewer than 1 segments should have thrown an IllegalArgumentException");
988 }
989 catch (IllegalArgumentException e)
990 {
991
992 }
993
994 try
995 {
996 new OffsetFlattener2d.NumSegments(badAmount);
997 fail("fewer than 1 segments should have thrown an IllegalArgumentException");
998 }
999 catch (IllegalArgumentException e)
1000 {
1001
1002 }
1003
1004 try
1005 {
1006 new Flattener3d.NumSegments(badAmount);
1007 fail("fewer than 1 segments should have thrown an IllegalArgumentException");
1008 }
1009 catch (IllegalArgumentException e)
1010 {
1011
1012 }
1013 }
1014 for (double badAmount : new double[] {0.0, -0.1})
1015 {
1016 try
1017 {
1018 new Flattener2d.MaxAngle(badAmount);
1019 fail("angle tolerance <= 0 should have thrown an IllegalArgumentException");
1020 }
1021 catch (IllegalArgumentException e)
1022 {
1023
1024 }
1025
1026 try
1027 {
1028 new OffsetFlattener2d.MaxAngle(badAmount);
1029 fail("angle tolerance <= 0 should have thrown an IllegalArgumentException");
1030 }
1031 catch (IllegalArgumentException e)
1032 {
1033
1034 }
1035
1036 try
1037 {
1038 new Flattener3d.MaxAngle(badAmount);
1039 fail("angle tolerance <= 0 should have thrown an IllegalArgumentException");
1040 }
1041 catch (IllegalArgumentException e)
1042 {
1043
1044 }
1045
1046 try
1047 {
1048 new Flattener2d.MaxDeviationAndAngle(1.0, badAmount);
1049 fail("angle tolerance <= 0 should have thrown an IllegalArgumentException");
1050 }
1051 catch (IllegalArgumentException e)
1052 {
1053
1054 }
1055
1056 try
1057 {
1058 new OffsetFlattener2d.MaxDeviationAndAngle(1.0, badAmount);
1059 fail("angle tolerance <= 0 should have thrown an IllegalArgumentException");
1060 }
1061 catch (IllegalArgumentException e)
1062 {
1063
1064 }
1065
1066 try
1067 {
1068 new Flattener3d.MaxDeviationAndAngle(1.0, badAmount);
1069 fail("angle tolerance <= 0 should have thrown an IllegalArgumentException");
1070 }
1071 catch (IllegalArgumentException e)
1072 {
1073
1074 }
1075
1076 try
1077 {
1078 new Flattener2d.MaxDeviationAndAngle(badAmount, 0.1);
1079 fail("deviation tolerance <= 0 should have thrown an IllegalArgumentException");
1080 }
1081 catch (IllegalArgumentException e)
1082 {
1083
1084 }
1085
1086 try
1087 {
1088 new OffsetFlattener2d.MaxDeviationAndAngle(badAmount, 0.1);
1089 fail("deviation tolerance <= 0 should have thrown an IllegalArgumentException");
1090 }
1091 catch (IllegalArgumentException e)
1092 {
1093
1094 }
1095
1096 try
1097 {
1098 new Flattener3d.MaxDeviationAndAngle(badAmount, 0.1);
1099 fail("deviation tolerance <= 0 should have thrown an IllegalArgumentException");
1100 }
1101 catch (IllegalArgumentException e)
1102 {
1103
1104 }
1105
1106 try
1107 {
1108 new Flattener2d.MaxDeviation(badAmount);
1109 fail("deviation tolerance <= 0 should have thrown an IllegalArgumentException");
1110 }
1111 catch (IllegalArgumentException e)
1112 {
1113
1114 }
1115
1116 try
1117 {
1118 new OffsetFlattener2d.MaxDeviation(badAmount);
1119 fail("deviation tolerance <= 0 should have thrown an IllegalArgumentException");
1120 }
1121 catch (IllegalArgumentException e)
1122 {
1123
1124 }
1125
1126 try
1127 {
1128 new Flattener3d.MaxDeviation(badAmount);
1129 fail("deviation tolerance <= 0 should have thrown an IllegalArgumentException");
1130 }
1131 catch (IllegalArgumentException e)
1132 {
1133
1134 }
1135
1136 }
1137 try
1138 {
1139 new Flattener2d.MaxAngle(Double.NaN);
1140 fail("angle tolerance NaN should have thrown an ArithmeticException");
1141 }
1142 catch (ArithmeticException e)
1143 {
1144
1145 }
1146
1147 try
1148 {
1149 new OffsetFlattener2d.MaxAngle(Double.NaN);
1150 fail("angle tolerance NaN should have thrown an ArithmeticException");
1151 }
1152 catch (ArithmeticException e)
1153 {
1154
1155 }
1156
1157 try
1158 {
1159 new Flattener3d.MaxAngle(Double.NaN);
1160 fail("angle tolerance NaN should have thrown an ArithmeticException");
1161 }
1162 catch (ArithmeticException e)
1163 {
1164
1165 }
1166
1167 try
1168 {
1169 new Flattener2d.MaxDeviationAndAngle(1.0, Double.NaN);
1170 fail("angle tolerance NaN should have thrown an ArithmeticException");
1171 }
1172 catch (ArithmeticException e)
1173 {
1174
1175 }
1176
1177 try
1178 {
1179 new OffsetFlattener2d.MaxDeviationAndAngle(1.0, Double.NaN);
1180 fail("angle tolerance NaN should have thrown an ArithmeticException");
1181 }
1182 catch (ArithmeticException e)
1183 {
1184
1185 }
1186
1187 try
1188 {
1189 new Flattener3d.MaxDeviationAndAngle(1.0, Double.NaN);
1190 fail("angle tolerance NaN should have thrown an ArithmeticException");
1191 }
1192 catch (ArithmeticException e)
1193 {
1194
1195 }
1196
1197 try
1198 {
1199 new Flattener2d.MaxDeviationAndAngle(Double.NaN, 0.1);
1200 fail("angle tolerance NaN should have thrown an ArithmeticException");
1201 }
1202 catch (ArithmeticException e)
1203 {
1204
1205 }
1206
1207 try
1208 {
1209 new OffsetFlattener2d.MaxDeviationAndAngle(Double.NaN, 0.1);
1210 fail("angle tolerance NaN should have thrown an ArithmeticException");
1211 }
1212 catch (ArithmeticException e)
1213 {
1214
1215 }
1216
1217 try
1218 {
1219 new Flattener3d.MaxDeviationAndAngle(Double.NaN, 0.1);
1220 fail("angle tolerance NaN should have thrown an ArithmeticException");
1221 }
1222 catch (ArithmeticException e)
1223 {
1224
1225 }
1226
1227 try
1228 {
1229 new Flattener2d.MaxDeviation(Double.NaN);
1230 fail("deviation tolerance NaN should have thrown an ArithmeticException");
1231 }
1232 catch (ArithmeticException e)
1233 {
1234
1235 }
1236
1237 try
1238 {
1239 new OffsetFlattener2d.MaxDeviation(Double.NaN);
1240 fail("deviation tolerance NaN should have thrown an ArithmeticException");
1241 }
1242 catch (ArithmeticException e)
1243 {
1244
1245 }
1246
1247 try
1248 {
1249 new Flattener3d.MaxDeviation(Double.NaN);
1250 fail("deviation tolerance NaN should have thrown an ArithmeticException");
1251 }
1252 catch (ArithmeticException e)
1253 {
1254
1255 }
1256
1257 try
1258 {
1259 new Bezier3d(new double[] {}, new double[] {}, new double[] {});
1260 fail("No points for a Bezier3d should have thrown an IllegalArgumentException");
1261 }
1262 catch (IllegalArgumentException e)
1263 {
1264
1265 }
1266
1267 try
1268 {
1269 new Bezier3d(new double[] {1, 2, 3}, new double[] {2, 3, 4}, new double[] {3, 4});
1270 fail("Non equal length arrays for a Bezier3d should have thrown an IllegalArgumentException");
1271 }
1272 catch (IllegalArgumentException e)
1273 {
1274
1275 }
1276
1277 try
1278 {
1279 new Bezier3d(new double[] {1, 2, 3}, new double[] {2, 3}, new double[] {3, 4, 5});
1280 fail("Non equal length arrays for a Bezier3d should have thrown an IllegalArgumentException");
1281 }
1282 catch (IllegalArgumentException e)
1283 {
1284
1285 }
1286 }
1287
1288
1289
1290
1291 @Test
1292 public void testBezier2dConstructors()
1293 {
1294 try
1295 {
1296 new Bezier2d(new double[] {1, 2}, new double[] {2, 3, 4});
1297 fail("Non equal length arrays for a Bezier2d should have thrown an IllegalArgumentException");
1298 }
1299 catch (IllegalArgumentException e)
1300 {
1301
1302 }
1303
1304 try
1305 {
1306 new Bezier2d(new Point2d(1, 2));
1307 fail("Too short array of Point2s for a Bezier2d should have thrown an IllegalArgumentException");
1308 }
1309 catch (IllegalArgumentException e)
1310 {
1311
1312 }
1313
1314 try
1315 {
1316 new Bezier2d(new double[] {1}, new double[] {2});
1317 fail("Too short arrays for a Bezier2d should have thrown an IllegalArgumentException");
1318 }
1319 catch (IllegalArgumentException e)
1320 {
1321
1322 }
1323
1324 new Bezier2d(new double[] {1, 2}, new double[] {2, 3});
1325
1326 Bezier2d b2d = new Bezier2d(new Point2d(1, 2), new Point2d(12, 13));
1327 assertEquals(2, b2d.size(), "Size is reported");
1328 assertEquals(1, b2d.getX(0), "x[0]");
1329 assertEquals(12, b2d.getX(1), "x[1]");
1330 assertEquals(2, b2d.getY(0), "y[0]");
1331 assertEquals(13, b2d.getY(1), "y[1]");
1332 assertEquals(Math.sqrt(2 * 11 * 11), b2d.getLength(), 0.00001, "Length is reported");
1333 assertEquals(Math.sqrt(2 * 11 * 11), b2d.getLength(), 0.00001, "Length is reported from the cache");
1334 assertTrue(b2d.toString().startsWith("Bezier2d ["), "toString returns something descriptive");
1335 assertEquals(Math.sqrt(11 * 11 + 11 * 11), b2d.getLength(), 0.0001, "Length of 2-point (degenerate) Bezier");
1336 Bezier2d derivative = b2d.derivative();
1337 assertEquals(0, derivative.getLength(), 0.0, "Length of 1st derivative");
1338 Bezier2d derivative2 = derivative.derivative();
1339 assertEquals(0, derivative2.getLength(), 0.0, "Length of 2nd derivative");
1340 Bezier2d derivative3 = derivative2.derivative();
1341 assertEquals(derivative2, derivative3, "No more change");
1342
1343 assertTrue(b2d.equals(b2d));
1344 assertFalse(b2d.equals(null));
1345 assertFalse(b2d.equals("not a bezier"));
1346 assertFalse(b2d.equals(new Bezier2d(new Point2d(1, 2), new Point2d(12, 14))));
1347 assertFalse(b2d.equals(new Bezier2d(new Point2d(3, 2), new Point2d(12, 13))));
1348 assertTrue(b2d.equals(new Bezier2d(new Point2d(1, 2), new Point2d(12, 13))));
1349 assertNotEquals(b2d.hashCode(), new Bezier2d(new Point2d(1, 2), new Point2d(12, 14)).hashCode());
1350 assertNotEquals(b2d.hashCode(), new Bezier2d(new Point2d(3, 2), new Point2d(12, 13)).hashCode());
1351 }
1352
1353
1354
1355
1356 @Test
1357 public void testBezier3dConstructors()
1358 {
1359 try
1360 {
1361 new Bezier3d(new double[] {1, 2}, new double[] {2, 3, 4}, new double[] {3, 4, 5});
1362 fail("Non equal length arrays for a Bezier3d should have thrown an IllegalArgumentException");
1363 }
1364 catch (IllegalArgumentException e)
1365 {
1366
1367 }
1368
1369 Bezier3d b3d = new Bezier3d(new Point3d(1, 2, 3), new Point3d(12, 13, 14));
1370 assertEquals(2, b3d.size(), "Size is reported");
1371 assertEquals(1, b3d.getX(0), "x[0]");
1372 assertEquals(12, b3d.getX(1), "x[1]");
1373 assertEquals(2, b3d.getY(0), "y[0]");
1374 assertEquals(13, b3d.getY(1), "y[1]");
1375 assertEquals(3, b3d.getZ(0), "z[0]");
1376 assertEquals(14, b3d.getZ(1), "z[1]");
1377 assertEquals(Math.sqrt(3 * 11 * 11), b3d.getLength(), 0.00001, "Length is reported");
1378 assertEquals(Math.sqrt(3 * 11 * 11), b3d.getLength(), 0.00001, "Length is reported from the cache");
1379 assertTrue(b3d.toString().startsWith("Bezier3d ["), "toString returns something descriptive");
1380 assertEquals(Math.sqrt(11 * 11 + 11 * 11 + 11 * 11), b3d.getLength(), 0.0001, "Length of 2-point (degenerate) Bezier");
1381 Bezier3d derivative = b3d.derivative();
1382 assertEquals(0, derivative.getLength(), 0.0, "Length of 1st derivative");
1383 Bezier3d derivative2 = derivative.derivative();
1384 assertEquals(0, derivative2.getLength(), 0.0, "Length of 2nd derivative");
1385 Bezier3d derivative3 = derivative2.derivative();
1386 assertEquals(derivative2, derivative3, "No more change");
1387
1388 assertTrue(b3d.equals(b3d));
1389 assertFalse(b3d.equals(null));
1390 assertFalse(b3d.equals("not a bezier"));
1391 assertFalse(b3d.equals(new Bezier3d(new Point3d(1, 2, 3), new Point3d(12, 14, 14))));
1392 assertFalse(b3d.equals(new Bezier3d(new Point3d(3, 2, 3), new Point3d(12, 13, 14))));
1393 assertFalse(b3d.equals(new Bezier3d(new Point3d(1, 2, 5), new Point3d(12, 13, 14))));
1394 assertTrue(b3d.equals(new Bezier3d(new Point3d(1, 2, 3), new Point3d(12, 13, 14))));
1395 assertNotEquals(b3d.hashCode(), new Bezier3d(new Point3d(1, 2, 3), new Point3d(12, 14, 14)).hashCode());
1396 assertNotEquals(b3d.hashCode(), new Bezier3d(new Point3d(3, 2, 3), new Point3d(12, 13, 14)).hashCode());
1397 assertNotEquals(b3d.hashCode(), new Bezier3d(new Point3d(1, 2, 5), new Point3d(12, 13, 14)).hashCode());
1398 }
1399
1400 }