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.twod;
018
019import org.apache.commons.geometry.core.RegionLocation;
020import org.apache.commons.geometry.core.Transform;
021import org.apache.commons.geometry.core.partitioning.Split;
022import org.apache.commons.numbers.core.Precision;
023
024/** Class representing a line segment in 2D Euclidean space. A line segment is a portion of
025 * a line with finite start and end points.
026 *
027 * <p>Instances of this class are guaranteed to be immutable.</p>
028 * @see Lines
029 * @see <a href="https://en.wikipedia.org/wiki/Line_segment">Line Segment</a>
030 */
031public final class Segment extends LineConvexSubset {
032
033    /** Start point for the segment. */
034    private final Vector2D startPoint;
035
036    /** End point for the segment. */
037    private final Vector2D endPoint;
038
039    /** Construct a new instance from a line and two points on the line. Callers are responsible for
040     * ensuring that the given points lie on the line and are in order of increasing abscissa.
041     * No validation is performed.
042     * @param line line for the segment
043     * @param startPoint segment start point
044     * @param endPoint segment end point
045     */
046    Segment(final Line line, final Vector2D startPoint, final Vector2D endPoint) {
047        super(line);
048
049        this.startPoint = startPoint;
050        this.endPoint = endPoint;
051    }
052
053    /** {@inheritDoc}
054     *
055     * <p>This method always returns {@code false}.</p>
056     */
057    @Override
058    public boolean isFull() {
059        return false;
060    }
061
062    /** {@inheritDoc}
063     *
064     * <p>This method always returns {@code false}.</p>
065     */
066    @Override
067    public boolean isInfinite() {
068        return false;
069    }
070
071    /** {@inheritDoc}
072     *
073     * <p>This method always returns {@code true}.</p>
074     */
075    @Override
076    public boolean isFinite() {
077        return true;
078    }
079
080    /** {@inheritDoc} */
081    @Override
082    public double getSize() {
083        return startPoint.distance(endPoint);
084    }
085
086    /** {@inheritDoc} */
087    @Override
088    public Vector2D getCentroid() {
089        return startPoint.lerp(endPoint, 0.5);
090    }
091
092    /** {@inheritDoc} */
093    @Override
094    public Vector2D getStartPoint() {
095        return startPoint;
096    }
097
098    /** {@inheritDoc} */
099    @Override
100    public double getSubspaceStart() {
101        return getLine().abscissa(startPoint);
102    }
103
104    /** {@inheritDoc} */
105    @Override
106    public Vector2D getEndPoint() {
107        return endPoint;
108    }
109
110    /** {@inheritDoc} */
111    @Override
112    public double getSubspaceEnd() {
113        return getLine().abscissa(endPoint);
114    }
115
116    /** {@inheritDoc} */
117    @Override
118    public Bounds2D getBounds() {
119        return Bounds2D.builder()
120                .add(startPoint)
121                .add(endPoint)
122                .build();
123    }
124
125    /** {@inheritDoc} */
126    @Override
127    public Segment transform(final Transform<Vector2D> transform) {
128        final Vector2D t1 = transform.apply(getStartPoint());
129        final Vector2D t2 = transform.apply(getEndPoint());
130
131        final Line tLine = getLine().transform(transform);
132
133        return new Segment(tLine, t1, t2);
134    }
135
136    /** {@inheritDoc} */
137    @Override
138    public Segment reverse() {
139        return new Segment(getLine().reverse(), endPoint, startPoint);
140    }
141
142    /** {@inheritDoc} */
143    @Override
144    public String toString() {
145        final StringBuilder sb = new StringBuilder();
146        sb.append(getClass().getSimpleName())
147            .append("[startPoint= ")
148            .append(getStartPoint())
149            .append(", endPoint= ")
150            .append(getEndPoint())
151            .append(']');
152
153        return sb.toString();
154    }
155
156    /** {@inheritDoc} */
157    @Override
158    RegionLocation classifyAbscissa(final double abscissa) {
159        final Precision.DoubleEquivalence precision = getPrecision();
160        final int startCmp = precision.compare(abscissa, getSubspaceStart());
161        if (startCmp > 0) {
162            final int endCmp = precision.compare(abscissa, getSubspaceEnd());
163            if (endCmp < 0) {
164                return RegionLocation.INSIDE;
165            } else if (endCmp == 0) {
166                return RegionLocation.BOUNDARY;
167            }
168        } else if (startCmp == 0) {
169            return RegionLocation.BOUNDARY;
170        }
171
172        return RegionLocation.OUTSIDE;
173    }
174
175    /** {@inheritDoc} */
176    @Override
177    double closestAbscissa(final double abscissa) {
178        return Math.max(getSubspaceStart(), Math.min(getSubspaceEnd(), abscissa));
179    }
180
181    /** {@inheritDoc} */
182    @Override
183    Split<LineConvexSubset> splitOnIntersection(final Line splitter, final Vector2D intersection) {
184        final Line line = getLine();
185
186        final Precision.DoubleEquivalence splitterPrecision = splitter.getPrecision();
187
188        final int startCmp = splitterPrecision.compare(splitter.offset(startPoint), 0.0);
189        final int endCmp = splitterPrecision.compare(splitter.offset(endPoint), 0.0);
190
191        if (startCmp == 0 && endCmp == 0) {
192            // the entire segment is directly on the splitter line
193            return new Split<>(null, null);
194        } else if (startCmp < 1 && endCmp < 1) {
195            // the entire segment is on the minus side
196            return new Split<>(this, null);
197        } else if (startCmp > -1 && endCmp > -1) {
198            // the entire segment is on the plus side
199            return new Split<>(null, this);
200        }
201
202        // we need to split the line
203        final Segment startSegment = new Segment(line, startPoint, intersection);
204        final Segment endSegment = new Segment(line, intersection, endPoint);
205
206        final Segment minus = (startCmp > 0) ? endSegment : startSegment;
207        final Segment plus = (startCmp > 0) ? startSegment : endSegment;
208
209        return new Split<>(minus, plus);
210    }
211}