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.RegionLocation;
23  import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
24  import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
25  import org.apache.commons.geometry.core.partitioning.Split;
26  import org.apache.commons.geometry.spherical.SphericalTestUtils;
27  import org.apache.commons.numbers.angle.Angle;
28  import org.apache.commons.numbers.core.Precision;
29  import org.junit.jupiter.api.Assertions;
30  import org.junit.jupiter.api.Test;
31  
32  class CutAngleTest {
33  
34      private static final double TEST_EPS = 1e-10;
35  
36      private static final Precision.DoubleEquivalence TEST_PRECISION =
37              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
38  
39      @Test
40      void testFromAzimuthAndDirection() {
41          // act/assert
42          checkCutAngle(CutAngles.fromAzimuthAndDirection(0.0, true, TEST_PRECISION),
43                  0.0, true);
44          checkCutAngle(CutAngles.fromAzimuthAndDirection(Math.PI, true, TEST_PRECISION),
45                  Math.PI, true);
46          checkCutAngle(CutAngles.fromAzimuthAndDirection(-Angle.PI_OVER_TWO, true, TEST_PRECISION),
47                  -Angle.PI_OVER_TWO, true);
48  
49          checkCutAngle(CutAngles.fromAzimuthAndDirection(0.0, false, TEST_PRECISION),
50                  0.0, false);
51          checkCutAngle(CutAngles.fromAzimuthAndDirection(Math.PI, false, TEST_PRECISION),
52                  Math.PI, false);
53          checkCutAngle(CutAngles.fromAzimuthAndDirection(-Angle.PI_OVER_TWO, false, TEST_PRECISION),
54                  -Angle.PI_OVER_TWO, false);
55      }
56  
57      @Test
58      void testFromPointAndDirection() {
59          // arrange
60          final Point1S pt = Point1S.of(-Angle.PI_OVER_TWO);
61  
62          // act/assert
63          checkCutAngle(CutAngles.fromPointAndDirection(Point1S.ZERO, true, TEST_PRECISION),
64                  0.0, true);
65          checkCutAngle(CutAngles.fromPointAndDirection(Point1S.PI, true, TEST_PRECISION),
66                  Math.PI, true);
67          checkCutAngle(CutAngles.fromPointAndDirection(pt, true, TEST_PRECISION),
68                  -Angle.PI_OVER_TWO, true);
69  
70          checkCutAngle(CutAngles.fromPointAndDirection(Point1S.ZERO, false, TEST_PRECISION),
71                  0.0, false);
72          checkCutAngle(CutAngles.fromPointAndDirection(Point1S.PI, false, TEST_PRECISION),
73                  Math.PI, false);
74          checkCutAngle(CutAngles.fromPointAndDirection(pt, false, TEST_PRECISION),
75                  -Angle.PI_OVER_TWO, false);
76      }
77  
78      @Test
79      void testCreatePositiveFacing() {
80          // act/assert
81          checkCutAngle(CutAngles.createPositiveFacing(Point1S.ZERO, TEST_PRECISION),
82                  0.0, true);
83          checkCutAngle(CutAngles.createPositiveFacing(Point1S.PI, TEST_PRECISION),
84                  Math.PI, true);
85          checkCutAngle(CutAngles.createPositiveFacing(-Angle.PI_OVER_TWO, TEST_PRECISION),
86                  -Angle.PI_OVER_TWO, true);
87      }
88  
89      @Test
90      void testCreateNegativeFacing() {
91          // act/assert
92          checkCutAngle(CutAngles.createNegativeFacing(Point1S.ZERO, TEST_PRECISION),
93                  0.0, false);
94          checkCutAngle(CutAngles.createNegativeFacing(Point1S.PI, TEST_PRECISION),
95                  Math.PI, false);
96          checkCutAngle(CutAngles.createNegativeFacing(-Angle.PI_OVER_TWO, TEST_PRECISION),
97                  -Angle.PI_OVER_TWO, false);
98      }
99  
100     @Test
101     void testOffset() {
102         // arrange
103         final CutAngle zeroPos = CutAngles.createPositiveFacing(0.0, TEST_PRECISION);
104         final CutAngle zeroNeg = CutAngles.createNegativeFacing(0.0, TEST_PRECISION);
105         final CutAngle negPiPos = CutAngles.createPositiveFacing(-Math.PI, TEST_PRECISION);
106 
107         final CutAngle piNeg = CutAngles.createNegativeFacing(Math.PI, TEST_PRECISION);
108         final CutAngle twoAndAHalfPiPos = CutAngles.createPositiveFacing(2.5 * Math.PI, TEST_PRECISION);
109 
110         // act/assert
111         checkOffset(zeroPos, 0, 0);
112         checkOffset(zeroPos, Angle.TWO_PI, 0);
113         checkOffset(zeroPos, 2.5 * Math.PI, Angle.PI_OVER_TWO);
114         checkOffset(zeroPos, Math.PI, Math.PI);
115         checkOffset(zeroPos, 3.5 * Math.PI, 1.5 * Math.PI);
116 
117         checkOffset(zeroNeg, 0, 0);
118         checkOffset(zeroNeg, Angle.TWO_PI, 0);
119         checkOffset(zeroNeg, 2.5 * Math.PI, -Angle.PI_OVER_TWO);
120         checkOffset(zeroNeg, Math.PI, -Math.PI);
121         checkOffset(zeroNeg, 3.5 * Math.PI, -1.5 * Math.PI);
122 
123         checkOffset(negPiPos, 0, -Math.PI);
124         checkOffset(negPiPos, Angle.TWO_PI, -Math.PI);
125         checkOffset(negPiPos, 2.5 * Math.PI, -Angle.PI_OVER_TWO);
126         checkOffset(negPiPos, Math.PI, 0);
127         checkOffset(negPiPos, 3.5 * Math.PI, Angle.PI_OVER_TWO);
128 
129         checkOffset(piNeg, 0, Math.PI);
130         checkOffset(piNeg, Angle.TWO_PI, Math.PI);
131         checkOffset(piNeg, 2.5 * Math.PI, Angle.PI_OVER_TWO);
132         checkOffset(piNeg, Math.PI, 0);
133         checkOffset(piNeg, 3.5 * Math.PI, -Angle.PI_OVER_TWO);
134 
135         checkOffset(twoAndAHalfPiPos, 0, -Angle.PI_OVER_TWO);
136         checkOffset(twoAndAHalfPiPos, Angle.TWO_PI, -Angle.PI_OVER_TWO);
137         checkOffset(twoAndAHalfPiPos, 2.5 * Math.PI, 0);
138         checkOffset(twoAndAHalfPiPos, Math.PI, Angle.PI_OVER_TWO);
139         checkOffset(twoAndAHalfPiPos, 3.5 * Math.PI, Math.PI);
140     }
141 
142     @Test
143     void testClassify() {
144         // arrange
145         final CutAngle zeroPos = CutAngles.createPositiveFacing(0.0, TEST_PRECISION);
146         final CutAngle zeroNeg = CutAngles.createNegativeFacing(0.0, TEST_PRECISION);
147         final CutAngle negPiPos = CutAngles.createPositiveFacing(-Math.PI, TEST_PRECISION);
148 
149         // act/assert
150         checkClassify(zeroPos, HyperplaneLocation.ON,
151                 0, 1e-16, -1e-16,
152                 Angle.TWO_PI - 1e-11, Angle.TWO_PI + 1e-11);
153         checkClassify(zeroPos, HyperplaneLocation.PLUS,
154                 0.5, 2.5 * Math.PI,
155                 -0.5, -Angle.PI_OVER_TWO);
156 
157         checkClassify(zeroNeg, HyperplaneLocation.ON,
158                 0, 1e-16, -1e-16,
159                 Angle.TWO_PI - 1e-11, Angle.TWO_PI + 1e-11);
160         checkClassify(zeroNeg, HyperplaneLocation.MINUS,
161                 0.5, 2.5 * Math.PI,
162                 -0.5, -Angle.PI_OVER_TWO);
163 
164         checkClassify(negPiPos, HyperplaneLocation.ON, Math.PI, Math.PI + 1e-11);
165         checkClassify(negPiPos, HyperplaneLocation.MINUS, 0.5, 2.5 * Math.PI,
166                 0, 1e-11, Angle.TWO_PI, Angle.TWO_PI - 1e-11);
167         checkClassify(negPiPos, HyperplaneLocation.PLUS, -0.5, -Angle.PI_OVER_TWO);
168     }
169 
170     @Test
171     void testContains() {
172         // arrange
173         final CutAngle pt = CutAngles.createNegativeFacing(Angle.PI_OVER_TWO, TEST_PRECISION);
174 
175         // act/assert
176         Assertions.assertFalse(pt.contains(Point1S.ZERO));
177         Assertions.assertFalse(pt.contains(Point1S.of(Angle.TWO_PI)));
178 
179         Assertions.assertFalse(pt.contains(Point1S.of(Math.PI)));
180         Assertions.assertFalse(pt.contains(Point1S.of(0.25 * Math.PI)));
181         Assertions.assertFalse(pt.contains(Point1S.of(-0.25 * Math.PI)));
182 
183         Assertions.assertTrue(pt.contains(Point1S.of(Angle.PI_OVER_TWO)));
184         Assertions.assertTrue(pt.contains(Point1S.of(Angle.PI_OVER_TWO + 1e-11)));
185         Assertions.assertTrue(pt.contains(Point1S.of(2.5 * Math.PI)));
186         Assertions.assertTrue(pt.contains(Point1S.of(-3.5 * Math.PI)));
187     }
188 
189     @Test
190     void testReverse() {
191         // arrange
192         final CutAngle pt = CutAngles.createNegativeFacing(Angle.PI_OVER_TWO, TEST_PRECISION);
193 
194         // act
195         final CutAngle result = pt.reverse();
196 
197         // assert
198         checkCutAngle(result, Angle.PI_OVER_TWO, true);
199         Assertions.assertSame(TEST_PRECISION, result.getPrecision());
200 
201         checkCutAngle(result.reverse(), Angle.PI_OVER_TWO, false);
202     }
203 
204     @Test
205     void testProject() {
206         // arrange
207         final CutAngle pt = CutAngles.createNegativeFacing(Angle.PI_OVER_TWO, TEST_PRECISION);
208 
209         // act/assert
210         for (double az = -Angle.TWO_PI; az <= Angle.TWO_PI; az += 0.2) {
211             Assertions.assertEquals(Angle.PI_OVER_TWO, pt.project(Point1S.of(az)).getAzimuth(), TEST_EPS);
212         }
213     }
214 
215     @Test
216     void testSimilarOrientation() {
217         // arrange
218         final CutAngle a = CutAngles.createPositiveFacing(0.0, TEST_PRECISION);
219         final CutAngle b = CutAngles.createNegativeFacing(0.0, TEST_PRECISION);
220         final CutAngle c = CutAngles.createPositiveFacing(-Angle.PI_OVER_TWO, TEST_PRECISION);
221 
222         // act/assert
223         Assertions.assertTrue(a.similarOrientation(a));
224         Assertions.assertFalse(a.similarOrientation(b));
225         Assertions.assertTrue(a.similarOrientation(c));
226     }
227 
228     @Test
229     void testTransform_rotate() {
230         // arrange
231         final Transform1S transform = Transform1S.createRotation(Angle.PI_OVER_TWO);
232 
233         // act
234         checkCutAngle(CutAngles.fromPointAndDirection(Point1S.ZERO, true, TEST_PRECISION).transform(transform),
235                 Angle.PI_OVER_TWO, true);
236         checkCutAngle(CutAngles.fromPointAndDirection(Point1S.ZERO, false, TEST_PRECISION).transform(transform),
237                 Angle.PI_OVER_TWO, false);
238 
239         checkCutAngle(CutAngles.fromPointAndDirection(Point1S.of(1.5 * Math.PI), true, TEST_PRECISION).transform(transform),
240                 Angle.TWO_PI, true);
241         checkCutAngle(CutAngles.fromPointAndDirection(Point1S.of(-Angle.PI_OVER_TWO), false, TEST_PRECISION).transform(transform),
242                 0.0, false);
243     }
244 
245     @Test
246     void testTransform_negate() {
247         // arrange
248         final Transform1S transform = Transform1S.createNegation();
249 
250         // act
251         checkCutAngle(CutAngles.fromPointAndDirection(Point1S.ZERO, true, TEST_PRECISION).transform(transform),
252                 0.0, false);
253         checkCutAngle(CutAngles.fromPointAndDirection(Point1S.ZERO, false, TEST_PRECISION).transform(transform),
254                 0.0, true);
255 
256         checkCutAngle(CutAngles.fromPointAndDirection(Point1S.of(1.5 * Math.PI), true, TEST_PRECISION).transform(transform),
257                 -1.5 * Math.PI, false);
258         checkCutAngle(CutAngles.fromPointAndDirection(Point1S.of(-Angle.PI_OVER_TWO), false, TEST_PRECISION).transform(transform),
259                 Angle.PI_OVER_TWO, true);
260     }
261 
262     @Test
263     void testSpan() {
264         // arrange
265         final CutAngle pt = CutAngles.fromPointAndDirection(Point1S.of(1.0), false, TEST_PRECISION);
266 
267         // act
268         final HyperplaneConvexSubset<Point1S> result = pt.span();
269 
270         // assert
271         Assertions.assertSame(pt, result.getHyperplane());
272     }
273 
274     @Test
275     void testEq() {
276         // arrange
277         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-3);
278 
279         final CutAngle a = CutAngles.fromPointAndDirection(Point1S.ZERO, true, precision);
280 
281         final CutAngle b = CutAngles.fromPointAndDirection(Point1S.PI, true, precision);
282         final CutAngle c = CutAngles.fromPointAndDirection(Point1S.ZERO, false, precision);
283         final CutAngle d = CutAngles.fromPointAndDirection(Point1S.ZERO, true, TEST_PRECISION);
284 
285         final CutAngle e = CutAngles.fromPointAndDirection(Point1S.ZERO, true, precision);
286         final CutAngle f = CutAngles.fromPointAndDirection(Point1S.of(Angle.TWO_PI), true, precision);
287         final CutAngle g = CutAngles.fromPointAndDirection(Point1S.of(1e-4), true, precision);
288         final CutAngle h = CutAngles.fromPointAndDirection(Point1S.of(-1e-4), true, precision);
289 
290         // act/assert
291         Assertions.assertTrue(a.eq(a, precision));
292 
293         Assertions.assertFalse(a.eq(b, precision));
294         Assertions.assertFalse(a.eq(c, precision));
295 
296         Assertions.assertTrue(a.eq(d, precision));
297         Assertions.assertTrue(a.eq(e, precision));
298         Assertions.assertTrue(a.eq(f, precision));
299         Assertions.assertTrue(a.eq(g, precision));
300         Assertions.assertTrue(a.eq(h, precision));
301     }
302 
303     @Test
304     void testHashCode() {
305         // arrange
306         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-3);
307 
308         final CutAngle a = CutAngles.fromPointAndDirection(Point1S.ZERO, true, TEST_PRECISION);
309         final CutAngle b = CutAngles.fromPointAndDirection(Point1S.PI, true, TEST_PRECISION);
310         final CutAngle c = CutAngles.fromPointAndDirection(Point1S.ZERO, false, TEST_PRECISION);
311         final CutAngle d = CutAngles.fromPointAndDirection(Point1S.ZERO, true, precision);
312         final CutAngle e = CutAngles.fromPointAndDirection(Point1S.ZERO, true, TEST_PRECISION);
313 
314         final int hash = a.hashCode();
315 
316         // act/assert
317         Assertions.assertEquals(hash, a.hashCode());
318 
319         Assertions.assertNotEquals(hash, b.hashCode());
320         Assertions.assertNotEquals(hash, c.hashCode());
321         Assertions.assertNotEquals(hash, d.hashCode());
322 
323         Assertions.assertEquals(hash, e.hashCode());
324     }
325 
326     @Test
327     void testEquals() {
328         // arrange
329         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-3);
330 
331         final CutAngle a = CutAngles.fromPointAndDirection(Point1S.ZERO, true, TEST_PRECISION);
332         final CutAngle b = CutAngles.fromPointAndDirection(Point1S.PI, true, TEST_PRECISION);
333         final CutAngle c = CutAngles.fromPointAndDirection(Point1S.ZERO, false, TEST_PRECISION);
334         final CutAngle d = CutAngles.fromPointAndDirection(Point1S.ZERO, true, precision);
335         final CutAngle e = CutAngles.fromPointAndDirection(Point1S.ZERO, true, TEST_PRECISION);
336 
337         // act/assert
338         GeometryTestUtils.assertSimpleEqualsCases(a);
339 
340         Assertions.assertNotEquals(a, b);
341         Assertions.assertNotEquals(a, c);
342         Assertions.assertNotEquals(a, d);
343 
344         Assertions.assertEquals(a, e);
345     }
346 
347     @Test
348     void testToString() {
349         // arrange
350         final CutAngle pt = CutAngles.createPositiveFacing(0.0, TEST_PRECISION);
351 
352         // act
353         final String str = pt.toString();
354 
355         // assert
356         Assertions.assertTrue(str.startsWith("CutAngle["));
357         Assertions.assertTrue(str.contains("point= ") && str.contains("positiveFacing= "));
358     }
359 
360     @Test
361     void testSubset_split() {
362         // arrange
363         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-3);
364 
365         final CutAngle pt = CutAngles.createPositiveFacing(-1.5, precision);
366         final HyperplaneConvexSubset<Point1S> sub = pt.span();
367 
368         // act/assert
369         checkSplit(sub, CutAngles.createPositiveFacing(1.0, precision), false, true);
370         checkSplit(sub, CutAngles.createPositiveFacing(-1.5 + 1e-2, precision), true, false);
371 
372         checkSplit(sub, CutAngles.createNegativeFacing(1.0, precision), true, false);
373         checkSplit(sub, CutAngles.createNegativeFacing(-1.5 + 1e-2, precision), false, true);
374 
375         checkSplit(sub, CutAngles.createNegativeFacing(-1.5, precision), false, false);
376         checkSplit(sub, CutAngles.createNegativeFacing(-1.5 + 1e-4, precision), false, false);
377         checkSplit(sub, CutAngles.createNegativeFacing(-1.5 - 1e-4, precision), false, false);
378     }
379 
380     private void checkSplit(final HyperplaneConvexSubset<Point1S> sub, final CutAngle splitter, final boolean minus, final boolean plus) {
381         final Split<? extends HyperplaneConvexSubset<Point1S>> split = sub.split(splitter);
382 
383         Assertions.assertSame(minus ? sub : null, split.getMinus());
384         Assertions.assertSame(plus ? sub : null, split.getPlus());
385     }
386 
387     @Test
388     void testSubset_simpleMethods() {
389         // arrange
390         final CutAngle pt = CutAngles.createPositiveFacing(1, TEST_PRECISION);
391         final HyperplaneConvexSubset<Point1S> sub = pt.span();
392 
393         // act/assert
394         Assertions.assertSame(pt, sub.getHyperplane());
395         Assertions.assertFalse(sub.isFull());
396         Assertions.assertFalse(sub.isEmpty());
397         Assertions.assertFalse(sub.isInfinite());
398         Assertions.assertTrue(sub.isFinite());
399         Assertions.assertEquals(0.0, sub.getSize(), TEST_EPS);
400         SphericalTestUtils.assertPointsEqual(Point1S.of(1), sub.getCentroid(), TEST_EPS);
401 
402         final List<? extends HyperplaneConvexSubset<Point1S>> list = sub.toConvex();
403         Assertions.assertEquals(1, list.size());
404         Assertions.assertSame(sub, list.get(0));
405     }
406 
407     @Test
408     void testSubset_classify() {
409         // arrange
410         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-1);
411         final CutAngle pt = CutAngles.createPositiveFacing(1, precision);
412         final HyperplaneConvexSubset<Point1S> sub = pt.span();
413 
414         // act/assert
415         Assertions.assertEquals(RegionLocation.BOUNDARY, sub.classify(Point1S.of(0.95)));
416         Assertions.assertEquals(RegionLocation.BOUNDARY, sub.classify(Point1S.of(1)));
417         Assertions.assertEquals(RegionLocation.BOUNDARY, sub.classify(Point1S.of(1.05)));
418 
419         Assertions.assertEquals(RegionLocation.OUTSIDE, sub.classify(Point1S.of(1.11)));
420         Assertions.assertEquals(RegionLocation.OUTSIDE, sub.classify(Point1S.of(0.89)));
421 
422         Assertions.assertEquals(RegionLocation.OUTSIDE, sub.classify(Point1S.of(-3)));
423         Assertions.assertEquals(RegionLocation.OUTSIDE, sub.classify(Point1S.of(10)));
424     }
425 
426     @Test
427     void testSubset_contains() {
428         // arrange
429         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-1);
430         final CutAngle pt = CutAngles.createPositiveFacing(1, precision);
431         final HyperplaneConvexSubset<Point1S> sub = pt.span();
432 
433         // act/assert
434         Assertions.assertTrue(sub.contains(Point1S.of(0.95)));
435         Assertions.assertTrue(sub.contains(Point1S.of(1)));
436         Assertions.assertTrue(sub.contains(Point1S.of(1.05)));
437 
438         Assertions.assertFalse(sub.contains(Point1S.of(1.11)));
439         Assertions.assertFalse(sub.contains(Point1S.of(0.89)));
440 
441         Assertions.assertFalse(sub.contains(Point1S.of(-3)));
442         Assertions.assertFalse(sub.contains(Point1S.of(10)));
443     }
444 
445     @Test
446     void testSubset_closestContained() {
447         // arrange
448         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-1);
449         final CutAngle pt = CutAngles.createPositiveFacing(1, precision);
450         final HyperplaneConvexSubset<Point1S> sub = pt.span();
451 
452         final Point1S expected = Point1S.of(1);
453 
454         // act/assert
455         Assertions.assertEquals(expected, sub.closest(Point1S.ZERO));
456         Assertions.assertEquals(expected, sub.closest(Point1S.of(Angle.PI_OVER_TWO)));
457         Assertions.assertEquals(expected, sub.closest(Point1S.PI));
458         Assertions.assertEquals(expected, sub.closest(Point1S.of(-Angle.PI_OVER_TWO)));
459         Assertions.assertEquals(expected, sub.closest(Point1S.of(Angle.TWO_PI)));
460     }
461 
462     @Test
463     void testSubset_transform() {
464         // arrange
465         final CutAngle pt = CutAngles.fromPointAndDirection(Point1S.of(Angle.PI_OVER_TWO), true, TEST_PRECISION);
466 
467         final Transform1S transform = Transform1S.createNegation().rotate(Math.PI);
468 
469         // act
470         final HyperplaneConvexSubset<Point1S> result = pt.span().transform(transform);
471 
472         // assert
473         checkCutAngle((CutAngle) result.getHyperplane(), Angle.PI_OVER_TWO, false);
474     }
475 
476     @Test
477     void testSubset_reverse() {
478         // arrange
479         final CutAngle pt = CutAngles.createPositiveFacing(2.0, TEST_PRECISION);
480         final HyperplaneConvexSubset<Point1S> sub = pt.span();
481 
482         // act
483         final HyperplaneConvexSubset<Point1S> result = sub.reverse();
484 
485         // assert
486         Assertions.assertEquals(2.0, ((CutAngle) result.getHyperplane()).getAzimuth(), TEST_EPS);
487         Assertions.assertFalse(((CutAngle) result.getHyperplane()).isPositiveFacing());
488 
489         Assertions.assertEquals(sub.getHyperplane(), result.reverse().getHyperplane());
490     }
491 
492     @Test
493     void testSubset_toString() {
494         // arrange
495         final CutAngle pt = CutAngles.createPositiveFacing(2, TEST_PRECISION);
496         final HyperplaneConvexSubset<Point1S> sub = pt.span();
497 
498         // act
499         final String str = sub.toString();
500 
501         //assert
502         Assertions.assertTrue(str.contains("CutAngleConvexSubset["));
503         Assertions.assertTrue(str.contains("point= "));
504         Assertions.assertTrue(str.contains("positiveFacing= "));
505     }
506 
507     private static void checkCutAngle(final CutAngle angle, final double az, final boolean positiveFacing) {
508         checkCutAngle(angle, az, positiveFacing, TEST_PRECISION);
509     }
510 
511     private static void checkCutAngle(final CutAngle angle, final double az, final boolean positiveFacing, final Precision.DoubleEquivalence precision) {
512         Assertions.assertEquals(az, angle.getAzimuth(), TEST_EPS);
513         Assertions.assertEquals(Angle.Rad.WITHIN_0_AND_2PI.applyAsDouble(az), angle.getNormalizedAzimuth(), TEST_EPS);
514         Assertions.assertEquals(az, angle.getPoint().getAzimuth(), TEST_EPS);
515         Assertions.assertEquals(positiveFacing, angle.isPositiveFacing());
516 
517         Assertions.assertSame(precision, angle.getPrecision());
518     }
519 
520     private static void checkOffset(final CutAngle pt, final double az, final double offset) {
521         Assertions.assertEquals(offset, pt.offset(Point1S.of(az)), TEST_EPS);
522     }
523 
524     private static void checkClassify(final CutAngle pt, final HyperplaneLocation loc, final double... azimuths) {
525         for (final double az : azimuths) {
526             Assertions.assertEquals(loc, pt.classify(Point1S.of(az)), "Unexpected location for azimuth " + az);
527         }
528     }
529 }