| /* |
| * 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.Arrays; |
| import java.util.List; |
| |
| import org.apache.commons.geometry.core.Transform; |
| 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; |
| import org.apache.commons.geometry.euclidean.oned.FunctionTransform1D; |
| import org.apache.commons.geometry.euclidean.oned.Interval; |
| import org.apache.commons.geometry.euclidean.oned.Vector1D; |
| import org.apache.commons.geometry.euclidean.twod.Line.SubspaceTransform; |
| |
| /** <p>Class representing a line segment in 2D Euclidean space. Segments |
| * need not be finite, in which case the start or end point (or both) |
| * will be null.</p> |
| * |
| * <p>Instances of this class are guaranteed to be immutable.</p> |
| */ |
| public final class Segment extends AbstractSubLine |
| implements ConvexSubHyperplane<Vector2D> { |
| /** String used to indicate the start point of the segment in the toString() representation. */ |
| private static final String START_STR = "start= "; |
| |
| /** String used to indicate the direction the segment in the toString() representation. */ |
| private static final String DIR_STR = "direction= "; |
| |
| /** String used to indicate the end point of the segment in the toString() representation. */ |
| private static final String END_STR = "end= "; |
| |
| /** String used as a separator value in the toString() representation. */ |
| private static final String SEP_STR = ", "; |
| |
| /** The interval representing the region of the line contained in |
| * the line segment. |
| */ |
| private final Interval interval; |
| |
| /** Construct a line segment from an underlying line and a 1D interval |
| * on it. |
| * @param line the underlying line |
| * @param interval 1D interval on the line defining the line segment |
| */ |
| private Segment(final Line line, final Interval interval) { |
| super(line); |
| |
| this.interval = interval; |
| } |
| |
| /** Get the start value in the 1D subspace of the line. |
| * @return the start value in the 1D subspace of the line. |
| */ |
| public double getSubspaceStart() { |
| return interval.getMin(); |
| } |
| |
| /** Get the end value in the 1D subspace of the line. |
| * @return the end value in the 1D subspace of the line |
| */ |
| public double getSubspaceEnd() { |
| return interval.getMax(); |
| } |
| |
| /** Get the start point of the line segment or null if no start point |
| * exists (ie, the segment is infinite). |
| * @return the start point of the line segment or null if no start point |
| * exists |
| */ |
| public Vector2D getStartPoint() { |
| return interval.hasMinBoundary() ? getLine().toSpace(interval.getMin()) : null; |
| } |
| |
| /** Get the end point of the line segment or null if no end point |
| * exists (ie, the segment is infinite). |
| * @return the end point of the line segment or null if no end point |
| * exists |
| */ |
| public Vector2D getEndPoint() { |
| return interval.hasMaxBoundary() ? getLine().toSpace(interval.getMax()) : null; |
| } |
| |
| /** Return the 1D interval for the line segment. |
| * @return the 1D interval for the line segment |
| * @see #getSubspaceRegion() |
| */ |
| public Interval getInterval() { |
| return interval; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isInfinite() { |
| return interval.isInfinite(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isFinite() { |
| return interval.isFinite(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Interval getSubspaceRegion() { |
| return getInterval(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public List<Segment> toConvex() { |
| return Arrays.asList(this); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Split<Segment> split(final Hyperplane<Vector2D> splitter) { |
| return splitInternal(splitter, this, (line, region) -> new Segment(line, (Interval) region)); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Segment transform(Transform<Vector2D> transform) { |
| final Line line = getLine(); |
| final SubspaceTransform st = line.subspaceTransform(transform); |
| |
| return fromInterval(st.getLine(), interval.transform(st.getTransform())); |
| } |
| |
| /** Get the unique intersection of this segment with the given line. Null is |
| * returned if no unique intersection point exists (ie, the lines are |
| * parallel or coincident) or the line does not intersect the segment. |
| * @param line line to intersect with this segment |
| * @return the unique intersection point between the line and this segment |
| * or null if no such point exists. |
| * @see Line#intersection(Line) |
| */ |
| public Vector2D intersection(final Line line) { |
| final Vector2D pt = getLine().intersection(line); |
| return (pt != null && contains(pt)) ? pt : null; |
| } |
| |
| /** Get the unique intersection of this instance with the given segment. Null |
| * is returned if the lines containing the segments do not have a unique intersection |
| * point (ie, they are parallel or coincident) or the intersection point is unique |
| * but in not contained in both segments. |
| * @param segment segment to intersect with |
| * @return the unique intersection point between this segment and the argument or |
| * null if no such point exists. |
| * @see Line#intersection(Line) |
| */ |
| public Vector2D intersection(final Segment segment) { |
| final Vector2D pt = intersection(segment.getLine()); |
| return (pt != null && segment.contains(pt)) ? pt : null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Segment reverse() { |
| final Interval reversedInterval = interval.transform(FunctionTransform1D.from(Vector1D::negate)); |
| return fromInterval(getLine().reverse(), reversedInterval); |
| } |
| |
| /** Return a string representation of the segment. |
| * |
| * <p>In order to keep the representation short but informative, the exact format used |
| * depends on the properties of the instance, as demonstrated in the examples |
| * below. |
| * <ul> |
| * <li>Infinite segment - |
| * {@code "Segment[lineOrigin= (0.0, 0.0), lineDirection= (1.0, 0.0)]"}</li> |
| * <li>Start point but no end point - |
| * {@code "Segment[start= (0.0, 0.0), direction= (1.0, 0.0)]"}</li> |
| * <li>End point but no start point - |
| * {@code "Segment[direction= (1.0, 0.0), end= (0.0, 0.0)]"}</li> |
| * <li>Start point and end point - |
| * {@code "Segment[start= (0.0, 0.0), end= (1.0, 0.0)]"}</li> |
| * </ul> |
| */ |
| @Override |
| public String toString() { |
| final Vector2D startPoint = getStartPoint(); |
| final Vector2D endPoint = getEndPoint(); |
| |
| final StringBuilder sb = new StringBuilder(); |
| sb.append(this.getClass().getSimpleName()) |
| .append('['); |
| |
| if (startPoint != null && endPoint != null) { |
| sb.append(START_STR) |
| .append(startPoint) |
| .append(SEP_STR) |
| .append(END_STR) |
| .append(endPoint); |
| } else if (startPoint != null) { |
| sb.append(START_STR) |
| .append(startPoint) |
| .append(SEP_STR) |
| .append(DIR_STR) |
| .append(getLine().getDirection()); |
| } else if (endPoint != null) { |
| sb.append(DIR_STR) |
| .append(getLine().getDirection()) |
| .append(SEP_STR) |
| .append(END_STR) |
| .append(endPoint); |
| } else { |
| final Line line = getLine(); |
| |
| sb.append("lineOrigin= ") |
| .append(line.getOrigin()) |
| .append(", lineDirection= ") |
| .append(line.getDirection()); |
| } |
| |
| sb.append(']'); |
| |
| return sb.toString(); |
| } |
| |
| /** Create a line segment between two points. The underlying line points in the direction from {@code start} |
| * to {@code end}. |
| * @param start start point for the line segment |
| * @param end end point for the line segment |
| * @param precision precision context used to determine floating point equality |
| * @return a new line segment between {@code start} and {@code end}. |
| */ |
| public static Segment fromPoints(final Vector2D start, final Vector2D end, final DoublePrecisionContext precision) { |
| final Line line = Line.fromPoints(start, end, precision); |
| return fromPointsOnLine(line, start, end); |
| } |
| |
| /** Construct a line segment from a starting point and a direction that the line should extend to |
| * infinity from. This is equivalent to constructing a ray. |
| * @param start start point for the segment |
| * @param direction direction that the line should extend from the segment |
| * @param precision precision context used to determine floating point equality |
| * @return a new line segment starting from the given point and extending to infinity in the |
| * specified direction |
| */ |
| public static Segment fromPointAndDirection(final Vector2D start, final Vector2D direction, |
| final DoublePrecisionContext precision) { |
| final Line line = Line.fromPointAndDirection(start, direction, precision); |
| return fromInterval(line, Interval.min(line.toSubspace(start).getX(), precision)); |
| } |
| |
| /** Create a line segment from an underlying line and a 1D interval on the line. |
| * @param line the line that the line segment will belong to |
| * @param interval 1D interval on the line |
| * @return a line segment defined by the given line and interval |
| */ |
| public static Segment fromInterval(final Line line, final Interval interval) { |
| return new Segment(line, interval); |
| } |
| |
| /** Create a line segment from an underlying line and a 1D interval on the line. |
| * @param line the line that the line segment will belong to |
| * @param a first 1D location on the line |
| * @param b second 1D location on the line |
| * @return a line segment defined by the given line and interval |
| */ |
| public static Segment fromInterval(final Line line, final double a, final double b) { |
| return fromInterval(line, Interval.of(a, b, line.getPrecision())); |
| } |
| |
| /** Create a line segment from an underlying line and a 1D interval on the line. |
| * @param line the line that the line segment will belong to |
| * @param a first 1D point on the line; must not be null |
| * @param b second 1D point on the line; must not be null |
| * @return a line segment defined by the given line and interval |
| */ |
| public static Segment fromInterval(final Line line, final Vector1D a, final Vector1D b) { |
| return fromInterval(line, a.getX(), b.getX()); |
| } |
| |
| /** Create a new line segment from a line and points known to lie on the line. |
| * @param line the line that the line segment will belong to |
| * @param start line segment start point known to lie on the line |
| * @param end line segment end poitn known to lie on the line |
| * @return a new line segment created from the line and points |
| */ |
| private static Segment fromPointsOnLine(final Line line, final Vector2D start, final Vector2D end) { |
| final double subspaceStart = line.toSubspace(start).getX(); |
| final double subspaceEnd = line.toSubspace(end).getX(); |
| |
| return fromInterval(line, subspaceStart, subspaceEnd); |
| } |
| } |