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 }