View Javadoc
1   package org.djutils.draw.bounds;
2   
3   import java.io.Serializable;
4   import java.util.Arrays;
5   import java.util.Collection;
6   import java.util.Iterator;
7   
8   import org.djutils.draw.Drawable3d;
9   import org.djutils.draw.Space3d;
10  import org.djutils.draw.point.Point3d;
11  import org.djutils.exceptions.Throw;
12  
13  /**
14   * Bounds3d is the generic class for the 3D extent of an object. It is an immutable object.
15   * <p>
16   * Copyright (c) 2020-2021 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 Bounds3d implements Serializable, Drawable3d, Bounds<Bounds3d, Space3d>
23  {
24      /** */
25      private static final long serialVersionUID = 2020829L;
26  
27      /** The lower bound for x. */
28      private final double minX;
29  
30      /** The lower bound for y. */
31      private final double minY;
32  
33      /** The lower bound for z. */
34      private final double minZ;
35  
36      /** The upper bound for x. */
37      private final double maxX;
38  
39      /** The upper bound for y. */
40      private final double maxY;
41  
42      /** The upper bound for z. */
43      private final double maxZ;
44  
45      /**
46       * Construct a Bounds3d by providing all lower and upper bounds.
47       * @param minX double; the lower bound for x
48       * @param maxX double; the upper bound for x
49       * @param minY double; the lower bound for y
50       * @param maxY double; the upper bound for y
51       * @param minZ double; the lower bound for z
52       * @param maxZ double; the upper bound for z
53       * @throws IllegalArgumentException when lower bounds are larger than upper boundingBox or any bound is NaN
54       */
55      public Bounds3d(final double minX, final double maxX, final double minY, final double maxY, final double minZ,
56              final double maxZ)
57      {
58          Throw.when(Double.isNaN(minX) || Double.isNaN(maxX) || Double.isNaN(minY) || Double.isNaN(maxY) || Double.isNaN(minZ)
59                  || Double.isNaN(maxZ), IllegalArgumentException.class, "Nan boundary value not permitted");
60          Throw.when(minX > maxX || minY > maxY || minZ > maxZ, IllegalArgumentException.class,
61                  "lower bound for each dimension should be less than or equal to its upper bound");
62          this.minX = minX;
63          this.minY = minY;
64          this.minZ = minZ;
65          this.maxX = maxX;
66          this.maxY = maxY;
67          this.maxZ = maxZ;
68      }
69  
70      /**
71       * Constructs a new Bounds3d around the origin (0, 0, 0).
72       * @param deltaX double; the deltaX value around the origin
73       * @param deltaY double; the deltaY value around the origin
74       * @param deltaZ double; the deltaZ value around the origin
75       * @throws IllegalArgumentException when one of the delta values is less than zero
76       */
77      public Bounds3d(final double deltaX, final double deltaY, final double deltaZ)
78      {
79          this(-0.5 * deltaX, 0.5 * deltaX, -0.5 * deltaY, 0.5 * deltaY, -0.5 * deltaZ, 0.5 * deltaZ);
80      }
81  
82      /**
83       * Construct a Bounds3d from some collection of points, finding the lowest and highest x and y coordinates.
84       * @param points Iterator&lt;? extends Point3d&gt;; the array of points to construct a Bounds3d from
85       * @throws NullPointerException when points is null
86       * @throws IllegalArgumentException when zero points are provided
87       */
88      public Bounds3d(final Iterator<? extends Point3d> points)
89      {
90          Throw.whenNull(points, "points may not be null");
91          Throw.when(!points.hasNext(), IllegalArgumentException.class, "need at least one point");
92          Point3d point = points.next();
93          double tempMinX = point.x;
94          double tempMaxX = point.x;
95          double tempMinY = point.y;
96          double tempMaxY = point.y;
97          double tempMinZ = point.z;
98          double tempMaxZ = point.z;
99          while (points.hasNext())
100         {
101             point = points.next();
102             tempMinX = Math.min(tempMinX, point.x);
103             tempMaxX = Math.max(tempMaxX, point.x);
104             tempMinY = Math.min(tempMinY, point.y);
105             tempMaxY = Math.max(tempMaxY, point.y);
106             tempMinZ = Math.min(tempMinZ, point.z);
107             tempMaxZ = Math.max(tempMaxZ, point.z);
108         }
109         this.minX = tempMinX;
110         this.maxX = tempMaxX;
111         this.minY = tempMinY;
112         this.maxY = tempMaxY;
113         this.minZ = tempMinZ;
114         this.maxZ = tempMaxZ;
115     }
116 
117     /**
118      * Construct a Bounds3d from an array of Point3d, finding the lowest and highest x, y and z coordinates.
119      * @param points Point3d[]; the points to construct a Bounds3d from
120      * @throws NullPointerException when points is null
121      * @throws IllegalArgumentException when zero points are provided
122      */
123     public Bounds3d(final Point3d[] points) throws NullPointerException, IllegalArgumentException
124     {
125         this(Arrays.stream(Throw.whenNull(points, "points may not be null")).iterator());
126     }
127 
128     /**
129      * Construct a Bounds3d for a Drawable3d.
130      * @param drawable3d Drawable3d; any object that implements the Drawable2d interface
131      * @throws NullPointerException when area is null
132      */
133     public Bounds3d(final Drawable3d drawable3d) throws NullPointerException
134     {
135         this(Throw.whenNull(drawable3d, "drawable3d may not be null").getPoints());
136     }
137 
138     /**
139      * Construct a Bounds3d from a collection of Point3d, finding the lowest and highest x and y coordinates.
140      * @param points Collection&lt;Point3d&gt;; the collection of points to construct a Bounds2d from
141      * @throws NullPointerException when points is null
142      * @throws IllegalArgumentException when the collection is empty
143      */
144     public Bounds3d(final Collection<Point3d> points)
145     {
146         this(Throw.whenNull(points, "points may not be null").iterator());
147     }
148 
149     /** {@inheritDoc} */
150     @Override
151     public Iterator<Point3d> getPoints()
152     {
153         Point3d[] array =
154                 new PoPoint3da>Point3d.html#Point3d">Point3d[] {new PoPoint3da>(getMinX(), getMinY(), getMinZ()), new Point3d(getMinX(), getMinY(), getMaxZ()),
155                         new PoPoint3da>(getMinX(), getMaxY(), getMinZ()), new Point3d(getMinX(), getMaxY(), getMaxZ()),
156                         new PoPoint3da>(getMaxX(), getMinY(), getMinZ()), new Point3d(getMaxX(), getMinY(), getMaxZ()),
157                         new PoPoint3da>(getMaxX(), getMaxY(), getMinZ()), new Point3d(getMaxX(), getMaxY(), getMaxZ())};
158         return Arrays.stream(array).iterator();
159     }
160 
161     /** {@inheritDoc} */
162     @Override
163     public int size()
164     {
165         return 8;
166     }
167 
168     /**
169      * Check if the Bounds3d contains a point. Contains returns false when the point is on the surface of this Bounds3d.
170      * @param point Point3d; the point
171      * @return boolean; whether the bounding box contains the point
172      * @throws NullPointerException when point is null
173      */
174     public boolean contains(final Point3d point)
175     {
176         Throw.whenNull(point, "point cannot be null");
177         return contains(point.x, point.y, point.z);
178     }
179 
180     /**
181      * Check if this Bounds3d contains a point. Contains returns false when the point is on the surface of this Bounds3d.
182      * @param x double; the x-coordinate of the point
183      * @param y double; the y-coordinate of the point
184      * @param z double; the z-coordinate of the point
185      * @return boolean; whether this Bounds3d contains the point
186      * @throws IllegalArgumentException when any of the coordinates is NaN
187      */
188     public boolean contains(final double x, final double y, final double z) throws IllegalArgumentException
189     {
190         Throw.when(Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z), IllegalArgumentException.class,
191                 "coordinates must be numbers (not NaN)");
192         return x > this.minX && x < this.maxX && y > this.minY && y < this.maxY && z > this.minZ && z < this.maxZ;
193     }
194 
195     /**
196      * Check if the Bounds3d contains another Bounds3d. Contains returns false when one of the faces of the other Bounds3d is
197      * overlapping with the face of this Bounds3d.
198      * @param drawable Drawable3d; the Bounds3d for which to check if it is completely contained within this Bounds3d
199      * @return boolean; whether the bounding box contains the provided bounding box
200      * @throws NullPointerException when otherBounds3d is null
201      */
202     public boolean contains(final Drawable3d drawable)
203     {
204         Throw.whenNull(drawable, "drawable cannot be null");
205         for (Iterator<? extends Point3d> iterator = drawable.getPoints(); iterator.hasNext();)
206         {
207             if (!contains(iterator.next()))
208             {
209                 return false;
210             }
211         }
212         return true;
213     }
214 
215     /** {@inheritDoc} */
216     @Override
217     public Bounds2d project()
218     {
219         return new Bounds2d(getMinX(), getMaxX(), getMinY(), getMaxY());
220     }
221 
222     /**
223      * Check if this Bounds3d contains a point. Covers returns true when the point is on a face of this Bounds3d.
224      * @param x double; the x-coordinate of the point
225      * @param y double; the y-coordinate of the point
226      * @param z double; the z-coordinate of the point
227      * @return boolean; whether the bounding box contains the point, including the faces
228      * @throws IllegalArgumentException when any of the coordinates is NaN
229      */
230     public boolean covers(final double x, final double y, final double z) throws IllegalArgumentException
231     {
232         Throw.when(Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z), IllegalArgumentException.class,
233                 "coordinates must be numbers (not NaN)");
234         return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY && z >= this.minZ && z <= this.maxZ;
235     }
236 
237     /**
238      * Check if this Bounds3d contains a point. Covers returns true when the point is on a face of this Bounds3d.
239      * @param point Point3d; the point
240      * @return boolean; whether the bounding box contains the point, including the faces
241      * @throws NullPointerException when point is null
242      */
243     public boolean covers(final Point3d point)
244     {
245         Throw.whenNull(point, "point cannot be null");
246         return covers(point.x, point.y, point.z);
247     }
248 
249     /** {@inheritDoc} */
250     @Override
251     public boolean covers(final Bounds3d otherBounds3d)
252     {
253         Throw.whenNull(otherBounds3d, "otherBounds3d cannot be null");
254         return covers(otherBounds3d.getMinX(), otherBounds3d.getMinY(), otherBounds3d.getMinZ())
255                 && covers(otherBounds3d.getMaxX(), otherBounds3d.getMaxY(), otherBounds3d.getMaxZ());
256     }
257 
258     /** {@inheritDoc} */
259     @Override
260     public boolean disjoint(final Bounds3d otherBounds3d)
261     {
262         Throw.whenNull(otherBounds3d, "otherBounds3d cannot be null");
263         return otherBounds3d.minX >= this.maxX || otherBounds3d.maxX <= this.minX || otherBounds3d.minY >= this.maxY
264                 || otherBounds3d.maxY <= this.minY || otherBounds3d.minZ >= this.maxZ || otherBounds3d.maxZ <= this.minZ;
265     }
266 
267     /** {@inheritDoc} */
268     @Override
269     public boolean intersects(final Bounds3d otherBounds3d)
270     {
271         return !disjoint(otherBounds3d);
272     }
273 
274     /** {@inheritDoc} */
275     @Override
276     public Bounds3dl#Bounds3d">Bounds3d intersection(final Bounds3d otherBounds3d)
277     {
278         Throw.whenNull(otherBounds3d, "otherBounds3d cannot be null");
279         if (disjoint(otherBounds3d))
280         {
281             return null;
282         }
283         return new Bounds3d(Math.max(this.minX, otherBounds3d.minX), Math.min(this.maxX, otherBounds3d.maxX),
284                 Math.max(this.minY, otherBounds3d.minY), Math.min(this.maxY, otherBounds3d.maxY),
285                 Math.max(this.minZ, otherBounds3d.minZ), Math.min(this.maxZ, otherBounds3d.maxZ));
286     }
287 
288     /**
289      * Return the extent of this Bounds3d in the z-direction.
290      * @return double; the extent of this Bounds3d in the z-direction
291      */
292     public double getDeltaZ()
293     {
294         return getMaxZ() - getMinZ();
295     }
296 
297     /**
298      * Return the volume of this Bounds3d.
299      * @return double; the volume of this Bounds3d
300      */
301     public double getVolume()
302     {
303         return getDeltaX() * getDeltaY() * getDeltaZ();
304     }
305 
306     /** {@inheritDoc} */
307     @Override
308     public double getMinX()
309     {
310         return this.minX;
311     }
312 
313     /** {@inheritDoc} */
314     @Override
315     public double getMaxX()
316     {
317         return this.maxX;
318     }
319 
320     /** {@inheritDoc} */
321     @Override
322     public double getMinY()
323     {
324         return this.minY;
325     }
326 
327     /** {@inheritDoc} */
328     @Override
329     public double getMaxY()
330     {
331         return this.maxY;
332     }
333 
334     /**
335      * Return the lower bound for z.
336      * @return double; the lower bound for z
337      */
338     public double getMinZ()
339     {
340         return this.minZ;
341     }
342 
343     /**
344      * Return the upper bound for z.
345      * @return double; the upper bound for z
346      */
347     public double getMaxZ()
348     {
349         return this.maxZ;
350     }
351 
352     /**
353      * Return the mid point of this Bounds3d.
354      * @return Point3d; the mid point of this Bounds3d
355      */
356     public Point3d midPoint()
357     {
358         return new Point3d((this.minX + this.maxX) / 2, (this.minY + this.maxY) / 2, (this.minZ + this.maxZ) / 2);
359     }
360 
361     /** {@inheritDoc} */
362     @Override
363     public Bounds3d getBounds()
364     {
365         return this;
366     }
367 
368     /** {@inheritDoc} */
369     @Override
370     public String toString()
371     {
372         return "Bounds3d [x[" + this.minX + " : " + this.maxX + "], y[" + this.minY + " : " + this.maxY + "], z[" + this.minZ
373                 + " : " + this.maxZ + "]]";
374     }
375 
376     /** {@inheritDoc} */
377     @Override
378     public int hashCode()
379     {
380         final int prime = 31;
381         int result = 1;
382         long temp;
383         temp = Double.doubleToLongBits(this.maxX);
384         result = prime * result + (int) (temp ^ (temp >>> 32));
385         temp = Double.doubleToLongBits(this.maxY);
386         result = prime * result + (int) (temp ^ (temp >>> 32));
387         temp = Double.doubleToLongBits(this.maxZ);
388         result = prime * result + (int) (temp ^ (temp >>> 32));
389         temp = Double.doubleToLongBits(this.minX);
390         result = prime * result + (int) (temp ^ (temp >>> 32));
391         temp = Double.doubleToLongBits(this.minY);
392         result = prime * result + (int) (temp ^ (temp >>> 32));
393         temp = Double.doubleToLongBits(this.minZ);
394         result = prime * result + (int) (temp ^ (temp >>> 32));
395         return result;
396     }
397 
398     /** {@inheritDoc} */
399     @SuppressWarnings("checkstyle:needbraces")
400     @Override
401     public boolean equals(final Object obj)
402     {
403         if (this == obj)
404             return true;
405         if (obj == null)
406             return false;
407         if (getClass() != obj.getClass())
408             return false;
409         Bounds3d./../../../org/djutils/draw/bounds/Bounds3d.html#Bounds3d">Bounds3d other = (Bounds3d) obj;
410         if (Double.doubleToLongBits(this.maxX) != Double.doubleToLongBits(other.maxX))
411             return false;
412         if (Double.doubleToLongBits(this.maxY) != Double.doubleToLongBits(other.maxY))
413             return false;
414         if (Double.doubleToLongBits(this.maxZ) != Double.doubleToLongBits(other.maxZ))
415             return false;
416         if (Double.doubleToLongBits(this.minX) != Double.doubleToLongBits(other.minX))
417             return false;
418         if (Double.doubleToLongBits(this.minY) != Double.doubleToLongBits(other.minY))
419             return false;
420         if (Double.doubleToLongBits(this.minZ) != Double.doubleToLongBits(other.minZ))
421             return false;
422         return true;
423     }
424 
425 }