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 ray in 2D Euclidean space. A ray is a portion of a line consisting of
025 * a single start point and extending to infinity along the direction of the line.
026 *
027 * <p>Instances of this class are guaranteed to be immutable.</p>
028 * @see ReverseRay
029 * @see Lines
030 */
031public final class Ray extends LineConvexSubset {
032
033    /** The start point for the ray. */
034    private final Vector2D startPoint;
035
036    /** Construct a ray from a line and a start point. Callers are responsible for ensuring that the
037     * given point lies on the line. No validation is performed.
038     * @param line line for the ray
039     * @param startPoint start point for the ray
040     */
041    Ray(final Line line, final Vector2D startPoint) {
042        super(line);
043
044        this.startPoint = startPoint;
045    }
046
047    /** {@inheritDoc}
048     *
049     * <p>This method always returns {@code false}.</p>
050     */
051    @Override
052    public boolean isFull() {
053        return false;
054    }
055
056    /** {@inheritDoc}
057    *
058    * <p>This method always returns {@code true}.</p>
059    */
060    @Override
061    public boolean isInfinite() {
062        return true;
063    }
064
065    /** {@inheritDoc}
066    *
067    * <p>This method always returns {@code false}.</p>
068    */
069    @Override
070    public boolean isFinite() {
071        return false;
072    }
073
074    /** {@inheritDoc}
075    *
076    * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
077    */
078    @Override
079    public double getSize() {
080        return Double.POSITIVE_INFINITY;
081    }
082
083    /** {@inheritDoc}
084     *
085     * <p>This method always returns {@code null}.</p>
086     */
087    @Override
088    public Vector2D getCentroid() {
089        return null;
090    }
091
092    @Override
093    public Vector2D getStartPoint() {
094        return startPoint;
095    }
096
097    /** {@inheritDoc} */
098    @Override
099    public double getSubspaceStart() {
100        return getLine().abscissa(startPoint);
101    }
102
103    /** {@inheritDoc}
104    *
105    * <p>This method always returns {@code null}.</p>
106    */
107    @Override
108    public Vector2D getEndPoint() {
109        return null;
110    }
111
112    /** {@inheritDoc}
113     *
114     * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
115     */
116    @Override
117    public double getSubspaceEnd() {
118        return Double.POSITIVE_INFINITY;
119    }
120
121    /** {@inheritDoc}
122     *
123     * <p>This method always returns {@code null}.</p>
124     */
125    @Override
126    public Bounds2D getBounds() {
127        return null; // infinite; no bounds
128    }
129
130    /** Get the direction of the ray. This is a convenience method for {@code ray.getLine().getDirection()}.
131     * @return the direction of the ray
132     */
133    public Vector2D getDirection() {
134        return getLine().getDirection();
135    }
136
137    /** {@inheritDoc} */
138    @Override
139    public Ray transform(final Transform<Vector2D> transform) {
140        final Line tLine = getLine().transform(transform);
141        final Vector2D tStart = transform.apply(getStartPoint());
142
143        return new Ray(tLine, tStart);
144    }
145
146    /** {@inheritDoc} */
147    @Override
148    public ReverseRay reverse() {
149        return new ReverseRay(getLine().reverse(), startPoint);
150    }
151
152    /** {@inheritDoc} */
153    @Override
154    public String toString() {
155        final StringBuilder sb = new StringBuilder();
156        sb.append(getClass().getSimpleName())
157            .append("[startPoint= ")
158            .append(getStartPoint())
159            .append(", direction= ")
160            .append(getLine().getDirection())
161            .append(']');
162
163        return sb.toString();
164    }
165
166    /** {@inheritDoc} */
167    @Override
168    RegionLocation classifyAbscissa(final double abscissa) {
169        final int cmp = getPrecision().compare(abscissa, getSubspaceStart());
170        if (cmp > 0) {
171            return RegionLocation.INSIDE;
172        } else if (cmp == 0) {
173            return RegionLocation.BOUNDARY;
174        }
175
176        return RegionLocation.OUTSIDE;
177    }
178
179    /** {@inheritDoc} */
180    @Override
181    double closestAbscissa(final double abscissa) {
182        return Math.max(getSubspaceStart(), abscissa);
183    }
184
185    /** {@inheritDoc} */
186    @Override
187    Split<LineConvexSubset> splitOnIntersection(final Line splitter, final Vector2D intersection) {
188        final Line line = getLine();
189        final Precision.DoubleEquivalence splitterPrecision = splitter.getPrecision();
190
191        final int startCmp = splitterPrecision.compare(splitter.offset(startPoint), 0.0);
192        final boolean pointsTowardPlus = splitter.getOffsetDirection().dot(line.getDirection()) >= 0.0;
193
194        if (pointsTowardPlus && startCmp > -1) {
195            // entirely on plus side
196            return new Split<>(null, this);
197        } else if (!pointsTowardPlus && startCmp < 1) {
198            // entirely on minus side
199            return new Split<>(this, null);
200        }
201
202        // we're going to be split
203        final Segment splitSeg = new Segment(line, startPoint, intersection);
204        final Ray splitRay = new Ray(line, intersection);
205
206        final LineConvexSubset minus = (startCmp > 0) ? splitRay : splitSeg;
207        final LineConvexSubset plus = (startCmp > 0) ? splitSeg : splitRay;
208
209        return new Split<>(minus, plus);
210    }
211}