1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_BASE_GF_BBOX3D_H
25 #define PXR_BASE_GF_BBOX3D_H
26 
27 /// \file gf/bbox3d.h
28 /// \ingroup group_gf_BasicGeometry
29 
30 #include "pxr/pxr.h"
31 #include "pxr/base/gf/matrix4d.h"
32 #include "pxr/base/gf/range3d.h"
33 #include "pxr/base/gf/api.h"
34 
35 #include <iosfwd>
36 
37 PXR_NAMESPACE_OPEN_SCOPE
38 
39 /// \class GfBBox3d
40 /// \ingroup group_gf_BasicGeometry
41 /// Basic type: arbitrarily oriented 3D bounding box.
42 ///
43 /// This class represents a three-dimensional bounding box as an
44 /// axis-aligned box (\c GfRange3d) and a matrix (\c GfMatrix4d) to
45 /// transform it into the correct space.
46 ///
47 /// A \c GfBBox3d is more useful than using just \c GfRange3d instances
48 /// (which are always axis-aligned) for these reasons:
49 ///
50 /// \li When an axis-aligned bounding box is transformed several times,
51 /// each transformation can result in inordinate growth of the bounding
52 /// box. By storing the transformation separately, it can be applied once
53 /// at the end, resulting in a much better fit.  For example, if the
54 /// bounding box at the leaf of a scene graph is transformed through
55 /// several levels of the graph hierarchy to the coordinate space at the
56 /// root, a \c GfBBox3d is generally much smaller than the \c GfRange3d
57 /// computed by transforming the box at each level.
58 ///
59 /// \li When two or more such bounding boxes are combined, having the
60 /// transformations stored separately means that there is a better
61 /// opportunity to choose a better coordinate space in which to combine
62 /// the boxes.
63 ///
64 /// \anchor bbox3d_zeroAreaFlag
65 /// <b> The Zero-area Primitives Flag </b>
66 ///
67 /// When bounding boxes are used in intersection test culling, it is
68 /// sometimes useful to extend them a little bit to allow
69 /// lower-dimensional objects with zero area, such as lines and points,
70 /// to be intersected. For example, consider a cube constructed of line
71 /// segments. The bounding box for this shape fits the cube exactly. If
72 /// an application wants to allow a near-miss of the silhouette edges of
73 /// the cube to be considered an intersection, it has to loosen the bbox
74 /// culling test a little bit.
75 ///
76 /// To distinguish when this loosening is necessary, each \c GfBBox3d
77 /// instance maintains a flag indicating whether any zero-area primitives
78 /// are contained within it. The application is responsible for setting
79 /// this flag correctly by calling \c SetHasZeroAreaPrimitives(). The
80 /// flag can be accessed during intersection tests by calling \c
81 /// HasZeroAreaPrimitives(). This flag is set by default in all
82 /// constructors to \c false.
83 ///
84 class GfBBox3d {
85 
86   public:
87 
88     /// The default constructor leaves the box empty, the transformation
89     /// matrix identity, and the \ref bbox3d_zeroAreaFlag "zero-area
90     /// primitives flag" \c false.
GfBBox3d()91     GfBBox3d() {
92         _matrix.SetIdentity();
93         _inverse.SetIdentity();
94         _isDegenerate = false;
95         _hasZeroAreaPrimitives = false;
96     }
97 
98     /// Copy constructor
GfBBox3d(const GfBBox3d & rhs)99     GfBBox3d(const GfBBox3d& rhs) :
100         _box(rhs._box) {
101         _matrix = rhs._matrix;
102         _inverse = rhs._inverse;
103         _isDegenerate = rhs._isDegenerate;
104         _hasZeroAreaPrimitives = rhs._hasZeroAreaPrimitives;
105     }
106 
107     /// This constructor takes a box and sets the matrix to identity.
GfBBox3d(const GfRange3d & box)108     GfBBox3d(const GfRange3d &box) :
109         _box(box) {
110         _matrix.SetIdentity();
111         _inverse.SetIdentity();
112         _isDegenerate = false;
113         _hasZeroAreaPrimitives = false;
114     }
115 
116     /// This constructor takes a box and a transformation matrix.
GfBBox3d(const GfRange3d & box,const GfMatrix4d & matrix)117     GfBBox3d(const GfRange3d &box, const GfMatrix4d &matrix) {
118         Set(box, matrix);
119         _hasZeroAreaPrimitives = false;
120     }
121 
122     /// Sets the axis-aligned box and transformation matrix.
Set(const GfRange3d & box,const GfMatrix4d & matrix)123     void                Set(const GfRange3d &box, const GfMatrix4d &matrix) {
124         _box = box;
125         _SetMatrices(matrix);
126     }
127 
128     /// Sets the transformation matrix only.  The axis-aligned box is not
129     /// modified.
SetMatrix(const GfMatrix4d & matrix)130     void                SetMatrix(const GfMatrix4d& matrix) {
131         _SetMatrices(matrix);
132     }
133 
134     /// Sets the range of the axis-aligned box only.  The transformation
135     /// matrix is not modified.
SetRange(const GfRange3d & box)136     void                SetRange(const GfRange3d& box) {
137         _box = box;
138     }
139 
140     /// Returns the range of the axis-aligned untransformed box.
GetRange()141     const GfRange3d &   GetRange() const {
142         return _box;
143     }
144 
145     /// Returns the range of the axis-aligned untransformed box.
146     /// This synonym of \c GetRange exists for compatibility purposes.
GetBox()147     const GfRange3d &	GetBox() const {
148         return GetRange();
149     }
150 
151     /// Returns the transformation matrix.
GetMatrix()152     const GfMatrix4d &  GetMatrix() const {
153         return _matrix;
154     }
155 
156     /// Returns the inverse of the transformation matrix. This will be the
157     /// identity matrix if the transformation matrix is not invertible.
GetInverseMatrix()158     const GfMatrix4d &  GetInverseMatrix() const {
159         return _inverse;
160     }
161 
162     /// Sets the \ref bbox3d_zeroAreaFlag "zero-area primitives flag" to the
163     /// given value.
SetHasZeroAreaPrimitives(bool hasThem)164     void                SetHasZeroAreaPrimitives(bool hasThem) {
165         _hasZeroAreaPrimitives = hasThem;
166     }
167 
168     /// Returns the current state of the \ref bbox3d_zeroAreaFlag "zero-area
169     /// primitives flag".
HasZeroAreaPrimitives()170     bool                HasZeroAreaPrimitives() const {
171         return _hasZeroAreaPrimitives;
172     }
173 
174     /// Returns the volume of the box (0 for an empty box).
175     GF_API
176     double              GetVolume() const;
177 
178     /// Transforms the bounding box by the given matrix, which is assumed to
179     /// be a global transformation to apply to the box. Therefore, this just
180     /// post-multiplies the box's matrix by \p matrix.
Transform(const GfMatrix4d & matrix)181     void                Transform(const GfMatrix4d &matrix) {
182         _SetMatrices(_matrix * matrix);
183     }
184 
185     /// Returns the axis-aligned range (as a \c GfRange3d) that results from
186     /// applying the transformation matrix to the wxis-aligned box and
187     /// aligning the result.
188     GF_API
189     GfRange3d           ComputeAlignedRange() const;
190 
191     /// Returns the axis-aligned range (as a \c GfRange3d) that results from
192     /// applying the transformation matrix to the axis-aligned box and
193     /// aligning the result. This synonym for \c ComputeAlignedRange exists
194     /// for compatibility purposes.
ComputeAlignedBox()195     GfRange3d           ComputeAlignedBox() const {
196         return ComputeAlignedRange();
197     }
198 
199     /// Combines two bboxes, returning a new bbox that contains both.  This
200     /// uses the coordinate space of one of the two original boxes as the
201     /// space of the result; it uses the one that produces whe smaller of the
202     /// two resulting boxes.
203     GF_API
204     static GfBBox3d     Combine(const GfBBox3d &b1, const GfBBox3d &b2);
205 
206     /// Returns the centroid of the bounding box.
207     /// The centroid is computed as the transformed centroid of the range.
208     GF_API
209     GfVec3d             ComputeCentroid() const;
210 
211     /// Hash.
hash_value(const GfBBox3d & b)212     friend inline size_t hash_value(const GfBBox3d &b) {
213         size_t h = 0;
214         boost::hash_combine(h, b._box);
215         boost::hash_combine(h, b._matrix);
216         return h;
217     }
218 
219     /// Component-wise equality test. The axis-aligned boxes and
220     /// transformation matrices match exactly for bboxes to be considered
221     /// equal. (To compare equality of the actual boxes, you can compute both
222     /// aligned boxes and test the results for equality.)
223     bool                operator ==(const GfBBox3d &b) const {
224         return (_box    == b._box &&
225                 _matrix == b._matrix);
226     }
227 
228     /// Component-wise inequality test. The axis-aligned boxes and
229     /// transformation matrices match exactly for bboxes to be considered
230     /// equal. (To compare equality of the actual boxes, you can compute both
231     /// aligned boxes and test the results for equality.)
232     bool                operator !=(const GfBBox3d &that) const {
233         return !(*this == that);
234     }
235 
236   private:
237     /// The axis-aligned box.
238     GfRange3d           _box;
239     /// Transformation matrix.
240     GfMatrix4d          _matrix;
241     /// Inverse of the transformation matrix.
242     GfMatrix4d          _inverse;
243     /// Flag indicating whether the matrix is degenerate.
244     bool                _isDegenerate;
245     /// Flag indicating whether the bbox contains zero-area primitives.
246     bool                _hasZeroAreaPrimitives;
247 
248     /// Sets the transformation matrix and the inverse, checking for
249     /// degeneracies.
250     GF_API
251     void                _SetMatrices(const GfMatrix4d &matrix);
252 
253     /// This is used by \c Combine() when it is determined which coordinate
254     /// space to use to combine two boxes: \p b2 is transformed into the space
255     /// of \p b1 and the results are merged in that space.
256     static GfBBox3d     _CombineInOrder(const GfBBox3d &b1, const GfBBox3d &b2);
257 };
258 
259 /// Output a GfBBox3d using the format [(range) matrix zeroArea]
260 ///
261 /// The zeroArea flag is true or false and indicates whether the
262 /// bbox has zero area primitives in it.
263 ///
264 /// \ingroup group_gf_DebuggingOutput
265 GF_API std::ostream& operator<<(std::ostream&, const GfBBox3d&);
266 
267 PXR_NAMESPACE_CLOSE_SCOPE
268 
269 #endif // PXR_BASE_GF_BBOX3D_H
270