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