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