688 lines
29 KiB
Plaintext
688 lines
29 KiB
Plaintext
|
/* -*-c++-*- */
|
||
|
/* osgEarth - Geospatial SDK for OpenSceneGraph
|
||
|
* Copyright 2020 Pelican Mapping
|
||
|
* http://osgearth.org
|
||
|
*
|
||
|
* osgEarth is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||
|
* IN THE SOFTWARE.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Lesser General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||
|
*/
|
||
|
#ifndef OSGEARTH_SCREEN_SPACE_LAYOUT_DECLUTTER_H
|
||
|
#define OSGEARTH_SCREEN_SPACE_LAYOUT_DECLUTTER_H 1
|
||
|
|
||
|
#include <osgEarth/ScreenSpaceLayoutImpl>
|
||
|
#include <osgEarth/CameraUtils>
|
||
|
|
||
|
#define FADE_UNIFORM_NAME "oe_declutter_fade"
|
||
|
|
||
|
namespace osgEarth { namespace Internal
|
||
|
{
|
||
|
using namespace osgEarth;
|
||
|
|
||
|
static const char* s_faderFS =
|
||
|
"uniform float " FADE_UNIFORM_NAME ";\n"
|
||
|
"void oe_declutter_apply_fade(inout vec4 color) { \n"
|
||
|
" color.a *= " FADE_UNIFORM_NAME ";\n"
|
||
|
"}\n";
|
||
|
|
||
|
// records information about each drawable.
|
||
|
// TODO: a way to clear out this list when drawables go away
|
||
|
struct DrawableInfo
|
||
|
{
|
||
|
DrawableInfo() : _lastAlpha(1.0f), _lastScale(1.0f), _frame(0u), _visible(true) { }
|
||
|
float _lastAlpha, _lastScale;
|
||
|
unsigned _frame;
|
||
|
bool _visible;
|
||
|
};
|
||
|
|
||
|
using DrawableMemory = std::unordered_map<const osg::Drawable*, DrawableInfo>;
|
||
|
|
||
|
typedef std::pair<const osg::Node*, osg::BoundingBox> RenderLeafBox;
|
||
|
|
||
|
// Data structure stored one-per-View.
|
||
|
struct PerCamInfo
|
||
|
{
|
||
|
PerCamInfo() : _lastTimeStamp(0), _firstFrame(true) { }
|
||
|
|
||
|
// remembers the state of each drawable from the previous pass
|
||
|
DrawableMemory _memory;
|
||
|
|
||
|
// re-usable structures (to avoid unnecessary re-allocation)
|
||
|
osgUtil::RenderBin::RenderLeafList _passed;
|
||
|
osgUtil::RenderBin::RenderLeafList _failed;
|
||
|
std::vector<RenderLeafBox> _used;
|
||
|
|
||
|
// time stamp of the previous pass, for calculating animation speed
|
||
|
osg::Timer_t _lastTimeStamp;
|
||
|
bool _firstFrame;
|
||
|
osg::Matrix _lastCamVPW;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* A custom RenderLeaf sorting algorithm for decluttering objects.
|
||
|
*
|
||
|
* First we sort the leaves front-to-back so that objects closer to the camera
|
||
|
* get higher priority. If you have installed a custom sorting functor,
|
||
|
* this is used instead.
|
||
|
*
|
||
|
* Next, we go though all the drawables and remove any that try to occupy
|
||
|
* already-occupied real estate in the 2D viewport. Objects that fail the test
|
||
|
* go on a "failed" list and are either completely removed from the display
|
||
|
* or transitioned to a secondary visual state (scaled down, alpha'd down)
|
||
|
* dependeing on the options setup.
|
||
|
*
|
||
|
* Drawables with the same parent (i.e., Geode) are treated as a group. As
|
||
|
* soon as one passes the occlusion test, all its siblings will automatically
|
||
|
* pass as well.
|
||
|
*/
|
||
|
struct /*internal*/ DeclutterImplementation : public osgUtil::RenderBin::SortCallback
|
||
|
{
|
||
|
DeclutterSortFunctor* _customSortFunctor;
|
||
|
ScreenSpaceLayoutContext* _context;
|
||
|
|
||
|
PerObjectFastMap<osg::Camera*, PerCamInfo> _perCam;
|
||
|
|
||
|
/**
|
||
|
* Constructs the new sorter.
|
||
|
* @param f Custom declutter sorting predicate. Pass NULL to use the
|
||
|
* default sorter (sort by distance-to-camera).
|
||
|
*/
|
||
|
DeclutterImplementation( ScreenSpaceLayoutContext* context, DeclutterSortFunctor* f = 0L )
|
||
|
: _context(context), _customSortFunctor(f)
|
||
|
{
|
||
|
//nop
|
||
|
}
|
||
|
|
||
|
// override.
|
||
|
// Sorts the bin. This runs in the CULL thread after the CULL traversal has completed.
|
||
|
void sortImplementation(osgUtil::RenderBin* bin)
|
||
|
{
|
||
|
const ScreenSpaceLayoutOptions& options = _context->_options;
|
||
|
|
||
|
osgUtil::RenderBin::RenderLeafList& leaves = bin->getRenderLeafList();
|
||
|
|
||
|
bin->copyLeavesFromStateGraphListToRenderLeafList();
|
||
|
|
||
|
// first, sort the leaves:
|
||
|
if ( _customSortFunctor && ScreenSpaceLayout::globallyEnabled )
|
||
|
{
|
||
|
// if there's a custom sorting function installed
|
||
|
std::sort( leaves.begin(), leaves.end(), SortContainer( *_customSortFunctor ) );
|
||
|
}
|
||
|
else if (options.sortByDistance() == true)
|
||
|
{
|
||
|
// default behavior:
|
||
|
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();
|
||
|
|
||
|
// bail out if this camera is a master camera with no GC
|
||
|
// (e.g., in a multi-screen layout)
|
||
|
if (cam == NULL || (cam->getGraphicsContext() == NULL && !cam->isRenderToTextureCamera()))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
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 the camera is actually an RTT slave camera, it's our picker, and we need to
|
||
|
// adjust the scale to match it.
|
||
|
if (CameraUtils::isPickCamera(cam) &&
|
||
|
cam->getView() &&
|
||
|
cam->getView()->getCamera())
|
||
|
{
|
||
|
osg::Camera* parentCam = cam->getView()->getCamera();
|
||
|
const osg::Viewport* refVP = parentCam->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;
|
||
|
|
||
|
unsigned limit = *options.maxObjects();
|
||
|
|
||
|
bool snapToPixel = options.snapToPixel() == true;
|
||
|
|
||
|
osg::Matrix camVPW;
|
||
|
camVPW.postMult(cam->getViewMatrix());
|
||
|
camVPW.postMult(cam->getProjectionMatrix());
|
||
|
camVPW.postMult(refWindowMatrix);
|
||
|
|
||
|
// has the camera moved?
|
||
|
bool camChanged = camVPW != local._lastCamVPW;
|
||
|
local._lastCamVPW = camVPW;
|
||
|
|
||
|
std::unordered_set<std::string> uniqueText;
|
||
|
|
||
|
// 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->getNumParents()? drawable->getParent(0) : 0L;
|
||
|
|
||
|
const ScreenSpaceLayoutData* layoutData = dynamic_cast<const ScreenSpaceLayoutData*>(drawable->getUserData());
|
||
|
|
||
|
// transform the bounding box of the drawable into window-space.
|
||
|
osg::BoundingBox box = drawable->getBoundingBox();
|
||
|
|
||
|
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 = 0.0f;
|
||
|
if (layoutData->getRotationDegrees() != 0.0f)
|
||
|
{
|
||
|
angle = deg2rad(layoutData->getRotationDegrees());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
osg::Vec3d loc = layoutData->getAnchorPoint() * camVPW;
|
||
|
osg::Vec3d proj = layoutData->getProjPoint() * camVPW;
|
||
|
proj -= loc;
|
||
|
angle = atan2(proj.y(), proj.x());
|
||
|
}
|
||
|
|
||
|
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 += angle < -osg::PI_2? osg::PI : -osg::PI; // JD #1029
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
offset.set( layoutData->_pixelOffset.x(), layoutData->_pixelOffset.y(), 0.f );
|
||
|
}
|
||
|
|
||
|
// 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( osg::minimum(ld.x(), lu.x()), osg::minimum(ld.y(), rd.y()), 0,
|
||
|
osg::maximum(rd.x(), ru.x()), osg::maximum(lu.y(), ru.y()), 0 );
|
||
|
else
|
||
|
box.set( osg::minimum(ld.x(), lu.x()), osg::minimum(lu.y(), ru.y()), 0,
|
||
|
osg::maximum(ld.x(), lu.x()), osg::maximum(ld.y(), rd.y()), 0 );
|
||
|
}
|
||
|
|
||
|
offset = refCamScaleMat * offset;
|
||
|
|
||
|
// handle the local translation
|
||
|
box.xMin() += offset.x();
|
||
|
box.xMax() += offset.x();
|
||
|
box.yMin() += offset.y();
|
||
|
box.yMax() += offset.y();
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
// Expand the box if this object is currently not visible, so that it takes a little
|
||
|
// more room for it to before visible once again.
|
||
|
DrawableInfo& info = local._memory[drawable];
|
||
|
float buffer = info._visible ? 1.0f : 3.0f;
|
||
|
|
||
|
// 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())-buffer,
|
||
|
floor(refWinPos.y() + box.yMin())-buffer,
|
||
|
refWinPos.z(),
|
||
|
ceil(refWinPos.x() + box.xMax())+buffer,
|
||
|
ceil(refWinPos.y() + box.yMax())+buffer,
|
||
|
refWinPos.z() );
|
||
|
|
||
|
// if snapping is enabled, only snap when the camera stops moving.
|
||
|
bool quantize = snapToPixel;
|
||
|
if ( quantize && !camChanged )
|
||
|
{
|
||
|
// Quanitize the window draw coordinates to 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;
|
||
|
}
|
||
|
|
||
|
if ( ScreenSpaceLayout::globallyEnabled )
|
||
|
{
|
||
|
// 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 ( drawableParent != 0L && 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 && !drawable->getName().empty())
|
||
|
{
|
||
|
auto r = uniqueText.emplace(drawable->getName());
|
||
|
|
||
|
if (layoutData && layoutData->_unique && r.second == false)
|
||
|
{
|
||
|
visible = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( visible )
|
||
|
{
|
||
|
// passed the test, so add the leaf's bbox to the "used" list, and add the leaf
|
||
|
// to the final draw list.
|
||
|
if (drawableParent)
|
||
|
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
|
||
|
if (drawableParent)
|
||
|
culledParents.insert(drawableParent);
|
||
|
|
||
|
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) );
|
||
|
newModelView.preMultScale( leaf->_modelview->getScale() * refCamScaleMat );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
offset = rot * offset;
|
||
|
newModelView.makeTranslate( osg::Vec3f(winPos.x() + offset.x(), winPos.y() + offset.y(), 0) );
|
||
|
newModelView.preMultScale( leaf->_modelview->getScale() * refCamScaleMat );
|
||
|
newModelView.preMultRotate( rot );
|
||
|
}
|
||
|
|
||
|
// 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 ( ScreenSpaceLayout::globallyEnabled )
|
||
|
{
|
||
|
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();
|
||
|
const osg::Node* drawableParent = drawable->getNumParents() > 0 ? drawable->getParent(0) : 0L;
|
||
|
|
||
|
if ( drawableParent == 0L || culledParents.find(drawableParent) == 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 / osg::maximum(*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 / osg::maximum(*options.inAnimationTime(), 0.001f);
|
||
|
if ( info._lastAlpha > 1.0f )
|
||
|
info._lastAlpha = 1.0f;
|
||
|
}
|
||
|
|
||
|
leaf->_depth = info._lastAlpha;
|
||
|
leaves.push_back( leaf );
|
||
|
|
||
|
info._frame++;
|
||
|
info._visible = true;
|
||
|
}
|
||
|
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 = drawable && (strcmp(drawable->className(), "BboxDrawable") == 0);
|
||
|
bool fullyOut = true;
|
||
|
|
||
|
if (info._frame > 0u)
|
||
|
{
|
||
|
if ( info._lastScale != *options.minAnimationScale() )
|
||
|
{
|
||
|
fullyOut = false;
|
||
|
info._lastScale -= elapsedSeconds / osg::maximum(*options.outAnimationTime(), 0.001f);
|
||
|
if ( info._lastScale < *options.minAnimationScale() )
|
||
|
info._lastScale = *options.minAnimationScale();
|
||
|
}
|
||
|
|
||
|
if ( info._lastAlpha != *options.minAnimationAlpha() )
|
||
|
{
|
||
|
fullyOut = false;
|
||
|
info._lastAlpha -= elapsedSeconds / osg::maximum(*options.outAnimationTime(), 0.001f);
|
||
|
if ( info._lastAlpha < *options.minAnimationAlpha() )
|
||
|
info._lastAlpha = *options.minAnimationAlpha();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// prevent first-frame "pop out"
|
||
|
info._lastScale = options.minAnimationScale().get();
|
||
|
info._lastAlpha = options.minAnimationAlpha().get();
|
||
|
}
|
||
|
|
||
|
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) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
info._frame++;
|
||
|
info._visible = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Custom draw routine for our declutter render bin.
|
||
|
*/
|
||
|
struct DeclutterDraw : public osgUtil::RenderBin::DrawCallback
|
||
|
{
|
||
|
ScreenSpaceLayoutContext* _context;
|
||
|
PerThread< osg::ref_ptr<osg::RefMatrix> > _ortho2D;
|
||
|
osg::ref_ptr<osg::Uniform> _fade;
|
||
|
|
||
|
struct RunningState
|
||
|
{
|
||
|
RunningState() : lastFade(-1.0f), lastPCP(NULL) { }
|
||
|
float lastFade;
|
||
|
const osg::Program::PerContextProgram* lastPCP;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Constructs the decluttering draw callback.
|
||
|
* @param context A shared context among all decluttering objects.
|
||
|
*/
|
||
|
DeclutterDraw( ScreenSpaceLayoutContext* context )
|
||
|
: _context( context )
|
||
|
{
|
||
|
// create the fade uniform.
|
||
|
_fade = new osg::Uniform( osg::Uniform::FLOAT, FADE_UNIFORM_NAME );
|
||
|
_fade->set( 1.0f );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Draws a bin. Most of this code is copied from osgUtil::RenderBin::drawImplementation.
|
||
|
* The modifications are (a) skipping code to render child bins, (b) setting a bin-global
|
||
|
* projection matrix in orthographic space, and (c) calling our custom "renderLeaf()" method
|
||
|
* instead of RenderLeaf::render()
|
||
|
*/
|
||
|
void drawImplementation( osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous )
|
||
|
{
|
||
|
osg::State& state = *renderInfo.getState();
|
||
|
|
||
|
unsigned int numToPop = (previous ? osgUtil::StateGraph::numToPop(previous->_parent) : 0);
|
||
|
if (numToPop>1) --numToPop;
|
||
|
unsigned int insertStateSetPosition = state.getStateSetStackSize() - numToPop;
|
||
|
|
||
|
if (bin->getStateSet())
|
||
|
{
|
||
|
state.insertStateSet(insertStateSetPosition, bin->getStateSet());
|
||
|
}
|
||
|
|
||
|
// apply a window-space projection matrix.
|
||
|
const osg::Viewport* vp = renderInfo.getCurrentCamera()->getViewport();
|
||
|
if ( vp )
|
||
|
{
|
||
|
osg::ref_ptr<osg::RefMatrix>& m = _ortho2D.get();
|
||
|
if ( !m.valid() )
|
||
|
m = new osg::RefMatrix();
|
||
|
|
||
|
//m->makeOrtho2D( vp->x(), vp->x()+vp->width()-1, vp->y(), vp->y()+vp->height()-1 );
|
||
|
m->makeOrtho( vp->x(), vp->x()+vp->width()-1, vp->y(), vp->y()+vp->height()-1, -1000, 1000);
|
||
|
state.applyProjectionMatrix( m.get() );
|
||
|
}
|
||
|
|
||
|
// initialize the fading uniform
|
||
|
RunningState rs;
|
||
|
|
||
|
// render the list
|
||
|
osgUtil::RenderBin::RenderLeafList& leaves = bin->getRenderLeafList();
|
||
|
|
||
|
for(osgUtil::RenderBin::RenderLeafList::reverse_iterator rlitr = leaves.rbegin();
|
||
|
rlitr!= leaves.rend();
|
||
|
++rlitr)
|
||
|
{
|
||
|
osgUtil::RenderLeaf* rl = *rlitr;
|
||
|
if ( rl->_depth > 0.0f)
|
||
|
{
|
||
|
renderLeaf( rl, renderInfo, previous, rs);
|
||
|
previous = rl;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( bin->getStateSet() )
|
||
|
{
|
||
|
state.removeStateSet(insertStateSetPosition);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Renders a single leaf. We already applied the projection matrix, so here we only
|
||
|
* need to apply a modelview matrix that specifies the ortho offset of the drawable.
|
||
|
*
|
||
|
* Most of this code is copied from RenderLeaf::draw() -- but I removed all the code
|
||
|
* dealing with nested bins, since decluttering does not support them.
|
||
|
*/
|
||
|
void renderLeaf( osgUtil::RenderLeaf* leaf, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous, RunningState& rs)
|
||
|
{
|
||
|
osg::State& state = *renderInfo.getState();
|
||
|
|
||
|
// don't draw this leaf if the abort rendering flag has been set.
|
||
|
if (state.getAbortRendering())
|
||
|
{
|
||
|
//cout << "early abort"<<endl;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
state.applyModelViewMatrix( leaf->_modelview.get() );
|
||
|
|
||
|
if (previous)
|
||
|
{
|
||
|
// apply state if required.
|
||
|
osgUtil::StateGraph* prev_rg = previous->_parent;
|
||
|
osgUtil::StateGraph* prev_rg_parent = prev_rg->_parent;
|
||
|
osgUtil::StateGraph* rg = leaf->_parent;
|
||
|
if (prev_rg_parent!=rg->_parent)
|
||
|
{
|
||
|
osgUtil::StateGraph::moveStateGraph(state,prev_rg_parent,rg->_parent);
|
||
|
|
||
|
// send state changes and matrix changes to OpenGL.
|
||
|
state.apply(rg->getStateSet());
|
||
|
|
||
|
}
|
||
|
else if (rg!=prev_rg)
|
||
|
{
|
||
|
// send state changes and matrix changes to OpenGL.
|
||
|
state.apply(rg->getStateSet());
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// apply state if required.
|
||
|
osgUtil::StateGraph::moveStateGraph(state,NULL,leaf->_parent->_parent);
|
||
|
|
||
|
state.apply(leaf->_parent->getStateSet());
|
||
|
}
|
||
|
|
||
|
// if we are using osg::Program which requires OSG's generated uniforms to track
|
||
|
// modelview and projection matrices then apply them now.
|
||
|
if (state.getUseModelViewAndProjectionUniforms())
|
||
|
state.applyModelViewAndProjectionUniformsIfRequired();
|
||
|
|
||
|
// apply the fading uniform
|
||
|
const osg::Program::PerContextProgram* pcp = state.getLastAppliedProgramObject();
|
||
|
if ( pcp )
|
||
|
{
|
||
|
if (pcp != rs.lastPCP || leaf->_depth != rs.lastFade)
|
||
|
{
|
||
|
rs.lastFade = ScreenSpaceLayout::globallyEnabled ? leaf->_depth : 1.0f;
|
||
|
_fade->set( rs.lastFade );
|
||
|
pcp->apply( *_fade.get() );
|
||
|
}
|
||
|
}
|
||
|
rs.lastPCP = pcp;
|
||
|
|
||
|
// draw the drawable
|
||
|
leaf->_drawable->draw(renderInfo);
|
||
|
|
||
|
if (leaf->_dynamic)
|
||
|
{
|
||
|
state.decrementDynamicObjectCount();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} }
|
||
|
|
||
|
#endif // OSGEARTH_SCREEN_SPACE_LAYOUT_DECLUTTER_H
|