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 import java.util.Locale;
8
9 import org.djutils.draw.Drawable3d;
10 import org.djutils.draw.Space3d;
11 import org.djutils.draw.point.Point3d;
12 import org.djutils.exceptions.Throw;
13
14
15
16
17
18
19
20
21
22
23 public class Bounds3d implements Serializable, Drawable3d, Bounds<Bounds3d, Point3d, Drawable3d, Space3d>
24 {
25
26 private static final long serialVersionUID = 2020829L;
27
28
29 private final double minX;
30
31
32 private final double minY;
33
34
35 private final double minZ;
36
37
38 private final double maxX;
39
40
41 private final double maxY;
42
43
44 private final double maxZ;
45
46
47
48
49
50
51
52
53
54
55
56 public Bounds3d(final double minX, final double maxX, final double minY, final double maxY, final double minZ,
57 final double maxZ)
58 {
59 Throw.when(Double.isNaN(minX) || Double.isNaN(maxX) || Double.isNaN(minY) || Double.isNaN(maxY) || Double.isNaN(minZ)
60 || Double.isNaN(maxZ), IllegalArgumentException.class, "Nan boundary value not permitted");
61 Throw.when(minX > maxX || minY > maxY || minZ > maxZ, IllegalArgumentException.class,
62 "lower bound for each dimension should be less than or equal to its upper bound");
63 this.minX = minX;
64 this.minY = minY;
65 this.minZ = minZ;
66 this.maxX = maxX;
67 this.maxY = maxY;
68 this.maxZ = maxZ;
69 }
70
71
72
73
74
75
76
77
78 public Bounds3d(final double deltaX, final double deltaY, final double deltaZ)
79 {
80 this(-0.5 * deltaX, 0.5 * deltaX, -0.5 * deltaY, 0.5 * deltaY, -0.5 * deltaZ, 0.5 * deltaZ);
81 }
82
83
84
85
86
87
88
89 public Bounds3d(final Iterator<? extends Point3d> points)
90 {
91 Throw.whenNull(points, "points may not be null");
92 Throw.when(!points.hasNext(), IllegalArgumentException.class, "need at least one point");
93 Point3d point = points.next();
94 double tempMinX = point.x;
95 double tempMaxX = point.x;
96 double tempMinY = point.y;
97 double tempMaxY = point.y;
98 double tempMinZ = point.z;
99 double tempMaxZ = point.z;
100 while (points.hasNext())
101 {
102 point = points.next();
103 tempMinX = Math.min(tempMinX, point.x);
104 tempMaxX = Math.max(tempMaxX, point.x);
105 tempMinY = Math.min(tempMinY, point.y);
106 tempMaxY = Math.max(tempMaxY, point.y);
107 tempMinZ = Math.min(tempMinZ, point.z);
108 tempMaxZ = Math.max(tempMaxZ, point.z);
109 }
110 this.minX = tempMinX;
111 this.maxX = tempMaxX;
112 this.minY = tempMinY;
113 this.maxY = tempMaxY;
114 this.minZ = tempMinZ;
115 this.maxZ = tempMaxZ;
116 }
117
118
119
120
121
122
123
124 public Bounds3d(final Point3d[] points) throws NullPointerException, IllegalArgumentException
125 {
126 this(Arrays.stream(Throw.whenNull(points, "points may not be null")).iterator());
127 }
128
129
130
131
132
133
134 public Bounds3d(final Drawable3d drawable3d) throws NullPointerException
135 {
136 this(Throw.whenNull(drawable3d, "drawable3d may not be null").getPoints());
137 }
138
139
140
141
142
143
144
145 public Bounds3d(final Drawable3d... drawable3d) throws NullPointerException, IllegalArgumentException
146 {
147 this(new Iterator<Point3d>()
148 {
149
150 private int nextArgument = 0;
151
152
153 private Iterator<? extends Point3d> currentIterator = ensureHasOne(drawable3d)[0].getPoints();
154
155 @Override
156 public boolean hasNext()
157 {
158 return this.nextArgument < drawable3d.length - 1 || this.currentIterator.hasNext();
159 }
160
161 @Override
162 public Point3d next()
163 {
164 if (this.currentIterator.hasNext())
165 {
166 return this.currentIterator.next();
167 }
168
169 this.nextArgument++;
170 this.currentIterator = drawable3d[this.nextArgument].getPoints();
171 return this.currentIterator.next();
172 }
173 });
174 }
175
176
177
178
179
180
181
182
183 static Drawable3de3d">Drawable3d[] ensureHasOne(final Drawable3d[] drawable3dArray) throws NullPointerException, IllegalArgumentException
184 {
185 Throw.whenNull(drawable3dArray, "Array may not be null");
186 Throw.when(drawable3dArray.length == 0, IllegalArgumentException.class, "Array must contain at least one value");
187 return drawable3dArray;
188 }
189
190
191
192
193
194
195
196 public Bounds3d(final Collection<Drawable3d> drawableCollection) throws NullPointerException, IllegalArgumentException
197 {
198 this(new Iterator<Point3d>()
199 {
200
201 private Iterator<Drawable3d> collectionIterator = ensureHasOne(drawableCollection.iterator());
202
203
204 private Iterator<? extends Point3d> currentIterator = this.collectionIterator.next().getPoints();
205
206 @Override
207 public boolean hasNext()
208 {
209 if (this.currentIterator == null)
210 {
211 return false;
212 }
213 return this.currentIterator.hasNext();
214 }
215
216 @Override
217 public Point3d next()
218 {
219 Point3d result = this.currentIterator.next();
220 if (!this.currentIterator.hasNext())
221 {
222 if (this.collectionIterator.hasNext())
223 {
224 this.currentIterator = this.collectionIterator.next().getPoints();
225 }
226 else
227 {
228 this.currentIterator = null;
229 }
230 }
231 return result;
232 }
233 });
234 }
235
236
237
238
239
240
241
242
243 static Iterator<Drawable3d> ensureHasOne(final Iterator<Drawable3d> iterator)
244 throws NullPointerException, IllegalArgumentException
245 {
246 Throw.when(!iterator.hasNext(), IllegalArgumentException.class, "Collection may not be empty");
247 return iterator;
248 }
249
250
251 @Override
252 public Iterator<Point3d> getPoints()
253 {
254 Point3d[] array =
255 new Point3doint3d.html#Point3d">Point3d[] { new Point3d(Point3dord">this.minX, this.minY, Point3ds="jxr_keyword">this.minZ), new Point3d(this.minX, this.minY, this.maxZ),
256 new Point3d(this.minX, this.maxY, Point3ds="jxr_keyword">this.minZ), new Point3d(this.minX, this.maxY, this.maxZ),
257 new Point3d(this.maxX, this.minY, Point3ds="jxr_keyword">this.minZ), new Point3d(this.maxX, this.minY, this.maxZ),
258 new Point3d(this.maxX, this.maxY, Point3ds="jxr_keyword">this.minZ), new Point3d(this.maxX, this.maxY, this.maxZ) };
259 return Arrays.stream(array).iterator();
260 }
261
262
263 @Override
264 public int size()
265 {
266 return 8;
267 }
268
269
270
271
272
273
274
275
276
277 public boolean contains(final double x, final double y, final double z) throws IllegalArgumentException
278 {
279 Throw.when(Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z), IllegalArgumentException.class,
280 "coordinates must be numbers (not NaN)");
281 return x > this.minX && x < this.maxX && y > this.minY && y < this.maxY && z > this.minZ && z < this.maxZ;
282 }
283
284
285 @Override
286 public boolean contains(final Point3d point)
287 {
288 Throw.whenNull(point, "point cannot be null");
289 return contains(point.x, point.y, point.z);
290 }
291
292
293 @Override
294 public boolean contains(final Drawable3d drawable) throws NullPointerException
295 {
296 Throw.whenNull(drawable, "drawable cannot be null");
297 Bounds3d bounds = drawable.getBounds();
298 return contains(bounds.minX, bounds.minY, bounds.minZ) && contains(bounds.maxX, bounds.maxY, bounds.maxZ);
299 }
300
301
302
303
304
305
306
307
308
309 public boolean covers(final double x, final double y, final double z) throws IllegalArgumentException
310 {
311 Throw.when(Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z), IllegalArgumentException.class,
312 "coordinates must be numbers (not NaN)");
313 return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY && z >= this.minZ && z <= this.maxZ;
314 }
315
316
317 @Override
318 public boolean covers(final Point3d point)
319 {
320 Throw.whenNull(point, "point cannot be null");
321 return covers(point.x, point.y, point.z);
322 }
323
324
325 @Override
326 public boolean covers(final Drawable3d drawable)
327 {
328 Throw.whenNull(drawable, "drawable cannot be null");
329 Bounds3d bounds = drawable.getBounds();
330 return covers(bounds.minX, bounds.minY, bounds.minZ) && covers(bounds.maxX, bounds.maxY, bounds.maxZ);
331 }
332
333
334 @Override
335 public boolean disjoint(final Drawable3d drawable)
336 {
337 Throw.whenNull(drawable, "drawable cannot be null");
338 Bounds3d bounds = drawable.getBounds();
339 return bounds.minX >= this.maxX || bounds.maxX <= this.minX || bounds.minY >= this.maxY || bounds.maxY <= this.minY
340 || bounds.minZ >= this.maxZ || bounds.maxZ <= this.minZ;
341 }
342
343
344 @Override
345 public boolean intersects(final Bounds3d otherBounds3d)
346 {
347 return !disjoint(otherBounds3d);
348 }
349
350
351 @Override
352 public Bounds3dl#Bounds3d">Bounds3d intersection(final Bounds3d otherBounds3d)
353 {
354 Throw.whenNull(otherBounds3d, "otherBounds3d cannot be null");
355 if (disjoint(otherBounds3d))
356 {
357 return null;
358 }
359 return new Bounds3d(Math.max(this.minX, otherBounds3d.minX), Math.min(this.maxX, otherBounds3d.maxX),
360 Math.max(this.minY, otherBounds3d.minY), Math.min(this.maxY, otherBounds3d.maxY),
361 Math.max(this.minZ, otherBounds3d.minZ), Math.min(this.maxZ, otherBounds3d.maxZ));
362 }
363
364
365 @Override
366 public Bounds2d project()
367 {
368 return new Bounds2d(this.minX, this.maxX, this.minY, this.maxY);
369 }
370
371
372
373
374
375 public double getDeltaZ()
376 {
377 return getMaxZ() - getMinZ();
378 }
379
380
381
382
383
384 public double getVolume()
385 {
386 return getDeltaX() * getDeltaY() * getDeltaZ();
387 }
388
389
390 @Override
391 public double getMinX()
392 {
393 return this.minX;
394 }
395
396
397 @Override
398 public double getMaxX()
399 {
400 return this.maxX;
401 }
402
403
404 @Override
405 public double getMinY()
406 {
407 return this.minY;
408 }
409
410
411 @Override
412 public double getMaxY()
413 {
414 return this.maxY;
415 }
416
417
418
419
420
421 public double getMinZ()
422 {
423 return this.minZ;
424 }
425
426
427
428
429
430 public double getMaxZ()
431 {
432 return this.maxZ;
433 }
434
435
436 @Override
437 public Point3d midPoint()
438 {
439 return new Point3d((this.minX + this.maxX) / 2, (this.minY + this.maxY) / 2, (this.minZ + this.maxZ) / 2);
440 }
441
442
443 @Override
444 public Bounds3d getBounds()
445 {
446 return this;
447 }
448
449
450 @Override
451 public String toString()
452 {
453 return toString("%f", false);
454 }
455
456
457 @Override
458 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
459 {
460 String format = String.format("%1$s[x[%2$s : %2$s], y[%2$s : %2$s, z[%2$s : %2$s]]",
461 doNotIncludeClassName ? "" : "Bounds3d ", doubleFormat);
462 return String.format(Locale.US, format, this.minX, this.maxX, this.minY, this.maxY, this.minZ, this.maxZ);
463 }
464
465
466 @Override
467 public int hashCode()
468 {
469 final int prime = 31;
470 int result = 1;
471 long temp;
472 temp = Double.doubleToLongBits(this.maxX);
473 result = prime * result + (int) (temp ^ (temp >>> 32));
474 temp = Double.doubleToLongBits(this.maxY);
475 result = prime * result + (int) (temp ^ (temp >>> 32));
476 temp = Double.doubleToLongBits(this.maxZ);
477 result = prime * result + (int) (temp ^ (temp >>> 32));
478 temp = Double.doubleToLongBits(this.minX);
479 result = prime * result + (int) (temp ^ (temp >>> 32));
480 temp = Double.doubleToLongBits(this.minY);
481 result = prime * result + (int) (temp ^ (temp >>> 32));
482 temp = Double.doubleToLongBits(this.minZ);
483 result = prime * result + (int) (temp ^ (temp >>> 32));
484 return result;
485 }
486
487
488 @SuppressWarnings("checkstyle:needbraces")
489 @Override
490 public boolean equals(final Object obj)
491 {
492 if (this == obj)
493 return true;
494 if (obj == null)
495 return false;
496 if (getClass() != obj.getClass())
497 return false;
498 Bounds3d./../../../org/djutils/draw/bounds/Bounds3d.html#Bounds3d">Bounds3d other = (Bounds3d) obj;
499 if (Double.doubleToLongBits(this.maxX) != Double.doubleToLongBits(other.maxX))
500 return false;
501 if (Double.doubleToLongBits(this.maxY) != Double.doubleToLongBits(other.maxY))
502 return false;
503 if (Double.doubleToLongBits(this.maxZ) != Double.doubleToLongBits(other.maxZ))
504 return false;
505 if (Double.doubleToLongBits(this.minX) != Double.doubleToLongBits(other.minX))
506 return false;
507 if (Double.doubleToLongBits(this.minY) != Double.doubleToLongBits(other.minY))
508 return false;
509 if (Double.doubleToLongBits(this.minZ) != Double.doubleToLongBits(other.minZ))
510 return false;
511 return true;
512 }
513
514 }