]> git.mxchange.org Git - flightgear.git/blob - src/Scenery/tileentry.cxx
initialize marker_xs.
[flightgear.git] / src / Scenery / 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  - curt@flightgear.org
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., 675 Mass Ave, Cambridge, MA 02139, USA.
20 //
21 // $Id$
22
23
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #include <simgear/compiler.h>
29
30 #include <simgear/bucket/newbucket.hxx>
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/math/sg_geodesy.hxx>
33 #include <simgear/math/sg_random.h>
34 #include <simgear/misc/sgstream.hxx>
35
36 #include <Aircraft/aircraft.hxx>
37 #include <Include/general.hxx>
38 #include <Main/globals.hxx>
39 #include <Main/viewer.hxx>
40 #include <Scenery/scenery.hxx>
41 #include <Time/light.hxx>
42 #include <Objects/apt_signs.hxx>
43 #include <Objects/matlib.hxx>
44 #include <Objects/newmat.hxx>
45 #include <Objects/obj.hxx>
46
47 #include "tileentry.hxx"
48 #include "tilemgr.hxx"
49
50
51 // Constructor
52 FGTileEntry::FGTileEntry ( const SGBucket& b )
53     : ncount( 0 ),
54       center( Point3D( 0.0 ) ),
55       tile_bucket( b ),
56       terra_transform( new ssgTransform ),
57       terra_range( new ssgRangeSelector ),
58       loaded(false),
59       pending_models(0)
60 {
61     nodes.clear();
62
63     // update the contents
64     // if ( vec3_ptrs.size() || vec2_ptrs.size() || index_ptrs.size() ) {
65     //     SG_LOG( SG_TERRAIN, SG_ALERT, 
66     //             "Attempting to overwrite existing or"
67     //             << " not properly freed leaf data." );
68     //     exit(-1);
69     // }
70 }
71
72
73 // Destructor
74 FGTileEntry::~FGTileEntry () {
75     // cout << "nodes = " << nodes.size() << endl;;
76     // delete[] nodes;
77 }
78
79
80 #if 0
81 // This is the current method cut and pasted from 
82 //  FGTileEntry::load( const SGPath& base, bool is_base )
83 void
84 FGTileEntry::WorldCoordinate( sgCoord *obj_pos, Point3D center,
85                               double lat, double lon, double elev, double hdg)
86 {
87     // setup transforms
88     Point3D geod( lon * SGD_DEGREES_TO_RADIANS,
89                   lat * SGD_DEGREES_TO_RADIANS,
90                   elev );
91         
92     Point3D world_pos = sgGeodToCart( geod );
93     Point3D offset = world_pos - center;
94         
95     sgMat4 POS;
96     sgMakeTransMat4( POS, offset.x(), offset.y(), offset.z() );
97
98     sgVec3 obj_rt, obj_up;
99     sgSetVec3( obj_rt, 0.0, 1.0, 0.0); // Y axis
100     sgSetVec3( obj_up, 0.0, 0.0, 1.0); // Z axis
101
102     sgMat4 ROT_lon, ROT_lat, ROT_hdg;
103     sgMakeRotMat4( ROT_lon, lon, obj_up );
104     sgMakeRotMat4( ROT_lat, 90 - lat, obj_rt );
105     sgMakeRotMat4( ROT_hdg, hdg, obj_up );
106
107     sgMat4 TUX;
108     sgCopyMat4( TUX, ROT_hdg );
109     sgPostMultMat4( TUX, ROT_lat );
110     sgPostMultMat4( TUX, ROT_lon );
111     sgPostMultMat4( TUX, POS );
112
113     sgSetCoord( obj_pos, TUX );
114 }
115 #endif
116
117
118 // Norman's 'fast hack' for above
119 static void WorldCoordinate( sgCoord *obj_pos, Point3D center, double lat,
120                              double lon, double elev, double hdg )
121 {
122     double lon_rad = lon * SGD_DEGREES_TO_RADIANS;
123     double lat_rad = lat * SGD_DEGREES_TO_RADIANS;
124     double hdg_rad = hdg * SGD_DEGREES_TO_RADIANS;
125
126     // setup transforms
127     Point3D geod( lon_rad, lat_rad, elev );
128         
129     Point3D world_pos = sgGeodToCart( geod );
130     Point3D offset = world_pos - center;
131
132     sgMat4 mat;
133
134     SGfloat sin_lat = (SGfloat)sin( lat_rad );
135     SGfloat cos_lat = (SGfloat)cos( lat_rad );
136     SGfloat cos_lon = (SGfloat)cos( lon_rad );
137     SGfloat sin_lon = (SGfloat)sin( lon_rad );
138     SGfloat sin_hdg = (SGfloat)sin( hdg_rad ) ;
139     SGfloat cos_hdg = (SGfloat)cos( hdg_rad ) ;
140
141     mat[0][0] =  cos_hdg * (SGfloat)sin_lat * (SGfloat)cos_lon - sin_hdg * (SGfloat)sin_lon;
142     mat[0][1] =  cos_hdg * (SGfloat)sin_lat * (SGfloat)sin_lon + sin_hdg * (SGfloat)cos_lon;
143     mat[0][2] = -cos_hdg * (SGfloat)cos_lat;
144     mat[0][3] =  SG_ZERO;
145
146     mat[1][0] = -sin_hdg * (SGfloat)sin_lat * (SGfloat)cos_lon - cos_hdg * (SGfloat)sin_lon;
147     mat[1][1] = -sin_hdg * (SGfloat)sin_lat * (SGfloat)sin_lon + cos_hdg * (SGfloat)cos_lon;
148     mat[1][2] =  sin_hdg * (SGfloat)cos_lat;
149     mat[1][3] =  SG_ZERO;
150
151     mat[2][0] = (SGfloat)cos_lat * (SGfloat)cos_lon;
152     mat[2][1] = (SGfloat)cos_lat * (SGfloat)sin_lon;
153     mat[2][2] = (SGfloat)sin_lat;
154     mat[2][3] =  SG_ZERO;
155
156     mat[3][0] = offset.x();
157     mat[3][1] = offset.y();
158     mat[3][2] = offset.z();
159     mat[3][3] = SG_ONE ;
160
161     sgSetCoord( obj_pos, mat );
162 }
163
164
165 // recurse an ssg tree and call removeKid() on every node from the
166 // bottom up.  Leaves the original branch in existance, but empty so
167 // it can be removed by the calling routine.
168 static void my_remove_branch( ssgBranch * branch ) {
169     for ( ssgEntity *k = branch->getKid( 0 );
170           k != NULL; 
171           k = branch->getNextKid() )
172     {
173         if ( k -> isAKindOf ( ssgTypeBranch() ) ) {
174             my_remove_branch( (ssgBranch *)k );
175             branch -> removeKid ( k );
176         } else if ( k -> isAKindOf ( ssgTypeLeaf() ) ) {
177             branch -> removeKid ( k ) ;
178         }
179     }
180 }
181
182
183 #ifdef WISH_PLIB_WAS_THREADED // but it isn't
184
185 // Schedule tile to be freed/removed
186 void FGTileEntry::sched_removal() {
187     global_tile_mgr.ready_to_delete( this );
188 }
189
190 #endif
191
192
193 // Clean up the memory used by this tile and delete the arrays used by
194 // ssg as well as the whole ssg branch
195 void FGTileEntry::free_tile() {
196     int i;
197     SG_LOG( SG_TERRAIN, SG_INFO,
198             "FREEING TILE = (" << tile_bucket << ")" );
199
200     SG_LOG( SG_TERRAIN, SG_DEBUG,
201             "  deleting " << nodes.size() << " nodes" );
202     nodes.clear();
203
204     // delete the ssg structures
205     SG_LOG( SG_TERRAIN, SG_DEBUG,
206             "  deleting (leaf data) vertex, normal, and "
207             << " texture coordinate arrays" );
208
209     for ( i = 0; i < (int)vec3_ptrs.size(); ++i ) {
210         delete [] vec3_ptrs[i];
211     }
212     vec3_ptrs.clear();
213
214     for ( i = 0; i < (int)vec2_ptrs.size(); ++i ) {
215         delete [] vec2_ptrs[i];
216     }
217     vec2_ptrs.clear();
218
219     for ( i = 0; i < (int)index_ptrs.size(); ++i ) {
220         delete index_ptrs[i];
221     }
222     index_ptrs.clear();
223
224     // delete the terrain branch (this should already have been
225     // disconnected from the scene graph)
226     ssgDeRefDelete( terra_transform );
227
228     if ( lights_transform ) {
229         // delete the terrain lighting branch (this should already have been
230     // disconnected from the scene graph)
231         ssgDeRefDelete( lights_transform );
232     }
233 }
234
235
236 // Update the ssg transform node for this tile so it can be
237 // properly drawn relative to our (0,0,0) point
238 void FGTileEntry::prep_ssg_node( const Point3D& p, float vis) {
239     if ( !loaded ) return;
240
241     SetOffset( p );
242
243 // #define USE_UP_AND_COMING_PLIB_FEATURE
244 #ifdef USE_UP_AND_COMING_PLIB_FEATURE
245     terra_range->setRange( 0, SG_ZERO );
246     terra_range->setRange( 1, vis + bounding_radius );
247     lights_range->setRange( 0, SG_ZERO );
248     lights_range->setRange( 1, vis * 1.5 + bounding_radius );
249 #else
250     float ranges[2];
251     ranges[0] = SG_ZERO;
252     ranges[1] = vis + bounding_radius;
253     terra_range->setRanges( ranges, 2 );
254     if ( lights_range ) {
255         ranges[1] = vis * 1.5 + bounding_radius;
256         lights_range->setRanges( ranges, 2 );
257     }
258 #endif
259     sgVec3 sgTrans;
260     sgSetVec3( sgTrans, offset.x(), offset.y(), offset.z() );
261     terra_transform->setTransform( sgTrans );
262
263     if ( lights_transform ) {
264         // we need to lift the lights above the terrain to avoid
265         // z-buffer fighting.  We do this based on our altitude and
266         // the distance this tile is away from scenery center.
267
268         sgVec3 up;
269         sgCopyVec3( up, globals->get_current_view()->get_world_up() );
270
271         double agl;
272         if ( current_aircraft.fdm_state ) {
273             agl = current_aircraft.fdm_state->get_Altitude() * SG_FEET_TO_METER
274                 - scenery.cur_elev;
275         } else {
276             agl = 0.0;
277         }
278
279         // sgTrans just happens to be the
280         // vector from scenery center to the center of this tile which
281         // is what we want to calculate the distance of
282         sgVec3 to;
283         sgCopyVec3( to, sgTrans );
284         double dist = sgLengthVec3( to );
285
286         if ( general.get_glDepthBits() > 16 ) {
287             sgScaleVec3( up, 10.0 + agl / 100.0 + dist / 10000 );
288         } else {
289             sgScaleVec3( up, 10.0 + agl / 20.0 + dist / 5000 );
290         }
291         sgAddVec3( sgTrans, up );
292         lights_transform->setTransform( sgTrans );
293
294         // select which set of lights based on sun angle
295         float sun_angle = cur_light_params.sun_angle * SGD_RADIANS_TO_DEGREES;
296         if ( sun_angle > 95 ) {
297             lights_brightness->select(0x04);
298         } else if ( sun_angle > 92 ) {
299             lights_brightness->select(0x02);
300         } else if ( sun_angle > 89 ) {
301             lights_brightness->select(0x01);
302         } else {
303             lights_brightness->select(0x00);
304         }
305     }
306 }
307
308
309 ssgLeaf* FGTileEntry::gen_lights( ssgVertexArray *lights, int inc, float bright ) {
310     // generate a repeatable random seed
311     float *p1 = lights->get( 0 );
312     unsigned int *seed = (unsigned int *)p1;
313     sg_srandom( *seed );
314
315     int size = lights->getNum() / inc;
316
317     // Allocate ssg structure
318     ssgVertexArray   *vl = new ssgVertexArray( size + 1 );
319     ssgNormalArray   *nl = NULL;
320     ssgTexCoordArray *tl = NULL;
321     ssgColourArray   *cl = new ssgColourArray( size + 1 );
322
323     sgVec4 color;
324     for ( int i = 0; i < lights->getNum(); ++i ) {
325         // this loop is slightly less efficient than it otherwise
326         // could be, but we want a red light to always be red, and a
327         // yellow light to always be yellow, etc. so we are trying to
328         // preserve the random sequence.
329         float zombie = sg_random();
330         if ( i % inc == 0 ) {
331             vl->add( lights->get(i) );
332
333             // factor = sg_random() ^ 2, range = 0 .. 1 concentrated towards 0
334             float factor = sg_random();
335             factor *= factor;
336
337             if ( zombie > 0.5 ) {
338                 // 50% chance of yellowish
339                 sgSetVec4( color, 0.9, 0.9, 0.3, bright - factor * 0.2 );
340             } else if ( zombie > 0.15 ) {
341                 // 35% chance of whitish
342                 sgSetVec4( color, 0.9, 0.9, 0.8, bright - factor * 0.2 );
343             } else if ( zombie > 0.05 ) {
344                 // 10% chance of orangish
345                 sgSetVec4( color, 0.9, 0.6, 0.2, bright - factor * 0.2 );
346             } else {
347                 // 5% chance of redish
348                 sgSetVec4( color, 0.9, 0.2, 0.2, bright - factor * 0.2 );
349             }
350             cl->add( color );
351         }
352     }
353
354     // create ssg leaf
355     ssgLeaf *leaf = 
356         new ssgVtxTable ( GL_POINTS, vl, nl, tl, cl );
357
358     // assign state
359     FGNewMat *newmat = material_lib.find( "LIGHTS" );
360     leaf->setState( newmat->get_state() );
361
362     return leaf;
363 }
364
365
366 ssgBranch*
367 FGTileEntry::obj_load( const std::string& path,
368                        ssgVertexArray* lights, bool is_base )
369 {
370     ssgBranch* result = 0;
371
372     // try loading binary format
373     result = fgBinObjLoad( path, this, lights, is_base );
374     if ( result == NULL ) {
375         // next try the older ascii format
376         result = fgAsciiObjLoad( path, this, lights, is_base );
377         if ( result == NULL ) {
378             // default to an ocean tile
379             result = fgGenTile( path, this );
380         }
381     }
382
383     return result;
384 }
385
386
387 void
388 FGTileEntry::load( const SGPath& base, bool is_base )
389 {
390     cout << "load() base = " << base.str() << endl;
391
392     // Generate names for later use
393     string index_str = tile_bucket.gen_index_str();
394
395     SGPath tile_path = base;
396     tile_path.append( tile_bucket.gen_base_path() );
397
398     SGPath basename = tile_path;
399     basename.append( index_str );
400     // string path = basename.str();
401
402     SG_LOG( SG_TERRAIN, SG_INFO, "Loading tile " << basename.str() );
403
404
405     // obj_load() will generate ground lighting for us ...
406     ssgVertexArray *light_pts = new ssgVertexArray( 100 );
407     ssgBranch* new_tile = new ssgBranch;
408
409     // Check for master .stg (scene terra gear) file
410     SGPath stg_name = basename;
411     stg_name.concat( ".stg" );
412
413     sg_gzifstream in( stg_name.str() );
414
415     if ( in.is_open() ) {
416         string token, name;
417
418         while ( ! in.eof() ) {
419             in >> token;
420
421             if ( token == "OBJECT_BASE" ) {
422                 in >> name >> ::skipws;
423                 SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
424                         << " name = " << name );
425
426                 SGPath custom_path = tile_path;
427                 custom_path.append( name );
428
429                 ssgBranch *custom_obj
430                     = obj_load( custom_path.str(), light_pts, true );
431
432                 if ( custom_obj != NULL ) {
433                     new_tile -> addKid( custom_obj );
434                 }
435             } else if ( token == "OBJECT" ) {
436                 in >> name >> ::skipws;
437                 SG_LOG( SG_TERRAIN, SG_DEBUG, "token = " << token
438                         << " name = " << name );
439
440                 SGPath custom_path = tile_path;
441                 custom_path.append( name );
442                 ssgBranch *custom_obj
443                     = obj_load( custom_path.str(), NULL, false );
444                 if ( custom_obj != NULL ) {
445                     new_tile -> addKid( custom_obj );
446                 }
447             } else if ( token == "OBJECT_STATIC" ) {
448                 // load object info
449                 double lon, lat, elev, hdg;
450                 in >> name >> lon >> lat >> elev >> hdg >> ::skipws;
451                 SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
452                         << " name = " << name 
453                         << " pos = " << lon << ", " << lat
454                         << " elevation = " << elev
455                         << " heading = " << hdg );
456
457                 // object loading is deferred to main render thread,
458                 // but lets figure out the paths right now.
459                 SGPath custom_path = tile_path;
460                 custom_path.append( name );
461
462                 sgCoord obj_pos;
463                 WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
464                 
465                 ssgTransform *obj_trans = new ssgTransform;
466                 obj_trans->setTransform( &obj_pos );
467
468                 // wire as much of the scene graph together as we can
469                 new_tile->addKid( obj_trans );
470
471                 // bump up the pending models count
472                 pending_models++;
473
474                 // push an entry onto the model load queue
475                 FGDeferredModel *dm
476                     = new FGDeferredModel( custom_path.str(), tile_path.str(),
477                                            this, obj_trans );
478                 FGTileMgr::model_ready( dm );
479             } else if ( token == "OBJECT_TAXI_SIGN" ) {
480                 // load object info
481                 double lon, lat, elev, hdg;
482                 in >> name >> lon >> lat >> elev >> hdg >> ::skipws;
483                 SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
484                         << " name = " << name 
485                         << " pos = " << lon << ", " << lat
486                         << " elevation = " << elev
487                         << " heading = " << hdg );
488
489                 // load the object itself
490                 SGPath custom_path = tile_path;
491                 custom_path.append( name );
492
493                 sgCoord obj_pos;
494                 WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
495
496                 ssgTransform *obj_trans = new ssgTransform;
497                 obj_trans->setTransform( &obj_pos );
498
499                 ssgBranch *custom_obj
500                     = gen_taxi_sign( custom_path.str(), name );
501
502                 // wire the pieces together
503                 if ( custom_obj != NULL ) {
504                     obj_trans -> addKid( custom_obj );
505                 }
506                 new_tile->addKid( obj_trans );
507             } else if ( token == "OBJECT_RUNWAY_SIGN" ) {
508                 // load object info
509                 double lon, lat, elev, hdg;
510                 in >> name >> lon >> lat >> elev >> hdg >> ::skipws;
511                 SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
512                         << " name = " << name 
513                         << " pos = " << lon << ", " << lat
514                         << " elevation = " << elev
515                         << " heading = " << hdg );
516
517                 // load the object itself
518                 SGPath custom_path = tile_path;
519                 custom_path.append( name );
520
521                 sgCoord obj_pos;
522                 WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
523
524                 ssgTransform *obj_trans = new ssgTransform;
525                 obj_trans->setTransform( &obj_pos );
526
527                 ssgBranch *custom_obj
528                     = gen_runway_sign( custom_path.str(), name );
529
530                 // wire the pieces together
531                 if ( custom_obj != NULL ) {
532                     obj_trans -> addKid( custom_obj );
533                 }
534                 new_tile->addKid( obj_trans );
535             } else if ( token == "RWY_LIGHTS" ) {
536                 double lon, lat, hdg, len, width;
537                 string common, end1, end2;
538                 in >> lon >> lat >> hdg >> len >> width
539                    >> common >> end1 >> end2;
540                 SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
541                         << " pos = " << lon << ", " << lat
542                         << " hdg = " << hdg
543                         << " size = " << len << ", " << width
544                         << " codes = " << common << " "
545                         << end1 << " " << end2 );
546             } else {
547                 SG_LOG( SG_TERRAIN, SG_ALERT,
548                         "Unknown token " << token << " in "
549                         << stg_name.str() );
550                 in >> ::skipws;
551             }
552         }
553     } else {
554         // no .stg file so this must be old scenery
555
556         new_tile = obj_load( basename.str(), light_pts, true );
557
558         // load custom objects
559         SG_LOG( SG_TERRAIN, SG_DEBUG, "Checking for custom objects ..." );
560
561         SGPath index_path = tile_path;
562         index_path.append( index_str );
563         index_path.concat( ".ind" );
564
565         SG_LOG( SG_TERRAIN, SG_DEBUG, "Looking in " << index_path.str() );
566
567         sg_gzifstream in( index_path.str() );
568
569         if ( in.is_open() ) {
570             string token, name;
571
572             while ( ! in.eof() ) {
573                 in >> token;
574
575                 if ( token == "OBJECT" ) {
576                     in >> name >> ::skipws;
577                     SG_LOG( SG_TERRAIN, SG_DEBUG, "token = " << token
578                             << " name = " << name );
579
580                     SGPath custom_path = tile_path;
581                     custom_path.append( name );
582                     ssgBranch *custom_obj
583                         = obj_load( custom_path.str(), NULL, false );
584                     if ( (new_tile != NULL) && (custom_obj != NULL) ) {
585                         new_tile -> addKid( custom_obj );
586                     }
587                 } else if ( token == "OBJECT_STATIC" ) {
588                     // load object info
589                     double lon, lat, elev, hdg;
590                     in >> name >> lon >> lat >> elev >> hdg >> ::skipws;
591                     SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
592                             << " name = " << name 
593                             << " pos = " << lon << ", " << lat
594                             << " elevation = " << elev
595                             << " heading = " << hdg );
596
597                     // object loading is deferred to main render thread,
598                     // but lets figure out the paths right now.
599                     SGPath custom_path = tile_path;
600                     custom_path.append( name );
601
602                     sgCoord obj_pos;
603                     WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
604                 
605                     ssgTransform *obj_trans = new ssgTransform;
606                     obj_trans->setTransform( &obj_pos );
607
608                     // wire as much of the scene graph together as we can
609                     new_tile->addKid( obj_trans );
610
611                     // bump up the pending models count
612                     pending_models++;
613
614                     // push an entry onto the model load queue
615                     FGDeferredModel *dm
616                         = new FGDeferredModel( custom_path.str(),
617                                                tile_path.str(),
618                                                this, obj_trans );
619                     FGTileMgr::model_ready( dm );
620                 } else if ( token == "OBJECT_TAXI_SIGN" ) {
621                     // load object info
622                     double lon, lat, elev, hdg;
623                     in >> name >> lon >> lat >> elev >> hdg >> ::skipws;
624                     SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
625                             << " name = " << name 
626                             << " pos = " << lon << ", " << lat
627                             << " elevation = " << elev
628                             << " heading = " << hdg );
629
630                     // load the object itself
631                     SGPath custom_path = tile_path;
632                     custom_path.append( name );
633
634                     sgCoord obj_pos;
635                     WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
636
637                     ssgTransform *obj_trans = new ssgTransform;
638                     obj_trans->setTransform( &obj_pos );
639
640                     ssgBranch *custom_obj
641                         = gen_taxi_sign( custom_path.str(), name );
642
643                     // wire the pieces together
644                     if ( (new_tile != NULL) && (custom_obj != NULL) ) {
645                         obj_trans -> addKid( custom_obj );
646                     }
647                     new_tile->addKid( obj_trans );
648                 } else if ( token == "OBJECT_RUNWAY_SIGN" ) {
649                     // load object info
650                     double lon, lat, elev, hdg;
651                     in >> name >> lon >> lat >> elev >> hdg >> ::skipws;
652                     SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
653                             << " name = " << name 
654                             << " pos = " << lon << ", " << lat
655                             << " elevation = " << elev
656                             << " heading = " << hdg );
657
658                     // load the object itself
659                     SGPath custom_path = tile_path;
660                     custom_path.append( name );
661
662                     sgCoord obj_pos;
663                     WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
664
665                     ssgTransform *obj_trans = new ssgTransform;
666                     obj_trans->setTransform( &obj_pos );
667
668                     ssgBranch *custom_obj
669                         = gen_runway_sign( custom_path.str(), name );
670
671                     // wire the pieces together
672                     if ( (new_tile != NULL) && (custom_obj != NULL) ) {
673                         obj_trans -> addKid( custom_obj );
674                     }
675                     new_tile->addKid( obj_trans );
676                 } else {
677                     SG_LOG( SG_TERRAIN, SG_ALERT,
678                             "Unknown token " << token << " in "
679                             << index_path.str() );
680                     in >> ::skipws;
681                 }
682             }
683         }
684     }
685
686     if ( new_tile != NULL ) {
687         terra_range->addKid( new_tile );
688     }
689
690     terra_transform->addKid( terra_range );
691
692     // calculate initial tile offset
693     SetOffset( scenery.center );
694     sgCoord sgcoord;
695     sgSetCoord( &sgcoord,
696                 offset.x(), offset.y(), offset.z(),
697                 0.0, 0.0, 0.0 );
698     terra_transform->setTransform( &sgcoord );
699     // terrain->addKid( terra_transform );
700
701     lights_transform = NULL;
702     lights_range = NULL;
703     /* uncomment this section for testing ground lights */
704     if ( light_pts->getNum() ) {
705         SG_LOG( SG_TERRAIN, SG_DEBUG, "generating lights" );
706         lights_transform = new ssgTransform;
707         lights_range = new ssgRangeSelector;
708         lights_brightness = new ssgSelector;
709         ssgLeaf *lights;
710
711         lights = gen_lights( light_pts, 4, 0.7 );
712         lights_brightness->addKid( lights );
713
714         lights = gen_lights( light_pts, 2, 0.85 );
715         lights_brightness->addKid( lights );
716
717         lights = gen_lights( light_pts, 1, 1.0 );
718         lights_brightness->addKid( lights );
719
720         lights_range->addKid( lights_brightness );
721         lights_transform->addKid( lights_range );
722         lights_transform->setTransform( &sgcoord );
723         // ground->addKid( lights_transform );
724     }
725     /* end of ground light section */
726 }
727
728
729 void
730 FGTileEntry::add_ssg_nodes( ssgBranch* terrain, ssgBranch* ground )
731 {
732     // bump up the ref count so we can remove this later without
733     // having ssg try to free the memory.
734     terra_transform->ref();
735     terrain->addKid( terra_transform );
736
737     SG_LOG( SG_TERRAIN, SG_DEBUG,
738             "connected a tile into scene graph.  terra_transform = "
739             << terra_transform );
740     SG_LOG( SG_TERRAIN, SG_DEBUG, "num parents now = "
741             << terra_transform->getNumParents() );
742
743     if ( lights_transform != 0 ) {
744         // bump up the ref count so we can remove this later without
745         // having ssg try to free the memory.
746         lights_transform->ref();
747         ground->addKid( lights_transform );
748     }
749
750     loaded = true;
751 }
752
753
754 void
755 FGTileEntry::disconnect_ssg_nodes()
756 {
757     SG_LOG( SG_TERRAIN, SG_INFO, "disconnecting ssg nodes" );
758
759     if ( ! loaded ) {
760         SG_LOG( SG_TERRAIN, SG_INFO, "removing a not-fully loaded tile!" );
761     } else {
762         SG_LOG( SG_TERRAIN, SG_INFO, "removing a fully loaded tile!  terra_transform = " << terra_transform );
763     }
764         
765     // find the terrain branch parent
766     int pcount = terra_transform->getNumParents();
767     if ( pcount > 0 ) {
768         // find the first parent (should only be one)
769         ssgBranch *parent = terra_transform->getParent( 0 ) ;
770         if( parent ) {
771             // disconnect the tile (we previously ref()'d it so it
772             // won't get freed now)
773             parent->removeKid( terra_transform );
774         } else {
775             SG_LOG( SG_TERRAIN, SG_ALERT,
776                     "parent pointer is NULL!  Dying" );
777             exit(-1);
778         }
779     } else {
780         SG_LOG( SG_TERRAIN, SG_ALERT,
781                 "Parent count is zero for an ssg tile!  Dying" );
782         exit(-1);
783     }
784
785     // find the terrain lighting branch
786     if ( lights_transform ) {
787         pcount = lights_transform->getNumParents();
788         if ( pcount > 0 ) {
789             // find the first parent (should only be one)
790             ssgBranch *parent = lights_transform->getParent( 0 ) ;
791             if( parent ) {
792                 // disconnect the light branch (we previously ref()'d
793                 // it so it won't get freed now)
794                 parent->removeKid( lights_transform );
795             } else {
796                 SG_LOG( SG_TERRAIN, SG_ALERT,
797                         "parent pointer is NULL!  Dying" );
798                 exit(-1);
799             }
800         } else {
801             SG_LOG( SG_TERRAIN, SG_ALERT,
802                     "Parent count is zero for an ssg light tile!  Dying" );
803             exit(-1);
804         }
805     }
806 }