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 }