]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tgdb/ReaderWriterSTG.cxx
Fixed a crash: the singleton needs to be instantiated the first time SGCommandMgr...
[simgear.git] / simgear / scene / tgdb / ReaderWriterSTG.cxx
1 // tileentry.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/math/SGGeometry.hxx>
42 #include <simgear/bucket/newbucket.hxx>
43 #include <simgear/debug/logstream.hxx>
44 #include <simgear/misc/sgstream.hxx>
45 #include <simgear/scene/util/OptionsReadFileCallback.hxx>
46 #include <simgear/scene/util/OsgMath.hxx>
47 #include <simgear/scene/util/RenderConstants.hxx>
48 #include <simgear/scene/util/SGReaderWriterOptions.hxx>
49 #include <simgear/scene/tgdb/apt_signs.hxx>
50 #include <simgear/scene/tgdb/obj.hxx>
51
52 #include "SGOceanTile.hxx"
53
54 namespace simgear {
55
56 /// Ok, this is a hack - we do not exactly know if it's an airport or not.
57 /// This feature might also vanish again later. This is currently to
58 /// support testing an external ai component that just loads the the airports
59 /// and supports ground queries on only these areas.
60 static bool isAirportBtg(const std::string& name)
61 {
62     if (name.size() < 8)
63         return false;
64     if (name.substr(4, 8) != ".btg")
65         return false;
66     for (unsigned i = 0; i < 4; ++i) {
67         if (name[i] < 'A' || 'Z' < name[i])
68             continue;
69         return true;
70     }
71     return false;
72 }
73
74 static SGBucket bucketIndexFromFileName(const std::string& fileName)
75 {
76   // Extract the bucket from the filename
77   std::istringstream ss(osgDB::getNameLessExtension(fileName));
78   long index;
79   ss >> index;
80   if (ss.fail())
81     return SGBucket();
82   
83   return SGBucket(index);
84 }
85
86 struct ReaderWriterSTG::_ModelBin {
87     struct _Object {
88         std::string _errorLocation;
89         std::string _token;
90         std::string _name;
91         osg::ref_ptr<SGReaderWriterOptions> _options;
92     };
93     struct _ObjectStatic {
94         _ObjectStatic() : _agl(false), _proxy(false), _lon(0), _lat(0), _elev(0), _hdg(0), _pitch(0), _roll(0) { }
95         std::string _errorLocation;
96         std::string _token;
97         std::string _name;
98         bool _agl;
99         bool _proxy;
100         double _lon, _lat, _elev;
101         double _hdg, _pitch, _roll;
102         osg::ref_ptr<SGReaderWriterOptions> _options;
103     };
104     struct _Sign {
105         _Sign() : _agl(false), _lon(0), _lat(0), _elev(0), _hdg(0), _size(-1) { }
106         std::string _errorLocation;
107         std::string _token;
108         std::string _name;
109         bool _agl;
110         double _lon, _lat, _elev;
111         double _hdg;
112         int _size;
113     };
114
115     class DelayLoadReadFileCallback : public OptionsReadFileCallback {
116     public:
117         virtual osgDB::ReaderWriter::ReadResult
118         readNode(const std::string&, const osgDB::Options*)
119         {
120             osg::ref_ptr<osg::Group> group = new osg::Group;
121             group->setName("STG-group-A");
122             group->setDataVariance(osg::Object::STATIC);
123             
124             for (std::list<_ObjectStatic>::iterator i = _objectStaticList.begin(); i != _objectStaticList.end(); ++i) {
125                 osg::ref_ptr<osg::Node> node;
126                 if (i->_proxy)  {
127                     osg::ref_ptr<osg::ProxyNode> proxy = new osg::ProxyNode;
128                     proxy->setName("proxyNode");
129                     proxy->setLoadingExternalReferenceMode(osg::ProxyNode::DEFER_LOADING_TO_DATABASE_PAGER);
130                     proxy->setFileName(0, i->_name);
131                     proxy->setDatabaseOptions(i->_options.get());
132                     node = proxy;
133                 } else {
134                     node = osgDB::readRefNodeFile(i->_name, i->_options.get());
135                     if (!node.valid()) {
136                         SG_LOG(SG_TERRAIN, SG_ALERT, i->_errorLocation << ": Failed to load "
137                                << i->_token << " '" << i->_name << "'");
138                         continue;
139                     }
140                 }
141                 if (SGPath(i->_name).lower_extension() == "ac")
142                     node->setNodeMask(~simgear::MODELLIGHT_BIT);
143                 
144                 osg::Matrix matrix;
145                 matrix = makeZUpFrame(SGGeod::fromDegM(i->_lon, i->_lat, i->_elev));
146                 matrix.preMultRotate(osg::Quat(SGMiscd::deg2rad(i->_hdg), osg::Vec3(0, 0, 1)));
147                 matrix.preMultRotate(osg::Quat(SGMiscd::deg2rad(i->_pitch), osg::Vec3(0, 1, 0)));
148                 matrix.preMultRotate(osg::Quat(SGMiscd::deg2rad(i->_roll), osg::Vec3(1, 0, 0)));
149                 
150                 osg::MatrixTransform* matrixTransform;
151                 matrixTransform = new osg::MatrixTransform(matrix);
152                 matrixTransform->setName("positionStaticObject");
153                 matrixTransform->setDataVariance(osg::Object::STATIC);
154                 matrixTransform->addChild(node.get());
155                 group->addChild(matrixTransform);
156             }
157             
158             simgear::AirportSignBuilder signBuilder(_options->getMaterialLib(), _bucket.get_center());
159             for (std::list<_Sign>::iterator i = _signList.begin(); i != _signList.end(); ++i)
160                 signBuilder.addSign(SGGeod::fromDegM(i->_lon, i->_lat, i->_elev), i->_hdg, i->_name, i->_size);
161             if (signBuilder.getSignsGroup())
162                 group->addChild(signBuilder.getSignsGroup());
163             
164             return group.release();
165         }
166         
167         std::list<_ObjectStatic> _objectStaticList;
168         std::list<_Sign> _signList;
169         
170         /// The original options to use for this bunch of models
171         osg::ref_ptr<SGReaderWriterOptions> _options;
172         SGBucket _bucket;
173     };
174     
175     _ModelBin() :
176         _foundBase(false)
177     { }
178
179     SGReaderWriterOptions* sharedOptions(const std::string& filePath, const osgDB::Options* options)
180     {
181         osg::ref_ptr<SGReaderWriterOptions> sharedOptions;
182         sharedOptions = SGReaderWriterOptions::copyOrCreate(options);
183         sharedOptions->getDatabasePathList().clear();
184
185         SGPath path = filePath;
186         path.append(".."); path.append(".."); path.append("..");
187         sharedOptions->getDatabasePathList().push_back(path.str());
188         
189         // ensure Models directory synced via TerraSync is searched before the copy in
190         // FG_ROOT, so that updated models can be used.
191         std::string terrasync_root = options->getPluginStringData("SimGear::TERRASYNC_ROOT");
192         if (!terrasync_root.empty()) {
193             sharedOptions->getDatabasePathList().push_back(terrasync_root);
194         }
195         
196         std::string fg_root = options->getPluginStringData("SimGear::FG_ROOT");
197         sharedOptions->getDatabasePathList().push_back(fg_root);
198
199         // TODO how should we handle this for OBJECT_SHARED?
200         sharedOptions->setModelData
201         (
202             sharedOptions->getModelData()
203           ? sharedOptions->getModelData()->clone()
204           : 0
205         );
206
207         return sharedOptions.release();
208     }
209     SGReaderWriterOptions* staticOptions(const std::string& filePath, const osgDB::Options* options)
210     {
211         osg::ref_ptr<SGReaderWriterOptions> staticOptions;
212         staticOptions = SGReaderWriterOptions::copyOrCreate(options);
213         staticOptions->getDatabasePathList().clear();
214
215         staticOptions->getDatabasePathList().push_back(filePath);
216         staticOptions->setObjectCacheHint(osgDB::Options::CACHE_NONE);
217
218         // Every model needs its own SGModelData to ensure load/unload is
219         // working properly
220         staticOptions->setModelData
221         (
222             staticOptions->getModelData()
223           ? staticOptions->getModelData()->clone()
224           : 0
225         );
226
227         return staticOptions.release();
228     }
229
230     double elevation(osg::Group& group, const SGGeod& geod)
231     {
232         SGVec3d start = SGVec3d::fromGeod(SGGeod::fromGeodM(geod, 10000));
233         SGVec3d end = SGVec3d::fromGeod(SGGeod::fromGeodM(geod, -1000));
234         
235         osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector;
236         intersector = new osgUtil::LineSegmentIntersector(toOsg(start), toOsg(end));
237         osgUtil::IntersectionVisitor visitor(intersector.get());
238         group.accept(visitor);
239         
240         if (!intersector->containsIntersections())
241             return 0;
242         
243         SGVec3d cart = toSG(intersector->getFirstIntersection().getWorldIntersectPoint());
244         return SGGeod::fromCart(cart).getElevationM();
245     }
246     
247     bool read(const std::string& absoluteFileName, const osgDB::Options* options)
248     {
249         if (absoluteFileName.empty())
250             return false;
251
252         sg_gzifstream stream(absoluteFileName);
253         if (!stream.is_open())
254             return false;
255
256         SG_LOG(SG_TERRAIN, SG_INFO, "Loading stg file " << absoluteFileName);
257     
258         std::string filePath = osgDB::getFilePath(absoluteFileName);
259
260         // do only load airport btg files.
261         bool onlyAirports = options->getPluginStringData("SimGear::FG_ONLY_AIRPORTS") == "ON";
262         // do only load terrain btg files
263         bool onlyTerrain = options->getPluginStringData("SimGear::FG_ONLY_TERRAIN") == "ON";
264         
265         while (!stream.eof()) {
266             // read a line
267             std::string line;
268             std::getline(stream, line);
269             
270             // strip comments
271             std::string::size_type hash_pos = line.find('#');
272             if (hash_pos != std::string::npos)
273                 line.resize(hash_pos);
274             
275             // and process further
276             std::stringstream in(line);
277             
278             std::string token;
279             in >> token;
280             
281             // No comment
282             if (token.empty())
283                 continue;
284             
285             // Then there is always a name
286             std::string name;
287             in >> name;
288             
289             SGPath path = filePath;
290             path.append(name);
291             
292             if (token == "OBJECT_BASE") {
293                 // Load only once (first found)
294                 SG_LOG( SG_TERRAIN, SG_BULK, "    " << token << " " << name );
295                 _foundBase = true;
296                 if (!onlyAirports || isAirportBtg(name)) {
297                     _Object obj;
298                     obj._errorLocation = absoluteFileName;
299                     obj._token = token;
300                     obj._name = path.str();
301                     obj._options = staticOptions(filePath, options);
302                     _objectList.push_back(obj);
303                 }
304                 
305             } else if (token == "OBJECT") {
306                 if (!onlyAirports || isAirportBtg(name)) {
307                     _Object obj;
308                     obj._errorLocation = absoluteFileName;
309                     obj._token = token;
310                     obj._name = path.str();
311                     obj._options = staticOptions(filePath, options);
312                     _objectList.push_back(obj);
313                 }
314                 
315             } else {
316                 // Always OK to load
317                 if (token == "OBJECT_STATIC" || token == "OBJECT_STATIC_AGL") {
318                     if (!onlyTerrain) {
319                         osg::ref_ptr<SGReaderWriterOptions> opt;
320                         opt = staticOptions(filePath, options);
321                         if (SGPath(name).lower_extension() == "ac")
322                             opt->setInstantiateEffects(true);
323                         else
324                             opt->setInstantiateEffects(false);
325                         _ObjectStatic obj;
326                         obj._errorLocation = absoluteFileName;
327                         obj._token = token;
328                         obj._name = name;
329                         obj._agl = (token == "OBJECT_STATIC_AGL");
330                         obj._proxy = true;
331                         in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch >> obj._roll;
332                         obj._options = opt;
333                         _objectStaticList.push_back(obj);
334                     }
335                         
336                 } else if (token == "OBJECT_SHARED" || token == "OBJECT_SHARED_AGL") {
337                     if (!onlyTerrain) {
338                         osg::ref_ptr<SGReaderWriterOptions> opt;
339                         opt = sharedOptions(filePath, options);
340                         if (SGPath(name).lower_extension() == "ac")
341                             opt->setInstantiateEffects(true);
342                         else
343                             opt->setInstantiateEffects(false);
344                         _ObjectStatic obj;
345                         obj._errorLocation = absoluteFileName;
346                         obj._token = token;
347                         obj._name = name;
348                         obj._agl = (token == "OBJECT_SHARED_AGL");
349                         obj._proxy = false;
350                         in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch >> obj._roll;
351                         obj._options = opt;
352                         _objectStaticList.push_back(obj);
353                     }
354
355                 } else if (token == "OBJECT_SIGN" || token == "OBJECT_SIGN_AGL") {
356                     if (!onlyTerrain) {
357                         _Sign sign;
358                         sign._token = token;
359                         sign._name = name;
360                         sign._agl = (token == "OBJECT_SIGN_AGL");
361                         in >> sign._lon >> sign._lat >> sign._elev >> sign._hdg >> sign._size;
362                         _signList.push_back(sign);
363                     }
364
365                 } else {
366                     SG_LOG( SG_TERRAIN, SG_ALERT, absoluteFileName
367                             << ": Unknown token '" << token << "'" );
368                 }
369             }
370         }
371         
372         return true;
373     }
374     
375     osg::Node* load(const SGBucket& bucket, const osgDB::Options* opt)
376     {
377         osg::ref_ptr<SGReaderWriterOptions> options;
378         options = SGReaderWriterOptions::copyOrCreate(opt);
379
380         osg::ref_ptr<osg::Group> terrainGroup = new osg::Group;
381         terrainGroup->setDataVariance(osg::Object::STATIC);
382         terrainGroup->setName("terrain");
383         
384         if (_foundBase) {
385             for (std::list<_Object>::iterator i = _objectList.begin(); i != _objectList.end(); ++i) {
386                 osg::ref_ptr<osg::Node> node;
387                 node = osgDB::readRefNodeFile(i->_name, i->_options.get());
388                 if (!node.valid()) {
389                     SG_LOG(SG_TERRAIN, SG_ALERT, i->_errorLocation << ": Failed to load "
390                            << i->_token << " '" << i->_name << "'");
391                     continue;
392                 }
393                 terrainGroup->addChild(node.get());
394             }
395         } else {
396             SG_LOG(SG_TERRAIN, SG_INFO, "  Generating ocean tile: " << bucket.gen_base_path() << "/" << bucket.gen_index_str());
397             
398             osg::Node* node = SGOceanTile(bucket, options->getMaterialLib());
399             if (node) {
400                 node->setName("SGOceanTile");
401                 terrainGroup->addChild(node);
402             } else {
403                 SG_LOG( SG_TERRAIN, SG_ALERT,
404                         "Warning: failed to generate ocean tile!" );
405             }
406         }
407
408         for (std::list<_ObjectStatic>::iterator i = _objectStaticList.begin(); i != _objectStaticList.end(); ++i) {
409             if (!i->_agl)
410                 continue;
411             i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat));
412         }
413         
414         for (std::list<_Sign>::iterator i = _signList.begin(); i != _signList.end(); ++i) {
415             if (!i->_agl)
416                 continue;
417             i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat));
418         }
419
420         if (_objectStaticList.empty() && _signList.empty()) {
421             // The simple case, just return the terrain group
422             return terrainGroup.release();
423         } else {
424             osg::PagedLOD* pagedLOD = new osg::PagedLOD;
425             pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER);
426             pagedLOD->setName("pagedObjectLOD");
427             
428             // This should be visible in any case.
429             // If this is replaced by some lower level of detail, the parent LOD node handles this.
430             pagedLOD->addChild(terrainGroup, 0, std::numeric_limits<float>::max());
431
432             // we just need to know about the read file callback that itself holds the data
433             osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback;
434             readFileCallback->_objectStaticList = _objectStaticList;
435             readFileCallback->_signList = _signList;
436             readFileCallback->_options = options;
437             readFileCallback->_bucket = bucket;
438             osg::ref_ptr<osgDB::Options> callbackOptions = new osgDB::Options;
439             callbackOptions->setReadFileCallback(readFileCallback.get());
440             pagedLOD->setDatabaseOptions(callbackOptions.get());
441             
442             pagedLOD->setFileName(pagedLOD->getNumChildren(), "Dummy name - use the stored data in the read file callback");
443             pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 30000);
444             
445             return pagedLOD;
446         }
447     }
448     
449     bool _foundBase;
450     std::list<_Object> _objectList;
451     std::list<_ObjectStatic> _objectStaticList;
452     std::list<_Sign> _signList;
453 };
454
455 ReaderWriterSTG::ReaderWriterSTG()
456 {
457     supportsExtension("stg", "SimGear stg database format");
458 }
459
460 ReaderWriterSTG::~ReaderWriterSTG()
461 {
462 }
463
464 const char* ReaderWriterSTG::className() const
465 {
466     return "STG Database reader";
467 }
468
469 osgDB::ReaderWriter::ReadResult
470 ReaderWriterSTG::readNode(const std::string& fileName, const osgDB::Options* options) const
471 {
472     _ModelBin modelBin;
473     SGBucket bucket(bucketIndexFromFileName(fileName));
474
475     // We treat 123.stg different than ./123.stg.
476     // The difference is that ./123.stg as well as any absolute path
477     // really loads the given stg file and only this.
478     // In contrast 123.stg uses the search paths to load a set of stg
479     // files spread across the scenery directories.
480     if (osgDB::getSimpleFileName(fileName) != fileName) {
481         if (!modelBin.read(fileName, options))
482             return ReadResult::FILE_NOT_FOUND;
483     } else {
484         // For stg meta files, we need options for the search path.
485         if (!options)
486             return ReadResult::FILE_NOT_FOUND;
487         
488         SG_LOG(SG_TERRAIN, SG_INFO, "Loading tile " << fileName);
489         
490         std::string basePath = bucket.gen_base_path();
491         
492         // Stop scanning once an object base is found
493         // This is considered a meta file, so apply the scenery path search
494         const osgDB::FilePathList& filePathList = options->getDatabasePathList();
495         for (osgDB::FilePathList::const_iterator i = filePathList.begin();
496              i != filePathList.end() && !modelBin._foundBase; ++i) {
497             SGPath objects(*i);
498             objects.append("Objects");
499             objects.append(basePath);
500             objects.append(fileName);
501             modelBin.read(objects.str(), options);
502             
503             SGPath terrain(*i);
504             terrain.append("Terrain");
505             terrain.append(basePath);
506             terrain.append(fileName);
507             modelBin.read(terrain.str(), options);
508         }
509     }
510
511     return modelBin.load(bucket, options);
512 }
513
514 }