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