Affine行列の適用順序

しょっちゅう忘れてしまうので…

基本
CGではAffine行列は3x3の行列で
a b 0
c d 0
tx ty 1
で表される。
この行列は元データである個々の座標に対して後ろから掛かり、適用後の座標を出す。
これは行列の中の移動を表すtx,tyの位置から明らか。

前から掛けると(行列)(縦表現座標)となり、結果は(ax+by,cx+dy,tx+ty+1)。これは求めたい結果ではない。
後ろから掛けると(横表現座標)(行列)となり、結果は(ax+cy+tx,bx+dy+ty,1)。こちらが求めたい結果。
ちなみに座標は(x,y,1)となる。最後の1は必要。

ということで
個々の座標データに行列を適用させるというのが行列の使われ方
座標に対して後ろから掛かる
という前程で進める。


複数の行列の適用順序
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformScale(t, 0.5, 2.0);
t = CGAffineTransformTranslate(t, 100.0, 100.0);
t = CGAffineTransformRotate(t, 1.0);

とプログラムを書いた場合、最終的な行列tは
(Rotate演算行列)(Translate演算行列)(Scale演算行列)
の結果となる。
つまり、行列追加関数は既存の行列の前に新しい行列を挿入する。
これが座標に後ろから掛かるので、個々の座標データにRotate演算、Translate演算、Scale演算の順に実行されたことと同じ結果(実際には先に適用行列の積が計算されていて、その結果を座標データに適用すると思う)。この順番は関数が書かれている順番と反対。

上のはCGAffineTransformの場合だが、
drawRect内でCGContextRefに行列を与える関数
CGContextScaleCTM(context, 0.5, 2.0);
CGContextTranslateCTM(context, 100,100);
CGContextRotateCTM(context, 1.0);
も同様の法則。

また、view.layerに与える行列の
CATransform3DScale(t, 0.5, 2.0, 1.0);
CATransform3DTranslate(t, 100.0, 100.0,1.0);
CATransform3DRotate(t, 1.0, 0.0, 0.0, 1.0);
も同様。


考察
後ろから掛けるのは行列の並び順と適用順を同じにするメリットがある。
追加する関数によって追加する場所が、最後じゃなくて最初に割り込んでくる理由は謎。

で、なぜ最初に割り込んでくるかですが、こちらにうまい説明がありました。
http://ameblo.jp/xcc/entry-10218023309.html
つまり、「行列によって原点と単位ベクトルの変更を行う」という考え方をすると関数の実行順序と一致するということです。
これらの関数を使う時の考え方は、「個々の座標データに適用される行列を指定する」というよりも「原点と単位ベクトルの変更を行う」という方がイメージしやすいから、それに合わせてこの仕様だと考えると納得出来ます。上の方で各座標データに適用するという考え方をしましたが、実際の処理でも各座標データに行列を適用させて処理していると予想しますので、この考え方は否定しません。

ただし注意点があります。
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformScale(t, 1.0, 2.0);
t = CGAffineTransformRotate(t, -M_PI/4);
の順で関数を書いた時、図のように解釈します。赤い線がいわゆる単位ベクトルです。

初め


CGAffineTransformScale(t, 1.0, 2.0)適用後


次にRotateを適用しますが、現在の座標(赤色)で回転すると考えます。ここがかなり独特です。
CGAffineTransformRotate(t, -M_PI/4)適用後

このようになります。赤のX軸とY軸が直角ではありません。
(1,0)と(0,1)がRotateとScale(ここではRotate、Scaleの順)の行列を適用してどこに行くかを考えるとわかります。

さらなる疑問
Appleの資料は読んでも理解できなかったのでもう一度検証してみる。


間違いありましたら指摘して下さい。

コメント

このブログの人気の投稿

Swiftのコンパイルエラー寄せ集め

Swift2.2からSwift3.0への変換を行ってみて

AVAudioSession細かいことまとめ(late 2014)