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.spherical.oned;
    18
    19  import java.util.List;
    20
    21  import org.apache.commons.geometry.core.Region;
    22  import org.apache.commons.geometry.core.RegionLocation;
    23  import org.apache.commons.geometry.core.partitioning.Split;
    24  import org.apache.commons.geometry.core.partitioning.SplitLocation;
    25  import org.apache.commons.numbers.angle.Angle;
    26  import org.apache.commons.numbers.core.Precision;
    27  import org.junit.jupiter.api.Assertions;
    28  import org.junit.jupiter.api.Test;
    29
    30  class AngularIntervalTest {
    31
    32      private static final double TEST_EPS = 1e-10;
    33
    34      private static final Precision.DoubleEquivalence TEST_PRECISION =
    35              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
    36
    37      @Test
    38      void testOf_doubles() {
    39          // act/assert
    40          checkInterval(AngularInterval.of(0, 1, TEST_PRECISION), 0, 1);
    41          checkInterval(AngularInterval.of(1, 0, TEST_PRECISION), 1, Angle.TWO_PI);
    42          checkInterval(AngularInterval.of(-2, -1.5, TEST_PRECISION), -2, -1.5);
    43          checkInterval(AngularInterval.of(-2, -2.5, TEST_PRECISION), -2, Angle.TWO_PI - 2.5);
    44
    45          checkFull(AngularInterval.of(1, 1, TEST_PRECISION));
    46          checkFull(AngularInterval.of(0, 1e-11, TEST_PRECISION));
    47          checkFull(AngularInterval.of(0, -1e-11, TEST_PRECISION));
    48          checkFull(AngularInterval.of(0, Angle.TWO_PI, TEST_PRECISION));
    49      }
    50
    51      @Test
    52      void testOf_doubles_invalidArgs() {
    53          // act/assert
    54          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Double.NEGATIVE_INFINITY, 0, TEST_PRECISION));
    55          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(0, Double.POSITIVE_INFINITY, TEST_PRECISION));
    56          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, TEST_PRECISION));
    57          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Double.NaN, 0, TEST_PRECISION));
    58          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(0, Double.NaN, TEST_PRECISION));
    59          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Double.NaN, Double.NaN, TEST_PRECISION));
    60      }
    61
    62      @Test
    63      void testOf_points() {
    64          // act/assert
    65          checkInterval(AngularInterval.of(Point1S.of(0), Point1S.of(1), TEST_PRECISION), 0, 1);
    66          checkInterval(AngularInterval.of(Point1S.of(1), Point1S.of(0), TEST_PRECISION), 1, Angle.TWO_PI);
    67          checkInterval(AngularInterval.of(Point1S.of(-2), Point1S.of(-1.5), TEST_PRECISION), -2, -1.5);
    68          checkInterval(AngularInterval.of(Point1S.of(-2), Point1S.of(-2.5), TEST_PRECISION), -2, Angle.TWO_PI - 2.5);
    69
    70          checkFull(AngularInterval.of(Point1S.of(1), Point1S.of(1), TEST_PRECISION));
    71          checkFull(AngularInterval.of(Point1S.of(0), Point1S.of(1e-11), TEST_PRECISION));
    72          checkFull(AngularInterval.of(Point1S.of(0), Point1S.of(-1e-11), TEST_PRECISION));
    73      }
    74
    75      @Test
    76      void testOf_points_invalidArgs() {
    77          // act/assert
    78          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Point1S.of(Double.NEGATIVE_INFINITY), Point1S.ZERO, TEST_PRECISION));
    79          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Point1S.ZERO, Point1S.of(Double.POSITIVE_INFINITY), TEST_PRECISION));
    80          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Point1S.of(Double.POSITIVE_INFINITY), Point1S.of(Double.NEGATIVE_INFINITY), TEST_PRECISION));
    81          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Point1S.NaN, Point1S.ZERO, TEST_PRECISION));
    82          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Point1S.ZERO, Point1S.NaN, TEST_PRECISION));
    83          Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(Point1S.NaN, Point1S.NaN, TEST_PRECISION));
    84      }
    85
    86      @Test
    87      void testOf_orientedPoints() {
    88          // arrange
    89          final Precision.DoubleEquivalence precisionA = Precision.doubleEquivalenceOfEpsilon(1e-3);
    90          final Precision.DoubleEquivalence precisionB = Precision.doubleEquivalenceOfEpsilon(1e-2);
    91
    92          final CutAngle zeroPos = CutAngles.createPositiveFacing(Point1S.ZERO, precisionA);
    93          final CutAngle zeroNeg = CutAngles.createNegativeFacing(Point1S.ZERO, precisionA);
    94
    95          final CutAngle piPos = CutAngles.createPositiveFacing(Point1S.PI, precisionA);
    96          final CutAngle piNeg = CutAngles.createNegativeFacing(Point1S.PI, precisionA);
    97
    98          final CutAngle almostPiPos = CutAngles.createPositiveFacing(Point1S.of(Math.PI + 5e-3), precisionB);
    99
    100         // act/assert
    101         checkInterval(AngularInterval.of(zeroNeg, piPos), 0, Math.PI);
    102         checkInterval(AngularInterval.of(zeroPos, piNeg), Math.PI, Angle.TWO_PI);
    103
    104         checkFull(AngularInterval.of(zeroPos, zeroNeg));
    105         checkFull(AngularInterval.of(zeroPos, piPos));
    106         checkFull(AngularInterval.of(piNeg, zeroNeg));
    107
    108         checkFull(AngularInterval.of(almostPiPos, piNeg));
    109         checkFull(AngularInterval.of(piNeg, almostPiPos));
    110     }
    111
    112     @Test
    113     void testOf_orientedPoints_invalidArgs() {
    114         // arrange
    115         final CutAngle pt = CutAngles.createNegativeFacing(Point1S.ZERO, TEST_PRECISION);
    116         final CutAngle nan = CutAngles.createPositiveFacing(Point1S.NaN, TEST_PRECISION);
    117
    118         // act/assert
    119         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(pt, nan));
    120         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(nan, pt));
    121         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.of(nan, nan));
    122     }
    123
    124     @Test
    125     void testFull() {
    126         // act
    127         final AngularInterval.Convex interval = AngularInterval.full();
    128
    129         // assert
    130         checkFull(interval);
    131     }
    132
    133     @Test
    134     void testClassify_full() {
    135         // arrange
    136         final AngularInterval interval = AngularInterval.full();
    137
    138         // act/assert
    139         for (double a = -2 * Math.PI; a >= 4 * Math.PI; a += 0.5) {
    140             checkClassify(interval, RegionLocation.INSIDE, Point1S.of(a));
    141         }
    142     }
    143
    144     @Test
    145     void testClassify_almostFull() {
    146         // arrange
    147         final AngularInterval interval = AngularInterval.of(1 + 2e-10, 1, TEST_PRECISION);
    148
    149         // act/assert
    150         checkClassify(interval, RegionLocation.BOUNDARY,
    151                 Point1S.of(1 + 2e-10), Point1S.of(1 + 6e-11), Point1S.of(1));
    152
    153         checkClassify(interval, RegionLocation.INSIDE, Point1S.of(1 + 6e-11 + Math.PI));
    154
    155         for (double a = 1 + 1e-9; a >= 1 - 1e-9 + Angle.TWO_PI; a += 0.5) {
    156             checkClassify(interval, RegionLocation.INSIDE, Point1S.of(a));
    157         }
    158     }
    159
    160     @Test
    161     void testClassify_sizeableGap() {
    162         // arrange
    163         final AngularInterval interval = AngularInterval.of(0.25, -0.25, TEST_PRECISION);
    164
    165         // act/assert
    166         checkClassify(interval, RegionLocation.OUTSIDE,
    167                 Point1S.ZERO, Point1S.of(-0.2), Point1S.of(0.2));
    168         checkClassify(interval, RegionLocation.BOUNDARY,
    169                 Point1S.of(-0.25), Point1S.of(0.2499999999999));
    170         checkClassify(interval, RegionLocation.INSIDE,
    171                 Point1S.of(1), Point1S.PI, Point1S.of(-1));
    172     }
    173
    174     @Test
    175     void testClassify_halfPi() {
    176         // arrange
    177         final AngularInterval interval = AngularInterval.of(Angle.PI_OVER_TWO, -Angle.PI_OVER_TWO, TEST_PRECISION);
    178
    179         // act/assert
    180         checkClassify(interval, RegionLocation.OUTSIDE,
    181                 Point1S.ZERO, Point1S.of(Angle.PI_OVER_TWO - 0.1), Point1S.of(-Angle.PI_OVER_TWO + 0.1));
    182         checkClassify(interval, RegionLocation.BOUNDARY,
    183                 Point1S.of(Angle.PI_OVER_TWO), Point1S.of(1.5 * Math.PI));
    184         checkClassify(interval, RegionLocation.INSIDE,
    185                 Point1S.PI, Point1S.of(Angle.PI_OVER_TWO + 0.1), Point1S.of(-Angle.PI_OVER_TWO - 0.1));
    186     }
    187
    188     @Test
    189     void testClassify_almostEmpty() {
    190         // arrange
    191         final AngularInterval interval = AngularInterval.of(1, 1 + 2e-10, TEST_PRECISION);
    192
    193         // act/assert
    194         checkClassify(interval, RegionLocation.BOUNDARY,
    195                 Point1S.of(1 + 2e-10), Point1S.of(1 + 6e-11), Point1S.of(1));
    196
    197         checkClassify(interval, RegionLocation.OUTSIDE, Point1S.of(1 + 6e-11 + Math.PI));
    198
    199         for (double a = 1 + 1e-9; a >= 1 - 1e-9 + Angle.TWO_PI; a += 0.5) {
    200             checkClassify(interval, RegionLocation.OUTSIDE, Point1S.of(a));
    201         }
    202     }
    203
    204     @Test
    205     void testProject_full() {
    206         // arrange
    207         final AngularInterval interval = AngularInterval.full();
    208
    209         // act/assert
    210         Assertions.assertNull(interval.project(Point1S.ZERO));
    211         Assertions.assertNull(interval.project(Point1S.PI));
    212     }
    213
    214     @Test
    215     void testProject() {
    216         // arrange
    217         final AngularInterval interval = AngularInterval.of(1, 2, TEST_PRECISION);
    218
    219         // act/assert
    220         Assertions.assertEquals(1, interval.project(Point1S.ZERO).getAzimuth(), TEST_EPS);
    221         Assertions.assertEquals(1, interval.project(Point1S.of(1)).getAzimuth(), TEST_EPS);
    222         Assertions.assertEquals(1, interval.project(Point1S.of(1.5)).getAzimuth(), TEST_EPS);
    223
    224         Assertions.assertEquals(2, interval.project(Point1S.of(2)).getAzimuth(), TEST_EPS);
    225         Assertions.assertEquals(2, interval.project(Point1S.PI).getAzimuth(), TEST_EPS);
    226         Assertions.assertEquals(2, interval.project(Point1S.of(1.4 + Math.PI)).getAzimuth(), TEST_EPS);
    227
    228         Assertions.assertEquals(1, interval.project(Point1S.of(1.5 + Math.PI)).getAzimuth(), TEST_EPS);
    229         Assertions.assertEquals(1, interval.project(Point1S.of(1.6 + Math.PI)).getAzimuth(), TEST_EPS);
    230     }
    231
    232     @Test
    233     void testTransform_full() {
    234         // arrange
    235         final AngularInterval interval = AngularInterval.full();
    236
    237         final Transform1S rotate = Transform1S.createRotation(Angle.PI_OVER_TWO);
    238         final Transform1S invert = Transform1S.createNegation().rotate(Angle.PI_OVER_TWO);
    239
    240         // act/assert
    241         checkFull(interval.transform(rotate));
    242         checkFull(interval.transform(invert));
    243     }
    244
    245     @Test
    246     void testTransform() {
    247         // arrange
    248         final AngularInterval interval = AngularInterval.of(Angle.PI_OVER_TWO, Math.PI, TEST_PRECISION);
    249
    250         final Transform1S rotate = Transform1S.createRotation(Angle.PI_OVER_TWO);
    251         final Transform1S invert = Transform1S.createNegation().rotate(Angle.PI_OVER_TWO);
    252
    253         // act/assert
    254         checkInterval(interval.transform(rotate), Math.PI, 1.5 * Math.PI);
    255         checkInterval(interval.transform(invert), -0.5 * Math.PI, 0.0);
    256     }
    257
    258     @Test
    259     void testWrapsZero() {
    260         // act/assert
    261         Assertions.assertFalse(AngularInterval.full().wrapsZero());
    262         Assertions.assertFalse(AngularInterval.of(0, Angle.PI_OVER_TWO, TEST_PRECISION).wrapsZero());
    263         Assertions.assertFalse(AngularInterval.of(Angle.PI_OVER_TWO, Math.PI, TEST_PRECISION).wrapsZero());
    264         Assertions.assertFalse(AngularInterval.of(Math.PI, 1.5 * Math.PI, TEST_PRECISION).wrapsZero());
    265         Assertions.assertFalse(AngularInterval.of(1.5 * Math.PI, Angle.TWO_PI - 1e-5, TEST_PRECISION).wrapsZero());
    266
    267         Assertions.assertTrue(AngularInterval.of(1.5 * Math.PI, Angle.TWO_PI, TEST_PRECISION).wrapsZero());
    268         Assertions.assertTrue(AngularInterval.of(1.5 * Math.PI, 2.5 * Math.PI, TEST_PRECISION).wrapsZero());
    269         Assertions.assertTrue(AngularInterval.of(-2.5 * Math.PI, -1.5 * Math.PI, TEST_PRECISION).wrapsZero());
    270     }
    271
    272     @Test
    273     void testToTree_full() {
    274         // arrange
    275         final AngularInterval interval = AngularInterval.full();
    276
    277         // act
    278         final RegionBSPTree1S tree = interval.toTree();
    279
    280         // assert
    281         Assertions.assertTrue(tree.isFull());
    282         Assertions.assertFalse(tree.isEmpty());
    283
    284         checkClassify(tree, RegionLocation.INSIDE,
    285                 Point1S.ZERO, Point1S.of(Angle.PI_OVER_TWO),
    286                 Point1S.PI, Point1S.of(-Angle.PI_OVER_TWO));
    287     }
    288
    289     @Test
    290     void testToTree_intervalEqualToPi() {
    291         // arrange
    292         final AngularInterval interval = AngularInterval.of(0.0, Math.PI, TEST_PRECISION);
    293
    294         // act
    295         final RegionBSPTree1S tree = interval.toTree();
    296
    297         // assert
    298         Assertions.assertFalse(tree.isFull());
    299         Assertions.assertFalse(tree.isEmpty());
    300
    301         checkClassify(tree, RegionLocation.BOUNDARY,
    302                 Point1S.ZERO, Point1S.PI);
    303
    304         checkClassify(tree, RegionLocation.INSIDE,
    305                 Point1S.of(1e-4), Point1S.of(0.25 * Math.PI),
    306                 Point1S.of(-1.25 * Math.PI), Point1S.of(Math.PI - 1e-4));
    307
    308         checkClassify(tree, RegionLocation.OUTSIDE,
    309                 Point1S.of(-1e-4), Point1S.of(-0.25 * Math.PI),
    310                 Point1S.of(1.25 * Math.PI), Point1S.of(-Math.PI + 1e-4));
    311     }
    312
    313     @Test
    314     void testToTree_intervalLessThanPi() {
    315         // arrange
    316         final AngularInterval interval = AngularInterval.of(Angle.PI_OVER_TWO, Math.PI, TEST_PRECISION);
    317
    318         // act
    319         final RegionBSPTree1S tree = interval.toTree();
    320
    321         // assert
    322         Assertions.assertFalse(tree.isFull());
    323         Assertions.assertFalse(tree.isEmpty());
    324
    325         checkClassify(tree, RegionLocation.BOUNDARY,
    326                 Point1S.of(Angle.PI_OVER_TWO), Point1S.PI);
    327
    328         checkClassify(tree, RegionLocation.INSIDE,
    329                 Point1S.of(0.51 * Math.PI), Point1S.of(0.75 * Math.PI),
    330                 Point1S.of(0.99 * Math.PI));
    331
    332         checkClassify(tree, RegionLocation.OUTSIDE,
    333                 Point1S.ZERO, Point1S.of(0.25 * Math.PI),
    334                 Point1S.of(1.25 * Math.PI), Point1S.of(1.75 * Math.PI));
    335     }
    336
    337     @Test
    338     void testToTree_intervalGreaterThanPi() {
    339         // arrange
    340         final AngularInterval interval = AngularInterval.of(Math.PI, Angle.PI_OVER_TWO, TEST_PRECISION);
    341
    342         // act
    343         final RegionBSPTree1S tree = interval.toTree();
    344
    345         // assert
    346         Assertions.assertFalse(tree.isFull());
    347         Assertions.assertFalse(tree.isEmpty());
    348
    349         checkClassify(tree, RegionLocation.BOUNDARY,
    350                 Point1S.of(Angle.PI_OVER_TWO), Point1S.PI);
    351
    352         checkClassify(tree, RegionLocation.INSIDE,
    353                 Point1S.ZERO, Point1S.of(0.25 * Math.PI),
    354                 Point1S.of(1.25 * Math.PI), Point1S.of(1.75 * Math.PI));
    355
    356         checkClassify(tree, RegionLocation.OUTSIDE,
    357                 Point1S.of(0.51 * Math.PI), Point1S.of(0.75 * Math.PI),
    358                 Point1S.of(0.99 * Math.PI));
    359     }
    360
    361     @Test
    362     void testToConvex_lessThanPi() {
    363         // arrange
    364         final AngularInterval interval = AngularInterval.of(0, Angle.PI_OVER_TWO, TEST_PRECISION);
    365
    366         //act
    367         final List<AngularInterval.Convex> result = interval.toConvex();
    368
    369         // assert
    370         Assertions.assertEquals(1, result.size());
    371         checkInterval(interval, 0, Angle.PI_OVER_TWO);
    372     }
    373
    374     @Test
    375     void testToConvex_equalToPi() {
    376         // arrange
    377         final AngularInterval interval = AngularInterval.of(Math.PI, Angle.TWO_PI, TEST_PRECISION);
    378
    379         //act
    380         final List<AngularInterval.Convex> result = interval.toConvex();
    381
    382         // assert
    383         Assertions.assertEquals(1, result.size());
    384         checkInterval(interval, Math.PI, Angle.TWO_PI);
    385     }
    386
    387     @Test
    388     void testToConvex_overPi() {
    389         // arrange
    390         final AngularInterval interval = AngularInterval.of(Math.PI, Angle.PI_OVER_TWO, TEST_PRECISION);
    391
    392         // act
    393         final List<AngularInterval.Convex> result = interval.toConvex();
    394
    395         // assert
    396         Assertions.assertEquals(2, result.size());
    397         checkInterval(result.get(0), Math.PI, 1.75 * Math.PI);
    398         checkInterval(result.get(1), 1.75 * Math.PI, 2.5 * Math.PI);
    399     }
    400
    401     @Test
    402     void testToConvex_overPi_splitAtZero() {
    403         // arrange
    404         final AngularInterval interval = AngularInterval.of(1.25 * Math.PI, 2.75 * Math.PI, TEST_PRECISION);
    405
    406         // act
    407         final List<AngularInterval.Convex> result = interval.toConvex();
    408
    409         // assert
    410         Assertions.assertEquals(2, result.size());
    411         checkInterval(result.get(0), 1.25 * Math.PI, Angle.TWO_PI);
    412         checkInterval(result.get(1), Angle.TWO_PI, 2.75 * Math.PI);
    413     }
    414
    415     @Test
    416     void testSplit_full() {
    417         // arrange
    418         final AngularInterval interval = AngularInterval.full();
    419         final CutAngle pt = CutAngles.createNegativeFacing(Angle.PI_OVER_TWO, TEST_PRECISION);
    420
    421         // act
    422         final Split<RegionBSPTree1S> split = interval.split(pt);
    423
    424         // assert
    425         Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
    426
    427         final RegionBSPTree1S minus = split.getMinus();
    428         checkClassify(minus, RegionLocation.BOUNDARY, Point1S.of(Angle.PI_OVER_TWO));
    429         checkClassify(minus, RegionLocation.INSIDE,
    430                 Point1S.PI, Point1S.of(-Angle.PI_OVER_TWO), Point1S.of(-0.25 * Math.PI));
    431         checkClassify(minus, RegionLocation.OUTSIDE,
    432                 Point1S.ZERO, Point1S.of(0.25 * Math.PI));
    433
    434         final RegionBSPTree1S plus = split.getPlus();
    435         checkClassify(plus, RegionLocation.BOUNDARY, Point1S.of(Angle.PI_OVER_TWO));
    436         checkClassify(plus, RegionLocation.INSIDE,
    437                 Point1S.ZERO, Point1S.of(0.25 * Math.PI));
    438         checkClassify(plus, RegionLocation.OUTSIDE,
    439                 Point1S.PI, Point1S.of(-Angle.PI_OVER_TWO), Point1S.of(-0.25 * Math.PI));
    440     }
    441
    442     @Test
    443     void testSplit_interval_both() {
    444         // arrange
    445         final AngularInterval interval = AngularInterval.of(Angle.PI_OVER_TWO, Math.PI, TEST_PRECISION);
    446         final CutAngle cut = CutAngles.createNegativeFacing(0.75 * Math.PI, TEST_PRECISION);
    447
    448         // act
    449         final Split<RegionBSPTree1S> split = interval.split(cut);
    450
    451         // assert
    452         Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
    453
    454         final RegionBSPTree1S minus = split.getMinus();
    455         checkClassify(minus, RegionLocation.BOUNDARY, Point1S.of(Math.PI), cut.getPoint());
    456         checkClassify(minus, RegionLocation.INSIDE, Point1S.of(0.8 * Math.PI));
    457         checkClassify(minus, RegionLocation.OUTSIDE,
    458                 Point1S.ZERO, Point1S.of(Angle.TWO_PI), Point1S.of(-Angle.PI_OVER_TWO),
    459                 Point1S.of(0.7 * Math.PI));
    460
    461         final RegionBSPTree1S plus = split.getPlus();
    462         checkClassify(plus, RegionLocation.BOUNDARY, Point1S.of(Angle.PI_OVER_TWO), cut.getPoint());
    463         checkClassify(plus, RegionLocation.INSIDE, Point1S.of(0.6 * Math.PI));
    464         checkClassify(plus, RegionLocation.OUTSIDE,
    465                 Point1S.ZERO, Point1S.of(Angle.TWO_PI), Point1S.of(-Angle.PI_OVER_TWO),
    466                 Point1S.of(0.8 * Math.PI));
    467     }
    468
    469     @Test
    470     void testToString() {
    471         // arrange
    472         final AngularInterval interval = AngularInterval.of(1, 2, TEST_PRECISION);
    473
    474         // act
    475         final String str = interval.toString();
    476
    477         // assert
    478         Assertions.assertTrue(str.contains("AngularInterval"));
    479         Assertions.assertTrue(str.contains("min= 1.0"));
    480         Assertions.assertTrue(str.contains("max= 2.0"));
    481     }
    482
    483     @Test
    484     void testConvex_of_doubles() {
    485         // act/assert
    486         checkInterval(AngularInterval.Convex.of(0, 1, TEST_PRECISION), 0, 1);
    487         checkInterval(AngularInterval.Convex.of(0, Math.PI, TEST_PRECISION), 0, Math.PI);
    488         checkInterval(AngularInterval.Convex.of(Math.PI + 2, 1, TEST_PRECISION), Math.PI + 2, Angle.TWO_PI + 1);
    489         checkInterval(AngularInterval.Convex.of(-2, -1.5, TEST_PRECISION), -2, -1.5);
    490
    491         checkFull(AngularInterval.Convex.of(1, 1, TEST_PRECISION));
    492         checkFull(AngularInterval.Convex.of(0, 1e-11, TEST_PRECISION));
    493         checkFull(AngularInterval.Convex.of(0, -1e-11, TEST_PRECISION));
    494         checkFull(AngularInterval.Convex.of(0, Angle.TWO_PI, TEST_PRECISION));
    495     }
    496
    497     @Test
    498     void testConvex_of_doubles_invalidArgs() {
    499         // act/assert
    500         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, TEST_PRECISION));
    501         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(0, Math.PI + 1e-1, TEST_PRECISION));
    502         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(Angle.PI_OVER_TWO, -Angle.PI_OVER_TWO + 1, TEST_PRECISION));
    503         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(0, -0.5, TEST_PRECISION));
    504     }
    505
    506     @Test
    507     void testConvex_of_points() {
    508         // act/assert
    509         checkInterval(AngularInterval.Convex.of(Point1S.of(0), Point1S.of(1), TEST_PRECISION), 0, 1);
    510         checkInterval(AngularInterval.Convex.of(Point1S.of(0), Point1S.of(Math.PI), TEST_PRECISION),
    511                 0, Math.PI);
    512         checkInterval(AngularInterval.Convex.of(Point1S.of(Math.PI + 2), Point1S.of(1), TEST_PRECISION),
    513                 Math.PI + 2, Angle.TWO_PI + 1);
    514         checkInterval(AngularInterval.Convex.of(Point1S.of(-2), Point1S.of(-1.5), TEST_PRECISION), -2, -1.5);
    515
    516         checkFull(AngularInterval.Convex.of(Point1S.of(1), Point1S.of(1), TEST_PRECISION));
    517         checkFull(AngularInterval.Convex.of(Point1S.of(0), Point1S.of(1e-11), TEST_PRECISION));
    518         checkFull(AngularInterval.Convex.of(Point1S.of(0), Point1S.of(-1e-11), TEST_PRECISION));
    519         checkFull(AngularInterval.Convex.of(Point1S.of(0), Point1S.of(Angle.TWO_PI), TEST_PRECISION));
    520     }
    521
    522     @Test
    523     void testConvex_of_points_invalidArgs() {
    524         // act/assert
    525         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(Point1S.of(Double.NEGATIVE_INFINITY),
    526                 Point1S.of(Double.POSITIVE_INFINITY), TEST_PRECISION));
    527         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(Point1S.of(0), Point1S.of(Math.PI + 1e-1), TEST_PRECISION));
    528         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(Point1S.of(Angle.PI_OVER_TWO),
    529                 Point1S.of(-Angle.PI_OVER_TWO + 1), TEST_PRECISION));
    530         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(Point1S.of(0), Point1S.of(-0.5), TEST_PRECISION));
    531     }
    532
    533     @Test
    534     void testConvex_of_cutAngles() {
    535         // arrange
    536         final Precision.DoubleEquivalence precisionA = Precision.doubleEquivalenceOfEpsilon(1e-3);
    537         final Precision.DoubleEquivalence precisionB = Precision.doubleEquivalenceOfEpsilon(1e-2);
    538
    539         final CutAngle zeroPos = CutAngles.createPositiveFacing(Point1S.ZERO, precisionA);
    540         final CutAngle zeroNeg = CutAngles.createNegativeFacing(Point1S.ZERO, precisionA);
    541
    542         final CutAngle piPos = CutAngles.createPositiveFacing(Point1S.PI, precisionA);
    543         final CutAngle piNeg = CutAngles.createNegativeFacing(Point1S.PI, precisionA);
    544
    545         final CutAngle almostPiPos = CutAngles.createPositiveFacing(Point1S.of(Math.PI + 5e-3), precisionB);
    546
    547         // act/assert
    548         checkInterval(AngularInterval.Convex.of(zeroNeg, piPos), 0, Math.PI);
    549         checkInterval(AngularInterval.Convex.of(zeroPos, piNeg), Math.PI, Angle.TWO_PI);
    550
    551         checkFull(AngularInterval.Convex.of(zeroPos, zeroNeg));
    552         checkFull(AngularInterval.Convex.of(zeroPos, piPos));
    553         checkFull(AngularInterval.Convex.of(piNeg, zeroNeg));
    554
    555         checkFull(AngularInterval.Convex.of(almostPiPos, piNeg));
    556         checkFull(AngularInterval.Convex.of(piNeg, almostPiPos));
    557     }
    558
    559     @Test
    560     void testConvex_of_cutAngles_invalidArgs() {
    561         // arrange
    562         final CutAngle pt = CutAngles.createNegativeFacing(Point1S.ZERO, TEST_PRECISION);
    563         final CutAngle nan = CutAngles.createPositiveFacing(Point1S.NaN, TEST_PRECISION);
    564
    565         // act/assert
    566         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(pt, nan));
    567         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(nan, pt));
    568         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(nan, nan));
    569         Assertions.assertThrows(IllegalArgumentException.class, () -> AngularInterval.Convex.of(
    570                 CutAngles.createNegativeFacing(1, TEST_PRECISION),
    571                 CutAngles.createPositiveFacing(0.5, TEST_PRECISION)));
    572     }
    573
    574     @Test
    575     void testConvex_toConvex() {
    576         // arrange
    577         final AngularInterval.Convex full = AngularInterval.full();
    578         final AngularInterval.Convex interval = AngularInterval.Convex.of(0, 1, TEST_PRECISION);
    579
    580         List<AngularInterval.Convex> result;
    581
    582         // act/assert
    583         result = full.toConvex();
    584         Assertions.assertEquals(1, result.size());
    585         Assertions.assertSame(full, result.get(0));
    586
    587         result = interval.toConvex();
    588         Assertions.assertEquals(1, result.size());
    589         Assertions.assertSame(interval, result.get(0));
    590     }
    591
    592     @Test
    593     void testSplitDiameter_full() {
    594         // arrange
    595         final AngularInterval.Convex full = AngularInterval.full();
    596         final CutAngle splitter = CutAngles.createPositiveFacing(Point1S.of(Angle.PI_OVER_TWO), TEST_PRECISION);
    597
    598         // act
    599         final Split<AngularInterval.Convex> split = full.splitDiameter(splitter);
    600
    601         // assert
    602         Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
    603
    604         checkInterval(split.getMinus(), 1.5 * Math.PI, 2.5 * Math.PI);
    605         checkInterval(split.getPlus(), 0.5 * Math.PI, 1.5 * Math.PI);
    606     }
    607
    608     @Test
    609     void testSplitDiameter_full_splitOnZero() {
    610         // arrange
    611         final AngularInterval.Convex full = AngularInterval.full();
    612         final CutAngle splitter = CutAngles.createNegativeFacing(Point1S.ZERO, TEST_PRECISION);
    613
    614         // act
    615         final Split<AngularInterval.Convex> split = full.splitDiameter(splitter);
    616
    617         // assert
    618         Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
    619
    620         checkInterval(split.getMinus(), 0, Math.PI);
    621         checkInterval(split.getPlus(), Math.PI, Angle.TWO_PI);
    622     }
    623
    624     @Test
    625     void testSplitDiameter_minus() {
    626         // arrange
    627         final AngularInterval.Convex interval = AngularInterval.Convex.of(0.1, Angle.PI_OVER_TWO, TEST_PRECISION);
    628         final CutAngle splitter = CutAngles.createNegativeFacing(Point1S.ZERO, TEST_PRECISION);
    629
    630         // act
    631         final Split<AngularInterval.Convex> split = interval.splitDiameter(splitter);
    632
    633         // assert
    634         Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
    635
    636         Assertions.assertSame(interval, split.getMinus());
    637         Assertions.assertNull(split.getPlus());
    638     }
    639
    640     @Test
    641     void testSplitDiameter_plus() {
    642         // arrange
    643         final AngularInterval.Convex interval = AngularInterval.Convex.of(-0.4 * Math.PI, 0.4 * Math.PI, TEST_PRECISION);
    644         final CutAngle splitter = CutAngles.createNegativeFacing(Point1S.of(Angle.PI_OVER_TWO), TEST_PRECISION);
    645
    646         // act
    647         final Split<AngularInterval.Convex> split = interval.splitDiameter(splitter);
    648
    649         // assert
    650         Assertions.assertEquals(SplitLocation.PLUS, split.getLocation());
    651
    652         Assertions.assertNull(split.getMinus());
    653         Assertions.assertSame(interval, split.getPlus());
    654     }
    655
    656     @Test
    657     void testSplitDiameter_both_negativeFacingSplitter() {
    658         // arrange
    659         final AngularInterval.Convex interval = AngularInterval.Convex.of(Angle.PI_OVER_TWO, -Angle.PI_OVER_TWO, TEST_PRECISION);
    660         final CutAngle splitter = CutAngles.createNegativeFacing(Point1S.of(Math.PI), TEST_PRECISION);
    661
    662         // act
    663         final Split<AngularInterval.Convex> split = interval.splitDiameter(splitter);
    664
    665         // assert
    666         Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
    667
    668         checkInterval(split.getMinus(), Math.PI, 1.5 * Math.PI);
    669         checkInterval(split.getPlus(), Angle.PI_OVER_TWO, Math.PI);
    670     }
    671
    672     @Test
    673     void testSplitDiameter_both_positiveFacingSplitter() {
    674         // arrange
    675         final AngularInterval.Convex interval = AngularInterval.Convex.of(Angle.PI_OVER_TWO, -Angle.PI_OVER_TWO, TEST_PRECISION);
    676         final CutAngle splitter = CutAngles.createPositiveFacing(Point1S.of(Math.PI), TEST_PRECISION);
    677
    678         // act
    679         final Split<AngularInterval.Convex> split = interval.splitDiameter(splitter);
    680
    681         // assert
    682         Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
    683
    684         checkInterval(split.getMinus(), Angle.PI_OVER_TWO, Math.PI);
    685         checkInterval(split.getPlus(), Math.PI, 1.5 * Math.PI);
    686     }
    687
    688     @Test
    689     void testSplitDiameter_both_antipodal_negativeFacingSplitter() {
    690         // arrange
    691         final AngularInterval.Convex interval = AngularInterval.Convex.of(Angle.PI_OVER_TWO, -Angle.PI_OVER_TWO, TEST_PRECISION);
    692         final CutAngle splitter = CutAngles.createNegativeFacing(Point1S.ZERO, TEST_PRECISION);
    693
    694         // act
    695         final Split<AngularInterval.Convex> split = interval.splitDiameter(splitter);
    696
    697         // assert
    698         Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
    699
    700         checkInterval(split.getMinus(), Angle.PI_OVER_TWO, Math.PI);
    701         checkInterval(split.getPlus(), Math.PI, 1.5 * Math.PI);
    702     }
    703
    704     @Test
    705     void testSplitDiameter_both_antipodal_positiveFacingSplitter() {
    706         // arrange
    707         final AngularInterval.Convex interval = AngularInterval.Convex.of(Angle.PI_OVER_TWO, -Angle.PI_OVER_TWO, TEST_PRECISION);
    708         final CutAngle splitter = CutAngles.createPositiveFacing(Point1S.ZERO, TEST_PRECISION);
    709
    710         // act
    711         final Split<AngularInterval.Convex> split = interval.splitDiameter(splitter);
    712
    713         // assert
    714         Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
    715
    716         checkInterval(split.getMinus(), Math.PI, 1.5 * Math.PI);
    717         checkInterval(split.getPlus(), Angle.PI_OVER_TWO, Math.PI);
    718     }
    719
    720     @Test
    721     void testSplitDiameter_splitOnBoundary_negativeFacing() {
    722         // arrange
    723         final AngularInterval.Convex interval = AngularInterval.Convex.of(Angle.PI_OVER_TWO, -Angle.PI_OVER_TWO, TEST_PRECISION);
    724         final CutAngle splitter = CutAngles.createNegativeFacing(Point1S.of(Angle.PI_OVER_TWO), TEST_PRECISION);
    725
    726         // act
    727         final Split<AngularInterval.Convex> split = interval.splitDiameter(splitter);
    728
    729         // assert
    730         Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
    731
    732         Assertions.assertSame(interval, split.getMinus());
    733         Assertions.assertNull(split.getPlus());
    734     }
    735
    736     @Test
    737     void testSplitDiameter_splitOnBoundary_positiveFacing() {
    738         // arrange
    739         final AngularInterval.Convex interval = AngularInterval.Convex.of(0, Math.PI, TEST_PRECISION);
    740         final CutAngle splitter = CutAngles.createPositiveFacing(Point1S.of(Math.PI), TEST_PRECISION);
    741
    742         // act
    743         final Split<AngularInterval.Convex> split = interval.splitDiameter(splitter);
    744
    745         // assert
    746         Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
    747
    748         Assertions.assertSame(interval, split.getMinus());
    749         Assertions.assertNull(split.getPlus());
    750     }
    751
    752     @Test
    753     void testConvex_transform() {
    754         // arrange
    755         final AngularInterval.Convex interval = AngularInterval.Convex.of(Angle.PI_OVER_TWO, Math.PI, TEST_PRECISION);
    756
    757         final Transform1S rotate = Transform1S.createRotation(Angle.PI_OVER_TWO);
    758         final Transform1S invert = Transform1S.createNegation().rotate(Angle.PI_OVER_TWO);
    759
    760         // act/assert
    761         checkInterval(interval.transform(rotate), Math.PI, 1.5 * Math.PI);
    762         checkInterval(interval.transform(invert), -0.5 * Math.PI, 0.0);
    763     }
    764
    765     private static void checkFull(final AngularInterval interval) {
    766         Assertions.assertTrue(interval.isFull());
    767         Assertions.assertFalse(interval.isEmpty());
    768
    769         Assertions.assertNull(interval.getMinBoundary());
    770         Assertions.assertEquals(0, interval.getMin(), TEST_EPS);
    771         Assertions.assertNull(interval.getMaxBoundary());
    772         Assertions.assertEquals(Angle.TWO_PI, interval.getMax(), TEST_EPS);
    773
    774         Assertions.assertNull(interval.getCentroid());
    775         Assertions.assertNull(interval.getMidPoint());
    776
    777         Assertions.assertEquals(Angle.TWO_PI, interval.getSize(), TEST_EPS);
    778         Assertions.assertEquals(0, interval.getBoundarySize(), TEST_EPS);
    779
    780         checkClassify(interval, RegionLocation.INSIDE, Point1S.ZERO, Point1S.of(Math.PI));
    781     }
    782
    783     private static void checkInterval(final AngularInterval interval, final double min, final double max) {
    784
    785         Assertions.assertFalse(interval.isFull());
    786         Assertions.assertFalse(interval.isEmpty());
    787
    788         final CutAngle minBoundary = interval.getMinBoundary();
    789         Assertions.assertEquals(min, minBoundary.getAzimuth(), TEST_EPS);
    790         Assertions.assertFalse(minBoundary.isPositiveFacing());
    791
    792         final CutAngle maxBoundary = interval.getMaxBoundary();
    793         Assertions.assertEquals(max, maxBoundary.getAzimuth(), TEST_EPS);
    794         Assertions.assertTrue(maxBoundary.isPositiveFacing());
    795
    796         Assertions.assertEquals(min, interval.getMin(), TEST_EPS);
    797         Assertions.assertEquals(max, interval.getMax(), TEST_EPS);
    798
    799         Assertions.assertEquals(0.5 * (max + min), interval.getMidPoint().getAzimuth(), TEST_EPS);
    800         Assertions.assertSame(interval.getMidPoint(), interval.getCentroid());
    801
    802         Assertions.assertEquals(0, interval.getBoundarySize(), TEST_EPS);
    803         Assertions.assertEquals(max - min, interval.getSize(), TEST_EPS);
    804
    805         checkClassify(interval, RegionLocation.INSIDE, interval.getMidPoint());
    806         checkClassify(interval, RegionLocation.BOUNDARY,
    807                 interval.getMinBoundary().getPoint(), interval.getMaxBoundary().getPoint());
    808         checkClassify(interval, RegionLocation.OUTSIDE, Point1S.of(interval.getMidPoint().getAzimuth() + Math.PI));
    809     }
    810
    811     private static void checkClassify(final Region<Point1S> region, final RegionLocation loc, final Point1S... pts) {
    812         for (final Point1S pt : pts) {
    813             Assertions.assertEquals(loc, region.classify(pt), "Unexpected location for point " + pt);
    814         }
    815     }
    816 }