]> git.mxchange.org Git - flightgear.git/blob - src/Main/fg_props.cxx
James Turner:
[flightgear.git] / src / Main / fg_props.cxx
1 // fg_props.cxx -- support for FlightGear properties.
2 //
3 // Written by David Megginson, started 2000.
4 //
5 // Copyright (C) 2000, 2001 David Megginson - david@megginson.com
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include <simgear/compiler.h>
28
29 #include <simgear/structure/exception.hxx>
30 #include <simgear/magvar/magvar.hxx>
31 #include <simgear/timing/sg_time.hxx>
32 #include <simgear/misc/sg_path.hxx>
33 #include <simgear/scene/material/matlib.hxx>
34 #include <simgear/sound/soundmgr_openal.hxx>
35
36 #include <iostream>
37
38 #include <Aircraft/aircraft.hxx>
39 #include <FDM/flight.hxx>
40
41 #include <Time/tmp.hxx>
42 #include <Environment/environment.hxx>
43
44 #include <GUI/gui.h>
45
46 #include "globals.hxx"
47 #include "fg_props.hxx"
48
49 using std::istream;
50 using std::ostream;
51
52 static bool winding_ccw = true; // FIXME: temporary
53
54 static bool fdm_data_logging = false; // FIXME: temporary
55
56 static bool frozen = false;     // FIXME: temporary
57
58
59 \f
60 ////////////////////////////////////////////////////////////////////////
61 // Default property bindings (not yet handled by any module).
62 ////////////////////////////////////////////////////////////////////////
63
64 struct LogClassMapping {
65   sgDebugClass c;
66   string name;
67   LogClassMapping(sgDebugClass cc, string nname) { c = cc; name = nname; }
68 };
69
70 LogClassMapping log_class_mappings [] = {
71   LogClassMapping(SG_NONE, "none"),
72   LogClassMapping(SG_TERRAIN, "terrain"),
73   LogClassMapping(SG_ASTRO, "astro"),
74   LogClassMapping(SG_FLIGHT, "flight"),
75   LogClassMapping(SG_INPUT, "input"),
76   LogClassMapping(SG_GL, "gl"),
77   LogClassMapping(SG_VIEW, "view"),
78   LogClassMapping(SG_COCKPIT, "cockpit"),
79   LogClassMapping(SG_GENERAL, "general"),
80   LogClassMapping(SG_MATH, "math"),
81   LogClassMapping(SG_EVENT, "event"),
82   LogClassMapping(SG_AIRCRAFT, "aircraft"),
83   LogClassMapping(SG_AUTOPILOT, "autopilot"),
84   LogClassMapping(SG_IO, "io"),
85   LogClassMapping(SG_CLIPPER, "clipper"),
86   LogClassMapping(SG_NETWORK, "network"),
87   LogClassMapping(SG_INSTR, "instrumentation"),
88   LogClassMapping(SG_SYSTEMS, "systems"),
89   LogClassMapping(SG_UNDEFD, "")
90 };
91
92
93 /**
94  * Get the logging classes.
95  */
96 static const char *
97 getLoggingClasses ()
98 {
99   sgDebugClass classes = logbuf::get_log_classes();
100   static string result;
101   result = "";
102   for (int i = 0; log_class_mappings[i].c != SG_UNDEFD; i++) {
103     if ((classes&log_class_mappings[i].c) > 0) {
104       if (!result.empty())
105         result += '|';
106       result += log_class_mappings[i].name;
107     }
108   }
109   return result.c_str();
110 }
111
112
113 static void
114 addLoggingClass (const string &name)
115 {
116   sgDebugClass classes = logbuf::get_log_classes();
117   for (int i = 0; log_class_mappings[i].c != SG_UNDEFD; i++) {
118     if (name == log_class_mappings[i].name) {
119       logbuf::set_log_classes(sgDebugClass(classes|log_class_mappings[i].c));
120       return;
121     }
122   }
123   SG_LOG(SG_GENERAL, SG_WARN, "Unknown logging class: " << name);
124 }
125
126
127 /**
128  * Set the logging classes.
129  */
130 static void
131 setLoggingClasses (const char * c)
132 {
133   string classes = c;
134   logbuf::set_log_classes(SG_NONE);
135
136   if (classes == "none") {
137     SG_LOG(SG_GENERAL, SG_INFO, "Disabled all logging classes");
138     return;
139   }
140
141   if (classes.empty() || classes == "all") { // default
142     logbuf::set_log_classes(SG_ALL);
143     SG_LOG(SG_GENERAL, SG_INFO, "Enabled all logging classes: "
144            << getLoggingClasses());
145     return;
146   }
147
148   string rest = classes;
149   string name = "";
150   int sep = rest.find('|');
151   while (sep > 0) {
152     name = rest.substr(0, sep);
153     rest = rest.substr(sep+1);
154     addLoggingClass(name);
155     sep = rest.find('|');
156   }
157   addLoggingClass(rest);
158   SG_LOG(SG_GENERAL, SG_INFO, "Set logging classes to "
159          << getLoggingClasses());
160 }
161
162
163 /**
164  * Get the logging priority.
165  */
166 static const char *
167 getLoggingPriority ()
168 {
169   switch (logbuf::get_log_priority()) {
170   case SG_BULK:
171     return "bulk";
172   case SG_DEBUG:
173     return "debug";
174   case SG_INFO:
175     return "info";
176   case SG_WARN:
177     return "warn";
178   case SG_ALERT:
179     return "alert";
180   default:
181     SG_LOG(SG_GENERAL, SG_WARN, "Internal: Unknown logging priority number: "
182            << logbuf::get_log_priority());
183     return "unknown";
184   }
185 }
186
187
188 /**
189  * Set the logging priority.
190  */
191 static void
192 setLoggingPriority (const char * p)
193 {
194   if (p == 0)
195       return;
196   string priority = p;
197   if (priority == "bulk") {
198     logbuf::set_log_priority(SG_BULK);
199   } else if (priority == "debug") {
200     logbuf::set_log_priority(SG_DEBUG);
201   } else if (priority.empty() || priority == "info") { // default
202     logbuf::set_log_priority(SG_INFO);
203   } else if (priority == "warn") {
204     logbuf::set_log_priority(SG_WARN);
205   } else if (priority == "alert") {
206     logbuf::set_log_priority(SG_ALERT);
207   } else {
208     SG_LOG(SG_GENERAL, SG_WARN, "Unknown logging priority " << priority);
209   }
210   SG_LOG(SG_GENERAL, SG_DEBUG, "Logging priority is " << getLoggingPriority());
211 }
212
213
214 /**
215  * Return the current frozen state.
216  */
217 static bool
218 getFreeze ()
219 {
220   return frozen;
221 }
222
223
224 /**
225  * Set the current frozen state.
226  */
227 static void
228 setFreeze (bool f)
229 {
230     frozen = f;
231
232     // Stop sound on a pause
233     SGSoundMgr *s = globals->get_soundmgr();
234     if ( s != NULL ) {
235         if ( f ) {
236             s->pause();
237         } else if (!fgGetBool("/sim/sound/pause")) {
238             s->resume();
239         }
240     }
241 }
242
243
244 /**
245  * Return the number of milliseconds elapsed since simulation started.
246  */
247 static double
248 getElapsedTime_sec ()
249 {
250   return globals->get_sim_time_sec();
251 }
252
253
254 /**
255  * Return the current Zulu time.
256  */
257 static const char *
258 getDateString ()
259 {
260   static char buf[64];          // FIXME
261   struct tm * t = globals->get_time_params()->getGmt();
262   sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d",
263           t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
264           t->tm_hour, t->tm_min, t->tm_sec);
265   return buf;
266 }
267
268
269 /**
270  * Set the current Zulu time.
271  */
272 static void
273 setDateString (const char * date_string)
274 {
275   static const SGPropertyNode *cur_time_override
276         = fgGetNode("/sim/time/cur-time-override", true);
277
278   SGTime * st = globals->get_time_params();
279   struct tm * current_time = st->getGmt();
280   struct tm new_time;
281
282                                 // Scan for basic ISO format
283                                 // YYYY-MM-DDTHH:MM:SS
284   int ret = sscanf(date_string, "%d-%d-%dT%d:%d:%d",
285                    &(new_time.tm_year), &(new_time.tm_mon),
286                    &(new_time.tm_mday), &(new_time.tm_hour),
287                    &(new_time.tm_min), &(new_time.tm_sec));
288
289                                 // Be pretty picky about this, so
290                                 // that strange things don't happen
291                                 // if the save file has been edited
292                                 // by hand.
293   if (ret != 6) {
294     SG_LOG(SG_INPUT, SG_WARN, "Date/time string " << date_string
295            << " not in YYYY-MM-DDTHH:MM:SS format; skipped");
296     return;
297   }
298
299                                 // OK, it looks like we got six
300                                 // values, one way or another.
301   new_time.tm_year -= 1900;
302   new_time.tm_mon -= 1;
303
304                                 // Now, tell flight gear to use
305                                 // the new time.  This was far
306                                 // too difficult, by the way.
307   long int warp =
308     mktime(&new_time) - mktime(current_time) + globals->get_warp();
309   double lon = current_aircraft.fdm_state->get_Longitude();
310   double lat = current_aircraft.fdm_state->get_Latitude();
311   globals->set_warp(warp);
312   st->update(lon, lat, cur_time_override->getLongValue(), warp);
313 }
314
315 /**
316  * Return the GMT as a string.
317  */
318 static const char *
319 getGMTString ()
320 {
321   static char buf[16];
322   struct tm *t = globals->get_time_params()->getGmt();
323   snprintf(buf, 16, "%.2d:%.2d:%.2d",
324       t->tm_hour, t->tm_min, t->tm_sec);
325   return buf;
326 }
327
328 /**
329  * Return the magnetic variation
330  */
331 static double
332 getMagVar ()
333 {
334   return globals->get_mag()->get_magvar() * SGD_RADIANS_TO_DEGREES;
335 }
336
337
338 /**
339  * Return the magnetic dip
340  */
341 static double
342 getMagDip ()
343 {
344   return globals->get_mag()->get_magdip() * SGD_RADIANS_TO_DEGREES;
345 }
346
347
348 /**
349  * Return the current heading in degrees.
350  */
351 static double
352 getHeadingMag ()
353 {
354   double magheading;
355   magheading = current_aircraft.fdm_state->get_Psi() * SGD_RADIANS_TO_DEGREES - getMagVar();
356   if (magheading < 0) magheading += 360;
357   return magheading;
358 }
359
360 static long
361 getWarp ()
362 {
363   return globals->get_warp();
364 }
365
366 static void
367 setWarp (long warp)
368 {
369   globals->set_warp(warp);
370 }
371
372 static long
373 getWarpDelta ()
374 {
375   return globals->get_warp_delta();
376 }
377
378 static void
379 setWarpDelta (long delta)
380 {
381   globals->set_warp_delta(delta);
382 }
383
384 static bool
385 getWindingCCW ()
386 {
387   return winding_ccw;
388 }
389
390 static void
391 setWindingCCW (bool state)
392 {
393   winding_ccw = state;
394   if ( winding_ccw )
395     glFrontFace ( GL_CCW );
396   else
397     glFrontFace ( GL_CW );
398 }
399
400 static bool
401 getFDMDataLogging ()
402 {
403   return fdm_data_logging;
404 }
405
406 static void
407 setFDMDataLogging (bool state)
408 {
409                                 // kludge; no getter or setter available
410   if (state != fdm_data_logging) {
411     fgToggleFDMdataLogging();
412     fdm_data_logging = state;
413   }
414 }
415
416 static const char *
417 getLongitudeString ()
418 {
419   static SGConstPropertyNode_ptr n = fgGetNode("/position/longitude-deg", true);
420   static SGConstPropertyNode_ptr f = fgGetNode("/sim/lon-lat-format", true);
421   static char buf[32];
422   double d = n->getDoubleValue();
423   int format = f->getIntValue();
424   char c = d < 0.0 ? 'W' : 'E';
425
426   if (format == 0) {
427     snprintf(buf, 32, "%3.6f%c", d, c);
428
429   } else if (format == 1) {
430     // dd mm.mmm' (DMM-Format) -- uses a round-off factor tailored to the
431     // required precision of the minutes field (three decimal places),
432     // preventing minute values of 60.
433     double deg = fabs(d) + 5.0E-4 / 60.0;
434     double min = fabs(deg - int(deg)) * 60.0 - 4.999E-4;
435     snprintf(buf, 32, "%d*%06.3f%c", int(d < 0.0 ? -deg : deg), min, c);
436
437   } else {
438     // mm'ss.s'' (DMS-Format) -- uses a round-off factor tailored to the
439     // required precision of the seconds field (one decimal place),
440     // preventing second values of 60.
441     double deg = fabs(d) + 0.05 / 3600.0;
442     double min = (deg - int(deg)) * 60.0;
443     double sec = (min - int(min)) * 60.0 - 0.049;
444     snprintf(buf, 32, "%d*%02d %04.1f%c", int(d < 0.0 ? -deg : deg),
445         int(min), fabs(sec), c);
446   }
447   buf[31] = '\0';
448   return buf;
449 }
450
451 static const char *
452 getLatitudeString ()
453 {
454   static SGConstPropertyNode_ptr n = fgGetNode("/position/latitude-deg", true);
455   static SGConstPropertyNode_ptr f = fgGetNode("/sim/lon-lat-format", true);
456   static char buf[32];
457   double d = n->getDoubleValue();
458   int format = f->getIntValue();
459   char c = d < 0.0 ? 'S' : 'N';
460
461   if (format == 0) {
462     snprintf(buf, 32, "%3.6f%c", d, c);
463
464   } else if (format == 1) {
465     double deg = fabs(d) + 5.0E-4 / 60.0;
466     double min = fabs(deg - int(deg)) * 60.0 - 4.999E-4;
467     snprintf(buf, 32, "%d*%06.3f%c", int(d < 0.0 ? -deg : deg), min, c);
468
469   } else {
470     double deg = fabs(d) + 0.05 / 3600.0;
471     double min = (deg - int(deg)) * 60.0;
472     double sec = (min - int(min)) * 60.0 - 0.049;
473     snprintf(buf, 32, "%d*%02d %04.1f%c", int(d < 0.0 ? -deg : deg),
474         int(min), fabs(sec), c);
475   }
476   buf[31] = '\0';
477   return buf;
478 }
479
480
481
482 \f
483 ////////////////////////////////////////////////////////////////////////
484 // Tie the properties.
485 ////////////////////////////////////////////////////////////////////////
486
487 FGProperties::FGProperties ()
488 {
489 }
490
491 FGProperties::~FGProperties ()
492 {
493 }
494
495 void
496 FGProperties::init ()
497 {
498 }
499
500 void
501 FGProperties::bind ()
502 {
503                                 // Simulation
504   fgTie("/sim/logging/priority", getLoggingPriority, setLoggingPriority);
505   fgTie("/sim/logging/classes", getLoggingClasses, setLoggingClasses);
506   fgTie("/sim/freeze/master", getFreeze, setFreeze);
507
508   fgTie("/sim/time/elapsed-sec", getElapsedTime_sec);
509   fgTie("/sim/time/gmt", getDateString, setDateString);
510   fgSetArchivable("/sim/time/gmt");
511   fgTie("/sim/time/gmt-string", getGMTString);
512
513                                 // Position
514   fgTie("/position/latitude-string", getLatitudeString);
515   fgTie("/position/longitude-string", getLongitudeString);
516
517                                 // Orientation
518   fgTie("/orientation/heading-magnetic-deg", getHeadingMag);
519
520   fgTie("/environment/magnetic-variation-deg", getMagVar);
521   fgTie("/environment/magnetic-dip-deg", getMagDip);
522
523   fgTie("/sim/time/warp", getWarp, setWarp, false);
524   fgTie("/sim/time/warp-delta", getWarpDelta, setWarpDelta);
525
526                                 // Misc. Temporary junk.
527   fgTie("/sim/temp/winding-ccw", getWindingCCW, setWindingCCW, false);
528   fgTie("/sim/temp/fdm-data-logging", getFDMDataLogging, setFDMDataLogging);
529 }
530
531 void
532 FGProperties::unbind ()
533 {
534                                 // Simulation
535   fgUntie("/sim/logging/priority");
536   fgUntie("/sim/logging/classes");
537   fgUntie("/sim/freeze/master");
538
539   fgUntie("/sim/time/elapsed-sec");
540   fgUntie("/sim/time/gmt");
541   fgUntie("/sim/time/gmt-string");
542                                 // Position
543   fgUntie("/position/latitude-string");
544   fgUntie("/position/longitude-string");
545
546                                 // Orientation
547   fgUntie("/orientation/heading-magnetic-deg");
548
549                                 // Environment
550   fgUntie("/environment/magnetic-variation-deg");
551   fgUntie("/environment/magnetic-dip-deg");
552
553   fgUntie("/sim/time/warp");
554   fgUntie("/sim/time/warp-delta");
555
556                                 // Misc. Temporary junk.
557   fgUntie("/sim/temp/winding-ccw");
558   fgUntie("/sim/temp/full-screen");
559   fgUntie("/sim/temp/fdm-data-logging");
560 }
561
562 void
563 FGProperties::update (double dt)
564 {
565                                 // Date and time
566     struct tm *t = globals->get_time_params()->getGmt();
567
568     fgSetInt("/sim/time/utc/year", t->tm_year + 1900);
569     fgSetInt("/sim/time/utc/month", t->tm_mon + 1);
570     fgSetInt("/sim/time/utc/day", t->tm_mday);
571     fgSetInt("/sim/time/utc/hour", t->tm_hour);
572     fgSetInt("/sim/time/utc/minute", t->tm_min);
573     fgSetInt("/sim/time/utc/second", t->tm_sec);
574
575     fgSetDouble("/sim/time/utc/day-seconds",
576                 t->tm_hour * 3600 +
577                 t->tm_min * 60 +
578                 t->tm_sec);
579
580     fgSetInt("/sim/time/local-offset",
581              globals->get_time_params()->get_local_offset());
582 }
583
584
585 \f
586 ////////////////////////////////////////////////////////////////////////
587 // Save and restore.
588 ////////////////////////////////////////////////////////////////////////
589
590
591 /**
592  * Save the current state of the simulator to a stream.
593  */
594 bool
595 fgSaveFlight (ostream &output, bool write_all)
596 {
597
598   fgSetBool("/sim/presets/onground", false);
599   fgSetArchivable("/sim/presets/onground");
600   fgSetBool("/sim/presets/trim", false);
601   fgSetArchivable("/sim/presets/trim");
602   fgSetString("/sim/presets/speed-set", "UVW");
603   fgSetArchivable("/sim/presets/speed-set");
604
605   try {
606     writeProperties(output, globals->get_props(), write_all);
607   } catch (const sg_exception &e) {
608     guiErrorMessage("Error saving flight: ", e);
609     return false;
610   }
611   return true;
612 }
613
614
615 /**
616  * Restore the current state of the simulator from a stream.
617  */
618 bool
619 fgLoadFlight (istream &input)
620 {
621   SGPropertyNode props;
622   try {
623     readProperties(input, &props);
624   } catch (const sg_exception &e) {
625     guiErrorMessage("Error reading saved flight: ", e);
626     return false;
627   }
628
629   fgSetBool("/sim/presets/onground", false);
630   fgSetBool("/sim/presets/trim", false);
631   fgSetString("/sim/presets/speed-set", "UVW");
632
633   copyProperties(&props, globals->get_props());
634   // When loading a flight, make it the
635   // new initial state.
636   globals->saveInitialState();
637   return true;
638 }
639
640
641 bool
642 fgLoadProps (const char * path, SGPropertyNode * props, bool in_fg_root, int default_mode)
643 {
644     string fullpath;
645     if (in_fg_root) {
646         SGPath loadpath(globals->get_fg_root());
647         loadpath.append(path);
648         fullpath = loadpath.str();
649     } else {
650         fullpath = path;
651     }
652
653     try {
654         readProperties(fullpath, props, default_mode);
655     } catch (const sg_exception &e) {
656         guiErrorMessage("Error reading properties: ", e);
657         return false;
658     }
659     return true;
660 }
661
662
663 \f
664 ////////////////////////////////////////////////////////////////////////
665 // Property convenience functions.
666 ////////////////////////////////////////////////////////////////////////
667
668 SGPropertyNode *
669 fgGetNode (const char * path, bool create)
670 {
671   return globals->get_props()->getNode(path, create);
672 }
673
674 SGPropertyNode * 
675 fgGetNode (const char * path, int index, bool create)
676 {
677   return globals->get_props()->getNode(path, index, create);
678 }
679
680 bool
681 fgHasNode (const char * path)
682 {
683   return (fgGetNode(path, false) != 0);
684 }
685
686 void
687 fgAddChangeListener (SGPropertyChangeListener * listener, const char * path)
688 {
689   fgGetNode(path, true)->addChangeListener(listener);
690 }
691
692 void
693 fgAddChangeListener (SGPropertyChangeListener * listener,
694                      const char * path, int index)
695 {
696   fgGetNode(path, index, true)->addChangeListener(listener);
697 }
698
699 bool
700 fgGetBool (const char * name, bool defaultValue)
701 {
702   return globals->get_props()->getBoolValue(name, defaultValue);
703 }
704
705 int
706 fgGetInt (const char * name, int defaultValue)
707 {
708   return globals->get_props()->getIntValue(name, defaultValue);
709 }
710
711 int
712 fgGetLong (const char * name, long defaultValue)
713 {
714   return globals->get_props()->getLongValue(name, defaultValue);
715 }
716
717 float
718 fgGetFloat (const char * name, float defaultValue)
719 {
720   return globals->get_props()->getFloatValue(name, defaultValue);
721 }
722
723 double
724 fgGetDouble (const char * name, double defaultValue)
725 {
726   return globals->get_props()->getDoubleValue(name, defaultValue);
727 }
728
729 const char *
730 fgGetString (const char * name, const char * defaultValue)
731 {
732   return globals->get_props()->getStringValue(name, defaultValue);
733 }
734
735 bool
736 fgSetBool (const char * name, bool val)
737 {
738   return globals->get_props()->setBoolValue(name, val);
739 }
740
741 bool
742 fgSetInt (const char * name, int val)
743 {
744   return globals->get_props()->setIntValue(name, val);
745 }
746
747 bool
748 fgSetLong (const char * name, long val)
749 {
750   return globals->get_props()->setLongValue(name, val);
751 }
752
753 bool
754 fgSetFloat (const char * name, float val)
755 {
756   return globals->get_props()->setFloatValue(name, val);
757 }
758
759 bool
760 fgSetDouble (const char * name, double val)
761 {
762   return globals->get_props()->setDoubleValue(name, val);
763 }
764
765 bool
766 fgSetString (const char * name, const char * val)
767 {
768   return globals->get_props()->setStringValue(name, val);
769 }
770
771 void
772 fgSetArchivable (const char * name, bool state)
773 {
774   SGPropertyNode * node = globals->get_props()->getNode(name);
775   if (node == 0)
776     SG_LOG(SG_GENERAL, SG_DEBUG,
777            "Attempt to set archive flag for non-existant property "
778            << name);
779   else
780     node->setAttribute(SGPropertyNode::ARCHIVE, state);
781 }
782
783 void
784 fgSetReadable (const char * name, bool state)
785 {
786   SGPropertyNode * node = globals->get_props()->getNode(name);
787   if (node == 0)
788     SG_LOG(SG_GENERAL, SG_DEBUG,
789            "Attempt to set read flag for non-existant property "
790            << name);
791   else
792     node->setAttribute(SGPropertyNode::READ, state);
793 }
794
795 void
796 fgSetWritable (const char * name, bool state)
797 {
798   SGPropertyNode * node = globals->get_props()->getNode(name);
799   if (node == 0)
800     SG_LOG(SG_GENERAL, SG_DEBUG,
801            "Attempt to set write flag for non-existant property "
802            << name);
803   else
804     node->setAttribute(SGPropertyNode::WRITE, state);
805 }
806
807 void
808 fgUntie (const char * name)
809 {
810   if (!globals->get_props()->untie(name))
811     SG_LOG(SG_GENERAL, SG_WARN, "Failed to untie property " << name);
812 }
813
814
815 // end of fg_props.cxx