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.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          // arrange
    39          final Vector2D p0 = Vector2D.of(1, 2);
    40          final Vector2D p1 = Vector2D.of(2, 2);
    41
    42          // act
    43          final Ray ray = Lines.rayFromPointAndDirection(p0, p0.vectorTo(p1), TEST_PRECISION);
    44
    45          // assert
    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          // arrange
    67          final Vector2D p = Vector2D.of(0, 2);
    68          final Vector2D d = Vector2D.of(1e-17, -1e-12);
    69
    70          // act/assert
    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          // arrange
    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          // act
    86          final Ray ray = Lines.rayFromPoint(line, p3);
    87
    88          // assert
    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         // arrange
    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         // act/assert
    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         // arrange
    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         // act
    137         final Ray ray = Lines.rayFromLocation(line, -2);
    138
    139         // assert
    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         // arrange
    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         // act/assert
    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         // arrange
    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         // act
    188         final Ray result = ray.transform(t);
    189
    190         // assert
    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         // arrange
    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         // act
    205         final Ray result = ray.transform(t);
    206
    207         // assert
    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         // arrange
    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             // act
    223             final ReverseRay rev = ray.reverse();
    224
    225             // assert
    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         // arrange
    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         // act/assert
    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         // arrange
    259         final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(1, 1), Vector2D.Unit.PLUS_X, TEST_PRECISION);
    260
    261         // act/assert
    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         // --- arrange
    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         // --- act
    285
    286         // parallel
    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         // coincident
    295         checkSplit(ray.split(Lines.fromPointAndAngle(p0.add(delta), 1e-20, TEST_PRECISION)),
    296                 null, null,
    297                 null, null);
    298
    299         // through point on ray
    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         // through start point
    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         // intersection below minus
    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         // arrange
    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         // act
    334         final Split<LineConvexSubset> split = ray.split(splitter);
    335
    336         // assert
    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         // arrange
    346         final Ray ray = Lines.rayFromPointAndDirection(Vector2D.of(2, -1), Vector2D.Unit.PLUS_X, TEST_PRECISION);
    347
    348         // act
    349         final Interval interval = ray.getInterval();
    350
    351         // assert
    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         // arrange
    361         final Ray ray = Lines.rayFromPointAndDirection(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
    362
    363         // act
    364         final String str = ray.toString();
    365
    366         // assert
    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 }