ConvexVolume.java

  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. import java.util.Arrays;
  19. import java.util.Collections;
  20. import java.util.List;
  21. import java.util.stream.Stream;

  22. import org.apache.commons.geometry.core.Transform;
  23. import org.apache.commons.geometry.core.partitioning.AbstractConvexHyperplaneBoundedRegion;
  24. import org.apache.commons.geometry.core.partitioning.Hyperplane;
  25. import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
  26. import org.apache.commons.geometry.core.partitioning.Split;

  27. /** Class representing a finite or infinite convex volume in Euclidean 3D space.
  28.  * The boundaries of this area, if any, are composed of plane convex subsets.
  29.  */
  30. public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D, PlaneConvexSubset>
  31.     implements BoundarySource3D {

  32.     /** Instance representing the full 3D volume. */
  33.     private static final ConvexVolume FULL = new ConvexVolume(Collections.emptyList());

  34.     /** Simple constructor. Callers are responsible for ensuring that the given path
  35.      * represents the boundary of a convex area. No validation is performed.
  36.      * @param boundaries the boundaries of the convex area
  37.      */
  38.     protected ConvexVolume(final List<PlaneConvexSubset> boundaries) {
  39.         super(boundaries);
  40.     }

  41.     /** {@inheritDoc} */
  42.     @Override
  43.     public Stream<PlaneConvexSubset> boundaryStream() {
  44.         return getBoundaries().stream();
  45.     }

  46.     /** {@inheritDoc} */
  47.     @Override
  48.     public double getSize() {
  49.         if (isFull()) {
  50.             return Double.POSITIVE_INFINITY;
  51.         }

  52.         double volumeSum = 0.0;

  53.         for (final PlaneConvexSubset boundary : getBoundaries()) {
  54.             if (boundary.isInfinite()) {
  55.                 return Double.POSITIVE_INFINITY;
  56.             }

  57.             final Plane boundaryPlane = boundary.getPlane();
  58.             final double boundaryArea = boundary.getSize();
  59.             final Vector3D boundaryCentroid = boundary.getCentroid();

  60.             volumeSum += boundaryArea * boundaryCentroid.dot(boundaryPlane.getNormal());
  61.         }

  62.         return volumeSum / 3.0;
  63.     }

  64.     /** {@inheritDoc} */
  65.     @Override
  66.     public Vector3D getCentroid() {
  67.         double volumeSum = 0.0;

  68.         double sumX = 0.0;
  69.         double sumY = 0.0;
  70.         double sumZ = 0.0;

  71.         for (final PlaneConvexSubset boundary : getBoundaries()) {
  72.             if (boundary.isInfinite()) {
  73.                 return null;
  74.             }

  75.             final Plane boundaryPlane = boundary.getPlane();
  76.             final double boundaryArea = boundary.getSize();
  77.             final Vector3D boundaryCentroid = boundary.getCentroid();

  78.             final double scaledVolume = boundaryArea * boundaryCentroid.dot(boundaryPlane.getNormal());

  79.             volumeSum += scaledVolume;

  80.             sumX += scaledVolume * boundaryCentroid.getX();
  81.             sumY += scaledVolume * boundaryCentroid.getY();
  82.             sumZ += scaledVolume * boundaryCentroid.getZ();
  83.         }

  84.         if (volumeSum > 0) {
  85.             final double size = volumeSum / 3.0;

  86.             // Since the volume we used when adding together the boundary contributions
  87.             // was 3x the actual pyramid size, we'll multiply by 1/4 here instead
  88.             // of 3/4 to adjust for the actual centroid position in each pyramid.
  89.             final double centroidScale = 1.0 / (4 * size);
  90.             return Vector3D.of(
  91.                     sumX * centroidScale,
  92.                     sumY * centroidScale,
  93.                     sumZ * centroidScale);
  94.         }

  95.         return null;
  96.     }

  97.     /** {@inheritDoc} */
  98.     @Override
  99.     public Split<ConvexVolume> split(final Hyperplane<Vector3D> splitter) {
  100.         return splitInternal(splitter, this, PlaneConvexSubset.class, ConvexVolume::new);
  101.     }

  102.     /** {@inheritDoc} */
  103.     @Override
  104.     public RegionBSPTree3D toTree() {
  105.         return RegionBSPTree3D.from(getBoundaries(), true);
  106.     }

  107.     /** {@inheritDoc} */
  108.     @Override
  109.     public PlaneConvexSubset trim(final HyperplaneConvexSubset<Vector3D> convexSubset) {
  110.         return (PlaneConvexSubset) super.trim(convexSubset);
  111.     }

  112.     /** Return a new instance transformed by the argument.
  113.      * @param transform transform to apply
  114.      * @return a new instance transformed by the argument
  115.      */
  116.     public ConvexVolume transform(final Transform<Vector3D> transform) {
  117.         return transformInternal(transform, this, PlaneConvexSubset.class, ConvexVolume::new);
  118.     }

  119.     /** Return an instance representing the full 3D volume.
  120.      * @return an instance representing the full 3D volume.
  121.      */
  122.     public static ConvexVolume full() {
  123.         return FULL;
  124.     }

  125.     /** Create a convex volume formed by the intersection of the negative half-spaces of the
  126.      * given bounding planes. The returned instance represents the volume that is on the
  127.      * minus side of all of the given plane. Note that this method does not support volumes
  128.      * of zero size (ie, infinitely thin volumes or points.)
  129.      * @param planes planes used to define the convex area
  130.      * @return a new convex volume instance representing the volume on the minus side of all
  131.      *      of the bounding plane or an instance representing the full space if the collection
  132.      *      is empty
  133.      * @throws IllegalArgumentException if the given set of bounding planes do not form a convex volume,
  134.      *      meaning that there is no region that is on the minus side of all of the bounding planes.
  135.      */
  136.     public static ConvexVolume fromBounds(final Plane... planes) {
  137.         return fromBounds(Arrays.asList(planes));
  138.     }

  139.     /** Create a convex volume formed by the intersection of the negative half-spaces of the
  140.      * given bounding planes. The returned instance represents the volume that is on the
  141.      * minus side of all of the given plane. Note that this method does not support volumes
  142.      * of zero size (ie, infinitely thin volumes or points.)
  143.      * @param boundingPlanes planes used to define the convex area
  144.      * @return a new convex volume instance representing the volume on the minus side of all
  145.      *      of the bounding plane or an instance representing the full space if the collection
  146.      *      is empty
  147.      * @throws IllegalArgumentException if the given set of bounding planes do not form a convex volume,
  148.      *      meaning that there is no region that is on the minus side of all of the bounding planes.
  149.      */
  150.     public static ConvexVolume fromBounds(final Iterable<? extends Plane> boundingPlanes) {
  151.         final List<PlaneConvexSubset> facets = new ConvexRegionBoundaryBuilder<>(PlaneConvexSubset.class)
  152.                 .build(boundingPlanes);
  153.         return facets.isEmpty() ? full() : new ConvexVolume(facets);
  154.     }
  155. }