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