View Javadoc
1   package org.djutils.draw.line;
2   
3   import org.djutils.base.AngleUtil;
4   import org.djutils.draw.DrawRuntimeException;
5   import org.djutils.draw.Drawable2d;
6   import org.djutils.draw.Space2d;
7   import org.djutils.draw.bounds.Bounds2d;
8   import org.djutils.draw.point.Point2d;
9   import org.djutils.exceptions.Throw;
10  
11  /**
12   * Ray2d is a half-line; it has one end point with non-infinite coordinates; the other end point is infinitely far away.
13   * <p>
14   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
15   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
16   * </p>
17   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
18   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
19   */
20  public class Ray2d extends Point2d implements Drawable2d, Ray<Ray2d, Point2d, Space2d>
21  {
22      /** */
23      private static final long serialVersionUID = 20210119L;
24  
25      /** Phi; the angle from the positive X axis direction in radians. */
26      @SuppressWarnings("checkstyle:visibilitymodifier")
27      public final double phi;
28  
29      /**
30       * Construct a new Ray2d.
31       * @param x double; the x coordinate of the finite end point of the ray
32       * @param y double; the y coordinate of the finite end point of the ray
33       * @param phi double; the angle from the positive X axis direction in radians.
34       * @throws DrawRuntimeException when phi is NaN
35       */
36      public Ray2d(final double x, final double y, final double phi) throws DrawRuntimeException
37      {
38          super(x, y);
39          Throw.when(Double.isNaN(phi), DrawRuntimeException.class, "phi may not be NaN");
40          this.phi = phi;
41      }
42  
43      /**
44       * Construct a new Ray2d.
45       * @param point Point2d; the finite end point of the ray
46       * @param phi double; the angle from the positive X axis direction in radians.
47       * @throws NullPointerException when point is null
48       * @throws DrawRuntimeException when phi is NaN
49       */
50      public Ray2d(final Point2d point, final double phi) throws NullPointerException, DrawRuntimeException
51      {
52          this(Throw.whenNull(point, "point may not be null").x, point.y, phi);
53      }
54  
55      /**
56       * Construct a new Ray2d.
57       * @param x double; the x coordinate of the finite end point of the ray
58       * @param y double; the y coordinate of the finite end point of the ray
59       * @param throughX double; the x coordinate of another point on the ray
60       * @param throughY double; the y coordinate of another point on the ray
61       * @throws DrawRuntimeException when throughX == x and throughY == y
62       */
63      public Ray2d(final double x, final double y, final double throughX, final double throughY) throws DrawRuntimeException
64      {
65          super(x, y);
66          Throw.when(throughX == x && throughY == y, DrawRuntimeException.class,
67                  "the coordinates of the through points must differ from (x, y)");
68          this.phi = Math.atan2(throughY - y, throughX - x);
69      }
70  
71      /**
72       * Construct a new Ray2d.
73       * @param point Point2d; the finite end point of the ray
74       * @param throughX double; the x coordinate of another point on the ray
75       * @param throughY double; the y coordinate of another point on the ray
76       * @throws NullPointerException when point is null
77       * @throws DrawRuntimeException when throughX == point.x and throughY == point.y
78       */
79      public Ray2d(final Point2d point, final double throughX, final double throughY)
80              throws NullPointerException, DrawRuntimeException
81      {
82          this(Throw.whenNull(point, "point may not be null").x, point.y, throughX, throughY);
83      }
84  
85      /**
86       * Construct a new Ray2d.
87       * @param x double; the x coordinate of the finite end point of the ray
88       * @param y double; the y coordinate of the finite end point of the ray
89       * @param throughPoint Point2d; another point on the ray
90       * @throws NullPointerException when throughPoint is null
91       * @throws DrawRuntimeException when throughPoint is exactly at (x, y)
92       */
93      public Ray2d(final double x, final double y, final Point2d throughPoint) throws NullPointerException, DrawRuntimeException
94      {
95          this(x, y, Throw.whenNull(throughPoint, "througPoint may not be null").x, throughPoint.y);
96      }
97  
98      /**
99       * Construct a new Ray2d.
100      * @param point Point2d; the finite end point of the ray
101      * @param throughPoint Point2d; another point on the ray
102      * @throws NullPointerException when point is null or throughPoint is null
103      * @throws DrawRuntimeException when throughPoint is exactly at point
104      */
105     public Ray2d(final Point2dd.html#Point2d">Point2d point, final Point2d throughPoint) throws NullPointerException, DrawRuntimeException
106     {
107         this(Throw.whenNull(point, "point may not be null").x, point.y,
108                 Throw.whenNull(throughPoint, "throughPoint may not be null").x, throughPoint.y);
109     }
110 
111     /** {@inheritDoc} */
112     @Override
113     public final double getPhi()
114     {
115         return this.phi;
116     }
117 
118     /** {@inheritDoc} */
119     @Override
120     public Point2d getEndPoint()
121     {
122         return new Point2d(this.x, this.y);
123     }
124 
125     /** {@inheritDoc} */
126     @Override
127     public Bounds2d getBounds()
128     {
129         double normalizedPhi = AngleUtil.normalizeAroundZero(this.phi);
130         boolean toPositiveX = Math.abs(normalizedPhi) <= Math.PI / 2; // Math.cos(Math.PI) is > 0 due to finite precision
131         return new Bounds2d(toPositiveX ? this.x : Double.NEGATIVE_INFINITY, toPositiveX ? Double.POSITIVE_INFINITY : this.x,
132                 normalizedPhi >= 0 ? this.y : Double.NEGATIVE_INFINITY, normalizedPhi <= 0 ? this.y : Double.POSITIVE_INFINITY);
133     }
134 
135     /** {@inheritDoc} */
136     @Override
137     public Ray2d neg()
138     {
139         return new Ray2d(-this.x, -this.y, this.phi + Math.PI);
140     }
141 
142     /** {@inheritDoc} */
143     @Override
144     public Ray2d getLocationExtended(final double position) throws DrawRuntimeException
145     {
146         Throw.when(Double.isNaN(position) || Double.isInfinite(position), DrawRuntimeException.class,
147                 "position must be finite");
148         return new Ray2d(this.x + Math.cos(this.phi) * position, this.y + Math.sin(this.phi) * position, this.phi);
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     public Point2dt2d">Point2d closestPointOnRay(final Point2d point) throws NullPointerException
154     {
155         Throw.whenNull(point, "point may not be null");
156         double dX = Math.cos(this.phi);
157         double dY = Math.sin(this.phi);
158         final double u = (point.x - this.x) * dX + (point.y - this.y) * dY;
159         if (u <= 0)
160         {
161             return getEndPoint();
162         }
163         return new Point2d(this.x + u * dX, this.y + u * dY);
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     public boolean epsilonEquals(final Ray2d other, final double epsilonCoordinate, final double epsilonDirection)
169             throws NullPointerException, IllegalArgumentException
170     {
171         Throw.whenNull(other, "other point may not be null");
172         Throw.when(
173                 Double.isNaN(epsilonCoordinate) || epsilonCoordinate < 0 || Double.isNaN(epsilonDirection)
174                         || epsilonDirection < 0,
175                 IllegalArgumentException.class, "epsilon values may not be negative and may not be NaN");
176         if (Math.abs(this.x - other.x) > epsilonCoordinate)
177         {
178             return false;
179         }
180         if (Math.abs(this.y - other.y) > epsilonCoordinate)
181         {
182             return false;
183         }
184         if (Math.abs(AngleUtil.normalizeAroundZero(this.phi - other.phi)) > epsilonDirection)
185         {
186             return false;
187         }
188         return true;
189     }
190 
191     /** {@inheritDoc} */
192     @Override
193     public int hashCode()
194     {
195         final int prime = 31;
196         int result = super.hashCode();
197         long temp;
198         temp = Double.doubleToLongBits(this.phi);
199         result = prime * result + (int) (temp ^ (temp >>> 32));
200         return result;
201     }
202 
203     /** {@inheritDoc} */
204     @Override
205     @SuppressWarnings("checkstyle:needbraces")
206     public boolean equals(final Object obj)
207     {
208         if (this == obj)
209             return true;
210         if (!super.equals(obj))
211             return false;
212         if (getClass() != obj.getClass())
213             return false;
214         Ray2d="../../../../org/djutils/draw/line/Ray2d.html#Ray2d">Ray2d other = (Ray2d) obj;
215         if (Double.doubleToLongBits(this.phi) != Double.doubleToLongBits(other.phi))
216             return false;
217         return true;
218     }
219 
220     /** {@inheritDoc} */
221     @Override
222     public String toString()
223     {
224         return "Ray2d [x=" + this.x + " y=" + this.y + " phi=" + this.phi + "]";
225     }
226 
227 }