読者です 読者をやめる 読者になる 読者になる

チリペヂィア

リンクフリー。サンプルコードなどは関連記事内でライセンスについて明示されない限り商用利用なども自由に行って構いませんが、自己責任でお願いします。またこれら日記内容の著作権自体は放棄していません。引用部分については引用元の権利に従ってください。

3Dベクトル任意軸回転での角度差(回転方向による符号つき)

Unityで任意軸回転する時の角度差を求める関数が見当たらないなーと思って、よくよく考えてみると、任意軸回転した座標って、同じ軸法線平面にある点同士でしか回転結果と一致しませんよね。3次元なんだから、ひねりの関係も含めて一般化するにはハードル高いという事に気付いた頃には後の祭り、ほとんど式が解けていました…しょうがない、続行で(あるある)。

ひねりの位置関係を考えて、回転先のベクトルは、回転元ベクトルと同じ平面(回転軸を法線とした平面:図中horizontalPlane)に射影します。回転方向の判定は、中心点/回転軸/前向き(回転元)のベクトルを使い、左右を分離する垂直平面(:図中verticalPlane)を定義して、射影済みベクトルと垂直平面の法線と内積をとってその符号を使う、って感じの作戦で行きます。

こんな感じです。(重大な修正20121016*1

float AxisAngleOnAxisPlane( Vector3 origin, Vector3 fromDirection, Vector3 toDirection, Vector3 axis ) {
	fromDirection.Normalize();
	axis.Normalize();
	Vector3 toDirectionProjected = toDirection - axis * Vector3.Dot(axis,toDirection);
	toDirectionProjected.Normalize();
	return Mathf.Acos(Mathf.Clamp(Vector3.Dot(fromDirection,toDirectionProjected),-1f,1f))) *
		(Vector3.Dot(Vector3.Cross(axis,fromDirection), toDirectionProjected) < 0f ? -Mathf.Rad2Deg : Mathf.Rad2Deg);
}

計算が簡単になるようにガッツリギュギュッと埋め込んじゃってるので何が何やら分かりませんが、計算のイメージをもう一度簡単にまとめると以下のようになっています。

  1. 軸を法線とした水平面に、回転先ベクトルを射影してねじれを無くします。
  2. 回転元ベクトルを前方向として「軸(法線)×前方向」で外積ベクトルを計算し、それを法線にした「左右を分離する垂直平面」を定義。
  3. 回転元ベクトルから射影済み回転先ベクトルへの角度(0-180度)を出しますが、その時、射影済み回転先ベクトルと左右分離用の垂直平面の法線で内積をとって、射影済み回転先ベクトルが左右分離平面の表側か裏側にあるかを判定して右回りか左回りか判定し、算出しておいた0-180度に対して符号として反映します。

*1:Acos()に渡す内積の値にClamp()処理を追加しました、って、アレ?単位ベクトル同士の内積って-1〜1じゃないの?って思ってたんですが、実際に使ってみると、裏側(-90〜90の範囲外)なのに表側(-90〜90の範囲内)の値を返すケースが確認されました。それでVector3.Angle()の実装に倣ってClamp()処理を追加したら上手く動いた、という次第でス。多分floatの誤差?…最初、内積の理解が足りない???と思ったんですが、向きが逆転するといっても「真裏のはずなのに何故かわずかに表側?ほとんどゼロ…」みたいな現象だったので「Dot計算時のfloatの計算誤差でわずかに-1〜1をオーバー、それでAcosによって一週分が切り捨てられて表側と誤審」って説が一番怪しいんじゃねーかなーと思っています。