diff --git a/simgear/scene/tgdb/ReaderWriterSTG.cxx b/simgear/scene/tgdb/ReaderWriterSTG.cxx index 274fdce9..96472060 100644 --- a/simgear/scene/tgdb/ReaderWriterSTG.cxx +++ b/simgear/scene/tgdb/ReaderWriterSTG.cxx @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -64,6 +65,7 @@ #define RAILWAY_ROUGH "OBJECT_RAILWAY_ROUGH" #define RAILWAY_DETAILED "OBJECT_RAILWAY_DETAILED" #define BUILDING_LIST "BUILDING_LIST" +#define TREE_LIST "TREE_LIST" namespace simgear { @@ -139,6 +141,13 @@ struct ReaderWriterSTG::_ModelBin { std::string _material_name; double _lon, _lat, _elev; }; + struct _TreeList { + _TreeList() : _lon(0), _lat(0), _elev(0) { } + std::string _filename; + std::string _material_name; + double _lon, _lat, _elev; + }; + class DelayLoadReadFileCallback : public OptionsReadFileCallback { @@ -258,6 +267,45 @@ struct ReaderWriterSTG::_ModelBin { } } + if (!_treeList.empty()) { + SGMaterialLibPtr matlib = _options->getMaterialLib(); + + if (!matlib) { + 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 + SGGeod geodPos = SGGeod::fromDegM(b._lon, b._lat, 0.0); + SGSharedPtr mat = matlib->find(b._material_name, geodPos); + + // trying to avoid crash on null material, see: + // https://sentry.io/organizations/flightgear/issues/1867075869 + if (!mat) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Tree list specifies unknown material: " << b._filename << " " << b._material_name); + continue; + } + + const auto path = SGPath(b._filename); + TreeBin* treeBin = new TreeBin(path, mat); + + SGTreeBinList treeList; + treeList.push_back(treeBin); + SG_LOG(SG_TERRAIN, SG_ALERT, "Making trees " << b._material_name); + + osg::MatrixTransform* matrixTransform; + matrixTransform = new osg::MatrixTransform(makeZUpFrame(SGGeod::fromDegM(b._lon, b._lat, b._elev))); + matrixTransform->setName("rotateTrees"); + matrixTransform->setDataVariance(osg::Object::STATIC); + matrixTransform->addChild(createForest(treeList, osg::Matrix::identity(), _options)); + group->addChild(matrixTransform); + + std::for_each(treeList.begin(), treeList.end(), [](TreeBin* bb) { + delete bb; + }); + } + } + } + return group.release(); } @@ -265,6 +313,7 @@ struct ReaderWriterSTG::_ModelBin { std::list<_ObjectStatic> _objectStaticList; std::list<_Sign> _signList; std::list<_BuildingList> _buildingList; + std::list<_TreeList> _treeList; /// The original options to use for this bunch of models osg::ref_ptr _options; @@ -554,7 +603,12 @@ struct ReaderWriterSTG::_ModelBin { in >> buildinglist._material_name >> buildinglist._lon >> buildinglist._lat >> buildinglist._elev; checkInsideBucket(absoluteFileName, buildinglist._lon, buildinglist._lat); _buildingListList.push_back(buildinglist); - //SG_LOG(SG_TERRAIN, SG_ALERT, "Building list: " << buildinglist._filename << " " << buildinglist._material_name << " " << buildinglist._lon << " " << buildinglist._lat); + } else if (token == TREE_LIST) { + _TreeList treelist; + treelist._filename = path.utf8Str(); + in >> treelist._material_name >> treelist._lon >> treelist._lat >> treelist._elev; + checkInsideBucket(absoluteFileName, treelist._lon, treelist._lat); + _treeListList.push_back(treelist); } 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() @@ -640,7 +694,7 @@ struct ReaderWriterSTG::_ModelBin { i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat)); } - if (_objectStaticList.empty() && _signList.empty() && (_buildingListList.size() == 0)) { + if (_objectStaticList.empty() && _signList.empty() && (_buildingListList.size() == 0) && (_treeListList.size() == 0)) { // The simple case, just return the terrain group return terrainGroup.release(); } else { @@ -656,6 +710,7 @@ struct ReaderWriterSTG::_ModelBin { osg::ref_ptr readFileCallback = new DelayLoadReadFileCallback; readFileCallback->_objectStaticList = _objectStaticList; readFileCallback->_buildingList = _buildingListList; + readFileCallback->_treeList = _treeListList; readFileCallback->_signList = _signList; readFileCallback->_options = options; readFileCallback->_bucket = bucket; @@ -683,6 +738,7 @@ struct ReaderWriterSTG::_ModelBin { std::list<_ObjectStatic> _objectStaticList; std::list<_Sign> _signList; std::list<_BuildingList> _buildingListList; + std::list<_TreeList> _treeListList; }; ReaderWriterSTG::ReaderWriterSTG() diff --git a/simgear/scene/tgdb/TreeBin.cxx b/simgear/scene/tgdb/TreeBin.cxx index 5687df2a..1695e7a3 100644 --- a/simgear/scene/tgdb/TreeBin.cxx +++ b/simgear/scene/tgdb/TreeBin.cxx @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -442,5 +443,70 @@ osg::Group* createForest(SGTreeBinList& forestList, const osg::Matrix& transform return mt; } +TreeBin::TreeBin(const SGMaterial *mat) +{ + texture_varieties = mat->get_tree_varieties(); + range = mat->get_tree_range(); + height = mat->get_tree_height(); + width = mat->get_tree_width(); + texture = mat->get_tree_texture(); + teffect = mat->get_tree_effect(); +}; + + +TreeBin::TreeBin(const SGPath& absoluteFileName, const SGMaterial *mat) : +TreeBin(mat) +{ + if (!absoluteFileName.exists()) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Tree 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 tree position, and may have + // 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); + + // Line format is X Y Z A B C + // where: + // X,Y,Z are the cartesian coordinates of the center tree + // A,B,C is the normal of the underlying terrain, defaulting to 0,0,1 + float x = 0.0f, y = 0.0f, z = 0.0f, a = 0.0f, b = 0.0f, c = 1.0f; + in >> x >> y >> z; + + if (in.bad() || in.fail()) { + SG_LOG(SG_TERRAIN, SG_WARN, "Error parsing tree entry in: " << absoluteFileName << " line: \"" << line << "\""); + continue; + } + + // these might fail, so check them after we look at failbit + in >> a >> b >> c; + + SGVec3f loc = SGVec3f(x,y,z); + SGVec3f norm = SGVec3f(a,b,c); + + SG_LOG(SG_TERRAIN, SG_ALERT, "Adding tree " << x << " " << y << " " << z); + + insert(Tree(loc, norm)); + } + + stream.close(); +}; + } diff --git a/simgear/scene/tgdb/TreeBin.hxx b/simgear/scene/tgdb/TreeBin.hxx index 138c2e0b..9b4b280e 100644 --- a/simgear/scene/tgdb/TreeBin.hxx +++ b/simgear/scene/tgdb/TreeBin.hxx @@ -35,6 +35,10 @@ namespace simgear { class TreeBin { public: + TreeBin() = default; + TreeBin(const SGMaterial *mat); + TreeBin(const SGPath& absoluteFileName, const SGMaterial *mat); + ~TreeBin() = default; struct Tree {