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<Plane> boundingPlanes) {
    180         final List<PlaneConvexSubset> facets = new ConvexRegionBoundaryBuilder<>(PlaneConvexSubset.class)
    181                 .build(boundingPlanes);
    182         return facets.isEmpty() ? full() : new ConvexVolume(facets);
    183     }
    184 }