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