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