From 77ecc046762d87ca7ce7e1dd537c2ef8f2f981c0 Mon Sep 17 00:00:00 2001 From: andy Date: Thu, 14 Sep 2006 18:18:33 +0000 Subject: [PATCH] Helicopter update from Maik: More realistic calculation of vortices at the blades and therefore real airfoil parameters can be used now (not to be mixed up with the vortex ring state which is still not simulated), ground effect is now continuous e. g. at buildings, calculating of the rotor in more than 4 directions, better documentation of the airfoil parameters. --- src/FDM/YASim/FGFDM.cpp | 16 +- src/FDM/YASim/Math.cpp | 5 + src/FDM/YASim/Math.hpp | 2 + src/FDM/YASim/Model.cpp | 16 +- src/FDM/YASim/Rotor.cpp | 507 ++++++++++++++++++++++++++---------- src/FDM/YASim/Rotor.hpp | 32 ++- src/FDM/YASim/Rotorpart.cpp | 144 +++++++--- src/FDM/YASim/Rotorpart.hpp | 13 +- 8 files changed, 543 insertions(+), 192 deletions(-) diff --git a/src/FDM/YASim/FGFDM.cpp b/src/FDM/YASim/FGFDM.cpp index 761a86375..1d1074619 100644 --- a/src/FDM/YASim/FGFDM.cpp +++ b/src/FDM/YASim/FGFDM.cpp @@ -180,13 +180,17 @@ void FGFDM::startElement(const char* name, const XMLAttributes &atts) Rotorgear* r = _airplane.getModel()->getRotorgear(); _currObj = r; #define p(x) if (a->hasAttribute(#x)) r->setParameter((char *)#x,attrf(a,#x) ); + #define p2(x,y) if (a->hasAttribute(#x)) {r->setParameter((char *)#y,attrf(a,#x) ); SG_LOG(SG_INPUT, SG_ALERT,"Warning: Aircraft defnition files uses outdated tag '" <<#x<<"', use '"<<#y<<"' instead (see README.YASim)" <setInUse(); } else if(eq(name, "wing")) { _airplane.setWing(parseWing(a, name)); @@ -493,8 +497,10 @@ void FGFDM::setOutputProperties(float dt) char b[256]; while((j = r->getValueforFGSet(j, b, &f))) if(b[0]) fgSetFloat(b,f); - - for(j=0; j < r->numRotorparts(); j++) { + j=0; + while((j = _airplane.getRotorgear()->getValueforFGSet(j, b, &f))) + if(b[0]) fgSetFloat(b,f); + for(j=0; j < r->numRotorparts(); j+=r->numRotorparts()>>2) { Rotorpart* s = (Rotorpart*)r->getRotorpart(j); char *b; int k; @@ -692,6 +698,7 @@ Rotor* FGFDM::parseRotor(XMLAttributes* a, const char* type) p(vortex_state_e2) p(twist) p(number_of_segments) + p(number_of_parts) p(rel_len_where_incidence_is_measured) p(chord) p(taper) @@ -705,7 +712,8 @@ Rotor* FGFDM::parseRotor(XMLAttributes* a, const char* type) p(airfoil_lift_coefficient) p(airfoil_drag_coefficient0) p(airfoil_drag_coefficient1) - + p(cyclic_factor) + p(rotor_correction_factor) #undef p _currObj = w; return w; diff --git a/src/FDM/YASim/Math.cpp b/src/FDM/YASim/Math.cpp index c7203941c..2c53c3559 100644 --- a/src/FDM/YASim/Math.cpp +++ b/src/FDM/YASim/Math.cpp @@ -80,6 +80,11 @@ float Math::pow(double base, double exp) return (float)::pow(base, exp); } +float Math::exp(float f) +{ + return (float)::exp(f); +} + double Math::ceil(double f) { return ::ceil(f); diff --git a/src/FDM/YASim/Math.hpp b/src/FDM/YASim/Math.hpp index 66bd6cc80..e729f6d72 100644 --- a/src/FDM/YASim/Math.hpp +++ b/src/FDM/YASim/Math.hpp @@ -20,6 +20,8 @@ public: static float atan2(float y, float x); static float asin(float f); static float acos(float f); + static float exp(float f); + static float sqr(float f) {return f*f;} // Takes two args and runs afoul of the Koenig rules. static float pow(double base, double exp); diff --git a/src/FDM/YASim/Model.cpp b/src/FDM/YASim/Model.cpp index 2fcdd5ca0..42479f95e 100644 --- a/src/FDM/YASim/Model.cpp +++ b/src/FDM/YASim/Model.cpp @@ -312,21 +312,7 @@ void Model::updateGround(State* s) } for(i=0; i<_rotorgear.getRotors()->size(); i++) { Rotor* r = (Rotor*)_rotorgear.getRotors()->get(i); - - // Get the point of the rotor center - float pos[3]; - r->getPosition(pos); - - // Transform the local coordinates to - // global coordinates. - double pt[3]; - s->posLocalToGlobal(pos, pt); - - // Ask for the ground plane in the global coordinate system - double global_ground[4]; - float global_vel[3]; - _ground_cb->getGroundPlane(pt, global_ground, global_vel); - r->setGlobalGround(global_ground, global_vel); + r->findGroundEffectAltitude(_ground_cb,s); } // The arrester hook diff --git a/src/FDM/YASim/Rotor.cpp b/src/FDM/YASim/Rotor.cpp index 0094b2309..e4a8b2970 100644 --- a/src/FDM/YASim/Rotor.cpp +++ b/src/FDM/YASim/Rotor.cpp @@ -3,6 +3,7 @@ #include "Math.hpp" #include "Surface.hpp" #include "Rotorpart.hpp" +#include "Ground.hpp" #include "Rotor.hpp" #include STL_IOSTREAM @@ -10,7 +11,14 @@ SG_USING_STD(setprecision); +#ifdef TEST_DEBUG #include +#endif +#include +#include +#include + + namespace yasim { @@ -50,6 +58,8 @@ Rotor::Rotor() _mincyclicail=-10./180*pi; _mincyclicele=-10./180*pi; _min_pitch=-.5/180*pi; + _cyclicele=0; + _cyclicail=0; _name[0] = 0; _normal[0] = _normal[1] = 0; _normal[2] = 1; @@ -81,12 +91,14 @@ Rotor::Rotor() _vortex_state_e1=1; _vortex_state_e2=1; _vortex_state_e3=1; + _vortex_state=0; _lift_factor=1; _liftcoef=0.1; _dragcoef0=0.1; _dragcoef1=0.1; _twist=0; _number_of_segments=1; + _number_of_parts=4; _rel_len_where_incidence_is_measured=0.7; _torque_of_inertia=1; _torque=0; @@ -110,6 +122,13 @@ Rotor::Rotor() _stall_v2sum=1; _collective=0; _yaw=_roll=0; + for (int k=0;k<4;k++) + for (i=0;i<3;i++) + _groundeffectpos[k][i]=0; + _ground_effect_altitude=1; + _cyclic_factor=1; + _lift_factor=_f_ge=_f_vs=_f_tl=1; + _rotor_correction_factor=.65; } Rotor::~Rotor() @@ -130,10 +149,13 @@ void Rotor::inititeration(float dt,float omegarel,float ddt_omegarel,float *rot) _ddt_omega=_omegan*ddt_omegarel; int i; for(i=0; i<_rotorparts.size(); i++) { + float s = Math::sin(2*pi*i/_number_of_parts); + float c = Math::cos(2*pi*i/_number_of_parts); Rotorpart* r = (Rotorpart*)_rotorparts.get(i); r->setOmega(_omega); r->setDdtOmega(_ddt_omega); r->inititeration(dt,rot); + r->setCyclic(_cyclicail*c+_cyclicele*s); } //calculate the normal of the rotor disc, for calcualtion of the downwash @@ -169,10 +191,28 @@ float Rotor::calcStall(float incidence,float speed) float Rotor::getLiftCoef(float incidence,float speed) { float stall=calcStall(incidence,speed); + /* the next shold look like this, but this is the inner loop of + the rotor simulation. For small angles (and we hav only small + angles) the first order approximation works well float c1= Math::sin(incidence-_airfoil_incidence_no_lift)*_liftcoef; + for c2 we would need higher order, because in stall the angle can be large + */ + float i2; + if (incidence > (pi/2)) + i2 = incidence-pi; + else if (incidence <-(pi/2)) + i2 = (incidence+pi); + else + i2 = incidence; + float c1= (i2-_airfoil_incidence_no_lift)*_liftcoef; + if (stall > 0) + { float c2= Math::sin(2*(incidence-_airfoil_incidence_no_lift)) *_liftcoef*_lift_factor_stall; return (1-stall)*c1 + stall *c2; + } + else + return c1; } float Rotor::getDragCoef(float incidence,float speed) @@ -187,14 +227,14 @@ float Rotor::getDragCoef(float incidence,float speed) int Rotor::getValueforFGSet(int j,char *text,float *f) { if (_name[0]==0) return 0; - if (4!=numRotorparts()) return 0; //compile first! + if (4>numRotorparts()) return 0; //compile first! if (j==0) { sprintf(text,"/rotors/%s/cone", _name); *f=( ((Rotorpart*)getRotorpart(0))->getrealAlpha() - +((Rotorpart*)getRotorpart(1))->getrealAlpha() - +((Rotorpart*)getRotorpart(2))->getrealAlpha() - +((Rotorpart*)getRotorpart(3))->getrealAlpha() + +((Rotorpart*)getRotorpart(1*(_number_of_parts>>2)))->getrealAlpha() + +((Rotorpart*)getRotorpart(2*(_number_of_parts>>2)))->getrealAlpha() + +((Rotorpart*)getRotorpart(3*(_number_of_parts>>2)))->getrealAlpha() )/4*180/pi; } else @@ -202,7 +242,7 @@ int Rotor::getValueforFGSet(int j,char *text,float *f) { sprintf(text,"/rotors/%s/roll", _name); _roll = ( ((Rotorpart*)getRotorpart(0))->getrealAlpha() - -((Rotorpart*)getRotorpart(2))->getrealAlpha() + -((Rotorpart*)getRotorpart(2*(_number_of_parts>>2)))->getrealAlpha() )/2*(_ccw?-1:1); *f=_roll *180/pi; } @@ -210,8 +250,8 @@ int Rotor::getValueforFGSet(int j,char *text,float *f) if (j==2) { sprintf(text,"/rotors/%s/yaw", _name); - _yaw=( ((Rotorpart*)getRotorpart(1))->getrealAlpha() - -((Rotorpart*)getRotorpart(3))->getrealAlpha() + _yaw=( ((Rotorpart*)getRotorpart(1*(_number_of_parts>>2)))->getrealAlpha() + -((Rotorpart*)getRotorpart(3*(_number_of_parts>>2)))->getrealAlpha() )/2; *f=_yaw*180/pi; } @@ -271,18 +311,19 @@ int Rotor::getValueforFGSet(int j,char *text,float *f) float rk,rl,p; p=(*f/90); k=int(p); - l=int(p+1); + l=k+1; rk=l-p; + rk=Math::clamp(rk,0,1);//Delete this rl=1-rk; if(w==2) {k+=2;l+=2;} else if(w==1) {k+=1;l+=1;} k%=4; l%=4; - if (w==1) *f=rk*((Rotorpart*) getRotorpart(k))->getrealAlpha()*180/pi - +rl*((Rotorpart*) getRotorpart(l))->getrealAlpha()*180/pi; - else if(w==2) *f=rk*((Rotorpart*)getRotorpart(k))->getIncidence()*180/pi - +rl*((Rotorpart*)getRotorpart(l))->getIncidence()*180/pi; + if (w==1) *f=rk*((Rotorpart*) getRotorpart(k*(_number_of_parts>>2)))->getrealAlpha()*180/pi + +rl*((Rotorpart*) getRotorpart(l*(_number_of_parts>>2)))->getrealAlpha()*180/pi; + else if(w==2) *f=rk*((Rotorpart*)getRotorpart(k*(_number_of_parts>>2)))->getIncidence()*180/pi + +rl*((Rotorpart*)getRotorpart(l*(_number_of_parts>>2)))->getIncidence()*180/pi; } return j+1; } @@ -557,6 +598,7 @@ void Rotor::setParameter(char *parametername, float value) p(vortex_state_e3,1) p(twist,pi/180.) p(number_of_segments,1) + p(number_of_parts,1) p(rel_len_where_incidence_is_measured,1) p(chord,1) p(taper,1) @@ -570,8 +612,11 @@ void Rotor::setParameter(char *parametername, float value) p(airfoil_lift_coefficient,1) p(airfoil_drag_coefficient0,1) p(airfoil_drag_coefficient1,1) - cout << "internal error in parameter set up for rotor: '" - << parametername <<"'" << endl; + p(cyclic_factor,1) + p(rotor_correction_factor,1) + SG_LOG(SG_INPUT, SG_ALERT, + "internal error in parameter set up for rotor: '" << + parametername <<"'" << endl); #undef p } @@ -598,23 +643,15 @@ void Rotor::setCollective(float lval) void Rotor::setCyclicele(float lval,float rval) { - rval = Math::clamp(rval, -1, 1); lval = Math::clamp(lval, -1, 1); - float col=_mincyclicele+(lval+1)/2*(_maxcyclicele-_mincyclicele); - if (_rotorparts.size()!=4) return; - ((Rotorpart*)_rotorparts.get(1))->setCyclic(lval); - ((Rotorpart*)_rotorparts.get(3))->setCyclic(-lval); + _cyclicele=_mincyclicele+(lval+1)/2*(_maxcyclicele-_mincyclicele); } void Rotor::setCyclicail(float lval,float rval) { lval = Math::clamp(lval, -1, 1); - rval = Math::clamp(rval, -1, 1); - float col=_mincyclicail+(lval+1)/2*(_maxcyclicail-_mincyclicail); - if (_rotorparts.size()!=4) return; if (_ccw) lval *=-1; - ((Rotorpart*)_rotorparts.get(0))->setCyclic(-lval); - ((Rotorpart*)_rotorparts.get(2))->setCyclic( lval); + _cyclicail=-(_mincyclicail+(lval+1)/2*(_maxcyclicail-_mincyclicail)); } void Rotor::getPosition(float* out) @@ -634,16 +671,8 @@ void Rotor::calcLiftFactor(float* v, float rho, State *s) _f_tl=1; _f_vs=1; - // find h, the distance to the ground - // The ground plane transformed to the local frame. - float ground[4]; - s->planeGlobalToLocal(_global_ground, ground); - - float h = ground[3] - Math::dot3(_base, ground); - // Now h is the distance from the rotor center to ground - // Calculate ground effect - _f_ge=1+_diameter/h*_ground_effect_constant; + _f_ge=1+_diameter/_ground_effect_altitude*_ground_effect_constant; // Now calculate translational lift float v_vert = Math::dot3(v,_normal); @@ -656,9 +685,104 @@ void Rotor::calcLiftFactor(float* v, float rho, State *s) _lift_factor = _f_ge*_f_tl*_f_vs; } -float Rotor::getGroundEffect(float* posOut) +void Rotor::findGroundEffectAltitude(Ground * ground_cb,State *s) { - return _diameter*0; //ground effect for rotor is calcualted not here + _ground_effect_altitude=findGroundEffectAltitude(ground_cb,s, + _groundeffectpos[0],_groundeffectpos[1], + _groundeffectpos[2],_groundeffectpos[3]); +} + +float Rotor::findGroundEffectAltitude(Ground * ground_cb,State *s, + float *pos0,float *pos1,float *pos2,float *pos3, + int iteration,float a0,float a1,float a2,float a3) +{ + float a[5]; + float *p[5],pos4[3]; + a[0]=a0; + a[1]=a1; + a[2]=a2; + a[3]=a3; + a[4]=-1; + p[0]=pos0; + p[1]=pos1; + p[2]=pos2; + p[3]=pos3; + p[4]=pos4; + Math::add3(p[0],p[2],p[4]); + Math::mul3(0.5,p[4],p[4]);//the center + + float mina=100*_diameter; + float suma=0; + for (int i=0;i<5;i++) + { + if (a[i]==-1)//in the first iteration,(iteration==0) no height is + //passed to this function, these missing values are + //marked by ==-1 + { + double pt[3]; + s->posLocalToGlobal(p[i], pt); + + // Ask for the ground plane in the global coordinate system + double global_ground[4]; + float global_vel[3]; + ground_cb->getGroundPlane(pt, global_ground, global_vel); + // find h, the distance to the ground + // The ground plane transformed to the local frame. + float ground[4]; + s->planeGlobalToLocal(global_ground, ground); + + a[i] = ground[3] - Math::dot3(p[i], ground); + // Now a[i] is the distance from p[i] to ground + } + suma+=a[i]; + if (a[i]=10*_diameter) + return mina; //the ground effect will be zero + + //check if further recursion is neccessary + //if the height does not differ more than 20%, than + //we can return then mean height, if not split + //zhe square to four parts and calcualte the height + //for each part + //suma * 0.2 is the mean + //0.15 is the maximum allowed difference from the mean + //to the height at the center + if ((iteration>2) + ||(Math::abs(suma*0.2-a[4])<(0.15*0.2*suma*(1<posLocalToGlobal(pc[i], pt); + + // Ask for the ground plane in the global coordinate system + double global_ground[4]; + float global_vel[3]; + ground_cb->getGroundPlane(pt, global_ground, global_vel); + // find h, the distance to the ground + // The ground plane transformed to the local frame. + float ground[4]; + s->planeGlobalToLocal(global_ground, ground); + + ac[i] = ground[3] - Math::dot3(p[i], ground); + // Now ac[i] is the distance from pc[i] to ground + } + return 0.25* + (findGroundEffectAltitude(ground_cb,s,p[0],pc[1],p[4],pc[3], + iteration+1,a[0],ac[0],a[4],ac[3]) + +findGroundEffectAltitude(ground_cb,s,p[1],pc[0],p[4],pc[1], + iteration+1,a[1],ac[0],a[4],ac[1]) + +findGroundEffectAltitude(ground_cb,s,p[2],pc[1],p[4],pc[2], + iteration+1,a[2],ac[1],a[4],ac[2]) + +findGroundEffectAltitude(ground_cb,s,p[3],pc[2],p[4],pc[3], + iteration+1,a[3],ac[2],a[4],ac[3]) + ); } void Rotor::getDownWash(float *pos, float *v_heli, float *downwash) @@ -743,12 +867,12 @@ void Rotor::compile() // Have we already been compiled? if(_rotorparts.size() != 0) return; - //rotor is divided into 4 pointlike parts + //rotor is divided into _number_of_parts parts + //each part is calcualted at _number_of_segments points - SG_LOG(SG_FLIGHT, SG_DEBUG, "debug: e " - << _mincyclicele << "..." <<_maxcyclicele << ' ' - << _mincyclicail << "..." << _maxcyclicail << ' ' - << _min_pitch << "..." << _max_pitch); + //clamp to 4..256 + //and make it a factor of 4 + _number_of_parts=(int(Math::clamp(_number_of_parts,4,256))>>2)<<2; _dynamic=_dynamic*(1/ //inverse of the time ( (60/_rotor_rpm)/4 //for rotating 90 deg @@ -761,7 +885,6 @@ void Rotor::compile() directions[0][1]=_forward[1]; directions[0][2]=_forward[2]; int i; - SG_LOG(SG_FLIGHT, SG_DEBUG, "Rotor rotating ccw? " << _ccw); for (i=1;i<5;i++) { if (!_ccw) @@ -771,17 +894,23 @@ void Rotor::compile() Math::unit3(directions[i],directions[i]); } Math::set3(directions[4],directions[0]); - float rotorpartmass = _weight_per_blade*_number_of_blades/4*.453; + for (i=0;i<4;i++) + { + Math::mul3(_diameter*0.7,directions[i],_groundeffectpos[i]); + Math::add3(_base,_groundeffectpos[i],_groundeffectpos[i]); + } + float rotorpartmass = _weight_per_blade*_number_of_blades/_number_of_parts*.453; //was pounds -> now kg - _torque_of_inertia = 1/12. * ( 4 * rotorpartmass) * _diameter + _torque_of_inertia = 1/12. * ( _number_of_parts * rotorpartmass) * _diameter * _diameter * _rel_blade_center * _rel_blade_center /(0.5*0.5); float speed=_rotor_rpm/60*_diameter*_rel_blade_center*pi; float lentocenter=_diameter*_rel_blade_center*0.5; float lentoforceattac=_diameter*_rel_len_hinge*0.5; float zentforce=rotorpartmass*speed*speed/lentocenter; - float pitchaforce=_force_at_pitch_a/4*.453*9.81; - //was pounds of force, now N, devided by 4 (so its now per rotorpart) + float pitchaforce=_force_at_pitch_a/_number_of_parts*.453*9.81; + // was pounds of force, now N, devided by _number_of_parts + //(so its now per rotorpart) float torque0=0,torquemax=0,torqueb=0; float omega=_rotor_rpm/60*2*pi; @@ -790,14 +919,14 @@ void Rotor::compile() _delta*=pitchaforce/(_pitch_a*omega*lentocenter*2*rotorpartmass); float phi=Math::atan2(2*omega*_delta,omega0*omega0-omega*omega); - float relamp=omega*omega/(2*_delta*Math::sqrt(sqr(omega0*omega0-omega*omega) - +4*_delta*_delta*omega*omega)); + float relamp=(omega*omega/(2*_delta*Math::sqrt(sqr(omega0*omega0-omega*omega) + +4*_delta*_delta*omega*omega)))*_cyclic_factor; if (!_no_torque) { - torque0=_power_at_pitch_0/4*1000/omega; + torque0=_power_at_pitch_0/_number_of_parts*1000/omega; // f*r=p/w ; p=f*s/t; r=s/t/w ; r*w*t = s - torqueb=_power_at_pitch_b/4*1000/omega; - torquemax=_power_at_pitch_b/4*1000/omega/_pitch_b*_max_pitch; + torqueb=_power_at_pitch_b/_number_of_parts*1000/omega; + torquemax=_power_at_pitch_b/_number_of_parts*1000/omega/_pitch_b*_max_pitch; if(_ccw) { @@ -807,36 +936,30 @@ void Rotor::compile() } } - SG_LOG(SG_FLIGHT, SG_DEBUG, - "spd: " << setprecision(8) << speed - << " lentoc: " << lentocenter - << " dia: " << _diameter - << " rbl: " << _rel_blade_center - << " hing: " << _rel_len_hinge - << " lfa: " << lentoforceattac); - - SG_LOG(SG_FLIGHT, SG_DEBUG, - "tq: " << setprecision(8) << torque0 << ".." << torquemax - << " d3: " << _delta3); - SG_LOG(SG_FLIGHT, SG_DEBUG, - "o/o0: " << setprecision(8) << omega/omega0 - << " phi: " << phi*180/pi - << " relamp: " << relamp - << " delta: " <<_delta); - - Rotorpart* rps[4]; - for (i=0;i<4;i++) + Rotorpart* rps[256]; + for (i=0;i<_number_of_parts;i++) { float lpos[3],lforceattac[3],lspeed[3],dirzentforce[3]; + float s = Math::sin(2*pi*i/_number_of_parts); + float c = Math::cos(2*pi*i/_number_of_parts); + float direction[3],nextdirection[3],help[3]; + Math::mul3(c ,directions[0],help); + Math::mul3(s ,directions[1],direction); + Math::add3(help,direction,direction); - Math::mul3(lentocenter,directions[i],lpos); + Math::mul3(c ,directions[1],help); + Math::mul3(s ,directions[2],nextdirection); + Math::add3(help,nextdirection,nextdirection); + + Math::mul3(lentocenter,direction,lpos); Math::add3(lpos,_base,lpos); - Math::mul3(lentoforceattac,directions[i+1],lforceattac); - //i+1: +90deg (gyro)!!! + Math::mul3(lentoforceattac,nextdirection,lforceattac); + //nextdirection: +90deg (gyro)!!! Math::add3(lforceattac,_base,lforceattac); - Math::mul3(speed,directions[i+1],lspeed); - Math::mul3(1,directions[i+1],dirzentforce); + Math::mul3(speed,nextdirection,lspeed); + Math::mul3(1,nextdirection,dirzentforce); + float maxcyclic=(i&1)?_maxcyclicele:_maxcyclicail; float mincyclic=(i&1)?_mincyclicele:_mincyclicail; @@ -845,19 +968,24 @@ void Rotor::compile() lspeed,dirzentforce,zentforce,pitchaforce, _max_pitch,_min_pitch, mincyclic,maxcyclic,_delta3,rotorpartmass,_translift, _rel_len_hinge,lentocenter); - rp->setAlphaoutput(_alphaoutput[i&1?i:(_ccw?i^2:i)],0); - rp->setAlphaoutput(_alphaoutput[4+(i&1?i:(_ccw?i^2:i))],1+(i>1)); + int k = i*4/_number_of_parts; + rp->setAlphaoutput(_alphaoutput[k&1?k:(_ccw?k^2:k)],0); + rp->setAlphaoutput(_alphaoutput[4+(k&1?k:(_ccw?k^2:k))],1+(k>1)); _rotorparts.add(rp); rp->setTorque(torquemax,torque0); rp->setRelamp(relamp); - rp->setDirectionofRotorPart(directions[i]); - rp->setTorqueOfInertia(_torque_of_inertia/4.); + rp->setDirectionofRotorPart(direction); + rp->setTorqueOfInertia(_torque_of_inertia/_number_of_parts); } - for (i=0;i<4;i++) + for (i=0;i<_number_of_parts;i++) { - rps[i]->setlastnextrp(rps[(i+3)%4],rps[(i+1)%4],rps[(i+2)%4]); + rps[i]->setlastnextrp(rps[(i-1+_number_of_parts)%_number_of_parts], + rps[(i+1)%_number_of_parts], + rps[(i+_number_of_parts/2)%_number_of_parts], + rps[(i-_number_of_parts/4+_number_of_parts)%_number_of_parts], + rps[(i+_number_of_parts/4)%_number_of_parts]); } - for (i=0;i<4;i++) + for (i=0;i<_number_of_parts;i++) { rps[i]->setCompiled(); } @@ -868,25 +996,27 @@ void Rotor::compile() if (_airfoil_lift_coefficient==0) { //calculate the lift and drag coefficients now - _liftcoef=0; + _dragcoef0=1; + _dragcoef1=1; + _liftcoef=1; + rps[0]->calculateAlpha(v_wind,rho_null,_pitch_a,0,0, + &(torque[0]),&(lift[0])); //max_pitch a + _liftcoef = pitchaforce/lift[0]; _dragcoef0=1; _dragcoef1=0; rps[0]->calculateAlpha(v_wind,rho_null,0,0,0,&(torque[0]),&(lift[0])); //0 degree, c0 - _liftcoef=0; _dragcoef0=0; _dragcoef1=1; rps[0]->calculateAlpha(v_wind,rho_null,0,0,0,&(torque[1]),&(lift[1])); //0 degree, c1 - _liftcoef=0; _dragcoef0=1; _dragcoef1=0; rps[0]->calculateAlpha(v_wind,rho_null,_pitch_b,0,0,&(torque[2]),&(lift[2])); //picth b, c0 - _liftcoef=0; _dragcoef0=0; _dragcoef1=1; rps[0]->calculateAlpha(v_wind,rho_null,_pitch_b,0,0,&(torque[3]),&(lift[3])); @@ -903,17 +1033,12 @@ void Rotor::compile() /(torque[1]/torque[0]-torque[3]/torque[2]); _dragcoef0=(torqueb-_dragcoef1*torque[3])/torque[2]; } - - _liftcoef=1; - rps[0]->calculateAlpha(v_wind,rho_null,_pitch_a,0,0, - &(torque[0]),&(lift[0])); //max_pitch a - _liftcoef = pitchaforce/lift[0]; } else { - _liftcoef=_airfoil_lift_coefficient/4*_number_of_blades; - _dragcoef0=_airfoil_drag_coefficient0/4*_number_of_blades; - _dragcoef1=_airfoil_drag_coefficient1/4*_number_of_blades; + _liftcoef=_airfoil_lift_coefficient/_number_of_parts*_number_of_blades; + _dragcoef0=_airfoil_drag_coefficient0/_number_of_parts*_number_of_blades*_c2; + _dragcoef1=_airfoil_drag_coefficient1/_number_of_parts*_number_of_blades*_c2; } //Check @@ -923,28 +1048,145 @@ void Rotor::compile() &(torque[1]),&(lift[1])); //pitch b rps[0]->calculateAlpha(v_wind,rho_null,0,0,0, &(torque[3]),&(lift[3])); //pitch 0 - SG_LOG(SG_FLIGHT, SG_DEBUG, + SG_LOG(SG_GENERAL, SG_DEBUG, "Rotor: coefficients for airfoil:" << endl << setprecision(6) - << " drag0: " << _dragcoef0*4/_number_of_blades - << " drag1: " << _dragcoef1*4/_number_of_blades - << " lift: " << _liftcoef*4/_number_of_blades + << " drag0: " << _dragcoef0*_number_of_parts/_number_of_blades/_c2 + << " drag1: " << _dragcoef1*_number_of_parts/_number_of_blades/_c2 + << " lift: " << _liftcoef*_number_of_parts/_number_of_blades << endl << "at 10 deg:" << endl << "drag: " << (Math::sin(10./180*pi)*_dragcoef1+_dragcoef0) - *4/_number_of_blades - << "lift: " << Math::sin(10./180*pi)*_liftcoef*4/_number_of_blades + *_number_of_parts/_number_of_blades/_c2 + << " lift: " << Math::sin(10./180*pi)*_liftcoef*_number_of_parts/_number_of_blades << endl - << "Some results (Pitch [degree], Power [kW], Lift [N]" << endl - << 0.0f << "deg " << Math::abs(torque[3]*4*_omegan/1000) << "kW " - << lift[3] << endl - << _pitch_a*180/pi << "deg " << Math::abs(torque[0]*4*_omegan/1000) - << "kW " << lift[0] << endl - << _pitch_b*180/pi << "deg " << Math::abs(torque[1]*4*_omegan/1000) - << "kW " << lift[1] << endl << endl); + << "Some results (Pitch [degree], Power [kW], Lift [N])" << endl + << 0.0f << "deg " << Math::abs(torque[3]*_number_of_parts*_omegan/1000) << "kW " + << lift[3]*_number_of_parts << endl + << _pitch_a*180/pi << "deg " << Math::abs(torque[0]*_number_of_parts*_omegan/1000) + << "kW " << lift[0]*_number_of_parts << endl + << _pitch_b*180/pi << "deg " << Math::abs(torque[1]*_number_of_parts*_omegan/1000) + << "kW " << lift[1]*_number_of_parts << endl << endl ); + //first calculation of relamp is wrong + //it used pitchaforce, but this was unknown and + //on the default value + _delta*=lift[0]/pitchaforce; + relamp=(omega*omega/(2*_delta*Math::sqrt(sqr(omega0*omega0-omega*omega) + +4*_delta*_delta*omega*omega)))*_cyclic_factor; + for (i=0;i<_number_of_parts;i++) + { + rps[i]->setRelamp(relamp); + } rps[0]->setOmega(0); + writeInfo(); +} +std::ostream & operator<<(std::ostream & out, /*const*/ Rotor& r) +{ +#define i(x) << #x << ":" << r.x << endl +#define iv(x) << #x << ":" << r.x[0] << ";" << r.x[1] << ";" <_rotorparts.size()) dt=((Rotorpart*)r0->_rotorparts.get(0))->getDt(); float rotor_brake_torque; - rotor_brake_torque=_rotorbrake*_max_power_rotor_brake; + rotor_brake_torque=_rotorbrake*_max_power_rotor_brake+_rotorgear_friction; //clamp it to the value you need to stop the rotor + //to avod accelerate the rotor to neagtive rpm: rotor_brake_torque=Math::clamp(rotor_brake_torque,0, total_torque_of_inertia/dt*omegarel); max_torque_of_engine-=rotor_brake_torque; @@ -1092,7 +1316,7 @@ void Rotorgear::calcForces(float* torqueOut) float lim1=-total_torque/total_torque_of_inertia; //accel. by autorotation - if (lim1<_engine_accell_limit) lim1=_engine_accell_limit; + if (lim1<_engine_accel_limit) lim1=_engine_accel_limit; //if the accel by autorotation greater than the max. engine //accel, then this is the limit, if not: the engine is the limit if (_ddt_omegarel>lim1) _ddt_omegarel=lim1; @@ -1125,6 +1349,7 @@ void Rotorgear::calcForces(float* torqueOut) } } } + _total_torque_on_engine=total_torque+_ddt_omegarel*total_torque_of_inertia; } } @@ -1173,24 +1398,36 @@ void Rotorgear::setParameter(char *parametername, float value) p(yasimdragfactor,1) p(yasimliftfactor,1) p(max_power_rotor_brake,1000) - p(engine_accell_limit,0.01) - cout << "internal error in parameter set up for rotorgear: '" - << parametername <<"'" << endl; + p(rotorgear_friction,1000) + p(engine_accel_limit,0.01) + SG_LOG(SG_INPUT, SG_ALERT, + "internal error in parameter set up for rotorgear: '" + << parametername <<"'" << endl); #undef p } - +int Rotorgear::getValueforFGSet(int j,char *text,float *f) +{ + if (j==0) + { + sprintf(text,"/rotors/gear/total_torque"); + *f=_total_torque_on_engine; + } else return 0; + return j+1; +} Rotorgear::Rotorgear() { _in_use=0; _engineon=0; _rotorbrake=0; _max_power_rotor_brake=1; + _rotorgear_friction=1; _max_power_engine=1000*450; _engine_prop_factor=0.05f; _yasimdragfactor=1; _yasimliftfactor=1; _ddt_omegarel=0; - _engine_accell_limit=0.05f; + _engine_accel_limit=0.05f; + _total_torque_on_engine=0; } Rotorgear::~Rotorgear() diff --git a/src/FDM/YASim/Rotor.hpp b/src/FDM/YASim/Rotor.hpp index df8bdae87..0f7953e8b 100644 --- a/src/FDM/YASim/Rotor.hpp +++ b/src/FDM/YASim/Rotor.hpp @@ -11,9 +11,11 @@ namespace yasim { class Surface; class Rotorpart; +class Ground; const float rho_null=1.184f; //25DegC, 101325Pa class Rotor { + friend std::ostream & operator<<(std::ostream & out, /*const*/ Rotor& r); private: float _torque; float _omega,_omegan,_omegarel,_ddt_omega,_omegarelneu; @@ -25,6 +27,9 @@ private: float _airfoil_drag_coefficient0; float _airfoil_drag_coefficient1; int _ccw; + int _number_of_blades; + int _number_of_segments; + int _number_of_parts; public: Rotor(); @@ -78,7 +83,7 @@ public: void getTip(float* tip); void calcLiftFactor(float* v, float rho, State *s); void getDownWash(float *pos, float * v_heli, float *downwash); - float getGroundEffect(float* posOut); + int getNumberOfBlades(){return _number_of_blades;} // Query the list of Rotorpart objects int numRotorparts(); @@ -102,24 +107,36 @@ public: float getOmegan() {return _omegan;} float getTaper() { return _taper;} float getChord() { return _chord;} + int getNumberOfParts() { return _number_of_parts;} float getOverallStall() {if (_stall_v2sum !=0 ) return _stall_sum/_stall_v2sum; else return 0;} + float getAirfoilIncidenceNoLift() {return _airfoil_incidence_no_lift;} + + Vector _rotorparts; + void findGroundEffectAltitude(Ground * ground_cb,State *s); + void writeInfo(); + private: void strncpy(char *dest,const char *src,int maxlen); void interp(float* v1, float* v2, float frac, float* out); float calcStall(float incidence,float speed); + float findGroundEffectAltitude(Ground * ground_cb,State *s, + float *pos0,float *pos1,float *pos2,float *pos3, + int iteration=0,float a0=-1,float a1=-1,float a2=-1,float a3=-1); Rotorpart* newRotorpart(float* pos, float *posforceattac, float *normal, float* speed,float *dirzentforce, float zentforce,float maxpitchforce, float maxpitch, float minpitch, float mincyclic,float maxcyclic, float delta3,float mass,float translift,float rellenhinge,float len); float _base[3]; + float _groundeffectpos[4][3]; + float _ground_effect_altitude; + //some postions, where to calcualte the ground effect float _normal[3];//the normal vector (direction of rotormast, pointing up) float _normal_with_yaw_roll[3];//the normal vector (perpendicular to rotordisc) float _forward[3]; float _diameter; - int _number_of_blades; float _weight_per_blade; float _rel_blade_center; float _min_pitch; @@ -166,7 +183,6 @@ private: float _dragcoef0; float _dragcoef1; float _twist; //outer incidence = inner inner incidence + _twist - int _number_of_segments; float _rel_len_where_incidence_is_measured; float _torque_of_inertia; float _rel_len_blade_start; @@ -179,7 +195,12 @@ private: float _stall_v2sum; float _yaw; float _roll; + float _cyclicail; + float _cyclicele; + float _cyclic_factor; + float _rotor_correction_factor; }; +std::ostream & operator<<(std::ostream & out, /*const*/ Rotor& r); class Rotorgear { private: @@ -191,8 +212,10 @@ private: float _yasimliftfactor; float _rotorbrake; float _max_power_rotor_brake; + float _rotorgear_friction; float _ddt_omegarel; - float _engine_accell_limit; + float _engine_accel_limit; + float _total_torque_on_engine; Vector _rotors; public: @@ -218,6 +241,7 @@ public: Vector* getRotors() { return &_rotors;} void initRotorIteration(float *lrot,float dt); void getDownWash(float *pos, float * v_heli, float *downwash); + int getValueforFGSet(int j,char *b,float *f); }; }; // namespace yasim diff --git a/src/FDM/YASim/Rotorpart.cpp b/src/FDM/YASim/Rotorpart.cpp index 708065d68..23c0fb613 100644 --- a/src/FDM/YASim/Rotorpart.cpp +++ b/src/FDM/YASim/Rotorpart.cpp @@ -45,6 +45,8 @@ Rotorpart::Rotorpart() _lastrp=0; _nextrp=0; _oppositerp=0; + _last90rp=0; + _next90rp=0; _translift=0; _dynamic=100; _c2=0; @@ -62,6 +64,8 @@ Rotorpart::Rotorpart() _diameter=10; _torque_of_inertia=0; _rel_len_blade_start=0; + _torque=0; + _rotor_correction_factor=0.6; } void Rotorpart::inititeration(float dt,float *rot) @@ -73,10 +77,10 @@ void Rotorpart::inititeration(float dt,float *rot) float a=Math::dot3(rot,_normal); if(a>0) _alphaalt=_alpha*Math::cos(a) - +_nextrp->getrealAlpha()*Math::sin(a); + +_next90rp->getrealAlpha()*Math::sin(a); else _alphaalt=_alpha*Math::cos(a) - +_lastrp->getrealAlpha()*Math::sin(-a); + +_last90rp->getrealAlpha()*Math::sin(-a); //calculate the rotation of the fuselage, determine //the part in the same direction as alpha //and add it ro _alphaalt @@ -102,8 +106,10 @@ void Rotorpart::setParameter(char *parametername, float value) p(number_of_segments) p(rel_len_where_incidence_is_measured) p(rel_len_blade_start) - cout << "internal error in parameter set up for rotorpart: '" - << parametername <<"'" << endl; + p(rotor_correction_factor) + SG_LOG(SG_INPUT, SG_ALERT, + "internal error in parameter set up for rotorpart: '" + << parametername <<"'" << endl); #undef p } @@ -281,14 +287,14 @@ float Rotorpart::getAlpha(int i) i=i&1; if (i==0) - return _alpha*180/3.14;//in Grad = 1 + return _alpha*180/pi;//in Grad = 1 else { if (_alpha2type==1) //yaw or roll return (getAlpha(0)-_oppositerp->getAlpha(0))/2; else //collective return (getAlpha(0)+_oppositerp->getAlpha(0)+ - _nextrp->getAlpha(0)+_lastrp->getAlpha(0))/4; + _next90rp->getAlpha(0)+_last90rp->getAlpha(0))/4; } } float Rotorpart::getrealAlpha(void) @@ -298,11 +304,7 @@ float Rotorpart::getrealAlpha(void) void Rotorpart::setAlphaoutput(char *text,int i) { - SG_LOG(SG_FLIGHT, SG_DEBUG, "setAlphaoutput rotorpart [" - << text << "] typ" << i); - strncpy(_alphaoutputbuf[i>0],text,255); - if (i>0) _alpha2type=i; } @@ -339,11 +341,13 @@ void Rotorpart::setCyclic(float pos) } void Rotorpart::setlastnextrp(Rotorpart*lastrp,Rotorpart*nextrp, - Rotorpart *oppositerp) + Rotorpart *oppositerp,Rotorpart*last90rp,Rotorpart*next90rp) { _lastrp=lastrp; _nextrp=nextrp; _oppositerp=oppositerp; + _last90rp=last90rp; + _next90rp=next90rp; } void Rotorpart::strncpy(char *dest,const char *src,int maxlen) @@ -373,7 +377,10 @@ float Rotorpart::calculateAlpha(float* v_rel_air, float rho, if((_nextrp==NULL)||(_lastrp==NULL)||(_rotor==NULL)) return 0.0;//not initialized. Can happen during startupt of flightgear if (returnlift!=NULL) *returnlift=0; - float flap_omega=(_nextrp->getrealAlpha()-_lastrp->getrealAlpha()) + /*float flap_omega=(_nextrp->getrealAlpha()-_lastrp->getrealAlpha()) + *_omega / pi*_rotor->getNumberOfParts()/4; hier mal die alte version probieren? + */ + float flap_omega=(_next90rp->getrealAlpha()-_last90rp->getrealAlpha()) *_omega / pi; float local_width=_diameter*(1-_rel_len_blade_start)/2. /(float (_number_of_segments)); @@ -382,8 +389,8 @@ float Rotorpart::calculateAlpha(float* v_rel_air, float rho, float rel = (n+.5)/(float (_number_of_segments)); float r= _diameter *0.5 *(rel*(1-_rel_len_blade_start) +_rel_len_blade_start); - float local_incidence=incidence+_twist *rel - _twist - *_rel_len_where_incidence_is_measured; + float local_incidence=incidence+_twist *rel - + _twist *_rel_len_where_incidence_is_measured; float local_chord = _rotor->getChord()*rel+_rotor->getChord() *_rotor->getTaper()*(1-rel); float A = local_chord * local_width; @@ -401,19 +408,30 @@ float Rotorpart::calculateAlpha(float* v_rel_air, float rho, //substract now the component of the air speed parallel to //the blade; - Math::mul3(Math::dot3(v_local,_directionofrotorpart), + Math::mul3(Math::dot3(v_local,_directionofrotorpart), _directionofrotorpart,v_help); Math::sub3(v_local,v_help,v_local); //split into direction and magnitude v_local_scalar=Math::mag3(v_local); if (v_local_scalar!=0) - Math::unit3(v_local,v_local); + //Math::unit3(v_local,v_help); + Math::mul3(1/v_local_scalar,v_local,v_help); float incidence_of_airspeed = Math::asin(Math::clamp( - Math::dot3(v_local,_normal),-1,1)) + local_incidence; + Math::dot3(v_help,_normal),-1,1)) + local_incidence; ias = incidence_of_airspeed; - float lift_wo_cyc = - _rotor->getLiftCoef(incidence_of_airspeed-cyc,v_local_scalar) + + //reduce the ias (Prantl factor) + float prantl_factor=2/pi*Math::acos(Math::exp( + -_rotor->getNumberOfBlades()/2.*(1-rel) + *Math::sqrt(1+1/Math::sqr(Math::tan( + pi/2-Math::abs(incidence_of_airspeed-local_incidence)))))); + incidence_of_airspeed = (incidence_of_airspeed+ + _rotor->getAirfoilIncidenceNoLift())*prantl_factor + *_rotor_correction_factor-_rotor->getAirfoilIncidenceNoLift(); + ias = incidence_of_airspeed; + float lift_wo_cyc = _rotor->getLiftCoef(incidence_of_airspeed + -cyc*_rotor_correction_factor,v_local_scalar) * v_local_scalar * v_local_scalar * A *rho *0.5; float lift_with_cyc = _rotor->getLiftCoef(incidence_of_airspeed,v_local_scalar) @@ -427,15 +445,28 @@ float Rotorpart::calculateAlpha(float* v_rel_air, float rho, //angle between blade movement caused by rotor-rotation and the //total movement of the blade + /* the next shold look like this, but this is the inner loop of + the rotor simulation. For small angles (and we hav only small + angles) the first order approximation works well lift_moment += r*(lift * Math::cos(angle) - drag * Math::sin(angle)); *torque += r*(drag * Math::cos(angle) + lift * Math::sin(angle)); - + */ + lift_moment += r*(lift * (1-angle*angle) + - drag * angle); + *torque += r*(drag * (1-angle*angle) + + lift * angle); + if (returnlift!=NULL) *returnlift+=lift; } - float alpha=Math::atan2(lift_moment,_zentipetalforce * _len); - + //as above, use 1st order approximation + //float alpha=Math::atan2(lift_moment,_zentipetalforce * _len); + float alpha; + if ((_zentipetalforce >1e-8) || (_zentipetalforce <-1e-8)) + alpha=lift_moment/(_zentipetalforce * _len); + else + alpha=0; return (alpha); } @@ -461,12 +492,13 @@ void Rotorpart::calcForce(float* v, float rho, float* out, float* torque, //Angle of blade which would produce no vertical force (where the //effective incidence is zero) - float cyc=_mincyclic+(_cyclic+1)/2*(_maxcyclic-_mincyclic); + //float cyc=_mincyclic+(_cyclic+1)/2*(_maxcyclic-_mincyclic); + float cyc=_cyclic; float col=_minpitch+(_collective+1)/2*(_maxpitch-_minpitch); _incidence=(col+cyc)-_delta3*_alphaalt; //the incidence of the rotorblade due to control input reduced by the //delta3 effect, see README.YASIM - float beta=_relamp*cyc+col; + //float beta=_relamp*cyc+col; //the incidence of the rotorblade which is used for the calculation float alpha,factor; //alpha is the flapping angle @@ -490,7 +522,7 @@ void Rotorpart::calcForce(float* v, float rho, float* out, float* torque, &scalar_torque); //calculate drag etc., e. g. for deccelrating the rotor if engine //is off and omega <10% - + alpha = Math::clamp(alpha,_alphamin,_alphamax); float rel =_omega*10 / _omegan; alpha=rel * alpha + (1-rel)* _alpha0; factor=_dt*_dynamic/10; @@ -505,11 +537,11 @@ void Rotorpart::calcForce(float* v, float rho, float* out, float* torque, alpha=_alphaalt+(alpha-_alphaalt)*factor; _alpha=alpha; - float meancosalpha=(1*Math::cos(_lastrp->getrealAlpha()) - +1*Math::cos(_nextrp->getrealAlpha()) + float meancosalpha=(1*Math::cos(_last90rp->getrealAlpha()) + +1*Math::cos(_next90rp->getrealAlpha()) +1*Math::cos(_oppositerp->getrealAlpha()) +1*Math::cos(alpha))/4; - float schwenkfactor=1-(Math::cos(_lastrp->getrealAlpha())-meancosalpha); + float schwenkfactor=1-(Math::cos(_lastrp->getrealAlpha())-meancosalpha)*_rotor->getNumberOfParts()/4; //missing: consideration of rellenhinge float xforce = Math::cos(alpha)*_zentipetalforce; @@ -531,8 +563,62 @@ void Rotorpart::getAccelTorque(float relaccel,float *t) int i; float f=_rotor->getCcw()?1:-1; for(i=0; i<3; i++) { - t[i]=f*-1* _normal[i]*relaccel*_omegan* _torque_of_inertia; + t[i]=f*-1* _normal[i]*relaccel*_omegan* _torque_of_inertia;// *_omeagan ? _rotor->addTorque(-relaccel*_omegan* _torque_of_inertia); } } +std::ostream & operator<<(std::ostream & out, const Rotorpart& rp) +{ +#define i(x) << #x << ":" << rp.x << endl +#define iv(x) << #x << ":" << rp.x[0] << ";" << rp.x[1] << ";" < +#include namespace yasim { class Rotor; class Rotorpart { + friend std::ostream & operator<<(std::ostream & out, const Rotorpart& rp); private: float _dt; float _last_torque[3]; @@ -54,7 +56,7 @@ namespace yasim { float calculateAlpha(float* v, float rho, float incidence, float cyc, float alphaalt, float *torque,float *returnlift=0); void setlastnextrp(Rotorpart*lastrp,Rotorpart*nextrp, - Rotorpart *oppositerp); + Rotorpart *oppositerp,Rotorpart*last90rp,Rotorpart*next90rp); void setTorque(float torque_max_force,float torque_no_force); void setOmega(float value); void setOmegaN(float value); @@ -69,10 +71,11 @@ namespace yasim { void setParameter(char *parametername, float value); void setRotor(Rotor *rotor); void setTorqueOfInertia(float toi); + void writeInfo(std::ostringstream &buffer); private: void strncpy(char *dest,const char *src,int maxlen); - Rotorpart *_lastrp,*_nextrp,*_oppositerp; + Rotorpart *_lastrp,*_nextrp,*_oppositerp,*_last90rp,*_next90rp; Rotor *_rotor; float _pos[3]; // position in local coords @@ -109,14 +112,14 @@ namespace yasim { int _number_of_segments; float _rel_len_where_incidence_is_measured; float _rel_len_blade_start; - float _rel_len_blade_measured; float _diameter; float _torque_of_inertia; float _torque; // total torque of rotor (scalar) for calculating new rotor rpm char _alphaoutputbuf[2][256]; int _alpha2type; + float _rotor_correction_factor; }; - + std::ostream & operator<<(std::ostream & out, const Rotorpart& rp); }; // namespace yasim #endif // _ROTORPART_HPP