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 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 Point3d[] { new Point3d(this.minX, this.minY, this.minZ), new Point3d(this.minX, this.minY, this.maxZ),
280 new Point3d(this.minX, this.maxY, this.minZ), new Point3d(this.minX, this.maxY, this.maxZ),
281 new Point3d(this.maxX, this.minY, this.minZ), new Point3d(this.maxX, this.minY, this.maxZ),
282 new Point3d(this.maxX, this.maxY, 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 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 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 }