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