// construct.cxx -- Class to manage the primary data used in the
//                  construction process
//
// Written by Curtis Olson, started May 1999.
//
// Copyright (C) 1999  Curtis L. Olson  - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id: construct.cxx,v 1.4 2004-11-19 22:25:49 curt Exp $

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <iostream>
#include <sstream>

#include <boost/foreach.hpp>

#include <simgear/compiler.h>
#include <simgear/constants.h>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/math/SGGeometry.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/texcoord.hxx>
#include <simgear/io/sg_binobj.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/debug/logstream.hxx>

#include <Geometry/poly_support.hxx>
#include <Geometry/poly_extra.hxx>

#include <Match/match.hxx>
#include <Osgb36/osgb36.hxx>

#include "construct.hxx"
#include "usgs.hxx"

using std::string;

double gSnap = 0.00000001;      // approx 1 mm

static const double cover_size = 1.0 / 120.0;
static const double half_cover_size = cover_size * 0.5;

static unsigned int cur_poly_id = 0;

// If we don't offset land use squares by some amount, then we can get
// land use square boundaries coinciding with tile boundaries.
//
// This can foul up our edge matching scheme because a subsequently
// generated adjacent tile might be forced to have edge nodes not
// found in the first tile and not on the shared edge.  This can lead
// to gaps.  If we put skirts around everything that might hide the
// problem.
static const double quarter_cover_size = cover_size * 0.25;

// Constructor
TGConstruct::TGConstruct():
        useUKGrid(false),
        writeSharedEdges(true),
        useOwnSharedEdges(false),
        ignoreLandmass(false),
        debug_all(false),
        ds_id((void*)-1)
{ }


// Destructor
TGConstruct::~TGConstruct() { 
    array.close();

    // land class polygons
    polys_in.clear();
    polys_clipped.clear();

    // All Nodes
    nodes.clear();
}

void TGConstruct::set_debug( std::string path, std::vector<string> defs )
{
    SG_LOG(SG_GENERAL, SG_ALERT, "Set debug Path " << path);
    
    debug_path = path;

    /* Find any ids for our tile */
    for (unsigned int i=0; i< defs.size(); i++) {
        string dsd     = defs[i];
        size_t d_pos   = dsd.find(":");
        string tile    = dsd.substr(0, d_pos);

        if( tile == bucket.gen_index_str() ) {
            dsd.erase(0, d_pos+1);

            if ( dsd == "all" ) {
                debug_all = true;
            } else {
                std::stringstream ss(dsd);
                int i;

                while (ss >> i)
                {
                    SG_LOG(SG_GENERAL, SG_ALERT, "Adding debug file " << i);

                    debug_shapes.push_back(i);

                    if (ss.peek() == ',')
                        ss.ignore();
                }
            }
        }
    }
}

bool TGConstruct::IsDebugShape( unsigned int id )
{
    bool is_debug = false;

    /* Check global flag */
    if ( debug_all ) {
        is_debug = true;
    } else {
        for (unsigned int i=0; i<debug_shapes.size(); i++) {
            if ( debug_shapes[i] == id ) {
                is_debug = true;
                break;
            }
        }
    }

    return is_debug;
}

void TGConstruct::WriteDebugShape( const char* layer_name, const TGShape& shape )
{
    char name[64];
    shape.GetName( name );

    ds_id = tgShapefileOpenDatasource( ds_name );
    l_id  = tgShapefileOpenLayer( ds_id, layer_name );

    tgShapefileCreateFeature( ds_id, l_id, shape.clip_mask, "test" );

    // close after each write
    ds_id = tgShapefileCloseDatasource( ds_id );
}

void TGConstruct::WriteDebugPoly( const char* layer_name, const char* name, const TGPolygon& poly )
{
    ds_id = tgShapefileOpenDatasource( ds_name );
    l_id  = tgShapefileOpenLayer( ds_id, layer_name );

    tgShapefileCreateFeature( ds_id, l_id, poly, name );

    // close after each write
    ds_id = tgShapefileCloseDatasource( ds_id );
}

void TGConstruct::WriteDebugPolys( const char* layer_name, const poly_list& polys )
{
    ds_id = tgShapefileOpenDatasource( ds_name );
    l_id  = tgShapefileOpenLayer( ds_id, layer_name );
    
    for( unsigned int i=0; i<polys.size(); i++ ) {        
        sprintf( feature_name, "poly_%d", i );
        tgShapefileCreateFeature( ds_id, l_id, polys[i], feature_name );
    }

    // close after each write
    ds_id = tgShapefileCloseDatasource( ds_id );
}

// STEP 1
// Load elevation data from an Array file (a regular grid of elevation data) 
// and return list of fitted nodes.
void TGConstruct::LoadElevationArray( void ) {
    string base = bucket.gen_base_path();
    int i;

    for ( i = 0; i < (int)load_dirs.size(); ++i ) {
        string array_path = get_work_base() + "/" + load_dirs[i] + "/" + base + "/" + bucket.gen_index_str();

        if ( array.open(array_path) ) {
            break;
        } else {
            SG_LOG(SG_GENERAL, SG_DEBUG, "Failed to open Array file " << array_path);
        }
    }

    array.parse( bucket );
    array.remove_voids( );

    point_list corner_list = array.get_corner_list();
    for (unsigned int i=0; i<corner_list.size(); i++) {
        nodes.unique_add(corner_list[i]);
    }

    point_list fit_list = array.get_fitted_list();
    for (unsigned int i=0; i<fit_list.size(); i++) {
        nodes.unique_add(fit_list[i]);
    }
}

// Add a polygon to the clipper.
void TGConstruct::add_poly( int area, const TGPolygon &poly, string material ) {
    TGShape shape;
    TGSuperPoly sp;

    if ( area < TG_MAX_AREA_TYPES ) {
        sp.set_poly( poly );
        sp.set_material( material );

        shape.sps.push_back( sp );

        polys_in.add_shape( area, shape );
    } else {
        SG_LOG( SG_CLIPPER, SG_ALERT, "Polygon type out of range = " << area);
        exit(-1);
    }
}

bool TGConstruct::load_poly(const string& path) {
    bool poly3d = false;
    bool with_tp = false;
    string first_line;
    string poly_name;
    AreaType poly_type;
    int contours, count, i, j, k;
    int hole_flag;
    int num_polys;
    double startx, starty, startz, x, y, z, lastx, lasty, lastz;

    sg_gzifstream in( path );

    if ( !in ) {
        SG_LOG( SG_CLIPPER, SG_ALERT, "Cannot open file: " << path );
        exit(-1);
    }

    TGPolygon   poly;
    TGTexParams tp;
    Point3D     p;
    
    // (this could break things, why is it here) in >> skipcomment;
    while ( !in.eof() ) {
        in >> first_line;
        if ( first_line == "#2D" ) {
            poly3d = false;
            with_tp = false;

            in >> poly_name;
            num_polys = 1;
        } else if ( first_line == "#2D_WITH_MASK" ) {
            poly3d = false;
            with_tp = false;

            in >> poly_name;
            in >> num_polys;
        } else if ( first_line == "#2D_WITH_TPS" ) {
            poly3d = false;
            with_tp = true;

            in >> poly_name;
            in >> num_polys;
        } else if ( first_line == "#3D" ) {
            poly3d = true;
            with_tp = false;

            in >> poly_name;
            num_polys = 1;
        } else {
            // support old format (default to 2d)
            poly3d = false;
            with_tp = false;

            poly_name = first_line;
            num_polys = 1;
        }
        poly_type = get_area_type( poly_name );

        int area = (int)poly_type;
        string material; 

        // only allow 1000 shapes per material
        int extension = polys_in.area_size( area ) / 1000;

        if (extension)
        {
            char buff[32];
            sprintf( buff, "%s_%d", get_area_name( area ).c_str(), extension );
            material = buff;
        }
        else
        {
            material = get_area_name( area );
        }
        
        
        // Generate a new Shape for the poly
        TGShape     shape;
        TGSuperPoly sp;

        for (k=0; k<num_polys;k++) {

            if ( with_tp ) {
                double width, length;
                double heading;
                double minu, maxu;
                double minv, maxv;

                in >> x;
                in >> y;
                in >> width;
                in >> length;
                in >> heading;
                in >> minu;
                in >> maxu;
                in >> minv;
                in >> maxv;

                tp.set_ref( Point3D(x, y, 0.0f) );
                tp.set_width( width );
                tp.set_length( length );
                tp.set_heading( heading );
                tp.set_minu( minu );
                tp.set_maxu( maxu );
                tp.set_minv( minv );
                tp.set_maxv( maxv );
            }

            in >> contours;

            poly.erase();

            for ( i = 0; i < contours; ++i ) {
                in >> count;

                if ( count < 3 ) {
                    SG_LOG( SG_CLIPPER, SG_ALERT, "Polygon with less than 3 data points." );
                    exit(-1);
                }

                in >> hole_flag;

                in >> startx;
                in >> starty;
                if ( poly3d ) {
                    in >> startz;
                } else {
                    startz = -9999.0;
                }
            
                p = Point3D(startx+nudge, starty+nudge, startz);
                p.snap( gSnap );
                poly.add_node( i, p );

                if ( poly3d ) {
                    nodes.unique_add_fixed_elevation( p );
                } else {
                    nodes.unique_add( p );
                }

                for ( j = 1; j < count - 1; ++j ) {
                    in >> x;
                    in >> y;
                    if ( poly3d ) {
                        in >> z;
                    } else {
                        z = -9999.0;
                    }
                    p = Point3D( x+nudge, y+nudge, z );
                    p.snap( gSnap );
                    poly.add_node( i, p );
                    if ( poly3d ) {
                        nodes.unique_add_fixed_elevation( p );
                    } else {
                        nodes.unique_add( p );
                    }
                }

                in >> lastx;
                in >> lasty;
                if ( poly3d ) {
                    in >> lastz;
                } else {
                    lastz = -9999.0;
                }

                if ( (fabs(startx - lastx) < SG_EPSILON) && 
                     (fabs(starty - lasty) < SG_EPSILON) && 
                     (fabs(startz - lastz) < SG_EPSILON) ) {
                    // last point same as first, discard
                } else {
                    p = Point3D( lastx+nudge, lasty+nudge, lastz );
                    p.snap( gSnap );
                    poly.add_node( i, p );
                    if ( poly3d ) {
                        nodes.unique_add_fixed_elevation( p );
                    } else {
                        nodes.unique_add( p );
                    }
                }
            }

            poly = remove_dups( poly );

            sp.set_poly( poly );
            sp.set_material( material );
            shape.sps.push_back( sp );

            if ( with_tp ) {
                shape.textured = true;
                shape.tps.push_back( tp );
            }
            else
            {
                shape.textured = false;
            }

            in >> skipcomment;
        }

        // Once the full poly is loaded, build the clip mask
        shape.BuildMask();
        shape.area = area;
        shape.id = cur_poly_id++;

        polys_in.add_shape( area, shape );

        if ( IsDebugShape( shape.id ) ) {
            WriteDebugShape( "loaded", shape );
        }
    }

    return true;
}


// Load a polygon definition file containing osgb36 Eastings and Northings
// and convert them to WGS84 Latitude and Longitude
bool TGConstruct::load_osgb36_poly(const string& path) {
    string poly_name;
    AreaType poly_type;
    int contours, count, i, j;
    int hole_flag;
    double startx, starty, x, y, lastx, lasty;

    SG_LOG( SG_CLIPPER, SG_INFO, "Loading " << path << " ..." );

    sg_gzifstream in( path );

    if ( !in ) {
        SG_LOG( SG_CLIPPER, SG_ALERT, "Cannot open file: " << path );
        exit(-1);
    }

    TGPolygon poly;

    Point3D p;
    Point3D OSRef;
    in >> skipcomment;
    while ( !in.eof() ) {
        in >> poly_name;
        SG_LOG( SG_CLIPPER, SG_INFO, "poly name = " << poly_name);
        poly_type = get_area_type( poly_name );
        SG_LOG( SG_CLIPPER, SG_INFO, "poly type (int) = " << (int)poly_type);
        in >> contours;
        SG_LOG( SG_CLIPPER, SG_INFO, "num contours = " << contours);

        poly.erase();

        for ( i = 0; i < contours; ++i ) {
            in >> count;

            if ( count < 3 ) {
                SG_LOG( SG_CLIPPER, SG_ALERT, "Polygon with less than 3 data points." );
                exit(-1);
            }

            in >> hole_flag;

            in >> startx;
            in >> starty;
            OSRef = Point3D(startx, starty, -9999.0);

            //Convert from OSGB36 Eastings/Northings to WGS84 Lat/Lon
            //Note that startx and starty themselves must not be altered since we compare them with unaltered lastx and lasty later
            p = OSGB36ToWGS84(OSRef);
	    
            poly.add_node( i, p );
            nodes.unique_add( p );

            SG_LOG( SG_CLIPPER, SG_BULK, "0 = " << startx << ", " << starty );

            for ( j = 1; j < count - 1; ++j ) {
                in >> x;
                in >> y;
                OSRef = Point3D( x, y, -9999.0 );
                p = OSGB36ToWGS84(OSRef);
		
                poly.add_node( i, p );
                nodes.unique_add( p );
                SG_LOG( SG_CLIPPER, SG_BULK, j << " = " << x << ", " << y );
            }

            in >> lastx;
            in >> lasty;

            if ( (fabs(startx - lastx) < SG_EPSILON) && 
                 (fabs(starty - lasty) < SG_EPSILON) ) {
                // last point same as first, discard
            } else {
                OSRef = Point3D( lastx, lasty, -9999.0 );
                p = OSGB36ToWGS84(OSRef);
		
                poly.add_node( i, p );
                nodes.unique_add( p );
                SG_LOG( SG_CLIPPER, SG_BULK, count - 1 << " = " << lastx << ", " << lasty );
            }
    	}

        // TODO : Make like OGR 
        int area = (int)poly_type;
        string material = get_area_name( area );
        add_poly(area, poly, material);
        // END TODO
    
        in >> skipcomment;
    }

    return true;
}

// load all 2d polygons from the specified load disk directories and
// clip against each other to resolve any overlaps
int TGConstruct::LoadLandclassPolys( void ) {
    int i;

    string base = bucket.gen_base_path();
    string poly_path;
    int count = 0;

    polys_in.clear();

    // load 2D polygons from all directories provided
    for ( i = 0; i < (int)load_dirs.size(); ++i ) {
        poly_path = get_work_base() + "/" + load_dirs[i] + '/' + base;

        string tile_str = bucket.gen_index_str();
        simgear::Dir d(poly_path);
        if (!d.exists()) {
            SG_LOG(SG_GENERAL, SG_DEBUG, "directory not found: " << poly_path);
            continue;
        }
    
        simgear::PathList files = d.children(simgear::Dir::TYPE_FILE);
        SG_LOG( SG_CLIPPER, SG_ALERT, files.size() << " Polys in " << d.path() );
        
        BOOST_FOREACH(const SGPath& p, files) {
            if (p.file_base() != tile_str) {
                continue;
            }
        
            string lext = p.complete_lower_extension();
            if ((lext == "arr") || (lext == "arr.gz") || (lext == "btg.gz") ||
                (lext == "fit") || (lext == "fit.gz") || (lext == "ind")) 
            {
                // skipped!
            } else if (lext == "osgb36") {
                SG_LOG(SG_GENERAL, SG_ALERT, " Loading osgb36 poly definition file " << p.file());
                load_osgb36_poly( p.str() );
                ++count;
            } else {
                load_poly( p.str() );
                SG_LOG(SG_GENERAL, SG_ALERT, " Loaded " << p.file());
                ++count;
            }
        } // of directory file children
    }
    SG_LOG(SG_GENERAL, SG_ALERT, " Total polys used for this tile: " << count );
    return count;
}

// Add a polygon to a list, merging if possible.
//
// Merge a polygon with an existing one if possible, append a new one
// otherwise; this function is used by actual_load_landcover, below,
// to reduce the number of separate polygons.
void TGConstruct::add_to_polys ( TGPolygon &accum, const TGPolygon &poly) {
    if ( accum.contours() > 0 ) {
        accum = tgPolygonUnionClipper( accum, poly );
    } else {
        accum = poly;
    }
}

// make the area specified area, look up the land cover type, and add
// it to polys
void TGConstruct::make_area( const LandCover &cover, TGPolygon *polys,
                       double x1, double y1, double x2, double y2,
                       double half_dx, double half_dy )
{
    const double fudge = 0.0001;  // (0.0001 degrees =~ 10 meters)

    AreaType area = get_landcover_type( cover,
                                   x1 + half_dx, y1 + half_dy,
                                   x2 - x1, y2 - y1 );

    if ( area != get_default_area_type() ) {
        // Create a square polygon and merge it into the list.
        TGPolygon poly;
        poly.erase();
        poly.add_node(0, Point3D(x1 - fudge, y1 - fudge, 0.0));
        poly.add_node(0, Point3D(x1 - fudge, y2 + fudge, 0.0));
        poly.add_node(0, Point3D(x2 + fudge, y2 + fudge, 0.0));
        poly.add_node(0, Point3D(x2 + fudge, y1 - fudge, 0.0));

        if ( measure_roughness( poly ) < 1.0 ) {
            add_to_polys(polys[area], poly);
        }
    }
}

// Come up with a "rough" metric for the roughness of the terrain
// coverted by a polygon
double TGConstruct::measure_roughness( TGPolygon &poly ) {
    int i;
    unsigned int j;

    // find the elevation range
    double max_z = -9999.0;
    double min_z = 9999.0;

    for ( i = 0; i < poly.contours(); ++i ) {
        point_list points = poly.get_contour( i );
        for ( j = 0; j < points.size(); ++j ) {
            double z;
            z = array.altitude_from_grid( points[j].x() * 3600.0, 
                                          points[j].y() * 3600.0 );
            if ( z < -9000 ) {
                z = array.closest_nonvoid_elev( points[j].x() * 3600.0, 
                                                points[j].y() * 3600.0 );
            }

            if ( z < min_z ) {
                min_z = z;
            }
            if ( z > max_z ) {
                max_z = z;
            }
        }
    }

    double diff = max_z - min_z;

    // 50m difference in polygon elevation range yields a roughness
    // metric of 1.0.  Less than 1.0 is relatively flat.  More than
    // 1.0 is relatively rough.

    SG_LOG(SG_GENERAL, SG_ALERT, "roughness = " << diff / 50.0 );

    return diff / 50.0;
}

AreaType TGConstruct::get_landcover_type (const LandCover &cover, double xpos, double ypos, double dx, double dy)
{
    // Look up the land cover for the square
    int cover_value = cover.getValue(xpos, ypos);
    AreaType area = translateUSGSCover(cover_value);

    if ( area != get_default_area_type() ) {
        // Non-default area is fine.
        return area;
    } else {
        // If we're stuck with the default area, try to borrow from a
        // neighbour.
        for (double x = xpos - dx; x <= xpos + dx; x += dx) {
            for (double y = ypos - dy; y < ypos + dx; y += dy) {
                if (x != xpos || y != ypos) {
                    cover_value = cover.getValue(x, y);
                    area = translateUSGSCover(cover_value);
                    if (area != get_default_area_type() ) {
                        return area;
                    }
                }
            }
        }
    }

    // OK, give up and return default
    return get_default_area_type();
}


// Generate polygons from land-cover raster.  Horizontally- or
// vertically-adjacent polygons will be merged automatically.
int TGConstruct::load_landcover()
{
    int count = 0;

    try {

        LandCover cover(get_cover());
        TGPolygon polys[TG_MAX_AREA_TYPES];
        TGPolygon poly;		// working polygon

        // Get the lower left (SW) corner of the tile
        double base_lon = bucket.get_center_lon()
            - 0.5 * bucket.get_width()
            - quarter_cover_size;
        double base_lat = bucket.get_center_lat()
            - 0.5 * bucket.get_height()
            - quarter_cover_size;

        SG_LOG(SG_GENERAL, SG_ALERT, "raster land cover: tile at " << base_lon << ',' << base_lat);
    
        double max_lon = bucket.get_center_lon()
            + 0.5 * bucket.get_width();
        double max_lat = bucket.get_center_lat()
            + 0.5 * bucket.get_height();

        SG_LOG(SG_GENERAL, SG_ALERT, "raster land cover: extends to " << max_lon << ',' << max_lat);

        double x1 = base_lon;
        double y1 = base_lat;
        double x2 = x1 + cover_size;
        double y2 = y1 + cover_size;

        while ( x1 < max_lon ) {
            while ( y1 < max_lat ) {
                make_area( cover, polys, x1, y1, x2, y2, half_cover_size, half_cover_size );
                
                y1 = y2;
                y2 += cover_size;
            }

            x1 = x2;
            x2 += cover_size;
            y1 = base_lat;
            y2 = y1 + cover_size;
        }

        // Now that we're finished looking up land cover, we have a list
        // of lists of polygons, one (possibly-empty) list for each area
        // type.  Add the remaining polygons to the clipper.
        for ( int i = 0; i < TG_MAX_AREA_TYPES; i++ ) {
            if ( polys[i].contours() ) {
                // TODO : REMOVE add_poly
                add_poly( i, polys[i], get_area_name((AreaType)i ));
                count++;
            }
        }
    } catch ( string e ) {
        SG_LOG(SG_GENERAL, SG_ALERT, "Died with exception: " << e);
        exit(-1);
    }

    // Return the number of polygons actually read.
    return count;
}

void TGConstruct::FixTJunctions( void ) {
    int before, after;

    // traverse each poly, and add intermediate nodes
    for ( unsigned int i = 0; i < TG_MAX_AREA_TYPES; ++i ) {
        for( unsigned int j = 0; j < polys_clipped.area_size(i); ++j ) {
            for( unsigned int k = 0; k < polys_clipped.shape_size(i, j); ++k ) {
                TGPolygon current = polys_clipped.get_poly(i, j, k);

                before  = current.total_size();
                current = add_tgnodes_to_poly( current, &nodes );
                after   = current.total_size();

                if (before != after) {
                   SG_LOG( SG_CLIPPER, SG_INFO, "Fixed T-Junctions in " << get_area_name( (AreaType)i ) << ":" << j+1 << "-" << k << " of " << (int)polys_clipped.area_size(i) << " nodes increased from " << before << " to " << after );   
                }

                /* Save it back */
                polys_clipped.set_poly( i, j, k, current );
            }
        }
    }
}

void TGConstruct::calc_gc_course_dist( const Point3D& start, const Point3D& dest, 
                                       double *course, double *dist )
{
    SGGeoc gs = start.toSGGeoc();
    SGGeoc gd = dest.toSGGeoc();
    *course = SGGeoc::courseRad(gs, gd);
    *dist = SGGeoc::distanceM(gs, gd);
}

// calculate spherical distance between two points (lon, lat specified
// in degrees, result returned in meters)
double TGConstruct::distanceSphere( const Point3D p1, const Point3D p2 ) {
    Point3D r1( p1.x() * SG_DEGREES_TO_RADIANS,
                p1.y() * SG_DEGREES_TO_RADIANS,
                p1.z() );
    Point3D r2( p2.x() * SG_DEGREES_TO_RADIANS,
                p2.y() * SG_DEGREES_TO_RADIANS,
                p2.z() );

    double course, dist_m;
    calc_gc_course_dist( r1, r2, &course, &dist_m );

    return dist_m;
}

// fix the elevations of the geodetic nodes
// This should be done in the nodes class itself, except for the need for the triangle type
// hopefully, this will get better when we have the area lookup via superpoly...
void TGConstruct::CalcElevations( void )
{
    TGPolyNodes tri_nodes;
    double e1, e2, e3, min;
    int    n1, n2, n3;
    Point3D p;

    SG_LOG(SG_GENERAL, SG_ALERT, "fixing node heights");

    for (int i = 0; i < (int)nodes.size(); ++i) {
        TGNode node = nodes.get_node( i );
        Point3D pos = node.GetPosition();

        if ( !node.GetFixedPosition() ) {
            // set elevation as interpolated point from DEM data.
            nodes.SetElevation( i, array.altitude_from_grid(pos.x() * 3600.0, pos.y() * 3600.0) );
        }
    }

    // now flatten some stuff
    for (unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++) {
        if ( is_lake_area( (AreaType)area ) ) {
            for (int shape = 0; shape < (int)polys_clipped.area_size(area); ++shape ) {
                for (int segment = 0; segment < (int)polys_clipped.shape_size(area, shape); ++segment ) {

                    SG_LOG( SG_CLIPPER, SG_INFO, "Flattening " << get_area_name( (AreaType)area ) << ":" << shape+1 << "-" << segment << " of " << (int)polys_clipped.area_size(area) );   
                    tri_nodes = polys_clipped.get_tri_idxs( area, shape, segment );

                    for (int tri=0; tri < tri_nodes.contours(); tri++) {
                        if (tri_nodes.contour_size( tri ) != 3) {
                            SG_LOG(SG_GENERAL, SG_ALERT, "triangle doesnt have 3 nodes" << tri_nodes.contour_size( tri ) );
                            exit(0);                        
                        }

                        n1 = tri_nodes.get_pt( tri, 0 );
                        e1 = nodes.get_node(n1).GetPosition().z();
                        n2 = tri_nodes.get_pt( tri, 1 );
                        e2 = nodes.get_node(n2).GetPosition().z();
                        n3 = tri_nodes.get_pt( tri, 2 );
                        e3 = nodes.get_node(n3).GetPosition().z();

                        min = e1;
                        if ( e2 < min ) { min = e2; }                  
                        if ( e3 < min ) { min = e3; }                  

                        nodes.SetElevation( n1, min );
                        nodes.SetElevation( n2, min );
                        nodes.SetElevation( n3, min );
                    }
                }
            }
        }

        if ( is_stream_area( (AreaType)area ) ) {
            for (int shape = 0; shape < (int)polys_clipped.area_size(area); ++shape ) {
                for (int segment = 0; segment < (int)polys_clipped.shape_size(area, shape); ++segment ) {

                    SG_LOG( SG_CLIPPER, SG_INFO, "Flattening " << get_area_name( (AreaType)area ) << ":" << shape+1 << "-" << segment << " of " << (int)polys_clipped.area_size(area) );   
                    tri_nodes = polys_clipped.get_tri_idxs( area, shape, segment );

                    for (int tri=0; tri < tri_nodes.contours(); tri++) {
                        if (tri_nodes.contour_size( tri ) != 3) {
                            SG_LOG(SG_GENERAL, SG_ALERT, "triangle doesnt have 3 nodes" << tri_nodes.contour_size( tri ) );
                            exit(0);                        
                        }

                        point_list raw_nodes = nodes.get_geod_nodes();

                        n1 = tri_nodes.get_pt( tri, 0 );
                        e1 = nodes.get_node(n1).GetPosition().z();
                        n2 = tri_nodes.get_pt( tri, 1 );
                        e2 = nodes.get_node(n2).GetPosition().z();
                        n3 = tri_nodes.get_pt( tri, 2 );
                        e3 = nodes.get_node(n3).GetPosition().z();

                        min = e1;
                        p   = raw_nodes[n1];

                        if ( e2 < min ) { min = e2; p = raw_nodes[n2]; }                  
                        if ( e3 < min ) { min = e3; p = raw_nodes[n3]; }                  

                        double d1 = distanceSphere( p, raw_nodes[n1] ); 
                        double d2 = distanceSphere( p, raw_nodes[n2] ); 
                        double d3 = distanceSphere( p, raw_nodes[n3] ); 

                        double max1 = d1 * 0.20 + min;
                        double max2 = d2 * 0.20 + min;
                        double max3 = d3 * 0.20 + min;

                        if ( max1 < e1 ) { nodes.SetElevation( n1, max1 ); }
                        if ( max2 < e2 ) { nodes.SetElevation( n2, max2 ); }
                        if ( max3 < e3 ) { nodes.SetElevation( n3, max3 ); }
                    }
                }
            }
        }

        if ( is_road_area( (AreaType)area ) ) {
            for (int shape = 0; shape < (int)polys_clipped.area_size(area); ++shape ) {
                for (int segment = 0; segment < (int)polys_clipped.shape_size(area, shape); ++segment ) {

                    SG_LOG( SG_CLIPPER, SG_INFO, "Flattening " << get_area_name( (AreaType)area ) << ":" << shape+1 << "-" << segment << " of " << (int)polys_clipped.area_size(area) );   
                    tri_nodes = polys_clipped.get_tri_idxs( area, shape, segment );

                    for (int tri=0; tri < tri_nodes.contours(); tri++) {
                        if (tri_nodes.contour_size( tri ) != 3) {
                            SG_LOG(SG_GENERAL, SG_ALERT, "triangle doesnt have 3 nodes" << tri_nodes.contour_size( tri ) );
                            exit(0);                        
                        }

                        point_list raw_nodes = nodes.get_geod_nodes();

                        n1 = tri_nodes.get_pt( tri, 0 );
                        e1 = nodes.get_node(n1).GetPosition().z();
                        n2 = tri_nodes.get_pt( tri, 1 );
                        e2 = nodes.get_node(n2).GetPosition().z();
                        n3 = tri_nodes.get_pt( tri, 2 );
                        e3 = nodes.get_node(n3).GetPosition().z();

                        min = e1;
                        p   = raw_nodes[n1];

                        if ( e2 < min ) { min = e2; p = raw_nodes[n2]; }                  
                        if ( e3 < min ) { min = e3; p = raw_nodes[n3]; }                  

                        double d1 = distanceSphere( p, raw_nodes[n1] ); 
                        double d2 = distanceSphere( p, raw_nodes[n2] ); 
                        double d3 = distanceSphere( p, raw_nodes[n3] ); 

                        double max1 = d1 * 0.30 + min;
                        double max2 = d2 * 0.30 + min;
                        double max3 = d3 * 0.30 + min;

                        if ( max1 < e1 ) { nodes.SetElevation( n1, max1 ); }
                        if ( max2 < e2 ) { nodes.SetElevation( n2, max2 ); }
                        if ( max3 < e3 ) { nodes.SetElevation( n3, max3 ); }
                    }
                }
            }
        }

        if ( is_ocean_area( (AreaType)area ) ) {
            for (int shape = 0; shape < (int)polys_clipped.area_size(area); ++shape ) {
                for (int segment = 0; segment < (int)polys_clipped.shape_size(area, shape); ++segment ) {

                    SG_LOG( SG_CLIPPER, SG_INFO, "Flattening " << get_area_name( (AreaType)area ) << ":" << shape+1 << "-" << segment << " of " << (int)polys_clipped.area_size(area) );   
                    tri_nodes = polys_clipped.get_tri_idxs( area, shape, segment );

                    for (int tri=0; tri < tri_nodes.contours(); tri++) {
                        if (tri_nodes.contour_size( tri ) != 3) {
                            SG_LOG(SG_GENERAL, SG_ALERT, "triangle doesnt have 3 nodes" << tri_nodes.contour_size( tri ) );
                            exit(0);                        
                        }

                        n1 = tri_nodes.get_pt( tri, 0 );
                        n2 = tri_nodes.get_pt( tri, 1 );
                        n3 = tri_nodes.get_pt( tri, 2 );

                        nodes.SetElevation( n1, 0.0 );
                        nodes.SetElevation( n2, 0.0 );
                        nodes.SetElevation( n3, 0.0 );
                    }
                }
            }
        }
    }
}

TGPolygon TGConstruct::area_tex_coords( const TGPolygon& tri )
{
    TGPolygon result;
    result.erase();

    // lots of conversion needed to use simgear API - perhaps we need a new simgear API?        
    for (int c=0; c<tri.contours(); c++)
    {
        // get the points, and calculate the elevations
        point_list nodes = tri.get_contour(c);
        std::vector< SGGeod > conv_geods;
        point_list tex_coords;

        for (int i = 0; i < (int)nodes.size(); ++i ) 
        {
            SGGeod conv_geod = SGGeod::fromDegM( nodes[i].x(), nodes[i].y(), nodes[i].z() );
        	SG_LOG(SG_GENERAL, SG_DEBUG, "geod pt = " << nodes[i] );
           	conv_geods.push_back( conv_geod );
        }
        
        // now calculate texture coordinates
        // generate identity interger list...  
        std::vector< int > node_idx;
        for (int i = 0; i < (int)conv_geods.size(); i++) {
            node_idx.push_back(i);
        }

        std::vector< SGVec2f > tp_list = sgCalcTexCoords( bucket, conv_geods, node_idx );
        // generate a contour of texture coordinates from the tp list
        for (int i = 0; i < (int)tp_list.size(); i++)
        {
            tex_coords.push_back( Point3D::fromSGVec2( tp_list[i] ) );
        }
        result.add_contour( tex_coords, 0 );
    }

    return result;
}

TGPolygon TGConstruct::linear_tex_coords( const TGPolygon& tri, const TGTexParams& tp )
{
    TGPolygon result;
    int i, j;

    result.erase();

    Point3D ref = tp.get_ref();
    double width = tp.get_width();
    double length = tp.get_length();
    double heading = tp.get_heading();
    double minu = tp.get_minu();
    double maxu = tp.get_maxu();
    double minv = tp.get_minv();
    double maxv = tp.get_maxv();
    SG_LOG( SG_GENERAL, SG_DEBUG, "section ref = " << ref );
    SG_LOG( SG_GENERAL, SG_DEBUG, "  width   = " << width );
    SG_LOG( SG_GENERAL, SG_DEBUG, "  length  = " << length );
    SG_LOG( SG_GENERAL, SG_DEBUG, "  heading = " << heading );
    SG_LOG( SG_GENERAL, SG_DEBUG, "  minv    = " << minv );
    SG_LOG( SG_GENERAL, SG_DEBUG, "  maxv    = " << maxv );
    SG_LOG( SG_GENERAL, SG_DEBUG, "  heading = " << heading );

    Point3D p, t;
    double x, y, tx, ty;

    for ( i = 0; i < tri.contours(); ++i ) 
    {
    	for ( j = 0; j < tri.contour_size( i ); ++j ) 
        {
    	    p = tri.get_pt( i, j );
    	    SG_LOG(SG_GENERAL, SG_DEBUG, "tex coords for contour " << i << " point " << j << ": " << p );

    	    //
    	    // 1. Calculate distance and bearing from the center of
    	    // the feature
    	    //

    	    // given alt, lat1, lon1, lat2, lon2, calculate starting
    	    // and ending az1, az2 and distance (s).  Lat, lon, and
    	    // azimuth are in degrees.  distance in meters
    	    double az1, az2, dist;
    	    geo_inverse_wgs_84( 0, ref.y(), ref.x(), p.y(), p.x(),
    				&az1, &az2, &dist );
    	    SG_LOG(SG_GENERAL, SG_DEBUG, "basic course from ref = " << az2);

    	    //
    	    // 2. Rotate this back into a coordinate system where Y
    	    // runs the length of the runway and X runs crossways.
    	    //

    	    double course = az2 - heading;
    	    while ( course < -360 ) { course += 360; }
    	    while ( course > 360 ) { course -= 360; }
    	    SG_LOG( SG_GENERAL, SG_DEBUG,
                        "  course = " << course << "  dist = " << dist );

    	    //
    	    // 3. Convert from polar to cartesian coordinates
    	    //

    	    x = sin( course * SGD_DEGREES_TO_RADIANS ) * dist;
    	    y = cos( course * SGD_DEGREES_TO_RADIANS ) * dist;
    	    SG_LOG(SG_GENERAL, SG_DEBUG, "  x = " << x << " y = " << y);

    	    //
    	    // 4. Map x, y point into texture coordinates
    	    //
    	    double tmp;

            tmp = x / width;
            tx = tmp * (maxu - minu) + minu;

            if ( tx < -1.0 )  { tx = -1.0; }
            if ( tx > 1.0 ) { tx = 1.0; }

    	    SG_LOG(SG_GENERAL, SG_DEBUG, "  (" << tx << ")");

            ty = (y/length) + minv;
    	    SG_LOG(SG_GENERAL, SG_DEBUG, "  (" << ty << ")");

    	    t = Point3D( tx, ty, 0 );
    	    SG_LOG(SG_GENERAL, SG_DEBUG, "  (" << tx << ", " << ty << ")");

    	    result.add_node( i, t );
    	}
    }

    return result;
}

// collect custom objects and move to scenery area
void TGConstruct::AddCustomObjects( void ) {
    // Create/open the output .stg file for writing
    SGPath dest_d(get_output_base().c_str());
    dest_d.append(bucket.gen_base_path().c_str());
    string dest_dir = dest_d.str_native();
    SGPath dest_i(dest_d);
    dest_i.append(bucket.gen_index_str());
    dest_i.concat(".stg");
    string dest_ind = dest_i.str_native();

    FILE *fp;
    if ( (fp = fopen( dest_ind.c_str(), "w" )) == NULL ) {
        SG_LOG( SG_GENERAL, SG_ALERT, "ERROR: opening " << dest_ind << " for writing!" );
        exit(-1);
    }

    // Start with the default custom object which is the base terrain
    fprintf(fp, "OBJECT_BASE %s.btg\n", bucket.gen_index_str().c_str());

    char line[2048];             // big enough?
    char token[256];
    char name[256];

    for ( int i = 0; i < (int)load_dirs.size(); ++i ) {
        SGPath base(get_work_base().c_str());
        base.append(load_dirs[i]);
        base.append( bucket.gen_base_path() );
        SGPath index(base);
        index.append( bucket.gen_index_str() );
        index.concat(".ind");
        string index_file = index.str_native();

        sg_gzifstream in( index_file );

        if ( ! in.is_open() ) {
            //No custom objects
        } else {
            while ( ! in.eof() ) {
                SG_LOG( SG_GENERAL, SG_INFO, "Collecting custom objects from " << index_file );
                in.getline(line, 2048);
                SG_LOG( SG_GENERAL, SG_INFO, "line = " << line );

                int result = sscanf( line, "%s %s", token, name );
                SG_LOG( SG_GENERAL, SG_INFO, "scanf scanned " << result << " tokens" );

                if ( result > 0 ) {
                    SG_LOG( SG_GENERAL, SG_INFO, "token = " << token << " name = " << name );

                    if ( strcmp( token, "OBJECT" ) == 0 ) {
                        base.append(name);
                        base.concat(".gz");
                        string basecom = base.str_native();
#ifdef _MSC_VER
                        string command = "copy " + basecom + " " + dest_dir;
#else
                        string command = "cp " + basecom + " " + dest_dir;
#endif
                        SG_LOG( SG_GENERAL, SG_INFO, "running " << command );
                        system( command.c_str() );

                        fprintf(fp, "OBJECT %s\n", name);
                    } else {
                        fprintf(fp, "%s\n", line);
                    }
                }
            }
        }
    }

    fclose(fp);
}

// Attempt to merge slivers into a list of polygons.
//
// For each sliver contour, see if a union with another polygon yields
// a polygon with no increased contours (i.e. the sliver is adjacent
// and can be merged.)  If so, replace the clipped polygon with the
// new polygon that has the sliver merged in.
void TGConstruct::merge_slivers( TGLandclass& clipped,  poly_list& slivers_list ) {
    TGPolygon poly, result, slivers, sliver;
    point_list contour;
    int original_contours, result_contours;
    bool done;
    int area, shape, segment, i, j;
    int merged = 0;
    int total = 0;
    
    for ( i = 0; i < (int)slivers_list.size(); i++ ) {
        slivers = slivers_list[i];

        for ( j = 0; j < slivers.contours(); ++j ) {
            // make the sliver polygon
            contour = slivers.get_contour( j );
            total++;
            
            sliver.erase();
            sliver.add_contour( contour, 0 );
            done = false;

            for ( area = 0; area < TG_MAX_AREA_TYPES && !done; ++area ) {
                if ( is_hole_area( area ) ) {
                	// don't merge a non-hole sliver in with a hole
                	continue;
                }

                for ( shape = 0; shape < (int)clipped.area_size(area) && !done; ++shape ) {
                    unsigned int shape_id = clipped.get_shape( area, shape ).id;

                    for ( segment = 0; segment < (int)clipped.shape_size(area, shape) && !done; ++segment ) {

                        poly = clipped.get_poly( area, shape, segment );
                        original_contours = poly.contours();
                        result = tgPolygonUnionClipper( poly, sliver );
                        result_contours = result.contours();

                        if ( original_contours == result_contours ) {
                            SG_LOG(SG_GENERAL, SG_INFO, "MERGED SLIVER " << i << ", " << j << " into area " << get_area_name( (AreaType)area ) << " id: " << shape_id << " segment: " << segment  );
                            
                            clipped.set_poly( area, shape, segment, result );
                            merged++;

                            /* add the sliver to the clip_mask, too */
                            TGPolygon mask = clipped.get_mask( area, shape );
                            result = tgPolygonUnionClipper( mask, sliver );
                            clipped.set_mask( area, shape, result );

                            if ( IsDebugShape( shape_id ) ) {
                                WriteDebugShape( "with_slivers", clipped.get_shape( area, shape ) );
                            }
                            
                            done = true;
                        }
                    }
                }
            }
        }
    }

    slivers_list.clear();
    
    SG_LOG(SG_GENERAL, SG_INFO, " UNMERGED SLIVERS: " << total - merged );
}

bool TGConstruct::ClipLandclassPolys( void ) {
    TGPolygon clipped, tmp;
    TGPolygon remains;
    TGPolygon safety_base;
    poly_list slivers;
    int i, j;
    Point3D p;
    point2d min, max;

#if !USE_ACCUMULATOR
    TGPolygon accum;
#endif
    
    // Get clip bounds
    min.x = bucket.get_center_lon() - 0.5 * bucket.get_width();
    min.y = bucket.get_center_lat() - 0.5 * bucket.get_height();
    max.x = bucket.get_center_lon() + 0.5 * bucket.get_width();
    max.y = bucket.get_center_lat() + 0.5 * bucket.get_height();

#if USE_ACCUMULATOR

    tgPolygonInitClipperAccumulator();
    
#else
    accum.erase();
#endif
    
    // set up clipping tile : and remember to add the nodes!
    safety_base.erase();

    p = Point3D(min.x, min.y, -9999.0);
    safety_base.add_node( 0, p );
    nodes.unique_add( p );

    p = Point3D(max.x, min.y, -9999.0);
    safety_base.add_node( 0, p );
    nodes.unique_add( p );

    p = Point3D(max.x, max.y, -9999.0);
    safety_base.add_node( 0, p );
    nodes.unique_add( p );

    p = Point3D(min.x, max.y, -9999.0);
    safety_base.add_node( 0, p );
    nodes.unique_add( p );

    // set up land mask, we clip most things to this since it is our
    // best representation of land vs. ocean.  If we have other less
    // accurate data that spills out into the ocean, we want to just
    // clip it.
    // also set up a mask for all water and islands
    TGPolygon land_mask, water_mask, island_mask;
    land_mask.erase();
    water_mask.erase();
    island_mask.erase();

    for ( i = 0; i < TG_MAX_AREA_TYPES; i++ ) {
        if ( is_landmass_area( i ) && !ignoreLandmass ) {
            for ( unsigned int j = 0; j < polys_in.area_size(i); ++j ) {
                land_mask = tgPolygonUnionClipper( land_mask, polys_in.get_mask(i, j) );

            }

        } else if ( is_water_area( i ) ) {
            for (unsigned int j = 0; j < polys_in.area_size(i); j++) {
                water_mask = tgPolygonUnionClipper( water_mask, polys_in.get_mask(i, j) );
            }
        } else if ( is_island_area( i ) ) {
            for (unsigned int j = 0; j < polys_in.area_size(i); j++) {
                island_mask = tgPolygonUnionClipper( island_mask, polys_in.get_mask(i, j) );
            }
        }
    }

    // Dump the masks
    if ( debug_all || debug_shapes.size() ) {
        WriteDebugPoly( "land_mask", "", land_mask );
        WriteDebugPoly( "water_mask", "", water_mask );
        WriteDebugPoly( "island_mask", "", island_mask );
    }

    // process polygons in priority order
    for ( i = 0; i < TG_MAX_AREA_TYPES; ++i ) {
        for( j = 0; j < (int)polys_in.area_size(i); ++j ) {
            TGPolygon current = polys_in.get_mask(i, j);

            SG_LOG( SG_CLIPPER, SG_INFO, "Clipping " << get_area_name( (AreaType)i ) << ":" << j+1 << " of " << polys_in.area_size(i) );                       

            tmp = current;

            // if not a hole, clip the area to the land_mask
            if ( !ignoreLandmass && !is_hole_area( i ) ) {
                tmp = tgPolygonIntClipper( tmp, land_mask );
            }

            // if a water area, cut out potential islands
            if ( is_water_area( i ) ) {
                // clip against island mask
                tmp = tgPolygonDiffClipper( tmp, island_mask );
            }

            if ( IsDebugShape( polys_in.get_shape( i, j ).id ) ) {
                char name[32];
                sprintf(name, "shape %d,%d", i,j);
                WriteDebugPoly( "pre-clip", name, tmp );
            }

#if USE_ACCUMULATOR
            clipped = tgPolygonDiffClipperWithAccumulator( tmp );
#else
            clipped = tgPolygonDiffClipper( tmp, accum );
#endif


            // only add to output list if the clip left us with a polygon
            if ( clipped.contours() > 0 ) {

#if FIND_SLIVERS
                // move slivers from clipped polygon to slivers polygon
                tgPolygonFindSlivers( clipped, slivers );
#endif

                // add the sliverless result polygon to the clipped polys list
                if ( clipped.contours() > 0  ) {
                    TGShape shape;

                    // copy all of the superpolys and texparams
                    shape.SetMask( clipped );
                    shape.textured = polys_in.get_textured( i, j );
                    shape.id  = polys_in.get_shape( i, j ).id;

                    shape.area= polys_in.get_shape( i, j ).area;
                    shape.sps = polys_in.get_shape( i, j ).sps;
                    shape.tps = polys_in.get_shape( i, j ).tps;

                    // shape.sps.push_back( sp );
                    polys_clipped.add_shape( i, shape );

                    if ( IsDebugShape( shape.id ) ) {
                        WriteDebugShape( "clipped", shape );
                    }
                }
            }

#if USE_ACCUMULATOR
            tgPolygonAddToClipperAccumulator( tmp );
#else
            accum   = tgPolygonUnionClipper( tmp, accum );
#endif
            
        }
    }

    if ( debug_all || debug_shapes.size() ) {
        // Dump the sliver list
        WriteDebugPolys( "poly_slivers", slivers );
    }

#if FIND_SLIVERS
    // Now, merge any slivers with clipped polys
    merge_slivers(polys_clipped, slivers);
#endif

    slivers.clear();

    // finally, what ever is left over goes to ocean
#if USE_ACCUMULATOR
    remains = tgPolygonDiffClipperWithAccumulator( safety_base );
#else
    remains = tgPolygonDiffClipper( safety_base, accum );
#endif

    if ( remains.contours() > 0 ) {
        // cout << "remains contours = " << remains.contours() << endl;
        // move slivers from remains polygon to slivers polygon

#if FIND_SLIVERS
        tgPolygonFindSlivers( remains, slivers );
#endif
        // cout << "  After sliver move:" << endl;
        // cout << "    remains = " << remains.contours() << endl;
        // cout << "    slivers = " << slivers.contours() << endl;

#if FIND_SLIVERS
        // merge any slivers with previously clipped
        // neighboring polygons
        if ( slivers.size() > 0 ) {

            if ( debug_all || debug_shapes.size() ) {
                // Dump the sliver list
                WriteDebugPolys( "remains_slivers", slivers );
            }
                        
            merge_slivers(polys_clipped, slivers);
        }
#endif

        if ( remains.contours() > 0 ) {
            TGSuperPoly sp;
            TGShape shape;

            string material = get_area_name(get_sliver_target_area_type());

            sp.set_material( material );
            sp.set_poly( remains );
            shape.SetMask( remains );
            shape.textured = false;
            shape.sps.push_back( sp );

            polys_clipped.add_shape( (int)get_sliver_target_area_type(), shape );
        }
    }

#if USE_ACCUMULATOR

    tgPolygonFreeClipperAccumulator();

#endif

    // Once clipping is complete, intersect the individual segments with their clip masks
    for (unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++) {
        for (unsigned int shape = 0; shape < polys_clipped.area_size(area); shape++ ) {
            SG_LOG( SG_CLIPPER, SG_INFO, "Seperating segments from clip mask for " << get_area_name( (AreaType)area ) << ":" << shape+1 << " of " << polys_clipped.area_size(area) );                       
            polys_clipped.get_shape(area, shape).IntersectPolys();
        }
    }

    // Now make sure any newly added intersection nodes are added to the tgnodes
    for (unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++) {
        for (unsigned int shape = 0; shape < polys_clipped.area_size(area); shape++ ) {
            for (unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++) {
                TGPolygon poly = polys_clipped.get_poly( area, shape, segment );

                SG_LOG( SG_CLIPPER, SG_INFO, "Collecting nodes for " << get_area_name( (AreaType)area ) << ":" << shape+1 << "-" << segment << " of " << polys_clipped.area_size(area) );                       

                for (int con=0; con < poly.contours(); con++) {
                    for (int node = 0; node < poly.contour_size( con ); node++) {
                        // ensure we have all nodes...
                        nodes.unique_add( poly.get_pt( con, node ) );
                    }
                }
            } 
        }
    }

    return true;
}

bool TGNodesSortByLon( const TGNode& n1, const TGNode& n2 )
{
    return ( n1.GetPosition().x() < n2.GetPosition().x() );
}

// TODO : Add to TGNodes class
#if 0

static void dump_lat_nodes( TGConstruct& c, double lat ) {
    node_list all_nodes = c.get_nodes()->get_node_list();
    node_list sorted_nodes;
    for (unsigned int i=0; i<all_nodes.size(); i++) {
        if ( fabs( all_nodes[i].GetPosition().y() - lat ) < 0.0000001 ) {
            sorted_nodes.push_back( all_nodes[i] );
        }
    }

    sort( sorted_nodes.begin(), sorted_nodes.end(), TGNodesSortByLon );

    for (unsigned int i=0; i<sorted_nodes.size(); i++) {
        string fixed;
        
        if ( sorted_nodes[i].GetFixedPosition() ) {
            fixed = " z is fixed elevation ";
        } else {
            fixed = " z is interpolated elevation ";
        }

        SG_LOG(SG_GENERAL, SG_ALERT, "Point[" << i << "] is " << sorted_nodes[i].GetPosition() << fixed );
    }
}
#endif

// This function populates the Superpoly tri_idx polygon.
// This polygon is a mirror of tris, except the verticies are 
// indexes into the node array (cast as unsigned long)
void TGConstruct::LookupNodesPerVertex( void )
{
    SG_LOG(SG_GENERAL, SG_ALERT, "LookupNodexPerVertex");

    // for each node, traverse all the triangles - and create face lists
    for ( unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++ ) {
        for( unsigned int shape = 0; shape < polys_clipped.area_size(area); shape++ ) {
            for ( unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++ ) {
                TGPolygon   tris = polys_clipped.get_tris( area, shape, segment );
                TGPolyNodes tri_nodes;
                int         idx;

                for (int tri=0; tri < tris.contours(); tri++) {
                    for (int vertex = 0; vertex < tris.contour_size(tri); vertex++) {
                        idx = nodes.find( tris.get_pt( tri, vertex ) );
                        if (idx >= 0) {
                            tri_nodes.add_node( tri, idx );
                        } else {
                            exit(0);
                        }
                    }
                }
                polys_clipped.set_tri_idxs(area, shape, segment, tri_nodes);
            }
        }
    }
}

void TGConstruct::LookupFacesPerNode( void )
{
    SG_LOG(SG_GENERAL, SG_ALERT, "LookupFacesPerNode");

    // Add each face that includes a node to the node's face list
    for ( unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++ ) {
        for( unsigned int shape = 0; shape < polys_clipped.area_size(area); shape++ ) {
            for ( unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++ ) {
                TGPolygon   tris = polys_clipped.get_tris(area, shape, segment);

                for (int tri=0; tri < tris.contours(); tri++) {
                    for (int sub = 0; sub < tris.contour_size(tri); sub++) {
                        int n = nodes.find( tris.get_pt( tri, sub ) );
                        nodes.AddFace( n, area, shape, segment, tri );
                    }
                }
            }
        }
    }
}

double TGConstruct::calc_tri_area( int_list& triangle_nodes ) {
    Point3D p1 = nodes.get_node( triangle_nodes[0] ).GetPosition();
    Point3D p2 = nodes.get_node( triangle_nodes[1] ).GetPosition();
    Point3D p3 = nodes.get_node( triangle_nodes[2] ).GetPosition();

    return triangle_area( p1, p2, p3 );
}

void TGConstruct::calc_normals( point_list& wgs84_nodes, TGSuperPoly& sp ) {
    // for each face in the superpoly, calculate a face normal
    SGVec3d v1, v2, normal;
    TGPolyNodes tri_nodes = sp.get_tri_idxs();
    int_list    face_nodes;
    double_list face_areas;
    point_list  face_normals;
    double      area;

    face_normals.clear();
    face_areas.clear();

    for (int i=0; i<tri_nodes.contours(); i++) {
        face_nodes = tri_nodes.get_contour(i);

        Point3D p1 = wgs84_nodes[ face_nodes[0] ];
        Point3D p2 = wgs84_nodes[ face_nodes[1] ];
        Point3D p3 = wgs84_nodes[ face_nodes[2] ];

        area  = calc_tri_area( face_nodes );

        // do some sanity checking.  With the introduction of landuse
        // areas, we can get some long skinny triangles that blow up our
        // "normal" calculations here.  Let's check for really small
        // triangle areas and check if one dimension of the triangle
        // coordinates is nearly coincident.  If so, assign the "default"
        // normal of straight up.

        bool degenerate = false;
        const double area_eps = 1.0e-12;
        if ( area < area_eps ) {
            degenerate = true;
        }

        if ( fabs(p1.x() - p2.x()) < SG_EPSILON && fabs(p1.x() - p3.x()) < SG_EPSILON ) {
            degenerate = true;
        }
        if ( fabs(p1.y() - p2.y()) < SG_EPSILON && fabs(p1.y() - p3.y()) < SG_EPSILON ) {
            degenerate = true;
        }
        if ( fabs(p1.z() - p2.z()) < SG_EPSILON && fabs(p1.z() - p3.z()) < SG_EPSILON ) {
            degenerate = true;
        }

        if ( degenerate ) {
            normal = normalize(SGVec3d(p1.x(), p1.y(), p1.z()));
        } else {
        	v1[0] = p2.x() - p1.x();
        	v1[1] = p2.y() - p1.y();
        	v1[2] = p2.z() - p1.z();
        	v2[0] = p3.x() - p1.x();
        	v2[1] = p3.y() - p1.y();
        	v2[2] = p3.z() - p1.z();
        	normal = normalize(cross(v1, v2));
        }
        
        face_normals.push_back( Point3D::fromSGVec3( normal ) );
        face_areas.push_back( area );
    }

    sp.set_face_normals( face_normals );
    sp.set_face_areas( face_areas );
}

void TGConstruct::CalcFaceNormals( void )
{
    // traverse the superpols, and calc normals for each tri within
    point_list wgs84_nodes = nodes.get_wgs84_nodes_as_Point3d();

    for (unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++) {
        for (unsigned int shape = 0; shape < polys_clipped.area_size(area); shape++ ) {
            for ( unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++ ) {
                SG_LOG( SG_CLIPPER, SG_INFO, "Calculating face normals for " << get_area_name( (AreaType)area ) << ":" << shape+1 << "-" << segment << " of " << polys_in.area_size(area) );                       
                calc_normals( wgs84_nodes, polys_clipped.get_superpoly( area, shape, segment ) ); 
            }
        }
    }
}

void TGConstruct::CalcPointNormals( void )
{
    // traverse triangle structure building the face normal table
    SG_LOG(SG_GENERAL, SG_ALERT, "Calculating point normals: 0%");

    Point3D normal;
    point_list wgs84_nodes = nodes.get_wgs84_nodes_as_Point3d();
    unsigned int ten_percent = nodes.size() / 10;
    unsigned int cur_percent = 10;
    
    for ( unsigned int i = 0; i<nodes.size(); i++ ) {
        TGNode node       = nodes.get_node( i );
        TGFaceList faces  = node.GetFaces();
        double total_area = 0.0;

        Point3D average( 0.0 );

        if ( i == ten_percent ) {
            SG_LOG(SG_GENERAL, SG_ALERT, "Calculating point normals: " << cur_percent << "%" );
            ten_percent += nodes.size() / 10;
            cur_percent += 10;
        }
        
        // for each triangle that shares this node
        for ( unsigned int j = 0; j < faces.size(); ++j ) {
            unsigned int at      = faces[j].area;
            unsigned int shape   = faces[j].shape;
            unsigned int segment = faces[j].seg;
            unsigned int tri     = faces[j].tri;
            int_list     face_nodes;
            double       face_area;

            normal     = polys_clipped.get_face_normal( at, shape, segment, tri );
            face_nodes = polys_clipped.get_tri_idxs( at, shape, segment ).get_contour( tri ) ;
            face_area  = polys_clipped.get_face_area( at, shape, segment, tri );

            normal *= face_area;	// scale normal weight relative to area
            total_area += face_area;
            average += normal;
        }
        average /= total_area;
        nodes.SetNormal( i, average );
    }
}

void TGConstruct::LoadSharedEdgeData( void )
{
    match.load_neighbor_shared( bucket, work_base );
    if ( useOwnSharedEdges ) {
        match.load_missing_shared( bucket, work_base );
    }
    match.add_shared_nodes( this );
}

void TGConstruct::SaveSharedEdgeData( void )
{
    match.split_tile( bucket, this );
    SG_LOG(SG_GENERAL, SG_ALERT, "Tile Split");

    if ( writeSharedEdges ) {
        SG_LOG(SG_GENERAL, SG_ALERT, "write shared edges");

        match.write_shared( bucket, work_base );
    }
}

void TGConstruct::TesselatePolys( void )
{
    // tesselate the polygons and prepair them for final output
    point_list poly_extra;
    Point3D min, max;

    for (unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++) {
        for (unsigned int shape = 0; shape < polys_clipped.area_size(area); shape++ ) {
            unsigned int id = polys_clipped.get_shape( area, shape ).id;

            if ( IsDebugShape( id ) ) {
                WriteDebugShape( "preteselate", polys_clipped.get_shape(area, shape) );
            }

            for ( unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++ ) {
                TGPolygon poly = polys_clipped.get_poly(area, shape, segment);

                poly.get_bounding_box(min, max);
                poly_extra = nodes.get_geod_inside( min, max );

                SG_LOG( SG_CLIPPER, SG_INFO, "Tesselating " << get_area_name( (AreaType)area ) << "(" << area << "): " << 
                        shape+1 << "-" << segment << " of " << (int)polys_clipped.area_size(area) << 
                        ": id = " << id );

//              TGPolygon tri = polygon_tesselate_alt_with_extra( poly, poly_extra, false );
                TGPolygon tri = polygon_tesselate_alt_with_extra_cgal( poly, poly_extra, false );

                // ensure all added nodes are accounted for
                for (int k=0; k< tri.contours(); k++) {
                    for (int l = 0; l < tri.contour_size(k); l++) {
                        // ensure we have all nodes...
                        nodes.unique_add( tri.get_pt( k, l ) );
                    }
                } 

                // Save the triangulation
                polys_clipped.set_tris( area, shape, segment, tri );
            }
        }
    }
}

void TGConstruct::WriteBtgFile( void )
{
    TGTriNodes normals, texcoords;
    normals.clear();
    texcoords.clear();

    group_list pts_v; pts_v.clear();
    group_list pts_n; pts_n.clear();
    string_list pt_materials; pt_materials.clear();

    group_list tris_v; tris_v.clear();
    group_list tris_n; tris_n.clear();
    group_list tris_tc; tris_tc.clear();
    string_list tri_materials; tri_materials.clear();

    group_list strips_v; strips_v.clear();
    group_list strips_n; strips_n.clear();
    group_list strips_tc; strips_tc.clear();
    string_list strip_materials; strip_materials.clear();

    int index;
    int_list pt_v, tri_v, strip_v;
    int_list pt_n, tri_n, strip_n;
    int_list tri_tc, strip_tc;

    for (unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++) {
        // only tesselate non holes
        if ( !is_hole_area( area ) ) {
            for (unsigned int shape = 0; shape < polys_clipped.area_size(area); shape++ ) {
                for ( unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++ ) {
                    SG_LOG( SG_CLIPPER, SG_INFO, "Ouput nodes for " << get_area_name( (AreaType)area ) << ":" << 
                            shape+1 << "-" << segment << " of " << polys_clipped.area_size(area) );                       

            	    TGPolyNodes tri_nodes = polys_clipped.get_tri_idxs(area, shape, segment);
            	    TGPolygon   tri_txs   = polys_clipped.get_texcoords(area, shape, segment);
            	    string      material  = polys_clipped.get_material(area, shape, segment);

            	    for (int k = 0; k < tri_nodes.contours(); ++k) {
            	        tri_v.clear();
            	        tri_n.clear();
            	        tri_tc.clear();
            	        for (int l = 0; l < tri_nodes.contour_size(k); ++l) {
                            index = tri_nodes.get_pt( k, l );
                		    tri_v.push_back( index );
    
                            // add the node's normal
                    		index = normals.unique_add( nodes.GetNormal( index ) );
                    		tri_n.push_back( index );
    
                    		Point3D tc = tri_txs.get_pt( k, l );
                    		index = texcoords.unique_add( tc );
                    		tri_tc.push_back( index );
                	    }
                	    tris_v.push_back( tri_v );
            	        tris_n.push_back( tri_n );
            	        tris_tc.push_back( tri_tc );
            	        tri_materials.push_back( material );
            	    }
                }    
            }
        }
    }
    
    std::vector< SGVec3d > wgs84_nodes = nodes.get_wgs84_nodes_as_SGVec3d();
    SGSphered d;

    for (int i = 0; i < (int)wgs84_nodes.size(); ++i) 
    {
        d.expandBy(wgs84_nodes[ i ]);
    }
    
    SGVec3d gbs_center = d.getCenter();
    double gbs_radius = d.getRadius();
    SG_LOG(SG_GENERAL, SG_DEBUG, "gbs center = " << gbs_center);
    SG_LOG(SG_GENERAL, SG_DEBUG, "Done with wgs84 node mapping");
    SG_LOG(SG_GENERAL, SG_DEBUG, "  center = " << gbs_center << " radius = " << gbs_radius );

    // null structures
    group_list fans_v; fans_v.clear();
    group_list fans_n; fans_n.clear();
    group_list fans_tc; fans_tc.clear();
    string_list fan_materials; fan_materials.clear();

    string base = get_output_base();
    string binname = bucket.gen_index_str();
    binname += ".btg";
    string txtname = bucket.gen_index_str();
    txtname += ".txt";
    
    std::vector< SGVec3f > normals_3f;
    for (int i=0; i < (int)normals.get_node_list().size(); i++ ) 
    {
        Point3D node = normals.get_node_list()[i];
        normals_3f.push_back( node.toSGVec3f() );
    }

    std::vector< SGVec2f > texcoords_2f;
    for (int i=0; i < (int)texcoords.get_node_list().size(); i++ ) 
    {
        Point3D node = texcoords.get_node_list()[i];
        texcoords_2f.push_back( node.toSGVec2f() );
    }

    SGBinObject obj;
    
    obj.set_gbs_center( gbs_center );
    obj.set_gbs_radius( gbs_radius );
    obj.set_wgs84_nodes( wgs84_nodes );
    obj.set_normals( normals_3f );
    obj.set_texcoords( texcoords_2f );
    obj.set_pts_v( pts_v );
    obj.set_pts_n( pts_n );
    obj.set_pt_materials( pt_materials );
    obj.set_tris_v( tris_v );
    obj.set_tris_n( tris_n );
    obj.set_tris_tc( tris_tc ); 
    obj.set_tri_materials( tri_materials );
    obj.set_strips_v( strips_v );
    obj.set_strips_n( strips_n );
    obj.set_strips_tc( strips_tc ); 
    obj.set_strip_materials( strip_materials );
    obj.set_fans_v( fans_v );
    obj.set_fans_n( fans_n );
    obj.set_fans_tc( fans_tc );
    obj.set_fan_materials( fan_materials );
    
    bool result;
    result = obj.write_bin( base, binname, bucket );
    if ( !result ) 
    {
        throw sg_exception("error writing file. :-(");
    }
    if (debug_all || debug_shapes.size())
    {
        result = obj.write_ascii( base, txtname, bucket );
        if ( !result )
        {
            throw sg_exception("error writing file. :-(");
        }
    }
}

void TGConstruct::CleanClippedPolys() {
    
    // Clean the polys
    for ( unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++ ) {
        for( unsigned int shape = 0; shape < polys_clipped.area_size(area); shape++ ) {
            unsigned int id = polys_clipped.get_shape( area, shape ).id;

            // step 1 : snap
            for ( unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++ ) {
                TGPolygon poly = polys_clipped.get_poly(area, shape, segment);
                poly = snap(poly, gSnap);
                polys_clipped.set_poly( area, shape, segment, poly );
            }

            if ( IsDebugShape( id ) ) {
                WriteDebugShape( "snapped", polys_clipped.get_shape( area, shape ) );
            }

            // step 2 : remove_dups
            for ( unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++ ) {
                TGPolygon poly = polys_clipped.get_poly(area, shape, segment);
                poly = remove_dups( poly );
                polys_clipped.set_poly( area, shape, segment, poly );
            }

            if ( IsDebugShape( id ) ) {
                WriteDebugShape( "rem dupes", polys_clipped.get_shape( area, shape ) );
            }

            // step 3 : remove_bad_contours
            for ( unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++ ) {
                TGPolygon poly = polys_clipped.get_poly(area, shape, segment);
                poly = remove_bad_contours( poly );
                polys_clipped.set_poly( area, shape, segment, poly );
            }

            if ( IsDebugShape( id ) ) {
                WriteDebugShape( "rem bad contours", polys_clipped.get_shape( area, shape ) );
            }

// todo - add up all segments in a shape for printout
#if 0
            after = poly.total_size();
            if (before != after) {
                SG_LOG( SG_CLIPPER, SG_INFO, "Cleanined poly " << get_area_name( (AreaType)area ) <<
                                                                                ":" << shape+1 << "-" << segment << " of " << polys_clipped.area_size(area) << " before: " << before << " after: " << after );
            }
#endif

        }
    }
}

void TGConstruct::CalcTextureCoordinates( void )
{
    for ( unsigned int area = 0; area < TG_MAX_AREA_TYPES; area++ ) {
        for( unsigned int shape = 0; shape < polys_clipped.area_size(area); shape++ ) {
            for ( unsigned int segment = 0; segment < polys_clipped.shape_size(area, shape); segment++ ) {
                TGPolygon poly = polys_clipped.get_poly(area, shape, segment);
                SG_LOG( SG_CLIPPER, SG_INFO, "Texturing " << get_area_name( (AreaType)area ) << "(" << area << "): " << 
                        shape+1 << "-" << segment << " of " << polys_clipped.area_size(area) );

                TGPolygon tri = polys_clipped.get_tris( area, shape, segment );
                TGPolygon tc;

                if ( polys_clipped.get_textured( area, shape ) ) {
                    SG_LOG(SG_GENERAL, SG_DEBUG, "USE TEXTURE PARAMS for tex coord calculations" );
                    tc = linear_tex_coords( tri, polys_clipped.get_texparams(area, shape, segment) );
                } else {
                    SG_LOG(SG_GENERAL, SG_DEBUG, "USE SIMGEAR for tex coord calculations" );
                    tc = area_tex_coords( tri );
                }
          	    polys_clipped.set_texcoords( area, shape, segment, tc );
            }
        }
    }
}

// master construction routine
// TODO : Split each step into its own function, and move 
//        into seperate files by major functionality
//        loading, clipping, tesselating, normals, and output
//        Also, we are still calculating some thing more than one 
//        (like face area - need to move this into superpoly )
void TGConstruct::ConstructBucketStage1() {

    SG_LOG(SG_GENERAL, SG_ALERT, "\nConstructing tile ID " << bucket.gen_index_str() << " in " << bucket.gen_base_path() );

    /* If we have some debug IDs, create a datasource */
    if ( debug_shapes.size() || debug_all ) {
        sprintf(ds_name, "%s/constructdbg_%s", debug_path.c_str(), bucket.gen_index_str().c_str() );
        SG_LOG(SG_GENERAL, SG_ALERT, "Debug_string: " << ds_name );
    } else {
        strcpy( ds_name, "" );
    }

    // STEP 1) 
    // Load grid of elevation data (Array)
    LoadElevationArray();
    
    // STEP 2) 
    // Clip 2D polygons against one another
    if ( LoadLandclassPolys() == 0 ) {
        // don't build the tile if there is no 2d data ... it *must*
        // be ocean and the sim can build the tile on the fly.
        return;
    }

    // STEP 3)
    // Load the land use polygons if the --cover option was specified
    if ( get_cover().size() > 0 ) {
        load_landcover();
    }

    // STEP 4)
    // Clip the Landclass polygons    
    ClipLandclassPolys(); 

    // STEP 5)
    // Clean the polys - after this, we shouldn't change their shape (other than slightly for
    // fix T-Junctions - as This is the end of the first pass for multicore design
    CleanClippedPolys();

    // END OF FIRST PASS : SAVE THE TILE DATA

    // STEP 5)
    // Merge in Shared data (just add the nodes to the nodelist)
    // When this step is complete, some nodes will have normals (from shared tiles)
    // and some will not
    // Load Shared Edge Data X,Y coords only
    LoadSharedEdgeData();

    // STEP 6) 
    // Fix T-Junctions by finding nodes that lie close to polygon edges, and
    // inserting them into the edge
    FixTJunctions();

    // TODO : Needs to be part of clipping 
    // just before union : If we need to clean again after fixing tjunctions, make 
    // sure we don't alter the shape
    // CleanClippedPolys();

    // STEP 7)
    // Generate triangles - we can't generate the node-face lookup table
    // until all polys are tesselated, as extra nodes can still be generated
    TesselatePolys();

    // STEP 8) 
    // Generate triangle vertex coordinates to node index lists
    // NOTE: After this point, no new nodes can be added
    LookupNodesPerVertex();

    // STEP 9)
    // Interpolate elevations, and flatten stuff
    CalcElevations();

    // STEP 10)
    // Generate face_connected list - shared data contains faces, too - save them somehow
    LookupFacesPerNode();

    // END OF SECOND PASS : SAVE THE TILE DATA

    // load shared edge data (with elevations, and face connected list)
    // LoadSharedEdgeDataWithElevation();

    // STEP 11)
    // Calculate Face Normals
    CalcFaceNormals();

    // STEP 12)
    // Calculate Point Normals
    CalcPointNormals();

#if 0
    if ( c.get_cover().size() > 0 ) {
        // Now for all the remaining "default" land cover polygons, assign
        // each one it's proper type from the land use/land cover
        // database.
        fix_land_cover_assignments( c );
    }
#endif

    // STEP 13)
    // Calculate Texture Coordinates
    CalcTextureCoordinates();

    // STEP 14)
    // Write out the shared edge data
    SaveSharedEdgeData();

    // STEP 15)
    // Generate the btg file
    WriteBtgFile();

    // STEP 16) 
    // Write Custom objects to .stg file 
    AddCustomObjects();
}