From e704d589f219c6cb605ea905f1516d480a789196 Mon Sep 17 00:00:00 2001
From: Bertrand Coconnier <bcoconni@users.sourceforge.net>
Date: Mon, 31 Dec 2018 00:59:21 +0100
Subject: [PATCH] [JSBSim] Takes into account the amount of unusable fuel.

Properties /consumable/fuel/tank[*]/unusable-* supplied in the -set.xml file are propagated to the aircraft model and accounted for in the tanks management.
---
 src/FDM/JSBSim/JSBSim.cxx                    | 18 +++-
 src/FDM/JSBSim/input_output/FGXMLElement.cpp | 29 +++---
 src/FDM/JSBSim/models/FGPropulsion.cpp       |  4 +-
 src/FDM/JSBSim/models/propulsion/FGTank.cpp  | 30 ++++--
 src/FDM/JSBSim/models/propulsion/FGTank.h    | 98 ++++++++++++--------
 5 files changed, 115 insertions(+), 64 deletions(-)

diff --git a/src/FDM/JSBSim/JSBSim.cxx b/src/FDM/JSBSim/JSBSim.cxx
index 004647c81..14855def1 100644
--- a/src/FDM/JSBSim/JSBSim.cxx
+++ b/src/FDM/JSBSim/JSBSim.cxx
@@ -273,19 +273,29 @@ FGJSBsim::FGJSBsim( double dt )
       double d;
       SGPropertyNode * node = fgGetNode("/consumables/fuel/tank", i, true);
       FGTank* tank = Propulsion->GetTank(i);
+      SGPropertyNode * prop = node->getNode( "density-ppg", true );
 
-      d = node->getNode( "density-ppg", true )->getDoubleValue();
+      d = prop->getDoubleValue();
       if( d > 0.0 ) {
         tank->SetDensity( d );
       } else {
-        node->getNode( "density-ppg", true )->setDoubleValue( SG_MAX2<double>(tank->GetDensity(), 0.1) );
+        prop->setDoubleValue( SG_MAX2<double>(tank->GetDensity(), 0.1) );
       }
 
-      d = node->getNode( "level-lbs", true )->getDoubleValue();
+      prop = node->getNode( "level-lbs", true );
+      d = prop->getDoubleValue();
       if( d > 0.0 ) {
         tank->SetContents( d );
       } else {
-        node->getNode( "level-lbs", true )->setDoubleValue( tank->GetContents() );
+        prop->setDoubleValue( tank->GetContents() );
+      }
+
+      prop = node->getNode( "unusable-gal_us", true );
+      d = prop->getDoubleValue();
+      if ( d > 0.0 ) {
+        tank->SetUnusableVolume( d );
+      } else {
+        prop->setDoubleValue(tank->GetUnusableVolume());
       }
       /* Capacity is read-only in FGTank and can't be overwritten from FlightGear */
       node->getNode("capacity-gal_us", true )->setDoubleValue( tank->GetCapacityGallons() );
diff --git a/src/FDM/JSBSim/input_output/FGXMLElement.cpp b/src/FDM/JSBSim/input_output/FGXMLElement.cpp
index 55e0fc09f..a4861052b 100644
--- a/src/FDM/JSBSim/input_output/FGXMLElement.cpp
+++ b/src/FDM/JSBSim/input_output/FGXMLElement.cpp
@@ -8,33 +8,27 @@
  ------------- Copyright (C) 2001  Jon S. Berndt (jon@jsbsim.org) -------------
 
  This program is free software; you can redistribute it and/or modify it under
- the terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2 of the License, or (at your option) any later
- version.
+ the terms of the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option) any
+ later version.
 
  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
  details.
 
- You should have received a copy of the GNU Lesser General Public License along with
- this program; if not, write to the Free Software Foundation, Inc., 59 Temple
- Place - Suite 330, Boston, MA  02111-1307, USA.
+ You should have received a copy of the GNU Lesser General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc., 59
+ Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
- Further information about the GNU Lesser General Public License can also be found on
- the world wide web at http://www.gnu.org.
+ Further information about the GNU Lesser General Public License can also be
+ found on the world wide web at http://www.gnu.org.
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 INCLUDES
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
 
-#include <cmath>
-#include <cstdlib>
-#include <iostream>
-#include <sstream>
-
 #include "FGXMLElement.h"
-#include "string_utilities.h"
 #include "FGJSBBase.h"
 
 using namespace std;
@@ -91,6 +85,12 @@ Element::Element(const string& nm)
     convert["FT3"]["M3"] = 1.0/convert["M3"]["FT3"];
     convert["LTR"]["IN3"] = 61.0237441;
     convert["IN3"]["LTR"] = 1.0/convert["LTR"]["IN3"];
+    convert["GAL"]["FT3"] = 0.133681;
+    convert["FT3"]["GAL"] = 1.0/convert["GAL"]["FT3"];
+    convert["IN3"]["GAL"] = convert["IN3"]["FT3"]*convert["FT3"]["GAL"];
+    convert["LTR"]["GAL"] = convert["LTR"]["IN3"]*convert["IN3"]["GAL"];
+    convert["M3"]["GAL"] = 1000.*convert["LTR"]["GAL"];
+    convert["CC"]["GAL"] = convert["CC"]["IN3"]*convert["IN3"]["GAL"];
     // Mass & Weight
     convert["LBS"]["KG"] = 0.45359237;
     convert["KG"]["LBS"] = 1.0/convert["LBS"]["KG"];
@@ -177,6 +177,7 @@ Element::Element(const string& nm)
     convert["M3"]["M3"] = 1.0;
     convert["FT3"]["FT3"] = 1.0;
     convert["LTR"]["LTR"] = 1.0;
+    convert["GAL"]["GAL"] = 1.0;
     // Mass & Weight
     convert["KG"]["KG"] = 1.00;
     convert["LBS"]["LBS"] = 1.00;
diff --git a/src/FDM/JSBSim/models/FGPropulsion.cpp b/src/FDM/JSBSim/models/FGPropulsion.cpp
index c78ddc9a8..0ebaaf623 100644
--- a/src/FDM/JSBSim/models/FGPropulsion.cpp
+++ b/src/FDM/JSBSim/models/FGPropulsion.cpp
@@ -204,7 +204,7 @@ void FGPropulsion::ConsumeFuel(FGEngine* engine)
       if (TankPriority != 0) {
         switch(Tank->GetType()) {
         case FGTank::ttFUEL:
-          if ((Tank->GetContents() > 0.0) && Tank->GetSelected() && (TankPriority == CurrentFuelTankPriority)) {
+          if ((Tank->GetContents() > Tank->GetUnusable()) && Tank->GetSelected() && (TankPriority == CurrentFuelTankPriority)) {
             TanksWithFuel++;
             Starved = false;
             FeedListFuel.push_back(TankId);
@@ -236,7 +236,7 @@ void FGPropulsion::ConsumeFuel(FGEngine* engine)
             break;
           case FGTank::ttOXIDIZER:
             hasOxTanks = true;
-            if (Tank->GetContents() > 0.0 && Tank->GetSelected() && TankPriority == CurrentOxidizerTankPriority) {
+            if (Tank->GetContents() > Tank->GetUnusable() && Tank->GetSelected() && TankPriority == CurrentOxidizerTankPriority) {
               TanksWithOxidizer++;
               if (TanksWithFuel > 0) Starved = false;
               FeedListOxi.push_back(TankId);
diff --git a/src/FDM/JSBSim/models/propulsion/FGTank.cpp b/src/FDM/JSBSim/models/propulsion/FGTank.cpp
index a414a5810..864076b0e 100644
--- a/src/FDM/JSBSim/models/propulsion/FGTank.cpp
+++ b/src/FDM/JSBSim/models/propulsion/FGTank.cpp
@@ -63,7 +63,7 @@ FGTank::FGTank(FGFDMExec* exec, Element* el, int tank_number)
   Radius = Contents = Standpipe = Length = InnerRadius = 0.0;
   ExternalFlow = 0.0;
   InitialStandpipe = 0.0;
-  Capacity = 0.00001;
+  Capacity = 0.00001; UnusableVol = 0.0;
   Priority = InitialPriority = 1;
   vXYZ.InitMatrix();
   vXYZ_drain.InitMatrix();
@@ -95,6 +95,8 @@ FGTank::FGTank(FGFDMExec* exec, Element* el, int tank_number)
     Capacity = el->FindElementValueAsNumberConvertTo("capacity", "LBS");
   if (el->FindElement("contents"))
     InitialContents = Contents = el->FindElementValueAsNumberConvertTo("contents", "LBS");
+  if (el->FindElement("unusable-volume"))
+    UnusableVol = el->FindElementValueAsNumberConvertTo("unusable-volume", "GAL");
   if (el->FindElement("temperature"))
     InitialTemperature = Temperature = el->FindElementValueAsNumber("temperature");
   if (el->FindElement("standpipe"))
@@ -115,6 +117,13 @@ FGTank::FGTank(FGFDMExec* exec, Element* el, int tank_number)
     Capacity = 0.00001;
     Contents = 0.0;
   }
+  if (Capacity <= GetUnusable()) {
+    cerr << el->ReadFrom() << "Tank capacity (" << Capacity
+         << " lbs) is lower than the amount of unusable fuel (" << GetUnusable()
+         << " lbs) for tank " << tank_number
+         << "! Did you accidentally swap unusable and capacity?" << endl;
+    throw("tank definition error");
+  }
   if (Contents > Capacity) {
     cerr << el->ReadFrom() << "Tank content (" << Contents
          << " lbs) is greater than tank capacity (" << Capacity
@@ -122,6 +131,11 @@ FGTank::FGTank(FGFDMExec* exec, Element* el, int tank_number)
          << "! Did you accidentally swap contents and capacity?" << endl;
     throw("tank definition error");
   }
+  if (Contents < GetUnusable()) {
+    cerr << el->ReadFrom() << "Tank content (" << Contents
+         << " lbs) is lower than the amount of unusable fuel (" << GetUnusable()
+         << " lbs) for tank " << tank_number << endl;
+  }
 
   PctFull = 100.0*Contents/Capacity;            // percent full; 0 to 100.0
 
@@ -253,17 +267,16 @@ double FGTank::Drain(double used)
 {
   double remaining = Contents - used;
 
-  if (remaining >= 0) { // Reduce contents by amount used.
-
+  if (remaining >= GetUnusable()) { // Reduce contents by amount used.
     Contents -= used;
-    PctFull = 100.0*Contents/Capacity;
-
   } else { // This tank must be empty.
+    if (Contents > GetUnusable())
+      Contents = GetUnusable();
 
-    Contents = 0.0;
-    PctFull = 0.0;
+    remaining = Contents;
   }
 
+  PctFull = 100.0*Contents/Capacity;
   CalculateInertias();
 
   return remaining;
@@ -443,6 +456,9 @@ void FGTank::bind(FGPropertyManager* PropertyManager)
   property_name = base_property_name + "/contents-lbs";
   PropertyManager->Tie( property_name.c_str(), (FGTank*)this, &FGTank::GetContents,
                                        &FGTank::SetContents );
+  property_name = base_property_name + "/unusable-volume-gal";
+  PropertyManager->Tie( property_name.c_str(), (FGTank*)this, &FGTank::GetUnusableVolume,
+                        &FGTank::SetUnusableVolume );
   property_name = base_property_name + "/pct-full";
   PropertyManager->Tie( property_name.c_str(), (FGTank*)this, &FGTank::GetPctFull);
 
diff --git a/src/FDM/JSBSim/models/propulsion/FGTank.h b/src/FDM/JSBSim/models/propulsion/FGTank.h
index 0c003d6f7..5ccf5c366 100644
--- a/src/FDM/JSBSim/models/propulsion/FGTank.h
+++ b/src/FDM/JSBSim/models/propulsion/FGTank.h
@@ -7,21 +7,21 @@
  ------------- Copyright (C) 1999  Jon S. Berndt (jon@jsbsim.org) -------------
 
  This program is free software; you can redistribute it and/or modify it under
- the terms of the GNU Lesser General Public License as published by the Free Software
- Foundation; either version 2 of the License, or (at your option) any later
- version.
+ the terms of the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option) any
+ later version.
 
  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
  details.
 
- You should have received a copy of the GNU Lesser General Public License along with
- this program; if not, write to the Free Software Foundation, Inc., 59 Temple
- Place - Suite 330, Boston, MA  02111-1307, USA.
+ You should have received a copy of the GNU Lesser General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc., 59
+ Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
- Further information about the GNU Lesser General Public License can also be found on
- the world wide web at http://www.gnu.org.
+ Further information about the GNU Lesser General Public License can also be
+ found on the world wide web at http://www.gnu.org.
 
 FUNCTIONAL DESCRIPTION
 --------------------------------------------------------------------------------
@@ -46,8 +46,6 @@ INCLUDES
 
 #include "FGJSBBase.h"
 #include "math/FGColumnVector3.h"
-#include "math/FGFunction.h"
-#include <string>
 
 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 FORWARD DECLARATIONS
@@ -58,6 +56,7 @@ namespace JSBSim {
 class Element;
 class FGPropertyManager;
 class FGFDMExec;
+class FGFunction;
 
 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 CLASS DOCUMENTATION
@@ -69,8 +68,8 @@ CLASS DOCUMENTATION
  
     Fuel temperature is calculated using the following assumptions:
 
-    Fuel temperature will only be calculated for tanks which have an initial fuel
-    temperature specified in the configuration file.
+    Fuel temperature will only be calculated for tanks which have an initial
+    fuel temperature specified in the configuration file.
 
     The surface area of the tank is estimated from the capacity in pounds.  It
     is assumed that the tank is a wing tank with dimensions h by 4h by 10h. The
@@ -84,27 +83,30 @@ CLASS DOCUMENTATION
 
 <h3>Fuel Dump:</h3>
 
-    Fuel dumping is handled by the FGPropulsion class.  A standpipe can be defined
-    here for each tank which sets the level of contents (in pounds) which is not dumpable.
-    Default standpipe level is zero, making all contents dumpable.
+    Fuel dumping is handled by the FGPropulsion class.  A standpipe can be
+    defined here for each tank which sets the level of contents (in pounds)
+    which is not dumpable.  Default standpipe level is zero, making all contents
+    dumpable.
 
 <h3>Fuel Transfer:</h3>
 
-    Fuel transfer is handled by the FGPropulsion class, however the contents of tanks
-    may be manipulated directly using the SetContents() function here, or via the property
-    tree at <tt>propulsion/tank[i]/contents-lbs</tt>, where i is the tank number (Tanks
-    are automatically numbered, starting at zero, in the order in which they are read in
-    the aircraft configuration file).  The latter method allows one to use a system of FCS
-    components to control tank contents.
+    Fuel transfer is handled by the FGPropulsion class, however the contents of
+    tanks may be manipulated directly using the SetContents() function here, or
+    via the property tree at <tt>propulsion/tank[i]/contents-lbs</tt>, where i
+    is the tank number (Tanks are automatically numbered, starting at zero, in
+    the order in which they are read in the aircraft configuration file).  The
+    latter method allows one to use a system of FCS components to control tank
+    contents.
 
-    There is also a property <tt>propulsion/tank[i]/external-flow-rate-pps</tt>. Setting
-    this property to a positive value causes the tank to fill at the rate specified.
-    Setting a negative number causes the tank to drain. The value is the rate in pounds
-    of fuel per second. The tank will not fill past 100% full and will not drain below 0%.
-    Fuel may be transfered between two tanks by setting the source tank's external flow rate
-    to a negative value and the destination's external flow rate to the same positive value.
-    Care must be taken to stop fuel flow before the source tank becomes empty to prevent
-    phantom fuel being created.
+    There is also a property
+    <tt>propulsion/tank[i]/external-flow-rate-pps</tt>. Setting this property to
+    a positive value causes the tank to fill at the rate specified.  Setting a
+    negative number causes the tank to drain. The value is the rate in pounds of
+    fuel per second. The tank will not fill past 100% full and will not drain
+    below 0%.  Fuel may be transfered between two tanks by setting the source
+    tank's external flow rate to a negative value and the destination's external
+    flow rate to the same positive value.  Care must be taken to stop fuel flow
+    before the source tank becomes empty to prevent phantom fuel being created.
 
 <h3>Configuration File Format:</h3>
 
@@ -133,6 +135,7 @@ CLASS DOCUMENTATION
   <contents unit="{LBS | KG}"> {number} </contents>
   <temperature> {number} </temperature> <!-- must be degrees fahrenheit -->
   <standpipe unit="{LBS | KG"}> {number} </standpipe>
+  <unusable unit="{LBS | KG}"> {number} </unusable>
   <priority> {integer} </priority>
   <density unit="{KG/L | LBS/GAL}"> {number} </density>
   <type> {string} </type> <!-- will override previous density setting -->
@@ -144,16 +147,19 @@ CLASS DOCUMENTATION
 - \b type - One of FUEL or OXIDIZER.  This is required.
 - \b radius - Equivalent radius of tank for modeling slosh, defaults to inches.
 - \b grain_config type - One of CYLINDRICAL or ENDBURNING.
-- \b length - length of tank for modeling solid fuel propellant grain, defaults to inches.
+- \b length - length of tank for modeling solid fuel propellant grain, defaults
+              to inches.
 - \b capacity - Capacity, defaults to pounds.
 - \b contents - Initial contents, defaults to pounds.
 - \b temperature - Initial temperature, defaults to degrees Fahrenheit.
 - \b standpipe - Minimum contents to which tank can dump, defaults to pounds.
+- \b unusable - Contents that cannot be used for combustion in the engine,
+                defaults to pounds.
 - \b priority - Establishes feed sequence of tank. "1" is the highest priority.
 - \b density - Density of liquid tank contents.
-- \b type - Named fuel type. One of AVGAS, JET-A, JET-A1, JET-B, JP-1, JP-2, JP-3,
-- \b        JP-4, JP-5, JP-6, JP-7, JP-8, JP-8+100, RP-1, T-1, ETHANOL, HYDRAZINE,
-- \b        F-34, F-35, F-40, F-44, AVTAG, AVCAT
+- \b type - Named fuel type. One of AVGAS, JET-A, JET-A1, JET-B, JP-1, JP-2,
+            JP-3, JP-4, JP-5, JP-6, JP-7, JP-8, JP-8+100, RP-1, T-1, ETHANOL,
+            HYDRAZINE, F-34, F-35, F-40, F-44, AVTAG, AVCAT
 
 location:
 - \b x - Location of tank on aircraft's x-axis, defaults to inches.
@@ -178,6 +184,7 @@ be printed to the console if the location is not given
 - \b contents - 0.0
 - \b temperature - -9999.0 (flag which indicates no temperature is set)
 - \b standpipe - 0.0 (all contents may be dumped)
+- \b unusable - 0.0 (all contents may be burnt)
 - \b priority - 1 (highest feed sequence priority)
 - \b density - 6.6
 
@@ -195,7 +202,8 @@ class FGTank : public FGJSBBase
 {
 public:
   /** Constructor.
-      The constructor reads in the defining parameters from a configuration file.
+      The constructor reads in the defining parameters from a configuration
+      file.
       @param exec a pointer to the base FGFDMExec instance.
       @param el a pointer to the Tank element.
       @param tank_number the tank number (zero based).
@@ -271,7 +279,19 @@ public:
 
   /** Returns the density of a named fuel type.
       @return the density, in lbs/gal, or 6.6 if name cannot be resolved. */
-  double ProcessFuelName(const std::string& name); 
+  double ProcessFuelName(const std::string& name);
+
+  /** Returns the amount of unusable fuel in the tank.
+      @return the amount in lbs. */
+  double GetUnusable(void) const {return UnusableVol*Density;}
+
+  /** Returns the unusable volume of fuel in the tank.
+      @return the volume in gal. */
+  double GetUnusableVolume(void) const {return UnusableVol;}
+
+  /** Sets the volume of unusable fuel in the tank.
+      @param amount the amount of unusable fuel in gal. */
+  void SetUnusableVolume(double volume) {UnusableVol = volume;}
 
   double GetIxx(void) const {return Ixx;}
   double GetIyy(void) const {return Iyy;}
@@ -289,7 +309,11 @@ public:
   int  GetPriority(void) const {return Priority;}
   void SetPriority(int p) { Priority = p; Selected = p>0 ? true:false; } 
 
+  /** Returns the fuel density.
+      @return the density in lbs/gal. */
   double GetDensity(void) const {return Density;}
+  /** Sets the fuel density.
+      @param d the density in lbs/gal. */
   void   SetDensity(double d) { Density = d; }
 
   double GetExternalFlow(void) const {return ExternalFlow;}
@@ -298,7 +322,7 @@ public:
   FGColumnVector3 GetXYZ(void) const;
   double GetXYZ(int idx) const;
 
-  const GrainType GetGrainType(void) const {return grainType;}
+  GrainType GetGrainType(void) const {return grainType;}
 
   double Fill(double amount);
   void SetContents(double amount);
@@ -321,7 +345,7 @@ private:
   FGFunction* function_ixx;
   FGFunction* function_iyy;
   FGFunction* function_izz;
-  double Capacity;
+  double Capacity, UnusableVol;
   double Radius;
   double InnerRadius;
   double Length;