View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.geometry.euclidean.threed;
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.stream.Stream;
23  
24  import org.apache.commons.geometry.core.Transform;
25  import org.apache.commons.geometry.core.partitioning.AbstractConvexHyperplaneBoundedRegion;
26  import org.apache.commons.geometry.core.partitioning.Hyperplane;
27  import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
28  import org.apache.commons.geometry.core.partitioning.Split;
29  
30  /** Class representing a finite or infinite convex volume in Euclidean 3D space.
31   * The boundaries of this area, if any, are composed of plane convex subsets.
32   */
33  public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D, PlaneConvexSubset>
34      implements BoundarySource3D {
35  
36      /** Instance representing the full 3D volume. */
37      private static final ConvexVolume FULL = new ConvexVolume(Collections.emptyList());
38  
39      /** Simple constructor. Callers are responsible for ensuring that the given path
40       * represents the boundary of a convex area. No validation is performed.
41       * @param boundaries the boundaries of the convex area
42       */
43      protected ConvexVolume(final List<PlaneConvexSubset> boundaries) {
44          super(boundaries);
45      }
46  
47      /** {@inheritDoc} */
48      @Override
49      public Stream<PlaneConvexSubset> boundaryStream() {
50          return getBoundaries().stream();
51      }
52  
53      /** {@inheritDoc} */
54      @Override
55      public double getSize() {
56          if (isFull()) {
57              return Double.POSITIVE_INFINITY;
58          }
59  
60          double volumeSum = 0.0;
61  
62          for (final PlaneConvexSubset boundary : getBoundaries()) {
63              if (boundary.isInfinite()) {
64                  return Double.POSITIVE_INFINITY;
65              }
66  
67              final Plane boundaryPlane = boundary.getPlane();
68              final double boundaryArea = boundary.getSize();
69              final Vector3D boundaryCentroid = boundary.getCentroid();
70  
71              volumeSum += boundaryArea * boundaryCentroid.dot(boundaryPlane.getNormal());
72          }
73  
74          return volumeSum / 3.0;
75      }
76  
77      /** {@inheritDoc} */
78      @Override
79      public Vector3D getCentroid() {
80          double volumeSum = 0.0;
81  
82          double sumX = 0.0;
83          double sumY = 0.0;
84          double sumZ = 0.0;
85  
86          for (final PlaneConvexSubset boundary : getBoundaries()) {
87              if (boundary.isInfinite()) {
88                  return null;
89              }
90  
91              final Plane boundaryPlane = boundary.getPlane();
92              final double boundaryArea = boundary.getSize();
93              final Vector3D boundaryCentroid = boundary.getCentroid();
94  
95              final double scaledVolume = boundaryArea * boundaryCentroid.dot(boundaryPlane.getNormal());
96  
97              volumeSum += scaledVolume;
98  
99              sumX += scaledVolume * boundaryCentroid.getX();
100             sumY += scaledVolume * boundaryCentroid.getY();
101             sumZ += scaledVolume * boundaryCentroid.getZ();
102         }
103 
104         if (volumeSum > 0) {
105             final double size = volumeSum / 3.0;
106 
107             // Since the volume we used when adding together the boundary contributions
108             // was 3x the actual pyramid size, we'll multiply by 1/4 here instead
109             // of 3/4 to adjust for the actual centroid position in each pyramid.
110             final double centroidScale = 1.0 / (4 * size);
111             return Vector3D.of(
112                     sumX * centroidScale,
113                     sumY * centroidScale,
114                     sumZ * centroidScale);
115         }
116 
117         return null;
118     }
119 
120     /** {@inheritDoc} */
121     @Override
122     public Split<ConvexVolume> split(final Hyperplane<Vector3D> splitter) {
123         return splitInternal(splitter, this, PlaneConvexSubset.class, ConvexVolume::new);
124     }
125 
126     /** {@inheritDoc} */
127     @Override
128     public RegionBSPTree3D toTree() {
129         return RegionBSPTree3D.from(getBoundaries(), true);
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     public PlaneConvexSubset trim(final HyperplaneConvexSubset<Vector3D> convexSubset) {
135         return (PlaneConvexSubset) super.trim(convexSubset);
136     }
137 
138     /** Return a new instance transformed by the argument.
139      * @param transform transform to apply
140      * @return a new instance transformed by the argument
141      */
142     public ConvexVolume transform(final Transform<Vector3D> transform) {
143         return transformInternal(transform, this, PlaneConvexSubset.class, ConvexVolume::new);
144     }
145 
146     /** Return an instance representing the full 3D volume.
147      * @return an instance representing the full 3D volume.
148      */
149     public static ConvexVolume full() {
150         return FULL;
151     }
152 
153     /** Create a convex volume formed by the intersection of the negative half-spaces of the
154      * given bounding planes. The returned instance represents the volume that is on the
155      * minus side of all of the given plane. Note that this method does not support volumes
156      * of zero size (ie, infinitely thin volumes or points.)
157      * @param planes planes used to define the convex area
158      * @return a new convex volume instance representing the volume on the minus side of all
159      *      of the bounding plane or an instance representing the full space if the collection
160      *      is empty
161      * @throws IllegalArgumentException if the given set of bounding planes do not form a convex volume,
162      *      meaning that there is no region that is on the minus side of all of the bounding planes.
163      */
164     public static ConvexVolume fromBounds(final Plane... planes) {
165         return fromBounds(Arrays.asList(planes));
166     }
167 
168     /** Create a convex volume formed by the intersection of the negative half-spaces of the
169      * given bounding planes. The returned instance represents the volume that is on the
170      * minus side of all of the given plane. Note that this method does not support volumes
171      * of zero size (ie, infinitely thin volumes or points.)
172      * @param boundingPlanes planes used to define the convex area
173      * @return a new convex volume instance representing the volume on the minus side of all
174      *      of the bounding plane or an instance representing the full space if the collection
175      *      is empty
176      * @throws IllegalArgumentException if the given set of bounding planes do not form a convex volume,
177      *      meaning that there is no region that is on the minus side of all of the bounding planes.
178      */
179     public static ConvexVolume fromBounds(final Iterable<? extends Plane> boundingPlanes) {
180         final List<PlaneConvexSubset> facets = new ConvexRegionBoundaryBuilder<>(PlaneConvexSubset.class)
181                 .build(boundingPlanes);
182         return facets.isEmpty() ? full() : new ConvexVolume(facets);
183     }
184 }