FlightGear next
Wing.cpp
Go to the documentation of this file.
1#include "yasim-common.hpp"
2#include "Model.hpp"
3#include "Surface.hpp"
4#include "Wing.hpp"
5
6namespace yasim {
7
8Wing::Wing(Version *ver, bool mirror) :
9 _version(ver),
10 _mirror(mirror)
11{
12}
13
14Wing::~Wing()
15{
16 WingSection* ws;
17 for (int s=0; s < _sections.size(); s++){
18 ws = (WingSection*)_sections.get(s);
19 for(int i=0; i<ws->_surfs.size(); i++) {
20 SurfRec* s = (SurfRec*)ws->_surfs.get(i);
21 delete s->surface;
22 delete s;
23 }
24 }
25}
26
27int Wing::addWingSection(float* base, float chord, float wingLength, float taper,
28 float sweep, float dihedral, float twist, float camber,
29 float idrag, float incidence)
30{
31 WingSection* ws = new WingSection;
32 if (_sections.size() == 0) {
33 // first section
34 Math::set3(base, _base);
35 ws->_rootChord = _float2chord(base, chord);
36 ws->_sectionIncidence = incidence;
37 } else {
38 WingSection* prev = (WingSection*)_sections.get(_sections.size()-1);
39 //use old wing tip instead of base argument
40 ws->_rootChord = prev->_tipChord;
41 ws->_sectionIncidence = prev->_sectionIncidence + prev->_twist;
42 }
43 ws->_length = wingLength;
44 ws->_taper = taper;
45 ws->_sweepAngleCenterLine = sweep;
46 ws->_dihedral = dihedral;
47 ws->_twist = twist;
48 ws->_camber = camber;
49 ws->_inducedDrag = idrag;
50 ws->_id = _sections.add(ws);
51 ws->calculateGeometry();
52 // first / only section
53 if (ws->_id == 0) {
54 _mac = ws->getMAC();
55 _netSpan = ws->_sectionSpan;
56 _area = ws->getArea();
57 _meanChord = ws->_meanChord;
58 _sweepLEMin = _sweepLEMax = ws->calculateSweepAngleLeadingEdge();
59 }
60 // append section: Calculate wing MAC from MACs of section and prev wing
61 else {
62 _mac = _weightedMeanChord(_mac, _area, ws->getMAC(), ws->getArea());
63 _netSpan += ws->_sectionSpan;
64 _area += ws->getArea();
65 _meanChord = _meanChord * ws->_meanChord * 0.5f;
66 float s = ws->calculateSweepAngleLeadingEdge();
67 if (_sweepLEMax < s) _sweepLEMax = s;
68 if (_sweepLEMin > s) _sweepLEMin = s;
69 }
70 _chord2float(ws->_tipChord, _tip);
71 _wingSpan = 2 * _tip[1];
72 if (_area > 0.0f) {
73 _aspectRatio = _wingSpan*_wingSpan/_area;
74 }
75 _taper = ws->_tipChord.length / ((WingSection*)_sections.get(0))->_rootChord.length;
76 return ws->_id;
77}
78
79Chord Wing::_float2chord(float* pos, float lenght)
80{
81 Chord c;
82 c.x = pos[0];
83 c.y = pos[1];
84 c.z = pos[2];
85 c.length = lenght;
86 return c;
87}
88
89void Wing::_chord2float(Chord c, float* pos)
90{
91 pos[0] = c.x;
92 pos[1] = c.y;
93 pos[2] = c.z;
94}
95
96Chord Wing::_weightedMeanChord(Chord a, float wa, Chord b, float wb)
97{
98 Chord c;
99 c.length = Math::weightedMean(a.length, wa, b.length, wb);
100 c.x = Math::weightedMean(a.x, wa, b.x, wb);
101 c.y = Math::weightedMean(a.y, wa, b.y, wb);
102 c.z = Math::weightedMean(a.z, wa, b.z, wb);
103 return c;
104}
105
106void Wing::WingSection::calculateGeometry()
107{
108 _meanChord = _rootChord.length*(_taper+1)*0.5f;
109 calculateWingCoordinateSystem();
110 calculateTipChord();
111 calculateSpan();
112 calculateMAC();
113}
114
115void Wing::WingSection::calculateWingCoordinateSystem() {
116 // prepare wing coordinate system, ignoring incidence and twist for now
117 // (tail incidence is varied by the solver)
118 // Generating a unit vector pointing out the left wing.
119 float left[3];
120 left[0] = -Math::tan(_sweepAngleCenterLine);
121 left[1] = Math::cos(_dihedral);
122 left[2] = Math::sin(_dihedral);
123 Math::unit3(left, left);
124 // The wing's Y axis will be the "left" vector. The Z axis will
125 // be perpendicular to this and the local (!) X axis, because we
126 // want motion along the local X axis to be zero AoA (i.e. in the
127 // wing's XY plane) by definition. Then the local X coordinate is
128 // just Y cross Z.
129 float *x = _orient, *y = _orient+3, *z = _orient+6;
130 x[0] = 1; x[1] = 0; x[2] = 0;
131 Math::set3(left, y);
132 Math::cross3(x, y, z);
133 Math::unit3(z, z);
134 Math::cross3(y, z, x);
135 // Derive the right side orientation matrix from this one.
136 int i;
137 for(i=0; i<9; i++) _rightOrient[i] = _orient[i];
138 // Negate all Y coordinates, this gets us a valid basis, but
139 // it's left handed! So...
140 for(i=1; i<9; i+=3) _rightOrient[i] = -_rightOrient[i];
141 // Change the direction of the Y axis to get back to a
142 // right-handed system.
143 for(i=3; i<6; i++) _rightOrient[i] = -_rightOrient[i];
144}
145
146void Wing::WingSection::calculateTipChord() {
147 float *y = _orient+3;
148 _tipChord.x = _rootChord.x + _length * y[0];
149 _tipChord.y = _rootChord.y + _length * y[1];
150 _tipChord.z = _rootChord.z + _length * y[2];
151 _tipChord.length = _rootChord.length * _taper;
152}
153
154void Wing::WingSection::calculateSpan()
155{
156 // wingspan in y-direction (not for vstab)
157 _sectionSpan = Math::abs(_rootChord.y - _tipChord.y);
158}
159
160void Wing::WingSection::calculateMAC()
161{
162 // http://www.nasascale.org/p2/wp-content/uploads/mac-calculator.htm
163 //const float commonFactor = _rootChord.length*0.5(1+2*_taper)/(3*_rootChord.length*(1+_taper));
164
165 const float commonFactor = (0.5+_taper)/(3*(1+_taper));
166 _mac.length = _rootChord.length-(2*_rootChord.length*(1-_taper)*commonFactor);
167 // y distance to root chord
168 _mac.y = Math::abs(2*(_tipChord.y-_rootChord.y))*commonFactor;
169 // MAC leading edge x = midpoint + half MAC length
170 _mac.x = -Math::tan(_sweepAngleCenterLine)*_mac.y + _mac.length/2 + _rootChord.x;
171 _mac.z = Math::tan(_dihedral)*_mac.y + _rootChord.z;
172 //add root y to get aircraft coordinates
173 _mac.y += _rootChord.y;
174}
175
176float Wing::WingSection::calculateSweepAngleLeadingEdge()
177{
178 if (_length == 0) {
179 return 0;
180 }
181 return Math::atan(
182 (sin(_sweepAngleCenterLine)+(1-_taper)*_rootChord.length/(2*_length)) /
183 cos(_sweepAngleCenterLine)
184 );
185}
186
187void Wing::WingSection::setIncidence(float incidence)
188{
189 //update surface
190 for(int i=0; i<_surfs.size(); i++)
191 ((SurfRec*)_surfs.get(i))->surface->setIncidence(incidence + _sectionIncidence);
192}
193
194void Wing::setFlapParams(int section, WingFlaps type, FlapParams fp)
195{
196 ((WingSection*)_sections.get(section))->_flapParams[type] = fp;
197}
198
199void Wing::setSectionDrag(int section, float pdrag)
200{
201 ((WingSection*)_sections.get(section))->_dragScale = pdrag;
202}
203
204void Wing::setSectionStallParams(int section, StallParams sp)
205{
206 ((WingSection*)_sections.get(section))->_stallParams = sp;
207}
208
209void Wing::setFlapPos(WingFlaps type,float lval, float rval)
210{
211 float min {-1.0f};
212 if (type == WING_SPOILER || type == WING_SLAT) {
213 min = 0.0f;
214 }
215 lval = Math::clamp(lval, min, 1.0f);
216 rval = Math::clamp(rval, min, 1.0f);
217 WingSection* ws;
218 for (int section=0; section < _sections.size(); section++)
219 {
220 ws = (WingSection*)_sections.get(section);
221 for(int i=0; i < ws->_flapSurfs[type].size(); i++) {
222 switch (type) {
223 case WING_FLAP0:
224 case WING_FLAP1:
225 ((Surface*)ws->_flapSurfs[type].get(i))->setFlapPos(lval);
226 if(_mirror) ((Surface*)ws->_flapSurfs[type].get(++i))->setFlapPos(rval);
227 break;
228 case WING_SLAT:
229 ((Surface*)ws->_flapSurfs[type].get(i))->setSlatPos(lval);
230 break;
231 case WING_SPOILER:
232 ((Surface*)ws->_flapSurfs[type].get(i))->setSpoilerPos(lval);
233 if(_mirror) ((Surface*)ws->_flapSurfs[type].get(++i))->setSpoilerPos(rval);
234 break;
235 }
236 }
237 }
238}
239
240void Wing::setFlapEffectiveness(WingFlaps f, float lval)
241{
242 lval = Math::clamp(lval, 1, 10);
243 WingSection* ws;
244 for (int section=0; section < _sections.size(); section++)
245 {
246 ws = (WingSection*)_sections.get(section);
247 for(int i=0; i<ws->_flapSurfs[f].size(); i++) {
248 ((Surface*)ws->_flapSurfs[f].get(i))->setFlapEffectiveness(lval);
249 }
250 }
251}
252
253
254void Wing::compile()
255{
256 WingSection* ws;
257 for (int section=0; section < _sections.size(); section++)
258 {
259 ws = (WingSection*)_sections.get(section);
260 // Have we already been compiled?
261 if(! ws->_surfs.empty()) return;
262
263 // Assemble the start/end coordinates of all control surfaces
264 // and the wing itself into an array, sort them,
265 // and remove duplicates. This gives us the boundaries of our
266 // segments.
267 const int NUM_BOUNDS {10};
268 float bounds[NUM_BOUNDS];
269 bounds[0] = ws->_flapParams[WING_FLAP0].start;
270 bounds[1] = ws->_flapParams[WING_FLAP0].end;
271 bounds[2] = ws->_flapParams[WING_FLAP1].start;
272 bounds[3] = ws->_flapParams[WING_FLAP1].end;
273 bounds[4] = ws->_flapParams[WING_SPOILER].start;
274 bounds[5] = ws->_flapParams[WING_SPOILER].end;
275 bounds[6] = ws->_flapParams[WING_SLAT].start;
276 bounds[7] = ws->_flapParams[WING_SLAT].end;
277 //and don't forget the root and the tip of the wing itself
278 bounds[8] = 0; bounds[9] = 1;
279
280 // Sort in increasing order
281 for(int i=0; i<NUM_BOUNDS; i++) {
282 int minIdx = i;
283 float minVal = bounds[i];
284 for(int j=i+1; j<NUM_BOUNDS; j++) {
285 if(bounds[j] < minVal) {
286 minIdx = j;
287 minVal = bounds[j];
288 }
289 }
290 float tmp = bounds[i];
291 bounds[i] = minVal; bounds[minIdx] = tmp;
292 }
293
294 // Uniqify
295 float last = bounds[0];
296 int nbounds = 1;
297 for(int i=1; i<NUM_BOUNDS; i++) {
298 if(bounds[i] != last)
299 bounds[nbounds++] = bounds[i];
300 last = bounds[i];
301 }
302
303 // Calculate a "nominal" segment length equal to an average chord,
304 // normalized to lie within 0-1 over the length of the wing.
305 float segLen = ws->_meanChord / ws->_length;
306
307 // Now go through each boundary and make segments
308 for(int i=0; i<(nbounds-1); i++) {
309 float start = bounds[i];
310 float end = bounds[i+1];
311 float mid = (start+end)/2;
312
313 bool hasFlap0=0, hasFlap1=0, hasSlat=0, hasSpoiler=0;
314 if(ws->_flapParams[WING_FLAP0].start < mid && mid < ws->_flapParams[WING_FLAP0].end)
315 hasFlap0 = 1;
316 if(ws->_flapParams[WING_FLAP1].start < mid && mid < ws->_flapParams[WING_FLAP1].end)
317 hasFlap1 = 1;
318 if(ws->_flapParams[WING_SLAT].start < mid && mid < ws->_flapParams[WING_SLAT].end)
319 hasSlat = 1;
320 if(ws->_flapParams[WING_SPOILER].start < mid && mid < ws->_flapParams[WING_SPOILER].end)
321 hasSpoiler = 1;
322
323 // FIXME: Should probably detect an error here if both flap0
324 // and flap1 are set. Right now flap1 overrides.
325
326 int nSegs = (int)Math::ceil((end-start)/segLen);
327 if (ws->_twist != 0 && nSegs < 8) // more segments if twisted
328 nSegs = 8;
329 float segWid = ws->_length * (end - start)/nSegs;
330
331 float base[3];
332 _chord2float(ws->_rootChord, base);
333 float tip[3];
334 _chord2float(ws->_tipChord, tip);
335
336 for(int j=0; j<nSegs; j++) {
337 float frac = start + (j+0.5f) * (end-start)/nSegs;
338 float pos[3];
339 _interp(base, tip, frac, pos);
340
341 float chord = ws->_rootChord.length * (1 - (1-ws->_taper)*frac);
342 float weight = chord * segWid;
343 float twist = ws->_twist * frac;
344
345 ws->newSurface(_version, pos, ws->_orient, chord,
346 hasFlap0, hasFlap1, hasSlat, hasSpoiler, weight, twist, _flow, _Mcrit);
347
348 if(_mirror) {
349 pos[1] = -pos[1];
350 ws->newSurface(_version, pos, ws->_rightOrient, chord,
351 hasFlap0, hasFlap1, hasSlat, hasSpoiler, weight, twist, _flow, _Mcrit);
352 }
353 }
354 }
355 // Last of all, re-set the incidence in case setIncidence() was
356 // called before we were compiled.
357 setIncidence(_incidence);
358 }
359 _writeInfoToProptree();
360}
361
362float Wing::getArea() const
363{
364 if (_mirror) return 2 * _area;
365 else return _area;
366};
367
368void Wing::multiplyLiftRatio(float factor)
369{
370 WingSection* ws;
371 for (int section=0; section < _sections.size(); section++)
372 {
373 ws = (WingSection*)_sections.get(section);
374 ws->multiplyLiftRatio(factor);
375 }
376}
377
378void Wing::multiplyDragCoefficient(float factor)
379{
380 WingSection* ws;
381 for (int section=0; section < _sections.size(); section++)
382 {
383 ws = (WingSection*)_sections.get(section);
384 ws->multiplyDragCoefficient(factor);
385 }
386}
387
389bool Wing::setIncidence(float incidence)
390{
391 if (incidence < _incidenceMin || incidence > _incidenceMax)
392 {
393 return false;
394 }
395 _incidence = incidence;
396 WingSection* ws;
397 for (int section=0; section < _sections.size(); section++)
398 {
399 ws = (WingSection*)_sections.get(section);
400 ws->setIncidence(incidence);
401 }
402 if (_wingN) {
403 _wingN->getNode("incidence-deg", true)->setFloatValue(_incidence * RAD2DEG);
404 }
405 return true;
406}
407
408void Wing::WingSection::setDragCoefficient(float scale)
409{
410 _dragScale = scale;
411 for(int i=0; i<_surfs.size(); i++) {
412 SurfRec* s = (SurfRec*)_surfs.get(i);
413 s->surface->setTotalForceCoefficient(scale * s->weight);
414 }
415}
416
417void Wing::WingSection::multiplyDragCoefficient(float factor)
418{
419 setDragCoefficient(_dragScale * factor);
420}
421
422void Wing::WingSection::setLiftRatio(float ratio)
423{
424 _liftRatio = ratio;
425 for(int i=0; i<_surfs.size(); i++)
426 ((SurfRec*)_surfs.get(i))->surface->setLiftCoefficient(ratio);
427}
428
429void Wing::WingSection::multiplyLiftRatio(float factor)
430{
431 setLiftRatio(_liftRatio * factor);
432}
433
434void Wing::WingSection::newSurface(Version* _version, float* pos, float* orient, float chord, bool hasFlap0, bool hasFlap1, bool hasSlat, bool hasSpoiler, float weight, float twist, FlowRegime flow, float mcrit)
435{
436 Surface* s = new Surface(_version, pos, weight);
437
438 s->setOrientation(orient);
439 s->setChord(chord);
440 s->setFlowRegime(flow);
441
442 // Camber is expressed as a fraction of stall peak, so convert.
443 s->setZeroAlphaLift(_camber*_stallParams.peak);
444
445 // The "main" (i.e. normal) stall angle
446 float stallAoA = _stallParams.aoa - _stallParams.width/4;
447 s->setStall(0, stallAoA);
448 s->setStallWidth(0, _stallParams.width);
449 s->setStallPeak(0, _stallParams.peak);
450
451 // The negative AoA stall is the same if we're using an symmetric
452 // airfoil, otherwise a "little worse".
453 if(_camber > 0) {
454 s->setStall(1, stallAoA * 0.8f);
455 s->setStallWidth(1, _stallParams.width * 0.5f);
456 } else {
457 s->setStall(1, stallAoA);
458 if( _version->isVersionOrNewer( YASIM_VERSION::V_2017_2 )) {
459 // what was presumably meant
460 s->setStallWidth(1, _stallParams.width);
461 } else {
462 // old code; presumably a copy&paste error
463 s->setStall(1, _stallParams.width);
464 }
465 }
466
467 // The "reverse" stalls are unmeasurable junk. Just use 13deg and
468 // "sharp".
469 s->setStallPeak(1, 1);
470 int i;
471 for(i=2; i<4; i++) {
472 s->setStall(i, 0.2267f);
473 s->setStallWidth(i, 0.01);
474 }
475
476 if(hasFlap0) s->setFlapParams(_flapParams[WING_FLAP0].lift, _flapParams[WING_FLAP0].drag);
477 if(hasFlap1) s->setFlapParams(_flapParams[WING_FLAP1].lift, _flapParams[WING_FLAP1].drag);
478 if(hasSlat) s->setSlatParams(_flapParams[WING_SLAT].aoa, _flapParams[WING_SLAT].drag);
479 if(hasSpoiler) s->setSpoilerParams(_flapParams[WING_SPOILER].lift, _flapParams[WING_SPOILER].drag);
480
481 if(hasFlap0) _flapSurfs[WING_FLAP0].add(s);
482 if(hasFlap1) _flapSurfs[WING_FLAP1].add(s);
483 if(hasSlat) _flapSurfs[WING_SLAT].add(s);
484 if(hasSpoiler) _flapSurfs[WING_SPOILER].add(s);
485
486 s->setInducedDrag(_inducedDrag);
487 if(flow == FLOW_TRANSONIC) {
488 s->setCriticalMachNumber(mcrit);
489 }
490
491 SurfRec *sr = new SurfRec();
492 sr->surface = s;
493 sr->weight = weight;
494 s->setTwist(twist);
495 _surfs.add(sr);
496}
497
498void Wing::_interp(const float* v1, const float* v2, const float frac, float* out)
499{
500 out[0] = v1[0] + frac*(v2[0]-v1[0]);
501 out[1] = v1[1] + frac*(v2[1]-v1[1]);
502 out[2] = v1[2] + frac*(v2[2]-v1[2]);
503}
504
505void Wing::_writeInfoToProptree()
506{
507 if (_wingN == nullptr)
508 return;
509 WingSection* ws = (WingSection*)_sections.get(0);
510 _wingN->getNode("root-chord", true)->setFloatValue(ws->_rootChord.length);
511 _wingN->getNode("tip-x", true)->setFloatValue(_tip[0]);
512 _wingN->getNode("tip-y", true)->setFloatValue(_tip[1]);
513 _wingN->getNode("tip-z", true)->setFloatValue(_tip[2]);
514 _wingN->getNode("base-x", true)->setFloatValue(_base[0]);
515 _wingN->getNode("base-y", true)->setFloatValue(_base[1]);
516 _wingN->getNode("base-z", true)->setFloatValue(_base[2]);
517 _wingN->getNode("taper", true)->setFloatValue(getTaper());
518 _wingN->getNode("wing-span", true)->setFloatValue(getSpan());
519 _wingN->getNode("wing-area", true)->setFloatValue(getArea());
520 _wingN->getNode("aspect-ratio", true)->setFloatValue(getAspectRatio());
521 _wingN->getNode("standard-mean-chord", true)->setFloatValue(getSMC());
522 _wingN->getNode("mac", true)->setFloatValue(getMACLength());
523 _wingN->getNode("mac-x", true)->setFloatValue(getMACx());
524 _wingN->getNode("mac-y", true)->setFloatValue(getMACy());
525 _wingN->getNode("mac-z", true)->setFloatValue(getMACz());
526
527 float wgt = 0;
528 float dragSum = 0;
529
530 for (int section=0; section < _sections.size(); section++) {
531 ws = (WingSection*)_sections.get(section);
532 float mass {0};
533 for (int surf=0; surf < ws->numSurfaces(); surf++) {
534 Surface* s = ws->getSurface(surf);
535 float drag = s->getTotalForceCoefficient();
536 dragSum += drag;
537
538 mass = ws->getSurfaceWeight(surf);
539 mass = mass * Math::sqrt(mass);
540 wgt += mass;
541 }
542 SGPropertyNode_ptr sectN = _wingN->getChild("section",section,1);
543 sectN->getNode("surface-count", true)->setFloatValue(ws->numSurfaces());
544 sectN->getNode("weight", true)->setFloatValue(mass);
545 sectN->getNode("base-x", true)->setFloatValue(ws->_rootChord.x);
546 sectN->getNode("base-y", true)->setFloatValue(ws->_rootChord.y);
547 sectN->getNode("base-z", true)->setFloatValue(ws->_rootChord.z);
548 sectN->getNode("chord", true)->setFloatValue(ws->_rootChord.length);
549 sectN->getNode("incidence-deg", true)->setFloatValue(ws->_sectionIncidence * RAD2DEG);
550 sectN->getNode("mac", true)->setFloatValue(ws->_mac.length);
551 sectN->getNode("mac-x", true)->setFloatValue(ws->getMACx());
552 sectN->getNode("mac-y", true)->setFloatValue(ws->getMACy());
553 sectN->getNode("mac-z", true)->setFloatValue(ws->getMACz());
554
555 }
556 _wingN->getNode("weight", true)->setFloatValue(wgt);
557 _wingN->getNode("drag", true)->setFloatValue(dragSum);
558}
559
560void Wing::weight2mass(float scale)
561{
562 if (_wingN == nullptr)
563 return;
564 float wgt = _wingN->getNode("weight", true)->getFloatValue();
565 _wingN->getNode("mass", true)->setFloatValue(wgt * scale);
566}
567
568// estimate a mass distibution and add masses to the model
569// they will be scaled to match total mass of aircraft later
570float Wing::updateModel(Model* model)
571{
572 _weight = 0;
573 WingSection* ws;
574 for (int section=0; section < _sections.size(); section++) {
575 ws = (WingSection*)_sections.get(section);
576 for(int surf=0; surf < ws->numSurfaces(); surf++) {
577 Surface* s = ws->getSurface(surf);
578 model->addSurface(s);
579
580 float weight = ws->getSurfaceWeight(surf);
581 weight = weight * Math::sqrt(weight);
582 _weight += weight;
583
584 float pos[3];
585 s->getPosition(pos);
586 int mid = model->getBody()->addMass(weight, pos, true);
587 if (_wingN != nullptr) {
588 SGPropertyNode_ptr n = _wingN->getNode("surfaces", true)->getChild("surface", s->getID(), true);
589 n->getNode("c0", true)->setFloatValue(s->getTotalForceCoefficient());
590 n->getNode("cdrag", true)->setFloatValue(s->getDragCoefficient());
591 n->getNode("clift", true)->setFloatValue(s->getLiftCoefficient());
592 n->getNode("mass-id", true)->setIntValue(mid);
593 }
594 }
595 }
596 if (_wingN != nullptr) {
597 _wingN->getNode("weight", true)->setFloatValue(_weight);
598 }
599 return _weight;
600}
601
602void Wing::printSectionInfo()
603{
604 WingSection* ws;
605 printf("#wing sections: %d\n", _sections.size());
606 for (int section=0; section < _sections.size(); section++) {
607 ws = (WingSection*)_sections.get(section);
608 printf("Section %d base point: x=\"%.3f\" y=\"%.3f\" z=\"%.3f\" chord=\"%.3f\" incidence=\"%.1f\" (at section root in deg)\n", section,
609 ws->_rootChord.x, ws->_rootChord.y, ws->_rootChord.z, ws->_rootChord.length, ws->_sectionIncidence * RAD2DEG);
610 }
611}
612
613}; // namespace yasim
static double scale(int center, int deadband, int min, int max, int value)
#define min(X, Y)
#define i(x)
#define _base