
図1:差動二輪ロボット。動かせるのは左右2輪の速さだけで、ハンドルは無い。左右を同じにすれば直進、右を速くすれば左へ曲がり、逆向きに回せばその場で向きだけ変わる
左右の車輪が別々の速さで回るロボットがあります。この2つの速さだけから、いま車体がどこにいて、どちらを向いているかを求めたい。この計算を順運動学(forward kinematics:入力側=関節や車輪の動きから、結果側=車体や手先の位置を求める向きの計算)と呼びます。
この記事は式と図で、次の3段を順に組み立てます。
| 段 | 入力 | 出力 | 使う式 |
|---|---|---|---|
| ① | 左右の車輪速 $v_l,\ v_r$ とトレッド $L$ | 前進速度 $v$、旋回角速度 $\omega$ | 2本の一次式 |
| ② | $v,\ \omega$ | 姿勢の変化率 $\dot x,\ \dot y,\ \dot\theta$ | 微分方程式 |
| ③ | ②を時間で積分 | 姿勢 $(x,\ y,\ \theta)$ | 円弧の閉じた式 |
$(x,\ y,\ \theta)$ の3つ組を姿勢(pose:位置と向きをまとめた呼び方)と言います。①②③が組み上がると、「左 0.6 m/s・右 1.0 m/s で 4 秒」と与えるだけで、4 秒後の姿勢が $(0.284\ \mathrm{m},\ 2.183\ \mathrm{m},\ 165.2^\circ)$ と一発で出ます。
最後の2節は、実装したときに必ず引っかかる刻み幅と曲がる角度の話です。③の式を近似で済ませると 3.2 m で 79.3 mm ずれること、そのずれが旋回の速さでは決まらないことを、実測の表で示します。
動作環境
Python 3.10.9
numpy 1.26.4
pybullet 3.2.7 ※「式が成り立つ前提」の節だけで使います
記号を決める
左右2輪で走るロボットを差動二輪(differential drive:左右の車輪の速度差で向きを変える方式。掃除ロボットや配膳ロボットの多くがこれ)と呼びます。ハンドルはありません。右を速く回せば左へ曲がる、それだけの仕組みです。

図2:差動二輪の記号。位置と向きは車体中心の1点で代表させる
| 記号 | 意味 | 単位 |
|---|---|---|
| $v_l,\ v_r$ | 左右の車輪の周速(車輪の外周が地面をこする速さ) | m/s |
| $L$ | トレッド(左右車輪の間隔) | m |
| $v$ | 車体の前進速度 | m/s |
| $\omega$ | 旋回角速度(オメガ:1秒あたり何ラジアン向きが変わるか。ラジアンは角度の単位で、$\pi$ ラジアン=180度) | rad/s |
| $x,\ y,\ \theta$ | 車体中心の位置と向き(姿勢) | m, m, rad |
前提が1つあります。車輪が横に滑らないことです。車輪は自分の向いている方向にしか進めない、という拘束で、これを非ホロノミック拘束(nonholonomic constraint:速度に掛かる、位置の式には書き直せない制約)と呼びます。この前提が崩れる例は後の節で実際に見ます。
① 車輪の速さから、前進速度と旋回角速度を出す
左右の車輪は車軸でつながっていて、別々の方向へは行けません。ということは、ある瞬間には両輪が同じ1点のまわりを、同じ角速度で回っていることになります。この点を瞬間回転中心(ICR=Instantaneous Center of Rotation:その瞬間だけ、車体が中心にして回っているとみなせる点)と呼びます。

図3:ICR と車輪速の関係。車体中心までの距離が旋回半径 $R$、左車輪までが $R - L/2$、右車輪までが $R + L/2$ になる
半径 $r$ の円を角速度 $\omega$ で回る点の速さは $r\omega$ です。図3の3点にそれぞれ当てはめます。
$$v_l = \omega\left(R - \frac{L}{2}\right), \qquad v_r = \omega\left(R + \frac{L}{2}\right), \qquad v = \omega R$$
上の2式を辺々引くと $R$ が消えて $\omega$ が、辺々足すと $L$ の項が消えて $v$ が出ます。
$$v_r - v_l = \omega L ;;\Longrightarrow;; \omega = \frac{v_r - v_l}{L}$$
$$v_r + v_l = 2\omega R = 2v ;;\Longrightarrow;; v = \frac{v_r + v_l}{2}$$
平均が前進、差が旋回です。 左右が同じ速さなら差がゼロになって、まっすぐ進みます。
import numpy as np
TREAD = 0.555 # 左右車輪の間隔 [m](後の節で使う Husky というロボットの実寸)
def body_twist(v_l: float, v_r: float, tread: float = TREAD) -> tuple[float, float]:
"""左右車輪の周速 [m/s] から、前進速度 v [m/s] と旋回角速度 omega [rad/s] を求める。"""
return (v_r + v_l) / 2.0, (v_r - v_l) / tread
この記事を通して使う例(左 0.6 m/s、右 1.0 m/s、$L = 0.555$ m)を入れると、$v = 0.8$ m/s、$\omega = 0.4/0.555 = 0.7207$ rad/s です。旋回半径は $R = v/\omega = 1.110$ m になります。
💡 $R$ の式に $v$ と $\omega$ の定義を代入すると $R = \dfrac{L}{2}\cdot\dfrac{v_r + v_l}{v_r - v_l}$ です。左右差が小さいほど $R$ は大きく(=ゆるく曲がる)、$v_r = v_l$ で無限大(=直線)になります。
同じ「順運動学」という言葉をロボットアームでも使います。
アームは関節角を式に入れれば手先の位置がその場で出ますが、車輪の場合は「回した量の積み重ね」が入力になるので、②③の積分(少しずつ足し合わせて累積を出す計算)が要ります。この違いが、この記事の後半のテーマです。
② 姿勢の変化率を書き下す
$v$ と $\omega$ は車体から見た速度です。地面に固定した座標での位置の変化に直すには、向き $\theta$ で分解します。

図4:前進速度 $v$ を $x$ 方向と $y$ 方向に分解する。横方向(車体の真横)の成分は、非ホロノミック拘束によって常にゼロ
$$\dot{x} = v\cos\theta, \qquad \dot{y} = v\sin\theta, \qquad \dot{\theta} = \omega$$
$\dot{x}$(エックスドット)は $x$ の時間微分、つまり「1秒あたり $x$ がどれだけ増えるか」です。この3本が差動二輪の運動方程式そのもので、順運動学とは、この3本を時間で積分することです。
$$x(t) = x_0 + \int_0^t v\cos\theta(\tau) d\tau, \qquad y(t) = y_0 + \int_0^t v\sin\theta(\tau) d\tau, \qquad \theta(t) = \theta_0 + \int_0^t \omega d\tau$$
厄介なのは $x$ と $y$ の式です。積分の中に $\theta(\tau)$ が入っていて、その $\theta$ 自身も時間とともに変わります。位置を出すには、まず向きを出さないといけません。 ここが「関節角を入れれば終わり」のアームとの分かれ目です。
③ 積分する:入力が一定なら、軌跡は円弧になる
$v$ と $\omega$ が一定の区間だけを考えます。すると向きは時間の一次式です。
$$\theta(t) = \theta_0 + \omega t$$
これを $x$ の式に代入すると、中身が $\cos(\theta_0 + \omega\tau)$ になって、そのまま積分できます。
$$x(t) = x_0 + \int_0^t v\cos(\theta_0 + \omega\tau) d\tau = x_0 + \frac{v}{\omega}\Bigl[\sin(\theta_0 + \omega t) - \sin\theta_0\Bigr]$$
$y$ も同じ手順で、$\sin$ の積分から $-\cos$ が出ます。$R = v/\omega$ を使って並べると、順運動学の答えはこの3行です。
$$\begin{aligned}
x(t) = x_0 + R\bigl[\sin(\theta_0 + \omega t) - \sin\theta_0\bigr], y(t) = y_0 - R\bigl[\cos(\theta_0 + \omega t) - \cos\theta_0\bigr], \theta(t) = \theta_0 + \omega t
\end{aligned}$$
このように答えが直接求まる形を閉じた式(closed-form:近似も繰り返し計算も要らず、代入だけで答えが出る式)と呼びます。式が言っているのは「半径 $R$ の円の上を $\omega t$ だけ回った先」で、向きが変わったぶんの $\sin$・$\cos$ の差が、そのまま移動量になります。中心 $(x_0 - R\sin\theta_0,\ y_0 + R\cos\theta_0)$ の円周上を動くので、一定入力のあいだ車体が描くのは必ず円弧です。
def step_exact(pose: np.ndarray, v: float, omega: float, dt: float) -> np.ndarray:
"""1ステップぶん、円弧として厳密に進める。"""
x, y, th = pose
if abs(omega) < 1e-12: # まっすぐのときは 0 割りになるので直線へ
return np.array([x + v * np.cos(th) * dt, y + v * np.sin(th) * dt, th])
radius = v / omega
th_new = th + omega * dt
return np.array([x + radius * (np.sin(th_new) - np.sin(th)),
y - radius * (np.cos(th_new) - np.cos(th)),
th_new])
pose は [x, y, 向き] の3つ組です。
⚠️
omegaがゼロに近いときの分岐は省略できません。$R = v/\omega$ がゼロ割りになります。左右の車輪速がぴったり同じになるのは、まっすぐ走らせるときに普通に起きる状況です。$\omega \to 0$ の極限では $\sin(\theta_0 + \omega t) - \sin\theta_0 \approx \omega t\cos\theta_0$ なので、$R$ の $\omega$ と約分されて $x = x_0 + vt\cos\theta_0$、つまり直線の式に落ちます。分岐の中身が直線なのはそのためです。
動かしてみる
①と③をつないで、4 秒ぶんの姿勢を出します。上の2つのコードに続けて貼れば、そのまま動きます。
def integrate(step_fn, v: float, omega: float, dt: float, n_steps: int) -> np.ndarray:
"""原点・向き 0 から n_steps ぶん進めた最終姿勢 [x, y, 向き] を返す。"""
pose = np.zeros(3)
for _ in range(n_steps):
pose = step_fn(pose, v, omega, dt)
return pose
v, omega = body_twist(0.6, 1.0) # ① 左 0.6 m/s、右 1.0 m/s
print(f"v = {v:.4f} m/s, omega = {omega:.4f} rad/s, 旋回半径 R = {v / omega:.4f} m")
# ③ 4 秒ぶんを 1 ステップで解く(閉じた式なので、これで厳密)
one_shot = step_exact(np.zeros(3), v, omega, 4.0)
# ③ 同じ 4 秒を 0.1 秒 × 40 歩に分けて解く
stepwise = integrate(step_exact, v, omega, 0.1, 40)
print(f"1 歩で解く x={one_shot[0]:.6f} y={one_shot[1]:.6f} θ={np.degrees(one_shot[2]):.2f}°")
print(f"40 歩で解く x={stepwise[0]:.6f} y={stepwise[1]:.6f} θ={np.degrees(stepwise[2]):.2f}°")
print(f"2つの差 {np.linalg.norm(stepwise[:2] - one_shot[:2]):.2e} m")
v = 0.8000 m/s, omega = 0.7207 rad/s, 旋回半径 R = 1.1100 m
1 歩で解く x=0.283975 y=2.183060 θ=165.18°
40 歩で解く x=0.283975 y=2.183060 θ=165.18°
2つの差 1.83e-15 m

図5:0.5 秒ごとの姿勢。矢印が車体の向き $\theta$ で、位置は半径 1.110 m の円弧の上を進む
前進速度 0.8 m/s で 4 秒なので、走った距離は 3.2 m です。$\theta$ は $\omega t = 0.7207 \times 4 = 2.8829$ ラジアン=165.18 度だけ変わりました。
注目してほしいのは3行目です。 4 秒をまとめて解いても、0.1 秒に刻んで40回解いても、答えは 1.83e-15 m しか違いません(倍精度浮動小数点の丸め誤差の水準で、事実上ゼロ)。閉じた式は近似ではないので、どう刻んでも結果が変わらないのです。
ここまでが順運動学の本体です。残りの2節は、この式を実際のロボットで使うときに引っかかる点を潰します。
式が成り立つ前提:車輪が横に滑ると合わない
最初に置いた「車輪が横に滑らない」という前提が崩れる例を、シミュレータで1つ見ておきます。必要なのは1行です。
pip install pybullet
PyBullet(物理シミュレータのPython版。ロボットを実機なしで動かせる)に、Husky という4輪の移動ロボットが最初から入っています。4輪とも車体に固定されていて、向きを変えません。つまり曲がるにはタイヤが横向きに滑る必要があります。
同じ指令(左 0.6 / 右 1.0 m/s)を与えます。
import numpy as np
import pybullet as p
import pybullet_data
R_WHEEL = 0.1651 # Husky の車輪半径 [m]
LEFT, RIGHT = [2, 4], [3, 5] # 前左・後左 / 前右・後右
p.connect(p.DIRECT) # 画面を出さずに動かす
p.setAdditionalSearchPath(pybullet_data.getDataPath())
p.setGravity(0, 0, -9.81)
p.loadURDF("plane.urdf")
robot = p.loadURDF("husky/husky.urdf", [0, 0, 0.1])
for j in LEFT: # 車輪の角速度 = 周速 / 車輪半径
p.setJointMotorControl2(robot, j, p.VELOCITY_CONTROL, targetVelocity=0.6 / R_WHEEL, force=500)
for j in RIGHT:
p.setJointMotorControl2(robot, j, p.VELOCITY_CONTROL, targetVelocity=1.0 / R_WHEEL, force=500)
for i in range(int(4.0 * 240)): # PyBullet の既定は 240 Hz
p.stepSimulation()
if i % 480 == 0:
print(f"t={i / 240:.1f}s 旋回角速度 {p.getBaseVelocity(robot)[1][2]:+.4f} rad/s")
print("車輪の実速度", [f"{p.getJointState(robot, j)[1]:.3f}" for j in LEFT + RIGHT])
print("4秒後の位置", [f"{v:+.4f}" for v in p.getBasePositionAndOrientation(robot)[0][:2]])
t=0.0s 旋回角速度 +0.0000 rad/s
t=2.0s 旋回角速度 +0.2741 rad/s
車輪の実速度 ['3.634', '3.634', '6.057', '6.057']
4秒後の位置 ['+2.5737', '+1.3522']
指令は 0.6/0.1651 = 3.634 と 1.0/0.1651 = 6.057 rad/s で、4輪とも指令どおりに回っています。 それでも車体の旋回角速度は、①の式が予測する 0.7207 rad/s に対して 0.2741 でした。4 秒後の位置は予測 (0.2840, 2.1831) に対して実測 (2.5737, 1.3522)、差は 2.436 m です。
🔴 ここで「係数を1つ直せば合う」と考えたくなりますが、合いません。8秒まで測ると旋回角速度は 0.14〜0.39 のあいだを動き続け、定常状態(時間が経っても値が落ち着くこと)になりません。トレッド $L$ を逆算すると 2〜4 秒の区間では実寸 0.555 m の 1.85 倍、4〜8 秒では 4.05 倍で、時間帯によって値が変わります。 間違っているのは係数ではなく、式の形(横滑りを含んでいないこと)です。
差動二輪の式が素直に効くのは、左右2輪+自由に向きを変えるキャスターという構成です。4輪固定のロボットに使うときは、この記事の式は出発点にしかなりません。
刻み幅:③を近似で済ませると 3.2 m で 79 mm ずれる
ここからが実装の話です。②の微分方程式を見て、閉じた式を導かずに「いま向いている方向へ $v\Delta t$ 進んで、向きを $\omega\Delta t$ 変える」と素直に書くと、こうなります。
def step_euler(pose: np.ndarray, v: float, omega: float, dt: float) -> np.ndarray:
"""1ステップぶん、直線で近似して進める。"""
x, y, th = pose
return np.array([x + v * np.cos(th) * dt,
y + v * np.sin(th) * dt,
th + omega * dt])
この書き方をオイラー法(Euler method:いまの速度のまま直線で進んだことにして、少しずつ足していく数値積分の手法)と呼びます。制御周期ごとに車輪速を読む実機のコードは、自然にこの形になります。
問題は、1ステップのあいだ車体はまっすぐ進んでいないことです。③で見たとおり本当の軌跡は円弧なのに、step_euler はそのステップの最初の向きのまま直線で進めます。円の接線を進むので、必ず円の外側へ出ます。

図6:接線をまっすぐ進むと、円弧の外側に出る。次の歩はその外側の点から始まる(見やすくするため、1歩で31度回る条件で描いています)
膨らみは1歩ぶんならわずかですが、次の歩はその外側の点から始まります。ずれは戻らず、積もります。
図7:誤差が戻らない理由。各ステップの膨らみは小さいが、開始点そのものがずれていく
同じ条件(左 0.6 / 右 1.0 m/s で 4 秒)で、2つの書き方を比べます。
truth = step_exact(np.zeros(3), v, omega, 4.0) # 閉じた式で一発=真値
pose_euler = integrate(step_euler, v, omega, 0.1, 40) # 直線近似で 10 Hz
pose_exact = integrate(step_exact, v, omega, 0.1, 40) # 円弧解で 10 Hz
print(f"真値 x={truth[0]:.6f} y={truth[1]:.6f}")
print(f"直線近似 x={pose_euler[0]:.6f} y={pose_euler[1]:.6f}"
f" 誤差 {np.linalg.norm(pose_euler[:2] - truth[:2]) * 1000:.1f} mm")
print(f"円弧で解く x={pose_exact[0]:.6f} y={pose_exact[1]:.6f}"
f" 誤差 {np.linalg.norm(pose_exact[:2] - truth[:2]):.2e} m")
真値 x=0.283975 y=2.183060
直線近似 x=0.362521 y=2.171882 誤差 79.3 mm
円弧で解く x=0.283975 y=2.183060 誤差 1.83e-15 m

図8:同じ指令を 0.5 秒刻みと 0.1 秒刻みで積分した軌跡。灰色の太線が本当の軌跡(円弧)で、赤が直線近似。赤は円弧の外側へ膨らんでいく

図9:刻み幅と 4 秒後の誤差。縦横とも桁で目盛を取った両対数グラフ(10倍ごとに等間隔)。赤が直線近似、青が円弧解で、両者は11桁以上離れている
| 刻み幅 | 直線近似 | 円弧で解く |
|---|---|---|
| 0.5 s(2 Hz) | 397.4 mm | 7.6e-16 m |
| 0.1 s(10 Hz) | 79.3 mm | 1.8e-15 m |
| 0.01 s(100 Hz) | 7.9 mm | 1.8e-14 m |
表1:円弧解の誤差は 1e-14 m 台。倍精度浮動小数点の丸め誤差そのもので、事実上ゼロ
赤い線は傾き1の直線に重なります。刻みを半分にすると誤差も半分、それ以上は良くなりません(刻み幅に正比例するこの性質を一次精度と呼びます)。10 Hz を 100 Hz にすれば 79.3 mm は 7.9 mm になりますが、計算量10倍に対して誤差は1/10です。 1 mm を切りたければさらに10倍が要り、そこまでやってもゼロにはなりません。青い線は刻み幅をどう変えても 1e-14 m 台のままです。
図9で青い線がわずかに右下がりなのは、歩数が増えるほど丸め誤差が積もるからです。1e-14 m は 0.00000000001 mm なので、実用上はどの点も同じくゼロと読んで構いません。
直線近似で足りるのはどんなときか
いつでも円弧解にすべきかというと、そうとも限りません。まっすぐ走るだけなら、直線近似の誤差はゼロです($\omega = 0$ のとき両者は同じ式になります)。実測しても 2.2e-15 m でした。
90 度曲がり切るまでに直線近似が出す誤差を、速度と制御周期で表にしました。この表だけ手元に置いておけば、次に迷ったときに引き直せます。
| 2 Hz | 5 Hz | 10 Hz | 20 Hz | 50 Hz | 100 Hz | |
|---|---|---|---|---|---|---|
| v = 0.2 m/s | 66.1 mm | 28.5 mm | 14.2 mm | 7.1 mm | 2.8 mm | 1.4 mm |
| v = 0.5 m/s | 165.1 mm | 71.2 mm | 35.6 mm | 17.8 mm | 7.1 mm | 3.5 mm |
| v = 1.0 m/s | 330.3 mm | 142.4 mm | 71.2 mm | 35.6 mm | 14.1 mm | 7.1 mm |
| v = 2.0 m/s | 660.6 mm | 284.8 mm | 142.4 mm | 71.2 mm | 28.3 mm | 14.1 mm |
表2:90 度曲がり終えた時点の位置誤差。速度にも刻み幅にも比例している
判断の目安はこうなります。
- まっすぐしか走らせない → 直線近似で構いません。誤差は原理的にゼロです
- 曲がるが、許容誤差が数 cm ある → 表2で自分の速度と制御周期を引いてください
- 曲がる/許容誤差が mm 単位/制御周期を上げたくない → 円弧解を使ってください
実装コストは step_exact と step_euler で変わりません。ここで悩む価値があるのは、既存コードを直すかどうかを判断するときだけです。
曲がる角度:誤差を決めているのは旋回の速さではなかった
刻み幅の次に疑うのは、たいてい旋回の速さです。速く回るほど1歩で向きが大きく変わるので、直線で近似したときのズレも大きくなるはずです。私もそう思って振りました。ならなかった。
総回転角を 90 度に固定したまま、旋回角速度 $\omega$ を 0.09 から 2.88 rad/s まで32倍振ります。速く回るぶん所要時間は短くなり、走る距離は 14.00 m から 0.40 m まで変わります。

図10:旋回角速度を32倍振っても、90 度曲がり終えた時点の誤差はほとんど動かない
| $\omega$ [rad/s] | 0.09 | 0.18 | 0.36 | 0.72 | 1.44 | 2.88 |
|---|---|---|---|---|---|---|
| 走った距離 | 14.00 m | 6.96 m | 3.52 m | 1.76 m | 0.88 m | 0.40 m |
| 誤差 | 56.7 mm | 56.4 mm | 56.9 mm | 56.9 mm | 57.0 mm | 52.8 mm |
表3:走る距離が35倍違うのに、誤差は 52.8〜57.0 mm の範囲に収まる
理由は式を並べると分かりました。1歩のあいだに向きは $\omega \Delta t$ だけ変わり、その半分ぶんが接線からのズレになります。進む距離 $v \Delta t$ に掛けて、1歩あたりの誤差はおよそ $\tfrac{1}{2} v \omega \Delta t^2$ です。総回転角 $\Delta\theta$ を曲がるのに必要な歩数は $\Delta\theta / (\omega \Delta t)$ なので、掛けると($\propto$ は「に比例する」の記号です)、
$$\text{誤差} \propto \frac{1}{2} v \omega \Delta t^2 \times \frac{\Delta\theta}{\omega \Delta t} = \frac{1}{2} v , \Delta t , \Delta\theta$$
$\omega$ が約分されて消えます。 残るのは前進速度・刻み幅・総回転角の3つだけです。実測でも、誤差を $v , \Delta t , \Delta\theta$ で割った値は 0.449 でほぼ一定でした(式が示す 0.5 に近い値です)。
| 総回転角(v=0.8 m/s・10 Hz) | 10 度 | 30 度 | 90 度 | 180 度 |
|---|---|---|---|---|
| 誤差 | 5.8 mm | 19.9 mm | 56.9 mm | 80.0 mm |
表4:総回転角には比例する。ただし 180 度では比例から外れる
⚠️ 比例が成り立つのは、おおむね 90 度くらいまでです。180 度では 113.8 mm(56.9の2倍)にならず 80.0 mm でした。半周まで回ると、前半のずれと後半のずれが向かい合って一部打ち消し合うためです。1周させるとさらに顕著で、誤差は 0.77 mm まで落ちます。「1周まわして戻ってきたら合っていた」は、精度が良いのではなく偶然打ち消し合っただけです。
だから私は、制御周期を上げる方向で直すのをやめました。周期を10倍にしても誤差は1/10にしかならず、曲がる量そのものは減らせません。効いたのは③の閉じた式を使うことだけでした。
まとめ
- 差動二輪の順運動学は3段です。①$v = (v_r + v_l)/2$、$\omega = (v_r - v_l)/L$ ②$\dot x = v\cos\theta,\ \dot y = v\sin\theta,\ \dot\theta = \omega$ ③②を積分して閉じた式にする
- $v$ と $\omega$ が一定なら軌跡は円弧なので、③は近似せずに $x = x_0 + R[\sin(\theta_0 + \omega t) - \sin\theta_0]$ の形で解けます。刻み幅を変えても答えが変わりません
- ③を直線で近似すると、3.2 m で 79.3 mm ずれます。制御周期を10倍にしても1/10までしか落ちません
- 誤差を決めるのは前進速度・刻み幅・総回転角の3つで、$\omega$ は式から消えます(32倍振っても 52.8〜57.0 mm)。ただし 90 度を超えると打ち消し合いが始まるので、「1周して合っていた」を精度の証拠にしてはいけません
- 車輪が横滑りする構成(4輪固定など)では、この式は係数調整では合いません
まずこれだけやってみてください。 「動かしてみる」のコードで step_exact を step_euler に差し替えて実行し、40 歩で解く の値が 1 歩の値からどれだけずれるかを自分の画面で見る。刻み幅を 0.1 から 0.5 に変えると、そのずれが 79.3 mm から 397.4 mm へ増えます。
次回は、この逆をやります。「行きたい速度と旋回」から左右の車輪速を逆算する話です。今回の式は「車輪をこう回したらどこへ行くか」しか答えないので、行き先を先に決めて走らせることがまだできません。 そこを埋めます。この記事をストックしておくと、続きを読むときの目次になります。