58using std::ostringstream;
66static inline double sqr(
double x) {
return x*x; }
77 Radius(0.0), BladeNum(0),
78 Sense(1.0), NominalRPM(0.0), MinimalRPM(0.0), MaximalRPM(0.0),
79 ExternalRPM(0), RPMdefinition(0), ExtRPMsource(NULL), SourceGearRatio(1.0),
80 BladeChord(0.0), LiftCurveSlope(0.0), BladeTwist(0.0), HingeOffset(0.0),
81 BladeFlappingMoment(0.0), BladeMassMoment(0.0), PolarMoment(0.0),
82 InflowLag(0.0), TipLossB(0.0),
83 GroundEffectExp(0.0), GroundEffectShift(0.0), GroundEffectScaleNorm(1.0),
84 LockNumberByRho(0.0), Solidity(0.0),
87 a0(0.0), a_1(0.0), b_1(0.0), a_dw(0.0),
89 H_drag(0.0), J_side(0.0), Torque(0.0), C_T(0.0),
90 lambda(-0.001), mu(0.0), nu(0.001), v_induced(0.0),
91 theta_downwash(0.0), phi_downwash(0.0),
92 ControlMap(eMainCtrl),
93 CollectiveCtrl(0.0), LateralCtrl(0.0), LongitudinalCtrl(0.0),
95 EngineRPM(0.0), MaxBrakePower(0.0), GearLoss(0.0), GearMoment(0.0)
99 double engine_power_est = 0.0;
107 for (
int i=0;
i<5;
i++) R[
i] = 0.0;
108 for (
int i=0;
i<5;
i++) B[
i] = 0.0;
112 if (thruster_element) {
116 }
else if (s < 0.1) {
124 if (thruster_element) {
127 cerr <<
"No thruster location found." << endl;
131 if (thruster_element) {
134 cerr <<
"No thruster orientation found." << endl;
142 ControlMap = eMainCtrl;
147 ControlMap = eTailCtrl;
148 }
else if (cm ==
"TANDEM") {
149 ControlMap = eTandemCtrl;
151 cerr <<
"# found unknown controlmap: '" << cm <<
"' using main rotor config." << endl;
158 SourceGearRatio = 1.0;
160 int rdef = RPMdefinition;
161 if (RPMdefinition>=0) {
171 if (RPMdefinition != rdef) {
172 cerr <<
"# discarded given RPM source (" << rdef <<
") and switched to external control (-1)." << endl;
177 engine_power_est = Configure(rotor_element);
184 Transmission->SetThrusterMoment(PolarMoment);
187 GearMoment = ConfigValueConv(rotor_element,
"gearmoment", 0.1*PolarMoment,
"SLUG*FT2");
188 GearMoment =
Constrain(1e-6, GearMoment, 1e9);
189 Transmission->SetEngineMoment(GearMoment);
191 Transmission->SetMaxBrakePower(MaxBrakePower);
193 GearLoss = ConfigValueConv(rotor_element,
"gearloss", 0.0025 * engine_power_est,
"HP");
194 GearLoss =
Constrain(0.0, GearLoss, 1e9);
196 Transmission->SetEngineFriction(GearLoss);
202 TboToHsr = { 0.0, 0.0, 1.0,
205 HsrToTbo = TboToHsr.Transposed();
209 damp_hagl =
Filter(1.0, dt);
221 if (Transmission)
delete Transmission;
229double FGRotor::ConfigValueConv(
Element* el,
const string& ename,
double default_val,
230 const string& unit,
bool tell)
234 double val = default_val;
236 string pname =
"*No parent element*";
251 cerr << pname <<
": missing element '" << ename <<
252 "' using estimated value: " << default_val << endl;
261double FGRotor::ConfigValue(
Element* el,
const string& ename,
double default_val,
bool tell)
263 return ConfigValueConv(el, ename, default_val,
"", tell);
270double FGRotor::Configure(
Element* rotor_element)
273 double estimate, engine_power_est=0.0;
274 const bool yell =
true;
275 const bool silent =
false;
278 Radius = 0.5 * ConfigValueConv(rotor_element,
"diameter", 42.0,
"FT", yell);
281 BladeNum = (int) ConfigValue(rotor_element,
"numblades", 3 , yell);
283 GearRatio = ConfigValue(rotor_element,
"gearratio", 1.0, yell);
287 estimate = (750.0/Radius)/(2.0*
M_PI) * 60.0;
288 NominalRPM = ConfigValue(rotor_element,
"nominalrpm", estimate, yell);
289 NominalRPM =
Constrain(2.0, NominalRPM, 1e9);
291 MinimalRPM = ConfigValue(rotor_element,
"minrpm", 1.0);
292 MinimalRPM =
Constrain(1.0, MinimalRPM, NominalRPM - 1.0);
294 MaximalRPM = ConfigValue(rotor_element,
"maxrpm", 2.0*NominalRPM);
295 MaximalRPM =
Constrain(NominalRPM, MaximalRPM, 1e9);
297 estimate =
Constrain(0.07, 2.0/Radius , 0.14);
298 estimate = estimate *
M_PI*Radius/BladeNum;
299 BladeChord = ConfigValueConv(rotor_element,
"chord", estimate,
"FT", yell);
301 LiftCurveSlope = ConfigValue(rotor_element,
"liftcurveslope", 6.0);
302 BladeTwist = ConfigValueConv(rotor_element,
"twist", -0.17,
"RAD");
304 HingeOffset = ConfigValueConv(rotor_element,
"hingeoffset", 0.05 * Radius,
"FT" );
306 estimate =
sqr(BladeChord) *
sqr(Radius - HingeOffset) * 0.57;
307 BladeFlappingMoment = ConfigValueConv(rotor_element,
"flappingmoment", estimate,
"SLUG*FT2");
308 BladeFlappingMoment =
Constrain(1e-9, BladeFlappingMoment, 1e9);
311 estimate = ( 3.0 * BladeFlappingMoment /
sqr(Radius) ) * (0.45 * Radius) ;
312 BladeMassMoment = ConfigValue(rotor_element,
"massmoment", estimate);
313 BladeMassMoment =
Constrain(1e-9, BladeMassMoment, 1e9);
315 estimate = 1.1 * BladeFlappingMoment * BladeNum;
316 PolarMoment = ConfigValueConv(rotor_element,
"polarmoment", estimate,
"SLUG*FT2");
317 PolarMoment =
Constrain(1e-9, PolarMoment, 1e9);
321 TipLossB = ConfigValue(rotor_element,
"tiplossfactor", 1.0, silent);
324 engine_power_est = 0.5 * BladeNum*BladeChord*Radius*Radius;
326 estimate = engine_power_est / 30.0;
327 MaxBrakePower = ConfigValueConv(rotor_element,
"maxbrakepower", estimate,
"HP");
330 GroundEffectExp = ConfigValue(rotor_element,
"groundeffectexp", 0.0);
331 GroundEffectShift = ConfigValueConv(rotor_element,
"groundeffectshift", 0.0,
"FT");
334 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];
335 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];
338 LockNumberByRho = LiftCurveSlope * BladeChord * R[4] / BladeFlappingMoment;
339 Solidity = BladeNum * BladeChord / (
M_PI * Radius);
342 double omega_tmp = (NominalRPM/60.0)*2.0*
M_PI;
343 estimate = 16.0/(LockNumberByRho*rho * omega_tmp );
345 InflowLag = ConfigValue(rotor_element,
"inflowlag", estimate, yell);
346 InflowLag =
Constrain(1e-6, InflowLag, 2.0);
348 return engine_power_est;
358 double a_ic,
double b_ic)
360 FGColumnVector3 v_r, v_shaft, v_w;
366 v_shaft = TboToHsr * InvTransform * v_r;
368 beta_orient = atan2(v_shaft(
eV),v_shaft(
eU));
370 v_w(
eU) = v_shaft(
eU)*cos(beta_orient) + v_shaft(
eV)*sin(beta_orient);
372 v_w(
eW) = v_shaft(
eW) - b_ic*v_shaft(
eU) - a_ic*v_shaft(
eV);
383 FGColumnVector3 av_s_fus, av_w_fus;
388 av_s_fus = TboToHsr * InvTransform * pqr;
390 av_w_fus(
eP)= av_s_fus(
eP)*cos(beta_orient) + av_s_fus(
eQ)*sin(beta_orient);
391 av_w_fus(
eQ)= - av_s_fus(
eP)*sin(beta_orient) + av_s_fus(
eQ)*cos(beta_orient);
392 av_w_fus(
eR)= av_s_fus(
eR);
407void FGRotor::calc_flow_and_thrust(
double theta_0,
double Uw,
double Ww,
411 double ct_over_sigma = 0.0;
412 double c0, ct_l, ct_t0, ct_t1;
415 mu = Uw/(Omega*Radius);
416 if (mu > 0.7) mu = 0.7;
419 ct_t0 = (1.0/3.0*B[3] + 1.0/2.0 * TipLossB*mu2 - 4.0/(9.0*
M_PI) * mu*mu2 ) * theta_0;
420 ct_t1 = (1.0/4.0*B[4] + 1.0/4.0 * B[2]*mu2) * BladeTwist;
422 ct_l = (1.0/2.0*B[2] + 1.0/4.0 * mu2) * lambda;
424 c0 = (LiftCurveSlope/2.0)*(ct_l + ct_t0 + ct_t1) * Solidity;
425 c0 = c0 / ( 2.0 * sqrt(
sqr(mu) +
sqr(lambda) ) + 1e-15);
431 nu = flow_scale * ((nu - c0) * exp(-dt/InflowLag) + c0);
435 lambda = Ww/(Omega*Radius) - nu;
437 ct_l = (1.0/2.0*B[2] + 1.0/4.0 * mu2) * lambda;
439 ct_over_sigma = (LiftCurveSlope/2.0)*(ct_l + ct_t0 + ct_t1);
441 Thrust = BladeNum*BladeChord*Radius*rho*
sqr(Omega*Radius) * ct_over_sigma;
443 C_T = ct_over_sigma * Solidity;
444 v_induced = nu * (Omega*Radius);
454void FGRotor::calc_coning_angle(
double theta_0)
456 double lock_gamma = LockNumberByRho * rho;
458 double a0_l = (1.0/6.0 + 0.04 * mu*mu*mu) * lambda;
459 double a0_t0 = (1.0/8.0 + 1.0/8.0 * mu*mu) * theta_0;
460 double a0_t1 = (1.0/10.0 + 1.0/12.0 * mu*mu) * BladeTwist;
461 a0 = lock_gamma * ( a0_l + a0_t0 + a0_t1);
469void FGRotor::calc_flapping_angles(
double theta_0,
const FGColumnVector3 &pqr_fus_w)
471 double lock_gamma = LockNumberByRho * rho;
474 double mu2_2 =
sqr(mu)/2.0;
475 double t075 = theta_0 + 0.75 * BladeTwist;
477 a_1 = 1.0/(1.0 - mu2_2) * (
478 (2.0*lambda + (8.0/3.0)*t075)*mu
479 + pqr_fus_w(
eP)/Omega
480 - 16.0 * pqr_fus_w(
eQ)/(lock_gamma*Omega)
483 b_1 = 1.0/(1.0 + mu2_2) * (
485 - pqr_fus_w(
eQ)/Omega
486 - 16.0 * pqr_fus_w(
eP)/(lock_gamma*Omega)
490 a_dw = 1.0/(1.0 - mu2_2) * (
491 (2.0*lambda + (8.0/3.0)*t075)*mu
492 - 24.0 * pqr_fus_w(
eQ)/(lock_gamma*Omega)
493 * ( 1.0 - ( 0.29 * t075 / (C_T/Solidity) ) )
503void FGRotor::calc_drag_and_side_forces(
double theta_0)
505 double cy_over_sigma;
506 double t075 = theta_0 + 0.75 * BladeTwist;
511 0.75*b_1*lambda - 1.5*a0*mu*lambda + 0.25*a_1*b_1*mu
512 - a0*a_1*
sqr(mu) + (1.0/6.0)*a0*a_1
513 - (0.75*mu*a0 - (1.0/3.0)*b_1 - 0.5*
sqr(mu)*b_1)*t075
515 cy_over_sigma *= LiftCurveSlope/2.0;
517 J_side = BladeNum * BladeChord * Radius * rho *
sqr(Omega*Radius) * cy_over_sigma;
528void FGRotor::calc_torque(
double theta_0)
531 double delta_dr = 0.009 + 0.3*
sqr(6.0*C_T/(LiftCurveSlope*Solidity));
533 Torque = rho * BladeNum * BladeChord * delta_dr *
sqr(Omega*Radius) * R[2] *
534 (1.0+4.5*
sqr(mu))/8.0
535 - (
Thrust*lambda + H_drag*mu)*Radius;
548void FGRotor::calc_downwash_angles()
550 FGColumnVector3 v_shaft;
551 v_shaft = TboToHsr * InvTransform *
in.AeroUVW;
553 theta_downwash = atan2( -v_shaft(
eU), v_induced - v_shaft(
eW)) + a1s;
554 phi_downwash = atan2( v_shaft(
eV), v_induced - v_shaft(
eW)) + b1s;
567 - H_drag*cos(beta_orient) - J_side*sin(beta_orient) +
Thrust*b_ic,
568 - H_drag*sin(beta_orient) + J_side*cos(beta_orient) +
Thrust*a_ic,
571 return HsrToTbo * F_s;
581 FGColumnVector3 M_s, M_hub, M_h;
585 a1s = a_1*cos(beta_orient) + b_1*sin(beta_orient) - b_ic;
586 b1s = b_1*cos(beta_orient) - a_1*sin(beta_orient) + a_ic;
588 mf = 0.5 * HingeOffset * BladeNum * Omega*Omega * BladeMassMoment;
592 M_s(
eN) = Torque * Sense ;
594 return HsrToTbo * M_s;
599void FGRotor::CalcRotorState(
void)
605 FGColumnVector3 vHub_ca, avFus_ca;
607 double filtered_hagl = 0.0;
608 double ge_factor = 1.0;
612 double h_agl_ft =
in.H_agl;
618 if (ExternalRPM && ExtRPMsource) {
619 RPM = ExtRPMsource->getDoubleValue() * ( SourceGearRatio /
GearRatio );
623 RPM =
Constrain(MinimalRPM, RPM, MaximalRPM);
625 Omega = (RPM/60.0)*2.0*
M_PI;
629 B_IC = LongitudinalCtrl;
630 theta_col = CollectiveCtrl;
634 if (GroundEffectExp > 1e-5) {
635 if (h_agl_ft<0.0) h_agl_ft = 0.0;
636 filtered_hagl = damp_hagl.execute(h_agl_ft) + GroundEffectShift;
638 ge_factor -= GroundEffectScaleNorm *
639 ( exp(-filtered_hagl*GroundEffectExp) * (RPM / NominalRPM) );
640 ge_factor =
Constrain(0.5, ge_factor, 1.0);
645 vHub_ca = hub_vel_body2ca(
in.AeroUVW,
in.AeroPQR, A_IC, B_IC);
647 avFus_ca = fus_angvel_body2ca(
in.AeroPQR);
649 calc_flow_and_thrust(theta_col, vHub_ca(
eU), vHub_ca(
eW), ge_factor);
651 calc_coning_angle(theta_col);
653 calc_flapping_angles(theta_col, avFus_ca);
655 calc_drag_and_side_forces(theta_col);
657 calc_torque(theta_col);
659 calc_downwash_angles();
663 vFn = body_forces(A_IC, B_IC);
677 Transmission->Calculate(EnginePower, Torque,
in.TotalDeltaT);
679 EngineRPM = Transmission->GetEngineRPM() *
GearRatio;
680 RPM = Transmission->GetThrusterRPM();
685 RPM =
Constrain(MinimalRPM, RPM, MaximalRPM);
696 string property_name, base_property_name;
699 property_name = base_property_name +
"/rotor-rpm";
702 property_name = base_property_name +
"/engine-rpm";
705 property_name = base_property_name +
"/a0-rad";
708 property_name = base_property_name +
"/a1-rad";
711 property_name = base_property_name +
"/b1-rad";
714 property_name = base_property_name +
"/inflow-ratio";
717 property_name = base_property_name +
"/advance-ratio";
720 property_name = base_property_name +
"/induced-inflow-ratio";
723 property_name = base_property_name +
"/vi-fps";
726 property_name = base_property_name +
"/thrust-coefficient";
729 property_name = base_property_name +
"/torque-lbsft";
732 property_name = base_property_name +
"/theta-downwash-rad";
735 property_name = base_property_name +
"/phi-downwash-rad";
738 property_name = base_property_name +
"/groundeffect-scale-norm";
742 switch (ControlMap) {
744 property_name = base_property_name +
"/antitorque-ctrl-rad";
748 property_name = base_property_name +
"/tail-collective-ctrl-rad";
750 property_name = base_property_name +
"/lateral-ctrl-rad";
752 property_name = base_property_name +
"/longitudinal-ctrl-rad";
756 property_name = base_property_name +
"/collective-ctrl-rad";
758 property_name = base_property_name +
"/lateral-ctrl-rad";
760 property_name = base_property_name +
"/longitudinal-ctrl-rad";
765 if (RPMdefinition == -1) {
766 property_name = base_property_name +
"/x-rpm-dict";
767 ExtRPMsource = PropertyManager->
GetNode(property_name,
true);
768 }
else if (RPMdefinition >= 0 && RPMdefinition !=
EngineNum) {
770 property_name = ipn +
"/rotor-rpm";
771 ExtRPMsource = PropertyManager->
GetNode(property_name,
false);
772 if (! ExtRPMsource) {
773 cerr <<
"# Warning: Engine number " <<
EngineNum <<
"." << endl;
774 cerr <<
"# No 'rotor-rpm' property found for engine " << RPMdefinition <<
"." << endl;
775 cerr <<
"# Please check order of engine definitons." << endl;
779 cerr <<
", given ExternalRPM value '" << RPMdefinition <<
"' unhandled." << endl;
793 buf <<
Name <<
" RPM (engine " <<
id <<
")";
831void FGRotor::Debug(
int from)
833 string ControlMapName;
839 cout <<
"\n Rotor Name: " <<
Name << endl;
840 cout <<
" Diameter = " << 2.0 * Radius <<
" ft." << endl;
841 cout <<
" Number of Blades = " << BladeNum << endl;
842 cout <<
" Gear Ratio = " <<
GearRatio << endl;
843 cout <<
" Sense = " << Sense << endl;
844 cout <<
" Nominal RPM = " << NominalRPM << endl;
845 cout <<
" Minimal RPM = " << MinimalRPM << endl;
846 cout <<
" Maximal RPM = " << MaximalRPM << endl;
849 if (RPMdefinition == -1) {
850 cout <<
" RPM is controlled externally" << endl;
852 cout <<
" RPM source set to thruster " << RPMdefinition << endl;
856 cout <<
" Blade Chord = " << BladeChord << endl;
857 cout <<
" Lift Curve Slope = " << LiftCurveSlope << endl;
858 cout <<
" Blade Twist = " << BladeTwist << endl;
859 cout <<
" Hinge Offset = " << HingeOffset << endl;
860 cout <<
" Blade Flapping Moment = " << BladeFlappingMoment << endl;
861 cout <<
" Blade Mass Moment = " << BladeMassMoment << endl;
862 cout <<
" Polar Moment = " << PolarMoment << endl;
863 cout <<
" Inflow Lag = " << InflowLag << endl;
864 cout <<
" Tip Loss = " << TipLossB << endl;
865 cout <<
" Lock Number = " << LockNumberByRho * 0.002356 <<
" (SL)" << endl;
866 cout <<
" Solidity = " << Solidity << endl;
867 cout <<
" Max Brake Power = " << MaxBrakePower/
hptoftlbssec <<
" HP" << endl;
868 cout <<
" Gear Loss = " << GearLoss/
hptoftlbssec <<
" HP" << endl;
869 cout <<
" Gear Moment = " << GearMoment << endl;
871 switch (ControlMap) {
872 case eTailCtrl: ControlMapName =
"Tail Rotor";
break;
873 case eTandemCtrl: ControlMapName =
"Tandem Rotor";
break;
874 default: ControlMapName =
"Main Rotor";
876 cout <<
" Control Mapping = " << ControlMapName << endl;
881 if (from == 0) cout <<
"Instantiated: FGRotor" << endl;
882 if (from == 1) cout <<
"Destroyed: FGRotor" << endl;
double FindElementValueAsNumberConvertTo(const std::string &el, const std::string &target_units)
Searches for the named element and converts and returns the data belonging to it.
const std::string & GetName(void) const
Retrieves the element name.
double FindElementValueAsNumber(const std::string &el="")
Searches for the named element and returns the data belonging to it as a number.
FGColumnVector3 FindElementTripletConvertTo(const std::string &target_units)
Composes a 3-element column vector for the supplied location or orientation.
std::string FindElementValue(const std::string &el="")
Searches for the named element and returns the string data belonging to it.
Element * GetParent(void)
Returns a pointer to the parent of an element.
double GetDataAsNumber(void)
Converts the element data to a number.
Element * FindElement(const std::string &el="")
Searches for a specified element.
This class implements a 3 element column vector.
FGThruster * GetThruster(void) const
double GetDeltaT(void) const
Returns the simulation delta T.
FGPropulsion * GetPropulsion(void)
Returns the FGPropulsion pointer.
FGPropertyManager * GetPropertyManager(void)
Returns a pointer to the property manager object.
void SetTransformType(TransformType ii)
const FGMatrix33 & Transform(void) const
const FGColumnVector3 & GetActingLocation(void) const
void SetAnglesToBody(double broll, double bpitch, double byaw)
void SetLocation(double x, double y, double z)
First order, (low pass / lag) filter.
static constexpr double Constrain(double min, double value, double max)
Constrain a value between a minimum and a maximum value.
static constexpr double hptoftlbssec
static std::string CreateIndexedPropertyName(const std::string &Property, int index)
FGMatrix33 Transposed(void) const
Transposed matrix.
FGPropertyNode * GetNode(void) const
void Tie(const std::string &name, T *pointer)
Tie a property to an external variable.
FGEngine * GetEngine(unsigned int index) const
Retrieves an engine object pointer from the list of engines.
~FGRotor()
Destructor for FGRotor.
double GetMu(void) const
Retrieves the tip-speed (aka advance) ratio.
double GetA0(void) const
Retrieves the rotor's coning angle.
double GetVi(void) const
Retrieves the induced velocity.
double Calculate(double EnginePower)
Returns the scalar thrust of the rotor, and adjusts the RPM value.
double GetRPM(void) const
Retrieves the RPMs of the rotor.
double GetGroundEffectScaleNorm(void) const
Retrieves the ground effect scaling factor.
double GetTorque(void) const
Retrieves the torque.
double GetEngineRPM(void) const
Retrieves the RPMs of the Engine, as seen from this rotor.
double GetNu(void) const
Retrieves the induced inflow ratio.
double GetPhiDW(void) const
Downwash angle - positive values point leftward (given a horizontal spinning rotor)
void SetLateralCtrl(double c)
Sets the lateral control input in radians.
double GetA1(void) const
Retrieves the longitudinal flapping angle with respect to the rotor shaft.
std::string GetThrusterLabels(int id, const std::string &delimeter)
double GetCollectiveCtrl(void) const
Retrieves the collective control input in radians.
double GetLambda(void) const
Retrieves the inflow ratio.
double GetLongitudinalCtrl(void) const
Retrieves the longitudinal control input in radians.
double GetThetaDW(void) const
Downwash angle - positive values point forward (given a horizontal spinning rotor)
FGRotor(FGFDMExec *exec, Element *rotor_element, int num)
Constructor for FGRotor.
double GetB1(void) const
Retrieves the lateral flapping angle with respect to the rotor shaft.
void SetCollectiveCtrl(double c)
Sets the collective control input in radians.
void SetGroundEffectScaleNorm(double g)
Sets the ground effect scaling factor.
double GetCT(void) const
Retrieves the thrust coefficient.
std::string GetThrusterValues(int id, const std::string &delimeter)
void SetLongitudinalCtrl(double c)
Sets the longitudinal control input in radians.
double GetLateralCtrl(void) const
Retrieves the lateral control input in radians.
double GetGearRatio(void)
struct JSBSim::FGThruster::Inputs in
FGThruster(FGFDMExec *FDMExec, Element *el, int num)
Constructor.
Utility class that handles power transmission in conjunction with FGRotor.
constexpr double sqr(double x)
simply square a value
std::string & to_upper(std::string &str)