1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.hipparchus.geometry.spherical.twod;
23
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.function.IntPredicate;
30
31 import org.hipparchus.exception.LocalizedCoreFormats;
32 import org.hipparchus.exception.MathIllegalArgumentException;
33 import org.hipparchus.exception.MathIllegalStateException;
34 import org.hipparchus.geometry.LocalizedGeometryFormats;
35 import org.hipparchus.geometry.enclosing.EnclosingBall;
36 import org.hipparchus.geometry.enclosing.WelzlEncloser;
37 import org.hipparchus.geometry.euclidean.threed.Euclidean3D;
38 import org.hipparchus.geometry.euclidean.threed.Rotation;
39 import org.hipparchus.geometry.euclidean.threed.RotationConvention;
40 import org.hipparchus.geometry.euclidean.threed.SphereGenerator;
41 import org.hipparchus.geometry.euclidean.threed.Vector3D;
42 import org.hipparchus.geometry.partitioning.AbstractRegion;
43 import org.hipparchus.geometry.partitioning.BSPTree;
44 import org.hipparchus.geometry.partitioning.BoundaryProjection;
45 import org.hipparchus.geometry.partitioning.RegionFactory;
46 import org.hipparchus.geometry.partitioning.SubHyperplane;
47 import org.hipparchus.geometry.spherical.oned.Arc;
48 import org.hipparchus.geometry.spherical.oned.Sphere1D;
49 import org.hipparchus.util.FastMath;
50 import org.hipparchus.util.MathUtils;
51 import org.hipparchus.util.Precision;
52
53
54
55 public class SphericalPolygonsSet extends AbstractRegion<Sphere2D, Sphere1D> {
56
57
58 private List<Vertex> loops;
59
60
61
62
63
64 public SphericalPolygonsSet(final double tolerance) throws MathIllegalArgumentException {
65 super(tolerance);
66 Sphere2D.checkTolerance(tolerance);
67 }
68
69
70
71
72
73
74 public SphericalPolygonsSet(final Vector3D pole, final double tolerance)
75 throws MathIllegalArgumentException {
76 super(new BSPTree<Sphere2D>(new Circle(pole, tolerance).wholeHyperplane(),
77 new BSPTree<Sphere2D>(Boolean.FALSE),
78 new BSPTree<Sphere2D>(Boolean.TRUE),
79 null),
80 tolerance);
81 Sphere2D.checkTolerance(tolerance);
82 }
83
84
85
86
87
88
89
90
91
92 public SphericalPolygonsSet(final Vector3D center, final Vector3D meridian,
93 final double outsideRadius, final int n,
94 final double tolerance)
95 throws MathIllegalArgumentException {
96 this(tolerance, createRegularPolygonVertices(center, meridian, outsideRadius, n));
97 }
98
99
100
101
102
103
104
105
106
107
108
109
110 public SphericalPolygonsSet(final BSPTree<Sphere2D> tree, final double tolerance)
111 throws MathIllegalArgumentException {
112 super(tree, tolerance);
113 Sphere2D.checkTolerance(tolerance);
114 }
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138 public SphericalPolygonsSet(final Collection<SubHyperplane<Sphere2D>> boundary, final double tolerance)
139 throws MathIllegalArgumentException {
140 super(boundary, tolerance);
141 Sphere2D.checkTolerance(tolerance);
142 }
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 public SphericalPolygonsSet(final double hyperplaneThickness, final S2Point ... vertices)
189 throws MathIllegalArgumentException {
190 super(verticesToTree(hyperplaneThickness, vertices), hyperplaneThickness);
191 Sphere2D.checkTolerance(hyperplaneThickness);
192 }
193
194
195
196
197
198
199
200
201 private static S2Point[] createRegularPolygonVertices(final Vector3D center, final Vector3D meridian,
202 final double outsideRadius, final int n) {
203 final S2Point[] array = new S2Point[n];
204 final Rotation r0 = new Rotation(Vector3D.crossProduct(center, meridian),
205 outsideRadius, RotationConvention.VECTOR_OPERATOR);
206 array[0] = new S2Point(r0.applyTo(center));
207
208 final Rotation r = new Rotation(center, MathUtils.TWO_PI / n, RotationConvention.VECTOR_OPERATOR);
209 for (int i = 1; i < n; ++i) {
210 array[i] = new S2Point(r.applyTo(array[i - 1].getVector()));
211 }
212
213 return array;
214 }
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234 private static BSPTree<Sphere2D> verticesToTree(final double hyperplaneThickness,
235 S2Point ... vertices) {
236
237 vertices = reduce(hyperplaneThickness, vertices).toArray(new S2Point[0]);
238 final int n = vertices.length;
239 if (n == 0) {
240
241 return new BSPTree<Sphere2D>(Boolean.TRUE);
242 }
243
244
245 final Vertex[] vArray = new Vertex[n];
246 for (int i = 0; i < n; ++i) {
247 vArray[i] = new Vertex(vertices[i]);
248 }
249
250
251 final List<Edge> edges = new ArrayList<>(n);
252 Vertex end = vArray[n - 1];
253 for (int i = 0; i < n; ++i) {
254
255
256 final Vertex start = end;
257 end = vArray[i];
258
259
260 final Circle circle = new Circle(start.getLocation(), end.getLocation(), hyperplaneThickness);
261
262
263 edges.add(new Edge(start, end,
264 Vector3D.angle(start.getLocation().getVector(),
265 end.getLocation().getVector()),
266 circle));
267
268 }
269
270
271 final BSPTree<Sphere2D> tree = new BSPTree<>();
272 insertEdges(hyperplaneThickness, tree, edges);
273
274 return tree;
275
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289 private static List<S2Point> reduce(final double hyperplaneThickness,
290 final S2Point[] vertices) {
291 final int n = vertices.length;
292 if (n <= 3) {
293
294 return Arrays.asList(vertices.clone());
295 }
296 final List<S2Point> points = new ArrayList<>();
297
298
299
300
301
302
303
304
305 final IntPredicate onCircleBackward = j -> {
306 final int i = n - 2 - j;
307
308 final Circle circle = new Circle(vertices[0], vertices[i], hyperplaneThickness);
309 final Arc arc = circle.getArc(vertices[0], vertices[i]);
310 if (arc.getSize() >= FastMath.PI) {
311 return false;
312 }
313 for (int k = i + 1; k < n; k++) {
314 final S2Point vertex = vertices[k];
315 if (FastMath.abs(circle.getOffset(vertex)) > hyperplaneThickness ||
316 arc.getOffset(circle.toSubSpace(vertex)) > 0) {
317
318 return false;
319 }
320 }
321 return true;
322 };
323
324 int last = n - 2 - searchHelper(onCircleBackward, 0, n - 2);
325 if (last > 1) {
326 points.add(vertices[last]);
327 } else {
328
329
330 return Arrays.asList(Arrays.copyOfRange(vertices, 0, 3));
331 }
332 final int first = last;
333
334 for (int j = 1; ; j += 2) {
335 final int lastFinal = last;
336 final IntPredicate onCircle = i -> {
337
338 final Circle circle = new Circle(vertices[lastFinal], vertices[i], hyperplaneThickness);
339 final Arc arc = circle.getArc(vertices[lastFinal], vertices[i]);
340 if (arc.getSize() >= FastMath.PI) {
341 return false;
342 }
343 final int end = lastFinal < i ? i : i + n;
344 for (int k = lastFinal + 1; k < end; k++) {
345 final S2Point vertex = vertices[k % n];
346 if (FastMath.abs(circle.getOffset(vertex)) > hyperplaneThickness ||
347 arc.getOffset(circle.toSubSpace(vertex)) > 0) {
348
349 return false;
350 }
351 }
352 return true;
353 };
354 j = searchHelper(onCircle, j, first + 1);
355 if (j >= first) {
356 break;
357 }
358 last = j;
359 points.add(vertices[last]);
360 }
361
362 final S2Point swap = points.remove(0);
363 points.add(swap);
364 return points;
365 }
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386 private static int searchHelper(final IntPredicate predicate,
387 final int a,
388 final int b) {
389 if (a > b) {
390 throw new MathIllegalArgumentException(
391 LocalizedCoreFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, a, b);
392 }
393
394 if (a == b) {
395 return a;
396 }
397 if (!predicate.test(a)) {
398 return a - 1;
399 }
400
401
402 int start = a;
403 int end = b;
404 for (int i = 2; a + i < b; i *= 2) {
405 if (predicate.test(a + i)) {
406
407 start = a + i;
408 } else {
409
410 end = a + i;
411 break;
412 }
413 }
414
415
416
417 int low = start;
418 int high = end - 1;
419 while (low <= high) {
420 final int mid = (low + high) >>> 1;
421 if (predicate.test(mid)) {
422 low = mid + 1;
423 } else {
424 high = mid - 1;
425 }
426 }
427
428 return low - 1;
429 }
430
431
432
433
434
435
436
437
438
439 private static void insertEdges(final double hyperplaneThickness,
440 final BSPTree<Sphere2D> node,
441 final List<Edge> edges) {
442
443
444 int index = 0;
445 Edge inserted = null;
446 while (inserted == null && index < edges.size()) {
447 inserted = edges.get(index++);
448 if (!node.insertCut(inserted.getCircle())) {
449 inserted = null;
450 }
451 }
452
453 if (inserted == null) {
454
455
456 final BSPTree<Sphere2D> parent = node.getParent();
457 if (parent == null || node == parent.getMinus()) {
458 node.setAttribute(Boolean.TRUE);
459 } else {
460 node.setAttribute(Boolean.FALSE);
461 }
462 return;
463 }
464
465
466
467 final List<Edge> outsideList = new ArrayList<>();
468 final List<Edge> insideList = new ArrayList<>();
469 for (final Edge edge : edges) {
470 if (edge != inserted) {
471 edge.split(inserted.getCircle(), outsideList, insideList);
472 }
473 }
474
475
476 if (!outsideList.isEmpty()) {
477 insertEdges(hyperplaneThickness, node.getPlus(), outsideList);
478 } else {
479 node.getPlus().setAttribute(Boolean.FALSE);
480 }
481 if (!insideList.isEmpty()) {
482 insertEdges(hyperplaneThickness, node.getMinus(), insideList);
483 } else {
484 node.getMinus().setAttribute(Boolean.TRUE);
485 }
486
487 }
488
489
490 @Override
491 public SphericalPolygonsSet buildNew(final BSPTree<Sphere2D> tree) {
492 return new SphericalPolygonsSet(tree, getTolerance());
493 }
494
495
496
497
498
499 @Override
500 protected void computeGeometricalProperties() throws MathIllegalStateException {
501
502 final BSPTree<Sphere2D> tree = getTree(true);
503
504 if (tree.getCut() == null) {
505
506
507
508 if (tree.getCut() == null && (Boolean) tree.getAttribute()) {
509
510 setSize(4 * FastMath.PI);
511 setBarycenter(new S2Point(0, 0));
512 } else {
513 setSize(0);
514 setBarycenter(S2Point.NaN);
515 }
516
517 } else {
518
519
520 final PropertiesComputer pc = new PropertiesComputer(getTolerance());
521 tree.visit(pc);
522 setSize(pc.getArea());
523 setBarycenter(pc.getBarycenter());
524
525 }
526
527 }
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552 public List<Vertex> getBoundaryLoops() throws MathIllegalStateException {
553
554 if (loops == null) {
555 if (getTree(false).getCut() == null) {
556 loops = Collections.emptyList();
557 } else {
558
559
560 final EdgesWithNodeInfoBuilder visitor = new EdgesWithNodeInfoBuilder(getTolerance());
561 getTree(true).visit(visitor);
562 final List<EdgeWithNodeInfo> edges = visitor.getEdges();
563
564
565
566 int pending = edges.size();
567 pending -= naturalFollowerConnections(edges);
568 if (pending > 0) {
569 pending -= splitEdgeConnections(edges);
570 }
571 if (pending > 0) {
572 closeVerticesConnections(edges);
573 }
574
575
576 loops = new ArrayList<>();
577 for (EdgeWithNodeInfo s = getUnprocessed(edges); s != null; s = getUnprocessed(edges)) {
578 loops.add(s.getStart());
579 followLoop(s);
580 }
581
582 }
583 }
584
585 return Collections.unmodifiableList(loops);
586
587 }
588
589
590
591
592
593 private int naturalFollowerConnections(final List<EdgeWithNodeInfo> edges) {
594 int connected = 0;
595 for (final EdgeWithNodeInfo edge : edges) {
596 if (edge.getEnd().getOutgoing() == null) {
597 for (final EdgeWithNodeInfo candidateNext : edges) {
598 if (EdgeWithNodeInfo.areNaturalFollowers(edge, candidateNext)) {
599
600 edge.setNextEdge(candidateNext);
601 ++connected;
602 break;
603 }
604 }
605 }
606 }
607 return connected;
608 }
609
610
611
612
613
614 private int splitEdgeConnections(final List<EdgeWithNodeInfo> edges) {
615 int connected = 0;
616 for (final EdgeWithNodeInfo edge : edges) {
617 if (edge.getEnd().getOutgoing() == null) {
618 for (final EdgeWithNodeInfo candidateNext : edges) {
619 if (EdgeWithNodeInfo.resultFromASplit(edge, candidateNext)) {
620
621 edge.setNextEdge(candidateNext);
622 ++connected;
623 break;
624 }
625 }
626 }
627 }
628 return connected;
629 }
630
631
632
633
634
635
636
637
638
639 private int closeVerticesConnections(final List<EdgeWithNodeInfo> edges) {
640 int connected = 0;
641 for (final EdgeWithNodeInfo edge : edges) {
642 if (edge.getEnd().getOutgoing() == null && edge.getEnd() != null) {
643 final Vector3D end = edge.getEnd().getLocation().getVector();
644 EdgeWithNodeInfo selectedNext = null;
645 double min = Double.POSITIVE_INFINITY;
646 for (final EdgeWithNodeInfo candidateNext : edges) {
647 if (candidateNext.getStart().getIncoming() == null) {
648 final double distance = Vector3D.distance(end, candidateNext.getStart().getLocation().getVector());
649 if (distance < min) {
650 selectedNext = candidateNext;
651 min = distance;
652 }
653 }
654 }
655 if (min <= getTolerance()) {
656
657 edge.setNextEdge(selectedNext);
658 ++connected;
659 }
660 }
661 }
662 return connected;
663 }
664
665
666
667
668
669
670 private EdgeWithNodeInfo getUnprocessed(final List<EdgeWithNodeInfo> edges) {
671 for (final EdgeWithNodeInfo edge : edges) {
672 if (!edge.isProcessed()) {
673 return edge;
674 }
675 }
676 return null;
677 }
678
679
680
681
682
683
684
685 private void followLoop(final EdgeWithNodeInfo defining) {
686
687 defining.setProcessed(true);
688
689
690 EdgeWithNodeInfo previous = defining;
691 EdgeWithNodeInfo next = (EdgeWithNodeInfo) defining.getEnd().getOutgoing();
692 while (next != defining) {
693 if (next == null) {
694
695 throw new MathIllegalStateException(LocalizedGeometryFormats.OUTLINE_BOUNDARY_LOOP_OPEN);
696 }
697 next.setProcessed(true);
698
699
700 if (Vector3D.angle(previous.getCircle().getPole(), next.getCircle().getPole()) <= Precision.EPSILON) {
701
702
703 previous.setNextEdge(next.getEnd().getOutgoing());
704 previous.setLength(previous.getLength() + next.getLength());
705 }
706
707 previous = next;
708 next = (EdgeWithNodeInfo) next.getEnd().getOutgoing();
709
710 }
711
712 }
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761 public EnclosingBall<Sphere2D, S2Point> getEnclosingCap() {
762
763
764 if (isEmpty()) {
765 return new EnclosingBall<Sphere2D, S2Point>(S2Point.PLUS_K, Double.NEGATIVE_INFINITY);
766 }
767 if (isFull()) {
768 return new EnclosingBall<Sphere2D, S2Point>(S2Point.PLUS_K, Double.POSITIVE_INFINITY);
769 }
770
771
772 final BSPTree<Sphere2D> root = getTree(false);
773 if (isEmpty(root.getMinus()) && isFull(root.getPlus())) {
774
775 final Circle circle = (Circle) root.getCut().getHyperplane();
776 return new EnclosingBall<Sphere2D, S2Point>(new S2Point(circle.getPole()).negate(),
777 0.5 * FastMath.PI);
778 }
779 if (isFull(root.getMinus()) && isEmpty(root.getPlus())) {
780
781 final Circle circle = (Circle) root.getCut().getHyperplane();
782 return new EnclosingBall<Sphere2D, S2Point>(new S2Point(circle.getPole()),
783 0.5 * FastMath.PI);
784 }
785
786
787 final List<Vector3D> points = getInsidePoints();
788
789
790 final List<Vertex> boundary = getBoundaryLoops();
791 for (final Vertex loopStart : boundary) {
792 int count = 0;
793 for (Vertex v = loopStart; count == 0 || v != loopStart; v = v.getOutgoing().getEnd()) {
794 ++count;
795 points.add(v.getLocation().getVector());
796 }
797 }
798
799
800 final SphereGenerator generator = new SphereGenerator();
801 final WelzlEncloser<Euclidean3D, Vector3D> encloser =
802 new WelzlEncloser<>(getTolerance(), generator);
803 EnclosingBall<Euclidean3D, Vector3D> enclosing3D = encloser.enclose(points);
804 final Vector3D[] support3D = enclosing3D.getSupport();
805
806
807 final double r = enclosing3D.getRadius();
808 final double h = enclosing3D.getCenter().getNorm();
809 if (h < getTolerance()) {
810
811
812 EnclosingBall<Sphere2D, S2Point> enclosingS2 =
813 new EnclosingBall<>(S2Point.PLUS_K, Double.POSITIVE_INFINITY);
814 for (Vector3D outsidePoint : getOutsidePoints()) {
815 final S2Point outsideS2 = new S2Point(outsidePoint);
816 final BoundaryProjection<Sphere2D> projection = projectToBoundary(outsideS2);
817 if (FastMath.PI - projection.getOffset() < enclosingS2.getRadius()) {
818 enclosingS2 = new EnclosingBall<>(outsideS2.negate(),
819 FastMath.PI - projection.getOffset(),
820 (S2Point) projection.getProjected());
821 }
822 }
823 return enclosingS2;
824 }
825 final S2Point[] support = new S2Point[support3D.length];
826 for (int i = 0; i < support3D.length; ++i) {
827 support[i] = new S2Point(support3D[i]);
828 }
829
830 return new EnclosingBall<>(new S2Point(enclosing3D.getCenter()),
831 FastMath.acos((1 + h * h - r * r) / (2 * h)),
832 support);
833
834
835 }
836
837
838
839
840 private List<Vector3D> getInsidePoints() {
841 final PropertiesComputer pc = new PropertiesComputer(getTolerance());
842 getTree(true).visit(pc);
843 return pc.getConvexCellsInsidePoints();
844 }
845
846
847
848
849 private List<Vector3D> getOutsidePoints() {
850 final SphericalPolygonsSet complement =
851 (SphericalPolygonsSet) new RegionFactory<Sphere2D>().getComplement(this);
852 final PropertiesComputer pc = new PropertiesComputer(getTolerance());
853 complement.getTree(true).visit(pc);
854 return pc.getConvexCellsInsidePoints();
855 }
856
857 }