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