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/io/iostreams/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 {
_ObjectStatic_ObjectStatic72 _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 fg_terrasync_root;
86 std::string input;
87 std::string output;
88 bool display_viewer = false;
89 bool osg_optimizer = false;
90 bool copy_files = false;
91 int group_size = 5000;
92
staticOptions(const std::string & filePath,const osgDB::Options * options)93 sg::SGReaderWriterOptions* staticOptions(const std::string& filePath,
94 const osgDB::Options* options) {
95 osg::ref_ptr<sg::SGReaderWriterOptions> staticOptions;
96 staticOptions = sg::SGReaderWriterOptions::copyOrCreate(options);
97 staticOptions->getDatabasePathList().clear();
98
99 staticOptions->getDatabasePathList().push_back(filePath);
100 staticOptions->setObjectCacheHint(osgDB::Options::CACHE_NONE);
101
102 // Every model needs its own SGModelData to ensure load/unload is
103 // working properly
104 staticOptions->setModelData(
105 staticOptions->getModelData() ?
106 staticOptions->getModelData()->clone() : 0);
107
108 return staticOptions.release();
109 }
110
sharedOptions(const std::string & filePath,const osgDB::Options * options)111 sg::SGReaderWriterOptions* sharedOptions(const std::string& filePath,
112 const osgDB::Options* options) {
113 osg::ref_ptr<sg::SGReaderWriterOptions> sharedOptions;
114 sharedOptions = sg::SGReaderWriterOptions::copyOrCreate(options);
115 sharedOptions->getDatabasePathList().clear();
116
117 SGPath path(filePath);
118 path.append("..");
119 path.append("..");
120 path.append("..");
121 sharedOptions->getDatabasePathList().push_back(path.str());
122
123 // ensure Models directory synced via TerraSync is searched before the copy in
124 // FG_ROOT, so that updated models can be used.
125 std::string terrasync_root = options->getPluginStringData("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
processSTG(osg::ref_ptr<simgear::SGReaderWriterOptions> options,std::string stg)141 int processSTG(osg::ref_ptr<simgear::SGReaderWriterOptions> options,
142 std::string stg) {
143
144 // List of .ac files to remove
145 std::vector<std::string> ac_files;
146
147 // Get the STG file
148 if (stg.empty()) {
149 SG_LOG(SG_TERRAIN, SG_ALERT, "STG file empty");
150 return EXIT_FAILURE;
151 }
152
153 SGPath sourcestg(input);
154 sourcestg.append(stg);
155
156 SGPath deststg(output);
157 deststg.append(stg);
158
159 SG_LOG(SG_TERRAIN, SG_INFO, "Processing " << sourcestg.c_str());
160
161 sg_gzifstream stream((std::string) sourcestg.c_str());
162 if (!stream.is_open()) {
163 SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open STG file " << sourcestg.c_str());
164 return EXIT_FAILURE;
165 }
166
167 // Extract the bucket from the filename
168 std::istringstream ss(osgDB::getStrippedName(sourcestg.file().c_str()));
169 long index;
170 ss >> index;
171 if (ss.fail()) {
172 SG_LOG(SG_TERRAIN, SG_ALERT,
173 "Unable to determine bucket from STG filename " << ss.rdbuf());
174 return EXIT_FAILURE;
175 }
176
177 SGBucket bucket = SGBucket(index);
178
179 // We will group the object into group_size x group_size
180 // block, so work out the number and dimensions in degrees
181 // of each block.
182 int lon_blocks = (int) ceil(bucket.get_width_m() / group_size);
183 int lat_blocks = (int) ceil(bucket.get_height_m() / group_size);
184 float lat_delta = bucket.get_height() / lat_blocks;
185 float lon_delta = bucket.get_width() / lon_blocks;
186 SG_LOG(SG_TERRAIN, SG_DEBUG,
187 "Splitting into (" << lon_blocks << "," << lat_blocks << ") blocks"
188 << "of size (" << lon_delta << "," << lat_delta << ") degrees\n");
189
190 std::list<_ObjectStatic> _objectStaticList[lon_blocks][lat_blocks];
191
192 // Write out the STG files.
193 SG_LOG(SG_TERRAIN, SG_DEBUG, "Writing to " << deststg.c_str());
194 std::ofstream stgout(deststg.c_str(), std::ofstream::out);
195
196 if (!stgout.is_open()) {
197 SG_LOG(SG_TERRAIN, SG_ALERT,
198 "Unable to open STG file to write " << deststg.c_str());
199 return EXIT_FAILURE;
200 }
201
202 while (!stream.eof()) {
203 // read a line
204 std::string line;
205 std::getline(stream, line);
206
207 // strip comments
208 std::string::size_type hash_pos = line.find('#');
209
210 if (hash_pos == 0)
211 stgout << line << "\n";
212
213 if (hash_pos != std::string::npos)
214 line.resize(hash_pos);
215
216 // and process further
217 std::stringstream in(line);
218
219 std::string token;
220 in >> token;
221
222 // No comment
223 if (token.empty())
224 continue;
225
226 // Then there is always a name
227 std::string name;
228 in >> name;
229
230 // The following tokens are ignored
231 // OBJECT_BASE - base scenery, not relevant
232 // OBJECT - airport BTG files
233 // OBJECT_STATIC_AGL - elevation needs to be calculated at runtime
234 // OBJECT_SHARED_AGL - elevation needs to be calculated at runtime
235 // OBJECT_SIGN - depends on materials library, which is runtime dependent
236
237 if ((token == "OBJECT_STATIC") || (token == "OBJECT_SHARED")) {
238 if (SGPath(name).lower_extension() != "ac") {
239 // We only attempt to merge static .ac objects, as they don't have any
240 // animations
241 SG_LOG(SG_TERRAIN, SG_DEBUG, "Ignoring non .ac file '" << name << "'");
242 stgout << line << "\n";
243 continue;
244 }
245
246 // If we merge it, then we should remove the .ac file from the output.
247 ac_files.push_back(name);
248
249 SGPath filePath(sourcestg.dir());
250 filePath.append(name);
251
252 _ObjectStatic obj;
253 osg::ref_ptr<sg::SGReaderWriterOptions> opt;
254
255 if (token == "OBJECT_STATIC") {
256 opt = staticOptions(deststg.dir().c_str(), options);
257 //if (SGPath(name).lower_extension() == "ac")
258 //opt->setInstantiateEffects(true); // Is this output correctly?
259 obj._shared = false;
260 } else if (token == "OBJECT_SHARED") {
261 opt = sharedOptions(deststg.dir().c_str(), options);
262 //if (SGPath(name).lower_extension() == "ac")
263 //opt->setInstantiateEffects(true); // Is this output correctly?
264 obj._shared = true;
265 } else {
266 SG_LOG(SG_TERRAIN, SG_ALERT, "Broken code - unexpected object '" << token << "'");
267 }
268
269 obj._token = token;
270 obj._name = name;
271 obj._options = opt;
272 in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg >> obj._pitch
273 >> obj._roll;
274
275 // Sanity check this object is in the correct bucket
276 if ((obj._lon < bucket.get_corner(0).getLongitudeDeg()) ||
277 (obj._lon > bucket.get_corner(2).getLongitudeDeg()) ||
278 (obj._lat < bucket.get_corner(0).getLatitudeDeg()) ||
279 (obj._lat > bucket.get_corner(2).getLatitudeDeg()) ) {
280 SG_LOG(SG_TERRAIN, SG_ALERT,
281 "File " << sourcestg.c_str() << " contains object outside bounds of bucket:\n" <<
282 " Bucket bounds (lat, lon) : (" <<
283 bucket.get_corner(0).getLatitudeDeg() << ", " <<
284 bucket.get_corner(0).getLongitudeDeg() << ") - (" <<
285 bucket.get_corner(2).getLatitudeDeg() << ", " <<
286 bucket.get_corner(2).getLongitudeDeg() << ") \n" <<
287 line << "\n");
288
289 // Simply leave it in place for the moment.
290 stgout << line << "\n";
291 } else {
292 // Determine the correct bucket for this object.
293 int x = (int) floor((obj._lon - bucket.get_corner(0).getLongitudeDeg()) / lon_delta);
294 int y = (int) floor((obj._lat - bucket.get_corner(0).getLatitudeDeg()) / lat_delta);
295 SG_LOG(SG_TERRAIN, SG_INFO,
296 "Assigned (" << obj._lon << "," << obj._lat << ") to block (" << x << "," << y << ")");
297
298 _objectStaticList[x][y].push_back(obj);
299 }
300 } else {
301 SG_LOG(SG_TERRAIN, SG_DEBUG, "Ignoring token '" << token << "'");
302 stgout << line << "\n";
303 }
304 }
305
306 stream.close();
307
308 for (int x = 0; x < lon_blocks; ++x) {
309 for (int y = 0; y < lat_blocks; ++y) {
310
311 if (_objectStaticList[x][y].size() == 0) {
312 // Nothing to do, so skip
313 continue;
314 }
315
316 //SG_LOG(SG_TERRAIN, SG_ALERT, "Object files " << _objectStaticList[x][y].size());
317
318 osg::ref_ptr<osg::Group> group = new osg::Group;
319 group->setName("STG merge");
320 group->setDataVariance(osg::Object::STATIC);
321 int files_loaded = 0;
322
323 // Calculate center of this block
324 const SGGeod center = SGGeod::fromDegM(
325 bucket.get_center_lon() - 0.5 * bucket.get_width() + (double) ((x +0.5) * lon_delta),
326 bucket.get_center_lat() - 0.5 * bucket.get_height() + (double) ((y + 0.5) * lat_delta),
327 0.0);
328
329 //SG_LOG(SG_TERRAIN, SG_ALERT,
330 // "Center of block: " << center.getLongitudeDeg() << ", " << center.getLatitudeDeg());
331
332 // Inverse used to translate individual matrices
333 SGVec3d shift;
334 SGGeodesy::SGGeodToCart(center , shift);
335
336 for (std::list<_ObjectStatic>::iterator i = _objectStaticList[x][y].begin();
337 i != _objectStaticList[x][y].end(); ++i) {
338
339 SG_LOG(SG_TERRAIN, SG_INFO, "Processing " << i->_name);
340
341 osg::ref_ptr<osg::Node> node;
342 node = osgDB::readRefNodeFile(i->_name, i->_options.get());
343 if (!node.valid()) {
344 SG_LOG(SG_TERRAIN, SG_ALERT,
345 stg << ": Failed to load " << i->_token << " '" << i->_name << "'");
346 continue;
347 }
348 files_loaded++;
349
350 if (SGPath(i->_name).lower_extension() == "ac")
351 node->setNodeMask(~sg::MODELLIGHT_BIT);
352
353 const SGGeod q = SGGeod::fromDegM(i->_lon, i->_lat, i->_elev);
354 SGVec3d coord;
355 SGGeodesy::SGGeodToCart(q, coord);
356 coord = coord - shift;
357
358 // Create an matrix to convert from global coordinates to the
359 // Z-Up local coordinate system used by scenery models.
360 // This is simply the inverse of the normal scenery model
361 // matrix.
362 osg::Matrix m = makeZUpFrameRelative(center);
363 osg::Matrix inv = osg::Matrix::inverse(m);
364 osg::Vec3f v = toOsg(coord) * inv;
365
366 osg::Matrix matrix;
367 matrix.setTrans(v);
368 matrix.preMultRotate(
369 osg::Quat(SGMiscd::deg2rad(i->_hdg), osg::Vec3(0, 0, 1)));
370 matrix.preMultRotate(
371 osg::Quat(SGMiscd::deg2rad(i->_pitch), osg::Vec3(0, 1, 0)));
372 matrix.preMultRotate(
373 osg::Quat(SGMiscd::deg2rad(i->_roll), osg::Vec3(1, 0, 0)));
374
375 osg::MatrixTransform* matrixTransform;
376 matrixTransform = new osg::MatrixTransform(matrix);
377 matrixTransform->setName("positionStaticObject");
378 matrixTransform->setDataVariance(osg::Object::STATIC);
379 matrixTransform->addChild(node.get());
380
381 // Shift the models so they are centered on the center of the block.
382 // We will place the object at the right position in the tile later.
383 group->addChild(matrixTransform);
384 }
385
386 osgViewer::Viewer viewer;
387
388 if (osg_optimizer || display_viewer) {
389 // Create a viewer - required for some Optimizers and if we are to display
390 // the results
391 viewer.setSceneData(group.get());
392 viewer.addEventHandler(new osgViewer::StatsHandler);
393 viewer.addEventHandler(new osgViewer::WindowSizeHandler);
394 viewer.addEventHandler(
395 new osgGA::StateSetManipulator(
396 viewer.getCamera()->getOrCreateStateSet()));
397 viewer.setCameraManipulator(new osgGA::TrackballManipulator());
398 viewer.realize();
399 }
400
401 if (osg_optimizer) {
402 // Run the Optimizer
403 osgUtil::Optimizer optimizer;
404
405 // See osgUtil::Optimizer for list of optimizations available.
406 //optimizer.optimize(group, osgUtil::Optimizer::ALL_OPTIMIZATIONS);
407 int optimizationOptions = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS
408 | osgUtil::Optimizer::REMOVE_REDUNDANT_NODES
409 | osgUtil::Optimizer::COMBINE_ADJACENT_LODS
410 | osgUtil::Optimizer::SHARE_DUPLICATE_STATE
411 | osgUtil::Optimizer::MERGE_GEOMETRY
412 | osgUtil::Optimizer::MAKE_FAST_GEOMETRY
413 | osgUtil::Optimizer::SPATIALIZE_GROUPS
414 | osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS
415 | osgUtil::Optimizer::TEXTURE_ATLAS_BUILDER
416 | osgUtil::Optimizer::CHECK_GEOMETRY
417 | osgUtil::Optimizer::STATIC_OBJECT_DETECTION;
418
419 optimizer.optimize(group, optimizationOptions);
420 }
421
422 // Serialize the result as a binary OSG file, including textures:
423 std::string filename = sourcestg.file();
424
425 // Include both the STG name and the indexes for uniqueness.
426 // ostringstream required for compilers that don't support C++11
427 std::ostringstream oss;
428 oss << x << y;
429 filename.append(oss.str());
430 filename.append(".osg");
431 SGPath osgpath = SGPath(deststg.dir(), filename);
432 osgDB::writeNodeFile(*group, osgpath.c_str(),
433 new osgDB::Options("WriteImageHint=IncludeData Compressor=zlib"));
434
435 // Write out the required STG entry for this merged set of objects, centered
436 // on the center of the tile.
437 stgout << "OBJECT_STATIC " << osgpath.file() << " " << center.getLongitudeDeg()
438 << " " << center.getLatitudeDeg() << " 0.0 0.0 0.0 0.0\n";
439
440 if (display_viewer) {
441 viewer.run();
442 }
443 }
444 }
445
446 if (copy_files) {
447 std::vector<std::string>::const_iterator iter;
448
449 for (iter = ac_files.begin(); iter != ac_files.end(); ++iter) {
450 SGPath acfile = SGPath(deststg.dir());
451 acfile.append(*iter);
452 if (acfile.isFile()) {
453 if (remove(acfile.c_str()) != 0) {
454 SG_LOG(SG_GENERAL, SG_ALERT, "Unable to remove " << acfile.c_str());
455 }
456 }
457 }
458 }
459
460 // Finished with this file.
461 stgout.flush();
462 stgout.close();
463
464 return EXIT_SUCCESS;
465 }
466
processDirectory(osg::ref_ptr<simgear::SGReaderWriterOptions> options,SGPath directory_name)467 int processDirectory(osg::ref_ptr<simgear::SGReaderWriterOptions> options,
468 SGPath directory_name) {
469
470 // List of STG files to process
471 std::vector<std::string> stg_files;
472
473 SGPath source = SGPath(input);
474 source.append(directory_name.c_str());
475
476 SG_LOG(SG_GENERAL, SG_INFO, "Processing " << source.c_str());
477
478 DIR *dir = NULL;
479 dir = opendir(source.c_str());
480
481 if (dir == NULL) {
482 SG_LOG(SG_GENERAL, SG_ALERT, "Unable to open " << source.c_str());
483 return EXIT_FAILURE;
484 }
485
486 if (copy_files) {
487 // Create the directory in the output directory
488 SGPath destination = SGPath(output);
489 destination.append(directory_name.c_str());
490 destination.append("."); // Do this so the path is a directory rather than a file!
491 if (destination.exists()) {
492 if (! destination.isDir()) {
493 SG_LOG(SG_GENERAL, SG_ALERT, destination.c_str() << " exists and is not a directory.");
494 return EXIT_FAILURE;
495 }
496 if (! destination.canWrite()) {
497 SG_LOG(SG_GENERAL, SG_ALERT, destination.c_str() << " cannot be written.");
498 return EXIT_FAILURE;
499 }
500 } else {
501
502 // Note that this create the directory part of the path,
503 int success = destination.create_dir();
504
505 if (success != 0) {
506 SG_LOG(SG_GENERAL, SG_ALERT, "Unable to create " << destination.dir());
507 return EXIT_FAILURE;
508 }
509
510 if (! destination.exists()) {
511 SG_LOG(SG_GENERAL, SG_ALERT, "Failed to create " << destination.dir());
512 return EXIT_FAILURE;
513 }
514 }
515 }
516
517 struct dirent *pent = NULL;
518 pent = readdir(dir);
519
520 while (pent != NULL) {
521 std::string fname = pent->d_name;
522 SG_LOG(SG_GENERAL, SG_DEBUG, " checking " << fname.c_str());
523
524 if ((fname.compare(".") == 0) || (fname.compare("..") == 0)) {
525 pent = readdir(dir);
526 continue;
527 }
528
529 SGPath fpath = SGPath(input);
530 fpath.append(directory_name.c_str());
531 fpath.append(fname);
532
533 if (fpath.isDir()) {
534 SGPath dirname = SGPath(directory_name);
535 dirname.append(fname);
536 if (processDirectory(options, dirname) == EXIT_FAILURE) {
537 return EXIT_FAILURE;
538 }
539 } else if (SGPath(fname).lower_extension() == "stg") {
540 stg_files.push_back(fname);
541 } else if (copy_files) {
542 // Copy it over if we're copying all files.
543 SGPath fsource = SGPath(input);
544 fsource.append(directory_name.c_str());
545 fsource.append(fname);
546 SGPath fdestination = SGPath(output);
547 fdestination.append(directory_name.c_str());
548 fdestination.append(fname);
549
550 SG_LOG(SG_GENERAL, SG_DEBUG, "Copying " << fsource.c_str() << " to " << fdestination.c_str());
551 std::ifstream src(fsource.c_str(), std::ios::binary);
552 std::ofstream dst(fdestination.c_str(), std::ios::binary);
553
554 dst << src.rdbuf();
555 }
556 pent = readdir(dir);
557 }
558
559 closedir(dir);
560
561 // Now we've copied the data, process the STG files
562 std::vector<std::string>::const_iterator iter;
563
564 for (iter = stg_files.begin(); iter != stg_files.end(); ++iter) {
565 SGPath stg = SGPath(directory_name);
566 stg.append(*iter);
567 processSTG(options, stg.c_str());
568 }
569
570 return EXIT_SUCCESS;
571 }
572
main(int argc,char ** argv)573 int main(int argc, char** argv) {
574 osg::ApplicationUsage* usage = new osg::ApplicationUsage();
575 usage->setApplicationName("stgmerge");
576 usage->setCommandLineUsage(
577 "Merge static model files within a given STG file.");
578 usage->addCommandLineOption("--input <dir>", "Scenery directory to read");
579 usage->addCommandLineOption("--output <dir>",
580 "Output directory for STGs and merged models");
581 usage->addCommandLineOption("--fg-root <dir>", "FG root directory",
582 "$FG_ROOT");
583 usage->addCommandLineOption("--fg-scenery <dir>", "FG scenery path",
584 "$FG_SCENERY");
585 usage->addCommandLineOption("--terrasync <dir>", "Terrasync path (for Models)");
586 usage->addCommandLineOption("--group-size <N>", "Group size (m)", "5000");
587 usage->addCommandLineOption("--optimize", "Optimize scene-graph");
588 usage->addCommandLineOption("--viewer", "Display loaded objects");
589 usage->addCommandLineOption("--copy-files", "Copy all contents of input directory into output directory");
590
591 // use an ArgumentParser object to manage the program arguments.
592 osg::ArgumentParser arguments(&argc, argv);
593
594 arguments.setApplicationUsage(usage);
595
596 if (arguments.read("--fg-root", fg_root)) {
597 } else if (const char *fg_root_env = std::getenv("FG_ROOT")) {
598 fg_root = fg_root_env;
599 } else {
600 fg_root = PKGLIBDIR;
601 }
602
603 if (arguments.read("--fg-scenery", fg_scenery)) {
604 } else if (const char *fg_scenery_env = std::getenv("FG_SCENERY")) {
605 fg_scenery = fg_scenery_env;
606 } else {
607 SGPath path(fg_root);
608 path.append("Scenery");
609 fg_scenery = path.str();
610 }
611
612 if (arguments.read("--terrasync", fg_terrasync_root)) {
613 } else {
614 fg_scenery = "";
615 }
616
617 if (!arguments.read("--input", input)) {
618 arguments.reportError("--input argument required.");
619 } else {
620 SGPath s(input);
621 if (!s.isDir()) {
622 arguments.reportError(
623 "--input directory does not exist or is not directory.");
624 } else if (!s.canRead()) {
625 arguments.reportError(
626 "--input directory cannot be read. Check permissions.");
627 }
628 }
629
630 if (!arguments.read("--output", output)) {
631 arguments.reportError("--output argument required.");
632 } else {
633 // Check directory exists, we can write to it, and we're not about to write
634 // to the same location as the STG file.
635 SGPath p(output);
636 SGPath s(input);
637 if (!p.isDir()) {
638 arguments.reportError("--output directory does not exist.");
639 }
640
641 if (!p.canWrite()) {
642 arguments.reportError(
643 "--output directory is not writeable. Check permissions.");
644 }
645
646 if (s == p) {
647 arguments.reportError(
648 "--output directory must differ from STG directory.");
649 }
650 }
651
652 if (arguments.read("--group-size")) {
653 if (! arguments.read("--group-size", group_size)) {
654 arguments.reportError(
655 "--group-size argument number be a positive integer.");
656 }
657 }
658
659 if (arguments.read("--viewer")) {
660 display_viewer = true;
661 }
662
663 if (arguments.read("--optimize")) {
664 osg_optimizer = true;
665 }
666
667 if (arguments.read("--copy-files")) {
668 copy_files = true;
669 }
670
671 if (arguments.errors()) {
672 arguments.writeErrorMessages(std::cout);
673 arguments.getApplicationUsage()->write(std::cout,
674 osg::ApplicationUsage::COMMAND_LINE_OPTION
675 | osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE, 80, true);
676 return EXIT_FAILURE;
677 }
678
679 SGSharedPtr<SGPropertyNode> props = new SGPropertyNode;
680 try {
681 SGPath preferencesFile = fg_root;
682 preferencesFile.append("defaults.xml");
683 readProperties(preferencesFile.str(), props);
684 } catch (...) {
685 // In case of an error, at least make summer :)
686 props->getNode("sim/startup/season", true)->setStringValue("summer");
687
688 SG_LOG(SG_GENERAL, SG_ALERT,
689 "Problems loading FlightGear preferences.\n" << "Probably FG_ROOT is not properly set.");
690 }
691
692 /// now set up the simgears required model stuff
693
694 simgear::ResourceManager::instance()->addBasePath(fg_root,
695 simgear::ResourceManager::PRIORITY_DEFAULT);
696 // Just reference simgears reader writer stuff so that the globals get
697 // pulled in by the linker ...
698 simgear::ModelRegistry::instance();
699
700 sgUserDataInit(props.get());
701 SGMaterialLibPtr ml = new SGMaterialLib;
702 SGPath mpath(fg_root);
703
704 // TODO: Pick up correct materials.xml file. Urrgh - this can't be runtime dependent...
705 mpath.append("Materials/default/materials.xml");
706 try {
707 ml->load(fg_root, mpath.str(), props);
708 } catch (...) {
709 SG_LOG(SG_GENERAL, SG_ALERT,
710 "Problems loading FlightGear materials.\n" << "Probably FG_ROOT is not properly set.");
711 }
712 simgear::SGModelLib::init(fg_root, props);
713
714 // Set up the reader/writer options
715 osg::ref_ptr<simgear::SGReaderWriterOptions> options;
716 if (osgDB::Options* ropt = osgDB::Registry::instance()->getOptions())
717 options = new simgear::SGReaderWriterOptions(*ropt);
718 else
719 options = new simgear::SGReaderWriterOptions;
720 osgDB::convertStringPathIntoFilePathList(fg_scenery,
721 options->getDatabasePathList());
722 options->setMaterialLib(ml);
723 options->setPropertyNode(props);
724 options->setPluginStringData("SimGear::FG_ROOT", fg_root);
725 options->setPluginStringData("SimGear::TERRASYNC_ROOT", fg_terrasync_root);
726
727 // Here, all arguments are processed
728 arguments.reportRemainingOptionsAsUnrecognized();
729 arguments.writeErrorMessages(std::cerr);
730
731 std::string dot = "";
732 return processDirectory(options, dot);
733 }
734