FlightGear next
Airplane.cpp
Go to the documentation of this file.
1#ifdef HAVE_CONFIG_H
2# include "config.h"
3#endif
4
5#include "Atmosphere.hpp"
6#include "ControlMap.hpp"
7#include "Gear.hpp"
8#include "Math.hpp"
9#include "Glue.hpp"
10#include "RigidBody.hpp"
11#include "Surface.hpp"
12#include "Rotorpart.hpp"
13#include "Thruster.hpp"
14#include "Hitch.hpp"
15#include "Airplane.hpp"
16#include "yasim-common.hpp"
17
18namespace yasim {
19
20// gadgets
21inline float abs(float f) { return f<0 ? -f : f; }
22
23Airplane::Airplane() : _model(this)
24{
25}
26
27Airplane::~Airplane()
28{
29 int i;
30 for(i=0; i<_fuselages.size(); i++)
31 delete (Fuselage*)_fuselages.get(i);
32 for(i=0; i<_tanks.size(); i++)
33 delete (Tank*)_tanks.get(i);
34 for(i=0; i<_thrusters.size(); i++)
35 delete (ThrustRec*)_thrusters.get(i);
36 for(i=0; i<_gears.size(); i++) {
37 GearRec* g = (GearRec*)_gears.get(i);
38 delete g->gear;
39 delete g;
40 }
41 for(i=0; i<_surfs.size(); i++)
42 delete (Surface*)_surfs.get(i);
43 for(i=0; i<_contacts.size(); i++) {
44 ContactRec* c = (ContactRec*)_contacts.get(i);
45 delete c->gear;
46 delete c;
47 }
48 for(i=0; i<_solveWeights.size(); i++)
49 delete (SolveWeight*)_solveWeights.get(i);
50 for(i=0; i<_config[CRUISE].controls.size(); i++) {
51 ControlSetting* c = (ControlSetting*)_config[CRUISE].controls.get(i);
52 delete c;
53 }
54 for(i=0; i<_config[APPROACH].controls.size(); i++) {
55 ControlSetting* c = (ControlSetting*)_config[APPROACH].controls.get(i);
56 delete c;
57 }
58 delete _wing;
59 delete _tail;
60 for(i=0; i<_vstabs.size(); i++)
61 delete (Wing*)_vstabs.get(i);
62 for(i=0; i<_weights.size(); i++)
63 delete (WeightRec*)_weights.get(i);
64}
65
66void Airplane::iterate(float dt)
67{
68 // The gear might have moved. Change their aerodynamics.
69 updateGearState();
70 _model.iterate();
71}
72
73void Airplane::calcFuelWeights()
74{
75 for(int i=0; i<_tanks.size(); i++) {
76 Tank* t = (Tank*)_tanks.get(i);
77 _model.getBody()->setMass(t->handle, t->fill);
78 }
79}
80
81void Airplane::getPilotAccel(float* out)
82{
83 State* s = _model.getState();
84
85 // Gravity
86 Glue::geodUp(s->pos, out);
87 Math::mul3(-9.8f, out, out);
88 Math::vmul33(s->orient, out, out);
89 out[0] = -out[0];
90
91 float acceleration[3];
92 // Convert to aircraft coordinates
93 Math::vmul33(s->orient, s->acc, acceleration);
94 acceleration[1] = -acceleration[1];
95 acceleration[2] = -acceleration[2];
96
97 Math::add3(acceleration, out, out);
98
99 // FIXME: rotational & centripetal acceleration needed
100}
101
102Wing* Airplane::getWing()
103{
104 if (_wing == nullptr) {
105 _wing = new Wing((Version*)this, true);
106 }
107 return _wing;
108}
109
110Wing* Airplane::getTail()
111{
112 if (_tail == nullptr) {
113 _tail = new Wing((Version*)this, true);
114 }
115 return _tail;
116}
117
118void Airplane::updateGearState()
119{
120 for(int i=0; i<_gears.size(); i++) {
121 GearRec* gr = (GearRec*)_gears.get(i);
122 float ext = gr->gear->getExtension();
123
124 gr->surf->setDragCoefficient(ext);
125 gr->surf->setYDrag(ext);
126 gr->surf->setLiftCoefficient(ext);
127 }
128}
129
131void Airplane::setConfig(Configuration cfg, float speed, float altitude, float fuel, float gla, float aoa)
132{
133 _config[cfg].id = cfg;
134 _config[cfg].speed = speed;
135 _config[cfg].altitude = altitude;
136 // solver assumes fixed (given) AoA for approach, so setup once;
137 // solver will change this for cruise config
138 _config[cfg].state.setupOrientationFromAoa(aoa);
139 _config[cfg].aoa = aoa; // not strictly needed, see runConfig()
140 _config[cfg].fuel = fuel;
141 _config[cfg].glideAngle = gla;
142}
143
145void Airplane::setElevatorControl(const char* propName)
146{
147 _approachElevator = _addControlSetting(APPROACH, propName, 0);
148}
149
151void Airplane::setHstabTrimControl(const char* propName)
152{
153 // there must be only one value for tail incidence but we need it in each
154 // config, so we have a 2nd variable
155 _tailIncidence = _addControlSetting(APPROACH, propName, 0);
156 _tailIncidenceCopy = _addControlSetting(CRUISE, propName, 0);
157}
158
159void Airplane::addControlSetting(Configuration cfg, const char* prop, float val)
160{
161 _addControlSetting(cfg, prop,val);
162}
163
164Airplane::ControlSetting* Airplane::_addControlSetting(Configuration cfg, const char* prop, float val)
165{
166 ControlSetting* c = new ControlSetting();
167 c->propHandle = _controlMap.getInputPropertyHandle(prop);
168 c->val = val;
169 _config[cfg].controls.add(c);
170 return c;
171}
172
173
177void Airplane::addControlInput(const char* propName, ControlMap::ControlType type, void* obj, int subobj, int opt, float src0, float src1, float dst0, float dst1)
178{
179 ControlMap::ObjectID oid = ControlMap::getObjectID(obj, subobj);
180 _controlMap.addMapping(propName, type, oid, opt, src0, src1, dst0, dst1);
181 // tail incidence is needed by solver so capture the prop name if used in XML
182 if (type == ControlMap::INCIDENCE && obj == _tail) {
183 setHstabTrimControl(propName);
184 }
185}
186
187void Airplane::addSolutionWeight(Configuration cfg, int idx, float wgt)
188{
189 SolveWeight* w = new SolveWeight();
190 w->cfg = cfg;
191 w->id = idx;
192 w->wgt = wgt;
193 _solveWeights.add(w);
194}
195
196void Airplane::addFuselage(const float* front, const float* back, float width,
197 float taper, float mid,
198 float cx, float cy, float cz, float idrag)
199{
200 Fuselage* f = new Fuselage();
201 Math::set3(front, f->front);
202 Math::set3(back, f->back);
203 f->width = width;
204 f->taper = taper;
205 f->mid = mid;
206 f->_cx=cx;
207 f->_cy=cy;
208 f->_cz=cz;
209 f->_idrag=idrag;
210 _fuselages.add(f);
211}
212
213int Airplane::addTank(const float* pos, float cap, float density)
214{
215 Tank* t = new Tank();
216 Math::set3(pos, t->pos);
217 t->cap = cap;
218 t->fill = cap;
219 t->density = density;
220 t->handle = 0xffffffff;
221 return _tanks.add(t);
222}
223
224void Airplane::addGear(Gear* gear)
225{
226 GearRec* g = new GearRec();
227 g->gear = gear;
228 g->surf = 0;
229 _gears.add(g);
230}
231
232void Airplane::addThruster(Thruster* thruster, float mass, const float* cg)
233{
234 ThrustRec* t = new ThrustRec();
235 t->thruster = thruster;
236 t->mass = mass;
237 Math::set3(cg, t->cg);
238 _thrusters.add(t);
239}
240
242void Airplane::addBallast(const float* pos, float mass)
243{
244 _model.getBody()->addMass(mass, pos, true);
245 _ballast += mass;
246}
247
249int Airplane::addWeight(const float* pos, float size)
250{
251 WeightRec* wr = new WeightRec();
252 wr->handle = _model.getBody()->addMass(0, pos);
253
254 wr->surf = new Surface(this, pos, size*size);
255 _model.addSurface(wr->surf);
256 _surfs.add(wr->surf);
257
258 return _weights.add(wr);
259}
260
262void Airplane::setWeight(int handle, float mass)
263{
264 WeightRec* wr = (WeightRec*)_weights.get(handle);
265
266 _model.getBody()->setMass(wr->handle, mass);
267
268 // Kill the aerodynamic drag if the mass is exactly zero. This is
269 // how we simulate droppable stores.
270 if(mass == 0) {
271 wr->surf->setDragCoefficient(0);
272 wr->surf->setYDrag(0);
273 wr->surf->setLiftCoefficient(0);
274 } else {
275 wr->surf->setDragCoefficient(1);
276 wr->surf->setYDrag(1);
277 wr->surf->setLiftCoefficient(1);
278 }
279}
280
281void Airplane::setFuelFraction(float frac)
282{
283 for(int i=0; i<_tanks.size(); i++) {
284 Tank* t = (Tank*)_tanks.get(i);
285 t->fill = frac * t->cap;
286 _model.getBody()->setMass(t->handle, t->cap * frac);
287 }
288}
289
296
297void Airplane::addContactPoint(const float* pos)
298{
299 ContactRec* c = new ContactRec;
300 c->gear = 0;
301 Math::set3(pos, c->p);
302 _contacts.add(c);
303}
304
305float Airplane::compileWing(Wing* w)
306{
307 // Make sure it's initialized. The surfaces will pop out with
308 // total drag coefficients equal to their areas, which is what we
309 // want.
310 w->compile();
311
312 // The tip of the wing is a contact point
313 float tip[3];
314 // need compile() before getTip()!
315 w->getTip(tip);
316 addContactPoint(tip);
317 if(w->isMirrored()) {
318 tip[1] *= -1;
319 addContactPoint(tip);
320 tip[1] *= -1; //undo mirror
321 }
322
323 float wgt = 0;
324 wgt = w->updateModel(&_model);
325 return wgt;
326}
327
328void Airplane::compileRotorgear()
329{
330 getRotorgear()->compile();
331}
332
333float Airplane::compileFuselage(Fuselage* f)
334{
335 // The front and back are contact points
336 addContactPoint(f->front);
337 addContactPoint(f->back);
338
339 float wgt = 0;
340 float fwd[3];
341 Math::sub3(f->front, f->back, fwd);
342 float len = Math::mag3(fwd);
343 if (len == 0) {
344 _failureMsg = "Zero length fuselage";
345 return 0;
346 }
347 float wid = f->width;
348 int segs = (int)Math::ceil(len/wid);
349 float segWgt = len*wid/segs;
350 int j;
351 for(j=0; j<segs; j++) {
352 float frac = (j+0.5f) / segs;
353 float scale = 1;
354 if(frac < f->mid)
355 scale = f->taper+(1-f->taper) * (frac / f->mid);
356 else {
357 if( isVersionOrNewer( YASIM_VERSION::V_32 ) ) {
358 // Correct calculation of width for fuselage taper.
359 scale = 1 - (1-f->taper) * (frac - f->mid) / (1 - f->mid);
360 } else {
361 // Original, incorrect calculation of width for fuselage taper.
362 scale = f->taper+(1-f->taper) * (frac - f->mid) / (1 - f->mid);
363 }
364 }
365
366 // Where are we?
367 float pos[3];
368 Math::mul3(frac, fwd, pos);
369 Math::add3(f->back, pos, pos);
370
371 // _Mass_ weighting goes as surface area^(3/2)
372 float mass = scale*segWgt * Math::sqrt(scale*segWgt);
373 _model.getBody()->addMass(mass, pos, true);
374 wgt += mass;
375
376
377 // The following is the original YASim value for sideDrag.
378 // Originally YASim calculated the fuselage's lateral drag
379 // coefficient as (solver drag factor) * (len/wid).
380 // However, this greatly underestimates a fuselage's lateral drag.
381 float sideDrag = len/wid;
382
383 if ( isVersionOrNewer( YASIM_VERSION::V_32 ) ) {
384 // New YASim assumes a fixed lateral drag coefficient of 0.5.
385 // This will not be multiplied by the solver drag factor, because
386 // that factor is tuned to match the drag in the direction of
387 // flight, which is completely independent of lateral drag.
388 // The value of 0.5 is only a ballpark estimate, roughly matching
389 // the side-on drag for a long cylinder at the higher Reynolds
390 // numbers typical for an aircraft's lateral drag.
391 // This fits if the fuselage is long and has a round cross section.
392 // For flat-sided fuselages, the value should be increased, up to
393 // a limit of around 2 for a long rectangular prism.
394 // For very short fuselages, in which the end effects are strong,
395 // the value should be reduced.
396 // Such adjustments can be made using the fuselage's "cy" and "cz"
397 // XML parameters: "cy" for the sides, "cz" for top and bottom.
398 sideDrag = 0.5;
399 }
400 float dragCoefficient = scale*segWgt*f->_cx;
401 if( isVersionOrNewer( YASIM_VERSION::V_32 ) ) {
402 dragCoefficient = scale*segWgt;
403 }
404
405 // Make a Surface too
406 Surface* s = new Surface(this, pos, dragCoefficient);
407 if( isVersionOrNewer( YASIM_VERSION::V_32 ) ) {
408 s->setDragCoefficient(f->_cx);
409 }
410 s->setYDrag(sideDrag*f->_cy);
411 s->setLiftCoefficient(sideDrag*f->_cz);
412 s->setInducedDrag(f->_idrag);
413
414 // FIXME: fails for fuselages aligned along the Y axis
415 float o[9];
416 float *x=o, *y=o+3, *z=o+6; // nicknames for the axes
417 Math::unit3(fwd, x);
418 y[0] = 0; y[1] = 1; y[2] = 0;
419 Math::cross3(x, y, z);
420 Math::unit3(z, z);
421 Math::cross3(z, x, y);
422 s->setOrientation(o);
423
424 _model.addSurface(s);
425 f->surfs.add(s);
426 _surfs.add(s);
427 }
428 return wgt;
429}
430
431// FIXME: should probably add a mass for the gear, too
432void Airplane::compileGear(GearRec* gr)
433{
434 Gear* g = gr->gear;
435
436
437 // Put the surface at the half-way point on the gear strut, give
438 // it a drag coefficient equal to a square of the same dimension
439 // (gear are really draggy) and make it symmetric. Assume that
440 // the "length" of the gear is 3x the compression distance
441 float pos[3], cmp[3];
442 g->getCompression(cmp);
443 float length = 3 * Math::mag3(cmp);
444 g->getPosition(pos);
445 pos[2] -= (g->getWheelRadius() + g->getTyreRadius());
446 Math::mul3(0.5, cmp, cmp);
447 Math::add3(pos, cmp, pos);
448
449 // Make a Surface object for the aerodynamic behavior
450 Surface* s = new Surface(this, pos, length*length);
451 gr->surf = s;
452
453 _model.addGear(g);
454 _model.addSurface(s);
455 _surfs.add(s);
456}
457
464
465void Airplane::compileContactPoints()
466{
467 // Figure it will compress by 20cm
468 float comp[3];
469 float DIST = 0.2f;
470 comp[0] = 0; comp[1] = 0; comp[2] = DIST;
471
472 // Give it a spring constant such that at full compression it will
473 // hold up 10 times the planes mass. That's about right. Yeah.
474 float mass = _model.getBody()->getTotalMass();
475 float spring = (1/DIST) * 9.8f * 10.0f * mass;
476 float damp = 2 * Math::sqrt(spring * mass);
477
478 for(int i=0; i<_contacts.size(); i++) {
479 ContactRec* c = (ContactRec*)_contacts.get(i);
480
481 Gear* g = new Gear();
482 c->gear = g;
483 g->setPosition(c->p);
484
485 g->setCompression(comp);
486 g->setSpring(spring);
487 g->setDamping(damp);
488 g->setBrake(1);
489
490 // I made these up
491 g->setStaticFriction(0.6f);
492 g->setDynamicFriction(0.5f);
493 g->setContactPoint(1);
494
495 _model.addGear(g);
496 }
497}
498
499void Airplane::compile(bool verbose)
500{
501 RigidBody* body = _model.getBody();
502 int firstMass = body->numMasses();
503 SGPropertyNode_ptr baseN = fgGetNode("/fdm/yasim/model/wings", true);
504
505 // Generate the point masses for the plane. Just use unitless
506 // numbers for a first pass, then go back through and rescale to
507 // make the weight right.
508 float aeroWgt = 0;
509
510 // The Wing objects
511 if (_wing)
512 {
513 if (baseN != nullptr) {
514 _wingsN = baseN->getChild("wing", 0, true);
515 _wing->setPropertyNode(_wingsN);
516 }
517 aeroWgt += compileWing(_wing);
518
519 // convert % to absolute x coordinates
520 _cgDesiredFront = _wing->getMACx() - _wing->getMACLength()*_cgDesiredMin;
521 _cgDesiredAft = _wing->getMACx() - _wing->getMACLength()*_cgDesiredMax;
522 if (baseN != 0) {
523 SGPropertyNode_ptr n = fgGetNode("/fdm/yasim/model", true);
524 n->getNode("cg-x-range-front", true)->setFloatValue(_cgDesiredFront);
525 n->getNode("cg-x-range-aft", true)->setFloatValue(_cgDesiredAft);
526 }
527 }
528 if (_tail)
529 {
530 if (baseN != nullptr) {
531 _wingsN = baseN->getChild("tail", 0, true);
532 _tail->setPropertyNode(_wingsN);
533 }
534 aeroWgt += compileWing(_tail);
535 }
536
537 for(int i=0; i<_vstabs.size(); i++)
538 {
539 Wing* vs = (Wing*)_vstabs.get(i);
540 if (baseN != 0) {
541 _wingsN = baseN->getChild("stab", i, true);
542 vs->setPropertyNode(_wingsN);
543 }
544 aeroWgt += compileWing(vs);
545 }
546
547 // The fuselage(s)
548 for(int i=0; i<_fuselages.size(); i++)
549 aeroWgt += compileFuselage((Fuselage*)_fuselages.get(i));
550
551 // Count up the absolute weight we have
552 float nonAeroWgt = _ballast;
553 for(int i=0; i<_thrusters.size(); i++)
554 nonAeroWgt += ((ThrustRec*)_thrusters.get(i))->mass;
555
556 // Rescale to the specified empty weight
557 float wscale = (_emptyWeight-nonAeroWgt)/aeroWgt;
558 for(int i=firstMass; i<body->numMasses(); i++) {
559 body->setMass(i, body->getMass(i)*wscale);
560 }
561 //if we have prop tree, give scale factor to each wing so it can export its mass to the prop tree
562 if (baseN != nullptr) {
563 if (_wing) _wing->weight2mass(wscale);
564 if (_tail) _tail->weight2mass(wscale);
565 for(int i=0; i<_vstabs.size(); i++)
566 {
567 ((Wing*)_vstabs.get(i))->weight2mass(wscale);
568 }
569 }
570 // Add the thruster masses
571 for(int i=0; i<_thrusters.size(); i++) {
572 ThrustRec* t = (ThrustRec*)_thrusters.get(i);
573 body->addMass(t->mass, t->cg, true);
574 }
575
576 // Add the tanks, empty for now.
577 float totalFuel = 0;
578 for(int i=0; i<_tanks.size(); i++) {
579 Tank* t = (Tank*)_tanks.get(i);
580 t->handle = body->addMass(0, t->pos);
581 totalFuel += t->cap;
582 }
583 _config[CRUISE].weight = _emptyWeight + totalFuel*_config[CRUISE].fuel;
584 _config[APPROACH].weight = _emptyWeight + totalFuel*_config[APPROACH].fuel;
585
586
587 body->recalc();
588
589 // Add surfaces for the landing gear.
590 for(int i=0; i<_gears.size(); i++)
591 compileGear((GearRec*)_gears.get(i));
592
593 // The Thruster objects
594 for(int i=0; i<_thrusters.size(); i++) {
595 ThrustRec* tr = (ThrustRec*)_thrusters.get(i);
596 tr->handle = _model.addThruster(tr->thruster);
597 }
598
599 if(_wing) {
600 // Ground effect
601 // If a double tapered wing is modelled with wing and mstab, wing must
602 // be outboard to get correct wingspan.
603 float pos[3];
604 float gespan = 0;
605 gespan = _wing->getSpan();
606 _wing->getBase(pos);
607 if(!isVersionOrNewer( YASIM_VERSION::V_2017_2 )) {
608 //old code
609 //float span = _length * Math::cos(_sweep) * Math::cos(_dihedral);
610 //span = 2*(span + Math::abs(_base[2]));
611 gespan -= 2*pos[1]; // cut away base (y-distance)
612 gespan += 2*Math::abs(pos[2]); // add (wrong) z-distance
613 }
614 if (baseN != 0)
615 baseN->getChild("wing", 0)->getNode("gnd-eff-span", true)->setFloatValue(gespan);
616 // where does the hard coded factor 0.15 come from?
617 _model.setGroundEffect(pos, gespan, 0.15f);
618 }
619
620 // solve function below resets failure message
621 // so check if we have any problems and abort here
622 if (_failureMsg) return;
623
624 solveGear();
625 calculateCGHardLimits();
626
627 if(_wing && _tail) solveAirplane(verbose);
628 else
629 {
630 // The rotor(s) mass:
631 compileRotorgear();
632 solveHelicopter(verbose);
633 }
634
635 // Do this after solveGear, because it creates "gear" objects that
636 // we don't want to affect.
637 compileContactPoints();
638}
639
640void Airplane::solveGear()
641{
642 float cg[3], pos[3];
643 _model.getBody()->getCG(cg);
644
645 // Calculate spring constant weightings for the gear. Weight by
646 // the inverse of the distance to the c.g. in the XY plane, which
647 // should be correct for most gear arrangements. Add 50cm of
648 // "buffer" to keep things from blowing up with aircraft with a
649 // single gear very near the c.g. (AV-8, for example).
650 float total = 0;
651 int i;
652 for(i=0; i<_gears.size(); i++) {
653 GearRec* gr = (GearRec*)_gears.get(i);
654 Gear* g = gr->gear;
655 g->getPosition(pos);
656 // Move pos to bottom of wheel if specified; in this case the exact
657 // contact point depends on aircraft orientation relative to ground,
658 // but this is probably good enough here.
659 pos[2] -= (g->getWheelRadius() + g->getTyreRadius());
660 Math::sub3(cg, pos, pos);
661 gr->wgt = 1.0f/(0.5f+Math::sqrt(pos[0]*pos[0] + pos[1]*pos[1]));
662 if (!g->getIgnoreWhileSolving())
663 total += gr->wgt;
664 }
665
666 // Renormalize so they sum to 1
667 for(i=0; i<_gears.size(); i++)
668 ((GearRec*)_gears.get(i))->wgt /= total;
669
670 // The force at max compression should be sufficient to stop a
671 // plane moving downwards at 2x the approach descent rate. Assume
672 // a 3 degree approach.
673 float descentRate = 2.0f*_config[APPROACH].speed/19.1f;
674
675 // Spread the kinetic energy according to the gear weights. This
676 // will results in an equal compression fraction (not distance) of
677 // each gear.
678 float energy = 0.5f*_config[APPROACH].weight*descentRate*descentRate;
679
680 for(i=0; i<_gears.size(); i++) {
681 GearRec* gr = (GearRec*)_gears.get(i);
682 float e = energy * gr->wgt;
683 float comp[3];
684 gr->gear->getCompression(comp);
685 float len = Math::mag3(comp)*(1+2*gr->gear->getInitialLoad());
686
687 // Energy in a spring: e = 0.5 * k * len^2
688 float k = 2 * e / (len*len);
689
690 gr->gear->setSpring(k * gr->gear->getSpring());
691
692 // Critically damped (too damped, too!)
693 gr->gear->setDamping(2*Math::sqrt(k*_config[APPROACH].weight*gr->wgt)
694 * gr->gear->getDamping());
695 }
696}
697
698void Airplane::calculateCGHardLimits()
699{
700 _cgMax = -1e6;
701 _cgMin = 1e6;
702 for (int i = 0; i < _gears.size(); i++) {
703 GearRec* gr = (GearRec*)_gears.get(i);
704 float pos[3];
705 gr->gear->getPosition(pos);
706 if (pos[0] > _cgMax) _cgMax = pos[0];
707 if (pos[0] < _cgMin) _cgMin = pos[0];
708 }
709}
710
711void Airplane::initEngines()
712{
713 for(int i=0; i<_thrusters.size(); i++) {
714 ThrustRec* tr = (ThrustRec*)_thrusters.get(i);
715 tr->thruster->init();
716 }
717}
718
719void Airplane::stabilizeThrust()
720{
721 for(int i=0; i<_thrusters.size(); i++)
722 _model.getThruster(i)->stabilize();
723}
724
726void Airplane::setupWeights(Configuration cfg)
727{
728 int i;
729 for(i=0; i<_weights.size(); i++)
730 setWeight(i, 0);
731 for(i=0; i<_solveWeights.size(); i++) {
732 SolveWeight* w = (SolveWeight*)_solveWeights.get(i);
733 if(w->cfg == cfg)
734 setWeight(w->id, w->wgt);
735 }
736}
737
739void Airplane::setControlValues(const Vector& controls)
740{
741 _controlMap.reset();
742 for(int i=0; i < controls.size(); i++) {
743 ControlSetting* c = (ControlSetting*)controls.get(i);
744 if (c->propHandle >= 0)
745 _controlMap.setInput(c->propHandle, c->val);
746 }
747 _controlMap.applyControls();
748}
749
750void Airplane::runConfig(Config &cfg)
751{
752 // aoa is consider to be given for approach so we calculate orientation
753 // for approach only once in setConfig() but everytime for cruise here.
754 if (!(cfg.id == APPROACH)) {
755 cfg.state.setupOrientationFromAoa(cfg.aoa);
756 }
757 cfg.state.setupSpeedAndPosition(cfg.speed, cfg.glideAngle);
758 _model.setState(&cfg.state);
759 _model.setStandardAtmosphere(cfg.altitude);
760 setControlValues(cfg.controls);
761
762 // The local wind
763 float wind[3];
764 Math::mul3(-1, cfg.state.v, wind);
765 cfg.state.globalToLocal(wind, wind);
766
767 setFuelFraction(cfg.fuel);
768 setupWeights(cfg.id);
769
770 // Set up the thruster parameters and iterate until the thrust
771 // stabilizes.
772 for(int i=0; i<_thrusters.size(); i++) {
773 Thruster* t = ((ThrustRec*)_thrusters.get(i))->thruster;
774 t->setWind(wind);
775 t->setStandardAtmosphere(cfg.altitude);
776 }
777
778 stabilizeThrust();
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(&cfg.state);
786}
787
789void Airplane::applyDragFactor(float factor)
790{
791 float applied = Math::pow(factor, _solverDelta);
792 _dragFactor *= applied;
793 if(_wing)
794 _wing->multiplyDragCoefficient(applied);
795 if(_tail)
796 _tail->multiplyDragCoefficient(applied);
797 int i;
798 for(i=0; i<_vstabs.size(); i++) {
799 Wing* w = (Wing*)_vstabs.get(i);
800 w->multiplyDragCoefficient(applied);
801 }
802 for(i=0; i<_fuselages.size(); i++) {
803 Fuselage* f = (Fuselage*)_fuselages.get(i);
804 for(int j=0; j<f->surfs.size(); j++) {
805 Surface* s = (Surface*)f->surfs.get(j);
806 if( isVersionOrNewer( YASIM_VERSION::V_32 ) ) {
807 // For new YASim, the solver drag factor is only applied to
808 // the X axis for Fuselage Surfaces.
809 // The solver is tuning the coefficient for longitudinal drag,
810 // along the direction of flight. A fuselage's lateral drag
811 // is completely independent and is normally much higher;
812 // it won't be affected by the streamlining done to reduce
813 // longitudinal drag. So the solver should only adjust the
814 // fuselage's longitudinal (X axis) drag coefficient.
815 s->mulDragCoefficient(applied);
816 } else {
817 // Originally YASim applied the drag factor to all axes
818 // for Fuselage Surfaces.
819 s->mulTotalForceCoefficient(applied);
820 }
821 }
822 }
823 for(i=0; i<_weights.size(); i++) {
824 WeightRec* wr = (WeightRec*)_weights.get(i);
825 wr->surf->mulTotalForceCoefficient(applied);
826 }
827 for(i=0; i<_gears.size(); i++) {
828 GearRec* gr = (GearRec*)_gears.get(i);
829 gr->surf->mulTotalForceCoefficient(applied);
830 }
831}
832
835void Airplane::applyLiftRatio(float factor)
836{
837 float applied = Math::pow(factor, _solverDelta);
838 _liftRatio *= applied;
839 if(_wing)
840 _wing->multiplyLiftRatio(applied);
841 if(_tail)
842 _tail->multiplyLiftRatio(applied);
843 int i;
844 for(i=0; i<_vstabs.size(); i++) {
845 Wing* w = (Wing*)_vstabs.get(i);
846 w->multiplyLiftRatio(applied);
847 }
848}
849
851float Airplane::normFactor(float f)
852{
853 if(f < 0) f = -f;
854 if(f < 1) f = 1/f;
855 return f;
856}
857
859float Airplane::_getPitch(Config &cfg)
860{
861 float tmp[3];
862 _model.getBody()->getAngularAccel(tmp);
863 cfg.state.localToGlobal(tmp, tmp);
864 return tmp[1];
865}
866
868float Airplane::_getLiftForce(Config &cfg)
869{
870 float tmp[3];
871 _model.getBody()->getAccel(tmp);
872 cfg.state.localToGlobal(tmp, tmp);
873 return cfg.weight * tmp[2];
874}
875
877float Airplane::_getDragForce(Config &cfg)
878{
879 float tmp[3];
880 _model.getBody()->getAccel(tmp);
881 cfg.state.localToGlobal(tmp, tmp);
882 return cfg.weight * tmp[0];
883}
884
885// This is a heuristic approach to "force" solver converge due to numeric
886// problems. To be replaced by a better solution later.
887float Airplane::_checkConvergence(float prev, float current)
888{
889 static int damping {0};
890 //different sign and almost same value -> oscilation;
891 if ((prev*current) < 0 && (abs(current + prev) < 0.01f)) {
892 if (!damping) fprintf(stderr,"YASim warning: possible convergence problem.\n");
893 damping++;
894 if (current < 1) current *= abs(current); // quadratic
895 else current = sqrt(current);
896 }
897 return current;
898}
899
900void Airplane::solveAirplane(bool verbose)
901{
902 static const float ARCMIN = 0.0002909f;
903
904 float tmp[3];
905 _solutionIterations = 0;
906 _failureMsg = 0;
907
908 if (_approachElevator == nullptr) {
909 setElevatorControl("/controls/flight/elevator-trim");
910 }
911
912 if (_tailIncidence == nullptr) {
913 // no control mapping from XML parser, so we just create "local"
914 // variables for solver instead of full mapping / property
915 _tailIncidence = new ControlSetting;
916 _tailIncidenceCopy = new ControlSetting;
917 }
918
919 if (verbose) {
920 fprintf(stdout,"i\tdAoa\tdTail\tcl0\tcp1\n");
921 }
922
923 float prevTailDelta {0};
924 while(1) {
925 if(_solutionIterations++ > _solverMaxIterations) {
926 _failureMsg = "Solution failed to converge!";
927 return;
928 }
929 // Run an iteration at cruise, and extract the needed numbers:
930 runConfig(_config[CRUISE]);
931
932 _model.getThrust(tmp);
933 float thrust = tmp[0] + _config[CRUISE].weight * Math::sin(_config[CRUISE].glideAngle) * 9.81;
934
935 float cDragForce = _getDragForce(_config[CRUISE]);
936 float clift0 = _getLiftForce(_config[CRUISE]);
937 float cpitch0 = _getPitch(_config[CRUISE]);
938
939 // Run an approach iteration, and do likewise
940 runConfig(_config[APPROACH]);
941 double apitch0 = _getPitch(_config[APPROACH]);
942 float alift = _getLiftForce(_config[APPROACH]);
943
944 // Modify the cruise AoA a bit to get a derivative
945 float savedAoa = _config[CRUISE].aoa;
946 _config[CRUISE].aoa += ARCMIN;
947 runConfig(_config[CRUISE]);
948 _config[CRUISE].aoa = savedAoa;
949
950 float clift1 = _getLiftForce(_config[CRUISE]);
951
952 // Do the same with the tail incidence
953 float savedIncidence = _tailIncidence->val;
954 // see setHstabTrimControl() for explanation
955 _tailIncidenceCopy->val = _tailIncidence->val += ARCMIN;
956 if (!_tail->setIncidence(_tailIncidence->val)) {
957 _failureMsg = "Tail incidence out of bounds.";
958 return;
959 };
960 runConfig(_config[CRUISE]);
961 _tailIncidenceCopy->val = _tailIncidence->val = savedIncidence;
962 _tail->setIncidence(_tailIncidence->val);
963
964 float cpitch1 = _getPitch(_config[CRUISE]);
965
966 // Now calculate:
967 float awgt = 9.8f * _config[APPROACH].weight;
968
969 float dragFactor = thrust / (thrust-cDragForce);
970 float liftFactor = awgt / (awgt+alift);
971
972 // Sanity:
973 if(dragFactor <= 0) {
974 _failureMsg = "dragFactor < 0 (drag > thrust)";
975 break;
976 }
977 if(liftFactor <= 0) {
978 _failureMsg = "liftFactor < 0";
979 break;
980 }
981
982 // And the elevator control in the approach. This works just
983 // like the tail incidence computation (it's solving for the
984 // same thing -- pitching moment -- by diddling a different
985 // variable).
986 const float ELEVDIDDLE = 0.001f;
987 _approachElevator->val += ELEVDIDDLE;
988 runConfig(_config[APPROACH]);
989 _approachElevator->val -= ELEVDIDDLE;
990
991 double apitch1 = _getPitch(_config[APPROACH]);
992
993 // Now apply the values we just computed. Note that the
994 // "minor" variables are deferred until we get the lift/drag
995 // numbers in the right ballpark.
996
997 applyDragFactor(dragFactor);
998 applyLiftRatio(liftFactor);
999
1000 // DON'T do the following until the above are sane
1001 if(normFactor(dragFactor) > _solverThreshold*1.0001
1002 || normFactor(liftFactor) > _solverThreshold*1.0001)
1003 {
1004 continue;
1005 }
1006
1007 // OK, now we can adjust the minor variables:
1008 float aoaDelta = -clift0 * (ARCMIN/(clift1-clift0));
1009 float tailDelta = -cpitch0 * (ARCMIN/(cpitch1-cpitch0));
1010 // following is a hack against oszilation variables,
1011 // needs more research to get a fully understood - it works
1012 if (_solverMode > 0) {
1013 tailDelta = _checkConvergence(prevTailDelta, tailDelta);
1014 prevTailDelta = tailDelta;
1015 }
1016 if (verbose) {
1017 fprintf(stdout,"%4d\t%f\t%f\t%f\t%f\n", _solutionIterations, aoaDelta, tailDelta, clift0, cpitch1);
1018 }
1019 _config[CRUISE].aoa += _solverDelta*aoaDelta;
1020 _tailIncidence->val += _solverDelta*tailDelta;
1021
1022 _config[CRUISE].aoa = Math::clamp(_config[CRUISE].aoa, -0.175f, 0.175f);
1023 _tailIncidence->val = Math::clamp(_tailIncidence->val, -0.175f, 0.175f);
1024
1025 if(abs(cDragForce/_config[CRUISE].weight) < _solverThreshold*0.0001 &&
1026 abs(alift/_config[APPROACH].weight) < _solverThreshold*0.0001 &&
1027 abs(aoaDelta) < _solverThreshold*.000017 &&
1028 abs(tailDelta) < _solverThreshold*.000017)
1029 {
1030 float elevDelta = -apitch0 * (ELEVDIDDLE/(apitch1-apitch0));
1031 if (verbose) {
1032 fprintf(stdout,"%4d dElev %f, ap0 %f,ap1 %f \n", _solutionIterations, elevDelta, apitch0, apitch1);
1033 }
1034 // If this finaly value is OK, then we're all done
1035 if(abs(elevDelta) < _solverThreshold*0.0001)
1036 break;
1037
1038 // Otherwise, adjust and do the next iteration
1039 _approachElevator->val += _solverDelta * elevDelta;
1040 if(abs(_approachElevator->val) > 1) {
1041 _failureMsg = "Insufficient elevator to trim for approach.";
1042 break;
1043 }
1044 }
1045 }
1046
1047 if(_dragFactor < 1e-06 || _dragFactor > 1e6) {
1048 _failureMsg = "Drag factor beyond reasonable bounds.";
1049 return;
1050 } else if(_liftRatio < 1e-04 || _liftRatio > 1e4) {
1051 _failureMsg = "Lift ratio beyond reasonable bounds.";
1052 return;
1053 } else if(Math::abs(_config[CRUISE].aoa) >= .17453293) {
1054 _failureMsg = "Cruise AoA > 10 degrees";
1055 return;
1056 } else if(Math::abs(_tailIncidence->val) >= .17453293) {
1057 _failureMsg = "Tail incidence > 10 degrees";
1058 return;
1059 }
1060 // if we have a property tree, export result from solver
1061 if (_wingsN != nullptr) {
1062 if (_tailIncidence->propHandle >= 0) {
1063 fgSetFloat(_controlMap.getProperty(_tailIncidence->propHandle)->name, _tailIncidence->val);
1064 }
1065 }
1066}
1067
1068void Airplane::solveHelicopter(bool verbose)
1069{
1070 _solutionIterations = 0;
1071 _failureMsg = 0;
1072 if (getRotorgear()!=0)
1073 {
1074 Rotorgear* rg = getRotorgear();
1075 applyDragFactor(Math::pow(rg->getYasimDragFactor()/1000,
1076 1/_solverDelta));
1077 applyLiftRatio(Math::pow(rg->getYasimLiftFactor(),
1078 1/_solverDelta));
1079 }
1080 else
1081 //huh, no wing and no rotor? (_rotorgear is constructed,
1082 //if a rotor is defined
1083 {
1084 applyDragFactor(Math::pow(15.7/1000, 1/_solverDelta));
1085 applyLiftRatio(Math::pow(104, 1/_solverDelta));
1086 }
1087 _config[CRUISE].state.setupState(0,0,0);
1088 _model.setState(&_config[CRUISE].state);
1089 setupWeights(APPROACH);
1090 _controlMap.reset();
1091 _model.getBody()->reset();
1092 _model.setStandardAtmosphere(_config[CRUISE].altitude);
1093}
1094
1095float Airplane::getCGMAC()
1096{
1097 if (_wing) {
1098 float cg[3];
1099 _model.getBody()->getCG(cg);
1100 return (_wing->getMACx() - cg[0]) / _wing->getMACLength();
1101 }
1102 return 0;
1103}
1104
1105float Airplane::getWingSpan() const
1106{
1107 if (_wing == nullptr) return -1;
1108 return _wing->getSpan();
1109}
1110
1111float Airplane::getWingArea() const
1112{
1113 if (_wing == nullptr) return -1;
1114 return _wing->getArea();
1115}
1116
1117float Airplane::_getWingLoad(float mass) const
1118{
1119 if (_wing == nullptr) return -1;
1120 float area = _wing->getArea();
1121 if (area == 0) return -1;
1122 else return mass / area;
1123}
1124
1126float Airplane::_getWingLever(const Wing* w) const
1127{
1128 if (w == nullptr) return -1;
1129 float cg[3];
1130 _model.getCG(cg);
1131 // aerodynamic center is at 25% of MAC
1132 float ac = w->getMACx() - 0.25f * w->getMACLength();
1133 return ac - cg[0];
1134}
1135
1137float Airplane::getMaxThrust()
1138{
1139 float wind[3] {0,0,0};
1140 float thrust[3] {0,0,0};
1141 float sum[3] {0,0,0};
1142 for(int i=0; i<_thrusters.size(); i++) {
1143 Thruster* t = ((ThrustRec*)_thrusters.get(i))->thruster;
1144 t->setWind(wind);
1145 t->setStandardAtmosphere(0);
1146 t->setThrottle(1);
1147 t->stabilize();
1148 t->getThrust(thrust);
1149 Math::add3(thrust, sum, sum);
1150 }
1151 return sum[0];
1152}
1153
1154float Airplane::getTailIncidence() const
1155{
1156 if (_tailIncidence != nullptr) {
1157 return _tailIncidence->val;
1158 }
1159 else return 0;
1160}
1161
1162float Airplane::getApproachElevator() const
1163{
1164 if (_approachElevator != nullptr) {
1165 return _approachElevator->val;
1166 }
1167 else return 0;
1168}
1169
1170}; // namespace yasim
double altitude
Definition ADA.cxx:46
static double scale(int center, int deadband, int min, int max, int value)
#define i(x)
State
Models an aircraft axis for purposes of trimming.
Definition FGTrimAxis.h:83
const double g(9.80665)
float abs(float f)
Definition Airplane.cpp:21
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
bool fgSetFloat(const char *name, float val)
Set a float value for a property.
Definition proptest.cpp:23