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>
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");
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")).iterator());
126 }
127
128
129
130
131
132
133 public Bounds3d(final Drawable3d drawable3d) throws NullPointerException
134 {
135 this(Throw.whenNull(drawable3d, "drawable3d").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, "drawable3dArray");
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 @Override
275 public Iterator<Point3d> getPoints()
276 {
277 Point3d[] array =
278 new Point3d[] {new Point3d(this.minX, this.minY, this.minZ), new Point3d(this.minX, this.minY, this.maxZ),
279 new Point3d(this.minX, this.maxY, this.minZ), new Point3d(this.minX, this.maxY, this.maxZ),
280 new Point3d(this.maxX, this.minY, this.minZ), new Point3d(this.maxX, this.minY, this.maxZ),
281 new Point3d(this.maxX, this.maxY, this.minZ), new Point3d(this.maxX, this.maxY, this.maxZ)};
282 return Arrays.stream(array).iterator();
283 }
284
285 @Override
286 public int size()
287 {
288 return 8;
289 }
290
291
292
293
294
295
296
297
298
299 public boolean contains(final double x, final double y, final double z) throws IllegalArgumentException
300 {
301 Throw.when(Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z), IllegalArgumentException.class,
302 "coordinates must be numbers (not NaN)");
303 return x > this.minX && x < this.maxX && y > this.minY && y < this.maxY && z > this.minZ && z < this.maxZ;
304 }
305
306 @Override
307 public boolean contains(final Point3d point)
308 {
309 Throw.whenNull(point, "point");
310 return contains(point.x, point.y, point.z);
311 }
312
313 @Override
314 public boolean contains(final Bounds3d otherBounds) throws NullPointerException
315 {
316 Throw.whenNull(otherBounds, "otherBounds");
317 return contains(otherBounds.minX, otherBounds.minY, otherBounds.minZ)
318 && contains(otherBounds.maxX, otherBounds.maxY, otherBounds.maxZ);
319 }
320
321
322
323
324
325
326
327
328
329 public boolean covers(final double x, final double y, final double z) throws IllegalArgumentException
330 {
331 Throw.when(Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z), IllegalArgumentException.class,
332 "coordinates must be numbers (not NaN)");
333 return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY && z >= this.minZ && z <= this.maxZ;
334 }
335
336 @Override
337 public boolean covers(final Point3d point)
338 {
339 Throw.whenNull(point, "point");
340 return covers(point.x, point.y, point.z);
341 }
342
343 @Override
344 public boolean covers(final Bounds3d otherBounds)
345 {
346 Throw.whenNull(otherBounds, "otherBounds");
347 return covers(otherBounds.minX, otherBounds.minY, otherBounds.minZ)
348 && covers(otherBounds.maxX, otherBounds.maxY, otherBounds.maxZ);
349 }
350
351 @Override
352 public boolean disjoint(final Bounds3d otherBounds)
353 {
354 Throw.whenNull(otherBounds, "otherBounds");
355 return otherBounds.minX > this.maxX || otherBounds.maxX < this.minX || otherBounds.minY > this.maxY
356 || otherBounds.maxY < this.minY || otherBounds.minZ > this.maxZ || otherBounds.maxZ < this.minZ;
357 }
358
359 @Override
360 public boolean intersects(final Bounds3d otherBounds3d)
361 {
362 return !disjoint(otherBounds3d);
363 }
364
365 @Override
366 public Bounds3d intersection(final Bounds3d otherBounds3d)
367 {
368 Throw.whenNull(otherBounds3d, "otherBounds3d");
369 if (disjoint(otherBounds3d))
370 {
371 return null;
372 }
373 return new Bounds3d(Math.max(this.minX, otherBounds3d.minX), Math.min(this.maxX, otherBounds3d.maxX),
374 Math.max(this.minY, otherBounds3d.minY), Math.min(this.maxY, otherBounds3d.maxY),
375 Math.max(this.minZ, otherBounds3d.minZ), Math.min(this.maxZ, otherBounds3d.maxZ));
376 }
377
378 @Override
379 public Bounds2d project()
380 {
381 return new Bounds2d(this.minX, this.maxX, this.minY, this.maxY);
382 }
383
384
385
386
387
388 public double getDeltaZ()
389 {
390 return getMaxZ() - getMinZ();
391 }
392
393
394
395
396
397 public double getVolume()
398 {
399 return getDeltaX() * getDeltaY() * getDeltaZ();
400 }
401
402 @Override
403 public double getMinX()
404 {
405 return this.minX;
406 }
407
408 @Override
409 public double getMaxX()
410 {
411 return this.maxX;
412 }
413
414 @Override
415 public double getMinY()
416 {
417 return this.minY;
418 }
419
420 @Override
421 public double getMaxY()
422 {
423 return this.maxY;
424 }
425
426
427
428
429
430 public double getMinZ()
431 {
432 return this.minZ;
433 }
434
435
436
437
438
439 public double getMaxZ()
440 {
441 return this.maxZ;
442 }
443
444 @Override
445 public Point3d midPoint()
446 {
447 return new Point3d((this.minX + this.maxX) / 2, (this.minY + this.maxY) / 2, (this.minZ + this.maxZ) / 2);
448 }
449
450 @Override
451 public Bounds3d getBounds()
452 {
453 return this;
454 }
455
456 @Override
457 public String toString()
458 {
459 return toString("%f", false);
460 }
461
462 @Override
463 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
464 {
465 String format = String.format("%1$s[x[%2$s : %2$s], y[%2$s : %2$s, z[%2$s : %2$s]]",
466 doNotIncludeClassName ? "" : "Bounds3d ", doubleFormat);
467 return String.format(Locale.US, format, this.minX, this.maxX, this.minY, this.maxY, this.minZ, this.maxZ);
468 }
469
470 @Override
471 public int hashCode()
472 {
473 final int prime = 31;
474 int result = 1;
475 long temp;
476 temp = Double.doubleToLongBits(this.maxX);
477 result = prime * result + (int) (temp ^ (temp >>> 32));
478 temp = Double.doubleToLongBits(this.maxY);
479 result = prime * result + (int) (temp ^ (temp >>> 32));
480 temp = Double.doubleToLongBits(this.maxZ);
481 result = prime * result + (int) (temp ^ (temp >>> 32));
482 temp = Double.doubleToLongBits(this.minX);
483 result = prime * result + (int) (temp ^ (temp >>> 32));
484 temp = Double.doubleToLongBits(this.minY);
485 result = prime * result + (int) (temp ^ (temp >>> 32));
486 temp = Double.doubleToLongBits(this.minZ);
487 result = prime * result + (int) (temp ^ (temp >>> 32));
488 return result;
489 }
490
491 @SuppressWarnings("checkstyle:needbraces")
492 @Override
493 public boolean equals(final Object obj)
494 {
495 if (this == obj)
496 return true;
497 if (obj == null)
498 return false;
499 if (getClass() != obj.getClass())
500 return false;
501 Bounds3d other = (Bounds3d) obj;
502 if (Double.doubleToLongBits(this.maxX) != Double.doubleToLongBits(other.maxX))
503 return false;
504 if (Double.doubleToLongBits(this.maxY) != Double.doubleToLongBits(other.maxY))
505 return false;
506 if (Double.doubleToLongBits(this.maxZ) != Double.doubleToLongBits(other.maxZ))
507 return false;
508 if (Double.doubleToLongBits(this.minX) != Double.doubleToLongBits(other.minX))
509 return false;
510 if (Double.doubleToLongBits(this.minY) != Double.doubleToLongBits(other.minY))
511 return false;
512 if (Double.doubleToLongBits(this.minZ) != Double.doubleToLongBits(other.minZ))
513 return false;
514 return true;
515 }
516
517 }