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