diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 89e260fa8..cadf3f7d5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -80,6 +80,7 @@ IF(DYNAMIC_OPENSCENEGRAPH) ADD_SUBDIRECTORY(osgplanets) ADD_SUBDIRECTORY(osgpoints) ADD_SUBDIRECTORY(osgpointsprite) + ADD_SUBDIRECTORY(osgposter) ADD_SUBDIRECTORY(osgprecipitation) ADD_SUBDIRECTORY(osgprerender) ADD_SUBDIRECTORY(osgprerendercubemap) diff --git a/examples/osgposter/CMakeLists.txt b/examples/osgposter/CMakeLists.txt new file mode 100644 index 000000000..173f64f3f --- /dev/null +++ b/examples/osgposter/CMakeLists.txt @@ -0,0 +1,2 @@ +SET(TARGET_SRC osgposter.cpp PosterPrinter.cpp ) +SETUP_EXAMPLE(osgposter) \ No newline at end of file diff --git a/examples/osgposter/PosterPrinter.cpp b/examples/osgposter/PosterPrinter.cpp new file mode 100644 index 000000000..b97aa2fac --- /dev/null +++ b/examples/osgposter/PosterPrinter.cpp @@ -0,0 +1,399 @@ +#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; + 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), pagedLOD, + 1.0, nv->getFrameStamp(), + pagedLOD->getDatabaseRequest(numChildren), pagedLOD->getDatabaseOptions() ); + } + else + { + nv->getDatabaseRequestHandler()->requestNodeFile( + pagedLOD->getDatabasePath()+pagedLOD->getFileName(numChildren), pagedLOD, + 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() +: _isRunning(false), _isFinishing(false), _lastBindingFrame(0), + _outputTiles(false), _outputTileExt("bmp"), + _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(); +} diff --git a/examples/osgposter/PosterPrinter.h b/examples/osgposter/PosterPrinter.h new file mode 100644 index 000000000..8b912e208 --- /dev/null +++ b/examples/osgposter/PosterPrinter.h @@ -0,0 +1,160 @@ +#ifndef OSGPOSTER_POSTERPRINTER +#define OSGPOSTER_POSTERPRINTER + +#include +#include +#include + +/** PosterVisitor: A visitor for adding culling callbacks to newly allocated paged nodes */ +class PosterVisitor : public osg::NodeVisitor +{ +public: + typedef std::set PagedNodeNameSet; + + PosterVisitor(); + META_NodeVisitor( osgPoster, PosterVisitor ); + + void insertName( const std::string& name ) + { if ( _pagedNodeNames.insert(name).second ) _needToApplyCount++; } + + void eraseName( const std::string& name ) + { if ( _pagedNodeNames.erase(name)>0 ) _needToApplyCount--; } + + void clearNames() { _pagedNodeNames.clear(); _needToApplyCount = 0; _appliedCount = 0; } + unsigned int getNumNames() const { return _pagedNodeNames.size(); } + + PagedNodeNameSet& getPagedNodeNames() { return _pagedNodeNames; } + const PagedNodeNameSet& getPagedNodeNames() const { return _pagedNodeNames; } + + unsigned int getNeedToApplyCount() const { return _needToApplyCount; } + unsigned int getAppliedCount() const { return _appliedCount; } + unsigned int inQueue() const { return _needToApplyCount>_appliedCount ? _needToApplyCount-_appliedCount : 0; } + + void setAddingCallbacks( bool b ) { _addingCallbacks = b; } + bool getAddingCallbacks() const { return _addingCallbacks; } + + virtual void apply( osg::LOD& node ); + virtual void apply( osg::PagedLOD& node ); + +protected: + bool hasCullCallback( osg::NodeCallback* nc, osg::NodeCallback* target ) + { + if ( nc==target ) return true; + else if ( !nc ) return false; + return hasCullCallback( nc->getNestedCallback(), target ); + } + + PagedNodeNameSet _pagedNodeNames; + unsigned int _needToApplyCount; + unsigned int _appliedCount; + bool _addingCallbacks; +}; + +/** PosterIntersector: A simple polytope intersector for updating pagedLODs in each image-tile */ +class PosterIntersector : public osgUtil::Intersector +{ +public: + typedef std::set PagedNodeNameSet; + + PosterIntersector( const osg::Polytope& polytope ); + PosterIntersector( double xMin, double yMin, double xMax, double yMax ); + + void setPosterVisitor( PosterVisitor* pcv ) { _visitor = pcv; } + PosterVisitor* getPosterVisitor() { return _visitor.get(); } + const PosterVisitor* getPosterVisitor() const { return _visitor.get(); } + + virtual Intersector* clone( osgUtil::IntersectionVisitor& iv ); + + virtual bool containsIntersections() + { return _visitor.valid()&&_visitor->getNumNames()>0; } + + virtual bool enter( const osg::Node& node ); + virtual void leave() {} + virtual void reset(); + virtual void intersect( osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable ); + +protected: + osgUtil::IntersectionVisitor* _intersectionVisitor; + osg::ref_ptr _visitor; + PosterIntersector* _parent; + osg::Polytope _polytope; +}; + +/** PosterPrinter: The implementation class of high-res rendering */ +class PosterPrinter : public osg::Referenced +{ +public: + typedef std::pair TilePosition; + typedef std::map< TilePosition, osg::ref_ptr > TileImages; + + PosterPrinter(); + + /** Set to output each sub-image-tile to disk */ + void setOutputTiles( bool b ) { _outputTiles = b; } + bool getOutputTiles() const { return _outputTiles; } + + /** Set the output sub-image-tile extension, e.g. bmp */ + void setOutputTileExtension( const std::string& ext ) { _outputTileExt = ext; } + const std::string& getOutputTileExtension() const { return _outputTileExt; } + + /** Set the output poster name, e.g. output.bmp */ + void setOutputPosterName( const std::string& name ) { _outputPosterName = name; } + const std::string& getOutputPosterName() const { return _outputPosterName; } + + /** Set the size of each sub-image-tile, e.g. 640x480 */ + void setTileSize( int w, int h ) { _tileSize.set(w, h); } + const osg::Vec2& getTileSize() const { return _tileSize; } + + /** Set the final size of the high-res poster, e.g. 6400x4800 */ + void setPosterSize( int w, int h ) { _posterSize.set(w, h); } + const osg::Vec2& getPosterSize() const { return _posterSize; } + + /** Set the capturing camera */ + void setCamera( osg::Camera* camera ) { _camera = camera; } + const osg::Camera* getCamera() const { return _camera.get(); } + + /** Set the final poster image, should be already allocated */ + void setFinalPoster( osg::Image* image ) { _finalPoster = image; } + const osg::Image* getFinalPoster() const { return _finalPoster.get(); } + + PosterVisitor* getPosterVisitor() { return _visitor.get(); } + const PosterVisitor* getPosterVisitor() const { return _visitor.get(); } + + bool done() const { return !_isRunning && !_isFinishing; } + + void init( const osg::Camera* camera ); + void init( const osg::Matrixd& view, const osg::Matrixd& proj ); + void frame( const osg::FrameStamp* fs, osg::Node* node ); + +protected: + virtual ~PosterPrinter() {} + + bool addCullCallbacks( const osg::FrameStamp* fs, osg::Node* node ); + void removeCullCallbacks( osg::Node* node ); + void bindCameraToImage( osg::Camera* camera, int row, int col ); + void recordImages(); + + bool _outputTiles; + std::string _outputTileExt; + std::string _outputPosterName; + osg::Vec2 _tileSize; + osg::Vec2 _posterSize; + + bool _isRunning; + bool _isFinishing; + int _lastBindingFrame; + int _tileRows; + int _tileColumns; + int _currentRow; + int _currentColumn; + osg::ref_ptr _intersector; + osg::ref_ptr _visitor; + + osg::Matrixd _currentViewMatrix; + osg::Matrixd _currentProjectionMatrix; + osg::ref_ptr _camera; + osg::ref_ptr _finalPoster; + TileImages _images; +}; + +#endif diff --git a/examples/osgposter/osgposter.cpp b/examples/osgposter/osgposter.cpp new file mode 100644 index 000000000..4f72bd68b --- /dev/null +++ b/examples/osgposter/osgposter.cpp @@ -0,0 +1,384 @@ +/* -*-c++-*- OpenSceneGraph example, osgposter. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* 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. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "PosterPrinter.h" + +/* Computing view matrix helpers */ +template +class FindTopMostNodeOfTypeVisitor : public osg::NodeVisitor +{ +public: + FindTopMostNodeOfTypeVisitor() + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), + _foundNode(0) + {} + + void apply( osg::Node& node ) + { + T* result = dynamic_cast( &node ); + if ( result ) _foundNode = result; + else traverse( node ); + } + + T* _foundNode; +}; + +template +T* findTopMostNodeOfType( osg::Node* node ) +{ + if ( !node ) return 0; + + FindTopMostNodeOfTypeVisitor fnotv; + node->accept( fnotv ); + return fnotv._foundNode; +} + +/* Computing view matrix functions */ +void computeViewMatrix( osg::Camera* camera, const osg::Vec3d& eye, const osg::Vec3d& hpr ) +{ + osg::Matrixd matrix; + matrix.makeTranslate( eye ); + matrix.preMult( osg::Matrixd::rotate( hpr[0], 0.0, 1.0, 0.0) ); + matrix.preMult( osg::Matrixd::rotate( hpr[1], 1.0, 0.0, 0.0) ); + matrix.preMult( osg::Matrixd::rotate( hpr[2], 0.0, 0.0, 1.0) ); + camera->setViewMatrix( osg::Matrixd::inverse(matrix) ); +} + +void computeViewMatrixOnEarth( osg::Camera* camera, osg::Node* scene, + const osg::Vec3d& latLongHeight, const osg::Vec3d& hpr ) +{ + osg::CoordinateSystemNode* csn = findTopMostNodeOfType(scene); + if ( !csn ) return; + + // Compute eye point in world coordiantes + osg::Vec3d eye; + csn->getEllipsoidModel()->convertLatLongHeightToXYZ( + latLongHeight.x(), latLongHeight.y(), latLongHeight.z(), eye.x(), eye.y(), eye.z() ); + + // Build matrix for computing target vector + osg::Matrixd target_matrix = + osg::Matrixd::rotate( -hpr.x(), osg::Vec3d(1,0,0), + -latLongHeight.x(), osg::Vec3d(0,1,0), + latLongHeight.y(), osg::Vec3d(0,0,1) ); + + // Compute tangent vector + osg::Vec3d tangent = target_matrix.preMult( osg::Vec3d(0,0,1) ); + + // Compute non-inclined, non-rolled up vector + osg::Vec3d up( eye ); + up.normalize(); + + // Incline by rotating the target- and up vector around the tangent/up-vector + // cross-product + osg::Vec3d up_cross_tangent = up ^ tangent; + osg::Matrixd incline_matrix = osg::Matrixd::rotate( hpr.y(), up_cross_tangent ); + osg::Vec3d target = incline_matrix.preMult( tangent ); + + // Roll by rotating the up vector around the target vector + osg::Matrixd roll_matrix = incline_matrix * osg::Matrixd::rotate( hpr.z(), target ); + up = roll_matrix.preMult( up ); + camera->setViewMatrixAsLookAt( eye, eye+target, up ); +} + +/* CustomRenderer: Do culling only while loading PagedLODs */ +class CustomRenderer : public osgViewer::Renderer +{ +public: + CustomRenderer( osg::Camera* camera ) + : osgViewer::Renderer(camera), _cullOnly(true) + { + } + + void setCullOnly(bool on) { _cullOnly = on; } + + virtual void operator ()( osg::GraphicsContext* ) + { + if ( _graphicsThreadDoesCull ) + { + if (_cullOnly) cull(); + else cull_draw(); + } + } + + virtual void cull() + { + osgUtil::SceneView* sceneView = _sceneView[0].get(); + if ( !sceneView || _done || _graphicsThreadDoesCull ) + return; + + updateSceneView( sceneView ); + + osgViewer::View* view = dynamic_cast( _camera->getView() ); + if ( view ) + sceneView->setFusionDistance( view->getFusionDistanceMode(), view->getFusionDistanceValue() ); + sceneView->inheritCullSettings( *(sceneView->getCamera()) ); + sceneView->cull(); + } + + bool _cullOnly; +}; + +/* PrintPosterHandler: A gui handler for interactive high-res capturing */ +class PrintPosterHandler : public osgGA::GUIEventHandler +{ +public: + PrintPosterHandler( PosterPrinter* printer ) + : _printer(printer), _started(false) {} + + bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa ) + { + osgViewer::View* view = dynamic_cast( &aa ); + if ( !view ) return false; + + switch( ea.getEventType() ) + { + case osgGA::GUIEventAdapter::FRAME: + if ( view->getDatabasePager() ) + { + // Wait until all paged nodes are processed + if ( view->getDatabasePager()->getRequestsInProgress() ) + break; + } + + if ( _printer.valid() ) + { + _printer->frame( view->getFrameStamp(), view->getSceneData() ); + if ( _started && _printer->done() ) + { + osg::Switch* root = dynamic_cast( view->getSceneData() ); + if ( root ) + { + // Assume child 0 is the loaded model and 1 is the poster camera + // Switch them in time to prevent dual traversals of subgraph + root->setValue( 0, true ); + root->setValue( 1, false ); + } + _started = false; + } + } + break; + + case osgGA::GUIEventAdapter::KEYDOWN: + if ( ea.getKey()=='p' || ea.getKey()=='P' ) + { + if ( _printer.valid() ) + { + osg::Switch* root = dynamic_cast( view->getSceneData() ); + if ( root ) + { + // Assume child 0 is the loaded model and 1 is the poster camera + root->setValue( 0, false ); + root->setValue( 1, true ); + } + + _printer->init( view->getCamera() ); + _started = true; + } + return true; + } + break; + + default: + break; + } + return false; + } + +protected: + osg::ref_ptr _printer; + bool _started; +}; + +/* The main entry */ +int main( int argc, char** argv ) +{ + osg::ArgumentParser arguments( &argc, argv ); + osg::ApplicationUsage* usage = arguments.getApplicationUsage(); + usage->setDescription( arguments.getApplicationName() + + " is the example which demonstrates how to render high-resolution images (posters)."); + usage->setCommandLineUsage( arguments.getApplicationName() + " [options] scene_file" ); + usage->addCommandLineOption( "-h or --help", "Display this information." ); + usage->addCommandLineOption( "--color ", "The background color." ); + usage->addCommandLineOption( "--ext ", "The output tiles' extension (Default: bmp)." ); + usage->addCommandLineOption( "--poster ", "The output poster's name (Default: poster.bmp)." ); + usage->addCommandLineOption( "--tilesize ", "Size of each image tile (Default: 640 480)." ); + usage->addCommandLineOption( "--finalsize ", "Size of the poster (Default: 6400 4800)." ); + usage->addCommandLineOption( "--enable-output-poster", "Output the final poster file (Default)." ); + usage->addCommandLineOption( "--disable-output-poster", "Don't output the final poster file." ); + usage->addCommandLineOption( "--enable-output-tiles", "Output all tile files." ); + usage->addCommandLineOption( "--disable-output-tiles", "Don't output all tile files (Default)." ); + usage->addCommandLineOption( "--use-fb", "Use Frame Buffer for rendering tiles (Default, recommended)."); + usage->addCommandLineOption( "--use-fbo", "Use Frame Buffer Object for rendering tiles."); + usage->addCommandLineOption( "--use-pbuffer","Use Pixel Buffer for rendering tiles."); + usage->addCommandLineOption( "--use-pbuffer-rtt","Use Pixel Buffer RTT for rendering tiles."); + usage->addCommandLineOption( "--inactive", "Inactive capturing mode." ); + usage->addCommandLineOption( "--camera-eye ", "Set eye position in inactive mode." ); + usage->addCommandLineOption( "--camera-latlongheight ", "Set eye position on earth in inactive mode." ); + usage->addCommandLineOption( "--camera-hpr

", "Set eye rotation in inactive mode." ); + + if ( arguments.read("-h") || arguments.read("--help") ) + { + usage->write( std::cout ); + return 1; + } + + // Poster arguments + bool activeMode = true; + bool outputPoster = true, outputTiles = false; + int tileWidth = 640, tileHeight = 480; + int posterWidth = 640*2, posterHeight = 480*2; + std::string posterName = "poster.bmp", extName = "bmp"; + osg::Vec4 bgColor(0.2f, 0.2f, 0.6f, 1.0f); + osg::Camera::RenderTargetImplementation renderImplementation = osg::Camera::FRAME_BUFFER; + + while ( arguments.read("--inactive") ) { activeMode = false; } + while ( arguments.read("--color", bgColor.r(), bgColor.g(), bgColor.b()) ) {} + while ( arguments.read("--tilesize", tileWidth, tileHeight) ) {} + while ( arguments.read("--finalsize", posterWidth, posterHeight) ) {} + while ( arguments.read("--poster", posterName) ) {} + while ( arguments.read("--ext", extName) ) {} + while ( arguments.read("--enable-output-poster") ) { outputPoster = true; } + while ( arguments.read("--disable-output-poster") ) { outputPoster = false; } + while ( arguments.read("--enable-output-tiles") ) { outputTiles = true; } + while ( arguments.read("--disable-output-tiles") ) { outputTiles = false; } + while ( arguments.read("--use-fbo")) { renderImplementation = osg::Camera::FRAME_BUFFER_OBJECT; } + while ( arguments.read("--use-pbuffer")) { renderImplementation = osg::Camera::PIXEL_BUFFER; } + while ( arguments.read("--use-pbuffer-rtt")) { renderImplementation = osg::Camera::PIXEL_BUFFER_RTT; } + while ( arguments.read("--use-fb")) { renderImplementation = osg::Camera::FRAME_BUFFER; } + + // Camera settings for inactive screenshot + bool useLatLongHeight = true; + osg::Vec3d eye; + osg::Vec3d latLongHeight( 50.0, 10.0, 2000.0 ); + osg::Vec3d hpr( 0.0, 0.0, 0.0 ); + if ( arguments.read("--camera-eye", eye.x(), eye.y(), eye.z()) ) + { + useLatLongHeight = false; + activeMode = false; + } + else if ( arguments.read("--camera-latlongheight", latLongHeight.x(), latLongHeight.y(), latLongHeight.z()) ) + { + activeMode = false; + latLongHeight.x() = osg::DegreesToRadians( latLongHeight.x() ); + latLongHeight.y() = osg::DegreesToRadians( latLongHeight.y() ); + } + if ( arguments.read("--camera-hpr", hpr.x(), hpr.y(), hpr.z()) ) + { + activeMode = false; + hpr.x() = osg::DegreesToRadians( hpr.x() ); + hpr.y() = osg::DegreesToRadians( hpr.y() ); + hpr.z() = osg::DegreesToRadians( hpr.z() ); + } + + // Construct scene graph + osg::Node* scene = osgDB::readNodeFiles( arguments ); + if ( !scene ) scene = osgDB::readNodeFile( "cow.osg" ); + if ( !scene ) + { + std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl; + return 1; + } + + // Create camera for rendering tiles offscreen. FrameBuffer is recommended because it requires less memory. + osg::ref_ptr camera = new osg::Camera; + camera->setClearColor( bgColor ); + camera->setClearMask( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + camera->setReferenceFrame( osg::Transform::ABSOLUTE_RF ); + camera->setRenderOrder( osg::Camera::PRE_RENDER ); + camera->setRenderTargetImplementation( renderImplementation ); + camera->setViewport( 0, 0, tileWidth, tileHeight ); + camera->addChild( scene ); + + // Set the printer + osg::ref_ptr printer = new PosterPrinter; + printer->setTileSize( tileWidth, tileHeight ); + printer->setPosterSize( posterWidth, posterHeight ); + printer->setCamera( camera.get() ); + + osg::ref_ptr posterImage = 0; + if ( outputPoster ) + { + posterImage = new osg::Image; + posterImage->allocateImage( posterWidth, posterHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE ); + printer->setFinalPoster( posterImage.get() ); + printer->setOutputPosterName( posterName ); + } + + // Create root and start the viewer + osg::ref_ptr root = new osg::Switch; + root->addChild( scene, true ); + root->addChild( camera.get(), false ); + + osgViewer::Viewer viewer; + viewer.setUpViewInWindow( 100, 100, tileWidth, tileHeight ); + viewer.setSceneData( root.get() ); + viewer.getDatabasePager()->setDoPreCompile( false ); + + if ( activeMode ) + { + viewer.addEventHandler( new PrintPosterHandler(printer.get()) ); + viewer.addEventHandler( new osgViewer::StatsHandler ); + viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) ); + viewer.setCameraManipulator( new osgGA::TrackballManipulator ); + viewer.run(); + } + else + { + osg::Camera* camera = viewer.getCamera(); + if ( !useLatLongHeight ) computeViewMatrix( camera, eye, hpr ); + else computeViewMatrixOnEarth( camera, scene, latLongHeight, hpr ); + + osg::ref_ptr renderer = new CustomRenderer( camera ); + camera->setRenderer( renderer.get() ); + viewer.setThreadingModel( osgViewer::Viewer::SingleThreaded ); + + // Realize and initiate the first PagedLOD request + viewer.realize(); + viewer.frame(); + + printer->init( camera ); + while ( !printer->done() ) + { + viewer.advance(); + + // Keep updating and culling until full level of detail is reached + renderer->setCullOnly( true ); + while ( viewer.getDatabasePager()->getRequestsInProgress() ) + { + viewer.updateTraversal(); + viewer.renderingTraversals(); + } + + renderer->setCullOnly( false ); + printer->frame( viewer.getFrameStamp(), viewer.getSceneData() ); + viewer.renderingTraversals(); + } + } + return 0; +}