1 package org.djutils.draw;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertFalse;
5 import static org.junit.jupiter.api.Assertions.assertNotEquals;
6 import static org.junit.jupiter.api.Assertions.assertTrue;
7 import static org.junit.jupiter.api.Assertions.fail;
8
9 import java.lang.reflect.Field;
10 import java.util.Arrays;
11
12 import org.djutils.draw.bounds.Bounds2d;
13 import org.djutils.draw.point.Point2d;
14 import org.junit.jupiter.api.Test;
15
16
17
18
19
20
21
22
23
24
25
26 public class Transform2dTest
27 {
28
29
30
31 @Test
32 public void testMatrixMultiplication()
33 {
34 double[] mA = new double[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
35 double[] mB = new double[] {2, 1, 0, 2, 4, 3, 3, 1, 2};
36 double[] mAmB = Transform2d.mulMatMat(mA, mB);
37 double[] expected = new double[] {15, 12, 12, 36, 30, 27, 57, 48, 42};
38 for (int i = 0; i < 9; i++)
39 {
40 if (mAmB[i] != expected[i])
41 {
42 fail(String.format("difference MA x MB at %d: expected %f, was: %f", i, expected[i], mAmB[i]));
43 }
44 }
45
46 double[] m = new double[] {1, 4, 2, 5, 3, 1, 4, 2, 5};
47 double[] v = new double[] {2, 5, 1};
48 double[] mv = Transform2d.mulMatVec(m, v);
49 double[] ev = new double[] {24, 26, 23};
50 for (int i = 0; i < 3; i++)
51 {
52 if (mv[i] != ev[i])
53 {
54 fail(String.format("difference M x V at %d: expected %f, was: %f", i, ev[i], mv[i]));
55 }
56 }
57
58 v = new double[] {1, 2};
59 mv = Transform2d.mulMatVec2(m, v);
60 ev = new double[] {11, 12};
61 for (int i = 0; i < 2; i++)
62 {
63 if (mv[i] != ev[i])
64 {
65 fail(String.format("difference M x V3 at %d: expected %f, was: %f", i, ev[i], mv[i]));
66 }
67 }
68 }
69
70
71
72
73 @Test
74 public void testConstructor()
75 {
76
77 Transform2d t = new Transform2d();
78 assertEquals(9, t.getMat().length, "matrix contians 9 values");
79 for (int row = 0; row < 3; row++)
80 {
81 for (int col = 0; col < 3; col++)
82 {
83 int e = row == col ? 1 : 0;
84 assertEquals(e, t.getMat()[3 * row + col], 0, "Value in identity matrix matches");
85 }
86 }
87 }
88
89
90
91
92 @Test
93 public void testTranslateScaleRotateShearAndReflect()
94 {
95 Transform2d t;
96
97 double[] values = new double[] {-100000, -100, -10, -3, -1, -0.3, -0.1, 0, 0.1, 0.3, 1, 3, 10, 100, 100000};
98 for (double dx : values)
99 {
100 for (double dy : values)
101 {
102
103 t = new Transform2d();
104 t.translate(dx, dy);
105 for (double px : values)
106 {
107 for (double py : values)
108 {
109 Point2d p = t.transform(new Point2d(px, py));
110 assertEquals(px + dx, p.x, 0.001, "translated x matches");
111 assertEquals(py + dy, p.y, 0.001, "translated y matches");
112 double[] result = t.transform(new double[] {px, py});
113 assertEquals(px + dx, result[0], 0.001, "translated x matches");
114 assertEquals(py + dy, result[1], 0.001, "translated y matches");
115 }
116 }
117
118 t = new Transform2d();
119 t.translate(new Point2d(dx, dy));
120 for (double px : values)
121 {
122 for (double py : values)
123 {
124 Point2d p = t.transform(new Point2d(px, py));
125 assertEquals(px + dx, p.x, 0.001, "transformed x matches");
126 assertEquals(py + dy, p.y, 0.001, "transformed y matches");
127 double[] result = t.transform(new double[] {px, py});
128 assertEquals(px + dx, result[0], 0.001, "transformed x matches");
129 assertEquals(py + dy, result[1], 0.001, "transformed y matches");
130 }
131 }
132
133 t = new Transform2d();
134 t.scale(dx, dy);
135 for (double px : values)
136 {
137 for (double py : values)
138 {
139 Point2d p = t.transform(new Point2d(px, py));
140 assertEquals(px * dx, p.x, 0.001, "scaled x matches");
141 assertEquals(py * dy, p.y, 0.001, "scaled y matches");
142 double[] result = t.transform(new double[] {px, py});
143 assertEquals(px * dx, result[0], 0.001, "scaled x matches");
144 assertEquals(py * dy, result[1], 0.001, "scaled y matches");
145 }
146 }
147
148 t = new Transform2d();
149 t.shear(dx, dy);
150 for (double px : values)
151 {
152 for (double py : values)
153 {
154 Point2d p = t.transform(new Point2d(px, py));
155 assertEquals(px + py * dx, p.x, 0.001, "sheared x matches");
156 assertEquals(py + px * dy, p.y, 0.001, "sheared y matches");
157 double[] result = t.transform(new double[] {px, py});
158 assertEquals(px + py * dx, result[0], 0.001, "sheared x matches");
159 assertEquals(py + px * dy, result[1], 0.001, "sheared y matches");
160 }
161 }
162 }
163
164 t = new Transform2d();
165 t.rotation(dx);
166 double sine = Math.sin(dx);
167 double cosine = Math.cos(dx);
168 for (double px : values)
169 {
170 for (double py : values)
171 {
172 Point2d p = t.transform(new Point2d(px, py));
173 assertEquals(px * cosine - py * sine, p.x, 0.001, "rotated x matches");
174 assertEquals(py * cosine + px * sine, p.y, 0.001, "rotated y matches");
175 double[] result = t.transform(new double[] {px, py});
176 assertEquals(px * cosine - py * sine, result[0], 0.001, "rotated x matches");
177 assertEquals(py * cosine + px * sine, result[1], 0.001, "rotated y matches");
178 }
179 }
180 }
181
182 t = new Transform2d();
183 t.reflectX();
184 for (double px : values)
185 {
186 for (double py : values)
187 {
188 Point2d p = t.transform(new Point2d(px, py));
189 assertEquals(-px, p.x, 0.001, "x-reflected x matches");
190 assertEquals(py, p.y, 0.001, "x-reflected y matches");
191 double[] result = t.transform(new double[] {px, py});
192 assertEquals(-px, result[0], 0.001, "x-reflected x matches");
193 assertEquals(py, result[1], 0.001, "x-reflected y matches");
194 }
195 }
196
197 t = new Transform2d();
198 t.reflectY();
199 for (double px : values)
200 {
201 for (double py : values)
202 {
203 Point2d p = t.transform(new Point2d(px, py));
204 assertEquals(px, p.x, 0.001, "y-reflected x matches");
205 assertEquals(-py, p.y, 0.001, "y-reflected y matches");
206 double[] result = t.transform(new double[] {px, py});
207 assertEquals(px, result[0], 0.001, "y-reflected x matches");
208 assertEquals(-py, result[1], 0.001, "y-reflected y matches");
209 }
210 }
211 }
212
213
214
215
216 @Test
217 public void transformTest()
218 {
219 Transform2d reflectionX = new Transform2d().reflectX();
220 Transform2d reflectionY = new Transform2d().reflectY();
221
222 double[] values = new double[] {-100, -0.1, 0, 0.01, 1, 100};
223 for (double translateX : values)
224 {
225 for (double translateY : values)
226 {
227 Transform2d translation = new Transform2d().translate(translateX, translateY);
228 for (double scaleX : values)
229 {
230 for (double scaleY : values)
231 {
232 Transform2d scaling = new Transform2d().scale(scaleX, scaleY);
233 for (double angle : new double[] {-2, 0, 0.5})
234 {
235 Transform2d rotation = new Transform2d().rotation(angle);
236 for (double shearX : values)
237 {
238 for (double shearY : values)
239 {
240 Transform2d t = new Transform2d().translate(translateX, translateY).scale(scaleX, scaleY)
241 .rotation(angle).shear(shearX, shearY);
242 Transform2d tReflectX = new Transform2d().reflectX().translate(translateX, translateY)
243 .scale(scaleX, scaleY).rotation(angle).shear(shearX, shearY);
244 Transform2d tReflectY = new Transform2d().reflectY().translate(translateX, translateY)
245 .scale(scaleX, scaleY).rotation(angle).shear(shearX, shearY);
246 Transform2d shearing = new Transform2d().shear(shearX, shearY);
247 for (double px : values)
248 {
249 for (double py : values)
250 {
251 Point2d p = new Point2d(px, py);
252 Point2d tp = t.transform(p);
253 Point2d chainP = translation
254 .transform(scaling.transform(rotation.transform(shearing.transform(p))));
255 assertEquals(chainP.x, tp.x, 0.0000001, "X");
256 assertEquals(chainP.y, tp.y, 0.0000001, "Y");
257 tp = tReflectX.transform(p);
258 Point2d chainPReflectX = reflectionX.transform(chainP);
259 assertEquals(chainPReflectX.x, tp.x, 0.0000001, "RX X");
260 assertEquals(chainPReflectX.y, tp.y, 0.0000001, "RX Y");
261 tp = tReflectY.transform(p);
262 Point2d chainPReflectY = reflectionY.transform(chainP);
263 assertEquals(chainPReflectY.x, tp.x, 0.0000001, "RY X");
264 assertEquals(chainPReflectY.y, tp.y, 0.0000001, "RY Y");
265 }
266 }
267 }
268 }
269 }
270 }
271 }
272 }
273 }
274 }
275
276
277
278
279 @Test
280 public void transformBounds2dTest()
281 {
282 double[] values = new double[] {-100, 0.1, 0, 0.1, 100};
283 double[] sizes = new double[] {0, 10, 100};
284 Transform2d t = new Transform2d().rotation(0.4).reflectX().scale(0.5, 1.5).shear(2, 3).translate(123, 456);
285
286 for (double x : values)
287 {
288 for (double y : values)
289 {
290 for (double xSize : sizes)
291 {
292 for (double ySize : sizes)
293 {
294 Bounds2d bb = new Bounds2d(x, x + xSize, y, y + ySize);
295 Point2d[] points = new Point2d[] {new Point2d(x, y), new Point2d(x + xSize, y),
296 new Point2d(x, y + ySize), new Point2d(x + xSize, y + ySize)};
297 Point2d[] transformedPoints = new Point2d[4];
298 for (int i = 0; i < points.length; i++)
299 {
300 transformedPoints[i] = t.transform(points[i]);
301 }
302 Bounds2d expected = new Bounds2d(Arrays.stream(transformedPoints).iterator());
303 Bounds2d got = t.transform(bb);
304 assertEquals(expected.getMinX(), got.getMinX(), 0.0001, "bb minX");
305 assertEquals(expected.getMaxX(), got.getMaxX(), 0.0001, "bb maxX");
306 assertEquals(expected.getMinY(), got.getMinY(), 0.0001, "bb minY");
307 assertEquals(expected.getMaxY(), got.getMaxY(), 0.0001, "bb maxY");
308 }
309 }
310 }
311 }
312 }
313
314
315
316
317 @Test
318 public void testBoundingRectangle2d()
319 {
320 Bounds2d bounds = new Bounds2d(-4, 4, -4, 4);
321
322
323 Transform2d transform = new Transform2d();
324 Bounds2d b = transform.transform(bounds);
325 testBounds2d(b, -4, 4, -4, 4);
326
327
328 transform = new Transform2d();
329 transform.translate(20, 10);
330 b = transform.transform(bounds);
331 testBounds2d(b, 20 - 4, 20 + 4, 10 - 4, 10 + 4);
332
333
334 transform = new Transform2d();
335 transform.rotation(Math.toRadians(90.0));
336 b = transform.transform(bounds);
337 testBounds2d(b, -4, 4, -4, 4);
338
339
340 transform = new Transform2d();
341 transform.rotation(Math.toRadians(45.0));
342 double d = 4.0 * Math.sqrt(2.0);
343 b = transform.transform(bounds);
344 testBounds2d(b, -d, d, -d, d);
345
346
347
348
349 transform = new Transform2d();
350 transform.translate(10, 20);
351 transform.rotation(Math.toRadians(45.0));
352 b = transform.transform(bounds);
353 testBounds2d(b, 10 - d, 10 + d, 20 - d, 20 + d);
354 }
355
356
357
358
359
360
361
362
363
364 private void testBounds2d(final Bounds2d b, final double minX, final double maxX, final double minY, final double maxY)
365 {
366 assertEquals(minX, b.getMinX(), 0.001);
367 assertEquals(maxX, b.getMaxX(), 0.001);
368 assertEquals(minY, b.getMinY(), 0.001);
369 assertEquals(maxY, b.getMaxY(), 0.001);
370 }
371
372
373
374
375 @Test
376 public void toStringTest()
377 {
378 assertTrue(new Transform2d().toString().startsWith("Transform2d "), "toString returns something descriptive");
379 }
380
381
382
383
384
385
386
387
388 @Test
389 @SuppressWarnings({"unlikely-arg-type"})
390 public void testHashCodeAndEquals()
391 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException
392 {
393
394
395 Transform2d reference = new Transform2d();
396 assertEquals(reference, new Transform2d(), "Two different instances with same matrix do test equal");
397 assertEquals(reference.hashCode(), new Transform2d().hashCode(),
398 "Two different instances with same matrix have same hash code");
399 for (int index = 0; index < 9; index++)
400 {
401
402 for (double alteration : new double[] {-100, -10, -Math.PI, -0.1, 0.3, Math.E, 123})
403 {
404 Transform2d other = new Transform2d();
405 Field matrix = other.getClass().getDeclaredField("mat");
406 matrix.setAccessible(true);
407 double[] matrixValues = (double[]) matrix.get(other);
408 matrixValues[index] = alteration;
409 assertNotEquals(reference, other, "Modified transform should not be equals");
410 assertNotEquals(reference.hashCode(), other.hashCode(), "HashCode should be different "
411 + "(or it does not take all elements of the internal array into account");
412 }
413 }
414 assertTrue(reference.equals(reference), "equal to itself");
415 assertFalse(reference.equals(null), "not equal to null");
416 assertFalse(reference.equals("nope"), "not equal to some other object");
417 }
418
419 }