1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.twod;
18
19 import org.apache.commons.geometry.core.GeometryTestUtils;
20 import org.apache.commons.geometry.core.RegionLocation;
21 import org.apache.commons.geometry.core.partitioning.Split;
22 import org.apache.commons.geometry.core.partitioning.SplitLocation;
23 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
24 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
25 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
26 import org.apache.commons.geometry.euclidean.oned.Interval;
27 import org.junit.Assert;
28 import org.junit.Test;
29
30 public class RayTest {
31
32 private static final double TEST_EPS = 1e-10;
33
34 private static final DoublePrecisionContext TEST_PRECISION =
35 new EpsilonDoublePrecisionContext(TEST_EPS);
36
37 @Test
38 public void testFromPointAndDirection() {
39
40 final Vector2D p0 = Vector2D.of(1, 2);
41 final Vector2D p1 = Vector2D.of(2, 2);
42
43
44 final Ray ray = Lines.rayFromPointAndDirection(p0, p0.vectorTo(p1), TEST_PRECISION);
45
46
47 Assert.assertFalse(ray.isFull());
48 Assert.assertFalse(ray.isEmpty());
49 Assert.assertTrue(ray.isInfinite());
50 Assert.assertFalse(ray.isFinite());
51
52 EuclideanTestUtils.assertCoordinatesEqual(p0, ray.getStartPoint(), TEST_EPS);
53 Assert.assertNull(ray.getEndPoint());
54
55 Assert.assertEquals(1, ray.getSubspaceStart(), TEST_EPS);
56 GeometryTestUtils.assertPositiveInfinity(ray.getSubspaceEnd());
57
58 GeometryTestUtils.assertPositiveInfinity(ray.getSize());
59 Assert.assertNull(ray.getCentroid());
60 Assert.assertNull(ray.getBounds());
61
62 EuclideanTestUtils.assertCoordinatesEqual(p0.vectorTo(p1), ray.getDirection(), TEST_EPS);
63 }
64
65 @Test
66 public void testFromPointAndDirection_invalidArgs() {
67
68 final Vector2D p = Vector2D.of(0, 2);
69 final Vector2D d = Vector2D.of(1e-17, -1e-12);
70
71
72 GeometryTestUtils.assertThrows(() -> {
73 Lines.rayFromPointAndDirection(p, d, TEST_PRECISION);
74 }, IllegalArgumentException.class, "Line direction cannot be zero");
75 }
76
77 @Test
78 public void testFromPoint() {
79
80 final Vector2D p0 = Vector2D.of(1, 1);
81 final Vector2D p1 = Vector2D.of(1, 2);
82 final Vector2D p3 = Vector2D.of(3, 3);
83
84 final Line line = Lines.fromPoints(p0, p1, TEST_PRECISION);
85
86
87 final Ray ray = Lines.rayFromPoint(line, p3);
88
89
90 Assert.assertFalse(ray.isFull());
91 Assert.assertFalse(ray.isEmpty());
92 Assert.assertTrue(ray.isInfinite());
93 Assert.assertFalse(ray.isFinite());
94
95 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 3), ray.getStartPoint(), TEST_EPS);
96 Assert.assertNull(ray.getEndPoint());
97
98 Assert.assertEquals(3, ray.getSubspaceStart(), TEST_EPS);
99 GeometryTestUtils.assertPositiveInfinity(ray.getSubspaceEnd());
100
101 GeometryTestUtils.assertPositiveInfinity(ray.getSize());
102 Assert.assertNull(ray.getCentroid());
103 Assert.assertNull(ray.getBounds());
104
105 EuclideanTestUtils.assertCoordinatesEqual(p0.vectorTo(p1), ray.getDirection(), TEST_EPS);
106 }
107
108 @Test
109 public void testFromPoint_invalidArgs() {
110
111 final Vector2D p = Vector2D.of(0, 2);
112 final Vector2D d = Vector2D.of(1, 1);
113 final Line line = Lines.fromPointAndDirection(p, d, TEST_PRECISION);
114
115
116 GeometryTestUtils.assertThrows(() -> {
117 Lines.rayFromPoint(line, Vector2D.NaN);
118 }, IllegalArgumentException.class, "Invalid ray start point: (NaN, NaN)");
119
120 GeometryTestUtils.assertThrows(() -> {
121 Lines.rayFromPoint(line, Vector2D.POSITIVE_INFINITY);
122 }, IllegalArgumentException.class, "Invalid ray start point: (Infinity, Infinity)");
123
124 GeometryTestUtils.assertThrows(() -> {
125 Lines.rayFromPoint(line, Vector2D.NEGATIVE_INFINITY);
126 }, IllegalArgumentException.class, "Invalid ray start point: (-Infinity, -Infinity)");
127 }
128
129 @Test
130 public void testFromLocation() {
131
132 final Vector2D p0 = Vector2D.of(1, 1);
133 final Vector2D p1 = Vector2D.of(1, 2);
134
135 final Line line = Lines.fromPoints(p0, p1, TEST_PRECISION);
136
137
138 final Ray ray = Lines.rayFromLocation(line, -2);
139
140
141 Assert.assertFalse(ray.isFull());
142 Assert.assertFalse(ray.isEmpty());
143 Assert.assertTrue(ray.isInfinite());
144 Assert.assertFalse(ray.isFinite());
145
146 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, -2), ray.getStartPoint(), TEST_EPS);
147 Assert.assertNull(ray.getEndPoint());
148
149 Assert.assertEquals(-2, ray.getSubspaceStart(), TEST_EPS);
150 GeometryTestUtils.assertPositiveInfinity(ray.getSubspaceEnd());
151
152 GeometryTestUtils.assertPositiveInfinity(ray.getSize());
153 Assert.assertNull(ray.getCentroid());
154 Assert.assertNull(ray.getBounds());
155
156 EuclideanTestUtils.assertCoordinatesEqual(p0.vectorTo(p1), ray.getDirection(), TEST_EPS);
157 }
158
159 @Test
160 public void testFromLocation_invalidArgs() {
161
162 final Vector2D p = Vector2D.of(0, 2);
163 final Vector2D d = Vector2D.of(1, 1);
164 final Line line = Lines.fromPointAndDirection(p, d, TEST_PRECISION);
165
166
167 GeometryTestUtils.assertThrows(() -> {
168 Lines.rayFromLocation(line, Double.NaN);
169 }, IllegalArgumentException.class, "Invalid ray start location: NaN");
170
171 GeometryTestUtils.assertThrows(() -> {
172 Lines.rayFromLocation(line, Double.POSITIVE_INFINITY);
173 }, IllegalArgumentException.class, "Invalid ray start location: Infinity");
174
175 GeometryTestUtils.assertThrows(() -> {
176 Lines.rayFromLocation(line, Double.NEGATIVE_INFINITY);
177 }, IllegalArgumentException.class, "Invalid ray start location: -Infinity");
178 }
179
180 @Test
181 public void testTransform() {
182
183 final AffineTransformMatrix2D t = AffineTransformMatrix2D.createRotation(-0.5 * Math.PI)
184 .translate(Vector2D.Unit.PLUS_X);
185
186 final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(1, 0), Vector2D.Unit.PLUS_X, TEST_PRECISION);
187
188
189 final Ray result = ray.transform(t);
190
191
192 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, -1), result.getStartPoint(), TEST_EPS);
193 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y, result.getDirection(), TEST_EPS);
194 }
195
196 @Test
197 public void testTransform_reflection() {
198
199 final AffineTransformMatrix2D t = AffineTransformMatrix2D.createRotation(0.5 * Math.PI)
200 .translate(Vector2D.Unit.PLUS_X)
201 .scale(1, -1);
202
203 final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(2, 3), Vector2D.Unit.PLUS_X, TEST_PRECISION);
204
205
206 final Ray result = ray.transform(t);
207
208
209 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, -2), result.getStartPoint(), TEST_EPS);
210 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y, result.getDirection(), TEST_EPS);
211 }
212
213 @Test
214 public void testReverse() {
215
216 final Vector2D start = Vector2D.of(1, 2);
217
218 EuclideanTestUtils.permuteSkipZero(-4, 4, 1, (x, y) -> {
219 final Vector2D dir = Vector2D.of(x, y);
220
221 final Ray ray = Lines.rayFromPointAndDirection(start, dir, TEST_PRECISION);
222
223
224 final ReverseRay rev = ray.reverse();
225
226
227 EuclideanTestUtils.assertCoordinatesEqual(ray.getLine().getOrigin(), rev.getLine().getOrigin(), TEST_EPS);
228 Assert.assertEquals(-1, ray.getLine().getDirection().dot(rev.getLine().getDirection()), TEST_EPS);
229
230 EuclideanTestUtils.assertCoordinatesEqual(ray.getStartPoint(), rev.getEndPoint(), TEST_EPS);
231 });
232 }
233
234 @Test
235 public void testClosest() {
236
237 final Vector2D p1 = Vector2D.of(0, -1);
238 final Vector2D p2 = Vector2D.of(0, 1);
239 final Ray ray = Lines.rayFromPointAndDirection(p1, p1.directionTo(p2), TEST_PRECISION);
240
241
242 EuclideanTestUtils.assertCoordinatesEqual(p1, ray.closest(p1), TEST_EPS);
243 EuclideanTestUtils.assertCoordinatesEqual(p1, ray.closest(Vector2D.of(0, -2)), TEST_EPS);
244 EuclideanTestUtils.assertCoordinatesEqual(p1, ray.closest(Vector2D.of(2, -2)), TEST_EPS);
245 EuclideanTestUtils.assertCoordinatesEqual(p1, ray.closest(Vector2D.of(-1, -1)), TEST_EPS);
246
247 EuclideanTestUtils.assertCoordinatesEqual(p2, ray.closest(p2), TEST_EPS);
248 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), ray.closest(Vector2D.of(0, 2)), TEST_EPS);
249 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), ray.closest(Vector2D.of(-2, 2)), TEST_EPS);
250 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 1), ray.closest(Vector2D.of(-1, 1)), TEST_EPS);
251
252 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, ray.closest(Vector2D.ZERO), TEST_EPS);
253 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 0.5), ray.closest(Vector2D.of(1, 0.5)), TEST_EPS);
254 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -0.5), ray.closest(Vector2D.of(-2, -0.5)), TEST_EPS);
255 }
256
257 @Test
258 public void testClassify() {
259
260 final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(1, 1), Vector2D.Unit.PLUS_X, TEST_PRECISION);
261
262
263 EuclideanTestUtils.assertRegionLocation(ray, RegionLocation.OUTSIDE,
264 Vector2D.of(2, 2), Vector2D.of(2, 0),
265 Vector2D.of(-5, 1), Vector2D.of(0, 1));
266
267 EuclideanTestUtils.assertRegionLocation(ray, RegionLocation.BOUNDARY,
268 Vector2D.of(1, 1), Vector2D.of(1 + 1e-16, 1));
269
270 EuclideanTestUtils.assertRegionLocation(ray, RegionLocation.INSIDE,
271 Vector2D.of(2, 1), Vector2D.of(5, 1 + 1e-16));
272 }
273
274 @Test
275 public void testSplit() {
276
277 final Vector2D p0 = Vector2D.of(1, 1);
278 final Vector2D p1 = Vector2D.of(3, 1);
279 final Vector2D low = Vector2D.of(0, 1);
280
281 final Vector2D delta = Vector2D.of(1e-11, 1e-11);
282
283 final Ray ray = Lines.rayFromPointAndDirection(p0, Vector2D.Unit.PLUS_X, TEST_PRECISION);
284
285
286
287
288 checkSplit(ray.split(Lines.fromPointAndAngle(Vector2D.of(2, 2), 0, TEST_PRECISION)),
289 null, null,
290 p0, null);
291 checkSplit(ray.split(Lines.fromPointAndAngle(Vector2D.of(2, 2), Math.PI, TEST_PRECISION)),
292 p0, null,
293 null, null);
294
295
296 checkSplit(ray.split(Lines.fromPointAndAngle(p0.add(delta), 1e-20, TEST_PRECISION)),
297 null, null,
298 null, null);
299
300
301 checkSplit(ray.split(Lines.fromPointAndAngle(p1, 1, TEST_PRECISION)),
302 p0, p1,
303 p1, null);
304 checkSplit(ray.split(Lines.fromPointAndAngle(p1, -1, TEST_PRECISION)),
305 p1, null,
306 p0, p1);
307
308
309 checkSplit(ray.split(Lines.fromPointAndAngle(p0.subtract(delta), 1, TEST_PRECISION)),
310 null, null,
311 p0, null);
312 checkSplit(ray.split(Lines.fromPointAndAngle(p0.add(delta), -1, TEST_PRECISION)),
313 p0, null,
314 null, null);
315
316
317 checkSplit(ray.split(Lines.fromPointAndAngle(low, 1, TEST_PRECISION)),
318 null, null,
319 p0, null);
320 checkSplit(ray.split(Lines.fromPointAndAngle(low, -1, TEST_PRECISION)),
321 p0, null,
322 null, null);
323 }
324
325 @Test
326 public void testSplit_smallAngle_pointOnSplitter() {
327
328 final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-5);
329
330 final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(1, 1e-6), Vector2D.of(-1, -1e-2), precision);
331
332 final Line splitter = Lines.fromPointAndAngle(Vector2D.ZERO, 0, precision);
333
334
335 final Split<LineConvexSubset> split = ray.split(splitter);
336
337
338 Assert.assertEquals(SplitLocation.PLUS, split.getLocation());
339
340 Assert.assertNull(split.getMinus());
341 Assert.assertSame(ray, split.getPlus());
342 }
343
344 @Test
345 public void testGetInterval() {
346
347 final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(2, -1), Vector2D.Unit.PLUS_X, TEST_PRECISION);
348
349
350 final Interval interval = ray.getInterval();
351
352
353 Assert.assertEquals(2, interval.getMin(), TEST_EPS);
354 GeometryTestUtils.assertPositiveInfinity(interval.getMax());
355
356 Assert.assertSame(ray.getLine().getPrecision(), interval.getMinBoundary().getPrecision());
357 }
358
359 @Test
360 public void testToString() {
361
362 final Ray ray = Lines.rayFromPointAndDirection(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
363
364
365 final String str = ray.toString();
366
367
368 GeometryTestUtils.assertContains("Ray[startPoint= (0", str);
369 GeometryTestUtils.assertContains(", direction= (1", str);
370 }
371
372 private static void checkSplit(final Split<LineConvexSubset> split, final Vector2D minusStart, final Vector2D minusEnd,
373 final Vector2D plusStart, final Vector2D plusEnd) {
374
375 final LineConvexSubset minus = split.getMinus();
376 if (minusStart == null && minusEnd == null) {
377 Assert.assertNull(minus);
378 } else {
379 checkPoint(minusStart, minus.getStartPoint());
380 checkPoint(minusEnd, minus.getEndPoint());
381 }
382
383
384 final LineConvexSubset plus = split.getPlus();
385 if (plusStart == null && plusEnd == null) {
386 Assert.assertNull(plus);
387 } else {
388 checkPoint(plusStart, plus.getStartPoint());
389 checkPoint(plusEnd, plus.getEndPoint());
390 }
391 }
392
393 private static void checkPoint(final Vector2D expected, final Vector2D pt) {
394 if (expected == null) {
395 Assert.assertNull(pt);
396 } else {
397 EuclideanTestUtils.assertCoordinatesEqual(expected, pt, TEST_EPS);
398 }
399 }
400 }