1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
8 Purpose: Encapsulates the landing gear elements
11 ------------- Copyright (C) 1999 Jon S. Berndt (jon@jsbsim.org) -------------
13 This program is free software; you can redistribute it and/or modify it under
14 the terms of the GNU Lesser General Public License as published by the Free Software
15 Foundation; either version 2 of the License, or (at your option) any later
18 This program is distributed in the hope that it will be useful, but WITHOUT
19 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
23 You should have received a copy of the GNU Lesser General Public License along with
24 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
25 Place - Suite 330, Boston, MA 02111-1307, USA.
27 Further information about the GNU Lesser General Public License can also be found on
28 the world wide web at http://www.gnu.org.
30 FUNCTIONAL DESCRIPTION
31 --------------------------------------------------------------------------------
34 --------------------------------------------------------------------------------
36 01/30/01 NHP Extended gear model to properly simulate steering and braking
37 07/08/09 BC Modified gear model to support large angles between aircraft and ground
39 /%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
41 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
47 #include "input_output/FGPropertyManager.h"
48 #include "models/FGGroundReactions.h"
49 #include "math/FGTable.h"
55 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
57 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
59 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
61 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
63 static const char *IdSrc = "$Id: FGLGear.cpp,v 1.89 2011/09/11 11:36:04 bcoconni Exp $";
64 static const char *IdHdr = ID_LGEAR;
66 // Body To Structural (body frame is rotated 180 deg about Y and lengths are given in
67 // ft instead of inches)
68 const FGMatrix33 FGLGear::Tb2s(-1./inchtoft, 0., 0., 0., 1./inchtoft, 0., 0., 0., -1./inchtoft);
70 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
72 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
74 FGLGear::FGLGear(Element* el, FGFDMExec* fdmex, int number, const struct Inputs& inputs) :
82 Element *force_table=0;
84 Element *dampCoeffRebound=0;
87 kSpring = bDamp = bDampRebound = dynamicFCoeff = staticFCoeff = rollingFCoeff = maxSteerAngle = 0;
88 sSteerType = sBrakeGroup = sSteerType = "";
91 eDampTypeRebound = dtLinear;
93 name = el->GetAttributeValue("name");
94 sContactType = el->GetAttributeValue("type");
95 if (sContactType == "BOGEY") {
96 eContactType = ctBOGEY;
97 } else if (sContactType == "STRUCTURE") {
98 eContactType = ctSTRUCTURE;
100 // Unknown contact point types will be treated as STRUCTURE.
101 eContactType = ctSTRUCTURE;
104 // Default values for structural contact points
105 if (eContactType == ctSTRUCTURE) {
106 kSpring = in.EmptyWeight;
108 bDampRebound = kSpring * 10;
113 if (el->FindElement("spring_coeff"))
114 kSpring = el->FindElementValueAsNumberConvertTo("spring_coeff", "LBS/FT");
115 if (el->FindElement("damping_coeff")) {
116 dampCoeff = el->FindElement("damping_coeff");
117 if (dampCoeff->GetAttributeValue("type") == "SQUARE") {
118 eDampType = dtSquare;
119 bDamp = el->FindElementValueAsNumberConvertTo("damping_coeff", "LBS/FT2/SEC2");
121 bDamp = el->FindElementValueAsNumberConvertTo("damping_coeff", "LBS/FT/SEC");
125 if (el->FindElement("damping_coeff_rebound")) {
126 dampCoeffRebound = el->FindElement("damping_coeff_rebound");
127 if (dampCoeffRebound->GetAttributeValue("type") == "SQUARE") {
128 eDampTypeRebound = dtSquare;
129 bDampRebound = el->FindElementValueAsNumberConvertTo("damping_coeff_rebound", "LBS/FT2/SEC2");
131 bDampRebound = el->FindElementValueAsNumberConvertTo("damping_coeff_rebound", "LBS/FT/SEC");
134 bDampRebound = bDamp;
135 eDampTypeRebound = eDampType;
138 if (el->FindElement("dynamic_friction"))
139 dynamicFCoeff = el->FindElementValueAsNumber("dynamic_friction");
140 if (el->FindElement("static_friction"))
141 staticFCoeff = el->FindElementValueAsNumber("static_friction");
142 if (el->FindElement("rolling_friction"))
143 rollingFCoeff = el->FindElementValueAsNumber("rolling_friction");
144 if (el->FindElement("max_steer"))
145 maxSteerAngle = el->FindElementValueAsNumberConvertTo("max_steer", "DEG");
146 if (el->FindElement("retractable"))
147 isRetractable = ((unsigned int)el->FindElementValueAsNumber("retractable"))>0.0?true:false;
149 GroundReactions = fdmex->GetGroundReactions();
150 PropertyManager = fdmex->GetPropertyManager();
153 force_table = el->FindElement("table");
154 while (force_table) {
155 force_type = force_table->GetAttributeValue("type");
156 if (force_type == "CORNERING_COEFF") {
157 ForceY_Table = new FGTable(PropertyManager, force_table);
159 cerr << "Undefined force table for " << name << " contact point" << endl;
161 force_table = el->FindNextElement("table");
164 sBrakeGroup = el->FindElementValue("brake_group");
166 if (maxSteerAngle == 360) sSteerType = "CASTERED";
167 else if (maxSteerAngle == 0.0) sSteerType = "FIXED";
168 else sSteerType = "STEERABLE";
170 Element* element = el->FindElement("location");
171 if (element) vXYZn = element->FindElementTripletConvertTo("IN");
172 else {cerr << "No location given for contact " << name << endl; exit(-1);}
173 SetTransformType(FGForce::tCustom);
175 element = el->FindElement("orientation");
176 if (element && (eContactType == ctBOGEY)) {
177 vGearOrient = element->FindElementTripletConvertTo("RAD");
179 double cp,sp,cr,sr,cy,sy;
181 cp=cos(vGearOrient(ePitch)); sp=sin(vGearOrient(ePitch));
182 cr=cos(vGearOrient(eRoll)); sr=sin(vGearOrient(eRoll));
183 cy=cos(vGearOrient(eYaw)); sy=sin(vGearOrient(eYaw));
189 mTGear(1,2) = sr*sp*cy - cr*sy;
190 mTGear(2,2) = sr*sp*sy + cr*cy;
193 mTGear(1,3) = cr*sp*cy + sr*sy;
194 mTGear(2,3) = cr*sp*sy - sr*cy;
203 if (sBrakeGroup == "LEFT" ) eBrakeGrp = bgLeft;
204 else if (sBrakeGroup == "RIGHT" ) eBrakeGrp = bgRight;
205 else if (sBrakeGroup == "CENTER") eBrakeGrp = bgCenter;
206 else if (sBrakeGroup == "NOSE" ) eBrakeGrp = bgNose;
207 else if (sBrakeGroup == "TAIL" ) eBrakeGrp = bgTail;
208 else if (sBrakeGroup == "NONE" ) eBrakeGrp = bgNone;
209 else if (sBrakeGroup.empty() ) {eBrakeGrp = bgNone;
210 sBrakeGroup = "NONE (defaulted)";}
212 cerr << "Improper braking group specification in config file: "
213 << sBrakeGroup << " is undefined." << endl;
216 if (sSteerType == "STEERABLE") eSteerType = stSteer;
217 else if (sSteerType == "FIXED" ) eSteerType = stFixed;
218 else if (sSteerType == "CASTERED" ) {eSteerType = stCaster; Castered = true;}
219 else if (sSteerType.empty() ) {eSteerType = stFixed;
220 sSteerType = "FIXED (defaulted)";}
222 cerr << "Improper steering type specification in config file: "
223 << sSteerType << " is undefined." << endl;
229 useFCSGearPos = false;
232 // Add some AI here to determine if gear is located properly according to its
233 // brake group type ??
235 WOW = lastWOW = false;
237 FirstContact = false;
238 StartedGroundRun = false;
239 TakeoffReported = LandingReported = false;
240 LandingDistanceTraveled = TakeoffDistanceTraveled = TakeoffDistanceTraveled50ft = 0.0;
241 MaximumStrutForce = MaximumStrutTravel = 0.0;
242 SinkRate = GroundSpeed = 0.0;
244 vWhlVelVec.InitMatrix();
246 compressLength = 0.0;
252 TirePressureNorm = 1.0;
261 // Initialize Lagrange multipliers
262 memset(LMultiplier, 0, sizeof(LMultiplier));
267 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
275 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
277 FGColumnVector3& FGLGear::GetBodyForces(void)
279 double t = fdmex->GetSimTime();
283 if (isRetractable) ComputeRetractionState();
286 FGColumnVector3 terrainVel, dummy;
288 vLocalGear = in.Tb2l * in.vWhlBodyVec[GearNumber]; // Get local frame wheel location
290 gearLoc = in.Location.LocalToLocation(vLocalGear);
291 // Compute the height of the theoretical location of the wheel (if strut is
292 // not compressed) with respect to the ground level
293 double height = fdmex->GetGroundCallback()->GetAGLevel(t, gearLoc, contact, normal, terrainVel, dummy);
294 vGroundNormal = in.Tec2b * normal;
296 // The height returned above is the AGL and is expressed in the Z direction
297 // of the ECEF coordinate frame. We now need to transform this height in
298 // actual compression of the strut (BOGEY) of in the normal direction to the
299 // ground (STRUCTURE)
300 double normalZ = (in.Tec2l*normal)(eZ);
301 double LGearProj = -(mTGear.Transposed() * vGroundNormal)(eZ);
303 switch (eContactType) {
305 compressLength = LGearProj > 0.0 ? height * normalZ / LGearProj : 0.0;
308 compressLength = height * normalZ / DotProduct(normal, normal);
312 if (compressLength > 0.00) {
315 // The following equations use the vector to the tire contact patch
316 // including the strut compression.
317 FGColumnVector3 vWhlDisplVec;
319 switch(eContactType) {
321 vWhlDisplVec = mTGear * FGColumnVector3(0., 0., -compressLength);
324 vWhlDisplVec = compressLength * vGroundNormal;
328 FGColumnVector3 vWhlContactVec = in.vWhlBodyVec[GearNumber] + vWhlDisplVec;
329 vActingXYZn = vXYZn + Tb2s * vWhlDisplVec;
330 FGColumnVector3 vBodyWhlVel = in.PQR * vWhlContactVec;
331 vBodyWhlVel += in.UVW - in.Tec2b * terrainVel;
333 vWhlVelVec = mTGear.Transposed() * vBodyWhlVel;
335 InitializeReporting();
336 ComputeSteeringAngle();
337 ComputeGroundCoordSys();
339 vLocalWhlVel = Transform().Transposed() * vBodyWhlVel;
341 if (fdmex->GetTrimStatus())
342 compressSpeed = 0.0; // Steady state is sought during trimming
344 compressSpeed = -vLocalWhlVel(eX);
345 if (eContactType == ctBOGEY)
346 compressSpeed /= LGearProj;
349 ComputeVerticalStrutForce();
351 // Compute the friction coefficients in the wheel ground plane.
352 if (eContactType == ctBOGEY) {
354 ComputeBrakeForceCoefficient();
355 ComputeSideForceCoefficient();
358 // Prepare the Jacobians and the Lagrange multipliers for later friction
359 // forces calculations.
360 ComputeJacobian(vWhlContactVec);
362 } else { // Gear is NOT compressed
365 compressLength = 0.0;
370 // Let wheel spin down slowly
371 vWhlVelVec(eX) -= 13.0 * in.TotalDeltaT;
372 if (vWhlVelVec(eX) < 0.0) vWhlVelVec(eX) = 0.0;
374 // Return to neutral position between 1.0 and 0.8 gear pos.
375 SteerAngle *= max(GetGearUnitPos()-0.8, 0.0)/0.2;
381 if (!fdmex->GetTrimStatus()) {
382 ReportTakeoffOrLanding();
384 // Require both WOW and LastWOW to be true before checking crash conditions
385 // to allow the WOW flag to be used in terminating a scripted run.
386 if (WOW && lastWOW) CrashDetect();
391 return FGForce::GetBodyForces();
394 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
395 // Build a local "ground" coordinate system defined by
396 // eX : normal to the ground
397 // eY : projection of the rolling direction on the ground
398 // eZ : projection of the sliping direction on the ground
400 void FGLGear::ComputeGroundCoordSys(void)
402 // Euler angles are built up to create a local frame to describe the forces
403 // applied to the gear by the ground. Here pitch, yaw and roll do not have
404 // any physical meaning. It is just a convenient notation.
405 // First, "pitch" and "yaw" are determined in order to align eX with the
407 if (vGroundNormal(eZ) < -1.0)
408 vOrient(ePitch) = 0.5*M_PI;
409 else if (1.0 < vGroundNormal(eZ))
410 vOrient(ePitch) = -0.5*M_PI;
412 vOrient(ePitch) = asin(-vGroundNormal(eZ));
414 if (fabs(vOrient(ePitch)) == 0.5*M_PI)
417 vOrient(eYaw) = atan2(vGroundNormal(eY), vGroundNormal(eX));
420 UpdateCustomTransformMatrix();
422 if (eContactType == ctBOGEY) {
423 // In the case of a bogey, the third angle "roll" is used to align the axis eY and eZ
424 // to the rolling and sliping direction respectively.
425 FGColumnVector3 updatedRollingAxis = Transform().Transposed() * mTGear
426 * FGColumnVector3(-sin(SteerAngle), cos(SteerAngle), 0.);
428 vOrient(eRoll) = atan2(updatedRollingAxis(eY), -updatedRollingAxis(eZ));
429 UpdateCustomTransformMatrix();
433 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
435 void FGLGear::ComputeRetractionState(void)
437 double gearPos = GetGearUnitPos();
438 if (gearPos < 0.01) {
442 vWhlVelVec.InitMatrix();
443 } else if (gearPos > 0.99) {
452 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
453 // Calculate tire slip angle.
455 void FGLGear::ComputeSlipAngle(void)
457 // Check that the speed is non-null otherwise use the current angle
458 if (vLocalWhlVel.Magnitude(eY,eZ) > 1E-3)
459 WheelSlip = -atan2(vLocalWhlVel(eZ), fabs(vLocalWhlVel(eY)))*radtodeg;
462 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
463 // Compute the steering angle in any case.
464 // This will also make sure that animations will look right.
466 void FGLGear::ComputeSteeringAngle(void)
468 switch (eSteerType) {
470 SteerAngle = degtorad * in.SteerPosDeg[GearNumber];
477 SteerAngle = degtorad * in.SteerPosDeg[GearNumber];
479 // Check that the speed is non-null otherwise use the current angle
480 if (vWhlVelVec.Magnitude(eX,eY) > 0.1)
481 SteerAngle = atan2(vWhlVelVec(eY), fabs(vWhlVelVec(eX)));
485 cerr << "Improper steering type membership detected for this gear." << endl;
490 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
491 // Reset reporting functionality after takeoff
493 void FGLGear::ResetReporting(void)
495 if (in.DistanceAGL > 200.0) {
496 FirstContact = false;
497 StartedGroundRun = false;
498 LandingReported = false;
499 TakeoffReported = true;
500 LandingDistanceTraveled = 0.0;
501 MaximumStrutForce = MaximumStrutTravel = 0.0;
505 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
507 void FGLGear::InitializeReporting(void)
509 // If this is the first time the wheel has made contact, remember some values
510 // for later printout.
514 SinkRate = compressSpeed;
515 GroundSpeed = in.Vground;
516 TakeoffReported = false;
519 // If the takeoff run is starting, initialize.
521 if ((in.Vground > 0.1) &&
522 (in.BrakePos[bgLeft] == 0) &&
523 (in.BrakePos[bgRight] == 0) &&
524 (in.TakeoffThrottle && !StartedGroundRun))
526 TakeoffDistanceTraveled = 0;
527 TakeoffDistanceTraveled50ft = 0;
528 StartedGroundRun = true;
532 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
533 // Takeoff and landing reporting functionality
535 void FGLGear::ReportTakeoffOrLanding(void)
538 LandingDistanceTraveled += in.Vground * in.TotalDeltaT;
540 if (StartedGroundRun) {
541 TakeoffDistanceTraveled50ft += in.Vground * in.TotalDeltaT;
542 if (WOW) TakeoffDistanceTraveled += in.Vground * in.TotalDeltaT;
546 && in.Vground <= 0.05
550 if (debug_lvl > 0) Report(erLand);
555 && (in.DistanceAGL - vLocalGear(eZ)) > 50.0
558 if (debug_lvl > 0) Report(erTakeoff);
561 if (lastWOW != WOW) PutMessage("GEAR_CONTACT: " + name, WOW);
564 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
565 // Crash detection logic (really out-of-bounds detection)
567 void FGLGear::CrashDetect(void)
569 if ( (compressLength > 500.0 ||
570 vFn.Magnitude() > 100000000.0 ||
571 GetMoments().Magnitude() > 5000000000.0 ||
572 SinkRate > 1.4666*30 ) && !fdmex->IntegrationSuspended())
574 PutMessage("Crash Detected: Simulation FREEZE.");
575 fdmex->SuspendIntegration();
579 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
580 // The following needs work regarding friction coefficients and braking and
581 // steering The BrakeFCoeff formula assumes that an anti-skid system is used.
582 // It also assumes that we won't be turning and braking at the same time.
583 // Will fix this later.
584 // [JSB] The braking force coefficients include normal rolling coefficient +
585 // a percentage of the static friction coefficient based on braking applied.
587 void FGLGear::ComputeBrakeForceCoefficient(void)
591 BrakeFCoeff = ( rollingFCoeff * (1.0 - in.BrakePos[bgLeft]) +
592 staticFCoeff * in.BrakePos[bgLeft] );
595 BrakeFCoeff = ( rollingFCoeff * (1.0 - in.BrakePos[bgRight]) +
596 staticFCoeff * in.BrakePos[bgRight] );
599 BrakeFCoeff = ( rollingFCoeff * (1.0 - in.BrakePos[bgCenter]) +
600 staticFCoeff * in.BrakePos[bgCenter] );
603 BrakeFCoeff = ( rollingFCoeff * (1.0 - in.BrakePos[bgCenter]) +
604 staticFCoeff * in.BrakePos[bgCenter] );
607 BrakeFCoeff = ( rollingFCoeff * (1.0 - in.BrakePos[bgCenter]) +
608 staticFCoeff * in.BrakePos[bgCenter] );
611 BrakeFCoeff = rollingFCoeff;
614 cerr << "Improper brake group membership detected for this gear." << endl;
619 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
620 // Compute the sideforce coefficients using Pacejka's Magic Formula.
622 // y(x) = D sin {C arctan [Bx - E(Bx - arctan Bx)]}
624 // Where: B = Stiffness Factor (0.06, here)
625 // C = Shape Factor (2.8, here)
626 // D = Peak Factor (0.8, here)
627 // E = Curvature Factor (1.03, here)
629 void FGLGear::ComputeSideForceCoefficient(void)
632 FCoeff = ForceY_Table->GetValue(WheelSlip);
634 double StiffSlip = Stiffness*WheelSlip;
635 FCoeff = Peak * sin(Shape*atan(StiffSlip - Curvature*(StiffSlip - atan(StiffSlip))));
639 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
640 // Compute the vertical force on the wheel using square-law damping (per comment
641 // in paper AIAA-2000-4303 - see header prologue comments). We might consider
642 // allowing for both square and linear damping force calculation. Also need to
643 // possibly give a "rebound damping factor" that differs from the compression
646 void FGLGear::ComputeVerticalStrutForce(void)
648 double springForce = 0;
649 double dampForce = 0;
651 springForce = -compressLength * kSpring;
653 if (compressSpeed >= 0.0) {
655 if (eDampType == dtLinear) dampForce = -compressSpeed * bDamp;
656 else dampForce = -compressSpeed * compressSpeed * bDamp;
660 if (eDampTypeRebound == dtLinear)
661 dampForce = -compressSpeed * bDampRebound;
663 dampForce = compressSpeed * compressSpeed * bDampRebound;
667 StrutForce = min(springForce + dampForce, (double)0.0);
669 // The reaction force of the wheel is always normal to the ground
670 switch (eContactType) {
672 // Project back the strut force in the local coordinate frame of the ground
673 vFn(eX) = StrutForce / (mTGear.Transposed()*vGroundNormal)(eZ);
676 vFn(eX) = -StrutForce;
680 // Remember these values for reporting
681 MaximumStrutForce = max(MaximumStrutForce, fabs(StrutForce));
682 MaximumStrutTravel = max(MaximumStrutTravel, fabs(compressLength));
685 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
687 double FGLGear::GetGearUnitPos(void)
689 // hack to provide backward compatibility to gear/gear-pos-norm property
690 if( useFCSGearPos || in.FCSGearPos != 1.0 ) {
691 useFCSGearPos = true;
692 return in.FCSGearPos;
697 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
698 // Compute the jacobian entries for the friction forces resolution later
701 void FGLGear::ComputeJacobian(const FGColumnVector3& vWhlContactVec)
703 // When the point of contact is moving, dynamic friction is used
704 // This type of friction is limited to ctSTRUCTURE elements because their
705 // friction coefficient is the same in every directions
706 if ((eContactType == ctSTRUCTURE) && (vLocalWhlVel.Magnitude(eY,eZ) > 1E-3)) {
707 FGColumnVector3 velocityDirection = vLocalWhlVel;
709 StaticFriction = false;
711 velocityDirection(eX) = 0.;
712 velocityDirection.Normalize();
714 LMultiplier[ftDynamic].ForceJacobian = Transform()*velocityDirection;
715 LMultiplier[ftDynamic].MomentJacobian = vWhlContactVec * LMultiplier[ftDynamic].ForceJacobian;
716 LMultiplier[ftDynamic].Max = 0.;
717 LMultiplier[ftDynamic].Min = -fabs(dynamicFCoeff * vFn(eX));
719 // The Lagrange multiplier value obtained from the previous iteration is kept
720 // This is supposed to accelerate the convergence of the projected Gauss-Seidel
721 // algorithm. The code just below is to make sure that the initial value
722 // is consistent with the current friction coefficient and normal reaction.
723 LMultiplier[ftDynamic].value = Constrain(LMultiplier[ftDynamic].Min, LMultiplier[ftDynamic].value, LMultiplier[ftDynamic].Max);
725 GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftDynamic]);
728 // Static friction is used for ctSTRUCTURE when the contact point is not moving.
729 // It is always used for ctBOGEY elements because the friction coefficients
730 // of a tyre depend on the direction of the movement (roll & side directions).
731 // This cannot be handled properly by the so-called "dynamic friction".
732 StaticFriction = true;
734 LMultiplier[ftRoll].ForceJacobian = Transform()*FGColumnVector3(0.,1.,0.);
735 LMultiplier[ftSide].ForceJacobian = Transform()*FGColumnVector3(0.,0.,1.);
736 LMultiplier[ftRoll].MomentJacobian = vWhlContactVec * LMultiplier[ftRoll].ForceJacobian;
737 LMultiplier[ftSide].MomentJacobian = vWhlContactVec * LMultiplier[ftSide].ForceJacobian;
739 switch(eContactType) {
741 LMultiplier[ftRoll].Max = fabs(BrakeFCoeff * vFn(eX));
742 LMultiplier[ftSide].Max = fabs(FCoeff * vFn(eX));
745 LMultiplier[ftRoll].Max = fabs(staticFCoeff * vFn(eX));
746 LMultiplier[ftSide].Max = fabs(staticFCoeff * vFn(eX));
750 LMultiplier[ftRoll].Min = -LMultiplier[ftRoll].Max;
751 LMultiplier[ftSide].Min = -LMultiplier[ftSide].Max;
753 // The Lagrange multiplier value obtained from the previous iteration is kept
754 // This is supposed to accelerate the convergence of the projected Gauss-Seidel
755 // algorithm. The code just below is to make sure that the initial value
756 // is consistent with the current friction coefficient and normal reaction.
757 LMultiplier[ftRoll].value = Constrain(LMultiplier[ftRoll].Min, LMultiplier[ftRoll].value, LMultiplier[ftRoll].Max);
758 LMultiplier[ftSide].value = Constrain(LMultiplier[ftSide].Min, LMultiplier[ftSide].value, LMultiplier[ftSide].Max);
760 GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftRoll]);
761 GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftSide]);
765 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
766 // This routine is called after the Lagrange multiplier has been computed in
767 // the FGAccelerations class. The friction forces of the landing gear are then
768 // updated accordingly.
769 void FGLGear::UpdateForces(void)
771 if (StaticFriction) {
772 vFn(eY) = LMultiplier[ftRoll].value;
773 vFn(eZ) = LMultiplier[ftSide].value;
776 vFn += LMultiplier[ftDynamic].value * (Transform ().Transposed() * LMultiplier[ftDynamic].ForceJacobian);
779 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
781 void FGLGear::bind(void)
783 string property_name;
784 string base_property_name;
786 switch(eContactType) {
788 base_property_name = CreateIndexedPropertyName("gear/unit", GearNumber);
791 base_property_name = CreateIndexedPropertyName("contact/unit", GearNumber);
797 property_name = base_property_name + "/WOW";
798 PropertyManager->Tie( property_name.c_str(), &WOW );
799 property_name = base_property_name + "/z-position";
800 PropertyManager->Tie( property_name.c_str(), (FGForce*)this,
801 &FGForce::GetLocationZ, &FGForce::SetLocationZ);
802 property_name = base_property_name + "/compression-ft";
803 PropertyManager->Tie( property_name.c_str(), &compressLength );
804 property_name = base_property_name + "/static_friction_coeff";
805 PropertyManager->Tie( property_name.c_str(), &staticFCoeff );
806 property_name = base_property_name + "/dynamic_friction_coeff";
807 PropertyManager->Tie( property_name.c_str(), &dynamicFCoeff );
809 if (eContactType == ctBOGEY) {
810 property_name = base_property_name + "/slip-angle-deg";
811 PropertyManager->Tie( property_name.c_str(), &WheelSlip );
812 property_name = base_property_name + "/wheel-speed-fps";
813 PropertyManager->Tie( property_name.c_str(), (FGLGear*)this,
814 &FGLGear::GetWheelRollVel);
815 property_name = base_property_name + "/side_friction_coeff";
816 PropertyManager->Tie( property_name.c_str(), &FCoeff );
817 property_name = base_property_name + "/rolling_friction_coeff";
818 PropertyManager->Tie( property_name.c_str(), &rollingFCoeff );
820 if (eSteerType == stCaster) {
821 property_name = base_property_name + "/steering-angle-deg";
822 PropertyManager->Tie( property_name.c_str(), this, &FGLGear::GetSteerAngleDeg );
823 property_name = base_property_name + "/castered";
824 PropertyManager->Tie( property_name.c_str(), &Castered);
828 if( isRetractable ) {
829 property_name = base_property_name + "/pos-norm";
830 PropertyManager->Tie( property_name.c_str(), &GearPos );
834 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
836 void FGLGear::Report(ReportType repType)
838 if (fabs(TakeoffDistanceTraveled) < 0.001) return; // Don't print superfluous reports
842 cout << endl << "Touchdown report for " << name << " (WOW at time: "
843 << fdmex->GetSimTime() << " seconds)" << endl;
844 cout << " Sink rate at contact: " << SinkRate << " fps, "
845 << SinkRate*0.3048 << " mps" << endl;
846 cout << " Contact ground speed: " << GroundSpeed*.5925 << " knots, "
847 << GroundSpeed*0.3048 << " mps" << endl;
848 cout << " Maximum contact force: " << MaximumStrutForce << " lbs, "
849 << MaximumStrutForce*4.448 << " Newtons" << endl;
850 cout << " Maximum strut travel: " << MaximumStrutTravel*12.0 << " inches, "
851 << MaximumStrutTravel*30.48 << " cm" << endl;
852 cout << " Distance traveled: " << LandingDistanceTraveled << " ft, "
853 << LandingDistanceTraveled*0.3048 << " meters" << endl;
854 LandingReported = true;
857 cout << endl << "Takeoff report for " << name << " (Liftoff at time: "
858 << fdmex->GetSimTime() << " seconds)" << endl;
859 cout << " Distance traveled: " << TakeoffDistanceTraveled
860 << " ft, " << TakeoffDistanceTraveled*0.3048 << " meters" << endl;
861 cout << " Distance traveled (over 50'): " << TakeoffDistanceTraveled50ft
862 << " ft, " << TakeoffDistanceTraveled50ft*0.3048 << " meters" << endl;
863 cout << " [Altitude (ASL): " << in.DistanceASL << " ft. / "
864 << in.DistanceASL*FGJSBBase::fttom << " m | Temperature: "
865 << in.Temperature - 459.67 << " F / "
866 << RankineToCelsius(in.Temperature) << " C]" << endl;
867 cout << " [Velocity (KCAS): " << in.VcalibratedKts << "]" << endl;
868 TakeoffReported = true;
875 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
876 // The bitmasked value choices are as follows:
877 // unset: In this case (the default) JSBSim would only print
878 // out the normally expected messages, essentially echoing
879 // the config files as they are read. If the environment
880 // variable is not set, debug_lvl is set to 1 internally
881 // 0: This requests JSBSim not to output any messages
883 // 1: This value explicity requests the normal JSBSim
885 // 2: This value asks for a message to be printed out when
886 // a class is instantiated
887 // 4: When this value is set, a message is displayed when a
888 // FGModel object executes its Run() method
889 // 8: When this value is set, various runtime state variables
890 // are printed out periodically
891 // 16: When set various parameters are sanity checked and
892 // a message is printed out when they go out of bounds
894 void FGLGear::Debug(int from)
896 if (debug_lvl <= 0) return;
898 if (debug_lvl & 1) { // Standard console startup message output
899 if (from == 0) { // Constructor - loading and initialization
900 cout << " " << sContactType << " " << name << endl;
901 cout << " Location: " << vXYZn << endl;
902 cout << " Spring Constant: " << kSpring << endl;
904 if (eDampType == dtLinear)
905 cout << " Damping Constant: " << bDamp << " (linear)" << endl;
907 cout << " Damping Constant: " << bDamp << " (square law)" << endl;
909 if (eDampTypeRebound == dtLinear)
910 cout << " Rebound Damping Constant: " << bDampRebound << " (linear)" << endl;
912 cout << " Rebound Damping Constant: " << bDampRebound << " (square law)" << endl;
914 cout << " Dynamic Friction: " << dynamicFCoeff << endl;
915 cout << " Static Friction: " << staticFCoeff << endl;
916 if (eContactType == ctBOGEY) {
917 cout << " Rolling Friction: " << rollingFCoeff << endl;
918 cout << " Steering Type: " << sSteerType << endl;
919 cout << " Grouping: " << sBrakeGroup << endl;
920 cout << " Max Steer Angle: " << maxSteerAngle << endl;
921 cout << " Retractable: " << isRetractable << endl;
925 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
926 if (from == 0) cout << "Instantiated: FGLGear" << endl;
927 if (from == 1) cout << "Destroyed: FGLGear" << endl;
929 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
931 if (debug_lvl & 8 ) { // Runtime state variables
933 if (debug_lvl & 16) { // Sanity checking
935 if (debug_lvl & 64) {
936 if (from == 0) { // Constructor
937 cout << IdSrc << endl;
938 cout << IdHdr << endl;
943 } // namespace JSBSim