マウスで書いた線をOctave用のデータにできます。IE8は未対応です。
ピョンピョンはねないで着地したい数値解析
GNU Octaveを使って数値解析をすることにした。
6つのグラフはばね定数kを変えてプロットしている。それぞれのグラフで、赤、緑、青、マゼンタ、シアンの順番にダンパー抵抗を1から200まで指数的に増やしている。横軸は時間で縦軸は車体の高さだ。初期位置は50cmの高さで、この時に10m/sで下に向かって落ちている。5m程度から落ちたケースとしている。これを見る限りだと調べている範囲では深く沈みこまないかどうかはほぼ、ダンパー抵抗で決まるようだ。5mから落ちているので若干沈み込むぐらいがいいだろう。k=50で、hは青の真ん中、h=14を選ぶことにした。
次に初速を0から20m/sまで変えてテストしてみる。5m/sを超えるのは厳しそうだ。
ここで、当初はk=100くらいを想定していたのだが、大分kが小さくなった。kが小さくなったことで、定常状態の位置がばねの自然長より短くなる。フックの法則より、mg/k ~= 0.20となり、ばねの自然長を余計に伸ばしておく必要がある。もともと自然長は1.2mだったため1.4mに変更。
suspension_test.m
function suspension_test() % Simulate spring and damper. spring_value = logspace(log10(10),log10(200),6); damper_value = logspace(log10(1),log10(200),15); clf; for i = 1:length(spring_value) subplot(2,3,i); hold on; axis([ 0, 5.0, -1, 1 ]); xlabel('Time'); ylabel('Position'); title(sprintf('k=%dm, h=1m,...,200m', spring_value(i))); for j = 1:length(damper_value) [t, x] = spring(spring_value(i), damper_value(j)); plot(t, x, num2str(floor((j+2)/3))); end hold off; end end function [t, x] = spring(spring_value, damper_value) m = 1.0; t = linspace(0,5,300); x0 = [0.5; -10]; A = [ 0 1 ; -spring_value / m -damper_value / m ]; [temp, istate, msg] = lsode(@(x)A*x, x0, t); x = temp(:,1)'; end
suspension_test2.m
function suspension_test2() % Simulate spring and damper. spring_value = logspace(log10(10),log10(200),6); damper_value = 14; initial_speed = linspace(0,20,15); clf; for i = 1:length(spring_value) subplot(2,3,i); hold on; axis([ 0, 5.0, -1, 1 ]); xlabel('Time'); ylabel('Position'); title(sprintf('k=%dm, v_0=0,...,20', spring_value(i))); for j = 1:length(initial_speed) [t, x] = spring(spring_value(i), damper_value, initial_speed(j)); plot(t, x, num2str(floor((j+2)/3))); end hold off; end end function [t, x] = spring(spring_value, damper_value, initial_speed) m = 1.0; t = linspace(0,5,300); x0 = [0.5; -initial_speed]; A = [ 0 1 ; -spring_value / m -damper_value / m ]; [temp, istate, msg] = lsode(@(x)A*x, x0, t); x = temp(:,1)'; end
ピョンピョンはねないで着地したいが失敗
レイキャストを使った2輪車は、車体から地面への距離を測って、距離と速度に応じて反発力をPythonで計算する。ソースコードはこんな感じだ。
import bge import math import mathutils suspensionLength = 1.2 downVect = mathutils.Vector((0, 0, -1)) springValue = 90.0 damperValue = 5.0 def update(cont): own = cont.owner # Check if the foot touch on the ground. obj, point, normal = own.rayCast(own.worldPosition + downVect, None, suspensionLength * 1.2, "", 0, 0, 0) if obj: velocityVect = own.getLinearVelocity() dist = own.getDistanceTo(point) position = dist - suspensionLength force = - position * springValue - velocityVect.z * damperValue own.applyForce([0, 0, force])
springValue
とdamperValue
はそれぞればねとダンパーに関するパラメーターだが、この値によって、車体がピョンピョン跳ねるか滑らかに安定するかが変わってくる。
上記は大まかには、重力とばねの力が安定する場所(地面ではない)を原点、springValue
を、damperValue
をとすれば、以下のような微分方程式でオブジェクトを動かしていることになる。
2階線形微分方程式 ( 同次 )の解法より、特性方程式の解によっての解が変わって車体の動き方が変わる。特性方程式は
で
まず、の場合は、判定式の解をと置くと、
と書けるが、が正であることから、は共に負で、ピョンピョンはねることなく次第に止まっていく。
1mの高さから落ちたとして3秒後には振幅が3cm程度に軽減したいので、を最初のケースと同じ程度に設定しておけばいいだろう。2.3にしておけばいいと思ったが、勘違いをしていることに気付いた。微分方程式では、車体が地面からどんなに離れていても力が働いていることになるが、現実にはそうではない。車輪が接地した瞬間にすでに速度がある程度ある状況から始めなければならない。
ちょっと、代数的に解析するのがめんどくさそうなので、いったん数値的に解析してみようと思う。
ところで、微分方程式は
と状態方程式であらわされる。はじめての現代制御理論 講義09 状態フィードバックと極配置によるとシステムが安定するにはAの固有値の実数部が負になればよい。
これは微分方程式の特性方程式と同じになる。この場合常に解の実数部は負になるので常に安定する。
並進運動する剛体の角運動方程式と非慣性系
さて、角運動方程式を学んだところで倒立振子(とうりつしんし)の制御の説明を探してみて、気づいたのは多くのサイトで角運動の方程式を建てる場合に暗黙の内に非慣性系での運動方程式を立てていることだ。なんとなく、そういうものかとスルーしたくなるが、ちょっと調べてみた。
倒立振子に関して参考にしたのは倒立振子の制御で、下の図のような倒立振子を安定させる問題を扱っている。
並進運動の方程式は、台車と棒を別の剛体として立てているのだが、力のモーメントについて勉強のように重心の位置の運動方程式は力のモーメントのように作用点によって方程式が変わることはないから、台車の支点の水平位置をとすると棒に関する縦方向の方程式は
となる。一般には角運動量は中心をどこに取るかによって変わってくるので中心が動いてしまうと方程式が立てられないはず。まずは、固定された座標系の原点をもとに角運動量を求めてみる。Cは力のモーメントとしてどこに作用するかわからないのでとりあえずは0にしておく。角運動量を棒の微小な長さに対して積分して
ここで
としてを代入。
棒の慣性モーメントJはだから、これは倒立振子の制御の
にを代入したものになる。
倒立振子の制御の方程式は重心を中心とする角運動量に関するものだが、角運動量の中心が加速度運動をする場合は、非慣性系の運動方程式(慣性系 ( ガリレイ変換、遠心力、コリオリの力、フーコーの振り子 )参照)が必要になる。一般に、質点がの位置にあり、とする。それぞれの質点に力がかかっている。質点の質量の合計を、重心の位置をとする。
非慣性系では原点の加速度に応じて、「慣性力」が働くように見える。この慣性力による力のモーメントはで、重心に原点の加速度とは逆方向に力が働いているように見える。角運動の中心を重心に取ると、になるので、となるので、たまたま、原点の加速度は角運動量には関係なくなる。
に関してだが、支点を中心とする角運動量を考えてみる。この場合、支点の抵抗による力のモーメントがになるのは、その他の場所を中心の角運動量を考えるより直観的にわかりやすい。支点を中心とする場合、作用点が回転の中心にあるのでもも角運動量に影響しなくなり、慣性力と重力だけが力のモーメントに寄与する。
結局、どこを角運動量の中心にとっても慣性力を適切にとれば、結果は同じになる。
行列の積の微分
基礎知識なのかもしれないけど知らなかったのでメモ。
とした場合、
のようにスカラー積の微分のように計算できる。
ついでに回転行列の微分。
とすると、
極座標表現でも同様で、
角運動量の疑問
角運動量について、説明サイトを見ていて理解に困難だった点は、角運動量について語る際に角運動量を自転として見る場合と公転として見る場合があることだ。Wikipediaの角運動量保存の法則を見ると、フィギュアスケートの高速スピンや「こま」の回転の話、後でケプラーの法則の話がでてくるが、前者は自転の話で、後者は公転の話になる。角運動量を理解するために運動量と角運動量の関係に関して検証してみた。
Wikipediaでは角運動量保存の法則の証明をしている。証明というからには、この法則が別の法則から導き出せる副次的なものということで、この別の法則は運動量保存の法則のことだ。質点に関して角運動量、力のモーメントをとした場合、を1階微分して
から力のモーメントにより角運動量が変化することがわかる。
ところで、質点をたくさん集めてみたときに、その重心は運動の法則に従っているように見える。n個の質量の質点があり、運動量の総和をとすると、
が外力、から内力を受け取るとすると、
ここで、作用、反作用の法則からだから、。よって、
となり、ここで、重心をとすると、
より、重心は質点が受け取ったすべての外力の総和により、運動の法則に従っているようにふるまう。小さいものをまとめても同じ法則に従うのだ。
一方、角運動量の総和をとすると、
となる。は外力による力のモーメントの総和になるが、とならなければ、質点を合成した場合に角運動量の保存則が成り立たない。質点を合成した場合の角運動量の保存則は、Wikipediaの作用、反作用の法則だけからは導けないらしい。運動量保存則だけでは不完全?でも同じ疑問を持った人がいるようだ。
作用、反作用の法則より、だから、
角運動量の保存則を成り立たせるための単純な法則を足すとすれば、とはに平行とすればよい。これは重力でも成り立つので納得できる。
式を見る限りでは角運動量に、自転、公転の区別はないようだ。
外積の微分
力のモーメントについて勉強(2)
力のモーメントについて勉強の棒による作用反作用の力と、その時の角加速度を求めてみる。本当は3次元で考えなければいけないと思うのだが、やり方がわからないので2次元で考える。まず、をで次のように表す。
1階微分すると
2階微分すると
となる。
より、
これをについて解くと、
これを使って、を表すと、
ここで棒は引っ張るか押すかしかできない(多分そうだと思う)ため、はに平行になる。そのため、
両辺にをかけて、
左辺は力のモーメントだから、とおき、また、より、
よって、力のモーメントが角加速度に寄与していることがわかる。
力のモーメントについて勉強
ネットの情報を漁っていると一輪車を倒れないようにすることは倒立振り子の安定化問題に似ていることが分かってきた。倒立振り子の安定化問題は制御工学の定番の課題らしく情報量は多い。運動方程式からモデルを作り安定するフィードバックを決定するのだが、高校物理の知識のまま安定化してしまった脳みそには回転を考慮した運動方程式が理解できないので剛体の力学を参考に勉強してみた。
回転を考えない場合、剛体にかかる力を求めて運動方程式に当てはめればいい。この場合、剛体のどの部分に力をかけるのかは考えない。一方、回転の場合は剛体にかかるトルクを求めて運動方程式に当てはめる。トルクは重心から力の作用点へのベクトルと力のベクトルの外積で定義される。外積なので重心方向に直角な分を取り出したものと考えることができる。ボールをボールの中心方向に押せば、回転せずに移動するが、表面と平行に押せば回転することから直観的に理解できる。
そして、剛体の運動は、高校物理でおなじみの並進運動の運動方程式と回転運動の運動方程式で2つの方程式で表すことができる。
ここでつまづいたのは、重心方向を向いている力は回転運動に寄与しないのであれば、重心方向を向いていない力は並進運動に寄与しないのではないかという勘違い。高校では力の合成と分解を勉強するが力の分解に似たことが並進運動と回転運動にも起こるのではないかと思ったが、そうでないことを実際検証してみた。下のように2つの物体が質量が無視できる変形しない棒でつながっていて、2つの物体に作用、反作用に法則により力がかかっているとする。
2つの物体の重心を とすると
さらに、外部から片方の物体に力をかける。は時間に依存するとして、ニュートン記法を使うと、
よって、
となりは相殺されて、2つの物体を合成した剛体はあたかも、質量の物体に力をかけたかのように並進運動する。これからは重心を向いているかどうかに関係なく、剛体の並進運動に寄与することがわかる。
ところで、2つの物体の運動エネルギーの和は、
2つの物体を合成した剛体の運動エネルギーは
その差は
なので常に正になる。つまり、剛体を動かすのに使われたエネルギーは見かけの運動エネルギーとは別に何らかのエネルギーとして使われていることがわかる。
剛体が回転運動をしていないときにはになるので、になる。
棒の長さがで剛体が角速度回転している場合、それぞれの物体の重心からの距離は、
として、
でお互いに反対方向を向いてるので、
よって、
となる、一方、剛体の重心を中心とした場合の慣性モーメントは
となるので、
となり、外部からの力で与えられたエネルギーとの差は剛体の見かけの運動エネルギーの差は回転エネルギーの定義と一致する。
試しにJavaScriptでシミュレーションをしてみる。剛体にかかる力を計算するのが面倒なので、赤と青の物体はばねでつながっている。緑の物体は赤と青を合成した剛体になっている。赤の物体と緑の物体に同じ力をかけて、その軌道を見ると、赤と青の物体の重心は緑の物体に見事一致している。
moment.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Moment Test</title> <script src="moment.js"></script> </head> <body> <h1>Moment Test</h1> <svg id="space" width="800" height="600" style="border: 5px solid blue; overflow: hidden;"> <circle id="x1" cx="10" cy="10" r="2" fill="red"/> <circle id="x2" cx="10" cy="10" r="4" fill="blue"/> <circle id="x3" cx="10" cy="10" r="3" fill="green"/> </svg> </body> </html>
moment.js
(function () { "use strict"; window.addEventListener("load", function () { var frameIndex = 0; var startTime = new Date().getTime(); var deltaTime = 1 / 24; var endFrameIndex = 10 / deltaTime; var x1Elem = document.getElementById("x1"); var x2Elem = document.getElementById("x2"); var x3Elem = document.getElementById("x3"); var m1 = 1.0; var m2 = 2.0; var m3 = m1 + m2; var x1 = [1, 50]; var x2 = [1, 58]; var x3 = [(m1 * x1[0] + m2 * x2[0]) / m3, (m1 * x1[1] + m2 * x2[1]) / m3]; var v1 = [0, -20]; var v2 = [10, -10]; var v3 = [(m1 * v1[0] + m2 * v2[0]) / m3, (m1 * v1[1] + m2 * v2[1]) / m3]; var G = 9.8; var restDist = 6; var springValue = 30.0; function next() { var startTime = new Date().getTime(); x1[0] += v1[0] * deltaTime; x1[1] += v1[1] * deltaTime; x2[0] += v2[0] * deltaTime; x2[1] += v2[1] * deltaTime; x3[0] += v3[0] * deltaTime; x3[1] += v3[1] * deltaTime; var dx = x1[0] - x2[0]; var dy = x1[1] - x2[1]; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var force = (dist - restDist) * springValue; v1[0] -= dx * force * deltaTime / (m1 * dist); v1[1] -= dy * force * deltaTime / (m1 * dist); v2[0] += dx * force * deltaTime / (m2 * dist); v2[1] += dy * force * deltaTime / (m2 * dist); } v2[1] += G * deltaTime / m2; v3[1] += G * deltaTime / m3; } function render() { x1Elem.setAttribute("cx", x1[0] * 10); x1Elem.setAttribute("cy", x1[1] * 10); x2Elem.setAttribute("cx", x2[0] * 10); x2Elem.setAttribute("cy", x2[1] * 10); x3Elem.setAttribute("cx", x3[0] * 10); x3Elem.setAttribute("cy", x3[1] * 10); } var timer = window.setInterval(function () { try { var nextFrameIndex = (new Date().getTime() - startTime) / (1000 * deltaTime); while (frameIndex <= nextFrameIndex) { next(); frameIndex++; } if (frameIndex > endFrameIndex) { window.clearInterval(timer); timer = null; } render(); } catch (e) { window.clearInterval(timer); timer = null; } }, deltaTime * 1000 * 0.9); }); })();
動作とアニメーションのスクリプトを分離
同じ動きをするキャラクターで動作のためのスクリプトを共有したいが見た目が違う場合を考えて、
2輪車用のスクリプトはアニメーションのスクリプトを分離できるようにして、スケーターにも使えるようにしている。
具体的には、動作のためのスクリプトの
self.leanAngle = math.asin(-normalVect.dot(worldLeftVect))
の部分でGameObjectに現在の車体の傾きを設定しておく。アニメーションのためのスクリプトではこの値を見てアニメーションを実行する。
動作のスクリプトはGirl Empty Objectに設定してあって、アニメーションのスクリプトはRigGirlに設定してある。GirlSkate_Leanは傾きごとの姿勢のためのアニメーションでフレーム1では右に傾き、61で正立、121で左に傾いている。アニメーションのactuatorのframeプロパティを設定して、傾きに従って姿勢が変わるようになっている。
import math def update(cont): owner = cont.owner parent = owner.parent acc = cont.actuators["GirlSkate_Lean"] cont.activate(acc) frame = parent.leanAngle / math.pi * 180 + 61 acc.frame = min(max(1, frame), 121)