BVE 緩和曲線の正体を探る
BVE5では緩和曲線が使えますが、他軌道で使う場合や、自軌道が緩和曲線で曲がる際に他軌道を直線や任意の曲線にしたいことがしばしばあります。そこで緩和曲線の正体がなんなのかを理解し、使いこなせるようになりたいのです。
緩和曲線とは
緩和曲線はカントのある曲線に差し掛かるとき、いきなりカントが変化すると乗り心地が悪く大変なことになってしまうので、曲線の前後でカントと曲線半径を徐々に変化させて乗り心地をよくしてくれる曲線です。
カントを徐々に変化させますが、カントの値によって最適な曲線半径が変わってくるので曲線半径も徐々に変化していきます。この「曲線半径が徐々に変化する」ということが非常に厄介なのです。
サイン半波長逓減
サイン半波長というとサインカーブの波長の半分、こんな感じでしょう。
この割合で曲率が変化していると仮定して話を進めていきます。すると半径を求める式は が緩和曲線開始地点からの距離、 が曲線の半径(今回は500)、 は緩和曲線長とすると以下のようになるはずです。
これはグラフにすると から下がってきて のとき になるような曲線が描かれます。
直線逓減
直線は言うまでもありませんね。先程と同じように半径を求める式を立てると
とでもなるのでしょうか。これも から下がってきて のとき になるような反比例の曲線です。
BVEの緩和曲線
BVE5ではMapに Curve.BeginTransition();
という構文があります。
これは「ここから緩和曲線を開始します」という意味を持ちます。Curve.Begin()
や Curve.End()
が来ると緩和曲線は終了します。
では実際の動作を見てみましょう。Mapは以下のようになっています。
50; Curve.BeginTransition(); 100; Curve.Begin(500);
※説明用にストラクチャを配置しています。また、カントはありません。
この白い板に挟まれた部分が緩和曲線です。曲線半径が一定ではないことがお分かりいただけると思います。
ここからは「曲率」を使っていきます。 曲率 = 1/半径
です。要は半径の逆数です。
さて、BVE公式を見ると Curve.SetFunction()
という構文もあるようです。これには0か1を渡すようですが、0ならサイン半波長逓減、1だと直線逓減になるようです。直線逓減の方も見てみましょう。
なんか違う気がする。程度の差ですね。GIFにしてみましょう。
まあ違うことは違うのでそれぞれ見ていきましょう。
曲線半径から緩和曲線を作り出す
こうして曲率・曲線半径が出たわけですが当方中学生、本当は三角関数もまだ習っていないのでここから微分やら積分やらして座標を求める気もありません。スクリプト書いてゴリ押しします。
サイン半波長逓減
計算した半径の扇形を、弧がつながるように重ねて緩和曲線を円曲線の集合体とみなします。
まずは曲線半径を0.5mから1mごとに求めていきます。 この1mというのは適当に決めた「小さい数字」です。もっと小さくすれば精度も上がります。0.5mは1mの半分です。
Node.jsを適当に書いて以下の表が得られました。
2026590.348 |
225324.927 |
81223.819 |
41522.656 |
25184.911 |
16914.97 |
12158.739 |
9174.861 |
7180.917 |
5783.04 |
4765.422 |
4001.778 |
3414.214 |
2952.569 |
2583.359 |
2283.54 |
2036.832 |
1831.47 |
1658.782 |
1512.26 |
1386.945 |
1279.007 |
1185.444 |
1103.885 |
1032.429 |
969.546 |
913.986 |
864.727 |
820.922 |
781.866 |
746.972 |
715.744 |
687.762 |
662.672 |
640.171 |
619.999 |
601.934 |
585.786 |
571.392 |
558.611 |
547.321 |
537.42 |
528.819 |
521.443 |
515.23 |
510.128 |
506.094 |
503.097 |
501.112 |
500.123 |
500.123 |
さて、この半径の円弧を重ねて座標を計算します。幅0.1mの帯を緩和曲線の形に合うストラクチャを緩和曲線の開始位置に設置します。
ストラクチャを生成するスクリプトはこのようになりました。
"use strict"; //ファイル書き込みのためfsモジュールをロード const fs = require("fs"); const step = 1; //緩和曲線を細かく分けるときの単位 const radius = 500, //円曲線半径 length = 50; //緩和曲線長 const width = 0.1; //出力するストラクチャの帯の幅 let angle = 0, //線路の向いている角度 まっすぐ前方が0 cx, cz, //緩和曲線を細かく分けた扇の中心 br = Infinity; //前の扇の半径 const points = [[[0, 0], [0, 0]]]; //出力する座標 for(let d = 0; d < length; d += step){ //曲率 const curvature = (Math.sin(((d + 0.5) / length - 0.5) * Math.PI) + 1) / (2 * radius); //現在の半径 const cr = 1 / curvature; if(!cx){ //中心座標の初期値 cx = cr; cz = 0; }else{ //前の半径との差 const dr = br - cr; //中心座標 cx -= Math.cos(angle) * dr; cz += Math.sin(angle) * dr; } //軌道座標 points.push([ [ cx - Math.cos(angle) * (cr + width / 2), //左X cz + Math.sin(angle) * (cr + width / 2) //左Z ], [ cx - Math.cos(angle) * (cr - width / 2), //右X cz + Math.sin(angle) * (cr - width / 2) //右Z ] ]); br = cr; angle += curvature * step; } //CSVストラクチャを出力 let csv = "CreateMeshBuilder\n"; points.forEach(p => csv += `AddVertex,${p[0][0]},0,${p[0][1]}\n`+ `AddVertex,${p[1][0]},0,${p[1][1]}\n`); for(let i = 0, len = points.length - 1; i < len; i++){ csv += "AddFace," + [0, 2, 3, 1].map(n => i * 2 + n).join(",") + "\n"; } csv += "GenerateNormals\nSetColor,255,255,255"; fs.writeFile("Structures/TransitionCurve.csv", csv);
さて、これで得られたCSVストラクチャをX形式に変換してBVEに読み込ませてみます。緩和曲線の開始地点にこのストラクチャを置くと…
いい感じですね。ピッタリです。(直線逓減モードのままストラクチャ置いて「ずれてるー!」ってなったのは内緒)
直線逓減
先程のスクリプトの曲率を求める21行目を
const curvature = (d + 0.5) / (radius * length);
として同様にストラクチャにすればOKです。こちらも問題なくできました。
まとめ
緩和曲線は難しい。