OpenSceneGraph/examples/osgphotoalbum/osgphotoalbum.cpp
2018-04-23 11:03:37 +01:00

780 lines
23 KiB
C++

/* OpenSceneGraph example, osgphotoalbum.
*
* 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 <osg/Notify>
#include <osg/MatrixTransform>
#include <osg/Switch>
#include <osg/PolygonOffset>
#include <osg/CullFace>
#include <osgUtil/Optimizer>
#include <osgDB/FileNameUtils>
#include <osgText/Text>
#include <osgViewer/Viewer>
#include "ImageReaderWriter.h"
#include <iostream>
using namespace osg;
// now register with Registry to instantiate the above reader/writer,
// declaring in main so that the code to set up PagedLOD can get a handle
// to the ImageReaderWriter's
osgDB::RegisterReaderWriterProxy<ImageReaderWriter> g_ImageReaderWriter;
class Album;
class Page : public osg::Transform
{
public:
static Page* createPage(Album* album, unsigned int pageNo, const std::string& frontFileName, const std::string& backFileName, float width, float height)
{
osg::ref_ptr<Page> page = new Page(album, pageNo, frontFileName, backFileName, width, height);
if (page.valid()) return page.release();
else return 0;
}
virtual void traverse(osg::NodeVisitor& nv);
void setRotation(float angle)
{
_rotation = angle;
_targetRotation = angle;
dirtyBound();
}
float getRotation() const { return _rotation; }
void rotateTo(float angle, float timeToRotateBy)
{
_targetRotation = angle;
_targetTime = timeToRotateBy;
}
bool rotating() const { return _targetRotation!=_rotation; }
void setPageVisible(bool frontVisible,bool backVisible)
{
_switch->setValue(0,!frontVisible && !backVisible);
_switch->setValue(1,frontVisible);
_switch->setValue(2,backVisible);
}
osg::Switch* getSwitch() { return _switch.get(); }
const osg::Switch* getSwitch() const { return _switch.get(); }
public:
virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,osg::NodeVisitor*) const
{
if (_referenceFrame==RELATIVE_RF)
{
matrix.preMult(getMatrix());
}
else // absolute
{
matrix = getMatrix();
}
return true;
}
/** Get the transformation matrix which moves from world coords to local coords.*/
virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,osg::NodeVisitor*) const
{
const osg::Matrix& inverse = getInverseMatrix();
if (_referenceFrame==RELATIVE_RF)
{
matrix.postMult(inverse);
}
else // absolute
{
matrix = inverse;
}
return true;
}
osg::Matrix getMatrix() const { return _pageOffset*osg::Matrix::rotate(-_rotation,0.0f,0.0f,1.0f); }
osg::Matrix getInverseMatrix() const { return osg::Matrix::inverse(getMatrix()); }
protected:
Page(Album* album, unsigned int pageNo, const std::string& frontFileName, const std::string& backFileName, float width, float height);
float _rotation;
osg::Matrix _pageOffset;
float _targetRotation;
float _targetTime;
float _lastTimeTraverse;
osg::ref_ptr<osg::Switch> _switch;
};
class Album : public osg::Referenced
{
public:
Album(osg::ArgumentParser& ap, float width, float height);
osg::Group* getScene() { return _group.get(); }
const osg::Group* getScene() const { return _group.get(); }
osg::Matrix getPageOffset(unsigned int pageNo) const;
bool nextPage(float timeToRotateBy) { return gotoPage(_currentPageNo+1,timeToRotateBy); }
bool previousPage(float timeToRotateBy) { return _currentPageNo>=1?gotoPage(_currentPageNo-1,timeToRotateBy):false; }
bool gotoPage(unsigned int pageNo, float timeToRotateBy);
osg::StateSet* getBackgroundStateSet() { return _backgroundStateSet.get(); }
void setVisibility();
protected:
typedef std::vector< osg::ref_ptr<Page> > PageList;
osg::ref_ptr<osg::Group> _group;
PageList _pages;
osg::ref_ptr<osg::StateSet> _backgroundStateSet;
unsigned int _currentPageNo;
float _radiusOfRings;
float _startAngleOfPages;
float _deltaAngleBetweenPages;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Page::Page(Album* album, unsigned int pageNo, const std::string& frontFileName, const std::string& backFileName, float width, float height)
{
// set up transform parts.
_rotation = 0;
_targetRotation = 0;
_targetTime = 0;
_lastTimeTraverse = 0;
_pageOffset = album->getPageOffset(pageNo);
setNumChildrenRequiringUpdateTraversal(1);
// set up subgraph
osgDB::ReaderWriter* readerWriter = osgDB::Registry::instance()->getReaderWriterForExtension("gdal");
if (!readerWriter)
{
std::cout<<"Error: GDAL plugin not available, cannot proceed with database creation"<<std::endl;
}
_switch = new osg::Switch;
ImageReaderWriter* rw = g_ImageReaderWriter.get();
// set up non visible page.
osg::Group* non_visible_page = new osg::Group;
_switch->addChild(non_visible_page);
{
osg::Geometry* geom = new osg::Geometry;
geom->setStateSet(album->getBackgroundStateSet());
osg::Vec3Array* coords = new osg::Vec3Array(8);
(*coords)[0].set(0.0f,0.0f,0.0f);
(*coords)[1].set(0.0f,0.0f,height);
(*coords)[2].set(0.0f,0.0f,height);
(*coords)[3].set(width,0.0f,height);
(*coords)[4].set(width,0.0f,height);
(*coords)[5].set(width,0.0f,0.0f);
(*coords)[6].set(width,0.0f,0.0f);
(*coords)[7].set(0.0f,0.0f,0.0f);
geom->setVertexArray(coords);
osg::Vec3Array* normals = new osg::Vec3Array(8);
(*normals)[0].set(-1.0f,0.0f,0.0f);
(*normals)[1].set(-1.0f,0.0f,0.0f);
(*normals)[2].set(0.0f,0.0f,-1.0f);
(*normals)[3].set(0.0f,0.0f,-1.0f);
(*normals)[4].set(1.0f,0.0f,0.0f);
(*normals)[5].set(1.0f,0.0f,0.0f);
(*normals)[6].set(0.0f,0.0f,1.0f);
(*normals)[7].set(0.0f,0.0f,1.0f);
geom->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
osg::Vec2Array* tcoords = new osg::Vec2Array(8);
(*tcoords)[0].set(0.0f,0.0f);
(*tcoords)[1].set(0.0f,1.0f);
(*tcoords)[2].set(0.0f,1.0f);
(*tcoords)[3].set(1.0f,1.0f);
(*tcoords)[4].set(1.0f,1.0f);
(*tcoords)[5].set(0.0f,1.0f);
(*tcoords)[6].set(0.0f,1.0f);
(*tcoords)[7].set(0.0f,0.0f);
geom->setTexCoordArray(0,tcoords);
osg::Vec4Array* colours = new osg::Vec4Array(1);
(*colours)[0].set(1.0f,1.0f,1.0,1.0f);
geom->setColorArray(colours, osg::Array::BIND_OVERALL);
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES,0,8));
// set up the geode.
osg::Geode* geode = new osg::Geode;
geode->addDrawable(geom);
non_visible_page->addChild(geode);
}
// set up visible page.
osg::Group* front_page = new osg::Group;
_switch->addChild(front_page);
{
osg::Geometry* geom = new osg::Geometry;
geom->setStateSet(album->getBackgroundStateSet());
osg::Vec3Array* coords = new osg::Vec3Array(4);
(*coords)[0].set(0.0f,0.0,height);
(*coords)[1].set(0.0f,0.0,0);
(*coords)[2].set(width,0.0,0);
(*coords)[3].set(width,0.0,height);
geom->setVertexArray(coords);
osg::Vec3Array* normals = new osg::Vec3Array(1);
(*normals)[0].set(0.0f,-1.0f,0.0f);
geom->setNormalArray(normals, osg::Array::BIND_OVERALL);
osg::Vec2Array* tcoords = new osg::Vec2Array(4);
(*tcoords)[0].set(0.0f,1.0f);
(*tcoords)[1].set(0.0f,0.0f);
(*tcoords)[2].set(1.0f,0.0f);
(*tcoords)[3].set(1.0f,1.0f);
geom->setTexCoordArray(0,tcoords);
osg::Vec4Array* colours = new osg::Vec4Array(1);
(*colours)[0].set(1.0f,1.0f,1.0,1.0f);
geom->setColorArray(colours, osg::Array::BIND_OVERALL);
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4));
// set up the geode.
osg::Geode* geode = new osg::Geode;
geode->addDrawable(geom);
front_page->addChild(geode);
}
if (!frontFileName.empty())
{
float cut_off_distance = 8.0f;
float max_visible_distance = 300.0f;
osg::Vec3 center(width*0.5f,0.0f,height*0.5f);
osgText::Text* text = new osgText::Text;
text->setFont("fonts/arial.ttf");
text->setPosition(center);
text->setCharacterSize(height/20.0f);
text->setAlignment(osgText::Text::CENTER_CENTER);
text->setAxisAlignment(osgText::Text::XZ_PLANE);
text->setColor(osg::Vec4(1.0f,1.0f,0.0f,1.0f));
text->setText(std::string("Loading ")+frontFileName);
osg::Geode* geode = new osg::Geode;
geode->addDrawable(text);
osg::PagedLOD* pagedlod = new osg::PagedLOD;
pagedlod->setCenter(center);
pagedlod->setRadius(1.6f);
pagedlod->setNumChildrenThatCannotBeExpired(2);
pagedlod->setRange(0,max_visible_distance,1e7);
pagedlod->addChild(geode);
pagedlod->setRange(1,cut_off_distance,max_visible_distance);
pagedlod->setFileName(1,rw->insertReference(frontFileName,256,width,height,false));
pagedlod->setRange(2,0.0f,cut_off_distance);
pagedlod->setFileName(2,rw->insertReference(frontFileName,1024,width,height,false));
front_page->addChild(pagedlod);
}
// set up back of page.
osg::Group* back_page = new osg::Group;
_switch->addChild(back_page);
{
osg::Geometry* geom = new osg::Geometry;
geom->setStateSet(album->getBackgroundStateSet());
osg::Vec3Array* coords = new osg::Vec3Array(4);
(*coords)[0].set(width,0.0,height);
(*coords)[1].set(width,0.0,0);
(*coords)[2].set(0.0f,0.0,0);
(*coords)[3].set(0.0f,0.0,height);
geom->setVertexArray(coords);
osg::Vec3Array* normals = new osg::Vec3Array(1);
(*normals)[0].set(0.0f,1.0f,0.0f);
geom->setNormalArray(normals, osg::Array::BIND_OVERALL);
osg::Vec2Array* tcoords = new osg::Vec2Array(4);
(*tcoords)[0].set(1.0f,1.0f);
(*tcoords)[1].set(1.0f,0.0f);
(*tcoords)[2].set(0.0f,0.0f);
(*tcoords)[3].set(0.0f,1.0f);
geom->setTexCoordArray(0,tcoords);
osg::Vec4Array* colours = new osg::Vec4Array(1);
(*colours)[0].set(1.0f,1.0f,1.0,1.0f);
geom->setColorArray(colours, osg::Array::BIND_OVERALL);
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4));
// set up the geode.
osg::Geode* geode = new osg::Geode;
geode->addDrawable(geom);
back_page->addChild(geode);
}
if (!backFileName.empty())
{
float cut_off_distance = 8.0f;
float max_visible_distance = 300.0f;
osg::Vec3 center(width*0.5f,0.0f,height*0.5f);
osgText::Text* text = new osgText::Text;
text->setFont("fonts/arial.ttf");
text->setPosition(center);
text->setCharacterSize(height/20.0f);
text->setAlignment(osgText::Text::CENTER_CENTER);
text->setAxisAlignment(osgText::Text::REVERSED_XZ_PLANE);
text->setColor(osg::Vec4(1.0f,1.0f,0.0f,1.0f));
text->setText(std::string("Loading ")+backFileName);
osg::Geode* geode = new osg::Geode;
geode->addDrawable(text);
osg::PagedLOD* pagedlod = new osg::PagedLOD;
pagedlod->setCenter(center);
pagedlod->setRadius(1.6f);
pagedlod->setNumChildrenThatCannotBeExpired(2);
pagedlod->setRange(0,max_visible_distance,1e7);
pagedlod->addChild(geode);
pagedlod->setRange(1,cut_off_distance,max_visible_distance);
pagedlod->setFileName(1,rw->insertReference(backFileName,256,width,height,true));
pagedlod->setRange(2,0.0f,cut_off_distance);
pagedlod->setFileName(2,rw->insertReference(backFileName,1024,width,height,true));
back_page->addChild(pagedlod);
}
addChild(_switch.get());
}
void Page::traverse(osg::NodeVisitor& nv)
{
// if app traversal update the frame count.
if (nv.getVisitorType()==osg::NodeVisitor::UPDATE_VISITOR)
{
const osg::FrameStamp* framestamp = nv.getFrameStamp();
if (framestamp)
{
double t = framestamp->getSimulationTime();
if (_rotation!=_targetRotation)
{
if (t>=_targetTime) _rotation = _targetRotation;
else _rotation += (_targetRotation-_rotation)*(t-_lastTimeTraverse)/(_targetTime-_lastTimeTraverse);
dirtyBound();
}
_lastTimeTraverse = t;
}
}
Transform::traverse(nv);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Album::Album(osg::ArgumentParser& arguments, float width, float height)
{
typedef std::vector<std::string> FileList;
FileList fileList;
for(int pos=1;pos<arguments.argc();++pos)
{
if (arguments.isString(pos))
{
std::string filename(arguments[pos]);
if (osgDB::getLowerCaseFileExtension(filename)=="album")
{
PhotoArchive* photoArchive = PhotoArchive::open(filename);
if (photoArchive)
{
g_ImageReaderWriter.get()->addPhotoArchive(photoArchive);
photoArchive->getImageFileNameList(fileList);
}
}
else
{
fileList.push_back(arguments[pos]);
}
}
}
_radiusOfRings = 0.02;
_startAngleOfPages = 0.0f;
_deltaAngleBetweenPages = osg::PI/(float)fileList.size();
_group = new osg::Group;
_group->getOrCreateStateSet()->setAttributeAndModes(new osg::CullFace,osg::StateAttribute::ON);
_backgroundStateSet = new osg::StateSet;
_backgroundStateSet->setAttributeAndModes(new osg::PolygonOffset(1.0f,1.0f),osg::StateAttribute::ON);
// load the images.
unsigned int i;
for(i=0;i<fileList.size();i+=2)
{
Page* page = i+1<fileList.size()?
Page::createPage(this,_pages.size(),fileList[i],fileList[i+1], width, height):
Page::createPage(this,_pages.size(),fileList[i],"", width, height);
if (page)
{
_pages.push_back(page);
_group->addChild(page);
}
}
setVisibility();
}
osg::Matrix Album::getPageOffset(unsigned int pageNo) const
{
float angleForPage = _startAngleOfPages+_deltaAngleBetweenPages*(float)pageNo;
osg::Vec3 delta(_radiusOfRings*sinf(angleForPage),-_radiusOfRings*cosf(angleForPage),0.0f);
return osg::Matrix::translate(delta);
}
bool Album::gotoPage(unsigned int pageNo, float timeToRotateBy)
{
if (pageNo>=_pages.size()) return false;
if (pageNo>_currentPageNo)
{
for(unsigned int i=_currentPageNo;i<pageNo;++i)
{
_pages[i]->rotateTo(osg::PI,timeToRotateBy);
}
_currentPageNo = pageNo;
return true;
}
else if (pageNo<_currentPageNo)
{
for(unsigned int i=pageNo;i<_currentPageNo;++i)
{
_pages[i]->rotateTo(0,timeToRotateBy);
}
_currentPageNo = pageNo;
return true;
}
return false;
}
void Album::setVisibility()
{
for(unsigned int i=0;i<_pages.size();++i)
{
bool front_visible = _pages[i]->rotating() ||
(i>0?_pages[i-1]->rotating():false) ||
i==_currentPageNo ||
i==0;
bool back_visible = _pages[i]->rotating() ||
((i+1)<_pages.size()?_pages[i+1]->rotating():false) ||
i==_currentPageNo-1 ||
i==_pages.size()-1;
_pages[i]->setPageVisible(front_visible,back_visible);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class SlideEventHandler : public osgGA::GUIEventHandler
{
public:
SlideEventHandler();
META_Object(osgStereImageApp,SlideEventHandler);
void set(Album* album, float timePerSlide, bool autoSteppingActive);
virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter&);
virtual void getUsage(osg::ApplicationUsage& usage) const;
protected:
~SlideEventHandler() {}
SlideEventHandler(const SlideEventHandler&,const osg::CopyOp&) {}
osg::ref_ptr<Album> _album;
bool _firstTraversal;
double _previousTime;
double _timePerSlide;
bool _autoSteppingActive;
};
SlideEventHandler::SlideEventHandler():
_album(0),
_firstTraversal(true),
_previousTime(-1.0f),
_timePerSlide(5.0),
_autoSteppingActive(false)
{
}
void SlideEventHandler::set(Album* album, float timePerSlide, bool autoSteppingActive)
{
_album = album;
_timePerSlide = timePerSlide;
_autoSteppingActive = autoSteppingActive;
}
bool SlideEventHandler::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter&)
{
switch(ea.getEventType())
{
case(osgGA::GUIEventAdapter::KEYDOWN):
{
if (ea.getKey()=='a')
{
_autoSteppingActive = !_autoSteppingActive;
_previousTime = ea.getTime();
return true;
}
else if (ea.getKey()=='n')
{
_album->nextPage(ea.getTime()+1.0f);
return true;
}
else if (ea.getKey()=='p')
{
_album->previousPage(ea.getTime()+1.0f);
return true;
}
return false;
}
case(osgGA::GUIEventAdapter::FRAME):
{
if (_autoSteppingActive)
{
if (_firstTraversal)
{
_firstTraversal = false;
_previousTime = ea.getTime();
}
else if (ea.getTime()-_previousTime>_timePerSlide)
{
_previousTime = ea.getTime();
_album->nextPage(ea.getTime()+1.0f);
}
}
_album->setVisibility();
}
default:
return false;
}
}
void SlideEventHandler::getUsage(osg::ApplicationUsage& usage) const
{
usage.addKeyboardMouseBinding("Space","Reset the image position to center");
usage.addKeyboardMouseBinding("a","Toggle on/off the automatic advancement for image to image");
usage.addKeyboardMouseBinding("n","Advance to next image");
usage.addKeyboardMouseBinding("p","Move to previous image");
}
int main( int argc, char **argv )
{
// use an ArgumentParser object to manage the program arguments.
osg::ArgumentParser arguments(&argc,argv);
// set up the usage document, in case we need to print out how to use this program.
arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the example which demonstrates use node masks to create stereo images.");
arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] image_file [image_file]");
arguments.getApplicationUsage()->addCommandLineOption("-d <float>","Time delay in seconds between the display of successive image pairs when in auto advance mode.");
arguments.getApplicationUsage()->addCommandLineOption("-a","Enter auto advance of image pairs on start up.");
arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display this information");
arguments.getApplicationUsage()->addCommandLineOption("--create <filename>","Create an photo archive of specified files");
// construct the viewer.
osgViewer::Viewer viewer(arguments);
viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded);
// register the handler to add keyboard and mouse handling.
SlideEventHandler* seh = new SlideEventHandler();
viewer.addEventHandler(seh);
// read any time delay argument.
float timeDelayBetweenSlides = 5.0f;
while (arguments.read("-d",timeDelayBetweenSlides)) {}
bool autoSteppingActive = false;
while (arguments.read("-a")) autoSteppingActive = true;
// if user request help write it out to cout.
if (arguments.read("-h") || arguments.read("--help"))
{
arguments.getApplicationUsage()->write(std::cout);
return 1;
}
std::string archiveName;
while (arguments.read("--create",archiveName)) {}
// 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;
}
if (arguments.argc()<=1)
{
arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION);
return 1;
}
if (!archiveName.empty())
{
// archive name set to create
PhotoArchive::FileNameList fileNameList;
for(int i=1;i<arguments.argc();++i)
{
if (arguments.isString(i)) fileNameList.push_back(std::string(arguments[i]));
}
PhotoArchive::buildArchive(archiveName,fileNameList);
return 0;
}
// now the windows have been realized we switch off the cursor to prevent it
// distracting the people seeing the stereo images.
double fovy, aspectRatio, zNear, zFar;
viewer.getCamera()->getProjectionMatrixAsPerspective(fovy, aspectRatio, zNear, zFar);
fovy = osg::DegreesToRadians(fovy);
double fovx = atan(tan(fovy*0.5)*aspectRatio)*2.0;
float radius = 1.0f;
float width = 2*radius*tan(fovx*0.5f);
float height = 2*radius*tan(fovy*0.5f);
osg::ref_ptr<Album> album = new Album(arguments,width,height);
// create the scene from the file list.
osg::ref_ptr<osg::Group> rootNode = album->getScene();
if (!rootNode) return 0;
//osgDB::writeNodeFile(*rootNode,"test.osgt");
// set the scene to render
viewer.setSceneData(album->getScene());
// set up the SlideEventHandler.
seh->set(album.get(),timeDelayBetweenSlides,autoSteppingActive);
viewer.realize();
// switch off the cursor
osgViewer::Viewer::Windows windows;
viewer.getWindows(windows);
for(osgViewer::Viewer::Windows::iterator itr = windows.begin();
itr != windows.end();
++itr)
{
(*itr)->useCursor(false);
}
return viewer.run();
}