1 package org.djutils.draw.point;
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.assertNotNull;
7 import static org.junit.jupiter.api.Assertions.assertNull;
8 import static org.junit.jupiter.api.Assertions.assertTrue;
9 import static org.junit.jupiter.api.Assertions.fail;
10
11 import java.awt.geom.Point2D;
12
13 import org.djutils.draw.DrawRuntimeException;
14 import org.djutils.draw.bounds.Bounds3d;
15 import org.djutils.draw.line.LineSegment3d;
16 import org.djutils.draw.line.PolyLine3d;
17 import org.djutils.exceptions.Try;
18 import org.junit.jupiter.api.Test;
19
20
21
22
23
24
25
26
27
28
29
30 public class Point3dTest
31 {
32
33
34
35 @SuppressWarnings("unlikely-arg-type")
36 @Test
37 public void testPoint3dConstruction()
38 {
39 Point3d p = new Point3d(10.0, -20.0, 16.0);
40 assertNotNull(p);
41 assertEquals(10.0, p.x, 0, "Access x");
42 assertEquals(-20.0, p.y, 0, "Access y");
43 assertEquals(16.0, p.z, 0, "Access z");
44 assertEquals(3, p.getDimensions(), "Dimensions is 3");
45
46 assertEquals(1, p.size(), "Size method returns 1");
47
48 Point2d projection = p.project();
49 assertEquals(10.0, projection.x, 0);
50 assertEquals(-20.0, projection.y, 0);
51
52 try
53 {
54 new Point3d(Double.NaN, 0, 0);
55 fail("NaN should have thrown an ArithmeticException");
56 }
57 catch (ArithmeticException e)
58 {
59
60 }
61
62 try
63 {
64 new Point3d(0, Double.NaN, 0);
65 fail("NaN should have thrown an ArithmeticException");
66 }
67 catch (ArithmeticException e)
68 {
69
70 }
71
72 try
73 {
74 new Point3d(0, 0, Double.NaN);
75 fail("NaN should have thrown an ArithmeticException");
76 }
77 catch (ArithmeticException e)
78 {
79
80 }
81
82 double[] p3Arr = new double[] {5.0, 6.0, 7.0};
83 p = new Point3d(p3Arr);
84 assertEquals(5.0, p.x, 0);
85 assertEquals(6.0, p.y, 0);
86 assertEquals(7.0, p.z, 0);
87 Try.testFail(new Try.Execution()
88 {
89 @Override
90 public void execute() throws Throwable
91 {
92 new Point3d(new double[] {});
93 }
94 }, "Should throw IAE", IllegalArgumentException.class);
95
96 Try.testFail(new Try.Execution()
97 {
98 @Override
99 public void execute() throws Throwable
100 {
101 new Point3d(new double[] {1.0});
102 }
103 }, "Should throw IAE", IllegalArgumentException.class);
104
105 Try.testFail(new Try.Execution()
106 {
107 @Override
108 public void execute() throws Throwable
109 {
110 new Point3d(new double[] {1.0, 2.0});
111 }
112 }, "Should throw IAE", IllegalArgumentException.class);
113
114 Try.testFail(new Try.Execution()
115 {
116 @Override
117 public void execute() throws Throwable
118 {
119 new Point3d(new double[] {1.0, 2.0, 3.0, 4.0});
120 }
121 }, "Should throw IAE", IllegalArgumentException.class);
122
123 Try.testFail(new Try.Execution()
124 {
125 @Override
126 public void execute() throws Throwable
127 {
128 new Point3d((Point2d) null, 0);
129 }
130 }, "Should throw NPE", NullPointerException.class);
131
132 Try.testFail(new Try.Execution()
133 {
134 @Override
135 public void execute() throws Throwable
136 {
137 new Point3d((Point2D.Double) null, 0);
138 }
139 }, "Should throw NPE", NullPointerException.class);
140
141 Try.testFail(new Try.Execution()
142 {
143 @Override
144 public void execute() throws Throwable
145 {
146 new Point3d(new Point2D.Double(Double.NaN, 2), 0);
147 }
148 }, "Should throw ArithmeticException", ArithmeticException.class);
149
150 Try.testFail(new Try.Execution()
151 {
152 @Override
153 public void execute() throws Throwable
154 {
155 new Point3d(new Point2D.Double(1, Double.NaN), 0);
156 }
157 }, "Should throw ArithmeticException", ArithmeticException.class);
158
159
160 assertTrue(p.equals(p));
161 assertEquals(p.hashCode(), p.hashCode());
162 Point2d p2d = new Point2d(1.0, 1.0);
163 assertFalse(p.equals(p2d));
164 assertFalse(p.equals(null));
165 assertNotEquals(p2d.hashCode(), p.hashCode());
166 assertEquals(p, p.translate(0.0, 0.0, 0.0), "Translating over 0,0,0 returns p");
167 assertNotEquals(p, p.translate(1.0, 0.0, 0.0));
168 assertNotEquals(p, p.translate(0.0, 1.0, 0.0));
169 assertNotEquals(p, p.translate(0.0, 0.0, 1.0));
170
171
172 p = new Point3d(10.0, 20.0, 30.0);
173 assertEquals("Point3d [x=10.000000, y=20.000000, z=30.000000]", p.toString());
174 assertEquals("Point3d [x=10.0, y=20.0, z=30.0]", p.toString("%.1f"));
175 assertEquals("[x=10, y=20, z=30]", p.toString("%.0f", true));
176
177
178 assertTrue(p.epsilonEquals(p, 0.1));
179 assertTrue(p.epsilonEquals(p, 0.001));
180 assertTrue(p.epsilonEquals(p, 0.0));
181 Point3d p3 = p.translate(0.001, 0.0, 0.0);
182 assertTrue(p.epsilonEquals(p3, 0.09));
183 assertTrue(p3.epsilonEquals(p, 0.09));
184 assertFalse(p.epsilonEquals(p3, 0.0009));
185 assertFalse(p3.epsilonEquals(p, 0.0009));
186 p3 = p.translate(0.0, 0.001, 0.0);
187 assertTrue(p.epsilonEquals(p3, 0.09));
188 assertTrue(p3.epsilonEquals(p, 0.09));
189 assertFalse(p.epsilonEquals(p3, 0.0009));
190 assertFalse(p3.epsilonEquals(p, 0.0009));
191 p3 = p.translate(0.0, 0.0, 0.001);
192 assertTrue(p.epsilonEquals(p3, 0.09));
193 assertTrue(p3.epsilonEquals(p, 0.09));
194 assertFalse(p.epsilonEquals(p3, 0.0009));
195 assertFalse(p3.epsilonEquals(p, 0.0009));
196
197 p2d = new Point2d(123, 456);
198 p3 = new Point3d(p2d, 789);
199 assertEquals(123, p3.x, 0, "x");
200 assertEquals(456, p3.y, 0, "y");
201 assertEquals(789, p3.z, 0, "z");
202
203 Point2D p2D = new java.awt.geom.Point2D.Double(123, 456);
204 p3 = new Point3d(p2D, 789);
205 assertEquals(123, p3.x, 0, "x");
206 assertEquals(456, p3.y, 0, "y");
207 assertEquals(789, p3.z, 0, "z");
208 }
209
210
211
212
213 @Test
214 public void testPoint3dOperators()
215 {
216 Point3d p = new Point3d(-0.1, -0.2, -0.3);
217 assertEquals(0.1, p.abs().x, 1E-6);
218 assertEquals(0.2, p.abs().y, 1E-6);
219 assertEquals(0.3, p.abs().z, 1E-6);
220 p = p.neg();
221 assertEquals(0.1, p.x, 1E-6);
222 assertEquals(0.2, p.y, 1E-6);
223 assertEquals(0.3, p.z, 1E-6);
224 p = p.scale(1.0);
225 assertEquals(0.1, p.x, 1E-6);
226 assertEquals(0.2, p.y, 1E-6);
227 assertEquals(0.3, p.z, 1E-6);
228 p = p.scale(10.0);
229 assertEquals(1.0, p.x, 1E-6);
230 assertEquals(2.0, p.y, 1E-6);
231 assertEquals(3.0, p.z, 1E-6);
232 p = p.translate(5.0, -1.0, 0.5);
233 assertEquals(6.0, p.x, 1E-6);
234 assertEquals(1.0, p.y, 1E-6);
235 assertEquals(3.5, p.z, 1E-6);
236 Point3d p3d = p.translate(1.0, 1.0, 1.0);
237 assertEquals(7.0, p3d.x, 1E-6);
238 assertEquals(2.0, p3d.y, 1E-6);
239 assertEquals(4.5, p3d.z, 1E-6);
240 p3d = p.translate(6.0, 1.0);
241 assertEquals(12.0, p3d.x, 1E-6);
242 assertEquals(2.0, p3d.y, 1E-6);
243 assertEquals(3.5, p3d.z, 1E-6);
244
245 try
246 {
247 p.translate(Double.NaN, 2.0);
248 fail("NaN translation should have thrown an ArithmeticException");
249 }
250 catch (ArithmeticException e)
251 {
252
253 }
254
255 try
256 {
257 p.translate(1.0, Double.NaN);
258 fail("NaN translation should have thrown an ArithmeticException");
259 }
260 catch (ArithmeticException e)
261 {
262
263 }
264
265 try
266 {
267 p.translate(Double.NaN, 2.0, 3.0);
268 fail("NaN translation should have thrown an ArithmeticException");
269 }
270 catch (ArithmeticException e)
271 {
272
273 }
274
275 try
276 {
277 p.translate(1.0, Double.NaN, 3.0);
278 fail("NaN translation should have thrown an ArithmeticException");
279 }
280 catch (ArithmeticException e)
281 {
282
283 }
284
285 try
286 {
287 p.translate(1.0, 2.0, Double.NaN);
288 fail("NaN translation should have thrown an ArithmeticException");
289 }
290 catch (ArithmeticException e)
291 {
292
293 }
294
295
296 Point3d p1 = new Point3d(1.0, 1.0, 1.0);
297 Point3d p2 = new Point3d(5.0, 5.0, 5.0);
298 assertEquals(p1, p1.interpolate(p2, 0.0), "Interpolate at 0.0 returns this");
299 assertEquals(p2, p2.interpolate(p1, 0.0));
300 assertEquals(p1, p1.interpolate(p1, 0.0));
301 assertEquals(new Point3d(3.0, 3.0, 3.0), p1.interpolate(p2, 0.5));
302
303
304 assertEquals(Math.sqrt(48.0), p1.distance(p2), 0.001);
305 assertEquals(48.0, p1.distanceSquared(p2), 0.001);
306 assertEquals(Math.sqrt(32.0), p1.horizontalDistance(p2), 0.001);
307 assertEquals(32.0, p1.horizontalDistanceSquared(p2), 0.001);
308
309
310 assertEquals(Math.toRadians(45.0), p2.horizontalDirection(), 0.001);
311 assertEquals(Math.toRadians(45.0), p1.horizontalDirection(p2), 0.001);
312 assertEquals(0.0, new Point3d(0.0, 0.0, 0.0).horizontalDirection(), 0.001);
313 assertEquals(Math.atan2(Math.sqrt(2.0), 1), p1.verticalDirection(p2), 0.001);
314 assertEquals(Math.PI / 2, p1.verticalDirection(new Point3d(2.0, 2.0, 1.0)), 0.01);
315 assertEquals(0, p1.verticalDirection(new Point3d(1.0, 1.0, 2.0)), 0.01);
316
317
318 Point3d pn = p2.normalize();
319 assertEquals(1.0 / Math.sqrt(3.0), pn.x, 0.001);
320 assertEquals(1.0 / Math.sqrt(3.0), pn.y, 0.001);
321 assertEquals(1.0 / Math.sqrt(3.0), pn.z, 0.001);
322
323 Try.testFail(new Try.Execution()
324 {
325 @Override
326 public void execute() throws Throwable
327 {
328 new Point3d(0.0, 0.0, 0.0).normalize();
329 }
330 }, "Should throw DRtE", DrawRuntimeException.class);
331
332 assertEquals(1, p1.size(), "size of a Point3d is 1");
333 Point2d projection = p1.project();
334 assertEquals(p1.x, projection.x, 0, "projected x");
335 assertEquals(p1.y, projection.y, 0, "projected y");
336
337 Bounds3d bounds = p1.getBounds();
338 assertEquals(p1.x, bounds.getMinX(), 0, "Bounds min x");
339 assertEquals(p1.y, bounds.getMinY(), 0, "Bounds min y");
340 assertEquals(p1.z, bounds.getMinZ(), 0, "Bounds min z");
341 assertEquals(p1.x, bounds.getMaxX(), 0, "Bounds max x");
342 assertEquals(p1.y, bounds.getMaxY(), 0, "Bounds max y");
343 assertEquals(p1.z, bounds.getMaxZ(), 0, "Bounds max z");
344 }
345
346
347
348
349 @Test
350 public void testPoint3dOperatorsNPE()
351 {
352 final Point3d p1 = new Point3d(1.0, 1.0, 1.0);
353
354 Try.testFail(new Try.Execution()
355 {
356 @Override
357 public void execute() throws Throwable
358 {
359 p1.interpolate(null, 0.5);
360 }
361 }, "Should throw NPE", NullPointerException.class);
362
363 Try.testFail(new Try.Execution()
364 {
365 @Override
366 public void execute() throws Throwable
367 {
368 p1.distance(null);
369 }
370 }, "Should throw NPE", NullPointerException.class);
371
372 Try.testFail(new Try.Execution()
373 {
374 @Override
375 public void execute() throws Throwable
376 {
377 p1.distanceSquared(null);
378 }
379 }, "Should throw NPE", NullPointerException.class);
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400 }
401
402
403
404
405
406 @Test
407 public void testClosestPointOnSegmentAndLine() throws DrawRuntimeException
408 {
409 Point3d p1 = new Point3d(-2, 3, 5);
410 for (Point3d p2 : new Point3d[] {new Point3d(7, 4, -5), new Point3d(-3, 6, 5) ,
411 new Point3d(-2, -5, 5) , new Point3d(8, 3, 5), new Point3d(-2, 3, 1) })
412 {
413 PolyLine3d line = new PolyLine3d(p1, p2);
414 for (double x = -10; x <= 10; x += 0.5)
415 {
416 for (double y = -10; y <= 10; y += 0.5)
417 {
418 for (double z = -10; z <= 10; z += 0.5)
419 {
420 Point3d p = new Point3d(x, y, z);
421
422 double fraction = 0.5;
423 double step = 0.25;
424 Point3d approximation = line.getLocationFraction(fraction);
425 double distance = approximation.distance(p);
426
427 for (int iteration = 0; iteration < 10; iteration++)
428 {
429
430 double upFraction = fraction + step;
431 Point3d upApproximation = line.getLocationFraction(upFraction);
432 double upDistance = upApproximation.distance(p);
433 if (upDistance < distance)
434 {
435 distance = upDistance;
436 fraction = upFraction;
437 approximation = upApproximation;
438 }
439 else
440 {
441
442 double downFraction = fraction - step;
443 Point3d downApproximation = line.getLocationFraction(downFraction);
444 double downDistance = downApproximation.distance(p);
445 if (downDistance < distance)
446 {
447 distance = downDistance;
448 fraction = downFraction;
449 approximation = downApproximation;
450 }
451 }
452 step /= 2;
453 }
454 Point3d result = p.closestPointOnSegment(p1, p2);
455 assertEquals(0, approximation.distance(result), line.getLength() / 1000,
456 "distance should be less than one thousandth of line length");
457 assertEquals(p1, p.closestPointOnSegment(p1, p1),
458 "zero length line segment should always return start point");
459 result = p.closestPointOnSegment(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
460 assertEquals(0, approximation.distance(result), line.getLength() / 1000,
461 "distance should be less than one thousandth of line length");
462
463 if (fraction > 0.001 && fraction < 0.999)
464 {
465 result = p.closestPointOnLine(p1, p2);
466 assertEquals(0, approximation.distance(result), line.getLength() / 1000,
467 "distance should be less than one thousandth of line length");
468 result = p.closestPointOnLine(p1, p2);
469 assertEquals(0, approximation.distance(result), line.getLength() / 1000,
470 "distance should be less than one thousandth of line length");
471 result = p.closestPointOnLine(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
472 assertEquals(0, approximation.distance(result), line.getLength() / 1000,
473 "distance should be less than one thousandth of line length");
474 }
475 else
476 {
477
478 double range = Math.max(Math.max(line.getLength(), p.distance(p1)), p.distance(p2));
479 step = 5.0;
480 fraction = 0.5;
481 distance = range;
482
483 for (int iteration = 0; iteration < 20; iteration++)
484 {
485
486 double upFraction = fraction + step;
487 Point3d upApproximation = line.getLocationFractionExtended(upFraction);
488 double upDistance = upApproximation.distance(p);
489 if (upDistance < distance)
490 {
491 distance = upDistance;
492 fraction = upFraction;
493 approximation = upApproximation;
494 }
495 else
496 {
497
498 double downFraction = fraction - step;
499 Point3d downApproximation = line.getLocationFractionExtended(downFraction);
500 double downDistance = downApproximation.distance(p);
501 if (downDistance < distance)
502 {
503 distance = downDistance;
504 fraction = downFraction;
505 approximation = downApproximation;
506 }
507 }
508 step /= 2;
509 }
510 result = p.closestPointOnLine(p1, p2);
511 assertEquals(0, approximation.distance(result), range / 1000,
512 "distance should be less than one thousandth of range");
513 result = p.closestPointOnLine(p1, p2);
514 assertEquals(0, approximation.distance(result), range / 1000,
515 "distance should be less than one thousandth of range");
516 result = p.closestPointOnLine(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
517 assertEquals(0, approximation.distance(result), range / 1000,
518 "distance should be less than one thousandth of range");
519 if (fraction < -0.001 || fraction > 1.001)
520 {
521 assertNull(new LineSegment3d(p1, p2).projectOrthogonal(p),
522 "projectOrthogonal should return null");
523 assertEquals(result, new LineSegment3d(p1, p2).projectOrthogonalExtended(p),
524 "projectOrthogonalExtended should return same result as closestPointOnLine");
525 }
526 }
527 }
528 }
529 }
530 }
531
532 try
533 {
534 p1.closestPointOnLine(null, new Point3d(5, 6, 7));
535 fail("null should have thrown a NullPointerException");
536 }
537 catch (NullPointerException e)
538 {
539
540 }
541
542 try
543 {
544 p1.closestPointOnLine(new Point3d(5, 6, 7), null);
545 fail("null should have thrown a NullPointerException");
546 }
547 catch (NullPointerException e)
548 {
549
550 }
551
552 try
553 {
554 p1.closestPointOnSegment(Double.NaN, 7, 8, 9, 10, 11);
555 fail("NaN value should have thrown an ArithmeticException");
556 }
557 catch (ArithmeticException e)
558 {
559
560 }
561
562 try
563 {
564 p1.closestPointOnSegment(6, Double.NaN, 8, 9, 10, 11);
565 fail("NaN value should have thrown an ArithmeticException");
566 }
567 catch (ArithmeticException e)
568 {
569
570 }
571
572 try
573 {
574 p1.closestPointOnSegment(6, 7, Double.NaN, 9, 10, 11);
575 fail("NaN value should have thrown an ArithmeticException");
576 }
577 catch (ArithmeticException e)
578 {
579
580 }
581
582 try
583 {
584 p1.closestPointOnSegment(6, 7, 8, Double.NaN, 10, 11);
585 fail("NaN value should have thrown an ArithmeticException");
586 }
587 catch (ArithmeticException e)
588 {
589
590 }
591
592 try
593 {
594 p1.closestPointOnSegment(6, 7, 8, 9, Double.NaN, 11);
595 fail("NaN value should have thrown an ArithmeticException");
596 }
597 catch (ArithmeticException e)
598 {
599
600 }
601
602 try
603 {
604 p1.closestPointOnSegment(6, 7, 8, 9, 10, Double.NaN);
605 fail("NaN value should have thrown an ArithmeticException");
606 }
607 catch (ArithmeticException e)
608 {
609
610 }
611
612 try
613 {
614 p1.closestPointOnLine(Double.NaN, 7, 8, 9, 10, 11);
615 fail("NaN value should have thrown an ArithmeticException");
616 }
617 catch (ArithmeticException e)
618 {
619
620 }
621
622 try
623 {
624 p1.closestPointOnLine(6, Double.NaN, 8, 9, 10, 11);
625 fail("NaN value should have thrown an ArithmeticException");
626 }
627 catch (ArithmeticException dere)
628 {
629
630 }
631
632 try
633 {
634 p1.closestPointOnLine(6, 7, Double.NaN, 9, 10, 11);
635 fail("NaN value should have thrown an ArithmeticException");
636 }
637 catch (ArithmeticException e)
638 {
639
640 }
641
642 try
643 {
644 p1.closestPointOnLine(6, 7, 8, Double.NaN, 10, 11);
645 fail("NaN value should have thrown an ArithmeticException");
646 }
647 catch (ArithmeticException e)
648 {
649
650 }
651
652 try
653 {
654 p1.closestPointOnLine(6, 7, 8, 9, Double.NaN, 11);
655 fail("NaN value should have thrown an ArithmeticException");
656 }
657 catch (ArithmeticException e)
658 {
659
660 }
661
662 try
663 {
664 p1.closestPointOnLine(6, 7, 8, 9, 10, Double.NaN);
665 fail("NaN value should have thrown an ArithmeticException");
666 }
667 catch (ArithmeticException e)
668 {
669
670 }
671
672 try
673 {
674 p1.closestPointOnLine(6, 7, 8, 6, 7, 8);
675 fail("identical points should have thrown a DrawRuntimeException");
676 }
677 catch (DrawRuntimeException dre)
678 {
679
680 }
681
682 }
683
684 }