From 716835c7ad9fad8d3447289472fae434736c9d0d Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Wed, 24 Feb 2021 21:49:42 +0000 Subject: [PATCH] WS30 Line Features Adds a LINE_FEATURES STG Verb of the form LINE_FEATURES Where is a text file consisting of line features to be rendered with the appropriate definition. Each line of has the form ... Where is the line feature width in m is an integer for attributes - likely a bitmask* are floats* are the lon/lat of point N on the line feature * denotes values that may be feature-dependent. For example to indicate street lighting, kerbing etc. There must be at least two points on the line feature. --- simgear/scene/tgdb/CMakeLists.txt | 2 + simgear/scene/tgdb/ReaderWriterSTG.cxx | 36 +++- simgear/scene/tgdb/VPBTechnique.cxx | 258 ++++++++++++++++++++++++- simgear/scene/tgdb/VPBTechnique.hxx | 25 +++ 4 files changed, 309 insertions(+), 12 deletions(-) diff --git a/simgear/scene/tgdb/CMakeLists.txt b/simgear/scene/tgdb/CMakeLists.txt index 279c5336..71edf2f6 100644 --- a/simgear/scene/tgdb/CMakeLists.txt +++ b/simgear/scene/tgdb/CMakeLists.txt @@ -2,6 +2,7 @@ include (SimGearComponent) set(HEADERS GroundLightManager.hxx + LineFeatureBin.hxx ReaderWriterSPT.hxx ReaderWriterSTG.hxx SGBuildingBin.hxx @@ -28,6 +29,7 @@ set(HEADERS set(SOURCES GroundLightManager.cxx + LineFeatureBin.cxx ReaderWriterSPT.cxx ReaderWriterSTG.cxx SGBuildingBin.cxx diff --git a/simgear/scene/tgdb/ReaderWriterSTG.cxx b/simgear/scene/tgdb/ReaderWriterSTG.cxx index bdbbc220..4418305a 100644 --- a/simgear/scene/tgdb/ReaderWriterSTG.cxx +++ b/simgear/scene/tgdb/ReaderWriterSTG.cxx @@ -70,6 +70,7 @@ #define RAILWAY_DETAILED "OBJECT_RAILWAY_DETAILED" #define BUILDING_LIST "BUILDING_LIST" #define TREE_LIST "TREE_LIST" +#define LINE_FEATURE_LIST "LINE_FEATURE_LIST" namespace simgear { @@ -152,6 +153,13 @@ struct ReaderWriterSTG::_ModelBin { double _lon, _lat, _elev; }; + struct _LineFeatureList { + _LineFeatureList() { } + std::string _filename; + std::string _material; + SGBucket _bucket; + }; + class DelayLoadReadFileCallback : public OptionsReadFileCallback { @@ -279,7 +287,7 @@ struct ReaderWriterSTG::_ModelBin { SG_LOG( SG_TERRAIN, SG_ALERT, "Unable to get materials definition for buildings"); } else { for (const auto& b : _treeList) { - // Build buildings for each list of buildings + // Build trees for each list of trees SGGeod geodPos = SGGeod::fromDegM(b._lon, b._lat, 0.0); SGSharedPtr mat = matlib->find(b._material_name, geodPos); @@ -310,6 +318,19 @@ struct ReaderWriterSTG::_ModelBin { } } + if (!_lineFeatureList.empty()) { + + LineFeatureBinList lineFeatures; + + for (const auto& b : _lineFeatureList) { + // add the lineFeatures to the list + const auto path = SGPath(b._filename); + lineFeatures.push_back(LineFeatureBin(path, b._material)); + } + + VPBTechnique::addLineFeatureList(_bucket, lineFeatures); + } + return group.release(); } @@ -318,6 +339,7 @@ struct ReaderWriterSTG::_ModelBin { std::list<_Sign> _signList; std::list<_BuildingList> _buildingList; std::list<_TreeList> _treeList; + std::list<_LineFeatureList> _lineFeatureList; /// The original options to use for this bunch of models osg::ref_ptr _options; @@ -568,7 +590,7 @@ struct ReaderWriterSTG::_ModelBin { if (token == BUILDING_ROUGH || token == BUILDING_DETAILED) { opt->setMaterialName("OSM_Building"); } else if (token == ROAD_ROUGH || token == ROAD_DETAILED) { - opt->setMaterialName("OSM_Road"); + opt->setMaterialName("OSM_LineFeature"); } else if (token == RAILWAY_ROUGH || token == RAILWAY_DETAILED) { opt->setMaterialName("OSM_Railway"); } else { @@ -611,6 +633,12 @@ struct ReaderWriterSTG::_ModelBin { in >> treelist._material_name >> treelist._lon >> treelist._lat >> treelist._elev; checkInsideBucket(absoluteFileName, treelist._lon, treelist._lat); _treeListList.push_back(treelist); + } else if (token == LINE_FEATURE_LIST) { + _LineFeatureList lineFeaturelist; + lineFeaturelist._filename = path.utf8Str(); + in >> lineFeaturelist._material; + lineFeaturelist._bucket = bucketIndexFromFileName(absoluteFileName.file_base().c_str()); + _lineFeatureListList.push_back(lineFeaturelist); } else { // Check registered callback for token. Keep lock until callback completed to make sure it will not be // executed after a thread successfully executed removeSTGObjectHandler() @@ -723,7 +751,7 @@ struct ReaderWriterSTG::_ModelBin { i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat)); } - if (_objectStaticList.empty() && _signList.empty() && _buildingListList.empty() && _treeListList.empty()) { + if (_objectStaticList.empty() && _signList.empty() && _buildingListList.empty() && _treeListList.empty() && _lineFeatureListList.empty()) { // The simple case, just return the terrain group return terrainGroup.release(); } else { @@ -742,6 +770,7 @@ struct ReaderWriterSTG::_ModelBin { readFileCallback->_objectStaticList = _objectStaticList; readFileCallback->_buildingList = _buildingListList; readFileCallback->_treeList = _treeListList; + readFileCallback->_lineFeatureList = _lineFeatureListList; readFileCallback->_signList = _signList; readFileCallback->_options = options; readFileCallback->_bucket = bucket; @@ -771,6 +800,7 @@ struct ReaderWriterSTG::_ModelBin { std::list<_Sign> _signList; std::list<_BuildingList> _buildingListList; std::list<_TreeList> _treeListList; + std::list<_LineFeatureList> _lineFeatureListList; }; ReaderWriterSTG::ReaderWriterSTG() diff --git a/simgear/scene/tgdb/VPBTechnique.cxx b/simgear/scene/tgdb/VPBTechnique.cxx index 5b3c6a65..0561dfdc 100644 --- a/simgear/scene/tgdb/VPBTechnique.cxx +++ b/simgear/scene/tgdb/VPBTechnique.cxx @@ -18,6 +18,8 @@ #include +#include + #include #include @@ -26,6 +28,7 @@ #include #include +#include #include #include @@ -37,11 +40,14 @@ #include #include +#include #include #include +#include #include #include + #include "VPBTechnique.hxx" #include "TreeBin.hxx" @@ -159,6 +165,7 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) { applyColorLayers(*buffer, masterLocator); applyTrees(*buffer, masterLocator); + applyLineFeatures(*buffer, masterLocator); } } else @@ -166,6 +173,7 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) generateGeometry(*buffer, masterLocator, centerModel); applyColorLayers(*buffer, masterLocator); applyTrees(*buffer, masterLocator); + applyLineFeatures(*buffer, masterLocator); } if (buffer->_transform.valid()) buffer->_transform->setThreadSafeRefUnref(true); @@ -1563,7 +1571,7 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) { bool use_random_vegetation = false; float vegetation_density = 1.0; - int vegetation_lod_range = 6; + int vegetation_lod_level = 6; float randomness = 1.0; SGPropertyNode* propertyNode = _options->getPropertyNode().get(); @@ -1571,11 +1579,11 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) if (propertyNode) { use_random_vegetation = propertyNode->getBoolValue("/sim/rendering/random-vegetation", use_random_vegetation); vegetation_density = propertyNode->getFloatValue("/sim/rendering/vegetation-density", vegetation_density); - vegetation_lod_range = propertyNode->getIntValue("/sim/rendering/vegetation-lod", vegetation_lod_range); + vegetation_lod_level = propertyNode->getIntValue("/sim/rendering/static-lod/vegetation-lod-level", vegetation_lod_level); } // Do not generate vegetation for tiles too far away or if we explicitly don't generate vegetation - if ((!use_random_vegetation) || (_terrainTile->getTileID().level < vegetation_lod_range)) { + if ((!use_random_vegetation) || (_terrainTile->getTileID().level < vegetation_lod_level)) { return; } @@ -1599,13 +1607,10 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) delete matcache; } - // Should probably be an error if we can't find a material, but we - // return for now. - - if (!mat) - { + if (!mat) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to find EvergreenForestmaterial at " << loc); return; - } + } TreeBin* bin = new TreeBin(); bin->texture = mat->get_tree_texture(); @@ -1742,6 +1747,224 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) buffer._transform->addChild(trees); } +void VPBTechnique::applyLineFeatures(BufferData& buffer, Locator* masterLocator) +{ + int roads_lod_range = 6; + + SGPropertyNode* propertyNode = _options->getPropertyNode().get(); + + if (propertyNode) { + roads_lod_range = propertyNode->getIntValue("/sim/rendering/static-lod/roads-lod-range", roads_lod_range); + } + + // Do not generate vegetation for tiles too far away + //if (_terrainTile->getTileID().level < roads_lod_range) { + if (_terrainTile->getTileID().level < roads_lod_range) { + return; + } + + SGMaterialLibPtr matlib = _options->getMaterialLib(); + SGMaterial* mat = 0; + osg::Vec3d world; + SGGeod loc; + + if (! matlib) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to get materials library to generate roads"); + return; + } + + // Determine the center of the tile, sadly non-trivial. + osg::Vec3d tileloc = computeCenter(buffer, masterLocator); + masterLocator->convertLocalToModel(tileloc, world); + const SGVec3d world2 = SGVec3d(world.x(), world.y(), world.z()); + loc = SGGeod::fromCart(world2); + + const osg::Matrixd R_vert = makeZUpFrameRelative(loc); + + // Get all appropriate roads. We assume that the VPB terrain tile is smaller than a Bucket size. + SGBucket bucket = SGBucket(loc); + auto roads = std::find_if(_lineFeatureLists.begin(), _lineFeatureLists.end(), [bucket](BucketLineFeatureBinList b){return (b.first == bucket);}); + + if (roads == _lineFeatureLists.end()) return; + + SGMaterialCache* matcache = _options->getMaterialLib()->generateMatCache(loc, _options); + + for (; roads != _lineFeatureLists.end(); ++roads) { + LineFeatureBinList roadBins = roads->second; + + for (auto rb = roadBins.begin(); rb != roadBins.end(); ++rb) + { + mat = matcache->find(rb->getMaterial()); + + if (!mat) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to find material " << rb->getMaterial() << " at " << loc << " " << bucket); + continue; + } + + unsigned int xsize = mat->get_xsize(); + unsigned int ysize = mat->get_ysize(); + + // Generate a geometry for this set of roads. + osg::Vec3Array* v = new osg::Vec3Array; + osg::Vec2Array* t = new osg::Vec2Array; + osg::Vec3Array* n = new osg::Vec3Array; + osg::Vec4Array* c = new osg::Vec4Array; + + auto lineFeatures = rb->getLineFeatures(); + + for (auto r = lineFeatures.begin(); r != lineFeatures.end(); ++r) { + generateLineFeature(buffer, masterLocator, *r, world, R_vert, v, t, n, xsize, ysize); + } + + if (v->size() == 0) continue; + + c->push_back(osg::Vec4(1.0,1.0,1.0,1.0)); + + osg::ref_ptr geometry = new osg::Geometry; + geometry->setVertexArray(v); + geometry->setTexCoordArray(0, t, osg::Array::BIND_PER_VERTEX); + geometry->setTexCoordArray(1, t, osg::Array::BIND_PER_VERTEX); + geometry->setNormalArray(n, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(c, osg::Array::BIND_OVERALL); + geometry->setUseDisplayList( false ); + geometry->setUseVertexBufferObjects( true ); + geometry->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLES, 0, v->size()) ); + + EffectGeode* geode = new EffectGeode; + geode->addDrawable(geometry); + + geode->setMaterial(mat); + geode->setEffect(mat->get_one_effect(0)); + buffer._transform->addChild(geode); + } + } +} + +void VPBTechnique::generateLineFeature(BufferData& buffer, Locator* masterLocator, LineFeatureBin::LineFeature road, osg::Vec3d modelCenter, const osg::Matrixd R_vert, osg::Vec3Array* v, osg::Vec2Array* t, osg::Vec3Array* n, unsigned int xsize, unsigned int ysize) +{ + if (road._nodes.size() < 2) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Coding error - LineFeatureBin::LineFeature with fewer than two nodes"); + return; + } + + SGVec3d tmp; + osg::Vec3d ma, mb; + std::list roadPoints; + + auto road_iter = road._nodes.begin(); + + SGGeodesy::SGGeodToCart(SGGeod(*road_iter), tmp); + ma = toOsg(tmp) - modelCenter; + + osg::Vec3d intersect = getMeshIntersection(buffer, masterLocator, ma); + + // If we've got an intersection, add it to the list + if (intersect != ma) roadPoints.push_back(intersect); + + road_iter++; + + for (; road_iter != road._nodes.end(); road_iter++) { + SGGeodesy::SGGeodToCart(SGGeod(*road_iter), tmp); + mb = toOsg(tmp) - modelCenter; + + intersect = getMeshIntersection(buffer, masterLocator, mb); + if (intersect != mb) roadPoints.push_back(intersect); + + osgSim::ElevationSlice es; + es.setStartPoint(ma); + es.setEndPoint(mb); + es.computeIntersections(buffer._geometry); + + auto esl = es.getIntersections(); + for(auto eslitr = esl.begin(); eslitr != esl.end(); ++eslitr) + { + roadPoints.push_back(*eslitr); + } + + // Now traverse the next segment + ma = mb; + } + + // We now have a series of points following the topography of the elevation mesh. + float xtex, ytex; + osg::Vec3d local, origin, top, start, end; + osg::Vec3d up = osg::Matrixd::inverse(R_vert) * osg::Vec3d(0,0,1); + + auto iter = roadPoints.begin(); + start = *iter + up; + iter++; + + float ytex_base = 0.0f; + + for (; iter != roadPoints.end(); iter++) { + end = *iter + up; + + // Find a spanwise vector + osg::Vec3d spanwise = ((end-start) ^ up); + spanwise.normalize(); + + // Define the road extents + const osg::Vec3d a = start - spanwise * road._width * 0.5; + const osg::Vec3d b = start + spanwise * road._width * 0.5; + const osg::Vec3d c = end - spanwise * road._width * 0.5; + const osg::Vec3d d = end + spanwise * road._width * 0.5; + + // Determine the x and y texture coordinates for the edges + xtex = 0.5 * road._width / xsize; + ytex = ytex_base + (end-start).length() / ysize; + + // Now generate two triangles, . + v->push_back(a); + v->push_back(b); + v->push_back(c); + + t->push_back(osg::Vec2d(0, ytex_base)); + t->push_back(osg::Vec2d(xtex, ytex_base)); + t->push_back(osg::Vec2d(0, ytex)); + + v->push_back(b); + v->push_back(d); + v->push_back(c); + + t->push_back(osg::Vec2d(xtex, ytex_base)); + t->push_back(osg::Vec2d(xtex, ytex)); + t->push_back(osg::Vec2d(0, ytex)); + + // Normal is straight from the quad + osg::Vec3d normal = (end-start)^spanwise; + normal.normalize(); + for (unsigned int i = 0; i < 6; i++) n->push_back(normal); + + start = end; + ytex_base = ytex; + } +} + +// Find the intersection of a given SGGeod with the terrain mesh +osg::Vec3d VPBTechnique::getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt) +{ + SGVec3d tmp; + osg::Vec3d local, origin, top; + + // Get a vertical line segment. + masterLocator->convertModelToLocal(pt, local); + masterLocator->convertLocalToModel(osg::Vec3d(local.x(), local.y(), -1000.0), origin); + masterLocator->convertLocalToModel(osg::Vec3d(local.x(), local.y(), 100000.0), top); + + osg::ref_ptr intersector; + intersector = new osgUtil::LineSegmentIntersector(origin, top); + osgUtil::IntersectionVisitor visitor(intersector.get()); + buffer._geometry->accept(visitor); + + if (intersector->containsIntersections()) { + // We have an intersection with the terrain model, so return it + return intersector->getFirstIntersection().getWorldIntersectPoint(); + } else { + // No intersection. Likely this point is outside our geometry. So just return the cartesian element. + return pt; + } +} + void VPBTechnique::update(osg::NodeVisitor& nv) { if (_terrainTile) _terrainTile->osg::Group::traverse(nv); @@ -1876,3 +2099,20 @@ osg::Vec3d VPBTechnique::checkAgainstElevationConstraints(osg::Vec3d origin, osg } } +void VPBTechnique::addLineFeatureList(SGBucket bucket, LineFeatureBinList roadList) +{ + const std::lock_guard lock(VPBTechnique::_lineFeatureLists_mutex); // Lock the _lineFeatureLists for this scope + _lineFeatureLists.push_back(std::pair(bucket, roadList)); +} + +void VPBTechnique::unloadLineFeatures(SGBucket bucket) +{ + SG_LOG(SG_TERRAIN, SG_ALERT, "Erasing all roads with entry " << bucket); + const std::lock_guard lock(VPBTechnique::_lineFeatureLists_mutex); // Lock the _lineFeatureLists for this scope + // C++ 20... + //std::erase_if(_lineFeatureLists, [bucket](BucketLineFeatureBinList p) { return p.first == bucket; } ); +} + + + + diff --git a/simgear/scene/tgdb/VPBTechnique.hxx b/simgear/scene/tgdb/VPBTechnique.hxx index 101a5bf5..f77015ed 100644 --- a/simgear/scene/tgdb/VPBTechnique.hxx +++ b/simgear/scene/tgdb/VPBTechnique.hxx @@ -28,8 +28,10 @@ #include #include +#include #include #include +#include using namespace osgTerrain; @@ -87,10 +89,15 @@ class VPBTechnique : public TerrainTechnique * for all graphics contexts. */ virtual void releaseGLObjects(osg::State* = 0) const; + // Elevation constraints ensure that the terrain mesh is placed underneath objects such as airports static void addElevationConstraint(osg::ref_ptr constraint, osg::Group* terrain); static void removeElevationConstraint(osg::ref_ptr constraint); static osg::Vec3d checkAgainstElevationConstraints(osg::Vec3d origin, osg::Vec3d vertex, float vertex_gap); + // LineFeatures are draped over the underlying mesh. + static void addLineFeatureList(SGBucket bucket, LineFeatureBinList roadList); + static void unloadLineFeatures(SGBucket bucket); + protected: virtual ~VPBTechnique(); @@ -121,6 +128,18 @@ class VPBTechnique : public TerrainTechnique virtual void applyTrees(BufferData& buffer, Locator* masterLocator); + virtual void applyLineFeatures(BufferData& buffer, Locator* masterLocator); + virtual void generateLineFeature(BufferData& buffer, Locator* + masterLocator, LineFeatureBin::LineFeature road, + osg::Vec3d modelCenter, + const osg::Matrixd R_vert, + osg::Vec3Array* v, + osg::Vec2Array* t, + osg::Vec3Array* n, + unsigned int xsize, + unsigned int ysize); + virtual osg::Vec3d getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt); + OpenThreads::Mutex _writeBufferMutex; osg::ref_ptr _currentBufferData; osg::ref_ptr _newBufferData; @@ -138,6 +157,12 @@ class VPBTechnique : public TerrainTechnique inline static osg::ref_ptr _constraintGroup = new osg::Group();; inline static std::mutex _constraint_mutex; // protects the _constraintGroup; static osg::Vec2d* _randomOffsets; + + typedef std::pair BucketLineFeatureBinList; + + inline static std::list _lineFeatureLists; + inline static std::mutex _lineFeatureLists_mutex; // protects the _roadLists; + }; }