1 // tileentry.cxx -- routines to handle a scenery tile
3 // Written by Curtis Olson, started May 1998.
5 // Copyright (C) 1998 - 2001 Curtis L. Olson - http://www.flightgear.org/~curt
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.
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.
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.
23 # include <simgear_config.h>
26 #include "ReaderWriterSTG.hxx"
29 #include <osg/MatrixTransform>
30 #include <osg/PagedLOD>
31 #include <osg/ProxyNode>
32 #include <osgUtil/LineSegmentIntersector>
33 #include <osgUtil/IntersectionVisitor>
35 #include <osgDB/FileNameUtils>
36 #include <osgDB/FileUtils>
37 #include <osgDB/Registry>
38 #include <osgDB/ReaderWriter>
39 #include <osgDB/ReadFile>
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>
53 #include "SGOceanTile.hxx"
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)
65 if (name.substr(4, 4) != ".btg")
67 for (unsigned i = 0; i < 4; ++i) {
68 if ('A' <= name[i] && name[i] <= 'Z')
75 static SGBucket bucketIndexFromFileName(const std::string& fileName)
77 // Extract the bucket from the filename
78 std::istringstream ss(osgDB::getNameLessExtension(fileName));
84 return SGBucket(index);
87 struct ReaderWriterSTG::_ModelBin {
89 std::string _errorLocation;
92 osg::ref_ptr<SGReaderWriterOptions> _options;
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;
101 double _lon, _lat, _elev;
102 double _hdg, _pitch, _roll;
103 osg::ref_ptr<SGReaderWriterOptions> _options;
106 _Sign() : _agl(false), _lon(0), _lat(0), _elev(0), _hdg(0), _size(-1) { }
107 std::string _errorLocation;
111 double _lon, _lat, _elev;
116 class DelayLoadReadFileCallback : public OptionsReadFileCallback {
119 // QuadTreeBuilder for structuring static objects
120 struct MakeQuadLeaf {
121 osg::LOD* operator() () const { return new osg::LOD; }
124 void operator() (osg::LOD* leaf, _ObjectStatic o) const
126 osg::ref_ptr<osg::Node> node;
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());
135 node = osgDB::readRefNodeFile(o._name, o._options.get());
137 SG_LOG(SG_TERRAIN, SG_ALERT, o._errorLocation << ": Failed to load "
138 << o._token << " '" << o._name << "'");
142 if (SGPath(o._name).lower_extension() == "ac")
143 node->setNodeMask(~simgear::MODELLIGHT_BIT);
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)));
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?
159 struct GetModelLODCoord {
160 GetModelLODCoord() {}
161 GetModelLODCoord(const GetModelLODCoord& rhs)
163 osg::Vec3 operator() (const _ObjectStatic& o) const
166 SGGeodesy::SGGeodToCart(SGGeod::fromDegM(o._lon, o._lat, o._elev), coord);
170 typedef QuadTreeBuilder<osg::LOD*, _ObjectStatic, MakeQuadLeaf, AddModelLOD,
171 GetModelLODCoord> STGObjectsQuadtree;
175 virtual osgDB::ReaderWriter::ReadResult
176 readNode(const std::string&, const osgDB::Options*)
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);
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());
190 return group.release();
193 std::list<_ObjectStatic> _objectStaticList;
194 std::list<_Sign> _signList;
196 /// The original options to use for this bunch of models
197 osg::ref_ptr<SGReaderWriterOptions> _options;
205 SGReaderWriterOptions* sharedOptions(const std::string& filePath, const osgDB::Options* options)
207 osg::ref_ptr<SGReaderWriterOptions> sharedOptions;
208 sharedOptions = SGReaderWriterOptions::copyOrCreate(options);
209 sharedOptions->getDatabasePathList().clear();
211 SGPath path = filePath;
212 path.append(".."); path.append(".."); path.append("..");
213 sharedOptions->getDatabasePathList().push_back(path.str());
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);
222 std::string fg_root = options->getPluginStringData("SimGear::FG_ROOT");
223 sharedOptions->getDatabasePathList().push_back(fg_root);
225 // TODO how should we handle this for OBJECT_SHARED?
226 sharedOptions->setModelData
228 sharedOptions->getModelData()
229 ? sharedOptions->getModelData()->clone()
233 return sharedOptions.release();
235 SGReaderWriterOptions* staticOptions(const std::string& filePath, const osgDB::Options* options)
237 osg::ref_ptr<SGReaderWriterOptions> staticOptions;
238 staticOptions = SGReaderWriterOptions::copyOrCreate(options);
239 staticOptions->getDatabasePathList().clear();
241 staticOptions->getDatabasePathList().push_back(filePath);
242 staticOptions->setObjectCacheHint(osgDB::Options::CACHE_NONE);
244 // Every model needs its own SGModelData to ensure load/unload is
246 staticOptions->setModelData
248 staticOptions->getModelData()
249 ? staticOptions->getModelData()->clone()
253 return staticOptions.release();
256 double elevation(osg::Group& group, const SGGeod& geod)
258 SGVec3d start = SGVec3d::fromGeod(SGGeod::fromGeodM(geod, 10000));
259 SGVec3d end = SGVec3d::fromGeod(SGGeod::fromGeodM(geod, -1000));
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);
266 if (!intersector->containsIntersections())
269 SGVec3d cart = toSG(intersector->getFirstIntersection().getWorldIntersectPoint());
270 return SGGeod::fromCart(cart).getElevationM();
273 bool read(const std::string& absoluteFileName, const osgDB::Options* options)
275 if (absoluteFileName.empty())
278 sg_gzifstream stream(absoluteFileName);
279 if (!stream.is_open())
282 SG_LOG(SG_TERRAIN, SG_INFO, "Loading stg file " << absoluteFileName);
284 std::string filePath = osgDB::getFilePath(absoluteFileName);
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";
291 while (!stream.eof()) {
294 std::getline(stream, line);
297 std::string::size_type hash_pos = line.find('#');
298 if (hash_pos != std::string::npos)
299 line.resize(hash_pos);
301 // and process further
302 std::stringstream in(line);
311 // Then there is always a name
315 SGPath path = filePath;
318 if (token == "OBJECT_BASE") {
319 // Load only once (first found)
320 SG_LOG( SG_TERRAIN, SG_BULK, " " << token << " " << name );
322 if (!onlyAirports || isAirportBtg(name)) {
324 obj._errorLocation = absoluteFileName;
326 obj._name = path.str();
327 obj._options = staticOptions(filePath, options);
328 _objectList.push_back(obj);
331 } else if (token == "OBJECT") {
332 if (!onlyAirports || isAirportBtg(name)) {
334 obj._errorLocation = absoluteFileName;
336 obj._name = path.str();
337 obj._options = staticOptions(filePath, options);
338 _objectList.push_back(obj);
343 if (token == "OBJECT_STATIC" || token == "OBJECT_STATIC_AGL") {
345 osg::ref_ptr<SGReaderWriterOptions> opt;
346 opt = staticOptions(filePath, options);
347 if (SGPath(name).lower_extension() == "ac")
348 opt->setInstantiateEffects(true);
350 opt->setInstantiateEffects(false);
352 obj._errorLocation = absoluteFileName;
355 obj._agl = (token == "OBJECT_STATIC_AGL");
357 in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch >> obj._roll;
359 _objectStaticList.push_back(obj);
362 } else if (token == "OBJECT_SHARED" || token == "OBJECT_SHARED_AGL") {
364 osg::ref_ptr<SGReaderWriterOptions> opt;
365 opt = sharedOptions(filePath, options);
366 if (SGPath(name).lower_extension() == "ac")
367 opt->setInstantiateEffects(true);
369 opt->setInstantiateEffects(false);
371 obj._errorLocation = absoluteFileName;
374 obj._agl = (token == "OBJECT_SHARED_AGL");
376 in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch >> obj._roll;
378 _objectStaticList.push_back(obj);
381 } else if (token == "OBJECT_SIGN" || token == "OBJECT_SIGN_AGL") {
386 sign._agl = (token == "OBJECT_SIGN_AGL");
387 in >> sign._lon >> sign._lat >> sign._elev >> sign._hdg >> sign._size;
388 _signList.push_back(sign);
392 SG_LOG( SG_TERRAIN, SG_ALERT, absoluteFileName
393 << ": Unknown token '" << token << "'" );
401 osg::Node* load(const SGBucket& bucket, const osgDB::Options* opt)
403 osg::ref_ptr<SGReaderWriterOptions> options;
404 options = SGReaderWriterOptions::copyOrCreate(opt);
406 osg::ref_ptr<osg::Group> terrainGroup = new osg::Group;
407 terrainGroup->setDataVariance(osg::Object::STATIC);
408 terrainGroup->setName("terrain");
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());
415 SG_LOG(SG_TERRAIN, SG_ALERT, i->_errorLocation << ": Failed to load "
416 << i->_token << " '" << i->_name << "'");
419 terrainGroup->addChild(node.get());
422 SG_LOG(SG_TERRAIN, SG_INFO, " Generating ocean tile: " << bucket.gen_base_path() << "/" << bucket.gen_index_str());
424 osg::Node* node = SGOceanTile(bucket, options->getMaterialLib());
426 node->setName("SGOceanTile");
427 terrainGroup->addChild(node);
429 SG_LOG( SG_TERRAIN, SG_ALERT,
430 "Warning: failed to generate ocean tile!" );
434 for (std::list<_ObjectStatic>::iterator i = _objectStaticList.begin(); i != _objectStaticList.end(); ++i) {
437 i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat));
440 for (std::list<_Sign>::iterator i = _signList.begin(); i != _signList.end(); ++i) {
443 i->_elev += elevation(*terrainGroup, SGGeod::fromDeg(i->_lon, i->_lat));
446 if (_objectStaticList.empty() && _signList.empty()) {
447 // The simple case, just return the terrain group
448 return terrainGroup.release();
450 osg::PagedLOD* pagedLOD = new osg::PagedLOD;
451 pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER);
452 pagedLOD->setName("pagedObjectLOD");
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());
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());
468 pagedLOD->setFileName(pagedLOD->getNumChildren(), "Dummy name - use the stored data in the read file callback");
469 pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 30000);
476 std::list<_Object> _objectList;
477 std::list<_ObjectStatic> _objectStaticList;
478 std::list<_Sign> _signList;
481 ReaderWriterSTG::ReaderWriterSTG()
483 supportsExtension("stg", "SimGear stg database format");
486 ReaderWriterSTG::~ReaderWriterSTG()
490 const char* ReaderWriterSTG::className() const
492 return "STG Database reader";
495 osgDB::ReaderWriter::ReadResult
496 ReaderWriterSTG::readNode(const std::string& fileName, const osgDB::Options* options) const
499 SGBucket bucket(bucketIndexFromFileName(fileName));
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;
510 // For stg meta files, we need options for the search path.
512 return ReadResult::FILE_NOT_FOUND;
514 SG_LOG(SG_TERRAIN, SG_INFO, "Loading tile " << fileName);
516 std::string basePath = bucket.gen_base_path();
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) {
524 objects.append("Objects");
525 objects.append(basePath);
526 objects.append(fileName);
527 modelBin.read(objects.str(), options);
530 terrain.append("Terrain");
531 terrain.append(basePath);
532 terrain.append(fileName);
533 modelBin.read(terrain.str(), options);
537 return modelBin.load(bucket, options);