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