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.ArrayList;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.List;
23 import java.util.Objects;
24
25 import org.apache.commons.geometry.core.Transform;
26 import org.apache.commons.geometry.core.partitioning.Hyperplane;
27 import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
28 import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
29 import org.apache.commons.geometry.core.partitioning.Split;
30 import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
31 import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
32 import org.apache.commons.geometry.euclidean.twod.Vector2D;
33 import org.apache.commons.numbers.angle.Angle;
34 import org.apache.commons.numbers.core.Precision;
35
36
37
38 public class RegionBSPTree1S extends AbstractRegionBSPTree<Point1S, RegionBSPTree1S.RegionNode1S> {
39
40 private static final Comparator<BoundaryPair> BOUNDARY_PAIR_COMPARATOR =
41 Comparator.comparingDouble(BoundaryPair::getMinValue);
42
43
44
45 public RegionBSPTree1S() {
46 this(false);
47 }
48
49
50
51
52
53
54 public RegionBSPTree1S(final boolean full) {
55 super(full);
56 }
57
58
59
60
61
62 public RegionBSPTree1S copy() {
63 final RegionBSPTree1S result = RegionBSPTree1S.empty();
64 result.copy(this);
65
66 return result;
67 }
68
69
70
71
72
73 public void add(final AngularInterval interval) {
74 union(fromInterval(interval));
75 }
76
77
78 @Override
79 public Point1S project(final Point1S pt) {
80 final BoundaryProjector1S projector = new BoundaryProjector1S(pt);
81 accept(projector);
82
83 return projector.getProjected();
84 }
85
86
87
88
89
90
91
92 @Override
93 public void transform(final Transform<Point1S> transform) {
94 if (!isFull() && !isEmpty()) {
95
96 final List<AngularInterval> intervals = toIntervals();
97
98 setEmpty();
99
100 for (final AngularInterval interval : intervals) {
101 union(interval.transform(transform).toTree());
102 }
103 }
104 }
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 @Override
129 public Split<RegionBSPTree1S> split(final Hyperplane<Point1S> splitter) {
130
131
132 if (!isEmpty() && splitter.classify(Point1S.ZERO) == HyperplaneLocation.ON) {
133 final CutAngle cut = (CutAngle) splitter;
134 if (cut.isPositiveFacing()) {
135 return new Split<>(null, copy());
136 } else {
137 return new Split<>(copy(), null);
138 }
139 }
140
141 return split(splitter, RegionBSPTree1S.empty(), RegionBSPTree1S.empty());
142 }
143
144
145
146
147
148
149 public Split<RegionBSPTree1S> splitDiameter(final CutAngle splitter) {
150
151 final CutAngle opposite = CutAngles.fromPointAndDirection(
152 splitter.getPoint().antipodal(),
153 !splitter.isPositiveFacing(),
154 splitter.getPrecision());
155
156 final double plusPoleOffset = splitter.isPositiveFacing() ?
157 +Angle.PI_OVER_TWO :
158 -Angle.PI_OVER_TWO;
159 final Point1S plusPole = Point1S.of(splitter.getAzimuth() + plusPoleOffset);
160
161 final boolean zeroOnPlusSide = splitter.getPrecision()
162 .lte(plusPole.distance(Point1S.ZERO), Angle.PI_OVER_TWO);
163
164 final Split<RegionBSPTree1S> firstSplit = split(splitter);
165 final Split<RegionBSPTree1S> secondSplit = split(opposite);
166
167 RegionBSPTree1S minus = RegionBSPTree1S.empty();
168 RegionBSPTree1S plus = RegionBSPTree1S.empty();
169
170 if (zeroOnPlusSide) {
171
172 safeUnion(plus, firstSplit.getPlus());
173 safeUnion(plus, secondSplit.getPlus());
174
175 minus = firstSplit.getMinus();
176 if (minus != null) {
177 minus = minus.split(opposite).getMinus();
178 }
179 } else {
180
181 safeUnion(minus, firstSplit.getMinus());
182 safeUnion(minus, secondSplit.getMinus());
183
184 plus = firstSplit.getPlus();
185 if (plus != null) {
186 plus = plus.split(opposite).getPlus();
187 }
188 }
189
190 return new Split<>(
191 (minus != null && !minus.isEmpty()) ? minus : null,
192 (plus != null && !plus.isEmpty()) ? plus : null);
193 }
194
195
196
197
198
199
200
201 public List<AngularInterval> toIntervals() {
202 if (isFull()) {
203 return Collections.singletonList(AngularInterval.full());
204 }
205
206 final List<BoundaryPair> insideBoundaryPairs = new ArrayList<>();
207 for (final RegionNode1S node : nodes()) {
208 if (node.isInside()) {
209 insideBoundaryPairs.add(getNodeBoundaryPair(node));
210 }
211 }
212
213 insideBoundaryPairs.sort(BOUNDARY_PAIR_COMPARATOR);
214
215 final int boundaryPairCount = insideBoundaryPairs.size();
216
217
218 final int startOffset = getIntervalStartIndex(insideBoundaryPairs);
219
220
221
222 final List<AngularInterval> intervals = new ArrayList<>();
223
224 BoundaryPair start = null;
225 BoundaryPair end = null;
226 BoundaryPair current;
227
228 for (int i = 0; i < boundaryPairCount; ++i) {
229 current = insideBoundaryPairs.get((i + startOffset) % boundaryPairCount);
230
231 if (start == null) {
232 start = current;
233 end = current;
234 } else if (Objects.equals(end.getMax(), current.getMin())) {
235
236 end = current;
237 } else {
238
239 intervals.add(createInterval(start, end));
240
241
242 start = current;
243 end = current;
244 }
245 }
246
247 if (start != null && end != null) {
248 intervals.add(createInterval(start, end));
249 }
250
251 return intervals;
252 }
253
254
255
256
257
258
259
260
261 private int getIntervalStartIndex(final List<BoundaryPair> boundaryPairs) {
262 final int size = boundaryPairs.size();
263
264 if (size > 0) {
265 BoundaryPair current;
266 BoundaryPair previous = boundaryPairs.get(size - 1);
267
268 for (int i = 0; i < size; ++i, previous = current) {
269 current = boundaryPairs.get(i);
270
271 if (!Objects.equals(current.getMin(), previous.getMax())) {
272 return i;
273 }
274 }
275 }
276
277 return 0;
278 }
279
280
281
282
283
284
285
286
287
288 private AngularInterval createInterval(final BoundaryPair start, final BoundaryPair end) {
289 CutAngle min = start.getMin();
290 CutAngle max = end.getMax();
291
292 final Precision.DoubleEquivalence precision = (min != null) ? min.getPrecision() : max.getPrecision();
293
294
295
296
297
298 if (min != null) {
299 if (min.isPositiveFacing()) {
300 min = min.reverse();
301 }
302 } else {
303 min = CutAngles.createNegativeFacing(0.0, precision);
304 }
305
306 if (max != null) {
307 if (!max.isPositiveFacing()) {
308 max = max.reverse();
309 }
310 } else {
311 max = CutAngles.createPositiveFacing(Angle.TWO_PI, precision);
312 }
313
314 return AngularInterval.of(min, max);
315 }
316
317
318
319
320
321 private BoundaryPair getNodeBoundaryPair(final RegionNode1S node) {
322 CutAngle min = null;
323 CutAngle max = null;
324
325 CutAngle pt;
326 RegionNode1S child = node;
327 RegionNode1S parent;
328
329 while ((min == null || max == null) && (parent = child.getParent()) != null) {
330 pt = (CutAngle) parent.getCutHyperplane();
331
332 if ((pt.isPositiveFacing() && child.isMinus()) ||
333 (!pt.isPositiveFacing() && child.isPlus())) {
334
335 if (max == null) {
336 max = pt;
337 }
338 } else if (min == null) {
339 min = pt;
340 }
341
342 child = parent;
343 }
344
345 return new BoundaryPair(min, max);
346 }
347
348
349 @Override
350 protected RegionSizeProperties<Point1S> computeRegionSizeProperties() {
351 if (isFull()) {
352 return new RegionSizeProperties<>(Angle.TWO_PI, null);
353 } else if (isEmpty()) {
354 return new RegionSizeProperties<>(0, null);
355 }
356
357 double size = 0;
358 Vector2D scaledCentroidSum = Vector2D.ZERO;
359
360 double intervalSize;
361
362 for (final AngularInterval interval : toIntervals()) {
363 intervalSize = interval.getSize();
364
365 size += intervalSize;
366 scaledCentroidSum = scaledCentroidSum.add(interval.getCentroid().getVector().withNorm(intervalSize));
367 }
368
369 final Precision.DoubleEquivalence precision = ((CutAngle) getRoot().getCutHyperplane()).getPrecision();
370
371 final Point1S centroid = scaledCentroidSum.eq(Vector2D.ZERO, precision) ?
372 null :
373 Point1S.from(scaledCentroidSum);
374
375 return new RegionSizeProperties<>(size, centroid);
376 }
377
378
379 @Override
380 protected RegionNode1S createNode() {
381 return new RegionNode1S(this);
382 }
383
384
385
386
387 public static RegionBSPTree1S empty() {
388 return new RegionBSPTree1S(false);
389 }
390
391
392
393
394
395 public static RegionBSPTree1S full() {
396 return new RegionBSPTree1S(true);
397 }
398
399
400
401
402
403 public static RegionBSPTree1S fromInterval(final AngularInterval interval) {
404 final CutAngle minBoundary = interval.getMinBoundary();
405 final CutAngle maxBoundary = interval.getMaxBoundary();
406
407 final RegionBSPTree1S tree = full();
408
409 if (minBoundary != null) {
410 tree.insert(minBoundary.span());
411 }
412
413 if (maxBoundary != null) {
414 tree.insert(maxBoundary.span());
415 }
416
417 return tree;
418 }
419
420
421
422
423
424
425 private static void safeUnion(final RegionBSPTree1S target, final RegionBSPTree1S input) {
426 if (input != null) {
427 target.union(input);
428 }
429 }
430
431
432
433 public static final class RegionNode1S extends AbstractRegionBSPTree.AbstractRegionNode<Point1S, RegionNode1S> {
434
435
436
437 private RegionNode1S(final AbstractBSPTree<Point1S, RegionNode1S> tree) {
438 super(tree);
439 }
440
441
442 @Override
443 protected RegionNode1S getSelf() {
444 return this;
445 }
446 }
447
448
449
450 private static final class BoundaryPair {
451
452
453 private final CutAngle min;
454
455
456 private final CutAngle max;
457
458
459
460
461
462 BoundaryPair(final CutAngle min, final CutAngle max) {
463 this.min = min;
464 this.max = max;
465 }
466
467
468
469
470 public CutAngle getMin() {
471 return min;
472 }
473
474
475
476
477 public CutAngle getMax() {
478 return max;
479 }
480
481
482
483
484
485 public double getMinValue() {
486 return (min != null) ? min.getNormalizedAzimuth() : 0;
487 }
488 }
489
490
491
492 private static final class BoundaryProjector1S extends BoundaryProjector<Point1S, RegionNode1S> {
493
494
495
496 BoundaryProjector1S(final Point1S point) {
497 super(point);
498 }
499
500
501 @Override
502 protected boolean isPossibleClosestCut(final HyperplaneSubset<Point1S> cut, final Point1S target,
503 final double minDist) {
504
505 return true;
506 }
507
508
509 @Override
510 protected Point1S disambiguateClosestPoint(final Point1S target, final Point1S a, final Point1S b) {
511
512 return a.getNormalizedAzimuth() < b.getNormalizedAzimuth() ? a : b;
513 }
514 }
515 }