#include #include #include #include #include #include #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 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(node); if ( lod && lod->getNumChildren()>0 ) lod->getChild(lod->getNumChildren()-1)->accept(*nv); } }; static osg::ref_ptr 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(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() && numChildrengetNumRanges() ) { 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 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; i0 ) _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 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( 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(*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; igetNumFileNames(); ++i ) { if ( pagedLOD->getFileName(i).empty() ) continue; if ( _parent->_visitor.valid() ) _parent->_visitor->insertName( pagedLOD->getFileName(i) ); break; } continue; } /*osg::LOD* lod = dynamic_cast(*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 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; tt(); ++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(); }