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