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