]> git.mxchange.org Git - flightgear.git/blob - src/FDM/YASim/FGFDM.cpp
Adds a basic FDM model for LaRCsim debugging purposes.
[flightgear.git] / src / FDM / YASim / FGFDM.cpp
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #include <Main/fg_props.hxx>
5
6 #include "Jet.hpp"
7 #include "SimpleJet.hpp"
8 #include "Gear.hpp"
9 #include "Atmosphere.hpp"
10 #include "PropEngine.hpp"
11 #include "Propeller.hpp"
12 #include "PistonEngine.hpp"
13
14 #include "FGFDM.hpp"
15 namespace yasim {
16
17 // Some conversion factors
18 static const float KTS2MPS = 0.514444444444;
19 static const float FT2M = 0.3048;
20 static const float DEG2RAD = 0.0174532925199;
21 static const float RPM2RAD = 0.10471975512;
22 static const float LBS2N = 4.44822;
23 static const float LBS2KG = 0.45359237;
24 static const float KG2LBS = 2.2046225;
25 static const float CM2GALS = 264.172037284;
26 static const float HP2W = 745.700;
27 static const float INHG2PA = 3386.389;
28 static const float K2DEGF = 1.8;
29 static const float K2DEGFOFFSET = -459.4;
30 static const float CIN2CM = 1.6387064e-5;
31
32 // Stubs, so that this can be compiled without the FlightGear
33 // binary.  What's the best way to handle this?
34
35 //     float fgGetFloat(char* name, float def) { return 0; }
36 //     void fgSetFloat(char* name, float val) {}
37
38 FGFDM::FGFDM()
39 {
40     _nextEngine = 0;
41
42     // Map /controls/flight/elevator to the approach elevator control.  This
43     // should probably be settable, but there are very few aircraft
44     // who trim their approaches using things other than elevator.
45     _airplane.setElevatorControl(parseAxis("/controls/flight/elevator-trim"));
46 }
47
48 FGFDM::~FGFDM()
49 {
50     int i;
51     for(i=0; i<_axes.size(); i++) {
52         AxisRec* a = (AxisRec*)_axes.get(i);
53         delete[] a->name;
54         delete a;
55     }
56     for(i=0; i<_thrusters.size(); i++) {
57         EngRec* er = (EngRec*)_thrusters.get(i);
58         delete[] er->prefix;
59         delete er->eng;
60         delete er;
61     }
62     for(i=0; i<_weights.size(); i++) {
63         WeightRec* wr = (WeightRec*)_weights.get(i);
64         delete[] wr->prop;
65         delete wr;
66     }
67     for(i=0; i<_controlProps.size(); i++)
68         delete (PropOut*)_controlProps.get(i);
69 }
70
71 void FGFDM::iterate(float dt)
72 {
73     getExternalInput(dt);
74     _airplane.iterate(dt);
75
76     if(fgGetBool("/sim/freeze/fuel") != true)
77         _airplane.consumeFuel(dt);
78
79     setOutputProperties();
80 }
81
82 Airplane* FGFDM::getAirplane()
83 {
84     return &_airplane;
85 }
86
87 void FGFDM::init()
88 {
89     // Allows the user to start with something other than full fuel
90     _airplane.setFuelFraction(fgGetFloat("/sim/fuel-fraction", 1));
91
92     // This has a nasty habit of being false at startup.  That's not
93     // good.
94     fgSetBool("/controls/gear/gear-down", true);
95 }
96
97 // Not the worlds safest parser.  But it's short & sweet.
98 void FGFDM::startElement(const char* name, const XMLAttributes &atts)
99 {
100     XMLAttributes* a = (XMLAttributes*)&atts;
101     float v[3];
102     char buf[64];
103
104     if(eq(name, "airplane")) {
105         _airplane.setWeight(attrf(a, "mass") * LBS2KG);
106     } else if(eq(name, "approach")) {
107         float spd = attrf(a, "speed") * KTS2MPS;
108         float alt = attrf(a, "alt", 0) * FT2M;
109         float aoa = attrf(a, "aoa", 0) * DEG2RAD;
110         _airplane.setApproach(spd, alt, aoa);
111         _cruiseCurr = false;
112     } else if(eq(name, "cruise")) {
113         float spd = attrf(a, "speed") * KTS2MPS;
114         float alt = attrf(a, "alt") * FT2M;
115         _airplane.setCruise(spd, alt);
116         _cruiseCurr = true;
117     } else if(eq(name, "cockpit")) {
118         v[0] = attrf(a, "x");
119         v[1] = attrf(a, "y");
120         v[2] = attrf(a, "z");
121         _airplane.setPilotPos(v);
122     } else if(eq(name, "wing")) {
123         _airplane.setWing(parseWing(a, name));
124     } else if(eq(name, "hstab")) {
125         _airplane.setTail(parseWing(a, name));
126     } else if(eq(name, "vstab")) {
127         _airplane.addVStab(parseWing(a, name));
128     } else if(eq(name, "propeller")) {
129         parsePropeller(a);
130     } else if(eq(name, "thruster")) {
131         SimpleJet* j = new SimpleJet();
132         _currObj = j;
133         v[0] = attrf(a, "x"); v[1] = attrf(a, "y"); v[2] = attrf(a, "z");
134         j->setPosition(v);
135         _airplane.addThruster(j, 0, v);
136         v[0] = attrf(a, "vx"); v[1] = attrf(a, "vy"); v[2] = attrf(a, "vz");
137         j->setDirection(v);
138         j->setThrust(attrf(a, "thrust") * LBS2N);
139     } else if(eq(name, "jet")) {
140         Jet* j = new Jet();
141         _currObj = j;
142         v[0] = attrf(a, "x");
143         v[1] = attrf(a, "y");
144         v[2] = attrf(a, "z");
145         float mass = attrf(a, "mass") * LBS2KG;
146         j->setMaxThrust(attrf(a, "thrust") * LBS2N,
147                         attrf(a, "afterburner", 0) * LBS2N);
148         j->setVectorAngle(attrf(a, "rotate", 0) * DEG2RAD);
149
150         float n1min = attrf(a, "n1-idle", 55);
151         float n1max = attrf(a, "n1-max", 102);
152         float n2min = attrf(a, "n2-idle", 73);
153         float n2max = attrf(a, "n2-max", 103);
154         j->setRPMs(n1min, n1max, n2min, n2max);
155
156         j->setTSFC(attrf(a, "tsfc", 0.8));
157         if(a->hasAttribute("egt"))  j->setEGT(attrf(a, "egt"));
158         if(a->hasAttribute("epr"))  j->setEPR(attrf(a, "epr"));
159         if(a->hasAttribute("exhaust-speed"))
160             j->setVMax(attrf(a, "exhaust-speed") * KTS2MPS);
161         
162         j->setPosition(v);
163         _airplane.addThruster(j, mass, v);
164         sprintf(buf, "/engines/engine[%d]", _nextEngine++);
165         EngRec* er = new EngRec();
166         er->eng = j;
167         er->prefix = dup(buf);
168         _thrusters.add(er);
169     } else if(eq(name, "gear")) {
170         Gear* g = new Gear();
171         _currObj = g;
172         v[0] = attrf(a, "x");
173         v[1] = attrf(a, "y");
174         v[2] = attrf(a, "z");
175         g->setPosition(v);
176         v[0] = 0;
177         v[1] = 0;
178         v[2] = attrf(a, "compression", 1);
179         g->setCompression(v);
180         g->setBrake(attrf(a, "skid", 0));
181         g->setStaticFriction(attrf(a, "sfric", 0.8));
182         g->setDynamicFriction(attrf(a, "dfric", 0.7));
183         g->setSpring(attrf(a, "spring", 1));
184         g->setDamping(attrf(a, "damp", 1));
185         _airplane.addGear(g);
186     } else if(eq(name, "fuselage")) {
187         float b[3];
188         v[0] = attrf(a, "ax");
189         v[1] = attrf(a, "ay");
190         v[2] = attrf(a, "az");
191         b[0] = attrf(a, "bx");
192         b[1] = attrf(a, "by");
193         b[2] = attrf(a, "bz");
194         float taper = attrf(a, "taper", 1);
195         float mid = attrf(a, "midpoint", 0.5);
196         _airplane.addFuselage(v, b, attrf(a, "width"), taper, mid);
197     } else if(eq(name, "tank")) {
198         v[0] = attrf(a, "x");
199         v[1] = attrf(a, "y");
200         v[2] = attrf(a, "z");
201         float density = 6.0; // gasoline, in lbs/gal
202         if(a->hasAttribute("jet")) density = 6.72; 
203         density *= LBS2KG*CM2GALS;
204         _airplane.addTank(v, attrf(a, "capacity") * LBS2KG, density);
205     } else if(eq(name, "ballast")) {
206         v[0] = attrf(a, "x");
207         v[1] = attrf(a, "y");
208         v[2] = attrf(a, "z");
209         _airplane.addBallast(v, attrf(a, "mass") * LBS2KG);
210     } else if(eq(name, "weight")) {
211         parseWeight(a);
212     } else if(eq(name, "stall")) {
213         Wing* w = (Wing*)_currObj;
214         w->setStall(attrf(a, "aoa") * DEG2RAD);
215         w->setStallWidth(attrf(a, "width", 2) * DEG2RAD);
216         w->setStallPeak(attrf(a, "peak", 1.5));
217     } else if(eq(name, "flap0")) {
218         ((Wing*)_currObj)->setFlap0(attrf(a, "start"), attrf(a, "end"),
219                                     attrf(a, "lift"), attrf(a, "drag"));
220     } else if(eq(name, "flap1")) {
221         ((Wing*)_currObj)->setFlap1(attrf(a, "start"), attrf(a, "end"),
222                                     attrf(a, "lift"), attrf(a, "drag"));
223     } else if(eq(name, "slat")) {
224         ((Wing*)_currObj)->setSlat(attrf(a, "start"), attrf(a, "end"),
225                                    attrf(a, "aoa"), attrf(a, "drag"));
226     } else if(eq(name, "spoiler")) {
227         ((Wing*)_currObj)->setSpoiler(attrf(a, "start"), attrf(a, "end"),
228                                       attrf(a, "lift"), attrf(a, "drag"));
229     } else if(eq(name, "actionpt")) {
230         v[0] = attrf(a, "x");
231         v[1] = attrf(a, "y");
232         v[2] = attrf(a, "z");
233         ((Thruster*)_currObj)->setPosition(v);
234     } else if(eq(name, "dir")) {
235         v[0] = attrf(a, "x");
236         v[1] = attrf(a, "y");
237         v[2] = attrf(a, "z");
238         ((Thruster*)_currObj)->setDirection(v);
239     } else if(eq(name, "control-setting")) {
240         // A cruise or approach control setting
241         const char* axis = a->getValue("axis");
242         float value = attrf(a, "value", 0);
243         if(_cruiseCurr)
244             _airplane.addCruiseControl(parseAxis(axis), value);
245         else
246             _airplane.addApproachControl(parseAxis(axis), value);
247     } else if(eq(name, "control-input")) {
248
249         // A mapping of input property to a control
250         int axis = parseAxis(a->getValue("axis"));
251         int control = parseOutput(a->getValue("control"));
252         int opt = 0;
253         opt |= a->hasAttribute("split") ? ControlMap::OPT_SPLIT : 0;
254         opt |= a->hasAttribute("invert") ? ControlMap::OPT_INVERT : 0;
255         opt |= a->hasAttribute("square") ? ControlMap::OPT_SQUARE : 0;
256         
257         ControlMap* cm = _airplane.getControlMap();
258         if(a->hasAttribute("src0")) {
259                            cm->addMapping(axis, control, _currObj, opt,
260                            attrf(a, "src0"), attrf(a, "src1"), 
261                            attrf(a, "dst0"), attrf(a, "dst1"));
262         } else {
263             cm->addMapping(axis, control, _currObj, opt);
264         }
265     } else if(eq(name, "control-output")) {
266         // A property output for a control on the current object
267         ControlMap* cm = _airplane.getControlMap();
268         int type = parseOutput(a->getValue("control"));
269         int handle = cm->getOutputHandle(_currObj, type);
270
271         PropOut* p = new PropOut();
272         p->prop = fgGetNode(a->getValue("prop"), true);
273         p->handle = handle;
274         p->type = type;
275         p->left = !(a->hasAttribute("side") &&
276                         eq("right", a->getValue("side")));
277         p->min = attrf(a, "min", cm->rangeMin(type));
278         p->max = attrf(a, "max", cm->rangeMax(type));
279         _controlProps.add(p);
280
281     } else if(eq(name, "control-speed")) {
282         ControlMap* cm = _airplane.getControlMap();
283         int type = parseOutput(a->getValue("control"));
284         int handle = cm->getOutputHandle(_currObj, type);
285         float time = attrf(a, "transition-time", 0);
286         
287         cm->setTransitionTime(handle, time);
288     } else {
289         SG_LOG(SG_FLIGHT,SG_ALERT,"Unexpected tag '"
290                << name << "' found in YASim aircraft description");
291         exit(1);
292     }
293 }
294
295 void FGFDM::getExternalInput(float dt)
296 {
297     // The control axes
298     ControlMap* cm = _airplane.getControlMap();
299     cm->reset();
300     int i;
301     for(i=0; i<_axes.size(); i++) {
302         AxisRec* a = (AxisRec*)_axes.get(i);
303         float val = fgGetFloat(a->name, 0);
304         cm->setInput(a->handle, val);
305     }
306     cm->applyControls(dt);
307
308     // Weights
309     for(i=0; i<_weights.size(); i++) {
310         WeightRec* wr = (WeightRec*)_weights.get(i);
311         _airplane.setWeight(wr->handle, LBS2KG * fgGetFloat(wr->prop));
312     }
313 }
314
315 void FGFDM::setOutputProperties()
316 {
317     char buf[256];
318     int i;
319
320     float grossWgt = _airplane.getModel()->getBody()->getTotalMass() * KG2LBS;
321     fgSetFloat("/yasim/gross-weight-lbs", grossWgt);
322
323     ControlMap* cm = _airplane.getControlMap();
324     for(i=0; i<_controlProps.size(); i++) {
325         PropOut* p = (PropOut*)_controlProps.get(i);
326         float val = (p->left
327                      ? cm->getOutput(p->handle)
328                      : cm->getOutputR(p->handle));
329         float rmin = cm->rangeMin(p->type);
330         float rmax = cm->rangeMax(p->type);
331         float frac = (val - rmin) / (rmax - rmin);
332         val = frac*(p->max - p->min) + p->min;
333         p->prop->setFloatValue(val);
334     }
335
336     float totalFuel = 0, totalCap = 0;
337     float fuelDensity = 720; // in kg/m^3, default to gasoline: ~6 lb/gal
338     for(i=0; i<_airplane.numTanks(); i++) {
339         fuelDensity = _airplane.getFuelDensity(i);
340         sprintf(buf, "/consumables/fuel/tank[%d]/level-gal_us", i);
341         fgSetFloat(buf, CM2GALS*_airplane.getFuel(i)/fuelDensity);
342         sprintf(buf, "/consumables/fuel/tank[%d]/level-lbs", i);
343         fgSetFloat(buf, KG2LBS*_airplane.getFuel(i));
344         totalFuel += _airplane.getFuel(i);
345         totalCap += _airplane.getTankCapacity(i);
346     }
347     if(totalCap != 0) {
348         fgSetFloat("/consumables/fuel/total-fuel-lbs", KG2LBS*totalFuel);
349         fgSetFloat("/consumables/fuel/total-fuel-gals",
350                    CM2GALS*totalFuel/fuelDensity);
351         fgSetFloat("/consumables/fuel/total-fuel-norm", totalFuel/totalCap);
352     }
353
354     for(i=0; i<_thrusters.size(); i++) {
355         EngRec* er = (EngRec*)_thrusters.get(i);
356         Thruster* t = er->eng;
357
358         sprintf(buf, "%s/fuel-flow-gph", er->prefix);
359         fgSetFloat(buf, (t->getFuelFlow()/fuelDensity) * 3600 * CM2GALS);
360
361         if(t->getPropEngine()) {
362             PropEngine* p = t->getPropEngine();
363
364             sprintf(buf, "%s/rpm", er->prefix);
365             fgSetFloat(buf, p->getOmega() / RPM2RAD);
366         }
367
368         if(t->getPistonEngine()) {
369             PistonEngine* p = t->getPistonEngine();
370             
371             sprintf(buf, "%s/mp-osi", er->prefix);
372             fgSetFloat(buf, p->getMP() * (1/INHG2PA));
373
374             sprintf(buf, "%s/egt-degf", er->prefix);
375             fgSetFloat(buf, p->getEGT() * K2DEGF + K2DEGFOFFSET);
376         }
377
378         if(t->getJet()) {
379             Jet* j = t->getJet();
380
381             sprintf(buf, "%s/n1", er->prefix);
382             fgSetFloat(buf, j->getN1());
383
384             sprintf(buf, "%s/n2", er->prefix);
385             fgSetFloat(buf, j->getN2());
386
387             sprintf(buf, "%s/epr", er->prefix);
388             fgSetFloat(buf, j->getEPR());
389
390             sprintf(buf, "%s/egt-degf", er->prefix);
391             fgSetFloat(buf, j->getEGT() * K2DEGF + K2DEGFOFFSET);
392         }
393     }
394 }
395
396 Wing* FGFDM::parseWing(XMLAttributes* a, const char* type)
397 {
398     Wing* w = new Wing();
399
400     float defDihed = 0;
401     if(eq(type, "vstab"))
402         defDihed = 90;
403     else
404         w->setMirror(true);
405
406     float pos[3];
407     pos[0] = attrf(a, "x");
408     pos[1] = attrf(a, "y");
409     pos[2] = attrf(a, "z");
410     w->setBase(pos);
411
412     w->setLength(attrf(a, "length"));
413     w->setChord(attrf(a, "chord"));
414     w->setSweep(attrf(a, "sweep", 0) * DEG2RAD);
415     w->setTaper(attrf(a, "taper", 1));
416     w->setDihedral(attrf(a, "dihedral", defDihed) * DEG2RAD);
417     w->setCamber(attrf(a, "camber", 0));
418     w->setIncidence(attrf(a, "incidence", 0) * DEG2RAD);
419     w->setTwist(attrf(a, "twist", 0) * DEG2RAD);
420
421     // The 70% is a magic number that sorta kinda seems to match known
422     // throttle settings to approach speed.
423     w->setInducedDrag(0.7*attrf(a, "idrag", 1));
424
425     float effect = attrf(a, "effectiveness", 1);
426     w->setDragScale(w->getDragScale()*effect);
427
428     _currObj = w;
429     return w;
430 }
431
432 void FGFDM::parsePropeller(XMLAttributes* a)
433 {
434     float cg[3];
435     cg[0] = attrf(a, "x");
436     cg[1] = attrf(a, "y");
437     cg[2] = attrf(a, "z");
438     float mass = attrf(a, "mass") * LBS2KG;
439     float moment = attrf(a, "moment");
440     float radius = attrf(a, "radius");
441     float speed = attrf(a, "cruise-speed") * KTS2MPS;
442     float omega = attrf(a, "cruise-rpm") * RPM2RAD;
443     float power = attrf(a, "cruise-power") * HP2W;
444     float rho = Atmosphere::getStdDensity(attrf(a, "cruise-alt") * FT2M);
445
446     // Hack, fix this pronto:
447     float engP = attrf(a, "eng-power") * HP2W;
448     float engS = attrf(a, "eng-rpm") * RPM2RAD;
449
450     Propeller* prop = new Propeller(radius, speed, omega, rho, power);
451     PistonEngine* eng = new PistonEngine(engP, engS);
452     PropEngine* thruster = new PropEngine(prop, eng, moment);
453     _airplane.addThruster(thruster, mass, cg);
454
455     if(a->hasAttribute("displacement"))
456         eng->setDisplacement(attrf(a, "displacement") * CIN2CM);
457
458     if(a->hasAttribute("compression"))
459         eng->setCompression(attrf(a, "compression"));        
460
461     if(a->hasAttribute("turbo-mul")) {
462         float mul = attrf(a, "turbo-mul");
463         float mp = attrf(a, "wastegate-mp", 1e6) * INHG2PA;
464         eng->setTurboParams(mul, mp);
465     }
466
467     if(a->hasAttribute("takeoff-power")) {
468         float power0 = attrf(a, "takeoff-power") * HP2W;
469         float omega0 = attrf(a, "takeoff-rpm") * RPM2RAD;
470         prop->setTakeoff(omega0, power0);
471     }
472
473     if(a->hasAttribute("max-rpm")) {
474         float max = attrf(a, "max-rpm") * RPM2RAD;
475         float min = attrf(a, "min-rpm") * RPM2RAD;
476         thruster->setVariableProp(min, max);
477     }
478
479     if(a->hasAttribute("manual-pitch")) {
480         prop->setManualPitch();
481     }
482
483     char buf[64];
484     sprintf(buf, "/engines/engine[%d]", _nextEngine++);
485     EngRec* er = new EngRec();
486     er->eng = thruster;
487     er->prefix = dup(buf);
488     _thrusters.add(er);
489
490     _currObj = thruster;
491 }
492
493 // Turns a string axis name into an integer for use by the
494 // ControlMap.  Creates a new axis if this one hasn't been defined
495 // yet.
496 int FGFDM::parseAxis(const char* name)
497 {
498     int i;
499     for(i=0; i<_axes.size(); i++) {
500         AxisRec* a = (AxisRec*)_axes.get(i);
501         if(eq(a->name, name))
502             return a->handle;
503     }
504
505     // Not there, make a new one.
506     AxisRec* a = new AxisRec();
507     a->name = dup(name);
508     a->handle = _airplane.getControlMap()->newInput();
509     _axes.add(a);
510     return a->handle;
511 }
512
513 int FGFDM::parseOutput(const char* name)
514 {
515     if(eq(name, "THROTTLE"))  return ControlMap::THROTTLE;
516     if(eq(name, "MIXTURE"))   return ControlMap::MIXTURE;
517     if(eq(name, "STARTER"))   return ControlMap::STARTER;
518     if(eq(name, "MAGNETOS"))  return ControlMap::MAGNETOS;
519     if(eq(name, "ADVANCE"))   return ControlMap::ADVANCE;
520     if(eq(name, "REHEAT"))    return ControlMap::REHEAT;
521     if(eq(name, "BOOST"))     return ControlMap::BOOST;
522     if(eq(name, "VECTOR"))    return ControlMap::VECTOR;
523     if(eq(name, "PROP"))      return ControlMap::PROP;
524     if(eq(name, "BRAKE"))     return ControlMap::BRAKE;
525     if(eq(name, "STEER"))     return ControlMap::STEER;
526     if(eq(name, "EXTEND"))    return ControlMap::EXTEND;
527     if(eq(name, "INCIDENCE")) return ControlMap::INCIDENCE;
528     if(eq(name, "FLAP0"))     return ControlMap::FLAP0;
529     if(eq(name, "FLAP1"))     return ControlMap::FLAP1;
530     if(eq(name, "SLAT"))      return ControlMap::SLAT;
531     if(eq(name, "SPOILER"))   return ControlMap::SPOILER;
532     if(eq(name, "CASTERING")) return ControlMap::CASTERING;
533     if(eq(name, "PROPPITCH")) return ControlMap::PROPPITCH;
534     SG_LOG(SG_FLIGHT,SG_ALERT,"Unrecognized control type '"
535            << name << "' in YASim aircraft description.");
536     exit(1);
537
538 }
539
540 void FGFDM::parseWeight(XMLAttributes* a)
541 {
542     WeightRec* wr = new WeightRec();
543
544     float v[3];
545     v[0] = attrf(a, "x");
546     v[1] = attrf(a, "y");
547     v[2] = attrf(a, "z");
548
549     wr->prop = dup(a->getValue("mass-prop"));
550     wr->size = attrf(a, "size", 0);
551     wr->handle = _airplane.addWeight(v, wr->size);
552
553     _weights.add(wr);
554 }
555
556 bool FGFDM::eq(const char* a, const char* b)
557 {
558     // Figure it out for yourself. :)
559     while(*a && *b && *a == *b) { a++; b++; }
560     return !(*a || *b);
561 }
562
563 char* FGFDM::dup(const char* s)
564 {
565     int len=0;
566     while(s[len++]);
567     char* s2 = new char[len+1];
568     char* p = s2;
569     while((*p++ = *s++));
570     s2[len] = 0;
571     return s2;
572 }
573
574 int FGFDM::attri(XMLAttributes* atts, char* attr)
575 {
576     if(!atts->hasAttribute(attr)) {
577         SG_LOG(SG_FLIGHT,SG_ALERT,"Missing '" << attr <<
578                "' in YASim aircraft description");
579         exit(1);
580     }
581     return attri(atts, attr, 0);
582 }
583
584 int FGFDM::attri(XMLAttributes* atts, char* attr, int def)
585 {
586     const char* val = atts->getValue(attr);
587     if(val == 0) return def;
588     else         return atol(val);
589 }
590
591 float FGFDM::attrf(XMLAttributes* atts, char* attr)
592 {
593     if(!atts->hasAttribute(attr)) {
594         SG_LOG(SG_FLIGHT,SG_ALERT,"Missing '" << attr <<
595                "' in YASim aircraft description");
596         exit(1);
597     }
598     return attrf(atts, attr, 0);
599 }
600
601 float FGFDM::attrf(XMLAttributes* atts, char* attr, float def)
602 {
603     const char* val = atts->getValue(attr);
604     if(val == 0) return def;
605     else         return (float)atof(val);    
606 }
607
608 }; // namespace yasim