A much better control model
authorNick Gasson <nick@nickg.me.uk>
Thu, 30 Apr 2009 18:51:17 +0000 (19:51 +0100)
committerNick Gasson <nick@nickg.me.uk>
Thu, 30 Apr 2009 18:51:17 +0000 (19:51 +0100)
include/IRollingStock.hpp
models.gnuplot [new file with mode: 0644]
src/Engine.cpp
src/Train.cpp

index 9b3613ef7ba6d6b02f0fa013705f3adf3f0419c5..6acbc3f2e31470337cdd7dcd15e2288de95741e2 100644 (file)
 
 #include <tr1/memory>
 
-// Useful physical constants
-const double MU_S = 0.6;   // Static coefficient of friction for rails
-const double MU_K = 0.5;   // Kinetic coefficient of friction for rails
-
 // Interface for various powered and unpowered parts of the train
 struct IRollingStock {
    virtual ~IRollingStock() {}
diff --git a/models.gnuplot b/models.gnuplot
new file mode 100644 (file)
index 0000000..647d3bb
--- /dev/null
@@ -0,0 +1,31 @@
+# Use this file to tweak the model parameters for engines
+# All force values are in kN
+
+# The x-value where the two curves intersect should match
+# the maximum speed for the real engine
+
+# Stationary tractive effort
+ps(x) = 34.7
+
+# Knee point where tractive effort tails off (m/s)
+knee = 10.0 
+
+# Continuous tractive effort
+pc(x) = (ps(0) * knee)/x
+
+p(x) = x < knee ? ps(x) : pc(x)
+
+# Resistance
+# There should be at least an order of magnitude difference a << b << c
+a = 2
+b = 0.02
+c = 0.0035
+q(x) = a + b*x + c*x*x
+
+set xrange [0:100]
+set yrange [0:50]
+set xlabel "Velocity (m/s)"
+set ylabel "Force (kN)"
+
+plot p(x) title 'Tractive effort', \
+     q(x) title 'Resistance'
\ No newline at end of file
index f3e2b2a1afa09e8881df0e7dba5375ec78134b6d..c5263c3d0eeb0877b885abee19b927e7c8cf6111 100644 (file)
 using namespace std;
 using namespace std::tr1;
 
+//
+//    READ THIS FIRST: physics model used by the steam engine
+//
+// Note: everything here uses SI units unless otherwise stated
+//
+// The "tractive effort" is a measure of the power of a steam engine
+// at a given velocity: P(v). Note: the value usually quoted on the
+// Wikipedia entry for trains is the /starting/ tractive effort
+// (i.e. P(0) = Fmax)
+//
+// Tractive effort is at its maximum value and constant up to some
+// speed TRACTIVE_EFFORT_KNEE after which it decreases as 1/x
+// These values are really engine-dependant, but we're simplifying here.
+//   P(v) = {  Fmax, if v < TRACTIVE_EFFORT_KNEE
+//          {  (Fmax * TRACTIVE_EFFORT_KNEE) / v, otherwise
+//
+// Resistance on the train is a combination of friction, drag, and some
+// other sources. This is usually approximated by a quadratic:
+//   Q(v) = a + bv + cv^2
+// Where v is velocity. The constants a, b, and c are usually determined
+// experimentally (we'll just have to guess). Where Q(v) intersects the
+// tractive effort curve P(v) determines the train's maximum speed.
+//
+// Run models.gnuplot to see an example of these curves. The functions
+// in that file should match the code here!!
+//
+
 // Concrete implementation of a steam engine
 class Engine : public IRollingStock,
                public IController,
@@ -39,20 +66,28 @@ public:
    // IController interface
    void actOn(Action anAction);
 private:
+   double tractiveEffort() const;
+   double resistance() const;
+   
    IModelPtr myModel;
 
    double mySpeed, myMass, myBoilerPressure, myFireTemp;
    double myFuelOnFire;
+   double myStatTractiveEffort;
    bool isBrakeOn;
    
    static const double MODEL_SCALE;
+   static const double TRACTIVE_EFFORT_KNEE;
 };
 
 const double Engine::MODEL_SCALE(0.4);
+const double Engine::TRACTIVE_EFFORT_KNEE(10.0);
 
 Engine::Engine()
-   : mySpeed(0.0), myMass(1000.0), myBoilerPressure(1.0),
-     myFireTemp(0.0), myFuelOnFire(0.0), isBrakeOn(true)
+   : mySpeed(0.0), myMass(29.0), myBoilerPressure(1.0),
+     myFireTemp(0.0), myFuelOnFire(0.0),
+     myStatTractiveEffort(34.7),
+     isBrakeOn(true)
 {
    myModel = loadModel("train.obj", MODEL_SCALE);
 }
@@ -63,55 +98,39 @@ void Engine::render() const
    myModel->render();
 }
 
-// Compute the next state of the engine
-void Engine::update()
+// Calculate the current tractive effort
+double Engine::tractiveEffort() const
 {
-   const double stopSpeed = 0.001;
-   
-   // Maximum friction
-   const double Fmax = (abs(mySpeed) < stopSpeed ? MU_S : MU_K) * myMass;
-
-   // Maximum braking force
-   const double Bmax = isBrakeOn ? 2000.0 : 0.0;
+   if (mySpeed < TRACTIVE_EFFORT_KNEE)
+      return myStatTractiveEffort;
+   else
+      return (myStatTractiveEffort * TRACTIVE_EFFORT_KNEE) / mySpeed;
+}
 
-   // `forwards' force
-   const double drivingForce = myBoilerPressure;
+// Calculate the magnitude of the resistance on the train
+double Engine::resistance() const
+{
+   const double a = 2.0;
+   const double b = 0.02;
+   const double c = 0.0035;
+   return a + b*mySpeed + c*mySpeed*mySpeed;
+}
 
-   // Net force
-   double netForce;
-   if (Fmax + Bmax > drivingForce && abs(mySpeed) < stopSpeed)
-      netForce = 0.0;
-   else
-      netForce = drivingForce - Fmax - Bmax;
+// Compute the next state of the engine
+void Engine::update()
+{
+   const double P = tractiveEffort();
+   const double Q = resistance();
 
-   const double accel = netForce / myMass;
+   // A fudge factor to make the acceleration look realistic
+   const double MASS_TWEAK = 10.0;
    
-   // Consume some fuel
-   const double burnRate = 0.999;
-   myFuelOnFire *= burnRate;
-
-   // The fire temparature is simply a function of fuel
-   // TODO: find a better function!
-   myFireTemp = min(myFuelOnFire * 100.0, 1000.0);
-
-   // Boiler pressure is roughly proportional to temparature
-   myBoilerPressure = myFireTemp * 10.0;
-
-   // Accelerate the train
-   mySpeed += accel / 1000.0;
-
-   // Apply drag: drag is roughly proportional to the square of
-   // velocity at high speeds
-   const double dragCoeff = 0.1;
-   mySpeed -= dragCoeff * mySpeed * mySpeed;
-
-   debug() << "Hold: " << (Fmax + Bmax)
-           << ", go: " << drivingForce
-           << ", temp=" << myFireTemp
-           << ", pressure=" << myBoilerPressure
-           << ", accel=" << accel
-           << ", drag=" << (dragCoeff * mySpeed * mySpeed);
+   const double a = (P - Q) / (myMass * MASS_TWEAK);
+
+   mySpeed += a;
    
+   debug() << "P=" << P << ", Q=" << Q
+           << ", a=" << a << ", v=" << mySpeed;
 }
 
 // User interface to the engine
index 9a9b08a0e4ba03fba9757043ef10ee9d192e63e0..cc0f48afd5c6319deb0a51859f205bb31a17bac7 100644 (file)
@@ -71,8 +71,12 @@ Train::Train(IMapPtr aMap)
 void Train::update()
 {
    myEngine->update();
+
+   // How many metres does a tile correspond to?
+   const double M_PER_UNIT = 10.0;
    
-   mySegmentDelta += myEngine->speed();
+   const double FPS = 30.0;
+   mySegmentDelta += myEngine->speed() / FPS / M_PER_UNIT;
    
    if (mySegmentDelta >= mySegment->segmentLength()) {
       // Moved onto a new piece of track