View Javadoc
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.Bounds3d;
13  import org.djutils.draw.point.Point3d;
14  import org.junit.jupiter.api.Test;
15  
16  /**
17   * Transform3dTest.java.
18   * <p>
19   * Copyright (c) 2020-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
20   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
21   * </p>
22   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
24   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
25   */
26  public class Transform3dTest
27  {
28      /**
29       * Test the matrix / vector multiplication.
30       */
31      @Test
32      public void testMatrixMultiplication()
33      {
34          double[] mA = new double[] {5, 7, 9, 10, 2, 3, 3, 8, 8, 10, 2, 3, 3, 3, 4, 8};
35          double[] mB = new double[] {3, 10, 12, 18, 12, 1, 4, 9, 9, 10, 12, 2, 3, 12, 4, 10};
36          double[] mAmB = Transform3d.mulMatMat(mA, mB);
37          double[] expected = new double[] {210, 267, 236, 271, 93, 149, 104, 149, 171, 146, 172, 268, 105, 169, 128, 169};
38          for (int i = 0; i < 16; 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, 0, 2, 0, 0, 3, 0, 4, 0, 0, 5, 0, 6, 0, 0, 7};
47          double[] v = new double[] {2, 5, 1, 8};
48          double[] mv = Transform3d.mulMatVec(m, v);
49          double[] ev = new double[] {4, 47, 5, 68};
50          for (int i = 0; i < 4; 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, 3};
59          mv = Transform3d.mulMatVec3(m, v);
60          ev = new double[] {7, 10, 15};
61          for (int i = 0; i < 3; 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       * Test that the constructor creates an Identity matrix.
72       */
73      @Test
74      public void testConstructor()
75      {
76          // TODO: decide whether the internal (flattened) matrix should be visible at all, or add a getter
77          Transform3d t = new Transform3d();
78          assertEquals(16, t.getMat().length, "matrix contians 16 values");
79          for (int row = 0; row < 4; row++)
80          {
81              for (int col = 0; col < 4; col++)
82              {
83                  int e = row == col ? 1 : 0;
84                  assertEquals(e, t.getMat()[4 * row + col], 0, "Value in identity matrix matches");
85              }
86          }
87      }
88  
89      /**
90       * Test the translate, scale, rotate, shear and reflect methods.
91       */
92      @Test
93      public void testTranslateScaleRotateShearAndReflect()
94      {
95          Transform3d t;
96          // Test time grows (explodes) with the 6th power of the length of values.
97          double[] values = new double[] {-100000, -100, -3, -1, -0.1, 0, 0.1, 1, 3, 100, 100000};
98          for (double dx : values)
99          {
100             for (double dy : values)
101             {
102                 for (double dz : values)
103                 {
104                     // Translate defined with a double[]
105                     t = new Transform3d();
106                     t.translate(dx, dy, dz);
107                     for (double px : values)
108                     {
109                         for (double py : values)
110                         {
111                             for (double pz : values)
112                             {
113                                 Point3d p = t.transform(new Point3d(px, py, pz));
114                                 assertEquals(px + dx, p.x, 0.001, "translated x matches");
115                                 assertEquals(py + dy, p.y, 0.001, "translated y matches");
116                                 assertEquals(pz + dz, p.z, 0.001, "translated z matches");
117                                 double[] result = t.transform(new double[] {px, py, pz});
118                                 assertEquals(px + dx, result[0], 0.001, "translated x matches");
119                                 assertEquals(py + dy, result[1], 0.001, "translated y matches");
120                                 assertEquals(pz + dz, result[2], 0.001, "translated z matches");
121                             }
122                         }
123                     }
124                     // Translate defined with a Point
125                     t = new Transform3d();
126                     t.translate(new Point3d(dx, dy, dz));
127                     for (double px : values)
128                     {
129                         for (double py : values)
130                         {
131                             for (double pz : values)
132                             {
133                                 Point3d p = t.transform(new Point3d(px, py, pz));
134                                 assertEquals(px + dx, p.x, 0.001, "translated x matches");
135                                 assertEquals(py + dy, p.y, 0.001, "translated y matches");
136                                 assertEquals(pz + dz, p.z, 0.001, "translated z matches");
137                                 double[] result = t.transform(new double[] {px, py, pz});
138                                 assertEquals(px + dx, result[0], 0.001, "translated x matches");
139                                 assertEquals(py + dy, result[1], 0.001, "translated y matches");
140                                 assertEquals(pz + dz, result[2], 0.001, "translated z matches");
141                             }
142                         }
143                     }
144                     // Scale
145                     t = new Transform3d();
146                     t.scale(dx, dy, dz);
147                     for (double px : values)
148                     {
149                         for (double py : values)
150                         {
151                             for (double pz : values)
152                             {
153                                 Point3d p = t.transform(new Point3d(px, py, pz));
154                                 assertEquals(px * dx, p.x, 0.001, "scaled x matches");
155                                 assertEquals(py * dy, p.y, 0.001, "scaled y matches");
156                                 assertEquals(pz * dz, p.z, 0.001, "scaled z matches");
157                                 double[] result = t.transform(new double[] {px, py, pz});
158                                 assertEquals(px * dx, result[0], 0.001, "scaled x matches");
159                                 assertEquals(py * dy, result[1], 0.001, "scaled y matches");
160                                 assertEquals(pz * dz, result[2], 0.001, "scaled z matches");
161                             }
162                         }
163                     }
164                     // ShearXY
165                     t = new Transform3d();
166                     t.shearXY(dx, dy);
167                     for (double px : values)
168                     {
169                         for (double py : values)
170                         {
171                             for (double pz : values)
172                             {
173                                 Point3d p = t.transform(new Point3d(px, py, pz));
174                                 assertEquals(px + pz * dx, p.x, 0.001, "sheared x matches");
175                                 assertEquals(py + pz * dy, p.y, 0.001, "sheared y matches");
176                                 assertEquals(pz, p.z, 0.001, "sheared z matches");
177                                 double[] result = t.transform(new double[] {px, py, pz});
178                                 assertEquals(px + pz * dx, result[0], 0.001, "sheared x matches");
179                                 assertEquals(py + pz * dy, result[1], 0.001, "sheared y matches");
180                                 assertEquals(pz, result[2], 0.001, "sheared z matches");
181                             }
182                         }
183                     }
184                     // ShearXZ
185                     t = new Transform3d();
186                     t.shearXZ(dx, dz);
187                     for (double px : values)
188                     {
189                         for (double py : values)
190                         {
191                             for (double pz : values)
192                             {
193                                 Point3d p = t.transform(new Point3d(px, py, pz));
194                                 assertEquals(px + py * dx, p.x, 0.001, "sheared x matches");
195                                 assertEquals(py, p.y, 0.001, "sheared y matches");
196                                 assertEquals(pz + py * dz, p.z, 0.001, "sheared z matches");
197                                 double[] result = t.transform(new double[] {px, py, pz});
198                                 assertEquals(px + py * dx, result[0], 0.001, "sheared x matches");
199                                 assertEquals(py, result[1], 0.001, "sheared y matches");
200                                 assertEquals(pz + py * dz, result[2], 0.001, "sheared z matches");
201                             }
202                         }
203                     }
204                     // ShearYZ
205                     t = new Transform3d();
206                     t.shearYZ(dy, dz);
207                     for (double px : values)
208                     {
209                         for (double py : values)
210                         {
211                             for (double pz : values)
212                             {
213                                 Point3d p = t.transform(new Point3d(px, py, pz));
214                                 assertEquals(px, p.x, 0.001, "sheared x matches");
215                                 assertEquals(py + px * dy, p.y, 0.001, "sheared y matches");
216                                 assertEquals(pz + px * dz, p.z, 0.001, "sheared z matches");
217                                 double[] result = t.transform(new double[] {px, py, pz});
218                                 assertEquals(px, result[0], 0.001, "sheared x matches");
219                                 assertEquals(py + px * dy, result[1], 0.001, "sheared y matches");
220                                 assertEquals(pz + px * dz, result[2], 0.001, "sheared z matches");
221                             }
222                         }
223                     }
224                 }
225                 // Rotate around Z (using dx as angle)
226                 t = new Transform3d();
227                 t.rotZ(dx);
228                 double sine = Math.sin(dx);
229                 double cosine = Math.cos(dx);
230                 for (double px : values)
231                 {
232                     for (double py : values)
233                     {
234                         for (double pz : values)
235                         {
236                             Point3d p = t.transform(new Point3d(px, py, pz));
237                             assertEquals(px * cosine - py * sine, p.x, 0.001, "rotated x matches");
238                             assertEquals(py * cosine + px * sine, p.y, 0.001, "rotated y matches");
239                             assertEquals(pz, p.z, 0.001, "rotated z matches");
240                             double[] result = t.transform(new double[] {px, py, pz});
241                             assertEquals(px * cosine - py * sine, result[0], 0.001, "rotated x matches");
242                             assertEquals(py * cosine + px * sine, result[1], 0.001, "rotated z matches");
243                             assertEquals(pz, result[2], 0.001, "rotated z matches");
244                         }
245                     }
246                 }
247                 // Rotate around X (using dx as angle)
248                 t = new Transform3d();
249                 t.rotX(dx);
250                 sine = Math.sin(dx);
251                 cosine = Math.cos(dx);
252                 for (double px : values)
253                 {
254                     for (double py : values)
255                     {
256                         for (double pz : values)
257                         {
258                             Point3d p = t.transform(new Point3d(px, py, pz));
259                             assertEquals(px, p.x, 0.001, "rotated x matches");
260                             assertEquals(py * cosine - pz * sine, p.y, 0.001, "rotated y matches");
261                             assertEquals(pz * cosine + py * sine, p.z, 0.001, "rotated z matches");
262                             double[] result = t.transform(new double[] {px, py, pz});
263                             assertEquals(px, result[0], 0.001, "rotated x matches");
264                             assertEquals(py * cosine - pz * sine, result[1], 0.001, "rotated z matches");
265                             assertEquals(pz * cosine + py * sine, result[2], 0.001, "rotated z matches");
266                         }
267                     }
268                 }
269                 // Rotate around Y (using dx as angle)
270                 t = new Transform3d();
271                 t.rotY(dx);
272                 sine = Math.sin(dx);
273                 cosine = Math.cos(dx);
274                 for (double px : values)
275                 {
276                     for (double py : values)
277                     {
278                         for (double pz : values)
279                         {
280                             Point3d p = t.transform(new Point3d(px, py, pz));
281                             assertEquals(px * cosine + pz * sine, p.x, 0.001, "rotated x matches");
282                             assertEquals(py, p.y, 0.001, "rotated y matches");
283                             assertEquals(pz * cosine - px * sine, p.z, 0.001, "rotated z matches");
284                             double[] result = t.transform(new double[] {px, py, pz});
285                             assertEquals(px * cosine + pz * sine, result[0], 0.001, "rotated x matches");
286                             assertEquals(py, result[1], 0.001, "rotated z matches");
287                             assertEquals(pz * cosine - px * sine, result[2], 0.001, "rotated z matches");
288                         }
289                     }
290                 }
291             }
292         }
293         // ReflectX
294         t = new Transform3d();
295         t.reflectX();
296         for (double px : values)
297         {
298             for (double py : values)
299             {
300                 for (double pz : values)
301                 {
302                     Point3d p = t.transform(new Point3d(px, py, pz));
303                     assertEquals(-px, p.x, 0.001, "x-reflected x matches");
304                     assertEquals(py, p.y, 0.001, "x-reflected y matches");
305                     assertEquals(pz, p.z, 0.001, "x-reflected z matches");
306                     double[] result = t.transform(new double[] {px, py, pz});
307                     assertEquals(-px, result[0], 0.001, "x-reflected x matches");
308                     assertEquals(py, result[1], 0.001, "x-reflected y matches");
309                     assertEquals(pz, result[2], 0.001, "x-reflected z matches");
310                 }
311             }
312         }
313         // ReflectY
314         t = new Transform3d();
315         t.reflectY();
316         for (double px : values)
317         {
318             for (double py : values)
319             {
320                 for (double pz : values)
321                 {
322                     Point3d p = t.transform(new Point3d(px, py, pz));
323                     assertEquals(px, p.x, 0.001, "y-reflected x matches");
324                     assertEquals(-py, p.y, 0.001, "y-reflected y matches");
325                     assertEquals(pz, p.z, 0.001, "y-reflected z matches");
326                     double[] result = t.transform(new double[] {px, py, pz});
327                     assertEquals(px, result[0], 0.001, "y-reflected x matches");
328                     assertEquals(-py, result[1], 0.001, "y-reflected y matches");
329                     assertEquals(pz, result[2], 0.001, "y-reflected z matches");
330                 }
331             }
332         }
333         // ReflectZ
334         t = new Transform3d();
335         t.reflectZ();
336         for (double px : values)
337         {
338             for (double py : values)
339             {
340                 for (double pz : values)
341                 {
342                     Point3d p = t.transform(new Point3d(px, py, pz));
343                     assertEquals(px, p.x, 0.001, "z-reflected x matches");
344                     assertEquals(py, p.y, 0.001, "z-reflected y matches");
345                     assertEquals(-pz, p.z, 0.001, "z-reflected z matches");
346                     double[] result = t.transform(new double[] {px, py, pz});
347                     assertEquals(px, result[0], 0.001, "z-reflected x matches");
348                     assertEquals(py, result[1], 0.001, "z-reflected y matches");
349                     assertEquals(-pz, result[2], 0.001, "z-reflected z matches");
350                 }
351             }
352         }
353     }
354 
355     /**
356      * Test the transform method.
357      */
358     @Test
359     public void transformTest()
360     {
361         Transform3d reflectionX = new Transform3d().reflectX();
362         Transform3d reflectionY = new Transform3d().reflectY();
363         Transform3d reflectionZ = new Transform3d().reflectZ();
364         // Test time explodes with the 6th power of the length of this array
365         double[] values = new double[] {-30, 0, 0.07, 25};
366         for (double translateX : values)
367         {
368             for (double translateY : values)
369             {
370                 for (double translateZ : values)
371                 {
372                     Transform3d translation = new Transform3d().translate(translateX, translateY, translateZ);
373                     for (double scaleX : values)
374                     {
375                         for (double scaleY : values)
376                         {
377                             for (double scaleZ : values)
378                             {
379                                 Transform3d scaling = new Transform3d().scale(scaleX, scaleY, scaleZ);
380                                 for (double angle : new double[] {-2, 0, 0.5})
381                                 {
382                                     Transform3d rotationX = new Transform3d().rotX(angle);
383                                     Transform3d rotationY = new Transform3d().rotY(angle);
384                                     Transform3d rotationZ = new Transform3d().rotZ(angle);
385                                     for (double shearA : values)
386                                     {
387                                         for (double shearB : values)
388                                         {
389                                             Transform3d t = new Transform3d().translate(translateX, translateY, translateZ)
390                                                     .scale(scaleX, scaleY, scaleZ).rotZ(angle).shearXY(shearA, shearB);
391                                             Transform3d shearXY = new Transform3d().shearXY(shearA, shearB);
392                                             Transform3d tReflectX =
393                                                     new Transform3d().reflectX().translate(translateX, translateY, translateZ)
394                                                             .scale(scaleX, scaleY, scaleZ).rotY(angle).shearYZ(shearA, shearB);
395                                             Transform3d shearYZ = new Transform3d().shearYZ(shearA, shearB);
396                                             Transform3d tReflectY =
397                                                     new Transform3d().reflectY().translate(translateX, translateY, translateZ)
398                                                             .scale(scaleX, scaleY, scaleZ).rotZ(angle).shearXZ(shearA, shearB);
399                                             Transform3d shearXZ = new Transform3d().shearXZ(shearA, shearB);
400                                             Transform3d tReflectZ =
401                                                     new Transform3d().reflectZ().translate(translateX, translateY, translateZ)
402                                                             .scale(scaleX, scaleY, scaleZ).rotX(angle).shearXY(shearA, shearB);
403                                             for (double px : values)
404                                             {
405                                                 for (double py : values)
406                                                 {
407                                                     for (double pz : values)
408                                                     {
409                                                         Point3d p = new Point3d(px, py, pz);
410                                                         Point3d tp = t.transform(p);
411                                                         Point3d chainP = translation.transform(
412                                                                 scaling.transform(rotationZ.transform(shearXY.transform(p))));
413                                                         assertEquals(chainP.x, tp.x, 0.0000001, "X");
414                                                         assertEquals(chainP.y, tp.y, 0.0000001, "Y");
415                                                         assertEquals(chainP.z, tp.z, 0.0000001, "Z");
416                                                         tp = tReflectX.transform(p);
417                                                         Point3d chainPReflectX = reflectionX.transform(translation.transform(
418                                                                 scaling.transform(rotationY.transform(shearYZ.transform(p)))));
419                                                         assertEquals(chainPReflectX.x, tp.x, 0.0000001, "RX X");
420                                                         assertEquals(chainPReflectX.y, tp.y, 0.0000001, "RX Y");
421                                                         assertEquals(chainPReflectX.z, tp.z, 0.0000001, "RX Z");
422                                                         tp = tReflectY.transform(p);
423                                                         Point3d chainPReflectY = reflectionY.transform(translation.transform(
424                                                                 scaling.transform(rotationZ.transform(shearXZ.transform(p)))));
425                                                         assertEquals(chainPReflectY.x, tp.x, 0.0000001, "RY X");
426                                                         assertEquals(chainPReflectY.y, tp.y, 0.0000001, "RY Y");
427                                                         assertEquals(chainPReflectY.z, tp.z, 0.0000001, "RY Z");
428                                                         tp = tReflectZ.transform(p);
429                                                         Point3d chainPReflectZ = reflectionZ.transform(translation.transform(
430                                                                 scaling.transform(rotationX.transform(shearXY.transform(p)))));
431                                                         assertEquals(chainPReflectZ.x, tp.x, 0.0000001, "RZ X");
432                                                         assertEquals(chainPReflectZ.y, tp.y, 0.0000001, "RZ Y");
433                                                         assertEquals(chainPReflectZ.z, tp.z, 0.0000001, "RZ Z");
434                                                     }
435                                                 }
436                                             }
437                                         }
438                                     }
439                                 }
440                             }
441                         }
442                     }
443                 }
444             }
445         }
446     }
447 
448     /**
449      * Test transformation of a bounding box.
450      */
451     @Test
452     public void transformBounds3dTest()
453     {
454         double[] values = new double[] {-100, 0.1, 0, 0.1, 100};
455         double[] sizes = new double[] {0, 10, 100};
456         Transform3d t = new Transform3d().rotX(0.4).rotZ(0.8).rotY(-1.2).reflectX().scale(0.5, 1.5, 2.5).shearXY(2, 3)
457                 .translate(123, 456, 789);
458         // System.out.println(t);
459         for (double x : values)
460         {
461             for (double y : values)
462             {
463                 for (double z : values)
464                 {
465                     for (double xSize : sizes)
466                     {
467                         for (double ySize : sizes)
468                         {
469                             for (double zSize : sizes)
470                             {
471                                 Bounds3d bb = new Bounds3d(x, x + xSize, y, y + ySize, z, z + zSize);
472                                 Point3d[] points = new Point3d[] {new Point3d(x, y, z), new Point3d(x + xSize, y, z),
473                                         new Point3d(x, y + ySize, z), new Point3d(x + xSize, y + ySize, z),
474                                         new Point3d(x, y, z + zSize), new Point3d(x + xSize, y, z + zSize),
475                                         new Point3d(x, y + ySize, z + zSize), new Point3d(x + xSize, y + ySize, z + zSize)};
476                                 Point3d[] transformedPoints = new Point3d[8];
477                                 for (int i = 0; i < points.length; i++)
478                                 {
479                                     transformedPoints[i] = t.transform(points[i]);
480                                 }
481                                 Bounds3d expected = new Bounds3d(Arrays.stream(transformedPoints).iterator());
482                                 Bounds3d got = t.transform(bb);
483                                 if (!got.equals(expected))
484                                 {
485                                     System.err.println("oops");
486                                     t.transform(bb);
487                                 }
488                                 assertEquals(expected.getMinX(), got.getMinX(), 0.0001, "bb minX");
489                                 assertEquals(expected.getMaxX(), got.getMaxX(), 0.0001, "bb maxX");
490                                 assertEquals(expected.getMinY(), got.getMinY(), 0.0001, "bb minY");
491                                 assertEquals(expected.getMaxY(), got.getMaxY(), 0.0001, "bb maxY");
492                                 assertEquals(expected.getMinZ(), got.getMinZ(), 0.0001, "bb minZ");
493                                 assertEquals(expected.getMaxZ(), got.getMaxZ(), 0.0001, "bb maxZ");
494                             }
495                         }
496                     }
497                 }
498             }
499         }
500     }
501 
502     /**
503      * Reproducible test of multiple transformations on a bounding box.
504      */
505     @Test
506     public void testBoundingBox3d()
507     {
508         Bounds3d bounds = new Bounds3d(-4, 4, -4, 4, -4, 4);
509 
510         // identical transformation
511         Transform3d transform = new Transform3d();
512         Bounds3d b = transform.transform(bounds);
513         testBounds3d(b, -4, 4, -4, 4, -4, 4);
514 
515         // translate x, y
516         transform = new Transform3d();
517         transform.translate(20, 10, 0);
518         b = transform.transform(bounds);
519         testBounds3d(b, 20 - 4, 20 + 4, 10 - 4, 10 + 4, -4, 4);
520 
521         // translate x, y, z
522         transform = new Transform3d();
523         transform.translate(-20, -10, -30);
524         b = transform.transform(bounds);
525         testBounds3d(b, -20 - 4, -20 + 4, -10 - 4, -10 + 4, -30 - 4, -30 + 4);
526 
527         // rotate 90 degrees (should be same)
528         transform = new Transform3d();
529         transform.rotZ(Math.toRadians(90.0));
530         b = transform.transform(bounds);
531         testBounds3d(b, -4, 4, -4, 4, -4, 4);
532 
533         // rotate 45 degrees in the XY-plane
534         transform = new Transform3d();
535         transform.rotZ(Math.toRadians(45.0));
536         double d = 4.0 * Math.sqrt(2.0);
537         b = transform.transform(bounds);
538         testBounds3d(b, -d, d, -d, d, -4, 4);
539 
540         // rotate 45 degrees in the XY-plane and then translate to (10, 20)
541         // note that to do FIRST rotation and THEN translation, the steps have to be built in the OPPOSITE order
542         // since matrix multiplication operates from RIGHT to LEFT.
543         transform = new Transform3d();
544         transform.translate(10, 20, 0);
545         transform.rotZ(Math.toRadians(45.0));
546         b = transform.transform(bounds);
547         testBounds3d(b, 10 - d, 10 + d, 20 - d, 20 + d, -4, 4);
548     }
549 
550     /**
551      * Check bounds values.
552      * @param b the box to test
553      * @param minX expected value
554      * @param maxX expected value
555      * @param minY expected value
556      * @param maxY expected value
557      * @param minZ expected value
558      * @param maxZ expected value
559      */
560     private void testBounds3d(final Bounds3d b, final double minX, final double maxX, final double minY, final double maxY,
561             final double minZ, final double maxZ)
562     {
563         assertEquals(minX, b.getMinX(), 0.001);
564         assertEquals(maxX, b.getMaxX(), 0.001);
565         assertEquals(minY, b.getMinY(), 0.001);
566         assertEquals(maxY, b.getMaxY(), 0.001);
567         assertEquals(minZ, b.getMinZ(), 0.001);
568         assertEquals(maxZ, b.getMaxZ(), 0.001);
569     }
570 
571     /**
572      * Check that toString returns something descriptive.
573      */
574     @Test
575     public void toStringTest()
576     {
577         assertTrue(new Transform3d().toString().startsWith("Transform3d "), "toString returns something descriptive");
578     }
579 
580     /**
581      * Check what transform does to a unit vector.
582      * @param args not used
583      */
584     public static void main(final String[] args)
585     {
586         Point3d unitVector = new Point3d(1, 0, 0);
587         double rotX = Math.toRadians(-55);
588         double rotY = Math.toRadians(-65);
589         double rotZ = Math.toRadians(-175);
590         Transform3d transform = new Transform3d();
591         transform.rotZ(rotZ);
592         System.out.println(transform.transform(unitVector));
593         transform.rotY(rotY);
594         System.out.println(transform.transform(unitVector));
595         transform.rotX(rotX);
596         Point3d rotated = transform.transform(unitVector);
597         System.out.println(rotated);
598         System.out.println("dirZ: " + Math.toDegrees(Math.atan2(rotated.y, rotated.x)));
599         System.out.println(
600                 "dirY: " + Math.toDegrees(Math.atan2(-rotated.z, Math.sqrt(rotated.x * rotated.x + rotated.y * rotated.y)))
601                         + " == " + Math.toDegrees(Math.atan2(-rotated.z, Math.hypot(rotated.x, rotated.y))));
602 
603     }
604 
605     /**
606      * Test the hashCode and equals methods.
607      * @throws SecurityException if that happens uncaught; this test has failed
608      * @throws NoSuchFieldException if that happens uncaught; this test has failed
609      * @throws IllegalAccessException if that happens uncaught; this test has failed
610      * @throws IllegalArgumentException if that happens uncaught; this test has failed
611      */
612     @Test
613     @SuppressWarnings({"unlikely-arg-type"})
614     public void testHashCodeAndEquals()
615             throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException
616     {
617         // Difficult to write a complete test because we can't control the values of the internal fields directly.
618         // We'll "solve" that using reflection.
619         Transform3d reference = new Transform3d();
620         assertEquals(reference, new Transform3d(), "Two different instances with same matrix do test equal");
621         assertEquals(reference.hashCode(), new Transform3d().hashCode(),
622                 "Two different instances with same matrix have same hash code");
623         for (int index = 0; index < 16; index++)
624         {
625             // Alter one element in the mat array at a time and expect the hash code to change and equals to return false.
626             for (double alteration : new double[] {-100, -10, -Math.PI, -0.1, 0.3, Math.E, 123})
627             {
628                 Transform3d other = new Transform3d();
629                 Field matrix = other.getClass().getDeclaredField("mat");
630                 matrix.setAccessible(true);
631                 double[] matrixValues = (double[]) matrix.get(other);
632                 matrixValues[index] = alteration;
633                 assertNotEquals(reference, other, "Modified transform should not be equals");
634                 assertNotEquals(reference.hashCode(), other.hashCode(), "HashCode should be different "
635                         + "(or it does not take all elements of the internal array into account");
636             }
637         }
638         assertTrue(reference.equals(reference), "equal to itself");
639         assertFalse(reference.equals(null), "not equal to null");
640         assertFalse(reference.equals("nope"), "not equal to some other object");
641     }
642 
643 }