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