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.100 2012/04/01 17:05:51 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);
69 const FGMatrix33 FGLGear::Ts2b(-inchtoft, 0., 0., 0., inchtoft, 0., 0., 0., -inchtoft);
71 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
73 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
75 FGLGear::FGLGear(Element* el, FGFDMExec* fdmex, int number, const struct Inputs& inputs) :
83 kSpring = bDamp = bDampRebound = dynamicFCoeff = staticFCoeff = rollingFCoeff = maxSteerAngle = 0;
84 isRetractable = false;
86 eDampTypeRebound = dtLinear;
88 name = el->GetAttributeValue("name");
89 string sContactType = el->GetAttributeValue("type");
90 if (sContactType == "BOGEY") {
91 eContactType = ctBOGEY;
92 } else if (sContactType == "STRUCTURE") {
93 eContactType = ctSTRUCTURE;
95 // Unknown contact point types will be treated as STRUCTURE.
96 eContactType = ctSTRUCTURE;
99 // Default values for structural contact points
100 if (eContactType == ctSTRUCTURE) {
101 kSpring = in.EmptyWeight;
103 bDampRebound = kSpring * 10;
108 if (el->FindElement("spring_coeff"))
109 kSpring = el->FindElementValueAsNumberConvertTo("spring_coeff", "LBS/FT");
110 if (el->FindElement("damping_coeff")) {
111 Element* dampCoeff = el->FindElement("damping_coeff");
112 if (dampCoeff->GetAttributeValue("type") == "SQUARE") {
113 eDampType = dtSquare;
114 bDamp = el->FindElementValueAsNumberConvertTo("damping_coeff", "LBS/FT2/SEC2");
116 bDamp = el->FindElementValueAsNumberConvertTo("damping_coeff", "LBS/FT/SEC");
120 if (el->FindElement("damping_coeff_rebound")) {
121 Element* dampCoeffRebound = el->FindElement("damping_coeff_rebound");
122 if (dampCoeffRebound->GetAttributeValue("type") == "SQUARE") {
123 eDampTypeRebound = dtSquare;
124 bDampRebound = el->FindElementValueAsNumberConvertTo("damping_coeff_rebound", "LBS/FT2/SEC2");
126 bDampRebound = el->FindElementValueAsNumberConvertTo("damping_coeff_rebound", "LBS/FT/SEC");
129 bDampRebound = bDamp;
130 eDampTypeRebound = eDampType;
133 if (el->FindElement("dynamic_friction"))
134 dynamicFCoeff = el->FindElementValueAsNumber("dynamic_friction");
135 if (el->FindElement("static_friction"))
136 staticFCoeff = el->FindElementValueAsNumber("static_friction");
137 if (el->FindElement("rolling_friction"))
138 rollingFCoeff = el->FindElementValueAsNumber("rolling_friction");
139 if (el->FindElement("retractable"))
140 isRetractable = ((unsigned int)el->FindElementValueAsNumber("retractable"))>0.0?true:false;
142 if (el->FindElement("max_steer"))
143 maxSteerAngle = el->FindElementValueAsNumberConvertTo("max_steer", "DEG");
145 if (maxSteerAngle == 360) {
146 eSteerType = stCaster;
149 else if (maxSteerAngle == 0.0) {
150 eSteerType = stFixed;
153 eSteerType = stSteer;
155 GroundReactions = fdmex->GetGroundReactions();
156 PropertyManager = fdmex->GetPropertyManager();
159 Element* force_table = el->FindElement("table");
160 while (force_table) {
161 string force_type = force_table->GetAttributeValue("type");
162 if (force_type == "CORNERING_COEFF") {
163 ForceY_Table = new FGTable(PropertyManager, force_table);
166 cerr << "Undefined force table for " << name << " contact point" << endl;
168 force_table = el->FindNextElement("table");
171 Element* element = el->FindElement("location");
172 if (element) vXYZn = element->FindElementTripletConvertTo("IN");
173 else {cerr << "No location given for contact " << name << endl; exit(-1);}
174 SetTransformType(FGForce::tCustom);
176 element = el->FindElement("orientation");
177 if (element && (eContactType == ctBOGEY)) {
178 FGQuaternion quatFromEuler(element->FindElementTripletConvertTo("RAD"));
180 mTGear = quatFromEuler.GetT();
188 string sBrakeGroup = el->FindElementValue("brake_group");
190 if (sBrakeGroup == "LEFT" ) eBrakeGrp = bgLeft;
191 else if (sBrakeGroup == "RIGHT" ) eBrakeGrp = bgRight;
192 else if (sBrakeGroup == "CENTER") eBrakeGrp = bgCenter;
193 else if (sBrakeGroup == "NOSE" ) eBrakeGrp = bgCenter; // Nose brake is not supported by FGFCS
194 else if (sBrakeGroup == "TAIL" ) eBrakeGrp = bgCenter; // Tail brake is not supported by FGFCS
195 else if (sBrakeGroup == "NONE" ) eBrakeGrp = bgNone;
196 else if (sBrakeGroup.empty() ) eBrakeGrp = bgNone;
198 cerr << "Improper braking group specification in config file: "
199 << sBrakeGroup << " is undefined." << endl;
203 useFCSGearPos = false;
205 // Add some AI here to determine if gear is located properly according to its
206 // brake group type ??
208 WOW = lastWOW = false;
210 FirstContact = false;
211 StartedGroundRun = false;
212 TakeoffReported = LandingReported = false;
213 LandingDistanceTraveled = TakeoffDistanceTraveled = TakeoffDistanceTraveled50ft = 0.0;
214 MaximumStrutForce = MaximumStrutTravel = 0.0;
215 SinkRate = GroundSpeed = 0.0;
217 vWhlVelVec.InitMatrix();
219 compressLength = 0.0;
232 // Initialize Lagrange multipliers
233 memset(LMultiplier, 0, sizeof(LMultiplier));
238 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
246 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
248 const FGColumnVector3& FGLGear::GetBodyForces(void)
250 double gearPos = 1.0;
251 double t = fdmex->GetSimTime();
255 if (isRetractable) gearPos = GetGearUnitPos();
257 if (gearPos > 0.99) { // Gear DOWN
258 FGColumnVector3 normal, terrainVel, dummy;
259 FGLocation gearLoc, contact;
260 FGColumnVector3 vWhlBodyVec = Ts2b * (vXYZn - in.vXYZcg);
262 vLocalGear = in.Tb2l * vWhlBodyVec; // Get local frame wheel location
263 gearLoc = in.Location.LocalToLocation(vLocalGear);
265 // Compute the height of the theoretical location of the wheel (if strut is
266 // not compressed) with respect to the ground level
267 double height = gearLoc.GetContactPoint(t, contact, normal, terrainVel, dummy);
271 vGroundNormal = in.Tec2b * normal;
273 // The height returned by GetGroundCallback() is the AGL and is expressed
274 // in the Z direction of the local coordinate frame. We now need to transform
275 // this height in actual compression of the strut (BOGEY) or in the normal
276 // direction to the ground (STRUCTURE)
277 double normalZ = (in.Tec2l*normal)(eZ);
278 double LGearProj = -(mTGear.Transposed() * vGroundNormal)(eZ);
279 FGColumnVector3 vWhlDisplVec;
281 // The following equations use the vector to the tire contact patch
282 // including the strut compression.
283 switch(eContactType) {
285 compressLength = LGearProj > 0.0 ? height * normalZ / LGearProj : 0.0;
286 vWhlDisplVec = mTGear * FGColumnVector3(0., 0., -compressLength);
289 compressLength = height * normalZ / DotProduct(normal, normal);
290 vWhlDisplVec = compressLength * vGroundNormal;
294 FGColumnVector3 vWhlContactVec = vWhlBodyVec + vWhlDisplVec;
295 vActingXYZn = vXYZn + Tb2s * vWhlDisplVec;
296 FGColumnVector3 vBodyWhlVel = in.PQR * vWhlContactVec;
297 vBodyWhlVel += in.UVW - in.Tec2b * terrainVel;
299 vWhlVelVec = mTGear.Transposed() * vBodyWhlVel;
301 InitializeReporting();
302 ComputeSteeringAngle();
303 ComputeGroundFrame();
305 vGroundWhlVel = mT.Transposed() * vBodyWhlVel;
307 if (fdmex->GetTrimStatus())
308 compressSpeed = 0.0; // Steady state is sought during trimming
310 compressSpeed = -vGroundWhlVel(eZ);
311 if (eContactType == ctBOGEY)
312 compressSpeed /= LGearProj;
315 ComputeVerticalStrutForce();
317 // Compute the friction coefficients in the wheel ground plane.
318 if (eContactType == ctBOGEY) {
320 ComputeBrakeForceCoefficient();
321 ComputeSideForceCoefficient();
324 // Prepare the Jacobians and the Lagrange multipliers for later friction
325 // forces calculations.
326 ComputeJacobian(vWhlContactVec);
328 } else { // Gear is NOT compressed
331 compressLength = 0.0;
336 LMultiplier[ftRoll].value = 0.0;
337 LMultiplier[ftSide].value = 0.0;
338 LMultiplier[ftDynamic].value = 0.0;
340 // Let wheel spin down slowly
341 vWhlVelVec(eX) -= 13.0 * in.TotalDeltaT;
342 if (vWhlVelVec(eX) < 0.0) vWhlVelVec(eX) = 0.0;
344 // Return to neutral position between 1.0 and 0.8 gear pos.
345 SteerAngle *= max(gearPos-0.8, 0.0)/0.2;
350 else if (gearPos < 0.01) { // Gear UP
352 vWhlVelVec.InitMatrix();
355 if (!fdmex->GetTrimStatus()) {
356 ReportTakeoffOrLanding();
358 // Require both WOW and LastWOW to be true before checking crash conditions
359 // to allow the WOW flag to be used in terminating a scripted run.
360 if (WOW && lastWOW) CrashDetect();
365 return FGForce::GetBodyForces();
368 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
369 // Build a local "ground" coordinate system defined by
370 // eX : projection of the rolling direction on the ground
371 // eY : projection of the sliping direction on the ground
372 // eZ : normal to the ground
374 void FGLGear::ComputeGroundFrame(void)
376 FGColumnVector3 roll = mTGear * FGColumnVector3(cos(SteerAngle), sin(SteerAngle), 0.);
377 FGColumnVector3 side = vGroundNormal * roll;
379 roll -= DotProduct(roll, vGroundNormal) * vGroundNormal;
383 mT(eX,eX) = roll(eX);
384 mT(eY,eX) = roll(eY);
385 mT(eZ,eX) = roll(eZ);
386 mT(eX,eY) = side(eX);
387 mT(eY,eY) = side(eY);
388 mT(eZ,eY) = side(eZ);
389 mT(eX,eZ) = vGroundNormal(eX);
390 mT(eY,eZ) = vGroundNormal(eY);
391 mT(eZ,eZ) = vGroundNormal(eZ);
394 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
395 // Calculate tire slip angle.
397 void FGLGear::ComputeSlipAngle(void)
399 // Check that the speed is non-null otherwise use the current angle
400 if (vGroundWhlVel.Magnitude(eX,eY) > 1E-3)
401 WheelSlip = -atan2(vGroundWhlVel(eY), fabs(vGroundWhlVel(eX)))*radtodeg;
404 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
405 // Compute the steering angle in any case.
406 // This will also make sure that animations will look right.
408 void FGLGear::ComputeSteeringAngle(void)
410 switch (eSteerType) {
412 SteerAngle = degtorad * in.SteerPosDeg[GearNumber];
419 SteerAngle = degtorad * in.SteerPosDeg[GearNumber];
421 // Check that the speed is non-null otherwise use the current angle
422 if (vWhlVelVec.Magnitude(eX,eY) > 0.1)
423 SteerAngle = atan2(vWhlVelVec(eY), fabs(vWhlVelVec(eX)));
427 cerr << "Improper steering type membership detected for this gear." << endl;
432 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
433 // Reset reporting functionality after takeoff
435 void FGLGear::ResetReporting(void)
437 if (in.DistanceAGL > 200.0) {
438 FirstContact = false;
439 StartedGroundRun = false;
440 LandingReported = false;
441 TakeoffReported = true;
442 LandingDistanceTraveled = 0.0;
443 MaximumStrutForce = MaximumStrutTravel = 0.0;
447 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
449 void FGLGear::InitializeReporting(void)
451 // If this is the first time the wheel has made contact, remember some values
452 // for later printout.
456 SinkRate = compressSpeed;
457 GroundSpeed = in.Vground;
458 TakeoffReported = false;
461 // If the takeoff run is starting, initialize.
463 if ((in.Vground > 0.1) &&
464 (in.BrakePos[bgLeft] == 0) &&
465 (in.BrakePos[bgRight] == 0) &&
466 (in.TakeoffThrottle && !StartedGroundRun))
468 TakeoffDistanceTraveled = 0;
469 TakeoffDistanceTraveled50ft = 0;
470 StartedGroundRun = true;
474 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
475 // Takeoff and landing reporting functionality
477 void FGLGear::ReportTakeoffOrLanding(void)
480 LandingDistanceTraveled += in.Vground * in.TotalDeltaT;
482 if (StartedGroundRun) {
483 TakeoffDistanceTraveled50ft += in.Vground * in.TotalDeltaT;
484 if (WOW) TakeoffDistanceTraveled += in.Vground * in.TotalDeltaT;
488 && in.Vground <= 0.05
492 if (debug_lvl > 0) Report(erLand);
497 && (in.DistanceAGL - vLocalGear(eZ)) > 50.0
500 if (debug_lvl > 0) Report(erTakeoff);
503 if (lastWOW != WOW) PutMessage("GEAR_CONTACT: " + name, WOW);
506 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
507 // Crash detection logic (really out-of-bounds detection)
509 void FGLGear::CrashDetect(void)
511 if ( (compressLength > 500.0 ||
512 vFn.Magnitude() > 100000000.0 ||
513 GetMoments().Magnitude() > 5000000000.0 ||
514 SinkRate > 1.4666*30 ) && !fdmex->IntegrationSuspended())
516 PutMessage("Crash Detected: Simulation FREEZE.");
517 fdmex->SuspendIntegration();
521 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
522 // The following needs work regarding friction coefficients and braking and
523 // steering The BrakeFCoeff formula assumes that an anti-skid system is used.
524 // It also assumes that we won't be turning and braking at the same time.
525 // Will fix this later.
526 // [JSB] The braking force coefficients include normal rolling coefficient +
527 // a percentage of the static friction coefficient based on braking applied.
529 void FGLGear::ComputeBrakeForceCoefficient(void)
531 BrakeFCoeff = rollingFCoeff;
533 if (eBrakeGrp != bgNone)
534 BrakeFCoeff += in.BrakePos[eBrakeGrp] * (staticFCoeff - rollingFCoeff);
537 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
538 // Compute the sideforce coefficients using Pacejka's Magic Formula.
540 // y(x) = D sin {C arctan [Bx - E(Bx - arctan Bx)]}
542 // Where: B = Stiffness Factor (0.06, here)
543 // C = Shape Factor (2.8, here)
544 // D = Peak Factor (0.8, here)
545 // E = Curvature Factor (1.03, here)
547 void FGLGear::ComputeSideForceCoefficient(void)
550 FCoeff = ForceY_Table->GetValue(WheelSlip);
552 double StiffSlip = Stiffness*WheelSlip;
553 FCoeff = Peak * sin(Shape*atan(StiffSlip - Curvature*(StiffSlip - atan(StiffSlip))));
557 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
558 // Compute the vertical force on the wheel using square-law damping (per comment
559 // in paper AIAA-2000-4303 - see header prologue comments). We might consider
560 // allowing for both square and linear damping force calculation. Also need to
561 // possibly give a "rebound damping factor" that differs from the compression
564 void FGLGear::ComputeVerticalStrutForce(void)
566 double springForce = 0;
567 double dampForce = 0;
569 springForce = -compressLength * kSpring;
571 if (compressSpeed >= 0.0) {
573 if (eDampType == dtLinear)
574 dampForce = -compressSpeed * bDamp;
576 dampForce = -compressSpeed * compressSpeed * bDamp;
580 if (eDampTypeRebound == dtLinear)
581 dampForce = -compressSpeed * bDampRebound;
583 dampForce = compressSpeed * compressSpeed * bDampRebound;
587 StrutForce = min(springForce + dampForce, (double)0.0);
589 // The reaction force of the wheel is always normal to the ground
590 switch (eContactType) {
592 // Project back the strut force in the local coordinate frame of the ground
593 vFn(eZ) = StrutForce / (mTGear.Transposed()*vGroundNormal)(eZ);
596 vFn(eZ) = -StrutForce;
600 // Remember these values for reporting
601 MaximumStrutForce = max(MaximumStrutForce, fabs(StrutForce));
602 MaximumStrutTravel = max(MaximumStrutTravel, fabs(compressLength));
605 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
607 double FGLGear::GetGearUnitPos(void) const
609 // hack to provide backward compatibility to gear/gear-pos-norm property
610 if( useFCSGearPos || in.FCSGearPos != 1.0 ) {
611 useFCSGearPos = true;
612 return in.FCSGearPos;
617 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
618 // Compute the jacobian entries for the friction forces resolution later
621 void FGLGear::ComputeJacobian(const FGColumnVector3& vWhlContactVec)
623 // When the point of contact is moving, dynamic friction is used
624 // This type of friction is limited to ctSTRUCTURE elements because their
625 // friction coefficient is the same in every directions
626 if ((eContactType == ctSTRUCTURE) && (vGroundWhlVel.Magnitude(eX,eY) > 1E-3)) {
628 FGColumnVector3 velocityDirection = vGroundWhlVel;
630 StaticFriction = false;
632 velocityDirection(eZ) = 0.;
633 velocityDirection.Normalize();
635 LMultiplier[ftDynamic].ForceJacobian = mT * velocityDirection;
636 LMultiplier[ftDynamic].MomentJacobian = vWhlContactVec * LMultiplier[ftDynamic].ForceJacobian;
637 LMultiplier[ftDynamic].Max = 0.;
638 LMultiplier[ftDynamic].Min = -fabs(dynamicFCoeff * vFn(eZ));
640 // The Lagrange multiplier value obtained from the previous iteration is kept
641 // This is supposed to accelerate the convergence of the projected Gauss-Seidel
642 // algorithm. The code just below is to make sure that the initial value
643 // is consistent with the current friction coefficient and normal reaction.
644 LMultiplier[ftDynamic].value = Constrain(LMultiplier[ftDynamic].Min, LMultiplier[ftDynamic].value, LMultiplier[ftDynamic].Max);
646 GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftDynamic]);
649 // Static friction is used for ctSTRUCTURE when the contact point is not moving.
650 // It is always used for ctBOGEY elements because the friction coefficients
651 // of a tyre depend on the direction of the movement (roll & side directions).
652 // This cannot be handled properly by the so-called "dynamic friction".
653 StaticFriction = true;
655 LMultiplier[ftRoll].ForceJacobian = mT * FGColumnVector3(1.,0.,0.);
656 LMultiplier[ftSide].ForceJacobian = mT * FGColumnVector3(0.,1.,0.);
657 LMultiplier[ftRoll].MomentJacobian = vWhlContactVec * LMultiplier[ftRoll].ForceJacobian;
658 LMultiplier[ftSide].MomentJacobian = vWhlContactVec * LMultiplier[ftSide].ForceJacobian;
660 switch(eContactType) {
662 LMultiplier[ftRoll].Max = fabs(BrakeFCoeff * vFn(eZ));
663 LMultiplier[ftSide].Max = fabs(FCoeff * vFn(eZ));
666 LMultiplier[ftRoll].Max = fabs(staticFCoeff * vFn(eZ));
667 LMultiplier[ftSide].Max = LMultiplier[ftRoll].Max;
671 LMultiplier[ftRoll].Min = -LMultiplier[ftRoll].Max;
672 LMultiplier[ftSide].Min = -LMultiplier[ftSide].Max;
674 // The Lagrange multiplier value obtained from the previous iteration is kept
675 // This is supposed to accelerate the convergence of the projected Gauss-Seidel
676 // algorithm. The code just below is to make sure that the initial value
677 // is consistent with the current friction coefficient and normal reaction.
678 LMultiplier[ftRoll].value = Constrain(LMultiplier[ftRoll].Min, LMultiplier[ftRoll].value, LMultiplier[ftRoll].Max);
679 LMultiplier[ftSide].value = Constrain(LMultiplier[ftSide].Min, LMultiplier[ftSide].value, LMultiplier[ftSide].Max);
681 GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftRoll]);
682 GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftSide]);
686 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
687 // This routine is called after the Lagrange multiplier has been computed in
688 // the FGAccelerations class. The friction forces of the landing gear are then
689 // updated accordingly.
690 void FGLGear::UpdateForces(void)
692 if (StaticFriction) {
693 vFn(eX) = LMultiplier[ftRoll].value;
694 vFn(eY) = LMultiplier[ftSide].value;
697 FGColumnVector3 forceDir = mT.Transposed() * LMultiplier[ftDynamic].ForceJacobian;
698 vFn(eX) = LMultiplier[ftDynamic].value * forceDir(eX);
699 vFn(eY) = LMultiplier[ftDynamic].value * forceDir(eY);
703 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
705 void FGLGear::bind(void)
707 string property_name;
708 string base_property_name;
710 switch(eContactType) {
712 base_property_name = CreateIndexedPropertyName("gear/unit", GearNumber);
715 base_property_name = CreateIndexedPropertyName("contact/unit", GearNumber);
721 property_name = base_property_name + "/WOW";
722 PropertyManager->Tie( property_name.c_str(), &WOW );
723 property_name = base_property_name + "/z-position";
724 PropertyManager->Tie( property_name.c_str(), (FGForce*)this,
725 &FGForce::GetLocationZ, &FGForce::SetLocationZ);
726 property_name = base_property_name + "/compression-ft";
727 PropertyManager->Tie( property_name.c_str(), &compressLength );
728 property_name = base_property_name + "/static_friction_coeff";
729 PropertyManager->Tie( property_name.c_str(), &staticFCoeff );
730 property_name = base_property_name + "/dynamic_friction_coeff";
731 PropertyManager->Tie( property_name.c_str(), &dynamicFCoeff );
733 if (eContactType == ctBOGEY) {
734 property_name = base_property_name + "/slip-angle-deg";
735 PropertyManager->Tie( property_name.c_str(), &WheelSlip );
736 property_name = base_property_name + "/wheel-speed-fps";
737 PropertyManager->Tie( property_name.c_str(), (FGLGear*)this,
738 &FGLGear::GetWheelRollVel);
739 property_name = base_property_name + "/side_friction_coeff";
740 PropertyManager->Tie( property_name.c_str(), &FCoeff );
741 property_name = base_property_name + "/rolling_friction_coeff";
742 PropertyManager->Tie( property_name.c_str(), &rollingFCoeff );
744 if (eSteerType == stCaster) {
745 property_name = base_property_name + "/steering-angle-deg";
746 PropertyManager->Tie( property_name.c_str(), this, &FGLGear::GetSteerAngleDeg );
747 property_name = base_property_name + "/castered";
748 PropertyManager->Tie( property_name.c_str(), &Castered);
752 if( isRetractable ) {
753 property_name = base_property_name + "/pos-norm";
754 PropertyManager->Tie( property_name.c_str(), &GearPos );
758 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
760 void FGLGear::Report(ReportType repType)
762 if (fabs(TakeoffDistanceTraveled) < 0.001) return; // Don't print superfluous reports
766 cout << endl << "Touchdown report for " << name << " (WOW at time: "
767 << fdmex->GetSimTime() << " seconds)" << endl;
768 cout << " Sink rate at contact: " << SinkRate << " fps, "
769 << SinkRate*0.3048 << " mps" << endl;
770 cout << " Contact ground speed: " << GroundSpeed*.5925 << " knots, "
771 << GroundSpeed*0.3048 << " mps" << endl;
772 cout << " Maximum contact force: " << MaximumStrutForce << " lbs, "
773 << MaximumStrutForce*4.448 << " Newtons" << endl;
774 cout << " Maximum strut travel: " << MaximumStrutTravel*12.0 << " inches, "
775 << MaximumStrutTravel*30.48 << " cm" << endl;
776 cout << " Distance traveled: " << LandingDistanceTraveled << " ft, "
777 << LandingDistanceTraveled*0.3048 << " meters" << endl;
778 LandingReported = true;
781 cout << endl << "Takeoff report for " << name << " (Liftoff at time: "
782 << fdmex->GetSimTime() << " seconds)" << endl;
783 cout << " Distance traveled: " << TakeoffDistanceTraveled
784 << " ft, " << TakeoffDistanceTraveled*0.3048 << " meters" << endl;
785 cout << " Distance traveled (over 50'): " << TakeoffDistanceTraveled50ft
786 << " ft, " << TakeoffDistanceTraveled50ft*0.3048 << " meters" << endl;
787 cout << " [Altitude (ASL): " << in.DistanceASL << " ft. / "
788 << in.DistanceASL*FGJSBBase::fttom << " m | Temperature: "
789 << in.Temperature - 459.67 << " F / "
790 << RankineToCelsius(in.Temperature) << " C]" << endl;
791 cout << " [Velocity (KCAS): " << in.VcalibratedKts << "]" << endl;
792 TakeoffReported = true;
799 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
800 // The bitmasked value choices are as follows:
801 // unset: In this case (the default) JSBSim would only print
802 // out the normally expected messages, essentially echoing
803 // the config files as they are read. If the environment
804 // variable is not set, debug_lvl is set to 1 internally
805 // 0: This requests JSBSim not to output any messages
807 // 1: This value explicity requests the normal JSBSim
809 // 2: This value asks for a message to be printed out when
810 // a class is instantiated
811 // 4: When this value is set, a message is displayed when a
812 // FGModel object executes its Run() method
813 // 8: When this value is set, various runtime state variables
814 // are printed out periodically
815 // 16: When set various parameters are sanity checked and
816 // a message is printed out when they go out of bounds
818 void FGLGear::Debug(int from)
820 static const char* sSteerType[] = {"STEERABLE", "FIXED", "CASTERED" };
821 static const char* sBrakeGroup[] = {"NONE", "LEFT", "RIGHT", "CENTER", "NOSE", "TAIL"};
822 static const char* sContactType[] = {"BOGEY", "STRUCTURE" };
824 if (debug_lvl <= 0) return;
826 if (debug_lvl & 1) { // Standard console startup message output
827 if (from == 0) { // Constructor - loading and initialization
828 cout << " " << sContactType[eContactType] << " " << name << endl;
829 cout << " Location: " << vXYZn << endl;
830 cout << " Spring Constant: " << kSpring << endl;
832 if (eDampType == dtLinear)
833 cout << " Damping Constant: " << bDamp << " (linear)" << endl;
835 cout << " Damping Constant: " << bDamp << " (square law)" << endl;
837 if (eDampTypeRebound == dtLinear)
838 cout << " Rebound Damping Constant: " << bDampRebound << " (linear)" << endl;
840 cout << " Rebound Damping Constant: " << bDampRebound << " (square law)" << endl;
842 cout << " Dynamic Friction: " << dynamicFCoeff << endl;
843 cout << " Static Friction: " << staticFCoeff << endl;
844 if (eContactType == ctBOGEY) {
845 cout << " Rolling Friction: " << rollingFCoeff << endl;
846 cout << " Steering Type: " << sSteerType[eSteerType] << endl;
847 cout << " Grouping: " << sBrakeGroup[eBrakeGrp] << endl;
848 cout << " Max Steer Angle: " << maxSteerAngle << endl;
849 cout << " Retractable: " << isRetractable << endl;
853 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
854 if (from == 0) cout << "Instantiated: FGLGear" << endl;
855 if (from == 1) cout << "Destroyed: FGLGear" << endl;
857 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
859 if (debug_lvl & 8 ) { // Runtime state variables
861 if (debug_lvl & 16) { // Sanity checking
863 if (debug_lvl & 64) {
864 if (from == 0) { // Constructor
865 cout << IdSrc << endl;
866 cout << IdHdr << endl;
871 } // namespace JSBSim