DrapingCullSet& DrapingCullSet::get(const osg::Camera* cam) { static PerObjectFastMap<const osg::Camera*, DrapingCullSet> sets; // Known issue: it is possible for a draping cull set to be "orphaned" - this // would happen if the cull set were populated and then not used. This is a // very unlikely scenario (because the scene graph would have to change mid-cull) // but nevertheless possible. return sets.get(cam); }
// override. // Sorts the bin. This runs in the CULL thread after the CULL traversal has completed. void sortImplementation(osgUtil::RenderBin* bin) { osgUtil::RenderBin::RenderLeafList& leaves = bin->getRenderLeafList(); // first, sort the leaves: if ( _customSortFunctor && s_declutteringEnabledGlobally ) { // if there's a custom sorting function installed bin->copyLeavesFromStateGraphListToRenderLeafList(); std::sort( leaves.begin(), leaves.end(), SortContainer( *_customSortFunctor ) ); } else { // default behavior: bin->copyLeavesFromStateGraphListToRenderLeafList(); std::sort( leaves.begin(), leaves.end(), SortFrontToBackPreservingGeodeTraversalOrder() ); } // nothing to sort? bail out if ( leaves.size() == 0 ) return; // access the view-specific persistent data: osg::Camera* cam = bin->getStage()->getCamera(); PerCamInfo& local = _perCam.get( cam ); osg::Timer_t now = osg::Timer::instance()->tick(); if (local._firstFrame) { local._firstFrame = false; local._lastTimeStamp = now; } // calculate the elapsed time since the previous pass; we'll use this for // the animations float elapsedSeconds = osg::Timer::instance()->delta_s(local._lastTimeStamp, now); local._lastTimeStamp = now; // Reset the local re-usable containers local._passed.clear(); // drawables that pass occlusion test local._failed.clear(); // drawables that fail occlusion test local._used.clear(); // list of occupied bounding boxes in screen space // compute a window matrix so we can do window-space culling. If this is an RTT camera // with a reference camera attachment, we actually want to declutter in the window-space // of the reference camera. (e.g., for picking). const osg::Viewport* vp = cam->getViewport(); osg::Matrix windowMatrix = vp->computeWindowMatrix(); osg::Vec3f refCamScale(1.0f, 1.0f, 1.0f); osg::Matrix refCamScaleMat; osg::Matrix refWindowMatrix = windowMatrix; if ( cam->isRenderToTextureCamera() ) { osg::Camera* refCam = dynamic_cast<osg::Camera*>(cam->getUserData()); if ( refCam ) { const osg::Viewport* refVP = refCam->getViewport(); refCamScale.set( vp->width() / refVP->width(), vp->height() / refVP->height(), 1.0 ); refCamScaleMat.makeScale( refCamScale ); refWindowMatrix = refVP->computeWindowMatrix(); } } // Track the parent nodes of drawables that are obscured (and culled). Drawables // with the same parent node (typically a Geode) are considered to be grouped and // will be culled as a group. std::set<const osg::Node*> culledParents; const ScreenSpaceLayoutOptions& options = _context->_options; unsigned limit = *options.maxObjects(); bool snapToPixel = options.snapToPixel() == true; // Go through each leaf and test for visibility. // Enforce the "max objects" limit along the way. for(osgUtil::RenderBin::RenderLeafList::iterator i = leaves.begin(); i != leaves.end() && local._passed.size() < limit; ++i ) { bool visible = true; osgUtil::RenderLeaf* leaf = *i; const osg::Drawable* drawable = leaf->getDrawable(); const osg::Node* drawableParent = drawable->getParent(0); const ScreenSpaceLayoutData* layoutData = dynamic_cast<const ScreenSpaceLayoutData*>(drawable->getUserData()); // transform the bounding box of the drawable into window-space. osg::BoundingBox box = Utils::getBoundingBox(drawable); osg::Vec3f offset; osg::Quat rot; if (layoutData) { // local transformation data // and management of the label orientation (must be always readable) bool isText = dynamic_cast<const osgText::Text*>(drawable) != 0L; float angle = layoutData->_localRotationRad; if ( isText && (angle < - osg::PI / 2. || angle > osg::PI / 2.) ) { // avoid the label characters to be inverted: // use a symetric translation and adapt the rotation to be in the desired angles offset.set( -layoutData->_pixelOffset.x() - box.xMax() - box.xMin(), -layoutData->_pixelOffset.y() - box.yMax() - box.yMin(), 0.f ); angle -= osg::PI; } else { offset.set( layoutData->_pixelOffset.x(), layoutData->_pixelOffset.y(), 0.f ); } // handle the local translation box.xMin() += offset.x(); box.xMax() += offset.x(); box.yMin() += offset.y(); box.yMax() += offset.y(); // handle the local rotation if ( angle != 0.f ) { rot.makeRotate ( angle, osg::Vec3d(0, 0, 1) ); osg::Vec3f ld = rot * ( osg::Vec3f(box.xMin(), box.yMin(), 0.) ); osg::Vec3f lu = rot * ( osg::Vec3f(box.xMin(), box.yMax(), 0.) ); osg::Vec3f ru = rot * ( osg::Vec3f(box.xMax(), box.yMax(), 0.) ); osg::Vec3f rd = rot * ( osg::Vec3f(box.xMax(), box.yMin(), 0.) ); if ( angle > - osg::PI / 2. && angle < osg::PI / 2.) box.set( std::min(ld.x(), lu.x()), std::min(ld.y(), rd.y()), 0, std::max(rd.x(), ru.x()), std::max(lu.y(), ru.y()), 0 ); else box.set( std::min(ld.x(), lu.x()), std::min(lu.y(), ru.y()), 0, std::max(ld.x(), lu.x()), std::max(ld.y(), rd.y()), 0 ); } } static osg::Vec4d s_zero_w(0,0,0,1); osg::Matrix MVP = (*leaf->_modelview.get()) * (*leaf->_projection.get()); osg::Vec4d clip = s_zero_w * MVP; osg::Vec3d clip_ndc( clip.x()/clip.w(), clip.y()/clip.w(), clip.z()/clip.w() ); // if we are using a reference camera (like for picking), we do the decluttering in // its viewport so that they match. osg::Vec3f winPos = clip_ndc * windowMatrix; osg::Vec3f refWinPos = clip_ndc * refWindowMatrix; // The "declutter" box is the box we use to reserve screen space. // This must be unquantized regardless of whether snapToPixel is set. box.set( floor(refWinPos.x() + box.xMin()), floor(refWinPos.y() + box.yMin()), refWinPos.z(), ceil(refWinPos.x() + box.xMax()), ceil(refWinPos.y() + box.yMax()), refWinPos.z() ); // if snapping is enabled, only snap when the camera stops moving. bool quantize = snapToPixel; if ( quantize ) { DrawableInfo& info = local._memory[drawable]; if ( info._lastXY.x() == winPos.x() && info._lastXY.y() == winPos.y() ) { // Quanitize the window draw coordinates so mitigate text rendering filtering anomalies. // Drawing text glyphs on pixel boundaries mitigates aliasing. // Adding 0.5 will cause the GPU to sample the glyph texels exactly on center. winPos.x() = floor(winPos.x()) + 0.5; winPos.y() = floor(winPos.y()) + 0.5; } else { info._lastXY.set( winPos.x(), winPos.y() ); } } if ( s_declutteringEnabledGlobally ) { // A max priority => never occlude. float priority = layoutData ? layoutData->_priority : 0.0f; if ( priority == FLT_MAX ) { visible = true; } // if this leaf is already in a culled group, skip it. else if ( culledParents.find(drawableParent) != culledParents.end() ) { visible = false; } else { // weed out any drawables that are obscured by closer drawables. // TODO: think about a more efficient algorithm - right now we are just using // brute force to compare all bbox's for( std::vector<RenderLeafBox>::const_iterator j = local._used.begin(); j != local._used.end(); ++j ) { // only need a 2D test since we're in clip space bool isClear = box.xMin() > j->second.xMax() || box.xMax() < j->second.xMin() || box.yMin() > j->second.yMax() || box.yMax() < j->second.yMin(); // if there's an overlap (and the conflict isn't from the same drawable // parent, which is acceptable), then the leaf is culled. if ( !isClear && drawableParent != j->first ) { visible = false; break; } } } } if ( visible ) { // passed the test, so add the leaf's bbox to the "used" list, and add the leaf // to the final draw list. local._used.push_back( std::make_pair(drawableParent, box) ); local._passed.push_back( leaf ); } else { // culled, so put the parent in the parents list so that any future leaves // with the same parent will be trivially rejected culledParents.insert( drawable->getParent(0) ); local._failed.push_back( leaf ); } // modify the leaf's modelview matrix to correctly position it in the 2D ortho // projection when it's drawn later. We'll also preserve the scale. osg::Matrix newModelView; if ( rot.zeroRotation() ) { newModelView.makeTranslate( osg::Vec3f(winPos.x() + offset.x(), winPos.y() + offset.y(), 0) ); } else { offset = rot * offset; newModelView.makeTranslate( osg::Vec3f(winPos.x() + offset.x(), winPos.y() + offset.y(), 0) ); newModelView.preMultRotate( rot ); } newModelView.preMultScale( leaf->_modelview->getScale() * refCamScaleMat ); // Leaf modelview matrixes are shared (by objects in the traversal stack) so we // cannot just replace it unfortunately. Have to make a new one. Perhaps a nice // allocation pool is in order here leaf->_modelview = new osg::RefMatrix( newModelView ); } // copy the final draw list back into the bin, rejecting any leaves whose parents // are in the cull list. if ( s_declutteringEnabledGlobally ) { leaves.clear(); for( osgUtil::RenderBin::RenderLeafList::const_iterator i=local._passed.begin(); i != local._passed.end(); ++i ) { osgUtil::RenderLeaf* leaf = *i; const osg::Drawable* drawable = leaf->getDrawable(); if ( culledParents.find( drawable->getParent(0) ) == culledParents.end() ) { DrawableInfo& info = local._memory[drawable]; bool fullyIn = true; // scale in until at full scale: if ( info._lastScale != 1.0f ) { fullyIn = false; info._lastScale += elapsedSeconds / std::max(*options.inAnimationTime(), 0.001f); if ( info._lastScale > 1.0f ) info._lastScale = 1.0f; } if ( info._lastScale != 1.0f ) leaf->_modelview->preMult( osg::Matrix::scale(info._lastScale,info._lastScale,1) ); // fade in until at full alpha: if ( info._lastAlpha != 1.0f ) { fullyIn = false; info._lastAlpha += elapsedSeconds / std::max(*options.inAnimationTime(), 0.001f); if ( info._lastAlpha > 1.0f ) info._lastAlpha = 1.0f; } leaf->_depth = info._lastAlpha; leaves.push_back( leaf ); } else { local._failed.push_back(leaf); } } // next, go through the FAILED list and sort them into failure bins so we can draw // them using a different technique if necessary. for( osgUtil::RenderBin::RenderLeafList::const_iterator i=local._failed.begin(); i != local._failed.end(); ++i ) { osgUtil::RenderLeaf* leaf = *i; const osg::Drawable* drawable = leaf->getDrawable(); DrawableInfo& info = local._memory[drawable]; bool isText = dynamic_cast<const osgText::Text*>(drawable) != 0L; bool isBbox = dynamic_cast<const osgEarth::Annotation::BboxDrawable*>(drawable) != 0L; bool fullyOut = true; if ( info._lastScale != *options.minAnimationScale() ) { fullyOut = false; info._lastScale -= elapsedSeconds / std::max(*options.outAnimationTime(), 0.001f); if ( info._lastScale < *options.minAnimationScale() ) info._lastScale = *options.minAnimationScale(); } if ( info._lastAlpha != *options.minAnimationAlpha() ) { fullyOut = false; info._lastAlpha -= elapsedSeconds / std::max(*options.outAnimationTime(), 0.001f); if ( info._lastAlpha < *options.minAnimationAlpha() ) info._lastAlpha = *options.minAnimationAlpha(); } leaf->_depth = info._lastAlpha; if ( (!isText && !isBbox) || !fullyOut ) { if ( info._lastAlpha > 0.01f && info._lastScale >= 0.0f ) { leaves.push_back( leaf ); // scale it: if ( info._lastScale != 1.0f ) leaf->_modelview->preMult( osg::Matrix::scale(info._lastScale,info._lastScale,1) ); } } } } }
// override. // Sorts the bin. This runs in the CULL thread after the CULL traversal has completed. void sortImplementation(osgUtil::RenderBin* bin) { osgUtil::RenderBin::RenderLeafList& leaves = bin->getRenderLeafList(); // first, sort the leaves: if ( _customSortFunctor && s_enabledGlobally ) { // if there's a custom sorting function installed bin->copyLeavesFromStateGraphListToRenderLeafList(); std::sort( leaves.begin(), leaves.end(), SortContainer( *_customSortFunctor ) ); } else { // default behavior: bin->copyLeavesFromStateGraphListToRenderLeafList(); std::sort( leaves.begin(), leaves.end(), SortFrontToBackPreservingGeodeTraversalOrder() ); } // nothing to sort? bail out if ( leaves.size() == 0 ) return; // access the view-specific persistent data: osg::Camera* cam = bin->getStage()->getCamera(); PerCamInfo& local = _perCam.get( cam ); osg::Timer_t now = osg::Timer::instance()->tick(); if (local._firstFrame) { local._firstFrame = false; local._lastTimeStamp = now; } // calculate the elapsed time since the previous pass; we'll use this for // the animations float elapsedSeconds = osg::Timer::instance()->delta_s(local._lastTimeStamp, now); local._lastTimeStamp = now; // Reset the local re-usable containers local._passed.clear(); // drawables that pass occlusion test local._failed.clear(); // drawables that fail occlusion test local._used.clear(); // list of occupied bounding boxes in screen space // compute a window matrix so we can do window-space culling. If this is an RTT camera // with a reference camera attachment, we actually want to declutter in the window-space // of the reference camera. const osg::Viewport* vp = cam->getViewport(); osg::Matrix windowMatrix = vp->computeWindowMatrix(); osg::Vec3f refCamScale(1.0f, 1.0f, 1.0f); osg::Matrix refCamScaleMat; osg::Matrix refWindowMatrix = windowMatrix; if ( cam->isRenderToTextureCamera() ) { osg::Camera* refCam = dynamic_cast<osg::Camera*>(cam->getUserData()); if ( refCam ) { const osg::Viewport* refVP = refCam->getViewport(); refCamScale.set( vp->width() / refVP->width(), vp->height() / refVP->height(), 1.0 ); refCamScaleMat.makeScale( refCamScale ); refWindowMatrix = refVP->computeWindowMatrix(); } } // Track the parent nodes of drawables that are obscured (and culled). Drawables // with the same parent node (typically a Geode) are considered to be grouped and // will be culled as a group. std::set<const osg::Node*> culledParents; const DeclutteringOptions& options = _context->_options; unsigned limit = *options.maxObjects(); // Go through each leaf and test for visibility. // Enforce the "max objects" limit along the way. for(osgUtil::RenderBin::RenderLeafList::iterator i = leaves.begin(); i != leaves.end() && local._passed.size() < limit; ++i ) { bool visible = true; osgUtil::RenderLeaf* leaf = *i; const osg::Drawable* drawable = leaf->getDrawable(); const osg::Node* drawableParent = drawable->getParent(0); // transform the bounding box of the drawable into window-space. osg::BoundingBox box = Utils::getBoundingBox(drawable); static osg::Vec4d s_zero_w(0,0,0,1); osg::Matrix MVP = (*leaf->_modelview.get()) * (*leaf->_projection.get()); osg::Vec4d clip = s_zero_w * MVP; osg::Vec3d clip_ndc( clip.x()/clip.w(), clip.y()/clip.w(), clip.z()/clip.w() ); osg::Vec3f winPos = clip_ndc * windowMatrix; // this accounts for the size difference when using a reference camera (RTT/picking) box.xMin() *= refCamScale.x(); box.xMax() *= refCamScale.x(); box.yMin() *= refCamScale.y(); box.yMax() *= refCamScale.y(); osg::Vec2f offset( -box.xMin(), -box.yMin() ); box.set( winPos.x() + box.xMin(), winPos.y() + box.yMin(), winPos.z(), winPos.x() + box.xMax(), winPos.y() + box.yMax(), winPos.z() ); // if this leaf is already in a culled group, skip it. if ( s_enabledGlobally ) { if ( culledParents.find(drawableParent) != culledParents.end() ) { visible = false; } else { // weed out any drawables that are obscured by closer drawables. // TODO: think about a more efficient algorithm - right now we are just using // brute force to compare all bbox's for( std::vector<RenderLeafBox>::const_iterator j = local._used.begin(); j != local._used.end(); ++j ) { // only need a 2D test since we're in clip space bool isClear = box.xMin() > j->second.xMax() || box.xMax() < j->second.xMin() || box.yMin() > j->second.yMax() || box.yMax() < j->second.yMin(); // if there's an overlap (and the conflict isn't from the same drawable // parent, which is acceptable), then the leaf is culled. if ( !isClear && drawableParent != j->first ) { visible = false; break; } } } } if ( visible ) { // passed the test, so add the leaf's bbox to the "used" list, and add the leaf // to the final draw list. //local._used.push_back( std::make_pair(drawableParent, box) ); local._used.push_back( std::make_pair(drawableParent, box) ); local._passed.push_back( leaf ); } else { // culled, so put the parent in the parents list so that any future leaves // with the same parent will be trivially rejected culledParents.insert( drawable->getParent(0) ); local._failed.push_back( leaf ); } // modify the leaf's modelview matrix to correctly position it in the 2D ortho // projection when it's drawn later. We'll also preserve the scale. osg::Matrix newModelView; newModelView.makeTranslate( box.xMin() + offset.x(), box.yMin() + offset.y(), 0 ); newModelView.preMultScale( leaf->_modelview->getScale() * refCamScaleMat ); // Leaf modelview matrixes are shared (by objects in the traversal stack) so we // cannot just replace it unfortunately. Have to make a new one. Perhaps a nice // allocation pool is in order here leaf->_modelview = new osg::RefMatrix( newModelView ); } // copy the final draw list back into the bin, rejecting any leaves whose parents // are in the cull list. if ( s_enabledGlobally ) { leaves.clear(); for( osgUtil::RenderBin::RenderLeafList::const_iterator i=local._passed.begin(); i != local._passed.end(); ++i ) { osgUtil::RenderLeaf* leaf = *i; const osg::Drawable* drawable = leaf->getDrawable(); if ( culledParents.find( drawable->getParent(0) ) == culledParents.end() ) { DrawableInfo& info = local._memory[drawable]; bool fullyIn = true; // scale in until at full scale: if ( info._lastScale != 1.0f ) { fullyIn = false; info._lastScale += elapsedSeconds / std::max(*options.inAnimationTime(), 0.001f); if ( info._lastScale > 1.0f ) info._lastScale = 1.0f; } if ( info._lastScale != 1.0f ) leaf->_modelview->preMult( osg::Matrix::scale(info._lastScale,info._lastScale,1) ); // fade in until at full alpha: if ( info._lastAlpha != 1.0f ) { fullyIn = false; info._lastAlpha += elapsedSeconds / std::max(*options.inAnimationTime(), 0.001f); if ( info._lastAlpha > 1.0f ) info._lastAlpha = 1.0f; } leaf->_depth = info._lastAlpha; leaves.push_back( leaf ); } else { local._failed.push_back(leaf); } } // next, go through the FAILED list and sort them into failure bins so we can draw // them using a different technique if necessary. for( osgUtil::RenderBin::RenderLeafList::const_iterator i=local._failed.begin(); i != local._failed.end(); ++i ) { osgUtil::RenderLeaf* leaf = *i; const osg::Drawable* drawable = leaf->getDrawable(); DrawableInfo& info = local._memory[drawable]; bool isText = dynamic_cast<const osgText::Text*>(drawable) != 0L; bool fullyOut = true; if ( info._lastScale != *options.minAnimationScale() ) { fullyOut = false; info._lastScale -= elapsedSeconds / std::max(*options.outAnimationTime(), 0.001f); if ( info._lastScale < *options.minAnimationScale() ) info._lastScale = *options.minAnimationScale(); } if ( info._lastAlpha != *options.minAnimationAlpha() ) { fullyOut = false; info._lastAlpha -= elapsedSeconds / std::max(*options.outAnimationTime(), 0.001f); if ( info._lastAlpha < *options.minAnimationAlpha() ) info._lastAlpha = *options.minAnimationAlpha(); } leaf->_depth = info._lastAlpha; if ( !isText || !fullyOut ) { if ( info._lastAlpha > 0.01f && info._lastScale >= 0.0f ) { leaves.push_back( leaf ); // scale it: if ( info._lastScale != 1.0f ) leaf->_modelview->preMult( osg::Matrix::scale(info._lastScale,info._lastScale,1) ); } } } } }