]> git.mxchange.org Git - flightgear.git/blob - utils/stgmerge/stgmerge.cxx
VS2015 compatability fixes.
[flightgear.git] / utils / stgmerge / stgmerge.cxx
1 // stgmerge.cxx -- combined STG models
2 //
3 // Copyright (C) 2015 Stuart Buchanan
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <iostream>
24 #include <string>
25 #include <cstdlib>
26 #include <iomanip>
27 #include <dirent.h>
28
29 #include <osg/MatrixTransform>
30 #include <osg/ArgumentParser>
31 #include <osg/Image>
32 #include <osgViewer/Viewer>
33 #include <osgViewer/ViewerEventHandlers>
34
35 #include <osgGA/StateSetManipulator>
36 #include <osgGA/TrackballManipulator>
37
38 #include <osgDB/FileNameUtils>
39 #include <osgDB/FileUtils>
40 #include <osgDB/Registry>
41 #include <osgDB/ReaderWriter>
42 #include <osgDB/ReadFile>
43 #include <osgDB/WriteFile>
44
45 #include <osgUtil/Optimizer>
46
47 #include <simgear/bucket/newbucket.hxx>
48 #include <simgear/bvh/BVHNode.hxx>
49 #include <simgear/bvh/BVHLineSegmentVisitor.hxx>
50 #include <simgear/bvh/BVHPager.hxx>
51 #include <simgear/bvh/BVHPageNode.hxx>
52 #include <simgear/debug/logstream.hxx>
53 #include <simgear/math/SGGeodesy.hxx>
54 #include <simgear/misc/sg_path.hxx>
55 #include <simgear/misc/sgstream.hxx>
56 #include <simgear/misc/ResourceManager.hxx>
57 #include <simgear/props/props.hxx>
58 #include <simgear/props/props_io.hxx>
59 #include <simgear/scene/material/matlib.hxx>
60 #include <simgear/scene/model/BVHPageNodeOSG.hxx>
61 #include <simgear/scene/model/ModelRegistry.hxx>
62 #include <simgear/scene/tgdb/apt_signs.hxx>
63 #include <simgear/scene/tgdb/userdata.hxx>
64 #include <simgear/scene/util/OptionsReadFileCallback.hxx>
65 #include <simgear/scene/util/OsgMath.hxx>
66 #include <simgear/scene/util/RenderConstants.hxx>
67 #include <simgear/scene/util/SGReaderWriterOptions.hxx>
68
69 namespace sg = simgear;
70
71 struct _ObjectStatic {
72         _ObjectStatic() :
73                         _lon(0), _lat(0), _elev(0), _hdg(0), _pitch(0), _roll(0), _shared(false) {
74         }
75         std::string _token;
76         std::string _name;
77         double _lon, _lat, _elev;
78         double _hdg, _pitch, _roll;
79         bool _shared;
80         osg::ref_ptr<sg::SGReaderWriterOptions> _options;
81 };
82
83 std::string fg_root;
84 std::string fg_scenery;
85 std::string input;
86 std::string output;
87 bool display_viewer = false;
88 bool osg_optimizer = false;
89 bool copy_files = false;
90 int group_size = 5000;
91
92 sg::SGReaderWriterOptions* staticOptions(const std::string& filePath,
93                 const osgDB::Options* options) {
94         osg::ref_ptr<sg::SGReaderWriterOptions> staticOptions;
95         staticOptions = sg::SGReaderWriterOptions::copyOrCreate(options);
96         staticOptions->getDatabasePathList().clear();
97
98         staticOptions->getDatabasePathList().push_back(filePath);
99         staticOptions->setObjectCacheHint(osgDB::Options::CACHE_NONE);
100
101         // Every model needs its own SGModelData to ensure load/unload is
102         // working properly
103         staticOptions->setModelData(
104                         staticOptions->getModelData() ?
105                                         staticOptions->getModelData()->clone() : 0);
106
107         return staticOptions.release();
108 }
109
110 sg::SGReaderWriterOptions* sharedOptions(const std::string& filePath,
111                 const osgDB::Options* options) {
112         osg::ref_ptr<sg::SGReaderWriterOptions> sharedOptions;
113         sharedOptions = sg::SGReaderWriterOptions::copyOrCreate(options);
114         sharedOptions->getDatabasePathList().clear();
115
116         SGPath path = filePath;
117         path.append("..");
118         path.append("..");
119         path.append("..");
120         sharedOptions->getDatabasePathList().push_back(path.str());
121
122         // ensure Models directory synced via TerraSync is searched before the copy in
123         // FG_ROOT, so that updated models can be used.
124         std::string terrasync_root = options->getPluginStringData(
125                         "SimGear::TERRASYNC_ROOT");
126         if (!terrasync_root.empty()) {
127                 sharedOptions->getDatabasePathList().push_back(terrasync_root);
128         }
129
130         std::string fg_root = options->getPluginStringData("SimGear::FG_ROOT");
131         sharedOptions->getDatabasePathList().push_back(fg_root);
132
133         // TODO how should we handle this for OBJECT_SHARED?
134         sharedOptions->setModelData(
135                         sharedOptions->getModelData() ?
136                                         sharedOptions->getModelData()->clone() : 0);
137
138         return sharedOptions.release();
139 }
140
141 int processSTG(osg::ref_ptr<simgear::SGReaderWriterOptions> options,
142                 std::string stg) {
143         // Get the STG file
144         if (stg.empty()) {
145                 SG_LOG(SG_TERRAIN, SG_ALERT, "STG file empty");
146                 return EXIT_FAILURE;
147         }
148
149         SG_LOG(SG_TERRAIN, SG_ALERT, "Loading stg file " << stg);
150
151         sg_gzifstream stream(stg);
152         if (!stream.is_open()) {
153                 SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open STG file " << stg);
154                 return EXIT_FAILURE;
155         }
156
157         // Extract the bucket from the filename
158         std::istringstream ss(osgDB::getStrippedName(stg));
159         long index;
160         ss >> index;
161         if (ss.fail()) {
162                 SG_LOG(SG_TERRAIN, SG_ALERT,
163                                 "Unable to determine bucket from STG filename " << ss);
164                 return EXIT_FAILURE;
165         }
166
167         SGBucket bucket = SGBucket(index);
168
169         // We will group the object into group_size x group_size
170         // block, so work out the number and dimensions in degrees
171         // of each block.
172         int lon_blocks = (int) ceil(bucket.get_width_m() / group_size);
173         int lat_blocks = (int) ceil(bucket.get_height_m() / group_size);
174         float lat_delta = bucket.get_height() / lat_blocks;
175         float lon_delta = bucket.get_width() / lon_blocks;
176         SG_LOG(SG_TERRAIN, SG_ALERT,
177                         "Splitting into (" << lon_blocks << "," << lat_blocks << ") blocks"
178                         << "of size (" << lon_delta << "," << lat_delta << ") degrees\n");
179
180         std::list<_ObjectStatic> _objectStaticList[lon_blocks][lat_blocks];
181
182         std::string filePath = osgDB::getFilePath(stg);
183
184         // Write out the STG files.
185         SGPath stgpath(stg);
186         std::string outpath = SGPath(SGPath(output), stgpath.file()).c_str();
187         SG_LOG(SG_TERRAIN, SG_ALERT, "Writing to " << outpath);
188         std::ofstream stgout(outpath.c_str(), std::ofstream::out);
189
190         if (!stgout.is_open()) {
191                 SG_LOG(SG_TERRAIN, SG_ALERT,
192                                 "Unable to open STG file to write " << outpath);
193                 return EXIT_FAILURE;
194         }
195
196         while (!stream.eof()) {
197                 // read a line
198                 std::string line;
199                 std::getline(stream, line);
200
201                 // strip comments
202                 std::string::size_type hash_pos = line.find('#');
203
204                 if (hash_pos == 0)
205                         stgout << line << "\n";
206
207                 if (hash_pos != std::string::npos)
208                         line.resize(hash_pos);
209
210                 // and process further
211                 std::stringstream in(line);
212
213                 std::string token;
214                 in >> token;
215
216                 // No comment
217                 if (token.empty())
218                         continue;
219
220                 // Then there is always a name
221                 std::string name;
222                 in >> name;
223
224                 SGPath path = filePath;
225                 path.append(name);
226
227                 //  The following tokens are ignored
228                 // OBJECT_BASE - base scenery, not relevant
229                 // OBJECT - airport BTG files
230                 // OBJECT_STATIC_AGL - elevation needs to be calculated at runtime
231                 // OBJECT_SHARED_AGL - elevation needs to be calculated at runtime
232                 // OBJECT_SIGN - depends on materials library, which is runtime dependent
233
234                 if ((token == "OBJECT_STATIC") || (token == "OBJECT_SHARED")) {
235
236                         _ObjectStatic obj;
237                         osg::ref_ptr<sg::SGReaderWriterOptions> opt;
238
239                         if (token == "OBJECT_STATIC") {
240                                 opt = staticOptions(filePath, options);
241                                 //if (SGPath(name).lower_extension() == "ac")
242                                 //opt->setInstantiateEffects(true);  // Is this output correctly?
243                                 obj._shared = false;
244                         } else if (token == "OBJECT_SHARED") {
245                                 opt = sharedOptions(filePath, options);
246                                 //if (SGPath(name).lower_extension() == "ac")
247                                 //opt->setInstantiateEffects(true);  // Is this output correctly?
248                                 obj._shared = true;
249                         } else {
250                                 SG_LOG(SG_TERRAIN, SG_ALERT, "Broken code - unexpected object '" << token << "'");
251                         }
252
253                         obj._token = token;
254                         obj._name = name;
255                         obj._options = opt;
256                         in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch
257                                         >> obj._roll;
258
259                         // Determine the correct bucket for this object.
260                         int x = (int) floor((obj._lon - bucket.get_corner(0).getLongitudeDeg()) / lon_delta);
261                         int y = (int) floor((obj._lat - bucket.get_corner(0).getLatitudeDeg()) / lat_delta);
262                         SG_LOG(SG_TERRAIN, SG_INFO,
263                                         "Assigned (" << obj._lon << "," << obj._lat << ") to block (" << x << "," << y << ")");
264
265                         _objectStaticList[x][y].push_back(obj);
266                 } else {
267                         SG_LOG(SG_TERRAIN, SG_ALERT, "Ignoring token '" << token << "'");
268                         stgout << line << "\n";
269                 }
270         }
271
272         stream.close();
273
274         for (int x = 0; x < lon_blocks; ++x) {
275                 for (int y = 0; y < lat_blocks; ++y) {
276
277                         if (_objectStaticList[x][y].size() == 0) {
278                                 // Nothing to do, so skip
279                                 continue;
280                         }
281
282                         //SG_LOG(SG_TERRAIN, SG_ALERT, "Object files " << _objectStaticList[x][y].size());
283
284                         osg::ref_ptr<osg::Group> group = new osg::Group;
285                         group->setName("STG merge");
286                         group->setDataVariance(osg::Object::STATIC);
287                         int files_loaded = 0;
288
289                         // Calculate center of this block
290                         const SGGeod center = SGGeod::fromDegM(
291                                         bucket.get_center_lon() - 0.5 * bucket.get_width() + (double) ((x +0.5) * lon_delta),
292                                         bucket.get_center_lat() - 0.5 * bucket.get_height() + (double) ((y + 0.5) * lat_delta),
293                                         0.0);
294
295                         //SG_LOG(SG_TERRAIN, SG_ALERT,
296                         //              "Center of block: " << center.getLongitudeDeg() << ", " << center.getLatitudeDeg());
297
298                         // Inverse used to translate individual matrices
299                         SGVec3d shift;
300                         SGGeodesy::SGGeodToCart(center , shift);
301
302                         for (std::list<_ObjectStatic>::iterator i = _objectStaticList[x][y].begin();
303                                         i != _objectStaticList[x][y].end(); ++i) {
304
305                                 // We don't process XML files, as they typically include animations which we can't output
306                                 /*
307                                  if (SGPath(i->_name).lower_extension() == "xml") {
308                                  //SG_LOG(SG_TERRAIN, SG_ALERT, "Ignoring non-static "
309                                  //        << i->_token << " '" << i->_name << "'");
310                                  std::cout << (i->_shared ? "OBJECT_SHARED " : "OBJECT_STATIC ");
311                                  std::cout << i->_name << " " << i->_lon << " " << i->_lat << " " << i->_elev << " " << i->_hdg;
312                                  std::cout << " " << i->_pitch << i->_roll << "\n";
313                                  continue;
314                                  }
315                                  */
316
317                                 SG_LOG(SG_TERRAIN, SG_INFO, "Processing " << i->_name);
318
319                                 osg::ref_ptr<osg::Node> node;
320                                 node = osgDB::readRefNodeFile(i->_name, i->_options.get());
321                                 if (!node.valid()) {
322                                         SG_LOG(SG_TERRAIN, SG_ALERT,
323                                                         stg << ": Failed to load " << i->_token << " '" << i->_name << "'");
324                                         continue;
325                                 }
326                                 files_loaded++;
327
328                                 if (SGPath(i->_name).lower_extension() == "ac")
329                                         node->setNodeMask(~sg::MODELLIGHT_BIT);
330
331                                 const SGGeod q = SGGeod::fromDegM(i->_lon, i->_lat, i->_elev);
332                                 SGVec3d coord;
333                                 SGGeodesy::SGGeodToCart(q, coord);
334                                 coord = coord - shift;
335
336                                 // Create an matrix to convert from global coordinates to the
337                                 // Z-Up local coordinate system used by scenery models.
338                                 // This is simply the inverse of the normal scenery model
339                                 // matrix.
340                                 osg::Matrix m = makeZUpFrameRelative(center);
341                                 osg::Matrix inv = osg::Matrix::inverse(m);
342                                 osg::Vec3f v = toOsg(coord) * inv;
343
344                                 osg::Matrix matrix;
345                                 matrix.setTrans(v);
346                                 matrix.preMultRotate(
347                                                 osg::Quat(SGMiscd::deg2rad(i->_hdg), osg::Vec3(0, 0, 1)));
348                                 matrix.preMultRotate(
349                                                 osg::Quat(SGMiscd::deg2rad(i->_pitch), osg::Vec3(0, 1, 0)));
350                                 matrix.preMultRotate(
351                                                 osg::Quat(SGMiscd::deg2rad(i->_roll), osg::Vec3(1, 0, 0)));
352
353                                 osg::MatrixTransform* matrixTransform;
354                                 matrixTransform = new osg::MatrixTransform(matrix);
355                                 matrixTransform->setName("positionStaticObject");
356                                 matrixTransform->setDataVariance(osg::Object::STATIC);
357                                 matrixTransform->addChild(node.get());
358
359                                 // Shift the models so they are centered on the center of the block.
360                                 // We will place the object at the right position in the tile later.
361                                 group->addChild(matrixTransform);
362                         }
363
364                         osgViewer::Viewer viewer;
365
366                         if (osg_optimizer || display_viewer) {
367                                 // Create a viewer - required for some Optimizers and if we are to display
368                                 // the results
369                                 viewer.setSceneData(group.get());
370                                 viewer.addEventHandler(new osgViewer::StatsHandler);
371                                 viewer.addEventHandler(new osgViewer::WindowSizeHandler);
372                                 viewer.addEventHandler(
373                                                 new osgGA::StateSetManipulator(
374                                                                 viewer.getCamera()->getOrCreateStateSet()));
375                                 viewer.setCameraManipulator(new osgGA::TrackballManipulator());
376                                 viewer.realize();
377                         }
378
379                         if (osg_optimizer) {
380                                 // Run the Optimizer
381                                 osgUtil::Optimizer optimizer;
382
383                                 //  See osgUtil::Optimizer for list of optimizations available.
384                                 //optimizer.optimize(group, osgUtil::Optimizer::ALL_OPTIMIZATIONS);
385                                 int optimizationOptions = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS
386                                                 | osgUtil::Optimizer::REMOVE_REDUNDANT_NODES
387                                                 | osgUtil::Optimizer::COMBINE_ADJACENT_LODS
388                                                 | osgUtil::Optimizer::SHARE_DUPLICATE_STATE
389                                                 | osgUtil::Optimizer::MERGE_GEOMETRY
390                                                 | osgUtil::Optimizer::MAKE_FAST_GEOMETRY
391                                                 | osgUtil::Optimizer::SPATIALIZE_GROUPS
392                                                 | osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS
393                                                 | osgUtil::Optimizer::TEXTURE_ATLAS_BUILDER
394                                                 | osgUtil::Optimizer::CHECK_GEOMETRY
395                                                 | osgUtil::Optimizer::STATIC_OBJECT_DETECTION;
396
397                                 optimizer.optimize(group, optimizationOptions);
398                         }
399
400                         // Serialize the result as a binary OSG file, including textures:
401                         std::string filename = stgpath.file();
402
403                         // Include both the STG name and the indexes for uniqueness.
404                         // ostringstream required for compilers that don't support C++11
405                         std::ostringstream oss;
406                         oss << x << y;
407                         filename.append(oss.str());
408                         filename.append(".osg");
409                         SGPath osgpath = SGPath(SGPath(output), filename);
410                         osgDB::writeNodeFile(*group, osgpath.c_str(),
411                                         new osgDB::Options("WriteImageHint=IncludeData Compressor=zlib"));
412
413                         // Write out the required STG entry for this merged set of objects, centered
414                         // on the center of the tile.
415                         stgout << "OBJECT_STATIC " << osgpath.file() << " " << center.getLongitudeDeg()
416                                         << " " << center.getLatitudeDeg() << " 0.0 0.0 0.0 0.0\n";
417
418                         if (display_viewer) {
419                                 viewer.run();
420                         }
421                 }
422         }
423
424         // Finished with this file.
425         stgout.flush();
426         stgout.close();
427
428         return EXIT_SUCCESS;
429 }
430
431 int main(int argc, char** argv) {
432         osg::ApplicationUsage* usage = new osg::ApplicationUsage();
433         usage->setApplicationName("stgmerge");
434         usage->setCommandLineUsage(
435                         "Merge static model files within a given STG file.");
436         usage->addCommandLineOption("--input <dir>", "Scenery directory to read");
437         usage->addCommandLineOption("--output <dir>",
438                         "Output directory for STGs and merged models");
439         usage->addCommandLineOption("--fg-root <dir>", "FG root directory",
440                         "$FG_ROOT");
441         usage->addCommandLineOption("--fg-scenery <dir>", "FG scenery path",
442                         "$FG_SCENERY");
443         usage->addCommandLineOption("--group-size <N>", "Group size (m)", "5000");
444         usage->addCommandLineOption("--optimize", "Optimize scene-graph");
445         usage->addCommandLineOption("--viewer", "Display loaded objects");
446         usage->addCommandLineOption("--copy-files", "Copy all contents of input directory into output directory");
447
448         // use an ArgumentParser object to manage the program arguments.
449         osg::ArgumentParser arguments(&argc, argv);
450
451         arguments.setApplicationUsage(usage);
452
453         if (arguments.read("--fg-root", fg_root)) {
454         } else if (const char *fg_root_env = std::getenv("FG_ROOT")) {
455                 fg_root = fg_root_env;
456         } else {
457                 fg_root = PKGLIBDIR;
458         }
459
460         if (arguments.read("--fg-scenery", fg_scenery)) {
461         } else if (const char *fg_scenery_env = std::getenv("FG_SCENERY")) {
462                 fg_scenery = fg_scenery_env;
463         } else {
464                 SGPath path(fg_root);
465                 path.append("Scenery");
466                 fg_scenery = path.str();
467         }
468
469         if (!arguments.read("--input", input)) {
470                 arguments.reportError("--input argument required.");
471         } else {
472                 SGPath s(input);
473                 if (!s.isDir()) {
474                         arguments.reportError(
475                                         "--input directory does not exist or is not directory.");
476                 } else if (!s.canRead()) {
477                         arguments.reportError(
478                                         "--input directory cannot be read. Check permissions.");
479                 }
480         }
481
482         if (!arguments.read("--output", output)) {
483                 arguments.reportError("--output argument required.");
484         } else {
485                 // Check directory exists, we can write to it, and we're not about to write
486                 // to the same location as the STG file.
487                 SGPath p(output);
488                 SGPath s(input);
489                 if (!p.isDir()) {
490                         arguments.reportError("--output directory does not exist.");
491                 }
492
493                 if (!p.canWrite()) {
494                         arguments.reportError(
495                                         "--output directory is not writeable. Check permissions.");
496                 }
497
498                 if (s == p) {
499                         arguments.reportError(
500                                         "--output directory must differ from STG directory.");
501                 }
502         }
503
504         if (arguments.read("--group-size")) {
505                 if (! arguments.read("--group-size", group_size)) {
506                         arguments.reportError(
507                                                                 "--group-size argument number be a positive integer.");
508                 }
509         }
510
511         if (arguments.read("--viewer")) {
512                 display_viewer = true;
513         }
514
515         if (arguments.read("--optimize")) {
516                 osg_optimizer = true;
517         }
518
519         if (arguments.read("--copy-files")) {
520                 copy_files = true;
521         }
522
523         if (arguments.errors()) {
524                 arguments.writeErrorMessages(std::cout);
525                 arguments.getApplicationUsage()->write(std::cout,
526                                 osg::ApplicationUsage::COMMAND_LINE_OPTION
527                                                 | osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE, 80, true);
528                 return EXIT_FAILURE;
529         }
530
531         SGSharedPtr<SGPropertyNode> props = new SGPropertyNode;
532         try {
533                 SGPath preferencesFile = fg_root;
534                 preferencesFile.append("preferences.xml");
535                 readProperties(preferencesFile.str(), props);
536         } catch (...) {
537                 // In case of an error, at least make summer :)
538                 props->getNode("sim/startup/season", true)->setStringValue("summer");
539
540                 SG_LOG(SG_GENERAL, SG_ALERT,
541                                 "Problems loading FlightGear preferences.\n" << "Probably FG_ROOT is not properly set.");
542         }
543
544         /// now set up the simgears required model stuff
545
546         simgear::ResourceManager::instance()->addBasePath(fg_root,
547                         simgear::ResourceManager::PRIORITY_DEFAULT);
548         // Just reference simgears reader writer stuff so that the globals get
549         // pulled in by the linker ...
550         simgear::ModelRegistry::instance();
551
552         sgUserDataInit(props.get());
553         SGMaterialLibPtr ml = new SGMaterialLib;
554         SGPath mpath(fg_root);
555
556         // TODO: Pick up correct materials.xml file.  Urrgh - this can't be runtime dependent...
557         mpath.append("Materials/default/materials.xml");
558         try {
559                 ml->load(fg_root, mpath.str(), props);
560         } catch (...) {
561                 SG_LOG(SG_GENERAL, SG_ALERT,
562                                 "Problems loading FlightGear materials.\n" << "Probably FG_ROOT is not properly set.");
563         }
564         simgear::SGModelLib::init(fg_root, props);
565
566         // Set up the reader/writer options
567         osg::ref_ptr<simgear::SGReaderWriterOptions> options;
568         if (osgDB::Options* ropt = osgDB::Registry::instance()->getOptions())
569                 options = new simgear::SGReaderWriterOptions(*ropt);
570         else
571                 options = new simgear::SGReaderWriterOptions;
572         osgDB::convertStringPathIntoFilePathList(fg_scenery,
573                         options->getDatabasePathList());
574         options->setMaterialLib(ml);
575         options->setPropertyNode(props);
576         options->setPluginStringData("SimGear::FG_ROOT", fg_root);
577
578         // Here, all arguments are processed
579         arguments.reportRemainingOptionsAsUnrecognized();
580         arguments.writeErrorMessages(std::cerr);
581
582         DIR *dir = NULL;
583         dir = opendir(input.c_str());
584         struct dirent *pent = NULL;
585
586         if (dir == NULL) {
587                 SG_LOG(SG_GENERAL, SG_ALERT, "Unable to open " << input);
588                 return EXIT_FAILURE;
589         }
590
591         // List of STG files to process
592         std::vector<std::string> stg_files;
593
594         pent = readdir(dir);
595
596         while (pent != NULL) {
597
598                 std::string fname = pent->d_name;
599
600                 if (SGPath(fname).lower_extension() == "stg") {
601                         SG_LOG(SG_GENERAL, SG_ALERT, "STG " << fname);
602                         stg_files.push_back(fname);
603                 } else if (copy_files) {
604                         // Copy it over if we're copying all files.
605                         SGPath source = SGPath(input);
606                         source.append(fname);
607                         SGPath destination = SGPath(output);
608                         destination.append(fname);
609                         //SG_LOG(SG_GENERAL, SG_ALERT, "Copying " << source.c_str() << " to " << destination.c_str());
610                         std::ifstream src(source.c_str(), std::ios::binary);
611                         std::ofstream dst(destination.c_str(), std::ios::binary);
612
613                         dst << src.rdbuf();
614                 }
615                 pent = readdir(dir);
616         }
617
618         closedir(dir);
619
620         // Now we've copied the data, process the STG files
621         std::vector<std::string>::const_iterator iter;
622
623         for (iter = stg_files.begin(); iter != stg_files.end(); ++iter) {
624                 SGPath stg = SGPath(input);
625                 stg.append(*iter);
626                 processSTG(options, stg.c_str());
627         }
628
629         return EXIT_SUCCESS;
630 }