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 SoLevelOfDetail SoLevelOfDetail.h Inventor/nodes/SoLevelOfDetail.h
35   \brief The SoLevelOfDetail class is used to choose a child based on projected size.
36 
37   \ingroup nodes
38 
39   A level-of-detail mechanism is typically used by application
40   programmers to assist the library in speeding up the rendering.
41 
42   The way a level-of-detail mechanism works is basically like this:
43 
44   Several versions of varying complexity of \e the \e same geometry /
45   shape is provided by the application programmer in sorted order from
46   "most complex" to "least complex" (where "complex" in this context
47   should be taken to mean more or less detailed in the number of
48   polygons / shapes used for rendering it).
49 
50   The run-time rendering system then, upon scenegraph traversal, will
51   on-the-fly calculate either the distance from the camera to the
52   3D-model in question, or the number of pixels in the screen
53   projection of the 3D-model. This value is then used to decide which
54   version of the model to actually render: as the model is moved
55   farther away from the camera, a less detailed version of the model
56   is used. And vice versa, as the model moves closer to the camera,
57   more and more detailed versions of it are rendered.
58 
59   This is under many different circumstances a very effective way to
60   let the application programmer assist to \e profoundly optimize the
61   rendering of her 3D-scene.
62 
63   There is of course a trade-off with the level-of-detail technique:
64   more versions of the same 3D model means the scenegraph will use up
65   more application memory resources. Also, generating the set of less
66   and less detailed versions of a 3D model from the original is often
67   not a trivial task to do properly. The process is often assisted by
68   software like what Kongsberg Oil & Gas Technologies offers in their
69   <a href="http://www.rational-reducer.com>Rational Reducer</a> package.
70 
71 
72   The SoLevelOfDetail node implements the "projected size" variety
73   level-of-detail technique (as opposed to the "object distance"
74   technique, as done by the SoLOD node).
75 
76   The node works by comparing the current projected size of the
77   smallest rectangle covering the bounding box of it's child geometry.
78 
79 
80   Along with this set of models of the same shape, a specification of
81   when to switch between them is also provided.
82 
83   Example scenegraph layout:
84 
85   \code
86   LevelOfDetail {
87      screenArea [ 2000, 500, 50 ]
88 
89      DEF version-0 Separator {
90        # most complex / detailed / heavy version of subgraph
91      }
92      DEF version-1 Separator {
93        # less complex version of subgraph
94      }
95      DEF version-2 Separator {
96        # even less complex version of subgraph
97      }
98      DEF version-3 Separator {
99        # simplest / "lightest" version of subgraph
100      }
101   }
102   \endcode
103 
104   The way the above sub-scenegraph would work would be the following:
105   if the rectangular area around the model's projected bounding box
106   covers \e more than 2000 pixels (meaning it will be up pretty close
107   to the camera), the most complex version of the model (\c version-0)
108   would be traversed (and rendered, of course). If the projected area
109   would be \e between 500 and 2000 pixels, the \c version-1 model
110   would be used. Ditto if the projected area was between 50 and 500
111   pixels, the \c version-2 version of the model would be
112   used. Finally, if the projected bounding box area would be \e less
113   than 50 square pixels, the presumably least detailed version of the
114   modeled would be used.
115 
116   (A common "trick" is to let the last of the SoLevelOfDetail node's
117   children be just an empty subgraph, so no geometry will be rendered
118   at all if the model is sufficiently far away. This will of course
119   have a positive effect on the total rendering time for the complete
120   scenegraph.)
121 
122   Note that the SoLevelOfDetail::screenArea vector will be influenced
123   by preceding SoComplexity nodes in the following way: if
124   SoComplexity::value is set from 0.0 up to 0.5, lower detail levels
125   than normal will be selected for traversal. If SoComplexity::value
126   is above 0.5, higher level details than normal will be used. An
127   SoComplexity::value equal to 1.0 will cause the first child of
128   SoLevelOfDetail to always be used.
129 
130 
131   As mentioned above, there is one other level-of-detail node in the
132   Coin library: SoLOD. The difference between that one and this is
133   just that instead of projected bounding box area, SoLOD uses the
134   distance between the camera and the object to find out when to
135   switch between the different model versions.
136 
137   Using SoLOD is faster, since figuring out the projected bounding box
138   area needs a certain amount of calculations. But using
139   SoLevelOfDetail is often "better", in the sense that it's really a
140   model's size and visibility in the viewport that determines whether
141   we could switch to a less complex version without losing enough
142   detail that it gives a noticable visual degradation.
143 
144   <b>FILE FORMAT/DEFAULTS:</b>
145   \code
146     LevelOfDetail {
147         screenArea 0
148     }
149   \endcode
150 
151   \sa SoLOD
152 */
153 
154 // *************************************************************************
155 
156 #include <Inventor/nodes/SoLevelOfDetail.h>
157 
158 #include <cstdlib>
159 
160 #include <Inventor/actions/SoAudioRenderAction.h>
161 #include <Inventor/actions/SoCallbackAction.h>
162 #include <Inventor/actions/SoGLRenderAction.h>
163 #include <Inventor/actions/SoGetBoundingBoxAction.h>
164 #include <Inventor/caches/SoBoundingBoxCache.h>
165 #include <Inventor/caches/SoBoundingBoxCache.h>
166 #include <Inventor/elements/SoCacheElement.h>
167 #include <Inventor/elements/SoComplexityElement.h>
168 #include <Inventor/elements/SoGLCacheContextElement.h>
169 #include <Inventor/elements/SoLocalBBoxMatrixElement.h>
170 #include <Inventor/elements/SoViewportRegionElement.h>
171 #include <Inventor/misc/SoChildList.h>
172 #include <Inventor/misc/SoState.h>
173 #include <Inventor/nodes/SoShape.h>
174 #include <Inventor/threads/SbStorage.h>
175 
176 #ifdef HAVE_CONFIG_H
177 #include "config.h"
178 #endif // HAVE_CONFIG_H
179 
180 #ifdef COIN_THREADSAFE
181 #include <Inventor/threads/SbMutex.h>
182 #endif // COIN_THREADSAFE
183 
184 #include "tidbitsp.h"
185 #include "nodes/SoSubNodeP.h"
186 
187 // *************************************************************************
188 
189 typedef struct {
190   SoGetBoundingBoxAction * bboxaction;
191 } so_lod_static_data;
192 
193 static void
so_lod_construct_data(void * closure)194 so_lod_construct_data(void * closure)
195 {
196   so_lod_static_data * data = (so_lod_static_data*) closure;
197   data->bboxaction = NULL;
198 }
199 
200 static void
so_lod_destruct_data(void * closure)201 so_lod_destruct_data(void * closure)
202 {
203   so_lod_static_data * data = (so_lod_static_data*) closure;
204   delete data->bboxaction;
205 }
206 
207 static SbStorage * so_lod_storage = NULL;
208 
209 // called from atexit
210 static void
so_lod_cleanup(void)211 so_lod_cleanup(void)
212 {
213   delete so_lod_storage;
214 }
215 
216 static SoGetBoundingBoxAction *
so_lod_get_bbox_action(void)217 so_lod_get_bbox_action(void)
218 {
219   so_lod_static_data * data = NULL;
220   data = (so_lod_static_data*) so_lod_storage->get();
221 
222   if (data->bboxaction == NULL) {
223     // The viewport region will be replaced every time the action is
224     // used, so we can just feed it a dummy here.
225     data->bboxaction = new SoGetBoundingBoxAction(SbViewportRegion());
226   }
227   return data->bboxaction;
228 }
229 
230 // *************************************************************************
231 
232 /*!
233   \var SoMFFloat SoLevelOfDetail::screenArea
234 
235   The screen area limits for the children. See usage example in main
236   class documentation of SoLevelOfDetail for an explanation of how
237   this vector should be set up correctly.
238 
239   By default this vector just contains a single value 0.0f.
240 */
241 
242 // *************************************************************************
243 
244 
245 #ifndef DOXYGEN_SKIP_THIS
246 
247 class SoLevelOfDetailP {
248 public:
249   SoBoundingBoxCache * bboxcache;
250 #ifdef COIN_THREADSAFE
251   // FIXME: a mutex for every instance seems a bit excessive,
252   // especially since MSWindows might have rather strict limits on the
253   // total amount of mutex resources a process (or even a user) can
254   // allocate. so consider making this a class-wide instance instead.
255   // -mortene.
256   SbMutex mutex;
257 #endif // COIN_THREADSAFE
258 
lock(void)259   void lock(void) {
260 #ifdef COIN_THREADSAFE
261     this->mutex.lock();
262 #endif // COIN_THREADSAFE
263  }
unlock(void)264   void unlock(void) {
265 #ifdef COIN_THREADSAFE
266     this->mutex.unlock();
267 #endif // COIN_THREADSAFE
268   }
269 };
270 
271 #endif // DOXYGEN_SKIP_THIS
272 
273 
274 SO_NODE_SOURCE(SoLevelOfDetail);
275 
276 #define PRIVATE(obj) ((obj)->pimpl)
277 
278 /*!
279   Default constructor.
280 */
SoLevelOfDetail(void)281 SoLevelOfDetail::SoLevelOfDetail(void)
282 {
283   this->commonConstructor();
284 }
285 
286 /*!
287   Constructor.
288 
289   The argument should be the approximate number of children which is
290   expected to be inserted below this node. The number need not be
291   exact, as it is only used as a hint for better memory resource
292   allocation.
293 */
SoLevelOfDetail(int numchildren)294 SoLevelOfDetail::SoLevelOfDetail(int numchildren)
295   : inherited(numchildren)
296 {
297   this->commonConstructor();
298 }
299 
300 // private
301 void
commonConstructor(void)302 SoLevelOfDetail::commonConstructor(void)
303 {
304   PRIVATE(this) = new SoLevelOfDetailP;
305   PRIVATE(this)->bboxcache = NULL;
306 
307   SO_NODE_INTERNAL_CONSTRUCTOR(SoLevelOfDetail);
308 
309   SO_NODE_ADD_FIELD(screenArea, (0));
310 }
311 
312 /*!
313   Destructor.
314 */
~SoLevelOfDetail()315 SoLevelOfDetail::~SoLevelOfDetail()
316 {
317   if (PRIVATE(this)->bboxcache) PRIVATE(this)->bboxcache->unref();
318   delete PRIVATE(this);
319 }
320 
321 // Documented in superclass.
322 void
initClass(void)323 SoLevelOfDetail::initClass(void)
324 {
325   SO_NODE_INTERNAL_INIT_CLASS(SoLevelOfDetail, SO_FROM_INVENTOR_1);
326 
327   so_lod_storage = new SbStorage(sizeof(so_lod_static_data),
328                                  so_lod_construct_data, so_lod_destruct_data);
329   coin_atexit((coin_atexit_f*) so_lod_cleanup, CC_ATEXIT_NORMAL);
330 }
331 
332 // Documented in superclass.
333 void
doAction(SoAction * action)334 SoLevelOfDetail::doAction(SoAction *action)
335 {
336   switch (action->getCurPathCode()) {
337   case SoAction::IN_PATH:
338     inherited::doAction(action); // normal path traversal
339     return;
340   case SoAction::OFF_PATH:
341     return; // this is a separator node, return.
342   case SoAction::BELOW_PATH:
343   case SoAction::NO_PATH:
344     break; // go on
345   default:
346     assert(0 && "unknown path code");
347     return;
348   }
349 
350   // for some strange reason, gcc (egcs-2.91.66) won't accept the code
351   // below inside a case (yes, I did use brackets).
352   // That's the reason for the strange switch/case above. pederb 19991116
353 
354   SoState * state = action->getState();
355   int n = this->getNumChildren();
356   if (n == 0) return;
357 
358   SbVec2s size;
359   SbBox3f bbox;
360   int i;
361   int idx = -1;
362   float projarea = 0.0f;
363 
364   SoComplexityTypeElement::Type complext = SoComplexityTypeElement::get(state);
365   float complexity = SbClamp(SoComplexityElement::get(state), 0.0f, 1.0f);
366 
367   if (n == 1) { idx = 0; goto traverse; }
368   if (complext == SoComplexityTypeElement::BOUNDING_BOX) { idx = n - 1; goto traverse; }
369   if (complexity == 0.0f) { idx = n - 1; goto traverse; }
370   if (complexity == 1.0f) { idx = 0; goto traverse; }
371   if (this->screenArea.getNum() == 0) { idx = 0; goto traverse; }
372 
373   if (!PRIVATE(this)->bboxcache || !PRIVATE(this)->bboxcache->isValid(state)) {
374     SoGetBoundingBoxAction * bboxAction = so_lod_get_bbox_action();
375 
376     bboxAction->setViewportRegion(SoViewportRegionElement::get(state));
377     // need to apply on the current path, not on the node, since we
378     // might need coordinates from the state. Also, we need to set the
379     // reset path so that we get the local bounding box for the nodes
380     // below this node.
381     bboxAction->setResetPath(action->getCurPath());
382     bboxAction->apply((SoPath*) action->getCurPath()); // find bbox of all children
383     bbox = bboxAction->getBoundingBox();
384   }
385   else {
386     bbox = PRIVATE(this)->bboxcache->getProjectedBox();
387   }
388   SoShape::getScreenSize(state, bbox, size);
389 
390   // The multiplication factor from the complexity setting is
391   // complexity+0.5 because the documented behavior of SoLevelOfDetail
392   // is to show lower detail levels than normal when
393   // SoComplexity::value < 0.5, and to show higher detail levels when
394   // SoComplexity::value > 0.5.
395   projarea = float(size[0]) * float(size[1]) * (complexity + 0.5f);
396 
397   // In case there are too few or too many screenArea values.
398   n = SbMin(n, this->screenArea.getNum());
399 
400   for (i = 0; i < n; i++) {
401     if (projarea > this->screenArea[i]) { idx = i; goto traverse; }
402   }
403 
404   // If we get here, projected area was lower than any of the
405   // screenArea value, so the last child should be traversed.
406   idx = this->getNumChildren() - 1;
407   // (fall through to traverse:)
408 
409  traverse:
410   this->getChildren()->traverse(action, idx);
411   return;
412 }
413 
414 // Documented in superclass.
415 void
callback(SoCallbackAction * action)416 SoLevelOfDetail::callback(SoCallbackAction *action)
417 {
418   SoLevelOfDetail::doAction((SoAction*)action);
419 }
420 
421 // Documented in superclass.
422 void
GLRender(SoGLRenderAction * action)423 SoLevelOfDetail::GLRender(SoGLRenderAction *action)
424 {
425   SoLevelOfDetail::doAction((SoAction*)action);
426   // don't auto cache LevelOfDetail nodes.
427   SoGLCacheContextElement::shouldAutoCache(action->getState(),
428                                            SoGLCacheContextElement::DONT_AUTO_CACHE);
429 }
430 
431 // Documented in superclass.
432 void
rayPick(SoRayPickAction * action)433 SoLevelOfDetail::rayPick(SoRayPickAction *action)
434 {
435   SoLevelOfDetail::doAction((SoAction*)action);
436 }
437 
438 // Documented in superclass.
439 void
audioRender(SoAudioRenderAction * action)440 SoLevelOfDetail::audioRender(SoAudioRenderAction * action)
441 {
442   /*
443      FIXME: Implement proper support for audio rendering. The
444      implementation will be similar to SoLOD, but will require
445      enabling some more elements for SoAudioRenderAction, as well as
446      rewriting this->doAction().
447 
448      The current implementation will render _all_ children instead of
449      just one of them.
450 
451      2003-02-05 thammer.
452    */
453   // let SoGroup traverse the children
454   inherited::audioRender(action);
455 }
456 
457 void
getBoundingBox(SoGetBoundingBoxAction * action)458 SoLevelOfDetail::getBoundingBox(SoGetBoundingBoxAction * action)
459 {
460   SoState * state = action->getState();
461 
462   SbXfBox3f childrenbbox;
463   SbBool childrencenterset;
464   SbVec3f childrencenter;
465 
466   SbBool iscaching = TRUE;
467 
468   switch (action->getCurPathCode()) {
469   case SoAction::OFF_PATH:
470   case SoAction::IN_PATH:
471     // can't cache if we're not traversing all children
472     iscaching = FALSE;
473     break;
474     return; // no need to do any more work
475   case SoAction::BELOW_PATH:
476   case SoAction::NO_PATH:
477     // check if this is a normal traversal
478     if (action->isInCameraSpace()) iscaching = FALSE;
479     break;
480   default:
481     iscaching = FALSE;
482     assert(0 && "unknown path code");
483     break;
484   }
485 
486   SbBool validcache = PRIVATE(this)->bboxcache && PRIVATE(this)->bboxcache->isValid(state);
487 
488   if (iscaching && validcache) {
489     SoCacheElement::addCacheDependency(state, PRIVATE(this)->bboxcache);
490     childrenbbox = PRIVATE(this)->bboxcache->getBox();
491     childrencenterset = PRIVATE(this)->bboxcache->isCenterSet();
492     childrencenter = PRIVATE(this)->bboxcache->getCenter();
493     if (PRIVATE(this)->bboxcache->hasLinesOrPoints()) {
494       SoBoundingBoxCache::setHasLinesOrPoints(state);
495     }
496   }
497   else {
498     // used to restore the bounding box after we have traversed children
499     SbXfBox3f abox = action->getXfBoundingBox();
500 
501     SbBool storedinvalid = FALSE;
502 
503     // always push since we update SoLocalBBoxMatrixElement
504     state->push();
505 
506     if (iscaching) {
507       storedinvalid = SoCacheElement::setInvalid(FALSE);
508 
509       // if we get here, we know bbox cache is not created or is invalid
510       PRIVATE(this)->lock();
511       if (PRIVATE(this)->bboxcache) PRIVATE(this)->bboxcache->unref();
512       PRIVATE(this)->bboxcache = new SoBoundingBoxCache(state);
513       PRIVATE(this)->bboxcache->ref();
514       PRIVATE(this)->unlock();
515       // set active cache to record cache dependencies
516       SoCacheElement::set(state, PRIVATE(this)->bboxcache);
517     }
518 
519     // the bounding box cache should be in the local coordinate system
520     SoLocalBBoxMatrixElement::makeIdentity(state);
521     action->getXfBoundingBox().makeEmpty();
522     action->getXfBoundingBox().setTransform(SbMatrix::identity());
523 
524     inherited::getBoundingBox(action);
525 
526     childrenbbox = action->getXfBoundingBox();
527     childrencenterset = action->isCenterSet();
528     if (childrencenterset) childrencenter = action->getCenter();
529 
530     action->getXfBoundingBox() = abox; // reset action bbox
531 
532     if (iscaching) {
533       PRIVATE(this)->bboxcache->set(childrenbbox, childrencenterset, childrencenter);
534     }
535     state->pop(); // we pushed before calculating the cache
536 
537     if (iscaching) {
538       SoCacheElement::setInvalid(storedinvalid);
539     }
540   }
541 
542   if (!childrenbbox.isEmpty()) {
543     action->extendBy(childrenbbox);
544     if (childrencenterset) {
545       // FIXME: shouldn't this assert() hold up? Investigate. 19990422 mortene.
546 #if 0 // disabled
547       assert(!action->isCenterSet());
548 #else
549       action->resetCenter();
550 #endif
551       action->setCenter(childrencenter, TRUE);
552     }
553   }
554 }
555 
556 // Doc from superclass.
557 void
notify(SoNotList * nl)558 SoLevelOfDetail::notify(SoNotList * nl)
559 {
560   if (nl->getLastField() != &this->screenArea) {
561     PRIVATE(this)->lock();
562     if (PRIVATE(this)->bboxcache) PRIVATE(this)->bboxcache->invalidate();
563     PRIVATE(this)->unlock();
564   }
565   inherited::notify(nl);
566 }
567 
568 #undef PRIVATE
569