Location : Home > Languages > Perl > Package
Title : Math::Quaternion
Toolbox Logo

名称

 Math::Quaternion - 4元数表現の Perl クラス


概要

use Math::Quaternion qw(slerp);
my $q = Math::Quaternion->new;  # 新規単位4元数の生成

# 軸 (0,1,0) に関する回転
my $q2 = Math::Quaternion->new({axis=>[0,1,0],angle=>0.1});
my @v = (1,2,3); # ベクトル
my @vrotated = $q2->rotate_vector(@v); # Rotate @v about (0,1,0).

my $q3 = Math::Quaternion::rotation(0.7,2,1,4); # 異なる回転
my $q4 = slerp($q2,$q3,0.5);                    # 補間された回転
my @vinterp = $q4->rotate_vector(@v);

説明

 本パッケージは4元数(quaternion)を生成し、演算することができる。4元数とは複素数の一般化として開発された数学的オブジェクトであり、4つの実数の配列により表現され、よく3次元の回転を表現するために用いられる。
 4元数に関する詳しい数学的説明は、例えば http://mathworld.wolfram.com/Quaternion.html を見よ。
 4元数は複素数やベクトルのように加算・積算・スケールが可能である。乗算は可能であるが、可換ではない。すなわち2つの4元数 $q1 と $q2 があれば、一般に $q1*$q2 != $q2*$q1 である。これは必ずしも可換でない、回転を表現するために用いられることと関係がある。
 回転を表現できればよく、内部的な≦表現二興味がなければ以下のことにだけ気をつけておけばよい:全ての4元数は、ベクトルの長さにあたる「ノルム」と呼ばれる量を持つ。ノルムが1であれば「単位4元数」と呼ばれる。回転を表す全ての4元数は単位4元数である。

 引数なしで new() を呼び出すと、回転を表さない単位4元数を生成する。

$q = Math::Quaternion->new;

 所与の軸に関して所与の角度(ラジアン)の回転を表す4元数を生成することもできる。

$qrot = Math::Quaternion->new({ axis => 0.1, angle => [ 2,3,4]});

 $q1 と $q2 があれば2種類の回転を考えることができるが、$q2 による回転の後に $q1 による回転を示したければ以下のように書く。

$q3 = $q2 * $q1;   # $q2 による回転の後に $q1 による回転

 これは順番を入れ替えた $q1 * $q2 とは同じものではないことに注意しよう。
 もし4元数による計算を多数回繰り返したら、結果は数値的不正確さのために単位4元数とは異なる値になるだろう。もし単位長を保持したければ以下のようにするとよい。

$unitquat = $anyquat->normalize;

 回転を示す4元数があれば、対応する 3x3 行列がある。これは以下のようにして取得する。

@matrix = $q->matrix3x3;

 同様に、OpenGL に渡すために 4x4 行列を生成することもできる。

@glmatrix = $q->matrix4x4;

 方向を示すベクトルがあれば、4元数 $q によってベクトルを回転させることもできる。

my @vector = (0,0,1);  # Z 方向を示すベクトル

my @newvec = $q->rotate_vector(@vector); # 新しい方向

 カメラの方向を示すために4元数を用いるいためには、2つの4元数が必要である。1つは始点を示し、もう1つは終点を示す。その間の方向を示す全ての4元数を取得したければ slerp() ルーチンを用いて始点から終点までスムーズに移動することを許すことになる。

use Math::Quaternion qw(slerp);

my ($qstart, $qend) = ... ;

# 始点から終点まで9点を $tween で設定

for my $t (1..9) {
   my $tween = slerp($qstart,$qend,0.1*$t); 
   ...
}

メソッド

new

my $q = Math::Quaternion->new;          # 新しい単位4元数を生成
my $q2 = Math::Quaternion->new(1,2,3,4);# 特定の4元数を生成
my $q3 = Math::Quaternion->new($q2);    # 既存の4元数をコピー
my $q4 = Math::Quaternion->new(5.6);    # 4元数 (5.6,0,0,0) を生成
my $q5 = Math::Quaternion->new(7,8,9);  # 4元数 (0,7,8,9) を生成

my $q6 = Math::Quaternion->new({
      axis => [ 1,2,3],         # ベクトル (1,2,3) の周りの
      angle => 0.2,             # 0.2 ラジアンの回転に対応する4元数を生成
});

my $q7 = Math::Quaternion->new({
      'v1' => [ 0,1,2],         # ベクトル (-1,2,0) の上への
      'v2' => [ -1,2,0],        # ベクトル (0,1,2) の回転に対応する4元数を生成
});

 パラメータが指定されなければ単位4元数を返す。1つの非参照パラメータが渡されればスカラ4元数を返す。1つのパラメータが与えられ、それが4元数または4つの数の配列への参照であるならば対応する4元数を返す。3つのパラメータが与えられればベクトル4元数を返す。4つのパラメータが与えられれば対応する4元数を返す。
 回転を示す4元数は、軸と回転角へのハッシュ参照を渡すか、始点と終点を示す2つのベクトルを特定するかで生成することができる。後者の方法は回転角を無視することで2つのベクトル間の最短の経路を取ることに注意すること。

unit

 単位4元数を返す。

my $u = Math::Quaternion->unit; # 4元数 (1,0,0,0) を返す。

conjugate

 引数の共役4元数を返す。

my $q = Math::Quaternion->new(1,2,3,4);
my $p = $q->conjugate;              # (1,-2,-3,-4)

inverse

 引数の逆4元数を返す。

my $q = Math::Quaternion->new(1,2,3,4);
my $qi = $q->inverse;

normalize

 引数で渡された4元数のノルムを1にして返す。

my $q = Math::Quaternion->new(1,2,3,4);
my $qn = $q->normalize;

modulus

 4元数にその共役4元数を乗じて得られたスカラの平方根で定義された絶対値を返す。

my $q = Math::Quaternion->new(1,2,3,4);
print $q->modulus;

isreal

 与えられた4元数が実数であれば 1 を返し、そうでなければ 0 を返す。

my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion->new(5,0,0,0);
print $q1->isreal; # 1;
print $q2->isreal; # 0;

multiply

 2つの4元数に対し乗算を行う。一方の引数がスカラであればスカラ乗算を行う。

my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion->new(5,6,7,8);
my $q3 = Math::Quaternion::multiply($q1,$q2);         # (-60 12 30 24)
my $q4 = Math::Quaternion::multiply($q1,$q1->inverse); # (1 0 0 0) 

dot

 2つの4元数の内積を返す。

my $q1=Math::Quaternion->new(1,2,3,4);
my $q2=Math::Quaternion->new(2,4,5,6);
my $q3 = Math::Quaternion::dot($q1,$q2);

plus

 2つの引数の和算を行う。

my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion->new(5,6,7,8);
my $q3 = Math::Quaternion::plus($q1,$q2);         # (6 8 10 12)

minus

 2つの引数の減算を行う。

my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion->new(5,6,7,8);
my $q3 = Math::Quaternion::minus($q1,$q2);         # (-4 -4 -4 -4)

power

 4元数のスカラまたは4元数べき乗を計算する。

my $q1 = Math::Quaternion->new(1,2,3,4);
my $q2 = Math::Quaternion::power($q1,4); # ( 668 -224 -336 -448 )
my $q3 = $q1->power(4);                  # ( 668 -224 -336 -448 )
my $q4 = $q1**(-1);                      # $q1->inverse と同じ

use Math::Trig;
my $q5 = exp(1)**( Math::Quaternion->new(pi,0,0) ); # おおよそ (-1 0 0 0)

negate

 与えられた4元数の符号を反転させる。

my $q = Math::Quaternion->new(1,2,3,4);
my $q1 = $q->negate;             # (-1,-2,-3,-4)

squarednorm

 引数のノルムの平方を返す。

my $q1 = Math::Quaternion->new(1,2,3,4);
my $sn = $q1->squarednorm;             # 30

scale

 2つの引数のスカラ乗算を行う。

my $q = Math::Quaternion->new(1,2,3,4);
my $qq = Math::Quaternion::scale($q,2);   # ( 2 4 6 8)
my $qqq= $q->scale(3);                    # ( 3 6 9 12 )

rotation

 回転に対応する4元数を生成する。3つの引数が与えられた場合には3つの軸ベクトルのなす角として翻訳される。

use Math::Trig;            # pi を定義。 my $theta = pi/2;
# 回転角は my $rotquat  =
Math::Quaternion::rotation($theta,0,0,1);

# $rotquat は Z 軸周りの 90 °の回転を示す

my ($x,$y,$z) = (1,0,0);	# X 軸方向の単位ベクトル
my ($xx,$yy,$zz) = $rotquat->rotate_vector($x,$y,$z);

# ($xx,$yy,$zz) は浮動小数点の誤差の範囲で ( 0, 1, 0) 

 rotation() はスカラ角とベクトルへの参照(どの順でもよい)を取り、対応する回転を示す4元数を生成する。

my @axis = (0,0,1);    # Z 軸についての回転
$theta = pi/2;
$rotquat = Math::Quaternion::rotation($theta,\@axis);

 rotation() への引数がともに参照であれば2つのベクトルと解釈され、1つめのベクトルの2つめのベクトルの上への回転に対応する4元数を返す。

my @startvec = (0,1,0);  # 北を指すベクトル
my @endvec   = (-1,0,0); # 西を指すベクトルt
$rotquat = Math::Quaternion::rotation(\@startvec,\@endvec);

my @newvec = $rotquat->rotate_vector(@startvec); # @endvec と同じ

rotation_angle

 4元数引数により示された回転角を返す。

my $q = Math::Quaternion::rotation(0.1,2,3,4);
my $theta = $q->rotation_angle; # 0.1 を返す。

rotation_axis

 回転を行う軸を示す単位ベクトルを返す。回転は4元数で示される。

my $q = Math::Quaternion::rotation(0.1,1,1,0);
my @v = $q->rotation_axis; # (0.5*sqrt(2),0.5*sqrt(2),0) を返す。

rotate_vector

 回転を示す4元数についてのメソッドとして呼び出されたとき、この4元数はベクトル引数に関する回転のために用いられる。

use Math::Trig;                     # pi の定義

my $theta = pi/2;                   # 90°の回転

my $rotquat = Math::Quaternion::rotation($theta,0,0,1); # Z 軸

my ($x,$y,$z) = (1,0,0);	# X 軸方向の単位ベクトル
my ($xx,$yy,$zz) = $rotquat->rotate_vector($x,$y,$z)

# ($xx,$yy,$zz) は浮動小数点の誤差の範囲内で ( 0, 1, 0)

matrix4x4

 1つの引数、回転を示す4元数を取る。対応する回転を示す16要素の OpenGL 行列を返す。

my $rotquat = Math::Quaternion::rotation($theta,@axis); # My rotation.
my @m = $rotquat->matrix4x4;

matrix3x3

 1つの引数、回転を示す4元数を取る。対応する回転を示す9要素の行列を返す。

my $rotquat = Math::Quaternion::rotation($theta,@axis); # My rotation.
my @m = $rotquat->matrix3x3;

matrix4x4andinverse

 matrix4x4 と似ているが、こちらは2つの配列参照のリストを返す。1つめは回転行列への参照であり、2つめはその逆行列への参照である。これはレンダリングする際に有用である。視点に回転行列を乗じ、変換し、逆行列を乗じ、結果として得られる長方形は常に視点に面している。

my $rotquat = Math::Quaternion::rotation($theta,@axis); # My rotation.
my ($matref,$invref) = $rotquat->matrix4x4andinverse;

stringify

 4元数の文字列表現を返す。4元数を文字列で自由に補間できるように '""' 演算子をオーバーロードしている。

my $q = Math::Quaternion->new(1,2,3,4);
print $q->stringify;                # "( 1 2 3 4 )"
print "$q";                         # "( 1 2 3 4 )"

slerp

 2つの4元数と1つのスカラを引数に取る。2つの4元数の間の球面上で線形補間を計算する。引数となる4元数は単位4元数とし、スカラは 0 と 1 の間の値をとる。0は1つめの4元数を示し、1は2つめの4元数を示す。

use Math::Trig;
my @axis = (0,0,1);
my $rq1 = Math::Quaternion::rotation(pi/2,\@axis);   # Z 軸に関する90°の回転
my $rq2 = Math::Quaternion::rotation(pi,\@axis);     # Z 軸に関する180°の回転

my $interp = Math::Quaternion::slerp($rq1,$rq2,0.5); # Z 軸に関する135°の回転

exp

 e^q を計算する。4元数 q は x+uy と書かれる。ただし x は実数、u は単位4元数である。このとき exp(q) == exp(x) * ( cos(y) + u sin(y) ) である。

my $q = Math::Quaternion->new(1,2,3,4);
print Math::Quaternion::exp($q);

log

 引数の対数を返す。負の実数の4元数の対数は、単位ベクトル u に対し (log(-q0),u*pi) の形式の値をとる。この場合 u は (1,0,0) とする。

my $q = Math::Quaternion->new(1,2,3,4);
print Math::Quaternion::log($q);

著者

 Jonathan Chin, <jon-quaternion.pm@earth.li>


謝辞

 Rene Uittenbogaard の有用な示唆に感謝。


参考資料


著作権とライセンス

 Copyright 2003 by Jonathan Chin

 本ライブラリはフリーソフトウェアであり、Perl 本体と同等の条件で修正/再配布してもよい。

Toolbox Logo
Updated : 2008/01/29