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