1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoGetBoundingBoxAction SoGetBoundingBoxAction.h Inventor/actions/SoGetBoundingBoxAction.h
35   \brief The SoGetBoundingBoxAction class calculates bounding boxes for nodes and subgraphs.
36 
37   \ingroup actions
38 
39   If this action is applied to a path or scene graph root, it will
40   calculate the bounding box and the center point of the geometry
41   contained within the scene.
42 
43   You don't have to apply an SoGetBoundingBoxAction to the \e root of
44   a scene. When using the action, you will get the bounding box of the
45   node you are applying it to and that node's sub-tree in the scene
46   graph (if any).
47 
48   The calculated bounding box will be in the local coordinates of that
49   sub-tree. If applying it to a scene graph root node, the calculated
50   bounding box will be in global coordinates.
51 
52   The use of bounding boxes is ubiquitous within the Coin library. It
53   is needed for the correct execution of and for performance
54   enhancements during rendering, picking, caching, culling, etc.
55 
56 
57   SoSeparator nodes are aggressively caching the results of bounding
58   box calculations, so that they are really only re-calculated
59   whenever the scenegraph rooted below any SoSeparator node has been
60   modified. This means that applying this action to scenegraphs, or
61   parts of scenegraphs, should be very quick on successive runs for
62   "static" parts of the scene.
63 
64   Note that the algorithm used is not guaranteed to always give an
65   exact bounding box: it combines bounding boxes in pairs and extends
66   one of them to contain the other. Since the boxes need not be
67   parallel to the principal axes the new box might not be a perfect
68   fit for the box not extended (it's coordinate system has been
69   changed).
70 
71   Note also that what is returned from getBoundingBox() will be
72   projected so as to be oriented along the principal axes, which can
73   often cause it to become quite a lot larger than what it was before
74   projection. For client code to obtain the best bounding box that
75   Coin can calculate (and which will usually be exact), you need to
76   use the getXfBoundingBox() method after having applied the
77   SoGetBoundingBoxAction.
78 
79   \sa SoSeparator::boundingBoxCaching
80 */
81 
82 #include <Inventor/actions/SoGetBoundingBoxAction.h>
83 
84 #include <Inventor/elements/SoBBoxModelMatrixElement.h>
85 #include <Inventor/elements/SoLocalBBoxMatrixElement.h>
86 #include <Inventor/elements/SoViewingMatrixElement.h>
87 #include <Inventor/elements/SoViewportRegionElement.h>
88 #include <Inventor/lists/SoEnabledElementsList.h>
89 #include <Inventor/misc/SoState.h>
90 #include <Inventor/nodes/SoNode.h>
91 
92 #if COIN_DEBUG
93 #include <Inventor/errors/SoDebugError.h>
94 #endif // COIN_DEBUG
95 
96 #include "actions/SoSubActionP.h"
97 #include "SbBasicP.h"
98 
99 // FIXME: kristian investigated the assumed bug-cases listed below,
100 // and found that it is fundamentally impossible making a perfect fit
101 // around any scene graph geometry with the current strategy of
102 // combining SbXfBox3f instances. (To get a perfect fit, we'd need a
103 // "memory" of all boxes used for combining into larger ones.)
104 //
105 // Seems hard to fix without breaking or extending the API in
106 // non-trivial ways -- but it could perhaps be done (SoGroup-derived
107 // nodes would at least have to store bboxes and transforms from all
108 // their children, and another algorithm than the current simple
109 // "combine two and two" would have to be run over this list).
110 //
111 // The bug-reports stored below for reference, as they would be nice
112 // to use for testing if we ever decide to try to fix the defect(s)
113 // with the bbox accumulations.
114 //
115 // 20030116 mortene.
116 //
117 // ======================================================================
118 //
119 //  013 Bounding box calculation of the scenegraph given below is
120 //      sub-optimal.
121 //
122 //      ----8<--- [snip] ---------8<--- [snip] ---------8<--- [snip] ---
123 //      #Inventor V2.1 ascii
124 //
125 //      Separator {
126 //         Separator {
127 //            Cube { }
128 //
129 //            BaseColor { rgb 1 0 0 }
130 //            Translation { translation +4 0 0 }
131 //            Separator {
132 //               Transform {
133 //                  translation 0 -0.5 0
134 //                  rotation 0 0 1 0.78
135 //                  scaleFactor 0.5 2 3
136 //                  scaleOrientation 1 0 0 0.78
137 //                  center 0.5 0.5 0.5
138 //               }
139 //               Cube { }
140 //            }
141 //         }
142 //
143 //         Translation { translation 0 +6 0 }
144 //         Separator {
145 //            Cube { }
146 
147 //            BaseColor { rgb 1 0 0 }
148 //            Translation { translation +4 0 0 }
149 //            Separator {
150 //               Transform {
151 //                  translation 10 -0.5 0 ~
152 //                  rotation 0 0 1 0.78
153 //                  scaleFactor 0.5 2 3
154 //                  scaleOrientation 1 0 0 0.78
155 //                  center 0.5 0.5 0.5
156 //               }
157 //               Cube { }
158 //            }
159 //         }
160 //      }
161 //      ----8<--- [snip] ---------8<--- [snip] ---------8<--- [snip] ---
162 //
163 //      A good opening gambit for investigating the bug is using the
164 //      SoGuiExamples/engines/computexfbox example code to load the scene
165 //      and view the resulting bbox.
166 //
167 //      mortene 20020729.
168 //
169 // ======================================================================
170 //
171 //  022 Sub-optimal bounding box calculations.
172 //
173 //      The fairly simple scenegraph below results in a rather sub-optimal
174 //      bounding box being calculated. For a good view of how it is, use
175 //      the SoGuiExamples/engines/computexfbbox example.
176 //
177 //      ----8<--- [snip] ---------8<--- [snip] ---------8<--- [snip] ---
178 //      #Inventor V2.1 ascii
179 //
180 //      Separator {
181 //         LightModel { model BASE_COLOR }
182 //
183 //         Cube { height 5 }
184 //
185 //         Separator {
186 //            Rotation { rotation 1 0 0  0.7854 }
187 //            Cube { }
188 //         }
189 //
190 //         Rotation { rotation 2 3 9  1.5708 }
191 //         Cube { height 4 }
192 //      }
193 //      ----8<--- [snip] ---------8<--- [snip] ---------8<--- [snip] ---
194 //
195 //      (Note that this scenegraph does not show a _grave_ bbox error. I
196 //      prioritized getting it as small as possible while still
197 //      demonstrating that there *is* an error. I have the original
198 //      scenegraph which I constructed this from, where the bbox is _way_
199 //      off.)
200 //
201 //      UPDATE 20020830 mortene: I used SGI Inventor to check both the
202 //      boundingbox of the minimal case above and the larger scene where
203 //      it comes out fairly sub-optimal for us -- and the original SGI
204 //      Inventor doesn't make any tighter fit than we are. So there might
205 //      be something fundamental about the case above which makes it
206 //      impossible to have SbXfBox3f.extendBy(SbXfbox3f) come out with a
207 //      perfect fit?  Need to investigate.
208 //
209 //      20020826 mortene.
210 //
211 // ======================================================================
212 
213 
214 // *************************************************************************
215 
216 /*!
217   \enum SoGetBoundingBoxAction::ResetType
218   \COININTERNAL
219 */
220 
221 class SoGetBoundingBoxActionP {
222 public:
223 };
224 
225 SO_ACTION_SOURCE(SoGetBoundingBoxAction);
226 
227 
228 // Overridden from parent class.
229 void
initClass(void)230 SoGetBoundingBoxAction::initClass(void)
231 {
232   SO_ACTION_INTERNAL_INIT_CLASS(SoGetBoundingBoxAction, SoAction);
233 
234   SO_ENABLE(SoGetBoundingBoxAction, SoViewportRegionElement);
235 }
236 
237 /*!
238   Constructor.
239 
240   It might seem unnecessary to have to pass in a viewport region
241   argument to calculate bounding boxes, but there is a good reason for
242   this: a few shape nodes needs to know the viewport region to
243   calculate their bounding box -- these include SoText2 and SoImage,
244   among others.
245 
246   What is particular about these shapes is that they are fundamentally
247   2D shapes, but they are being rendered on the screen "surface" as if
248   they were in a 3D scene. (This is possible because we can match each
249   pixel's depth value against the 3D shapes in the scene.)
250 
251   To compute an accurate 3D bounding box of a shape rendered in 2D on
252   the screen "surface", you need to "de-project" the screen-space area
253   it occupies to a 2D rectangle placed at some depth in the
254   scene. This "de-projecting" operation needs to know about the
255   dimensions of the viewport.
256 
257   Also, some 3D shapes like for instance SoNurbsSurface, get slightly
258   distorted if there's an SoComplexity node in the scenegraph with the
259   SoComplexity::value field set to SCREEN_SPACE. Then it is also
260   necessary to know the viewport region to find out how to accurately
261   calculate the bounding box of those shapes.
262 
263   You would usually want to pass in a viewport region equal to the
264   layout of the current renderarea canvas. If you have a viewer or
265   So\@Gui\@RenderArea available, you can get hold of the viewport region
266   data simply by doing
267 
268   \code
269      const SbViewportRegion & vpreg = viewer->getViewportRegion();
270   \endcode
271 
272   (If you don't have a viewer or renderarea available in your
273   application at the point where you want to get the bounding box, it
274   probably doesn't matter much what you set it to. The accuracy of the
275   bounding box calculation might be slightly wrong versus the actual
276   rendered appearance of the scene, but this is usually not
277   noticable.)
278 */
SoGetBoundingBoxAction(const SbViewportRegion & vp)279 SoGetBoundingBoxAction::SoGetBoundingBoxAction(const SbViewportRegion & vp)
280   : center(0, 0, 0),
281     vpregion(vp),
282     resettype(SoGetBoundingBoxAction::ALL),
283     resetpath(NULL),
284     flags(SoGetBoundingBoxAction::RESET_BEFORE)
285 {
286   SO_ACTION_CONSTRUCTOR(SoGetBoundingBoxAction);
287 }
288 
289 /*!
290   Destructor.
291 */
~SoGetBoundingBoxAction()292 SoGetBoundingBoxAction::~SoGetBoundingBoxAction()
293 {
294 }
295 
296 // *************************************************************************
297 
298 /*!
299   Set a new viewport region with this method, if it has changed from
300   the one passed in with the constructor.
301 */
302 void
setViewportRegion(const SbViewportRegion & newregion)303 SoGetBoundingBoxAction::setViewportRegion(const SbViewportRegion & newregion)
304 {
305   this->vpregion = newregion;
306 }
307 
308 /*!
309   Returns the viewport region used by the action instance.
310 */
311 const SbViewportRegion &
getViewportRegion(void) const312 SoGetBoundingBoxAction::getViewportRegion(void) const
313 {
314   return this->vpregion;
315 }
316 
317 /*!
318   Returns the projected bounding box after (or during) traversal.
319 */
320 SbBox3f
getBoundingBox(void) const321 SoGetBoundingBoxAction::getBoundingBox(void) const
322 {
323   return this->bbox.project();
324 }
325 
326 /*!
327   Returns the bounding box and transformation matrix to global
328   coordinates. Use after (or during) traversal.
329 */
330 SbXfBox3f &
getXfBoundingBox(void)331 SoGetBoundingBoxAction::getXfBoundingBox(void)
332 {
333   return this->bbox;
334 }
335 
336 /*!
337   Returns center point of scene after the action has been applied.
338 
339   This might differ from the geometric center of the bounding box, as
340   shape nodes may "weight" the center point according to various
341   criteria (i.e. a faceset could for instance weight the center point
342   according to the area within its bounding box where there are more
343   polygons).
344 */
345 const SbVec3f &
getCenter(void) const346 SoGetBoundingBoxAction::getCenter(void) const
347 {
348   if (!this->isCenterSet()) {
349     // Cast away constness and set.
350     SoGetBoundingBoxAction * action = const_cast<SoGetBoundingBoxAction *>(this);
351     action->center.setValue(0.0f, 0.0f, 0.0f);
352   }
353   // Center point should not be affected by the current
354   // transformation.
355   return this->center;
356 }
357 
358 /*!
359   Sets whether the returned bounding box should be calculated in the
360   coordinate system of the camera space or not.
361 */
362 void
setInCameraSpace(const SbBool on)363 SoGetBoundingBoxAction::setInCameraSpace(const SbBool on)
364 {
365   if (on) this->flags |= SoGetBoundingBoxAction::CAMERA_SPACE;
366   else this->flags &= ~SoGetBoundingBoxAction::CAMERA_SPACE;
367 }
368 
369 /*!
370   Returns whether the bounding box returned is to be in camera space.
371 */
372 SbBool
isInCameraSpace(void) const373 SoGetBoundingBoxAction::isInCameraSpace(void) const
374 {
375   return (this->flags & SoGetBoundingBoxAction::CAMERA_SPACE) != 0;
376 }
377 
378 /*!
379   Forces the computed bounding box to be reset and the transformation
380   to be identity before or after the tail node of \a path, depending
381   on the \a resetbefore argument.  \c NULL can be specified for the \a
382   path argument to disable this behavior.
383 
384   \sa getResetPath(), isResetPath(), isResetBefore(), getWhatReset()
385 */
386 
387 void
setResetPath(const SoPath * path,const SbBool resetbefore,const ResetType what)388 SoGetBoundingBoxAction::setResetPath(const SoPath * path,
389                                      const SbBool resetbefore,
390                                      const ResetType what)
391 {
392   this->resetpath = path;
393   this->resettype = what;
394   if (resetbefore) this->flags |= SoGetBoundingBoxAction::RESET_BEFORE;
395   else this->flags &= ~SoGetBoundingBoxAction::RESET_BEFORE;
396 }
397 
398 /*!
399   Returns the reset path (or \c NULL).
400 
401   \sa setResetPath(), isResetPath(), isResetBefore(), getWhatReset()
402 */
403 const SoPath *
getResetPath(void) const404 SoGetBoundingBoxAction::getResetPath(void) const
405 {
406   return this->resetpath;
407 }
408 
409 /*!
410   Returns whether a reset path is set or not.
411 
412   \sa setResetPath(), getResetPath(), isResetBefore(), getWhatReset()
413 */
414 SbBool
isResetPath(void) const415 SoGetBoundingBoxAction::isResetPath(void) const
416 {
417   return this->resetpath != NULL;
418 }
419 
420 /*!
421   Returns whether the bounding box and transformation is reset before
422   or after the tail node of the reset path.
423 
424   \sa setResetPath(), getResetPath(), isResetPath(), getWhatReset()
425 */
426 SbBool
isResetBefore(void) const427 SoGetBoundingBoxAction::isResetBefore(void) const
428 {
429   return (this->flags & SoGetBoundingBoxAction::RESET_BEFORE) != 0;
430 }
431 
432 /*!
433   Returns what type of reset has been specified for the reset path.
434 
435   \sa setResetPath(), getResetPath(), isResetPath(), isResetBefore()
436 */
437 SoGetBoundingBoxAction::ResetType
getWhatReset(void) const438 SoGetBoundingBoxAction::getWhatReset(void) const
439 {
440   return this->resettype;
441 }
442 
443 /*!
444   \COININTERNAL
445   Called before node traversal of each node (from SoNode action method).
446 */
447 void
checkResetBefore(void)448 SoGetBoundingBoxAction::checkResetBefore(void)
449 {
450   if (this->resetpath && this->isResetBefore()) {
451     const SoFullPath * curpath = reclassify_cast<const SoFullPath *>(this->getCurPath());
452     const SoFullPath * theresetpath = reclassify_cast<const SoFullPath *>(this->resetpath);
453     if ((curpath->getTail() == theresetpath->getTail()) &&
454         curpath->containsPath(theresetpath)) {
455       if (this->resettype & SoGetBoundingBoxAction::TRANSFORM) {
456         SoBBoxModelMatrixElement::reset(this->getState(), curpath->getTail());
457       }
458       if (this->resettype & SoGetBoundingBoxAction::BBOX) {
459         this->bbox.makeEmpty();
460         this->bbox.setTransform(SbMatrix::identity());
461         this->resetCenter();
462       }
463     }
464   }
465 }
466 
467 /*!
468   \COININTERNAL
469   Called after node traversal of each node (from SoNode action method).
470 */
471 void
checkResetAfter(void)472 SoGetBoundingBoxAction::checkResetAfter(void)
473 {
474   if (this->resetpath && !this->isResetBefore()) {
475     const SoFullPath * curpath = reclassify_cast<const SoFullPath *>(this->getCurPath());
476     const SoFullPath * theresetpath = reclassify_cast<const SoFullPath *>(this->resetpath);
477     if ((curpath->getTail() == theresetpath->getTail()) &&
478         curpath->containsPath(theresetpath)) {
479       if (this->resettype & SoGetBoundingBoxAction::TRANSFORM) {
480         SoBBoxModelMatrixElement::reset(this->getState(), curpath->getTail());
481       }
482       if (this->resettype & SoGetBoundingBoxAction::BBOX) {
483         this->bbox.makeEmpty();
484         this->bbox.setTransform(SbMatrix::identity());
485         this->resetCenter();
486       }
487     }
488   }
489 }
490 
491 /*!
492   Extend bounding box by the given \a box. Called from nodes during
493   traversal.
494 
495   Should usually not be of interest to application programmers, unless
496   you're extending Coin with your own shapenode extension classes.
497 */
498 void
extendBy(const SbBox3f & box)499 SoGetBoundingBoxAction::extendBy(const SbBox3f & box)
500 {
501   if (box.isEmpty()) {
502 #if COIN_DEBUG
503     SoDebugError::postWarning("SoGetBoundingBoxAction::extendBy", "empty box");
504 #endif // COIN_DEBUG
505     return;
506   }
507 
508   SbXfBox3f xfbox = box;
509   SbMatrix transform = SoLocalBBoxMatrixElement::get(this->getState());
510   if (this->isInCameraSpace()) {
511     transform.multRight(SoViewingMatrixElement::get(this->getState()));
512   }
513 
514   xfbox.transform(transform);
515   this->bbox.extendBy(xfbox);
516 }
517 
518 /*! \overload */
519 void
extendBy(const SbXfBox3f & box)520 SoGetBoundingBoxAction::extendBy(const SbXfBox3f & box)
521 {
522   if (box.isEmpty()) {
523 #if COIN_DEBUG
524     SoDebugError::postWarning("SoGetBoundingBoxAction::extendBy", "empty box");
525 #endif // COIN_DEBUG
526     return;
527   }
528 
529   SbXfBox3f lbox = box;
530   SbMatrix transform = SoLocalBBoxMatrixElement::get(this->state);
531   if (this->isInCameraSpace()) {
532     transform.multRight(SoViewingMatrixElement::get(this->state));
533   }
534   lbox.transform(transform);
535   this->bbox.extendBy(lbox);
536 }
537 
538 /*!
539   \COININTERNAL
540   Set a new center point during traversal.
541 */
542 void
setCenter(const SbVec3f & centerarg,const SbBool transformcenter)543 SoGetBoundingBoxAction::setCenter(const SbVec3f & centerarg,
544                                   const SbBool transformcenter)
545 {
546   assert(!this->isCenterSet());
547   this->flags |= SoGetBoundingBoxAction::CENTER_SET;
548 
549   if (transformcenter) {
550     SbMatrix lmat = SoLocalBBoxMatrixElement::get(this->state);
551     if (this->isInCameraSpace()) {
552       lmat.multRight(SoViewingMatrixElement::get(this->state));
553     }
554     lmat.multVecMatrix(centerarg, this->center);
555   }
556   else {
557     this->center = centerarg;
558   }
559 
560 #if COIN_DEBUG && 0 // debug
561   SoDebugError::postInfo("SoGetBoundingBoxAction::setCenter",
562                          "center: <%f, %f, %f>, transformcenter: %s, "
563                          "this->center: <%f, %f, %f>",
564                          centerarg[0], centerarg[1], centerarg[2],
565                          transformcenter ? "TRUE" : "FALSE",
566                          this->center[0], this->center[1], this->center[2]);
567 #endif // debug
568 }
569 
570 /*!
571   \COININTERNAL
572   Query about the center point during traversal.
573 */
574 SbBool
isCenterSet(void) const575 SoGetBoundingBoxAction::isCenterSet(void) const
576 {
577   return (this->flags & SoGetBoundingBoxAction::CENTER_SET) != 0;
578 }
579 
580 /*!
581   \COININTERNAL
582   Reset the scene center point during traversal.
583 */
584 void
resetCenter(void)585 SoGetBoundingBoxAction::resetCenter(void)
586 {
587   this->flags &= ~SoGetBoundingBoxAction::CENTER_SET;
588   this->center.setValue(0.0f, 0.0f, 0.0f);
589 }
590 
591 // Documented in superclass. Overridden to reset center point and
592 // bounding box before traversal starts.
593 void
beginTraversal(SoNode * node)594 SoGetBoundingBoxAction::beginTraversal(SoNode * node)
595 {
596   this->resetCenter();
597   this->bbox.makeEmpty();
598 
599   SoViewportRegionElement::set(this->getState(), this->vpregion);
600   inherited::beginTraversal(node);
601 }
602