]> git.mxchange.org Git - flightgear.git/commitdiff
stgmerge enhancements
authorStuart Buchanan <stuart_d_buchanan@yahoo.co.uk>
Sun, 22 Feb 2015 21:37:18 +0000 (21:37 +0000)
committerStuart Buchanan <stuart_d_buchanan@yahoo.co.uk>
Sun, 22 Feb 2015 21:37:18 +0000 (21:37 +0000)
Various stgmerge enhancements to make it functional:
- Now runs against an entire tile directory, reading each stg file
- optionally optimizes the mesh (untested)
- outputs to a second directory
- control over the size of merged meshes

utils/stgmerge/stgmerge.cxx

index bdc617e12045809243c4f6cecae28769a4e6920e..aec51ba89a03ab53cf1aa59b8c84045b983997d3 100644 (file)
 #endif
 
 #include <iostream>
+#include <string>
 #include <cstdlib>
 #include <iomanip>
+#include <dirent.h>
 
 #include <osg/MatrixTransform>
 #include <osg/ArgumentParser>
@@ -48,6 +50,7 @@
 #include <simgear/bvh/BVHPager.hxx>
 #include <simgear/bvh/BVHPageNode.hxx>
 #include <simgear/debug/logstream.hxx>
+#include <simgear/math/SGGeodesy.hxx>
 #include <simgear/misc/sg_path.hxx>
 #include <simgear/misc/sgstream.hxx>
 #include <simgear/misc/ResourceManager.hxx>
 #include <simgear/scene/util/RenderConstants.hxx>
 #include <simgear/scene/util/SGReaderWriterOptions.hxx>
 
-
 namespace sg = simgear;
 
 struct _ObjectStatic {
-    _ObjectStatic() : _lon(0), _lat(0), _elev(0), _hdg(0), _pitch(0), _roll(0), _shared(false) { }
-    std::string _token;
-    std::string _name;
-    double _lon, _lat, _elev;
-    double _hdg, _pitch, _roll;
-    bool _shared;
-    osg::ref_ptr<sg::SGReaderWriterOptions> _options;
+       _ObjectStatic() :
+                       _lon(0), _lat(0), _elev(0), _hdg(0), _pitch(0), _roll(0), _shared(false) {
+       }
+       std::string _token;
+       std::string _name;
+       double _lon, _lat, _elev;
+       double _hdg, _pitch, _roll;
+       bool _shared;
+       osg::ref_ptr<sg::SGReaderWriterOptions> _options;
 };
 
-std::list<_ObjectStatic> _objectStaticList;
+std::string fg_root;
+std::string fg_scenery;
+std::string input;
+std::string output;
+bool display_viewer = false;
+bool osg_optimizer = false;
+bool copy_files = false;
+int group_size = 5000;
+
+sg::SGReaderWriterOptions* staticOptions(const std::string& filePath,
+               const osgDB::Options* options) {
+       osg::ref_ptr<sg::SGReaderWriterOptions> staticOptions;
+       staticOptions = sg::SGReaderWriterOptions::copyOrCreate(options);
+       staticOptions->getDatabasePathList().clear();
+
+       staticOptions->getDatabasePathList().push_back(filePath);
+       staticOptions->setObjectCacheHint(osgDB::Options::CACHE_NONE);
+
+       // Every model needs its own SGModelData to ensure load/unload is
+       // working properly
+       staticOptions->setModelData(
+                       staticOptions->getModelData() ?
+                                       staticOptions->getModelData()->clone() : 0);
+
+       return staticOptions.release();
+}
 
-sg::SGReaderWriterOptions* staticOptions(const std::string& filePath, const osgDB::Options* options)
-{
-    osg::ref_ptr<sg::SGReaderWriterOptions> staticOptions;
-    staticOptions = sg::SGReaderWriterOptions::copyOrCreate(options);
-    staticOptions->getDatabasePathList().clear();
+sg::SGReaderWriterOptions* sharedOptions(const std::string& filePath,
+               const osgDB::Options* options) {
+       osg::ref_ptr<sg::SGReaderWriterOptions> sharedOptions;
+       sharedOptions = sg::SGReaderWriterOptions::copyOrCreate(options);
+       sharedOptions->getDatabasePathList().clear();
+
+       SGPath path = filePath;
+       path.append("..");
+       path.append("..");
+       path.append("..");
+       sharedOptions->getDatabasePathList().push_back(path.str());
+
+       // ensure Models directory synced via TerraSync is searched before the copy in
+       // FG_ROOT, so that updated models can be used.
+       std::string terrasync_root = options->getPluginStringData(
+                       "SimGear::TERRASYNC_ROOT");
+       if (!terrasync_root.empty()) {
+               sharedOptions->getDatabasePathList().push_back(terrasync_root);
+       }
 
-    staticOptions->getDatabasePathList().push_back(filePath);
-    staticOptions->setObjectCacheHint(osgDB::Options::CACHE_NONE);
+       std::string fg_root = options->getPluginStringData("SimGear::FG_ROOT");
+       sharedOptions->getDatabasePathList().push_back(fg_root);
 
-    // Every model needs its own SGModelData to ensure load/unload is
-    // working properly
-    staticOptions->setModelData
-    (
-        staticOptions->getModelData()
-      ? staticOptions->getModelData()->clone()
-      : 0
-    );
+       // TODO how should we handle this for OBJECT_SHARED?
+       sharedOptions->setModelData(
+                       sharedOptions->getModelData() ?
+                                       sharedOptions->getModelData()->clone() : 0);
 
-    return staticOptions.release();
+       return sharedOptions.release();
 }
 
-sg::SGReaderWriterOptions* sharedOptions(const std::string& filePath, const osgDB::Options* options)
-{
-    osg::ref_ptr<sg::SGReaderWriterOptions> sharedOptions;
-    sharedOptions = sg::SGReaderWriterOptions::copyOrCreate(options);
-    sharedOptions->getDatabasePathList().clear();
-
-    SGPath path = filePath;
-    path.append(".."); path.append(".."); path.append("..");
-    sharedOptions->getDatabasePathList().push_back(path.str());
-
-    // ensure Models directory synced via TerraSync is searched before the copy in
-    // FG_ROOT, so that updated models can be used.
-    std::string terrasync_root = options->getPluginStringData("SimGear::TERRASYNC_ROOT");
-    if (!terrasync_root.empty()) {
-        sharedOptions->getDatabasePathList().push_back(terrasync_root);
-    }
-
-    std::string fg_root = options->getPluginStringData("SimGear::FG_ROOT");
-    sharedOptions->getDatabasePathList().push_back(fg_root);
-
-    // TODO how should we handle this for OBJECT_SHARED?
-    sharedOptions->setModelData
-    (
-        sharedOptions->getModelData()
-      ? sharedOptions->getModelData()->clone()
-      : 0
-    );
-
-    return sharedOptions.release();
-}
-
-int
-main(int argc, char** argv)
-{
-    /// Read arguments and environment variables.
-
-    // use an ArgumentParser object to manage the program arguments.
-    osg::ArgumentParser arguments(&argc, argv);
-
-    std::string fg_root;
-    if (arguments.read("--fg-root", fg_root)) {
-    } else if (const char *fg_root_env = std::getenv("FG_ROOT")) {
-        fg_root = fg_root_env;
-    } else {
-        fg_root = PKGLIBDIR;
-    }
-
-    std::string fg_scenery;
-    if (arguments.read("--fg-scenery", fg_scenery)) {
-    } else if (const char *fg_scenery_env = std::getenv("FG_SCENERY")) {
-        fg_scenery = fg_scenery_env;
-    } else {
-        SGPath path(fg_root);
-        path.append("Scenery");
-        fg_scenery = path.str();
-    }
-
-    std::string stg;
-    if (! arguments.read("--stg", stg)) {
-        SG_LOG(SG_GENERAL, SG_ALERT, "No --stg argument");
-        return EXIT_FAILURE;
-    }
-
-    SGSharedPtr<SGPropertyNode> props = new SGPropertyNode;
-    try {
-        SGPath preferencesFile = fg_root;
-        preferencesFile.append("preferences.xml");
-        readProperties(preferencesFile.str(), props);
-    } catch (...) {
-        // In case of an error, at least make summer :)
-        props->getNode("sim/startup/season", true)->setStringValue("summer");
-
-        SG_LOG(SG_GENERAL, SG_ALERT, "Problems loading FlightGear preferences.\n"
-               << "Probably FG_ROOT is not properly set.");
-    }
-
-    /// now set up the simgears required model stuff
-
-    simgear::ResourceManager::instance()->addBasePath(fg_root, simgear::ResourceManager::PRIORITY_DEFAULT);
-    // Just reference simgears reader writer stuff so that the globals get
-    // pulled in by the linker ...
-    simgear::ModelRegistry::instance();
-
-    sgUserDataInit(props.get());
-    SGMaterialLibPtr ml = new SGMaterialLib;
-    SGPath mpath(fg_root);
-
-    // TODO: Pick up correct materials.xml file.  Urrgh - this can't be runtime dependent...
-    mpath.append("Materials/default/materials.xml");
-    try {
-        ml->load(fg_root, mpath.str(), props);
-    } catch (...) {
-        SG_LOG(SG_GENERAL, SG_ALERT, "Problems loading FlightGear materials.\n"
-               << "Probably FG_ROOT is not properly set.");
-    }
-    simgear::SGModelLib::init(fg_root, props);
-
-    // Set up the reader/writer options
-    osg::ref_ptr<simgear::SGReaderWriterOptions> options;
-    if (osgDB::Options* ropt = osgDB::Registry::instance()->getOptions())
-        options = new simgear::SGReaderWriterOptions(*ropt);
-    else
-        options = new simgear::SGReaderWriterOptions;
-    osgDB::convertStringPathIntoFilePathList(fg_scenery,
-                                             options->getDatabasePathList());
-    options->setMaterialLib(ml);
-    options->setPropertyNode(props);
-    options->setPluginStringData("SimGear::FG_ROOT", fg_root);
-
-    // Here, all arguments are processed
-    arguments.reportRemainingOptionsAsUnrecognized();
-    arguments.writeErrorMessages(std::cerr);
-
-    // Get the STG file
-    if (stg.empty()) {
-       SG_LOG(SG_TERRAIN, SG_ALERT, "STG file empty");
-       return EXIT_FAILURE;
-    }
+int processSTG(osg::ref_ptr<simgear::SGReaderWriterOptions> options,
+               std::string stg) {
+       // Get the STG file
+       if (stg.empty()) {
+               SG_LOG(SG_TERRAIN, SG_ALERT, "STG file empty");
+               return EXIT_FAILURE;
+       }
 
        SG_LOG(SG_TERRAIN, SG_ALERT, "Loading stg file " << stg);
 
        sg_gzifstream stream(stg);
        if (!stream.is_open()) {
-       SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open STG file " << stg);
+               SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open STG file " << stg);
                return EXIT_FAILURE;
        }
 
@@ -231,21 +159,40 @@ main(int argc, char** argv)
        long index;
        ss >> index;
        if (ss.fail()) {
-       SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to determine bucket from STG filename " << ss);
+               SG_LOG(SG_TERRAIN, SG_ALERT,
+                               "Unable to determine bucket from STG filename " << ss);
                return EXIT_FAILURE;
        }
 
-       // Work out the transform to the center of the tile
        SGBucket bucket = SGBucket(index);
-    osg::Matrix tile_transform;
-    tile_transform = makeZUpFrame(bucket.get_center());
 
-    // Inverse used to translate individual matrices
-    SGVec3d shift;
-    SGGeodesy::SGGeodToCart(bucket.get_center(), shift);
+       // We will group the object into group_size x group_size
+       // block, so work out the number and dimensions in degrees
+       // of each block.
+       int lon_blocks = (int) ceil(bucket.get_width_m() / group_size);
+       int lat_blocks = (int) ceil(bucket.get_height_m() / group_size);
+       float lat_delta = bucket.get_height() / lat_blocks;
+       float lon_delta = bucket.get_width() / lon_blocks;
+       SG_LOG(SG_TERRAIN, SG_ALERT,
+                       "Splitting into (" << lon_blocks << "," << lat_blocks << ") blocks"
+                       << "of size (" << lon_delta << "," << lat_delta << ") degrees\n");
+
+       std::list<_ObjectStatic> _objectStaticList[lon_blocks][lat_blocks];
 
        std::string filePath = osgDB::getFilePath(stg);
 
+       // Write out the STG files.
+       SGPath stgpath(stg);
+       std::string outpath = SGPath(SGPath(output), stgpath.file()).c_str();
+       SG_LOG(SG_TERRAIN, SG_ALERT, "Writing to " << outpath);
+       std::ofstream stgout(outpath.c_str(), std::ofstream::out);
+
+       if (!stgout.is_open()) {
+               SG_LOG(SG_TERRAIN, SG_ALERT,
+                               "Unable to open STG file to write " << outpath);
+               return EXIT_FAILURE;
+       }
+
        while (!stream.eof()) {
                // read a line
                std::string line;
@@ -253,6 +200,10 @@ main(int argc, char** argv)
 
                // strip comments
                std::string::size_type hash_pos = line.find('#');
+
+               if (hash_pos == 0)
+                       stgout << line << "\n";
+
                if (hash_pos != std::string::npos)
                        line.resize(hash_pos);
 
@@ -280,125 +231,400 @@ main(int argc, char** argv)
                // OBJECT_SHARED_AGL - elevation needs to be calculated at runtime
                // OBJECT_SIGN - depends on materials library, which is runtime dependent
 
-               if (token == "OBJECT_STATIC") {
-                       osg::ref_ptr<sg::SGReaderWriterOptions> opt;
-                       opt = staticOptions(filePath, options);
-                       //if (SGPath(name).lower_extension() == "ac")
-                       //opt->setInstantiateEffects(true);  // Is this output correctly?
+               if ((token == "OBJECT_STATIC") || (token == "OBJECT_SHARED")) {
+
                        _ObjectStatic obj;
-                       obj._token = token;
-                       obj._name = name;
-                       in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch >> obj._roll;
-                       obj._shared = false;
-                       obj._options = opt;
-                       _objectStaticList.push_back(obj);
-               } else if (token == "OBJECT_SHARED") {
                        osg::ref_ptr<sg::SGReaderWriterOptions> opt;
-                       opt = sharedOptions(filePath, options);
-                       //if (SGPath(name).lower_extension() == "ac")
-                       //opt->setInstantiateEffects(true);  // Is this output correctly?
-                       _ObjectStatic obj;
+
+                       if (token == "OBJECT_STATIC") {
+                               opt = staticOptions(filePath, options);
+                               //if (SGPath(name).lower_extension() == "ac")
+                               //opt->setInstantiateEffects(true);  // Is this output correctly?
+                               obj._shared = false;
+                       } else if (token == "OBJECT_SHARED") {
+                               opt = sharedOptions(filePath, options);
+                               //if (SGPath(name).lower_extension() == "ac")
+                               //opt->setInstantiateEffects(true);  // Is this output correctly?
+                               obj._shared = true;
+                       } else {
+                               SG_LOG(SG_TERRAIN, SG_ALERT, "Broken code - unexpected object '" << token << "'");
+                       }
+
                        obj._token = token;
                        obj._name = name;
-                       in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch >> obj._roll;
-                       obj._shared = true;
                        obj._options = opt;
-                       _objectStaticList.push_back(obj);
+                       in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch
+                                       >> obj._roll;
+
+                       // Determine the correct bucket for this object.
+                       int x = (int) floor((obj._lon - bucket.get_corner(0).getLongitudeDeg()) / lon_delta);
+                       int y = (int) floor((obj._lat - bucket.get_corner(0).getLatitudeDeg()) / lat_delta);
+                       SG_LOG(SG_TERRAIN, SG_INFO,
+                                       "Assigned (" << obj._lon << "," << obj._lat << ") to block (" << x << "," << y << ")");
+
+                       _objectStaticList[x][y].push_back(obj);
                } else {
-                       SG_LOG( SG_TERRAIN, SG_ALERT, "Ignoring token '" << token << "'" );
-                       std::cout << line << "\n";
+                       SG_LOG(SG_TERRAIN, SG_ALERT, "Ignoring token '" << token << "'");
+                       stgout << line << "\n";
                }
        }
 
-       //  We now have a list of objects and signs to process.
+       stream.close();
+
+       for (int x = 0; x < lon_blocks; ++x) {
+               for (int y = 0; y < lat_blocks; ++y) {
+
+                       if (_objectStaticList[x][y].size() == 0) {
+                               // Nothing to do, so skip
+                               continue;
+                       }
+
+                       //SG_LOG(SG_TERRAIN, SG_ALERT, "Object files " << _objectStaticList[x][y].size());
+
+                       osg::ref_ptr<osg::Group> group = new osg::Group;
+                       group->setName("STG merge");
+                       group->setDataVariance(osg::Object::STATIC);
+                       int files_loaded = 0;
+
+                       // Calculate center of this block
+                       const SGGeod center = SGGeod::fromDegM(
+                                       bucket.get_center_lon() - 0.5 * bucket.get_width() + (double) ((x +0.5) * lon_delta),
+                                       bucket.get_center_lat() - 0.5 * bucket.get_height() + (double) ((y + 0.5) * lat_delta),
+                                       0.0);
+
+                       //SG_LOG(SG_TERRAIN, SG_ALERT,
+                       //              "Center of block: " << center.getLongitudeDeg() << ", " << center.getLatitudeDeg());
+
+                       // Inverse used to translate individual matrices
+                       SGVec3d shift;
+                       SGGeodesy::SGGeodToCart(center , shift);
+
+                       for (std::list<_ObjectStatic>::iterator i = _objectStaticList[x][y].begin();
+                                       i != _objectStaticList[x][y].end(); ++i) {
+
+                               // We don't process XML files, as they typically include animations which we can't output
+                               /*
+                                if (SGPath(i->_name).lower_extension() == "xml") {
+                                //SG_LOG(SG_TERRAIN, SG_ALERT, "Ignoring non-static "
+                                //        << i->_token << " '" << i->_name << "'");
+                                std::cout << (i->_shared ? "OBJECT_SHARED " : "OBJECT_STATIC ");
+                                std::cout << i->_name << " " << i->_lon << " " << i->_lat << " " << i->_elev << " " << i->_hdg;
+                                std::cout << " " << i->_pitch << i->_roll << "\n";
+                                continue;
+                                }
+                                */
+
+                               SG_LOG(SG_TERRAIN, SG_INFO, "Processing " << i->_name);
+
+                               osg::ref_ptr<osg::Node> node;
+                               node = osgDB::readRefNodeFile(i->_name, i->_options.get());
+                               if (!node.valid()) {
+                                       SG_LOG(SG_TERRAIN, SG_ALERT,
+                                                       stg << ": Failed to load " << i->_token << " '" << i->_name << "'");
+                                       continue;
+                               }
+                               files_loaded++;
+
+                               if (SGPath(i->_name).lower_extension() == "ac")
+                                       node->setNodeMask(~sg::MODELLIGHT_BIT);
+
+                               const SGGeod q = SGGeod::fromDegM(i->_lon, i->_lat, i->_elev);
+                               SGVec3d coord;
+                               SGGeodesy::SGGeodToCart(q, coord);
+                               coord = coord - shift;
+
+                               // Create an matrix to convert from global coordinates to the
+                               // Z-Up local coordinate system used by scenery models.
+                               // This is simply the inverse of the normal scenery model
+                               // matrix.
+                               osg::Matrix m = makeZUpFrameRelative(center);
+                               osg::Matrix inv = osg::Matrix::inverse(m);
+                               osg::Vec3f v = toOsg(coord) * inv;
+
+                               osg::Matrix matrix;
+                               matrix.setTrans(v);
+                               matrix.preMultRotate(
+                                               osg::Quat(SGMiscd::deg2rad(i->_hdg), osg::Vec3(0, 0, 1)));
+                               matrix.preMultRotate(
+                                               osg::Quat(SGMiscd::deg2rad(i->_pitch), osg::Vec3(0, 1, 0)));
+                               matrix.preMultRotate(
+                                               osg::Quat(SGMiscd::deg2rad(i->_roll), osg::Vec3(1, 0, 0)));
+
+                               osg::MatrixTransform* matrixTransform;
+                               matrixTransform = new osg::MatrixTransform(matrix);
+                               matrixTransform->setName("positionStaticObject");
+                               matrixTransform->setDataVariance(osg::Object::STATIC);
+                               matrixTransform->addChild(node.get());
+
+                               // Shift the models so they are centered on the center of the block.
+                               // We will place the object at the right position in the tile later.
+                               group->addChild(matrixTransform);
+                       }
+
+                       osgViewer::Viewer viewer;
+
+                       if (osg_optimizer || display_viewer) {
+                               // Create a viewer - required for some Optimizers and if we are to display
+                               // the results
+                               viewer.setSceneData(group.get());
+                               viewer.addEventHandler(new osgViewer::StatsHandler);
+                               viewer.addEventHandler(new osgViewer::WindowSizeHandler);
+                               viewer.addEventHandler(
+                                               new osgGA::StateSetManipulator(
+                                                               viewer.getCamera()->getOrCreateStateSet()));
+                               viewer.setCameraManipulator(new osgGA::TrackballManipulator());
+                               viewer.realize();
+                       }
+
+                       if (osg_optimizer) {
+                               // Run the Optimizer
+                               osgUtil::Optimizer optimizer;
+
+                               //  See osgUtil::Optimizer for list of optimizations available.
+                               //optimizer.optimize(group, osgUtil::Optimizer::ALL_OPTIMIZATIONS);
+                               int optimizationOptions = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS
+                                               | osgUtil::Optimizer::REMOVE_REDUNDANT_NODES
+                                               | osgUtil::Optimizer::COMBINE_ADJACENT_LODS
+                                               | osgUtil::Optimizer::SHARE_DUPLICATE_STATE
+                                               | osgUtil::Optimizer::MERGE_GEOMETRY
+                                               | osgUtil::Optimizer::MAKE_FAST_GEOMETRY
+                                               | osgUtil::Optimizer::SPATIALIZE_GROUPS
+                                               | osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS
+                                               | osgUtil::Optimizer::TEXTURE_ATLAS_BUILDER
+                                               | osgUtil::Optimizer::CHECK_GEOMETRY
+                                               | osgUtil::Optimizer::STATIC_OBJECT_DETECTION;
+
+                               optimizer.optimize(group, optimizationOptions);
+                       }
+
+                       // Serialize the result as a binary OSG file, including textures:
+                       std::string filename = stgpath.file();
+
+                       // Include both the STG name and the indexes for uniqueness.
+                       // ostringstream required for compilers that don't support C++11
+                       std::ostringstream oss;
+                       oss << x << y;
+                       filename.append(oss.str());
+                       filename.append(".osg");
+                       SGPath osgpath = SGPath(SGPath(output), filename);
+                       osgDB::writeNodeFile(*group, osgpath.c_str(),
+                                       new osgDB::Options("WriteImageHint=IncludeData Compressor=zlib"));
+
+                       // Write out the required STG entry for this merged set of objects, centered
+                       // on the center of the tile.
+                       stgout << "OBJECT_STATIC " << osgpath.file() << " " << center.getLongitudeDeg()
+                                       << " " << center.getLatitudeDeg() << " 0.0 0.0 0.0 0.0\n";
+
+                       if (display_viewer) {
+                               viewer.run();
+                       }
+               }
+       }
 
-    osg::ref_ptr<osg::Group> group = new osg::Group;
-    group->setName("STG merge");
-    group->setDataVariance(osg::Object::STATIC);
-    int files_loaded = 0;
+       // Finished with this file.
+       stgout.flush();
+       stgout.close();
 
-    for (std::list<_ObjectStatic>::iterator i = _objectStaticList.begin(); i != _objectStaticList.end(); ++i) {
+       return EXIT_SUCCESS;
+}
 
-       // We don't process XML files, as they typically include animations which we can't output
-       if (SGPath(i->_name).lower_extension() == "xml") {
-               //SG_LOG(SG_TERRAIN, SG_ALERT, "Ignoring non-static "
-                       //         << i->_token << " '" << i->_name << "'");
-               std::cout << (i->_shared ? "OBJECT_SHARED " : "OBJECT_STATIC ");
-               std::cout << i->_lon << " " << i->_lat << " " << i->_elev << " " << i->_hdg;
-               std::cout << " " << i->_pitch << i->_roll << "\n";
-               continue;
-       }
+int main(int argc, char** argv) {
+       osg::ApplicationUsage* usage = new osg::ApplicationUsage();
+       usage->setApplicationName("stgmerge");
+       usage->setCommandLineUsage(
+                       "Merge static model files within a given STG file.");
+       usage->addCommandLineOption("--input <dir>", "Scenery directory to read");
+       usage->addCommandLineOption("--output <dir>",
+                       "Output directory for STGs and merged models");
+       usage->addCommandLineOption("--fg-root <dir>", "FG root directory",
+                       "$FG_ROOT");
+       usage->addCommandLineOption("--fg-scenery <dir>", "FG scenery path",
+                       "$FG_SCENERY");
+       usage->addCommandLineOption("--group-size <N>", "Group size (m)", "5000");
+       usage->addCommandLineOption("--optimize", "Optimize scene-graph");
+       usage->addCommandLineOption("--viewer", "Display loaded objects");
+       usage->addCommandLineOption("--copy-files", "Copy all contents of input directory into output directory");
+
+       // use an ArgumentParser object to manage the program arguments.
+       osg::ArgumentParser arguments(&argc, argv);
+
+       arguments.setApplicationUsage(usage);
+
+       if (arguments.read("--fg-root", fg_root)) {
+       } else if (const char *fg_root_env = std::getenv("FG_ROOT")) {
+               fg_root = fg_root_env;
+       } else {
+               fg_root = PKGLIBDIR;
+       }
 
-               SG_LOG( SG_TERRAIN, SG_ALERT, "Processing " << i->_name);
+       if (arguments.read("--fg-scenery", fg_scenery)) {
+       } else if (const char *fg_scenery_env = std::getenv("FG_SCENERY")) {
+               fg_scenery = fg_scenery_env;
+       } else {
+               SGPath path(fg_root);
+               path.append("Scenery");
+               fg_scenery = path.str();
+       }
 
-        osg::ref_ptr<osg::Node> node;
-               node = osgDB::readRefNodeFile(i->_name, i->_options.get());
-               if (!node.valid()) {
-                       SG_LOG(SG_TERRAIN, SG_ALERT, stg << ": Failed to load "
-                                  << i->_token << " '" << i->_name << "'");
-                       continue;
+       if (!arguments.read("--input", input)) {
+               arguments.reportError("--input argument required.");
+       } else {
+               SGPath s(input);
+               if (!s.isDir()) {
+                       arguments.reportError(
+                                       "--input directory does not exist or is not directory.");
+               } else if (!s.canRead()) {
+                       arguments.reportError(
+                                       "--input directory cannot be read. Check permissions.");
+               }
+       }
+
+       if (!arguments.read("--output", output)) {
+               arguments.reportError("--output argument required.");
+       } else {
+               // Check directory exists, we can write to it, and we're not about to write
+               // to the same location as the STG file.
+               SGPath p(output);
+               SGPath s(input);
+               if (!p.isDir()) {
+                       arguments.reportError("--output directory does not exist.");
+               }
+
+               if (!p.canWrite()) {
+                       arguments.reportError(
+                                       "--output directory is not writeable. Check permissions.");
+               }
+
+               if (s == p) {
+                       arguments.reportError(
+                                       "--output directory must differ from STG directory.");
                }
-               files_loaded++;
-
-        if (SGPath(i->_name).lower_extension() == "ac")
-            node->setNodeMask(~sg::MODELLIGHT_BIT);
-
-        osg::Matrix matrix;
-        matrix = makeZUpFrame(SGGeod::fromDegM(i->_lon, i->_lat, i->_elev));
-        matrix.preMultRotate(osg::Quat(SGMiscd::deg2rad(i->_hdg), osg::Vec3(0, 0, 1)));
-        matrix.preMultRotate(osg::Quat(SGMiscd::deg2rad(i->_pitch), osg::Vec3(0, 1, 0)));
-        matrix.preMultRotate(osg::Quat(SGMiscd::deg2rad(i->_roll), osg::Vec3(1, 0, 0)));
-
-        osg::MatrixTransform* matrixTransform;
-        matrixTransform = new osg::MatrixTransform(matrix);
-        matrixTransform->setName("positionStaticObject");
-        matrixTransform->setDataVariance(osg::Object::STATIC);
-        matrixTransform->addChild(node.get());
-
-        // Shift the models so they are centered on the center of the tile.
-        // We will place the object at the center of the tile later.
-        osg::Matrix unshift;
-        unshift.makeTranslate(- toOsg(shift));
-        osg::MatrixTransform* unshiftTransform = new osg::MatrixTransform();
-        unshiftTransform->addChild(matrixTransform);
-        group->addChild(unshiftTransform);
-    }
-
-    if (files_loaded == 0) {
-       // Nothing to do - no models were changed.
-
-    }
-
-    // Create a viewer - required for some Optimizers
-    osgViewer::Viewer viewer;
-    viewer.setSceneData(group.get());
-    viewer.addEventHandler(new osgViewer::StatsHandler);
-    viewer.addEventHandler(new osgViewer::WindowSizeHandler);
-    viewer.addEventHandler(
-               new osgGA::StateSetManipulator(
-                       viewer.getCamera()->getOrCreateStateSet()));
-                       viewer.setCameraManipulator(new osgGA::TrackballManipulator());
-    viewer.realize();
-
-    // Write out the pre-optimized version
-    osgDB::writeNodeFile(*group, "old.osgb",
-               new osgDB::Options("WriteImageHint=IncludeData Compressor=zlib"));
-
-    // Run the Optimizer
-    osgUtil::Optimizer optimizer;
-    optimizer.optimize( group, osgUtil::Optimizer::ALL_OPTIMIZATIONS );
-
-    // Serialize the result as a binary OSG file, including textures:
-    osgDB::writeNodeFile(*group, "new.osgb",
-               new osgDB::Options("WriteImageHint=IncludeData Compressor=zlib"));
-
-    // Write out the required STG entry for this merged set of objects, centered
-    // on the center of the tile.
-    std::cout << "Files loaded " << files_loaded << "\n";
-    std::cout << "OBJECT_STATIC static.osgb "
-        << bucket.get_center_lon() << " "
-        << bucket.get_center_lat() << "0.0 0.0 0.0 0.0\n";
-
-    viewer.run();
-    return EXIT_SUCCESS;
+       }
+
+       if (arguments.read("--group-size")) {
+               if (! arguments.read("--group-size", group_size)) {
+                       arguments.reportError(
+                                                               "--group-size argument number be a positive integer.");
+               }
+       }
+
+       if (arguments.read("--viewer")) {
+               display_viewer = true;
+       }
+
+       if (arguments.read("--optimize")) {
+               osg_optimizer = true;
+       }
+
+       if (arguments.read("--copy-files")) {
+               copy_files = true;
+       }
+
+       if (arguments.errors()) {
+               arguments.writeErrorMessages(std::cout);
+               arguments.getApplicationUsage()->write(std::cout,
+                               osg::ApplicationUsage::COMMAND_LINE_OPTION
+                                               | osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE, 80, true);
+               return EXIT_FAILURE;
+       }
+
+       SGSharedPtr<SGPropertyNode> props = new SGPropertyNode;
+       try {
+               SGPath preferencesFile = fg_root;
+               preferencesFile.append("preferences.xml");
+               readProperties(preferencesFile.str(), props);
+       } catch (...) {
+               // In case of an error, at least make summer :)
+               props->getNode("sim/startup/season", true)->setStringValue("summer");
+
+               SG_LOG(SG_GENERAL, SG_ALERT,
+                               "Problems loading FlightGear preferences.\n" << "Probably FG_ROOT is not properly set.");
+       }
+
+       /// now set up the simgears required model stuff
+
+       simgear::ResourceManager::instance()->addBasePath(fg_root,
+                       simgear::ResourceManager::PRIORITY_DEFAULT);
+       // Just reference simgears reader writer stuff so that the globals get
+       // pulled in by the linker ...
+       simgear::ModelRegistry::instance();
+
+       sgUserDataInit(props.get());
+       SGMaterialLibPtr ml = new SGMaterialLib;
+       SGPath mpath(fg_root);
+
+       // TODO: Pick up correct materials.xml file.  Urrgh - this can't be runtime dependent...
+       mpath.append("Materials/default/materials.xml");
+       try {
+               ml->load(fg_root, mpath.str(), props);
+       } catch (...) {
+               SG_LOG(SG_GENERAL, SG_ALERT,
+                               "Problems loading FlightGear materials.\n" << "Probably FG_ROOT is not properly set.");
+       }
+       simgear::SGModelLib::init(fg_root, props);
+
+       // Set up the reader/writer options
+       osg::ref_ptr<simgear::SGReaderWriterOptions> options;
+       if (osgDB::Options* ropt = osgDB::Registry::instance()->getOptions())
+               options = new simgear::SGReaderWriterOptions(*ropt);
+       else
+               options = new simgear::SGReaderWriterOptions;
+       osgDB::convertStringPathIntoFilePathList(fg_scenery,
+                       options->getDatabasePathList());
+       options->setMaterialLib(ml);
+       options->setPropertyNode(props);
+       options->setPluginStringData("SimGear::FG_ROOT", fg_root);
+
+       // Here, all arguments are processed
+       arguments.reportRemainingOptionsAsUnrecognized();
+       arguments.writeErrorMessages(std::cerr);
+
+       DIR *dir = NULL;
+       dir = opendir(input.c_str());
+       struct dirent *pent = NULL;
+
+       if (dir == NULL) {
+               SG_LOG(SG_GENERAL, SG_ALERT, "Unable to open " << input);
+               return EXIT_FAILURE;
+       }
+
+       // List of STG files to process
+       std::vector<std::string> stg_files;
+
+       pent = readdir(dir);
+
+       while (pent != NULL) {
+
+               std::string fname = pent->d_name;
+
+               if (SGPath(fname).lower_extension() == "stg") {
+                       SG_LOG(SG_GENERAL, SG_ALERT, "STG " << fname);
+                       stg_files.push_back(fname);
+               } else if (copy_files) {
+                       // Copy it over if we're copying all files.
+                       SGPath source = SGPath(input);
+                       source.append(fname);
+                       SGPath destination = SGPath(output);
+                       destination.append(fname);
+                       //SG_LOG(SG_GENERAL, SG_ALERT, "Copying " << source.c_str() << " to " << destination.c_str());
+                       std::ifstream src(source.c_str(), std::ios::binary);
+                       std::ofstream dst(destination.c_str(), std::ios::binary);
+
+                       dst << src.rdbuf();
+               }
+               pent = readdir(dir);
+       }
+
+       closedir(dir);
+
+       // Now we've copied the data, process the STG files
+       std::vector<std::string>::const_iterator iter;
+
+       for (iter = stg_files.begin(); iter != stg_files.end(); ++iter) {
+               SGPath stg = SGPath(input);
+               stg.append(*iter);
+               processSTG(options, stg.c_str());
+       }
+
+       return EXIT_SUCCESS;
 }