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
13
14
15
16
17
18
19
20 public class Ray3d extends Point3d implements Drawable3d, Ray<Ray3d, Point3d, Space3d>
21 {
22
23 private static final long serialVersionUID = 20210119L;
24
25
26 @SuppressWarnings("checkstyle:visibilitymodifier")
27 public final double phi;
28
29
30 @SuppressWarnings("checkstyle:visibilitymodifier")
31 public final double theta;
32
33
34
35
36
37
38
39
40
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
53
54
55
56
57
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
66
67
68
69
70
71
72
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
86
87
88
89
90
91
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
101
102
103
104
105
106
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
116
117
118
119
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
128 @Override
129 public final double getPhi()
130 {
131 return this.phi;
132 }
133
134
135
136
137
138 public final double getTheta()
139 {
140 return this.theta;
141 }
142
143
144 @Override
145 public Point3d getEndPoint()
146 {
147 return new Point3d(this.x, this.y, this.z);
148 }
149
150
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
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
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
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;
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
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
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
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
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 }