Hi, Please find attached a very small fix for the DXF reader. The bug made OSG crash with some files. Actually, itr was incremented into the loop and after the test with nlist.end(). Then, the unreferencing of itr when nlist is equals to nlist.end() caused the crash.
Cheers, Joachim
/* dxfReader for OpenSceneGraph Copyright (C) 2005 by GraphArchitecture ( grapharchitecture.com ) * Programmed by Paul de Repentigny <[email protected]> * * OpenSceneGraph is (C) 2004 Robert Osfield * * This library is provided as-is, without support of any kind. * * Read DXF docs or OSG docs for any related questions. * * You may contact the author if you have suggestions/corrections/enhancements. */ #include "dxfEntity.h" #include "dxfFile.h" #include "scene.h" #include "dxfBlock.h" #include "codeValue.h" #include <osg/io_utils> // just for debugging using namespace std; using namespace osg; // static std::map<std::string, ref_ptr<dxfBasicEntity> > dxfEntity::_registry; RegisterEntityProxy<dxf3DFace> g_dxf3DFace; RegisterEntityProxy<dxfCircle> g_dxfCircle; RegisterEntityProxy<dxfArc> g_dxfArc; RegisterEntityProxy<dxfPoint> g_dxfPoint; RegisterEntityProxy<dxfLine> g_dxfLine; RegisterEntityProxy<dxfVertex> g_dxfVertex; RegisterEntityProxy<dxfPolyline> g_dxfPolyline; RegisterEntityProxy<dxfLWPolyline> g_dxfLWPolyline; RegisterEntityProxy<dxfInsert> g_dxfInsert; RegisterEntityProxy<dxfText> g_dxfText; void dxfBasicEntity::assign(dxfFile* , codeValue& cv) { switch (cv._groupCode) { case 8: _layer = cv._string; break; case 62: _color = cv._short; break; } } void dxf3DFace::assign(dxfFile* dxf, codeValue& cv) { double d = cv._double; switch (cv._groupCode) { case 10: case 11: case 12: case 13: _vertices[cv._groupCode - 10].x() = d; break; case 20: case 21: case 22: case 23: _vertices[cv._groupCode - 20].y() = d; break; case 30: case 31: case 32: case 33: _vertices[cv._groupCode - 30].z() = d; break; default: dxfBasicEntity::assign(dxf, cv); break; } } void dxf3DFace::drawScene(scene* sc) { std::vector<Vec3d> vlist; short nfaces = 3; // Hate to do that, but hey, that's written in the DXF specs: if (_vertices[2] != _vertices[3]) nfaces = 4; for (short i = nfaces-1; i >= 0; i--) vlist.push_back(_vertices[i]); if (nfaces == 3) { // to do make sure we're % 3 sc->addTriangles(getLayer(), _color, vlist); } else if (nfaces == 4) { // to do make sure we're % 4 sc->addQuads(getLayer(), _color, vlist); } } void dxfVertex::assign(dxfFile* dxf, codeValue& cv) { double d = cv._double; // 2005.12.13 pdr: learned today that negative indices mean something and were possible int s = cv._int; // 2005.12.13 pdr: group codes [70,78] now signed int. if ( s < 0 ) s = -s; switch (cv._groupCode) { case 10: _vertex.x() = d; break; case 20: _vertex.y() = d; break; case 30: _vertex.z() = d; break; case 71: _indice1 = s; break; case 72: _indice2 = s; break; case 73: _indice3 = s; break; case 74: _indice4 = s; break; default: dxfBasicEntity::assign(dxf, cv); break; } } void dxfCircle::assign(dxfFile* dxf, codeValue& cv) { double d = cv._double; //unsigned short s = cv._short; switch (cv._groupCode) { case 10: _center.x() = d; break; case 20: _center.y() = d; break; case 30: _center.z() = d; break; case 40: _radius = d; break; case 210: _ocs.x() = d; break; case 220: _ocs.y() = d; break; case 230: _ocs.z() = d; break; default: dxfBasicEntity::assign(dxf, cv); break; } } void dxfCircle::drawScene(scene* sc) { Matrixd m; getOCSMatrix(_ocs, m); sc->ocs(m); std::vector<Vec3d> vlist; double theta=5.0; // we generate polyline from "spokes" at theta degrees at arc's center if (_useAccuracy) { // we generate points on a polyline where each point lies on the arc, thus the maximum error occurs at the midpoint of each line segment where it lies furthest inside the arc // If we divide the segment in half and connect the bisection point to the arc's center, we have two rightangled triangles with // one side=r-maxError, hypotenuse=r, and internal angle at center is half the angle we will step with: double maxError=min(_maxError,_radius); // Avoid offending acos() in the edge case where allowable deviation is greater than radius. double newtheta=acos( (_radius-maxError) / _radius); newtheta=osg::RadiansToDegrees(newtheta)*2.0; // Option to only use the new accuracy code when it would improve on the accuracy of the old method if (_improveAccuracyOnly) { theta=min(newtheta,theta); } else { theta=newtheta; } } theta=osg::DegreesToRadians(theta); // We create an anglestep<=theta so that the line's points are evenly distributed around the circle unsigned int numsteps=static_cast<unsigned int>(floor(osg::PI*2/theta)); if (numsteps<3) numsteps=3; // Sanity check: minimal representation of a circle is a tri double anglestep=osg::PI*2/numsteps; double angle1 = 0.0; Vec3d a = _center; Vec3d b; for(unsigned int r=0;r<=numsteps;r++) { b = a + Vec3d(_radius * (double) sin(angle1), _radius * (double) cos(angle1), 0); angle1 += anglestep; vlist.push_back(b); } sc->addLineStrip(getLayer(), _color, vlist); // Should really add LineLoop implementation and save a vertex sc->ocs_clear(); } void dxfArc::assign(dxfFile* dxf, codeValue& cv) { double d = cv._double; //unsigned short s = cv._short; switch (cv._groupCode) { case 10: _center.x() = d; break; case 20: _center.y() = d; break; case 30: _center.z() = d; break; case 40: _radius = d; break; case 50: _startAngle = d; break; case 51: _endAngle = d; break; case 210: _ocs.x() = d; break; case 220: _ocs.y() = d; break; case 230: _ocs.z() = d; break; default: dxfBasicEntity::assign(dxf, cv); break; } } void dxfArc::drawScene(scene* sc) { Matrixd m; getOCSMatrix(_ocs, m); sc->ocs(m); std::vector<Vec3d> vlist; double end; double start; if (_startAngle > _endAngle) { start = _startAngle; end = _endAngle + 360; } else { start = _startAngle; end = _endAngle; } double theta=5.0; // we generate polyline from "spokes" at theta degrees at arc's center if (_useAccuracy) { // we generate points on a polyline where each point lies on the arc, thus the maximum error occurs at the midpoint of each line segment where it lies furthest inside the arc // If we divide the segment in half and connect the bisection point to the arc's center, we have two rightangled triangles with // one side=r-maxError, hypotenuse=r, and internal angle at center is half the angle we will step with: double maxError=min(_maxError,_radius); // Avoid offending acos() in the edge case where allowable deviation is greater than radius. double newtheta=acos( (_radius-maxError) / _radius); newtheta=osg::RadiansToDegrees(newtheta)*2.0; //cout<<"r="<<_radius<<" _me="<<_maxError<<" (_radius-_maxError)="<<(_radius-_maxError)<<" newtheta="<<newtheta<<endl; // Option to only use the new accuracy code when it would improve on the accuracy of the old method if (_improveAccuracyOnly) { theta=min(newtheta,theta); } else { theta=newtheta; } } double angle_step = DegreesToRadians(end - start); int numsteps = (int)((end - start)/theta); //cout<<"arc theta="<<osg::RadiansToDegrees(theta)<<" end="<<end<<" start="<<start<<" numsteps="<<numsteps<<" e-s/theta="<<((end-start)/theta)<<" end-start="<<(end-start)<<endl; if (numsteps * theta < (end - start)) numsteps++; numsteps=max(numsteps,2); // Whatever else, minimum representation of an arc is a straightline angle_step /= (double) numsteps; end = DegreesToRadians((-_startAngle)+90.0); start = DegreesToRadians((-_endAngle)+90.0); double angle1 = start; Vec3d a = _center; Vec3d b; for (int r = 0; r <= numsteps; r++) { b = a + Vec3d(_radius * (double) sin(angle1), _radius * (double) cos(angle1), 0); angle1 += angle_step; vlist.push_back(b); } sc->addLineStrip(getLayer(), _color, vlist); sc->ocs_clear(); } void dxfLine::assign(dxfFile* dxf, codeValue& cv) { double d = cv._double; //unsigned short s = cv._short; switch (cv._groupCode) { case 10: _a.x() = d; break; case 20: _a.y() = d; break; case 30: _a.z() = d; break; case 11: _b.x() = d; break; case 21: _b.y() = d; break; case 31: _b.z() = d; break; case 210: _ocs.x() = d; break; case 220: _ocs.y() = d; break; case 230: _ocs.z() = d; break; default: dxfBasicEntity::assign(dxf, cv); break; } } void dxfLine::drawScene(scene* sc) { Matrixd m; getOCSMatrix(_ocs, m); // don't know why this doesn't work // sc->ocs(m); sc->addLine(getLayer(), _color, _b, _a); // static long lcount = 0; // std::cout << ++lcount << " "; // sc->ocs_clear(); } void dxfPoint::assign(dxfFile* dxf, codeValue& cv) { double d = cv._double; //unsigned short s = cv._short; switch (cv._groupCode) { case 10: _a.x() = d; break; case 20: _a.y() = d; break; case 30: _a.z() = d; break; default: dxfBasicEntity::assign(dxf, cv); break; } } void dxfPoint::drawScene(scene* sc) { Matrixd m; getOCSMatrix(_ocs, m); sc->addPoint(getLayer(), _color,_a); } void dxfPolyline::assign(dxfFile* dxf, codeValue& cv) { string s = cv._string; if (cv._groupCode == 0) { if (s == "VERTEX") { _currentVertex = new dxfVertex; _vertices.push_back(_currentVertex); } } else if (_currentVertex) { _currentVertex->assign(dxf, cv); if ((_flag & 64 /*i.e. polymesh*/) && (cv._groupCode == 70 /*i.e. vertex flag*/) && (cv._int && 128 /*i.e. vertex is actually a face*/)) _indices.push_back(_currentVertex); // Add the index only if _currentvertex is actually an index } else { double d = cv._double; switch (cv._groupCode) { case 10: // dummy break; case 20: // dummy break; case 30: _elevation = d; // what is elevation? break; case 70: _flag = cv._int; // 2005.12.13 pdr: group codes [70,78] now signed int. break; case 71: // Meaningful only when _surfacetype == 6, don' trust it for polymeshes. // From the docs : // "The 71 group specifies the number of vertices in the mesh, and the 72 group // specifies the number of faces. Although these counts are correct for all meshes // created with the PFACE command, applications are not required to place correct // values in these fields.)" // Amusing isn't it ? _mcount = cv._int; // 2005.12.13 pdr: group codes [70,78] now signed int. break; case 72: // Meaningful only when _surfacetype == 6, don' trust it for polymeshes. // From the docs : // "The 71 group specifies the number of vertices in the mesh, and the 72 group // specifies the number of faces. Although these counts are correct for all meshes // created with the PFACE command, applications are not required to place correct // values in these fields.)" // Amusing isn't it ? _ncount = cv._int; // 2005.12.13 pdr: group codes [70,78] now signed int. break; case 73: _mdensity = cv._int; // 2005.12.13 pdr: group codes [70,78] now signed int. break; case 74: _ndensity = cv._int; // 2005.12.13 pdr: group codes [70,78] now signed int. break; case 75: _surfacetype = cv._int; // 2005.12.13 pdr: group codes [70,78] now signed int. break; case 210: _ocs.x() = d; break; case 220: _ocs.y() = d; break; case 230: _ocs.z() = d; break; default: dxfBasicEntity::assign(dxf, cv); break; } } } void dxfPolyline::drawScene(scene* sc) { Matrixd m; getOCSMatrix(_ocs, m); sc->ocs(m); std::vector<Vec3d> vlist; std::vector<Vec3d> qlist; Vec3d a, b, c, d; bool invert_order = false; if (_flag & 16) { std::vector<Vec3d> nlist; Vec3d nr; bool nset = false; //dxfVertex* v = NULL; unsigned int ncount; unsigned int mcount; if (_surfacetype == 6) { // I dont have examples of type 5 and 8, but they may be the same as 6 mcount = _mdensity; ncount = _ndensity; } else { mcount = _mcount; ncount = _ncount; } for (unsigned int n = 0; n < ncount-1; n++) { for (unsigned int m = 1; m < mcount; m++) { // 0 a = _vertices[(m-1)*ncount+n].get()->getVertex(); // 1 b = _vertices[m*ncount+n].get()->getVertex(); // 3 c = _vertices[(m)*ncount+n+1].get()->getVertex(); // 2 d = _vertices[(m-1)*ncount+n+1].get()->getVertex(); if (a == b ) { vlist.push_back(a); vlist.push_back(c); vlist.push_back(d); b = c; c = d; } else if (c == d) { vlist.push_back(a); vlist.push_back(b); vlist.push_back(c); } else { qlist.push_back(a); qlist.push_back(b); qlist.push_back(c); qlist.push_back(d); } if (!nset) { nset = true; nr = (b - a) ^ (c - a); nr.normalize(); } nlist.push_back(a); } } if (_flag & 1) { for (unsigned int n = 0; n < ncount-1; n++) { // 0 a = _vertices[(mcount-1)*ncount+n].get()->getVertex(); // 1 b = _vertices[0*ncount+n].get()->getVertex(); // 3 c = _vertices[(0)*ncount+n+1].get()->getVertex(); // 2 d = _vertices[(mcount-1)*ncount+n+1].get()->getVertex(); if (a == b ) { vlist.push_back(a); vlist.push_back(c); vlist.push_back(d); b = c; c = d; } else if (c == d) { vlist.push_back(a); vlist.push_back(b); vlist.push_back(c); } else { qlist.push_back(a); qlist.push_back(b); qlist.push_back(c); qlist.push_back(d); } nlist.push_back(a); } } if (_flag & 32) { for (unsigned int m = 1; m < mcount; m++) { // 0 a = _vertices[(m-1)*ncount+(ncount-1)].get()->getVertex(); // 1 b = _vertices[m*ncount+(ncount-1)].get()->getVertex(); // 3 c = _vertices[(m)*ncount].get()->getVertex(); // 2 d = _vertices[(m-1)*ncount].get()->getVertex(); if (a == b ) { vlist.push_back(a); vlist.push_back(c); vlist.push_back(d); b = c; c = d; } else if (c == d) { vlist.push_back(a); vlist.push_back(b); vlist.push_back(c); } else { qlist.push_back(a); qlist.push_back(b); qlist.push_back(c); qlist.push_back(d); } nlist.push_back(a); } } // a naive attempt to determine vertex ordering VList::iterator itr = nlist.begin(); Vec3d lastn = (*itr++); double bad_c = 0; double good_c = 0; long bad=0,good=0; for (; itr != nlist.end(); ++itr) { if ((*itr)== lastn) continue; Vec3d diff = ((*itr)-lastn); diff.normalize(); float dot = diff * nr; if (dot > 0.0) { bad_c += dot; ++bad; } else { ++good; good_c += dot; } } if (bad > good) { invert_order = true; } if (qlist.size()) sc->addQuads(getLayer(), _color, qlist, invert_order); if (vlist.size()) sc->addTriangles(getLayer(), _color, vlist, invert_order); } else if (_flag & 64) { unsigned short _facetype = 3; for (unsigned int i = 0; i < _indices.size(); i++) { dxfVertex* vindice = _indices[i].get(); if (!vindice) continue; if (vindice->getIndice4()) { _facetype = 4; d = _vertices[vindice->getIndice4()-1].get()->getVertex(); } else { _facetype = 3; } if (vindice->getIndice3()) { c = _vertices[vindice->getIndice3()-1].get()->getVertex(); } else { c = vindice->getVertex(); // Vertex not indexed. Use as is } if (vindice->getIndice2()) { b = _vertices[vindice->getIndice2()-1].get()->getVertex(); } else { b = vindice->getVertex(); // Vertex not indexed. Use as is } if (vindice->getIndice1()) { a = _vertices[vindice->getIndice1()-1].get()->getVertex(); } else { a = vindice->getVertex(); // Vertex not indexed. Use as is } if (_facetype == 4) { qlist.push_back(d); qlist.push_back(c); qlist.push_back(b); qlist.push_back(a); } else { // 2005.12.13 pdr: vlist! not qlist! vlist.push_back(c); vlist.push_back(b); vlist.push_back(a); } } if (vlist.size()) sc->addTriangles(getLayer(), _color, vlist); if (qlist.size()) sc->addQuads(getLayer(), _color, qlist); // is there a flag 1 or 32 for 64? } else { // simple polyline? for (int i = _vertices.size()-1; i >= 0; i--) vlist.push_back(_vertices[i]->getVertex()); if (_flag & 1) { // std::cout << "line loop " << _vertices.size() << std::endl; sc->addLineLoop(getLayer(), _color, vlist); } else { // std::cout << "line strip " << _vertices.size() << std::endl; sc->addLineStrip(getLayer(), _color, vlist); } } sc->ocs_clear(); } void dxfLWPolyline::assign(dxfFile* dxf, codeValue& cv) { string s = cv._string; double d = cv._double; switch (cv._groupCode) { case 10: _lastv.x() = d; // x break; case 20: _lastv.y() = d; _lastv.z() = _elevation; _vertices.push_back ( _lastv ); // y -> on shoot break; case 38: _elevation = d; // what is elevation? break; case 70: _flag = cv._int; // 2005.12.13 pdr: group codes [70,78] now signed int. break; case 90: _vcount = cv._short; break; case 210: _ocs.x() = d; break; case 220: _ocs.y() = d; break; case 230: _ocs.z() = d; break; default: dxfBasicEntity::assign(dxf, cv); break; } } void dxfLWPolyline::drawScene(scene* sc) { // if (getLayer() != "UDF2" && getLayer() != "ENGINES") return; // if (!(_flag & 16)) return; Matrixd m; getOCSMatrix(_ocs, m); sc->ocs(m); if (_flag & 1) { // std::cout << "lwpolyline line loop " << _vertices.size() << std::endl; sc->addLineLoop(getLayer(), _color, _vertices); } else { // std::cout << "lwpolyline line strip " << _vertices.size() << std::endl; sc->addLineStrip(getLayer(), _color, _vertices); } sc->ocs_clear(); } void dxfInsert::assign(dxfFile* dxf, codeValue& cv) { string s = cv._string; if (_done || (cv._groupCode == 0 && s != "INSERT")) { _done = true; return; } if (cv._groupCode == 2 && !_block) { _blockName = s; _block = dxf->findBlock(s); } else { double d = cv._double; switch (cv._groupCode) { case 10: _point.x() = d; break; case 20: _point.y() = d; break; case 30: _point.z() = d; break; case 41: _scale.x() = d; break; case 42: _scale.y() = d; break; case 43: _scale.z() = d; break; case 50: _rotation = d; break; case 210: _ocs.x() = d; break; case 220: _ocs.y() = d; break; case 230: _ocs.z() = d; break; default: dxfBasicEntity::assign(dxf, cv); break; } } } /// hum. read the doc, then come back here. then try to figure. void dxfInsert::drawScene(scene* sc) { // INSERTs can be nested. So pull the current matrix // and push it back after we fill our context // This is a snapshot in time. I will rewrite all this to be cleaner, // but for now, it seems working fine // (with the files I have, the results are equal to Voloview, // and better than Deep Exploration and Lightwave). // sanity check (useful when no block remains after all unsupported entities have been filtered out) if (!_block) return; Matrixd back = sc->backMatrix(); Matrixd m; m.makeIdentity(); sc->pushMatrix(m, true); Vec3d trans = _block->getPosition(); sc->blockOffset(-trans); if (_rotation) { sc->pushMatrix(Matrixd::rotate(osg::DegreesToRadians(_rotation), 0,0,1)); } sc->pushMatrix(Matrixd::scale(_scale.x(), _scale.y(), _scale.z())); sc->pushMatrix(Matrixd::translate(_point.x(), _point.y(), _point.z())); getOCSMatrix(_ocs, m); sc->pushMatrix(m); sc->pushMatrix(back); EntityList& l = _block->getEntityList(); for (EntityList::iterator itr = l.begin(); itr != l.end(); ++itr) { dxfBasicEntity* e = (*itr)->getEntity(); if (e) { e->drawScene(sc); } } sc->popMatrix(); // ocs sc->popMatrix(); // translate sc->popMatrix(); // scale if (_rotation) { sc->popMatrix(); // rotate } sc->popMatrix(); // identity sc->popMatrix(); // back sc->blockOffset(Vec3d(0,0,0)); } void dxfText::assign(dxfFile* dxf, codeValue& cv) { switch (cv._groupCode) { case 1: _string = cv._string; break; case 10: _point1.x() = cv._double; break; case 20: _point1.y() = cv._double; break; case 30: _point1.z() = cv._double; break; case 11: _point2.x() = cv._double; break; case 21: _point2.y() = cv._double; break; case 31: _point2.z() = cv._double; break; case 40: _height = cv._double; break; case 41: _xscale = cv._double; break; case 50: _rotation = cv._double; break; case 71: _flags = cv._int; break; case 72: _hjustify = cv._int; break; case 73: _vjustify = cv._int; break; case 210: _ocs.x() = cv._double; break; case 220: _ocs.y() = cv._double; break; case 230: _ocs.z() = cv._double; break; default: dxfBasicEntity::assign(dxf, cv); break; } } void dxfText::drawScene(scene* sc) { osgText::Text::AlignmentType align; Matrixd m; getOCSMatrix(_ocs, m); sc->ocs(m); ref_ptr<osgText::Text> _text = new osgText::Text; _text->setText(_string); _text->setCharacterSize( _height, 1.0/_xscale ); _text->setFont("arial.ttf"); Quat qr( DegreesToRadians(_rotation), Z_AXIS ); if ( _flags & 2 ) qr = Quat( PI, Y_AXIS ) * qr; if ( _flags & 4 ) qr = Quat( PI, X_AXIS ) * qr; _text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION); _text->setRotation(qr); if ( _hjustify != 0 || _vjustify !=0 ) _point1 = _point2; switch (_vjustify) { case 3: switch (_hjustify) { case 2: align = osgText::Text::RIGHT_TOP; break; case 1: align = osgText::Text::CENTER_TOP; break; default: align = osgText::Text::LEFT_TOP; } break; case 2: switch (_hjustify) { case 2: align = osgText::Text::RIGHT_CENTER; break; case 1: align = osgText::Text::CENTER_CENTER; break; default: align = osgText::Text::LEFT_CENTER; } break; case 1: switch (_hjustify) { case 2: align = osgText::Text::RIGHT_BOTTOM; break; case 1: align = osgText::Text::CENTER_BOTTOM; break; default: align = osgText::Text::LEFT_BOTTOM; } break; default: switch (_hjustify) { case 2: align = osgText::Text::RIGHT_BOTTOM_BASE_LINE; break; case 1: align = osgText::Text::CENTER_BOTTOM_BASE_LINE; break; default: align = osgText::Text::LEFT_BOTTOM_BASE_LINE; } break; } _text->setAlignment(align); sc->addText(getLayer(), _color, _point1, _text.get()); sc->ocs_clear(); } // static void dxfEntity::registerEntity(dxfBasicEntity* entity) { _registry[entity->name()] = entity; } // static void dxfEntity::unregisterEntity(dxfBasicEntity* entity) { map<string, ref_ptr<dxfBasicEntity > >::iterator itr = _registry.find(entity->name()); if (itr != _registry.end()) { _registry.erase(itr); } } void dxfEntity::drawScene(scene* sc) { for (std::vector<ref_ptr<dxfBasicEntity > >::iterator itr = _entityList.begin(); itr != _entityList.end(); ++itr) { (*itr)->drawScene(sc); } } void dxfEntity::assign(dxfFile* dxf, codeValue& cv) { string s = cv._string; if (cv._groupCode == 66 && !(_entity && string("TABLE") == _entity->name())) { // The funny thing here. Group code 66 has been called 'obsoleted' // for a POLYLINE. But not for an INSERT. Moreover, a TABLE // can have a 66 for... an obscure bottom cell color value. // I decided to rely on the presence of the 66 code for // the POLYLINE. If you find a better alternative, // contact me, or correct this code // and post the correction to osg mailing list _seqend = true; } else if (_seqend && cv._groupCode == 0 && s == "SEQEND") { _seqend = false; // cout << "... off" << endl; } else if (_entity) { _entity->assign(dxf, cv); } }
smime.p7s
Description: S/MIME cryptographic signature
_______________________________________________ osg-submissions mailing list [email protected] http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org
