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