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.Drawable3d;
6   import org.djutils.draw.Space3d;
7   import org.djutils.draw.bounds.Bounds3d;
8   import org.djutils.draw.point.Point3d;
9   import org.djutils.exceptions.Throw;
10  
11  /**
12   * Ray3d 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 Ray3d extends Point3d implements Drawable3d, Ray<Ray3d, Point3d, Space3d>
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      /** Theta; the angle from the positive Z axis direction in radians. */
30      @SuppressWarnings("checkstyle:visibilitymodifier")
31      public final double theta;
32  
33      /**
34       * Construct a new Ray3d.
35       * @param x double; the x coordinate of the finite end point of the ray
36       * @param y double; the y coordinate of the finite end point of the ray
37       * @param z double; the z coordinate of the finite end point of the ray
38       * @param phi double; the angle from the positive X axis direction in radians.
39       * @param theta double; the angle from the positive Z axis direction in radians
40       * @throws DrawRuntimeException when phi is NaN
41       */
42      public Ray3d(final double x, final double y, final double z, final double phi, final double theta)
43              throws DrawRuntimeException
44      {
45          super(x, y, z);
46          Throw.when(Double.isNaN(phi) || Double.isNaN(theta), DrawRuntimeException.class, "phi and theta may not be NaN");
47          this.phi = phi;
48          this.theta = theta;
49      }
50  
51      /**
52       * Construct a new Ray3d.
53       * @param point Point3d; the finite end point of the ray
54       * @param phi double; the angle from the positive X axis direction in radians.
55       * @param theta double; the angle from the positive Z axis direction in radians
56       * @throws NullPointerException when point is null
57       * @throws DrawRuntimeException when phi is NaN, or theta is NaN
58       */
59      public Ray3d(final Point3d point, final double phi, final double theta) throws NullPointerException, DrawRuntimeException
60      {
61          this(Throw.whenNull(point, "point may not be null").x, point.y, point.z, phi, theta);
62      }
63  
64      /**
65       * Construct a new Ray3d.
66       * @param x double; the x coordinate of the finite end point of the ray
67       * @param y double; the y coordinate of the finite end point of the ray
68       * @param z double; the z coordinate of the finite end point of the ray
69       * @param throughX double; the x coordinate of another point on the ray
70       * @param throughY double; the y coordinate of another point on the ray
71       * @param throughZ double; the z coordinate of another point on the ray
72       * @throws DrawRuntimeException when throughX == x and throughY == y
73       */
74      public Ray3d(final double x, final double y, final double z, final double throughX, final double throughY,
75              final double throughZ) throws DrawRuntimeException
76      {
77          super(x, y, z);
78          Throw.when(throughX == x && throughY == y && throughZ == z, DrawRuntimeException.class,
79                  "the coordinates of the through points must differ from (x, y, z)");
80          this.phi = Math.atan2(throughY - y, throughX - x);
81          this.theta = Math.atan2(throughZ - z, Math.hypot(throughX - x, throughY - y));
82      }
83  
84      /**
85       * Construct a new Ray3d.
86       * @param point Point3d; the finite end point of the ray
87       * @param throughX double; the x coordinate of another point on the ray
88       * @param throughY double; the y coordinate of another point on the ray
89       * @param throughZ double; the z coordinate of another point on the ray
90       * @throws NullPointerException when point is null
91       * @throws DrawRuntimeException when throughX == point.x and throughY == point.y
92       */
93      public Ray3d(final Point3d point, final double throughX, final double throughY, final double throughZ)
94              throws NullPointerException, DrawRuntimeException
95      {
96          this(Throw.whenNull(point, "point may not be null").x, point.y, point.z, throughX, throughY, throughZ);
97      }
98  
99      /**
100      * Construct a new Ray3d.
101      * @param x double; the x coordinate of the finite end point of the ray
102      * @param y double; the y coordinate of the finite end point of the ray
103      * @param z double; the z coordinate of the finite end point of the ray
104      * @param throughPoint Point3d; another point on the ray
105      * @throws NullPointerException when throughPoint is null
106      * @throws DrawRuntimeException when throughPoint is exactly at (x, y)
107      */
108     public Ray3d(final double x, final double y, final double z, final Point3d throughPoint)
109             throws NullPointerException, DrawRuntimeException
110     {
111         this(x, y, z, Throw.whenNull(throughPoint, "througPoint may not be null").x, throughPoint.y, throughPoint.z);
112     }
113 
114     /**
115      * Construct a new Ray3d.
116      * @param point Point3d; the finite end point of the ray
117      * @param throughPoint Point3d; another point on the ray
118      * @throws NullPointerException when point is null or throughPoint is null
119      * @throws DrawRuntimeException when throughPoint is exactly at point
120      */
121     public Ray3d(final Point3dd.html#Point3d">Point3d point, final Point3d throughPoint) throws NullPointerException, DrawRuntimeException
122     {
123         this(Throw.whenNull(point, "point may not be null").x, point.y, point.z,
124                 Throw.whenNull(throughPoint, "throughPoint may not be null").x, throughPoint.y, throughPoint.z);
125     }
126 
127     /** {@inheritDoc} */
128     @Override
129     public final double getPhi()
130     {
131         return this.phi;
132     }
133 
134     /**
135      * Retrieve the angle from the positive Z axis direction in radians.
136      * @return double; the angle from the positive Z axis direction in radians
137      */
138     public final double getTheta()
139     {
140         return this.theta;
141     }
142 
143     /** {@inheritDoc} */
144     @Override
145     public Point3d getEndPoint()
146     {
147         return new Point3d(this.x, this.y, this.z);
148     }
149 
150     /** {@inheritDoc} */
151     @Override
152     public Point3dt3d">Point3d closestPointOnRay(final Point3d point) throws NullPointerException
153     {
154         Throw.whenNull(point, "point may not be null");
155         double sinTheta = Math.sin(this.theta);
156         double dX = Math.cos(this.phi) * sinTheta;
157         double dY = Math.sin(this.phi) * sinTheta;
158         double dZ = Math.cos(this.theta);
159         final double u = (point.x - this.x) * dX + (point.y - this.y) * dY + (point.z - this.z) * dZ;
160         if (u <= 0)
161         {
162             return getEndPoint();
163         }
164         return new Point3d(this.x + u * dX, this.y + u * dY, this.z + u * dZ);
165     }
166 
167     /** {@inheritDoc} */
168     @Override
169     public Ray3d neg()
170     {
171         return new Ray3d(-this.x, -this.y, -this.z, this.phi + Math.PI, this.theta + Math.PI);
172     }
173 
174     /** {@inheritDoc} */
175     @Override
176     public Ray3d getLocationExtended(final double position) throws DrawRuntimeException
177     {
178         Throw.when(Double.isNaN(position) || Double.isInfinite(position), DrawRuntimeException.class,
179                 "position must be finite");
180         double sinTheta = Math.sin(this.theta);
181         double dX = Math.cos(this.phi) * sinTheta;
182         double dY = Math.sin(this.phi) * sinTheta;
183         double dZ = Math.cos(this.theta);
184         return new Ray3d(this.x + dX * position, this.y + dY * position, this.z + dZ * position, this.phi, this.theta);
185     }
186 
187     /** {@inheritDoc} */
188     @Override
189     public Bounds3d getBounds()
190     {
191         double normalizedPhi = AngleUtil.normalizeAroundZero(this.phi);
192         double normalizedTheta = AngleUtil.normalizeAroundZero(this.theta);
193         boolean toPositiveX = Math.abs(normalizedPhi) <= Math.PI / 2; // Math.cos(Math.PI) is > 0 due to finite precision
194         return new Bounds3d(toPositiveX ? this.x : Double.NEGATIVE_INFINITY, toPositiveX ? Double.POSITIVE_INFINITY : this.x,
195                 normalizedPhi >= 0 ? this.y : Double.NEGATIVE_INFINITY, normalizedPhi <= 0 ? this.y : Double.POSITIVE_INFINITY,
196                 normalizedTheta >= 0 ? this.z : Double.NEGATIVE_INFINITY,
197                 normalizedTheta <= 0 ? this.z : Double.POSITIVE_INFINITY);
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public boolean epsilonEquals(final Ray3d other, final double epsilonCoordinate, final double epsilonRotation)
203             throws NullPointerException, IllegalArgumentException
204     {
205         Throw.whenNull(other, "other point may not be null");
206         Throw.when(
207                 Double.isNaN(epsilonCoordinate) || epsilonCoordinate < 0 || Double.isNaN(epsilonRotation)
208                         || epsilonRotation < 0,
209                 IllegalArgumentException.class, "epsilon values may not be negative and may not be NaN");
210         if (Math.abs(this.x - other.x) > epsilonCoordinate)
211         {
212             return false;
213         }
214         if (Math.abs(this.y - other.y) > epsilonCoordinate)
215         {
216             return false;
217         }
218         if (Math.abs(this.z - other.z) > epsilonCoordinate)
219         {
220             return false;
221         }
222         if (Math.abs(AngleUtil.normalizeAroundZero(this.phi - other.phi)) > epsilonRotation)
223         {
224             return false;
225         }
226         if (Math.abs(AngleUtil.normalizeAroundZero(this.theta - other.theta)) > epsilonRotation)
227         {
228             return false;
229         }
230         return true;
231     }
232 
233     /** {@inheritDoc} */
234     @Override
235     public int hashCode()
236     {
237         final int prime = 31;
238         int result = super.hashCode();
239         long temp;
240         temp = Double.doubleToLongBits(this.phi);
241         result = prime * result + (int) (temp ^ (temp >>> 32));
242         temp = Double.doubleToLongBits(this.theta);
243         result = prime * result + (int) (temp ^ (temp >>> 32));
244         return result;
245     }
246 
247     /** {@inheritDoc} */
248     @Override
249     @SuppressWarnings("checkstyle:needbraces")
250     public boolean equals(final Object obj)
251     {
252         if (this == obj)
253             return true;
254         if (!super.equals(obj))
255             return false;
256         if (getClass() != obj.getClass())
257             return false;
258         Ray3d="../../../../org/djutils/draw/line/Ray3d.html#Ray3d">Ray3d other = (Ray3d) obj;
259         if (Double.doubleToLongBits(this.phi) != Double.doubleToLongBits(other.phi))
260             return false;
261         if (Double.doubleToLongBits(this.theta) != Double.doubleToLongBits(other.theta))
262             return false;
263         return true;
264     }
265 
266     /** {@inheritDoc} */
267     @Override
268     public String toString()
269     {
270         return "Ray3d [x=" + this.x + " y=" + this.y + " z=" + this.z + " phi=" + this.phi + " theta=" + this.theta + "]";
271     }
272 
273 }