FGRotor::FGRotor(FGFDMExec *exec, Element* rotor_element, int num) : FGThruster(exec, rotor_element, num) { FGColumnVector3 location, orientation; Element *thruster_element; PropertyManager = fdmex->GetPropertyManager(); dt = fdmex->GetDeltaT(); /* apply defaults */ rho = 0.002356; // just a sane value RPM = 0.0; Sense = 1.0; tailRotorPresent = false; effective_tail_col = 0.001; // just a sane value prop_inflow_ratio_lambda = 0.0; prop_advance_ratio_mu = 0.0; prop_inflow_ratio_induced_nu = 0.0; prop_mr_torque = 0.0; prop_thrust_coefficient = 0.0; prop_coning_angle = 0.0; prop_theta_downwash = prop_phi_downwash = 0.0; hover_threshold = 0.0; hover_scale = 0.0; mr.zero(); tr.zero(); // debug stuff prop_DumpFlag = 0; /* configure */ Type = ttRotor; SetTransformType(FGForce::tCustom); // get data from parent and 'mount' the rotor system thruster_element = rotor_element->GetParent()->FindElement("sense"); if (thruster_element) { Sense = thruster_element->GetDataAsNumber() >= 0.0 ? 1.0: -1.0; } thruster_element = rotor_element->GetParent()->FindElement("location"); if (thruster_element) location = thruster_element->FindElementTripletConvertTo("IN"); else cerr << "No thruster location found." << endl; thruster_element = rotor_element->GetParent()->FindElement("orient"); if (thruster_element) orientation = thruster_element->FindElementTripletConvertTo("RAD"); else cerr << "No thruster orientation found." << endl; SetLocation(location); SetAnglesToBody(orientation); // get main rotor parameters mr.parent = rotor_element; int flags = eMain; string a_val=""; a_val = rotor_element->GetAttributeValue("variant"); if ( a_val == "coaxial" ) { flags += eCoaxial; cerr << "# found 'coaxial' variant" << endl; } if (Sense<0.0) { flags += eRotCW; } mr.configure(flags); mr.rk.init(0,dt,6); // get tail rotor parameters tr.parent=rotor_element->FindElement("tailrotor"); if (tr.parent) { tailRotorPresent = true; } else { tailRotorPresent = false; cerr << "# No tailrotor found, assuming a single rotor." << endl; } if (tailRotorPresent) { int flags = eTail; if (Sense<0.0) { flags += eRotCW; } tr.configure(flags, &mr); tr.rk.init(0,dt,6); tr.RpmRatio = tr.NominalRPM/mr.NominalRPM; // 'connect' } /* remaining parameters */ // ground effect double c_ground_effect = 0.0; // uh1 ~ 0.28 , larger values increase the effect ground_effect_exp = 0.0; ground_effect_shift = 0.0; if (rotor_element->FindElement("cgroundeffect")) c_ground_effect = rotor_element->FindElementValueAsNumber("cgroundeffect"); if (rotor_element->FindElement("groundeffectshift")) ground_effect_shift = rotor_element->FindElementValueAsNumberConvertTo("groundeffectshift","FT"); // prepare calculations, see /TA77/ if (c_ground_effect > 1e-5) { ground_effect_exp = 1.0 / ( 2.0*mr.Radius * c_ground_effect ); } else { ground_effect_exp = 0.0; // disable } // smooth out jumps in hagl reported, otherwise the ground effect // calculation would cause jumps too. 1Hz seems sufficient. damp_hagl = Filter(1.0,dt); // misc, experimental if (rotor_element->FindElement("hoverthreshold")) hover_threshold = rotor_element->FindElementValueAsNumberConvertTo("hoverthreshold", "FT/SEC"); if (rotor_element->FindElement("hoverscale")) hover_scale = rotor_element->FindElementValueAsNumber("hoverscale"); // enable import-export bind(); // unused right now prop_rotorbrake->setDoubleValue(0.0); prop_freewheel_factor->setDoubleValue(1.0); Debug(0); } // Constructor
FGThruster::FGThruster(FGFDMExec *FDMExec, Element *el, int num ): FGForce(FDMExec) { Element* thruster_element = el->GetParent(); Element* element; FGColumnVector3 location, orientation, pointing; Type = ttDirect; SetTransformType(FGForce::tCustom); Name = el->GetAttributeValue("name"); GearRatio = 1.0; ReverserAngle = 0.0; Thrust = 0.0; EngineNum = num; PropertyManager = FDMExec->GetPropertyManager(); // Determine the initial location and orientation of this thruster and load the // thruster with this information. element = thruster_element->FindElement("location"); if (element) location = element->FindElementTripletConvertTo("IN"); else cerr << fgred << " No thruster location found." << reset << endl; SetLocation(location); string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("propulsion/engine", EngineNum); element = thruster_element->FindElement("pointing"); if (element) { // This defines a fixed nozzle that has no public interface property to gimbal or reverse it. pointing = element->FindElementTripletConvertTo("RAD"); // The specification of RAD here is superfluous, // and simply precludes a conversion. mT.InitMatrix(); mT(1,1) = pointing(1); mT(2,1) = pointing(2); mT(3,1) = pointing(3); } else { element = thruster_element->FindElement("orient"); if (element) orientation = element->FindElementTripletConvertTo("RAD"); SetAnglesToBody(orientation); property_name = base_property_name + "/pitch-angle-rad"; PropertyManager->Tie( property_name.c_str(), (FGForce *)this, &FGForce::GetPitch, &FGForce::SetPitch); property_name = base_property_name + "/yaw-angle-rad"; PropertyManager->Tie( property_name.c_str(), (FGForce *)this, &FGForce::GetYaw, &FGForce::SetYaw); if (el->GetName() == "direct") // this is a direct thruster. At this time // only a direct thruster can be reversed. { property_name = base_property_name + "/reverser-angle-rad"; PropertyManager->Tie( property_name.c_str(), (FGThruster *)this, &FGThruster::GetReverserAngle, &FGThruster::SetReverserAngle); } } Debug(0); }
FGGasCell::FGGasCell(FGFDMExec* exec, Element* el, unsigned int num, const struct Inputs& input) : FGForce(exec), in(input) { string token; Element* element; FGPropertyManager* PropertyManager = exec->GetPropertyManager(); MassBalance = exec->GetMassBalance(); gasCellJ = FGMatrix33(); gasCellM = FGColumnVector3(); Buoyancy = MaxVolume = MaxOverpressure = Temperature = Pressure = Contents = Volume = dVolumeIdeal = 0.0; Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0; ValveCoefficient = ValveOpen = 0.0; CellNum = num; // NOTE: In the local system X points north, Y points east and Z points down. SetTransformType(FGForce::tLocalBody); type = el->GetAttributeValue("type"); if (type == "HYDROGEN") Type = ttHYDROGEN; else if (type == "HELIUM") Type = ttHELIUM; else if (type == "AIR") Type = ttAIR; else Type = ttUNKNOWN; element = el->FindElement("location"); if (element) { vXYZ = element->FindElementTripletConvertTo("IN"); } else { std::stringstream error; error << "Fatal Error: No location found for this gas cell." << endl; throw std::runtime_error(error.str()); } if ((el->FindElement("x_radius") || el->FindElement("x_width")) && (el->FindElement("y_radius") || el->FindElement("y_width")) && (el->FindElement("z_radius") || el->FindElement("z_width"))) { if (el->FindElement("x_radius")) { Xradius = el->FindElementValueAsNumberConvertTo("x_radius", "FT"); } if (el->FindElement("y_radius")) { Yradius = el->FindElementValueAsNumberConvertTo("y_radius", "FT"); } if (el->FindElement("z_radius")) { Zradius = el->FindElementValueAsNumberConvertTo("z_radius", "FT"); } if (el->FindElement("x_width")) { Xwidth = el->FindElementValueAsNumberConvertTo("x_width", "FT"); } if (el->FindElement("y_width")) { Ywidth = el->FindElementValueAsNumberConvertTo("y_width", "FT"); } if (el->FindElement("z_width")) { Zwidth = el->FindElementValueAsNumberConvertTo("z_width", "FT"); } // The volume is a (potentially) extruded ellipsoid. // However, currently only a few combinations of radius and width are // fully supported. if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Ellipsoid volume. MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0; } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Cylindrical volume. MaxVolume = M_PI * Yradius * Zradius * Xwidth; } else { cerr << "Warning: Unsupported gas cell shape." << endl; MaxVolume = (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 + M_PI * Yradius * Zradius * Xwidth + M_PI * Xradius * Zradius * Ywidth + M_PI * Xradius * Yradius * Zwidth + 2.0 * Xradius * Ywidth * Zwidth + 2.0 * Yradius * Xwidth * Zwidth + 2.0 * Zradius * Xwidth * Ywidth + Xwidth * Ywidth * Zwidth); } } else { std::stringstream error; error << "Fatal Error: Gas cell shape must be given." << endl; throw std::runtime_error(error.str()); } if (el->FindElement("max_overpressure")) { MaxOverpressure = el->FindElementValueAsNumberConvertTo("max_overpressure", "LBS/FT2"); } if (el->FindElement("fullness")) { const double Fullness = el->FindElementValueAsNumber("fullness"); if (0 <= Fullness) { Volume = Fullness * MaxVolume; } else { cerr << "Warning: Invalid initial gas cell fullness value." << endl; } } if (el->FindElement("valve_coefficient")) { ValveCoefficient = el->FindElementValueAsNumberConvertTo("valve_coefficient", "FT4*SEC/SLUG"); ValveCoefficient = max(ValveCoefficient, 0.0); } // Initialize state SetLocation(vXYZ); if (Temperature == 0.0) { Temperature = in.Temperature; } if (Pressure == 0.0) { Pressure = in.Pressure; } if (Volume != 0.0) { // Calculate initial gas content. Contents = Pressure * Volume / (R * Temperature); // Clip to max allowed value. const double IdealPressure = Contents * R * Temperature / MaxVolume; if (IdealPressure > Pressure + MaxOverpressure) { Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature); Pressure = Pressure + MaxOverpressure; } else { Pressure = max(IdealPressure, Pressure); } } else { // Calculate initial gas content. Contents = Pressure * MaxVolume / (R * Temperature); } Volume = Contents * R * Temperature / Pressure; Mass = Contents * M_gas(); // Bind relevant properties string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("buoyant_forces/gas-cell", CellNum); property_name = base_property_name + "/max_volume-ft3"; PropertyManager->Tie( property_name.c_str(), &MaxVolume, false ); PropertyManager->GetNode()->SetWritable( property_name, false ); property_name = base_property_name + "/temp-R"; PropertyManager->Tie( property_name.c_str(), &Temperature, false ); property_name = base_property_name + "/pressure-psf"; PropertyManager->Tie( property_name.c_str(), &Pressure, false ); property_name = base_property_name + "/volume-ft3"; PropertyManager->Tie( property_name.c_str(), &Volume, false ); property_name = base_property_name + "/buoyancy-lbs"; PropertyManager->Tie( property_name.c_str(), &Buoyancy, false ); property_name = base_property_name + "/contents-mol"; PropertyManager->Tie( property_name.c_str(), &Contents, false ); property_name = base_property_name + "/valve_open"; PropertyManager->Tie( property_name.c_str(), &ValveOpen, false ); Debug(0); // Read heat transfer coefficients if (Element* heat = el->FindElement("heat")) { Element* function_element = heat->FindElement("function"); while (function_element) { HeatTransferCoeff.push_back(new FGFunction(PropertyManager, function_element)); function_element = heat->FindNextElement("function"); } } // Load ballonets if there are any if (Element* ballonet_element = el->FindElement("ballonet")) { while (ballonet_element) { Ballonet.push_back(new FGBallonet(exec, ballonet_element, Ballonet.size(), this, in)); ballonet_element = el->FindNextElement("ballonet"); } } }