diff --git a/simgear/scene/tgdb/AreaFeatureBin.cxx b/simgear/scene/tgdb/AreaFeatureBin.cxx new file mode 100644 index 00000000..e9b18b50 --- /dev/null +++ b/simgear/scene/tgdb/AreaFeatureBin.cxx @@ -0,0 +1,109 @@ +/* -*-c++-*- + * + * Copyright (C) 2008 Stuart Buchanan + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include +#include +#include +#include + +#include "AreaFeatureBin.hxx" + +using namespace osg; + +namespace simgear +{ + +AreaFeatureBin::AreaFeatureBin(const SGPath& absoluteFileName, const std::string material) : + _material(material) +{ + if (!absoluteFileName.exists()) { + SG_LOG(SG_TERRAIN, SG_ALERT, "AreaFeature list file " << absoluteFileName << " does not exist."); + return; + } + + sg_gzifstream stream(absoluteFileName); + if (!stream.is_open()) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open " << absoluteFileName); + return; + } + + while (!stream.eof()) { + // read a line. Each line defines a single line feature, consisting of a width followed by a series of lon/lat positions. + // Or a comment, starting with # + std::string line; + std::getline(stream, line); + + // strip comments + std::string::size_type hash_pos = line.find('#'); + if (hash_pos != std::string::npos) + line.resize(hash_pos); + + // and process further + std::stringstream in(line); + + // Area format is Area A B C D lon0 lat0 lon1 lat1 lon2 lat2 lon3 lat4.... + // where: + // Area is the area of the feature in m^2 + // A, B, C, D are generic attributes. Their interpretation may vary by feature type + // lon[n], lat[n] are pairs of lon/lat defining straight road segments + int attributes = 0; + float area=0, a=0, b=0, c=0, d=0; + std::list nodes; + + in >> area >> attributes >> a >> b >> c >> d; + + if (in.bad() || in.fail()) { + SG_LOG(SG_TERRAIN, SG_WARN, "Error parsing area entry in: " << absoluteFileName << " line: \"" << line << "\""); + continue; + } + + while (true) { + double lon = 0.0f, lat=0.0f; + in >> lon >> lat; + + if (in.bad() || in.fail()) { + break; + } + + SGVec3d tmp; + SGGeodesy::SGGeodToCart(SGGeod::fromDeg(lon, lat), tmp); + nodes.push_back(toOsg(tmp)); + } + + if (nodes.size() > 2) { + insert(AreaFeature(nodes, area, attributes, a, b, c, d)); + } else { + SG_LOG(SG_TERRAIN, SG_WARN, "AreaFeature definition with fewer than three lon/lat nodes : " << absoluteFileName << " line: \"" << line << "\""); + } + } + + stream.close(); +}; + + +} diff --git a/simgear/scene/tgdb/AreaFeatureBin.hxx b/simgear/scene/tgdb/AreaFeatureBin.hxx new file mode 100644 index 00000000..3d0deb6a --- /dev/null +++ b/simgear/scene/tgdb/AreaFeatureBin.hxx @@ -0,0 +1,80 @@ +/* -*-c++-*- + * + * Copyright (C) 2021 Stuart Buchanan + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ + +#ifndef AREA_BIN_HXX +#define AREA_BIN_HXX + +#include +#include + +#include +#include +#include + +#include + +namespace simgear +{ +class AreaFeatureBin { +public: + AreaFeatureBin() = default; + AreaFeatureBin(const SGPath& absoluteFileName, const std::string material); + + ~AreaFeatureBin() = default; + + struct AreaFeature { + const std::list _nodes; + const float _area; + const int _attributes; + const float _a; + const float _b; + const float _c; + const float _d; + AreaFeature(const std::list nodes, const float area, const int attributes, const float a, const float b, const float c, const float d) : + _nodes(nodes), _area(area), _attributes(attributes), _a(a), _b(b), _c(c), _d(d) + { + } + }; + + typedef std::list AreaFeatureList; + + void insert(const AreaFeature& t) { + _areaFeatureList.push_back(t); + } + + const AreaFeatureList getAreaFeatures() const { + return _areaFeatureList; + } + + const std::string getMaterial() const { + return _material; + } + +private: + AreaFeatureList _areaFeatureList; + const std::string _material; +}; + +typedef std::list AreaFeatureBinList; + +} + +#endif diff --git a/simgear/scene/tgdb/CMakeLists.txt b/simgear/scene/tgdb/CMakeLists.txt index 42ca6548..ec586332 100644 --- a/simgear/scene/tgdb/CMakeLists.txt +++ b/simgear/scene/tgdb/CMakeLists.txt @@ -2,6 +2,7 @@ include (SimGearComponent) set(HEADERS GroundLightManager.hxx + AreaFeatureBin.hxx LineFeatureBin.hxx ReaderWriterSPT.hxx ReaderWriterSTG.hxx @@ -30,6 +31,7 @@ set(HEADERS set(SOURCES GroundLightManager.cxx + AreaFeatureBin.cxx LineFeatureBin.cxx ReaderWriterSPT.cxx ReaderWriterSTG.cxx diff --git a/simgear/scene/tgdb/LineFeatureBin.cxx b/simgear/scene/tgdb/LineFeatureBin.cxx index 769bb49b..edfd361e 100644 --- a/simgear/scene/tgdb/LineFeatureBin.cxx +++ b/simgear/scene/tgdb/LineFeatureBin.cxx @@ -66,9 +66,10 @@ LineFeatureBin::LineFeatureBin(const SGPath& absoluteFileName, const std::string // and process further std::stringstream in(line); - // Line format is W A B C D lon0 lat0 lon1 lat1 lon2 lat2 lon3 lat4.... + // Line format is W attr A B C D lon0 lat0 lon1 lat1 lon2 lat2 lon3 lat4.... // where: // W is the width in m + // attr is an integer attribute set // A, B, C, D are generic attributes. Their interpretation may vary by feature type // lon[n], lat[n] are pairs of lon/lat defining straight road segments float w = 0.0f; diff --git a/simgear/scene/tgdb/ReaderWriterSTG.cxx b/simgear/scene/tgdb/ReaderWriterSTG.cxx index ca6ba984..aad3f372 100644 --- a/simgear/scene/tgdb/ReaderWriterSTG.cxx +++ b/simgear/scene/tgdb/ReaderWriterSTG.cxx @@ -71,6 +71,7 @@ #define BUILDING_LIST "BUILDING_LIST" #define TREE_LIST "TREE_LIST" #define LINE_FEATURE_LIST "LINE_FEATURE_LIST" +#define AREA_FEATURE_LIST "AREA_FEATURE_LIST" namespace simgear { @@ -159,7 +160,12 @@ struct ReaderWriterSTG::_ModelBin { std::string _material; SGBucket _bucket; }; - + struct _AreaFeatureList { + _AreaFeatureList() { } + std::string _filename; + std::string _material; + SGBucket _bucket; + }; class DelayLoadReadFileCallback : public OptionsReadFileCallback { @@ -334,6 +340,19 @@ struct ReaderWriterSTG::_ModelBin { VPBTechnique::addLineFeatureList(_bucket, lineFeatures, _terrainNode); } + if (!_areaFeatureList.empty()) { + + AreaFeatureBinList areaFeatures; + + for (const auto& b : _areaFeatureList) { + // add the lineFeatures to the list + const auto path = SGPath(b._filename); + areaFeatures.push_back(AreaFeatureBin(path, b._material)); + } + + VPBTechnique::addAreaFeatureList(_bucket, areaFeatures, _terrainNode); + } + return group.release(); } @@ -343,6 +362,7 @@ struct ReaderWriterSTG::_ModelBin { std::list<_BuildingList> _buildingList; std::list<_TreeList> _treeList; std::list<_LineFeatureList> _lineFeatureList; + std::list<_AreaFeatureList> _areaFeatureList; osg::ref_ptr _terrainNode; /// The original options to use for this bunch of models @@ -646,6 +666,12 @@ struct ReaderWriterSTG::_ModelBin { in >> lineFeaturelist._material; lineFeaturelist._bucket = bucketIndexFromFileName(absoluteFileName.file_base().c_str()); _lineFeatureListList.push_back(lineFeaturelist); + } else if (token == AREA_FEATURE_LIST) { + _AreaFeatureList areaFeaturelist; + areaFeaturelist._filename = path.utf8Str(); + in >> areaFeaturelist._material; + areaFeaturelist._bucket = bucketIndexFromFileName(absoluteFileName.file_base().c_str()); + _areaFeatureListList.push_back(areaFeaturelist); } 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() @@ -762,7 +788,7 @@ struct ReaderWriterSTG::_ModelBin { i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat)); } - if (_objectStaticList.empty() && _signList.empty() && _buildingListList.empty() && _treeListList.empty() && _lineFeatureListList.empty()) { + if (_objectStaticList.empty() && _signList.empty() && _buildingListList.empty() && _treeListList.empty() && _lineFeatureListList.empty() && _areaFeatureListList.empty()) { // The simple case, just return the terrain group return terrainGroup.release(); } else { @@ -787,6 +813,7 @@ struct ReaderWriterSTG::_ModelBin { if (vpb_active && vpb_node) { readFileCallback->_lineFeatureList = _lineFeatureListList; + readFileCallback->_areaFeatureList = _areaFeatureListList; readFileCallback->_terrainNode = vpb_node; } @@ -817,6 +844,7 @@ struct ReaderWriterSTG::_ModelBin { std::list<_BuildingList> _buildingListList; std::list<_TreeList> _treeListList; std::list<_LineFeatureList> _lineFeatureListList; + std::list<_AreaFeatureList> _areaFeatureListList; }; ReaderWriterSTG::ReaderWriterSTG() diff --git a/simgear/scene/tgdb/VPBTechnique.cxx b/simgear/scene/tgdb/VPBTechnique.cxx index cb4fc90f..2ba6f7c0 100644 --- a/simgear/scene/tgdb/VPBTechnique.cxx +++ b/simgear/scene/tgdb/VPBTechnique.cxx @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -167,6 +168,7 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) applyColorLayers(*buffer, masterLocator); applyTrees(*buffer, masterLocator); applyLineFeatures(*buffer, masterLocator); + applyAreaFeatures(*buffer, masterLocator); } } else @@ -175,6 +177,7 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) applyColorLayers(*buffer, masterLocator); applyTrees(*buffer, masterLocator); applyLineFeatures(*buffer, masterLocator); + applyAreaFeatures(*buffer, masterLocator); } if (buffer->_transform.valid()) buffer->_transform->setThreadSafeRefUnref(true); @@ -1456,10 +1459,6 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator) const int land_class = int(round(tc.x() * 255.0)); mat = matcache->find(land_class); - if ((land_class == 26) || (land_class == 27)) { - SG_LOG(SG_TERRAIN, SG_ALERT, "Placing trees for " << land_class << " " << mat->get_one_texture(0,0) << " " << mat->get_names()[0]); - } - if (!mat) { //SG_LOG(SG_TERRAIN, SG_ALERT, "Failed to find material for landclass " << land_class << " (" << tc.r() << ")"); continue; @@ -1774,6 +1773,162 @@ void VPBTechnique::generateLineFeature(BufferData& buffer, Locator* masterLocato } } +void VPBTechnique::applyAreaFeatures(BufferData& buffer, Locator* masterLocator) +{ + unsigned int area_features_lod_range = 6; + float minArea = 1000.0; + + const unsigned int tileLevel = _terrainTile->getTileID().level; + const SGPropertyNode* propertyNode = _options->getPropertyNode().get(); + + if (propertyNode) { + const SGPropertyNode* static_lod = propertyNode->getNode("/sim/rendering/static-lod"); + area_features_lod_range = static_lod->getIntValue("area-features-lod-level", area_features_lod_range); + if (static_lod->getChildren("lod-level").size() > tileLevel) { + minArea = static_lod->getChildren("lod-level")[tileLevel]->getFloatValue("area-features-min-width", minArea); + minArea *= minArea; + } + } + + if (tileLevel < area_features_lod_range) { + // Do not generate areas for tiles too far away + return; + } + + const SGMaterialLibPtr matlib = _options->getMaterialLib(); + SGMaterial* mat = 0; + + if (! matlib) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to get materials library to generate areas"); + return; + } + + + // Get all appropriate areas. We assume that the VPB terrain tile is smaller than a Bucket size. + const osg::Vec3d world = buffer._transform->getMatrix().getTrans(); + const SGGeod loc = SGGeod::fromCart(toSG(world)); + const SGBucket bucket = SGBucket(loc); + auto areas = std::find_if(_areaFeatureLists.begin(), _areaFeatureLists.end(), [bucket](BucketAreaFeatureBinList b){return (b.first == bucket);}); + + if (areas == _areaFeatureLists.end()) return; + + SGMaterialCache* matcache = _options->getMaterialLib()->generateMatCache(loc, _options); + + for (; areas != _areaFeatureLists.end(); ++areas) { + const AreaFeatureBinList areaBins = areas->second; + + for (auto rb = areaBins.begin(); rb != areaBins.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 areas. + osg::Vec3Array* v = new osg::Vec3Array; + osg::Vec2Array* t = new osg::Vec2Array; + osg::Vec3Array* n = new osg::Vec3Array; + osg::Vec4Array* c = new osg::Vec4Array; + + 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 ); + + auto areaFeatures = rb->getAreaFeatures(); + + for (auto r = areaFeatures.begin(); r != areaFeatures.end(); ++r) { + if (r->_area > minArea) generateAreaFeature(buffer, masterLocator, *r, world, geometry, v, t, n, xsize, ysize); + } + + if (v->size() == 0) continue; + c->push_back(osg::Vec4(1.0,1.0,1.0,1.0)); + + EffectGeode* geode = new EffectGeode; + geode->addDrawable(geometry); + + geode->setMaterial(mat); + geode->setEffect(mat->get_one_effect(0)); + geode->setNodeMask( ~(simgear::CASTSHADOW_BIT | simgear::MODELLIGHT_BIT) ); + buffer._transform->addChild(geode); + } + } +} + +void VPBTechnique::generateAreaFeature(BufferData& buffer, Locator* masterLocator, AreaFeatureBin::AreaFeature area, osg::Vec3d modelCenter, osg::Geometry* geometry, osg::Vec3Array* v, osg::Vec2Array* t, osg::Vec3Array* n, unsigned int xsize, unsigned int ysize) +{ + if (area._nodes.size() < 3) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Coding error - AreaFeatureBin::LineFeature with fewer than three nodes"); + return; + } + + osg::Vec3d ma; + + // We're in Earth-centered coordinates, so "up" is simply directly away from (0,0,0) + osg::Vec3d up = modelCenter; + up.normalize(); + + osg::ref_ptr tessellator = new osgUtil::Tessellator; + tessellator->setBoundaryOnly(false); + tessellator->setTessellationNormal(up); + tessellator->beginTessellation(); + tessellator->beginContour(); + + // Build up the tesselator while also determining the correct elevation for the feature. + double elev = 0; + unsigned int elev_count = 0; + + auto area_iter = area._nodes.begin(); + osg::Vec3d pt = *area_iter - modelCenter; + ma = getMeshIntersection(buffer, masterLocator, pt, up); + + // Only build this area if the first vertex is on the mesh. This ensures that the + // area is only generated once, no matter how many tiles it spans. + if (ma == pt) return; + + for (; area_iter != area._nodes.end(); area_iter++) { + pt = *area_iter - modelCenter; + ma = getMeshIntersection(buffer, masterLocator, pt, up); + if (ma !=pt) { + elev += up*ma; + elev_count++; + } + tessellator->addVertex(new osg::Vec3f(*area_iter - modelCenter)); + } + + tessellator->endContour(); + tessellator->endTessellation(); + + auto primList = tessellator->getPrimList(); + if (primList.size() == 0) return; + + unsigned int idx = 0; + + auto primItr = primList.begin(); + for (; primItr < primList.end(); ++ primItr) { + auto vertices = (*primItr)->_vertices; + std::for_each( vertices.begin(), + vertices.end(), + [v, t, n, up, elev, elev_count](auto vtx) { + v->push_back(*vtx + up * (elev / elev_count)); + t->push_back(osg::Vec2f(vtx->x(), vtx->y())); + n->push_back(up); + } + ); + geometry->addPrimitiveSet(new osg::DrawArrays((*primItr)->_mode, idx, vertices.size())); + idx += vertices.size(); + } +} + // Find the intersection of a given SGGeod with the terrain mesh osg::Vec3d VPBTechnique::getMeshIntersection(BufferData& buffer, Locator* masterLocator, osg::Vec3d pt, osg::Vec3d up) { @@ -1961,12 +2116,42 @@ void VPBTechnique::addLineFeatureList(SGBucket bucket, LineFeatureBinList roadLi terrainNode->accept(ftv); } -void VPBTechnique::unloadLineFeatures(SGBucket bucket) +void VPBTechnique::addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaList, osg::ref_ptr terrainNode) +{ + if (areaList.empty()) return; + + // Block to mutex the List alone + { + const std::lock_guard lock(VPBTechnique::_areaFeatureLists_mutex); // Lock the _lineFeatureLists for this scope + _areaFeatureLists.push_back(std::pair(bucket, areaList)); + } + + // We need to trigger a re-build of the appropriate Terrain tile, so create a pretend node and run the TerrainVisitor to + // "dirty" the TerrainTile that it intersects with. + osg::ref_ptr n = new osg::Node(); + + //SGVec3d coord1, coord2; + //SGGeodesy::SGGeodToCart(SGGeod::fromDegM(bucket.get_center_lon() -0.5*bucket.get_width(), bucket.get_center_lat() -0.5*bucket.get_height(), 0.0), coord1); + //SGGeodesy::SGGeodToCart(SGGeod::fromDegM(bucket.get_center_lon() +0.5*bucket.get_width(), bucket.get_center_lat() +0.5*bucket.get_height(), 0.0), coord2); + //osg::BoundingBox bbox = osg::BoundingBox(toOsg(coord1), toOsg(coord2)); + //n->setInitialBound(bbox); + + SGVec3d coord; + SGGeodesy::SGGeodToCart(SGGeod::fromDegM(bucket.get_center_lon(), bucket.get_center_lat(), 0.0), coord); + n->setInitialBound(osg::BoundingSphere(toOsg(coord), max(bucket.get_width_m(), bucket.get_height_m()))); + + SG_LOG(SG_TERRAIN, SG_DEBUG, "Adding line features to " << bucket.gen_index_str()); + TerrainVisitor ftv(n, TerrainTile::ALL_DIRTY, 0); + terrainNode->accept(ftv); +} + +void VPBTechnique::unloadFeatures(SGBucket bucket) { SG_LOG(SG_TERRAIN, SG_DEBUG, "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; } ); + //std::erase_if(_lineFeatureLists, [bucket](BucketAreaFeatureBinList p) { return p.first == bucket; } ); } diff --git a/simgear/scene/tgdb/VPBTechnique.hxx b/simgear/scene/tgdb/VPBTechnique.hxx index 4c382f37..9399e673 100644 --- a/simgear/scene/tgdb/VPBTechnique.hxx +++ b/simgear/scene/tgdb/VPBTechnique.hxx @@ -31,6 +31,7 @@ #include #include #include +#include #include using namespace osgTerrain; @@ -94,9 +95,10 @@ class VPBTechnique : public TerrainTechnique 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. + // LineFeatures and AreaFeaturesare draped over the underlying mesh. static void addLineFeatureList(SGBucket bucket, LineFeatureBinList roadList, osg::ref_ptr terrainNode); - static void unloadLineFeatures(SGBucket bucket); + static void addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaList, osg::ref_ptr terrainNode); + static void unloadFeatures(SGBucket bucket); protected: @@ -142,6 +144,18 @@ class VPBTechnique : public TerrainTechnique osg::Vec3Array* n, unsigned int xsize, unsigned int ysize); + + virtual void applyAreaFeatures(BufferData& buffer, Locator* masterLocator); + virtual void generateAreaFeature(BufferData& buffer, Locator* + masterLocator, AreaFeatureBin::AreaFeature area, + osg::Vec3d modelCenter, + osg::Geometry* geometry, + 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, osg::Vec3d up); OpenThreads::Mutex _writeBufferMutex; @@ -161,9 +175,13 @@ class VPBTechnique : public TerrainTechnique static osg::Vec2d* _randomOffsets; typedef std::pair BucketLineFeatureBinList; + typedef std::pair BucketAreaFeatureBinList; inline static std::list _lineFeatureLists; - inline static std::mutex _lineFeatureLists_mutex; // protects the _roadLists; + inline static std::mutex _lineFeatureLists_mutex; // protects the _lineFeatureLists; + + inline static std::list _areaFeatureLists; + inline static std::mutex _areaFeatureLists_mutex; // protects the _areaFeatureLists; };