JSBSim Flight Dynamics Model 1.3.0 (09 Apr 2026)
An Open Source Flight Dynamics and Control Software Library in C++
Loading...
Searching...
No Matches
FGLGear.cpp
1/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2
3 Module: FGLGear.cpp
4 Author: Jon S. Berndt
5 Norman H. Princen
6 Bertrand Coconnier
7 Date started: 11/18/99
8 Purpose: Encapsulates the landing gear elements
9 Called by: FGAircraft
10
11 ------------- Copyright (C) 1999 Jon S. Berndt (jon@jsbsim.org) -------------
12
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
15 Software Foundation; either version 2 of the License, or (at your option) any
16 later version.
17
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
21 details.
22
23 You should have received a copy of the GNU Lesser General Public License along
24 with this program; if not, write to the Free Software Foundation, Inc., 59
25 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
27 Further information about the GNU Lesser General Public License can also be
28 found on the world wide web at http://www.gnu.org.
29
30FUNCTIONAL DESCRIPTION
31--------------------------------------------------------------------------------
32
33HISTORY
34--------------------------------------------------------------------------------
3511/18/99 JSB Created
3601/30/01 NHP Extended gear model to properly simulate steering and braking
3707/08/09 BC Modified gear model to support large angles between aircraft
38 and ground
39
40/%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
41INCLUDES
42%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
43
44#include "FGLGear.h"
45#include "FGFDMExec.h"
46#include "models/FGGroundReactions.h"
47#include "models/FGInertial.h"
48#include "math/FGTable.h"
49#include "input_output/FGXMLElement.h"
50#include "input_output/FGLog.h"
51
52using namespace std;
53
54namespace JSBSim {
55
56/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
57DEFINITIONS
58%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
59
60/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
61GLOBAL DATA
62%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
63
64// Body To Structural (body frame is rotated 180 deg about Y and lengths are
65// given in ft instead of inches)
66const FGMatrix33 FGLGear::Tb2s(-1./inchtoft, 0., 0., 0., 1./inchtoft, 0., 0., 0., -1./inchtoft);
67const FGMatrix33 FGLGear::Ts2b(-inchtoft, 0., 0., 0., inchtoft, 0., 0., 0., -inchtoft);
68
69/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
70CLASS IMPLEMENTATION
71%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
72
73FGLGear::FGLGear(Element* el, FGFDMExec* fdmex, int number, const struct Inputs& inputs) :
74 FGForce(fdmex),
75 in(inputs),
76 GearNumber(number),
77 SteerAngle(0.0),
78 Castered(false),
79 StaticFriction(false),
80 eSteerType(stSteer)
81{
82 kSpring = bDamp = bDampRebound = dynamicFCoeff = staticFCoeff = rollingFCoeff = maxSteerAngle = 0;
83 isRetractable = false;
84 eDampType = dtLinear;
85 eDampTypeRebound = dtLinear;
86
87 name = el->GetAttributeValue("name");
88 string sContactType = el->GetAttributeValue("type");
89 if (sContactType == "BOGEY") {
90 eContactType = ctBOGEY;
91 } else if (sContactType == "STRUCTURE") {
92 eContactType = ctSTRUCTURE;
93 } else {
94 // Unknown contact point types will be treated as STRUCTURE.
95 eContactType = ctSTRUCTURE;
96 }
97
98 // Default values for structural contact points
99 if (eContactType == ctSTRUCTURE) {
100 kSpring = in.EmptyWeight;
101 bDamp = kSpring;
102 bDampRebound = kSpring * 10;
103 staticFCoeff = 1.0;
104 dynamicFCoeff = 1.0;
105 }
106
107 auto PropertyManager = fdmex->GetPropertyManager();
108
109 fStrutForce = 0;
110 Element* strutForce = el->FindElement("strut_force");
111 if (strutForce) {
112 Element* springFunc = strutForce->FindElement("function");
113 fStrutForce = new FGFunction(fdmex, springFunc);
114 }
115 else {
116 if (el->FindElement("spring_coeff"))
117 kSpring = el->FindElementValueAsNumberConvertTo("spring_coeff", "LBS/FT");
118 if (el->FindElement("damping_coeff")) {
119 Element* dampCoeff = el->FindElement("damping_coeff");
120 if (dampCoeff->GetAttributeValue("type") == "SQUARE") {
121 eDampType = dtSquare;
122 bDamp = el->FindElementValueAsNumberConvertTo("damping_coeff", "LBS/FT2/SEC2");
123 } else {
124 bDamp = el->FindElementValueAsNumberConvertTo("damping_coeff", "LBS/FT/SEC");
125 }
126 }
127
128 if (el->FindElement("damping_coeff_rebound")) {
129 Element* dampCoeffRebound = el->FindElement("damping_coeff_rebound");
130 if (dampCoeffRebound->GetAttributeValue("type") == "SQUARE") {
131 eDampTypeRebound = dtSquare;
132 bDampRebound = el->FindElementValueAsNumberConvertTo("damping_coeff_rebound", "LBS/FT2/SEC2");
133 } else {
134 bDampRebound = el->FindElementValueAsNumberConvertTo("damping_coeff_rebound", "LBS/FT/SEC");
135 }
136 } else {
137 bDampRebound = bDamp;
138 eDampTypeRebound = eDampType;
139 }
140 }
141
142 if (el->FindElement("dynamic_friction"))
143 dynamicFCoeff = el->FindElementValueAsNumber("dynamic_friction");
144 if (el->FindElement("static_friction"))
145 staticFCoeff = el->FindElementValueAsNumber("static_friction");
146 if (el->FindElement("rolling_friction"))
147 rollingFCoeff = el->FindElementValueAsNumber("rolling_friction");
148 if (el->FindElement("retractable"))
149 isRetractable = ((unsigned int)el->FindElementValueAsNumber("retractable"))>0.0?true:false;
150
151 if (el->FindElement("max_steer"))
152 maxSteerAngle = el->FindElementValueAsNumberConvertTo("max_steer", "DEG");
153
154 Element* castered_el = el->FindElement("castered");
155
156 if ((maxSteerAngle == 360 && !castered_el)
157 || (castered_el && castered_el->GetDataAsNumber() != 0.0)) {
158 eSteerType = stCaster;
159 Castered = true;
160 }
161 else if (maxSteerAngle == 0.0) {
162 eSteerType = stFixed;
163 }
164 else
165 eSteerType = stSteer;
166
167 GroundReactions = fdmex->GetGroundReactions().get();
168
169 ForceY_Table = 0;
170 Element* force_table = el->FindElement("table");
171 while (force_table) {
172 string force_type = force_table->GetAttributeValue("name");
173 if (force_type == "CORNERING_COEFF") {
174 ForceY_Table = new FGTable(PropertyManager, force_table);
175 break;
176 } else {
177 FGXMLLogging log(force_table, LogLevel::ERROR);
178 log << "Undefined force table for " << name << " contact point\n";
179 }
180 force_table = el->FindNextElement("table");
181 }
182
183 Element* element = el->FindElement("location");
184 if (element) vXYZn = element->FindElementTripletConvertTo("IN");
185 else {
186 XMLLogException err(el);
187 err << "\nNo location given for contact " << name << "\n";
188 throw err;
189 }
190 SetTransformType(FGForce::tCustom);
191
192 element = el->FindElement("orientation");
193 if (element && (eContactType == ctBOGEY)) {
194 FGQuaternion quatFromEuler(element->FindElementTripletConvertTo("RAD"));
195
196 mTGear = quatFromEuler.GetT();
197 }
198 else {
199 mTGear(1,1) = 1.;
200 mTGear(2,2) = 1.;
201 mTGear(3,3) = 1.;
202 }
203
204 string sBrakeGroup = el->FindElementValue("brake_group");
205
206 if (sBrakeGroup == "LEFT" ) eBrakeGrp = bgLeft;
207 else if (sBrakeGroup == "RIGHT" ) eBrakeGrp = bgRight;
208 else if (sBrakeGroup == "CENTER") eBrakeGrp = bgCenter;
209 else if (sBrakeGroup == "NOSE" ) eBrakeGrp = bgCenter; // Nose brake is not supported by FGFCS
210 else if (sBrakeGroup == "TAIL" ) eBrakeGrp = bgCenter; // Tail brake is not supported by FGFCS
211 else if (sBrakeGroup == "NONE" ) eBrakeGrp = bgNone;
212 else if (sBrakeGroup.empty() ) eBrakeGrp = bgNone;
213 else {
214 FGXMLLogging log(el, LogLevel::ERROR);
215 log << "Improper braking group specification in config file: "
216 << sBrakeGroup << " is undefined.\n";
217 }
218
219// Add some AI here to determine if gear is located properly according to its
220// brake group type ??
221
222 useFCSGearPos = false;
223 ReportEnable = true;
224 TakeoffReported = LandingReported = false;
225
226 // Set Pacejka terms
227
228 Stiffness = 0.06;
229 Shape = 2.8;
230 Peak = staticFCoeff;
231 Curvature = 1.03;
232
233 ResetToIC();
234
235 bind(PropertyManager.get());
236
237 Debug(0);
238}
239
240//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
241
243{
244 delete ForceY_Table;
245 delete fStrutForce;
246 Debug(1);
247}
248
249//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
250
251void FGLGear::ResetToIC(void)
252{
253 GearPos = 1.0;
254
255 WOW = lastWOW = false;
256 FirstContact = false;
257 StartedGroundRun = false;
258 LandingDistanceTraveled = TakeoffDistanceTraveled = TakeoffDistanceTraveled50ft = 0.0;
259 MaximumStrutForce = MaximumStrutTravel = 0.0;
260 SinkRate = GroundSpeed = 0.0;
261 SteerAngle = 0.0;
262
263 vWhlVelVec.InitMatrix();
264
265 compressLength = 0.0;
266 compressSpeed = 0.0;
267 maxCompLen = 0.0;
268
269 WheelSlip = 0.0;
270
271 // Initialize Lagrange multipliers
272 for (int i=0; i < 3; i++) {
273 LMultiplier[i].ForceJacobian.InitMatrix();
274 LMultiplier[i].LeverArm.InitMatrix();
275 LMultiplier[i].Min = 0.0;
276 LMultiplier[i].Max = 0.0;
277 LMultiplier[i].value = 0.0;
278 }
279}
280
281//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
282
284{
285 double gearPos = 1.0;
286
287 vFn.InitMatrix();
288
289 // Compute AGL
290 FGColumnVector3 normal, terrainVel, dummy;
291 FGLocation gearLoc, contact;
292 FGColumnVector3 vWhlBodyVec = Ts2b * (vXYZn - in.vXYZcg);
293
294 vLocalGear = in.Tb2l * vWhlBodyVec; // Get local frame wheel location
295 gearLoc = in.Location.LocalToLocation(vLocalGear);
296
297 // Compute the height of the theoretical location of the wheel (if strut is
298 // not compressed) with respect to the ground level (AGL)
299 double height = fdmex->GetInertial()->GetContactPoint(gearLoc, contact,
300 normal, terrainVel, dummy);
301
302 // Don't want strut compression when in contact with the ground to return
303 // a negative AGL
304 AGL = max(height, 0.0);
305
306 if (isRetractable) gearPos = GetGearUnitPos();
307
308 if (gearPos > 0.99) { // Gear DOWN
309
310 if (!fdmex->GetTrimStatus())
311 height -= GroundReactions->GetBumpHeight();
312 staticFFactor = GroundReactions->GetStaticFFactor();
313 rollingFFactor = GroundReactions->GetRollingFFactor();
314 maximumForce = GroundReactions->GetMaximumForce();
315 bumpiness = GroundReactions->GetBumpiness();
316 isSolid = GroundReactions->GetSolid();
317
318 FGColumnVector3 vWhlDisplVec;
319 double LGearProj = 1.0;
320
321 if (height < 0.0) {
322 WOW = true;
323 vGroundNormal = in.Tec2b * normal;
324
325 // The height returned by GetGroundCallback() is the AGL and is expressed
326 // in the Z direction of the local coordinate frame. We now need to
327 // transform this height in actual compression of the strut (BOGEY) or in
328 // the normal direction to the ground (STRUCTURE)
329 double normalZ = (in.Tec2l*normal)(eZ);
330 LGearProj = -(mTGear.Transposed() * vGroundNormal)(eZ);
331
332 // The following equations use the vector to the tire contact patch
333 // including the strut compression.
334 switch(eContactType) {
335 case ctBOGEY:
336 if (isSolid) {
337 compressLength = LGearProj > 0.0 ? height * normalZ / LGearProj : 0.0;
338 vWhlDisplVec = mTGear * FGColumnVector3(0., 0., -compressLength);
339 } else {
340 // Gears don't (or hardly) compress in liquids
341 WOW = false;
342 }
343 break;
344 case ctSTRUCTURE:
345 compressLength = height * normalZ / DotProduct(normal, normal);
346 vWhlDisplVec = compressLength * vGroundNormal;
347 break;
348 }
349 }
350 else
351 WOW = false;
352
353 if (WOW) {
354 FGColumnVector3 vWhlContactVec = vWhlBodyVec + vWhlDisplVec;
355 vActingXYZn = vXYZn + Tb2s * vWhlDisplVec;
356 FGColumnVector3 vBodyWhlVel = in.PQR * vWhlContactVec;
357 vBodyWhlVel += in.UVW - in.Tec2b * terrainVel;
358 vWhlVelVec = mTGear.Transposed() * vBodyWhlVel;
359
360 InitializeReporting();
361 ComputeSteeringAngle();
362 ComputeGroundFrame();
363
364 vGroundWhlVel = mT.Transposed() * vBodyWhlVel;
365
366 if (fdmex->GetTrimStatus() || in.TotalDeltaT == 0.0)
367 compressSpeed = 0.0; // Steady state is sought during trimming
368 else {
369 compressSpeed = -vGroundWhlVel(eZ);
370 if (eContactType == ctBOGEY)
371 compressSpeed /= LGearProj;
372
373 // If the gear is entering in contact with the ground during the current
374 // time step, the compression speed might actually be lower than the
375 // aircraft velocity projected along the gear leg (compressSpeed).
376 double maxCompressSpeed = compressLength/in.TotalDeltaT;
377 if (fabs(compressSpeed) > maxCompressSpeed)
378 compressSpeed = sign(compressSpeed)*maxCompressSpeed;
379 }
380
381 ComputeVerticalStrutForce();
382
383 // Compute the friction coefficients in the wheel ground plane.
384 if (eContactType == ctBOGEY) {
385 ComputeSlipAngle();
386 ComputeBrakeForceCoefficient();
387 ComputeSideForceCoefficient();
388 }
389
390 // Prepare the Jacobians and the Lagrange multipliers for later friction
391 // forces calculations.
392 ComputeJacobian(vWhlContactVec);
393 } else { // Gear is NOT compressed
394 compressLength = 0.0;
395 compressSpeed = 0.0;
396 WheelSlip = 0.0;
397 StrutForce = 0.0;
398 vWhlDisplVec.InitMatrix();
399
400 LMultiplier[ftRoll].value = 0.0;
401 LMultiplier[ftSide].value = 0.0;
402 LMultiplier[ftDynamic].value = 0.0;
403
404 // Return to neutral position between 1.0 and 0.8 gear pos.
405 SteerAngle *= max(gearPos-0.8, 0.0)/0.2;
406
407 ResetReporting();
408 }
409 }
410
411 if (!WOW) {
412 // Let wheel spin down slowly
413 vWhlVelVec(eX) -= 13.0 * in.TotalDeltaT;
414 if (vWhlVelVec(eX) < 0.0) vWhlVelVec(eX) = 0.0;
415 }
416
417 if (!fdmex->GetTrimStatus()) {
418 ReportTakeoffOrLanding();
419
420 // Require both WOW and LastWOW to be true before checking crash conditions
421 // to allow the WOW flag to be used in terminating a scripted run.
422 if (WOW && lastWOW) CrashDetect();
423
424 lastWOW = WOW;
425 }
426
427 return FGForce::GetBodyForces();
428}
429
430//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
431// Build a local "ground" coordinate system defined by
432// eX : projection of the rolling direction on the ground
433// eY : projection of the sliping direction on the ground
434// eZ : normal to the ground
435
436void FGLGear::ComputeGroundFrame(void)
437{
438 FGColumnVector3 roll = mTGear * FGColumnVector3(cos(SteerAngle), sin(SteerAngle), 0.);
439 FGColumnVector3 side = vGroundNormal * roll;
440
441 roll -= DotProduct(roll, vGroundNormal) * vGroundNormal;
442 roll.Normalize();
443 side.Normalize();
444
445 mT(eX,eX) = roll(eX);
446 mT(eY,eX) = roll(eY);
447 mT(eZ,eX) = roll(eZ);
448 mT(eX,eY) = side(eX);
449 mT(eY,eY) = side(eY);
450 mT(eZ,eY) = side(eZ);
451 mT(eX,eZ) = vGroundNormal(eX);
452 mT(eY,eZ) = vGroundNormal(eY);
453 mT(eZ,eZ) = vGroundNormal(eZ);
454}
455
456//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
457// Calculate tire slip angle.
458
459void FGLGear::ComputeSlipAngle(void)
460{
461// Check that the speed is non-null otherwise keep the current angle
462 if (vGroundWhlVel.Magnitude(eX,eY) > 1E-3)
463 WheelSlip = -atan2(vGroundWhlVel(eY), fabs(vGroundWhlVel(eX)))*radtodeg;
464}
465
466//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
467// Compute the steering angle in any case.
468// This will also make sure that animations will look right.
469
470void FGLGear::ComputeSteeringAngle(void)
471{
472 if (Castered) {
473 // Check that the speed is non-null otherwise keep the current angle
474 if (vWhlVelVec.Magnitude(eX,eY) > 0.1)
475 SteerAngle = atan2(vWhlVelVec(eY), fabs(vWhlVelVec(eX)));
476 }
477}
478
479//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
480// Reset reporting functionality after takeoff
481
482void FGLGear::ResetReporting(void)
483{
484 if (in.DistanceAGL > 200.0) {
485 FirstContact = false;
486 StartedGroundRun = false;
487 LandingReported = false;
488 TakeoffReported = true;
489 LandingDistanceTraveled = 0.0;
490 MaximumStrutForce = MaximumStrutTravel = 0.0;
491 }
492}
493
494//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
495
496void FGLGear::InitializeReporting(void)
497{
498 // If this is the first time the wheel has made contact, remember some values
499 // for later printout.
500
501 if (!FirstContact) {
502 FirstContact = true;
503 SinkRate = compressSpeed;
504 GroundSpeed = in.Vground;
505 TakeoffReported = false;
506 }
507
508 // If the takeoff run is starting, initialize.
509
510 if ((in.Vground > 0.1) &&
511 (in.BrakePos[bgLeft] == 0) &&
512 (in.BrakePos[bgRight] == 0) &&
513 (in.TakeoffThrottle && !StartedGroundRun))
514 {
515 TakeoffDistanceTraveled = 0;
516 TakeoffDistanceTraveled50ft = 0;
517 StartedGroundRun = true;
518 }
519}
520
521//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
522// Takeoff and landing reporting functionality
523
524void FGLGear::ReportTakeoffOrLanding(void)
525{
526 if (FirstContact)
527 LandingDistanceTraveled += in.Vground * in.TotalDeltaT;
528
529 if (StartedGroundRun) {
530 TakeoffDistanceTraveled50ft += in.Vground * in.TotalDeltaT;
531 if (WOW) TakeoffDistanceTraveled += in.Vground * in.TotalDeltaT;
532 }
533
534 if ( ReportEnable
535 && in.Vground <= 0.05
536 && !LandingReported
537 && in.WOW)
538 {
539 if (debug_lvl > 0) Report(erLand);
540 }
541
542 if ( ReportEnable
543 && !TakeoffReported
544 && (in.DistanceAGL - vLocalGear(eZ)) > 50.0
545 && !in.WOW)
546 {
547 if (debug_lvl > 0) Report(erTakeoff);
548 }
549
550 if (lastWOW != WOW)
551 {
552 if (debug_lvl > 0) {
553 FGLogging log(LogLevel::INFO);
554 log << "GEAR_CONTACT: " << fixed << fdmex->GetSimTime() << " seconds: "
555 << name << " " << WOW << "\n";
556 }
557 }
558}
559
560//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
561// Crash detection logic (really out-of-bounds detection)
562
563void FGLGear::CrashDetect(void)
564{
565 if ( (compressLength > 500.0 ||
566 vFn.Magnitude() > 100000000.0 ||
567 GetMoments().Magnitude() > 5000000000.0 ||
568 SinkRate > 1.4666*30 ) && !fdmex->IntegrationSuspended())
569 {
570 if (debug_lvl > 0) {
571 FGLogging log(LogLevel::INFO);
572 log << "*CRASH DETECTED* " << fixed << fdmex->GetSimTime() << " seconds: "
573 << name << "\n";
574 }
575
576 // fdmex->SuspendIntegration();
577 }
578}
579
580//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
581// The following needs work regarding friction coefficients and braking and
582// steering The BrakeFCoeff formula assumes that an anti-skid system is used.
583// It also assumes that we won't be turning and braking at the same time.
584// Will fix this later.
585// [JSB] The braking force coefficients include normal rolling coefficient +
586// a percentage of the static friction coefficient based on braking applied.
587
588void FGLGear::ComputeBrakeForceCoefficient(void)
589{
590 BrakeFCoeff = rollingFFactor * rollingFCoeff;
591
592 if (eBrakeGrp != bgNone)
593 BrakeFCoeff += in.BrakePos[eBrakeGrp] * staticFFactor * (staticFCoeff - rollingFCoeff);
594}
595
596//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
597// Compute the sideforce coefficients using Pacejka's Magic Formula.
598//
599// y(x) = D sin {C arctan [Bx - E(Bx - arctan Bx)]}
600//
601// Where: B = Stiffness Factor (0.06, here)
602// C = Shape Factor (2.8, here)
603// D = Peak Factor (0.8, here)
604// E = Curvature Factor (1.03, here)
605
606void FGLGear::ComputeSideForceCoefficient(void)
607{
608 if (ForceY_Table) {
609 FCoeff = ForceY_Table->GetValue(WheelSlip);
610 } else {
611 double StiffSlip = Stiffness*WheelSlip;
612 FCoeff = Peak * sin(Shape*atan(StiffSlip - Curvature*(StiffSlip - atan(StiffSlip))));
613 }
614 FCoeff *= staticFFactor;
615}
616
617//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
618// Compute the vertical force on the wheel using square-law damping (per comment
619// in paper AIAA-2000-4303 - see header prologue comments). We might consider
620// allowing for both square and linear damping force calculation. Also need to
621// possibly give a "rebound damping factor" that differs from the compression
622// case.
623
624void FGLGear::ComputeVerticalStrutForce()
625{
626 if (fStrutForce)
627 StrutForce = min(fStrutForce->GetValue(), (double)0.0);
628 else {
629 double springForce = -compressLength * kSpring;
630 double dampForce = 0;
631
632 if (compressSpeed >= 0.0) {
633
634 if (eDampType == dtLinear)
635 dampForce = -compressSpeed * bDamp;
636 else
637 dampForce = -compressSpeed * compressSpeed * bDamp;
638
639 } else {
640
641 if (eDampTypeRebound == dtLinear)
642 dampForce = -compressSpeed * bDampRebound;
643 else
644 dampForce = compressSpeed * compressSpeed * bDampRebound;
645
646 }
647
648 StrutForce = min(springForce + dampForce, (double)0.0);
649 if (StrutForce > maximumForce) {
650 StrutForce = maximumForce;
651 compressLength = -StrutForce / kSpring;
652 }
653 }
654
655 // The reaction force of the wheel is always normal to the ground
656 switch (eContactType) {
657 case ctBOGEY:
658 // Project back the strut force in the local coordinate frame of the ground
659 vFn(eZ) = StrutForce / (mTGear.Transposed()*vGroundNormal)(eZ);
660 break;
661 case ctSTRUCTURE:
662 vFn(eZ) = -StrutForce;
663 break;
664 }
665
666 // Remember these values for reporting
667 MaximumStrutForce = max(MaximumStrutForce, fabs(StrutForce));
668 MaximumStrutTravel = max(MaximumStrutTravel, fabs(compressLength));
669}
670
671//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
672
673double FGLGear::GetGearUnitPos(void) const
674{
675 // hack to provide backward compatibility to gear/gear-pos-norm property
676 if( useFCSGearPos || in.FCSGearPos != 1.0 ) {
677 useFCSGearPos = true;
678 return in.FCSGearPos;
679 }
680 return GearPos;
681}
682
683//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
684// Compute the jacobian entries for the friction forces resolution later
685// in FGPropagate
686
687void FGLGear::ComputeJacobian(const FGColumnVector3& vWhlContactVec)
688{
689 // When the point of contact is moving, dynamic friction is used
690 // This type of friction is limited to ctSTRUCTURE elements because their
691 // friction coefficient is the same in every directions
692 if ((eContactType == ctSTRUCTURE) && (vGroundWhlVel.Magnitude(eX,eY) > 1E-3)) {
693
694 FGColumnVector3 velocityDirection = vGroundWhlVel;
695
696 StaticFriction = false;
697
698 velocityDirection(eZ) = 0.;
699 velocityDirection.Normalize();
700
701 LMultiplier[ftDynamic].ForceJacobian = mT * velocityDirection;
702 LMultiplier[ftDynamic].Max = 0.;
703 LMultiplier[ftDynamic].Min = -fabs(staticFFactor * dynamicFCoeff * vFn(eZ));
704 LMultiplier[ftDynamic].LeverArm = vWhlContactVec;
705
706 // The Lagrange multiplier value obtained from the previous iteration is
707 // kept. This is supposed to accelerate the convergence of the projected
708 // Gauss-Seidel algorithm. The code just below is to make sure that the
709 // initial value is consistent with the current friction coefficient and
710 // normal reaction.
711 LMultiplier[ftDynamic].value = Constrain(LMultiplier[ftDynamic].Min, LMultiplier[ftDynamic].value, LMultiplier[ftDynamic].Max);
712
713 GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftDynamic]);
714 }
715 else {
716 // Static friction is used for ctSTRUCTURE when the contact point is not
717 // moving. It is always used for ctBOGEY elements because the friction
718 // coefficients of a tyre depend on the direction of the movement (roll &
719 // side directions). This cannot be handled properly by the so-called
720 // "dynamic friction".
721 StaticFriction = true;
722
723 LMultiplier[ftRoll].ForceJacobian = mT * FGColumnVector3(1.,0.,0.);
724 LMultiplier[ftSide].ForceJacobian = mT * FGColumnVector3(0.,1.,0.);
725 LMultiplier[ftRoll].LeverArm = vWhlContactVec;
726 LMultiplier[ftSide].LeverArm = vWhlContactVec;
727
728 switch(eContactType) {
729 case ctBOGEY:
730 LMultiplier[ftRoll].Max = fabs(BrakeFCoeff * vFn(eZ));
731 LMultiplier[ftSide].Max = fabs(FCoeff * vFn(eZ));
732 break;
733 case ctSTRUCTURE:
734 LMultiplier[ftRoll].Max = fabs(staticFFactor * staticFCoeff * vFn(eZ));
735 LMultiplier[ftSide].Max = LMultiplier[ftRoll].Max;
736 break;
737 }
738
739 LMultiplier[ftRoll].Min = -LMultiplier[ftRoll].Max;
740 LMultiplier[ftSide].Min = -LMultiplier[ftSide].Max;
741
742 // The Lagrange multiplier value obtained from the previous iteration is
743 // kept. This is supposed to accelerate the convergence of the projected
744 // Gauss-Seidel algorithm. The code just below is to make sure that the
745 // initial value is consistent with the current friction coefficient and
746 // normal reaction.
747 LMultiplier[ftRoll].value = Constrain(LMultiplier[ftRoll].Min, LMultiplier[ftRoll].value, LMultiplier[ftRoll].Max);
748 LMultiplier[ftSide].value = Constrain(LMultiplier[ftSide].Min, LMultiplier[ftSide].value, LMultiplier[ftSide].Max);
749
750 GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftRoll]);
751 GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftSide]);
752 }
753}
754
755//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
756// This routine is called after the Lagrange multiplier has been computed in
757// the FGAccelerations class. The friction forces of the landing gear are then
758// updated accordingly.
759void FGLGear::UpdateForces(void)
760{
761 if (StaticFriction) {
762 vFn(eX) = LMultiplier[ftRoll].value;
763 vFn(eY) = LMultiplier[ftSide].value;
764 }
765 else {
766 FGColumnVector3 forceDir = mT.Transposed() * LMultiplier[ftDynamic].ForceJacobian;
767 vFn(eX) = LMultiplier[ftDynamic].value * forceDir(eX);
768 vFn(eY) = LMultiplier[ftDynamic].value * forceDir(eY);
769 }
770}
771
772//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
773
774void FGLGear::SetstaticFCoeff(double coeff)
775{
776 staticFCoeff = coeff;
777 Peak = coeff;
778}
779
780//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
781
782void FGLGear::bind(FGPropertyManager* PropertyManager)
783{
784 string property_name;
785 string base_property_name;
786
787 switch(eContactType) {
788 case ctBOGEY:
789 base_property_name = CreateIndexedPropertyName("gear/unit", GearNumber);
790 break;
791 case ctSTRUCTURE:
792 base_property_name = CreateIndexedPropertyName("contact/unit", GearNumber);
793 break;
794 default:
795 return;
796 }
797
798 property_name = base_property_name + "/AGL-ft";
799 PropertyManager->Tie(property_name.c_str(), &AGL);
800 property_name = base_property_name + "/WOW";
801 PropertyManager->Tie( property_name.c_str(), &WOW );
802 property_name = base_property_name + "/x-position";
803 PropertyManager->Tie( property_name.c_str(), (FGForce*)this,
804 &FGForce::GetLocationX, &FGForce::SetLocationX);
805 property_name = base_property_name + "/y-position";
806 PropertyManager->Tie( property_name.c_str(), (FGForce*)this,
807 &FGForce::GetLocationY, &FGForce::SetLocationY);
808 property_name = base_property_name + "/z-position";
809 PropertyManager->Tie( property_name.c_str(), (FGForce*)this,
810 &FGForce::GetLocationZ, &FGForce::SetLocationZ);
811 property_name = base_property_name + "/compression-ft";
812 PropertyManager->Tie( property_name.c_str(), &compressLength );
813 property_name = base_property_name + "/compression-velocity-fps";
814 PropertyManager->Tie( property_name.c_str(), &compressSpeed );
815 property_name = base_property_name + "/static_friction_coeff";
816 PropertyManager->Tie( property_name.c_str(), (FGLGear*)this,
817 &FGLGear::GetstaticFCoeff, &FGLGear::SetstaticFCoeff);
818 property_name = base_property_name + "/dynamic_friction_coeff";
819 PropertyManager->Tie( property_name.c_str(), &dynamicFCoeff );
820
821 if (eContactType == ctBOGEY) {
822 property_name = base_property_name + "/slip-angle-deg";
823 PropertyManager->Tie( property_name.c_str(), &WheelSlip );
824 property_name = base_property_name + "/wheel-speed-fps";
825 PropertyManager->Tie( property_name.c_str(), (FGLGear*)this,
826 &FGLGear::GetWheelRollVel);
827 property_name = base_property_name + "/side_friction_coeff";
828 PropertyManager->Tie( property_name.c_str(), &FCoeff );
829 property_name = base_property_name + "/rolling_friction_coeff";
830 PropertyManager->Tie( property_name.c_str(), &rollingFCoeff );
831
832 if (eSteerType == stCaster) {
833 property_name = base_property_name + "/steering-angle-deg";
834 PropertyManager->Tie( property_name.c_str(), this, &FGLGear::GetSteerAngleDeg );
835 property_name = base_property_name + "/castered";
836 PropertyManager->Tie( property_name.c_str(), &Castered);
837 }
838 }
839
840 if( isRetractable ) {
841 property_name = base_property_name + "/pos-norm";
842 PropertyManager->Tie( property_name.c_str(), &GearPos );
843 }
844
845 if (eSteerType != stFixed) {
846 // This property allows the FCS to override the steering position angle that
847 // is set by the property fcs/steer-cmd-norm. The prefix fcs/ has been kept
848 // for backward compatibility.
849 string tmp = CreateIndexedPropertyName("fcs/steer-pos-deg", GearNumber);
850 PropertyManager->Tie(tmp.c_str(), this, &FGLGear::GetSteerAngleDeg, &FGLGear::SetSteerAngleDeg);
851 }
852
853 property_name = base_property_name + "/solid";
854 PropertyManager->Tie( property_name.c_str(), &isSolid);
855 property_name = base_property_name + "/bumpiness";
856 PropertyManager->Tie( property_name.c_str(), &bumpiness);
857 property_name = base_property_name + "/maximum-force-lbs";
858 PropertyManager->Tie( property_name.c_str(), &maximumForce);
859 property_name = base_property_name + "/rolling_friction-factor";
860 PropertyManager->Tie( property_name.c_str(), &rollingFFactor);
861 property_name = base_property_name + "/static-friction-factor";
862 PropertyManager->Tie( property_name.c_str(), &staticFFactor);
863}
864
865//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
866
867void FGLGear::Report(ReportType repType)
868{
869 if (fabs(TakeoffDistanceTraveled) < 0.001) return; // Don't print superfluous reports
870
871 FGLogging log(LogLevel::INFO);
872
873 switch(repType) {
874 case erLand:
875 log << "\nTouchdown report for " << name << " (WOW at time: " << fixed
876 << fdmex->GetSimTime() << " seconds)\n";
877 log << " Sink rate at contact: " << SinkRate << " fps, "
878 << SinkRate*0.3048 << " mps\n";
879 log << " Contact ground speed: " << GroundSpeed*.5925 << " knots, "
880 << GroundSpeed*0.3048 << " mps\n";
881 log << " Maximum contact force: " << MaximumStrutForce << " lbs, "
882 << MaximumStrutForce*4.448 << " Newtons\n";
883 log << " Maximum strut travel: " << MaximumStrutTravel*12.0 << " inches, "
884 << MaximumStrutTravel*30.48 << " cm\n";
885 log << " Distance traveled: " << LandingDistanceTraveled << " ft, "
886 << LandingDistanceTraveled*0.3048 << " meters\n";
887 LandingReported = true;
888 break;
889 case erTakeoff:
890 log << "\nTakeoff report for " << name << " (Liftoff at time: " << fixed
891 << fdmex->GetSimTime() << " seconds)\n";
892 log << " Distance traveled: " << TakeoffDistanceTraveled
893 << " ft, " << TakeoffDistanceTraveled*0.3048 << " meters\n";
894 log << " Distance traveled (over 50'): " << TakeoffDistanceTraveled50ft
895 << " ft, " << TakeoffDistanceTraveled50ft*0.3048 << " meters\n";
896 log << " [Altitude (ASL): " << in.DistanceASL << " ft. / "
897 << in.DistanceASL*FGJSBBase::fttom << " m | Temperature: "
898 << in.Temperature - 459.67 << " F / "
899 << RankineToCelsius(in.Temperature) << " C]\n";
900 log << " [Velocity (KCAS): " << in.VcalibratedKts << "]\n";
901 TakeoffReported = true;
902 break;
903 case erNone:
904 break;
905 }
906}
907
908//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
909// The bitmasked value choices are as follows:
910// unset: In this case (the default) JSBSim would only print
911// out the normally expected messages, essentially echoing
912// the config files as they are read. If the environment
913// variable is not set, debug_lvl is set to 1 internally
914// 0: This requests JSBSim not to output any messages
915// whatsoever.
916// 1: This value explicity requests the normal JSBSim
917// startup messages
918// 2: This value asks for a message to be printed out when
919// a class is instantiated
920// 4: When this value is set, a message is displayed when a
921// FGModel object executes its Run() method
922// 8: When this value is set, various runtime state variables
923// are printed out periodically
924// 16: When set various parameters are sanity checked and
925// a message is printed out when they go out of bounds
926
927void FGLGear::Debug(int from)
928{
929 static const char* sSteerType[] = {"STEERABLE", "FIXED", "CASTERED" };
930 static const char* sBrakeGroup[] = {"NONE", "LEFT", "RIGHT", "CENTER", "NOSE", "TAIL"};
931 static const char* sContactType[] = {"BOGEY", "STRUCTURE" };
932
933 if (debug_lvl <= 0) return;
934
935 if (debug_lvl & 1) { // Standard console startup message output
936 FGLogging log(LogLevel::DEBUG);
937 if (from == 0) { // Constructor - loading and initialization
938 log << " " << sContactType[eContactType] << " " << name << "\n" << fixed;
939 log << " Location: " << vXYZn << "\n";
940 log << " Spring Constant: " << kSpring << "\n";
941
942 if (eDampType == dtLinear)
943 log << " Damping Constant: " << bDamp << " (linear)\n";
944 else
945 log << " Damping Constant: " << bDamp << " (square law)\n";
946
947 if (eDampTypeRebound == dtLinear)
948 log << " Rebound Damping Constant: " << bDampRebound << " (linear)\n";
949 else
950 log << " Rebound Damping Constant: " << bDampRebound << " (square law)\n";
951
952 log << " Dynamic Friction: " << dynamicFCoeff << "\n";
953 log << " Static Friction: " << staticFCoeff << "\n";
954 if (eContactType == ctBOGEY) {
955 log << " Rolling Friction: " << rollingFCoeff << "\n";
956 log << " Steering Type: " << sSteerType[eSteerType] << "\n";
957 log << " Grouping: " << sBrakeGroup[eBrakeGrp] << "\n";
958 log << " Max Steer Angle: " << maxSteerAngle << "\n";
959 log << " Retractable: " << isRetractable << "\n";
960 }
961 }
962 }
963 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
964 FGLogging log(LogLevel::DEBUG);
965 if (from == 0) log << "Instantiated: FGLGear\n";
966 if (from == 1) log << "Destroyed: FGLGear\n";
967 }
968 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
969 }
970 if (debug_lvl & 8 ) { // Runtime state variables
971 }
972 if (debug_lvl & 16) { // Sanity checking
973 }
974 if (debug_lvl & 64) {
975 if (from == 0) { // Constructor
976 }
977 }
978}
979
980} // namespace JSBSim
Element * FindElement(const std::string &el="")
Searches for a specified element.
FGColumnVector3 FindElementTripletConvertTo(const std::string &target_units)
Composes a 3-element column vector for the supplied location or orientation.
std::string GetAttributeValue(const std::string &key)
Retrieves an attribute.
std::string FindElementValue(const std::string &el="")
Searches for the named element and returns the string data belonging to it.
double FindElementValueAsNumberConvertTo(const std::string &el, const std::string &target_units)
Searches for the named element and converts and returns the data belonging to it.
Element * FindNextElement(const std::string &el="")
Searches for the next element as specified.
double GetDataAsNumber(void)
Converts the element data to a number.
double FindElementValueAsNumber(const std::string &el="")
Searches for the named element and returns the data belonging to it as a number.
This class implements a 3 element column vector.
double Magnitude(void) const
Length of the vector.
FGColumnVector3 & Normalize(void)
Normalize.
Encapsulates the JSBSim simulation executive.
Definition FGFDMExec.h:185
std::shared_ptr< FGGroundReactions > GetGroundReactions(void) const
Returns the FGGroundReactions pointer.
bool IntegrationSuspended(void) const
Returns the simulation suspension state.
Definition FGFDMExec.h:563
double GetSimTime(void) const
Returns the cumulative simulation time in seconds.
Definition FGFDMExec.h:550
std::shared_ptr< FGInertial > GetInertial(void) const
Returns the FGInertial pointer.
std::shared_ptr< FGPropertyManager > GetPropertyManager(void) const
Returns a pointer to the property manager object.
Definition FGFDMExec.h:422
Utility class that aids in the conversion of forces between coordinate systems and calculation of mom...
Definition FGForce.h:222
Represents a mathematical function.
Definition FGFunction.h:765
double GetValue(void) const override
Retrieves the value of the function object.
static constexpr double Constrain(double min, double value, double max)
Constrain a value between a minimum and a maximum value.
Definition FGJSBBase.h:289
static constexpr double RankineToCelsius(double rankine)
Converts from degrees Rankine to degrees Celsius.
Definition FGJSBBase.h:200
const FGColumnVector3 & GetBodyForces(void) override
The Force vector for this gear.
Definition FGLGear.cpp:283
FGLGear(Element *el, FGFDMExec *Executive, int number, const struct Inputs &input)
Constructor.
Definition FGLGear.cpp:73
~FGLGear()
Destructor.
Definition FGLGear.cpp:242
FGLocation holds an arbitrary location in the Earth centered Earth fixed reference frame (ECEF).
Definition FGLocation.h:152
FGLocation LocalToLocation(const FGColumnVector3 &lvec) const
Conversion from Local frame coordinates to a location in the earth centered and fixed frame.
Definition FGLocation.h:326
FGMatrix33 Transposed(void) const
Transposed matrix.
Definition FGMatrix33.h:221
Models the Quaternion representation of rotations.
const FGMatrix33 & GetT(void) const
Transformation matrix.
bool GetSolid(void)
Gets the surface is a solid flag value.
Definition FGSurface.h:107
double GetBumpiness(void)
Gets the normalized bumpiness factor associated with the surface.
Definition FGSurface.h:104
double GetRollingFFactor(void)
Gets the rolling friction factor of the surface area.
Definition FGSurface.h:98
double GetMaximumForce(void)
Gets the maximum force of the surface area.
Definition FGSurface.h:101
double GetBumpHeight()
Returns the height of the bump at the provided offset.
Definition FGSurface.cpp:92
double GetStaticFFactor(void)
Gets the static friction factor of the surface area.
Definition FGSurface.h:95
Lookup table class.
Definition FGTable.h:234
double GetValue(void) const
Get the current table value.
Definition FGTable.cpp:465
Main namespace for the JSBSim Flight Dynamics Model.
Definition FGFDMExec.cpp:71
double DotProduct(const FGColumnVector3 &v1, const FGColumnVector3 &v2)
Dot product of two vectors Compute and return the euclidean dot (or scalar) product of two vectors v1...