View Javadoc
1   package org.djutils.draw.line;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertTrue;
5   import static org.junit.Assert.fail;
6   
7   import java.util.ArrayList;
8   import java.util.Arrays;
9   import java.util.List;
10  
11  import org.djutils.draw.DrawRuntimeException;
12  import org.djutils.draw.point.Point3d;
13  import org.junit.Test;
14  
15  /**
16   * Polygon3dTest.java.
17   * <p>
18   * Copyright (c) 2020-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
20   * </p>
21   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
22   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
23   */
24  public class Polygon3dTest
25  {
26  
27      /**
28       * Test the constructors.
29       */
30      @Test
31      public void testConstructors()
32      {
33          double[] x = new double[] { 1, 3, 5 };
34          double[] y = new double[] { 2, 1, 10 };
35          double[] z = new double[] { 2, 3, 4 };
36  
37          Polygon3d polygon = new Polygon3d(x, y, z);
38          checkPolygon("constructed from arrays", x, y, z, polygon);
39          Polygon3d reversed = polygon.reverse();
40          for (int index = 0; index < 3; index++)
41          {
42              Point3d p = reversed.get(x.length - 1 - index);
43              assertEquals("reversed x", x[index], p.x, 0.0001);
44              assertEquals("reversed y", y[index], p.y, 0.0001);
45              assertEquals("reversed z", z[index], p.z, 0.0001);
46          }
47  
48          x = new double[] { 1, 3, 5, 1 };
49          y = new double[] { 2, 1, 10, 2 };
50          z = new double[] { 2, 3, 4, 2 };
51          polygon = new Polygon3d(x, y, z); // Last point is duplicate of first point; should be handled gracefully
52          assertTrue("toString returns something descriptive", polygon.toString().startsWith("Polygon3d"));
53          assertTrue("toString can suppress the class name", polygon.toString().indexOf(polygon.toString(true)) > 0);
54          checkPolygon("constructed from arrays", x, y, z, polygon);
55          Polygon3d otherPolygon = new Polygon3d(polygon.get(0), polygon.get(1), polygon.get(2), polygon.get(0));
56          assertEquals("polygon constructed from all points of existing polygon with first point duplicated at end is equal "
57                  + "to original", polygon, otherPolygon);
58          // Make a Polygon3d from Point3d where last point differs from first only in y
59          new Polygon3d(polygon.get(0), polygon.get(1), polygon.get(2), new Point3d(polygon.get(0).x, 123, polygon.get(0).z));
60          // Make a Polygon3d from Point3d where last point differs from first only in z
61          new Polygon3d(polygon.get(0), polygon.get(1), polygon.get(2), new Point3d(polygon.get(0).x, polygon.get(0).y, 123));
62  
63          x = new double[] { 1, 3, 1 }; // x coordinate of last point matches that of first
64          y = new double[] { 2, 1, 10 }; // not true for y coordinates
65          z = new double[] { 2, 3, 2 }; // true for z
66          polygon = new Polygon3d(x, y, z);
67          // System.out.println(polygon);
68          checkPolygon("constructed from arrays with first and last x equal", x, y, z, polygon);
69  
70          x = new double[] { 1, 3, 1 }; // x coordinate of last point matches that of first
71          y = new double[] { 2, 1, 2 }; // same for y
72          z = new double[] { 2, 3, 4 }; // not true for z
73          polygon = new Polygon3d(x, y, z);
74          // System.out.println(polygon);
75          checkPolygon("constructed from arrays with first and last x equal", x, y, z, polygon);
76  
77          x = new double[] { 1, 3, 5, 3 };
78          y = new double[] { 2, 2, 10, 10 };
79          z = new double[] { 4, 4, 4, 4 };
80          polygon = new Polygon3d(x, y, z);
81          checkPolygon("constructed from arrays", x, y, z, polygon);
82          // convert the points of polygon to an array of Point3d
83          List<Point3d> list = new ArrayList<>();
84          polygon.getPoints().forEachRemaining(list::add);
85          otherPolygon = new Polygon3d(list);
86          assertEquals("Polygon created from polygon points is equal to original polygon", polygon, otherPolygon);
87          otherPolygon = new Polygon3d(list.get(0), list.get(1), list.get(2), list.get(3));
88          assertEquals("Polygon created from all four points of existing polygon is equal to original", polygon, otherPolygon);
89  
90          Point3d[] pointArray = list.toArray(new Point3d[0]);
91          otherPolygon = new Polygon3d(pointArray);
92          assertEquals("Polygon created from array of points of existing polygon is equal to original", polygon, otherPolygon);
93  
94          list.add(list.get(0));
95          otherPolygon = new Polygon3d(list.iterator());
96          assertEquals("Polygon created from polygon points and duplicate of first point at end is equal to original polygon",
97                  polygon, otherPolygon);
98  
99          otherPolygon = new Polygon3d(list);
100         assertEquals("Polygon created from polygon points and duplicate of first point at end is equal to original polygon",
101                 polygon, otherPolygon);
102         // Add a point that only differs in y
103         list.add(new Point3d(list.get(0).x, 123, list.get(0).z));
104         new Polygon3d(list.iterator());
105         list.add(list.get(0));
106         new Polygon3d(list.iterator());
107 
108         // Add a point that only differs in z
109         list.add(new Point3d(list.get(0).x, list.get(0).y, 123));
110         new Polygon3d(list.iterator());
111         list.add(list.get(0));
112         new Polygon3d(list.iterator());
113 
114         // Make last TWO points duplicate of first point
115         list.add(list.get(0));
116         try
117         {
118             new Polygon3d(list);
119             fail("last two points equal to first point should have thrown a DrawRuntimeException");
120         }
121         catch (DrawRuntimeException dre)
122         {
123             // Ignore expected exception
124         }
125 
126         try
127         {
128             polygon.getSegment(-1);
129             fail("Negative index should have thrown a DrawRuntimeException");
130         }
131         catch (DrawRuntimeException dre)
132         {
133             // Ignore expected exception
134         }
135 
136         try
137         {
138             polygon.getSegment(polygon.size());
139             fail("index equal to size (or more) should have thrown a DrawRuntimeException");
140         }
141         catch (DrawRuntimeException dre)
142         {
143             // Ignore expected exception
144         }
145 
146         try
147         {
148             new Polygon3d(new double[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, new double[] { 1, 2, 3, 4 });
149             fail("unequal length of coordinate array should have thrown a DrawRuntimeException");
150         }
151         catch (DrawRuntimeException dre)
152         {
153             // Ignore expected exception
154         }
155 
156         try
157         {
158             new Polygon3d(new double[] { 1, 2, 3 }, new double[] { 1, 2, 3, 4 }, new double[] { 1, 2, 3 });
159             fail("unequal length of coordinate array should have thrown a DrawRuntimeException");
160         }
161         catch (DrawRuntimeException dre)
162         {
163             // Ignore expected exception
164         }
165 
166         try
167         {
168             new Polygon3d(new double[] { 1, 2, 3, 4 }, new double[] { 1, 2, 3 }, new double[] { 1, 2, 3 });
169             fail("unequal length of coordinate array should have thrown a DrawRuntimeException");
170         }
171         catch (DrawRuntimeException dre)
172         {
173             // Ignore expected exception
174         }
175 
176         try
177         {
178             new Polygon3d(null, new double[] { 1, 2, 3 }, new double[] { 1, 2, 3 });
179             fail("null for x array hould have thrown a NullPointerException");
180         }
181         catch (NullPointerException npe)
182         {
183             // Ignore expected exception
184         }
185 
186         try
187         {
188             new Polygon3d(new double[] { 1, 2, 3 }, null, new double[] { 1, 2, 3 });
189             fail("null for x array hould have thrown a NullPointerException");
190         }
191         catch (NullPointerException npe)
192         {
193             // Ignore expected exception
194         }
195 
196         try
197         {
198             new Polygon3d(new double[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, null);
199             fail("null for x array hould have thrown a NullPointerException");
200         }
201         catch (NullPointerException npe)
202         {
203             // Ignore expected exception
204         }
205 
206         try
207         {
208             new Polygon3d(new double[] { 1 }, new double[] { 1 }, new double[] { 1 });
209             fail("too short coordinate array should have thrown a DrawRuntimeException");
210         }
211         catch (DrawRuntimeException dre)
212         {
213             // Ignore expected exception
214         }
215 
216         try
217         {
218             new Polygon3d(new Point3d(1, 2, 3), new Point3d(1, 2, 3), new Point3d[] {});
219             fail("too short coordinate array should have thrown a DrawRuntimeException");
220         }
221         catch (DrawRuntimeException dre)
222         {
223             // Ignore expected exception
224         }
225 
226         try
227         {
228             new Polygon3d(new Point3d(1, 2, 3), new Point3d(1, 2, 3), (Point3d[]) null);
229             fail("too short coordinate array should have thrown a DrawRuntimeException");
230         }
231         catch (DrawRuntimeException dre)
232         {
233             // Ignore expected exception
234         }
235 
236         try
237         {
238             new Polygon3d(new Point3d(1, 2, 3), new Point3d(3, 2, 5),
239                     new Point3d[] { new Point3d(1, 2, 3), new Point3d(1, 2, 3) });
240             fail("two identical points at end, matching first point should have thrown a DrawRuntimeException");
241         }
242         catch (DrawRuntimeException dre)
243         {
244             // Ignore expected exception
245         }
246 
247         list.clear();
248         list.add(new Point3d(1, 2, 3));
249         try
250         {
251             new Polygon3d(list);
252             fail("too short list should have thrown a DrawRuntimeException");
253         }
254         catch (DrawRuntimeException dre)
255         {
256             // Ignore expected exception
257         }
258 
259     }
260     
261     /**
262      * Test the filtering constructors.
263      */
264     @Test
265     public void filterTest()
266     {
267         Point3d[] points = new Point3d[] {new Point3d(1, 2, 3), new Point3d(1, 2, 3), new Point3d(4, 5, 6)};
268         try
269         {
270             new Polygon3d(false, points);
271             fail("duplicate point should have thrown a DrawRuntimeException");
272         }
273         catch (DrawRuntimeException dre)
274         {
275             // Ignore expected exception
276         }
277         
278         assertEquals("After filtering; there are two points left", 2, new Polygon3d(true, points).size()); 
279         
280         List<Point3d> list = Arrays.asList(points);
281         try
282         {
283             new Polygon3d(false, list);
284             fail("duplicate point should have thrown a DrawRuntimeException");
285         }
286         catch (DrawRuntimeException dre)
287         {
288             // Ignore expected exception
289         }
290         
291         assertEquals("After filtering; there are two points left", 2, new Polygon3d(true, list).size()); 
292     }
293 
294     /**
295      * Test the reverse and project methods.
296      * @throws DrawRuntimeException should not happen; this test has failed if it does happen
297      */
298     @Test
299     public final void reverseAndProjectTest() throws DrawRuntimeException
300     {
301         Point3d p0 = new Point3d(1.1, 2.21, 3.1);
302         Point3d p1 = new Point3d(2.1, 2.22, 3.2);
303         Point3d p2 = new Point3d(2.1, 2.23, 3.3);
304         Point3d p2x = new Point3d(p2.x, p2.y, p2.z + 1);
305         Point3d p3 = new Point3d(4.1, 2.24, 3.4);
306         Point3d p4 = new Point3d(5.1, 2.25, 3.5);
307         Point3d p5 = new Point3d(6.1, 2.26, 3.6);
308 
309         Polygon3d l01 = new Polygon3d(p0, p1);
310         Polygon3d r = l01.reverse();
311         assertEquals("result has size 2", 2, r.size());
312         assertEquals("point 0 is p1", p1, r.get(0));
313         assertEquals("point 1 is p0", p0, r.get(1));
314 
315         Polygon3d l05 = new Polygon3d(p0, p1, p2, p3, p4, p5);
316         r = l05.reverse();
317         assertEquals("result has size 6", 6, r.size());
318         assertEquals("point 0 is p5", p5, r.get(0));
319         assertEquals("point 1 is p4", p4, r.get(1));
320         assertEquals("point 2 is p3", p3, r.get(2));
321         assertEquals("point 3 is p2", p2, r.get(3));
322         assertEquals("point 4 is p1", p1, r.get(4));
323         assertEquals("point 5 is p0", p0, r.get(5));
324 
325         PolyLine2d l2d = l05.project();
326         assertEquals("result has size 6", 6, l2d.size());
327         assertEquals("point 0 is p5", p0.project(), l2d.get(0));
328         assertEquals("point 1 is p4", p1.project(), l2d.get(1));
329         assertEquals("point 2 is p3", p2.project(), l2d.get(2));
330         assertEquals("point 3 is p2", p3.project(), l2d.get(3));
331         assertEquals("point 4 is p1", p4.project(), l2d.get(4));
332         assertEquals("point 5 is p0", p5.project(), l2d.get(5));
333 
334         l05 = new Polygon3d(p0, p1, p2, p2x, p3, p4, p5);
335         l2d = l05.project();
336         assertEquals("result has size 6", 6, l2d.size());
337         assertEquals("point 0 is p5", p0.project(), l2d.get(0));
338         assertEquals("point 1 is p4", p1.project(), l2d.get(1));
339         assertEquals("point 2 is p3", p2.project(), l2d.get(2));
340         assertEquals("point 3 is p2", p3.project(), l2d.get(3));
341         assertEquals("point 4 is p1", p4.project(), l2d.get(4));
342         assertEquals("point 5 is p0", p5.project(), l2d.get(5));
343 
344         Polygon3d l22x = new Polygon3d(p2, p2x);
345         try
346         {
347             l22x.project();
348             fail("Projecting a polygon3d that entirely projects to one point should have thrown an exception");
349         }
350         catch (DrawRuntimeException dre)
351         {
352             // Ignore expected exception
353         }
354     }
355 
356     /**
357      * Verify the various properties of a Polygon3d.
358      * @param where String; description of the test
359      * @param x double[]; the expected x coordinates
360      * @param y double[]; the expected y coordinates
361      * @param z double[]; the expected z coordinates
362      * @param polygon Polygon3d; the Polygon3d
363      */
364     private void checkPolygon(final String where, final double[] x, final double[] y, final double[] z, final Polygon3d polygon)
365     {
366         double cumulativeLength = 0;
367         for (int index = 0; index < polygon.size(); index++)
368         {
369             assertEquals(where + " x[index]", x[index], polygon.getX(index), Math.ulp(x[index]));
370             assertEquals(where + " y[index]", y[index], polygon.getY(index), Math.ulp(y[index]));
371             assertEquals(where + " z[index]", z[index], polygon.getZ(index), Math.ulp(z[index]));
372             LineSegment3d segment = polygon.getSegment(index);
373             assertEquals(where + " segment start x", x[index], segment.startX, Math.ulp(x[index]));
374             assertEquals(where + " segment start y", y[index], segment.startY, Math.ulp(y[index]));
375             assertEquals(where + " segment start z", z[index], segment.startZ, Math.ulp(z[index]));
376             int wrappedIndex = (index + 1) % polygon.size();
377             assertEquals(where + " segment end x", x[wrappedIndex], segment.endX, Math.ulp(x[wrappedIndex]));
378             assertEquals(where + " segment end y", y[wrappedIndex], segment.endY, Math.ulp(y[wrappedIndex]));
379             assertEquals(where + " segment end z", z[wrappedIndex], segment.endZ, Math.ulp(z[wrappedIndex]));
380             cumulativeLength += segment.getLength();
381         }
382         assertEquals(where + " circumference", cumulativeLength, polygon.getLength(),
383                 polygon.size() * Math.ulp(cumulativeLength));
384     }
385 
386     /**
387      * Test the debugging output methods.
388      */
389     @Test
390     public void testExports()
391     {
392         Point3d[] points = new Point3d[] { new Point3d(123.456, 345.678, 901.234), new Point3d(234.567, 456.789, 12.345),
393                 new Point3d(-12.345, -34.567, 45.678) };
394         Polygon3d pl = new Polygon3d(points);
395         String[] out = pl.toExcel().split("\\n");
396         assertEquals("Excel output consists of one line per point plus one", points.length + 1, out.length);
397         for (int index = 0; index <= points.length; index++)
398         {
399             String[] fields = out[index].split("\\t");
400             assertEquals("each line consists of three fields", 3, fields.length);
401             try
402             {
403                 double x = Double.parseDouble(fields[0].trim());
404                 assertEquals("x matches", points[index % pl.size()].x, x, 0.001);
405             }
406             catch (NumberFormatException nfe)
407             {
408                 fail("First field " + fields[0] + " does not parse as a double");
409             }
410             try
411             {
412                 double y = Double.parseDouble(fields[1].trim());
413                 assertEquals("y matches", points[index % pl.size()].y, y, 0.001);
414             }
415             catch (NumberFormatException nfe)
416             {
417                 fail("Second field " + fields[1] + " does not parse as a double");
418             }
419             try
420             {
421                 double z = Double.parseDouble(fields[2].trim());
422                 assertEquals("z matches", points[index % pl.size()].z, z, 0.001);
423             }
424             catch (NumberFormatException nfe)
425             {
426                 fail("Second field " + fields[2] + " does not parse as a double");
427             }
428         }
429     }
430 
431 }