syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

2次元の同次変換行列

(これは2012-08-04に書いた故OCNブログの記事を移植したものです)

MSDNにあるDirect2Dの付録ページ(英語版はこちら)に書いてある、2次元の回転の3x3同次変換行列の数式が2012年8月現時点で間違っているので、とりあえず正しいものをC言語構造体っぽい形式で書いておきます。

・原点を中心とする回転行列:

R_0 = {
  { cosθ, sinθ, 0 },
  { -sinθ, cosθ, 0 },
  { 0, 0, 1 },
};

・任意の点 (x, y) を中心とする回転行列:

R_1 = {
  { cosθ, sinθ, 0 },
  { -sinθ, cosθ, 0 },
  { x*(1-cosθ) + y*sinθ, -x*sinθ + y*(1-cosθ), 1 },
};

ちなみに上記R_1は、

  1. 点(x,y)を原点にもっていく平行移動行列T(-x,-y)
  2. 原点中心にθ回転する行列R(θ)
  3. 平行移動をキャンセルする行列T(x,y)

の積で表すことができます。同次変換を理解しているならば、加法定理云々で証明するよりも行列積で考えたほうが分かりやすいです。

同様にして、スケーリング行列も同次変換で表すことができます。

  1. 点(x,y)を原点にもっていく平行移動行列T(-x,-y)
  2. 原点中心に拡大縮小する行列S(sx,sy)
  3. 平行移動をキャンセルする行列T(x,y)

・任意の点 (x, y) を中心とするスケーリング行列:

S_1 = {
  { sx, 0, 0 },
  { 0, sy, 0 },
  { -x * sx + x, -y * sy + y, 1 },
};

仕事でWindows XP/Vista/7向けに、Vista/7での通常の描画時はDirect2D、印刷時はGDI+、XPでは常にGDI+、のように自動で切り替わるC++用の簡易ラッパーを書いているんですが、GDI+の行列Gdiplus::MatrixがムダにPImpl的な作りになっていたり、D2Dの行列演算ヘルパーの一部が非インライン実装になってるせいで、D2D/GDI+で共通に使える行列型や同次変換行列の生成処理は自前で定義しないと共通化できません(XPだとD2DヘルパーDLLは使えない)。で、回転行列はどうだったかな、と調べていて見つけたMSDNライブラリの間違いに気付いたのが始まり。

XPが消えて、さらにWindows 8用Direct2D 1.1(印刷処理をサポート)がVista/7にバックポートされれば、こんなつまらんラッパーを書く必要もなくなるんですが……

GDI+同等機能をD2Dで実装するとき、Graphics::DrawArc(), DrawPie(), FillPie()あたりに少々手こずりました。GDI+は弧を描画するとき、楕円の境界ボックスと偏角(時計回り)を直接指定できるんですが、D2Dは旧GDIに近く、楕円の境界ボックスと端点を指定して弧のジオメトリを生成することになるので、偏角を使う場合は自前で端点を計算しないといけません。計算式自体は大して難しくもないのですが(連立2次方程式を解くだけ)、また時間があるときに紹介することにします。

2018-09-25追記:
数式の間違いをこれまでに何度かフィードバックで指摘しましたが、いまだ改善されていません。