f61a6aa4e7
The DatabasePager now passes the Terrain pointer into the ReaderWriter's via osgDB::Options object, rather than pushing a NodePath containing the Terrain onto NodeVisitor. This change means that the DatabasePager nolonger needs to observer the whole NodePath and will be lighter and quicker for it. The change also means that ReadFileCallback can now run custom NodeVisitor's on the scene graph without having to worry about TerrainTile's constructing scene graphs prior to the Terrain being assigned. Also changed is the NodeVisitor::DatabaseRequestHandler which now requires a NodePath to the node that you wish to add to rather than just the pointer to the node you wish to add to. This is more robust when handling scenes with multiple parental paths, whereas previously errors could have occurred due to the default of picking the first available parental path. This change means that subclasses of DatabasePager will need to be updated to use this new function entry point.
400 lines
14 KiB
C++
400 lines
14 KiB
C++
#include <osg/ClusterCullingCallback>
|
|
#include <osgDB/ReadFile>
|
|
#include <osgDB/WriteFile>
|
|
#include <string.h>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include "PosterPrinter.h"
|
|
|
|
/* PagedLoadingCallback: Callback for loading paged nodes while doing intersecting test */
|
|
struct PagedLoadingCallback : public osgUtil::IntersectionVisitor::ReadCallback
|
|
{
|
|
virtual osg::Node* readNodeFile( const std::string& filename )
|
|
{
|
|
return osgDB::readNodeFile( filename );
|
|
}
|
|
};
|
|
static osg::ref_ptr<PagedLoadingCallback> g_pagedLoadingCallback = new PagedLoadingCallback;
|
|
|
|
/* LodCullingCallback: Callback for culling LODs and selecting the highest level */
|
|
class LodCullingCallback : public osg::NodeCallback
|
|
{
|
|
public:
|
|
virtual void operator()( osg::Node* node, osg::NodeVisitor* nv )
|
|
{
|
|
osg::LOD* lod = static_cast<osg::LOD*>(node);
|
|
if ( lod && lod->getNumChildren()>0 )
|
|
lod->getChild(lod->getNumChildren()-1)->accept(*nv);
|
|
}
|
|
};
|
|
static osg::ref_ptr<LodCullingCallback> g_lodCullingCallback = new LodCullingCallback;
|
|
|
|
/* PagedCullingCallback: Callback for culling paged nodes and selecting the highest level */
|
|
class PagedCullingCallback : public osg::NodeCallback
|
|
{
|
|
public:
|
|
virtual void operator()( osg::Node* node, osg::NodeVisitor* nv )
|
|
{
|
|
osg::PagedLOD* pagedLOD = static_cast<osg::PagedLOD*>(node);
|
|
if ( pagedLOD && pagedLOD->getNumChildren()>0 )
|
|
{
|
|
unsigned int numChildren = pagedLOD->getNumChildren();
|
|
bool updateTimeStamp = nv->getVisitorType()==osg::NodeVisitor::CULL_VISITOR;
|
|
if ( nv->getFrameStamp() && updateTimeStamp )
|
|
{
|
|
double timeStamp = nv->getFrameStamp()?nv->getFrameStamp()->getReferenceTime():0.0;
|
|
unsigned int frameNumber = nv->getFrameStamp()?nv->getFrameStamp()->getFrameNumber():0;
|
|
|
|
pagedLOD->setFrameNumberOfLastTraversal( frameNumber );
|
|
pagedLOD->setTimeStamp( numChildren-1, timeStamp );
|
|
pagedLOD->setFrameNumber( numChildren-1, frameNumber );
|
|
pagedLOD->getChild(numChildren-1)->accept(*nv);
|
|
}
|
|
|
|
// Request for new child
|
|
if ( !pagedLOD->getDisableExternalChildrenPaging() &&
|
|
nv->getDatabaseRequestHandler() &&
|
|
numChildren<pagedLOD->getNumRanges() )
|
|
{
|
|
if ( pagedLOD->getDatabasePath().empty() )
|
|
{
|
|
nv->getDatabaseRequestHandler()->requestNodeFile(
|
|
pagedLOD->getFileName(numChildren), nv->getNodePath(),
|
|
1.0, nv->getFrameStamp(),
|
|
pagedLOD->getDatabaseRequest(numChildren), pagedLOD->getDatabaseOptions() );
|
|
}
|
|
else
|
|
{
|
|
nv->getDatabaseRequestHandler()->requestNodeFile(
|
|
pagedLOD->getDatabasePath()+pagedLOD->getFileName(numChildren), nv->getNodePath(),
|
|
1.0, nv->getFrameStamp(),
|
|
pagedLOD->getDatabaseRequest(numChildren), pagedLOD->getDatabaseOptions() );
|
|
}
|
|
}
|
|
}
|
|
//node->traverse(*nv);
|
|
}
|
|
};
|
|
static osg::ref_ptr<PagedCullingCallback> g_pagedCullingCallback = new PagedCullingCallback;
|
|
|
|
/* PosterVisitor: A visitor for adding culling callbacks to newly allocated paged nodes */
|
|
PosterVisitor::PosterVisitor()
|
|
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
|
|
_appliedCount(0), _needToApplyCount(0),
|
|
_addingCallbacks(true)
|
|
{
|
|
}
|
|
|
|
void PosterVisitor::apply( osg::LOD& node )
|
|
{
|
|
/*if ( !hasCullCallback(node.getCullCallback(), g_lodCullingCallback.get()) )
|
|
{
|
|
if ( !node.getName().empty() )
|
|
{
|
|
PagedNodeNameSet::iterator itr = _pagedNodeNames.find( node.getName() );
|
|
if ( itr!=_pagedNodeNames.end() )
|
|
{
|
|
insertCullCallback( node, g_lodCullingCallback.get() );
|
|
_appliedCount++;
|
|
}
|
|
}
|
|
}
|
|
else if ( !_addingCallbacks )
|
|
{
|
|
node.removeCullCallback( g_lodCullingCallback.get() );
|
|
_appliedCount--;
|
|
}*/
|
|
traverse( node );
|
|
}
|
|
|
|
void PosterVisitor::apply( osg::PagedLOD& node )
|
|
{
|
|
if ( !hasCullCallback(node.getCullCallback(), g_pagedCullingCallback.get()) )
|
|
{
|
|
for ( unsigned int i=0; i<node.getNumFileNames(); ++i )
|
|
{
|
|
if ( node.getFileName(i).empty() ) continue;
|
|
|
|
PagedNodeNameSet::iterator itr = _pagedNodeNames.find( node.getFileName(i) );
|
|
if ( itr!=_pagedNodeNames.end() )
|
|
{
|
|
node.addCullCallback( g_pagedCullingCallback.get() );
|
|
_appliedCount++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else if ( !_addingCallbacks )
|
|
{
|
|
node.removeCullCallback( g_pagedCullingCallback.get() );
|
|
if ( _appliedCount>0 ) _appliedCount--;
|
|
}
|
|
traverse( node );
|
|
}
|
|
|
|
/* PosterIntersector: A simple polytope intersector for updating pagedLODs in each image-tile */
|
|
PosterIntersector::PosterIntersector( const osg::Polytope& polytope )
|
|
: _intersectionVisitor(0), _parent(0), _polytope(polytope)
|
|
{}
|
|
|
|
PosterIntersector::PosterIntersector( double xMin, double yMin, double xMax, double yMax )
|
|
: Intersector(osgUtil::Intersector::PROJECTION),
|
|
_intersectionVisitor(0), _parent(0)
|
|
{
|
|
_polytope.add( osg::Plane( 1.0, 0.0, 0.0,-xMin) );
|
|
_polytope.add( osg::Plane(-1.0, 0.0, 0.0, xMax) );
|
|
_polytope.add( osg::Plane( 0.0, 1.0, 0.0,-yMin) );
|
|
_polytope.add( osg::Plane( 0.0,-1.0, 0.0, yMax) );
|
|
}
|
|
|
|
osgUtil::Intersector* PosterIntersector::clone( osgUtil::IntersectionVisitor& iv )
|
|
{
|
|
osg::Matrix matrix;
|
|
if ( iv.getProjectionMatrix() ) matrix.preMult( *iv.getProjectionMatrix() );
|
|
if ( iv.getViewMatrix() ) matrix.preMult( *iv.getViewMatrix() );
|
|
if ( iv.getModelMatrix() ) matrix.preMult( *iv.getModelMatrix() );
|
|
|
|
osg::Polytope transformedPolytope;
|
|
transformedPolytope.setAndTransformProvidingInverse( _polytope, matrix );
|
|
|
|
osg::ref_ptr<PosterIntersector> pi = new PosterIntersector( transformedPolytope );
|
|
pi->_intersectionVisitor = &iv;
|
|
pi->_parent = this;
|
|
return pi.release();
|
|
}
|
|
|
|
bool PosterIntersector::enter( const osg::Node& node )
|
|
{
|
|
if ( !node.isCullingActive() ) return true;
|
|
if ( _polytope.contains(node.getBound()) )
|
|
{
|
|
if ( node.getCullCallback() )
|
|
{
|
|
const osg::ClusterCullingCallback* cccb =
|
|
dynamic_cast<const osg::ClusterCullingCallback*>( node.getCullCallback() );
|
|
if ( cccb && cccb->cull(_intersectionVisitor, 0, NULL) ) return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void PosterIntersector::reset()
|
|
{
|
|
_intersectionVisitor = NULL;
|
|
Intersector::reset();
|
|
}
|
|
|
|
void PosterIntersector::intersect( osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable )
|
|
{
|
|
if ( !_polytope.contains(drawable->getBound()) ) return;
|
|
if ( iv.getDoDummyTraversal() ) return;
|
|
|
|
// Find and collect all paged LODs in the node path
|
|
osg::NodePath& nodePath = iv.getNodePath();
|
|
for ( osg::NodePath::iterator itr=nodePath.begin(); itr!=nodePath.end(); ++itr )
|
|
{
|
|
osg::PagedLOD* pagedLOD = dynamic_cast<osg::PagedLOD*>(*itr);
|
|
if ( pagedLOD )
|
|
{
|
|
// FIXME: The first non-empty getFileName() is used as the identity of this paged node.
|
|
// This should work with VPB-generated terrains but maybe unusable with others.
|
|
for ( unsigned int i=0; i<pagedLOD->getNumFileNames(); ++i )
|
|
{
|
|
if ( pagedLOD->getFileName(i).empty() ) continue;
|
|
if ( _parent->_visitor.valid() )
|
|
_parent->_visitor->insertName( pagedLOD->getFileName(i) );
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/*osg::LOD* lod = dynamic_cast<osg::LOD*>(*itr);
|
|
if ( lod )
|
|
{
|
|
if ( !lod->getName().empty() && _parent->_visitor.valid() )
|
|
_parent->_visitor->insertName( lod->getName() );
|
|
}*/
|
|
}
|
|
}
|
|
|
|
/* PosterPrinter: The implementation class of high-res rendering */
|
|
PosterPrinter::PosterPrinter():
|
|
_outputTiles(false), _outputTileExt("bmp"),
|
|
_isRunning(false), _isFinishing(false), _lastBindingFrame(0),
|
|
_currentRow(0), _currentColumn(0),
|
|
_camera(0), _finalPoster(0)
|
|
{
|
|
_intersector = new PosterIntersector(-1.0, -1.0, 1.0, 1.0);
|
|
_visitor = new PosterVisitor;
|
|
_intersector->setPosterVisitor( _visitor.get() );
|
|
}
|
|
|
|
void PosterPrinter::init( const osg::Camera* camera )
|
|
{
|
|
if ( _camera.valid() )
|
|
init( camera->getViewMatrix(), camera->getProjectionMatrix() );
|
|
}
|
|
|
|
void PosterPrinter::init( const osg::Matrixd& view, const osg::Matrixd& proj )
|
|
{
|
|
if ( _isRunning ) return;
|
|
_images.clear();
|
|
_visitor->clearNames();
|
|
_tileRows = (int)(_posterSize.y() / _tileSize.y());
|
|
_tileColumns = (int)(_posterSize.x() / _tileSize.x());
|
|
_currentRow = 0;
|
|
_currentColumn = 0;
|
|
_currentViewMatrix = view;
|
|
_currentProjectionMatrix = proj;
|
|
_lastBindingFrame = 0;
|
|
_isRunning = true;
|
|
_isFinishing = false;
|
|
}
|
|
|
|
void PosterPrinter::frame( const osg::FrameStamp* fs, osg::Node* node )
|
|
{
|
|
// Add cull callbacks to all existing paged nodes,
|
|
// and advance frame when all callbacks are dispatched.
|
|
if ( addCullCallbacks(fs, node) )
|
|
return;
|
|
|
|
if ( _isFinishing )
|
|
{
|
|
if ( (fs->getFrameNumber()-_lastBindingFrame)>2 )
|
|
{
|
|
// Record images and the final poster
|
|
recordImages();
|
|
if ( _finalPoster.valid() )
|
|
{
|
|
std::cout << "Writing final result to file..." << std::endl;
|
|
osgDB::writeImageFile( *_finalPoster, _outputPosterName );
|
|
}
|
|
|
|
// Release all cull callbacks to free unused paged nodes
|
|
removeCullCallbacks( node );
|
|
_visitor->clearNames();
|
|
|
|
_isFinishing = false;
|
|
std::cout << "Recording images finished." << std::endl;
|
|
}
|
|
}
|
|
|
|
if ( _isRunning )
|
|
{
|
|
// Every "copy-to-image" process seems to be finished in 2 frames.
|
|
// So record them and dispatch camera to next tiles.
|
|
if ( (fs->getFrameNumber()-_lastBindingFrame)>2 )
|
|
{
|
|
// Record images and unref them to free memory
|
|
recordImages();
|
|
|
|
// Release all cull callbacks to free unused paged nodes
|
|
removeCullCallbacks( node );
|
|
_visitor->clearNames();
|
|
|
|
if ( _camera.valid() )
|
|
{
|
|
std::cout << "Binding sub-camera " << _currentRow << "_" << _currentColumn
|
|
<< " to image..." << std::endl;
|
|
bindCameraToImage( _camera.get(), _currentRow, _currentColumn );
|
|
if ( _currentColumn<_tileColumns-1 )
|
|
{
|
|
_currentColumn++;
|
|
}
|
|
else
|
|
{
|
|
if ( _currentRow<_tileRows-1 )
|
|
{
|
|
_currentRow++;
|
|
_currentColumn = 0;
|
|
}
|
|
else
|
|
{
|
|
_isRunning = false;
|
|
_isFinishing = true;
|
|
}
|
|
}
|
|
}
|
|
_lastBindingFrame = fs->getFrameNumber();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PosterPrinter::addCullCallbacks( const osg::FrameStamp* fs, osg::Node* node )
|
|
{
|
|
if ( !_visitor->inQueue() || done() )
|
|
return false;
|
|
|
|
_visitor->setAddingCallbacks( true );
|
|
_camera->accept( *_visitor );
|
|
_lastBindingFrame = fs->getFrameNumber();
|
|
|
|
std::cout << "Dispatching callbacks to paged nodes... "
|
|
<< _visitor->inQueue() << std::endl;
|
|
return true;
|
|
}
|
|
|
|
void PosterPrinter::removeCullCallbacks( osg::Node* node )
|
|
{
|
|
_visitor->setAddingCallbacks( false );
|
|
_camera->accept( *_visitor );
|
|
}
|
|
|
|
void PosterPrinter::bindCameraToImage( osg::Camera* camera, int row, int col )
|
|
{
|
|
std::stringstream stream;
|
|
stream << "image_" << row << "_" << col;
|
|
|
|
osg::ref_ptr<osg::Image> image = new osg::Image;
|
|
image->setName( stream.str() );
|
|
image->allocateImage( (int)_tileSize.x(), (int)_tileSize.y(), 1, GL_RGBA, GL_UNSIGNED_BYTE );
|
|
_images[TilePosition(row,col)] = image.get();
|
|
|
|
// Calculate projection matrix offset of each tile
|
|
osg::Matrix offsetMatrix =
|
|
osg::Matrix::scale(_tileColumns, _tileRows, 1.0) *
|
|
osg::Matrix::translate(_tileColumns-1-2*col, _tileRows-1-2*row, 0.0);
|
|
camera->setViewMatrix( _currentViewMatrix );
|
|
camera->setProjectionMatrix( _currentProjectionMatrix * offsetMatrix );
|
|
|
|
// Check intersections between the image-tile box and the model
|
|
osgUtil::IntersectionVisitor iv( _intersector.get() );
|
|
iv.setReadCallback( g_pagedLoadingCallback.get() );
|
|
_intersector->reset();
|
|
camera->accept( iv );
|
|
if ( _intersector->containsIntersections() )
|
|
{
|
|
// Apply a cull calback to every paged node obtained, to force the highest level displaying.
|
|
// This will be done by the PosterVisitor, who already records all the paged nodes.
|
|
}
|
|
|
|
// Reattach cameras and new allocated images
|
|
camera->setRenderingCache( NULL ); // FIXME: Uses for reattaching camera with image, maybe inefficient?
|
|
camera->detach( osg::Camera::COLOR_BUFFER );
|
|
camera->attach( osg::Camera::COLOR_BUFFER, image.get(), 0, 0 );
|
|
}
|
|
|
|
void PosterPrinter::recordImages()
|
|
{
|
|
for ( TileImages::iterator itr=_images.begin(); itr!=_images.end(); ++itr )
|
|
{
|
|
osg::Image* image = (itr->second).get();
|
|
if ( _finalPoster.valid() )
|
|
{
|
|
// FIXME: A stupid way to combine tile images to final result. Any better ideas?
|
|
unsigned int row = itr->first.first, col = itr->first.second;
|
|
for ( int t=0; t<image->t(); ++t )
|
|
{
|
|
unsigned char* source = image->data( 0, t );
|
|
unsigned char* target = _finalPoster->data( col*(int)_tileSize.x(), t + row*(int)_tileSize.y() );
|
|
memcpy( target, source, image->s() * 4 * sizeof(unsigned char) );
|
|
}
|
|
}
|
|
|
|
if ( _outputTiles )
|
|
osgDB::writeImageFile( *image, image->getName()+"."+_outputTileExt );
|
|
}
|
|
_images.clear();
|
|
}
|