1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.threed;
18
19 import java.util.ArrayList;
20 import java.util.Iterator;
21 import java.util.List;
22
23 import org.apache.commons.geometry.core.RegionLocation;
24 import org.apache.commons.geometry.core.partitioning.Hyperplane;
25 import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
26 import org.apache.commons.geometry.core.partitioning.Split;
27 import org.apache.commons.geometry.euclidean.internal.Vectors;
28 import org.apache.commons.geometry.euclidean.threed.line.Lines3D;
29 import org.apache.commons.geometry.euclidean.twod.ConvexArea;
30 import org.apache.commons.geometry.euclidean.twod.Vector2D;
31 import org.apache.commons.numbers.core.Precision;
32
33
34
35 abstract class AbstractConvexPolygon3D extends AbstractPlaneSubset implements ConvexPolygon3D {
36
37
38 private final Plane plane;
39
40
41
42
43 AbstractConvexPolygon3D(final Plane plane) {
44 this.plane = plane;
45 }
46
47
48 @Override
49 public Plane getPlane() {
50 return plane;
51 }
52
53
54
55
56
57 @Override
58 public boolean isFull() {
59 return false;
60 }
61
62
63
64
65
66 @Override
67 public boolean isEmpty() {
68 return false;
69 }
70
71
72 @Override
73 public double getSize() {
74
75 final List<Vector3D> vertices = getVertices();
76
77 double crossSumX = 0.0;
78 double crossSumY = 0.0;
79 double crossSumZ = 0.0;
80
81 Vector3D prevPt = vertices.get(vertices.size() - 1);
82 Vector3D cross;
83 for (final Vector3D curPt : vertices) {
84 cross = prevPt.cross(curPt);
85
86 crossSumX += cross.getX();
87 crossSumY += cross.getY();
88 crossSumZ += cross.getZ();
89
90 prevPt = curPt;
91 }
92
93 return 0.5 * plane.getNormal().dot(Vector3D.of(crossSumX, crossSumY, crossSumZ));
94 }
95
96
97 @Override
98 public Vector3D getCentroid() {
99 final List<Vector3D> vertices = getVertices();
100
101 double areaSum = 0.0;
102 double scaledCentroidSumX = 0.0;
103 double scaledCentroidSumY = 0.0;
104 double scaledCentroidSumZ = 0.0;
105
106 final Iterator<Vector3D> it = vertices.iterator();
107
108 final Vector3D startPt = it.next();
109
110 Vector3D prevPt = it.next();
111 Vector3D curPt;
112
113 Vector3D prevVec = startPt.vectorTo(prevPt);
114 Vector3D curVec;
115
116 double triArea;
117 Vector3D triCentroid;
118 while (it.hasNext()) {
119 curPt = it.next();
120 curVec = startPt.vectorTo(curPt);
121
122 triArea = 0.5 * prevVec.cross(curVec).norm();
123 triCentroid = Vector3D.centroid(startPt, prevPt, curPt);
124
125 areaSum += triArea;
126
127 scaledCentroidSumX += triArea * triCentroid.getX();
128 scaledCentroidSumY += triArea * triCentroid.getY();
129 scaledCentroidSumZ += triArea * triCentroid.getZ();
130
131 prevPt = curPt;
132 prevVec = curVec;
133 }
134
135 if (areaSum > 0) {
136 final double scale = 1 / areaSum;
137 return Vector3D.of(
138 scale * scaledCentroidSumX,
139 scale * scaledCentroidSumY,
140 scale * scaledCentroidSumZ
141 );
142 }
143
144
145
146 final Vector3D min = Vector3D.min(vertices);
147 final Vector3D max = Vector3D.max(vertices);
148
149 return min.lerp(max, 0.5);
150 }
151
152
153 @Override
154 public Bounds3D getBounds() {
155 return Bounds3D.from(getVertices());
156 }
157
158
159 @Override
160 public RegionLocation classify(final Vector3D pt) {
161 if (plane.contains(pt)) {
162 final List<Vector3D> vertices = getVertices();
163 final Precision.DoubleEquivalence precision = plane.getPrecision();
164
165 final Vector3D normal = plane.getNormal();
166 Vector3D edgeVec;
167 Vector3D edgePlusVec;
168 Vector3D testVec;
169
170 Vector3D offsetVec;
171 double offsetSign;
172 double offset;
173 int cmp;
174
175 boolean onBoundary = false;
176
177 Vector3D startVertex = vertices.get(vertices.size() - 1);
178 for (final Vector3D nextVertex : vertices) {
179
180 edgeVec = startVertex.vectorTo(nextVertex);
181 edgePlusVec = edgeVec.cross(normal);
182
183 testVec = startVertex.vectorTo(pt);
184
185 offsetVec = testVec.reject(edgeVec);
186 offsetSign = Math.signum(offsetVec.dot(edgePlusVec));
187 offset = offsetSign * offsetVec.norm();
188
189 cmp = precision.compare(offset, 0.0);
190 if (cmp > 0) {
191
192 return RegionLocation.OUTSIDE;
193 } else if (cmp == 0) {
194 onBoundary = true;
195 }
196
197 startVertex = nextVertex;
198 }
199
200 if (onBoundary) {
201
202 return RegionLocation.BOUNDARY;
203 }
204
205
206 return RegionLocation.INSIDE;
207 }
208
209
210 return RegionLocation.OUTSIDE;
211 }
212
213
214 @Override
215 public Vector3D closest(final Vector3D pt) {
216 final Vector3D normal = plane.getNormal();
217 final Precision.DoubleEquivalence precision = plane.getPrecision();
218
219 final List<Vector3D> vertices = getVertices();
220
221 final Vector3D projPt = plane.project(pt);
222
223 Vector3D edgeVec;
224 Vector3D edgePlusVec;
225 Vector3D testVec;
226
227 Vector3D offsetVec;
228 double offsetSign;
229 double offset;
230 int cmp;
231
232 Vector3D boundaryVec;
233 double boundaryPointT;
234 Vector3D boundaryPoint;
235 double boundaryPointDistSq;
236
237 double closestBoundaryPointDistSq = Double.POSITIVE_INFINITY;
238 Vector3D closestBoundaryPoint = null;
239
240 Vector3D startVertex = vertices.get(vertices.size() - 1);
241 for (final Vector3D nextVertex : vertices) {
242
243 edgeVec = startVertex.vectorTo(nextVertex);
244 edgePlusVec = edgeVec.cross(normal);
245
246 testVec = startVertex.vectorTo(projPt);
247
248 offsetVec = testVec.reject(edgeVec);
249 offsetSign = Math.signum(offsetVec.dot(edgePlusVec));
250 offset = offsetSign * offsetVec.norm();
251
252 cmp = precision.compare(offset, 0.0);
253 if (cmp >= 0) {
254
255
256
257 boundaryVec = testVec.subtract(offsetVec);
258 boundaryPointT =
259 Math.signum(boundaryVec.dot(edgeVec)) * (boundaryVec.norm() / Vectors.checkedNorm(edgeVec));
260 boundaryPointT = Math.max(0, Math.min(1, boundaryPointT));
261
262 boundaryPoint = startVertex.lerp(nextVertex, boundaryPointT);
263
264 boundaryPointDistSq = boundaryPoint.distanceSq(projPt);
265 if (boundaryPointDistSq < closestBoundaryPointDistSq) {
266 closestBoundaryPointDistSq = boundaryPointDistSq;
267 closestBoundaryPoint = boundaryPoint;
268 }
269 }
270
271 startVertex = nextVertex;
272 }
273
274 if (closestBoundaryPoint != null) {
275
276 return closestBoundaryPoint;
277 }
278
279
280 return projPt;
281 }
282
283
284 @Override
285 public PlaneConvexSubset.Embedded getEmbedded() {
286 final EmbeddingPlane embeddingPlane = plane.getEmbedding();
287 final List<Vector2D> subspaceVertices = embeddingPlane.toSubspace(getVertices());
288 final ConvexArea area = ConvexArea.convexPolygonFromVertices(subspaceVertices,
289 embeddingPlane.getPrecision());
290
291 return new EmbeddedAreaPlaneConvexSubset(embeddingPlane, area);
292 }
293
294
295 @Override
296 public Split<PlaneConvexSubset> split(final Hyperplane<Vector3D> splitter) {
297 final Plane splitterPlane = (Plane) splitter;
298 final List<Vector3D> vertices = getVertices();
299
300 final int size = vertices.size();
301
302 int minusPlusTransitionIdx = -1;
303 Vector3D minusPlusInsertVertex = null;
304
305 int plusMinusTransitionIdx = -1;
306 Vector3D plusMinusInsertVertex = null;
307
308 int transitionCount = 0;
309
310 Vector3D curVertex;
311 HyperplaneLocation curLoc;
312
313 int lastSideIdx = -1;
314 Vector3D lastSideVertex = null;
315 HyperplaneLocation lastSideLoc = null;
316
317 int lastBoundaryIdx = -1;
318
319 for (int i = 0; i <= size || transitionCount == 1; ++i) {
320
321 curVertex = vertices.get(i % size);
322 curLoc = splitter.classify(curVertex);
323
324 if (lastSideLoc == HyperplaneLocation.MINUS && curLoc == HyperplaneLocation.PLUS) {
325
326 minusPlusTransitionIdx = Math.max(lastSideIdx, lastBoundaryIdx);
327 ++transitionCount;
328
329 if (lastBoundaryIdx < 0) {
330
331 minusPlusInsertVertex = splitterPlane.intersection(
332 Lines3D.fromPoints(lastSideVertex, curVertex, splitterPlane.getPrecision()));
333 }
334 } else if (lastSideLoc == HyperplaneLocation.PLUS && curLoc == HyperplaneLocation.MINUS) {
335
336 plusMinusTransitionIdx = Math.max(lastSideIdx, lastBoundaryIdx);
337 ++transitionCount;
338
339 if (lastBoundaryIdx < 0) {
340
341 plusMinusInsertVertex = splitterPlane.intersection(
342 Lines3D.fromPoints(lastSideVertex, curVertex, splitterPlane.getPrecision()));
343 }
344 }
345
346 if (curLoc == HyperplaneLocation.ON) {
347 lastBoundaryIdx = i;
348 } else {
349 lastBoundaryIdx = -1;
350
351 lastSideIdx = i;
352 lastSideVertex = curVertex;
353 lastSideLoc = curLoc;
354 }
355 }
356
357 if (minusPlusTransitionIdx > -1 && plusMinusTransitionIdx > -1) {
358
359 final List<Vector3D> minusVertices = buildPolygonSplitVertexList(
360 plusMinusTransitionIdx, plusMinusInsertVertex,
361 minusPlusTransitionIdx, minusPlusInsertVertex, vertices);
362 final List<Vector3D> plusVertices = buildPolygonSplitVertexList(
363 minusPlusTransitionIdx, minusPlusInsertVertex,
364 plusMinusTransitionIdx, plusMinusInsertVertex, vertices);
365
366
367
368 return new Split<>(
369 Planes.fromConvexPlanarVertices(plane, minusVertices),
370 Planes.fromConvexPlanarVertices(plane, plusVertices));
371
372 } else if (lastSideLoc == HyperplaneLocation.PLUS) {
373
374 return new Split<>(null, this);
375 } else if (lastSideLoc == HyperplaneLocation.MINUS) {
376
377 return new Split<>(this, null);
378 }
379
380
381 return new Split<>(null, null);
382 }
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 private List<Vector3D> buildPolygonSplitVertexList(final int enterIdx, final Vector3D newEnterPt,
405 final int exitIdx, final Vector3D newExitPt, final List<? extends Vector3D> vertices) {
406
407 final int size = vertices.size();
408
409 final boolean hasNewEnterPt = newEnterPt != null;
410 final boolean hasNewExitPt = newExitPt != null;
411
412 final int startIdx = (hasNewEnterPt ? enterIdx + 1 : enterIdx) % size;
413 final int endIdx = exitIdx % size;
414
415 final boolean hasWrappedIndices = endIdx < startIdx;
416
417 final int resultSize = (hasWrappedIndices ? endIdx + size : endIdx) - startIdx + 1;
418 final List<Vector3D> result = new ArrayList<>(resultSize);
419
420 if (hasNewEnterPt) {
421 result.add(newEnterPt);
422 }
423
424 if (hasWrappedIndices) {
425 result.addAll(vertices.subList(startIdx, size));
426 result.addAll(vertices.subList(0, endIdx + 1));
427 } else {
428 result.addAll(vertices.subList(startIdx, endIdx + 1));
429 }
430
431 if (hasNewExitPt) {
432 result.add(newExitPt);
433 }
434
435 return result;
436 }
437
438
439 @Override
440 public String toString() {
441 final StringBuilder sb = new StringBuilder();
442 sb.append(getClass().getSimpleName())
443 .append("[normal= ")
444 .append(getPlane().getNormal())
445 .append(", vertices= ")
446 .append(getVertices())
447 .append(']');
448
449 return sb.toString();
450 }
451 }