001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.geometry.euclidean.threed; 018 019import java.util.Arrays; 020import java.util.Collections; 021import java.util.List; 022import java.util.stream.Stream; 023 024import org.apache.commons.geometry.core.Transform; 025import org.apache.commons.geometry.core.partitioning.AbstractConvexHyperplaneBoundedRegion; 026import org.apache.commons.geometry.core.partitioning.Hyperplane; 027import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset; 028import org.apache.commons.geometry.core.partitioning.Split; 029 030/** Class representing a finite or infinite convex volume in Euclidean 3D space. 031 * The boundaries of this area, if any, are composed of plane convex subsets. 032 */ 033public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D, PlaneConvexSubset> 034 implements BoundarySource3D { 035 036 /** Instance representing the full 3D volume. */ 037 private static final ConvexVolume FULL = new ConvexVolume(Collections.emptyList()); 038 039 /** Simple constructor. Callers are responsible for ensuring that the given path 040 * represents the boundary of a convex area. No validation is performed. 041 * @param boundaries the boundaries of the convex area 042 */ 043 protected ConvexVolume(final List<PlaneConvexSubset> boundaries) { 044 super(boundaries); 045 } 046 047 /** {@inheritDoc} */ 048 @Override 049 public Stream<PlaneConvexSubset> boundaryStream() { 050 return getBoundaries().stream(); 051 } 052 053 /** {@inheritDoc} */ 054 @Override 055 public double getSize() { 056 if (isFull()) { 057 return Double.POSITIVE_INFINITY; 058 } 059 060 double volumeSum = 0.0; 061 062 for (final PlaneConvexSubset boundary : getBoundaries()) { 063 if (boundary.isInfinite()) { 064 return Double.POSITIVE_INFINITY; 065 } 066 067 final Plane boundaryPlane = boundary.getPlane(); 068 final double boundaryArea = boundary.getSize(); 069 final Vector3D boundaryCentroid = boundary.getCentroid(); 070 071 volumeSum += boundaryArea * boundaryCentroid.dot(boundaryPlane.getNormal()); 072 } 073 074 return volumeSum / 3.0; 075 } 076 077 /** {@inheritDoc} */ 078 @Override 079 public Vector3D getCentroid() { 080 double volumeSum = 0.0; 081 082 double sumX = 0.0; 083 double sumY = 0.0; 084 double sumZ = 0.0; 085 086 for (final PlaneConvexSubset boundary : getBoundaries()) { 087 if (boundary.isInfinite()) { 088 return null; 089 } 090 091 final Plane boundaryPlane = boundary.getPlane(); 092 final double boundaryArea = boundary.getSize(); 093 final Vector3D boundaryCentroid = boundary.getCentroid(); 094 095 final double scaledVolume = boundaryArea * boundaryCentroid.dot(boundaryPlane.getNormal()); 096 097 volumeSum += scaledVolume; 098 099 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}