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