View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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          // arrange
40          final Vector2D p0 = Vector2D.of(1, 2);
41          final Vector2D p1 = Vector2D.of(2, 2);
42  
43          // act
44          final Ray ray = Lines.rayFromPointAndDirection(p0, p0.vectorTo(p1), TEST_PRECISION);
45  
46          // assert
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          // arrange
68          final Vector2D p = Vector2D.of(0, 2);
69          final Vector2D d = Vector2D.of(1e-17, -1e-12);
70  
71          // act/assert
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          // arrange
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          // act
87          final Ray ray = Lines.rayFromPoint(line, p3);
88  
89          // assert
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         // arrange
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         // act/assert
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         // arrange
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         // act
138         final Ray ray = Lines.rayFromLocation(line, -2);
139 
140         // assert
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         // arrange
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         // act/assert
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         // arrange
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         // act
189         final Ray result = ray.transform(t);
190 
191         // assert
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         // arrange
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         // act
206         final Ray result = ray.transform(t);
207 
208         // assert
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         // arrange
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             // act
224             final ReverseRay rev = ray.reverse();
225 
226             // assert
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         // arrange
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         // act/assert
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         // arrange
260         final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(1, 1), Vector2D.Unit.PLUS_X, TEST_PRECISION);
261 
262         // act/assert
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         // --- arrange
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         // --- act
286 
287         // parallel
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         // coincident
296         checkSplit(ray.split(Lines.fromPointAndAngle(p0.add(delta), 1e-20, TEST_PRECISION)),
297                 null, null,
298                 null, null);
299 
300         // through point on ray
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         // through start point
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         // intersection below minus
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         // arrange
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         // act
335         final Split<LineConvexSubset> split = ray.split(splitter);
336 
337         // assert
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         // arrange
347         final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(2, -1), Vector2D.Unit.PLUS_X, TEST_PRECISION);
348 
349         // act
350         final Interval interval = ray.getInterval();
351 
352         // assert
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         // arrange
362         final Ray ray = Lines.rayFromPointAndDirection(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
363 
364         // act
365         final String str = ray.toString();
366 
367         // assert
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 }