動画
クソフマス2020提出曲②火花散らして フレン・シーフォのテーマ
— kai_yuki (@kai_yuki) 2020年12月19日
右腕は2重振り子の運動方程式をOpenITG上で数値計算しています。公開済みのものをそのまま出しても味気ないので盛り上がりに合わせて首が伸びる要素を追加しました #クソフマス2020 pic.twitter.com/aXHSMGcSYP
ダウンロード
発想
良く知られたフレンシーフォの絵ですが、力を抜いたら自然に下方向にだらんとぶら下がるのではないかと考えました。
その際に、慣性の力で少し逆方向にオーバーランしそうだなと思いました。
そうすると、フレンシーフォの肘から先は肘を固定点とした振り子状の動きをすると考えた方が自然ではないかと考えました。
更に考えると、肘までが固定でその先が振り子状になっているというのも変で、肩から肘までも同様に振り子状になっていると考えた方がより自然ではないかということに気づきました。
そこで、フレンシーフォの右腕を2重振り子であるとモデル化し、どのような動きをするかをStepManiaを用いて確かめたいと思いました。
方程式の導出と離散化
wikipediaにちょうどいい方程式と図があったのでそのまま使います。
二重振り子 - Wikipedia
1行目をθ1の2回微分、2行目をθ2の2回微分に関してまとめ直したのが以下の式です。
powerA = impactA - (0.5 * powerB * math.cos(math.rad(degreeA - degreeB)) + 0.5 * 2 * verocityB * math.sin(math.rad(degreeA - degreeB)) + gravity * math.sin(math.rad(degreeA)) + resist * verocityA); powerB = - (powerA * math.cos(math.rad(degreeA - degreeB)) - 2 * verocityA * math.sin(math.rad(degreeA - degreeB)) + gravity * math.sin(math.rad(degreeB)) + resist * verocityB);
ただし、
θ1(角度):degreeA θ1の微分(角速度):verocityA θ1の2回微分(角運動量):powerA
としています。θ2は同様にdegreeBとします。
また、簡単のためにl1=l2=1、m1=m2=1とします。
impactAは外力の項で、ここがコンボに依存する部分となります。
resist*verocityAは速度に依存する抵抗力(空気抵抗のイメージ)です。
powerBの方程式で2 * verocityAとなっている箇所がありますがここは本来はverocityA* verocityAです。が、正規化をサボったせいでどうしてもうまく動かなかったので2 * verocityAで近似しています。
また、微分の代わりに前進差分を取ることにして以下のように計算することにします。
degreeA(t+Δt)=degreeA(t)+Δt*verocityA(t) verocityA(t+Δt)=verocityA(t)+Δt*powerA(t)
数値計算的にもっと精度のよい方法はあるんですが(ルンゲクッタ法とか)、今回はそれらしい計算ができればよいので1次の項まででよしとします。
一定速度以上の場合は角運動量の逆方向に摩擦力(friction)が働くこととします。
以上をまとめたのが以下です。
currentcombo = STATSMAN:GetCurStageStats():GetPlayerStageStats(0):GetCurrentCombo(); if (currentcombo >= combo + 3) then impactA = 3000; impactB = 1000; combo = currentcombo; elseif (combo > currentcombo) then impactA = 0; impactB = 0; combo = currentcombo; else impactA = 0; impactB = 0; end --外力、重力、空気抵抗 powerA = impactA - (0.5 * powerB * math.cos(math.rad(degreeA - degreeB)) + 0.5 * 2 * verocityB * math.sin(math.rad(degreeA - degreeB)) + gravity * math.sin(math.rad(degreeA)) + resist * verocityA); --摩擦力 速度が小さいときは摩擦を考えない if (verocityA > 1.0) then powerA = powerA - friction; elseif (-1.0 > verocityA) then powerA = powerA + friction; end degreeA = degreeA + 0.02 * verocityA; verocityA = verocityA + 0.02 * powerA;
これを第4回のlua講座で解説したupdatecommandを用いた無限ループと組み合わせます。
実は前回の記事では解説していなかったのですが、無限ループをどの頻度で回すかはself:sleep(秒)の行に依存します。
(正確には<<命令の記述>>の実行時間も含まれますが十分小さいとします)
そうすると、上記の前進差分のΔtとself:sleepの時間を合わせる
(今回は両方とも0.02)
ことにより、実経過時間に応じた数値計算結果をOpenITG上で表示することができます。
前回の記事でインタラクティブな処理を実現可能と書きましたが、運動方程式の数値計算をOpenITG上で行うことにより、
実時間スケールの運動の計算と表示を簡単に行うことができる、というのが非常に強力だと思っています。
余談ですが、2重振り子の運動方程式はパラメータを適切に決めるとカオス的なふるまいをすることが知られています。
今回のモデルでは摩擦力や空気抵抗を考慮しているため保存系ではないこと、
外力をコンボに応じて与えていることから、カオス的なふるまいと関係なくランダムな動きになるのは当然なのですが、
それらしい動きを運動方程式の数値計算だけから表現することができたので満足です。
参考URL
Lua API for OpenITG / NotITG(英語)
https://sm.heysora.net/doc/
ITGのLuaを書くのにまず参照するページです。OpenITGで使用できる各種命令が記載されています。
Lua for StepMania 5
https://quietly-turning.github.io/Lua-For-SM5/
StepMania5版のlua解説サイト(英語)です。使用できる関数や3.9系との違いなど、SM5系のluaを作成するのに必要な情報が詰まっています。
paraphrohn’s diary
https://paraphrohn.hateblo.jp/
日本語でLuaの解説が書かれた貴重なページです。Luaの基本から譜面のワープ、ハイスピ変更ギミックなど様々な実装についての解説があります。