WS30: AREA_FEATURE_LIST STG verb - lakes, lochs

- Add and AREA_FEATURE_LIST STG verb.
- /sim/rendering/static-lod/area-features-lod-level to control point at
  which such feature are rendered
- /sim/rendering/static-lod/lod-leve[n]/area-features-min-width sets
  the minimum width for feature rendering at that LoD level.

STG Format:

AREA_FEATURE_LIST <file> <material>

File format:

Area Attrib A B C D lon0 lat0 lon1 lat1 lon2 lat2 lon3 lat4....

where:
 Area is the area of the feature in m^2
 Attrib is an integer attribute (currently unused)
 A, B, C, D are generic float attributes.  Their interpretation may vary by feature type
 lon[n], lat[n] are pairs of lon/lat defining straight road segments
This commit is contained in:
Stuart Buchanan 2021-04-04 16:25:04 +01:00
parent 5f026c840c
commit 769e00ffdf
7 changed files with 434 additions and 11 deletions

View File

@ -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 <simgear_config.h>
#endif
#include <osgDB/ReadFile>
#include <osgDB/FileUtils>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/math/SGGeod.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#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<osg::Vec3d> 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();
};
}

View File

@ -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 <vector>
#include <string>
#include <osg/Geometry>
#include <osg/Group>
#include <osg/Matrix>
#include <simgear/misc/sg_path.hxx>
namespace simgear
{
class AreaFeatureBin {
public:
AreaFeatureBin() = default;
AreaFeatureBin(const SGPath& absoluteFileName, const std::string material);
~AreaFeatureBin() = default;
struct AreaFeature {
const std::list<osg::Vec3d> _nodes;
const float _area;
const int _attributes;
const float _a;
const float _b;
const float _c;
const float _d;
AreaFeature(const std::list<osg::Vec3d> 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<AreaFeature> 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<AreaFeatureBin> AreaFeatureBinList;
}
#endif

View File

@ -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

View File

@ -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;

View File

@ -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<osg::Node> _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()

View File

@ -25,6 +25,7 @@
#include <osgUtil/LineSegmentIntersector>
#include <osgUtil/IntersectionVisitor>
#include <osgUtil/MeshOptimizers>
#include <osgUtil/Tessellator>
#include <osgDB/FileUtils>
#include <osgDB/ReadFile>
@ -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<osg::Geometry> 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<osgUtil::Tessellator> 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<osg::Node> terrainNode)
{
if (areaList.empty()) return;
// Block to mutex the List alone
{
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::unloadFeatures(SGBucket bucket)
{
SG_LOG(SG_TERRAIN, SG_DEBUG, "Erasing all roads 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; } );
//std::erase_if(_lineFeatureLists, [bucket](BucketAreaFeatureBinList p) { return p.first == bucket; } );
}

View File

@ -31,6 +31,7 @@
#include <simgear/bucket/newbucket.hxx>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/tgdb/AreaFeatureBin.hxx>
#include <simgear/scene/tgdb/LineFeatureBin.hxx>
using namespace osgTerrain;
@ -94,9 +95,10 @@ class VPBTechnique : public TerrainTechnique
static void removeElevationConstraint(osg::ref_ptr<osg::Node> 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<osg::Node> terrainNode);
static void unloadLineFeatures(SGBucket bucket);
static void addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaList, osg::ref_ptr<osg::Node> 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<SGBucket, LineFeatureBinList> BucketLineFeatureBinList;
typedef std::pair<SGBucket, AreaFeatureBinList> BucketAreaFeatureBinList;
inline static std::list<BucketLineFeatureBinList> _lineFeatureLists;
inline static std::mutex _lineFeatureLists_mutex; // protects the _roadLists;
inline static std::mutex _lineFeatureLists_mutex; // protects the _lineFeatureLists;
inline static std::list<BucketAreaFeatureBinList> _areaFeatureLists;
inline static std::mutex _areaFeatureLists_mutex; // protects the _areaFeatureLists;
};