OpenSceneGraph/examples/osgautocapture/osgautocapture.cpp

420 lines
15 KiB
C++

/**
* TODO:
* 1) Change example to use offscreen rendering (pbuffer) so that it becomes a true commandline tool with now windows
* 2) Make example work with other threading models than SingleThreaded
* 3) Add support for autocapture to movies
*
*/
#include <osg/ArgumentParser>
#include <osg/CoordinateSystemNode>
#include <osg/Matrix>
#include <osg/NodeVisitor>
#include <osgUtil/IntersectionVisitor>
#include <osgUtil/GLObjectsVisitor>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgGA/DriveManipulator>
#include <osgGA/FlightManipulator>
#include <osgGA/KeySwitchMatrixManipulator>
#include <osgGA/TerrainManipulator>
#include <osgGA/TrackballManipulator>
#include <osgTerrain/Terrain>
#include <osgTerrain/GeometryTechnique>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/Renderer>
#include <iostream>
#include <sstream>
/** Helper class*/
template<class T>
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<T*>(&node);
if (result)
_foundNode = result;
else
traverse(node);
}
T* _foundNode;
};
/** Convenience function*/
template<class T>
T* findTopMostNodeOfType(osg::Node* node)
{
if (!node) return 0;
FindTopMostNodeOfTypeVisitor<T> fnotv;
node->accept(fnotv);
return fnotv._foundNode;
}
/** Capture the frame buffer and write image to disk*/
class WindowCaptureCallback : public osg::Camera::DrawCallback
{
public:
WindowCaptureCallback(GLenum readBuffer, const std::string& name):
_readBuffer(readBuffer),
_fileName(name)
{
_image = new osg::Image;
}
virtual void operator () (osg::RenderInfo& renderInfo) const
{
#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE)
glReadBuffer(_readBuffer);
#else
osg::notify(osg::NOTICE)<<"Error: GLES unable to do glReadBuffer"<<std::endl;
#endif
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
osg::GraphicsContext* gc = renderInfo.getState()->getGraphicsContext();
if (gc->getTraits())
{
GLenum pixelFormat;
if (gc->getTraits()->alpha)
pixelFormat = GL_RGBA;
else
pixelFormat = GL_RGB;
#if defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE)
if (pixelFormat == GL_RGB)
{
GLint value = 0;
#ifndef GL_IMPLEMENTATION_COLOR_READ_FORMAT
#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B
#endif
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &value);
if ( value != GL_RGB ||
value != GL_UNSIGNED_BYTE )
{
pixelFormat = GL_RGBA;//always supported
}
}
#endif
int width = gc->getTraits()->width;
int height = gc->getTraits()->height;
std::cout<<"Capture: size="<<width<<"x"<<height<<", format="<<(pixelFormat == GL_RGBA ? "GL_RGBA":"GL_RGB")<<std::endl;
_image->readPixels(0, 0, width, height, pixelFormat, GL_UNSIGNED_BYTE);
}
if (!_fileName.empty())
{
std::cout << "Writing to: " << _fileName << std::endl;
osgDB::writeImageFile(*_image, _fileName);
}
}
protected:
GLenum _readBuffer;
std::string _fileName;
osg::ref_ptr<osg::Image> _image;
mutable OpenThreads::Mutex _mutex;
};
/** Do Culling only while loading PagedLODs*/
class CustomRenderer : public osgViewer::Renderer
{
public:
CustomRenderer(osg::Camera* camera)
: osgViewer::Renderer(camera),
_cullOnly(true)
{
}
/** Set flag to omit drawing in renderingTraversals */
void setCullOnly(bool on) { _cullOnly = on; }
virtual void operator () (osg::GraphicsContext* /*context*/)
{
if (_graphicsThreadDoesCull)
{
if (_cullOnly)
cull();
else
cull_draw();
}
}
virtual void cull()
{
osgUtil::SceneView* sceneView = _sceneView[0].get();
if (!sceneView || _done ) return;
updateSceneView(sceneView);
osgViewer::View* view = dynamic_cast<osgViewer::View*>(_camera->getView());
if (view) sceneView->setFusionDistance(view->getFusionDistanceMode(), view->getFusionDistanceValue());
sceneView->inheritCullSettings(*(sceneView->getCamera()));
sceneView->cull();
}
bool _cullOnly;
};
//===============================================================
// MAIN
//
int main( int argc, char **argv )
{
osg::ArgumentParser arguments(&argc, argv);
osg::ApplicationUsage* usage = arguments.getApplicationUsage();
usage->setApplicationName(arguments.getApplicationName());
usage->setDescription(arguments.getApplicationName()+" loads a model, sets a camera position and automatically captures screenshot to disk");
usage->setCommandLineUsage(arguments.getApplicationName()+" [options] filename ...");
usage->addCommandLineOption("--camera <lat> <lon> <alt> <heading> <incline> <roll>", "Specify camera position for image capture. Angles are specified in degrees and altitude in meters above sealevel (e.g. --camera 55 10 300000 0 30 0)");
usage->addCommandLineOption("--filename", "Filename for the captured image", "autocapture.jpg");
usage->addCommandLineOption("--db-threads", "Number of DatabasePager threads to use", "2");
usage->addCommandLineOption("--active", "Use active rendering instead of passive / lazy rendering");
usage->addCommandLineOption("--pbuffer", "Render into a pbuffer, not into a window");
// Construct the viewer and register options arguments.
osgViewer::Viewer viewer(arguments);
if (arguments.argc()<=1)
{
arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION);
return 1;
}
// Get user specified number of DatabaseThreads
int dbThreads = 2;
arguments.read("--db-threads", dbThreads);
if (dbThreads < 1) dbThreads = 1;
osg::DisplaySettings::instance()->setNumOfDatabaseThreadsHint(dbThreads);
// Get user specified file name
std::string fileName("autocapture.jpg");
arguments.read("--filename", fileName);
// Rendering mode is passive by default
bool activeMode = false;
if (arguments.read("--active"))
activeMode = true;
bool use_pbuffer = false;
if (arguments.read("--pbuffer")) {
if (!activeMode) {
use_pbuffer = true;
} else {
osg::notify(osg::NOTICE)<<"ignoring --pbuffer because --active specified on commandline"<<std::endl;
}
}
if (use_pbuffer) {
osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits(ds);
if (viewer.getCamera()->getGraphicsContext() && viewer.getCamera()->getGraphicsContext()->getTraits()) {
//use viewer settings for window size
osg::ref_ptr<const osg::GraphicsContext::Traits> src_traits = viewer.getCamera()->getGraphicsContext()->getTraits();
traits->screenNum = src_traits->screenNum;
traits->displayNum = src_traits->displayNum;
traits->hostName = src_traits->hostName;
traits->width = src_traits->width;
traits->height = src_traits->height;
traits->red = src_traits->red;
traits->green = src_traits->green;
traits->blue = src_traits->blue;
traits->alpha = src_traits->alpha;
traits->depth = src_traits->depth;
traits->pbuffer = true;
} else {
//viewer would use fullscreen size (unknown here) pbuffer will use 4096 x4096 (or best available)
traits->width = 1 << 12;
traits->height = 1 << 12;
traits->pbuffer = true;
traits->readDISPLAY();
traits->setUndefinedScreenDetailsToDefaultScreen();
}
osg::ref_ptr<osg::GraphicsContext> pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
if (pbuffer.valid())
{
osg::notify(osg::NOTICE)<<"Pixel buffer has been created successfully."<<std::endl;
osg::ref_ptr<osg::Camera> camera = new osg::Camera(*viewer.getCamera());
camera->setGraphicsContext(pbuffer.get());
camera->setViewport(new osg::Viewport(0,0,traits->width,traits->height));
GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);
viewer.setCamera(camera.get());
}
else
{
osg::notify(osg::NOTICE)<<"Pixel buffer has not been created successfully."<<std::endl;
}
}
// Read camera settings for screenshot
double lat=50;
double lon=10;
double alt=2000;
double heading=0;
double incline=45;
double roll=0;
bool camera_specified=false;
if (arguments.read("--camera", lat, lon, alt, heading, incline, roll))
{
camera_specified=true;
lat = osg::DegreesToRadians(lat);
lon = osg::DegreesToRadians(lon);
heading = osg::DegreesToRadians(heading);
incline = osg::DegreesToRadians(incline);
roll = osg::DegreesToRadians(roll);
}
// load the data
osg::ref_ptr<osg::Node> loadedModel = osgDB::readRefNodeFiles(arguments);
if (!loadedModel)
{
std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl;
return 1;
}
// any option left unread are converted into errors to write out later.
arguments.reportRemainingOptionsAsUnrecognized();
// report any errors if they have occurred when parsing the program arguments.
if (arguments.errors())
{
arguments.writeErrorMessages(std::cout);
return 1;
}
// Setup specified camera
if (camera_specified)
{
osg::CoordinateSystemNode* csn = findTopMostNodeOfType<osg::CoordinateSystemNode>(loadedModel.get());
if(!csn) return 1;
// Compute eye point in world coordinates
osg::Vec3d eye;
csn->getEllipsoidModel()->convertLatLongHeightToXYZ(lat, lon, alt, eye.x(), eye.y(), eye.z());
// Build matrix for computing target vector
osg::Matrixd target_matrix = osg::Matrixd::rotate(-heading, osg::Vec3d(1,0,0),
-lat, osg::Vec3d(0,1,0),
lon, 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(incline, 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(roll, target);
up = roll_matrix.preMult(up);
viewer.getCamera()->setViewMatrixAsLookAt(eye, eye+target, up);
}
else
{
// Only add camera manipulators if camera is not specified
camera_specified=false;
osg::ref_ptr<osgGA::KeySwitchMatrixManipulator> keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator;
keyswitchManipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() );
keyswitchManipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() );
keyswitchManipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() );
keyswitchManipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() );
viewer.setCameraManipulator( keyswitchManipulator.get() );
}
// Optimize DatabasePager for auto-capture
osgDB::DatabasePager* pager = viewer.getDatabasePager();
pager->setDoPreCompile(false);
// Install custom renderer
osg::ref_ptr<CustomRenderer> customRenderer = new CustomRenderer(viewer.getCamera());
viewer.getCamera()->setRenderer(customRenderer.get());
// Override threading model
viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded);
// Set the final SceneData to show
viewer.setSceneData(loadedModel.get());
// Realize GUI
viewer.realize();
//--- Load PageLOD tiles ---
// Initiate the first PagedLOD request
viewer.frame();
osg::Timer_t beforeLoadTick = osg::Timer::instance()->tick();
// Keep updating and culling until full level of detail is reached
while(!viewer.done() && pager->getRequestsInProgress())
{
// std::cout <<pager->getRequestsInProgress()<<" ";
viewer.updateTraversal();
viewer.renderingTraversals();
}
// std::cout<<std::endl;
osg::Timer_t afterLoadTick = osg::Timer::instance()->tick();
std::cout<<"Load and Compile time = "<<osg::Timer::instance()->delta_s(beforeLoadTick, afterLoadTick)<<" seconds"<<std::endl;
// Do cull and draw to render the scene correctly
customRenderer->setCullOnly(false);
//--- Capture the image!!! ---
if (!activeMode)
{
// Add the WindowCaptureCallback now that we have full resolution
GLenum buffer = viewer.getCamera()->getGraphicsContext()->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
viewer.getCamera()->setFinalDrawCallback(new WindowCaptureCallback(buffer, fileName));
osg::Timer_t beforeRenderTick = osg::Timer::instance()->tick();
// Do rendering with capture callback
viewer.renderingTraversals();
osg::Timer_t afterRenderTick = osg::Timer::instance()->tick();
std::cout<<"Rendering time = "<<osg::Timer::instance()->delta_s(beforeRenderTick, afterRenderTick) <<" seconds"<<std::endl;
return 0;
}
else
{
return viewer.run();
}
}