+ // 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) && (vLocalWhlVel.Magnitude(eY,eZ) > 1E-3)) {
+ FGColumnVector3 velocityDirection = vLocalWhlVel;
+
+ StaticFriction = false;
+
+ velocityDirection(eX) = 0.;
+ velocityDirection.Normalize();
+
+ LMultiplier[ftDynamic].ForceJacobian = Transform()*velocityDirection;
+ LMultiplier[ftDynamic].MomentJacobian = vWhlContactVec * LMultiplier[ftDynamic].ForceJacobian;
+ LMultiplier[ftDynamic].Max = 0.;
+ LMultiplier[ftDynamic].Min = -fabs(dynamicFCoeff * vFn(eX));
+ LMultiplier[ftDynamic].value = Constrain(LMultiplier[ftDynamic].Min, LMultiplier[ftDynamic].value, LMultiplier[ftDynamic].Max);
+ }
+ 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 = Transform()*FGColumnVector3(0.,1.,0.);
+ LMultiplier[ftSide].ForceJacobian = Transform()*FGColumnVector3(0.,0.,1.);
+ LMultiplier[ftRoll].MomentJacobian = vWhlContactVec * LMultiplier[ftRoll].ForceJacobian;
+ LMultiplier[ftSide].MomentJacobian = vWhlContactVec * LMultiplier[ftSide].ForceJacobian;
+
+ switch(eContactType) {
+ case ctBOGEY:
+ LMultiplier[ftRoll].Max = fabs(BrakeFCoeff * vFn(eX));
+ LMultiplier[ftSide].Max = fabs(FCoeff * vFn(eX));
+ break;
+ case ctSTRUCTURE:
+ LMultiplier[ftRoll].Max = fabs(staticFCoeff * vFn(eX));
+ LMultiplier[ftSide].Max = fabs(staticFCoeff * vFn(eX));
+ break;
+ }
+
+ LMultiplier[ftRoll].Min = -LMultiplier[ftRoll].Max;
+ LMultiplier[ftSide].Min = -LMultiplier[ftSide].Max;
+ 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);
+ }
+}
+
+//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+// This function is used by the MultiplierIterator class to enumerate the
+// Lagrange multipliers of a landing gear. This allows to encapsulate the storage
+// of the multipliers in FGLGear without exposing it. From an outside point of
+// view, each FGLGear instance has a number of Lagrange multipliers which can be
+// accessed through this routine without knowing the exact constraint which they
+// model.
+
+FGPropagate::LagrangeMultiplier* FGLGear::GetMultiplierEntry(int entry)
+{
+ switch(entry) {
+ case 0:
+ if (StaticFriction)
+ return &LMultiplier[ftRoll];
+ else
+ return &LMultiplier[ftDynamic];
+ case 1:
+ if (StaticFriction)
+ return &LMultiplier[ftSide];
+ default:
+ return NULL;
+ }
+}
+
+//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+// This routine is called after the Lagrange multiplier has been computed. The
+// friction forces of the landing gear are then updated accordingly.
+FGColumnVector3& FGLGear::UpdateForces(void)
+{
+ if (StaticFriction) {
+ vFn(eY) = LMultiplier[ftRoll].value;
+ vFn(eZ) = LMultiplier[ftSide].value;
+ }
+ else
+ vFn += LMultiplier[ftDynamic].value * (Transform ().Transposed() * LMultiplier[ftDynamic].ForceJacobian);
+
+ // Return the updated force in the body frame
+ return FGForce::GetBodyForces();
+}
+
+//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+void FGLGear::bind(void)
+{
+ string property_name;
+ string base_property_name;
+ base_property_name = CreateIndexedPropertyName("gear/unit", GearNumber);
+ if (eContactType == ctBOGEY) {
+ property_name = base_property_name + "/slip-angle-deg";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), &WheelSlip );
+ property_name = base_property_name + "/WOW";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), &WOW );
+ property_name = base_property_name + "/wheel-speed-fps";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), (FGLGear*)this,
+ &FGLGear::GetWheelRollVel);
+ property_name = base_property_name + "/z-position";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), (FGForce*)this,
+ &FGForce::GetLocationZ, &FGForce::SetLocationZ);
+ property_name = base_property_name + "/compression-ft";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), &compressLength );
+ property_name = base_property_name + "/side_friction_coeff";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), &FCoeff );
+
+ property_name = base_property_name + "/static_friction_coeff";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), &staticFCoeff );
+ property_name = base_property_name + "/rolling_friction_coeff";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), &rollingFCoeff );
+ property_name = base_property_name + "/dynamic_friction_coeff";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), &dynamicFCoeff );
+
+ if (eSteerType == stCaster) {
+ property_name = base_property_name + "/steering-angle-deg";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), this, &FGLGear::GetSteerAngleDeg );
+ property_name = base_property_name + "/castered";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), &Castered);
+ }
+ }
+
+ if( isRetractable ) {
+ property_name = base_property_name + "/pos-norm";
+ fdmex->GetPropertyManager()->Tie( property_name.c_str(), &GearPos );
+ }