LCOV - code coverage report
Current view: top level - models/propulsion - FGRotor.cpp (source / functions) Hit Total Coverage
Test: JSBSim-Coverage-Statistics Lines: 1 464 0.2 %
Date: 2010-08-24 Functions: 3 32 9.4 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 3 162 1.9 %

           Branch data     Line data    Source code
       1                 :            : /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
       2                 :            : 
       3                 :            :  Module:       FGRotor.cpp
       4                 :            :  Author:       Jon S. Berndt
       5                 :            :  Date started: 08/24/00
       6                 :            :  Purpose:      Encapsulates the rotor object
       7                 :            : 
       8                 :            :  ------------- Copyright (C) 2000  Jon S. Berndt (jsb@hal-pc.org) -------------
       9                 :            : 
      10                 :            :  This program is free software; you can redistribute it and/or modify it under
      11                 :            :  the terms of the GNU Lesser General Public License as published by the Free Software
      12                 :            :  Foundation; either version 2 of the License, or (at your option) any later
      13                 :            :  version.
      14                 :            : 
      15                 :            :  This program is distributed in the hope that it will be useful, but WITHOUT
      16                 :            :  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
      17                 :            :  FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
      18                 :            :  details.
      19                 :            : 
      20                 :            :  You should have received a copy of the GNU Lesser General Public License along with
      21                 :            :  this program; if not, write to the Free Software Foundation, Inc., 59 Temple
      22                 :            :  Place - Suite 330, Boston, MA  02111-1307, USA.
      23                 :            : 
      24                 :            :  Further information about the GNU Lesser General Public License can also be found on
      25                 :            :  the world wide web at http://www.gnu.org.
      26                 :            : 
      27                 :            : FUNCTIONAL DESCRIPTION
      28                 :            : --------------------------------------------------------------------------------
      29                 :            : 
      30                 :            : HISTORY
      31                 :            : --------------------------------------------------------------------------------
      32                 :            : 08/24/00  JSB  Created
      33                 :            : 01/01/10  T.Kreitler test implementation
      34                 :            : 
      35                 :            : %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      36                 :            : INCLUDES
      37                 :            : %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
      38                 :            : 
      39                 :            : #include <iostream>
      40                 :            : #include <sstream>
      41                 :            : 
      42                 :            : #include "FGRotor.h"
      43                 :            : 
      44                 :            : #include "models/FGPropagate.h"
      45                 :            : #include "models/FGAtmosphere.h"
      46                 :            : #include "models/FGAuxiliary.h"
      47                 :            : #include "models/FGMassBalance.h"
      48                 :            : 
      49                 :            : #include "input_output/FGXMLElement.h"
      50                 :            : 
      51                 :            : #include "math/FGRungeKutta.h"
      52                 :            : 
      53                 :            : using namespace std;
      54                 :            : 
      55                 :            : namespace JSBSim {
      56                 :            : 
      57                 :            : static const char *IdSrc = "$Id: FGRotor.cpp,v 1.9 2010/06/05 12:12:34 jberndt Exp $";
      58                 :            : static const char *IdHdr = ID_ROTOR;
      59                 :            : 
      60                 :            : /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      61                 :            : MISC
      62                 :            : %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
      63                 :            : 
      64                 :            : static int dump_req; // debug schwafel flag
      65                 :            : 
      66                 :          0 : static inline double sqr(double x) { return x*x; }
      67                 :            : 
      68                 :            : /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      69                 :            : CLASS IMPLEMENTATION
      70                 :            : %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
      71                 :            : 
      72                 :            : // starting with 'inner' rotor, FGRotor constructor is further down
      73                 :            : 
      74 [ #  # ][ #  # ]:          0 : FGRotor::rotor::~rotor() { }
                 [ #  # ]
      75                 :            : 
      76                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      77                 :            : 
      78                 :            : // hmm, not a real alternative to a pretty long initializer list
      79                 :            : 
      80                 :          0 : void FGRotor::rotor::zero() {
      81                 :            :   FGColumnVector3 zero_vec(0.0, 0.0, 0.0);
      82                 :            : 
      83                 :          0 :   flags               = 0;
      84                 :          0 :   parent              = NULL  ;
      85                 :            : 
      86                 :          0 :   reports             = 0;
      87                 :            : 
      88                 :            :   // configuration
      89                 :          0 :   Radius              = 0.0 ;
      90                 :          0 :   BladeNum            = 0   ;
      91                 :          0 :   RelDistance_xhub    = 0.0 ;
      92                 :          0 :   RelShift_yhub       = 0.0 ;
      93                 :          0 :   RelHeight_zhub      = 0.0 ;
      94                 :          0 :   NominalRPM          = 0.0 ;
      95                 :          0 :   MinRPM              = 0.0 ;
      96                 :          0 :   BladeChord          = 0.0 ;
      97                 :          0 :   LiftCurveSlope      = 0.0 ;
      98                 :          0 :   BladeFlappingMoment = 0.0 ;
      99                 :          0 :   BladeTwist          = 0.0 ;
     100                 :          0 :   BladeMassMoment     = 0.0 ;
     101                 :          0 :   TipLossB            = 0.0 ;
     102                 :          0 :   PolarMoment         = 0.0 ;
     103                 :          0 :   InflowLag           = 0.0 ;
     104                 :          0 :   ShaftTilt           = 0.0 ;
     105                 :          0 :   HingeOffset         = 0.0 ;
     106                 :          0 :   HingeOffset_hover   = 0.0 ;
     107                 :          0 :   CantAngleD3         = 0.0 ;
     108                 :            : 
     109                 :          0 :   theta_shaft         = 0.0 ;
     110                 :          0 :   phi_shaft           = 0.0 ;
     111                 :            : 
     112                 :            :   // derived parameters
     113                 :          0 :   LockNumberByRho     = 0.0 ;
     114                 :          0 :   solidity            = 0.0 ;
     115                 :          0 :   RpmRatio            = 0.0 ;
     116                 :            : 
     117         [ #  # ]:          0 :   for (int i=0; i<5; i++) R[i] = 0.0;
     118         [ #  # ]:          0 :   for (int i=0; i<6; i++) B[i] = 0.0;
     119                 :            : 
     120                 :          0 :   BodyToShaft.InitMatrix();
     121                 :          0 :   ShaftToBody.InitMatrix();
     122                 :            : 
     123                 :            :   // dynamic values
     124                 :          0 :   ActualRPM           = 0.0 ;
     125                 :          0 :   Omega               = 0.0 ;
     126                 :          0 :   beta_orient         = 0.0 ;
     127                 :          0 :   a0                  = 0.0 ;
     128                 :          0 :   a_1 = b_1 = a_dw    = 0.0 ;
     129                 :          0 :   a1s = b1s           = 0.0 ;
     130                 :          0 :   H_drag = J_side     = 0.0 ;
     131                 :            : 
     132                 :          0 :   Torque              = 0.0 ;
     133                 :          0 :   Thrust              = 0.0 ;
     134                 :          0 :   Ct                  = 0.0 ;
     135                 :          0 :   lambda              = 0.0 ;
     136                 :          0 :   mu                  = 0.0 ;
     137                 :          0 :   nu                  = 0.0 ;
     138                 :          0 :   v_induced           = 0.0 ;
     139                 :            : 
     140                 :          0 :   force      = zero_vec ;
     141                 :          0 :   moment     = zero_vec ;
     142                 :            : 
     143                 :          0 : }
     144                 :            : 
     145                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     146                 :            : 
     147                 :            : // 5in1: value-fetch-convert-default-return function 
     148                 :            : 
     149                 :            : double FGRotor::rotor::cnf_elem(  const string& ename, double default_val, 
     150                 :          0 :                                   const string& unit, bool tell)
     151                 :            : {
     152                 :            : 
     153                 :          0 :   Element *e = NULL;
     154                 :          0 :   double val=default_val;
     155                 :            : 
     156                 :          0 :   std::string pname = "*No parent element*";
     157                 :            : 
     158         [ #  # ]:          0 :   if (parent) {
     159                 :          0 :     e = parent->FindElement(ename);
     160                 :          0 :     pname = parent->GetName();
     161                 :            :   }
     162                 :            : 
     163         [ #  # ]:          0 :   if (e) {
     164         [ #  # ]:          0 :     if (unit.empty()) {
     165                 :            :       // val = e->FindElementValueAsNumber(ename); 
     166                 :            :       // yields to: Attempting to get single data value from multiple lines
     167                 :          0 :       val = parent->FindElementValueAsNumber(ename);
     168                 :            :     } else {
     169                 :            :       // val = e->FindElementValueAsNumberConvertTo(ename,unit); 
     170                 :            :       // yields to: Attempting to get non-existent element diameter + crash, why ?
     171                 :          0 :       val = parent->FindElementValueAsNumberConvertTo(ename,unit);
     172                 :            :     }
     173                 :            :   } else {
     174         [ #  # ]:          0 :     if (tell) {
     175                 :          0 :       cerr << pname << ": missing element '" << ename <<"' using estimated value: " << default_val << endl;
     176                 :            :     }
     177                 :            :   }
     178                 :            : 
     179                 :          0 :   return val;
     180                 :            : }
     181                 :            : 
     182                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     183                 :            : 
     184                 :          0 : double FGRotor::rotor::cnf_elem(const string& ename, double default_val, bool tell)
     185                 :            : {
     186                 :          0 :   return cnf_elem(ename, default_val, "", tell);
     187                 :            : }
     188                 :            : 
     189                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     190                 :            : 
     191                 :            : // 1. read configuration and try to fill holes, ymmv
     192                 :            : // 2. calculate derived parameters and transforms
     193                 :          0 : void FGRotor::rotor::configure(int f, const rotor *xmain)
     194                 :            : {
     195                 :            : 
     196                 :            :   double estimate;
     197                 :          0 :   const bool yell   = true;
     198                 :          0 :   const bool silent = false;
     199                 :            : 
     200                 :          0 :   flags = f;
     201                 :            : 
     202         [ #  # ]:          0 :   estimate = (xmain) ? 2.0 * xmain->Radius * 0.2 : 42.0;
     203                 :          0 :   Radius = 0.5 * cnf_elem("diameter", estimate, "FT", yell);
     204                 :            : 
     205         [ #  # ]:          0 :   estimate = (xmain) ? xmain->BladeNum  : 2.0;
     206                 :          0 :   estimate = Constrain(1.0,estimate,4.0);
     207                 :          0 :   BladeNum = (int) cnf_elem("numblades", estimate, yell);
     208                 :            : 
     209         [ #  # ]:          0 :   estimate = (xmain) ? - xmain->Radius * 1.05 - Radius : - 0.025 * Radius ; 
     210                 :          0 :   RelDistance_xhub = cnf_elem("xhub", estimate, "FT", yell);
     211                 :            : 
     212                 :          0 :   RelShift_yhub = cnf_elem("yhub", 0.0, "FT", silent);
     213                 :            :   
     214                 :          0 :   estimate = - 0.1 * Radius - 4.0;
     215                 :          0 :   RelHeight_zhub = cnf_elem("zhub", estimate, "FT", yell);
     216                 :            :   
     217                 :            :   // make sure that v_tip (omega*r) is below 0.7mach ~ 750ft/s
     218                 :          0 :   estimate = (750.0/Radius)/(2.0*M_PI) * 60.0;  // 7160/Radius
     219                 :          0 :   NominalRPM = cnf_elem("nominalrpm", estimate, yell);
     220                 :            : 
     221                 :          0 :   MinRPM = cnf_elem("minrpm", 1.0, silent);
     222                 :          0 :   MinRPM = Constrain(1.0, MinRPM, NominalRPM-1.0);
     223                 :            : 
     224         [ #  # ]:          0 :   estimate = (xmain) ? 0.12 : 0.07;  // guess solidity
     225                 :          0 :   estimate = estimate * M_PI*Radius/BladeNum;
     226                 :          0 :   BladeChord = cnf_elem("chord", estimate, "FT", yell);
     227                 :            : 
     228                 :          0 :   LiftCurveSlope = cnf_elem("liftcurveslope", 6.0, yell); // "1/RAD"
     229                 :            : 
     230                 :          0 :   estimate = sqr(BladeChord) * sqr(Radius) * 0.57;
     231                 :          0 :   BladeFlappingMoment = cnf_elem("flappingmoment", estimate, "SLUG*FT2", yell);   
     232                 :          0 :   BladeFlappingMoment = Constrain(0.1, BladeFlappingMoment, 1e9);
     233                 :            : 
     234                 :          0 :   BladeTwist = cnf_elem("twist", -0.17, "RAD", yell);
     235                 :            : 
     236                 :          0 :   estimate = sqr(BladeChord) * BladeChord * 15.66; // might be really wrong!
     237                 :          0 :   BladeMassMoment = cnf_elem("massmoment", estimate, yell); // slug-ft
     238                 :          0 :   BladeMassMoment = Constrain(0.1, BladeMassMoment, 1e9);
     239                 :            : 
     240                 :          0 :   TipLossB = cnf_elem("tiplossfactor", 0.98, silent);
     241                 :            : 
     242                 :          0 :   estimate = 1.1 * BladeFlappingMoment * BladeNum;
     243                 :          0 :   PolarMoment = cnf_elem("polarmoment", estimate, "SLUG*FT2", silent);
     244                 :          0 :   PolarMoment = Constrain(0.1, PolarMoment, 1e9);
     245                 :            : 
     246                 :          0 :   InflowLag = cnf_elem("inflowlag", 0.2, silent); // fixme, depends on size
     247                 :            : 
     248         [ #  # ]:          0 :   estimate = (xmain) ? 0.0 : -0.06;  
     249                 :          0 :   ShaftTilt = cnf_elem("shafttilt", estimate, "RAD", silent);
     250                 :            : 
     251                 :            :   // ignore differences between teeter/hingeless/fully-articulated constructions
     252                 :          0 :   estimate = 0.05 * Radius;
     253         [ #  # ]:          0 :   HingeOffset = cnf_elem("hingeoffset", estimate, "FT", (xmain) ? silent : yell);
     254                 :            : 
     255                 :          0 :   CantAngleD3 = cnf_elem("cantangle", 0.0, "RAD", silent);  
     256                 :            : 
     257                 :            :   // derived parameters
     258                 :            : 
     259                 :            :   // precalc often used powers
     260                 :          0 :   R[0]=1.0; R[1]=Radius;   R[2]=R[1]*R[1]; R[3]=R[2]*R[1]; R[4]=R[3]*R[1];
     261                 :          0 :   B[0]=1.0; B[1]=TipLossB; B[2]=B[1]*B[1]; B[3]=B[2]*B[1]; B[4]=B[3]*B[1]; B[5]=B[4]*B[1];
     262                 :            : 
     263                 :          0 :   LockNumberByRho = LiftCurveSlope * BladeChord * R[4] / BladeFlappingMoment;
     264                 :          0 :   solidity = BladeNum * BladeChord / (M_PI * Radius);
     265                 :            : 
     266                 :            :   // use simple orientations at the moment
     267         [ #  # ]:          0 :   if (flags & eTail) { // axis parallel to Y_body
     268                 :          0 :     theta_shaft = 0.0; // no tilt
     269                 :          0 :     phi_shaft = 0.5*M_PI;
     270                 :            : 
     271                 :            :     // opposite direction if main rotor is spinning CW
     272 [ #  # ][ #  # ]:          0 :     if (xmain && (xmain->flags & eRotCW) ) { 
     273                 :          0 :       phi_shaft = -phi_shaft; 
     274                 :            :     }
     275                 :            :   } else {  // more or less upright
     276                 :          0 :     theta_shaft = ShaftTilt;
     277                 :          0 :     phi_shaft = 0.0; 
     278                 :            :   }
     279                 :            : 
     280                 :            :   // setup Shaft-Body transforms, see /SH79/ eqn(17,18)
     281                 :          0 :   double st = sin(theta_shaft);
     282                 :          0 :   double ct = cos(theta_shaft);
     283                 :          0 :   double sp = sin(phi_shaft);
     284                 :          0 :   double cp = cos(phi_shaft);
     285                 :            : 
     286                 :            :   ShaftToBody.InitMatrix(    ct, st*sp, st*cp,
     287                 :            :                             0.0,    cp,   -sp,
     288                 :          0 :                             -st, ct*sp, ct*cp  );
     289                 :            : 
     290                 :          0 :   BodyToShaft  =  ShaftToBody.Inverse();
     291                 :            : 
     292                 :            :   // misc defaults
     293                 :          0 :   nu = 0.001; // help the flow solver by providing some moving molecules
     294                 :            :   
     295                 :            :   return;
     296                 :            : }
     297                 :            : 
     298                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     299                 :            : 
     300                 :            : // calculate control-axes components of total airspeed at the hub.
     301                 :            : // sets rotor orientation angle (beta) as side effect. /SH79/ eqn(19-22)
     302                 :            : 
     303                 :            : FGColumnVector3 FGRotor::rotor::hub_vel_body2ca( const FGColumnVector3 &uvw, 
     304                 :            :                                                  const FGColumnVector3 &pqr,
     305                 :          0 :                                                  double a_ic, double b_ic)
     306                 :            : {
     307                 :            : 
     308                 :          0 :   FGColumnVector3  v_r, v_shaft, v_w;
     309                 :          0 :   FGColumnVector3  pos(RelDistance_xhub,0.0,RelHeight_zhub);
     310                 :            : 
     311                 :            :   v_r = uvw + pqr*pos;
     312                 :          0 :   v_shaft = BodyToShaft * v_r;
     313                 :            : 
     314                 :          0 :   beta_orient = atan2(v_shaft(eV),v_shaft(eU));
     315                 :            : 
     316                 :          0 :   v_w(eU) = v_shaft(eU)*cos(beta_orient) + v_shaft(eV)*sin(beta_orient);
     317                 :          0 :   v_w(eV) = 0.0;
     318                 :          0 :   v_w(eW) = v_shaft(eW) - b_ic*v_shaft(eU) - a_ic*v_shaft(eV);
     319                 :            : 
     320                 :          0 :   return v_w;
     321                 :            : }
     322                 :            : 
     323                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     324                 :            : 
     325                 :            : // express fuselage angular velocity in control axes /SH79/ eqn(30,31)
     326                 :            : 
     327                 :          0 : FGColumnVector3 FGRotor::rotor::fus_angvel_body2ca( const FGColumnVector3 &pqr)
     328                 :            : {
     329                 :          0 :   FGColumnVector3 av_s_fus, av_w_fus;    
     330                 :            : 
     331                 :          0 :   av_s_fus = BodyToShaft * pqr;
     332                 :            : 
     333                 :          0 :   av_w_fus(eP)=   av_s_fus(eP)*cos(beta_orient) + av_s_fus(eQ)*sin(beta_orient);
     334                 :          0 :   av_w_fus(eQ)= - av_s_fus(eP)*sin(beta_orient) + av_s_fus(eQ)*cos(beta_orient);
     335                 :          0 :   av_w_fus(eR)=   av_s_fus(eR);
     336                 :            :          
     337                 :          0 :   return av_w_fus;
     338                 :            : }
     339                 :            : 
     340                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     341                 :            : 
     342                 :            : // problem function passed to rk solver
     343                 :            : 
     344                 :          0 :   double FGRotor::rotor::dnuFunction::pFunc(double x, double nu) {
     345                 :            :     double d_nu;
     346                 :            :     d_nu =  k_sat * (ct_lambda * (k_wor - nu) + k_theta) / 
     347                 :          0 :                      (2.0 * sqrt( mu2 + sqr(k_wor - nu)) ); 
     348                 :          0 :     d_nu = d_nu * k_flowscale - nu;
     349                 :          0 :     return  d_nu; 
     350                 :            :   }; 
     351                 :            : 
     352                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     353                 :            : 
     354                 :            :   // merge params to keep the equation short
     355                 :          0 :   void FGRotor::rotor::dnuFunction::update_params(rotor *r, double ct_t01, double fs, double w) {
     356                 :          0 :     k_sat       = 0.5* r->solidity * r->LiftCurveSlope;
     357                 :          0 :     ct_lambda   = 1.0/2.0*r->B[2] + 1.0/4.0 * r->mu*r->mu;
     358                 :          0 :     k_wor       = w/(r->Omega*r->Radius);
     359                 :          0 :     k_theta     = ct_t01;
     360                 :          0 :     mu2         = r->mu * r->mu;
     361                 :          0 :     k_flowscale = fs;
     362                 :          0 :   };
     363                 :            : 
     364                 :            : 
     365                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     366                 :            : 
     367                 :            : // Calculate rotor thrust and inflow-ratio (lambda), this is achieved by
     368                 :            : // approximating a solution for the differential equation:
     369                 :            : // 
     370                 :            : //  dnu/dt = 1/tau ( Ct / (2*sqrt(mu^2+lambda^2))  -  nu )  , /SH79/ eqn(26).
     371                 :            : // 
     372                 :            : // Propper calculation of the inflow-ratio (lambda) is vital for the
     373                 :            : // following calculations. Simple implementations (i.e. Newton-Raphson w/o
     374                 :            : // checking) tend to oscillate or overshoot in the low speed region,
     375                 :            : // therefore a more expensive solver is used.
     376                 :            : //
     377                 :            : // The flow_scale parameter is used to approximate a reduction of inflow
     378                 :            : // if the helicopter is close to the ground, yielding to higher thrust,
     379                 :            : // see /TA77/ eqn(10a). Doing the ground effect calculation here seems
     380                 :            : // more favorable then to code it in the fdm_config.
     381                 :            : 
     382                 :            : void FGRotor::rotor::calc_flow_and_thrust(    
     383                 :            :                            double dt, double rho, double theta_0,
     384                 :          0 :                            double Uw, double Ww, double flow_scale)
     385                 :            : {
     386                 :            : 
     387                 :          0 :   double ct_over_sigma = 0.0;
     388                 :            :   double ct_l, ct_t0, ct_t1;
     389                 :          0 :   double nu_ret = 0.0;
     390                 :            : 
     391                 :          0 :   mu = Uw/(Omega*Radius); // /SH79/ eqn(24)
     392                 :            : 
     393                 :          0 :   ct_t0 = (1.0/3.0*B[3] + 1.0/2.0 * TipLossB*mu*mu - 4.0/(9.0*M_PI) * mu*mu*mu )*theta_0;
     394                 :          0 :   ct_t1 = (1.0/4.0*B[4] + 1.0/4.0 * B[2]*mu*mu)*BladeTwist;
     395                 :            : 
     396                 :            :   // merge params together
     397                 :          0 :   flowEquation.update_params(this, ct_t0+ct_t1, flow_scale, Ww);
     398                 :            :   
     399                 :          0 :   nu_ret = rk.evolve(nu, &flowEquation);
     400                 :            : 
     401         [ #  # ]:          0 :   if (rk.getStatus() != FGRungeKutta::eNoError) { // never observed so far
     402                 :          0 :     cerr << "# IEHHHH [" << flags << "]: Solver Error - resetting!" << endl;
     403                 :          0 :     rk.clearStatus();
     404                 :          0 :     nu_ret = nu; // use old value and keep fingers crossed.
     405                 :            :   }
     406                 :            : 
     407                 :            :   // keep an eye on the solver, but be quiet after a hundred messages
     408 [ #  # ][ #  # ]:          0 :   if (reports < 100 && rk.getIterations()>6) {
                 [ #  # ]
     409                 :            :     cerr << "# LOOK [" << flags << "]: Solver took " 
     410                 :          0 :          << rk.getIterations() << " rounds." << endl;
     411                 :          0 :     reports++;
     412         [ #  # ]:          0 :     if (reports==100) {
     413                 :          0 :       cerr << "# stopped babbling after 100 notifications." << endl;
     414                 :            :     }
     415                 :            :   }
     416                 :            : 
     417                 :            :   // now from nu to lambda, Ct, and Thrust
     418                 :            : 
     419                 :          0 :   nu = nu_ret;
     420                 :          0 :   lambda = Ww/(Omega*Radius) - nu; // /SH79/ eqn(25)
     421                 :            : 
     422                 :          0 :   ct_l  = (1.0/2.0*B[2] + 1.0/4.0 * mu*mu)*lambda;  
     423                 :          0 :   ct_over_sigma = (LiftCurveSlope/2.0)*(ct_l + ct_t0 + ct_t1); // /SH79/ eqn(27)
     424                 :            : 
     425                 :          0 :   Thrust = BladeNum*BladeChord*Radius*rho*sqr(Omega*Radius) * ct_over_sigma;
     426                 :            : 
     427                 :          0 :   Ct = ct_over_sigma * solidity;
     428                 :          0 :   v_induced = nu * (Omega*Radius);
     429                 :            : 
     430 [ #  # ][ #  # ]:          0 :   if (dump_req && (flags & eMain) ) {
     431                 :          0 :     printf("# mu %f : nu %f lambda %f vi %f\n", mu, nu, lambda, v_induced);
     432                 :            :   }
     433                 :            : 
     434                 :          0 : }
     435                 :            : 
     436                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     437                 :            : 
     438                 :            : // this is the most arcane part in the calculation chain the
     439                 :            : // constants used should be reverted to more general parameters.
     440                 :            : // otoh: it works also for smaller rotors, sigh!
     441                 :            : // See /SH79/ eqn(36), and /BA41/ for a richer set of equations.
     442                 :            : 
     443                 :          0 : void FGRotor::rotor::calc_torque(double rho, double theta_0)
     444                 :            : {
     445                 :            :   double Qa0;
     446                 :            :   double cq_s_m[5], cq_over_sigma;
     447                 :            :   double l,m,t075; // shortcuts
     448                 :            : 
     449                 :          0 :   t075 = theta_0 + 0.75 * BladeTwist;
     450                 :            : 
     451                 :          0 :   m = mu;
     452                 :          0 :   l = lambda;
     453                 :            : 
     454                 :          0 :   cq_s_m[0] = 0.00109 - 0.0036*l - 0.0027*t075 - 1.10*sqr(l) - 0.545*l*t075 + 0.122*sqr(t075);
     455                 :          0 :   cq_s_m[2] = ( 0.00109 - 0.0027*t075 - 3.13*sqr(l) - 6.35*l*t075 - 1.93*sqr(t075) ) * sqr(m);
     456                 :          0 :   cq_s_m[3] = - 0.133*l*t075 * sqr(m)*m;
     457                 :          0 :   cq_s_m[4] = ( - 0.976*sqr(l) - 6.38*l*t075 - 5.26*sqr(t075) ) * sqr(m)*sqr(m);
     458                 :            : 
     459                 :          0 :   cq_over_sigma = cq_s_m[0] + cq_s_m[2] + cq_s_m[3] + cq_s_m[4];
     460                 :            :   // guess an a (LiftCurveSlope) is included in eqn above, so check if there is a large
     461                 :            :   // influence when  a_'other-model'/ a_'ch54' diverts from 1.0.
     462                 :            : 
     463                 :          0 :   Qa0 = BladeNum * BladeChord * R[2] * rho * sqr(Omega*Radius);
     464                 :            : 
     465                 :            : // TODO: figure out how to handle negative cq_over_sigma/torque
     466                 :            : 
     467                 :          0 :   Torque =  Qa0 *  cq_over_sigma;
     468                 :            : 
     469                 :            :   return;
     470                 :            : }
     471                 :            : 
     472                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     473                 :            : 
     474                 :            : // The coning angle doesn't apply for teetering rotors, but calculating
     475                 :            : // doesn't hurt. /SH79/ eqn(29)
     476                 :            : 
     477                 :          0 : void FGRotor::rotor::calc_coning_angle(double rho, double theta_0)
     478                 :            : {
     479                 :          0 :   double lock_gamma = LockNumberByRho * rho;
     480                 :            : 
     481                 :          0 :   double a0_l  = (1.0/6.0  * B[3] + 0.04 * mu*mu*mu) * lambda;
     482                 :          0 :   double a0_t0 = (1.0/8.0  * B[4] + 1.0/8.0  * B[2]*mu*mu) * theta_0;
     483                 :          0 :   double a0_t1 = (1.0/10.0 * B[5] + 1.0/12.0 * B[3]*mu*mu) * BladeTwist;
     484                 :          0 :   a0 = lock_gamma * ( a0_l + a0_t0 + a0_t1);
     485                 :            :   return;
     486                 :            : }
     487                 :            : 
     488                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     489                 :            : 
     490                 :            : // Flapping angles relative to control axes /SH79/ eqn(32)
     491                 :            : 
     492                 :            : void FGRotor::rotor::calc_flapping_angles(  double rho, double theta_0, 
     493                 :          0 :                                             const FGColumnVector3 &pqr_fus_w)
     494                 :            : {
     495                 :          0 :   double lock_gamma = LockNumberByRho * rho;
     496                 :            : 
     497                 :          0 :   double mu2_2B2 = sqr(mu)/(2.0*B[2]);
     498                 :          0 :   double t075 = theta_0 + 0.75 * BladeTwist;  // common approximation for rectangular blades
     499                 :            :   
     500                 :            :   a_1 = 1.0/(1.0 - mu2_2B2) * (
     501                 :            :                                  (2.0*lambda + (8.0/3.0)*t075)*mu
     502                 :            :                                + pqr_fus_w(eP)/Omega
     503                 :            :                                - 16.0 * pqr_fus_w(eQ)/(B[4]*lock_gamma*Omega)
     504                 :          0 :                              );
     505                 :            :   
     506                 :            :   b_1 = 1.0/(1.0 + mu2_2B2) * (
     507                 :            :                                  (4.0/3.0)*mu*a0
     508                 :            :                                - pqr_fus_w(eQ)/Omega
     509                 :            :                                - 16.0 * pqr_fus_w(eP)/(B[4]*lock_gamma*Omega)
     510                 :          0 :                              );
     511                 :            :   
     512                 :            :   // used in  force calc
     513                 :            :   a_dw = 1.0/(1.0 - mu2_2B2) * (
     514                 :            :                                  (2.0*lambda + (8.0/3.0)*t075)*mu
     515                 :            :                                - 24.0 * pqr_fus_w(eQ)/(B[4]*lock_gamma*Omega)
     516                 :            :                                  * ( 1.0 - ( 0.29 * t075 / (Ct/solidity) ) )
     517                 :          0 :                              );
     518                 :            :   
     519                 :            :   return;
     520                 :            : }
     521                 :            : 
     522                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     523                 :            : 
     524                 :            : // /SH79/ eqn(38,39)
     525                 :            : 
     526                 :          0 : void FGRotor::rotor::calc_drag_and_side_forces(double rho, double theta_0)
     527                 :            : {
     528                 :            :   double cy_over_sigma  ;
     529                 :          0 :   double t075 = theta_0 + 0.75 * BladeTwist;
     530                 :            : 
     531                 :          0 :   H_drag = Thrust * a_dw;
     532                 :            :   
     533                 :            :   cy_over_sigma = (
     534                 :            :                       0.75*b_1*lambda - 1.5*a0*mu*lambda + 0.25*a_1*b_1*mu
     535                 :            :                     - a0*a_1*sqr(mu) + (1.0/6.0)*a0*a_1
     536                 :            :                     - (0.75*mu*a0 - (1.0/3.0)*b_1 - 0.5*sqr(mu)*b_1)*t075
     537                 :          0 :                   );
     538                 :          0 :   cy_over_sigma *= LiftCurveSlope/2.0;
     539                 :            :   
     540                 :          0 :   J_side = BladeNum * BladeChord * Radius * rho * sqr(Omega*Radius) * cy_over_sigma;
     541                 :            : 
     542                 :            :   return;
     543                 :            : }
     544                 :            : 
     545                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     546                 :            : 
     547                 :            : // transform rotor forces from control axes to shaft axes, and express
     548                 :            : // in body axes /SH79/ eqn(40,41)
     549                 :            : 
     550                 :          0 : FGColumnVector3 FGRotor::rotor::body_forces(double a_ic, double b_ic)
     551                 :            : {
     552                 :            :     FGColumnVector3 F_s(
     553                 :            :           - H_drag*cos(beta_orient) - J_side*sin(beta_orient) + Thrust*b_ic,
     554                 :            :           - H_drag*sin(beta_orient) + J_side*cos(beta_orient) + Thrust*a_ic,
     555                 :          0 :           - Thrust);    
     556                 :            : 
     557 [ #  # ][ #  # ]:          0 :     if (dump_req && (flags & eMain) ) {
     558                 :          0 :       printf("# abß:  % f % f % f\n", a_ic, b_ic, beta_orient );
     559                 :          0 :       printf("# HJT:  % .2f % .2f % .2f\n", H_drag, J_side, Thrust );
     560                 :          0 :       printf("# F_s:  % .2f % .2f % .2f\n", F_s(1), F_s(2), F_s(3) );
     561                 :          0 :       FGColumnVector3 F_h;   
     562                 :          0 :       F_h = ShaftToBody * F_s;
     563                 :          0 :       printf("# F_h:  % .2f % .2f % .2f\n", F_h(1), F_h(2), F_h(3) );
     564                 :            :     }
     565                 :            : 
     566                 :          0 :     return ShaftToBody * F_s;
     567                 :            : }
     568                 :            : 
     569                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     570                 :            : 
     571                 :            : // rotational sense is handled here
     572                 :            : // still a todo: how to get propper values for 'BladeMassMoment'
     573                 :            : // here might be a good place to tweak hovering stability, check /AM50/.
     574                 :            : 
     575                 :          0 : FGColumnVector3 FGRotor::rotor::body_moments(FGColumnVector3 F_h, double a_ic, double b_ic)
     576                 :            : {
     577                 :          0 :   FGColumnVector3 M_s, M_hub, M_h;
     578                 :            :   
     579                 :          0 :   FGColumnVector3 h_pos(RelDistance_xhub, 0.0, RelHeight_zhub);
     580                 :            : 
     581                 :            :   // vermutlich ein biege moment, bzw.widerstands moment ~ d^3
     582                 :          0 :   double M_w_tilde = 0.0 ;
     583                 :          0 :   double mf = 0.0 ;
     584                 :            :  
     585                 :          0 :   M_w_tilde = BladeMassMoment;
     586                 :            : 
     587                 :            :   // cyclic flapping relative to shaft axes /SH79/ eqn(43)
     588                 :          0 :   a1s = a_1*cos(beta_orient) + b_1*sin(beta_orient) - b_ic;
     589                 :          0 :   b1s = b_1*cos(beta_orient) - a_1*sin(beta_orient) + a_ic;
     590                 :            : 
     591                 :            :   // mind this: no HingeOffset, no additional pitch/roll moments
     592                 :          0 :   mf = 0.5 * (HingeOffset+HingeOffset_hover) * BladeNum * Omega*Omega * M_w_tilde;
     593                 :          0 :   M_s(eL) = mf*b1s;
     594                 :          0 :   M_s(eM) = mf*a1s;
     595                 :          0 :   M_s(eN) = Torque;
     596                 :            : 
     597         [ #  # ]:          0 :   if (flags & eRotCW) {
     598                 :          0 :     M_s(eN) = -M_s(eN);
     599                 :            :   }
     600                 :            : 
     601         [ #  # ]:          0 :   if (flags & eCoaxial) {
     602                 :          0 :     M_s(eN) = 0.0;
     603                 :            :   }
     604                 :            : 
     605                 :          0 :   M_hub = ShaftToBody * M_s;
     606                 :            : 
     607                 :          0 :   M_h = M_hub + (h_pos * F_h);
     608                 :            : 
     609                 :          0 :   return M_h;
     610                 :            : }
     611                 :            : 
     612                 :            : 
     613                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     614                 :            : 
     615                 :            : // Constructor
     616                 :            : 
     617                 :          0 : FGRotor::FGRotor(FGFDMExec *exec, Element* rotor_element, int num)
     618                 :          0 :                     : FGThruster(exec, rotor_element, num)
     619                 :            : {
     620                 :            : 
     621                 :          0 :   FGColumnVector3 location, orientation;
     622                 :            :   Element *thruster_element;
     623                 :            : 
     624                 :          0 :   PropertyManager = fdmex->GetPropertyManager();
     625                 :          0 :   dt = fdmex->GetDeltaT();
     626                 :            : 
     627                 :            :   /* apply defaults */
     628                 :            : 
     629                 :          0 :   rho = 0.002356; // just a sane value
     630                 :            :   
     631                 :          0 :   RPM = 0.0;
     632                 :          0 :   Sense = 1.0;
     633                 :          0 :   tailRotorPresent = false;
     634                 :            :   
     635                 :          0 :   effective_tail_col = 0.001; // just a sane value 
     636                 :            : 
     637                 :          0 :   prop_inflow_ratio_lambda =  0.0;
     638                 :          0 :   prop_advance_ratio_mu = 0.0; 
     639                 :          0 :   prop_inflow_ratio_induced_nu = 0.0;
     640                 :          0 :   prop_mr_torque = 0.0;
     641                 :          0 :   prop_thrust_coefficient = 0.0;
     642                 :          0 :   prop_coning_angle = 0.0;
     643                 :            : 
     644                 :          0 :   prop_theta_downwash = prop_phi_downwash = 0.0;
     645                 :            : 
     646                 :          0 :   hover_threshold = 0.0;
     647                 :          0 :   hover_scale = 0.0;
     648                 :            : 
     649                 :          0 :   mr.zero();
     650                 :          0 :   tr.zero();
     651                 :            : 
     652                 :            :   // debug stuff
     653                 :          0 :   prop_DumpFlag = 0;
     654                 :            : 
     655                 :            :   /* configure */
     656                 :            : 
     657                 :          0 :   Type = ttRotor;
     658                 :          0 :   SetTransformType(FGForce::tCustom);
     659                 :            : 
     660                 :            :   // get data from parent and 'mount' the rotor system
     661                 :            : 
     662                 :          0 :   thruster_element = rotor_element->GetParent()->FindElement("sense");
     663   [ #  #  #  # ]:          0 :   if (thruster_element) {
     664 [ #  # ][ #  # ]:          0 :     Sense = thruster_element->GetDataAsNumber() >= 0.0 ? 1.0: -1.0;
     665                 :            :   }
     666                 :            : 
     667                 :          0 :   thruster_element = rotor_element->GetParent()->FindElement("location");
     668 [ #  # ][ #  # ]:          0 :   if (thruster_element)  location = thruster_element->FindElementTripletConvertTo("IN");
     669                 :          0 :   else          cerr << "No thruster location found." << endl;
     670                 :            : 
     671                 :          0 :   thruster_element = rotor_element->GetParent()->FindElement("orient");
     672 [ #  # ][ #  # ]:          0 :   if (thruster_element)  orientation = thruster_element->FindElementTripletConvertTo("RAD");
     673                 :          0 :   else          cerr << "No thruster orientation found." << endl;
     674                 :            : 
     675                 :          0 :   SetLocation(location);
     676                 :          0 :   SetAnglesToBody(orientation);
     677                 :            : 
     678                 :            :   // get main rotor parameters 
     679                 :          0 :   mr.parent = rotor_element;
     680                 :            : 
     681                 :          0 :   int flags = eMain;
     682                 :            : 
     683                 :          0 :   string a_val="";
     684                 :          0 :   a_val = rotor_element->GetAttributeValue("variant");
     685   [ #  #  #  # ]:          0 :   if ( a_val == "coaxial" ) {
     686                 :          0 :     flags += eCoaxial;
     687                 :          0 :     cerr << "# found 'coaxial' variant" << endl;
     688                 :            :   }  
     689                 :            : 
     690 [ #  # ][ #  # ]:          0 :   if (Sense<0.0) {
     691                 :          0 :     flags += eRotCW;
     692                 :            :   }
     693                 :            :     
     694                 :          0 :   mr.configure(flags);
     695                 :            : 
     696                 :          0 :   mr.rk.init(0,dt,6);
     697                 :            : 
     698                 :            :   // get tail rotor parameters
     699                 :          0 :   tr.parent=rotor_element->FindElement("tailrotor");
     700   [ #  #  #  # ]:          0 :   if (tr.parent) {
     701                 :          0 :     tailRotorPresent = true;
     702                 :            :   } else {
     703                 :          0 :     tailRotorPresent = false;
     704                 :          0 :     cerr << "# No tailrotor found, assuming a single rotor." << endl;
     705                 :            :   }
     706                 :            : 
     707 [ #  # ][ #  # ]:          0 :   if (tailRotorPresent) {
     708                 :          0 :     int flags = eTail;
     709 [ #  # ][ #  # ]:          0 :     if (Sense<0.0) {
     710                 :          0 :       flags += eRotCW;
     711                 :            :     }
     712                 :          0 :     tr.configure(flags, &mr);
     713                 :          0 :     tr.rk.init(0,dt,6);
     714                 :          0 :     tr.RpmRatio = tr.NominalRPM/mr.NominalRPM; // 'connect'
     715                 :            :   }
     716                 :            : 
     717                 :            :   /* remaining parameters */
     718                 :            : 
     719                 :            :   // ground effect  
     720                 :          0 :   double c_ground_effect = 0.0;  // uh1 ~ 0.28 , larger values increase the effect
     721                 :          0 :   ground_effect_exp = 0.0;  
     722                 :          0 :   ground_effect_shift = 0.0;
     723                 :            : 
     724 [ #  # ][ #  # ]:          0 :   if (rotor_element->FindElement("cgroundeffect"))
     725                 :          0 :     c_ground_effect = rotor_element->FindElementValueAsNumber("cgroundeffect");
     726                 :            : 
     727 [ #  # ][ #  # ]:          0 :   if (rotor_element->FindElement("groundeffectshift"))
     728                 :          0 :     ground_effect_shift = rotor_element->FindElementValueAsNumberConvertTo("groundeffectshift","FT");
     729                 :            : 
     730                 :            :   // prepare calculations, see /TA77/
     731 [ #  # ][ #  # ]:          0 :   if (c_ground_effect > 1e-5) {
     732                 :          0 :     ground_effect_exp = 1.0 / ( 2.0*mr.Radius * c_ground_effect );
     733                 :            :   } else {
     734                 :          0 :     ground_effect_exp = 0.0; // disable
     735                 :            :   }
     736                 :            : 
     737                 :            :   // smooth out jumps in hagl reported, otherwise the ground effect
     738                 :            :   // calculation would cause jumps too. 1Hz seems sufficient.
     739                 :          0 :   damp_hagl = Filter(1.0,dt);
     740                 :            : 
     741                 :            : 
     742                 :            :   // misc, experimental
     743 [ #  # ][ #  # ]:          0 :   if (rotor_element->FindElement("hoverthreshold"))
     744                 :          0 :     hover_threshold = rotor_element->FindElementValueAsNumberConvertTo("hoverthreshold", "FT/SEC");
     745                 :            : 
     746 [ #  # ][ #  # ]:          0 :   if (rotor_element->FindElement("hoverscale"))
     747                 :          0 :     hover_scale = rotor_element->FindElementValueAsNumber("hoverscale");
     748                 :            : 
     749                 :            :   // enable import-export
     750                 :          0 :   bind();
     751                 :            : 
     752                 :            :   // unused right now
     753                 :          0 :   prop_rotorbrake->setDoubleValue(0.0);
     754                 :          0 :   prop_freewheel_factor->setDoubleValue(1.0);  
     755                 :            : 
     756                 :          0 :   Debug(0);
     757                 :            : 
     758                 :          0 : }  // Constructor
     759                 :            : 
     760                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     761                 :            : 
     762                 :          0 : FGRotor::~FGRotor()
     763                 :            : {
     764                 :          0 :   Debug(1);
     765 [ #  # ][ #  # ]:          0 : }
                 [ #  # ]
     766                 :            : 
     767                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     768                 :            : 
     769                 :            : // mea-culpa - the connection to the engine might be wrong, but the calling
     770                 :            : // convention appears to be 'variable' too.
     771                 :            : //   piston call:  
     772                 :            : //        return Thruster->Calculate((Eng_HP * hptoftlbssec)-Thruster->GetPowerRequired());
     773                 :            : //   turbine call: 
     774                 :            : //        Thrust = Thruster->Calculate(Thrust); // allow thruster to modify thrust (i.e. reversing)
     775                 :            : //
     776                 :            : // Here 'Calculate' takes thrust and estimates the power provided.
     777                 :            : 
     778                 :          0 : double FGRotor::Calculate(double PowerAvailable)
     779                 :            : {
     780                 :            :   // controls
     781                 :            :   double A_IC;       // lateral (roll) control in radians
     782                 :            :   double B_IC;       // longitudinal (pitch) control in radians
     783                 :            :   double theta_col;  // main rotor collective pitch in radians
     784                 :            :   double tail_col;   // tail rotor collective in radians
     785                 :            : 
     786                 :            :   // state
     787                 :          0 :   double h_agl_ft = 0.0;
     788                 :            :   double Vt ;
     789                 :            : 
     790                 :          0 :   FGColumnVector3 UVW_h;
     791                 :          0 :   FGColumnVector3 PQR_h;
     792                 :            : 
     793                 :            :   /* total vehicle velocity including wind effects in feet per second. */
     794                 :          0 :   Vt = fdmex->GetAuxiliary()->GetVt();
     795                 :            : 
     796                 :          0 :   dt = fdmex->GetDeltaT(); // might be variable ?
     797                 :            : 
     798                 :          0 :   dump_req = prop_DumpFlag;
     799                 :          0 :   prop_DumpFlag = 0;
     800                 :            : 
     801                 :            :   // fetch often used values
     802                 :          0 :   rho = fdmex->GetAtmosphere()->GetDensity(); // slugs/ft^3.
     803                 :            : 
     804                 :          0 :   UVW_h = fdmex->GetAuxiliary()->GetAeroUVW();
     805                 :          0 :   PQR_h = fdmex->GetAuxiliary()->GetAeroPQR();
     806                 :            : 
     807                 :            :   // handle present RPM now, calc omega values.
     808                 :            : 
     809         [ #  # ]:          0 :   if (RPM < mr.MinRPM) { // kludge, otherwise calculations go bananas 
     810                 :          0 :     RPM = mr.MinRPM;
     811                 :            :   }
     812                 :            : 
     813                 :          0 :   mr.ActualRPM = RPM;
     814                 :          0 :   mr.Omega = (RPM/60.0)*2.0*M_PI;
     815                 :            : 
     816         [ #  # ]:          0 :   if (tailRotorPresent) {
     817                 :          0 :     tr.ActualRPM = RPM*tr.RpmRatio;
     818                 :          0 :     tr.Omega = (RPM*tr.RpmRatio/60.0)*2.0*M_PI;
     819                 :            :   }
     820                 :            : 
     821                 :            :   // read control inputs
     822                 :            : 
     823                 :          0 :   A_IC      = prop_lateral_ctrl->getDoubleValue();
     824                 :          0 :   B_IC      = prop_longitudinal_ctrl->getDoubleValue();
     825                 :          0 :   theta_col = prop_collective_ctrl->getDoubleValue();
     826                 :          0 :   tail_col  = 0.0;
     827         [ #  # ]:          0 :   if (tailRotorPresent) {
     828                 :          0 :     tail_col  = prop_antitorque_ctrl->getDoubleValue();
     829                 :            :   }
     830                 :            : 
     831                 :            : 
     832                 :          0 :   FGColumnVector3  vHub_ca = mr.hub_vel_body2ca(UVW_h,PQR_h,A_IC,B_IC);
     833                 :          0 :   FGColumnVector3 avFus_ca = mr.fus_angvel_body2ca(PQR_h);
     834                 :            : 
     835                 :            : 
     836                 :          0 :   h_agl_ft = fdmex->GetPropagate()->GetDistanceAGL();
     837                 :            : 
     838                 :            :   double filtered_hagl;
     839                 :          0 :   filtered_hagl = damp_hagl.execute( h_agl_ft + ground_effect_shift );
     840                 :            : 
     841                 :            :   // gnuplot> plot [-1:50] 1 - exp((-x/44)/.28)
     842                 :          0 :   double ge_factor = 1.0;  
     843         [ #  # ]:          0 :   if (ground_effect_exp > 1e-5) {
     844                 :          0 :     ge_factor -= exp(-filtered_hagl*ground_effect_exp);
     845                 :            :   }
     846                 :            :   // clamp
     847         [ #  # ]:          0 :   if (ge_factor<0.5) ge_factor=0.5;
     848                 :            : 
     849         [ #  # ]:          0 :   if (dump_req) {
     850                 :          0 :     printf("# GE h: %.3f  (%.3f) f: %f\n", filtered_hagl, h_agl_ft + ground_effect_shift, ge_factor);
     851                 :            :   }
     852                 :            : 
     853                 :            : 
     854                 :            :   // EXPERIMENTAL: modify rotor for hover, see rotor::body_moments for the consequences
     855 [ #  # ][ #  # ]:          0 :   if (hover_threshold > 1e-5 && Vt < hover_threshold) {
     856                 :          0 :     double scale = 1.0 - Vt/hover_threshold;
     857                 :          0 :     mr.HingeOffset_hover = scale*hover_scale*mr.Radius;
     858                 :            :   } else {
     859                 :          0 :     mr.HingeOffset_hover = 0.0;
     860                 :            :   }
     861                 :            : 
     862                 :            :   // all set, start calculations
     863                 :            : 
     864                 :            :   /* MAIN ROTOR */
     865                 :            : 
     866                 :          0 :   mr.calc_flow_and_thrust(dt, rho, theta_col, vHub_ca(eU), vHub_ca(eW), ge_factor);
     867                 :            : 
     868                 :          0 :   prop_inflow_ratio_lambda = mr.lambda;
     869                 :          0 :   prop_advance_ratio_mu = mr.mu;
     870                 :          0 :   prop_inflow_ratio_induced_nu = mr.nu;
     871                 :          0 :   prop_thrust_coefficient = mr.Ct;
     872                 :            : 
     873                 :          0 :   mr.calc_coning_angle(rho, theta_col);
     874                 :          0 :   prop_coning_angle = mr.a0;
     875                 :            : 
     876                 :          0 :   mr.calc_torque(rho, theta_col);
     877                 :          0 :   prop_mr_torque = mr.Torque;
     878                 :            : 
     879                 :          0 :   mr.calc_flapping_angles(rho, theta_col, avFus_ca);
     880                 :          0 :   mr.calc_drag_and_side_forces(rho, theta_col);
     881                 :            : 
     882                 :          0 :   FGColumnVector3 F_h_mr = mr.body_forces(A_IC,B_IC);
     883                 :          0 :   FGColumnVector3 M_h_mr = mr.body_moments(F_h_mr, A_IC, B_IC); 
     884                 :            : 
     885                 :            :   // export downwash angles
     886                 :            :   // theta: positive for downwash moving from +z_h towards +x_h
     887                 :            :   // phi:   positive for downwash moving from +z_h towards -y_h
     888                 :            : 
     889                 :          0 :   prop_theta_downwash = atan2( - UVW_h(eU), mr.v_induced - UVW_h(eW));
     890                 :          0 :   prop_phi_downwash   = atan2(   UVW_h(eV), mr.v_induced - UVW_h(eW));
     891                 :            : 
     892                 :          0 :   mr.force(eX) = F_h_mr(1);
     893                 :          0 :   mr.force(eY) = F_h_mr(2);
     894                 :          0 :   mr.force(eZ) = F_h_mr(3);
     895                 :            : 
     896                 :          0 :   mr.moment(eL) =  M_h_mr(1);
     897                 :          0 :   mr.moment(eM) =  M_h_mr(2); 
     898                 :          0 :   mr.moment(eN) =  M_h_mr(3);
     899                 :            : 
     900                 :            :   /* TAIL ROTOR */
     901                 :            : 
     902                 :            :   FGColumnVector3 F_h_tr(0.0, 0.0, 0.0);
     903                 :            :   FGColumnVector3 M_h_tr(0.0, 0.0, 0.0);
     904                 :            : 
     905         [ #  # ]:          0 :   if (tailRotorPresent) {
     906                 :          0 :     FGColumnVector3  vHub_ca_tr = tr.hub_vel_body2ca(UVW_h,PQR_h);
     907                 :          0 :     FGColumnVector3 avFus_ca_tr = tr.fus_angvel_body2ca(PQR_h);
     908                 :            : 
     909                 :          0 :     tr.calc_flow_and_thrust(dt, rho, tail_col, vHub_ca_tr(eU), vHub_ca_tr(eW));
     910                 :          0 :     tr.calc_coning_angle(rho, tail_col);
     911                 :            : 
     912                 :            :     // test code for cantered tail rotor, see if it has a notable effect. /SH79/ eqn(47)
     913         [ #  # ]:          0 :     if (fabs(tr.CantAngleD3)>1e-5) {
     914                 :          0 :       double tan_d3 = tan(tr.CantAngleD3);
     915                 :            :       double d_t0t;
     916                 :          0 :       double ca_dt = dt/12.0;
     917         [ #  # ]:          0 :       for (int i = 0; i<12; i++) {
     918                 :          0 :         d_t0t = 1/0.1*(tail_col - tr.a0 * tan_d3 - effective_tail_col);
     919                 :          0 :         effective_tail_col += d_t0t*ca_dt;
     920                 :            :       }
     921                 :            :     } else {
     922                 :          0 :       effective_tail_col = tail_col;
     923                 :            :     }
     924                 :            : 
     925                 :          0 :     tr.calc_torque(rho, effective_tail_col);
     926                 :          0 :     tr.calc_flapping_angles(rho, effective_tail_col, avFus_ca_tr);
     927                 :          0 :     tr.calc_drag_and_side_forces(rho, effective_tail_col);
     928                 :            : 
     929                 :          0 :     F_h_tr = tr.body_forces();
     930                 :          0 :     M_h_tr = tr.body_moments(F_h_tr); 
     931                 :            :   }
     932                 :            : 
     933                 :          0 :   tr.force(eX)  =   F_h_tr(1) ;
     934                 :          0 :   tr.force(eY)  =   F_h_tr(2) ;
     935                 :          0 :   tr.force(eZ)  =   F_h_tr(3) ;
     936                 :          0 :   tr.moment(eL) =   M_h_tr(1) ;
     937                 :          0 :   tr.moment(eM) =   M_h_tr(2) ;
     938                 :          0 :   tr.moment(eN) =   M_h_tr(3) ;
     939                 :            : 
     940                 :            : /* 
     941                 :            :     TODO: 
     942                 :            :       check negative mr.Torque conditions
     943                 :            :       freewheel factor: assure [0..1] just multiply with available power
     944                 :            :       rotorbrake: just steal from available power
     945                 :            : 
     946                 :            : */
     947                 :            : 
     948                 :            :   // calculate new RPM, assuming a stiff connection between engine and rotor. 
     949                 :            : 
     950                 :          0 :   double engine_hp     =  PowerAvailable/2.24; // 'undo' force via the estimation factor used in aeromatic
     951                 :          0 :   double engine_torque =  550.0*engine_hp/mr.Omega;
     952                 :          0 :   double Omega_dot     = (engine_torque - mr.Torque) / mr.PolarMoment;
     953                 :            : 
     954                 :          0 :   RPM += ( Omega_dot * dt )/(2.0*M_PI) * 60.0;
     955                 :            : 
     956                 :            :   if (0 && dump_req) {
     957                 :            :     printf("# SENSE      :  % d % d\n", mr.flags & eRotCW ? -1 : 1, tr.flags & eRotCW ? -1 : 1);
     958                 :            :     printf("# vi         :  % f % f\n", mr.v_induced, tr.v_induced);
     959                 :            :     printf("# a0 a1 b1   :  % f % f % f\n", mr.a0, mr.a_1, mr.b_1 );
     960                 :            :     printf("# m  forces  :  % f % f % f\n", mr.force(eX), mr.force(eY), mr.force(eZ) );
     961                 :            :     printf("# m  moments :  % f % f % f\n", mr.moment(eL), mr.moment(eM), mr.moment(eN) );
     962                 :            :     printf("# t  forces  :  % f % f % f\n", tr.force(eX), tr.force(eY), tr.force(eZ) );
     963                 :            :     printf("# t  moments :  % f % f % f\n", tr.moment(eL), tr.moment(eM), tr.moment(eN) );
     964                 :            :   }
     965                 :            : 
     966                 :            :   // finally set vFn & vMn
     967                 :          0 :   vFn = mr.force  + tr.force;
     968                 :          0 :   vMn = mr.moment + tr.moment;
     969                 :            : 
     970                 :            :   // and just lie here
     971                 :          0 :   Thrust = 0.0; 
     972                 :            : 
     973                 :            :   // return unmodified thrust to the turbine. 
     974                 :            :   // :TK: As far as I can see the return value is unused.
     975                 :          0 :   return PowerAvailable;
     976                 :            : 
     977                 :            : }  // Calculate
     978                 :            : 
     979                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     980                 :            : 
     981                 :            : // FGThruster does return 0.0 (the implicit direct thruster)
     982                 :            : // piston CALL:  return Thruster->Calculate((Eng_HP * hptoftlbssec)-Thruster->GetPowerRequired());
     983                 :            : 
     984                 :          0 : double FGRotor::GetPowerRequired(void)
     985                 :            : {
     986                 :          0 :   PowerRequired = 0.0;
     987                 :          0 :   return PowerRequired;
     988                 :            : }
     989                 :            : 
     990                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     991                 :            : 
     992                 :          0 : bool FGRotor::bind(void) {
     993                 :            : 
     994                 :          0 :   string property_name, base_property_name;
     995                 :          0 :   base_property_name = CreateIndexedPropertyName("propulsion/engine", EngineNum);
     996                 :            : 
     997                 :          0 :   PropertyManager->Tie( base_property_name + "/rotor-rpm", this, &FGRotor::GetRPM );
     998                 :          0 :   PropertyManager->Tie( base_property_name + "/thrust-mr-lbs", &mr.Thrust );
     999                 :          0 :   PropertyManager->Tie( base_property_name + "/vi-mr-fps", &mr.v_induced );
    1000                 :          0 :   PropertyManager->Tie( base_property_name + "/a0-mr-rad", &mr.a0 );
    1001                 :          0 :   PropertyManager->Tie( base_property_name + "/a1-mr-rad", &mr.a1s ); // s means shaft axes
    1002                 :          0 :   PropertyManager->Tie( base_property_name + "/b1-mr-rad", &mr.b1s );
    1003                 :          0 :   PropertyManager->Tie( base_property_name + "/thrust-tr-lbs", &tr.Thrust );
    1004                 :          0 :   PropertyManager->Tie( base_property_name + "/vi-tr-fps", &tr.v_induced );
    1005                 :            : 
    1006                 :            :   // lambda
    1007                 :          0 :   PropertyManager->Tie( base_property_name + "/inflow-ratio", &prop_inflow_ratio_lambda );
    1008                 :            :   // mu
    1009                 :          0 :   PropertyManager->Tie( base_property_name + "/advance-ratio", &prop_advance_ratio_mu );
    1010                 :            :   // nu
    1011                 :          0 :   PropertyManager->Tie( base_property_name + "/induced-inflow-ratio", &prop_inflow_ratio_induced_nu );
    1012                 :            : 
    1013                 :          0 :   PropertyManager->Tie( base_property_name + "/torque-mr-lbsft", &prop_mr_torque );
    1014                 :          0 :   PropertyManager->Tie( base_property_name + "/thrust-coefficient", &prop_thrust_coefficient );
    1015                 :          0 :   PropertyManager->Tie( base_property_name + "/main-rotor-rpm", &mr.ActualRPM );
    1016                 :          0 :   PropertyManager->Tie( base_property_name + "/tail-rotor-rpm", &tr.ActualRPM );
    1017                 :            : 
    1018                 :            :   // position of the downwash
    1019                 :          0 :   PropertyManager->Tie( base_property_name + "/theta-downwash-rad", &prop_theta_downwash );
    1020                 :          0 :   PropertyManager->Tie( base_property_name + "/phi-downwash-rad", &prop_phi_downwash );  
    1021                 :            : 
    1022                 :            :   // nodes to use via get<xyz>Value
    1023                 :          0 :   prop_collective_ctrl = PropertyManager->GetNode(base_property_name + "/collective-ctrl-rad",true);
    1024                 :          0 :   prop_lateral_ctrl = PropertyManager->GetNode(base_property_name + "/lateral-ctrl-rad",true);
    1025                 :          0 :   prop_longitudinal_ctrl = PropertyManager->GetNode(base_property_name + "/longitudinal-ctrl-rad",true);
    1026                 :          0 :   prop_antitorque_ctrl =   PropertyManager->GetNode(base_property_name + "/antitorque-ctrl-rad",true);
    1027                 :            : 
    1028                 :          0 :   prop_rotorbrake =   PropertyManager->GetNode(base_property_name + "/rotorbrake-hp", true);
    1029                 :          0 :   prop_freewheel_factor =   PropertyManager->GetNode(base_property_name + "/freewheel-factor", true);
    1030                 :            : 
    1031                 :          0 :   PropertyManager->Tie( base_property_name + "/dump-flag", &prop_DumpFlag );
    1032                 :            : 
    1033                 :          0 :   return true;
    1034                 :            : }
    1035                 :            : 
    1036                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    1037                 :            : 
    1038                 :          0 : string FGRotor::GetThrusterLabels(int id, string delimeter)
    1039                 :            : {
    1040                 :            : 
    1041                 :          0 :   std::ostringstream buf;
    1042                 :            : 
    1043                 :          0 :   buf << Name << " RPM (engine " << id << ")";
    1044                 :            : 
    1045                 :          0 :   return buf.str();
    1046                 :            : 
    1047                 :            : }
    1048                 :            : 
    1049                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    1050                 :            : 
    1051                 :          0 : string FGRotor::GetThrusterValues(int id, string delimeter)
    1052                 :            : {
    1053                 :          0 :   std::ostringstream buf;
    1054                 :            : 
    1055                 :          0 :   buf << RPM;
    1056                 :            : 
    1057                 :          0 :   return buf.str();
    1058                 :            : }
    1059                 :            : 
    1060                 :            : //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    1061                 :            : //    The bitmasked value choices are as follows:
    1062                 :            : //    unset: In this case (the default) JSBSim would only print
    1063                 :            : //       out the normally expected messages, essentially echoing
    1064                 :            : //       the config files as they are read. If the environment
    1065                 :            : //       variable is not set, debug_lvl is set to 1 internally
    1066                 :            : //    0: This requests JSBSim not to output any messages
    1067                 :            : //       whatsoever.
    1068                 :            : //    1: This value explicity requests the normal JSBSim
    1069                 :            : //       startup messages
    1070                 :            : //    2: This value asks for a message to be printed out when
    1071                 :            : //       a class is instantiated
    1072                 :            : //    4: When this value is set, a message is displayed when a
    1073                 :            : //       FGModel object executes its Run() method
    1074                 :            : //    8: When this value is set, various runtime state variables
    1075                 :            : //       are printed out periodically
    1076                 :            : //    16: When set various parameters are sanity checked and
    1077                 :            : //       a message is printed out when they go out of bounds
    1078                 :            : 
    1079                 :          0 : void FGRotor::Debug(int from)
    1080                 :            : {
    1081         [ #  # ]:          0 :   if (debug_lvl <= 0) return;
    1082                 :            : 
    1083         [ #  # ]:          0 :   if (debug_lvl & 1) { // Standard console startup message output
    1084         [ #  # ]:          0 :     if (from == 0) { // Constructor
    1085                 :          0 :       cout << "\n    Rotor Name: " << Name << endl;
    1086                 :            :     }
    1087                 :            :   }
    1088         [ #  # ]:          0 :   if (debug_lvl & 2 ) { // Instantiation/Destruction notification
    1089         [ #  # ]:          0 :     if (from == 0) cout << "Instantiated: FGRotor" << endl;
    1090         [ #  # ]:          0 :     if (from == 1) cout << "Destroyed:    FGRotor" << endl;
    1091                 :            :   }
    1092                 :          0 :   if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
    1093                 :            :   }
    1094                 :          0 :   if (debug_lvl & 8 ) { // Runtime state variables
    1095                 :            :   }
    1096                 :          0 :   if (debug_lvl & 16) { // Sanity checking
    1097                 :            :   }
    1098         [ #  # ]:          0 :   if (debug_lvl & 64) {
    1099         [ #  # ]:          0 :     if (from == 0) { // Constructor
    1100                 :          0 :       cout << IdSrc << endl;
    1101                 :          0 :       cout << IdHdr << endl;
    1102                 :            :     }
    1103                 :            :   }
    1104                 :            : }
    1105                 :            : 
    1106 [ +  + ][ +  - ]:         12 : } // namespace JSBSim 
    1107                 :            : 

Generated by: LCOV version 1.9