概要
数学をちゃんとやっている人からすると割と当たり前なのですが、私が中途半端にしかやっていなくて少し沼ったのでその覚え書きです。
説明
UECppで以下のようなFromToRotion関数(ベクトルAからベクトルBまでの回転)があったとします。しかしこれはうまく回転しません。
// DirA・DirBは単位ベクトルとする
FQuat CalcFromToRotion(FVector DirA, FVector DirB)
{
float theta = FMath::Acos(FVector::DotProduct(DirA, DirB));
FVector Axis = FVector::CrossProduct(DirA, DirB);
FQuat quat = FQuat(Axis, theta);
return quat;
}
なぜかというとAxisが単位ベクトルになっていないためです。正規化しましょう。関数ではDirAとDirBの外積ベクトルを求めているわけではありますが、2つのベクトルが単位ベクトルだからといって、外積結果も単位ベクトルになるとは限らないためです。
外積ベクトルの大きさは以下のように定義されます。
∣a×b∣=∣a∣∣b∣sinθ
これは外積ベクトルの大きさは2つのベクトルA・Bがなす平行四辺形の面積として表現されるためです。平行四辺形の面積は底面x高さで求まるので、底面が『|a|』で高さが『∣b∣sinθ』だとすると、定義通りの計算式が出てくるわけですね。
この時、ベクトルA・Bが単位ベクトルだとすると『∣a∣∣b∣』は1なので『sinθ』がそのまま大きさになります。θは2つのベクトルのなす角でここでのsinθは0 ~ 1の範囲で変動するので、結果外積ベクトルの大きさは必ずしも1ではないということが発生します。
これらを考慮したうえで先ほどの関数を正しく治すと以下のようになります。Axisをきちんと正規化しているので正しく回転できるようになります。
// DirA・DirBは単位ベクトルとする
FQuat CalcFromToRotion(FVector DirA, FVector DirB)
{
float theta = FMath::Acos(FVector::DotProduct(DirA, DirB));
FVector Axis = FVector::CrossProduct(DirA, DirB);
// 回転軸はきちんと正規化する。正規ベクトル同士の外積結果は長さ1の正規ベクトルにはならないため
Axis.Normalize();
FQuat quat = FQuat(Axis, theta);
return quat;
}
結論
外積ベクトルを回転軸として使う時は必ず正規化しよう!