root/OpenSceneGraph/trunk/examples/osgstereoimage/osgstereoimage.cpp @ 7228

Revision 7228, 20.9 kB (checked in by robert, 7 years ago)

From Luc Frauciel, "I've done 2 main modifications :
1) added texture->setResizeNonPowerOfTwoHint(false); when loading an
image. It speeds up by 10 the loading of large images.
2) added a --disk option : only a filelist is read, images are only
loaded when needed. It allows to handle very large set of very large
images that would not fit in memory. Nothing change when the option is
not set."

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/* OpenSceneGraph example, osgstereoimage.
2*
3*  Permission is hereby granted, free of charge, to any person obtaining a copy
4*  of this software and associated documentation files (the "Software"), to deal
5*  in the Software without restriction, including without limitation the rights
6*  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7*  copies of the Software, and to permit persons to whom the Software is
8*  furnished to do so, subject to the following conditions:
9*
10*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11*  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12*  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13*  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14*  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15*  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
16*  THE SOFTWARE.
17*/
18
19#include <osgViewer/Viewer>
20#include <osgDB/ReadFile>
21#include <osgDB/WriteFile>
22#include <osgUtil/Optimizer>
23
24#include <osg/Geode>
25#include <osg/Notify>
26#include <osg/MatrixTransform>
27#include <osg/Switch>
28#include <osg/TexMat>
29#include <osg/Texture2D>
30
31#include <iostream>
32
33typedef std::vector<std::string> FileList;
34
35osg::Geode* createSectorForImage(osg::Image* image, osg::TexMat* texmat, float s,float t, float radius, float height, float length)
36{
37
38    int numSegments = 20;
39    float Theta = length/radius;
40    float dTheta = Theta/(float)(numSegments-1);
41   
42    float ThetaZero = height*s/(t*radius);
43   
44    // set up the texture.
45    osg::Texture2D* texture = new osg::Texture2D;
46    texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR);
47    texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR);
48    texture->setResizeNonPowerOfTwoHint(false);
49    texture->setImage(image);
50
51    // set up the drawstate.
52    osg::StateSet* dstate = new osg::StateSet;
53    dstate->setMode(GL_CULL_FACE,osg::StateAttribute::OFF);
54    dstate->setMode(GL_LIGHTING,osg::StateAttribute::OFF);
55    dstate->setTextureAttributeAndModes(0, texture,osg::StateAttribute::ON);
56    dstate->setTextureAttribute(0, texmat);
57
58    // set up the geoset.
59    osg::Geometry* geom = new osg::Geometry;
60    geom->setStateSet(dstate);
61
62    osg::Vec3Array* coords = new osg::Vec3Array();
63    osg::Vec2Array* tcoords = new osg::Vec2Array();
64   
65    int i;
66    float angle = -Theta/2.0f;
67    for(i=0;
68        i<numSegments;
69        ++i, angle+=dTheta)
70    {
71        coords->push_back(osg::Vec3(sinf(angle)*radius,cosf(angle)*radius,height*0.5f)); // top
72        coords->push_back(osg::Vec3(sinf(angle)*radius,cosf(angle)*radius,-height*0.5f)); // bottom.
73       
74        tcoords->push_back(osg::Vec2(angle/ThetaZero+0.5f,1.0f)); // top
75        tcoords->push_back(osg::Vec2(angle/ThetaZero+0.5f,0.0f)); // bottom.
76
77    }
78
79    osg::Vec4Array* colors = new osg::Vec4Array();
80    colors->push_back(osg::Vec4(1.0f,1.0f,1.0f,1.0f));
81
82    osg::DrawArrays* elements = new osg::DrawArrays(osg::PrimitiveSet::QUAD_STRIP,0,coords->size());
83
84   
85
86    geom->setVertexArray(coords);
87    geom->setTexCoordArray(0,tcoords);
88    geom->setColorArray(colors);
89    geom->setColorBinding(osg::Geometry::BIND_OVERALL);
90
91    geom->addPrimitiveSet(elements);
92
93    // set up the geode.
94    osg::Geode* geode = new osg::Geode;
95    geode->addDrawable(geom);
96
97    return geode;
98
99}
100
101osg::Group * loadImages(std::string image1, std::string image2, osg::TexMat* texmatLeft, osg::TexMat* texmatRight, float radius, float height, float length) {
102        osg::ref_ptr<osg::Image> imageLeft = osgDB::readImageFile(image1);
103        osg::ref_ptr<osg::Image> imageRight = osgDB::readImageFile(image2);
104        if (imageLeft.valid() && imageRight.valid())
105        {
106            float average_s = (imageLeft->s()+imageRight->s())*0.5f;
107            float average_t = (imageLeft->t()+imageRight->t())*0.5f;
108            osg::Geode* geodeLeft = createSectorForImage(imageLeft.get(),texmatLeft,average_s,average_t, radius, height, length);
109            geodeLeft->setNodeMask(0x01);
110
111            osg::Geode* geodeRight = createSectorForImage(imageRight.get(),texmatRight,average_s,average_t, radius, height, length);
112            geodeRight->setNodeMask(0x02);
113
114            osg::Group * imageGroup = new osg::Group;
115           
116            imageGroup->addChild(geodeLeft);
117            imageGroup->addChild(geodeRight);
118            return imageGroup;
119        }
120        else
121        {
122            std::cout << "Warning: Unable to load both image files, '"<<image1<<"' & '"<<image2<<"', required for stereo imaging."<<std::endl;
123            return 0;
124        }
125}
126
127// create a switch containing a set of child each containing a
128// stereo image pair.
129osg::Switch* createScene(FileList fileList, osg::TexMat* texmatLeft, osg::TexMat* texmatRight, float radius, float height, float length)
130{
131    osg::Switch* sw = new osg::Switch;
132
133    // load the images.
134    for(unsigned int i=0;i+1<fileList.size();i+=2)
135    {
136        osg::Group * imageGroup = loadImages(fileList[i],fileList[i+1],texmatLeft,texmatRight, radius,  height, length);
137        if (imageGroup) sw->addChild(imageGroup);
138    }
139
140
141    if (sw->getNumChildren()>0)
142    {
143        // select first child.
144        sw->setSingleChildOn(0);
145    }
146
147    return sw;
148}
149
150class SlideEventHandler : public osgGA::GUIEventHandler
151{
152public:
153
154    SlideEventHandler();
155   
156    META_Object(osgStereImageApp,SlideEventHandler);
157
158
159    void set(osg::Switch* sw, float offsetX, float offsetY, osg::TexMat* texmatLeft, osg::TexMat* texmatRight, float timePerSlide, bool autoSteppingActive);
160
161    void set(FileList fileList, osg::Switch* sw, float offsetX, float offsetY, osg::TexMat* texmatLeft, osg::TexMat* texmatRight, float radius, float height, float length, float timePerSlide, bool autoSteppingActive);
162
163
164    virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter&);
165   
166    virtual void getUsage(osg::ApplicationUsage& usage) const;
167
168    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
169   
170    void nextSlide();
171   
172    void previousSlide();
173   
174    void scaleImage(float s);
175   
176    void offsetImage(float ds,float dt);
177
178    void rotateImage(float rx,float ry);
179
180    void initTexMatrices();
181
182protected:
183
184    ~SlideEventHandler() {}
185    SlideEventHandler(const SlideEventHandler&,const osg::CopyOp&) {}
186
187    osg::ref_ptr<osg::Switch>   _switch;
188    osg::ref_ptr<osg::TexMat>   _texmatLeft;
189    osg::ref_ptr<osg::TexMat>   _texmatRight;
190    float                        _radius;
191    float                        _height;
192    float                        _length;
193    bool                        _firstTraversal;
194    unsigned int                _activeSlide;
195    double                      _previousTime;
196    double                      _timePerSlide;
197    bool                        _autoSteppingActive;
198    float                       _initSeperationX;
199    float                       _currentSeperationX;
200    float                       _initSeperationY;
201    float                       _currentSeperationY;
202    FileList                     _fileList;
203       
204};
205
206SlideEventHandler::SlideEventHandler():
207    _switch(0),
208    _texmatLeft(0),
209    _texmatRight(0),
210    _firstTraversal(true),
211    _activeSlide(0),
212    _previousTime(-1.0f),
213    _timePerSlide(5.0),
214    _autoSteppingActive(false)
215{
216}
217
218void SlideEventHandler::set(osg::Switch* sw, float offsetX, float offsetY, osg::TexMat* texmatLeft, osg::TexMat* texmatRight, float timePerSlide, bool autoSteppingActive)
219{
220    _switch = sw;
221    _switch->setUpdateCallback(this);
222
223    _texmatLeft = texmatLeft;
224    _texmatRight = texmatRight;
225
226    _timePerSlide = timePerSlide;
227    _autoSteppingActive = autoSteppingActive;   
228   
229    _initSeperationX = offsetX;
230    _currentSeperationX = _initSeperationX;
231
232    _initSeperationY = offsetY;
233    _currentSeperationY = _initSeperationY;
234
235    initTexMatrices();
236
237}
238
239void SlideEventHandler::set(FileList fileList, osg::Switch* sw, float offsetX, float offsetY, osg::TexMat* texmatLeft, osg::TexMat* texmatRight, float radius, float height, float length, float timePerSlide, bool autoSteppingActive)
240{
241    _switch = sw;
242    _switch->setUpdateCallback(this);
243    _fileList=FileList(fileList);
244
245    osg::ref_ptr<osg::Group> imageGroup = loadImages(fileList[0],fileList[1],texmatLeft,texmatRight, radius,  height, length);
246    if (imageGroup.get())_switch->addChild(imageGroup.get());
247
248    _texmatLeft = texmatLeft;
249    _texmatRight = texmatRight;
250
251    _radius=radius;
252    _height=height;
253    _length=length;
254
255    _timePerSlide = timePerSlide;
256    _autoSteppingActive = autoSteppingActive;   
257   
258    _initSeperationX = offsetX;
259    _currentSeperationX = _initSeperationX;
260
261    _initSeperationY = offsetY;
262    _currentSeperationY = _initSeperationY;
263
264    initTexMatrices();
265}
266
267
268bool SlideEventHandler::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter&)
269{
270    switch(ea.getEventType())
271    {
272        case(osgGA::GUIEventAdapter::KEYDOWN):
273        {
274            if (ea.getKey()=='a')
275            {
276                _autoSteppingActive = !_autoSteppingActive;
277                _previousTime = ea.getTime();
278                return true;
279            }
280            else if (ea.getKey()=='n')
281            {
282                nextSlide();
283                return true;
284            }
285            else if (ea.getKey()=='p')
286            {
287                previousSlide();
288                return true;
289            }
290            else if (ea.getKey()=='w')
291            {
292                scaleImage(0.99f);
293                return true;
294            }
295            else if (ea.getKey()=='s')
296            {
297                scaleImage(1.01f);
298                return true;
299            }
300            else if (ea.getKey()=='j')
301            {
302                offsetImage(-0.001f,0.0f);
303                return true;
304            }
305            else if (ea.getKey()=='k')
306            {
307                offsetImage(0.001f,0.0f);
308                return true;
309            }
310            else if (ea.getKey()=='i')
311            {
312                offsetImage(0.0f,-0.001f);
313                return true;
314            }
315            else if (ea.getKey()=='m')
316            {
317                offsetImage(0.0f,0.001f);
318                return true;
319            }
320            else if (ea.getKey()==' ')
321            {
322                initTexMatrices();
323                return true;
324            }
325            return false;
326        }
327        case(osgGA::GUIEventAdapter::DRAG):
328        case(osgGA::GUIEventAdapter::MOVE):
329        {
330            static float px = ea.getXnormalized();
331            static float py = ea.getYnormalized();
332           
333            float dx = ea.getXnormalized()-px;
334            float dy = ea.getYnormalized()-py;
335           
336            px = ea.getXnormalized();
337            py = ea.getYnormalized();
338           
339            rotateImage(dx,dy);
340           
341            return true;
342        }
343
344        default:
345            return false;
346    }
347}
348
349void SlideEventHandler::getUsage(osg::ApplicationUsage& usage) const
350{
351    usage.addKeyboardMouseBinding("Space","Reset the image position to center");
352    usage.addKeyboardMouseBinding("a","Toggle on/off the automatic advancement for image to image");
353    usage.addKeyboardMouseBinding("n","Advance to next image");
354    usage.addKeyboardMouseBinding("p","Move to previous image");
355    usage.addKeyboardMouseBinding("q","Zoom into the image");
356    usage.addKeyboardMouseBinding("a","Zoom out of the image");
357    usage.addKeyboardMouseBinding("j","Reduce horizontal offset");
358    usage.addKeyboardMouseBinding("k","Increase horizontal offset");
359    usage.addKeyboardMouseBinding("m","Reduce vertical offset");
360    usage.addKeyboardMouseBinding("i","Increase vertical offset");
361}
362
363void SlideEventHandler::operator()(osg::Node* node, osg::NodeVisitor* nv)
364{
365    if (_autoSteppingActive && nv->getFrameStamp())
366    {
367        double time = nv->getFrameStamp()->getSimulationTime();
368       
369        if (_firstTraversal)
370        {
371            _firstTraversal = false;
372            _previousTime = time;
373        }
374        else if (time-_previousTime>_timePerSlide)
375        {
376            _previousTime = time;
377           
378            nextSlide();
379        }
380       
381    }
382
383    traverse(node,nv);
384}
385
386void SlideEventHandler::nextSlide()
387{
388
389    if (_switch->getNumChildren()==0) return;
390
391    ++_activeSlide;
392
393    if (_fileList.size()>0) {
394        if (_activeSlide>= _fileList.size()/2 ) _activeSlide = 0;
395        osg::ref_ptr<osg::Group> images = loadImages(_fileList[2*_activeSlide],_fileList[2*_activeSlide+1],_texmatLeft.get(),_texmatRight.get(),_radius,_height,_length);
396        if (images.valid()) _switch->replaceChild(_switch->getChild(0),images.get());
397
398    } else {
399        if (_activeSlide>=_switch->getNumChildren()) _activeSlide = 0;
400
401        _switch->setSingleChildOn(_activeSlide);
402    }
403}
404
405void SlideEventHandler::previousSlide()
406{
407    if (_switch->getNumChildren()==0) return;
408
409    if (_fileList.size()>0) {
410        if (_activeSlide==0) _activeSlide = _fileList.size()/2-1;
411        else --_activeSlide;
412        osg::ref_ptr<osg::Group> images = loadImages(_fileList[2*_activeSlide],_fileList[2*_activeSlide+1],_texmatLeft.get(),_texmatRight.get(),_radius,_height,_length);
413        if (images.valid()) _switch->replaceChild(_switch->getChild(0),images.get());
414    } else {   
415        if (_activeSlide==0) _activeSlide = _switch->getNumChildren()-1;
416        else --_activeSlide;
417
418        _switch->setSingleChildOn(_activeSlide);
419    }
420}
421
422void SlideEventHandler::scaleImage(float s)
423{
424    _texmatLeft->setMatrix(_texmatLeft->getMatrix()*osg::Matrix::translate(-0.5f,-0.5f,0.0f)*osg::Matrix::scale(s,s,1.0f)*osg::Matrix::translate(0.5f,0.5f,0.0f));
425    _texmatRight->setMatrix(_texmatRight->getMatrix()*osg::Matrix::translate(-0.5f,-0.5f,0.0f)*osg::Matrix::scale(s,s,1.0f)*osg::Matrix::translate(0.5f,0.5f,0.0f));
426}
427
428void SlideEventHandler::offsetImage(float ds,float dt)
429{
430    _currentSeperationX+=ds;
431    _currentSeperationY+=dt;
432    osg::notify(osg::NOTICE)<<"image offset x = "<<_currentSeperationX<<"  y ="<<_currentSeperationY<<std::endl;
433    _texmatLeft->setMatrix(_texmatLeft->getMatrix()*osg::Matrix::translate(ds,dt,0.0f));
434    _texmatRight->setMatrix(_texmatRight->getMatrix()*osg::Matrix::translate(-ds,-dt,0.0f));
435}
436
437void SlideEventHandler::rotateImage(float rx,float ry)
438{
439    const float scale = 0.5f;
440    _texmatLeft->setMatrix(_texmatLeft->getMatrix()*osg::Matrix::translate(-rx*scale,-ry*scale,0.0f));
441    _texmatRight->setMatrix(_texmatRight->getMatrix()*osg::Matrix::translate(-rx*scale,-ry*scale,0.0f));
442}
443
444void SlideEventHandler::initTexMatrices()
445{
446    _texmatLeft->setMatrix(osg::Matrix::translate(_initSeperationX,_initSeperationY,0.0f));
447    _texmatRight->setMatrix(osg::Matrix::translate(-_initSeperationX,-_initSeperationY,0.0f));
448}
449
450
451
452int main( int argc, char **argv )
453{
454    // use an ArgumentParser object to manage the program arguments.
455    osg::ArgumentParser arguments(&argc,argv);
456   
457    // set up the usage document, in case we need to print out how to use this program.
458    arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the example which demonstrates use node masks to create stereo images.");
459    arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] image_file_left_eye image_file_right_eye");
460    arguments.getApplicationUsage()->addCommandLineOption("-d <float>","Time delay in sceonds between the display of successive image pairs when in auto advance mode.");
461    arguments.getApplicationUsage()->addCommandLineOption("-a","Enter auto advance of image pairs on start up.");
462    arguments.getApplicationUsage()->addCommandLineOption("-x <float>","Horizontal offset of left and right images.");
463    arguments.getApplicationUsage()->addCommandLineOption("-y <float>","Vertical offset of left and right images.");
464    arguments.getApplicationUsage()->addCommandLineOption("--disk","Keep images on disk");
465    arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display this information");
466    arguments.getApplicationUsage()->addCommandLineOption("--SingleThreaded","Select SingleThreaded threading model for viewer.");
467    arguments.getApplicationUsage()->addCommandLineOption("--CullDrawThreadPerContext","Select CullDrawThreadPerContext threading model for viewer.");
468    arguments.getApplicationUsage()->addCommandLineOption("--DrawThreadPerContext","Select DrawThreadPerContext threading model for viewer.");
469    arguments.getApplicationUsage()->addCommandLineOption("--CullThreadPerCameraDrawThreadPerContext","Select CullThreadPerCameraDrawThreadPerContext threading model for viewer.");
470   
471
472    // construct the viewer.
473    osgViewer::Viewer viewer;
474
475    // register the handler to add keyboard and mosue handling.
476    SlideEventHandler* seh = new SlideEventHandler();
477    viewer.addEventHandler(seh);
478
479    // read any time delay argument.
480    float timeDelayBetweenSlides = 5.0f;
481    while (arguments.read("-d",timeDelayBetweenSlides)) {}
482
483    bool autoSteppingActive = false;
484    while (arguments.read("-a")) autoSteppingActive = true;
485
486    float offsetX=0.0f;
487    while (arguments.read("-x",offsetX)) {}
488
489    float offsetY=0.0f;
490    while (arguments.read("-y",offsetY)) {}
491
492    bool onDisk=false;
493    while (arguments.read("--disk")) { onDisk=true;}
494
495    // if user request help write it out to cout.
496    if (arguments.read("-h") || arguments.read("--help"))
497    {
498        arguments.getApplicationUsage()->write(std::cout);
499        return 1;
500    }
501
502    osgViewer::Viewer::ThreadingModel threading = osgViewer::Viewer::SingleThreaded;
503    while (arguments.read("--SingleThreaded")) threading = osgViewer::Viewer::SingleThreaded;
504    while (arguments.read("--CullDrawThreadPerContext")) threading = osgViewer::Viewer::CullDrawThreadPerContext;
505    while (arguments.read("--DrawThreadPerContext")) threading = osgViewer::Viewer::DrawThreadPerContext;
506    while (arguments.read("--CullThreadPerCameraDrawThreadPerContext")) threading = osgViewer::Viewer::CullThreadPerCameraDrawThreadPerContext;
507
508    viewer.setThreadingModel(threading);
509
510    // any option left unread are converted into errors to write out later.
511    arguments.reportRemainingOptionsAsUnrecognized();
512
513    // report any errors if they have occured when parsing the program aguments.
514    if (arguments.errors())
515    {
516        arguments.writeErrorMessages(std::cout);
517        return 1;
518    }
519   
520    // extract the filenames from the arguments list.
521    FileList fileList;
522    for(int pos=1;pos<arguments.argc();++pos)
523    {
524        if (arguments.isString(pos)) fileList.push_back(arguments[pos]);
525    }
526
527    if (fileList.empty())
528    {
529        fileList.push_back("Images/dog_left_eye.jpg");
530         fileList.push_back("Images/dog_right_eye.jpg");
531    }
532    else if (fileList.size()<2)
533    {
534        arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION);
535        return 1;
536    }
537
538    // now the windows have been realized we switch off the cursor to prevent it
539    // distracting the people seeing the stereo images.
540    double fovy, aspectRatio, zNear, zFar;
541    viewer.getCamera()->getProjectionMatrixAsPerspective(fovy, aspectRatio, zNear, zFar);
542
543    float radius = 1.0f;
544    float height = 2*radius*tan(osg::DegreesToRadians(fovy)*0.5f);
545    float length = osg::PI*radius;  // half a cylinder.
546
547    // use a texure matrix to control the placement of the image.
548    osg::TexMat* texmatLeft = new osg::TexMat;
549    osg::TexMat* texmatRight = new osg::TexMat;
550
551    // creat the scene from the file list.
552    osg::ref_ptr<osg::Switch> rootNode;
553    if (!onDisk)  rootNode = createScene(fileList,texmatLeft,texmatRight,radius,height,length);
554    else rootNode=new osg::Switch();
555
556    //osgDB::writeNodeFile(*rootNode,"test.osg");
557
558
559    viewer.getCamera()->setCullMask(0xffffffff);
560    viewer.getCamera()->setCullMaskLeft(0x00000001);
561    viewer.getCamera()->setCullMaskRight(0x00000002);
562
563    // set up the use of stereo by default.
564    osg::DisplaySettings::instance()->setStereo(true);
565
566    // set the scene to render
567    viewer.setSceneData(rootNode.get());
568
569
570    // create the windows and run the threads.
571    viewer.realize();
572
573
574    // switch off the cursor
575    osgViewer::Viewer::Windows windows;
576    viewer.getWindows(windows);
577    for(osgViewer::Viewer::Windows::iterator itr = windows.begin();
578        itr != windows.end();
579        ++itr)
580    {
581        (*itr)->useCursor(false);
582    }
583
584    viewer.setFusionDistance(osgUtil::SceneView::USE_FUSION_DISTANCE_VALUE,radius);
585
586    // set up the SlideEventHandler.
587    if (onDisk) seh->set(fileList,rootNode.get(),offsetX,offsetY,texmatLeft,texmatRight,radius,height,length,timeDelayBetweenSlides,autoSteppingActive);
588    else seh->set(rootNode.get(),offsetX,offsetY,texmatLeft,texmatRight,timeDelayBetweenSlides,autoSteppingActive);
589   
590    osg::Matrix homePosition;
591    homePosition.makeLookAt(osg::Vec3(0.0f,0.0f,0.0f),osg::Vec3(0.0f,1.0f,0.0f),osg::Vec3(0.0f,0.0f,1.0f));
592       
593    while( !viewer.done() )
594    {
595        viewer.getCamera()->setViewMatrix(homePosition);
596
597        // fire off the cull and draw traversals of the scene.
598        viewer.frame();
599       
600    }
601   
602    return 0;
603}
Note: See TracBrowser for help on using the browser.