418 lines
14 KiB
C++
418 lines
14 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;
|
|
}
|
|
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 coordiantes
|
|
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();
|
|
}
|
|
}
|