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