1 package org.djutils.draw.line;
2
3 import static org.junit.Assert.assertEquals;
4 import static org.junit.Assert.assertFalse;
5 import static org.junit.Assert.assertNotEquals;
6 import static org.junit.Assert.assertNull;
7 import static org.junit.Assert.assertTrue;
8 import static org.junit.Assert.fail;
9
10 import java.util.Iterator;
11 import java.util.NoSuchElementException;
12
13 import org.djutils.base.AngleUtil;
14 import org.djutils.draw.DrawRuntimeException;
15 import org.djutils.draw.bounds.Bounds2d;
16 import org.djutils.draw.point.OrientedPoint2d;
17 import org.djutils.draw.point.Point2d;
18 import org.junit.Test;
19
20
21
22
23
24
25
26
27
28
29 public class Ray2dTest
30 {
31
32
33
34 @Test
35 public void testConstructors()
36 {
37 verifyRay("Constructor from x, y, phi", new Ray2d(1, 2, 3), 1, 2, 3);
38 verifyRay("Constructor from Point2d, phi", new Ray2d(new Point2d(0.1, 0.2), -0.3), 0.1, 0.2, -0.3);
39 verifyRay("Constructor from x, y, throughX, throughY", new Ray2d(1, 2, 3, 5), 1, 2, Math.atan2(3, 2));
40 verifyRay("Constructor from x, y, throughX, throughY", new Ray2d(1, 2, 1, 5), 1, 2, Math.atan2(3, 0));
41 verifyRay("Constructor from x, y, throughX, throughY", new Ray2d(1, 2, 3, 2), 1, 2, Math.atan2(0, 2));
42 verifyRay("Constructor from Point2d, throughX, throughY", new Ray2d(new Point2d(1, 2), 3, 5), 1, 2, Math.atan2(3, 2));
43 verifyRay("Constructor from Point2d, throughX, throughY", new Ray2d(new Point2d(1, 2), 1, 5), 1, 2, Math.atan2(3, 0));
44 verifyRay("Constructor from Point2d, throughX, throughY", new Ray2d(new Point2d(1, 2), 3, 2), 1, 2, Math.atan2(0, 2));
45 verifyRay("Constructor from x, y, Point2d", new Ray2d(1, 2, new Point2d(3, 5)), 1, 2, Math.atan2(3, 2));
46 verifyRay("Constructor from x, y, Point2d", new Ray2d(1, 2, new Point2d(1, 5)), 1, 2, Math.atan2(3, 0));
47 verifyRay("Constructor from x, y, Point2d", new Ray2d(1, 2, new Point2d(3, 2)), 1, 2, Math.atan2(0, 2));
48 verifyRay("Constructor from Point2d, Point2d", new Ray2d(new Point2d(1, 2), new Point2d(3, 5)), 1, 2, Math.atan2(3, 2));
49 verifyRay("Constructor from Point2d, Point2d", new Ray2d(new Point2d(1, 2), new Point2d(1, 5)), 1, 2, Math.atan2(3, 0));
50 verifyRay("Constructor from Point2d, Point2d", new Ray2d(new Point2d(1, 2), new Point2d(3, 2)), 1, 2, Math.atan2(0, 2));
51
52 try
53 {
54 new Ray2d(1, 2, Double.NaN);
55 fail("NaN for phy should have thrown a DrawRuntimeException");
56 }
57 catch (DrawRuntimeException dre)
58 {
59
60 }
61
62 try
63 {
64 new Ray2d(null, 1);
65 fail("null for point should have thrown a NullPointerException");
66 }
67 catch (NullPointerException dre)
68 {
69
70 }
71
72 try
73 {
74 new Ray2d(1, 2, 1, 2);
75 fail("Same coordinates for through point should have thrown a DrawRuntimeException");
76 }
77 catch (DrawRuntimeException dre)
78 {
79
80 }
81
82 try
83 {
84 new Ray2d(1, 2, new Point2d(1, 2));
85 fail("Same coordinates for through point should have thrown a DrawRuntimeException");
86 }
87 catch (DrawRuntimeException dre)
88 {
89
90 }
91
92 try
93 {
94 new Ray2d(new Point2d(1, 2), 1, 2);
95 fail("Same coordinates for through point should have thrown a DrawRuntimeException");
96 }
97 catch (DrawRuntimeException dre)
98 {
99
100 }
101
102 try
103 {
104 new Ray2d(1, 2, null);
105 fail("null for through point should have thrown a NullPointerException");
106 }
107 catch (NullPointerException dre)
108 {
109
110 }
111
112 try
113 {
114 new Ray2d(null, new Point2d(3, 4));
115 fail("null for point should have thrown a NullPointerException");
116 }
117 catch (NullPointerException dre)
118 {
119
120 }
121
122 try
123 {
124 new Ray2d(new Point2d(1, 2), null);
125 fail("null for through point should have thrown a NullPointerException");
126 }
127 catch (NullPointerException dre)
128 {
129
130 }
131
132 Ray2d ray = new Ray2d(1, 2, 3);
133 assertTrue("toString returns something descriptive", ray.toString().startsWith("Ray2d"));
134 assertTrue("toString can suppress the class name", ray.toString().indexOf(ray.toString(true)) > 0);
135 }
136
137
138
139
140
141
142
143
144
145 private void verifyRay(final String description, final Ray2d ray, final double expectedX, final double expectedY,
146 final double expectedPhi)
147 {
148 assertEquals(description + " getX", expectedX, ray.getX(), 0.0001);
149 assertEquals(description + " x", expectedX, ray.x, 0.0001);
150 assertEquals(description + " getY", expectedY, ray.getY(), 0.0001);
151 assertEquals(description + " y", expectedY, ray.y, 0.0001);
152 assertEquals(description + " getPhi", expectedPhi, ray.getPhi(), 0.0001);
153 assertEquals(description + " phi", expectedPhi, ray.phi, 0.0001);
154 Point2d startPoint = ray.getEndPoint();
155 assertEquals(description + " getStartPoint x", expectedX, startPoint.x, 0.0001);
156 assertEquals(description + " getStartPoint y", expectedY, startPoint.y, 0.0001);
157 Ray2d negated = ray.neg();
158 assertEquals(description + " neg x", -expectedX, negated.x, 0.0001);
159 assertEquals(description + " neg y", -expectedY, negated.y, 0.0001);
160 assertEquals(description + " neg phi", expectedPhi + Math.PI, negated.phi, 0.0001);
161 Ray2d flipped = ray.flip();
162 assertEquals(description + " getX", expectedX, flipped.getX(), 0.0001);
163 assertEquals(description + " x", expectedX, flipped.x, 0.0001);
164 assertEquals(description + " getY", expectedY, flipped.getY(), 0.0001);
165 assertEquals(description + " y", expectedY, flipped.y, 0.0001);
166 assertEquals(description + " getPhi", expectedPhi + Math.PI, flipped.getPhi(), 0.0001);
167 assertEquals(description + " phi", expectedPhi + Math.PI, flipped.phi, 0.0001);
168 assertEquals(description + " size", 2, ray.size());
169 Iterator<Point2d> iterator = ray.getPoints();
170
171 assertTrue(iterator.hasNext());
172 Point2d point = iterator.next();
173 assertEquals(description + " iterator first point x", expectedX, point.x, 0.0001);
174 assertEquals(description + " iterator first point y", expectedY, point.y, 0.0001);
175 assertTrue(iterator.hasNext());
176 point = iterator.next();
177
178 assertTrue(description + " iterator second point is at infinity",
179 Double.isInfinite(point.x) || Double.isInfinite(point.y));
180 assertFalse(iterator.hasNext());
181 try
182 {
183 iterator.next();
184 fail("Should have thrown a NoSuchElementException");
185 }
186 catch (NoSuchElementException nsee)
187 {
188
189 }
190 }
191
192
193
194
195 @Test
196 public void boundsTest()
197 {
198
199
200 verifyBounds(new Ray2d(1, 2, 0).getBounds(), 1, 2, Double.POSITIVE_INFINITY, 2);
201
202
203 verifyBounds(new Ray2d(1, 2, 0.2).getBounds(), 1, 2, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
204
205
206 verifyBounds(new Ray2d(1, 2, Math.PI / 2).getBounds(), 1, 2, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
207
208
209 verifyBounds(new Ray2d(1, 2, 2).getBounds(), Double.NEGATIVE_INFINITY, 2, 1, Double.POSITIVE_INFINITY);
210
211
212 verifyBounds(new Ray2d(1, 2, Math.PI).getBounds(), Double.NEGATIVE_INFINITY, 2, 1, Double.POSITIVE_INFINITY);
213
214
215 verifyBounds(new Ray2d(1, 2, 4).getBounds(), Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1, 2);
216
217
218 verifyBounds(new Ray2d(1, 2, -1).getBounds(), 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 2);
219
220
221 verifyBounds(new Ray2d(1, 2, -Math.PI / 2).getBounds(), 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 2);
222
223 }
224
225
226
227
228
229
230
231
232
233 private void verifyBounds(final Bounds2d bounds, final double expectedMinX, final double expectedMinY,
234 final double expectedMaxX, final double expectedMaxY)
235 {
236 assertEquals("Bounds minX", expectedMinX, bounds.getMinX(), 0.0001);
237 assertEquals("Bounds minY", expectedMinY, bounds.getMinY(), 0.0001);
238 assertEquals("Bounds maxX", expectedMaxX, bounds.getMaxX(), 0.0001);
239 assertEquals("Bounds maxY", expectedMaxY, bounds.getMaxY(), 0.0001);
240 }
241
242
243
244
245 @Test
246 public void testLocation()
247 {
248 try
249 {
250 new Ray2d(1, 2, 1).getLocation(Double.NaN);
251 fail("NaN position should have thrown a DrawRuntimeException");
252 }
253 catch (DrawRuntimeException dre)
254 {
255
256 }
257
258 try
259 {
260 new Ray2d(1, 2, 1).getLocation(-1);
261 fail("Negative position should have thrown a DrawRuntimeException");
262 }
263 catch (DrawRuntimeException dre)
264 {
265
266 }
267
268 try
269 {
270 new Ray2d(1, 2, 1).getLocation(Double.POSITIVE_INFINITY);
271 fail("Infinite position should have thrown a DrawRuntimeException");
272 }
273 catch (DrawRuntimeException dre)
274 {
275
276 }
277
278 try
279 {
280 new Ray2d(1, 2, 1).getLocation(Double.NEGATIVE_INFINITY);
281 fail("Infinite position should have thrown a DrawRuntimeException");
282 }
283 catch (DrawRuntimeException dre)
284 {
285
286 }
287
288 try
289 {
290 new Ray2d(1, 2, 1).getLocationExtended(Double.POSITIVE_INFINITY);
291 fail("Infinite position should have thrown a DrawRuntimeException");
292 }
293 catch (DrawRuntimeException dre)
294 {
295
296 }
297
298 try
299 {
300 new Ray2d(1, 2, 1).getLocationExtended(Double.NEGATIVE_INFINITY);
301 fail("Infinite position should have thrown a DrawRuntimeException");
302 }
303 catch (DrawRuntimeException dre)
304 {
305
306 }
307
308 try
309 {
310 new Ray2d(1, 2, 1).getLocationExtended(Double.NaN);
311 fail("NaN position should have thrown a DrawRuntimeException");
312 }
313 catch (DrawRuntimeException dre)
314 {
315
316 }
317
318 for (double phi : new double[] { 0, 1, 2, 3, 4, 5, -1, -2, Math.PI })
319 {
320 Ray2d ray = new Ray2d(1, 2, phi);
321 for (double position : new double[] { 0, 10, 0.1, -2 })
322 {
323 Ray2d result = ray.getLocationExtended(position);
324 assertEquals("result is position distance away from base of ray", Math.abs(position), ray.distance(result),
325 0.001);
326 assertEquals("result has same phi as ray", ray.phi, result.phi, 0.00001);
327 assertTrue("Reverse position on result yields ray",
328 ray.epsilonEquals(result.getLocationExtended(-position), 0.0001));
329 if (position > 0)
330 {
331 assertEquals("result lies in on ray", AngleUtil.normalizeAroundZero(ray.phi), ray.directionTo(result),
332 0.0001);
333 }
334 if (position < 0)
335 {
336 assertEquals("ray lies on result", AngleUtil.normalizeAroundZero(result.phi), result.directionTo(ray),
337 0.0001);
338 }
339 }
340 }
341 }
342
343
344
345
346 @Test
347 public void testClosestPointAndProjectOrthogonal()
348 {
349 Ray2d ray = new Ray2d(1, 2, 1);
350 try
351 {
352 ray.closestPointOnRay(null);
353 fail("Null for point should have thrown a NullPointerException");
354 }
355 catch (NullPointerException npe)
356 {
357
358 }
359
360 Point2d result = ray.closestPointOnRay(new Point2d(1, 0));
361 assertEquals("result is start point", ray.x, result.x, 0);
362 assertEquals("result is start point", ray.y, result.y, 0);
363 result = ray.closestPointOnRay(new Point2d(0, 2));
364 assertEquals("result is start point", ray.x, result.x, 0);
365 assertEquals("result is start point", ray.y, result.y, 0);
366 result = ray.closestPointOnRay(new Point2d(1, 2));
367 assertEquals("result is start point", ray.x, result.x, 0);
368 assertEquals("result is start point", ray.y, result.y, 0);
369
370 assertNull("projection misses the ray", ray.projectOrthogonal(new Point2d(1, 0)));
371 assertNull("projection misses the ray", ray.projectOrthogonal(new Point2d(0, 2)));
372 assertEquals("projection hits start point of ray", new Point2d(1, 2), ray.projectOrthogonal(new Point2d(1, 2)));
373 assertEquals("extended projection returns same point as projection on sufficiently long line segment", 0,
374 new LineSegment2d(ray.getLocationExtended(-100), ray.getLocation(100)).closestPointOnSegment(new Point2d(1, 0))
375 .distance(ray.projectOrthogonalExtended(new Point2d(1, 0))),
376 0.0001);
377
378 Point2d projectingPoint = new Point2d(10, 10);
379 result = ray.closestPointOnRay(projectingPoint);
380 double distance = result.distance(ray.getEndPoint());
381 assertTrue("distance from start is > 0", distance > 0);
382
383 double angle = ray.getPhi() - result.directionTo(projectingPoint);
384 assertEquals("angle should be about 90 degrees", Math.PI / 2, Math.abs(AngleUtil.normalizeAroundZero(angle)), 0.0001);
385 assertEquals("projection hits closest point on the ray", 0, result.distance(ray.projectOrthogonal(projectingPoint)),
386 0.0001);
387 assertEquals("projectOrthogonalExtended returns same result as long as orthogonal projection exists", 0,
388 result.distance(ray.projectOrthogonalExtended(projectingPoint)), 0.0001);
389 }
390
391
392
393
394 @Test
395 public void testProject()
396 {
397 Ray2d ray = new Ray2d(1, 2, 20, 10);
398 assertTrue("projects outside", Double.isNaN(ray.projectOrthogonalFractional(new Point2d(1, 1))));
399 assertTrue("projects before start", ray.projectOrthogonalFractionalExtended(new Point2d(1, 1)) < 0);
400 assertEquals("projects at", -new Point2d(1 - 19 - 19, 2 - 8 - 8).distance(ray),
401 ray.projectOrthogonalFractionalExtended(new Point2d(1 - 19 - 19 + 8, 2 - 8 - 8 - 19)), 0.0001);
402
403 for (int x = -2; x < 5; x++)
404 {
405 for (int y = -2; y < 5; y++)
406 {
407 Point2d point = new Point2d(x, y);
408 double fraction = ray.projectOrthogonalFractionalExtended(point);
409 if (fraction < 0)
410 {
411 assertTrue("non extended version yields NaN", Double.isNaN(ray.projectOrthogonalFractional(point)));
412 assertNull("non extended projectOrthogonal yields null", ray.projectOrthogonal(point));
413 }
414 else
415 {
416 assertEquals("non extended version yields same", fraction, ray.projectOrthogonalFractional(point), 0.00001);
417 assertEquals("non extended version yields same as extended version", ray.projectOrthogonal(point),
418 ray.projectOrthogonalExtended(point));
419 }
420 Point2d projected = ray.projectOrthogonalExtended(point);
421 assertEquals("projecting projected point yields same", fraction,
422 ray.projectOrthogonalFractionalExtended(projected), 0.00001);
423 }
424 }
425 }
426
427
428
429
430 @Test
431 public void epsilonEqualsTest()
432 {
433 Ray2d ray = new Ray2d(1, 2, -1);
434 try
435 {
436 ray.epsilonEquals((Ray2d) null, 1, 1);
437 fail("Null pointer should have thrown a NullPointerException");
438 }
439 catch (NullPointerException npe)
440 {
441
442 }
443
444 try
445 {
446 ray.epsilonEquals(ray, -0.1, 1);
447 fail("Negative epsilonCoordinate should have thrown an IllegalArgumentException");
448 }
449 catch (IllegalArgumentException npe)
450 {
451
452 }
453
454 try
455 {
456 ray.epsilonEquals(ray, 1, -0.1);
457 fail("Negative epsilonDirection should have thrown an IllegalArgumentException");
458 }
459 catch (IllegalArgumentException npe)
460 {
461
462 }
463
464 try
465 {
466 ray.epsilonEquals(ray, Double.NaN, 1);
467 fail("NaN epsilonCoordinate should have thrown an IllegalArgumentException");
468 }
469 catch (IllegalArgumentException npe)
470 {
471
472 }
473
474 try
475 {
476 ray.epsilonEquals(ray, 1, Double.NaN);
477 fail("NaN epsilonDirection should have thrown an IllegalArgumentException");
478 }
479 catch (IllegalArgumentException npe)
480 {
481
482 }
483
484 double[] deltas = new double[] { 0.0, -0.125, 0.125, -1, 1 };
485 for (double dX : deltas)
486 {
487 for (double dY : deltas)
488 {
489 for (double dPhi : deltas)
490 {
491 Ray2d other = new Ray2d(ray.x + dX, ray.y + dY, ray.phi + dPhi);
492 for (double epsilon : new double[] { 0, 0.125, 0.5, 0.9, 1.0, 1.1 })
493 {
494
495 boolean result = ray.epsilonEquals(other, epsilon, Double.POSITIVE_INFINITY);
496 boolean expected = Math.abs(dX) <= epsilon && Math.abs(dY) <= epsilon;
497 assertEquals("result of epsilonEquals checking x, y, z", expected, result);
498
499 result = ray.epsilonEquals(other, Double.POSITIVE_INFINITY, epsilon);
500 expected = Math.abs(dPhi) <= epsilon;
501 assertEquals("result of epsilonEquals checking phi", expected, result);
502 }
503 }
504 }
505 }
506 }
507
508
509
510
511 @Test
512 public void equalsAndHashCodeTest()
513 {
514 Ray2d ray = new Ray2d(1, 2, 11, 12);
515 assertEquals("equal to itself", ray, ray);
516 assertNotEquals("not equal to null", ray, null);
517 assertNotEquals("not equal to different object with same parent class", ray, new OrientedPoint2d(1, 2));
518 assertNotEquals("not equal to ray with different direction", ray, new Ray2d(1, 2, 11, 10));
519 assertNotEquals("not equal to ray with different start x", ray, new Ray2d(2, 2, 12, 12));
520 assertNotEquals("not equal to ray with different start y", ray, new Ray2d(1, 3, 12, 13));
521 assertEquals("equal to ray with same x, y and direction", ray, new Ray2d(1, 2, 21, 22));
522
523 assertNotEquals("hashCode depends on x", ray.hashCode(), new Ray2d(2, 2, 12, 12));
524 assertNotEquals("hashCode depends on y", ray.hashCode(), new Ray2d(1, 3, 11, 13));
525 assertNotEquals("hashCode depends on phi", ray.hashCode(), new Ray2d(1, 2, 11, 10));
526 }
527
528 }