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