/**************************************************************************\ * * This file is part of the Coin 3D visualization library. * Copyright (C) 1998-2007 by Systems in Motion. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * ("GPL") version 2 as published by the Free Software Foundation. * See the file LICENSE.GPL at the root directory of this source * distribution for additional information about the GNU GPL. * * For using Coin with software that can not be combined with the GNU * GPL, and for taking advantage of the additional benefits of our * support services, please contact Systems in Motion about acquiring * a Coin Professional Edition License. * * See http://www.coin3d.org/ for more information. * * Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY. * http://www.sim.no/ sales@sim.no coin-support@coin3d.org * \**************************************************************************/ /*! \class SoLevelOfDetail SoLevelOfDetail.h Inventor/nodes/SoLevelOfDetail.h \brief The SoLevelOfDetail class is used to choose a child based on projected size. \ingroup nodes A level-of-detail mechanism is typically used by application programmers to assist the library in speeding up the rendering. The way a level-of-detail mechanism works is basically like this: Several versions of varying complexity of \e the \e same geometry / shape is provided by the application programmer in sorted order from "most complex" to "least complex" (where "complex" in this context should be taken to mean more or less detailed in the number of polygons / shapes used for rendering it). The run-time rendering system then, upon scenegraph traversal, will on-the-fly calculate either the distance from the camera to the 3D-model in question, or the number of pixels in the screen projection of the 3D-model. This value is then used to decide which version of the model to actually render: as the model is moved farther away from the camera, a less detailed version of the model is used. And vice versa, as the model moves closer to the camera, more and more detailed versions of it are rendered. This is under many different circumstances a very effective way to let the application programmer assist to \e profoundly optimize the rendering of her 3D-scene. There is of course a trade-off with the level-of-detail technique: more versions of the same 3D model means the scenegraph will use up more application memory resources. Also, generating the set of less and less detailed versions of a 3D model from the original is often not a trivial task to do properly. The process is often assisted by software like what Systems in Motion offers in their FILE FORMAT/DEFAULTS: \code LevelOfDetail { screenArea 0 } \endcode \sa SoLOD */ // ************************************************************************* #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif // HAVE_CONFIG_H #ifdef COIN_THREADSAFE #include #endif // COIN_THREADSAFE // ************************************************************************* typedef struct { SoGetBoundingBoxAction * bboxaction; } so_lod_static_data; static void so_lod_construct_data(void * closure) { so_lod_static_data * data = (so_lod_static_data*) closure; data->bboxaction = NULL; } static void so_lod_destruct_data(void * closure) { so_lod_static_data * data = (so_lod_static_data*) closure; delete data->bboxaction; } static SbStorage * so_lod_storage = NULL; // called from atexit static void so_lod_cleanup(void) { delete so_lod_storage; } static SoGetBoundingBoxAction * so_lod_get_bbox_action(void) { so_lod_static_data * data = NULL; data = (so_lod_static_data*) so_lod_storage->get(); if (data->bboxaction == NULL) { // The viewport region will be replaced every time the action is // used, so we can just feed it a dummy here. data->bboxaction = new SoGetBoundingBoxAction(SbViewportRegion()); } return data->bboxaction; } // ************************************************************************* /*! \var SoMFFloat SoLevelOfDetail::screenArea The screen area limits for the children. See usage example in main class documentation of SoLevelOfDetail for an explanation of how this vector should be set up correctly. By default this vector just contains a single value 0.0f. */ // ************************************************************************* #ifndef DOXYGEN_SKIP_THIS class SoLevelOfDetailP { public: SoBoundingBoxCache * bboxcache; #ifdef COIN_THREADSAFE SbMutex mutex; #endif // COIN_THREADSAFE void lock(void) { #ifdef COIN_THREADSAFE this->mutex.lock(); #endif // COIN_THREADSAFE } void unlock(void) { #ifdef COIN_THREADSAFE this->mutex.unlock(); #endif // COIN_THREADSAFE } }; #endif // DOXYGEN_SKIP_THIS SO_NODE_SOURCE(SoLevelOfDetail); #undef THIS #define THIS this->pimpl /*! Default constructor. */ SoLevelOfDetail::SoLevelOfDetail(void) { this->commonConstructor(); } /*! Constructor. The argument should be the approximate number of children which is expected to be inserted below this node. The number need not be exact, as it is only used as a hint for better memory resource allocation. */ SoLevelOfDetail::SoLevelOfDetail(int numchildren) : inherited(numchildren) { this->commonConstructor(); } // private void SoLevelOfDetail::commonConstructor(void) { THIS = new SoLevelOfDetailP; THIS->bboxcache = NULL; SO_NODE_INTERNAL_CONSTRUCTOR(SoLevelOfDetail); SO_NODE_ADD_FIELD(screenArea, (0)); } /*! Destructor. */ SoLevelOfDetail::~SoLevelOfDetail() { if (THIS->bboxcache) THIS->bboxcache->unref(); delete THIS; } // Documented in superclass. void SoLevelOfDetail::initClass(void) { SO_NODE_INTERNAL_INIT_CLASS(SoLevelOfDetail, SO_FROM_INVENTOR_1); so_lod_storage = new SbStorage(sizeof(so_lod_static_data), so_lod_construct_data, so_lod_destruct_data); coin_atexit((coin_atexit_f*) so_lod_cleanup, CC_ATEXIT_NORMAL); } // Documented in superclass. void SoLevelOfDetail::doAction(SoAction *action) { switch (action->getCurPathCode()) { case SoAction::IN_PATH: inherited::doAction(action); // normal path traversal return; case SoAction::OFF_PATH: return; // this is a separator node, return. case SoAction::BELOW_PATH: case SoAction::NO_PATH: break; // go on default: assert(0 && "unknown path code"); return; } // for some strange reason, gcc (egcs-2.91.66) won't accept the code // below inside a case (yes, I did use brackets). // That's the reason for the strange switch/case above. pederb 19991116 SoState * state = action->getState(); int n = this->getNumChildren(); if (n == 0) return; SbVec2s size; SbBox3f bbox; int i; int idx = -1; float projarea = 0.0f; SoComplexityTypeElement::Type complext = SoComplexityTypeElement::get(state); float complexity = SbClamp(SoComplexityElement::get(state), 0.0f, 1.0f); if (n == 1) { idx = 0; goto traverse; } if (complext == SoComplexityTypeElement::BOUNDING_BOX) { idx = n - 1; goto traverse; } if (complexity == 0.0f) { idx = n - 1; goto traverse; } if (complexity == 1.0f) { idx = 0; goto traverse; } if (this->screenArea.getNum() == 0) { idx = 0; goto traverse; } if (!THIS->bboxcache || !THIS->bboxcache->isValid(state)) { SoGetBoundingBoxAction * bboxAction = so_lod_get_bbox_action(); bboxAction->setViewportRegion(SoViewportRegionElement::get(state)); // need to apply on the current path, not on the node, since we // might need coordinates from the state. Also, we need to set the // reset path so that we get the local bounding box for the nodes // below this node. bboxAction->setResetPath(action->getCurPath()); bboxAction->apply((SoPath*) action->getCurPath()); // find bbox of all children bbox = bboxAction->getBoundingBox(); } else { bbox = THIS->bboxcache->getProjectedBox(); } SoShape::getScreenSize(state, bbox, size); // The multiplication factor from the complexity setting is // complexity+0.5 because the documented behavior of SoLevelOfDetail // is to show lower detail levels than normal when // SoComplexity::value < 0.5, and to show higher detail levels when // SoComplexity::value > 0.5. projarea = float(size[0]) * float(size[1]) * (complexity + 0.5f); // In case there are too few or too many screenArea values. n = SbMin(n, this->screenArea.getNum()); for (i = 0; i < n; i++) { if (projarea > this->screenArea[i]) { idx = i; goto traverse; } } // If we get here, projected area was lower than any of the // screenArea value, so the last child should be traversed. idx = this->getNumChildren() - 1; // (fall through to traverse:) traverse: this->getChildren()->traverse(action, idx); return; } // Documented in superclass. void SoLevelOfDetail::callback(SoCallbackAction *action) { SoLevelOfDetail::doAction((SoAction*)action); } // Documented in superclass. void SoLevelOfDetail::GLRender(SoGLRenderAction *action) { SoLevelOfDetail::doAction((SoAction*)action); // don't auto cache LevelOfDetail nodes. SoGLCacheContextElement::shouldAutoCache(action->getState(), SoGLCacheContextElement::DONT_AUTO_CACHE); } // Documented in superclass. void SoLevelOfDetail::rayPick(SoRayPickAction *action) { SoLevelOfDetail::doAction((SoAction*)action); } // Documented in superclass. void SoLevelOfDetail::audioRender(SoAudioRenderAction * action) { /* FIXME: Implement proper support for audio rendering. The implementation will be similar to SoLOD, but will require enabling some more elements for SoAudioRenderAction, as well as rewriting this->doAction(). The current implementation will render _all_ children instead of just one of them. 2003-02-05 thammer. */ // let SoGroup traverse the children inherited::audioRender(action); } void SoLevelOfDetail::getBoundingBox(SoGetBoundingBoxAction * action) { SoState * state = action->getState(); SbXfBox3f childrenbbox; SbBool childrencenterset; SbVec3f childrencenter; SbBool iscaching = TRUE; switch (action->getCurPathCode()) { case SoAction::OFF_PATH: case SoAction::IN_PATH: // can't cache if we're not traversing all children iscaching = FALSE; break; return; // no need to do any more work case SoAction::BELOW_PATH: case SoAction::NO_PATH: // check if this is a normal traversal if (action->isInCameraSpace()) iscaching = FALSE; break; default: iscaching = FALSE; assert(0 && "unknown path code"); break; } SbBool validcache = THIS->bboxcache && THIS->bboxcache->isValid(state); if (iscaching && validcache) { SoCacheElement::addCacheDependency(state, THIS->bboxcache); childrenbbox = THIS->bboxcache->getBox(); childrencenterset = THIS->bboxcache->isCenterSet(); childrencenter = THIS->bboxcache->getCenter(); if (THIS->bboxcache->hasLinesOrPoints()) { SoBoundingBoxCache::setHasLinesOrPoints(state); } } else { // used to restore the bounding box after we have traversed children SbXfBox3f abox = action->getXfBoundingBox(); SbBool storedinvalid = FALSE; // always push since we update SoLocalBBoxMatrixElement state->push(); if (iscaching) { storedinvalid = SoCacheElement::setInvalid(FALSE); // if we get here, we know bbox cache is not created or is invalid THIS->lock(); if (THIS->bboxcache) THIS->bboxcache->unref(); THIS->bboxcache = new SoBoundingBoxCache(state); THIS->bboxcache->ref(); THIS->unlock(); // set active cache to record cache dependencies SoCacheElement::set(state, THIS->bboxcache); } // the bounding box cache should be in the local coordinate system SoLocalBBoxMatrixElement::makeIdentity(state); action->getXfBoundingBox().makeEmpty(); action->getXfBoundingBox().setTransform(SbMatrix::identity()); inherited::getBoundingBox(action); childrenbbox = action->getXfBoundingBox(); childrencenterset = action->isCenterSet(); if (childrencenterset) childrencenter = action->getCenter(); action->getXfBoundingBox() = abox; // reset action bbox if (iscaching) { THIS->bboxcache->set(childrenbbox, childrencenterset, childrencenter); } state->pop(); // we pushed before calculating the cache if (iscaching) { SoCacheElement::setInvalid(storedinvalid); } } if (!childrenbbox.isEmpty()) { action->extendBy(childrenbbox); if (childrencenterset) { // FIXME: shouldn't this assert() hold up? Investigate. 19990422 mortene. #if 0 // disabled assert(!action->isCenterSet()); #else action->resetCenter(); #endif action->setCenter(childrencenter, TRUE); } } } // Doc from superclass. void SoLevelOfDetail::notify(SoNotList * nl) { if (nl->getLastField() != &this->screenArea) { THIS->lock(); if (THIS->bboxcache) THIS->bboxcache->invalidate(); THIS->unlock(); } inherited::notify(nl); }