+ // When the point of contact is moving, dynamic friction is used
+ // This type of friction is limited to ctSTRUCTURE elements because their
+ // friction coefficient is the same in every directions
+ if ((eContactType == ctSTRUCTURE) && (vGroundWhlVel.Magnitude(eX,eY) > 1E-3)) {
+
+ FGColumnVector3 velocityDirection = vGroundWhlVel;
+
+ StaticFriction = false;
+
+ velocityDirection(eZ) = 0.;
+ velocityDirection.Normalize();
+
+ LMultiplier[ftDynamic].ForceJacobian = mT * velocityDirection;
+ LMultiplier[ftDynamic].MomentJacobian = vWhlContactVec * LMultiplier[ftDynamic].ForceJacobian;
+ LMultiplier[ftDynamic].Max = 0.;
+ LMultiplier[ftDynamic].Min = -fabs(dynamicFCoeff * vFn(eZ));
+
+ // The Lagrange multiplier value obtained from the previous iteration is kept
+ // This is supposed to accelerate the convergence of the projected Gauss-Seidel
+ // algorithm. The code just below is to make sure that the initial value
+ // is consistent with the current friction coefficient and normal reaction.
+ LMultiplier[ftDynamic].value = Constrain(LMultiplier[ftDynamic].Min, LMultiplier[ftDynamic].value, LMultiplier[ftDynamic].Max);
+
+ GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftDynamic]);
+ }
+ else {
+ // Static friction is used for ctSTRUCTURE when the contact point is not moving.
+ // It is always used for ctBOGEY elements because the friction coefficients
+ // of a tyre depend on the direction of the movement (roll & side directions).
+ // This cannot be handled properly by the so-called "dynamic friction".
+ StaticFriction = true;
+
+ LMultiplier[ftRoll].ForceJacobian = mT * FGColumnVector3(1.,0.,0.);
+ LMultiplier[ftSide].ForceJacobian = mT * FGColumnVector3(0.,1.,0.);
+ LMultiplier[ftRoll].MomentJacobian = vWhlContactVec * LMultiplier[ftRoll].ForceJacobian;
+ LMultiplier[ftSide].MomentJacobian = vWhlContactVec * LMultiplier[ftSide].ForceJacobian;
+
+ switch(eContactType) {
+ case ctBOGEY:
+ LMultiplier[ftRoll].Max = fabs(BrakeFCoeff * vFn(eZ));
+ LMultiplier[ftSide].Max = fabs(FCoeff * vFn(eZ));
+ break;
+ case ctSTRUCTURE:
+ LMultiplier[ftRoll].Max = fabs(staticFCoeff * vFn(eZ));
+ LMultiplier[ftSide].Max = LMultiplier[ftRoll].Max;
+ break;
+ }
+
+ LMultiplier[ftRoll].Min = -LMultiplier[ftRoll].Max;
+ LMultiplier[ftSide].Min = -LMultiplier[ftSide].Max;
+
+ // The Lagrange multiplier value obtained from the previous iteration is kept
+ // This is supposed to accelerate the convergence of the projected Gauss-Seidel
+ // algorithm. The code just below is to make sure that the initial value
+ // is consistent with the current friction coefficient and normal reaction.
+ LMultiplier[ftRoll].value = Constrain(LMultiplier[ftRoll].Min, LMultiplier[ftRoll].value, LMultiplier[ftRoll].Max);
+ LMultiplier[ftSide].value = Constrain(LMultiplier[ftSide].Min, LMultiplier[ftSide].value, LMultiplier[ftSide].Max);
+
+ GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftRoll]);
+ GroundReactions->RegisterLagrangeMultiplier(&LMultiplier[ftSide]);
+ }
+}
+
+//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+// This routine is called after the Lagrange multiplier has been computed in
+// the FGAccelerations class. The friction forces of the landing gear are then
+// updated accordingly.
+void FGLGear::UpdateForces(void)
+{
+ if (StaticFriction) {
+ vFn(eX) = LMultiplier[ftRoll].value;
+ vFn(eY) = LMultiplier[ftSide].value;
+ }
+ else {
+ FGColumnVector3 forceDir = mT.Transposed() * LMultiplier[ftDynamic].ForceJacobian;
+ vFn(eX) = LMultiplier[ftDynamic].value * forceDir(eX);
+ vFn(eY) = LMultiplier[ftDynamic].value * forceDir(eY);
+ }
+}
+
+//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+void FGLGear::bind(void)
+{
+ string property_name;
+ string base_property_name;
+
+ switch(eContactType) {
+ case ctBOGEY:
+ base_property_name = CreateIndexedPropertyName("gear/unit", GearNumber);
+ break;
+ case ctSTRUCTURE:
+ base_property_name = CreateIndexedPropertyName("contact/unit", GearNumber);
+ break;
+ default:
+ return;
+ }
+
+ property_name = base_property_name + "/WOW";
+ PropertyManager->Tie( property_name.c_str(), &WOW );
+ property_name = base_property_name + "/z-position";
+ PropertyManager->Tie( property_name.c_str(), (FGForce*)this,
+ &FGForce::GetLocationZ, &FGForce::SetLocationZ);
+ property_name = base_property_name + "/compression-ft";
+ PropertyManager->Tie( property_name.c_str(), &compressLength );
+ property_name = base_property_name + "/static_friction_coeff";
+ PropertyManager->Tie( property_name.c_str(), &staticFCoeff );
+ property_name = base_property_name + "/dynamic_friction_coeff";
+ PropertyManager->Tie( property_name.c_str(), &dynamicFCoeff );
+
+ if (eContactType == ctBOGEY) {
+ property_name = base_property_name + "/slip-angle-deg";
+ PropertyManager->Tie( property_name.c_str(), &WheelSlip );
+ property_name = base_property_name + "/wheel-speed-fps";
+ PropertyManager->Tie( property_name.c_str(), (FGLGear*)this,
+ &FGLGear::GetWheelRollVel);
+ property_name = base_property_name + "/side_friction_coeff";
+ PropertyManager->Tie( property_name.c_str(), &FCoeff );
+ property_name = base_property_name + "/rolling_friction_coeff";
+ PropertyManager->Tie( property_name.c_str(), &rollingFCoeff );
+
+ if (eSteerType == stCaster) {
+ property_name = base_property_name + "/steering-angle-deg";
+ PropertyManager->Tie( property_name.c_str(), this, &FGLGear::GetSteerAngleDeg );
+ property_name = base_property_name + "/castered";
+ PropertyManager->Tie( property_name.c_str(), &Castered);
+ }
+ }
+
+ if( isRetractable ) {
+ property_name = base_property_name + "/pos-norm";
+ PropertyManager->Tie( property_name.c_str(), &GearPos );
+ }