1 package org.djutils.draw.point;
2
3 import java.util.Arrays;
4 import java.util.Iterator;
5 import java.util.Locale;
6 import java.util.Objects;
7
8 import org.djutils.draw.Directed2d;
9 import org.djutils.draw.DrawRuntimeException;
10 import org.djutils.exceptions.Throw;
11 import org.djutils.math.AngleUtil;
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public class DirectedPoint2d extends Point2d implements Directed2d<DirectedPoint2d>
27 {
28
29 private static final long serialVersionUID = 20200828L;
30
31
32 @SuppressWarnings("checkstyle:visibilitymodifier")
33 public final double dirZ;
34
35
36
37
38
39
40
41
42 public DirectedPoint2d(final double x, final double y, final double dirZ)
43 {
44 super(x, y);
45 Throw.whenNaN(dirZ, "dirZ");
46 this.dirZ = dirZ;
47 }
48
49
50
51
52
53
54
55
56
57
58 public DirectedPoint2d(final double[] xy, final double dirZ)
59 {
60 super(xy);
61 Throw.whenNaN(dirZ, "dirZ");
62 this.dirZ = dirZ;
63 }
64
65
66
67
68
69
70
71
72 public DirectedPoint2d(final java.awt.geom.Point2D point, final double dirZ)
73 {
74 super(point);
75 Throw.whenNaN(dirZ, "dirZ");
76 this.dirZ = dirZ;
77 }
78
79
80
81
82
83
84
85
86 public DirectedPoint2d(final Point2d point, final double dirZ)
87 {
88 this(point.x, point.y, dirZ);
89 }
90
91
92
93
94
95
96
97
98
99
100 public DirectedPoint2d(final double x, final double y, final double throughX, final double throughY)
101 {
102 this(x, y, buildDirection(throughX - x, throughY - y));
103 }
104
105
106
107
108
109
110
111
112 private static double buildDirection(final double dX, final double dY)
113 {
114 Throw.when(0 == dX && 0 == dY, IllegalArgumentException.class, "Through point may not be equal to point");
115 return Math.atan2(dY, dX);
116 }
117
118
119
120
121
122
123
124
125
126
127 public DirectedPoint2d(final Point2d point, final Point2d throughPoint)
128 {
129 this(Throw.whenNull(point, "point").x, point.y, Throw.whenNull(throughPoint, "throughPoint").x, throughPoint.y);
130 }
131
132
133
134
135
136
137
138
139
140
141
142 public DirectedPoint2d(final Point2d point, final double throughX, final double throughY)
143 {
144 this(Throw.whenNull(point, "point").x, point.y, throughX, throughY);
145 }
146
147 @Override
148 public DirectedPoint2d translate(final double dX, final double dY)
149 {
150 return new DirectedPoint2d(this.x + dX, this.y + dY, this.dirZ);
151 }
152
153 @Override
154 public DirectedPoint3d translate(final double dX, final double dY, final double z)
155 throws ArithmeticException, IllegalArgumentException
156 {
157 return new DirectedPoint3d(this.x + dX, this.y + dY, z, 0, this.dirZ);
158 }
159
160 @Override
161 public DirectedPoint2d scale(final double factor) throws IllegalArgumentException
162 {
163 Throw.whenNaN(factor, "factor");
164 return new DirectedPoint2d(this.x * factor, this.y * factor, this.dirZ);
165 }
166
167 @Override
168 public DirectedPoint2d neg()
169 {
170 return new DirectedPoint2d(-this.x, -this.y, AngleUtil.normalizeAroundZero(this.dirZ + Math.PI));
171 }
172
173 @Override
174 public DirectedPoint2d abs()
175 {
176 return new DirectedPoint2d(Math.abs(this.x), Math.abs(this.y), this.dirZ);
177 }
178
179 @Override
180 public DirectedPoint2d normalize() throws DrawRuntimeException
181 {
182 double length = Math.sqrt(this.x * this.x + this.y * this.y);
183 Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0)");
184 return this.scale(1.0 / length);
185 }
186
187
188
189
190
191
192
193
194
195
196
197
198
199 public DirectedPoint2d interpolate(final DirectedPoint2d otherPoint, final double fraction)
200 {
201 Throw.whenNull(otherPoint, "otherPoint");
202 Throw.whenNaN(fraction, "fraction");
203 if (0.0 == fraction)
204 {
205 return this;
206 }
207 if (1.0 == fraction)
208 {
209 return otherPoint;
210 }
211 return new DirectedPoint2d((1.0 - fraction) * this.x + fraction * otherPoint.x,
212 (1.0 - fraction) * this.y + fraction * otherPoint.y,
213 AngleUtil.interpolateShortest(this.dirZ, otherPoint.dirZ, fraction));
214 }
215
216
217
218
219
220
221
222
223 public DirectedPoint2d rotate(final double rotateZ)
224 {
225 Throw.whenNaN(rotateZ, "rotateZ");
226 return new DirectedPoint2d(this.x, this.y, AngleUtil.normalizeAroundZero(this.dirZ + rotateZ));
227 }
228
229 @Override
230 public double getDirZ()
231 {
232 return this.dirZ;
233 }
234
235 @Override
236 public Iterator<Point2d> iterator()
237 {
238 return Arrays.stream(new Point2d[] {this}).iterator();
239 }
240
241 @Override
242 public String toString()
243 {
244 return toString("%f", false);
245 }
246
247 @Override
248 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
249 {
250 String format =
251 String.format("%1$s[x=%2$s, y=%2$s, dirZ=%2$s]", doNotIncludeClassName ? "" : "DirectedPoint2d ", doubleFormat);
252 return String.format(Locale.US, format, this.x, this.y, this.dirZ);
253 }
254
255 @Override
256 public boolean epsilonEquals(final DirectedPoint2d other, final double epsilonCoordinate, final double epsilonRotation)
257 throws NullPointerException, IllegalArgumentException
258 {
259 Throw.whenNull(other, "other");
260 Throw.whenNaN(epsilonCoordinate, "epsilonCoordinate");
261 Throw.whenNaN(epsilonRotation, "epsilonRotation");
262 Throw.when(epsilonCoordinate < 0 || epsilonRotation < 0, IllegalArgumentException.class,
263 "epsilonCoordinate and epsilonRotation may not be negative");
264 if (Math.abs(this.x - other.x) > epsilonCoordinate)
265 {
266 return false;
267 }
268 if (Math.abs(this.y - other.y) > epsilonCoordinate)
269 {
270 return false;
271 }
272 if (Math.abs(AngleUtil.normalizeAroundZero(this.dirZ - other.dirZ)) > epsilonRotation)
273 {
274 return false;
275 }
276 return true;
277 }
278
279 @Override
280 public int hashCode()
281 {
282 final int prime = 31;
283 int result = super.hashCode();
284 result = prime * result + Objects.hash(this.dirZ);
285 return result;
286 }
287
288 @Override
289 @SuppressWarnings("checkstyle:needbraces")
290 public boolean equals(final Object obj)
291 {
292 if (this == obj)
293 return true;
294 if (!super.equals(obj))
295 return false;
296 if (getClass() != obj.getClass())
297 return false;
298 DirectedPoint2d other = (DirectedPoint2d) obj;
299 return Double.doubleToLongBits(this.dirZ) == Double.doubleToLongBits(other.dirZ);
300 }
301
302 }