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 - curt@flightgear.org
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., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #include <simgear/compiler.h>
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>
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>
47 #include "tileentry.hxx"
48 #include "tilemgr.hxx"
52 FGTileEntry::FGTileEntry ( const SGBucket& b )
54 center( Point3D( 0.0 ) ),
56 terra_transform( new ssgTransform ),
57 terra_range( new ssgRangeSelector ),
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." );
74 FGTileEntry::~FGTileEntry () {
75 // cout << "nodes = " << nodes.size() << endl;;
81 // This is the current method cut and pasted from
82 // FGTileEntry::load( const SGPath& base, bool is_base )
84 FGTileEntry::WorldCoordinate( sgCoord *obj_pos, Point3D center,
85 double lat, double lon, double elev, double hdg)
88 Point3D geod( lon * SGD_DEGREES_TO_RADIANS,
89 lat * SGD_DEGREES_TO_RADIANS,
92 Point3D world_pos = sgGeodToCart( geod );
93 Point3D offset = world_pos - center;
96 sgMakeTransMat4( POS, offset.x(), offset.y(), offset.z() );
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
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 );
108 sgCopyMat4( TUX, ROT_hdg );
109 sgPostMultMat4( TUX, ROT_lat );
110 sgPostMultMat4( TUX, ROT_lon );
111 sgPostMultMat4( TUX, POS );
113 sgSetCoord( obj_pos, TUX );
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 )
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;
127 Point3D geod( lon_rad, lat_rad, elev );
129 Point3D world_pos = sgGeodToCart( geod );
130 Point3D offset = world_pos - center;
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 ) ;
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;
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;
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;
156 mat[3][0] = offset.x();
157 mat[3][1] = offset.y();
158 mat[3][2] = offset.z();
161 sgSetCoord( obj_pos, mat );
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 );
171 k = branch->getNextKid() )
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 ) ;
183 #ifdef WISH_PLIB_WAS_THREADED // but it isn't
185 // Schedule tile to be freed/removed
186 void FGTileEntry::sched_removal() {
187 global_tile_mgr.ready_to_delete( this );
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() {
197 SG_LOG( SG_TERRAIN, SG_INFO,
198 "FREEING TILE = (" << tile_bucket << ")" );
200 SG_LOG( SG_TERRAIN, SG_DEBUG,
201 " deleting " << nodes.size() << " nodes" );
204 // delete the ssg structures
205 SG_LOG( SG_TERRAIN, SG_DEBUG,
206 " deleting (leaf data) vertex, normal, and "
207 << " texture coordinate arrays" );
209 for ( i = 0; i < (int)vec3_ptrs.size(); ++i ) {
210 delete [] vec3_ptrs[i];
214 for ( i = 0; i < (int)vec2_ptrs.size(); ++i ) {
215 delete [] vec2_ptrs[i];
219 for ( i = 0; i < (int)index_ptrs.size(); ++i ) {
220 delete index_ptrs[i];
224 // delete the terrain branch (this should already have been
225 // disconnected from the scene graph)
226 ssgDeRefDelete( terra_transform );
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 );
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;
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 );
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 );
260 sgSetVec3( sgTrans, offset.x(), offset.y(), offset.z() );
261 terra_transform->setTransform( sgTrans );
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.
269 sgCopyVec3( up, globals->get_current_view()->get_world_up() );
272 if ( current_aircraft.fdm_state ) {
273 agl = current_aircraft.fdm_state->get_Altitude() * SG_FEET_TO_METER
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
283 sgCopyVec3( to, sgTrans );
284 double dist = sgLengthVec3( to );
286 if ( general.get_glDepthBits() > 16 ) {
287 sgScaleVec3( up, 10.0 + agl / 100.0 + dist / 10000 );
289 sgScaleVec3( up, 10.0 + agl / 20.0 + dist / 5000 );
291 sgAddVec3( sgTrans, up );
292 lights_transform->setTransform( sgTrans );
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);
303 lights_brightness->select(0x00);
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;
315 int size = lights->getNum() / inc;
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 );
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) );
333 // factor = sg_random() ^ 2, range = 0 .. 1 concentrated towards 0
334 float factor = sg_random();
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 );
347 // 5% chance of redish
348 sgSetVec4( color, 0.9, 0.2, 0.2, bright - factor * 0.2 );
356 new ssgVtxTable ( GL_POINTS, vl, nl, tl, cl );
359 FGNewMat *newmat = material_lib.find( "LIGHTS" );
360 leaf->setState( newmat->get_state() );
367 FGTileEntry::obj_load( const std::string& path,
368 ssgVertexArray* lights, bool is_base )
370 ssgBranch* result = 0;
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 );
388 FGTileEntry::load( const SGPath& base, bool is_base )
390 cout << "load() base = " << base.str() << endl;
392 // Generate names for later use
393 string index_str = tile_bucket.gen_index_str();
395 SGPath tile_path = base;
396 tile_path.append( tile_bucket.gen_base_path() );
398 SGPath basename = tile_path;
399 basename.append( index_str );
400 // string path = basename.str();
402 SG_LOG( SG_TERRAIN, SG_INFO, "Loading tile " << basename.str() );
405 // obj_load() will generate ground lighting for us ...
406 ssgVertexArray *light_pts = new ssgVertexArray( 100 );
407 ssgBranch* new_tile = new ssgBranch;
409 // Check for master .stg (scene terra gear) file
410 SGPath stg_name = basename;
411 stg_name.concat( ".stg" );
413 sg_gzifstream in( stg_name.str() );
415 if ( in.is_open() ) {
418 while ( ! in.eof() ) {
421 if ( token == "OBJECT_BASE" ) {
422 in >> name >> ::skipws;
423 SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
424 << " name = " << name );
426 SGPath custom_path = tile_path;
427 custom_path.append( name );
429 ssgBranch *custom_obj
430 = obj_load( custom_path.str(), light_pts, true );
432 if ( custom_obj != NULL ) {
433 new_tile -> addKid( custom_obj );
435 } else if ( token == "OBJECT" ) {
436 in >> name >> ::skipws;
437 SG_LOG( SG_TERRAIN, SG_DEBUG, "token = " << token
438 << " name = " << name );
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 );
447 } else if ( token == "OBJECT_STATIC" ) {
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 );
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 );
463 WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
465 ssgTransform *obj_trans = new ssgTransform;
466 obj_trans->setTransform( &obj_pos );
468 // wire as much of the scene graph together as we can
469 new_tile->addKid( obj_trans );
471 // bump up the pending models count
474 // push an entry onto the model load queue
476 = new FGDeferredModel( custom_path.str(), tile_path.str(),
478 FGTileMgr::model_ready( dm );
479 } else if ( token == "OBJECT_TAXI_SIGN" ) {
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 );
489 // load the object itself
490 SGPath custom_path = tile_path;
491 custom_path.append( name );
494 WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
496 ssgTransform *obj_trans = new ssgTransform;
497 obj_trans->setTransform( &obj_pos );
499 ssgBranch *custom_obj
500 = gen_taxi_sign( custom_path.str(), name );
502 // wire the pieces together
503 if ( custom_obj != NULL ) {
504 obj_trans -> addKid( custom_obj );
506 new_tile->addKid( obj_trans );
507 } else if ( token == "OBJECT_RUNWAY_SIGN" ) {
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 );
517 // load the object itself
518 SGPath custom_path = tile_path;
519 custom_path.append( name );
522 WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
524 ssgTransform *obj_trans = new ssgTransform;
525 obj_trans->setTransform( &obj_pos );
527 ssgBranch *custom_obj
528 = gen_runway_sign( custom_path.str(), name );
530 // wire the pieces together
531 if ( custom_obj != NULL ) {
532 obj_trans -> addKid( custom_obj );
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
543 << " size = " << len << ", " << width
544 << " codes = " << common << " "
545 << end1 << " " << end2 );
547 SG_LOG( SG_TERRAIN, SG_ALERT,
548 "Unknown token " << token << " in "
554 // no .stg file so this must be old scenery
556 new_tile = obj_load( basename.str(), light_pts, true );
558 // load custom objects
559 SG_LOG( SG_TERRAIN, SG_DEBUG, "Checking for custom objects ..." );
561 SGPath index_path = tile_path;
562 index_path.append( index_str );
563 index_path.concat( ".ind" );
565 SG_LOG( SG_TERRAIN, SG_DEBUG, "Looking in " << index_path.str() );
567 sg_gzifstream in( index_path.str() );
569 if ( in.is_open() ) {
572 while ( ! in.eof() ) {
575 if ( token == "OBJECT" ) {
576 in >> name >> ::skipws;
577 SG_LOG( SG_TERRAIN, SG_DEBUG, "token = " << token
578 << " name = " << name );
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 );
587 } else if ( token == "OBJECT_STATIC" ) {
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 );
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 );
603 WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
605 ssgTransform *obj_trans = new ssgTransform;
606 obj_trans->setTransform( &obj_pos );
608 // wire as much of the scene graph together as we can
609 new_tile->addKid( obj_trans );
611 // bump up the pending models count
614 // push an entry onto the model load queue
616 = new FGDeferredModel( custom_path.str(),
619 FGTileMgr::model_ready( dm );
620 } else if ( token == "OBJECT_TAXI_SIGN" ) {
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 );
630 // load the object itself
631 SGPath custom_path = tile_path;
632 custom_path.append( name );
635 WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
637 ssgTransform *obj_trans = new ssgTransform;
638 obj_trans->setTransform( &obj_pos );
640 ssgBranch *custom_obj
641 = gen_taxi_sign( custom_path.str(), name );
643 // wire the pieces together
644 if ( (new_tile != NULL) && (custom_obj != NULL) ) {
645 obj_trans -> addKid( custom_obj );
647 new_tile->addKid( obj_trans );
648 } else if ( token == "OBJECT_RUNWAY_SIGN" ) {
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 );
658 // load the object itself
659 SGPath custom_path = tile_path;
660 custom_path.append( name );
663 WorldCoordinate( &obj_pos, center, lat, lon, elev, hdg );
665 ssgTransform *obj_trans = new ssgTransform;
666 obj_trans->setTransform( &obj_pos );
668 ssgBranch *custom_obj
669 = gen_runway_sign( custom_path.str(), name );
671 // wire the pieces together
672 if ( (new_tile != NULL) && (custom_obj != NULL) ) {
673 obj_trans -> addKid( custom_obj );
675 new_tile->addKid( obj_trans );
677 SG_LOG( SG_TERRAIN, SG_ALERT,
678 "Unknown token " << token << " in "
679 << index_path.str() );
686 if ( new_tile != NULL ) {
687 terra_range->addKid( new_tile );
690 terra_transform->addKid( terra_range );
692 // calculate initial tile offset
693 SetOffset( scenery.center );
695 sgSetCoord( &sgcoord,
696 offset.x(), offset.y(), offset.z(),
698 terra_transform->setTransform( &sgcoord );
699 // terrain->addKid( terra_transform );
701 lights_transform = 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;
711 lights = gen_lights( light_pts, 4, 0.7 );
712 lights_brightness->addKid( lights );
714 lights = gen_lights( light_pts, 2, 0.85 );
715 lights_brightness->addKid( lights );
717 lights = gen_lights( light_pts, 1, 1.0 );
718 lights_brightness->addKid( lights );
720 lights_range->addKid( lights_brightness );
721 lights_transform->addKid( lights_range );
722 lights_transform->setTransform( &sgcoord );
723 // ground->addKid( lights_transform );
725 /* end of ground light section */
730 FGTileEntry::add_ssg_nodes( ssgBranch* terrain, ssgBranch* ground )
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 );
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() );
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 );
755 FGTileEntry::disconnect_ssg_nodes()
757 SG_LOG( SG_TERRAIN, SG_INFO, "disconnecting ssg nodes" );
760 SG_LOG( SG_TERRAIN, SG_INFO, "removing a not-fully loaded tile!" );
762 SG_LOG( SG_TERRAIN, SG_INFO, "removing a fully loaded tile! terra_transform = " << terra_transform );
765 // find the terrain branch parent
766 int pcount = terra_transform->getNumParents();
768 // find the first parent (should only be one)
769 ssgBranch *parent = terra_transform->getParent( 0 ) ;
771 // disconnect the tile (we previously ref()'d it so it
772 // won't get freed now)
773 parent->removeKid( terra_transform );
775 SG_LOG( SG_TERRAIN, SG_ALERT,
776 "parent pointer is NULL! Dying" );
780 SG_LOG( SG_TERRAIN, SG_ALERT,
781 "Parent count is zero for an ssg tile! Dying" );
785 // find the terrain lighting branch
786 if ( lights_transform ) {
787 pcount = lights_transform->getNumParents();
789 // find the first parent (should only be one)
790 ssgBranch *parent = lights_transform->getParent( 0 ) ;
792 // disconnect the light branch (we previously ref()'d
793 // it so it won't get freed now)
794 parent->removeKid( lights_transform );
796 SG_LOG( SG_TERRAIN, SG_ALERT,
797 "parent pointer is NULL! Dying" );
801 SG_LOG( SG_TERRAIN, SG_ALERT,
802 "Parent count is zero for an ssg light tile! Dying" );