1 // ReaderWriterSTG.cxx -- routines to handle a scenery tile
2 //
3 // Written by Curtis Olson, started May 1998.
4 //
5 // Copyright (C) 1998 - 2001  Curtis L. Olson  - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 
22 #ifdef HAVE_CONFIG_H
23 #  include <simgear_config.h>
24 #endif
25 
26 #include "ReaderWriterSTG.hxx"
27 
28 #include <osg/LOD>
29 #include <osg/MatrixTransform>
30 #include <osg/PagedLOD>
31 #include <osg/ProxyNode>
32 #include <osgUtil/LineSegmentIntersector>
33 #include <osgUtil/IntersectionVisitor>
34 
35 #include <osgDB/FileNameUtils>
36 #include <osgDB/FileUtils>
37 #include <osgDB/Registry>
38 #include <osgDB/ReaderWriter>
39 #include <osgDB/ReadFile>
40 
41 #include <simgear/bucket/newbucket.hxx>
42 #include <simgear/debug/ErrorReportingCallback.hxx>
43 #include <simgear/debug/logstream.hxx>
44 #include <simgear/math/SGGeometry.hxx>
45 #include <simgear/math/sg_random.h>
46 
47 #include <simgear/io/iostreams/sgstream.hxx>
48 #include <simgear/scene/util/OptionsReadFileCallback.hxx>
49 #include <simgear/scene/util/OsgMath.hxx>
50 #include <simgear/scene/util/QuadTreeBuilder.hxx>
51 #include <simgear/scene/util/RenderConstants.hxx>
52 #include <simgear/scene/util/SGReaderWriterOptions.hxx>
53 #include <simgear/scene/tgdb/apt_signs.hxx>
54 #include <simgear/scene/tgdb/obj.hxx>
55 #include <simgear/scene/material/matlib.hxx>
56 #include <simgear/scene/tgdb/SGBuildingBin.hxx>
57 
58 #include "SGOceanTile.hxx"
59 
60 #define BUILDING_ROUGH "OBJECT_BUILDING_MESH_ROUGH"
61 #define BUILDING_DETAILED "OBJECT_BUILDING_MESH_DETAILED"
62 #define ROAD_ROUGH "OBJECT_ROAD_ROUGH"
63 #define ROAD_DETAILED "OBJECT_ROAD_DETAILED"
64 #define RAILWAY_ROUGH "OBJECT_RAILWAY_ROUGH"
65 #define RAILWAY_DETAILED "OBJECT_RAILWAY_DETAILED"
66 #define BUILDING_LIST "BUILDING_LIST"
67 
68 namespace simgear {
69 
70 /// Ok, this is a hack - we do not exactly know if it's an airport or not.
71 /// This feature might also vanish again later. This is currently to
72 /// support testing an external ai component that just loads the the airports
73 /// and supports ground queries on only these areas.
isAirportBtg(const std::string & name)74 static bool isAirportBtg(const std::string& name)
75 {
76     if (name.size() < 8)
77         return false;
78     if (name.substr(4, 4) != ".btg")
79         return false;
80     for (unsigned i = 0; i < 4; ++i) {
81         if ('A' <= name[i] && name[i] <= 'Z')
82             continue;
83         return false;
84     }
85     return true;
86 }
87 
bucketIndexFromFileName(const std::string & fileName)88 static SGBucket bucketIndexFromFileName(const std::string& fileName)
89 {
90   // Extract the bucket from the filename
91   std::istringstream ss(osgDB::getNameLessExtension(fileName));
92   long index;
93   ss >> index;
94   if (ss.fail())
95     return SGBucket();
96 
97   return SGBucket(index);
98 }
99 
100 /**
101  * callback per STG token, with access synced by a lock.
102  */
103 using TokenCallbackMap = std::map<std::string,ReaderWriterSTG::STGObjectCallback>;
104 static TokenCallbackMap globalStgObjectCallbacks = {};
105 static OpenThreads::Mutex globalStgObjectCallbackLock;
106 
107 struct ReaderWriterSTG::_ModelBin {
108     struct _Object {
109         SGPath _errorLocation;
110         std::string _token;
111         std::string _name;
112         osg::ref_ptr<SGReaderWriterOptions> _options;
113     };
114     struct _ObjectStatic {
_ObjectStaticsimgear::ReaderWriterSTG::_ModelBin::_ObjectStatic115         _ObjectStatic() : _agl(false), _proxy(false), _lon(0), _lat(0), _elev(0), _hdg(0), _pitch(0), _roll(0), _range(SG_OBJECT_RANGE_ROUGH) { }
116         SGPath _errorLocation;
117         std::string _token;
118         std::string _name;
119         bool _agl;
120         bool _proxy;
121         double _lon, _lat, _elev;
122         double _hdg, _pitch, _roll;
123         double _range;
124         osg::ref_ptr<SGReaderWriterOptions> _options;
125     };
126     struct _Sign {
_Signsimgear::ReaderWriterSTG::_ModelBin::_Sign127         _Sign() : _agl(false), _lon(0), _lat(0), _elev(0), _hdg(0), _size(-1) { }
128         SGPath _errorLocation;
129         std::string _token;
130         std::string _name;
131         bool _agl;
132         double _lon, _lat, _elev;
133         double _hdg;
134         int _size;
135     };
136     struct _BuildingList {
_BuildingListsimgear::ReaderWriterSTG::_ModelBin::_BuildingList137       _BuildingList() : _lon(0), _lat(0), _elev(0) { }
138       std::string _filename;
139       std::string _material_name;
140       double _lon, _lat, _elev;
141     };
142 
143     class DelayLoadReadFileCallback : public OptionsReadFileCallback {
144 
145     private:
146       // QuadTreeBuilder for structuring static objects
147       struct MakeQuadLeaf {
operator ()simgear::ReaderWriterSTG::_ModelBin::DelayLoadReadFileCallback::MakeQuadLeaf148           osg::LOD* operator() () const { return new osg::LOD; }
149       };
150       struct AddModelLOD {
operator ()simgear::ReaderWriterSTG::_ModelBin::DelayLoadReadFileCallback::AddModelLOD151           void operator() (osg::LOD* leaf, _ObjectStatic o) const
152           {
153             osg::ref_ptr<osg::Node> node;
154             if (o._proxy)  {
155                 osg::ref_ptr<osg::ProxyNode> proxy = new osg::ProxyNode;
156                 proxy->setName("proxyNode");
157                 proxy->setLoadingExternalReferenceMode(osg::ProxyNode::DEFER_LOADING_TO_DATABASE_PAGER);
158                 proxy->setFileName(0, o._name);
159                 proxy->setDatabaseOptions(o._options);
160 
161                 // Give the node some values so the Quadtree builder has
162                 // a BoundingBox to work with prior to the model being loaded.
163                 proxy->setCenter(osg::Vec3f(0.0f,0.0f,0.0f));
164                 proxy->setRadius(10);
165                 proxy->setCenterMode(osg::ProxyNode::UNION_OF_BOUNDING_SPHERE_AND_USER_DEFINED);
166                 node = proxy;
167             } else {
168                 ErrorReportContext ec("terrain-stg", o._errorLocation.utf8Str());
169 #if OSG_VERSION_LESS_THAN(3,4,0)
170                 node = osgDB::readNodeFile(o._name, o._options.get());
171 #else
172                 node = osgDB::readRefNodeFile(o._name, o._options.get());
173 #endif
174                 if (!node.valid()) {
175                     SG_LOG(SG_TERRAIN, SG_ALERT, o._errorLocation << ": Failed to load "
176                            << o._token << " '" << o._name << "'");
177                     return;
178                 }
179             }
180             if (SGPath(o._name).lower_extension() == "ac")
181                 node->setNodeMask(~simgear::MODELLIGHT_BIT);
182 
183             osg::Matrix matrix;
184             matrix = makeZUpFrame(SGGeod::fromDegM(o._lon, o._lat, o._elev));
185             matrix.preMultRotate(osg::Quat(SGMiscd::deg2rad(o._hdg), osg::Vec3(0, 0, 1)));
186             matrix.preMultRotate(osg::Quat(SGMiscd::deg2rad(o._pitch), osg::Vec3(0, 1, 0)));
187             matrix.preMultRotate(osg::Quat(SGMiscd::deg2rad(o._roll), osg::Vec3(1, 0, 0)));
188 
189             osg::MatrixTransform* matrixTransform;
190             matrixTransform = new osg::MatrixTransform(matrix);
191             matrixTransform->setName("rotateStaticObject");
192             matrixTransform->setDataVariance(osg::Object::STATIC);
193             matrixTransform->addChild(node.get());
194 
195             leaf->addChild(matrixTransform, 0, o._range);
196           }
197       };
198       struct GetModelLODCoord {
GetModelLODCoordsimgear::ReaderWriterSTG::_ModelBin::DelayLoadReadFileCallback::GetModelLODCoord199           GetModelLODCoord() {}
GetModelLODCoordsimgear::ReaderWriterSTG::_ModelBin::DelayLoadReadFileCallback::GetModelLODCoord200           GetModelLODCoord(const GetModelLODCoord& rhs)
201           {}
operator ()simgear::ReaderWriterSTG::_ModelBin::DelayLoadReadFileCallback::GetModelLODCoord202           osg::Vec3 operator() (const _ObjectStatic& o) const
203           {
204               SGVec3d coord;
205               SGGeodesy::SGGeodToCart(SGGeod::fromDegM(o._lon, o._lat, o._elev), coord);
206               return toOsg(coord);
207           }
208       };
209       typedef QuadTreeBuilder<osg::LOD*, _ObjectStatic, MakeQuadLeaf, AddModelLOD,
210                               GetModelLODCoord>  STGObjectsQuadtree;
211     public:
212         virtual osgDB::ReaderWriter::ReadResult
readNode(const std::string &,const osgDB::Options *)213         readNode(const std::string&, const osgDB::Options*)
214         {
215             ErrorReportContext ec("terrain-bucket", _bucket.gen_index_str());
216 
217             STGObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
218             quadtree.buildQuadTree(_objectStaticList.begin(), _objectStaticList.end());
219             osg::ref_ptr<osg::Group> group = quadtree.getRoot();
220             string group_name = string("STG-group-A ").append(_bucket.gen_index_str());
221             group->setName(group_name);
222             group->setDataVariance(osg::Object::STATIC);
223 
224             simgear::AirportSignBuilder signBuilder(_options->getMaterialLib(), _bucket.get_center());
225             for (std::list<_Sign>::iterator i = _signList.begin(); i != _signList.end(); ++i)
226                 signBuilder.addSign(SGGeod::fromDegM(i->_lon, i->_lat, i->_elev), i->_hdg, i->_name, i->_size);
227             if (signBuilder.getSignsGroup())
228                 group->addChild(signBuilder.getSignsGroup());
229 
230             if (!_buildingList.empty()) {
231                 SGMaterialLibPtr matlib = _options->getMaterialLib();
232                 bool useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
233 
234                 if (!matlib) {
235                     SG_LOG( SG_TERRAIN, SG_ALERT, "Unable to get materials definition for buildings");
236                 } else {
237                     for (const auto& b : _buildingList) {
238                         // Build buildings for each list of buildings
239                         SGGeod geodPos = SGGeod::fromDegM(b._lon, b._lat, 0.0);
240                         SGSharedPtr<SGMaterial> mat = matlib->find(b._material_name, geodPos);
241 
242                         // trying to avoid crash on null material, see:
243                         // https://sentry.io/organizations/flightgear/issues/1867075869
244                         if (!mat) {
245                             SG_LOG(SG_TERRAIN, SG_ALERT, "Building specifies unknown material: " << b._material_name);
246                             continue;
247                         }
248 
249                         const auto path = SGPath(b._filename);
250                         SGBuildingBin* buildingBin = new SGBuildingBin(path, mat, useVBOs);
251 
252                         SGBuildingBinList bbList;
253                         bbList.push_back(buildingBin);
254 
255                         osg::MatrixTransform* matrixTransform;
256                         matrixTransform = new osg::MatrixTransform(makeZUpFrame(SGGeod::fromDegM(b._lon, b._lat, b._elev)));
257                         matrixTransform->setName("rotateBuildings");
258                         matrixTransform->setDataVariance(osg::Object::STATIC);
259                         matrixTransform->addChild(createRandomBuildings(bbList, osg::Matrix::identity(), _options));
260                         group->addChild(matrixTransform);
261 
262                         std::for_each(bbList.begin(), bbList.end(), [](SGBuildingBin* bb) {
263                             delete bb;
264                         });
265                     }
266                 }
267             }
268 
269             return group.release();
270         }
271 
272         mt _seed;
273         std::list<_ObjectStatic> _objectStaticList;
274         std::list<_Sign> _signList;
275         std::list<_BuildingList> _buildingList;
276 
277         /// The original options to use for this bunch of models
278         osg::ref_ptr<SGReaderWriterOptions> _options;
279         SGBucket _bucket;
280     };
281 
_ModelBinsimgear::ReaderWriterSTG::_ModelBin282     _ModelBin() :
283         _object_range_bare(SG_OBJECT_RANGE_BARE),
284         _object_range_rough(SG_OBJECT_RANGE_ROUGH),
285         _object_range_detailed(SG_OBJECT_RANGE_DETAILED),
286         _foundBase(false)
287     { }
288 
sharedOptionssimgear::ReaderWriterSTG::_ModelBin289     SGReaderWriterOptions* sharedOptions(const std::string& filePath, const osgDB::Options* options)
290     {
291         osg::ref_ptr<SGReaderWriterOptions> sharedOptions;
292         sharedOptions = SGReaderWriterOptions::copyOrCreate(options);
293         sharedOptions->getDatabasePathList().clear();
294 
295         SGPath path = filePath;
296         path.append(".."); path.append(".."); path.append("..");
297         sharedOptions->getDatabasePathList().push_back(path.utf8Str());
298 
299         // ensure Models directory synced via TerraSync is searched before the copy in
300         // FG_ROOT, so that updated models can be used.
301         std::string terrasync_root = options->getPluginStringData("SimGear::TERRASYNC_ROOT");
302         if (!terrasync_root.empty()) {
303             sharedOptions->getDatabasePathList().push_back(terrasync_root);
304         }
305 
306         std::string fg_root = options->getPluginStringData("SimGear::FG_ROOT");
307         sharedOptions->getDatabasePathList().push_back(fg_root);
308 
309         // TODO how should we handle this for OBJECT_SHARED?
310         sharedOptions->setModelData
311         (
312             sharedOptions->getModelData()
313           ? sharedOptions->getModelData()->clone()
314           : 0
315         );
316 
317         return sharedOptions.release();
318     }
staticOptionssimgear::ReaderWriterSTG::_ModelBin319     SGReaderWriterOptions* staticOptions(const std::string& filePath, const osgDB::Options* options)
320     {
321         osg::ref_ptr<SGReaderWriterOptions> staticOptions;
322         staticOptions = SGReaderWriterOptions::copyOrCreate(options);
323         staticOptions->getDatabasePathList().clear();
324 
325         staticOptions->getDatabasePathList().push_back(filePath);
326         staticOptions->setObjectCacheHint(osgDB::Options::CACHE_NONE);
327 
328         // Every model needs its own SGModelData to ensure load/unload is
329         // working properly
330         staticOptions->setModelData
331         (
332             staticOptions->getModelData()
333           ? staticOptions->getModelData()->clone()
334           : 0
335         );
336 
337         return staticOptions.release();
338     }
339 
elevationsimgear::ReaderWriterSTG::_ModelBin340     double elevation(osg::Group& group, const SGGeod& geod)
341     {
342         SGVec3d start = SGVec3d::fromGeod(SGGeod::fromGeodM(geod, 10000));
343         SGVec3d end = SGVec3d::fromGeod(SGGeod::fromGeodM(geod, -1000));
344 
345         osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector;
346         intersector = new osgUtil::LineSegmentIntersector(toOsg(start), toOsg(end));
347         osgUtil::IntersectionVisitor visitor(intersector.get());
348         group.accept(visitor);
349 
350         if (!intersector->containsIntersections())
351             return 0;
352 
353         SGVec3d cart = toSG(intersector->getFirstIntersection().getWorldIntersectPoint());
354         return SGGeod::fromCart(cart).getElevationM();
355     }
356 
checkInsideBucketsimgear::ReaderWriterSTG::_ModelBin357     void checkInsideBucket(const SGPath& absoluteFileName, float lon, float lat) {
358         SGBucket bucket = bucketIndexFromFileName(absoluteFileName.file_base().c_str());
359         SGBucket correctBucket = SGBucket( SGGeod::fromDeg(lon, lat));
360 
361         if (bucket != correctBucket) {
362           SG_LOG( SG_TERRAIN, SG_DEV_WARN, absoluteFileName
363                   << ": Object at " << lon << ", " << lat <<
364                   " in incorrect bucket (" << bucket << ") - should be in " <<
365                   correctBucket.gen_index_str() << " (" << correctBucket << ")");
366         }
367     }
368 
readsimgear::ReaderWriterSTG::_ModelBin369     bool read(const SGPath& absoluteFileName, const osgDB::Options* options)
370     {
371         if (!absoluteFileName.exists()) {
372             return false;
373         }
374 
375         sg_gzifstream stream(absoluteFileName);
376         if (!stream.is_open()) {
377             return false;
378         }
379 
380         // starting with 2018.3 we will use deltas rather than absolutes as it is more intuitive for the user
381         // and somewhat easier to visualise
382         double detailedRange = atof(options->getPluginStringData("SimGear::LOD_RANGE_DETAILED").c_str());
383         double bareRangeDelta = atof(options->getPluginStringData("SimGear::LOD_RANGE_BARE").c_str());
384         double roughRangeDelta = atof(options->getPluginStringData("SimGear::LOD_RANGE_ROUGH").c_str());
385 
386         // Determine object ranges.  Mesh size of 2000mx2000m needs to be accounted for.
387         _object_range_detailed = 1414.0f + detailedRange;
388         _object_range_bare = _object_range_detailed + bareRangeDelta;
389         _object_range_rough = _object_range_detailed + roughRangeDelta;
390 
391         SG_LOG(SG_TERRAIN, SG_INFO, "Loading stg file " << absoluteFileName);
392 
393         std::string filePath = osgDB::getFilePath(absoluteFileName.utf8Str());
394 
395         // Bucket provides a consistent seed
396         // so we have consistent set of pseudo-random numbers for each STG file
397         mt seed;
398         int bucket = atoi(absoluteFileName.file_base().c_str());
399         mt_init(&seed, bucket);
400 
401         // do only load airport btg files.
402         bool onlyAirports = options->getPluginStringData("SimGear::FG_ONLY_AIRPORTS") == "ON";
403         // do only load terrain btg files
404         bool onlyTerrain = options->getPluginStringData("SimGear::FG_ONLY_TERRAIN") == "ON";
405 
406         while (!stream.eof()) {
407             // read a line
408             std::string line;
409             std::getline(stream, line);
410 
411             // strip comments
412             std::string::size_type hash_pos = line.find('#');
413             if (hash_pos != std::string::npos)
414                 line.resize(hash_pos);
415 
416             // and process further
417             std::stringstream in(line);
418 
419             std::string token;
420             in >> token;
421 
422             // No comment
423             if (token.empty())
424                 continue;
425 
426             // Then there is always a name
427             std::string name;
428             in >> name;
429 
430             SGPath path = filePath;
431             path.append(name);
432 
433             if (token == "OBJECT_BASE") {
434                 // Load only once (first found)
435                 SG_LOG( SG_TERRAIN, SG_BULK, "    " << token << " " << name );
436                 _foundBase = true;
437                 if (!onlyAirports || isAirportBtg(name)) {
438                     _Object obj;
439                     obj._errorLocation = absoluteFileName;
440                     obj._token = token;
441                     obj._name = path.utf8Str();
442                     obj._options = staticOptions(filePath, options);
443                     _objectList.push_back(obj);
444                 }
445 
446             } else if (token == "OBJECT") {
447                 if (!onlyAirports || isAirportBtg(name)) {
448                     _Object obj;
449                     obj._errorLocation = absoluteFileName;
450                     obj._token = token;
451                     obj._name = path.utf8Str();
452                     obj._options = staticOptions(filePath, options);
453                     _objectList.push_back(obj);
454                 }
455 
456             } else if (!onlyTerrain) {
457                 // Load non-terrain objects
458 
459 								// Determine an appropriate range for the object, which has some randomness
460 								double range = _object_range_rough;
461 								double lrand = mt_rand(&seed);
462 								if      (lrand < 0.1) range = range * 2.0;
463 								else if (lrand < 0.4) range = range * 1.5;
464 
465                 if (token == "OBJECT_STATIC" || token == "OBJECT_STATIC_AGL") {
466 										osg::ref_ptr<SGReaderWriterOptions> opt;
467 										opt = staticOptions(filePath, options);
468 										if (SGPath(name).lower_extension() == "ac")
469 												opt->setInstantiateEffects(true);
470 										else
471 												opt->setInstantiateEffects(false);
472 										_ObjectStatic obj;
473                                         opt->addErrorContext("terrain-stg", absoluteFileName.utf8Str());
474                                         obj._errorLocation = absoluteFileName;
475                                         obj._token = token;
476                                         obj._name = name;
477                                         obj._agl = (token == "OBJECT_STATIC_AGL");
478                                         obj._proxy = true;
479                                         in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch >> obj._roll;
480                                         obj._range = range;
481                                         obj._options = opt;
482                     checkInsideBucket(absoluteFileName, obj._lon, obj._lat);
483                     _objectStaticList.push_back(obj);
484                 } else if (token == "OBJECT_SHARED" || token == "OBJECT_SHARED_AGL") {
485 										osg::ref_ptr<SGReaderWriterOptions> opt;
486 										opt = sharedOptions(filePath, options);
487 										if (SGPath(name).lower_extension() == "ac")
488 												opt->setInstantiateEffects(true);
489 										else
490 												opt->setInstantiateEffects(false);
491 										_ObjectStatic obj;
492 										obj._errorLocation = absoluteFileName;
493 										obj._token = token;
494 										obj._name = name;
495 										obj._agl = (token == "OBJECT_SHARED_AGL");
496 										obj._proxy = false;
497 										in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch >> obj._roll;
498 										obj._range = range;
499 										obj._options = opt;
500                     checkInsideBucket(absoluteFileName, obj._lon, obj._lat);
501                     _objectStaticList.push_back(obj);
502                 } else if (token == "OBJECT_SIGN" || token == "OBJECT_SIGN_AGL") {
503 										_Sign sign;
504 										sign._token = token;
505 										sign._name = name;
506 										sign._agl = (token == "OBJECT_SIGN_AGL");
507 										in >> sign._lon >> sign._lat >> sign._elev >> sign._hdg >> sign._size;
508 										_signList.push_back(sign);
509                 } else if (token == BUILDING_ROUGH || token == BUILDING_DETAILED ||
510                            token == ROAD_ROUGH     || token == ROAD_DETAILED     ||
511                            token == RAILWAY_ROUGH  || token == RAILWAY_DETAILED)   {
512                     osg::ref_ptr<SGReaderWriterOptions> opt;
513                     opt = staticOptions(filePath, options);
514                     _ObjectStatic obj;
515 
516                     opt->setInstantiateEffects(false);
517                     opt->addErrorContext("terrain-stg", absoluteFileName.utf8Str());
518 
519                     if (SGPath(name).lower_extension() == "ac") {
520                       // Generate material/Effects lookups for raw models, i.e.
521                       // those not wrapped in an XML while will include Effects
522                       opt->setInstantiateMaterialEffects(true);
523 
524                       if (token == BUILDING_ROUGH || token == BUILDING_DETAILED) {
525                         opt->setMaterialName("OSM_Building");
526                       } else if (token == ROAD_ROUGH || token == ROAD_DETAILED) {
527                         opt->setMaterialName("OSM_Road");
528                       } else if (token == RAILWAY_ROUGH || token == RAILWAY_DETAILED) {
529                         opt->setMaterialName("OSM_Railway");
530                       } else {
531                         // Programming error.  If we get here then someone has added a verb to the list of
532                         // tokens above but not in this set of if-else statements.
533                         SG_LOG(SG_TERRAIN, SG_ALERT, "Programming Error - STG token without material branch");
534                       }
535                     }
536 
537                     obj._errorLocation = absoluteFileName;
538                     obj._token = token;
539                     obj._name = name;
540                     obj._agl = false;
541                     obj._proxy = true;
542                     in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch >> obj._roll;
543 
544                     opt->setLocation(obj._lon, obj._lat);
545                     if (token == BUILDING_DETAILED || token == ROAD_DETAILED || token == RAILWAY_DETAILED ) {
546 											// Apply a lower LOD range if this is a detailed mesh
547 											range = _object_range_detailed;
548 											double lrand = mt_rand(&seed);
549 											if      (lrand < 0.1) range = range * 2.0;
550 											else if (lrand < 0.4) range = range * 1.5;
551 										}
552 
553                     obj._range = range;
554 
555                     obj._options = opt;
556                     checkInsideBucket(absoluteFileName, obj._lon, obj._lat);
557                     _objectStaticList.push_back(obj);
558                 } else if (token == BUILDING_LIST) {
559                   _BuildingList buildinglist;
560                   buildinglist._filename = path.utf8Str();
561                   in >> buildinglist._material_name >> buildinglist._lon >> buildinglist._lat >> buildinglist._elev;
562                   checkInsideBucket(absoluteFileName, buildinglist._lon, buildinglist._lat);
563                   _buildingListList.push_back(buildinglist);
564                   //SG_LOG(SG_TERRAIN, SG_ALERT, "Building list: " << buildinglist._filename << " " << buildinglist._material_name << " " << buildinglist._lon << " " << buildinglist._lat);
565                 } else {
566                     // Check registered callback for token. Keep lock until callback completed to make sure it will not be
567                     // executed after a thread successfully executed removeSTGObjectHandler()
568                     {
569                         OpenThreads::ScopedLock<OpenThreads::Mutex> lock(globalStgObjectCallbackLock);
570                         STGObjectCallback callback = globalStgObjectCallbacks[token];
571 
572                         if (callback != nullptr) {
573                             _ObjectStatic obj;
574                             // pitch and roll are not common, so passed in "restofline" only
575                             in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg;
576                             string_list restofline;
577                             std::string buf;
578                             while (in >> buf) {
579                                 restofline.push_back(buf);
580                             }
581                             callback(token,name, SGGeod::fromDegM(obj._lon, obj._lat, obj._elev), obj._hdg,restofline);
582                         } else {
583                             // SG_LOG( SG_TERRAIN, SG_ALERT, absoluteFileName << ": Unknown token '" << token << "'" );
584                             simgear::reportFailure(simgear::LoadFailure::Misconfigured, simgear::ErrorCode::BTGLoad,
585                                                    "Unknown STG token:" + token, absoluteFileName);
586                         }
587                     }
588                 }
589             }
590         }
591 
592         return true;
593     }
594 
loadsimgear::ReaderWriterSTG::_ModelBin595     osg::Node* load(const SGBucket& bucket, const osgDB::Options* opt)
596     {
597         osg::ref_ptr<SGReaderWriterOptions> options;
598         options = SGReaderWriterOptions::copyOrCreate(opt);
599         float pagedLODExpiry = atoi(options->getPluginStringData("SimGear::PAGED_LOD_EXPIRY").c_str());
600 
601         osg::ref_ptr<osg::Group> terrainGroup = new osg::Group;
602         terrainGroup->setDataVariance(osg::Object::STATIC);
603         std::string terrain_name = string("terrain ").append(bucket.gen_index_str());
604         terrainGroup->setName(terrain_name);
605 
606         simgear::ErrorReportContext ec{"terrain-bucket", bucket.gen_index_str()};
607 
608         if (_foundBase) {
609             for (auto stgObject : _objectList) {
610                 osg::ref_ptr<osg::Node> node;
611                 simgear::ErrorReportContext ec("terrain-stg", stgObject._errorLocation.utf8Str());
612 
613 #if OSG_VERSION_LESS_THAN(3,4,0)
614                 node = osgDB::readNodeFile(stgObject._name, stgObject._options.get());
615 #else
616                 node = osgDB::readRefNodeFile(stgObject._name, stgObject._options.get());
617 #endif
618                 if (!node.valid()) {
619                     SG_LOG(SG_TERRAIN, SG_ALERT, stgObject._errorLocation << ": Failed to load "
620                            << stgObject._token << " '" << stgObject._name << "'");
621                     continue;
622                 }
623                 terrainGroup->addChild(node.get());
624             }
625         } else {
626             SG_LOG(SG_TERRAIN, SG_INFO, "  Generating ocean tile: " << bucket.gen_base_path() << "/" << bucket.gen_index_str());
627 
628             osg::Node* node = SGOceanTile(bucket, options->getMaterialLib());
629             if (node) {
630                 node->setName("SGOceanTile");
631                 terrainGroup->addChild(node);
632             } else {
633                 SG_LOG( SG_TERRAIN, SG_ALERT,
634                         "Warning: failed to generate ocean tile!" );
635             }
636         }
637         for (std::list<_ObjectStatic>::iterator i = _objectStaticList.begin(); i != _objectStaticList.end(); ++i) {
638             if (!i->_agl)
639                 continue;
640             i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat));
641         }
642 
643         for (std::list<_Sign>::iterator i = _signList.begin(); i != _signList.end(); ++i) {
644             if (!i->_agl)
645                 continue;
646             i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat));
647         }
648 
649         if (_objectStaticList.empty() && _signList.empty() && (_buildingListList.size() == 0)) {
650             // The simple case, just return the terrain group
651             return terrainGroup.release();
652         } else {
653             osg::PagedLOD* pagedLOD = new osg::PagedLOD;
654             pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER);
655             std::string name = string("pagedObjectLOD ").append(bucket.gen_index_str());
656             pagedLOD->setName(name);
657 
658             // This should be visible in any case.
659             // If this is replaced by some lower level of detail, the parent LOD node handles this.
660             pagedLOD->addChild(terrainGroup, 0, std::numeric_limits<float>::max());
661             pagedLOD->setMinimumExpiryTime(0, pagedLODExpiry);
662 
663             // we just need to know about the read file callback that itself holds the data
664             osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback;
665             readFileCallback->_objectStaticList = _objectStaticList;
666             readFileCallback->_buildingList = _buildingListList;
667             readFileCallback->_signList = _signList;
668             readFileCallback->_options = options;
669             readFileCallback->_bucket = bucket;
670             osg::ref_ptr<osgDB::Options> callbackOptions = new osgDB::Options;
671             callbackOptions->setReadFileCallback(readFileCallback.get());
672             pagedLOD->setDatabaseOptions(callbackOptions.get());
673 
674             pagedLOD->setFileName(pagedLOD->getNumChildren(), "Dummy name - use the stored data in the read file callback");
675 
676             // Objects may end up displayed up to 2x the object range.
677             pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough);
678             pagedLOD->setMinimumExpiryTime(pagedLOD->getNumChildren(), pagedLODExpiry);
679             pagedLOD->setRadius(SG_TILE_RADIUS);
680             SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() );
681             SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Range: " << (2.0 * _object_range_rough));
682             SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Radius: " << SG_TILE_RADIUS);
683             return pagedLOD;
684         }
685     }
686 
687     double _object_range_bare;
688     double _object_range_rough;
689     double _object_range_detailed;
690     bool _foundBase;
691     std::list<_Object> _objectList;
692     std::list<_ObjectStatic> _objectStaticList;
693     std::list<_Sign> _signList;
694     std::list<_BuildingList> _buildingListList;
695 };
696 
ReaderWriterSTG()697 ReaderWriterSTG::ReaderWriterSTG()
698 {
699     supportsExtension("stg", "SimGear stg database format");
700 }
701 
~ReaderWriterSTG()702 ReaderWriterSTG::~ReaderWriterSTG()
703 {
704 }
705 
className() const706 const char* ReaderWriterSTG::className() const
707 {
708     return "STG Database reader";
709 }
710 
711 osgDB::ReaderWriter::ReadResult
readNode(const std::string & fileName,const osgDB::Options * options) const712 ReaderWriterSTG::readNode(const std::string& fileName, const osgDB::Options* options) const
713 {
714     _ModelBin modelBin;
715     SGBucket bucket(bucketIndexFromFileName(fileName));
716     simgear::ErrorReportContext ec("terrain-bucket", bucket.gen_index_str());
717 
718     // We treat 123.stg different than ./123.stg.
719     // The difference is that ./123.stg as well as any absolute path
720     // really loads the given stg file and only this.
721     // In contrast 123.stg uses the search paths to load a set of stg
722     // files spread across the scenery directories.
723     if (osgDB::getSimpleFileName(fileName) != fileName) {
724         simgear::ErrorReportContext ec("terrain-stg", fileName);
725         if (!modelBin.read(fileName, options))
726             return ReadResult::FILE_NOT_FOUND;
727     }
728 
729     // For stg meta files, we need options for the search path.
730     if (!options) {
731         return ReadResult::FILE_NOT_FOUND;
732     }
733 
734     const auto sgOpts = dynamic_cast<const SGReaderWriterOptions*>(options);
735     if (!sgOpts || sgOpts->getSceneryPathSuffixes().empty()) {
736         SG_LOG(SG_TERRAIN, SG_ALERT, "Loading tile " << fileName << ", no scenery path suffixes were configured so giving up");
737         return ReadResult::FILE_NOT_FOUND;
738     }
739 
740     SG_LOG(SG_TERRAIN, SG_INFO, "Loading tile " << fileName);
741 
742     std::string basePath = bucket.gen_base_path();
743 
744     // Stop scanning paths once an object base is found
745     // But do load all STGs at the same level (i.e from the same scenery path)
746     const osgDB::FilePathList& filePathList = options->getDatabasePathList();
747     for (auto path : filePathList) {
748         if (modelBin._foundBase) {
749             break;
750         }
751 
752         SGPath base(path);
753         // check for non-suffixed file, and warn.
754         SGPath pathWithoutSuffix = base / basePath / fileName;
755         if (pathWithoutSuffix.exists()) {
756             SG_LOG(SG_TERRAIN, SG_ALERT, "Found scenery file " << pathWithoutSuffix << " in scenery path " << path
757                    << ".\nScenery paths without type subdirectories are no longer supported, please move thse files\n"
758                    << "into a an appropriate subdirectory, for example:" << base / "Objects" / basePath / fileName);
759         }
760 
761         for (auto suffix : sgOpts->getSceneryPathSuffixes()) {
762             const auto p = base / suffix / basePath / fileName;
763             simgear::ErrorReportContext ec("terrain-stg", p.utf8Str());
764             modelBin.read(p, options);
765         }
766     }
767 
768     return modelBin.load(bucket, options);
769 }
770 
771 
setSTGObjectHandler(const std::string & token,STGObjectCallback callback)772 void ReaderWriterSTG::setSTGObjectHandler(const std::string &token, STGObjectCallback callback)
773 {
774     OpenThreads::ScopedLock<OpenThreads::Mutex> lock(globalStgObjectCallbackLock);
775     globalStgObjectCallbacks[token] = callback;
776 }
777 
removeSTGObjectHandler(const std::string & token,STGObjectCallback callback)778 void ReaderWriterSTG::removeSTGObjectHandler(const std::string &token, STGObjectCallback callback)
779 {
780     OpenThreads::ScopedLock<OpenThreads::Mutex> lock(globalStgObjectCallbackLock);
781     globalStgObjectCallbacks.erase(token);
782 }
783 }
784