WS30: Don't defer reading of line features

Previously, reading LINE_FEATURE_LIST/AREA_FEATURE_LIST/COASTLINE_LIST
files were not read with the .stg file, but deferred into the
DelayLoadReadFileCallback.  When the the Callback was executed, the
files were read and appropriate terrain tiles dirtied for regeneration.

However, re-generating dirtied tiles caused significant frame pauses
and was disabled in commit
deb802f74a

This meant that the Line/Area/Coastline features would only be rendered
when the next LoD tile was generated.  Which created a race condition
on startup for the tile the user starts on, between the
DelayLoadReadFileCallback and the tile itself.

This commit reads these files at the same time as the initial terrain
so that the VPB has all the data required when the tile is initially
rendered.  The downside is that line data is read for every tile,
even if it is far away.  As the line data is stored as a series of
lat/lon floats, this is assumed to be OK.

An alternative would be to re-instate the Visitor, and instead
of dirtying existing terrain, generate the roads, and flag them to
the UpdateVisitor to add to the terrain node.
This commit is contained in:
Stuart Buchanan 2021-06-06 22:10:52 +01:00
parent 16778e5c02
commit 1a6bf3c284
3 changed files with 49 additions and 152 deletions

View File

@ -334,46 +334,6 @@ 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, _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);
}
if (!_coastFeatureList.empty()) {
CoastlineBinList coastFeatures;
for (const auto& b : _coastFeatureList) {
// add the lineFeatures to the list
const auto path = SGPath(b._filename);
coastFeatures.push_back(CoastlineBin(path));
}
VPBTechnique::addCoastlineList(_bucket, coastFeatures, _terrainNode);
}
return group.release();
}
@ -382,10 +342,6 @@ struct ReaderWriterSTG::_ModelBin {
std::list<_Sign> _signList;
std::list<_BuildingList> _buildingList;
std::list<_TreeList> _treeList;
std::list<_LineFeatureList> _lineFeatureList;
std::list<_AreaFeatureList> _areaFeatureList;
std::list<_CoastlineList> _coastFeatureList;
osg::ref_ptr<osg::Node> _terrainNode;
/// The original options to use for this bunch of models
osg::ref_ptr<SGReaderWriterOptions> _options;
@ -745,6 +701,47 @@ struct ReaderWriterSTG::_ModelBin {
bool vpb_active = SGSceneFeatures::instance()->getVPBActive();
if (vpb_active) {
// Load any line area or coastline features, which we will need before we generate the tile.
if (!_lineFeatureListList.empty()) {
LineFeatureBinList lineFeatures;
for (const auto& b : _lineFeatureListList) {
// add the lineFeatures to the list
const auto path = SGPath(b._filename);
lineFeatures.push_back(LineFeatureBin(path, b._material));
}
VPBTechnique::addLineFeatureList(bucket, lineFeatures);
}
if (!_areaFeatureListList.empty()) {
AreaFeatureBinList areaFeatures;
for (const auto& b : _areaFeatureListList) {
// add the lineFeatures to the list
const auto path = SGPath(b._filename);
areaFeatures.push_back(AreaFeatureBin(path, b._material));
}
VPBTechnique::addAreaFeatureList(bucket, areaFeatures);
}
if (!_coastFeatureListList.empty()) {
CoastlineBinList coastFeatures;
for (const auto& b : _coastFeatureListList) {
// add the lineFeatures to the list
const auto path = SGPath(b._filename);
coastFeatures.push_back(CoastlineBin(path));
}
VPBTechnique::addCoastlineList(bucket, coastFeatures);
}
std::string filename = "vpb/" + bucket.gen_vpb_base() + ".osgb";
if (tile_map.count(filename) == 0) {
vpb_node = osgDB::readRefNodeFile(filename, options);
@ -846,13 +843,6 @@ struct ReaderWriterSTG::_ModelBin {
readFileCallback->_options = options;
readFileCallback->_bucket = bucket;
if (vpb_active && vpb_node) {
readFileCallback->_lineFeatureList = _lineFeatureListList;
readFileCallback->_areaFeatureList = _areaFeatureListList;
readFileCallback->_coastFeatureList = _coastFeatureListList;
readFileCallback->_terrainNode = vpb_node;
}
osg::ref_ptr<osgDB::Options> callbackOptions = new osgDB::Options;
callbackOptions->setReadFileCallback(readFileCallback.get());
pagedLOD->setDatabaseOptions(callbackOptions.get());

View File

@ -137,13 +137,12 @@ void VPBTechnique::setFilterMatrixAs(FilterType filterType)
void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded)
{
if (!_terrainTile) return;
if (dirtyMask==0) return;
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_writeBufferMutex);
osg::ref_ptr<TerrainTile> tile = _terrainTile;
if (dirtyMask==0) return;
osgTerrain::TileID tileID = tile->getTileID();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Init of tile " << tileID.x << "," << tileID.y << " level " << tileID.level << " " << dirtyMask);
@ -2233,43 +2232,6 @@ void VPBTechnique::releaseGLObjects(osg::State* state) const
if (_newBufferData.valid() && _newBufferData->_transform.valid()) _newBufferData->_transform->releaseGLObjects(state);
}
// Simple vistor to check for any underlying terrain meshes that intersect with a given constraint therefore may need to be modified
// (e.g elevation lowered to ensure the terrain doesn't poke through an airport mesh, or line features generated)
class TerrainVisitor : public osg::NodeVisitor {
public:
osg::ref_ptr<osg::Node> _constraint; // Object describing the volume to be modified.
int _dirtyMask; // Dirty mask to apply.
int _minLevel; // Minimum LoD level to modify.
TerrainVisitor( osg::ref_ptr<osg::Node> node, int mask, int minLevel) :
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
_constraint(node),
_dirtyMask(mask),
_minLevel(minLevel)
{ }
virtual ~TerrainVisitor()
{ }
void apply(osg::Node& node)
{
osgTerrain::TerrainTile* tile = dynamic_cast<osgTerrain::TerrainTile*>(&node);
if (tile) {
// Determine if the constraint should affect this tile.
const int level = tile->getTileID().level;
const osg::BoundingSphere tileBB = tile->getBound();
if ((level >= _minLevel) && tileBB.intersects(_constraint->getBound())) {
// Dirty any existing terrain tiles containing this constraint, which will force regeneration
osgTerrain::TileID tileID = tile->getTileID();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Setting dirty mask for tile " << tileID.x << "," << tileID.y << " level " << tileID.level << " " << _dirtyMask);
tile->setDirtyMask(_dirtyMask);
} else if (tileBB.intersects(_constraint->getBound())) {
traverse(node);
}
} else {
traverse(node);
}
}
};
// Add an osg object representing a contraint on the terrain mesh. The generated terrain mesh will not include any vertices that
// lie above the constraint model. (Note that geometry may result in edges intersecting the constraint model in cases where there
// are significantly higher vertices that lie just outside the constraint model.
@ -2277,9 +2239,6 @@ void VPBTechnique::addElevationConstraint(osg::ref_ptr<osg::Node> constraint, os
{
const std::lock_guard<std::mutex> lock(VPBTechnique::_constraint_mutex); // Lock the _constraintGroup for this scope
_constraintGroup->addChild(constraint.get());
TerrainVisitor ftv(constraint, TerrainTile::ALL_DIRTY, 0);
terrain->accept(ftv);
}
// Remove a previously added constraint. E.g on model unload.
@ -2310,7 +2269,7 @@ osg::Vec3d VPBTechnique::checkAgainstElevationConstraints(osg::Vec3d origin, osg
}
}
void VPBTechnique::addLineFeatureList(SGBucket bucket, LineFeatureBinList roadList, osg::ref_ptr<osg::Node> terrainNode)
void VPBTechnique::addLineFeatureList(SGBucket bucket, LineFeatureBinList roadList)
{
if (roadList.empty()) return;
@ -2320,26 +2279,9 @@ void VPBTechnique::addLineFeatureList(SGBucket bucket, LineFeatureBinList roadLi
_lineFeatureLists.push_back(std::pair(bucket, roadList));
}
// 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<osg::Node> 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::addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaList, osg::ref_ptr<osg::Node> terrainNode)
void VPBTechnique::addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaList)
{
if (areaList.empty()) return;
@ -2348,27 +2290,9 @@ void VPBTechnique::addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaLi
const std::lock_guard<std::mutex> 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<osg::Node> 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::addCoastlineList(SGBucket bucket, CoastlineBinList coastline, osg::ref_ptr<osg::Node> terrainNode)
void VPBTechnique::addCoastlineList(SGBucket bucket, CoastlineBinList coastline)
{
if (coastline.empty()) return;
@ -2378,28 +2302,11 @@ void VPBTechnique::addCoastlineList(SGBucket bucket, CoastlineBinList coastline,
_coastFeatureLists.push_back(std::pair(bucket, coastline));
}
// 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<osg::Node> 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);
SG_LOG(SG_TERRAIN, SG_DEBUG, "Erasing all features with entry " << bucket);
const std::lock_guard<std::mutex> lock(VPBTechnique::_lineFeatureLists_mutex); // Lock the _lineFeatureLists for this scope
// C++ 20...
//std::erase_if(_lineFeatureLists, [bucket](BucketLineFeatureBinList p) { return p.first == bucket; } );

View File

@ -97,9 +97,9 @@ class VPBTechnique : public TerrainTechnique
static osg::Vec3d checkAgainstElevationConstraints(osg::Vec3d origin, osg::Vec3d vertex, float vertex_gap);
// LineFeatures and AreaFeatures are draped over the underlying mesh.
static void addLineFeatureList(SGBucket bucket, LineFeatureBinList roadList, osg::ref_ptr<osg::Node> terrainNode);
static void addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaList, osg::ref_ptr<osg::Node> terrainNode);
static void addCoastlineList(SGBucket bucket, CoastlineBinList areaList, osg::ref_ptr<osg::Node> terrainNode);
static void addLineFeatureList(SGBucket bucket, LineFeatureBinList roadList);
static void addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaList);
static void addCoastlineList(SGBucket bucket, CoastlineBinList areaList);
static void unloadFeatures(SGBucket bucket);
protected: