1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.core.partitioning;
18
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.function.Function;
24
25 import org.apache.commons.geometry.core.Point;
26 import org.apache.commons.geometry.core.RegionLocation;
27 import org.apache.commons.geometry.core.Transform;
28
29
30
31
32
33
34 public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>, S extends HyperplaneConvexSubset<P>>
35 implements HyperplaneBoundedRegion<P> {
36
37 private final List<S> boundaries;
38
39
40
41
42
43 protected AbstractConvexHyperplaneBoundedRegion(final List<S> boundaries) {
44 this.boundaries = Collections.unmodifiableList(boundaries);
45 }
46
47
48
49
50
51 public List<S> getBoundaries() {
52 return boundaries;
53 }
54
55
56 @Override
57 public boolean isFull() {
58
59 return boundaries.isEmpty();
60 }
61
62
63
64
65
66 @Override
67 public boolean isEmpty() {
68 return false;
69 }
70
71
72 @Override
73 public double getBoundarySize() {
74 double sum = 0.0;
75 for (final S boundary : boundaries) {
76 sum += boundary.getSize();
77 }
78
79 return sum;
80 }
81
82
83 @Override
84 public RegionLocation classify(final P pt) {
85 boolean isOn = false;
86
87 HyperplaneLocation loc;
88 for (final S boundary : boundaries) {
89 loc = boundary.getHyperplane().classify(pt);
90
91 if (loc == HyperplaneLocation.PLUS) {
92 return RegionLocation.OUTSIDE;
93 } else if (loc == HyperplaneLocation.ON) {
94 isOn = true;
95 }
96 }
97
98 return isOn ? RegionLocation.BOUNDARY : RegionLocation.INSIDE;
99 }
100
101
102 @Override
103 public P project(final P pt) {
104
105 P projected;
106 double dist;
107
108 P closestPt = null;
109 double closestDist = Double.POSITIVE_INFINITY;
110
111 for (final S boundary : boundaries) {
112 projected = boundary.closest(pt);
113 dist = pt.distance(projected);
114
115 if (projected != null && (closestPt == null || dist < closestDist)) {
116 closestPt = projected;
117 closestDist = dist;
118 }
119 }
120
121 return closestPt;
122 }
123
124
125
126
127
128
129 public HyperplaneConvexSubset<P> trim(final HyperplaneConvexSubset<P> sub) {
130 HyperplaneConvexSubset<P> remaining = sub;
131 for (final S boundary : boundaries) {
132 remaining = remaining.split(boundary.getHyperplane()).getMinus();
133 if (remaining == null) {
134 break;
135 }
136 }
137
138 return remaining;
139 }
140
141
142 @Override
143 public String toString() {
144 final StringBuilder sb = new StringBuilder();
145 sb.append(this.getClass().getSimpleName())
146 .append("[boundaries= ")
147 .append(boundaries)
148 .append(']');
149
150 return sb.toString();
151 }
152
153
154
155
156
157
158
159
160
161
162 protected <R extends AbstractConvexHyperplaneBoundedRegion<P, S>> R transformInternal(
163 final Transform<P> transform, final R thisInstance, final Class<S> boundaryType,
164 final Function<? super List<S>, R> factory) {
165
166 if (isFull()) {
167 return thisInstance;
168 }
169
170 final List<S> origBoundaries = getBoundaries();
171
172 final int size = origBoundaries.size();
173 final List<S> tBoundaries = new ArrayList<>(size);
174
175
176 final S boundary = origBoundaries.get(0);
177 HyperplaneConvexSubset<P> tBoundary = boundary.transform(transform);
178
179 final boolean reverseDirection = swapsInsideOutside(transform);
180
181
182 if (reverseDirection) {
183 tBoundary = tBoundary.reverse();
184 }
185 tBoundaries.add(boundaryType.cast(tBoundary));
186
187 for (int i = 1; i < origBoundaries.size(); ++i) {
188 tBoundary = origBoundaries.get(i).transform(transform);
189
190 if (reverseDirection) {
191 tBoundary = tBoundary.reverse();
192 }
193
194 tBoundaries.add(boundaryType.cast(tBoundary));
195 }
196
197 return factory.apply(tBoundaries);
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211 protected boolean swapsInsideOutside(final Transform<P> transform) {
212 return !transform.preservesOrientation();
213 }
214
215
216
217
218
219
220
221
222
223
224 protected <R extends AbstractConvexHyperplaneBoundedRegion<P, S>> Split<R> splitInternal(
225 final Hyperplane<P> splitter, final R thisInstance, final Class<S> boundaryType,
226 final Function<List<S>, R> factory) {
227
228 return isFull() ?
229 splitInternalFull(splitter, boundaryType, factory) :
230 splitInternalNonFull(splitter, thisInstance, boundaryType, factory);
231 }
232
233
234
235
236
237
238
239
240 private <R extends AbstractConvexHyperplaneBoundedRegion<P, S>> Split<R> splitInternalFull(
241 final Hyperplane<P> splitter, final Class<S> boundaryType, final Function<? super List<S>, R> factory) {
242
243 final R minus = factory.apply(Collections.singletonList(boundaryType.cast(splitter.span())));
244 final R plus = factory.apply(Collections.singletonList(boundaryType.cast(splitter.reverse().span())));
245
246 return new Split<>(minus, plus);
247 }
248
249
250
251
252
253
254
255
256
257
258 private <R extends AbstractConvexHyperplaneBoundedRegion<P, S>> Split<R> splitInternalNonFull(
259 final Hyperplane<P> splitter, final R thisInstance, final Class<S> boundaryType,
260 final Function<? super List<S>, R> factory) {
261
262 final HyperplaneConvexSubset<P> trimmedSplitter = trim(splitter.span());
263
264 if (trimmedSplitter == null) {
265
266
267
268 final SplitLocation regionLoc = determineRegionPlusMinusLocation(splitter);
269 return regionLoc == SplitLocation.MINUS ?
270 new Split<>(thisInstance, null) :
271 new Split<>(null, thisInstance);
272 }
273
274
275
276 final ArrayList<S> minusBoundaries = new ArrayList<>();
277 final ArrayList<S> plusBoundaries = new ArrayList<>();
278
279 splitBoundaries(splitter, boundaryType, minusBoundaries, plusBoundaries);
280
281
282
283
284
285 if (!trimmedSplitter.isFull()) {
286 if (minusBoundaries.isEmpty()) {
287 if (plusBoundaries.isEmpty()) {
288 return new Split<>(null, null);
289 }
290 return new Split<>(null, thisInstance);
291 } else if (plusBoundaries.isEmpty()) {
292 return new Split<>(thisInstance, null);
293 }
294 }
295
296
297 minusBoundaries.add(boundaryType.cast(trimmedSplitter));
298 plusBoundaries.add(boundaryType.cast(trimmedSplitter.reverse()));
299
300 minusBoundaries.trimToSize();
301 plusBoundaries.trimToSize();
302
303 return new Split<>(factory.apply(minusBoundaries), factory.apply(plusBoundaries));
304 }
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331 private SplitLocation determineRegionPlusMinusLocation(final Hyperplane<P> splitter) {
332 double minusSize = 0;
333 double plusSize = 0;
334
335 Split<? extends HyperplaneConvexSubset<P>> split;
336 SplitLocation loc;
337
338 for (final S boundary : boundaries) {
339 split = boundary.split(splitter);
340 loc = split.getLocation();
341
342 if (loc == SplitLocation.MINUS || loc == SplitLocation.PLUS) {
343 return loc;
344 } else if (loc == SplitLocation.NEITHER) {
345 return splitter.similarOrientation(boundary.getHyperplane()) ?
346 SplitLocation.MINUS :
347 SplitLocation.PLUS;
348 } else {
349 minusSize += split.getMinus().getSize();
350 plusSize += split.getPlus().getSize();
351 }
352 }
353
354 return minusSize > plusSize ? SplitLocation.MINUS : SplitLocation.PLUS;
355 }
356
357
358
359
360
361
362
363
364
365
366 private void splitBoundaries(final Hyperplane<P> splitter, final Class<S> boundaryType,
367 final List<S> minusBoundaries, final List<S> plusBoundaries) {
368
369 Split<? extends HyperplaneConvexSubset<P>> split;
370 HyperplaneConvexSubset<P> minusBoundary;
371 HyperplaneConvexSubset<P> plusBoundary;
372
373 for (final S boundary : boundaries) {
374 split = boundary.split(splitter);
375
376 minusBoundary = split.getMinus();
377 plusBoundary = split.getPlus();
378
379 if (minusBoundary != null) {
380 minusBoundaries.add(boundaryType.cast(minusBoundary));
381 }
382
383 if (plusBoundary != null) {
384 plusBoundaries.add(boundaryType.cast(plusBoundary));
385 }
386 }
387 }
388
389
390
391
392
393 protected static class ConvexRegionBoundaryBuilder<P extends Point<P>, S extends HyperplaneConvexSubset<P>> {
394
395
396 private final Class<S> subsetType;
397
398
399
400
401
402 public ConvexRegionBoundaryBuilder(final Class<S> subsetType) {
403 this.subsetType = subsetType;
404 }
405
406
407
408
409
410
411
412 public List<S> build(final Iterable<? extends Hyperplane<P>> bounds) {
413
414 final List<S> boundaries = new ArrayList<>();
415
416
417 int boundIdx = -1;
418 HyperplaneConvexSubset<P> boundary;
419
420 for (final Hyperplane<P> currentBound : bounds) {
421 ++boundIdx;
422
423 boundary = splitBound(currentBound, bounds, boundIdx);
424 if (boundary != null) {
425 boundaries.add(subsetType.cast(boundary));
426 }
427 }
428
429 if (boundIdx > 0 && boundaries.isEmpty()) {
430
431 throw nonConvexException(bounds);
432 }
433
434 return boundaries;
435 }
436
437
438
439
440
441
442
443
444
445
446 private HyperplaneConvexSubset<P> splitBound(final Hyperplane<P> currentBound,
447 final Iterable<? extends Hyperplane<P>> bounds, final int currentBoundIdx) {
448
449 HyperplaneConvexSubset<P> boundary = currentBound.span();
450
451 final Iterator<? extends Hyperplane<P>> boundsIt = bounds.iterator();
452
453 Hyperplane<P> splitter;
454 int splitterIdx = -1;
455
456 while (boundsIt.hasNext() && boundary != null) {
457 splitter = boundsIt.next();
458 ++splitterIdx;
459
460 if (currentBound == splitter) {
461
462
463 if (currentBoundIdx > splitterIdx) {
464
465
466 return null;
467 }
468 } else {
469
470 final Split<? extends HyperplaneConvexSubset<P>> split = boundary.split(splitter);
471
472 if (split.getLocation() != SplitLocation.NEITHER) {
473
474 boundary = split.getMinus();
475 } else if (!currentBound.similarOrientation(splitter)) {
476
477
478
479 throw nonConvexException(bounds);
480 } else if (currentBoundIdx > splitterIdx) {
481
482
483 return null;
484 }
485 }
486 }
487
488 return boundary;
489 }
490
491
492
493
494
495 private IllegalArgumentException nonConvexException(final Iterable<? extends Hyperplane<P>> bounds) {
496 return new IllegalArgumentException("Bounding hyperplanes do not produce a convex region: " + bounds);
497 }
498 }
499 }