実体ローラーコースターの計算
ヤコビの楕円関数にるローラーコースターの計算がありますが、理論計算ではなく、此処では実体に近い条件で計算します。
その為、楕円関数は使用せず、ローラーコースターのコースを微小分割し、位置と速度エネルギーから移動の計算をします。
直線から、円につながる部分を除けば円運動なので、円運動部分はヤコビの楕円関数を使用しても良いのですが、結果はあまり変わりません。
基本的に考慮する値として、
人の乗る箱、車輪、乗っている人の総重量 M
重心の位置 G
重心の慣性モーメント Ig
車輪の半径 Rs
車輪の慣性モーメント
Ih
車輪の位置 S
コースターの半径 Rc
軌道の抵抗、空気の抵抗
等を考慮する必要がありますが、軌道の抵抗、空気の抵抗に関しては難しいので計算に入れていません。
又、車輪の位置Sに関しては固定値としています。
計算は水平直進時速度を与えて、エネルギー量からコースターの軌道をどうたどるか計算します。
円軌道上にある場合は、箱も回転するので、回転するのは箱と車輪の二つになります。
直進する場合はの回転は車輪のみです。
プログラム上では実際に高さ方向に移動した量として
Hr = hr-h
を使用しています。
全エネルギー量は変化しないことから、コースター半径上の速度は、直進時でのエネルギーから重心の位置エネルギーを引いて、残ったエネルギーで角速度をを計算します。
直進時のエネルギーと位置エネルギーが一致した点が、箱の停止する位置となります。
直進時のエネルギーがコースターの上死点での位置エネルギーと一致した場合、そこで停止し、直進時のエネルギーの方が大きい場合は、止まらずに回転し、小さい場合は、停止後逆行します。
円軌道上を移動している場合は、単振り子相当の振り子長さ計算により計算が出来ます。
その場合、完全に円運動した場合の、最下点での角速度を使用して計算をすることが出来ます。
ローラーコースターの軌道で問題となるのは、水平直線部分から円軌道につながる部分です。
円軌道部では。
車輪が、左図の様に、円軌道に接するため、重心位置が、Ho分だけ円軌道の中心位置に近づきます。
直線部分から円軌道に繋がる部分は、左図の様に、徐々に円軌道に入ります。
この区間は、車輪の間隔が広いほど長くなります。
この部分での計算は、二つの車輪が軌道に接触する点と、車輪の中心を結んだ直線の交点を計算し、この交点を回転中心として、仮の軌道半径を求め、その半径から速度を計算します。
直線移動は半径が無限大であり。半径無限大から円軌道上の半径に徐々に移行する計算になります。
重心の位置移動は左図の様になります。
各状態変移部分に番号を振って、この間を細かく分割して計算します。
車輪間の分割数をNとして、他の分割数を設定します。
円軌道部分は、車輪間の分割された長さΔlで円弧部分の長さを除算して分割数Nrを設定します。
分割された位置の速度と分割されたΔlからその場所での通過時間を計算し、通過時間を加算していけば、スタートしてから経過した時間後の位置が計算できることになります。
問題は、上死点で停止するか、停止する速度に近い場合です、Δlの通過時間が非常に長くなりアニメーション表示に支障がでます。
上死点での停止速度に対して、0.99999999程度ずれていれば、通常の計算では問題は発生しません。
左図は、プログラムの起動時の画面です。
車輪の間隔は1mに固定してあります、値を変更するのにはプログラム上で変更する必要があります。
上死点で停止する速度を入力しても、計算誤差により上死点で停止することはありません、その為、上死点で停止させる様に設定する為のチェックボックスが設けてあります。
アニメーションの表示を行う場合、デフォルトで分割時間がアニメーションのタイマーに使用されますが、あまりにもアニメーションが遅い場合、或いは早い場合はアニメタイマー時間のチェックボックスにチェックを入れ、アニメタイマー時間を設定します。
左図は半径1.5mの実行画面です。
上死点で停止せずに、回転をしています。
アニメーションを実行すると、スタートから回転停止を繰り返し表示します。
プログラム
unit Main; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls; type TForm1 = class(TForm) Image1: TImage; Memo1: TMemo; AnimBtn: TButton; Timer1: TTimer; RcEdit: TLabeledEdit; RsEdit: TLabeledEdit; hEdit: TLabeledEdit; IhEdit: TLabeledEdit; IgEdit: TLabeledEdit; CalcBtn: TButton; TimEdit: TLabeledEdit; NEdit: TLabeledEdit; VEdit: TLabeledEdit; MEdit: TLabeledEdit; ResetBtn: TButton; CheckBox1: TCheckBox; AnitimeEdit: TLabeledEdit; CheckBox2: TCheckBox; procedure FormCreate(Sender: TObject); procedure AnimBtnClick(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure CalcBtnClick(Sender: TObject); procedure ResetBtnClick(Sender: TObject); private { Private 宣言 } procedure CoasterDraw; procedure ImageClear; procedure BoxDraw(Hc, Wc: integer; Gx, Gy, Q, Sc:double); function angular_velocity(Rc0, R, Hr: double): double; procedure angular_revision; function Circle_Intersection(x0, y0, r0, x1, y1, r1: double; var xc0, yc0, xc1, yc1: double): boolean; procedure FirstData; procedure drawya(x, y: integer; Q: double); function HorizontalSpeed: double; procedure MsOut; function Calc_Ho(Rc, Rs: double): double; public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} uses system.Math; const g = 9.80665; // m/s^2 var Rc : double; // コースター半径 Rs : double; // 車輪半径 Ro : double; // 回転時重心位置半径 S : double; // 重心位置からの車輪水平距離 h : double; // 重心高さ Ig : double; // 重心位置慣性モーメント Ih : double; // 車輪四個の慣性モーメント M : double; // 全質量 V : double; // 水平速度 Wo : double; // 回転時角速度 Hr : double; // 重心の高さ方向移動量 Bh : double; // 箱の高さ Bw : double; // 箱の長さ Gh : double; // 箱に対する重心高さ N : integer; // 単位分割数 Na : integer; // アニメーション分割数 Qe : double; // 角度補正終了角 Tim : double; // 分解時間 Timi : integer; // タイマー時間 AniTime : double; // アニメ時タイマー指定時間 AQ : array of array of double; // X, Y Box角度 区間速度 区間距離 時間 ∑t AT : array of array of double; // x, y, 箱角度 CT : array of integer; // タイマー時間補正用値 MF : byte; // 0 頂点を超えず 1 頂点で停止 2 回転 Sc : double; // 作図スケール Hc, Wc : integer; // 作図中心 canvus 座標 TP : integer; // 頂点配列No TPF : boolean; // 頂点停止フラグ MsF : boolean; // 区間時間Overフラグ Ho : Double; // 円弧内軌道時箱高さ修正値 // 画像消去 procedure TForm1.ImageClear; begin image1.Canvas.Pen.Mode := pmCopy; Image1.Canvas.Brush.Style := bsSolid; Image1.Canvas.Brush.Color := clWhite; Image1.Canvas.FillRect(rect(0, 0, Image1.Width, Image1.Height)); end; // 箱の作図 // Hc, Wc 原点座標 // Gx, Gy 重心位置 // Q 箱の角度 // Sc 作図スケール procedure TForm1.BoxDraw(Hc, Wc: integer; Gx, Gy, Q, Sc:double); var Qr0, Qr1, Rr : double; // 車輪の位置角度半径 Qb0, Qb1, Rb0 : double; // 箱の上側の角度半径 Qb2, Qb3, Rb1 : double; // 箱の下側の角度半径 IGx, IGy : integer; // 重心位置 Iwx0, Iwy0, Iwx1, Iwy1, IRs : integer; // 車輪位置半径 Bx0, By0, Bx1, By1, Bx2, By2, Bx3, By3 : integer; // 箱の四点座標 Tmp, hms, BmG : double; begin hms := h - Rs; Tmp := arcTan(hms / S); Qr0 := pi + Tmp + Q; // 車輪位置の角度0 Qr1 := 2 * pi- Tmp + Q; // 車輪位置の角度1 Rr := sqrt(hms * hms + S * S) * Sc; // 重心から車輪の距離 BmG := Bh - Gh; Tmp := arcTan(BmG / Bw * 2); Qb0 := pi - Tmp + Q; // 箱の隅の角度0 Qb1 := Tmp + Q; // 箱の隅の角度1 Rb0 := sqrt(BmG * BmG + Bw * Bw / 4) * Sc; // 重心から箱の隅迄の距離0,1 Tmp := arcTan(Gh / Bw * 2); Qb2 := 2 * pi - Tmp + Q; // 箱の隅の角度2 Qb3 := pi + Tmp + q; // 箱の隅の角度3 Rb1 := sqrt(Gh * Gh + Bw * Bw / 4) * Sc; // 重心から箱の隅迄の距離2,3 // 重心の座標 IGx := round(Gx * Sc) + Wc; IGy := -round(Gy * Sc) + Hc; Irs := round(Rs * Sc); // 車輪の半径 // 車輪の座標 Iwx0 := round(cos(Qr0) * Rr) + IGx; Iwy0 := -round(sin(Qr0) * Rr) + IGy; Iwx1 := round(cos(Qr1) * Rr) + IGx; Iwy1 := -round(sin(Qr1) * Rr) + IGy; // 車輪の作図 Image1.Canvas.Brush.Style := bsClear; Image1.Canvas.Pen.Style := psSolid; Image1.Canvas.Pen.Color := clGreen; Image1.Canvas.Ellipse(Iwx0 - Irs, Iwy0 - Irs, Iwx0 + Irs + 1, Iwy0 + Irs + 1); Image1.Canvas.Pen.Color := clRed; Image1.Canvas.Ellipse(Iwx1 - Irs, Iwy1 - Irs, Iwx1 + Irs + 1, Iwy1 + Irs + 1); // 箱の四隅計算 Bx0 := round(cos(Qb0) * Rb0) + IGx; By0 := -round(sin(Qb0) * Rb0) + IGy; Bx1 := round(cos(Qb1) * Rb0) + IGx; By1 := -round(sin(Qb1) * Rb0) + IGy; Bx2 := round(cos(Qb2) * Rb1) + IGx; By2 := -round(sin(Qb2) * Rb1) + IGy; Bx3 := round(cos(Qb3) * Rb1) + IGx; By3 := -round(sin(Qb3) * Rb1) + IGy; Image1.Canvas.Pen.Color := clFuchsia; // 箱の作図 Image1.Canvas.MoveTo(Bx0, By0); Image1.Canvas.LineTo(Bx1, By1); Image1.Canvas.LineTo(Bx2, By2); Image1.Canvas.LineTo(Bx3, By3); Image1.Canvas.LineTo(Bx0, By0); // 重心円 Image1.Canvas.Pen.Color := clMaroon; Image1.Canvas.Ellipse(IGx - 2, IGy - 2, IGx + 3, IGy + 3); end; // 円形軌道部高さ方向移動計算 // Rc 起動半径 // Rs 車輪半径 function TForm1.Calc_Ho(Rc, Rs: double): double; var Q, Hn, Hz : double; begin Q := arcSin(S / (Rc - Rs)); Hn := Rs - Rs * cos(Q); Hz := Rc - Rc * cos(Q); result := Hz - Hn; // 回転時高さ移動分 end; // コースター作図 procedure TForm1.CoasterDraw; var IRc, IRo : integer; Q : double; xli : integer; Dc : Double; Ls : Double; I : integer; dl : double; x0, y0, x1, y1, r0, r1 : double; xc0, yc0, xc1, yc1: double; x, y : double; Qd, Qg, Rg : double; xi, yi : integer; x0i, x1i, y0i, y1i : integer; XYi : array[0..5] of array[0..1] of integer; begin image1.Canvas.Pen.Mode := pmCopy; Image1.Canvas.Brush.Style := bsClear; // 作図中心位置 Hc := Image1.Height div 2 - 25; Wc := Image1.Width div 2; // 作図スケール設定 Dc := 200; if Rc < 4 then Dc := 50 + round(sqrt(Rc) * 75); if h >= 0 then Sc := Dc / Rc // 作図スケール else Sc := Dc / (Rc - h); // 軌道円作図 IRc := Round(Rc * Sc); Image1.Canvas.Pen.Style := psSolid; Image1.Canvas.Pen.Width := 2; Image1.Canvas.Pen.Color := clblue; Image1.Canvas.Ellipse(Wc - IRc, Hc - IRc, Wc + IRc + 1, Hc + IRc + 1); // 軌道直線作図 xli := round(sc * 0.5); Image1.Canvas.MoveTo(Wc - IRc - xli, Hc + IRc + 1); Image1.Canvas.LineTo(Wc + IRc + xli, Hc + IRc + 1); // 重心位置円弧部作図 Ho := Calc_Ho(Rc, Rs); // 回転時高さ移動分 Qe := arcSin(S / (Rc - Rs)); // 円弧の初めと終わりの角度 Ro := Rc - Ho - h; // 回転半径 xc0 := cos(-pi / 2 + Qe) * Ro; // 円弧の始まりと終わりX値 yc0 := sin(-pi / 2 + Qe) * Ro; // 円弧の始まりと終わりy値 x0i := Wc - round(xc0 * Sc); // 円弧の終わりX座標値 x1i := Wc + round(xc0 * Sc); // 円弧の始まりX座標値 y0i := Hc - round(yc0 * Sc); // 円弧の始まりと終わり座標値 y1i := y0i; IRo := round(Ro * Sc); // 半径 // 円弧作図 Image1.Canvas.Pen.Width := 1; Image1.Canvas.Arc(Wc - IRo, Hc - IRo, Wc + IRo + 1, Hc + IRo + 1, x1i, y1i, x0i + 1, y0i + 1); // 中心位置円作図 Image1.Canvas.Pen.Color := clRed; Image1.Canvas.Ellipse(Wc - 3, Hc - 3, Wc + 4, Hc + 4); // 重心移動直線部分作図 Image1.Canvas.Pen.Color := clblue; xi := Wc - round(S * Sc); // x座標 yi := Hc + round((Rc - h) * Sc); // y座標 Image1.Canvas.MoveTo(Wc - IRc - xli, yi); Image1.Canvas.LineTo(xi, yi); xi := Wc + round(S * Sc); Image1.Canvas.MoveTo(Wc + IRc + xli, yi); Image1.Canvas.LineTo(xi, yi); // 重心移動直線から円弧作図 侵入部 Qg := arcTan((h - rs) / S); // 重心と車輪の角度 Rg := sqrt((h - Rs) * (h - Rs) + S * S); dl := 2 * S / 4; x0 := 0; // コースター中心座標X y0 := 0; // コースター中心座標y r0 := Rc - Rs; // コースター半径 xi := Wc - round(S * Sc); // x座標 XYi[4, 0] := Wc + round(S * Sc); // 反対側x座標 XYi[4, 1] := yi; // 反対側y座標 Image1.Canvas.MoveTo(xi, yi); // 重心直線端点に移動 for I := 3 downto 0 do begin Ls := dl * I; // 後輪の位置 x1 := -Ls; // 後輪の位置X y1 := -(Rc - Rs); // 後輪の位置y r1 := S + S; // 前輪と後輪の距離 // 二円の交点 後輪の位置から前輪位置計算 if not Circle_Intersection(x0, y0, r0, x1, y1, r1, xc0, yc0, xc1, yc1) then break; if xc0 > xc1 then begin y := yc0; end else begin y := yc1; end; // 箱角度 Qd := arcsin(((Rc- Rs) + y) / r1); // 重心位置 x1 := - Ls + cos(Qg + Qd) * Rg; y1 := Rc - Rs - sin(Qg + Qd) * Rg; xi := round(x1 * Sc) + Wc; yi := round(y1 * sc) + Hc; XYi[I, 0] := Wc - round(x1 * Sc); // 反対側x座標 XYi[I, 1] := yi; // 反対側y座標 Image1.Canvas.LineTo(xi, yi); // 計算点まで線作図 end; xi := XYi[4, 0]; yi := XYi[4, 1]; Image1.Canvas.MoveTo(xi, yi); // 反対側重心直線端点に移動 for I := 3 downto 0 do begin xi := XYi[I, 0]; yi := XYi[I, 1]; Image1.Canvas.LineTo(xi, yi); // 反対側計算点まで線作図 end; end; // 区間時間より、区間の通過時間が長くなると、分割時間を自動調整するので // その時のメッセージ出力です。 procedure TForm1.MsOut; var yi, xi, pich : integer; begin pich := 15; yi := Image1.Height - pich * 3 - 1; xi := 50; Image1.Canvas.Font.Color := clGreen; Image1.Canvas.TextOut(xi, yi, '分割時間Over個数がゼロではありません。'); yi := yi + pich; Image1.Canvas.TextOut(xi, yi, '分割数を増やすか、分割時間を長くしてみてください。'); yi := yi + pich; Image1.Canvas.TextOut(xi, yi, '水平速度が上死点停止速度に近すぎても発生します。'); end; // 最上位点から落下した場合の水平移動速度 // 円の最下点の速度ではありません // 車輪の位置により、水平位置移動の重心の位置は円の最下点の重心位置より低くなります。 function TForm1.HorizontalSpeed: double; var Er : double; hr0 : double; begin result := 0; hr0 := Rc - h + Ro; Er := M * hr0 * g; if Er > 0 then result := sqrt(2 * Er / (M + Ih / Rs / Rs)); end; // 速度計算 // Wo 角速度 // V 水平速度 // Rc0 コースター半径 // R重心半径 // Hr 重心高さ function TForm1.angular_velocity(Rc0, R, Hr: double): double; var Es : double; // 水平速度エネルギー Er : double; // 最下点角速度エネルギー Vr, Rr: double; begin Vr := V / Rs); Rr := Rc0 / Rs ; Es := (M * V * V + Ih * Vr * Vr) / 2; Er := Es - Hr * M * g; if Er >= 0 then begin Wo := sqrt(2 * Er / (M * R * R + Ih * Rr * Rr + Ig)); Result := R * Wo; end else begin Er := -Er; Wo := sqrt(2 * Er / (M * R * R + Ih * Rr * Rr + Ig)); Result := - R * Wo; end; // Memo1.Lines.Add('角速度 ' + floatTostrF(Wo, fffixed, 10,7)); // Memo1.Lines.Add(' 速度 ' + floatTostrF(Vp, fffixed, 10,7)); end; // 二円の交点 // x0, y0, ro 円1のx,y座標r半径 // x1, y1, r1 円2のx,y座標r半径 // xc0,yc0 交点xy座標1 // xc0,yc0 交点xy座標2 function TForm1.Circle_Intersection(x0, y0, r0, x1, y1, r1: double; var xc0, yc0, xc1, yc1: double): boolean; var x, y : double; a : double; sq, xy2 : double; begin result := false; x1 := x1 - x0; y1 := y1 - y0; x := sqrt(x1 * x1 + y1 * y1); // 交点があったらTrue if (x < (r0 + r1)) and (x > abs(r0 - r1)) then result := true; // 交点が無かったら終了 if not result then exit; a := (x1 * x1 + y1 * y1 + r0 * r0 - r1 * r1) / 2; sq := sqrt((x1 * x1 + y1 * y1) * r0 * r0 - a * a); xy2 := x1 * x1 + y1 * y1; x := (a * x1 + y1 * sq) / xy2; y := (a * y1 - x1 * sq) / xy2; xc0 := x + x0; yc0 := y + x0; x := (a * x1 - y1 * sq) / xy2; y := (a * y1 + x1 * sq) / xy2; xc1 := x + x0; yc1 := y + x0; end; // ローラーコースターの移動計算 procedure TForm1.angular_revision; var Vp : double; // 特定位置の速度 dl : double; // Δ距離 dq : double; // Δ角度 dls : double; // 円部分Δ距離 dlm : double; // 円部分直線Δ距離 Ls : double; // 後輪位置 I, J, K, Nn, Nb : integer; x, y : double; x0, y0, r0, x1, y1, r1 : double; xc0, yc0, xc1, yc1 : double; Qd, Qg, Rg : double; Nr : integer; // Arc部分分割数 xi, yi : integer; dt : double; // 直線Δt Zt : double; // 区分用積算時間 P0, P1, P2, P3, P4, P5 : integer; // 通過点 PP : integer; // 上死点配列No tmax : double; // 区間最大時間 Zmax : double; // 全合計時間 Rf : double; // Rc代用値 直線から円区間 STF : boolean; // 転送フラグ procedure MFset; // 回転 停止 往復フラグセット var Es, Er : double; // 運動エネルギー begin ES := (M * V * V + Ih * V / Rs * V / Rs) / 2; // 水平移動エネルギー Er := (Rc - h + Ro) * M * g; // 高さエネルギー if Er > Es then MF := 0; // 往復 if Er = Es then MF := 1; // 頂点停止 if Er < Es then MF := 2; // 回転 // MF := 1 は演算誤差によりは発生しませんのでチェックボックスで設定 if CheckBox2.Checked then MF := 1;; end; procedure Sort; var I : integer; begin Nb := 0; Zt := 0; K := 0; // 配列Noリセット for I := P5 - 1 downto Nn do begin // 区間速度がプラスの範囲 回転する場合はMn=0 x := AQ[I, 6]; // 加算時間 if x > Zt then begin // 指定時間を超えたら y := (x - Zt) / Tim; // 差分計算/指定時間 J := trunc(y); // 少数点以下切り捨て if STF then begin At[Nb, 0] := AQ[I, 0]; // x座標 At[Nb, 1] := AQ[I, 1]; // y座標 At[Nb, 2] := AQ[I, 2]; // 箱角度 CT[Nb] := J; // 区間数保存 end; inc(Nb); // 配列Noインクリメント inc(K); // タイマーカウンターインクリメント K := K + J; // タイマーカウンター+区間数 Zt := K * Tim; // 指定時間計算 end; end; end; begin Memo1.Clear; Memo1.Lines.Add('重心半径 ' + floatTostrF(Ro, fffixed, 10,7)); Vp := HorizontalSpeed; Memo1.Lines.Add('最上点で停止する '); Memo1.Lines.Add(' 水平速度 ' + floatTostr(Vp)); // 上死点停止をチェックボックスで設定 if CheckBox2.Checked then V := VP * 1.0000000000001; // 僅かに停止する水平速度より早くします手前で停止するのを防止 // Memo1.Lines.Add(' 水平速度 ' + floatTostrF(V, fffixed, 15,12)); MsF := False; // 区間時間Overフラグリセット Rg := sqrt((h - Rs) * (h - Rs) + S * S); Qe := arcSin(S / (Rc - Rs)); // 円部の初めと終わりの角度 Qg := arcTan((h - rs) / S); // 重心と車輪の角度 dl := 2 * S / N; // 分割用距離 Nr := round(Ro * (2 * pi - Qe - Qe) / dl); // 円部 分割数 dq := (2 * pi - Qe - Qe) / Nr; // 円部Δ角度 dls := (2 * pi - Qe - Qe) * Ro / Nr; // 円部Δ距離 dlm := sin(dq / 2) * Ro * 2; // 円部Δ直線距離 I := trunc(Rc) - 1; // 直線部分割倍率 if I < 1 then I := 1; P0 := 0; // 直線終わり P1 := I * N; // 直線始め P2 := P1 + N; // 円から直線 PP := P2 + Nr div 2; // 円の中間 P3 := P2 + Nr; // 直線から円 P4 := P3 + N; // 直線終わり P5 := P4 + P1; // 直線始め MFset; // 回転フラグ 0 往復 1 上死点停止 2 回転 try // 0 1 2 3 4 5 6 setlength(AQ, P5, 7); // X, Y Box角度 区間速度 区間距離 時間 ∑t except on EOutOfMemory do begin application.MessageBox('計算用配列メモリー不足です' + #13#10 + '分割数が多すぎます。' + #13#10 + '分割数を少なくして再実行してください。','注意',0); end; end; for I := 0 to P5 - 1 do begin // 配列クリア for J := 0 to 6 do AQ[I, J] := 0; end; dt := dl / V; // Δt // 直線部分 for I := P5 - 1 downto P4 do begin Ls := dl * (I - P4) + S; // 重心位置 AQ[I, 0] := -Ls; // 重心位置X AQ[I, 1] := - Rc + h; // 重心位置y AQ[I, 2] := 0; // 箱の角度 AQ[I, 3] := V; // 速度 AQ[I, 4] := dl; // Δ距離 AQ[I, 5] := dt; // Δt end; // 直線から円 Bq0 := AQ[P4, 2]; // 箱角度0 for I := P4 - 1 downto P3 do begin Ls := dl * (I - P3 - N) + S + S; // 後輪の位置 x0 := 0; // コースター中心座標X y0 := 0; // コースター中心座標y r0 := Rc - Rs; // コースター半径 x1 := -Ls; // 後輪の位置X y1 := -(Rc - Rs); // 後輪の位置y r1 := S + S; // 前輪と後輪の距離 // 二円の交点 後輪の位置から前輪位置計算 if not Circle_Intersection(x0, y0, r0, x1, y1, r1, xc0, yc0, xc1, yc1) then break; if xc0 > xc1 then begin y := yc0; x := xc0; end else begin y := yc1; x := xc1; end; // 箱角度 Qd := arcsin(((Rc- Rs) + y) / r1); AQ[I, 2] := Qd; // 箱角度 // 重心位置 x1 := - Ls + cos(Qg + Qd) * Rg; y1 := Rc - Rs - sin(Qg + Qd) * Rg; AQ[I, 0] := x1; AQ[I, 1] := -y1; // 前輪位置 // xi := round(x * sc) + Wc; // yi := round(- y * sc) + Hc; // image1.Canvas.Ellipse(xi - Ri, yi - Ri, xi + Ri + 1, yi + Ri + 1); // 距離 直線で代用 yc0 := y / x; // α=y/x yc1 := yc0 * -Ls; // y=αx // 二つの車輪の軌道の接点と車輪の中心を結んだ直線の交点を回転中心とする軌道半径計算 rf := sqrt((yc1 - y) * (yc1 - y) + (-Ls - x) * (-Ls - x)) + Rs; // 軌道半径Rc代用値 r0 := Rf - h - Calc_Ho(Rf, Rs); // 半径Ro代用値 x := AQ[I, 0] - AQ[I + 1, 0]; y := AQ[I, 1] - AQ[I + 1, 1]; x1 := sqrt(x * x + y * y); // 区間距離 AQ[I, 4] := x1; Hr := (Rc - h) - y1; // 重心高さ Vp := angular_velocity(Rf, r0, Hr); // 重心速度Vp計算 半径r0の円として計算 AQ[I, 3] := Vp; if Vp >= 0 then Vp := (AQ[I + 1, 3] + Vp) * 0.5; // 区間平均速度 // 通過時間 Vp=0 は一個しか発生しないので平均値にゼロはなしMaxdoubleの値は基本的に無し if Vp <> 0 then AQ[I, 5] := x1 / Vp // Δt else AQ[I, 5] := Maxdouble; end; // 円部分 for I := P3 - 1 downto P2 do begin Qg := - pi / 2 + Qe + (P3 - I) * dq; Qd := Qg + pi / 2; // 箱角度 AQ[I, 2] := Qd; // 重心位置 x1 := cos(Qg) * Ro; y1 := sin(Qg) * Ro; AQ[I, 0] := x1; AQ[I, 1] := y1; // r0 := sqrt(x1 * x1 + y1 * y1); Roと同じ値になります Hr := (Rc - h) + y1; Vp := angular_velocity(Rc, Ro, Hr); // 円部分の速度の計算 AQ[I, 3] := Vp; if Vp >= 0 then Vp := (AQ[I + 1, 3] + Vp) * 0.5; // 平均速度 AQ[I, 4] := dls; // 区間距離 = 円部Δ距離 // 通過時間 Vp=0 は一個しか発生しないので平均値にゼロはなしMaxdoubleの値は基本的に無し if Vp <> 0 then AQ[I, 5] := dls / Vp // Δt else AQ[I, 5] := Maxdouble; if (MF = 0) and (I = PP) then AQ[I, 3] := -1; // 往復だったら最大頂点まで end; // 分割数が少ない時回転になってしまうのを防止 // 直線部分と直線から円部分ミラーコピー // X座標の符号を反転してコピー 角度は2Πから引きます(角度のミラー) J := 0; for I := P5 - 1 downto P3 do begin inc(J); for K := 0 to 5 do begin if K = 0 then AQ[J, K] := -AQ[I, K] else if k = 2 then AQ[J, K] := 2 * pi - AQ[I, K] // 角度のミラーコビー else AQ[J, K] := AQ[I, K]; end; end; // ∑t計算 Zmax AQ[I, 7] I := P5 - 1; Zmax := AQ[I, 5]; AQ[I, 6] := AQ[I, 5]; // Δt = AQ[I, 6] Tmax := AQ[I, 6]; Nn := I; Xi := 0; for I := P5 - 2 downto P0 do begin if AQ[I, 3] >= 0 then Nn := I else Break; if AQ[I, 5] > 0 then Zmax := Zmax + AQ[I, 5]; // 合計時間計算 if AQ[I, 5] > Tmax then begin Tmax := AQ[I, 5]; end; if AQ[I, 5] > Tim then begin // 指定時間より長い部分 inc(Xi); // 数カウント end; AQ[I, 6] := AQ[I + 1, 6] + AQ[I, 5]; // アニメ表示用合計時間計算 end; if Nn > 0 then Zmax := Zmax + Zmax; // I がゼロまで到達しない場合往復時間 if not CheckBox2.Checked then begin Memo1.Lines.Add('合計時間 ' + floatTostrF(Zmax, fffixed, 10,7)); Memo1.Lines.Add('最大区間時間 ' + floatTostrF(Tmax, fffixed, 10,7)); end else begin Memo1.Lines.Add('合計時間 ∞'); Memo1.Lines.Add('最大区間時間 ∞'); end; Memo1.Lines.Add('分割時間Over個数 ' + IntTostr(Xi)); if (Xi > 0) and (N > 50000) then Memo1.Lines.Add('水平速度が上死点停止速度に近すぎます。'); if Xi > 0 then MsF := True; // 定間隔時間時間順に並び変え STF := False; // 配列サイズ Nb 取得 Sort; // 配列確保 try setlength(At, Nb, 3); // アニメ作図用配列確保 setlength(CT, Nb); // タイマー区間補正用配列 except on EOutOfMemory do begin application.MessageBox('アニメ用配列メモリー不足です' + #13#10 + '分割数が多すぎます。' + #13#10 + '分割数を少なくして再実行してください。','注意',0); end; end; // 並び変えフラグ STF := True; // 時間順に並び変え Sort; // 配列の大きさ 往復の場合は、片道分なので二倍に // 回転する場合は、全部カウントしているのでそのまま if Mf = 0 then Xi := Nb + Nb - 1 else Xi := Nb; // 箱作図用配列確保 try setLength(At, Xi, 3); setlength(CT, Xi); except on EOutOfMemory do begin application.MessageBox('アニメ用配列メモリー不足です' + #13#10 + '分割数が多すぎます。' + #13#10 + '分割数を少なくして再実行してください。','注意',0); end; end; // 回転しない場合 反転位置までミラーコピー Na := xi - 1; yi := 0; if MF = 0 then begin xi = Na - yi; for I := 0 to Nb - 1 do begin At[Xi - yi, 0] := At[I, 0]; At[Xi - yi, 1] := At[I, 1]; At[Xi - yi, 2] := At[I, 2]; CT[Xi - Yi] := CT[I]; inc(yi); end; end; // 上死点検索用フラグ設定 TPF := False; if MF = 1 then TPF := True; // 上死点停止フラグセット // 箱作図 と上死点検索 for I := 0 to Na do begin if TPF then begin // 上死点検索 if At[I, 2] > pi then begin // 箱の角度がpiを超えたら上死点一つ越え TP := I - 1; // 一つ前の配列No break; end; end; BoxDraw(Hc, Wc, At[I, 0], At[I, 1], At[I, 2], Sc); end; if MsF and (not TPF) then Msout; // 区間時間が指定時間より長いのがあった場合メッセージ表示 if MF = 1 then begin BoxDraw(Hc, Wc, 0, Ro, Pi, Sc); Image1.Canvas.Font.Color := clBlue; Image1.Canvas.TextOut(Image1.Width - 120, 10, '*上死点で停止*'); end; end; var A0, A1, A2 : double; // x, y, 角度 TM : integer; // アニメ配列カウンター Erf : boolean; // 箱消去フラグ AnimF : Boolean; // アニメ停止フラグ TJ : integer; // アニメーションタイマー procedure TForm1.Timer1Timer(Sender: TObject); begin timer1.Enabled := False; dec(TJ); if TM = 1 then timer1.Interval := Timi; if Erf then BoxDraw(Hc, Wc, A0, A1, A2, Sc); // 箱消去 A0 := At[TM, 0]; A1 := At[TM, 1]; A2 := At[TM, 2]; if TJ < 0 then TJ := CT[TM]; BoxDraw(Hc, Wc, A0, A1, A2, Sc); // 箱作図 Erf := True; if TJ = 0 then inc(TM, 1); if (TM > Na) then begin // アニメ配列カウンターが配列の大きさを超えたら TM := 0; // アニメ配列カウンター先頭へ if MF = 0 then timer1.Interval := 500; if MF = 2 then timer1.Interval := 1000; end; if (not AnimF) or (TPF and (TP + 1 = TM)) then begin // タイマー停止処理 if TPF and (TP + 1 = TM) then begin BoxDraw(Hc, Wc, A0, A1, A2, Sc); // 箱消去 BoxDraw(Hc, Wc, 0, Ro, pi, Sc); // 上死点位置作図 end; AnimF := False; animbtn.Caption := 'アニメ開始'; AnimBtn.Enabled := True; CalcBtn.Enabled := True; ResetBtn.Enabled := True; exit; end; timer1.Enabled := True; end; // アニメーションの開始停止 procedure TForm1.AnimBtnClick(Sender: TObject); var ch : integer; begin if checkbox1.Checked then begin val(AnitimeEdit.Text, Anitime, ch); if ch <> 0 then begin application.MessageBox('アニメーション時間に間違いがあります。','注意',0); exit; end; if Anitime < 0.005 then begin application.MessageBox('アニメーション時間は0.005以上にして下さい。','注意',0); exit; end; if Anitime > 1 then begin application.MessageBox('アニメーション時間は1以下にして下さい。','注意',0); exit; end; end; if not AnimF then begin if not checkbox1.Checked then Timi := Round(Tim * 1000) else Timi := Round(Anitime * 1000); if Timi > 1000 then Timi := 1000; ImageClear; Image1.Canvas.Font.Color := clBlue; Image1.Canvas.TextOut(10, 5, 'タイマー時間' + intTostr(Timi) + 'ミリ秒'); application.ProcessMessages; AnimBtn.Caption := 'アニメ停止'; CalcBtn.Enabled := False; ResetBtn.Enabled := False; AnimF := True; CoasterDraw; if MsF and (not TPF) then Msout else Image1.Canvas.TextOut(20, Image1.Height - 20, '*周期時間は合計時間より長くなります*'); if MF = 1 then begin Image1.Canvas.Font.Color := clBlue; Image1.Canvas.TextOut(Image1.Width - 120, 10, '*上死点で停止*'); end; image1.Canvas.Pen.Mode := pmNotXor; Erf := False; TM := 0; TJ := 0; timer1.Interval := 1000; Timer1Timer(nil); // Timer1.Enabled := true; end else begin AnimBtn.Caption := '停止中'; AnimBtn.Enabled := False; AnimF := False; end; end; // 各種入力処理と計算 procedure TForm1.CalcBtnClick(Sender: TObject); var ch: integer; begin Timer1.Enabled := False; val(RcEdit.Text, Rc, ch); if ch <> 0 then begin application.MessageBox('コースター半径に間違いがあります。','注意',0); exit; end; if Rc < 1.5 then begin application.MessageBox('コースター半径が小さすぎます。','注意',0); exit; end; if Rc > 30 then begin application.MessageBox('コースター半径が大きすぎます。','注意',0); exit; end; val(RsEdit.Text, Rs, ch); if ch <> 0 then begin application.MessageBox('車輪半径に間違いがあります。','注意',0); exit; end; if Rs <= 0.001 then begin application.MessageBox('車輪半径が小さすぎます。','注意',0); exit; end; if Rs > 1 then begin application.MessageBox('車輪半径が大きすぎます。','注意',0); exit; end; val(IhEdit.Text, Ih, ch); if ch <> 0 then begin application.MessageBox('車輪四個の慣性モーメントに間違いがあります。','注意',0); exit; end; if Ih < 0 then begin application.MessageBox('車輪四個の慣性モーメントがマイナスです。','注意',0); exit; end; if Ih > 50000 then begin application.MessageBox('車輪四個の慣性モーメントが大きすぎます。','注意',0); exit; end; val(hEdit.Text, h, ch); if ch <> 0 then begin application.MessageBox('重心の高さに間違いがあります。','注意',0); exit; end; if h >= Rc - Rs then begin application.MessageBox('重心の高さが高すぎます。','注意',0); exit; end; if (Rc + h) < 0.5 then begin application.MessageBox('重心の高さが低すぎます。','注意',0); exit; end; val(IgEdit.Text, Ig, ch); if ch <> 0 then begin application.MessageBox('重心の慣性モーメントに間違いがあります。','注意',0); exit; end; if Ig < 0 then begin application.MessageBox('重心の慣性モーメントがゼロ以下です。','注意',0); exit; end; if Ig > 80000 then begin application.MessageBox('重心の慣性モーメントが大きすぎます。','注意',0); exit; end; val(TimEdit.Text, Tim, ch); if ch <> 0 then begin application.MessageBox('分割時間に間違いがあります。','注意',0); exit; end; if Tim < 0.005 then begin application.MessageBox('分割時間の値が小さすぎます。','注意',0); exit; end; val(NEdit.Text, N, ch); if ch <> 0 then begin application.MessageBox('計算分割数に間違いがあります。','注意',0); exit; end; if N < 2 then begin application.MessageBox('計算分割数に2以下は設定できません。','注意',0); exit; end; if N > 100000 then begin application.MessageBox('計算分割数が大きすぎます。' + #13#10 + '100000が上限です。','注意',0); exit; end; val(VEdit.Text, V, ch); if ch <> 0 then begin application.MessageBox('水平速度に間違いがあります。','注意',0); exit; end; if V <= 0.1 then begin application.MessageBox('水平速度が小さすぎます。','注意',0); exit; end; val(MEdit.Text, M, ch); if ch <> 0 then begin application.MessageBox('全質量に間違いがあります。','注意',0); exit; end; if M < 0 then begin application.MessageBox('全質量がゼロ以下です。','注意',0); exit; end; if M + ih <= 0 then begin application.MessageBox('全質量, 車輪の慣性モーメントがゼロです、' + #13#10 + 'どちらか一つは、ゼロより大きくして下さい。','注意',0); exit; end; MF := 0; AnimBtn.Enabled := False; CalcBtn.Enabled := False; ResetBtn.Enabled := False; ImageClear; CoasterDraw; // コースター円作図 angular_revision; // 箱位置計算と作図 AnimBtn.Enabled := True; animbtn.Caption := 'アニメ開始'; ResetBtn.Enabled := True; CalcBtn.Enabled := True; end; // 矢印の矢作図 procedure TForm1.drawya(x, y: integer; Q: double); const Yl = 15; Q1 = 15 / 180 * pi; var x0, x1, x2: integer; y0, y1, y2: integer; begin x0 := x + Wc; y0 := y + Hc; x1 := round(cos(Q + Q1) * Yl) + x + Wc; y1 := round(sin(Q + Q1) * Yl) + y + Hc; x2 := round(cos(Q - Q1) * Yl) + x + Wc; y2 := round(sin(Q - Q1) * Yl) + y + Hc; Image1.Canvas.MoveTo(x0, y0); Image1.Canvas.LineTo(x1, y1); Image1.Canvas.MoveTo(x0, y0); Image1.Canvas.LineTo(x2, y2); end; // 起動時画面設定 procedure TForm1.FirstData; begin Rc := 4; rs := 0.3; S := 0.5; // 固定値 h := 0.7; Gh := 0.5; // 固定値 Bh := 1; // 固定値 Bw := 2; // 固定値 M := 200; Ig := 50; Ih := 4; V := 8; N := 1000; Tim := 0.1; ImageClear; CoasterDraw; angular_revision; BoxDraw(Hc + 15, Wc, -2, 0, 0, Sc); Image1.Canvas.MoveTo(Wc - 150, Hc + 50); Image1.Canvas.LineTo(Wc - 20, Hc + 50); Image1.Canvas.MoveTo(Wc - 100, Hc + 15); Image1.Canvas.LineTo(Wc - 20, Hc + 15); Image1.Canvas.MoveTo(Wc - 30, Hc + 50); Image1.Canvas.LineTo(Wc - 30, Hc + 15); drawya(-30, 50, - pi / 2); // 下向き drawya(-30, 15, + pi / 2); // 上向き Image1.Canvas.MoveTo(Wc, Hc); Image1.Canvas.LineTo(Wc - round(Rc * SC * sin(pi / 4)), Hc - round(Rc * SC * sin(pi / 4))); drawya(- round(Rc * SC * sin(pi / 4)), - round(Rc * SC * sin(pi / 4)), pi * 1 / 4); // 右向き Image1.Canvas.Font.Color := clBlack; Image1.Canvas.Font.Size := 10; Image1.Canvas.Brush.Style := bsSolid; Image1.Canvas.TextOut(Wc - 100, Hc - 80, 'コースター半径'); Image1.Canvas.Brush.Style := bsClear; Image1.Canvas.TextOut(Wc - 100, Hc - 2, 'G'); Image1.Canvas.TextOut(Wc - 23, Hc + 25, '重心高さ'); Image1.Canvas.MoveTo(Wc - 75, Hc + 35); Image1.Canvas.LineTo(Wc - 50, Hc + 80); drawya(-69, 46, 60 / 180 * pi); // 左上向き Image1.Canvas.MoveTo(Wc - 75, Hc + 35); Image1.Canvas.LineTo(Wc - 75, Hc + 85); Image1.Canvas.MoveTo(Wc - 125, Hc + 35); Image1.Canvas.LineTo(Wc - 125, Hc + 85); Image1.Canvas.MoveTo(Wc - 75, Hc + 80); Image1.Canvas.LineTo(Wc - 125, Hc + 80); drawya( -75, 80, Pi); // 右向き drawya(-125, 80, 0); // 左向き Image1.Canvas.TextOut(Wc - 110, Hc + 62, '1m'); Image1.Canvas.MoveTo(Wc - 175, Hc + 204); Image1.Canvas.LineTo(Wc - 175, Hc + 254); Image1.Canvas.MoveTo(Wc - 225, Hc + 204); Image1.Canvas.LineTo(Wc - 225, Hc + 254); Image1.Canvas.MoveTo(Wc - 175, Hc + 249); Image1.Canvas.LineTo(Wc - 225, Hc + 249); drawya(-175, 249, Pi); // 右向き drawya(-225, 249, 0); // 左向き Image1.Canvas.TextOut(Wc - 210, Hc + 230, '1m'); Image1.Canvas.TextOut(Wc - 60, Hc + 80, '車輪半径'); Image1.Canvas.TextOut(20, 20, '参考図'); Image1.Canvas.TextOut(200, Image1.Height - 40, '計算分割数は、1mの分割数です。'); end; // デフォルト値設定 procedure TForm1.ResetBtnClick(Sender: TObject); begin ResetBtn.Enabled := False; CalcBtn.Enabled := False; AnimBtn.Enabled := False; Checkbox1.Checked := False; Checkbox2.Checked := False; RcEdit.Text := '4'; RsEdit.Text := '0.3'; IhEdit.Text := '4'; hEdit.Text := '0.7'; IgEdit.Text := '50'; TimEdit.Text := '0.1'; NEdit.Text := '10000'; VEdit.Text := '10.2646'; MEdit.Text := '200'; FirstData; application.ProcessMessages; Memo1.Clear; Memo1.Lines.Add('現在の表示は参考図です。'); Memo1.Lines.Add('計算値ではありません。'); ResetBtn.Enabled := True; CalcBtn.Enabled := True; end; // 初期設定 procedure TForm1.FormCreate(Sender: TObject); begin ResetBtn.Enabled := False; AnimBtn.Enabled := False; timer1.Enabled := False; timer1.Interval := 50; Image1.Height := 500; Image1.Width := 500; FirstData; Memo1.Clear; Memo1.Lines.Add('現在の表示は参考図です。'); Memo1.Lines.Add('計算値ではありません。'); MF := 0; end; end.