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.DrawException;
9   import org.djutils.draw.DrawRuntimeException;
10  import org.djutils.draw.Drawable3d;
11  import org.djutils.draw.Space3d;
12  import org.djutils.draw.bounds.Bounds3d;
13  import org.djutils.draw.point.OrientedPoint3d;
14  import org.djutils.draw.point.Point2d;
15  import org.djutils.draw.point.Point3d;
16  import org.djutils.exceptions.Throw;
17  import org.djutils.logger.CategoryLogger;
18  
19  /**
20   * Implementation of Line for 3D space.
21   * <p>
22   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
23   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
24   * </p>
25   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
26   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
27   */
28  public class PolyLine3d implements Drawable3d, PolyLine<PolyLine3d, Point3d, Space3d, OrientedPoint3d>
29  {
30      /** */
31      private static final long serialVersionUID = 20200911L;
32  
33      /** The points of the line. */
34      private final Point3d[] points;
35  
36      /** The cumulative length of the line at point 'i'. */
37      private final double[] lengthIndexedLine;
38  
39      /** The length. */
40      private final double length;
41  
42      /** Bounding box of this Line3d. */
43      private final Bounds3d bounds;
44  
45      /**
46       * Construct a new Line3d and initialize its length indexed line, bounds, centroid and length.
47       * @param copyNeeded boolean; if true; a deep copy of the points array is stored instead of the provided array
48       * @param points Point3d[]; the array of points to construct this Line3d from.
49       * @throws NullPointerException when iterator is null
50       * @throws DrawRuntimeException when the provided points do not constitute a valid line (too few points or identical
51       *             adjacent points)
52       */
53      private PolyLine3d(final boolean copyNeeded, final Point3d[] points) throws NullPointerException, DrawRuntimeException
54      {
55          Throw.whenNull(points, "points cannot be null");
56          Throw.when(points.length < 2, DrawRuntimeException.class, "Need at least two points");
57          this.points = copyNeeded ? Arrays.copyOf(points, points.length) : points;
58          Point3d prevPoint = points[0];
59          double minX = prevPoint.x;
60          double minY = prevPoint.y;
61          double minZ = prevPoint.z;
62          double maxX = prevPoint.x;
63          double maxY = prevPoint.y;
64          double maxZ = prevPoint.z;
65          this.lengthIndexedLine = new double[this.points.length];
66          this.lengthIndexedLine[0] = 0.0;
67          for (int i = 1; i < this.points.length; i++)
68          {
69              Point3d point = this.points[i];
70              minX = Math.min(minX, point.x);
71              minY = Math.min(minY, point.y);
72              minZ = Math.min(minZ, point.z);
73              maxX = Math.max(maxX, point.x);
74              maxY = Math.max(maxY, point.y);
75              maxZ = Math.max(maxZ, point.z);
76              if (prevPoint.x == point.x && prevPoint.y == point.y && prevPoint.z == point.z)
77              {
78                  throw new DrawRuntimeException(
79                          "Degenerate Line3d; point " + (i - 1) + " has the same x, y and z as point " + i);
80              }
81              this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + prevPoint.distance(point);
82              prevPoint = point;
83          }
84          this.length = this.lengthIndexedLine[this.lengthIndexedLine.length - 1];
85          this.bounds = new Bounds3d(minX, maxX, minY, maxY, minZ, maxZ);
86      }
87  
88      /**
89       * Construct a new PolyLine3d from an array of Point3d.
90       * @param point1 Point3d; starting point of the PolyLine3d
91       * @param point2 Point3d; second point of the PolyLine3d
92       * @param otherPoints Point3d...; additional points of the PolyLine3d
93       * @throws NullPointerException when iterator is null
94       * @throws DrawRuntimeException when the provided points do not constitute a valid line (too few points or identical
95       *             adjacent points)
96       */
97      public PolyLine3d(final Point3d.html#Point3d">Point3d point1, final Point3d point2, final Point3d... otherPoints)
98              throws NullPointerException, DrawRuntimeException
99      {
100         this(false, spliceArray(point1, point2, otherPoints));
101     }
102 
103     /**
104      * Construct an array of Point3d from two points plus an array of Point3d.
105      * @param point1 Point3d; the first point (ends up at index 0 of the result)
106      * @param point2 Point3d; the second point (ends up at index 1 of the result)
107      * @param otherPoints Point3d...; may be null, may be empty. If non empty, the elements in otherPoints end up at index 2 and
108      *            up in the result
109      * @return Point2d[]; the combined array
110      */
111     private static Point3d.html#Point3d">Point3dPoint3d">Point3d[] spliceArray(final Point3d.html#Point3d">Point3d point1, final Point3d point2, final Point3d... otherPoints)
112     {
113         Point3dhtml#Point3d">Point3d[] result = new Point3d[2 + (otherPoints == null ? 0 : otherPoints.length)];
114         result[0] = point1;
115         result[1] = point2;
116         if (otherPoints != null)
117         {
118             for (int i = 0; i < otherPoints.length; i++)
119             {
120                 result[i + 2] = otherPoints[i];
121             }
122         }
123         return result;
124     }
125 
126     /**
127      * Construct a new PolyLine3d from an array of Point3d.
128      * @param points Point3d[]; points of the PolyLine3d
129      * @throws NullPointerException when iterator is null
130      * @throws DrawRuntimeException when the provided points do not constitute a valid line (too few points or identical
131      *             adjacent points)
132      */
133     public PolyLine3d(final Point3d[] points) throws NullPointerException, DrawRuntimeException
134     {
135         this(true, checkLengthIsTwoOrMore(Throw.whenNull(points, "points may not be null")));
136     }
137 
138     /**
139      * Check that the length of an array of Point3d is at least two.
140      * @param points Point3d[]; the array of points to check
141      * @return Point3d[]; points
142      * @throws DrawRuntimeException when the length of points is less than two
143      */
144     private static Point3dint3d[] checkLengthIsTwoOrMore(final Point3d[] points) throws DrawRuntimeException
145     {
146         Throw.when(points.length < 2, DrawRuntimeException.class, "Need at least two points");
147         return points;
148     }
149 
150     /**
151      * Construct a new Line3d and initialize its length indexed line, bounds, centroid and length.
152      * @param iterator Iterator&lt;Point3d&gt;; iterator that will provide all points that constitute the new Line3d
153      * @throws NullPointerException when iterator is null
154      * @throws DrawException when the iterator provides too few points, or some adjacent identical points)
155      */
156     public PolyLine3d(final Iterator<Point3d> iterator) throws NullPointerException, DrawException
157     {
158         this(iteratorToList(Throw.whenNull(iterator, "iterator cannot be null")));
159     }
160 
161     /**
162      * Construct a new Line3d from a List&lt;Point3d&gt;.
163      * @param pointList List&lt;Point3d&gt;; the list of points to construct this Line3d from.
164      * @throws DrawRuntimeException when the provided points do not constitute a valid line (too few points or identical
165      *             adjacent points)
166      */
167     public PolyLine3d(final List<Point3d> pointList) throws DrawRuntimeException
168     {
169         this(false, pointList.toArray(new Point3d[pointList.size()]));
170     }
171 
172     /** {@inheritDoc} */
173     @Override
174     public PolyLine3d instantiate(final List<Point3d> pointList) throws NullPointerException, DrawRuntimeException
175     {
176         return new PolyLine3d(pointList);
177     }
178 
179     /**
180      * Build a list from the Point3d objects that an iterator provides.
181      * @param iterator Iterator&lt;Point3d&gt;; the iterator that will provide the points
182      * @return List&lt;Point3d&gt;; a list of the points provided by the iterator
183      */
184     private static List<Point3d> iteratorToList(final Iterator<Point3d> iterator)
185     {
186         List<Point3d> result = new ArrayList<>();
187         iterator.forEachRemaining(result::add);
188         return result;
189     }
190 
191     /** {@inheritDoc} */
192     @Override
193     public int size()
194     {
195         return this.points.length;
196     }
197 
198     /** {@inheritDoc} */
199     @Override
200     public final Point3d get(final int i) throws IndexOutOfBoundsException
201     {
202         return this.points[i];
203     }
204 
205     /** {@inheritDoc} */
206     @Override
207     public final double lengthAtIndex(final int index)
208     {
209         return this.lengthIndexedLine[index];
210     }
211 
212     /** {@inheritDoc} */
213     @Override
214     public final double getLength()
215     {
216         return this.length;
217     }
218 
219     /** {@inheritDoc} */
220     @Override
221     public Iterator<Point3d> getPoints()
222     {
223         return Arrays.stream(this.points).iterator();
224     }
225 
226     /** {@inheritDoc} */
227     @Override
228     public Bounds3d getBounds()
229     {
230         return this.bounds;
231     }
232 
233     /**
234      * Construct a new Line3d that is equal to this line except for segments that are shorter than the <cite>noiseLevel</cite>.
235      * The result is guaranteed to start with the first point of this line and end with the last point of this line.
236      * @param noiseLevel double; the minimum segment length that is <b>not</b> removed
237      * @return Line3d; the filtered line
238      */
239     public final PolyLine3d noiseFilteredLine(final double noiseLevel)
240     {
241         if (this.size() <= 2)
242         {
243             return this; // Except for some cached fields; an Line3d is immutable; so safe to return
244         }
245         Point3d prevPoint = null;
246         List<Point3d> list = null;
247         for (int index = 0; index < this.size(); index++)
248         {
249             Point3d currentPoint = this.points[index];
250             if (null != prevPoint && prevPoint.distance(currentPoint) < noiseLevel)
251             {
252                 if (null == list)
253                 {
254                     // Found something to filter; copy this up to (and including) prevPoint
255                     list = new ArrayList<>();
256                     for (int i = 0; i < index; i++)
257                     {
258                         list.add(this.points[i]);
259                     }
260                 }
261                 if (index == this.size() - 1)
262                 {
263                     if (list.size() > 1)
264                     {
265                         // Replace the last point of the result by the last point of this Line3d
266                         list.set(list.size() - 1, currentPoint);
267                     }
268                     else
269                     {
270                         // Append the last point of this even though it is close to the first point than the noise value to
271                         // comply with the requirement that first and last point of this are ALWAYS included in the result.
272                         list.add(currentPoint);
273                     }
274                 }
275                 continue; // Do not replace prevPoint by currentPoint
276             }
277             else if (null != list)
278             {
279                 list.add(currentPoint);
280             }
281             prevPoint = currentPoint;
282         }
283         if (null == list)
284         {
285             return this;
286         }
287         if (list.size() == 2 && list.get(0).equals(list.get(1)))
288         {
289             // Insert point 1 of this; it MUST be different from point 0; so we don't have to test for anything.
290             list.add(1, this.points[1]);
291         }
292         try
293         {
294             return new PolyLine3d(list);
295         }
296         catch (DrawRuntimeException exception)
297         {
298             CategoryLogger.always().error(exception);
299             throw new Error(exception);
300         }
301     }
302 
303     /**
304      * Concatenate several Line3d instances.
305      * @param lines PolyLine3d...; Line3d... one or more Line3d. The last point of the first &lt;strong&gt;must&lt;/strong&gt;
306      *            match the first of the second, etc.
307      * @return Line3d
308      * @throws DrawException if zero lines are given, or when there is a gap between consecutive lines
309      */
310     public static PolyLine3d concatenate(final PolyLine3d... lines) throws DrawException
311     {
312         return concatenate(0.0, lines);
313     }
314 
315     /**
316      * Concatenate two Line3d instances. This method is separate for efficiency reasons.
317      * @param tolerance double; the tolerance between the end point of a line and the first point of the next line
318      * @param line1 PolyLine3d; first line
319      * @param line2 PolyLine3d; second line
320      * @return Line3d; the concatenation of the two lines
321      * @throws DrawException if zero lines are given, or when there is a gap between consecutive lines
322      */
323     public static PolyLine3d concatenate(final PolyLine3d.html#PolyLine3d">PolyLine3djxr_keyword">double tolerance, final PolyLine3d.html#PolyLine3d">PolyLine3d line1, final PolyLine3d line2)
324             throws DrawException
325     {
326         if (line1.getLast().distance(line2.getFirst()) > tolerance)
327         {
328             throw new DrawException("Lines are not connected: " + line1.getLast() + " to " + line2.getFirst() + " distance is "
329                     + line1.getLast().distance(line2.getFirst()) + " > " + tolerance);
330         }
331         int size = line1.size() + line2.size() - 1;
332         Point3dhtml#Point3d">Point3d[] points = new Point3d[size];
333         int nextIndex = 0;
334         for (int j = 0; j < line1.size(); j++)
335         {
336             points[nextIndex++] = line1.get(j);
337         }
338         for (int j = 1; j < line2.size(); j++)
339         {
340             points[nextIndex++] = line2.get(j);
341         }
342         return new PolyLine3d(false, points);
343     }
344 
345     /**
346      * Concatenate several Line3d instances.
347      * @param tolerance double; the tolerance between the end point of a line and the first point of the next line
348      * @param lines PolyLine3d...; Line3d... one or more Line3d. The last point of the first &lt;strong&gt;must&lt;/strong&gt;
349      *            match the first of the second, etc.
350      * @return Line3d; the concatenation of the lines
351      * @throws DrawException if zero lines are given, or when there is a gap between consecutive lines
352      */
353     public static PolyLine3d concatenate(final double tolerance, final PolyLine3d... lines) throws DrawException
354     {
355         if (0 == lines.length)
356         {
357             throw new DrawException("Empty argument list");
358         }
359         else if (1 == lines.length)
360         {
361             return lines[0];
362         }
363         int size = lines[0].size();
364         for (int i = 1; i < lines.length; i++)
365         {
366             if (lines[i - 1].getLast().distance(lines[i].getFirst()) > tolerance)
367             {
368                 throw new DrawException("Lines are not connected: " + lines[i - 1].getLast() + " to " + lines[i].getFirst()
369                         + " distance is " + lines[i - 1].getLast().distance(lines[i].getFirst()) + " > " + tolerance);
370             }
371             size += lines[i].size() - 1;
372         }
373         Point3dhtml#Point3d">Point3d[] points = new Point3d[size];
374         int nextIndex = 0;
375         for (int i = 0; i < lines.length; i++)
376         {
377             PolyLine3d line = lines[i];
378             for (int j = 0 == i ? 0 : 1; j < line.size(); j++)
379             {
380                 points[nextIndex++] = line.get(j);
381             }
382         }
383         return new PolyLine3d(false, points);
384     }
385 
386     /** {@inheritDoc} */
387     @Override
388     public PolyLine2d project() throws DrawRuntimeException
389     {
390         List<Point2d> pointList = new ArrayList<>();
391         Point2d prevPoint = null;
392         for (Point3d point3d : this.points)
393         {
394             Point2d point = point3d.project();
395             if (prevPoint != null)
396             {
397                 if (prevPoint.x == point.x && prevPoint.y == point.y)
398                 {
399                     continue;
400                 }
401             }
402             pointList.add(point);
403             prevPoint = point;
404         }
405         return new PolyLine2d(pointList);
406     }
407 
408     /**
409      * Create a new Line3d, filtering out repeating successive points.
410      * @param points Point3d...; the coordinates of the line as Point3d
411      * @return the line
412      * @throws DrawException when number of points &lt; 2
413      */
414     public static PolyLine3d createAndCleanLine3d(final Point3d... points) throws DrawException
415     {
416         if (points.length < 2)
417         {
418             throw new DrawException("Degenerate Line3d; has " + points.length + " point" + (points.length != 1 ? "s" : ""));
419         }
420         return createAndCleanLine3d(new ArrayList<>(Arrays.asList(points)));
421     }
422 
423     /**
424      * Create an Line3d, while filtering out repeating successive points.
425      * @param pointList List&lt;Point3d&gt;; list of the coordinates of the line as Point3d; any duplicate points in this list
426      *            are removed (this method may modify the provided list)
427      * @return Line3d; the line
428      * @throws DrawException when number of non-equal points &lt; 2
429      */
430     public static PolyLine3d createAndCleanLine3d(final List<Point3d> pointList) throws DrawException
431     {
432         // TODO avoid modifying the input list.
433         // clean successive equal points
434         int i = 1;
435         while (i < pointList.size())
436         {
437             if (pointList.get(i - 1).equals(pointList.get(i)))
438             {
439                 pointList.remove(i);
440             }
441             else
442             {
443                 i++;
444             }
445         }
446         return new PolyLine3d(pointList);
447     }
448 
449     /** {@inheritDoc} */
450     @Override
451     public final OrientedPoint3d getLocationExtended(final double position)
452     {
453         if (position >= 0.0 && position <= getLength())
454         {
455             try
456             {
457                 return getLocation(position);
458             }
459             catch (DrawException exception)
460             {
461                 // cannot happen
462             }
463         }
464 
465         // position before start point -- extrapolate using direction from first point to second point of this Line3d
466         if (position < 0.0)
467         {
468             double len = position;
469             double fraction = len / (this.lengthIndexedLine[1] - this.lengthIndexedLine[0]);
470             Point3d p1 = this.points[0];
471             Point3d p2 = this.points[1];
472             return new OrientedPoint3d(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y),
473                     p1.z + fraction * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
474         }
475 
476         // position beyond end point -- extrapolate using the direction from the before last point to the last point of this
477         // Line3d
478         int n1 = this.lengthIndexedLine.length - 1;
479         int n2 = this.lengthIndexedLine.length - 2;
480         double len = position - getLength();
481         double fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
482         while (Double.isInfinite(fraction))
483         {
484             if (--n2 < 0)
485             {
486                 CategoryLogger.always().error("lengthIndexedLine of {} is invalid", this);
487                 Point3d p = this.points[n1];
488                 return new OrientedPoint3d(p.x, p.y, p.z, 0.0, 0.0, 0.0); // Bogus direction
489             }
490             fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
491         }
492         Point3d p1 = this.points[n2];
493         Point3d p2 = this.points[n1];
494         return new OrientedPoint3d(p2.x + fraction * (p2.x - p1.x), p2.y + fraction * (p2.y - p1.y),
495                 p2.z + fraction * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
496     }
497 
498     /** {@inheritDoc} */
499     @Override
500     public final OrientedPoint3d getLocation(final double position) throws DrawException
501     {
502         if (position < 0.0 || position > getLength())
503         {
504             throw new DrawException("getLocationSI for line: position < 0.0 or > line length. Position = " + position
505                     + " m. Length = " + getLength() + " m.");
506         }
507         // handle special cases: position == 0.0, or position == length
508         if (position == 0.0)
509         {
510             Point3d p1 = this.points[0];
511             Point3d p2 = this.points[1];
512             return new OrientedPoint3d(p1.x, p1.y, p1.z, 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
513         }
514         if (position == getLength())
515         {
516             Point3d p1 = this.points[this.points.length - 2];
517             Point3d p2 = this.points[this.points.length - 1];
518             return new OrientedPoint3d(p2.x, p2.y, p2.z, 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
519         }
520 
521         // find the index of the line segment, use binary search
522         int index = find(position);
523         double remainder = position - this.lengthIndexedLine[index];
524         double fraction = remainder / (this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index]);
525         Point3d p1 = this.points[index];
526         Point3d p2 = this.points[index + 1];
527         return new OrientedPoint3d(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y),
528                 p1.z + fraction * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
529     }
530 
531     /** {@inheritDoc} */
532     @Override
533     public PolyLine3d extract(final double start, final double end) throws DrawException
534     {
535         if (Double.isNaN(start) || Double.isNaN(end) || start < 0 || start >= end || end > getLength())
536         {
537             throw new DrawException(
538                     "Bad interval (" + start + ".." + end + "; length of this Line3d is " + this.getLength() + ")");
539         }
540         double cumulativeLength = 0;
541         double nextCumulativeLength = 0;
542         double segmentLength = 0;
543         int index = 0;
544         List<Point3d> pointList = new ArrayList<>();
545         while (start > cumulativeLength)
546         {
547             Point3d fromPoint = get(index);
548             index++;
549             Point3d toPoint = get(index);
550             segmentLength = fromPoint.distance(toPoint);
551             cumulativeLength = nextCumulativeLength;
552             nextCumulativeLength = cumulativeLength + segmentLength;
553             if (nextCumulativeLength >= start)
554             {
555                 break;
556             }
557         }
558         if (start == nextCumulativeLength)
559         {
560             pointList.add(get(index));
561         }
562         else
563         {
564             pointList.add(get(index - 1).interpolate(get(index), (start - cumulativeLength) / segmentLength));
565             if (end > nextCumulativeLength)
566             {
567                 pointList.add(get(index));
568             }
569         }
570         while (end > nextCumulativeLength)
571         {
572             Point3d fromPoint = get(index);
573             index++;
574             if (index >= size())
575             {
576                 break; // rounding error
577             }
578             Point3d toPoint = get(index);
579             segmentLength = fromPoint.distance(toPoint);
580             cumulativeLength = nextCumulativeLength;
581             nextCumulativeLength = cumulativeLength + segmentLength;
582             if (nextCumulativeLength >= end)
583             {
584                 break;
585             }
586             pointList.add(toPoint);
587         }
588         if (end == nextCumulativeLength)
589         {
590             pointList.add(get(index));
591         }
592         else
593         {
594             Point3d point = get(index - 1).interpolate(get(index), (end - cumulativeLength) / segmentLength);
595             // can be the same due to rounding
596             if (!point.equals(pointList.get(pointList.size() - 1)))
597             {
598                 pointList.add(point);
599             }
600         }
601         try
602         {
603             return instantiate(pointList);
604         }
605         catch (DrawRuntimeException exception)
606         {
607             CategoryLogger.always().error(exception, "interval " + start + ".." + end + " too short");
608             throw new DrawException("interval " + start + ".." + end + "too short");
609         }
610     }
611 
612     /** {@inheritDoc} */
613     @Override
614     public PolyLine3d truncate(final double position) throws DrawException
615     {
616         if (position <= 0.0 || position > getLength())
617         {
618             throw new DrawException("truncate for line: position <= 0.0 or > line length. Position = " + position
619                     + ". Length = " + getLength() + " m.");
620         }
621 
622         // handle special case: position == length
623         if (position == getLength())
624         {
625             return this;
626         }
627 
628         // find the index of the line segment
629         int index = find(position);
630         double remainder = position - lengthAtIndex(index);
631         double fraction = remainder / (lengthAtIndex(index + 1) - lengthAtIndex(index));
632         Point3d p1 = get(index);
633         Point3d lastPoint;
634         if (0.0 == fraction)
635         {
636             index--;
637             lastPoint = p1;
638         }
639         else
640         {
641             Point3d p2 = get(index + 1);
642             lastPoint = p1.interpolate(p2, fraction);
643 
644         }
645         // FIXME: Cannot create a P[]; will have to do it with a List<P>
646         List<Point3d> coords = new ArrayList<>(index + 2);
647         for (int i = 0; i <= index; i++)
648         {
649             coords.add(get(i));
650         }
651         coords.add(lastPoint);
652         return instantiate(coords);
653     }
654 
655     /** {@inheritDoc} */
656     @Override
657     @SuppressWarnings("checkstyle:designforextension")
658     public int hashCode()
659     {
660         final int prime = 31;
661         int result = 1;
662         result = prime * result + Arrays.hashCode(this.points);
663         return result;
664     }
665 
666     /** {@inheritDoc} */
667     @Override
668     public String toString()
669     {
670         return "PolyLine3d [points=" + Arrays.toString(this.points) + "]";
671     }
672 
673     /** {@inheritDoc} */
674     @Override
675     @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
676     public boolean equals(final Object obj)
677     {
678         if (this == obj)
679             return true;
680         if (obj == null)
681             return false;
682         if (getClass() != obj.getClass())
683             return false;
684         PolyLine3d../../../org/djutils/draw/line/PolyLine3d.html#PolyLine3d">PolyLine3d other = (PolyLine3d) obj;
685         if (!Arrays.equals(this.points, other.points))
686             return false;
687         return true;
688     }
689 
690 }