root/OpenSceneGraph/trunk/examples/osgspheresegment/osgspheresegment.cpp @ 5049

Revision 5049, 22.9 kB (checked in by robert, 9 years ago)

Added moving sphere segment intersections.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1#include <osgProducer/Viewer>
2
3#include <osg/Group>
4#include <osg/Geode>
5#include <osg/ShapeDrawable>
6#include <osg/Texture2D>
7#include <osg/PositionAttitudeTransform>
8#include <osg/MatrixTransform>
9#include <osg/Geometry>
10
11#include <osgUtil/SmoothingVisitor>
12
13#include <osgDB/ReadFile>
14
15#include <osgText/Text>
16
17#include <osgSim/SphereSegment>
18#include <osgSim/OverlayNode>
19
20#include <osgParticle/ExplosionEffect>
21#include <osgParticle/SmokeEffect>
22#include <osgParticle/FireEffect>
23#include <osgParticle/ParticleSystemUpdater>
24
25// for the grid data..
26#include "../osghangglide/terrain_coords.h"
27
28osg::AnimationPath* createAnimationPath(const osg::Vec3& center,float radius,double looptime)
29{
30    // set up the animation path
31    osg::AnimationPath* animationPath = new osg::AnimationPath;
32    animationPath->setLoopMode(osg::AnimationPath::LOOP);
33   
34    int numSamples = 40;
35    float yaw = 0.0f;
36    float yaw_delta = 2.0f*osg::PI/((float)numSamples-1.0f);
37    float roll = osg::inDegrees(30.0f);
38   
39    double time=0.0f;
40    double time_delta = looptime/(double)numSamples;
41    for(int i=0;i<numSamples;++i)
42    {
43        osg::Vec3 position(center+osg::Vec3(sinf(yaw)*radius,cosf(yaw)*radius,0.0f));
44        osg::Quat rotation(osg::Quat(roll,osg::Vec3(0.0,1.0,0.0))*osg::Quat(-(yaw+osg::inDegrees(90.0f)),osg::Vec3(0.0,0.0,1.0)));
45       
46        animationPath->insert(time,osg::AnimationPath::ControlPoint(position,rotation));
47
48        yaw += yaw_delta;
49        time += time_delta;
50
51    }
52    return animationPath;   
53}
54
55
56
57class IntersectionUpdateCallback : public osg::NodeCallback
58{
59        virtual void operator()(osg::Node* /*node*/, osg::NodeVisitor* nv)
60        {
61            if (!root_ || !terrain_ || !ss_ || !intersectionGroup_)
62            {
63                osg::notify(osg::NOTICE)<<"IntersectionUpdateCallback not set up correctly."<<std::endl;
64                return;
65            }
66       
67            //traverse(node,nv);
68            frameCount_++;
69            if (frameCount_ > 200)
70            {
71                // first we need find the transformation matrix that takes
72                // the terrain into the coordinate frame of the sphere segment.
73                osg::Matrixd terrainLocalToWorld;
74                osg::MatrixList terrain_worldMatrices = terrain_->getWorldMatrices(root_.get());
75                if (terrain_worldMatrices.empty()) terrainLocalToWorld.makeIdentity();
76                else if (terrain_worldMatrices.size()==1) terrainLocalToWorld = terrain_worldMatrices.front();
77                else
78                {
79                    osg::notify(osg::NOTICE)<<"IntersectionUpdateCallback: warning cannot interestect with multiple terrain instances, just uses first one."<<std::endl;
80                    terrainLocalToWorld = terrain_worldMatrices.front();
81                }
82               
83                // sphere segment is easier as this callback is attached to the node, so the node visitor has the unique path to it already.
84                osg::Matrixd ssWorldToLocal = osg::computeWorldToLocal(nv->getNodePath());
85               
86                // now we can compute the terrain to ss transform
87                osg::Matrixd possie = terrainLocalToWorld*ssWorldToLocal;
88               
89                osgSim::SphereSegment::LineList lines = ss_->computeIntersection(possie, terrain_.get());
90                if (!lines.empty())
91                {
92                    osg::notify(osg::NOTICE)<<"We've found intersections!!!!"<<std::endl;
93           
94                    if (intersectionGroup_.valid())
95                    {
96                        // now we need to place the intersections which are in the SphereSegmenet's coordinate frame into
97                        // to the final position.
98                        osg::MatrixTransform* mt = new osg::MatrixTransform;
99                        mt->setMatrix(osg::computeLocalToWorld(nv->getNodePath()));
100                        intersectionGroup_->addChild(mt);
101
102                        osg::Geode* geode = new osg::Geode;
103                        mt->addChild(geode);
104
105                        geode->getOrCreateStateSet()->setMode(GL_LIGHTING,osg::StateAttribute::OFF);
106
107                        for(osgSim::SphereSegment::LineList::iterator itr=lines.begin();
108                           itr!=lines.end();
109                           ++itr)
110                        {
111                            osg::Geometry* geom = new osg::Geometry;
112                            geode->addDrawable(geom);
113
114                            osg::Vec3Array* vertices = itr->get();
115                            geom->setVertexArray(vertices);
116                            geom->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, vertices->getNumElements()));
117                        }
118                    }
119                }
120                else
121                {
122                       osg::notify(osg::NOTICE)<<"No intersections found"<<std::endl;
123                }
124
125                   
126                frameCount_ = 0;
127            }
128        }
129    public:
130    osg::observer_ptr<osg::Group> root_;
131    osg::observer_ptr<osg::Geode> terrain_;
132    osg::observer_ptr<osgSim::SphereSegment> ss_;
133    osg::observer_ptr<osg::Group> intersectionGroup_;
134    unsigned frameCount_;
135};
136
137
138osg::Node* createMovingModel(const osg::Vec3& center, float radius, osg::Geode * terrainGeode, osg::Group * root)
139{
140    float animationLength = 10.0f;
141
142    osg::AnimationPath* animationPath = createAnimationPath(center,radius,animationLength);
143
144    osg::Group* model = new osg::Group;
145
146    osg::Node* glider = osgDB::readNodeFile("glider.osg");
147    if (glider)
148    {
149        const osg::BoundingSphere& bs = glider->getBound();
150
151        float size = radius/bs.radius()*0.3f;
152        osg::MatrixTransform* positioned = new osg::MatrixTransform;
153        positioned->setDataVariance(osg::Object::STATIC);
154        positioned->setMatrix(osg::Matrix::translate(-bs.center())*
155                                     osg::Matrix::scale(size,size,size)*
156                                     osg::Matrix::rotate(osg::inDegrees(-90.0f),0.0f,0.0f,1.0f));
157   
158        positioned->addChild(glider);
159   
160        osg::PositionAttitudeTransform* xform = new osg::PositionAttitudeTransform;   
161        xform->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
162        xform->setUpdateCallback(new osg::AnimationPathCallback(animationPath,0.0,1.0));
163        xform->addChild(positioned);
164        model->addChild(xform);
165    }
166    if (1)
167    {
168        // The IntersectionUpdateCallback has to have a safe place to put all its generated geometry into,
169        // and this group can't be in the parental chain of the callback otherwise we will end up invalidating
170        // traversal iterators.
171        osg::Group* intersectionGroup = new osg::Group;
172        root->addChild(intersectionGroup);
173   
174        osg::PositionAttitudeTransform* xform = new osg::PositionAttitudeTransform;   
175        xform->setUpdateCallback(new osg::AnimationPathCallback(animationPath,0.0,1.0));
176       
177        osgSim::SphereSegment * ss = new osgSim::SphereSegment(osg::Vec3d(0.0,0.0,0.0),
178                                700.0f, // radius
179                                osg::DegreesToRadians(135.0f),
180                                osg::DegreesToRadians(240.0f),
181                                osg::DegreesToRadians(-90.0f),
182                                osg::DegreesToRadians(-70.0f),
183                                60);
184                               
185        IntersectionUpdateCallback * iuc = new IntersectionUpdateCallback;
186        iuc->frameCount_ = 0;
187        iuc->root_ = root;
188        iuc->terrain_ = terrainGeode;
189        iuc->ss_ = ss;
190        iuc->intersectionGroup_ = intersectionGroup;
191        ss->setUpdateCallback(iuc);
192        ss->setAllColors(osg::Vec4(1.0f,1.0f,1.0f,0.5f));
193        ss->setSideColor(osg::Vec4(0.5f,1.0f,1.0f,0.1f));
194        xform->addChild(ss);
195        model->addChild(xform);
196    }
197 
198    osg::Node* cessna = osgDB::readNodeFile("cessna.osg");
199    if (cessna)
200    {
201        const osg::BoundingSphere& bs = cessna->getBound();
202
203        osgText::Text* text = new osgText::Text;
204        float size = radius/bs.radius()*0.3f;
205
206        text->setPosition(bs.center());
207        text->setText("Cessna");
208        text->setAlignment(osgText::Text::CENTER_CENTER);
209        text->setAxisAlignment(osgText::Text::SCREEN);
210        text->setCharacterSize(40.0f);
211        text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
212       
213        osg::Geode* geode = new osg::Geode;
214        geode->addDrawable(text);
215   
216        osg::LOD* lod = new osg::LOD;
217        lod->setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
218        lod->addChild(geode,0.0f,100.0f);
219        lod->addChild(cessna,100.0f,10000.0f);
220
221
222        osg::MatrixTransform* positioned = new osg::MatrixTransform;
223        positioned->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
224        positioned->setDataVariance(osg::Object::STATIC);
225        positioned->setMatrix(osg::Matrix::translate(-bs.center())*
226                                     osg::Matrix::scale(size,size,size)*
227                                     osg::Matrix::rotate(osg::inDegrees(180.0f),0.0f,0.0f,1.0f));
228   
229        //positioned->addChild(cessna);
230        positioned->addChild(lod);
231   
232        osg::MatrixTransform* xform = new osg::MatrixTransform;
233        xform->setUpdateCallback(new osg::AnimationPathCallback(animationPath,0.0f,2.0));
234        xform->addChild(positioned);
235       
236        model->addChild(xform);
237    }
238   
239    return model;
240}
241
242osg::Group* createOverlay(const osg::Vec3& center, float radius)
243{
244    osg::Group* group = new osg::Group;
245   
246    // create a grid of lines.
247    {
248        osg::Geometry* geom = new osg::Geometry;
249       
250        unsigned int num_rows = 10;
251
252        osg::Vec3 left = center+osg::Vec3(-radius,-radius,0.0f);
253        osg::Vec3 right = center+osg::Vec3(radius,-radius,0.0f);
254        osg::Vec3 delta_row = osg::Vec3(0.0f,2.0f*radius/float(num_rows-1),0.0f);
255
256        osg::Vec3 top = center+osg::Vec3(-radius,radius,0.0f);
257        osg::Vec3 bottom = center+osg::Vec3(-radius,-radius,0.0f);
258        osg::Vec3 delta_column = osg::Vec3(2.0f*radius/float(num_rows-1),0.0f,0.0f);
259
260        osg::Vec3Array* vertices = new osg::Vec3Array;
261        for(unsigned int i=0; i<num_rows; ++i)
262        {
263            vertices->push_back(left);
264            vertices->push_back(right);
265            left += delta_row;
266            right += delta_row;
267
268            vertices->push_back(top);
269            vertices->push_back(bottom);
270            top += delta_column;
271            bottom += delta_column;
272        }
273       
274        geom->setVertexArray(vertices);
275
276        osg::Vec4ubArray& color = *(new osg::Vec4ubArray(1));
277        color[0].set(0,0,0,255);
278        geom->setColorArray(&color);
279        geom->setColorBinding(osg::Geometry::BIND_OVERALL);
280
281        geom->addPrimitiveSet(new osg::DrawArrays(GL_LINES,0,vertices->getNumElements()));
282
283        geom->getOrCreateStateSet()->setMode(GL_LIGHTING,osg::StateAttribute::OFF);
284
285        osg::Geode* geode = new osg::Geode;
286        geode->addDrawable(geom);
287        group->addChild(geode);       
288    }
289   
290    return group;
291}
292
293osg::Vec3 computeTerrainIntersection(osg::Node* subgraph,float x,float y)
294{
295    osgUtil::IntersectVisitor iv;
296    osg::ref_ptr<osg::LineSegment> segDown = new osg::LineSegment;
297
298    const osg::BoundingSphere& bs = subgraph->getBound();
299    float zMax = bs.center().z()+bs.radius();
300    float zMin = bs.center().z()-bs.radius();
301   
302    segDown->set(osg::Vec3(x,y,zMin),osg::Vec3(x,y,zMax));
303    iv.addLineSegment(segDown.get());
304
305    subgraph->accept(iv);
306
307    if (iv.hits())
308    {
309        osgUtil::IntersectVisitor::HitList& hitList = iv.getHitList(segDown.get());
310        if (!hitList.empty())
311        {
312            osg::Vec3 ip = hitList.front().getWorldIntersectPoint();
313            return  ip;
314        }
315    }
316
317    return osg::Vec3(x,y,0.0f);
318}
319
320
321//////////////////////////////////////////////////////////////////////////////
322// MAIN SCENE GRAPH BUILDING FUNCTION
323//////////////////////////////////////////////////////////////////////////////
324
325void build_world(osg::Group *root, unsigned int testCase)
326{
327
328    // create terrain
329    osg::ref_ptr<osg::Geode> terrainGeode = 0;
330    {
331        terrainGeode = new osg::Geode;
332
333        osg::StateSet* stateset = new osg::StateSet();
334        osg::Image* image = osgDB::readImageFile("Images/lz.rgb");
335        if (image)
336        {
337            osg::Texture2D* texture = new osg::Texture2D;
338            texture->setImage(image);
339            stateset->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON);
340        }
341
342        terrainGeode->setStateSet( stateset );
343
344       
345        {
346            unsigned int numColumns = 38;
347            unsigned int numRows = 39;
348            unsigned int r, c;
349           
350            osg::Vec3 origin(0.0f,0.0f,0.0f);
351            osg::Vec3 size(1000.0f,1000.0f,250.0f);
352
353            osg::Geometry* geometry = new osg::Geometry;
354
355            osg::Vec3Array& v = *(new osg::Vec3Array(numColumns*numRows));
356            osg::Vec2Array& tc = *(new osg::Vec2Array(numColumns*numRows));
357            osg::Vec4ubArray& color = *(new osg::Vec4ubArray(1));
358
359            color[0].set(255,255,255,255);
360
361            float rowCoordDelta = size.y()/(float)(numRows-1);
362            float columnCoordDelta = size.x()/(float)(numColumns-1);
363
364            float rowTexDelta = 1.0f/(float)(numRows-1);
365            float columnTexDelta = 1.0f/(float)(numColumns-1);
366
367            // compute z range of z values of grid data so we can scale it.
368            float min_z = FLT_MAX;
369            float max_z = -FLT_MAX;
370            for(r=0;r<numRows;++r)
371            {
372                for(c=0;c<numColumns;++c)
373                {
374                    min_z = osg::minimum(min_z,vertex[r+c*numRows][2]);
375                    max_z = osg::maximum(max_z,vertex[r+c*numRows][2]);
376                }
377            }
378
379            float scale_z = size.z()/(max_z-min_z);
380
381            osg::Vec3 pos = origin;
382            osg::Vec2 tex(0.0f,0.0f);
383            int vi=0;
384            for(r=0;r<numRows;++r)
385            {
386                pos.x() = origin.x();
387                tex.x() = 0.0f;
388                for(c=0;c<numColumns;++c)
389                {
390                    v[vi].set(pos.x(),pos.y(),pos.z()+(vertex[r+c*numRows][2]-min_z)*scale_z);
391                    tc[vi] = tex;
392                    pos.x()+=columnCoordDelta;
393                    tex.x()+=columnTexDelta;
394                    ++vi;
395                }
396                pos.y() += rowCoordDelta;
397                tex.y() += rowTexDelta;
398            }
399
400            geometry->setVertexArray(&v);
401            geometry->setTexCoordArray(0, &tc);
402            geometry->setColorArray(&color);
403            geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
404
405            for(r=0;r<numRows-1;++r)
406            {
407                osg::DrawElementsUShort& drawElements = *(new osg::DrawElementsUShort(GL_QUAD_STRIP,2*numColumns));
408                geometry->addPrimitiveSet(&drawElements);
409                int ei=0;
410                for(c=0;c<numColumns;++c)
411                {
412                    drawElements[ei++] = (r+1)*numColumns+c;
413                    drawElements[ei++] = (r)*numColumns+c;
414                }
415            }
416           
417            osgUtil::SmoothingVisitor smoother;
418            smoother.smooth(*geometry);
419           
420            terrainGeode->addDrawable(geometry);
421        }
422       
423    }   
424
425
426    // create sphere segment
427    osg::ref_ptr<osgSim::SphereSegment> ss = 0;
428    {
429
430        switch(testCase)
431        {
432            case(0):       
433                ss = new osgSim::SphereSegment(
434                                computeTerrainIntersection(terrainGeode.get(),550.0f,780.0f), // center
435                                510.0f, // radius
436                                osg::DegreesToRadians(135.0f),
437                                osg::DegreesToRadians(240.0f),
438                                osg::DegreesToRadians(-10.0f),
439                                osg::DegreesToRadians(30.0f),
440                                60);
441                break;
442            case(1):       
443                ss = new osgSim::SphereSegment(
444                                computeTerrainIntersection(terrainGeode.get(),550.0f,780.0f), // center
445                                510.0f, // radius
446                                osg::DegreesToRadians(45.0f),
447                                osg::DegreesToRadians(240.0f),
448                                osg::DegreesToRadians(-10.0f),
449                                osg::DegreesToRadians(30.0f),
450                                60);
451                break;
452            case(2):       
453                ss = new osgSim::SphereSegment(
454                                computeTerrainIntersection(terrainGeode.get(),550.0f,780.0f), // center
455                                510.0f, // radius
456                                osg::DegreesToRadians(5.0f),
457                                osg::DegreesToRadians(355.0f),
458                                osg::DegreesToRadians(-10.0f),
459                                osg::DegreesToRadians(30.0f),
460                                60);
461                break;
462            case(3):       
463                ss = new osgSim::SphereSegment(
464                                computeTerrainIntersection(terrainGeode.get(),550.0f,780.0f), // center
465                                510.0f, // radius
466                                osg::DegreesToRadians(0.0f),
467                                osg::DegreesToRadians(360.0f),
468                                osg::DegreesToRadians(-10.0f),
469                                osg::DegreesToRadians(30.0f),
470                                60);
471                break;
472        };
473       
474        ss->setAllColors(osg::Vec4(1.0f,1.0f,1.0f,0.5f));
475        ss->setSideColor(osg::Vec4(0.0f,1.0f,1.0f,0.1f));
476#if 1
477        root->addChild(ss.get());
478#endif
479    }
480   
481#if 1   
482    root->addChild(ss->computeIntersectionSubgraph(osg::Matrixd::identity(), terrainGeode.get()));
483#else
484    osgSim::SphereSegment::LineList lines = ss->computeIntersection(osg::Matrixd::identity(), terrainGeode.get());
485    if (!lines.empty())
486    {
487        osg::notify(osg::NOTICE)<<"We've found intersections!!!!"<<std::endl;
488
489        osg::Geode* geode = new osg::Geode;
490        root->addChild(geode);
491
492        geode->getOrCreateStateSet()->setMode(GL_LIGHTING,osg::StateAttribute::OFF);
493
494        for(osgSim::SphereSegment::LineList::iterator itr=lines.begin();
495           itr!=lines.end();
496           ++itr)
497        {
498            osg::Geometry* geom = new osg::Geometry;
499            geode->addDrawable(geom);
500           
501            osg::Vec3Array* vertices = itr->get();
502            geom->setVertexArray(vertices);
503            geom->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, vertices->getNumElements()));
504        }
505    }
506    else
507    {
508       osg::notify(osg::NOTICE)<<"No intersections found"<<std::endl;
509    }
510#endif
511
512#if 0
513
514
515    osgSim::OverlayNode* overlayNode = new osgSim::OverlayNode;
516   
517    overlayNode->getOrCreateStateSet()->setTextureAttribute(1, new osg::TexEnv(osg::TexEnv::DECAL));
518
519    const osg::BoundingSphere& bs = terrainGeode->getBound();
520    osg::Group* overlaySubgraph = createOverlay(bs.center(), bs.radius()*0.5f);
521    overlaySubgraph->addChild(ss.get());
522    overlayNode->setOverlaySubgraph(overlaySubgraph);
523    overlayNode->setOverlayTextureSizeHint(2048);
524    overlayNode->addChild(terrainGeode.get());
525
526    root->addChild(overlayNode);
527   
528#else
529    root->addChild(terrainGeode.get());
530#endif
531
532
533    // create particle effects
534    {   
535        osg::Vec3 position = computeTerrainIntersection(terrainGeode.get(),100.0f,100.0f);
536
537        osgParticle::ExplosionEffect* explosion = new osgParticle::ExplosionEffect(position, 10.0f);
538        osgParticle::SmokeEffect* smoke = new osgParticle::SmokeEffect(position, 10.0f);
539        osgParticle::FireEffect* fire = new osgParticle::FireEffect(position, 10.0f);
540
541        root->addChild(explosion);
542        root->addChild(smoke);
543        root->addChild(fire);
544    }
545   
546    // create particle effects
547    {   
548        osg::Vec3 position = computeTerrainIntersection(terrainGeode.get(),200.0f,100.0f);
549
550        osgParticle::ExplosionEffect* explosion = new osgParticle::ExplosionEffect(position, 1.0f);
551        osgParticle::SmokeEffect* smoke = new osgParticle::SmokeEffect(position, 1.0f);
552        osgParticle::FireEffect* fire = new osgParticle::FireEffect(position, 1.0f);
553
554        root->addChild(explosion);
555        root->addChild(smoke);
556        root->addChild(fire);
557    }
558   
559    // create the moving models.
560    {
561        root->addChild(createMovingModel(osg::Vec3(500.0f,500.0f,500.0f),100.0f, terrainGeode.get(), root));
562    }
563}
564
565
566//////////////////////////////////////////////////////////////////////////////
567// main()
568//////////////////////////////////////////////////////////////////////////////
569
570
571int main(int argc, char **argv)
572{
573    // use an ArgumentParser object to manage the program arguments.
574    osg::ArgumentParser arguments(&argc,argv);
575   
576    // set up the usage document, in case we need to print out how to use this program.
577    arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the example which demonstrates use of particle systems.");
578    arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] image_file_left_eye image_file_right_eye");
579    arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display this information");
580   
581
582    // construct the viewer.
583    osgProducer::Viewer viewer(arguments);
584
585    // set up the value with sensible default event handlers.
586    viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
587
588    // get details on keyboard and mouse bindings used by the viewer.
589    viewer.getUsage(*arguments.getApplicationUsage());
590
591    // if user request help write it out to cout.
592    unsigned int testCase = 0;
593    if (arguments.read("-t", testCase)) {}
594
595
596    // if user request help write it out to cout.
597    if (arguments.read("-h") || arguments.read("--help"))
598    {
599        arguments.getApplicationUsage()->write(std::cout);
600        return 1;
601    }
602
603    // any option left unread are converted into errors to write out later.
604    arguments.reportRemainingOptionsAsUnrecognized();
605
606    // report any errors if they have occured when parsing the program aguments.
607    if (arguments.errors())
608    {
609        arguments.writeErrorMessages(std::cout);
610        return 1;
611    }
612   
613    osg::Group *root = new osg::Group;
614    build_world(root, testCase);
615   
616    // add a viewport to the viewer and attach the scene graph.
617    viewer.setSceneData(root);
618       
619    // create the windows and run the threads.
620    viewer.realize();
621
622    while( !viewer.done() )
623    {
624        // wait for all cull and draw threads to complete.
625        viewer.sync();
626
627        // update the scene by traversing it with the the update visitor which will
628        // call all node update callbacks and animations.
629        viewer.update();
630         
631        // fire off the cull and draw traversals of the scene.
632        viewer.frame();
633       
634    }
635   
636    // wait for all cull and draw threads to complete before exit.
637    viewer.sync();
638
639    return 0;
640}
Note: See TracBrowser for help on using the browser.