FlightGear next
Surface.cpp
Go to the documentation of this file.
1#include <Main/fg_props.hxx>
2#include "yasim-common.hpp"
3#include "Math.hpp"
4#include "Surface.hpp"
5
6namespace yasim {
7int Surface::s_idGenerator = 0;
8
9Surface::Surface(Version* version, const float* pos, float c0 = 1 ) :
10 _version(version),
11 _c0(c0)
12{
13 _id = s_idGenerator++;
14
15 _orient[0] = 1; _orient[1] = 0; _orient[2] = 0;
16 _orient[3] = 0; _orient[4] = 1; _orient[5] = 0;
17 _orient[6] = 0; _orient[7] = 0; _orient[8] = 1;
18
19 Math::set3(pos, _pos);
20
21 _surfN = fgGetNode("/fdm/yasim/debug/surfaces", true);
22 if (_surfN != 0) {
23 _surfN = _surfN->getChild("surface", _id, true);
24 _fxN = _surfN->getNode("f-x", true);
25 _fyN = _surfN->getNode("f-y", true);
26 _fzN = _surfN->getNode("f-z", true);
27 _fabsN = _surfN->getNode("f-abs", true);
28 _alphaN = _surfN->getNode("alpha", true);
29 _stallAlphaN = _surfN->getNode("stall-alpha", true);
30 _flapN = _surfN->getNode("flap-pos", true);
31 _slatN = _surfN->getNode("slat-pos", true);
32 _spoilerN = _surfN->getNode("spoiler-pos", true);
33 _incidenceN = _surfN->getNode("incidence-deg", true);
34 _incidenceN->setFloatValue(0);
35 _twistN = _surfN->getNode("twist-deg", true);
36 _twistN->setFloatValue(0);
37 _pgCorrectionN = _surfN->getNode("pg-correction", true);
38 _pgCorrectionN->setFloatValue(1);
39 _dcdwaveN = _surfN->getNode("wavedrag", true);
40 _dcdwaveN->setFloatValue(1);
41 _surfN->getNode("pos-x", true)->setFloatValue(pos[0]);
42 _surfN->getNode("pos-y", true)->setFloatValue(pos[1]);
43 _surfN->getNode("pos-z", true)->setFloatValue(pos[2]);
44 _surfN->getNode("chord",true)->setFloatValue(0);
45 _surfN->getNode("axis-x", true)->setFloatValue(0);
46 _surfN->getNode("axis-y", true)->setFloatValue(0);
47 _surfN->getNode("axis-z", true)->setFloatValue(0);
48 }
49}
50
51
52void Surface::setPosition(const float* pos)
53{
54 Math::set3(pos, _pos);
55 if (_surfN != 0) {
56 _surfN->getNode("pos-x", true)->setFloatValue(pos[0]);
57 _surfN->getNode("pos-y", true)->setFloatValue(pos[1]);
58 _surfN->getNode("pos-z", true)->setFloatValue(pos[2]);
59 }
60}
61
62void Surface::setChord(float chord)
63{
64 _chord = chord;
65 if (_surfN != 0) {
66 _surfN->getNode("chord",true)->setFloatValue(_chord);
67 }
68}
69
70void Surface::setOrientation(const float* o)
71{
72 for(int i=0; i<9; i++) _orient[i] = o[i];
73 if (_surfN) {
74 // export the chord line (transformed into aircraft coordiantes)
75 float xaxis[3] {1,0,0};
76 Math::tmul33(_orient,xaxis, xaxis);
77 _surfN->getNode("axis-x", true)->setFloatValue(xaxis[0]);
78 _surfN->getNode("axis-y", true)->setFloatValue(xaxis[1]);
79 _surfN->getNode("axis-z", true)->setFloatValue(xaxis[2]);
80 }
81}
82
83
84void Surface::setSlatParams(float stallDelta, float dragPenalty)
85{
86 _slatAlpha = stallDelta;
87 _slatDrag = dragPenalty;
88}
89
90void Surface::setFlapParams(float liftAdd, float dragPenalty)
91{
92 _flapLift = liftAdd;
93 _flapDrag = dragPenalty;
94}
95
96void Surface::setSpoilerParams(float liftPenalty, float dragPenalty)
97{
98 _spoilerLift = liftPenalty;
99 _spoilerDrag = dragPenalty;
100}
101
102void Surface::setFlapPos(float pos)
103{
104 if (_flapPos != pos) {
105 _flapPos = pos;
106 if (_surfN != 0) {
107 _flapN->setFloatValue(pos);
108 }
109 }
110}
111
112void Surface::setSlatPos(float pos)
113{
114 if (_slatPos != pos) {
115 _slatPos = pos;
116 if (_surfN != 0) {
117 _slatN->setFloatValue(pos);
118 }
119 }
120}
121
122void Surface::setSpoilerPos(float pos)
123{
124 if (_spoilerPos != pos) {
125 _spoilerPos = pos;
126 if (_surfN != 0) _spoilerN->setFloatValue(pos);
127 }
128}
129
130
131// Calculate the aerodynamic force given a wind vector v (in the
132// aircraft's "local" coordinates), an air density rho and the freestream
133// mach number (for compressibility correction). Returns a torque about
134// the Y axis ("pitch"), too.
135void Surface::calcForce(const float* v, const float rho, float mach, float* out, float* torque)
136{
137 // initialize outputs to zero
138 Math::zero3(out);
139 Math::zero3(torque);
140
141 // Split v into magnitude and direction:
142 float vel = Math::mag3(v);
143
144 // Zero velocity means zero force by definition (also prevents div0).
145 if(vel == 0) {
146 return;
147 }
148
149 // special case this so the logic below doesn't produce a non-zero
150 // force; should probably have a "no force" flag instead...
151 if(_cx == 0. && _cy == 0. && _cz == 0.) {
152 return;
153 }
154
155 // Normalize wind and convert to the surface's coordinates
156 Math::mul3(1/vel, v, out);
157 Math::vmul33(_orient, out, out);
158
159 // "Rotate" by the incidence angle. Assume small angles, so we
160 // need to diddle only the Z component, X is relatively unchanged
161 // by small rotations. sin(a) ~ a, cos(a) ~ 1 for small a
162 float incidence = _incidence + _twist;
163 out[2] += incidence * out[0]; // z' = z + incidence * x
164
165 // Hold onto the local wind vector so we can multiply the induced
166 // drag at the end.
167 float lwind[3];
168 Math::set3(out, lwind);
169
170 // Diddle the Z force according to our configuration
171 float stallMul = stallFunc(out);
172 stallMul *= 1 + _spoilerPos * (_spoilerLift - 1);
173 float stallLift = (stallMul - 1) * _cz * out[2];
174 float flaplift = flapLift(out[2]);
175
176 out[2] *= _cz; // scaling factor
177 out[2] += _cz*_cz0; // zero-alpha lift
178 out[2] += stallLift;
179 out[2] += flaplift;
180
181 //compute Prandtl/Glauert compressibility factor
182 float pg_correction {1};
183 float wavedrag {0};
184 if (_flow == FLOW_TRANSONIC) {
185 if (mach < 0.8f) {
186 pg_correction = 1.0f/sqrt(1.0f-(mach*mach));
187 }
188 if ((mach >= 0.8f) && (mach < 1.2f)) {
189 pg_correction = Math::polynomial(pg_coefficients, mach);
190 }
191 if (mach >= 1.2f) {
192 pg_correction = 2.0f/(((mach*mach)-1.0f)*YASIM_PI);
193 }
194 out[2] *= pg_correction;
195
196 // Add mach dependent wave drag (Perkins and Hage)
197 if (mach > _Mcrit) {
198 wavedrag = 9.5f * Math::pow((mach > 1.0f ? 1.0f : mach)-_Mcrit, 2.8f) + 0.00193f;
199 out[0] += wavedrag;
200 }
201 }
202
203
204 // Airfoil lift (pre-stall and zero-alpha) torques "up" (negative
205 // torque) around the Y axis, while flap lift pushes down. Both
206 // forces are considered to act at one third chord from the
207 // edge. Convert to local (i.e. airplane) coordiantes and store
208 // into "torque".
209 torque[0] = 0;
210 torque[1] = 0.1667f * _chord * (flaplift - (_cz*_cz0 + stallLift));
211 torque[2] = 0;
212 Math::tmul33(_orient, torque, torque);
213
214 // The X (drag) force gets diddled for control deflection
215 out[0] = controlDrag(out[2], _cx * out[0]);
216
217 // Add in any specific Y (side force) coefficient.
218 out[1] *= _cy;
219
220 // Diddle the induced drag
221 Math::mul3(-1*_inducedDrag*out[2]*lwind[2], lwind, lwind);
222 Math::add3(lwind, out, out);
223
224 // Add mach dependent wave drag
225
226
227 // Reverse the incidence rotation to get back to surface
228 // coordinates. Since out[] is now the force vector and is
229 // roughly parallel with Z, the small-angle approximation
230 // must change its X component.
231 if( _version->isVersionOrNewer( YASIM_VERSION::V_32 )) {
232 out[0] += incidence * out[2];
233 } else {
234 out[2] -= incidence * out[0];
235 }
236
237 // Convert back to external coordinates
238 Math::tmul33(_orient, out, out);
239
240 // Add in the units to make a real force:
241 float scale = 0.5f*rho*vel*vel*_c0;
242 Math::mul3(scale, out, out);
243 Math::mul3(scale, torque, torque);
244 // if we have a property tree, export info
245 if (_surfN != 0) {
246 _fabsN->setFloatValue(Math::mag3(out));
247 _fxN->setFloatValue(out[0]);
248 _fyN->setFloatValue(out[1]);
249 _fzN->setFloatValue(out[2]);
250 _alphaN->setFloatValue(_alpha);
251 _stallAlphaN->setFloatValue(_stallAlpha);
252 _pgCorrectionN->setFloatValue(pg_correction);
253 _dcdwaveN->setFloatValue(wavedrag);
254 }
255}
256
257#if 0
258void Surface::test()
259{
260 static const float DEG2RAD = 0.0174532925199;
261 float v[3], force[3], torque[3];
262 float rho = Atmosphere::getStdDensity(0);
263 float spd = 30;
264
265 setFlapPos(0);
266 setSlatPos(0);
267 setSpoilerPos(0);
268
269 for(float angle = -90; angle<90; angle += 0.01) {
270 float rad = angle * DEG2RAD;
271 v[0] = spd * -Math::cos(rad);
272 v[1] = 0;
273 v[2] = spd * Math::sin(rad);
274 calcForce(v, rho, force, torque);
275 float lift = force[2] * Math::cos(rad) + force[0] * Math::sin(rad);
276 //__builtin_printf("%f %f\n", angle, lift);
277 __builtin_printf("%f %f\n", angle, torque[2]);
278 }
279}
280#endif
281
282// Returns a multiplier for the "plain" force equations that
283// approximates an airfoil's lift/stall curve.
284float Surface::stallFunc(float* v)
285{
286 // Sanity check to treat FPU psychopathology
287 if(v[0] == 0) return 1;
288
289 _alpha = Math::abs(v[2]/v[0]);
290
291 // Wacky use of indexing, see setStall*() methods.
292 int fwdBak = v[0] > 0; // set if this is "backward motion"
293 int posNeg = v[2] < 0; // set if the airflow is toward -z
294 int i = (fwdBak<<1) | posNeg;
295
296 _stallAlpha = _stalls[i];
297 if(_stallAlpha == 0)
298 return 1;
299
300 // consider slat position, moves the stall aoa some degrees
301 if(i == 0) {
302 if( _version->isVersionOrNewer( YASIM_VERSION::V_32 )) {
303 _stallAlpha += _slatPos * _slatAlpha;
304 } else {
305 _stallAlpha += _slatAlpha;
306 }
307 }
308
309 // Beyond the stall
310 if(_alpha > _stallAlpha+_widths[i])
311 return 1;
312
313 // (note mask: we want to use the "positive" stall angle here)
314 float scale = 0.5f*_peaks[fwdBak]/_stalls[i&2];
315
316 // Before the stall
317 if(_alpha <= _stallAlpha)
318 return scale;
319
320 // Inside the stall. Compute a cubic interpolation between the
321 // pre-stall "scale" value and the post-stall unity.
322 float frac = (_alpha - _stallAlpha) / _widths[i];
323 frac = frac*frac*(3-2*frac);
324
325 return scale*(1-frac) + frac;
326}
327
328// Similar to the above -- interpolates out the flap lift past the
329// stall alpha
330float Surface::flapLift(float alpha)
331{
332 float flapLift = _cz * _flapPos * (_flapLift-1) * _flapEffectiveness;
333
334 if(_stalls[0] == 0)
335 return 0;
336
337 if(alpha < 0) alpha = -alpha;
338 if(alpha < _stalls[0])
339 return flapLift;
340 else if(alpha > _stalls[0] + _widths[0])
341 return 0;
342
343 float frac = (alpha - _stalls[0]) / _widths[0];
344 frac = frac*frac*(3-2*frac);
345 return flapLift * (1-frac);
346}
347
348float Surface::controlDrag(float lift, float drag)
349{
350 // Negative flap deflections don't affect drag until their lift
351 // multiplier exceeds the "camber" (cz0) of the surface. Use a
352 // synthesized "fp" number instead of the actual flap position.
353 float fp = _flapPos;
354 if(fp < 0) {
355 fp = -fp;
356 fp -= _cz0/(_flapLift-1);
357 if(fp < 0) fp = 0;
358 }
359
360 // Calculate an "effective" drag -- this is the drag that would
361 // have been produced by an unflapped surface at the same lift.
362 float flapDragAoA = (_flapLift - 1 - _cz0) * _stalls[0];
363 float fd = Math::abs(lift * flapDragAoA * fp);
364 if(drag < 0) fd = -fd;
365 drag += fd;
366
367 // Now multiply by the various control factors
368 drag *= 1 + fp * (_flapDrag - 1);
369 drag *= 1 + _spoilerPos * (_spoilerDrag - 1);
370 drag *= 1 + _slatPos * (_slatDrag - 1);
371
372 return drag;
373}
374
375
376void Surface::setIncidence(float angle) {
377 _incidence = angle * -1;
378 if (_surfN != 0) {
379 _incidenceN->setFloatValue(angle * RAD2DEG);
380 }
381}
382
383
384void Surface::setTwist(float angle) {
385 _twist = angle * -1;
386 if (_surfN != 0) {
387 _twistN->setFloatValue(angle * RAD2DEG);
388 }
389}
390
391}; // namespace yasim
static double scale(int center, int deadband, int min, int max, int value)
#define i(x)
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27