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