diff --git a/simgear/scene/tgdb/CMakeLists.txt b/simgear/scene/tgdb/CMakeLists.txt index ec586332..44dcda9d 100644 --- a/simgear/scene/tgdb/CMakeLists.txt +++ b/simgear/scene/tgdb/CMakeLists.txt @@ -1,8 +1,9 @@ include (SimGearComponent) set(HEADERS - GroundLightManager.hxx AreaFeatureBin.hxx + CoastlineBin.hxx + GroundLightManager.hxx LineFeatureBin.hxx ReaderWriterSPT.hxx ReaderWriterSTG.hxx @@ -22,6 +23,7 @@ set(HEADERS ShaderGeometry.hxx TreeBin.hxx VPBElevationSlice.hxx + VPBTileBounds.hxx VPBTechnique.hxx apt_signs.hxx obj.hxx @@ -30,8 +32,9 @@ set(HEADERS ) set(SOURCES - GroundLightManager.cxx AreaFeatureBin.cxx + CoastlineBin.cxx + GroundLightManager.cxx LineFeatureBin.cxx ReaderWriterSPT.cxx ReaderWriterSTG.cxx @@ -42,6 +45,7 @@ set(SOURCES ShaderGeometry.cxx TreeBin.cxx VPBElevationSlice.cxx + VPBTileBounds.cxx VPBTechnique.cxx apt_signs.cxx obj.cxx diff --git a/simgear/scene/tgdb/CoastlineBin.cxx b/simgear/scene/tgdb/CoastlineBin.cxx new file mode 100644 index 00000000..577fc485 --- /dev/null +++ b/simgear/scene/tgdb/CoastlineBin.cxx @@ -0,0 +1,99 @@ +/* -*-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 "CoastlineBin.hxx" + +using namespace osg; + +namespace simgear +{ + +CoastlineBin::CoastlineBin(const SGPath& absoluteFileName) +{ + if (!absoluteFileName.exists()) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Coastline 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 coast line feature, consisting of 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); + + if (in.bad() || in.fail()) { + SG_LOG(SG_TERRAIN, SG_WARN, "Error parsing area entry in: " << absoluteFileName << " line: \"" << line << "\""); + continue; + } + + std::list nodes; + + 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() > 1) { + insert(Coastline(nodes)); + } else { + SG_LOG(SG_TERRAIN, SG_WARN, "Coastline definition with fewer than two lon/lat nodes : " << absoluteFileName << " line: \"" << line << "\""); + } + } + + stream.close(); +}; + + +} diff --git a/simgear/scene/tgdb/CoastlineBin.hxx b/simgear/scene/tgdb/CoastlineBin.hxx new file mode 100644 index 00000000..e8fa7d74 --- /dev/null +++ b/simgear/scene/tgdb/CoastlineBin.hxx @@ -0,0 +1,69 @@ +/* -*-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 COAST_BIN_HXX +#define COAST_BIN_HXX + +#include +#include + +#include +#include +#include + +#include + +namespace simgear +{ +class CoastlineBin { +public: + CoastlineBin() = default; + CoastlineBin(const SGPath& absoluteFileName); + + ~CoastlineBin() = default; + + struct Coastline { + const std::list _nodes; + Coastline(const std::list nodes) : + _nodes(nodes) + { + } + }; + + typedef std::list CoastlineList; + + void insert(const Coastline& t) { + _coastFeatureList.push_back(t); + } + + const CoastlineList getCoastlines() const { + return _coastFeatureList; + } + +private: + CoastlineList _coastFeatureList; +}; + +typedef std::list CoastlineBinList; + +} + +#endif diff --git a/simgear/scene/tgdb/LineFeatureBin.hxx b/simgear/scene/tgdb/LineFeatureBin.hxx index 88b0312d..d7746888 100644 --- a/simgear/scene/tgdb/LineFeatureBin.hxx +++ b/simgear/scene/tgdb/LineFeatureBin.hxx @@ -48,7 +48,7 @@ public: const float _b; const float _c; const float _d; - LineFeature(const std::list nodes, const float w, const int attributes, const float a, const float b, const float c, const float d) : + LineFeature(const std::list nodes, const float w, const int attributes=0, const float a=0.0, const float b=0.0, const float c=0.0, const float d=0.0) : _nodes(nodes), _width(w), _attributes(attributes), _a(a), _b(b), _c(c), _d(d) { } diff --git a/simgear/scene/tgdb/ReaderWriterSTG.cxx b/simgear/scene/tgdb/ReaderWriterSTG.cxx index 34e00b41..01b73b64 100644 --- a/simgear/scene/tgdb/ReaderWriterSTG.cxx +++ b/simgear/scene/tgdb/ReaderWriterSTG.cxx @@ -72,6 +72,7 @@ #define TREE_LIST "TREE_LIST" #define LINE_FEATURE_LIST "LINE_FEATURE_LIST" #define AREA_FEATURE_LIST "AREA_FEATURE_LIST" +#define COASTLINE_LIST "COASTLINE_LIST" namespace simgear { @@ -167,6 +168,12 @@ struct ReaderWriterSTG::_ModelBin { SGBucket _bucket; }; + struct _CoastlineList { + _CoastlineList() { } + std::string _filename; + SGBucket _bucket; + }; + class DelayLoadReadFileCallback : public OptionsReadFileCallback { private: @@ -353,6 +360,20 @@ struct ReaderWriterSTG::_ModelBin { 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(); } @@ -363,6 +384,7 @@ struct ReaderWriterSTG::_ModelBin { std::list<_TreeList> _treeList; std::list<_LineFeatureList> _lineFeatureList; std::list<_AreaFeatureList> _areaFeatureList; + std::list<_CoastlineList> _coastFeatureList; osg::ref_ptr _terrainNode; /// The original options to use for this bunch of models @@ -672,6 +694,11 @@ struct ReaderWriterSTG::_ModelBin { in >> areaFeaturelist._material; areaFeaturelist._bucket = bucketIndexFromFileName(absoluteFileName.file_base().c_str()); _areaFeatureListList.push_back(areaFeaturelist); + } else if (token == COASTLINE_LIST) { + _CoastlineList coastFeaturelist; + coastFeaturelist._filename = path.utf8Str(); + coastFeaturelist._bucket = bucketIndexFromFileName(absoluteFileName.file_base().c_str()); + _coastFeatureListList.push_back(coastFeaturelist); } 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() @@ -790,50 +817,58 @@ struct ReaderWriterSTG::_ModelBin { i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat)); } - if (_objectStaticList.empty() && _signList.empty() && _buildingListList.empty() && _treeListList.empty() && _lineFeatureListList.empty() && _areaFeatureListList.empty()) { + if (_objectStaticList.empty() && + _signList.empty() && + _buildingListList.empty() && + _treeListList.empty() && + _lineFeatureListList.empty() && + _areaFeatureListList.empty() && + _coastFeatureListList.empty()) + { // The simple case, just return the terrain group return terrainGroup.release(); - } else { - osg::PagedLOD* pagedLOD = new osg::PagedLOD; - pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER); - std::string name = string("pagedObjectLOD ").append(bucket.gen_index_str()); - pagedLOD->setName(name); - - // This should be visible in any case. - // If this is replaced by some lower level of detail, the parent LOD node handles this. - pagedLOD->addChild(terrainGroup, 0, std::numeric_limits::max()); - pagedLOD->setMinimumExpiryTime(0, pagedLODExpiry); - - // we just need to know about the read file callback that itself holds the data - osg::ref_ptr readFileCallback = new DelayLoadReadFileCallback; - readFileCallback->_objectStaticList = _objectStaticList; - readFileCallback->_buildingList = _buildingListList; - readFileCallback->_treeList = _treeListList; - readFileCallback->_signList = _signList; - readFileCallback->_options = options; - readFileCallback->_bucket = bucket; - - if (vpb_active && vpb_node) { - readFileCallback->_lineFeatureList = _lineFeatureListList; - readFileCallback->_areaFeatureList = _areaFeatureListList; - readFileCallback->_terrainNode = vpb_node; - } - - osg::ref_ptr callbackOptions = new osgDB::Options; - callbackOptions->setReadFileCallback(readFileCallback.get()); - pagedLOD->setDatabaseOptions(callbackOptions.get()); - - pagedLOD->setFileName(pagedLOD->getNumChildren(), "Dummy name - use the stored data in the read file callback"); - - // Objects may end up displayed up to 2x the object range. - pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough); - pagedLOD->setMinimumExpiryTime(pagedLOD->getNumChildren(), pagedLODExpiry); - pagedLOD->setRadius(SG_TILE_RADIUS); - SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() ); - SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Range: " << (2.0 * _object_range_rough)); - SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Radius: " << SG_TILE_RADIUS); - return pagedLOD; } + + osg::PagedLOD* pagedLOD = new osg::PagedLOD; + pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER); + std::string name = string("pagedObjectLOD ").append(bucket.gen_index_str()); + pagedLOD->setName(name); + + // This should be visible in any case. + // If this is replaced by some lower level of detail, the parent LOD node handles this. + pagedLOD->addChild(terrainGroup, 0, std::numeric_limits::max()); + pagedLOD->setMinimumExpiryTime(0, pagedLODExpiry); + + // we just need to know about the read file callback that itself holds the data + osg::ref_ptr readFileCallback = new DelayLoadReadFileCallback; + readFileCallback->_objectStaticList = _objectStaticList; + readFileCallback->_buildingList = _buildingListList; + readFileCallback->_treeList = _treeListList; + readFileCallback->_signList = _signList; + 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 callbackOptions = new osgDB::Options; + callbackOptions->setReadFileCallback(readFileCallback.get()); + pagedLOD->setDatabaseOptions(callbackOptions.get()); + + pagedLOD->setFileName(pagedLOD->getNumChildren(), "Dummy name - use the stored data in the read file callback"); + + // Objects may end up displayed up to 2x the object range. + pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough); + pagedLOD->setMinimumExpiryTime(pagedLOD->getNumChildren(), pagedLODExpiry); + pagedLOD->setRadius(SG_TILE_RADIUS); + SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() ); + SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Range: " << (2.0 * _object_range_rough)); + SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Radius: " << SG_TILE_RADIUS); + return pagedLOD; } double _object_range_bare; @@ -847,6 +882,7 @@ struct ReaderWriterSTG::_ModelBin { std::list<_TreeList> _treeListList; std::list<_LineFeatureList> _lineFeatureListList; std::list<_AreaFeatureList> _areaFeatureListList; + std::list<_CoastlineList> _coastFeatureListList; }; ReaderWriterSTG::ReaderWriterSTG() diff --git a/simgear/scene/tgdb/VPBTechnique.cxx b/simgear/scene/tgdb/VPBTechnique.cxx index 2ba6f7c0..2120ab0c 100644 --- a/simgear/scene/tgdb/VPBTechnique.cxx +++ b/simgear/scene/tgdb/VPBTechnique.cxx @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,7 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) applyTrees(*buffer, masterLocator); applyLineFeatures(*buffer, masterLocator); applyAreaFeatures(*buffer, masterLocator); + applyCoastline(*buffer, masterLocator); } } else @@ -177,7 +179,7 @@ void VPBTechnique::init(int dirtyMask, bool assumeMultiThreaded) applyColorLayers(*buffer, masterLocator); applyTrees(*buffer, masterLocator); applyLineFeatures(*buffer, masterLocator); - applyAreaFeatures(*buffer, masterLocator); + applyCoastline(*buffer, masterLocator); } if (buffer->_transform.valid()) buffer->_transform->setThreadSafeRefUnref(true); @@ -1298,6 +1300,8 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator) } } + SG_LOG(SG_TERRAIN, SG_DEBUG, "VPB Image level:" << _terrainTile->getTileID().level << " " << image->s() << "x" << image->t() << " mipmaps:" << image->getNumMipmapLevels() << " format:" << image->getInternalTextureFormat()); + osg::ref_ptr texture2D = new osg::Texture2D; texture2D->setImage(image); texture2D->setMaxAnisotropy(16.0f); @@ -1309,16 +1313,6 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator) texture2D->setWrap(osg::Texture::WRAP_S,osg::Texture::CLAMP_TO_EDGE); texture2D->setWrap(osg::Texture::WRAP_T,osg::Texture::CLAMP_TO_EDGE); - bool mipMapping = !(texture2D->getFilter(osg::Texture::MIN_FILTER)==osg::Texture::LINEAR || texture2D->getFilter(osg::Texture::MIN_FILTER)==osg::Texture::NEAREST); - bool s_NotPowerOfTwo = image->s()==0 || (image->s() & (image->s() - 1)); - bool t_NotPowerOfTwo = image->t()==0 || (image->t() & (image->t() - 1)); - - if (mipMapping && (s_NotPowerOfTwo || t_NotPowerOfTwo)) - { - SG_LOG(SG_TERRAIN, SG_ALERT, "Disabling mipmapping for non power of two tile size("<s()<<", "<t()<<")"); - texture2D->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - } - osg::StateSet* stateset = buffer._landGeode->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, texture2D, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(1, atlas.image, osg::StateAttribute::ON); @@ -1680,23 +1674,24 @@ void VPBTechnique::applyLineFeatures(BufferData& buffer, Locator* masterLocator) void VPBTechnique::generateLineFeature(BufferData& buffer, Locator* masterLocator, LineFeatureBin::LineFeature road, osg::Vec3d modelCenter, 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; - } - - osg::Vec3d ma, mb; - std::list roadPoints; - auto road_iter = road._nodes.begin(); - // We're in Earth-centered coordinates, so "up" is simply directly away from (0,0,0) osg::Vec3d up = modelCenter; up.normalize(); + TileBounds tileBounds(masterLocator, up); + + std::list nodes = tileBounds.clipToTile(road._nodes); + + // We need at least two node to make a road. + if (nodes.size() < 2) return; + + osg::Vec3d ma, mb; + std::list roadPoints; + auto road_iter = nodes.begin(); ma = getMeshIntersection(buffer, masterLocator, *road_iter - modelCenter, up); road_iter++; - for (; road_iter != road._nodes.end(); road_iter++) { + for (; road_iter != nodes.end(); road_iter++) { mb = getMeshIntersection(buffer, masterLocator, *road_iter - modelCenter, up); auto esl = VPBElevationSlice::computeVPBElevationSlice(buffer._landGeometry, ma, mb, up); @@ -1929,6 +1924,203 @@ void VPBTechnique::generateAreaFeature(BufferData& buffer, Locator* masterLocato } } +void VPBTechnique::applyCoastline(BufferData& buffer, Locator* masterLocator) +{ + unsigned int coast_features_lod_range = 4; + float coastWidth = 40.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"); + coast_features_lod_range = static_lod->getIntValue("coastline-lod-level", coast_features_lod_range); + coastWidth = static_lod->getIntValue("coastline-width", coastWidth); + } + + if (tileLevel < coast_features_lod_range) { + // Do not generate coasts for tiles too far away + return; + } + + const SGMaterialLibPtr matlib = _options->getMaterialLib(); + if (! matlib) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to get materials library to generate areas"); + return; + } + + // Get all appropriate coasts. 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 coasts = std::find_if(_coastFeatureLists.begin(), _coastFeatureLists.end(), [bucket](BucketCoastlineBinList b){return (b.first == bucket);}); + + if (coasts == _coastFeatureLists.end()) return; + + // We're in Earth-centered coordinates, so "up" is simply directly away from (0,0,0) + osg::Vec3d up = world; + up.normalize(); + + TileBounds tileBounds(masterLocator, up); + + SGMaterialCache* matcache = matlib->generateMatCache(loc, _options); + SGMaterial* mat = matcache->find("ws30coastline"); + + if (!mat) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to find material ws30coastline at " << loc << " " << bucket); + return; + } + + unsigned int xsize = mat->get_xsize(); + unsigned int ysize = mat->get_ysize(); + + // Generate a geometry for this set of coasts. + osg::Vec3Array* v = new osg::Vec3Array; + osg::Vec2Array* t = new osg::Vec2Array; + osg::Vec3Array* n = new osg::Vec3Array; + osg::Vec4Array* c = new osg::Vec4Array; + + for (; coasts != _coastFeatureLists.end(); ++coasts) { + const CoastlineBinList coastBins = coasts->second; + + for (auto rb = coastBins.begin(); rb != coastBins.end(); ++rb) + { + auto coastFeatures = rb->getCoastlines(); + + for (auto r = coastFeatures.begin(); r != coastFeatures.end(); ++r) { + auto clipped = tileBounds.clipToTile(r->_nodes); + if (clipped.size() > 1) { + // We need at least two points to render a line. + LineFeatureBin::LineFeature line = LineFeatureBin::LineFeature(clipped, coastWidth); + generateCoastlineFeature(buffer, masterLocator, line, world, v, t, n, xsize, ysize); + } + } + } + } + + if (v->size() == 0) { + SG_LOG(SG_TERRAIN, SG_ALERT, "No vertices for coastline! " << loc << " " << bucket); + return; + } + + SG_LOG(SG_TERRAIN, SG_ALERT, "Generating coastline of " << v->size() << " vertices."); + + 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)); + geode->setNodeMask( ~(simgear::CASTSHADOW_BIT | simgear::MODELLIGHT_BIT) ); + buffer._transform->addChild(geode); +} + +void VPBTechnique::generateCoastlineFeature(BufferData& buffer, Locator* masterLocator, LineFeatureBin::LineFeature coastline, osg::Vec3d modelCenter, osg::Vec3Array* v, osg::Vec2Array* t, osg::Vec3Array* n, unsigned int xsize, unsigned int ysize) +{ + if (coastline._nodes.size() < 2) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Coding error - LineFeatureBin::LineFeature with fewer than two nodes"); + return; + } + + osg::Vec3d ma, mb; + std::list coastlinePoints; + auto coastline_iter = coastline._nodes.begin(); + + // We're in Earth-centered coordinates, so "up" is simply directly away from (0,0,0) + osg::Vec3d up = modelCenter; + up.normalize(); + + ma = getMeshIntersection(buffer, masterLocator, *coastline_iter - modelCenter, up); + coastline_iter++; + + for (; coastline_iter != coastline._nodes.end(); coastline_iter++) { + mb = getMeshIntersection(buffer, masterLocator, *coastline_iter - modelCenter, up); + auto esl = VPBElevationSlice::computeVPBElevationSlice(buffer._landGeometry, ma, mb, up); + + for(auto eslitr = esl.begin(); eslitr != esl.end(); ++eslitr) { + coastlinePoints.push_back(*eslitr); + } + + // Now traverse the next segment + ma = mb; + } + + if (coastlinePoints.size() == 0) return; + + // We now have a series of points following the topography of the elevation mesh. + + auto iter = coastlinePoints.begin(); + osg::Vec3d start = *iter; + iter++; + + osg::Vec3d last_spanwise = (*iter - start)^ up; + last_spanwise.normalize(); + + float yTexBaseA = 0.0f; + float yTexBaseB = 0.0f; + + for (; iter != coastlinePoints.end(); iter++) { + + osg::Vec3d end = *iter; + + // Ignore tiny segments - likely artifacts of the elevation slicer + if ((end - start).length2() < 5.0) continue; + + // Find a spanwise vector + osg::Vec3d spanwise = ((end-start) ^ up); + spanwise.normalize(); + + // Define the coastline extents. Angle it in slightly on the seaward side + const osg::Vec3d a = start + up; + const osg::Vec3d b = start + last_spanwise * coastline._width; + const osg::Vec3d c = end + up; + const osg::Vec3d d = end + spanwise * coastline._width; + + // Determine the x and y texture coordinates for the edges + const float xTex = 0.5 * coastline._width / xsize; + const float yTexA = yTexBaseA + (c-a).length() / ysize; + const float yTexB = yTexBaseB + (d-b).length() / ysize; + + // Now generate two triangles, . + v->push_back(a); + v->push_back(b); + v->push_back(c); + + t->push_back(osg::Vec2d(0, yTexBaseA)); + t->push_back(osg::Vec2d(xTex, yTexBaseB)); + t->push_back(osg::Vec2d(0, yTexA)); + + v->push_back(b); + v->push_back(d); + v->push_back(c); + + t->push_back(osg::Vec2d(xTex, yTexBaseB)); + t->push_back(osg::Vec2d(xTex, yTexB)); + t->push_back(osg::Vec2d(0, yTexBaseA)); + + // 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; + yTexBaseA = yTexA; + yTexBaseB = yTexB; + last_spanwise = spanwise; + } +} + // 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) { @@ -2145,6 +2337,35 @@ void VPBTechnique::addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaLi terrainNode->accept(ftv); } +void VPBTechnique::addCoastlineList(SGBucket bucket, CoastlineBinList coastline, osg::ref_ptr terrainNode) +{ + if (coastline.empty()) return; + + // Block to mutex the List alone + { + const std::lock_guard lock(VPBTechnique::_coastFeatureLists_mutex); // Lock the _lineFeatureLists for this scope + _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 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); @@ -2152,8 +2373,7 @@ void VPBTechnique::unloadFeatures(SGBucket bucket) // C++ 20... //std::erase_if(_lineFeatureLists, [bucket](BucketLineFeatureBinList p) { return p.first == bucket; } ); //std::erase_if(_lineFeatureLists, [bucket](BucketAreaFeatureBinList p) { return p.first == bucket; } ); + //std::erase_if(_coastFeatureLists, [bucket](BucketCoastlineBinList p) { return p.first == bucket; } ); } - - diff --git a/simgear/scene/tgdb/VPBTechnique.hxx b/simgear/scene/tgdb/VPBTechnique.hxx index 9399e673..7291c050 100644 --- a/simgear/scene/tgdb/VPBTechnique.hxx +++ b/simgear/scene/tgdb/VPBTechnique.hxx @@ -33,6 +33,7 @@ #include #include #include +#include using namespace osgTerrain; @@ -95,9 +96,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 and AreaFeaturesare draped over the underlying mesh. + // LineFeatures and AreaFeatures are draped over the underlying mesh. static void addLineFeatureList(SGBucket bucket, LineFeatureBinList roadList, osg::ref_ptr terrainNode); static void addAreaFeatureList(SGBucket bucket, AreaFeatureBinList areaList, osg::ref_ptr terrainNode); + static void addCoastlineList(SGBucket bucket, CoastlineBinList areaList, osg::ref_ptr terrainNode); static void unloadFeatures(SGBucket bucket); protected: @@ -136,8 +138,9 @@ 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, + virtual void generateLineFeature(BufferData& buffer, + Locator* masterLocator, + LineFeatureBin::LineFeature road, osg::Vec3d modelCenter, osg::Vec3Array* v, osg::Vec2Array* t, @@ -146,8 +149,9 @@ class VPBTechnique : public TerrainTechnique unsigned int ysize); virtual void applyAreaFeatures(BufferData& buffer, Locator* masterLocator); - virtual void generateAreaFeature(BufferData& buffer, Locator* - masterLocator, AreaFeatureBin::AreaFeature area, + virtual void generateAreaFeature(BufferData& buffer, + Locator* masterLocator, + AreaFeatureBin::AreaFeature area, osg::Vec3d modelCenter, osg::Geometry* geometry, osg::Vec3Array* v, @@ -156,6 +160,17 @@ class VPBTechnique : public TerrainTechnique unsigned int xsize, unsigned int ysize); + virtual void applyCoastline(BufferData& buffer, Locator* masterLocator); + virtual void generateCoastlineFeature(BufferData& buffer, + Locator* masterLocator, + LineFeatureBin::LineFeature coastLine, + osg::Vec3d modelCenter, + 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; @@ -176,6 +191,7 @@ class VPBTechnique : public TerrainTechnique typedef std::pair BucketLineFeatureBinList; typedef std::pair BucketAreaFeatureBinList; + typedef std::pair BucketCoastlineBinList; inline static std::list _lineFeatureLists; inline static std::mutex _lineFeatureLists_mutex; // protects the _lineFeatureLists; @@ -183,8 +199,12 @@ class VPBTechnique : public TerrainTechnique inline static std::list _areaFeatureLists; inline static std::mutex _areaFeatureLists_mutex; // protects the _areaFeatureLists; + inline static std::list _coastFeatureLists; + inline static std::mutex _coastFeatureLists_mutex; // protects the _areaFeatureLists; + +}; + }; -} #endif diff --git a/simgear/scene/tgdb/VPBTileBounds.cxx b/simgear/scene/tgdb/VPBTileBounds.cxx new file mode 100644 index 00000000..7cdcea24 --- /dev/null +++ b/simgear/scene/tgdb/VPBTileBounds.cxx @@ -0,0 +1,146 @@ +// VPBTileBounds.cxx -- VirtualPlanetBuilder Tile bounds for clipping +// +// 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. + +#include "VPBTileBounds.hxx" + +#include + + +TileBounds::TileBounds(Locator *locator, osg::Vec3d up) { + // Determine the corners of the tile; + locator->convertLocalToModel(osg::Vec3d(0.0, 0.0, 0.0), v00); + locator->convertLocalToModel(osg::Vec3d(1.0, 0.0, 0.0), v10); + locator->convertLocalToModel(osg::Vec3d(0.0, 1.0, 0.0), v01); + locator->convertLocalToModel(osg::Vec3d(1.0, 1.0, 0.0), v11); + + corner[0] = v10; + corner[1] = v11; + corner[2] = v01; + corner[3] = v00; + + // Determine the normals of the planes defining the vertical edges of the tiles. + // This can be found using the cross product of the horizontal line and an appropriate "up" + // vector. We will approximate the last using the center rather than working out different + // "up" vectors for each edge individually. We assume that (0,0) in local coords is bottom left. + south = (v10 - v00) ^ up; + east = (v11 - v10) ^ up; + north = (v01 - v11) ^ up; + west = (v00 - v01) ^ up; +} + +std::list TileBounds::clipToTile(std::list points) { + + std::list lreturn; + + bool last_in = false; + auto last_pt = points.begin(); + + for (auto p = points.begin(); p != points.end(); p++) { + if (insideTile(*p)) { + +/* + if ((last_in == false) && (last_pt != p)) { + // Last point was outside the border of the tile, so we need to + // add the actual intersection as an additional point if required. + lreturn.push_back(getTileIntersection(*p, *last_pt)); + } +*/ + + // Last point wasn't in, but we need to include it to ensure we get an + // intersection with the edge of the tile. + if (last_in == false) lreturn.push_back(*last_pt); + + // Point is within bounds, so add it. + lreturn.push_back(*p); + + last_in = true; + } else if (last_in) { + // Point is outside bounds, but last point was inside. So add it to ensure + // we catch the intersection with the mesh edge. + lreturn.push_back(*p); + last_in = false; + } + + last_pt = p; + } + + return lreturn; +} + +bool TileBounds::insideTile(osg::Vec3d pt) { + + float s = south * (pt - v00); + float e = east * (pt - v10); + float n = north * (pt - v11); + float w = west * (pt - v01); + + return ((s<0) && (e<0) && (n<0) && (w<0)); +} + +// Get the Tile intersection, and also the corner of the tile to the right of the intersection. +// This corner can be used to define the seaward edge of some coastline. +osg::Vec3d TileBounds::getTileIntersection(osg::Vec3d inside, osg::Vec3d outside) { + + if (! insideTile(inside)) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Invalid VPB Tile intersection - \"inside\" point not inside!"); + return inside; + } + + if (insideTile(outside)) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Invalid VPB Tile intersection - \"outside\" point not outside!"); + return outside; + } + + // Simply clip against each of the planes in turn. + osg::Vec3d intersect = outside; + intersect = getPlaneIntersection(inside, intersect, south, v00); + intersect = getPlaneIntersection(inside, intersect, east, v10); + intersect = getPlaneIntersection(inside, intersect, north, v11); + intersect = getPlaneIntersection(inside, intersect, west, v01); + + return intersect; +} + +osg::Vec3d TileBounds::getPlaneIntersection(osg::Vec3d inside, osg::Vec3d outside, osg::Vec3d normal, osg::Vec3d plane) { + // Check for any intersection at all first + osg::Vec3d n = normal; + osg::Vec3d p0 = plane; + osg::Vec3d l0 = inside; + osg::Vec3d l = outside-inside; + + if (fabs(l * n) < 0.01) return outside; + + float d = ((p0 - l0) * n) / (l * n); + return l0 + l * d; + + +/* + float i = normal * (outside - plane); + + if (i < 0) { + // There's an intersection, so calculate it + double d = ((plane - inside) * normal) / ((outside - inside) * normal); + return inside + (outside - inside) * d; + } else { + // No intersection + return outside; + } +*/ +} + + diff --git a/simgear/scene/tgdb/VPBTileBounds.hxx b/simgear/scene/tgdb/VPBTileBounds.hxx new file mode 100644 index 00000000..570bc16e --- /dev/null +++ b/simgear/scene/tgdb/VPBTileBounds.hxx @@ -0,0 +1,49 @@ +// VPBTileBounds.hxx -- VirtualPlanetBuilder Tile bounds for clipping +// +// 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 VPBTILEBOUNDS +#define VPBTILEBOUNDS 1 + +#include +#include + +using namespace osgTerrain; + + +class TileBounds { + public: + TileBounds(Locator *locator, osg::Vec3d up); + virtual std::list clipToTile(std::list points); + + protected: + + virtual bool insideTile(osg::Vec3d pt); + virtual osg::Vec3d getTileIntersection(osg::Vec3d inside, osg::Vec3d outside); + virtual osg::Vec3d getPlaneIntersection(osg::Vec3d inside, osg::Vec3d outside, osg::Vec3d normal, osg::Vec3d plane); + + // Corners of the tile + osg::Vec3d v00, v01, v10, v11; + osg::Vec3d corner[4]; + + // Plan normals for the tile bounds + osg::Vec3d north, east, south, west; + + +}; + +#endif