/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Module: FGLGear.cpp Author: Jon S. Berndt Norman H. Princen Bertrand Coconnier Date started: 11/18/99 Purpose: Encapsulates the landing gear elements Called by: FGAircraft ------------- Copyright (C) 1999 Jon S. Berndt (jon@jsbsim.org) ------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Further information about the GNU Lesser General Public License can also be found on the world wide web at http://www.gnu.org. FUNCTIONAL DESCRIPTION -------------------------------------------------------------------------------- HISTORY -------------------------------------------------------------------------------- 11/18/99 JSB Created 01/30/01 NHP Extended gear model to properly simulate steering and braking 07/08/09 BC Modified gear model to support large angles between aircraft and ground /%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ #include <cstdlib> #include <cstring> #include "math/FGFunction.h" #include "FGLGear.h" #include "models/FGGroundReactions.h" #include "math/FGTable.h" #include "input_output/FGXMLElement.h" using namespace std; namespace JSBSim { /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DEFINITIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GLOBAL DATA %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ IDENT(IdSrc,"$Id: FGLGear.cpp,v 1.125 2017/02/21 21:14:13 bcoconni Exp $"); IDENT(IdHdr,ID_LGEAR); // Body To Structural (body frame is rotated 180 deg about Y and lengths are given in // ft instead of inches) const FGMatrix33 FGLGear::Tb2s(-1./inchtoft, 0., 0., 0., 1./inchtoft, 0., 0., 0., -1./inchtoft); const FGMatrix33 FGLGear::Ts2b(-inchtoft, 0., 0., 0., inchtoft, 0., 0., 0., -inchtoft); /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS IMPLEMENTATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ FGLGear::FGLGear(Element* el, FGFDMExec* fdmex, int number, const struct Inputs& inputs) : FGSurface(fdmex, number), FGForce(fdmex), in(inputs), GearNumber(number), SteerAngle(0.0), Castered(false), StaticFriction(false), eSteerType(stSteer) { kSpring = bDamp = bDampRebound = dynamicFCoeff = staticFCoeff = rollingFCoeff = maxSteerAngle = 0; isRetractable = false; eDampType = dtLinear; eDampTypeRebound = dtLinear; name = el->GetAttributeValue("name"); string sContactType = el->GetAttributeValue("type"); if (sContactType == "BOGEY") { eContactType = ctBOGEY; } else if (sContactType == "STRUCTURE") { eContactType = ctSTRUCTURE; } else { // Unknown contact point types will be treated as STRUCTURE. eContactType = ctSTRUCTURE; } // Default values for structural contact points if (eContactType == ctSTRUCTURE) { kSpring = in.EmptyWeight; bDamp = kSpring; bDampRebound = kSpring * 10; staticFCoeff = 1.0; dynamicFCoeff = 1.0; } PropertyManager = fdmex->GetPropertyManager(); fStrutForce = 0; Element* strutForce = el->FindElement("strut_force"); if (strutForce) { Element* springFunc = strutForce->FindElement("function"); fStrutForce = new FGFunction(PropertyManager, springFunc); } else { if (el->FindElement("spring_coeff")) kSpring = el->FindElementValueAsNumberConvertTo("spring_coeff", "LBS/FT"); if (el->FindElement("damping_coeff")) { Element* dampCoeff = el->FindElement("damping_coeff"); if (dampCoeff->GetAttributeValue("type") == "SQUARE") { eDampType = dtSquare; bDamp = el->FindElementValueAsNumberConvertTo("damping_coeff", "LBS/FT2/SEC2"); } else { bDamp = el->FindElementValueAsNumberConvertTo("damping_coeff", "LBS/FT/SEC"); } } if (el->FindElement("damping_coeff_rebound")) { Element* dampCoeffRebound = el->FindElement("damping_coeff_rebound"); if (dampCoeffRebound->GetAttributeValue("type") == "SQUARE") { eDampTypeRebound = dtSquare; bDampRebound = el->FindElementValueAsNumberConvertTo("damping_coeff_rebound", "LBS/FT2/SEC2"); } else { bDampRebound = el->FindElementValueAsNumberConvertTo("damping_coeff_rebound", "LBS/FT/SEC"); } } else { bDampRebound = bDamp; eDampTypeRebound = eDampType; } } if (el->FindElement("dynamic_friction")) dynamicFCoeff = el->FindElementValueAsNumber("dynamic_friction"); if (el->FindElement("static_friction")) staticFCoeff = el->FindElementValueAsNumber("static_friction"); if (el->FindElement("rolling_friction")) rollingFCoeff = el->FindElementValueAsNumber("rolling_friction"); if (el->FindElement("retractable")) isRetractable = ((unsigned int)el->FindElementValueAsNumber("retractable"))>0.0?true:false; if (el->FindElement("max_steer")) maxSteerAngle = el->FindElementValueAsNumberConvertTo("max_steer", "DEG"); Element* castered_el = el->FindElement("castered"); if ((maxSteerAngle == 360 && !castered_el) || (castered_el && castered_el->GetDataAsNumber() != 0.0)) { eSteerType = stCaster; Castered = true; } else if (maxSteerAngle == 0.0) { eSteerType = stFixed; } else eSteerType = stSteer; GroundReactions = fdmex->GetGroundReactions(); ForceY_Table = 0; Element* force_table = el->FindElement("table"); while (force_table) { string force_type = force_table->GetAttributeValue("type"); if (force_type == "CORNERING_COEFF") { ForceY_Table = new FGTable(PropertyManager, force_table); break; } else { cerr << "Undefined force table for " << name << " contact point" << endl; } force_table = el->FindNextElement("table"); } Element* element = el->FindElement("location"); if (element) vXYZn = element->FindElementTripletConvertTo("IN"); else {cerr << "No location given for contact " << name << endl; exit(-1);} SetTransformType(FGForce::tCustom); element = el->FindElement("orientation"); if (element && (eContactType == ctBOGEY)) { FGQuaternion quatFromEuler(element->FindElementTripletConvertTo("RAD")); mTGear = quatFromEuler.GetT(); } else { mTGear(1,1) = 1.; mTGear(2,2) = 1.; mTGear(3,3) = 1.; } string sBrakeGroup = el->FindElementValue("brake_group"); if (sBrakeGroup == "LEFT" ) eBrakeGrp = bgLeft; else if (sBrakeGroup == "RIGHT" ) eBrakeGrp = bgRight; else if (sBrakeGroup == "CENTER") eBrakeGrp = bgCenter; else if (sBrakeGroup == "NOSE" ) eBrakeGrp = bgCenter; // Nose brake is not supported by FGFCS else if (sBrakeGroup == "TAIL" ) eBrakeGrp = bgCenter; // Tail brake is not supported by FGFCS else if (sBrakeGroup == "NONE" ) eBrakeGrp = bgNone; else if (sBrakeGroup.empty() ) eBrakeGrp = bgNone; else { cerr << "Improper braking group specification in config file: " << sBrakeGroup << " is undefined." << endl; } // Add some AI here to determine if gear is located properly according to its // brake group type ?? useFCSGearPos = false; ReportEnable = true; TakeoffReported = LandingReported = false; // Set Pacejka terms Stiffness = 0.06; Shape = 2.8; Peak = staticFCoeff; Curvature = 1.03; ResetToIC(); Debug(0); } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% FGLGear::~FGLGear() { delete ForceY_Table; delete fStrutForce; Debug(1); } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% void FGLGear::ResetToIC(void) { GearPos = 1.0; WOW = lastWOW = false; FirstContact = false; StartedGroundRun = false; LandingDistanceTraveled = TakeoffDistanceTraveled = TakeoffDistanceTraveled50ft = 0.0; MaximumStrutForce = MaximumStrutTravel = 0.0; SinkRate = GroundSpeed = 0.0; SteerAngle = 0.0; vWhlVelVec.InitMatrix(); compressLength = 0.0; compressSpeed = 0.0; maxCompLen = 0.0; WheelSlip = 0.0; // Initialize Lagrange multipliers for (int i=0; i < 3; i++) { LMultiplier[i].ForceJacobian.InitMatrix(); LMultiplier[i].MomentJacobian.InitMatrix(); LMultiplier[i].Min = 0.0; LMultiplier[i].Max = 0.0; LMultiplier[i].value = 0.0; } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% const FGColumnVector3& FGLGear::GetBodyForces(FGSurface *surface) { double gearPos = 1.0; vFn.InitMatrix(); if (isRetractable) gearPos = GetGearUnitPos(); if (gearPos > 0.99) { // Gear DOWN FGColumnVector3 normal, terrainVel, dummy; FGLocation gearLoc, contact; FGColumnVector3 vWhlBodyVec = Ts2b * (vXYZn - in.vXYZcg); vLocalGear = in.Tb2l * vWhlBodyVec; // Get local frame wheel location gearLoc = in.Location.LocalToLocation(vLocalGear); // Compute the height of the theoretical location of the wheel (if strut is // not compressed) with respect to the ground level double height = gearLoc.GetContactPoint(contact, normal, terrainVel, dummy); // Does this surface contact point interact with another surface? if (surface) { if (!fdmex->GetTrimStatus()) height -= (*surface).GetBumpHeight(); staticFFactor = (*surface).GetStaticFFactor(); rollingFFactor = (*surface).GetRollingFFactor(); maximumForce = (*surface).GetMaximumForce(); isSolid = (*surface).GetSolid(); } FGColumnVector3 vWhlDisplVec; double LGearProj = 1.0; if (height < 0.0) { WOW = true; vGroundNormal = in.Tec2b * normal; // The height returned by GetGroundCallback() is the AGL and is expressed // in the Z direction of the local coordinate frame. We now need to transform // this height in actual compression of the strut (BOGEY) or in the normal // direction to the ground (STRUCTURE) double normalZ = (in.Tec2l*normal)(eZ); LGearProj = -(mTGear.Transposed() * vGroundNormal)(eZ); // The following equations use the vector to the tire contact patch // including the strut compression. switch(eContactType) { case ctBOGEY: if (isSolid) { compressLength = LGearProj > 0.0 ? height * normalZ / LGearProj : 0.0; vWhlDisplVec = mTGear * FGColumnVector3(0., 0., -compressLength); } else { // Gears don't (or hardly) compress in liquids WOW = false; } break; case ctSTRUCTURE: compressLength = height * normalZ / DotProduct(normal, normal); vWhlDisplVec = compressLength * vGroundNormal; break; } } else WOW = false; if (WOW) { FGColumnVector3 vWhlContactVec = vWhlBodyVec + vWhlDisplVec; vActingXYZn = vXYZn + Tb2s * vWhlDisplVec; FGColumnVector3 vBodyWhlVel = in.PQR * vWhlContactVec; vBodyWhlVel += in.UVW - in.Tec2b * terrainVel; vWhlVelVec = mTGear.Transposed() * vBodyWhlVel; InitializeReporting(); ComputeSteeringAngle(); ComputeGroundFrame(); vGroundWhlVel = mT.Transposed() * vBodyWhlVel; if (fdmex->GetTrimStatus()) compressSpeed = 0.0; // Steady state is sought during trimming else { compressSpeed = -vGroundWhlVel(eZ); if (eContactType == ctBOGEY) compressSpeed /= LGearProj; } ComputeVerticalStrutForce(); // Compute the friction coefficients in the wheel ground plane. if (eContactType == ctBOGEY) { ComputeSlipAngle(); ComputeBrakeForceCoefficient(); ComputeSideForceCoefficient(); } // Prepare the Jacobians and the Lagrange multipliers for later friction // forces calculations. ComputeJacobian(vWhlContactVec); } else { // Gear is NOT compressed compressLength = 0.0; compressSpeed = 0.0; WheelSlip = 0.0; StrutForce = 0.0; vWhlDisplVec.InitMatrix(); LMultiplier[ftRoll].value = 0.0; LMultiplier[ftSide].value = 0.0; LMultiplier[ftDynamic].value = 0.0; // Let wheel spin down slowly vWhlVelVec(eX) -= 13.0 * in.TotalDeltaT; if (vWhlVelVec(eX) < 0.0) vWhlVelVec(eX) = 0.0; // Return to neutral position between 1.0 and 0.8 gear pos. SteerAngle *= max(gearPos-0.8, 0.0)/0.2; ResetReporting(); } } if (!fdmex->GetTrimStatus()) { ReportTakeoffOrLanding(); // Require both WOW and LastWOW to be true before checking crash conditions // to allow the WOW flag to be used in terminating a scripted run. if (WOW && lastWOW) CrashDetect(); lastWOW = WOW; } return FGForce::GetBodyForces(); } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Build a local "ground" coordinate system defined by // eX : projection of the rolling direction on the ground // eY : projection of the sliping direction on the ground // eZ : normal to the ground void FGLGear::ComputeGroundFrame(void) { FGColumnVector3 roll = mTGear * FGColumnVector3(cos(SteerAngle), sin(SteerAngle), 0.); FGColumnVector3 side = vGroundNormal * roll; roll -= DotProduct(roll, vGroundNormal) * vGroundNormal; roll.Normalize(); side.Normalize(); mT(eX,eX) = roll(eX); mT(eY,eX) = roll(eY); mT(eZ,eX) = roll(eZ); mT(eX,eY) = side(eX); mT(eY,eY) = side(eY); mT(eZ,eY) = side(eZ); mT(eX,eZ) = vGroundNormal(eX); mT(eY,eZ) = vGroundNormal(eY); mT(eZ,eZ) = vGroundNormal(eZ); } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Calculate tire slip angle. void FGLGear::ComputeSlipAngle(void) { // Check that the speed is non-null otherwise keep the current angle if (vGroundWhlVel.Magnitude(eX,eY) > 1E-3) WheelSlip = -atan2(vGroundWhlVel(eY), fabs(vGroundWhlVel(eX)))*radtodeg; } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Compute the steering angle in any case. // This will also make sure that animations will look right. void FGLGear::ComputeSteeringAngle(void) { if (Castered) { // Check that the speed is non-null otherwise keep the current angle if (vWhlVelVec.Magnitude(eX,eY) > 0.1) SteerAngle = atan2(vWhlVelVec(eY), fabs(vWhlVelVec(eX))); } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Reset reporting functionality after takeoff void FGLGear::ResetReporting(void) { if (in.DistanceAGL > 200.0) { FirstContact = false; StartedGroundRun = false; LandingReported = false; TakeoffReported = true; LandingDistanceTraveled = 0.0; MaximumStrutForce = MaximumStrutTravel = 0.0; } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% void FGLGear::InitializeReporting(void) { // If this is the first time the wheel has made contact, remember some values // for later printout. if (!FirstContact) { FirstContact = true; SinkRate = compressSpeed; GroundSpeed = in.Vground; TakeoffReported = false; } // If the takeoff run is starting, initialize. if ((in.Vground > 0.1) && (in.BrakePos[bgLeft] == 0) && (in.BrakePos[bgRight] == 0) && (in.TakeoffThrottle && !StartedGroundRun)) { TakeoffDistanceTraveled = 0; TakeoffDistanceTraveled50ft = 0; StartedGroundRun = true; } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Takeoff and landing reporting functionality void FGLGear::ReportTakeoffOrLanding(void) { if (FirstContact) LandingDistanceTraveled += in.Vground * in.TotalDeltaT; if (StartedGroundRun) { TakeoffDistanceTraveled50ft += in.Vground * in.TotalDeltaT; if (WOW) TakeoffDistanceTraveled += in.Vground * in.TotalDeltaT; } if ( ReportEnable && in.Vground <= 0.05 && !LandingReported && in.WOW) { if (debug_lvl > 0) Report(erLand); } if ( ReportEnable && !TakeoffReported && (in.DistanceAGL - vLocalGear(eZ)) > 50.0 && !in.WOW) { if (debug_lvl > 0) Report(erTakeoff); } if (lastWOW != WOW) { ostringstream buf; buf << "GEAR_CONTACT: " << fdmex->GetSimTime() << " seconds: " << name; PutMessage(buf.str(), WOW); } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Crash detection logic (really out-of-bounds detection) void FGLGear::CrashDetect(void) { if ( (compressLength > 500.0 || vFn.Magnitude() > 100000000.0 || GetMoments().Magnitude() > 5000000000.0 || SinkRate > 1.4666*30 ) && !fdmex->IntegrationSuspended()) { ostringstream buf; buf << "*CRASH DETECTED* " << fdmex->GetSimTime() << " seconds: " << name; PutMessage(buf.str()); // fdmex->SuspendIntegration(); } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // The following needs work regarding friction coefficients and braking and // steering The BrakeFCoeff formula assumes that an anti-skid system is used. // It also assumes that we won't be turning and braking at the same time. // Will fix this later. // [JSB] The braking force coefficients include normal rolling coefficient + // a percentage of the static friction coefficient based on braking applied. void FGLGear::ComputeBrakeForceCoefficient(void) { BrakeFCoeff = rollingFFactor * rollingFCoeff; if (eBrakeGrp != bgNone) BrakeFCoeff += in.BrakePos[eBrakeGrp] * staticFFactor * (staticFCoeff - rollingFCoeff); } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Compute the sideforce coefficients using Pacejka's Magic Formula. // // y(x) = D sin {C arctan [Bx - E(Bx - arctan Bx)]} // // Where: B = Stiffness Factor (0.06, here) // C = Shape Factor (2.8, here) // D = Peak Factor (0.8, here) // E = Curvature Factor (1.03, here) void FGLGear::ComputeSideForceCoefficient(void) { if (ForceY_Table) { FCoeff = ForceY_Table->GetValue(WheelSlip); } else { double StiffSlip = Stiffness*WheelSlip; FCoeff = Peak * sin(Shape*atan(StiffSlip - Curvature*(StiffSlip - atan(StiffSlip)))); } FCoeff *= staticFFactor; } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Compute the vertical force on the wheel using square-law damping (per comment // in paper AIAA-2000-4303 - see header prologue comments). We might consider // allowing for both square and linear damping force calculation. Also need to // possibly give a "rebound damping factor" that differs from the compression // case. void FGLGear::ComputeVerticalStrutForce() { double springForce = 0; double dampForce = 0; if (fStrutForce) StrutForce = min(fStrutForce->GetValue(), (double)0.0); else { springForce = -compressLength * kSpring; if (compressSpeed >= 0.0) { if (eDampType == dtLinear) dampForce = -compressSpeed * bDamp; else dampForce = -compressSpeed * compressSpeed * bDamp; } else { if (eDampTypeRebound == dtLinear) dampForce = -compressSpeed * bDampRebound; else dampForce = compressSpeed * compressSpeed * bDampRebound; } StrutForce = min(springForce + dampForce, (double)0.0); if (StrutForce > maximumForce) { StrutForce = maximumForce; compressLength = -StrutForce / kSpring; } } // The reaction force of the wheel is always normal to the ground switch (eContactType) { case ctBOGEY: // Project back the strut force in the local coordinate frame of the ground vFn(eZ) = StrutForce / (mTGear.Transposed()*vGroundNormal)(eZ); break; case ctSTRUCTURE: vFn(eZ) = -StrutForce; break; } // Remember these values for reporting MaximumStrutForce = max(MaximumStrutForce, fabs(StrutForce)); MaximumStrutTravel = max(MaximumStrutTravel, fabs(compressLength)); } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% double FGLGear::GetGearUnitPos(void) const { // hack to provide backward compatibility to gear/gear-pos-norm property if( useFCSGearPos || in.FCSGearPos != 1.0 ) { useFCSGearPos = true; return in.FCSGearPos; } return GearPos; } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Compute the jacobian entries for the friction forces resolution later // in FGPropagate void FGLGear::ComputeJacobian(const FGColumnVector3& vWhlContactVec) { // When the point of contact is moving, dynamic friction is used // This type of friction is limited to ctSTRUCTURE elements because their // friction coefficient is the same in every directions if ((eContactType == ctSTRUCTURE) && (vGroundWhlVel.Magnitude(eX,eY) > 1E-3)) { FGColumnVector3 velocityDirection = vGroundWhlVel; StaticFriction = false; velocityDirection(eZ) = 0.; velocityDirection.Normalize(); LMultiplier[ftDynamic].ForceJacobian = mT * velocityDirection; LMultiplier[ftDynamic].MomentJacobian = vWhlContactVec * LMultiplier[ftDynamic].ForceJacobian; LMultiplier[ftDynamic].Max = 0.; LMultiplier[ftDynamic].Min = -fabs(staticFFactor * dynamicFCoeff * vFn(eZ)); // The Lagrange multiplier value obtained from the previous iteration is kept // This is supposed to accelerate the convergence of the projected Gauss-Seidel // algorithm. The code just below is to make sure that the initial value // is consistent with the current friction coefficient and normal reaction. LMultiplier[ftDynamic].value = Constrain(LMultiplier[ftDynamic].Min, LMultiplier[ftDynamic].value, LMultiplier[ftDynamic].Max); GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftDynamic]); } else { // Static friction is used for ctSTRUCTURE when the contact point is not moving. // It is always used for ctBOGEY elements because the friction coefficients // of a tyre depend on the direction of the movement (roll & side directions). // This cannot be handled properly by the so-called "dynamic friction". StaticFriction = true; LMultiplier[ftRoll].ForceJacobian = mT * FGColumnVector3(1.,0.,0.); LMultiplier[ftSide].ForceJacobian = mT * FGColumnVector3(0.,1.,0.); LMultiplier[ftRoll].MomentJacobian = vWhlContactVec * LMultiplier[ftRoll].ForceJacobian; LMultiplier[ftSide].MomentJacobian = vWhlContactVec * LMultiplier[ftSide].ForceJacobian; switch(eContactType) { case ctBOGEY: LMultiplier[ftRoll].Max = fabs(BrakeFCoeff * vFn(eZ)); LMultiplier[ftSide].Max = fabs(FCoeff * vFn(eZ)); break; case ctSTRUCTURE: LMultiplier[ftRoll].Max = fabs(staticFFactor * staticFCoeff * vFn(eZ)); LMultiplier[ftSide].Max = LMultiplier[ftRoll].Max; break; } LMultiplier[ftRoll].Min = -LMultiplier[ftRoll].Max; LMultiplier[ftSide].Min = -LMultiplier[ftSide].Max; // The Lagrange multiplier value obtained from the previous iteration is kept // This is supposed to accelerate the convergence of the projected Gauss-Seidel // algorithm. The code just below is to make sure that the initial value // is consistent with the current friction coefficient and normal reaction. LMultiplier[ftRoll].value = Constrain(LMultiplier[ftRoll].Min, LMultiplier[ftRoll].value, LMultiplier[ftRoll].Max); LMultiplier[ftSide].value = Constrain(LMultiplier[ftSide].Min, LMultiplier[ftSide].value, LMultiplier[ftSide].Max); GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftRoll]); GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftSide]); } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // This routine is called after the Lagrange multiplier has been computed in // the FGAccelerations class. The friction forces of the landing gear are then // updated accordingly. void FGLGear::UpdateForces(void) { if (StaticFriction) { vFn(eX) = LMultiplier[ftRoll].value; vFn(eY) = LMultiplier[ftSide].value; } else { FGColumnVector3 forceDir = mT.Transposed() * LMultiplier[ftDynamic].ForceJacobian; vFn(eX) = LMultiplier[ftDynamic].value * forceDir(eX); vFn(eY) = LMultiplier[ftDynamic].value * forceDir(eY); } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% void FGLGear::SetstaticFCoeff(double coeff) { staticFCoeff = coeff; Peak = coeff; } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% void FGLGear::bind(void) { string property_name; string base_property_name; switch(eContactType) { case ctBOGEY: eSurfaceType = FGSurface::ctBOGEY; base_property_name = CreateIndexedPropertyName("gear/unit", GearNumber); break; case ctSTRUCTURE: eSurfaceType = FGSurface::ctSTRUCTURE; base_property_name = CreateIndexedPropertyName("contact/unit", GearNumber); break; default: return; } FGSurface::bind(); property_name = base_property_name + "/WOW"; PropertyManager->Tie( property_name.c_str(), &WOW ); property_name = base_property_name + "/x-position"; PropertyManager->Tie( property_name.c_str(), (FGForce*)this, &FGForce::GetLocationX, &FGForce::SetLocationX); property_name = base_property_name + "/y-position"; PropertyManager->Tie( property_name.c_str(), (FGForce*)this, &FGForce::GetLocationY, &FGForce::SetLocationY); property_name = base_property_name + "/z-position"; PropertyManager->Tie( property_name.c_str(), (FGForce*)this, &FGForce::GetLocationZ, &FGForce::SetLocationZ); property_name = base_property_name + "/compression-ft"; PropertyManager->Tie( property_name.c_str(), &compressLength ); property_name = base_property_name + "/compression-velocity-fps"; PropertyManager->Tie( property_name.c_str(), &compressSpeed ); property_name = base_property_name + "/static_friction_coeff"; PropertyManager->Tie( property_name.c_str(), (FGLGear*)this, &FGLGear::GetstaticFCoeff, &FGLGear::SetstaticFCoeff); property_name = base_property_name + "/dynamic_friction_coeff"; PropertyManager->Tie( property_name.c_str(), &dynamicFCoeff ); if (eContactType == ctBOGEY) { property_name = base_property_name + "/slip-angle-deg"; PropertyManager->Tie( property_name.c_str(), &WheelSlip ); property_name = base_property_name + "/wheel-speed-fps"; PropertyManager->Tie( property_name.c_str(), (FGLGear*)this, &FGLGear::GetWheelRollVel); property_name = base_property_name + "/side_friction_coeff"; PropertyManager->Tie( property_name.c_str(), &FCoeff ); property_name = base_property_name + "/rolling_friction_coeff"; PropertyManager->Tie( property_name.c_str(), &rollingFCoeff ); if (eSteerType == stCaster) { property_name = base_property_name + "/steering-angle-deg"; PropertyManager->Tie( property_name.c_str(), this, &FGLGear::GetSteerAngleDeg ); property_name = base_property_name + "/castered"; PropertyManager->Tie( property_name.c_str(), &Castered); } } if( isRetractable ) { property_name = base_property_name + "/pos-norm"; PropertyManager->Tie( property_name.c_str(), &GearPos ); } if (eSteerType != stFixed) { // This property allows the FCS to override the steering position angle that // is set by the property fcs/steer-cmd-norm. The prefix fcs/ has been kept // for backward compatibility. string tmp = CreateIndexedPropertyName("fcs/steer-pos-deg", GearNumber); PropertyManager->Tie(tmp.c_str(), this, &FGLGear::GetSteerAngleDeg, &FGLGear::SetSteerAngleDeg); } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% void FGLGear::Report(ReportType repType) { if (fabs(TakeoffDistanceTraveled) < 0.001) return; // Don't print superfluous reports switch(repType) { case erLand: cout << endl << "Touchdown report for " << name << " (WOW at time: " << fdmex->GetSimTime() << " seconds)" << endl; cout << " Sink rate at contact: " << SinkRate << " fps, " << SinkRate*0.3048 << " mps" << endl; cout << " Contact ground speed: " << GroundSpeed*.5925 << " knots, " << GroundSpeed*0.3048 << " mps" << endl; cout << " Maximum contact force: " << MaximumStrutForce << " lbs, " << MaximumStrutForce*4.448 << " Newtons" << endl; cout << " Maximum strut travel: " << MaximumStrutTravel*12.0 << " inches, " << MaximumStrutTravel*30.48 << " cm" << endl; cout << " Distance traveled: " << LandingDistanceTraveled << " ft, " << LandingDistanceTraveled*0.3048 << " meters" << endl; LandingReported = true; break; case erTakeoff: cout << endl << "Takeoff report for " << name << " (Liftoff at time: " << fdmex->GetSimTime() << " seconds)" << endl; cout << " Distance traveled: " << TakeoffDistanceTraveled << " ft, " << TakeoffDistanceTraveled*0.3048 << " meters" << endl; cout << " Distance traveled (over 50'): " << TakeoffDistanceTraveled50ft << " ft, " << TakeoffDistanceTraveled50ft*0.3048 << " meters" << endl; cout << " [Altitude (ASL): " << in.DistanceASL << " ft. / " << in.DistanceASL*FGJSBBase::fttom << " m | Temperature: " << in.Temperature - 459.67 << " F / " << RankineToCelsius(in.Temperature) << " C]" << endl; cout << " [Velocity (KCAS): " << in.VcalibratedKts << "]" << endl; TakeoffReported = true; break; case erNone: break; } } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // The bitmasked value choices are as follows: // unset: In this case (the default) JSBSim would only print // out the normally expected messages, essentially echoing // the config files as they are read. If the environment // variable is not set, debug_lvl is set to 1 internally // 0: This requests JSBSim not to output any messages // whatsoever. // 1: This value explicity requests the normal JSBSim // startup messages // 2: This value asks for a message to be printed out when // a class is instantiated // 4: When this value is set, a message is displayed when a // FGModel object executes its Run() method // 8: When this value is set, various runtime state variables // are printed out periodically // 16: When set various parameters are sanity checked and // a message is printed out when they go out of bounds void FGLGear::Debug(int from) { static const char* sSteerType[] = {"STEERABLE", "FIXED", "CASTERED" }; static const char* sBrakeGroup[] = {"NONE", "LEFT", "RIGHT", "CENTER", "NOSE", "TAIL"}; static const char* sContactType[] = {"BOGEY", "STRUCTURE" }; if (debug_lvl <= 0) return; if (debug_lvl & 1) { // Standard console startup message output if (from == 0) { // Constructor - loading and initialization cout << " " << sContactType[eContactType] << " " << name << endl; cout << " Location: " << vXYZn << endl; cout << " Spring Constant: " << kSpring << endl; if (eDampType == dtLinear) cout << " Damping Constant: " << bDamp << " (linear)" << endl; else cout << " Damping Constant: " << bDamp << " (square law)" << endl; if (eDampTypeRebound == dtLinear) cout << " Rebound Damping Constant: " << bDampRebound << " (linear)" << endl; else cout << " Rebound Damping Constant: " << bDampRebound << " (square law)" << endl; cout << " Dynamic Friction: " << dynamicFCoeff << endl; cout << " Static Friction: " << staticFCoeff << endl; if (eContactType == ctBOGEY) { cout << " Rolling Friction: " << rollingFCoeff << endl; cout << " Steering Type: " << sSteerType[eSteerType] << endl; cout << " Grouping: " << sBrakeGroup[eBrakeGrp] << endl; cout << " Max Steer Angle: " << maxSteerAngle << endl; cout << " Retractable: " << isRetractable << endl; } } } if (debug_lvl & 2 ) { // Instantiation/Destruction notification if (from == 0) cout << "Instantiated: FGLGear" << endl; if (from == 1) cout << "Destroyed: FGLGear" << endl; } if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects } if (debug_lvl & 8 ) { // Runtime state variables } if (debug_lvl & 16) { // Sanity checking } if (debug_lvl & 64) { if (from == 0) { // Constructor cout << IdSrc << endl; cout << IdHdr << endl; } } } } // namespace JSBSim