+ "FREEING TILE = (" << tile_bucket << ")" );
+
+ SG_LOG( SG_TERRAIN, SG_DEBUG, "(start) free_tracker = " << free_tracker );
+
+ if ( !(free_tracker & NODES) ) {
+ free_tracker |= NODES;
+ } else if ( !(free_tracker & VEC_PTRS) ) {
+ free_tracker |= VEC_PTRS;
+ } else if ( !(free_tracker & TERRA_NODE) ) {
+ // delete the terrain branch (this should already have been
+ // disconnected from the scene graph)
+ SG_LOG( SG_TERRAIN, SG_DEBUG, "FREEING terra_transform" );
+ if ( fgPartialFreeSSGtree( terra_transform.get(), delete_size ) == 0 ) {
+ terra_transform = 0;
+ free_tracker |= TERRA_NODE;
+ }
+ } else if ( !(free_tracker & GROUND_LIGHTS) && gnd_lights_transform.get() ) {
+ // delete the terrain lighting branch (this should already have been
+ // disconnected from the scene graph)
+ SG_LOG( SG_TERRAIN, SG_DEBUG, "FREEING gnd_lights_transform" );
+ if ( fgPartialFreeSSGtree( gnd_lights_transform.get(), delete_size ) == 0 ) {
+ gnd_lights_transform = 0;
+ free_tracker |= GROUND_LIGHTS;
+ }
+ } else if ( !(free_tracker & VASI_LIGHTS) && vasi_lights_selector.get() ) {
+ // delete the runway lighting branch (this should already have
+ // been disconnected from the scene graph)
+ SG_LOG( SG_TERRAIN, SG_DEBUG, "FREEING vasi_lights_selector" );
+ if ( fgPartialFreeSSGtree( vasi_lights_selector.get(), delete_size ) == 0 ) {
+ vasi_lights_selector = 0;
+ free_tracker |= VASI_LIGHTS;
+ }
+ } else if ( !(free_tracker & RWY_LIGHTS) && rwy_lights_selector.get() ) {
+ // delete the runway lighting branch (this should already have
+ // been disconnected from the scene graph)
+ SG_LOG( SG_TERRAIN, SG_DEBUG, "FREEING rwy_lights_selector" );
+ if ( fgPartialFreeSSGtree( rwy_lights_selector.get(), delete_size ) == 0 ) {
+ rwy_lights_selector = 0;
+ free_tracker |= RWY_LIGHTS;
+ }
+ } else if ( !(free_tracker & TAXI_LIGHTS) && taxi_lights_selector.get() ) {
+ // delete the taxi lighting branch (this should already have been
+ // disconnected from the scene graph)
+ SG_LOG( SG_TERRAIN, SG_DEBUG, "FREEING taxi_lights_selector" );
+ if ( fgPartialFreeSSGtree( taxi_lights_selector.get(), delete_size ) == 0 ) {
+ taxi_lights_selector = 0;
+ free_tracker |= TAXI_LIGHTS;
+ }
+ } else if ( !(free_tracker & LIGHTMAPS) ) {
+ free_tracker |= LIGHTMAPS;
+ } else {
+ return true;
+ }
+
+ SG_LOG( SG_TERRAIN, SG_DEBUG, "(end) free_tracker = " << free_tracker );
+
+ // if we fall down to here, we still have work todo, return false
+ return false;
+}
+
+
+// Update the ssg transform node for this tile so it can be
+// properly drawn relative to our (0,0,0) point
+void FGTileEntry::prep_ssg_node( const Point3D& p, sgVec3 up, float vis) {
+ if ( !loaded ) return;
+
+ // visibility can change from frame to frame so we update the
+ // range selector cutoff's each time.
+ terra_range->setRange( 0, 0, vis + bounding_radius );
+
+ if ( gnd_lights_range.get() ) {
+ gnd_lights_range->setRange( 0, 0, vis * 1.5 + bounding_radius );
+ }
+
+ SGVec3d lt_trans(center.x(), center.y(), center.z());
+
+ FGLight *l = (FGLight *)(globals->get_subsystem("lighting"));
+ if ( gnd_lights_transform.get() ) {
+ // we need to lift the lights above the terrain to avoid
+ // z-buffer fighting. We do this based on our altitude and
+ // the distance this tile is away from scenery center.
+
+ // we expect 'up' to be a unit vector coming in, but since we
+ // modify the value of lift_vec, we need to create a local
+ // copy.
+ SGVec3f lift_vec(up);
+
+ double agl;
+ agl = globals->get_current_view()->getAltitudeASL_ft()*SG_FEET_TO_METER
+ - globals->get_current_view()->getSGLocation()->get_cur_elev_m();
+
+ // Compute the distance of the scenery center from the view position.
+ double dist = center.distance3D(p);
+
+ if ( general.get_glDepthBits() > 16 ) {
+ lift_vec *= 10.0 + agl / 100.0 + dist / 10000;
+ } else {
+ lift_vec *= 10.0 + agl / 20.0 + dist / 5000;
+ }
+
+ gnd_lights_transform->setTransform( lt_trans + toVec3d(lift_vec) );
+
+ // select which set of lights based on sun angle
+ float sun_angle = l->get_sun_angle() * SGD_RADIANS_TO_DEGREES;
+ if ( sun_angle > 95 ) {
+ gnd_lights_brightness->setSingleChildOn(2);
+ } else if ( sun_angle > 92 ) {
+ gnd_lights_brightness->setSingleChildOn(1);
+ } else if ( sun_angle > 89 ) {
+ gnd_lights_brightness->setSingleChildOn(0);
+ } else {
+ gnd_lights_brightness->setAllChildrenOff();
+ }
+ }
+
+ if ( rwy_lights_transform.get() ) {
+ // turn runway lights on/off based on sun angle and visibility
+ float sun_angle = l->get_sun_angle() * SGD_RADIANS_TO_DEGREES;
+ if ( sun_angle > 85 ||
+ (fgGetDouble("/environment/visibility-m") < 5000.0) ) {
+ rwy_lights_selector->setAllChildrenOn();
+ } else {
+ rwy_lights_selector->setAllChildrenOff();
+ }
+ }
+
+ if ( taxi_lights_transform.get() ) {
+ // turn taxi lights on/off based on sun angle and visibility
+ float sun_angle = l->get_sun_angle() * SGD_RADIANS_TO_DEGREES;
+ if ( sun_angle > 85 ||
+ (fgGetDouble("/environment/visibility-m") < 5000.0) ) {
+ taxi_lights_selector->setAllChildrenOn();
+ } else {
+ taxi_lights_selector->setAllChildrenOff();
+ }
+ }
+}
+
+
+osg::Node*
+FGTileEntry::gen_lights( SGMaterialLib *matlib, osg::Vec3Array *lights,
+ int inc, float bright )
+{
+ // generate a repeatable random seed
+ sg_srandom( (unsigned)(*lights)[0][0] );
+
+ // Allocate ssg structure
+ osg::Vec3Array *vl = new osg::Vec3Array;
+ osg::Vec4Array *cl = new osg::Vec4Array;
+
+ for ( unsigned i = 0; i < lights->size(); ++i ) {
+ // this loop is slightly less efficient than it otherwise
+ // could be, but we want a red light to always be red, and a
+ // yellow light to always be yellow, etc. so we are trying to
+ // preserve the random sequence.
+ float zombie = sg_random();
+ if ( i % inc == 0 ) {
+ vl->push_back( (*lights)[i] );
+
+ // factor = sg_random() ^ 2, range = 0 .. 1 concentrated towards 0
+ float factor = sg_random();
+ factor *= factor;
+
+ osg::Vec4 color;
+ if ( zombie > 0.5 ) {
+ // 50% chance of yellowish
+ color = osg::Vec4( 0.9, 0.9, 0.3, bright - factor * 0.2 );
+ } else if ( zombie > 0.15 ) {
+ // 35% chance of whitish
+ color = osg::Vec4( 0.9, 0.9, 0.8, bright - factor * 0.2 );
+ } else if ( zombie > 0.05 ) {
+ // 10% chance of orangish
+ color = osg::Vec4( 0.9, 0.6, 0.2, bright - factor * 0.2 );
+ } else {
+ // 5% chance of redish
+ color = osg::Vec4( 0.9, 0.2, 0.2, bright - factor * 0.2 );
+ }
+ cl->push_back( color );
+ }
+ }
+
+ // create ssg leaf
+ osg::Geometry* geometry = new osg::Geometry;
+ geometry->setVertexArray(vl);
+ geometry->setColorArray(cl);
+ geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
+ geometry->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, vl->size()));
+ osg::Geode* geode = new osg::Geode;
+ geode->addDrawable(geometry);
+
+ // assign state
+ SGMaterial *mat = matlib->find( "GROUND_LIGHTS" );
+ geode->setStateSet(mat->get_state());
+
+ return geode;
+}
+
+
+bool FGTileEntry::obj_load( const string& path,
+ osg::Group *geometry,
+ osg::Group *vasi_lights,
+ osg::Group *rwy_lights,
+ osg::Group *taxi_lights,
+ osg::Vec3Array *ground_lights, bool is_base )
+{
+ Point3D c; // returned center point
+ double br; // returned bounding radius
+
+ bool use_random_objects =
+ fgGetBool("/sim/rendering/random-objects", true);
+
+ // try loading binary format
+ if ( SGBinObjLoad( path, is_base,
+ &c, &br, globals->get_matlib(), use_random_objects,
+ geometry, vasi_lights, rwy_lights, taxi_lights,
+ ground_lights ) )
+ {
+ if ( is_base ) {
+ center = c;
+ bounding_radius = br;
+ }
+ }
+
+ return (geometry != NULL);
+}
+
+
+typedef enum {
+ OBJECT,
+ OBJECT_SHARED,
+ OBJECT_STATIC,
+ OBJECT_SIGN,
+ OBJECT_RUNWAY_SIGN
+} object_type;
+
+
+// storage class for deferred object processing in FGTileEntry::load()
+struct Object {
+ Object(object_type t, const string& token, const SGPath& p, istream& in)
+ : type(t), path(p)
+ {
+ in >> name;
+ if (type != OBJECT)
+ in >> lon >> lat >> elev >> hdg;
+ in >> ::skipeol;
+
+ if (type == OBJECT)
+ SG_LOG(SG_TERRAIN, SG_INFO, " " << token << " " << name);
+ else
+ SG_LOG(SG_TERRAIN, SG_INFO, " " << token << " " << name << " lon=" <<
+ lon << " lat=" << lat << " elev=" << elev << " hdg=" << hdg);
+ }
+ object_type type;
+ string name;
+ SGPath path;
+ double lon, lat, elev, hdg;
+};
+
+
+void
+FGTileEntry::load( const string_list &path_list, bool is_base )
+{
+ bool found_tile_base = false;
+
+ SGPath object_base;
+ vector<const Object*> objects;
+
+ string index_str = tile_bucket.gen_index_str();
+ SG_LOG( SG_TERRAIN, SG_INFO, "Loading tile " << index_str );
+
+ // scan and parse all files and store information
+ for (unsigned int i = 0; i < path_list.size(); i++) {
+ // If we found a terrain tile in Terrain/, we have to process the
+ // Objects/ dir in the same group, too, before we can stop scanning.
+ // FGGlobals::set_fg_scenery() inserts an empty string to path_list
+ // as marker.
+ if (path_list[i].empty()) {
+ if (found_tile_base)
+ break;
+ else
+ continue;
+ }
+
+ bool has_base = false;
+
+ SGPath tile_path = path_list[i];
+ tile_path.append( tile_bucket.gen_base_path() );
+
+ SGPath basename = tile_path;
+ basename.append( index_str );
+
+ SG_LOG( SG_TERRAIN, SG_INFO, " Trying " << basename.str() );
+
+
+ // Check for master .stg (scene terra gear) file
+ SGPath stg_name = basename;
+ stg_name.concat( ".stg" );
+
+ sg_gzifstream in( stg_name.str() );
+ if ( !in.is_open() )
+ continue;
+
+ while ( ! in.eof() ) {
+ string token;
+ in >> token;
+
+ if ( token[0] == '#' ) {
+ in >> ::skipeol;
+ continue;
+ }
+ // Load only once (first found)
+ if ( token == "OBJECT_BASE" ) {
+ string name;
+ in >> name >> ::skipws;
+ SG_LOG( SG_TERRAIN, SG_INFO, " " << token << " " << name );
+
+ if (!found_tile_base) {
+ found_tile_base = true;
+ has_base = true;
+
+ object_base = tile_path;
+ object_base.append(name);
+
+ } else
+ SG_LOG(SG_TERRAIN, SG_INFO, " (skipped)");
+
+ // Load only if base is not in another file
+ } else if ( token == "OBJECT" ) {
+ if (!found_tile_base || has_base)
+ objects.push_back(new Object(OBJECT, token, tile_path, in));
+ else {
+ string name;
+ in >> name >> ::skipeol;
+ SG_LOG(SG_TERRAIN, SG_INFO, " " << token << " "
+ << name << " (skipped)");
+ }
+
+ // Always OK to load
+ } else if ( token == "OBJECT_STATIC" ) {
+ objects.push_back(new Object(OBJECT_STATIC, token, tile_path, in));
+
+ } else if ( token == "OBJECT_SHARED" ) {
+ objects.push_back(new Object(OBJECT_SHARED, token, tile_path, in));
+
+ } else if ( token == "OBJECT_SIGN" ) {
+ objects.push_back(new Object(OBJECT_SIGN, token, tile_path, in));
+
+ } else if ( token == "OBJECT_RUNWAY_SIGN" ) {
+ objects.push_back(new Object(OBJECT_RUNWAY_SIGN, token, tile_path, in));
+
+ } else {
+ SG_LOG( SG_TERRAIN, SG_DEBUG,
+ "Unknown token '" << token << "' in " << stg_name.str() );
+ in >> ::skipws;
+ }
+ }
+ }
+
+
+ // obj_load() will generate ground lighting for us ...
+ osg::ref_ptr<osg::Vec3Array> light_pts = new osg::Vec3Array;
+ osg::Group* new_tile = new osg::Group;
+
+
+ if (found_tile_base) {
+ // load tile if found ...
+ osg::ref_ptr<osg::Group> geometry = new osg::Group;
+ if ( obj_load( object_base.str(), geometry.get(),
+ NULL, NULL, NULL, light_pts.get(), true ) ) {
+ new_tile -> addChild( geometry.get() );
+ }
+
+ } else {
+ // ... or generate an ocean tile on the fly
+ SG_LOG(SG_TERRAIN, SG_INFO, " Generating ocean tile");
+ osg::ref_ptr<osg::Group> geometry = new osg::Group;
+ Point3D c;
+ double br;
+ if ( SGGenTile( path_list[0], tile_bucket, &c, &br,
+ globals->get_matlib(), geometry.get() ) ) {
+ center = c;
+ bounding_radius = br;
+ new_tile -> addChild( geometry.get() );
+ } else {
+ SG_LOG( SG_TERRAIN, SG_ALERT,
+ "Warning: failed to generate ocean tile!" );
+ }
+ }