]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tgdb/TileEntry.cxx
Don't include <iostream> and "using" declarations in header files
[simgear.git] / simgear / scene / tgdb / TileEntry.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 #ifdef HAVE_CONFIG_H
22 #  include <simgear_config.h>
23 #endif
24
25 #include <simgear/compiler.h>
26 #include <plib/ul.h>
27
28 #include STL_STRING
29 #include <sstream>
30 #include <istream>
31
32 #include <osg/Array>
33 #include <osg/Geometry>
34 #include <osg/Geode>
35 #include <osg/LOD>
36 #include <osg/MatrixTransform>
37 #include <osg/Math>
38 #include <osg/NodeCallback>
39 #include <osg/Switch>
40
41 #include <osgDB/FileNameUtils>
42 #include <osgDB/ReaderWriter>
43 #include <osgDB/ReadFile>
44 #include <osgDB/Registry>
45
46 #include <simgear/bucket/newbucket.hxx>
47 #include <simgear/debug/logstream.hxx>
48 #include <simgear/math/polar3d.hxx>
49 #include <simgear/math/sg_geodesy.hxx>
50 #include <simgear/math/sg_random.h>
51 #include <simgear/math/SGMath.hxx>
52 #include <simgear/misc/sgstream.hxx>
53 #include <simgear/scene/material/mat.hxx>
54 #include <simgear/scene/material/matlib.hxx>
55 #include <simgear/scene/model/ModelRegistry.hxx>
56 #include <simgear/scene/tgdb/apt_signs.hxx>
57 #include <simgear/scene/tgdb/obj.hxx>
58 #include <simgear/scene/tgdb/SGReaderWriterBTGOptions.hxx>
59 #include <simgear/scene/model/placementtrans.hxx>
60 #include <simgear/scene/util/SGUpdateVisitor.hxx>
61
62 #include "ReaderWriterSTG.hxx"
63 #include "TileEntry.hxx"
64
65 SG_USING_STD(string);
66 using namespace simgear;
67
68 ModelLoadHelper *TileEntry::_modelLoader=0;
69
70 namespace {
71 osgDB::RegisterReaderWriterProxy<ReaderWriterSTG> g_readerWriterSTGProxy;
72 ModelRegistryCallbackProxy<LoadOnlyCallback> g_stgCallbackProxy("stg");
73 }
74
75 // FIXME: investigate what huge update flood is clamped away here ...
76 class FGTileUpdateCallback : public osg::NodeCallback {
77 public:
78   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
79   {
80     assert(dynamic_cast<SGUpdateVisitor*>(nv));
81     SGUpdateVisitor* updateVisitor = static_cast<SGUpdateVisitor*>(nv);
82
83     osg::Vec3 center = node->getBound().center();
84     double distance = dist(updateVisitor->getGlobalEyePos(),
85                            SGVec3d(center[0], center[1], center[2]));
86     if (updateVisitor->getVisibility() + node->getBound().radius() < distance)
87       return;
88
89     traverse(node, nv);
90   }
91 };
92
93 namespace
94 {
95 // Update the timestamp on a tile whenever it is in view.
96
97 class TileCullCallback : public osg::NodeCallback
98 {
99 public:
100     TileCullCallback() : _timeStamp(0) {}
101     TileCullCallback(const TileCullCallback& tc, const osg::CopyOp& copyOp) :
102         NodeCallback(tc, copyOp), _timeStamp(tc._timeStamp)
103     {
104     }
105
106     virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
107     double getTimeStamp() const { return _timeStamp; }
108     void setTimeStamp(double timeStamp) { _timeStamp = timeStamp; }
109 protected:
110     double _timeStamp;
111 };
112 }
113
114 void TileCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
115 {
116     if (nv->getFrameStamp())
117         _timeStamp = nv->getFrameStamp()->getReferenceTime();
118     traverse(node, nv);
119 }
120
121 double TileEntry::get_timestamp() const
122 {
123     if (_node.valid()) {
124         return (dynamic_cast<TileCullCallback*>(_node->getCullCallback()))
125             ->getTimeStamp();
126     } else
127         return DBL_MAX;
128 }
129
130 void TileEntry::set_timestamp(double time_ms)
131 {
132     if (_node.valid()) {
133         TileCullCallback* cb
134             = dynamic_cast<TileCullCallback*>(_node->getCullCallback());
135         if (cb)
136             cb->setTimeStamp(time_ms);
137     }
138 }
139
140 // Constructor
141 TileEntry::TileEntry ( const SGBucket& b )
142     : tile_bucket( b ),
143       _node( new osg::LOD ),
144       is_inner_ring(false),
145       free_tracker(0),
146       tileFileName(b.gen_index_str())
147 {
148     _node->setUpdateCallback(new FGTileUpdateCallback);
149     _node->setCullCallback(new TileCullCallback);
150     tileFileName += ".stg";
151     _node->setName(tileFileName);
152     // Give a default LOD range so that traversals that traverse
153     // active children (like the groundcache lookup) will work before
154     // tile manager has had a chance to update this node.
155     _node->setRange(0, 0.0, 10000.0);
156 }
157
158
159 // Destructor
160 TileEntry::~TileEntry ()
161 {
162 }
163
164 static void WorldCoordinate(osg::Matrix& obj_pos, double lat,
165                             double lon, double elev, double hdg)
166 {
167     SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
168     obj_pos = geod.makeZUpFrame();
169     // hdg is not a compass heading, but a counter-clockwise rotation
170     // around the Z axis
171     obj_pos.preMult(osg::Matrix::rotate(hdg * SGD_DEGREES_TO_RADIANS,
172                                         0.0, 0.0, 1.0));
173 }
174
175
176 // Free "n" leaf elements of an ssg tree.  returns the number of
177 // elements freed.  An empty branch node is considered a leaf.  This
178 // is intended to spread the load of freeing a complex tile out over
179 // several frames.
180 static int fgPartialFreeSSGtree( osg::Group *b, int n ) {
181     int num_deletes = b->getNumChildren();
182
183     b->removeChildren(0, b->getNumChildren());
184
185     return num_deletes;
186 }
187
188
189 // Clean up the memory used by this tile and delete the arrays used by
190 // ssg as well as the whole ssg branch
191 bool TileEntry::free_tile() {
192     int delete_size = 100;
193     SG_LOG( SG_TERRAIN, SG_DEBUG,
194             "FREEING TILE = (" << tile_bucket << ")" );
195
196     SG_LOG( SG_TERRAIN, SG_DEBUG, "(start) free_tracker = " << free_tracker );
197
198     if ( !(free_tracker & NODES) ) {
199         free_tracker |= NODES;
200     } else if ( !(free_tracker & VEC_PTRS) ) {
201         free_tracker |= VEC_PTRS;
202     } else if ( !(free_tracker & TERRA_NODE) ) {
203         // delete the terrain branch (this should already have been
204         // disconnected from the scene graph)
205         SG_LOG( SG_TERRAIN, SG_DEBUG, "FREEING terra_transform" );
206         if ( fgPartialFreeSSGtree( _node.get(), delete_size ) == 0 ) {
207             _node = 0;
208             free_tracker |= TERRA_NODE;
209         }
210     } else if ( !(free_tracker & LIGHTMAPS) ) {
211         free_tracker |= LIGHTMAPS;
212     } else {
213         return true;
214     }
215
216     SG_LOG( SG_TERRAIN, SG_DEBUG, "(end) free_tracker = " << free_tracker );
217
218     // if we fall down to here, we still have work todo, return false
219     return false;
220 }
221
222
223 // Update the ssg transform node for this tile so it can be
224 // properly drawn relative to our (0,0,0) point
225 void TileEntry::prep_ssg_node(float vis) {
226     if (!is_loaded())
227         return;
228     // visibility can change from frame to frame so we update the
229     // range selector cutoff's each time.
230     float bounding_radius = _node->getChild(0)->getBound().radius();
231     _node->setRange( 0, 0, vis + bounding_radius );
232 }
233
234 bool TileEntry::obj_load( const string& path,
235                             osg::Group *geometry, bool is_base, const osgDB::ReaderWriter::Options*options)
236 {
237     osg::Node* node = osgDB::readNodeFile(path, options);
238     if (node)
239       geometry->addChild(node);
240
241     return node != 0;
242 }
243
244
245 typedef enum {
246     OBJECT,
247     OBJECT_SHARED,
248     OBJECT_STATIC,
249     OBJECT_SIGN,
250     OBJECT_RUNWAY_SIGN
251 } object_type;
252
253
254 // storage class for deferred object processing in TileEntry::load()
255 struct Object {
256     Object(object_type t, const string& token, const SGPath& p,
257            std::istream& in)
258         : type(t), path(p)
259     {
260         in >> name;
261         if (type != OBJECT)
262             in >> lon >> lat >> elev >> hdg;
263         in >> ::skipeol;
264
265         if (type == OBJECT)
266             SG_LOG(SG_TERRAIN, SG_INFO, "    " << token << "  " << name);
267         else
268             SG_LOG(SG_TERRAIN, SG_INFO, "    " << token << "  " << name << "  lon=" <<
269                     lon << "  lat=" << lat << "  elev=" << elev << "  hdg=" << hdg);
270     }
271     object_type type;
272     string name;
273     SGPath path;
274     double lon, lat, elev, hdg;
275 };
276
277 // Work in progress... load the tile based entirely by name cuz that's
278 // what we'll want to do with the database pager.
279
280 osg::Node*
281 TileEntry::loadTileByName(const string& index_str,
282                           const osgDB::ReaderWriter::Options* options)
283 {
284     long tileIndex;
285     {
286         std::istringstream idxStream(index_str);
287         idxStream >> tileIndex;
288     }
289     SGBucket tile_bucket(tileIndex);
290     const string basePath = tile_bucket.gen_base_path();
291
292     bool found_tile_base = false;
293
294     SGPath object_base;
295     vector<const Object*> objects;
296
297     SG_LOG( SG_TERRAIN, SG_INFO, "Loading tile " << index_str );
298
299     osgDB::FilePathList path_list=options->getDatabasePathList();
300
301     // scan and parse all files and store information
302     for (unsigned int i = 0; i < path_list.size(); i++) {
303         // If we found a terrain tile in Terrain/, we have to process the
304         // Objects/ dir in the same group, too, before we can stop scanning.
305         // FGGlobals::set_fg_scenery() inserts an empty string to path_list
306         // as marker.
307
308         if (path_list[i].empty()) {
309             if (found_tile_base)
310                 break;
311             else
312                 continue;
313         }
314
315         bool has_base = false;
316
317         SGPath tile_path = path_list[i];
318         tile_path.append(basePath);
319
320         SGPath basename = tile_path;
321         basename.append( index_str );
322
323         SG_LOG( SG_TERRAIN, SG_INFO, "  Trying " << basename.str() );
324
325
326         // Check for master .stg (scene terra gear) file
327         SGPath stg_name = basename;
328         stg_name.concat( ".stg" );
329
330         sg_gzifstream in( stg_name.str() );
331         if ( !in.is_open() )
332             continue;
333
334         while ( ! in.eof() ) {
335             string token;
336             in >> token;
337
338             if ( token.empty() || token[0] == '#' ) {
339                in >> ::skipeol;
340                continue;
341             }
342                             // Load only once (first found)
343             if ( token == "OBJECT_BASE" ) {
344                 string name;
345                 in >> name >> ::skipws;
346                 SG_LOG( SG_TERRAIN, SG_INFO, "    " << token << " " << name );
347
348                 if (!found_tile_base) {
349                     found_tile_base = true;
350                     has_base = true;
351
352                     object_base = tile_path;
353                     object_base.append(name);
354
355                 } else
356                     SG_LOG(SG_TERRAIN, SG_INFO, "    (skipped)");
357
358                             // Load only if base is not in another file
359             } else if ( token == "OBJECT" ) {
360                 if (!found_tile_base || has_base)
361                     objects.push_back(new Object(OBJECT, token, tile_path, in));
362                 else {
363                     string name;
364                     in >> name >> ::skipeol;
365                     SG_LOG(SG_TERRAIN, SG_INFO, "    " << token << "  "
366                             << name << "  (skipped)");
367                 }
368
369                             // Always OK to load
370             } else if ( token == "OBJECT_STATIC" ) {
371                 objects.push_back(new Object(OBJECT_STATIC, token, tile_path, in));
372
373             } else if ( token == "OBJECT_SHARED" ) {
374                 objects.push_back(new Object(OBJECT_SHARED, token, tile_path, in));
375
376             } else if ( token == "OBJECT_SIGN" ) {
377                 objects.push_back(new Object(OBJECT_SIGN, token, tile_path, in));
378
379             } else if ( token == "OBJECT_RUNWAY_SIGN" ) {
380                 objects.push_back(new Object(OBJECT_RUNWAY_SIGN, token, tile_path, in));
381
382             } else {
383                 SG_LOG( SG_TERRAIN, SG_DEBUG,
384                         "Unknown token '" << token << "' in " << stg_name.str() );
385                 in >> ::skipws;
386             }
387         }
388     }
389
390     SGReaderWriterBTGOptions *opt = new SGReaderWriterBTGOptions(*dynamic_cast<const SGReaderWriterBTGOptions *>(options));
391
392     // obj_load() will generate ground lighting for us ...
393     osg::Group* new_tile = new osg::Group;
394
395     if (found_tile_base) {
396         // load tile if found ...
397         opt->setCalcLights(true);
398         obj_load( object_base.str(), new_tile, true, options);
399
400     } else {
401         // ... or generate an ocean tile on the fly
402         SG_LOG(SG_TERRAIN, SG_INFO, "  Generating ocean tile");
403         if ( !SGGenTile( path_list[0], tile_bucket,
404                         opt->getMatlib(), new_tile ) ) {
405             SG_LOG( SG_TERRAIN, SG_ALERT,
406                     "Warning: failed to generate ocean tile!" );
407         }
408     }
409
410
411     // now that we have a valid center, process all the objects
412     for (unsigned int j = 0; j < objects.size(); j++) {
413         const Object *obj = objects[j];
414
415         if (obj->type == OBJECT) {
416             SGPath custom_path = obj->path;
417             custom_path.append( obj->name );
418             opt->setCalcLights(true);
419             obj_load( custom_path.str(), new_tile, false, options);
420
421         } else if (obj->type == OBJECT_SHARED || obj->type == OBJECT_STATIC) {
422             // object loading is deferred to main render thread,
423             // but lets figure out the paths right now.
424             SGPath custom_path;
425             if ( obj->type == OBJECT_STATIC ) {
426                 custom_path = obj->path;
427             } else {
428                 // custom_path = globals->get_fg_root();
429             }
430             custom_path.append( obj->name );
431
432             osg::Matrix obj_pos;
433             WorldCoordinate( obj_pos, obj->lat, obj->lon, obj->elev, obj->hdg );
434
435             osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
436             obj_trans->setMatrix( obj_pos );
437
438             // wire as much of the scene graph together as we can
439             new_tile->addChild( obj_trans );
440
441             osg::Node* model = 0;
442             if(_modelLoader)
443                 model = _modelLoader->loadTileModel(custom_path.str(),
444                                                     obj->type == OBJECT_SHARED);
445             if (model)
446                 obj_trans->addChild(model);
447         } else if (obj->type == OBJECT_SIGN || obj->type == OBJECT_RUNWAY_SIGN) {
448             // load the object itself
449             SGPath custom_path = obj->path;
450             custom_path.append( obj->name );
451
452             osg::Matrix obj_pos;
453             WorldCoordinate( obj_pos, obj->lat, obj->lon, obj->elev, obj->hdg );
454
455             osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
456             obj_trans->setMatrix( obj_pos );
457
458             osg::Node *custom_obj = 0;
459             if (obj->type == OBJECT_SIGN)
460                 custom_obj = SGMakeSign(opt->getMatlib(), custom_path.str(), obj->name);
461             else
462                 custom_obj = SGMakeRunwaySign(opt->getMatlib(), custom_path.str(), obj->name);
463
464             // wire the pieces together
465             if ( custom_obj != NULL ) {
466                 obj_trans -> addChild( custom_obj );
467             }
468             new_tile->addChild( obj_trans );
469
470         }
471         delete obj;
472     }
473     return new_tile;
474 }
475
476 void
477 TileEntry::addToSceneGraph(osg::Group *terrain_branch)
478 {
479     terrain_branch->addChild( _node.get() );
480
481     SG_LOG( SG_TERRAIN, SG_DEBUG,
482             "connected a tile into scene graph.  _node = "
483             << _node.get() );
484     SG_LOG( SG_TERRAIN, SG_DEBUG, "num parents now = "
485             << _node->getNumParents() );
486 }
487
488
489 void
490 TileEntry::removeFromSceneGraph()
491 {
492     SG_LOG( SG_TERRAIN, SG_DEBUG, "disconnecting TileEntry nodes" );
493
494     if (! is_loaded()) {
495         SG_LOG( SG_TERRAIN, SG_DEBUG, "removing a not-fully loaded tile!" );
496     } else {
497         SG_LOG( SG_TERRAIN, SG_DEBUG, "removing a fully loaded tile!  _node = " << _node.get() );
498     }
499
500     // find the nodes branch parent
501     if ( _node->getNumParents() > 0 ) {
502         // find the first parent (should only be one)
503         osg::Group *parent = _node->getParent( 0 ) ;
504         if( parent ) {
505             parent->removeChild( _node.get() );
506         }
507     }
508 }
509