]> git.mxchange.org Git - flightgear.git/blob - src/FDM/YASim/Airplane.cpp
Update masses on the rigid body when consuming fuel. Simply updating
[flightgear.git] / src / FDM / YASim / Airplane.cpp
1 #include "Atmosphere.hpp"
2 #include "ControlMap.hpp"
3 #include "Gear.hpp"
4 #include "Math.hpp"
5 #include "Glue.hpp"
6 #include "RigidBody.hpp"
7 #include "Surface.hpp"
8 #include "Thruster.hpp"
9
10 #include "Airplane.hpp"
11
12 namespace yasim {
13
14 // gadgets
15 inline float norm(float f) { return f<1 ? 1/f : f; }
16 inline float abs(float f) { return f<0 ? -f : f; }
17
18 Airplane::Airplane()
19 {
20     _emptyWeight = 0;
21     _pilotPos[0] = _pilotPos[1] = _pilotPos[2] = 0;
22     _wing = 0;
23     _tail = 0;
24     _ballast = 0;
25     _cruiseP = 0;
26     _cruiseT = 0;
27     _cruiseSpeed = 0;
28     _cruiseWeight = 0;
29     _approachP = 0;
30     _approachT = 0;
31     _approachSpeed = 0;
32     _approachAoA = 0;
33     _approachWeight = 0;
34
35     _dragFactor = 1;
36     _liftRatio = 1;
37     _cruiseAoA = 0;
38     _tailIncidence = 0;
39 }
40
41 Airplane::~Airplane()
42 {
43     int i;
44     for(i=0; i<_fuselages.size(); i++)
45         delete (Fuselage*)_fuselages.get(i);
46     for(i=0; i<_tanks.size(); i++)
47         delete (Tank*)_tanks.get(i);
48     for(i=0; i<_thrusters.size(); i++)
49         delete (ThrustRec*)_thrusters.get(i);
50     for(i=0; i<_gears.size(); i++)
51         delete (GearRec*)_gears.get(i);
52     for(i=0; i<_surfs.size(); i++)
53         delete (Surface*)_surfs.get(i);    
54     for(i=0; i<_contacts.size(); i++)
55         delete[] (float*)_contacts.get(i);
56 }
57
58 void Airplane::iterate(float dt)
59 {
60     // The gear might have moved.  Change their aerodynamics.
61     updateGearState();
62
63     _model.iterate();
64 }
65
66 void Airplane::consumeFuel(float dt)
67 {
68     // This is a really simple implementation that assumes all engines
69     // draw equally from all tanks in proportion to the amount of fuel
70     // stored there.  Needs to be fixed, but that has to wait for a
71     // decision as to what the property interface will look like.
72     int i, outOfFuel = 0;
73     float fuelFlow = 0, totalFuel = 0.00001; // <-- overflow protection
74     for(i=0; i<_thrusters.size(); i++)
75         fuelFlow += ((ThrustRec*)_thrusters.get(i))->thruster->getFuelFlow();
76     for(i=0; i<_tanks.size(); i++)
77         totalFuel += ((Tank*)_tanks.get(i))->fill;
78     for(i=0; i<_tanks.size(); i++) {
79         Tank* t = (Tank*)_tanks.get(i);
80         t->fill -= dt * fuelFlow * (t->fill/totalFuel);
81         if(t->fill <= 0) {
82             t->fill = 0;
83             outOfFuel = 1;
84         }
85     }
86     if(outOfFuel)
87         for(int i=0; i<_thrusters.size(); i++)
88             ((ThrustRec*)_thrusters.get(i))->thruster->setFuelState(false);
89
90     // Set the tank masses on the RigidBody
91     for(i=0; i<_tanks.size(); i++) {
92         Tank* t = (Tank*)_tanks.get(i);
93         _model.getBody()->setMass(t->handle, t->fill);
94     }
95 }
96
97 ControlMap* Airplane::getControlMap()
98 {
99     return &_controls;
100 }
101
102 Model* Airplane::getModel()
103 {
104     return &_model;
105 }
106
107 void Airplane::getPilotAccel(float* out)
108 {
109     State* s = _model.getState();
110
111     // Gravity
112     Glue::geodUp(s->pos, out);
113     Math::mul3(-9.8f, out, out);
114
115     // The regular acceleration
116     float tmp[3];
117     Math::mul3(-1, s->acc, tmp);
118     Math::add3(tmp, out, out);
119
120     // Convert to aircraft coordinates
121     Math::vmul33(s->orient, out, out);
122
123     // FIXME: rotational & centripetal acceleration needed
124 }
125
126 void Airplane::setPilotPos(float* pos)
127 {
128     int i;
129     for(i=0; i<3; i++) _pilotPos[i] = pos[i];
130 }
131
132 void Airplane::getPilotPos(float* out)
133 {
134     int i;
135     for(i=0; i<3; i++) out[i] = _pilotPos[i];
136 }
137
138 int Airplane::numGear()
139 {
140     return _gears.size();
141 }
142
143 Gear* Airplane::getGear(int g)
144 {
145     return ((GearRec*)_gears.get(g))->gear;
146 }
147
148 void Airplane::updateGearState()
149 {
150     for(int i=0; i<_gears.size(); i++) {
151         GearRec* gr = (GearRec*)_gears.get(i);
152         float ext = gr->gear->getExtension();
153
154         gr->surf->setXDrag(ext);
155         gr->surf->setYDrag(ext);
156         gr->surf->setZDrag(ext);
157     }
158 }
159
160 void Airplane::setApproach(float speed, float altitude)
161 {
162     // The zero AoA will become a calculated stall AoA in compile()
163     setApproach(speed, altitude, 0);
164 }
165
166 void Airplane::setApproach(float speed, float altitude, float aoa)
167 {
168     _approachSpeed = speed;
169     _approachP = Atmosphere::getStdPressure(altitude);
170     _approachT = Atmosphere::getStdTemperature(altitude);
171     _approachAoA = aoa;
172 }
173  
174 void Airplane::setCruise(float speed, float altitude)
175 {
176     _cruiseSpeed = speed;
177     _cruiseP = Atmosphere::getStdPressure(altitude);
178     _cruiseT = Atmosphere::getStdTemperature(altitude);
179     _cruiseAoA = 0;
180     _tailIncidence = 0;
181 }
182
183 void Airplane::setElevatorControl(int control)
184 {
185     _approachElevator.control = control;
186     _approachElevator.val = 0;
187     _approachControls.add(&_approachElevator);
188 }
189
190 void Airplane::addApproachControl(int control, float val)
191 {
192     Control* c = new Control();
193     c->control = control;
194     c->val = val;
195     _approachControls.add(c);
196 }
197
198 void Airplane::addCruiseControl(int control, float val)
199 {
200     Control* c = new Control();
201     c->control = control;
202     c->val = val;
203     _cruiseControls.add(c);
204 }
205
206 int Airplane::numTanks()
207 {
208     return _tanks.size();
209 }
210
211 float Airplane::getFuel(int tank)
212 {
213     return ((Tank*)_tanks.get(tank))->fill;
214 }
215
216 float Airplane::getFuelDensity(int tank)
217 {
218     return ((Tank*)_tanks.get(tank))->density;
219 }
220
221 float Airplane::getTankCapacity(int tank)
222 {
223     return ((Tank*)_tanks.get(tank))->cap;
224 }
225
226 void Airplane::setWeight(float weight)
227 {
228     _emptyWeight = weight;
229 }
230
231 void Airplane::setWing(Wing* wing)
232 {
233     _wing = wing;
234 }
235
236 void Airplane::setTail(Wing* tail)
237 {
238     _tail = tail;
239 }
240
241 void Airplane::addVStab(Wing* vstab)
242 {
243     _vstabs.add(vstab);
244 }
245
246 void Airplane::addFuselage(float* front, float* back, float width,
247                            float taper, float mid)
248 {
249     Fuselage* f = new Fuselage();
250     int i;
251     for(i=0; i<3; i++) {
252         f->front[i] = front[i];
253         f->back[i]  = back[i];
254     }
255     f->width = width;
256     f->taper = taper;
257     f->mid = mid;
258     _fuselages.add(f);
259 }
260
261 int Airplane::addTank(float* pos, float cap, float density)
262 {
263     Tank* t = new Tank();
264     int i;
265     for(i=0; i<3; i++) t->pos[i] = pos[i];
266     t->cap = cap;
267     t->fill = cap;
268     t->density = density;
269     t->handle = 0xffffffff;
270     return _tanks.add(t);
271 }
272
273 void Airplane::addGear(Gear* gear)
274 {
275     GearRec* g = new GearRec();
276     g->gear = gear;
277     g->surf = 0;
278     _gears.add(g);
279 }
280
281 void Airplane::addThruster(Thruster* thruster, float mass, float* cg)
282 {
283     ThrustRec* t = new ThrustRec();
284     t->thruster = thruster;
285     t->mass = mass;
286     int i;
287     for(i=0; i<3; i++) t->cg[i] = cg[i];
288     _thrusters.add(t);
289 }
290
291 void Airplane::addBallast(float* pos, float mass)
292 {
293     _model.getBody()->addMass(mass, pos);
294     _ballast += mass;
295 }
296
297 int Airplane::addWeight(float* pos, float size)
298 {
299     WeightRec* wr = new WeightRec();
300     wr->handle = _model.getBody()->addMass(0, pos);
301
302     wr->surf = new Surface();
303     wr->surf->setPosition(pos);
304     wr->surf->setTotalDrag(size*size);
305     _model.addSurface(wr->surf);
306     _surfs.add(wr->surf);
307
308     return _weights.add(wr);
309 }
310
311 void Airplane::setWeight(int handle, float mass)
312 {
313     WeightRec* wr = (WeightRec*)_weights.get(handle);
314
315     _model.getBody()->setMass(wr->handle, mass);
316
317     // Kill the aerodynamic drag if the mass is exactly zero.  This is
318     // how we simulate droppable stores.
319     if(mass == 0) {
320         wr->surf->setXDrag(0);
321         wr->surf->setYDrag(0);
322         wr->surf->setZDrag(0);
323     } else {
324         wr->surf->setXDrag(1);
325         wr->surf->setYDrag(1);
326         wr->surf->setZDrag(1);
327     }
328 }
329
330 void Airplane::setFuelFraction(float frac)
331 {
332     int i;
333     for(i=0; i<_tanks.size(); i++) {
334         Tank* t = (Tank*)_tanks.get(i);
335         t->fill = frac * t->cap;
336         _model.getBody()->setMass(t->handle, t->cap * frac);
337     }
338 }
339
340 float Airplane::getDragCoefficient()
341 {
342     return _dragFactor;
343 }
344
345 float Airplane::getLiftRatio()
346 {
347     return _liftRatio;
348 }
349
350 float Airplane::getCruiseAoA()
351 {
352     return _cruiseAoA;
353 }
354
355 float Airplane::getTailIncidence()
356 {
357     return _tailIncidence;
358 }
359
360 char* Airplane::getFailureMsg()
361 {
362     return _failureMsg;
363 }
364
365 int Airplane::getSolutionIterations()
366 {
367     return _solutionIterations;
368 }
369
370 void Airplane::setupState(float aoa, float speed, State* s)
371 {
372     float cosAoA = Math::cos(aoa);
373     float sinAoA = Math::sin(aoa);
374     s->orient[0] =  cosAoA; s->orient[1] = 0; s->orient[2] = sinAoA;
375     s->orient[3] =       0; s->orient[4] = 1; s->orient[5] =      0;
376     s->orient[6] = -sinAoA; s->orient[7] = 0; s->orient[8] = cosAoA;
377
378     s->v[0] = speed; s->v[1] = 0; s->v[2] = 0;
379
380     int i;
381     for(i=0; i<3; i++)
382         s->pos[i] = s->rot[i] = s->acc[i] = s->racc[i] = 0;
383
384     // Put us 1m above the origin, or else the gravity computation in
385     // Model goes nuts
386     s->pos[2] = 1;
387 }
388
389 void Airplane::addContactPoint(float* pos)
390 {
391     float* cp = new float[3];
392     cp[0] = pos[0];
393     cp[1] = pos[1];
394     cp[2] = pos[2];
395     _contacts.add(cp);
396 }
397
398 float Airplane::compileWing(Wing* w)
399 {
400     // The tip of the wing is a contact point
401     float tip[3];
402     w->getTip(tip);
403     addContactPoint(tip);
404     if(w->isMirrored()) {
405         tip[1] *= -1;
406         addContactPoint(tip);
407     }
408
409     // Make sure it's initialized.  The surfaces will pop out with
410     // total drag coefficients equal to their areas, which is what we
411     // want.
412     w->compile();
413
414     float wgt = 0;
415     int i;
416     for(i=0; i<w->numSurfaces(); i++) {
417         Surface* s = (Surface*)w->getSurface(i);
418
419         float td = s->getTotalDrag();
420         s->setTotalDrag(td);
421
422         _model.addSurface(s);
423
424         float mass = w->getSurfaceWeight(i);
425         mass = mass * Math::sqrt(mass);
426         float pos[3];
427         s->getPosition(pos);
428         _model.getBody()->addMass(mass, pos);
429         wgt += mass;
430     }
431     return wgt;
432 }
433
434 float Airplane::compileFuselage(Fuselage* f)
435 {
436     // The front and back are contact points
437     addContactPoint(f->front);
438     addContactPoint(f->back);
439
440     float wgt = 0;
441     float fwd[3];
442     Math::sub3(f->front, f->back, fwd);
443     float len = Math::mag3(fwd);
444     float wid = f->width;
445     int segs = (int)Math::ceil(len/wid);
446     float segWgt = len*wid/segs;
447     int j;
448     for(j=0; j<segs; j++) {
449         float frac = (j+0.5f) / segs;
450
451         float scale = 1;
452         if(frac < f->mid)
453             scale = f->taper+(1-f->taper) * (frac / f->mid);
454         else
455             scale = f->taper+(1-f->taper) * (frac - f->mid) / (1 - f->mid);
456
457         // Where are we?
458         float pos[3];
459         Math::mul3(frac, fwd, pos);
460         Math::add3(f->back, pos, pos);
461
462         // _Mass_ weighting goes as surface area^(3/2)
463         float mass = scale*segWgt * Math::sqrt(scale*segWgt);
464         _model.getBody()->addMass(mass, pos);
465         wgt += mass;
466
467         // Make a Surface too
468         Surface* s = new Surface();
469         s->setPosition(pos);
470         float sideDrag = len/wid;
471         s->setYDrag(sideDrag);
472         s->setZDrag(sideDrag);
473         s->setTotalDrag(scale*segWgt);
474
475         // FIXME: fails for fuselages aligned along the Y axis
476         float o[9];
477         float *x=o, *y=o+3, *z=o+6; // nicknames for the axes
478         Math::unit3(fwd, x);
479         y[0] = 0; y[1] = 1; y[2] = 0;
480         Math::cross3(x, y, z);
481         Math::unit3(z, z);
482         Math::cross3(z, x, y);
483         s->setOrientation(o);
484
485         _model.addSurface(s);
486         _surfs.add(s);
487     }
488     return wgt;
489 }
490
491 // FIXME: should probably add a mass for the gear, too
492 void Airplane::compileGear(GearRec* gr)
493 {
494     Gear* g = gr->gear;
495
496     // Make a Surface object for the aerodynamic behavior
497     Surface* s = new Surface();
498     gr->surf = s;
499
500     // Put the surface at the half-way point on the gear strut, give
501     // it a drag coefficient equal to a square of the same dimension
502     // (gear are really draggy) and make it symmetric.  Assume that
503     // the "length" of the gear is 3x the compression distance
504     float pos[3], cmp[3];
505     g->getCompression(cmp);
506     float length = 3 * Math::mag3(cmp);
507     g->getPosition(pos);
508     Math::mul3(0.5, cmp, cmp);
509     Math::add3(pos, cmp, pos);
510
511     s->setPosition(pos);
512     s->setTotalDrag(length*length);
513
514     _model.addGear(g);
515     _model.addSurface(s);
516     _surfs.add(s);
517 }
518
519 void Airplane::compileContactPoints()
520 {
521     // Figure it will compress by 20cm
522     float comp[3];
523     float DIST = 0.2f;
524     comp[0] = 0; comp[1] = 0; comp[2] = DIST;
525
526     // Give it a spring constant such that at full compression it will
527     // hold up 10 times the planes mass.  That's about right.  Yeah.
528     float mass = _model.getBody()->getTotalMass();
529     float spring = (1/DIST) * 9.8f * 10.0f * mass;
530     float damp = 2 * Math::sqrt(spring * mass);
531
532     int i;
533     for(i=0; i<_contacts.size(); i++) {
534         float *cp = (float*)_contacts.get(i);
535
536         Gear* g = new Gear();
537         g->setPosition(cp);
538         
539         g->setCompression(comp);
540         g->setSpring(spring);
541         g->setDamping(damp);
542         g->setBrake(1);
543
544         // I made these up
545         g->setStaticFriction(0.6f);
546         g->setDynamicFriction(0.5f);
547
548         _model.addGear(g);
549     }
550 }
551
552 void Airplane::compile()
553 {
554     double ground[3];
555     ground[0] = 0; ground[1] = 0; ground[2] = 1;
556     _model.setGroundPlane(ground, -100000);
557
558     RigidBody* body = _model.getBody();
559     int firstMass = body->numMasses();
560
561     // Generate the point masses for the plane.  Just use unitless
562     // numbers for a first pass, then go back through and rescale to
563     // make the weight right.
564     float aeroWgt = 0;
565
566     // The Wing objects
567     aeroWgt += compileWing(_wing);
568     aeroWgt += compileWing(_tail);
569     int i;
570     for(i=0; i<_vstabs.size(); i++) {
571         aeroWgt += compileWing((Wing*)_vstabs.get(i)); 
572     }
573     
574     // The fuselage(s)
575     for(i=0; i<_fuselages.size(); i++) {
576         aeroWgt += compileFuselage((Fuselage*)_fuselages.get(i));
577     }
578
579     // Count up the absolute weight we have
580     float nonAeroWgt = _ballast;
581     for(i=0; i<_thrusters.size(); i++)
582         nonAeroWgt += ((ThrustRec*)_thrusters.get(i))->mass;
583
584     // Rescale to the specified empty weight
585     float wscale = (_emptyWeight-nonAeroWgt)/aeroWgt;
586     for(i=firstMass; i<body->numMasses(); i++)
587         body->setMass(i, body->getMass(i)*wscale);
588
589     // Add the thruster masses
590     for(i=0; i<_thrusters.size(); i++) {
591         ThrustRec* t = (ThrustRec*)_thrusters.get(i);
592         body->addMass(t->mass, t->cg);
593     }
594
595     // Add the tanks, empty for now.
596     float totalFuel = 0;
597     for(i=0; i<_tanks.size(); i++) { 
598         Tank* t = (Tank*)_tanks.get(i); 
599         t->handle = body->addMass(0, t->pos);
600         totalFuel += t->cap;
601     }
602     _cruiseWeight = _emptyWeight + totalFuel*0.5f;
603     _approachWeight = _emptyWeight + totalFuel*0.2f;
604
605     body->recalc();
606
607     // Add surfaces for the landing gear.
608     for(i=0; i<_gears.size(); i++)
609         compileGear((GearRec*)_gears.get(i));
610
611     // The Thruster objects
612     for(i=0; i<_thrusters.size(); i++) {
613         ThrustRec* tr = (ThrustRec*)_thrusters.get(i);
614         tr->handle = _model.addThruster(tr->thruster);
615     }
616
617     // Ground effect
618     float gepos[3];
619     float gespan = _wing->getGroundEffect(gepos);
620     _model.setGroundEffect(gepos, gespan, 0.15f);
621
622     solveGear();
623     solve();
624
625     // Do this after solveGear, because it creates "gear" objects that
626     // we don't want to affect.
627     compileContactPoints();
628 }
629
630 void Airplane::solveGear()
631 {
632     float cg[3], pos[3];
633     _model.getBody()->getCG(cg);
634
635     // Calculate spring constant weightings for the gear.  Weight by
636     // the inverse of the distance to the c.g. in the XY plane, which
637     // should be correct for most gear arrangements.  Add 50cm of
638     // "buffer" to keep things from blowing up with aircraft with a
639     // single gear very near the c.g. (AV-8, for example).
640     float total = 0;
641     int i;
642     for(i=0; i<_gears.size(); i++) {
643         GearRec* gr = (GearRec*)_gears.get(i);
644         Gear* g = gr->gear;
645         g->getPosition(pos);
646         Math::sub3(cg, pos, pos);
647         gr->wgt = 1.0f/(0.5f+Math::sqrt(pos[0]*pos[0] + pos[1]*pos[1]));
648         total += gr->wgt;
649     }
650
651     // Renormalize so they sum to 1
652     for(i=0; i<_gears.size(); i++)
653         ((GearRec*)_gears.get(i))->wgt /= total;
654     
655     // The force at max compression should be sufficient to stop a
656     // plane moving downwards at 2x the approach descent rate.  Assume
657     // a 3 degree approach.
658     float descentRate = 2.0f*_approachSpeed/19.1f;
659
660     // Spread the kinetic energy according to the gear weights.  This
661     // will results in an equal compression fraction (not distance) of
662     // each gear.
663     float energy = 0.5f*_approachWeight*descentRate*descentRate;
664
665     for(i=0; i<_gears.size(); i++) {
666         GearRec* gr = (GearRec*)_gears.get(i);
667         float e = energy * gr->wgt;
668         float comp[3];
669         gr->gear->getCompression(comp);
670         float len = Math::mag3(comp);
671
672         // Energy in a spring: e = 0.5 * k * len^2
673         float k = 2 * e / (len*len);
674
675         gr->gear->setSpring(k * gr->gear->getSpring());
676
677         // Critically damped (too damped, too!)
678         gr->gear->setDamping(2*Math::sqrt(k*_approachWeight*gr->wgt)
679                              * gr->gear->getDamping());
680
681         // These are pretty generic
682         gr->gear->setStaticFriction(0.8f);
683         gr->gear->setDynamicFriction(0.7f);
684     }
685 }
686
687 void Airplane::initEngines()
688 {
689     for(int i=0; i<_thrusters.size(); i++) {
690         ThrustRec* tr = (ThrustRec*)_thrusters.get(i);
691         tr->thruster->init();
692     }
693 }
694
695 void Airplane::stabilizeThrust()
696 {
697     int i;
698     for(i=0; i<_thrusters.size(); i++)
699         _model.getThruster(i)->stabilize();
700 }
701
702 void Airplane::runCruise()
703 {
704     setupState(_cruiseAoA, _cruiseSpeed, &_cruiseState);
705     _model.setState(&_cruiseState);
706     _model.setAir(_cruiseP, _cruiseT,
707                   Atmosphere::calcStdDensity(_cruiseP, _cruiseT));
708
709     // The control configuration
710     _controls.reset();
711     int i;
712     for(i=0; i<_cruiseControls.size(); i++) {
713         Control* c = (Control*)_cruiseControls.get(i);
714         _controls.setInput(c->control, c->val);
715     }
716     _controls.applyControls(1000000); // Huge dt value
717
718     // The local wind
719     float wind[3];
720     Math::mul3(-1, _cruiseState.v, wind);
721     Math::vmul33(_cruiseState.orient, wind, wind);
722  
723     // Cruise is by convention at 50% tank capacity
724     setFuelFraction(0.5);
725    
726     // Set up the thruster parameters and iterate until the thrust
727     // stabilizes.
728     for(i=0; i<_thrusters.size(); i++) {
729         Thruster* t = ((ThrustRec*)_thrusters.get(i))->thruster;
730         t->setWind(wind);
731         t->setAir(_cruiseP, _cruiseT,
732                   Atmosphere::calcStdDensity(_cruiseP, _cruiseT));
733     }
734     stabilizeThrust();
735
736     updateGearState();
737
738     // Precompute thrust in the model, and calculate aerodynamic forces
739     _model.getBody()->recalc();
740     _model.getBody()->reset();
741     _model.initIteration();
742     _model.calcForces(&_cruiseState);
743 }
744
745 void Airplane::runApproach()
746 {
747     setupState(_approachAoA, _approachSpeed, &_approachState);
748     _model.setState(&_approachState);
749     _model.setAir(_approachP, _approachT,
750                   Atmosphere::calcStdDensity(_approachP, _approachT));
751
752     // The control configuration
753     _controls.reset();
754     int i;
755     for(i=0; i<_approachControls.size(); i++) {
756         Control* c = (Control*)_approachControls.get(i);
757         _controls.setInput(c->control, c->val);
758     }
759     _controls.applyControls(1000000);
760
761     // The local wind
762     float wind[3];
763     Math::mul3(-1, _approachState.v, wind);
764     Math::vmul33(_approachState.orient, wind, wind);
765     
766     // Approach is by convention at 20% tank capacity
767     setFuelFraction(0.2f);
768
769     // Run the thrusters until they get to a stable setting.  FIXME:
770     // this is lots of wasted work.
771     for(i=0; i<_thrusters.size(); i++) {
772         Thruster* t = ((ThrustRec*)_thrusters.get(i))->thruster;
773         t->setWind(wind);
774         t->setAir(_approachP, _approachT,
775                   Atmosphere::calcStdDensity(_approachP, _approachT));
776     }
777     stabilizeThrust();
778
779     updateGearState();
780
781     // Precompute thrust in the model, and calculate aerodynamic forces
782     _model.getBody()->recalc();
783     _model.getBody()->reset();
784     _model.initIteration();
785     _model.calcForces(&_approachState);
786 }
787
788 void Airplane::applyDragFactor(float factor)
789 {
790     float applied = Math::sqrt(factor);
791     _dragFactor *= applied;
792     _wing->setDragScale(_wing->getDragScale() * applied);
793     _tail->setDragScale(_tail->getDragScale() * applied);
794     int i;
795     for(i=0; i<_vstabs.size(); i++) {
796         Wing* w = (Wing*)_vstabs.get(i);
797         w->setDragScale(w->getDragScale() * applied);
798     }
799     for(i=0; i<_surfs.size(); i++) {
800         Surface* s = (Surface*)_surfs.get(i);
801         s->setTotalDrag(s->getTotalDrag() * applied);
802     }
803 }
804
805 void Airplane::applyLiftRatio(float factor)
806 {
807     float applied = Math::sqrt(factor);
808     _liftRatio *= applied;
809     _wing->setLiftRatio(_wing->getLiftRatio() * applied);
810     _tail->setLiftRatio(_tail->getLiftRatio() * applied);
811     int i;
812     for(i=0; i<_vstabs.size(); i++) {
813         Wing* w = (Wing*)_vstabs.get(i);
814         w->setLiftRatio(w->getLiftRatio() * applied);
815     }
816 }
817
818 float Airplane::clamp(float val, float min, float max)
819 {
820     if(val < min) return min;
821     if(val > max) return max;
822     return val;
823 }
824
825 float Airplane::normFactor(float f)
826 {
827     if(f < 0) f = -f;
828     if(f < 1) f = 1/f;
829     return f;
830 }
831
832 void Airplane::solve()
833 {
834     static const float ARCMIN = 0.0002909f;
835
836     float tmp[3];
837     _solutionIterations = 0;
838     _failureMsg = 0;
839     while(1) {
840 #if 0
841         printf("%d %f %f %f %f %f\n", //DEBUG
842                _solutionIterations,
843                1000*_dragFactor,
844                _liftRatio,
845                _cruiseAoA,
846                _tailIncidence,
847                _approachElevator.val);
848 #endif
849
850         if(_solutionIterations++ > 10000) {
851             _failureMsg = "Solution failed to converge after 10000 iterations";
852             return;
853         }
854
855         // Run an iteration at cruise, and extract the needed numbers:
856         runCruise();
857
858         _model.getThrust(tmp);
859         float thrust = tmp[0];
860
861         _model.getBody()->getAccel(tmp);
862         Math::tmul33(_cruiseState.orient, tmp, tmp);
863         float xforce = _cruiseWeight * tmp[0];
864         float clift0 = _cruiseWeight * tmp[2];
865
866         _model.getBody()->getAngularAccel(tmp);
867         Math::tmul33(_cruiseState.orient, tmp, tmp);
868         float pitch0 = tmp[1];
869
870         // Run an approach iteration, and do likewise
871         runApproach();
872
873         _model.getBody()->getAngularAccel(tmp);
874         Math::tmul33(_approachState.orient, tmp, tmp);
875         double apitch0 = tmp[1];
876
877         _model.getBody()->getAccel(tmp);
878         Math::tmul33(_approachState.orient, tmp, tmp);
879         float alift = _approachWeight * tmp[2];
880
881         // Modify the cruise AoA a bit to get a derivative
882         _cruiseAoA += ARCMIN;
883         runCruise();
884         _cruiseAoA -= ARCMIN;
885
886         _model.getBody()->getAccel(tmp);
887         Math::tmul33(_cruiseState.orient, tmp, tmp);
888         float clift1 = _cruiseWeight * tmp[2];
889
890         // Do the same with the tail incidence
891         _tail->setIncidence(_tailIncidence + ARCMIN);
892         runCruise();
893         _tail->setIncidence(_tailIncidence);
894
895         _model.getBody()->getAngularAccel(tmp);
896         Math::tmul33(_cruiseState.orient, tmp, tmp);
897         float pitch1 = tmp[1];
898
899         // Now calculate:
900         float awgt = 9.8f * _approachWeight;
901
902         float dragFactor = thrust / (thrust-xforce);
903         float liftFactor = awgt / (awgt+alift);
904         float aoaDelta = -clift0 * (ARCMIN/(clift1-clift0));
905         float tailDelta = -pitch0 * (ARCMIN/(pitch1-pitch0));
906
907         // Sanity:
908         if(dragFactor <= 0 || liftFactor <= 0)
909             break;
910
911         // And the elevator control in the approach.  This works just
912         // like the tail incidence computation (it's solving for the
913         // same thing -- pitching moment -- by diddling a different
914         // variable).
915         const float ELEVDIDDLE = 0.001f;
916         _approachElevator.val += ELEVDIDDLE;
917         runApproach();
918         _approachElevator.val -= ELEVDIDDLE;
919
920         _model.getBody()->getAngularAccel(tmp);
921         Math::tmul33(_approachState.orient, tmp, tmp);
922         double apitch1 = tmp[1];
923         float elevDelta = -apitch0 * (ELEVDIDDLE/(apitch1-apitch0));
924
925         // Now apply the values we just computed.  Note that the
926         // "minor" variables are deferred until we get the lift/drag
927         // numbers in the right ballpark.
928
929         applyDragFactor(dragFactor);
930         applyLiftRatio(liftFactor);
931
932         // DON'T do the following until the above are sane
933         if(normFactor(dragFactor) > 1.0001
934            || normFactor(liftFactor) > 1.0001)
935         {
936             continue;
937         }
938
939         // OK, now we can adjust the minor variables:
940         _cruiseAoA += 0.5f*aoaDelta;
941         _tailIncidence += 0.5f*tailDelta;
942         
943         _cruiseAoA = clamp(_cruiseAoA, -0.175f, 0.175f);
944         _tailIncidence = clamp(_tailIncidence, -0.175f, 0.175f);
945
946         if(abs(xforce/_cruiseWeight) < 0.0001 &&
947            abs(alift/_approachWeight) < 0.0001 &&
948            abs(aoaDelta) < .000017 &&
949            abs(tailDelta) < .000017)
950         {
951             // If this finaly value is OK, then we're all done
952             if(abs(elevDelta) < 0.0001)
953                 break;
954
955             // Otherwise, adjust and do the next iteration
956             _approachElevator.val += 0.8 * elevDelta;
957             if(abs(_approachElevator.val) > 1) {
958                 _failureMsg = "Insufficient elevator to trim for approach";
959                 break;
960             }
961         }
962     }
963
964     if(_dragFactor < 1e-06 || _dragFactor > 1e6) {
965         _failureMsg = "Drag factor beyond reasonable bounds.";
966         return;
967     } else if(_liftRatio < 1e-04 || _liftRatio > 1e4) {
968         _failureMsg = "Lift ratio beyond reasonable bounds.";
969         return;
970     } else if(Math::abs(_cruiseAoA) >= .17453293) {
971         _failureMsg = "Cruise AoA > 10 degrees";
972         return;
973     } else if(Math::abs(_tailIncidence) >= .17453293) {
974         _failureMsg = "Tail incidence > 10 degrees";
975         return;
976     }
977 }
978 }; // namespace yasim