1 // Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and
2 // other Axom Project Developers. See the top-level LICENSE file for details.
3 //
4 // SPDX-License-Identifier: (BSD-3-Clause)
5
6 #ifndef AXOM_PRIMAL_BOUNDINGBOX_HPP_
7 #define AXOM_PRIMAL_BOUNDINGBOX_HPP_
8
9 #include <limits>
10
11 #include "axom/config.hpp"
12
13 #include "axom/core/Macros.hpp" // for AXOM_HOST__DEVICE
14 #include "axom/core/numerics/floating_point_limits.hpp"
15
16 #include "axom/primal/geometry/Point.hpp"
17 #include "axom/primal/geometry/Vector.hpp"
18
19 #include "axom/primal/operators/detail/intersect_bounding_box_impl.hpp"
20
21 namespace axom
22 {
23 namespace primal
24 {
25 // Forward declare the templated classes and operator functions
26 template <typename T, int NDIMS>
27 class BoundingBox;
28
29 /// \name Forward Declared Overloaded Operators
30 ///@{
31
32 /*!
33 * \brief Equality comparison operator for bounding boxes.
34 * Two bounding boxes are equal when they have the same bounds
35 */
36 template <typename T, int NDIMS>
37 AXOM_HOST_DEVICE bool operator==(const BoundingBox<T, NDIMS>& lhs,
38 const BoundingBox<T, NDIMS>& rhs);
39
40 /*!
41 * \brief Inequality comparison operator for bounding boxes.
42 * Two bounding boxes are unequal when they have different bounds
43 */
44 template <typename T, int NDIMS>
45 bool operator!=(const BoundingBox<T, NDIMS>& lhs,
46 const BoundingBox<T, NDIMS>& rhs);
47
48 /*!
49 * \brief Overloaded output operator for bounding boxes
50 */
51 template <typename T, int NDIMS>
52 std::ostream& operator<<(std::ostream& os, const BoundingBox<T, NDIMS>& bb);
53
54 ///@}
55
56 /*!
57 * \accelerated
58 * \class
59 *
60 * \brief BoundingBox represents and axis-aligned bounding box defined by
61 * its min and max coordinates.
62 *
63 * \tparam T the coordinate type, e.g., double, float, etc.
64 * \tparam NDIMS the number of dimensions
65 */
66 template <typename T, int NDIMS>
67 class BoundingBox
68 {
69 public:
70 typedef T CoordType;
71 typedef Point<T, NDIMS> PointType;
72 typedef Vector<T, NDIMS> VectorType;
73 typedef BoundingBox<T, NDIMS> BoxType;
74
75 static constexpr T InvalidMin = std::numeric_limits<T>::max();
76 static constexpr T InvalidMax = std::numeric_limits<T>::lowest();
77
78 public:
79 /*!
80 * \brief Constructor. Creates a bounding box with an invalid bound
81 * The lower bound is set to the greatest possible point and the upper bound
82 * is set to the smallest possible point. This way adding any point resets
83 * the bounds to a valid range.
84 */
85 AXOM_HOST_DEVICE
BoundingBox()86 BoundingBox() : m_min(PointType(InvalidMin)), m_max(PointType(InvalidMax)) { }
87
88 /*!
89 * \brief Constructor. Creates a bounding box containing a single point
90 */
91 AXOM_HOST_DEVICE
BoundingBox(const PointType & pt)92 explicit BoundingBox(const PointType& pt) : m_min(pt), m_max(pt) { }
93
94 /*!
95 * \brief Constructor. Creates a bounding box containing the collection of
96 * points.
97 * \pre pt must point to at least n valid point
98 * \note If n <= 0, defaults to default constructor values
99 */
100 AXOM_HOST_DEVICE
101 BoundingBox(const PointType* pts, int n);
102
103 /*!
104 * \brief Constructor. Creates a bounding box with a given min and max point
105 * The code ensures that the bounds are valid.
106 */
107 AXOM_HOST_DEVICE
BoundingBox(const PointType & lowerPt,const PointType & upperPt)108 BoundingBox(const PointType& lowerPt, const PointType& upperPt)
109 : m_min(lowerPt)
110 , m_max(upperPt)
111 {
112 this->checkAndFixBounds();
113 }
114
115 /*!
116 * \brief Resets the bounds to those of the default constructor
117 * \note This invalidates the bounding box (i.e. isValid() will be false)
118 */
119 AXOM_HOST_DEVICE
120 void clear();
121
122 /*!
123 * \brief Returns const reference to the min corner of the bounding box.
124 * \return const reference to the min corner of the bounding box.
125 */
126 AXOM_HOST_DEVICE
getMin() const127 const PointType& getMin() const { return m_min; };
128
129 /*!
130 * \brief Returns const reference to the max corner of the bounding box.
131 * \return const reference to the max corner of the bounding box.
132 */
133 AXOM_HOST_DEVICE
getMax() const134 const PointType& getMax() const { return m_max; };
135
136 /*!
137 * \brief Returns the centroid (midpoint) of the bounding box.
138 * \return Point at the bounding box centroid.
139 */
140 AXOM_HOST_DEVICE
getCentroid() const141 PointType getCentroid() const { return PointType::midpoint(m_min, m_max); }
142
143 /*!
144 * \brief Returns a vector from the min to the max points of the bounding box
145 * \return Vector from min point to max point of bounding box.
146 */
147 AXOM_HOST_DEVICE
range() const148 VectorType range() const { return VectorType(m_min, m_max); };
149
150 /*!
151 * \brief Updates bounds to include the provided point.
152 * \param [in] pt to include.
153 */
154 template <typename OtherType>
155 AXOM_HOST_DEVICE void addPoint(const Point<OtherType, NDIMS>& pt);
156
157 /*!
158 * \brief Updates bounds to include the provided bounding box.
159 * Convenience function -- equivalent to adding the min and max point of bbox
160 * \param [in] bbox to include.
161 */
162 template <typename OtherType>
163 AXOM_HOST_DEVICE void addBox(const BoundingBox<OtherType, NDIMS>& bbox);
164
165 /*!
166 * \brief Returns the dimension of the ambient space for this bounding box.
167 * \return d the dimension of this bounding box instance.
168 * \post d >= 1.
169 */
170 AXOM_HOST_DEVICE
dimension() const171 int dimension() const { return NDIMS; };
172
173 /*!
174 * \brief Finds the longest dimension of the bounding box
175 * \return idx the index of the longest dimension.
176 * \post idx >= 0 < NDIMS
177 * \note In the case of ties, where the bounding box has more than one side
178 * with the same length, the code picks the first dimension as the longest
179 * dimension.
180 */
181 AXOM_HOST_DEVICE
182 int getLongestDimension() const;
183
184 /*!
185 * \brief Intersects the current bounding box with another bounding box
186 * \param [in] otherBox The other box to intersect
187 * \note If the intersection is empty, the bounding box will be cleared
188 * \return A reference to the bounding box after it has been intersected
189 */
190 BoundingBox& intersect(const BoundingBox& otherBox);
191
192 /*!
193 * \brief Expands the lower and upper bounds by the given amount.
194 * \param [in] expansionAmount an absolute amount to expand
195 * \note Moves min & max point expansionAmount away from the center.
196 * \note This function checks to ensure the bounding box is valid afterwards.
197 * \note If expansionAmount is negative, the bounding box will contract
198 * \return A reference to the bounding box after it has been expanded
199 */
200 AXOM_HOST_DEVICE
201 BoundingBox& expand(T expansionAmount);
202
203 /*!
204 * \brief Scales the bounding box about its center by a given amount.
205 * \param [in] scaleFactor the multiplicative factor by which to scale
206 * \note Checks to ensure that the bounding box is valid after inflation.
207 * \note If scaleFactor is less than 1, the bounding box will shrink.
208 * \note If scaleFactor is 0, the bounding box will shrink to its midpoint
209 * \note The sign of the shrinkFactor has no effect since we are shrinking
210 * towards the center, and we fix the bounds after shrinking
211 * \return A reference to the bounding box after it has been scaled
212 */
213 AXOM_HOST_DEVICE BoundingBox& scale(double scaleFactor);
214
215 /*!
216 * \brief Shifts the bounding box by a fixed displacement.
217 * \param [in] displacement the amount with which to move the bounding box
218 * \return A reference to the bounding box after it has been shifted
219 */
220 AXOM_HOST_DEVICE
221 BoundingBox& shift(const VectorType& displacement);
222
223 /*!
224 * \brief Checks whether the box contains the point
225 * \param [in] otherPt the point that we are checking
226 * \return status true if point inside the box, else false.
227 *
228 * \note This function assumes all intervals are closed
229 * (i.e. contain their boundaries). We may need to deal with open
230 * and half open boundaries in the future.
231 */
232 template <typename OtherType>
233 AXOM_HOST_DEVICE bool contains(const Point<OtherType, NDIMS>& otherPt) const;
234
235 /*!
236 * \brief Checks whether the box fully contains another bounding box
237 * \param [in] otherBB the bounding box that we are checking
238 * \return status true if bb is inside the box, else false.
239 * \note We are allowing the other bounding box to have a different coordinate
240 * type. This should work as long as the two Ts are comparable with
241 * operator<().
242 */
243 template <typename OtherType>
244 bool contains(const BoundingBox<OtherType, NDIMS>& otherBB) const;
245
246 /*!
247 * \param [in] otherBB the bounding box that we are checking.
248 * \return status true if bb intersects otherBB, else false.
249 * \note We are allowing the other bounding box to have a different coordinate
250 * type. This should work as long as the two Ts are comparable with
251 * operator<().
252 */
253 template <typename OtherType>
254 AXOM_HOST_DEVICE bool intersectsWith(
255 const BoundingBox<OtherType, NDIMS>& otherBB) const;
256
257 /*!
258 * \brief Checks that we have a valid bounding box.
259 * \note A bounding box is valid when the length of each dimension is greater
260 * than or equal to zero.
261 * \return status true if point inside the box, else false.
262 */
263 AXOM_HOST_DEVICE
264 bool isValid() const;
265
266 /*!
267 * \brief Subdivides this bounding box instance into two sub-boxes by
268 * splitting along the given dimension. If a dimension is not provided, this
269 * method will split the bounding box along the longest dimension.
270 * \param [in,out] right the right sub-box.
271 * \param [in,out] left the left sub-box.
272 * \param [in] dimension the dimension to split along (optional)
273 * \pre dimension >= -1 && dimension < NDIMS
274 * \note if dimension==-1, the bounding box is split along its longest edge.
275 */
276 void bisect(BoxType& right, BoxType& left, int dimension = -1) const;
277
278 /*!
279 * \brief Simple formatted print of a bounding box instance
280 * \param os The output stream to write to
281 * \return A reference to the modified ostream
282 */
283 std::ostream& print(std::ostream& os) const;
284
285 /// \name Static methods
286 /// @{
287
288 /*!
289 * \brief Returns the list of points of a 2-D BoundingBox instance.
290 * \param [in] bb user-supplied instance of the bounding box.
291 * \param [out] pnts the list of points
292 * \post pnts.size() == 4
293 * \note The ordering of the points has as follows:
294 * \verbatim
295 *
296 * 3 2
297 * +------+
298 * | |
299 * | |
300 * +------+
301 * 0 1
302 *
303 * \endverbatim
304 */
305 static void getPoints(const BoundingBox<T, 2>& bb,
306 std::vector<Point<T, 2>>& pnts);
307
308 /*!
309 * \brief Returns the list of points of a 3-D BoundingBox instance.
310 * \param [in] bb user-supplied instance of the bounding box.
311 * \param [out] pnts the list of points
312 * \post pnts.size() == 8
313 * \note The ordering of the points has as follows:
314 * \verbatim
315 *
316 * 4 7
317 * +----------+
318 * /| /|
319 * 5 / | 6 / |
320 * +--|-------+ |
321 * | +-------|--+
322 * | / 0 | / 3
323 * |/ |/
324 * +----------+
325 * 1 2
326 *
327 * \endverbatim
328 */
329 static void getPoints(const BoundingBox<T, 3>& bb,
330 std::vector<Point<T, 3>>& pnts);
331
332 /// @}
333
334 private:
335 /*!
336 * \brief Sets the min point for this bounding box instance.
337 * \param [in] newMin the new min point.
338 */
setMin(const PointType & newMin)339 inline void setMin(const PointType& newMin) { m_min = newMin; };
340
341 /*!
342 * \brief Sets the max point for this bounding box instance.
343 * \param [in] newMax the new max point.
344 */
setMax(const PointType & newMax)345 inline void setMax(const PointType& newMax) { m_max = newMax; };
346
347 /*!
348 * \brief Ensures that the bounds are valid.
349 * A bounding box is valid when its extent in each dimension
350 * (max coordinate minus min coordinate) is greater than or equal to zero
351 */
352 AXOM_HOST_DEVICE void checkAndFixBounds();
353
354 private:
355 PointType m_min;
356 PointType m_max;
357 };
358
359 } /* namespace primal */
360
361 } /* namespace axom */
362
363 //------------------------------------------------------------------------------
364 // BoundingBox implementation
365 //------------------------------------------------------------------------------
366 namespace axom
367 {
368 namespace primal
369 {
370 //------------------------------------------------------------------------------
371
372 template <typename T, int NDIMS>
373 constexpr T BoundingBox<T, NDIMS>::InvalidMin;
374
375 template <typename T, int NDIMS>
376 constexpr T BoundingBox<T, NDIMS>::InvalidMax;
377
378 //------------------------------------------------------------------------------
379 template <typename T, int NDIMS>
380 template <typename OtherT>
contains(const Point<OtherT,NDIMS> & otherPt) const381 bool BoundingBox<T, NDIMS>::contains(const Point<OtherT, NDIMS>& otherPt) const
382 {
383 for(int dim = 0; dim < NDIMS; ++dim)
384 {
385 if(otherPt[dim] < m_min[dim] || otherPt[dim] > m_max[dim])
386 {
387 return false;
388 }
389 }
390
391 return true;
392 }
393
394 //------------------------------------------------------------------------------
395 template <typename T, int NDIMS>
BoundingBox(const PointType * pts,int n)396 BoundingBox<T, NDIMS>::BoundingBox(const PointType* pts, int n)
397 {
398 if(n <= 0)
399 {
400 clear();
401 }
402 // sanity check:
403 SLIC_ASSERT(pts != nullptr);
404
405 this->m_min = this->m_max = pts[0];
406
407 for(int i = 1; i < n; i++)
408 {
409 this->addPoint(pts[i]);
410 }
411 }
412
413 //------------------------------------------------------------------------------
414 template <typename T, int NDIMS>
415 template <typename OtherT>
contains(const BoundingBox<OtherT,NDIMS> & otherBB) const416 bool BoundingBox<T, NDIMS>::contains(const BoundingBox<OtherT, NDIMS>& otherBB) const
417 {
418 return this->contains(otherBB.getMin()) && this->contains(otherBB.getMax());
419 }
420
421 //------------------------------------------------------------------------------
422 template <typename T, int NDIMS>
423 template <typename OtherType>
intersectsWith(const BoundingBox<OtherType,NDIMS> & otherBB) const424 bool BoundingBox<T, NDIMS>::intersectsWith(
425 const BoundingBox<OtherType, NDIMS>& otherBB) const
426 {
427 bool status = true;
428
429 // AABBs cannot intersect if they are separated along any dimension
430 for(int i = 0; i < NDIMS; ++i)
431 {
432 status &= detail::intersect_bbox_bbox(m_min[i],
433 m_max[i],
434 otherBB.m_min[i],
435 otherBB.m_max[i]);
436 } // END for all dimensions
437
438 return status;
439 }
440
441 //------------------------------------------------------------------------------
442 template <typename T, int NDIMS>
isValid() const443 bool BoundingBox<T, NDIMS>::isValid() const
444 {
445 for(int dim = 0; dim < NDIMS; ++dim)
446 {
447 if(m_min[dim] > m_max[dim])
448 {
449 return false;
450 }
451 }
452 return true;
453 }
454
455 //------------------------------------------------------------------------------
456 template <typename T, int NDIMS>
457 template <typename OtherT>
addPoint(const Point<OtherT,NDIMS> & pt)458 void BoundingBox<T, NDIMS>::addPoint(const Point<OtherT, NDIMS>& pt)
459 {
460 for(int dim = 0; dim < NDIMS; ++dim)
461 {
462 T coord = static_cast<T>(pt[dim]);
463
464 if(coord < m_min[dim])
465 {
466 m_min[dim] = coord;
467 }
468
469 if(coord > m_max[dim])
470 {
471 m_max[dim] = coord;
472 }
473 }
474 }
475
476 //------------------------------------------------------------------------------
477 template <typename T, int NDIMS>
478 template <typename OtherT>
addBox(const BoundingBox<OtherT,NDIMS> & bbox)479 void BoundingBox<T, NDIMS>::addBox(const BoundingBox<OtherT, NDIMS>& bbox)
480 {
481 this->addPoint(bbox.getMin());
482 this->addPoint(bbox.getMax());
483 }
484
485 //------------------------------------------------------------------------------
486 template <typename T, int NDIMS>
getLongestDimension() const487 int BoundingBox<T, NDIMS>::getLongestDimension() const
488 {
489 SLIC_ASSERT(this->isValid());
490
491 int maxDim = 0;
492 T max = axom::numerics::floating_point_limits<T>::min();
493 for(int i = 0; i < NDIMS; ++i)
494 {
495 T dx = m_max[i] - m_min[i];
496 if(dx > max)
497 {
498 max = dx;
499 maxDim = i;
500 }
501
502 } // END for all dimensions
503
504 return maxDim;
505 }
506
507 //------------------------------------------------------------------------------
508 template <typename T, int NDIMS>
expand(T expansionAmount)509 BoundingBox<T, NDIMS>& BoundingBox<T, NDIMS>::expand(T expansionAmount)
510 {
511 for(int dim = 0; dim < NDIMS; ++dim)
512 {
513 m_min[dim] -= expansionAmount;
514 m_max[dim] += expansionAmount;
515 }
516
517 this->checkAndFixBounds();
518 return *this;
519 }
520
521 //------------------------------------------------------------------------------
522 template <typename T, int NDIMS>
scale(double scaleFactor)523 BoundingBox<T, NDIMS>& BoundingBox<T, NDIMS>::scale(double scaleFactor)
524 {
525 const PointType midpoint = getCentroid();
526 const VectorType r = static_cast<T>(scaleFactor * 0.5) * range();
527
528 m_min = PointType(midpoint.array() - r.array());
529 m_max = PointType(midpoint.array() + r.array());
530
531 this->checkAndFixBounds();
532
533 return *this;
534 }
535
536 //------------------------------------------------------------------------------
537 template <typename T, int NDIMS>
shift(const VectorType & disp)538 BoundingBox<T, NDIMS>& BoundingBox<T, NDIMS>::shift(const VectorType& disp)
539 {
540 m_min.array() += disp.array();
541 m_max.array() += disp.array();
542
543 return *this;
544 }
545
546 //------------------------------------------------------------------------------
547 template <typename T, int NDIMS>
checkAndFixBounds()548 void BoundingBox<T, NDIMS>::checkAndFixBounds()
549 {
550 for(int dim = 0; dim < NDIMS; ++dim)
551 {
552 if(m_min[dim] > m_max[dim])
553 {
554 utilities::swap(m_min[dim], m_max[dim]);
555 }
556 }
557 }
558
559 //------------------------------------------------------------------------------
560 template <typename T, int NDIMS>
clear()561 void BoundingBox<T, NDIMS>::clear()
562 {
563 m_min = PointType(InvalidMin);
564 m_max = PointType(InvalidMax);
565 }
566
567 //------------------------------------------------------------------------------
568 template <typename T, int NDIMS>
print(std::ostream & os) const569 std::ostream& BoundingBox<T, NDIMS>::print(std::ostream& os) const
570 {
571 os << "{ min:" << m_min << "; max:" << m_max << "; range:" << range() << " }";
572 return os;
573 }
574
575 //------------------------------------------------------------------------------
576 template <typename T, int NDIMS>
intersect(const BoundingBox & otherBox)577 BoundingBox<T, NDIMS>& BoundingBox<T, NDIMS>::intersect(const BoundingBox& otherBox)
578 {
579 for(int i = 0; i < NDIMS; ++i)
580 {
581 m_min[i] = std::max(m_min[i], otherBox.m_min[i]);
582 m_max[i] = std::min(m_max[i], otherBox.m_max[i]);
583 }
584
585 if(!isValid())
586 {
587 clear();
588 }
589
590 return *this;
591 }
592
593 //------------------------------------------------------------------------------
594 template <typename T, int NDIMS>
bisect(BoxType & right,BoxType & left,int dim) const595 void BoundingBox<T, NDIMS>::bisect(BoxType& right, BoxType& left, int dim) const
596 {
597 SLIC_ASSERT(this->isValid());
598
599 if(dim < 0)
600 {
601 dim = this->getLongestDimension();
602 }
603 SLIC_ASSERT(dim >= 0 && dim < NDIMS);
604
605 // calculate mid along the given dimension
606 T mid = 0.5 * (m_max[dim] + m_min[dim]);
607
608 // update right
609 right.setMin(this->getMin());
610 PointType new_right_max = this->getMax();
611 new_right_max[dim] = mid;
612 right.setMax(new_right_max);
613 SLIC_ASSERT(right.isValid());
614
615 // update left
616 left.setMax(this->getMax());
617 PointType new_left_min = this->getMin();
618 new_left_min[dim] = mid;
619 left.setMin(new_left_min);
620 SLIC_ASSERT(left.isValid());
621 }
622
623 //------------------------------------------------------------------------------
624 // Implementation of static methods
625 //------------------------------------------------------------------------------
626 template <typename T, int NDIMS>
getPoints(const BoundingBox<T,2> & bb,std::vector<Point<T,2>> & pnts)627 inline void BoundingBox<T, NDIMS>::getPoints(const BoundingBox<T, 2>& bb,
628 std::vector<Point<T, 2>>& pnts)
629 {
630 pnts.resize(4);
631 const Point<T, 2>& min = bb.getMin();
632 const Point<T, 2>& max = bb.getMax();
633
634 pnts[0] = Point<T, 2>::make_point(min[0], min[1]);
635 pnts[1] = Point<T, 2>::make_point(max[0], min[1]);
636 pnts[2] = Point<T, 2>::make_point(max[0], max[1]);
637 pnts[3] = Point<T, 2>::make_point(min[0], max[1]);
638 }
639
640 //------------------------------------------------------------------------------
641 template <typename T, int NDIMS>
getPoints(const BoundingBox<T,3> & bb,std::vector<Point<T,3>> & pnts)642 inline void BoundingBox<T, NDIMS>::getPoints(const BoundingBox<T, 3>& bb,
643 std::vector<Point<T, 3>>& pnts)
644 {
645 pnts.resize(8);
646 const Point<T, 3>& min = bb.getMin();
647 const Point<T, 3>& max = bb.getMax();
648
649 pnts[0] = Point<T, 3>::make_point(min[0], min[1], min[2]);
650 pnts[1] = Point<T, 3>::make_point(max[0], min[1], min[2]);
651 pnts[2] = Point<T, 3>::make_point(max[0], max[1], min[2]);
652 pnts[3] = Point<T, 3>::make_point(min[0], max[1], min[2]);
653
654 pnts[4] = Point<T, 3>::make_point(min[0], min[1], max[2]);
655 pnts[5] = Point<T, 3>::make_point(max[0], min[1], max[2]);
656 pnts[6] = Point<T, 3>::make_point(max[0], max[1], max[2]);
657 pnts[7] = Point<T, 3>::make_point(min[0], max[1], max[2]);
658 }
659
660 //------------------------------------------------------------------------------
661 /// Free functions implementing comparison and arithmetic operators
662 //------------------------------------------------------------------------------
663
664 template <typename T, int NDIMS>
operator ==(const BoundingBox<T,NDIMS> & lhs,const BoundingBox<T,NDIMS> & rhs)665 bool operator==(const BoundingBox<T, NDIMS>& lhs, const BoundingBox<T, NDIMS>& rhs)
666 {
667 return lhs.getMin() == rhs.getMin() && lhs.getMax() == rhs.getMax();
668 }
669
670 //------------------------------------------------------------------------------
671 template <typename T, int NDIMS>
operator !=(const BoundingBox<T,NDIMS> & lhs,const BoundingBox<T,NDIMS> & rhs)672 bool operator!=(const BoundingBox<T, NDIMS>& lhs, const BoundingBox<T, NDIMS>& rhs)
673 {
674 return !(lhs == rhs);
675 }
676
677 //------------------------------------------------------------------------------
678 template <typename T, int NDIMS>
operator <<(std::ostream & os,const BoundingBox<T,NDIMS> & bb)679 std::ostream& operator<<(std::ostream& os, const BoundingBox<T, NDIMS>& bb)
680 {
681 return bb.print(os);
682 }
683
684 } // namespace primal
685 } // namespace axom
686
687 #endif // AXOM_PRIMAL_BOUNDINGBOX_HPP_
688