blob: e272193125788208a0309be6d0eb1b851447518a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.geometry.euclidean.twod;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.AbstractConvexHyperplaneBoundedRegion;
import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
/** Class representing a finite or infinite convex area in Euclidean 2D space.
* The boundaries of this area, if any, are composed of line segments.
*/
public final class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D, Segment> {
/** Instance representing the full 2D plane. */
private static final ConvexArea FULL = new ConvexArea(Collections.emptyList());
/** Simple constructor. Callers are responsible for ensuring that the given path
* represents the boundary of a convex area. No validation is performed.
* @param boundaries the boundaries of the convex area
*/
private ConvexArea(final List<Segment> boundaries) {
super(boundaries);
}
/** Get the connected line segment paths comprising the boundary of the area. The
* segments are oriented so that their minus sides point toward the interior of the
* region. The size of the returned list is
* <ul>
* <li><strong>0</strong> if the convex area is full,</li>
* <li><strong>1</strong> if at least one boundary is present and
* a single path can connect all segments (this will be the case
* for most instances), and</li>
* <li><strong>2</strong> if only two boundaries exist and they are
* parallel to each other (in which case they cannot be connected
* as a single path).</li>
* </ul>
* @return the line segment paths comprising the boundary of the area.
*/
public List<Polyline> getBoundaryPaths() {
return InteriorAngleSegmentConnector.connectMinimized(getBoundaries());
}
/** Get the vertices for the area. The vertices lie at the intersections of the
* area bounding lines.
* @return the vertices for the area
*/
public List<Vector2D> getVertices() {
final List<Polyline> path = getBoundaryPaths();
// we will only have vertices if we have a single path; otherwise, we have a full
// area or two non-intersecting infinite segments
if (path.size() == 1) {
return path.get(0).getVertices();
}
return Collections.emptyList();
}
/** Return a new instance transformed by the argument.
* @param transform transform to apply
* @return a new instance transformed by the argument
*/
public ConvexArea transform(final Transform<Vector2D> transform) {
return transformInternal(transform, this, Segment.class, ConvexArea::new);
}
/** {@inheritDoc} */
@Override
public Segment trim(final ConvexSubHyperplane<Vector2D> convexSubHyperplane) {
return (Segment) super.trim(convexSubHyperplane);
}
/** {@inheritDoc} */
@Override
public double getSize() {
if (isFull()) {
return Double.POSITIVE_INFINITY;
}
double quadrilateralAreaSum = 0.0;
for (Segment segment : getBoundaries()) {
if (segment.isInfinite()) {
return Double.POSITIVE_INFINITY;
}
quadrilateralAreaSum += segment.getStartPoint().signedArea(segment.getEndPoint());
}
return 0.5 * quadrilateralAreaSum;
}
/** {@inheritDoc} */
@Override
public Vector2D getBarycenter() {
List<Segment> boundaries = getBoundaries();
double quadrilateralAreaSum = 0.0;
double scaledSumX = 0.0;
double scaledSumY = 0.0;
double signedArea;
Vector2D startPoint;
Vector2D endPoint;
for (Segment seg : boundaries) {
if (seg.isInfinite()) {
// infinite => no barycenter
return null;
}
startPoint = seg.getStartPoint();
endPoint = seg.getEndPoint();
signedArea = startPoint.signedArea(endPoint);
quadrilateralAreaSum += signedArea;
scaledSumX += signedArea * (startPoint.getX() + endPoint.getX());
scaledSumY += signedArea * (startPoint.getY() + endPoint.getY());
}
if (quadrilateralAreaSum > 0) {
return Vector2D.of(scaledSumX, scaledSumY).multiply(1.0 / (3.0 * quadrilateralAreaSum));
}
return null;
}
/** {@inheritDoc} */
@Override
public Split<ConvexArea> split(final Hyperplane<Vector2D> splitter) {
return splitInternal(splitter, this, Segment.class, ConvexArea::new);
}
/** Return a BSP tree instance representing the same region as the current instance.
* @return a BSP tree instance representing the same region as the current instance
*/
public RegionBSPTree2D toTree() {
return RegionBSPTree2D.from(this);
}
/** Return an instance representing the full 2D area.
* @return an instance representing the full 2D area.
*/
public static ConvexArea full() {
return FULL;
}
/** Construct a convex area by creating lines between adjacent vertices. The vertices must be given in a
* counter-clockwise around order the interior of the shape. If the area is intended to be closed, the
* beginning point must be repeated at the end of the path.
* @param vertices vertices to use to construct the area
* @param precision precision context used to create new line instances
* @return a convex area constructed using lines between adjacent vertices
* @see #fromVertexLoop(Collection, DoublePrecisionContext)
* @see #fromVertices(Collection, boolean, DoublePrecisionContext)
*/
public static ConvexArea fromVertices(final Collection<Vector2D> vertices,
final DoublePrecisionContext precision) {
return fromVertices(vertices, false, precision);
}
/** Construct a convex area by creating lines between adjacent vertices. An implicit line is created between the
* last vertex given and the first one. The vertices must be given in a counter-clockwise around order the interior
* of the shape.
* @param vertices vertices to use to construct the area
* @param precision precision context used to create new line instances
* @return a convex area constructed using lines between adjacent vertices
* @see #fromVertices(Collection, DoublePrecisionContext)
* @see #fromVertices(Collection, boolean, DoublePrecisionContext)
*/
public static ConvexArea fromVertexLoop(final Collection<Vector2D> vertices,
final DoublePrecisionContext precision) {
return fromVertices(vertices, true, precision);
}
/** Construct a convex area from lines between adjacent vertices.
* @param vertices vertices to use to construct the area
* @param close if true, an additional line will be created between the last and first vertex
* @param precision precision context used to create new line instances
* @return a convex area constructed using lines between adjacent vertices
*/
public static ConvexArea fromVertices(final Collection<Vector2D> vertices, boolean close,
final DoublePrecisionContext precision) {
if (vertices.isEmpty()) {
return full();
}
final List<Line> lines = new ArrayList<>();
Vector2D first = null;
Vector2D prev = null;
Vector2D cur = null;
for (Vector2D vertex : vertices) {
cur = vertex;
if (first == null) {
first = cur;
}
if (prev != null && !cur.eq(prev, precision)) {
lines.add(Line.fromPoints(prev, cur, precision));
}
prev = cur;
}
if (close && cur != null && !cur.eq(first, precision)) {
lines.add(Line.fromPoints(cur, first, precision));
}
if (!vertices.isEmpty() && lines.isEmpty()) {
throw new IllegalStateException("Unable to create convex area: only a single unique vertex provided");
}
return fromBounds(lines);
}
/** Construct a convex area from a line segment path. The area represents the intersection of all of the negative
* half-spaces of the lines in the path. The boundaries of the returned area may therefore not match the line
* segments in the path.
* @param path path to construct the area from
* @return a convex area constructed from the lines in the given path
*/
public static ConvexArea fromPath(final Polyline path) {
final List<Line> lines = new ArrayList<>();
for (Segment segment : path) {
lines.add(segment.getLine());
}
return fromBounds(lines);
}
/** Create a convex area formed by the intersection of the negative half-spaces of the
* given bounding lines. The returned instance represents the area that is on the
* minus side of all of the given lines. Note that this method does not support areas
* of zero size (ie, infinitely thin areas or points.)
* @param bounds lines used to define the convex area
* @return a new convex area instance representing the area on the minus side of all
* of the bounding lines or an instance representing the full area if no lines are
* given
* @throws org.apache.commons.geometry.core.exception.GeometryException if the given set of bounding lines do
* not form a convex area, meaning that there is no region that is on the minus side of all of the bounding
* lines.
*/
public static ConvexArea fromBounds(final Line... bounds) {
return fromBounds(Arrays.asList(bounds));
}
/** Create a convex area formed by the intersection of the negative half-spaces of the
* given bounding lines. The returned instance represents the area that is on the
* minus side of all of the given lines. Note that this method does not support areas
* of zero size (ie, infinitely thin areas or points.)
* @param bounds lines used to define the convex area
* @return a new convex area instance representing the area on the minus side of all
* of the bounding lines or an instance representing the full area if the collection
* is empty
* @throws org.apache.commons.geometry.core.exception.GeometryException if the given set of bounding lines do
* not form a convex area, meaning that there is no region that is on the minus side of all of the bounding
* lines.
*/
public static ConvexArea fromBounds(final Iterable<Line> bounds) {
final List<Segment> segments = new ConvexRegionBoundaryBuilder<>(Segment.class).build(bounds);
return segments.isEmpty() ? full() : new ConvexArea(segments);
}
}