BVE 地上子2点間照査

ある間隔で設置した2つの地上子を通過した時間により列車速度を判定し、速度超過であれば非常制動を行う仕組み。
タイマーが地上にある地上時間比較式とタイマーが車両にある車上時間比較式の2つがあるが、やりたいことはほぼ同じなので詳細は割愛。要はタイマーの時間は固定で、制限速度に応じて地上子の間隔を変える。

# 1.簡単な方法
地上子を通過するとSetBeaconData関数が呼ばれる。2回呼ばれたときのそれぞれの時刻の差とタイマーを比較して、時刻差<タイマーのときに非常ブレーキをかけてやれば良い

## 1) タイマー開始とタイマー終了の2機能を使う方法
「タイマー開始」の時刻を保存し、「タイマー終了」の時刻との差を求める。
ATS-PTプラグインのように2種類の地上子を用意したり、Optionalの値によって機能を分けても良い。

~~~C#
// 変数・定数
private int nowTime; // 現在時刻[ms] (Elapse関数で毎フレームセット)
private int oldTime; // 前回の地上子通過時刻[ms]
private const int MAX_BEACON_TIME = 500; // タイマー時間[ms]

void SetBeaconData(AtsBeaconData beaconData)
{
  switch (beaconData.Type)
  {
    case 10: // タイマー開始
      this.oldTime = nowTime; // 現在時刻を代入
      break;
    case 11: // タイマー終了
      if (nowTime - this.oldTime < MAX_BEACON_TIME)
      {
        // 通過時間がタイマー時間より短いなら非常制動
        ApplyEmgBeake();
      }
      break;
  }
}
~~~

## 2) 1機能でやる方法
実車に近い方法。地上子を通過した時点でタイマーに残り時間をセットし、毎フレーム減らしていく。また地上子を通過した際にタイマーに時間が残っていたら非常制動させる。

~~~C#
// 変数・定数
private int restTimer; // タイマー残り時間[ms]
private int oldTime; // 前フレームの時刻[ms]
private const int MAX_BEACON_TIME = 500; // タイマー時間[ms]

public static AtsPlugin.AtsHandles Elapse(AtsPlugin.AtsVehicleState vehicleState, IntPtr panel, IntPtr sound)
{
  // (中略)
  if (this.restTimer > 0)
  {
    this.restTimer -= vehicleState.Time - oldTime; // 前フレームとの差を減らす
    if (this.restTimer < 0) this.restTimer = 0;
  }
  // (中略)
  this.oldTime = vehicleState.Time;
}

public static void SetBeaconData(AtsBeaconData beaconData)
{
  switch (beaconData.Type)
  {
    case 10: // タイマー開始
      if (this.restTimer > 0)
      {
        // 通過時間がタイマー時間より短いなら非常制動
        ApplyEmgBeake();
      }
      // タイマーに残り時間をセット
      this.restTimer = MAX_BEACON_TIME;
      break;
  }
}

~~~



# 2.より精密に
　上記の方法は地上子通過の前フレーム間の時間差で行うため、厳密には地上子間の通過時間ではない。また、フレームレートが下がった場合に誤差が大きくなるという指摘もある。(※追記：逆に、フレームレートが極端に大きい場合(60fpsを超える場合)は計算誤差が大きくなる。BVEから取得できる時刻はms単位のため。)
　そこで、地上子通過の前後のフレームから地上子通過時刻を擬似的に求めて判定を行ってみる。
　具体的には、地上子の設定距離程を通知する地上子を設置し、前フレームの距離程～地上子間の距離と地上子～次フレームの距離程の比から、地上子通過時刻を求める。
　※アナログな保安装置なので厳密的にやらなくてもいいと個人的には思う。さまざまな条件によって誤差が発生するので+5km/hとか+10km/hで設定されているわけだし。

~~~C#
// 変数・定数
private bool flag; // 地上子通過フラグ
private double oldTime; // 前回の地上子通過時刻[ms]
private double newTime; // 今回の地上子通過時刻[ms]
private double beaconLocation; // 地上子設置距離程[m]
private const int MAX_BEACON_TIME = 500; // タイマー時間[ms]

public static AtsPlugin.AtsHandles Elapse(AtsPlugin.AtsVehicleState vehicleState, IntPtr panel, IntPtr sound)
{
  // (中略)
  // タイマー地上子を通過した時
  if (this.flag == true) {
    double passLocation1 = ●; // 地上子通過の前フレームの距離程[m]
    int passTime1 = ★; // 地上子通過の前フレームの時刻[ms]
    double passLocation2 = vehicleState.Location; // 地上子通過の後フレームの距離程[m]
    int passTime2 = vehicleState.Time; // 地上子通過の後フレームの時刻[ms]

    if (passLocation1 == passLocation2) {
      newTime = passTime2; // 変化していない→計算する必要がない
    } else {
      newTime = ( ( (passLocation2 - beaconLocation) * passTime1 + (beaconLocation - passLocation1) * passTime2 ) / (passLocation2 - passLocation1); // 比から計算
    }

    if ( (newTime - oldTime) < MAX_BEACON_TIME) { {
      // 規定時間以内に通過したので非常制動
      ApplyEmgBeake();
    }
  }
  // (中略)
}

public static void SetBeaconData(AtsBeaconData beaconData)
{
  switch (beaconData.Type)
  {
    case 313010: // タイマー開始
      // タイマー地上子を通過した時
      this.flag = true; // 地上子通過フラグを立てる
      this.oldTime = this.newTime; // コピー
      break;
    case 313015: // タイマー開始
      // 距離程通知地上子を通過した時
      beaconLocation = (double)(beaconData.Optional) / 100.0; // sendDataにセットされた距離程をセット
      break;
  }
}
~~~

※AtsEXを使用すれば、313015番地上子は不要
~~~C#
using AtsEx.PluginHost.Plugins;

public static void SetBeaconData(AtsBeaconData beaconData)
{
  switch (beaconData.Type)
  {
    case 313010: // タイマー開始
      // タイマー地上子を通過した時
      this.flag = true; // 地上子通過フラグを立てる
      this.oldTime = this.newTime; // コピー

      int beaconIndex = BveHacker.Scenario.Route.Beacons.CurrentIndex; // 通過したBeaconのインデックス
      beaconLocation = BveHacker.Scenario.Route.Beacons[beaconIndex].Location;
      break;
  }
}
~~~