View Javadoc
1   package org.djutils.draw.line;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.Iterator;
6   import java.util.List;
7   
8   import org.djutils.draw.DrawRuntimeException;
9   import org.djutils.draw.point.Point3d;
10  import org.djutils.exceptions.Throw;
11  
12  /**
13   * Polygon3d.java. Closed PolyLine3d. The actual closing point (which is the same as the starting point) is NOT included in the
14   * super PolyLine3d. The constructors automatically remove the last point if it is a at the same location as the first point.
15   * <p>
16   * Copyright (c) 2020-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
17   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
18   * </p>
19   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
20   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
21   */
22  public class Polygon3d extends PolyLine3d
23  {
24      /** */
25      private static final long serialVersionUID = 20209999L;
26  
27      /**
28       * Construct a new Polygon3d.
29       * @param x double[]; the x coordinates of the points
30       * @param y double[]; the y coordinates of the points
31       * @param z double[]; the z coordinates of the points
32       * @throws DrawRuntimeException when any two successive points are equal, or when there are too few points
33       */
34      public Polygon3d(final double[] x, final double[] y, final double[] z) throws DrawRuntimeException
35      {
36          super(fixClosingPointX(Throw.whenNull(x, "x"), Throw.whenNull(y, "y"), Throw.whenNull(z, "z")),
37                  fixClosingPointY(x, y, z), fixClosingPointZ(x, y, z));
38      }
39  
40      /**
41       * Ensure that the last point is not equal to the first. Remove the last point if necessary.
42       * @param x double[]; the x coordinates of the points
43       * @param y double[]; the y coordinates of the points
44       * @param z double[]; the z coordinates of the points
45       * @return double[]; the y coordinates of the points (possibly a copy with the last element removed)
46       */
47      static double[] fixClosingPointX(final double[] x, final double[] y, final double[] z)
48      {
49          if (x.length > 1 && y.length == x.length && z.length == x.length && x[0] == x[x.length - 1] && y[0] == y[x.length - 1]
50                  && z[0] == z[z.length - 1])
51          {
52              return Arrays.copyOf(x, x.length - 1);
53          }
54          return x;
55      }
56  
57      /**
58       * Ensure that the last point is not equal to the first. Remove the last point if necessary.
59       * @param x double[]; the x coordinates of the points
60       * @param y double[]; the y coordinates of the points
61       * @param z double[]; the z coordinates of the points
62       * @return double[]; the y coordinates of the points (possibly a copy with the last element removed)
63       */
64      static double[] fixClosingPointY(final double[] x, final double[] y, final double[] z)
65      {
66          if (x.length > 1 && y.length == x.length && z.length == x.length && x[0] == x[x.length - 1] && y[0] == y[x.length - 1]
67                  && z[0] == z[z.length - 1])
68          {
69              return Arrays.copyOf(y, x.length - 1);
70          }
71          return y;
72      }
73  
74      /**
75       * Ensure that the last point is not equal to the first. Remove the last point if necessary.
76       * @param x double[]; the x coordinates of the points
77       * @param y double[]; the y coordinates of the points
78       * @param z double[]; the z coordinates of the points
79       * @return double[]; the y coordinates of the points (possibly a copy with the last element removed)
80       */
81      static double[] fixClosingPointZ(final double[] x, final double[] y, final double[] z)
82      {
83          if (x.length > 1 && y.length == x.length && z.length == x.length && x[0] == x[x.length - 1] && y[0] == y[x.length - 1]
84                  && z[0] == z[z.length - 1])
85          {
86              return Arrays.copyOf(z, x.length - 1);
87          }
88          return z;
89      }
90  
91      /**
92       * Construct a new Polygon3d.
93       * @param points Point3d[]; array of Point3d objects.
94       * @throws NullPointerException when points is null
95       * @throws DrawRuntimeException when points is too short, or contains successive duplicate points
96       */
97      public Polygon3d(final Point3d[] points) throws NullPointerException, DrawRuntimeException
98      {
99          this(PolyLine3d.makeArray(Throw.whenNull(points, "points"), p -> p.x), PolyLine3d.makeArray(points, p -> p.y),
100                 PolyLine3d.makeArray(points, p -> p.z));
101     }
102 
103     /**
104      * Construct a new Polygon3d.
105      * @param point1 Point3d; the first point of the new Polygon3d
106      * @param point2 Point3d; the second point of the new Polygon3d
107      * @param otherPoints Point3d[]; all remaining points of the new Polygon3d (may be null)
108      * @throws NullPointerException when point1 or point2 is null
109      * @throws DrawRuntimeException when point1 is equal to the last point of otherPoints, or any two successive points are
110      *             equal
111      */
112     public Polygon3d(final Point3d point1, final Point3d point2, final Point3d... otherPoints)
113             throws NullPointerException, DrawRuntimeException
114     {
115         super(Throw.whenNull(point1, "point1"), Throw.whenNull(point2, "point2"), fixClosingPoint(point1, otherPoints));
116     }
117 
118     /**
119      * Ensure that the last point of otherPoints is not equal to point1. Remove the last point if necessary.
120      * @param point1 Point3d; the first point of a new Polygon3d
121      * @param otherPoints Point3d[]; the remaining points of a new Polygon3d (may be null)
122      * @return Point3d[]; otherPoints (possibly a copy thereof with the last entry removed)
123      */
124     private static Point3d[] fixClosingPoint(final Point3d point1, final Point3d[] otherPoints)
125     {
126         if (otherPoints == null || otherPoints.length == 0)
127         {
128             return otherPoints;
129         }
130         Point3d[] result = otherPoints;
131         Point3d lastPoint = result[result.length - 1];
132         if (point1.x == lastPoint.x && point1.y == lastPoint.y)
133         {
134             result = Arrays.copyOf(otherPoints, result.length - 1);
135             lastPoint = result[result.length - 1];
136         }
137         Throw.when(point1.x == lastPoint.x && point1.y == lastPoint.y, DrawRuntimeException.class,
138                 "Before last point and last point are at same location");
139         return result;
140     }
141 
142     /**
143      * Construct a new Polygon3d from a list of Point3d objects.
144      * @param points List&lt;Point3d&gt;; the list of points
145      * @throws NullPointerException when points is null
146      * @throws DrawRuntimeException when points is too short, or the last two points are at the same location
147      */
148     public Polygon3d(final List<Point3d> points) throws NullPointerException, DrawRuntimeException
149     {
150         super(fixClosingPoint(true, Throw.whenNull(points, "points")));
151 
152     }
153 
154     /**
155      * Ensure that the last point in the list is different from the first point by possibly removing the last point.
156      * @param doNotModifyList boolean; if true; the list of points will not be modified (if the last point is to be removed; the
157      *            entire list up to the last point is duplicated)
158      * @param points List&lt;Point3d&gt;; the list of points
159      * @return List&lt;Point3d&gt;; the fixed list
160      * @throws DrawRuntimeException when the (resulting) list is too short, or the before last and last point of points have the
161      *             same coordinates
162      */
163     private static List<Point3d> fixClosingPoint(final boolean doNotModifyList, final List<Point3d> points)
164             throws DrawRuntimeException
165     {
166         Throw.when(points.size() < 2, DrawRuntimeException.class, "Need at least two points");
167         Point3d firstPoint = points.get(0);
168         Point3d lastPoint = points.get(points.size() - 1);
169         List<Point3d> result = points;
170         if (firstPoint.x == lastPoint.x && firstPoint.y == lastPoint.y && firstPoint.z == lastPoint.z)
171         {
172             if (doNotModifyList)
173             {
174                 result = new ArrayList<>(points.size() - 1);
175                 for (int i = 0; i < points.size() - 1; i++)
176                 {
177                     result.add(points.get(i));
178                 }
179             }
180             else
181             {
182                 result.remove(points.size() - 1);
183             }
184             lastPoint = result.get(result.size() - 1);
185         }
186         Throw.when(firstPoint.x == lastPoint.x && firstPoint.y == lastPoint.y && firstPoint.z == lastPoint.z,
187                 DrawRuntimeException.class, "Before last point and last point are at same location");
188         return result;
189     }
190 
191     /**
192      * Construct a new Polygon3d from an iterator that yields Point3d.
193      * @param iterator Iterator&lt;Point3d&gt;; the iterator
194      */
195     public Polygon3d(final Iterator<Point3d> iterator)
196     {
197         this(fixClosingPoint(false, iteratorToList(Throw.whenNull(iterator, "iterator"))));
198     }
199 
200     /**
201      * Create a new Polygon3d, optionally filtering out repeating successive points.
202      * @param filterDuplicates boolean; if true; filter out successive repeated points; otherwise do not filter
203      * @param points Point3d...; the coordinates of the polygon as Point3d
204      * @throws DrawRuntimeException when number of points &lt; 2
205      */
206     public Polygon3d(final boolean filterDuplicates, final Point3d... points) throws DrawRuntimeException
207     {
208         this(PolyLine3d.cleanPoints(filterDuplicates, Arrays.stream(points).iterator()));
209     }
210 
211     /**
212      * Create a new Polygon3d, optionally filtering out repeating successive points.
213      * @param filterDuplicates boolean; if true; filter out successive repeated points; otherwise do not filter
214      * @param pointList List&lt;Point3d&gt;; list of the coordinates of the line as Point3d; any duplicate points in this list
215      *            are removed (this method may modify the provided list)
216      * @throws DrawRuntimeException when number of non-equal points &lt; 2
217      */
218     public Polygon3d(final boolean filterDuplicates, final List<Point3d> pointList) throws DrawRuntimeException
219     {
220         this(PolyLine3d.cleanPoints(filterDuplicates, pointList.iterator()));
221     }
222 
223     /**
224      * Construct a new Polygon3d from an existing one. This constructor is primarily intended for use in extending classes.
225      * @param polygon Polygon3d; the existing Polygon3d
226      */
227     public Polygon3d(final Polygon3d polygon)
228     {
229         super(polygon);
230     }
231 
232     @Override
233     public double getLength()
234     {
235         // Length a polygon is computed by taking the length of the PolyLine and adding the length of the closing segment
236         return super.getLength()
237                 + Math.hypot(Math.hypot(getX(size() - 1) - getX(0), getY(size() - 1) - getY(0)), getZ(size() - 1) - getZ(0));
238     }
239 
240     @Override
241     public LineSegment3d getSegment(final int index)
242     {
243         if (index < size() - 1)
244         {
245             return super.getSegment(index);
246         }
247         Throw.when(index != size() - 1, DrawRuntimeException.class, "index must be in range 0..size() - 1");
248         return new LineSegment3d(getX(index), getY(index), getZ(index), getX(0), getY(0), getZ(0));
249     }
250 
251     @Override
252     public Polygon2d project() throws DrawRuntimeException
253     {
254         double[] projectedX = new double[this.size()];
255         double[] projectedY = new double[this.size()];
256 
257         int nextIndex = 0;
258         for (int i = 0; i < this.size(); i++)
259         {
260             if (i > 0 && getX(i) == getX(i - 1) && getY(i) == getY(i - 1))
261             {
262                 continue;
263             }
264             projectedX[nextIndex] = getX(i);
265             projectedY[nextIndex] = getY(i);
266             nextIndex++;
267         }
268         if (nextIndex < projectedX.length)
269         {
270             return new Polygon2d(Arrays.copyOf(projectedX, nextIndex), Arrays.copyOf(projectedY, nextIndex));
271         }
272         return new Polygon2d(projectedX, projectedY);
273     }
274 
275     @Override
276     public Polygon3d reverse()
277     {
278         return new Polygon3d(super.reverse().getPoints());
279     }
280 
281     @Override
282     public final String toString()
283     {
284         return toString("%f", false);
285     }
286 
287     @Override
288     public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
289     {
290         StringBuilder result = new StringBuilder();
291         if (!doNotIncludeClassName)
292         {
293             result.append("Polygon3d ");
294         }
295         result.append("[super=");
296         result.append(super.toString(doubleFormat, false));
297         result.append("]");
298         return result.toString();
299     }
300 
301 }