/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This application is open source and may be redistributed and/or modified * freely and without restriction, both in commericial and non commericial applications, * as long as this copyright notice is maintained. * * This application is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ /** Example of use of delaunay triangulator with constraints. * this could be a method of generating terrains, a constraint forces certain edges to * exist in the triangulation. */ #include #include #include #include #include #include #include #include #include #include // tessellator triangulates the constrained triangles #include #include #include /** here are 2 common types of constraint * Area - forces an area to be filled; replacement geometry is a canopy and optional wall * Linear - constructs a closed loop of constant width around a line. */ class WallConstraint: public osgUtil::DelaunayConstraint { // forces lines to eb edge // wall constraint - can generate a wall at the coordinates of the constraint public: /** if you derive a class from DelaunayConstraint then you can create * a specific geometry creation routine. */ WallConstraint() : height(0), txxrepWall(10), txyrepWall(10) { } /** or create a wall around the constraint area: */ virtual osg::Geometry * makeWallGeometry(void) const; /** for basic purposes, you can call these routines to make simple fill in geometries */ virtual osg::DrawArrays* makeWall(void ) const { // build a wall height high around the constraint const osg::Vec3Array *_line= dynamic_cast(getVertexArray()); return (new osg::DrawArrays(osg::PrimitiveSet::QUAD_STRIP,0,2*_line->size())); } virtual osg::Vec3Array *getWall(const float height) const; virtual osg::Vec2Array *getWallTexcoords(const float height) const; virtual osg::Vec3Array *getWallNormals(void) const { osg::ref_ptr nrms=new osg::Vec3Array; const osg::Vec3Array *vertices= dynamic_cast(getVertexArray()); for (unsigned int ipr=0; iprgetMode()==osg::PrimitiveSet::LINE_LOOP || prset->getMode()==osg::PrimitiveSet::LINE_STRIP) { // loops and walls // start with the last point on the loop osg::Vec3 prevp=(*vertices)[prset->index (prset->getNumIndices()-1)]; for (unsigned int i=0; igetNumIndices(); i++) { const osg::Vec3 curp=(*vertices)[prset->index (i)]; osg::Vec3 nrm=(curp-prevp)^osg::Vec3(0,0,1); nrm.normalize(); nrms->push_back(nrm); nrms->push_back(nrm); prevp=curp; } const osg::Vec3 curp=(*vertices)[prset->index (0)]; osg::Vec3 nrm=(curp-prevp)^osg::Vec3(0,0,1); nrm.normalize(); nrms->push_back(nrm); nrms->push_back(nrm); } } return nrms.release(); } // geometry creation parameters void setWallTexrep(const float w,const float h) { txxrepWall=w;txyrepWall=h;} /** Wall Geometry will return with this texture applied: */ void setTexture(const char *tx) { texture=tx;} /** fence/wall height */ void setHeight(const float h) { height=h;} protected: float height; std::string texture; float txxrepWall, txyrepWall; }; class ArealConstraint: public osgUtil::DelaunayConstraint { // forces edges of an area to fit triangles // areal constraint - general nonuniform field, forest, lake etc. public: /** if you derive a class from DelaunayConstraint then you can create * a specific geometry creation routine. */ ArealConstraint() : txxrepArea(10), txyrepArea(10),txxrepWall(10), txyrepWall(10) { } /** return a geometry that fills the constraint. */ virtual osg::Geometry * makeAreal( osg::Vec3Array *points); /** or create a wall around the constraint area: */ virtual osg::Geometry * makeWallGeometry( osg::Vec3Array *points) ; /** for basic purposes, you can call these routines to make simple fill in geometries */ virtual osg::DrawArrays* makeWall(void ) const; virtual osg::Vec3Array *getWall(const float height) const; virtual osg::Vec2Array *getWallTexcoords(const float height) const; virtual osg::Vec3Array *getWallNormals(void) const; /** Canopies are the same triangles as the terrain but offset by height above * (height might be 0). */ virtual osg::DrawArrays* makeCanopy(void ) const; virtual osg::Vec3Array *getCanopy(const osg::Vec3Array *points,const float height) const; virtual osg::Vec2Array *getCanopyTexcoords(const osg::Vec3Array *points) const; virtual osg::Vec3Array *getCanopyNormals(const osg::Vec3Array *points) const; // geometry creation parameters void setTexrep(const float w,const float h) { txxrepArea=w;txyrepArea=h;} void setWallTexrep(const float w,const float h) { txxrepWall=w;txyrepWall=h;} /** Geometry will return with this texture applied: */ void setWallTexture(const char *tx) { walltexture=tx;} /** Geometry will return with this texture applied: */ void setTexture(const char *tx) { texture=tx;} /** fence/wall height */ void setHeight(const float h) { height=h;} std::string walltexture; protected: float height; std::string texture; float txxrepArea, txyrepArea; float txxrepWall, txyrepWall; }; class LinearConstraint: public osgUtil::DelaunayConstraint { /** forces edges of a "road" to fit triangles * if 2 roads cross, then the overlap will be replaced by a 'cross road' * and the roads built up to the cross roads with a texture along its length. */ public: LinearConstraint() : osgUtil::DelaunayConstraint(), txxrepAlong(10), txyrepAcross(10), width(2) { } /** geometry creation parameters */ /* Width of linear feature (eg road, railway) */ void setWidth(const float w) { width=w;} /** Texture repeat distance across linear (often equal to width) and along its length */ virtual void setTexrep(const float w,const float h) { txyrepAcross=h;txxrepAlong=w; } /** generate constant width around line - creates the area to be cut into the terrain. */ virtual void setVertices( osg::Vec3Array *lp, const float width); /** return a geometry that fills the constraint. */ virtual osg::Geometry *makeGeometry(const osg::Vec3Array *points) ; /** return normals array - flat shaded */ osg::Vec3Array* getNormals(const osg::Vec3Array *points); /** Roads apply a texture proportional to length along the road line. */ virtual osg::DrawArrays* makeRoad( ) const; virtual osg::Vec3Array *getRoadVertices() const; virtual osg::Vec2Array *getRoadTexcoords(const osg::Vec3Array *points) ; virtual osg::Vec3Array *getRoadNormals(const osg::Vec3Array *points) const; /** Geometry will return with this texture applied: */ void setTexture(const char *tx) { texture=tx;} protected: osg::ref_ptr _tcoords; osg::ref_ptr _edgecoords; float txxrepAlong, txyrepAcross; std::string texture; float width; // width of a linear feature osg::ref_ptr _midline; // defines the midline of a road, rail etc. }; /** a specific type of constaint - that replaces an area with a pyramid */ class pyramid : public osgUtil::DelaunayConstraint { /** sample user constriant - creates hole in terrain to fit base of pyramid, and * geometry of an Egyptian pyramid to fit the hole. */ public: pyramid() : _side(100.) {} void setpos(const osg::Vec3 p, const float size) { _pos=p;_side=size;} virtual osg::Geometry * makeGeometry(void) const { // create pyramid geometry. Centre plus points around base const osg::Vec3Array *_line= dynamic_cast(getVertexArray()); osg::Geometry *gm=new osg::Geometry; osg::Vec3Array *pts=new osg::Vec3Array; osg::Vec3Array *norms=new osg::Vec3Array; osg::Vec2Array *tcoords=new osg::Vec2Array; int ip; pts->push_back(_pos+osg::Vec3(0,0,_side)*0.5); for (ip=0; ip<4; ip++) { pts->push_back((*_line)[ip]); } for (ip=1; ip<5; ip++) { osg::Vec3 nrm=((*pts)[ip]-(*pts)[0])^((*pts)[ip==4?0:ip+1]-(*pts)[ip]); nrm.normalize( ); norms->push_back(nrm); } gm->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE); gm->setVertexArray(pts); osg::StateSet *dstate= gm->getOrCreateStateSet( ); dstate->setMode( GL_LIGHTING, osg::StateAttribute::ON ); osg::Image* image = osgDB::readImageFile("Images/Brick-Std-Orange.TGA"); if (image) { osg::Texture2D* txt = new osg::Texture2D; txt->setImage(image); txt->setWrap( osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT ); txt->setWrap( osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT ); dstate->setTextureAttributeAndModes(0,txt,osg::StateAttribute::ON); } gm->setNormalArray(norms); //// gm->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_FAN,0,6)); osg::DrawElementsUInt *dui=new osg::DrawElementsUInt(GL_TRIANGLES); for (ip=0; ip<4; ip++) { dui->push_back(0); dui->push_back(ip+1); dui->push_back(ip==3?1:ip+2); } tcoords->push_back(osg::Vec2(2,4)); tcoords->push_back(osg::Vec2(0,0)); tcoords->push_back(osg::Vec2(4,0)); tcoords->push_back(osg::Vec2(0,0)); tcoords->push_back(osg::Vec2(4,0)); gm->setTexCoordArray(0,tcoords); gm->addPrimitiveSet(dui); return gm; } virtual void calcVertices( void) { // must have a position first osg::Vec3Array *edges=new osg::Vec3Array; osg::Vec3 valong; edges->push_back(_pos+osg::Vec3(0.5,0.5,0)*_side); edges->push_back(_pos+osg::Vec3(-0.5,0.5,0)*_side); edges->push_back(_pos+osg::Vec3(-0.5,-0.5,0)*_side); edges->push_back(_pos+osg::Vec3(0.5,-0.5,0)*_side); setVertexArray(edges); } private: osg::Vec3 _pos; // where the pyramid is float _side ; // length of side }; float getheight(const float x, const float y) { // returns the x,y,height of terrain return 150*sin(x*.0020)*cos(y*.0020); } osg::Vec3d getpt(const int np) { // returns the x,y,height of terrain up to maxp^2 points static int maxp =40; int i=np/maxp; int j=np%maxp; // make the random scale 0.00 if you want an equispaced XY grid. float x=3000.0/(maxp-1)*i+16.*(float)rand()/RAND_MAX; float y=3000.0/(maxp-1)*j+16.*(float)rand()/RAND_MAX; float z=getheight(x,y); if (np>=maxp*maxp) z=-1.e32; return osg::Vec3d(x,y,z); } osg::Node* createHUD(const int ndcs,std::string what) { // add a string reporting the type of winding rule tessellation applied osg::Geode* geode = new osg::Geode(); std::string timesFont("fonts/arial.ttf"); // turn lighting off for the text and disable depth test to ensure its always ontop. osg::StateSet* stateset = geode->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING,osg::StateAttribute::OFF); // Disable depth test, and make sure that the hud is drawn after everything // else so that it always appears ontop. stateset->setMode(GL_DEPTH_TEST,osg::StateAttribute::OFF); stateset->setRenderBinDetails(11,"RenderBin"); osg::Vec3 position(50.0f,900.0f,0.0f); osg::Vec3 delta(0.0f,-35.0f,0.0f); { osgText::Text* text = new osgText::Text; geode->addDrawable( text ); std::ostringstream cue; cue<<"Delaunay triangulation with constraints level "<setFont(timesFont); text->setPosition(position); text->setText(cue.str()); text->setColor(osg::Vec4(1.0,1.0,0.8,1.0)); position += delta*(ndcs+2); #if 0 text = new osgText::Text; geode->addDrawable( text ); text->setFont(timesFont); text->setPosition(position); text->setText("(use 'W' wireframe & 'T' texture to visualise mesh)"); text->setColor(osg::Vec4(1.0,1.0,0.8,1.0)); position += delta; #endif } { osgText::Text* text = new osgText::Text; geode->addDrawable( text ); text->setFont(timesFont); text->setPosition(position); text->setText("Press 'n' to add another constraint."); } // create the hud. osg::MatrixTransform* modelview_abs = new osg::MatrixTransform; modelview_abs->setReferenceFrame(osg::Transform::ABSOLUTE_RF); modelview_abs->setMatrix(osg::Matrix::identity()); modelview_abs->addChild(geode); osg::Projection* projection = new osg::Projection; projection->setMatrix(osg::Matrix::ortho2D(0,1280,0,1024)); projection->addChild(modelview_abs); return projection; } osg::Group *makedelaunay(const int ndcs) { // create a terrain tile. This is just an example! // ndcs is the number of delaunay constraints to be applied osg::ref_ptr grp=new osg::Group; osg::ref_ptr geode=new osg::Geode; osg::ref_ptr trig=new osgUtil::DelaunayTriangulator(); osg::StateSet *stateset=geode->getOrCreateStateSet(); osg::Vec3Array *points=new osg::Vec3Array; osg::Image* image = osgDB::readImageFile("Images/blueFlowers.png"); if (image) { osg::Texture2D* texture = new osg::Texture2D; texture->setImage(image); texture->setWrap( osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT ); texture->setWrap( osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT ); stateset->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON); } geode->setStateSet( stateset ); unsigned int i; int eod=0; while (eod>=0) { osg::Vec3d pos=getpt(eod); if (pos.z()>-10000) { points->push_back(pos); eod++; } else { eod=-9999; } } std::vector < pyramid* > pyrlist; osg::ref_ptr wc; // This example does not remove the interior osg::ref_ptr dc2; osg::ref_ptr forest; osg::ref_ptr dc3; osg::ref_ptr dc6; osg::ref_ptr dc6a; osg::ref_ptr dc8; osg::ref_ptr forestroad; osg::ref_ptr forestroad2; osg::ref_ptr forestroad3; osg::ref_ptr dc; std::ostringstream what; if (1==0) { // add a simple constraint of few points osg::ref_ptr dc=new osgUtil::DelaunayConstraint; osg::Vec3Array *bounds=new osg::Vec3Array; unsigned int nmax=4; for (i=0 ; ipush_back(osg::Vec3(x,y,getheight(x,y))); } dc->setVertexArray(bounds); dc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP,0,nmax) ); trig->addInputConstraint(dc.get()); what << nmax << " point simple constraint\n"; } if (ndcs>0) { // add 5 pyramids for (unsigned int ipy=0; ipy<5/*5*/; ipy++) { osg::ref_ptr pyr=new pyramid; float x=2210+ipy*120, y=1120+ipy*220; pyr->setpos(osg::Vec3(x,y,getheight(x,y)),125.0+10*ipy); pyr->calcVertices(); // make vertices pyr->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP,0,4) ); trig->addInputConstraint(pyr.get()); pyrlist.push_back(pyr.get()); } what << 5 << " pyramids\n"; if (ndcs>1) { // add a simple constraint feature - this can cut holes in the terrain or just leave the triangles // with edges forced to the constraint. dc=new osgUtil::DelaunayConstraint; osg::Vec3Array *bounds=new osg::Vec3Array; for (i=0 ; i<12; i++) { float x=610.0+420*sin(i/3.0),y=610.0+420*cos(i/3.0); bounds->push_back(osg::Vec3(x,y,getheight(x,y))); } dc->setVertexArray(bounds); dc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP,0,12) ); trig->addInputConstraint(dc.get()); what << 12 << " point closed loop"; if (ndcs>2) { wc=new WallConstraint; // This example does not remove the interior // eg to force terrain edges that are on ridges in the terrain etc. // use wireframe to see the constrained edges. // NB this is not necessarily a closed loop of edges. // we do however build a wall at the coordinates. bounds=new osg::Vec3Array; for (i=0 ; i<5; i++) { float x=1610.0+420*sin(i/1.0),y=1610.0+420*cos(i/1.0); bounds->push_back(osg::Vec3(x,y,getheight(x,y))); } wc->setVertexArray(bounds); wc->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP,0,5) ); wc->setHeight(12.0); trig->addInputConstraint(wc.get()); what << " with interior removed\n"; what << 5 << " point wall derived constraint\n"; if (ndcs>3) { // add a removed area and replace it with a different texture dc2=new ArealConstraint; bounds=new osg::Vec3Array; for (i=0 ; i<18; i++) { float x=1610.0+420*sin(i/3.0),y=610.0+220*cos(i/3.0); bounds->push_back(osg::Vec3(x,y,getheight(x,y))); } dc2->setVertexArray(bounds); dc2->setTexrep(100,100); // texture is repeated at this frequency dc2->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP,0,18) ); trig->addInputConstraint(dc2.get()); what << 18 << " point area replaced\n"; if (ndcs>4) { dc3=new LinearConstraint; // a linear feature or 'road' osg::Vec3Array *verts=new osg::Vec3Array; for (i=0 ; i<32; i++) { float x=610.0+50*i+90*sin(i/5.0),y=1110.0+90*cos(i/5.0); verts->push_back(osg::Vec3(x,y,getheight(x,y))); } dc3->setVertices(verts,9.5); // width of road for (osg::Vec3Array::iterator vit=points->begin(); vit!=points->end(); ) { if (dc3->contains(*vit)) { vit=points->erase(vit); } else { vit++; } } trig->addInputConstraint(dc3.get()); what << 32 << " point road constraint\n"; if (ndcs>5) { // add a removed area and replace it with a 'forest' with textured roof and walls forest=new ArealConstraint; bounds=new osg::Vec3Array; for (i=0 ; i<12; i++) { float x=610.0+420*sin(i/2.0),y=1810.0+420*cos(i/2.0); bounds->push_back(osg::Vec3(x,y,getheight(x,y))); } forest->setVertexArray(bounds); forest->setHeight(50); forest->setWallTexrep(100,50); forest->setTexrep(100,100); // texture is repeated at this frequency forest->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP,0,12) ); if (ndcs==6) trig->addInputConstraint(forest.get()); what << 12 << " point forest constraint\n"; if (ndcs>6) { // add roads that intersect forest osg::ref_ptr forestplus=new osgUtil::DelaunayConstraint; forestroad=new LinearConstraint; verts=new osg::Vec3Array; for (i=0 ; i<12; i++) { int ip=(i-6)*(i-6); float xp=410.0+20.0*ip; float y=1210.0+150*i; verts->push_back(osg::Vec3(xp,y,getheight(xp,y))); } forestroad->setVertices(verts,22); // add road forestplus->merge(forestroad.get()); forestroad2=new LinearConstraint; verts=new osg::Vec3Array; for (i=0 ; i<12; i++) { int ip=(i-6)*(i-6); float xp=810.0-10.0*ip; float y=1010.0+150*i; verts->push_back(osg::Vec3(xp,y,getheight(xp,y))); } forestroad2->setVertices(verts,22); // add road forestplus->merge(forestroad2.get()); forestroad3=new LinearConstraint; verts=new osg::Vec3Array; for (i=0 ; i<6; i++) { int ip=(i-6)*(i-6); float xp=210.0+140.0*i+ip*10.0; float y=1510.0+150*i; verts->push_back(osg::Vec3(xp,y,getheight(xp,y))); } forestroad3->setVertices(verts,22); // add road forestplus->merge(forestroad3.get()); forestplus->merge(forest.get()); forestplus->handleOverlaps(); for (osg::Vec3Array::iterator vit=points->begin(); vit!=points->end(); ) { if (forestroad->contains(*vit)) { vit=points->erase(vit); } else if (forestroad2->contains(*vit)) { vit=points->erase(vit); } else if (forestroad3->contains(*vit)) { vit=points->erase(vit); } else { vit++; } } trig->addInputConstraint(forestplus.get()); what << " roads intersect forest constraint\n"; if (ndcs>7) { // this option adds a more complex DC // made of several (ok 2 - extend your own way) overlapping DC's osg::ref_ptr dcoverlap=new osgUtil::DelaunayConstraint; float x=1200; float y=1900; { verts=new osg::Vec3Array; dc6=new LinearConstraint; verts->push_back(osg::Vec3(x-180,y,getheight(x-180,y))); verts->push_back(osg::Vec3(x+180,y,getheight(x+180,y))); dc6->setVertices(verts,22); // width of road dcoverlap->merge(dc6.get()); } { dc6a= new LinearConstraint; verts=new osg::Vec3Array; verts->push_back(osg::Vec3(x,y-180,getheight(x,y-180))); verts->push_back(osg::Vec3(x-20,y,getheight(x,y))); verts->push_back(osg::Vec3(x,y+180,getheight(x,y+180))); dc6a->setVertices(verts,22); // width of road dcoverlap->merge(dc6a.get()); } what << "2 intersecting roads, with added points\n"; if (ndcs>9) { // add yet more roads dc8= new LinearConstraint; verts=new osg::Vec3Array; float rad=60.0; for (float theta=0; theta<4*osg::PI; theta+=0.1*osg::PI) { float xp=x+rad*cos(theta), yp=y+rad*sin(theta); verts->push_back(osg::Vec3(xp,yp,getheight(xp,yp))); rad+=2.5; } dc8->setVertices(verts,16); // width of road dcoverlap->merge(dc8.get()); what << "Spiral road crosses several other constraints."; } dcoverlap->handleOverlaps(); if (ndcs>8) { // remove vertices cleans up the texturing at the intersection. dcoverlap->removeVerticesInside(dc6.get()); dcoverlap->removeVerticesInside(dc6a.get()); if (dc8.valid()) dcoverlap->removeVerticesInside(dc8.get()); what << " remove internal vertices to improve texturing."; } for (osg::Vec3Array::iterator vit=points->begin(); vit!=points->end(); ) { if (dcoverlap->contains(*vit)) { vit=points->erase(vit); } else { vit++; } } trig->addInputConstraint(dcoverlap.get()); } } } } } } } } // ndcs>0 trig->setInputPointArray(points); /** NB you need to supply a vec3 array for the triangulator to calculate normals into */ osg::Vec3Array *norms=new osg::Vec3Array; trig->setOutputNormalArray(norms); trig->triangulate(); osg::notify(osg::WARN) << " End of trig\n " < gm=new osg::Geometry; gm->setVertexArray(points); // points may have been modified in order by triangulation. /** calculate texture coords for terrain points */ if (image) { float repeat=150.0, ry=150.0; // how often to repeat texture osg::Vec2Array *tcoords=new osg::Vec2Array; for (osg::Vec3Array::iterator itr=points->begin(); itr!=points->end(); itr++) { osg::Vec2 tcatxy((*itr).x()/repeat,(*itr).y()/ry); tcoords->push_back(tcatxy); } gm->setTexCoordArray(0,tcoords); } gm->addPrimitiveSet(trig->getTriangles()); gm->setNormalArray(trig->getOutputNormalArray()); gm->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE); geode->addDrawable(gm.get()); if (ndcs>0) { for ( std::vector < pyramid* >::iterator itr=pyrlist.begin(); itr!=pyrlist.end(); itr++) { trig->removeInternalTriangles(*itr); geode->addDrawable((*itr)->makeGeometry()); // this fills the holes of each pyramid with geometry } if (ndcs>2) { trig->removeInternalTriangles(dc.get()); wc->setTexture("Images/Brick-Norman-Brown.TGA"); // wall looks like brick geode->addDrawable(wc->makeWallGeometry()); // this creates wall at wc drawarrays if (ndcs>3) { trig->removeInternalTriangles(dc2.get()); osg::ref_ptr arpts=dc2->getPoints(points); dc2->setTexture("Images/purpleFlowers.png"); geode->addDrawable(dc2->makeAreal(arpts.get())); // this creates fill in geometry if (ndcs>4) { // a simple "road" trig->removeInternalTriangles(dc3.get()); dc3->setTexture ("Images/road.png"); dc3->setTexrep(40,9.5); // texture is repeated at this frequency geode->addDrawable(dc3->makeGeometry(points)); // this creates road geometry if (ndcs>5) { if (ndcs>6) { // road & forest overlap - order of removal is important trig->removeInternalTriangles(forestroad.get()); trig->removeInternalTriangles(forestroad2.get()); trig->removeInternalTriangles(forestroad3.get()); } trig->removeInternalTriangles(forest.get()); forest->setTexture("Images/forestRoof.png"); osg::ref_ptr locpts=forest->getPoints(points); geode->addDrawable(forest->makeAreal(locpts.get())); forest->setWallTexture("Images/forestWall.png"); geode->addDrawable(forest->makeWallGeometry(locpts.get()) ); for (osg::Vec3Array::iterator vit=(*locpts).begin(); vit!=(*locpts).end(); vit++) { (*vit)+=osg::Vec3(0,0,30); } if (ndcs>6) {// road & forest overlap forestroad->setTexture ("Images/road.png"); forestroad->setTexrep(40,22); // texture is repeated at this frequency geode->addDrawable(forestroad->makeGeometry(points)); // this creates road geometry forestroad2->setTexture ("Images/road.png"); forestroad2->setTexrep(40,22); // texture is repeated at this frequency geode->addDrawable(forestroad2->makeGeometry(points)); // this creates road geometry forestroad3->setTexture ("Images/road.png"); forestroad3->setTexrep(40,22); // texture is repeated at this frequency geode->addDrawable(forestroad3->makeGeometry(points)); // this creates road geometry if (ndcs>7) {// several overlapping DC's - add geom trig->removeInternalTriangles(dc6.get()); // dc6->makeDrawable(); // dc6a->makeDrawable(); dc6->setTexture ("Images/road.png"); dc6->setTexrep(40,22); // texture is repeated at this frequency geode->addDrawable(dc6->makeGeometry(points)); // this creates road geometry trig->removeInternalTriangles(dc6a.get()); dc6a->setTexture ("Images/road.png"); dc6a->setTexrep(40,22); // texture is repeated at this frequency geode->addDrawable(dc6a->makeGeometry(points)); // this creates road geometry if (dc8.valid()) { trig->removeInternalTriangles(dc8.get()); dc8->setTexture ("Images/road.png"); dc8->setTexrep(40,16); // texture is repeated at this frequency geode->addDrawable(dc8->makeGeometry(points)); // this creates road geometry } } } } } } } } grp->addChild(geode.get()); grp->addChild(createHUD(ndcs,what.str())); return grp.release(); } class KeyboardEventHandler : public osgGA::GUIEventHandler { // extra event handler traps 'n' key to re-triangulate the basic terrain. public: KeyboardEventHandler(osgViewer::Viewer &vr): viewer(vr), iview(0) {} virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter&) { switch(ea.getEventType()) { case(osgGA::GUIEventAdapter::KEYDOWN): { if (ea.getKey()=='n') { iview++; if (iview>10) iview=0; osg::ref_ptr loadedModel = makedelaunay(iview); viewer.setSceneData(loadedModel.get()); return true; } break; } default: break; } return false; } osgViewer::Viewer &viewer; int iview; }; osg::Vec3Array * WallConstraint::getWall(const float height) const { // return array of points for a wall height high around the constraint osg::Vec3Array *wall=new osg::Vec3Array; if (height>0.0) { osg::Vec3 off(0,0,height); const osg::Vec3Array *vertices= dynamic_cast(getVertexArray()); for (unsigned int ipr=0; iprgetMode()==osg::PrimitiveSet::LINE_LOOP || prset->getMode()==osg::PrimitiveSet::LINE_STRIP) { // nothing else loops // start with the last point on the loop for (unsigned int i=0; igetNumIndices(); i++) { const osg::Vec3 curp=(*vertices)[prset->index (i)]; wall->push_back(curp); wall->push_back(curp+off); } const osg::Vec3 curp=(*vertices)[prset->index (0)]; wall->push_back(curp); wall->push_back(curp+off); } } } return wall; } osg::Vec2Array * WallConstraint::getWallTexcoords(const float height) const { // return array of points for a wall height high around the constraint osg::Vec2Array *tcoords= NULL; if (height>0.0) { float texrepRound=txxrepWall; tcoords= new osg::Vec2Array; float circumference=0; // distance around wall to get exact number of repeats of texture const osg::Vec3Array *vertices= dynamic_cast(getVertexArray()); for (unsigned int ipr=0; iprindex (prset->getNumIndices()-1)]; unsigned int i; for (i=0; igetNumIndices(); i++) { const osg::Vec3 curp=(*vertices)[prset->index (i)]; circumference+=(curp-prevp).length(); prevp=curp; } const osg::Vec3 curp=(*vertices)[prset->index (0)]; circumference+=(curp-prevp).length(); int nround=(int)(circumference/txxrepWall); if (nround<1) nround=1; // at least one repeat. texrepRound=circumference/nround; float ds=0; prevp=(*vertices)[prset->index (prset->getNumIndices()-1)]; if (tcoords) { for (i=0; igetNumIndices(); i++) { const osg::Vec3 curp=(*vertices)[prset->index (i)]; osg::Vec2 tci=osg::Vec2f(ds/texrepRound,0/txyrepWall); tcoords->push_back(tci); tci=osg::Vec2f(ds/texrepRound,height/txyrepWall); tcoords->push_back(tci); ds+=(curp-prevp).length(); prevp=curp; } osg::Vec2 tci=osg::Vec2f(ds/texrepRound,0/txyrepWall); tcoords->push_back(tci); tci=osg::Vec2f(ds/texrepRound,height/txyrepWall); tcoords->push_back(tci); } } // per primitiveset } return tcoords; } osg::Geometry *WallConstraint::makeWallGeometry() const { osg::ref_ptr gm=new osg::Geometry; // the wall if (texture!="") { osg::Image* image = osgDB::readImageFile(texture.c_str()); if (image) { osg::Texture2D* txt = new osg::Texture2D; osg::StateSet* stateset = gm->getOrCreateStateSet(); txt->setImage(image); txt->setWrap( osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT ); txt->setWrap( osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP ); stateset->setTextureAttributeAndModes(0,txt,osg::StateAttribute::ON); osg::Material* material = new osg::Material; material->setAmbient(osg::Material::FRONT_AND_BACK,osg::Vec4(1.0f,1.0f,0.0f,1.0f)); material->setDiffuse(osg::Material::FRONT_AND_BACK,osg::Vec4(1.0f,1.0f,1.0f,1.0f)); stateset->setAttribute(material,osg::StateAttribute::ON); stateset->setMode( GL_LIGHTING, osg::StateAttribute::ON ); } } gm->setVertexArray(getWall(height)); gm->addPrimitiveSet(makeWall()); gm->setTexCoordArray(0,getWallTexcoords(height)); gm->setNormalBinding(osg::Geometry::BIND_PER_VERTEX); gm->setNormalArray(getWallNormals()); // this creates normals to walls return gm.release(); } osg::Vec3Array *ArealConstraint::getWallNormals() const { osg::Vec3Array *nrms=new osg::Vec3Array; const osg::Vec3Array *vertices= dynamic_cast(getVertexArray()); for (unsigned int ipr=0; iprgetMode()==osg::PrimitiveSet::LINE_LOOP) { // nothing else loops // start with the last point on the loop osg::Vec3 prevp=(*vertices)[prset->index (prset->getNumIndices()-1)]; for (unsigned int i=0; igetNumIndices(); i++) { const osg::Vec3 curp=(*vertices)[prset->index (i)]; osg::Vec3 nrm=(curp-prevp)^osg::Vec3(0,0,1); nrm.normalize(); nrms->push_back(nrm); nrms->push_back(nrm); prevp=curp; } const osg::Vec3 curp=(*vertices)[prset->index (0)]; osg::Vec3 nrm=(curp-prevp)^osg::Vec3(0,0,1); nrm.normalize(); nrms->push_back(nrm); nrms->push_back(nrm); } } return nrms; } osg::Vec3Array * ArealConstraint::getWall(const float height) const { // return array of points for a wall height high around the constraint osg::Vec3Array *wall=new osg::Vec3Array; if (height>0.0) { osg::Vec3 off(0,0,height); const osg::Vec3Array *vertices= dynamic_cast(getVertexArray()); for (unsigned int ipr=0; iprgetMode()==osg::PrimitiveSet::LINE_LOOP) { // nothing else loops // start with the last point on the loop for (unsigned int i=0; igetNumIndices(); i++) { const osg::Vec3 curp=(*vertices)[prset->index (i)]; wall->push_back(curp); wall->push_back(curp+off); } const osg::Vec3 curp=(*vertices)[prset->index (0)]; wall->push_back(curp); wall->push_back(curp+off); } } } return wall; } osg::Vec2Array * ArealConstraint::getWallTexcoords(const float height) const { // return array of points for a wall height high around the constraint osg::Vec2Array *tcoords= NULL; if (height>0.0) { float texrepRound=txxrepWall; tcoords= new osg::Vec2Array; float circumference=0; // distance around wall to get exact number of repeats of texture const osg::Vec3Array *vertices= dynamic_cast(getVertexArray()); for (unsigned int ipr=0; iprindex (prset->getNumIndices()-1)]; unsigned int i; for (i=0; igetNumIndices(); i++) { const osg::Vec3 curp=(*vertices)[prset->index (i)]; circumference+=(curp-prevp).length(); prevp=curp; } const osg::Vec3 curp=(*vertices)[prset->index (0)]; circumference+=(curp-prevp).length(); int nround=(int)(circumference/txxrepWall); if (nround<1) nround=1; // at least one repeat. texrepRound=circumference/nround; float ds=0; prevp=(*vertices)[prset->index (prset->getNumIndices()-1)]; if (tcoords) { for (i=0; igetNumIndices(); i++) { const osg::Vec3 curp=(*vertices)[prset->index (i)]; osg::Vec2 tci=osg::Vec2f(ds/texrepRound,0/txyrepWall); tcoords->push_back(tci); tci=osg::Vec2f(ds/texrepRound,height/txyrepWall); tcoords->push_back(tci); ds+=(curp-prevp).length(); prevp=curp; } osg::Vec2 tci=osg::Vec2f(ds/texrepRound,0/txyrepWall); tcoords->push_back(tci); tci=osg::Vec2f(ds/texrepRound,height/txyrepWall); tcoords->push_back(tci); } } // per primitiveset } return tcoords; } osg::DrawArrays* ArealConstraint::makeCanopy( void ) const { return (new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,0,3*_interiorTris.size())); } osg::Vec3Array *ArealConstraint::getCanopy(const osg::Vec3Array *points,const float height) const { // returns the array of vertices in the canopy osg::Vec3 off(0,0,height); osg::Vec3Array *internals=new osg::Vec3Array; trilist::const_iterator tritr; for (tritr=_interiorTris.begin(); tritr!=_interiorTris.end();tritr++) { for (int i=0; i<3; i++) { int index=(*tritr)[i]; internals->push_back((*points)[index]+off); } } return internals; } osg::Vec3Array *ArealConstraint::getCanopyNormals(const osg::Vec3Array *points) const { osg::Vec3Array *nrms=new osg::Vec3Array; trilist::const_iterator tritr; for (tritr=_interiorTris.begin(); tritr!=_interiorTris.end();tritr++) { osg::Vec3 e1=(*points)[(*tritr)[1]]-(*points)[(*tritr)[0]]; osg::Vec3 e2=(*points)[(*tritr)[2]]-(*points)[(*tritr)[0]]; osg::Vec3 nrm=e1^e2; nrm.normalize(); nrms->push_back(nrm); } return nrms; } osg::Vec2Array *ArealConstraint::getCanopyTexcoords(const osg::Vec3Array *points) const { osg::Vec3Array::const_iterator tritr; osg::ref_ptr tcoords= new osg::Vec2Array ; for (tritr=points->begin(); tritr!=points->end();tritr++) { // calculate tcoords for terrain from xy drape. osg::Vec2 tci=osg::Vec2f(tritr->x()/txxrepArea, tritr->y()/txyrepArea); tcoords->push_back(tci); } return tcoords.release(); } osg::DrawArrays * ArealConstraint::makeWall(void) const { // build a wall height high around the constraint const osg::Vec3Array *_line= dynamic_cast(getVertexArray()); return (new osg::DrawArrays(osg::PrimitiveSet::QUAD_STRIP,0,2+2*_line->size())); } osg::Geometry *ArealConstraint::makeWallGeometry( osg::Vec3Array *pt) { osg::ref_ptr gm=new osg::Geometry; // the wall osg::ref_ptr edges=new osg::Geometry; // edges of bounds edges->setVertexArray(pt); osg::DrawElementsUInt *trgeom=getTriangles(); edges->addPrimitiveSet(trgeom); osg::ref_ptr tscx=new osgUtil::Tessellator; // this assembles all the constraints tscx->setTessellationType(osgUtil::Tessellator::TESS_TYPE_GEOMETRY); tscx->setBoundaryOnly(true); tscx->setWindingType( osgUtil::Tessellator::TESS_WINDING_NONZERO); // find all edges. const osg::Vec3Array *points=dynamic_cast(getVertexArray()); tscx->retessellatePolygons(*(edges)); // find all edges if (walltexture!="") { osg::Image* image = osgDB::readImageFile(walltexture.c_str()); if (image) { osg::Texture2D* txt = new osg::Texture2D; osg::StateSet* stateset = gm->getOrCreateStateSet(); txt->setImage(image); txt->setWrap( osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT ); txt->setWrap( osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP ); stateset->setTextureAttributeAndModes(0,txt,osg::StateAttribute::ON); } } points=dynamic_cast(edges->getVertexArray()); int nstart=0; osg::ref_ptr coords=new osg::Vec3Array; osg::ref_ptr tcoords=new osg::Vec2Array; for (unsigned int i=0; igetNumPrimitiveSets(); i++) { osg::PrimitiveSet *pr=edges->getPrimitiveSet(i); if (pr->getMode() == osg::PrimitiveSet::LINE_LOOP) { float ds=0; for (unsigned int icon=0; icongetNumIndices(); icon++) { unsigned int ithis=pr->index(icon); osg::Vec3 pt= (*points)[ithis]; coords->push_back(pt); coords->push_back(pt+osg::Vec3(0,0,height)); tcoords->push_back(osg::Vec2(ds/txxrepWall,0)); tcoords->push_back(osg::Vec2(ds/txxrepWall,1.0)); if (icongetNumIndices()-1) ds+=((*points)[pr->index(icon+1)]-(*points)[ithis]).length(); else ds+=((*points)[pr->index(0)]-(*points)[ithis]).length(); } // repeat first point unsigned int ithis=pr->index(0); coords->push_back((*points)[ithis]); coords->push_back((*points)[ithis]+osg::Vec3(0,0,height)); tcoords->push_back(osg::Vec2(ds/txxrepWall,0)); tcoords->push_back(osg::Vec2(ds/txxrepWall,1.0)); gm->setVertexArray(coords.get()); gm->setTexCoordArray(0,tcoords.get()); gm->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUAD_STRIP,nstart,2+2*pr->getNumIndices())); nstart+=2+2*pr->getNumIndices(); } } return gm.release(); } osg::Geometry * ArealConstraint::makeAreal( osg::Vec3Array *points) { osg::ref_ptr gm; // the fill in area if (_interiorTris.size()>0) { gm =new osg::Geometry; // the forest roof gm->setVertexArray(points); osg::DrawElementsUInt *trgeom=getTriangles(); gm->addPrimitiveSet(trgeom); gm->setNormalArray(getCanopyNormals(points)); gm->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE); gm->setTexCoordArray(0,getCanopyTexcoords(points)); osg::Image* image = osgDB::readImageFile(texture); if (image) { osg::Texture2D* txt = new osg::Texture2D; osg::StateSet* stateset = gm->getOrCreateStateSet(); txt->setImage(image); txt->setWrap( osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT ); txt->setWrap( osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT ); stateset->setTextureAttributeAndModes(0,txt,osg::StateAttribute::ON); osg::Material* material = new osg::Material; material->setAmbient(osg::Material::FRONT_AND_BACK,osg::Vec4(1.0f,1.0f,1.0f,1.0f)); material->setDiffuse(osg::Material::FRONT_AND_BACK,osg::Vec4(1.0f,1.0f,1.0f,1.0f)); stateset->setAttribute(material,osg::StateAttribute::ON); stateset->setMode( GL_LIGHTING, osg::StateAttribute::ON ); } } return gm.release(); } void LinearConstraint::setVertices( osg::Vec3Array *lp, const float w) { // generate constant width around line (calls setvertices(edges)) osg::ref_ptr edges=new osg::Vec3Array; _tcoords=new osg::Vec2Array; // texture coordinates for replacement geometry _edgecoords=new osg::Vec3Array; // posiiton coordinates for replacement geometry width=w; _midline=lp; float ds=0; for(unsigned int i=0;isize();i++) { osg::Vec3 valong; osg::Vec3 pos[2]; if (i==0) { valong=(*lp)[i+1]-(*lp)[i]; } else if (i==lp->size()-1) { valong=(*lp)[i]-(*lp)[i-1]; } else { valong=(*lp)[i+1]-(*lp)[i-1]; } valong.normalize(); osg::Vec3 vperp=valong^osg::Vec3(0,0,1); pos[0]=(*lp)[i]-vperp*.5*width; pos[1]=(*lp)[i]+vperp*.5*width; edges->push_back(pos[0]); _edgecoords->push_back(pos[0]); _tcoords->push_back(osg::Vec2(0/txyrepAcross,ds/txxrepAlong)); edges->insert(edges->begin() ,pos[1]); _edgecoords->insert(_edgecoords->begin() ,pos[1]); _tcoords->insert(_tcoords->begin() ,osg::Vec2(width/txyrepAcross,ds/txxrepAlong)); if (isize()-1) ds+=((*lp)[i+1]-(*lp)[i]).length(); } setVertexArray(edges.get()); addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP,0,edges->size()) ); } osg::DrawArrays* LinearConstraint::makeRoad(void ) const { return new osg::DrawArrays(osg::PrimitiveSet::QUAD_STRIP,0,2*_midline->size()); } osg::Vec3Array *LinearConstraint::getRoadNormals(const osg::Vec3Array* /*points*/) const { osg::Vec3Array *nrms=new osg::Vec3Array; for(unsigned int i=0;i<_midline->size();i++) { osg::Vec3 valong; // vector along midline of road if (i==0) { valong=(*_midline)[i+1]-(*_midline)[i]; } else if (i==_midline->size()-1) { valong=(*_midline)[i]-(*_midline)[i-1]; } else { valong=(*_midline)[i+1]-(*_midline)[i-1]; } osg::Vec3 vperp=valong^osg::Vec3(0,0,1); osg::Vec3 nrm=vperp^valong; // normal to linear nrm.normalize(); nrms->push_back(nrm); // repeated for each vertex of linear. nrms->push_back(nrm); } return nrms; } osg::Vec3Array *LinearConstraint::getRoadVertices() const { osg::Vec3Array *linearEdges=new osg::Vec3Array; for(unsigned int i=0;i<_midline->size();i++) { osg::Vec3 valong; // vector along midline of road if (i==0) { valong=(*_midline)[i+1]-(*_midline)[i]; } else if (i==_midline->size()-1) { valong=(*_midline)[i]-(*_midline)[i-1]; } else { valong=(*_midline)[i+1]-(*_midline)[i-1]; } valong.normalize(); osg::Vec3 vperp=valong^osg::Vec3(0,0,1); // vector across road // sides of linear linearEdges->push_back((*_midline)[i]-vperp*.5*width); linearEdges->push_back((*_midline)[i]+vperp*.5*width); } return linearEdges; } osg::Vec2Array *LinearConstraint::getRoadTexcoords(const osg::Vec3Array *points) { // need to create a vec2 array from the coordinates that fits the road osg::Vec3Array::const_iterator tritr; osg::ref_ptr tcoords= new osg::Vec2Array ; for (tritr=points->begin(); tritr!=points->end();tritr++) { osg::Vec2 tci(-1.,-1.); int ib=0; // osg::Vec3Array *varr=dynamic_cast(getVertexArray()); bool ptfound=false; for (osg::Vec3Array::iterator vit=_edgecoords->begin(); vit!= _edgecoords->end() && !ptfound; vit++) { if ((*vit)==(*tritr)) { tci=_tcoords->at(ib); ptfound=true; } ib++; } if (!ptfound) { // search for surrounding points and interpolate ib=0; osg::Vec3 pminus=(_edgecoords->back()); // need pminus for interpolation int ibm1=_edgecoords->size()-1; for (osg::Vec3Array::iterator vit=_edgecoords->begin(); vit!= _edgecoords->end() /*&& !ptfound*/; vit++) { osg::Vec3 pplus=(*vit)-(*tritr); osg::Vec3 dpm=pminus-(*tritr); pplus.set (pplus.x(),pplus.y(),0); dpm.set (dpm.x(),dpm.y(),0); float dprod=pplus*dpm/(pplus.length() * dpm.length()); if (dprod<-0.9999) { // *tritr lies between.... osg::Vec2 tminus=_tcoords->at(ibm1); osg::Vec2 tplus=_tcoords->at(ib); float frac=(dpm.length()/(dpm.length()+pplus.length())); tci=tminus+((tplus-tminus)*frac); ptfound=true; } ibm1=ib; ib++; pminus=(*vit); } } tcoords->push_back(tci); } // some extra points are not interpolated as they lie between 2 interpolated vertices for (tritr=points->begin(); tritr!=points->end();tritr++) { int ib=tritr-points->begin(); osg::Vec2 tci=tcoords->at(ib); if (tci.x()<-.99 && tci.y()<-.99) { // search through each of the primitivesets osg::Vec3Array::const_iterator ptitr; // osg::notify(osg::WARN) << "Not calculated " << (*tritr).x() <<"," << (*tritr).y() << std::endl; for (ptitr=points->begin(); ptitr!=points->end();ptitr++) { } } } return tcoords.release(); } osg::Vec3Array * LinearConstraint::getNormals(const osg::Vec3Array *points) { osg::ref_ptr norms=new osg::Vec3Array; for (osg::DrawElementsUInt::iterator uiitr=prim_tris_->begin(); uiitr!=prim_tris_->end();uiitr+=3) { osg::Vec3 e1=(*points)[*(uiitr+1)]-(*points)[(*uiitr)]; osg::Vec3 e2=(*points)[*(uiitr+2)]-(*points)[*(uiitr+1)]; osg::Vec3 n=e1^e2; n.normalize(); // if (n.z()<0) n=-n; norms->push_back(n); } return norms.release(); } osg::Geometry * LinearConstraint::makeGeometry(const osg::Vec3Array *points) { osg::ref_ptr gm=new osg::Geometry; // the fill in road/railway if (_midline->size()>0) { osg::ref_ptr locpts=getPoints(points); if (texture!="") { osg::Image* image = osgDB::readImageFile(texture.c_str()); if (image) { osg::Texture2D* txt = new osg::Texture2D; osg::StateSet* stateset = gm->getOrCreateStateSet(); txt->setImage(image); txt->setWrap( osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT ); txt->setWrap( osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT ); stateset->setTextureAttributeAndModes(0,txt,osg::StateAttribute::ON); osg::Material* material = new osg::Material; material->setAmbient(osg::Material::FRONT_AND_BACK,osg::Vec4(1.0f,1.0f,1.0f,1.0f)); material->setDiffuse(osg::Material::FRONT_AND_BACK,osg::Vec4(1.0f,1.0f,1.0f,1.0f)); stateset->setAttribute(material,osg::StateAttribute::ON); stateset->setMode( GL_LIGHTING, osg::StateAttribute::ON ); } gm->setTexCoordArray(0,getRoadTexcoords(locpts.get())); } gm->setVertexArray(locpts.get()); gm->setNormalArray(getNormals(locpts.get())); gm->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE); gm->addPrimitiveSet(getTriangles()); } return gm.release(); } int main( int argc, char **argv ) { // use an ArgumentParser object to manage the program arguments. osg::ArgumentParser arguments(&argc,argv); // construct the viewer. osgViewer::Viewer viewer; // create the scene from internal specified terrain/constraints. osg::ref_ptr loadedModel = makedelaunay(0); // if no model has been successfully loaded report failure. if (!loadedModel) { std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl; return 1; } // optimize the scene graph, remove rendundent nodes and state etc. osgUtil::Optimizer optimizer; optimizer.optimize(loadedModel.get()); // pass the loaded scene graph to the viewer. viewer.setSceneData(loadedModel.get()); // copied from osgtessealte.cpp // add event handler for keyboard 'n' to retriangulate viewer.addEventHandler(new KeyboardEventHandler(viewer)); return viewer.run(); }